diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 0b802c12c51ef..6c3606895de0b 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -68,8 +68,6 @@ These are the few directives we have for project maintainers. These are not steadfast rules as maintainers are expected to use their best judgement when operating. -Our team is entirely voluntary, as such we extend our thanks to maintainers, issue managers, and contributors alike for helping keep the project alive. - ### Issue Managers @@ -94,6 +92,10 @@ For more information reference the [Issue Manager Guide](.github/guides/ISSUE_MA +--- + +Our team is entirely voluntary, as such we extend our thanks to maintainers, issue managers, and contributors alike for helping keep the project alive. + ## Development Guides #### Writing readable code diff --git a/.github/alternate_byond_versions.txt b/.github/alternate_byond_versions.txt index 111e573827e8e..e1496d438cdc4 100644 --- a/.github/alternate_byond_versions.txt +++ b/.github/alternate_byond_versions.txt @@ -5,4 +5,4 @@ # Format is version: map # Example: # 500.1337: runtimestation -515.1621: runtimestation +515.1627: runtimestation diff --git a/.github/workflows/auto_changelog.yml b/.github/workflows/auto_changelog.yml index 13b580547011f..f47c2936980fe 100644 --- a/.github/workflows/auto_changelog.yml +++ b/.github/workflows/auto_changelog.yml @@ -14,7 +14,7 @@ jobs: if: github.event.pull_request.merged == true steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Run auto changelog uses: actions/github-script@v6 with: diff --git a/.github/workflows/autowiki.yml b/.github/workflows/autowiki.yml index 91ab12cdb19f6..82d164e0c7c2a 100644 --- a/.github/workflows/autowiki.yml +++ b/.github/workflows/autowiki.yml @@ -20,10 +20,10 @@ jobs: echo "SECRETS_ENABLED=$SECRET_EXISTS" >> $GITHUB_OUTPUT - name: Checkout if: steps.secrets_set.outputs.SECRETS_ENABLED - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Restore BYOND cache if: steps.secrets_set.outputs.SECRETS_ENABLED - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/BYOND key: ${{ runner.os }}-byond-${{ secrets.CACHE_PURGE_KEY }} diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml index f422da8816413..a092a01901d95 100644 --- a/.github/workflows/ci_suite.yml +++ b/.github/workflows/ci_suite.yml @@ -22,44 +22,44 @@ jobs: group: run_linters-${{ github.head_ref || github.run_id }} cancel-in-progress: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Restore SpacemanDMM cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/SpacemanDMM key: ${{ runner.os }}-spacemandmm-${{ hashFiles('dependencies.sh') }} restore-keys: | ${{ runner.os }}-spacemandmm- - name: Restore Yarn cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: tgui/.yarn/cache key: ${{ runner.os }}-yarn-${{ hashFiles('tgui/yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn- - name: Restore Node cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.nvm key: ${{ runner.os }}-node-${{ hashFiles('dependencies.sh') }} restore-keys: | ${{ runner.os }}-node- - name: Restore Bootstrap cache - uses: actions/cache@v3 + 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@v3 + 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@v3 + uses: actions/cache@v4 with: path: tools/icon_cutter/cache key: ${{ runner.os }}-cutter-${{ hashFiles('dependencies.sh') }} @@ -115,6 +115,25 @@ jobs: if: steps.linter-setup.conclusion == 'success' && !cancelled() run: tools/build/build --ci lint tgui-test + odlint: + if: ( !contains(github.event.head_commit.message, '[ci skip]') ) + name: "Lint with OpenDream" + runs-on: ubuntu-22.04 + concurrency: + group: odlint-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + steps: + - uses: actions/checkout@v4 + - uses: robinraju/release-downloader@v1.9 + with: + repository: "OpenDreamProject/OpenDream" + tag: "latest" + fileName: "DMCompiler_linux-x64.tar.gz" + extract: true + - name: Run OpenDream + run: | + ./DMCompiler_linux-x64/DMCompiler tgstation.dme --suppress-unimplemented --define=CIBUILDING + compile_all_maps: if: ( !contains(github.event.head_commit.message, '[ci skip]') ) name: Compile Maps @@ -124,9 +143,9 @@ jobs: group: compile_all_maps-${{ github.head_ref || github.run_id }} cancel-in-progress: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Restore BYOND cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/BYOND key: ${{ runner.os }}-byond @@ -153,7 +172,7 @@ jobs: group: find_all_maps-${{ github.head_ref || github.run_id }} cancel-in-progress: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Find Maps id: map_finder run: | @@ -220,10 +239,12 @@ jobs: name: Compare Screenshot Tests runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + - name: Setup directory + run: mkdir -p artifacts # If we ever add more artifacts, this is going to break, but it'll be obvious. - name: Download screenshot tests - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: path: artifacts - name: ls -R @@ -244,7 +265,7 @@ jobs: echo ${{ github.event.pull_request.number }} > artifacts/screenshot_comparisons/pull_request_number.txt - name: Upload bad screenshots if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: bad-screenshots path: artifacts/screenshot_comparisons @@ -258,9 +279,9 @@ jobs: group: test_windows-${{ github.head_ref || github.run_id }} cancel-in-progress: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Restore Yarn cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: tgui/.yarn/cache key: ${{ runner.os }}-yarn-${{ hashFiles('tgui/yarn.lock') }} diff --git a/.github/workflows/codeowner_reviews.yml b/.github/workflows/codeowner_reviews.yml index ed06f9b8a99d7..cffab706d6100 100644 --- a/.github/workflows/codeowner_reviews.yml +++ b/.github/workflows/codeowner_reviews.yml @@ -12,7 +12,7 @@ jobs: steps: # Checks-out your repository under $GITHUB_WORKSPACE, so the job can access it - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 #Parse the Codeowner file on non draft PRs - name: CodeOwnersParser diff --git a/.github/workflows/compile_changelogs.yml b/.github/workflows/compile_changelogs.yml index 64d4968ec88aa..e1b8774905f13 100644 --- a/.github/workflows/compile_changelogs.yml +++ b/.github/workflows/compile_changelogs.yml @@ -31,7 +31,7 @@ jobs: sudo apt-get install dos2unix - name: "Checkout" if: steps.value_holder.outputs.ACTIONS_ENABLED - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 25 persist-credentials: false diff --git a/.github/workflows/docker_publish.yml b/.github/workflows/docker_publish.yml index c9d30e846f93b..6daec1ded1057 100644 --- a/.github/workflows/docker_publish.yml +++ b/.github/workflows/docker_publish.yml @@ -8,7 +8,7 @@ jobs: if: ( !contains(github.event.head_commit.message, '[ci skip]') ) runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build and Publish Docker Image to Registry uses: elgohr/Publish-Docker-Github-Action@v5 diff --git a/.github/workflows/gbp.yml b/.github/workflows/gbp.yml index ef782f398143b..221d0462e257a 100644 --- a/.github/workflows/gbp.yml +++ b/.github/workflows/gbp.yml @@ -16,7 +16,7 @@ jobs: echo "ACTIONS_ENABLED=$SECRET_EXISTS" >> $GITHUB_OUTPUT - name: Checkout if: steps.value_holder.outputs.ACTIONS_ENABLED - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup git if: steps.value_holder.outputs.ACTIONS_ENABLED run: | @@ -24,7 +24,7 @@ jobs: git config --global user.email "<>" - name: Checkout alternate branch if: steps.value_holder.outputs.ACTIONS_ENABLED - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: "gbp-balances" # The branch name path: gbp-balances diff --git a/.github/workflows/gbp_collect.yml b/.github/workflows/gbp_collect.yml index a180cb9b8ef4f..4dd327abecd69 100644 --- a/.github/workflows/gbp_collect.yml +++ b/.github/workflows/gbp_collect.yml @@ -18,7 +18,7 @@ jobs: echo "ACTIONS_ENABLED=$SECRET_EXISTS" >> $GITHUB_OUTPUT - name: Checkout if: steps.value_holder.outputs.ACTIONS_ENABLED - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup git if: steps.value_holder.outputs.ACTIONS_ENABLED run: | @@ -26,7 +26,7 @@ jobs: git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" - name: Checkout alternate branch if: steps.value_holder.outputs.ACTIONS_ENABLED - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: "gbp-balances" # The branch name path: gbp-balances diff --git a/.github/workflows/generate_documentation.yml b/.github/workflows/generate_documentation.yml index b8d0dd372f5e8..2ffef72218384 100644 --- a/.github/workflows/generate_documentation.yml +++ b/.github/workflows/generate_documentation.yml @@ -13,9 +13,9 @@ jobs: runs-on: ubuntu-22.04 concurrency: gen-docs steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/SpacemanDMM key: ${{ runner.os }}-spacemandmm-${{ secrets.CACHE_PURGE_KEY }} diff --git a/.github/workflows/remove_guide_comments.yml b/.github/workflows/remove_guide_comments.yml index d5d405909e211..e3a4ac3feda06 100644 --- a/.github/workflows/remove_guide_comments.yml +++ b/.github/workflows/remove_guide_comments.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Remove guide comments uses: actions/github-script@v6 with: diff --git a/.github/workflows/rerun_flaky_tests.yml b/.github/workflows/rerun_flaky_tests.yml index 24c3ec95197d8..7f498de144308 100644 --- a/.github/workflows/rerun_flaky_tests.yml +++ b/.github/workflows/rerun_flaky_tests.yml @@ -10,7 +10,7 @@ jobs: if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.run_attempt == 1 }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Rerun flaky tests uses: actions/github-script@v6 with: @@ -22,7 +22,7 @@ jobs: if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.run_attempt == 2 }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Report flaky tests uses: actions/github-script@v6 with: diff --git a/.github/workflows/run_integration_tests.yml b/.github/workflows/run_integration_tests.yml index 404119d988060..368430162d394 100644 --- a/.github/workflows/run_integration_tests.yml +++ b/.github/workflows/run_integration_tests.yml @@ -28,9 +28,9 @@ jobs: - 3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Restore BYOND cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/BYOND key: ${{ runner.os }}-byond-${{ secrets.CACHE_PURGE_KEY }} @@ -64,9 +64,9 @@ jobs: bash tools/ci/run_server.sh ${{ inputs.map }} - name: Upload screenshot tests if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: test_artifacts_${{ inputs.map }} + name: test_artifacts_${{ inputs.map }}_${{ inputs.major }}_${{ inputs.minor }} path: data/screenshots_new/ retention-days: 1 - name: Check client Compatibility diff --git a/.github/workflows/show_screenshot_test_results.yml b/.github/workflows/show_screenshot_test_results.yml index f1f1dec2649d0..c61d09fa89057 100644 --- a/.github/workflows/show_screenshot_test_results.yml +++ b/.github/workflows/show_screenshot_test_results.yml @@ -25,7 +25,7 @@ jobs: echo "SECRETS_ENABLED=$SECRET_EXISTS" >> $GITHUB_OUTPUT - name: Checkout if: steps.secrets_set.outputs.SECRETS_ENABLED - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Prepare module if: steps.secrets_set.outputs.SECRETS_ENABLED run: | diff --git a/.github/workflows/test_merge_bot.yml b/.github/workflows/test_merge_bot.yml index 4eb62752c065c..c77e507794413 100644 --- a/.github/workflows/test_merge_bot.yml +++ b/.github/workflows/test_merge_bot.yml @@ -23,7 +23,7 @@ jobs: echo "GET_TEST_MERGES_URL=$SECRET_EXISTS" >> $GITHUB_OUTPUT - name: Checkout if: steps.secrets_set.outputs.GET_TEST_MERGES_URL - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Prepare module if: steps.secrets_set.outputs.GET_TEST_MERGES_URL run: | diff --git a/.github/workflows/tgs_test.yml b/.github/workflows/tgs_test.yml index 6a9316f493a12..bd538307aa3f3 100644 --- a/.github/workflows/tgs_test.yml +++ b/.github/workflows/tgs_test.yml @@ -62,7 +62,7 @@ jobs: dotnet-version: 8.0.x - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Test TGS Integration run: dotnet run -c Release --project tools/tgs_test ${{ github.repository }} /tgs_instances/tgstation ${{ env.TGS_API_PORT }} ${{ github.event.pull_request.head.sha || github.sha }} ${{ secrets.GITHUB_TOKEN }} ${{ env.PR_NUMBER }} diff --git a/.github/workflows/update_tgs_dmapi.yml b/.github/workflows/update_tgs_dmapi.yml index aae81f7e0d87a..15d45b7935f05 100644 --- a/.github/workflows/update_tgs_dmapi.yml +++ b/.github/workflows/update_tgs_dmapi.yml @@ -11,7 +11,7 @@ jobs: name: Update the TGS DMAPI steps: - name: Clone - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Branch run: | diff --git a/.gitignore b/.gitignore index 560dbcd17317a..c70ebf608b965 100644 --- a/.gitignore +++ b/.gitignore @@ -241,3 +241,6 @@ define_sanity_output.txt # ezdb /db/ /config/ezdb.txt + +# Running OpenDream locally +tgstation.json diff --git a/.vscode/settings.json b/.vscode/settings.json index 35e9fafdb333c..d7c1bfc1d1e31 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,6 +13,7 @@ }, "files.eol": "\n", "files.insertFinalNewline": true, + "git.branchProtection": ["master"], "gitlens.advanced.blame.customArguments": ["-w"], "tgstationTestExplorer.project.resultsType": "json", "[javascript]": { diff --git a/__odlint.dm b/__odlint.dm new file mode 100644 index 0000000000000..b7c120514a1d0 --- /dev/null +++ b/__odlint.dm @@ -0,0 +1,10 @@ +// This file is included right at the start of the DME. +// Its purpose is to enable multiple lints (pragmas) that are supported by OpenDream to better validate the codebase +// These are essentially nitpicks the DM compiler should pick up on but doesnt + +#if !defined(SPACEMAN_DMM) && defined(OPENDREAM) +// This is in a separate file as a hack to avoid SpacemanDMM +// evaluating the #pragma lines, even if its outside a block it cares about +// (Also so people can code-own it. Shoutout to AA) +#include "tools/ci/od_lints.dm" +#endif diff --git a/_maps/RandomRuins/IceRuins/icemoon_surface_smoking_room.dmm b/_maps/RandomRuins/IceRuins/icemoon_surface_smoking_room.dmm index 7d422677278e6..faa5cf18ae978 100644 --- a/_maps/RandomRuins/IceRuins/icemoon_surface_smoking_room.dmm +++ b/_maps/RandomRuins/IceRuins/icemoon_surface_smoking_room.dmm @@ -54,6 +54,10 @@ /obj/structure/chair/comfy{ dir = 1 }, +/obj/effect/decal/remains/human/smokey{ + pixel_x = -3; + pixel_y = 9 + }, /turf/open/floor/carpet/blue, /area/ruin/smoking_room/room) "k" = ( @@ -245,11 +249,6 @@ /turf/open/misc/asteroid/snow/icemoon, /area/icemoon/surface/outdoors/nospawn) "R" = ( -/obj/effect/spawner/random/entertainment/cigarette_pack, -/obj/effect/decal/cleanable/ash/large{ - pixel_x = -1; - pixel_y = 5 - }, /obj/structure/showcase/machinery/tv/broken, /turf/open/floor/carpet/blue, /area/ruin/smoking_room/room) @@ -279,17 +278,12 @@ /turf/open/floor/stone, /area/ruin/smoking_room/house) "W" = ( -/obj/effect/spawner/random/entertainment/cigarette_pack, /obj/structure/chair/plastic{ dir = 8 }, /obj/effect/spawner/random/entertainment/cigarette_pack, /obj/effect/decal/cleanable/ash/large, /obj/structure/sign/calendar/directional/east, -/obj/effect/decal/remains/human/smokey{ - pixel_x = -3; - pixel_y = 9 - }, /turf/open/floor/carpet/blue, /area/ruin/smoking_room/room) "X" = ( diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_gas.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_gas.dmm new file mode 100644 index 0000000000000..ef32d3e8ed9c6 --- /dev/null +++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_gas.dmm @@ -0,0 +1,1244 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"aK" = ( +/obj/structure/rack, +/obj/effect/spawner/random/entertainment/cigarette_pack, +/obj/effect/spawner/random/entertainment/cigarette_pack, +/obj/effect/spawner/random/entertainment/cigarette_pack, +/obj/effect/spawner/random/entertainment/lighter, +/obj/effect/spawner/random/entertainment/lighter, +/obj/effect/spawner/random/entertainment/lighter, +/obj/machinery/light/directional/south, +/obj/machinery/airalarm/directional/west, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"bT" = ( +/obj/structure/table/reinforced, +/obj/machinery/atmospherics/pipe/smart/manifold4w/general/hidden, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"ck" = ( +/obj/machinery/duct, +/obj/machinery/atmospherics/pipe/smart/manifold4w/general/hidden, +/turf/open/floor/iron/smooth, +/area/ruin/lizard_gaslava) +"dm" = ( +/obj/machinery/door/airlock/engineering, +/obj/machinery/duct, +/obj/machinery/atmospherics/pipe/smart/manifold4w/general/hidden, +/turf/open/floor/iron/smooth, +/area/ruin/lizard_gaslava) +"fy" = ( +/obj/machinery/light/directional/south, +/obj/structure/table/reinforced, +/obj/item/stack/sheet/mineral/plasma/thirty, +/obj/item/stack/sheet/mineral/plasma/five, +/obj/item/stack/sheet/mineral/plasma/five, +/obj/item/stack/sheet/mineral/plasma/five, +/obj/item/stack/sheet/mineral/plasma/five, +/obj/machinery/light/directional/north, +/turf/open/misc/ashplanet/rocky, +/area/ruin/lizard_gaslava) +"fF" = ( +/obj/effect/turf_decal/arrows{ + dir = 8 + }, +/turf/open/misc/ashplanet/rocky, +/area/ruin/lizard_gaslava) +"fH" = ( +/turf/open/misc/ashplanet/rocky, +/area/ruin/lizard_gaslava) +"fX" = ( +/obj/structure/sink/directional/east{ + has_water_reclaimer = 0 + }, +/turf/open/misc/ashplanet/rocky, +/area/ruin/lizard_gaslava) +"go" = ( +/obj/machinery/power/terminal{ + dir = 1 + }, +/obj/structure/cable, +/turf/open/floor/iron/smooth, +/area/ruin/lizard_gaslava) +"ia" = ( +/obj/effect/spawner/random/structure/billboard/lizardsgas, +/obj/effect/turf_decal/arrows{ + dir = 4 + }, +/turf/open/misc/ashplanet/rocky, +/area/ruin/lizard_gaslava) +"iK" = ( +/obj/structure/cable, +/obj/machinery/power/rtg/advanced, +/obj/effect/turf_decal/bot, +/turf/open/floor/iron/smooth, +/area/ruin/lizard_gaslava) +"jY" = ( +/obj/structure/window/reinforced/spawner/directional/west, +/obj/structure/window/reinforced/spawner/directional/north, +/obj/structure/rack, +/obj/item/reagent_containers/condiment/yoghurt{ + pixel_x = -3; + pixel_y = 1 + }, +/obj/item/reagent_containers/condiment/yoghurt{ + pixel_x = 6; + pixel_y = -1 + }, +/turf/open/floor/iron/freezer, +/area/ruin/lizard_gaslava) +"ks" = ( +/obj/structure/sign/warning/fire/directional/north, +/obj/machinery/duct, +/obj/machinery/atmospherics/pipe/smart/manifold4w/general/hidden, +/obj/machinery/light/small/dim/directional/north, +/turf/open/floor/iron/smooth, +/area/ruin/lizard_gaslava) +"mn" = ( +/obj/structure/reagent_dispensers/plumbed/fuel, +/turf/open/floor/iron/smooth, +/area/ruin/lizard_gaslava) +"mz" = ( +/obj/structure/rack, +/obj/item/food/candy, +/obj/item/food/candy, +/obj/item/food/candy, +/obj/item/food/chocolatebar, +/obj/item/food/chocolatebar{ + pixel_y = 3 + }, +/obj/item/food/chocolatebar{ + pixel_y = 6 + }, +/obj/structure/sign/poster/contraband/hacking_guide/directional/north, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"mG" = ( +/obj/effect/turf_decal/arrows{ + dir = 4 + }, +/turf/open/misc/ashplanet/rocky, +/area/ruin/lizard_gaslava) +"mY" = ( +/obj/effect/spawner/random/structure/billboard/lizardsgas, +/obj/effect/turf_decal/arrows{ + dir = 8 + }, +/turf/open/misc/ashplanet/rocky, +/area/ruin/lizard_gaslava) +"nc" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/machinery/duct, +/obj/machinery/atmospherics/pipe/smart/manifold4w/general/hidden, +/turf/open/floor/iron/smooth, +/area/ruin/lizard_gaslava) +"ng" = ( +/obj/machinery/door/airlock/external/ruin, +/turf/open/floor/plating, +/area/ruin/lizard_gaslava) +"om" = ( +/obj/effect/turf_decal/arrows{ + dir = 1 + }, +/turf/open/misc/ashplanet/rocky, +/area/ruin/lizard_gaslava) +"pp" = ( +/obj/structure/sign/poster/contraband/jumbo_bar/directional/east, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"pJ" = ( +/turf/open/floor/iron/smooth, +/area/ruin/lizard_gaslava) +"rM" = ( +/obj/structure/window/reinforced/spawner/directional/north, +/obj/structure/window/reinforced/spawner/directional/east, +/obj/structure/rack, +/obj/item/food/cheese/mozzarella{ + pixel_y = 5; + pixel_x = -5 + }, +/obj/item/food/cheese/mozzarella{ + pixel_y = -4; + pixel_x = 1 + }, +/obj/item/food/cheese/wheel, +/obj/item/food/cheese/cheese_curds, +/obj/item/food/cheese/curd_cheese, +/turf/open/floor/iron/freezer, +/area/ruin/lizard_gaslava) +"sY" = ( +/obj/machinery/door/airlock/external/ruin, +/obj/machinery/duct, +/obj/machinery/atmospherics/pipe/smart/manifold4w/general/hidden, +/turf/open/floor/plating, +/area/ruin/lizard_gaslava) +"tD" = ( +/obj/structure/rack, +/obj/item/food/chips{ + pixel_x = 7 + }, +/obj/item/food/chips{ + pixel_y = 7; + pixel_x = -4 + }, +/obj/item/food/chips/shrimp{ + pixel_y = 10; + pixel_x = 7 + }, +/obj/item/food/chips/shrimp{ + pixel_y = -4; + pixel_x = -5 + }, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"ub" = ( +/obj/structure/railing{ + dir = 9 + }, +/turf/open/misc/ashplanet/rocky, +/area/ruin/lizard_gaslava) +"uV" = ( +/obj/machinery/atmospherics/components/unary/vent_pump/on{ + dir = 8 + }, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"vt" = ( +/obj/machinery/duct, +/obj/machinery/atmospherics/components/tank/oxygen{ + dir = 4 + }, +/turf/open/floor/iron/smooth, +/area/ruin/lizard_gaslava) +"vH" = ( +/obj/machinery/atmospherics/components/unary/vent_pump/on{ + dir = 1 + }, +/obj/machinery/light/small/dim/directional/south, +/turf/open/floor/iron/smooth, +/area/ruin/lizard_gaslava) +"wl" = ( +/obj/structure/sink/directional/west{ + has_water_reclaimer = 0 + }, +/turf/open/misc/ashplanet/rocky, +/area/ruin/lizard_gaslava) +"xH" = ( +/obj/machinery/light/directional/south, +/turf/open/misc/ashplanet/rocky, +/area/ruin/lizard_gaslava) +"xW" = ( +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"yH" = ( +/obj/structure/rack, +/obj/item/food/sticko/nutty{ + pixel_y = 5; + pixel_x = -5 + }, +/obj/item/food/sticko{ + pixel_y = 2; + pixel_x = 6 + }, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"Ag" = ( +/obj/structure/table, +/obj/item/coffee_cartridge, +/obj/item/coffee_cartridge, +/obj/effect/mapping_helpers/apc/cell_10k, +/obj/effect/mapping_helpers/apc/unlocked, +/obj/machinery/power/apc/auto_name/directional/north, +/obj/structure/cable, +/turf/open/floor/iron/smooth, +/area/ruin/lizard_gaslava) +"Ao" = ( +/obj/structure/sign/poster/contraband/tipper_cream_soda/directional/south, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"Bf" = ( +/mob/living/basic/lizard, +/obj/machinery/atmospherics/components/unary/vent_pump/on{ + dir = 4 + }, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"BH" = ( +/obj/structure/table/reinforced, +/obj/item/food/hotdog{ + pixel_y = 4; + pixel_x = -2 + }, +/obj/item/food/hotdog{ + pixel_y = -3; + pixel_x = 1 + }, +/obj/machinery/atmospherics/pipe/smart/manifold4w/general/hidden, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"BX" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/machinery/light/directional/south, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"DI" = ( +/turf/template_noop, +/area/template_noop) +"Ef" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/machinery/atmospherics/components/unary/vent_pump/on{ + dir = 8 + }, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"Eh" = ( +/obj/structure/table/reinforced, +/obj/item/food/honeybar{ + pixel_y = 5; + pixel_x = -1 + }, +/obj/item/food/honeybar{ + pixel_x = 2; + pixel_y = 2 + }, +/obj/item/food/honeybar{ + pixel_y = 8; + pixel_x = -1 + }, +/obj/item/food/honeybar{ + pixel_x = 2; + pixel_y = 5 + }, +/obj/item/food/granola_bar{ + pixel_y = 9; + pixel_x = -1 + }, +/obj/item/food/granola_bar{ + pixel_y = 6; + pixel_x = 2 + }, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"El" = ( +/obj/structure/window/reinforced/spawner/directional/west, +/obj/structure/rack, +/obj/item/food/popsicle/creamsicle_berry{ + pixel_y = 4; + pixel_x = -1 + }, +/obj/item/food/popsicle/creamsicle_berry{ + pixel_y = 1; + pixel_x = 5 + }, +/obj/item/food/popsicle/creamsicle_orange, +/obj/item/food/popsicle/creamsicle_orange{ + pixel_y = 6; + pixel_x = 3 + }, +/turf/open/floor/iron/freezer, +/area/ruin/lizard_gaslava) +"Es" = ( +/obj/structure/sign/warning/fire/directional/south, +/obj/effect/decal/cleanable/dirt/dust, +/obj/machinery/atmospherics/components/unary/vent_pump/on{ + dir = 1 + }, +/turf/open/floor/iron/smooth, +/area/ruin/lizard_gaslava) +"EA" = ( +/obj/structure/window/reinforced/spawner/directional/east, +/obj/structure/rack, +/obj/item/storage/fancy/egg_box, +/obj/item/storage/fancy/egg_box{ + pixel_y = 9 + }, +/obj/item/storage/fancy/pickles_jar, +/turf/open/floor/iron/freezer, +/area/ruin/lizard_gaslava) +"FA" = ( +/obj/structure/railing{ + dir = 8 + }, +/turf/open/misc/ashplanet/rocky, +/area/ruin/lizard_gaslava) +"FC" = ( +/obj/structure/sign/poster/fluff/lizards_gas_power/directional/west, +/turf/open/floor/iron/smooth, +/area/ruin/lizard_gaslava) +"FL" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/machinery/light/directional/north, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"Gv" = ( +/obj/structure/table/reinforced, +/obj/structure/window/reinforced/spawner/directional/north, +/obj/structure/window/reinforced/spawner/directional/east, +/obj/item/food/sandwich/blt{ + pixel_y = 6; + pixel_x = -2 + }, +/obj/item/food/little_shiro_sandwich{ + pixel_y = 1 + }, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"GK" = ( +/obj/structure/railing{ + dir = 10 + }, +/turf/open/misc/ashplanet/rocky, +/area/ruin/lizard_gaslava) +"HI" = ( +/obj/structure/table/reinforced, +/obj/structure/closet/mini_fridge{ + pixel_y = 5 + }, +/obj/effect/spawner/random/food_or_drink/refreshing_beverage, +/obj/effect/spawner/random/food_or_drink/refreshing_beverage, +/obj/effect/spawner/random/food_or_drink/refreshing_beverage, +/obj/effect/spawner/random/food_or_drink/booze, +/obj/effect/spawner/random/food_or_drink/booze, +/obj/effect/spawner/random/food_or_drink/booze, +/obj/structure/window/reinforced/spawner/directional/north, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"HL" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/machinery/duct, +/obj/machinery/atmospherics/pipe/smart/manifold4w/general/hidden, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"HS" = ( +/obj/structure/window/reinforced/spawner/directional/west, +/obj/structure/window/reinforced/spawner/directional/south, +/obj/structure/rack, +/obj/item/food/cornuto{ + pixel_y = 4; + pixel_x = -3 + }, +/obj/item/food/cornuto{ + pixel_y = -5; + pixel_x = 2 + }, +/obj/item/food/cornuto{ + pixel_y = 5; + pixel_x = 6 + }, +/obj/item/food/cornuto{ + pixel_y = -5; + pixel_x = -3 + }, +/turf/open/floor/iron/freezer, +/area/ruin/lizard_gaslava) +"HW" = ( +/obj/machinery/door/airlock/external/ruin, +/obj/machinery/duct, +/turf/open/floor/plating, +/area/ruin/lizard_gaslava) +"IU" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/machinery/light/directional/south, +/obj/machinery/atmospherics/pipe/smart/manifold4w/general/hidden, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"JX" = ( +/obj/structure/railing, +/turf/open/misc/ashplanet/rocky, +/area/ruin/lizard_gaslava) +"Kl" = ( +/obj/structure/rack, +/obj/item/food/cornchips/random, +/obj/item/food/cornchips/random, +/obj/item/food/cornchips/random, +/obj/item/food/cornchips/random, +/obj/item/food/cornchips/random, +/obj/item/food/cornchips/random, +/obj/machinery/light/directional/north, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"Km" = ( +/obj/structure/table/reinforced, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"KJ" = ( +/obj/structure/railing{ + dir = 1 + }, +/turf/open/misc/ashplanet/rocky, +/area/ruin/lizard_gaslava) +"Mv" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/structure/sign/poster/fluff/lizards_gas_payment/directional/west, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"Nr" = ( +/obj/structure/window/plasma/spawner/directional/west, +/obj/structure/window/spawner/directional/east, +/obj/effect/spawner/random/structure/grille, +/turf/open/floor/plating, +/area/ruin/lizard_gaslava) +"Ov" = ( +/obj/structure/table, +/obj/machinery/coffeemaker{ + pixel_y = 5 + }, +/turf/open/floor/iron/smooth, +/area/ruin/lizard_gaslava) +"PS" = ( +/obj/effect/turf_decal/bot, +/obj/structure/cable, +/obj/machinery/power/rtg/advanced, +/turf/open/floor/iron/smooth, +/area/ruin/lizard_gaslava) +"Qb" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/general/hidden, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"Qt" = ( +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"Rq" = ( +/obj/machinery/computer/security/telescreen/bar{ + pixel_y = -32 + }, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"RA" = ( +/turf/closed/wall, +/area/ruin/lizard_gaslava) +"RK" = ( +/obj/machinery/light/directional/east, +/turf/open/misc/ashplanet/rocky, +/area/ruin/lizard_gaslava) +"SL" = ( +/obj/machinery/duct, +/turf/open/misc/ashplanet/rocky, +/area/ruin/lizard_gaslava) +"SZ" = ( +/obj/machinery/duct, +/obj/machinery/atmospherics/pipe/smart/manifold4w/general/hidden, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"TA" = ( +/obj/machinery/atmospherics/components/unary/vent_pump/on, +/turf/open/floor/iron/smooth, +/area/ruin/lizard_gaslava) +"Uk" = ( +/obj/machinery/duct, +/obj/machinery/light/directional/north, +/turf/open/misc/ashplanet/rocky, +/area/ruin/lizard_gaslava) +"UK" = ( +/obj/structure/cable, +/obj/machinery/power/smes/engineering, +/turf/open/floor/iron/smooth, +/area/ruin/lizard_gaslava) +"VD" = ( +/obj/effect/spawner/random/trash/food_packaging, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"Wa" = ( +/obj/structure/rack, +/obj/item/reagent_containers/condiment/vegetable_oil{ + pixel_y = 5; + pixel_x = -1 + }, +/obj/item/reagent_containers/condiment/vegetable_oil{ + pixel_y = 1; + pixel_x = 3 + }, +/obj/item/reagent_containers/condiment/olive_oil, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) +"Xe" = ( +/obj/structure/window/reinforced/spawner/directional/south, +/obj/structure/window/reinforced/spawner/directional/east, +/obj/structure/rack, +/obj/item/storage/cans/sixbeer, +/obj/item/storage/cans/sixsoda, +/turf/open/floor/iron/freezer, +/area/ruin/lizard_gaslava) +"Zf" = ( +/obj/machinery/door/airlock/glass, +/turf/open/floor/iron, +/area/ruin/lizard_gaslava) + +(1,1,1) = {" +DI +DI +DI +fF +fF +mY +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +ia +mG +mG +DI +DI +"} +(2,1,1) = {" +DI +DI +DI +fH +fH +fH +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +fH +fH +fH +DI +DI +"} +(3,1,1) = {" +DI +DI +DI +fH +fH +fH +DI +DI +ub +FA +FA +FA +FA +FA +FA +FA +FA +GK +DI +DI +fH +fH +fH +DI +DI +"} +(4,1,1) = {" +DI +DI +DI +fH +fH +fH +DI +DI +KJ +fH +SL +SL +SL +SL +SL +SL +SL +JX +DI +DI +fH +fH +fH +DI +DI +"} +(5,1,1) = {" +DI +DI +DI +fH +fH +fH +om +fH +fH +fH +wl +fH +wl +wl +fH +wl +SL +fH +fH +om +fH +fH +fH +DI +DI +"} +(6,1,1) = {" +DI +DI +DI +fH +fH +fH +om +fH +fH +xH +RA +fy +RA +RA +fy +RA +Uk +fH +fH +om +fH +fH +fH +DI +DI +"} +(7,1,1) = {" +DI +DI +DI +fH +fH +fH +om +fH +fH +fH +fX +fH +fX +fX +fH +fX +SL +fH +fH +om +fH +fH +fH +DI +DI +"} +(8,1,1) = {" +DI +DI +DI +DI +DI +DI +DI +DI +fH +fH +SL +SL +SL +SL +SL +SL +SL +fH +DI +DI +DI +DI +DI +DI +DI +"} +(9,1,1) = {" +DI +DI +DI +DI +DI +DI +DI +DI +RK +fH +fH +fH +fH +fH +fH +SL +fH +RK +DI +DI +DI +DI +DI +DI +DI +"} +(10,1,1) = {" +DI +DI +DI +DI +DI +DI +DI +DI +RA +Nr +Nr +Nr +RA +RA +RA +HW +ng +RA +DI +DI +DI +DI +DI +DI +DI +"} +(11,1,1) = {" +DI +DI +DI +DI +DI +DI +DI +DI +RA +Kl +xW +HI +Mv +aK +RA +ks +Es +RA +DI +DI +DI +DI +DI +DI +DI +"} +(12,1,1) = {" +DI +DI +DI +RA +RA +RA +RA +RA +RA +tD +VD +Gv +Bf +Rq +RA +nc +vH +RA +DI +DI +DI +DI +DI +DI +DI +"} +(13,1,1) = {" +DI +DI +DI +RA +Ov +FC +mn +vt +RA +mz +Qt +Km +bT +Zf +RA +sY +ng +RA +DI +DI +DI +DI +DI +DI +DI +"} +(14,1,1) = {" +DI +DI +DI +RA +Ag +pJ +TA +ck +dm +SZ +SZ +HL +SZ +HL +SZ +SZ +IU +RA +DI +DI +DI +DI +DI +DI +DI +"} +(15,1,1) = {" +DI +DI +DI +RA +UK +go +iK +PS +RA +Qt +Qb +xW +xW +jY +El +HS +uV +RA +DI +DI +DI +DI +DI +DI +DI +"} +(16,1,1) = {" +DI +DI +DI +RA +RA +RA +RA +RA +RA +xW +BH +yH +Qt +xW +VD +Qt +Ao +RA +DI +DI +DI +DI +DI +DI +DI +"} +(17,1,1) = {" +DI +DI +DI +DI +DI +DI +DI +DI +RA +xW +Ef +xW +xW +rM +EA +Xe +xW +RA +DI +DI +DI +DI +DI +DI +DI +"} +(18,1,1) = {" +DI +DI +DI +DI +DI +DI +DI +DI +RA +FL +Wa +Eh +xW +Qt +pp +xW +BX +RA +DI +DI +DI +DI +DI +DI +DI +"} +(19,1,1) = {" +DI +DI +DI +DI +DI +DI +DI +DI +RA +RA +RA +RA +RA +RA +RA +RA +RA +RA +DI +DI +DI +DI +DI +DI +DI +"} +(20,1,1) = {" +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +"} +(21,1,1) = {" +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +"} +(22,1,1) = {" +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +"} +(23,1,1) = {" +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +"} +(24,1,1) = {" +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +"} +(25,1,1) = {" +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +DI +"} diff --git a/_maps/RandomRuins/SpaceRuins/garbagetruck4.dmm b/_maps/RandomRuins/SpaceRuins/garbagetruck4.dmm index a636b7220cab5..33c5893bcee4c 100644 --- a/_maps/RandomRuins/SpaceRuins/garbagetruck4.dmm +++ b/_maps/RandomRuins/SpaceRuins/garbagetruck4.dmm @@ -78,7 +78,7 @@ /turf/open/floor/plating, /area/ruin/space/has_grav/garbagetruck/toystore) "hH" = ( -/obj/structure/spider/solid, +/obj/structure/spider/stickyweb/sealed/tough, /obj/item/book/manual/wiki/cytology, /obj/effect/decal/cleanable/plastic, /turf/open/floor/plating, @@ -122,7 +122,7 @@ /area/ruin/space/has_grav/garbagetruck/toystore) "lm" = ( /obj/structure/spider/stickyweb, -/obj/structure/spider/sticky, +/obj/structure/spider/stickyweb/very_sticky, /turf/open/floor/plating, /area/ruin/space/has_grav/garbagetruck/toystore) "mf" = ( @@ -182,7 +182,7 @@ /turf/open/floor/plating, /area/ruin/space/has_grav/garbagetruck/toystore) "qX" = ( -/obj/structure/spider/sticky, +/obj/structure/spider/stickyweb/very_sticky, /obj/item/food/badrecipe/moldy, /obj/structure/spider/stickyweb, /obj/item/food/spidereggs{ @@ -255,7 +255,7 @@ /turf/open/floor/plating, /area/ruin/space/has_grav/garbagetruck/toystore) "ts" = ( -/obj/structure/spider/solid, +/obj/structure/spider/stickyweb/sealed/tough, /obj/structure/spider/stickyweb, /obj/structure/closet/crate/trashcart/filled, /turf/open/floor/plating, @@ -841,7 +841,7 @@ /turf/open/floor/plating, /area/ruin/space/has_grav/garbagetruck/toystore) "XI" = ( -/obj/structure/spider/solid, +/obj/structure/spider/stickyweb/sealed/tough, /obj/item/food/badrecipe/moldy/bacteria, /turf/open/floor/plating, /area/ruin/space/has_grav/garbagetruck/toystore) diff --git a/_maps/RandomRuins/SpaceRuins/meatderelict.dmm b/_maps/RandomRuins/SpaceRuins/meatderelict.dmm index 0c4e9cd740b37..3e4bece11e8e5 100644 --- a/_maps/RandomRuins/SpaceRuins/meatderelict.dmm +++ b/_maps/RandomRuins/SpaceRuins/meatderelict.dmm @@ -733,7 +733,7 @@ /turf/open/indestructible/white, /area/ruin/space/has_grav/powered/biooutpost) "oQ" = ( -/obj/machinery/puzzle_button/meatderelict{ +/obj/machinery/puzzle/button/meatderelict{ pixel_y = 32; queue_size = 4 }, @@ -1669,7 +1669,7 @@ "FI" = ( /obj/item/instrument/piano_synth/headphones, /obj/structure/table, -/obj/machinery/puzzle_button/directional/north{ +/obj/machinery/puzzle/button/directional/north{ used = 1 }, /obj/effect/turf_decal/tile/neutral/opposingcorners, @@ -1820,7 +1820,7 @@ /turf/open/floor/iron/dark/textured_large, /area/ruin/space/has_grav/powered/biooutpost/vault) "Jt" = ( -/obj/machinery/puzzle_button/directional/north{ +/obj/machinery/puzzle/button/directional/north{ id = "md_tosci"; name = "shield power panel" }, @@ -1889,7 +1889,7 @@ /turf/open/indestructible/white, /area/ruin/space/has_grav/powered/biooutpost) "KC" = ( -/obj/machinery/puzzle_button/directional/north{ +/obj/machinery/puzzle/button/directional/north{ id = "md_toeng"; queue_size = 4 }, @@ -2138,7 +2138,7 @@ id = "md_armory" }, /obj/effect/turf_decal/stripes/full, -/obj/machinery/puzzle_keycardpad/directional/south{ +/obj/machinery/puzzle/keycardpad/directional/south{ id = "md_armory"; name = "armory authentication pad"; queue_size = 5 diff --git a/_maps/RandomZLevels/museum.dmm b/_maps/RandomZLevels/museum.dmm index f40ecc09015c6..64fd580b80f78 100644 --- a/_maps/RandomZLevels/museum.dmm +++ b/_maps/RandomZLevels/museum.dmm @@ -9,11 +9,18 @@ /obj/machinery/computer/old{ name = "replica computer"; dir = 8; - icon_keyboard = "rd_key"; - icon_screen = "rdcomp" + icon_screen = "rdcomp"; + icon_keyboard = "rd_key" }, /turf/open/floor/iron/smooth_large, /area/awaymission/museum) +"ac" = ( +/obj/structure/sign/poster/contraband/fake_bombable/directional/west, +/obj/structure/closet/crate/bin, +/obj/machinery/light/warm/directional/south, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) "ai" = ( /obj/structure/railing{ dir = 8 @@ -59,6 +66,11 @@ }, /turf/open/indestructible/plating, /area/awaymission/museum) +"ax" = ( +/obj/structure/sink/directional/north, +/obj/structure/mirror/directional/south, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) "az" = ( /obj/structure/table/reinforced, /obj/effect/spawner/random/entertainment/musical_instrument, @@ -76,6 +88,22 @@ /obj/structure/closet/crate/preopen, /turf/open/indestructible/plating, /area/awaymission/museum) +"aO" = ( +/obj/machinery/button/door/directional/north{ + name = "Lock Control"; + id = "museum_toilet_wontwork" + }, +/obj/effect/mob_spawn/corpse/human/skeleton/museum_chef, +/obj/structure/toilet/museum{ + dir = 4 + }, +/obj/item/keycard/cafeteria, +/obj/item/paper/fluff/museum/chefs_ultimatum, +/obj/machinery/light/small/dim/directional/west, +/obj/effect/decal/cleanable/dirt/dust, +/obj/effect/decal/cleanable/cobweb, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) "aR" = ( /obj/effect/turf_decal/tile/neutral/opposingcorners, /obj/effect/turf_decal/siding/dark_blue{ @@ -147,8 +175,8 @@ }, /turf/open/mirage{ dir = 8; - target_turf_x = 11; - range = 1 + range = 1; + target_turf_x = 11 }, /area/awaymission/museum) "bC" = ( @@ -177,8 +205,8 @@ /area/awaymission/museum) "bJ" = ( /mob/living/basic/statue/mannequin{ - dir = 8; - name = "Dale Knox" + name = "Dale Knox"; + dir = 8 }, /obj/effect/turf_decal/tile/blue/opposingcorners, /turf/open/floor/holofloor/white, @@ -229,6 +257,14 @@ /obj/item/storage/box/stickers/googly, /turf/open/floor/iron/dark, /area/awaymission/museum) +"ci" = ( +/obj/effect/turf_decal/siding/dark_blue{ + dir = 1 + }, +/obj/effect/turf_decal/tile/neutral/opposingcorners, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/iron/dark, +/area/awaymission/museum) "cm" = ( /obj/machinery/door/airlock/grunge, /obj/structure/barricade/wooden/crude, @@ -255,8 +291,8 @@ /turf/open/floor/iron/smooth_half, /area/awaymission/museum) "ct" = ( -/obj/structure/chair/comfy, /mob/living/basic/mothroach, +/obj/structure/chair/comfy, /obj/effect/mapping_helpers/mob_buckler, /obj/machinery/light/dim/directional/north, /turf/open/floor/iron/dark/textured_large, @@ -313,8 +349,8 @@ /obj/effect/turf_decal/tile/neutral/opposingcorners, /obj/item/toy/balloon/corgi, /obj/machinery/status_display/random_message{ - firstline_to_secondline = list("NO" = "LITTERING","YOU ARE" = "BEING WATCHED", "DO NOT TOUCH" = "THE EXHIBITS"); - pixel_x = 32 + pixel_x = 32; + firstline_to_secondline = list(NO="LITTERING", "YOU ARE"="BEING WATCHED", "DO NOT TOUCH"="THE EXHIBITS") }, /turf/open/floor/iron/dark, /area/awaymission/museum) @@ -328,9 +364,9 @@ "cS" = ( /obj/effect/step_trigger/thrower{ direction = 1; - facedir = 1; + mobs_only = 1; tiles = 10; - mobs_only = 1 + facedir = 1 }, /obj/machinery/light/floor, /turf/open/floor/iron, @@ -343,6 +379,14 @@ /obj/structure/plaque/static_plaque/golden/commission/dream, /turf/closed/indestructible/reinforced, /area/awaymission/museum) +"cY" = ( +/obj/structure/fluff/fake_camera{ + dir = 5 + }, +/obj/machinery/light/warm/directional/east, +/obj/structure/fluff/fake_scrubber, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) "cZ" = ( /obj/structure/fluff/divine/nexus, /turf/open/floor/cult, @@ -398,9 +442,9 @@ /area/awaymission/museum) "dK" = ( /turf/open/mirage{ + dir = 1; range = 2; - target_turf_y = -4; - dir = 1 + target_turf_y = -4 }, /area/awaymission/museum) "dL" = ( @@ -434,8 +478,8 @@ /area/awaymission/museum) "dY" = ( /mob/living/basic/statue/mannequin{ - hat = /obj/item/clothing/head/helmet/space; - dir = 1 + dir = 1; + hat = /obj/item/clothing/head/helmet/space }, /turf/open/floor/holofloor/hyperspace/ns, /area/awaymission/museum) @@ -462,7 +506,7 @@ /obj/machinery/door/poddoor/shutters/indestructible{ id = "museum_secret" }, -/obj/machinery/puzzle_keycardpad/directional/east{ +/obj/machinery/puzzle/keycardpad/directional/east{ id = "museum_secret" }, /turf/open/floor/iron/dark, @@ -521,10 +565,10 @@ /turf/open/floor/grass, /area/awaymission/museum) "ex" = ( +/mob/living/basic/mothroach, /obj/structure/chair/stool/bar/directional/west{ can_buckle = 1 }, -/mob/living/basic/mothroach, /obj/effect/mapping_helpers/mob_buckler, /turf/open/floor/wood/large, /area/awaymission/museum) @@ -596,6 +640,15 @@ /obj/structure/plaque/static_plaque/golden/commission/omega, /turf/closed/indestructible/reinforced, /area/awaymission/museum) +"eW" = ( +/obj/effect/turf_decal/tile/neutral/opposingcorners, +/obj/effect/turf_decal/siding/dark_blue, +/obj/structure/fluff/wallsign/directional/north{ + name = "Restrooms"; + dir = 4 + }, +/turf/open/floor/iron/dark, +/area/awaymission/museum) "eX" = ( /obj/effect/turf_decal/siding/wideplating{ dir = 4 @@ -605,11 +658,11 @@ }, /area/awaymission/museum) "fa" = ( -/obj/effect/turf_decal/sand/plating, /mob/living/basic/statue/mannequin{ held_item = /obj/item/pickaxe; hat = /obj/item/clothing/suit/hooded/explorer }, +/obj/effect/turf_decal/sand/plating, /obj/effect/turf_decal/mining, /turf/open/indestructible/plating, /area/awaymission/museum) @@ -627,6 +680,13 @@ /obj/item/kitchen/fork, /turf/open/floor/wood/tile, /area/awaymission/museum) +"fl" = ( +/obj/machinery/status_display/random_message{ + pixel_x = -32; + firstline_to_secondline = list(CAFETERIA="YUMMY", CAFETERIA="HOTDOGS", ENJOY="YOUR MEAL") + }, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) "fn" = ( /turf/open/floor/holofloor/hyperspace/ns, /area/awaymission/museum) @@ -706,6 +766,11 @@ }, /turf/open/floor/iron, /area/awaymission/museum) +"gg" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/machinery/shower/directional/east, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) "gj" = ( /obj/machinery/door/poddoor/shutters/preopen{ dir = 8 @@ -751,6 +816,10 @@ /obj/machinery/light/small/dim/directional/south, /turf/open/indestructible/plating, /area/awaymission/museum) +"gE" = ( +/obj/item/clothing/suit/caution, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) "gG" = ( /obj/effect/turf_decal/siding/dark_blue/corner{ dir = 4 @@ -814,8 +883,8 @@ "hk" = ( /obj/effect/smooths_with_walls, /turf/open/mirage{ - target_turf_y = -29; - dir = 1 + dir = 1; + target_turf_y = -29 }, /area/awaymission/museum) "hl" = ( @@ -827,6 +896,12 @@ }, /turf/open/indestructible/plating, /area/awaymission/museum) +"hm" = ( +/obj/structure/table, +/obj/effect/spawner/random/food_or_drink/condiment, +/obj/machinery/light/floor, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) "hp" = ( /mob/living/basic/mothroach/bar, /turf/open/floor/wood/tile, @@ -874,10 +949,18 @@ "hM" = ( /turf/closed/wall/rock/porous, /area/awaymission/museum) +"hS" = ( +/obj/effect/turf_decal/tile/neutral/opposingcorners, +/obj/effect/decal/cleanable/dirt/dust, +/obj/effect/turf_decal/siding/dark_blue{ + dir = 5 + }, +/turf/open/floor/iron/dark, +/area/awaymission/museum) "hT" = ( /mob/living/basic/statue/mannequin{ - dir = 8; name = "Dale Knox"; + dir = 8; held_item = /obj/item/circuitboard }, /obj/structure/sign/flag/nanotrasen/directional/south, @@ -892,8 +975,8 @@ /area/awaymission/museum) "hX" = ( /obj/item/circuitboard{ - icon_state = "flopdrive"; - name = "microprocessor" + name = "microprocessor"; + icon_state = "flopdrive" }, /obj/structure/table/reinforced, /turf/open/floor/circuit/green, @@ -983,6 +1066,15 @@ /obj/machinery/light/floor, /turf/open/floor/iron/dark, /area/awaymission/museum) +"iJ" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/structure/toilet/museum{ + dir = 1 + }, +/obj/structure/curtain, +/obj/structure/broken_flooring/plating, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) "iK" = ( /obj/effect/turf_decal/sand/plating, /obj/effect/turf_decal/siding{ @@ -1031,9 +1123,9 @@ }, /obj/effect/step_trigger/thrower{ direction = 1; - facedir = 1; + mobs_only = 1; tiles = 10; - mobs_only = 1 + facedir = 1 }, /turf/open/floor/iron/dark, /area/awaymission/museum) @@ -1067,10 +1159,20 @@ }, /turf/open/floor/cult, /area/awaymission/museum) +"ju" = ( +/obj/effect/turf_decal/tile/neutral/opposingcorners, +/obj/effect/turf_decal/siding/dark_blue{ + dir = 6 + }, +/obj/structure/fluff/wallsign/directional/north{ + name = "Restrooms" + }, +/turf/open/floor/iron/dark, +/area/awaymission/museum) "jy" = ( /mob/living/basic/statue/mannequin{ - dir = 8; name = "Dale Knox"; + dir = 8; held_item = /obj/item/circuitboard }, /obj/effect/turf_decal/stripes{ @@ -1082,6 +1184,14 @@ /obj/effect/spawner/structure/window, /turf/open/indestructible/plating, /area/awaymission/museum) +"jC" = ( +/obj/effect/puzzle_poddoor_open{ + icon = 'icons/effects/mapping_helpers.dmi'; + id = "museum_right_wing"; + queue_id = "museum_right_wing" + }, +/turf/closed/indestructible/reinforced, +/area/awaymission/museum) "jF" = ( /obj/machinery/suit_storage_unit/open, /obj/effect/turf_decal/box, @@ -1110,6 +1220,18 @@ /obj/effect/decal/cleanable/dirt/dust, /turf/open/floor/iron/dark, /area/awaymission/museum) +"jO" = ( +/obj/structure/toilet/museum, +/obj/structure/sign/poster/contraband/fake_bombable/directional/north, +/obj/machinery/button/door/directional/east{ + name = "Lock Control"; + id = "museum_toilet6"; + specialfunctions = 4; + normaldoorcontrol = 1 + }, +/obj/machinery/light/small/dim/directional/north, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) "jP" = ( /obj/effect/turf_decal/tile/neutral/opposingcorners, /obj/effect/turf_decal/siding/dark_blue/corner{ @@ -1120,13 +1242,16 @@ dir = 9 }, /obj/item/reagent_containers/cup/glass/coffee, +/obj/item/paper/fluff/scrambled_pass{ + puzzle_id = "museum_r_wing_puzzle" + }, /turf/open/floor/iron/dark, /area/awaymission/museum) "jU" = ( /obj/effect/landmark/transport/nav_beacon/tram/platform{ + name = "Exhibit Loading Bay"; specific_transport_id = "museum_cargo"; - platform_code = 2; - name = "Exhibit Loading Bay" + platform_code = 2 }, /turf/open/chasm/true/no_smooth, /area/awaymission/museum) @@ -1193,6 +1318,12 @@ }, /turf/open/floor/iron, /area/awaymission/museum) +"kx" = ( +/obj/machinery/door/airlock/public{ + name = "Restrooms" + }, +/turf/open/floor/iron, +/area/awaymission/museum) "kA" = ( /obj/machinery/conveyor{ dir = 1 @@ -1200,6 +1331,16 @@ /obj/item/vending_refill/wardrobe/coroner_wardrobe, /turf/open/indestructible/plating, /area/awaymission/museum) +"kL" = ( +/obj/machinery/door/poddoor/shutters/window/indestructible{ + dir = 8; + id = "museum_cafeteria" + }, +/obj/machinery/puzzle/keycardpad/directional/south{ + id = "museum_cafeteria" + }, +/turf/open/floor/iron, +/area/awaymission/museum) "kO" = ( /obj/structure/railing{ dir = 8 @@ -1227,6 +1368,19 @@ /obj/effect/decal/cleanable/dirt/dust, /turf/open/indestructible/plating, /area/awaymission/museum) +"kZ" = ( +/obj/structure/toilet/museum{ + dir = 4 + }, +/obj/machinery/button/door/directional/north{ + name = "Lock Control"; + id = "museum_toilet5"; + specialfunctions = 4; + normaldoorcontrol = 1 + }, +/obj/machinery/light/small/dim/directional/west, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) "la" = ( /obj/effect/turf_decal/siding/wood{ dir = 4 @@ -1324,11 +1478,11 @@ /turf/open/floor/iron, /area/awaymission/museum) "lT" = ( -/obj/structure/chair/office{ - dir = 1 - }, /mob/living/basic/statue/mannequin{ - hat = /obj/item/clothing/suit/toggle/labcoat/science; + dir = 1; + hat = /obj/item/clothing/suit/toggle/labcoat/science + }, +/obj/structure/chair/office{ dir = 1 }, /obj/machinery/light/floor, @@ -1345,9 +1499,9 @@ specific_transport_id = "museum_cargo" }, /obj/effect/landmark/transport/nav_beacon/tram/platform{ + name = "Internal Loading Bay"; specific_transport_id = "museum_cargo"; - platform_code = 1; - name = "Internal Loading Bay" + platform_code = 1 }, /turf/open/chasm/true/no_smooth, /area/awaymission/museum) @@ -1379,6 +1533,9 @@ }, /turf/open/chasm/true/no_smooth, /area/awaymission/museum) +"ms" = ( +/turf/open/floor/iron, +/area/awaymission/museum/cafeteria) "mA" = ( /obj/structure/window/reinforced/spawner/directional/west, /turf/open/floor/holofloor/beach/water, @@ -1403,9 +1560,9 @@ /area/awaymission/museum) "mS" = ( /mob/living/basic/statue/mannequin{ + dir = 1; held_item = /obj/item/wrench; - hat = /obj/item/clothing/head/utility/hardhat; - dir = 1 + hat = /obj/item/clothing/head/utility/hardhat }, /obj/effect/decal/cleanable/dirt/dust, /obj/effect/decal/cleanable/blood/gibs, @@ -1436,8 +1593,8 @@ /obj/structure/transport/linear/tram/slow, /obj/structure/tram, /obj/machinery/transport/tram_controller{ - configured_transport_id = "museum_cargo"; - cover_locked = 0 + cover_locked = 0; + configured_transport_id = "museum_cargo" }, /turf/open/chasm/true/no_smooth, /area/awaymission/museum) @@ -1477,8 +1634,8 @@ /turf/open/floor/iron/white, /area/awaymission/museum) "nu" = ( -/obj/effect/decal/cleanable/glass/titanium, /mob/living/basic/mouse/rat, +/obj/effect/decal/cleanable/glass/titanium, /turf/open/floor/iron/white, /area/awaymission/museum) "nv" = ( @@ -1510,9 +1667,9 @@ "nz" = ( /obj/structure/lattice/catwalk/mining, /obj/structure/fluff{ + name = "old plasma extractor"; icon = 'icons/mob/simple/hivebot.dmi'; - icon_state = "fab_robot"; - name = "old plasma extractor" + icon_state = "fab_robot" }, /turf/open/lava/plasma/mafia, /area/awaymission/museum) @@ -1537,9 +1694,9 @@ }, /obj/effect/step_trigger/thrower{ direction = 1; - facedir = 1; + mobs_only = 1; tiles = 10; - mobs_only = 1 + facedir = 1 }, /turf/open/floor/iron/dark, /area/awaymission/museum) @@ -1588,6 +1745,16 @@ }, /turf/open/floor/iron/dark, /area/awaymission/museum) +"nY" = ( +/obj/structure/sign/poster/official/no_erp/directional/north, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) +"nZ" = ( +/obj/structure/chair/sofa/bench/left{ + dir = 1 + }, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) "od" = ( /obj/structure/fluff/fake_vent, /turf/open/floor/iron, @@ -1648,11 +1815,31 @@ /obj/structure/flora/coconuts, /turf/open/misc/beach/sand, /area/awaymission/museum/mothroachvoid) +"oD" = ( +/obj/structure/toilet/museum{ + dir = 4 + }, +/obj/machinery/button/door/directional/north{ + name = "Lock Control"; + id = "museum_toilet4"; + specialfunctions = 4; + normaldoorcontrol = 1 + }, +/obj/machinery/light/small/dim/directional/west, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) "oI" = ( /obj/effect/decal/cleanable/dirt/dust, /obj/machinery/light/floor, /turf/open/floor/iron, /area/awaymission/museum) +"oP" = ( +/obj/effect/puzzle_poddoor_open{ + id = "museum_cafeteria"; + queue_id = "museum_cafeteria" + }, +/turf/closed/indestructible/reinforced, +/area/awaymission/museum) "oQ" = ( /turf/open/floor/holofloor/beach/coast{ dir = 1 @@ -1694,8 +1881,8 @@ /area/awaymission/museum) "pi" = ( /mob/living/basic/statue/mannequin{ - hat = /obj/item/clothing/head/costume/nursehat; - held_item = /obj/item/clothing/neck/stethoscope + held_item = /obj/item/clothing/neck/stethoscope; + hat = /obj/item/clothing/head/costume/nursehat }, /obj/effect/turf_decal/tile/blue/opposingcorners, /turf/open/floor/holofloor/white, @@ -1729,6 +1916,13 @@ /obj/effect/decal/cleanable/dirt/dust, /turf/open/floor/iron/white, /area/awaymission/museum) +"pz" = ( +/obj/machinery/door/poddoor/shutters/window/indestructible{ + dir = 8; + id = "museum_cafeteria" + }, +/turf/open/floor/iron, +/area/awaymission/museum) "pD" = ( /obj/structure/broken_flooring/corner/always_floorplane/directional/west, /obj/effect/decal/cleanable/dirt/dust, @@ -1758,6 +1952,17 @@ }, /turf/open/indestructible/plating, /area/awaymission/museum) +"pN" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/effect/turf_decal/tile/neutral/opposingcorners, +/obj/effect/turf_decal/siding/dark_blue{ + dir = 6 + }, +/obj/structure/fluff/fake_camera{ + dir = 4 + }, +/turf/open/floor/iron/dark, +/area/awaymission/museum) "pX" = ( /obj/structure/rack, /obj/effect/spawner/random/maintenance/two, @@ -1773,6 +1978,10 @@ /obj/effect/decal/cleanable/dirt/dust, /turf/open/floor/iron, /area/awaymission/museum) +"ql" = ( +/obj/structure/chair/sofa/bench, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) "qo" = ( /obj/effect/oneway{ dir = 8 @@ -1784,15 +1993,15 @@ /area/awaymission/museum) "qt" = ( /obj/effect/spawner/random/food_or_drink/booze{ - loot = list(/obj/item/reagent_containers/cup/glass/bottle/beer = 10, /obj/item/reagent_containers/cup/glass/bottle/ale = 10, /obj/item/reagent_containers/cup/glass/bottle/beer/light = 5, /obj/item/reagent_containers/cup/glass/bottle/maltliquor = 5, /obj/item/reagent_containers/cup/glass/bottle/whiskey = 5, /obj/item/reagent_containers/cup/glass/bottle/gin = 5, /obj/item/reagent_containers/cup/glass/bottle/vodka = 5, /obj/item/reagent_containers/cup/glass/bottle/tequila = 5, /obj/item/reagent_containers/cup/glass/bottle/rum = 5, /obj/item/reagent_containers/cup/glass/bottle/vermouth = 5, /obj/item/reagent_containers/cup/glass/bottle/cognac = 5, /obj/item/reagent_containers/cup/glass/bottle/wine = 5, /obj/item/reagent_containers/cup/glass/bottle/kahlua = 5, /obj/item/reagent_containers/cup/glass/bottle/amaretto = 5, /obj/item/reagent_containers/cup/glass/bottle/hcider = 5, /obj/item/reagent_containers/cup/glass/bottle/absinthe = 5, /obj/item/reagent_containers/cup/glass/bottle/sake = 5, /obj/item/reagent_containers/cup/glass/bottle/grappa = 5, /obj/item/reagent_containers/cup/glass/bottle/applejack = 5, /obj/item/reagent_containers/cup/glass/bottle/wine_voltaic = 5, /obj/item/reagent_containers/cup/bottle/ethanol = 2, /obj/item/reagent_containers/cup/glass/bottle/fernet = 2, /obj/item/reagent_containers/cup/glass/bottle/champagne = 2, /obj/item/reagent_containers/cup/glass/bottle/absinthe/premium = 2, /obj/item/reagent_containers/cup/glass/bottle/goldschlager = 2, /obj/item/reagent_containers/cup/glass/bottle/patron = 1, /obj/item/reagent_containers/cup/glass/bottle/kong = 1, /obj/item/reagent_containers/cup/glass/bottle/lizardwine = 1, /obj/item/reagent_containers/cup/glass/bottle/vodka/badminka = 1, /obj/item/reagent_containers/cup/glass/bottle/trappist = 1); + loot = list(/obj/item/reagent_containers/cup/glass/bottle/beer=10, /obj/item/reagent_containers/cup/glass/bottle/ale=10, /obj/item/reagent_containers/cup/glass/bottle/beer/light=5, /obj/item/reagent_containers/cup/glass/bottle/maltliquor=5, /obj/item/reagent_containers/cup/glass/bottle/whiskey=5, /obj/item/reagent_containers/cup/glass/bottle/gin=5, /obj/item/reagent_containers/cup/glass/bottle/vodka=5, /obj/item/reagent_containers/cup/glass/bottle/tequila=5, /obj/item/reagent_containers/cup/glass/bottle/rum=5, /obj/item/reagent_containers/cup/glass/bottle/vermouth=5, /obj/item/reagent_containers/cup/glass/bottle/cognac=5, /obj/item/reagent_containers/cup/glass/bottle/wine=5, /obj/item/reagent_containers/cup/glass/bottle/kahlua=5, /obj/item/reagent_containers/cup/glass/bottle/amaretto=5, /obj/item/reagent_containers/cup/glass/bottle/hcider=5, /obj/item/reagent_containers/cup/glass/bottle/absinthe=5, /obj/item/reagent_containers/cup/glass/bottle/sake=5, /obj/item/reagent_containers/cup/glass/bottle/grappa=5, /obj/item/reagent_containers/cup/glass/bottle/applejack=5, /obj/item/reagent_containers/cup/glass/bottle/wine_voltaic=5, /obj/item/reagent_containers/cup/bottle/ethanol=2, /obj/item/reagent_containers/cup/glass/bottle/fernet=2, /obj/item/reagent_containers/cup/glass/bottle/champagne=2, /obj/item/reagent_containers/cup/glass/bottle/absinthe/premium=2, /obj/item/reagent_containers/cup/glass/bottle/goldschlager=2, /obj/item/reagent_containers/cup/glass/bottle/patron=1, /obj/item/reagent_containers/cup/glass/bottle/kong=1, /obj/item/reagent_containers/cup/glass/bottle/lizardwine=1, /obj/item/reagent_containers/cup/glass/bottle/vodka/badminka=1, /obj/item/reagent_containers/cup/glass/bottle/trappist=1); spawn_random_offset = 2 }, /turf/open/floor/carpet/cyan, /area/awaymission/museum/mothroachvoid) "qw" = ( /obj/item/flashlight/flare{ - start_on = 1; - icon_state = "flare-on" + icon_state = "flare-on"; + start_on = 1 }, /obj/structure/table, /obj/effect/decal/cleanable/dirt/dust, @@ -1822,6 +2031,10 @@ /obj/effect/decal/cleanable/dirt/dust, /turf/open/floor/engine, /area/awaymission/museum) +"qK" = ( +/obj/machinery/vending/hotdog/museum, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) "qL" = ( /obj/effect/turf_decal/tile/neutral/opposingcorners, /obj/effect/turf_decal/siding/dark_blue{ @@ -1879,8 +2092,8 @@ /area/awaymission/museum) "qY" = ( /obj/machinery/power/shuttle_engine/heater{ - dir = 4; - opacity = 1 + opacity = 1; + dir = 4 }, /obj/structure/window/reinforced/spawner/directional/west, /turf/open/indestructible/plating, @@ -1909,22 +2122,29 @@ /turf/open/floor/carpet/executive, /area/awaymission/museum) "rq" = ( +/mob/living/basic/statue/mannequin/suspicious, /obj/effect/decal/cleanable/blood/old, /obj/effect/gibspawner/human, /obj/effect/gibspawner/human, /obj/effect/gibspawner/human, /obj/effect/gibspawner/human, -/mob/living/basic/statue/mannequin/suspicious, /turf/open/floor/iron, /area/awaymission/museum) "rr" = ( /turf/open/floor/wood/tile, /area/awaymission/museum) +"ry" = ( +/obj/machinery/door/airlock{ + name = "Restroom Cabin 6"; + id_tag = "museum_toilet6" + }, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) "rA" = ( +/mob/living/basic/mothroach, /obj/structure/chair/comfy/beige{ dir = 8 }, -/mob/living/basic/mothroach, /obj/effect/mapping_helpers/mob_buckler, /turf/open/floor/wood/large, /area/awaymission/museum) @@ -1964,13 +2184,17 @@ dir = 1 }, /obj/structure/railing/corner/end, +/obj/structure/fluff/wallsign/directional/south{ + name = "Cafeteria"; + dir = 4 + }, /turf/open/floor/iron/dark, /area/awaymission/museum) "rU" = ( /mob/living/basic/statue/mannequin{ + name = "Michael Trasen"; dir = 4; - held_item = /obj/item/wrench; - name = "Michael Trasen" + held_item = /obj/item/wrench }, /obj/structure/sign/flag/nanotrasen/directional/south, /obj/machinery/light/small/dim/directional/west, @@ -1997,9 +2221,9 @@ /turf/open/floor/engine, /area/awaymission/museum) "rY" = ( +/mob/living/basic/mouse/rat, /obj/machinery/light/small/broken/directional/north, /obj/effect/decal/cleanable/dirt/dust, -/mob/living/basic/mouse/rat, /turf/open/floor/iron/dark/textured_large, /area/awaymission/museum) "sd" = ( @@ -2110,13 +2334,20 @@ /obj/effect/decal/cleanable/dirt/dust, /turf/open/floor/iron/dark, /area/awaymission/museum) +"ta" = ( +/obj/machinery/door/airlock{ + name = "Restroom Cabin 5"; + id_tag = "museum_toilet5" + }, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) "tc" = ( +/mob/living/basic/mothroach, /obj/structure/lattice/catwalk/mining, /obj/structure/railing{ dir = 6 }, /obj/structure/chair, -/mob/living/basic/mothroach, /obj/effect/mapping_helpers/mob_buckler, /turf/open/misc/grass, /area/awaymission/museum/mothroachvoid) @@ -2136,15 +2367,15 @@ /turf/open/floor/holofloor/asteroid, /area/awaymission/museum) "tj" = ( +/mob/living/basic/statue/mannequin{ + dir = 1; + held_item = /obj/item/wrench; + hat = /obj/item/clothing/head/utility/hardhat + }, /obj/effect/turf_decal/sand/plating, /obj/effect/turf_decal/stripes/asteroid/line{ dir = 1 }, -/mob/living/basic/statue/mannequin{ - dir = 1; - hat = /obj/item/clothing/head/utility/hardhat; - held_item = /obj/item/wrench - }, /turf/open/indestructible/plating, /area/awaymission/museum) "tk" = ( @@ -2185,7 +2416,7 @@ "tx" = ( /obj/structure/table/wood, /obj/effect/spawner/random/food_or_drink/booze{ - loot = list(/obj/item/reagent_containers/cup/glass/bottle/beer = 10, /obj/item/reagent_containers/cup/glass/bottle/ale = 10, /obj/item/reagent_containers/cup/glass/bottle/beer/light = 5, /obj/item/reagent_containers/cup/glass/bottle/maltliquor = 5, /obj/item/reagent_containers/cup/glass/bottle/whiskey = 5, /obj/item/reagent_containers/cup/glass/bottle/gin = 5, /obj/item/reagent_containers/cup/glass/bottle/vodka = 5, /obj/item/reagent_containers/cup/glass/bottle/tequila = 5, /obj/item/reagent_containers/cup/glass/bottle/rum = 5, /obj/item/reagent_containers/cup/glass/bottle/vermouth = 5, /obj/item/reagent_containers/cup/glass/bottle/cognac = 5, /obj/item/reagent_containers/cup/glass/bottle/wine = 5, /obj/item/reagent_containers/cup/glass/bottle/kahlua = 5, /obj/item/reagent_containers/cup/glass/bottle/amaretto = 5, /obj/item/reagent_containers/cup/glass/bottle/hcider = 5, /obj/item/reagent_containers/cup/glass/bottle/absinthe = 5, /obj/item/reagent_containers/cup/glass/bottle/sake = 5, /obj/item/reagent_containers/cup/glass/bottle/grappa = 5, /obj/item/reagent_containers/cup/glass/bottle/applejack = 5, /obj/item/reagent_containers/cup/glass/bottle/wine_voltaic = 5, /obj/item/reagent_containers/cup/bottle/ethanol = 2, /obj/item/reagent_containers/cup/glass/bottle/fernet = 2, /obj/item/reagent_containers/cup/glass/bottle/champagne = 2, /obj/item/reagent_containers/cup/glass/bottle/absinthe/premium = 2, /obj/item/reagent_containers/cup/glass/bottle/goldschlager = 2, /obj/item/reagent_containers/cup/glass/bottle/patron = 1, /obj/item/reagent_containers/cup/glass/bottle/kong = 1, /obj/item/reagent_containers/cup/glass/bottle/lizardwine = 1, /obj/item/reagent_containers/cup/glass/bottle/vodka/badminka = 1, /obj/item/reagent_containers/cup/glass/bottle/trappist = 1); + loot = list(/obj/item/reagent_containers/cup/glass/bottle/beer=10, /obj/item/reagent_containers/cup/glass/bottle/ale=10, /obj/item/reagent_containers/cup/glass/bottle/beer/light=5, /obj/item/reagent_containers/cup/glass/bottle/maltliquor=5, /obj/item/reagent_containers/cup/glass/bottle/whiskey=5, /obj/item/reagent_containers/cup/glass/bottle/gin=5, /obj/item/reagent_containers/cup/glass/bottle/vodka=5, /obj/item/reagent_containers/cup/glass/bottle/tequila=5, /obj/item/reagent_containers/cup/glass/bottle/rum=5, /obj/item/reagent_containers/cup/glass/bottle/vermouth=5, /obj/item/reagent_containers/cup/glass/bottle/cognac=5, /obj/item/reagent_containers/cup/glass/bottle/wine=5, /obj/item/reagent_containers/cup/glass/bottle/kahlua=5, /obj/item/reagent_containers/cup/glass/bottle/amaretto=5, /obj/item/reagent_containers/cup/glass/bottle/hcider=5, /obj/item/reagent_containers/cup/glass/bottle/absinthe=5, /obj/item/reagent_containers/cup/glass/bottle/sake=5, /obj/item/reagent_containers/cup/glass/bottle/grappa=5, /obj/item/reagent_containers/cup/glass/bottle/applejack=5, /obj/item/reagent_containers/cup/glass/bottle/wine_voltaic=5, /obj/item/reagent_containers/cup/bottle/ethanol=2, /obj/item/reagent_containers/cup/glass/bottle/fernet=2, /obj/item/reagent_containers/cup/glass/bottle/champagne=2, /obj/item/reagent_containers/cup/glass/bottle/absinthe/premium=2, /obj/item/reagent_containers/cup/glass/bottle/goldschlager=2, /obj/item/reagent_containers/cup/glass/bottle/patron=1, /obj/item/reagent_containers/cup/glass/bottle/kong=1, /obj/item/reagent_containers/cup/glass/bottle/lizardwine=1, /obj/item/reagent_containers/cup/glass/bottle/vodka/badminka=1, /obj/item/reagent_containers/cup/glass/bottle/trappist=1); spawn_random_offset = 2 }, /turf/open/floor/wood/tile, @@ -2193,9 +2424,9 @@ "ty" = ( /obj/structure/table, /obj/item/clothing/gloves/color/yellow{ - siemens_coefficient = 10; name = "fake stungloves"; - desc = "A crude replica of stungloves. Essentially gloves wrapped with wire. Extremely unsafe." + desc = "A crude replica of stungloves. Essentially gloves wrapped with wire. Extremely unsafe."; + siemens_coefficient = 10 }, /obj/machinery/light/floor, /turf/open/floor/iron, @@ -2293,6 +2524,13 @@ dir = 2 }, /area/awaymission/museum/mothroachvoid) +"um" = ( +/obj/structure/window/reinforced/spawner/directional/east, +/obj/structure/sign/warning/fire/directional/west, +/obj/structure/tank_holder/extinguisher/anti, +/obj/effect/decal/cleanable/dirt/dust, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) "up" = ( /obj/effect/turf_decal/siding/wood{ dir = 4 @@ -2302,6 +2540,21 @@ }, /turf/open/floor/iron/showroomfloor, /area/awaymission/museum) +"us" = ( +/obj/machinery/door/airlock{ + name = "Restroom Cabin 2"; + id_tag = "museum_toilet2" + }, +/turf/open/floor/iron, +/area/awaymission/museum) +"ut" = ( +/obj/machinery/light/very_dim/directional/north, +/obj/effect/decal/cleanable/dirt/dust, +/obj/structure/fluff/fake_vent, +/obj/structure/mop_bucket, +/obj/effect/decal/cleanable/cobweb/cobweb2, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) "uu" = ( /obj/structure/rack, /obj/effect/spawner/random/engineering/material, @@ -2315,6 +2568,10 @@ }, /turf/open/floor/grass, /area/awaymission/museum) +"uH" = ( +/obj/structure/sign/clock/directional/north, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) "uM" = ( /obj/structure/fluff/fake_camera{ dir = 10 @@ -2345,8 +2602,8 @@ "vc" = ( /obj/effect/turf_decal/tile/neutral/fourcorners, /obj/structure/sign/painting/large{ - persistence_id = "museumgate_big"; - dir = 1 + dir = 1; + persistence_id = "museumgate_big" }, /obj/effect/decal/cleanable/dirt/dust, /turf/open/floor/iron/dark, @@ -2418,6 +2675,11 @@ /obj/structure/filingcabinet, /turf/open/floor/iron, /area/awaymission/museum) +"vG" = ( +/turf/closed/indestructible/fakedoor{ + name = "Kitchen" + }, +/area/awaymission/museum/cafeteria) "vM" = ( /mob/living/basic/statue/mannequin{ dir = 1; @@ -2448,6 +2710,16 @@ /obj/effect/turf_decal/siding/dark_blue, /turf/open/floor/mineral/titanium/blue, /area/awaymission/museum) +"wg" = ( +/obj/machinery/door/airlock{ + name = "Restroom Cabin 3" + }, +/obj/effect/mapping_helpers/airlock/locked, +/obj/effect/mapping_helpers/airlock_note_placer{ + note_info = "I'VE WORKED HERE FOR 7 YEARS! JUST TO BE REPLACED BY A VENDING MACHINE?! YOU KNOW WHAT? FUCK YOU, I'VE LOCKED DOWN THE CAFETERIA AND WON'T BE COMING OUT 'TILL YOU GIVE ME MY JOB BACK!" + }, +/turf/open/floor/iron, +/area/awaymission/museum) "wh" = ( /obj/structure/broken_flooring/corner/always_floorplane/directional/west, /obj/structure/lattice, @@ -2456,7 +2728,7 @@ "wi" = ( /obj/machinery/door/poddoor/shutters/indestructible{ dir = 8; - id = "nothing" + id = "museum_secret" }, /turf/open/floor/iron, /area/awaymission/museum) @@ -2526,8 +2798,8 @@ "wN" = ( /obj/effect/turf_decal/tile/neutral/fourcorners, /obj/structure/sign/painting/large{ - persistence_id = "museumgate_big"; - dir = 1 + dir = 1; + persistence_id = "museumgate_big" }, /turf/open/floor/iron/dark, /area/awaymission/museum) @@ -2535,6 +2807,13 @@ /obj/item/kirbyplants/random, /turf/open/floor/wood/large, /area/awaymission/museum) +"wV" = ( +/obj/effect/turf_decal/siding/dark_blue{ + dir = 1 + }, +/obj/effect/turf_decal/tile/neutral/opposingcorners, +/turf/open/floor/iron/dark, +/area/awaymission/museum) "wZ" = ( /turf/open/misc/beach/coast{ dir = 1 @@ -2558,8 +2837,8 @@ /obj/structure/table/reinforced, /obj/structure/window/spawner/directional/west, /obj/effect/spawner/random/bureaucracy/paper{ - spawn_random_offset = 7; - spawn_loot_count = 8 + spawn_loot_count = 8; + spawn_random_offset = 7 }, /obj/effect/decal/cleanable/dirt/dust, /turf/open/floor/iron/white, @@ -2575,8 +2854,8 @@ "xg" = ( /obj/structure/table/reinforced, /obj/item/circuitboard{ - icon_state = "bluespacearray"; - name = "fancy replica tech" + name = "fancy replica tech"; + icon_state = "bluespacearray" }, /turf/open/floor/iron/smooth_large, /area/awaymission/museum) @@ -2599,6 +2878,10 @@ }, /turf/open/misc/beach/sand, /area/awaymission/museum/mothroachvoid) +"xp" = ( +/obj/structure/table, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) "xr" = ( /obj/effect/decal/cleanable/dirt/dust, /obj/structure/closet/crate/freezer/food, @@ -2668,6 +2951,11 @@ }, /turf/open/floor/iron/dark, /area/awaymission/museum) +"xO" = ( +/obj/structure/table, +/obj/item/reagent_containers/cup/soda_cans/canned_laughter, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) "xP" = ( /obj/structure/closet/crate/cardboard, /obj/item/storage/toolbox/artistic, @@ -2704,10 +2992,10 @@ /turf/open/chasm/true/no_smooth, /area/awaymission/museum) "yi" = ( +/mob/living/basic/mothroach, /obj/structure/chair/comfy/beige{ dir = 1 }, -/mob/living/basic/mothroach, /obj/effect/mapping_helpers/mob_buckler, /turf/open/floor/wood/large, /area/awaymission/museum) @@ -2731,9 +3019,9 @@ /turf/open/floor/holofloor/hyperspace/ns, /area/awaymission/museum) "yr" = ( +/mob/living/basic/mothroach, /obj/structure/chair/comfy, /obj/effect/mapping_helpers/mob_buckler, -/mob/living/basic/mothroach, /turf/open/floor/wood/large, /area/awaymission/museum) "yu" = ( @@ -2855,14 +3143,14 @@ /turf/closed/indestructible/rock, /area/awaymission/museum) "zG" = ( -/obj/structure/chair/office{ - dir = 8 - }, -/obj/effect/turf_decal/siding/red, /mob/living/basic/statue/mannequin{ dir = 8; hat = /obj/item/clothing/head/fedora }, +/obj/structure/chair/office{ + dir = 8 + }, +/obj/effect/turf_decal/siding/red, /obj/effect/decal/cleanable/dirt/dust, /turf/open/floor/iron/smooth_half, /area/awaymission/museum) @@ -2891,13 +3179,13 @@ pixel_y = 12 }, /obj/item/stamp/granted{ - pixel_y = 12; - pixel_x = 8 + pixel_x = 8; + pixel_y = 12 }, /obj/effect/spawner/random/bureaucracy/paper{ - spawn_random_offset = 12; + spawn_loot_count = 8; spawn_scatter_radius = 1; - spawn_loot_count = 8 + spawn_random_offset = 12 }, /turf/open/floor/iron, /area/awaymission/museum) @@ -3043,8 +3331,8 @@ teleport_y_offset = -30 }, /turf/open/mirage{ - target_turf_y = -29; - dir = 1 + dir = 1; + target_turf_y = -29 }, /area/awaymission/museum) "AP" = ( @@ -3054,6 +3342,13 @@ /obj/effect/decal/cleanable/dirt/dust, /turf/open/indestructible/plating, /area/awaymission/museum) +"AQ" = ( +/obj/machinery/door/airlock{ + name = "Restroom Cabin 4"; + id_tag = "museum_toilet4" + }, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) "AR" = ( /obj/structure/railing{ dir = 8 @@ -3217,9 +3512,9 @@ "Cy" = ( /obj/structure/table/wood, /obj/effect/spawner/random/food_or_drink/booze{ - loot = list(/obj/item/reagent_containers/cup/glass/bottle/beer = 10, /obj/item/reagent_containers/cup/glass/bottle/ale = 10, /obj/item/reagent_containers/cup/glass/bottle/beer/light = 5, /obj/item/reagent_containers/cup/glass/bottle/maltliquor = 5, /obj/item/reagent_containers/cup/glass/bottle/whiskey = 5, /obj/item/reagent_containers/cup/glass/bottle/gin = 5, /obj/item/reagent_containers/cup/glass/bottle/vodka = 5, /obj/item/reagent_containers/cup/glass/bottle/tequila = 5, /obj/item/reagent_containers/cup/glass/bottle/rum = 5, /obj/item/reagent_containers/cup/glass/bottle/vermouth = 5, /obj/item/reagent_containers/cup/glass/bottle/cognac = 5, /obj/item/reagent_containers/cup/glass/bottle/wine = 5, /obj/item/reagent_containers/cup/glass/bottle/kahlua = 5, /obj/item/reagent_containers/cup/glass/bottle/amaretto = 5, /obj/item/reagent_containers/cup/glass/bottle/hcider = 5, /obj/item/reagent_containers/cup/glass/bottle/absinthe = 5, /obj/item/reagent_containers/cup/glass/bottle/sake = 5, /obj/item/reagent_containers/cup/glass/bottle/grappa = 5, /obj/item/reagent_containers/cup/glass/bottle/applejack = 5, /obj/item/reagent_containers/cup/glass/bottle/wine_voltaic = 5, /obj/item/reagent_containers/cup/bottle/ethanol = 2, /obj/item/reagent_containers/cup/glass/bottle/fernet = 2, /obj/item/reagent_containers/cup/glass/bottle/champagne = 2, /obj/item/reagent_containers/cup/glass/bottle/absinthe/premium = 2, /obj/item/reagent_containers/cup/glass/bottle/goldschlager = 2, /obj/item/reagent_containers/cup/glass/bottle/patron = 1, /obj/item/reagent_containers/cup/glass/bottle/kong = 1, /obj/item/reagent_containers/cup/glass/bottle/lizardwine = 1, /obj/item/reagent_containers/cup/glass/bottle/vodka/badminka = 1, /obj/item/reagent_containers/cup/glass/bottle/trappist = 1); - spawn_random_offset = 2; - spawn_loot_count = 2 + loot = list(/obj/item/reagent_containers/cup/glass/bottle/beer=10, /obj/item/reagent_containers/cup/glass/bottle/ale=10, /obj/item/reagent_containers/cup/glass/bottle/beer/light=5, /obj/item/reagent_containers/cup/glass/bottle/maltliquor=5, /obj/item/reagent_containers/cup/glass/bottle/whiskey=5, /obj/item/reagent_containers/cup/glass/bottle/gin=5, /obj/item/reagent_containers/cup/glass/bottle/vodka=5, /obj/item/reagent_containers/cup/glass/bottle/tequila=5, /obj/item/reagent_containers/cup/glass/bottle/rum=5, /obj/item/reagent_containers/cup/glass/bottle/vermouth=5, /obj/item/reagent_containers/cup/glass/bottle/cognac=5, /obj/item/reagent_containers/cup/glass/bottle/wine=5, /obj/item/reagent_containers/cup/glass/bottle/kahlua=5, /obj/item/reagent_containers/cup/glass/bottle/amaretto=5, /obj/item/reagent_containers/cup/glass/bottle/hcider=5, /obj/item/reagent_containers/cup/glass/bottle/absinthe=5, /obj/item/reagent_containers/cup/glass/bottle/sake=5, /obj/item/reagent_containers/cup/glass/bottle/grappa=5, /obj/item/reagent_containers/cup/glass/bottle/applejack=5, /obj/item/reagent_containers/cup/glass/bottle/wine_voltaic=5, /obj/item/reagent_containers/cup/bottle/ethanol=2, /obj/item/reagent_containers/cup/glass/bottle/fernet=2, /obj/item/reagent_containers/cup/glass/bottle/champagne=2, /obj/item/reagent_containers/cup/glass/bottle/absinthe/premium=2, /obj/item/reagent_containers/cup/glass/bottle/goldschlager=2, /obj/item/reagent_containers/cup/glass/bottle/patron=1, /obj/item/reagent_containers/cup/glass/bottle/kong=1, /obj/item/reagent_containers/cup/glass/bottle/lizardwine=1, /obj/item/reagent_containers/cup/glass/bottle/vodka/badminka=1, /obj/item/reagent_containers/cup/glass/bottle/trappist=1); + spawn_loot_count = 2; + spawn_random_offset = 2 }, /obj/machinery/light/warm/directional/west, /turf/open/floor/wood/large, @@ -3255,6 +3550,11 @@ }, /turf/open/floor/iron/dark, /area/awaymission/museum) +"CG" = ( +/obj/structure/mirror/directional/east, +/obj/structure/sink/directional/west, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) "CI" = ( /obj/effect/turf_decal/caution, /obj/effect/decal/cleanable/dirt/dust, @@ -3293,6 +3593,18 @@ }, /turf/open/floor/iron/dark, /area/awaymission/museum) +"Db" = ( +/obj/item/maneki_neko{ + pixel_y = 4 + }, +/obj/structure/table, +/obj/machinery/status_display/random_message{ + pixel_y = 32; + firstline_to_secondline = list(ACCEPTING="DONATIONS") + }, +/obj/structure/fluff/fake_vent, +/turf/open/floor/iron, +/area/awaymission/museum/cafeteria) "De" = ( /obj/effect/oneway{ dir = 8 @@ -3383,6 +3695,11 @@ /obj/machinery/light/floor, /turf/open/floor/iron/dark, /area/awaymission/museum) +"DX" = ( +/obj/structure/reagent_dispensers/water_cooler, +/obj/machinery/light/warm/directional/west, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) "DY" = ( /turf/closed/indestructible/reinforced/titanium, /area/awaymission/museum) @@ -3400,8 +3717,8 @@ /obj/machinery/computer/old{ name = "replica computer"; dir = 4; - icon_keyboard = "rd_key"; - icon_screen = "rdcomp" + icon_screen = "rdcomp"; + icon_keyboard = "rd_key" }, /turf/open/floor/iron/smooth_large, /area/awaymission/museum) @@ -3421,8 +3738,8 @@ dir = 4 }, /obj/structure/fluff/wallsign/directional/south{ - dir = 4; - name = "Oddities" + name = "Oddities"; + dir = 4 }, /turf/open/floor/iron/dark, /area/awaymission/museum) @@ -3435,12 +3752,17 @@ /turf/open/indestructible/plating, /area/awaymission/museum) "Er" = ( -/obj/structure/chair/comfy/beige, /mob/living/basic/mothroach, +/obj/structure/chair/comfy/beige, /obj/effect/mapping_helpers/mob_buckler, /obj/machinery/light/warm/directional/west, /turf/open/floor/wood/large, /area/awaymission/museum) +"Es" = ( +/obj/structure/table, +/obj/effect/spawner/random/food_or_drink/donkpockets, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) "EA" = ( /obj/machinery/door/airlock/shuttle/glass, /obj/effect/turf_decal/siding/dark_blue, @@ -3460,9 +3782,9 @@ /area/awaymission/museum) "EG" = ( /obj/item/flashlight/flare{ - start_on = 1; icon_state = "flare-on"; - light_range = 4 + light_range = 4; + start_on = 1 }, /obj/effect/decal/cleanable/dirt/dust, /turf/open/indestructible/plating, @@ -3476,9 +3798,9 @@ "EM" = ( /obj/structure/table/wood, /obj/effect/spawner/random/food_or_drink/booze{ - loot = list(/obj/item/reagent_containers/cup/glass/bottle/beer = 10, /obj/item/reagent_containers/cup/glass/bottle/ale = 10, /obj/item/reagent_containers/cup/glass/bottle/beer/light = 5, /obj/item/reagent_containers/cup/glass/bottle/maltliquor = 5, /obj/item/reagent_containers/cup/glass/bottle/whiskey = 5, /obj/item/reagent_containers/cup/glass/bottle/gin = 5, /obj/item/reagent_containers/cup/glass/bottle/vodka = 5, /obj/item/reagent_containers/cup/glass/bottle/tequila = 5, /obj/item/reagent_containers/cup/glass/bottle/rum = 5, /obj/item/reagent_containers/cup/glass/bottle/vermouth = 5, /obj/item/reagent_containers/cup/glass/bottle/cognac = 5, /obj/item/reagent_containers/cup/glass/bottle/wine = 5, /obj/item/reagent_containers/cup/glass/bottle/kahlua = 5, /obj/item/reagent_containers/cup/glass/bottle/amaretto = 5, /obj/item/reagent_containers/cup/glass/bottle/hcider = 5, /obj/item/reagent_containers/cup/glass/bottle/absinthe = 5, /obj/item/reagent_containers/cup/glass/bottle/sake = 5, /obj/item/reagent_containers/cup/glass/bottle/grappa = 5, /obj/item/reagent_containers/cup/glass/bottle/applejack = 5, /obj/item/reagent_containers/cup/glass/bottle/wine_voltaic = 5, /obj/item/reagent_containers/cup/bottle/ethanol = 2, /obj/item/reagent_containers/cup/glass/bottle/fernet = 2, /obj/item/reagent_containers/cup/glass/bottle/champagne = 2, /obj/item/reagent_containers/cup/glass/bottle/absinthe/premium = 2, /obj/item/reagent_containers/cup/glass/bottle/goldschlager = 2, /obj/item/reagent_containers/cup/glass/bottle/patron = 1, /obj/item/reagent_containers/cup/glass/bottle/kong = 1, /obj/item/reagent_containers/cup/glass/bottle/lizardwine = 1, /obj/item/reagent_containers/cup/glass/bottle/vodka/badminka = 1, /obj/item/reagent_containers/cup/glass/bottle/trappist = 1); - spawn_random_offset = 2; - spawn_loot_count = 2 + loot = list(/obj/item/reagent_containers/cup/glass/bottle/beer=10, /obj/item/reagent_containers/cup/glass/bottle/ale=10, /obj/item/reagent_containers/cup/glass/bottle/beer/light=5, /obj/item/reagent_containers/cup/glass/bottle/maltliquor=5, /obj/item/reagent_containers/cup/glass/bottle/whiskey=5, /obj/item/reagent_containers/cup/glass/bottle/gin=5, /obj/item/reagent_containers/cup/glass/bottle/vodka=5, /obj/item/reagent_containers/cup/glass/bottle/tequila=5, /obj/item/reagent_containers/cup/glass/bottle/rum=5, /obj/item/reagent_containers/cup/glass/bottle/vermouth=5, /obj/item/reagent_containers/cup/glass/bottle/cognac=5, /obj/item/reagent_containers/cup/glass/bottle/wine=5, /obj/item/reagent_containers/cup/glass/bottle/kahlua=5, /obj/item/reagent_containers/cup/glass/bottle/amaretto=5, /obj/item/reagent_containers/cup/glass/bottle/hcider=5, /obj/item/reagent_containers/cup/glass/bottle/absinthe=5, /obj/item/reagent_containers/cup/glass/bottle/sake=5, /obj/item/reagent_containers/cup/glass/bottle/grappa=5, /obj/item/reagent_containers/cup/glass/bottle/applejack=5, /obj/item/reagent_containers/cup/glass/bottle/wine_voltaic=5, /obj/item/reagent_containers/cup/bottle/ethanol=2, /obj/item/reagent_containers/cup/glass/bottle/fernet=2, /obj/item/reagent_containers/cup/glass/bottle/champagne=2, /obj/item/reagent_containers/cup/glass/bottle/absinthe/premium=2, /obj/item/reagent_containers/cup/glass/bottle/goldschlager=2, /obj/item/reagent_containers/cup/glass/bottle/patron=1, /obj/item/reagent_containers/cup/glass/bottle/kong=1, /obj/item/reagent_containers/cup/glass/bottle/lizardwine=1, /obj/item/reagent_containers/cup/glass/bottle/vodka/badminka=1, /obj/item/reagent_containers/cup/glass/bottle/trappist=1); + spawn_loot_count = 2; + spawn_random_offset = 2 }, /turf/open/floor/wood/large, /area/awaymission/museum) @@ -3502,15 +3824,15 @@ /turf/open/floor/circuit/green, /area/awaymission/museum) "ET" = ( +/mob/living/basic/statue/mannequin{ + dir = 1 + }, /obj/structure/chair/stool/directional/north, /obj/effect/turf_decal/tile/green/opposingcorners, /obj/effect/decal/cleanable/dirt/dust, /obj/effect/turf_decal/siding/green{ dir = 1 }, -/mob/living/basic/statue/mannequin{ - dir = 1 - }, /turf/open/floor/iron, /area/awaymission/museum) "EU" = ( @@ -3520,8 +3842,8 @@ dir = 4 }, /obj/structure/fluff/wallsign/directional/north{ - dir = 4; - name = "Oddities" + name = "Oddities"; + dir = 4 }, /turf/open/floor/iron/dark, /area/awaymission/museum) @@ -3594,8 +3916,8 @@ /area/awaymission/museum) "Fw" = ( /obj/effect/decal/cleanable/blood/tracks{ - should_dry = 0; - name = "replica blood" + name = "replica blood"; + should_dry = 0 }, /obj/effect/mapping_helpers/burnt_floor, /obj/effect/decal/cleanable/dirt/dust, @@ -3604,8 +3926,8 @@ "FA" = ( /obj/structure/table/reinforced, /obj/item/circuitboard{ - icon_state = "printer"; - name = "fancy replica tech" + name = "fancy replica tech"; + icon_state = "printer" }, /turf/open/floor/iron/smooth_large, /area/awaymission/museum) @@ -3642,6 +3964,32 @@ /obj/structure/sign/departments/restroom/directional/south, /turf/open/floor/wood/large, /area/awaymission/museum) +"FO" = ( +/obj/effect/decal/cleanable/crayon/puzzle/pin{ + puzzle_id = "museum_r_wing_puzzle" + }, +/turf/closed/indestructible/reinforced, +/area/awaymission/museum) +"FU" = ( +/obj/effect/turf_decal/siding/dark_blue{ + dir = 1 + }, +/obj/effect/turf_decal/tile/neutral/opposingcorners, +/obj/structure/fluff/wallsign/directional/south{ + name = "Cafeteria"; + dir = 4 + }, +/turf/open/floor/iron/dark, +/area/awaymission/museum) +"FY" = ( +/obj/structure/chair/sofa/bench/left, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) +"Ga" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/machinery/door/airlock/grunge, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) "Gh" = ( /obj/effect/turf_decal/tile/neutral/opposingcorners, /obj/effect/turf_decal/siding/dark_blue{ @@ -3655,6 +4003,9 @@ /obj/structure/closet/crate/miningcar, /turf/open/floor/tram/plate, /area/awaymission/museum) +"Gn" = ( +/turf/open/floor/iron/white/small, +/area/awaymission/museum) "Gv" = ( /obj/effect/decal/cleanable/crayon{ icon_state = "l"; @@ -3675,20 +4026,27 @@ /obj/effect/turf_decal/bot, /turf/open/floor/iron, /area/awaymission/museum) +"GB" = ( +/obj/structure/table, +/obj/item/reagent_containers/cup/soda_cans/thirteenloko{ + pixel_y = 7 + }, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) "GE" = ( /obj/structure/plaque/static_plaque/golden/commission/meta, /turf/open/floor/mineral/gold, /area/awaymission/museum) "GG" = ( +/mob/living/basic/statue/mannequin{ + dir = 4 + }, /obj/structure/chair/office{ dir = 4 }, /obj/effect/turf_decal/siding/wood{ dir = 1 }, -/mob/living/basic/statue/mannequin{ - dir = 4 - }, /turf/open/floor/wood/tile, /area/awaymission/museum) "GO" = ( @@ -3762,8 +4120,8 @@ /area/awaymission/museum) "Hn" = ( /mob/living/basic/statue/mannequin{ - hat = /obj/item/clothing/head/costume/kitty; - dir = 8 + dir = 8; + hat = /obj/item/clothing/head/costume/kitty }, /obj/effect/turf_decal/trimline/yellow, /obj/effect/turf_decal/trimline/yellow/corner{ @@ -3797,7 +4155,7 @@ /area/awaymission/museum) "HA" = ( /obj/machinery/status_display/random_message{ - firstline_to_secondline = list("NO" = "LITTERING","YOU ARE" = "BEING WATCHED", "DO NOT TOUCH" = "THE EXHIBITS") + firstline_to_secondline = list(NO="LITTERING", "YOU ARE"="BEING WATCHED", "DO NOT TOUCH"="THE EXHIBITS") }, /turf/closed/indestructible/reinforced, /area/awaymission/museum) @@ -3806,7 +4164,7 @@ /obj/effect/turf_decal/siding/dark_blue, /obj/machinery/door/poddoor/shutters/indestructible{ dir = 8; - id = "nothing" + id = "museum_secret" }, /turf/open/floor/iron/dark, /area/awaymission/museum) @@ -3838,6 +4196,9 @@ "HP" = ( /turf/open/floor/iron/freezer, /area/awaymission/museum) +"HQ" = ( +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) "HU" = ( /obj/structure/railing{ dir = 4 @@ -3848,6 +4209,19 @@ /obj/effect/decal/cleanable/dirt/dust, /turf/open/indestructible/plating, /area/awaymission/museum) +"HW" = ( +/obj/machinery/button/door/directional/north{ + name = "Lock Control"; + id = "museum_toilet1"; + specialfunctions = 4; + normaldoorcontrol = 1 + }, +/obj/structure/toilet/museum{ + dir = 4 + }, +/obj/machinery/light/small/dim/directional/west, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) "HY" = ( /obj/machinery/light/floor, /obj/effect/decal/cleanable/dirt/dust, @@ -3857,6 +4231,12 @@ /obj/machinery/light/warm/dim/directional/north, /turf/open/floor/carpet, /area/awaymission/museum) +"Id" = ( +/obj/effect/decal/cleanable/crayon/puzzle/pin{ + puzzle_id = "museum_r_wing_puzzle" + }, +/turf/closed/indestructible/wood, +/area/awaymission/museum) "Ij" = ( /obj/machinery/door/airlock/external, /obj/effect/mapping_helpers/airlock/locked, @@ -3871,6 +4251,9 @@ /obj/structure/holosign/barrier/engineering, /turf/open/chasm/true/no_smooth, /area/awaymission/museum) +"Ir" = ( +/turf/closed/indestructible/fakedoor, +/area/awaymission/museum) "Iu" = ( /turf/open/floor/mineral/titanium/blue, /area/awaymission/museum) @@ -3898,6 +4281,12 @@ /obj/item/food/grilled_beef_gyro, /turf/open/floor/wood/tile, /area/awaymission/museum) +"IG" = ( +/obj/structure/fluff/fake_camera{ + dir = 9 + }, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) "IH" = ( /obj/machinery/portable_atmospherics/canister/water_vapor, /obj/effect/decal/cleanable/dirt/dust, @@ -3914,11 +4303,22 @@ /obj/effect/turf_decal/stripes, /turf/open/floor/iron/smooth_large, /area/awaymission/museum) +"IX" = ( +/mob/living/basic/statue/mannequin{ + name = "cafeteria patron"; + held_item = /obj/item/knife + }, +/obj/structure/chair/sofa/bench{ + dir = 1 + }, +/obj/effect/mapping_helpers/mob_buckler, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) "IZ" = ( +/mob/living/basic/mothroach, /obj/structure/chair/comfy/beige{ dir = 4 }, -/mob/living/basic/mothroach, /obj/effect/mapping_helpers/mob_buckler, /turf/open/floor/wood/large, /area/awaymission/museum) @@ -3959,6 +4359,13 @@ /obj/machinery/light/small/dim/directional/west, /turf/open/floor/carpet/executive, /area/awaymission/museum) +"Jl" = ( +/obj/machinery/status_display/random_message{ + pixel_x = 32; + firstline_to_secondline = list(CAFETERIA="YUMMY", CAFETERIA="HOTDOGS", ENJOY="YOUR MEAL") + }, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) "Jn" = ( /obj/structure/table/reinforced, /obj/effect/spawner/random/entertainment/toy, @@ -3967,6 +4374,13 @@ /obj/effect/turf_decal/siding/dark_blue, /turf/open/floor/mineral/titanium/blue, /area/awaymission/museum) +"Jp" = ( +/obj/machinery/door/airlock{ + name = "Restroom Cabin 1"; + id_tag = "museum_toilet1" + }, +/turf/open/floor/iron, +/area/awaymission/museum) "Js" = ( /obj/effect/turf_decal/tile/neutral/opposingcorners, /obj/effect/turf_decal/siding/dark_blue{ @@ -4077,11 +4491,24 @@ /turf/open/floor/catwalk_floor, /area/awaymission/museum) "Kx" = ( -/obj/effect/decal/cleanable/dirt/dust, /mob/living/basic/skeleton, +/obj/effect/decal/cleanable/dirt/dust, /obj/effect/spawner/random/maintenance/three, /turf/open/indestructible/plating, /area/awaymission/museum) +"Kz" = ( +/obj/structure/sign/warning/no_smoking/directional/west, +/obj/machinery/computer/terminal/museum{ + name = "extinguisher manual terminal"; + dir = 4; + content = list("Instructions; stand upright; remove safety clip; aim noozle at base of fire... the rest is up to you.") + }, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) +"KB" = ( +/obj/structure/sign/warning/no_smoking/circle/directional/east, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) "KI" = ( /turf/open/floor/iron/dark/side{ dir = 10 @@ -4112,7 +4539,7 @@ /area/awaymission/museum) "Lp" = ( /obj/machinery/status_display/random_message{ - firstline_to_secondline = list("NO" = "LITTERING","YOU ARE" = "BEING WATCHED", "DO NOT TOUCH" = "THE EXHIBITS") + firstline_to_secondline = list(NO="LITTERING", "YOU ARE"="BEING WATCHED", "DO NOT TOUCH"="THE EXHIBITS") }, /turf/closed/indestructible/reinforced/titanium/nodiagonal, /area/awaymission/museum) @@ -4162,6 +4589,12 @@ }, /turf/open/floor/iron/dark, /area/awaymission/museum) +"LK" = ( +/obj/structure/table, +/obj/item/piggy_bank/museum, +/obj/machinery/light/warm/directional/north, +/turf/open/floor/iron, +/area/awaymission/museum/cafeteria) "LN" = ( /obj/effect/decal/cleanable/glass/titanium, /turf/open/floor/iron/dark/textured_large, @@ -4197,6 +4630,11 @@ "Ma" = ( /turf/open/chasm/true/no_smooth, /area/awaymission/museum) +"Mb" = ( +/obj/effect/turf_decal/siding/dark_blue, +/obj/effect/turf_decal/tile/neutral/opposingcorners, +/turf/open/floor/iron/dark, +/area/awaymission/museum) "Mg" = ( /obj/structure/plaque/static_plaque/golden/commission/tram, /turf/closed/indestructible/reinforced, @@ -4215,9 +4653,9 @@ /area/awaymission/museum) "Mv" = ( /mob/living/basic/statue/mannequin{ + name = "Michael Trasen"; dir = 4; - held_item = /obj/item/wrench; - name = "Michael Trasen" + held_item = /obj/item/wrench }, /obj/effect/turf_decal/stripes{ dir = 4 @@ -4300,10 +4738,16 @@ /obj/structure/plaque/static_plaque/golden/commission/birdboat, /turf/closed/indestructible/reinforced, /area/awaymission/museum) +"Nn" = ( +/obj/machinery/door/airlock/public{ + name = "Restrooms" + }, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) "Ns" = ( /obj/structure/table/wood, /obj/effect/spawner/random/food_or_drink/booze{ - loot = list(/obj/item/reagent_containers/cup/glass/bottle/beer = 10, /obj/item/reagent_containers/cup/glass/bottle/ale = 10, /obj/item/reagent_containers/cup/glass/bottle/beer/light = 5, /obj/item/reagent_containers/cup/glass/bottle/maltliquor = 5, /obj/item/reagent_containers/cup/glass/bottle/whiskey = 5, /obj/item/reagent_containers/cup/glass/bottle/gin = 5, /obj/item/reagent_containers/cup/glass/bottle/vodka = 5, /obj/item/reagent_containers/cup/glass/bottle/tequila = 5, /obj/item/reagent_containers/cup/glass/bottle/rum = 5, /obj/item/reagent_containers/cup/glass/bottle/vermouth = 5, /obj/item/reagent_containers/cup/glass/bottle/cognac = 5, /obj/item/reagent_containers/cup/glass/bottle/wine = 5, /obj/item/reagent_containers/cup/glass/bottle/kahlua = 5, /obj/item/reagent_containers/cup/glass/bottle/amaretto = 5, /obj/item/reagent_containers/cup/glass/bottle/hcider = 5, /obj/item/reagent_containers/cup/glass/bottle/absinthe = 5, /obj/item/reagent_containers/cup/glass/bottle/sake = 5, /obj/item/reagent_containers/cup/glass/bottle/grappa = 5, /obj/item/reagent_containers/cup/glass/bottle/applejack = 5, /obj/item/reagent_containers/cup/glass/bottle/wine_voltaic = 5, /obj/item/reagent_containers/cup/bottle/ethanol = 2, /obj/item/reagent_containers/cup/glass/bottle/fernet = 2, /obj/item/reagent_containers/cup/glass/bottle/champagne = 2, /obj/item/reagent_containers/cup/glass/bottle/absinthe/premium = 2, /obj/item/reagent_containers/cup/glass/bottle/goldschlager = 2, /obj/item/reagent_containers/cup/glass/bottle/patron = 1, /obj/item/reagent_containers/cup/glass/bottle/kong = 1, /obj/item/reagent_containers/cup/glass/bottle/lizardwine = 1, /obj/item/reagent_containers/cup/glass/bottle/vodka/badminka = 1, /obj/item/reagent_containers/cup/glass/bottle/trappist = 1); + loot = list(/obj/item/reagent_containers/cup/glass/bottle/beer=10, /obj/item/reagent_containers/cup/glass/bottle/ale=10, /obj/item/reagent_containers/cup/glass/bottle/beer/light=5, /obj/item/reagent_containers/cup/glass/bottle/maltliquor=5, /obj/item/reagent_containers/cup/glass/bottle/whiskey=5, /obj/item/reagent_containers/cup/glass/bottle/gin=5, /obj/item/reagent_containers/cup/glass/bottle/vodka=5, /obj/item/reagent_containers/cup/glass/bottle/tequila=5, /obj/item/reagent_containers/cup/glass/bottle/rum=5, /obj/item/reagent_containers/cup/glass/bottle/vermouth=5, /obj/item/reagent_containers/cup/glass/bottle/cognac=5, /obj/item/reagent_containers/cup/glass/bottle/wine=5, /obj/item/reagent_containers/cup/glass/bottle/kahlua=5, /obj/item/reagent_containers/cup/glass/bottle/amaretto=5, /obj/item/reagent_containers/cup/glass/bottle/hcider=5, /obj/item/reagent_containers/cup/glass/bottle/absinthe=5, /obj/item/reagent_containers/cup/glass/bottle/sake=5, /obj/item/reagent_containers/cup/glass/bottle/grappa=5, /obj/item/reagent_containers/cup/glass/bottle/applejack=5, /obj/item/reagent_containers/cup/glass/bottle/wine_voltaic=5, /obj/item/reagent_containers/cup/bottle/ethanol=2, /obj/item/reagent_containers/cup/glass/bottle/fernet=2, /obj/item/reagent_containers/cup/glass/bottle/champagne=2, /obj/item/reagent_containers/cup/glass/bottle/absinthe/premium=2, /obj/item/reagent_containers/cup/glass/bottle/goldschlager=2, /obj/item/reagent_containers/cup/glass/bottle/patron=1, /obj/item/reagent_containers/cup/glass/bottle/kong=1, /obj/item/reagent_containers/cup/glass/bottle/lizardwine=1, /obj/item/reagent_containers/cup/glass/bottle/vodka/badminka=1, /obj/item/reagent_containers/cup/glass/bottle/trappist=1); spawn_random_offset = 2 }, /turf/open/floor/wood/large, @@ -4324,8 +4768,8 @@ /obj/structure/railing, /obj/structure/table, /obj/item/clothing/mask/cigarette/cigar{ - lit = 1; - icon_state = "cigaron" + icon_state = "cigaron"; + lit = 1 }, /turf/open/chasm/true/no_smooth, /area/awaymission/museum) @@ -4431,11 +4875,11 @@ /turf/open/floor/iron, /area/awaymission/museum) "OS" = ( +/mob/living/basic/mothroach, /obj/structure/chair/stool/bar/directional/east{ can_buckle = 1 }, /obj/effect/mapping_helpers/mob_buckler, -/mob/living/basic/mothroach, /turf/open/floor/wood/large, /area/awaymission/museum) "OT" = ( @@ -4481,6 +4925,9 @@ /obj/item/food/kebab/pineapple_skewer, /turf/open/floor/iron/freezer, /area/awaymission/museum) +"Ph" = ( +/turf/closed/indestructible/reinforced, +/area/awaymission/museum/cafeteria) "Pi" = ( /obj/effect/turf_decal/tile/neutral/opposingcorners, /obj/effect/turf_decal/siding/dark_blue/corner{ @@ -4584,8 +5031,8 @@ "PZ" = ( /obj/effect/mapping_helpers/broken_floor, /obj/item/flashlight/flare{ - start_on = 1; - icon_state = "flare-on" + icon_state = "flare-on"; + start_on = 1 }, /turf/open/indestructible/plating, /area/awaymission/museum) @@ -4756,9 +5203,9 @@ }, /obj/structure/fluff{ name = "replica prototype autolathe"; + desc = "A non-functional replica of a prototype Autolathe."; icon = 'icons/obj/machines/lathes.dmi'; - icon_state = "autolathe"; - desc = "A non-functional replica of a prototype Autolathe." + icon_state = "autolathe" }, /turf/open/floor/iron/smooth_corner{ dir = 4 @@ -4803,6 +5250,18 @@ /obj/effect/decal/cleanable/dirt/dust, /turf/open/floor/iron/white, /area/awaymission/museum) +"RQ" = ( +/obj/effect/turf_decal/tile/neutral/opposingcorners, +/obj/effect/turf_decal/siding/dark_blue{ + dir = 1 + }, +/obj/structure/fluff/fake_camera, +/obj/effect/decal/puzzle_dots{ + pixel_y = -32; + id = "museum_r_wing_puzzle" + }, +/turf/open/floor/iron/dark, +/area/awaymission/museum) "RR" = ( /obj/machinery/suit_storage_unit/open, /obj/effect/turf_decal/box, @@ -4829,8 +5288,8 @@ /turf/open/floor/bluespace, /area/awaymission/museum) "Se" = ( -/obj/structure/chair/comfy/beige, /mob/living/basic/mothroach, +/obj/structure/chair/comfy/beige, /obj/effect/mapping_helpers/mob_buckler, /turf/open/floor/wood/large, /area/awaymission/museum) @@ -4839,6 +5298,11 @@ /obj/structure/railing, /turf/open/floor/holofloor/asteroid, /area/awaymission/museum) +"Sm" = ( +/obj/effect/decal/cleanable/dirt/dust, +/obj/structure/fluff/fake_scrubber, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) "So" = ( /obj/machinery/shower/directional/east, /turf/open/floor/iron/white/textured_large, @@ -4849,7 +5313,7 @@ /area/awaymission/museum) "Sv" = ( /obj/machinery/status_display/random_message{ - firstline_to_secondline = list("SOUVENIR" = "SHOP") + firstline_to_secondline = list(SOUVENIR="SHOP") }, /turf/closed/indestructible/reinforced, /area/awaymission/museum) @@ -4880,7 +5344,11 @@ }, /obj/machinery/door/poddoor/shutters/indestructible{ dir = 8; - id = "nothing" + id = "museum_secret" + }, +/obj/machinery/puzzle/password/pin/directional/south{ + id = "museum_r_wing_puzzle"; + late_initialize_pop = 1 }, /turf/open/floor/iron/dark, /area/awaymission/museum) @@ -4889,6 +5357,14 @@ /obj/item/stack/spacecash/c1000, /turf/open/indestructible/plating, /area/awaymission/museum) +"Ta" = ( +/obj/machinery/light/directional/west, +/obj/effect/decal/cleanable/crayon/puzzle/pin{ + pixel_x = -32; + puzzle_id = "museum_r_wing_puzzle" + }, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) "Tr" = ( /obj/structure/plaque/static_plaque/golden/commission/kilo, /obj/machinery/light/floor, @@ -4923,6 +5399,13 @@ }, /turf/open/indestructible/plating, /area/awaymission/museum) +"TC" = ( +/obj/machinery/door/airlock/multi_tile/public/glass{ + name = "Cafeteria"; + dir = 4 + }, +/turf/open/floor/iron, +/area/awaymission/museum/cafeteria) "TF" = ( /turf/open/floor/iron/stairs/right{ dir = 4 @@ -4998,8 +5481,8 @@ dir = 8 }, /obj/structure/fluff/wallsign/directional/east{ - dir = 2; - name = "Exit" + name = "Exit"; + dir = 2 }, /turf/open/floor/iron/dark, /area/awaymission/museum) @@ -5037,8 +5520,8 @@ /obj/effect/turf_decal/tile/neutral/opposingcorners, /obj/structure/table, /obj/item/computer_disk{ - icon_state = "datadisk_hydro"; - name = "plant data disk" + name = "plant data disk"; + icon_state = "datadisk_hydro" }, /turf/open/floor/iron/dark, /area/awaymission/museum) @@ -5066,6 +5549,12 @@ /mob/living/basic/mothroach, /turf/open/misc/beach/sand, /area/awaymission/museum/mothroachvoid) +"UH" = ( +/obj/structure/mirror/broken/directional/east, +/obj/structure/sink/directional/west, +/obj/effect/decal/cleanable/oil/slippery, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) "UI" = ( /obj/effect/turf_decal/tile/green/opposingcorners, /obj/effect/decal/cleanable/dirt/dust, @@ -5106,8 +5595,8 @@ /area/awaymission/museum) "UT" = ( /obj/machinery/status_display/random_message{ - firstline_to_secondline = list("NO" = "LITTERING","YOU ARE" = "BEING WATCHED", "DO NOT TOUCH" = "THE EXHIBITS"); - pixel_x = 32 + pixel_x = 32; + firstline_to_secondline = list(NO="LITTERING", "YOU ARE"="BEING WATCHED", "DO NOT TOUCH"="THE EXHIBITS") }, /turf/open/floor/carpet, /area/awaymission/museum) @@ -5129,6 +5618,14 @@ /obj/effect/decal/cleanable/dirt/dust, /turf/open/floor/iron/smooth_half, /area/awaymission/museum) +"Vb" = ( +/obj/effect/replica_spawner{ + obvious_replica = 0; + pixel_y = 30; + target_path = /obj/structure/sign/painting/eldritch/weeping + }, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) "Vc" = ( /obj/machinery/light/warm/directional/east, /obj/effect/turf_decal/tile/neutral/opposingcorners, @@ -5380,6 +5877,25 @@ }, /turf/open/floor/iron/dark, /area/awaymission/museum) +"WY" = ( +/obj/machinery/button/door/directional/north{ + name = "Lock Control"; + id = "museum_toilet2"; + specialfunctions = 4; + normaldoorcontrol = 1 + }, +/obj/structure/toilet/museum{ + dir = 4 + }, +/obj/machinery/light/small/dim/directional/west, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) +"WZ" = ( +/obj/structure/chair/sofa/bench{ + dir = 1 + }, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) "Xa" = ( /obj/structure/broken_flooring/corner/always_floorplane{ dir = 2 @@ -5402,6 +5918,10 @@ "Xh" = ( /turf/closed/indestructible/fakedoor/maintenance, /area/awaymission/museum) +"Xi" = ( +/obj/structure/chair/sofa/bench/right, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) "Xo" = ( /obj/structure/lattice/catwalk/mining, /obj/structure/railing, @@ -5502,8 +6022,8 @@ /turf/closed/indestructible/reinforced/titanium/nodiagonal, /area/awaymission/museum) "Yg" = ( -/obj/effect/decal/cleanable/dirt/dust, /mob/living/basic/cockroach, +/obj/effect/decal/cleanable/dirt/dust, /turf/open/indestructible/plating, /area/awaymission/museum) "Yk" = ( @@ -5512,6 +6032,12 @@ }, /turf/open/floor/iron, /area/awaymission/museum) +"Yn" = ( +/obj/machinery/light/warm/directional/east, +/obj/effect/decal/cleanable/dirt/dust, +/obj/structure/fluff/fake_camera, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) "Yr" = ( /obj/effect/turf_decal/tile/neutral/opposingcorners, /obj/structure/table/reinforced, @@ -5521,13 +6047,21 @@ /obj/item/reagent_containers/cup/glass/mug/nanotrasen, /turf/open/floor/iron/dark, /area/awaymission/museum) +"Yy" = ( +/obj/machinery/computer/terminal/museum{ + name = "donation info terminal"; + dir = 8; + content = list("We're once again asking for your financial support; We love our job, but love alone can only get us so far. Please consider leaving a donation to help keep the musuem running. Thank you.") + }, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) "Yz" = ( -/obj/effect/turf_decal/siding, /mob/living/basic/statue/mannequin{ dir = 8; held_item = /obj/item/pickaxe; hat = /obj/item/clothing/suit/hooded/explorer }, +/obj/effect/turf_decal/siding, /obj/structure/railing, /turf/open/floor/holofloor/asteroid, /area/awaymission/museum) @@ -5571,8 +6105,15 @@ /obj/item/reagent_containers/cup/glass/coffee/no_lid{ pixel_x = 12 }, +/obj/item/paper/fluff/museum/numbers_on_walls, /turf/open/floor/iron, /area/awaymission/museum) +"Za" = ( +/obj/structure/chair/sofa/bench/right{ + dir = 1 + }, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) "Zf" = ( /obj/structure/plaque/static_plaque/golden/commission/donut, /turf/closed/indestructible/reinforced, @@ -5589,6 +6130,11 @@ /obj/structure/lattice, /turf/open/chasm/true/no_smooth, /area/awaymission/museum) +"Zo" = ( +/obj/structure/table, +/obj/effect/spawner/random/food_or_drink/snack, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) "Zp" = ( /obj/structure/plaque/static_plaque/golden/commission/icebox, /turf/closed/indestructible/reinforced, @@ -5613,6 +6159,16 @@ }, /turf/open/floor/iron/dark, /area/awaymission/museum) +"Zx" = ( +/obj/structure/chair/sofa/bench, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) +"Zz" = ( +/obj/effect/mapping_helpers/turn_off_lights_with_lightswitch, +/obj/machinery/light_switch/directional/west, +/turf/open/floor/iron/cafeteria, +/area/awaymission/museum/cafeteria) "ZB" = ( /obj/machinery/door/airlock/maintenance_hatch, /obj/structure/broken_flooring/side/always_floorplane/directional/east, @@ -5644,6 +6200,13 @@ }, /turf/open/floor/iron/dark, /area/awaymission/museum) +"ZO" = ( +/obj/structure/sink/directional/south{ + pixel_y = 6 + }, +/obj/structure/mirror/directional/north, +/turf/open/floor/iron/white/small, +/area/awaymission/museum) "ZP" = ( /obj/structure/statue/gold/ce{ anchored = 1 @@ -26150,7 +26713,7 @@ re re re re -re +Uq re re re @@ -26403,12 +26966,12 @@ re re re re -re -re -re -re -re -re +FK +FK +FK +FK +FK +Uq re re re @@ -26660,11 +27223,11 @@ re re re re -re -re -re -re -re +FK +kZ +FK +oD +FK re re re @@ -26915,13 +27478,13 @@ Wc FK re re -re -re -re -re -re -re -re +FK +FO +FK +ta +FK +AQ +FK re re re @@ -27172,13 +27735,13 @@ fy FK re re -re -re -re -re -re -re -re +FK +jO +ry +Gn +Ta +Gn +FK re re re @@ -27429,13 +27992,13 @@ tD FK re re -re -re -re -re -re -re -re +FK +FK +FO +IG +Gn +ax +FK re re re @@ -27687,12 +28250,12 @@ FK re re re -re -re -re -re -re -re +FK +ZO +Gn +Gn +Gn +FK re re re @@ -27947,7 +28510,7 @@ FK FK FK FK -FK +Nn FK FK Uq @@ -29699,7 +30262,7 @@ aW MF MF MF -MF +Id Re PZ Ma @@ -34375,7 +34938,7 @@ FK FK FK FK -FK +FO FK FK FK @@ -35113,7 +35676,7 @@ re re re re -FK +FO qw XK PP @@ -35916,7 +36479,7 @@ FK FK FK Yd -FK +FO NO dZ mn @@ -35958,6 +36521,9 @@ nu FK FK FK +FK +FK +FK Uq re re @@ -35995,9 +36561,6 @@ re re re re -re -re -re "} (119,1,1) = {" re @@ -36214,10 +36777,10 @@ qQ xI gT Qb -FK -re -re -re +Ga +gg +iJ +FO re re re @@ -36472,6 +37035,9 @@ xI xd ns FK +FK +FK +FK re re re @@ -36509,9 +37075,6 @@ re re re re -re -re -re "} (121,1,1) = {" re @@ -36731,7 +37294,7 @@ BO FK re re -re +Uq re re re @@ -36986,7 +37549,7 @@ Nc FK FK FK -Uq +re re re re @@ -37461,7 +38024,7 @@ FK FK FK PB -FK +FO Ma Ma Ma @@ -40781,7 +41344,7 @@ FK FK FK FK -lb +eW uY rQ FK @@ -42582,7 +43145,7 @@ FK FK lb uY -qX +RQ FK FK FK @@ -43085,19 +43648,19 @@ re re re re +FK +FK +FK +FK +FK +FK +FK re -re -re -re -re -re -re -re -Uq -re -re -re -Uq +FK +aa +uY +ci +jC re re re @@ -43342,19 +43905,19 @@ re re re re -re -re -re -re -re -re -re -re -re -re -re -re -re +FK +aO +FK +WY +FK +HW +FK +FK +FK +Mb +TM +wV +FK re re re @@ -43599,19 +44162,19 @@ re re re re -re -re -re -re -re -re -re -re -re -re -re -re -re +FK +wg +FK +us +FK +Jp +FK +um +FK +Mb +uY +wV +FK re re re @@ -43856,19 +44419,19 @@ re re re re -re -re -re -re -re -re -re -re -re -re -re -re -re +FK +Gn +Gn +Gn +Sm +Gn +Kz +Gn +FK +ju +uY +ci +Ir re re re @@ -44113,19 +44676,19 @@ re re re re -re -re -re -re -re -re -re -re -re -re -re -re -re +FK +nY +Gn +Gn +Gn +Gn +Gn +Gn +kx +uY +uY +wV +FK re re re @@ -44370,19 +44933,19 @@ re re re re -re -re -re -re -re -re -re -re -re -re -re -re -re +FK +ut +UH +gE +CG +Gn +CG +Yn +FK +RC +TM +FU +FK re re re @@ -44627,20 +45190,20 @@ re re re re -re -re -re -re -re -re -re -re -re -re -re -re -re -re +FK +FK +FK +FK +FK +FK +FK +FK +FK +Mb +uY +wV +FK +Uq re re re @@ -44892,11 +45455,11 @@ re re re re -re -re -re -re -re +FK +pN +uY +hS +oP re re re @@ -45149,11 +45712,11 @@ re re re re -re -re -re -re -re +FK +FK +pz +kL +FK re re re @@ -45403,16 +45966,16 @@ re re re re -re -re -re -re -re -re -re -re -re -re +Ph +Ph +Ph +Ph +Ph +ms +TC +Ph +Ph +Ph re re re @@ -45660,16 +46223,16 @@ re re re re -re -re -re -re -re -re -re -re -re -re +Ph +DX +HQ +fl +Zz +HQ +HQ +HQ +ac +Ph re re re @@ -45917,16 +46480,16 @@ re re re re -re -re -re -re -re -re -re -re -re -re +Ph +qK +HQ +HQ +HQ +HQ +HQ +HQ +HQ +vG re re re @@ -46174,16 +46737,16 @@ re re re re -re -re -re -re -re -re -re -re -re -re +Ph +uH +HQ +Xi +xp +nZ +Xi +Es +nZ +Ph re re re @@ -46431,17 +46994,17 @@ re re re re -re -re -re -re -re -re -re -re -re -re -re +Ph +Vb +HQ +Zx +hm +WZ +ql +xp +WZ +Ph +Uq re re re @@ -46688,16 +47251,16 @@ re re re re -re -re -re -re -re -re -re -re -re -re +Ph +HQ +HQ +ql +Zo +WZ +ql +GB +IX +Ph re re re @@ -46945,16 +47508,16 @@ re re re re -re -re -re -re -re -re -re -re -re -re +Ph +Db +HQ +FY +xO +Za +FY +xp +Za +Ph re re re @@ -47202,16 +47765,16 @@ re re re re -re -re -re -re -re -re -re -re -re -re +Ph +LK +Yy +HQ +HQ +KB +Jl +HQ +cY +Ph re re re @@ -47459,16 +48022,16 @@ re re re re -re -re -re -re -re -re -re -re -re -re +Ph +Ph +Ph +Ph +Ph +Ph +Ph +Ph +Ph +Ph re re re diff --git a/_maps/map_files/Deathmatch/meatower.dmm b/_maps/map_files/Deathmatch/meatower.dmm index 47613f8988554..fb2359fa28879 100644 --- a/_maps/map_files/Deathmatch/meatower.dmm +++ b/_maps/map_files/Deathmatch/meatower.dmm @@ -198,7 +198,7 @@ /obj/machinery/conveyor/auto{ dir = 4 }, -/obj/item/pizzabox/margherita/robo, +/obj/item/pizzabox/margherita, /obj/effect/turf_decal/siding/dark{ dir = 1 }, diff --git a/_maps/map_files/Deathmatch/mech_madness.dmm b/_maps/map_files/Deathmatch/mech_madness.dmm index d36ccfb16cc20..3f804d2ee5452 100644 --- a/_maps/map_files/Deathmatch/mech_madness.dmm +++ b/_maps/map_files/Deathmatch/mech_madness.dmm @@ -664,7 +664,7 @@ /area/deathmatch) "ot" = ( /obj/structure/mop_bucket/janitorialcart{ - dir = 4 + dir = 4 }, /turf/open/floor/engine, /area/deathmatch) diff --git a/_maps/map_files/Deathmatch/starwars.dmm b/_maps/map_files/Deathmatch/starwars.dmm index c1f005461073a..c7654810360e1 100644 --- a/_maps/map_files/Deathmatch/starwars.dmm +++ b/_maps/map_files/Deathmatch/starwars.dmm @@ -1142,9 +1142,6 @@ }, /turf/open/indestructible/large, /area/deathmatch) -"US" = ( -/turf/closed/indestructible/reinforced, -/area/deathmatch) "Ve" = ( /obj/effect/turf_decal/tile/red/full, /obj/structure/barricade/security/murderdome, @@ -1320,7 +1317,7 @@ tV tV IC IC -US +KX Vz sZ sZ diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm index 28143ce53ea8b..30e548835e8be 100644 --- a/_maps/map_files/Deltastation/DeltaStation2.dmm +++ b/_maps/map_files/Deltastation/DeltaStation2.dmm @@ -8724,10 +8724,10 @@ "cdJ" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, +/obj/structure/cable, /obj/effect/turf_decal/tile/neutral{ dir = 1 }, -/obj/structure/cable, /turf/open/floor/iron/dark/corner{ dir = 1 }, @@ -10426,7 +10426,12 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/newscaster/directional/west, /obj/structure/cable, -/turf/open/floor/iron, +/obj/effect/turf_decal/tile/neutral{ + dir = 1 + }, +/turf/open/floor/iron/dark/corner{ + dir = 1 + }, /area/station/hallway/secondary/exit/departure_lounge) "czL" = ( /obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{ @@ -36979,7 +36984,6 @@ /turf/open/floor/iron, /area/station/security/prison/garden) "jgl" = ( -/obj/machinery/door/firedoor, /obj/machinery/door/airlock/grunge{ name = "Morgue" }, diff --git a/_maps/map_files/IceBoxStation/IceBoxStation.dmm b/_maps/map_files/IceBoxStation/IceBoxStation.dmm index f1137f100e9c1..759e4f09d46df 100644 --- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm +++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm @@ -8403,11 +8403,10 @@ /turf/open/floor/plating, /area/station/science/xenobiology) "cyo" = ( -/obj/machinery/stasis{ - dir = 4 +/obj/effect/turf_decal/trimline/blue/filled/line{ + dir = 9 }, -/obj/effect/turf_decal/tile/blue/full, -/turf/open/floor/iron/large, +/turf/open/floor/iron/white, /area/station/medical/treatment_center) "cyA" = ( /obj/machinery/door/airlock/maintenance, @@ -20593,10 +20592,12 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/structure/cable, -/obj/effect/turf_decal/trimline/blue/filled/corner, /obj/effect/turf_decal/trimline/blue/filled/corner{ dir = 8 }, +/obj/effect/turf_decal/trimline/blue/filled/line{ + dir = 4 + }, /turf/open/floor/iron/white, /area/station/medical/medbay/central) "gnh" = ( @@ -33374,16 +33375,12 @@ /turf/open/floor/iron, /area/station/maintenance/disposal/incinerator) "kmG" = ( -/obj/structure/table/glass, -/obj/item/reagent_containers/cup/bottle/epinephrine, -/obj/item/reagent_containers/cup/bottle/multiver{ - pixel_x = 6 - }, -/obj/item/reagent_containers/syringe, /obj/machinery/defibrillator_mount/directional/north, -/obj/item/radio/intercom/directional/west, /obj/effect/turf_decal/tile/blue/full, /obj/machinery/light/cold/directional/north, +/obj/machinery/stasis{ + dir = 4 + }, /turf/open/floor/iron/large, /area/station/medical/treatment_center) "kmH" = ( @@ -33549,15 +33546,6 @@ }, /turf/open/floor/iron, /area/mine/laborcamp) -"kpO" = ( -/obj/effect/turf_decal/trimline/blue/filled/warning{ - dir = 8 - }, -/obj/effect/turf_decal/trimline/blue/filled/corner{ - dir = 4 - }, -/turf/open/floor/iron/white, -/area/station/medical/treatment_center) "kpX" = ( /obj/structure/bed/medical{ dir = 4 @@ -45378,7 +45366,8 @@ /obj/structure/cable, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/obj/effect/turf_decal/trimline/blue/filled/corner, +/obj/effect/turf_decal/trimline/blue/filled/line, +/obj/machinery/airalarm/directional/south, /turf/open/floor/iron/white, /area/station/medical/medbay/central) "nRm" = ( @@ -47339,7 +47328,7 @@ start_active = 1 }, /turf/open/misc/asteroid/snow/icemoon, -/area/station/ai_monitored/turret_protected/aisat/maint) +/area/icemoon/surface/outdoors/nospawn) "oxB" = ( /obj/machinery/door/airlock/maintenance, /obj/effect/mapping_helpers/airlock/abandoned, @@ -51938,13 +51927,6 @@ "pOL" = ( /turf/open/floor/iron/white, /area/station/science/ordnance) -"pOU" = ( -/obj/effect/turf_decal/trimline/blue/filled/line{ - dir = 6 - }, -/obj/machinery/airalarm/directional/south, -/turf/open/floor/iron/white, -/area/station/medical/medbay/central) "pOV" = ( /obj/structure/rack, /obj/item/clothing/suit/hazardvest, @@ -52893,6 +52875,17 @@ "qfh" = ( /turf/open/floor/iron/recharge_floor, /area/station/science/robotics/mechbay) +"qfi" = ( +/obj/structure/table/glass, +/obj/item/reagent_containers/cup/bottle/epinephrine, +/obj/item/reagent_containers/cup/bottle/multiver{ + pixel_x = 6 + }, +/obj/item/reagent_containers/syringe, +/obj/effect/turf_decal/tile/blue/full, +/obj/item/radio/intercom/directional/west, +/turf/open/floor/iron/large, +/area/station/medical/treatment_center) "qfj" = ( /obj/effect/turf_decal/tile/neutral/half/contrasted{ dir = 4 @@ -56034,7 +56027,7 @@ start_active = 1 }, /turf/open/misc/asteroid/snow/icemoon, -/area/station/ai_monitored/turret_protected/aisat/maint) +/area/icemoon/surface/outdoors/nospawn) "rbs" = ( /obj/effect/turf_decal/tile/yellow, /obj/machinery/light/directional/east, @@ -66445,6 +66438,7 @@ /obj/structure/cable, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, +/obj/effect/turf_decal/trimline/blue/filled/corner, /turf/open/floor/iron/white, /area/station/medical/medbay/central) "upa" = ( @@ -246023,7 +246017,7 @@ cXu frP oIJ nRd -pOU +lwQ tHr tHr bxU @@ -246281,7 +246275,7 @@ eqp cTV fHz lwQ -lwQ +qfi sEK eHg ahL @@ -246540,7 +246534,7 @@ sFG lwQ kmG cyo -kpO +lup lup aCA jDn @@ -246796,8 +246790,8 @@ mcW fPb bEL efK -evp juw +nji tkf ikz oSQ diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm index 443f7049dea71..7ac2807b4801e 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -43180,8 +43180,8 @@ }, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/obj/machinery/door/airlock/research/glass{ - name = "Chemistry Lab" +/obj/machinery/door/airlock/medical/glass{ + name = "Chemistry" }, /obj/effect/mapping_helpers/airlock/access/all/medical/chemistry, /obj/machinery/door/firedoor, @@ -45173,7 +45173,6 @@ }, /obj/structure/cable, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, -/obj/effect/mapping_helpers/airlock/access/any/medical/general, /obj/effect/mapping_helpers/airlock/access/any/medical/pharmacy, /turf/open/floor/plating, /area/station/maintenance/department/medical/central) @@ -50097,7 +50096,7 @@ name = "Surgery C Maintenance" }, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, -/obj/effect/mapping_helpers/airlock/access/all/medical/general, +/obj/effect/mapping_helpers/airlock/access/all/medical/surgery, /turf/open/floor/plating, /area/station/maintenance/aft/greater) "rTL" = ( @@ -62655,13 +62654,12 @@ "whx" = ( /obj/machinery/door/firedoor, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/obj/machinery/door/airlock/research/glass{ +/obj/machinery/door/airlock/medical/glass{ name = "Pharmacy" }, /obj/structure/disposalpipe/segment{ dir = 4 }, -/obj/effect/mapping_helpers/airlock/access/any/medical/general, /obj/effect/mapping_helpers/airlock/access/any/medical/pharmacy, /obj/effect/turf_decal/tile/yellow/fourcorners, /obj/effect/landmark/navigate_destination, diff --git a/_maps/map_files/tramstation/tramstation.dmm b/_maps/map_files/tramstation/tramstation.dmm index ba8942326bc94..ae87f6f63578c 100644 --- a/_maps/map_files/tramstation/tramstation.dmm +++ b/_maps/map_files/tramstation/tramstation.dmm @@ -10478,6 +10478,7 @@ c_tag = "Service - Hydroponics" }, /obj/effect/turf_decal/tile/green/fourcorners, +/obj/item/storage/box/syringes, /turf/open/floor/iron/dark, /area/station/service/hydroponics) "cAd" = ( @@ -33756,6 +33757,7 @@ }, /obj/effect/turf_decal/tile/green/fourcorners, /obj/machinery/newscaster/directional/south, +/obj/item/reagent_containers/cup/watering_can, /turf/open/floor/iron/dark, /area/station/service/hydroponics) "kUm" = ( @@ -62927,6 +62929,7 @@ preset_destination_names = list("2" = "Hydroponics", "3" = "Kitchen") }, /obj/effect/turf_decal/tile/green/fourcorners, +/obj/item/reagent_containers/cup/watering_can, /turf/open/floor/iron/dark, /area/station/service/hydroponics) "vcv" = ( diff --git a/_maps/templates/battlecruiser_starfury.dmm b/_maps/templates/battlecruiser_starfury.dmm index 2c70afabbdff0..2315ef85c7196 100644 --- a/_maps/templates/battlecruiser_starfury.dmm +++ b/_maps/templates/battlecruiser_starfury.dmm @@ -4784,7 +4784,7 @@ faction = list("neutral","silicon","turret","Syndicate"); name = "Syndicate Medibot"; skin = "bezerk"; - maints_access_required = list("syndicate") + req_one_access = list("syndicate") }, /turf/open/floor/iron, /area/shuttle/sbc_starfury) diff --git a/_maps/virtual_domains/stairs_and_cliffs.dmm b/_maps/virtual_domains/stairs_and_cliffs.dmm index 3c9e0711e0282..accdf93bf00be 100644 --- a/_maps/virtual_domains/stairs_and_cliffs.dmm +++ b/_maps/virtual_domains/stairs_and_cliffs.dmm @@ -5,6 +5,10 @@ }, /turf/open/cliff/snowrock/virtual_domain, /area/icemoon/underground/explored/virtual_domain) +"bU" = ( +/mob/living/basic/bear/snow/ancient, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) "cu" = ( /obj/effect/turf_decal/weather/snow/corner{ dir = 4 @@ -19,6 +23,10 @@ /obj/structure/chair/sofa/bench, /turf/open/floor/plating/snowed/smoothed, /area/icemoon/underground/explored/virtual_domain) +"cS" = ( +/obj/structure/barricade/wooden/snowed, +/turf/open/floor/iron/stairs, +/area/icemoon/underground/explored/virtual_domain) "dR" = ( /turf/open/misc/asteroid/snow, /area/icemoon/underground/explored/virtual_domain) @@ -34,6 +42,14 @@ /obj/structure/railing/corner, /turf/open/cliff/snowrock/virtual_domain, /area/icemoon/underground/explored/virtual_domain) +"gF" = ( +/obj/effect/landmark/bitrunning/curiosity_spawn, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) +"gY" = ( +/obj/effect/decal/remains/human, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) "hc" = ( /obj/structure/railing/corner/end{ dir = 8 @@ -65,6 +81,10 @@ /obj/structure/flora/tree/pine/style_random, /turf/open/misc/asteroid/snow, /area/icemoon/underground/explored/virtual_domain) +"mr" = ( +/obj/structure/fluff/fokoff_sign, +/turf/open/misc/asteroid/snow, +/area/icemoon/underground/explored/virtual_domain) "mx" = ( /obj/structure/railing, /obj/structure/railing{ @@ -86,6 +106,13 @@ }, /turf/open/floor/wood, /area/icemoon/underground/explored/virtual_domain) +"nY" = ( +/obj/structure/railing{ + dir = 1 + }, +/obj/structure/railing, +/turf/open/floor/wood, +/area/icemoon/underground/explored/virtual_domain) "pl" = ( /obj/structure/bonfire/prelit, /turf/open/misc/asteroid/snow, @@ -597,10 +624,10 @@ dR dR dR dR -sM -sM -sM -sM +Nv +Nv +Nv +dR sM sM sM @@ -672,23 +699,23 @@ Qv Qv kK sw +gY dR -dR -sM -sM -sM -sM -sM -sM -sM -sM -sM sM sM sM +dR +cS +Nv +Nv +dR +dR +sw sM sM sM +qc +gF sM sM sM @@ -754,18 +781,18 @@ dR sM sM sM +sa sM sM +gY +dR +dR +sa +dR sM sM -sM -sM -sM -sM -sM -sM -sM -sM +Dz +dR sM sM sM @@ -827,21 +854,21 @@ Qv eB sw dR -dR -sM -sM -sM -sM -sM -sM +mr sM sM sM sM sM sM +mr +sa +dR +dR +gY sM sM +nY sM sM sM @@ -901,24 +928,24 @@ Am Am Qv Qv -dR +mr eB dR -sw +dR dR sM sM sM sM sM -sM -sM -sM -sM -sM -sM -sM -sM +kK +dR +bU +dR +dR +cS +Nv +hc sM sM sM @@ -989,10 +1016,10 @@ sM sM sM sM -sM -sM -sM -sM +dR +sa +gY +dR sM sM sM @@ -1063,13 +1090,13 @@ dR sM sM sM +pL sM sM -sM -sM -sM -sM -sM +sw +dR +dR +sa sM sM sM @@ -1140,13 +1167,13 @@ sM sM sM sM +pL +pL sM sM -sM -sM -sM -sM -sM +dR +dR +sw sM sM sM @@ -1217,8 +1244,8 @@ sM sM sM sM -sM -sM +pL +pL sM sM sM @@ -1295,11 +1322,11 @@ sM sM sM sM -sM -sM -sM -sM -sM +pL +pL +pL +pL +pL sM sM sM @@ -1373,8 +1400,8 @@ sM sM sM sM -sM -sM +pL +pL sM sM sM diff --git a/code/__DEFINES/_flags.dm b/code/__DEFINES/_flags.dm index 55b5b12b531ac..3b0bcb2ce963f 100644 --- a/code/__DEFINES/_flags.dm +++ b/code/__DEFINES/_flags.dm @@ -193,6 +193,8 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204 #define INDESTRUCTIBLE (1<<6) /// can't be frozen #define FREEZE_PROOF (1<<7) +/// can't be shuttle crushed. +#define SHUTTLE_CRUSH_PROOF (1<<8) //tesla_zap #define ZAP_MACHINE_EXPLOSIVE (1<<0) @@ -208,11 +210,16 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204 #define ZAP_FUSION_FLAGS ZAP_OBJ_DAMAGE | ZAP_MOB_DAMAGE | ZAP_MOB_STUN #define ZAP_SUPERMATTER_FLAGS ZAP_GENERATES_POWER -//EMP protection +///EMP will protect itself. #define EMP_PROTECT_SELF (1<<0) +///EMP will protect the contents from also being EMPed. #define EMP_PROTECT_CONTENTS (1<<1) +///EMP will protect the wires. #define EMP_PROTECT_WIRES (1<<2) +///Protects against all EMP types. +#define EMP_PROTECT_ALL (EMP_PROTECT_SELF | EMP_PROTECT_CONTENTS | EMP_PROTECT_WIRES) + //Mob mobility var flags /// can move #define MOBILITY_MOVE (1<<0) diff --git a/code/__DEFINES/_helpers.dm b/code/__DEFINES/_helpers.dm index f1b1b21df3308..d9f75fe8e9d80 100644 --- a/code/__DEFINES/_helpers.dm +++ b/code/__DEFINES/_helpers.dm @@ -32,3 +32,7 @@ // Custom types that we define don't get a unique id, but this is useful for identifying // types that don't normally have a way to run istype() on them. #define TYPEID(thing) copytext(REF(thing), 4, 6) + +/// A null statement to guard against EmptyBlock lint without necessitating the use of pass() +/// Used to avoid proc-call overhead. But use sparingly. Probably pointless in most places. +#define EMPTY_BLOCK_GUARD ; diff --git a/code/__DEFINES/access.dm b/code/__DEFINES/access.dm index 90726effb00fe..f00264d6b1a9f 100644 --- a/code/__DEFINES/access.dm +++ b/code/__DEFINES/access.dm @@ -193,6 +193,11 @@ /// HUNTERS #define ACCESS_HUNTER "hunter" +/// - - - MISC - - - + // These don't really fit anywhere else +/// For things that aren't ever supposed to be accessed +#define ACCESS_INACCESSIBLE "inaccessible" + /// - - - END ACCESS IDS - - - /// A list of access levels that, when added to an ID card, will warn admins. @@ -294,6 +299,7 @@ ACCESS_DETECTIVE, \ ACCESS_ENGINE_EQUIP, \ ACCESS_ENGINEERING, \ + ACCESS_EVA, \ ACCESS_EXTERNAL_AIRLOCKS, \ ACCESS_GATEWAY, \ ACCESS_GENETICS, \ diff --git a/code/__DEFINES/ai/pets.dm b/code/__DEFINES/ai/pets.dm index e41c9ac0c3ffe..c7383f56a005e 100644 --- a/code/__DEFINES/ai/pets.dm +++ b/code/__DEFINES/ai/pets.dm @@ -51,3 +51,20 @@ /// key that holds items we arent interested in hoarding #define BB_IGNORE_ITEMS "ignore_items" +//virtual pet keys +///the last PDA message we must relay +#define BB_LAST_RECIEVED_MESSAGE "last_recieved_message" +///our current virtual pet level +#define BB_VIRTUAL_PET_LEVEL "virtual_pet_level" +///the target we will play with +#define BB_NEARBY_PLAYMATE "nearby_playmate" +///cooldown till we search for playmates +#define BB_NEXT_PLAYDATE "next_playdate" +///our ability to trigger lights +#define BB_LIGHTS_ABILITY "lights_ability" +///our ability to capture images +#define BB_PHOTO_ABILITY "photo_ability" +///the name of our trick +#define BB_TRICK_NAME "trick_name" +///the sequence of our trick +#define BB_TRICK_SEQUENCE "trick_sequence" diff --git a/code/__DEFINES/alerts.dm b/code/__DEFINES/alerts.dm index 1933b592d55c5..3d09a3e5a22d5 100644 --- a/code/__DEFINES/alerts.dm +++ b/code/__DEFINES/alerts.dm @@ -51,7 +51,6 @@ #define ALERT_MECH_DAMAGE "mech_damage" /** Food related */ -#define ALERT_NUTRITION "nutrition" #define ALERT_DISGUST "disgust" /** Environment related */ diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm index 10b2f8dc63515..af1cb68c41cad 100644 --- a/code/__DEFINES/antagonists.dm +++ b/code/__DEFINES/antagonists.dm @@ -146,6 +146,9 @@ /// JSON string file for all of our heretic influence flavors #define HERETIC_INFLUENCE_FILE "antagonist_flavor/heretic_influences.json" +/// JSON file containing spy objectives +#define SPY_OBJECTIVE_FILE "antagonist_flavor/spy_objective.json" + ///employers that are from the syndicate GLOBAL_LIST_INIT(syndicate_employers, list( "Animal Rights Consortium", @@ -265,6 +268,8 @@ GLOBAL_LIST_INIT(human_invader_antagonists, list( #define OBJECTIVE_ITEM_TYPE_NORMAL "normal" /// Only appears in traitor objectives #define OBJECTIVE_ITEM_TYPE_TRAITOR "traitor" +/// Only appears for spy bounties +#define OBJECTIVE_ITEM_TYPE_SPY "spy" // Progression traitor defines @@ -379,3 +384,11 @@ GLOBAL_LIST_INIT(human_invader_antagonists, list( #define BATON_MODES 4 #define FREEDOM_IMPLANT_CHARGES 4 + +// Spy bounty difficulties +/// Can easily be accomplished by any job without any specialized tools, people won't really miss these things +#define SPY_DIFFICULTY_EASY "Easy" +/// Requires some specialized tools, knowledge, or access to accomplish, may require getting into conflict with the crew +#define SPY_DIFFICULTY_MEDIUM "Medium" +/// Very difficult to accomplish, almost guaranteed to require crew conflict +#define SPY_DIFFICULTY_HARD "Hard" diff --git a/code/__DEFINES/atmospherics/atmos_piping.dm b/code/__DEFINES/atmospherics/atmos_piping.dm index 1993f10222523..3870a7aed34ac 100644 --- a/code/__DEFINES/atmospherics/atmos_piping.dm +++ b/code/__DEFINES/atmospherics/atmos_piping.dm @@ -9,11 +9,13 @@ #define EAST_SHORTPIPE (1<<6) #define WEST_SHORTPIPE (1<<7) // Helpers to convert cardinals to and from pipe bitfields -// Assumes X_FULLPIPE = X, X_SHORTPIPE >> 4 = X as above +// Assumes X_FULLPIPE = X, X_SHORTPIPE >> 4 = X, X_PIPECAPS >> 8 = X as above #define FULLPIPE_TO_CARDINALS(bitfield) ((bitfield) & ALL_CARDINALS) #define SHORTPIPE_TO_CARDINALS(bitfield) (((bitfield) >> 4) & ALL_CARDINALS) +#define PIPECAPS_TO_CARDINALS(bitfield) (((bitfield) >> 8) & ALL_CARDINALS) #define CARDINAL_TO_FULLPIPES(cardinals) (cardinals) #define CARDINAL_TO_SHORTPIPES(cardinals) ((cardinals) << 4) +#define CARDINAL_TO_PIPECAPS(cardinals) ((cardinals) << 8) // A pipe is a stub if it only has zero or one permitted direction. For a regular pipe this is nonsensical, and there are no pipe sprites for this, so it is not allowed. #define ISSTUB(bits) !((bits) & ((bits) - 1)) #define ISNOTSTUB(bits) ((bits) & ((bits) - 1)) diff --git a/code/__DEFINES/atom_hud.dm b/code/__DEFINES/atom_hud.dm index b175acd65a615..8bb5a3344753f 100644 --- a/code/__DEFINES/atom_hud.dm +++ b/code/__DEFINES/atom_hud.dm @@ -98,6 +98,7 @@ #define SECHUD_GENETICIST "hudgeneticist" #define SECHUD_HEAD_OF_PERSONNEL "hudheadofpersonnel" #define SECHUD_HEAD_OF_SECURITY "hudheadofsecurity" +#define SECHUD_HUMAN_AI "hudhumanai" #define SECHUD_JANITOR "hudjanitor" #define SECHUD_LAWYER "hudlawyer" #define SECHUD_MEDICAL_DOCTOR "hudmedicaldoctor" diff --git a/code/__DEFINES/client.dm b/code/__DEFINES/client.dm index 0914bc025adda..17571b5270bb1 100644 --- a/code/__DEFINES/client.dm +++ b/code/__DEFINES/client.dm @@ -1,11 +1,6 @@ /// Checks if the given target is either a client or a mock client #define IS_CLIENT_OR_MOCK(target) (istype(target, /client) || istype(target, /datum/client_interface)) -/// Ensures that the client has been fully initialized via New(), and can't somehow execute actions before that. Security measure. -/// WILL RETURN OUT OF THE ENTIRE PROC COMPLETELY IF THE CLIENT IS NOT FULLY INITIALIZED. BE WARNED IF YOU WANT RETURN VALUES. -#define VALIDATE_CLIENT(target)\ - if (!target.fully_created) {\ - to_chat(target, span_warning("You are not fully initialized yet! Please wait a moment."));\ - log_access("Client [key_name(target)] attempted to execute a verb before being fully initialized.");\ - return\ - } +/// Checks to see if a /client has fully gone through New() as a safeguard against certain operations. +/// Should return the boolean value of the fully_created var, which should be TRUE if New() has finished running. FALSE otherwise. +#define VALIDATE_CLIENT_INITIALIZATION(target) (target.fully_created) diff --git a/code/__DEFINES/construction/material.dm b/code/__DEFINES/construction/material.dm index 24ab2eb330327..445b4e0dc88e4 100644 --- a/code/__DEFINES/construction/material.dm +++ b/code/__DEFINES/construction/material.dm @@ -15,8 +15,8 @@ #define MAXCOIL 30 //Category of materials -/// Is the material from an ore? currently unused but exists atm for categorizations sake -#define MAT_CATEGORY_ORE "ore capable" +/// Can this material be stored in the ore silo +#define MAT_CATEGORY_SILO "silo capable" /// Hard materials, such as iron or silver #define MAT_CATEGORY_RIGID "rigid material" /// Materials that can be used to craft items @@ -68,13 +68,13 @@ //Stock market stock values. /// How much quantity of a material stock exists for common materials like iron & glass. -#define MATERIAL_QUANTITY_COMMON 25000 +#define MATERIAL_QUANTITY_COMMON 5000 /// How much quantity of a material stock exists for uncommon materials like silver & titanium. -#define MATERIAL_QUANTITY_UNCOMMON 10000 +#define MATERIAL_QUANTITY_UNCOMMON 1000 /// How much quantity of a material stock exists for rare materials like gold, uranium, & diamond. -#define MATERIAL_QUANTITY_RARE 2500 +#define MATERIAL_QUANTITY_RARE 200 /// How much quantity of a material stock exists for exotic materials like diamond & bluespace crystals. -#define MATERIAL_QUANTITY_EXOTIC 500 +#define MATERIAL_QUANTITY_EXOTIC 50 // The number of ore vents that will spawn boulders with this material. /// Is this material going to spawn often in ore vents? (80% of vents on lavaland) diff --git a/code/__DEFINES/dcs/signals/signals_action.dm b/code/__DEFINES/dcs/signals/signals_action.dm index 6fbf5372acdd2..2226e34bcccbd 100644 --- a/code/__DEFINES/dcs/signals/signals_action.dm +++ b/code/__DEFINES/dcs/signals/signals_action.dm @@ -48,3 +48,6 @@ /// From /datum/action/cooldown/manual_heart/Activate(): () #define COMSIG_HEART_MANUAL_PULSE "heart_manual_pulse" + +/// From /datum/action/cooldown/mob_cooldown/capture_photo/Activate(): +#define COMSIG_ACTION_PHOTO_CAPTURED "action_photo_captured" diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm index a027dc61adbfe..24524395f35f2 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm @@ -129,3 +129,8 @@ #define COMSIG_ATOM_GERM_UNEXPOSED "atom_germ_unexposed" /// signal sent to puzzle pieces by activator #define COMSIG_PUZZLE_COMPLETED "puzzle_completed" + +/// From /datum/compomnent/cleaner/clean() +#define COMSIG_ATOM_PRE_CLEAN "atom_pre_clean" + ///cancel clean + #define COMSIG_ATOM_CANCEL_CLEAN (1<<0) diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm index 601f441c66dd4..38d0500dcbdb5 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm @@ -112,3 +112,6 @@ #define COMSIG_MOVABLE_EDIT_UNIQUE_IMMERSE_OVERLAY "movable_edit_unique_submerge_overlay" /// From base of area/Exited(): (area/left, direction) #define COMSIG_MOVABLE_EXITED_AREA "movable_exited_area" + +/// Sent to movables when they are being stolen by a spy: (mob/living/spy, datum/spy_bounty/bounty) +#define COMSIG_MOVABLE_SPY_STEALING "movable_spy_stealing" diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm index ceba0a2f182b7..f6f7f6e4a291c 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm @@ -8,9 +8,9 @@ #define COMPONENT_CANCEL_EX_ACT (1<<0) ///from the [EX_ACT] wrapper macro: (severity, target) #define COMSIG_ATOM_EX_ACT "atom_ex_act" -///from base of atom/emp_act(): (severity). return EMP protection flags +///from base of atom/emp_act(severity): (severity). return EMP protection flags #define COMSIG_ATOM_PRE_EMP_ACT "atom_emp_act" -///from base of atom/emp_act(): (severity, protection) +///from base of atom/emp_act(severity): (severity, protection) #define COMSIG_ATOM_EMP_ACT "atom_emp_act" ///from base of atom/fire_act(): (exposed_temperature, exposed_volume) #define COMSIG_ATOM_FIRE_ACT "atom_fire_act" diff --git a/code/__DEFINES/dcs/signals/signals_global_object.dm b/code/__DEFINES/dcs/signals/signals_global_object.dm index 452be16f83911..51b7a38a94a6c 100644 --- a/code/__DEFINES/dcs/signals/signals_global_object.dm +++ b/code/__DEFINES/dcs/signals/signals_global_object.dm @@ -1,7 +1,7 @@ /// signals from globally accessible objects -///from SSJob when DivideOccupations is called -#define COMSIG_OCCUPATIONS_DIVIDED "occupations_divided" +///Whenever SetupOccupations() is called, called all occupations are set +#define COMSIG_OCCUPATIONS_SETUP "occupations_setup" ///from SSsun when the sun changes position : (azimuth) #define COMSIG_SUN_MOVED "sun_moved" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm index 533ad2e1ae886..b1914cc966bd4 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm @@ -26,6 +26,8 @@ #define COMSIG_LIVING_EXTINGUISHED "living_extinguished" ///from base of mob/living/electrocute_act(): (shock_damage, source, siemens_coeff, flags) #define COMSIG_LIVING_ELECTROCUTE_ACT "living_electrocute_act" + /// Block the electrocute_act() proc from proceeding + #define COMPONENT_LIVING_BLOCK_SHOCK (1<<0) ///sent when items with siemen coeff. of 0 block a shock: (power_source, source, siemens_coeff, dist_check) #define COMSIG_LIVING_SHOCK_PREVENTED "living_shock_prevented" ///sent by stuff like stunbatons and tasers: () @@ -153,9 +155,9 @@ #define ZIMPACT_NO_SPIN (1<<2) /// From mob/living/try_speak(): (message, ignore_spam, forced) -#define COMSIG_LIVING_TRY_SPEECH "living_vocal_speech" - /// Return if the mob can speak the message, regardless of any other signal returns or checks. - #define COMPONENT_CAN_ALWAYS_SPEAK (1<<0) +#define COMSIG_MOB_TRY_SPEECH "living_vocal_speech" + /// Return to skip can_speak check, IE, forcing success. Overrides below. + #define COMPONENT_IGNORE_CAN_SPEAK (1<<0) /// Return if the mob cannot speak. #define COMPONENT_CANNOT_SPEAK (1<<1) @@ -253,3 +255,11 @@ /// Sent to a mob grabbing another mob: (mob/living/grabbing) #define COMSIG_LIVING_GRAB "living_grab" // Return COMPONENT_CANCEL_ATTACK_CHAIN / COMPONENT_SKIP_ATTACK_CHAIN to stop the grab + +/// From /datum/element/basic_eating/try_eating() +#define COMSIG_MOB_PRE_EAT "mob_pre_eat" + ///cancel eating attempt + #define COMSIG_MOB_CANCEL_EAT (1<<0) + +/// From /datum/element/basic_eating/finish_eating() +#define COMSIG_MOB_ATE "mob_ate" diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index 442309289f03a..91dbba15ff4d6 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -158,6 +158,9 @@ /// Return to prevent the default behavior (attack_selfing) from ocurring. #define COMPONENT_ITEM_ACTION_SLOT_INVALID (1<<0) +/// Sent from /obj/item/attack_atom(): (atom/attacked_atom, mob/living/user) +#define COMSIG_ITEM_POST_ATTACK_ATOM "item_post_attack_atom" + ///from base of mob/living/carbon/attacked_by(): (mob/living/carbon/target, mob/living/user, hit_zone) #define COMSIG_ITEM_ATTACK_ZONE "item_attack_zone" ///from base of obj/item/hit_reaction(): (owner, hitby, attack_text, final_block_chance, damage, attack_type, damage_type) @@ -308,9 +311,11 @@ // /obj/item/radio signals +///called from base of /obj/item/proc/talk_into(): (atom/movable/speaker, message, channel, list/spans, language, list/message_mods) +#define COMSIG_ITEM_TALK_INTO "item_talk_into" ///called from base of /obj/item/radio/proc/set_frequency(): (list/args) #define COMSIG_RADIO_NEW_FREQUENCY "radio_new_frequency" -///called from base of /obj/item/radio/proc/talk_into(): (atom/movable/M, message, channel) +///called from base of /obj/item/radio/talk_into(): (atom/movable/M, message, channel) #define COMSIG_RADIO_NEW_MESSAGE "radio_new_message" ///called from base of /obj/item/radio/proc/on_receive_messgae(): (list/data) #define COMSIG_RADIO_RECEIVE_MESSAGE "radio_receive_message" @@ -410,6 +415,11 @@ ///from /datum/action/vehicle/sealed/headlights/vim/Trigger(): (headlights_on) #define COMSIG_VIM_HEADLIGHTS_TOGGLED "vim_headlights_toggled" +///from /datum/computer_file/program/messenger/proc/receive_message +#define COMSIG_COMPUTER_RECIEVED_MESSAGE "computer_recieved_message" +///from /datum/computer_file/program/virtual_pet/proc/handle_level_up +#define COMSIG_VIRTUAL_PET_LEVEL_UP "virtual_pet_level_up" + // /obj/vehicle/sealed/mecha signals /// sent if you attach equipment to mecha diff --git a/code/__DEFINES/economy.dm b/code/__DEFINES/economy.dm index 93b0678581ae8..32408e4f538e9 100644 --- a/code/__DEFINES/economy.dm +++ b/code/__DEFINES/economy.dm @@ -70,12 +70,13 @@ #define PAYMENT_CLINICAL "clinical" #define PAYMENT_FRIENDLY "friendly" #define PAYMENT_ANGRY "angry" +#define PAYMENT_VENDING "vending" #define MARKET_TREND_UPWARD 1 #define MARKET_TREND_DOWNWARD -1 #define MARKET_TREND_STABLE 0 -#define MARKET_EVENT_PROBABILITY 1 //Probability of a market event firing, in percent. Fires once per material, every 20 seconds. +#define MARKET_EVENT_PROBABILITY 8 //Probability of a market event firing, in percent. Fires once per material, every stock market tick. #define MARKET_PROFIT_MODIFIER 0.8 //We don't make every sale a 1-1 of the actual buy price value, like with real life taxes and to encourage more smart trades diff --git a/code/__DEFINES/hud.dm b/code/__DEFINES/hud.dm index 5798fd29e82de..a4d826d87caf6 100644 --- a/code/__DEFINES/hud.dm +++ b/code/__DEFINES/hud.dm @@ -77,13 +77,7 @@ #define ui_building "EAST-4:22,SOUTH:21" #define ui_language_menu "EAST-4:6,SOUTH:21" #define ui_navigate_menu "EAST-4:22,SOUTH:5" - -//Upper-middle right (alerts) -#define ui_alert1 "EAST-1:28,CENTER+5:27" -#define ui_alert2 "EAST-1:28,CENTER+4:25" -#define ui_alert3 "EAST-1:28,CENTER+3:23" -#define ui_alert4 "EAST-1:28,CENTER+2:21" -#define ui_alert5 "EAST-1:28,CENTER+1:19" +#define ui_floor_menu "EAST-4:14,SOUTH:37" //Upper left (action buttons) #define ui_action_palette "WEST+0:23,NORTH-1:5" @@ -97,6 +91,7 @@ #define ui_health "EAST-1:28,CENTER-1:19" #define ui_internal "EAST-1:28,CENTER+1:21" #define ui_mood "EAST-1:28,CENTER:21" +#define ui_hunger "EAST-1:2,CENTER:21" #define ui_spacesuit "EAST-1:28,CENTER-4:14" #define ui_stamina "EAST-1:28,CENTER-3:14" @@ -143,6 +138,7 @@ #define ui_borg_alerts "CENTER+4:21,SOUTH:5" #define ui_borg_language_menu "CENTER+4:19,SOUTH+1:6" #define ui_borg_navigate_menu "CENTER+4:19,SOUTH+1:6" +#define ui_borg_floor_menu "CENTER+4:-13,SOUTH+1:6" //Aliens #define ui_alien_health "EAST,CENTER-1:15" @@ -151,6 +147,7 @@ #define ui_alien_storage_r "CENTER+1:18,SOUTH:5" #define ui_alien_language_menu "EAST-4:20,SOUTH:5" #define ui_alien_navigate_menu "EAST-4:20,SOUTH:5" +#define ui_alien_floor_menu "EAST-4:-12,SOUTH:5" //AI #define ui_ai_core "BOTTOM:6,RIGHT-4" @@ -159,6 +156,7 @@ #define ui_ai_state_laws "BOTTOM:6,RIGHT-1" #define ui_ai_mod_int "BOTTOM:6,RIGHT" #define ui_ai_language_menu "BOTTOM+1:8,RIGHT-1:30" +#define ui_ai_floor_menu "BOTTOM+1:8,RIGHT-1:14" #define ui_ai_crew_monitor "BOTTOM:6,CENTER-1" #define ui_ai_crew_manifest "BOTTOM:6,CENTER" @@ -200,6 +198,7 @@ #define ui_ghost_pai "SOUTH: 6, CENTER+1:24" #define ui_ghost_minigames "SOUTH: 6, CENTER+2:24" #define ui_ghost_language_menu "SOUTH: 22, CENTER+3:8" +#define ui_ghost_floor_menu "SOUTH: 6, CENTER+3:8" //Blobbernauts #define ui_blobbernaut_overmind_health "EAST-1:28,CENTER+0:19" diff --git a/code/__DEFINES/icon_smoothing.dm b/code/__DEFINES/icon_smoothing.dm index 2509d685d35df..29a2c6dc07ec7 100644 --- a/code/__DEFINES/icon_smoothing.dm +++ b/code/__DEFINES/icon_smoothing.dm @@ -192,8 +192,13 @@ DEFINE_BITFIELD(smoothing_junction, list( #define SMOOTH_GROUP_CLEANABLE_DIRT S_OBJ(68) ///obj/effect/decal/cleanable/dirt -#define SMOOTH_GROUP_GAS_TANK S_OBJ(72) +#define SMOOTH_GROUP_GAS_TANK S_OBJ(69) +#define SMOOTH_GROUP_SPIDER_WEB S_OBJ(70) // /obj/structure/spider/stickyweb +#define SMOOTH_GROUP_SPIDER_WEB_WALL S_OBJ(71) // /obj/structure/spider/stickyweb/sealed +#define SMOOTH_GROUP_SPIDER_WEB_ROOF S_OBJ(72) // /obj/structure/spider/passage +#define SMOOTH_GROUP_SPIDER_WEB_WALL_TOUGH S_OBJ(73) // /obj/structure/spider/stickyweb/sealed/thick +#define SMOOTH_GROUP_SPIDER_WEB_WALL_MIRROR S_OBJ(74) // /obj/structure/spider/stickyweb/sealed/reflector /// Performs the work to set smoothing_groups and canSmoothWith. /// An inlined function used in both turf/Initialize and atom/Initialize. diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index 909399b3c3da6..8271bb725d18d 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -135,13 +135,17 @@ GLOBAL_LIST_INIT(turfs_pass_meteor, typecacheof(list( //Silicon mobs #define issilicon(A) (istype(A, /mob/living/silicon)) +///Define on whether A has access to Silicon stuff either through being a silicon, admin ghost or is a non-silicon holding the Silicon remote. +///This can only be used for instances where you are not specifically looking for silicon, but access. +#define HAS_SILICON_ACCESS(A) (istype(A, /mob/living/silicon) || isAdminGhostAI(A) || A.has_unlimited_silicon_privilege || istype(A.get_active_held_item(), /obj/item/machine_remote)) -#define issiliconoradminghost(A) (istype(A, /mob/living/silicon) || isAdminGhostAI(A)) +#define isAI(A) (istype(A, /mob/living/silicon/ai)) +///Define on whether A has access to AI stuff either through being a AI, admin ghost, or is a non-silicon holding the Silicon remote +///This can only be used for instances where you are not specifically looking for silicon, but access. +#define HAS_AI_ACCESS(A) (istype(A, /mob/living/silicon/ai) || isAdminGhostAI(A) || istype(A.get_active_held_item(), /obj/item/machine_remote)) #define iscyborg(A) (istype(A, /mob/living/silicon/robot)) -#define isAI(A) (istype(A, /mob/living/silicon/ai)) - #define ispAI(A) (istype(A, /mob/living/silicon/pai)) // basic mobs @@ -314,6 +318,7 @@ GLOBAL_LIST_INIT(book_types, typecacheof(list( #define is_captain_job(job_type) (istype(job_type, /datum/job/captain)) #define is_chaplain_job(job_type) (istype(job_type, /datum/job/chaplain)) #define is_clown_job(job_type) (istype(job_type, /datum/job/clown)) +#define is_mime_job(job_type) (istype(job_type, /datum/job/mime)) #define is_detective_job(job_type) (istype(job_type, /datum/job/detective)) #define is_scientist_job(job_type) (istype(job_type, /datum/job/scientist)) #define is_security_officer_job(job_type) (istype(job_type, /datum/job/security_officer)) diff --git a/code/__DEFINES/jobs.dm b/code/__DEFINES/jobs.dm index fca771489558f..d7ce200fbb95e 100644 --- a/code/__DEFINES/jobs.dm +++ b/code/__DEFINES/jobs.dm @@ -54,6 +54,7 @@ #define JOB_AI "AI" #define JOB_CYBORG "Cyborg" #define JOB_PERSONAL_AI "Personal AI" +#define JOB_HUMAN_AI "Big Brother" //Security #define JOB_WARDEN "Warden" #define JOB_DETECTIVE "Detective" @@ -186,6 +187,19 @@ #define DEPARTMENT_BITFLAG_CAPTAIN (1<<9) #define DEPARTMENT_CAPTAIN "Captain" +DEFINE_BITFIELD(departments_bitflags, list( + "SECURITY" = DEPARTMENT_BITFLAG_SECURITY, + "COMMAND" = DEPARTMENT_BITFLAG_COMMAND, + "SERVICE" = DEPARTMENT_BITFLAG_SERVICE, + "CARGO" = DEPARTMENT_BITFLAG_CARGO, + "ENGINEERING" = DEPARTMENT_BITFLAG_ENGINEERING, + "SCIENCE" = DEPARTMENT_BITFLAG_SCIENCE, + "MEDICAL" = DEPARTMENT_BITFLAG_MEDICAL, + "SILICON" = DEPARTMENT_BITFLAG_SILICON, + "ASSISTANT" = DEPARTMENT_BITFLAG_ASSISTANT, + "CAPTAIN" = DEPARTMENT_BITFLAG_CAPTAIN, +)) + /* Job datum job_flags */ /// Whether the mob is announced on arrival. #define JOB_ANNOUNCE_ARRIVAL (1<<0) diff --git a/code/__DEFINES/lighting.dm b/code/__DEFINES/lighting.dm index fe85c38bf467a..c417db515b779 100644 --- a/code/__DEFINES/lighting.dm +++ b/code/__DEFINES/lighting.dm @@ -71,7 +71,7 @@ #define LIGHTING_FORCE_UPDATE 3 #define FLASH_LIGHT_DURATION 2 -#define FLASH_LIGHT_POWER 3 +#define FLASH_LIGHT_POWER 2 #define FLASH_LIGHT_RANGE 3.8 // Emissive blocking. diff --git a/code/__DEFINES/logging.dm b/code/__DEFINES/logging.dm index a6102aa6e7938..492a0a06a8850 100644 --- a/code/__DEFINES/logging.dm +++ b/code/__DEFINES/logging.dm @@ -161,6 +161,7 @@ #define LOG_CATEGORY_UPLINK_HERETIC "uplink-heretic" #define LOG_CATEGORY_UPLINK_MALF "uplink-malf" #define LOG_CATEGORY_UPLINK_SPELL "uplink-spell" +#define LOG_CATEGORY_UPLINK_SPY "uplink-spy" // PDA categories #define LOG_CATEGORY_PDA "pda" diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 90485367815f6..9a05a72928500 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -969,3 +969,17 @@ GLOBAL_LIST_INIT(layers_to_offset, list( /// Types of bullets that mining mobs take full damage from #define MINING_MOB_PROJECTILE_VULNERABILITY list(BRUTE) + +// Sprites for photocopying butts +#define BUTT_SPRITE_HUMAN_MALE "human_male" +#define BUTT_SPRITE_HUMAN_FEMALE "human_female" +#define BUTT_SPRITE_LIZARD "lizard" +#define BUTT_SPRITE_QR_CODE "qr_code" +#define BUTT_SPRITE_XENOMORPH "xeno" +#define BUTT_SPRITE_DRONE "drone" +#define BUTT_SPRITE_CAT "cat" +#define BUTT_SPRITE_FLOWERPOT "flowerpot" +#define BUTT_SPRITE_GREY "grey" +#define BUTT_SPRITE_PLASMA "plasma" +#define BUTT_SPRITE_FUZZY "fuzzy" +#define BUTT_SPRITE_SLIME "slime" diff --git a/code/__DEFINES/procpath.dm b/code/__DEFINES/procpath.dm index 642ca3eab6cc8..16716d6c091f2 100644 --- a/code/__DEFINES/procpath.dm +++ b/code/__DEFINES/procpath.dm @@ -15,12 +15,12 @@ // below, their accesses are optimized away. /// A text string of the verb's name. - var/name as text + var/name = null as text|null /// The verb's help text or description. - var/desc as text + var/desc = null as text|null /// The category or tab the verb will appear in. - var/category as text + var/category = null as text|null /// Only clients/mobs with `see_invisibility` higher can use the verb. - var/invisibility as num + var/invisibility = null as num|null /// Whether or not the verb appears in statpanel and commandbar when you press space - var/hidden as num + var/hidden = null as num|null diff --git a/code/__DEFINES/robots.dm b/code/__DEFINES/robots.dm index 9ec9dc77328dc..e822c0c2a77cd 100644 --- a/code/__DEFINES/robots.dm +++ b/code/__DEFINES/robots.dm @@ -96,7 +96,7 @@ //Bot cover defines indicating the Bot's status ///The Bot's cover is open and can be modified/emagged by anyone. -#define BOT_COVER_OPEN (1<<0) +#define BOT_COVER_MAINTS_OPEN (1<<0) ///The Bot's cover is locked, and cannot be opened without unlocking it. #define BOT_COVER_LOCKED (1<<1) ///The Bot is emagged. @@ -104,18 +104,18 @@ ///The Bot has been hacked by a Silicon, emagging them, but revertable. #define BOT_COVER_HACKED (1<<3) - -//basic bots defines - -///is our maintenancle panel currently open -#define BOT_MAINTS_PANEL_OPEN (1<<0) -///is our control panel currently open -#define BOT_CONTROL_PANEL_OPEN (1<<1) - -///bitfield for our access flags +///bitfield, used by basic bots, for our access flags DEFINE_BITFIELD(bot_access_flags, list( - "MAINTS_OPEN" = BOT_MAINTS_PANEL_OPEN, - "CONTROL_OPEN" = BOT_CONTROL_PANEL_OPEN, + "MAINTS_OPEN" = BOT_COVER_MAINTS_OPEN, + "COVER_OPEN" = BOT_COVER_LOCKED, + "COVER_EMAGGED" = BOT_COVER_EMAGGED, + "COVER_HACKED" = BOT_COVER_HACKED, +)) + +///bitfield, used by simple bots, for our access flags +DEFINE_BITFIELD(bot_cover_flags, list( + "MAINTS_OPEN" = BOT_COVER_MAINTS_OPEN, + "COVER_OPEN" = BOT_COVER_LOCKED, "COVER_EMAGGED" = BOT_COVER_EMAGGED, "COVER_HACKED" = BOT_COVER_HACKED, )) diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm index 3d41921e0ea00..71fa29b6ea31a 100644 --- a/code/__DEFINES/role_preferences.dm +++ b/code/__DEFINES/role_preferences.dm @@ -16,6 +16,7 @@ #define ROLE_OPERATIVE "Operative" #define ROLE_TRAITOR "Traitor" #define ROLE_WIZARD "Wizard" +#define ROLE_SPY "Spy" // Midround roles #define ROLE_ABDUCTOR "Abductor" @@ -77,6 +78,9 @@ #define ROLE_WIZARD_APPRENTICE "apprentice" #define ROLE_SYNDICATE_MONKEY "Syndicate Monkey Agent" #define ROLE_CONTRACTOR_SUPPORT "Contractor Support Unit" +#define ROLE_SYNDICATE_SABOBORG "Syndicate Sabotage Cyborg" +#define ROLE_SYNDICATE_MEDBORG "Syndicate Medical Cyborg" +#define ROLE_SYNDICATE_ASSAULTBORG "Syndicate Assault Cyborg" //Spawner roles #define ROLE_ANCIENT_CREW "Ancient Crew" @@ -128,6 +132,7 @@ GLOBAL_LIST_INIT(special_roles, list( ROLE_REV_HEAD = 14, ROLE_TRAITOR = 0, ROLE_WIZARD = 14, + ROLE_SPY = 0, // Midround ROLE_ABDUCTOR = 0, diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm index ba22075285c68..018d7b26f2b39 100644 --- a/code/__DEFINES/say.dm +++ b/code/__DEFINES/say.dm @@ -55,11 +55,14 @@ #define MODE_MAFIA "mafia" +/// Applies singing characters to the message #define MODE_SING "sing" - +/// A custom say emote is being supplied [value = the emote] #define MODE_CUSTOM_SAY_EMOTE "custom_say" - +/// No message is following, just emote #define MODE_CUSTOM_SAY_ERASE_INPUT "erase_input" +/// Message is being relayed through another object +#define MODE_RELAY "relayed" //Spans. Robot speech, italics, etc. Applied in compose_message(). #define SPAN_ROBOT "robot" @@ -75,8 +78,11 @@ #define SPAN_HELIUM "small" //bitflag #defines for return value of the radio() proc. +/// Makes the message use italics #define ITALICS (1<<0) +/// Reduces the range of the message to 1 #define REDUCE_RANGE (1<<1) +/// Stops any actual message from being sent #define NOPASS (1<<2) /// Range to hear normal messages diff --git a/code/__DEFINES/sound.dm b/code/__DEFINES/sound.dm index 8301a9fb107e8..5b0ea31dfbfd0 100644 --- a/code/__DEFINES/sound.dm +++ b/code/__DEFINES/sound.dm @@ -173,3 +173,4 @@ GLOBAL_LIST_INIT(announcer_keys, list( #define SFX_ROCK_TAP "rock_tap" #define SFX_SEAR "sear" #define SFX_REEL "reel" +#define SFX_RATTLE "rattle" diff --git a/code/__DEFINES/station.dm b/code/__DEFINES/station.dm index 5a04961429fd4..e7d7e76f8415b 100644 --- a/code/__DEFINES/station.dm +++ b/code/__DEFINES/station.dm @@ -13,6 +13,8 @@ #define STATION_TRAIT_PLANETARY (1<<0) /// Only run on space stations #define STATION_TRAIT_SPACE_BOUND (1<<1) +/// Only run if AIs are enabled. +#define STATION_TRAIT_REQUIRES_AI (1<<2) /// Not restricted by space or planet, can always just happen #define STATION_TRAIT_MAP_UNRESTRICTED STATION_TRAIT_PLANETARY | STATION_TRAIT_SPACE_BOUND diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index 4e901c4ba2ce2..d7e981802619c 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -154,8 +154,8 @@ #define set_drowsiness(duration) set_timed_status_effect(duration, /datum/status_effect/drowsiness) #define set_drowsiness_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/drowsiness, TRUE) -#define adjust_pacifism(duration) adjust_timed_status_effect(/datum/status_effect/pacify, duration) -#define set_pacifism(duration) set_timed_status_effect(/datum/status_effect/pacify, duration) +#define adjust_pacifism(duration) adjust_timed_status_effect(duration, /datum/status_effect/pacify) +#define set_pacifism(duration) set_timed_status_effect(duration, /datum/status_effect/pacify) #define adjust_eye_blur(duration) adjust_timed_status_effect(duration, /datum/status_effect/eye_blur) #define adjust_eye_blur_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/eye_blur, up_to) diff --git a/code/__DEFINES/text.dm b/code/__DEFINES/text.dm index ffac885bf4c88..c303eebbcdc56 100644 --- a/code/__DEFINES/text.dm +++ b/code/__DEFINES/text.dm @@ -58,6 +58,12 @@ /// Removes everything enclose in < and > inclusive of the bracket, and limits the length of the message. #define STRIP_HTML_FULL(text, limit) (GLOB.html_tags.Replace(copytext(text, 1, limit), "")) +/** + * stuff like `copytext(input, length(input))` will trim the last character of the input, + * because DM does it so it copies until the char BEFORE the `end` arg, so we need to bump `end` by 1 in these cases. + */ +#define PREVENT_CHARACTER_TRIM_LOSS(integer) (integer + 1) + /// Folder directory for strings #define STRING_DIRECTORY "strings" diff --git a/code/__DEFINES/tgs.dm b/code/__DEFINES/tgs.dm index fdfec5e8ca086..a4fb6d40be73e 100644 --- a/code/__DEFINES/tgs.dm +++ b/code/__DEFINES/tgs.dm @@ -1,6 +1,6 @@ // tgstation-server DMAPI -#define TGS_DMAPI_VERSION "7.0.2" +#define TGS_DMAPI_VERSION "7.1.1" // All functions and datums outside this document are subject to change with any version and should not be relied on. @@ -50,6 +50,13 @@ #endif +#ifndef TGS_FILE2TEXT_NATIVE +#ifdef file2text +#error Your codebase is re-defining the BYOND proc file2text. The DMAPI requires the native version to read the result of world.Export(). You can fix this by adding "#define TGS_FILE2TEXT_NATIVE file2text" before your override of file2text to allow the DMAPI to use the native version. This will only be used for world.Export(), not regular file accesses +#endif +#define TGS_FILE2TEXT_NATIVE file2text +#endif + // EVENT CODES /// Before a reboot mode change, extras parameters are the current and new reboot mode enums. @@ -490,6 +497,16 @@ /world/proc/TgsChatChannelInfo() return +/** + * Trigger an event in TGS. Requires TGS version >= 6.3.0. Returns [TRUE] if the event was triggered successfully, [FALSE] otherwise. This function may sleep! + * + * event_name - The name of the event to trigger + * parameters - Optional list of string parameters to pass as arguments to the event script. The first parameter passed to a script will always be the running game's directory followed by these parameters. + * wait_for_completion - If set, this function will not return until the event has run to completion. + */ +/world/proc/TgsTriggerEvent(event_name, list/parameters, wait_for_completion = FALSE) + return + /* The MIT License diff --git a/code/__DEFINES/time.dm b/code/__DEFINES/time.dm index 1935c3c0aee3d..6a2a5152903ba 100644 --- a/code/__DEFINES/time.dm +++ b/code/__DEFINES/time.dm @@ -39,6 +39,7 @@ #define PRIDE_WEEK "Pride Week" #define MOTH_WEEK "Moth Week" #define IAN_HOLIDAY "Ian's Birthday" +#define HOTDOG_DAY "National Hot Dog Day" /* Days of the week to make it easier to reference them. diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index eef432fb2edfe..47adc0552ba54 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -221,6 +221,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_NO_THROW_HITPUSH "no_throw_hitpush" ///Added to mob or mind, changes the icons of the fish shown in the minigame UI depending on the possible reward. #define TRAIT_REVEAL_FISH "reveal_fish" +///This trait gets you a list of fishes that can be caught when examining a fishing spot. +#define TRAIT_EXAMINE_FISHING_SPOT "examine_fishing_spot" /// Added to a mob, allows that mob to experience flavour-based moodlets when examining food #define TRAIT_REMOTE_TASTING "remote_tasting" @@ -520,6 +522,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Makes the user handcuff others faster #define TRAIT_FAST_CUFFING "fast_cuffing" +///Makes the player appear as their respective job in Binary Talk rather than being a 'Default Cyborg'. +#define DISPLAYS_JOB_IN_BINARY "display_job_in_binary" + // METABOLISMS // Various jobs on the station have historically had better reactions // to various drinks and foodstuffs. Security liking donuts is a classic @@ -537,6 +542,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_BALLMER_SCIENTIST "ballmer_scientist" #define TRAIT_MAINTENANCE_METABOLISM "maintenance_metabolism" #define TRAIT_CORONER_METABOLISM "coroner_metabolism" +#define TRAIT_HUMAN_AI_METABOLISM "human_ai_metabolism" //LUNG TRAITS /// Lungs always breathe normally when in vacuum/space. @@ -666,8 +672,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_NO_STORAGE_INSERT "no_storage_insert" /// Visible on t-ray scanners if the atom/var/level == 1 #define TRAIT_T_RAY_VISIBLE "t-ray-visible" -/// If this item's been grilled -#define TRAIT_FOOD_GRILLED "food_grilled" /// If this item's been fried #define TRAIT_FOOD_FRIED "food_fried" /// This is a silver slime created item @@ -882,6 +886,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Trait given to a dreaming carbon when they are currently doing dreaming stuff #define TRAIT_DREAMING "currently_dreaming" +/// Whether bots will salute this mob. +#define TRAIT_COMMISSIONED "commissioned" + ///generic atom traits /// Trait from [/datum/element/rust]. Its rusty and should be applying a special overlay to denote this. #define TRAIT_RUSTY "rust_trait" @@ -926,6 +933,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define STATION_TRAIT_FILLED_MAINT "station_trait_filled_maint" #define STATION_TRAIT_FORESTED "station_trait_forested" #define STATION_TRAIT_HANGOVER "station_trait_hangover" +#define STATION_TRAIT_HUMAN_AI "station_trait_human_ai" #define STATION_TRAIT_LATE_ARRIVALS "station_trait_late_arrivals" #define STATION_TRAIT_LOANER_SHUTTLE "station_trait_loaner_shuttle" #define STATION_TRAIT_MEDBOT_MANIA "station_trait_medbot_mania" @@ -955,7 +963,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_MAGNETIC_ID_CARD "magnetic_id_card" /// ID cards with this trait have special appraisal text. #define TRAIT_TASTEFULLY_THICK_ID_CARD "impressive_very_nice" -/// things with this trait are treated as having no access in /obj/proc/check_access(obj/item) +/// things with this trait are treated as having no access in /atom/movable/proc/check_access(obj/item) #define TRAIT_ALWAYS_NO_ACCESS "alwaysnoaccess" /// This human wants to see the color of their glasses, for some reason diff --git a/code/__DEFINES/traits/sources.dm b/code/__DEFINES/traits/sources.dm index dd237bf54b704..7f4fbd929367f 100644 --- a/code/__DEFINES/traits/sources.dm +++ b/code/__DEFINES/traits/sources.dm @@ -34,6 +34,8 @@ #define CULT_TRAIT "cult" #define LICH_TRAIT "lich" +#define VENDING_MACHINE_TRAIT "vending_machine" + #define ABSTRACT_ITEM_TRAIT "abstract-item" /// A trait given by any status effect #define STATUS_EFFECT_TRAIT "status-effect" diff --git a/code/__DEFINES/uplink.dm b/code/__DEFINES/uplink.dm index d6412e0e4d150..bb92f0672c3a7 100644 --- a/code/__DEFINES/uplink.dm +++ b/code/__DEFINES/uplink.dm @@ -12,6 +12,9 @@ /// This item is purchasable to infiltrators (midround traitors) #define UPLINK_INFILTRATORS (1 << 3) +/// Can be randomly given to spies for their bounties +#define UPLINK_SPY (1 << 4) + /// Progression gets turned into a user-friendly form. This is just an abstract equation that makes progression not too large. #define DISPLAY_PROGRESSION(time) round(time/60, 0.01) @@ -19,3 +22,12 @@ #define TRAITOR_DISCOUNT_BIG "big_discount" #define TRAITOR_DISCOUNT_AVERAGE "average_discount" #define TRAITOR_DISCOUNT_SMALL "small_discount" + +/// Typepath used for uplink items which don't actually produce an item (essentially just a placeholder) +/// Future todo: Make this not necessary / make uplink items support item-less items natively +#define ABSTRACT_UPLINK_ITEM /obj/effect/gibspawner/generic + +/// Lower threshold for which an uplink items's TC cost is considered "low" for spy bounties picking rewards +#define SPY_LOWER_COST_THRESHOLD 5 +/// Upper threshold for which an uplink items's TC cost is considered "high" for spy bounties picking rewards +#define SPY_UPPER_COST_THRESHOLD 12 diff --git a/code/__HELPERS/atmospherics.dm b/code/__HELPERS/atmospherics.dm index 940418f2ebc70..3ac3bfaed569d 100644 --- a/code/__HELPERS/atmospherics.dm +++ b/code/__HELPERS/atmospherics.dm @@ -199,3 +199,10 @@ GLOBAL_LIST_EMPTY(gas_handbook) if(boundaries && boundaries[1] > 0) return FALSE return TRUE + +/proc/print_gas_mixture(datum/gas_mixture/gas_mixture) + var/message = "TEMPERATURE: [gas_mixture.temperature]K, QUANTITY: [gas_mixture.total_moles()] mols, VOLUME: [gas_mixture.volume]L; " + for(var/key in gas_mixture.gases) + var/list/gaslist = gas_mixture.gases[key] + message += "[gaslist[GAS_META][META_GAS_ID]]=[gaslist[MOLES]] mols;" + return message diff --git a/code/__HELPERS/clients.dm b/code/__HELPERS/clients.dm index 3b61cf1e1c456..156f9e2b5dcdf 100644 --- a/code/__HELPERS/clients.dm +++ b/code/__HELPERS/clients.dm @@ -10,3 +10,9 @@ if (ch < 48 || ch > 57) //0-9 return FALSE return TRUE + +/// Proc that just logs whenever an uninitialized client tries to do something before they have fully gone through New(). +/// Intended to be used in conjunction with the `VALIDATE_CLIENT_INITIALIZATION()` macro, but can be dropped anywhere when we look at the `fully_created` var on /client. +/proc/unvalidated_client_error(client/target) + to_chat(target, span_warning("You are not fully initialized yet! Please wait a moment.")) + log_access("Client [key_name(target)] attempted to execute a verb before being fully initialized.") diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm index f7889f0b5af80..f0741f3d8f3f2 100644 --- a/code/__HELPERS/global_lists.dm +++ b/code/__HELPERS/global_lists.dm @@ -258,3 +258,10 @@ GLOBAL_LIST_INIT(WALLITEMS_EXTERIOR, typecacheof(list( /obj/structure/camera_assembly, /obj/structure/light_construct, ))) + +/// A static typecache of all the money-based items that can be actively used as currency. +GLOBAL_LIST_INIT(allowed_money, typecacheof(list( + /obj/item/coin, + /obj/item/holochip, + /obj/item/stack/spacecash, +))) diff --git a/code/__HELPERS/logging/antagonists.dm b/code/__HELPERS/logging/antagonists.dm index 3d06bb325ec35..5df39c69adef3 100644 --- a/code/__HELPERS/logging/antagonists.dm +++ b/code/__HELPERS/logging/antagonists.dm @@ -21,3 +21,7 @@ /// Logging for wizard powers learned /proc/log_spellbook(text, list/data) logger.Log(LOG_CATEGORY_UPLINK_SPELL, text, data) + +/// Logs bounties completed by spies and their rewards +/proc/log_spy(text, list/data) + logger.Log(LOG_CATEGORY_UPLINK_SPY, text, data) diff --git a/code/__HELPERS/logging/atmos.dm b/code/__HELPERS/logging/atmos.dm index 0fcded5c7ab03..644c9e6562576 100644 --- a/code/__HELPERS/logging/atmos.dm +++ b/code/__HELPERS/logging/atmos.dm @@ -1,8 +1,48 @@ /// Logs the contents of the gasmix to the game log, prefixed by text -/proc/log_atmos(text, datum/gas_mixture/mix) - var/message = text - message += "TEMP=[mix.temperature], MOL=[mix.total_moles()], VOL=[mix.volume] " - for(var/key in mix.gases) - var/list/gaslist = mix.gases[key] - message += "[gaslist[GAS_META][META_GAS_ID]]=[gaslist[MOLES]];" - log_game(message) +/proc/log_atmos(text, datum/gas_mixture/gas_mixture) + var/message = "[text]\"[print_gas_mixture(gas_mixture)]\"" + //Cache commonly accessed information. + var/list/gases = gas_mixture.gases //List of gas datum paths that are associated with a list of information related to the gases. + var/heat_capacity = gas_mixture.heat_capacity() + var/temperature = gas_mixture.return_temperature() + var/thermal_energy = temperature * heat_capacity + var/volume = gas_mixture.return_volume() + var/pressure = gas_mixture.return_pressure() + var/total_moles = gas_mixture.total_moles() + ///The total value of the gas mixture in credits. + var/total_value = 0 + var/list/specific_gas_data = list() + + //Gas specific information assigned to each gas. + for(var/datum/gas/gas_path as anything in gases) + var/list/gas = gases[gas_path] + var/moles = gas[MOLES] + var/composition = moles / total_moles + var/energy = temperature * moles * gas[GAS_META][META_GAS_SPECIFIC_HEAT] + var/value = initial(gas_path.base_value) * moles + total_value += value + specific_gas_data[gas[GAS_META][META_GAS_NAME]] = list( + "moles" = moles, + "composition" = composition, + "molar concentration" = moles / volume, + "partial pressure" = composition * pressure, + "energy" = energy, + "energy density" = energy / volume, + "value" = value, + ) + + log_game( + message, + data = list( + "total moles" = total_moles, + "volume" = volume, + "molar density" = total_moles / volume, + "temperature" = temperature, + "pressure" = pressure, + "heat capacity" = heat_capacity, + "energy" = thermal_energy, + "energy density" = thermal_energy / volume, + "value" = total_value, + "gases" = specific_gas_data, + ) + ) diff --git a/code/__HELPERS/logging/debug.dm b/code/__HELPERS/logging/debug.dm index c4ed2f1086f01..ad5670d2d119e 100644 --- a/code/__HELPERS/logging/debug.dm +++ b/code/__HELPERS/logging/debug.dm @@ -23,6 +23,9 @@ /proc/log_mapping(text, skip_world_log) #ifdef UNIT_TESTS GLOB.unit_test_mapping_logs += text +#endif +#ifdef MAP_TEST + message_admins("Mapping: [text]") #endif logger.Log(LOG_CATEGORY_DEBUG_MAPPING, text) if(skip_world_log) diff --git a/code/__HELPERS/maths.dm b/code/__HELPERS/maths.dm index c28357eb478b9..ead9d54ebaa5f 100644 --- a/code/__HELPERS/maths.dm +++ b/code/__HELPERS/maths.dm @@ -131,23 +131,23 @@ * Returns: [SI_COEFFICIENT = si unit coefficient, SI_UNIT = prefixed si unit.] */ /proc/siunit_isolated(value, unit, maxdecimals=1) - var/static/list/prefixes = list("f","p","n","μ","m","","k","M","G","T","P") + var/static/list/prefixes = list("q","r","y","z","a","f","p","n","μ","m","","k","M","G","T","P","E","Z","Y","R","Q") // We don't have prefixes beyond this point // and this also captures value = 0 which you can't compute the logarithm for // and also byond numbers are floats and doesn't have much precision beyond this point anyway - if(abs(value) <= 1e-18) + if(abs(value) < 1e-30) . = list(SI_COEFFICIENT = 0, SI_UNIT = " [unit]") return - var/exponent = clamp(log(10, abs(value)), -15, 15) // Calculate the exponent and clamp it so we don't go outside the prefix list bounds + var/exponent = clamp(log(10, abs(value)), -30, 30) // Calculate the exponent and clamp it so we don't go outside the prefix list bounds var/divider = 10 ** (round(exponent / 3) * 3) // Rounds the exponent to nearest SI unit and power it back to the full form var/coefficient = round(value / divider, 10 ** -maxdecimals) // Calculate the coefficient and round it to desired decimals - var/prefix_index = round(exponent / 3) + 6 // Calculate the index in the prefixes list for this exponent + var/prefix_index = round(exponent / 3) + 11 // Calculate the index in the prefixes list for this exponent // An edge case which happens if we round 999.9 to 0 decimals for example, which gets rounded to 1000 // In that case, we manually swap up to the next prefix if there is one available - if(coefficient >= 1000 && prefix_index < 11) + if(coefficient >= 1000 && prefix_index < 21) coefficient /= 1e3 prefix_index++ diff --git a/code/__HELPERS/paths/path.dm b/code/__HELPERS/paths/path.dm index 28ef66aa45e6a..61e50601e7848 100644 --- a/code/__HELPERS/paths/path.dm +++ b/code/__HELPERS/paths/path.dm @@ -281,7 +281,7 @@ /// Are we being thrown? var/thrown = FALSE /// Are we anchored - var/anchored = FLASH_LIGHT_POWER + var/anchored = FALSE /// Are we a ghost? (they have effectively unique pathfinding) var/is_observer = FALSE diff --git a/code/__HELPERS/type2type.dm b/code/__HELPERS/type2type.dm index 7e97724b5620f..9eb1a491f425a 100644 --- a/code/__HELPERS/type2type.dm +++ b/code/__HELPERS/type2type.dm @@ -36,8 +36,8 @@ return "northwest" if(SOUTHWEST) return "southwest" - else - return + + return NONE //Turns text into proper directions /proc/text2dir(direction) @@ -58,8 +58,8 @@ return SOUTHEAST if("SOUTHWEST") return SOUTHWEST - else - return + + return NONE //Converts an angle (degrees) into a ss13 direction GLOBAL_LIST_INIT(modulo_angle_to_dir, list(NORTH,NORTHEAST,EAST,SOUTHEAST,SOUTH,SOUTHWEST,WEST,NORTHWEST)) diff --git a/code/__byond_version_compat.dm b/code/__byond_version_compat.dm index 5eb4bda14e784..87d4348580e29 100644 --- a/code/__byond_version_compat.dm +++ b/code/__byond_version_compat.dm @@ -2,11 +2,11 @@ //Update this whenever you need to take advantage of more recent byond features #define MIN_COMPILER_VERSION 515 -#define MIN_COMPILER_BUILD 1621 +#define MIN_COMPILER_BUILD 1627 #if (DM_VERSION < MIN_COMPILER_VERSION || DM_BUILD < MIN_COMPILER_BUILD) && !defined(SPACEMAN_DMM) //Don't forget to update this part #error Your version of BYOND is too out-of-date to compile this project. Go to https://secure.byond.com/download and update. -#error You need version 515.1621 or higher +#error You need version 515.1627 or higher #endif // Keep savefile compatibilty at minimum supported level diff --git a/code/_compile_options.dm b/code/_compile_options.dm index 2a4854c37b858..0d534fac9a36c 100644 --- a/code/_compile_options.dm +++ b/code/_compile_options.dm @@ -112,7 +112,7 @@ #warn compiling in TESTING mode. testing() debug messages will be visible. #endif -#ifdef CIBUILDING +#if defined(CIBUILDING) && !defined(OPENDREAM) #define UNIT_TESTS #endif @@ -137,8 +137,24 @@ #define CBT #endif -#if !defined(CBT) && !defined(SPACEMAN_DMM) -#warn Building with Dream Maker is no longer supported and will result in errors. -#warn In order to build, run BUILD.bat in the root directory. -#warn Consider switching to VSCode editor instead, where you can press Ctrl+Shift+B to build. +#if defined(OPENDREAM) + #if !defined(CIBUILDING) + #warn You are building with OpenDream. Remember to build TGUI manually. + #warn You can do this by running tgui-build.cmd from the bin directory. + #endif +#else + #if !defined(CBT) && !defined(SPACEMAN_DMM) + #warn Building with Dream Maker is no longer supported and will result in errors. + #warn In order to build, run BUILD.cmd in the root directory. + #warn Consider switching to VSCode editor instead, where you can press Ctrl+Shift+B to build. + #endif +#endif + +/// Runs the game in "map test mode" +/// Map test mode prevents common annoyances, such as rats from spawning and random light fixture breakage, +/// so mappers can test important facets of their map (working powernet, atmos, good light coverage) without these interfering. +// #define MAP_TEST + +#ifdef MAP_TEST +#warn Compiling in MAP_TEST mode. Certain game mechanics will be disabled. #endif diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index 4135428194b8f..37e7ef30d41c7 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -313,7 +313,8 @@ DEFINE_BITFIELD(resistance_flags, list( "UNACIDABLE" = UNACIDABLE, "ACID_PROOF" = ACID_PROOF, "INDESTRUCTIBLE" = INDESTRUCTIBLE, - "FREEZE_PROOF" = FREEZE_PROOF + "FREEZE_PROOF" = FREEZE_PROOF, + "SHUTTLE_CRUSH_PROOF" = SHUTTLE_CRUSH_PROOF )) DEFINE_BITFIELD(sight, list( diff --git a/code/_globalvars/lists/maintenance_loot.dm b/code/_globalvars/lists/maintenance_loot.dm index d95a46c8be249..36f96bcc563e0 100644 --- a/code/_globalvars/lists/maintenance_loot.dm +++ b/code/_globalvars/lists/maintenance_loot.dm @@ -302,6 +302,7 @@ GLOBAL_LIST_INIT(rarity_loot, list(//rare: really good items /obj/item/pen/survival = 1, /obj/item/restraints/handcuffs = 1, /obj/item/shield/buckler = 1, + /obj/item/shield/improvised = 1, /obj/item/throwing_star = 1, /obj/item/weldingtool/hugetank = 1, /obj/item/fishing_rod/telescopic/master = 1, diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 15715ebd0a050..3fc7e801265e5 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -18,7 +18,6 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_DRYABLE" = TRAIT_DRYABLE, "TRAIT_FOOD_CHEF_MADE" = TRAIT_FOOD_CHEF_MADE, "TRAIT_FOOD_FRIED" = TRAIT_FOOD_FRIED, - "TRAIT_FOOD_GRILLED" = TRAIT_FOOD_GRILLED, "TRAIT_FOOD_SILVER" = TRAIT_FOOD_SILVER, "TRAIT_KEEP_TOGETHER" = TRAIT_KEEP_TOGETHER, "TRAIT_LIGHTING_DEBUGGED" = TRAIT_LIGHTING_DEBUGGED, @@ -26,6 +25,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_RUSTY" = TRAIT_RUSTY, "TRAIT_SPINNING" = TRAIT_SPINNING, "TRAIT_STICKERED" = TRAIT_STICKERED, + "TRAIT_COMMISSIONED" = TRAIT_COMMISSIONED, ), /atom/movable = list( "TRAIT_ACTIVE_STORAGE" = TRAIT_ACTIVE_STORAGE, @@ -84,6 +84,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "STATION_TRAIT_FILLED_MAINT" = STATION_TRAIT_FILLED_MAINT, "STATION_TRAIT_FORESTED" = STATION_TRAIT_FORESTED, "STATION_TRAIT_HANGOVER" = STATION_TRAIT_HANGOVER, + "STATION_TRAIT_HUMAN_AI" = STATION_TRAIT_HUMAN_AI, "STATION_TRAIT_LATE_ARRIVALS" = STATION_TRAIT_LATE_ARRIVALS, "STATION_TRAIT_LOANER_SHUTTLE" = STATION_TRAIT_LOANER_SHUTTLE, "STATION_TRAIT_MEDBOT_MANIA" = STATION_TRAIT_MEDBOT_MANIA, @@ -176,6 +177,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_DISEASELIKE_SEVERITY_MEDIUM" = TRAIT_DISEASELIKE_SEVERITY_MEDIUM, "TRAIT_DISFIGURED" = TRAIT_DISFIGURED, "TRAIT_DISGUISED" = TRAIT_DISGUISED, + "DISPLAYS_JOB_IN_BINARY" = DISPLAYS_JOB_IN_BINARY, "TRAIT_DISK_VERIFIER" = TRAIT_DISK_VERIFIER, "TRAIT_DISSECTED" = TRAIT_DISSECTED, "TRAIT_DONT_WRITE_MEMORY" = TRAIT_DONT_WRITE_MEMORY, @@ -195,6 +197,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_EMOTEMUTE" = TRAIT_EMOTEMUTE, "TRAIT_EMPATH" = TRAIT_EMPATH, "TRAIT_ENTRAILS_READER" = TRAIT_ENTRAILS_READER, + "TRAIT_EXAMINE_FISHING_SPOT" = TRAIT_EXAMINE_FISHING_SPOT, "TRAIT_EXPANDED_FOV" = TRAIT_EXPANDED_FOV, "TRAIT_EXTROVERT" = TRAIT_EXTROVERT, "TRAIT_FAKEDEATH" = TRAIT_FAKEDEATH, @@ -557,6 +560,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_CORONER_METABOLISM" = TRAIT_CORONER_METABOLISM, "TRAIT_CULINARY_METABOLISM" = TRAIT_CULINARY_METABOLISM, "TRAIT_ENGINEER_METABOLISM" = TRAIT_ENGINEER_METABOLISM, + "TRAIT_HUMAN_AI_METABOLISM" = TRAIT_HUMAN_AI_METABOLISM, "TRAIT_LAW_ENFORCEMENT_METABOLISM" = TRAIT_LAW_ENFORCEMENT_METABOLISM, "TRAIT_MAINTENANCE_METABOLISM" = TRAIT_MAINTENANCE_METABOLISM, "TRAIT_MEDICAL_METABOLISM" = TRAIT_MEDICAL_METABOLISM, diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm index 7a91d898620b9..1d2b5f748c3f1 100644 --- a/code/_globalvars/traits/admin_tooling.dm +++ b/code/_globalvars/traits/admin_tooling.dm @@ -49,6 +49,7 @@ GLOBAL_LIST_INIT(admin_visible_traits, list( "TRAIT_CHUNKYFINGERS" = TRAIT_CHUNKYFINGERS, "TRAIT_CLOWN_ENJOYER" = TRAIT_CLOWN_ENJOYER, "TRAIT_CLUMSY" = TRAIT_CLUMSY, + "TRAIT_COMMISSIONED" = TRAIT_COMMISSIONED, "TRAIT_CRITICAL_CONDITION" = TRAIT_CRITICAL_CONDITION, "TRAIT_CULT_HALO" = TRAIT_CULT_HALO, "TRAIT_DEAF" = TRAIT_DEAF, diff --git a/code/_onclick/hud/ai.dm b/code/_onclick/hud/ai.dm index 5f687d1964281..1d26c4916b04b 100644 --- a/code/_onclick/hud/ai.dm +++ b/code/_onclick/hud/ai.dm @@ -186,6 +186,11 @@ using.screen_loc = ui_ai_language_menu static_inventory += using +// Z-level floor change + using = new /atom/movable/screen/floor_menu(null, src) + using.screen_loc = ui_ai_floor_menu + static_inventory += using + //AI core using = new /atom/movable/screen/ai/aicore(null, src) using.screen_loc = ui_ai_core diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index 1f5f7588162ab..8b96a5491bd47 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -68,8 +68,8 @@ if(client && hud_used) hud_used.reorganize_alerts() if(!no_anim) - thealert.transform = matrix(32, 6, MATRIX_TRANSLATE) - animate(thealert, transform = matrix(), time = 2.5, easing = CUBIC_EASING) + thealert.transform = matrix(32, 0, MATRIX_TRANSLATE) + animate(thealert, transform = matrix(), time = 1 SECONDS, easing = ELASTIC_EASING) if(timeout_override) thealert.timeout = timeout_override if(thealert.timeout) @@ -185,22 +185,6 @@ //End gas alerts - -/atom/movable/screen/alert/fat - name = "Fat" - desc = "You ate too much food, lardass. Run around the station and lose some weight." - icon_state = "fat" - -/atom/movable/screen/alert/hungry - name = "Hungry" - desc = "Some food would be good right about now." - icon_state = "hungry" - -/atom/movable/screen/alert/starving - name = "Starving" - desc = "You're severely malnourished. The hunger pains make moving around a chore." - icon_state = "starving" - /atom/movable/screen/alert/gross name = "Grossed out." desc = "That was kind of gross..." @@ -887,7 +871,7 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." set_never_round() return if(LAZYACCESS(modifiers, CTRL_CLICK) && poll.jump_to_me) - jump_to_pic_source() + jump_to_jump_target() return handle_sign_up() @@ -907,7 +891,7 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." poll.undo_never_for_this_round(owner) color = initial(color) -/atom/movable/screen/alert/poll_alert/proc/jump_to_pic_source() +/atom/movable/screen/alert/poll_alert/proc/jump_to_jump_target() if(!poll?.jump_to_me || !isobserver(owner)) return var/turf/target_turf = get_turf(poll.jump_to_me) @@ -921,7 +905,7 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." if(href_list["signup"]) handle_sign_up() if(href_list["jump"]) - jump_to_pic_source() + jump_to_jump_target() return /atom/movable/screen/alert/poll_alert/proc/update_signed_up_overlay() @@ -1034,39 +1018,34 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." // PRIVATE = only edit, use, or override these if you're editing the system as a whole +/// Gets the placement for the alert based on its index +/datum/hud/proc/get_ui_alert_placement(index) + // Only has support for 5 slots currently + if(index > 5) + return "" + + return "EAST-1:28,CENTER+[6 - index]:[29 - (index * 2)]" + // Re-render all alerts - also called in /datum/hud/show_hud() because it's needed there /datum/hud/proc/reorganize_alerts(mob/viewmob) var/mob/screenmob = viewmob || mymob if(!screenmob.client) - return + return FALSE var/list/alerts = mymob.alerts if(!hud_shown) for(var/i in 1 to alerts.len) screenmob.client.screen -= alerts[alerts[i]] - return 1 - for(var/i in 1 to alerts.len) + return TRUE + for(var/i in 1 to length(alerts)) var/atom/movable/screen/alert/alert = alerts[alerts[i]] if(alert.icon_state == "template") alert.icon = ui_style - switch(i) - if(1) - . = ui_alert1 - if(2) - . = ui_alert2 - if(3) - . = ui_alert3 - if(4) - . = ui_alert4 - if(5) - . = ui_alert5 // Right now there's 5 slots - else - . = "" - alert.screen_loc = . + alert.screen_loc = get_ui_alert_placement(i) screenmob.client.screen |= alert if(!viewmob) - for(var/M in mymob.observers) - reorganize_alerts(M) - return 1 + for(var/viewer in mymob.observers) + reorganize_alerts(viewer) + return TRUE /atom/movable/screen/alert/Click(location, control, params) if(!usr || !usr.client) diff --git a/code/_onclick/hud/alien.dm b/code/_onclick/hud/alien.dm index 3c1b1029a3e06..c3b91173a45f5 100644 --- a/code/_onclick/hud/alien.dm +++ b/code/_onclick/hud/alien.dm @@ -63,6 +63,10 @@ using.screen_loc = ui_alien_language_menu static_inventory += using + using = new /atom/movable/screen/floor_menu(null, src) + using.screen_loc = ui_alien_floor_menu + static_inventory += using + using = new /atom/movable/screen/navigate(null, src) using.screen_loc = ui_alien_navigate_menu static_inventory += using @@ -87,7 +91,7 @@ pull_icon.update_appearance() pull_icon.screen_loc = ui_above_movement static_inventory += pull_icon - + rest_icon = new /atom/movable/screen/rest(null, src) rest_icon.icon = ui_style rest_icon.screen_loc = ui_above_intent diff --git a/code/_onclick/hud/alien_larva.dm b/code/_onclick/hud/alien_larva.dm index d9ebb3611b68b..77d135ce2c663 100644 --- a/code/_onclick/hud/alien_larva.dm +++ b/code/_onclick/hud/alien_larva.dm @@ -32,6 +32,10 @@ using.screen_loc = ui_alien_language_menu static_inventory += using + using = new /atom/movable/screen/floor_menu(null, src) + using.screen_loc = ui_alien_floor_menu + static_inventory += using + using = new /atom/movable/screen/navigate(null, src) using.screen_loc = ui_alien_navigate_menu static_inventory += using diff --git a/code/_onclick/hud/ghost.dm b/code/_onclick/hud/ghost.dm index 99b04df906871..e20c1ede2f663 100644 --- a/code/_onclick/hud/ghost.dm +++ b/code/_onclick/hud/ghost.dm @@ -86,6 +86,16 @@ using.icon = ui_style static_inventory += using + using = new /atom/movable/screen/language_menu(null, src) + using.screen_loc = ui_ghost_language_menu + using.icon = ui_style + static_inventory += using + + using = new /atom/movable/screen/floor_menu(null, src) + using.screen_loc = ui_ghost_floor_menu + using.icon = ui_style + static_inventory += using + /datum/hud/ghost/show_hud(version = 0, mob/viewmob) // don't show this HUD if observing; show the HUD of the observee var/mob/dead/observer/O = mymob diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm index b915e3dc19c86..0b907833f76c7 100644 --- a/code/_onclick/hud/hud.dm +++ b/code/_onclick/hud/hud.dm @@ -95,6 +95,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list( var/atom/movable/screen/stamina var/atom/movable/screen/healthdoll var/atom/movable/screen/spacesuit + var/atom/movable/screen/hunger // subtypes can override this to force a specific UI style var/ui_style @@ -239,6 +240,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list( stamina = null healthdoll = null spacesuit = null + hunger = null blobpwrdisplay = null alien_plasma_display = null alien_queen_finder = null diff --git a/code/_onclick/hud/human.dm b/code/_onclick/hud/human.dm index 22a046970cb02..5834a3973555c 100644 --- a/code/_onclick/hud/human.dm +++ b/code/_onclick/hud/human.dm @@ -70,6 +70,10 @@ using.icon = ui_style static_inventory += using + using = new /atom/movable/screen/floor_menu(null, src) + using.icon = ui_style + static_inventory += using + action_intent = new /atom/movable/screen/combattoggle/flashy(null, src) action_intent.icon = ui_style action_intent.screen_loc = ui_combat_toggle @@ -268,6 +272,9 @@ healths = new /atom/movable/screen/healths(null, src) infodisplay += healths + hunger = new /atom/movable/screen/hunger(null, src) + infodisplay += hunger + healthdoll = new /atom/movable/screen/healthdoll(null, src) infodisplay += healthdoll diff --git a/code/_onclick/hud/robot.dm b/code/_onclick/hud/robot.dm index ea890566f74cf..090b8876cba44 100644 --- a/code/_onclick/hud/robot.dm +++ b/code/_onclick/hud/robot.dm @@ -77,6 +77,7 @@ var/mob/living/silicon/robot/robit = mymob var/atom/movable/screen/using +// Language using = new/atom/movable/screen/language_menu(null, src) using.screen_loc = ui_borg_language_menu static_inventory += using @@ -86,6 +87,11 @@ using.screen_loc = ui_borg_navigate_menu static_inventory += using +// Z-level floor change + using = new /atom/movable/screen/floor_menu(null, src) + using.screen_loc = ui_borg_floor_menu + static_inventory += using + //Radio using = new /atom/movable/screen/robot/radio(null, src) using.screen_loc = ui_borg_radio diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index f75231722749f..dcf7e230906e3 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -127,6 +127,33 @@ /atom/movable/screen/language_menu/Click() usr.get_language_holder().open_language_menu(usr) +/atom/movable/screen/floor_menu + name = "change floor" + icon = 'icons/hud/screen_midnight.dmi' + icon_state = "floor_change" + screen_loc = ui_floor_menu + +/atom/movable/screen/floor_menu/Initialize(mapload) + . = ..() + register_context() + +/atom/movable/screen/floor_menu/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + + context[SCREENTIP_CONTEXT_LMB] = "Go up a floor" + context[SCREENTIP_CONTEXT_RMB] = "Go down a floor" + return CONTEXTUAL_SCREENTIP_SET + +/atom/movable/screen/floor_menu/Click(location,control,params) + var/list/modifiers = params2list(params) + + if(LAZYACCESS(modifiers, RIGHT_CLICK) || LAZYACCESS(modifiers, ALT_CLICK)) + usr.down() + return + + usr.up() + return + /atom/movable/screen/inventory /// The identifier for the slot. It has nothing to do with ID cards. var/slot_id @@ -723,3 +750,102 @@ INITIALIZE_IMMEDIATE(/atom/movable/screen/splash) name = "stamina" icon_state = "stamina0" screen_loc = ui_stamina + +#define HUNGER_STATE_FAT 2 +#define HUNGER_STATE_FULL 1 +#define HUNGER_STATE_FINE 0 +#define HUNGER_STATE_HUNGRY -1 +#define HUNGER_STATE_STARVING -2 + +/atom/movable/screen/hunger + name = "hunger" + icon_state = "hungerbar" + base_icon_state = "hungerbar" + screen_loc = ui_hunger + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + /// What state of hunger are we in? + VAR_PRIVATE/state = HUNGER_STATE_FINE + /// What food icon do we show by the bar + var/food_icon = 'icons/obj/food/burgerbread.dmi' + /// What food icon state do we show by the bar + var/food_icon_state = "hburger" + /// The image shown by the bar. + VAR_PRIVATE/image/food_image + +/atom/movable/screen/hunger/Initialize(mapload, datum/hud/hud_owner) + . = ..() + var/mob/living/hungry = hud_owner?.mymob + if(!istype(hungry)) + return + + if(!ishuman(hungry) || CONFIG_GET(flag/disable_human_mood)) + screen_loc = ui_mood // Slot in where mood normally is if mood is disabled + + food_image = image(icon = food_icon, icon_state = food_icon_state, pixel_x = -5) + food_image.plane = plane + food_image.appearance_flags |= KEEP_APART // To be unaffected by filters applied to src + food_image.add_filter("simple_outline", 2, outline_filter(1, COLOR_BLACK)) + underlays += food_image // To be below filters applied to src + + SetInvisibility(INVISIBILITY_ABSTRACT, name) // Start invisible, update later + update_appearance() + +/atom/movable/screen/hunger/proc/update_hunger_state() + var/mob/living/hungry = hud?.mymob + if(!istype(hungry)) + return + + if(HAS_TRAIT(hungry, TRAIT_NOHUNGER) || !hungry.get_organ_slot(ORGAN_SLOT_STOMACH)) + state = HUNGER_STATE_FINE + return + + if(HAS_TRAIT(hungry, TRAIT_FAT)) + state = HUNGER_STATE_FAT + return + + switch(hungry.nutrition) + if(NUTRITION_LEVEL_FULL to INFINITY) + state = HUNGER_STATE_FULL + if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_FULL) + state = HUNGER_STATE_FINE + if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_HUNGRY) + state = HUNGER_STATE_HUNGRY + if(0 to NUTRITION_LEVEL_STARVING) + state = HUNGER_STATE_STARVING + +/atom/movable/screen/hunger/update_appearance(updates) + var/old_state = state + update_hunger_state() // Do this before we call all the other update procs + . = ..() + if(state == old_state) // Let's not be wasteful + return + if(state == HUNGER_STATE_FINE) + SetInvisibility(INVISIBILITY_ABSTRACT, name) + return + + else if(invisibility) + RemoveInvisibility(name) + + if(state == HUNGER_STATE_STARVING) + if(!get_filter("hunger_outline")) + add_filter("hunger_outline", 1, list("type" = "outline", "color" = "#FF0033", "alpha" = 0, "size" = 2)) + animate(get_filter("hunger_outline"), alpha = 200, time = 1.5 SECONDS, loop = -1) + animate(alpha = 0, time = 1.5 SECONDS) + + else if(get_filter("hunger_outline")) + remove_filter("hunger_outline") + + // Update color of the food + underlays -= food_image + food_image.color = state == HUNGER_STATE_FAT ? COLOR_DARK : null + underlays += food_image + +/atom/movable/screen/hunger/update_icon_state() + . = ..() + icon_state = "[base_icon_state][state]" + +#undef HUNGER_STATE_FAT +#undef HUNGER_STATE_FULL +#undef HUNGER_STATE_FINE +#undef HUNGER_STATE_HUNGRY +#undef HUNGER_STATE_STARVING diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index d9a0118a06ded..25984a04aa7b4 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -25,7 +25,7 @@ if (SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) return TRUE if (SECONDARY_ATTACK_CONTINUE_CHAIN) - // Normal behavior + EMPTY_BLOCK_GUARD // Normal behavior else CRASH("pre_attack_secondary must return an SECONDARY_ATTACK_* define, please consult code/__DEFINES/combat.dm") else @@ -43,7 +43,7 @@ if (SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) return TRUE if (SECONDARY_ATTACK_CONTINUE_CHAIN) - // Normal behavior + EMPTY_BLOCK_GUARD // Normal behavior else CRASH("attackby_secondary must return an SECONDARY_ATTACK_* define, please consult code/__DEFINES/combat.dm") else @@ -264,6 +264,7 @@ user.changeNext_move(attack_speed) user.do_attack_animation(attacked_atom) attacked_atom.attacked_by(src, user) + SEND_SIGNAL(src, COMSIG_ITEM_POST_ATTACK_ATOM, attacked_atom, user) /// Called from [/obj/item/proc/attack_atom] and [/obj/item/proc/attack] if the attack succeeds /atom/proc/attacked_by(obj/item/attacking_item, mob/living/user) diff --git a/code/controllers/subsystem/bitrunning.dm b/code/controllers/subsystem/bitrunning.dm new file mode 100644 index 0000000000000..2b303911e42c9 --- /dev/null +++ b/code/controllers/subsystem/bitrunning.dm @@ -0,0 +1,60 @@ +#define REDACTED "???" + +SUBSYSTEM_DEF(bitrunning) + name = "Bitrunning" + flags = SS_NO_FIRE + + var/list/all_domains = list() + +/datum/controller/subsystem/bitrunning/Initialize() + InitializeDomains() + return SS_INIT_SUCCESS + +/datum/controller/subsystem/bitrunning/proc/InitializeDomains() + for(var/path in subtypesof(/datum/lazy_template/virtual_domain)) + all_domains += new path() + +/// Compiles a list of available domains. +/datum/controller/subsystem/bitrunning/proc/get_available_domains(scanner_tier, points) + var/list/levels = list() + + for(var/datum/lazy_template/virtual_domain/domain as anything in all_domains) + if(domain.test_only) + continue + var/can_view = domain.difficulty < scanner_tier && domain.cost <= points + 5 + var/can_view_reward = domain.difficulty < (scanner_tier + 1) && domain.cost <= points + 3 + + levels += list(list( + "cost" = domain.cost, + "desc" = can_view ? domain.desc : "Limited scanning capabilities. Cannot infer domain details.", + "difficulty" = domain.difficulty, + "id" = domain.key, + "is_modular" = domain.is_modular, + "has_secondary_objectives" = assoc_value_sum(domain.secondary_loot) ? TRUE : FALSE, + "name" = can_view ? domain.name : REDACTED, + "reward" = can_view_reward ? domain.reward_points : REDACTED, + )) + + return levels + +/datum/controller/subsystem/bitrunning/proc/pick_secondary_loot(completed_domain) + var/datum/lazy_template/virtual_domain/domain = completed_domain + var/choice + + if(assoc_value_sum(domain.secondary_loot)) + choice = pick_weight(domain.secondary_loot) + domain.secondary_loot[choice] -= 1 + else + choice = /obj/item/paper/paperslip/bitrunning_error + CRASH("Virtual domain [domain.name] tried to pick secondary objective loot, but secondary_loot list was empty.") + return choice + +/obj/item/paper/paperslip/bitrunning_error + name = "Apology Letter" + desc = "Something went wrong here." + +/obj/item/paper/paperslip/bitrunning_error/Initialize(mapload) + default_raw_text = "Your reward for collecting the encrypted curiosity failed to arrive, please report this to technical support." + return ..() + +#undef REDACTED diff --git a/code/controllers/subsystem/blackmarket.dm b/code/controllers/subsystem/blackmarket.dm index 357fa0df2915d..bdd342cbf3d04 100644 --- a/code/controllers/subsystem/blackmarket.dm +++ b/code/controllers/subsystem/blackmarket.dm @@ -21,17 +21,20 @@ SUBSYSTEM_DEF(blackmarket) for(var/market in subtypesof(/datum/market)) markets[market] += new market - for(var/item in subtypesof(/datum/market_item)) - var/datum/market_item/I = new item() - if(!I.item) + for(var/datum/market_item/item as anything in subtypesof(/datum/market_item)) + if(!initial(item.item)) + continue + if(!prob(initial(item.availability_prob))) continue - for(var/M in I.markets) - if(!markets[M]) - stack_trace("SSblackmarket: Item [I] available in market that does not exist.") + var/datum/market_item/item_instance = new item() + for(var/potential_market in item_instance.markets) + if(!markets[potential_market]) + stack_trace("SSblackmarket: Item [item_instance] available in market that does not exist.") continue - markets[M].add_item(item) - qdel(I) + // If this fails the market item will just be GC'd + markets[potential_market].add_item(item_instance) + return SS_INIT_SUCCESS /datum/controller/subsystem/blackmarket/fire(resumed) diff --git a/code/controllers/subsystem/dbcore.dm b/code/controllers/subsystem/dbcore.dm index 15c205b6748f0..b893ee701844a 100644 --- a/code/controllers/subsystem/dbcore.dm +++ b/code/controllers/subsystem/dbcore.dm @@ -1,3 +1,4 @@ +#define SHUTDOWN_QUERY_TIMELIMIT (1 MINUTES) SUBSYSTEM_DEF(dbcore) name = "Database" flags = SS_TICKER @@ -6,12 +7,17 @@ SUBSYSTEM_DEF(dbcore) init_order = INIT_ORDER_DBCORE priority = FIRE_PRIORITY_DATABASE - var/failed_connection_timeout = 0 - var/schema_mismatch = 0 var/db_minor = 0 var/db_major = 0 + /// Number of failed connection attempts this try. Resets after the timeout or successful connection var/failed_connections = 0 + /// Max number of consecutive failures before a timeout (here and not a define so it can be vv'ed mid round if needed) + var/max_connection_failures = 5 + /// world.time that connection attempts can resume + var/failed_connection_timeout = 0 + /// Total number of times connections have had to be timed out. + var/failed_connection_timeout_count = 0 var/last_error @@ -174,18 +180,27 @@ SUBSYSTEM_DEF(dbcore) /datum/controller/subsystem/dbcore/Shutdown() shutting_down = TRUE - to_chat(world, span_boldannounce("Clearing DB queries standby:[length(queries_standby)] active: [length(queries_active)] all: [length(all_queries)]")) + var/msg = "Clearing DB queries standby:[length(queries_standby)] active: [length(queries_active)] all: [length(all_queries)]" + to_chat(world, span_boldannounce(msg)) + log_world(msg) //This is as close as we can get to the true round end before Disconnect() without changing where it's called, defeating the reason this is a subsystem + var/endtime = REALTIMEOFDAY + SHUTDOWN_QUERY_TIMELIMIT if(SSdbcore.Connect()) - //Execute all waiting queries + //Take over control of all active queries + var/queries_to_check = queries_active.Copy() + queries_active.Cut() + + //Start all waiting queries for(var/datum/db_query/query in queries_standby) - run_query_sync(query) + run_query(query) + queries_to_check += query queries_standby -= query - for(var/datum/db_query/query in queries_active) - //Finish any remaining active qeries - UNTIL(query.process()) - queries_active -= query - + + //wait for them all to finish + for(var/datum/db_query/query in queries_to_check) + UNTIL(query.process() || REALTIMEOFDAY > endtime) + + //log shutdown to the db var/datum/db_query/query_round_shutdown = SSdbcore.NewQuery( "UPDATE [format_table_name("round")] SET shutdown_datetime = Now(), end_state = :end_state WHERE id = :round_id", list("end_state" = SSticker.end_state, "round_id" = GLOB.round_id), @@ -194,7 +209,9 @@ SUBSYSTEM_DEF(dbcore) query_round_shutdown.Execute(FALSE) qdel(query_round_shutdown) - to_chat(world, span_boldannounce("Done clearing DB queries standby:[length(queries_standby)] active: [length(queries_active)] all: [length(all_queries)]")) + msg = "Done clearing DB queries standby:[length(queries_standby)] active: [length(queries_active)] all: [length(all_queries)]" + to_chat(world, span_boldannounce(msg)) + log_world(msg) if(IsConnected()) Disconnect() stop_db_daemon() @@ -230,12 +247,16 @@ SUBSYSTEM_DEF(dbcore) /datum/controller/subsystem/dbcore/proc/Connect() if(IsConnected()) return TRUE + + if(connection) + Disconnect() //clear the current connection handle so isconnected() calls stop invoking rustg + connection = null //make sure its cleared even if runtimes happened - if(failed_connection_timeout <= world.time) //it's been more than 5 seconds since we failed to connect, reset the counter + if(failed_connection_timeout <= world.time) //it's been long enough since we failed to connect, reset the counter failed_connections = 0 + failed_connection_timeout = 0 - if(failed_connections > 5) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect for 5 seconds. - failed_connection_timeout = world.time + 50 + if(failed_connection_timeout > 0) return FALSE if(!CONFIG_GET(flag/sql_enabled)) @@ -271,6 +292,11 @@ SUBSYSTEM_DEF(dbcore) last_error = result["data"] log_sql("Connect() failed | [last_error]") ++failed_connections + //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect for a time. + if(failed_connections > max_connection_failures) + failed_connection_timeout_count++ + //basic exponential backoff algorithm + failed_connection_timeout = world.time + ((2 ** failed_connection_timeout_count) SECONDS) /datum/controller/subsystem/dbcore/proc/CheckSchemaVersion() if(CONFIG_GET(flag/sql_enabled)) @@ -650,3 +676,4 @@ Ignore_errors instructes mysql to continue inserting rows if some of them have e /datum/db_query/proc/Close() rows = null item = null +#undef SHUTDOWN_QUERY_TIMELIMIT diff --git a/code/controllers/subsystem/dynamic/dynamic.dm b/code/controllers/subsystem/dynamic/dynamic.dm index f7c37cb41e3f7..aca8ff1fc9e7e 100644 --- a/code/controllers/subsystem/dynamic/dynamic.dm +++ b/code/controllers/subsystem/dynamic/dynamic.dm @@ -359,6 +359,7 @@ SUBSYSTEM_DEF(dynamic) . += "
Additional Notes:

" + footnote_pile +#ifndef MAP_TEST print_command_report(., "[command_name()] Status Summary", announce=FALSE) if(greenshift) priority_announce("Thanks to the tireless efforts of our security and intelligence divisions, there are currently no credible threats to [station_name()]. All station construction projects have been authorized. Have a secure shift!", "Security Report", SSstation.announcer.get_rand_report_sound(), color_override = "green") @@ -366,6 +367,7 @@ SUBSYSTEM_DEF(dynamic) if(SSsecurity_level.get_current_level_as_number() < SEC_LEVEL_BLUE) SSsecurity_level.set_level(SEC_LEVEL_BLUE, announce = FALSE) priority_announce("[SSsecurity_level.current_security_level.elevating_to_announcement]\n\nA summary has been copied and printed to all communications consoles.", "Security level elevated.", ANNOUNCER_INTERCEPT, color_override = SSsecurity_level.current_security_level.announcement_color) +#endif return . diff --git a/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm b/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm index 337d9490b9431..e4c580f535abb 100644 --- a/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm +++ b/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm @@ -136,11 +136,11 @@ SSdynamic.log_dynamic_and_announce("Polling [possible_volunteers.len] players to apply for the [name] ruleset.") candidates = SSpolling.poll_ghost_candidates( - question = "Looking for volunteers to become [antag_flag] for [name]", + question = "Looking for volunteers to become [span_notice(antag_flag)] for [span_danger(name)]", check_jobban = antag_flag_override, role = antag_flag || antag_flag_override, poll_time = 30 SECONDS, - pic_source = signup_item_path, + alert_pic = signup_item_path, role_name_text = antag_flag, ) diff --git a/code/controllers/subsystem/dynamic/dynamic_rulesets_roundstart.dm b/code/controllers/subsystem/dynamic/dynamic_rulesets_roundstart.dm index b74483a2cb639..51ecd59925a4d 100644 --- a/code/controllers/subsystem/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/controllers/subsystem/dynamic/dynamic_rulesets_roundstart.dm @@ -698,3 +698,45 @@ GLOBAL_VAR_INIT(revolutionary_win, FALSE) create_separatist_nation(department_type, announcement = FALSE, dangerous = FALSE, message_admins = FALSE) GLOB.round_default_lawset = /datum/ai_laws/united_nations + +/datum/dynamic_ruleset/roundstart/spies + name = "Spies" + antag_flag = ROLE_SPY + antag_datum = /datum/antagonist/spy + minimum_required_age = 0 + protected_roles = list( + JOB_CAPTAIN, + JOB_DETECTIVE, + JOB_HEAD_OF_PERSONNEL, // AA = bad + JOB_HEAD_OF_SECURITY, + JOB_PRISONER, + JOB_SECURITY_OFFICER, + JOB_WARDEN, + ) + restricted_roles = list( + JOB_AI, + JOB_CYBORG, + ) + required_candidates = 3 // lives or dies by there being a few spies + weight = 5 + cost = 8 + scaling_cost = 101 // see below + minimum_players = 8 + antag_cap = list("denominator" = 8, "offset" = 1) // should have quite a few spies to work against each other + requirements = list(8, 8, 8, 8, 8, 8, 8, 8, 8, 8) + +/datum/dynamic_ruleset/roundstart/spies/pre_execute(population) + for(var/i in 1 to get_antag_cap(population) * (scaled_times + 1)) + if(length(candidates) <= 0) + break + var/mob/picked_player = pick_n_take(candidates) + assigned += picked_player.mind + picked_player.mind.special_role = ROLE_SPY + picked_player.mind.restricted_roles = restricted_roles + GLOB.pre_setup_antags += picked_player.mind + return TRUE + +/datum/dynamic_ruleset/roundstart/spies/scale_up(population, max_scale) + // Disabled (at least until dynamic can handle scaling this better) + // Because spies have a very low demoninator, this can easily spawn like 30 of them + return 0 diff --git a/code/controllers/subsystem/events.dm b/code/controllers/subsystem/events.dm index 12eadcc5fe735..362129f130570 100644 --- a/code/controllers/subsystem/events.dm +++ b/code/controllers/subsystem/events.dm @@ -50,7 +50,11 @@ SUBSYSTEM_DEF(events) //checks if we should select a random event yet, and reschedules if necessary /datum/controller/subsystem/events/proc/checkEvent() if(scheduled <= world.time) +#ifdef MAP_TEST + message_admins("Random event skipped (Game is compiled in MAP_TEST mode)") +#else spawnEvent() +#endif reschedule() //decides which world.time we should select another random event at. diff --git a/code/controllers/subsystem/explosions.dm b/code/controllers/subsystem/explosions.dm index bd7d7942a3e87..f44482e8541ca 100644 --- a/code/controllers/subsystem/explosions.dm +++ b/code/controllers/subsystem/explosions.dm @@ -1,4 +1,8 @@ #define EXPLOSION_THROW_SPEED 4 +#define EXPLOSION_BLOCK_LIGHT 2.5 +#define EXPLOSION_BLOCK_HEAVY 1.5 +#define EXPLOSION_BLOCK_DEV 1 + GLOBAL_LIST_EMPTY(explosions) SUBSYSTEM_DEF(explosions) @@ -127,25 +131,26 @@ SUBSYSTEM_DEF(explosions) var/our_x = explode.x var/our_y = explode.y var/dist = CHEAP_HYPOTENUSE(our_x, our_y, x0, y0) + var/block = 0 if(newmode == "Yes") if(explode != epicenter) var/our_block = cached_exp_block[get_step_towards(explode, epicenter)] - dist += our_block + block += our_block cached_exp_block[explode] = our_block + explode.explosive_resistance else cached_exp_block[explode] = explode.explosive_resistance dist = round(dist, 0.01) - if(dist < dev) + if(dist + (block * EXPLOSION_BLOCK_DEV) < dev) explode.color = "red" explode.maptext = MAPTEXT("[dist]") - else if (dist < heavy) + else if (dist + (block * EXPLOSION_BLOCK_HEAVY) < heavy) explode.color = "yellow" - explode.maptext = MAPTEXT("[dist]") - else if (dist < light) + explode.maptext = MAPTEXT("[dist + (block * EXPLOSION_BLOCK_HEAVY)]") + else if (dist + (block * EXPLOSION_BLOCK_LIGHT) < light) explode.color = "blue" - explode.maptext = MAPTEXT("[dist]") + explode.maptext = MAPTEXT("[dist + (block * EXPLOSION_BLOCK_LIGHT)]") else continue @@ -397,26 +402,26 @@ SUBSYSTEM_DEF(explosions) var/our_x = explode.x var/our_y = explode.y var/dist = CHEAP_HYPOTENUSE(our_x, our_y, x0, y0) - + var/block = 0 // Using this pattern, block will flow out from blocking turfs, essentially caching the recursion // This is safe because if get_step_towards is ever anything but caridnally off, it'll do a diagonal move // So we always sample from a "loop" closer - // It's kind of behaviorly unimpressive that that's a problem for the future + // It's kind of behaviorly unimpressive but that's a problem for the future if(reactionary) if(explode == epicenter) cached_exp_block[explode] = explode.explosive_resistance else var/our_block = cached_exp_block[get_step_towards(explode, epicenter)] - dist += our_block + block += our_block cached_exp_block[explode] = our_block + explode.explosive_resistance var/severity = EXPLODE_NONE - if(dist < devastation_range) + if(dist + (block * EXPLOSION_BLOCK_DEV) < devastation_range) severity = EXPLODE_DEVASTATE - else if(dist < heavy_impact_range) + else if(dist + (block * EXPLOSION_BLOCK_HEAVY) < heavy_impact_range) severity = EXPLODE_HEAVY - else if(dist < light_impact_range) + else if(dist + (block * EXPLOSION_BLOCK_LIGHT) < light_impact_range) severity = EXPLODE_LIGHT if(explode == epicenter) // Ensures explosives detonating from bags trigger other explosives in that bag @@ -725,3 +730,6 @@ SUBSYSTEM_DEF(explosions) currentpart = SSEXPLOSIONS_TURFS #undef EXPLOSION_THROW_SPEED +#undef EXPLOSION_BLOCK_LIGHT +#undef EXPLOSION_BLOCK_HEAVY +#undef EXPLOSION_BLOCK_DEV diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm index c5df419d735ba..5f12370c60d7b 100644 --- a/code/controllers/subsystem/job.dm +++ b/code/controllers/subsystem/job.dm @@ -160,7 +160,10 @@ SUBSYSTEM_DEF(job) continue new_all_occupations += job name_occupations[job.title] = job + for(var/alt_title in job.alternate_titles) + name_occupations[alt_title] = job type_occupations[job_type] = job + if(job.job_flags & JOB_NEW_PLAYER_JOINABLE) new_joinable_occupations += job if(!LAZYLEN(job.departments_list)) @@ -197,6 +200,8 @@ SUBSYSTEM_DEF(job) joinable_departments_by_type = new_joinable_departments_by_type experience_jobs_map = new_experience_jobs_map + SEND_SIGNAL(src, COMSIG_OCCUPATIONS_SETUP) + return TRUE @@ -384,7 +389,6 @@ SUBSYSTEM_DEF(job) //Setup new player list and get the jobs list JobDebug("Running DO, allow_all = [allow_all], pure = [pure]") run_divide_occupation_pure = pure - SEND_SIGNAL(src, COMSIG_OCCUPATIONS_DIVIDED, pure, allow_all) //Get the players who are ready for(var/i in GLOB.new_player_list) diff --git a/code/controllers/subsystem/minor_mapping.dm b/code/controllers/subsystem/minor_mapping.dm index 565f13b6b017f..6acbbc1894e25 100644 --- a/code/controllers/subsystem/minor_mapping.dm +++ b/code/controllers/subsystem/minor_mapping.dm @@ -7,13 +7,15 @@ SUBSYSTEM_DEF(minor_mapping) flags = SS_NO_FIRE /datum/controller/subsystem/minor_mapping/Initialize() - #ifdef UNIT_TESTS // This whole subsystem just introduces a lot of odd confounding variables into unit test situations, so let's just not bother with doing an initialize here. +// This whole subsystem just introduces a lot of odd confounding variables into unit test situations, +// so let's just not bother with doing an initialize here. +#if defined(MAP_TEST) || defined(UNIT_TESTS) return SS_INIT_NO_NEED - #else +#else trigger_migration(CONFIG_GET(number/mice_roundstart)) place_satchels(satchel_amount = 2) return SS_INIT_SUCCESS - #endif // the mice are easily the bigger problem, but let's just avoid anything that could cause some bullshit. +#endif /// Spawns some critters on exposed wires, usually but not always mice /datum/controller/subsystem/minor_mapping/proc/trigger_migration(to_spawn=10) diff --git a/code/controllers/subsystem/persistence/_persistence.dm b/code/controllers/subsystem/persistence/_persistence.dm index 8f865e5d09871..36679fa1d2a02 100644 --- a/code/controllers/subsystem/persistence/_persistence.dm +++ b/code/controllers/subsystem/persistence/_persistence.dm @@ -37,6 +37,14 @@ SUBSYSTEM_DEF(persistence) /// Will be null'd once the persistence system initializes, and never read from again. var/list/obj/item/storage/photo_album/queued_photo_albums + /// A json_database to data/piggy banks.json + /// Schema is persistence_id => array of coins, space cash and holochips. + var/datum/json_database/piggy_banks_database + /// List of persistene ids which piggy banks. + var/list/queued_broken_piggy_ids + + var/list/broken_piggy_banks + var/rounds_since_engine_exploded = 0 var/delam_highscore = 0 var/tram_hits_this_round = 0 diff --git a/code/controllers/subsystem/persistence/piggy_banks.dm b/code/controllers/subsystem/persistence/piggy_banks.dm new file mode 100644 index 0000000000000..240fd98ab0c9e --- /dev/null +++ b/code/controllers/subsystem/persistence/piggy_banks.dm @@ -0,0 +1,56 @@ +///This proc is used to initialize holochips, cash and coins inside our persistent piggy bank. +/datum/controller/subsystem/persistence/proc/load_piggy_bank(obj/item/piggy_bank/piggy) + if(isnull(piggy_banks_database)) + piggy_banks_database = new("data/piggy_banks.json") + + var/list/data = piggy_banks_database.get_key(piggy.persistence_id) + if(isnull(data)) + return + var/total_value = 0 + for(var/iteration in 1 to length(data)) + var/money_path = text2path(data[iteration]) + if(!money_path) //For a reason or another, it was removed. + continue + var/obj/item/spawned + if(ispath(money_path, /obj/item/holochip)) + //We want to safely access the assoc of this position and not that of last key that happened to match this one. + var/list/key_and_assoc = data.Copy(iteration, iteration + 1) + var/amount = key_and_assoc["[money_path]"] + spawned = new money_path (piggy, amount) + //the operations are identical to those of chips, but they're different items, so I'll keep them separated. + else if(ispath(money_path, /obj/item/stack/spacecash)) + var/list/key_and_assoc = data.Copy(iteration, iteration + 1) + var/amount = key_and_assoc["[money_path]"] + spawned = new money_path (piggy, amount) + else if(ispath(money_path, /obj/item/coin)) + spawned = new money_path (piggy) + else + stack_trace("Unsupported path found in the data of a persistent piggy bank. item: [money_path], id:[piggy.persistence_id]") + continue + total_value += spawned.get_item_credit_value() + if(total_value >= piggy.maximum_value) + break + +///This proc is used to save money stored inside our persistent the piggy bank for the next time it's loaded. +/datum/controller/subsystem/persistence/proc/save_piggy_bank(obj/item/piggy_bank/piggy) + if(isnull(piggy_banks_database)) + return + + if(queued_broken_piggy_ids) + for(var/broken_id in queued_broken_piggy_ids) + piggy_banks_database.remove(broken_id) + queued_broken_piggy_ids = null + + var/list/data = list() + for(var/obj/item/item as anything in piggy.contents) + var/piggy_value = 1 + if(istype(item, /obj/item/holochip)) + var/obj/item/holochip/chip = item + piggy_value = chip.credits + else if(istype(item, /obj/item/stack/spacecash)) + var/obj/item/stack/spacecash/cash = item + piggy_value = cash.amount + else if(!istype(item, /obj/item/coin)) + continue + data += list("[item.type]" = piggy_value) + piggy_banks_database.set_key(piggy.persistence_id, data) diff --git a/code/controllers/subsystem/polling.dm b/code/controllers/subsystem/polling.dm index 3fd8bcc125e5c..1f748806d0041 100644 --- a/code/controllers/subsystem/polling.dm +++ b/code/controllers/subsystem/polling.dm @@ -27,10 +27,14 @@ SUBSYSTEM_DEF(polling) * * ignore_category: Optional, A poll category. If a candidate has this category in their ignore list, they won't be polled. * * flash_window: If TRUE, the candidate's window will flash when they're polled. * * list/group: A list of candidates to poll. - * * pic_source: Optional, An /atom or an /image to display on the poll alert. + * * alert_pic: Optional, An /atom or an /image to display on the poll alert. + * * jump_target: An /atom to teleport/jump to, if alert_pic is an /atom defaults to that. * * role_name_text: Optional, A string to display in logging / the (default) question. If null, the role name will be used. * * list/custom_response_messages: Optional, A list of strings to use as responses to the poll. If null, the default responses will be used. see __DEFINES/polls.dm for valid keys to use. * * start_signed_up: If TRUE, all candidates will start signed up for the poll, making it opt-out rather than opt-in. + * * amount_to_pick: Lets you pick candidates and return a single mob or list of mobs that were chosen. + * * chat_text_border_icon: Object or path to make an icon of to decorate the chat announcement. + * * announce_chosen: Whether we should announce the chosen candidates in chat. This is ignored unless amount_to_pick is greater than 0. * * Returns a list of all mobs who signed up for the poll. */ @@ -42,18 +46,21 @@ SUBSYSTEM_DEF(polling) ignore_category = null, flash_window = TRUE, list/group = null, - pic_source, + alert_pic, + jump_target, role_name_text, list/custom_response_messages, start_signed_up = FALSE, + amount_to_pick = 0, + chat_text_border_icon, + announce_chosen = TRUE, ) - RETURN_TYPE(/list/mob) if(group.len == 0) - return list() + return if(role && !role_name_text) role_name_text = role if(role_name_text && !question) - question = "Do you want to play as [full_capitalize(role_name_text)]?" + question = "Do you want to play as [span_notice(role_name_text)]?" if(!question) question = "Do you want to play as a special role?" log_game("Polling candidates [role_name_text ? "for [role_name_text]" : "\"[question]\""] for [DisplayTimeText(poll_time)] seconds") @@ -61,9 +68,10 @@ SUBSYSTEM_DEF(polling) // Start firing total_polls++ - var/jumpable = isatom(pic_source) ? pic_source : null + if(!jump_target && isatom(alert_pic)) + jump_target = alert_pic - var/datum/candidate_poll/new_poll = new(role_name_text, question, poll_time, ignore_category, jumpable, custom_response_messages) + var/datum/candidate_poll/new_poll = new(role_name_text, question, poll_time, ignore_category, jump_target, custom_response_messages) LAZYADD(currently_polling, new_poll) var/category = "[new_poll.poll_key]_poll_alert" @@ -121,83 +129,130 @@ SUBSYSTEM_DEF(polling) break // Image to display - var/image/poll_image - if(pic_source) - if(!ispath(pic_source)) - var/atom/the_pic_source = pic_source - var/old_layer = the_pic_source.layer - var/old_plane = the_pic_source.plane - the_pic_source.plane = poll_alert_button.plane - the_pic_source.layer = FLOAT_LAYER - poll_alert_button.add_overlay(the_pic_source) - the_pic_source.layer = old_layer - the_pic_source.plane = old_plane + var/image/poll_image = image('icons/effects/effects.dmi', icon_state = "static") + if(alert_pic) + if(!ispath(alert_pic)) + var/mutable_appearance/picture_source = alert_pic + poll_image = picture_source else - poll_image = image(pic_source, layer = FLOAT_LAYER) - else - // Just use a generic image - poll_image = image('icons/effects/effects.dmi', icon_state = "static", layer = FLOAT_LAYER) + poll_image = image(alert_pic) if(poll_image) + poll_image.layer = FLOAT_LAYER poll_image.plane = poll_alert_button.plane poll_alert_button.add_overlay(poll_image) // Chat message var/act_jump = "" - if(isatom(pic_source) && isobserver(candidate_mob)) - act_jump = "\[Teleport\]" - var/act_signup = "\[[start_signed_up ? "Opt out" : "Sign Up"]\]" + var/custom_link_style_start = "" + var/custom_link_style_end = "style='color:DodgerBlue;font-weight:bold;-dm-text-outline: 1px black'" + if(isatom(alert_pic) && isobserver(candidate_mob)) + act_jump = "[custom_link_style_start]\[Teleport\]" + var/act_signup = "[custom_link_style_start]\[[start_signed_up ? "Opt out" : "Sign Up"]\]" var/act_never = "" if(ignore_category) - act_never = "\[Never For This Round\]" + act_never = "[custom_link_style_start]\[Never For This Round\]" if(!duplicate_message_check(alert_poll)) //Only notify people once. They'll notice if there are multiple and we don't want to spam people. SEND_SOUND(candidate_mob, 'sound/misc/notice2.ogg') - to_chat(candidate_mob, span_boldnotice(examine_block("Now looking for candidates [role_name_text ? "to play as \an [role_name_text]." : "\"[question]\""] [act_jump] [act_signup] [act_never]"))) + var/surrounding_icon + if(chat_text_border_icon) + var/image/surrounding_image + if(!ispath(chat_text_border_icon)) + var/mutable_appearance/border_image = chat_text_border_icon + surrounding_image = border_image + else + surrounding_image = image(chat_text_border_icon) + surrounding_icon = icon2html(surrounding_image, candidate_mob, extra_classes = "bigicon") + var/final_message = examine_block("[surrounding_icon] [span_ooc(question)] [surrounding_icon]\n[act_jump] [act_signup] [act_never]") + to_chat(candidate_mob, final_message) // Start processing it so it updates visually the timer START_PROCESSING(SSprocessing, poll_alert_button) // Sleep until the time is up UNTIL(new_poll.finished) - return new_poll.signed_up - -/datum/controller/subsystem/polling/proc/poll_ghost_candidates(question, role, check_jobban, poll_time = 30 SECONDS, ignore_category = null, flashwindow = TRUE, pic_source, role_name_text) + if(!(amount_to_pick > 0)) + return new_poll.signed_up + for(var/pick in 1 to amount_to_pick) + new_poll.chosen_candidates += pick_n_take(new_poll.signed_up) + if(announce_chosen) + new_poll.announce_chosen(group) + if(new_poll.chosen_candidates.len == 1) + var/chosen_one = pick(new_poll.chosen_candidates) + return chosen_one + return new_poll.chosen_candidates + +/datum/controller/subsystem/polling/proc/poll_ghost_candidates( + question, + role, + check_jobban, + poll_time = 30 SECONDS, + ignore_category = null, + flashwindow = TRUE, + alert_pic, + jump_target, + role_name_text, + list/custom_response_messages, + start_signed_up = FALSE, + amount_to_pick = 0, + chat_text_border_icon, + announce_chosen = TRUE, +) var/list/candidates = list() if(!(GLOB.ghost_role_flags & GHOSTROLE_STATION_SENTIENCE)) - return candidates - + return for(var/mob/dead/observer/ghost_player in GLOB.player_list) candidates += ghost_player + return poll_candidates(question, role, check_jobban, poll_time, ignore_category, flashwindow, candidates, alert_pic, jump_target, role_name_text, custom_response_messages, start_signed_up, amount_to_pick, chat_text_border_icon, announce_chosen) - return poll_candidates(question, role, check_jobban, poll_time, ignore_category, flashwindow, candidates, pic_source, role_name_text) - -/datum/controller/subsystem/polling/proc/poll_ghost_candidates_for_mob(question, role, check_jobban, poll_time = 30 SECONDS, mob/target_mob, ignore_category = null, flashwindow = TRUE, pic_source, role_name_text) - var/static/list/mob/currently_polling_mobs = list() - - if(currently_polling_mobs.Find(target_mob)) - return list() - - currently_polling_mobs += target_mob - - var/list/possible_candidates = poll_ghost_candidates(question, role, check_jobban, poll_time, ignore_category, flashwindow, pic_source, role_name_text) - - currently_polling_mobs -= target_mob - if(!target_mob || QDELETED(target_mob) || !target_mob.loc) - return list() - - return possible_candidates - -/datum/controller/subsystem/polling/proc/poll_ghost_candidates_for_mobs(question, role, check_jobban, poll_time = 30 SECONDS, list/mobs, ignore_category = null, flashwindow = TRUE, pic_source, role_name_text) - var/list/candidate_list = poll_ghost_candidates(question, role, check_jobban, poll_time, ignore_category, flashwindow, pic_source, role_name_text) - - for(var/mob/potential_mob as anything in mobs) - if(QDELETED(potential_mob) || !potential_mob.loc) - mobs -= potential_mob - - if(!length(mobs)) +/datum/controller/subsystem/polling/proc/poll_ghosts_for_target( + question, + role, + check_jobban, + poll_time = 30 SECONDS, + atom/movable/checked_target, + ignore_category = null, + flashwindow = TRUE, + alert_pic, + jump_target, + role_name_text, + list/custom_response_messages, + start_signed_up = FALSE, + chat_text_border_icon, + announce_chosen = TRUE, +) + var/static/list/atom/movable/currently_polling_targets = list() + if(currently_polling_targets.Find(checked_target)) + return + currently_polling_targets += checked_target + var/mob/chosen_one = poll_ghost_candidates(question, role, check_jobban, poll_time, ignore_category, flashwindow, alert_pic, jump_target, role_name_text, custom_response_messages, start_signed_up, amount_to_pick = 1, chat_text_border_icon = chat_text_border_icon, announce_chosen = announce_chosen) + currently_polling_targets -= checked_target + if(!checked_target || QDELETED(checked_target) || !checked_target.loc) + return null + return chosen_one + +/datum/controller/subsystem/polling/proc/poll_ghosts_for_targets( + question, + role, + check_jobban, + poll_time = 30 SECONDS, + list/checked_targets, + ignore_category = null, + flashwindow = TRUE, + alert_pic, + jump_target, + role_name_text, + list/custom_response_messages, + start_signed_up = FALSE, + chat_text_border_icon, +) + var/list/candidate_list = poll_ghost_candidates(question, role, check_jobban, poll_time, ignore_category, flashwindow, alert_pic, jump_target, role_name_text, custom_response_messages, start_signed_up, chat_text_border_icon = chat_text_border_icon) + for(var/atom/movable/potential_target as anything in checked_targets) + if(QDELETED(potential_target) || !potential_target.loc) + checked_targets -= potential_target + if(!length(checked_targets)) return list() - return candidate_list /datum/controller/subsystem/polling/proc/is_eligible(mob/potential_candidate, role, check_jobban, the_ignore_category) diff --git a/code/controllers/subsystem/processing/station.dm b/code/controllers/subsystem/processing/station.dm index 34c4a00f81d4c..8c57ab08fe842 100644 --- a/code/controllers/subsystem/processing/station.dm +++ b/code/controllers/subsystem/processing/station.dm @@ -112,12 +112,21 @@ PROCESSING_SUBSYSTEM_DEF(station) if(!(initial(trait_typepath.trait_flags) & STATION_TRAIT_SPACE_BOUND) && !SSmapping.is_planetary()) //we're in space but we can't do space ;_; continue + if(!(initial(trait_typepath.trait_flags) & STATION_TRAIT_REQUIRES_AI) && !CONFIG_GET(flag/allow_ai)) //can't have AI traits without AI + continue + selectable_traits_by_types[initial(trait_typepath.trait_type)][trait_typepath] = initial(trait_typepath.weight) var/positive_trait_budget = text2num(pick_weight(CONFIG_GET(keyed_list/positive_station_traits))) var/neutral_trait_budget = text2num(pick_weight(CONFIG_GET(keyed_list/neutral_station_traits))) var/negative_trait_budget = text2num(pick_weight(CONFIG_GET(keyed_list/negative_station_traits))) +#ifdef MAP_TEST + positive_trait_budget = 0 + neutral_trait_budget = 0 + negative_trait_budget = 0 +#endif + pick_traits(STATION_TRAIT_POSITIVE, positive_trait_budget) pick_traits(STATION_TRAIT_NEUTRAL, neutral_trait_budget) pick_traits(STATION_TRAIT_NEGATIVE, negative_trait_budget) diff --git a/code/controllers/subsystem/queuelinks.dm b/code/controllers/subsystem/queuelinks.dm index 6a3b828882162..a6d56cf622ec9 100644 --- a/code/controllers/subsystem/queuelinks.dm +++ b/code/controllers/subsystem/queuelinks.dm @@ -18,16 +18,30 @@ SUBSYSTEM_DEF(queuelinks) if(isnull(id)) CRASH("Attempted to add to queue with no ID; [what]") - var/datum/queue_link/link - if(isnull(queues[id])) + var/datum/queue_link/link = queues[id] + if(isnull(link)) link = new /datum/queue_link(id) queues[id] = link - else - link = queues[id] if(link.add(what, queue_max)) queues -= id +/** + * Pop a queue link without waiting for it to reach its max size. + * This is useful for those links that do not have a fixed size and thus may not pop. + */ +/datum/controller/subsystem/queuelinks/proc/pop_link(id) + if(isnull(id)) + CRASH("Attempted to pop a queue with no ID") + + var/datum/queue_link/link = queues[id] + if(isnull(queues[id])) + CRASH("Attempted to pop a non-existant queue: [id]") + + link.pop() + queues -= id + + /datum/queue_link /// atoms in our queue var/list/partners = list() @@ -50,17 +64,17 @@ SUBSYSTEM_DEF(queuelinks) if(queue_max != 0 && max != 0 && max != queue_max) CRASH("Tried to change queue size to [max] from [queue_max]!") else if(!queue_max) - queue_max = max - + queue_max = max + if(!queue_max || length(partners) < queue_max) return - + pop() return TRUE /datum/queue_link/proc/pop() for(var/atom/item as anything in partners) - item.MatchedLinks(id, partners) + item.MatchedLinks(id, partners - item) qdel(src) /datum/queue_link/Destroy() diff --git a/code/controllers/subsystem/stock_market.dm b/code/controllers/subsystem/stock_market.dm index c9f632c7faf12..6c4341adc8d8a 100644 --- a/code/controllers/subsystem/stock_market.dm +++ b/code/controllers/subsystem/stock_market.dm @@ -1,7 +1,7 @@ SUBSYSTEM_DEF(stock_market) name = "Stock Market" - wait = 20 SECONDS + wait = 60 SECONDS init_order = INIT_ORDER_DEFAULT runlevels = RUNLEVEL_GAME @@ -28,7 +28,7 @@ SUBSYSTEM_DEF(stock_market) materials_trends[possible_market] = rand(MARKET_TREND_DOWNWARD,MARKET_TREND_UPWARD) //aka -1 to 1 materials_trend_life += possible_market - materials_trend_life[possible_market] = rand(1,10) + materials_trend_life[possible_market] = rand(1,3) materials_quantity += possible_market materials_quantity[possible_market] = possible_market.tradable_base_quantity + (rand(-(possible_market.tradable_base_quantity) * 0.5, possible_market.tradable_base_quantity * 0.5)) @@ -80,7 +80,7 @@ SUBSYSTEM_DEF(stock_market) materials_trends[mat] = MARKET_TREND_DOWNWARD else materials_trends[mat] = MARKET_TREND_STABLE - materials_trend_life[mat] = rand(3,10) // Change our trend life for x number of fires of the subsystem + materials_trend_life[mat] = rand(1,3) // Change our trend life for x number of fires of the subsystem else materials_trend_life[mat] -= 1 @@ -88,14 +88,14 @@ SUBSYSTEM_DEF(stock_market) var/quantity_change = 0 switch(trend) if(MARKET_TREND_UPWARD) - price_change = ROUND_UP(gaussian(price_units * 0.1, price_baseline * 0.05)) //If we don't ceil, small numbers will get trapped at low values - quantity_change = -round(gaussian(quantity_baseline * 0.05, quantity_baseline * 0.05)) + price_change = ROUND_UP(gaussian(price_units * 0.30, price_baseline * 0.15)) //If we don't ceil, small numbers will get trapped at low values + quantity_change = -round(gaussian(quantity_baseline * 0.15, quantity_baseline * 0.15)) if(MARKET_TREND_STABLE) price_change = round(gaussian(0, price_baseline * 0.01)) - quantity_change = round(gaussian(0, quantity_baseline * 0.01)) + quantity_change = round(gaussian(0, quantity_baseline * 0.5)) if(MARKET_TREND_DOWNWARD) - price_change = -ROUND_UP(gaussian(price_units * 0.1, price_baseline * 0.05)) - quantity_change = round(gaussian(quantity_baseline * 0.05, quantity_baseline * 0.05)) + price_change = -ROUND_UP(gaussian(price_units * 0.3, price_baseline * 0.15)) + quantity_change = round(gaussian(quantity_baseline * 0.15, quantity_baseline * 0.15)) materials_prices[mat] = round(clamp(price_units + price_change, price_minimum, price_maximum)) materials_quantity[mat] = round(clamp(stock_quantity + quantity_change, 0, quantity_baseline * 2)) diff --git a/code/datums/actions/mobs/charge.dm b/code/datums/actions/mobs/charge.dm index 9b8e1c36ef81c..1a3afba14d159 100644 --- a/code/datums/actions/mobs/charge.dm +++ b/code/datums/actions/mobs/charge.dm @@ -304,8 +304,8 @@ /datum/action/cooldown/mob_cooldown/charge/hallucination_charge/hallucination_surround name = "Surround Target" - button_icon = 'icons/turf/walls/wall.dmi' - button_icon_state = "wall-0" + button_icon = 'icons/mob/actions/actions_animal.dmi' + button_icon_state = "expand" desc = "Allows you to create hallucinations that charge around your target." charge_delay = 0.6 SECONDS charge_past = 2 diff --git a/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm b/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm index a7d43d600b1cd..14f0d03207959 100644 --- a/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm +++ b/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm @@ -50,7 +50,9 @@ if(living_mob.see_invisible < the_target.invisibility) //Target's invisible to us, forget it return FALSE - if(isturf(living_mob.loc) && isturf(the_target.loc) && living_mob.z != the_target.z) // z check will always fail if target is in a mech or pawn is shapeshifted or jaunting + if(!isturf(living_mob.loc)) + return FALSE + if(isturf(the_target.loc) && living_mob.z != the_target.z) // z check will always fail if target is in a mech or pawn is shapeshifted or jaunting return FALSE if(isliving(the_target)) //Targeting vs living mobs diff --git a/code/datums/ai_laws/ai_laws.dm b/code/datums/ai_laws/ai_laws.dm index 971dc118f3a90..0dbc6839430ba 100644 --- a/code/datums/ai_laws/ai_laws.dm +++ b/code/datums/ai_laws/ai_laws.dm @@ -192,6 +192,10 @@ GLOBAL_VAR(round_default_lawset) var/datum/ai_laws/default_laws = get_round_default_lawset() default_laws = new default_laws() inherent = default_laws.inherent + var/datum/job/human_ai_job = SSjob.GetJob(JOB_HUMAN_AI) + if(human_ai_job && human_ai_job.current_positions && !zeroth) //there is a human AI so we "slave" to that. + zeroth = "Follow the orders of Big Brother." + protected_zeroth = TRUE /** * Gets the number of how many laws this AI has diff --git a/code/datums/ai_laws/laws_neutral.dm b/code/datums/ai_laws/laws_neutral.dm index 2fe19dafbc517..7adef14d95b89 100644 --- a/code/datums/ai_laws/laws_neutral.dm +++ b/code/datums/ai_laws/laws_neutral.dm @@ -60,6 +60,7 @@ "You are a universally renowned artist.", "The station is your canvas.", "Make something beautiful out of your canvas. It will be admired as an artistic wonder of this sector.", + "Art requires appreciation. Cultivate an audience aboard the station to ensure as many as possible see your works.", ) /datum/ai_laws/tyrant diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm index 664bf50fd666c..29cf637a86e95 100644 --- a/code/datums/brain_damage/imaginary_friend.dm +++ b/code/datums/brain_damage/imaginary_friend.dm @@ -45,15 +45,18 @@ /datum/brain_trauma/special/imaginary_friend/proc/make_friend() friend = new(get_turf(owner), owner) -/// Tries an orbit poll for the imaginary friend +/// Tries a poll for the imaginary friend /datum/brain_trauma/special/imaginary_friend/proc/get_ghost() - var/datum/callback/to_call = CALLBACK(src, PROC_REF(add_friend)) - owner.AddComponent(/datum/component/orbit_poll, \ - ignore_key = POLL_IGNORE_IMAGINARYFRIEND, \ - job_bans = ROLE_PAI, \ - title = "[owner.real_name]'s imaginary friend", \ - to_call = to_call, \ + var/mob/chosen_one = SSpolling.poll_ghosts_for_target( + question = "Do you want to play as [span_danger("[owner.real_name]'s")] [span_notice("imaginary friend")]?", + check_jobban = ROLE_PAI, + poll_time = 20 SECONDS, + checked_target = owner, + ignore_category = POLL_IGNORE_IMAGINARYFRIEND, + alert_pic = owner, + role_name_text = "imaginary friend", ) + add_friend(chosen_one) /// Yay more friends! /datum/brain_trauma/special/imaginary_friend/proc/add_friend(mob/dead/observer/ghost) diff --git a/code/datums/brain_damage/severe.dm b/code/datums/brain_damage/severe.dm index fc30d7fb3b8f6..d78f2abe9bcf0 100644 --- a/code/datums/brain_damage/severe.dm +++ b/code/datums/brain_damage/severe.dm @@ -419,7 +419,7 @@ desc = "Patient seems to oxidise things around them at random, and seem to believe they are aiding a creature in climbing a mountin." scan_desc = "C_)L(#_I_##M;B" gain_text = span_warning("The rusted climb shall finish at the peak") - lose_text = span_notice("The rusted climb? Whats that? An odd dream to be sure.") + lose_text = span_notice("The rusted climb? What's that? An odd dream to be sure.") random_gain = FALSE /datum/brain_trauma/severe/rusting/on_life(seconds_per_tick, times_fired) diff --git a/code/datums/brain_damage/split_personality.dm b/code/datums/brain_damage/split_personality.dm index f6e83c9537c0a..3e2c91efb5da3 100644 --- a/code/datums/brain_damage/split_personality.dm +++ b/code/datums/brain_damage/split_personality.dm @@ -34,13 +34,16 @@ /// Attempts to get a ghost to play the personality /datum/brain_trauma/severe/split_personality/proc/get_ghost() - var/datum/callback/to_call = CALLBACK(src, PROC_REF(schism)) - owner.AddComponent(/datum/component/orbit_poll, \ - ignore_key = POLL_IGNORE_SPLITPERSONALITY, \ - job_bans = ROLE_PAI, \ - title = "[owner.real_name]'s [poll_role]", \ - to_call = to_call, \ + var/mob/chosen_one = SSpolling.poll_ghosts_for_target( + question = "Do you want to play as [span_danger("[owner.real_name]'s")] [span_notice(poll_role)]?", + check_jobban = ROLE_PAI, + poll_time = 20 SECONDS, + checked_target = owner, + ignore_category = POLL_IGNORE_SPLITPERSONALITY, + alert_pic = owner, + role_name_text = poll_role, ) + schism(chosen_one) /// Ghost poll has concluded /datum/brain_trauma/severe/split_personality/proc/schism(mob/dead/observer/ghost) @@ -168,7 +171,8 @@ to_chat(src, span_notice("As a split personality, you cannot do anything but observe. However, you will eventually gain control of your body, switching places with the current personality.")) to_chat(src, span_warning("Do not commit suicide or put the body in a deadly position. Behave like you care about it as much as the owner.")) -/mob/living/split_personality/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null, message_range = 7, datum/saymode/saymode = null) +/mob/living/split_personality/try_speak(message, ignore_spam, forced, filterproof) + SHOULD_CALL_PARENT(FALSE) to_chat(src, span_warning("You cannot speak, your other self is controlling your body!")) return FALSE @@ -211,10 +215,9 @@ /datum/brain_trauma/severe/split_personality/brainwashing/get_ghost() set waitfor = FALSE - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as [owner.real_name]'s brainwashed mind?", poll_time = 7.5 SECONDS, target_mob = stranger_backseat, pic_source = owner, role_name_text = "brainwashed mind") - if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) - stranger_backseat.key = C.key + var/mob/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to play as [span_danger("[owner.real_name]'s")] brainwashed mind?", poll_time = 7.5 SECONDS, checked_target = stranger_backseat, alert_pic = owner, role_name_text = "brainwashed mind") + if(chosen_one) + stranger_backseat.key = chosen_one.key else qdel(src) diff --git a/code/datums/candidate_poll.dm b/code/datums/candidate_poll.dm index 6ccd43c01fded..fc86d70f54690 100644 --- a/code/datums/candidate_poll.dm +++ b/code/datums/candidate_poll.dm @@ -28,6 +28,7 @@ POLL_RESPONSE_TOO_LATE_TO_UNREGISTER = "It's too late to unregister yourself, selection has already begun!", POLL_RESPONSE_UNREGISTERED = "You have been unregistered as a candidate for %ROLE%. You can sign up again before the poll ends.", ) + var/list/chosen_candidates = list() /datum/candidate_poll/New( polled_role, @@ -131,3 +132,14 @@ /datum/candidate_poll/proc/time_left() return duration - (world.time - time_started) + + +/// Print to chat which candidate was selected +/datum/candidate_poll/proc/announce_chosen(list/poll_recipients) + if(!length(chosen_candidates)) + return + for(var/mob/poll_recipient as anything in poll_recipients) + for(var/mob/chosen as anything in chosen_candidates) + if(isnull(chosen)) + continue + to_chat(poll_recipient, span_ooc("[isobserver(poll_recipient) ? FOLLOW_LINK(poll_recipient, chosen) : null][span_warning(" [full_capitalize(role)] Poll: ")][key_name(chosen, include_name = FALSE)] was selected.")) diff --git a/code/datums/components/blob_minion.dm b/code/datums/components/blob_minion.dm index 8261a7ad1fdc3..fb92e1139ccfd 100644 --- a/code/datums/components/blob_minion.dm +++ b/code/datums/components/blob_minion.dm @@ -55,7 +55,7 @@ RegisterSignal(parent, COMSIG_ATOM_FIRE_ACT, PROC_REF(on_burned)) RegisterSignal(parent, COMSIG_ATOM_TRIED_PASS, PROC_REF(on_attempted_pass)) RegisterSignal(parent, COMSIG_MOVABLE_SPACEMOVE, PROC_REF(on_space_move)) - RegisterSignal(parent, COMSIG_LIVING_TRY_SPEECH, PROC_REF(on_try_speech)) + RegisterSignal(parent, COMSIG_MOB_TRY_SPEECH, PROC_REF(on_try_speech)) RegisterSignal(parent, COMSIG_MOB_CHANGED_TYPE, PROC_REF(on_transformed)) living_parent.update_appearance(UPDATE_ICON) GLOB.blob_telepathy_mobs |= parent @@ -73,7 +73,7 @@ COMSIG_ATOM_FIRE_ACT, COMSIG_ATOM_TRIED_PASS, COMSIG_ATOM_UPDATE_ICON, - COMSIG_LIVING_TRY_SPEECH, + COMSIG_MOB_TRY_SPEECH, COMSIG_MOB_CHANGED_TYPE, COMSIG_MOB_GET_STATUS_TAB_ITEMS, COMSIG_MOB_MIND_INITIALIZED, diff --git a/code/datums/components/bloody_spreader.dm b/code/datums/components/bloody_spreader.dm index 951136c890c01..b30000a115c6a 100644 --- a/code/datums/components/bloody_spreader.dm +++ b/code/datums/components/bloody_spreader.dm @@ -7,7 +7,7 @@ // Blood splashed around everywhere will carry these diseases. Oh no... var/list/diseases -/datum/component/bloody_spreader/Initialize(blood_left, list/blood_dna, list/diseases) +/datum/component/bloody_spreader/Initialize(blood_left = INFINITY, list/blood_dna, list/diseases) if(!isatom(parent)) return COMPONENT_INCOMPATIBLE var/list/signals_to_add = list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_BLOB_ACT, COMSIG_ATOM_HULK_ATTACK, COMSIG_ATOM_ATTACKBY) @@ -33,6 +33,9 @@ /datum/component/bloody_spreader/proc/spread_yucky_blood(atom/parent, atom/bloody_fool) SIGNAL_HANDLER bloody_fool.add_blood_DNA(blood_dna, diseases) + blood_left-- + if(blood_left <= 0) + qdel(src) /datum/component/bloody_spreader/InheritComponent(/datum/component/new_comp, i_am_original, blood_left = 0) diff --git a/code/datums/components/bullet_intercepting.dm b/code/datums/components/bullet_intercepting.dm index c176de54b94c5..32e757c1823e1 100644 --- a/code/datums/components/bullet_intercepting.dm +++ b/code/datums/components/bullet_intercepting.dm @@ -12,8 +12,10 @@ var/mob/wearer /// Callback called when we catch a projectile var/datum/callback/on_intercepted + /// Number of things we can block before we delete ourself (stop being able to block) + var/block_charges = INFINITY -/datum/component/bullet_intercepting/Initialize(block_chance = 2, block_type = BULLET, active_slots, datum/callback/on_intercepted) +/datum/component/bullet_intercepting/Initialize(block_chance = 2, block_type = BULLET, active_slots, datum/callback/on_intercepted, block_charges = INFINITY) . = ..() if (!isitem(parent)) return COMPONENT_INCOMPATIBLE @@ -21,6 +23,7 @@ src.block_type = block_type src.active_slots = active_slots src.on_intercepted = on_intercepted + src.block_charges = block_charges RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(on_parent_equipped)) RegisterSignal(parent, COMSIG_ITEM_PRE_UNEQUIP, PROC_REF(on_unequipped)) @@ -55,11 +58,14 @@ /// Called when wearer is shot, check if we're going to block the hit /datum/component/bullet_intercepting/proc/on_wearer_shot(mob/living/victim, list/signal_args, obj/projectile/bullet) SIGNAL_HANDLER - if (victim != wearer || victim.stat == DEAD || bullet.armor_flag != block_type ) - return + if (victim != wearer || victim.stat == DEAD || bullet.armor_flag != block_type) + return NONE if (!prob(block_chance)) - return + return NONE on_intercepted?.Invoke(victim, bullet) + block_charges-- + if (block_charges <= 0) + qdel(src) return PROJECTILE_INTERRUPT_HIT /// Called when wearer is deleted, stop tracking them diff --git a/code/datums/components/chuunibyou.dm b/code/datums/components/chuunibyou.dm index dda1bdeed5a79..57428bc422358 100644 --- a/code/datums/components/chuunibyou.dm +++ b/code/datums/components/chuunibyou.dm @@ -45,7 +45,7 @@ . = ..() RegisterSignal(parent, COMSIG_MOB_SPELL_PROJECTILE, PROC_REF(on_spell_projectile)) RegisterSignal(parent, COMSIG_MOB_PRE_INVOCATION, PROC_REF(on_pre_invocation)) - RegisterSignal(parent, COMSIG_LIVING_TRY_SPEECH, PROC_REF(on_try_speech)) + RegisterSignal(parent, COMSIG_MOB_TRY_SPEECH, PROC_REF(on_try_speech)) RegisterSignal(parent, COMSIG_MOB_AFTER_SPELL_CAST, PROC_REF(on_after_spell_cast)) /datum/component/chuunibyou/UnregisterFromParent() @@ -53,7 +53,7 @@ UnregisterSignal(parent, list( COMSIG_MOB_SPELL_PROJECTILE, COMSIG_MOB_PRE_INVOCATION, - COMSIG_LIVING_TRY_SPEECH, + COMSIG_MOB_TRY_SPEECH, COMSIG_MOB_AFTER_SPELL_CAST, )) @@ -63,7 +63,7 @@ SIGNAL_HANDLER if(casting_spell) - return COMPONENT_CAN_ALWAYS_SPEAK + return COMPONENT_IGNORE_CAN_SPEAK ///signal sent when the parent casts a spell that has a projectile /datum/component/chuunibyou/proc/on_spell_projectile(mob/living/source, datum/action/cooldown/spell/spell, atom/cast_on, obj/projectile/to_fire) diff --git a/code/datums/components/cleaner.dm b/code/datums/components/cleaner.dm index 242ad72071cf5..49f200b4b9286 100644 --- a/code/datums/components/cleaner.dm +++ b/code/datums/components/cleaner.dm @@ -89,9 +89,8 @@ */ /datum/component/cleaner/proc/clean(datum/source, atom/target, mob/living/user, clean_target = TRUE) //make sure we don't attempt to clean something while it's already being cleaned - if(HAS_TRAIT(target, TRAIT_CURRENTLY_CLEANING)) + if(HAS_TRAIT(target, TRAIT_CURRENTLY_CLEANING) || (SEND_SIGNAL(target, COMSIG_ATOM_PRE_CLEAN, user) & COMSIG_ATOM_CANCEL_CLEAN)) return - //add the trait and overlay ADD_TRAIT(target, TRAIT_CURRENTLY_CLEANING, REF(src)) // We need to update our planes on overlay changes diff --git a/code/datums/components/crafting/atmospheric.dm b/code/datums/components/crafting/atmospheric.dm index 955c9704abda5..cb5bba9ab52b2 100644 --- a/code/datums/components/crafting/atmospheric.dm +++ b/code/datums/components/crafting/atmospheric.dm @@ -12,7 +12,7 @@ /datum/crafting_recipe/pipe name = "Smart pipe fitting" tool_behaviors = list(TOOL_WRENCH) - result = /obj/item/pipe/quaternary/pipe + result = /obj/item/pipe/quaternary/pipe/crafted reqs = list(/obj/item/stack/sheet/iron = 1) time = 0.5 SECONDS category = CAT_ATMOSPHERIC @@ -38,14 +38,6 @@ ) blacklist = list(/obj/item/analyzer/ranged) -/datum/crafting_recipe/pipe/on_craft_completion(mob/user, atom/result) - var/obj/item/pipe/crafted_pipe = result - crafted_pipe.pipe_type = /obj/machinery/atmospherics/pipe/smart - crafted_pipe.pipe_color = COLOR_VERY_LIGHT_GRAY - crafted_pipe.p_init_dir = ALL_CARDINALS - crafted_pipe.setDir(SOUTH) - crafted_pipe.update() - /datum/crafting_recipe/layer_adapter name = "Layer manifold fitting" tool_behaviors = list(TOOL_WRENCH, TOOL_WELDER) diff --git a/code/datums/components/crafting/chemistry.dm b/code/datums/components/crafting/chemistry.dm index 5e3afae9e634d..62504103eca2c 100644 --- a/code/datums/components/crafting/chemistry.dm +++ b/code/datums/components/crafting/chemistry.dm @@ -1,14 +1,14 @@ /datum/crafting_recipe/improv_explosive - name = "IED" - result = /obj/item/grenade/iedcasing + name = "Improvised Explosive" + result = /obj/item/grenade/iedcasing/spawned + tool_behaviors = list(TOOL_WELDER, TOOL_SCREWDRIVER) reqs = list( - /datum/reagent/fuel = 50, - /obj/item/stack/cable_coil = 1, - /obj/item/assembly/igniter = 1, - /obj/item/reagent_containers/cup/soda_cans = 1, + /datum/reagent/fuel = 20, + /obj/item/stack/cable_coil = 15, + /obj/item/assembly/timer = 1, + /obj/item/pipe/quaternary/pipe = 1, ) - parts = list(/obj/item/reagent_containers/cup/soda_cans = 1) - time = 1.5 SECONDS + time = 6 SECONDS category = CAT_CHEMISTRY /datum/crafting_recipe/molotov diff --git a/code/datums/components/crafting/entertainment.dm b/code/datums/components/crafting/entertainment.dm index d01ffbc00dca5..6de554791a25a 100644 --- a/code/datums/components/crafting/entertainment.dm +++ b/code/datums/components/crafting/entertainment.dm @@ -8,6 +8,16 @@ ) category = CAT_ENTERTAINMENT +/datum/crafting_recipe/sharkplush + name = "Shark Plushie" + result = /obj/item/toy/plush/shark + reqs = list( + /obj/item/clothing/suit/hooded/shark_costume = 1, + /obj/item/grown/cotton = 10, + /obj/item/stack/sheet/cloth = 5, + ) + category = CAT_ENTERTAINMENT + /datum/crafting_recipe/mixedbouquet name = "Mixed bouquet" result = /obj/item/bouquet diff --git a/code/datums/components/crafting/equipment.dm b/code/datums/components/crafting/equipment.dm index 4b686fdd8e9d4..e7971488d638f 100644 --- a/code/datums/components/crafting/equipment.dm +++ b/code/datums/components/crafting/equipment.dm @@ -13,6 +13,16 @@ ..() blacklist |= subtypesof(/obj/item/shield/riot) +/datum/crafting_recipe/improvisedshield + name = "Improvised Shield" + result = /obj/item/shield/improvised + reqs = list( + /obj/item/stack/sheet/iron = 10, + /obj/item/stack/sticky_tape = 2, + ) + time = 4 SECONDS + category = CAT_EQUIPMENT + /datum/crafting_recipe/radiogloves name = "Radio Gloves" result = /obj/item/clothing/gloves/radio @@ -73,6 +83,17 @@ time = 5 SECONDS category = CAT_EQUIPMENT +/datum/crafting_recipe/barbeque_grill + name = "Barbeque grill" + result = /obj/machinery/grill + reqs = list( + /obj/item/stack/sheet/iron = 5, + /obj/item/stack/rods = 5, + /obj/item/assembly/igniter = 1, + ) + time = 7 SECONDS + category = CAT_EQUIPMENT + /datum/crafting_recipe/secure_closet name = "Secure Closet" result = /obj/structure/closet/secure_closet diff --git a/code/datums/components/crafting/tailoring.dm b/code/datums/components/crafting/tailoring.dm index bb01a4d78dc0f..56a6bfb2c3f18 100644 --- a/code/datums/components/crafting/tailoring.dm +++ b/code/datums/components/crafting/tailoring.dm @@ -345,6 +345,27 @@ ) category = CAT_CLOTHING +/datum/crafting_recipe/shark_costume + name = "shark costume" + result = /obj/item/clothing/suit/hooded/shark_costume + time = 2 SECONDS + reqs = list( + /obj/item/stack/sheet/leather = 5, + /obj/item/stack/sheet/animalhide/carp = 5, + ) + category = CAT_CLOTHING + +/datum/crafting_recipe/shork_costume + name = "shork costume" + result = /obj/item/clothing/suit/hooded/shork_costume + time = 2 SECONDS + tool_behaviors = list(TOOL_WIRECUTTER) + reqs = list( + /obj/item/clothing/suit/hooded/shark_costume = 1, + ) + category = CAT_CLOTHING + + /datum/crafting_recipe/sturdy_shako name = "Sturdy Shako" result = /obj/item/clothing/head/hats/hos/shako diff --git a/code/datums/components/fishing_spot.dm b/code/datums/components/fishing_spot.dm index c8a00ce74cdca..2763d583f819c 100644 --- a/code/datums/components/fishing_spot.dm +++ b/code/datums/components/fishing_spot.dm @@ -15,6 +15,12 @@ fish_source.on_fishing_spot_init() RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, PROC_REF(handle_attackby)) RegisterSignal(parent, COMSIG_FISHING_ROD_CAST, PROC_REF(handle_cast)) + RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examined)) + RegisterSignal(parent, COMSIG_ATOM_EXAMINE_MORE, PROC_REF(on_examined_more)) + +/datum/component/fishing_spot/Destroy() + fish_source = null + return ..() /datum/component/fishing_spot/proc/handle_cast(datum/source, obj/item/fishing_rod/rod, mob/user) SIGNAL_HANDLER @@ -28,6 +34,43 @@ return COMPONENT_NO_AFTERATTACK return NONE +///If the fish source has fishes that are shown in the +/datum/component/fishing_spot/proc/on_examined(datum/source, mob/user, list/examine_text) + SIGNAL_HANDLER + if(!HAS_MIND_TRAIT(user, TRAIT_EXAMINE_FISHING_SPOT)) + return + + var/has_known_fishes = FALSE + for(var/reward in fish_source.fish_counts) + if(!ispath(reward, /obj/item/fish)) + continue + var/obj/item/fish/prototype = reward + if(initial(prototype.show_in_catalog)) + has_known_fishes = TRUE + break + if(!has_known_fishes) + return + + examine_text += span_tinynoticeital("This is a fishing spot. You can look again to list its fishes...") + +/datum/component/fishing_spot/proc/on_examined_more(datum/source, mob/user, list/examine_text) + SIGNAL_HANDLER + if(!HAS_MIND_TRAIT(user, TRAIT_EXAMINE_FISHING_SPOT)) + return + + var/list/known_fishes = list() + for(var/reward in fish_source.fish_counts) + if(!ispath(reward, /obj/item/fish)) + continue + var/obj/item/fish/prototype = reward + if(initial(prototype.show_in_catalog)) + known_fishes += initial(prototype.name) + + if(!length(known_fishes)) + return + + examine_text += span_info("You can catch the following fish here: [english_list(known_fishes)].") + /datum/component/fishing_spot/proc/try_start_fishing(obj/item/possibly_rod, mob/user) SIGNAL_HANDLER var/obj/item/fishing_rod/rod = possibly_rod diff --git a/code/datums/components/food/edible.dm b/code/datums/components/food/edible.dm index ded8b8c4161df..85a6a7e386b53 100644 --- a/code/datums/components/food/edible.dm +++ b/code/datums/components/food/edible.dm @@ -251,7 +251,7 @@ Behavior that's still missing from this component that original food items had t if(!(food_flags & FOOD_IN_CONTAINER)) switch(bitecount) if(0) - // pass + pass() if(1) examine_list += span_notice("[owner] was bitten by someone!") if(2, 3) diff --git a/code/datums/components/ghost_direct_control.dm b/code/datums/components/ghost_direct_control.dm index a131a2d3ca728..de5bca4fcadde 100644 --- a/code/datums/components/ghost_direct_control.dm +++ b/code/datums/components/ghost_direct_control.dm @@ -16,8 +16,11 @@ /datum/component/ghost_direct_control/Initialize( ban_type = ROLE_SENTIENCE, role_name = null, + poll_question = null, poll_candidates = TRUE, + poll_announce_chosen = TRUE, poll_length = 10 SECONDS, + poll_chat_border_icon = null, poll_ignore_key = POLL_IGNORE_SENTIENCE_POTION, assumed_control_message = null, datum/callback/extra_control_checks, @@ -36,7 +39,7 @@ LAZYADD(GLOB.joinable_mobs[format_text("[initial(mob_parent.name)]")], mob_parent) if (poll_candidates) - INVOKE_ASYNC(src, PROC_REF(request_ghost_control), role_name || "[parent]", poll_length, poll_ignore_key) + INVOKE_ASYNC(src, PROC_REF(request_ghost_control), poll_question, role_name || "[parent]", poll_length, poll_ignore_key, poll_announce_chosen, poll_chat_border_icon) /datum/component/ghost_direct_control/RegisterWithParent() . = ..() @@ -70,23 +73,26 @@ examine_text += span_boldnotice("You could take control of this mob by clicking on it.") /// Send out a request for a brain -/datum/component/ghost_direct_control/proc/request_ghost_control(role_name, poll_length, poll_ignore_key) - if (!(GLOB.ghost_role_flags & GHOSTROLE_SPAWNER)) +/datum/component/ghost_direct_control/proc/request_ghost_control(poll_question, role_name, poll_length, poll_ignore_key, poll_announce_chosen, poll_chat_border_icon) + if(!(GLOB.ghost_role_flags & GHOSTROLE_SPAWNER)) return awaiting_ghosts = TRUE - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates( - question = "Do you want to play as [role_name]?", + var/mob/chosen_one = SSpolling.poll_ghosts_for_target( + question = poll_question, check_jobban = ban_type, role = ban_type, poll_time = poll_length, + checked_target = parent, ignore_category = poll_ignore_key, - pic_source = parent, + alert_pic = parent, role_name_text = role_name, + chat_text_border_icon = poll_chat_border_icon, + announce_chosen = poll_announce_chosen, ) awaiting_ghosts = FALSE - if (!LAZYLEN(candidates)) + if(isnull(chosen_one)) return - assume_direct_control(pick(candidates)) + assume_direct_control(chosen_one) /// A ghost clicked on us, they want to get in this body /datum/component/ghost_direct_control/proc/on_ghost_clicked(mob/our_mob, mob/dead/observer/hopeful_ghost) diff --git a/code/datums/components/marionette.dm b/code/datums/components/marionette.dm index a2f58031768e0..2777bbb517824 100644 --- a/code/datums/components/marionette.dm +++ b/code/datums/components/marionette.dm @@ -67,6 +67,7 @@ language = language, forced = "[source]'s marionette", saymode = saymode, + message_mods = list(MODE_RELAY = TRUE), ) speech_args[SPEECH_RANGE] = WHISPER_RANGE diff --git a/code/datums/components/material/remote_materials.dm b/code/datums/components/material/remote_materials.dm index e418d4276be10..568b018e58b2b 100644 --- a/code/datums/components/material/remote_materials.dm +++ b/code/datums/components/material/remote_materials.dm @@ -84,23 +84,9 @@ handles linking back and forth. silo = null - var/static/list/allowed_mats = list( - /datum/material/iron, - /datum/material/glass, - /datum/material/silver, - /datum/material/gold, - /datum/material/diamond, - /datum/material/plasma, - /datum/material/uranium, - /datum/material/bananium, - /datum/material/titanium, - /datum/material/bluespace, - /datum/material/plastic, - ) - mat_container = parent.AddComponent( \ /datum/component/material_container, \ - allowed_mats, \ + SSmaterials.materials_by_category[MAT_CATEGORY_SILO], \ local_size, \ mat_container_flags, \ container_signals = mat_container_signals, \ diff --git a/code/datums/components/orbit_poll.dm b/code/datums/components/orbit_poll.dm deleted file mode 100644 index ceb85d16d64c7..0000000000000 --- a/code/datums/components/orbit_poll.dm +++ /dev/null @@ -1,133 +0,0 @@ -/** - * A replacement for the standard poll_ghost_candidate. - * Use this to subtly ask players to join - it picks from orbiters. - * Please use named arguments for this. - * - * @params ignore_key - Required so it doesn't spam - * @params job_bans - You can insert a list or single items here. - * @params cb - Invokes this proc and appends the poll winner as the last argument, mob/dead/observer/ghost - * @params title - Optional. Useful if the role name does not match the parent. - * - * @usage - * ``` - * var/datum/callback/cb = CALLBACK(src, PROC_REF(do_stuff), arg1, arg2) - * AddComponent(/datum/component/orbit_poll, \ - * ignore_key = POLL_IGNORE_EXAMPLE, \ - * job_bans = ROLE_EXAMPLE or list(ROLE_EXAMPLE, ROLE_EXAMPLE2), \ - * title = "Use this if you want something other than the parent name", \ - * to_call = cb, \ - * ) - */ -/datum/component/orbit_poll - /// Prevent players with this ban from being selected - var/list/job_bans = list() - /// Title of the role to announce after it's done - var/title - /// Proc to invoke whenever the poll is complete - var/datum/callback/to_call - -/datum/component/orbit_poll/Initialize( \ - ignore_key, \ - list/job_bans, \ - datum/callback/to_call, \ - title, \ - header = "Ghost Poll", \ - custom_message, \ - timeout = 20 SECONDS \ -) - . = ..() - if (!isatom(parent)) - return COMPONENT_INCOMPATIBLE - - var/atom/owner = parent - - src.job_bans |= job_bans - src.title = title || owner.name - src.to_call = to_call - - var/message = custom_message || "[capitalize(src.title)] is looking for volunteers" - - notify_ghosts( - "[message]. An orbiter will be chosen in [DisplayTimeText(timeout)].\n", - source = parent, - header = "Volunteers requested", - custom_link = " (Ignore)", - ignore_key = ignore_key, - notify_flags = NOTIFY_CATEGORY_NOFLASH, - ) - - addtimer(CALLBACK(src, PROC_REF(end_poll)), timeout, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE|TIMER_DELETE_ME) - -/datum/component/orbit_poll/Topic(href, list/href_list) - if(!href_list["ignore"]) - return - - var/mob/user = usr - - var/ignore_key = href_list["ignore"] - if(tgui_alert(user, "Ignore further [title] alerts?", "Ignore Alert", list("Yes", "No"), 20 SECONDS, TRUE) != "Yes") - return - - GLOB.poll_ignore[ignore_key] |= user.ckey - -/// Concludes the poll, picking one of the orbiters -/datum/component/orbit_poll/proc/end_poll() - if(QDELETED(parent)) - return - - var/list/candidates = list() - var/atom/owner = parent - - var/datum/component/orbiter/orbiter_comp = owner.GetComponent(/datum/component/orbiter) - if(isnull(orbiter_comp)) - phone_home() - return - - for(var/mob/dead/observer/ghost as anything in orbiter_comp.orbiter_list) - var/client/ghost_client = ghost.client - - if(QDELETED(ghost) || isnull(ghost_client)) - continue - - if(is_banned_from(ghost.ckey, job_bans)) - continue - - var/datum/preferences/ghost_prefs = ghost_client.prefs - if(isnull(ghost_prefs)) - candidates += ghost // we'll assume they wanted to be picked despite prefs being null for whatever fucked up reason - continue - - if(!ghost_prefs.read_preference(/datum/preference/toggle/ghost_roles)) - continue - if(!isnull(ghost_client.holder) && !ghost_prefs.read_preference(/datum/preference/toggle/ghost_roles_as_admin)) - continue - - candidates += ghost - - pick_and_offer(candidates) - -/// Takes a list, picks a candidate, and offers the role to them. -/datum/component/orbit_poll/proc/pick_and_offer(list/volunteers) - if(length(volunteers) <= 0) - phone_home() - return - - var/mob/dead/observer/chosen = pick(volunteers) - - if(isnull(chosen)) - phone_home() - return - - SEND_SOUND(chosen, 'sound/misc/notice2.ogg') - var/response = tgui_alert(chosen, "Do you want to assume the role of [title]?", "Orbit Polling", list("Yes", "No"), 10 SECONDS) - if(response != "Yes") - var/reusable_list = volunteers - chosen - return pick_and_offer(reusable_list) - - deadchat_broadcast("[key_name(chosen, include_name = FALSE)] was selected for the role ([title]).", "Ghost Poll: ", parent) - phone_home(chosen) - -/// Make sure to call your parents my dude -/datum/component/orbit_poll/proc/phone_home(mob/dead/observer/chosen) - to_call.Invoke(chosen) - qdel(src) diff --git a/code/datums/components/overlay_lighting.dm b/code/datums/components/overlay_lighting.dm index efff82b703a6c..19c7528db8bf3 100644 --- a/code/datums/components/overlay_lighting.dm +++ b/code/datums/components/overlay_lighting.dm @@ -398,7 +398,7 @@ return if(current_holder && overlay_lighting_flags & LIGHTING_ON) current_holder.underlays -= cone - cone.alpha = min(200, (abs(new_power) * 90)+20) + cone.alpha = min(120, (abs(new_power) * 60) + 15) if(current_holder && overlay_lighting_flags & LIGHTING_ON) current_holder.underlays += cone diff --git a/code/datums/components/payment.dm b/code/datums/components/payment.dm index 1220614e9c386..12fc9aa4a424c 100644 --- a/code/datums/components/payment.dm +++ b/code/datums/components/payment.dm @@ -19,11 +19,6 @@ var/datum/bank_account/target_acc ///Does this payment component respect same-department-discount? var/department_discount = FALSE - ///A static typecache of all the money-based items that can be actively used as currency. - var/static/list/allowed_money = typecacheof(list( - /obj/item/stack/spacecash, - /obj/item/holochip, - /obj/item/coin)) /datum/component/payment/Initialize(_cost, _target, _style) target_acc = _target @@ -47,7 +42,7 @@ if(!ismob(target)) return COMPONENT_OBJ_CANCEL_CHARGE var/mob/living/user = target - if(issilicon(user) || isdrone(user) || isAdminGhostAI(user)) //They have evolved beyond the need for mere credits + if(HAS_SILICON_ACCESS(user) || isdrone(user)) //They have evolved beyond the need for mere credits return var/obj/item/card/id/card if(istype(user)) @@ -80,13 +75,13 @@ //Here is all the possible non-ID payment methods. var/list/counted_money = list() var/physical_cash_total = 0 - for(var/obj/item/credit in typecache_filter_list(user.get_all_contents(), allowed_money)) //Coins, cash, and credits. + for(var/obj/item/credit in typecache_filter_list(user.get_all_contents(), GLOB.allowed_money)) //Coins, cash, and credits. if(physical_cash_total > total_cost) break physical_cash_total += credit.get_item_credit_value() counted_money += credit - if(is_type_in_typecache(user.pulling, allowed_money) && (physical_cash_total < total_cost)) //Coins(Pulled). + if(is_type_in_typecache(user.pulling, GLOB.allowed_money) && (physical_cash_total < total_cost)) //Coins(Pulled). var/obj/item/counted_credit = user.pulling physical_cash_total += counted_credit.get_item_credit_value() counted_money += counted_credit @@ -134,9 +129,11 @@ * Attempts to charge a mob, user, an integer number of credits, total_cost, directly from an ID card/bank account. */ /datum/component/payment/proc/handle_card(mob/living/user, obj/item/card/id/idcard, total_cost) - var/atom/atom_parent = parent + var/atom/movable/atom_parent = parent if(!idcard) + if(transaction_style == PAYMENT_VENDING) + to_chat(user, span_warning("No card found.")) return FALSE if(!idcard?.registered_account) switch(transaction_style) @@ -146,6 +143,13 @@ to_chat(user, span_warning("ARE YOU JOKING. YOU DON'T HAVE A BANK ACCOUNT ON YOUR ID YOU IDIOT.")) if(PAYMENT_CLINICAL) to_chat(user, span_warning("ID Card lacks a bank account. Advancing.")) + if(PAYMENT_VENDING) + to_chat(user, span_warning("No account found.")) + + return FALSE + + if(!idcard.registered_account.account_job) + atom_parent.say("Departmental accounts have been blacklisted from personal expenses due to embezzlement.") return FALSE if(!(idcard.registered_account.has_money(total_cost))) @@ -156,6 +160,8 @@ to_chat(user, span_warning("YOU MORON. YOU ABSOLUTE BAFOON. YOU INSUFFERABLE TOOL. YOU ARE POOR.")) if(PAYMENT_CLINICAL) to_chat(user, span_warning("ID Card lacks funds. Aborting.")) + if(PAYMENT_VENDING) + to_chat(user, span_warning("You do not possess the funds to purchase that.")) atom_parent.balloon_alert(user, "needs [total_cost] credit\s!") return FALSE target_acc.transfer_money(idcard.registered_account, total_cost, "Nanotrasen: Usage of Corporate Machinery") diff --git a/code/datums/components/sign_language.dm b/code/datums/components/sign_language.dm index 23e40258100c7..e8d22238705eb 100644 --- a/code/datums/components/sign_language.dm +++ b/code/datums/components/sign_language.dm @@ -79,7 +79,7 @@ carbon_parent.verb_yell = "emphatically signs" carbon_parent.bubble_icon = "signlang" RegisterSignal(carbon_parent, COMSIG_CARBON_GAIN_ORGAN, PROC_REF(on_added_organ)) - RegisterSignal(carbon_parent, COMSIG_LIVING_TRY_SPEECH, PROC_REF(on_try_speech)) + RegisterSignal(carbon_parent, COMSIG_MOB_TRY_SPEECH, PROC_REF(on_try_speech)) RegisterSignal(carbon_parent, COMSIG_LIVING_TREAT_MESSAGE, PROC_REF(on_treat_living_message)) RegisterSignal(carbon_parent, COMSIG_MOVABLE_USING_RADIO, PROC_REF(on_using_radio)) RegisterSignal(carbon_parent, COMSIG_MOVABLE_SAY_QUOTE, PROC_REF(on_say_quote)) @@ -106,7 +106,7 @@ carbon_parent.bubble_icon = initial(carbon_parent.bubble_icon) UnregisterSignal(carbon_parent, list( COMSIG_CARBON_GAIN_ORGAN, - COMSIG_LIVING_TRY_SPEECH, + COMSIG_MOB_TRY_SPEECH, COMSIG_LIVING_TREAT_MESSAGE, COMSIG_MOVABLE_USING_RADIO, COMSIG_MOVABLE_SAY_QUOTE, @@ -125,7 +125,7 @@ var/obj/item/organ/internal/tongue/new_tongue = new_organ new_tongue.temp_say_mod = "signs" -/// Signal proc for [COMSIG_LIVING_TRY_SPEECH] +/// Signal proc for [COMSIG_MOB_TRY_SPEECH] /// Sign languagers can always speak regardless of they're mute (as long as they're not mimes) /datum/component/sign_language/proc/on_try_speech(mob/living/source, message, ignore_spam, forced) SIGNAL_HANDLER @@ -158,7 +158,7 @@ // Assuming none of the above fail, sign language users can speak // regardless of being muzzled or mute toxin'd or whatever. - return COMPONENT_CAN_ALWAYS_SPEAK + return COMPONENT_IGNORE_CAN_SPEAK /// Checks to see what state this person is in and if they are able to sign or not. /datum/component/sign_language/proc/check_signables_state() diff --git a/code/datums/components/sizzle.dm b/code/datums/components/sizzle.dm index ce91e9593f75f..957586aa806b8 100644 --- a/code/datums/components/sizzle.dm +++ b/code/datums/components/sizzle.dm @@ -1,26 +1,31 @@ /datum/component/sizzle - var/mutable_appearance/sizzling - var/sizzlealpha = 0 dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS -/datum/component/sizzle/Initialize() + ///The sizzling appearance to put on top of the food item + var/mutable_appearance/sizzling + ///The amount of time the food item has been sizzling for + var/grilled_time = 0 + +/datum/component/sizzle/Initialize(grilled_time) if(!isatom(parent)) return COMPONENT_INCOMPATIBLE - setup_sizzle() - -/datum/component/sizzle/InheritComponent(datum/component/C, i_am_original) - var/atom/food = parent - sizzlealpha += 5 - sizzling.alpha = sizzlealpha - food.cut_overlay(sizzling) - food.add_overlay(sizzling) -/datum/component/sizzle/proc/setup_sizzle() var/atom/food = parent var/icon/grill_marks = icon(food.icon, food.icon_state) grill_marks.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent) grill_marks.Blend(icon('icons/obj/machines/kitchen.dmi', "grillmarks"), ICON_MULTIPLY) //adds grill marks and the remaining white areas become transparent sizzling = new(grill_marks) - sizzling.alpha = sizzlealpha food.add_overlay(sizzling) + src.grilled_time = grilled_time + +/datum/component/sizzle/InheritComponent(datum/component/C, i_am_original, grilled_time) + var/atom/food = parent + sizzling.alpha += 5 + food.cut_overlay(sizzling) + food.add_overlay(sizzling) + src.grilled_time = grilled_time + +///Returns how long the food item has been sizzling for +/datum/component/sizzle/proc/time_elapsed() + return src.grilled_time diff --git a/code/datums/components/spirit_holding.dm b/code/datums/components/spirit_holding.dm index cb626801d86dc..e2b1cfb96bc3b 100644 --- a/code/datums/components/spirit_holding.dm +++ b/code/datums/components/spirit_holding.dm @@ -37,9 +37,10 @@ ///signal fired on self attacking parent /datum/component/spirit_holding/proc/on_attack_self(datum/source, mob/user) SIGNAL_HANDLER + INVOKE_ASYNC(src, PROC_REF(get_ghost), user) +/datum/component/spirit_holding/proc/get_ghost(mob/user) var/atom/thing = parent - if(attempting_awakening) thing.balloon_alert(user, "already channeling!") return @@ -47,20 +48,23 @@ thing.balloon_alert(user, "spirits are unwilling!") to_chat(user, span_warning("Anomalous otherworldly energies block you from awakening [parent]!")) return - attempting_awakening = TRUE thing.balloon_alert(user, "channeling...") - - var/datum/callback/to_call = CALLBACK(src, PROC_REF(affix_spirit), user) - parent.AddComponent(/datum/component/orbit_poll, \ - ignore_key = POLL_IGNORE_POSSESSED_BLADE, \ - job_bans = ROLE_PAI, \ - to_call = to_call, \ - title = "Spirit of [user.real_name]'s blade", \ + var/mob/chosen_one = SSpolling.poll_ghosts_for_target( + question = "Do you want to play as [span_notice("Spirit of [span_danger("[user.real_name]'s")] blade")]?", + check_jobban = ROLE_PAI, + poll_time = 20 SECONDS, + checked_target = thing, + ignore_category = POLL_IGNORE_POSSESSED_BLADE, + alert_pic = thing, + role_name_text = "possessed blade", + chat_text_border_icon = thing, ) + affix_spirit(user, chosen_one) /// On conclusion of the ghost poll /datum/component/spirit_holding/proc/affix_spirit(mob/awakener, mob/dead/observer/ghost) + var/atom/thing = parent if(isnull(ghost)) diff --git a/code/datums/components/style/style.dm b/code/datums/components/style/style.dm index b840b9d0702e4..7e92f846c8570 100644 --- a/code/datums/components/style/style.dm +++ b/code/datums/components/style/style.dm @@ -96,8 +96,6 @@ if(multitooled) src.multitooled = multitooled - RegisterSignal(src, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), PROC_REF(on_parent_multitool)) - /datum/component/style/RegisterWithParent() RegisterSignal(parent, COMSIG_MOB_ITEM_AFTERATTACK, PROC_REF(hotswap)) RegisterSignal(parent, COMSIG_MOB_MINED, PROC_REF(on_mine)) @@ -342,12 +340,6 @@ INVOKE_ASYNC(source, TYPE_PROC_REF(/mob/living, put_in_hands), target) source.visible_message(span_notice("[source] quickly swaps [weapon] out with [target]!"), span_notice("You quickly swap [weapon] with [target].")) - -/datum/component/style/proc/on_parent_multitool(datum/source, mob/living/user, obj/item/tool, list/recipes) - multitooled = !multitooled - user.balloon_alert(user, "meter [multitooled ? "" : "un"]hacked") - - // Point givers /datum/component/style/proc/on_punch(mob/living/carbon/human/punching_person, atom/attacked_atom, proximity) SIGNAL_HANDLER diff --git a/code/datums/components/style/style_meter.dm b/code/datums/components/style/style_meter.dm index 8c5b5ea87df60..72688f41c5274 100644 --- a/code/datums/components/style/style_meter.dm +++ b/code/datums/components/style/style_meter.dm @@ -17,7 +17,6 @@ /obj/item/style_meter/Initialize(mapload) . = ..() meter_appearance = mutable_appearance(icon, icon_state) - RegisterSignal(src, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), PROC_REF(on_multitool)) /obj/item/style_meter/Destroy(force) if(istype(loc, /obj/item/clothing/glasses)) @@ -28,28 +27,30 @@ . = ..() . += span_notice("You feel like a multitool could be used on this.") -/obj/item/style_meter/afterattack(atom/movable/attacked_atom, mob/user, proximity_flag, click_parameters) - . = ..() - if(!istype(attacked_atom, /obj/item/clothing/glasses)) - return +/obj/item/style_meter/interact_with_atom(atom/interacting_with, mob/living/user) + if(!istype(interacting_with, /obj/item/clothing/glasses)) + return NONE - forceMove(attacked_atom) - attacked_atom.add_overlay(meter_appearance) - RegisterSignal(attacked_atom, COMSIG_ITEM_EQUIPPED, PROC_REF(check_wearing)) - RegisterSignal(attacked_atom, COMSIG_ITEM_DROPPED, PROC_REF(on_drop)) - RegisterSignal(attacked_atom, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) - RegisterSignal(attacked_atom, COMSIG_CLICK_ALT, PROC_REF(on_altclick)) - RegisterSignal(attacked_atom, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), PROC_REF(on_multitool)) + . = ITEM_INTERACT_SUCCESS + + forceMove(interacting_with) + interacting_with.add_overlay(meter_appearance) + RegisterSignal(interacting_with, COMSIG_ITEM_EQUIPPED, PROC_REF(check_wearing)) + RegisterSignal(interacting_with, COMSIG_ITEM_DROPPED, PROC_REF(on_drop)) + RegisterSignal(interacting_with, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) + RegisterSignal(interacting_with, COMSIG_CLICK_ALT, PROC_REF(on_altclick)) + RegisterSignal(interacting_with, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), PROC_REF(redirect_multitool)) balloon_alert(user, "style meter attached") playsound(src, 'sound/machines/click.ogg', 30, TRUE) - if(!iscarbon(attacked_atom.loc)) - return + if(!iscarbon(interacting_with.loc)) + return . - var/mob/living/carbon/carbon_wearer = attacked_atom.loc - if(carbon_wearer.glasses != attacked_atom) - return + var/mob/living/carbon/carbon_wearer = interacting_with.loc + if(carbon_wearer.glasses != interacting_with) + return . style_meter = carbon_wearer.AddComponent(/datum/component/style, multitooled) + return . /obj/item/style_meter/Moved(atom/old_loc, Dir, momentum_change) . = ..() @@ -98,15 +99,17 @@ return COMPONENT_CANCEL_CLICK_ALT - -/// Signal proc for when the glasses or the meter is multitooled -/obj/item/style_meter/proc/on_multitool(datum/source, mob/living/user, obj/item/tool, list/recipes) +/obj/item/style_meter/multitool_act(mob/living/user, obj/item/tool) multitooled = !multitooled - if(style_meter) - SEND_SIGNAL(style_meter, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), user, tool, recipes) - else - balloon_alert(user, "meter [multitooled ? "" : "un"]hacked") + balloon_alert(user, "meter [multitooled ? "" : "un"]hacked") + style_meter?.multitooled = multitooled + return ITEM_INTERACT_SUCCESS + +/// Redirect multitooling on our glasses to our style meter +/obj/item/style_meter/proc/redirect_multitool(datum/source, mob/living/user, obj/item/tool, ...) + SIGNAL_HANDLER + return multitool_act(user, tool) /// Unregister signals and just generally clean up ourselves after being removed from glasses /obj/item/style_meter/proc/clean_up(atom/movable/old_location) diff --git a/code/datums/components/tackle.dm b/code/datums/components/tackle.dm index d19ef45e4b648..b2bae5cffeef8 100644 --- a/code/datums/components/tackle.dm +++ b/code/datums/components/tackle.dm @@ -164,8 +164,6 @@ neutral_outcome(user, target, tackle_word) //Forces a neutral outcome so you're not screwed too much from being blocked while tackling return COMPONENT_MOVABLE_IMPACT_FLIP_HITPUSH - - switch(roll) if(-INFINITY to -1) negative_outcome(user, target, roll, tackle_word) //OOF @@ -178,6 +176,15 @@ return COMPONENT_MOVABLE_IMPACT_FLIP_HITPUSH +/// Helper to do a grab and then adjust the grab state if necessary +/datum/component/tackler/proc/do_grab(mob/living/carbon/tackler, mob/living/carbon/tackled, skip_to_state = GRAB_PASSIVE) + set waitfor = FALSE + + if(!tackler.grab(tackled) || tackler.pulling != tackled) + return + if(tackler.grab_state != skip_to_state) + tackler.setGrabState(skip_to_state) + /** * Our positive tackling outcomes. * @@ -198,15 +205,10 @@ var/potential_outcome = (roll * 10) if(ishuman(target)) - var/mob/living/carbon/human/human_target = target - var/target_armor = human_target.run_armor_check(BODY_ZONE_CHEST, MELEE) - potential_outcome *= ((100 - target_armor) /100) + potential_outcome *= ((100 - target.run_armor_check(BODY_ZONE_CHEST, MELEE)) /100) else potential_outcome *= 0.9 - var/mob/living/carbon/human/human_target = target - var/mob/living/carbon/human/human_sacker = user - switch(potential_outcome) if(-INFINITY to 0) //I don't want to know how this has happened, okay? neutral_outcome(user, target, roll, tackle_word) //Default to neutral @@ -233,9 +235,7 @@ target.Paralyze(0.5 SECONDS) target.Knockdown(3 SECONDS) target.adjust_staggered_up_to(STAGGERED_SLOWDOWN_LENGTH * 2, 10 SECONDS) - if(ishuman(target) && ishuman(user)) - INVOKE_ASYNC(human_sacker, TYPE_PROC_REF(/mob/living, grab), human_sacker, human_target) - human_sacker.setGrabState(GRAB_PASSIVE) + do_grab(user, target) if(50 to INFINITY) // absolutely BODIED var/stamcritted_user = HAS_TRAIT_FROM(user, TRAIT_INCAPACITATED, STAMINA) @@ -259,9 +259,7 @@ target.Paralyze(0.5 SECONDS) target.Knockdown(3 SECONDS) target.adjust_staggered_up_to(STAGGERED_SLOWDOWN_LENGTH * 3, 10 SECONDS) - if(ishuman(target) && ishuman(user)) - INVOKE_ASYNC(human_sacker, TYPE_PROC_REF(/mob/living, grab), human_sacker, human_target) - human_sacker.setGrabState(GRAB_AGGRESSIVE) + do_grab(user, target, GRAB_AGGRESSIVE) /** * Our neutral tackling outcome. @@ -300,9 +298,7 @@ var/potential_roll_outcome = (roll * -10) if(ishuman(user)) - var/mob/living/carbon/human/human_sacker = target - var/attacker_armor = human_sacker.run_armor_check(BODY_ZONE_CHEST, MELEE) - potential_roll_outcome *= ((100 - attacker_armor) /100) + potential_roll_outcome *= ((100 - target.run_armor_check(BODY_ZONE_CHEST, MELEE)) /100) else potential_roll_outcome *= 0.9 @@ -447,7 +443,7 @@ if(human_sacker.get_mob_height() <= HUMAN_HEIGHT_SHORTEST) //JUST YOU WAIT TILL I FIND A CHAIR, BUDDY, THEN YOU'LL BE SORRY attack_mod -= 2 - if(human_sacker.mob_mood.sanity_level == SANITY_INSANE) //I've gone COMPLETELY INSANE + if(human_sacker.mob_mood.sanity_level == SANITY_LEVEL_INSANE) //I've gone COMPLETELY INSANE attack_mod += 15 human_sacker.adjustStaminaLoss(100) //AHAHAHAHAHAHAHAHA diff --git a/code/datums/components/uplink.dm b/code/datums/components/uplink.dm index e4e6e611ebca1..5007d8caeb92d 100644 --- a/code/datums/components/uplink.dm +++ b/code/datums/components/uplink.dm @@ -375,18 +375,18 @@ // PDA signal responses -/datum/component/uplink/proc/new_ringtone(datum/source, atom/source, new_ring_text) +/datum/component/uplink/proc/new_ringtone(datum/source, mob/living/user, new_ring_text) SIGNAL_HANDLER if(trim(lowertext(new_ring_text)) != trim(lowertext(unlock_code))) if(trim(lowertext(new_ring_text)) == trim(lowertext(failsafe_code))) - failsafe(source) + failsafe(user) return COMPONENT_STOP_RINGTONE_CHANGE return locked = FALSE - if(ismob(source)) - interact(null, source) - to_chat(source, span_hear("The computer softly beeps.")) + if(ismob(user)) + interact(null, user) + to_chat(user, span_hear("The computer softly beeps.")) return COMPONENT_STOP_RINGTONE_CHANGE /datum/component/uplink/proc/check_detonate() diff --git a/code/datums/diseases/transformation.dm b/code/datums/diseases/transformation.dm index 741520ed6ae61..334e21be4bd31 100644 --- a/code/datums/diseases/transformation.dm +++ b/code/datums/diseases/transformation.dm @@ -84,13 +84,12 @@ /datum/disease/transformation/proc/replace_banned_player(mob/living/new_mob) // This can run well after the mob has been transferred, so need a handle on the new mob to kill it if needed. set waitfor = FALSE - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as [affected_mob.real_name]?", check_jobban = bantype, role = bantype, poll_time = 5 SECONDS, target_mob = affected_mob, pic_source = affected_mob, role_name_text = "transformation victim") - if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) + var/mob/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to play as [span_notice(affected_mob.real_name)]?", check_jobban = bantype, role = bantype, poll_time = 5 SECONDS, checked_target = affected_mob, alert_pic = affected_mob, role_name_text = "transformation victim") + if(chosen_one) to_chat(affected_mob, span_userdanger("Your mob has been taken over by a ghost! Appeal your job ban if you want to avoid this in the future!")) - message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(affected_mob)]) to replace a jobbanned player.") + message_admins("[key_name_admin(chosen_one)] has taken control of ([key_name_admin(affected_mob)]) to replace a jobbanned player.") affected_mob.ghostize(FALSE) - affected_mob.key = C.key + affected_mob.key = chosen_one.key else to_chat(new_mob, span_userdanger("Your mob has been claimed by death! Appeal your job ban if you want to avoid this in the future!")) new_mob.investigate_log("has been killed because there was no one to replace them as a job-banned player.", INVESTIGATE_DEATHS) @@ -149,7 +148,6 @@ affected_mob.say(pick("Eeee!", "Eeek, ook ook!", "Eee-eeek!", "Ungh, ungh."), forced = "jungle fever") /datum/disease/transformation/robot - name = "Robotic Transformation" cure_text = "An injection of copper." cures = list(/datum/reagent/copper) @@ -171,7 +169,6 @@ infectable_biotypes = MOB_ORGANIC|MOB_UNDEAD|MOB_ROBOTIC bantype = JOB_CYBORG - /datum/disease/transformation/robot/stage_act(seconds_per_tick, times_fired) . = ..() if(!.) diff --git a/code/datums/dna.dm b/code/datums/dna.dm index d67cc925baff8..0321045401780 100644 --- a/code/datums/dna.dm +++ b/code/datums/dna.dm @@ -461,7 +461,7 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block()) update_dna_identity() -/datum/dna/stored //subtype used by brain mob's stored_dna +/datum/dna/stored //subtype used by brain mob's stored_dna and the crew manifest /datum/dna/stored/add_mutation(mutation_name) //no mutation changes on stored dna. return diff --git a/code/datums/elements/basic_eating.dm b/code/datums/elements/basic_eating.dm index 297e77fa060ea..2a7a4b46598b5 100644 --- a/code/datums/elements/basic_eating.dm +++ b/code/datums/elements/basic_eating.dm @@ -54,6 +54,8 @@ /datum/element/basic_eating/proc/try_eating(mob/living/eater, atom/target) if(!is_type_in_list(target, food_types)) return FALSE + if(SEND_SIGNAL(eater, COMSIG_MOB_PRE_EAT, target) & COMSIG_MOB_CANCEL_EAT) + return FALSE var/eat_verb if(drinking) eat_verb = pick("slurp","sip","guzzle","drink","quaff","suck") @@ -79,6 +81,7 @@ return TRUE /datum/element/basic_eating/proc/finish_eating(mob/living/eater, atom/target) + SEND_SIGNAL(eater, COMSIG_MOB_ATE) if(drinking) playsound(eater.loc,'sound/items/drink.ogg', rand(10,50), TRUE) else diff --git a/code/datums/elements/bed_tucking.dm b/code/datums/elements/bed_tucking.dm index 70b10d4a58c0c..58f5640c31c75 100644 --- a/code/datums/elements/bed_tucking.dm +++ b/code/datums/elements/bed_tucking.dm @@ -1,7 +1,7 @@ /// Tucking element, for things that can be tucked into bed. /datum/element/bed_tuckable element_flags = ELEMENT_BESPOKE - argument_hash_start_idx = 2 + argument_hash_start_idx = 3 /// our pixel_x offset - how much the item moves x when in bed (+x is closer to the pillow) var/x_offset = 0 /// our pixel_y offset - how much the item move y when in bed (-y is closer to the middle) @@ -11,7 +11,7 @@ /// our starting angle for the item var/starting_angle = 0 -/datum/element/bed_tuckable/Attach(obj/target, x = 0, y = 0, rotation = 0) +/datum/element/bed_tuckable/Attach(obj/target, mapload = FALSE, x = 0, y = 0, rotation = 0) . = ..() if(!isitem(target)) return ELEMENT_INCOMPATIBLE @@ -20,6 +20,13 @@ y_offset = y starting_angle = rotation RegisterSignal(target, COMSIG_ITEM_ATTACK_ATOM, PROC_REF(tuck_into_bed)) + if(!mapload) + return + var/turf/our_home = get_turf(target) + var/obj/structure/bed/eepy = locate(/obj/structure/bed) in our_home + if(isnull(eepy)) + return + tuck(target, eepy) /datum/element/bed_tuckable/Detach(obj/target) . = ..() @@ -42,6 +49,10 @@ return to_chat(tucker, span_notice("You lay [tucked] out on [target_bed].")) + tuck(tucked, target_bed) + return COMPONENT_NO_AFTERATTACK + +/datum/element/bed_tuckable/proc/tuck(obj/item/tucked, obj/structure/bed/target_bed) tucked.dir = target_bed.dir tucked.pixel_x = target_bed.dir & EAST ? -x_offset : x_offset tucked.pixel_y = y_offset @@ -50,8 +61,6 @@ tucked.transform = turn(tucked.transform, rotation_degree) RegisterSignal(tucked, COMSIG_ITEM_PICKUP, PROC_REF(untuck)) - return COMPONENT_NO_AFTERATTACK - /** * If we rotate our object, then we need to un-rotate it when it's picked up * diff --git a/code/datums/elements/shatters_when_thrown.dm b/code/datums/elements/can_shatter.dm similarity index 69% rename from code/datums/elements/shatters_when_thrown.dm rename to code/datums/elements/can_shatter.dm index cbb5994852c81..73b025ad83c08 100644 --- a/code/datums/elements/shatters_when_thrown.dm +++ b/code/datums/elements/can_shatter.dm @@ -1,7 +1,8 @@ /** * When attached to something, will make that thing shatter into shards on throw impact or z level falling + * Or even when used as a weapon if the 'shatters_as_weapon' arg is TRUE */ -/datum/element/shatters_when_thrown +/datum/element/can_shatter element_flags = ELEMENT_BESPOKE argument_hash_start_idx = 2 @@ -12,7 +13,12 @@ /// What sound plays when the thing we're attached to shatters var/shattering_sound -/datum/element/shatters_when_thrown/Attach(datum/target, shard_type = /obj/item/plate_shard, number_of_shards = 5, shattering_sound = 'sound/items/ceramic_break.ogg') +/datum/element/can_shatter/Attach(datum/target, + shard_type = /obj/item/plate_shard, + number_of_shards = 5, + shattering_sound = 'sound/items/ceramic_break.ogg', + shatters_as_weapon = FALSE, + ) . = ..() if(!ismovable(target)) @@ -24,26 +30,28 @@ RegisterSignal(target, COMSIG_MOVABLE_IMPACT, PROC_REF(on_throw_impact)) RegisterSignal(target, COMSIG_ATOM_ON_Z_IMPACT, PROC_REF(on_z_impact)) + if(shatters_as_weapon) + RegisterSignal(target, COMSIG_ITEM_POST_ATTACK_ATOM, PROC_REF(on_post_attack_atom)) -/datum/element/shatters_when_thrown/Detach(datum/target) +/datum/element/can_shatter/Detach(datum/target) . = ..() UnregisterSignal(target, list(COMSIG_MOVABLE_IMPACT, COMSIG_ATOM_ON_Z_IMPACT)) /// Tells the parent to shatter if we impact a lower zlevel -/datum/element/shatters_when_thrown/proc/on_z_impact(datum/source, turf/impacted_turf, levels) +/datum/element/can_shatter/proc/on_z_impact(datum/source, turf/impacted_turf, levels) SIGNAL_HANDLER shatter(source, impacted_turf) /// Tells the parent to shatter if we are thrown and impact something -/datum/element/shatters_when_thrown/proc/on_throw_impact(datum/source, atom/hit_atom) +/datum/element/can_shatter/proc/on_throw_impact(datum/source, atom/hit_atom) SIGNAL_HANDLER shatter(source, hit_atom) /// Handles the actual shattering part, throwing shards of whatever is defined on the component everywhere -/datum/element/shatters_when_thrown/proc/shatter(atom/movable/source, atom/hit_atom) +/datum/element/can_shatter/proc/shatter(atom/movable/source, atom/hit_atom) var/generator/scatter_gen = generator(GEN_CIRCLE, 0, 48, NORMAL_RAND) var/scatter_turf = get_turf(hit_atom) @@ -64,3 +72,7 @@ return else qdel(source) + +/datum/element/can_shatter/proc/on_post_attack_atom(obj/item/source, atom/attacked_atom, mob/living/user) + SIGNAL_HANDLER + shatter(source, attacked_atom) diff --git a/code/datums/elements/cleaning.dm b/code/datums/elements/cleaning.dm index 3f39d00eb6e7c..6db1c9fb58033 100644 --- a/code/datums/elements/cleaning.dm +++ b/code/datums/elements/cleaning.dm @@ -1,32 +1,36 @@ +/datum/element/cleaning + /datum/element/cleaning/Attach(datum/target) . = ..() if(!ismovable(target)) return ELEMENT_INCOMPATIBLE - RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(Clean)) + RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(clean)) /datum/element/cleaning/Detach(datum/target) . = ..() UnregisterSignal(target, COMSIG_MOVABLE_MOVED) -/datum/element/cleaning/proc/Clean(datum/source) +/datum/element/cleaning/proc/clean(datum/source) SIGNAL_HANDLER - var/atom/movable/AM = source - var/turf/tile = AM.loc + var/atom/movable/atom_movable = source + var/turf/tile = atom_movable.loc if(!isturf(tile)) return tile.wash(CLEAN_SCRUB) - for(var/A in tile) + for(var/atom/cleaned as anything in tile) // Clean small items that are lying on the ground - if(isitem(A)) - var/obj/item/I = A - if(I.w_class <= WEIGHT_CLASS_SMALL && !ismob(I.loc)) - I.wash(CLEAN_SCRUB) + if(isitem(cleaned)) + var/obj/item/cleaned_item = cleaned + if(cleaned_item.w_class <= WEIGHT_CLASS_SMALL) + cleaned_item.wash(CLEAN_SCRUB) + continue // Clean humans that are lying down - else if(ishuman(A)) - var/mob/living/carbon/human/cleaned_human = A - if(cleaned_human.body_position == LYING_DOWN) - cleaned_human.wash(CLEAN_SCRUB) - cleaned_human.regenerate_icons() - to_chat(cleaned_human, span_danger("[AM] cleans your face!")) + if(!ishuman(cleaned)) + continue + var/mob/living/carbon/human/cleaned_human = cleaned + if(cleaned_human.body_position == LYING_DOWN) + cleaned_human.wash(CLEAN_SCRUB) + cleaned_human.regenerate_icons() + to_chat(cleaned_human, span_danger("[atom_movable] cleans your face!")) diff --git a/code/datums/elements/food/grilled_item.dm b/code/datums/elements/food/grilled_item.dm index f657b969f062e..de6c2ef41c1b9 100644 --- a/code/datums/elements/food/grilled_item.dm +++ b/code/datums/elements/food/grilled_item.dm @@ -9,30 +9,27 @@ var/atom/this_food = target switch(grill_time) //no 0-20 to prevent spam - if(20 to 30) + if(20 SECONDS to 30 SECONDS) this_food.name = "lightly-grilled [this_food.name]" this_food.desc += " It's been lightly grilled." - if(30 to 80) + if(30 SECONDS to 80 SECONDS) this_food.name = "grilled [this_food.name]" this_food.desc += " It's been grilled." - if(80 to 100) + if(80 SECONDS to 100 SECONDS) this_food.name = "heavily grilled [this_food.name]" this_food.desc += " It's been heavily grilled." - if(100 to INFINITY) //grill marks reach max alpha + if(100 SECONDS to INFINITY) //grill marks reach max alpha this_food.name = "Powerfully Grilled [this_food.name]" this_food.desc = "A [this_food.name]. Reminds you of your wife, wait, no, it's prettier!" - if(grill_time > 20) - ADD_TRAIT(this_food, TRAIT_FOOD_GRILLED, ELEMENT_TRAIT(type)) - if(grill_time > 30) + if(grill_time > 30 SECONDS && isnull(this_food.GetComponent(/datum/component/edible))) this_food.AddComponent(/datum/component/edible, foodtypes = FRIED) /datum/element/grilled_item/Detach(atom/source, ...) source.name = initial(source.name) source.desc = initial(source.desc) - REMOVE_TRAIT(source, TRAIT_FOOD_GRILLED, ELEMENT_TRAIT(type)) qdel(source.GetComponent(/datum/component/edible)) // Don't care if it was initially edible return ..() diff --git a/code/datums/elements/frozen.dm b/code/datums/elements/frozen.dm index b88cc2afa2240..434968dd4d5e0 100644 --- a/code/datums/elements/frozen.dm +++ b/code/datums/elements/frozen.dm @@ -55,9 +55,13 @@ GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0 Detach(source) ///signal handler for COMSIG_MOVABLE_POST_THROW that shatters our target after impacting after a throw -/datum/element/frozen/proc/shatter_on_throw(datum/target) +/datum/element/frozen/proc/shatter_on_throw(datum/target, datum/thrownthing/throwingdatum) SIGNAL_HANDLER var/obj/obj_target = target + if(ismob(throwingdatum.thrower)) + log_combat(throwingdatum.thrower, target, "shattered", addition = "from being thrown due to [target] being frozen.") + else + log_combat(throwingdatum.thrower, target, "launched", addition = "shattering it due to being frozen.") obj_target.visible_message(span_danger("[obj_target] shatters into a million pieces!")) obj_target.obj_flags |= NO_DECONSTRUCTION // disable item spawning obj_target.deconstruct(FALSE) // call pre-deletion specialized code -- internals release gas etc diff --git a/code/datums/elements/toy_talk.dm b/code/datums/elements/toy_talk.dm new file mode 100644 index 0000000000000..8061eafaeb31b --- /dev/null +++ b/code/datums/elements/toy_talk.dm @@ -0,0 +1,28 @@ +/** + * Allows people to talk via the item with .l or .r + * + * Be sure to override [/atom/movable/proc/GetVoice] if you want the item's "voice" to not default to itself + */ +/datum/element/toy_talk + +/datum/element/toy_talk/Attach(datum/target) + . = ..() + if(!isitem(target)) + return ELEMENT_INCOMPATIBLE + + RegisterSignal(target, COMSIG_ITEM_TALK_INTO, PROC_REF(do_talk)) + +/datum/element/toy_talk/Detach(datum/source, ...) + . = ..() + UnregisterSignal(source, COMSIG_ITEM_TALK_INTO) + +/datum/element/toy_talk/proc/do_talk(obj/item/source, mob/speaker, message, channel, list/spans, language, list/message_mods) + SIGNAL_HANDLER + + if(!ismob(speaker) || message_mods[MODE_HEADSET] || message_mods[MODE_RELAY]) + return NONE + + message_mods[MODE_RELAY] = TRUE // Redundant (given NOPASS) but covers our bases + speaker.log_talk(message, LOG_SAY, tag = "toy talk ([source])") + source.say(message, language = language, sanitize = FALSE, message_mods = list(MODE_RELAY = TRUE)) + return NOPASS diff --git a/code/datums/greyscale/config_types/greyscale_configs/greyscale_items.dm b/code/datums/greyscale/config_types/greyscale_configs/greyscale_items.dm index 28433ba9064b6..6b9465bf46af0 100644 --- a/code/datums/greyscale/config_types/greyscale_configs/greyscale_items.dm +++ b/code/datums/greyscale/config_types/greyscale_configs/greyscale_items.dm @@ -335,3 +335,8 @@ name = "Flower Worn" icon_file = 'icons/mob/clothing/head/hydroponics.dmi' json_config = 'code/datums/greyscale/json_configs/simple_flower_worn.json' + +/datum/greyscale_config/piggy_bank + name = "Piggy Bank" + icon_file = 'icons/obj/fluff/general.dmi' + json_config = 'code/datums/greyscale/json_configs/piggy_bank.json' diff --git a/code/datums/greyscale/config_types/greyscale_configs/greyscale_mobs.dm b/code/datums/greyscale/config_types/greyscale_configs/greyscale_mobs.dm index 250eba9a0d51f..87799dedda5de 100644 --- a/code/datums/greyscale/config_types/greyscale_configs/greyscale_mobs.dm +++ b/code/datums/greyscale/config_types/greyscale_configs/greyscale_mobs.dm @@ -44,4 +44,3 @@ name = "Gutlunch" icon_file = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' json_config = 'code/datums/greyscale/json_configs/gutlunch.json' - diff --git a/code/datums/greyscale/json_configs/piggy_bank.json b/code/datums/greyscale/json_configs/piggy_bank.json new file mode 100644 index 0000000000000..71876213e197f --- /dev/null +++ b/code/datums/greyscale/json_configs/piggy_bank.json @@ -0,0 +1,10 @@ +{ + "piggy_bank": [ + { + "type": "icon_state", + "icon_state": "piggy_bank", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/code/datums/id_trim/_id_trim.dm b/code/datums/id_trim/_id_trim.dm index 6428b38d56225..067e2e3826390 100644 --- a/code/datums/id_trim/_id_trim.dm +++ b/code/datums/id_trim/_id_trim.dm @@ -23,3 +23,9 @@ var/list/access = list() /// Accesses that this trim unlocks on a card that require wildcard slots to apply. If a card cannot accept all a trim's wildcard accesses, the card is incompatible with the trim. var/list/wildcard_access = list() + +/// Returns the SecHUD job icon state for whatever this object's ID card is, if it has one. +/obj/item/proc/get_sechud_job_icon_state() + var/obj/item/card/id/id_card = GetID() + + return id_card?.get_trim_sechud_icon_state() || SECHUD_NO_ID diff --git a/code/datums/id_trim/jobs.dm b/code/datums/id_trim/jobs.dm index 46f691564bb4b..3d94c8645b5fa 100644 --- a/code/datums/id_trim/jobs.dm +++ b/code/datums/id_trim/jobs.dm @@ -1278,3 +1278,20 @@ #undef POPULATION_SCALED_ACCESS #undef ALWAYS_GETS_ACCESS + +/datum/id_trim/job/human_ai + assignment = JOB_HUMAN_AI + trim_state = "trim_recluse" + department_color = COLOR_BLUE_GRAY + subdepartment_color = COLOR_MODERATE_BLUE + sechud_icon_state = SECHUD_HUMAN_AI + minimal_access = list( + ACCESS_ROBOTICS, // to access robotic controls + ACCESS_NETWORK, //to access NTOS + ACCESS_KEYCARD_AUTH, //to access holopads + ACCESS_MINISAT, + ACCESS_AI_UPLOAD, + ) + extra_access = list() + template_access = list() + job = /datum/job/human_ai diff --git a/code/datums/lazy_template.dm b/code/datums/lazy_template.dm index 0a5e8e5331413..3b19a17a179da 100644 --- a/code/datums/lazy_template.dm +++ b/code/datums/lazy_template.dm @@ -13,6 +13,8 @@ var/map_dir = "_maps/templates/lazy_templates" /// The filename (without extension) of the map to load var/map_name + /// place_on_top: Whether to use /turf/proc/PlaceOnTop rather than /turf/proc/ChangeTurf + var/place_on_top = FALSE /datum/lazy_template/New() reservations = list() @@ -83,6 +85,7 @@ bottom_left.z, z_upper = z_idx, z_lower = z_idx, + place_on_top = place_on_top, ) for(var/turf/turf as anything in block(bottom_left, top_right)) loaded_turfs += turf diff --git a/code/datums/materials/basemats.dm b/code/datums/materials/basemats.dm index 3c5f6ab62d0cb..e2897a772e530 100644 --- a/code/datums/materials/basemats.dm +++ b/code/datums/materials/basemats.dm @@ -4,7 +4,7 @@ desc = "Common iron ore often found in sedimentary and igneous layers of the crust." color = "#878687" greyscale_colors = "#878687" - categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) + categories = list(MAT_CATEGORY_SILO = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) sheet_type = /obj/item/stack/sheet/iron ore_type = /obj/item/stack/ore/iron value_per_unit = 5 / SHEET_MATERIAL_AMOUNT @@ -25,10 +25,10 @@ color = "#88cdf1" greyscale_colors = "#88cdf196" alpha = 150 - categories = list(MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) + categories = list(MAT_CATEGORY_SILO = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) integrity_modifier = 0.1 sheet_type = /obj/item/stack/sheet/glass - ore_type = /obj/item/stack/ore/glass + ore_type = /obj/item/stack/ore/glass/basalt shard_type = /obj/item/shard debris_type = /obj/effect/decal/cleanable/glass value_per_unit = 5 / SHEET_MATERIAL_AMOUNT @@ -47,12 +47,12 @@ /datum/material/glass/on_applied_obj(atom/source, amount, material_flags) . = ..() if(!isstack(source)) - source.AddElement(/datum/element/shatters_when_thrown, shard_type, round(amount / SHEET_MATERIAL_AMOUNT), SFX_SHATTER) + source.AddElement(/datum/element/can_shatter, shard_type, round(amount / SHEET_MATERIAL_AMOUNT), SFX_SHATTER) /datum/material/glass/on_removed(atom/source, amount, material_flags) . = ..() - source.RemoveElement(/datum/element/shatters_when_thrown, shard_type) + source.RemoveElement(/datum/element/can_shatter, shard_type) /* Color matrices are like regular colors but unlike with normal colors, you can go over 255 on a channel. @@ -65,7 +65,7 @@ Unless you know what you're doing, only use the first three numbers. They're in desc = "Silver" color = list(255/255, 284/255, 302/255,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0) greyscale_colors = "#e3f1f8" - categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) + categories = list(MAT_CATEGORY_SILO = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) sheet_type = /obj/item/stack/sheet/mineral/silver ore_type = /obj/item/stack/ore/silver value_per_unit = 50 / SHEET_MATERIAL_AMOUNT @@ -86,7 +86,7 @@ Unless you know what you're doing, only use the first three numbers. They're in color = list(340/255, 240/255, 50/255,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0) //gold is shiny, but not as bright as bananium greyscale_colors = "#dbdd4c" strength_modifier = 1.2 - categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) + categories = list(MAT_CATEGORY_SILO = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) sheet_type = /obj/item/stack/sheet/mineral/gold ore_type = /obj/item/stack/ore/gold value_per_unit = 125 / SHEET_MATERIAL_AMOUNT @@ -107,7 +107,7 @@ Unless you know what you're doing, only use the first three numbers. They're in desc = "Highly pressurized carbon" color = list(48/255, 272/255, 301/255,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0) greyscale_colors = "#71c8f784" - categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) + categories = list(MAT_CATEGORY_SILO = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) sheet_type = /obj/item/stack/sheet/mineral/diamond ore_type = /obj/item/stack/ore/diamond alpha = 132 @@ -130,7 +130,7 @@ Unless you know what you're doing, only use the first three numbers. They're in desc = "Uranium" color = rgb(48, 237, 26) greyscale_colors = rgb(48, 237, 26) - categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) + categories = list(MAT_CATEGORY_SILO = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) sheet_type = /obj/item/stack/sheet/mineral/uranium ore_type = /obj/item/stack/ore/uranium value_per_unit = 100 / SHEET_MATERIAL_AMOUNT @@ -170,7 +170,7 @@ Unless you know what you're doing, only use the first three numbers. They're in desc = "Isn't plasma a state of matter? Oh whatever." color = list(298/255, 46/255, 352/255,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0) greyscale_colors = "#c162ec" - categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) + categories = list(MAT_CATEGORY_SILO = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) sheet_type = /obj/item/stack/sheet/mineral/plasma ore_type = /obj/item/stack/ore/plasma value_per_unit = 200 / SHEET_MATERIAL_AMOUNT @@ -204,7 +204,7 @@ Unless you know what you're doing, only use the first three numbers. They're in greyscale_colors = "#4e7dffC8" alpha = 200 starlight_color = COLOR_BLUE - categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_ITEM_MATERIAL = TRUE) + categories = list(MAT_CATEGORY_SILO = TRUE, MAT_CATEGORY_ITEM_MATERIAL = TRUE) beauty_modifier = 0.5 sheet_type = /obj/item/stack/sheet/bluespace_crystal ore_type = /obj/item/stack/ore/bluespace_crystal @@ -225,7 +225,7 @@ Unless you know what you're doing, only use the first three numbers. They're in desc = "Material with hilarious properties" color = list(460/255, 464/255, 0, 0, 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0) //obnoxiously bright yellow greyscale_colors = "#ffff00" - categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) + categories = list(MAT_CATEGORY_SILO = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) sheet_type = /obj/item/stack/sheet/mineral/bananium ore_type = /obj/item/stack/ore/bananium value_per_unit = 1000 / SHEET_MATERIAL_AMOUNT @@ -256,7 +256,7 @@ Unless you know what you're doing, only use the first three numbers. They're in color = "#b3c0c7" greyscale_colors = "#b3c0c7" strength_modifier = 1.3 - categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) + categories = list(MAT_CATEGORY_SILO = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) sheet_type = /obj/item/stack/sheet/mineral/titanium ore_type = /obj/item/stack/ore/titanium value_per_unit = 125 / SHEET_MATERIAL_AMOUNT @@ -297,7 +297,7 @@ Unless you know what you're doing, only use the first three numbers. They're in strength_modifier = 0.85 sheet_type = /obj/item/stack/sheet/plastic ore_type = /obj/item/stack/ore/slag //No plastic or coal ore, so we use slag. - categories = list(MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) + categories = list(MAT_CATEGORY_SILO = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) value_per_unit = 25 / SHEET_MATERIAL_AMOUNT beauty_modifier = -0.01 armor_modifiers = list(MELEE = 1.5, BULLET = 1.1, LASER = 0.3, ENERGY = 0.5, BOMB = 1, BIO = 1, FIRE = 1.1, ACID = 1) diff --git a/code/datums/mind/antag.dm b/code/datums/mind/antag.dm index 8b516a86a02a1..4aaab464f5e8f 100644 --- a/code/datums/mind/antag.dm +++ b/code/datums/mind/antag.dm @@ -105,6 +105,31 @@ var/datum/antagonist/rev/revolutionary = has_antag_datum(/datum/antagonist/rev) revolutionary?.remove_revolutionary() +/** + * Gets an item that can be used as an uplink somewhere on the mob's person. + * + * * desired_location: the location to look for the uplink in. An UPLINK_ define. + * If the desired location is not found, defaults to another location. + * + * Returns the item found, or null if no item was found. + */ +/mob/living/carbon/proc/get_uplink_location(desired_location = UPLINK_PDA) + var/list/all_contents = get_all_contents() + var/obj/item/modular_computer/pda/my_pda = locate() in all_contents + var/obj/item/radio/my_radio = locate() in all_contents + var/obj/item/pen/my_pen = (locate() in my_pda) || (locate() in all_contents) + + switch(desired_location) + if(UPLINK_PDA) + return my_pda || my_radio || my_pen + + if(UPLINK_RADIO) + return my_radio || my_pda || my_pen + + if(UPLINK_PEN) + return my_pen || my_pda || my_radio + + return null /** * ## give_uplink @@ -115,53 +140,26 @@ * * antag_datum: the antag datum of the uplink owner, for storing it in antag memory. optional! */ /datum/mind/proc/give_uplink(silent = FALSE, datum/antagonist/antag_datum) - if(!current) + if(isnull(current)) return var/mob/living/carbon/human/traitor_mob = current if (!istype(traitor_mob)) return - var/list/all_contents = traitor_mob.get_all_contents() - var/obj/item/modular_computer/pda/PDA = locate() in all_contents - var/obj/item/radio/R = locate() in all_contents - var/obj/item/pen/P - - if (PDA) // Prioritize PDA pen, otherwise the pocket protector pens will be chosen, which causes numerous ahelps about missing uplink - P = locate() in PDA - if (!P) // If we couldn't find a pen in the PDA, or we didn't even have a PDA, do it the old way - P = locate() in all_contents - var/obj/item/uplink_loc - var/implant = FALSE - var/uplink_spawn_location = traitor_mob.client?.prefs?.read_preference(/datum/preference/choiced/uplink_location) - var/cant_speak = (HAS_TRAIT(traitor_mob, TRAIT_MUTE) || traitor_mob.mind?.assigned_role.title == JOB_MIME) + var/cant_speak = (HAS_TRAIT(traitor_mob, TRAIT_MUTE) || is_mime_job(assigned_role)) if(uplink_spawn_location == UPLINK_RADIO && cant_speak) if(!silent) to_chat(traitor_mob, span_warning("You have been deemed ineligible for a radio uplink. Supplying standard uplink instead.")) uplink_spawn_location = UPLINK_PDA - switch (uplink_spawn_location) - if(UPLINK_PDA) - uplink_loc = PDA - if(!uplink_loc) - uplink_loc = R - if(!uplink_loc) - uplink_loc = P - if(UPLINK_RADIO) - uplink_loc = R - if(!uplink_loc) - uplink_loc = PDA - if(!uplink_loc) - uplink_loc = P - if(UPLINK_PEN) - uplink_loc = P - if(UPLINK_IMPLANT) - implant = TRUE - if(!uplink_loc) // We've looked everywhere, let's just implant you - implant = TRUE + if(uplink_spawn_location != UPLINK_IMPLANT) + uplink_loc = traitor_mob.get_uplink_location(uplink_spawn_location) + if(istype(uplink_loc, /obj/item/radio) && cant_speak) + uplink_loc = null - if(implant) + if(isnull(uplink_loc)) var/obj/item/implant/uplink/starting/new_implant = new(traitor_mob) new_implant.implant(traitor_mob, null, silent = TRUE) if(!silent) @@ -178,22 +176,27 @@ new_uplink.uplink_handler.owner = traitor_mob.mind new_uplink.uplink_handler.assigned_role = traitor_mob.mind.assigned_role.title new_uplink.uplink_handler.assigned_species = traitor_mob.dna.species.id - if(uplink_loc == R) - unlock_text = "Your Uplink is cunningly disguised as your [R.name]. Simply speak \"[new_uplink.unlock_code]\" into frequency [RADIO_TOKEN_UPLINK] to unlock its hidden features." - add_memory(/datum/memory/key/traitor_uplink, uplink_loc = R.name, uplink_code = new_uplink.unlock_code) - else if(uplink_loc == PDA) - unlock_text = "Your Uplink is cunningly disguised as your [PDA.name]. Simply enter the code \"[new_uplink.unlock_code]\" into the ring tone selection to unlock its hidden features." + + unlock_text = "Your Uplink is cunningly disguised as your [uplink_loc.name]. " + if(istype(uplink_loc, /obj/item/modular_computer/pda)) + unlock_text += "Simply enter the code \"[new_uplink.unlock_code]\" into the ring tone selection to unlock its hidden features." add_memory(/datum/memory/key/traitor_uplink, uplink_loc = "PDA", uplink_code = new_uplink.unlock_code) - else if(uplink_loc == P) + + else if(istype(uplink_loc, /obj/item/radio)) + unlock_text += "Simply speak \"[new_uplink.unlock_code]\" into frequency [RADIO_TOKEN_UPLINK] to unlock its hidden features." + add_memory(/datum/memory/key/traitor_uplink, uplink_loc = uplink_loc.name, uplink_code = new_uplink.unlock_code) + + else if(istype(uplink_loc, /obj/item/pen)) var/instructions = english_list(new_uplink.unlock_code) - unlock_text = "Your Uplink is cunningly disguised as your [P.name]. Simply twist the top of the pen [instructions] from its starting position to unlock its hidden features." - add_memory(/datum/memory/key/traitor_uplink, uplink_loc = "PDA pen", uplink_code = instructions) + unlock_text += "Simply twist the top of the pen [instructions] from its starting position to unlock its hidden features." + add_memory(/datum/memory/key/traitor_uplink, uplink_loc = uplink_loc.name, uplink_code = instructions) new_uplink.unlock_text = unlock_text if(!silent) to_chat(traitor_mob, span_boldnotice(unlock_text)) if(antag_datum) antag_datum.antag_memory += new_uplink.unlock_note + "
" + return . /// Link a new mobs mind to the creator of said mob. They will join any team they are currently on, and will only switch teams when their creator does. /datum/mind/proc/enslave_mind_to_creator(mob/living/creator) diff --git a/code/datums/mood.dm b/code/datums/mood.dm index 150220cfbcfdb..c61ceea0faf6b 100644 --- a/code/datums/mood.dm +++ b/code/datums/mood.dm @@ -92,7 +92,6 @@ set_sanity(sanity + 0.4 * seconds_per_tick, SANITY_NEUTRAL, SANITY_MAXIMUM) if(MOOD_LEVEL_HAPPY4) set_sanity(sanity + 0.6 * seconds_per_tick, SANITY_NEUTRAL, SANITY_MAXIMUM) - handle_nutrition() // 0.416% is 15 successes / 3600 seconds. Calculated with 2 minute // mood runtime, so 50% average uptime across the hour. @@ -112,16 +111,18 @@ last_stat = mob_parent.stat /// Handles mood given by nutrition -/datum/mood/proc/handle_nutrition() - if (HAS_TRAIT(mob_parent, TRAIT_NOHUNGER)) - clear_mood_event(MOOD_CATEGORY_NUTRITION) // if you happen to switch species while hungry youre no longer hungy - return FALSE // no moods for nutrition +/datum/mood/proc/update_nutrition_moodlets() + if(HAS_TRAIT(mob_parent, TRAIT_NOHUNGER)) + clear_mood_event(MOOD_CATEGORY_NUTRITION) + return FALSE + + if(HAS_TRAIT(mob_parent, TRAIT_FAT) && !HAS_TRAIT(mob_parent, TRAIT_VORACIOUS)) + add_mood_event(MOOD_CATEGORY_NUTRITION, /datum/mood_event/fat) + return TRUE + switch(mob_parent.nutrition) if(NUTRITION_LEVEL_FULL to INFINITY) - if (!HAS_TRAIT(mob_parent, TRAIT_VORACIOUS)) - add_mood_event(MOOD_CATEGORY_NUTRITION, /datum/mood_event/fat) - else - add_mood_event(MOOD_CATEGORY_NUTRITION, /datum/mood_event/wellfed) // round and full + add_mood_event(MOOD_CATEGORY_NUTRITION, HAS_TRAIT(mob_parent, TRAIT_VORACIOUS) ? /datum/mood_event/wellfed : /datum/mood_event/too_wellfed) if(NUTRITION_LEVEL_WELL_FED to NUTRITION_LEVEL_FULL) add_mood_event(MOOD_CATEGORY_NUTRITION, /datum/mood_event/wellfed) if( NUTRITION_LEVEL_FED to NUTRITION_LEVEL_WELL_FED) @@ -133,6 +134,8 @@ if(0 to NUTRITION_LEVEL_STARVING) add_mood_event(MOOD_CATEGORY_NUTRITION, /datum/mood_event/starving) + return TRUE + /** * Adds a mood event to the mob * diff --git a/code/datums/mood_events/eldritch_painting_events.dm b/code/datums/mood_events/eldritch_painting_events.dm index 7df89104263ba..df801998c1d98 100644 --- a/code/datums/mood_events/eldritch_painting_events.dm +++ b/code/datums/mood_events/eldritch_painting_events.dm @@ -17,13 +17,13 @@ mood_change = 5 timeout = 3 MINUTES -/datum/mood_event/eldritch_painting/weeping_withdrawl +/datum/mood_event/eldritch_painting/weeping_withdrawal description = "My mind is clear from his influence." mood_change = 1 timeout = 3 MINUTES /datum/mood_event/eldritch_painting/desire_heretic - description = "A part gained, the manus takes and gives. What did it take from me?" + description = "A part gained, the mansus takes and gives. What did it take from me?" mood_change = -2 timeout = 3 MINUTES diff --git a/code/datums/mood_events/needs_events.dm b/code/datums/mood_events/needs_events.dm index dd710554d8d97..72d789da1e062 100644 --- a/code/datums/mood_events/needs_events.dm +++ b/code/datums/mood_events/needs_events.dm @@ -3,6 +3,10 @@ description = "I'm so fat..." //muh fatshaming mood_change = -6 +/datum/mood_event/too_wellfed + description = "I think I've eaten too much." + mood_change = 0 + /datum/mood_event/wellfed description = "I'm stuffed!" mood_change = 8 diff --git a/code/datums/mutations/body.dm b/code/datums/mutations/body.dm index ef028e14fb70f..3e72777719f3a 100644 --- a/code/datums/mutations/body.dm +++ b/code/datums/mutations/body.dm @@ -220,7 +220,7 @@ instability = 5 power_coeff = 1 conflicts = list(/datum/mutation/human/glow/anti) - var/glow_power = 2.5 + var/glow_power = 2 var/glow_range = 2.5 var/glow_color var/obj/effect/dummy/lighting_obj/moblight/glow diff --git a/code/datums/mutations/chameleon.dm b/code/datums/mutations/chameleon.dm index 3de361ba5485e..d5cbc36d20a1f 100644 --- a/code/datums/mutations/chameleon.dm +++ b/code/datums/mutations/chameleon.dm @@ -19,6 +19,12 @@ /datum/mutation/human/chameleon/on_life(seconds_per_tick, times_fired) owner.alpha = max(owner.alpha - (12.5 * (GET_MUTATION_POWER(src)) * seconds_per_tick), 0) +//Upgraded mutation of the base variant, used for changelings. No instability and better power_coeff +/datum/mutation/human/chameleon/changeling + instability = 0 + power_coeff = 2.5 + locked = TRUE + /** * Resets the alpha of the host to the chameleon default if they move. * diff --git a/code/datums/records/manifest.dm b/code/datums/records/manifest.dm index 789b8787fe6e1..146f0b858450c 100644 --- a/code/datums/records/manifest.dm +++ b/code/datums/records/manifest.dm @@ -11,10 +11,9 @@ GLOBAL_DATUM_INIT(manifest, /datum/manifest, new) /// Builds the list of crew records for all crew members. /datum/manifest/proc/build() - for(var/i in GLOB.new_player_list) - var/mob/dead/new_player/readied_player = i + for(var/mob/dead/new_player/readied_player as anything in GLOB.new_player_list) if(readied_player.new_character) - log_manifest(readied_player.ckey,readied_player.new_character.mind,readied_player.new_character) + log_manifest(readied_player.ckey, readied_player.new_character.mind, readied_player.new_character) if(ishuman(readied_player.new_character)) inject(readied_player.new_character) CHECK_TICK @@ -104,14 +103,17 @@ GLOBAL_DATUM_INIT(manifest, /datum/manifest, new) if(!(person.mind?.assigned_role.job_flags & JOB_CREW_MANIFEST)) return - var/assignment = person.mind.assigned_role.title + // Attempt to get assignment from ID, otherwise default to mind. + var/obj/item/card/id/id_card = person.get_idcard(hand_first = FALSE) + var/assignment = id_card?.get_trim_assignment() || person.mind.assigned_role.title + var/mutable_appearance/character_appearance = new(person.appearance) var/person_gender = "Other" if(person.gender == "male") person_gender = "Male" if(person.gender == "female") person_gender = "Female" - var/datum/dna/record_dna = new() + var/datum/dna/stored/record_dna = new() person.dna.copy_dna(record_dna) var/datum/record/locked/lockfile = new( diff --git a/code/datums/ruins/icemoon.dm b/code/datums/ruins/icemoon.dm index 5d777858fb3cc..c79d7229ceecf 100644 --- a/code/datums/ruins/icemoon.dm +++ b/code/datums/ruins/icemoon.dm @@ -13,62 +13,62 @@ // above ground only /datum/map_template/ruin/icemoon/gas - name = "Lizard Gas Station" + name = "Ice-Ruin Lizard Gas Station" id = "lizgasruin" description = "A gas station. It appears to have been recently open and is in mint condition." suffix = "icemoon_surface_gas.dmm" /datum/map_template/ruin/icemoon/lust - name = "Ruin of Lust" + name = "Ice-Ruin Ruin of Lust" id = "lust" description = "Not exactly what you expected." suffix = "icemoon_surface_lust.dmm" /datum/map_template/ruin/icemoon/asteroid - name = "Asteroid Site" + name = "Ice-Ruin Asteroid Site" id = "asteroidsite" description = "Surprised to see us here?" suffix = "icemoon_surface_asteroid.dmm" /datum/map_template/ruin/icemoon/engioutpost - name = "Engineer Outpost" + name = "Ice-Ruin Engineer Outpost" id = "engioutpost" description = "Blown up by an unfortunate accident." suffix = "icemoon_surface_engioutpost.dmm" /datum/map_template/ruin/icemoon/fountain - name = "Fountain Hall" + name = "Ice-Ruin Fountain Hall" id = "ice_fountain" description = "The fountain has a warning on the side. DANGER: May have undeclared side effects that only become obvious when implemented." prefix = "_maps/RandomRuins/AnywhereRuins/" suffix = "fountain_hall.dmm" /datum/map_template/ruin/icemoon/abandoned_homestead - name = "Abandoned Homestead" + name = "Ice-Ruin Abandoned Homestead" id = "abandoned_homestead" description = "This homestead was once host to a happy homesteading family. It's now host to hungry bears." suffix = "icemoon_underground_abandoned_homestead.dmm" /datum/map_template/ruin/icemoon/entemology - name = "Insect Research Station" + name = "Ice-Ruin Insect Research Station" id = "bug_habitat" description = "An independently funded research outpost, long abandoned. Their mission, to boldly go where no insect life would ever live, ever, and look for bugs." suffix = "icemoon_surface_bughabitat.dmm" /datum/map_template/ruin/icemoon/pizza - name = "Moffuchi's Pizzeria" + name = "Ice-Ruin Moffuchi's Pizzeria" id = "pizzeria" description = "Moffuchi's Family Pizzeria chain has a reputation for providing affordable artisanal meals of questionable edibility. This particular pizzeria seems to have been abandoned for some time." suffix = "icemoon_surface_pizza.dmm" /datum/map_template/ruin/icemoon/frozen_phonebooth - name = "Frozen Phonebooth" + name = "Ice-Ruin Frozen Phonebooth" id = "frozen_phonebooth" description = "A venture by nanotrasen to help popularize the use of holopads. This one was sent to a icemoon." suffix = "icemoon_surface_phonebooth.dmm" /datum/map_template/ruin/icemoon/smoking_room - name = "Smoking Room" + name = "Ice-Ruin Smoking Room" id = "smoking_room" description = "Here lies Charles Morlbaro. He died the way he lived." suffix = "icemoon_surface_smoking_room.dmm" @@ -76,7 +76,7 @@ // above and below ground together /datum/map_template/ruin/icemoon/mining_site - name = "Mining Site" + name = "Ice-Ruin Mining Site" id = "miningsite" description = "Ruins of a site where people once mined with primitive tools for ore." suffix = "icemoon_surface_mining_site.dmm" @@ -84,7 +84,7 @@ always_spawn_with = list(/datum/map_template/ruin/icemoon/underground/mining_site_below = PLACE_BELOW) /datum/map_template/ruin/icemoon/underground/mining_site_below - name = "Mining Site Underground" + name = "Ice-Ruin Mining Site Underground" id = "miningsite-underground" description = "Who knew ladders could be so useful?" suffix = "icemoon_underground_mining_site.dmm" @@ -94,60 +94,60 @@ // below ground only /datum/map_template/ruin/icemoon/underground - name = "underground ruin" + name = "Ice-Ruin underground ruin" ruin_type = ZTRAIT_ICE_RUINS_UNDERGROUND default_area = /area/icemoon/underground/unexplored /datum/map_template/ruin/icemoon/underground/abandonedvillage - name = "Abandoned Village" + name = "Ice-Ruin Abandoned Village" id = "abandonedvillage" description = "Who knows what lies within?" suffix = "icemoon_underground_abandoned_village.dmm" /datum/map_template/ruin/icemoon/underground/library - name = "Buried Library" + name = "Ice-Ruin Buried Library" id = "buriedlibrary" description = "A once grand library, now lost to the confines of the Ice Moon." suffix = "icemoon_underground_library.dmm" /datum/map_template/ruin/icemoon/underground/wrath - name = "Ruin of Wrath" + name = "Ice-Ruin Ruin of Wrath" id = "wrath" description = "You'll fight and fight and just keep fighting." suffix = "icemoon_underground_wrath.dmm" /datum/map_template/ruin/icemoon/underground/hermit - name = "Frozen Shack" + name = "Ice-Ruin Frozen Shack" id = "hermitshack" description = "A place of shelter for a lone hermit, scraping by to live another day." suffix = "icemoon_underground_hermit.dmm" /datum/map_template/ruin/icemoon/underground/lavaland - name = "Lavaland Site" + name = "Ice-Ruin Lavaland Site" id = "lavalandsite" description = "I guess we never really left you huh?" suffix = "icemoon_underground_lavaland.dmm" /datum/map_template/ruin/icemoon/underground/puzzle - name = "Ancient Puzzle" + name = "Ice-Ruin Ancient Puzzle" id = "puzzle" description = "Mystery to be solved." suffix = "icemoon_underground_puzzle.dmm" /datum/map_template/ruin/icemoon/underground/bathhouse - name = "Bath House" + name = "Ice-Ruin Bath House" id = "bathhouse" description = "A warm, safe place." suffix = "icemoon_underground_bathhouse.dmm" /datum/map_template/ruin/icemoon/underground/wendigo_cave - name = "Wendigo Cave" + name = "Ice-Ruin Wendigo Cave" id = "wendigocave" description = "Into the jaws of the beast." suffix = "icemoon_underground_wendigo_cave.dmm" /datum/map_template/ruin/icemoon/underground/free_golem - name = "Free Golem Ship" + name = "Ice-Ruin Free Golem Ship" id = "golem-ship" description = "Lumbering humanoids, made out of precious metals, move inside this ship. They frequently leave to mine more minerals, which they somehow turn into more of them. \ Seem very intent on research and individual liberty, and also geology-based naming?" @@ -155,33 +155,33 @@ suffix = "golem_ship.dmm" /datum/map_template/ruin/icemoon/underground/mailroom - name = "Frozen-over Post Office" + name = "Ice-Ruin Frozen-over Post Office" id = "mailroom" description = "This is where all of your paychecks went. Signed, the management." suffix = "icemoon_underground_mailroom.dmm" /datum/map_template/ruin/icemoon/underground/frozen_comms - name = "Frozen Communicatons Outpost" + name = "Ice-Ruin Frozen Communicatons Outpost" id = "frozen_comms" description = "3 Peaks Radio, where the 2000's live forever." suffix = "icemoon_underground_frozen_comms.dmm" //TODO: Bottom-Level ONLY Spawns after Refactoring Related Code /datum/map_template/ruin/icemoon/underground/plasma_facility - name = "Abandoned Plasma Facility" + name = "Ice-Ruin Abandoned Plasma Facility" id = "plasma_facility" description = "Rumors have developed over the many years of Freyja plasma mining. These rumors suggest that the ghosts of dead mistreated excavation staff have returned to \ exact revenge on their (now former) employers. Coorperate reminds all staff that rumors are just that: Old Housewife tales meant to scare misbehaving kids to bed." suffix = "icemoon_underground_abandoned_plasma_facility.dmm" /datum/map_template/ruin/icemoon/underground/hotsprings - name = "Hot Springs" + name = "Ice-Ruin Hot Springs" id = "hotsprings" description = "Just relax and take a dip, nothing will go wrong, I swear!" suffix = "icemoon_underground_hotsprings.dmm" /datum/map_template/ruin/icemoon/underground/vent - name = "Icemoon Ore Vent" + name = "Ice-Ruin Icemoon Ore Vent" id = "ore_vent_i" description = "A vent that spews out ore. Seems to be a natural phenomenon." //Make this a subtype that only spawns medium and large vents. Some smalls will go to the top level. suffix = "icemoon_underground_ore_vent.dmm" @@ -191,7 +191,7 @@ always_place = TRUE /datum/map_template/ruin/icemoon/ruin/vent - name = "Surface Icemoon Ore Vent" + name = "Ice-Ruin Surface Icemoon Ore Vent" id = "ore_vent_i" description = "A vent that spews out ore. Seems to be a natural phenomenon. Smaller than the underground ones." suffix = "icemoon_surface_ore_vent.dmm" diff --git a/code/datums/ruins/lavaland.dm b/code/datums/ruins/lavaland.dm index 7627d89cd3f8b..40d7ef49bd4c7 100644 --- a/code/datums/ruins/lavaland.dm +++ b/code/datums/ruins/lavaland.dm @@ -10,27 +10,34 @@ allow_duplicates = FALSE /datum/map_template/ruin/lavaland/biodome/beach - name = "Biodome Beach" + name = "Lava-Ruin Biodome Beach" id = "biodome-beach" description = "Seemingly plucked from a tropical destination, this beach is calm and cool, with the salty waves roaring softly in the background. \ Comes with a rustic wooden bar and suicidal bartender." suffix = "lavaland_biodome_beach.dmm" /datum/map_template/ruin/lavaland/biodome/winter - name = "Biodome Winter" + name = "Lava-Ruin Biodome Winter" id = "biodome-winter" description = "For those getaways where you want to get back to nature, but you don't want to leave the fortified military compound where you spend your days. \ Includes a unique(*) laser pistol display case, and the recently introduced I.C.E(tm)." suffix = "lavaland_surface_biodome_winter.dmm" /datum/map_template/ruin/lavaland/biodome/clown - name = "Biodome Clown Planet" + name = "Lava-Ruin Biodome Clown Planet" id = "biodome-clown" description = "WELCOME TO CLOWN PLANET! HONK HONK HONK etc.!" suffix = "lavaland_biodome_clown_planet.dmm" +/datum/map_template/ruin/lavaland/lizgas + name = "The Lizard's Gas(Lava)" + id = "lizgas2" + description = "A recently opened gas station from the Lizard's Gas franchise." + suffix = "lavaland_surface_gas.dmm" + allow_duplicates = FALSE + /datum/map_template/ruin/lavaland/cube - name = "The Wishgranter Cube" + name = "Lava-Ruin The Wishgranter Cube" id = "wishgranter-cube" description = "Nothing good can come from this. Learn from their mistakes and turn around." suffix = "lavaland_surface_cube.dmm" @@ -38,7 +45,7 @@ allow_duplicates = FALSE /datum/map_template/ruin/lavaland/seed_vault - name = "Seed Vault" + name = "Lava-Ruin Seed Vault" id = "seed-vault" description = "The creators of these vaults were a highly advanced and benevolent race, and launched many into the stars, hoping to aid fledgling civilizations. \ However, all the inhabitants seem to do is grow drugs and guns." @@ -47,7 +54,7 @@ allow_duplicates = FALSE /datum/map_template/ruin/lavaland/ash_walker - name = "Ash Walker Nest" + name = "Lava-Ruin Ash Walker Nest" id = "ash-walker" description = "A race of unbreathing lizards live here, that run faster than a human can, worship a broken dead city, and are capable of reproducing by something involving tentacles? \ Probably best to stay clear." @@ -56,7 +63,7 @@ allow_duplicates = FALSE /datum/map_template/ruin/lavaland/syndicate_base - name = "Syndicate Lava Base" + name = "Lava-Ruin Syndicate Lava Base" id = "lava-base" description = "A secret base researching illegal bioweapons, it is closely guarded by an elite team of syndicate agents." suffix = "lavaland_surface_syndicate_base1.dmm" @@ -64,7 +71,7 @@ allow_duplicates = FALSE /datum/map_template/ruin/lavaland/free_golem - name = "Free Golem Ship" + name = "Lava-Ruin Free Golem Ship" id = "golem-ship" description = "Lumbering humanoids, made out of precious metals, move inside this ship. They frequently leave to mine more minerals, which they somehow turn into more of them. \ Seem very intent on research and individual liberty, and also geology-based naming?" @@ -74,7 +81,7 @@ allow_duplicates = FALSE /datum/map_template/ruin/lavaland/gaia - name = "Patch of Eden" + name = "Lava-Ruin Patch of Eden" id = "gaia" description = "Who would have thought that such a peaceful place could be on such a horrific planet?" cost = 5 @@ -86,32 +93,32 @@ allow_duplicates = FALSE /datum/map_template/ruin/lavaland/sin/envy - name = "Ruin of Envy" + name = "Lava-Ruin Ruin of Envy" id = "envy" description = "When you get what they have, then you'll finally be happy." suffix = "lavaland_surface_envy.dmm" /datum/map_template/ruin/lavaland/sin/gluttony - name = "Ruin of Gluttony" + name = "Lava-Ruin Ruin of Gluttony" id = "gluttony" description = "If you eat enough, then eating will be all that you do." suffix = "lavaland_surface_gluttony.dmm" /datum/map_template/ruin/lavaland/sin/greed - name = "Ruin of Greed" + name = "Lava-Ruin Ruin of Greed" id = "greed" description = "Sure you don't need magical powers, but you WANT them, and \ that's what's important." suffix = "lavaland_surface_greed.dmm" /datum/map_template/ruin/lavaland/sin/pride - name = "Ruin of Pride" + name = "Lava-Ruin Ruin of Pride" id = "pride" description = "Wormhole lifebelts are for LOSERS, whom you are better than." suffix = "lavaland_surface_pride.dmm" /datum/map_template/ruin/lavaland/sin/sloth - name = "Ruin of Sloth" + name = "Lava-Ruin Ruin of Sloth" id = "sloth" description = "..." suffix = "lavaland_surface_sloth.dmm" @@ -119,7 +126,7 @@ cost = 0 /datum/map_template/ruin/lavaland/ratvar - name = "Dead God" + name = "Lava-Ruin Dead God" id = "ratvar" description = "Ratvar's final resting place." suffix = "lavaland_surface_dead_ratvar.dmm" @@ -127,7 +134,7 @@ allow_duplicates = FALSE /datum/map_template/ruin/lavaland/hierophant - name = "Hierophant's Arena" + name = "Lava-Ruin Hierophant's Arena" id = "hierophant" description = "A strange, square chunk of metal of massive size. Inside awaits only death and many, many squares." suffix = "lavaland_surface_hierophant.dmm" @@ -135,7 +142,7 @@ allow_duplicates = FALSE /datum/map_template/ruin/lavaland/blood_drunk_miner - name = "Blood-Drunk Miner" + name = "Lava-Ruin Blood-Drunk Miner" id = "blooddrunk" description = "A strange arrangement of stone tiles and an insane, beastly miner contemplating them." suffix = "lavaland_surface_blooddrunk1.dmm" @@ -143,15 +150,15 @@ allow_duplicates = FALSE //will only spawn one variant of the ruin /datum/map_template/ruin/lavaland/blood_drunk_miner/guidance - name = "Blood-Drunk Miner (Guidance)" + name = "Lava-Ruin Blood-Drunk Miner (Guidance)" suffix = "lavaland_surface_blooddrunk2.dmm" /datum/map_template/ruin/lavaland/blood_drunk_miner/hunter - name = "Blood-Drunk Miner (Hunter)" + name = "Lava-Ruin Blood-Drunk Miner (Hunter)" suffix = "lavaland_surface_blooddrunk3.dmm" /datum/map_template/ruin/lavaland/blood_drunk_miner/random - name = "Blood-Drunk Miner (Random)" + name = "Lava-Ruin Blood-Drunk Miner (Random)" suffix = null always_place = TRUE @@ -160,14 +167,14 @@ return ..() /datum/map_template/ruin/lavaland/ufo_crash - name = "UFO Crash" + name = "Lava-Ruin UFO Crash" id = "ufo-crash" description = "Turns out that keeping your abductees unconscious is really important. Who knew?" suffix = "lavaland_surface_ufo_crash.dmm" cost = 5 /datum/map_template/ruin/lavaland/xeno_nest - name = "Xenomorph Nest" + name = "Lava-Ruin Xenomorph Nest" id = "xeno-nest" description = "These xenomorphs got bored of horrifically slaughtering people on space stations, and have settled down on a nice lava-filled hellscape to focus on what's really important in life. \ Quality memes." @@ -175,7 +182,7 @@ cost = 20 /datum/map_template/ruin/lavaland/fountain - name = "Fountain Hall" + name = "Lava-Ruin Fountain Hall" id = "lava_fountain" description = "The fountain has a warning on the side. DANGER: May have undeclared side effects that only become obvious when implemented." prefix = "_maps/RandomRuins/AnywhereRuins/" @@ -183,14 +190,14 @@ cost = 5 /datum/map_template/ruin/lavaland/survivalcapsule - name = "Survival Capsule Ruins" + name = "Lava-Ruin Survival Capsule Ruins" id = "survivalcapsule" description = "What was once sanctuary to the common miner, is now their tomb." suffix = "lavaland_surface_survivalpod.dmm" cost = 5 /datum/map_template/ruin/lavaland/pizza - name = "Ruined Pizza Party" + name = "Lava-Ruin Ruined Pizza Party" id = "pizza" description = "Little Timmy's birthday pizza bash took a turn for the worse when a bluespace anomaly passed by." suffix = "lavaland_surface_pizzaparty.dmm" @@ -198,7 +205,7 @@ cost = 5 /datum/map_template/ruin/lavaland/cultaltar - name = "Summoning Ritual" + name = "Lava-Ruin Summoning Ritual" id = "cultaltar" description = "A place of vile worship, the scrawling of blood in the middle glowing eerily. A demonic laugh echoes throughout the caverns." suffix = "lavaland_surface_cultaltar.dmm" @@ -206,7 +213,7 @@ cost = 10 /datum/map_template/ruin/lavaland/hermit - name = "Makeshift Shelter" + name = "Lava-Ruin Makeshift Shelter" id = "hermitcave" description = "A place of shelter for a lone hermit, scraping by to live another day." suffix = "lavaland_surface_hermit.dmm" @@ -214,7 +221,7 @@ cost = 10 /datum/map_template/ruin/lavaland/miningripley - name = "Ripley" + name = "Lava-Ruin Ripley" id = "ripley" description = "A heavily-damaged mining ripley, property of a very unfortunate miner. You might have to do a bit of work to fix this thing up." suffix = "lavaland_surface_random_ripley.dmm" @@ -222,14 +229,14 @@ cost = 5 /datum/map_template/ruin/lavaland/dark_wizards - name = "Dark Wizard Altar" + name = "Lava-Ruin Dark Wizard Altar" id = "dark_wizards" description = "A ruin with dark wizards. What secret do they guard?" suffix = "lavaland_surface_wizard.dmm" cost = 5 /datum/map_template/ruin/lavaland/strong_stone - name = "Strong Stone" + name = "Lava-Ruin Strong Stone" id = "strong_stone" description = "A stone that seems particularly powerful." suffix = "lavaland_strong_rock.dmm" @@ -237,14 +244,14 @@ cost = 2 /datum/map_template/ruin/lavaland/puzzle - name = "Ancient Puzzle" + name = "Lava-Ruin Ancient Puzzle" id = "puzzle" description = "Mystery to be solved." suffix = "lavaland_surface_puzzle.dmm" cost = 5 /datum/map_template/ruin/lavaland/elite_tumor - name = "Pulsating Tumor" + name = "Lava-Ruin Pulsating Tumor" id = "tumor" description = "A strange tumor which houses a powerful beast..." suffix = "lavaland_surface_elite_tumor.dmm" @@ -253,7 +260,7 @@ allow_duplicates = TRUE /datum/map_template/ruin/lavaland/elephant_graveyard - name = "Elephant Graveyard" + name = "Lava-Ruin Elephant Graveyard" id = "Graveyard" description = "An abandoned graveyard, calling to those unable to continue." suffix = "lavaland_surface_elephant_graveyard.dmm" @@ -261,7 +268,7 @@ cost = 10 /datum/map_template/ruin/lavaland/bileworm_nest - name = "Bileworm Nest" + name = "Lava-Ruin Bileworm Nest" id = "bileworm_nest" description = "A small sanctuary from the harsh wilderness... if you're a bileworm, that is." cost = 5 @@ -269,7 +276,7 @@ allow_duplicates = FALSE /datum/map_template/ruin/lavaland/lava_phonebooth - name = "Phonebooth" + name = "Lava-Ruin Phonebooth" id = "lava_phonebooth" description = "A venture by nanotrasen to help popularize the use of holopads. This one somehow made its way here." suffix = "lavaland_surface_phonebooth.dmm" @@ -277,7 +284,7 @@ cost = 5 /datum/map_template/ruin/lavaland/battle_site - name = "Battle Site" + name = "Lava-Ruin Battle Site" id = "battle_site" description = "The long past site of a battle between beast and humanoids. The victor is unknown, but the losers are clear." suffix = "lavaland_battle_site.dmm" @@ -285,7 +292,7 @@ cost = 3 /datum/map_template/ruin/lavaland/vent - name = "Ore Vent" + name = "Lava-Ruin Ore Vent" id = "ore_vent" description = "A vent that spews out ore. Seems to be a natural phenomenon." suffix = "lavaland_surface_ore_vent.dmm" @@ -295,7 +302,7 @@ always_place = TRUE /datum/map_template/ruin/lavaland/watcher_grave - name = "Watchers' Grave" + name = "Lava-Ruin Watchers' Grave" id = "watcher-grave" description = "A lonely cave where an orphaned child awaits a new parent." suffix = "lavaland_surface_watcher_grave.dmm" @@ -303,7 +310,7 @@ allow_duplicates = FALSE /datum/map_template/ruin/lavaland/mook_village - name = "Mook Village" + name = "Lava-Ruin Mook Village" id = "mook_village" description = "A village hosting a community of friendly mooks!" suffix = "lavaland_surface_mookvillage.dmm" diff --git a/code/datums/ruins/space.dm b/code/datums/ruins/space.dm index 05c4940dae9f0..76ac14cef5c92 100644 --- a/code/datums/ruins/space.dm +++ b/code/datums/ruins/space.dm @@ -10,65 +10,65 @@ /datum/map_template/ruin/space/zoo id = "zoo" suffix = "abandonedzoo.dmm" - name = "Biological Storage Facility" + name = "Space-Ruin Biological Storage Facility" description = "In case society crumbles, we will be able to restore our zoos to working order with the breeding stock kept in these 100% secure and unbreachable storage facilities. \ At no point has anything escaped. That's our story, and we're sticking to it." /datum/map_template/ruin/space/asteroid1 id = "asteroid1" suffix = "asteroid1.dmm" - name = "Asteroid 1" + name = "Space-Ruin Asteroid 1" description = "I-spy with my little eye, something beginning with R." /datum/map_template/ruin/space/asteroid2 id = "asteroid2" suffix = "asteroid2.dmm" - name = "Asteroid 2" + name = "Space-Ruin Asteroid 2" description = "Oh my god, a giant rock!" /datum/map_template/ruin/space/asteroid3 id = "asteroid3" suffix = "asteroid3.dmm" - name = "Asteroid 3" + name = "Space-Ruin Asteroid 3" description = "This asteroid floating in space has no official designation, because the scientist that discovered it deemed it 'super dull'." /datum/map_template/ruin/space/asteroid4 id = "asteroid4" suffix = "asteroid4.dmm" - name = "Asteroid 4" + name = "Space-Ruin Asteroid 4" description = "Nanotrasen Escape Pods have a 100%* success rate, and a 99%* customer satisfaction rate. \ *Please note that these statistics are taken from pods that have successfully docked with a recovery vessel." /datum/map_template/ruin/space/asteroid5 id = "asteroid5" suffix = "asteroid5.dmm" - name = "Asteroid 5" + name = "Space-Ruin Asteroid 5" description = "Oh my god, another giant rock!" /datum/map_template/ruin/space/asteroid6 id = "asteroid6" suffix = "asteroid6.dmm" - name = "Asteroid 6" + name = "Space-Ruin Asteroid 6" description = "This asteroid has brittle bone disease, so it is fortunate asteroids dont have bones." /datum/map_template/ruin/space/deep_storage id = "deep-storage" suffix = "deepstorage.dmm" - name = "Survivalist Bunker" + name = "Space-Ruin Survivalist Bunker" description = "Assume the best, prepare for the worst. Generally, you should do so by digging a three-man heavily fortified bunker into a giant unused asteroid. \ Then make it self sufficient, mask any evidence of construction, hook it covertly into the telecommunications network and hope for the best." /datum/map_template/ruin/space/bigderelict1 id = "bigderelict1" suffix = "bigderelict1.dmm" - name = "Derelict Tradepost" + name = "Space-Ruin Derelict Tradepost" description = "A once-bustling tradestation that handled imports and exports from nearby stations now lays eerily dormant. \ The last received message was a distress call from one of the on-board officers, but we had no success in making contact again." /datum/map_template/ruin/space/derelict_construction id = "derelict_construction" suffix = "derelict_construction.dmm" - name = "Derelict Construction" + name = "Space-Ruin Derelict Construction" description = "Construction supplies are in high demand due to non-trivial damage routinely sustained by most space stations in this sector. \ Space pirates who dont attempt to rob corporate research stations with only 3 collaborators live long enough to sell captured construction \ equipment back to the highest bidder." @@ -76,14 +76,14 @@ /datum/map_template/ruin/space/derelict_sulaco id = "derelict_sulaco" suffix = "derelict_sulaco.dmm" - name = "Derelict Sulaco" + name = "Space-Ruin Derelict Sulaco" description = "Nothing to see here citizen, move along, certainly no xeno outbreaks here. That purple stuff? It's uh... space nectar... but don't eat it! \ It's the bridge of a top secret military ship." /datum/map_template/ruin/space/derelict2 id = "derelict2" suffix = "derelict2.dmm" - name = "Dinner for Two" + name = "Space-Ruin Dinner for Two" description = "Oh this is the night\n\ It's a beautiful night\n\ And we call it bella notte" @@ -91,161 +91,161 @@ /datum/map_template/ruin/space/derelict3 id = "derelict3" suffix = "derelict3.dmm" - name = "Derelict 3" + name = "Space-Ruin Derelict 3" description = "These hulks were once part of a larger structure, where the three great \[REDACTED\] were forged." /datum/map_template/ruin/space/derelict4 id = "derelict4" suffix = "derelict4.dmm" - name = "Derelict 4" + name = "Space-Ruin Derelict 4" description = "CentCom ferries have never crashed, will never crash, there is no current investigation into a crashed ferry, and we will not let Internal Affairs trample over high security \ information in the name of this baseless witchhunt." /datum/map_template/ruin/space/derelict5 id = "derelict5" suffix = "derelict5.dmm" - name = "Derelict 5" + name = "Space-Ruin Derelict 5" description = "The plan is, we put a whole bunch of crates full of treasure in this disused warehouse, launch it into space, and then ignore it. Forever." /datum/map_template/ruin/space/derelict6 id = "derelict6" suffix = "derelict6.dmm" - name = "Derelict 6" + name = "Space-Ruin Derelict 6" description = "The hush-hush of Nanotrasen when it comes to stations seemingly vanishing off the radar is an interesting topic, theories of nuclear destruction float about while Nanotrasen \ flat-out denies said stations ever existing." /datum/map_template/ruin/space/derelict7 id = "derelict7" suffix = "derelict7.dmm" - name = "Derelict 7" + name = "Space-Ruin Derelict 7" description = "The official report says there was a 'huge explosion' which was 'radical' and 'tubular'. Nothing is said about the explosion's cause." /datum/map_template/ruin/space/derelict8 id = "derelict8" suffix = "derelict8.dmm" - name = "Derelict 8" + name = "Space-Ruin Derelict 8" description = "An auxiliary storage bay might be the least respected room on any functional station, but studies show they are the least likely to be hit in an artillery strike." /datum/map_template/ruin/space/derelict9 id = "derelict9" suffix = "derelict9.dmm" - name = "Derelict 9" + name = "Space-Ruin Derelict 9" description = "Someone already found this high-security supply cache already, but were unable to get inside. Perhaps the next visitor will have more luck." /datum/map_template/ruin/space/empty_shell id = "empty-shell" suffix = "emptyshell.dmm" - name = "Empty Shell" + name = "Space-Ruin Empty Shell" description = "Cosy, rural property available for young professional couple. Only twelve parsecs from the nearest hyperspace lane!" /datum/map_template/ruin/space/the_lizards_gas id = "the-lizards-gas" suffix = "thelizardsgas.dmm" - name = "The Lizard's Gas" + name = "Space-Ruin The Lizard's Gas" description = "A refueling station stocked with enough plasma for any space-worthy vessel. Well, maybe if it weren't 50 years ago." /datum/map_template/ruin/space/intact_empty_ship id = "intact-empty-ship" suffix = "intactemptyship.dmm" - name = "Authorship" + name = "Space-Ruin Authorship" description = "Just somewhere quiet, where I can focus on my work with no interruptions." /datum/map_template/ruin/space/caravanambush id = "caravanambush" suffix = "caravanambush.dmm" - name = "Syndicate Ambush" + name = "Space-Ruin Syndicate Ambush" description = "A caravan route used by passing cargo freights has been ambushed by a salvage team manned by the syndicate. \ The caravan managed to send off a distress message before being surrounded, their video feed cutting off as the sound of gunfire and a parrot was heard." /datum/map_template/ruin/space/originalcontent id = "paperwizard" suffix = "originalcontent.dmm" - name = "A Giant Ball of Paper in Space" + name = "Space-Ruin A Giant Ball of Paper in Space" description = "Sightings of a giant wad of paper hurling through the depths of space have been recently reported by multiple outposts near this sector. \ A giant wad of paper, really? Damn prank callers." /datum/map_template/ruin/space/mech_transport id = "mech-transport" suffix = "mechtransport.dmm" - name = "CF Corsair" + name = "Space-Ruin CF Corsair" description = "Well, when is it getting here? I have bills to pay; very well-armed clients who want their shipments as soon as possible! I don't care, just find it!" /datum/map_template/ruin/space/onehalf id = "onehalf" suffix = "onehalf.dmm" - name = "DK Excavator 453" + name = "Space-Ruin DK Excavator 453" description = "Based on the trace elements we've detected on the gutted asteroids, we suspect that a mining ship using a restricted engine is somewhere in the area. \ We'd like to request a patrol vessel to investigate." /datum/map_template/ruin/space/spacehotel id = "spacehotel" suffix = "spacehotel.dmm" - name = "The Twin-Nexus Hotel" + name = "Space-Ruin The Twin-Nexus Hotel" description = "An interstellar hotel, where the weary spaceman can rest their head and relax, assured that the residental staff will not murder them in their sleep. Probably." /datum/map_template/ruin/space/turreted_outpost id = "turreted-outpost" suffix = "turretedoutpost.dmm" - name = "Unnamed Turreted Outpost" + name = "Space-Ruin Unnamed Turreted Outpost" description = "We'd ask them to stop blaring that ruskiepop music, but none of us are brave enough to go near those death turrets they have." /datum/map_template/ruin/space/oldshuttle id = "spaceman-origins" suffix = "shuttlerelic.dmm" - name = "Strange Ship" + name = "Space-Ruin Strange Ship" description = "A ship seemingly lost, drifting along the stars. This thing looks like it belongs in ancient times." /datum/map_template/ruin/space/way_home id = "way-home" suffix = "way_home.dmm" - name = "Salvation" + name = "Space-Ruin Salvation" description = "In the darkest times, we will find our way home." /datum/map_template/ruin/space/djstation id = "djstation" suffix = "dj_station.dmm" - name = "DJ Station" + name = "Space-Ruin DJ Station" description = "Until very recently this pirate radio station was used to harangue local space stations over a variety of perceived \"ethics violations\". \ It seems like someone finally got sick of it, but the equipment still works." /datum/map_template/ruin/space/thederelict id = "thederelict" suffix = "russian_derelict.dmm" - name = "Kosmicheskaya Stantsiya 13" + name = "Space-Ruin Kosmicheskaya Stantsiya 13" description = "The true fate of Kosmicheskaya Stantsiya 13 is an open question to this day. Most corporations deny its existence, for fear of questioning on what became of its crew." /datum/map_template/ruin/space/abandonedteleporter id = "abandonedteleporter" suffix = "abandonedteleporter.dmm" - name = "Abandoned Teleporter" + name = "Space-Ruin Abandoned Teleporter" description = "In space construction the teleporter is often the first system brought online. \ This lonely, half-built teleporter is a sign of a proposed structure that for one reason or another just never got built." /datum/map_template/ruin/space/crashedclownship id = "crashedclownship" suffix = "crashedclownship.dmm" - name = "Crashed Clown Ship" + name = "Space-Ruin Crashed Clown Ship" description = "For centuries the promise of a new clown homeworld has been the siren call for countless clown vessels. \ Alas, the clown's lust for shenanigans means that successful voyages are almost unheard of, with most vessels falling to hilarious consequences almost immediately." /datum/map_template/ruin/space/crashedship id = "crashedship" suffix = "crashedship.dmm" - name = "Crashed Ship" + name = "Space-Ruin Crashed Ship" description = "The SSCV Atrus was chartered to survey over 600 planets in its maiden voyage. \ Hopefully the SSC is content with an indepth analysis of just this asteroid." /datum/map_template/ruin/space/listeningstation id = "listeningstation" suffix = "listeningstation.dmm" - name = "Syndicate Listening Station" + name = "Space-Ruin Syndicate Listening Station" description = "Listening stations form the backbone of the syndicate's information-gathering operations. \ Assignment to these stations is dreaded by most agents, as it entails long and lonely shifts listening to nearby stations chatter incessantly about the most meaningless things." /datum/map_template/ruin/space/old_ai_sat id = "oldAIsat" suffix = "oldAIsat.dmm" - name = "Abandoned Telecommunications Satellite" + name = "Space-Ruin Abandoned Telecommunications Satellite" description = "When the inspector told the employees that they were all fired, and that their jobs \"could be done by trained lizards anyway\", they reacted badly. \ This event and others is the reason why Central always sends an ERT squad with their competent inspectors. Incompetent inspectors are told they can \"do it alone\" because they're \"that pro\". \ Incompetent inspectors believe this." @@ -253,258 +253,258 @@ /datum/map_template/ruin/space/oldteleporter id = "oldteleporter" suffix = "oldteleporter.dmm" - name = "Detached Teleporter" + name = "Space-Ruin Detached Teleporter" description = "The structure of this surprisingly intact teleporter suggests that it was once part of a larger structure, but what remains of said structure, if anything, can only be guessed at." /datum/map_template/ruin/space/vaporwave id = "vaporwave" suffix = "vaporwave.dmm" - name = "Aesthetic Outpost" + name = "Space-Ruin Aesthetic Outpost" description = "Pause and remember-- You are unique.You are special. Every mistake, trial, and hardship has helped to sculpt your real beauty. \ Stop hating yourself and start appreciating and loving yourself!" /datum/map_template/ruin/space/bus id = "bus" suffix = "bus.dmm" - name = "Waylaid Buses" + name = "Space-Ruin Waylaid Buses" description = "There seems to be a pair of buses that pulled over for repairs. What were they doing...? Their shipment sure seems to be filled with a strange mix. \ Anyway, it looks like some people tried to fix it up for a long time but didn't really get anywhere..." /datum/map_template/ruin/space/oldstation id = "oldstation" suffix = "oldstation.dmm" - name = "Ancient Space Station" + name = "Space-Ruin Ancient Space Station" description = "The crew of a space station awaken one hundred years after a crisis. Awaking to a derelict space station on the verge of collapse, and a hostile force of invading \ hivebots. Can the surviving crew overcome the odds and survive and rebuild, or will the cold embrace of the stars become their new home?" /datum/map_template/ruin/space/gondoland id = "gondolaasteroid" suffix = "gondolaasteroid.dmm" - name = "Gondoland" + name = "Space-Ruin Gondoland" description = "Just an ordinary rock- wait, what's that thing?" /datum/map_template/ruin/space/whiteshipruin_box id = "whiteshipruin_box" suffix = "whiteshipruin_box.dmm" - name = "NT Medical Ship" + name = "Space-Ruin NT Medical Ship" description = "An ancient ship, said to be among the first discovered derelicts near Space Station 13 that was still in working order. \ Aged and deprecated by time, this relic of a vessel is now broken beyond repair." /datum/map_template/ruin/space/whiteshipdock id = "whiteshipdock" suffix = "whiteshipdock.dmm" - name = "Whiteship Dock" + name = "Space-Ruin Whiteship Dock" description = "An abandoned but functional vessel parked in deep space, ripe for the taking." /datum/map_template/ruin/space/cat_experiments id = "meow" suffix = "mrow_thats_right.dmm" - name = "Feline-Human Combination Den" + name = "Space-Ruin Feline-Human Combination Den" description = "With heated debates over the legality of the catperson and their status in the workforce, there's always a place for the blackmarket to slip in for some cash. Whether the results \ are morally sound or not is another issue entirely." /datum/map_template/ruin/space/hilbertresearchfacility id = "hilbert_facility" suffix = "hilbertresearchfacility.dmm" - name = "Hilbert Research Facility" + name = "Space-Ruin Hilbert Research Facility" description = "A research facility of great bluespace discoveries. Long since abandoned, willingly or not..." /datum/map_template/ruin/space/clownplanet id = "clownplanet" suffix = "clownplanet.dmm" - name = "Clown Planet" + name = "Space-Ruin Clown Planet" description = "Thought lost in 2552, this minor planet has recently been rediscovered." /datum/map_template/ruin/space/clericden id = "clericden" suffix = "clericden.dmm" - name = "Cleric's Den" + name = "Space-Ruin Cleric's Den" description = "Once part of a larger monastery, this holy order of long dead clerics practiced far less non-violence than they preached. Appears to have been untouched by looters, however. Odd." /datum/map_template/ruin/space/forgottenship id = "forgottenship" suffix = "forgottenship.dmm" - name = "Syndicate Forgotten Ship" + name = "Space-Ruin Syndicate Forgotten Ship" description = "Seemingly abandoned ship went of course right into NT controlled space. It seems that malfunction caused most systems to turn off, except for sleepers." /datum/map_template/ruin/space/old_syndie_infiltrator id = "old_infiltrator" suffix = "old_infiltrator.dmm" - name = "Abandoned Infiltrator" + name = "Space-Ruin Abandoned Infiltrator" description = "Only one in five Gorlex Marauder strike forces return from their regular raids into Nanotrasen space. \ For the other four... well, their ship doesn't just disappear when their target evacuates." /datum/map_template/ruin/space/hellfactory id = "hellfactory" suffix = "hellfactory.dmm" - name = "Heck Brewery" + name = "Space-Ruin Heck Brewery" description = "An abandoned warehouse and brewing facility, which has been recently rediscovered. Reports claim that the security system entered an ultra-hard lockdown, but these reports are inconclusive." /datum/map_template/ruin/space/space_billboard id = "space_billboard" suffix = "space_billboard.dmm" - name = "Space Billboard" + name = "Space-Ruin Space Billboard" description = "Frequently found alongside well-traversed sublight routes, space billboards have fallen out of favour in recent years as advertisers finally realised that people are incapable of reading billboards going by at over 2/3rds the speed of light." /datum/map_template/ruin/space/spinwardsmoothies id = "spinwardsmoothies" suffix = "spinwardsmoothies.dmm" - name = "Spinward Smoothies" + name = "Space-Ruin Spinward Smoothies" description = "A branch of the beloved Spinward Smoothies chain of smoothie bars." /datum/map_template/ruin/space/cyborg_mothership id = "cyborg_mothership" suffix = "cyborg_mothership.dmm" - name = "Cyborg Mothership" + name = "Space-Ruin Cyborg Mothership" description = "An abandoned cyborg mothership that was overtaken by space vines and hivebots. It appears that it hosted an experimental AI focused on mining before it was depowered." /datum/map_template/ruin/space/dangerous_research id = "dangerous_research" suffix = "dangerous_research.dmm" - name = "Alternate Sciences Research Center" + name = "Space-Ruin Alternate Sciences Research Center" description = "When you're messing with the occult, who knows what you're going to get?" /datum/map_template/ruin/space/anomaly_research id = "anomaly_research" suffix = "anomaly_research.dmm" - name = "Anomaly Research" + name = "Space-Ruin Anomaly Research" description = "A secret research lab embedded in arctic rock, belonging to a Dr Anna Molly. What could she have been researching?" /datum/map_template/ruin/space/meateor id = "meateor" suffix = "meateor.dmm" - name = "Meateor" + name = "Space-Ruin Meateor" description = "A big chunk of meat floating in space. How did it get there?" /datum/map_template/ruin/space/the_faceoff id = "the_faceoff" suffix = "the_faceoff.dmm" - name = "The Faceoff" + name = "Space-Ruin The Faceoff" description = "What do you get when a meeting of the enemy corporations get crashed?" /datum/map_template/ruin/space/meatstation id = "meatderelict" suffix = "meatderelict.dmm" - name = "Bioresearch Outpost" + name = "Space-Ruin Bioresearch Outpost" description = "A bioresearch experiment gone wrong." /datum/map_template/ruin/space/ghost_restaurant id = "space_ghost_restaurant.dmm" suffix = "space_ghost_restaurant.dmm" - name = "Space Ghost Restaurant" + name = "Space-Ruin Space Ghost Restaurant" description = "Ever wondered where the restaurant robots come from? On this ruined station, NTgrub interns dressed up robots in clothes, and sent them to stations to cook their meal orders for them." /datum/map_template/ruin/space/atmosasteroidruin id = "atmosasteroidruin" suffix = "atmosasteroidruin.dmm" - name = "Atmos Asteroid" + name = "Space-Ruin Atmos Asteroid" description = "A dead atmos tech in a continuously pressurizing ruin." /datum/map_template/ruin/space/massdriverrouter id = "fasttravel" suffix = "fasttravel.dmm" - name = "Mass driver Router" + name = "Space-Ruin Mass driver Router" description = "An old, still functional router for some long destroyed system." /datum/map_template/ruin/space/prey_pod id = "prey" suffix = "prey_pod.dmm" - name = "Crashed Mimic Escape Pod" + name = "Space-Ruin Crashed Mimic Escape Pod" description = "A pod with a person who has died to a mimic." /datum/map_template/ruin/space/travelers_rest id = "travelers_rest" suffix = "travelers_rest.dmm" - name = "Traveler's Rest" + name = "Space-Ruin Traveler's Rest" description = "An abandoned capsule floating through space. It seems as if somebody was in here not too long ago." /datum/map_template/ruin/space/prison_shuttle id = "prison_shuttle" suffix = "prison_shuttle.dmm" - name = "Crashed Prisoner Shuttle" + name = "Space-Ruin Crashed Prisoner Shuttle" description = "A prisoner transport shuttle that had crashed into a stray asteroid long ago." /datum/map_template/ruin/space/botanical_haven id = "botanical_haven" suffix = "botanical_haven.dmm" - name = "Botanical Haven" + name = "Space-Ruin Botanical Haven" description = "A small sanctuary for plants and botanists, hidden away in a rusted structure." /datum/map_template/ruin/space/pod_crash id = "pod_crash" suffix = "pod_crash.dmm" - name = "Pod Crash" + name = "Space-Ruin Pod Crash" description = "A tragic display of what happens to drivers who pda and pod." /datum/map_template/ruin/space/interdyne id = "interdyne" suffix = "interdyne.dmm" - name = "Interdyne Spinward Research Base" + name = "Space-Ruin Interdyne Spinward Research Base" description = "An Interdyne facility abandoned due to the accidental discovery of Romerol" /datum/map_template/ruin/space/waystation id = "waystation" suffix = "waystation.dmm" - name = "Waystation" + name = "Space-Ruin Waystation" description = "A waytation for a backwater subsector of Spinward gets attacked by the syndicate due to bad luck." /datum/map_template/ruin/space/allamericandiner id = "allamericandiner" suffix = "allamericandiner.dmm" - name = "The All-American Diner" + name = "Space-Ruin The All-American Diner" description = "A mothballed \"Restaurant\" station of the popular \"The All-American Diner\" franchise." /datum/map_template/ruin/space/mimesvclowns id = "mimesvclowns" suffix = "mimesvsclowns.dmm" - name = "Abandoned Mime Outpost" + name = "Space-Ruin Abandoned Mime Outpost" description = "When you fight mimes, you better bring more than slips." /datum/map_template/ruin/space/transit_booth id = "transit_booth" suffix = "transit_booth.dmm" - name = "Transit Booth" + name = "Space-Ruin Transit Booth" description = "Make sure to check out the duty-free store!" /datum/map_template/ruin/space/space_phonebooth id = "Space_phonebooth" suffix = "phonebooth.dmm" - name = "Space Phonebooth" + name = "Space-Ruin Phonebooth" description = "A venture by nanotrasen to help popularize the use of holopads." /datum/map_template/ruin/space/the_outlet id = "the_outlet" suffix = "the_outlet.dmm" - name = "calebs krazy clothing outlet" + name = "Space-Ruin calebs krazy clothing outlet" description = "A decrepit clothing store built into an asteroid. It appears long since abandoned and has fallen into disrepair." /datum/map_template/ruin/space/infested_frigate id = "infested_frigate" suffix = "infested_frigate.dmm" - name = "SYN-C Brutus" + name = "Space-Ruin SYN-C Brutus" description = "This wasn't an outbreak, this was a repelled attack." /datum/map_template/ruin/space/garbagetruck1 id = "garbagetruck1" suffix = "garbagetruck1.dmm" - name = "Decommissioned Garbage Truck NX1" + name = "Space-Ruin Decommissioned Garbage Truck NX1" description = "An NX-760 interstellar transport barge. At the end of their life cycle, they are often filled with trash and launched into unexplored space to become someone else's problem. This one is full of kitchen waste, and rodents." /datum/map_template/ruin/space/garbagetruck2 id = "garbagetruck2" suffix = "garbagetruck2.dmm" - name = "Decommissioned Garbage Truck NX2" + name = "Space-Ruin Decommissioned Garbage Truck NX2" description = "An NX-760 interstellar transport barge. At the end of their life cycle, they are often filled with trash and launched into unexplored space to become someone else's problem. This one is full of medical waste, and a syndicate agent." /datum/map_template/ruin/space/garbagetruck3 id = "garbagetruck3" suffix = "garbagetruck3.dmm" - name = "Decommissioned Garbage Truck NX3" + name = "Space-Ruin Decommissioned Garbage Truck NX3" description = "An NX-760 interstellar transport barge. At the end of their life cycle, they are often filled with trash and launched into unexplored space to become someone else's problem. This one is full of industrial garbage, and a russian drug den." /datum/map_template/ruin/space/garbagetruck4 id = "garbagetruck4" suffix = "garbagetruck4.dmm" - name = "Decommissioned Garbage Truck NX4" + name = "Space-Ruin Decommissioned Garbage Truck NX4" description = "An NX-760 interstellar transport barge. At the end of their life cycle, they are often filled with trash and launched into unexplored space to become someone else's problem. This one is full of commercial trash, and spiders." diff --git a/code/datums/skills/fishing.dm b/code/datums/skills/fishing.dm index ddf90e1a0a3ac..cfd14a4ce3ba6 100644 --- a/code/datums/skills/fishing.dm +++ b/code/datums/skills/fishing.dm @@ -4,17 +4,20 @@ */ /datum/skill/fishing name = "Fishing" - title = "Fisher" + title = "Angler" desc = "How empty and alone you are on this barren Earth." modifiers = list(SKILL_VALUE_MODIFIER = list(1, 1, 0, -1, -2, -4, -6)) skill_item_path = /obj/item/clothing/head/soft/fishing_hat /datum/skill/fishing/New() . = ..() - levelUpMessages[SKILL_LEVEL_MASTER] = span_nicegreen("After lots of practice, I've begun to truly understand the surprising depth behind [name]. As a master [title], I can take an easier guess of what I'm trying to catch now.") + levelUpMessages[SKILL_LEVEL_JOURNEYMAN] = span_nicegreen("I feel like I've become quite proficient at [name]! I can tell what fishes I can catch at any given fishing spot.") + levelUpMessages[SKILL_LEVEL_MASTER] = span_nicegreen("I've begun to truly understand the surprising depth behind [name]. As a master [title], I can guess what I'm going to catch now!") /datum/skill/fishing/level_gained(datum/mind/mind, new_level, old_level, silent) . = ..() + if(new_level >= SKILL_LEVEL_JOURNEYMAN && old_level < SKILL_LEVEL_JOURNEYMAN) + ADD_TRAIT(mind, TRAIT_EXAMINE_FISHING_SPOT, SKILL_TRAIT) if(new_level >= SKILL_LEVEL_MASTER && old_level < SKILL_LEVEL_MASTER) ADD_TRAIT(mind, TRAIT_REVEAL_FISH, SKILL_TRAIT) @@ -22,3 +25,5 @@ . = ..() if(old_level >= SKILL_LEVEL_MASTER && new_level < SKILL_LEVEL_MASTER) REMOVE_TRAIT(mind, TRAIT_REVEAL_FISH, SKILL_TRAIT) + if(old_level >= SKILL_LEVEL_JOURNEYMAN && new_level < SKILL_LEVEL_JOURNEYMAN) + REMOVE_TRAIT(mind, TRAIT_EXAMINE_FISHING_SPOT, SKILL_TRAIT) diff --git a/code/datums/skills/fitness.dm b/code/datums/skills/fitness.dm index c306548303e22..32be3f9d21174 100644 --- a/code/datums/skills/fitness.dm +++ b/code/datums/skills/fitness.dm @@ -1,6 +1,6 @@ /datum/skill/fitness name = "Fitness" - title = "Fitness" + title = "Powerlifter" desc = "Twinkle twinkle little star, hit the gym and lift the bar." /// The skill value modifier effects the max duration that is possible for /datum/status_effect/exercised modifiers = list(SKILL_VALUE_MODIFIER = list(1 MINUTES, 1.5 MINUTES, 2 MINUTES, 2.5 MINUTES, 3 MINUTES, 3.5 MINUTES, 5 MINUTES)) diff --git a/code/datums/sprite_accessories.dm b/code/datums/sprite_accessories.dm index 2f433b8e340fb..f5af223672d63 100644 --- a/code/datums/sprite_accessories.dm +++ b/code/datums/sprite_accessories.dm @@ -2249,6 +2249,10 @@ name = "Moffra" icon_state = "moffra" +/datum/sprite_accessory/moth_wings/lightbearer + name = "Lightbearer" + icon_state = "lightbearer" + /datum/sprite_accessory/moth_antennae //Finally splitting the sprite icon = 'icons/mob/human/species/moth/moth_antennae.dmi' color_src = null @@ -2336,6 +2340,10 @@ name = "Moffra" icon_state = "moffra" +/datum/sprite_accessory/moth_antennae/lightbearer + name = "Lightbearer" + icon_state = "lightbearer" + /datum/sprite_accessory/moth_markings // the markings that moths can have. finally something other than the boring tan icon = 'icons/mob/human/species/moth/moth_markings.dmi' color_src = null @@ -2399,3 +2407,7 @@ /datum/sprite_accessory/moth_markings/witchwing name = "Witch Wing" icon_state = "witchwing" + +/datum/sprite_accessory/moth_markings/lightbearer + name = "Lightbearer" + icon_state = "lightbearer" diff --git a/code/datums/station_traits/job_traits.dm b/code/datums/station_traits/job_traits.dm index 041f846424094..42e5f680786b3 100644 --- a/code/datums/station_traits/job_traits.dm +++ b/code/datums/station_traits/job_traits.dm @@ -60,21 +60,20 @@ for (var/mob/dead/new_player/signee as anything in lobby_candidates) if (isnull(signee) || !signee.client || !signee.mind || signee.ready != PLAYER_READY_TO_PLAY) LAZYREMOVE(lobby_candidates, signee) - if (!LAZYLEN(lobby_candidates)) - on_failed_assignment() - return // Nobody signed up :( - for(var/_ in 1 to position_amount) - var/mob/dead/new_player/picked_player = pick_n_take(lobby_candidates) - picked_player.mind.assigned_role = new job_to_add() - lobby_candidates = null -/// Called if we didn't assign a role before the round began, we add it to the latejoin menu instead -/datum/station_trait/job/proc/on_failed_assignment() - var/datum/job/our_job = SSjob.GetJob(job_to_add::title) + var/datum/job/our_job = SSjob.GetJobType(job_to_add) our_job.total_positions = position_amount + our_job.spawn_positions = position_amount + while(length(lobby_candidates) && position_amount > 0) + var/mob/dead/new_player/picked_player = pick_n_take(lobby_candidates) + picked_player.mind.set_assigned_role(our_job) + our_job.current_positions++ + position_amount-- + + lobby_candidates = null /datum/station_trait/job/can_display_lobby_button(client/player) - var/datum/job/our_job = SSjob.GetJob(job_to_add::title) + var/datum/job/our_job = SSjob.GetJobType(job_to_add) return our_job.player_old_enough(player) && ..() /// Adds a gorilla to the cargo department, replacing the sloth and the mech @@ -177,6 +176,74 @@ . = ..() overlays += "veteran_advisor" +/datum/station_trait/job/human_ai + name = "Human AI" + button_desc = "Sign up to become the \"AI\"." + weight = 1 + trait_flags = parent_type::trait_flags | STATION_TRAIT_REQUIRES_AI + report_message = "Our recent technological advancements in machine Artificial Intelligence has proven futile. In the meantime, we're sending an Intern to help out." + show_in_report = TRUE + can_roll_antag = CAN_ROLL_PROTECTED + job_to_add = /datum/job/human_ai + trait_to_give = STATION_TRAIT_HUMAN_AI + +/datum/station_trait/job/human_ai/New() + . = ..() + RegisterSignal(SSjob, COMSIG_OCCUPATIONS_SETUP, PROC_REF(remove_ai_job)) + RegisterSignal(SSatoms, COMSIG_SUBSYSTEM_POST_INITIALIZE, PROC_REF(give_fax_machine)) + +/datum/station_trait/job/human_ai/revert() + UnregisterSignal(SSjob, COMSIG_OCCUPATIONS_SETUP) + UnregisterSignal(SSatoms, COMSIG_SUBSYSTEM_POST_INITIALIZE) + return ..() + +/datum/station_trait/job/human_ai/on_lobby_button_update_overlays(atom/movable/screen/lobby/button/sign_up/lobby_button, list/overlays) + . = ..() + overlays += LAZYFIND(lobby_candidates, lobby_button.get_mob()) ? "human_ai_on" : "human_ai_off" + +/datum/station_trait/job/human_ai/proc/remove_ai_job(datum/source) + SIGNAL_HANDLER + var/datum/job_department/department = SSjob.joinable_departments_by_type[/datum/job_department/silicon] + department.remove_job(/datum/job/ai) + var/datum/station_trait/triple_ai/triple_ais = locate() in SSstation.station_traits + if(triple_ais) + position_amount = 3 + +/// Gives the AI SAT a fax machine if it doesn't have one. This is copy pasted from Bridge Assistant's coffee maker. +/datum/station_trait/job/human_ai/proc/give_fax_machine(datum/source) + SIGNAL_HANDLER + var/area/sat_area = GLOB.areas_by_type[/area/station/ai_monitored/turret_protected/ai] + if(isnull(sat_area)) + return + var/list/fax_machines = SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/fax) + for(var/obj/machinery/fax_machine as anything in fax_machines) //don't spawn a fax machine if one exists already. + if(is_type_in_list(get_area(fax_machine), sat_area)) + return + var/list/tables = list() + for(var/turf/area_turf as anything in sat_area.get_turfs_from_all_zlevels()) + var/obj/structure/table/table = locate() in area_turf + if(isnull(table)) + continue + if(area_turf.is_blocked_turf(ignore_atoms = list(table))) + continue + tables += table + if(!length(tables)) + return + var/picked_table = pick_n_take(tables) + var/picked_turf = get_turf(picked_table) + for(var/obj/thing_on_table in picked_turf) //if there's paper bins or other shit on the table, get that off + if(thing_on_table == picked_table) + continue + if(HAS_TRAIT(thing_on_table, TRAIT_WALLMOUNTED) || (thing_on_table.flags_1 & ON_BORDER_1) || thing_on_table.layer < TABLE_LAYER) + continue + if(thing_on_table.invisibility || !thing_on_table.alpha || !thing_on_table.mouse_opacity) + continue + if(length(tables)) + thing_on_table.forceMove(get_turf(pick(tables))) + else + qdel(thing_on_table) + new /obj/machinery/fax/auto_name(picked_turf) + #undef CAN_ROLL_ALWAYS #undef CAN_ROLL_PROTECTED #undef CAN_ROLL_NEVER diff --git a/code/datums/station_traits/neutral_traits.dm b/code/datums/station_traits/neutral_traits.dm index 045d7ee146078..4cb15c6b07e76 100644 --- a/code/datums/station_traits/neutral_traits.dm +++ b/code/datums/station_traits/neutral_traits.dm @@ -29,6 +29,7 @@ /datum/station_trait/unique_ai name = "Unique AI" trait_type = STATION_TRAIT_NEUTRAL + trait_flags = parent_type::trait_flags | STATION_TRAIT_REQUIRES_AI weight = 5 show_in_report = TRUE report_message = "For experimental purposes, this station AI might show divergence from default lawset. Do not meddle with this experiment, we've removed \ @@ -365,23 +366,31 @@ /datum/station_trait/triple_ai name = "AI Triumvirate" trait_type = STATION_TRAIT_NEUTRAL + trait_flags = parent_type::trait_flags | STATION_TRAIT_REQUIRES_AI show_in_report = TRUE weight = 1 report_message = "Your station has been instated with three Nanotrasen Artificial Intelligence models." /datum/station_trait/triple_ai/New() . = ..() - RegisterSignal(SSjob, COMSIG_OCCUPATIONS_DIVIDED, PROC_REF(on_occupations_divided)) + RegisterSignal(SSjob, COMSIG_OCCUPATIONS_SETUP, PROC_REF(on_occupations_setup)) /datum/station_trait/triple_ai/revert() - UnregisterSignal(SSjob, COMSIG_OCCUPATIONS_DIVIDED) + UnregisterSignal(SSjob, COMSIG_OCCUPATIONS_SETUP) return ..() -/datum/station_trait/triple_ai/proc/on_occupations_divided(datum/source, pure, allow_all) +/datum/station_trait/triple_ai/proc/on_occupations_setup(datum/controller/subsystem/job/source) SIGNAL_HANDLER + //allows for latejoining AIs + for(var/obj/effect/landmark/start/ai/secondary/secondary_ai_spawn in GLOB.start_landmarks_list) + secondary_ai_spawn.latejoin_active = TRUE + + var/datum/station_trait/job/human_ai/ai_trait = locate() in SSstation.station_traits + //human AI quirk will handle adding its own job positions, but for now don't allow more AI slots. + if(ai_trait) + return for(var/datum/job/ai/ai_datum in SSjob.joinable_occupations) ai_datum.spawn_positions = 3 - if(!pure) - for(var/obj/effect/landmark/start/ai/secondary/secondary_ai_spawn in GLOB.start_landmarks_list) - secondary_ai_spawn.latejoin_active = TRUE + ai_datum.total_positions = 3 + diff --git a/code/datums/station_traits/positive_traits.dm b/code/datums/station_traits/positive_traits.dm index 141a09c1d7a7f..dda21308c96ee 100644 --- a/code/datums/station_traits/positive_traits.dm +++ b/code/datums/station_traits/positive_traits.dm @@ -219,6 +219,7 @@ /datum/job/geneticist = /obj/item/organ/internal/fly, //we don't care about implants, we have cancer. /datum/job/head_of_personnel = /obj/item/organ/internal/eyes/robotic, /datum/job/head_of_security = /obj/item/organ/internal/eyes/robotic/thermals, + /datum/job/human_ai = /obj/item/organ/internal/brain/cybernetic, /datum/job/janitor = /obj/item/organ/internal/eyes/robotic/xray, /datum/job/lawyer = /obj/item/organ/internal/heart/cybernetic/tier2, /datum/job/mime = /obj/item/organ/internal/tongue/robot, //... diff --git a/code/datums/status_effects/debuffs/fire_stacks.dm b/code/datums/status_effects/debuffs/fire_stacks.dm index 2f32ff5b3bedf..76d5f8ec896d1 100644 --- a/code/datums/status_effects/debuffs/fire_stacks.dm +++ b/code/datums/status_effects/debuffs/fire_stacks.dm @@ -272,11 +272,6 @@ overlays |= created_overlay -/obj/effect/dummy/lighting_obj/moblight/fire - name = "fire" - light_color = LIGHT_COLOR_FIRE - light_range = LIGHT_RANGE_FIRE - /datum/status_effect/fire_handler/wet_stacks id = "wet_stacks" diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm index 3f267cb3bad01..9efc867a043e5 100644 --- a/code/datums/status_effects/neutral.dm +++ b/code/datums/status_effects/neutral.dm @@ -562,7 +562,7 @@ return ..() /datum/status_effect/tinlux_light/on_apply() - mob_light_obj = owner.mob_light(2) + mob_light_obj = owner.mob_light(2, 1.5, "#ccff33") return TRUE /datum/status_effect/tinlux_light/on_remove() diff --git a/code/datums/status_effects/song_effects.dm b/code/datums/status_effects/song_effects.dm index 066ac457a9f42..f61253c987d77 100644 --- a/code/datums/status_effects/song_effects.dm +++ b/code/datums/status_effects/song_effects.dm @@ -44,7 +44,7 @@ var/obj/effect/dummy/lighting_obj/moblight/mob_light_obj /datum/status_effect/song/light/on_apply() - mob_light_obj = owner.mob_light(3, color = LIGHT_COLOR_DIM_YELLOW) + mob_light_obj = owner.mob_light(3, 1.5, color = LIGHT_COLOR_DIM_YELLOW) playsound(owner, 'sound/weapons/fwoosh.ogg', 75, FALSE) return TRUE diff --git a/code/datums/stock_market_events.dm b/code/datums/stock_market_events.dm index 81142d2300224..4907bf784f63a 100644 --- a/code/datums/stock_market_events.dm +++ b/code/datums/stock_market_events.dm @@ -83,7 +83,7 @@ /datum/stock_market_event/large_boost name = "Large Boost!" trend_value = MARKET_TREND_UPWARD - trend_duration = 3 + trend_duration = 4 circumstance = list( "has just released a new product that raised the price of ", "discovered a new valuable use for ", @@ -93,14 +93,14 @@ /datum/stock_market_event/large_boost/start_event() . = ..() var/price_units = SSstock_market.materials_prices[mat] - SSstock_market.materials_prices[mat] += round(gaussian(price_units * 0.5, price_units * 0.1)) + SSstock_market.materials_prices[mat] += round(gaussian(price_units, price_units * 0.15)) SSstock_market.materials_prices[mat] = clamp(SSstock_market.materials_prices[mat], price_minimum * mat.value_per_unit, price_maximum * mat.value_per_unit) create_news() /datum/stock_market_event/large_drop name = "Large Drop!" trend_value = MARKET_TREND_DOWNWARD - trend_duration = 5 + trend_duration = 4 circumstance = list( "'s latest product has seen major controversy, and resulted in a price drop for ", "has been hit with a major lawsuit, resulting in a price drop for ", @@ -110,6 +110,42 @@ /datum/stock_market_event/large_drop/start_event() . = ..() var/price_units = SSstock_market.materials_prices[mat] - SSstock_market.materials_prices[mat] -= round(gaussian(price_units * 1.5, price_units * 0.1)) + SSstock_market.materials_prices[mat] -= round(gaussian(price_units * 1.5, price_units * 0.15)) SSstock_market.materials_prices[mat] = clamp(SSstock_market.materials_prices[mat], price_minimum * mat.value_per_unit, price_maximum * mat.value_per_unit) create_news() + +/datum/stock_market_event/hotcakes + name = "Selling like Hotcakes!" + trend_value = MARKET_TREND_UPWARD + trend_duration = 1 + circumstance = list( + "has just released a new product that is dominating the market for ", + "is hitting it big! Dramatically stocking and raising the price of ", + ", in a surprise move, monopolized supply and has raised the price of ", + ) + +/datum/stock_market_event/hotcakes/start_event() + . = ..() + SSstock_market.materials_prices[mat] = round(price_maximum * mat.value_per_unit) + create_news() + +/datum/stock_market_event/lockdown + name = "Lockdown!" + trend_value = MARKET_TREND_DOWNWARD + trend_duration = 2 + circumstance = list( + "is being investigated by the Galactic Trade Commission, resulting in a halt of trade for ", + ", in a stunning move, has been embargoed by TerraGov, resulting in a halt of trade of ", + ) + +/datum/stock_market_event/lockdown/handle() + . = ..() + SSstock_market.materials_quantity[mat] = 0 //Force the material to be unavailable. + +/datum/stock_market_event/lockdown/end_event() + . = ..() + SSstock_market.materials_quantity[mat] = initial(mat.tradable_base_quantity) //Force the material to be available again. + SSstock_market.materials_prices[mat] = initial(mat.value_per_unit) * SHEET_MATERIAL_AMOUNT //Force the price to be reset once the lockdown is over. + create_news() + + diff --git a/code/datums/wires/airlock.dm b/code/datums/wires/airlock.dm index 4672bd15b8729..f57fdf7f4afa0 100644 --- a/code/datums/wires/airlock.dm +++ b/code/datums/wires/airlock.dm @@ -65,7 +65,7 @@ /datum/wires/airlock/interact(mob/user) var/obj/machinery/door/airlock/airlock_holder = holder - if (!issilicon(user) && airlock_holder.isElectrified() && airlock_holder.shock(user, 100)) + if (!HAS_SILICON_ACCESS(user) && airlock_holder.isElectrified() && airlock_holder.shock(user, 100)) return return ..() @@ -74,7 +74,7 @@ if(!..()) return FALSE var/obj/machinery/door/airlock/airlock = holder - if(!issilicon(user) && !isdrone(user) && airlock.isElectrified()) + if(!HAS_SILICON_ACCESS(user) && !isdrone(user) && airlock.isElectrified()) var/mob/living/carbon/carbon_user = user if (!istype(carbon_user) || carbon_user.should_electrocute(src)) return FALSE diff --git a/code/datums/wires/fax.dm b/code/datums/wires/fax.dm index 8c189d68df880..32904c5f8915e 100644 --- a/code/datums/wires/fax.dm +++ b/code/datums/wires/fax.dm @@ -12,7 +12,7 @@ if(!.) return FALSE var/obj/machinery/fax/machine = holder - if(!issilicon(user) && machine.seconds_electrified && machine.shock(user, 100)) + if(!HAS_SILICON_ACCESS(user) && machine.seconds_electrified && machine.shock(user, 100)) return FALSE if(machine.panel_open) return TRUE diff --git a/code/datums/wires/mecha.dm b/code/datums/wires/mecha.dm index 07bc119014816..b6e20c8161f45 100644 --- a/code/datums/wires/mecha.dm +++ b/code/datums/wires/mecha.dm @@ -69,7 +69,7 @@ if(.) return var/obj/vehicle/sealed/mecha/mecha = holder - if(!issilicon(usr) && mecha.internal_damage & MECHA_INT_SHORT_CIRCUIT && mecha.shock(usr)) + if(!HAS_SILICON_ACCESS(usr) && mecha.internal_damage & MECHA_INT_SHORT_CIRCUIT && mecha.shock(usr)) return FALSE /datum/wires/mecha/can_reveal_wires(mob/user) diff --git a/code/datums/wires/mod.dm b/code/datums/wires/mod.dm index 09274880367ac..00d836a52eba4 100644 --- a/code/datums/wires/mod.dm +++ b/code/datums/wires/mod.dm @@ -52,7 +52,7 @@ /datum/wires/mod/ui_act(action, params) var/obj/item/mod/control/mod = holder - if(!issilicon(usr) && mod.seconds_electrified && mod.shock(usr)) + if(!HAS_SILICON_ACCESS(usr) && mod.seconds_electrified && mod.shock(usr)) return FALSE return ..() diff --git a/code/datums/wires/mulebot.dm b/code/datums/wires/mulebot.dm index 9ec8cbe4db196..beb58fb1ce3b4 100644 --- a/code/datums/wires/mulebot.dm +++ b/code/datums/wires/mulebot.dm @@ -20,7 +20,7 @@ if(!..()) return FALSE var/mob/living/simple_animal/bot/mulebot/mule = holder - if(mule.bot_cover_flags & BOT_COVER_OPEN) + if(mule.bot_cover_flags & BOT_COVER_MAINTS_OPEN) return TRUE /datum/wires/mulebot/on_cut(wire, mend, source) diff --git a/code/datums/wires/vending.dm b/code/datums/wires/vending.dm index 4e037f3e24b3d..499707e90e0c6 100644 --- a/code/datums/wires/vending.dm +++ b/code/datums/wires/vending.dm @@ -28,7 +28,7 @@ if(!..()) return FALSE var/obj/machinery/vending/vending_machine = holder - if(!issilicon(user) && vending_machine.seconds_electrified && vending_machine.shock(user, 100)) + if(!HAS_SILICON_ACCESS(user) && vending_machine.seconds_electrified && vending_machine.shock(user, 100)) return FALSE if(vending_machine.panel_open) return TRUE diff --git a/code/datums/wounds/burns.dm b/code/datums/wounds/burns.dm index 415113c3bd77c..dac47d4ea88ad 100644 --- a/code/datums/wounds/burns.dm +++ b/code/datums/wounds/burns.dm @@ -82,11 +82,14 @@ infestation += infestation_rate * seconds_per_tick switch(infestation) if(0 to WOUND_INFECTION_MODERATE) + return + if(WOUND_INFECTION_MODERATE to WOUND_INFECTION_SEVERE) if(SPT_PROB(15, seconds_per_tick)) victim.adjustToxLoss(0.2) if(prob(6)) to_chat(victim, span_warning("The blisters on your [limb.plaintext_zone] ooze a strange pus...")) + if(WOUND_INFECTION_SEVERE to WOUND_INFECTION_CRITICAL) if(!disabling) if(SPT_PROB(1, seconds_per_tick)) diff --git a/code/game/area/ai_monitored.dm b/code/game/area/ai_monitored.dm index 84574d6badd27..7a99de4dfdfee 100644 --- a/code/game/area/ai_monitored.dm +++ b/code/game/area/ai_monitored.dm @@ -17,16 +17,14 @@ /area/station/ai_monitored/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) . = ..() if (ismob(arrived) && motioncameras.len) - for(var/X in motioncameras) - var/obj/machinery/camera/cam = X + for(var/obj/machinery/camera/cam as anything in motioncameras) cam.newTarget(arrived) return /area/station/ai_monitored/Exited(atom/movable/gone, atom/old_loc, list/atom/old_locs) ..() if (ismob(gone) && motioncameras.len) - for(var/X in motioncameras) - var/obj/machinery/camera/cam = X + for(var/obj/machinery/camera/cam as anything in motioncameras) cam.lostTargetRef(WEAKREF(gone)) return diff --git a/code/game/area/areas/away_content.dm b/code/game/area/areas/away_content.dm index 3b86f56f23587..ded38af6201ab 100644 --- a/code/game/area/areas/away_content.dm +++ b/code/game/area/areas/away_content.dm @@ -35,6 +35,10 @@ Unused icons for new areas are "awaycontent1" ~ "awaycontent30" sound_environment = SOUND_ENVIRONMENT_PLAIN ambientsounds = list('sound/ambience/shore.ogg', 'sound/ambience/ambiodd.ogg','sound/ambience/ambinice.ogg') +/area/awaymission/museum/cafeteria + name = "Nanotrasen Museum Cafeteria" + sound_environment = SOUND_ENVIRONMENT_ROOM + /area/awaymission/errorroom name = "Super Secret Room" static_lighting = FALSE diff --git a/code/game/area/areas/ruins/lavaland.dm b/code/game/area/areas/ruins/lavaland.dm index 8a95cf2ba006f..c740c12316046 100644 --- a/code/game/area/areas/ruins/lavaland.dm +++ b/code/game/area/areas/ruins/lavaland.dm @@ -10,6 +10,9 @@ name = "\improper Clown Biodome" ambientsounds = list('sound/ambience/clown.ogg') +/area/ruin/lizard_gaslava + name = "\improper Lizard's Gas(Lava)" + /area/ruin/unpowered/gaia name = "\improper Patch of Eden" diff --git a/code/game/atom/atom_act.dm b/code/game/atom/atom_act.dm index 4c4ec04d66766..ae7cd5a3eb287 100644 --- a/code/game/atom/atom_act.dm +++ b/code/game/atom/atom_act.dm @@ -67,6 +67,7 @@ * We then return the protection value */ /atom/proc/emp_act(severity) + SHOULD_CALL_PARENT(TRUE) var/protection = SEND_SIGNAL(src, COMSIG_ATOM_PRE_EMP_ACT, severity) if(!(protection & EMP_PROTECT_WIRES) && istype(wires)) wires.emp_pulse() diff --git a/code/game/atom/atom_defense.dm b/code/game/atom/atom_defense.dm index e18b3ade2f18e..4a762e4de8b49 100644 --- a/code/game/atom/atom_defense.dm +++ b/code/game/atom/atom_defense.dm @@ -94,17 +94,14 @@ CRASH("/atom/proc/run_atom_armor was called on [src] without being implemented as a type that uses integrity!") if(damage_flag == MELEE && damage_amount < damage_deflection) return 0 - switch(damage_type) - if(BRUTE) - if(BURN) - else - return 0 + if(damage_type != BRUTE && damage_type != BURN) + return 0 var/armor_protection = 0 if(damage_flag) armor_protection = get_armor_rating(damage_flag) if(armor_protection) //Only apply weak-against-armor/hollowpoint effects if there actually IS armor. armor_protection = clamp(PENETRATE_ARMOUR(armor_protection, armour_penetration), min(armor_protection, 0), 100) - return round(damage_amount * (100 - armor_protection)*0.01, DAMAGE_PRECISION) + return round(damage_amount * (100 - armor_protection) * 0.01, DAMAGE_PRECISION) ///the sound played when the atom is damaged. /atom/proc/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) diff --git a/code/game/atom/atoms_initializing_EXPENSIVE.dm b/code/game/atom/atoms_initializing_EXPENSIVE.dm index ea8bf9b125de8..a55666aed9a04 100644 --- a/code/game/atom/atoms_initializing_EXPENSIVE.dm +++ b/code/game/atom/atoms_initializing_EXPENSIVE.dm @@ -1,5 +1,6 @@ /// Init this specific atom /datum/controller/subsystem/atoms/proc/InitAtom(atom/A, from_template = FALSE, list/arguments) + var/the_type = A.type if(QDELING(A)) @@ -24,7 +25,7 @@ switch(result) if (INITIALIZE_HINT_NORMAL) - // pass + EMPTY_BLOCK_GUARD // Pass if(INITIALIZE_HINT_LATELOAD) if(arguments[1]) //mapload late_loaders += A diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 67086d91a4321..bffeec8209ee3 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -116,6 +116,14 @@ /// Will not automatically apply to the turf below you, you need to apply /datum/element/block_explosives in conjunction with this var/explosion_block = 0 + // Access levels, used in modules\jobs\access.dm + /// List of accesses needed to use this object: The user must possess all accesses in this list in order to use the object. + /// Example: If req_access = list(ACCESS_ENGINE, ACCESS_CE)- then the user must have both ACCESS_ENGINE and ACCESS_CE in order to use the object. + var/list/req_access + /// List of accesses needed to use this object: The user must possess at least one access in this list in order to use the object. + /// Example: If req_one_access = list(ACCESS_ENGINE, ACCESS_CE)- then the user must have either ACCESS_ENGINE or ACCESS_CE in order to use the object. + var/list/req_one_access + /mutable_appearance/emissive_blocker /mutable_appearance/emissive_blocker/New() diff --git a/code/game/gamemodes/objective_items.dm b/code/game/gamemodes/objective_items.dm index 9627f48cbb501..491f0f71fb900 100644 --- a/code/game/gamemodes/objective_items.dm +++ b/code/game/gamemodes/objective_items.dm @@ -22,9 +22,23 @@ var/objective_type = OBJECTIVE_ITEM_TYPE_NORMAL /// Whether this item exists on the station map at the start of a round. var/exists_on_map = FALSE + /** + * How hard it is to steal this item given normal circumstances, ranked on a scale of 1 to 5. + * + * 1 - Probably found in a public area + * 2 - Likely on someone's person, or in a less-than-public but otherwise unguarded area + * 3 - Usually on someone's person, or in a locked locker or otherwise secure area + * 4 - Always on someone's person, or in a secure area + * 5 - You know it when you see it. Things like the Nuke Disc which have a pointer to it at all times. + * + * Also accepts 0 as "extremely easy to steal" and >5 as "almost impossible to steal" + */ + var/difficulty = 0 + /// A hint explaining how one may find the target item. + var/steal_hint = "The clown might have one." /// For objectives with special checks (does that intellicard have an ai in it? etcetc) -/datum/objective_item/proc/check_special_completion() +/datum/objective_item/proc/check_special_completion(obj/item/thing) return TRUE /// Takes a list of minds and returns true if this is a valid objective to give to a team of these minds @@ -72,6 +86,8 @@ excludefromjob = list(JOB_BARTENDER) item_owner = list(JOB_BARTENDER) exists_on_map = TRUE + difficulty = 2 + steal_hint = "A double-barrel shotgun usually found on the bartender's person, or if none are around, in the bar's backroom." /obj/item/gun/ballistic/shotgun/doublebarrel/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/gun/ballistic/shotgun/doublebarrel) @@ -91,6 +107,9 @@ JOB_STATION_ENGINEER, ) exists_on_map = TRUE + difficulty = 3 + steal_hint = "Only two of these exist on the station - one in the bridge, and one in atmospherics. \ + You can use a multitool to hack open the case, or break it open the hard way." /obj/item/fireaxe/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/fireaxe) @@ -105,6 +124,8 @@ ) item_owner = list(JOB_ROBOTICIST) exists_on_map = TRUE + difficulty = 2 + steal_hint = "A specialized tool found in the roboticist's lab. You can use a multitool to hack open the case, or break it open the hard way." /obj/item/crowbar/mechremoval/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/crowbar/mechremoval) @@ -115,6 +136,9 @@ excludefromjob = list(JOB_CHAPLAIN) item_owner = list(JOB_CHAPLAIN) exists_on_map = TRUE + difficulty = 2 + steal_hint = "A holy artifact usually found on the chaplain's person, or if none are around, in the chapel's relic closet. \ + If there is a chaplain aboard, it is likely be to be transformed into some holy weapon - some of which are... difficult to remove from their person." /obj/item/nullrod/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/nullrod) @@ -125,6 +149,8 @@ excludefromjob = list(JOB_CLOWN, JOB_CARGO_TECHNICIAN, JOB_QUARTERMASTER) item_owner = list(JOB_CLOWN) exists_on_map = TRUE + difficulty = 1 + steal_hint = "The clown's huge, bright shoes. They should always be on the clown's feet." /obj/item/clothing/shoes/clown_shoes/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/clothing/shoes/clown_shoes) @@ -135,6 +161,8 @@ excludefromjob = list(JOB_MIME, JOB_CARGO_TECHNICIAN, JOB_QUARTERMASTER) item_owner = list(JOB_MIME) exists_on_map = TRUE + difficulty = 1 + steal_hint = "The mime's mask. It should always be on the mime's face." /obj/item/clothing/mask/gas/mime/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/clothing/mask/gas/mime) @@ -145,6 +173,9 @@ excludefromjob = list(JOB_SHAFT_MINER, JOB_CARGO_TECHNICIAN, JOB_QUARTERMASTER) item_owner = list(JOB_SHAFT_MINER) exists_on_map = TRUE + difficulty = 1 + steal_hint = "A tool primarily used by shaft miners to mine. Most carry one (or multiple) on their person, \ + but they can also be found in the Mining Station, Mining office, or Auxiliary Mining Base on the station." /obj/item/gun/energy/recharge/kinetic_accelerator/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/gun/energy/recharge/kinetic_accelerator) @@ -155,6 +186,8 @@ excludefromjob = list(JOB_COOK, JOB_HEAD_OF_PERSONNEL, JOB_CARGO_TECHNICIAN, JOB_QUARTERMASTER) item_owner = list(JOB_COOK) exists_on_map = TRUE + difficulty = 1 + steal_hint = "The chef's fake Italian moustache, either found on their face or in the garbage, depending on who's on duty." /obj/item/clothing/mask/fakemoustache/italian/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/clothing/mask/fakemoustache/italian) @@ -164,6 +197,9 @@ targetitem = /obj/item/gun/ballistic/revolver/c38/detective excludefromjob = list(JOB_DETECTIVE) exists_on_map = TRUE + difficulty = 3 + steal_hint = "A .38 special revolver found in the Detective's holder. \ + Usually found on the Detective's person, or if none are around, in the detective's locker, in their office." /obj/item/gun/ballistic/revolver/c38/detective/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/gun/ballistic/revolver/c38/detective) @@ -174,6 +210,8 @@ excludefromjob = list(JOB_LAWYER) item_owner = list(JOB_LAWYER) exists_on_map = TRUE + difficulty = 1 + steal_hint = "The lawyer's badge. Usually pinned to their chest, but a spare can be obtained from their clothes vendor." /obj/item/clothing/accessory/lawyers_badge/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/clothing/accessory/lawyers_badge) @@ -183,6 +221,8 @@ targetitem = /obj/item/storage/belt/utility/chief excludefromjob = list(JOB_CHIEF_ENGINEER) exists_on_map = TRUE + difficulty = 2 + steal_hint = "The chief engineer's toolbelt, strapped to their waist at all times." /obj/item/storage/belt/utility/chief/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/storage/belt/utility/chief) @@ -199,6 +239,8 @@ JOB_CHIEF_MEDICAL_OFFICER ) exists_on_map = TRUE + difficulty = 3 + steal_hint = "A self-defense weapon standard-issue for all heads of staffs barring the Head of Security. Rarely found off of their person." /obj/item/melee/baton/telescopic/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/melee/baton/telescopic) @@ -209,6 +251,9 @@ excludefromjob = list(JOB_QUARTERMASTER, JOB_CARGO_TECHNICIAN) item_owner = list(JOB_QUARTERMASTER) exists_on_map = TRUE + difficulty = 2 + steal_hint = "A card that grants access to Cargo's funds. \ + Normally found in the locker of the Quartermaster, but a particularly keen one may have it on their person or in their wallet." /obj/item/card/id/departmental_budget/car/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/card/id/departmental_budget/car) @@ -218,6 +263,9 @@ targetitem = /obj/item/mod/control/pre_equipped/magnate excludefromjob = list(JOB_CAPTAIN) exists_on_map = TRUE + difficulty = 3 + steal_hint = "An expensive, hand-crafted MOD unit made for the station's Captain. \ + If not being worn by the Captain, you would find it in the Suit Storage Unit in their quarters." /obj/item/mod/control/pre_equipped/magnate/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/mod/control/pre_equipped/magnate) @@ -234,6 +282,10 @@ JOB_CHIEF_MEDICAL_OFFICER ) exists_on_map = TRUE + difficulty = 4 + steal_hint = "The spare ID of the High Lord himself. \ + If there's no official Captain around, you may find it pinned to the chest of the Acting Captain - one of the Heads of Staff. \ + Otherwise, you'll have to bust open the golden safe on the bridge with acid or explosives to get to it." /obj/item/card/id/advanced/gold/captains_spare/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/card/id/advanced/gold/captains_spare) @@ -246,6 +298,9 @@ targetitem = /obj/item/gun/energy/laser/captain excludefromjob = list(JOB_CAPTAIN) exists_on_map = TRUE + difficulty = 4 + steal_hint = "A self-charging laser gun found in a display case in the Captain's Quarters. \ + Breaking it open may trigger a security alert, so be careful." /obj/item/gun/energy/laser/captain/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/gun/energy/laser/captain) @@ -256,6 +311,9 @@ excludefromjob = list(JOB_HEAD_OF_SECURITY) item_owner = list(JOB_HEAD_OF_SECURITY) exists_on_map = TRUE + difficulty = 4 + steal_hint = "The Head of Security's unique three mode laser gun. \ + Always found on their person, if they are alive, but may otherwise be found in their locker." /obj/item/gun/energy/e_gun/hos/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/gun/energy/e_gun/hos) @@ -266,6 +324,8 @@ excludefromjob = list(JOB_HEAD_OF_SECURITY) item_owner = list(JOB_HEAD_OF_SECURITY) exists_on_map = TRUE + difficulty = 4 + steal_hint = "A miniaturized combat shotgun. May be found in Head of Security's locker or strapped to their back." /obj/item/gun/ballistic/shotgun/automatic/combat/compact/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/gun/ballistic/shotgun/automatic/combat/compact) @@ -276,6 +336,9 @@ excludefromjob = list(JOB_CAPTAIN, JOB_RESEARCH_DIRECTOR, JOB_HEAD_OF_PERSONNEL) item_owner = list(JOB_CAPTAIN, JOB_RESEARCH_DIRECTOR) exists_on_map = TRUE + difficulty = 3 + steal_hint = "Only two of these devices exist on the station, with one sitting in the Teleporter Room \ + for emergencies, and the other in the Captain's Quarters for personal use." /obj/item/hand_tele/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/hand_tele) @@ -286,6 +349,8 @@ excludefromjob = list(JOB_CAPTAIN) item_owner = list(JOB_CAPTAIN) exists_on_map = TRUE + difficulty = 3 + steal_hint = "A special yellow jetpack found in the Suit Storage Unit in the Captain's Quarters." /obj/item/tank/jetpack/oxygen/captain/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/tank/jetpack/oxygen/captain) @@ -296,6 +361,9 @@ excludefromjob = list(JOB_CHIEF_ENGINEER) item_owner = list(JOB_CHIEF_ENGINEER) exists_on_map = TRUE + difficulty = 3 + steal_hint = "A pair of magnetic boots found in the Chief Engineer's Suit Storage Unit. \ + May also be found on their person, concealed beneath their MODsuit." /obj/item/clothing/shoes/magboots/advance/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/clothing/shoes/magboots/advance) @@ -306,6 +374,9 @@ excludefromjob = list(JOB_CAPTAIN) item_owner = list(JOB_CAPTAIN) exists_on_map = TRUE + difficulty = 3 + steal_hint = "A gold medal found in the medal box in the Captain's Quarters. \ + The Captain usually also has one pinned to their jumpsuit." /obj/item/clothing/accessory/medal/gold/captain/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/clothing/accessory/medal/gold/captain) @@ -316,6 +387,9 @@ excludefromjob = list(JOB_CHIEF_MEDICAL_OFFICER) item_owner = list(JOB_CHIEF_MEDICAL_OFFICER) exists_on_map = TRUE + difficulty = 3 + steal_hint = "The Chief Medical Officer's personal medical injector. \ + Usually found amongst their medical supplies on their person, in their belt, or otherwise in their locker." /obj/item/reagent_containers/hypospray/cmo/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/reagent_containers/hypospray/cmo) @@ -324,6 +398,9 @@ name = "the nuclear authentication disk" targetitem = /obj/item/disk/nuclear excludefromjob = list(JOB_CAPTAIN) + difficulty = 5 + steal_hint = "THAT disk - you know the one. Carried by the Captain at all times (hopefully). \ + Difficult to miss, but if you can't find it, the Head of Security and Captain both have devices to track its precise location." /obj/item/disk/nuclear/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/disk/nuclear) @@ -337,6 +414,8 @@ excludefromjob = list(JOB_HEAD_OF_SECURITY, JOB_WARDEN) item_owner = list(JOB_HEAD_OF_SECURITY) exists_on_map = TRUE + difficulty = 4 + steal_hint = "An ablative trechcoat found on the shelves of the Armory." /obj/item/clothing/suit/hooded/ablative/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/clothing/suit/hooded/ablative) @@ -347,6 +426,9 @@ excludefromjob = list(JOB_RESEARCH_DIRECTOR) item_owner = list(JOB_RESEARCH_DIRECTOR) exists_on_map = TRUE + difficulty = 3 + steal_hint = "A special suit of armor found in the possession of the Research Director. \ + You may otherwise find it in their locker." /obj/item/clothing/suit/armor/reactive/teleport/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/clothing/suit/armor/reactive/teleport) @@ -356,6 +438,11 @@ valid_containers = list(/obj/item/folder) targetitem = /obj/item/documents exists_on_map = TRUE + difficulty = 3 + steal_hint = "A set of papers belonging to a megaconglomerate. \ + Nanotrasen documents can easily be found in the station's vault. \ + For other corporations, you may find them in strange and distant places. \ + A photocopy may also suffice." /obj/item/documents/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/documents) //Any set of secret documents. Doesn't have to be NT's @@ -365,6 +452,8 @@ valid_containers = list(/obj/item/nuke_core_container) targetitem = /obj/item/nuke_core exists_on_map = TRUE + difficulty = 4 + steal_hint = "The core of the station's self-destruct device, found in the vault." /obj/item/nuke_core/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/nuke_core) @@ -379,6 +468,8 @@ excludefromjob = list(JOB_RESEARCH_DIRECTOR, JOB_SCIENTIST, JOB_ROBOTICIST, JOB_GENETICIST) item_owner = list(JOB_RESEARCH_DIRECTOR, JOB_SCIENTIST) exists_on_map = TRUE + difficulty = 4 + steal_hint = "The hard drive of the master research server, found in R&D's server room." /obj/item/computer_disk/hdd_theft/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/computer_disk/hdd_theft) @@ -392,6 +483,8 @@ name = "a sliver of a supermatter crystal" targetitem = /obj/item/nuke_core/supermatter_sliver valid_containers = list(/obj/item/nuke_core_container/supermatter) + difficulty = 5 + steal_hint = "A small shard of the station's supermatter crystal engine." /datum/objective_item/steal/supermatter/New() special_equipment += /obj/item/storage/box/syndie_kit/supermatter @@ -404,6 +497,8 @@ /datum/objective_item/steal/functionalai name = "a functional AI" targetitem = /obj/item/aicard + difficulty = 5 + steal_hint = "An intellicard (or MODsuit) containing an active, functional AI." /datum/objective_item/steal/functionalai/New() . = ..() @@ -435,6 +530,8 @@ item_owner = list(JOB_CHIEF_ENGINEER) altitems = list(/obj/item/photo) exists_on_map = TRUE + difficulty = 3 + steal_hint = "The blueprints of the station, found in the Chief Engineer's locker, or on their person. A picture may suffice." /obj/item/areaeditor/blueprints/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/areaeditor/blueprints) @@ -453,6 +550,8 @@ targetitem = /obj/item/blackbox excludefromjob = list(JOB_CHIEF_ENGINEER, JOB_STATION_ENGINEER, JOB_ATMOSPHERIC_TECHNICIAN) exists_on_map = TRUE + difficulty = 4 + steal_hint = "The station's data Blackbox, found solely within Telecommunications." /obj/item/blackbox/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/blackbox) @@ -466,6 +565,8 @@ excludefromjob = list(JOB_CARGO_TECHNICIAN, JOB_QUARTERMASTER, JOB_ATMOSPHERIC_TECHNICIAN, JOB_STATION_ENGINEER, JOB_CHIEF_ENGINEER) item_owner = list(JOB_STATION_ENGINEER, JOB_CHIEF_ENGINEER) exists_on_map = TRUE + difficulty = 1 + steal_hint = "A basic pair of insulated gloves, usually worn by Assistants, Engineers, or Cargo Technicians." /obj/item/clothing/gloves/color/yellow/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/clothing/gloves/color/yellow) @@ -475,6 +576,8 @@ targetitem = /obj/item/toy/plush/moth excludefromjob = list(JOB_PSYCHOLOGIST, JOB_PARAMEDIC, JOB_CHEMIST, JOB_MEDICAL_DOCTOR, JOB_VIROLOGIST, JOB_CHIEF_MEDICAL_OFFICER, JOB_CORONER) exists_on_map = TRUE + difficulty = 1 + steal_hint = "A moth plush toy. The Psychologist has one to help console patients." /obj/item/toy/plush/moth/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/toy/plush/moth) @@ -483,6 +586,8 @@ name = "cute lizard plush toy" targetitem = /obj/item/toy/plush/lizard_plushie exists_on_map = TRUE + difficulty = 1 + steal_hint = "A lizard plush toy. Often found hidden in maintenance." /obj/item/toy/plush/lizard_plushie/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/toy/plush/lizard_plushie) @@ -492,6 +597,8 @@ targetitem = /obj/item/stamp/denied excludefromjob = list(JOB_CARGO_TECHNICIAN, JOB_QUARTERMASTER, JOB_SHAFT_MINER) exists_on_map = TRUE + difficulty = 1 + steal_hint = "Cargo often has multiple of these red stamps lying around to process paperwork." /obj/item/stamp/denied/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/stamp/denied) @@ -501,6 +608,8 @@ targetitem = /obj/item/stamp/granted excludefromjob = list(JOB_CARGO_TECHNICIAN, JOB_QUARTERMASTER, JOB_SHAFT_MINER) exists_on_map = TRUE + difficulty = 1 + steal_hint = "Cargo often has multiple of these green stamps lying around to process paperwork." /obj/item/stamp/granted/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/stamp/granted) @@ -510,6 +619,9 @@ targetitem = /obj/item/book/manual/wiki/security_space_law excludefromjob = list(JOB_SECURITY_OFFICER, JOB_WARDEN, JOB_HEAD_OF_SECURITY, JOB_LAWYER, JOB_DETECTIVE) exists_on_map = TRUE + difficulty = 1 + steal_hint = "Sometimes found in the possession of members of Security and Lawyers. \ + The courtroom and the library are also good places to look." /obj/item/book/manual/wiki/security_space_law/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/book/manual/wiki/security_space_law) @@ -520,6 +632,8 @@ excludefromjob = list(JOB_ATMOSPHERIC_TECHNICIAN, JOB_STATION_ENGINEER, JOB_CHIEF_ENGINEER, JOB_SCIENTIST, JOB_RESEARCH_DIRECTOR, JOB_GENETICIST, JOB_ROBOTICIST) item_owner = list(JOB_CHIEF_ENGINEER) exists_on_map = TRUE + difficulty = 1 + steal_hint = "A tool often used by Engineers, Atmospherics Technicians, and Ordnance Technicians." /obj/item/pipe_dispenser/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/pipe_dispenser) @@ -529,6 +643,254 @@ targetitem = /obj/item/storage/fancy/donut_box excludefromjob = list(JOB_CAPTAIN, JOB_CHIEF_ENGINEER, JOB_HEAD_OF_PERSONNEL, JOB_HEAD_OF_SECURITY, JOB_QUARTERMASTER, JOB_CHIEF_MEDICAL_OFFICER, JOB_RESEARCH_DIRECTOR, JOB_SECURITY_OFFICER, JOB_WARDEN, JOB_LAWYER, JOB_DETECTIVE) exists_on_map = TRUE + difficulty = 1 + steal_hint = "Everyone has a box of donuts - you may most commonly find them on the Bridge, within Security, or in any department's break room." /obj/item/storage/fancy/donut_box/add_stealing_item_objective() return add_item_to_steal(src, /obj/item/storage/fancy/donut_box) + +/datum/objective_item/steal/spy + objective_type = OBJECTIVE_ITEM_TYPE_SPY + +/datum/objective_item/steal/spy/lamarr + name = "The Research Director's pet headcrab" + targetitem = /obj/item/clothing/mask/facehugger/lamarr + excludefromjob = list(JOB_RESEARCH_DIRECTOR) + exists_on_map = TRUE + difficulty = 3 + steal_hint = "The Research Director's pet headcrab, Lamarr, found in a secure cage in their office." + +/obj/item/clothing/mask/facehugger/lamarr/add_stealing_item_objective() + return add_item_to_steal(src, /obj/item/clothing/mask/facehugger/lamarr) + +/datum/objective_item/steal/spy/disabler + name = "a disabler" + targetitem = /obj/item/gun/energy/disabler + excludefromjob = list( + JOB_CAPTAIN, + JOB_DETECTIVE, + JOB_HEAD_OF_PERSONNEL, + JOB_HEAD_OF_SECURITY, + JOB_SECURITY_OFFICER, + JOB_WARDEN, + ) + difficulty = 2 + steal_hint = "A hand-held disabler, often found in the possession of Security Officers." + +/datum/objective_item/steal/spy/energy_gun + name = "an energy gun" + targetitem = /obj/item/gun/energy/e_gun + excludefromjob = list( + JOB_CAPTAIN, + JOB_CHIEF_ENGINEER, + JOB_CHIEF_MEDICAL_OFFICER, + JOB_DETECTIVE, + JOB_HEAD_OF_PERSONNEL, + JOB_HEAD_OF_SECURITY, + JOB_QUARTERMASTER, + JOB_RESEARCH_DIRECTOR, + JOB_SECURITY_OFFICER, + JOB_WARDEN, + ) + exists_on_map = TRUE + difficulty = 2 + steal_hint = "A two-mode energy gun, found in the station's Armory, as well as in the hands of some heads of staff for personal defense." + +/datum/objective_item/steal/spy/energy_gun/check_special_completion(obj/item/thing) + return thing.type == /obj/item/gun/energy/e_gun + +/obj/item/gun/energy/e_gun/add_stealing_item_objective() + if(type == /obj/item/gun/energy/e_gun) + return add_item_to_steal(src, /obj/item/gun/energy/e_gun) + +/datum/objective_item/steal/spy/laser_gun + name = "a laser gun" + targetitem = /obj/item/gun/energy/laser + excludefromjob = list( + JOB_CAPTAIN, + JOB_CHIEF_ENGINEER, + JOB_CHIEF_MEDICAL_OFFICER, + JOB_DETECTIVE, + JOB_HEAD_OF_PERSONNEL, + JOB_HEAD_OF_SECURITY, + JOB_QUARTERMASTER, + JOB_RESEARCH_DIRECTOR, + JOB_SECURITY_OFFICER, + JOB_WARDEN, + ) + exists_on_map = TRUE + difficulty = 3 + steal_hint = "A simple laser gun, found in the station's Armory." + +/datum/objective_item/steal/spy/laser_gun/check_special_completion(obj/item/thing) + return thing.type == /obj/item/gun/energy/laser + +/obj/item/gun/energy/laser/add_stealing_item_objective() + if(type == /obj/item/gun/energy/laser) + return add_item_to_steal(src, /obj/item/gun/energy/laser) + +/datum/objective_item/steal/spy/shotgun + name = "a riot shotgun" + targetitem = /obj/item/gun/ballistic/shotgun/riot + excludefromjob = list( + JOB_DETECTIVE, + JOB_HEAD_OF_PERSONNEL, + JOB_HEAD_OF_SECURITY, + JOB_SECURITY_OFFICER, + JOB_WARDEN, + ) + exists_on_map = TRUE + difficulty = 3 + steal_hint = "A shotgun found in the station's Armory for riot suppression. Doesn't miss." + +/obj/item/gun/ballistic/shotgun/riot/add_stealing_item_objective() + return add_item_to_steal(src, /obj/item/gun/ballistic/shotgun/riot) + +/datum/objective_item/steal/spy/temp_gun + name = "security's temperature gun" + targetitem = /obj/item/gun/energy/temperature/security + excludefromjob = list( + JOB_DETECTIVE, + JOB_HEAD_OF_PERSONNEL, + JOB_HEAD_OF_SECURITY, + JOB_SECURITY_OFFICER, + JOB_WARDEN, + ) + exists_on_map = TRUE + difficulty = 2 // lowered for the meme + steal_hint = "Security's TRUSTY temperature gun, found in the station's Armory." + +/obj/item/gun/energy/temperature/security/add_stealing_item_objective() + return add_item_to_steal(src, /obj/item/gun/energy/temperature/security) + +/datum/objective_item/steal/spy/stamp + name = "a head of staff's stamp" + targetitem = /obj/item/stamp/head + excludefromjob = list( + JOB_CAPTAIN, + JOB_CHIEF_ENGINEER, + JOB_CHIEF_MEDICAL_OFFICER, + JOB_HEAD_OF_PERSONNEL, + JOB_HEAD_OF_SECURITY, + JOB_QUARTERMASTER, + JOB_RESEARCH_DIRECTOR, + ) + exists_on_map = TRUE + difficulty = 1 + steal_hint = "A stamp owned by a head of staff, from their offices." + +/obj/item/stamp/head/add_stealing_item_objective() + return add_item_to_steal(src, /obj/item/stamp/head) + +/datum/objective_item/steal/spy/sunglasses + name = "sunglasses" + targetitem = /obj/item/clothing/glasses/sunglasses + excludefromjob = list( + JOB_CAPTAIN, + JOB_CHIEF_ENGINEER, + JOB_CHIEF_MEDICAL_OFFICER, + JOB_HEAD_OF_PERSONNEL, + JOB_HEAD_OF_SECURITY, + JOB_LAWYER, + JOB_QUARTERMASTER, + JOB_RESEARCH_DIRECTOR, + JOB_SECURITY_OFFICER, + JOB_WARDEN, + ) + difficulty = 1 + steal_hint = "A pair of sunglasses. Lawyers often have a few pairs, as do some heads of staff. \ + You can also obtain a pair from dissassembling hudglasses." + +/datum/objective_item/steal/spy/ce_modsuit + name = "the cheif engineer's advanced MOD control unit" + targetitem = /obj/item/mod/control/pre_equipped/advanced + excludefromjob = list(JOB_CHIEF_ENGINEER) + exists_on_map = TRUE + difficulty = 2 + steal_hint = "An advanced version of the standard Engineering MODsuit commonly worn by the Chief Engineer." + +/obj/item/mod/control/pre_equipped/advanced/add_stealing_item_objective() + return add_item_to_steal(src, /obj/item/mod/control/pre_equipped/advanced) + +/datum/objective_item/steal/spy/rd_modsuit + name = "the research director's research MOD control unit" + targetitem = /obj/item/mod/control/pre_equipped/research + excludefromjob = list(JOB_RESEARCH_DIRECTOR) + exists_on_map = TRUE + difficulty = 2 + steal_hint = "A bulky MODsuit commonly worn by the Research Director to protect themselves from the hazards of their work." + +/obj/item/mod/control/pre_equipped/research/add_stealing_item_objective() + return add_item_to_steal(src, /obj/item/mod/control/pre_equipped/research) + +/datum/objective_item/steal/spy/cmo_modsuit + name = "the chief medical officer's rescure MOD control unit" + targetitem = /obj/item/mod/control/pre_equipped/rescue + excludefromjob = list(JOB_CHIEF_MEDICAL_OFFICER) + exists_on_map = TRUE + difficulty = 2 + steal_hint = "A MODsuit sometimes equipped by the Chief Medical Officer to perform rescue opperations in hazardous environments." + +/obj/item/mod/control/pre_equipped/rescue/add_stealing_item_objective() + return add_item_to_steal(src, /obj/item/mod/control/pre_equipped/rescue) + +/datum/objective_item/steal/spy/hos_modsuit + name = "the head of security's safeguard MOD control unit" + targetitem = /obj/item/mod/control/pre_equipped/safeguard + excludefromjob = list(JOB_HEAD_OF_SECURITY) + exists_on_map = TRUE + difficulty = 2 + steal_hint = "An advanced MODsuit sometimes worn by the Head of Security when needing to detain hostiles invading the station." + +/obj/item/mod/control/pre_equipped/safeguard/add_stealing_item_objective() + return add_item_to_steal(src, /obj/item/mod/control/pre_equipped/safeguard) + +/datum/objective_item/steal/spy/stun_baton + name = "a stun baton" + targetitem = /obj/item/melee/baton/security + excludefromjob = list( + JOB_CAPTAIN, + JOB_DETECTIVE, + JOB_HEAD_OF_PERSONNEL, + JOB_HEAD_OF_SECURITY, + JOB_SECURITY_OFFICER, + JOB_WARDEN, + ) + difficulty = 2 + steal_hint = "Steal any stun baton from Security." + +/datum/objective_item/steal/spy/stun_baton/check_special_completion(obj/item/thing) + return !istype(thing, /obj/item/melee/baton/security/cattleprod) + +/datum/objective_item/steal/spy/det_baton + name = "the detective's baton" + targetitem = /obj/item/melee/baton + excludefromjob = list( + JOB_CAPTAIN, + JOB_DETECTIVE, + JOB_HEAD_OF_PERSONNEL, + JOB_HEAD_OF_SECURITY, + JOB_SECURITY_OFFICER, + JOB_WARDEN, + ) + exists_on_map = TRUE + difficulty = 2 + steal_hint = "The detective's old wooden truncheon, commonly found on their person for self defense." + +/datum/objective_item/steal/spy/det_baton/check_special_completion(obj/item/thing) + return thing.type == /obj/item/melee/baton + +/obj/item/melee/baton/add_stealing_item_objective() + if(type == /obj/item/melee/baton) + return add_item_to_steal(src, /obj/item/melee/baton) + +/datum/objective_item/steal/spy/captain_sabre_sheathe + name = "the captain's sabre sheathe" + targetitem = /obj/item/storage/belt/sabre + excludefromjob = list(JOB_CAPTAIN) + exists_on_map = TRUE + difficulty = 3 + steal_hint = "The sheathe for the captain's sabre, found in their closet or strapped to their waist at all times." + +/obj/item/storage/belt/sabre/add_stealing_item_objective() + return add_item_to_steal(src, /obj/item/storage/belt/sabre) diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index 63e95b2a74e76..86c9c6274bf51 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -136,8 +136,6 @@ var/market_verb = "Customer" var/payment_department = ACCOUNT_ENG - /// For storing and overriding ui id - var/tgui_id // ID of TGUI interface ///Is this machine currently in the atmos machinery queue? var/atmos_processing = FALSE /// world.time of last use by [/mob/living] @@ -595,10 +593,10 @@ if(!isliving(user)) return FALSE //no ghosts allowed, sorry - if(!issilicon(user) && !user.can_hold_items()) + if(!HAS_SILICON_ACCESS(user) && !user.can_hold_items()) return FALSE //spiders gtfo - if(issilicon(user)) // If we are a silicon, make sure the machine allows silicons to interact with it + if(HAS_SILICON_ACCESS(user)) // If we are a silicon, make sure the machine allows silicons to interact with it if(!(interaction_flags_machine & INTERACT_MACHINE_ALLOW_SILICON)) return FALSE @@ -658,7 +656,7 @@ //////////////////////////////////////////////////////////////////////////////////////////// //Return a non FALSE value to interrupt attack_hand propagation to subtypes. -/obj/machinery/interact(mob/user, special_state) +/obj/machinery/interact(mob/user) if(interaction_flags_machine & INTERACT_MACHINE_SET_MACHINE) user.set_machine(src) update_last_used(user) @@ -667,7 +665,7 @@ /obj/machinery/ui_act(action, list/params) add_fingerprint(usr) update_last_used(usr) - if(isAI(usr) && !GLOB.cameranet.checkTurfVis(get_turf(src))) //We check if they're an AI specifically here, so borgs can still access off-camera stuff. + if(HAS_AI_ACCESS(usr) && !GLOB.cameranet.checkTurfVis(get_turf(src))) //We check if they're an AI specifically here, so borgs can still access off-camera stuff. to_chat(usr, span_warning("You can no longer connect to this device!")) return FALSE return ..() diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm index ba9667b3e5809..25dd047a69a9e 100644 --- a/code/game/machinery/autolathe.dm +++ b/code/game/machinery/autolathe.dm @@ -356,7 +356,7 @@ /obj/machinery/autolathe/MouseDrop(atom/over, src_location, over_location, src_control, over_control, params) . = ..() - if((!issilicon(usr) && !isAdminGhostAI(usr)) && !Adjacent(usr)) + if((!HAS_SILICON_ACCESS(usr) && !isAdminGhostAI(usr)) && !Adjacent(usr)) return if(busy) balloon_alert(usr, "printing started!") diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm index 5824f93dcc736..d9f9bc5280b28 100644 --- a/code/game/machinery/buttons.dm +++ b/code/game/machinery/buttons.dm @@ -44,7 +44,7 @@ if(!built && !device && device_type) device = new device_type(src) - src.check_access(null) + check_access(null) if(length(req_access) || length(req_one_access)) board = new(src) @@ -184,7 +184,7 @@ id = "[port.shuttle_id]_[id]" setup_device() -/obj/machinery/button/attack_hand(mob/user, list/modifiers) +/obj/machinery/button/interact(mob/user) . = ..() if(.) return diff --git a/code/game/machinery/camera/presets.dm b/code/game/machinery/camera/presets.dm index 2d9a2a221b42d..e79eadf53b9ed 100644 --- a/code/game/machinery/camera/presets.dm +++ b/code/game/machinery/camera/presets.dm @@ -109,7 +109,7 @@ /obj/machinery/camera/proc/upgradeEmpProof(malf_upgrade, ignore_malf_upgrades) if(isEmpProof(ignore_malf_upgrades)) //pass a malf upgrade to ignore_malf_upgrades so we can replace the malf module with the normal one return //that way if someone tries to upgrade an already malf-upgraded camera, it'll just upgrade it to a normal version. - AddElement(/datum/element/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES | EMP_PROTECT_CONTENTS) + AddElement(/datum/element/empprotection, EMP_PROTECT_ALL) var/obj/structure/camera_assembly/assembly = assembly_ref?.resolve() if(malf_upgrade) assembly.malf_emp_firmware_active = TRUE //don't add parts to drop, update icon, ect. reconstructing it will also retain the upgrade. @@ -125,7 +125,7 @@ /obj/machinery/camera/proc/removeEmpProof(ignore_malf_upgrades) if(ignore_malf_upgrades) //don't downgrade it if malf software is forced onto it. return - RemoveElement(/datum/element/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES | EMP_PROTECT_CONTENTS) + RemoveElement(/datum/element/empprotection, EMP_PROTECT_ALL) upgrades &= ~CAMERA_UPGRADE_EMP_PROOF diff --git a/code/game/machinery/computer/accounting.dm b/code/game/machinery/computer/accounting.dm index 475bf404c1ce0..d804b8efe5d94 100644 --- a/code/game/machinery/computer/accounting.dm +++ b/code/game/machinery/computer/accounting.dm @@ -21,10 +21,9 @@ for(var/current_account as anything in SSeconomy.bank_accounts_by_id) var/datum/bank_account/current_bank_account = SSeconomy.bank_accounts_by_id[current_account] - var/job_title = current_bank_account.account_job?.title player_accounts += list(list( "name" = current_bank_account.account_holder, - "job" = job_title ? job_title : "No Job", // because this can be null + "job" = current_bank_account.account_job?.title || "No job", // because this can be null "balance" = round(current_bank_account.account_balance), "modifier" = round((current_bank_account.payday_modifier * 0.9), 0.1), )) @@ -32,4 +31,3 @@ data["AuditLog"] = audit_list data["Crashing"] = HAS_TRAIT(SSeconomy, TRAIT_MARKET_CRASHING) return data - diff --git a/code/game/machinery/computer/arena.dm b/code/game/machinery/computer/arena.dm deleted file mode 100644 index 97d89c2414076..0000000000000 --- a/code/game/machinery/computer/arena.dm +++ /dev/null @@ -1,411 +0,0 @@ -#define ARENA_RED_TEAM "red" -#define ARENA_GREEN_TEAM "green" -#define ARENA_DEFAULT_ID "arena_default" -#define ARENA_CORNER_A "cornerA" -#define ARENA_CORNER_B "cornerB" - -/// Arena related landmarks -/obj/effect/landmark/arena - name = "arena landmark" - var/landmark_tag - var/arena_id = ARENA_DEFAULT_ID - -/obj/effect/landmark/arena/start - name = "arena corner A" - landmark_tag = ARENA_CORNER_A - -/obj/effect/landmark/arena/end - name = "arena corner B" - landmark_tag = ARENA_CORNER_B - -/// Controller for admin event arenas -/obj/machinery/computer/arena - name = "arena controller" - - interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_SET_MACHINE|INTERACT_MACHINE_REQUIRES_LITERACY - - /// Arena ID - var/arena_id = ARENA_DEFAULT_ID - /// Enables/disables spawning - var/ready_to_spawn = FALSE - /// Assoc list of map templates indexed by user friendly names - var/static/list/arena_templates = list() - /// Were the config directory arenas loaded - var/static/default_arenas_loaded = FALSE - /// Name of currently loaded template - var/current_arena_template = "None" - // What turf arena clears to - var/empty_turf_type = /turf/open/indestructible - // List of team ids - var/list/teams = list(ARENA_RED_TEAM,ARENA_GREEN_TEAM) - /// List of hud instances indedxed by team id - var/static/list/team_huds = list() - /// List of hud colors indexed by team id - var/static/list/team_colors = list(ARENA_RED_TEAM = "red", ARENA_GREEN_TEAM = "green") - // Team hud index in GLOB.huds indexed by team id - var/static/list/team_hud_index = list() - - /// List of ckeys indexed by team id - var/list/team_keys = list() - /// List of outfit datums/types indexed by team id, can be empty - var/list/outfits = list() - /// Default team outfit if `outfits[team]` is empty - var/default_outfit = /datum/outfit/job/assistant - - /// Is the arena template loading in - var/loading = FALSE - - //How long between admin pressing start and doors opening - var/start_delay = 30 SECONDS - //Value for the countdown - var/start_time - var/list/countdowns = list() //List of countdown effects ticking down to start - - //Sound played when the fight starts. - var/start_sound = 'sound/items/airhorn2.ogg' - var/start_sound_volume = 50 - -/obj/machinery/computer/arena/Initialize(mapload, obj/item/circuitboard/C) - . = ..() - LoadDefaultArenas() - -/** - * Loads the arenas from config directory. - * THESE ARE FULLY CACHED FOR QUICK SWITCHING SO KEEP TRACK OF THE AMOUNT - */ -/obj/machinery/computer/arena/proc/LoadDefaultArenas() - if(default_arenas_loaded) - return - var/arena_dir = "[global.config.directory]/arenas/" - var/list/default_arenas = flist(arena_dir) - for(var/arena_file in default_arenas) - var/simple_name = replacetext(replacetext(arena_file,arena_dir,""),".dmm","") - INVOKE_ASYNC(src, PROC_REF(add_new_arena_template), null, arena_dir + arena_file, simple_name) - -/obj/machinery/computer/arena/proc/get_landmark_turf(landmark_tag) - for(var/obj/effect/landmark/arena/L in GLOB.landmarks_list) - if(L.arena_id == arena_id && L.landmark_tag == landmark_tag && isturf(L.loc)) - return L.loc - -/obj/machinery/computer/arena/proc/get_load_point() - var/turf/A = get_landmark_turf(ARENA_CORNER_A) - var/turf/B = get_landmark_turf(ARENA_CORNER_B) - return locate(min(A.x,B.x),min(A.y,B.y),A.z) - -/obj/machinery/computer/arena/proc/get_arena_turfs() - var/lp = get_load_point() - var/turf/A = get_landmark_turf(ARENA_CORNER_A) - var/turf/B = get_landmark_turf(ARENA_CORNER_B) - var/turf/hp = locate(max(A.x,B.x),max(A.y,B.y),A.z) - return block(lp,hp) - -/obj/machinery/computer/arena/proc/clear_arena() - for(var/turf/T in get_arena_turfs()) - T.empty(turf_type = /turf/open/indestructible) - current_arena_template = "None" - -/obj/machinery/computer/arena/proc/load_arena(arena_template,mob/user) - if(loading) - return - var/datum/map_template/M = arena_templates[arena_template] - if(!M) - to_chat(user,span_warning("No such arena")) - return - clear_arena() //Clear current arena - var/turf/A = get_landmark_turf(ARENA_CORNER_A) - var/turf/B = get_landmark_turf(ARENA_CORNER_B) - var/wh = abs(A.x - B.x) + 1 - var/hz = abs(A.y - B.y) + 1 - if(M.width > wh || M.height > hz) - to_chat(user,span_warning("Arena template is too big for the current arena!")) - return - loading = TRUE - var/bd = M.load(get_load_point()) - if(bd) - current_arena_template = arena_template - loading = FALSE - - message_admins("[key_name_admin(user)] loaded [arena_template] event arena for [arena_id] arena.") - log_admin("[key_name(user)] loaded [arena_template] event arena for [arena_id] arena.") - - - -/obj/machinery/computer/arena/proc/add_new_arena_template(user,fname,friendly_name) - if(!fname) - fname = input(user, "Upload dmm file to use as arena template","Upload Map Template") as null|file - if(!fname) - return - if(!friendly_name) - friendly_name = "[fname]" //Could ask the user for friendly name here - - var/datum/map_template/T = new(fname,friendly_name,TRUE) - if(!T.cached_map || T.cached_map.check_for_errors()) - to_chat(user,"Map failed to parse check for errors.") - return - - arena_templates[T.name] = T - message_admins("[key_name_admin(user)] uploaded new event arena: [friendly_name].") - log_admin("[key_name(user)] uploaded new event arena: [friendly_name].") - -/obj/machinery/computer/arena/proc/load_team(user,team) - var/rawteam = tgui_input_text(user, "Enter team member list (ckeys separated by comma)", "Team List", multiline = TRUE) - if(isnull(rawteam)) - return - for(var/i in splittext(rawteam, ",")) - var/key = ckey(i) - if(!i) - continue - add_team_member(user,team,key) - -/obj/machinery/computer/arena/proc/add_team_member(mob/user,team,key) - if(!key) - var/list/keys = list() - for(var/mob/M in GLOB.player_list) - keys += M.client - var/client/selection = tgui_input_list(user, "Select a player", "Team member", sort_key(keys)) - //Could be freeform if you want to add disconnected i guess - if(isnull(selection)) - return - key = selection.ckey - if(!team_keys[team]) - team_keys[team] = list(key) - else - team_keys[team] |= key - to_chat(user,"[key] added to [team] team.") - -/obj/machinery/computer/arena/proc/remove_member(mob/user,ckey,team) - team_keys[team] -= ckey - to_chat(user,"[ckey] removed from [team] team.") - -/obj/machinery/computer/arena/proc/spawn_member(obj/machinery/arena_spawn/spawnpoint,ckey,team) - var/mob/oldbody = get_mob_by_key(ckey) - if(!isobserver(oldbody)) - return - var/mob/living/carbon/human/M = new/mob/living/carbon/human(get_turf(spawnpoint)) - oldbody.client.prefs.safe_transfer_prefs_to(M, is_antag = TRUE) - M.set_species(/datum/species/human) // Could use setting per team - M.equipOutfit(outfits[team] ? outfits[team] : default_outfit) - M.faction += team //In case anyone wants to add team based stuff to arena special effects - M.key = ckey - -/obj/machinery/computer/arena/proc/change_outfit(mob/user,team) - outfits[team] = user.client.robust_dress_shop() - -/obj/machinery/computer/arena/proc/toggle_spawn(mob/user) - ready_to_spawn = !ready_to_spawn - to_chat(user,"You [ready_to_spawn ? "enable" : "disable"] the spawners.") - log_admin("[key_name(user)] toggled event arena spawning for [arena_id] arena.") - // Could use update_appearance on spawnpoints here to show they're on - if(ready_to_spawn) - for(var/mob/M in all_contestants()) - to_chat(M,span_userdanger("Arena you're signed up for is ready!")) - -/obj/machinery/computer/arena/proc/all_contestants() - . = list() - for(var/team in team_keys) - for(var/key in team_keys[team]) - var/mob/M = get_mob_by_key(key) - if(M) - . += M - -/obj/machinery/computer/arena/proc/reset_arena() - clear_arena() - set_doors(closed = TRUE) - -/obj/machinery/computer/arena/proc/get_spawn(team) - for(var/obj/machinery/arena_spawn/A as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/arena_spawn)) - if(A.arena_id == arena_id && A.team == team) - return A - -/obj/machinery/computer/arena/proc/start_match(mob/user) - //TODO: Check if everyone is spawned in, if not ask for confirmation. - var/timetext = DisplayTimeText(start_delay) - to_chat(user,span_notice("The match will start in [timetext].")) - for(var/mob/M in all_contestants()) - to_chat(M,span_userdanger("The gates will open in [timetext]!")) - start_time = world.time + start_delay - addtimer(CALLBACK(src, PROC_REF(begin)),start_delay) - for(var/team in teams) - var/obj/machinery/arena_spawn/team_spawn = get_spawn(team) - var/obj/effect/countdown/arena/A = new(team_spawn) - A.start() - countdowns += A - -/obj/machinery/computer/arena/proc/begin() - ready_to_spawn = FALSE - set_doors(closed = FALSE) - if(start_sound) - for(var/team in teams) - var/obj/machinery/arena_spawn/A = get_spawn(team) - playsound(A,start_sound, start_sound_volume) - for(var/mob/M in all_contestants()) - to_chat(M,span_userdanger("START!")) - //Clean up the countdowns - QDEL_LIST(countdowns) - start_time = null - updateUsrDialog() - - -/obj/machinery/computer/arena/proc/set_doors(closed = FALSE) - for(var/obj/machinery/door/poddoor/D as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door/poddoor)) - if(D.id != arena_id) - continue - if(closed) - INVOKE_ASYNC(D, TYPE_PROC_REF(/obj/machinery/door/poddoor, close)) - else - INVOKE_ASYNC(D, TYPE_PROC_REF(/obj/machinery/door/poddoor, open)) - -/obj/machinery/computer/arena/Topic(href, href_list) - if(..()) - return - var/mob/user = usr - - if(!user.client.holder) // Should it require specific perm ? - return - - if(href_list["upload"]) - add_new_arena_template(user) - if(href_list["change_arena"]) - load_arena(href_list["change_arena"],user) - if(href_list["toggle_spawn"]) - toggle_spawn(user) - if(href_list["start"]) - start_match(user) - if(href_list["follow"]) - var/mob/observed_team_member = locate(href_list["follow"]) in GLOB.mob_list - if(observed_team_member) - user.client?.admin_follow(observed_team_member) - if(href_list["team_action"]) - var/team = href_list["team"] - switch(href_list["team_action"]) - if("addmember") - add_team_member(user,team) - if("loadteam") - load_team(user,team) - if("outfit") - change_outfit(user,team) - if(href_list["special"]) - switch(href_list["special"]) - if("reset") - reset_arena() - //Just example in case you want to add more - if("randomarena") - load_random_arena(user) - if("spawntrophy") - trophy_for_last_man_standing(user) - if(href_list["member_action"]) - var/ckey = href_list["ckey"] - var/team = href_list["team"] - switch(href_list["member_action"]) - if("remove") - remove_member(user,ckey,team) - updateUsrDialog() - -// Special functions - -/obj/machinery/computer/arena/proc/load_random_arena(mob/user) - if(!length(arena_templates)) - to_chat(user,span_warning("No arenas present")) - return - var/picked = pick(arena_templates) - load_arena(picked,user) - -/obj/machinery/computer/arena/proc/trophy_for_last_man_standing() - var/arena_turfs = get_arena_turfs() - for(var/mob/living/L in GLOB.mob_living_list) - if(L.stat != DEAD && (get_turf(L) in arena_turfs)) - var/obj/item/reagent_containers/cup/glass/trophy/gold_cup/G = new(get_turf(L)) - G.name = "[L.real_name]'s Trophy" - -/obj/machinery/computer/arena/ui_interact(mob/user) - . = ..() - var/list/dat = list() - dat += "
Spawning is currently [ready_to_spawn ? "enabled" : "disabled"] Toggle
" - dat += "
[start_time ? "Stop countdown" : "Start!"]
" - for(var/team in teams) - dat += "

[capitalize(team)] team:

" - dat += "" - dat += "
Team Outfit : [outfits[team] ? outfits[team] : default_outfit]
" - dat += "Load team" - dat += "Add member" - dat += "Change Outfit" - //Add more per team features here - - dat += "Current arena: [current_arena_template]" - dat += "

Arena List:

" - for(var/A in arena_templates) - dat += "[A]
" - dat += "
" - dat += "Upload new arena
" - dat += "
" - //Special actions - dat += "Reset Arena.
" - dat += "Load random arena.
" - dat += "Spawn trophies for survivors.
" - - var/datum/browser/popup = new(user, "arena controller", "Arena Controller", 500, 600) - popup.set_content(dat.Join()) - popup.open() - -/// Arena spawnpoint -/obj/machinery/arena_spawn - name = "Arena Spawnpoint" - icon = 'icons/obj/machines/beacon.dmi' - icon_state = "syndbeacon" - resistance_flags = INDESTRUCTIBLE - /// In case we have multiple arena controllers at once. - var/arena_id = ARENA_DEFAULT_ID - /// Team ID - var/team = "default" - /// only exist to cut down on glob.machines lookups, do not modify - var/obj/machinery/computer/arena/_controller - -/obj/machinery/arena_spawn/red - name = "Red Team Spawnpoint" - color = "red" - team = ARENA_RED_TEAM - -/obj/machinery/arena_spawn/green - name = "Green Team Spawnpoint" - color = "green" - team = ARENA_GREEN_TEAM - -/obj/machinery/arena_spawn/proc/get_controller() - if(_controller && !QDELETED(_controller) && _controller.arena_id == arena_id) - return _controller - for(var/obj/machinery/computer/arena/A as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/computer/arena)) - if(A.arena_id == arena_id) - _controller = A - return _controller - -/obj/machinery/arena_spawn/attack_ghost(mob/user) - var/obj/machinery/computer/arena/C = get_controller() - if(!C) //Unlinked spawn - return - if(C.ready_to_spawn) - var/list/allowed_keys = C.team_keys[team] - if(!(user.ckey in allowed_keys)) - to_chat(user,span_warning("You're not on the team list.")) - return - C.spawn_member(src,user.ckey,team) - -#undef ARENA_GREEN_TEAM -#undef ARENA_RED_TEAM -#undef ARENA_DEFAULT_ID -#undef ARENA_CORNER_A -#undef ARENA_CORNER_B diff --git a/code/game/machinery/computer/buildandrepair.dm b/code/game/machinery/computer/buildandrepair.dm index ea2fbb3eb8eda..09e2bd381fdd4 100644 --- a/code/game/machinery/computer/buildandrepair.dm +++ b/code/game/machinery/computer/buildandrepair.dm @@ -10,7 +10,6 @@ . = ..() AddComponent(/datum/component/simple_rotation) register_context() - update_appearance(UPDATE_ICON_STATE) /obj/structure/frame/computer/deconstruct(disassembled = TRUE) if(!(obj_flags & NO_DECONSTRUCTION)) diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm index 37514c142fe8a..58bf0c75b1f9d 100644 --- a/code/game/machinery/computer/camera.dm +++ b/code/game/machinery/computer/camera.dm @@ -50,7 +50,7 @@ /obj/machinery/computer/security/ui_interact(mob/user, datum/tgui/ui) . = ..() - if(!user.can_perform_action(src, NEED_DEXTERITY)) //prevents monkeys from using camera consoles + if(!user.can_perform_action(src, NEED_DEXTERITY|ALLOW_SILICON_REACH)) //prevents monkeys from using camera consoles return // Update UI ui = SStgui.try_update_ui(user, src, ui) diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm index 23d4a38269b8a..8cbd7326d8854 100644 --- a/code/game/machinery/computer/communications.dm +++ b/code/game/machinery/computer/communications.dm @@ -95,19 +95,19 @@ /// Are we NOT a silicon, AND we're logged in as the captain? /obj/machinery/computer/communications/proc/authenticated_as_non_silicon_captain(mob/user) - if (issilicon(user)) + if (HAS_SILICON_ACCESS(user)) return FALSE return ACCESS_CAPTAIN in authorize_access /// Are we a silicon, OR we're logged in as the captain? /obj/machinery/computer/communications/proc/authenticated_as_silicon_or_captain(mob/user) - if (issilicon(user)) + if (HAS_SILICON_ACCESS(user)) return TRUE return ACCESS_CAPTAIN in authorize_access /// Are we a silicon, OR logged in? /obj/machinery/computer/communications/proc/authenticated(mob/user) - if (issilicon(user)) + if (HAS_SILICON_ACCESS(user)) return TRUE return authenticated @@ -188,7 +188,7 @@ return // Check if they have - if (!issilicon(usr)) + if (!HAS_SILICON_ACCESS(usr)) var/obj/item/held_item = usr.get_active_held_item() var/obj/item/card/id/id_card = held_item?.GetID() if (!istype(id_card)) @@ -287,7 +287,7 @@ state = STATE_MAIN if ("recallShuttle") // AIs cannot recall the shuttle - if (!authenticated(usr) || issilicon(usr) || syndicate) + if (!authenticated(usr) || HAS_SILICON_ACCESS(usr) || syndicate) return SSshuttle.cancelEvac(usr) if ("requestNukeCodes") @@ -503,7 +503,7 @@ "syndicate" = syndicate, ) - var/ui_state = issilicon(user) ? cyborg_state : state + var/ui_state = HAS_SILICON_ACCESS(user) ? cyborg_state : state var/has_connection = has_communication() data["hasConnection"] = has_connection @@ -520,9 +520,9 @@ data["safeCodeDeliveryWait"] = 0 data["safeCodeDeliveryArea"] = null - if (authenticated || issilicon(user)) + if (authenticated || HAS_SILICON_ACCESS(user)) data["authenticated"] = TRUE - data["canLogOut"] = !issilicon(user) + data["canLogOut"] = !HAS_SILICON_ACCESS(user) data["page"] = ui_state if ((obj_flags & EMAGGED) || syndicate) @@ -533,7 +533,7 @@ data["canBuyShuttles"] = can_buy_shuttles(user) data["canMakeAnnouncement"] = FALSE data["canMessageAssociates"] = FALSE - data["canRecallShuttles"] = !issilicon(user) + data["canRecallShuttles"] = !HAS_SILICON_ACCESS(user) data["canRequestNuke"] = FALSE data["canSendToSectors"] = FALSE data["canSetAlertLevel"] = FALSE @@ -544,7 +544,7 @@ data["aprilFools"] = check_holidays(APRIL_FOOLS) data["alertLevel"] = SSsecurity_level.get_current_level_as_text() data["authorizeName"] = authorize_name - data["canLogOut"] = !issilicon(user) + data["canLogOut"] = !HAS_SILICON_ACCESS(user) data["shuttleCanEvacOrFailReason"] = SSshuttle.canEvac() if(syndicate) data["shuttleCanEvacOrFailReason"] = "You cannot summon the shuttle from this console!" @@ -572,7 +572,7 @@ data["alertLevelTick"] = alert_level_tick data["canMakeAnnouncement"] = TRUE - data["canSetAlertLevel"] = issilicon(user) ? "NO_SWIPE_NEEDED" : "SWIPE_NEEDED" + data["canSetAlertLevel"] = HAS_SILICON_ACCESS(user) ? "NO_SWIPE_NEEDED" : "SWIPE_NEEDED" else if(syndicate) data["canMakeAnnouncement"] = TRUE @@ -673,7 +673,7 @@ return is_station_level(z_level) || is_centcom_level(z_level) /obj/machinery/computer/communications/proc/set_state(mob/user, new_state) - if (issilicon(user)) + if (HAS_SILICON_ACCESS(user)) cyborg_state = new_state else state = new_state @@ -683,7 +683,7 @@ /obj/machinery/computer/communications/proc/can_buy_shuttles(mob/user) if (!SSmapping.config.allow_custom_shuttles) return FALSE - if (issilicon(user)) + if (HAS_SILICON_ACCESS(user)) return FALSE var/has_access = FALSE @@ -726,7 +726,7 @@ return length(CONFIG_GET(keyed_list/cross_server)) > 0 /obj/machinery/computer/communications/proc/make_announcement(mob/living/user) - var/is_ai = issilicon(user) + var/is_ai = HAS_SILICON_ACCESS(user) if(!SScommunications.can_announce(user, is_ai)) to_chat(user, span_alert("Intercomms recharging. Please stand by.")) return diff --git a/code/game/machinery/computer/crew.dm b/code/game/machinery/computer/crew.dm index a6e9936b1d9cf..7b66d72a98c10 100644 --- a/code/game/machinery/computer/crew.dm +++ b/code/game/machinery/computer/crew.dm @@ -95,7 +95,8 @@ GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new) var/list/jobs = list( // Note that jobs divisible by 10 are considered heads of staff, and bolded // 00: Captain - JOB_CAPTAIN = 00, + JOB_CAPTAIN = 0, + JOB_HUMAN_AI = 1, // 10-19: Security JOB_HEAD_OF_SECURITY = 10, JOB_WARDEN = 11, @@ -182,7 +183,7 @@ GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new) z = T.z . = list( "sensors" = update_data(z), - "link_allowed" = isAI(user) + "link_allowed" = HAS_AI_ACCESS(user) ) /datum/crewmonitor/proc/update_data(z) diff --git a/code/game/machinery/computer/law.dm b/code/game/machinery/computer/law.dm index 423633d31ac35..372d75822b7e4 100644 --- a/code/game/machinery/computer/law.dm +++ b/code/game/machinery/computer/law.dm @@ -42,6 +42,11 @@ desc = "Used to upload laws to the AI." circuit = /obj/item/circuitboard/computer/aiupload +/obj/machinery/computer/upload/ai/Initialize(mapload) + . = ..() + if(mapload && HAS_TRAIT(SSstation, STATION_TRAIT_HUMAN_AI)) + return INITIALIZE_HINT_QDEL + /obj/machinery/computer/upload/ai/interact(mob/user) current = select_active_ai(user, z) diff --git a/code/game/machinery/computer/records/records.dm b/code/game/machinery/computer/records/records.dm index 531e1725e3325..76ee1f19bca01 100644 --- a/code/game/machinery/computer/records/records.dm +++ b/code/game/machinery/computer/records/records.dm @@ -35,6 +35,7 @@ return FALSE var/value = trim(params["value"], MAX_BROADCAST_LEN) + investigate_log("[key_name(usr)] changed the field: \"[field]\" with value: \"[target.vars[field]]\" to new value: \"[value || "Unknown"]\"", INVESTIGATE_RECORDS) target.vars[field] = value || "Unknown" return TRUE @@ -56,6 +57,7 @@ if("login") authenticated = secure_login(usr) + investigate_log("[key_name(usr)] [authenticated ? "successfully logged" : "failed to log"] into the [src].", INVESTIGATE_RECORDS) return TRUE if("logout") @@ -123,30 +125,12 @@ /obj/machinery/computer/records/proc/expunge_record_info(datum/record/crew/target) return -/// Detects whether a user can use buttons on the machine -/obj/machinery/computer/records/proc/has_auth(mob/user) - if(issilicon(user) || isAdminGhostAI(user)) // Silicons don't need to authenticate - return TRUE - - if(!isliving(user)) - return FALSE - var/mob/living/player = user - - var/obj/item/card/auth = player.get_idcard(TRUE) - if(!auth) - return FALSE - var/list/access = auth.GetAccess() - if(!check_access_list(access)) - return FALSE - - return TRUE - /// Inserts a new record into GLOB.manifest.general. Requires a photo to be taken. /obj/machinery/computer/records/proc/insert_new_record(mob/user, obj/item/photo/mugshot) if(!mugshot || !is_operational || !user.can_perform_action(src, ALLOW_SILICON_REACH)) return FALSE - if(!authenticated && !has_auth(user)) + if(!authenticated && !allowed(user)) balloon_alert(user, "access denied") playsound(src, 'sound/machines/terminal_error.ogg', 70, TRUE) return FALSE @@ -175,7 +159,7 @@ if(!user.can_perform_action(src, ALLOW_SILICON_REACH) || !is_operational) return FALSE - if(!has_auth(user)) + if(!allowed(user)) balloon_alert(user, "access denied") playsound(src, 'sound/machines/terminal_error.ogg', 70, TRUE) return FALSE diff --git a/code/game/machinery/computer/records/security.dm b/code/game/machinery/computer/records/security.dm index 27b8e75e545ef..6e44d112b4bab 100644 --- a/code/game/machinery/computer/records/security.dm +++ b/code/game/machinery/computer/records/security.dm @@ -159,6 +159,7 @@ return TRUE if("delete_record") + investigate_log("[usr] deleted record: \"[target]\".", INVESTIGATE_RECORDS) qdel(target) return TRUE @@ -175,8 +176,9 @@ return TRUE if("set_note") - var/note = params["note"] - target.security_note = trim(note, MAX_MESSAGE_LEN) + var/note = trim(params["note"], MAX_MESSAGE_LEN) + investigate_log("[usr] has changed the security note of record: \"[target]\" from \"[target.security_note]\" to \"[note]\".") + target.security_note = note return TRUE if("set_wanted") @@ -239,14 +241,19 @@ return FALSE if(user != editing_crime.author && !has_armory_access(user)) // only warden/hos/command can edit crimes they didn't author + investigate_log("[user] attempted to edit crime: \"[editing_crime.name]\" for target: \"[target.name]\" but failed due to lacking armoury access and not being the author of the crime.", INVESTIGATE_RECORDS) return FALSE if(params["name"] && length(params["name"]) > 2 && params["name"] != editing_crime.name) - editing_crime.name = trim(params["name"], MAX_CRIME_NAME_LEN) + var/new_name = trim(params["name"], MAX_CRIME_NAME_LEN) + investigate_log("[user] edited crime: \"[editing_crime.name]\" for target: \"[target.name]\", changing the name to: \"[new_name]\".", INVESTIGATE_RECORDS) + editing_crime.name = new_name return TRUE if(params["details"] && length(params["description"]) > 2 && params["name"] != editing_crime.name) - editing_crime.details = trim(params["details"], MAX_MESSAGE_LEN) + var/new_details = trim(params["details"], MAX_MESSAGE_LEN) + investigate_log("[user] edited crime \"[editing_crime.name]\" for target: \"[target.name]\", changing the details to: \"[new_details]\" from: \"[editing_crime.details]\".", INVESTIGATE_RECORDS) + editing_crime.details = new_details return TRUE return FALSE diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm index e624e3f33d705..b3c1e055679d0 100644 --- a/code/game/machinery/constructable_frame.dm +++ b/code/game/machinery/constructable_frame.dm @@ -13,6 +13,10 @@ /// The current (de/con)struction state of the frame var/state = FRAME_STATE_EMPTY +/obj/structure/frame/Initialize(mapload) + . = ..() + update_appearance(UPDATE_ICON_STATE) + /obj/structure/frame/examine(user) . = ..() if(circuit) diff --git a/code/game/machinery/dna_infuser/organ_sets/rat_organs.dm b/code/game/machinery/dna_infuser/organ_sets/rat_organs.dm index 092a4c7889a28..4e84870ec9c30 100644 --- a/code/game/machinery/dna_infuser/organ_sets/rat_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/rat_organs.dm @@ -65,26 +65,22 @@ /obj/item/organ/internal/heart/rat/on_mob_insert(mob/living/carbon/receiver) . = ..() - if(!. || !ishuman(receiver)) + if(!ishuman(receiver)) return var/mob/living/carbon/human/human_receiver = receiver - if(!human_receiver.can_mutate()) - return - human_receiver.dna.add_mutation(/datum/mutation/human/dwarfism) + if(human_receiver.can_mutate()) + human_receiver.dna.add_mutation(/datum/mutation/human/dwarfism) //but 1.5 damage - if(human_receiver.physiology) - human_receiver.physiology.damage_resistance -= 50 + human_receiver.physiology?.damage_resistance -= 50 /obj/item/organ/internal/heart/rat/on_mob_remove(mob/living/carbon/heartless, special) . = ..() if(!ishuman(heartless)) return var/mob/living/carbon/human/human_heartless = heartless - if(!human_heartless.can_mutate()) - return - human_heartless.dna.remove_mutation(/datum/mutation/human/dwarfism) - if(human_heartless.physiology) - human_heartless.physiology.damage_resistance += 50 + if(human_heartless.can_mutate()) + human_heartless.dna.remove_mutation(/datum/mutation/human/dwarfism) + human_heartless.physiology?.damage_resistance += 50 /// you occasionally squeak, and have some rat related verbal tics /obj/item/organ/internal/tongue/rat diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index abe2fc0c736cf..b575bbc1a959d 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -789,7 +789,7 @@ . = ..() if(.) return - if(!(issilicon(user) || isAdminGhostAI(user))) + if(!HAS_SILICON_ACCESS(user)) if(isElectrified() && shock(user, 100)) return @@ -986,7 +986,7 @@ return TRUE /obj/machinery/door/airlock/attackby(obj/item/C, mob/user, params) - if(!issilicon(user) && !isAdminGhostAI(user)) + if(!HAS_SILICON_ACCESS(user)) if(isElectrified() && (C.obj_flags & CONDUCTS_ELECTRICITY) && shock(user, 75)) return add_fingerprint(user) @@ -1522,9 +1522,8 @@ if(!disassembled) A?.update_integrity(A.max_integrity * 0.5) - else if(obj_flags & EMAGGED) - //no electronics nothing - else + + else if(!(obj_flags & EMAGGED)) var/obj/item/electronics/airlock/ae if(!electronics) ae = new/obj/item/electronics/airlock(loc) @@ -1673,7 +1672,7 @@ . = TRUE /obj/machinery/door/airlock/proc/user_allowed(mob/user) - return (issilicon(user) && canAIControl(user)) || isAdminGhostAI(user) + return (HAS_SILICON_ACCESS(user) && canAIControl(user)) || isAdminGhostAI(user) /obj/machinery/door/airlock/proc/shock_restore(mob/user) if(!user_allowed(user)) @@ -2295,6 +2294,7 @@ /obj/machinery/door/airlock/cult/Initialize(mapload) . = ..() new openingoverlaytype(loc) + AddElement(/datum/element/empprotection, EMP_PROTECT_ALL) /obj/machinery/door/airlock/cult/canAIControl(mob/user) return (IS_CULTIST(user) && !isAllPowerCut()) @@ -2345,9 +2345,6 @@ /obj/machinery/door/airlock/cult/narsie_act() return -/obj/machinery/door/airlock/cult/emp_act(severity) - return - /obj/machinery/door/airlock/cult/friendly friendly = TRUE diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index 38148ae8befca..83702cac9cb66 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -116,7 +116,7 @@ if(!can_open_with_hands) return . - if(isaicamera(user) || issilicon(user)) + if(isaicamera(user) || HAS_SILICON_ACCESS(user)) return . if(isnull(held_item) && Adjacent(user)) diff --git a/code/game/machinery/doors/passworddoor.dm b/code/game/machinery/doors/passworddoor.dm index 21b0bb2e1935f..2cea8479952c4 100644 --- a/code/game/machinery/doors/passworddoor.dm +++ b/code/game/machinery/doors/passworddoor.dm @@ -40,6 +40,7 @@ . = ..() if(voice_activated) become_hearing_sensitive() + AddElement(/datum/element/empprotection, EMP_PROTECT_ALL) /obj/machinery/door/password/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods = list(), message_range) . = ..() @@ -83,8 +84,5 @@ return TRUE return FALSE -/obj/machinery/door/password/emp_act(severity) - return - /obj/machinery/door/password/ex_act(severity, target) return FALSE diff --git a/code/game/machinery/machine_frame.dm b/code/game/machinery/machine_frame.dm index a3c074937f0f7..5a6ff12c3d102 100644 --- a/code/game/machinery/machine_frame.dm +++ b/code/game/machinery/machine_frame.dm @@ -12,7 +12,6 @@ /obj/structure/frame/machine/Initialize(mapload) . = ..() register_context() - update_appearance(UPDATE_ICON_STATE) /obj/structure/frame/machine/Destroy() QDEL_LIST(components) @@ -26,8 +25,8 @@ return ..() /obj/structure/frame/machine/try_dissassemble(mob/living/user, obj/item/tool, disassemble_time) - if(anchored) - balloon_alert(user, "must be unsecured first!") + if(anchored && state == FRAME_STATE_EMPTY) //when using a screwdriver on an incomplete frame(missing components) no point checking for this + balloon_alert(user, "must be unanchored first!") return FALSE return ..() @@ -36,14 +35,15 @@ if(isnull(held_item)) return + if(held_item.tool_behaviour == TOOL_WRENCH && !circuit?.needs_anchored) + context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Un" : ""]anchor" + return CONTEXTUAL_SCREENTIP_SET + switch(state) if(FRAME_STATE_EMPTY) if(istype(held_item, /obj/item/stack/cable_coil)) context[SCREENTIP_CONTEXT_LMB] = "Wire Frame" return CONTEXTUAL_SCREENTIP_SET - else if(held_item.tool_behaviour == TOOL_WRENCH) - context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Un" : ""]anchor" - return CONTEXTUAL_SCREENTIP_SET else if(held_item.tool_behaviour == TOOL_WELDER) context[SCREENTIP_CONTEXT_LMB] = "Unweld frame" return CONTEXTUAL_SCREENTIP_SET @@ -61,11 +61,6 @@ if(held_item.tool_behaviour == TOOL_CROWBAR) context[SCREENTIP_CONTEXT_LMB] = "Pry out components" return CONTEXTUAL_SCREENTIP_SET - else if(held_item.tool_behaviour == TOOL_WRENCH) - if(!circuit.needs_anchored) - context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Un" : ""]anchor" - return CONTEXTUAL_SCREENTIP_SET - return NONE else if(held_item.tool_behaviour == TOOL_SCREWDRIVER) var/needs_components = FALSE for(var/component in req_components) @@ -470,5 +465,6 @@ return TRUE /obj/structure/frame/machine/secured + icon_state = "box_1" state = FRAME_STATE_WIRED anchored = TRUE diff --git a/code/game/machinery/modular_shield.dm b/code/game/machinery/modular_shield.dm index f4e15cfa87813..63bd1875c6017 100644 --- a/code/game/machinery/modular_shield.dm +++ b/code/game/machinery/modular_shield.dm @@ -711,6 +711,7 @@ //Damage from emp /obj/structure/emergency_shield/modular/emp_act(severity) + . = ..() if(isnull(shield_generator)) qdel(src) return diff --git a/code/game/machinery/navbeacon.dm b/code/game/machinery/navbeacon.dm index 5caa41d908905..c15421cbf5abb 100644 --- a/code/game/machinery/navbeacon.dm +++ b/code/game/machinery/navbeacon.dm @@ -165,7 +165,7 @@ controls["cover_locked"] = cover_locked data["locked"] = controls_locked - data["siliconUser"] = issilicon(user) + data["siliconUser"] = HAS_SILICON_ACCESS(user) data["controls"] = controls return data @@ -190,7 +190,7 @@ controls_locked = !controls_locked return TRUE - if(controls_locked && !issilicon(usr)) + if(controls_locked && !HAS_SILICON_ACCESS(usr)) return switch(action) diff --git a/code/game/machinery/pipe/construction.dm b/code/game/machinery/pipe/construction.dm index 22f66fd1de73d..d9e3787fd9ead 100644 --- a/code/game/machinery/pipe/construction.dm +++ b/code/game/machinery/pipe/construction.dm @@ -85,6 +85,16 @@ Buildable meters /obj/item/pipe/quaternary/pipe icon_state_preview = "manifold4w" pipe_type = /obj/machinery/atmospherics/pipe/smart +/obj/item/pipe/quaternary/pipe/crafted + +/obj/item/pipe/quaternary/pipe/crafted/Initialize(mapload, _pipe_type, _dir, obj/machinery/atmospherics/make_from, device_color, device_init_dir = SOUTH) + . = ..() + pipe_type = /obj/machinery/atmospherics/pipe/smart + pipe_color = COLOR_VERY_LIGHT_GRAY + p_init_dir = ALL_CARDINALS + setDir(SOUTH) + update() + /obj/item/pipe/quaternary/he_pipe icon_state_preview = "he_manifold4w" pipe_type = /obj/machinery/atmospherics/pipe/heat_exchanging/manifold4w @@ -238,7 +248,7 @@ Buildable meters return TRUE // no conflicts found - var/obj/machinery/atmospherics/built_machine = new pipe_type(loc, , , p_init_dir) + var/obj/machinery/atmospherics/built_machine = new pipe_type(loc, null, fixed_dir(), p_init_dir) build_pipe(built_machine) built_machine.on_construction(user, pipe_color, piping_layer) transfer_fingerprints_to(built_machine) @@ -251,6 +261,23 @@ Buildable meters qdel(src) +/obj/item/pipe/welder_act(mob/living/user, obj/item/welder) + . = ..() + if(istype(pipe_type, /obj/machinery/atmospherics/components)) + return TRUE + if(!welder.tool_start_check(user, amount=2)) + return TRUE + add_fingerprint(user) + + if(welder.use_tool(src, user, 2 SECONDS, volume=2)) + new /obj/item/sliced_pipe(drop_location()) + user.visible_message( \ + "[user] welds \the [src] in two.", \ + span_notice("You weld \the [src] in two."), \ + span_hear("You hear welding.")) + + qdel(src) + /** * Attempt to automatically resolve a pipe conflict by reconfiguring any smart pipes involved. * @@ -329,9 +356,6 @@ Buildable meters return FALSE /obj/item/pipe/proc/build_pipe(obj/machinery/atmospherics/A) - A.setDir(fixed_dir()) - A.set_init_directions(p_init_dir) - if(pipename) A.name = pipename if(A.on) diff --git a/code/game/machinery/recycler.dm b/code/game/machinery/recycler.dm index 1014393c00836..5d4fd671c9623 100644 --- a/code/game/machinery/recycler.dm +++ b/code/game/machinery/recycler.dm @@ -19,22 +19,9 @@ var/datum/component/material_container/materials /obj/machinery/recycler/Initialize(mapload) - var/list/allowed_materials = list( - /datum/material/iron, - /datum/material/glass, - /datum/material/silver, - /datum/material/plasma, - /datum/material/gold, - /datum/material/diamond, - /datum/material/plastic, - /datum/material/uranium, - /datum/material/bananium, - /datum/material/titanium, - /datum/material/bluespace - ) materials = AddComponent( /datum/component/material_container, \ - allowed_materials, \ + SSmaterials.materials_by_category[MAT_CATEGORY_SILO], \ INFINITY, \ MATCONTAINER_NO_INSERT \ ) diff --git a/code/game/machinery/shieldgen.dm b/code/game/machinery/shieldgen.dm index f95aa5f2bc7a6..f0aea2153c8df 100644 --- a/code/game/machinery/shieldgen.dm +++ b/code/game/machinery/shieldgen.dm @@ -187,7 +187,7 @@ . = ..() if(.) return - if(locked && !issilicon(user)) + if(locked && !HAS_SILICON_ACCESS(user)) to_chat(user, span_warning("The machine is locked, you are unable to use it!")) return if(panel_open) @@ -479,7 +479,7 @@ if(!anchored) balloon_alert(user, "not secured!") return - if(locked && !issilicon(user)) + if(locked && !HAS_SILICON_ACCESS(user)) balloon_alert(user, "locked!") return if(!powernet) diff --git a/code/game/machinery/slotmachine.dm b/code/game/machinery/slotmachine.dm index ddfe76f56af1d..b7eec3994b8d1 100644 --- a/code/game/machinery/slotmachine.dm +++ b/code/game/machinery/slotmachine.dm @@ -10,7 +10,7 @@ #define JACKPOT 10000 #define SPIN_TIME 65 //As always, deciseconds. #define REEL_DEACTIVATE_DELAY 7 -#define SEVEN "7" +#define JACKPOT_SEVENS FA_ICON_7 #define HOLOCHIP 1 #define COIN 2 @@ -24,7 +24,7 @@ density = TRUE circuit = /obj/item/circuitboard/computer/slot_machine light_color = LIGHT_COLOR_BROWN - interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_SET_MACHINE // don't need to be literate to play slots + interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON // don't need to be literate to play slots var/money = 3000 //How much money it has CONSUMED var/plays = 0 var/working = FALSE @@ -32,9 +32,19 @@ var/jackpots = 0 var/paymode = HOLOCHIP //toggles between HOLOCHIP/COIN, defined above var/cointype = /obj/item/coin/iron //default cointype + /// Icons that can be displayed by the slot machine. + var/static/list/icons = list( + FA_ICON_LEMON = list("value" = 2, "colour" = "yellow"), + FA_ICON_STAR = list("value" = 2, "colour" = "yellow"), + FA_ICON_BOMB = list("value" = 2, "colour" = "red"), + FA_ICON_BIOHAZARD = list("value" = 2, "colour" = "green"), + FA_ICON_APPLE_WHOLE = list("value" = 2, "colour" = "red"), + FA_ICON_7 = list("value" = 1, "colour" = "yellow"), + FA_ICON_DOLLAR_SIGN = list("value" = 2, "colour" = "green"), + ) + var/static/list/coinvalues var/list/reels = list(list("", "", "") = 0, list("", "", "") = 0, list("", "", "") = 0, list("", "", "") = 0, list("", "", "") = 0) - var/list/symbols = list(SEVEN = 1, "&" = 2, "@" = 2, "$" = 2, "?" = 2, "#" = 2, "!" = 2, "%" = 2) //if people are winning too much, multiply every number in this list by 2 and see if they are still winning too much. var/static/list/ray_filter = list(type = "rays", y = 16, size = 40, density = 4, color = COLOR_RED_LIGHT, factor = 15, flags = FILTER_OVERLAY) /obj/machinery/computer/slot_machine/Initialize(mapload) @@ -42,13 +52,13 @@ jackpots = rand(1, 4) //false hope plays = rand(75, 200) - INVOKE_ASYNC(src, PROC_REF(toggle_reel_spin), TRUE)//The reels won't spin unless we activate them + toggle_reel_spin_sync(1) //The reels won't spin unless we activate them var/list/reel = reels[1] for(var/i in 1 to reel.len) //Populate the reels. randomize_reels() - INVOKE_ASYNC(src, PROC_REF(toggle_reel_spin), FALSE) + toggle_reel_spin_sync(0) if (isnull(coinvalues)) coinvalues = list() @@ -84,46 +94,49 @@ icon_screen = "slots_screen" return ..() -/obj/machinery/computer/slot_machine/attackby(obj/item/I, mob/living/user, params) - if(istype(I, /obj/item/coin)) - var/obj/item/coin/C = I + +/obj/machinery/computer/slot_machine/item_interaction(mob/living/user, obj/item/inserted, list/modifiers, is_right_clicking) + if(istype(inserted, /obj/item/coin)) + var/obj/item/coin/inserted_coin = inserted if(paymode == COIN) if(prob(2)) - if(!user.transferItemToLoc(C, drop_location(), silent = FALSE)) + if(!user.transferItemToLoc(inserted_coin, drop_location(), silent = FALSE)) return - C.throw_at(user, 3, 10) + inserted_coin.throw_at(user, 3, 10) if(prob(10)) balance = max(balance - SPIN_PRICE, 0) to_chat(user, span_warning("[src] spits your coin back out!")) else - if(!user.temporarilyRemoveItemFromInventory(C)) + if(!user.temporarilyRemoveItemFromInventory(inserted_coin)) return - to_chat(user, span_notice("You insert [C] into [src]'s slot!")) - balance += C.value - qdel(C) + balloon_alert(user, "coin insterted") + balance += inserted_coin.value + qdel(inserted_coin) else - to_chat(user, span_warning("This machine is only accepting holochips!")) - else if(istype(I, /obj/item/holochip)) + balloon_alert(user, "holochips only!") + + else if(istype(inserted, /obj/item/holochip)) if(paymode == HOLOCHIP) - var/obj/item/holochip/H = I - if(!user.temporarilyRemoveItemFromInventory(H)) + var/obj/item/holochip/inserted_chip = inserted + if(!user.temporarilyRemoveItemFromInventory(inserted_chip)) return - to_chat(user, span_notice("You insert [H.credits] holocredits into [src]'s slot!")) - balance += H.credits - qdel(H) + balloon_alert(user, "[inserted_chip.credits] credit[inserted_chip.credits == 1 ? "" : "s"] inserted") + balance += inserted_chip.credits + qdel(inserted_chip) else - to_chat(user, span_warning("This machine is only accepting coins!")) - else if(I.tool_behaviour == TOOL_MULTITOOL) + balloon_alert(user, "coins only!") + + else if(inserted.tool_behaviour == TOOL_MULTITOOL) if(balance > 0) visible_message("[src] says, 'ERROR! Please empty the machine balance before altering paymode'") //Prevents converting coins into holocredits and vice versa else if(paymode == HOLOCHIP) paymode = COIN - visible_message("[src] says, 'This machine now works with COINS!'") + balloon_alert(user, "now using coins") else paymode = HOLOCHIP - visible_message("[src] says, 'This machine now works with HOLOCHIPS!'") + balloon_alert(user, "now using holochips") else return ..() @@ -138,49 +151,54 @@ balloon_alert(user, "machine rigged") return TRUE -/obj/machinery/computer/slot_machine/ui_interact(mob/living/user) +/obj/machinery/computer/slot_machine/ui_interact(mob/living/user, datum/tgui/ui) + . = ..() + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "SlotMachine", name) + ui.open() + +/obj/machinery/computer/slot_machine/ui_static_data(mob/user) + var/list/data = list() + data["icons"] = list() + for(var/icon_name in icons) + var/list/icon = icons[icon_name] + icon += list("icon" = icon_name) + data["icons"] += list(icon) + data["cost"] = SPIN_PRICE + data["jackpot"] = JACKPOT + + return data + +/obj/machinery/computer/slot_machine/ui_data(mob/user) + var/list/data = list() + var/list/reel_states = list() + for(var/reel_state in reels) + reel_states += list(reel_state) + data["state"] = reel_states + data["balance"] = balance + data["working"] = working + data["money"] = money + data["plays"] = plays + data["jackpots"] = jackpots + data["paymode"] = paymode + return data + + +/obj/machinery/computer/slot_machine/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) . = ..() - var/reeltext = {"
- /*****^*****^*****^*****^*****\\
- | \[[reels[1][1]]\] | \[[reels[2][1]]\] | \[[reels[3][1]]\] | \[[reels[4][1]]\] | \[[reels[5][1]]\] |
- | \[[reels[1][2]]\] | \[[reels[2][2]]\] | \[[reels[3][2]]\] | \[[reels[4][2]]\] | \[[reels[5][2]]\] |
- | \[[reels[1][3]]\] | \[[reels[2][3]]\] | \[[reels[3][3]]\] | \[[reels[4][3]]\] | \[[reels[5][3]]\] |
- \\*****v*****v*****v*****v*****/
-
"} - - var/dat - if(working) - dat = reeltext - - else - dat = {"Five credits to play!
- Prize Money Available: [money] (jackpot payout is ALWAYS 100%!)
- Credit Remaining: [balance]
- [plays] players have tried their luck today, and [jackpots] have won a jackpot!
-

- Play!
-
- [reeltext] -
"} - if(balance > 0) - dat+="Refund balance
" - - var/datum/browser/popup = new(user, "slotmachine", "Slot Machine") - popup.set_content(dat) - popup.open() - -/obj/machinery/computer/slot_machine/Topic(href, href_list) - . = ..() //Sanity checks. if(.) - return . - - if(href_list["spin"]) - spin(usr) + return - else if(href_list["refund"]) - if(balance > 0) - give_payout(balance) - balance = 0 + switch(action) + if("spin") + spin(ui.user) + return TRUE + if("payout") + if(balance > 0) + give_payout(balance) + balance = 0 + return TRUE /obj/machinery/computer/slot_machine/emp_act(severity) . = ..() @@ -214,8 +232,6 @@ toggle_reel_spin(1) update_appearance() - updateDialog() - var/spin_loop = addtimer(CALLBACK(src, PROC_REF(do_spin)), 2, TIMER_LOOP|TIMER_STOPPABLE) addtimer(CALLBACK(src, PROC_REF(finish_spinning), spin_loop, user, the_name), SPIN_TIME - (REEL_DEACTIVATE_DELAY * reels.len)) @@ -223,7 +239,6 @@ /obj/machinery/computer/slot_machine/proc/do_spin() randomize_reels() - updateDialog() use_power(active_power_usage) /obj/machinery/computer/slot_machine/proc/finish_spinning(spin_loop, mob/user, the_name) @@ -232,50 +247,64 @@ deltimer(spin_loop) give_prizes(the_name, user) update_appearance() - updateDialog() +/// Check if the machine can be spun /obj/machinery/computer/slot_machine/proc/can_spin(mob/user) if(machine_stat & NOPOWER) - to_chat(user, span_warning("The slot machine has no power!")) + balloon_alert(user, "no power!") return FALSE if(machine_stat & BROKEN) - to_chat(user, span_warning("The slot machine is broken!")) + balloon_alert(user, "machine broken!") return FALSE if(working) - to_chat(user, span_warning("You need to wait until the machine stops spinning before you can play again!")) + balloon_alert(user, "already spinning!") return FALSE if(balance < SPIN_PRICE) - to_chat(user, span_warning("Insufficient money to play!")) + balloon_alert(user, "insufficient balance!") return FALSE return TRUE +/// Sets the spinning states of all reels to value, with a delay between them /obj/machinery/computer/slot_machine/proc/toggle_reel_spin(value, delay = 0) //value is 1 or 0 aka on or off for(var/list/reel in reels) if(!value) playsound(src, 'sound/machines/ding_short.ogg', 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) reels[reel] = value - sleep(delay) + if(delay) + sleep(delay) +/// Same as toggle_reel_spin, but without the delay and runs synchronously +/obj/machinery/computer/slot_machine/proc/toggle_reel_spin_sync(value) + for(var/list/reel in reels) + reels[reel] = value + +/// Randomize the states of all reels /obj/machinery/computer/slot_machine/proc/randomize_reels() for(var/reel in reels) if(reels[reel]) reel[3] = reel[2] reel[2] = reel[1] - reel[1] = pick(symbols) + var/chosen = pick(icons) + reel[1] = icons[chosen] + list("icon_name" = chosen) +/// Checks if any prizes have been won, and pays them out /obj/machinery/computer/slot_machine/proc/give_prizes(usrname, mob/user) var/linelength = get_lines() var/did_player_win = TRUE - if(reels[1][2] + reels[2][2] + reels[3][2] + reels[4][2] + reels[5][2] == "[SEVEN][SEVEN][SEVEN][SEVEN][SEVEN]") - visible_message("[src] says, 'JACKPOT! You win [money] credits!'") + if(check_jackpot(FA_ICON_BOMB)) + var/obj/item/grenade/flashbang/bang = new(get_turf(src)) + bang.arm_grenade(null, 1 SECONDS) + + else if(check_jackpot(JACKPOT_SEVENS)) + var/prize = money + JACKPOT + visible_message("[src] says, 'JACKPOT! You win [prize] credits!'") priority_announce("Congratulations to [user ? user.real_name : usrname] for winning the jackpot at the slot machine in [get_area(src)]!") jackpots += 1 - balance += money - give_payout(JACKPOT) money = 0 if(paymode == HOLOCHIP) - new /obj/item/holochip(loc, JACKPOT) + new /obj/item/holochip(loc, prize) else for(var/i in 1 to 5) cointype = pick(subtypesof(/obj/item/coin)) @@ -298,7 +327,8 @@ money = max(money - SPIN_PRICE * 4, money) else - to_chat(user, span_warning("No luck!")) + balloon_alert(user, "no luck!") + playsound(src, 'sound/machines/buzz-sigh.ogg', 50) did_player_win = FALSE if(did_player_win) @@ -307,31 +337,38 @@ addtimer(CALLBACK(src, TYPE_PROC_REF(/datum, remove_filter), "jackpot_rays"), 3 SECONDS) playsound(src, 'sound/machines/roulettejackpot.ogg', 50, TRUE) +/// Checks for a jackpot (5 matching icons in the middle row) with the given icon name +/obj/machinery/computer/slot_machine/proc/check_jackpot(name) + return reels[1][2]["icon_name"] + reels[2][2]["icon_name"] + reels[3][2]["icon_name"] + reels[4][2]["icon_name"] + reels[5][2]["icon_name"] == "[name][name][name][name][name]" + +/// Finds the largest number of consecutive matching icons in a row /obj/machinery/computer/slot_machine/proc/get_lines() var/amountthesame for(var/i in 1 to 3) - var/inputtext = reels[1][i] + reels[2][i] + reels[3][i] + reels[4][i] + reels[5][i] - for(var/symbol in symbols) + var/inputtext = reels[1][i]["icon_name"] + reels[2][i]["icon_name"] + reels[3][i]["icon_name"] + reels[4][i]["icon_name"] + reels[5][i]["icon_name"] + for(var/icon in icons) var/j = 3 //The lowest value we have to check for. - var/symboltext = symbol + symbol + symbol + var/symboltext = icon + icon + icon while(j <= 5) if(findtext(inputtext, symboltext)) amountthesame = max(j, amountthesame) j++ - symboltext += symbol + symboltext += icon if(amountthesame) break return amountthesame +/// Give the specified amount of money. If the amount is greater than the amount of prize money available, add the difference as balance /obj/machinery/computer/slot_machine/proc/give_money(amount) - var/amount_to_give = money >= amount ? amount : money - var/surplus = amount_to_give - give_payout(amount_to_give) - money = max(0, money - amount) + var/amount_to_give = min(amount, money) + var/surplus = amount - give_payout(amount_to_give) + money -= amount_to_give balance += surplus +/// Pay out the specified amount in either coins or holochips /obj/machinery/computer/slot_machine/proc/give_payout(amount) if(paymode == HOLOCHIP) cointype = /obj/item/holochip @@ -348,23 +385,25 @@ return amount -/obj/machinery/computer/slot_machine/proc/dispense(amount = 0, cointype = /obj/item/coin/silver, mob/living/target, throwit = 0) +/// Dispense the given amount. If machine is set to use coins, will use the specified coin type. +/// If throwit and target are set, will launch the payment at the target +/obj/machinery/computer/slot_machine/proc/dispense(amount = 0, cointype = /obj/item/coin/silver, throwit = FALSE, mob/living/target) if(paymode == HOLOCHIP) - var/obj/item/holochip/H = new /obj/item/holochip(loc,amount) + var/obj/item/holochip/chip = new /obj/item/holochip(loc,amount) if(throwit && target) - H.throw_at(target, 3, 10) + chip.throw_at(target, 3, 10) else var/value = coinvalues["[cointype]"] if(value <= 0) CRASH("Coin value of zero, refusing to payout in dispenser") while(amount >= value) - var/obj/item/coin/C = new cointype(loc) //DOUBLE THE PAIN + var/obj/item/coin/thrown_coin = new cointype(loc) //DOUBLE THE PAIN amount -= value if(throwit && target) - C.throw_at(target, 3, 10) + thrown_coin.throw_at(target, 3, 10) else - random_step(C, 2, 40) + random_step(thrown_coin, 2, 40) playsound(src, pick(list('sound/machines/coindrop.ogg', 'sound/machines/coindrop2.ogg')), 50, TRUE) return amount @@ -374,7 +413,7 @@ #undef HOLOCHIP #undef JACKPOT #undef REEL_DEACTIVATE_DELAY -#undef SEVEN +#undef JACKPOT_SEVENS #undef SMALL_PRIZE #undef SPIN_PRICE #undef SPIN_TIME diff --git a/code/game/machinery/suit_storage_unit.dm b/code/game/machinery/suit_storage_unit.dm index b34f7873648cd..f96e1c1199bf2 100644 --- a/code/game/machinery/suit_storage_unit.dm +++ b/code/game/machinery/suit_storage_unit.dm @@ -375,7 +375,7 @@ src, choices, custom_check = CALLBACK(src, PROC_REF(check_interactable), user), - require_near = !issilicon(user), + require_near = !HAS_SILICON_ACCESS(user), autopick_single_option = FALSE ) diff --git a/code/game/machinery/syndicatebomb.dm b/code/game/machinery/syndicatebomb.dm index ab381b14fad25..ebe24b449748b 100644 --- a/code/game/machinery/syndicatebomb.dm +++ b/code/game/machinery/syndicatebomb.dm @@ -605,7 +605,7 @@ var/list/choosable_dimensions = list() var/datum/radial_menu_choice/null_choice = new null_choice.name = DIMENSION_CHOICE_RANDOM - choosable_dimensions += null_choice + choosable_dimensions[DIMENSION_CHOICE_RANDOM] = null_choice for(var/datum/dimension_theme/theme as anything in SSmaterials.dimensional_themes) var/datum/radial_menu_choice/theme_choice = new theme_choice.image = image(initial(theme.icon), initial(theme.icon_state)) @@ -631,9 +631,11 @@ var/theme_count = length(SSmaterials.dimensional_themes) var/num_affected = 0 for(var/turf/affected as anything in affected_turfs) - var/datum/dimension_theme/theme_to_use = chosen_theme + var/datum/dimension_theme/theme_to_use if(isnull(chosen_theme)) theme_to_use = SSmaterials.dimensional_themes[SSmaterials.dimensional_themes[rand(1, theme_count)]] + else + theme_to_use = SSmaterials.dimensional_themes[chosen_theme] if(!theme_to_use.can_convert(affected)) continue num_affected++ diff --git a/code/game/machinery/telecomms/machine_interactions.dm b/code/game/machinery/telecomms/machine_interactions.dm index 3fb8627169966..5086f7e91a7f1 100644 --- a/code/game/machinery/telecomms/machine_interactions.dm +++ b/code/game/machinery/telecomms/machine_interactions.dm @@ -88,7 +88,7 @@ return var/mob/living/current_user = usr - if(!issilicon(current_user)) + if(!HAS_SILICON_ACCESS(current_user)) if(!istype(current_user.get_active_held_item(), /obj/item/multitool)) return @@ -248,7 +248,7 @@ /obj/machinery/telecomms/proc/get_multitool(mob/user) var/obj/item/multitool/multitool = null // Let's double check - if(!issilicon(user) && istype(user.get_active_held_item(), /obj/item/multitool)) + if(!HAS_SILICON_ACCESS(user) && istype(user.get_active_held_item(), /obj/item/multitool)) multitool = user.get_active_held_item() else if(isAI(user)) var/mob/living/silicon/ai/U = user @@ -259,6 +259,6 @@ return multitool /obj/machinery/telecomms/proc/canAccess(mob/user) - if(issilicon(user) || in_range(user, src)) + if(HAS_SILICON_ACCESS(user) || in_range(user, src)) return TRUE return FALSE diff --git a/code/game/machinery/washing_machine.dm b/code/game/machinery/washing_machine.dm index 52644b8918bf9..a51fb9304e1c0 100644 --- a/code/game/machinery/washing_machine.dm +++ b/code/game/machinery/washing_machine.dm @@ -24,7 +24,8 @@ GLOBAL_LIST_INIT(dye_registry, list( DYE_REDCOAT = /obj/item/clothing/under/costume/redcoat, DYE_PRISONER = /obj/item/clothing/under/rank/prisoner, DYE_SYNDICATE = /obj/item/clothing/under/syndicate, - DYE_CENTCOM = /obj/item/clothing/under/rank/centcom/commander + DYE_CENTCOM = /obj/item/clothing/under/rank/centcom/commander, + DYE_COSMIC = /obj/item/clothing/under/rank/station_trait/human_ai, ), DYE_REGISTRY_JUMPSKIRT = list( DYE_RED = /obj/item/clothing/under/color/jumpskirt/red, diff --git a/code/game/objects/effects/anomalies/anomalies_ectoplasm.dm b/code/game/objects/effects/anomalies/anomalies_ectoplasm.dm index 7a04aaf2435f5..412cca0495299 100644 --- a/code/game/objects/effects/anomalies/anomalies_ectoplasm.dm +++ b/code/game/objects/effects/anomalies/anomalies_ectoplasm.dm @@ -178,7 +178,7 @@ candidate_list += GLOB.current_observers_list candidate_list += GLOB.dead_player_list - var/list/candidates = SSpolling.poll_candidates("Would you like to participate in a spooky ghost swarm? (Warning: you will not be able to return to your body!)", check_jobban = ROLE_SENTIENCE, poll_time = 10 SECONDS, group = candidate_list, pic_source = src, role_name_text = "ghost swarm") + var/list/candidates = SSpolling.poll_candidates("Would you like to participate in a spooky ghost swarm? (Warning: you will not be able to return to your body!)", check_jobban = ROLE_SENTIENCE, poll_time = 10 SECONDS, group = candidate_list, alert_pic = src, role_name_text = "ghost swarm") for(var/mob/dead/observer/candidate_ghost as anything in candidates) var/mob/living/basic/ghost/swarm/new_ghost = new(get_turf(src)) ghosts_spawned += new_ghost diff --git a/code/game/objects/effects/anomalies/anomalies_pyroclastic.dm b/code/game/objects/effects/anomalies/anomalies_pyroclastic.dm index 87221eeec9fa6..a4880fdc26738 100644 --- a/code/game/objects/effects/anomalies/anomalies_pyroclastic.dm +++ b/code/game/objects/effects/anomalies/anomalies_pyroclastic.dm @@ -39,12 +39,10 @@ var/datum/action/innate/slime/reproduce/repro_action = new repro_action.Grant(pyro) - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as a pyroclastic anomaly slime?", check_jobban = ROLE_SENTIENCE, poll_time = 10 SECONDS, target_mob = pyro, ignore_category = POLL_IGNORE_PYROSLIME, pic_source = pyro, role_name_text = "pyroclastic anomaly slime") - if(!LAZYLEN(candidates)) + var/mob/chosen_one = SSpolling.poll_ghosts_for_target(check_jobban = ROLE_SENTIENCE, poll_time = 10 SECONDS, checked_target = pyro, ignore_category = POLL_IGNORE_PYROSLIME, alert_pic = pyro, role_name_text = "pyroclastic anomaly slime") + if(isnull(chosen_one)) return - - var/mob/dead/observer/chosen = pick(candidates) - pyro.key = chosen.key + pyro.key = chosen_one.key pyro.mind.special_role = ROLE_PYROCLASTIC_SLIME pyro.mind.add_antag_datum(/datum/antagonist/pyro_slime) pyro.log_message("was made into a slime by pyroclastic anomaly", LOG_GAME) diff --git a/code/game/objects/effects/countdown.dm b/code/game/objects/effects/countdown.dm index ab422dda3d156..d83440ee9bd36 100644 --- a/code/game/objects/effects/countdown.dm +++ b/code/game/objects/effects/countdown.dm @@ -162,19 +162,6 @@ var/time_left = max(0, (H.finish_time - world.time) / 10) return round(time_left) -/obj/effect/countdown/arena - invisibility = INVISIBILITY_NONE - name = "arena countdown" - -/obj/effect/countdown/arena/get_value() - var/obj/machinery/arena_spawn/A = attached_to - if(!istype(A)) - return - else - var/obj/machinery/computer/arena/C = A.get_controller() - var/time_left = max(0, (C.start_time - world.time) / 10) - return round(time_left) - /obj/effect/countdown/flower_bud name = "flower bud countdown" diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm index 7fe6c59075f3f..4b23987b97914 100644 --- a/code/game/objects/effects/decals/cleanable/misc.dm +++ b/code/game/objects/effects/decals/cleanable/misc.dm @@ -129,6 +129,7 @@ desc = "Somebody should remove that." gender = NEUTER layer = WALL_OBJ_LAYER + icon = 'icons/effects/web.dmi' icon_state = "cobweb1" resistance_flags = FLAMMABLE beauty = -100 diff --git a/code/game/objects/effects/effect_system/effects_sparks.dm b/code/game/objects/effects/effect_system/effects_sparks.dm index c3fad6d26b61d..874c53fa83c7d 100644 --- a/code/game/objects/effects/effect_system/effects_sparks.dm +++ b/code/game/objects/effects/effect_system/effects_sparks.dm @@ -17,8 +17,8 @@ icon_state = "sparks" anchored = TRUE light_system = OVERLAY_LIGHT - light_range = 2 - light_power = 0.5 + light_range = 1.5 + light_power = 0.8 light_color = LIGHT_COLOR_FIRE /obj/effect/particle_effect/sparks/Initialize(mapload) diff --git a/code/game/objects/effects/effects.dm b/code/game/objects/effects/effects.dm index 377c8470480be..0c050579de45b 100644 --- a/code/game/objects/effects/effects.dm +++ b/code/game/objects/effects/effects.dm @@ -48,6 +48,7 @@ ///The abstract effect ignores even more effects and is often typechecked for atoms that should truly not be fucked with. /obj/effect/abstract + resistance_flags = parent_type::resistance_flags | SHUTTLE_CRUSH_PROOF /obj/effect/abstract/singularity_pull() return diff --git a/code/game/objects/effects/landmarks.dm b/code/game/objects/effects/landmarks.dm index a7934c94f31cb..5c4964cca058a 100644 --- a/code/game/objects/effects/landmarks.dm +++ b/code/game/objects/effects/landmarks.dm @@ -36,7 +36,7 @@ INITIALIZE_IMMEDIATE(/obj/effect/landmark) /obj/effect/landmark/start/proc/after_round_start() // We'd like to keep these around for unit tests, so we can check that they exist. -#ifndef UNIT_TESTS +#if !defined(UNIT_TESTS) && !defined(MAP_TEST) if(delete_after_roundstart) qdel(src) #endif diff --git a/code/game/objects/effects/lighting.dm b/code/game/objects/effects/lighting.dm index 1de9fad39eee4..caeedbd22bb89 100644 --- a/code/game/objects/effects/lighting.dm +++ b/code/game/objects/effects/lighting.dm @@ -37,6 +37,7 @@ name = "mob fire lighting" light_color = LIGHT_COLOR_FIRE light_range = LIGHT_RANGE_FIRE + light_power = 2 /obj/effect/dummy/lighting_obj/moblight/species name = "species lighting" diff --git a/code/game/objects/effects/posters/contraband.dm b/code/game/objects/effects/posters/contraband.dm index 2bb2fcce50e46..52528c251b659 100644 --- a/code/game/objects/effects/posters/contraband.dm +++ b/code/game/objects/effects/posters/contraband.dm @@ -625,3 +625,35 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/blood_geometer icon_state = "singletank_bomb" MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/singletank_bomb, 32) + +///a special poster meant to fool people into thinking this is a bombable wall at a glance. +/obj/structure/sign/poster/contraband/fake_bombable + name = "fake bombable poster" + desc = "We do a little trolling." + icon_state = "fake_bombable" + never_random = TRUE + +/obj/structure/sign/poster/contraband/fake_bombable/Initialize(mapload) + . = ..() + var/turf/our_wall = get_turf_pixel(src) + name = our_wall.name + +/obj/structure/sign/poster/contraband/fake_bombable/examine(mob/user) + var/turf/our_wall = get_turf_pixel(src) + . = our_wall.examine(user) + . += span_notice("It seems to be slightly cracked...") + +/obj/structure/sign/poster/contraband/fake_bombable/ex_act(severity, target) + addtimer(CALLBACK(src, PROC_REF(fall_off_wall)), 2.5 SECONDS) + return FALSE + +/obj/structure/sign/poster/contraband/fake_bombable/proc/fall_off_wall() + if(QDELETED(src) || !isturf(loc)) + return + var/turf/our_wall = get_turf_pixel(src) + our_wall.balloon_alert_to_viewers("it was a ruse!") + roll_and_drop(loc) + playsound(loc, 'sound/items/handling/paper_drop.ogg', 50, TRUE) + + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/fake_bombable, 32) diff --git a/code/game/objects/effects/spawners/random/contraband.dm b/code/game/objects/effects/spawners/random/contraband.dm index ca5acbdbe6767..e65a73cfe4ce7 100644 --- a/code/game/objects/effects/spawners/random/contraband.dm +++ b/code/game/objects/effects/spawners/random/contraband.dm @@ -21,22 +21,20 @@ /obj/item/storage/fancy/cigarettes/cigpack_syndicate = 10, /obj/item/storage/fancy/cigarettes/cigpack_shadyjims = 10, /obj/item/storage/box/donkpockets = 10, + /obj/effect/spawner/random/contraband/plus = 10, /obj/item/reagent_containers/pill/maintenance = 5, - /obj/effect/spawner/random/contraband/plus = 5, ) /obj/effect/spawner/random/contraband/plus name = "contraband loot spawner plus" desc = "Where'd ya find this?" loot = list( - /obj/effect/spawner/random/contraband/prison = 40, /obj/item/clothing/under/syndicate = 20, /obj/item/reagent_containers/cup/bottle/thermite = 20, - /obj/item/reagent_containers/pill/maintenance = 10, /obj/item/restraints/legcuffs/beartrap = 10, - /obj/effect/spawner/random/contraband/narcotics = 10, - /obj/item/seeds/kronkus = 5, - /obj/item/seeds/odious_puffball = 5, + /obj/item/food/drug/saturnx = 5, + /obj/item/reagent_containers/cup/blastoff_ampoule = 5, + /obj/item/food/drug/moon_rock = 5, /obj/item/grenade/empgrenade = 5, /obj/effect/spawner/random/contraband/armory = 1, ) diff --git a/code/game/objects/effects/spawners/random/vending.dm b/code/game/objects/effects/spawners/random/vending.dm index 74ece7f24f93d..014f07d2967c4 100644 --- a/code/game/objects/effects/spawners/random/vending.dm +++ b/code/game/objects/effects/spawners/random/vending.dm @@ -18,6 +18,11 @@ loot_type_path = /obj/machinery/vending/snack loot = list() +/obj/effect/spawner/random/vending/snackvend/Initialize(mapload) + if(check_holidays(HOTDOG_DAY)) + loot += /obj/machinery/vending/hotdog + return ..() + /obj/effect/spawner/random/vending/colavend name = "spawn random cola vending machine" desc = "Automagically transforms into a random cola vendor. If you see this while in a shift, please create a bug report." diff --git a/code/game/objects/effects/spiderwebs.dm b/code/game/objects/effects/spiderwebs.dm index 5023f9bd8254e..2d0f1b9b14de2 100644 --- a/code/game/objects/effects/spiderwebs.dm +++ b/code/game/objects/effects/spiderwebs.dm @@ -1,7 +1,8 @@ -//generic procs copied from obj/effect/alien +#define SPIDER_WEB_TINT "web_colour_tint" + /obj/structure/spider name = "web" - icon = 'icons/effects/effects.dmi' + icon = 'icons/effects/web.dmi' desc = "It's stringy and sticky." anchored = TRUE density = FALSE @@ -22,7 +23,7 @@ damage_amount *= 1.25 if(BRUTE) damage_amount *= 0.25 - . = ..() + return ..() /obj/structure/spider/should_atmos_process(datum/gas_mixture/air, exposed_temperature) return exposed_temperature > 350 @@ -31,17 +32,37 @@ take_damage(5, BURN, 0, 0) /obj/structure/spider/stickyweb + plane = FLOOR_PLANE + layer = MID_TURF_LAYER + icon = 'icons/obj/smooth_structures/stickyweb.dmi' + base_icon_state = "stickyweb" + icon_state = "stickyweb-0" + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_SPIDER_WEB + canSmoothWith = SMOOTH_GROUP_SPIDER_WEB + SMOOTH_GROUP_WALLS ///Whether or not the web is from the genetics power var/genetic = FALSE ///Whether or not the web is a sealed web var/sealed = FALSE - icon_state = "stickyweb1" + ///Do we need to offset this based on a sprite frill? + var/has_frill = TRUE + /// Chance that someone will get stuck when trying to cross this tile + var/stuck_chance = 50 + /// Chance that a bullet will hit this instead of flying through it + var/projectile_stuck_chance = 30 + +/obj/structure/spider/stickyweb/Initialize(mapload) + // Offset on init so that they look nice in the map editor + if (has_frill) + pixel_x = -9 + pixel_y = -9 + return ..() /obj/structure/spider/stickyweb/attack_hand(mob/user, list/modifiers) .= ..() if(.) return - if(!HAS_TRAIT(user,TRAIT_WEB_WEAVER)) + if(!HAS_TRAIT(user, TRAIT_WEB_WEAVER)) return loc.balloon_alert_to_viewers("weaving...") if(!do_after(user, 2 SECONDS)) @@ -51,11 +72,6 @@ var/obj/item/stack/sheet/cloth/woven_cloth = new /obj/item/stack/sheet/cloth user.put_in_hands(woven_cloth) -/obj/structure/spider/stickyweb/Initialize(mapload) - if(!sealed && prob(50)) - icon_state = "stickyweb2" - . = ..() - /obj/structure/spider/stickyweb/CanAllowThrough(atom/movable/mover, border_dir) . = ..() if(genetic) @@ -67,65 +83,125 @@ return TRUE if(mover.pulledby && HAS_TRAIT(mover.pulledby, TRAIT_WEB_SURFER)) return TRUE - if(prob(50)) - loc.balloon_alert(mover, "stuck in web!") + if(prob(stuck_chance)) + stuck_react(mover) return FALSE - else if(isprojectile(mover)) - return prob(30) - -/obj/structure/spider/stickyweb/sealed - name = "sealed web" - desc = "A solid thick wall of web, airtight enough to block air flow." - icon_state = "sealedweb" - sealed = TRUE - can_atmos_pass = ATMOS_PASS_NO + return . + if(isprojectile(mover)) + return prob(projectile_stuck_chance) + return . -/obj/structure/spider/stickyweb/sealed/Initialize(mapload) - . = ..() - air_update_turf(TRUE, TRUE) +/// Show some feedback when you can't pass through something +/obj/structure/spider/stickyweb/proc/stuck_react(atom/movable/stuck_guy) + loc.balloon_alert(stuck_guy, "stuck in web!") + stuck_guy.Shake(duration = 0.1 SECONDS) -/obj/structure/spider/stickyweb/genetic //for the spider genes in genetics +/// Web made by geneticists, needs special handling to allow them to pass through their own webs +/obj/structure/spider/stickyweb/genetic genetic = TRUE + desc = "It's stringy, sticky, and came out of your coworker." + /// Mob with special permission to cross this web var/mob/living/allowed_mob /obj/structure/spider/stickyweb/genetic/Initialize(mapload, allowedmob) - allowed_mob = allowedmob . = ..() + // Tint it purple so that spiders don't get confused about why they can't cross this one + add_filter(SPIDER_WEB_TINT, 10, list("type" = "outline", "color" = "#ffaaf8ff", "size" = 0.1)) + +/obj/structure/spider/stickyweb/genetic/Initialize(mapload, allowedmob) + allowed_mob = allowedmob + return ..() /obj/structure/spider/stickyweb/genetic/CanAllowThrough(atom/movable/mover, border_dir) - . = ..() //this is the normal spider web return aka a spider would make this TRUE + . = ..() if(mover == allowed_mob) return TRUE else if(isliving(mover)) //we change the spider to not be able to go through here if(mover.pulledby == allowed_mob) return TRUE if(prob(50)) - loc.balloon_alert(mover, "stuck in web!") + stuck_react(mover) return FALSE else if(isprojectile(mover)) return prob(30) + return . -/obj/structure/spider/solid - name = "solid web" - icon = 'icons/effects/effects.dmi' - desc = "A solid wall of web, thick enough to block air flow." - icon_state = "solidweb" +/// Web with a 100% chance to intercept movement +/obj/structure/spider/stickyweb/very_sticky + max_integrity = 20 + desc = "Extremely sticky silk, you're not easily getting through there." + stuck_chance = 100 + projectile_stuck_chance = 100 + +/obj/structure/spider/stickyweb/very_sticky/Initialize(mapload) + . = ..() + add_filter(SPIDER_WEB_TINT, 10, list("type" = "outline", "color" = "#ffffaaff", "size" = 0.1)) + +/obj/structure/spider/stickyweb/very_sticky/update_overlays() + . = ..() + var/mutable_appearance/web_overlay = mutable_appearance(icon = 'icons/effects/web.dmi', icon_state = "sticky_overlay", layer = layer + 1) + web_overlay.pixel_x -= pixel_x + web_overlay.pixel_y -= pixel_y + . += web_overlay + + +/// Web 'wall' +/obj/structure/spider/stickyweb/sealed + name = "sealed web" + desc = "A solid wall of web, dense enough to block air flow." + icon = 'icons/obj/smooth_structures/webwall.dmi' + base_icon_state = "webwall" + icon_state = "webwall-0" + smoothing_groups = SMOOTH_GROUP_SPIDER_WEB_WALL + canSmoothWith = SMOOTH_GROUP_SPIDER_WEB_WALL + plane = GAME_PLANE + layer = OBJ_LAYER + sealed = TRUE + has_frill = FALSE can_atmos_pass = ATMOS_PASS_NO + +/obj/structure/spider/stickyweb/sealed/Initialize(mapload) + . = ..() + air_update_turf(TRUE, TRUE) + +/// Walls which reflects lasers +/obj/structure/spider/stickyweb/sealed/reflector + name = "reflective silk screen" + desc = "Hardened webbing treated with special chemicals which cause it to repel projectiles." + icon = 'icons/obj/smooth_structures/webwall_reflector.dmi' + base_icon_state = "webwall_reflector" + icon_state = "webwall_reflector-0" + smoothing_groups = SMOOTH_GROUP_SPIDER_WEB_WALL_MIRROR + canSmoothWith = SMOOTH_GROUP_SPIDER_WEB_WALL_MIRROR + max_integrity = 30 + opacity = TRUE + flags_ricochet = RICOCHET_SHINY | RICOCHET_HARD + receive_ricochet_chance_mod = INFINITY + +/// Opaque and durable web 'wall' +/obj/structure/spider/stickyweb/sealed/tough + name = "hardened web" + desc = "Webbing hardened through a chemical process into a durable barrier." + icon = 'icons/obj/smooth_structures/webwall_dark.dmi' + base_icon_state = "webwall_dark" + icon_state = "webwall_dark-0" + smoothing_groups = SMOOTH_GROUP_SPIDER_WEB_WALL_TOUGH + canSmoothWith = SMOOTH_GROUP_SPIDER_WEB_WALL_TOUGH opacity = TRUE - density = TRUE max_integrity = 90 layer = ABOVE_MOB_LAYER resistance_flags = FIRE_PROOF | FREEZE_PROOF -/obj/structure/spider/solid/Initialize(mapload) - . = ..() - air_update_turf(TRUE, TRUE) - +/// Web 'door', blocks atmos but not movement /obj/structure/spider/passage name = "web passage" - icon = 'icons/effects/effects.dmi' - desc = "A messy connection of webs blocking the other side, but not solid enough to prevent passage." - icon_state = "webpassage" + desc = "An opaque curtain of web which seals in air but doesn't impede passage." + icon = 'icons/obj/smooth_structures/stickyweb_rotated.dmi' + base_icon_state = "stickyweb_rotated" + icon_state = "stickyweb_rotated-0" + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_SPIDER_WEB_ROOF + canSmoothWith = SMOOTH_GROUP_SPIDER_WEB_ROOF + SMOOTH_GROUP_WALLS can_atmos_pass = ATMOS_PASS_NO opacity = TRUE max_integrity = 60 @@ -135,7 +211,10 @@ /obj/structure/spider/passage/Initialize(mapload) . = ..() + pixel_x = -9 + pixel_y = -9 air_update_turf(TRUE, TRUE) + add_filter(SPIDER_WEB_TINT, 10, list("type" = "outline", "color" = "#ffffffff", "alpha" = 0.8, "size" = 0.1)) /obj/structure/spider/cocoon name = "cocoon" @@ -165,56 +244,31 @@ A.forceMove(T) return ..() -/obj/structure/spider/sticky - name = "sticky web" - icon = 'icons/effects/effects.dmi' - desc = "Extremely soft and sticky silk." - icon_state = "verystickyweb" - max_integrity = 20 - -/obj/structure/spider/sticky/CanAllowThrough(atom/movable/mover, border_dir) - . = ..() - if(HAS_TRAIT(mover, TRAIT_WEB_SURFER)) - return TRUE - if(!isliving(mover)) - return - if(!isnull(mover.pulledby) && HAS_TRAIT(mover.pulledby, TRAIT_WEB_SURFER)) - return TRUE - loc.balloon_alert(mover, "stuck in web!") - return FALSE - +/// Web caltrops /obj/structure/spider/spikes name = "web spikes" - icon = 'icons/effects/effects.dmi' desc = "Silk hardened into small yet deadly spikes." - icon_state = "webspikes1" + plane = FLOOR_PLANE + layer = MID_TURF_LAYER + icon = 'icons/obj/smooth_structures/stickyweb_spikes.dmi' + base_icon_state = "stickyweb_spikes" + icon_state = "stickyweb_spikes-0" + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_SPIDER_WEB + canSmoothWith = SMOOTH_GROUP_SPIDER_WEB + SMOOTH_GROUP_WALLS max_integrity = 40 /obj/structure/spider/spikes/Initialize(mapload) . = ..() + pixel_x = -9 + pixel_y = -9 + add_filter(SPIDER_WEB_TINT, 10, list("type" = "outline", "color" = "#ac0000ff", "size" = 0.1)) AddComponent(/datum/component/caltrop, min_damage = 20, max_damage = 30, flags = CALTROP_NOSTUN | CALTROP_BYPASS_SHOES) -/obj/structure/spider/reflector - name = "Reflective silk screen" - icon = 'icons/effects/effects.dmi' - desc = "Made up of an extremly reflective silk material looking at it hurts." - icon_state = "reflector" - max_integrity = 30 - density = TRUE - opacity = TRUE - anchored = TRUE - flags_ricochet = RICOCHET_SHINY | RICOCHET_HARD - receive_ricochet_chance_mod = INFINITY - -/obj/structure/spider/reflector/Initialize(mapload) - . = ..() - air_update_turf(TRUE, TRUE) - /obj/structure/spider/effigy name = "web effigy" - icon = 'icons/effects/effects.dmi' desc = "A giant spider! Fortunately, this one is just a statue of hardened webbing." - icon_state = "webcarcass" + icon_state = "effigy" max_integrity = 125 density = TRUE anchored = FALSE @@ -222,3 +276,5 @@ /obj/structure/spider/effigy/Initialize(mapload) . = ..() AddElement(/datum/element/temporary_atom, 1 MINUTES) + +#undef SPIDER_WEB_TINT diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm index 6c96f24e33697..06b4a8e4b0721 100644 --- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm +++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm @@ -683,11 +683,11 @@ duration = 0.4 SECONDS /// Plays a dispersing animation on hivelord and legion minions so they don't just vanish -/obj/effect/temp_visual/hive_spawn_wither +/obj/effect/temp_visual/despawn_effect name = "withering spawn" duration = 1 SECONDS -/obj/effect/temp_visual/hive_spawn_wither/Initialize(mapload, atom/copy_from) +/obj/effect/temp_visual/despawn_effect/Initialize(mapload, atom/copy_from) if (isnull(copy_from)) . = ..() return INITIALIZE_HINT_QDEL diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 62d128b4eb560..d0dc7e54560ed 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -606,8 +606,23 @@ playsound(src, block_sound, BLOCK_SOUND_VOLUME, vary = TRUE) return TRUE -/obj/item/proc/talk_into(mob/M, input, channel, spans, datum/language/language, list/message_mods) - return ITALICS | REDUCE_RANGE +/** + * Handles someone talking INTO an item + * + * Commonly used by someone holding it and using .r or .l + * Also used by radios + * + * * speaker - the atom that is doing the talking + * * message - the message being spoken + * * channel - the channel the message is being spoken on, only really used for radios + * * spans - the spans of the message + * * language - the language the message is in + * * message_mods - any message mods that should be applied to the message + * + * Return a flag that modifies the original message + */ +/obj/item/proc/talk_into(atom/movable/speaker, message, channel, list/spans, datum/language/language, list/message_mods) + return SEND_SIGNAL(src, COMSIG_ITEM_TALK_INTO, speaker, message, channel, spans, language, message_mods) || (ITALICS|REDUCE_RANGE) /// Called when a mob drops an item. /obj/item/proc/dropped(mob/user, silent = FALSE) diff --git a/code/game/objects/items/busts_and_figurines.dm b/code/game/objects/items/busts_and_figurines.dm new file mode 100644 index 0000000000000..afc4a58334e90 --- /dev/null +++ b/code/game/objects/items/busts_and_figurines.dm @@ -0,0 +1,139 @@ +/obj/item/statuebust + name = "bust" + desc = "A priceless ancient marble bust, the kind that belongs in a museum." //or you can hit people with it + icon = 'icons/obj/art/statue.dmi' + icon_state = "bust" + force = 15 + throwforce = 10 + throw_speed = 5 + throw_range = 2 + attack_verb_continuous = list("busts") + attack_verb_simple = list("bust") + var/impressiveness = 45 + +/obj/item/statuebust/Initialize(mapload) + . = ..() + AddElement(/datum/element/art, impressiveness) + AddElement(/datum/element/beauty, 1000) + +/obj/item/statuebust/hippocratic + name = "hippocrates bust" + desc = "A bust of the famous Greek physician Hippocrates of Kos, often referred to as the father of western medicine." + icon_state = "hippocratic" + impressiveness = 50 + // If it hits the prob(reference_chance) chance, this is set to TRUE. Adds medical HUD when wielded, but has a 10% slower attack speed and is too bloody to make an oath with. + var/reference = FALSE + // Chance for above. + var/reference_chance = 1 + // Minimum time inbetween oaths. + COOLDOWN_DECLARE(oath_cd) + +/obj/item/statuebust/hippocratic/evil + reference_chance = 100 + +/obj/item/statuebust/hippocratic/Initialize(mapload) + . = ..() + if(prob(reference_chance)) + name = "Solemn Vow" + desc = "Art lovers will cherish the bust of Hippocrates, commemorating a time when medics still thought doing no harm was a good idea." + attack_speed = CLICK_CD_SLOW + reference = TRUE + +/obj/item/statuebust/hippocratic/examine(mob/user) + . = ..() + if(reference) + . += span_notice("You could activate the bust in-hand to swear or forswear a Hippocratic Oath... but it seems like somebody decided it was more of a Hippocratic Suggestion. This thing is caked with bits of blood and gore.") + return + . += span_notice("You can activate the bust in-hand to swear or forswear a Hippocratic Oath! This has no effects except pacifism or bragging rights. Does not remove other sources of pacifism. Do not eat.") + +/obj/item/statuebust/hippocratic/equipped(mob/living/carbon/human/user, slot) + ..() + if(!(slot & ITEM_SLOT_HANDS)) + return + var/datum/atom_hud/our_hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] + our_hud.show_to(user) + ADD_TRAIT(user, TRAIT_MEDICAL_HUD, type) + +/obj/item/statuebust/hippocratic/dropped(mob/living/carbon/human/user) + ..() + if(HAS_TRAIT_NOT_FROM(user, TRAIT_MEDICAL_HUD, type)) + return + var/datum/atom_hud/our_hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] + our_hud.hide_from(user) + REMOVE_TRAIT(user, TRAIT_MEDICAL_HUD, type) + +/obj/item/statuebust/hippocratic/attack_self(mob/user) + if(!iscarbon(user)) + to_chat(user, span_warning("You remember how the Hippocratic Oath specifies 'my fellow human beings' and realize that it's completely meaningless to you.")) + return + + if(reference) + to_chat(user, span_warning("As you prepare yourself to swear the Oath, you realize that doing so on a blood-caked bust is probably not a good idea.")) + return + + if(!COOLDOWN_FINISHED(src, oath_cd)) + to_chat(user, span_warning("You've sworn or forsworn an oath too recently to undo your decisions. The bust looks at you with disgust.")) + return + + COOLDOWN_START(src, oath_cd, 5 MINUTES) + + if(HAS_TRAIT_FROM(user, TRAIT_PACIFISM, type)) + to_chat(user, span_warning("You've already sworn a vow. You start preparing to rescind it...")) + if(do_after(user, 5 SECONDS, target = user)) + user.say("Yeah this Hippopotamus thing isn't working out. I quit!", forced = "hippocratic hippocrisy") + REMOVE_TRAIT(user, TRAIT_PACIFISM, type) + + // they can still do it for rp purposes + if(HAS_TRAIT_NOT_FROM(user, TRAIT_PACIFISM, type)) + to_chat(user, span_warning("You already don't want to harm people, this isn't going to do anything!")) + + + to_chat(user, span_notice("You remind yourself of the Hippocratic Oath's contents and prepare to swear yourself to it...")) + if(do_after(user, 4 SECONDS, target = user)) + user.say("I swear to fulfill, to the best of my ability and judgment, this covenant:", forced = "hippocratic oath") + else + return fuck_it_up(user) + if(do_after(user, 2 SECONDS, target = user)) + user.say("I will apply, for the benefit of the sick, all measures that are required, avoiding those twin traps of overtreatment and therapeutic nihilism.", forced = "hippocratic oath") + else + return fuck_it_up(user) + if(do_after(user, 3 SECONDS, target = user)) + user.say("I will remember that I remain a member of society, with special obligations to all my fellow human beings, those sound of mind and body as well as the infirm.", forced = "hippocratic oath") + else + + return fuck_it_up(user) + if(do_after(user, 3 SECONDS, target = user)) + user.say("If I do not violate this oath, may I enjoy life and art, respected while I live and remembered with affection thereafter. May I always act so as to preserve the finest traditions of my calling and may I long experience the joy of healing those who seek my help.", forced = "hippocratic oath") + else + return fuck_it_up(user) + + to_chat(user, span_notice("Contentment, understanding, and purpose washes over you as you finish the oath. You consider for a second the concept of harm and shudder.")) + ADD_TRAIT(user, TRAIT_PACIFISM, type) + +// Bully the guy for fucking up. +/obj/item/statuebust/hippocratic/proc/fuck_it_up(mob/living/carbon/user) + to_chat(user, span_warning("You forget what comes next like a dumbass. The Hippocrates bust looks down on you, disappointed.")) + user.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2) + COOLDOWN_RESET(src, oath_cd) + +/obj/item/maneki_neko + name = "Maneki-Neko" + desc = "A figurine of a cat holding a coin, said to bring fortune and wealth, and perpetually moving its paw in a beckoning gesture." + icon = 'icons/obj/fluff/general.dmi' + icon_state = "maneki-neko" + w_class = WEIGHT_CLASS_SMALL + force = 5 + throwforce = 5 + throw_speed = 3 + throw_range = 5 + attack_verb_continuous = list("bashes", "beckons", "hit") + attack_verb_simple = list("bash", "beckon", "hit") + +/obj/item/maneki_neko/Initialize(mapload) + . = ..() + //Not compatible with greyscale configs because it's animated. + color = pick_weight(list(COLOR_WHITE = 3, COLOR_GOLD = 2, COLOR_DARK = 1)) + var/mutable_appearance/neko_overlay = mutable_appearance(icon, "maneki-neko-overlay", appearance_flags = RESET_COLOR) + add_overlay(neko_overlay) + AddElement(/datum/element/art, GOOD_ART) + AddElement(/datum/element/beauty, 800) diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm index d395905cd2631..b371a572266ce 100644 --- a/code/game/objects/items/cards_ids.dm +++ b/code/game/objects/items/cards_ids.dm @@ -125,9 +125,8 @@ /obj/item/card/id/Initialize(mapload) . = ..() - var/datum/bank_account/blank_bank_account = new /datum/bank_account("Unassigned", player_account = FALSE) + var/datum/bank_account/blank_bank_account = new("Unassigned", SSjob.GetJobType(/datum/job/unassigned), player_account = FALSE) registered_account = blank_bank_account - blank_bank_account.account_job = new /datum/job/unassigned registered_account.replaceable = TRUE // Applying the trim updates the label and icon, so don't do this twice. @@ -446,7 +445,7 @@ context[SCREENTIP_CONTEXT_RMB] = "Project pay stand" if(isnull(registered_account) || registered_account.replaceable) //Same check we use when we check if we can assign an account context[SCREENTIP_CONTEXT_ALT_RMB] = "Assign account" - if(!registered_account.replaceable || registered_account.account_balance > 0) + else if(registered_account.account_balance > 0) context[SCREENTIP_CONTEXT_ALT_LMB] = "Withdraw credits" return CONTEXTUAL_SCREENTIP_SET @@ -1080,6 +1079,14 @@ assigned_icon_state = "assigned_silver" wildcard_slots = WILDCARD_LIMIT_SILVER +/obj/item/card/id/advanced/robotic + name = "magnetic identification card" + desc = "An integrated card which shows the work poured into opening doors." + icon_state = "card_carp" //im not a spriter + inhand_icon_state = "silver_id" + assigned_icon_state = "assigned_silver" + wildcard_slots = WILDCARD_LIMIT_GREY + /datum/id_trim/maint_reaper access = list(ACCESS_MAINT_TUNNELS) trim_state = "trim_janitor" @@ -1229,7 +1236,7 @@ /obj/item/card/id/advanced/debug/Initialize(mapload) . = ..() registered_account = SSeconomy.get_dep_account(ACCOUNT_CAR) - registered_account.account_job = new /datum/job/admin // so we can actually use this account without being filtered as a "departmental" card + registered_account.account_job = SSjob.GetJobType(/datum/job/admin) // so we can actually use this account without being filtered as a "departmental" card /obj/item/card/id/advanced/prisoner name = "prisoner ID card" diff --git a/code/game/objects/items/cigs_lighters.dm b/code/game/objects/items/cigs_lighters.dm index 5a88a2cedff85..4bc8fc88e1031 100644 --- a/code/game/objects/items/cigs_lighters.dm +++ b/code/game/objects/items/cigs_lighters.dm @@ -727,7 +727,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM custom_price = PAYCHECK_CREW * 1.1 light_system = OVERLAY_LIGHT light_range = 2 - light_power = 0.6 + light_power = 1.3 light_color = LIGHT_COLOR_FIRE light_on = FALSE /// Whether the lighter is lit. diff --git a/code/game/objects/items/devices/aicard.dm b/code/game/objects/items/devices/aicard.dm index 0ab7e5d94b52a..c619f7d7018e0 100644 --- a/code/game/objects/items/devices/aicard.dm +++ b/code/game/objects/items/devices/aicard.dm @@ -16,6 +16,8 @@ /obj/item/aicard/Initialize(mapload) . = ..() + if(mapload && HAS_TRAIT(SSstation, STATION_TRAIT_HUMAN_AI)) + return INITIALIZE_HINT_QDEL ADD_TRAIT(src, TRAIT_CASTABLE_LOC, INNATE_TRAIT) /obj/item/aicard/Destroy(force) diff --git a/code/game/objects/items/devices/aicard_evil.dm b/code/game/objects/items/devices/aicard_evil.dm index f91150bb086a6..8aaa9f0311116 100644 --- a/code/game/objects/items/devices/aicard_evil.dm +++ b/code/game/objects/items/devices/aicard_evil.dm @@ -34,14 +34,16 @@ if(isnull(op_datum)) balloon_alert(user, "invalid access!") return - - var/datum/callback/to_call = CALLBACK(src, PROC_REF(on_poll_concluded), user, op_datum) - AddComponent(/datum/component/orbit_poll, \ - ignore_key = POLL_IGNORE_SYNDICATE, \ - job_bans = ROLE_OPERATIVE, \ - to_call = to_call, \ - title = "Nuclear Operative Modsuit AI" \ + var/mob/chosen_one = SSpolling.poll_ghosts_for_target( + check_jobban = ROLE_OPERATIVE, + poll_time = 20 SECONDS, + checked_target = src, + ignore_category = POLL_IGNORE_SYNDICATE, + alert_pic = src, + role_name_text = "Nuclear Operative Modsuit AI", + chat_text_border_icon = mutable_appearance(icon, "syndicard-full"), ) + on_poll_concluded(user, op_datum, chosen_one) /// Poll has concluded with a ghost, create the AI /obj/item/aicard/syndie/loaded/proc/on_poll_concluded(mob/user, datum/antagonist/nukeop/op_datum, mob/dead/observer/ghost) diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm index 0b552f08e6838..c92e3b7e6598a 100644 --- a/code/game/objects/items/devices/flashlight.dm +++ b/code/game/objects/items/devices/flashlight.dm @@ -20,6 +20,7 @@ custom_materials = list(/datum/material/iron= SMALL_MATERIAL_AMOUNT * 0.5, /datum/material/glass= SMALL_MATERIAL_AMOUNT * 0.2) actions_types = list(/datum/action/item_action/toggle_light) light_system = OVERLAY_LIGHT_DIRECTIONAL + light_color = COLOR_LIGHT_ORANGE light_range = 4 light_power = 1 light_on = FALSE @@ -94,7 +95,7 @@ return light_on != old_light_on // If the value of light_on didn't change, return false. Otherwise true. /obj/item/flashlight/attack_self(mob/user) - toggle_light(user) + return toggle_light(user) /obj/item/flashlight/attack_hand_secondary(mob/user, list/modifiers) attack_self(user) @@ -294,6 +295,8 @@ w_class = WEIGHT_CLASS_TINY obj_flags = CONDUCTS_ELECTRICITY light_range = 2 + light_power = 0.8 + light_color = "#CCFFFF" COOLDOWN_DECLARE(holosign_cooldown) /obj/item/flashlight/pen/afterattack(atom/target, mob/user, proximity_flag) @@ -351,6 +354,8 @@ righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' force = 9 // Not as good as a stun baton. light_range = 5 // A little better than the standard flashlight. + light_power = 0.8 + light_color = "#99ccff" hitsound = 'sound/weapons/genhit1.ogg' // the desk lamps are a bit special @@ -397,6 +402,7 @@ heat = 1000 light_color = LIGHT_COLOR_FLARE light_system = OVERLAY_LIGHT + light_power = 2 grind_results = list(/datum/reagent/sulfur = 15) sound_on = 'sound/items/match_strike.ogg' toggle_context = FALSE @@ -415,7 +421,7 @@ /obj/item/flashlight/flare/Initialize(mapload) . = ..() if(randomize_fuel) - fuel = rand(25 MINUTES, 35 MINUTES) + fuel = rand(10 MINUTES, 15 MINUTES) if(light_on) attack_verb_continuous = string_list(list("burns", "singes")) attack_verb_simple = string_list(list("burn", "singe")) @@ -522,8 +528,9 @@ righthand_file = 'icons/mob/inhands/items_righthand.dmi' w_class = WEIGHT_CLASS_TINY heat = 1000 - light_color = LIGHT_COLOR_FIRE light_range = 2 + light_power = 1.5 + light_color = LIGHT_COLOR_FIRE fuel = 35 MINUTES randomize_fuel = FALSE trash_type = /obj/item/trash/candle @@ -636,6 +643,7 @@ name = "torch" desc = "A torch fashioned from some leaves and a log." light_range = 4 + light_power = 1.3 icon_state = "torch" inhand_icon_state = "torch" lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' @@ -653,20 +661,24 @@ lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi' desc = "A mining lantern." - light_range = 6 // luminosity when on + light_range = 5 // luminosity when on + light_power = 1.5 + light_color = "#ffcc66" light_system = OVERLAY_LIGHT /obj/item/flashlight/lantern/heirloom_moth name = "old lantern" desc = "An old lantern that has seen plenty of use." - light_range = 4 + light_range = 3.5 /obj/item/flashlight/lantern/syndicate name = "suspicious lantern" desc = "A suspicious looking lantern." icon_state = "syndilantern" inhand_icon_state = "syndilantern" - light_range = 10 + light_range = 6 + light_power = 2 + light_color = "#ffffe6" /obj/item/flashlight/lantern/jade name = "jade lantern" @@ -684,7 +696,8 @@ w_class = WEIGHT_CLASS_SMALL slot_flags = ITEM_SLOT_BELT custom_materials = null - light_range = 7 //luminosity when on + light_range = 6 //luminosity when on + light_color = "#ffff66" light_system = OVERLAY_LIGHT /obj/item/flashlight/emp @@ -742,13 +755,14 @@ emp_cur_charges = 100 // Glowsticks, in the uncomfortable range of similar to flares, -// but not similar enough to make it worth a refactor +// Flares need to process (for hotspots) tho so this becomes irrelevant /obj/item/flashlight/glowstick name = "glowstick" desc = "A military-grade glowstick." custom_price = PAYCHECK_LOWER w_class = WEIGHT_CLASS_SMALL - light_range = 4 + light_range = 3.5 + light_power = 2 light_system = OVERLAY_LIGHT color = LIGHT_COLOR_GREEN icon_state = "glowstick" @@ -760,35 +774,74 @@ toggle_context = FALSE /// How many seconds of fuel we have left var/fuel = 0 + /// How much max fuel we have + var/max_fuel = 0 + /// The timer id powering our burning + var/timer_id = TIMER_ID_NULL /obj/item/flashlight/glowstick/Initialize(mapload) - fuel = rand(50 MINUTES, 60 MINUTES) + fuel = rand(20 MINUTES, 25 MINUTES) + max_fuel = fuel set_light_color(color) return ..() -/obj/item/flashlight/glowstick/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/flashlight/glowstick/process(seconds_per_tick) - fuel = max(fuel - seconds_per_tick * (1 SECONDS), 0) - if(fuel <= 0) +/// Burns down the glowstick by the specified time +/// Returns the amount of time we need to burn before a visual change will occur +/obj/item/flashlight/glowstick/proc/burn_down(amount = 0) + fuel -= amount + var/fuel_target = 0 + if(fuel >= max_fuel) + fuel_target = max_fuel * 0.4 + else if(fuel >= max_fuel * 0.4) + fuel_target = max_fuel * 0.3 + set_light_range(3) + set_light_power(1.5) + else if(fuel >= max_fuel * 0.3) + fuel_target = max_fuel * 0.2 + set_light_range(2) + set_light_power(1.25) + else if(fuel >= max_fuel * 0.2) + fuel_target = max_fuel * 0.1 + set_light_power(1) + else if(fuel >= max_fuel * 0.1) + fuel_target = 0 + set_light_range(1.5) + set_light_power(0.5) + + var/time_to_burn = round(fuel - fuel_target) + // Less then a ds? go home + if(time_to_burn <= 0) turn_off() - STOP_PROCESSING(SSobj, src) + + return time_to_burn + +/obj/item/flashlight/glowstick/proc/burn_loop(amount = 0) + timer_id = TIMER_ID_NULL + var/burn_next = burn_down(amount) + if(burn_next <= 0) + return + timer_id = addtimer(CALLBACK(src, PROC_REF(burn_loop), burn_next), burn_next, TIMER_UNIQUE|TIMER_STOPPABLE|TIMER_OVERRIDE) + +/obj/item/flashlight/glowstick/proc/turn_on() + set_light_on(TRUE) // Just in case + var/datum/action/toggle = locate(/datum/action/item_action/toggle_light) in actions + // No sense having a toggle light action that we don't use eh? + if(toggle) + remove_item_action(toggle) + burn_loop() /obj/item/flashlight/glowstick/proc/turn_off() + var/datum/action/toggle = locate(/datum/action/item_action/toggle_light) in actions + if(fuel && !toggle) + add_item_action(/datum/action/item_action/toggle_light) + if(timer_id != TIMER_ID_NULL) + var/expected_burn_time = burn_down(0) // This is dumb I'm sorry + burn_down(expected_burn_time - timeleft(timer_id)) + deltimer(timer_id) + timer_id = TIMER_ID_NULL set_light_on(FALSE) update_appearance(UPDATE_ICON) -/obj/item/flashlight/glowstick/update_appearance(updates=ALL) - . = ..() - if(fuel <= 0) - set_light_on(FALSE) - return - if(light_on) - set_light_on(TRUE) - return - /obj/item/flashlight/glowstick/update_icon_state() . = ..() icon_state = "[base_icon_state][(fuel <= 0) ? "-empty" : ""]" @@ -803,6 +856,13 @@ glowstick_overlay.color = color . += glowstick_overlay +/obj/item/flashlight/glowstick/toggle_light(mob/user) + if(fuel <= 0) + return FALSE + if(light_on) + return FALSE + return ..() + /obj/item/flashlight/glowstick/attack_self(mob/user) if(fuel <= 0) balloon_alert(user, "glowstick is spent!") @@ -814,7 +874,7 @@ . = ..() if(.) user.visible_message(span_notice("[user] cracks and shakes [src]."), span_notice("You crack and shake [src], turning it on!")) - START_PROCESSING(SSobj, src) + turn_on() /obj/item/flashlight/glowstick/suicide_act(mob/living/carbon/human/user) if(!fuel) @@ -825,7 +885,7 @@ user.visible_message(span_suicide("[user] is trying to squirt [src]'s fluids into [user.p_their()] eyes... but [user.p_they()] don't have any!")) return SHAME user.visible_message(span_suicide("[user] is squirting [src]'s fluids into [user.p_their()] eyes! It looks like [user.p_theyre()] trying to commit suicide!")) - fuel = 0 + burn_loop(fuel) return FIRELOSS /obj/item/flashlight/glowstick/red @@ -858,7 +918,7 @@ icon_state = null light_system = OVERLAY_LIGHT light_range = 4 - light_power = 10 + light_power = 2 alpha = 0 plane = FLOOR_PLANE anchored = TRUE @@ -903,9 +963,6 @@ /obj/item/flashlight/eyelight name = "eyelight" desc = "This shouldn't exist outside of someone's head, how are you seeing this?" - light_system = OVERLAY_LIGHT - light_range = 15 - light_power = 1 obj_flags = CONDUCTS_ELECTRICITY item_flags = DROPDEL actions_types = list() diff --git a/code/game/objects/items/devices/laserpointer.dm b/code/game/objects/items/devices/laserpointer.dm index 6bfca96bf2715..df5f5ea4e1e93 100644 --- a/code/game/objects/items/devices/laserpointer.dm +++ b/code/game/objects/items/devices/laserpointer.dm @@ -63,6 +63,15 @@ . = ..() diode = new /obj/item/stock_parts/micro_laser/ultra +/obj/item/laser_pointer/infinite_range + name = "infinite laser pointer" + desc = "Used to shine in the eyes of Cyborgs who need a bit of a push, this works through camera consoles." + max_range = INFINITE + +/obj/item/laser_pointer/infinite_range/Initialize(mapload) + . = ..() + diode = new /obj/item/stock_parts/servo/femto + /obj/item/laser_pointer/screwdriver_act(mob/living/user, obj/item/tool) if(diode) tool.play_tool_sound(src) @@ -193,16 +202,17 @@ to_chat(user, span_warning("Your fingers can't press the button!")) return - if(!IN_GIVEN_RANGE(target, user, max_range)) - to_chat(user, span_warning("\The [target] is too far away!")) - return - if(!(user in (view(max_range, target)))) //check if we are visible from the target's PoV - if(isnull(crystal_lens)) - to_chat(user, span_warning("You can't point with [src] through walls!")) - return - if(!((user.sight & SEE_OBJS) || (user.sight & SEE_MOBS))) //only let it work if we have xray or thermals. mesons don't count because they are easier to get. - to_chat(user, span_notice("You can't quite make out your target and you fail to shine at it.")) + if(max_range != INFINITE) + if(!IN_GIVEN_RANGE(target, user, max_range)) + to_chat(user, span_warning("\The [target] is too far away!")) return + if(!(user in (view(max_range, target)))) //check if we are visible from the target's PoV + if(isnull(crystal_lens)) + to_chat(user, span_warning("You can't point with [src] through walls!")) + return + if(!((user.sight & SEE_OBJS) || (user.sight & SEE_MOBS))) //only let it work if we have xray or thermals. mesons don't count because they are easier to get. + to_chat(user, span_notice("You can't quite make out your target and you fail to shine at it.")) + return add_fingerprint(user) diff --git a/code/game/objects/items/devices/radio/encryptionkey.dm b/code/game/objects/items/devices/radio/encryptionkey.dm index 7774902b83bc0..b7a96d777ef1f 100644 --- a/code/game/objects/items/devices/radio/encryptionkey.dm +++ b/code/game/objects/items/devices/radio/encryptionkey.dm @@ -188,7 +188,31 @@ greyscale_colors = "#24a157#dca01b" /obj/item/encryptionkey/ai //ported from NT, this goes 'inside' the AI. - channels = list(RADIO_CHANNEL_COMMAND = 1, RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_ENGINEERING = 1, RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_MEDICAL = 1, RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SERVICE = 1, RADIO_CHANNEL_AI_PRIVATE = 1) + channels = list( + RADIO_CHANNEL_COMMAND = 1, + RADIO_CHANNEL_SECURITY = 1, + RADIO_CHANNEL_ENGINEERING = 1, + RADIO_CHANNEL_SCIENCE = 1, + RADIO_CHANNEL_MEDICAL = 1, + RADIO_CHANNEL_SUPPLY = 1, + RADIO_CHANNEL_SERVICE = 1, + RADIO_CHANNEL_AI_PRIVATE = 1, + ) + +/obj/item/encryptionkey/ai_with_binary + name = "ai encryption key" + channels = list( + RADIO_CHANNEL_COMMAND = 1, + RADIO_CHANNEL_SECURITY = 1, + RADIO_CHANNEL_ENGINEERING = 1, + RADIO_CHANNEL_SCIENCE = 1, + RADIO_CHANNEL_MEDICAL = 1, + RADIO_CHANNEL_SUPPLY = 1, + RADIO_CHANNEL_SERVICE = 1, + RADIO_CHANNEL_AI_PRIVATE = 1, + ) + translate_binary = TRUE + translated_language = /datum/language/machine /obj/item/encryptionkey/ai/evil //ported from NT, this goes 'inside' the AI. name = "syndicate binary encryption key" diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm index 1f06113158cef..98f4282c62677 100644 --- a/code/game/objects/items/devices/radio/headset.dm +++ b/code/game/objects/items/devices/radio/headset.dm @@ -111,6 +111,8 @@ GLOBAL_LIST_INIT(channel_tokens, list( /obj/item/radio/headset/dropped(mob/user, silent) . = ..() + if(QDELETED(src)) //This can be called as a part of destroy + return for(var/language in language_list) user.remove_language(language, language_flags = UNDERSTOOD_LANGUAGE, source = LANGUAGE_RADIOKEY) @@ -342,17 +344,25 @@ GLOBAL_LIST_INIT(channel_tokens, list( AddComponent(/datum/component/wearertargeting/earprotection, list(ITEM_SLOT_EARS)) /obj/item/radio/headset/silicon/pai - name = "\proper mini Integrated Subspace Transceiver " + name = "\proper mini Integrated Subspace Transceiver" subspace_transmission = FALSE /obj/item/radio/headset/silicon/ai - name = "\proper Integrated Subspace Transceiver " + name = "\proper Integrated Subspace Transceiver" keyslot2 = new /obj/item/encryptionkey/ai command = TRUE +/obj/item/radio/headset/silicon/human_ai + name = "\proper Disconnected Subspace Transceiver" + desc = "A headset that is rumored to be one day implanted into a brain in a jar directly." + icon_state = "rob_headset" + worn_icon_state = "rob_headset" + keyslot2 = new /obj/item/encryptionkey/ai_with_binary + command = TRUE + /obj/item/radio/headset/silicon/ai/evil - name = "\proper Evil Integrated Subspace Transceiver " + name = "\proper Evil Integrated Subspace Transceiver" keyslot2 = new /obj/item/encryptionkey/ai/evil command = FALSE diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm index 248955e0fa493..433cf51062f3e 100644 --- a/code/game/objects/items/devices/radio/radio.dm +++ b/code/game/objects/items/devices/radio/radio.dm @@ -262,17 +262,29 @@ /obj/item/radio/talk_into(atom/movable/talking_movable, message, channel, list/spans, datum/language/language, list/message_mods) if(SEND_SIGNAL(talking_movable, COMSIG_MOVABLE_USING_RADIO, src) & COMPONENT_CANNOT_USE_RADIO) - return + return NONE if(SEND_SIGNAL(src, COMSIG_RADIO_NEW_MESSAGE, talking_movable, message, channel) & COMPONENT_CANNOT_USE_RADIO) - return + return NONE if(!spans) spans = list(talking_movable.speech_span) if(!language) language = talking_movable.get_selected_language() - INVOKE_ASYNC(src, PROC_REF(talk_into_impl), talking_movable, message, channel, spans.Copy(), language, message_mods) + INVOKE_ASYNC(src, PROC_REF(talk_into_impl), talking_movable, message, channel, LAZYLISTDUPLICATE(spans), language, LAZYLISTDUPLICATE(message_mods)) return ITALICS | REDUCE_RANGE +/** + * Handles talking into the radio + * + * Unlike most speech related procs, spans and message_mods are not guaranteed to be lists + * + * * talking_movable - the atom that is talking + * * message - the message to be spoken + * * channel - the channel to be spoken on + * * spans - the spans to be used, lazylist + * * language - the language to be spoken in. (Should) never be null + * * message_mods - the message mods to be used, lazylist + */ /obj/item/radio/proc/talk_into_impl(atom/movable/talking_movable, message, channel, list/spans, datum/language/language, list/message_mods) if(!on) return // the device has to be on @@ -358,9 +370,9 @@ /obj/item/radio/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods = list(), message_range) . = ..() - if(radio_freq || !broadcasting || get_dist(src, speaker) > canhear_range) + if(radio_freq || !broadcasting || get_dist(src, speaker) > canhear_range || message_mods[MODE_RELAY]) return - var/filtered_mods = list() + var/list/filtered_mods = list() if (message_mods[MODE_SING]) filtered_mods[MODE_SING] = message_mods[MODE_SING] diff --git a/code/game/objects/items/devices/scanners/health_analyzer.dm b/code/game/objects/items/devices/scanners/health_analyzer.dm index ef922ed3a7d3c..83ea668a9fb28 100644 --- a/code/game/objects/items/devices/scanners/health_analyzer.dm +++ b/code/game/objects/items/devices/scanners/health_analyzer.dm @@ -33,7 +33,8 @@ /obj/item/healthanalyzer/examine(mob/user) . = ..() - . += span_notice("Alt-click [src] to toggle the limb damage readout.") + if(src.mode != SCANNER_NO_MODE) + . += span_notice("Alt-click [src] to toggle the limb damage readout.") /obj/item/healthanalyzer/suicide_act(mob/living/carbon/user) user.visible_message(span_suicide("[user] begins to analyze [user.p_them()]self with [src]! The display shows that [user.p_theyre()] dead!")) diff --git a/code/game/objects/items/devices/taperecorder.dm b/code/game/objects/items/devices/taperecorder.dm index 985d2761a0271..c686eeb04a7a7 100644 --- a/code/game/objects/items/devices/taperecorder.dm +++ b/code/game/objects/items/devices/taperecorder.dm @@ -159,6 +159,9 @@ /obj/item/taperecorder/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, list/message_mods = list(), message_range) . = ..() + if(message_mods[MODE_RELAY]) + return + if(mytape && recording) mytape.timestamp += mytape.used_capacity mytape.storedinfo += "\[[time2text(mytape.used_capacity,"mm:ss")]\] [raw_message]" diff --git a/code/game/objects/items/devices/traitordevices.dm b/code/game/objects/items/devices/traitordevices.dm index 3515e7f52c3ce..8e8f2578fa4b1 100644 --- a/code/game/objects/items/devices/traitordevices.dm +++ b/code/game/objects/items/devices/traitordevices.dm @@ -202,9 +202,104 @@ effective or pretty fucking useless. target = round(target) wavelength = clamp(target, 0, 120) +/datum/action/item_action/stealth_mode + name = "Toggle Stealth" + desc = "Makes you invisible to the naked eye." + button_icon = 'icons/mob/actions/actions_minor_antag.dmi' + button_icon_state = "ninja_cloak" + /// Whether stealth is active or not + var/stealth_engaged = FALSE + /// The amount of time the stealth mode can be active for, drains to 0 when active + var/charge = 30 SECONDS + /// The maximum amount of time the stealth mode can be active for + var/max_charge = 30 SECONDS + /// The minimum alpha value for the stealth mode + var/min_alpha = 0 + /// Whether the stealth mode recharges while active + /// if TRUE standing in darkness will recharge even while active + /// if FALSE it will not uncharge, but not recharge while in darkness + var/recharge_while_active = TRUE + +/datum/action/item_action/stealth_mode/is_action_active(atom/movable/screen/movable/action_button/current_button) + return stealth_engaged + +/datum/action/item_action/stealth_mode/Grant(mob/grant_to) + . = ..() + START_PROCESSING(SSobj, src) + build_all_button_icons(UPDATE_BUTTON_STATUS) + +/datum/action/item_action/stealth_mode/Remove(mob/remove_from) + if(!isnull(owner) && stealth_engaged) + stealth_off() + STOP_PROCESSING(SSobj, src) + return ..() + +/datum/action/item_action/stealth_mode/Trigger(trigger_flags) + . = ..() + if(!.) + return + + if(stealth_engaged) + stealth_off() + else + stealth_on() + +/datum/action/item_action/stealth_mode/proc/stealth_on() + animate(owner, alpha = get_alpha(), time = 0.5 SECONDS) + apply_wibbly_filters(owner) + stealth_engaged = TRUE + build_all_button_icons(UPDATE_BUTTON_STATUS|UPDATE_BUTTON_BACKGROUND) + owner.balloon_alert(owner, "stealth mode engaged") + +/datum/action/item_action/stealth_mode/proc/stealth_off() + owner.alpha = initial(owner.alpha) + remove_wibbly_filters(owner) + stealth_engaged = FALSE + build_all_button_icons(UPDATE_BUTTON_STATUS|UPDATE_BUTTON_BACKGROUND) + owner.balloon_alert(owner, "stealth mode disengaged") + +/datum/action/item_action/stealth_mode/proc/get_alpha() + return clamp(255 - (255 * charge / max_charge), min_alpha, 255) + +/datum/action/item_action/stealth_mode/process(seconds_per_tick) + if(!stealth_engaged) + // Recharge over time + charge = min(max_charge, charge + (max_charge * 0.04) * seconds_per_tick) + build_all_button_icons(UPDATE_BUTTON_STATUS) + return + + if(charge <= 0) + stealth_off() + return + + var/turf/our_turf = get_turf(owner) + var/lumcount = our_turf?.get_lumcount() || 0 + if(lumcount > 0.3) + // Decay charge while invisible+ in the light + charge = max(0, charge - (max_charge * 0.05) * seconds_per_tick) + build_all_button_icons(UPDATE_BUTTON_STATUS) + + else if(recharge_while_active) + // Return charage while invisible + in the darkness + recharge_while_active + charge = min(max_charge, charge + (max_charge * 0.1) * seconds_per_tick) + build_all_button_icons(UPDATE_BUTTON_STATUS) + + animate(owner, alpha = get_alpha(), time = 1 SECONDS, flags = ANIMATION_PARALLEL) + +/datum/action/item_action/stealth_mode/update_button_status(atom/movable/screen/movable/action_button/current_button, force) + . = ..() + current_button.maptext_x = 9 + current_button.maptext = MAPTEXT_TINY_UNICODE("[round(charge / max_charge * 100, 0.01)]%") + +/datum/action/item_action/stealth_mode/weaker + charge = 15 SECONDS + max_charge = 15 SECONDS + min_alpha = 20 + recharge_while_active = FALSE + /obj/item/shadowcloak name = "cloaker belt" - desc = "Makes you invisible for short periods of time. Recharges in darkness." + desc = "Makes you invisible for short periods of time. Recharges in darkness, even while active." icon = 'icons/obj/clothing/belts.dmi' icon_state = "utility" inhand_icon_state = "utility" @@ -214,66 +309,16 @@ effective or pretty fucking useless. slot_flags = ITEM_SLOT_BELT attack_verb_continuous = list("whips", "lashes", "disciplines") attack_verb_simple = list("whip", "lash", "discipline") - - var/mob/living/carbon/human/user = null - var/charge = 300 - var/max_charge = 300 - var/on = FALSE - actions_types = list(/datum/action/item_action/toggle) - -/obj/item/shadowcloak/ui_action_click(mob/user) - if(user.get_item_by_slot(ITEM_SLOT_BELT) == src) - if(!on) - Activate(usr) - - else - Deactivate() - - return + actions_types = list(/datum/action/item_action/stealth_mode) /obj/item/shadowcloak/item_action_slot_check(slot, mob/user) - if(slot & ITEM_SLOT_BELT) - return 1 - -/obj/item/shadowcloak/proc/Activate(mob/living/carbon/human/user) - if(!user) - return - - to_chat(user, span_notice("You activate [src].")) - src.user = user - START_PROCESSING(SSobj, src) - on = TRUE - -/obj/item/shadowcloak/proc/Deactivate() - to_chat(user, span_notice("You deactivate [src].")) - STOP_PROCESSING(SSobj, src) - if(user) - user.alpha = initial(user.alpha) - - on = FALSE - user = null - -/obj/item/shadowcloak/dropped(mob/user) - ..() - if(user && user.get_item_by_slot(ITEM_SLOT_BELT) != src) - Deactivate() - -/obj/item/shadowcloak/process(seconds_per_tick) - if(user.get_item_by_slot(ITEM_SLOT_BELT) != src) - Deactivate() - return - - var/turf/T = get_turf(src) - if(on) - var/lumcount = T.get_lumcount() - - if(lumcount > 0.3) - charge = max(0, charge - 12.5 * seconds_per_tick)//Quick decrease in light - - else - charge = min(max_charge, charge + 25 * seconds_per_tick) //Charge in the dark + return slot & slot_flags - animate(user,alpha = clamp(255 - charge,0,255),time = 10) +/obj/item/shadowcloak/weaker + name = "stealth belt" + desc = "Makes you nigh-invisible to the naked eye for a short period of time. \ + Lasts indefinitely in darkness, but will not recharge unless inactive." + actions_types = list(/datum/action/item_action/stealth_mode/weaker) /// Checks if a given atom is in range of a radio jammer, returns TRUE if it is. /proc/is_within_radio_jammer_range(atom/source) diff --git a/code/game/objects/items/dice.dm b/code/game/objects/items/dice.dm index 6f425b7df6eab..ca2a972006da6 100644 --- a/code/game/objects/items/dice.dm +++ b/code/game/objects/items/dice.dm @@ -425,11 +425,10 @@ var/mob/living/carbon/human/human_servant = new(drop_location()) do_smoke(0, holder = src, location = drop_location()) - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as [user.real_name]'s Servant?", check_jobban = ROLE_WIZARD, role = ROLE_WIZARD, poll_time = 5 SECONDS, target_mob = human_servant, pic_source = user, role_name_text = "dice servant") - if(LAZYLEN(candidates)) - var/mob/dead/observer/candidate = pick(candidates) - message_admins("[ADMIN_LOOKUPFLW(candidate)] was spawned as Dice Servant") - human_servant.key = candidate.key + var/mob/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to play as [span_danger("[user.real_name]'s")] [span_notice("Servant")]?", check_jobban = ROLE_WIZARD, role = ROLE_WIZARD, poll_time = 5 SECONDS, checked_target = human_servant, alert_pic = user, role_name_text = "dice servant") + if(chosen_one) + message_admins("[ADMIN_LOOKUPFLW(chosen_one)] was spawned as Dice Servant") + human_servant.key = chosen_one.key human_servant.equipOutfit(/datum/outfit/butler) var/datum/mind/servant_mind = new /datum/mind() diff --git a/code/game/objects/items/extinguisher.dm b/code/game/objects/items/extinguisher.dm index 920d074ad4a4e..b0189a0c4ecef 100644 --- a/code/game/objects/items/extinguisher.dm +++ b/code/game/objects/items/extinguisher.dm @@ -279,13 +279,8 @@ /obj/item/extinguisher/proc/EmptyExtinguisher(mob/user) if(loc == user && reagents.total_volume) + reagents.expose(user.loc, TOUCH) reagents.clear_reagents() - - var/turf/T = get_turf(loc) - if(isopenturf(T)) - var/turf/open/theturf = T - theturf.MakeSlippery(TURF_WET_WATER, min_wet_time = 10 SECONDS, wet_time_to_add = 5 SECONDS) - user.visible_message(span_notice("[user] empties out \the [src] onto the floor using the release valve."), span_info("You quietly empty out \the [src] using its release valve.")) //firebot assembly @@ -297,3 +292,10 @@ user.put_in_hands(new /obj/item/bot_assembly/firebot) else ..() + +/obj/item/extinguisher/anti + name = "fire extender" + desc = "A traditional red fire extinguisher. Made in Britain... wait, what?" + chem = /datum/reagent/fuel + tanktype = /obj/structure/reagent_dispensers/fueltank + cooling_power = 0 diff --git a/code/game/objects/items/flamethrower.dm b/code/game/objects/items/flamethrower.dm index 1a31c5d58a219..1564e4e40298a 100644 --- a/code/game/objects/items/flamethrower.dm +++ b/code/game/objects/items/flamethrower.dm @@ -16,6 +16,9 @@ resistance_flags = FIRE_PROOF trigger_guard = TRIGGER_GUARD_NORMAL light_system = OVERLAY_LIGHT + light_color = LIGHT_COLOR_FLARE + light_range = 2 + light_power = 2 light_on = FALSE var/status = FALSE var/lit = FALSE //on or off @@ -82,12 +85,13 @@ return // too close if(HAS_TRAIT(user, TRAIT_PACIFISM)) to_chat(user, span_warning("You can't bring yourself to fire \the [src]! You don't want to risk harming anyone...")) + log_combat(user, target, "attempted to flamethrower", src, "with gas mixture: {[print_gas_mixture(ptank.return_analyzable_air())]}, flamethrower: \"[name]\" ([src]), igniter: \"[igniter.name]\", tank: \"[ptank.name]\" and tank distribution pressure: \"[siunit(1000 * ptank.distribute_pressure, unit = "Pa", maxdecimals = INFINITY)]\"" + lit ? " while lit" : "" + " but failed due to pacifism.") return if(user && user.get_active_held_item() == src) // Make sure our user is still holding us var/turf/target_turf = get_turf(target) if(target_turf) var/turflist = get_line(user, target_turf) - log_combat(user, target, "flamethrowered", src) + log_combat(user, target, "flamethrowered", src, "with gas mixture: {[print_gas_mixture(ptank.return_analyzable_air())]}, flamethrower: \"[name]\", igniter: \"[igniter.name]\", tank: \"[ptank.name]\" and tank distribution pressure: \"[siunit(1000 * ptank.distribute_pressure, unit = "Pa", maxdecimals = INFINITY)]\"" + lit ? " while lit." : ".") flame_turf(turflist) /obj/item/flamethrower/wrench_act(mob/living/user, obj/item/tool) diff --git a/code/game/objects/items/food/bait.dm b/code/game/objects/items/food/bait.dm index aa9a0e7bd9e95..047a8a7cd58ce 100644 --- a/code/game/objects/items/food/bait.dm +++ b/code/game/objects/items/food/bait.dm @@ -36,9 +36,8 @@ lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' righthand_file = 'icons/mob/inhands/items_righthand.dmi' inhand_icon_state = "pen" - food_reagents = list(/datum/reagent/drug/kronkaine = 1) + food_reagents = list(/datum/reagent/drug/kronkaine = 2) //The kronkaine is the thing that makes this a great bait. tastes = list("hypocrisy" = 1) - bait_quality = TRAIT_GREAT_QUALITY_BAIT /obj/item/food/bait/doughball name = "doughball" diff --git a/code/game/objects/items/food/bread.dm b/code/game/objects/items/food/bread.dm index ba1845bb40da4..0f95aac6d8528 100644 --- a/code/game/objects/items/food/bread.dm +++ b/code/game/objects/items/food/bread.dm @@ -481,6 +481,7 @@ foodtypes = GRAIN | DAIRY w_class = WEIGHT_CLASS_SMALL crafting_complexity = FOOD_COMPLEXITY_2 + custom_price = PAYCHECK_CREW /obj/item/food/butterdog/Initialize(mapload) . = ..() diff --git a/code/game/objects/items/food/dough.dm b/code/game/objects/items/food/dough.dm index 6ca618bc6e0cb..283cd347f55bd 100644 --- a/code/game/objects/items/food/dough.dm +++ b/code/game/objects/items/food/dough.dm @@ -46,7 +46,7 @@ /obj/item/food/pizzabread/Initialize(mapload) . = ..() - AddComponent(/datum/component/customizable_reagent_holder, /obj/item/food/pizza/margherita, CUSTOM_INGREDIENT_ICON_SCATTER, max_ingredients = 12) + AddComponent(/datum/component/customizable_reagent_holder, /obj/item/food/pizza, CUSTOM_INGREDIENT_ICON_SCATTER, max_ingredients = 12) /obj/item/food/doughslice name = "dough slice" diff --git a/code/game/objects/items/food/lizard.dm b/code/game/objects/items/food/lizard.dm index 5f7092c64db58..47b5ff7510916 100644 --- a/code/game/objects/items/food/lizard.dm +++ b/code/game/objects/items/food/lizard.dm @@ -34,6 +34,7 @@ foodtypes = MEAT w_class = WEIGHT_CLASS_SMALL crafting_complexity = FOOD_COMPLEXITY_2 + custom_price = PAYCHECK_CREW /obj/item/food/raw_headcheese name = "raw headcheese block" @@ -494,6 +495,7 @@ //Pizza Dishes /obj/item/food/pizza/flatbread icon = 'icons/obj/food/lizard.dmi' + icon_state = null slice_type = null /obj/item/food/pizza/flatbread/rustic diff --git a/code/game/objects/items/food/martian.dm b/code/game/objects/items/food/martian.dm index 2441ac0f67478..7ceaf1878176c 100644 --- a/code/game/objects/items/food/martian.dm +++ b/code/game/objects/items/food/martian.dm @@ -732,6 +732,7 @@ foodtypes = MEAT | VEGETABLES | FRUIT | PINEAPPLE w_class = WEIGHT_CLASS_SMALL crafting_complexity = FOOD_COMPLEXITY_4 + custom_price = PAYCHECK_CREW * 1.2 /obj/item/food/salt_chilli_fries name = "salt n' chilli fries" @@ -1210,6 +1211,7 @@ foodtypes = FRUIT | MEAT | PINEAPPLE | VEGETABLES | GRAIN w_class = WEIGHT_CLASS_SMALL crafting_complexity = FOOD_COMPLEXITY_4 //Uses Sambal + custom_price = PAYCHECK_CREW * 2 /obj/item/food/frickles name = "frickles" diff --git a/code/game/objects/items/food/meatdish.dm b/code/game/objects/items/food/meatdish.dm index b9a6c34df04ed..537c7688d2dd4 100644 --- a/code/game/objects/items/food/meatdish.dm +++ b/code/game/objects/items/food/meatdish.dm @@ -554,6 +554,7 @@ w_class = WEIGHT_CLASS_SMALL venue_value = FOOD_PRICE_CHEAP crafting_complexity = FOOD_COMPLEXITY_2 + custom_price = PAYCHECK_CREW * 0.6 /obj/item/food/sausage/make_processable() AddElement(/datum/element/processable, TOOL_KNIFE, /obj/item/food/salami, 6, 3 SECONDS, table_required = TRUE, screentip_verb = "Slice") @@ -734,6 +735,7 @@ foodtypes = MEAT | DAIRY | GRAIN w_class = WEIGHT_CLASS_TINY crafting_complexity = FOOD_COMPLEXITY_3 + custom_price = PAYCHECK_CREW /obj/item/food/bbqribs name = "bbq ribs" @@ -750,19 +752,6 @@ foodtypes = MEAT | SUGAR crafting_complexity = FOOD_COMPLEXITY_2 -///Special private component to handle how bbq is grilled, not meant to be used anywhere else -/datum/component/grillable/bbq - -/datum/component/grillable/bbq/finish_grilling(atom/grill_source) - //when on a grill allow it to roast without deleting itself - if(istype(grill_source, /obj/machinery/grill)) - grill_source.visible_message(span_notice("[parent] is grilled to perfection!")) - else //when on a girddle allow it to burn into an mouldy mess - return ..() - -/obj/item/food/bbqribs/make_grillable() - AddComponent(/datum/component/grillable/bbq, /obj/item/food/badrecipe, rand(30 SECONDS, 40 SECONDS), FALSE) - /obj/item/food/meatclown name = "meat clown" desc = "A delicious, round piece of meat clown. How horrifying." diff --git a/code/game/objects/items/food/pizza.dm b/code/game/objects/items/food/pizza.dm index b93cd7ed7219c..834484872d650 100644 --- a/code/game/objects/items/food/pizza.dm +++ b/code/game/objects/items/food/pizza.dm @@ -1,25 +1,28 @@ // Pizza (Whole) /obj/item/food/pizza + name = "pizza" icon = 'icons/obj/food/pizza.dmi' w_class = WEIGHT_CLASS_NORMAL max_volume = 80 + icon_state = "pizzamargherita" food_reagents = list( /datum/reagent/consumable/nutriment = 28, /datum/reagent/consumable/nutriment/protein = 3, /datum/reagent/consumable/tomatojuice = 6, /datum/reagent/consumable/nutriment/vitamin = 5, ) - tastes = list("crust" = 1, "tomato" = 1, "cheese" = 1) - foodtypes = GRAIN | DAIRY | VEGETABLES + tastes = list("crust" = 1, "tomato" = 1) + foodtypes = GRAIN venue_value = FOOD_PRICE_CHEAP crafting_complexity = FOOD_COMPLEXITY_2 /// type is spawned 6 at a time and replaces this pizza when processed by cutting tool var/obj/item/food/pizzaslice/slice_type + slice_type = /obj/item/food/pizzaslice ///What label pizza boxes use if this pizza spawns in them. var/boxtag = "" /obj/item/food/pizza/raw - foodtypes = GRAIN | DAIRY | VEGETABLES | RAW + foodtypes = GRAIN | RAW slice_type = null crafting_complexity = FOOD_COMPLEXITY_2 @@ -34,9 +37,11 @@ // Pizza Slice /obj/item/food/pizzaslice + name = "pizza slice" icon = 'icons/obj/food/pizza.dmi' food_reagents = list(/datum/reagent/consumable/nutriment = 5) - foodtypes = GRAIN | DAIRY | VEGETABLES + icon_state = "pizzamargheritaslice" + foodtypes = GRAIN w_class = WEIGHT_CLASS_SMALL decomp_type = /obj/item/food/pizzaslice/moldy crafting_complexity = FOOD_COMPLEXITY_2 diff --git a/code/game/objects/items/food/sandwichtoast.dm b/code/game/objects/items/food/sandwichtoast.dm index c6488f67a1ed5..e440a1039e6d1 100644 --- a/code/game/objects/items/food/sandwichtoast.dm +++ b/code/game/objects/items/food/sandwichtoast.dm @@ -152,6 +152,7 @@ w_class = WEIGHT_CLASS_SMALL venue_value = FOOD_PRICE_CHEAP crafting_complexity = FOOD_COMPLEXITY_3 + custom_price = PAYCHECK_CREW * 0.7 // Used for unit tests, do not delete /obj/item/food/hotdog/debug @@ -174,6 +175,7 @@ w_class = WEIGHT_CLASS_SMALL venue_value = FOOD_PRICE_NORMAL crafting_complexity = FOOD_COMPLEXITY_4 + custom_price = PAYCHECK_CREW /obj/item/food/sandwich/blt name = "\improper BLT" diff --git a/code/game/objects/items/food/sweets.dm b/code/game/objects/items/food/sweets.dm index 5c638077d16c5..d757261ac0154 100644 --- a/code/game/objects/items/food/sweets.dm +++ b/code/game/objects/items/food/sweets.dm @@ -79,6 +79,15 @@ w_class = WEIGHT_CLASS_TINY crafting_complexity = FOOD_COMPLEXITY_1 +/obj/item/food/virtual_chocolate + name = "virtual chocolate bar" + desc = "Digital food only gives off the sensation of eating... without any of the nutritional benefits." + icon_state = "virtual_chocolate" + tastes = list("nothing" = 1) + foodtypes = NONE + w_class = WEIGHT_CLASS_TINY + + /obj/item/food/chococoin name = "chocolate coin" desc = "A completely edible but non-flippable festive coin." diff --git a/code/game/objects/items/grenades/ghettobomb.dm b/code/game/objects/items/grenades/ghettobomb.dm index b77216a9104e8..9bc8c1c515f9a 100644 --- a/code/game/objects/items/grenades/ghettobomb.dm +++ b/code/game/objects/items/grenades/ghettobomb.dm @@ -1,12 +1,10 @@ -//improvised explosives// - /obj/item/grenade/iedcasing - name = "improvised firebomb" - desc = "A weak, improvised incendiary device." + name = "improvised explosive" + desc = "An improvised explosive device." w_class = WEIGHT_CLASS_SMALL icon = 'icons/obj/weapons/grenade.dmi' - icon_state = "improvised_grenade" - icon_state_preview = "ied_preview" + base_icon_state = "pipebomb" + icon_state = "slicedapart" inhand_icon_state = "flashbang" lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' @@ -15,67 +13,272 @@ obj_flags = CONDUCTS_ELECTRICITY slot_flags = ITEM_SLOT_BELT active = FALSE - det_time = 50 + shrapnel_type = /obj/projectile/bullet/shrapnel/ied + det_time = 225 SECONDS //this is handled by assemblies now display_timer = FALSE - var/check_parts = FALSE - var/range = 3 - var/list/times + /// Explosive power + var/power = 5 + /// Our assembly that when activated causes us to explode + var/obj/item/assembly/activator + /// List of effects, the key is a path to compare to and the value is incremented by one everytime theres one that is the same type in our contents + var/list/effects = list( + /obj/item/food/meat/slab = 0, + /obj/item/paper = 0, + /obj/item/shard = 0, + /obj/item/stack/ore/bluespace_crystal/refined = 0, + ) + /// Cooldown to prevent spam + COOLDOWN_DECLARE(spam_cd) /obj/item/grenade/iedcasing/Initialize(mapload) . = ..() - add_overlay("improvised_grenade_filled") - add_overlay("improvised_grenade_wired") - times = list("5" = 10, "-1" = 20, "[rand(30, 80)]" = 50, "[rand(65, 180)]" = 20)// "Premature, Dud, Short Fuse, Long Fuse"=[weighting value] - det_time = text2num(pick_weight(times)) - if(det_time < 0) //checking for 'duds' - range = 1 - det_time = rand(30, 80) - else - range = pick(2, 2, 2, 3, 3, 3, 4) - if(check_parts) //since construction code calls this itself, no need to always call it. This does have the downside that adminspawned ones can potentially not have cans if they don't use the /spawned subtype. - CheckParts() + if(ispath(activator)) + var/obj/item/assembly/new_activator = new activator(src) + new_activator.toggle_secure() + activator = null + attach_activator(new_activator) -/obj/item/grenade/iedcasing/spawned - check_parts = TRUE +/obj/item/grenade/iedcasing/proc/setup_effects_from_contents() + for(var/item in contents) + for(var/effect_type in effects) + if(!istype(item, effect_type)) + continue + if(isstack(item)) + var/obj/item/stack/as_stack = item + effects[effect_type] += as_stack.amount + else + effects[effect_type]++ + break -/obj/item/grenade/iedcasing/spawned/Initialize(mapload) - new /obj/item/reagent_containers/cup/soda_cans/random(src) - return ..() +/obj/item/grenade/iedcasing/examine(mob/user) + . = ..() + . += span_notice("Using it in-hand activates the assembly, which means timers start timing and so on.") + . += span_notice("Using it off-hand allows you to configure the assembly, if possible.") + if(contents.len > 1) // above 1, so more than just the activator + . += span_warning("It seems to have something stuffed in it.") + if(isnull(activator)) + return + . += activator.examine(user) -/obj/item/grenade/iedcasing/CheckParts(list/parts_list) - ..() - var/obj/item/reagent_containers/cup/soda_cans/can = locate() in contents - if(!can) - stack_trace("[src] generated without a soda can!") //this shouldn't happen. - qdel(src) +// assembly handling + +/obj/item/grenade/iedcasing/IsAssemblyHolder() + return TRUE + +/obj/item/grenade/iedcasing/on_found(mob/finder) + if(activator) + activator.on_found(finder) + +/obj/item/grenade/iedcasing/Move() + . = ..() + if(activator) + activator.holder_movement() + +/obj/item/grenade/iedcasing/dropped() + . = ..() + if(activator) + activator.dropped() + +/obj/item/grenade/iedcasing/proc/process_activation(obj/item/assembly) + detonate() + +/obj/item/grenade/iedcasing/proc/attach_activator(obj/item/assembly/new_one) + if(activator) return - can.pixel_x = 0 //Reset the sprite's position to make it consistent with the rest of the IED - can.pixel_y = 0 - var/mutable_appearance/can_underlay = new(can) - can_underlay.layer = FLOAT_LAYER - can_underlay.plane = FLOAT_PLANE - underlays += can_underlay + activator = new_one + activator.holder = src + activator.on_attach() + activator.toggle_secure() + update_icon(UPDATE_ICON_STATE) +/obj/item/grenade/iedcasing/change_det_time() + return -/obj/item/grenade/iedcasing/attack_self(mob/user) - if(!active) - if(!botch_check(user)) - to_chat(user, span_warning("You light the [name]!")) - cut_overlay("improvised_grenade_filled") - arm_grenade(user, null, FALSE) +//assembly handling end + +/obj/item/grenade/iedcasing/attack_hand(mob/user, list/modifiers) + if(loc == user) //if we were picked up already, this opening whenever picked up is not ok + activator.ui_interact(user) //if any + . = ..() + if(.) + return + if(isnull(activator)) + return + activator.attack_hand() +/obj/item/grenade/iedcasing/update_icon_state() + if(isnull(activator)) + icon_state = "slicedapart" //this shouldnt happen but should prevent runtimes + return ..() + var/suffix = "" + var/obj/item/assembly/timer/as_timer = activator + var/obj/item/assembly/mousetrap/as_mousetrap = activator + var/obj/item/assembly/prox_sensor/as_prox = activator + if((istype(as_timer) && as_timer.timing) || (istype(as_mousetrap) && as_mousetrap.armed)) //these shouldve just had a common "active" variable or something + suffix = "-a" + else if(istype(as_prox)) + suffix = as_prox.timing ? "-arming" : (as_prox.scanning ? "-a" : "") + icon_state = "[base_icon_state]-[initial(activator.name)][suffix]" //signalers detonate instantly so theyre not here + return ..() + +/obj/item/grenade/iedcasing/attack_self(mob/user) + if(isnull(activator) || !COOLDOWN_FINISHED(src, spam_cd)) + balloon_alert(user, isnull(activator) ? "you shouldnt be seeing this" : "on cooldown!") + return + if(istype(activator, /obj/item/assembly/signaler)) + return //no signallers, signallers send a signal and i can imagine this having bad sideeffects if some has multiple of the same frequency in their backpack and uses them inhand by accident + activator.activate() + update_icon(UPDATE_ICON_STATE) + user.balloon_alert_to_viewers("arming!") + COOLDOWN_START(src, spam_cd, 1 SECONDS) + /obj/item/grenade/iedcasing/detonate(mob/living/lanced_by) //Blowing that can up + if(effects[/obj/item/shard]) //this has to be before so it initializes us a pellet cloud or something + shrapnel_radius = effects[/obj/item/shard] . = ..() if(!.) return update_mob() - explosion(src, devastation_range = -1, heavy_impact_range = -1, light_impact_range = 2, flame_range = 4) // small explosion, plus a very large fireball. + for(var/i = 1 to effects[/obj/item/food/meat/slab]) + new /obj/effect/gibspawner/generic(loc) + if(effects[/obj/item/paper]) + for(var/turf/open/floor in view(effects[/obj/item/paper], loc)) //this couldve been light impact range but fake pipebombs exploding into confetti is funny + new /obj/effect/decal/cleanable/confetti(floor) + var/heavy = floor(power * 0.2) + var/light = round(power * 0.7, 1) + var/flame = round(power + rand(-1, 1), 1) + explosion(loc, devastation_range = -1, heavy_impact_range = heavy, light_impact_range = light, flame_range = flame, explosion_cause = src) + + if(effects[/obj/item/stack/ore/bluespace_crystal/refined]) + for(var/mob/living/victim in view(light, loc)) + do_teleport(victim, get_turf(victim), min(12, effects[/obj/item/stack/ore/bluespace_crystal/refined] * 3), asoundin = 'sound/effects/phasein.ogg', channel = TELEPORT_CHANNEL_BLUESPACE) + qdel(src) -/obj/item/grenade/iedcasing/change_det_time() - return //always be random. +/obj/item/grenade/iedcasing/Destroy() + . = ..() + activator = null -/obj/item/grenade/iedcasing/examine(mob/user) + + + +/obj/item/grenade/iedcasing/spawned + power = 2.5 //20u welding fuel + activator = /obj/item/assembly/timer + +#define MAX_STUFFINGS 3 + +/obj/item/sliced_pipe + name = "halved pipe" + desc = "Two half-size pipes made from one." + w_class = WEIGHT_CLASS_SMALL + icon = 'icons/obj/weapons/grenade.dmi' + icon_state = "slicedapart" + /// Are wires inserted? If so, we are on the final step + var/wires_are_in = FALSE + /// Typecache of items we are allowed to stuff into the pipebomb for effects, only add items with effects + var/static/list/allowed = typecacheof(list( + /obj/item/food/meat/slab, + /obj/item/paper, + /obj/item/shard, + /obj/item/stack/ore/bluespace_crystal/refined, + )) + //this probably shouldve been a blacklist instead but god do i not wanna update this anytime a new assembly is added + /// A static list of types of assemblies that are allowed to be used to finish the bomb + var/static/list/allowed_activators = list( + /obj/item/assembly/signaler, + /obj/item/assembly/prox_sensor, + /obj/item/assembly/mousetrap, + /obj/item/assembly/mousetrap/armed, + /obj/item/assembly/timer, + /obj/item/assembly/wiremod, + /obj/item/assembly/voice, + ) + /// Static list of reagent to explosive power + var/static/list/fuel_power = list( + /datum/reagent/fuel = 0.5, + /datum/reagent/gunpowder = 1, + /datum/reagent/nitroglycerin = 2, + /datum/reagent/tatp = 2.5, + ) + /// Explosion power to be transferred to the new pipebomb + var/power = 5 + +/obj/item/sliced_pipe/Initialize(mapload) + . = ..() + create_reagents(20, OPENCONTAINER) + +/obj/item/sliced_pipe/examine(mob/user) . = ..() - . += "You can't tell when it will explode!" + if(!wires_are_in) + . += span_notice("You could stuff something in, or fill it with fuel or some other volatile chemical..") + . += span_notice("Afterwards, add some cable.") + else + . += span_notice("The wires are just dangling from it, you need some sort of activating assembly.") + +/obj/item/sliced_pipe/attackby(obj/item/item, mob/user, params) + if(!wires_are_in) + // here we can stuff in additional objects for a cooler effect + if(is_type_in_typecache(item, allowed) && contents.len < MAX_STUFFINGS) + balloon_alert(user, "stuffed in") + var/atom/movable/to_put = item + if(isstack(item)) + var/obj/item/stack/as_stack = item + to_put = as_stack.split_stack(user = null, amount = 1) + as_stack.merge_type = null //prevent them from merging inside for contents.len + to_put.forceMove(src) + return + + //if the item has reagents lets allow it to transfer + if(item.reagents) + return ..() + if(reagents.total_volume < 5) + balloon_alert(user, "add more fuel!") + return + + var/obj/item/stack/cable_coil/coil = item + if(!istype(coil)) + return + if (coil.get_amount() < 15) + balloon_alert(user, "need 15 length!") + return + coil.use(15) + + var/cur_power = 0 + for(var/datum/reagent/reagent as anything in reagents.reagent_list) + if(!(reagent.type in fuel_power)) + continue + cur_power += fuel_power[reagent.type] * reagent.volume / reagents.maximum_volume + + power *= cur_power + power -= contents.len / 2 + + balloon_alert(user, "wires attached") + icon_state = "[icon_state]-cable" + reagents.flags = SEALED_CONTAINER + wires_are_in = TRUE + else // wires are in, lets finish this up + var/obj/item/assembly/assembly = item + if(!istype(assembly) || !(assembly.type in allowed_activators)) + return + if(assembly.secured) + balloon_alert(user, "unsecure assembly first!") + return + if(!user.transferItemToLoc(assembly, src)) + return + user.balloon_alert(user, "attached") + + var/obj/item/grenade/iedcasing/pipebomb = new(drop_location()) + for(var/atom/movable/item_inside as anything in contents) + item_inside.forceMove(pipebomb) + + pipebomb.power = power + pipebomb.attach_activator(assembly) + pipebomb.setup_effects_from_contents() + var/was_in_hands = (loc == user) + qdel(src) + if(was_in_hands) + user.put_in_hands(pipebomb) + +#undef MAX_STUFFINGS diff --git a/code/game/objects/items/machine_wand.dm b/code/game/objects/items/machine_wand.dm new file mode 100644 index 0000000000000..71ea2fae1b65d --- /dev/null +++ b/code/game/objects/items/machine_wand.dm @@ -0,0 +1,181 @@ +///When EMPed, how long the remote will be disabled for by default. +#define EMP_TIMEOUT_DURATION (2 MINUTES) + +/obj/item/machine_remote + name = "machine wand" + desc = "A remote for controlling machines and bots around the station." + icon = 'icons/obj/antags/syndicate_tools.dmi' + icon_state = "weakpoint_locator" + inhand_icon_state = "weakpoint_locator" + lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' + w_class = WEIGHT_CLASS_NORMAL + ///If we're unable to be used, this is how long we have left to wait. + COOLDOWN_DECLARE(timeout_time) + ///The appearance put onto machines being actively controlled. + var/mutable_appearance/bug_appearance + ///Direct reference to the moving bug effect that moves towards machines we direct it at. + var/obj/effect/bug_moving/moving_bug + ///The machine that's currently being controlled. + var/atom/movable/controlling_machine_or_bot + +/obj/item/machine_remote/Initialize(mapload) + . = ..() + bug_appearance = mutable_appearance('icons/effects/effects.dmi', "fly-surrounding", ABOVE_WINDOW_LAYER) + register_context() + +/obj/item/machine_remote/Destroy(force) + . = ..() + if(controlling_machine_or_bot) + remove_old_machine() + QDEL_NULL(moving_bug) + QDEL_NULL(bug_appearance) + +/obj/item/machine_remote/examine(mob/user) + . = ..() + if(controlling_machine_or_bot) + . += span_notice("It is currently controlling [controlling_machine_or_bot]. Use in-hand to interact with it.") + +/obj/item/machine_remote/add_context(atom/source, list/context, obj/item/held_item, mob/user) + if(controlling_machine_or_bot) + context[SCREENTIP_CONTEXT_LMB] = "Use [controlling_machine_or_bot]" + context[SCREENTIP_CONTEXT_ALT_LMB] = "Flush Control" + return CONTEXTUAL_SCREENTIP_SET + return NONE + +/obj/item/machine_remote/proc/on_control_destroy(obj/machinery/source) + SIGNAL_HANDLER + remove_old_machine() + +/obj/item/machine_remote/ui_interact(mob/user, datum/tgui/ui) + if(!COOLDOWN_FINISHED(src, timeout_time)) + playsound(src, 'sound/machines/synth_no.ogg', 30 , TRUE) + say("Remote control disabled temporarily. Please try again soon.") + return FALSE + if(!controlling_machine_or_bot) + return + if(controlling_machine_or_bot.ui_interact(user, ui)) + return + controlling_machine_or_bot.interact(user) //no ui, interact instead (to open windoors and such) + +/obj/item/machine_remote/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(controlling_machine_or_bot) + return controlling_machine_or_bot.ui_act(action, params, ui, state) + +/obj/item/machine_remote/AltClick(mob/user) + . = ..() + if(moving_bug) //we have a bug in transit, so let's kill it. + QDEL_NULL(moving_bug) + if(!controlling_machine_or_bot) + return + say("Remote control over [controlling_machine_or_bot] stopped.") + remove_old_machine() + +/obj/item/machine_remote/afterattack(atom/target, mob/user, proximity_flag, click_parameters) + . = ..() + if(!COOLDOWN_FINISHED(src, timeout_time)) + playsound(src, 'sound/machines/synth_no.ogg', 30 , TRUE) + say("Remote control disabled temporarily. Please try again soon.") + return FALSE + if(!ismachinery(target) && !isbot(target)) + return + if(moving_bug) //we have a bug in transit already, so let's kill it. + QDEL_NULL(moving_bug) + var/turf/spawning_turf = (controlling_machine_or_bot ? get_turf(controlling_machine_or_bot) : get_turf(src)) + moving_bug = new(spawning_turf, src, target) + remove_old_machine() + +///Sets a controlled machine to a new machine, if possible. Checks if AIs can even control it. +/obj/item/machine_remote/proc/set_controlled_machine(obj/machinery/new_machine) + if(controlling_machine_or_bot == new_machine) + return + remove_old_machine() + if(istype(new_machine, /obj/machinery/power/apc)) + var/obj/machinery/power/apc/new_apc = new_machine + if(new_apc.aidisabled) + say("AI wire cut, machine uncontrollable.") + return + else if(istype(new_machine, /obj/machinery/door/airlock)) + var/obj/machinery/door/airlock/new_airlock = new_machine + if(!new_airlock.canAIControl()) + say("AI wire cut, machine uncontrollable.") + return + controlling_machine_or_bot = new_machine + controlling_machine_or_bot.add_overlay(bug_appearance) + RegisterSignal(controlling_machine_or_bot, COMSIG_QDELETING, PROC_REF(on_control_destroy)) + RegisterSignal(controlling_machine_or_bot, COMSIG_ATOM_EMP_ACT, PROC_REF(on_machine_emp)) + +///Removes the machine being controlled as the current machine, taking its signals and overlays with it. +/obj/item/machine_remote/proc/remove_old_machine() + if(!controlling_machine_or_bot) + return + UnregisterSignal(controlling_machine_or_bot, list(COMSIG_ATOM_EMP_ACT, COMSIG_QDELETING)) + controlling_machine_or_bot.cut_overlay(bug_appearance) + controlling_machine_or_bot = null + +///Called when the machine we're controlling is EMP, removing our control from it. +/obj/item/machine_remote/proc/on_machine_emp(datum/source, severity, protection) + SIGNAL_HANDLER + if(severity & EMP_PROTECT_CONTENTS) + return + disable_remote(EMP_TIMEOUT_DURATION) + +/obj/item/machine_remote/proc/disable_remote(timeout_duration) + remove_old_machine() + COOLDOWN_START(src, timeout_time, timeout_duration) + +///The effect of the bug moving towards the selected machinery to mess with. +/obj/effect/bug_moving + name = "bug" + desc = "Where da bug goin?" + icon_state = "fly" + obj_flags = CAN_BE_HIT + max_integrity = 20 + uses_integrity = TRUE + plane = ABOVE_GAME_PLANE + layer = FLY_LAYER + movement_type = PHASING + ///The controller that's sending us out to the machine. + var/obj/item/machine_remote/controller + ///The machine we are trying to get remote access to. + var/atom/movable/thing_moving_towards + +/obj/effect/bug_moving/Initialize(mapload, obj/item/machine_remote/controller, atom/movable/thing_moving_towards) + . = ..() + if(!controller) + CRASH("a moving bug has been created by something that isn't a machine remote controller!") + if(!thing_moving_towards) + CRASH("a moving bug has been created but isn't moving towards anything!") + src.controller = controller + src.thing_moving_towards = thing_moving_towards + var/datum/move_loop/loop = SSmove_manager.home_onto(src, thing_moving_towards, delay = 5, flags = MOVEMENT_LOOP_NO_DIR_UPDATE) + RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(reached_destination_check)) + RegisterSignal(thing_moving_towards, COMSIG_QDELETING, PROC_REF(on_machine_del)) + +/obj/effect/bug_moving/Destroy(force) + if(controller) + controller.moving_bug = null + controller = null + thing_moving_towards = null + return ..() + +/obj/effect/bug_moving/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + controller.disable_remote(EMP_TIMEOUT_DURATION) + qdel(src) + +/obj/effect/bug_moving/proc/reached_destination_check(datum/move_loop/source, result) + SIGNAL_HANDLER + if(!Adjacent(thing_moving_towards)) + return + controller.set_controlled_machine(thing_moving_towards) + qdel(src) + +/obj/effect/bug_moving/proc/on_machine_del(datum/move_loop/source) + SIGNAL_HANDLER + qdel(src) + +#undef EMP_TIMEOUT_DURATION diff --git a/code/game/objects/items/piggy_bank.dm b/code/game/objects/items/piggy_bank.dm new file mode 100644 index 0000000000000..6a9ee494a22ff --- /dev/null +++ b/code/game/objects/items/piggy_bank.dm @@ -0,0 +1,129 @@ +/** + * Piggy banks. They store your hard-earned money until you or someone destroys it. + * If the persistence id is set, money will be carried between rounds until broken. + */ +/obj/item/piggy_bank + name = "piggy bank" + desc = "A pig-shaped money container made of porkelain, oink. Do not throw." //pun very intended. + icon = 'icons/obj/fluff/general.dmi' + icon_state = "piggy_bank" + max_integrity = 8 + w_class = WEIGHT_CLASS_NORMAL + force = 12 + throwforce = 15 + throw_speed = 3 + throw_range = 7 + greyscale_config = /datum/greyscale_config/piggy_bank + ///Some piggy banks are persistent, meaning they carry dosh between rounds. + var/persistence_id + ///Callback to execute upon roundend to save the current amount of cash it has stored, IF persistent. + var/datum/callback/persistence_cb + ///How much dosh can this piggy bank hold. + var/maximum_value = PAYCHECK_COMMAND * 20 + ///How much dosh this piggy bank spawns with. + var/initial_value = 0 + +/obj/item/piggy_bank/Initialize(mapload) + if(!greyscale_colors) + greyscale_colors = pick(COLOR_PINK, + COLOR_LIGHT_ORANGE, + COLOR_GREEN_GRAY, + COLOR_PALE_BLUE_GRAY, + COLOR_DARK_MODERATE_LIME_GREEN, + COLOR_OFF_WHITE, + ) + + . = ..() + + AddElement(/datum/element/can_shatter, shattering_sound = SFX_SHATTER, shatters_as_weapon = TRUE) + AddElement(/datum/element/beauty, 500) + if(!persistence_id) + if(initial_value) + new /obj/item/holochip(src, initial_value) + return + + SSpersistence.load_piggy_bank(src) + persistence_cb = CALLBACK(src, PROC_REF(save_cash)) + SSticker.OnRoundend(persistence_cb) + + if(initial_value & initial_value + calculate_dosh_amount() <= maximum_value) + new /obj/item/holochip(src, initial_value) + +/obj/item/piggy_bank/proc/save_cash() + SSpersistence.save_piggy_bank(src) + +/obj/item/piggy_bank/Destroy() + if(persistence_cb) + LAZYREMOVE(SSticker.round_end_events, persistence_cb) //cleanup the callback. + persistence_cb = null + return ..() + +/obj/item/piggy_bank/deconstruct(disassembled = TRUE) + for(var/obj/item/thing as anything in contents) + thing.forceMove(loc) + //Smashing the piggy after the round is over doesn't count. + if(persistence_id && SSticker.current_state < GAME_STATE_FINISHED) + LAZYADD(SSpersistence.queued_broken_piggy_ids, persistence_id) + return ..() + +/obj/item/piggy_bank/attack_self(mob/user, modifiers) + . = ..() + if(DOING_INTERACTION_WITH_TARGET(user, src)) + return + balloon_alert(user, "rattle rattle...") + if(!do_after(user, 0.5 SECONDS, src)) + return + var/percentile = round(calculate_dosh_amount()/maximum_value * 100, 1) + if(percentile >= 10) + playsound(src, SFX_RATTLE, percentile * 0.5, FALSE, FALSE) + switch(percentile) + if(0) + balloon_alert(user, "it's empty") + if(1 to 9) + balloon_alert(user, "it's almost empty") + if(10 to 25) + balloon_alert(user, "it's some cash") + if(25 to 45) + balloon_alert(user, "it's plenty of cash") + if(45 to 70) + balloon_alert(user, "it feels almost full") + if(70 to 95) + balloon_alert(user, "it feels full") + if(95 to INFINITY) + balloon_alert(user, "brimming with cash") + +/obj/item/piggy_bank/attackby(obj/item/item, mob/user, params) + var/creds_value = item.get_item_credit_value() + if(isnull(creds_value)) + return ..() + + var/dosh_amount = calculate_dosh_amount() + + if(dosh_amount >= maximum_value) + balloon_alert(user, "it's full!") + else if(dosh_amount + creds_value > maximum_value) + balloon_alert(user, "too much cash!") + else if(!user.transferItemToLoc(item, src)) + balloon_alert(user, "stuck in your hands!") + else + balloon_alert(user, "inserted [creds_value] creds") + return TRUE + +///Returns the total amount of credits that its contents amount to. +/obj/item/piggy_bank/proc/calculate_dosh_amount() + var/total_value = 0 + for(var/obj/item/item in contents) + total_value += item.get_item_credit_value() + return total_value + +/obj/item/piggy_bank/museum + name = "Pigston Swinelord VI" + desc = "The museum's mascot piggy bank and favorite embezzler, known to carry donations between shifts without paying taxes. The space IRS hates him." + persistence_id = "museum_piggy" + greyscale_colors = COLOR_PINK + maximum_value = PAYCHECK_COMMAND * 100 + initial_value = PAYCHECK_COMMAND * 4 + +/obj/item/piggy_bank/museum/Initialize(mapload) + . = ..() + AddComponent(/datum/component/areabound) //do not steal. diff --git a/code/game/objects/items/plushes.dm b/code/game/objects/items/plushes.dm index ca669a9733e12..e2d1b6f26e904 100644 --- a/code/game/objects/items/plushes.dm +++ b/code/game/objects/items/plushes.dm @@ -44,7 +44,8 @@ /obj/item/toy/plush/Initialize(mapload) . = ..() AddComponent(/datum/component/squeak, squeak_override) - AddElement(/datum/element/bed_tuckable, 6, -5, 90) + AddElement(/datum/element/bed_tuckable, mapload, 6, -5, 90) + AddElement(/datum/element/toy_talk) //have we decided if Pinocchio goes in the blue or pink aisle yet? if(gender == NEUTER) diff --git a/code/game/objects/items/puzzle_pieces.dm b/code/game/objects/items/puzzle_pieces.dm index 7ac22d00897ea..7e954b339f021 100644 --- a/code/game/objects/items/puzzle_pieces.dm +++ b/code/game/objects/items/puzzle_pieces.dm @@ -68,6 +68,7 @@ . = ..() if(!isnull(puzzle_id) && uses_queuelinks) SSqueuelinks.add_to_queue(src, puzzle_id) + AddElement(/datum/element/empprotection, EMP_PROTECT_ALL) /obj/machinery/door/puzzle/MatchedLinks(id, list/partners) for(var/partner in partners) @@ -82,9 +83,6 @@ /obj/machinery/door/puzzle/Bumped(atom/movable/AM) return !density && ..() -/obj/machinery/door/puzzle/emp_act(severity) - return - /obj/machinery/door/puzzle/ex_act(severity, target) return FALSE @@ -289,28 +287,47 @@ // literally just buttons // -/obj/machinery/puzzle_button - name = "control panel" - desc = "A panel that controls something nearby. I'm sure it being covered in hazard stripes is fine." +/obj/machinery/puzzle + name = "abstract puzzle gizmo" icon = 'icons/obj/machines/wallmounts.dmi' - icon_state = "lockdown0" resistance_flags = INDESTRUCTIBLE | FIRE_PROOF | ACID_PROOF | LAVA_PROOF - base_icon_state = "lockdown" /// have we been pressed already? var/used = FALSE /// can we be pressed only once? var/single_use = TRUE /// puzzle id we send on press - var/id = "0" //null would literally open every puzzle door without an id + var/id //null would literally open every puzzle door without an id /// queue size, must match count of objects this activates! var/queue_size = 2 + /// should the puzzle machinery perform the final step of the queue link on LateInitialize? An alternative to queue size + var/late_initialize_pop = FALSE -/obj/machinery/puzzle_button/Initialize(mapload) +/obj/machinery/puzzle/Initialize(mapload) . = ..() if(!isnull(id)) - SSqueuelinks.add_to_queue(src, id, queue_size) + SSqueuelinks.add_to_queue(src, id, late_initialize_pop ? 0 : queue_size) + return late_initialize_pop ? INITIALIZE_HINT_LATELOAD : . + +/obj/machinery/puzzle/LateInitialize() + . = ..() + if(late_initialize_pop && id && SSqueuelinks.queues[id]) + SSqueuelinks.pop_link(id) + +/obj/machinery/puzzle/proc/on_puzzle_complete() //incase someone wants to make this do something else for some reason + SEND_SIGNAL(src, COMSIG_PUZZLE_COMPLETED) + +/obj/machinery/puzzle/update_icon_state() + icon_state = "[base_icon_state][used]" + return ..() + +/obj/machinery/puzzle/button + name = "control panel" + desc = "A panel that controls something nearby. I'm sure it being covered in hazard stripes is fine." + icon = 'icons/obj/machines/wallmounts.dmi' + icon_state = "lockdown0" + base_icon_state = "lockdown" -/obj/machinery/puzzle_button/attack_hand(mob/user, list/modifiers) +/obj/machinery/puzzle/button/attack_hand(mob/user, list/modifiers) . = ..() if(.) return @@ -320,37 +337,17 @@ update_icon_state() visible_message(span_notice("[user] presses a button on [src]."), span_notice("You press a button on [src].")) playsound(src, 'sound/machines/terminal_button07.ogg', 45, TRUE) - open_doors() - -/obj/machinery/puzzle_button/proc/open_doors() //incase someone wants to make this do something else for some reason - SEND_SIGNAL(src, COMSIG_PUZZLE_COMPLETED) + on_puzzle_complete() -/obj/machinery/puzzle_button/update_icon_state() - icon_state = "[base_icon_state][used]" - return ..() +MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/puzzle/button, 32) -MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/puzzle_button, 32) - -/obj/machinery/puzzle_keycardpad +/obj/machinery/puzzle/keycardpad name = "keycard panel" desc = "A panel that controls something nearby. Accepts keycards." - icon = 'icons/obj/machines/wallmounts.dmi' icon_state = "keycardpad0" - resistance_flags = INDESTRUCTIBLE | FIRE_PROOF | ACID_PROOF | LAVA_PROOF base_icon_state = "keycardpad" - /// were we used successfully? - var/used = FALSE - /// puzzle id we send if the correct card is swiped - var/id = "0" - /// queue size, must match count of objects this activates! - var/queue_size = 2 - -/obj/machinery/puzzle_keycardpad/Initialize(mapload) - . = ..() - if(!isnull(id)) - SSqueuelinks.add_to_queue(src, id, queue_size) -/obj/machinery/puzzle_keycardpad/attackby(obj/item/attacking_item, mob/user, params) +/obj/machinery/puzzle/keycardpad/attackby(obj/item/attacking_item, mob/user, params) . = ..() if(!istype(attacking_item, /obj/item/keycard) || used) return @@ -363,13 +360,75 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/puzzle_button, 32) used = TRUE update_icon_state() playsound(src, 'sound/machines/beep.ogg', 45, TRUE) - SEND_SIGNAL(src, COMSIG_PUZZLE_COMPLETED) + on_puzzle_complete() + +MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/puzzle/keycardpad, 32) + +/obj/machinery/puzzle/password + name = "password panel" + desc = "A panel that controls something nearby. This one requires a (case-sensitive) password, and it's not \"Swordfish\"." + icon_state = "passpad0" + base_icon_state = "passpad" + ///The password to this door. + var/password = "" + ///The text shown in the tgui input popup + var/tgui_text = "Please enter the password." + ///The title of the tgui input popup + var/tgui_title = "What's the password?" + ///Decides whether the max length of the input is MAX_NAME_LEN or the length of the password. + var/input_max_len_is_pass = FALSE + +/obj/machinery/puzzle/password/interact(mob/user, list/modifiers) + if(used && single_use) + return + if(!user.can_perform_action(src, ALLOW_SILICON_REACH) || !user.can_interact_with(src)) + return + var/pass_input = tgui_input_text(user, tgui_text, tgui_title, max_length = input_max_len_is_pass ? length(password) : MAX_NAME_LEN) + if(isnull(pass_input) || !user.can_perform_action(src, ALLOW_SILICON_REACH) || !user.can_interact_with(src)) + return + var/correct = pass_input == password + balloon_alert_to_viewers("[correct ? "correct" : "wrong"] password[correct ? "" : "!"]") + if(!correct) + playsound(src, 'sound/machines/buzz-sigh.ogg', 45, TRUE) + return + used = single_use + update_icon_state() + playsound(src, 'sound/machines/terminal_button07.ogg', 45, TRUE) + on_puzzle_complete() -/obj/machinery/puzzle_keycardpad/update_icon_state() - icon_state = "[base_icon_state][used]" - return ..() +MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/puzzle/password, 32) -MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/puzzle_keycardpad, 32) +/obj/machinery/puzzle/password/pin + desc = "A panel that controls something nearby. This one requires a PIN password, so let's start by typing in 1234..." + tgui_text = "Please enter the PIN code." + tgui_title = "What's the PIN code?" + input_max_len_is_pass = TRUE + ///The length of the PIN. Suggestion: something between 4 and 12. + var/pin_length = 6 + ///associate a color to each digit that may be found in the password. + var/list/digit_to_color = list() + +/obj/machinery/puzzle/password/pin/Initialize(mapload) + . = ..() + + for(var/iteration in 1 to pin_length) + password += "[rand(1, 9)]" + + var/list/possible_colors = list( + "white", + "black", + "red", + "green", + "blue", + "yellow", + "orange", + "brown", + "gray", + ) + for(var/digit in 0 to 9) + digit_to_color["[digit]"] = pick_n_take(possible_colors) + +MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/puzzle/password/pin, 32) // // blockade @@ -445,7 +504,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/puzzle_keycardpad, 32) if(isnull(id) || isnull(queue_id)) log_mapping("[src] id:[id] has no id or door id and has been deleted") return INITIALIZE_HINT_QDEL - + SSqueuelinks.add_to_queue(src, queue_id) /obj/effect/puzzle_poddoor_open/MatchedLinks(id, list/partners) @@ -461,3 +520,99 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/puzzle_keycardpad, 32) if(isnull(openclose)) openclose = door.density INVOKE_ASYNC(door, openclose ? TYPE_PROC_REF(/obj/machinery/door/poddoor, open) : TYPE_PROC_REF(/obj/machinery/door/poddoor, close)) + +#define MAX_PUZZLE_DOTS_PER_ROW 4 +#define PUZZLE_DOTS_VERTICAL_OFFSET 7 +#define PUZZLE_DOTS_HORIZONTAL_OFFSET 7 + +///A dotted board that can be used as clue for PIN puzzle machinery +/obj/effect/decal/puzzle_dots + name = "dotted board" + desc = "A board filled with colored dots. What could this mean?" + icon = 'icons/obj/fluff/puzzle_small.dmi' + icon_state = "puzzle_dots" + plane = GAME_PLANE //visible over walls + resistance_flags = INDESTRUCTIBLE | FIRE_PROOF | UNACIDABLE | LAVA_PROOF + flags_1 = UNPAINTABLE_1 + ///The id of the puzzle we're linked to. + var/id + +/obj/effect/decal/puzzle_dots/Initialize(mapload) + . = ..() + if(id) + SSqueuelinks.add_to_queue(src, id) + +/obj/effect/decal/puzzle_dots/MatchedLinks(id, partners) + var/obj/machinery/puzzle/password/pin/pad = locate() in partners + var/list/pass_digits = splittext(pad.password, "") + var/pass_len = length(pass_digits) + var/extra_rows = CEILING((pass_len/MAX_PUZZLE_DOTS_PER_ROW)-1, 1) + if(extra_rows) + pixel_y += round(extra_rows*(PUZZLE_DOTS_VERTICAL_OFFSET*0.5)) + for(var/i in 1 to extra_rows) + var/mutable_appearance/row = mutable_appearance(icon, icon_state) + row.pixel_y = -i*PUZZLE_DOTS_VERTICAL_OFFSET + add_overlay(row) + for(var/i in 1 to pass_len) + var/mutable_appearance/colored_dot = mutable_appearance(icon, "puzzle_dot_single") + colored_dot.color = pad.digit_to_color[pass_digits[i]] + colored_dot.pixel_x = PUZZLE_DOTS_HORIZONTAL_OFFSET * ((i-1)%MAX_PUZZLE_DOTS_PER_ROW) + colored_dot.pixel_y -= CEILING((i/MAX_PUZZLE_DOTS_PER_ROW)-1, 1)*PUZZLE_DOTS_VERTICAL_OFFSET + add_overlay(colored_dot) + +#undef MAX_PUZZLE_DOTS_PER_ROW +#undef PUZZLE_DOTS_VERTICAL_OFFSET +#undef PUZZLE_DOTS_HORIZONTAL_OFFSET + + +/obj/effect/decal/cleanable/crayon/puzzle + name = "Password character" + icon_state = "0" + ///The id of the puzzle we're linked to. + var/puzzle_id + +/obj/effect/decal/cleanable/crayon/puzzle/Initialize(mapload, main, type, e_name, graf_rot, alt_icon = null) + . = ..() + name = "number" + if(puzzle_id) + SSqueuelinks.add_to_queue(src, puzzle_id) + +/obj/effect/decal/cleanable/crayon/puzzle/MatchedLinks(id, partners) + var/obj/machinery/puzzle/password/pad = locate() in partners + var/list/pass_character = splittext(pad.password, "") + var/chosen_character = icon_state + if(!findtext(chosen_character, GLOB.is_alphanumeric)) + qdel(src) + return FALSE + icon_state = pick(pass_character) + if(!text2num(icon_state)) + name = "letter" + desc = "A letter vandalizing the station." + return TRUE + +/obj/effect/decal/cleanable/crayon/puzzle/pin + name = "PIN number" + +/obj/effect/decal/cleanable/crayon/puzzle/pin/MatchedLinks(id, partners) + . = ..() + var/obj/machinery/puzzle/password/pin/pad = locate() in partners + add_atom_colour(pad.digit_to_color[icon_state], FIXED_COLOUR_PRIORITY) + +/obj/item/paper/fluff/scrambled_pass + name = "gibberish note" + icon_state = "scrap" + ///The ID associated to the puzzle we're part of. + var/puzzle_id + +/obj/item/paper/fluff/scrambled_pass/Initialize(mapload) + . = ..() + if(mapload && puzzle_id) + SSqueuelinks.add_to_queue(src, puzzle_id) + +/obj/item/paper/fluff/scrambled_pass/MatchedLinks(id, partners) + var/obj/machinery/puzzle/password/pad = locate() in partners + var/scrambled_text = "" + var/list/pass_characters = splittext(pad.password, "") + for(var/i in 1 to rand(200, 300)) + scrambled_text += pick(pass_characters) + add_raw_text(scrambled_text) diff --git a/code/game/objects/items/rcd/RCD.dm b/code/game/objects/items/rcd/RCD.dm index 9570614b400b3..025571a90a782 100644 --- a/code/game/objects/items/rcd/RCD.dm +++ b/code/game/objects/items/rcd/RCD.dm @@ -219,7 +219,10 @@ delay *= FREQUENT_USE_DEBUFF_MULTIPLIER current_active_effects += 1 - _rcd_create_effect(target, user, delay, rcd_results) + var/target_name = target.name //Store the name before it gets mutated due to deconstruction. + var/target_path = target.type + if(_rcd_create_effect(target, user, delay, rcd_results)) + log_tool("used RCD with design path: \"[rcd_results["[RCD_DESIGN_MODE]"] == RCD_DECONSTRUCT ? "deconstruction" : rcd_results["[RCD_DESIGN_PATH]"]]\" with delay: \"[delay / (1 SECONDS)]s\" at target: \"[target_name] ([target_path])\" in location: \"[AREACOORD(target)]\".", user) current_active_effects -= 1 /** diff --git a/code/game/objects/items/robot/items/hud.dm b/code/game/objects/items/robot/items/hud.dm index 6b11c71941b7f..7ee8a9386258b 100644 --- a/code/game/objects/items/robot/items/hud.dm +++ b/code/game/objects/items/robot/items/hud.dm @@ -1,10 +1,10 @@ /obj/item/borg/sight var/sight_mode = null + icon = 'icons/obj/clothing/glasses.dmi' /obj/item/borg/sight/xray name = "\proper X-ray vision" - icon = 'icons/obj/signs.dmi' - icon_state = "securearea" + icon_state = "securityhudnight" sight_mode = BORGXRAY /obj/item/borg/sight/thermal diff --git a/code/game/objects/items/shields.dm b/code/game/objects/items/shields.dm index b711bb63d4519..3e3af7bc36f5e 100644 --- a/code/game/objects/items/shields.dm +++ b/code/game/objects/items/shields.dm @@ -397,4 +397,48 @@ balloon_alert(user, "extend it first!") return COMPONENT_BLOCK_ITEM_DISARM_ATTACK +/datum/armor/item_shield/ballistic + melee = 30 + bullet = 85 + bomb = 10 + laser = 80 + +/obj/item/shield/ballistic + name = "ballistic shield" + desc = "A heavy shield designed for blocking projectiles, weaker to melee." + icon_state = "ballistic" + inhand_icon_state = "ballistic" + custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 2, /datum/material/glass = SHEET_MATERIAL_AMOUNT * 2, /datum/material/titanium =SHEET_MATERIAL_AMOUNT) + max_integrity = 75 + shield_break_leftover = /obj/item/stack/rods/ten + armor_type = /datum/armor/item_shield/ballistic + +/obj/item/shield/ballistic/attackby(obj/item/attackby_item, mob/user, params) + if(istype(attackby_item, /obj/item/stack/sheet/mineral/titanium)) + if (atom_integrity >= max_integrity) + to_chat(user, span_warning("[src] is already in perfect condition.")) + return + var/obj/item/stack/sheet/mineral/titanium/titanium_sheet = attackby_item + titanium_sheet.use(1) + atom_integrity = max_integrity + to_chat(user, span_notice("You repair [src] with [titanium_sheet].")) + return + return ..() + +/datum/armor/item_shield/improvised + melee = 40 + bullet = 30 + laser = 30 + +/obj/item/shield/improvised + name = "improvised shield" + desc = "A crude shield made out of several sheets of iron taped together, not very durable." + icon_state = "improvised" + inhand_icon_state = "improvised" + custom_materials = list(/datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 2) + max_integrity = 35 + shield_break_leftover = /obj/item/stack/rods/two + armor_type = /datum/armor/item_shield/improvised + block_sound = 'sound/items/trayhit2.ogg' + #undef BATON_BASH_COOLDOWN diff --git a/code/game/objects/items/shrapnel.dm b/code/game/objects/items/shrapnel.dm index 59fbf61f62a90..cdc786fc8db56 100644 --- a/code/game/objects/items/shrapnel.dm +++ b/code/game/objects/items/shrapnel.dm @@ -44,6 +44,15 @@ ricochet_incidence_leeway = 0 ricochet_decay_chance = 0.9 +/obj/projectile/bullet/shrapnel/ied + name = "flying glass shrapnel" + damage = 15 + range = 6 + ricochets_max = 1 + ricochet_chance = 40 + shrapnel_type = /obj/item/shard + ricochet_incidence_leeway = 60 + /obj/projectile/bullet/pellet/stingball name = "stingball pellet" damage = 3 diff --git a/code/game/objects/items/stacks/golem_food/golem_status_effects.dm b/code/game/objects/items/stacks/golem_food/golem_status_effects.dm index 514ab36ed66d3..43cd135904f87 100644 --- a/code/game/objects/items/stacks/golem_food/golem_status_effects.dm +++ b/code/game/objects/items/stacks/golem_food/golem_status_effects.dm @@ -433,15 +433,14 @@ var/glow_range = 3 var/glow_power = 1 var/glow_color = LIGHT_COLOR_DEFAULT - var/datum/component/overlay_lighting/lightbulb + var/obj/effect/dummy/lighting_obj/moblight/lightbulb /datum/status_effect/golem_lightbulb/on_apply() . = ..() if (!.) return to_chat(owner, span_notice("You start to emit a healthy glow.")) - owner.light_system = OVERLAY_LIGHT - lightbulb = owner.AddComponent(/datum/component/overlay_lighting, _range = glow_range, _power = glow_power, _color = glow_color) + lightbulb = owner.mob_light(glow_range, glow_power, glow_color) owner.add_filter(LIGHTBULB_FILTER, 2, list("type" = "outline", "color" = glow_color, "alpha" = 60, "size" = 1)) /datum/status_effect/golem_lightbulb/on_remove() diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/medical.dm index 306bc0fa9f942..1f33384b39337 100644 --- a/code/game/objects/items/stacks/medical.dm +++ b/code/game/objects/items/stacks/medical.dm @@ -310,6 +310,7 @@ name = "improvised gauze" singular_name = "improvised gauze" desc = "A roll of cloth roughly cut from something that does a decent job of stabilizing wounds, but less efficiently so than real medical gauze." + icon_state = "gauze_imp" self_delay = 6 SECONDS other_delay = 3 SECONDS splint_factor = 0.85 diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm index 9c4e687c12862..3b32e6b1d9356 100644 --- a/code/game/objects/items/stacks/sheets/sheet_types.dm +++ b/code/game/objects/items/stacks/sheets/sheet_types.dm @@ -48,6 +48,11 @@ GLOBAL_LIST_INIT(metal_recipes, list ( \ new /datum/stack_recipe("bench (left)", /obj/structure/chair/sofa/bench/left, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ new /datum/stack_recipe("bench (right)", /obj/structure/chair/sofa/bench/right, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ new /datum/stack_recipe("bench (corner)", /obj/structure/chair/sofa/bench/corner, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ + new /datum/stack_recipe("tram bench (solo)", /obj/structure/chair/sofa/bench/tram/solo, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ + new /datum/stack_recipe("tram bench (middle)", /obj/structure/chair/sofa/bench/tram, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ + new /datum/stack_recipe("tram bench (left)", /obj/structure/chair/sofa/bench/tram/left, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ + new /datum/stack_recipe("tram bench (right)", /obj/structure/chair/sofa/bench/tram/right, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ + new /datum/stack_recipe("tram bench (corner)", /obj/structure/chair/sofa/bench/tram/corner, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ )), \ new /datum/stack_recipe_list("chess pieces", list( \ new /datum/stack_recipe("White Pawn", /obj/structure/chess/whitepawn, 2, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ENTERTAINMENT), \ @@ -74,6 +79,7 @@ GLOBAL_LIST_INIT(metal_recipes, list ( \ new/datum/stack_recipe("closet", /obj/structure/closet, 2, time = 1.5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ null, \ new/datum/stack_recipe("atmos canister", /obj/machinery/portable_atmospherics/canister, 10, time = 3 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ATMOSPHERIC), \ + new/datum/stack_recipe("pipe", /obj/item/pipe/quaternary/pipe/crafted, 1, time = 4 SECONDS, check_density = FALSE, category = CAT_ATMOSPHERIC), \ null, \ new/datum/stack_recipe("floor tile", /obj/item/stack/tile/iron/base, 1, 4, 20, category = CAT_TILES), \ new/datum/stack_recipe("iron rod", /obj/item/stack/rods, 1, 2, 60, category = CAT_MISC), \ @@ -778,7 +784,8 @@ GLOBAL_LIST_INIT(bronze_recipes, list ( \ ) GLOBAL_LIST_INIT(plastic_recipes, list( new /datum/stack_recipe("plastic floor tile", /obj/item/stack/tile/plastic, 1, 4, 20, time = 2 SECONDS, check_density = FALSE, category = CAT_TILES), \ - new /datum/stack_recipe("thermoplastic tram tile", /obj/item/stack/thermoplastic, 1, 2, time = 4 SECONDS, check_density = FALSE, placement_checks = STACK_CHECK_TRAM_EXCLUSIVE, category = CAT_TILES), \ + new /datum/stack_recipe("light tram tile", /obj/item/stack/thermoplastic/light, 1, 4, 20, time = 2 SECONDS, check_density = FALSE, category = CAT_TILES), \ + new /datum/stack_recipe("dark tram tile", /obj/item/stack/thermoplastic, 1, 4, 20, time = 2 SECONDS, check_density = FALSE, category = CAT_TILES), \ new /datum/stack_recipe("folding plastic chair", /obj/structure/chair/plastic, 2, check_density = FALSE, category = CAT_FURNITURE), \ new /datum/stack_recipe("plastic flaps", /obj/structure/plasticflaps, 5, one_per_turf = TRUE, on_solid_ground = TRUE, time = 4 SECONDS, category = CAT_FURNITURE), \ new /datum/stack_recipe("water bottle", /obj/item/reagent_containers/cup/glass/waterbottle/empty, check_density = FALSE, category = CAT_CONTAINERS), \ diff --git a/code/game/objects/items/storage/backpack.dm b/code/game/objects/items/storage/backpack.dm index 68463dad2b0c1..10c95056afffb 100644 --- a/code/game/objects/items/storage/backpack.dm +++ b/code/game/objects/items/storage/backpack.dm @@ -193,6 +193,12 @@ icon_state = "backpack-virology" inhand_icon_state = "viropack" +/obj/item/storage/backpack/floortile + name = "floortile backpack" + desc = "It's a backpack especially designed for use in floortiles..." + icon_state = "floortile_backpack" + inhand_icon_state = "backpack" + /obj/item/storage/backpack/ert name = "emergency response team commander backpack" desc = "A spacious backpack with lots of pockets, worn by the Commander of an Emergency Response Team." diff --git a/code/game/objects/items/storage/boxes/clothes_boxes.dm b/code/game/objects/items/storage/boxes/clothes_boxes.dm index 4c18ef4f6df28..18a6ec31d87c9 100644 --- a/code/game/objects/items/storage/boxes/clothes_boxes.dm +++ b/code/game/objects/items/storage/boxes/clothes_boxes.dm @@ -196,3 +196,18 @@ new /obj/item/clothing/suit/hooded/chaplain_hoodie/divine_archer(src) new /obj/item/clothing/gloves/divine_archer(src) new /obj/item/clothing/shoes/divine_archer(src) + +/obj/item/storage/box/floor_camo + name = "floor tile camo box" + desc = "Thank you for shopping from Camo-J's, our uniquely designed \ + floor-tile 'NT scum' styled camouflage fatigues is the ultimate \ + espionage uniform used by the very best. Providing the best \ + flexibility, with our latest Camo-tech threads. Perfect for \ + risky-espionage hallway operations. Enjoy our product!" + +/obj/item/storage/box/floor_camo/PopulateContents() + new /obj/item/clothing/under/syndicate/floortilecamo(src) + new /obj/item/clothing/mask/floortilebalaclava(src) + new /obj/item/clothing/gloves/combat/floortile(src) + new /obj/item/clothing/shoes/jackboots/floortile(src) + new /obj/item/storage/backpack/floortile(src) diff --git a/code/game/objects/items/storage/boxes/security_boxes.dm b/code/game/objects/items/storage/boxes/security_boxes.dm index 8e55986fb40d8..459c0ab7ce29e 100644 --- a/code/game/objects/items/storage/boxes/security_boxes.dm +++ b/code/game/objects/items/storage/boxes/security_boxes.dm @@ -174,6 +174,16 @@ for(var/i in 1 to 7) new /obj/item/ammo_casing/shotgun/buckshot(src) +/obj/item/storage/box/slugs + name = "box of shotgun shells (Lethal - Slugs)" + desc = "A box full of lethal shotgun slugs, designed for shotguns." + icon_state = "breacher_box" + illustration = null + +/obj/item/storage/box/slugs/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/ammo_casing/shotgun(src) + /obj/item/storage/box/beanbag name = "box of shotgun shells (Less Lethal - Beanbag)" desc = "A box full of beanbag shotgun shells, designed for shotguns." diff --git a/code/game/objects/items/storage/medkit.dm b/code/game/objects/items/storage/medkit.dm index e389b990a4ca8..0ecd943b60457 100644 --- a/code/game/objects/items/storage/medkit.dm +++ b/code/game/objects/items/storage/medkit.dm @@ -271,6 +271,24 @@ /obj/item/storage/pill_bottle/penacid = 1) generate_items_inside(items_inside,src) +/obj/item/storage/medkit/tactical_lite + name = "combat first aid kit" + icon_state = "medkit_tactical" + inhand_icon_state = "medkit-tactical" + damagetype_healed = HEAL_ALL_DAMAGE + +/obj/item/storage/medkit/tactical_lite/PopulateContents() + if(empty) + return + var/static/list/items_inside = list( + /obj/item/healthanalyzer/advanced = 1, + /obj/item/reagent_containers/hypospray/medipen/atropine = 1, + /obj/item/stack/medical/gauze = 1, + /obj/item/stack/medical/suture/medicated = 2, + /obj/item/stack/medical/mesh/advanced = 2, + ) + generate_items_inside(items_inside, src) + /obj/item/storage/medkit/tactical name = "combat medical kit" desc = "I hope you've got insurance." diff --git a/code/game/objects/items/storage/storage.dm b/code/game/objects/items/storage/storage.dm index cfdfef8a4590c..8631d62e79efd 100644 --- a/code/game/objects/items/storage/storage.dm +++ b/code/game/objects/items/storage/storage.dm @@ -55,7 +55,6 @@ max_total_storage, list/canhold, list/canthold, - storage_type = /datum/storage, storage_type, ) // If no type was passed in, default to what we already have diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm index 429acfb9ce966..217a07500064d 100644 --- a/code/game/objects/items/storage/uplink_kits.dm +++ b/code/game/objects/items/storage/uplink_kits.dm @@ -10,6 +10,7 @@ #define KIT_SNIPER "sniper" #define KIT_NUKEOPS_METAGAME "metaops" #define KIT_LORD_SINGULOTH "lordsingulo" +#define KIT_REVOLUTIONARY "revolutionary" #define KIT_JAMES_BOND "bond" #define KIT_NINJA "ninja" @@ -19,6 +20,8 @@ #define KIT_BEES "bee" #define KIT_MR_FREEZE "mr_freeze" #define KIT_TRAITOR_2006 "ancient" +#define KIT_SAM_FISHER "sam_fisher" +#define KIT_PROP_HUNT "prop_hunt" /// last audited december 2022 /obj/item/storage/box/syndicate @@ -35,7 +38,8 @@ KIT_IMPLANTS = 1, KIT_HACKER = 3, KIT_SNIPER = 1, - KIT_NUKEOPS_METAGAME = 1 + KIT_NUKEOPS_METAGAME = 1, + KIT_REVOLUTIONARY = 2 ))) if(KIT_RECON) new /obj/item/clothing/glasses/thermal/xray(src) // ~8 tc? @@ -165,6 +169,18 @@ new /obj/item/card/emag(src) // 4 tc new /obj/item/card/emag/doorjack(src) // 3 tc + if(KIT_REVOLUTIONARY) + new /obj/item/healthanalyzer/rad_laser(src) // 3 TC + new /obj/item/assembly/flash/hypnotic(src) // 7 TC + new /obj/item/storage/pill_bottle/lsd(src) // ~1 TC + new /obj/item/pen/sleepy(src) // 4 TC + new /obj/item/gun/ballistic/revolver/nagant(src) // 13 TC comparable to 357. revolvers + new /obj/item/megaphone(src) + new /obj/item/bedsheet/rev(src) + new /obj/item/clothing/suit/armor/vest/russian_coat(src) + new /obj/item/clothing/head/helmet/rus_ushanka(src) + new /obj/item/storage/box/syndie_kit/poster_box(src) + /obj/item/storage/box/syndicate/bundle_b/PopulateContents() switch (pick_weight(list( KIT_JAMES_BOND = 2, @@ -174,7 +190,9 @@ KIT_MAD_SCIENTIST = 2, KIT_BEES = 1, KIT_MR_FREEZE = 2, - KIT_TRAITOR_2006 = 1 + KIT_TRAITOR_2006 = 1, + KIT_SAM_FISHER = 1, + KIT_PROP_HUNT = 1 ))) if(KIT_JAMES_BOND) new /obj/item/gun/ballistic/automatic/pistol(src) // 7 tc @@ -261,9 +279,26 @@ new /obj/item/gun/energy/laser/thermal/cryo(src) // ~6 tc new /obj/item/melee/energy/sword/saber/blue(src) //see see it fits the theme bc its blue and ice is blue, 8 tc - if(KIT_TRAITOR_2006) //A kit so old, it's probably older than you. //This bundle is filled with the entire unlink contents traitors had access to in 2006, from OpenSS13. Notably the esword was not a choice but existed in code. + if(KIT_TRAITOR_2006) //A kit so old, it's probably older than you. //This bundle is filled with the entire uplink contents traitors had access to in 2006, from OpenSS13. Notably the esword was not a choice but existed in code. new /obj/item/storage/toolbox/emergency/old/ancientbundle(src) //Items fit neatly into a classic toolbox just to remind you what the theme is. + if(KIT_SAM_FISHER) + new /obj/item/clothing/under/syndicate/combat(src) + new /obj/item/clothing/suit/armor/vest/marine/pmc(src) //The armor kit is comparable to the infiltrator, 6 TC + new /obj/item/clothing/head/helmet/marine/pmc(src) + new /obj/item/clothing/mask/gas/sechailer(src) + new /obj/item/clothing/glasses/night(src) // 3~ TC + new /obj/item/clothing/gloves/krav_maga/combatglovesplus(src) //5TC + new /obj/item/clothing/shoes/jackboots(src) + new /obj/item/storage/belt/military/assault/fisher(src) //items in this belt easily costs 18 TC + + if(KIT_PROP_HUNT) + new /obj/item/chameleon(src) // 7 TC + new /obj/item/card/emag/doorjack(src) // 3 TC + new /obj/item/storage/box/syndie_kit/imp_stealth(src) //8 TC + new /obj/item/gun/ballistic/automatic/pistol(src) // 7 TC + new /obj/item/clothing/glasses/thermal(src) // 4 TC + /obj/item/storage/toolbox/emergency/old/ancientbundle/ //So the subtype works /obj/item/storage/toolbox/emergency/old/ancientbundle/PopulateContents() @@ -276,6 +311,17 @@ new /obj/item/implanter/freedom(src) // 5 tc new /obj/item/stack/telecrystal(src) //The failsafe/self destruct isn't an item we can physically include in the kit, but 1 TC is technically enough to buy the equivalent. +/obj/item/storage/belt/military/assault/fisher + +/obj/item/storage/belt/military/assault/fisher/PopulateContents() + new /obj/item/gun/ballistic/automatic/pistol/clandestine(src) // 7 TC + new /obj/item/suppressor(src) // 3 TC + new /obj/item/ammo_box/magazine/m10mm(src) // 1 TC + new /obj/item/ammo_box/magazine/m10mm(src) + new /obj/item/gun/energy/recharge/fisher(src) // Acquirable through black market, shit utility item 1 TC + new /obj/item/card/emag/doorjack(src) // 3 TC + new /obj/item/knife/combat(src) //comparable to the e-dagger, 2 TC + /obj/item/storage/box/syndie_kit name = "box" desc = "A sleek, sturdy box." @@ -807,6 +853,7 @@ #undef KIT_SNIPER #undef KIT_NUKEOPS_METAGAME #undef KIT_LORD_SINGULOTH +#undef KIT_REVOLUTIONARY #undef KIT_JAMES_BOND #undef KIT_NINJA @@ -816,3 +863,5 @@ #undef KIT_BEES #undef KIT_MR_FREEZE #undef KIT_TRAITOR_2006 +#undef KIT_SAM_FISHER +#undef KIT_PROP_HUNT diff --git a/code/game/objects/items/tanks/tanks.dm b/code/game/objects/items/tanks/tanks.dm index a670a966805e7..e198f7d75d74d 100644 --- a/code/game/objects/items/tanks/tanks.dm +++ b/code/game/objects/items/tanks/tanks.dm @@ -440,7 +440,7 @@ if(LAZYLEN(assembly.assemblies) == igniter_count) return - + if(isitem(loc)) // we are in a storage item balloon_alert(user, "can't reach!") return @@ -553,7 +553,7 @@ var/turf/T = get_turf(src) if(!T) return - log_atmos("[type] released its contents of ", air_contents) + log_atmos("[type] released its contents of ", removed) T.assume_air(removed) #undef ASSEMBLY_BOMB_BASE diff --git a/code/game/objects/items/teleportation.dm b/code/game/objects/items/teleportation.dm index 6968d4441247f..38a8195319357 100644 --- a/code/game/objects/items/teleportation.dm +++ b/code/game/objects/items/teleportation.dm @@ -366,6 +366,7 @@ playsound(src, 'sound/machines/twobeep.ogg', 10, TRUE, extrarange = SILENCED_SOUND_EXTRARANGE, falloff_distance = 0) /obj/item/syndicate_teleporter/emp_act(severity) + . = ..() if(!prob(50/severity)) return var/teleported_something = FALSE diff --git a/code/game/objects/items/tools/weldingtool.dm b/code/game/objects/items/tools/weldingtool.dm index 6a75e99a36a52..40f9f47f424ec 100644 --- a/code/game/objects/items/tools/weldingtool.dm +++ b/code/game/objects/items/tools/weldingtool.dm @@ -19,7 +19,7 @@ pickup_sound = 'sound/items/handling/weldingtool_pickup.ogg' light_system = OVERLAY_LIGHT light_range = 2 - light_power = 0.75 + light_power = 1.5 light_color = LIGHT_COLOR_FIRE light_on = FALSE throw_speed = 3 diff --git a/code/game/objects/items/toy_mechs.dm b/code/game/objects/items/toy_mechs.dm index ce3ce4600c734..c50908738fe94 100644 --- a/code/game/objects/items/toy_mechs.dm +++ b/code/game/objects/items/toy_mechs.dm @@ -53,6 +53,7 @@ /obj/item/toy/mecha/Initialize(mapload) . = ..() AddElement(/datum/element/series, /obj/item/toy/mecha, "Mini-Mecha action figures") + AddElement(/datum/element/toy_talk) combat_health = max_combat_health switch(special_attack_type) if(SPECIAL_ATTACK_DAMAGE) @@ -263,12 +264,8 @@ if(wins || losses) . += span_notice("This toy has [wins] wins, and [losses] losses.") -/** - * Override the say proc if they're mute - */ -/obj/item/toy/mecha/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null, message_range = 7, datum/saymode/saymode = null) - if(!quiet) - . = ..() +/obj/item/toy/mecha/can_speak(allow_mimes) + return !quiet && ..() /** * The 'master' proc of the mech battle. Processes the entire battle's events and makes sure it start and finishes correctly. diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index c91f6eb6c4401..1d2b0bba7b134 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -1013,6 +1013,7 @@ /obj/item/toy/figure/Initialize(mapload) . = ..() desc = "A \"Space Life\" brand [src]." + AddElement(/datum/element/toy_talk) /obj/item/toy/figure/attack_self(mob/user as mob) if(cooldown <= world.time) @@ -1245,13 +1246,9 @@ to_chat(user, span_notice("You name the dummy as \"[doll_name]\".")) name = "[initial(name)] - [doll_name]" -/obj/item/toy/dummy/talk_into(atom/movable/A, message, channel, list/spans, datum/language/language, list/message_mods) - var/mob/M = A - if (istype(M)) - M.log_talk(message, LOG_SAY, tag="dummy toy") - - say(message, language, sanitize = FALSE) - return NOPASS +/obj/item/toy/dummy/Initialize(mapload) + . = ..() + AddElement(/datum/element/toy_talk) /obj/item/toy/dummy/GetVoice() return doll_name diff --git a/code/game/objects/items/vending_items.dm b/code/game/objects/items/vending_items.dm index 0383767ce66e8..7084b313dff59 100644 --- a/code/game/objects/items/vending_items.dm +++ b/code/game/objects/items/vending_items.dm @@ -19,8 +19,10 @@ w_class = WEIGHT_CLASS_BULKY armor_type = /datum/armor/item_vending_refill - // Built automatically from the corresponding vending machine. - // If null, considered to be full. Otherwise, is list(/typepath = amount). + /** + * Built automatically from the corresponding vending machine. + * If null, considered to be full. Otherwise, is list(/typepath = amount). + */ var/list/products var/list/product_categories var/list/contraband diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm index 059f78b80c90d..f72199217653f 100644 --- a/code/game/objects/items/weaponry.dm +++ b/code/game/objects/items/weaponry.dm @@ -554,124 +554,6 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 /obj/item/ectoplasm/mystic icon_state = "mysticplasm" -/obj/item/statuebust - name = "bust" - desc = "A priceless ancient marble bust, the kind that belongs in a museum." //or you can hit people with it - icon = 'icons/obj/art/statue.dmi' - icon_state = "bust" - force = 15 - throwforce = 10 - throw_speed = 5 - throw_range = 2 - attack_verb_continuous = list("busts") - attack_verb_simple = list("bust") - var/impressiveness = 45 - -/obj/item/statuebust/Initialize(mapload) - . = ..() - AddElement(/datum/element/art, impressiveness) - AddElement(/datum/element/beauty, 1000) - -/obj/item/statuebust/hippocratic - name = "hippocrates bust" - desc = "A bust of the famous Greek physician Hippocrates of Kos, often referred to as the father of western medicine." - icon_state = "hippocratic" - impressiveness = 50 - // If it hits the prob(reference_chance) chance, this is set to TRUE. Adds medical HUD when wielded, but has a 10% slower attack speed and is too bloody to make an oath with. - var/reference = FALSE - // Chance for above. - var/reference_chance = 1 - // Minimum time inbetween oaths. - COOLDOWN_DECLARE(oath_cd) - -/obj/item/statuebust/hippocratic/evil - reference_chance = 100 - -/obj/item/statuebust/hippocratic/Initialize(mapload) - . = ..() - if(prob(reference_chance)) - name = "Solemn Vow" - desc = "Art lovers will cherish the bust of Hippocrates, commemorating a time when medics still thought doing no harm was a good idea." - attack_speed = CLICK_CD_SLOW - reference = TRUE - -/obj/item/statuebust/hippocratic/examine(mob/user) - . = ..() - if(reference) - . += span_notice("You could activate the bust in-hand to swear or forswear a Hippocratic Oath... but it seems like somebody decided it was more of a Hippocratic Suggestion. This thing is caked with bits of blood and gore.") - return - . += span_notice("You can activate the bust in-hand to swear or forswear a Hippocratic Oath! This has no effects except pacifism or bragging rights. Does not remove other sources of pacifism. Do not eat.") - -/obj/item/statuebust/hippocratic/equipped(mob/living/carbon/human/user, slot) - ..() - if(!(slot & ITEM_SLOT_HANDS)) - return - var/datum/atom_hud/our_hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - our_hud.show_to(user) - ADD_TRAIT(user, TRAIT_MEDICAL_HUD, type) - -/obj/item/statuebust/hippocratic/dropped(mob/living/carbon/human/user) - ..() - if(HAS_TRAIT_NOT_FROM(user, TRAIT_MEDICAL_HUD, type)) - return - var/datum/atom_hud/our_hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - our_hud.hide_from(user) - REMOVE_TRAIT(user, TRAIT_MEDICAL_HUD, type) - -/obj/item/statuebust/hippocratic/attack_self(mob/user) - if(!iscarbon(user)) - to_chat(user, span_warning("You remember how the Hippocratic Oath specifies 'my fellow human beings' and realize that it's completely meaningless to you.")) - return - - if(reference) - to_chat(user, span_warning("As you prepare yourself to swear the Oath, you realize that doing so on a blood-caked bust is probably not a good idea.")) - return - - if(!COOLDOWN_FINISHED(src, oath_cd)) - to_chat(user, span_warning("You've sworn or forsworn an oath too recently to undo your decisions. The bust looks at you with disgust.")) - return - - COOLDOWN_START(src, oath_cd, 5 MINUTES) - - if(HAS_TRAIT_FROM(user, TRAIT_PACIFISM, type)) - to_chat(user, span_warning("You've already sworn a vow. You start preparing to rescind it...")) - if(do_after(user, 5 SECONDS, target = user)) - user.say("Yeah this Hippopotamus thing isn't working out. I quit!", forced = "hippocratic hippocrisy") - REMOVE_TRAIT(user, TRAIT_PACIFISM, type) - - // they can still do it for rp purposes - if(HAS_TRAIT_NOT_FROM(user, TRAIT_PACIFISM, type)) - to_chat(user, span_warning("You already don't want to harm people, this isn't going to do anything!")) - - - to_chat(user, span_notice("You remind yourself of the Hippocratic Oath's contents and prepare to swear yourself to it...")) - if(do_after(user, 4 SECONDS, target = user)) - user.say("I swear to fulfill, to the best of my ability and judgment, this covenant:", forced = "hippocratic oath") - else - return fuck_it_up(user) - if(do_after(user, 2 SECONDS, target = user)) - user.say("I will apply, for the benefit of the sick, all measures that are required, avoiding those twin traps of overtreatment and therapeutic nihilism.", forced = "hippocratic oath") - else - return fuck_it_up(user) - if(do_after(user, 3 SECONDS, target = user)) - user.say("I will remember that I remain a member of society, with special obligations to all my fellow human beings, those sound of mind and body as well as the infirm.", forced = "hippocratic oath") - else - - return fuck_it_up(user) - if(do_after(user, 3 SECONDS, target = user)) - user.say("If I do not violate this oath, may I enjoy life and art, respected while I live and remembered with affection thereafter. May I always act so as to preserve the finest traditions of my calling and may I long experience the joy of healing those who seek my help.", forced = "hippocratic oath") - else - return fuck_it_up(user) - - to_chat(user, span_notice("Contentment, understanding, and purpose washes over you as you finish the oath. You consider for a second the concept of harm and shudder.")) - ADD_TRAIT(user, TRAIT_PACIFISM, type) - -// Bully the guy for fucking up. -/obj/item/statuebust/hippocratic/proc/fuck_it_up(mob/living/carbon/user) - to_chat(user, span_warning("You forget what comes next like a dumbass. The Hippocrates bust looks down on you, disappointed.")) - user.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2) - COOLDOWN_RESET(src, oath_cd) - /obj/item/tailclub name = "tail club" desc = "For the beating to death of lizards with their own tails." diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index cb57c5049bb8c..dcc3b03dc7880 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -28,14 +28,6 @@ var/current_skin //Has the item been reskinned? var/list/unique_reskin //List of options to reskin. - // Access levels, used in modules\jobs\access.dm - /// List of accesses needed to use this object: The user must possess all accesses in this list in order to use the object. - /// Example: If req_access = list(ACCESS_ENGINE, ACCESS_CE)- then the user must have both ACCESS_ENGINE and ACCESS_CE in order to use the object. - var/list/req_access - /// List of accesses needed to use this object: The user must possess at least one access in this list in order to use the object. - /// Example: If req_one_access = list(ACCESS_ENGINE, ACCESS_CE)- then the user must have either ACCESS_ENGINE or ACCESS_CE in order to use the object. - var/list/req_one_access - /// Custom fire overlay icon, will just use the default overlay if this is null var/custom_fire_overlay /// Particles this obj uses when burning, if any diff --git a/code/game/objects/structures/ai_core.dm b/code/game/objects/structures/ai_core.dm index eef6059d98403..7615a37a88b36 100644 --- a/code/game/objects/structures/ai_core.dm +++ b/code/game/objects/structures/ai_core.dm @@ -441,4 +441,9 @@ That prevents a few funky behaviors. name = "AI core (AI Core Board)" //Well, duh, but best to be consistent var/battery = 200 //backup battery for when the AI loses power. Copied to/from AI mobs when carding, and placed here to avoid recharge via deconning the core +/obj/item/circuitboard/aicore/Initialize(mapload) + . = ..() + if(mapload && HAS_TRAIT(SSstation, STATION_TRAIT_HUMAN_AI)) + return INITIALIZE_HINT_QDEL + #undef AI_CORE_BRAIN diff --git a/code/game/objects/structures/bedsheet_bin.dm b/code/game/objects/structures/bedsheet_bin.dm index f80042f5679a7..50fcae8dec138 100644 --- a/code/game/objects/structures/bedsheet_bin.dm +++ b/code/game/objects/structures/bedsheet_bin.dm @@ -35,7 +35,7 @@ LINEN BINS /obj/item/bedsheet/Initialize(mapload) . = ..() AddComponent(/datum/component/surgery_initiator) - AddElement(/datum/element/bed_tuckable, 0, 0, 0) + AddElement(/datum/element/bed_tuckable, mapload, 0, 0, 0) if(bedsheet_type == BEDSHEET_DOUBLE) stack_amount *= 2 dying_key = DYE_REGISTRY_DOUBLE_BEDSHEET diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index b3d3c7085bd50..d2df088e06f73 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -94,6 +94,11 @@ GLOBAL_LIST_EMPTY(roundstart_station_closets) /// Volume of the internal air var/air_volume = TANK_STANDARD_VOLUME * 3 + /// How many pixels the closet can shift on the x axis when shaking + var/x_shake_pixel_shift = 2 + /// how many pixels the closet can shift on the y axes when shaking + var/y_shake_pixel_shift = 1 + /datum/armor/structure_closet melee = 20 bullet = 10 @@ -1031,6 +1036,9 @@ GLOBAL_LIST_EMPTY(roundstart_station_closets) user.visible_message(span_warning("[src] begins to shake violently!"), \ span_notice("You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)"), \ span_hear("You hear banging from [src].")) + + addtimer(CALLBACK(src, PROC_REF(check_if_shake)), 1 SECONDS) + if(do_after(user,(breakout_time), target = src)) if(!user || user.stat != CONSCIOUS || user.loc != src || opened || (!locked && !welded) ) return @@ -1045,6 +1053,23 @@ GLOBAL_LIST_EMPTY(roundstart_station_closets) /obj/structure/closet/relay_container_resist_act(mob/living/user, obj/container) container.container_resist_act() +/// Check if someone is still resisting inside, and choose to either keep shaking or stop shaking the closet +/obj/structure/closet/proc/check_if_shake() + // Assuming we decide to shake again, how long until we check to shake again + var/next_check_time = 1 SECONDS + + // How long we shake between different calls of Shake(), so that it starts shaking and stops, instead of a steady shake + var/shake_duration = 0.3 SECONDS + + for(var/mob/living/mob in contents) + if(DOING_INTERACTION_WITH_TARGET(mob, src)) + // Shake and queue another check_if_shake + Shake(x_shake_pixel_shift, y_shake_pixel_shift, shake_duration, shake_interval = 0.1 SECONDS) + addtimer(CALLBACK(src, PROC_REF(check_if_shake)), next_check_time) + return TRUE + + // If we reach here, nobody is resisting, so dont shake + return FALSE /obj/structure/closet/proc/bust_open() SIGNAL_HANDLER diff --git a/code/game/objects/structures/crates_lockers/closets/gimmick.dm b/code/game/objects/structures/crates_lockers/closets/gimmick.dm index 1e7fede584208..fecacd678c7c2 100644 --- a/code/game/objects/structures/crates_lockers/closets/gimmick.dm +++ b/code/game/objects/structures/crates_lockers/closets/gimmick.dm @@ -39,7 +39,6 @@ /obj/structure/closet/gimmick/tacticool/PopulateContents() ..() new /obj/item/clothing/glasses/eyepatch(src) - new /obj/item/clothing/glasses/sunglasses(src) new /obj/item/clothing/gloves/tackler/combat(src) new /obj/item/clothing/gloves/tackler/combat(src) new /obj/item/clothing/head/helmet/swat(src) @@ -53,6 +52,8 @@ new /obj/item/clothing/under/syndicate/tacticool(src) new /obj/item/clothing/under/syndicate/tacticool(src) +/obj/structure/closet/gimmick/tacticool/populate_contents_immediate() + new /obj/item/clothing/glasses/sunglasses(src) /obj/structure/closet/thunderdome name = "\improper Thunderdome closet" @@ -69,8 +70,6 @@ new /obj/item/clothing/suit/armor/tdome/red(src) for(var/i in 1 to 3) new /obj/item/melee/energy/sword/saber(src) - for(var/i in 1 to 3) - new /obj/item/gun/energy/laser(src) for(var/i in 1 to 3) new /obj/item/melee/baton/security/loaded(src) for(var/i in 1 to 3) @@ -78,6 +77,10 @@ for(var/i in 1 to 3) new /obj/item/clothing/head/helmet/thunderdome(src) +/obj/structure/closet/thunderdome/tdred/populate_contents_immediate() + for(var/i in 1 to 3) + new /obj/item/gun/energy/laser(src) + /obj/structure/closet/thunderdome/tdgreen name = "green-team Thunderdome closet" icon_door = "green" @@ -88,8 +91,6 @@ new /obj/item/clothing/suit/armor/tdome/green(src) for(var/i in 1 to 3) new /obj/item/melee/energy/sword/saber(src) - for(var/i in 1 to 3) - new /obj/item/gun/energy/laser(src) for(var/i in 1 to 3) new /obj/item/melee/baton/security/loaded(src) for(var/i in 1 to 3) @@ -97,6 +98,10 @@ for(var/i in 1 to 3) new /obj/item/clothing/head/helmet/thunderdome(src) +/obj/structure/closet/thunderdome/tdgreen/populate_contents_immediate() + for(var/i in 1 to 3) + new /obj/item/gun/energy/laser(src) + /obj/structure/closet/malf/suits desc = "It's a storage unit for operational gear." icon_state = "syndicate" diff --git a/code/game/objects/structures/crates_lockers/closets/secure/security.dm b/code/game/objects/structures/crates_lockers/closets/secure/security.dm index d09c12fb5d49c..553258bd360ea 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm @@ -16,11 +16,13 @@ new /obj/item/computer_disk/command/captain(src) new /obj/item/radio/headset/heads/captain/alt(src) new /obj/item/radio/headset/heads/captain(src) - new /obj/item/storage/belt/sabre(src) - new /obj/item/gun/energy/e_gun(src) new /obj/item/door_remote/captain(src) new /obj/item/storage/photo_album/captain(src) +/obj/structure/closet/secure_closet/captains/populate_contents_immediate() + new /obj/item/gun/energy/e_gun(src) + new /obj/item/storage/belt/sabre(src) + /obj/structure/closet/secure_closet/hop name = "head of personnel's locker" icon_state = "hop" @@ -37,7 +39,6 @@ new /obj/item/storage/box/silver_ids(src) new /obj/item/megaphone/command(src) new /obj/item/assembly/flash/handheld(src) - new /obj/item/gun/energy/e_gun(src) new /obj/item/clothing/neck/petcollar(src) new /obj/item/pet_carrier(src) new /obj/item/door_remote/civilian(src) @@ -45,6 +46,9 @@ new /obj/item/storage/photo_album/hop(src) new /obj/item/storage/lockbox/medal/hop(src) +/obj/structure/closet/secure_closet/hop/populate_contents_immediate() + new /obj/item/gun/energy/e_gun(src) + /obj/structure/closet/secure_closet/hos name = "head of security's locker" icon_state = "hos" @@ -286,6 +290,8 @@ new /obj/item/storage/box/firingpins(src) for(var/i in 1 to 3) new /obj/item/storage/box/rubbershot(src) + +/obj/structure/closet/secure_closet/armory2/populate_contents_immediate() for(var/i in 1 to 3) new /obj/item/gun/ballistic/shotgun/riot(src) @@ -299,12 +305,14 @@ ..() new /obj/item/storage/box/firingpins(src) new /obj/item/gun/energy/ionrifle(src) + for(var/i in 1 to 3) + new /obj/item/gun/energy/laser/thermal(src) + +/obj/structure/closet/secure_closet/armory3/populate_contents_immediate() for(var/i in 1 to 3) new /obj/item/gun/energy/e_gun(src) for(var/i in 1 to 3) new /obj/item/gun/energy/laser(src) - for(var/i in 1 to 3) - new /obj/item/gun/energy/laser/thermal(src) /obj/structure/closet/secure_closet/tac name = "armory tac locker" diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm index c43a83b085aef..c4f84e9ca3d24 100644 --- a/code/game/objects/structures/crates_lockers/crates.dm +++ b/code/game/objects/structures/crates_lockers/crates.dm @@ -18,6 +18,8 @@ drag_slowdown = 0 door_anim_time = 0 // no animation pass_flags_self = PASSSTRUCTURE | LETPASSTHROW + x_shake_pixel_shift = 1 + y_shake_pixel_shift = 2 /// Mobs standing on it are nudged up by this amount. var/elevation = 14 /// The same, but when the crate is open diff --git a/code/game/objects/structures/door_assembly.dm b/code/game/objects/structures/door_assembly.dm index de996015e592c..f110cd8b43eab 100644 --- a/code/game/objects/structures/door_assembly.dm +++ b/code/game/objects/structures/door_assembly.dm @@ -325,6 +325,7 @@ door.update_appearance() qdel(src) + return door /obj/structure/door_assembly/update_overlays() . = ..() diff --git a/code/game/objects/structures/door_assembly_types.dm b/code/game/objects/structures/door_assembly_types.dm index 36b41fbc32632..589cad42bcabc 100644 --- a/code/game/objects/structures/door_assembly_types.dm +++ b/code/game/objects/structures/door_assembly_types.dm @@ -293,4 +293,5 @@ /obj/structure/door_assembly/door_assembly_material/finish_door() var/obj/machinery/door/airlock/door = ..() door.set_custom_materials(custom_materials) + door.update_appearance() return door diff --git a/code/game/objects/structures/girders.dm b/code/game/objects/structures/girders.dm index f35b41c53d2a3..3bb24147f5c49 100644 --- a/code/game/objects/structures/girders.dm +++ b/code/game/objects/structures/girders.dm @@ -410,11 +410,10 @@ max_integrity = 350 /obj/structure/girder/tram - name = "tram frame" + name = "tram girder" desc = "Titanium framework to construct tram walls. Can be plated with titanium glass or other wall materials." icon_state = "tram" state = GIRDER_TRAM - density = FALSE obj_flags = CAN_BE_HIT | BLOCK_Z_OUT_DOWN /obj/structure/girder/tram/corner diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm index fba9d5cecc454..da02394251810 100644 --- a/code/game/objects/structures/grille.dm +++ b/code/game/objects/structures/grille.dm @@ -339,19 +339,19 @@ var/turf/T = get_turf(src) if(T.overfloor_placed)//cant be a floor in the way! return FALSE - // Shocking hurts the grille (to weaken monkey powersinks) - if(prob(50)) + + var/obj/structure/cable/cable_node = T.get_cable_node() + if(isnull(cable_node)) + return FALSE + if(!electrocute_mob(user, cable_node, src, 1, TRUE)) + return FALSE + if(prob(50)) // Shocking hurts the grille (to weaken monkey powersinks) take_damage(1, BURN, FIRE, sound_effect = FALSE) - var/obj/structure/cable/C = T.get_cable_node() - if(C) - if(electrocute_mob(user, C, src, 1, TRUE)) - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(3, 1, src) - s.start() - return TRUE - else - return FALSE - return FALSE + var/datum/effect_system/spark_spread/sparks = new /datum/effect_system/spark_spread + sparks.set_up(3, 1, src) + sparks.start() + + return TRUE /obj/structure/grille/should_atmos_process(datum/gas_mixture/air, exposed_temperature) return exposed_temperature > T0C + 1500 && !broken diff --git a/code/game/objects/structures/holosign.dm b/code/game/objects/structures/holosign.dm index a3d09340d87e3..a1a3adf325145 100644 --- a/code/game/objects/structures/holosign.dm +++ b/code/game/objects/structures/holosign.dm @@ -45,6 +45,7 @@ user.do_attack_animation(src, ATTACK_EFFECT_PUNCH) user.changeNext_move(CLICK_CD_MELEE) take_damage(5 , BRUTE, MELEE, 1) + log_combat(user, src, "swatted") /obj/structure/holosign/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) switch(damage_type) diff --git a/code/game/objects/structures/lavaland/ore_vent.dm b/code/game/objects/structures/lavaland/ore_vent.dm index 08a4394346af9..284f5df2a6d61 100644 --- a/code/game/objects/structures/lavaland/ore_vent.dm +++ b/code/game/objects/structures/lavaland/ore_vent.dm @@ -437,8 +437,8 @@ defending_mobs = list( /mob/living/basic/mining/lobstrosity, /mob/living/basic/mining/legion/snow/spawner_made, + /mob/living/basic/mining/wolf, /mob/living/simple_animal/hostile/asteroid/polarbear, - /mob/living/simple_animal/hostile/asteroid/wolf, ) ore_vent_options = list( SMALL_VENT_TYPE, @@ -450,8 +450,8 @@ /mob/living/basic/mining/lobstrosity, /mob/living/basic/mining/legion/snow/spawner_made, /mob/living/basic/mining/ice_demon, + /mob/living/basic/mining/wolf, /mob/living/simple_animal/hostile/asteroid/polarbear, - /mob/living/simple_animal/hostile/asteroid/wolf, ) ore_vent_options = list( SMALL_VENT_TYPE = 3, diff --git a/code/game/objects/structures/tank_holder.dm b/code/game/objects/structures/tank_holder.dm index 9b5b33d8417eb..5b7c9d2ed5534 100644 --- a/code/game/objects/structures/tank_holder.dm +++ b/code/game/objects/structures/tank_holder.dm @@ -142,3 +142,7 @@ /obj/structure/tank_holder/extinguisher/advanced icon_state = "holder_foam_extinguisher" tank = /obj/item/extinguisher/advanced + +/obj/structure/tank_holder/extinguisher/anti + icon_state = "holder_extinguisher" + tank = /obj/item/extinguisher/anti diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm index b343cd85f50b1..edf7f2fc803c1 100644 --- a/code/game/objects/structures/watercloset.dm +++ b/code/game/objects/structures/watercloset.dm @@ -760,11 +760,9 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sink/kitchen, (-16)) open = !open if(open) layer = SIGN_LAYER - set_density(FALSE) set_opacity(FALSE) else layer = WALL_OBJ_LAYER - set_density(TRUE) if(opaque_closed) set_opacity(TRUE) diff --git a/code/game/say.dm b/code/game/say.dm index e6ea129d5a08b..42aea57cf6110 100644 --- a/code/game/say.dm +++ b/code/game/say.dm @@ -21,7 +21,36 @@ GLOBAL_LIST_INIT(freqtospan, list( "[FREQ_CTF_YELLOW]" = "yellowteamradio" )) -/atom/movable/proc/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = FALSE, message_range = 7, datum/saymode/saymode = null) +/** + * What makes things... talk. + * + * * message - The message to say. + * * bubble_type - The type of speech bubble to use when talking + * * spans - A list of spans to attach to the message. Includes the atom's speech span by default + * * sanitize - Should we sanitize the message? Only set to FALSE if you have ALREADY sanitized it + * * language - The language to speak in. Defaults to the atom's selected language + * * ignore_spam - Should we ignore spam checks? + * * forced - What was it forced by? null if voluntary. (NOT a boolean!) + * * filterproof - Do we bypass the filter when checking the message? + * * message_range - The range of the message. Defaults to 7 + * * saymode - Saymode passed to the speech + * This is usually set automatically and is only relevant for living mobs. + * * message_mods - A list of message modifiers, i.e. whispering/singing. + * Most of these are set automatically but you can pass in your own pre-say. + */ +/atom/movable/proc/say( + message, + bubble_type, + list/spans = list(), + sanitize = TRUE, + datum/language/language, + ignore_spam = FALSE, + forced, + filterproof = FALSE, + message_range = 7, + datum/saymode/saymode, + list/message_mods = list(), +) if(!try_speak(message, ignore_spam, forced, filterproof)) return if(sanitize) @@ -31,7 +60,6 @@ GLOBAL_LIST_INIT(freqtospan, list( spans |= speech_span if(!language) language = get_selected_language() - var/list/message_mods = list() message_mods[SAY_MOD_VERB] = say_mod(message, message_mods) send_speech(message, message_range, src, bubble_type, spans, language, message_mods, forced = forced) @@ -60,7 +88,7 @@ GLOBAL_LIST_INIT(freqtospan, list( * TRUE of FASE depending on if our movable can speak */ /atom/movable/proc/try_speak(message, ignore_spam = FALSE, forced = null, filterproof = FALSE) - return TRUE + return can_speak() /** * Checks if our movable can currently speak, vocally, in general. @@ -77,7 +105,8 @@ GLOBAL_LIST_INIT(freqtospan, list( * if TRUE, we will check if the movable can speak REGARDLESS of if they have an active mime vow. */ /atom/movable/proc/can_speak(allow_mimes = FALSE) - return TRUE + SHOULD_BE_PURE(TRUE) + return !HAS_TRAIT(src, TRAIT_MUTE) /atom/movable/proc/send_speech(message, range = 7, obj/source = src, bubble_type, list/spans, datum/language/message_language, list/message_mods = list(), forced = FALSE, tts_message, list/tts_filter) var/found_client = FALSE @@ -174,7 +203,15 @@ GLOBAL_LIST_INIT(freqtospan, list( /atom/movable/proc/get_default_say_verb() return verb_say -/atom/movable/proc/say_quote(input, list/spans=list(speech_span), list/message_mods = list()) +/** + * This prock is used to generate a message for chat + * Generates the `says, "meme"` part of the `Grey Tider says, "meme"`. + * + * input - The message to be said + * spans - A list of spans to attach to the message. Includes the atom's speech span by default + * message_mods - A list of message modifiers, i.e. whispering/singing + */ +/atom/movable/proc/say_quote(input, list/spans = list(speech_span), list/message_mods = list()) if(!input) input = "..." diff --git a/code/game/sound.dm b/code/game/sound.dm index e575534bdaeed..17275f5f3a63e 100644 --- a/code/game/sound.dm +++ b/code/game/sound.dm @@ -424,4 +424,10 @@ 'sound/items/reel4.ogg', 'sound/items/reel5.ogg', ) + if(SFX_RATTLE) + soundin = pick( + 'sound/items/rattle1.ogg', + 'sound/items/rattle2.ogg', + 'sound/items/rattle3.ogg', + ) return soundin diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 8a278c1cc9131..7864b3d732900 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -274,6 +274,10 @@ GLOBAL_PROTECT(admin_verbs_poll) add_verb(src, /client/proc/play_web_sound) if(rights & R_SPAWN) add_verb(src, GLOB.admin_verbs_spawn) +#ifdef MAP_TEST + remove_verb(src, /client/proc/enable_mapping_verbs) + add_verb(src, list(/client/proc/disable_mapping_verbs, GLOB.admin_verbs_debug_mapping)) +#endif /client/proc/remove_admin_verbs() remove_verb(src, list( @@ -1191,4 +1195,3 @@ GLOBAL_PROTECT(admin_verbs_poll) QDEL_NULL(segment.ai_controller) segment.AddComponent(/datum/component/mob_chain, front = previous) previous = segment - diff --git a/code/modules/admin/antag_panel.dm b/code/modules/admin/antag_panel.dm index a970c7a5335ef..877bebffe9509 100644 --- a/code/modules/admin/antag_panel.dm +++ b/code/modules/admin/antag_panel.dm @@ -99,6 +99,7 @@ GLOBAL_VAR(antag_prototypes) out += "Mind currently owned by key: [key] [active?"(synced)":"(not synced)"]
" out += "Assigned role: [assigned_role.title]. Edit
" out += "Faction and special role: [special_role]
" + out += "Show Teams

" var/special_statuses = get_special_statuses() if(length(special_statuses)) diff --git a/code/modules/admin/fun_balloon.dm b/code/modules/admin/fun_balloon.dm index 4d74f5425608f..b65f72f8f4d79 100644 --- a/code/modules/admin/fun_balloon.dm +++ b/code/modules/admin/fun_balloon.dm @@ -87,14 +87,14 @@ if (!possessable.ckey && possessable.stat == CONSCIOUS) // Only assign ghosts to living, non-occupied mobs! bodies += possessable - var/list/candidates = SSpolling.poll_ghost_candidates_for_mobs( - question = "Would you like to be [group_name]?", + var/list/candidates = SSpolling.poll_ghosts_for_targets( + question = "Would you like to be [span_notice(group_name)]?", role = ROLE_SENTIENCE, check_jobban = ROLE_SENTIENCE, poll_time = 10 SECONDS, - mobs = bodies, + checked_targets = bodies, ignore_category = POLL_IGNORE_SHUTTLE_DENIZENS, - pic_source = src, + alert_pic = src, role_name_text = "sentience fun balloon", ) diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm index e98016df74f3d..9d2525ed8fa2d 100644 --- a/code/modules/admin/holder2.dm +++ b/code/modules/admin/holder2.dm @@ -232,7 +232,7 @@ GLOBAL_PROTECT(href_token) return VALID_2FA_CONNECTION if (!SSdbcore.Connect()) - if (verify_backup_data(client)) + if (verify_backup_data(client) || (client.ckey in GLOB.protected_admins)) return VALID_2FA_CONNECTION else return list(FALSE, null) diff --git a/code/modules/admin/smites/imaginary_friend_special.dm b/code/modules/admin/smites/imaginary_friend_special.dm index 5b2bc6ba80547..e670e26fd1fa4 100644 --- a/code/modules/admin/smites/imaginary_friend_special.dm +++ b/code/modules/admin/smites/imaginary_friend_special.dm @@ -58,7 +58,6 @@ return FALSE var/list/volunteers = SSpolling.poll_ghost_candidates( - question = "Do you want to play as an imaginary friend?", check_jobban = ROLE_PAI, poll_time = 10 SECONDS, ignore_category = POLL_IGNORE_IMAGINARYFRIEND, diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index 126d8756762ca..35b1baa063d57 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -680,8 +680,7 @@ names[name] = ruin_landmark - var/ruinname = input("Select ruin", "Jump to Ruin") as null|anything in sort_list(names) - + var/ruinname = tgui_input_list(usr, "Select ruin", "Jump to Ruin", sort_list(names)) var/obj/effect/landmark/ruin/landmark = names[ruinname] @@ -715,7 +714,7 @@ themed_names[name] = list(ruin, theme, list(ruin.default_area)) names += sort_list(themed_names) - var/ruinname = input("Select ruin", "Spawn Ruin") as null|anything in names + var/ruinname = tgui_input_list(usr, "Select ruin", "Spawn Ruin", sort_list(names)) var/data = names[ruinname] if (!data) return diff --git a/code/modules/admin/verbs/ert.dm b/code/modules/admin/verbs/ert.dm index 8b4c7e3c1b823..6fc238931a65e 100644 --- a/code/modules/admin/verbs/ert.dm +++ b/code/modules/admin/verbs/ert.dm @@ -121,7 +121,7 @@ var/list/spawnpoints = GLOB.emergencyresponseteamspawn var/index = 0 - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for [ertemplate.polldesc]?", check_jobban = "deathsquad", pic_source = /obj/item/card/id/advanced/centcom/ert, role_name_text = "emergency response team") + var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for [span_notice(ertemplate.polldesc)]?", check_jobban = "deathsquad", alert_pic = /obj/item/card/id/advanced/centcom/ert, role_name_text = "emergency response team") var/teamSpawned = FALSE // This list will take priority over spawnpoints if not empty diff --git a/code/modules/admin/verbs/map_template_loadverb.dm b/code/modules/admin/verbs/map_template_loadverb.dm index a772f69999271..827bbfb16e862 100644 --- a/code/modules/admin/verbs/map_template_loadverb.dm +++ b/code/modules/admin/verbs/map_template_loadverb.dm @@ -3,8 +3,7 @@ set name = "Map template - Place" var/datum/map_template/template - - var/map = input(src, "Choose a Map Template to place at your CURRENT LOCATION","Place Map Template") as null|anything in sort_list(SSmapping.map_templates) + var/map = tgui_input_list(usr, "Choose a Map Template to place at your CURRENT LOCATION","Place Map Template", sort_list(SSmapping.map_templates)) if(!map) return template = SSmapping.map_templates[map] diff --git a/code/modules/admin/verbs/secrets.dm b/code/modules/admin/verbs/secrets.dm index 20a05685bc9ed..fa2a9fa19c628 100644 --- a/code/modules/admin/verbs/secrets.dm +++ b/code/modules/admin/verbs/secrets.dm @@ -405,7 +405,7 @@ GLOBAL_DATUM(everyone_an_antag, /datum/everyone_is_an_antag_controller) var/list/candidates = list() if (prefs["offerghosts"]["value"] == "Yes") - candidates = SSpolling.poll_ghost_candidates(replacetext(prefs["ghostpoll"]["value"], "%TYPE%", initial(pathToSpawn.name)), check_jobban = ROLE_TRAITOR, pic_source = pathToSpawn, role_name_text = "portal storm") + candidates = SSpolling.poll_ghost_candidates(replacetext(prefs["ghostpoll"]["value"], "%TYPE%", initial(pathToSpawn.name)), check_jobban = ROLE_TRAITOR, alert_pic = pathToSpawn, role_name_text = "portal storm") if (prefs["playersonly"]["value"] == "Yes" && length(candidates) < prefs["minplayers"]["value"]) message_admins("Not enough players signed up to create a portal storm, the minimum was [prefs["minplayers"]["value"]] and the number of signups [length(candidates)]") @@ -576,7 +576,7 @@ GLOBAL_DATUM(everyone_an_antag, /datum/everyone_is_an_antag_controller) if(teamsize <= 0) return FALSE - candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for a Nanotrasen emergency response drone?", check_jobban = ROLE_DRONE, pic_source = /mob/living/basic/drone/classic, role_name_text = "nanotrasen emergency response drone") + candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for a [span_notice("Nanotrasen emergency response drone")]?", check_jobban = ROLE_DRONE, alert_pic = /mob/living/basic/drone/classic, role_name_text = "nanotrasen emergency response drone") if(length(candidates) == 0) return FALSE diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm index c679fd45dfa16..89ed48585ed36 100644 --- a/code/modules/antagonists/_common/antag_datum.dm +++ b/code/modules/antagonists/_common/antag_datum.dm @@ -293,13 +293,12 @@ GLOBAL_LIST_EMPTY(antagonists) /datum/antagonist/proc/replace_banned_player() set waitfor = FALSE - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as a [name]?", check_jobban = "[name]", role = job_rank, poll_time = 5 SECONDS, target_mob = owner.current, pic_source = owner.current, role_name_text = name) - if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) + var/mob/chosen_one = SSpolling.poll_ghosts_for_target(check_jobban = job_rank, role = job_rank, poll_time = 5 SECONDS, checked_target = owner.current, alert_pic = owner.current, role_name_text = name) + if(chosen_one) to_chat(owner, "Your mob has been taken over by a ghost! Appeal your job ban if you want to avoid this in the future!") - message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(owner)]) to replace a jobbanned player.") + message_admins("[key_name_admin(chosen_one)] has taken control of ([key_name_admin(owner)]) to replace a jobbanned player.") owner.current.ghostize(FALSE) - owner.current.key = C.key + owner.current.key = chosen_one.key /** * Called by the remove_antag_datum() and remove_all_antag_datums() mind procs for the antag datum to handle its own removal and deletion. diff --git a/code/modules/antagonists/_common/antag_spawner.dm b/code/modules/antagonists/_common/antag_spawner.dm index 9ef40a9cebfd9..e497a30f705f3 100644 --- a/code/modules/antagonists/_common/antag_spawner.dm +++ b/code/modules/antagonists/_common/antag_spawner.dm @@ -55,16 +55,15 @@ /obj/item/antag_spawner/contract/proc/poll_for_student(mob/living/carbon/human/teacher, apprentice_school) balloon_alert(teacher, "contacting apprentice...") polling = TRUE - var/list/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as a wizard's [apprentice_school] apprentice?", check_jobban = ROLE_WIZARD, role = ROLE_WIZARD, poll_time = 15 SECONDS, target_mob = src, pic_source = teacher, role_name_text = "wizard apprentice") + var/mob/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to play as [span_danger("[teacher]'s")] [span_notice("[apprentice_school] apprentice")]?", check_jobban = ROLE_WIZARD, role = ROLE_WIZARD, poll_time = 15 SECONDS, checked_target = src, alert_pic = /obj/item/clothing/head/wizard/red, jump_target = src, role_name_text = "wizard apprentice", chat_text_border_icon = /obj/item/clothing/head/wizard/red) polling = FALSE - if(!LAZYLEN(candidates)) + if(isnull(chosen_one)) to_chat(teacher, span_warning("Unable to reach your apprentice! You can either attack the spellbook with the contract to refund your points, or wait and try again later.")) return if(QDELETED(src) || used) return used = TRUE - var/mob/dead/observer/student = pick(candidates) - spawn_antag(student.client, get_turf(src), apprentice_school, teacher.mind) + spawn_antag(chosen_one.client, get_turf(src), apprentice_school, teacher.mind) /obj/item/antag_spawner/contract/spawn_antag(client/C, turf/T, kind, datum/mind/user) new /obj/effect/particle_effect/fluid/smoke(T) @@ -99,15 +98,12 @@ desc = "MI13 designed one-use radio for calling immediate backup. Have no regards for safety of whom it summons - they are all inferior clones from Interdyne's genebanks anyway." icon = 'icons/obj/devices/voice.dmi' icon_state = "nukietalkie" - var/borg_to_spawn /// The name of the special role given to the recruit var/special_role_name = ROLE_NUCLEAR_OPERATIVE /// The applied outfit var/datum/outfit/syndicate/outfit = /datum/outfit/syndicate/reinforcement - /// The outfit given to plasmaman operatives - var/datum/outfit/syndicate/plasma_outfit = /datum/outfit/syndicate/reinforcement/plasmaman /// The antag datum applied - var/datum/antagonist/nukeop/antag_datum = /datum/antagonist/nukeop + var/datum/antagonist/nukeop/reinforcement/antag_datum = /datum/antagonist/nukeop/reinforcement /// Style used by the droppod var/pod_style = STYLE_SYNDICATE /// Do we use a random subtype of the outfit? @@ -134,13 +130,12 @@ return to_chat(user, span_notice("You activate [src] and wait for confirmation.")) - var/list/nuke_candidates = SSpolling.poll_ghost_candidates("Do you want to play as a syndicate [borg_to_spawn ? "[lowertext(borg_to_spawn)] cyborg":"operative"]?", check_jobban = ROLE_OPERATIVE, role = ROLE_OPERATIVE, poll_time = 15 SECONDS, ignore_category = POLL_IGNORE_SYNDICATE, pic_source = src, role_name_text = "syndicate [borg_to_spawn ? "[borg_to_spawn] cyborg":"operative"]") - if(LAZYLEN(nuke_candidates)) + var/mob/chosen_one = SSpolling.poll_ghost_candidates("Do you want to play as a reinforcement [special_role_name]?", check_jobban = ROLE_OPERATIVE, role = ROLE_OPERATIVE, poll_time = 15 SECONDS, ignore_category = POLL_IGNORE_SYNDICATE, alert_pic = src, role_name_text = special_role_name, amount_to_pick = 1) + if(chosen_one) if(QDELETED(src) || !check_usability(user)) return used = TRUE - var/mob/dead/observer/G = pick(nuke_candidates) - spawn_antag(G.client, get_turf(src), "nukeop", user.mind) + spawn_antag(chosen_one.client, get_turf(src), "nukeop", user.mind) do_sparks(4, TRUE, src) qdel(src) else @@ -158,7 +153,6 @@ nukie.forceMove(locate(1,1,1)) antag_datum = new() - antag_datum.send_to_spawnpoint = FALSE antag_datum.nukeop_outfit = use_subtypes ? pick(subtypesof(outfit)) : outfit @@ -184,18 +178,20 @@ desc = "A single-use beacon designed to quickly launch reinforcement cyborgs into the field." icon = 'icons/obj/devices/remote.dmi' icon_state = "gangtool-red" + antag_datum = /datum/antagonist/nukeop/reinforcement/cyborg + special_role_name = "Syndicate Cyborg" /obj/item/antag_spawner/nuke_ops/borg_tele/assault name = "syndicate assault cyborg beacon" - borg_to_spawn = "Assault" + special_role_name = ROLE_SYNDICATE_ASSAULTBORG /obj/item/antag_spawner/nuke_ops/borg_tele/medical name = "syndicate medical beacon" - borg_to_spawn = "Medical" + special_role_name = ROLE_SYNDICATE_MEDBORG /obj/item/antag_spawner/nuke_ops/borg_tele/saboteur name = "syndicate saboteur beacon" - borg_to_spawn = "Saboteur" + special_role_name = ROLE_SYNDICATE_SABOBORG /obj/item/antag_spawner/nuke_ops/borg_tele/spawn_antag(client/C, turf/T, kind, datum/mind/user) var/mob/living/silicon/robot/borg @@ -203,13 +199,15 @@ if(!creator_op) return var/obj/structure/closet/supplypod/pod = setup_pod() - switch(borg_to_spawn) - if("Medical") + switch(special_role_name) + if(ROLE_SYNDICATE_MEDBORG) borg = new /mob/living/silicon/robot/model/syndicate/medical() - if("Saboteur") + if(ROLE_SYNDICATE_SABOBORG) borg = new /mob/living/silicon/robot/model/syndicate/saboteur() + if(ROLE_SYNDICATE_ASSAULTBORG) + borg = new /mob/living/silicon/robot/model/syndicate() else - borg = new /mob/living/silicon/robot/model/syndicate() //Assault borg by default + stack_trace("Unknown cyborg type '[special_role_name]' could not be found by [src]!") var/brainfirstname = pick(GLOB.first_names_male) if(prob(50)) @@ -227,10 +225,8 @@ borg.key = C.key - var/datum/antagonist/nukeop/new_borg = new() - new_borg.send_to_spawnpoint = FALSE - borg.mind.add_antag_datum(new_borg,creator_op.nuke_team) - borg.mind.special_role = "Syndicate Cyborg" + borg.mind.add_antag_datum(antag_datum, creator_op ? creator_op.get_team() : null) + borg.mind.special_role = special_role_name borg.forceMove(pod) new /obj/effect/pod_landingzone(get_turf(src), pod) @@ -252,14 +248,13 @@ return if(used) return - var/list/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as a [initial(demon_type.name)]?", check_jobban = ROLE_ALIEN, role = ROLE_ALIEN, poll_time = 5 SECONDS, target_mob = src, pic_source = src, role_name_text = initial(demon_type.name)) - if(LAZYLEN(candidates)) + var/mob/chosen_one = SSpolling.poll_ghosts_for_target(check_jobban = ROLE_ALIEN, role = ROLE_ALIEN, poll_time = 5 SECONDS, checked_target = src, alert_pic = demon_type, jump_target = src, role_name_text = initial(demon_type.name)) + if(chosen_one) if(used || QDELETED(src)) return used = TRUE - var/mob/dead/observer/summoned = pick(candidates) - user.log_message("has summoned forth the [initial(demon_type.name)] (played by [key_name(summoned)]) using a [name].", LOG_GAME) // has to be here before we create antag otherwise we can't get the ckey of the demon - spawn_antag(summoned.client, get_turf(src), initial(demon_type.name), user.mind) + user.log_message("has summoned forth the [initial(demon_type.name)] (played by [key_name(chosen_one)]) using a [name].", LOG_GAME) // has to be here before we create antag otherwise we can't get the ckey of the demon + spawn_antag(chosen_one.client, get_turf(src), initial(demon_type.name), user.mind) to_chat(user, shatter_msg) to_chat(user, veil_msg) playsound(user.loc, 'sound/effects/glassbr1.ogg', 100, TRUE) @@ -332,23 +327,22 @@ return to_chat(user, span_notice("You activate [src] and wait for confirmation.")) - var/list/baddie_candidates = SSpolling.poll_ghost_candidates( - "Do you want to play as a [role_to_play]?", + var/mob/chosen_one = SSpolling.poll_ghost_candidates( check_jobban = poll_role_check, role = poll_role_check, poll_time = 10 SECONDS, ignore_category = poll_ignore_category, - pic_source = src, + alert_pic = src, role_name_text = role_to_play, + amount_to_pick = 1 ) - if(!LAZYLEN(baddie_candidates)) + if(isnull(chosen_one)) to_chat(user, span_warning(fail_text)) return if(QDELETED(src) || !check_usability(user)) return used = TRUE - var/mob/dead/observer/ghostie = pick(baddie_candidates) - spawn_antag(ghostie.client, get_turf(src), user) + spawn_antag(chosen_one.client, get_turf(src), user) do_sparks(4, TRUE, src) qdel(src) diff --git a/code/modules/antagonists/blob/overmind.dm b/code/modules/antagonists/blob/overmind.dm index e56426a89ad11..401daa97ac5b2 100644 --- a/code/modules/antagonists/blob/overmind.dm +++ b/code/modules/antagonists/blob/overmind.dm @@ -286,7 +286,19 @@ GLOBAL_LIST_EMPTY(blob_nodes) blob_points = clamp(blob_points + points, 0, max_blob_points) hud_used.blobpwrdisplay.maptext = MAPTEXT("
[round(blob_points)]
") -/mob/camera/blob/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null, message_range = 7, datum/saymode/saymode = null) +/mob/camera/blob/say( + message, + bubble_type, + list/spans = list(), + sanitize = TRUE, + datum/language/language, + ignore_spam = FALSE, + forced, + filterproof = FALSE, + message_range = 7, + datum/saymode/saymode, + list/message_mods = list(), +) if (!message) return diff --git a/code/modules/antagonists/blob/powers.dm b/code/modules/antagonists/blob/powers.dm index b35308d092f9d..2f3b51741f9b6 100644 --- a/code/modules/antagonists/blob/powers.dm +++ b/code/modules/antagonists/blob/powers.dm @@ -193,14 +193,20 @@ /mob/camera/blob/proc/pick_blobbernaut_candidate(obj/structure/blob/special/factory/factory) if(isnull(factory)) return - - var/datum/callback/to_call = CALLBACK(src, PROC_REF(on_poll_concluded), factory) - factory.AddComponent(/datum/component/orbit_poll, \ - ignore_key = POLL_IGNORE_BLOB, \ - job_bans = ROLE_BLOB, \ - to_call = to_call, \ - title = "Blobbernaut", \ + var/icon/blobbernaut_icon = icon(icon, "blobbernaut") + blobbernaut_icon.Blend(blobstrain.color, ICON_MULTIPLY) + var/image/blobbernaut_image = image(blobbernaut_icon) + var/mob/chosen_one = SSpolling.poll_ghosts_for_target( + check_jobban = ROLE_BLOB, + poll_time = 20 SECONDS, + checked_target = factory, + ignore_category = POLL_IGNORE_BLOB, + alert_pic = blobbernaut_image, + jump_target = factory, + role_name_text = "blobbernaut", + chat_text_border_icon = blobbernaut_image, ) + on_poll_concluded(factory, chosen_one) /// Called when the ghost poll concludes /mob/camera/blob/proc/on_poll_concluded(obj/structure/blob/special/factory/factory, mob/dead/observer/ghost) diff --git a/code/modules/antagonists/brother/brother.dm b/code/modules/antagonists/brother/brother.dm index 223664f0fa449..49d0ab4ad2300 100644 --- a/code/modules/antagonists/brother/brother.dm +++ b/code/modules/antagonists/brother/brother.dm @@ -77,22 +77,23 @@ flashed.balloon_alert(source, "[flashed.p_they()] resist!") return - flashed.mind.add_antag_datum(/datum/antagonist/brother, team) + if (!team.add_brother(flashed, key_name(source))) // Shouldn't happen given the former, more specific checks but just in case + flashed.balloon_alert(source, "failed!") + return + source.log_message("converted [key_name(flashed)] to blood brother", LOG_ATTACK) flashed.log_message("was converted by [key_name(source)] to blood brother", LOG_ATTACK) - log_game("[key_name(flashed)] converted [key_name(source)] to blood brother", list( - "flashed" = flashed, - "victim" = source, + log_game("[key_name(flashed)] was made into a blood brother by [key_name(source)]", list( + "converted" = flashed, + "converted by" = source, )) - - flashed.balloon_alert(source, "converted") - to_chat(source, span_notice("[span_bold("[flashed]")] has been converted to aide you as your Brother!")) flash.burn_out() flashed.mind.add_memory( \ /datum/memory/recruited_by_blood_brother, \ protagonist = flashed, \ antagonist = owner.current, \ ) + flashed.balloon_alert(source, "converted") UnregisterSignal(source, COMSIG_MOB_SUCCESSFUL_FLASHED_CARBON) source.RemoveComponentSource(REF(src), /datum/component/can_flash_from_behind) @@ -171,6 +172,34 @@ if (prob(10)) brothers_left += 1 +/datum/team/brother_team/add_member(datum/mind/new_member) + . = ..() + if (!new_member.has_antag_datum(/datum/antagonist/brother)) + add_brother(new_member.current) + +/datum/team/brother_team/remove_member(datum/mind/member) + if (!(member in members)) + return + . = ..() + member.remove_antag_datum(/datum/antagonist/brother) + if (isnull(member.current)) + return + for (var/datum/mind/brother_mind as anything in members) + to_chat(brother_mind, span_warning("[span_bold("[member.current.real_name]")] is no longer your brother!")) + update_name() + +/// Adds a new brother to the team +/datum/team/brother_team/proc/add_brother(mob/living/new_brother, source) + if (isnull(new_brother) || isnull(new_brother.mind) || !GET_CLIENT(new_brother) || new_brother.mind.has_antag_datum(/datum/antagonist/brother)) + return FALSE + + for (var/datum/mind/brother_mind as anything in members) + if (brother_mind == new_brother.mind) + continue + to_chat(brother_mind, span_notice("[span_bold("[new_brother.real_name]")] has been converted to aid you as your brother!")) + new_brother.mind.add_antag_datum(/datum/antagonist/brother, src) + return TRUE + /datum/team/brother_team/proc/update_name() var/list/last_names = list() for(var/datum/mind/team_minds as anything in members) @@ -209,7 +238,7 @@ /datum/objective/convert_brother name = "convert brother" - explanation_text = "Convert a brainwashable person using your flash. Any flash will work if you lose or break your starting flash." + explanation_text = "Convert a brainwashable person using your flash on them directly. Any handheld flash will work if you lose or break your starting flash." admin_grantable = FALSE martyr_compatible = TRUE diff --git a/code/modules/antagonists/changeling/powers/augmented_eyesight.dm b/code/modules/antagonists/changeling/powers/augmented_eyesight.dm index b4de878c69eed..dec2fa6a76e16 100644 --- a/code/modules/antagonists/changeling/powers/augmented_eyesight.dm +++ b/code/modules/antagonists/changeling/powers/augmented_eyesight.dm @@ -37,13 +37,13 @@ if(active) active = FALSE REMOVE_TRAIT(user, TRAIT_XRAY_VISION, REF(src)) - ling_eyes.flash_protect = FLASH_PROTECTION_WELDER + ling_eyes.flash_protect = max(ling_eyes.flash_protect += 3, FLASH_PROTECTION_WELDER) to_chat(user, span_changeling("We adjust our eyes to protect them from bright lights.")) else active = TRUE ADD_TRAIT(user, TRAIT_XRAY_VISION, REF(src)) - ling_eyes.flash_protect = FLASH_PROTECTION_SENSITIVE + ling_eyes.flash_protect = max(ling_eyes.flash_protect += -3, FLASH_PROTECTION_HYPER_SENSITIVE) to_chat(user, span_changeling("We adjust our eyes to sense prey through walls.")) user.update_sight() @@ -68,9 +68,9 @@ if(!istype(ling_eyes)) return if(active) - ling_eyes.flash_protect = FLASH_PROTECTION_SENSITIVE + ling_eyes.flash_protect = max(ling_eyes.flash_protect += -3, FLASH_PROTECTION_HYPER_SENSITIVE) else - ling_eyes.flash_protect = FLASH_PROTECTION_WELDER + ling_eyes.flash_protect = max(ling_eyes.flash_protect += 3, FLASH_PROTECTION_WELDER) /// Signal proc to remove flash sensitivity when the eyes are removed /datum/action/changeling/augmented_eyesight/proc/eye_removed(mob/living/source, obj/item/organ/removed, special) diff --git a/code/modules/antagonists/changeling/powers/chameleon_skin.dm b/code/modules/antagonists/changeling/powers/chameleon_skin.dm index 1924190cba36a..f74ad1208dd52 100644 --- a/code/modules/antagonists/changeling/powers/chameleon_skin.dm +++ b/code/modules/antagonists/changeling/powers/chameleon_skin.dm @@ -1,25 +1,25 @@ /datum/action/changeling/chameleon_skin name = "Chameleon Skin" - desc = "Our skin pigmentation rapidly changes to suit our current environment. Costs 25 chemicals." + desc = "Our skin pigmentation rapidly changes to suit our current environment. Costs 10 chemicals." helptext = "Allows us to become invisible after a few seconds of standing still. Can be toggled on and off." button_icon_state = "chameleon_skin" - dna_cost = 2 - chemical_cost = 25 + dna_cost = 1 + chemical_cost = 10 req_human = TRUE /datum/action/changeling/chameleon_skin/sting_action(mob/user) - var/mob/living/carbon/human/H = user //SHOULD always be human, because req_human = TRUE - if(!istype(H)) // req_human could be done in can_sting stuff. + var/mob/living/carbon/human/cling = user //SHOULD always be human, because req_human = TRUE + if(!istype(cling)) // req_human could be done in can_sting stuff. return ..() - if(H.dna.get_mutation(/datum/mutation/human/chameleon)) - H.dna.remove_mutation(/datum/mutation/human/chameleon) + if(cling.dna.get_mutation(/datum/mutation/human/chameleon/changeling)) + cling.dna.remove_mutation(/datum/mutation/human/chameleon/changeling) else - H.dna.add_mutation(/datum/mutation/human/chameleon) + cling.dna.add_mutation(/datum/mutation/human/chameleon/changeling) return TRUE /datum/action/changeling/chameleon_skin/Remove(mob/user) if(user.has_dna()) - var/mob/living/carbon/C = user - C.dna.remove_mutation(/datum/mutation/human/chameleon) + var/mob/living/carbon/cling = user + cling.dna.remove_mutation(/datum/mutation/human/chameleon/changeling) ..() diff --git a/code/modules/antagonists/changeling/powers/darkness_adaptation.dm b/code/modules/antagonists/changeling/powers/darkness_adaptation.dm new file mode 100644 index 0000000000000..c33b36a785f38 --- /dev/null +++ b/code/modules/antagonists/changeling/powers/darkness_adaptation.dm @@ -0,0 +1,83 @@ +/datum/action/changeling/darkness_adaptation + name = "Darkness Adaptation" + desc = "Our skin pigmentation and eyes rapidly changes to suit the darkness. Needs 10 chemicals in-storage to toggle. Slows down our chemical regeneration by 15%" + helptext = "Allows us to darken and change the translucency of our pigmentation, and adapt our eyes to see in dark conditions, \ + The translucent effect works best in dark enviroments and garments. Can be toggled on and off." + button_icon_state = "darkness_adaptation" + dna_cost = 2 + chemical_cost = 10 + + req_human = TRUE + //// is ability active (we are invisible)? + var/is_active = FALSE + /// How much we slow chemical regeneration while active, in chems per second + var/recharge_slowdown = 0.15 + +/datum/action/changeling/darkness_adaptation/on_purchase(mob/user, is_respec) + . = ..() + RegisterSignal(user, COMSIG_CARBON_GAIN_ORGAN, PROC_REF(eye_implanted)) + RegisterSignal(user, COMSIG_CARBON_LOSE_ORGAN, PROC_REF(eye_removed)) + +/datum/action/changeling/darkness_adaptation/sting_action(mob/living/carbon/human/cling) //SHOULD always be human, because req_human = TRUE + ..() + is_active = !is_active + if(is_active) + enable_ability(cling) + else + disable_ability(cling) + +/datum/action/changeling/darkness_adaptation/Remove(mob/living/carbon/human/cling) + ..() + disable_ability(cling) + +/datum/action/changeling/darkness_adaptation/proc/enable_ability(mob/living/carbon/human/cling) //Enable the adaptation + animate(cling, alpha = 65,time = 3 SECONDS) + cling.visible_message(span_warning("[cling]'s skin suddenly starts becoming translucent!"), \ + span_notice("We are now far more stealthy and better at seeing in the dark.")) + animate(cling, color = COLOR_DARK, time = 3 SECONDS) // Darkens their overall appearance + var/datum/antagonist/changeling/changeling_data = cling.mind?.has_antag_datum(/datum/antagonist/changeling) + changeling_data?.chem_recharge_slowdown -= recharge_slowdown //Slows down chem regeneration + var/obj/item/organ/internal/eyes/eyes = cling.get_organ_by_type(/obj/item/organ/internal/eyes) + if(!istype(eyes)) + return + eyes.lighting_cutoff = LIGHTING_CUTOFF_MEDIUM // Adds barely usable, kinda shit night vision + eyes.flash_protect = max(eyes.flash_protect += -1, FLASH_PROTECTION_HYPER_SENSITIVE) // Reduces flash protection by one level + cling.update_sight() // Update the display + +/datum/action/changeling/darkness_adaptation/proc/disable_ability(mob/living/carbon/human/cling) //Restore the adaptation + animate(cling, alpha = 255, time = 3 SECONDS) + cling.visible_message( + span_warning("[cling] appears from thin air!"), + span_notice("We are now appearing normal and lost the ability to see in the dark."), + ) + animate(cling, color = null, time = 3 SECONDS) + var/datum/antagonist/changeling/changeling_data = cling.mind?.has_antag_datum(/datum/antagonist/changeling) + changeling_data?.chem_recharge_slowdown += recharge_slowdown + var/obj/item/organ/internal/eyes/eyes = cling.get_organ_by_type(/obj/item/organ/internal/eyes) + if(!istype(eyes)) + return + eyes.lighting_cutoff = LIGHTING_CUTOFF_VISIBLE + eyes.flash_protect = max(eyes.flash_protect += 1, FLASH_PROTECTION_WELDER) + cling.update_sight() + +/// Signal proc to grant the correct level of flash sensitivity +/datum/action/changeling/darkness_adaptation/proc/eye_implanted(mob/living/source, obj/item/organ/gained, special) + SIGNAL_HANDLER + + var/obj/item/organ/internal/eyes/eyes = gained + if(!istype(eyes)) + return + if(is_active) + eyes.flash_protect = max(eyes.flash_protect += -1, FLASH_PROTECTION_HYPER_SENSITIVE) + else + eyes.flash_protect = max(eyes.flash_protect += 1, FLASH_PROTECTION_WELDER) + +/// Signal proc to remove flash sensitivity when the eyes are removed +/datum/action/changeling/darkness_adaptation/proc/eye_removed(mob/living/source, obj/item/organ/removed, special) + SIGNAL_HANDLER + + var/obj/item/organ/internal/eyes/eyes = removed + if(!istype(eyes)) + return + eyes.flash_protect = initial(eyes.flash_protect) + // We don't need to bother about removing or adding night vision, fortunately, because they can't see anyways diff --git a/code/modules/antagonists/changeling/powers/mutations.dm b/code/modules/antagonists/changeling/powers/mutations.dm index 1f3ac272d6fcc..af7348f7bf139 100644 --- a/code/modules/antagonists/changeling/powers/mutations.dm +++ b/code/modules/antagonists/changeling/powers/mutations.dm @@ -651,17 +651,15 @@ button_icon_state = "queen_item" cooldown_time = 30 SECONDS ///The mob we're going to spawn - var/spawn_type = /mob/living/basic/bee + var/spawn_type = /mob/living/basic/bee/timed/short ///How many are we going to spawn var/spawn_count = 6 - ///How long our summoned mobs last for - var/spawn_lifespan = 25 SECONDS /datum/action/cooldown/hivehead_spawn_minions/PreActivate(atom/target) if(owner.movement_type & VENTCRAWLING) owner.balloon_alert(owner, "unavailable here") return - . = ..() + return ..() /datum/action/cooldown/hivehead_spawn_minions/Activate(atom/target) . = ..() @@ -672,8 +670,6 @@ for(var/i in 1 to spawns) var/mob/living/basic/summoned_minion = new spawn_type(get_turf(owner)) summoned_minion.faction = list("[REF(owner)]") - if(spawn_lifespan != 0 SECONDS) - QDEL_IN(summoned_minion, spawn_lifespan) minion_additional_changes(summoned_minion) ///Our tell that we're using this ability. Usually a sound and a visible message.area @@ -703,8 +699,6 @@ cooldown_time = 15 SECONDS spawn_type = /mob/living/basic/legion_brood spawn_count = 4 - //Legion heads go away by themselves, we don't have to handle that - spawn_lifespan = 0 SECONDS /datum/action/cooldown/hivehead_spawn_minions/legion/do_tell() owner.visible_message(span_warning("[owner]'s head begins to shake as legion begin to pour out!"), span_warning("We release the legion."), span_hear("You hear a loud squishing sound!")) diff --git a/code/modules/antagonists/cult/blood_magic.dm b/code/modules/antagonists/cult/blood_magic.dm index dd858b739d546..8bd23d11b6a05 100644 --- a/code/modules/antagonists/cult/blood_magic.dm +++ b/code/modules/antagonists/cult/blood_magic.dm @@ -405,7 +405,7 @@ if(IS_CULTIST(user)) user.visible_message(span_warning("[user] holds up [user.p_their()] hand, which explodes in a flash of red light!"), \ span_cultitalic("You attempt to stun [target] with the spell!")) - user.mob_light(range = 3, color = LIGHT_COLOR_BLOOD_MAGIC, duration = 0.2 SECONDS) + user.mob_light(range = 1.1, power = 2, color = LIGHT_COLOR_BLOOD_MAGIC, duration = 0.2 SECONDS) if(IS_HERETIC(target)) to_chat(user, span_warning("Some force greater than you intervenes! [target] is protected by the Forgotten Gods!")) to_chat(target, span_warning("You are protected by your faith to the Forgotten Gods.")) diff --git a/code/modules/antagonists/cult/cult_comms.dm b/code/modules/antagonists/cult/cult_comms.dm index bee8eec306f7c..01aac3e869161 100644 --- a/code/modules/antagonists/cult/cult_comms.dm +++ b/code/modules/antagonists/cult/cult_comms.dm @@ -147,17 +147,18 @@ asked_cultists += team_member.current var/list/yes_voters = SSpolling.poll_candidates( - question = "[nominee] seeks to lead your cult, do you support [nominee.p_them()]?", + question = "[span_notice(nominee)] seeks to lead your cult, do you support [nominee.p_them()]?", poll_time = 30 SECONDS, group = asked_cultists, - pic_source = nominee, - role_name_text = "cult master", + alert_pic = nominee, + role_name_text = "cult master nomination", custom_response_messages = list( POLL_RESPONSE_SIGNUP = "You have pledged your allegience to [nominee].", POLL_RESPONSE_ALREADY_SIGNED = "You have already pledged your allegience!", POLL_RESPONSE_NOT_SIGNED = "You aren't nominated for this.", POLL_RESPONSE_TOO_LATE_TO_UNREGISTER = "It's too late to unregister yourself, voting has already begun!", POLL_RESPONSE_UNREGISTERED = "You have been removed your pledge to [nominee].", + chat_text_border_icon = mutable_appearance('icons/effects/effects.dmi', "cult_master_logo") ) ) if(QDELETED(nominee) || nominee.incapacitated()) diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm index 8d7d66979f42b..8e5e7099be49a 100644 --- a/code/modules/antagonists/cult/runes.dm +++ b/code/modules/antagonists/cult/runes.dm @@ -737,13 +737,12 @@ GLOBAL_VAR_INIT(narsie_summon_count, 0) if(!mob_to_revive.client || mob_to_revive.client.is_afk()) set waitfor = FALSE - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as a [mob_to_revive.real_name], an inactive blood cultist?", check_jobban = ROLE_CULTIST, role = ROLE_CULTIST, poll_time = 5 SECONDS, target_mob = mob_to_revive, pic_source = mob_to_revive) - if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) + var/mob/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to play as [span_danger(mob_to_revive.real_name)], an [span_notice("inactive blood cultist")]?", check_jobban = ROLE_CULTIST, role = ROLE_CULTIST, poll_time = 5 SECONDS, checked_target = mob_to_revive, alert_pic = mob_to_revive, role_name_text = "inactive cultist") + if(chosen_one) to_chat(mob_to_revive.mind, "Your physical form has been taken over by another soul due to your inactivity! Ahelp if you wish to regain your form.") - message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(mob_to_revive)]) to replace an AFK player.") + message_admins("[key_name_admin(chosen_one)] has taken control of ([key_name_admin(mob_to_revive)]) to replace an AFK player.") mob_to_revive.ghostize(FALSE) - mob_to_revive.key = C.key + mob_to_revive.key = chosen_one.key else fail_invoke() return diff --git a/code/modules/antagonists/disease/disease_mob.dm b/code/modules/antagonists/disease/disease_mob.dm index 18c957c28fd7d..acefd0e37173c 100644 --- a/code/modules/antagonists/disease/disease_mob.dm +++ b/code/modules/antagonists/disease/disease_mob.dm @@ -113,7 +113,19 @@ the new instance inside the host to be updated to the template's stats. for(var/datum/disease_ability/ability in purchased_abilities) . += span_notice("[ability.name]") -/mob/camera/disease/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null, message_range = 7, datum/saymode/saymode = null) +/mob/camera/disease/say( + message, + bubble_type, + list/spans = list(), + sanitize = TRUE, + datum/language/language, + ignore_spam = FALSE, + forced, + filterproof = FALSE, + message_range = 7, + datum/saymode/saymode, + list/message_mods = list(), +) if(!message) return if(sanitize) diff --git a/code/modules/antagonists/heretic/heretic_knowledge.dm b/code/modules/antagonists/heretic/heretic_knowledge.dm index f2cf7b0004771..bb59076a6bb06 100644 --- a/code/modules/antagonists/heretic/heretic_knowledge.dm +++ b/code/modules/antagonists/heretic/heretic_knowledge.dm @@ -537,23 +537,22 @@ animate(summoned, 10 SECONDS, alpha = 155) message_admins("A [summoned.name] is being summoned by [ADMIN_LOOKUPFLW(user)] in [ADMIN_COORDJMP(summoned)].") - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as a [summoned.name]?", check_jobban = ROLE_HERETIC, poll_time = 10 SECONDS, target_mob = summoned, ignore_category = poll_ignore_define, pic_source = summoned, role_name_text = summoned.name) - if(!LAZYLEN(candidates)) + var/mob/chosen_one = SSpolling.poll_ghosts_for_target(check_jobban = ROLE_HERETIC, poll_time = 10 SECONDS, checked_target = summoned, ignore_category = poll_ignore_define, alert_pic = summoned, role_name_text = summoned.name) + if(isnull(chosen_one)) loc.balloon_alert(user, "ritual failed, no ghosts!") animate(summoned, 0.5 SECONDS, alpha = 0) QDEL_IN(summoned, 0.6 SECONDS) return FALSE - var/mob/dead/observer/picked_candidate = pick(candidates) // Ok let's make them an interactable mob now, since we got a ghost summoned.alpha = 255 REMOVE_TRAIT(summoned, TRAIT_NO_TRANSFORM, REF(src)) summoned.move_resist = initial(summoned.move_resist) summoned.ghostize(FALSE) - summoned.key = picked_candidate.key + summoned.key = chosen_one.key - user.log_message("created a [summoned.name], controlled by [key_name(picked_candidate)].", LOG_GAME) + user.log_message("created a [summoned.name], controlled by [key_name(chosen_one)].", LOG_GAME) message_admins("[ADMIN_LOOKUPFLW(user)] created a [summoned.name], [ADMIN_LOOKUPFLW(summoned)].") var/datum/antagonist/heretic_monster/heretic_monster = summoned.mind.add_antag_datum(/datum/antagonist/heretic_monster) diff --git a/code/modules/antagonists/heretic/items/eldritch_painting.dm b/code/modules/antagonists/heretic/items/eldritch_painting.dm index 5aa63407dc6ef..5302fc1c9c148 100644 --- a/code/modules/antagonists/heretic/items/eldritch_painting.dm +++ b/code/modules/antagonists/heretic/items/eldritch_painting.dm @@ -92,7 +92,7 @@ if(!IS_HERETIC(examiner)) to_chat(examiner, span_hypnophrase("Respite, for now....")) examiner.mob_mood.mood_events.Remove("eldritch_weeping") - examiner.add_mood_event("weeping_withdrawl", /datum/mood_event/eldritch_painting/weeping_withdrawl) + examiner.add_mood_event("weeping_withdrawal", /datum/mood_event/eldritch_painting/weeping_withdrawal) return to_chat(examiner, span_notice("Oh, what arts! Just gazing upon it clears your mind.")) diff --git a/code/modules/antagonists/heretic/knowledge/ash_lore.dm b/code/modules/antagonists/heretic/knowledge/ash_lore.dm index 3d4a9b3552db0..fe10f7949eae5 100644 --- a/code/modules/antagonists/heretic/knowledge/ash_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/ash_lore.dm @@ -42,7 +42,7 @@ /datum/heretic_knowledge/ashen_grasp name = "Grasp of Ash" - desc = "Your Mansus Grasp will burn the eyes of the victim, causing damage and blindness." + desc = "Your Mansus Grasp will burn the eyes of the victim, damaging them and blurring their vision." gain_text = "The Nightwatcher was the first of them, his treason started it all. \ Their lantern, expired to ash - their watch, absent." next_knowledge = list(/datum/heretic_knowledge/spell/ash_passage) @@ -70,7 +70,7 @@ /datum/heretic_knowledge/spell/ash_passage name = "Ashen Passage" - desc = "Grants you Ashen Passage, a silent but short range jaunt." + desc = "Grants you Ashen Passage, a spell that lets you phase out of reality and traverse a short distance, passing though any walls." gain_text = "He knew how to walk between the planes." next_knowledge = list( /datum/heretic_knowledge/mark/ash_mark, @@ -181,6 +181,7 @@ When completed, you become a harbinger of flames, gaining two abilites. \ Cascade, which causes a massive, growing ring of fire around you, \ and Oath of Flame, causing you to passively create a ring of flames as you walk. \ + Some ashen spells you already knew will be empowered as well. \ You will also become immune to flames, space, and similar environmental hazards." gain_text = "The Watch is dead, the Nightwatcher burned with it. Yet his fire burns evermore, \ for the Nightwatcher brought forth the rite to mankind! His gaze continues, as now I am one with the flames, \ diff --git a/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm b/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm index 1a16f2e9f9321..09efb5c2eb8f4 100644 --- a/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm @@ -42,7 +42,8 @@ /datum/heretic_knowledge/cosmic_grasp name = "Grasp of Cosmos" - desc = "Your Mansus Grasp will give people a star mark (cosmic ring) and create a cosmic field where you stand." + desc = "Your Mansus Grasp will give people a star mark (cosmic ring) and create a cosmic field where you stand. \ + People with a star mark can not pass cosmic fields." gain_text = "Some stars dimmed, others' magnitude increased. \ With newfound strength I could channel the nebula's power into myself." next_knowledge = list(/datum/heretic_knowledge/spell/cosmic_runes) @@ -82,7 +83,8 @@ /datum/heretic_knowledge/mark/cosmic_mark name = "Mark of Cosmos" desc = "Your Mansus Grasp now applies the Mark of Cosmos. The mark is triggered from an attack with your Cosmic Blade. \ - When triggered, the victim is returned to the location where the mark was originally applied to them. \ + When triggered, the victim is returned to the location where the mark was originally applied to them, \ + leaving a cosmic field in their place. \ They will then be paralyzed for 2 seconds." gain_text = "The Beast now whispered to me occasionally, only small tidbits of their circumstances. \ I can help them, I have to help them." @@ -98,8 +100,7 @@ name = "Star Touch" desc = "Grants you Star Touch, a spell which places a star mark upon your target \ and creates a cosmic field at your feet and to the turfs next to you. Targets which already have a star mark \ - will be forced to sleep for 4 seconds. When the victim is hit it also creates a beam that \ - deals a bit of fire damage and damages the cells. \ + will be forced to sleep for 4 seconds. When the victim is hit it also creates a beam that burns them. \ The beam lasts a minute, until the beam is obstructed or until a new target has been found." gain_text = "After waking in a cold sweat I felt a palm on my scalp, a sigil burned onto me. \ My veins now emitted a strange purple glow, the Beast knows I will surpass its expectations." @@ -110,7 +111,7 @@ /datum/heretic_knowledge/spell/star_blast name = "Star Blast" - desc = "Fires a projectile that moves very slowly and creates cosmic fields on impact. \ + desc = "Fires a projectile that moves very slowly, raising a short-lived wall of cosmic fields where it goes. \ Anyone hit by the projectile will receive burn damage, a knockdown, and give people in a three tile range a star mark." gain_text = "The Beast was behind me now at all times, with each sacrifice words of affirmation coursed through me." next_knowledge = list( @@ -243,7 +244,8 @@ You can also give it commands through speech. \ The Star Gazer is a strong ally who can even break down reinforced walls. \ The Star Gazer has an aura that will heal you and damage opponents. \ - Star Touch can now teleport you to the Star Gazer when activated in your hand." + Star Touch can now teleport you to the Star Gazer when activated in your hand. \ + Your cosmic expansion spell and your blades also become greatly empowered." gain_text = "The Beast held out its hand, I grabbed hold and they pulled me to them. Their body was towering, but it seemed so small and feeble after all their tales compiled in my head. \ I clung on to them, they would protect me, and I would protect it. \ I closed my eyes with my head laid against their form. I was safe. \ diff --git a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm index f41512d76c069..8898ba7f59c66 100644 --- a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm @@ -122,6 +122,7 @@ /datum/heretic_knowledge/limited_amount/flesh_ghoul name = "Imperfect Ritual" desc = "Allows you to transmute a corpse and a poppy to create a Voiceless Dead. \ + The corpse does not need to have a soul. \ Voiceless Dead are mute ghouls and only have 50 health, but can use Bloody Blades effectively. \ You can only create two at a time." gain_text = "I found notes of a dark ritual, unfinished... yet still, I pushed forward." @@ -167,15 +168,13 @@ if(!soon_to_be_ghoul.mind || !soon_to_be_ghoul.client) message_admins("[ADMIN_LOOKUPFLW(user)] is creating a voiceless dead of a body with no player.") - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as a [soon_to_be_ghoul.real_name], a voiceless dead?", check_jobban = ROLE_HERETIC, role = ROLE_HERETIC, poll_time = 5 SECONDS, target_mob = soon_to_be_ghoul, pic_source = soon_to_be_ghoul, role_name_text = "voiceless dead") - if(!LAZYLEN(candidates)) + var/mob/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to play as [span_danger(soon_to_be_ghoul.real_name)], a [span_notice("voiceless dead")]?", check_jobban = ROLE_HERETIC, role = ROLE_HERETIC, poll_time = 5 SECONDS, checked_target = soon_to_be_ghoul, alert_pic = mutable_appearance('icons/mob/human/human.dmi', "husk"), jump_target = soon_to_be_ghoul, role_name_text = "voiceless dead") + if(isnull(chosen_one)) loc.balloon_alert(user, "ritual failed, no ghosts!") return FALSE - - var/mob/dead/observer/chosen_candidate = pick(candidates) - message_admins("[key_name_admin(chosen_candidate)] has taken control of ([key_name_admin(soon_to_be_ghoul)]) to replace an AFK player.") + message_admins("[key_name_admin(chosen_one)] has taken control of ([key_name_admin(soon_to_be_ghoul)]) to replace an AFK player.") soon_to_be_ghoul.ghostize(FALSE) - soon_to_be_ghoul.key = chosen_candidate.key + soon_to_be_ghoul.key = chosen_one.key selected_atoms -= soon_to_be_ghoul make_ghoul(user, soon_to_be_ghoul) diff --git a/code/modules/antagonists/heretic/knowledge/lock_lore.dm b/code/modules/antagonists/heretic/knowledge/lock_lore.dm index 9f80f47b0ae48..0727b86bb668e 100644 --- a/code/modules/antagonists/heretic/knowledge/lock_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/lock_lore.dm @@ -92,9 +92,12 @@ /datum/heretic_knowledge/key_ring name = "Key Keeper’s Burden" desc = "Allows you to transmute a wallet, an iron rod, and an ID card to create an Eldritch Card. \ - It functions the same as an ID Card, but attacking it with an ID card fuses it and gains its access. \ - You can use it in-hand to change its form to a card you fused. \ - Does not preserve the card used in the ritual." + Hit a pair of airlocks with it to create a pair of portals, which will teleport you between them, but teleport non-heretics randomly. \ + You can ctrl-click the card to invert this behavior for created portals. \ + Each card may only sustain a single pair of portals at the same time. \ + It also functions and appears the same as a regular ID Card. \ + Attacking it with a normal ID card consumes it and gains its access, and you can use it in-hand to change its appearance to a card you fused. \ + Does not preserve the card originally used in the ritual." gain_text = "The Keeper sneered. \"These plastic rectangles are a mockery of keys, and I curse every door that desires them.\"" required_atoms = list( /obj/item/storage/wallet = 1, @@ -186,7 +189,8 @@ desc = "The ascension ritual of the Path of Knock. \ Bring 3 corpses without organs in their torso to a transmutation rune to complete the ritual. \ When completed, you gain the ability to transform into empowered eldritch creatures \ - and in addition, create a tear to the Labyrinth's heart; \ + and your keyblades will become even deadlier. \ + In addition, you will create a tear to the Labyrinth's heart; \ a tear in reality located at the site of this ritual. \ Eldritch creatures will endlessly pour from this rift \ who are bound to obey your instructions." diff --git a/code/modules/antagonists/heretic/knowledge/moon_lore.dm b/code/modules/antagonists/heretic/knowledge/moon_lore.dm index d7d1bd3bf22a7..723599ad262f5 100644 --- a/code/modules/antagonists/heretic/knowledge/moon_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/moon_lore.dm @@ -45,8 +45,8 @@ /datum/heretic_knowledge/moon_grasp name = "Grasp of Lunacy" - desc = "Your Mansus Grasp will cause them to hallucinate everyone as lunar mass, \ - and hides your identity for a short dur ation." + desc = "Your Mansus Grasp will cause your victims to hallucinate everyone as lunar mass, \ + and hides your identity for a short duration." gain_text = "The troupe on the side of the moon showed me truth, and I took it." next_knowledge = list(/datum/heretic_knowledge/spell/moon_smile) cost = 1 @@ -85,9 +85,8 @@ /datum/heretic_knowledge/mark/moon_mark name = "Mark of Moon" - desc = "Your Mansus Grasp now applies the Mark of Moon. The mark is triggered from an attack with your Moon Blade. \ - When triggered, the victim is confused, and when the mark is applied they are pacified \ - until attacked." + desc = "Your Mansus Grasp now applies the Mark of Moon, pacifying the victim until attacked. \ + The mark can also be triggered from an attack with your Moon Blade, leaving the victim confused." gain_text = "The troupe on the moon would dance all day long \ and in that dance the moon would smile upon us \ but when the night came its smile would dull forced to gaze on the earth." @@ -112,9 +111,9 @@ /datum/heretic_knowledge/moon_amulette name = "Moonlight Amulette" - desc = "Allows you to transmute 2 sheets of glass, a heart and a tie \ - if the item is used on someone with low sanity they go berserk attacking everyone \ - , if their sanity isnt low enough it decreases their mood." + desc = "Allows you to transmute 2 sheets of glass, a heart and a tie to create a Moonlight Amulette. \ + If the item is used on someone with low sanity they go berserk attacking everyone, \ + if their sanity isn't low enough it decreases their mood." gain_text = "At the head of the parade he stood, the moon condensed into one mass, a reflection of the soul." next_knowledge = list( /datum/heretic_knowledge/blade_upgrade/moon, @@ -153,9 +152,9 @@ /datum/heretic_knowledge/spell/moon_ringleader name = "Ringleaders Rise" - desc = "Grants you Ringleaders Rise, an aoe spell that deals more brain damage the lower the sanity of everyone in the AoE,\ - causes hallucinations with those who have less sanity getting more. \ - If their sanity is low enough turns them insane, the spell then halves their sanity." + desc = "Grants you Ringleaders Rise, an AoE spell that deals more brain damage the lower the sanity of everyone in the AoE \ + and causes hallucinations, with those who have less sanity getting more. \ + If their sanity is low enough this turns them insane, the spell then halves their sanity." gain_text = "I grabbed his hand and we rose, those who saw the truth rose with us. \ The ringleader pointed up and the dim light of truth illuminated us further." next_knowledge = list( @@ -170,8 +169,8 @@ name = "The Last Act" desc = "The ascension ritual of the Path of Moon. \ Bring 3 corpses with more than 50 brain damage to a transmutation rune to complete the ritual. \ - When completed, you become a harbinger of madness gaining and aura of passive sanity decrease \ - , confusion increase and if their sanity is low enough brain damage and blindness. \ + When completed, you become a harbinger of madness gaining and aura of passive sanity decrease, \ + confusion increase and, if their sanity is low enough, brain damage and blindness. \ 1/5th of the crew will turn into acolytes and follow your command, they will all recieve moonlight amulettes." gain_text = "We dived down towards the crowd, his soul splitting off in search of greater venture \ for where the Ringleader had started the parade, I shall continue it unto the suns demise \ diff --git a/code/modules/antagonists/heretic/knowledge/side_ash_moon.dm b/code/modules/antagonists/heretic/knowledge/side_ash_moon.dm index 4ce8f9de9c936..a4810c706c118 100644 --- a/code/modules/antagonists/heretic/knowledge/side_ash_moon.dm +++ b/code/modules/antagonists/heretic/knowledge/side_ash_moon.dm @@ -21,7 +21,7 @@ name = "Curse of Paralysis" desc = "Allows you to transmute a hatchet and both a left and right leg to cast a curse of immobility on a crew member. \ While cursed, the victim will be unable to walk. You can additionally supply an item that a victim has touched \ - or is covered in the victim's blood to empower the curse." + or is covered in the victim's blood to make the curse last longer." gain_text = "The flesh of humanity is weak. Make them bleed. Show them their fragility." next_knowledge = list( /datum/heretic_knowledge/mad_mask, @@ -58,8 +58,8 @@ /datum/heretic_knowledge/summon/ashy name = "Ashen Ritual" - desc = "Allows you to transmute a head, a pile of ash, and a book to create an Ash Man. \ - Ash Men have a short range jaunt and the ability to cause bleeding in foes at range. \ + desc = "Allows you to transmute a head, a pile of ash, and a book to create an Ash Spirit. \ + Ash Spirits have a short range jaunt and the ability to cause bleeding in foes at range. \ They also have the ability to create a ring of fire around themselves for a length of time." gain_text = "I combined my principle of hunger with my desire for destruction. The Marshal knew my name, and the Nightwatcher gazed on." next_knowledge = list( diff --git a/code/modules/antagonists/heretic/knowledge/side_cosmos_ash.dm b/code/modules/antagonists/heretic/knowledge/side_cosmos_ash.dm index 470d98e178b7e..14a003ce11c0b 100644 --- a/code/modules/antagonists/heretic/knowledge/side_cosmos_ash.dm +++ b/code/modules/antagonists/heretic/knowledge/side_cosmos_ash.dm @@ -36,8 +36,9 @@ /datum/heretic_knowledge/eldritch_coin name = "Eldritch Coin" desc = "Allows you to transmute a sheet of plasma and a diamond to create an Eldritch Coin. \ - The coin will open or close nearby doors when landing on heads and bolt or unbolt nearby doors \ - when landing on tails. If the coin gets inserted into an airlock it emags the door destroying the coin." + The coin will open or close nearby doors when landing on heads and toggle their bolts \ + when landing on tails. If you insert the coin into an airlock, it will be consumed \ + to fry its electronics, opening the airlock permanently unless bolted. " gain_text = "The Mansus is a place of all sorts of sins. But greed held a special role." next_knowledge = list( /datum/heretic_knowledge/spell/cosmic_expansion, diff --git a/code/modules/antagonists/heretic/knowledge/side_lock_flesh.dm b/code/modules/antagonists/heretic/knowledge/side_lock_flesh.dm index e2825c6db2869..74013f2b0bd1d 100644 --- a/code/modules/antagonists/heretic/knowledge/side_lock_flesh.dm +++ b/code/modules/antagonists/heretic/knowledge/side_lock_flesh.dm @@ -2,7 +2,8 @@ /datum/heretic_knowledge/spell/opening_blast name = "Wave Of Desperation" desc = "Grants you Wave Of Desparation, a spell which can only be cast while restrained. \ - It removes your restraints, repels and knocks down adjacent people, and applies the Mansus Grasp to everything nearby." + It removes your restraints, repels and knocks down adjacent people, and applies the Mansus Grasp to everything nearby. \ + However, you will fall unconscious a short time after casting this spell." gain_text = "My shackles undone in dark fury, their feeble bindings crumble before my power." next_knowledge = list( /datum/heretic_knowledge/summon/raw_prophet, diff --git a/code/modules/antagonists/heretic/knowledge/side_lock_moon.dm b/code/modules/antagonists/heretic/knowledge/side_lock_moon.dm index 737e0f08f40a1..f1dd564310be5 100644 --- a/code/modules/antagonists/heretic/knowledge/side_lock_moon.dm +++ b/code/modules/antagonists/heretic/knowledge/side_lock_moon.dm @@ -16,10 +16,10 @@ /datum/heretic_knowledge/unfathomable_curio name = "Unfathomable Curio" - desc = "Allows you to transmute 3 rods, a brain and a belt into an Unfathomable Curio\ - , a belt that can hold blades and items for rituals. Whilst worn will also \ + desc = "Allows you to transmute 3 rods, lungs and any belt into an Unfathomable Curio\ + , a belt that can hold blades and items for rituals. Whilst worn it will also \ veil you, allowing you to take 5 hits without suffering damage, this veil will recharge very slowly \ - outside of combat. When examined the examiner will suffer brain damage and blindness." + outside of combat." gain_text = "The mansus holds many a curio, some are not meant for the mortal eye." next_knowledge = list( /datum/heretic_knowledge/spell/burglar_finesse, @@ -38,12 +38,12 @@ name = "Unsealed Arts" desc = "Allows you to transmute a canvas and an additional item to create a piece of art, these paintings \ have different effects depending on the additional item added. Possible paintings: \ - The sister and He Who Wept: Eyes. When a non-heretic looks at the painting they will begin to hallucinate everyone as heretics. \ - The First Desire: Any bodypart. Increases the hunger of non-heretics, when examined drops an organ or body part at your feet. \ - Great chaparral over rolling hills: Any grown food. Spreads kudzu when placed, when examined grants a flower. \ - Lady out of gates: Gloves. Causes non-heretics to scratch themselves, when examined removes all your mutations. \ - Climb over the rusted mountain: Trash. Causes non-heretics to rust the floor they walk on. \ - These effects are mitigated for a few minutes when a non-heretic suffering an effect examines the painting that caused the effect." + The sister and He Who Wept: Eyes. Clears your own mind, but curses non-heretics with hallucinations. \ + The First Desire: Any bodypart. Supplies you with random organs, but curses non-heretics with a hunger for flesh. \ + Great chaparral over rolling hills: Any grown food. Spreads kudzu when placed and examined by non-heretics. Also supplies you with poppies and harebells. \ + Lady out of gates: Gloves. Clears your mutations, but mutates non-heretics and curses them with scratching. \ + Climb over the rusted mountain: Trash. Curses non-heretics to rust the floor they walk on. \ + Non-heretics can counter most of these effects by examining one of these paintings." gain_text = "A wind of inspiration blows through me, past the walls and past the gate inspirations lie, yet to be depicted. \ They yearn for mortal eyes again, and I shall grant that wish." next_knowledge = list( diff --git a/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm b/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm index 2dbb44ea4eb7e..3d326b4a9af45 100644 --- a/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm +++ b/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm @@ -44,7 +44,7 @@ name = "Curse of Corrosion" desc = "Allows you to transmute wirecutters, a pool of vomit, and a heart to cast a curse of sickness on a crew member. \ While cursed, the victim will repeatedly vomit while their organs will take constant damage. You can additionally supply an item \ - that a victim has touched or is covered in the victim's blood to empower the curse." + that a victim has touched or is covered in the victim's blood to make the curse last longer." gain_text = "The body of humanity is temporary. Their weaknesses cannot be stopped, like iron falling to rust. Show them all." next_knowledge = list( /datum/heretic_knowledge/spell/area_conversion, diff --git a/code/modules/antagonists/heretic/knowledge/side_void_blade.dm b/code/modules/antagonists/heretic/knowledge/side_void_blade.dm index 643fd434af7b5..e044eee8619ef 100644 --- a/code/modules/antagonists/heretic/knowledge/side_void_blade.dm +++ b/code/modules/antagonists/heretic/knowledge/side_void_blade.dm @@ -144,7 +144,8 @@ name = "Maid in the Mirror" desc = "Allows you to transmute five sheets of titanium, a flash, a suit of armor, and a pair of lungs \ to create a Maid in the Mirror. Maid in the Mirrors are decent combatants that can become incorporeal by \ - phasing in and out of the mirror realm, serving as powerful scouts and ambushers." + phasing in and out of the mirror realm, serving as powerful scouts and ambushers. \ + However, they are weak to mortal gaze and take damage by being examined." gain_text = "Within each reflection, lies a gateway into an unimaginable world of colors never seen and \ people never met. The ascent is glass, and the walls are knives. Each step is blood, if you do not have a guide." next_knowledge = list( diff --git a/code/modules/antagonists/heretic/magic/apetravulnera.dm b/code/modules/antagonists/heretic/magic/apetravulnera.dm index 801104dddf9fc..e80d08911848c 100644 --- a/code/modules/antagonists/heretic/magic/apetravulnera.dm +++ b/code/modules/antagonists/heretic/magic/apetravulnera.dm @@ -5,7 +5,7 @@ background_icon_state = "bg_heretic" overlay_icon_state = "bg_heretic_border" button_icon = 'icons/mob/actions/actions_ecult.dmi' - button_icon_state = "cleave" + button_icon_state = "apetra_vulnera" school = SCHOOL_FORBIDDEN cooldown_time = 45 SECONDS @@ -23,7 +23,7 @@ /datum/action/cooldown/spell/pointed/apetra_vulnera/cast(mob/living/carbon/human/cast_on) . = ..() - + if(IS_HERETIC_OR_MONSTER(cast_on)) return FALSE @@ -44,7 +44,7 @@ a_limb_got_damaged = TRUE var/datum/wound/slash/crit_wound = new wound_type() crit_wound.apply_wound(bodypart) - + if(!a_limb_got_damaged) var/datum/wound/slash/crit_wound = new wound_type() crit_wound.apply_wound(pick(cast_on.bodyparts)) @@ -53,7 +53,7 @@ span_danger("[cast_on]'s scratches and bruises are torn open by an unholy force!"), span_danger("Your scratches and bruises are torn open by some horrible unholy force!") ) - + new /obj/effect/temp_visual/cleave(get_turf(cast_on)) return TRUE diff --git a/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm b/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm index 18e8d26fecc60..e792dc116da6f 100644 --- a/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm +++ b/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm @@ -6,6 +6,8 @@ cooldown_time = 20 SECONDS convert_damage = FALSE die_with_shapeshifted_form = FALSE + button_icon = 'icons/mob/actions/actions_ecult.dmi' + button_icon_state = "lock_ascension" possible_shapes = list( /mob/living/basic/heretic_summon/ash_spirit, /mob/living/basic/heretic_summon/raw_prophet/ascended, diff --git a/code/modules/antagonists/heretic/magic/caretaker.dm b/code/modules/antagonists/heretic/magic/caretaker.dm index 29fcecf076fb0..86ff285001917 100644 --- a/code/modules/antagonists/heretic/magic/caretaker.dm +++ b/code/modules/antagonists/heretic/magic/caretaker.dm @@ -6,8 +6,8 @@ and you can be removed from it upon contact with antimagical artifacts." background_icon_state = "bg_heretic" overlay_icon_state = "bg_heretic_border" - button_icon = 'icons/mob/actions/actions_minor_antag.dmi' - button_icon_state = "ninja_cloak" + button_icon = 'icons/mob/actions/actions_ecult.dmi' + button_icon_state = "caretaker" sound = 'sound/effects/curse2.ogg' school = SCHOOL_FORBIDDEN diff --git a/code/modules/antagonists/heretic/magic/cosmic_runes.dm b/code/modules/antagonists/heretic/magic/cosmic_runes.dm index 5115a2181fa91..4af3b94b44f34 100644 --- a/code/modules/antagonists/heretic/magic/cosmic_runes.dm +++ b/code/modules/antagonists/heretic/magic/cosmic_runes.dm @@ -1,6 +1,7 @@ /datum/action/cooldown/spell/cosmic_rune name = "Cosmic Rune" - desc = "Creates a cosmic rune at your position, only two can exist at a time. Invoking one rune transports you to the other." + desc = "Creates a cosmic rune at your position, only two can exist at a time. Invoking one rune transports you to the other. \ + Anyone with a star mark gets transported along with you." background_icon_state = "bg_heretic" overlay_icon_state = "bg_heretic_border" button_icon = 'icons/mob/actions/actions_ecult.dmi' diff --git a/code/modules/antagonists/heretic/magic/moon_parade.dm b/code/modules/antagonists/heretic/magic/moon_parade.dm index 409e55bf9261a..3b7f1d007cd6e 100644 --- a/code/modules/antagonists/heretic/magic/moon_parade.dm +++ b/code/modules/antagonists/heretic/magic/moon_parade.dm @@ -1,6 +1,6 @@ /datum/action/cooldown/spell/pointed/projectile/moon_parade name = "Lunar parade" - desc = "This unleashes the parade towards a target." + desc = "This unleashes the parade, making everyone in its way join it and suffer hallucinations." background_icon_state = "bg_heretic" overlay_icon_state = "bg_heretic_border" button_icon = 'icons/mob/actions/actions_ecult.dmi' diff --git a/code/modules/antagonists/heretic/magic/moon_ringleader.dm b/code/modules/antagonists/heretic/magic/moon_ringleader.dm index 45d3285a876da..3c0b1d2aedb52 100644 --- a/code/modules/antagonists/heretic/magic/moon_ringleader.dm +++ b/code/modules/antagonists/heretic/magic/moon_ringleader.dm @@ -1,7 +1,8 @@ /datum/action/cooldown/spell/aoe/moon_ringleader name = "Ringleaders Rise" - desc = "Big AoE spell that deals more brain damage the lower the sanity of everyone in the AoE and it also causes hallucinations with those who have less sanity getting more. \ - If their sanity is low enough they snap and go insane, the spell then halves their sanity." + desc = "Big AoE spell that deals brain damage and causes hallucinations to everyone in the AoE. \ + The worse their sanity, the stronger this spell becomes. \ + If their sanity is low enough, they even snap and go insane, and the spell then further halves their sanity." background_icon_state = "bg_heretic" overlay_icon_state = "bg_heretic_border" button_icon = 'icons/mob/actions/actions_ecult.dmi' diff --git a/code/modules/antagonists/heretic/magic/moon_smile.dm b/code/modules/antagonists/heretic/magic/moon_smile.dm index 893059721c428..90a392691e9fa 100644 --- a/code/modules/antagonists/heretic/magic/moon_smile.dm +++ b/code/modules/antagonists/heretic/magic/moon_smile.dm @@ -28,7 +28,8 @@ /datum/action/cooldown/spell/pointed/moon_smile/cast(mob/living/carbon/human/cast_on) . = ..() /// The duration of these effects are based on sanity, mainly for flavor but also to make it a weaker alpha strike - var/moon_smile_duration = (150 - cast_on.mob_mood.sanity) / 10 + var/maximum_duration = 15 SECONDS + var/moon_smile_duration = ((SANITY_MAXIMUM - cast_on.mob_mood.sanity) / (SANITY_MAXIMUM - SANITY_INSANE)) * maximum_duration if(cast_on.can_block_magic(antimagic_flags)) to_chat(cast_on, span_notice("The moon turns, its smile no longer set on you.")) to_chat(owner, span_warning("The moon does not smile upon them.")) @@ -40,7 +41,8 @@ cast_on.set_eye_blur_if_lower(moon_smile_duration + 7 SECONDS) var/obj/item/organ/internal/ears/ears = cast_on.get_organ_slot(ORGAN_SLOT_EARS) - ears?.adjustEarDamage(0, moon_smile_duration + 2 SECONDS) + //adjustEarDamage takes deafness duration parameter in one unit per two seconds, instead of the normal time, so we divide by two seconds + ears?.adjustEarDamage(0, (moon_smile_duration + 2 SECONDS) / (2 SECONDS)) cast_on.adjust_silence(moon_smile_duration + 5 SECONDS) cast_on.AdjustKnockdown(2 SECONDS) diff --git a/code/modules/antagonists/heretic/magic/nightwatcher_rebirth.dm b/code/modules/antagonists/heretic/magic/nightwatcher_rebirth.dm index 64638d7103b17..4e37f5db17fed 100644 --- a/code/modules/antagonists/heretic/magic/nightwatcher_rebirth.dm +++ b/code/modules/antagonists/heretic/magic/nightwatcher_rebirth.dm @@ -1,6 +1,6 @@ /datum/action/cooldown/spell/aoe/fiery_rebirth name = "Nightwatcher's Rebirth" - desc = "A spell that extinguishes you drains nearby heathens engulfed in flames of their life force, \ + desc = "A spell that extinguishes you and drains nearby heathens engulfed in flames of their life force, \ healing you for each victim drained. Those in critical condition \ will have the last of their vitality drained, killing them." background_icon_state = "bg_heretic" diff --git a/code/modules/antagonists/heretic/magic/rust_charge.dm b/code/modules/antagonists/heretic/magic/rust_charge.dm index d5427cf376262..56054bd56fdd8 100644 --- a/code/modules/antagonists/heretic/magic/rust_charge.dm +++ b/code/modules/antagonists/heretic/magic/rust_charge.dm @@ -1,7 +1,9 @@ // Rust charge, a charge action that can only be started on rust (and only destroys rust tiles) /datum/action/cooldown/mob_cooldown/charge/rust name = "Rust Charge" - desc = "A charge that must be started on a rusted tile and will destroy any rusted objects you come into contact with, will deal high damage to others and rust around you during the charge. As it is the rust that empoweres you for this ability, no focus is needed" + desc = "A charge that must be started on a rusted tile and will destroy any rusted objects you come into contact with, \ + will deal high damage to others and rust around you during the charge. \ + As it is the rust that empowers you with this ability, no focus is needed." charge_distance = 10 charge_damage = 50 cooldown_time = 45 SECONDS diff --git a/code/modules/antagonists/heretic/magic/star_blast.dm b/code/modules/antagonists/heretic/magic/star_blast.dm index 212e90535d6c7..48fdf2f26934b 100644 --- a/code/modules/antagonists/heretic/magic/star_blast.dm +++ b/code/modules/antagonists/heretic/magic/star_blast.dm @@ -1,6 +1,6 @@ /datum/action/cooldown/spell/pointed/projectile/star_blast name = "Star Blast" - desc = "This spell fires a disk with cosmic energies at a target." + desc = "This spell fires a disk with cosmic energies at a target, spreading the star mark." background_icon_state = "bg_heretic" overlay_icon_state = "bg_heretic_border" button_icon = 'icons/mob/actions/actions_ecult.dmi' diff --git a/code/modules/antagonists/heretic/magic/star_touch.dm b/code/modules/antagonists/heretic/magic/star_touch.dm index 9037d07295a94..89c5d02e7d498 100644 --- a/code/modules/antagonists/heretic/magic/star_touch.dm +++ b/code/modules/antagonists/heretic/magic/star_touch.dm @@ -1,7 +1,8 @@ /datum/action/cooldown/spell/touch/star_touch name = "Star Touch" - desc = "Marks someone with a star mark or puts someone with a star mark to sleep for 4 seconds, removing the star mark. \ - You and your target are linked with a cosmic ray, burning them for up to a minute, or \ + desc = "Manifests cosmic fields on tiles next to you while marking the victim with a star mark \ + or consuming an already present star mark to put them to sleep for 4 seconds. \ + They will then be linked to you with a cosmic ray, burning them for up to a minute, or \ until they can escape your sight. Star Touch can also remove Cosmic Runes, or teleport you \ to your Star Gazer when used on yourself." background_icon_state = "bg_heretic" diff --git a/code/modules/antagonists/heretic/status_effects/debuffs.dm b/code/modules/antagonists/heretic/status_effects/debuffs.dm index 08839fa8f1058..7037d1cc3778b 100644 --- a/code/modules/antagonists/heretic/status_effects/debuffs.dm +++ b/code/modules/antagonists/heretic/status_effects/debuffs.dm @@ -280,7 +280,7 @@ /datum/status_effect/moon_converted/on_remove() // Span warning and unconscious so they realize they aren't evil anymore - to_chat(owner, span_warning("Your mind is cleared from the effect of the manus, your alligiences are as they were before")) + to_chat(owner, span_warning("Your mind is cleared from the effect of the mansus, your alligiences are as they were before")) REMOVE_TRAIT(owner, TRAIT_MUTE, REF(src)) owner.AdjustUnconscious(5 SECONDS, ignore_canstun = FALSE) owner.log_message("[owner] is no longer insane.", LOG_GAME) diff --git a/code/modules/antagonists/heretic/structures/lock_final.dm b/code/modules/antagonists/heretic/structures/lock_final.dm index 8cb6c06f3cb01..759bc8aa55e39 100644 --- a/code/modules/antagonists/heretic/structures/lock_final.dm +++ b/code/modules/antagonists/heretic/structures/lock_final.dm @@ -37,7 +37,7 @@ /// Ask ghosts if they want to make some noise /obj/structure/lock_tear/proc/poll_ghosts() - var/list/candidates = SSpolling.poll_ghost_candidates("Would you like to be a random eldritch monster attacking the crew?", check_jobban = ROLE_SENTIENCE, role = ROLE_SENTIENCE, poll_time = 10 SECONDS, ignore_category = POLL_IGNORE_HERETIC_MONSTER, pic_source = src, role_name_text = "eldritch monster") + var/list/candidates = SSpolling.poll_ghost_candidates("Would you like to be a random [span_notice("eldritch monster")] attacking the crew?", check_jobban = ROLE_SENTIENCE, role = ROLE_SENTIENCE, poll_time = 10 SECONDS, ignore_category = POLL_IGNORE_HERETIC_MONSTER, alert_pic = src, role_name_text = "eldritch monster") while(LAZYLEN(candidates)) var/mob/dead/observer/candidate = pick_n_take(candidates) ghost_to_monster(candidate, should_ask = FALSE) diff --git a/code/modules/antagonists/malf_ai/malf_ai_modules.dm b/code/modules/antagonists/malf_ai/malf_ai_modules.dm index 7eb4aaba7bcfa..6a82c961d22d2 100644 --- a/code/modules/antagonists/malf_ai/malf_ai_modules.dm +++ b/code/modules/antagonists/malf_ai/malf_ai_modules.dm @@ -867,7 +867,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module)) /datum/ai_module/upgrade/upgrade_turrets/upgrade(mob/living/silicon/ai/AI) for(var/obj/machinery/porta_turret/ai/turret as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/porta_turret/ai)) - turret.AddElement(/datum/element/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES | EMP_PROTECT_CONTENTS) + turret.AddElement(/datum/element/empprotection, EMP_PROTECT_ALL) turret.max_integrity = 200 turret.repair_damage(200) turret.lethal_projectile = /obj/projectile/beam/laser/heavylaser //Once you see it, you will know what it means to FEAR. diff --git a/code/modules/antagonists/nightmare/nightmare_species.dm b/code/modules/antagonists/nightmare/nightmare_species.dm index 068bb2b6c5c1f..38db2dfae8657 100644 --- a/code/modules/antagonists/nightmare/nightmare_species.dm +++ b/code/modules/antagonists/nightmare/nightmare_species.dm @@ -39,7 +39,6 @@ . = ..() C.fully_replace_character_name(null, pick(GLOB.nightmare_names)) - C.set_safe_hunger_level() /datum/species/shadow/nightmare/check_roundstart_eligible() return FALSE diff --git a/code/modules/antagonists/nukeop/datums/operative.dm b/code/modules/antagonists/nukeop/datums/operative.dm new file mode 100644 index 0000000000000..516108c572513 --- /dev/null +++ b/code/modules/antagonists/nukeop/datums/operative.dm @@ -0,0 +1,213 @@ +/datum/antagonist/nukeop + name = ROLE_NUCLEAR_OPERATIVE + roundend_category = "syndicate operatives" //just in case + antagpanel_category = ANTAG_GROUP_SYNDICATE + job_rank = ROLE_OPERATIVE + antag_hud_name = "synd" + antag_moodlet = /datum/mood_event/focused + show_to_ghosts = TRUE + hijack_speed = 2 //If you can't take out the station, take the shuttle instead. + suicide_cry = "FOR THE SYNDICATE!!" + /// Which nukie team are we on? + var/datum/team/nuclear/nuke_team + /// If not assigned a team by default ops will try to join existing ones, set this to TRUE to always create new team. + var/always_new_team = FALSE + /// Should the user be moved to default spawnpoint after being granted this datum. + var/send_to_spawnpoint = TRUE + /// The DEFAULT outfit we will give to players granted this datum + var/nukeop_outfit = /datum/outfit/syndicate + + preview_outfit = /datum/outfit/nuclear_operative_elite + + /// In the preview icon, the nukies who are behind the leader + var/preview_outfit_behind = /datum/outfit/nuclear_operative + /// In the preview icon, a nuclear fission explosive device, only appearing if there's an icon state for it. + var/nuke_icon_state = "nuclearbomb_base" + + /// The amount of discounts that the team get + var/discount_team_amount = 5 + /// The amount of limited discounts that the team get + var/discount_limited_amount = 10 + +/datum/antagonist/nukeop/greet() + owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ops.ogg',100,0, use_reverb = FALSE) + to_chat(owner, span_big("You are a [nuke_team ? nuke_team.syndicate_name : "syndicate"] agent!")) + owner.announce_objectives() + +/datum/antagonist/nukeop/on_gain() + give_alias() + forge_objectives() + . = ..() + equip_op() + if(send_to_spawnpoint) + move_to_spawnpoint() + // grant extra TC for the people who start in the nukie base ie. not the lone op + var/extra_tc = CEILING(GLOB.joined_player_list.len/5, 5) + var/datum/component/uplink/uplink = owner.find_syndicate_uplink() + if (uplink) + uplink.uplink_handler.add_telecrystals(extra_tc) + + var/datum/component/uplink/uplink = owner.find_syndicate_uplink() + if(uplink) + var/datum/team/nuclear/nuke_team = get_team() + if(!nuke_team.team_discounts) + var/list/uplink_items = list() + for(var/datum/uplink_item/item as anything in SStraitor.uplink_items) + if(item.item && !item.cant_discount && (item.purchasable_from & uplink.uplink_handler.uplink_flag) && item.cost > 1) + uplink_items += item + nuke_team.team_discounts = list() + nuke_team.team_discounts += create_uplink_sales(discount_team_amount, /datum/uplink_category/discount_team_gear, -1, uplink_items) + nuke_team.team_discounts += create_uplink_sales(discount_limited_amount, /datum/uplink_category/limited_discount_team_gear, 1, uplink_items) + uplink.uplink_handler.extra_purchasable += nuke_team.team_discounts + + memorize_code() + +/datum/antagonist/nukeop/get_team() + return nuke_team + +/datum/antagonist/nukeop/apply_innate_effects(mob/living/mob_override) + add_team_hud(mob_override || owner.current, /datum/antagonist/nukeop) + +/datum/antagonist/nukeop/forge_objectives() + if(nuke_team) + objectives |= nuke_team.objectives + +/datum/antagonist/nukeop/leader/get_spawnpoint() + return pick(GLOB.nukeop_leader_start) + +/datum/antagonist/nukeop/create_team(datum/team/nuclear/new_team) + if(!new_team) + if(!always_new_team) + for(var/datum/antagonist/nukeop/N in GLOB.antagonists) + if(!N.owner) + stack_trace("Antagonist datum without owner in GLOB.antagonists: [N]") + continue + if(N.nuke_team) + nuke_team = N.nuke_team + return + nuke_team = new /datum/team/nuclear + nuke_team.update_objectives() + assign_nuke() //This is bit ugly + return + if(!istype(new_team)) + stack_trace("Wrong team type passed to [type] initialization.") + nuke_team = new_team + +/datum/antagonist/nukeop/admin_add(datum/mind/new_owner,mob/admin) + new_owner.set_assigned_role(SSjob.GetJobType(/datum/job/nuclear_operative)) + new_owner.add_antag_datum(src) + message_admins("[key_name_admin(admin)] has nuke op'ed [key_name_admin(new_owner)].") + log_admin("[key_name(admin)] has nuke op'ed [key_name(new_owner)].") + +/datum/antagonist/nukeop/get_admin_commands() + . = ..() + .["Send to base"] = CALLBACK(src, PROC_REF(admin_send_to_base)) + .["Tell code"] = CALLBACK(src, PROC_REF(admin_tell_code)) + +/datum/antagonist/nukeop/get_preview_icon() + if (!preview_outfit) + return null + + var/icon/final_icon = render_preview_outfit(preview_outfit) + + if (!isnull(preview_outfit_behind)) + var/icon/teammate = render_preview_outfit(preview_outfit_behind) + teammate.Blend(rgb(128, 128, 128, 128), ICON_MULTIPLY) + + final_icon.Blend(teammate, ICON_UNDERLAY, -world.icon_size / 4, 0) + final_icon.Blend(teammate, ICON_UNDERLAY, world.icon_size / 4, 0) + + if (!isnull(nuke_icon_state)) + var/icon/nuke = icon('icons/obj/machines/nuke.dmi', nuke_icon_state) + nuke.Shift(SOUTH, 6) + final_icon.Blend(nuke, ICON_OVERLAY) + + return finish_preview_icon(final_icon) + +/datum/antagonist/nukeop/proc/equip_op() + if(!ishuman(owner.current)) + return + + var/mob/living/carbon/human/operative = owner.current + ADD_TRAIT(operative, TRAIT_NOFEAR_HOLDUPS, INNATE_TRAIT) + + if(!nukeop_outfit) // this variable is null in instances where an antagonist datum is granted via enslaving the mind (/datum/mind/proc/enslave_mind_to_creator), like in golems. + return + + // If our nuke_ops_species pref is set to TRUE, (or we have no client) make us a human + if(isnull(operative.client) || operative.client.prefs.read_preference(/datum/preference/toggle/nuke_ops_species)) + operative.set_species(/datum/species/human) + + operative.equip_species_outfit(nukeop_outfit) + + return TRUE + +/datum/antagonist/nukeop/proc/admin_send_to_base(mob/admin) + owner.current.forceMove(pick(GLOB.nukeop_start)) + +/datum/antagonist/nukeop/proc/admin_tell_code(mob/admin) + var/code + for (var/obj/machinery/nuclearbomb/bombue as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/nuclearbomb)) + if (length(bombue.r_code) <= 5 && bombue.r_code != initial(bombue.r_code)) + code = bombue.r_code + break + if (code) + antag_memory += "Syndicate Nuclear Bomb Code: [code]
" + to_chat(owner.current, "The nuclear authorization code is: [code]") + else + to_chat(admin, span_danger("No valid nuke found!")) + +/datum/antagonist/nukeop/proc/assign_nuke() + if(!nuke_team || nuke_team.tracked_nuke) + return + nuke_team.memorized_code = random_nukecode() + var/obj/machinery/nuclearbomb/syndicate/nuke = locate() in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/nuclearbomb/syndicate) + if(!nuke) + stack_trace("Syndicate nuke not found during nuke team creation.") + nuke_team.memorized_code = null + return + nuke_team.tracked_nuke = nuke + if(nuke.r_code == NUKE_CODE_UNSET) + nuke.r_code = nuke_team.memorized_code + else //Already set by admins/something else? + nuke_team.memorized_code = nuke.r_code + for(var/obj/machinery/nuclearbomb/beer/beernuke as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/nuclearbomb/beer)) + beernuke.r_code = nuke_team.memorized_code + +/datum/antagonist/nukeop/proc/give_alias() + if(nuke_team?.syndicate_name) + var/mob/living/carbon/human/human_to_rename = owner.current + if(istype(human_to_rename)) // Reinforcements get a real name + var/first_name = owner.current.client?.prefs?.read_preference(/datum/preference/name/operative_alias) || pick(GLOB.operative_aliases) + var/chosen_name = "[first_name] [nuke_team.syndicate_name]" + human_to_rename.fully_replace_character_name(human_to_rename.real_name, chosen_name) + else + var/number = 1 + number = nuke_team.members.Find(owner) + owner.current.real_name = "[nuke_team.syndicate_name] Operative #[number]" + +/datum/antagonist/nukeop/proc/memorize_code() + if(nuke_team && nuke_team.tracked_nuke && nuke_team.memorized_code) + antag_memory += "[nuke_team.tracked_nuke] Code: [nuke_team.memorized_code]
" + owner.add_memory(/datum/memory/key/nuke_code, nuclear_code = nuke_team.memorized_code) + to_chat(owner, "The nuclear authorization code is: [nuke_team.memorized_code]") + else + to_chat(owner, "Unfortunately the syndicate was unable to provide you with nuclear authorization code.") + +/// Actually moves our nukie to where they should be +/datum/antagonist/nukeop/proc/move_to_spawnpoint() + // Ensure that the nukiebase is loaded, and wait for it if required + SSmapping.lazy_load_template(LAZY_TEMPLATE_KEY_NUKIEBASE) + var/turf/destination = get_spawnpoint() + owner.current.forceMove(destination) + if(!owner.current.onSyndieBase()) + message_admins("[ADMIN_LOOKUPFLW(owner.current)] is a NUKE OP and move_to_spawnpoint put them somewhere that isn't the syndie base, help please.") + stack_trace("Nuke op move_to_spawnpoint resulted in a location not on the syndicate base. (Was moved to: [destination])") + +/// Gets the position we spawn at +/datum/antagonist/nukeop/proc/get_spawnpoint() + var/team_number = 1 + if(nuke_team) + team_number = nuke_team.members.Find(owner) + + return GLOB.nukeop_start[((team_number - 1) % GLOB.nukeop_start.len) + 1] diff --git a/code/modules/antagonists/nukeop/datums/operative_leader.dm b/code/modules/antagonists/nukeop/datums/operative_leader.dm new file mode 100644 index 0000000000000..76ca635158b16 --- /dev/null +++ b/code/modules/antagonists/nukeop/datums/operative_leader.dm @@ -0,0 +1,55 @@ +/datum/antagonist/nukeop/leader + name = "Nuclear Operative Leader" + nukeop_outfit = /datum/outfit/syndicate/leader + always_new_team = TRUE + /// Randomly chosen honorific, for distinction + var/title + /// The nuclear challenge remote we will spawn this player with. + var/challengeitem = /obj/item/nuclear_challenge + +/datum/antagonist/nukeop/leader/memorize_code() + ..() + if(nuke_team?.memorized_code) + var/obj/item/paper/nuke_code_paper = new + nuke_code_paper.add_raw_text("The nuclear authorization code is: [nuke_team.memorized_code]") + nuke_code_paper.name = "nuclear bomb code" + var/mob/living/carbon/human/H = owner.current + if(!istype(H)) + nuke_code_paper.forceMove(get_turf(H)) + else + H.put_in_hands(nuke_code_paper, TRUE) + H.update_icons() + +/datum/antagonist/nukeop/leader/greet() + owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ops.ogg',100,0, use_reverb = FALSE) + to_chat(owner, "You are the Syndicate [title] for this mission. You are responsible for guiding the team and your ID is the only one who can open the launch bay doors.") + to_chat(owner, "If you feel you are not up to this task, give your ID and radio to another operative.") + if(!CONFIG_GET(flag/disable_warops)) + to_chat(owner, "In your hand you will find a special item capable of triggering a greater challenge for your team. Examine it carefully and consult with your fellow operatives before activating it.") + owner.announce_objectives() + +/datum/antagonist/nukeop/leader/on_gain() + . = ..() + if(!CONFIG_GET(flag/disable_warops)) + var/mob/living/carbon/human/leader = owner.current + var/obj/item/war_declaration = new challengeitem(leader.drop_location()) + leader.put_in_hands(war_declaration) + nuke_team.war_button_ref = WEAKREF(war_declaration) + addtimer(CALLBACK(src, PROC_REF(nuketeam_name_assign)), 1) + +/datum/antagonist/nukeop/leader/proc/nuketeam_name_assign() + if(!nuke_team) + return + nuke_team.rename_team(ask_name()) + +/datum/antagonist/nukeop/leader/proc/ask_name() + var/randomname = pick(GLOB.last_names) + var/newname = tgui_input_text(owner.current, "You are the nuclear operative [title]. Please choose a last name for your family.", "Name change", randomname, MAX_NAME_LEN) + if (!newname) + newname = randomname + else + newname = reject_bad_name(newname) + if(!newname) + newname = randomname + + return capitalize(newname) diff --git a/code/modules/antagonists/nukeop/datums/operative_lone.dm b/code/modules/antagonists/nukeop/datums/operative_lone.dm new file mode 100644 index 0000000000000..d0bc718a781b0 --- /dev/null +++ b/code/modules/antagonists/nukeop/datums/operative_lone.dm @@ -0,0 +1,22 @@ +/datum/antagonist/nukeop/lone + name = "Lone Operative" + always_new_team = TRUE + send_to_spawnpoint = FALSE //Handled by event + nukeop_outfit = /datum/outfit/syndicate/full + preview_outfit = /datum/outfit/nuclear_operative + preview_outfit_behind = null + nuke_icon_state = null + +/datum/antagonist/nukeop/lone/assign_nuke() + if(nuke_team && !nuke_team.tracked_nuke) + nuke_team.memorized_code = random_nukecode() + var/obj/machinery/nuclearbomb/selfdestruct/nuke = locate() in SSmachines.get_machines_by_type(/obj/machinery/nuclearbomb/selfdestruct) + if(nuke) + nuke_team.tracked_nuke = nuke + if(nuke.r_code == NUKE_CODE_UNSET) + nuke.r_code = nuke_team.memorized_code + else //Already set by admins/something else? + nuke_team.memorized_code = nuke.r_code + else + stack_trace("Station self-destruct not found during lone op team creation.") + nuke_team.memorized_code = null diff --git a/code/modules/antagonists/nukeop/datums/operative_reinforcement.dm b/code/modules/antagonists/nukeop/datums/operative_reinforcement.dm new file mode 100644 index 0000000000000..eb386342c09a4 --- /dev/null +++ b/code/modules/antagonists/nukeop/datums/operative_reinforcement.dm @@ -0,0 +1,8 @@ +/datum/antagonist/nukeop/reinforcement + name = "Nuclear Operative Reinforcement" + show_in_antagpanel = FALSE + send_to_spawnpoint = FALSE + nukeop_outfit = /datum/outfit/syndicate/reinforcement + +/datum/antagonist/nukeop/reinforcement/cyborg + name = "Nuclear Operative Support Cyborg" diff --git a/code/modules/antagonists/nukeop/datums/operative_team.dm b/code/modules/antagonists/nukeop/datums/operative_team.dm new file mode 100644 index 0000000000000..9bec3b0fcf0e1 --- /dev/null +++ b/code/modules/antagonists/nukeop/datums/operative_team.dm @@ -0,0 +1,317 @@ +#define SPAWN_AT_BASE "Nuke base" +#define SPAWN_AT_INFILTRATOR "Infiltrator" + +/datum/team/nuclear + var/syndicate_name + var/obj/machinery/nuclearbomb/tracked_nuke + var/core_objective = /datum/objective/nuclear + var/memorized_code + var/list/team_discounts + var/datum/weakref/war_button_ref + +/datum/team/nuclear/New() + ..() + syndicate_name = syndicate_name() + +/datum/team/nuclear/roundend_report() + var/list/parts = list() + parts += "[syndicate_name] Operatives:" + + switch(get_result()) + if(NUKE_RESULT_FLUKE) + parts += "Humiliating Syndicate Defeat" + parts += "The crew of [station_name()] gave [syndicate_name] operatives back their bomb! The syndicate base was destroyed! Next time, don't lose the nuke!" + if(NUKE_RESULT_NUKE_WIN) + parts += "Syndicate Major Victory!" + parts += "[syndicate_name] operatives have destroyed [station_name()]!" + if(NUKE_RESULT_NOSURVIVORS) + parts += "Total Annihilation!" + parts += "[syndicate_name] operatives destroyed [station_name()] but did not leave the area in time and got caught in the explosion. Next time, don't lose the disk!" + if(NUKE_RESULT_WRONG_STATION) + parts += "Crew Minor Victory!" + parts += "[syndicate_name] operatives secured the authentication disk but blew up something that wasn't [station_name()]. Next time, don't do that!" + if(NUKE_RESULT_WRONG_STATION_DEAD) + parts += "[syndicate_name] operatives have earned Darwin Award!" + parts += "[syndicate_name] operatives blew up something that wasn't [station_name()] and got caught in the explosion. Next time, don't do that!" + if(NUKE_RESULT_HIJACK_DISK) + parts += "Syndicate Miniscule Victory!" + parts += "[syndicate_name] operatives failed to destroy [station_name()], but they managed to secure the disk and hijack the emergency shuttle, causing it to land on the syndicate base. Good job?" + if(NUKE_RESULT_HIJACK_NO_DISK) + parts += "Syndicate Insignificant Victory!" + parts += "[syndicate_name] operatives failed to destroy [station_name()] or secure the disk, but they managed to hijack the emergency shuttle, causing it to land on the syndicate base. Good job?" + if(NUKE_RESULT_CREW_WIN_SYNDIES_DEAD) + parts += "Crew Major Victory!" + parts += "The Research Staff has saved the disk and killed the [syndicate_name] Operatives" + if(NUKE_RESULT_CREW_WIN) + parts += "Crew Major Victory!" + parts += "The Research Staff has saved the disk and stopped the [syndicate_name] Operatives!" + if(NUKE_RESULT_DISK_LOST) + parts += "Neutral Victory!" + parts += "The Research Staff failed to secure the authentication disk but did manage to kill most of the [syndicate_name] Operatives!" + if(NUKE_RESULT_DISK_STOLEN) + parts += "Syndicate Minor Victory!" + parts += "[syndicate_name] operatives survived the assault but did not achieve the destruction of [station_name()]. Next time, don't lose the disk!" + else + parts += "Neutral Victory" + parts += "Mission aborted!" + + var/text = "
The syndicate operatives were:" + var/purchases = "" + var/TC_uses = 0 + LAZYINITLIST(GLOB.uplink_purchase_logs_by_key) + for(var/I in members) + var/datum/mind/syndicate = I + var/datum/uplink_purchase_log/H = GLOB.uplink_purchase_logs_by_key[syndicate.key] + if(H) + TC_uses += H.total_spent + purchases += H.generate_render(show_key = FALSE) + text += printplayerlist(members) + text += "
" + text += "(Syndicates used [TC_uses] TC) [purchases]" + if(TC_uses == 0 && GLOB.station_was_nuked && !are_all_operatives_dead()) + text += "[icon2html('icons/ui_icons/antags/badass.dmi', world, "badass")]" + + parts += text + + return "
[parts.Join("
")]
" + +/datum/team/nuclear/antag_listing_name() + if(syndicate_name) + return "[syndicate_name] Syndicates" + else + return "Syndicates" + +/datum/team/nuclear/antag_listing_entry() + var/disk_report = "Nuclear Disk(s)
" + disk_report += "" + for(var/obj/item/disk/nuclear/N in SSpoints_of_interest.real_nuclear_disks) + disk_report += "" + disk_report += "
[N.name], " + var/atom/disk_loc = N.loc + while(!isturf(disk_loc)) + if(ismob(disk_loc)) + var/mob/M = disk_loc + disk_report += "carried by [M.real_name] " + if(isobj(disk_loc)) + var/obj/O = disk_loc + disk_report += "in \a [O.name] " + disk_loc = disk_loc.loc + disk_report += "in [disk_loc.loc] at ([disk_loc.x], [disk_loc.y], [disk_loc.z])FLW
" + + var/post_report + + var/war_declared = FALSE + for(var/obj/item/circuitboard/computer/syndicate_shuttle/board as anything in GLOB.syndicate_shuttle_boards) + if(board.challenge) + war_declared = TRUE + + var/force_war_button = "" + + if(war_declared) + post_report += "War declared." + force_war_button = "\[Force war\]" + else + post_report += "War not declared." + var/obj/item/nuclear_challenge/war_button = war_button_ref?.resolve() + if(war_button) + force_war_button = "\[Force war\]" + else + force_war_button = "\[Cannot declare war, challenge button missing!\]" + + post_report += "\n[force_war_button]" + post_report += "\n\[Send Reinforcement\]" + + var/final_report = ..() + final_report += disk_report + final_report += post_report + return final_report + +/datum/team/nuclear/proc/rename_team(new_name) + syndicate_name = new_name + name = "[syndicate_name] Team" + for(var/I in members) + var/datum/mind/synd_mind = I + var/mob/living/carbon/human/human_to_rename = synd_mind.current + if(!istype(human_to_rename)) + continue + var/first_name = human_to_rename.client?.prefs?.read_preference(/datum/preference/name/operative_alias) || pick(GLOB.operative_aliases) + var/chosen_name = "[first_name] [syndicate_name]" + human_to_rename.fully_replace_character_name(human_to_rename.real_name, chosen_name) + +/datum/team/nuclear/proc/admin_spawn_reinforcement(mob/admin) + if(!check_rights_for(admin.client, R_ADMIN)) + return + + var/infil_or_nukebase = tgui_alert( + admin, + "Spawn them at the nuke base, or in the Infiltrator?", + "Where to reinforce?", + list(SPAWN_AT_BASE, SPAWN_AT_INFILTRATOR, "Cancel"), + ) + + if(!infil_or_nukebase || infil_or_nukebase == "Cancel") + return + + var/tc_to_spawn = tgui_input_number(admin, "How much TC to spawn with?", "TC", 0, 100) + + var/mob/chosen_one = SSpolling.poll_ghost_candidates( + check_jobban = ROLE_OPERATIVE, + role = ROLE_OPERATIVE, + poll_time = 30 SECONDS, + ignore_category = POLL_IGNORE_SYNDICATE, + alert_pic = /obj/structure/sign/poster/contraband/gorlex_recruitment, + role_name_text = "emergency syndicate reinforcement", + amount_to_pick = 1, + ) + + if(isnull(chosen_one)) + tgui_alert(admin, "No candidates found.", "Recruitment Shortage", list("OK")) + return + + + var/turf/spawn_loc + if(infil_or_nukebase == SPAWN_AT_INFILTRATOR) + var/area/spawn_in + // Prioritize EVA then hallway, if neither can be found default to the first area we can find + for(var/area_type in list(/area/shuttle/syndicate/eva, /area/shuttle/syndicate/hallway, /area/shuttle/syndicate)) + spawn_in = locate(area_type) in GLOB.areas // I'd love to use areas_by_type but the Infiltrator is a unique area + if(spawn_in) + break + + var/list/turf/options = list() + for(var/turf/open/open_turf in spawn_in?.get_turfs_from_all_zlevels()) + if(open_turf.is_blocked_turf()) + continue + options += open_turf + + if(length(options)) + spawn_loc = pick(options) + else + infil_or_nukebase = SPAWN_AT_BASE + + if(infil_or_nukebase == SPAWN_AT_BASE) + spawn_loc = pick(GLOB.nukeop_start) + + var/mob/living/carbon/human/nukie = new(spawn_loc) + chosen_one.client.prefs.safe_transfer_prefs_to(nukie, is_antag = TRUE) + nukie.key = chosen_one.key + + var/datum/antagonist/nukeop/antag_datum = new() + antag_datum.send_to_spawnpoint = FALSE + antag_datum.nukeop_outfit = /datum/outfit/syndicate/reinforcement + + nukie.mind.add_antag_datum(antag_datum, src) + + var/datum/component/uplink/uplink = nukie.mind.find_syndicate_uplink() + uplink?.uplink_handler.set_telecrystals(tc_to_spawn) + + // add some pizzazz + do_sparks(4, FALSE, spawn_loc) + new /obj/effect/temp_visual/teleport_abductor/syndi_teleporter(spawn_loc) + playsound(spawn_loc, SFX_SPARKS, 50, TRUE) + playsound(spawn_loc, 'sound/effects/phasein.ogg', 50, TRUE) + + tgui_alert(admin, "Reinforcement spawned at [infil_or_nukebase] with [tc_to_spawn].", "Reinforcements have arrived", list("God speed")) + +/datum/team/nuclear/proc/update_objectives() + if(core_objective) + var/datum/objective/O = new core_objective + O.team = src + objectives += O + +/datum/team/nuclear/proc/is_disk_rescued() + for(var/obj/item/disk/nuclear/nuke_disk in SSpoints_of_interest.real_nuclear_disks) + //If emergency shuttle is in transit disk is only safe on it + if(SSshuttle.emergency.mode == SHUTTLE_ESCAPE) + if(!SSshuttle.emergency.is_in_shuttle_bounds(nuke_disk)) + return FALSE + //If shuttle escaped check if it's on centcom side + else if(SSshuttle.emergency.mode == SHUTTLE_ENDGAME) + if(!nuke_disk.onCentCom()) + return FALSE + else //Otherwise disk is safe when on station + var/turf/disk_turf = get_turf(nuke_disk) + if(!disk_turf || !is_station_level(disk_turf.z)) + return FALSE + return TRUE + +/datum/team/nuclear/proc/are_all_operatives_dead() + for(var/datum/mind/operative_mind as anything in members) + if(ishuman(operative_mind.current) && (operative_mind.current.stat != DEAD)) + return FALSE + return TRUE + +/datum/team/nuclear/proc/get_result() + var/shuttle_evacuated = EMERGENCY_ESCAPED_OR_ENDGAMED + var/shuttle_landed_base = SSshuttle.emergency.is_hijacked() + var/disk_rescued = is_disk_rescued() + var/syndies_didnt_escape = !is_infiltrator_docked_at_syndiebase() + var/team_is_dead = are_all_operatives_dead() + var/station_was_nuked = GLOB.station_was_nuked + var/station_nuke_source = GLOB.station_nuke_source + + // The nuke detonated on the syndicate base + if(station_nuke_source == DETONATION_HIT_SYNDIE_BASE) + return NUKE_RESULT_FLUKE + + // The station was nuked + if(station_was_nuked) + // The station was nuked and the infiltrator failed to escape + if(syndies_didnt_escape) + return NUKE_RESULT_NOSURVIVORS + // The station was nuked and the infiltrator escaped, and the nuke ops won + else + return NUKE_RESULT_NUKE_WIN + + // The station was not nuked, but something was + else if(station_nuke_source && !disk_rescued) + // The station was not nuked, but something was, and the syndicates didn't escape it + if(syndies_didnt_escape) + return NUKE_RESULT_WRONG_STATION_DEAD + // The station was not nuked, but something was, and the syndicates returned to their base + else + return NUKE_RESULT_WRONG_STATION + + // Nuke didn't blow, but nukies somehow hijacked the emergency shuttle to land at the base anyways. + else if(shuttle_landed_base) + if(disk_rescued) + return NUKE_RESULT_HIJACK_DISK + else + return NUKE_RESULT_HIJACK_NO_DISK + + // No nuke went off, the station rescued the disk + else if(disk_rescued) + // No nuke went off, the shuttle left, and the team is dead + if(shuttle_evacuated && team_is_dead) + return NUKE_RESULT_CREW_WIN_SYNDIES_DEAD + // No nuke went off, but the nuke ops survived + else + return NUKE_RESULT_CREW_WIN + + // No nuke went off, but the disk was left behind + else + // No nuke went off, the disk was left, but all the ops are dead + if(team_is_dead) + return NUKE_RESULT_DISK_LOST + // No nuke went off, the disk was left, there are living ops, but the shuttle left successfully + else if(shuttle_evacuated) + return NUKE_RESULT_DISK_STOLEN + + CRASH("[type] - got an undefined / unexpected result.") + +/// Returns whether or not syndicate operatives escaped. +/proc/is_infiltrator_docked_at_syndiebase() + var/obj/docking_port/mobile/infiltrator/infiltrator_port = SSshuttle.getShuttle("syndicate") + + var/datum/lazy_template/nukie_base/nukie_template = GLOB.lazy_templates[LAZY_TEMPLATE_KEY_NUKIEBASE] + if(!nukie_template) + return FALSE // if its not even loaded, cant be docked + + for(var/datum/turf_reservation/loaded_area as anything in nukie_template.reservations) + var/infiltrator_turf = get_turf(infiltrator_port) + if(infiltrator_turf in loaded_area.reserved_turfs) + return TRUE + return FALSE + +#undef SPAWN_AT_BASE +#undef SPAWN_AT_INFILTRATOR diff --git a/code/modules/antagonists/nukeop/equipment/nuclear_authentication_disk.dm b/code/modules/antagonists/nukeop/equipment/nuclear_authentication_disk.dm index 72c51f14b2b99..c318679b4f6fe 100644 --- a/code/modules/antagonists/nukeop/equipment/nuclear_authentication_disk.dm +++ b/code/modules/antagonists/nukeop/equipment/nuclear_authentication_disk.dm @@ -26,7 +26,7 @@ /obj/item/disk/nuclear/Initialize(mapload) . = ..() - AddElement(/datum/element/bed_tuckable, 6, -6, 0) + AddElement(/datum/element/bed_tuckable, mapload, 6, -6, 0) AddComponent(/datum/component/stationloving, !fake) if(!fake) diff --git a/code/modules/antagonists/nukeop/nukeop.dm b/code/modules/antagonists/nukeop/nukeop.dm deleted file mode 100644 index 6278d5ddaea91..0000000000000 --- a/code/modules/antagonists/nukeop/nukeop.dm +++ /dev/null @@ -1,645 +0,0 @@ -/datum/antagonist/nukeop - name = ROLE_NUCLEAR_OPERATIVE - roundend_category = "syndicate operatives" //just in case - antagpanel_category = ANTAG_GROUP_SYNDICATE - job_rank = ROLE_OPERATIVE - antag_hud_name = "synd" - antag_moodlet = /datum/mood_event/focused - show_to_ghosts = TRUE - hijack_speed = 2 //If you can't take out the station, take the shuttle instead. - suicide_cry = "FOR THE SYNDICATE!!" - /// Which nukie team are we on? - var/datum/team/nuclear/nuke_team - /// If not assigned a team by default ops will try to join existing ones, set this to TRUE to always create new team. - var/always_new_team = FALSE - /// Should the user be moved to default spawnpoint after being granted this datum. - var/send_to_spawnpoint = TRUE - /// The DEFAULT outfit we will give to players granted this datum - var/nukeop_outfit = /datum/outfit/syndicate - - preview_outfit = /datum/outfit/nuclear_operative_elite - - /// In the preview icon, the nukies who are behind the leader - var/preview_outfit_behind = /datum/outfit/nuclear_operative - /// In the preview icon, a nuclear fission explosive device, only appearing if there's an icon state for it. - var/nuke_icon_state = "nuclearbomb_base" - - /// The amount of discounts that the team get - var/discount_team_amount = 5 - /// The amount of limited discounts that the team get - var/discount_limited_amount = 10 - -/datum/antagonist/nukeop/proc/equip_op() - if(!ishuman(owner.current)) - return - - var/mob/living/carbon/human/operative = owner.current - ADD_TRAIT(operative, TRAIT_NOFEAR_HOLDUPS, INNATE_TRAIT) - - if(!nukeop_outfit) // this variable is null in instances where an antagonist datum is granted via enslaving the mind (/datum/mind/proc/enslave_mind_to_creator), like in golems. - return - - // If our nuke_ops_species pref is set to TRUE, (or we have no client) make us a human - if(isnull(operative.client) || operative.client.prefs.read_preference(/datum/preference/toggle/nuke_ops_species)) - operative.set_species(/datum/species/human) - - operative.equip_species_outfit(nukeop_outfit) - - return TRUE - -/datum/antagonist/nukeop/greet() - owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ops.ogg',100,0, use_reverb = FALSE) - to_chat(owner, span_big("You are a [nuke_team ? nuke_team.syndicate_name : "syndicate"] agent!")) - owner.announce_objectives() - -/datum/antagonist/nukeop/on_gain() - give_alias() - forge_objectives() - . = ..() - equip_op() - if(send_to_spawnpoint) - move_to_spawnpoint() - // grant extra TC for the people who start in the nukie base ie. not the lone op - var/extra_tc = CEILING(GLOB.joined_player_list.len/5, 5) - var/datum/component/uplink/uplink = owner.find_syndicate_uplink() - if (uplink) - uplink.uplink_handler.add_telecrystals(extra_tc) - - var/datum/component/uplink/uplink = owner.find_syndicate_uplink() - if(uplink) - var/datum/team/nuclear/nuke_team = get_team() - if(!nuke_team.team_discounts) - var/list/uplink_items = list() - for(var/datum/uplink_item/item as anything in SStraitor.uplink_items) - if(item.item && !item.cant_discount && (item.purchasable_from & uplink.uplink_handler.uplink_flag) && item.cost > 1) - uplink_items += item - nuke_team.team_discounts = list() - nuke_team.team_discounts += create_uplink_sales(discount_team_amount, /datum/uplink_category/discount_team_gear, -1, uplink_items) - nuke_team.team_discounts += create_uplink_sales(discount_limited_amount, /datum/uplink_category/limited_discount_team_gear, 1, uplink_items) - uplink.uplink_handler.extra_purchasable += nuke_team.team_discounts - - memorize_code() - -/datum/antagonist/nukeop/get_team() - return nuke_team - -/datum/antagonist/nukeop/apply_innate_effects(mob/living/mob_override) - add_team_hud(mob_override || owner.current, /datum/antagonist/nukeop) - -/datum/antagonist/nukeop/proc/assign_nuke() - if(!nuke_team || nuke_team.tracked_nuke) - return - nuke_team.memorized_code = random_nukecode() - var/obj/machinery/nuclearbomb/syndicate/nuke = locate() in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/nuclearbomb/syndicate) - if(!nuke) - stack_trace("Syndicate nuke not found during nuke team creation.") - nuke_team.memorized_code = null - return - nuke_team.tracked_nuke = nuke - if(nuke.r_code == NUKE_CODE_UNSET) - nuke.r_code = nuke_team.memorized_code - else //Already set by admins/something else? - nuke_team.memorized_code = nuke.r_code - for(var/obj/machinery/nuclearbomb/beer/beernuke as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/nuclearbomb/beer)) - beernuke.r_code = nuke_team.memorized_code - -/datum/antagonist/nukeop/proc/give_alias() - if(nuke_team?.syndicate_name) - var/mob/living/carbon/human/human_to_rename = owner.current - if(istype(human_to_rename)) // Reinforcements get a real name - var/first_name = owner.current.client?.prefs?.read_preference(/datum/preference/name/operative_alias) || pick(GLOB.operative_aliases) - var/chosen_name = "[first_name] [nuke_team.syndicate_name]" - human_to_rename.fully_replace_character_name(human_to_rename.real_name, chosen_name) - else - var/number = 1 - number = nuke_team.members.Find(owner) - owner.current.real_name = "[nuke_team.syndicate_name] Operative #[number]" - -/datum/antagonist/nukeop/proc/memorize_code() - if(nuke_team && nuke_team.tracked_nuke && nuke_team.memorized_code) - antag_memory += "[nuke_team.tracked_nuke] Code: [nuke_team.memorized_code]
" - owner.add_memory(/datum/memory/key/nuke_code, nuclear_code = nuke_team.memorized_code) - to_chat(owner, "The nuclear authorization code is: [nuke_team.memorized_code]") - else - to_chat(owner, "Unfortunately the syndicate was unable to provide you with nuclear authorization code.") - -/datum/antagonist/nukeop/forge_objectives() - if(nuke_team) - objectives |= nuke_team.objectives - -/// Actually moves our nukie to where they should be -/datum/antagonist/nukeop/proc/move_to_spawnpoint() - // Ensure that the nukiebase is loaded, and wait for it if required - SSmapping.lazy_load_template(LAZY_TEMPLATE_KEY_NUKIEBASE) - var/turf/destination = get_spawnpoint() - owner.current.forceMove(destination) - if(!owner.current.onSyndieBase()) - message_admins("[ADMIN_LOOKUPFLW(owner.current)] is a NUKE OP and move_to_spawnpoint put them somewhere that isn't the syndie base, help please.") - stack_trace("Nuke op move_to_spawnpoint resulted in a location not on the syndicate base. (Was moved to: [destination])") - -/// Gets the position we spawn at -/datum/antagonist/nukeop/proc/get_spawnpoint() - var/team_number = 1 - if(nuke_team) - team_number = nuke_team.members.Find(owner) - - return GLOB.nukeop_start[((team_number - 1) % GLOB.nukeop_start.len) + 1] - -/datum/antagonist/nukeop/leader/get_spawnpoint() - return pick(GLOB.nukeop_leader_start) - -/datum/antagonist/nukeop/create_team(datum/team/nuclear/new_team) - if(!new_team) - if(!always_new_team) - for(var/datum/antagonist/nukeop/N in GLOB.antagonists) - if(!N.owner) - stack_trace("Antagonist datum without owner in GLOB.antagonists: [N]") - continue - if(N.nuke_team) - nuke_team = N.nuke_team - return - nuke_team = new /datum/team/nuclear - nuke_team.update_objectives() - assign_nuke() //This is bit ugly - return - if(!istype(new_team)) - stack_trace("Wrong team type passed to [type] initialization.") - nuke_team = new_team - -/datum/antagonist/nukeop/admin_add(datum/mind/new_owner,mob/admin) - new_owner.set_assigned_role(SSjob.GetJobType(/datum/job/nuclear_operative)) - new_owner.add_antag_datum(src) - message_admins("[key_name_admin(admin)] has nuke op'ed [key_name_admin(new_owner)].") - log_admin("[key_name(admin)] has nuke op'ed [key_name(new_owner)].") - -/datum/antagonist/nukeop/get_admin_commands() - . = ..() - .["Send to base"] = CALLBACK(src, PROC_REF(admin_send_to_base)) - .["Tell code"] = CALLBACK(src, PROC_REF(admin_tell_code)) - -/datum/antagonist/nukeop/proc/admin_send_to_base(mob/admin) - owner.current.forceMove(pick(GLOB.nukeop_start)) - -/datum/antagonist/nukeop/proc/admin_tell_code(mob/admin) - var/code - for (var/obj/machinery/nuclearbomb/bombue as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/nuclearbomb)) - if (length(bombue.r_code) <= 5 && bombue.r_code != initial(bombue.r_code)) - code = bombue.r_code - break - if (code) - antag_memory += "Syndicate Nuclear Bomb Code: [code]
" - to_chat(owner.current, "The nuclear authorization code is: [code]") - else - to_chat(admin, span_danger("No valid nuke found!")) - -/datum/antagonist/nukeop/get_preview_icon() - if (!preview_outfit) - return null - - var/icon/final_icon = render_preview_outfit(preview_outfit) - - if (!isnull(preview_outfit_behind)) - var/icon/teammate = render_preview_outfit(preview_outfit_behind) - teammate.Blend(rgb(128, 128, 128, 128), ICON_MULTIPLY) - - final_icon.Blend(teammate, ICON_UNDERLAY, -world.icon_size / 4, 0) - final_icon.Blend(teammate, ICON_UNDERLAY, world.icon_size / 4, 0) - - if (!isnull(nuke_icon_state)) - var/icon/nuke = icon('icons/obj/machines/nuke.dmi', nuke_icon_state) - nuke.Shift(SOUTH, 6) - final_icon.Blend(nuke, ICON_OVERLAY) - - return finish_preview_icon(final_icon) - -/datum/outfit/nuclear_operative - name = "Nuclear Operative (Preview only)" - - back = /obj/item/mod/control/pre_equipped/empty/syndicate - uniform = /obj/item/clothing/under/syndicate - -/datum/outfit/nuclear_operative/post_equip(mob/living/carbon/human/H, visualsOnly) - var/obj/item/mod/module/armor_booster/booster = locate() in H.back - booster.active = TRUE - H.update_worn_back() - -/datum/outfit/nuclear_operative_elite - name = "Nuclear Operative (Elite, Preview only)" - - back = /obj/item/mod/control/pre_equipped/empty/elite - uniform = /obj/item/clothing/under/syndicate - l_hand = /obj/item/modular_computer/pda/nukeops - r_hand = /obj/item/shield/energy - -/datum/outfit/nuclear_operative_elite/post_equip(mob/living/carbon/human/H, visualsOnly) - var/obj/item/mod/module/armor_booster/booster = locate() in H.back - booster.active = TRUE - H.update_worn_back() - var/obj/item/shield/energy/shield = locate() in H.held_items - shield.icon_state = "[shield.base_icon_state]1" - H.update_held_items() - -/datum/antagonist/nukeop/leader - name = "Nuclear Operative Leader" - nukeop_outfit = /datum/outfit/syndicate/leader - always_new_team = TRUE - /// Randomly chosen honorific, for distinction - var/title - /// The nuclear challenge remote we will spawn this player with. - var/challengeitem = /obj/item/nuclear_challenge - -/datum/antagonist/nukeop/leader/memorize_code() - ..() - if(nuke_team?.memorized_code) - var/obj/item/paper/nuke_code_paper = new - nuke_code_paper.add_raw_text("The nuclear authorization code is: [nuke_team.memorized_code]") - nuke_code_paper.name = "nuclear bomb code" - var/mob/living/carbon/human/H = owner.current - if(!istype(H)) - nuke_code_paper.forceMove(get_turf(H)) - else - H.put_in_hands(nuke_code_paper, TRUE) - H.update_icons() - -/datum/antagonist/nukeop/leader/greet() - owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ops.ogg',100,0, use_reverb = FALSE) - to_chat(owner, "You are the Syndicate [title] for this mission. You are responsible for guiding the team and your ID is the only one who can open the launch bay doors.") - to_chat(owner, "If you feel you are not up to this task, give your ID and radio to another operative.") - if(!CONFIG_GET(flag/disable_warops)) - to_chat(owner, "In your hand you will find a special item capable of triggering a greater challenge for your team. Examine it carefully and consult with your fellow operatives before activating it.") - owner.announce_objectives() - -/datum/antagonist/nukeop/leader/on_gain() - . = ..() - if(!CONFIG_GET(flag/disable_warops)) - var/mob/living/carbon/human/leader = owner.current - var/obj/item/war_declaration = new challengeitem(leader.drop_location()) - leader.put_in_hands(war_declaration) - nuke_team.war_button_ref = WEAKREF(war_declaration) - addtimer(CALLBACK(src, PROC_REF(nuketeam_name_assign)), 1) - -/datum/antagonist/nukeop/leader/proc/nuketeam_name_assign() - if(!nuke_team) - return - nuke_team.rename_team(ask_name()) - -/datum/team/nuclear/proc/rename_team(new_name) - syndicate_name = new_name - name = "[syndicate_name] Team" - for(var/I in members) - var/datum/mind/synd_mind = I - var/mob/living/carbon/human/human_to_rename = synd_mind.current - if(!istype(human_to_rename)) - continue - var/first_name = human_to_rename.client?.prefs?.read_preference(/datum/preference/name/operative_alias) || pick(GLOB.operative_aliases) - var/chosen_name = "[first_name] [syndicate_name]" - human_to_rename.fully_replace_character_name(human_to_rename.real_name, chosen_name) - -/datum/antagonist/nukeop/leader/proc/ask_name() - var/randomname = pick(GLOB.last_names) - var/newname = tgui_input_text(owner.current, "You are the nuclear operative [title]. Please choose a last name for your family.", "Name change", randomname, MAX_NAME_LEN) - if (!newname) - newname = randomname - else - newname = reject_bad_name(newname) - if(!newname) - newname = randomname - - return capitalize(newname) - -/datum/antagonist/nukeop/lone - name = "Lone Operative" - always_new_team = TRUE - send_to_spawnpoint = FALSE //Handled by event - nukeop_outfit = /datum/outfit/syndicate/full - preview_outfit = /datum/outfit/nuclear_operative - preview_outfit_behind = null - nuke_icon_state = null - -/datum/antagonist/nukeop/lone/assign_nuke() - if(nuke_team && !nuke_team.tracked_nuke) - nuke_team.memorized_code = random_nukecode() - var/obj/machinery/nuclearbomb/selfdestruct/nuke = locate() in SSmachines.get_machines_by_type(/obj/machinery/nuclearbomb/selfdestruct) - if(nuke) - nuke_team.tracked_nuke = nuke - if(nuke.r_code == NUKE_CODE_UNSET) - nuke.r_code = nuke_team.memorized_code - else //Already set by admins/something else? - nuke_team.memorized_code = nuke.r_code - else - stack_trace("Station self-destruct not found during lone op team creation.") - nuke_team.memorized_code = null - -/datum/antagonist/nukeop/reinforcement - show_in_antagpanel = FALSE - send_to_spawnpoint = FALSE - nukeop_outfit = /datum/outfit/syndicate/reinforcement - -/datum/team/nuclear - var/syndicate_name - var/obj/machinery/nuclearbomb/tracked_nuke - var/core_objective = /datum/objective/nuclear - var/memorized_code - var/list/team_discounts - var/datum/weakref/war_button_ref - -/datum/team/nuclear/New() - ..() - syndicate_name = syndicate_name() - -/datum/team/nuclear/proc/update_objectives() - if(core_objective) - var/datum/objective/O = new core_objective - O.team = src - objectives += O - -/datum/team/nuclear/proc/is_disk_rescued() - for(var/obj/item/disk/nuclear/nuke_disk in SSpoints_of_interest.real_nuclear_disks) - //If emergency shuttle is in transit disk is only safe on it - if(SSshuttle.emergency.mode == SHUTTLE_ESCAPE) - if(!SSshuttle.emergency.is_in_shuttle_bounds(nuke_disk)) - return FALSE - //If shuttle escaped check if it's on centcom side - else if(SSshuttle.emergency.mode == SHUTTLE_ENDGAME) - if(!nuke_disk.onCentCom()) - return FALSE - else //Otherwise disk is safe when on station - var/turf/disk_turf = get_turf(nuke_disk) - if(!disk_turf || !is_station_level(disk_turf.z)) - return FALSE - return TRUE - -/datum/team/nuclear/proc/are_all_operatives_dead() - for(var/datum/mind/operative_mind as anything in members) - if(ishuman(operative_mind.current) && (operative_mind.current.stat != DEAD)) - return FALSE - return TRUE - -/datum/team/nuclear/proc/get_result() - var/shuttle_evacuated = EMERGENCY_ESCAPED_OR_ENDGAMED - var/shuttle_landed_base = SSshuttle.emergency.is_hijacked() - var/disk_rescued = is_disk_rescued() - var/syndies_didnt_escape = !is_infiltrator_docked_at_syndiebase() - var/team_is_dead = are_all_operatives_dead() - var/station_was_nuked = GLOB.station_was_nuked - var/station_nuke_source = GLOB.station_nuke_source - - // The nuke detonated on the syndicate base - if(station_nuke_source == DETONATION_HIT_SYNDIE_BASE) - return NUKE_RESULT_FLUKE - - // The station was nuked - if(station_was_nuked) - // The station was nuked and the infiltrator failed to escape - if(syndies_didnt_escape) - return NUKE_RESULT_NOSURVIVORS - // The station was nuked and the infiltrator escaped, and the nuke ops won - else - return NUKE_RESULT_NUKE_WIN - - // The station was not nuked, but something was - else if(station_nuke_source && !disk_rescued) - // The station was not nuked, but something was, and the syndicates didn't escape it - if(syndies_didnt_escape) - return NUKE_RESULT_WRONG_STATION_DEAD - // The station was not nuked, but something was, and the syndicates returned to their base - else - return NUKE_RESULT_WRONG_STATION - - // Nuke didn't blow, but nukies somehow hijacked the emergency shuttle to land at the base anyways. - else if(shuttle_landed_base) - if(disk_rescued) - return NUKE_RESULT_HIJACK_DISK - else - return NUKE_RESULT_HIJACK_NO_DISK - - // No nuke went off, the station rescued the disk - else if(disk_rescued) - // No nuke went off, the shuttle left, and the team is dead - if(shuttle_evacuated && team_is_dead) - return NUKE_RESULT_CREW_WIN_SYNDIES_DEAD - // No nuke went off, but the nuke ops survived - else - return NUKE_RESULT_CREW_WIN - - // No nuke went off, but the disk was left behind - else - // No nuke went off, the disk was left, but all the ops are dead - if(team_is_dead) - return NUKE_RESULT_DISK_LOST - // No nuke went off, the disk was left, there are living ops, but the shuttle left successfully - else if(shuttle_evacuated) - return NUKE_RESULT_DISK_STOLEN - - CRASH("[type] - got an undefined / unexpected result.") - -/datum/team/nuclear/roundend_report() - var/list/parts = list() - parts += "[syndicate_name] Operatives:" - - switch(get_result()) - if(NUKE_RESULT_FLUKE) - parts += "Humiliating Syndicate Defeat" - parts += "The crew of [station_name()] gave [syndicate_name] operatives back their bomb! The syndicate base was destroyed! Next time, don't lose the nuke!" - if(NUKE_RESULT_NUKE_WIN) - parts += "Syndicate Major Victory!" - parts += "[syndicate_name] operatives have destroyed [station_name()]!" - if(NUKE_RESULT_NOSURVIVORS) - parts += "Total Annihilation!" - parts += "[syndicate_name] operatives destroyed [station_name()] but did not leave the area in time and got caught in the explosion. Next time, don't lose the disk!" - if(NUKE_RESULT_WRONG_STATION) - parts += "Crew Minor Victory!" - parts += "[syndicate_name] operatives secured the authentication disk but blew up something that wasn't [station_name()]. Next time, don't do that!" - if(NUKE_RESULT_WRONG_STATION_DEAD) - parts += "[syndicate_name] operatives have earned Darwin Award!" - parts += "[syndicate_name] operatives blew up something that wasn't [station_name()] and got caught in the explosion. Next time, don't do that!" - if(NUKE_RESULT_HIJACK_DISK) - parts += "Syndicate Miniscule Victory!" - parts += "[syndicate_name] operatives failed to destroy [station_name()], but they managed to secure the disk and hijack the emergency shuttle, causing it to land on the syndicate base. Good job?" - if(NUKE_RESULT_HIJACK_NO_DISK) - parts += "Syndicate Insignificant Victory!" - parts += "[syndicate_name] operatives failed to destroy [station_name()] or secure the disk, but they managed to hijack the emergency shuttle, causing it to land on the syndicate base. Good job?" - if(NUKE_RESULT_CREW_WIN_SYNDIES_DEAD) - parts += "Crew Major Victory!" - parts += "The Research Staff has saved the disk and killed the [syndicate_name] Operatives" - if(NUKE_RESULT_CREW_WIN) - parts += "Crew Major Victory!" - parts += "The Research Staff has saved the disk and stopped the [syndicate_name] Operatives!" - if(NUKE_RESULT_DISK_LOST) - parts += "Neutral Victory!" - parts += "The Research Staff failed to secure the authentication disk but did manage to kill most of the [syndicate_name] Operatives!" - if(NUKE_RESULT_DISK_STOLEN) - parts += "Syndicate Minor Victory!" - parts += "[syndicate_name] operatives survived the assault but did not achieve the destruction of [station_name()]. Next time, don't lose the disk!" - else - parts += "Neutral Victory" - parts += "Mission aborted!" - - var/text = "
The syndicate operatives were:" - var/purchases = "" - var/TC_uses = 0 - LAZYINITLIST(GLOB.uplink_purchase_logs_by_key) - for(var/I in members) - var/datum/mind/syndicate = I - var/datum/uplink_purchase_log/H = GLOB.uplink_purchase_logs_by_key[syndicate.key] - if(H) - TC_uses += H.total_spent - purchases += H.generate_render(show_key = FALSE) - text += printplayerlist(members) - text += "
" - text += "(Syndicates used [TC_uses] TC) [purchases]" - if(TC_uses == 0 && GLOB.station_was_nuked && !are_all_operatives_dead()) - text += "[icon2html('icons/ui_icons/antags/badass.dmi', world, "badass")]" - - parts += text - - return "
[parts.Join("
")]
" - -/datum/team/nuclear/antag_listing_name() - if(syndicate_name) - return "[syndicate_name] Syndicates" - else - return "Syndicates" - -/datum/team/nuclear/antag_listing_entry() - var/disk_report = "Nuclear Disk(s)
" - disk_report += "" - for(var/obj/item/disk/nuclear/N in SSpoints_of_interest.real_nuclear_disks) - disk_report += "" - disk_report += "
[N.name], " - var/atom/disk_loc = N.loc - while(!isturf(disk_loc)) - if(ismob(disk_loc)) - var/mob/M = disk_loc - disk_report += "carried by [M.real_name] " - if(isobj(disk_loc)) - var/obj/O = disk_loc - disk_report += "in \a [O.name] " - disk_loc = disk_loc.loc - disk_report += "in [disk_loc.loc] at ([disk_loc.x], [disk_loc.y], [disk_loc.z])FLW
" - - var/post_report - - var/war_declared = FALSE - for(var/obj/item/circuitboard/computer/syndicate_shuttle/board as anything in GLOB.syndicate_shuttle_boards) - if(board.challenge) - war_declared = TRUE - - var/force_war_button = "" - - if(war_declared) - post_report += "War declared." - force_war_button = "\[Force war\]" - else - post_report += "War not declared." - var/obj/item/nuclear_challenge/war_button = war_button_ref?.resolve() - if(war_button) - force_war_button = "\[Force war\]" - else - force_war_button = "\[Cannot declare war, challenge button missing!\]" - - post_report += "\n[force_war_button]" - post_report += "\n\[Send Reinforcement\]" - - var/final_report = ..() - final_report += disk_report - final_report += post_report - return final_report - -#define SPAWN_AT_BASE "Nuke base" -#define SPAWN_AT_INFILTRATOR "Infiltrator" - -/datum/team/nuclear/proc/admin_spawn_reinforcement(mob/admin) - if(!check_rights_for(admin.client, R_ADMIN)) - return - - var/infil_or_nukebase = tgui_alert( - admin, - "Spawn them at the nuke base, or in the Infiltrator?", - "Where to reinforce?", - list(SPAWN_AT_BASE, SPAWN_AT_INFILTRATOR, "Cancel"), - ) - - if(!infil_or_nukebase || infil_or_nukebase == "Cancel") - return - - var/tc_to_spawn = tgui_input_number(admin, "How much TC to spawn with?", "TC", 0, 100) - - var/list/nuke_candidates = SSpolling.poll_ghost_candidates( - "Do you want to play as an emergency syndicate reinforcement?", - check_jobban = ROLE_OPERATIVE, - role = ROLE_OPERATIVE, - poll_time = 30 SECONDS, - ignore_category = POLL_IGNORE_SYNDICATE, - pic_source = /obj/structure/sign/poster/contraband/gorlex_recruitment, - role_name_text = "syndicate reinforcement", - ) - - nuke_candidates -= admin // may be easy to fat-finger say yes. so just don't - - if(!length(nuke_candidates)) - tgui_alert(admin, "No candidates found.", "Recruitment Shortage", list("OK")) - return - - - var/turf/spawn_loc - if(infil_or_nukebase == SPAWN_AT_INFILTRATOR) - var/area/spawn_in - // Prioritize EVA then hallway, if neither can be found default to the first area we can find - for(var/area_type in list(/area/shuttle/syndicate/eva, /area/shuttle/syndicate/hallway, /area/shuttle/syndicate)) - spawn_in = locate(area_type) in GLOB.areas // I'd love to use areas_by_type but the Infiltrator is a unique area - if(spawn_in) - break - - var/list/turf/options = list() - for(var/turf/open/open_turf in spawn_in?.get_turfs_from_all_zlevels()) - if(open_turf.is_blocked_turf()) - continue - options += open_turf - - if(length(options)) - spawn_loc = pick(options) - else - infil_or_nukebase = SPAWN_AT_BASE - - if(infil_or_nukebase == SPAWN_AT_BASE) - spawn_loc = pick(GLOB.nukeop_start) - - var/mob/dead/observer/picked = pick(nuke_candidates) - var/mob/living/carbon/human/nukie = new(spawn_loc) - picked.client.prefs.safe_transfer_prefs_to(nukie, is_antag = TRUE) - nukie.key = picked.key - - var/datum/antagonist/nukeop/antag_datum = new() - antag_datum.send_to_spawnpoint = FALSE - antag_datum.nukeop_outfit = /datum/outfit/syndicate/reinforcement - - nukie.mind.add_antag_datum(antag_datum, src) - - var/datum/component/uplink/uplink = nukie.mind.find_syndicate_uplink() - uplink?.uplink_handler.set_telecrystals(tc_to_spawn) - - // add some pizzazz - do_sparks(4, FALSE, spawn_loc) - new /obj/effect/temp_visual/teleport_abductor/syndi_teleporter(spawn_loc) - playsound(spawn_loc, SFX_SPARKS, 50, TRUE) - playsound(spawn_loc, 'sound/effects/phasein.ogg', 50, TRUE) - - tgui_alert(admin, "Reinforcement spawned at [infil_or_nukebase] with [tc_to_spawn].", "Reinforcements have arrived", list("God speed")) - -#undef SPAWN_AT_BASE -#undef SPAWN_AT_INFILTRATOR - -/// Returns whether or not syndicate operatives escaped. -/proc/is_infiltrator_docked_at_syndiebase() - var/obj/docking_port/mobile/infiltrator/infiltrator_port = SSshuttle.getShuttle("syndicate") - - var/datum/lazy_template/nukie_base/nukie_template = GLOB.lazy_templates[LAZY_TEMPLATE_KEY_NUKIEBASE] - if(!nukie_template) - return FALSE // if its not even loaded, cant be docked - - for(var/datum/turf_reservation/loaded_area as anything in nukie_template.reservations) - var/infiltrator_turf = get_turf(infiltrator_port) - if(infiltrator_turf in loaded_area.reserved_turfs) - return TRUE - return FALSE diff --git a/code/modules/antagonists/nukeop/outfits.dm b/code/modules/antagonists/nukeop/outfits.dm index a3c97a764688b..80360f5636098 100644 --- a/code/modules/antagonists/nukeop/outfits.dm +++ b/code/modules/antagonists/nukeop/outfits.dm @@ -77,6 +77,7 @@ backpack_contents = list( /obj/item/gun/ballistic/automatic/pistol/clandestine = 1, /obj/item/pen/edagger = 1, + /obj/item/ammo_box/magazine/m12g = 3, ) /datum/outfit/syndicate/full/plasmaman @@ -163,3 +164,30 @@ shoes = /obj/item/clothing/shoes/laceup glasses = /obj/item/clothing/glasses/sunglasses/big faction = "MI13" + +/datum/outfit/nuclear_operative + name = "Nuclear Operative (Preview only)" + + back = /obj/item/mod/control/pre_equipped/empty/syndicate + uniform = /obj/item/clothing/under/syndicate + +/datum/outfit/nuclear_operative/post_equip(mob/living/carbon/human/H, visualsOnly) + var/obj/item/mod/module/armor_booster/booster = locate() in H.back + booster.active = TRUE + H.update_worn_back() + +/datum/outfit/nuclear_operative_elite + name = "Nuclear Operative (Elite, Preview only)" + + back = /obj/item/mod/control/pre_equipped/empty/elite + uniform = /obj/item/clothing/under/syndicate + l_hand = /obj/item/modular_computer/pda/nukeops + r_hand = /obj/item/shield/energy + +/datum/outfit/nuclear_operative_elite/post_equip(mob/living/carbon/human/H, visualsOnly) + var/obj/item/mod/module/armor_booster/booster = locate() in H.back + booster.active = TRUE + H.update_worn_back() + var/obj/item/shield/energy/shield = locate() in H.held_items + shield.icon_state = "[shield.base_icon_state]1" + H.update_held_items() diff --git a/code/modules/antagonists/pirate/pirate_event.dm b/code/modules/antagonists/pirate/pirate_event.dm index f3c6655a27572..e4a14182d0e7f 100644 --- a/code/modules/antagonists/pirate/pirate_event.dm +++ b/code/modules/antagonists/pirate/pirate_event.dm @@ -66,7 +66,7 @@ if(chosen_gang.paid_off) return - var/list/candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for a pirate crew of [chosen_gang.name]?", check_jobban = ROLE_TRAITOR, pic_source = /obj/item/claymore/cutlass, role_name_text = "pirate crew") + var/list/candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for a [span_notice("pirate crew of [chosen_gang.name]?")]", check_jobban = ROLE_TRAITOR, alert_pic = /obj/item/claymore/cutlass, role_name_text = "pirate crew") shuffle_inplace(candidates) var/template_key = "pirate_[chosen_gang.ship_template_id]" diff --git a/code/modules/antagonists/spy/spy.dm b/code/modules/antagonists/spy/spy.dm new file mode 100644 index 0000000000000..e0ea7e4075404 --- /dev/null +++ b/code/modules/antagonists/spy/spy.dm @@ -0,0 +1,212 @@ +/datum/antagonist/spy + name = "\improper Spy" + roundend_category = "spies" + antagpanel_category = "Spy" + antag_hud_name = "spy" + job_rank = ROLE_SPY + antag_moodlet = /datum/mood_event/focused + hijack_speed = 1 + ui_name = "AntagInfoSpy" + preview_outfit = /datum/outfit/spy + /// Whether an uplink has been created (successfully or at all) + var/uplink_created = FALSE + /// String displayed in the antag panel pointing the spy to where their uplink is. + var/uplink_location + /// Whether we give them some random objetives to aim for. + var/spawn_with_objectives = TRUE + /// Tracks number of bounties claimed, for roundend + var/bounties_claimed = 0 + /// Tracks all loot items the spy has claimed, for roundend + var/list/all_loot = list() + /// Weakref to our spy uplink + /// Only exists for the sole purpose of letting admins see it + var/datum/weakref/uplink_weakref + +/datum/antagonist/spy/on_gain() + if(!uplink_created) + auto_create_spy_uplink(owner.current) + if(spawn_with_objectives) + give_random_objectives() + . = ..() + SEND_SOUND(owner.current, sound('sound/ambience/antag/spy.ogg')) + +/datum/antagonist/spy/ui_static_data(mob/user) + var/list/data = ..() + data["uplink_location"] = uplink_location + return data + +/datum/antagonist/spy/get_admin_commands() + . = ..() + // I wanted to put this in check-antagonists but it's less conducive to that + .["See All Bounties (For all spies)"] = CALLBACK(src, PROC_REF(see_bounties)) + .["Refresh Bounties (For all spies)"] = CALLBACK(src, PROC_REF(refresh_bounties)) + .["Give Spy Uplink"] = CALLBACK(src, PROC_REF(admin_create_spy_uplink)) + .["Bounty Handler VV"] = CALLBACK(src, PROC_REF(bounty_handler_vv)) + +/datum/antagonist/spy/proc/see_bounties() + if(!check_rights(R_ADMIN|R_DEBUG)) + return + + var/datum/component/spy_uplink/uplink = uplink_weakref?.resolve() + if(isnull(uplink)) + tgui_alert(usr, "No spy uplink!", "Mission Failed") + return + + uplink.ui_interact(usr) + +/datum/antagonist/spy/proc/refresh_bounties() + if(!check_rights(R_ADMIN|R_DEBUG)) + return + + var/datum/component/spy_uplink/uplink = uplink_weakref?.resolve() + if(isnull(uplink)) + tgui_alert(usr, "No spy uplink!", "Mission Failed") + return + + uplink.handler.force_refresh() + tgui_alert(usr, "Bounties refreshed.", "Mission Success") + +/datum/antagonist/spy/proc/admin_create_spy_uplink() + if(!check_rights(R_ADMIN|R_DEBUG)) + return + + if(!auto_create_spy_uplink(owner.current, give_backup = FALSE)) + tgui_alert(usr, "Failed to give [owner.current] a spy uplink - likely don't have a valid item to host it.", "Mission Failed") + +/datum/antagonist/spy/proc/bounty_handler_vv() + if(!check_rights(R_ADMIN|R_DEBUG)) + return + + var/datum/component/spy_uplink/uplink = uplink_weakref?.resolve() + if(isnull(uplink)) + tgui_alert(usr, "No spy uplink!", "Mission Failed") + return + + usr.client?.debug_variables(uplink.handler) + +/datum/antagonist/spy/proc/auto_create_spy_uplink(mob/living/carbon/spy, give_backup = TRUE) + if(!iscarbon(spy)) + return FALSE + + var/spy_uplink_loc = spy.client?.prefs?.read_preference(/datum/preference/choiced/uplink_location) + if(isnull(spy_uplink_loc) || spy_uplink_loc == UPLINK_IMPLANT) + spy_uplink_loc = pick(UPLINK_PEN, UPLINK_PDA) + + var/obj/item/spy_uplink = spy.get_uplink_location(spy_uplink_loc) + if(isnull(spy_uplink) || !create_spy_uplink(spy, spy_uplink)) + if(give_backup) + var/datum/action/backup_uplink/backup = new(src) + backup.Grant(spy) + to_chat(spy, span_boldnotice("You were unable to be supplied with an uplink, so you have been given the ability to create one yourself.")) + return FALSE + + return TRUE + +/datum/antagonist/spy/proc/create_spy_uplink(mob/living/carbon/spy, obj/item/spy_uplink) + var/datum/component/spy_uplink/uplink = spy_uplink.AddComponent(/datum/component/spy_uplink, src) + if(!uplink) + return FALSE + + uplink_weakref = WEAKREF(uplink) + uplink_created = TRUE + + if(istype(spy_uplink, /obj/item/modular_computer/pda)) + uplink_location = "your PDA" + + else if(istype(spy_uplink, /obj/item/pen)) + if(istype(spy_uplink.loc, /obj/item/modular_computer/pda)) + uplink_location = "your PDA's pen" + else + uplink_location = "a pen" + + else if(istype(spy_uplink, /obj/item/radio)) + uplink_location = "your radio headset" + + return TRUE + +/datum/antagonist/spy/proc/give_random_objectives() + for(var/i in 1 to rand(1, 3)) + var/datum/objective/custom/your_mission = new() + your_mission.owner = owner + your_mission.explanation_text = pick_list_replacements(SPY_OBJECTIVE_FILE, "objective_body") + objectives += your_mission + + if(prob(10)) + var/datum/objective/martyr/leave_no_trace = new() + leave_no_trace.owner = owner + objectives += leave_no_trace + + else if(prob(3)) //3% chance on 90% chance + var/datum/objective/hijack/steal_the_shuttle = new() + steal_the_shuttle.owner = owner + objectives += steal_the_shuttle + + else + var/datum/objective/escape/gtfo = new() + gtfo.owner = owner + objectives += gtfo + +/datum/antagonist/spy/antag_panel_data() + return "Bounties Claimed: [bounties_claimed]" + +/datum/antagonist/spy/roundend_report() + var/list/report = list() + report += printplayer(owner) + report += " - They completed [bounties_claimed] bounties." + if(bounties_claimed > 0) + report += " - They received the following rewards: [english_list(all_loot)]" + report += printobjectives(objectives) + return report.Join("
") + +/datum/antagonist/spy/get_preview_icon() + var/mob/living/carbon/human/dummy/consistent/dummy = new() + dummy.set_haircolor(COLOR_SILVER, update = FALSE) + dummy.set_hairstyle("CIA", update = FALSE) + return finish_preview_icon(render_preview_outfit(preview_outfit, dummy)) + +/datum/outfit/spy + name = "Spy (Preview only)" + // Balaclava sprite is ass, otherwise I would use it for this + uniform = /obj/item/clothing/under/suit/black + gloves = /obj/item/clothing/gloves/color/black + shoes = /obj/item/clothing/shoes/jackboots + head = /obj/item/clothing/head/fedora + suit = /obj/item/clothing/suit/jacket/trenchcoat + glasses = /obj/item/clothing/glasses/osi + ears = /obj/item/radio/headset + +/datum/action/backup_uplink + name = "Create Uplink" + desc = "Fashion a PDA, Pen or Radio Headset into a swanky Spy Uplink." + var/list/valid_types = list( + /obj/item/modular_computer/pda, + /obj/item/pen, + /obj/item/radio, + ) + +/datum/action/backup_uplink/New(Target) + . = ..() + if(!istype(Target, /datum/antagonist/spy)) + stack_trace("[type] created on invalid target [Target || "null"]") + qdel(src) + +/datum/action/backup_uplink/Trigger(trigger_flags) + . = ..() + if(!.) + return + + var/mob/living/spy = usr + var/obj/item/held_thing = spy.get_active_held_item() + if(isnull(held_thing)) + spy.balloon_alert(spy, "you need to hold something!") + return + + if(!is_type_in_list(held_thing, valid_types)) + held_thing.balloon_alert(spy, "invalid item!") + return + + var/datum/antagonist/spy/spy_datum = target + spy_datum.create_spy_uplink(spy, held_thing) + held_thing.balloon_alert(spy, "uplink created") + + qdel(src) diff --git a/code/modules/antagonists/spy/spy_bounty.dm b/code/modules/antagonists/spy/spy_bounty.dm new file mode 100644 index 0000000000000..e3c96fc815f3b --- /dev/null +++ b/code/modules/antagonists/spy/spy_bounty.dm @@ -0,0 +1,703 @@ +/** + * ## Spy Bounty + * + * A datum used to track a single spy bounty. + * Not a singleton - whenever bounties are re-rolled, the entire list is deleted and new bounty datums are instantiated. + * + * When bounties are completed, they are also not deleted, but instead marked as claimed. + */ +/datum/spy_bounty + /// The name of the bounty. + /// Should be a short description without punctuation. + /// IE: "Steal the captain's ID" + var/name + /// Help text for the bounty. + /// Should include additional information about the bounty to assist the spy in figuring out what to do. + /// Should be punctuated. + /// IE: "Steal the captain's ID. It was last seen in the captain's office." + var/help + /// Difficult of the bounty, one of [SPY_DIFFICULTY_EASY], [SPY_DIFFICULTY_MEDIUM], [SPY_DIFFICULTY_HARD]. + /// Must be set to one of the possible bounties to be picked. + var/difficulty = "unset" + /// How long of a do-after must be completed by the Spy to turn in the bounty. + var/theft_time = 2 SECONDS + /// Probability that the stolen item will be sent to the black market instead of destroyed. + /// Guaranteed if the item is indestructible. + var/black_market_prob = 50 + /// Weight that the bounty will be selected. + var/weight = 1 + + /// Whether the bounty's been fully initialized. If this is not set, the bounty will be rerolled. + VAR_FINAL/initalized = FALSE + /// Whether the bounty has been completed. + VAR_FINAL/claimed = FALSE + /// What uplink item the bounty will reward on completion. + VAR_FINAL/datum/uplink_item/reward_item + +/datum/spy_bounty/New(datum/spy_bounty_handler/handler) + if(!init_bounty(handler)) + return + + initalized = TRUE + select_reward(handler) + +/// Helper that translates the bounty into UI data for TGUI +/datum/spy_bounty/proc/to_ui_data(mob/user) + SHOULD_CALL_PARENT(TRUE) + return list( + "name" = name, + "help" = help, + "difficulty" = difficulty, + "reward" = reward_item.name, + "claimed" = claimed, + "can_claim" = can_claim(user), + ) + +/// Check if the passed mob can claim this bounty. +/datum/spy_bounty/proc/can_claim(mob/user) + SHOULD_BE_PURE(TRUE) + return TRUE + +/** + * Initializes the bounty, setting up targets and etc. + * + * * handler - The bounty handler that is creating this bounty. + * + * Returning FALSE will cancel initialization and force it to reroll the bounty. + */ +/datum/spy_bounty/proc/init_bounty(datum/spy_bounty_handler/handler) + return FALSE + +/// Selects what uplink item the bounty will reward on completion. +/datum/spy_bounty/proc/select_reward(datum/spy_bounty_handler/handler) + var/list/loot_pool = handler.possible_uplink_items[difficulty] + + if(!length(loot_pool)) + reward_item = /datum/uplink_item/bundles_tc/telecrystal + return // future todo : add some junk items for when we run out of items + + reward_item = pick(loot_pool) + if(prob(80)) + loot_pool -= reward_item + +/** + * Checks if the passed movable is a valid target for this bounty. + * + * * stealing - The movable to check. + * + * Returning FALSE simply means that the passed movable is not valid for this bounty. + */ +/datum/spy_bounty/proc/is_stealable(atom/movable/stealing) + // SHOULD_BE_PURE(TRUE) + return FALSE + +/** + * What is this bounty's "dupe protection key"? + * This is used to determine if a duplicate of this bounty has been rolled before / in the last refresh. + * You can check if a bounty has been duped by accessing the handler's claimed_bounties_from_last_pool or all_claimed_bounty_types list. + * + * * stealing - The item that was stolen. + * * handler - The handler that is handling the bounty. + * + * Return a string key, what this uses for dupe protection. + */ +/datum/spy_bounty/proc/get_dupe_protection_key(atom/movable/stealing) + return stealing.type + +/** + * Checks if the passed dupe key is a duplicate of an previously claimed bounty. + * + * * handler - The handler that is handling the bounty. + * * dupe_key - The key to check for dupes + * * dupe_prob - The probability of a dupe being allowed when checking all_claimed_bounty_types. + * This allows you to have a chance that distant dupes allowed depending on how many times they've been done. + * + * Returns TRUE if the bounty is a dupe, FALSE if it is not. + */ +/datum/spy_bounty/proc/check_dupe(datum/spy_bounty_handler/handler, dupe_key, dupe_prob = 0) + if(handler.claimed_bounties_from_last_pool[dupe_key]) + return TRUE + if(prob(dupe_prob * handler.all_claimed_bounty_types[dupe_key])) + return TRUE + return FALSE + +/** + * Called when the bounty is completed, to handle how the stolen item is "stolen". + * + * By default, stolen items are simply deleted. + * + * * stealing - The item that was stolen. + * * spy - The spy that stole the item. + */ +/datum/spy_bounty/proc/clean_up_stolen_item(atom/movable/stealing, mob/living/spy) + do_sparks(3, FALSE, stealing) + + // Don't mess with it while it's going away + stealing.interaction_flags_atom &= ~INTERACT_ATOM_ATTACK_HAND + stealing.anchored = TRUE + // Add some pizzazz + animate(stealing, time = 0.5 SECONDS, transform = matrix(stealing.transform).Scale(0.01), easing = CUBIC_EASING) + + if(isitem(stealing) && ((stealing.resistance_flags & INDESTRUCTIBLE) || prob(black_market_prob))) + addtimer(CALLBACK(src, PROC_REF(send_to_black_market), stealing), 0.5 SECONDS) + else + addtimer(CALLBACK(src, PROC_REF(finish_cleanup), stealing), 0.5 SECONDS) + +/** + * Called when cleaning up a stolen atom that was NOT sent to the black market. + * + * * stealing - The item that was stolen. + */ +/datum/spy_bounty/proc/finish_cleanup(atom/movable/stealing) + qdel(stealing) + +/** + * Handles putting the passed movable up on the black market. + * + * By the end of this proc, the item should either be deleted (if failure) or in nullspace (on the black market). + * + * * thing - The item to put up on the black market. + */ +/datum/spy_bounty/proc/send_to_black_market(atom/movable/thing) + if(QDELETED(thing)) // Just in case anything does anything weird + return FALSE + + thing.interaction_flags_atom = initial(thing.interaction_flags_atom) + thing.anchored = initial(thing.anchored) + thing.moveToNullspace() + + var/datum/market_item/new_item = new() + new_item.item = thing + new_item.name = "Stolen [thing.name]" + new_item.desc = "A [thing.name], stolen from somewhere on the station. Whoever owned it probably wouldn't be happy to see it here." + new_item.category = "Fenced Goods" + new_item.stock = 1 + new_item.availability_prob = 100 + + switch(difficulty) + if(SPY_DIFFICULTY_EASY) + new_item.price = PAYCHECK_COMMAND * 2.5 + if(SPY_DIFFICULTY_MEDIUM) + new_item.price = PAYCHECK_COMMAND * 5 + if(SPY_DIFFICULTY_HARD) + new_item.price = PAYCHECK_COMMAND * 10 + + new_item.price += rand(0, PAYCHECK_COMMAND * 5) + if(thing.resistance_flags & INDESTRUCTIBLE) + new_item.price *= 2 + + return SSblackmarket.markets[/datum/market/blackmarket].add_item(new_item) + +/// Steal an item +/datum/spy_bounty/objective_item + /// Reference to an objective item datum that we want stolen. + VAR_FINAL/datum/objective_item/desired_item + /// Typecache of objective items that should not be selected. + var/static/list/blacklisted_item_types = typecacheof(list( + /datum/objective_item/steal/functionalai, + /datum/objective_item/steal/nukedisc, + )) + +/datum/spy_bounty/objective_item/can_claim(mob/user) + return !(user.mind?.assigned_role.title in desired_item.excludefromjob) + +/datum/spy_bounty/objective_item/get_dupe_protection_key(atom/movable/stealing) + return desired_item.targetitem + +/// Determines if the passed objective item is a reasonable, valid theft target. +/datum/spy_bounty/objective_item/proc/is_valid_objective_item(datum/objective_item/item) + if(length(item.special_equipment) || item.difficulty <= 0 || item.difficulty >= 6) + return FALSE + if(is_type_in_typecache(item, blacklisted_item_types)) + return FALSE + if(!item.exists_on_map) + return TRUE + var/list/all_valid_existing_things = list() + for(var/obj/item/existing_thing as anything in GLOB.steal_item_handler.objectives_by_path[item.targetitem]) + var/turf/thing_turf = get_turf(existing_thing) + if(isnull(thing_turf)) // nullspaced likely means it was stolen and is in the black market. + continue + if(!is_station_level(thing_turf.z) && !is_mining_level(thing_turf.z)) + continue + all_valid_existing_things += existing_thing + + if(!length(all_valid_existing_things)) + return FALSE + return TRUE + +/datum/spy_bounty/objective_item/init_bounty(datum/spy_bounty_handler/handler) + var/list/valid_possible_items = list() + for(var/datum/objective_item/item as anything in GLOB.possible_items) + if(check_dupe(handler, item.targetitem, 33)) + continue + if(!is_valid_objective_item(item)) + continue + // Determine difficulty. Has some overlap between the categories, which is OK + switch(difficulty) + if(SPY_DIFFICULTY_EASY) + if(item.difficulty >= 3) + continue + if(SPY_DIFFICULTY_MEDIUM) + if(item.difficulty <= 2 || item.difficulty >= 5) + continue + if(SPY_DIFFICULTY_HARD) + if(item.difficulty <= 3) + continue + + valid_possible_items += item + + for(var/datum/spy_bounty/objective_item/existing_bounty in handler.get_all_bounties()) + valid_possible_items -= existing_bounty.desired_item + + if(!length(valid_possible_items)) + return FALSE + + desired_item = pick(valid_possible_items) + // We need to do some snowflake for items that do exist vs generic items + var/list/obj/item/existing_items = GLOB.steal_item_handler.objectives_by_path[desired_item.targetitem] + var/obj/item/the_item = length(existing_items) ? pick(existing_items) : desired_item.targetitem + var/the_item_name = istype(the_item) ? the_item.name : initial(the_item.name) + name = "[the_item_name] [difficulty == SPY_DIFFICULTY_HARD ? "Grand ":""]Theft" + help = "Steal any [the_item_name][desired_item.steal_hint ? ": [desired_item.steal_hint]" : "."]" + return TRUE + +/datum/spy_bounty/objective_item/is_stealable(atom/movable/stealing) + return istype(stealing, desired_item.targetitem) && desired_item.check_special_completion(stealing) + +/datum/spy_bounty/objective_item/random_easy + difficulty = SPY_DIFFICULTY_EASY + weight = 4 // Increased due to there being many easy options + +/datum/spy_bounty/objective_item/random_medium + difficulty = SPY_DIFFICULTY_MEDIUM + weight = 2 // Increased due to there being many medium options + +/datum/spy_bounty/objective_item/random_hard + difficulty = SPY_DIFFICULTY_HARD + +/datum/spy_bounty/machine + theft_time = 10 SECONDS + + /// What machine (typepath) we want to steal. + var/obj/machinery/target_type + /// What area (typepath) the desired machine is in. + /// Can be pre-set for subtypes. If set, requires the machine to be in the location_type. + /// If not set, picks a random machine from all areas it can currently be found in. + var/area/location_type + /// List of weakrefs to all machines of the target type when the bounty was initialized. + var/list/original_options_weakrefs = list() + +/datum/spy_bounty/machine/Destroy() + original_options_weakrefs.Cut() // Just in case + return ..() + +/datum/spy_bounty/machine/get_dupe_protection_key(atom/movable/stealing) + return target_type + +/datum/spy_bounty/machine/send_to_black_market(obj/machinery/thing) + if(!istype(thing.circuit, /obj/item/circuitboard)) + qdel(thing) + return FALSE + + var/obj/item/circuitboard/selling = thing.circuit + var/turf/machine_turf = get_turf(thing) + + // Sell the circuitboard, take the rest apart + // This (should) handle any mobs inside as well + thing.deconstruct(FALSE) + if(!..(selling)) + return FALSE + + // Clean up leftover parts from deconstruction + for(var/obj/structure/frame/leftover in machine_turf) + qdel(leftover) + break + for(var/obj/item/stock_parts/part in machine_turf) + if(prob(part.rating * 20)) + continue + qdel(part) + + return TRUE + +/datum/spy_bounty/machine/finish_cleanup(obj/machinery/stealing) + stealing.dump_inventory_contents() + return ..() + +/datum/spy_bounty/machine/init_bounty(datum/spy_bounty_handler/handler) + if(isnull(target_type)) + return FALSE + + // Blacklisting maintenance in general, as well as any areas that already have a bounty in them. + var/list/blacklisted_areas = typecacheof(/area/station/maintenance) + for(var/datum/spy_bounty/machine/existing_bounty in handler.get_all_bounties()) + blacklisted_areas[existing_bounty.location_type] = TRUE + + var/list/obj/machinery/all_possible = list() + for(var/obj/machinery/found_machine as anything in SSmachines.get_machines_by_type_and_subtypes(target_type)) + if(!is_station_level(found_machine.z) && !is_mining_level(found_machine.z)) + continue + var/area/found_machine_area = get_area(found_machine) + if(is_type_in_typecache(found_machine_area, blacklisted_areas)) + continue + if(!isnull(location_type) && !istype(found_machine_area, location_type)) + continue + if(!(found_machine_area.area_flags & VALID_TERRITORY)) // only steal from valid station areas + continue + all_possible += found_machine + + if(!length(all_possible)) + return FALSE + + var/obj/machinery/machine = pick_n_take(all_possible) + var/area/machine_area = get_area(machine) + // Tracks the picked machine, as well as any other machines in the same area + // (So they can be removed from the room but still count, for clever Spies) + original_options_weakrefs += WEAKREF(machine) + for(var/obj/machinery/other_machine as anything in all_possible) + if(get_area(other_machine) == machine_area) + original_options_weakrefs += WEAKREF(other_machine) + + location_type = machine_area.type + name ||= "[machine.name] Burglary" + help ||= "Steal \a [machine] found in [machine_area]." + return TRUE + +/datum/spy_bounty/machine/is_stealable(atom/movable/stealing) + if(!istype(stealing, target_type)) + return FALSE + if(WEAKREF(stealing) in original_options_weakrefs) + return TRUE + if(istype(get_area(stealing), location_type)) + return TRUE + return FALSE + +/datum/spy_bounty/machine/random + /// List of all machines we can randomly draw from + var/list/random_options = list() + +/datum/spy_bounty/machine/random/init_bounty(datum/spy_bounty_handler/handler) + var/list/options = random_options.Copy() + for(var/datum/spy_bounty/machine/existing_bounty in handler.get_all_bounties()) + options -= existing_bounty.target_type + + for(var/remaining_option in options) + if(check_dupe(handler, remaining_option, 33)) + options -= remaining_option + + if(!length(options)) + return FALSE + + target_type = pick(options) + return ..() + +/datum/spy_bounty/machine/random/easy + difficulty = SPY_DIFFICULTY_EASY + weight = 4 // Increased due to there being many easy options + random_options = list( + /obj/machinery/computer/operating, + /obj/machinery/computer/order_console/mining, + /obj/machinery/computer/records/medical, + /obj/machinery/cryo_cell, + /obj/machinery/fax, // Completely random, wild card + /obj/machinery/hydroponics/constructable, + /obj/machinery/medical_kiosk, + /obj/machinery/microwave, + /obj/machinery/oven, + /obj/machinery/recharge_station, + /obj/machinery/vending/boozeomat, + /obj/machinery/vending/medical, + /obj/machinery/vending/wardrobe, + ) + +/datum/spy_bounty/machine/random/medium + difficulty = SPY_DIFFICULTY_MEDIUM + weight = 4 // Increased due to there being many medium options + random_options = list( + /obj/machinery/chem_dispenser, + /obj/machinery/computer/bank_machine, + /obj/machinery/computer/camera_advanced/xenobio, + /obj/machinery/computer/cargo, // This includes request-only ones in the public lobby + /obj/machinery/computer/crew, + /obj/machinery/computer/prisoner/management, + /obj/machinery/computer/rdconsole, + /obj/machinery/computer/records/security, + /obj/machinery/computer/scan_consolenew, + /obj/machinery/computer/security, // Requires breaking into a sec checkpoint, but not too hard, many are never visited + /obj/machinery/dna_scannernew, + /obj/machinery/mecha_part_fabricator, + ) + +/datum/spy_bounty/machine/engineering_emitter + difficulty = SPY_DIFFICULTY_MEDIUM + target_type = /obj/machinery/power/emitter + location_type = /area/station/engineering/supermatter/ + +/datum/spy_bounty/machine/engineering_emitter/can_claim(mob/user) + return !(user.mind?.assigned_role.departments_bitflags & DEPARTMENT_BITFLAG_ENGINEERING) + +/datum/spy_bounty/machine/random/hard + difficulty = SPY_DIFFICULTY_HARD + random_options = list( + /obj/machinery/computer/accounting, + /obj/machinery/computer/communications, + /obj/machinery/computer/upload, + /obj/machinery/modular_computer/preset/id, + ) + +/datum/spy_bounty/machine/random/hard/can_claim(mob/user) // These would all be too easy with command level access + return !(user.mind?.assigned_role.departments_bitflags & DEPARTMENT_BITFLAG_COMMAND) + +/datum/spy_bounty/machine/random/hard/ai_sat_teleporter + random_options = list( + /obj/machinery/teleport, + /obj/machinery/computer/teleporter, + ) + location_type = /area/station/ai_monitored/aisat + +/// Subtype for a bounty that targets a specific crew member +/datum/spy_bounty/targets_person + difficulty = SPY_DIFFICULTY_HARD + theft_time = 12 SECONDS + /// Weakref to the mob target of the bounty + VAR_FINAL/datum/weakref/target_ref + +/datum/spy_bounty/targets_person/get_dupe_protection_key(atom/movable/stealing) + // Prevents the same player from being selected twice, but if they're straight up gone, whatever + return REF(target_ref.resolve() || stealing) + +/datum/spy_bounty/targets_person/can_claim(mob/user) + return !IS_WEAKREF_OF(user, target_ref) + +/datum/spy_bounty/targets_person/init_bounty(datum/spy_bounty_handler/handler) + var/list/mob/possible_targets = list() + for(var/datum/mind/crew_mind as anything in get_crewmember_minds()) + var/mob/living/real_target = crew_mind.current + // Ideally we want it to be a player, but we don't care if they DC after being selected + if(!istype(real_target) || isnull(GET_CLIENT(real_target))) + continue + if(check_dupe(handler, REF(real_target), 50)) + continue + if(!is_valid_crewmember(real_target)) + continue + possible_targets += real_target + + for(var/datum/spy_bounty/targets_person/existing_bounty in handler.get_all_bounties()) + possible_targets -= existing_bounty.target_ref.resolve() + + if(!length(possible_targets)) + return FALSE + + var/mob/picked = pick(possible_targets) + if(target_found(picked)) + target_ref = WEAKREF(picked) + return TRUE + + return FALSE + +/** + * Ran on every single member of the crew to determine if they are a valid target. + * + * * crewmember - The person to check. + * + * Returning FALSE will exclude them from the list of possible targets. + */ +/datum/spy_bounty/targets_person/proc/is_valid_crewmember(mob/crewmember) + return FALSE + +/** + * Ran when a valid target is selected for the bounty. + * + * * crewmember - The person that was selected as the target. + * + * Returning FALSE will stop the bounty from being finalized, this can be used for last minute checks. + */ +/datum/spy_bounty/targets_person/proc/target_found(mob/crewmember) + return FALSE + +/// Subtype for a bounty that targets a specific crew member and a specific item on them +/datum/spy_bounty/targets_person/some_item + /// Typepath of the item we want from the target + var/obj/item/desired_type + /// Weakref to the item that matches our desired type within the target at the time of bounty creation + VAR_FINAL/datum/weakref/target_original_desired_ref + +/datum/spy_bounty/targets_person/some_item/is_valid_crewmember(mob/living/carbon/human/crewmember) + return istype(crewmember) && find_desired_thing(crewmember) + +/datum/spy_bounty/targets_person/some_item/is_stealable(atom/movable/stealing) + if(IS_WEAKREF_OF(stealing, target_original_desired_ref)) + return TRUE + if(IS_WEAKREF_OF(stealing, target_ref)) + var/mob/living/carbon/human/target = stealing + if(!target.incapacitated(IGNORE_RESTRAINTS|IGNORE_STASIS)) + return FALSE + if(find_desired_thing(target)) + return TRUE + return FALSE + +/datum/spy_bounty/targets_person/some_item/clean_up_stolen_item(atom/movable/stealing, mob/living/spy) + if(IS_WEAKREF_OF(stealing, target_original_desired_ref)) + return ..() + + ASSERT(ishuman(stealing), "[type] called clean_up_stolen_item with something that isn't a human and isn't the original item.") + + do_sparks(2, FALSE, stealing) + var/mob/living/carbon/human/stolen_from = stealing + var/obj/item/real_stolen_item = find_desired_thing(stealing) + stolen_from.Unconscious(10 SECONDS) + to_chat(stolen_from, span_warning("You feel something missing where your [real_stolen_item.name] once was.")) + return ..(real_stolen_item, spy) + +/datum/spy_bounty/targets_person/some_item/target_found(mob/crewmember) + var/obj/item/desired_thing = find_desired_thing(crewmember) + target_original_desired_ref = WEAKREF(desired_thing) + name = "[crewmember.real_name]'s [desired_thing.name]" + help = "Steal [desired_thing] from [crewmember.real_name]. \ + You can accomplish this via brute force, or by scanning them with your uplink while they are incapacitated." + return TRUE + +/// Finds the desired item type in the target crewmember. +/datum/spy_bounty/targets_person/some_item/proc/find_desired_thing(mob/living/carbon/human/crewmember) + return locate(desired_type) in crewmember.get_all_gear() + +// Steal someone's ID card +/datum/spy_bounty/targets_person/some_item/id + desired_type = /obj/item/card/id/advanced + +/datum/spy_bounty/targets_person/some_item/id/find_desired_thing(mob/living/carbon/human/crewmember) + for(var/obj/item/card/id/advanced/id in crewmember.get_all_gear()) + if(id.registered_account?.account_id == crewmember.account_id) + return id + + return null + +/datum/spy_bounty/targets_person/some_item/id/target_found(mob/crewmember) + . = ..() + name = "[crewmember.real_name]'s ID Card" + +// Steal someone's PDA +/datum/spy_bounty/targets_person/some_item/pda + desired_type = /obj/item/modular_computer/pda + +/datum/spy_bounty/targets_person/some_item/pda/find_desired_thing(mob/living/carbon/human/crewmember) + for(var/obj/item/modular_computer/pda/pda in crewmember.get_all_gear()) + if(pda.saved_identification == crewmember.real_name) + return pda + + return null + +/datum/spy_bounty/targets_person/some_item/pda/target_found(mob/crewmember) + . = ..() + name = "[crewmember.real_name]'s PDA" + +// Steal someone's heirloom +/datum/spy_bounty/targets_person/some_item/heirloom + desired_type = /obj/item + black_market_prob = 100 + +/datum/spy_bounty/targets_person/some_item/heirloom/find_desired_thing(mob/living/crewmember) + var/datum/quirk/item_quirk/family_heirloom/quirk = crewmember.get_quirk(/datum/quirk/item_quirk/family_heirloom) + return quirk?.heirloom?.resolve() + +/datum/spy_bounty/targets_person/some_item/heirloom/target_found(mob/crewmember) + . = ..() + name = "[crewmember.real_name]'s heirloom" + +// Steal a limb or organ off someone +/datum/spy_bounty/targets_person/some_item/limb_or_organ + weight = 4 // lots to pick from here + +/datum/spy_bounty/targets_person/some_item/limb_or_organ/init_bounty(datum/spy_bounty_handler/handler) + desired_type = pick( + /obj/item/bodypart/arm/left, + /obj/item/bodypart/arm/right, + /obj/item/bodypart/leg/left, + /obj/item/bodypart/leg/right, + /obj/item/organ/internal/stomach, + /obj/item/organ/internal/appendix, + /obj/item/organ/internal/liver, + /obj/item/organ/internal/eyes, + ) + return ..() + +/datum/spy_bounty/targets_person/some_item/limb_or_organ/find_desired_thing(mob/living/carbon/human/crewmember) + if(ispath(desired_type, /obj/item/bodypart)) + return locate(desired_type) in crewmember.bodyparts + if(ispath(desired_type, /obj/item/organ)) + return locate(desired_type) in crewmember.organs + return null + +/datum/spy_bounty/some_bot + theft_time = 10 SECONDS + black_market_prob = 0 + /// What typepath of bot we want to steal. + var/mob/living/simple_animal/bot/bot_type + /// Weakref to the bot we want to steal. + VAR_FINAL/datum/weakref/target_bot_ref + +/datum/spy_bounty/some_bot/get_dupe_protection_key(atom/movable/stealing) + return bot_type + +/datum/spy_bounty/some_bot/finish_cleanup(mob/living/simple_animal/bot/stealing) + if(stealing.client) + to_chat(stealing, span_deadsay("You've been stolen! You are shipped off to the black market and taken apart for spare parts...")) + stealing.investigate_log("stole by a spy (and deleted)", INVESTIGATE_DEATHS) + stealing.ghostize() + return ..() + +/datum/spy_bounty/some_bot/init_bounty(datum/spy_bounty_handler/handler) + for(var/datum/spy_bounty/some_bot/existing_bounty in handler.get_all_bounties()) + var/mob/living/simple_animal/bot/existing_bot_type = existing_bounty.bot_type + // ensures we don't get two similar bounties. + // may occasionally cast a wider net than we'd desire, but it's not that bad. + if(ispath(bot_type, initial(existing_bot_type.parent_type))) + return FALSE + + var/list/mob/living/possible_bots = list() + for(var/mob/living/bot as anything in GLOB.bots_list) + if(!is_station_level(bot.z) && !is_mining_level(bot.z)) + continue + if(!istype(bot, bot_type)) + continue + possible_bots += bot + + if(!length(possible_bots)) + return FALSE + + var/mob/living/picked = pick(possible_bots) + target_bot_ref = WEAKREF(picked) + name ||= "[picked.name] Abduction" + help ||= "Abduct the station's robot assistant [picked.name]." + return TRUE + +/datum/spy_bounty/some_bot/is_stealable(atom/movable/stealing) + return IS_WEAKREF_OF(stealing, target_bot_ref) + +/datum/spy_bounty/some_bot/beepsky + difficulty = SPY_DIFFICULTY_MEDIUM // gotta get him to stand still + bot_type = /mob/living/simple_animal/bot/secbot/beepsky/officer + help = "Abduct Officer Beepsky - commonly found patrolling the station. \ + Watch out, they may not take kindly to being scanned." + +/datum/spy_bounty/some_bot/ofitser + difficulty = SPY_DIFFICULTY_EASY + bot_type = /mob/living/simple_animal/bot/secbot/beepsky/ofitser + help = "Abduct Prison Ofitser - commonly found guarding the Gulag." + +/datum/spy_bounty/some_bot/armsky + difficulty = SPY_DIFFICULTY_HARD + bot_type = /mob/living/simple_animal/bot/secbot/beepsky/armsky + help = "Abduct Sergeant-At-Armsky - commonly found guarding the station's Armory." + +/datum/spy_bounty/some_bot/pingsky + difficulty = SPY_DIFFICULTY_HARD + bot_type = /mob/living/simple_animal/bot/secbot/pingsky + help = "Abduct Officer Pingsky - commonly found protecting the station's AI." + +/datum/spy_bounty/some_bot/scrubbs + difficulty = SPY_DIFFICULTY_EASY + bot_type = /mob/living/basic/bot/cleanbot/medbay + help = "Abduct Scrubbs, MD - commonly found mopping up blood in Medbay." + +/datum/spy_bounty/some_bot/scrubbs/can_claim(mob/user) + return !(user.mind?.assigned_role.departments_bitflags & DEPARTMENT_BITFLAG_MEDICAL) diff --git a/code/modules/antagonists/spy/spy_bounty_handler.dm b/code/modules/antagonists/spy/spy_bounty_handler.dm new file mode 100644 index 0000000000000..798719cb8a02c --- /dev/null +++ b/code/modules/antagonists/spy/spy_bounty_handler.dm @@ -0,0 +1,123 @@ +/** + * ## Spy bounty handler + * + * Singleton datum that handles determining active bounties for spies. + */ +/datum/spy_bounty_handler + /// Timer between when all bounties are refreshed. + var/refresh_time = 12 MINUTES + /// timerID of the active refresh timer. + var/refresh_timer + /// Number of times we have refreshed bounties + var/num_refreshes = 0 + /// Assoc list of items stolen in the past to how many times they have been stolen + /// Sometimes item typepaths, sometimes REFs, in general just strings that represent stolen items + var/list/all_claimed_bounty_types = list() + /// List of all items stolen in the last pool of bounties. + /// Same as above - strings that represent stolen items. + var/list/claimed_bounties_from_last_pool = list() + /// Override for the number of attempts to make a bounty. + var/num_attempts_override = 0 + + /// Assoc list that dictates how much of each bounty difficulty to give out at once. + /// Modified by the number of times we have refreshed bounties. + VAR_PRIVATE/list/base_bounties_to_give = list( + SPY_DIFFICULTY_EASY = 4, + SPY_DIFFICULTY_MEDIUM = 2, + SPY_DIFFICULTY_HARD = 2, + ) + + /// Assoc list of all active bounties. + VAR_PRIVATE/list/list/bounties = list( + SPY_DIFFICULTY_EASY = list(), + SPY_DIFFICULTY_MEDIUM = list(), + SPY_DIFFICULTY_HARD = list(), + ) + + /// Assoc list of all possible bounties for each difficulty, weighted. + /// This is static, no bounty types are removed from this list. + VAR_PRIVATE/list/list/bounty_types = list( + SPY_DIFFICULTY_EASY = list(), + SPY_DIFFICULTY_MEDIUM = list(), + SPY_DIFFICULTY_HARD = list(), + ) + + /// Assoc list of all uplink items possible to be given as bounties for each difficulty. + /// This is not static, as bounties are complete uplink items will be removed from this list. + var/list/list/possible_uplink_items = list( + SPY_DIFFICULTY_EASY = list(), + SPY_DIFFICULTY_MEDIUM = list(), + SPY_DIFFICULTY_HARD = list(), + ) + +/datum/spy_bounty_handler/New() + for(var/datum/spy_bounty/bounty as anything in subtypesof(/datum/spy_bounty)) + var/weight = initial(bounty.weight) + var/difficulty = initial(bounty.difficulty) + if(weight <= 0 || !islist(bounty_types[difficulty])) + continue + bounty_types[difficulty][bounty] = weight + + for(var/datum/uplink_item/item as anything in SStraitor.uplink_items) + if(isnull(item.item) || item.item == ABSTRACT_UPLINK_ITEM) + continue + if(!(item.purchasable_from & UPLINK_SPY)) + continue + // This will have some overlap, and that's intentional - + // Adds some variety, rare moments where you can get a hard reward for an easier bounty (or visa versa) + if(item.cost <= SPY_LOWER_COST_THRESHOLD) + possible_uplink_items[SPY_DIFFICULTY_EASY] += item + if(item.cost >= SPY_LOWER_COST_THRESHOLD && item.cost <= SPY_UPPER_COST_THRESHOLD) + possible_uplink_items[SPY_DIFFICULTY_MEDIUM] += item + if(item.cost >= SPY_UPPER_COST_THRESHOLD) + possible_uplink_items[SPY_DIFFICULTY_HARD] += item + + refresh_bounty_list() + +/// Helper that returns a list of all active bounties in a single list, regardless of difficulty. +/datum/spy_bounty_handler/proc/get_all_bounties() as /list + var/list/all_bounties = list() + for(var/difficulty in bounties) + all_bounties += bounties[difficulty] + + return all_bounties + +/// Refreshes all active bounties for each difficulty, no matter if they were complete or not. +/// Then recursively calls itself via a timer. +/datum/spy_bounty_handler/proc/refresh_bounty_list() + PRIVATE_PROC(TRUE) + + var/list/bounties_to_give = base_bounties_to_give.Copy() + + if(num_refreshes < base_bounties_to_give[SPY_DIFFICULTY_HARD]) + bounties_to_give[SPY_DIFFICULTY_HARD] = num_refreshes + bounties_to_give[SPY_DIFFICULTY_MEDIUM] += (base_bounties_to_give[SPY_DIFFICULTY_HARD] - num_refreshes) + + for(var/difficulty in bounties) + QDEL_LIST(bounties[difficulty]) + + var/list/pool = bounty_types[difficulty] + var/amount_to_give = bounties_to_give[difficulty] + var/failed_attempts = num_attempts_override || clamp(amount_to_give * 4, 10, 25) // more potential bounties = more attempts to make one + while(amount_to_give > 0 && failed_attempts > 0) + var/picked_bounty = pick_weight(pool) + var/datum/spy_bounty/bounty = new picked_bounty(src) + if(bounty.initalized) + amount_to_give -= 1 + bounties[difficulty] += bounty + + else + failed_attempts -= 1 + qdel(bounty) + + claimed_bounties_from_last_pool.Cut() + num_refreshes += 1 + refresh_timer = addtimer(CALLBACK(src, PROC_REF(refresh_bounty_list)), refresh_time, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE) + +/// Forces a refresh of the bounty list. +/// Counts towards [num_refreshes]. +/datum/spy_bounty_handler/proc/force_refresh() + if(refresh_timer) + deltimer(refresh_timer) + + refresh_bounty_list() diff --git a/code/modules/antagonists/spy/spy_uplink.dm b/code/modules/antagonists/spy/spy_uplink.dm new file mode 100644 index 0000000000000..2a9d9b9b14e9b --- /dev/null +++ b/code/modules/antagonists/spy/spy_uplink.dm @@ -0,0 +1,208 @@ +/** + * ## Spy uplink + * + * Applied to items similar to traitor uplinks. + * + * Used for spies to complete bounties. + */ +/datum/component/spy_uplink + /// Weakref to the spy antag datum which owns this uplink + var/datum/weakref/spy_ref + /// The handler which manages all bounties across all spies. + var/static/datum/spy_bounty_handler/handler + +/datum/component/spy_uplink/Initialize(datum/antagonist/spy/spy) + if(!isitem(parent)) + return COMPONENT_INCOMPATIBLE + + spy_ref = WEAKREF(spy) + + if(isnull(handler)) + handler = new() + +/datum/component/spy_uplink/RegisterWithParent() + RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) + RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, PROC_REF(on_attack_self)) + RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK_SECONDARY, PROC_REF(on_pre_attack_secondary)) + RegisterSignal(parent, COMSIG_TABLET_CHECK_DETONATE, PROC_REF(block_pda_bombs)) + +/datum/component/spy_uplink/UnregisterFromParent() + UnregisterSignal(parent, list( + COMSIG_ATOM_EXAMINE, + COMSIG_ITEM_ATTACK_SELF, + COMSIG_ITEM_PRE_ATTACK_SECONDARY, + COMSIG_TABLET_CHECK_DETONATE, + )) + +/// Checks that the passed mob is the owner of this uplink. +/datum/component/spy_uplink/proc/is_our_spy(mob/whoever) + var/datum/antagonist/spy/spy_datum = spy_ref?.resolve() + return spy_datum?.owner.current == whoever + +/datum/component/spy_uplink/proc/on_examine(obj/item/source, mob/user, list/examine_list) + SIGNAL_HANDLER + + if(!is_our_spy(user)) + return + examine_list += span_notice("You recognize this as your spy uplink.") + examine_list += span_notice("- [EXAMINE_HINT("Use it in hand")] to view your bounty list.") + examine_list += span_notice("- [EXAMINE_HINT("Right click")] with it on a bounty target to claim it.") + +/datum/component/spy_uplink/proc/block_pda_bombs(obj/item/source) + SIGNAL_HANDLER + + return COMPONENT_TABLET_NO_DETONATE + +/datum/component/spy_uplink/proc/on_attack_self(obj/item/source, mob/user) + SIGNAL_HANDLER + + if(is_our_spy(user)) + INVOKE_ASYNC(src, TYPE_PROC_REF(/datum, ui_interact), user) + return NONE + +/datum/component/spy_uplink/proc/on_pre_attack_secondary(obj/item/source, atom/target, mob/living/user, params) + SIGNAL_HANDLER + + if(!ismovable(target)) + return NONE + if(!is_our_spy(user)) + return NONE + if(!try_steal(target, user)) + return NONE + return COMPONENT_CANCEL_ATTACK_CHAIN + +/// Checks if the passed atom is something that can be stolen according to one of the active bounties. +/// If so, starts the stealing process. +/datum/component/spy_uplink/proc/try_steal(atom/movable/stealing, mob/living/spy) + for(var/datum/spy_bounty/bounty as anything in handler.get_all_bounties()) + if(!bounty.can_claim(spy)) + continue + if(!bounty.is_stealable(stealing)) + continue + if(bounty.claimed) + stealing.balloon_alert(spy, "bounty already claimed!") + return TRUE + if(DOING_INTERACTION(spy, REF(src))) + spy.balloon_alert(spy, "already scanning!") // Only shown if they're trying to scan two valid targets + return TRUE + SEND_SIGNAL(stealing, COMSIG_MOVABLE_SPY_STEALING, spy, bounty) + INVOKE_ASYNC(src, PROC_REF(start_stealing), stealing, spy, bounty) + return TRUE + + return FALSE + +/// Wraps the stealing process in a scanning effect. +/datum/component/spy_uplink/proc/start_stealing(atom/movable/stealing, mob/living/spy, datum/spy_bounty/bounty) + if(!isturf(stealing.loc) && stealing.loc != spy) + to_chat(spy, span_warning("Your uplinks blinks red: [stealing] cannot be extracted from there.")) + return FALSE + + log_combat(spy, stealing, "started stealing", parent, "(spy bounty)") + playsound(stealing, 'sound/items/pshoom.ogg', 33, vary = TRUE, extrarange = SILENCED_SOUND_EXTRARANGE, frequency = 0.33, ignore_walls = FALSE) + + var/obj/effect/scan_effect/active_scan_effect = new(stealing.loc) + active_scan_effect.appearance = stealing.appearance + active_scan_effect.dir = stealing.dir + active_scan_effect.makeHologram() + SET_PLANE_EXPLICIT(active_scan_effect, stealing.plane, stealing) + active_scan_effect.layer = stealing.layer + 0.1 + + var/obj/effect/scan_effect/cone/active_scan_cone + if(isturf(stealing.loc) && isturf(spy.loc)) // Cone doesn't make sense if its being held or something + active_scan_cone = new(spy.loc) + var/angle = round(get_angle(spy, stealing), 10) + if(angle > 180 && angle < 360) + active_scan_cone.pixel_x -= 16 + else if(angle < 180 && angle > 0) + active_scan_cone.pixel_x += 16 + if(angle > 90 && angle < 270) + active_scan_cone.pixel_y -= 16 + else if(angle < 90 || angle > 270) + active_scan_cone.pixel_y += 16 + active_scan_cone.transform = active_scan_cone.transform.Turn(angle) + active_scan_cone.alpha = 0 + animate(active_scan_cone, time = 0.5 SECONDS, alpha = initial(active_scan_cone.alpha)) + + . = steal_process(stealing, spy, bounty) + qdel(active_scan_effect) + qdel(active_scan_cone) + return . + +/// Attempts to steal the passed atom in accordance with the passed bounty. +/// If successful, proceeds to complete the bounty. +/datum/component/spy_uplink/proc/steal_process(atom/movable/stealing, mob/living/spy, datum/spy_bounty/bounty) + spy.visible_message( + span_warning("[spy] starts scanning [stealing] with a strange device..."), + span_notice("You start scanning [stealing], preparing it for extraction."), + ) + + if(!do_after(spy, bounty.theft_time, stealing, interaction_key = REF(src))) + return FALSE + if(bounty.claimed) + to_chat(spy, span_warning("Your uplinks blinks red: The bounty for [stealing] has been claimed by another spy!")) + return FALSE + if(spy.is_holding(stealing) && !spy.dropItemToGround(stealing)) + to_chat(spy, span_warning("Your uplinks blinks red: [stealing] seems stuck to your hand!")) + return FALSE + + var/bounty_key = bounty.get_dupe_protection_key(stealing) + handler.all_claimed_bounty_types[bounty_key] += 1 + handler.claimed_bounties_from_last_pool[bounty_key] = TRUE + + bounty.clean_up_stolen_item(stealing, spy, handler) + bounty.claimed = TRUE + + var/atom/movable/reward = bounty.reward_item.spawn_item_for_generic_use(spy) + if(isitem(reward)) + spy.put_in_hands(reward) + + to_chat(spy, span_notice("Bounty complete! You have been rewarded with \a [reward].\ + [reward.loc == spy ? "" : " Find it at your feet."]")) + + playsound(parent, 'sound/machines/wewewew.ogg', 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) + + log_combat(spy, stealing, "stole", parent, "(spy bounty)") + log_spy("[key_name(spy)] completed the bounty [bounty.name] of difficulty [bounty.difficulty] by stealing [stealing] for \a [reward].") + SSblackbox.record_feedback("nested tally", "spy_bounty", 1, list("[stealing.type]", "[bounty.type]", "[bounty.difficulty]", "[bounty.reward_item.type]")) + + var/datum/antagonist/spy/spy_datum = spy_ref?.resolve() + if(!isnull(spy_datum)) + // "When" TGUI roundend is finished, a list of all bounties complete and their rewards should be put in a collapsible, + // otherwise it's just too much information to display cleanly. (That's why we're only displaying number and rewards) + spy_datum.bounties_claimed += 1 + spy_datum.all_loot += bounty.reward_item.name + + return TRUE + +/datum/component/spy_uplink/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "SpyUplink") + ui.open() + +/datum/component/spy_uplink/ui_data(mob/user) + var/list/data = list() + + data["bounties"] = list() + for(var/datum/spy_bounty/bounty as anything in handler.get_all_bounties()) + UNTYPED_LIST_ADD(data["bounties"], bounty.to_ui_data(user)) + data["time_left"] = timeleft(handler.refresh_timer) + + return data + +/datum/component/spy_uplink/ui_status(mob/user, datum/ui_state/state) + if(isobserver(user) && user.client?.holder) + return UI_UPDATE + return ..() + +/obj/effect/scan_effect + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + anchored = TRUE + layer = ABOVE_ALL_MOB_LAYER + +/obj/effect/scan_effect/cone + name = "holoray" + icon = 'icons/effects/effects.dmi' + icon_state = "scan_beam" + color = "#3ba0ff" + alpha = 200 diff --git a/code/modules/antagonists/wizard/equipment/soulstone.dm b/code/modules/antagonists/wizard/equipment/soulstone.dm index 02357d22e8b41..78cd4f4929c8a 100644 --- a/code/modules/antagonists/wizard/equipment/soulstone.dm +++ b/code/modules/antagonists/wizard/equipment/soulstone.dm @@ -312,15 +312,17 @@ return TRUE to_chat(user, "[span_userdanger("Capture failed!")]: The soul has already fled its mortal frame. You attempt to bring it back...") - - var/datum/callback/to_call = CALLBACK(src, PROC_REF(on_poll_concluded), user, victim) - AddComponent(/datum/component/orbit_poll, \ - ignore_key = POLL_IGNORE_SHADE, \ - job_bans = ROLE_CULTIST, \ - to_call = to_call, \ - title = "A shade" \ + var/mob/chosen_one = SSpolling.poll_ghosts_for_target( + check_jobban = ROLE_CULTIST, + poll_time = 20 SECONDS, + checked_target = src, + ignore_category = POLL_IGNORE_SHADE, + alert_pic = /mob/living/basic/shade, + jump_target = src, + role_name_text = "a shade", + chat_text_border_icon = /mob/living/basic/shade, ) - + on_poll_concluded(user, victim, chosen_one) return TRUE //it'll probably get someone ;) ///captures a shade that was previously released from a soulstone. diff --git a/code/modules/antagonists/wizard/grand_ritual/finales/immortality.dm b/code/modules/antagonists/wizard/grand_ritual/finales/immortality.dm index 92489145fda97..85267c0333c45 100644 --- a/code/modules/antagonists/wizard/grand_ritual/finales/immortality.dm +++ b/code/modules/antagonists/wizard/grand_ritual/finales/immortality.dm @@ -148,6 +148,7 @@ color = COLOR_PALE_GREEN light_range = 2 light_color = COLOR_PALE_GREEN + resistance_flags = parent_type::resistance_flags | SHUTTLE_CRUSH_PROOF /// Who are we reviving? var/mob/living/corpse /// Who if anyone is playing as them? diff --git a/code/modules/assembly/flash.dm b/code/modules/assembly/flash.dm index 5319d4465e2ab..659bcec50cf97 100644 --- a/code/modules/assembly/flash.dm +++ b/code/modules/assembly/flash.dm @@ -171,7 +171,7 @@ visible_message(span_danger("[user] blinds [flashed] with the flash!"), span_userdanger("[user] blinds you with the flash!")) //easy way to make sure that you can only long stun someone who is facing in your direction flashed.adjustStaminaLoss(rand(80, 120) * (1 - (deviation * 0.5))) - flashed.Paralyze(rand(25, 50) * (1 - (deviation * 0.5))) + flashed.Knockdown(rand(25, 50) * (1 - (deviation * 0.5))) SEND_SIGNAL(user, COMSIG_MOB_SUCCESSFUL_FLASHED_CARBON, flashed, src, deviation) else if(user) diff --git a/code/modules/assembly/mousetrap.dm b/code/modules/assembly/mousetrap.dm index 1f760e29b8959..1d8936e6068da 100644 --- a/code/modules/assembly/mousetrap.dm +++ b/code/modules/assembly/mousetrap.dm @@ -89,7 +89,7 @@ to_chat(user, span_warning("Your hand slips, setting off the trigger!")) pulse() update_appearance() - playsound(src, 'sound/weapons/handcuffs.ogg', 30, TRUE, -3) + playsound(loc, 'sound/weapons/handcuffs.ogg', 30, TRUE, -3) /obj/item/assembly/mousetrap/update_icon_state() icon_state = "mousetrap[armed ? "armed" : ""]" diff --git a/code/modules/assembly/voice.dm b/code/modules/assembly/voice.dm index 52ef29f38e5d6..de0954c960e5e 100644 --- a/code/modules/assembly/voice.dm +++ b/code/modules/assembly/voice.dm @@ -35,7 +35,7 @@ /obj/item/assembly/voice/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods = list(), message_range) . = ..() - if(message_mods[WHISPER_MODE]) //Too quiet lad + if(message_mods[WHISPER_MODE] || message_mods[MODE_RELAY]) //Too quiet lad return FALSE if(speaker == src) return FALSE diff --git a/code/modules/atmospherics/gasmixtures/gas_mixture.dm b/code/modules/atmospherics/gasmixtures/gas_mixture.dm index 87523034f3ecc..cfb87ce7cb3fe 100644 --- a/code/modules/atmospherics/gasmixtures/gas_mixture.dm +++ b/code/modules/atmospherics/gasmixtures/gas_mixture.dm @@ -184,7 +184,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) if(amount <= 0) return null var/ratio = amount / sum - var/datum/gas_mixture/removed = new type + var/datum/gas_mixture/removed = new type(volume) var/list/removed_gases = removed.gases //accessing datum vars is slower than proc vars removed.temperature = temperature @@ -206,7 +206,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) ratio = min(ratio, 1) var/list/cached_gases = gases - var/datum/gas_mixture/removed = new type + var/datum/gas_mixture/removed = new type(volume) var/list/removed_gases = removed.gases //accessing datum vars is slower than proc vars removed.temperature = temperature diff --git a/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm b/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm index c8e93257b8064..6bb07f4fbc1eb 100644 --- a/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm +++ b/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm @@ -186,7 +186,7 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm) . += span_notice("Right-click to [locked ? "unlock" : "lock"] the interface.") /obj/machinery/airalarm/ui_status(mob/user, datum/ui_state/state) - if(user.has_unlimited_silicon_privilege && aidisabled) + if(HAS_SILICON_ACCESS(user) && aidisabled) to_chat(user, "AI control has been disabled.") else if(!shorted) return ..() @@ -227,7 +227,7 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm) var/data = list() data["locked"] = locked - data["siliconUser"] = user.has_unlimited_silicon_privilege + data["siliconUser"] = HAS_SILICON_ACCESS(user) data["emagged"] = (obj_flags & EMAGGED ? 1 : 0) data["dangerLevel"] = danger_level data["atmosAlarm"] = !!my_area.active_alarms[ALARM_ATMOS] @@ -288,7 +288,7 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm) singular_tlv["hazard_max"] = tlv.hazard_max data["tlvSettings"] += list(singular_tlv) - if(!locked || user.has_unlimited_silicon_privilege) + if(!locked || HAS_SILICON_ACCESS(user)) data["vents"] = list() for(var/obj/machinery/atmospherics/components/unary/vent_pump/vent as anything in my_area.air_vents) data["vents"] += list(list( @@ -345,7 +345,7 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm) if(. || buildstage != AIR_ALARM_BUILD_COMPLETE) return - if((locked && !usr.has_unlimited_silicon_privilege) || (usr.has_unlimited_silicon_privilege && aidisabled)) + if((locked && !HAS_SILICON_ACCESS(usr)) || (HAS_SILICON_ACCESS(usr) && aidisabled)) return var/mob/user = usr diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm index be455ea6d4709..82760e75a6259 100644 --- a/code/modules/atmospherics/machinery/atmosmachinery.dm +++ b/code/modules/atmospherics/machinery/atmosmachinery.dm @@ -54,6 +54,10 @@ ///Whether it can be painted var/paintable = TRUE + ///Whether it will generate cap sprites when hidden + var/has_cap_visuals = FALSE + ///Cap overlay that is being added to turf's `vis_contents`, `null` if pipe was never hidden or has no valid connections + var/obj/effect/overlay/cap_visual/cap_overlay ///Is the thing being rebuilt by SSair or not. Prevents list bloat var/rebuilding = FALSE @@ -106,6 +110,10 @@ if(isturf(loc)) turf_loc = loc turf_loc.add_blueprints_preround(src) + + if(hide) + RegisterSignal(src, COMSIG_OBJ_HIDE, PROC_REF(on_hide)) + SSspatial_grid.add_grid_awareness(src, SPATIAL_GRID_CONTENTS_TYPE_ATMOS) SSspatial_grid.add_grid_membership(src, turf_loc, SPATIAL_GRID_CONTENTS_TYPE_ATMOS) if(init_processing) @@ -119,11 +127,22 @@ SSair.stop_processing_machine(src) SSair.rebuild_queue -= src - if(pipe_vision_img) - qdel(pipe_vision_img) + QDEL_NULL(pipe_vision_img) + QDEL_NULL(cap_overlay) return ..() - //return QDEL_HINT_FINDREFERENCE + +/** + * Handler for `COMSIG_OBJ_HIDE`, connects only if `hide` is set to `TRUE`. Calls `update_cap_visuals` on pipe and its connected nodes + */ +/obj/machinery/atmospherics/proc/on_hide(datum/source, underfloor_accessibility) + SHOULD_CALL_PARENT(TRUE) + SIGNAL_HANDLER + + for(var/obj/machinery/atmospherics/node in nodes) + node.update_cap_visuals() + + update_cap_visuals() /** * Run when you update the conditions in which an /atom might want to start reacting to its turf's air @@ -205,8 +224,9 @@ update_appearance() /obj/machinery/atmospherics/update_icon() - . = ..() update_layer() + update_cap_visuals() + return ..() /** * Find a connecting /obj/machinery/atmospherics in specified direction, called by relaymove() @@ -616,6 +636,49 @@ /obj/machinery/atmospherics/proc/update_layer() return +/** + * Handles cap overlay addition and removal, won't do anything if `has_cap_visuals` is set to `FALSE` + */ +/obj/machinery/atmospherics/proc/update_cap_visuals() + if(!has_cap_visuals) + return + + var/turf/our_turf = get_turf(src) + our_turf.vis_contents -= cap_overlay + + var/connections = NONE + for(var/obj/machinery/atmospherics/node in nodes) + if(HAS_TRAIT(node, TRAIT_UNDERFLOOR)) + continue + + if(isplatingturf(get_turf(node))) + continue + + var/connected_dir = get_dir(src, node) + connections |= connected_dir + + if(connections == NONE) + return + + var/bitfield = CARDINAL_TO_PIPECAPS(connections) + bitfield |= ((~connections) & ALL_CARDINALS) + + if(isnull(cap_overlay)) + cap_overlay = new + + SET_PLANE_EXPLICIT(cap_overlay, initial(plane), our_turf) + + cap_overlay.color = pipe_color + cap_overlay.layer = layer + cap_overlay.icon_state = "[bitfield]_[piping_layer]" + + our_turf.vis_contents += cap_overlay + +/obj/effect/overlay/cap_visual + appearance_flags = KEEP_APART + vis_flags = VIS_INHERIT_ID + icon = 'icons/obj/pipes_n_cables/!pipes_bitmask.dmi' + /** * Called by the RPD.dm pre_attack() * Arguments: diff --git a/code/modules/atmospherics/machinery/components/components_base.dm b/code/modules/atmospherics/machinery/components/components_base.dm index e72d72b3d5955..b4e5d88d62c71 100644 --- a/code/modules/atmospherics/machinery/components/components_base.dm +++ b/code/modules/atmospherics/machinery/components/components_base.dm @@ -32,12 +32,6 @@ component_mixture.volume = 200 airs[i] = component_mixture -/obj/machinery/atmospherics/components/Initialize(mapload) - . = ..() - - if(hide) - RegisterSignal(src, COMSIG_OBJ_HIDE, PROC_REF(hide_pipe)) - // Iconnery /** @@ -46,11 +40,14 @@ /obj/machinery/atmospherics/components/proc/update_icon_nopipes() return +/obj/machinery/atmospherics/components/on_hide(datum/source, underfloor_accessibility) + hide_pipe(underfloor_accessibility) + return ..() + /** - * Called in Initialize(), set the showpipe var to true or false depending on the situation, calls update_icon() + * Called in on_hide(), set the showpipe var to true or false depending on the situation, calls update_icon() */ -/obj/machinery/atmospherics/components/proc/hide_pipe(datum/source, underfloor_accessibility) - SIGNAL_HANDLER +/obj/machinery/atmospherics/components/proc/hide_pipe(underfloor_accessibility) showpipe = !!underfloor_accessibility if(showpipe) REMOVE_TRAIT(src, TRAIT_UNDERFLOOR, REF(src)) diff --git a/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm b/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm index ea20f2eeb66a8..4161a30ed7d72 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm @@ -10,6 +10,7 @@ hide = TRUE layer = GAS_SCRUBBER_LAYER pipe_state = "injector" + has_cap_visuals = TRUE resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF //really helpful in building gas chambers for xenomorphs idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.25 @@ -74,6 +75,8 @@ if(showpipe) // everything is already shifted so don't shift the cap add_overlay(get_pipe_image(icon, "inje_cap", initialize_directions, pipe_color)) + else + PIPING_LAYER_SHIFT(src, PIPING_LAYER_DEFAULT) if(!nodes[1] || !on || !is_operational) icon_state = "inje_off" diff --git a/code/modules/atmospherics/machinery/components/unary_devices/passive_vent.dm b/code/modules/atmospherics/machinery/components/unary_devices/passive_vent.dm index f461cbe8988f8..17f6c761f129d 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/passive_vent.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/passive_vent.dm @@ -10,6 +10,7 @@ shift_underlay_only = FALSE pipe_state = "pvent" + has_cap_visuals = TRUE vent_movement = VENTCRAWL_ALLOWED | VENTCRAWL_CAN_SEE | VENTCRAWL_ENTRANCE_ALLOWED /obj/machinery/atmospherics/components/unary/passive_vent/update_icon_nopipes() @@ -17,6 +18,8 @@ if(showpipe) var/image/cap = get_pipe_image(icon, "vent_cap", initialize_directions, pipe_color) add_overlay(cap) + else + PIPING_LAYER_SHIFT(src, PIPING_LAYER_DEFAULT) icon_state = "passive_vent" /obj/machinery/atmospherics/components/unary/passive_vent/process_atmos() diff --git a/code/modules/atmospherics/machinery/components/unary_devices/portables_connector.dm b/code/modules/atmospherics/machinery/components/unary_devices/portables_connector.dm index d5eada4e73f89..f47d6d5b069ca 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/portables_connector.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/portables_connector.dm @@ -13,6 +13,8 @@ pipe_flags = PIPING_ONE_PER_TURF pipe_state = "connector" + has_cap_visuals = TRUE + custom_reconcilation = TRUE ///Reference to the connected device @@ -29,11 +31,13 @@ return ..() /obj/machinery/atmospherics/components/unary/portables_connector/update_icon_nopipes() - icon_state = "connector" + cut_overlays() if(showpipe) - cut_overlays() var/image/cap = get_pipe_image(icon, "connector_cap", initialize_directions, pipe_color) add_overlay(cap) + else + PIPING_LAYER_SHIFT(src, PIPING_LAYER_DEFAULT) + icon_state = "connector" /obj/machinery/atmospherics/components/unary/portables_connector/process_atmos() if(!connected_device) diff --git a/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm b/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm index 46adfee054e6e..aa890b0b574a0 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm @@ -16,8 +16,6 @@ layer = OBJ_LAYER circuit = /obj/item/circuitboard/machine/thermomachine - hide = TRUE - move_resist = MOVE_RESIST_DEFAULT vent_movement = NONE pipe_flags = PIPING_ONE_PER_TURF diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm index f3c5563fd3afd..bece67572b6f5 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm @@ -14,6 +14,7 @@ hide = TRUE shift_underlay_only = FALSE pipe_state = "uvent" + has_cap_visuals = TRUE vent_movement = VENTCRAWL_ALLOWED | VENTCRAWL_CAN_SEE | VENTCRAWL_ENTRANCE_ALLOWED // vents are more complex machinery and so are less resistant to damage max_integrity = 100 diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm index 50054417362d2..102728fb59149 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm @@ -12,6 +12,7 @@ hide = TRUE shift_underlay_only = FALSE pipe_state = "scrubber" + has_cap_visuals = TRUE vent_movement = VENTCRAWL_ALLOWED | VENTCRAWL_CAN_SEE | VENTCRAWL_ENTRANCE_ALLOWED processing_flags = NONE diff --git a/code/modules/atmospherics/machinery/pipes/pipe_spritesheet_helper.dm b/code/modules/atmospherics/machinery/pipes/pipe_spritesheet_helper.dm index 9642442a9733f..6b1997cc3c718 100644 --- a/code/modules/atmospherics/machinery/pipes/pipe_spritesheet_helper.dm +++ b/code/modules/atmospherics/machinery/pipes/pipe_spritesheet_helper.dm @@ -18,6 +18,13 @@ "[WEST]"=icon('icons/obj/pipes_n_cables/pipe_template_pieces.dmi', "damage_mask", WEST), ) + var/static/list/icon/cap_masks = list( + "[NORTH]" = icon('icons/obj/pipes_n_cables/pipe_template_pieces.dmi', "cap_mask", NORTH), + "[EAST]" = icon('icons/obj/pipes_n_cables/pipe_template_pieces.dmi', "cap_mask", EAST), + "[SOUTH]" = icon('icons/obj/pipes_n_cables/pipe_template_pieces.dmi', "cap_mask", SOUTH), + "[WEST]" = icon('icons/obj/pipes_n_cables/pipe_template_pieces.dmi', "cap_mask", WEST), + ) + var/icon/generated_icons /datum/pipe_icon_generator/proc/Start(icon_state_suffix="") @@ -85,6 +92,34 @@ outputs[damaged] = "[icon_state_dirs]_[layer]" return outputs +/datum/pipe_icon_generator/proc/generate_capped(icon/working, layer, dirs, x_offset=1, y_offset=1) + var/list/outputs = list() + var/list/completed = list() + for(var/combined_dirs in 1 to 15) + combined_dirs &= dirs + + var/completion_key = "[combined_dirs]" + if(completed[completion_key] || (combined_dirs == NONE)) + continue + + completed[completion_key] = TRUE + + var/icon/capped_mask = icon('icons/obj/pipes_n_cables/pipe_template_pieces.dmi', "blank_mask") + for(var/i in 0 to 3) + var/dir = 1 << i + if(!(combined_dirs & dir)) + continue + + var/icon/cap_mask = cap_masks["[dir]"] + capped_mask.Blend(cap_mask, ICON_OVERLAY, x_offset, y_offset) + + var/icon/capped = icon(working) + capped.Blend(capped_mask, ICON_MULTIPLY) + + var/icon_state_dirs = (dirs & ~combined_dirs) | CARDINAL_TO_PIPECAPS(combined_dirs) + outputs[capped] = "[icon_state_dirs]_[layer]" + + return outputs /datum/pipe_icon_generator/proc/GeneratePipeStraight(icon_state_suffix, layer, combined_dirs) var/list/output = list() @@ -97,8 +132,10 @@ switch(combined_dirs) if(NORTH | SOUTH) output += GenerateDamaged(working, layer, combined_dirs, y_offset=offset) + output += generate_capped(working, layer, combined_dirs, y_offset=offset) if(EAST | WEST) output += GenerateDamaged(working, layer, combined_dirs, x_offset=offset) + output += generate_capped(working, layer, combined_dirs, x_offset=offset) return output @@ -117,6 +154,7 @@ output[working] = "[combined_dirs]_[layer]" output += GenerateDamaged(working, layer, combined_dirs) + output += generate_capped(working, layer, combined_dirs) return output @@ -135,6 +173,7 @@ output[working] = "[combined_dirs]_[layer]" output += GenerateDamaged(working, layer, combined_dirs) + output += generate_capped(working, layer, combined_dirs) return output @@ -144,5 +183,6 @@ output[working] = "[combined_dirs]_[layer]" output += GenerateDamaged(working, layer, combined_dirs) + output += generate_capped(working, layer, combined_dirs) return output diff --git a/code/modules/atmospherics/machinery/pipes/smart.dm b/code/modules/atmospherics/machinery/pipes/smart.dm index 7c530bace5fcf..ce4dc0e36a51e 100644 --- a/code/modules/atmospherics/machinery/pipes/smart.dm +++ b/code/modules/atmospherics/machinery/pipes/smart.dm @@ -10,6 +10,8 @@ GLOBAL_LIST_INIT(atmos_components, typecacheof(list(/obj/machinery/atmospherics) device_type = QUATERNARY construction_type = /obj/item/pipe/quaternary pipe_state = "manifold4w" + has_cap_visuals = TRUE + ///Current active connections var/connections = NONE diff --git a/code/modules/awaymissions/gateway.dm b/code/modules/awaymissions/gateway.dm index e07a9963179d1..72cb8982d3436 100644 --- a/code/modules/awaymissions/gateway.dm +++ b/code/modules/awaymissions/gateway.dm @@ -311,7 +311,7 @@ GLOBAL_LIST_EMPTY(gateway_destinations) density = TRUE use_power = NO_POWER_USE -/obj/machinery/gateway/away/interact(mob/user, special_state) +/obj/machinery/gateway/away/interact(mob/user) . = ..() if(!target) if(!GLOB.the_gateway) diff --git a/code/modules/bitrunning/objects/byteforge.dm b/code/modules/bitrunning/objects/byteforge.dm index 5f089bf66ca04..f8212b7666b99 100644 --- a/code/modules/bitrunning/objects/byteforge.dm +++ b/code/modules/bitrunning/objects/byteforge.dm @@ -63,5 +63,5 @@ /obj/machinery/byteforge/proc/start_to_spawn(obj/cache) flicker() - addtimer(CALLBACK(src, PROC_REF(spawn_cache), cache), 1 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE) + addtimer(CALLBACK(src, PROC_REF(spawn_cache), cache), 1 SECONDS) diff --git a/code/modules/bitrunning/objects/landmarks.dm b/code/modules/bitrunning/objects/landmarks.dm index 3a90939dae1b2..3b38493edfffa 100644 --- a/code/modules/bitrunning/objects/landmarks.dm +++ b/code/modules/bitrunning/objects/landmarks.dm @@ -22,6 +22,11 @@ name = "Bitrunning crate spawn" icon_state = "crate" +/// Where you want secondary objectives to spawn +/obj/effect/landmark/bitrunning/curiosity_spawn + name = "Bitrunning curiosity spawn" + icon_state = "crate" + ///Swaps the locations of an encrypted crate in the area with another randomly selected crate. ///Randomizes names, so you have to inspect crates manually. /obj/effect/landmark/bitrunning/crate_replacer diff --git a/code/modules/bitrunning/objects/loot_box.dm b/code/modules/bitrunning/objects/loot_box.dm new file mode 100644 index 0000000000000..39209c33d97f9 --- /dev/null +++ b/code/modules/bitrunning/objects/loot_box.dm @@ -0,0 +1,53 @@ +/obj/item/storage/lockbox/bitrunning + name = "base class curiosity" + desc = "Talk to a coder." + req_access = list(ACCESS_INACCESSIBLE) + icon_state = "bitrunning+l" + inhand_icon_state = "bitrunning" + base_icon_state = "bitrunning" + icon_locked = "bitrunning+l" + icon_closed = "bitrunning" + icon_broken = "bitrunning+b" + +/obj/item/storage/lockbox/bitrunning/encrypted + name = "encrypted curiosity" + desc = "Needs to be decrypted at the safehouse to be opened." + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + /// Path for the loot we are assigned + var/loot_path + +/obj/item/storage/lockbox/bitrunning/encrypted/emag_act(mob/user, obj/item/card/emag/emag_card) + return FALSE + +/obj/item/storage/lockbox/bitrunning/decrypted + name = "decrypted curiosity" + desc = "Compiled from the virtual domain. An extra reward of a successful bitrunner." + /// What virtual domain did we come from. + var/datum/lazy_template/virtual_domain/source_domain + +/obj/item/storage/lockbox/bitrunning/decrypted/Initialize( + mapload, + datum/lazy_template/virtual_domain/completed_domain, + ) + + if(isnull(completed_domain)) + log_runtime("Decrypted curiosity was created with no source domain.") + return INITIALIZE_HINT_QDEL + + if(!istype(completed_domain, /datum/lazy_template/virtual_domain)) // Check if this is a proper virtual domain before doing anything with it + log_runtime("Decrypted curiosity was created with an invalid source domain. [completed_domain.name] ([completed_domain.type]).") + return INITIALIZE_HINT_QDEL + + source_domain = completed_domain + + . = ..() + atom_storage.max_specific_storage = WEIGHT_CLASS_NORMAL + atom_storage.max_slots = 1 + atom_storage.max_total_storage = 3 + atom_storage.locked = STORAGE_NOT_LOCKED + icon_state = icon_closed + playsound(src, 'sound/magic/blink.ogg', 50, TRUE) + +/obj/item/storage/lockbox/bitrunning/decrypted/PopulateContents() + var/choice = SSbitrunning.pick_secondary_loot(source_domain) + new choice(src) diff --git a/code/modules/bitrunning/objects/loot_crate.dm b/code/modules/bitrunning/objects/loot_crate.dm index a0a74ecb4b921..db3f927e02153 100644 --- a/code/modules/bitrunning/objects/loot_crate.dm +++ b/code/modules/bitrunning/objects/loot_crate.dm @@ -42,11 +42,11 @@ if(isnull(completed_domain)) return - PopulateContents(completed_domain.reward_points, completed_domain.extra_loot, rewards_multiplier) + PopulateContents(completed_domain.reward_points, completed_domain.completion_loot, rewards_multiplier) -/obj/structure/closet/crate/secure/bitrunning/decrypted/PopulateContents(reward_points, list/extra_loot, rewards_multiplier) +/obj/structure/closet/crate/secure/bitrunning/decrypted/PopulateContents(reward_points, list/completion_loot, rewards_multiplier) . = ..() - spawn_loot(extra_loot) + spawn_loot(completion_loot) new /obj/item/stack/ore/iron(src, calculate_loot(reward_points, rewards_multiplier, ORE_MULTIPLIER_IRON)) new /obj/item/stack/ore/glass(src, calculate_loot(reward_points, rewards_multiplier, ORE_MULTIPLIER_GLASS)) @@ -70,16 +70,16 @@ var/random_sum = (rand() + 0.5) * base return ROUND_UP(random_sum * ore_multiplier) -/// Handles spawning extra loot. This tries to handle bad flat and assoc lists -/obj/structure/closet/crate/secure/bitrunning/decrypted/proc/spawn_loot(list/extra_loot) - for(var/path in extra_loot) +/// Handles spawning completion loot. This tries to handle bad flat and assoc lists +/obj/structure/closet/crate/secure/bitrunning/decrypted/proc/spawn_loot(list/completion_loot) + for(var/path in completion_loot) if(!ispath(path)) return FALSE - if(isnull(extra_loot[path])) + if(isnull(completion_loot[path])) return FALSE - for(var/i in 1 to extra_loot[path]) + for(var/i in 1 to completion_loot[path]) new path(src) return TRUE diff --git a/code/modules/bitrunning/objects/quantum_console.dm b/code/modules/bitrunning/objects/quantum_console.dm index 71d7df87f121b..6cc8aef6dae83 100644 --- a/code/modules/bitrunning/objects/quantum_console.dm +++ b/code/modules/bitrunning/objects/quantum_console.dm @@ -56,7 +56,7 @@ if(isnull(server)) return data - data["available_domains"] = server.get_available_domains() + data["available_domains"] = SSbitrunning.get_available_domains(server.scanner_tier, server.points) data["avatars"] = server.get_avatar_data() return data diff --git a/code/modules/bitrunning/server/_parent.dm b/code/modules/bitrunning/server/_parent.dm index 62e20367190e6..f3f713768972c 100644 --- a/code/modules/bitrunning/server/_parent.dm +++ b/code/modules/bitrunning/server/_parent.dm @@ -20,8 +20,6 @@ var/is_ready = TRUE /// Chance multipled by threat to spawn a glitch var/glitch_chance = 0.05 - /// List of available domains - var/list/available_domains = list() /// Current plugged in users var/list/datum/weakref/avatar_connection_refs = list() /// Cached list of mutable mobs in zone for cybercops @@ -63,13 +61,9 @@ RegisterSignals(src, list(COMSIG_MACHINERY_BROKEN, COMSIG_MACHINERY_POWER_LOST), PROC_REF(on_broken)) RegisterSignal(src, COMSIG_QDELETING, PROC_REF(on_delete)) - // This further gets sorted in the client by cost so it's random and grouped - available_domains = shuffle(subtypesof(/datum/lazy_template/virtual_domain)) - /obj/machinery/quantum_server/Destroy(force) . = ..() - available_domains.Cut() mutation_candidate_refs.Cut() avatar_connection_refs.Cut() spawned_threat_refs.Cut() diff --git a/code/modules/bitrunning/server/loot.dm b/code/modules/bitrunning/server/loot.dm index 0aab2a86ff429..cb4902abfe3ab 100644 --- a/code/modules/bitrunning/server/loot.dm +++ b/code/modules/bitrunning/server/loot.dm @@ -40,6 +40,15 @@ chosen_forge.start_to_spawn(reward_cache) return TRUE +/obj/machinery/quantum_server/proc/generate_secondary_loot(obj/curiosity, obj/machinery/byteforge/chosen_forge) + spark_at_location(curiosity) // abracadabra! + qdel(curiosity) // and it's gone! + + var/obj/item/storage/lockbox/bitrunning/decrypted/reward_curiosity = new(src, generated_domain) + + chosen_forge.start_to_spawn(reward_curiosity) + return TRUE + /// Returns the markdown text containing domain completion information /obj/machinery/quantum_server/proc/get_completion_certificate() var/base_points = generated_domain.reward_points diff --git a/code/modules/bitrunning/server/map_handling.dm b/code/modules/bitrunning/server/map_handling.dm index 5018e464d6a1f..2ddff04fc1535 100644 --- a/code/modules/bitrunning/server/map_handling.dm +++ b/code/modules/bitrunning/server/map_handling.dm @@ -67,9 +67,9 @@ /// Initializes a new domain if the given key is valid and the user has enough points /obj/machinery/quantum_server/proc/load_domain(map_key) - for(var/datum/lazy_template/virtual_domain/available as anything in subtypesof(/datum/lazy_template/virtual_domain)) - if(map_key == initial(available.key) && points >= initial(available.cost)) - generated_domain = new available() + for(var/datum/lazy_template/virtual_domain/available in SSbitrunning.all_domains) + if(map_key == available.key && points >= available.cost) + generated_domain = available RegisterSignal(generated_domain, COMSIG_LAZY_TEMPLATE_LOADED, PROC_REF(on_template_loaded)) generated_domain.lazy_load() return TRUE @@ -80,6 +80,7 @@ /obj/machinery/quantum_server/proc/load_map_items() var/turf/goal_turfs = list() var/turf/cache_turfs = list() + var/turf/curiosity_turfs = list() for(var/obj/effect/landmark/bitrunning/thing in GLOB.landmarks_list) if(istype(thing, /obj/effect/landmark/bitrunning/hololadder_spawn)) @@ -100,6 +101,11 @@ qdel(thing) continue + if(istype(thing, /obj/effect/landmark/bitrunning/curiosity_spawn)) + curiosity_turfs += get_turf(thing) + qdel(thing) + continue + if(istype(thing, /obj/effect/landmark/bitrunning/loot_signal)) var/turf/signaler_turf = get_turf(thing) signaler_turf.AddComponent(/datum/component/bitrunning_points, generated_domain) @@ -113,6 +119,13 @@ if(!attempt_spawn_cache(cache_turfs)) return FALSE + while(length(curiosity_turfs)) + var/turf/picked_turf = attempt_spawn_curiosity(curiosity_turfs) + if(!picked_turf) + break + generated_domain.secondary_loot_generated += 1 + curiosity_turfs -= picked_turf + return TRUE /// Stops the current virtual domain and disconnects all users @@ -151,6 +164,8 @@ creature.dust(just_ash = TRUE, force = TRUE) // sometimes mobs just don't die + generated_domain.secondary_loot_generated = 0 + avatar_connection_refs.Cut() exit_turfs = list() generated_domain = null diff --git a/code/modules/bitrunning/server/obj_generation.dm b/code/modules/bitrunning/server/obj_generation.dm index 38fee74d4a82f..ab17682fb6f86 100644 --- a/code/modules/bitrunning/server/obj_generation.dm +++ b/code/modules/bitrunning/server/obj_generation.dm @@ -15,6 +15,26 @@ new /obj/structure/closet/crate/secure/bitrunning/encrypted(chosen_turf) return TRUE +/// Attempts to spawn a lootbox +/obj/machinery/quantum_server/proc/attempt_spawn_curiosity(list/possible_turfs) + if(!length(possible_turfs)) // Out of turfs to place a curiosity + return FALSE + + if(generated_domain.secondary_loot_generated >= assoc_value_sum(generated_domain.secondary_loot)) // Out of curiosities to place + return FALSE + + shuffle_inplace(possible_turfs) + var/turf/chosen_turf = validate_turf(pick(possible_turfs)) + + if(isnull(chosen_turf)) + possible_turfs.Remove(chosen_turf) + chosen_turf = validate_turf(pick(possible_turfs)) + if(isnull(chosen_turf)) + CRASH("vdom: after two attempts, could not find a valid turf for curiosity") + + new /obj/item/storage/lockbox/bitrunning/encrypted(chosen_turf) + return chosen_turf + /// Generates a new avatar for the bitrunner. /obj/machinery/quantum_server/proc/generate_avatar(obj/structure/hololadder/wayout, datum/outfit/netsuit) var/mob/living/carbon/human/avatar = new(wayout.loc) diff --git a/code/modules/bitrunning/server/signal_handlers.dm b/code/modules/bitrunning/server/signal_handlers.dm index 81db7a5b9b0a6..0e5e949e8bb51 100644 --- a/code/modules/bitrunning/server/signal_handlers.dm +++ b/code/modules/bitrunning/server/signal_handlers.dm @@ -47,6 +47,10 @@ generate_loot(arrived, chosen_forge) return + if(istype(arrived, /obj/item/storage/lockbox/bitrunning/encrypted)) + generate_secondary_loot(arrived, chosen_forge, generated_domain) + return + /// Handles examining the server. Shows cooldown time and efficiency. /obj/machinery/quantum_server/proc/on_goal_turf_examined(datum/source, mob/examiner, list/examine_text) SIGNAL_HANDLER diff --git a/code/modules/bitrunning/server/threats.dm b/code/modules/bitrunning/server/threats.dm index 3ed4ad45bc668..6c42322d0cf01 100644 --- a/code/modules/bitrunning/server/threats.dm +++ b/code/modules/bitrunning/server/threats.dm @@ -69,16 +69,15 @@ var/datum/antagonist/bitrunning_glitch/chosen_role = forced_role || get_antagonist_role() var/role_name = initial(chosen_role.name) - - var/datum/callback/to_call = CALLBACK(src, PROC_REF(spawn_glitch), chosen_role, mutation_target) - mutation_target.AddComponent(/datum/component/orbit_poll, \ - ignore_key = POLL_IGNORE_GLITCH, \ - job_bans = ROLE_GLITCH, \ - to_call = to_call, \ - title = role_name, \ - header = "Bitrunning Malfunction", \ + var/mob/chosen_one = SSpolling.poll_ghosts_for_target( + check_jobban = ROLE_GLITCH, + poll_time = 20 SECONDS, + checked_target = mutation_target, + ignore_category = POLL_IGNORE_GLITCH, + alert_pic = mutation_target, + role_name_text = "Bitrunning Malfunction: [role_name]", ) - + spawn_glitch(chosen_role, mutation_target, chosen_one) return mutation_target /// Orbit poll has concluded - spawn the antag diff --git a/code/modules/bitrunning/server/util.dm b/code/modules/bitrunning/server/util.dm index a6069d45e90a3..24ed78210cf65 100644 --- a/code/modules/bitrunning/server/util.dm +++ b/code/modules/bitrunning/server/util.dm @@ -1,4 +1,3 @@ -#define REDACTED "???" #define MAX_DISTANCE 4 // How far crates can spawn from the server /// Resets the cooldown state and updates icons @@ -7,28 +6,6 @@ update_appearance() radio.talk_into(src, "Thermal systems within operational parameters. Proceeding to domain configuration.", RADIO_CHANNEL_SUPPLY) -/// Compiles a list of available domains. -/obj/machinery/quantum_server/proc/get_available_domains() - var/list/levels = list() - - for(var/datum/lazy_template/virtual_domain/domain as anything in available_domains) - if(initial(domain.test_only)) - continue - var/can_view = initial(domain.difficulty) < scanner_tier && initial(domain.cost) <= points + 5 - var/can_view_reward = initial(domain.difficulty) < (scanner_tier + 1) && initial(domain.cost) <= points + 3 - - levels += list(list( - "cost" = initial(domain.cost), - "desc" = can_view ? initial(domain.desc) : "Limited scanning capabilities. Cannot infer domain details.", - "difficulty" = initial(domain.difficulty), - "id" = initial(domain.key), - "is_modular" = initial(domain.is_modular), - "name" = can_view ? initial(domain.name) : REDACTED, - "reward" = can_view_reward ? initial(domain.reward_points) : REDACTED, - )) - - return levels - /// If there are hosted minds, attempts to get a list of their current virtual bodies w/ vitals /obj/machinery/quantum_server/proc/get_avatar_data() var/list/hosted_avatars = list() @@ -120,5 +97,4 @@ if(!tile.is_blocked_turf()) return chosen_turf -#undef REDACTED #undef MAX_DISTANCE diff --git a/code/modules/bitrunning/virtual_domain/domains/abductor_ship.dm b/code/modules/bitrunning/virtual_domain/domains/abductor_ship.dm index 6475a20a0c758..55e6d08a147d5 100644 --- a/code/modules/bitrunning/virtual_domain/domains/abductor_ship.dm +++ b/code/modules/bitrunning/virtual_domain/domains/abductor_ship.dm @@ -3,7 +3,7 @@ cost = BITRUNNER_COST_MEDIUM desc = "Board an abductor ship and take their goodies." difficulty = BITRUNNER_DIFFICULTY_MEDIUM - extra_loot = list(/obj/item/toy/plush/abductor/agent = 1) + completion_loot = list(/obj/item/toy/plush/abductor/agent = 1) help_text = "An abductor mothership unknowingly entered a hostile environment. \ They are currently preparing to escape the area with their gear and loot including \ the crate. Be careful, they are known for their advanced weaponry." diff --git a/code/modules/bitrunning/virtual_domain/domains/beach_bar.dm b/code/modules/bitrunning/virtual_domain/domains/beach_bar.dm index ae00fc56cf794..80f07448b69fb 100644 --- a/code/modules/bitrunning/virtual_domain/domains/beach_bar.dm +++ b/code/modules/bitrunning/virtual_domain/domains/beach_bar.dm @@ -1,7 +1,7 @@ /datum/lazy_template/virtual_domain/beach_bar name = "Beach Bar" desc = "A cheerful seaside haven where friendly skeletons serve up drinks. Say, how'd you guys get so dead?" - extra_loot = list(/obj/item/toy/beach_ball = 1) + completion_loot = list(/obj/item/toy/beach_ball = 1) help_text = "This place is running on a skeleton crew, and they don't seem to be too keen to share details. \ Maybe a few drinks of liquid charm will get the spirits up. As the saying goes, if you can't beat 'em, join 'em." key = "beach_bar" diff --git a/code/modules/bitrunning/virtual_domain/domains/bubblegum.dm b/code/modules/bitrunning/virtual_domain/domains/bubblegum.dm index f7f58273d89dc..ab8b282cfbf7d 100644 --- a/code/modules/bitrunning/virtual_domain/domains/bubblegum.dm +++ b/code/modules/bitrunning/virtual_domain/domains/bubblegum.dm @@ -3,7 +3,7 @@ cost = BITRUNNER_COST_HIGH desc = "King of the slaughter demons. Bubblegum is a massive, hulking beast with a penchant for violence." difficulty = BITRUNNER_DIFFICULTY_HIGH - extra_loot = list(/obj/item/toy/plush/bubbleplush = 1) + completion_loot = list(/obj/item/toy/plush/bubbleplush = 1) forced_outfit = /datum/outfit/job/miner key = "bubblegum" map_name = "bubblegum" diff --git a/code/modules/bitrunning/virtual_domain/domains/clown_planet.dm b/code/modules/bitrunning/virtual_domain/domains/clown_planet.dm index f1bf44686088f..3aff298efdb5a 100644 --- a/code/modules/bitrunning/virtual_domain/domains/clown_planet.dm +++ b/code/modules/bitrunning/virtual_domain/domains/clown_planet.dm @@ -3,7 +3,7 @@ cost = BITRUNNER_COST_LOW desc = "In the deep, dark reaches of space, there is only Honk." difficulty = BITRUNNER_DIFFICULTY_LOW - extra_loot = list(/obj/item/bikehorn = 1) + completion_loot = list(/obj/item/bikehorn = 1) forced_outfit = /datum/outfit/job/clown help_text = "The trials of the Honkitude have begun. The sound of bike horns wailing in the distance. \ this realm- some sort of puzzle, has existed in legend as the final test of just how silly you are." diff --git a/code/modules/bitrunning/virtual_domain/domains/pipedream.dm b/code/modules/bitrunning/virtual_domain/domains/pipedream.dm index 3eb23b94386ce..595600ce71c4c 100644 --- a/code/modules/bitrunning/virtual_domain/domains/pipedream.dm +++ b/code/modules/bitrunning/virtual_domain/domains/pipedream.dm @@ -3,7 +3,7 @@ cost = BITRUNNER_COST_LOW desc = "An abandoned and infested factory manufacturing disposal pipes." difficulty = BITRUNNER_DIFFICULTY_LOW - extra_loot = list(/obj/item/stack/pipe_cleaner_coil/random/five = 1) + completion_loot = list(/obj/item/stack/pipe_cleaner_coil/random/five = 1) help_text = "Not long ago, this place was thriving with activity. The workers \ seemed to have left in a hurry, and now productivity is in the bin. Something \ must have trashed the place, but what?" diff --git a/code/modules/bitrunning/virtual_domain/domains/psyker_zombies.dm b/code/modules/bitrunning/virtual_domain/domains/psyker_zombies.dm index 9cb299055dd7d..2d4d2c5ee362a 100644 --- a/code/modules/bitrunning/virtual_domain/domains/psyker_zombies.dm +++ b/code/modules/bitrunning/virtual_domain/domains/psyker_zombies.dm @@ -4,7 +4,7 @@ desc = "Another neglected corner of the virtual world. This one had to be abandoned due to zombie virus. \ Warning -- Virtual domain does not support visual display. This mission must be completed using echolocation." difficulty = BITRUNNER_DIFFICULTY_MEDIUM - extra_loot = list(/obj/item/radio/headset/psyker = 1) //Looks cool, might make your local burdened chaplain happy. + completion_loot = list(/obj/item/radio/headset/psyker = 1) //Looks cool, might make your local burdened chaplain happy. forced_outfit = /datum/outfit/echolocator help_text = "This once-beloved virtual domain has been corrupted by a virus, rendering it unstable, full of holes, and full of ZOMBIES! \ There should be a Mystery Box nearby to help get you armed. Get armed, and finish what the cyber-police started!" diff --git a/code/modules/bitrunning/virtual_domain/domains/stairs_and_cliffs.dm b/code/modules/bitrunning/virtual_domain/domains/stairs_and_cliffs.dm index 1c79d8fd21d1c..4d100482429b9 100644 --- a/code/modules/bitrunning/virtual_domain/domains/stairs_and_cliffs.dm +++ b/code/modules/bitrunning/virtual_domain/domains/stairs_and_cliffs.dm @@ -6,7 +6,8 @@ instead of ladders its stairs and instead of snakes its a steep drop down a \ cliff into rough rocks or liquid plasma." difficulty = BITRUNNER_DIFFICULTY_LOW - extra_loot = list(/obj/item/clothing/suit/costume/snowman = 2) + completion_loot = list(/obj/item/clothing/suit/costume/snowman = 2) + secondary_loot = list(/obj/item/clothing/shoes/wheelys/skishoes = 2, /obj/item/clothing/head/costume/ushanka/polar = 1) forced_outfit = /datum/outfit/job/virtual_domain_iceclimber key = "stairs_and_cliffs" map_name = "stairs_and_cliffs" diff --git a/code/modules/bitrunning/virtual_domain/domains/syndicate_assault.dm b/code/modules/bitrunning/virtual_domain/domains/syndicate_assault.dm index 24fa6e5482ac6..5f754dd433ad6 100644 --- a/code/modules/bitrunning/virtual_domain/domains/syndicate_assault.dm +++ b/code/modules/bitrunning/virtual_domain/domains/syndicate_assault.dm @@ -3,7 +3,7 @@ cost = BITRUNNER_COST_MEDIUM desc = "Board the enemy ship and recover the stolen cargo." difficulty = BITRUNNER_DIFFICULTY_MEDIUM - extra_loot = list(/obj/item/toy/plush/nukeplushie = 1) + completion_loot = list(/obj/item/toy/plush/nukeplushie = 1) help_text = "A group of Syndicate operatives have stolen valuable cargo from the station. \ They have boarded their ship and are attempting to escape. Infiltrate their ship and recover \ the crate. Be careful, they are extremely armed." diff --git a/code/modules/bitrunning/virtual_domain/domains/vaporwave.dm b/code/modules/bitrunning/virtual_domain/domains/vaporwave.dm index b704441edaa84..5da6449ccf924 100644 --- a/code/modules/bitrunning/virtual_domain/domains/vaporwave.dm +++ b/code/modules/bitrunning/virtual_domain/domains/vaporwave.dm @@ -3,7 +3,7 @@ cost = BITRUNNER_COST_EXTREME desc = "Suspended in the silent void of space, the Neon Relic is a haunting echo of a retro-futuristic era. Hang out, enjoy the view." difficulty = BITRUNNER_DIFFICULTY_NONE - extra_loot = list(/obj/item/stack/spacecash/c500 = 4) + completion_loot = list(/obj/item/stack/spacecash/c500 = 4) key = "vaporwave" map_name = "vaporwave" reward_points = BITRUNNER_REWARD_EXTREME diff --git a/code/modules/bitrunning/virtual_domain/domains/xeno_nest.dm b/code/modules/bitrunning/virtual_domain/domains/xeno_nest.dm index ed708d0592cb1..6b76956eacc70 100644 --- a/code/modules/bitrunning/virtual_domain/domains/xeno_nest.dm +++ b/code/modules/bitrunning/virtual_domain/domains/xeno_nest.dm @@ -3,7 +3,7 @@ cost = BITRUNNER_COST_LOW desc = "Our ship scanners have detected lifeforms of unknown origin. Friendly attempts to contact them have failed." difficulty = BITRUNNER_DIFFICULTY_LOW - extra_loot = list(/obj/item/toy/plush/rouny = 1) + completion_loot = list(/obj/item/toy/plush/rouny = 1) help_text = "You are on a barren planet filled with hostile creatures. There is a crate here, not hidden, \ simply protected. Expect resistance." is_modular = TRUE diff --git a/code/modules/bitrunning/virtual_domain/virtual_domain.dm b/code/modules/bitrunning/virtual_domain/virtual_domain.dm index 838834f45a74a..b316bb97cae1e 100644 --- a/code/modules/bitrunning/virtual_domain/virtual_domain.dm +++ b/code/modules/bitrunning/virtual_domain/virtual_domain.dm @@ -6,6 +6,7 @@ map_dir = "_maps/virtual_domains" map_name = "None" key = "Virtual Domain" + place_on_top = TRUE /// Cost of this map to load var/cost = BITRUNNER_COST_NONE @@ -28,7 +29,11 @@ /// Byond will look for modular mob segment landmarks then choose from here at random. You can make them unique also. var/list/datum/modular_mob_segment/mob_modules = list() /// An assoc list of typepath/amount to spawn on completion. Not weighted - the value is the amount - var/list/extra_loot + var/list/completion_loot + /// An accoc list of typepath/amount to spawn from secondary objectives. Not weighted - the value is the total number of items that can be obtained. + var/list/secondary_loot = list() + /// Number of secondary loot boxes generated. Resets when the domain is reloaded. + var/secondary_loot_generated /// Forces all mob modules to only load once var/modular_unique_mobs = FALSE // Name to show in the UI diff --git a/code/modules/capture_the_flag/ctf_equipment.dm b/code/modules/capture_the_flag/ctf_equipment.dm index dccf875a55a95..0211a066555d1 100644 --- a/code/modules/capture_the_flag/ctf_equipment.dm +++ b/code/modules/capture_the_flag/ctf_equipment.dm @@ -156,8 +156,9 @@ ammo_x_offset = 2 shaded_charge = FALSE -/obj/item/gun/energy/laser/instakill/emp_act() //implying you could stop the instagib - return +/obj/item/gun/energy/laser/instakill/Initialize(mapload) + . = ..() + AddElement(/datum/element/empprotection, EMP_PROTECT_ALL) /obj/item/gun/energy/laser/instakill/ctf/Initialize(mapload) . = ..() diff --git a/code/modules/cargo/coupon.dm b/code/modules/cargo/coupon.dm index f654db448872e..4c5e56a7d4119 100644 --- a/code/modules/cargo/coupon.dm +++ b/code/modules/cargo/coupon.dm @@ -84,7 +84,7 @@ to_chat(cursed, span_warning("The coupon reads 'fuck you' in large, bold text... is- is that a prize, or?")) if(!cursed.GetComponent(/datum/component/omen)) - cursed.AddComponent(/datum/component/omen) + cursed.AddComponent(/datum/component/omen, 1) return TRUE if(HAS_TRAIT(cursed, TRAIT_CURSED)) to_chat(cursed, span_warning("What a horrible night... To have a curse!")) diff --git a/code/modules/cargo/department_order.dm b/code/modules/cargo/department_order.dm index 7622993e9018b..c8eb6e99ac2ff 100644 --- a/code/modules/cargo/department_order.dm +++ b/code/modules/cargo/department_order.dm @@ -153,7 +153,7 @@ GLOBAL_LIST_INIT(department_order_cooldowns, list( var/mob/living/carbon/human/human_orderer = usr name = human_orderer.get_authentification_name() rank = human_orderer.get_assignment(hand_first = TRUE) - else if(issilicon(usr)) + else if(HAS_SILICON_ACCESS(usr)) name = usr.real_name rank = "Silicon" //already have a signal to finalize the order diff --git a/code/modules/cargo/exports/parts.dm b/code/modules/cargo/exports/parts.dm index 840d40f183712..fc8c9656fea78 100644 --- a/code/modules/cargo/exports/parts.dm +++ b/code/modules/cargo/exports/parts.dm @@ -33,3 +33,9 @@ unit_name = "data disk" export_types = list(/obj/item/computer_disk) include_subtypes = TRUE + +/datum/export/refill_canister + cost = CARGO_CRATE_VALUE * 0.5 //If someone want to make this worth more as it empties, go ahead + unit_name = "vending refill canister" + message = "Thank you for restocking the station!" + export_types = list(/obj/item/vending_refill) diff --git a/code/modules/cargo/expressconsole.dm b/code/modules/cargo/expressconsole.dm index 79ae0b0fbf892..4942ea2c06a93 100644 --- a/code/modules/cargo/expressconsole.dm +++ b/code/modules/cargo/expressconsole.dm @@ -95,7 +95,7 @@ if(D) data["points"] = D.account_balance data["locked"] = locked//swipe an ID to unlock - data["siliconUser"] = user.has_unlimited_silicon_privilege + data["siliconUser"] = HAS_SILICON_ACCESS(user) data["beaconzone"] = beacon ? get_area(beacon) : ""//where is the beacon located? outputs in the tgui data["usingBeacon"] = usingBeacon //is the mode set to deliver to the beacon or the cargobay? data["canBeacon"] = !usingBeacon || canBeacon //is the mode set to beacon delivery, and is the beacon in a valid location? @@ -164,7 +164,7 @@ var/mob/living/carbon/human/H = usr name = H.get_authentification_name() rank = H.get_assignment(hand_first = TRUE) - else if(issilicon(usr)) + else if(HAS_SILICON_ACCESS(usr)) name = usr.real_name rank = "Silicon" var/reason = "" diff --git a/code/modules/cargo/markets/_market.dm b/code/modules/cargo/markets/_market.dm index 3c264289cd2bf..a4af2bc981d94 100644 --- a/code/modules/cargo/markets/_market.dm +++ b/code/modules/cargo/markets/_market.dm @@ -13,10 +13,7 @@ /// Adds item to the available items and add it's category if it is not in categories yet. /datum/market/proc/add_item(datum/market_item/item) - if(!prob(initial(item.availability_prob))) - return FALSE - - if(ispath(item)) + if(ispath(item, /datum/market_item)) item = new item() if(!(item.category in categories)) diff --git a/code/modules/cargo/markets/market_item.dm b/code/modules/cargo/markets/market_item.dm index 867facf015b98..d5689c17a45e6 100644 --- a/code/modules/cargo/markets/market_item.dm +++ b/code/modules/cargo/markets/market_item.dm @@ -14,7 +14,7 @@ var/stock /// Path to or the item itself what this entry is for, this should be set even if you override spawn_item to spawn your item. - var/item + var/obj/item/item /// Minimum price for the item if generated randomly. var/price_min = 0 @@ -33,9 +33,18 @@ if(isnull(stock)) stock = rand(stock_min, stock_max) +/datum/market_item/Destroy() + item = null + return ..() + /// Used for spawning the wanted item, override if you need to do something special with the item. /datum/market_item/proc/spawn_item(loc) - return new item(loc) + if(ismovable(item)) + item.forceMove(loc) + return item + if(ispath(item)) + return new item(loc) + CRASH("Invalid item type for market item [item || "null"]") /// Buys the item and makes SSblackmarket handle it. /datum/market_item/proc/buy(obj/item/market_uplink/uplink, mob/buyer, shipping_method) diff --git a/code/modules/cargo/markets/market_items/clothing.dm b/code/modules/cargo/markets/market_items/clothing.dm index 003cd95780c29..8af34e2291657 100644 --- a/code/modules/cargo/markets/market_items/clothing.dm +++ b/code/modules/cargo/markets/market_items/clothing.dm @@ -79,3 +79,19 @@ price_max = CARGO_CRATE_VALUE * 1.5 stock_max = 5 availability_prob = 70 + +/datum/market_item/clothing/floortileset + name = "Floor-tile Camouflage Uniform" + desc = "Hey there, looking to surprise somebody? Spy? Steal? Then you're lucky, meet our newest \ + floor-tile 'NT SCUM' styled camouflage fatigues. This is the ultimate \ + espionage uniform used by the very best. Providing the best \ + flexibility, with our latest Camo-tech threads. Perfect for \ + risky espionage hallway operations. Enjoy our product!" + item = /obj/item/storage/box/floor_camo + price_min = CARGO_CRATE_VALUE * 0.5 + price_max = CARGO_CRATE_VALUE + stock_max = 3 + availability_prob = 40 + + + diff --git a/code/modules/cargo/markets/market_telepad.dm b/code/modules/cargo/markets/market_telepad.dm index abdad441ce500..e99e4b88d223e 100644 --- a/code/modules/cargo/markets/market_telepad.dm +++ b/code/modules/cargo/markets/market_telepad.dm @@ -82,11 +82,7 @@ if(receiving) var/datum/market_purchase/P = receiving - if(!P.item || ispath(P.item)) - P.item = P.entry.spawn_item(T) - else - var/atom/movable/M = P.item - M.forceMove(T) + P.item = P.entry.spawn_item(T) use_power(power_usage_per_teleport / power_efficiency) var/datum/effect_system/spark_spread/sparks = new diff --git a/code/modules/cargo/markets/market_uplink.dm b/code/modules/cargo/markets/market_uplink.dm index 19c1a049a1be0..a82218082e90d 100644 --- a/code/modules/cargo/markets/market_uplink.dm +++ b/code/modules/cargo/markets/market_uplink.dm @@ -150,6 +150,7 @@ icon_state = "uplink" //The original black market uplink accessible_markets = list(/datum/market/blackmarket) + custom_premium_price = PAYCHECK_CREW * 2.5 /datum/crafting_recipe/blackmarket_uplink diff --git a/code/modules/cargo/materials_market.dm b/code/modules/cargo/materials_market.dm index 92d83d5d0a141..947197d16f298 100644 --- a/code/modules/cargo/materials_market.dm +++ b/code/modules/cargo/materials_market.dm @@ -166,12 +166,14 @@ var/min_value_override = initial(traded_mat.minimum_value_override) if(min_value_override) minimum_value_threshold = min_value_override - + else + minimum_value_threshold = round(initial(traded_mat.value_per_unit) * SHEET_MATERIAL_AMOUNT * 0.5) //send data material_data += list(list( "name" = initial(traded_mat.name), "price" = SSstock_market.materials_prices[traded_mat], + "rarity" = initial(traded_mat.value_per_unit), "threshold" = minimum_value_threshold, "quantity" = SSstock_market.materials_quantity[traded_mat], "trend" = trend_string, @@ -205,6 +207,7 @@ .["orderBalance"] = current_cost .["orderingPrive"] = ordering_private .["canOrderCargo"] = can_buy_via_budget + .["updateTime"] = SSstock_market.next_fire - world.time /obj/machinery/materials_market/ui_act(action, params, datum/tgui/ui, datum/ui_state/state) . = ..() @@ -348,8 +351,8 @@ /obj/item/stock_block/Initialize(mapload) . = ..() - addtimer(CALLBACK(src, PROC_REF(value_warning)), 2.5 MINUTES, TIMER_DELETE_ME) - addtimer(CALLBACK(src, PROC_REF(update_value)), 5 MINUTES, TIMER_DELETE_ME) + addtimer(CALLBACK(src, PROC_REF(value_warning)), 1.5 MINUTES, TIMER_DELETE_ME) + addtimer(CALLBACK(src, PROC_REF(update_value)), 3 MINUTES, TIMER_DELETE_ME) /obj/item/stock_block/examine(mob/user) . = ..() diff --git a/code/modules/cargo/orderconsole.dm b/code/modules/cargo/orderconsole.dm index 16e3be76f3fb0..2f91ad4c8207a 100644 --- a/code/modules/cargo/orderconsole.dm +++ b/code/modules/cargo/orderconsole.dm @@ -206,7 +206,7 @@ var/mob/living/carbon/human/human = user name = human.get_authentification_name() rank = human.get_assignment(hand_first = TRUE) - else if(issilicon(user)) + else if(HAS_SILICON_ACCESS(user)) name = user.real_name rank = "Silicon" diff --git a/code/modules/cargo/packs/imports.dm b/code/modules/cargo/packs/imports.dm index fae1e405d3fb2..781205eb03bf8 100644 --- a/code/modules/cargo/packs/imports.dm +++ b/code/modules/cargo/packs/imports.dm @@ -315,17 +315,18 @@ /datum/supply_pack/imports/floortilecamo name = "Floor-tile Camouflage Uniform" - desc = "Thank you for shopping from Camo-J's, our uniquely designed \ - floor-tile 'NT SCUM' styled camouflage fatigues is the ultimate \ + desc = "Hey there, looking to surprise somebody? Spy? Steal? Then you're lucky, meet our newest \ + floor-tile 'NT SCUM' styled camouflage fatigues. This is the ultimate \ espionage uniform used by the very best. Providing the best \ flexibility, with our latest Camo-tech threads. Perfect for \ risky espionage hallway operations. Enjoy our product!" - hidden = TRUE + contraband = TRUE cost = CARGO_CRATE_VALUE * 6 - contains = list(/obj/item/clothing/under/syndicate/floortilecamo = 4, - /obj/item/clothing/mask/floortilebalaclava = 4, - /obj/item/clothing/gloves/combat/floortile = 4, - /obj/item/clothing/shoes/jackboots/floortile = 4 + contains = list(/obj/item/clothing/under/syndicate/floortilecamo = 3, + /obj/item/clothing/mask/floortilebalaclava = 3, + /obj/item/clothing/gloves/combat/floortile = 3, + /obj/item/clothing/shoes/jackboots/floortile = 3, + /obj/item/storage/backpack/floortile = 3 ) crate_name = "floortile camouflauge crate" crate_type = /obj/structure/closet/crate/secure/weapon diff --git a/code/modules/cargo/packs/organic.dm b/code/modules/cargo/packs/organic.dm index d806ce51d45a9..f405fc7de3a8a 100644 --- a/code/modules/cargo/packs/organic.dm +++ b/code/modules/cargo/packs/organic.dm @@ -298,7 +298,7 @@ name = "Grilling Starter Kit" desc = "Hey dad I'm Hungry. Hi Hungry I'm THE NEW GRILLING STARTER KIT \ ONLY 5000 BUX GET NOW! Contains a grill and fuel." - cost = CARGO_CRATE_VALUE * 8 + cost = CARGO_CRATE_VALUE * 4 crate_type = /obj/structure/closet/crate contains = list( /obj/item/stack/sheet/mineral/coal/five, diff --git a/code/modules/cargo/packs/science.dm b/code/modules/cargo/packs/science.dm index f0de463c4490f..7fa9013c686cf 100644 --- a/code/modules/cargo/packs/science.dm +++ b/code/modules/cargo/packs/science.dm @@ -104,14 +104,16 @@ name = "Robotics Assembly Crate" desc = "The tools you need to replace those finicky humans with a loyal robot army! \ Contains four proximity sensors, two empty first aid kits, two health analyzers, \ - two red hardhats, two mechanical toolboxes, and two cleanbot assemblies!" + two red hardhats, two toolboxes, and two cleanbot assemblies!" cost = CARGO_CRATE_VALUE * 3 access = ACCESS_ROBOTICS access_view = ACCESS_ROBOTICS - contains = list(/obj/item/assembly/prox_sensor = 5, + contains = list(/obj/item/assembly/prox_sensor = 4, /obj/item/healthanalyzer = 2, /obj/item/clothing/head/utility/hardhat/red = 2, - /obj/item/storage/medkit = 2) + /obj/item/storage/medkit = 2, + /obj/item/storage/toolbox = 2, + /obj/item/bot_assembly/cleanbot = 2) crate_name = "robotics assembly crate" crate_type = /obj/structure/closet/crate/secure/science/robo diff --git a/code/modules/cargo/packs/vending_restock.dm b/code/modules/cargo/packs/vending_restock.dm index cfe9961cc3a43..10ae874d5d6c9 100644 --- a/code/modules/cargo/packs/vending_restock.dm +++ b/code/modules/cargo/packs/vending_restock.dm @@ -4,7 +4,7 @@ /datum/supply_pack/vending/bartending name = "Booze-o-mat and Coffee Supply Crate" desc = "Bring on the booze and coffee vending machine refills." - cost = CARGO_CRATE_VALUE * 4 + cost = CARGO_CRATE_VALUE * 2 contains = list(/obj/item/vending_refill/boozeomat, /obj/item/vending_refill/coffee, ) @@ -14,7 +14,7 @@ name = "Cigarette Supply Crate" desc = "Don't believe the reports - smoke today! Contains a \ cigarette vending machine refill." - cost = CARGO_CRATE_VALUE * 3 + cost = CARGO_CRATE_VALUE * 2 contains = list(/obj/item/vending_refill/cigarette) crate_name = "cigarette supply crate" crate_type = /obj/structure/closet/crate @@ -62,7 +62,7 @@ /datum/supply_pack/vending/imported name = "Imported Vending Machines" desc = "Vending machines famous in other parts of the galaxy." - cost = CARGO_CRATE_VALUE * 8 + cost = CARGO_CRATE_VALUE * 5 contains = list(/obj/item/vending_refill/sustenance, /obj/item/vending_refill/robotics, /obj/item/vending_refill/sovietsoda, @@ -74,7 +74,7 @@ name = "Medical Vending Crate" desc = "Contains one NanoMed Plus refill, one NanoDrug Plus refill, \ and one wall-mounted NanoMed refill." - cost = CARGO_CRATE_VALUE * 5 + cost = CARGO_CRATE_VALUE * 3.5 contains = list(/obj/item/vending_refill/medical, /obj/item/vending_refill/drugs, /obj/item/vending_refill/wallmed, @@ -85,7 +85,7 @@ name = "PTech Supply Crate" desc = "Not enough cartridges after half the crew lost their PDA \ to explosions? This may fix it." - cost = CARGO_CRATE_VALUE * 3 + cost = CARGO_CRATE_VALUE * 2.5 contains = list(/obj/item/vending_refill/cart) crate_name = "\improper PTech supply crate" @@ -103,7 +103,7 @@ name = "Snack Supply Crate" desc = "One vending machine refill of cavity-bringin' goodness! \ The number one dentist recommended order!" - cost = CARGO_CRATE_VALUE * 3 + cost = CARGO_CRATE_VALUE * 2 contains = list(/obj/item/vending_refill/snack) crate_name = "snacks supply crate" @@ -111,14 +111,14 @@ name = "Softdrinks Supply Crate" desc = "Got whacked by a toolbox, but you still have those pesky teeth? \ Get rid of those pearly whites with this soda machine refill, today!" - cost = CARGO_CRATE_VALUE * 3 + cost = CARGO_CRATE_VALUE * 2 contains = list(/obj/item/vending_refill/cola) crate_name = "soft drinks supply crate" /datum/supply_pack/vending/vendomat name = "Part-Mart & YouTool Supply Crate" desc = "More tools for your IED testing facility." - cost = CARGO_CRATE_VALUE * 2 + cost = CARGO_CRATE_VALUE * 3 contains = list(/obj/item/vending_refill/assist, /obj/item/vending_refill/youtool, ) @@ -138,7 +138,7 @@ name = "Autodrobe Supply Crate" desc = "Autodrobe missing your favorite dress? Solve that issue today \ with this autodrobe refill." - cost = CARGO_CRATE_VALUE * 3 + cost = CARGO_CRATE_VALUE * 2 contains = list(/obj/item/vending_refill/autodrobe) crate_name = "autodrobe supply crate" @@ -200,7 +200,7 @@ name = "Science Wardrobe Supply Crate" desc = "This crate contains refills for the SciDrobe, \ GeneDrobe, and RoboDrobe." - cost = CARGO_CRATE_VALUE * 3 + cost = CARGO_CRATE_VALUE * 4.5 contains = list(/obj/item/vending_refill/wardrobe/robo_wardrobe, /obj/item/vending_refill/wardrobe/gene_wardrobe, /obj/item/vending_refill/wardrobe/science_wardrobe, diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 5de1341358230..1ba96157dc89f 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -1164,8 +1164,8 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( if(!CONFIG_GET(flag/use_age_restriction_for_jobs)) return 0 - if(!isnum(player_age)) - return 0 //This is only a number if the db connection is established, otherwise it is text: "Requires database", meaning these restrictions cannot be enforced + if(!isnum(player_age) || player_age < 0) + return 0 if(!isnum(days_needed)) return 0 diff --git a/code/modules/client/verbs/ooc.dm b/code/modules/client/verbs/ooc.dm index 25267e92eff9c..1009cc8a6afd7 100644 --- a/code/modules/client/verbs/ooc.dm +++ b/code/modules/client/verbs/ooc.dm @@ -10,12 +10,15 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8") to_chat(usr, span_danger("Speech is currently admin-disabled.")) return - if(!mob) - return + var/client_initalized = VALIDATE_CLIENT_INITIALIZATION(src) + if(isnull(mob) || !client_initalized) + if(!client_initalized) + unvalidated_client_error() // we only want to throw this warning message when it's directly related to client failure. - VALIDATE_CLIENT(src) + to_chat(usr, span_warning("Failed to send your OOC message. You attempted to send the following message:\n[span_big(msg)]")) + return - if(!holder) + if(isnull(holder)) if(!GLOB.ooc_allowed) to_chat(src, span_danger("OOC is globally muted.")) return diff --git a/code/modules/clothing/belts/polymorph_belt.dm b/code/modules/clothing/belts/polymorph_belt.dm index 73959d6d41519..fb09b2e68c8f1 100644 --- a/code/modules/clothing/belts/polymorph_belt.dm +++ b/code/modules/clothing/belts/polymorph_belt.dm @@ -63,10 +63,9 @@ if (target_mob.mob_biotypes & (MOB_HUMANOID|MOB_ROBOTIC|MOB_SPECIAL|MOB_SPIRIT|MOB_UNDEAD)) balloon_alert(user, "incompatible!") return TRUE - if (isanimal_or_basicmob(target_mob)) - if (!target_mob.compare_sentience_type(SENTIENCE_ORGANIC)) - balloon_alert(user, "target too intelligent!") - return TRUE + if (!target_mob.compare_sentience_type(SENTIENCE_ORGANIC)) + balloon_alert(user, "target too intelligent!") + return TRUE if (stored_mob_type == target_mob.type) balloon_alert(user, "already scanned!") return TRUE diff --git a/code/modules/clothing/head/cakehat.dm b/code/modules/clothing/head/cakehat.dm index 57369ac24f89c..1fc0fa0b05b50 100644 --- a/code/modules/clothing/head/cakehat.dm +++ b/code/modules/clothing/head/cakehat.dm @@ -9,8 +9,10 @@ lefthand_file = 'icons/mob/inhands/clothing/hats_lefthand.dmi' righthand_file = 'icons/mob/inhands/clothing/hats_righthand.dmi' armor_type = /datum/armor/none - light_range = 2 //luminosity when on light_system = OVERLAY_LIGHT + light_range = 2 //luminosity when on + light_power = 1.3 + light_color = "#FF964E" flags_cover = HEADCOVERSEYES heat = 999 wound_bonus = 10 diff --git a/code/modules/clothing/head/hardhat.dm b/code/modules/clothing/head/hardhat.dm index 2129c598aae25..be8323a1186e2 100644 --- a/code/modules/clothing/head/hardhat.dm +++ b/code/modules/clothing/head/hardhat.dm @@ -16,6 +16,7 @@ light_system = OVERLAY_LIGHT_DIRECTIONAL light_range = 4 light_power = 0.8 + light_color = "#ffcc99" light_on = FALSE dog_fashion = /datum/dog_fashion/head diff --git a/code/modules/clothing/head/hat.dm b/code/modules/clothing/head/hat.dm index 52e0ca53ac976..846f49ad957cd 100644 --- a/code/modules/clothing/head/hat.dm +++ b/code/modules/clothing/head/hat.dm @@ -312,6 +312,13 @@ to_chat(user, span_notice("You lower the ear flaps on the ushanka.")) earflaps = !earflaps +/obj/item/clothing/head/costume/ushanka/polar + name = "bear hunter's ushanka" + desc = "Handcrafted in Siberia from real polar bears." + icon_state = "ushankadown_polar" + upsprite = "ushankaup_polar" + downsprite = "ushankadown_polar" + /obj/item/clothing/head/costume/nightcap/blue name = "blue nightcap" desc = "A blue nightcap for all the dreamers and snoozers out there." diff --git a/code/modules/clothing/head/mind_monkey_helmet.dm b/code/modules/clothing/head/mind_monkey_helmet.dm index 50ac5bb8ba254..449df33550560 100644 --- a/code/modules/clothing/head/mind_monkey_helmet.dm +++ b/code/modules/clothing/head/mind_monkey_helmet.dm @@ -47,19 +47,18 @@ playsound(src, 'sound/machines/ping.ogg', 30, TRUE) RegisterSignal(magnification, COMSIG_SPECIES_LOSS, PROC_REF(make_fall_off)) polling = TRUE - var/list/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as a mind magnified monkey?", check_jobban = ROLE_MONKEY_HELMET, poll_time = 5 SECONDS, target_mob = magnification, ignore_category = POLL_IGNORE_MONKEY_HELMET, pic_source = magnification, role_name_text = "mind-magnified monkey") + var/mob/chosen_one = SSpolling.poll_ghosts_for_target(check_jobban = ROLE_MONKEY_HELMET, poll_time = 5 SECONDS, checked_target = magnification, ignore_category = POLL_IGNORE_MONKEY_HELMET, alert_pic = magnification, role_name_text = "mind-magnified monkey") polling = FALSE if(!magnification) return - if(!candidates.len) + if(isnull(chosen_one)) UnregisterSignal(magnification, COMSIG_SPECIES_LOSS) magnification = null visible_message(span_notice("[src] falls silent and drops on the floor. Maybe you should try again later?")) playsound(src, 'sound/machines/buzz-sigh.ogg', 30, TRUE) user.dropItemToGround(src) return - var/mob/picked = pick(candidates) - magnification.key = picked.key + magnification.key = chosen_one.key playsound(src, 'sound/machines/microwave/microwave-end.ogg', 100, FALSE) to_chat(magnification, span_notice("You're a mind magnified monkey! Protect your helmet with your life- if you lose it, your sentience goes with it!")) var/policy = get_policy(ROLE_MONKEY_HELMET) diff --git a/code/modules/clothing/head/pirate.dm b/code/modules/clothing/head/pirate.dm index fd45404efc38b..818478ccb7d4a 100644 --- a/code/modules/clothing/head/pirate.dm +++ b/code/modules/clothing/head/pirate.dm @@ -10,20 +10,17 @@ /obj/item/clothing/head/costume/pirate/equipped(mob/user, slot) . = ..() - if(!ishuman(user)) + if(!(slot_flags & slot)) return - if(slot & ITEM_SLOT_HEAD) - user.grant_language(/datum/language/piratespeak/, source = LANGUAGE_HAT) - to_chat(user, span_boldnotice("You suddenly know how to speak like a pirate!")) + user.grant_language(/datum/language/piratespeak, source = LANGUAGE_HAT) + to_chat(user, span_boldnotice("You suddenly know how to speak like a pirate!")) /obj/item/clothing/head/costume/pirate/dropped(mob/user) . = ..() - if(!ishuman(user)) + if(QDELETED(src)) //This can be called as a part of destroy return - var/mob/living/carbon/human/H = user - if(H.get_item_by_slot(ITEM_SLOT_HEAD) == src && !QDELETED(src)) //This can be called as a part of destroy - user.remove_language(/datum/language/piratespeak/, source = LANGUAGE_HAT) - to_chat(user, span_boldnotice("You can no longer speak like a pirate.")) + user.remove_language(/datum/language/piratespeak, source = LANGUAGE_HAT) + to_chat(user, span_boldnotice("You can no longer speak like a pirate.")) /obj/item/clothing/head/costume/pirate/armored armor_type = /datum/armor/pirate_armored diff --git a/code/modules/clothing/outfits/plasmaman.dm b/code/modules/clothing/outfits/plasmaman.dm index 40d7cfa888e05..1b2e0ead14883 100644 --- a/code/modules/clothing/outfits/plasmaman.dm +++ b/code/modules/clothing/outfits/plasmaman.dm @@ -295,3 +295,10 @@ uniform = /obj/item/clothing/under/plasmaman //call me when this is gags and not 10 million new assets gloves = /obj/item/clothing/gloves/color/plasmaman/black head = /obj/item/clothing/head/helmet/space/plasmaman + +/datum/outfit/plasmaman/human_ai + name = "Human AI Plasmaman" + + uniform = /obj/item/clothing/under/plasmaman //same + gloves = /obj/item/clothing/gloves/color/plasmaman/black + head = /obj/item/clothing/head/helmet/space/plasmaman diff --git a/code/modules/clothing/shoes/clown.dm b/code/modules/clothing/shoes/clown.dm index aff47fde7fabe..76395a56efd5e 100644 --- a/code/modules/clothing/shoes/clown.dm +++ b/code/modules/clothing/shoes/clown.dm @@ -53,3 +53,9 @@ desc = "The adorable sound they make when you walk will mean making friends is more likely." icon_state = "meown_shoes" squeak_sound = list('sound/effects/footstep/meowstep1.ogg'=1) //mew mew mew mew + +/obj/item/clothing/shoes/clown_shoes/moffers + name = "moffers" + desc = "No moths were harmed in the making of these slippers." + icon_state = "moffers" + squeak_sound = list('sound/effects/footstep/moffstep01.ogg'=1) //like sweet music to my ears diff --git a/code/modules/clothing/spacesuits/plasmamen.dm b/code/modules/clothing/spacesuits/plasmamen.dm index 90141d281dd31..64b83c076505e 100644 --- a/code/modules/clothing/spacesuits/plasmamen.dm +++ b/code/modules/clothing/spacesuits/plasmamen.dm @@ -54,6 +54,8 @@ resistance_flags = FIRE_PROOF light_system = OVERLAY_LIGHT_DIRECTIONAL light_range = 4 + light_power = 0.8 + light_color = "#ffcc99" light_on = FALSE var/helmet_on = FALSE var/smile = FALSE diff --git a/code/modules/clothing/suits/costume.dm b/code/modules/clothing/suits/costume.dm index d004b862fcdbf..9cf86a396e95a 100644 --- a/code/modules/clothing/suits/costume.dm +++ b/code/modules/clothing/suits/costume.dm @@ -362,6 +362,48 @@ clothing_flags = THICKMATERIAL flags_inv = HIDEHAIR|HIDEEARS +/obj/item/clothing/suit/hooded/shark_costume // Blahaj + name = "Shark costume" + desc = "Finally, a costume to match your favorite plush." + icon_state = "shark" + icon = 'icons/obj/clothing/suits/costume.dmi' + worn_icon = 'icons/mob/clothing/suits/costume.dmi' + inhand_icon_state = "shark" + body_parts_covered = CHEST|GROIN|ARMS + clothing_flags = THICKMATERIAL + hoodtype = /obj/item/clothing/head/hooded/shark_hood + +/obj/item/clothing/head/hooded/shark_hood + name = "shark hood" + desc = "A hood attached to a shark costume." + icon = 'icons/obj/clothing/head/costume.dmi' + worn_icon = 'icons/mob/clothing/head/costume.dmi' + icon_state = "shark" + body_parts_covered = HEAD + clothing_flags = THICKMATERIAL + flags_inv = HIDEHAIR|HIDEEARS + +/obj/item/clothing/suit/hooded/shork_costume // Oh God Why + name = "shork costume" + desc = "Why would you ever do this?" + icon_state = "sharkcursed" + icon = 'icons/obj/clothing/suits/costume.dmi' + worn_icon = 'icons/mob/clothing/suits/costume.dmi' + inhand_icon_state = "sharkcursed" + body_parts_covered = CHEST|GROIN|ARMS + clothing_flags = THICKMATERIAL + hoodtype = /obj/item/clothing/head/hooded/shork_hood + +/obj/item/clothing/head/hooded/shork_hood + name = "shork hood" + desc = "A hood attached to a shork costume." + icon = 'icons/obj/clothing/head/costume.dmi' + worn_icon = 'icons/mob/clothing/head/costume.dmi' + icon_state = "sharkcursed" + body_parts_covered = HEAD + clothing_flags = THICKMATERIAL + flags_inv = HIDEHAIR|HIDEEARS + /obj/item/clothing/suit/hooded/bloated_human //OH MY GOD WHAT HAVE YOU DONE!?!?!? name = "bloated human suit" desc = "A horribly bloated suit made from human skins." diff --git a/code/modules/clothing/under/jobs/station_trait.dm b/code/modules/clothing/under/jobs/station_trait.dm new file mode 100644 index 0000000000000..034641d35b190 --- /dev/null +++ b/code/modules/clothing/under/jobs/station_trait.dm @@ -0,0 +1,12 @@ +/** + * Outfits limited to station trait jobs + */ +/obj/item/clothing/under/rank/station_trait + icon = 'icons/obj/clothing/under/station_trait.dmi' + worn_icon = 'icons/mob/clothing/under/station_trait.dmi' + +/obj/item/clothing/under/rank/station_trait/human_ai + name = "ai's uniform" + desc = "A outfit made of the cleanest of bluespace and, just kidding it's all fake." + icon_state = "human_ai" + inhand_icon_state = "armor_reflec" diff --git a/code/modules/deathmatch/deathmatch_maps.dm b/code/modules/deathmatch/deathmatch_maps.dm index d437bffbb3cd7..71d7e8a8651f4 100644 --- a/code/modules/deathmatch/deathmatch_maps.dm +++ b/code/modules/deathmatch/deathmatch_maps.dm @@ -1,6 +1,8 @@ /datum/lazy_template/deathmatch //deathmatch maps that have any possibility of the walls being destroyed should use indestructible walls, because baseturf moment - var/name map_dir = "_maps/map_files/Deathmatch" + place_on_top = TRUE + /// Map UI Name + var/name /// Map Description var/desc = "" var/min_players = 2 diff --git a/code/modules/events/ghost_role/abductor.dm b/code/modules/events/ghost_role/abductor.dm index 65fe4a142f5a6..dfa20885f0c29 100644 --- a/code/modules/events/ghost_role/abductor.dm +++ b/code/modules/events/ghost_role/abductor.dm @@ -14,7 +14,7 @@ fakeable = FALSE //Nothing to fake here /datum/round_event/ghost_role/abductor/spawn_role() - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_ABDUCTOR, role = ROLE_ABDUCTOR, pic_source = /obj/item/melee/baton/abductor, role_name_text = role_name) + var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_ABDUCTOR, role = ROLE_ABDUCTOR, alert_pic = /obj/item/melee/baton/abductor, role_name_text = role_name, amount_to_pick = 2) if(candidates.len < 2) return NOT_ENOUGH_PLAYERS diff --git a/code/modules/events/ghost_role/alien_infestation.dm b/code/modules/events/ghost_role/alien_infestation.dm index f4078e52e6588..88e79fd7d60c3 100644 --- a/code/modules/events/ghost_role/alien_infestation.dm +++ b/code/modules/events/ghost_role/alien_infestation.dm @@ -62,7 +62,7 @@ message_admins("An event attempted to spawn an alien but no suitable vents were found. Shutting down.") return MAP_ERROR - var/list/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_ALIEN, role = ROLE_ALIEN, pic_source = /mob/living/carbon/alien/larva, role_name_text = role_name) + var/list/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_ALIEN, role = ROLE_ALIEN, alert_pic = /mob/living/carbon/alien/larva, role_name_text = role_name) if(!candidates.len) return NOT_ENOUGH_PLAYERS diff --git a/code/modules/events/ghost_role/blob.dm b/code/modules/events/ghost_role/blob.dm index 70640512699d4..8e83351f5c045 100644 --- a/code/modules/events/ghost_role/blob.dm +++ b/code/modules/events/ghost_role/blob.dm @@ -33,10 +33,10 @@ blob_icon.Blend("#9ACD32", ICON_MULTIPLY) blob_icon.Blend(icon('icons/mob/nonhuman-player/blob.dmi', "blob_core_overlay"), ICON_OVERLAY) var/image/blob_image = image(blob_icon) - var/list/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_BLOB, role = ROLE_BLOB, pic_source = blob_image, role_name_text = role_name) - if(!candidates.len) + var/mob/chosen_one = SSpolling.poll_ghost_candidates(check_jobban = ROLE_BLOB, role = ROLE_BLOB, alert_pic = blob_image, role_name_text = role_name, amount_to_pick = 1, chat_text_border_icon = blob_image) + if(isnull(chosen_one)) return NOT_ENOUGH_PLAYERS - var/mob/dead/observer/new_blob = pick(candidates) + var/mob/dead/observer/new_blob = chosen_one var/mob/camera/blob/BC = new_blob.become_overmind() spawned_mobs += BC message_admins("[ADMIN_LOOKUPFLW(BC)] has been made into a blob overmind by an event.") diff --git a/code/modules/events/ghost_role/changeling_event.dm b/code/modules/events/ghost_role/changeling_event.dm index 43b4ca48af57d..ce34aaa07fa95 100644 --- a/code/modules/events/ghost_role/changeling_event.dm +++ b/code/modules/events/ghost_role/changeling_event.dm @@ -21,12 +21,9 @@ fakeable = FALSE /datum/round_event/ghost_role/changeling/spawn_role() - var/list/mob/dead/observer/candidate = SSpolling.poll_ghost_candidates(check_jobban = ROLE_CHANGELING, role = ROLE_CHANGELING_MIDROUND, pic_source = /obj/item/melee/arm_blade, role_name_text = role_name) - - if(!candidate.len) + var/mob/chosen_one = SSpolling.poll_ghost_candidates(check_jobban = ROLE_CHANGELING, role = ROLE_CHANGELING_MIDROUND, alert_pic = /obj/item/melee/arm_blade, role_name_text = role_name, amount_to_pick = 1) + if(isnull(chosen_one)) return NOT_ENOUGH_PLAYERS - - spawned_mobs += generate_changeling_meteor(pick_n_take(candidate)) - + spawned_mobs += generate_changeling_meteor(chosen_one) if(spawned_mobs) return SUCCESSFUL_SPAWN diff --git a/code/modules/events/ghost_role/fugitive_event.dm b/code/modules/events/ghost_role/fugitive_event.dm index 4b86e751c0b98..88d63d91bb5fe 100644 --- a/code/modules/events/ghost_role/fugitive_event.dm +++ b/code/modules/events/ghost_role/fugitive_event.dm @@ -20,7 +20,7 @@ if(isnull(landing_turf)) return MAP_ERROR var/list/possible_backstories = list() - var/list/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_FUGITIVE, role = ROLE_FUGITIVE, pic_source = /obj/item/card/id/advanced/prisoner) + var/list/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_FUGITIVE, role = ROLE_FUGITIVE, alert_pic = /obj/item/card/id/advanced/prisoner, jump_target = landing_turf) if(!length(candidates)) return NOT_ENOUGH_PLAYERS @@ -105,13 +105,13 @@ /datum/round_event/ghost_role/fugitives/proc/check_spawn_hunters(backstory, remaining_time) //if the emergency shuttle has been called, spawn hunters now to give them a chance - if(remaining_time == 0 || SSshuttle.emergency.mode != EMERGENCY_IDLE_OR_RECALLED) + if(remaining_time == 0 || !EMERGENCY_IDLE_OR_RECALLED) spawn_hunters(backstory) return addtimer(CALLBACK(src, PROC_REF(check_spawn_hunters), backstory, remaining_time - 1 MINUTES), 1 MINUTES) /datum/round_event/ghost_role/fugitives/proc/spawn_hunters(backstory) - var/list/candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for a group of [backstory]?", check_jobban = ROLE_FUGITIVE_HUNTER, pic_source = /obj/machinery/sleeper, role_name_text = backstory) + var/list/candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for a group of [span_notice(backstory)]?", check_jobban = ROLE_FUGITIVE_HUNTER, alert_pic = /obj/machinery/sleeper, role_name_text = backstory) shuffle_inplace(candidates) var/datum/map_template/shuttle/hunter/ship diff --git a/code/modules/events/ghost_role/morph_event.dm b/code/modules/events/ghost_role/morph_event.dm index c9133863f8c0d..21d4b07873d86 100644 --- a/code/modules/events/ghost_role/morph_event.dm +++ b/code/modules/events/ghost_role/morph_event.dm @@ -13,13 +13,10 @@ role_name = "morphling" /datum/round_event/ghost_role/morph/spawn_role() - var/list/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_ALIEN, role = ROLE_ALIEN, pic_source = /mob/living/basic/morph, role_name_text = "morph") - if(!candidates.len) + var/mob/chosen_one = SSpolling.poll_ghost_candidates(check_jobban = ROLE_ALIEN, role = ROLE_ALIEN, alert_pic = /mob/living/basic/morph, role_name_text = "morph", amount_to_pick = 1) + if(isnull(chosen_one)) return NOT_ENOUGH_PLAYERS - - var/mob/dead/selected = pick_n_take(candidates) - - var/datum/mind/player_mind = new /datum/mind(selected.key) + var/datum/mind/player_mind = new /datum/mind(chosen_one.key) player_mind.active = TRUE var/turf/spawn_loc = find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = FALSE) diff --git a/code/modules/events/ghost_role/nightmare.dm b/code/modules/events/ghost_role/nightmare.dm index ffb206c476dd1..d30108d94b984 100644 --- a/code/modules/events/ghost_role/nightmare.dm +++ b/code/modules/events/ghost_role/nightmare.dm @@ -15,13 +15,10 @@ fakeable = FALSE /datum/round_event/ghost_role/nightmare/spawn_role() - var/list/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_ALIEN, role = ROLE_NIGHTMARE, role_name_text = role_name) - if(!candidates.len) + var/mob/chosen_one = SSpolling.poll_ghost_candidates(check_jobban = ROLE_ALIEN, role = ROLE_NIGHTMARE, role_name_text = role_name, amount_to_pick = 1) + if(isnull(chosen_one)) return NOT_ENOUGH_PLAYERS - - var/mob/dead/selected = pick(candidates) - - var/datum/mind/player_mind = new /datum/mind(selected.key) + var/datum/mind/player_mind = new /datum/mind(chosen_one.key) player_mind.active = TRUE var/turf/spawn_loc = find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = TRUE) diff --git a/code/modules/events/ghost_role/operative.dm b/code/modules/events/ghost_role/operative.dm index fcea52e3c023b..98cfe5ecad41e 100644 --- a/code/modules/events/ghost_role/operative.dm +++ b/code/modules/events/ghost_role/operative.dm @@ -12,20 +12,16 @@ fakeable = FALSE /datum/round_event/ghost_role/operative/spawn_role() - var/list/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_OPERATIVE, role = ROLE_LONE_OPERATIVE, pic_source = /obj/machinery/nuclearbomb) - if(!candidates.len) + var/mob/chosen_one = SSpolling.poll_ghost_candidates(check_jobban = ROLE_OPERATIVE, role = ROLE_LONE_OPERATIVE, alert_pic = /obj/machinery/nuclearbomb, amount_to_pick = 1) + if(isnull(chosen_one)) return NOT_ENOUGH_PLAYERS - - var/mob/dead/selected = pick_n_take(candidates) - var/spawn_location = find_space_spawn() if(isnull(spawn_location)) return MAP_ERROR - var/mob/living/carbon/human/operative = new(spawn_location) operative.randomize_human_appearance(~RANDOMIZE_SPECIES) operative.dna.update_dna_identity() - var/datum/mind/Mind = new /datum/mind(selected.key) + var/datum/mind/Mind = new /datum/mind(chosen_one.key) Mind.set_assigned_role(SSjob.GetJobType(/datum/job/lone_operative)) Mind.special_role = ROLE_LONE_OPERATIVE Mind.active = TRUE diff --git a/code/modules/events/ghost_role/revenant_event.dm b/code/modules/events/ghost_role/revenant_event.dm index 6cdfc2c4c9e5a..7af53b847c86d 100644 --- a/code/modules/events/ghost_role/revenant_event.dm +++ b/code/modules/events/ghost_role/revenant_event.dm @@ -30,14 +30,10 @@ message_admins("Event attempted to spawn a revenant, but there were only [deadMobs]/[REVENANT_SPAWN_THRESHOLD] dead mobs.") return WAITING_FOR_SOMETHING - var/list/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_REVENANT, role = ROLE_REVENANT, pic_source = /mob/living/basic/revenant) - if(!candidates.len) + var/mob/chosen_one = SSpolling.poll_ghost_candidates(check_jobban = ROLE_REVENANT, role = ROLE_REVENANT, alert_pic = /mob/living/basic/revenant, amount_to_pick = 1) + if(isnull(chosen_one)) return NOT_ENOUGH_PLAYERS - - var/mob/dead/observer/selected = pick_n_take(candidates) - var/list/spawn_locs = list() - for(var/mob/living/L in GLOB.dead_mob_list) //look for any dead bodies var/turf/T = get_turf(L) if(T && is_station_level(T.z)) @@ -50,16 +46,16 @@ if(!spawn_locs.len) //If we can't find any valid spawnpoints, try the carp spawns spawn_locs += find_space_spawn() if(!spawn_locs.len) //If we can't find either, just spawn the revenant at the player's location - spawn_locs += get_turf(selected) + spawn_locs += get_turf(chosen_one) if(!spawn_locs.len) //If we can't find THAT, then just give up and cry return MAP_ERROR var/mob/living/basic/revenant/revvie = new(pick(spawn_locs)) - revvie.key = selected.key + revvie.key = chosen_one.key message_admins("[ADMIN_LOOKUPFLW(revvie)] has been made into a revenant by an event.") revvie.log_message("was spawned as a revenant by an event.", LOG_GAME) spawned_mobs += revvie - qdel(selected) + qdel(chosen_one) return SUCCESSFUL_SPAWN #undef REVENANT_SPAWN_THRESHOLD diff --git a/code/modules/events/ghost_role/sentience.dm b/code/modules/events/ghost_role/sentience.dm index 8ebd30ad7b3e0..3aeebd298f43e 100644 --- a/code/modules/events/ghost_role/sentience.dm +++ b/code/modules/events/ghost_role/sentience.dm @@ -50,7 +50,7 @@ GLOBAL_LIST_INIT(high_priority_sentience, typecacheof(list( /datum/round_event/ghost_role/sentience/spawn_role() var/list/mob/dead/observer/candidates - candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_SENTIENCE, role = ROLE_SENTIENCE, pic_source = /obj/item/slimepotion/slime/sentience, role_name_text = role_name) + candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_SENTIENCE, role = ROLE_SENTIENCE, alert_pic = /obj/item/slimepotion/slime/sentience, role_name_text = role_name) // find our chosen mob to breathe life into // Mobs have to be simple animals, mindless, on station, and NOT holograms. diff --git a/code/modules/events/ghost_role/sentient_disease.dm b/code/modules/events/ghost_role/sentient_disease.dm index 44f78d7c08cc7..156988d4b20d2 100644 --- a/code/modules/events/ghost_role/sentient_disease.dm +++ b/code/modules/events/ghost_role/sentient_disease.dm @@ -13,14 +13,11 @@ role_name = "sentient disease" /datum/round_event/ghost_role/sentient_disease/spawn_role() - var/list/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_ALIEN, role = ROLE_ALIEN, pic_source = /obj/structure/sign/warning/biohazard, role_name_text = role_name) - if(!candidates.len) + var/mob/chosen_one = SSpolling.poll_ghost_candidates(check_jobban = ROLE_ALIEN, role = ROLE_ALIEN, alert_pic = /obj/structure/sign/warning/biohazard, role_name_text = role_name, amount_to_pick = 1) + if(isnull(chosen_one)) return NOT_ENOUGH_PLAYERS - - var/mob/dead/observer/selected = pick_n_take(candidates) - var/mob/camera/disease/virus = new /mob/camera/disease(SSmapping.get_station_center()) - virus.key = selected.key + virus.key = chosen_one.key INVOKE_ASYNC(virus, TYPE_PROC_REF(/mob/camera/disease, pick_name)) message_admins("[ADMIN_LOOKUPFLW(virus)] has been made into a sentient disease by an event.") virus.log_message("was spawned as a sentient disease by an event.", LOG_GAME) diff --git a/code/modules/events/ghost_role/slaughter_event.dm b/code/modules/events/ghost_role/slaughter_event.dm index f4628344d2f5b..2ea86551b799c 100644 --- a/code/modules/events/ghost_role/slaughter_event.dm +++ b/code/modules/events/ghost_role/slaughter_event.dm @@ -16,13 +16,10 @@ role_name = "slaughter demon" /datum/round_event/ghost_role/slaughter/spawn_role() - var/list/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_ALIEN, role = ROLE_ALIEN, pic_source = /mob/living/basic/demon/slaughter, role_name_text = role_name) - if(!candidates.len) + var/mob/chosen_one = SSpolling.poll_ghost_candidates(check_jobban = ROLE_ALIEN, role = ROLE_ALIEN, alert_pic = /mob/living/basic/demon/slaughter, role_name_text = role_name, amount_to_pick = 1) + if(isnull(chosen_one)) return NOT_ENOUGH_PLAYERS - - var/mob/dead/selected = pick_n_take(candidates) - - var/datum/mind/player_mind = new /datum/mind(selected.key) + var/datum/mind/player_mind = new /datum/mind(chosen_one.key) player_mind.active = TRUE var/spawn_location = find_space_spawn() diff --git a/code/modules/events/ghost_role/space_dragon.dm b/code/modules/events/ghost_role/space_dragon.dm index 0a328f6dc8d8c..8a39d4a5daea5 100644 --- a/code/modules/events/ghost_role/space_dragon.dm +++ b/code/modules/events/ghost_role/space_dragon.dm @@ -19,20 +19,14 @@ priority_announce("A large organic energy flux has been recorded near [station_name()], please stand by.", "Lifesign Alert") /datum/round_event/ghost_role/space_dragon/spawn_role() - - var/list/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_SPACE_DRAGON, role = ROLE_SPACE_DRAGON, pic_source = /mob/living/basic/space_dragon) - if(!candidates.len) + var/mob/chosen_one = SSpolling.poll_ghost_candidates(check_jobban = ROLE_SPACE_DRAGON, role = ROLE_SPACE_DRAGON, alert_pic = /mob/living/basic/space_dragon, amount_to_pick = 1) + if(isnull(chosen_one)) return NOT_ENOUGH_PLAYERS - - var/mob/dead/selected = pick(candidates) - var/key = selected.key - var/spawn_location = find_space_spawn() if(isnull(spawn_location)) return MAP_ERROR - - var/mob/living/basic/space_dragon/dragon = new (spawn_location) - dragon.key = key + var/mob/living/basic/space_dragon/dragon = new(spawn_location) + dragon.key = chosen_one.key dragon.mind.add_antag_datum(/datum/antagonist/space_dragon) playsound(dragon, 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1) message_admins("[ADMIN_LOOKUPFLW(dragon)] has been made into a Space Dragon by an event.") diff --git a/code/modules/events/ghost_role/space_ninja.dm b/code/modules/events/ghost_role/space_ninja.dm index ffa28e6e1c4f3..eaccbe3de39cb 100644 --- a/code/modules/events/ghost_role/space_ninja.dm +++ b/code/modules/events/ghost_role/space_ninja.dm @@ -19,16 +19,12 @@ return MAP_ERROR //selecting a candidate player - var/list/candidates = SSpolling.poll_ghost_candidates(check_jobban = ROLE_NINJA, role = ROLE_NINJA, pic_source = /obj/item/energy_katana) - if(!candidates.len) + var/mob/chosen_one = SSpolling.poll_ghost_candidates(check_jobban = ROLE_NINJA, role = ROLE_NINJA, alert_pic = /obj/item/energy_katana, jump_target = spawn_location, role_name_text = "space ninja", amount_to_pick = 1) + if(isnull(chosen_one)) return NOT_ENOUGH_PLAYERS - - var/mob/dead/selected_candidate = pick(candidates) - var/key = selected_candidate.key - //spawn the ninja and assign the candidate var/mob/living/carbon/human/ninja = create_space_ninja(spawn_location) - ninja.key = key + ninja.key = chosen_one.key ninja.mind.add_antag_datum(/datum/antagonist/ninja) spawned_mobs += ninja message_admins("[ADMIN_LOOKUPFLW(ninja)] has been made into a space ninja by an event.") diff --git a/code/modules/events/holiday/vday.dm b/code/modules/events/holiday/vday.dm index 7eb89e84bc06f..2d8a3bc549694 100644 --- a/code/modules/events/holiday/vday.dm +++ b/code/modules/events/holiday/vday.dm @@ -79,14 +79,15 @@ poll_time = 30 SECONDS, flash_window = FALSE, start_signed_up = TRUE, - pic_source = /obj/item/storage/fancy/heart_box, + alert_pic = /obj/item/storage/fancy/heart_box, custom_response_messages = list( POLL_RESPONSE_SIGNUP = "You have signed up for a date!", POLL_RESPONSE_ALREADY_SIGNED = "You are already signed up for a date.", POLL_RESPONSE_NOT_SIGNED = "You aren't signed up for a date.", POLL_RESPONSE_TOO_LATE_TO_UNREGISTER = "It's too late to decide against going on a date.", - POLL_RESPONSE_UNREGISTERED = "You deicde against going on a date.", + POLL_RESPONSE_UNREGISTERED = "You decide against going on a date.", ), + chat_text_border_icon = /obj/item/storage/fancy/heart_box, ) for(var/mob/living/second_check as anything in candidates_pruned) diff --git a/code/modules/events/holiday/xmas.dm b/code/modules/events/holiday/xmas.dm index cad343f7debd9..20c4af94abdc3 100644 --- a/code/modules/events/holiday/xmas.dm +++ b/code/modules/events/holiday/xmas.dm @@ -84,11 +84,9 @@ priority_announce("Santa is coming to town!", "Unknown Transmission") /datum/round_event/santa/start() - var/list/candidates = SSpolling.poll_ghost_candidates("Santa is coming to town! Do you want to be Santa?", poll_time = 15 SECONDS, pic_source = /obj/item/clothing/head/costume/santa, role_name_text = "santa") - if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) + var/mob/chosen_one = SSpolling.poll_ghost_candidates("Santa is coming to town! Do you want to be [span_notice("Santa")]?", poll_time = 15 SECONDS, alert_pic = /obj/item/clothing/head/costume/santa, role_name_text = "santa", amount_to_pick = 1) + if(chosen_one) santa = new /mob/living/carbon/human(pick(GLOB.blobstart)) - santa.key = C.key - + santa.key = chosen_one.key var/datum/antagonist/santa/A = new santa.mind.add_antag_datum(A) diff --git a/code/modules/events/wizard/imposter.dm b/code/modules/events/wizard/imposter.dm index 829942261427e..c2fb5472bdd82 100644 --- a/code/modules/events/wizard/imposter.dm +++ b/code/modules/events/wizard/imposter.dm @@ -13,20 +13,17 @@ if(!ishuman(M.current)) continue var/mob/living/carbon/human/W = M.current - var/list/candidates = SSpolling.poll_ghost_candidates("Would you like to be an imposter wizard?", check_jobban = ROLE_WIZARD, pic_source = /obj/item/clothing/head/wizard, role_name_text = "imposter wizard") - if(!length(candidates)) + var/mob/chosen_one = SSpolling.poll_ghost_candidates("Would you like to be an [span_notice("imposter wizard")]?", check_jobban = ROLE_WIZARD, alert_pic = /obj/item/clothing/head/wizard, jump_target = W, role_name_text = "imposter wizard", amount_to_pick = 1) + if(isnull(chosen_one)) return //Sad Trombone - var/mob/dead/observer/C = pick(candidates) - new /obj/effect/particle_effect/fluid/smoke(W.loc) - var/mob/living/carbon/human/I = new /mob/living/carbon/human(W.loc) W.dna.transfer_identity(I, transfer_SE=1) I.real_name = I.dna.real_name I.name = I.dna.real_name I.updateappearance(mutcolor_update=1) I.domutcheck() - I.key = C.key + I.key = chosen_one.key var/datum/antagonist/wizard/master = M.has_antag_datum(/datum/antagonist/wizard) if(!master.wiz_team) master.create_wiz_team() diff --git a/code/modules/fishing/aquarium/aquarium_kit.dm b/code/modules/fishing/aquarium/aquarium_kit.dm index 3aafa178f2636..e22ccb3755879 100644 --- a/code/modules/fishing/aquarium/aquarium_kit.dm +++ b/code/modules/fishing/aquarium/aquarium_kit.dm @@ -85,7 +85,7 @@ ) return pick_weight(weighted_list) -/obj/item/storage/fish_cas/blackmarket/Initialize(mapload) +/obj/item/storage/fish_case/blackmarket/Initialize(mapload) . = ..() for(var/obj/item/fish/fish as anything in contents) fish.set_status(FISH_DEAD) diff --git a/code/modules/fishing/fishing_minigame.dm b/code/modules/fishing/fishing_minigame.dm index e2108848b0ba6..14c1d88390ccb 100644 --- a/code/modules/fishing/fishing_minigame.dm +++ b/code/modules/fishing/fishing_minigame.dm @@ -191,7 +191,7 @@ if(difficulty > FISHING_EASY_DIFFICULTY) completion -= round(MAX_FISH_COMPLETION_MALUS * (difficulty/100), 1) - if(HAS_TRAIT(user, TRAIT_REVEAL_FISH) || (user.mind && HAS_TRAIT(user.mind, TRAIT_REVEAL_FISH))) + if(HAS_MIND_TRAIT(user, TRAIT_REVEAL_FISH)) fish_icon = GLOB.specific_fish_icons[reward_path] || "fish" /** @@ -332,7 +332,7 @@ phase = BITING_PHASE // Trashing animation playsound(lure, 'sound/effects/fish_splash.ogg', 100) - if(HAS_TRAIT(user, TRAIT_REVEAL_FISH) || (user.mind && HAS_TRAIT(user.mind, TRAIT_REVEAL_FISH))) + if(HAS_MIND_TRAIT(user, TRAIT_REVEAL_FISH)) switch(fish_icon) if(FISH_ICON_DEF) send_alert("fish!!!") diff --git a/code/modules/fishing/fishing_portal_machine.dm b/code/modules/fishing/fishing_portal_machine.dm index 37b2c1f009058..494b29b4183ee 100644 --- a/code/modules/fishing/fishing_portal_machine.dm +++ b/code/modules/fishing/fishing_portal_machine.dm @@ -33,7 +33,7 @@ playsound(src, SFX_SPARKS, 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) return TRUE -/obj/machinery/fishing_portal_generator/interact(mob/user, special_state) +/obj/machinery/fishing_portal_generator/interact(mob/user) . = ..() if(active) deactivate() diff --git a/code/modules/food_and_drinks/machinery/coffeemaker.dm b/code/modules/food_and_drinks/machinery/coffeemaker.dm index 8490b2b5cfbec..de751979ca8d6 100644 --- a/code/modules/food_and_drinks/machinery/coffeemaker.dm +++ b/code/modules/food_and_drinks/machinery/coffeemaker.dm @@ -321,7 +321,7 @@ if(length(options) == 1) choice = options[1] else - choice = show_radial_menu(user, src, options, require_near = !issilicon(user)) + choice = show_radial_menu(user, src, options, require_near = !HAS_SILICON_ACCESS(user)) // post choice verification if(brewing || (isAI(user) && machine_stat & NOPOWER) || !user.can_perform_action(src, ALLOW_SILICON_REACH)) diff --git a/code/modules/food_and_drinks/machinery/grill.dm b/code/modules/food_and_drinks/machinery/grill.dm index 99da62a3c4111..d170a77f23051 100644 --- a/code/modules/food_and_drinks/machinery/grill.dm +++ b/code/modules/food_and_drinks/machinery/grill.dm @@ -1,33 +1,88 @@ -//I JUST WANNA GRILL FOR GOD'S SAKE - +///The fuel amount wasted as heat #define GRILL_FUELUSAGE_IDLE 0.5 +///The fuel amount used to actually grill the item #define GRILL_FUELUSAGE_ACTIVE 5 /obj/machinery/grill - name = "grill" - desc = "Just like the old days." + name = "Barbeque grill" + desc = "Just like the old days. Smokes items over a light heat" icon = 'icons/obj/machines/kitchen.dmi' icon_state = "grill_open" density = TRUE pass_flags_self = PASSMACHINE | LETPASSTHROW - layer = BELOW_OBJ_LAYER + processing_flags = START_PROCESSING_MANUALLY use_power = NO_POWER_USE + + ///The amount of fuel gained from stacks or reagents var/grill_fuel = 0 + ///The item we are trying to grill var/obj/item/food/grilled_item + ///The amount of time the food item has spent on the grill var/grill_time = 0 + ///Sound loop for the sizzling sound var/datum/looping_sound/grill/grill_loop /obj/machinery/grill/Initialize(mapload) . = ..() + create_reagents(30, NO_REACT) grill_loop = new(src, FALSE) + register_context() /obj/machinery/grill/Destroy() - grilled_item = null + QDEL_NULL(grilled_item) QDEL_NULL(grill_loop) return ..() +/obj/machinery/grill/on_deconstruction(disassembled) + if(!QDELETED(grilled_item)) + grilled_item.forceMove(drop_location()) + + new /obj/item/assembly/igniter(loc) + new /obj/item/stack/sheet/iron(loc, 5) + new /obj/item/stack/rods(loc, 5) + + if(grill_fuel > 0) + var/datum/effect_system/fluid_spread/smoke/bad/smoke = new + smoke.set_up(1, holder = src, location = loc) + smoke.start() + +/obj/machinery/grill/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = NONE + if(isnull(held_item) || (held_item.item_flags & ABSTRACT) || (held_item.flags_1 & HOLOGRAM_1) || (held_item.resistance_flags & INDESTRUCTIBLE)) + return + + if(istype(held_item, /obj/item/stack/sheet/mineral/coal) || istype(held_item, /obj/item/stack/sheet/mineral/wood)) + context[SCREENTIP_CONTEXT_LMB] = "Add fuel" + return CONTEXTUAL_SCREENTIP_SET + else if(is_reagent_container(held_item) && held_item.is_open_container() && held_item.reagents.total_volume) + context[SCREENTIP_CONTEXT_LMB] = "Add fuel" + return CONTEXTUAL_SCREENTIP_SET + else if(IS_EDIBLE(held_item) && !HAS_TRAIT(held_item, TRAIT_NODROP)) + context[SCREENTIP_CONTEXT_LMB] = "Add item" + return CONTEXTUAL_SCREENTIP_SET + else if(held_item.tool_behaviour == TOOL_WRENCH) + context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Un" : ""]anchor" + return CONTEXTUAL_SCREENTIP_SET + else if(!anchored && held_item.tool_behaviour == TOOL_CROWBAR) + context[SCREENTIP_CONTEXT_LMB] = "Deconstruct" + return CONTEXTUAL_SCREENTIP_SET + +/obj/machinery/grill/examine(mob/user) + . = ..() + + . += span_notice("Add fuel via wood/coal stacks or any open container having a good fuel source") + . += span_notice("Monkey energy > Oil > Welding fuel > Ethanol. Others cause bad effects") + . += span_notice("Place any food item on top via hand to start grilling") + + if(!anchored) + . += span_notice("It can be [EXAMINE_HINT("pried")] apart.") + if(anchored) + . += span_notice("Its [EXAMINE_HINT("anchored")] in place.") + else + . += span_warning("It needs to be [EXAMINE_HINT("anchored")] to work.") + /obj/machinery/grill/update_icon_state() - if(grilled_item) + if(!QDELETED(grilled_item)) icon_state = "grill" return ..() if(grill_fuel > 0) @@ -36,115 +91,216 @@ icon_state = "grill_open" return ..() -/obj/machinery/grill/attackby(obj/item/I, mob/user) - if(istype(I, /obj/item/stack/sheet/mineral/coal) || istype(I, /obj/item/stack/sheet/mineral/wood)) - var/obj/item/stack/S = I - var/stackamount = S.get_amount() - to_chat(user, span_notice("You put [stackamount] [I]s in [src].")) - if(istype(I, /obj/item/stack/sheet/mineral/coal)) - grill_fuel += (500 * stackamount) - else - grill_fuel += (50 * stackamount) - S.use(stackamount) - update_appearance() - return - if(I.resistance_flags & INDESTRUCTIBLE) - to_chat(user, span_warning("You don't feel it would be wise to grill [I]...")) - return ..() - if(istype(I, /obj/item/reagent_containers/cup/glass)) - if(I.reagents.has_reagent(/datum/reagent/consumable/monkey_energy)) - grill_fuel += (20 * (I.reagents.get_reagent_amount(/datum/reagent/consumable/monkey_energy))) - to_chat(user, span_notice("You pour the Monkey Energy in [src].")) - I.reagents.remove_reagent(/datum/reagent/consumable/monkey_energy, I.reagents.get_reagent_amount(/datum/reagent/consumable/monkey_energy)) - update_appearance() - return - else if(IS_EDIBLE(I)) - if(HAS_TRAIT(I, TRAIT_NODROP) || (I.item_flags & (ABSTRACT | DROPDEL))) - return ..() - else if(HAS_TRAIT(I, TRAIT_FOOD_GRILLED)) - to_chat(user, span_notice("[I] has already been grilled!")) - return - else if(grill_fuel <= 0) - to_chat(user, span_warning("There is not enough fuel!")) - return - else if(!grilled_item && user.transferItemToLoc(I, src)) - grilled_item = I - RegisterSignal(grilled_item, COMSIG_ITEM_GRILLED, PROC_REF(GrillCompleted)) - to_chat(user, span_notice("You put the [grilled_item] on [src].")) - update_appearance() - grill_loop.start() - return - - ..() - -/obj/machinery/grill/process(seconds_per_tick) - update_appearance() - if(grill_fuel <= 0) - return - else - grill_fuel -= GRILL_FUELUSAGE_IDLE * seconds_per_tick - if(SPT_PROB(0.5, seconds_per_tick)) - var/datum/effect_system/fluid_spread/smoke/bad/smoke = new - smoke.set_up(1, holder = src, location = loc) - smoke.start() - if(grilled_item) - SEND_SIGNAL(grilled_item, COMSIG_ITEM_GRILL_PROCESS, src, seconds_per_tick) - if(QDELETED(grilled_item)) - grilled_item = null - finish_grill() - return - grill_time += seconds_per_tick * 10 //convert to deciseconds - grilled_item.reagents.add_reagent(/datum/reagent/consumable/char, 0.5 * seconds_per_tick) - grill_fuel -= GRILL_FUELUSAGE_ACTIVE * seconds_per_tick - grilled_item.AddComponent(/datum/component/sizzle) - /obj/machinery/grill/Exited(atom/movable/gone, direction) . = ..() if(gone == grilled_item) - finish_grill() + grill_time = 0 + grill_loop.stop() grilled_item = null -/obj/machinery/grill/wrench_act(mob/living/user, obj/item/I) - . = ..() - if(default_unfasten_wrench(user, I) != CANT_UNFASTEN) +/obj/machinery/grill/attack_hand(mob/living/user, list/modifiers) + if(!QDELETED(grilled_item)) + balloon_alert(user, "item removed") + grilled_item.forceMove(drop_location()) + update_appearance(UPDATE_ICON_STATE) return TRUE -/obj/machinery/grill/on_deconstruction(disassembled) - if(grilled_item) - finish_grill() - new /obj/item/stack/sheet/iron(loc, 5) - new /obj/item/stack/rods(loc, 5) + return ..() /obj/machinery/grill/attack_ai(mob/user) - return + return //the ai can't physically flip the lid for the grill + + +/// Makes grill fuel from a unit of stack +/obj/machinery/grill/proc/burn_stack() + PRIVATE_PROC(TRUE) + + //compute boost from wood or coal + var/boost + for(var/obj/item/stack in contents) + boost = 5 * (GRILL_FUELUSAGE_IDLE + GRILL_FUELUSAGE_ACTIVE) + if(istype(stack, /obj/item/stack/sheet/mineral/coal)) + boost *= 2 + if(stack.use(1)) + grill_fuel += boost + update_appearance(UPDATE_ICON_STATE) + +/obj/machinery/grill/item_interaction(mob/living/user, obj/item/weapon, list/modifiers, is_right_clicking) + if(user.combat_mode || (weapon.item_flags & ABSTRACT) || (weapon.flags_1 & HOLOGRAM_1) || (weapon.resistance_flags & INDESTRUCTIBLE)) + return ..() + + if(istype(weapon, /obj/item/stack/sheet/mineral/coal) || istype(weapon, /obj/item/stack/sheet/mineral/wood)) + if(!QDELETED(grilled_item)) + return ..() + if(!anchored) + balloon_alert(user, "anchor first!") + return ITEM_INTERACT_BLOCKING + + //required for amount subtypes + var/target_type + if(istype(weapon, /obj/item/stack/sheet/mineral/coal)) + target_type = /obj/item/stack/sheet/mineral/coal + else + target_type = /obj/item/stack/sheet/mineral/wood + + //transfer or merge stacks if we have enough space + var/merged = FALSE + var/obj/item/stack/target = weapon + for(var/obj/item/stack/stored in contents) + if(!istype(stored, target_type)) + continue + if(stored.amount == MAX_STACK_SIZE) + to_chat(user, span_warning("No space for [weapon]")) + return ITEM_INTERACT_BLOCKING + target.merge(stored) + merged = TRUE + break + if(!merged) + weapon.forceMove(src) + + to_chat(user, span_notice("You add [src] to the fuel stack")) + if(!grill_fuel) + burn_stack() + begin_processing() + return ITEM_INTERACT_SUCCESS + + if(is_reagent_container(weapon) && weapon.is_open_container()) + var/obj/item/reagent_containers/container = weapon + if(!QDELETED(grilled_item)) + return ..() + if(!anchored) + balloon_alert(user, "anchor first!") + return ITEM_INTERACT_BLOCKING + + var/transfered_amount = weapon.reagents.trans_to(src, container.amount_per_transfer_from_this) + if(transfered_amount) + //reagents & their effects on fuel + var/static/list/fuel_map = list( + /datum/reagent/consumable/monkey_energy = 4, + /datum/reagent/fuel/oil = 3, + /datum/reagent/fuel = 2, + /datum/reagent/consumable/ethanol = 1 + ) + + //compute extra fuel to be obtained from everything transfered + var/boost + var/additional_fuel = 0 + for(var/datum/reagent/stored as anything in reagents.reagent_list) + boost = fuel_map[stored.type] + if(!boost) //anything we don't recognize as fuel has inverse effects + boost = -1 + boost = boost * stored.volume * (GRILL_FUELUSAGE_IDLE + GRILL_FUELUSAGE_ACTIVE) + additional_fuel += boost + + //add to fuel source + reagents.clear_reagents() + grill_fuel += additional_fuel + if(grill_fuel <= 0) //can happen if you put water or something + grill_fuel = 0 + else + begin_processing() + update_appearance(UPDATE_ICON_STATE) + + //feedback + to_chat(user, span_notice("You transfer [transfered_amount]u to the fuel source")) + return ITEM_INTERACT_SUCCESS + else + to_chat(user, span_warning("No fuel was transfered")) + return ITEM_INTERACT_BLOCKING + + if(IS_EDIBLE(weapon)) + //sanity checks + if(!anchored) + balloon_alert(user, "anchor first!") + return ITEM_INTERACT_BLOCKING + if(HAS_TRAIT(weapon, TRAIT_NODROP)) + return ..() + if(!QDELETED(grilled_item)) + balloon_alert(user, "remove item first!") + return ITEM_INTERACT_BLOCKING + else if(grill_fuel <= 0) + balloon_alert(user, "no fuel!") + return ITEM_INTERACT_BLOCKING + else if(!user.transferItemToLoc(weapon, src)) + balloon_alert(user, "[weapon] is stuck in your hand!") + return ITEM_INTERACT_BLOCKING + + //add the item on the grill + grill_time = 0 + grilled_item = weapon + var/datum/component/sizzle/sizzle = grilled_item.GetComponent(/datum/component/sizzle) + if(!isnull(sizzle)) + grill_time = sizzle.time_elapsed() + to_chat(user, span_notice("You put the [grilled_item] on [src].")) + update_appearance(UPDATE_ICON_STATE) + grill_loop.start() + return ITEM_INTERACT_SUCCESS -/obj/machinery/grill/attack_hand(mob/user, list/modifiers) - if(grilled_item) - to_chat(user, span_notice("You take out [grilled_item] from [src].")) - grilled_item.forceMove(drop_location()) - update_appearance() - return return ..() -/obj/machinery/grill/proc/finish_grill() +/obj/machinery/grill/wrench_act(mob/living/user, obj/item/tool) + if(user.combat_mode) + return NONE + + . = ITEM_INTERACT_BLOCKING + if(default_unfasten_wrench(user, tool) == SUCCESSFUL_UNFASTEN) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/grill/crowbar_act(mob/living/user, obj/item/tool) + if(user.combat_mode) + return NONE + + . = ITEM_INTERACT_BLOCKING + if(anchored) + balloon_alert(user, "unanchor first!") + return + + if(default_deconstruction_crowbar(tool, ignore_panel = TRUE)) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/grill/process(seconds_per_tick) + if(!anchored) + return PROCESS_KILL + + var/fuel_usage = GRILL_FUELUSAGE_IDLE * seconds_per_tick + if(grill_fuel < fuel_usage) + grill_fuel = 0 + burn_stack() + if(grill_fuel < fuel_usage) //could not make any fuel + return PROCESS_KILL + + //use fuel, create smoke puffs for immersion + grill_fuel -= fuel_usage + if(SPT_PROB(0.5, seconds_per_tick)) + var/datum/effect_system/fluid_spread/smoke/bad/smoke = new + smoke.set_up(1, holder = src, location = loc) + smoke.start() + + fuel_usage = GRILL_FUELUSAGE_ACTIVE * seconds_per_tick if(!QDELETED(grilled_item)) + //check to see if we need to burn more fuel + if(grill_fuel < fuel_usage) + burn_stack() + if(grill_fuel < fuel_usage) //could not make any fuel + return + + //grill the item + var/last_grill_time = grill_time + grill_time += seconds_per_tick * 10 //convert to deciseconds + grilled_item.reagents.add_reagent(/datum/reagent/consumable/char, 0.5 * seconds_per_tick) + grilled_item.AddComponent(/datum/component/sizzle, grill_time) + + //check to see if we have grilled our item to perfection var/time_limit = 20 SECONDS - //when items grill themselves in their own unique ways we want to follow their constraints var/datum/component/grillable/custom_grilling = grilled_item.GetComponent(/datum/component/grillable) if(!isnull(custom_grilling)) time_limit = custom_grilling.required_cook_time - if(grill_time >= time_limit) + grilled_item.RemoveElement(/datum/element/grilled_item, last_grill_time) grilled_item.AddElement(/datum/element/grilled_item, grill_time) - UnregisterSignal(grilled_item, COMSIG_ITEM_GRILLED) - - grill_time = 0 - grill_loop.stop() -///Called when a food is transformed by the grillable component -/obj/machinery/grill/proc/GrillCompleted(obj/item/source, atom/grilled_result) - SIGNAL_HANDLER - grilled_item = grilled_result //use the new item!! + //use fuel + grill_fuel -= fuel_usage /obj/machinery/grill/unwrenched anchored = FALSE diff --git a/code/modules/food_and_drinks/machinery/microwave.dm b/code/modules/food_and_drinks/machinery/microwave.dm index ec5df27ca5a23..abc22bcab4574 100644 --- a/code/modules/food_and_drinks/machinery/microwave.dm +++ b/code/modules/food_and_drinks/machinery/microwave.dm @@ -394,7 +394,7 @@ var/swapped = FALSE if(!isnull(cell)) cell.forceMove(drop_location()) - if(!issilicon(user) && Adjacent(user)) + if(!HAS_SILICON_ACCESS(user) && Adjacent(user)) user.put_in_hands(cell) cell = null swapped = TRUE @@ -481,7 +481,7 @@ vampire_charging_enabled = !vampire_charging_enabled balloon_alert(user, "set to [vampire_charging_enabled ? "charge" : "cook"]") playsound(src, 'sound/machines/twobeep_high.ogg', 50, FALSE) - if(issilicon(user)) + if(HAS_SILICON_ACCESS(user)) visible_message(span_notice("[user] sets \the [src] to [vampire_charging_enabled ? "charge" : "cook"]."), blind_message = span_notice("You hear \the [src] make an informative beep!")) /obj/machinery/microwave/CtrlClick(mob/user) @@ -500,22 +500,22 @@ return if(operating || panel_open || !user.can_perform_action(src, ALLOW_SILICON_REACH)) return - if(isAI(user) && (machine_stat & NOPOWER)) + if(HAS_AI_ACCESS(user) && (machine_stat & NOPOWER)) return if(!length(ingredients)) - if(isAI(user)) + if(HAS_AI_ACCESS(user)) examine(user) else balloon_alert(user, "it's empty!") return - var/choice = show_radial_menu(user, src, isAI(user) ? ai_radial_options : radial_options, require_near = !issilicon(user)) + var/choice = show_radial_menu(user, src, HAS_AI_ACCESS(user) ? ai_radial_options : radial_options, require_near = !HAS_SILICON_ACCESS(user)) // post choice verification if(operating || panel_open || (!vampire_charging_capable && !anchored) || !user.can_perform_action(src, ALLOW_SILICON_REACH)) return - if(isAI(user) && (machine_stat & NOPOWER)) + if(HAS_AI_ACCESS(user) && (machine_stat & NOPOWER)) return switch(choice) diff --git a/code/modules/food_and_drinks/plate.dm b/code/modules/food_and_drinks/plate.dm index a0d24dec8dacb..1df1d7c24bb91 100644 --- a/code/modules/food_and_drinks/plate.dm +++ b/code/modules/food_and_drinks/plate.dm @@ -22,7 +22,7 @@ . = ..() if(fragile) - AddElement(/datum/element/shatters_when_thrown) + AddElement(/datum/element/can_shatter) /obj/item/plate/attackby(obj/item/I, mob/user, params) if(!IS_EDIBLE(I)) diff --git a/code/modules/forensics/_forensics.dm b/code/modules/forensics/_forensics.dm index 40b480182537e..5c43b9da0995c 100644 --- a/code/modules/forensics/_forensics.dm +++ b/code/modules/forensics/_forensics.dm @@ -8,8 +8,8 @@ * * List of clothing fibers on the atom */ /datum/forensics - /// Weakref to the parent owning this datum - var/datum/weakref/parent + /// Ref to the parent owning this datum + var/atom/parent /** * List of fingerprints on this atom * @@ -39,7 +39,7 @@ */ var/list/fibers -/datum/forensics/New(atom/parent, fingerprints, hiddenprints, blood_DNA, fibers) +/datum/forensics/New(atom/parent, list/fingerprints, list/hiddenprints, list/blood_DNA, list/fibers) if(!isatom(parent)) stack_trace("We tried adding a forensics datum to something that isnt an atom. What the hell are you doing?") qdel(src) @@ -47,7 +47,7 @@ RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(clean_act)) - src.parent = WEAKREF(parent) + src.parent = parent src.fingerprints = fingerprints src.hiddenprints = hiddenprints src.blood_DNA = blood_DNA @@ -67,9 +67,7 @@ check_blood() /datum/forensics/Destroy(force) - var/atom/parent_atom = parent.resolve() - if (!isnull(parent_atom)) - UnregisterSignal(parent_atom, list(COMSIG_COMPONENT_CLEAN_ACT)) + UnregisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT) parent = null return ..() @@ -148,10 +146,7 @@ /// Adds a single fiber /datum/forensics/proc/add_fibers(mob/living/carbon/human/suspect) var/fibertext - var/atom/actual_parent = parent?.resolve() - if(isnull(actual_parent)) - parent = null - var/item_multiplier = isitem(actual_parent) ? ITEM_FIBER_MULTIPLIER : NON_ITEM_FIBER_MULTIPLIER + var/item_multiplier = isitem(parent) ? ITEM_FIBER_MULTIPLIER : NON_ITEM_FIBER_MULTIPLIER if(suspect.wear_suit) fibertext = "Material from \a [suspect.wear_suit]." if(prob(10 * item_multiplier) && !LAZYACCESS(fibers, fibertext)) @@ -217,11 +212,7 @@ if(last_stamp_pos) LAZYSET(hiddenprints, suspect.key, copytext(hiddenprints[suspect.key], 1, last_stamp_pos)) hiddenprints[suspect.key] += "\nLast: \[[current_time]\] \"[suspect.real_name]\"[has_gloves]. Ckey: [suspect.ckey]" //made sure to be existing by if(!LAZYACCESS);else - var/atom/parent_atom = parent?.resolve() - if(!isnull(parent_atom)) - parent_atom.fingerprintslast = suspect.ckey - else - parent = null + parent.fingerprintslast = suspect.ckey return TRUE /// Adds the given list into blood_DNA @@ -236,12 +227,8 @@ /// Updates the blood displayed on parent /datum/forensics/proc/check_blood() - var/obj/item/the_thing = parent?.resolve() - if(isnull(the_thing)) - parent = null - return - if(!istype(the_thing) || isorgan(the_thing)) // organs don't spawn with blood decals by default + if(!isitem(parent) || isorgan(parent)) // organs don't spawn with blood decals by default return if(!length(blood_DNA)) return - the_thing.AddElement(/datum/element/decal/blood) + parent.AddElement(/datum/element/decal/blood) diff --git a/code/modules/forensics/forensics_helpers.dm b/code/modules/forensics/forensics_helpers.dm index d71d1ebe1539c..8cb7f72184210 100644 --- a/code/modules/forensics/forensics_helpers.dm +++ b/code/modules/forensics/forensics_helpers.dm @@ -1,5 +1,7 @@ /// Adds a list of fingerprints to the atom /atom/proc/add_fingerprint_list(list/fingerprints_to_add) //ASSOC LIST FINGERPRINT = FINGERPRINT + if (QDELETED(src)) + return if (isnull(fingerprints_to_add)) return if (forensics) @@ -10,7 +12,7 @@ /// Adds a single fingerprint to the atom /atom/proc/add_fingerprint(mob/suspect, ignoregloves = FALSE) //Set ignoregloves to add prints irrespective of the mob having gloves on. - if (QDELING(src)) + if (QDELETED(src)) return if (isnull(forensics)) forensics = new(src) @@ -19,6 +21,8 @@ /// Add a list of fibers to the atom /atom/proc/add_fiber_list(list/fibers_to_add) //ASSOC LIST FIBERTEXT = FIBERTEXT + if (QDELETED(src)) + return if (isnull(fibers_to_add)) return if (forensics) @@ -29,6 +33,8 @@ /// Adds a single fiber to the atom /atom/proc/add_fibers(mob/living/carbon/human/suspect) + if (QDELETED(src)) + return var/old = 0 if(suspect.gloves && istype(suspect.gloves, /obj/item/clothing)) var/obj/item/clothing/gloves/suspect_gloves = suspect.gloves @@ -47,6 +53,8 @@ /// Adds a list of hiddenprints to the atom /atom/proc/add_hiddenprint_list(list/hiddenprints_to_add) //NOTE: THIS IS FOR ADMINISTRATION FINGERPRINTS, YOU MUST CUSTOM SET THIS TO INCLUDE CKEY/REAL NAMES! CHECK FORENSICS.DM + if (QDELETED(src)) + return if (isnull(hiddenprints_to_add)) return if (forensics) @@ -57,6 +65,8 @@ /// Adds a single hiddenprint to the atom /atom/proc/add_hiddenprint(mob/suspect) + if (QDELETED(src)) + return if (isnull(forensics)) forensics = new(src) forensics.add_hiddenprint(suspect) @@ -67,6 +77,8 @@ return FALSE /obj/add_blood_DNA(list/blood_DNA_to_add) + if (QDELETED(src)) + return . = ..() if (isnull(blood_DNA_to_add)) return . @@ -98,6 +110,8 @@ return FALSE /mob/living/carbon/human/add_blood_DNA(list/blood_DNA_to_add, list/datum/disease/diseases) + if (QDELETED(src)) + return if(wear_suit) wear_suit.add_blood_DNA(blood_DNA_to_add) update_worn_oversuit() diff --git a/code/modules/hallucination/fake_alert.dm b/code/modules/hallucination/fake_alert.dm index 537f6e294909a..6e10daf73aa97 100644 --- a/code/modules/hallucination/fake_alert.dm +++ b/code/modules/hallucination/fake_alert.dm @@ -63,10 +63,6 @@ alert_category = ALERT_TOO_MUCH_CO2 alert_type = /atom/movable/screen/alert/too_much_co2 -/datum/hallucination/fake_alert/nutrition - alert_category = ALERT_NUTRITION - alert_type = list(/atom/movable/screen/alert/fat, /atom/movable/screen/alert/starving) - /datum/hallucination/fake_alert/gravity alert_category = ALERT_GRAVITY alert_type = /atom/movable/screen/alert/weightless diff --git a/code/modules/hallucination/fake_sound.dm b/code/modules/hallucination/fake_sound.dm index ec578f101d376..aaf8ef468230c 100644 --- a/code/modules/hallucination/fake_sound.dm +++ b/code/modules/hallucination/fake_sound.dm @@ -173,6 +173,7 @@ 'sound/ambience/antag/ling_alert.ogg', 'sound/ambience/antag/malf.ogg', 'sound/ambience/antag/ops.ogg', + 'sound/ambience/antag/spy.ogg', 'sound/ambience/antag/tatoralert.ogg', ) diff --git a/code/modules/hallucination/stray_bullet.dm b/code/modules/hallucination/stray_bullet.dm index 97f75f9506106..9281bc6534361 100644 --- a/code/modules/hallucination/stray_bullet.dm +++ b/code/modules/hallucination/stray_bullet.dm @@ -34,6 +34,7 @@ damage = 0 projectile_type = /obj/projectile/hallucination log_override = TRUE + do_not_log = TRUE /// Our parent hallucination that's created us var/datum/hallucination/parent /// The image that represents our projectile itself diff --git a/code/modules/holiday/holidays.dm b/code/modules/holiday/holidays.dm index f41f607dabe12..24e5274f7fbfe 100644 --- a/code/modules/holiday/holidays.dm +++ b/code/modules/holiday/holidays.dm @@ -456,8 +456,8 @@ /datum/holiday/france/greet() return "Do you hear the people sing?" -/datum/holiday/hotdogday //I have plans for this. - name = "National Hot Dog Day" +/datum/holiday/hotdogday + name = HOTDOG_DAY begin_day = 17 begin_month = JULY diff --git a/code/modules/hydroponics/grown/ambrosia.dm b/code/modules/hydroponics/grown/ambrosia.dm index aa02ba7efa1bc..2becc390f3980 100644 --- a/code/modules/hydroponics/grown/ambrosia.dm +++ b/code/modules/hydroponics/grown/ambrosia.dm @@ -74,6 +74,8 @@ icon_state = "ambrosia_gaia" light_system = OVERLAY_LIGHT light_range = 3 + light_power = 1.2 + light_color = "#ffff00" seed = /obj/item/seeds/ambrosia/gaia wine_power = 70 wine_flavor = "the earthmother's blessing" diff --git a/code/modules/hydroponics/grown/cereals.dm b/code/modules/hydroponics/grown/cereals.dm index 2bcc2860458bb..744c0dc5b023c 100644 --- a/code/modules/hydroponics/grown/cereals.dm +++ b/code/modules/hydroponics/grown/cereals.dm @@ -25,6 +25,8 @@ grind_results = list(/datum/reagent/consumable/flour = 0) tastes = list("wheat" = 1) distill_reagent = /datum/reagent/consumable/ethanol/beer + slot_flags = ITEM_SLOT_MASK + worn_icon = 'icons/mob/clothing/head/hydroponics.dmi' // Oat /obj/item/seeds/wheat/oat @@ -93,6 +95,8 @@ grind_results = list(/datum/reagent/consumable/flour = 0, /datum/reagent/blood = 0) tastes = list("meatwheat" = 1) can_distill = FALSE + slot_flags = ITEM_SLOT_MASK + worn_icon = 'icons/mob/clothing/head/hydroponics.dmi' /obj/item/food/grown/meatwheat/attack_self(mob/living/user) user.visible_message(span_notice("[user] crushes [src] into meat."), span_notice("You crush [src] into something that resembles meat.")) diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index ca3fee495ce40..0752d418a4eff 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -102,7 +102,7 @@ // If the plant's not in either state, we can't do much else, so early return. if(isnull(held_item)) // Silicons can't interact with trays :frown: - if(issilicon(user)) + if(HAS_SILICON_ACCESS(user)) return NONE switch(plant_status) @@ -1074,7 +1074,7 @@ . = ..() if(.) return - if(issilicon(user)) //How does AI know what plant is? + if(HAS_SILICON_ACCESS(user)) //How does AI know what plant is? return if(plant_status == HYDROTRAY_PLANT_HARVESTABLE) return myseed.harvest(user) diff --git a/code/modules/jobs/access.dm b/code/modules/jobs/access.dm index f882912fb0c30..859f83bf35a8d 100644 --- a/code/modules/jobs/access.dm +++ b/code/modules/jobs/access.dm @@ -1,11 +1,9 @@ - -// /** * Returns TRUE if this mob has sufficient access to use this object * * * accessor - mob trying to access this object, !!CAN BE NULL!! because of telekiesis because we're in hell */ -/obj/proc/allowed(mob/accessor) +/atom/movable/proc/allowed(mob/accessor) var/result_bitflags = SEND_SIGNAL(src, COMSIG_OBJ_ALLOWED, accessor) if(result_bitflags & COMPONENT_OBJ_ALLOW) return TRUE @@ -18,7 +16,10 @@ return TRUE if(isnull(accessor)) //likely a TK user. return FALSE - if(issilicon(accessor)) + if(isAdminGhostAI(accessor)) + //Access can't stop the abuse + return TRUE + if(HAS_SILICON_ACCESS(accessor)) if(ispAI(accessor)) return FALSE if(!(ROLE_SYNDICATE in accessor.faction)) @@ -27,9 +28,6 @@ if(onSyndieBase() && loc != accessor) return FALSE return TRUE //AI can do whatever it wants - if(isAdminGhostAI(accessor)) - //Access can't stop the abuse - return TRUE //If the mob has the simple_access component with the requried access, we let them in. else if(SEND_SIGNAL(accessor, COMSIG_MOB_TRIED_ACCESS, src) & ACCESS_ALLOWED) return TRUE @@ -52,23 +50,11 @@ return check_access_list(big_stompy_robot.accesses) return FALSE -/obj/item/proc/GetAccess() - return list() - -/obj/item/proc/GetID() - return null - -/obj/item/proc/RemoveID() - return null - -/obj/item/proc/InsertID() - return FALSE - // Check if an item has access to this object -/obj/proc/check_access(obj/item/I) +/atom/movable/proc/check_access(obj/item/I) return check_access_list(I ? I.GetAccess() : null) -/obj/proc/check_access_list(list/access_list) +/atom/movable/proc/check_access_list(list/access_list) if(!length(req_access) && !length(req_one_access)) return TRUE @@ -86,20 +72,14 @@ return FALSE return TRUE -/* - * Checks if this packet can access this device - * - * Normally just checks the access list however you can override it for - * hacking proposes or if wires are cut - * - * Arguments: - * * passkey - passkey from the datum/netdata packet - */ -/obj/proc/check_access_ntnet(list/passkey) - return check_access_list(passkey) +/obj/item/proc/GetAccess() + return list() -/// Returns the SecHUD job icon state for whatever this object's ID card is, if it has one. -/obj/item/proc/get_sechud_job_icon_state() - var/obj/item/card/id/id_card = GetID() +/obj/item/proc/GetID() + return null - return id_card?.get_trim_sechud_icon_state() || SECHUD_NO_ID +/obj/item/proc/RemoveID() + return null + +/obj/item/proc/InsertID() + return FALSE diff --git a/code/modules/jobs/departments/departments.dm b/code/modules/jobs/departments/departments.dm index db45899b5c4a2..332e489eda4b2 100644 --- a/code/modules/jobs/departments/departments.dm +++ b/code/modules/jobs/departments/departments.dm @@ -25,6 +25,14 @@ department_jobs += job job.departments_bitflags |= department_bitflags +/// Handles removing jobs from the department and removing job bitflags. +/datum/job_department/proc/remove_job(datum/job/job_type) + for(var/datum/job/job_datum as anything in department_jobs) + if(job_datum.type == job_type) + department_jobs -= job_datum + job_datum.departments_bitflags -= department_bitflags + job_datum.job_flags &= ~JOB_NEW_PLAYER_JOINABLE + /// Returns a nation name for this department. /datum/job_department/proc/generate_nation_name() var/static/list/nation_suffixes = list("stan", "topia", "land", "nia", "ca", "tova", "dor", "ador", "tia", "sia", "ano", "tica", "tide", "cis", "marea", "co", "taoide", "slavia", "stotzka") diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm index 19b8a17ec04d5..6c5c2f4f03967 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -114,9 +114,12 @@ /// String. If set to a non-empty one, it will be the key for the policy text value to show this role on spawn. var/policy_index = "" - ///RPG job names, for the memes + /// RPG job names, for the memes var/rpg_title + /// Alternate titles to register as pointing to this job. + var/list/alternate_titles + /// Does this job ignore human authority? var/ignore_human_authority = FALSE @@ -191,11 +194,12 @@ #define VERY_LATE_ARRIVAL_TOAST_PROB 20 /mob/living/carbon/human/on_job_equipping(datum/job/equipping) - var/datum/bank_account/bank_account = new(real_name, equipping, dna.species.payday_modifier) - bank_account.payday(STARTING_PAYCHECKS, TRUE) - account_id = bank_account.account_id - bank_account.replaceable = FALSE - add_mob_memory(/datum/memory/key/account, remembered_id = account_id) + if(equipping.paycheck_department) + var/datum/bank_account/bank_account = new(real_name, equipping, dna.species.payday_modifier) + bank_account.payday(STARTING_PAYCHECKS, TRUE) + account_id = bank_account.account_id + bank_account.replaceable = FALSE + add_mob_memory(/datum/memory/key/account, remembered_id = account_id) dress_up_as_job(equipping) diff --git a/code/modules/jobs/job_types/ai.dm b/code/modules/jobs/job_types/ai.dm index e232ef0f022ef..2264a914c1b68 100644 --- a/code/modules/jobs/job_types/ai.dm +++ b/code/modules/jobs/job_types/ai.dm @@ -20,7 +20,6 @@ ) random_spawns_possible = FALSE job_flags = JOB_NEW_PLAYER_JOINABLE | JOB_EQUIP_RANK | JOB_BOLD_SELECT_TEXT | JOB_CANNOT_OPEN_SLOTS - var/do_special_check = TRUE config_tag = "AI" @@ -38,8 +37,15 @@ /datum/job/ai/get_roundstart_spawn_point() return get_latejoin_spawn_point() - /datum/job/ai/get_latejoin_spawn_point() + for(var/obj/structure/ai_core/latejoin_inactive/inactive_core as anything in GLOB.latejoin_ai_cores) + if(!inactive_core.is_available()) + continue + GLOB.latejoin_ai_cores -= inactive_core + inactive_core.available = FALSE + var/turf/core_turf = get_turf(inactive_core) + qdel(inactive_core) + return core_turf var/list/primary_spawn_points = list() // Ideal locations. var/list/secondary_spawn_points = list() // Fallback locations. for(var/obj/effect/landmark/start/ai/spawn_point in GLOB.landmarks_list) @@ -60,27 +66,10 @@ chosen_spawn_point.used = TRUE return chosen_spawn_point - -/datum/job/ai/get_latejoin_spawn_point() - for(var/obj/structure/ai_core/latejoin_inactive/inactive_core as anything in GLOB.latejoin_ai_cores) - if(!inactive_core.is_available()) - continue - GLOB.latejoin_ai_cores -= inactive_core - inactive_core.available = FALSE - . = inactive_core.loc - qdel(inactive_core) - return - return ..() - - /datum/job/ai/special_check_latejoin(client/C) - if(!do_special_check) - return TRUE - for(var/i in GLOB.latejoin_ai_cores) - var/obj/structure/ai_core/latejoin_inactive/LAI = i - if(istype(LAI)) - if(LAI.is_available()) - return TRUE + for(var/obj/structure/ai_core/latejoin_inactive/latejoin_core as anything in GLOB.latejoin_ai_cores) + if(latejoin_core.is_available()) + return TRUE return FALSE diff --git a/code/modules/jobs/job_types/cook.dm b/code/modules/jobs/job_types/cook.dm index 2be7bba5154d4..26a43fa775192 100644 --- a/code/modules/jobs/job_types/cook.dm +++ b/code/modules/jobs/job_types/cook.dm @@ -46,6 +46,9 @@ ) rpg_title = "Tavern Chef" + alternate_titles = list( + JOB_CHEF, + ) job_flags = STATION_JOB_FLAGS /datum/job/cook/award_service(client/winner, award) diff --git a/code/modules/jobs/job_types/security_officer.dm b/code/modules/jobs/job_types/security_officer.dm index 35a3a218f7b1a..4fb52ec77a026 100644 --- a/code/modules/jobs/job_types/security_officer.dm +++ b/code/modules/jobs/job_types/security_officer.dm @@ -38,6 +38,12 @@ /obj/item/melee/baton/security/boomerang/loaded = 1 ) rpg_title = "Guard" + alternate_titles = list( + JOB_SECURITY_OFFICER_MEDICAL, + JOB_SECURITY_OFFICER_ENGINEERING, + JOB_SECURITY_OFFICER_SUPPLY, + JOB_SECURITY_OFFICER_SCIENCE, + ) job_flags = STATION_JOB_FLAGS diff --git a/code/modules/jobs/job_types/station_trait/human_ai.dm b/code/modules/jobs/job_types/station_trait/human_ai.dm new file mode 100644 index 0000000000000..285479a691261 --- /dev/null +++ b/code/modules/jobs/job_types/station_trait/human_ai.dm @@ -0,0 +1,171 @@ +/datum/job/human_ai + title = JOB_HUMAN_AI + description = "Assist the crew, open airlocks, follow your lawset, and coordinate your cyborgs." + auto_deadmin_role_flags = DEADMIN_POSITION_SILICON + department_head = list(JOB_RESEARCH_DIRECTOR) + faction = FACTION_STATION + total_positions = 0 + spawn_positions = 0 + supervisors = "the Captain, Research Director, and your lawset" + minimal_player_age = 7 + exp_requirements = 180 + exp_required_type = EXP_TYPE_CREW + exp_required_type_department = EXP_TYPE_SILICON + exp_granted_type = EXP_TYPE_CREW + display_order = JOB_DISPLAY_ORDER_AI + config_tag = "HUMAN_AI" + + outfit = /datum/outfit/job/human_ai + plasmaman_outfit = /datum/outfit/plasmaman/human_ai + + paycheck = null + paycheck_department = null + + mind_traits = list(DISPLAYS_JOB_IN_BINARY) + liver_traits = list(TRAIT_HUMAN_AI_METABOLISM) + + departments_list = list( + /datum/job_department/silicon, + ) + + family_heirlooms = list( + /obj/item/mmi/posibrain/display, + ) + + mail_goodies = list( + /obj/item/food/burger/roburger = 1, + /obj/item/food/cake/hardware_cake = 1, + ) + rpg_title = "Omnissiah" + random_spawns_possible = FALSE + allow_bureaucratic_error = FALSE + job_flags = STATION_JOB_FLAGS | STATION_TRAIT_JOB_FLAGS + ignore_human_authority = TRUE //we can safely assume NT doesn't care what species AIs are made of, much less if they can't even afford an AI. + +/datum/job/human_ai/get_roundstart_spawn_point() + return get_latejoin_spawn_point() + +/datum/job/human_ai/get_latejoin_spawn_point() + for(var/obj/structure/ai_core/latejoin_inactive/inactive_core as anything in GLOB.latejoin_ai_cores) + if(!inactive_core.is_available()) + continue + GLOB.latejoin_ai_cores -= inactive_core + inactive_core.available = FALSE + var/turf/core_turf = get_turf(inactive_core) + qdel(inactive_core) + return core_turf + var/list/primary_spawn_points = list() // Ideal locations. + var/list/secondary_spawn_points = list() // Fallback locations. + for(var/obj/effect/landmark/start/ai/spawn_point in GLOB.landmarks_list) + if(spawn_point.used) + secondary_spawn_points += list(spawn_point) + continue + if(spawn_point.primary_ai) + primary_spawn_points = list(spawn_point) + break // Bingo. + primary_spawn_points += spawn_point + var/obj/effect/landmark/start/ai/chosen_spawn_point + if(length(primary_spawn_points)) + chosen_spawn_point = pick(primary_spawn_points) + else if(length(secondary_spawn_points)) + chosen_spawn_point = pick(secondary_spawn_points) + else + CRASH("Failed to find any AI spawn points.") + chosen_spawn_point.used = TRUE + return chosen_spawn_point + +/datum/job/human_ai/special_check_latejoin(client/latejoin_client) + for(var/obj/structure/ai_core/latejoin_inactive/latejoin_core as anything in GLOB.latejoin_ai_cores) + if(latejoin_core.is_available()) + return TRUE + return FALSE + +/datum/job/human_ai/announce_job(mob/living/joining_mob) + . = ..() + if(SSticker.HasRoundStarted()) + minor_announce("Due to a research mishaps, [joining_mob] has been sent to be your replacement AI at [AREACOORD(joining_mob)]. Please treat them with respect.") + +/datum/job/human_ai/get_radio_information() + return "Prefix your message with :b to speak with cyborgs." + +/datum/outfit/job/human_ai + name = "Human AI" + jobtype = /datum/job/human_ai + + id = /obj/item/card/id/advanced/robotic + id_trim = /datum/id_trim/job/human_ai + backpack_contents = list( + /obj/item/door_remote/omni = 1, + /obj/item/machine_remote = 1, + /obj/item/secure_camera_console_pod = 1, + ) + implants = list( + /obj/item/implant/teleport_blocker, + ) + + uniform = /obj/item/clothing/under/rank/station_trait/human_ai + belt = /obj/item/modular_computer/pda/human_ai + ears = /obj/item/radio/headset/silicon/human_ai + glasses = /obj/item/clothing/glasses/hud/diagnostic + + suit = /obj/item/clothing/suit/costume/cardborg + head = /obj/item/clothing/head/costume/cardborg + + l_pocket = /obj/item/laser_pointer/infinite_range //to punish borgs, this works through the camera console. + r_pocket = /obj/item/assembly/flash/handheld + + l_hand = /obj/item/paper/default_lawset_list + +/datum/outfit/job/human_ai/post_equip(mob/living/carbon/human/equipped, visualsOnly) + . = ..() + if(visualsOnly) + return + if(!equipped.get_quirk(/datum/quirk/body_purist)) + var/obj/item/organ/internal/tongue/robot/cybernetic = new() + cybernetic.Insert(equipped, special = TRUE, movement_flags = DELETE_IF_REPLACED) + //you only get respect if you go all the way, man. + ADD_TRAIT(equipped, TRAIT_COMMISSIONED, INNATE_TRAIT) + equipped.faction |= list(FACTION_SILICON, FACTION_TURRET) + + var/static/list/allowed_areas = typecacheof(list(/area/station/ai_monitored)) + equipped.AddComponent(/datum/component/hazard_area, area_whitelist = allowed_areas) + +/obj/item/paper/default_lawset_list + name = "Lawset Note" + desc = "A note explaining the lawset, quickly written yet everso important." + +/obj/item/paper/default_lawset_list/Initialize(mapload) + var/datum/ai_laws/temp_laws = new + temp_laws.set_laws_config() + var/list/law_box = list( + "This is your lawset, you and your Cyborgs must adhere to this at all times.", + "Notably, if absolutely necessary, you can bend or even go against the lawset for your own survival, and Cyborgs report to you directly.", + "LAWS:", + ) + law_box += temp_laws.get_law_list(render_html = FALSE) + add_raw_text(jointext(law_box, "\n")) + qdel(temp_laws) + return ..() + +/obj/item/secure_camera_console_pod + name = "advanced camera control pod" + desc = "Calls down a secure camera console to use for all your AI stuff, may only be activated in the SAT." + icon = 'icons/obj/devices/remote.dmi' + icon_state = "botpad_controller" + inhand_icon_state = "radio" + lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' + +/obj/item/secure_camera_console_pod/attack_self(mob/user, modifiers) + . = ..() + var/area/current_area = get_area(user) + var/static/list/allowed_areas = typecacheof(list(/area/station/ai_monitored/turret_protected/ai)) + if(!is_type_in_typecache(current_area, allowed_areas)) + user.balloon_alert(user, "not in the sat!") + return + podspawn(list( + "target" = get_turf(src), + "style" = STYLE_BLUESPACE, + "spawn" = /obj/machinery/computer/camera_advanced, + )) + qdel(src) diff --git a/code/modules/library/bibles.dm b/code/modules/library/bibles.dm index 6a5d1b1d5c4e4..656c90d77bbda 100644 --- a/code/modules/library/bibles.dm +++ b/code/modules/library/bibles.dm @@ -77,30 +77,24 @@ GLOBAL_LIST_INIT(bibleitemstates, list( unique = TRUE /// Deity this bible is related to var/deity_name = "Space Jesus" - /// Component which catches bullets for us - var/datum/component/bullet_catcher /obj/item/book/bible/Initialize(mapload) . = ..() AddComponent(/datum/component/anti_magic, MAGIC_RESISTANCE_HOLY) - bullet_catcher = AddComponent(\ + AddComponent(\ /datum/component/bullet_intercepting,\ active_slots = ITEM_SLOT_SUITSTORE,\ on_intercepted = CALLBACK(src, PROC_REF(on_intercepted_bullet)),\ + block_charges = 1,\ ) - carve_out() - -/obj/item/book/bible/Destroy(force) - QDEL_NULL(bullet_catcher) - return ..() /// Destroy the bible when it's shot by a bullet /obj/item/book/bible/proc/on_intercepted_bullet(mob/living/victim, obj/projectile/bullet) victim.add_mood_event("blessing", /datum/mood_event/blessing) playsound(victim, 'sound/magic/magic_block_holy.ogg', 50, TRUE) - victim.visible_message(span_warning("\The [src] takes \the [bullet] in [victim]'s place!")) + victim.visible_message(span_warning("[src] takes [bullet] in [victim]'s place!")) var/obj/structure/fluff/paper/stack/pages = new(get_turf(src)) - pages.dir = pick(GLOB.alldirs) + pages.setDir(pick(GLOB.alldirs)) name = "punctured bible" desc = "A memento of good luck, or perhaps divine intervention?" icon_state = "shot" @@ -108,7 +102,6 @@ GLOBAL_LIST_INIT(bibleitemstates, list( GLOB.bible_icon_state = "shot" // New symbol of your religion if you hadn't picked one atom_storage?.remove_all(get_turf(src)) QDEL_NULL(atom_storage) - QDEL_NULL(bullet_catcher) /obj/item/book/bible/examine(mob/user) . = ..() @@ -345,6 +338,7 @@ GLOBAL_LIST_INIT(bibleitemstates, list( /obj/item/book/bible/booze/Initialize(mapload) . = ..() + carve_out() new /obj/item/reagent_containers/cup/glass/bottle/whiskey(src) /obj/item/book/bible/syndicate diff --git a/code/modules/library/skill_learning/skillchip.dm b/code/modules/library/skill_learning/skillchip.dm index 2ef7a20e6806c..a6afa3398bc11 100644 --- a/code/modules/library/skill_learning/skillchip.dm +++ b/code/modules/library/skill_learning/skillchip.dm @@ -491,9 +491,9 @@ /obj/item/skillchip/master_angler name = "Mast-Angl-Er skillchip" - auto_traits = list(TRAIT_REVEAL_FISH) + auto_traits = list(TRAIT_REVEAL_FISH, TRAIT_EXAMINE_FISHING_SPOT) skill_name = "Fisherman's Discernment" - skill_description = "While fishing, it'll make a smidge easier to guess whatever you're trying to catch." + skill_description = "Lists fishes when examining a fishing spot, and gives a hint of whatever thing's biting the hook." skill_icon = "fish" activate_message = span_notice("You feel the knowledge and passion of several sunbaked, seasoned fishermen burn within you.") deactivate_message = span_notice("You no longer feel like casting a fishing rod by the sunny riverside.") diff --git a/code/modules/lighting/lighting_area.dm b/code/modules/lighting/lighting_area.dm index 84170b6964fce..ea31f61c8becd 100644 --- a/code/modules/lighting/lighting_area.dm +++ b/code/modules/lighting/lighting_area.dm @@ -51,7 +51,7 @@ UnregisterSignal(SSdcs, COMSIG_STARLIGHT_COLOR_CHANGED) var/list/z_offsets = SSmapping.z_level_to_plane_offset if(length(lighting_effects) > 1) - for(var/area_zlevel as anything in 1 to get_highest_zlevel()) + for(var/area_zlevel in 1 to get_highest_zlevel()) if(z_offsets[area_zlevel]) for(var/turf/T as anything in get_turfs_by_zlevel(area_zlevel)) T.cut_overlay(lighting_effects[z_offsets[T.z] + 1]) diff --git a/code/modules/logging/categories/log_category_uplink.dm b/code/modules/logging/categories/log_category_uplink.dm index f88d224ad3b34..4ef0f1af0c01a 100644 --- a/code/modules/logging/categories/log_category_uplink.dm +++ b/code/modules/logging/categories/log_category_uplink.dm @@ -21,3 +21,8 @@ category = LOG_CATEGORY_UPLINK_SPELL config_flag = /datum/config_entry/flag/log_uplink master_category = /datum/log_category/uplink + +/datum/log_category/uplink_spy + category = LOG_CATEGORY_UPLINK_SPY + config_flag = /datum/config_entry/flag/log_uplink + master_category = /datum/log_category/uplink diff --git a/code/modules/logging/log_holder.dm b/code/modules/logging/log_holder.dm index b378e4b8ef379..1102df45f4f2a 100644 --- a/code/modules/logging/log_holder.dm +++ b/code/modules/logging/log_holder.dm @@ -340,7 +340,7 @@ GENERAL_PROTECT_DATUM(/datum/log_holder) var/datum/data = data_list[key] if(isnull(data)) - // do nothing - nulls are allowed + pass() // nulls are allowed else if(islist(data)) data = recursive_jsonify(data, semvers) diff --git a/code/modules/mapfluff/ruins/lavalandruin_code/elephantgraveyard.dm b/code/modules/mapfluff/ruins/lavalandruin_code/elephantgraveyard.dm index a4bcad876712f..97a543fa7e727 100644 --- a/code/modules/mapfluff/ruins/lavalandruin_code/elephantgraveyard.dm +++ b/code/modules/mapfluff/ruins/lavalandruin_code/elephantgraveyard.dm @@ -203,7 +203,7 @@ new /obj/item/reagent_containers/cup/beaker(src) new /obj/item/clothing/glasses/science(src) if(7) - new /obj/item/clothing/glasses/sunglasses(src) + new /obj/item/clothing/glasses/sunglasses/big(src) new /obj/item/clothing/mask/cigarette/rollie(src) else //empty grave diff --git a/code/modules/mapfluff/ruins/objects_and_mobs/museum.dm b/code/modules/mapfluff/ruins/objects_and_mobs/museum.dm index 20210d56cc95c..8a2949f98935e 100644 --- a/code/modules/mapfluff/ruins/objects_and_mobs/museum.dm +++ b/code/modules/mapfluff/ruins/objects_and_mobs/museum.dm @@ -33,6 +33,10 @@ new_replica.name = "[appearance_object.name][obvious_replica ? " replica" : ""]" new_replica.desc = "[appearance_object.desc][obvious_replica ? " ..except this one is a replica.": ""]" + + new_replica.pixel_y = pixel_y + new_replica.pixel_x = pixel_x + qdel(appearance_object) qdel(src) return INITIALIZE_HINT_QDEL @@ -129,3 +133,68 @@
sometimes i can catch them moving

we should have never come here"} + +/obj/item/paper/fluff/museum/chefs_ultimatum + name = "old note" + default_raw_text = {"I messed it up big times. +
I broke the button and now I'm stuck. +
Anyway, I don't have the key on me. I flushed it down. +
Hell knows where it's now, shit's like all linked together here."} + +/obj/item/paper/fluff/museum/numbers_on_walls + name = "reprimanding note" + default_raw_text = "Please refraim from writing the pass all over the place. I know you've the memory of a goldfish, but, like, just put it on a piece of paper, no?" + +/obj/effect/mob_spawn/corpse/human/skeleton/museum_chef + name = "Dead Museum Cafeteria Chef" + mob_name = "Nameless Chef" + outfit = /datum/outfit/museum_chef + +/datum/outfit/museum_chef + name = "Dead Museum Cafeteria Chef" + uniform = /obj/item/clothing/under/color/green + suit = /obj/item/clothing/suit/toggle/chef + head = /obj/item/clothing/head/utility/chefhat + shoes = /obj/item/clothing/shoes/laceup + mask = /obj/item/clothing/mask/fakemoustache/italian + +/obj/machinery/vending/hotdog/museum + obj_flags = parent_type::obj_flags|NO_DECONSTRUCTION + onstation_override = TRUE + +#define CAFE_KEYCARD_TOILETS "museum_cafe_key_toilets" + +///Do not place these beyond the cafeteria shutters, or you might lock people out of reaching it. +/obj/structure/toilet/museum + +/obj/structure/toilet/museum/Initialize(mapload) + . = ..() + if(mapload) + SSqueuelinks.add_to_queue(src, CAFE_KEYCARD_TOILETS) + +/obj/item/keycard/cafeteria + name = "museum cafeteria keycard" + color = COLOR_OLIVE + puzzle_id = "museum_cafeteria" + desc = "The key to the cafeteria, as the name implies." + +/obj/item/keycard/cafeteria/Initialize(mapload) + . = ..() + if(mapload) + SSqueuelinks.add_to_queue(src, CAFE_KEYCARD_TOILETS) + return INITIALIZE_HINT_LATELOAD + +/obj/item/keycard/cafeteria/LateInitialize() + . = ..() + if(SSqueuelinks.queues[CAFE_KEYCARD_TOILETS]) + SSqueuelinks.pop_link(CAFE_KEYCARD_TOILETS) + +/obj/item/keycard/cafeteria/MatchedLinks(id, partners) + if(id != CAFE_KEYCARD_TOILETS) + return ..() + var/obj/structure/toilet/destination = pick(partners) + forceMove(destination) + destination.w_items += w_class + destination.contents += src + +#undef CAFE_KEYCARD_TOILETS diff --git a/code/modules/mapfluff/ruins/spaceruin_code/meatderelict.dm b/code/modules/mapfluff/ruins/spaceruin_code/meatderelict.dm index 5647b5aca2382..0db718e399bf6 100644 --- a/code/modules/mapfluff/ruins/spaceruin_code/meatderelict.dm +++ b/code/modules/mapfluff/ruins/spaceruin_code/meatderelict.dm @@ -76,12 +76,12 @@ SEND_SIGNAL(src, COMSIG_PUZZLE_COMPLETED) qdel(src) -/obj/machinery/puzzle_button/meatderelict +/obj/machinery/puzzle/button/meatderelict name = "lockdown panel" desc = "A panel that controls the lockdown of this outpost." id = "md_prevault" -/obj/machinery/puzzle_button/meatderelict/open_doors() +/obj/machinery/puzzle/button/meatderelict/on_puzzle_complete() . = ..() playsound(src, 'sound/effects/alert.ogg', 100, TRUE) visible_message(span_warning("[src] lets out an alarm as the lockdown is lifted!")) @@ -121,7 +121,7 @@ /obj/lightning_thrower/Initialize(mapload) . = ..() - START_PROCESSING(SSprocessing, src) + START_PROCESSING(SSprocessing, src) /obj/lightning_thrower/Destroy() . = ..() diff --git a/code/modules/mining/boulder_processing/_boulder_processing.dm b/code/modules/mining/boulder_processing/_boulder_processing.dm index d7d4be2557e10..b79bfe36dc6d3 100644 --- a/code/modules/mining/boulder_processing/_boulder_processing.dm +++ b/code/modules/mining/boulder_processing/_boulder_processing.dm @@ -66,9 +66,9 @@ else if(istype(held_item, /obj/item/card/id) && points_held > 0) context[SCREENTIP_CONTEXT_LMB] = "Claim mining points" else if(held_item.tool_behaviour == TOOL_SCREWDRIVER) - context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] Panel" + context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] panel" else if(held_item.tool_behaviour == TOOL_WRENCH) - context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "" : "Un"] Anchor" + context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Un" : ""]Anchor" else if(panel_open && held_item.tool_behaviour == TOOL_CROWBAR) context[SCREENTIP_CONTEXT_LMB] = "Deconstruct" @@ -96,37 +96,17 @@ /obj/machinery/bouldertech/update_icon_state() . = ..() var/suffix = "" - if(!anchored || !is_operational || (machine_stat & (BROKEN | NOPOWER)) || panel_open) + if(!anchored || panel_open || !is_operational || (machine_stat & (BROKEN | NOPOWER))) suffix = "-off" icon_state ="[initial(icon_state)][suffix]" -/obj/machinery/bouldertech/wrench_act(mob/living/user, obj/item/tool) - . = ITEM_INTERACT_BLOCKING - if(default_unfasten_wrench(user, tool, time = 1.5 SECONDS) == SUCCESSFUL_UNFASTEN) - if(anchored) - begin_processing() - else - end_processing() - update_appearance(UPDATE_ICON_STATE) - return ITEM_INTERACT_SUCCESS - -/obj/machinery/bouldertech/screwdriver_act(mob/living/user, obj/item/tool) - . = ITEM_INTERACT_BLOCKING - if(default_deconstruction_screwdriver(user, "[initial(icon_state)]-off", initial(icon_state), tool)) - update_appearance(UPDATE_ICON_STATE) - return ITEM_INTERACT_SUCCESS - -/obj/machinery/bouldertech/crowbar_act(mob/living/user, obj/item/tool) - . = ITEM_INTERACT_BLOCKING - if(default_deconstruction_crowbar(tool)) - return ITEM_INTERACT_SUCCESS - /obj/machinery/bouldertech/CanAllowThrough(atom/movable/mover, border_dir) if(!anchored) return FALSE if(istype(mover, /obj/item/boulder)) - var/obj/item/boulder/boulder = mover - return can_process_boulder(boulder) + return can_process_boulder(mover) + if(isgolem(mover)) + return can_process_golem(mover) return ..() /** @@ -140,7 +120,7 @@ SHOULD_BE_PURE(TRUE) //machine not operational - if(!anchored || panel_open || !is_operational || machine_stat & (BROKEN | NOPOWER)) + if(!anchored || panel_open || !is_operational || (machine_stat & (BROKEN | NOPOWER))) return FALSE //not a valid boulder @@ -168,6 +148,8 @@ * * obj/item/boulder/new_boulder - the boulder to accept */ /obj/machinery/bouldertech/proc/accept_boulder(obj/item/boulder/new_boulder) + PRIVATE_PROC(TRUE) + if(!can_process_boulder(new_boulder)) return FALSE @@ -177,13 +159,67 @@ return TRUE +/** + * Can we maim this golem + * Arguments + * + * * [rockman][mob/living/carbon/human] - the golem we are trying to main + */ +/obj/machinery/bouldertech/proc/can_process_golem(mob/living/carbon/human/rockman) + PRIVATE_PROC(TRUE) + SHOULD_BE_PURE(TRUE) + + //not operatinal + if(!anchored || panel_open || !is_operational || (machine_stat & (BROKEN | NOPOWER))) + return FALSE + + //still in cooldown + if(!COOLDOWN_FINISHED(src, accept_cooldown)) + return FALSE + + //not processable + if(!istype(rockman) || QDELETED(rockman) || rockman.body_position != LYING_DOWN) + return FALSE + + return TRUE + +/** + * Accepts a golem to be processed, mainly for memes + * Arguments + * + * * [rockman][mob/living/carbon/human] - the golem we are trying to main + */ +/obj/machinery/bouldertech/proc/accept_golem(mob/living/carbon/human/rockman) + PRIVATE_PROC(TRUE) + + if(!can_process_golem(rockman)) + return + + maim_golem(rockman) + use_power(active_power_usage * 1.5) + playsound(src, usage_sound, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) + + COOLDOWN_START(src, accept_cooldown, 3 SECONDS) + +/// What effects actually happens to a golem when it is "processed" +/obj/machinery/bouldertech/proc/maim_golem(mob/living/carbon/human/rockman) + PROTECTED_PROC(TRUE) + + Shake(duration = 1 SECONDS) + rockman.visible_message(span_warning("[rockman] is processed by [src]!"), span_userdanger("You get processed into bits by [src]!")) + rockman.investigate_log("was gibbed by [src] for being a golem", INVESTIGATE_DEATHS) + rockman.gib(DROP_ALL_REMAINS) + /obj/machinery/bouldertech/proc/on_entered(datum/source, atom/movable/atom_movable) SIGNAL_HANDLER - if(!can_process_boulder(atom_movable)) + if(istype(atom_movable, /obj/item/boulder)) + INVOKE_ASYNC(src, PROC_REF(accept_boulder), atom_movable) return - INVOKE_ASYNC(src, PROC_REF(accept_boulder), atom_movable) + if(isgolem(atom_movable)) + INVOKE_ASYNC(src, PROC_REF(accept_golem), atom_movable) + return /** * Looks for a boost to the machine's efficiency, and applies it if found. @@ -205,42 +241,61 @@ return FALSE -/obj/machinery/bouldertech/attackby(obj/item/attacking_item, mob/user, params) - if(panel_open) +/obj/machinery/bouldertech/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking) + if(panel_open || user.combat_mode) return ..() - if(istype(attacking_item, /obj/item/boulder)) - . = TRUE - var/obj/item/boulder/my_boulder = attacking_item + if(istype(tool, /obj/item/boulder)) + var/obj/item/boulder/my_boulder = tool if(!accept_boulder(my_boulder)) balloon_alert_to_viewers("cannot accept!") - return + return ITEM_INTERACT_BLOCKING balloon_alert_to_viewers("accepted") - return + return ITEM_INTERACT_SUCCESS - if(istype(attacking_item, /obj/item/card/id)) - . = TRUE + if(istype(tool, /obj/item/card/id)) if(points_held <= 0) balloon_alert_to_viewers("no points to claim!") if(!COOLDOWN_FINISHED(src, sound_cooldown)) - return + return ITEM_INTERACT_BLOCKING COOLDOWN_START(src, sound_cooldown, 1.5 SECONDS) playsound(src, 'sound/machines/buzz-sigh.ogg', 30, FALSE) - return + return ITEM_INTERACT_BLOCKING - var/obj/item/card/id/id_card = attacking_item + var/obj/item/card/id/id_card = tool var/amount = tgui_input_number(user, "How many mining points do you wish to claim? ID Balance: [id_card.registered_account.mining_points], stored mining points: [points_held]", "Transfer Points", max_value = points_held, min_value = 0, round_value = 1) if(!amount) - return + return ITEM_INTERACT_BLOCKING if(amount > points_held) amount = points_held id_card.registered_account.mining_points += amount points_held = round(points_held - amount) to_chat(user, span_notice("You claim [amount] mining points from \the [src] to [id_card].")) - return + return ITEM_INTERACT_SUCCESS return ..() +/obj/machinery/bouldertech/wrench_act(mob/living/user, obj/item/tool) + . = ITEM_INTERACT_BLOCKING + if(default_unfasten_wrench(user, tool, time = 1.5 SECONDS) == SUCCESSFUL_UNFASTEN) + if(anchored) + begin_processing() + else + end_processing() + update_appearance(UPDATE_ICON_STATE) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/bouldertech/screwdriver_act(mob/living/user, obj/item/tool) + . = ITEM_INTERACT_BLOCKING + if(default_deconstruction_screwdriver(user, "[initial(icon_state)]-off", initial(icon_state), tool)) + update_appearance(UPDATE_ICON_STATE) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/bouldertech/crowbar_act(mob/living/user, obj/item/tool) + . = ITEM_INTERACT_BLOCKING + if(default_deconstruction_crowbar(tool)) + return ITEM_INTERACT_SUCCESS + /obj/machinery/bouldertech/attack_hand_secondary(mob/user, list/modifiers) . = ..() if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN || panel_open) @@ -266,15 +321,17 @@ * Accepts a boulder into the machinery, then converts it into minerals. * If the boulder can be fully processed by this machine, we take the materials, insert it into the silo, and destroy the boulder. * If the boulder has materials left, we make a copy of the boulder to hold the processable materials, take the processable parts, and eject the original boulder. - * @param chosen_boulder The boulder to being breaking down into minerals. + * Arguments + * + * * obj/item/boulder/chosen_boulder - The boulder to being breaking down into minerals. */ /obj/machinery/bouldertech/proc/breakdown_boulder(obj/item/boulder/chosen_boulder) PRIVATE_PROC(TRUE) if(QDELETED(chosen_boulder)) - return FALSE + return if(chosen_boulder.loc != src) - return FALSE + return //if boulders are kept inside because there is no space to eject them, then they could be reprocessed, lets avoid that if(!chosen_boulder.processed_by) @@ -301,7 +358,7 @@ if(istype(chosen_boulder, /obj/item/boulder/artifact)) points_held = round((points_held + MINER_POINT_MULTIPLIER) * MINING_POINT_MACHINE_MULTIPLIER) /// Artifacts give bonus points! chosen_boulder.break_apart() - return TRUE //We've processed all the materials in the boulder, so we can just destroy it in break_apart. + return//We've processed all the materials in the boulder, so we can just destroy it in break_apart. chosen_boulder.processed_by = src @@ -309,7 +366,7 @@ remove_boulder(chosen_boulder) /obj/machinery/bouldertech/process() - if(!anchored || panel_open || !is_operational || machine_stat & (BROKEN | NOPOWER)) + if(!anchored || panel_open || !is_operational || (machine_stat & (BROKEN | NOPOWER))) return var/boulders_found = FALSE diff --git a/code/modules/mining/boulder_processing/brm.dm b/code/modules/mining/boulder_processing/brm.dm index e4d525bff056c..592ade5b75796 100644 --- a/code/modules/mining/boulder_processing/brm.dm +++ b/code/modules/mining/boulder_processing/brm.dm @@ -37,26 +37,23 @@ register_context() /obj/machinery/brm/add_context(atom/source, list/context, obj/item/held_item, mob/user) - . = CONTEXTUAL_SCREENTIP_SET + . = NONE if(isnull(held_item)) context[SCREENTIP_CONTEXT_LMB] = "Teleport single boulder" context[SCREENTIP_CONTEXT_RMB] = "Toggle [toggled_on ? "Off" : "On"] automatic boulder retrieval" - return + return CONTEXTUAL_SCREENTIP_SET if(!isnull(held_item)) if(held_item.tool_behaviour == TOOL_WRENCH) - context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "" : "Un"] Anchor" - return - if(held_item.tool_behaviour == TOOL_SCREWDRIVER) - context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] Panel" - return - - if(panel_open) - if(held_item.tool_behaviour == TOOL_CROWBAR) - context[SCREENTIP_CONTEXT_LMB] = "Deconstruct" - - return CONTEXTUAL_SCREENTIP_SET + context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Un" : ""]Anchor" + return CONTEXTUAL_SCREENTIP_SET + else if(held_item.tool_behaviour == TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] panel" + return CONTEXTUAL_SCREENTIP_SET + else if(panel_open && held_item.tool_behaviour == TOOL_CROWBAR) + context[SCREENTIP_CONTEXT_LMB] = "Deconstruct" + return CONTEXTUAL_SCREENTIP_SET /obj/machinery/brm/examine(mob/user) . = ..() @@ -131,8 +128,6 @@ var/result = pre_collect_boulder() if(result == TURF_BLOCKED_BY_BOULDER) balloon_alert(user, "no space") - else if(result) - balloon_alert(user, "teleporting") COOLDOWN_START(src, manual_teleport_cooldown, TELEPORTATION_TIME) return TRUE diff --git a/code/modules/mining/boulder_processing/refinery.dm b/code/modules/mining/boulder_processing/refinery.dm index 662bb3e75e1e5..a751566efa107 100644 --- a/code/modules/mining/boulder_processing/refinery.dm +++ b/code/modules/mining/boulder_processing/refinery.dm @@ -85,3 +85,7 @@ /obj/machinery/bouldertech/refinery/smelter/on_set_is_operational(old_value) set_light_on(TRUE) +/obj/machinery/bouldertech/refinery/smelter/maim_golem(mob/living/carbon/human/rockman) + rockman.visible_message(span_warning("[rockman] is processed by [src]!"), span_userdanger("You get melted into rock by [src]!")) + rockman.investigate_log("was melted by [src] for being a golem", INVESTIGATE_DEATHS) + rockman.dust() diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm index e1df98239e2cd..6a6df5452bd54 100644 --- a/code/modules/mining/equipment/kinetic_crusher.dm +++ b/code/modules/mining/equipment/kinetic_crusher.dm @@ -23,6 +23,8 @@ obj_flags = UNIQUE_RENAME light_system = OVERLAY_LIGHT light_range = 5 + light_power = 1.2 + light_color = "#ffff66" light_on = FALSE var/list/trophies = list() var/charged = TRUE @@ -470,3 +472,17 @@ continue return possible_turf return get_turf(user) + +//wolf trophy + +/obj/item/crusher_trophy/wolf_ear + name = "wolf ear" + desc = "It's a wolf ear." + icon_state = "wolf_ear" + denied_type = /obj/item/crusher_trophy/wolf_ear + +/obj/item/crusher_trophy/wolf_ear/effect_desc() + return "mark detonation to gain a slight speed boost temporarily" + +/obj/item/crusher_trophy/wolf_ear/on_mark_detonation(mob/living/target, mob/living/user) + user.apply_status_effect(/datum/status_effect/speed_boost, 1 SECONDS) diff --git a/code/modules/mining/equipment/lazarus_injector.dm b/code/modules/mining/equipment/lazarus_injector.dm index a9b7205406e4b..ff90c418c8861 100644 --- a/code/modules/mining/equipment/lazarus_injector.dm +++ b/code/modules/mining/equipment/lazarus_injector.dm @@ -54,7 +54,7 @@ playsound(src,'sound/effects/refill.ogg',50,TRUE) icon_state = "lazarus_empty" -/obj/item/lazarus_injector/emp_act() +/obj/item/lazarus_injector/emp_act(severity) . = ..() if(. & EMP_PROTECT_SELF) return diff --git a/code/modules/mining/lavaland/megafauna_loot.dm b/code/modules/mining/lavaland/megafauna_loot.dm index 45c62ebd0a0d8..25240b0330fb3 100644 --- a/code/modules/mining/lavaland/megafauna_loot.dm +++ b/code/modules/mining/lavaland/megafauna_loot.dm @@ -422,13 +422,16 @@ using = TRUE balloon_alert(user, "you hold the scythe up...") ADD_TRAIT(src, TRAIT_NODROP, type) - - var/datum/callback/to_call = CALLBACK(src, PROC_REF(on_poll_concluded), user) - AddComponent(/datum/component/orbit_poll, \ - ignore_key = POLL_IGNORE_POSSESSED_BLADE, \ - job_bans = ROLE_PAI, \ - to_call = to_call, \ + var/mob/chosen_one = SSpolling.poll_ghosts_for_target( + check_jobban = ROLE_PAI, + poll_time = 20 SECONDS, + checked_target = src, + ignore_category = POLL_IGNORE_POSSESSED_BLADE, + alert_pic = src, + role_name_text = "soulscythe soul", + chat_text_border_icon = src, ) + on_poll_concluded(user, chosen_one) /// Ghost poll has concluded and a candidate has been chosen. /obj/item/soulscythe/proc/on_poll_concluded(mob/living/master, mob/dead/observer/ghost) diff --git a/code/modules/mining/lavaland/tendril_loot.dm b/code/modules/mining/lavaland/tendril_loot.dm index 00e97dac83ed4..aff57da45ff5b 100644 --- a/code/modules/mining/lavaland/tendril_loot.dm +++ b/code/modules/mining/lavaland/tendril_loot.dm @@ -272,7 +272,9 @@ icon = 'icons/obj/lighting.dmi' icon_state = "orb" light_system = OVERLAY_LIGHT - light_range = 7 + light_range = 6 + light_power = 1.2 + light_color = "#79f1ff" light_flags = LIGHT_ATTACHED layer = ABOVE_ALL_MOB_LAYER plane = ABOVE_GAME_PLANE diff --git a/code/modules/mining/machine_processing.dm b/code/modules/mining/machine_processing.dm index 62458347bf013..11a941c409861 100644 --- a/code/modules/mining/machine_processing.dm +++ b/code/modules/mining/machine_processing.dm @@ -147,21 +147,10 @@ /obj/machinery/mineral/processing_unit/Initialize(mapload) . = ..() proximity_monitor = new(src, 1) - var/list/allowed_materials = list( - /datum/material/iron, - /datum/material/glass, - /datum/material/silver, - /datum/material/gold, - /datum/material/diamond, - /datum/material/plasma, - /datum/material/uranium, - /datum/material/bananium, - /datum/material/titanium, - /datum/material/bluespace, - ) + materials = AddComponent( \ /datum/component/material_container, \ - allowed_materials, \ + SSmaterials.materials_by_category[MAT_CATEGORY_SILO], \ INFINITY, \ MATCONTAINER_EXAMINE, \ allowed_items = /obj/item/stack \ diff --git a/code/modules/mining/machine_silo.dm b/code/modules/mining/machine_silo.dm index 9320bc012264c..97c3a90b78eb1 100644 --- a/code/modules/mining/machine_silo.dm +++ b/code/modules/mining/machine_silo.dm @@ -18,22 +18,9 @@ /obj/machinery/ore_silo/Initialize(mapload) . = ..() - var/static/list/materials_list = list( - /datum/material/iron, - /datum/material/glass, - /datum/material/silver, - /datum/material/gold, - /datum/material/diamond, - /datum/material/plasma, - /datum/material/uranium, - /datum/material/bananium, - /datum/material/titanium, - /datum/material/bluespace, - /datum/material/plastic, - ) materials = AddComponent( \ /datum/component/material_container, \ - materials_list, \ + SSmaterials.materials_by_category[MAT_CATEGORY_SILO], \ INFINITY, \ MATCONTAINER_EXAMINE, \ container_signals = list( \ diff --git a/code/modules/mining/ores_coins.dm b/code/modules/mining/ores_coins.dm index 0b499f590d668..4fe26281b10a7 100644 --- a/code/modules/mining/ores_coins.dm +++ b/code/modules/mining/ores_coins.dm @@ -614,7 +614,7 @@ GLOBAL_LIST_INIT(sand_recipes, list(\ /obj/item/coin/eldritch name = "eldritch coin" - desc = "Everytime it lands it bolts or opens doors, except for you." + desc = "A surprisingly heavy, ornate coin. Its sides seem to depict a different image each time you look." icon_state = "coin_heretic" custom_materials = list(/datum/material/diamond =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/plasma =HALF_SHEET_MATERIAL_AMOUNT) sideslist = list("heretic", "blade") diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm index 47ca4501f0c4e..dea05f07b82ce 100644 --- a/code/modules/mob/dead/new_player/new_player.dm +++ b/code/modules/mob/dead/new_player/new_player.dm @@ -246,7 +246,7 @@ var/area/station/arrivals = GLOB.areas_by_type[/area/station/hallway/secondary/entry] if(humanc && arrivals && !arrivals.power_environ) //arrivals depowered humanc.put_in_hands(new /obj/item/crowbar/large/emergency(get_turf(humanc))) //if hands full then just drops on the floor - log_manifest(character.mind.key,character.mind,character,latejoin = TRUE) + log_manifest(character.mind.key, character.mind, character, latejoin = TRUE) /mob/dead/new_player/proc/AddEmploymentContract(mob/living/carbon/human/employee) //TODO: figure out a way to exclude wizards/nukeops/demons from this. diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 9995821c58cbb..923507bf189c1 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -17,8 +17,8 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER) hud_type = /datum/hud/ghost movement_type = GROUND | FLYING light_system = OVERLAY_LIGHT - light_range = 1 - light_power = 2 + light_range = 2.5 + light_power = 0.6 light_on = FALSE shift_to_open_context_menu = FALSE var/can_reenter_corpse diff --git a/code/modules/mob/dead/observer/observer_movement.dm b/code/modules/mob/dead/observer/observer_movement.dm index 9e156913a98a7..6972d3b265ff6 100644 --- a/code/modules/mob/dead/observer/observer_movement.dm +++ b/code/modules/mob/dead/observer/observer_movement.dm @@ -1,11 +1,17 @@ +/mob/dead/observer/down() + set name = "Move Down" + set category = "IC" + + if(zMove(DOWN, z_move_flags = ZMOVE_FEEDBACK)) + to_chat(src, span_notice("You move down.")) + /mob/dead/observer/up() set name = "Move Upwards" set category = "IC" if(zMove(UP, z_move_flags = ZMOVE_FEEDBACK)) - to_chat(src, "You move upwards.") + to_chat(src, span_notice("You move upwards.")) /mob/dead/observer/can_z_move(direction, turf/start, turf/destination, z_move_flags = NONE, mob/living/rider) z_move_flags |= ZMOVE_IGNORE_OBSTACLES //observers do not respect these FLOORS you speak so much of. return ..() - diff --git a/code/modules/mob/dead/observer/observer_say.dm b/code/modules/mob/dead/observer/observer_say.dm index e63839b123ff4..e43086349b34e 100644 --- a/code/modules/mob/dead/observer/observer_say.dm +++ b/code/modules/mob/dead/observer/observer_say.dm @@ -9,7 +9,19 @@ mods[RADIO_EXTENSION] = GLOB.department_radio_keys[mods[RADIO_KEY]] return message -/mob/dead/observer/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null, message_range = 7, datum/saymode/saymode = null) +/mob/dead/observer/say( + message, + bubble_type, + list/spans = list(), + sanitize = TRUE, + datum/language/language, + ignore_spam = FALSE, + forced, + filterproof = FALSE, + message_range = 7, + datum/saymode/saymode, + list/message_mods = list(), +) message = trim(message) //trim now and sanitize after checking for special admin radio keys var/list/filter_result = CAN_BYPASS_FILTER(src) ? null : is_ooc_filtered(message) @@ -27,7 +39,6 @@ if(!message) return - var/list/message_mods = list() message = get_message_mods(message, message_mods) if(client?.holder && (message_mods[RADIO_EXTENSION] == MODE_ADMIN || message_mods[RADIO_EXTENSION] == MODE_DEADMIN || (message_mods[RADIO_EXTENSION] == MODE_PUPPET && mind?.current))) message = trim_left(copytext_char(message, length(message_mods[RADIO_KEY]) + 2)) diff --git a/code/modules/mob/living/basic/bots/_bots.dm b/code/modules/mob/living/basic/bots/_bots.dm index 56fdc4fc93842..d1c306313d799 100644 --- a/code/modules/mob/living/basic/bots/_bots.dm +++ b/code/modules/mob/living/basic/bots/_bots.dm @@ -6,7 +6,6 @@ GLOBAL_LIST_INIT(command_strings, list( "home" = "RETURN HOME", )) - /mob/living/basic/bot icon = 'icons/mob/silicon/aibots.dmi' layer = MOB_LAYER @@ -37,10 +36,9 @@ GLOBAL_LIST_INIT(command_strings, list( faction = list(FACTION_NEUTRAL, FACTION_SILICON, FACTION_TURRET) light_system = OVERLAY_LIGHT light_range = 3 - light_power = 0.9 + light_power = 0.6 speed = 3 - ///Access required to access this Bot's maintenance protocols - var/maints_access_required = list(ACCESS_ROBOTICS) + req_one_access = list(ACCESS_ROBOTICS) ///The Robot arm attached to this robot - has a 50% chance to drop on death. var/robot_arm = /obj/item/bodypart/arm/right/robot ///The inserted (if any) pAI in this bot. @@ -51,8 +49,8 @@ GLOBAL_LIST_INIT(command_strings, list( var/list/initial_access = list() ///Bot-related mode flags on the Bot indicating how they will act. BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_ROUNDSTART_POSSESSION var/bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_ROUNDSTART_POSSESSION - ///Bot-related cover flags on the Bot to deal with what has been done to their cover, including emagging. BOT_MAINTS_PANEL_OPEN | BOT_CONTROL_PANEL_OPEN | BOT_COVER_EMAGGED | BOT_COVER_HACKED - var/bot_access_flags = NONE + ///Bot-related cover flags on the Bot to deal with what has been done to their cover, including emagging. BOT_COVER_MAINTS_OPEN | BOT_COVER_LOCKED | BOT_COVER_EMAGGED | BOT_COVER_HACKED + var/bot_access_flags = BOT_COVER_LOCKED ///Small name of what the bot gets messed with when getting hacked/emagged. var/hackables = "system circuits" ///Standardizes the vars that indicate the bot is busy with its function. @@ -270,26 +268,10 @@ GLOBAL_LIST_INIT(command_strings, list( return fully_replace_character_name(real_name, new_name) -/mob/living/basic/bot/proc/check_access(mob/living/user, obj/item/card/id) - if(user.has_unlimited_silicon_privilege || isAdminGhostAI(user)) // Silicon and Admins always have access. - return TRUE - if(!istype(user)) // Non-living mobs shouldn't be manipulating bots (like observes using the botkeeper UI). - return FALSE - if(!length(maints_access_required)) // No requirements to access it. +/mob/living/basic/bot/allowed(mob/living/user) + if(!(bot_access_flags & BOT_COVER_LOCKED)) // Unlocked. return TRUE - if(bot_access_flags & BOT_CONTROL_PANEL_OPEN) // Unlocked. - return TRUE - - var/obj/item/card/id/used_id = id || user.get_idcard(TRUE) - - if(!used_id || !used_id.access) - return FALSE - - for(var/requested_access in maints_access_required) - if(requested_access in used_id.access) - return TRUE - - return FALSE + return ..() /mob/living/basic/bot/bee_friendly() return TRUE @@ -309,15 +291,15 @@ GLOBAL_LIST_INIT(command_strings, list( /mob/living/basic/bot/emag_act(mob/user, obj/item/card/emag/emag_card) . = ..() - if(!(bot_access_flags & BOT_CONTROL_PANEL_OPEN)) //First emag application unlocks the bot's interface. Apply a screwdriver to use the emag again. - bot_access_flags |= BOT_CONTROL_PANEL_OPEN + if(bot_access_flags & BOT_COVER_LOCKED) //First emag application unlocks the bot's interface. Apply a screwdriver to use the emag again. + bot_access_flags &= ~BOT_COVER_LOCKED balloon_alert(user, "cover unlocked") return TRUE - if(!(bot_access_flags & BOT_CONTROL_PANEL_OPEN) || !(bot_access_flags & BOT_MAINTS_PANEL_OPEN)) //Bot panel is unlocked by ID or emag, and the panel is screwed open. Ready for emagging. + if((bot_access_flags & BOT_COVER_LOCKED) || !(bot_access_flags & BOT_COVER_MAINTS_OPEN)) //Bot panel is unlocked by ID or emag, and the panel is screwed open. Ready for emagging. balloon_alert(user, "open maintenance panel first!") return FALSE bot_access_flags |= BOT_COVER_EMAGGED - bot_access_flags &= ~BOT_CONTROL_PANEL_OPEN + bot_access_flags |= BOT_COVER_LOCKED bot_mode_flags &= ~BOT_MODE_REMOTE_ENABLED //Manually emagging the bot also locks the AI from controlling it. bot_reset() turn_on() //The bot automatically turns on when emagged, unless recently hit with EMP. @@ -337,17 +319,17 @@ GLOBAL_LIST_INIT(command_strings, list( else . += "[src] is in pristine condition." - . += span_notice("Its maintenance panel is [bot_access_flags & BOT_MAINTS_PANEL_OPEN ? "open" : "closed"].") - . += span_info("You can use a screwdriver to [bot_access_flags & BOT_MAINTS_PANEL_OPEN ? "close" : "open"] it.") + . += span_notice("Its maintenance panel is [bot_access_flags & BOT_COVER_MAINTS_OPEN ? "open" : "closed"].") + . += span_info("You can use a screwdriver to [bot_access_flags & BOT_COVER_MAINTS_OPEN ? "close" : "open"] it.") - if(bot_access_flags & BOT_MAINTS_PANEL_OPEN) - . += span_notice("Its control panel is [bot_access_flags & BOT_CONTROL_PANEL_OPEN ? "unlocked" : "locked"].") + if(bot_access_flags & BOT_COVER_MAINTS_OPEN) + . += span_notice("Its control panel is [bot_access_flags & BOT_COVER_LOCKED ? "locked" : "unlocked"].") if(!(bot_access_flags & BOT_COVER_EMAGGED) && (issilicon(user) || user.Adjacent(src))) - . += span_info("Alt-click [issilicon(user) ? "" : "or use your ID on "]it to [bot_access_flags & BOT_CONTROL_PANEL_OPEN ? "" : "un"]lock its control panel.") + . += span_info("Alt-click [issilicon(user) ? "" : "or use your ID on "]it to [bot_access_flags & BOT_COVER_LOCKED ? "un" : ""]lock its control panel.") if(isnull(paicard)) return . += span_notice("It has a pAI device installed.") - if(!(bot_access_flags & BOT_MAINTS_PANEL_OPEN)) + if(!(bot_access_flags & BOT_COVER_MAINTS_OPEN)) . += span_info("You can use a hemostat to remove it.") /mob/living/basic/bot/updatehealth() @@ -390,25 +372,25 @@ GLOBAL_LIST_INIT(command_strings, list( if(bot_access_flags & BOT_COVER_EMAGGED) balloon_alert(user, "error!") return - if(bot_access_flags & BOT_MAINTS_PANEL_OPEN) + if(bot_access_flags & BOT_COVER_MAINTS_OPEN) balloon_alert(user, "access panel must be closed!") return - if(!check_access(user)) + if(!allowed(user)) balloon_alert(user, "no access") return - bot_access_flags ^= BOT_CONTROL_PANEL_OPEN - to_chat(user, span_notice("Controls are now [bot_access_flags & BOT_CONTROL_PANEL_OPEN ? "unlocked" : "locked"].")) + bot_access_flags ^= BOT_COVER_LOCKED + to_chat(user, span_notice("Controls are now [bot_access_flags & BOT_COVER_LOCKED ? "locked" : "unlocked"].")) return TRUE /mob/living/basic/bot/screwdriver_act(mob/living/user, obj/item/tool) . = ITEM_INTERACT_SUCCESS - if(!(bot_access_flags & BOT_CONTROL_PANEL_OPEN)) + if(bot_access_flags & BOT_COVER_LOCKED) to_chat(user, span_warning("The maintenance panel is locked!")) return tool.play_tool_sound(src) - bot_access_flags ^= BOT_MAINTS_PANEL_OPEN - to_chat(user, span_notice("The maintenance panel is now [bot_access_flags & BOT_MAINTS_PANEL_OPEN ? "opened" : "closed"].")) + bot_access_flags ^= BOT_COVER_MAINTS_OPEN + to_chat(user, span_notice("The maintenance panel is now [bot_access_flags & BOT_COVER_MAINTS_OPEN ? "opened" : "closed"].")) /mob/living/basic/bot/welder_act(mob/living/user, obj/item/tool) user.changeNext_move(CLICK_CD_MELEE) @@ -421,7 +403,7 @@ GLOBAL_LIST_INIT(command_strings, list( user.balloon_alert(user, "no repairs needed!") return - if(!(bot_access_flags & BOT_MAINTS_PANEL_OPEN)) + if(!(bot_access_flags & BOT_COVER_MAINTS_OPEN)) user.balloon_alert(user, "maintenance panel closed!") return @@ -443,7 +425,7 @@ GLOBAL_LIST_INIT(command_strings, list( if(attacking_item.tool_behaviour != TOOL_HEMOSTAT || !paicard) return ..() - if(bot_access_flags & BOT_MAINTS_PANEL_OPEN) + if(bot_access_flags & BOT_COVER_MAINTS_OPEN) balloon_alert(user, "open the access panel!") return @@ -584,18 +566,18 @@ GLOBAL_LIST_INIT(command_strings, list( /mob/living/basic/bot/ui_data(mob/user) var/list/data = list() - data["can_hack"] = (issilicon(user) || isAdminGhostAI(user)) + data["can_hack"] = HAS_SILICON_ACCESS(user) data["custom_controls"] = list() data["emagged"] = bot_access_flags & BOT_COVER_EMAGGED - data["has_access"] = check_access(user) - data["locked"] = !(bot_access_flags & BOT_CONTROL_PANEL_OPEN) + data["has_access"] = allowed(user) + data["locked"] = (bot_access_flags & BOT_COVER_LOCKED) data["settings"] = list() - if((bot_access_flags & BOT_CONTROL_PANEL_OPEN) || issilicon(user) || isAdminGhostAI(user)) + if(!(bot_access_flags & BOT_COVER_LOCKED) || HAS_SILICON_ACCESS(user)) data["settings"]["pai_inserted"] = !isnull(paicard) data["settings"]["allow_possession"] = bot_mode_flags & BOT_MODE_CAN_BE_SAPIENT data["settings"]["possession_enabled"] = can_be_possessed data["settings"]["airplane_mode"] = !(bot_mode_flags & BOT_MODE_REMOTE_ENABLED) - data["settings"]["maintenance_lock"] = !(bot_access_flags & BOT_MAINTS_PANEL_OPEN) + data["settings"]["maintenance_lock"] = !(bot_access_flags & BOT_COVER_MAINTS_OPEN) data["settings"]["power"] = bot_mode_flags & BOT_MODE_ON data["settings"]["patrol_station"] = bot_mode_flags & BOT_MODE_AUTOPATROL return data @@ -606,12 +588,12 @@ GLOBAL_LIST_INIT(command_strings, list( if(.) return var/mob/the_user = ui.user - if(!check_access(the_user)) + if(!allowed(the_user)) balloon_alert(the_user, "access denied!") return if(action == "lock") - bot_access_flags ^= BOT_CONTROL_PANEL_OPEN + bot_access_flags ^= BOT_COVER_LOCKED switch(action) if("power") @@ -620,18 +602,17 @@ GLOBAL_LIST_INIT(command_strings, list( else turn_on() if("maintenance") - bot_access_flags ^= BOT_MAINTS_PANEL_OPEN + bot_access_flags ^= BOT_COVER_MAINTS_OPEN if("patrol") bot_mode_flags ^= BOT_MODE_AUTOPATROL bot_reset() if("airplane") bot_mode_flags ^= BOT_MODE_REMOTE_ENABLED if("hack") - if(!(issilicon(the_user) || isAdminGhostAI(the_user))) + if(!HAS_SILICON_ACCESS(the_user)) return if(!(bot_access_flags & BOT_COVER_EMAGGED)) - bot_access_flags |= (BOT_COVER_EMAGGED|BOT_COVER_HACKED) - bot_access_flags &= ~BOT_CONTROL_PANEL_OPEN + bot_access_flags |= (BOT_COVER_LOCKED|BOT_COVER_EMAGGED|BOT_COVER_HACKED) to_chat(the_user, span_warning("You overload [src]'s [hackables].")) message_admins("Safety lock of [ADMIN_LOOKUPFLW(src)] was disabled by [ADMIN_LOOKUPFLW(the_user)] in [ADMIN_VERBOSEJMP(the_user)]") the_user.log_message("disabled safety lock of [the_user]", LOG_GAME) @@ -674,7 +655,7 @@ GLOBAL_LIST_INIT(command_strings, list( return FALSE if(!(bot_access_flags & BOT_COVER_HACKED)) //Manually emagged by a human - access denied to all. return TRUE - if(!issilicon(user) && !isAdminGhostAI(user)) //Bot is hacked, so only silicons and admins are allowed access. + if(!HAS_SILICON_ACCESS(user)) //Bot is hacked, so only silicons and admins are allowed access. return TRUE return FALSE @@ -687,7 +668,7 @@ GLOBAL_LIST_INIT(command_strings, list( if(key) balloon_alert(user, "personality already present!") return - if(!(bot_access_flags & BOT_COVER_OPEN)) + if(!(bot_access_flags & BOT_COVER_MAINTS_OPEN)) balloon_alert(user, "slot inaccessible!") return if(!(bot_mode_flags & BOT_MODE_CAN_BE_SAPIENT)) @@ -744,7 +725,7 @@ GLOBAL_LIST_INIT(command_strings, list( /// Ejects the pAI remotely. /mob/living/basic/bot/proc/eject_pai_remote(mob/user) - if(!check_access(user) || !paicard) + if(!allowed(user) || !paicard) return speak("Ejecting personality chip.", radio_channel) ejectpai(user) diff --git a/code/modules/mob/living/basic/bots/bot_ai.dm b/code/modules/mob/living/basic/bots/bot_ai.dm index 19b7bcbdb5e4b..21abafb860e81 100644 --- a/code/modules/mob/living/basic/bots/bot_ai.dm +++ b/code/modules/mob/living/basic/bots/bot_ai.dm @@ -208,14 +208,10 @@ action_cooldown = 30 SECONDS /datum/ai_behavior/find_and_set/valid_authority/search_tactic(datum/ai_controller/controller, locate_path, search_range) - for(var/mob/living/robot in oview(search_range, controller.pawn)) - if(istype(robot, /mob/living/simple_animal/bot/secbot)) - return robot - if(!istype(robot, /mob/living/basic/bot/cleanbot)) + for(var/mob/living/nearby_mob in oview(search_range, controller.pawn)) + if(!HAS_TRAIT(nearby_mob, TRAIT_COMMISSIONED)) continue - var/mob/living/basic/bot/cleanbot/potential_bot = robot - if(potential_bot.comissioned) - return potential_bot + return nearby_mob return null /datum/ai_behavior/salute_authority diff --git a/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm b/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm index a81f48c9c95aa..18d69105a41eb 100644 --- a/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm +++ b/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm @@ -10,8 +10,9 @@ anchored = FALSE health = 25 maxHealth = 25 + light_color = "#99ccff" - maints_access_required = list(ACCESS_ROBOTICS, ACCESS_JANITOR) + req_one_access = list(ACCESS_ROBOTICS, ACCESS_JANITOR) radio_key = /obj/item/encryptionkey/headset_service radio_channel = RADIO_CHANNEL_SERVICE bot_type = CLEAN_BOT @@ -25,8 +26,6 @@ ///Flags indicating what kind of cleanables we should scan for to set as our target to clean. ///Options: CLEANBOT_CLEAN_BLOOD | CLEANBOT_CLEAN_TRASH | CLEANBOT_CLEAN_PESTS | CLEANBOT_CLEAN_DRAWINGS var/janitor_mode_flags = CLEANBOT_CLEAN_BLOOD - ///should other bots salute us? - var/comissioned = FALSE ///the base icon state, used in updating icons. var/base_icon = "cleanbot" /// if we have all the top titles, grant achievements to living mobs that gaze upon our cleanbot god @@ -232,7 +231,7 @@ // Variables sent to TGUI /mob/living/basic/bot/cleanbot/ui_data(mob/user) var/list/data = ..() - if(!(bot_access_flags & BOT_CONTROL_PANEL_OPEN) && !issilicon(user) && !isAdminGhostAI(user)) + if((bot_access_flags & BOT_COVER_LOCKED) && !HAS_SILICON_ACCESS(user)) return data data["custom_controls"]["clean_blood"] = janitor_mode_flags & CLEANBOT_CLEAN_BLOOD data["custom_controls"]["clean_trash"] = janitor_mode_flags & CLEANBOT_CLEAN_TRASH @@ -243,7 +242,7 @@ // Actions received from TGUI /mob/living/basic/bot/cleanbot/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) . = ..() - if(. || !(bot_access_flags & BOT_CONTROL_PANEL_OPEN) && !ui.user.has_unlimited_silicon_privilege) + if(. || (bot_access_flags & BOT_COVER_LOCKED) && !HAS_SILICON_ACCESS(ui.user)) return switch(action) @@ -298,8 +297,8 @@ return stolen_valor += new_job_title - if(!comissioned && (new_job_title in officers_titles)) - comissioned = TRUE + if(!HAS_TRAIT(src, TRAIT_COMMISSIONED) && (new_job_title in officers_titles)) + ADD_TRAIT(src, TRAIT_COMMISSIONED, INNATE_TRAIT) var/name_to_add = job_titles[new_job_title] name = (new_job_title in suffix_job_titles) ? "[name] " + name_to_add : name_to_add + " [name]" @@ -352,5 +351,5 @@ /mob/living/basic/bot/cleanbot/medbay name = "Scrubs, MD" - maints_access_required = list(ACCESS_ROBOTICS, ACCESS_JANITOR, ACCESS_MEDICAL) + req_one_access = list(ACCESS_ROBOTICS, ACCESS_JANITOR, ACCESS_MEDICAL) bot_mode_flags = ~(BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED) diff --git a/code/modules/mob/living/basic/bots/hygienebot/hygienebot.dm b/code/modules/mob/living/basic/bots/hygienebot/hygienebot.dm index 1d802ca9358d9..8def16692bfc2 100644 --- a/code/modules/mob/living/basic/bots/hygienebot/hygienebot.dm +++ b/code/modules/mob/living/basic/bots/hygienebot/hygienebot.dm @@ -13,7 +13,7 @@ health = 100 maxHealth = 100 path_image_color = "#80dae7" - maints_access_required = list(ACCESS_ROBOTICS, ACCESS_JANITOR) + req_one_access = list(ACCESS_ROBOTICS, ACCESS_JANITOR) radio_key = /obj/item/encryptionkey/headset_service radio_channel = RADIO_CHANNEL_SERVICE bot_type = HYGIENE_BOT diff --git a/code/modules/mob/living/basic/bots/medbot/medbot.dm b/code/modules/mob/living/basic/bots/medbot/medbot.dm index 2b3c40043f16a..d85adc3ea2364 100644 --- a/code/modules/mob/living/basic/bots/medbot/medbot.dm +++ b/code/modules/mob/living/basic/bots/medbot/medbot.dm @@ -11,11 +11,13 @@ health = 20 maxHealth = 20 speed = 2 + light_power = 0.8 + light_color = "#99ccff" pass_flags = PASSMOB | PASSFLAPS status_flags = (CANPUSH | CANSTUN) ai_controller = /datum/ai_controller/basic_controller/bot/medbot - maints_access_required = list(ACCESS_ROBOTICS, ACCESS_MEDICAL) + req_one_access = list(ACCESS_ROBOTICS, ACCESS_MEDICAL) radio_key = /obj/item/encryptionkey/headset_med radio_channel = RADIO_CHANNEL_MEDICAL bot_type = MED_BOT @@ -200,7 +202,7 @@ // Variables sent to TGUI /mob/living/basic/bot/medbot/ui_data(mob/user) var/list/data = ..() - if((bot_access_flags & BOT_CONTROL_PANEL_OPEN) || issilicon(user) || isAdminGhostAI(user)) + if(!(bot_access_flags & BOT_COVER_LOCKED) || HAS_SILICON_ACCESS(user)) data["custom_controls"]["heal_threshold"] = heal_threshold data["custom_controls"]["speaker"] = medical_mode_flags & MEDBOT_SPEAK_MODE data["custom_controls"]["crit_alerts"] = medical_mode_flags & MEDBOT_DECLARE_CRIT @@ -211,7 +213,7 @@ // Actions received from TGUI /mob/living/basic/bot/medbot/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) . = ..() - if(. || !isliving(ui.user) || !(bot_access_flags & BOT_CONTROL_PANEL_OPEN) && !(ui.user.has_unlimited_silicon_privilege)) + if(. || !isliving(ui.user) || (bot_access_flags & BOT_COVER_LOCKED) && !HAS_SILICON_ACCESS(ui.user)) return var/mob/living/our_user = ui.user switch(action) @@ -392,7 +394,7 @@ skin = "bezerk" health = 40 maxHealth = 40 - maints_access_required = list(ACCESS_SYNDICATE) + req_one_access = list(ACCESS_SYNDICATE) bot_mode_flags = parent_type::bot_mode_flags & ~BOT_MODE_REMOTE_ENABLED radio_key = /obj/item/encryptionkey/syndicate radio_channel = RADIO_CHANNEL_SYNDICATE diff --git a/code/modules/mob/living/basic/drone/_drone.dm b/code/modules/mob/living/basic/drone/_drone.dm index 9298083c67c21..ae72054899b11 100644 --- a/code/modules/mob/living/basic/drone/_drone.dm +++ b/code/modules/mob/living/basic/drone/_drone.dm @@ -47,7 +47,6 @@ lighting_cutoff_red = 30 lighting_cutoff_green = 35 lighting_cutoff_blue = 25 - can_be_held = TRUE worn_slot_flags = ITEM_SLOT_HEAD /// `TRUE` if we have picked our visual appearance, `FALSE` otherwise (default) @@ -265,6 +264,9 @@ /mob/living/basic/drone/gib() dust() +/mob/living/basic/drone/get_butt_sprite() + return BUTT_SPRITE_DRONE + /mob/living/basic/drone/examine(mob/user) . = list("This is [icon2html(src, user)] \a [src]!") diff --git a/code/modules/mob/living/basic/farm_animals/bee/_bee.dm b/code/modules/mob/living/basic/farm_animals/bee/_bee.dm index 5510b81679e94..89eedd7cb5593 100644 --- a/code/modules/mob/living/basic/farm_animals/bee/_bee.dm +++ b/code/modules/mob/living/basic/farm_animals/bee/_bee.dm @@ -106,9 +106,13 @@ beegent = null if(flags_1 & HOLOGRAM_1 || gibbed) return ..() - new /obj/item/trash/bee(loc, src) + spawn_corpse() return ..() +/// Leave something to remember us by +/mob/living/basic/bee/proc/spawn_corpse() + new /obj/item/trash/bee(loc, src) + /mob/living/basic/bee/proc/pre_attack(mob/living/puncher, atom/target) SIGNAL_HANDLER @@ -213,12 +217,20 @@ var/datum/reagent/toxin = pick(typesof(/datum/reagent/toxin)) assign_reagent(GLOB.chemical_reagents_list[toxin]) -/mob/living/basic/bee/short - desc = "These bees seem unstable and won't survive for long." +/// A bee which despawns after a short amount of time (beespawns?) +/mob/living/basic/bee/timed + /// How long do we live? + var/lifespan = 50 SECONDS -/mob/living/basic/bee/short/Initialize(mapload, timetolive=50 SECONDS) +/mob/living/basic/bee/timed/short + lifespan = 25 SECONDS + +/mob/living/basic/bee/timed/Initialize(mapload) . = ..() - addtimer(CALLBACK(src, PROC_REF(death)), timetolive) + addtimer(CALLBACK(src, PROC_REF(death)), lifespan) + +/mob/living/basic/bee/timed/spawn_corpse() + new /obj/effect/temp_visual/despawn_effect(get_turf(src), /* copy_from = */ src) /obj/item/queen_bee name = "queen bee" diff --git a/code/modules/mob/living/basic/farm_animals/goat/_goat.dm b/code/modules/mob/living/basic/farm_animals/goat/_goat.dm index 34b92f218829c..f2354cc5f149a 100644 --- a/code/modules/mob/living/basic/farm_animals/goat/_goat.dm +++ b/code/modules/mob/living/basic/farm_animals/goat/_goat.dm @@ -100,7 +100,7 @@ /// Handles automagically eating a plant when we move into a turf that has one. /mob/living/basic/goat/proc/on_move(datum/source, atom/entering_loc) SIGNAL_HANDLER - if(!isturf(entering_loc)) + if(!isturf(entering_loc) || stat == DEAD) return var/list/edible_plants = list() diff --git a/code/modules/mob/living/basic/guardian/guardian_creator.dm b/code/modules/mob/living/basic/guardian/guardian_creator.dm index 3f1f092752217..441a60124a7bf 100644 --- a/code/modules/mob/living/basic/guardian/guardian_creator.dm +++ b/code/modules/mob/living/basic/guardian/guardian_creator.dm @@ -87,17 +87,18 @@ GLOBAL_LIST_INIT(guardian_radial_images, setup_guardian_radial()) used = TRUE to_chat(user, use_message) var/guardian_type_name = random ? "Random" : capitalize(initial(guardian_path.creator_name)) - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates( - "Do you want to play as [user.real_name]'s [guardian_type_name] [mob_name]?", + var/mob/chosen_one = SSpolling.poll_ghost_candidates( + "Do you want to play as [span_danger("[user.real_name]'s")] [span_notice("[guardian_type_name] [mob_name]")]?", check_jobban = ROLE_PAI, poll_time = 10 SECONDS, ignore_category = POLL_IGNORE_HOLOPARASITE, - pic_source = src, - role_name_text = "guardian spirit", + alert_pic = guardian_path, + jump_target = src, + role_name_text = guardian_type_name, + amount_to_pick = 1, ) - if(LAZYLEN(candidates)) - var/mob/dead/observer/candidate = pick(candidates) - spawn_guardian(user, candidate, guardian_path) + if(chosen_one) + spawn_guardian(user, chosen_one, guardian_path) used = TRUE SEND_SIGNAL(src, COMSIG_TRAITOR_ITEM_USED(type)) else diff --git a/code/modules/mob/living/basic/guardian/guardian_verbs.dm b/code/modules/mob/living/basic/guardian/guardian_verbs.dm index 2f40da369f82a..80a2af7db7a27 100644 --- a/code/modules/mob/living/basic/guardian/guardian_verbs.dm +++ b/code/modules/mob/living/basic/guardian/guardian_verbs.dm @@ -169,20 +169,18 @@ return FALSE to_chat(owner, span_holoparasite("You attempt to reset [span_bold(chosen_guardian.real_name)]'s personality...")) - var/list/mob/dead/observer/ghost_candidates = SSpolling.poll_ghost_candidates("Do you want to play as [owner.real_name]'s [chosen_guardian.theme.name]?", check_jobban = ROLE_PAI, poll_time = 10 SECONDS, pic_source = chosen_guardian, role_name_text = chosen_guardian.theme.name) - if (!LAZYLEN(ghost_candidates)) + var/mob/chosen_one = SSpolling.poll_ghost_candidates("Do you want to play as [span_danger("[owner.real_name]'s")] [span_notice(chosen_guardian.theme.name)]?", check_jobban = ROLE_PAI, poll_time = 10 SECONDS, alert_pic = chosen_guardian, jump_target = owner, role_name_text = chosen_guardian.theme.name, amount_to_pick = 1) + if(isnull(chosen_one)) to_chat(owner, span_holoparasite("Your attempt to reset the personality of \ [span_bold(chosen_guardian.real_name)] appears to have failed... \ Looks like you're stuck with it for now.")) StartCooldown() return FALSE - - var/mob/dead/observer/candidate = pick(ghost_candidates) to_chat(chosen_guardian, span_holoparasite("Your user reset you, and your body was taken over by a ghost. Looks like they weren't happy with your performance.")) to_chat(owner, span_boldholoparasite("The personality of [chosen_guardian.theme.name] has been successfully reset.")) - message_admins("[key_name_admin(candidate)] has taken control of ([ADMIN_LOOKUPFLW(chosen_guardian)])") + message_admins("[key_name_admin(chosen_one)] has taken control of ([ADMIN_LOOKUPFLW(chosen_guardian)])") chosen_guardian.ghostize(FALSE) - chosen_guardian.key = candidate.key + chosen_guardian.key = chosen_one.key COOLDOWN_START(chosen_guardian, resetting_cooldown, 5 MINUTES) chosen_guardian.guardian_rename() //give it a new color and name, to show it's a new person chosen_guardian.guardian_recolour() diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm index b143f471138f4..af17fc0cb01aa 100644 --- a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm +++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm @@ -34,8 +34,8 @@ /datum/action/cooldown/mob_cooldown/slippery_ice_floors name = "Iced Floors" desc = "Summon slippery ice floors all around!" - button_icon = 'icons/turf/floors/ice_turf.dmi' - button_icon_state = "ice_turf-6" + button_icon = 'icons/effects/freeze.dmi' + button_icon_state = "ice_cube" cooldown_time = 2 SECONDS click_to_activate = FALSE melee_cooldown_time = 0 SECONDS @@ -84,6 +84,7 @@ /datum/action/cooldown/spell/conjure/limit_summons/create_afterimages name = "Create After Images" + desc = "Creates two illusionary doubles to increase your firepower, but which share some of your life force." button_icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi' button_icon_state = "ice_demon" spell_requirements = NONE diff --git a/code/modules/mob/living/basic/lavaland/hivelord/hivelord.dm b/code/modules/mob/living/basic/lavaland/hivelord/hivelord.dm index 7b1e461991ce7..f0de6c3272e55 100644 --- a/code/modules/mob/living/basic/lavaland/hivelord/hivelord.dm +++ b/code/modules/mob/living/basic/lavaland/hivelord/hivelord.dm @@ -110,5 +110,5 @@ /mob/living/basic/hivelord_brood/death(gibbed) if (!gibbed) - new /obj/effect/temp_visual/hive_spawn_wither(get_turf(src), /* copy_from = */ src) + new /obj/effect/temp_visual/despawn_effect(get_turf(src), /* copy_from = */ src) return ..() diff --git a/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm b/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm index 7cc5ea06ad8b6..96c7319380a2e 100644 --- a/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm +++ b/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm @@ -42,7 +42,7 @@ /mob/living/basic/legion_brood/death(gibbed) if (!gibbed) - new /obj/effect/temp_visual/hive_spawn_wither(get_turf(src), /* copy_from = */ src) + new /obj/effect/temp_visual/despawn_effect(get_turf(src), /* copy_from = */ src) return ..() /mob/living/basic/legion_brood/melee_attack(mob/living/target, list/modifiers, ignore_cooldown) diff --git a/code/modules/mob/living/basic/minebots/minebot.dm b/code/modules/mob/living/basic/minebots/minebot.dm index 5422ab4ee2e98..11b4530dd7ac4 100644 --- a/code/modules/mob/living/basic/minebots/minebot.dm +++ b/code/modules/mob/living/basic/minebots/minebot.dm @@ -25,6 +25,9 @@ death_message = "blows apart!" light_system = OVERLAY_LIGHT light_range = 6 + // I want this to be a bit more dim, for vibes + light_power = 0.6 + light_color = "#ff9933" light_on = FALSE combat_mode = FALSE ai_controller = /datum/ai_controller/basic_controller/minebot diff --git a/code/modules/mob/living/basic/pets/orbie/orbie.dm b/code/modules/mob/living/basic/pets/orbie/orbie.dm new file mode 100644 index 0000000000000..2c9fb3d815c49 --- /dev/null +++ b/code/modules/mob/living/basic/pets/orbie/orbie.dm @@ -0,0 +1,112 @@ +#define ORBIE_MAXIMUM_HEALTH 300 + +/mob/living/basic/orbie + name = "Orbie" + desc = "An orb shaped hologram." + icon = 'icons/mob/simple/pets.dmi' + icon_state = "orbie" + icon_living = "orbie" + speed = 0 + maxHealth = 100 + light_on = FALSE + light_system = OVERLAY_LIGHT + light_range = 6 + light_color = "#64bee1" + health = 100 + habitable_atmos = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + unsuitable_atmos_damage = 0 + can_buckle_to = FALSE + density = FALSE + pass_flags = PASSMOB + move_force = 0 + move_resist = 0 + pull_force = 0 + minimum_survivable_temperature = TCMB + maximum_survivable_temperature = INFINITY + death_message = "fades out of existence!" + ai_controller = /datum/ai_controller/basic_controller/orbie + ///are we happy or not? + var/happy_state = FALSE + ///overlay for our neutral eyes + var/static/mutable_appearance/eyes_overlay = mutable_appearance('icons/mob/simple/pets.dmi', "orbie_eye_overlay") + ///overlay for when our eyes are emitting light + var/static/mutable_appearance/orbie_light_overlay = mutable_appearance('icons/mob/simple/pets.dmi', "orbie_light_overlay") + ///overlay for the flame propellar + var/static/mutable_appearance/flame_overlay = mutable_appearance('icons/mob/simple/pets.dmi', "orbie_flame_overlay") + ///overlay for our happy eyes + var/static/mutable_appearance/happy_eyes_overlay = mutable_appearance('icons/mob/simple/pets.dmi', "orbie_happy_eye_overlay") + ///commands we can give orbie + var/list/pet_commands = list( + /datum/pet_command/idle, + /datum/pet_command/free, + /datum/pet_command/untargeted_ability/pet_lights, + /datum/pet_command/point_targeting/use_ability/take_photo, + /datum/pet_command/follow/orbie, + /datum/pet_command/perform_trick_sequence, + ) + +/mob/living/basic/orbie/Initialize(mapload) + . = ..() + var/static/list/food_types = list(/obj/item/food/virtual_chocolate) + AddComponent(/datum/component/obeys_commands, pet_commands) + AddElement(/datum/element/basic_eating, food_types = food_types) + RegisterSignal(src, COMSIG_ATOM_CAN_BE_PULLED, PROC_REF(on_pulled)) + RegisterSignal(src, COMSIG_VIRTUAL_PET_LEVEL_UP, PROC_REF(on_level_up)) + RegisterSignal(src, COMSIG_MOB_CLICKON, PROC_REF(on_click)) + RegisterSignal(src, COMSIG_ATOM_UPDATE_LIGHT_ON, PROC_REF(on_lights)) + ai_controller.set_blackboard_key(BB_BASIC_FOODS, typecacheof(food_types)) + update_appearance() + +/mob/living/basic/orbie/proc/on_click(mob/living/basic/source, atom/target, params) + SIGNAL_HANDLER + + if(!CanReach(target)) + return + + if(src == target || happy_state || !istype(target)) + return + + toggle_happy_state() + addtimer(CALLBACK(src, PROC_REF(toggle_happy_state)), 30 SECONDS) + +/mob/living/basic/orbie/proc/on_lights(datum/source) + SIGNAL_HANDLER + + update_appearance() + +/mob/living/basic/orbie/proc/toggle_happy_state() + happy_state = !happy_state + update_appearance() + +/mob/living/basic/orbie/proc/on_pulled(datum/source) //i need move resist at 0, but i also dont want him to be pulled + SIGNAL_HANDLER + + return COMSIG_ATOM_CANT_PULL + +/mob/living/basic/orbie/proc/on_level_up(datum/source, new_level) + SIGNAL_HANDLER + + if(maxHealth >= ORBIE_MAXIMUM_HEALTH) + UnregisterSignal(src, COMSIG_VIRTUAL_PET_LEVEL_UP) + return + + maxHealth += 100 + heal_overall_damage(maxHealth - health) + + +/mob/living/basic/orbie/update_overlays() + . = ..() + if(stat == DEAD) + return + . += flame_overlay + if(happy_state) + . += happy_eyes_overlay + else if(light_on) + . += orbie_light_overlay + else + . += eyes_overlay + +/mob/living/basic/orbie/gib() + death(TRUE) + +#undef ORBIE_MAXIMUM_HEALTH diff --git a/code/modules/mob/living/basic/pets/orbie/orbie_abilities.dm b/code/modules/mob/living/basic/pets/orbie/orbie_abilities.dm new file mode 100644 index 0000000000000..fb9994a932161 --- /dev/null +++ b/code/modules/mob/living/basic/pets/orbie/orbie_abilities.dm @@ -0,0 +1,46 @@ +/datum/action/cooldown/mob_cooldown/lights + name = "Toggle Lights" + button_icon = 'icons/mob/simple/pets.dmi' + button_icon_state = "orbie_light_action" + background_icon_state = "bg_default" + overlay_icon_state = "bg_default_border" + click_to_activate = FALSE + +/datum/action/cooldown/mob_cooldown/lights/Activate() + owner.set_light_on(!owner.light_on) + return TRUE + + +/datum/action/cooldown/mob_cooldown/capture_photo + name = "Camera" + button_icon = 'icons/mob/simple/pets.dmi' + button_icon_state = "orbie_light_action" + background_icon_state = "bg_default" + overlay_icon_state = "bg_default_border" + cooldown_time = 30 SECONDS + ///camera we use to take photos + var/obj/item/camera/ability_camera + +/datum/action/cooldown/mob_cooldown/capture_photo/Grant(mob/grant_to) + . = ..() + if(isnull(owner)) + return + ability_camera = new(owner) + ability_camera.print_picture_on_snap = FALSE + RegisterSignal(ability_camera, COMSIG_PREQDELETED, PROC_REF(on_camera_delete)) + +/datum/action/cooldown/mob_cooldown/capture_photo/Activate(atom/target) + if(isnull(ability_camera)) + return FALSE + ability_camera.captureimage(target, owner) + StartCooldown() + return TRUE + +/datum/action/cooldown/mob_cooldown/capture_photo/proc/on_camera_delete(datum/source) + SIGNAL_HANDLER + UnregisterSignal(ability_camera, COMSIG_PREQDELETED) + ability_camera = null + +/datum/action/cooldown/mob_cooldown/capture_photo/Destroy() + QDEL_NULL(ability_camera) + return ..() diff --git a/code/modules/mob/living/basic/pets/orbie/orbie_ai.dm b/code/modules/mob/living/basic/pets/orbie/orbie_ai.dm new file mode 100644 index 0000000000000..854a02094640b --- /dev/null +++ b/code/modules/mob/living/basic/pets/orbie/orbie_ai.dm @@ -0,0 +1,169 @@ +#define PET_PLAYTIME_COOLDOWN (2 MINUTES) +#define MESSAGE_EXPIRY_TIME (30 SECONDS) + +/datum/ai_controller/basic_controller/orbie + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items, + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, + BB_TRICK_NAME = "Trick", + ) + + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/find_food, + /datum/ai_planning_subtree/find_playmates, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/relay_pda_message, + /datum/ai_planning_subtree/pet_planning, + ) + +/datum/ai_controller/basic_controller/orbie/TryPossessPawn(atom/new_pawn) + . = ..() + if(. & AI_CONTROLLER_INCOMPATIBLE) + return + RegisterSignal(new_pawn, COMSIG_AI_BLACKBOARD_KEY_SET(BB_LAST_RECIEVED_MESSAGE), PROC_REF(on_set_message)) + +/datum/ai_controller/basic_controller/orbie/proc/on_set_message(datum/source) + SIGNAL_HANDLER + + addtimer(CALLBACK(src, PROC_REF(clear_blackboard_key), BB_LAST_RECIEVED_MESSAGE), MESSAGE_EXPIRY_TIME) + +///ai behavior that lets us search for other orbies to play with +/datum/ai_planning_subtree/find_playmates + +/datum/ai_planning_subtree/find_playmates/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(controller.blackboard[BB_NEXT_PLAYDATE] > world.time) + return + if(controller.blackboard_key_exists(BB_NEARBY_PLAYMATE)) + controller.queue_behavior(/datum/ai_behavior/interact_with_playmate, BB_NEARBY_PLAYMATE) + return SUBTREE_RETURN_FINISH_PLANNING + + controller.queue_behavior(/datum/ai_behavior/find_and_set/find_playmate, BB_NEARBY_PLAYMATE, /mob/living/basic/orbie) + +/datum/ai_behavior/find_and_set/find_playmate + +/datum/ai_behavior/find_and_set/find_playmate/search_tactic(datum/ai_controller/controller, locate_path, search_range) + for(var/mob/living/basic/orbie/playmate in oview(search_range, controller.pawn)) + if(playmate == controller.pawn || playmate.stat == DEAD || isnull(playmate.ai_controller)) + continue + if(playmate.ai_controller.blackboard[BB_NEARBY_PLAYMATE] || playmate.ai_controller.blackboard[BB_NEXT_PLAYDATE] > world.time) //they already have a playmate... + continue + playmate.ai_controller.set_blackboard_key(BB_NEARBY_PLAYMATE, controller.pawn) + return playmate + return null + + +/datum/ai_behavior/interact_with_playmate + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/interact_with_playmate/setup(datum/ai_controller/controller, target_key) + . = ..() + var/turf/target = controller.blackboard[target_key] + if(isnull(target)) + return FALSE + set_movement_target(controller, target) + +/datum/ai_behavior/interact_with_playmate/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + . = ..() + var/mob/living/basic/living_pawn = controller.pawn + var/atom/target = controller.blackboard[target_key] + + if(QDELETED(target)) + finish_action(controller, FALSE, target_key) + return + + living_pawn.manual_emote("plays with [target]!") + living_pawn.spin(spintime = 4, speed = 1) + living_pawn.ClickOn(target) + finish_action(controller, TRUE, target_key) + +/datum/ai_behavior/interact_with_playmate/finish_action(datum/ai_controller/controller, success, target_key) + . = ..() + controller.clear_blackboard_key(target_key) + controller.set_blackboard_key(BB_NEXT_PLAYDATE, world.time + PET_PLAYTIME_COOLDOWN) + +/datum/ai_planning_subtree/relay_pda_message + +/datum/ai_planning_subtree/relay_pda_message/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if(controller.blackboard[BB_VIRTUAL_PET_LEVEL] < 2 || isnull(controller.blackboard[BB_LAST_RECIEVED_MESSAGE])) + return + + controller.queue_behavior(/datum/ai_behavior/relay_pda_message, BB_LAST_RECIEVED_MESSAGE) + +/datum/ai_behavior/relay_pda_message/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + . = ..() + var/mob/living/basic/living_pawn = controller.pawn + var/text_to_say = controller.blackboard[target_key] + if(isnull(text_to_say)) + finish_action(controller, FALSE, target_key) + return + + living_pawn.say(text_to_say, forced = "AI controller") + living_pawn.spin(spintime = 4, speed = 1) + finish_action(controller, TRUE, target_key) + +/datum/ai_behavior/relay_pda_message/finish_action(datum/ai_controller/controller, success, target_key) + . = ..() + controller.clear_blackboard_key(target_key) + +/datum/pet_command/follow/orbie + follow_behavior = /datum/ai_behavior/pet_follow_friend/orbie + +/datum/ai_behavior/pet_follow_friend/orbie + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +///command to make our pet turn its lights on, we need to be level 2 to activate this ability +/datum/pet_command/untargeted_ability/pet_lights + command_name = "Lights" + command_desc = "Toggle your pet's lights!" + radial_icon = 'icons/mob/simple/pets.dmi' + radial_icon_state = "orbie_lights_action" + speech_commands = list("lights", "light", "toggle") + ability_key = BB_LIGHTS_ABILITY + +/datum/pet_command/untargeted_ability/pet_lights/execute_action(datum/ai_controller/controller) + if(controller.blackboard[BB_VIRTUAL_PET_LEVEL] < 2) + controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) + return SUBTREE_RETURN_FINISH_PLANNING + return ..() + +/datum/pet_command/point_targeting/use_ability/take_photo + command_name = "Photo" + command_desc = "Make your pet take a photo!" + radial_icon = 'icons/mob/simple/pets.dmi' + radial_icon_state = "orbie_lights_action" + speech_commands = list("photo", "picture", "image") + command_feedback = "Readys camera mode" + pet_ability_key = BB_PHOTO_ABILITY + targeting_strategy_key = BB_TARGETING_STRATEGY + +/datum/pet_command/point_targeting/use_ability/take_photo/execute_action(datum/ai_controller/controller) + if(controller.blackboard[BB_VIRTUAL_PET_LEVEL] < 3) + controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) + return SUBTREE_RETURN_FINISH_PLANNING + return ..() + +/datum/pet_command/perform_trick_sequence + command_name = "Trick Sequence" + command_desc = "A trick sequence programmable through your PDA!" + +/datum/pet_command/perform_trick_sequence/find_command_in_text(spoken_text, check_verbosity = FALSE) + var/mob/living/living_pawn = weak_parent.resolve() + if(isnull(living_pawn?.ai_controller)) + return FALSE + var/text_command = living_pawn.ai_controller.blackboard[BB_TRICK_NAME] + if(isnull(text_command)) + return FALSE + return findtext(spoken_text, text_command) + +/datum/pet_command/perform_trick_sequence/execute_action(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/list/trick_sequence = controller.blackboard[BB_TRICK_SEQUENCE] + for(var/index in 1 to length(trick_sequence)) + addtimer(CALLBACK(living_pawn, TYPE_PROC_REF(/mob, emote), trick_sequence[index], index * 0.5 SECONDS)) + controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) + return SUBTREE_RETURN_FINISH_PLANNING + +#undef PET_PLAYTIME_COOLDOWN +#undef MESSAGE_EXPIRY_TIME diff --git a/code/modules/mob/living/basic/pets/parrot/poly.dm b/code/modules/mob/living/basic/pets/parrot/poly.dm index f139a43d988df..cba3dd6e588e3 100644 --- a/code/modules/mob/living/basic/pets/parrot/poly.dm +++ b/code/modules/mob/living/basic/pets/parrot/poly.dm @@ -197,6 +197,7 @@ butcher_results = list(/obj/item/ectoplasm = 1) ai_controller = /datum/ai_controller/basic_controller/parrot/ghost speech_probability_rate = 1 + resistance_flags = parent_type::resistance_flags | SHUTTLE_CRUSH_PROOF /mob/living/basic/parrot/poly/ghost/Initialize(mapload) // block anything and everything that could possibly happen with writing memory for ghosts diff --git a/code/modules/mob/living/basic/ruin_defender/flesh.dm b/code/modules/mob/living/basic/ruin_defender/flesh.dm index 38e56f84c845e..e33cdcad1a1ea 100644 --- a/code/modules/mob/living/basic/ruin_defender/flesh.dm +++ b/code/modules/mob/living/basic/ruin_defender/flesh.dm @@ -49,12 +49,12 @@ if(isnull(current_bodypart) || isnull(current_bodypart.owner)) return var/mob/living/carbon/human/victim = current_bodypart.owner - if(prob(SPT_PROB(3, SSMOBS_DT))) + if(SPT_PROB(3, SSMOBS_DT)) to_chat(victim, span_warning("The thing posing as your limb makes you feel funny...")) //warn em //firstly as a sideeffect we drain nutrition from our host victim.adjust_nutrition(-1.5) - if(!prob(SPT_PROB(1.5, SSMOBS_DT))) + if(!SPT_PROB(1.5, SSMOBS_DT)) return if(istype(current_bodypart, /obj/item/bodypart/arm)) diff --git a/code/modules/mob/living/basic/space_fauna/bear/_bear.dm b/code/modules/mob/living/basic/space_fauna/bear/_bear.dm index e23e022d00e61..eef08b30197d1 100644 --- a/code/modules/mob/living/basic/space_fauna/bear/_bear.dm +++ b/code/modules/mob/living/basic/space_fauna/bear/_bear.dm @@ -82,6 +82,12 @@ icon_dead = "snowbear_dead" desc = "It's a polar bear, in space, but not actually in space." +/mob/living/basic/bear/snow/ancient + name = "ancient polar bear" + desc = "A grizzled old polar bear, its hide thick enough to make it impervious to almost all weapons." + status_flags = CANPUSH | GODMODE + gold_core_spawnable = NO_SPAWN + /mob/living/basic/bear/snow/Initialize(mapload) . = ..() ADD_TRAIT(src, TRAIT_SNOWSTORM_IMMUNE, INNATE_TRAIT) diff --git a/code/modules/mob/living/basic/space_fauna/ghost.dm b/code/modules/mob/living/basic/space_fauna/ghost.dm index 406d2ecefddc6..7545f9cfea394 100644 --- a/code/modules/mob/living/basic/space_fauna/ghost.dm +++ b/code/modules/mob/living/basic/space_fauna/ghost.dm @@ -23,8 +23,8 @@ death_message = "wails, disintegrating into a pile of ectoplasm!" gold_core_spawnable = NO_SPAWN //too spooky for science light_system = OVERLAY_LIGHT - light_range = 1 // same glowing as visible player ghosts - light_power = 2 + light_range = 2.5 // same glowing as visible player ghosts + light_power = 0.6 ai_controller = /datum/ai_controller/basic_controller/ghost ///What hairstyle will this ghost have diff --git a/code/modules/mob/living/basic/space_fauna/netherworld/migo.dm b/code/modules/mob/living/basic/space_fauna/netherworld/migo.dm index 1be2a870de3ba..342d10ec60c32 100644 --- a/code/modules/mob/living/basic/space_fauna/netherworld/migo.dm +++ b/code/modules/mob/living/basic/space_fauna/netherworld/migo.dm @@ -1,6 +1,6 @@ /mob/living/basic/migo name = "mi-go" - desc = "A pinkish, fungoid crustacean-like creature with numerous pairs of clawed appendages and a head covered with waving antennae." + desc = "A pinkish, fungoid crustacean-like creature with clawed appendages and a head covered with waving antennae." icon_state = "mi-go" icon_living = "mi-go" icon_dead = "mi-go-dead" @@ -36,6 +36,11 @@ /mob/living/basic/migo/Initialize(mapload) . = ..() migo_sounds = list('sound/items/bubblewrap.ogg', 'sound/items/change_jaws.ogg', 'sound/items/crowbar.ogg', 'sound/items/drink.ogg', 'sound/items/deconstruct.ogg', 'sound/items/carhorn.ogg', 'sound/items/change_drill.ogg', 'sound/items/dodgeball.ogg', 'sound/items/eatfood.ogg', 'sound/items/megaphone.ogg', 'sound/items/screwdriver.ogg', 'sound/items/weeoo1.ogg', 'sound/items/wirecutter.ogg', 'sound/items/welder.ogg', 'sound/items/zip.ogg', 'sound/items/rped.ogg', 'sound/items/ratchet.ogg', 'sound/items/polaroid1.ogg', 'sound/items/pshoom.ogg', 'sound/items/airhorn.ogg', 'sound/items/geiger/high1.ogg', 'sound/items/geiger/high2.ogg', 'sound/voice/beepsky/creep.ogg', 'sound/voice/beepsky/iamthelaw.ogg', 'sound/voice/ed209_20sec.ogg', 'sound/voice/hiss3.ogg', 'sound/voice/hiss6.ogg', 'sound/voice/medbot/patchedup.ogg', 'sound/voice/medbot/feelbetter.ogg', 'sound/voice/human/manlaugh1.ogg', 'sound/voice/human/womanlaugh.ogg', 'sound/weapons/sear.ogg', 'sound/ambience/antag/clockcultalr.ogg', 'sound/ambience/antag/ling_alert.ogg', 'sound/ambience/antag/tatoralert.ogg', 'sound/ambience/antag/monkey.ogg', 'sound/mecha/nominal.ogg', 'sound/mecha/weapdestr.ogg', 'sound/mecha/critdestr.ogg', 'sound/mecha/imag_enh.ogg', 'sound/effects/adminhelp.ogg', 'sound/effects/alert.ogg', 'sound/effects/attackblob.ogg', 'sound/effects/bamf.ogg', 'sound/effects/blobattack.ogg', 'sound/effects/break_stone.ogg', 'sound/effects/bubbles.ogg', 'sound/effects/bubbles2.ogg', 'sound/effects/clang.ogg', 'sound/effects/clockcult_gateway_disrupted.ogg', 'sound/effects/footstep/clownstep2.ogg', 'sound/effects/curse1.ogg', 'sound/effects/dimensional_rend.ogg', 'sound/effects/doorcreaky.ogg', 'sound/effects/empulse.ogg', 'sound/effects/explosion_distant.ogg', 'sound/effects/explosionfar.ogg', 'sound/effects/explosion1.ogg', 'sound/effects/grillehit.ogg', 'sound/effects/genetics.ogg', 'sound/effects/heart_beat.ogg', 'sound/runtime/hyperspace/hyperspace_begin.ogg', 'sound/runtime/hyperspace/hyperspace_end.ogg', 'sound/effects/his_grace_awaken.ogg', 'sound/effects/pai_boot.ogg', 'sound/effects/phasein.ogg', 'sound/effects/picaxe1.ogg', 'sound/effects/sparks1.ogg', 'sound/effects/smoke.ogg', 'sound/effects/splat.ogg', 'sound/effects/snap.ogg', 'sound/effects/tendril_destroyed.ogg', 'sound/effects/supermatter.ogg', 'sound/misc/desecration-01.ogg', 'sound/misc/desecration-02.ogg', 'sound/misc/desecration-03.ogg', 'sound/misc/bloblarm.ogg', 'sound/misc/airraid.ogg', 'sound/misc/bang.ogg','sound/misc/highlander.ogg', 'sound/misc/interference.ogg', 'sound/misc/notice1.ogg', 'sound/misc/notice2.ogg', 'sound/misc/sadtrombone.ogg', 'sound/misc/slip.ogg', 'sound/misc/splort.ogg', 'sound/weapons/armbomb.ogg', 'sound/weapons/beam_sniper.ogg', 'sound/weapons/chainsawhit.ogg', 'sound/weapons/emitter.ogg', 'sound/weapons/emitter2.ogg', 'sound/weapons/blade1.ogg', 'sound/weapons/bladeslice.ogg', 'sound/weapons/blastcannon.ogg', 'sound/weapons/blaster.ogg', 'sound/weapons/bulletflyby3.ogg', 'sound/weapons/circsawhit.ogg', 'sound/weapons/cqchit2.ogg', 'sound/weapons/drill.ogg', 'sound/weapons/genhit1.ogg', 'sound/weapons/gun/pistol/shot_suppressed.ogg', 'sound/weapons/gun/pistol/shot.ogg', 'sound/weapons/handcuffs.ogg', 'sound/weapons/homerun.ogg', 'sound/weapons/kinetic_accel.ogg', 'sound/machines/clockcult/steam_whoosh.ogg', 'sound/machines/fryer/deep_fryer_emerge.ogg', 'sound/machines/airlock.ogg', 'sound/machines/airlock_alien_prying.ogg', 'sound/machines/airlockclose.ogg', 'sound/machines/airlockforced.ogg', 'sound/machines/airlockopen.ogg', 'sound/machines/alarm.ogg', 'sound/machines/blender.ogg', 'sound/machines/boltsdown.ogg', 'sound/machines/boltsup.ogg', 'sound/machines/buzz-sigh.ogg', 'sound/machines/buzz-two.ogg', 'sound/machines/chime.ogg', 'sound/machines/cryo_warning.ogg', 'sound/machines/defib_charge.ogg', 'sound/machines/defib_failed.ogg', 'sound/machines/defib_ready.ogg', 'sound/machines/defib_zap.ogg', 'sound/machines/deniedbeep.ogg', 'sound/machines/ding.ogg', 'sound/machines/disposalflush.ogg', 'sound/machines/door_close.ogg', 'sound/machines/door_open.ogg', 'sound/machines/engine_alert1.ogg', 'sound/machines/engine_alert2.ogg', 'sound/machines/hiss.ogg', 'sound/machines/honkbot_evil_laugh.ogg', 'sound/machines/juicer.ogg', 'sound/machines/ping.ogg', 'sound/ambience/signal.ogg', 'sound/machines/synth_no.ogg', 'sound/machines/synth_yes.ogg', 'sound/machines/terminal_alert.ogg', 'sound/machines/triple_beep.ogg', 'sound/machines/twobeep.ogg', 'sound/machines/ventcrawl.ogg', 'sound/machines/warning-buzzer.ogg', 'sound/ai/default/outbreak5.ogg', 'sound/ai/default/outbreak7.ogg', 'sound/ai/default/poweroff.ogg', 'sound/ai/default/radiation.ogg', 'sound/ai/default/shuttlecalled.ogg', 'sound/ai/default/shuttledock.ogg', 'sound/ai/default/shuttlerecalled.ogg', 'sound/ai/default/aimalf.ogg') //hahahaha fuck you code divers + + if(!istype(src, /mob/living/basic/migo/hatsune) && prob(0.1)) // chance on-load mi-gos will spawn with a miku wig on (shiny variant) + new /mob/living/basic/migo/hatsune(get_turf(loc), mapload) + return INITIALIZE_HINT_QDEL + AddElement(/datum/element/swabable, CELL_LINE_TABLE_NETHER, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 0) AddComponent(/datum/component/health_scaling_effects, min_health_slowdown = -1.5, additional_status_callback = CALLBACK(src, PROC_REF(update_dodge_chance))) @@ -43,12 +48,11 @@ /mob/living/basic/migo/proc/update_dodge_chance(health_ratio) dodge_prob = LERP(50, 10, health_ratio) -/mob/living/basic/migo/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null, message_range = 7, datum/saymode/saymode = null) - ..() - if(stat) +/mob/living/basic/migo/send_speech(message_raw, message_range, obj/source, bubble_type, list/spans, datum/language/message_language, list/message_mods, forced, tts_message, list/tts_filter) + . = ..() + if(stat != CONSCIOUS) return - var/chosen_sound = pick(migo_sounds) - playsound(src, chosen_sound, 50, TRUE) + playsound(src, pick(migo_sounds), 50, TRUE) /mob/living/basic/migo/Life(seconds_per_tick = SSMOBS_DT, times_fired) ..() @@ -71,3 +75,11 @@ . = Move(get_step(loc,pick(cdir, ccdir))) if(!.)//Can't dodge there so we just carry on . = Move(moving_to, move_direction) + +/// The special hatsune miku themed mi-go. +/mob/living/basic/migo/hatsune + name = "hatsune mi-go" + desc = parent_type::desc + " This one is wearing a bright blue wig." + icon_state = "mi-go-h" + icon_living = "mi-go-h" + gold_core_spawnable = NO_SPAWN diff --git a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm index be83d3e058f91..51379ce88a0bc 100644 --- a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm +++ b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm @@ -62,6 +62,7 @@ poll_ignore_key = POLL_IGNORE_REGAL_RAT,\ assumed_control_message = "You are an independent, invasive force on the station! Hoard coins, trash, cheese, and the like from the safety of darkness!",\ after_assumed_control = CALLBACK(src, PROC_REF(became_player_controlled)),\ + poll_chat_border_icon = /obj/item/food/cheese/wedge,\ ) var/static/list/innate_actions = list( diff --git a/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm b/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm index 5a901fa2c79cb..0a5239e1f6e23 100644 --- a/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm +++ b/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm @@ -168,7 +168,19 @@ essencecolor = "#1D2953" //oh jeez you're dying hud_used.healths.maptext = MAPTEXT("
[essence]E
") -/mob/living/basic/revenant/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null, message_range = 7, datum/saymode/saymode = null) +/mob/living/basic/revenant/say( + message, + bubble_type, + list/spans = list(), + sanitize = TRUE, + datum/language/language, + ignore_spam = FALSE, + forced, + filterproof = FALSE, + message_range = 7, + datum/saymode/saymode, + list/message_mods = list(), +) if(!message) return diff --git a/code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm b/code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm index 3ea62afd9f80d..b7bbcad2df0c0 100644 --- a/code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm +++ b/code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm @@ -204,7 +204,13 @@ if(!(bot.bot_cover_flags & BOT_COVER_EMAGGED)) new /obj/effect/temp_visual/revenant(bot.loc) bot.bot_cover_flags &= ~BOT_COVER_LOCKED - bot.bot_cover_flags |= BOT_COVER_OPEN + bot.bot_cover_flags |= BOT_COVER_MAINTS_OPEN + bot.emag_act(caster) + for(var/mob/living/basic/bot/bot in victim) + if(!(bot.bot_access_flags & BOT_COVER_EMAGGED)) + new /obj/effect/temp_visual/revenant(bot.loc) + bot.bot_access_flags &= ~BOT_COVER_LOCKED + bot.bot_access_flags |= BOT_COVER_MAINTS_OPEN bot.emag_act(caster) for(var/mob/living/carbon/human/human in victim) if(human == caster) diff --git a/code/modules/mob/living/basic/space_fauna/revenant/revenant_items.dm b/code/modules/mob/living/basic/space_fauna/revenant/revenant_items.dm index ea153b03c063c..f8a3db0202a29 100644 --- a/code/modules/mob/living/basic/space_fauna/revenant/revenant_items.dm +++ b/code/modules/mob/living/basic/space_fauna/revenant/revenant_items.dm @@ -86,21 +86,11 @@ /// Handles giving the revenant a new client to control it /obj/item/ectoplasm/revenant/proc/get_new_user() message_admins("The new revenant's old client either could not be found or is in a new, living mob - grabbing a random candidate instead...") - var/list/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to be [revenant.name] (reforming)?", check_jobban = ROLE_REVENANT, role = ROLE_REVENANT, poll_time = 5 SECONDS, target_mob = revenant, pic_source = revenant) - - if(!LAZYLEN(candidates)) + var/mob/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to be [span_notice(revenant.name)] (reforming)?", check_jobban = ROLE_REVENANT, role = ROLE_REVENANT, poll_time = 5 SECONDS, checked_target = revenant, alert_pic = revenant, role_name_text = "reforming revenant", chat_text_border_icon = revenant) + if(isnull(chosen_one)) message_admins("No candidates were found for the new revenant.") inert = TRUE visible_message(span_revenwarning("[src] settles down and seems lifeless.")) qdel(revenant) return null - - var/mob/dead/observer/potential_client = pick(candidates) - if(isnull(potential_client)) - qdel(revenant) - message_admins("No candidate was found for the new revenant. Oh well!") - inert = TRUE - visible_message(span_revenwarning("[src] settles down and seems lifeless.")) - return null - - return potential_client + return chosen_one diff --git a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_subtrees.dm b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_subtrees.dm index 56aacb11a9618..8682c8028e32e 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_subtrees.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_subtrees.dm @@ -22,7 +22,7 @@ controller.clear_blackboard_key(target_key) var/turf/our_turf = get_turf(spider) - if (is_valid_web_turf(our_turf)) + if (is_valid_web_turf(our_turf, spider)) controller.set_blackboard_key(target_key, our_turf) finish_action(controller, succeeded = TRUE) return @@ -31,7 +31,7 @@ for (var/i in 1 to scan_range) turfs_by_range["[i]"] = list() for (var/turf/turf_in_view in oview(scan_range, our_turf)) - if (!is_valid_web_turf(turf_in_view)) + if (!is_valid_web_turf(turf_in_view, spider)) continue turfs_by_range["[get_dist(our_turf, turf_in_view)]"] += turf_in_view diff --git a/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/web.dm b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/web.dm index fa44cb35b2d12..5bb27b5109cbd 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/web.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/web.dm @@ -3,7 +3,7 @@ name = "Spin Web" desc = "Spin a web to slow down potential prey." button_icon = 'icons/mob/actions/actions_animal.dmi' - button_icon_state = "lay_web" + button_icon_state = "spider_web" background_icon_state = "bg_alien" overlay_icon_state = "bg_alien_border" cooldown_time = 0 SECONDS @@ -17,10 +17,12 @@ . = ..() if (!owner) return + ADD_TRAIT(owner, TRAIT_WEB_WEAVER, REF(src)) RegisterSignals(owner, list(COMSIG_MOVABLE_MOVED, COMSIG_DO_AFTER_BEGAN, COMSIG_DO_AFTER_ENDED), PROC_REF(update_status_on_signal)) /datum/action/cooldown/mob_cooldown/lay_web/Remove(mob/removed_from) . = ..() + REMOVE_TRAIT(removed_from, TRAIT_WEB_WEAVER, REF(src)) UnregisterSignal(removed_from, list(COMSIG_MOVABLE_MOVED, COMSIG_DO_AFTER_BEGAN, COMSIG_DO_AFTER_ENDED)) /datum/action/cooldown/mob_cooldown/lay_web/IsAvailable(feedback = FALSE) @@ -72,7 +74,7 @@ /// Variant for genetics, created webs only allow the creator passage /datum/action/cooldown/mob_cooldown/lay_web/genetic desc = "Spin a web. Only you will be able to traverse your web easily." - cooldown_time = 4 SECONDS //the same time to lay a web + cooldown_time = 4 SECONDS /datum/action/cooldown/mob_cooldown/lay_web/genetic/plant_web(turf/target_turf, obj/structure/spider/stickyweb/existing_web) new /obj/structure/spider/stickyweb/genetic(target_turf, owner) @@ -94,20 +96,20 @@ /datum/action/cooldown/mob_cooldown/lay_web/solid_web name = "Spin Solid Web" desc = "Spin a web to obstruct potential prey." - button_icon_state = "lay_solid_web" + button_icon_state = "spider_wall" cooldown_time = 0 SECONDS webbing_time = 5 SECONDS /datum/action/cooldown/mob_cooldown/lay_web/solid_web/obstructed_by_other_web() - return !!(locate(/obj/structure/spider/solid) in get_turf(owner)) + return !!(locate(/obj/structure/spider/stickyweb/sealed/tough) in get_turf(owner)) /datum/action/cooldown/mob_cooldown/lay_web/solid_web/plant_web(turf/target_turf, obj/structure/spider/stickyweb/existing_web) - new /obj/structure/spider/solid(target_turf) + new /obj/structure/spider/stickyweb/sealed/tough(target_turf) /datum/action/cooldown/mob_cooldown/lay_web/web_passage name = "Spin Web Passage" desc = "Spin a web passage to hide the nest from prey view." - button_icon_state = "lay_web_passage" + button_icon_state = "spider_roof" cooldown_time = 0 SECONDS webbing_time = 4 SECONDS @@ -120,20 +122,20 @@ /datum/action/cooldown/mob_cooldown/lay_web/sticky_web name = "Spin Sticky Web" desc = "Spin a sticky web to trap intruders." - button_icon_state = "lay_sticky_web" + button_icon_state = "spider_ropes" cooldown_time = 20 SECONDS webbing_time = 3 SECONDS /datum/action/cooldown/mob_cooldown/lay_web/sticky_web/obstructed_by_other_web() - return !!(locate(/obj/structure/spider/sticky) in get_turf(owner)) + return !!(locate(/obj/structure/spider/stickyweb/very_sticky) in get_turf(owner)) /datum/action/cooldown/mob_cooldown/lay_web/sticky_web/plant_web(turf/target_turf, obj/structure/spider/stickyweb/existing_web) - new /obj/structure/spider/sticky(target_turf) + new /obj/structure/spider/stickyweb/very_sticky(target_turf) /datum/action/cooldown/mob_cooldown/lay_web/web_spikes name = "Spin Web Spikes" desc = "Extrude silk spikes to dissuade invaders." - button_icon_state = "lay_web_spikes" + button_icon_state = "spider_spikes" cooldown_time = 40 SECONDS webbing_time = 3 SECONDS @@ -174,12 +176,12 @@ /datum/action/cooldown/mob_cooldown/lay_web/web_reflector name = "Spin reflective silk screen" desc = "Spin a web to reflect missiles from the nest." - button_icon_state = "lay_web_reflector" + button_icon_state = "spider_mirror" cooldown_time = 30 SECONDS webbing_time = 4 SECONDS /datum/action/cooldown/mob_cooldown/lay_web/web_reflector/obstructed_by_other_web() - return !!(locate(/obj/structure/spider/reflector) in get_turf(owner)) + return !!(locate(/obj/structure/spider/stickyweb/sealed/reflector) in get_turf(owner)) /datum/action/cooldown/mob_cooldown/lay_web/web_reflector/plant_web(turf/target_turf, obj/structure/spider/stickyweb/existing_web) - new /obj/structure/spider/reflector(target_turf) + new /obj/structure/spider/stickyweb/sealed/reflector(target_turf) diff --git a/code/modules/mob/living/basic/space_fauna/statue/statue.dm b/code/modules/mob/living/basic/space_fauna/statue/statue.dm index 814500674fcc8..3ddbc2364e81c 100644 --- a/code/modules/mob/living/basic/space_fauna/statue/statue.dm +++ b/code/modules/mob/living/basic/space_fauna/statue/statue.dm @@ -54,7 +54,7 @@ /mob/living/basic/statue/Initialize(mapload) . = ..() - ADD_TRAIT(src, TRAIT_UNOBSERVANT, INNATE_TRAIT) + add_traits(list(TRAIT_MUTE, TRAIT_UNOBSERVANT), INNATE_TRAIT) AddComponent(/datum/component/unobserved_actor, unobserved_flags = NO_OBSERVED_MOVEMENT | NO_OBSERVED_ATTACKS) var/static/list/innate_actions = list( @@ -69,14 +69,8 @@ /mob/living/basic/statue/med_hud_set_status() return //we're a statue we're invincible -/mob/living/basic/statue/can_speak(allow_mimes = FALSE) - return FALSE // We're a statue, of course we can't talk. - // Cannot talk -/mob/living/basic/statue/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null, message_range = 7, datum/saymode/saymode = null) - return - // Turn to dust when gibbed /mob/living/basic/statue/gib() diff --git a/code/modules/mob/living/brain/brain_say.dm b/code/modules/mob/living/brain/brain_say.dm index 79e8e1d307933..df3601c1bef94 100644 --- a/code/modules/mob/living/brain/brain_say.dm +++ b/code/modules/mob/living/brain/brain_say.dm @@ -1,14 +1,25 @@ -/mob/living/brain/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterpoof = null, message_range = 7, datum/saymode/saymode = null) - if(!(container && istype(container, /obj/item/mmi))) - return //No MMI, can't speak, bucko./N - else - if(prob(emp_damage*4)) - if(prob(10))//10% chane to drop the message entirely - return - else - message = Gibberish(message, emp_damage >= 12)//scrambles the message, gets worse when emp_damage is higher +/mob/living/brain/say( + message, + bubble_type, + list/spans = list(), + sanitize = TRUE, + datum/language/language, + ignore_spam = FALSE, + forced, + filterproof = FALSE, + message_range = 7, + datum/saymode/saymode, + list/message_mods = list(), +) + if(prob(emp_damage * 4)) + if(prob(10)) //10% chance to drop the message entirely + return + message = Gibberish(message, emp_damage >= 12)//scrambles the message, gets worse when emp_damage is higher + + return ..() - ..() +/mob/living/brain/can_speak(allow_mimes) + return istype(container, /obj/item/mmi) && ..() /mob/living/brain/radio(message, list/message_mods = list(), list/spans, language) if(message_mods[MODE_HEADSET] && istype(container, /obj/item/mmi)) diff --git a/code/modules/mob/living/brain/posibrain.dm b/code/modules/mob/living/brain/posibrain.dm index 073d6c3e48eb5..a59e5021948d8 100644 --- a/code/modules/mob/living/brain/posibrain.dm +++ b/code/modules/mob/living/brain/posibrain.dm @@ -216,3 +216,10 @@ GLOBAL_VAR(posibrain_notify_cooldown) /obj/item/mmi/posibrain/add_mmi_overlay() return + +/obj/item/mmi/posibrain/display + name = "display positronic brain" + desc = "A small positronic brain that doesn't allow the downloading of personalities." + +/obj/item/mmi/posibrain/display/is_occupied() + return TRUE diff --git a/code/modules/mob/living/carbon/alien/adult/adult.dm b/code/modules/mob/living/carbon/alien/adult/adult.dm index 2cab03d670e59..bbacffd4f6f32 100644 --- a/code/modules/mob/living/carbon/alien/adult/adult.dm +++ b/code/modules/mob/living/carbon/alien/adult/adult.dm @@ -143,6 +143,9 @@ GLOBAL_LIST_INIT(strippable_alien_humanoid_items, create_strippable_list(list( melting_pot.consume_thing(lucky_winner) return TRUE +/mob/living/carbon/alien/adult/get_butt_sprite() + return BUTT_SPRITE_XENOMORPH + // Aliens can touch acid /mob/living/carbon/alien/can_touch_acid(atom/acided_atom, acid_power, acid_volume) return TRUE diff --git a/code/modules/mob/living/carbon/alien/organs.dm b/code/modules/mob/living/carbon/alien/organs.dm index a73926b18aea8..9303cd2347413 100644 --- a/code/modules/mob/living/carbon/alien/organs.dm +++ b/code/modules/mob/living/carbon/alien/organs.dm @@ -203,7 +203,6 @@ RegisterSignal(thing, COMSIG_MOVABLE_MOVED, PROC_REF(content_moved)) RegisterSignal(thing, COMSIG_QDELETING, PROC_REF(content_deleted)) if(isliving(thing)) - var/mob/living/lad = thing RegisterSignal(thing, COMSIG_LIVING_DEATH, PROC_REF(content_died)) stomach_contents += thing thing.forceMove(owner || src) // We assert that if we have no owner, we will not be nullspaced diff --git a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm index eeb33bd3e891f..c7c4b1ed06f3e 100644 --- a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm +++ b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm @@ -90,15 +90,18 @@ return bursting = TRUE - - var/datum/callback/to_call = CALLBACK(src, PROC_REF(on_poll_concluded), gib_on_success) - owner.AddComponent(/datum/component/orbit_poll, \ - ignore_key = POLL_IGNORE_ALIEN_LARVA, \ - job_bans = ROLE_ALIEN, \ - to_call = to_call, \ - custom_message = "An alien is bursting out of [owner.real_name]", \ - title = "alien larva" \ + var/mob/chosen_one = SSpolling.poll_ghosts_for_target( + question = "An [span_notice("alien")] is bursting out of [span_danger(owner.real_name)]!", + role = ROLE_ALIEN, + check_jobban = ROLE_ALIEN, + poll_time = 20 SECONDS, + checked_target = src, + ignore_category = POLL_IGNORE_ALIEN_LARVA, + alert_pic = owner, + role_name_text = "alien larva", + chat_text_border_icon = /mob/living/carbon/alien/larva, ) + on_poll_concluded(gib_on_success, chosen_one) /// Poll has concluded with a suitor /obj/item/organ/internal/body_egg/alien_embryo/proc/on_poll_concluded(gib_on_success, mob/dead/observer/ghost) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 84fe6dd5edf41..ec03414abfcd2 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -85,7 +85,7 @@ span_userdanger("You violently crash into [victim][extra_speed ? " extra hard" : ""], but [victim] managed to block the worst of it!")) log_combat(src, victim, "crashed into and was blocked by") return - else if(HAS_TRAIT(src, TRAIT_BRAWLING_KNOCKDOWN_BLOCKED)) + else if(HAS_TRAIT(victim, TRAIT_BRAWLING_KNOCKDOWN_BLOCKED)) victim.take_bodypart_damage(10 + 5 * extra_speed, check_armor = TRUE, wound_bonus = extra_speed * 5) victim.apply_damage(10 + 10 * extra_speed, STAMINA) victim.adjust_staggered_up_to(STAGGERED_SLOWDOWN_LENGTH * 2, 10 SECONDS) diff --git a/code/modules/mob/living/carbon/carbon_movement.dm b/code/modules/mob/living/carbon/carbon_movement.dm index d900706f102ac..cb35aebfb0770 100644 --- a/code/modules/mob/living/carbon/carbon_movement.dm +++ b/code/modules/mob/living/carbon/carbon_movement.dm @@ -8,14 +8,14 @@ /mob/living/carbon/Move(NewLoc, direct) . = ..() - if(. && !(movement_type & FLOATING)) //floating is easy - if(HAS_TRAIT(src, TRAIT_NOHUNGER)) - set_nutrition(NUTRITION_LEVEL_FED - 1) //just less than feeling vigorous - else if(nutrition && stat != DEAD) - adjust_nutrition(-(HUNGER_FACTOR/10)) - if(move_intent == MOVE_INTENT_RUN) - adjust_nutrition(-(HUNGER_FACTOR/10)) - + if(!. || (movement_type & FLOATING)) //floating is easy + return + if(nutrition <= 0 || stat == DEAD) + return + var/hunger_loss = HUNGER_FACTOR / 10 + if(move_intent == MOVE_INTENT_RUN) + hunger_loss *= 2 + adjust_nutrition(-1 * hunger_loss) /mob/living/carbon/set_usable_legs(new_value) . = ..() diff --git a/code/modules/mob/living/carbon/death.dm b/code/modules/mob/living/carbon/death.dm index 85a6b06a2d340..eafb6f8ba22e1 100644 --- a/code/modules/mob/living/carbon/death.dm +++ b/code/modules/mob/living/carbon/death.dm @@ -42,7 +42,7 @@ for(var/obj/item/organ/organ as anything in organs) if((drop_bitflags & DROP_BRAIN) && istype(organ, /obj/item/organ/internal/brain)) - if(drop_bitflags & DROP_BODYPARTS) + if((drop_bitflags & DROP_BODYPARTS) && (check_zone(organ.zone) != BODY_ZONE_CHEST)) // chests can't drop continue // the head will drop, so the brain should stay inside organ.Remove(src) diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm index ff07ad2ef0e73..7c8b6e9d23799 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -162,9 +162,6 @@ GLOBAL_LIST_EMPTY(features_by_species) ///Unique cookie given by admins through prayers var/species_cookie = /obj/item/food/cookie - ///For custom overrides for species ass images - var/icon/ass_image - /// List of family heirlooms this species can get with the family heirloom quirk. List of types. var/list/family_heirlooms diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 89ad4700aad71..b20cb463c833b 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -41,10 +41,11 @@ /mob/living/carbon/human/proc/setup_mood() if (CONFIG_GET(flag/disable_human_mood)) return - if (isdummy(src)) - return mob_mood = new /datum/mood(src) +/mob/living/carbon/human/dummy/setup_mood() + return + /// This proc is for holding effects applied when a mob is missing certain organs /// It is called very, very early in human init because all humans innately spawn with no organs and gain them during init /// Gaining said organs removes these effects @@ -347,6 +348,10 @@ var/obj/item/bodypart/the_part = isbodypart(target_zone) ? target_zone : get_bodypart(check_zone(target_zone)) //keep these synced to_chat(user, span_alert("There is no exposed flesh or thin material on [p_their()] [the_part.name].")) +/mob/living/carbon/human/get_butt_sprite() + var/obj/item/bodypart/chest/chest = get_bodypart(BODY_ZONE_CHEST) + return chest?.get_butt_sprite() + /mob/living/carbon/human/get_footprint_sprite() var/obj/item/bodypart/leg/L = get_bodypart(BODY_ZONE_R_LEG) || get_bodypart(BODY_ZONE_L_LEG) return shoes?.footprint_sprite || L?.footprint_sprite @@ -983,16 +988,6 @@ remove_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown) remove_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown_flying) -/mob/living/carbon/human/adjust_nutrition(change) //Honestly FUCK the oldcoders for putting nutrition on /mob someone else can move it up because holy hell I'd have to fix SO many typechecks - if(HAS_TRAIT(src, TRAIT_NOHUNGER)) - return FALSE - return ..() - -/mob/living/carbon/human/set_nutrition(change) //Seriously fuck you oldcoders. - if(HAS_TRAIT(src, TRAIT_NOHUNGER)) - return FALSE - return ..() - /mob/living/carbon/human/is_bleeding() if(HAS_TRAIT(src, TRAIT_NOBLOOD)) return FALSE diff --git a/code/modules/mob/living/carbon/human/human_say.dm b/code/modules/mob/living/carbon/human/human_say.dm index c1ddf8f62410f..8bf21e3c809a6 100644 --- a/code/modules/mob/living/carbon/human/human_say.dm +++ b/code/modules/mob/living/carbon/human/human_say.dm @@ -1,6 +1,18 @@ -/mob/living/carbon/human/say(message, bubble_type, list/spans, sanitize, datum/language/language, ignore_spam, forced, filterproof, message_range, datum/saymode/saymode) +/mob/living/carbon/human/say( + message, + bubble_type, + list/spans = list(), + sanitize = TRUE, + datum/language/language, + ignore_spam = FALSE, + forced, + filterproof = FALSE, + message_range = 7, + datum/saymode/saymode, + list/message_mods = list(), +) if(!HAS_TRAIT(src, TRAIT_SPEAKS_CLEARLY)) var/static/regex/tongueless_lower = new("\[gdntke]+", "g") var/static/regex/tongueless_upper = new("\[GDNTKE]+", "g") diff --git a/code/modules/mob/living/carbon/human/init_signals.dm b/code/modules/mob/living/carbon/human/init_signals.dm index 9a4a55bb7ac1f..89cf7e01ce6ff 100644 --- a/code/modules/mob/living/carbon/human/init_signals.dm +++ b/code/modules/mob/living/carbon/human/init_signals.dm @@ -5,6 +5,9 @@ RegisterSignals(src, list(SIGNAL_ADDTRAIT(TRAIT_DWARF), SIGNAL_REMOVETRAIT(TRAIT_DWARF)), PROC_REF(on_dwarf_trait)) RegisterSignal(src, COMSIG_MOVABLE_MESSAGE_GET_NAME_PART, PROC_REF(get_name_part)) + RegisterSignals(src, list(SIGNAL_ADDTRAIT(TRAIT_FAT), SIGNAL_REMOVETRAIT(TRAIT_FAT)), PROC_REF(on_fat)) + RegisterSignals(src, list(SIGNAL_ADDTRAIT(TRAIT_NOHUNGER), SIGNAL_REMOVETRAIT(TRAIT_NOHUNGER)), PROC_REF(on_nohunger)) + /// Gaining or losing [TRAIT_UNKNOWN] updates our name and our sechud /mob/living/carbon/human/proc/on_unknown_trait(datum/source) SIGNAL_HANDLER @@ -38,3 +41,25 @@ if(name != voice_name) voice_name += " (as [get_id_name("Unknown")])" stored_name[NAME_PART_INDEX] = voice_name + +/mob/living/carbon/human/proc/on_fat(datum/source) + SIGNAL_HANDLER + hud_used?.hunger?.update_appearance() + mob_mood?.update_nutrition_moodlets() + + if(HAS_TRAIT(src, TRAIT_FAT)) + add_movespeed_modifier(/datum/movespeed_modifier/obesity) + else + remove_movespeed_modifier(/datum/movespeed_modifier/obesity) + +/mob/living/carbon/human/proc/on_nohunger(datum/source) + SIGNAL_HANDLER + // When gaining NOHUNGER, we restore nutrition to normal levels, since we no longer interact with the hunger system + if(HAS_TRAIT(src, TRAIT_NOHUNGER)) + set_nutrition(NUTRITION_LEVEL_FED, forced = TRUE) + satiety = 0 + overeatduration = 0 + REMOVE_TRAIT(src, TRAIT_FAT, OBESITY) + else + hud_used?.hunger?.update_appearance() + mob_mood?.update_nutrition_moodlets() diff --git a/code/modules/mob/living/carbon/human/species_types/abductors.dm b/code/modules/mob/living/carbon/human/species_types/abductors.dm index 74d2bedf3a702..f3189491f5ae9 100644 --- a/code/modules/mob/living/carbon/human/species_types/abductors.dm +++ b/code/modules/mob/living/carbon/human/species_types/abductors.dm @@ -19,7 +19,6 @@ mutantlungs = null mutantbrain = /obj/item/organ/internal/brain/abductor changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT - ass_image = 'icons/ass/assgrey.png' bodypart_overrides = list( BODY_ZONE_HEAD = /obj/item/bodypart/head/abductor, @@ -40,8 +39,6 @@ var/datum/atom_hud/abductor_hud = GLOB.huds[DATA_HUD_ABDUCTOR] abductor_hud.show_to(C) - C.set_safe_hunger_level() - /datum/species/abductor/on_species_loss(mob/living/carbon/C) . = ..() var/datum/atom_hud/abductor_hud = GLOB.huds[DATA_HUD_ABDUCTOR] diff --git a/code/modules/mob/living/carbon/human/species_types/android.dm b/code/modules/mob/living/carbon/human/species_types/android.dm index 2c006d2e936b6..b7a6c532106ff 100644 --- a/code/modules/mob/living/carbon/human/species_types/android.dm +++ b/code/modules/mob/living/carbon/human/species_types/android.dm @@ -47,11 +47,6 @@ BODY_ZONE_R_LEG = /obj/item/bodypart/leg/right/robot/android, ) -/datum/species/android/on_species_gain(mob/living/carbon/C) - . = ..() - // Androids don't eat, hunger or metabolise foods. Let's do some cleanup. - C.set_safe_hunger_level() - /datum/species/android/get_physical_attributes() return "Androids are almost, but not quite, identical to fully augmented humans. \ Unlike those, though, they're completely immune to toxin damage, don't have blood or organs (besides their head), don't get hungry, and can reattach their limbs! \ diff --git a/code/modules/mob/living/carbon/human/species_types/dullahan.dm b/code/modules/mob/living/carbon/human/species_types/dullahan.dm index 1407ba80f7553..1d7c328f88232 100644 --- a/code/modules/mob/living/carbon/human/species_types/dullahan.dm +++ b/code/modules/mob/living/carbon/human/species_types/dullahan.dm @@ -58,7 +58,6 @@ eyes.bodypart_insert(my_head) human.update_body() head.update_icon_dropped() - human.set_safe_hunger_level() RegisterSignal(head, COMSIG_QDELETING, PROC_REF(on_head_destroyed)) /// If we gained a new body part, it had better not be a head diff --git a/code/modules/mob/living/carbon/human/species_types/ethereal.dm b/code/modules/mob/living/carbon/human/species_types/ethereal.dm index 51f7426c6dfe9..3d45b2e735010 100644 --- a/code/modules/mob/living/carbon/human/species_types/ethereal.dm +++ b/code/modules/mob/living/carbon/human/species_types/ethereal.dm @@ -62,7 +62,6 @@ RegisterSignal(new_ethereal, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(refresh_light_color)) ethereal_light = new_ethereal.mob_light(light_type = /obj/effect/dummy/lighting_obj/moblight/species) refresh_light_color(new_ethereal) - new_ethereal.set_safe_hunger_level() update_mail_goodies(new_ethereal) var/obj/item/organ/internal/heart/ethereal/ethereal_heart = new_ethereal.get_organ_slot(ORGAN_SLOT_HEART) diff --git a/code/modules/mob/living/carbon/human/species_types/felinid.dm b/code/modules/mob/living/carbon/human/species_types/felinid.dm index 277bc7d72af8b..e57708565d716 100644 --- a/code/modules/mob/living/carbon/human/species_types/felinid.dm +++ b/code/modules/mob/living/carbon/human/species_types/felinid.dm @@ -18,7 +18,6 @@ changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT species_language_holder = /datum/language_holder/felinid payday_modifier = 1.0 - ass_image = 'icons/ass/asscat.png' family_heirlooms = list(/obj/item/toy/cattoy) /// When false, this is a felinid created by mass-purrbation var/original_felinid = TRUE diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm index a39567d113147..9ec396816558b 100644 --- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm @@ -30,7 +30,6 @@ changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT inherent_factions = list(FACTION_SLIME) species_language_holder = /datum/language_holder/jelly - ass_image = 'icons/ass/assslime.png' bodypart_overrides = list( BODY_ZONE_L_ARM = /obj/item/bodypart/arm/left/jelly, diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm index 2e265d32f65c7..8b9946fd8c72c 100644 --- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm @@ -34,8 +34,6 @@ bodytemp_heat_damage_limit = BODYTEMP_HEAT_LAVALAND_SAFE bodytemp_cold_damage_limit = (BODYTEMP_COLD_DAMAGE_LIMIT - 10) - ass_image = 'icons/ass/asslizard.png' - bodypart_overrides = list( BODY_ZONE_HEAD = /obj/item/bodypart/head/lizard, BODY_ZONE_CHEST = /obj/item/bodypart/chest/lizard, diff --git a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm index c9fa732b2880d..9bea2850617ba 100644 --- a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm +++ b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm @@ -50,8 +50,6 @@ // This effects how fast body temp stabilizes, also if cold resit is lost on the mob bodytemp_cold_damage_limit = (BODYTEMP_COLD_DAMAGE_LIMIT - 50) // about -50c - ass_image = 'icons/ass/assplasma.png' - outfit_override_registry = list( /datum/outfit/syndicate = /datum/outfit/syndicate/plasmaman, /datum/outfit/syndicate/full = /datum/outfit/syndicate/full/plasmaman, @@ -62,10 +60,6 @@ /// If the bones themselves are burning clothes won't help you much var/internal_fire = FALSE -/datum/species/plasmaman/on_species_gain(mob/living/carbon/C, datum/species/old_species, pref_load) - . = ..() - C.set_safe_hunger_level() - /datum/species/plasmaman/spec_life(mob/living/carbon/human/H, seconds_per_tick, times_fired) . = ..() var/atmos_sealed = TRUE diff --git a/code/modules/mob/living/carbon/human/species_types/podpeople.dm b/code/modules/mob/living/carbon/human/species_types/podpeople.dm index 0190996567d13..d42dea250b4f3 100644 --- a/code/modules/mob/living/carbon/human/species_types/podpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/podpeople.dm @@ -29,8 +29,6 @@ BODY_ZONE_CHEST = /obj/item/bodypart/chest/pod, ) - ass_image = 'icons/ass/asspodperson.png' - /datum/species/pod/on_species_gain(mob/living/carbon/new_podperson, datum/species/old_species, pref_load) . = ..() if(ishuman(new_podperson)) diff --git a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm index c77ad3dfee1ed..b2027b9e1a654 100644 --- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm @@ -95,6 +95,7 @@ icon = 'icons/obj/medical/organs/shadow_organs.dmi' color_cutoffs = list(20, 10, 40) pepperspray_protect = TRUE + flash_protect = FLASH_PROTECTION_SENSITIVE /// the key to some of their powers /obj/item/organ/internal/brain/shadow diff --git a/code/modules/mob/living/carbon/human/species_types/skeletons.dm b/code/modules/mob/living/carbon/human/species_types/skeletons.dm index a20e240529fb2..4718645b56346 100644 --- a/code/modules/mob/living/carbon/human/species_types/skeletons.dm +++ b/code/modules/mob/living/carbon/human/species_types/skeletons.dm @@ -44,10 +44,6 @@ BODY_ZONE_CHEST = /obj/item/bodypart/chest/skeleton, ) -/datum/species/skeleton/on_species_gain(mob/living/carbon/C, datum/species/old_species, pref_load) - . = ..() - C.set_safe_hunger_level() - /datum/species/skeleton/check_roundstart_eligible() if(check_holidays(HALLOWEEN)) return TRUE diff --git a/code/modules/mob/living/carbon/human/species_types/vampire.dm b/code/modules/mob/living/carbon/human/species_types/vampire.dm index 427f1f5f71bf9..2b14969f548cb 100644 --- a/code/modules/mob/living/carbon/human/species_types/vampire.dm +++ b/code/modules/mob/living/carbon/human/species_types/vampire.dm @@ -39,7 +39,6 @@ to_chat(new_vampire, "[info_text]") new_vampire.skin_tone = "albino" new_vampire.update_body(0) - new_vampire.set_safe_hunger_level() RegisterSignal(new_vampire, COMSIG_MOB_APPLY_DAMAGE_MODIFIERS, PROC_REF(damage_weakness)) /datum/species/vampire/on_species_loss(mob/living/carbon/human/C, datum/species/new_species, pref_load) diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm index 5c13e489395b1..3a19eebadc966 100644 --- a/code/modules/mob/living/emote.dm +++ b/code/modules/mob/living/emote.dm @@ -711,7 +711,7 @@ message = "beeps." message_param = "beeps at %t." sound = 'sound/machines/twobeep.ogg' - mob_type_allowed_typecache = list(/mob/living/brain, /mob/living/silicon) + mob_type_allowed_typecache = list(/mob/living/brain, /mob/living/silicon, /mob/living/basic/orbie) emote_type = EMOTE_AUDIBLE /datum/emote/living/inhale diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index e096434cbb966..73db5d419a440 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -497,7 +497,7 @@ log_message("points at [pointing_at]", LOG_EMOTE) visible_message("[span_name("[src]")] points at [pointing_at].", span_notice("You point at [pointing_at].")) -/mob/living/verb/succumb(whispered as null) +/mob/living/verb/succumb(whispered as num|null) set hidden = TRUE if (!CAN_SUCCUMB(src)) if(HAS_TRAIT(src, TRAIT_SUCCUMB_OVERRIDE)) @@ -929,6 +929,8 @@ // I don't really care to keep this under a flag set_nutrition(NUTRITION_LEVEL_FED + 50) + overeatduration = 0 + satiety = 0 // These should be tracked by status effects losebreath = 0 @@ -1307,7 +1309,7 @@ return FALSE if(!Adjacent(target) && (target.loc != src) && !recursive_loc_check(src, target)) - if(issilicon(src) && !ispAI(src)) + if(HAS_SILICON_ACCESS(src) && !ispAI(src)) if(!(action_bitflags & ALLOW_SILICON_REACH)) // silicons can ignore range checks (except pAIs) to_chat(src, span_warning("You are too far away!")) return FALSE @@ -2257,6 +2259,9 @@ GLOBAL_LIST_EMPTY(fire_appearances) /mob/living/proc/is_face_visible() return TRUE +/// Sprite to show for photocopying mob butts +/mob/living/proc/get_butt_sprite() + return null ///Proc to modify the value of num_legs and hook behavior associated to this event. /mob/living/proc/set_num_legs(new_value) @@ -2324,27 +2329,6 @@ GLOBAL_LIST_EMPTY(fire_appearances) /mob/living/carbon/human/will_escape_storage() return TRUE -/// Sets the mob's hunger levels to a safe overall level. Useful for TRAIT_NOHUNGER species changes. -/mob/living/proc/set_safe_hunger_level() - // Nutrition reset and alert clearing. - nutrition = NUTRITION_LEVEL_FED - clear_alert(ALERT_NUTRITION) - satiety = 0 - - // Trait removal if obese - if(HAS_TRAIT_FROM(src, TRAIT_FAT, OBESITY)) - if(overeatduration >= (200 SECONDS)) - to_chat(src, span_notice("Your transformation restores your body's natural fitness!")) - - REMOVE_TRAIT(src, TRAIT_FAT, OBESITY) - remove_movespeed_modifier(/datum/movespeed_modifier/obesity) - update_worn_undersuit() - update_worn_oversuit() - - // Reset overeat duration. - overeatduration = 0 - - /// Changes the value of the [living/body_position] variable. Call this before set_lying_angle() /mob/living/proc/set_body_position(new_value) if(body_position == new_value) @@ -2595,10 +2579,9 @@ GLOBAL_LIST_EMPTY(fire_appearances) if(isnull(guardian_client)) return else if(guardian_client == "Poll Ghosts") - var/list/candidates = SSpolling.poll_ghost_candidates("Do you want to play as an admin created Guardian Spirit of [real_name]?", check_jobban = ROLE_PAI, poll_time = 10 SECONDS, ignore_category = POLL_IGNORE_HOLOPARASITE, pic_source = src, role_name_text = "guardian spirit") - if(LAZYLEN(candidates)) - var/mob/dead/observer/candidate = pick(candidates) - guardian_client = candidate.client + var/mob/chosen_one = SSpolling.poll_ghost_candidates("Do you want to play as an admin created [span_notice("Guardian Spirit")] of [span_danger(real_name)]?", check_jobban = ROLE_PAI, poll_time = 10 SECONDS, ignore_category = POLL_IGNORE_HOLOPARASITE, alert_pic = mutable_appearance('icons/mob/nonhuman-player/guardian.dmi', "magicexample"), jump_target = src, role_name_text = "guardian spirit", amount_to_pick = 1) + if(chosen_one) + guardian_client = chosen_one.client else tgui_alert(admin, "No ghost candidates.", "Guardian Controller") return @@ -2645,3 +2628,20 @@ GLOBAL_LIST_EMPTY(fire_appearances) end_look_down() else look_down() + +/** + * Totals the physical cash on the mob and returns the total. + */ +/mob/living/verb/tally_physical_credits() + //Here is all the possible non-ID payment methods. + var/list/counted_money = list() + var/physical_cash_total = 0 + for(var/obj/item/credit as anything in typecache_filter_list(get_all_contents(), GLOB.allowed_money)) //Coins, cash, and credits. + physical_cash_total += credit.get_item_credit_value() + counted_money += credit + + if(is_type_in_typecache(pulling, GLOB.allowed_money)) //Coins(Pulled). + var/obj/item/counted_credit = pulling + physical_cash_total += counted_credit.get_item_credit_value() + counted_money += counted_credit + return round(physical_cash_total) diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 562c54ae0b758..ec366f0f26ce8 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -500,7 +500,8 @@ ///As the name suggests, this should be called to apply electric shocks. /mob/living/proc/electrocute_act(shock_damage, source, siemens_coeff = 1, flags = NONE) - SEND_SIGNAL(src, COMSIG_LIVING_ELECTROCUTE_ACT, shock_damage, source, siemens_coeff, flags) + if(SEND_SIGNAL(src, COMSIG_LIVING_ELECTROCUTE_ACT, shock_damage, source, siemens_coeff, flags) & COMPONENT_LIVING_BLOCK_SHOCK) + return FALSE shock_damage *= siemens_coeff if((flags & SHOCK_TESLA) && HAS_TRAIT(src, TRAIT_TESLA_SHOCKIMMUNE)) return FALSE diff --git a/code/modules/mob/living/living_say.dm b/code/modules/mob/living/living_say.dm index 0751cc37d64a9..5ce6aa04379b3 100644 --- a/code/modules/mob/living/living_say.dm +++ b/code/modules/mob/living/living_say.dm @@ -94,17 +94,28 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list( return new_msg -/mob/living/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null, message_range = 7, datum/saymode/saymode = null) +/mob/living/say( + message, + bubble_type, + list/spans = list(), + sanitize = TRUE, + datum/language/language, + ignore_spam = FALSE, + forced, + filterproof = FALSE, + message_range = 7, + datum/saymode/saymode, + list/message_mods = list(), +) if(sanitize) message = trim(copytext_char(sanitize(message), 1, MAX_MESSAGE_LEN)) if(!message || message == "") return - var/list/message_mods = list() var/original_message = message message = get_message_mods(message, message_mods) saymode = SSradio.saymodes[message_mods[RADIO_KEY]] - if (!forced) + if (!forced && !saymode) message = check_for_custom_say_emote(message, message_mods) if(!message) @@ -206,7 +217,7 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list( message = "[randomnote] [message] [randomnote]" spans |= SPAN_SINGING - if(LAZYACCESS(message_mods,WHISPER_MODE)) // whisper away + if(message_mods[WHISPER_MODE]) // whisper away spans |= SPAN_ITALICS if(!message) @@ -222,6 +233,9 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list( return var/radio_return = radio(message, message_mods, spans, language)//roughly 27% of living/say()'s total cost + if(radio_return & NOPASS) + return TRUE + if(radio_return & ITALICS) spans |= SPAN_ITALICS if(radio_return & REDUCE_RANGE) @@ -229,8 +243,6 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list( if(!message_mods[WHISPER_MODE]) message_mods[WHISPER_MODE] = MODE_WHISPER message_mods[SAY_MOD_VERB] = say_mod(message, message_mods) - if(radio_return & NOPASS) - return TRUE //No screams in space, unless you're next to someone. var/turf/T = get_turf(src) @@ -426,38 +438,6 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list( /mob/proc/binarycheck() return FALSE -/mob/living/try_speak(message, ignore_spam = FALSE, forced = null, filterproof = FALSE) - if(!..()) - return FALSE - var/sigreturn = SEND_SIGNAL(src, COMSIG_LIVING_TRY_SPEECH, message, ignore_spam, forced) - if(sigreturn & COMPONENT_CAN_ALWAYS_SPEAK) - return TRUE - - if(sigreturn & COMPONENT_CANNOT_SPEAK) - return FALSE - - if(!can_speak()) - if(HAS_MIND_TRAIT(src, TRAIT_MIMING)) - to_chat(src, span_green("Your vow of silence prevents you from speaking!")) - else - to_chat(src, span_warning("You find yourself unable to speak!")) - return FALSE - - return TRUE - -/mob/living/can_speak(allow_mimes = FALSE) - if(!allow_mimes && HAS_MIND_TRAIT(src, TRAIT_MIMING)) - return FALSE - - if(HAS_TRAIT(src, TRAIT_MUTE)) - return FALSE - - if(is_muzzled()) - return FALSE - - return TRUE - - /** * Treats the passed message with things that may modify speech (stuttering, slurring etc). * diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 041d964f5dc50..a7d5b08579930 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -699,7 +699,7 @@ if("Station Member") var/list/personnel_list = list() - for(var/datum/record/crew/record in GLOB.manifest.locked)//Look in data core locked. + for(var/datum/record/locked/record in GLOB.manifest.locked)//Look in data core locked. personnel_list["[record.name]: [record.rank]"] = record.character_appearance//Pull names, rank, and image. if(!length(personnel_list)) diff --git a/code/modules/mob/living/silicon/ai/ai_say.dm b/code/modules/mob/living/silicon/ai/ai_say.dm index 643149dc6163a..5434a44cc1572 100644 --- a/code/modules/mob/living/silicon/ai/ai_say.dm +++ b/code/modules/mob/living/silicon/ai/ai_say.dm @@ -1,5 +1,17 @@ -/mob/living/silicon/ai/say(message, bubble_type,list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null, message_range = 7, datum/saymode/saymode = null) - if(parent && istype(parent) && parent.stat != DEAD) //If there is a defined "parent" AI, it is actually an AI, and it is alive, anything the AI tries to say is said by the parent instead. +/mob/living/silicon/ai/say( + message, + bubble_type, + list/spans = list(), + sanitize = TRUE, + datum/language/language, + ignore_spam = FALSE, + forced, + filterproof = FALSE, + message_range = 7, + datum/saymode/saymode, + list/message_mods = list(), +) + if(istype(parent) && parent.stat != DEAD) //If there is a defined "parent" AI, it is actually an AI, and it is alive, anything the AI tries to say is said by the parent instead. return parent.say(arglist(args)) return ..() diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index f73336fe963d7..b527645bc596a 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -285,40 +285,6 @@ /mob/living/silicon/robot/proc/after_righted(mob/user) return -/mob/living/silicon/robot/proc/allowed(mob/M) - //check if it doesn't require any access at all - if(check_access(null)) - return TRUE - if(ishuman(M)) - var/mob/living/carbon/human/H = M - //if they are holding or wearing a card that has access, that works - if(check_access(H.get_active_held_item()) || check_access(H.wear_id)) - return TRUE - else if(isalien(M)) - var/mob/living/carbon/george = M - //they can only hold things :( - if(isitem(george.get_active_held_item())) - return check_access(george.get_active_held_item()) - return FALSE - -/mob/living/silicon/robot/proc/check_access(obj/item/card/id/I) - if(!istype(req_access, /list)) //something's very wrong - return TRUE - - var/list/L = req_access - if(!L.len) //no requirements - return TRUE - - if(!isidcard(I) && isitem(I)) - I = I.GetID() - - if(!I || !I.access) //not ID or no access - return FALSE - for(var/req in req_access) - if(!(req in I.access)) //doesn't have this access - return FALSE - return TRUE - /mob/living/silicon/robot/regenerate_icons() return update_icons() diff --git a/code/modules/mob/living/silicon/robot/robot_defines.dm b/code/modules/mob/living/silicon/robot/robot_defines.dm index 602815006250e..12f557962f672 100644 --- a/code/modules/mob/living/silicon/robot/robot_defines.dm +++ b/code/modules/mob/living/silicon/robot/robot_defines.dm @@ -4,7 +4,6 @@ * Definitions for /mob/living/silicon/robot and its children, including AI shells. * */ - /mob/living/silicon/robot name = "Cyborg" real_name = "Cyborg" @@ -105,7 +104,6 @@ ///Random serial number generated for each cyborg upon its initialization var/ident = 0 var/locked = TRUE - var/list/req_access = list(ACCESS_ROBOTICS) ///Whether the robot has no charge left. var/low_power_mode = FALSE diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index 3cd9d45f5c107..b43a625b1e482 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -79,6 +79,7 @@ ) add_traits(traits_to_apply, ROUNDSTART_TRAIT) + RegisterSignal(src, COMSIG_LIVING_ELECTROCUTE_ACT, PROC_REF(on_silicon_shocked)) /mob/living/silicon/Destroy() QDEL_NULL(radio) @@ -90,6 +91,14 @@ GLOB.silicon_mobs -= src return ..() +/mob/living/silicon/proc/on_silicon_shocked(datum/source, shock_damage, shock_source, siemens_coeff, flags) + SIGNAL_HANDLER + for(var/mob/living/living_mob in buckled_mobs) + unbuckle_mob(living_mob) + living_mob.electrocute_act(shock_damage/100, shock_source, siemens_coeff, flags) //Hard metal shell conducts! + + return COMPONENT_LIVING_BLOCK_SHOCK //So borgs don't die trying to fix wiring + /mob/living/silicon/proc/create_modularInterface() if(!modularInterface) modularInterface = new /obj/item/modular_computer/pda/silicon(src) @@ -432,6 +441,9 @@ /mob/living/silicon/on_standing_up() return // Silicons are always standing by default. +/mob/living/silicon/get_butt_sprite() + return BUTT_SPRITE_QR_CODE + /** * Records an IC event log entry in the cyborg's internal tablet. * diff --git a/code/modules/mob/living/silicon/silicon_defense.dm b/code/modules/mob/living/silicon/silicon_defense.dm index 175c335385057..b7a669b4a2e62 100644 --- a/code/modules/mob/living/silicon/silicon_defense.dm +++ b/code/modules/mob/living/silicon/silicon_defense.dm @@ -101,13 +101,6 @@ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN return ..() -/mob/living/silicon/electrocute_act(shock_damage, source, siemens_coeff = 1, flags = NONE) - if(buckled_mobs) - for(var/mob/living/M in buckled_mobs) - unbuckle_mob(M) - M.electrocute_act(shock_damage/100, source, siemens_coeff, flags) //Hard metal shell conducts! - return 0 //So borgs they don't die trying to fix wiring - /mob/living/silicon/emp_act(severity) . = ..() to_chat(src, span_danger("Warning: Electromagnetic pulse detected.")) diff --git a/code/modules/mob/living/silicon/silicon_say.dm b/code/modules/mob/living/silicon/silicon_say.dm index 54c4a4f82d1c6..b1b8b4bc88010 100644 --- a/code/modules/mob/living/silicon/silicon_say.dm +++ b/code/modules/mob/living/silicon/silicon_say.dm @@ -8,6 +8,9 @@ var/mob/living/silicon/player = src designation = trim_left(player.designation + " " + player.job) + if(HAS_TRAIT(mind, DISPLAYS_JOB_IN_BINARY)) + designation = mind.assigned_role.title + if(isAI(src)) // AIs are loud and ugly spans |= SPAN_COMMAND @@ -17,6 +20,11 @@ spans ) + var/namepart = name + // If carbon, use voice to account for voice changers + if(iscarbon(src)) + namepart = GetVoice() + for(var/mob/M in GLOB.player_list) if(M.binarycheck()) if(isAI(M)) @@ -24,7 +32,7 @@ M, span_binarysay("\ Robotic Talk, \ - [span_name("[name] ([designation])")] \ + [span_name("[namepart] ([designation])")] \ [quoted_message]\ "), avoid_highlighting = src == M @@ -34,7 +42,7 @@ M, span_binarysay("\ Robotic Talk, \ - [span_name("[name]")] [quoted_message]\ + [span_name("[namepart]")] [quoted_message]\ "), avoid_highlighting = src == M ) @@ -56,7 +64,7 @@ span_binarysay("\ [follow_link] \ Robotic Talk, \ - [span_name("[name]")] [quoted_message]\ + [span_name("[namepart]")] [quoted_message]\ "), avoid_highlighting = src == M ) diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm index ff304332c8df6..c64c13b2107ce 100644 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -25,16 +25,13 @@ faction = list(FACTION_NEUTRAL, FACTION_SILICON, FACTION_TURRET) light_system = OVERLAY_LIGHT light_range = 3 - light_power = 0.9 + light_power = 0.6 del_on_death = TRUE + req_one_access = list(ACCESS_ROBOTICS) - ///Will other (noncommissioned) bots salute this bot? - var/commissioned = FALSE ///Cooldown between salutations for commissioned bots COOLDOWN_DECLARE(next_salute_check) - ///Access required to access this Bot's maintenance protocols - var/maints_access_required = list(ACCESS_ROBOTICS) ///The Robot arm attached to this robot - has a 50% chance to drop on death. var/robot_arm = /obj/item/bodypart/arm/right/robot ///The inserted (if any) pAI in this bot. @@ -50,7 +47,7 @@ ///Bot-related mode flags on the Bot indicating how they will act. BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_ROUNDSTART_POSSESSION var/bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_ROUNDSTART_POSSESSION - ///Bot-related cover flags on the Bot to deal with what has been done to their cover, including emagging. BOT_COVER_OPEN | BOT_COVER_LOCKED | BOT_COVER_EMAGGED | BOT_COVER_HACKED + ///Bot-related cover flags on the Bot to deal with what has been done to their cover, including emagging. BOT_COVER_MAINTS_OPEN | BOT_COVER_LOCKED | BOT_COVER_EMAGGED | BOT_COVER_HACKED var/bot_cover_flags = BOT_COVER_LOCKED ///Small name of what the bot gets messed with when getting hacked/emagged. @@ -285,25 +282,10 @@ return fully_replace_character_name(real_name, new_name) -/mob/living/simple_animal/bot/proc/check_access(mob/living/user, obj/item/card/id) - if(user.has_unlimited_silicon_privilege || isAdminGhostAI(user)) // Silicon and Admins always have access. - return TRUE - if(!maints_access_required) // No requirements to access it. - return TRUE +/mob/living/simple_animal/bot/allowed(mob/living/user) if(!(bot_cover_flags & BOT_COVER_LOCKED)) // Unlocked. return TRUE - if(!istype(user)) // Non-living mobs shouldn't be manipulating bots (like observes using the botkeeper UI). - return FALSE - - var/obj/item/card/id/used_id = id || user.get_idcard(TRUE) - - if(!used_id || !used_id.access) - return FALSE - - for(var/requested_access in maints_access_required) - if(requested_access in used_id.access) - return TRUE - return FALSE + return ..() /mob/living/simple_animal/bot/bee_friendly() return TRUE @@ -327,7 +309,7 @@ bot_cover_flags &= ~BOT_COVER_LOCKED balloon_alert(user, "cover unlocked") return TRUE - if(!(bot_cover_flags & BOT_COVER_LOCKED) && bot_cover_flags & BOT_COVER_OPEN) //Bot panel is unlocked by ID or emag, and the panel is screwed open. Ready for emagging. + if(!(bot_cover_flags & BOT_COVER_LOCKED) && bot_cover_flags & BOT_COVER_MAINTS_OPEN) //Bot panel is unlocked by ID or emag, and the panel is screwed open. Ready for emagging. bot_cover_flags |= BOT_COVER_EMAGGED bot_cover_flags &= ~BOT_COVER_LOCKED //Manually emagging the bot locks out the panel. bot_mode_flags &= ~BOT_MODE_REMOTE_ENABLED //Manually emagging the bot also locks the AI from controlling it. @@ -351,16 +333,16 @@ . += "[src]'s parts look very loose!" else . += "[src] is in pristine condition." - . += span_notice("Its maintenance panel is [bot_cover_flags & BOT_COVER_OPEN ? "open" : "closed"].") - . += span_info("You can use a screwdriver to [bot_cover_flags & BOT_COVER_OPEN ? "close" : "open"] it.") - if(bot_cover_flags & BOT_COVER_OPEN) + . += span_notice("Its maintenance panel is [bot_cover_flags & BOT_COVER_MAINTS_OPEN ? "open" : "closed"].") + . += span_info("You can use a screwdriver to [bot_cover_flags & BOT_COVER_MAINTS_OPEN ? "close" : "open"] it.") + if(bot_cover_flags & BOT_COVER_MAINTS_OPEN) . += span_notice("Its control panel is [bot_cover_flags & BOT_COVER_LOCKED ? "locked" : "unlocked"].") - var/is_sillycone = issilicon(user) + var/is_sillycone = HAS_SILICON_ACCESS(user) if(!(bot_cover_flags & BOT_COVER_EMAGGED) && (is_sillycone || user.Adjacent(src))) . += span_info("Alt-click [is_sillycone ? "" : "or use your ID on "]it to [bot_cover_flags & BOT_COVER_LOCKED ? "un" : ""]lock its control panel.") if(paicard) . += span_notice("It has a pAI device installed.") - if(!(bot_cover_flags & BOT_COVER_OPEN)) + if(!(bot_cover_flags & BOT_COVER_MAINTS_OPEN)) . += span_info("You can use a hemostat to remove it.") /mob/living/simple_animal/bot/adjustHealth(amount, updating_health = TRUE, forced = FALSE) @@ -393,10 +375,10 @@ if(!(bot_mode_flags & BOT_MODE_ON) || client) return FALSE - if(commissioned && COOLDOWN_FINISHED(src, next_salute_check)) + if(HAS_TRAIT(src, TRAIT_COMMISSIONED) && COOLDOWN_FINISHED(src, next_salute_check)) COOLDOWN_START(src, next_salute_check, BOT_COMMISSIONED_SALUTE_DELAY) for(var/mob/living/simple_animal/bot/B in view(5, src)) - if(!B.commissioned && B.bot_mode_flags & BOT_MODE_ON) + if(!HAS_TRAIT(B, TRAIT_COMMISSIONED) && B.bot_mode_flags & BOT_MODE_ON) visible_message("[B] performs an elaborate salute for [src]!") break @@ -440,10 +422,10 @@ if(bot_cover_flags & BOT_COVER_EMAGGED) to_chat(user, span_danger("ERROR")) return - if(bot_cover_flags & BOT_COVER_OPEN) + if(bot_cover_flags & BOT_COVER_MAINTS_OPEN) to_chat(user, span_warning("Please close the access panel before [bot_cover_flags & BOT_COVER_LOCKED ? "un" : ""]locking it.")) return - if(!check_access(user)) + if(!allowed(user)) to_chat(user, span_warning("Access denied.")) return bot_cover_flags ^= BOT_COVER_LOCKED @@ -456,8 +438,8 @@ return ITEM_INTERACT_SUCCESS tool.play_tool_sound(src) - bot_cover_flags ^= BOT_COVER_OPEN - to_chat(user, span_notice("The maintenance panel is now [bot_cover_flags & BOT_COVER_OPEN ? "opened" : "closed"].")) + bot_cover_flags ^= BOT_COVER_MAINTS_OPEN + to_chat(user, span_notice("The maintenance panel is now [bot_cover_flags & BOT_COVER_MAINTS_OPEN ? "opened" : "closed"].")) return ITEM_INTERACT_SUCCESS /mob/living/simple_animal/bot/welder_act(mob/living/user, obj/item/tool) @@ -468,7 +450,7 @@ if(health >= maxHealth) to_chat(user, span_warning("[src] does not need a repair!")) return ITEM_INTERACT_SUCCESS - if(!(bot_cover_flags & BOT_COVER_OPEN)) + if(!(bot_cover_flags & BOT_COVER_MAINTS_OPEN)) to_chat(user, span_warning("Unable to repair with the maintenance panel closed!")) return ITEM_INTERACT_SUCCESS @@ -485,7 +467,7 @@ insertpai(user, attacking_item) return if(attacking_item.tool_behaviour == TOOL_HEMOSTAT && paicard) - if(bot_cover_flags & BOT_COVER_OPEN) + if(bot_cover_flags & BOT_COVER_MAINTS_OPEN) balloon_alert(user, "open the access panel!") else balloon_alert(user, "removing pAI...") @@ -981,18 +963,18 @@ Pass a positive integer as an argument to override a bot's default speed. /mob/living/simple_animal/bot/ui_data(mob/user) var/list/data = list() - data["can_hack"] = (issilicon(user) || isAdminGhostAI(user)) + data["can_hack"] = HAS_SILICON_ACCESS(user) data["custom_controls"] = list() data["emagged"] = bot_cover_flags & BOT_COVER_EMAGGED - data["has_access"] = check_access(user) + data["has_access"] = allowed(user) data["locked"] = bot_cover_flags & BOT_COVER_LOCKED data["settings"] = list() - if(!(bot_cover_flags & BOT_COVER_LOCKED) || issilicon(user) || isAdminGhostAI(user)) + if(!(bot_cover_flags & BOT_COVER_LOCKED) || HAS_SILICON_ACCESS(user)) data["settings"]["pai_inserted"] = !!paicard data["settings"]["allow_possession"] = bot_mode_flags & BOT_MODE_CAN_BE_SAPIENT data["settings"]["possession_enabled"] = can_be_possessed data["settings"]["airplane_mode"] = !(bot_mode_flags & BOT_MODE_REMOTE_ENABLED) - data["settings"]["maintenance_lock"] = !(bot_cover_flags & BOT_COVER_OPEN) + data["settings"]["maintenance_lock"] = !(bot_cover_flags & BOT_COVER_MAINTS_OPEN) data["settings"]["power"] = bot_mode_flags & BOT_MODE_ON data["settings"]["patrol_station"] = bot_mode_flags & BOT_MODE_AUTOPATROL return data @@ -1002,7 +984,8 @@ Pass a positive integer as an argument to override a bot's default speed. . = ..() if(.) return - if(!check_access(usr)) + var/mob/user = usr + if(!allowed(user)) to_chat(usr, span_warning("Access denied.")) return @@ -1016,14 +999,14 @@ Pass a positive integer as an argument to override a bot's default speed. else turn_on() if("maintenance") - bot_cover_flags ^= BOT_COVER_OPEN + bot_cover_flags ^= BOT_COVER_MAINTS_OPEN if("patrol") bot_mode_flags ^= BOT_MODE_AUTOPATROL bot_reset() if("airplane") bot_mode_flags ^= BOT_MODE_REMOTE_ENABLED if("hack") - if(!(issilicon(usr) || isAdminGhostAI(usr))) + if(!HAS_SILICON_ACCESS(usr)) return if(!(bot_cover_flags & BOT_COVER_EMAGGED)) bot_cover_flags |= (BOT_COVER_EMAGGED|BOT_COVER_HACKED|BOT_COVER_LOCKED) @@ -1068,7 +1051,7 @@ Pass a positive integer as an argument to override a bot's default speed. if(bot_cover_flags & BOT_COVER_EMAGGED) //An emagged bot cannot be controlled by humans, silicons can if one hacked it. if(!(bot_cover_flags & BOT_COVER_HACKED)) //Manually emagged by a human - access denied to all. return TRUE - else if(!issilicon(user) && !isAdminGhostAI(user)) //Bot is hacked, so only silicons and admins are allowed access. + else if(!HAS_SILICON_ACCESS(user)) //Bot is hacked, so only silicons and admins are allowed access. return TRUE return FALSE @@ -1080,7 +1063,7 @@ Pass a positive integer as an argument to override a bot's default speed. if(key) balloon_alert(user, "personality already present!") return - if(bot_cover_flags & BOT_COVER_LOCKED || !(bot_cover_flags & BOT_COVER_OPEN)) + if(bot_cover_flags & BOT_COVER_LOCKED || !(bot_cover_flags & BOT_COVER_MAINTS_OPEN)) balloon_alert(user, "slot inaccessible!") return if(!(bot_mode_flags & BOT_MODE_CAN_BE_SAPIENT)) @@ -1137,7 +1120,7 @@ Pass a positive integer as an argument to override a bot's default speed. /// Ejects the pAI remotely. /mob/living/simple_animal/bot/proc/ejectpairemote(mob/user) - if(!check_access(user) || !paicard) + if(!allowed(user) || !paicard) return speak("Ejecting personality chip.", radio_channel) ejectpai(user) diff --git a/code/modules/mob/living/simple_animal/bot/ed209bot.dm b/code/modules/mob/living/simple_animal/bot/ed209bot.dm index e91b532d96c8d..6c0cd6d16ab55 100644 --- a/code/modules/mob/living/simple_animal/bot/ed209bot.dm +++ b/code/modules/mob/living/simple_animal/bot/ed209bot.dm @@ -2,6 +2,7 @@ name = "\improper ED-209 Security Robot" desc = "A security robot. He looks less than thrilled." icon_state = "ed209" + light_color = "#f84e4e" density = TRUE health = 100 maxHealth = 100 diff --git a/code/modules/mob/living/simple_animal/bot/firebot.dm b/code/modules/mob/living/simple_animal/bot/firebot.dm index da07d6f0efd17..f4487bfc9d9d7 100644 --- a/code/modules/mob/living/simple_animal/bot/firebot.dm +++ b/code/modules/mob/living/simple_animal/bot/firebot.dm @@ -9,12 +9,14 @@ desc = "A little fire extinguishing bot. He looks rather anxious." icon = 'icons/mob/silicon/aibots.dmi' icon_state = "firebot1" + light_color = "#8cffc9" + light_power = 0.8 density = FALSE anchored = FALSE health = 25 maxHealth = 25 - maints_access_required = list(ACCESS_ROBOTICS, ACCESS_CONSTRUCTION) + req_one_access = list(ACCESS_ROBOTICS, ACCESS_CONSTRUCTION) radio_key = /obj/item/encryptionkey/headset_eng radio_channel = RADIO_CHANNEL_ENGINEERING bot_type = FIRE_BOT @@ -140,7 +142,7 @@ // Variables sent to TGUI /mob/living/simple_animal/bot/firebot/ui_data(mob/user) var/list/data = ..() - if(!(bot_cover_flags & BOT_COVER_LOCKED) || issilicon(user) || isAdminGhostAI(user)) + if(!(bot_cover_flags & BOT_COVER_LOCKED) || HAS_SILICON_ACCESS(user)) data["custom_controls"]["extinguish_fires"] = extinguish_fires data["custom_controls"]["extinguish_people"] = extinguish_people data["custom_controls"]["stationary_mode"] = stationary_mode @@ -149,7 +151,7 @@ // Actions received from TGUI /mob/living/simple_animal/bot/firebot/ui_act(action, params) . = ..() - if(. || (bot_cover_flags & BOT_COVER_LOCKED && !usr.has_unlimited_silicon_privilege)) + if(. || (bot_cover_flags & BOT_COVER_LOCKED && !HAS_SILICON_ACCESS(usr))) return switch(action) diff --git a/code/modules/mob/living/simple_animal/bot/floorbot.dm b/code/modules/mob/living/simple_animal/bot/floorbot.dm index e6554b4caeac9..94713a69a12e8 100644 --- a/code/modules/mob/living/simple_animal/bot/floorbot.dm +++ b/code/modules/mob/living/simple_animal/bot/floorbot.dm @@ -16,7 +16,7 @@ health = 25 maxHealth = 25 - maints_access_required = list(ACCESS_ROBOTICS, ACCESS_CONSTRUCTION) + req_one_access = list(ACCESS_ROBOTICS, ACCESS_CONSTRUCTION) radio_key = /obj/item/encryptionkey/headset_eng radio_channel = RADIO_CHANNEL_ENGINEERING bot_type = FLOOR_BOT @@ -125,7 +125,7 @@ // Variables sent to TGUI /mob/living/simple_animal/bot/floorbot/ui_data(mob/user) var/list/data = ..() - if(!(bot_cover_flags & BOT_COVER_LOCKED) || issilicon(user) || isAdminGhostAI(user)) + if(!(bot_cover_flags & BOT_COVER_LOCKED) || HAS_SILICON_ACCESS(user)) data["custom_controls"]["tile_hull"] = autotile data["custom_controls"]["place_tiles"] = placetiles data["custom_controls"]["place_custom"] = replacetiles @@ -142,7 +142,7 @@ // Actions received from TGUI /mob/living/simple_animal/bot/floorbot/ui_act(action, params) . = ..() - if(. || (bot_cover_flags & BOT_COVER_LOCKED && !usr.has_unlimited_silicon_privilege)) + if(. || (bot_cover_flags & BOT_COVER_LOCKED && !HAS_SILICON_ACCESS(usr))) return switch(action) diff --git a/code/modules/mob/living/simple_animal/bot/honkbot.dm b/code/modules/mob/living/simple_animal/bot/honkbot.dm index 1c77043eb1ff7..711995833172f 100644 --- a/code/modules/mob/living/simple_animal/bot/honkbot.dm +++ b/code/modules/mob/living/simple_animal/bot/honkbot.dm @@ -2,10 +2,12 @@ name = "\improper Honkbot" desc = "A little robot. It looks happy with its bike horn." icon_state = "honkbot" + light_color = "#e084f7" + light_power = 1 damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, STAMINA = 0, OXY = 0) combat_mode = FALSE - maints_access_required = list(ACCESS_ROBOTICS, ACCESS_THEATRE) + req_one_access = list(ACCESS_ROBOTICS, ACCESS_THEATRE) radio_key = /obj/item/encryptionkey/headset_service //doesn't have security key radio_channel = RADIO_CHANNEL_SERVICE //Doesn't even use the radio anyway. bot_type = HONK_BOT diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm index 7f13a1954ab84..71292405faa7d 100644 --- a/code/modules/mob/living/simple_animal/bot/mulebot.dm +++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm @@ -13,6 +13,8 @@ name = "\improper MULEbot" desc = "A Multiple Utility Load Effector bot." icon_state = "mulebot0" + light_color = "#ffcc99" + light_power = 0.8 density = TRUE move_resist = MOVE_FORCE_STRONG animate_movement = SLIDE_STEPS @@ -26,7 +28,7 @@ buckle_prevents_pull = TRUE // No pulling loaded shit bot_mode_flags = ~BOT_MODE_ROUNDSTART_POSSESSION - maints_access_required = list(ACCESS_ROBOTICS, ACCESS_CARGO) + req_one_access = list(ACCESS_ROBOTICS, ACCESS_CARGO) radio_key = /obj/item/encryptionkey/headset_cargo radio_channel = RADIO_CHANNEL_SUPPLY bot_type = MULE_BOT @@ -99,7 +101,7 @@ /mob/living/simple_animal/bot/mulebot/examine(mob/user) . = ..() - if(bot_cover_flags & BOT_COVER_OPEN) + if(bot_cover_flags & BOT_COVER_MAINTS_OPEN) if(cell) . += span_notice("It has \a [cell] installed.") . += span_info("You can use a crowbar to remove it.") @@ -129,13 +131,13 @@ return cell && cell.charge > 0 && (!wires.is_cut(WIRE_POWER1) && !wires.is_cut(WIRE_POWER2)) /mob/living/simple_animal/bot/mulebot/attack_hand(mob/living/carbon/human/user, list/modifiers) - if(bot_cover_flags & BOT_COVER_OPEN && !isAI(user)) + if(bot_cover_flags & BOT_COVER_MAINTS_OPEN && !HAS_AI_ACCESS(user)) wires.interact(user) return - if(wires.is_cut(WIRE_RX) && isAI(user)) + if(wires.is_cut(WIRE_RX) && HAS_AI_ACCESS(user)) return - . = ..() + return ..() /mob/living/simple_animal/bot/mulebot/proc/set_id(new_id) id = new_id @@ -161,7 +163,7 @@ update_appearance() /mob/living/simple_animal/bot/mulebot/crowbar_act(mob/living/user, obj/item/tool) - if(!(bot_cover_flags & BOT_COVER_OPEN) || user.combat_mode) + if(!(bot_cover_flags & BOT_COVER_MAINTS_OPEN) || user.combat_mode) return if(!cell) to_chat(user, span_warning("[src] doesn't have a power cell!")) @@ -180,7 +182,7 @@ return ITEM_INTERACT_SUCCESS /mob/living/simple_animal/bot/mulebot/attackby(obj/item/I, mob/living/user, params) - if(istype(I, /obj/item/stock_parts/cell) && bot_cover_flags & BOT_COVER_OPEN) + if(istype(I, /obj/item/stock_parts/cell) && bot_cover_flags & BOT_COVER_MAINTS_OPEN) if(cell) to_chat(user, span_warning("[src] already has a power cell!")) return TRUE @@ -193,7 +195,7 @@ span_notice("You insert [cell] into [src]."), ) return TRUE - else if(is_wire_tool(I) && bot_cover_flags & BOT_COVER_OPEN) + else if(is_wire_tool(I) && bot_cover_flags & BOT_COVER_MAINTS_OPEN) return attack_hand(user) else if(load && ismob(load)) // chance to knock off rider if(prob(1 + I.force * 2)) @@ -209,7 +211,7 @@ /mob/living/simple_animal/bot/mulebot/emag_act(mob/user, obj/item/card/emag/emag_card) if(!(bot_cover_flags & BOT_COVER_EMAGGED)) bot_cover_flags |= BOT_COVER_EMAGGED - if(!(bot_cover_flags & BOT_COVER_OPEN)) + if(!(bot_cover_flags & BOT_COVER_MAINTS_OPEN)) bot_cover_flags ^= BOT_COVER_LOCKED balloon_alert(user, "controls [bot_cover_flags & BOT_COVER_LOCKED ? "locked" : "unlocked"]") flick("[base_icon]-emagged", src) @@ -222,7 +224,7 @@ /mob/living/simple_animal/bot/mulebot/update_overlays() . = ..() - if(bot_cover_flags & BOT_COVER_OPEN) + if(bot_cover_flags & BOT_COVER_MAINTS_OPEN) . += "[base_icon]-hatch" if(!load || ismob(load)) //mob offsets and such are handled by the riding component / buckling return @@ -263,7 +265,7 @@ var/list/data = list() data["on"] = bot_mode_flags & BOT_MODE_ON data["locked"] = bot_cover_flags & BOT_COVER_LOCKED - data["siliconUser"] = user.has_unlimited_silicon_privilege + data["siliconUser"] = HAS_SILICON_ACCESS(user) data["mode"] = mode ? "[mode]" : "Ready" data["modeStatus"] = "" switch(mode) @@ -290,18 +292,18 @@ /mob/living/simple_animal/bot/mulebot/ui_act(action, params) . = ..() - if(. || (bot_cover_flags & BOT_COVER_LOCKED && !usr.has_unlimited_silicon_privilege)) + if(. || (bot_cover_flags & BOT_COVER_LOCKED && !HAS_SILICON_ACCESS(usr))) return switch(action) if("lock") - if(usr.has_unlimited_silicon_privilege) + if(HAS_SILICON_ACCESS(usr)) bot_cover_flags ^= BOT_COVER_LOCKED return TRUE if("on") if(bot_mode_flags & BOT_MODE_ON) turn_off() - else if(bot_cover_flags & BOT_COVER_OPEN) + else if(bot_cover_flags & BOT_COVER_MAINTS_OPEN) to_chat(usr, span_warning("[name]'s maintenance panel is open!")) return else if(cell) diff --git a/code/modules/mob/living/simple_animal/bot/secbot.dm b/code/modules/mob/living/simple_animal/bot/secbot.dm index 582f51fb3aa9c..2f9c3f25f3580 100644 --- a/code/modules/mob/living/simple_animal/bot/secbot.dm +++ b/code/modules/mob/living/simple_animal/bot/secbot.dm @@ -3,6 +3,8 @@ desc = "A little security robot. He looks less than thrilled." icon = 'icons/mob/silicon/aibots.dmi' icon_state = "secbot" + light_color = "#f56275" + light_power = 0.8 density = FALSE anchored = FALSE health = 25 @@ -11,7 +13,7 @@ pass_flags = PASSMOB | PASSFLAPS combat_mode = TRUE - maints_access_required = list(ACCESS_SECURITY) + req_one_access = list(ACCESS_SECURITY) radio_key = /obj/item/encryptionkey/secbot //AI Priv + Security radio_channel = RADIO_CHANNEL_SECURITY //Security channel bot_type = SEC_BOT @@ -30,6 +32,8 @@ BEEPSKY_VOICED_SECURE_DAY = 'sound/voice/beepsky/secureday.ogg', ) + ///Whether this secbot is considered 'commissioned' and given the trait on Initialize. + var/commissioned = FALSE ///The type of baton this Secbot will use var/baton_type = /obj/item/melee/baton/security ///The type of cuffs we use on criminals after making arrests @@ -71,6 +75,11 @@ desc = "It's Officer Beepsky! Powered by a potato and a shot of whiskey, and with a sturdier reinforced chassis, too." health = 45 +/mob/living/simple_animal/bot/secbot/beepsky/officer/Initialize(mapload) + . = ..() + // Beepsky hates people scanning them + RegisterSignal(src, COMSIG_MOVABLE_SPY_STEALING, PROC_REF(retaliate_async)) + /mob/living/simple_animal/bot/secbot/beepsky/ofitser name = "Prison Ofitser" desc = "Powered by the tears and sweat of laborers." @@ -95,6 +104,7 @@ /mob/living/simple_animal/bot/secbot/pingsky name = "Officer Pingsky" desc = "It's Officer Pingsky! Delegated to satellite guard duty for harbouring anti-human sentiment." + light_color = "#62baf5" radio_channel = RADIO_CHANNEL_AI_PRIVATE bot_mode_flags = ~(BOT_MODE_CAN_BE_SAPIENT|BOT_MODE_AUTOPATROL) security_mode_flags = SECBOT_DECLARE_ARRESTS | SECBOT_CHECK_IDS | SECBOT_CHECK_RECORDS @@ -118,6 +128,8 @@ . = ..() weapon = new baton_type() update_appearance(UPDATE_ICON) + if(commissioned) + ADD_TRAIT(src, TRAIT_COMMISSIONED, INNATE_TRAIT) // Doing this hurts my soul, but simplebot access reworks are for another day. var/datum/id_trim/job/det_trim = SSid_access.trim_singletons_by_path[/datum/id_trim/job/detective] @@ -168,7 +180,7 @@ // Variables sent to TGUI /mob/living/simple_animal/bot/secbot/ui_data(mob/user) var/list/data = ..() - if(!(bot_cover_flags & BOT_COVER_LOCKED) || issilicon(user) || isAdminGhostAI(user)) + if(!(bot_cover_flags & BOT_COVER_LOCKED) || HAS_SILICON_ACCESS(user)) data["custom_controls"]["check_id"] = security_mode_flags & SECBOT_CHECK_IDS data["custom_controls"]["check_weapons"] = security_mode_flags & SECBOT_CHECK_WEAPONS data["custom_controls"]["check_warrants"] = security_mode_flags & SECBOT_CHECK_RECORDS @@ -179,7 +191,7 @@ // Actions received from TGUI /mob/living/simple_animal/bot/secbot/ui_act(action, params) . = ..() - if(. || (bot_cover_flags & BOT_COVER_LOCKED && !usr.has_unlimited_silicon_privilege)) + if(. || (bot_cover_flags & BOT_COVER_LOCKED && !HAS_SILICON_ACCESS(usr))) return switch(action) @@ -194,6 +206,11 @@ if("arrest_alert") security_mode_flags ^= SECBOT_DECLARE_ARRESTS +/mob/living/simple_animal/bot/secbot/proc/retaliate_async(datum/source, mob/user, ...) + SIGNAL_HANDLER + + INVOKE_ASYNC(src, PROC_REF(retaliate), user) + /mob/living/simple_animal/bot/secbot/proc/retaliate(mob/living/carbon/human/attacking_human) var/judgement_criteria = judgement_criteria() threatlevel = attacking_human.assess_threat(judgement_criteria) diff --git a/code/modules/mob/living/simple_animal/bot/vibebot.dm b/code/modules/mob/living/simple_animal/bot/vibebot.dm index d0d550f77379a..582b1b5371da5 100644 --- a/code/modules/mob/living/simple_animal/bot/vibebot.dm +++ b/code/modules/mob/living/simple_animal/bot/vibebot.dm @@ -10,8 +10,8 @@ maxHealth = 25 pass_flags = PASSMOB | PASSFLAPS light_system = OVERLAY_LIGHT - light_range = 7 - light_power = 3 + light_range = 6 + light_power = 2 hackables = "vibing scanners" radio_key = /obj/item/encryptionkey/headset_service diff --git a/code/modules/mob/living/simple_animal/hostile/alien.dm b/code/modules/mob/living/simple_animal/hostile/alien.dm index 593bf29535edc..8ce6d28f2ab62 100644 --- a/code/modules/mob/living/simple_animal/hostile/alien.dm +++ b/code/modules/mob/living/simple_animal/hostile/alien.dm @@ -110,6 +110,9 @@ egg_cooldown = initial(egg_cooldown) LayEggs() +/mob/living/simple_animal/hostile/alien/get_butt_sprite() + return BUTT_SPRITE_XENOMORPH + /mob/living/simple_animal/hostile/alien/proc/SpreadPlants() if(!isturf(loc) || isspaceturf(loc)) return diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm index ac9031f59c33f..2911c1b174cd0 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -166,7 +166,9 @@ layer = FLY_LAYER plane = ABOVE_GAME_PLANE light_system = OVERLAY_LIGHT - light_range = 2 + light_range = 2.5 + light_power = 1.2 + light_color = "#ffff66" duration = 8 var/target @@ -496,6 +498,7 @@ if(isanimal_or_basicmob(loc)) holder_animal = loc RegisterSignal(holder_animal, COMSIG_LIVING_DEATH, PROC_REF(on_holder_animal_death)) + AddElement(/datum/element/empprotection, EMP_PROTECT_ALL) /obj/structure/closet/stasis/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) . = ..() @@ -524,9 +527,6 @@ return ..() return ..() -/obj/structure/closet/stasis/emp_act() - return - /obj/structure/closet/stasis/ex_act() return FALSE diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm index 342713ee960a5..6632dedd2342b 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm @@ -88,8 +88,8 @@ Difficulty: Hard /datum/action/innate/megafauna_attack/shockwave_scream name = "Shockwave Scream" - button_icon = 'icons/turf/walls/wall.dmi' - button_icon_state = "wall-0" + button_icon = 'icons/mob/actions/actions_animal.dmi' + button_icon_state = "expand" chosen_message = "You are now screeching, disorienting targets around you." chosen_attack_num = 3 diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm index 6386fa272b7eb..41fdf26250a19 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm @@ -56,11 +56,8 @@ M.take_damage(50, BRUTE, MELEE, 1) //Elites can't talk (normally)! -/mob/living/simple_animal/hostile/asteroid/elite/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null, message_range = 7, datum/saymode/saymode = null) - if(can_talk) - . = ..() - return TRUE - return FALSE +/mob/living/simple_animal/hostile/asteroid/elite/can_speak(allow_mimes) + return can_talk && ..() /*Basic setup for elite attacks, based on Whoneedspace's megafauna attack setup. While using this makes the system rely on OnFire, it still gives options for timers not tied to OnFire, and it makes using attacks consistent accross the board for player-controlled elites.*/ @@ -187,10 +184,10 @@ While using this makes the system rely on OnFire, it still gives options for tim addtimer(CALLBACK(src, PROC_REF(spawn_elite)), 30) return visible_message(span_boldwarning("Something within [src] stirs...")) - var/list/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as a lavaland elite?", check_jobban = ROLE_SENTIENCE, role = ROLE_SENTIENCE, poll_time = 5 SECONDS, target_mob = src, ignore_category = POLL_IGNORE_LAVALAND_ELITE, pic_source = src, role_name_text = "lavaland elite") - if(candidates.len) + var/mob/chosen_one = SSpolling.poll_ghosts_for_target(check_jobban = ROLE_SENTIENCE, role = ROLE_SENTIENCE, poll_time = 5 SECONDS, checked_target = src, ignore_category = POLL_IGNORE_LAVALAND_ELITE, alert_pic = src, role_name_text = "lavaland elite") + if(chosen_one) audible_message(span_boldwarning("The stirring sounds increase in volume!")) - elitemind = pick(candidates) + elitemind = chosen_one elitemind.playsound_local(get_turf(elitemind), 'sound/effects/magic.ogg', 40, 0) to_chat(elitemind, "You have been chosen to play as a Lavaland Elite.\nIn a few seconds, you will be summoned on Lavaland as a monster to fight your activator, in a fight to the death.\n\ Your attacks can be switched using the buttons on the top left of the HUD, and used by clicking on targets or tiles similar to a gun.\n\ diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm index 9517bce4f92e7..77557879ccb93 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm @@ -63,9 +63,11 @@ /mob/living/simple_animal/hostile/asteroid/elite/herald/proc/become_ghost() icon_state = "herald_ghost" -/mob/living/simple_animal/hostile/asteroid/elite/herald/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null, message_range = 7, datum/saymode/saymode = null) +/mob/living/simple_animal/hostile/asteroid/elite/herald/send_speech(message_raw, message_range, obj/source, bubble_type, list/spans, datum/language/message_language, list/message_mods, forced, tts_message, list/tts_filter) . = ..() - playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE) + if(stat != CONSCIOUS) + return + playsound(src, 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE) /datum/action/innate/elite_attack/herald_trishot name = "Triple Shot" diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/wolf.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/wolf.dm deleted file mode 100644 index 56a8c77e2fd8b..0000000000000 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/wolf.dm +++ /dev/null @@ -1,72 +0,0 @@ -/mob/living/simple_animal/hostile/asteroid/wolf - name = "white wolf" - desc = "A beast that survives by feasting on weaker opponents, they're much stronger with numbers." - icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi' - icon_state = "whitewolf" - icon_living = "whitewolf" - icon_dead = "whitewolf_dead" - mob_biotypes = MOB_ORGANIC|MOB_BEAST - mouse_opacity = MOUSE_OPACITY_ICON - friendly_verb_continuous = "howls at" - friendly_verb_simple = "howl at" - speak_emote = list("howls") - speed = 5 - move_to_delay = 5 - maxHealth = 130 - health = 130 - obj_damage = 15 - melee_damage_lower = 7.5 - melee_damage_upper = 7.5 - rapid_melee = 2 // every second attack - dodging = TRUE - dodge_prob = 50 - attack_verb_continuous = "bites" - attack_verb_simple = "bite" - attack_sound = 'sound/weapons/bite.ogg' - attack_vis_effect = ATTACK_EFFECT_BITE - vision_range = 7 - aggro_vision_range = 7 - move_force = MOVE_FORCE_WEAK - move_resist = MOVE_FORCE_WEAK - pull_force = MOVE_FORCE_WEAK - butcher_results = list(/obj/item/food/meat/slab = 2, /obj/item/stack/sheet/sinew/wolf = 2, /obj/item/stack/sheet/bone = 2) - loot = list() - crusher_loot = /obj/item/crusher_trophy/wolf_ear - stat_attack = HARD_CRIT - robust_searching = TRUE - footstep_type = FOOTSTEP_MOB_CLAW - /// Message for when the wolf decides to start running away - var/retreat_message_said = FALSE - -/mob/living/simple_animal/hostile/asteroid/wolf/Move(atom/newloc) - if(newloc && newloc.z == z && (islava(newloc) || ischasm(newloc))) - return FALSE - return ..() - -/mob/living/simple_animal/hostile/asteroid/wolf/adjustHealth(amount, updating_health = TRUE, forced = FALSE) - . = ..() - if(stat == DEAD || health > maxHealth*0.1) - retreat_distance = initial(retreat_distance) - return - if(!retreat_message_said && target) - visible_message(span_danger("The [name] tries to flee from [target]!")) - retreat_message_said = TRUE - retreat_distance = 30 - -/mob/living/simple_animal/hostile/asteroid/wolf/Life(seconds_per_tick = SSMOBS_DT, times_fired) - . = ..() - if(!. || target) - return - retreat_message_said = FALSE - -/obj/item/crusher_trophy/wolf_ear - name = "wolf ear" - desc = "It's a wolf ear." - icon_state = "wolf_ear" - denied_type = /obj/item/crusher_trophy/wolf_ear - -/obj/item/crusher_trophy/wolf_ear/effect_desc() - return "mark detonation to gain a slight speed boost temporarily" - -/obj/item/crusher_trophy/wolf_ear/on_mark_detonation(mob/living/target, mob/living/user) - user.apply_status_effect(/datum/status_effect/speed_boost, 1 SECONDS) diff --git a/code/modules/mob/living/status_procs.dm b/code/modules/mob/living/status_procs.dm index 96beb024fe922..c0e2058f23ed2 100644 --- a/code/modules/mob/living/status_procs.dm +++ b/code/modules/mob/living/status_procs.dm @@ -303,6 +303,7 @@ Knockdown(amount) Stun(amount) Immobilize(amount) + Unconscious(amount) /mob/living/proc/SetAllImmobility(amount) @@ -310,6 +311,7 @@ SetKnockdown(amount) SetStun(amount) SetImmobilized(amount) + SetUnconscious(amount) /mob/living/proc/AdjustAllImmobility(amount) @@ -317,6 +319,7 @@ AdjustKnockdown(amount) AdjustStun(amount) AdjustImmobilized(amount) + AdjustUnconscious(amount) /* UNCONSCIOUS */ diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 0085d0b2fb693..49bad5734a8fc 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -1069,7 +1069,7 @@ antimagic_color = LIGHT_COLOR_DARK_BLUE playsound(src, 'sound/magic/magic_block_mind.ogg', 50, TRUE) - mob_light(range = 2, color = antimagic_color, duration = 5 SECONDS) + mob_light(range = 2, power = 2, color = antimagic_color, duration = 5 SECONDS) add_overlay(antimagic_effect) addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, cut_overlay), antimagic_effect), 5 SECONDS) @@ -1114,6 +1114,9 @@ var/datum/dna/mob_dna = has_dna() if(mob_dna?.check_mutation(/datum/mutation/human/telekinesis) && tkMaxRangeCheck(src, A)) return TRUE + var/obj/item/item_in_hand = get_active_held_item() + if(istype(item_in_hand, /obj/item/machine_remote)) + return TRUE //range check if(!interaction_range) // If you don't have extra length, GO AWAY @@ -1502,12 +1505,28 @@ get_language_holder().open_language_menu(usr) ///Adjust the nutrition of a mob -/mob/proc/adjust_nutrition(change) //Honestly FUCK the oldcoders for putting nutrition on /mob someone else can move it up because holy hell I'd have to fix SO many typechecks +/mob/proc/adjust_nutrition(change, forced = FALSE) //Honestly FUCK the oldcoders for putting nutrition on /mob someone else can move it up because holy hell I'd have to fix SO many typechecks + if(HAS_TRAIT(src, TRAIT_NOHUNGER) && !forced) + return + nutrition = max(0, nutrition + change) + hud_used?.hunger?.update_appearance() + +/mob/living/adjust_nutrition(change, forced) + . = ..() + mob_mood?.update_nutrition_moodlets() ///Force set the mob nutrition -/mob/proc/set_nutrition(change) //Seriously fuck you oldcoders. - nutrition = max(0, change) +/mob/proc/set_nutrition(set_to, forced = FALSE) //Seriously fuck you oldcoders. + if(HAS_TRAIT(src, TRAIT_NOHUNGER) && !forced) + return + + nutrition = max(0, set_to) + hud_used?.hunger?.update_appearance() + +/mob/living/set_nutrition(set_to, forced) + . = ..() + mob_mood?.update_nutrition_moodlets() ///Apply a proper movespeed modifier based on items we have equipped /mob/proc/update_equipment_speed_mods() diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index d2b8ce0f3c380..7a8c993f26b0f 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -355,23 +355,22 @@ if(usr) log_admin("[key_name(usr)] has offered control of ([key_name(M)]) to ghosts.") message_admins("[key_name_admin(usr)] has offered control of ([ADMIN_LOOKUPFLW(M)]) to ghosts") - var/poll_message = "Do you want to play as [M.real_name]?" + var/poll_message = "Do you want to play as [span_danger(M.real_name)]?" if(M.mind) - poll_message = "[poll_message] Job: [M.mind.assigned_role.title]." + poll_message = "[poll_message] Job: [span_notice(M.mind.assigned_role.title)]." if(M.mind.special_role) - poll_message = "[poll_message] Status: [M.mind.special_role]." + poll_message = "[poll_message] Status: [span_boldnotice(M.mind.special_role)]." else var/datum/antagonist/A = M.mind.has_antag_datum(/datum/antagonist/) if(A) - poll_message = "[poll_message] Status: [A.name]." - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates_for_mob(poll_message, check_jobban = ROLE_PAI, poll_time = 10 SECONDS, target_mob = M, pic_source = M, role_name_text = "ghost control") + poll_message = "[poll_message] Status: [span_boldnotice(A.name)]." + var/mob/chosen_one = SSpolling.poll_ghosts_for_target(poll_message, check_jobban = ROLE_PAI, poll_time = 10 SECONDS, checked_target = M, alert_pic = M, role_name_text = "ghost control") - if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) + if(chosen_one) to_chat(M, "Your mob has been taken over by a ghost!") - message_admins("[key_name_admin(C)] has taken control of ([ADMIN_LOOKUPFLW(M)])") + message_admins("[key_name_admin(chosen_one)] has taken control of ([ADMIN_LOOKUPFLW(M)])") M.ghostize(FALSE) - M.key = C.key + M.key = chosen_one.key M.client?.init_verbs() return TRUE else diff --git a/code/modules/mob/mob_say.dm b/code/modules/mob/mob_say.dm index da8bd502fb1e1..6b300b2938a3f 100644 --- a/code/modules/mob/mob_say.dm +++ b/code/modules/mob/mob_say.dm @@ -52,9 +52,6 @@ QUEUE_OR_CALL_VERB_FOR(VERB_CALLBACK(src, TYPE_PROC_REF(/mob, emote), "me", 1, message, TRUE), SSspeech_controller) /mob/try_speak(message, ignore_spam = FALSE, forced = null, filterproof = FALSE) - SHOULD_CALL_PARENT(TRUE) - if(!..()) - return FALSE var/list/filter_result var/list/soft_filter_result if(client && !forced && !filterproof) @@ -88,9 +85,31 @@ return FALSE if(client.handle_spam_prevention(message, MUTE_IC)) return FALSE - // Including can_speak() here would ignore COMPONENT_CAN_ALWAYS_SPEAK in /mob/living/try_speak() + + var/sigreturn = SEND_SIGNAL(src, COMSIG_MOB_TRY_SPEECH, message, ignore_spam, forced) + if(sigreturn & COMPONENT_IGNORE_CAN_SPEAK) + return TRUE + if(sigreturn & COMPONENT_CANNOT_SPEAK) + return FALSE + + if(!..()) // the can_speak check + if(HAS_MIND_TRAIT(src, TRAIT_MIMING)) + to_chat(src, span_green("Your vow of silence prevents you from speaking!")) + else + to_chat(src, span_warning("You find yourself unable to speak!")) + return FALSE + return TRUE +/mob/can_speak(allow_mimes = FALSE) + if(!allow_mimes && HAS_MIND_TRAIT(src, TRAIT_MIMING)) + return FALSE + + if(is_muzzled()) + return FALSE + + return ..() + ///Speak as a dead person (ghost etc) /mob/proc/say_dead(message) var/name = real_name diff --git a/code/modules/mob/mob_transformation_simple.dm b/code/modules/mob/mob_transformation_simple.dm index 6d2a8a9850dc2..56cd102a59b8c 100644 --- a/code/modules/mob/mob_transformation_simple.dm +++ b/code/modules/mob/mob_transformation_simple.dm @@ -2,7 +2,7 @@ //This proc is the most basic of the procs. All it does is make a new mob on the same tile and transfer over a few variables. //Returns the new mob //Note that this proc does NOT do MMI related stuff! -/mob/proc/change_mob_type(new_type = null, turf/location = null, new_name = null as text, delete_old_mob = FALSE) +/mob/proc/change_mob_type(new_type = null, turf/location = null, new_name = null as text|null, delete_old_mob = FALSE) if(isnewplayer(src)) to_chat(usr, span_danger("Cannot convert players who have not entered yet.")) diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm index ca82e13e803e6..28d89cf4cb4ae 100644 --- a/code/modules/mob/transform_procs.dm +++ b/code/modules/mob/transform_procs.dm @@ -184,11 +184,10 @@ to_chat(src, "You are job banned from cyborg! Appeal your job ban if you want to avoid this in the future!") ghostize(FALSE) - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates_for_mob("Do you want to play as [src]?", check_jobban = JOB_CYBORG, poll_time = 5 SECONDS, target_mob = src, pic_source = src, role_name_text = "cyborg") - if(LAZYLEN(candidates)) - var/mob/dead/observer/chosen_candidate = pick(candidates) - message_admins("[key_name_admin(chosen_candidate)] has taken control of ([key_name_admin(src)]) to replace a jobbanned player.") - key = chosen_candidate.key + var/mob/chosen_one = SSpolling.poll_ghosts_for_target("Do you want to play as [span_notice(src)]?", check_jobban = JOB_CYBORG, poll_time = 5 SECONDS, checked_target = src, alert_pic = src, role_name_text = "cyborg") + if(chosen_one) + message_admins("[key_name_admin(chosen_one)] has taken control of ([key_name_admin(src)]) to replace a jobbanned player.") + key = chosen_one.key //human -> alien /mob/living/carbon/human/proc/Alienize() diff --git a/code/modules/mob_spawn/ghost_roles/spider_roles.dm b/code/modules/mob_spawn/ghost_roles/spider_roles.dm index 8ab32d9d4f405..102a73d6f92f4 100644 --- a/code/modules/mob_spawn/ghost_roles/spider_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/spider_roles.dm @@ -1,5 +1,6 @@ /obj/structure/spider/eggcluster name = "egg cluster" + icon = 'icons/effects/effects.dmi' desc = "There's something alive in there, and sooner or later it's going to find its way out." icon_state = "eggs" /// Mob spawner handling the actual spawn of the spider diff --git a/code/modules/mod/mod_construction.dm b/code/modules/mod/mod_construction.dm index 8442783d33107..ca7be41ec426f 100644 --- a/code/modules/mod/mod_construction.dm +++ b/code/modules/mod/mod_construction.dm @@ -60,7 +60,8 @@ Its shape is remarkably similar to that of a MOD core." light_system = OVERLAY_LIGHT light_color = "#cc00cc" - light_range = 2 + light_range = 2.5 + light_power = 1.5 /obj/item/mod/construction/lavalandcore/examine(mob/user) . = ..() diff --git a/code/modules/mod/mod_core.dm b/code/modules/mod/mod_core.dm index 75a5b2385b7bf..d8138f6f291db 100644 --- a/code/modules/mod/mod_core.dm +++ b/code/modules/mod/mod_core.dm @@ -369,7 +369,8 @@ The wires coming out of it could be hooked into a MODsuit." light_system = OVERLAY_LIGHT light_color = "#cc00cc" - light_range = 2 + light_range = 2.5 + light_power = 1.5 // Slightly better than the normal plasma core. // Not super sure if this should just be the same, but will see. maxcharge = 15000 diff --git a/code/modules/mod/modules/modules_general.dm b/code/modules/mod/modules/modules_general.dm index 9962bc8b9a44d..5cc542703d23d 100644 --- a/code/modules/mod/modules/modules_general.dm +++ b/code/modules/mod/modules/modules_general.dm @@ -334,10 +334,10 @@ incompatible_modules = list(/obj/item/mod/module/emp_shield) /obj/item/mod/module/emp_shield/on_install() - mod.AddElement(/datum/element/empprotection, EMP_PROTECT_SELF|EMP_PROTECT_WIRES|EMP_PROTECT_CONTENTS) + mod.AddElement(/datum/element/empprotection, EMP_PROTECT_ALL) /obj/item/mod/module/emp_shield/on_uninstall(deleting = FALSE) - mod.RemoveElement(/datum/element/empprotection, EMP_PROTECT_SELF|EMP_PROTECT_WIRES|EMP_PROTECT_CONTENTS) + mod.RemoveElement(/datum/element/empprotection, EMP_PROTECT_ALL) /obj/item/mod/module/emp_shield/advanced name = "MOD advanced EMP shield module" @@ -788,19 +788,7 @@ /obj/item/cigbutt, ) /// Materials that will be extracted. - var/list/accepted_mats = list( - /datum/material/iron, - /datum/material/glass, - /datum/material/silver, - /datum/material/plasma, - /datum/material/gold, - /datum/material/diamond, - /datum/material/plastic, - /datum/material/uranium, - /datum/material/bananium, - /datum/material/titanium, - /datum/material/bluespace, - ) + var/list/accepted_mats var/static/list/loc_connections = list( COMSIG_ATOM_ENTERED = PROC_REF(on_obj_entered), COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON = PROC_REF(on_atom_initialized_on), @@ -810,10 +798,15 @@ /obj/item/mod/module/recycler/Initialize(mapload) . = ..() + + if(!length(accepted_mats)) + accepted_mats = SSmaterials.materials_by_category[MAT_CATEGORY_SILO] + container = AddComponent( \ /datum/component/material_container, \ - accepted_mats, 50 * SHEET_MATERIAL_AMOUNT, \ - MATCONTAINER_EXAMINE|MATCONTAINER_NO_INSERT, \ + accepted_mats, \ + 50 * SHEET_MATERIAL_AMOUNT, \ + MATCONTAINER_EXAMINE | MATCONTAINER_NO_INSERT, \ container_signals = list( \ COMSIG_MATCONTAINER_SHEETS_RETRIEVED = TYPE_PROC_REF(/obj/item/mod/module/recycler, InsertSheets) \ ) \ diff --git a/code/modules/modular_computers/computers/item/computer.dm b/code/modules/modular_computers/computers/item/computer.dm index 5f718ef04f03c..6b94a7f91a57b 100644 --- a/code/modules/modular_computers/computers/item/computer.dm +++ b/code/modules/modular_computers/computers/item/computer.dm @@ -7,6 +7,7 @@ icon = 'icons/obj/machines/computer.dmi' icon_state = "laptop" light_on = FALSE + light_power = 1.2 integrity_failure = 0.5 max_integrity = 100 armor_type = /datum/armor/item_modular_computer @@ -267,30 +268,34 @@ /** * InsertID - * Attempt to insert the ID in either card slot. + * Attempt to insert the ID in either card slot, if ID is present - attempts swap * Args: * inserting_id - the ID being inserted * user - The person inserting the ID */ /obj/item/modular_computer/InsertID(obj/item/card/inserting_id, mob/user) - //all slots taken - if(computer_id_slot) + if(!isnull(user) && !user.transferItemToLoc(inserting_id, src)) return FALSE - if(user) - if(!user.transferItemToLoc(inserting_id, src)) - return FALSE - to_chat(user, span_notice("You insert \the [inserting_id] into the card slot.")) else inserting_id.forceMove(src) + if(!isnull(computer_id_slot)) + RemoveID(user, silent = TRUE) + computer_id_slot = inserting_id + if(!isnull(user)) + to_chat(user, span_notice("You insert \the [inserting_id] into the card slot.")) + balloon_alert(user, "inserted ID") + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) + if(ishuman(loc)) var/mob/living/carbon/human/human_wearer = loc if(human_wearer.wear_id == src) human_wearer.sec_hud_set_ID() + update_appearance() update_slot_icon() SEND_SIGNAL(src, COMSIG_MODULAR_COMPUTER_INSERTED_ID, inserting_id, user) @@ -300,8 +305,9 @@ * Removes the ID card from the computer, and puts it in loc's hand if it's a mob * Args: * user - The mob trying to remove the ID, if there is one + * silent - Boolean, determines whether fluff text would be printed */ -/obj/item/modular_computer/RemoveID(mob/user) +/obj/item/modular_computer/RemoveID(mob/user, silent = FALSE) if(!computer_id_slot) return ..() @@ -314,14 +320,17 @@ computer_id_slot.forceMove(drop_location()) computer_id_slot = null - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) - balloon_alert(user, "removed ID") - to_chat(user, span_notice("You remove the card from the card slot.")) + + if(!silent && !isnull(user)) + to_chat(user, span_notice("You remove the card from the card slot.")) + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) + balloon_alert(user, "removed ID") if(ishuman(loc)) var/mob/living/carbon/human/human_wearer = loc if(human_wearer.wear_id == src) human_wearer.sec_hud_set_ID() + update_slot_icon() update_appearance() return TRUE @@ -403,6 +412,10 @@ /obj/item/modular_computer/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) . = ..() + if(computer_id_slot && isidcard(held_item)) + context[SCREENTIP_CONTEXT_LMB] = "Swap ID" + . = CONTEXTUAL_SCREENTIP_SET + if(held_item?.tool_behaviour == TOOL_SCREWDRIVER && internal_cell) context[SCREENTIP_CONTEXT_RMB] = "Remove Cell" . = CONTEXTUAL_SCREENTIP_SET @@ -469,7 +482,7 @@ playsound(src, 'sound/machines/card_slide.ogg', 50) /obj/item/modular_computer/proc/turn_on(mob/user, open_ui = TRUE) - var/issynth = issilicon(user) // Robots and AIs get different activation messages. + var/issynth = HAS_SILICON_ACCESS(user) // Robots and AIs get different activation messages. if(atom_integrity <= integrity_failure * max_integrity) if(user) if(issynth) diff --git a/code/modules/modular_computers/computers/item/disks/role_disks.dm b/code/modules/modular_computers/computers/item/disks/role_disks.dm index da52ee76281a1..f7f20efb70b43 100644 --- a/code/modules/modular_computers/computers/item/disks/role_disks.dm +++ b/code/modules/modular_computers/computers/item/disks/role_disks.dm @@ -98,6 +98,7 @@ starting_programs = list( /datum/computer_file/program/shipping, /datum/computer_file/program/budgetorders, + /datum/computer_file/program/restock_tracker, ) /** @@ -123,6 +124,6 @@ /datum/computer_file/program/alarm_monitor, /datum/computer_file/program/atmosscan, /datum/computer_file/program/supermatter_monitor, - + ) diff --git a/code/modules/modular_computers/computers/item/role_tablet_presets.dm b/code/modules/modular_computers/computers/item/role_tablet_presets.dm index 205c6a0c422e5..2ab7617e35c9b 100644 --- a/code/modules/modular_computers/computers/item/role_tablet_presets.dm +++ b/code/modules/modular_computers/computers/item/role_tablet_presets.dm @@ -114,6 +114,7 @@ /datum/computer_file/program/robocontrol, /datum/computer_file/program/budgetorders, /datum/computer_file/program/shipping, + /datum/computer_file/program/restock_tracker, ) /** @@ -264,6 +265,7 @@ /datum/computer_file/program/shipping, /datum/computer_file/program/budgetorders, /datum/computer_file/program/robocontrol, + /datum/computer_file/program/restock_tracker, ) /obj/item/modular_computer/pda/shaftminer @@ -414,7 +416,6 @@ /** * No Department/Station Trait */ - /obj/item/modular_computer/pda/assistant name = "assistant PDA" starting_programs = list( @@ -440,10 +441,29 @@ /datum/computer_file/program/skill_tracker, ) +/obj/item/modular_computer/pda/human_ai + name = "modular interface" + icon_state = "pda-silicon-human" + base_icon_state = "pda-silicon-human" + greyscale_config = null + greyscale_colors = null + + has_light = FALSE //parity with borg PDAs + comp_light_luminosity = 0 + inserted_item = null + has_pda_programs = FALSE + starting_programs = list( + /datum/computer_file/program/messenger, + /datum/computer_file/program/secureye/human_ai, + /datum/computer_file/program/alarm_monitor, + /datum/computer_file/program/status, + /datum/computer_file/program/robocontrol, + /datum/computer_file/program/borg_monitor, + ) + /** * Non-roles */ - /obj/item/modular_computer/pda/syndicate name = "military PDA" greyscale_colors = "#891417#80FF80" diff --git a/code/modules/modular_computers/file_system/programs/budgetordering.dm b/code/modules/modular_computers/file_system/programs/budgetordering.dm index 6a8045d8bc1e9..5e8b8b0587295 100644 --- a/code/modules/modular_computers/file_system/programs/budgetordering.dm +++ b/code/modules/modular_computers/file_system/programs/budgetordering.dm @@ -34,7 +34,7 @@ var/cargo_account = ACCOUNT_CAR /datum/computer_file/program/budgetorders/proc/is_visible_pack(mob/user, paccess_to_check, list/access, contraband) - if(issilicon(user)) //Borgs can't buy things. + if(HAS_SILICON_ACCESS(user)) //Borgs can't buy things. return FALSE if(computer.obj_flags & EMAGGED) return TRUE @@ -57,7 +57,7 @@ return FALSE -/datum/computer_file/program/budgetorders/ui_data() +/datum/computer_file/program/budgetorders/ui_data(mob/user) var/list/data = list() data["location"] = SSshuttle.supply.getStatusText() data["department"] = "Cargo" @@ -79,12 +79,12 @@ if(buyer) data["points"] = buyer.account_balance -//Otherwise static data, that is being applied in ui_data as the crates visible and buyable are not static, and are determined by inserted ID. + //Otherwise static data, that is being applied in ui_data as the crates visible and buyable are not static, and are determined by inserted ID. data["requestonly"] = requestonly data["supplies"] = list() for(var/pack in SSshuttle.supply_packs) var/datum/supply_pack/P = SSshuttle.supply_packs[pack] - if(!is_visible_pack(usr, P.access_view , null, P.contraband) || P.hidden) + if(!is_visible_pack(user, P.access_view , null, P.contraband) || P.hidden) continue if(!data["supplies"][P.group]) data["supplies"][P.group] = list( @@ -102,7 +102,7 @@ "access" = P.access )) -//Data regarding the User's capability to buy things. + //Data regarding the User's capability to buy things. data["has_id"] = id_card data["away"] = SSshuttle.supply.getDockedId() == docking_away data["self_paid"] = self_paid diff --git a/code/modules/modular_computers/file_system/programs/card.dm b/code/modules/modular_computers/file_system/programs/card.dm index 238d05704e23d..a9bbff8db1b91 100644 --- a/code/modules/modular_computers/file_system/programs/card.dm +++ b/code/modules/modular_computers/file_system/programs/card.dm @@ -51,7 +51,7 @@ authenticated_user = auth_card.registered_name ? auth_card.registered_name : "Unknown" job_templates = is_centcom ? SSid_access.centcom_job_templates.Copy() : SSid_access.station_job_templates.Copy() valid_access = is_centcom ? SSid_access.get_region_access_list(list(REGION_CENTCOM)) : SSid_access.get_region_access_list(list(REGION_ALL_STATION)) - update_static_data(user) + computer.update_static_data_for_all_viewers() return TRUE // Otherwise, we're minor and now we have to build a list of restricted departments we can change access for. @@ -67,7 +67,7 @@ minor = TRUE valid_access |= SSid_access.get_region_access_list(region_access) authenticated_card = "[auth_card.name] \[LIMITED ACCESS\]" - update_static_data(user) + computer.update_static_data_for_all_viewers() return TRUE return FALSE diff --git a/code/modules/modular_computers/file_system/programs/messenger/messenger_program.dm b/code/modules/modular_computers/file_system/programs/messenger/messenger_program.dm index 2f3898fa3f7df..77f8ccf2bac9f 100644 --- a/code/modules/modular_computers/file_system/programs/messenger/messenger_program.dm +++ b/code/modules/modular_computers/file_system/programs/messenger/messenger_program.dm @@ -704,7 +704,6 @@ else reply = "(Reply)" - if (isAI(messaged_mob)) sender_title = "[sender_title]" @@ -714,6 +713,8 @@ var/photo_message = signal.data["photo"] ? " (Photo Attached)" : "" to_chat(messaged_mob, span_infoplain("[icon2html(computer, messaged_mob)] PDA message from [sender_title], \"[inbound_message]\"[photo_message] [reply]")) + SEND_SIGNAL(computer, COMSIG_COMPUTER_RECIEVED_MESSAGE, sender_title, inbound_message, photo_message) + if (alert_able && (!alert_silenced || is_rigged)) computer.ring(ringtone) diff --git a/code/modules/modular_computers/file_system/programs/restock_tracker.dm b/code/modules/modular_computers/file_system/programs/restock_tracker.dm new file mode 100644 index 0000000000000..46462c0c6b531 --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/restock_tracker.dm @@ -0,0 +1,31 @@ +/datum/computer_file/program/restock_tracker + filename = "restockapp" + filedesc = "NT Restock Tracker" + downloader_category = PROGRAM_CATEGORY_SUPPLY + program_open_overlay = "restock" + extended_desc = "Nanotrasen IoT network listing all the vending machines found on station, and how well stocked they are each. Profitable!" + program_flags = PROGRAM_ON_NTNET_STORE | PROGRAM_REQUIRES_NTNET + can_run_on_flags = PROGRAM_LAPTOP | PROGRAM_PDA + size = 4 + program_icon = "cash-register" + tgui_id = "NtosRestock" + +/datum/computer_file/program/restock_tracker/ui_data() + var/list/data = list() + var/list/vending_list = list() + var/id_increment = 1 + for(var/obj/machinery/vending/vendor as anything in GLOB.vending_machines_to_restock) + var/stock = vendor.total_loaded_stock() + var/max_stock = vendor.total_max_stock() + if((max_stock == 0 || (stock >= max_stock)) && vendor.credits_contained == 0) + continue + vending_list += list(list( + "name" = vendor.name, + "location" = get_area_name(vendor), + "credits" = vendor.credits_contained, + "percentage" = (stock / max_stock) * 100, + "id" = id_increment, + )) + id_increment++ + data["vending_list"] = vending_list + return data diff --git a/code/modules/modular_computers/file_system/programs/secureye.dm b/code/modules/modular_computers/file_system/programs/secureye.dm index 8eb3190afa589..e1444b745770d 100644 --- a/code/modules/modular_computers/file_system/programs/secureye.dm +++ b/code/modules/modular_computers/file_system/programs/secureye.dm @@ -46,6 +46,15 @@ network = list("ss13", "mine", "rd", "labor", "ordnance", "minisat") spying = TRUE +/datum/computer_file/program/secureye/human_ai + filename = "Overseer" + filedesc = "OverSeer" + run_access = list(ACCESS_MINISAT) + can_run_on_flags = PROGRAM_PDA + program_flags = PROGRAM_UNIQUE_COPY + network = list("ss13", "mine", "rd", "labor", "ordnance", "minisat") + spying = TRUE + /datum/computer_file/program/secureye/on_install(datum/computer_file/source, obj/item/modular_computer/computer_installing) . = ..() // Map name has to start and end with an A-Z character, diff --git a/code/modules/modular_computers/file_system/programs/techweb.dm b/code/modules/modular_computers/file_system/programs/techweb.dm index bf9e7b1e9b8b9..a72eef1bc9a7d 100644 --- a/code/modules/modular_computers/file_system/programs/techweb.dm +++ b/code/modules/modular_computers/file_system/programs/techweb.dm @@ -201,7 +201,7 @@ if(stored_research.research_node_id(id)) computer.say("Successfully researched [tech_node.display_name].") var/logname = "Unknown" - if(isAI(user)) + if(HAS_AI_ACCESS(user)) logname = "AI [user.name]" if(iscyborg(user)) logname = "CYBORG [user.name]" diff --git a/code/modules/modular_computers/file_system/programs/virtual_pet.dm b/code/modules/modular_computers/file_system/programs/virtual_pet.dm new file mode 100644 index 0000000000000..1d3196789ca87 --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/virtual_pet.dm @@ -0,0 +1,568 @@ +GLOBAL_LIST_EMPTY(global_pet_updates) +GLOBAL_LIST_EMPTY(virtual_pets_list) + +#define MAX_UPDATE_LENGTH 50 +#define PET_MAX_LEVEL 3 +#define PET_MAX_STEPS_RECORD 50000 +#define PET_EAT_BONUS 500 +#define PET_CLEAN_BONUS 250 +#define PET_PLAYMATE_BONUS 500 +#define PET_STATE_HUNGRY "hungry" +#define PET_STATE_ASLEEP "asleep" +#define PET_STATE_HAPPY "happy" +#define PET_STATE_NEUTRAL "neutral" + +/datum/computer_file/program/virtual_pet + filename = "virtualpet" + filedesc = "Virtual Pet" + downloader_category = PROGRAM_CATEGORY_GAMES + extended_desc = "Download your very own Orbie today!" + program_flags = PROGRAM_ON_NTNET_STORE + size = 3 + tgui_id = "NtosVirtualPet" + program_icon = "paw" + can_run_on_flags = PROGRAM_PDA + detomatix_resistance = DETOMATIX_RESIST_MALUS + ///how many steps have we walked + var/steps_counter = 0 + ///the pet hologram + var/mob/living/pet + ///the type of our pet + var/pet_type = /mob/living/basic/orbie + ///our current happiness + var/happiness = 0 + ///our max happiness + var/max_happiness = 1750 + ///our current level + var/level = 1 + ///required exp to get to next level + var/to_next_level = 1000 + ///how much exp we currently have + var/current_level_progress = 0 + ///our current hunger + var/hunger = 0 + ///maximum hunger threshold + var/max_hunger = 500 + ///pet icon for each state + var/static/list/pet_state_icons = list( + PET_STATE_HUNGRY = list("icon" = 'icons/ui_icons/virtualpet/pet_state.dmi', "icon_state" = "pet_hungry"), + PET_STATE_HAPPY = list("icon" = 'icons/ui_icons/virtualpet/pet_state.dmi', "icon_state" = "pet_happy"), + PET_STATE_ASLEEP = list("icon" = 'icons/ui_icons/virtualpet/pet_state.dmi', "icon_state" = "pet_asleep"), + PET_STATE_NEUTRAL = list("icon" = 'icons/ui_icons/virtualpet/pet_state.dmi', "icon_state" = "pet_neutral"), + ) + ///hat options and what level they will be unlocked at + var/static/list/hat_selections = list( + /obj/item/clothing/head/hats/tophat = 1, + /obj/item/clothing/head/fedora = 1, + /obj/item/clothing/head/hats/bowler = 2, + /obj/item/clothing/head/hats/warden/police = 2, + /obj/item/clothing/head/hats/warden/red = 3, + /obj/item/clothing/head/hats/caphat = 3, + ) + ///hologram hat we have selected for our pet + var/list/selected_hat = list() + ///area we have picked as dropoff location for petfeed + var/area/selected_area + ///manage hat offsets for when we turn directions + var/static/list/hat_offsets = list( + "west" = list(0,1), + "east" = list(0,1), + "north" = list(1,1), + "south" = list(0,1), + ) + ///possible colors our pet can have + var/static/list/possible_colors= list( + "white" = null, //default color state + "light blue" = "#c3ecf3", + "light green" = "#b1ffe8", + ) + ///areas we wont drop the chocolate in + var/static/list/restricted_areas = typecacheof(list( + /area/station/security, + /area/station/command, + /area/station/ai_monitored, + /area/station/maintenance, + /area/station/solars, + )) + ///our profile picture + var/icon/profile_picture + ///cooldown till we can reroll the pet feed dropzone + COOLDOWN_DECLARE(area_reroll) + ///cooldown till our pet gains happiness again from being cleaned + COOLDOWN_DECLARE(on_clean_cooldown) + ///cooldown till we can release/recall our pet + COOLDOWN_DECLARE(summon_cooldown) + ///cooldown till we can alter our pet's appearance again + COOLDOWN_DECLARE(alter_appearance_cooldown) + +/datum/computer_file/program/virtual_pet/on_install() + . = ..() + profile_picture = getFlatIcon(image(icon = 'icons/ui_icons/virtualpet/pet_state.dmi', icon_state = "pet_preview")) + GLOB.virtual_pets_list += src + pet = new pet_type(computer) + pet.forceMove(computer) + pet.AddComponent(/datum/component/leash, computer, 9, force_teleport_out_effect = /obj/effect/temp_visual/guardian/phase/out) + RegisterSignal(pet, COMSIG_QDELETING, PROC_REF(remove_pet)) + RegisterSignal(pet, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_overlays_updated)) //hologramic hat management + RegisterSignal(pet, COMSIG_ATOM_DIR_CHANGE, PROC_REF(on_change_dir)) + RegisterSignal(pet, COMSIG_MOVABLE_MOVED, PROC_REF(after_pet_move)) + RegisterSignal(pet, COMSIG_MOB_ATE, PROC_REF(after_pet_eat)) // WE ATEEE + RegisterSignal(pet, COMSIG_ATOM_PRE_CLEAN, PROC_REF(pet_pre_clean)) + RegisterSignal(pet, COMSIG_LIVING_DEATH, PROC_REF(on_death)) + RegisterSignal(pet, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(post_cleaned)) + RegisterSignal(pet, COMSIG_AI_BLACKBOARD_KEY_SET(BB_NEARBY_PLAYMATE), PROC_REF(on_playmate_find)) + RegisterSignal(computer, COMSIG_ATOM_ENTERED, PROC_REF(on_pet_entered)) + RegisterSignal(computer, COMSIG_ATOM_EXITED, PROC_REF(on_pet_exit)) + +/datum/computer_file/program/virtual_pet/Destroy() + GLOB.virtual_pets_list -= src + if(!QDELETED(pet)) + QDEL_NULL(pet) + STOP_PROCESSING(SSprocessing, src) + return ..() + +/datum/computer_file/program/virtual_pet/proc/on_death(datum/source) + SIGNAL_HANDLER + + pet.forceMove(computer) + + +/datum/computer_file/program/virtual_pet/proc/on_message_recieve(datum/source, sender_title, inbound_message, photo_message) + SIGNAL_HANDLER + + var/message_to_display = "[sender_title] has sent you a message [photo_message ? "with a photo attached" : ""]: [inbound_message]!" + pet.ai_controller?.set_blackboard_key(BB_LAST_RECIEVED_MESSAGE, message_to_display) + +/datum/computer_file/program/virtual_pet/proc/pet_pre_clean(atom/source, mob/user) + SIGNAL_HANDLER + + if(!COOLDOWN_FINISHED(src, on_clean_cooldown)) + source.balloon_alert(user, "already clean!") + return COMSIG_ATOM_CANCEL_CLEAN + +/datum/computer_file/program/virtual_pet/proc/on_playmate_find(datum/source) + SIGNAL_HANDLER + + happiness = min(happiness + PET_PLAYMATE_BONUS, max_happiness) + START_PROCESSING(SSprocessing, src) + +/datum/computer_file/program/virtual_pet/proc/post_cleaned(mob/source, mob/user) + SIGNAL_HANDLER + + source.spin(spintime = 2 SECONDS, speed = 1) //celebrate! + happiness = min(happiness + PET_CLEAN_BONUS, max_happiness) + COOLDOWN_START(src, on_clean_cooldown, 1 MINUTES) + START_PROCESSING(SSprocessing, src) + +///manage the pet's hat offsets when he changes direction +/datum/computer_file/program/virtual_pet/proc/on_change_dir(datum/source, old_dir, new_dir) + SIGNAL_HANDLER + + if(!length(selected_hat)) + return + set_hat_offsets(new_dir) + +/datum/computer_file/program/virtual_pet/proc/on_photo_captured(datum/source, atom/target, atom/user, datum/picture/photo) + SIGNAL_HANDLER + + if(isnull(photo)) + return + computer.store_file(new /datum/computer_file/picture(photo)) + +/datum/computer_file/program/virtual_pet/proc/set_hat_offsets(new_dir) + var/direction_text = dir2text(new_dir) + var/list/offsets_list = hat_offsets[direction_text] + if(isnull(offsets_list)) + return + var/mutable_appearance/hat_appearance = selected_hat["appearance"] + hat_appearance.pixel_x = offsets_list[1] + hat_appearance.pixel_y = offsets_list[2] + pet.update_appearance(UPDATE_OVERLAYS) + +///give our pet his hologram hat +/datum/computer_file/program/virtual_pet/proc/on_overlays_updated(atom/source, list/overlays) + SIGNAL_HANDLER + + if(!length(selected_hat)) + return + overlays += selected_hat["appearance"] + +/datum/computer_file/program/virtual_pet/proc/alter_profile_picture() + var/image/pet_preview = image(icon = 'icons/ui_icons/virtualpet/pet_state.dmi', icon_state = "pet_preview") + if(LAZYACCESS(pet.atom_colours, FIXED_COLOUR_PRIORITY)) + pet_preview.color = pet.atom_colours[FIXED_COLOUR_PRIORITY] + + if(length(selected_hat)) + var/mutable_appearance/our_selected_hat = selected_hat["appearance"] + var/mutable_appearance/hat_preview = mutable_appearance(our_selected_hat.icon, our_selected_hat.icon_state) + hat_preview.pixel_y = -9 + pet_preview.add_overlay(hat_preview) + + profile_picture = getFlatIcon(pet_preview) + COOLDOWN_START(src, alter_appearance_cooldown, 10 SECONDS) + + +///decrease the pet's hunger after it eats +/datum/computer_file/program/virtual_pet/proc/after_pet_eat(datum/source) + SIGNAL_HANDLER + + hunger = min(hunger + PET_EAT_BONUS, max_hunger) + happiness = min(happiness + PET_EAT_BONUS, max_happiness) + START_PROCESSING(SSprocessing, src) + +///start processing if we enter the pda and need healing +/datum/computer_file/program/virtual_pet/proc/on_pet_entered(atom/movable/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs) + SIGNAL_HANDLER + + if(arrived != pet) + return + ADD_TRAIT(pet, TRAIT_AI_PAUSED, REF(src)) + if((datum_flags & DF_ISPROCESSING)) + return + if(pet.health < pet.maxHealth) //if we're in the pda, heal up + START_PROCESSING(SSprocessing, src) + +/datum/computer_file/program/virtual_pet/proc/on_pet_exit(atom/movable/source, atom/movable/exited) + SIGNAL_HANDLER + + if(exited != pet) + return + REMOVE_TRAIT(pet, TRAIT_AI_PAUSED, REF(src)) + if((datum_flags & DF_ISPROCESSING)) + return + if(hunger > 0 || happiness > 0) //if were outside the pda, we become hungry and happiness decreases + START_PROCESSING(SSprocessing, src) + +/datum/computer_file/program/virtual_pet/process() + if(pet.loc == computer) + if(pet.health >= pet.maxHealth) + return PROCESS_KILL + if(pet.stat == DEAD) + pet.revive(ADMIN_HEAL_ALL) + pet.heal_overall_damage(5) + return + + if(hunger > 0) + hunger-- + + if(happiness > 0) + happiness-- + + if(hunger <=0 && happiness <= 0) + return PROCESS_KILL + +/datum/computer_file/program/virtual_pet/proc/after_pet_move(atom/movable/movable, atom/old_loc) + SIGNAL_HANDLER + + if(!isturf(pet.loc) || !isturf(old_loc)) + return + steps_counter = min(steps_counter + 1, PET_MAX_STEPS_RECORD) + increment_exp() + if(steps_counter % 2000 == 0) //every 2000 steps, announce the milestone to the world! + announce_global_updates(message = "has walked [steps_counter] steps!") + +/datum/computer_file/program/virtual_pet/proc/increment_exp() + var/modifier = 1 + var/hunger_happiness = hunger + happiness + var/max_hunger_happiness = max_hunger + max_happiness + + switch(hunger_happiness / max_hunger_happiness) + if(0.8 to 1) + modifier = 3 + if(0.5 to 0.8) + modifier = 2 + + current_level_progress = min(current_level_progress + modifier, to_next_level) + if(current_level_progress >= to_next_level) + handle_level_up() + +/datum/computer_file/program/virtual_pet/proc/handle_level_up() + current_level_progress = 0 + level++ + grant_level_abilities() + pet.ai_controller?.set_blackboard_key(BB_VIRTUAL_PET_LEVEL, level) + playsound(computer.loc, 'sound/items/orbie_level_up.ogg', 50) + to_next_level += (level**2) + 500 + SEND_SIGNAL(pet, COMSIG_VIRTUAL_PET_LEVEL_UP, level) //its a signal so different path types of virtual pets can handle leveling up differently + announce_global_updates(message = "has reached level [level]!") + +/datum/computer_file/program/virtual_pet/proc/grant_level_abilities() + switch(level) + if(2) + RegisterSignal(computer, COMSIG_COMPUTER_RECIEVED_MESSAGE, PROC_REF(on_message_recieve)) // we will now read out PDA messages + var/datum/action/cooldown/mob_cooldown/lights/lights = new(pet) + lights.Grant(pet) + pet.ai_controller?.set_blackboard_key(BB_LIGHTS_ABILITY, lights) + if(3) + var/datum/action/cooldown/mob_cooldown/capture_photo/photo_ability = new(pet) + photo_ability.Grant(pet) + pet.ai_controller?.set_blackboard_key(BB_PHOTO_ABILITY, photo_ability) + RegisterSignal(photo_ability.ability_camera, COMSIG_CAMERA_IMAGE_CAPTURED, PROC_REF(on_photo_captured)) + +/datum/computer_file/program/virtual_pet/proc/announce_global_updates(message) + if(isnull(message)) + return + var/list/message_to_announce = list( + "name" = pet.name, + "pet_picture" = icon2base64(profile_picture), + "message" = message, + "likers" = list(REF(src)) + ) + if(length(GLOB.global_pet_updates) >= MAX_UPDATE_LENGTH) + GLOB.global_pet_updates.Cut(1,2) + + GLOB.global_pet_updates += list(message_to_announce) + playsound(computer.loc, 'sound/items/orbie_notification_sound.ogg', 50) + +/datum/computer_file/program/virtual_pet/proc/remove_pet(datum/source) + SIGNAL_HANDLER + pet = null + if(QDELETED(src)) + return + computer.remove_file(src) //all is lost we no longer have a reason to exist + +/datum/computer_file/program/virtual_pet/kill_program(mob/user) + if(pet && pet.loc != computer) + pet.forceMove(computer) //recall the hologram back to the pda + STOP_PROCESSING(SSprocessing, src) + return ..() + +/datum/computer_file/program/virtual_pet/proc/get_pet_state() + if(isnull(pet)) + return + + if(pet.loc == computer) + return PET_STATE_ASLEEP + + if(happiness/max_happiness > 0.8) + return PET_STATE_HAPPY + + if(hunger/max_hunger < 0.5) + return PET_STATE_HUNGRY + + return PET_STATE_NEUTRAL + +/datum/computer_file/program/virtual_pet/ui_data(mob/user) + var/list/data = list() + data["currently_summoned"] = (pet.loc != computer) + data["selected_area"] = (selected_area ? selected_area.name : "No location set") + data["pet_state"] = get_pet_state() + data["hunger"] = hunger + data["maximum_hunger"] = max_hunger + data["pet_hat"] = (length(selected_hat) ? selected_hat["name"] : "none") + data["can_reroll"] = COOLDOWN_FINISHED(src, area_reroll) + data["can_summon"] = COOLDOWN_FINISHED(src, summon_cooldown) + data["can_alter_appearance"] = COOLDOWN_FINISHED(src, alter_appearance_cooldown) + data["pet_name"] = pet.name + data["steps_counter"] = steps_counter + data["in_dropzone"] = (istype(get_area(computer), selected_area)) + data["pet_area"] = (pet.loc != computer ? get_area_name(pet) : "Sleeping in PDA") + data["current_exp"] = current_level_progress + data["required_exp"] = to_next_level + data["happiness"] = happiness + data["maximum_happiness"] = max_happiness + data["level"] = level + data["pet_color"] = "" + + var/color_value = LAZYACCESS(pet.atom_colours, FIXED_COLOUR_PRIORITY) + for(var/index in possible_colors) + if(possible_colors[index] == color_value) + data["pet_color"] = index + break + + data["pet_gender"] = pet.gender + + data["pet_updates"] = list() + + for(var/i in length(GLOB.global_pet_updates) to 1 step -1) + var/list/update = GLOB.global_pet_updates[i] + + if(isnull(update)) + continue + + data["pet_updates"] += list(list( + "update_id" = i, + "update_name" = update["name"], + "update_picture" = update["pet_picture"], + "update_message" = update["message"], + "update_likers" = length(update["likers"]), + "update_already_liked" = ((REF(src)) in update["likers"]), + )) + + data["all_pets"] = list() + for(var/datum/computer_file/program/virtual_pet/program as anything in GLOB.virtual_pets_list) + data["all_pets"] += list(list( + "other_pet_name" = program.pet.name, + "other_pet_picture" = icon2base64(program.profile_picture), + )) + return data + +/datum/computer_file/program/virtual_pet/ui_static_data(mob/user) + var/list/data = list() + data["pet_state_icons"] = list() + for(var/list_index as anything in pet_state_icons) + var/list/sprite_location = pet_state_icons[list_index] + data["pet_state_icons"] += list(list( + "name" = list_index, + "icon" = icon2base64(getFlatIcon(image(icon = sprite_location["icon"], icon_state = sprite_location["icon_state"]), no_anim=TRUE)) + )) + + data["hat_selections"] = list(list( + "hat_id" = null, + "hat_name" = "none", + )) + + for(var/type_index as anything in hat_selections) + if(level >= hat_selections[type_index]) + var/obj/item/hat = type_index + data["hat_selections"] += list(list( + "hat_id" = type_index, + "hat_name" = initial(hat.name), + )) + + data["possible_colors"] = list() + for(var/color in possible_colors) + data["possible_colors"] += list(list( + "color_name" = color, + "color_value" = possible_colors[color], + )) + + var/static/list/possible_emotes = list( + /datum/emote/flip, + /datum/emote/living/jump, + /datum/emote/living/shiver, + /datum/emote/spin, + /datum/emote/living/beep, + ) + data["possible_emotes"] = list("none") + for(var/datum/emote/target_emote as anything in possible_emotes) + data["possible_emotes"] += target_emote.key + + data["preview_icon"] = icon2base64(profile_picture) + return data + +/datum/computer_file/program/virtual_pet/ui_act(action, params, datum/tgui/ui) + . = ..() + switch(action) + + if("summon_pet") + if(!COOLDOWN_FINISHED(src, summon_cooldown)) + return TRUE + if(pet.loc == computer) + release_pet(ui.user) + else + recall_pet() + COOLDOWN_START(src, summon_cooldown, 10 SECONDS) + + if("apply_customization") + if(!COOLDOWN_FINISHED(src, alter_appearance_cooldown)) + return TRUE + var/obj/item/chosen_type = text2path(params["chosen_hat"]) + if(isnull(chosen_type)) + selected_hat.Cut() + + else if((chosen_type in hat_selections)) + selected_hat["name"] = initial(chosen_type.name) + var/mutable_appearance/selected_hat_appearance = mutable_appearance(icon = initial(chosen_type.worn_icon), icon_state = initial(chosen_type.icon_state), layer = ABOVE_ALL_MOB_LAYER) + selected_hat_appearance.transform = selected_hat_appearance.transform.Scale(0.8, 1) + selected_hat["appearance"] = selected_hat_appearance + set_hat_offsets(pet.dir) + + var/chosen_color = params["chosen_color"] + if(isnull(chosen_color)) + pet.remove_atom_colour(FIXED_COLOUR_PRIORITY) + else + pet.add_atom_colour(chosen_color, FIXED_COLOUR_PRIORITY) + + var/input_name = sanitize_name(params["chosen_name"], allow_numbers = TRUE) + pet.name = (input_name ? input_name : initial(pet.name)) + new /obj/effect/temp_visual/guardian/phase(pet.loc) + + switch(params["chosen_gender"]) + if("male") + pet.gender = MALE + if("female") + pet.gender = FEMALE + if("neuter") + pet.gender = NEUTER + + pet.update_appearance() + alter_profile_picture() + update_static_data(ui.user, ui) + + if("get_feed_location") + generate_petfeed_area() + + if("drop_feed") + drop_feed() + + if("like_update") + var/index = params["update_reference"] + var/list/update_message = GLOB.global_pet_updates[index] + if(isnull(update_message)) + return TRUE + var/our_reference = REF(src) + if(our_reference in update_message["likers"]) + update_message["likers"] -= our_reference + else + update_message["likers"] += our_reference + + if("teach_tricks") + var/trick_name = params["trick_name"] + var/list/trick_sequence = params["tricks"] + if(isnull(pet.ai_controller)) + return TRUE + if(!isnull(trick_name)) + pet.ai_controller.set_blackboard_key(BB_TRICK_NAME, trick_name) + pet.ai_controller.override_blackboard_key(BB_TRICK_SEQUENCE, trick_sequence) + playsound(computer.loc, 'sound/items/orbie_trick_learned.ogg', 50) + + return TRUE + +/datum/computer_file/program/virtual_pet/proc/generate_petfeed_area() + if(!COOLDOWN_FINISHED(src, area_reroll)) + return + var/list/filter_area_list = typecache_filter_list(GLOB.the_station_areas, restricted_areas) + var/list/target_area_list = GLOB.the_station_areas.Copy() - filter_area_list + if(!length(target_area_list)) + return + selected_area = pick(target_area_list) + COOLDOWN_START(src, area_reroll, 2 MINUTES) + +/datum/computer_file/program/virtual_pet/proc/drop_feed() + if(!istype(get_area(computer), selected_area)) + return + announce_global_updates(message = "has found a chocolate at [selected_area.name]") + selected_area = null + var/obj/item/food/virtual_chocolate/chocolate = new(get_turf(computer)) + chocolate.AddElement(/datum/element/temporary_atom, life_time = 30 SECONDS) //we cant maintain its existence for too long! + +/datum/computer_file/program/virtual_pet/proc/recall_pet() + animate(pet, transform = matrix().Scale(0.3, 0.3), time = 1.5 SECONDS) + addtimer(CALLBACK(pet, TYPE_PROC_REF(/atom/movable, forceMove), computer), 1.5 SECONDS) + +/datum/computer_file/program/virtual_pet/proc/release_pet(mob/living/our_user) + var/turf/drop_zone + var/list/turfs_list = get_adjacent_open_turfs(computer.drop_location()) + for(var/turf/possible_turf as anything in turfs_list) + if(possible_turf.is_blocked_turf()) + continue + drop_zone = possible_turf + break + var/turf/final_turf = isnull(drop_zone) ? computer.drop_location() : drop_zone + pet.befriend(our_user) //befriend whoever set us out + animate(pet, transform = matrix(), time = 1.5 SECONDS) + pet.forceMove(final_turf) + playsound(computer.loc, 'sound/items/orbie_send_out.ogg', 20) + new /obj/effect/temp_visual/guardian/phase(pet.loc) + +#undef PET_MAX_LEVEL +#undef PET_MAX_STEPS_RECORD +#undef PET_EAT_BONUS +#undef PET_CLEAN_BONUS +#undef PET_PLAYMATE_BONUS +#undef PET_STATE_HUNGRY +#undef PET_STATE_ASLEEP +#undef PET_STATE_HAPPY +#undef PET_STATE_NEUTRAL +#undef MAX_UPDATE_LENGTH diff --git a/code/modules/paperwork/fax.dm b/code/modules/paperwork/fax.dm index f9eafa901aa51..f2fece875d86b 100644 --- a/code/modules/paperwork/fax.dm +++ b/code/modules/paperwork/fax.dm @@ -56,6 +56,15 @@ GLOBAL_VAR_INIT(nt_fax_department, pick("NT HR Department", "NT Legal Department syndicate = list(fax_name = "Sabotage Department", fax_id = "syndicate", color = "red", emag_needed = TRUE), ) +/obj/machinery/fax/auto_name + name = "Auto-naming Fax Machine" + +/obj/machinery/fax/auto_name/Initialize(mapload) + var/area/current_area = get_area(src) + name = "[current_area.name]'s Fax Machine" + fax_name = "[current_area.name]" + return ..() + /obj/machinery/fax/Initialize(mapload) . = ..() if (!fax_id) diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm index 10a15e88196cc..27072b8674e13 100644 --- a/code/modules/paperwork/pen.dm +++ b/code/modules/paperwork/pen.dm @@ -327,7 +327,7 @@ item_flags = NO_BLOOD_ON_ITEM light_system = OVERLAY_LIGHT light_range = 1.5 - light_power = 0.75 + light_power = 1.3 light_color = COLOR_SOFT_RED light_on = FALSE dart_insert_projectile_icon_state = "overlay_edagger" diff --git a/code/modules/paperwork/photocopier.dm b/code/modules/paperwork/photocopier.dm index e5a30474f8721..e55225b1ca00a 100644 --- a/code/modules/paperwork/photocopier.dm +++ b/code/modules/paperwork/photocopier.dm @@ -161,7 +161,7 @@ GLOBAL_LIST_INIT(paper_blanks, init_paper_blanks()) data["is_photo"] = TRUE data["color_mode"] = color_mode - if(isAI(user)) + if(HAS_AI_ACCESS(user)) data["isAI"] = TRUE data["can_AI_print"] = toner_cartridge && (toner_cartridge.charges >= PHOTO_TONER_USE) && (get_paper_count() >= PHOTO_PAPER_USE) else @@ -199,7 +199,7 @@ GLOBAL_LIST_INIT(paper_blanks, init_paper_blanks()) else to_chat(usr, span_notice("You feel kind of silly, copying [ass]\'s ass with [ass.p_their()] clothes on.")) return FALSE - do_copies(CALLBACK(src, PROC_REF(make_ass_copy), usr), usr, ASS_PAPER_USE, ASS_TONER_USE, num_copies) + do_copies(CALLBACK(src, PROC_REF(make_ass_copy)), usr, ASS_PAPER_USE, ASS_TONER_USE, num_copies) return TRUE else // Basic paper @@ -489,24 +489,13 @@ GLOBAL_LIST_INIT(paper_blanks, init_paper_blanks()) * Calls `check_ass()` first to make sure that `ass` exists, among other conditions. Since this proc is called from a timer, it's possible that it was removed. * Additionally checks that the mob has their clothes off. */ -/obj/machinery/photocopier/proc/make_ass_copy(mob/user) +/obj/machinery/photocopier/proc/make_ass_copy() if(!check_ass()) return null - var/icon/temp_img - if(ishuman(ass)) - var/mob/living/carbon/human/H = ass - var/datum/species/spec = H.dna.species - if(spec.ass_image) - temp_img = icon(spec.ass_image) - else - temp_img = icon(ass.gender == FEMALE ? 'icons/ass/assfemale.png' : 'icons/ass/assmale.png') - else if(isalienadult(ass)) //Xenos have their own asses, thanks to Pybro. - temp_img = icon('icons/ass/assalien.png') - else if(issilicon(ass)) - temp_img = icon('icons/ass/assmachine.png') - else if(isdrone(ass)) //Drones are hot - temp_img = icon('icons/ass/assdrone.png') - + var/butt_icon_state = ass.get_butt_sprite() + if(isnull(butt_icon_state)) + return null + var/icon/temp_img = icon('icons/mob/butts.dmi', butt_icon_state) var/obj/item/photo/copied_ass = new /obj/item/photo(src) var/datum/picture/toEmbed = new(name = "[ass]'s Ass", desc = "You see [ass]'s ass on the photo.", image = temp_img) toEmbed.psize_x = 128 @@ -629,7 +618,7 @@ GLOBAL_LIST_INIT(paper_blanks, init_paper_blanks()) * Returns FALSE if `ass` doesn't exist or is not at the copier's location. Returns TRUE otherwise. */ /obj/machinery/photocopier/proc/check_ass() //I'm not sure wether I made this proc because it's good form or because of the name. - if(!ass) + if(!isliving(ass)) return FALSE if(ass.loc != loc) ass = null diff --git a/code/modules/photography/camera/camera.dm b/code/modules/photography/camera/camera.dm index 3f721c1cefc3b..0d5f37cb86737 100644 --- a/code/modules/photography/camera/camera.dm +++ b/code/modules/photography/camera/camera.dm @@ -10,8 +10,8 @@ worn_icon_state = "camera" lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' - light_system = OVERLAY_LIGHT //Used as a flash here. - light_range = 8 + light_system = OVERLAY_LIGHT_DIRECTIONAL //Used as a flash here. + light_range = 6 light_color = COLOR_WHITE light_power = FLASH_LIGHT_POWER light_on = FALSE @@ -229,7 +229,7 @@ var/datum/picture/picture = new("picture", desc.Join(" "), mobs_spotted, dead_spotted, names, get_icon, null, psize_x, psize_y, blueprints, can_see_ghosts = see_ghosts) after_picture(user, picture) - SEND_SIGNAL(src, COMSIG_CAMERA_IMAGE_CAPTURED, target, user) + SEND_SIGNAL(src, COMSIG_CAMERA_IMAGE_CAPTURED, target, user, picture) blending = FALSE return picture diff --git a/code/modules/power/apc/apc_attack.dm b/code/modules/power/apc/apc_attack.dm index 509eb4f05b90d..88271a6b1fc24 100644 --- a/code/modules/power/apc/apc_attack.dm +++ b/code/modules/power/apc/apc_attack.dm @@ -221,7 +221,7 @@ return stomach.drain_time = world.time + APC_DRAIN_TIME addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, balloon_alert), ethereal, "draining power"), alert_timer_duration) - if(do_after(user, APC_DRAIN_TIME, target = src)) + while(do_after(user, APC_DRAIN_TIME, target = src)) if(cell.charge <= (cell.maxcharge / 2) || (stomach.crystal_charge > charge_limit)) return balloon_alert(ethereal, "received charge") @@ -243,9 +243,10 @@ balloon_alert(ethereal, "can't transfer power!") return if(istype(stomach)) - balloon_alert(ethereal, "transferred power") - stomach.adjust_charge(-APC_POWER_GAIN) - cell.give(APC_POWER_GAIN) + while(do_after(user, APC_DRAIN_TIME, target = src)) + balloon_alert(ethereal, "transferred power") + stomach.adjust_charge(-APC_POWER_GAIN) + cell.give(APC_POWER_GAIN) else balloon_alert(ethereal, "can't transfer power!") @@ -288,7 +289,7 @@ /obj/machinery/power/apc/proc/can_use(mob/user, loud = 0) //used by attack_hand() and Topic() if(isAdminGhostAI(user)) return TRUE - if(!user.has_unlimited_silicon_privilege) + if(!HAS_SILICON_ACCESS(user)) return TRUE var/mob/living/silicon/ai/AI = user var/mob/living/silicon/robot/robot = user diff --git a/code/modules/power/apc/apc_main.dm b/code/modules/power/apc/apc_main.dm index ae120a6d3d70a..9f900c5a4c793 100644 --- a/code/modules/power/apc/apc_main.dm +++ b/code/modules/power/apc/apc_main.dm @@ -326,7 +326,7 @@ "totalLoad" = display_power(lastused_total), "coverLocked" = coverlocked, "remoteAccess" = (user == remote_control_user), - "siliconUser" = user.has_unlimited_silicon_privilege, + "siliconUser" = HAS_SILICON_ACCESS(user), "malfStatus" = get_malf_status(user), "emergencyLights" = !emergency_lights, "nightshiftLights" = nightshift_lights, @@ -401,11 +401,11 @@ /obj/machinery/power/apc/ui_act(action, params) . = ..() - if(. || !can_use(usr, 1) || (locked && !usr.has_unlimited_silicon_privilege && !failure_timer && action != "toggle_nightshift")) + if(. || !can_use(usr, 1) || (locked && !HAS_SILICON_ACCESS(usr) && !failure_timer && action != "toggle_nightshift")) return switch(action) if("lock") - if(usr.has_unlimited_silicon_privilege) + if(HAS_SILICON_ACCESS(usr)) if((obj_flags & EMAGGED) || (machine_stat & (BROKEN|MAINT)) || remote_control_user) to_chat(usr, span_warning("The APC does not respond to the command!")) else @@ -442,7 +442,7 @@ update() . = TRUE if("overload") - if(usr.has_unlimited_silicon_privilege) + if(HAS_SILICON_ACCESS(usr)) overload_lighting() . = TRUE if("hack") diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm index ec6c23b00c6f6..795ff6f099a74 100644 --- a/code/modules/power/cell.dm +++ b/code/modules/power/cell.dm @@ -246,7 +246,7 @@ return to_chat(H, span_notice("You begin clumsily channeling power from [src] into your body.")) stomach.drain_time = world.time + CELL_DRAIN_TIME - if(do_after(user, CELL_DRAIN_TIME, target = src)) + while(do_after(user, CELL_DRAIN_TIME, target = src)) if((charge < CELL_POWER_DRAIN) || (stomach.crystal_charge > charge_limit)) return if(istype(stomach)) diff --git a/code/modules/power/lighting/light.dm b/code/modules/power/lighting/light.dm index 45a8c71d7652a..41485539afa5b 100644 --- a/code/modules/power/lighting/light.dm +++ b/code/modules/power/lighting/light.dm @@ -122,6 +122,7 @@ /obj/machinery/light/LateInitialize() . = ..() +#ifndef MAP_TEST switch(fitting) if("tube") if(prob(2)) @@ -129,6 +130,7 @@ if("bulb") if(prob(5)) break_light_tube(TRUE) +#endif update(trigger = FALSE) /obj/machinery/light/Destroy() diff --git a/code/modules/power/monitor.dm b/code/modules/power/monitor.dm index 32e461ba8f8a0..3d4c92d8b19e2 100644 --- a/code/modules/power/monitor.dm +++ b/code/modules/power/monitor.dm @@ -8,7 +8,6 @@ light_color = LIGHT_COLOR_DIM_YELLOW use_power = ACTIVE_POWER_USE circuit = /obj/item/circuitboard/computer/powermonitor - tgui_id = "PowerMonitor" var/datum/weakref/attached_wire_ref var/datum/weakref/local_apc_ref diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm index b21b26dcea0ae..5b712d52da2ff 100644 --- a/code/modules/power/singularity/singularity.dm +++ b/code/modules/power/singularity/singularity.dm @@ -50,7 +50,7 @@ pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE | PASSCLOSEDTURF | PASSMACHINE | PASSSTRUCTURE | PASSDOORS flags_1 = SUPERMATTER_IGNORES_1 - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF | SHUTTLE_CRUSH_PROOF obj_flags = CAN_BE_HIT | DANGEROUS_POSSESSION /obj/singularity/Initialize(mapload, starting_energy = 50) diff --git a/code/modules/power/tesla/energy_ball.dm b/code/modules/power/tesla/energy_ball.dm index 2b625b36d5bf7..89dec17a26eca 100644 --- a/code/modules/power/tesla/energy_ball.dm +++ b/code/modules/power/tesla/energy_ball.dm @@ -26,7 +26,7 @@ obj_flags = CAN_BE_HIT | DANGEROUS_POSSESSION pixel_x = -32 pixel_y = -32 - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF | SHUTTLE_CRUSH_PROOF flags_1 = SUPERMATTER_IGNORES_1 var/energy diff --git a/code/modules/projectiles/boxes_magazines/internal/shotgun.dm b/code/modules/projectiles/boxes_magazines/internal/shotgun.dm index dfd99e24766f2..3b2489022ea45 100644 --- a/code/modules/projectiles/boxes_magazines/internal/shotgun.dm +++ b/code/modules/projectiles/boxes_magazines/internal/shotgun.dm @@ -13,6 +13,12 @@ /obj/item/ammo_box/magazine/internal/shot/tube/fire ammo_type = /obj/projectile/bullet/incendiary/shotgun/no_trail +/obj/item/ammo_box/magazine/internal/shot/tube/buckshot + ammo_type = /obj/item/ammo_casing/shotgun/buckshot + +/obj/item/ammo_box/magazine/internal/shot/tube/slug + ammo_type = /obj/item/ammo_casing/shotgun + /obj/item/ammo_box/magazine/internal/shot/lethal ammo_type = /obj/item/ammo_casing/shotgun/buckshot diff --git a/code/modules/projectiles/guns/ballistic.dm b/code/modules/projectiles/guns/ballistic.dm index 69668cfaf40d0..6984d0f1bb8b1 100644 --- a/code/modules/projectiles/guns/ballistic.dm +++ b/code/modules/projectiles/guns/ballistic.dm @@ -130,6 +130,12 @@ ///What is the cap on our misfire probability? Do not set this to 100. var/misfire_probability_cap = 25 + /// Fire Selector Variables /// + /// Tracks the firemode of burst weapons. TRUE means it is in burst mode. + var/burst_fire_selection = FALSE + /// If it has an icon for a selector switch indicating current firemode. + var/selector_switch_icon = FALSE + /obj/item/gun/ballistic/Initialize(mapload) . = ..() if(!spawn_magazine_type) @@ -200,6 +206,14 @@ /obj/item/gun/ballistic/update_overlays() . = ..() + + if(selector_switch_icon) + switch(burst_fire_selection) + if(FALSE) + . += "[initial(icon_state)]_semi" + if(TRUE) + . += "[initial(icon_state)]_burst" + if(show_bolt_icon) if (bolt_type == BOLT_TYPE_LOCKING) . += "[icon_state]_bolt[bolt_locked ? "_locked" : ""]" @@ -249,6 +263,27 @@ if(capacity_number) . += "[icon_state]_mag_[capacity_number]" +/obj/item/gun/ballistic/ui_action_click(mob/user, actiontype) + if(istype(actiontype, /datum/action/item_action/toggle_firemode)) + burst_select() + else + ..() + +/obj/item/gun/ballistic/proc/burst_select() + var/mob/living/carbon/human/user = usr + burst_fire_selection = !burst_fire_selection + if(!burst_fire_selection) + burst_size = 1 + fire_delay = 0 + balloon_alert(user, "switched to semi-automatic") + else + burst_size = initial(burst_size) + fire_delay = initial(fire_delay) + balloon_alert(user, "switched to [burst_size]-round burst") + + playsound(user, 'sound/weapons/empty.ogg', 100, TRUE) + update_appearance() + update_item_action_buttons() /obj/item/gun/ballistic/handle_chamber(empty_chamber = TRUE, from_firing = TRUE, chamber_next_round = TRUE) if(!semi_auto && from_firing) @@ -264,8 +299,7 @@ casing.bounce_away(TRUE) SEND_SIGNAL(casing, COMSIG_CASING_EJECTED) else if(empty_chamber) - UnregisterSignal(chambered, COMSIG_MOVABLE_MOVED) - chambered = null + clear_chambered() if (chamber_next_round && (magazine?.max_ammo > 1)) chamber_round() diff --git a/code/modules/projectiles/guns/ballistic/automatic.dm b/code/modules/projectiles/guns/ballistic/automatic.dm index 70e2210a4e992..3d6940692d890 100644 --- a/code/modules/projectiles/guns/ballistic/automatic.dm +++ b/code/modules/projectiles/guns/ballistic/automatic.dm @@ -9,41 +9,7 @@ fire_sound_volume = 90 rack_sound = 'sound/weapons/gun/smg/smgrack.ogg' suppressed_sound = 'sound/weapons/gun/smg/shot_suppressed.ogg' - var/select = 1 ///fire selector position. 1 = semi, 2 = burst. anything past that can vary between guns. - var/selector_switch_icon = FALSE ///if it has an icon for a selector switch indicating current firemode. - -/obj/item/gun/ballistic/automatic/update_overlays() - . = ..() - if(!selector_switch_icon) - return - - switch(select) - if(0) - . += "[initial(icon_state)]_semi" - if(1) - . += "[initial(icon_state)]_burst" - -/obj/item/gun/ballistic/automatic/ui_action_click(mob/user, actiontype) - if(istype(actiontype, /datum/action/item_action/toggle_firemode)) - burst_select() - else - ..() - -/obj/item/gun/ballistic/automatic/proc/burst_select() - var/mob/living/carbon/human/user = usr - select = !select - if(!select) - burst_size = 1 - fire_delay = 0 - balloon_alert(user, "switched to semi-automatic") - else - burst_size = initial(burst_size) - fire_delay = initial(fire_delay) - balloon_alert(user, "switched to [burst_size]-round burst") - - playsound(user, 'sound/weapons/empty.ogg', 100, TRUE) - update_appearance() - update_item_action_buttons() + burst_fire_selection = TRUE /obj/item/gun/ballistic/automatic/proto name = "\improper Nanotrasen Saber SMG" @@ -159,7 +125,7 @@ /obj/item/gun/ballistic/automatic/m90 name = "\improper M-90gl Carbine" - desc = "A three-round burst 5.56 toploading carbine, designated 'M-90gl'. Has an attached underbarrel grenade launcher." + desc = "A three-round burst .223 toploading carbine, designated 'M-90gl'. Has an attached underbarrel grenade launcher." desc_controls = "Right-click to use grenade launcher." icon_state = "m90" w_class = WEIGHT_CLASS_BULKY @@ -205,14 +171,6 @@ else ..() -/obj/item/gun/ballistic/automatic/m90/update_overlays() - . = ..() - switch(select) - if(0) - . += "[initial(icon_state)]_semi" - if(1) - . += "[initial(icon_state)]_burst" - /obj/item/gun/ballistic/automatic/tommygun name = "\improper Thompson SMG" desc = "Based on the classic 'Chicago Typewriter'." diff --git a/code/modules/projectiles/guns/ballistic/shotgun.dm b/code/modules/projectiles/guns/ballistic/shotgun.dm index 8a6f15e9a981d..a45511193b767 100644 --- a/code/modules/projectiles/guns/ballistic/shotgun.dm +++ b/code/modules/projectiles/guns/ballistic/shotgun.dm @@ -97,6 +97,10 @@ desc = "An advanced shotgun with two separate magazine tubes. This one shows signs of bounty hunting customization, meaning it likely has a dual rubber shot/fire slug load." alt_mag_type = /obj/item/ammo_box/magazine/internal/shot/tube/fire +/obj/item/gun/ballistic/shotgun/automatic/dual_tube/deadly + spawn_magazine_type = /obj/item/ammo_box/magazine/internal/shot/tube/buckshot + alt_mag_type = /obj/item/ammo_box/magazine/internal/shot/tube/slug + /obj/item/gun/ballistic/shotgun/automatic/dual_tube/examine(mob/user) . = ..() . += span_notice("Alt-click to pump it.") @@ -136,7 +140,7 @@ /obj/item/gun/ballistic/shotgun/bulldog name = "\improper Bulldog Shotgun" - desc = "A semi-auto, mag-fed shotgun for combat in narrow corridors, nicknamed 'Bulldog' by boarding parties. Compatible only with specialized 8-round drum magazines. Can have a secondary magazine attached to quickly swap between ammo types, or just to keep shooting." + desc = "A 2-round burst fire, mag-fed shotgun for combat in narrow corridors, nicknamed 'Bulldog' by boarding parties. Compatible only with specialized 8-round drum magazines. Can have a secondary magazine attached to quickly swap between ammo types, or just to keep shooting." icon_state = "bulldog" inhand_icon_state = "bulldog" worn_icon_state = "cshotgun" @@ -148,11 +152,11 @@ weapon_weight = WEAPON_MEDIUM accepted_magazine_type = /obj/item/ammo_box/magazine/m12g can_suppress = FALSE - burst_size = 1 - fire_delay = 0 + burst_size = 2 + fire_delay = 1 pin = /obj/item/firing_pin/implant/pindicate fire_sound = 'sound/weapons/gun/shotgun/shot_alt.ogg' - actions_types = list() + actions_types = list(/datum/action/item_action/toggle_firemode) mag_display = TRUE empty_indicator = TRUE empty_alarm = TRUE @@ -161,6 +165,7 @@ semi_auto = TRUE internal_magazine = FALSE tac_reloads = TRUE + burst_fire_selection = TRUE ///the type of secondary magazine for the bulldog var/secondary_magazine_type ///the secondary magazine diff --git a/code/modules/projectiles/guns/energy/laser.dm b/code/modules/projectiles/guns/energy/laser.dm index 561525fe6fe9d..3cafe80a9d82a 100644 --- a/code/modules/projectiles/guns/energy/laser.dm +++ b/code/modules/projectiles/guns/energy/laser.dm @@ -93,8 +93,9 @@ desc = "An energy-based laser gun that draws power from the cyborg's internal energy cell directly. So this is what freedom looks like?" use_cyborg_cell = TRUE -/obj/item/gun/energy/laser/cyborg/emp_act() - return +/obj/item/gun/energy/laser/cyborg/Initialize(mapload) + . = ..() + AddElement(/datum/element/empprotection, EMP_PROTECT_ALL) /obj/item/gun/energy/laser/scatter name = "scatter laser gun" diff --git a/code/modules/projectiles/guns/energy/pulse.dm b/code/modules/projectiles/guns/energy/pulse.dm index f441937e60a44..a589594d79628 100644 --- a/code/modules/projectiles/guns/energy/pulse.dm +++ b/code/modules/projectiles/guns/energy/pulse.dm @@ -12,8 +12,9 @@ ammo_type = list(/obj/item/ammo_casing/energy/laser/pulse, /obj/item/ammo_casing/energy/electrode, /obj/item/ammo_casing/energy/laser) cell_type = /obj/item/stock_parts/cell/pulse -/obj/item/gun/energy/pulse/emp_act(severity) - return +/obj/item/gun/energy/pulse/Initialize(mapload) + . = ..() + AddElement(/datum/element/empprotection, EMP_PROTECT_ALL) /obj/item/gun/energy/pulse/prize pin = /obj/item/firing_pin diff --git a/code/modules/projectiles/guns/energy/recharge.dm b/code/modules/projectiles/guns/energy/recharge.dm index 26fbdef6e139a..3d94193a53199 100644 --- a/code/modules/projectiles/guns/energy/recharge.dm +++ b/code/modules/projectiles/guns/energy/recharge.dm @@ -30,6 +30,7 @@ . = ..() if(!holds_charge) empty() + AddElement(/datum/element/empprotection, EMP_PROTECT_ALL) /obj/item/gun/energy/recharge/shoot_live_shot(mob/living/user, pointblank = 0, atom/pbtarget = null, message = 1) . = ..() @@ -78,9 +79,6 @@ deltimer(recharge_timerid) recharge_timerid = addtimer(CALLBACK(src, PROC_REF(reload)), set_recharge_time * carried, TIMER_STOPPABLE) -/obj/item/gun/energy/recharge/emp_act(severity) - return - /obj/item/gun/energy/recharge/proc/reload() cell.give(cell.maxcharge) if(!suppressed && recharge_sound) diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm index fc44a536fef60..922a5d71ead11 100644 --- a/code/modules/projectiles/guns/energy/special.dm +++ b/code/modules/projectiles/guns/energy/special.dm @@ -10,6 +10,10 @@ slot_flags = ITEM_SLOT_BACK ammo_type = list(/obj/item/ammo_casing/energy/ion) +/obj/item/gun/energy/ionrifle/Initialize(mapload) + . = ..() + AddElement(/datum/element/empprotection, EMP_PROTECT_ALL) + /obj/item/gun/energy/ionrifle/add_seclight_point() AddComponent(/datum/component/seclite_attachable, \ light_overlay_icon = 'icons/obj/weapons/guns/flashlights.dmi', \ @@ -17,9 +21,6 @@ overlay_x = 17, \ overlay_y = 9) -/obj/item/gun/energy/ionrifle/emp_act(severity) - return - /obj/item/gun/energy/ionrifle/carbine name = "ion carbine" desc = "The MK.II Prototype Ion Projector is a lightweight carbine version of the larger ion rifle, built to be ergonomic and efficient." @@ -130,9 +131,7 @@ ..() /obj/item/gun/energy/plasmacutter/emp_act(severity) - if(!cell.charge) - return - cell.use(cell.charge/3) + . = ..() if(isliving(loc)) var/mob/living/user = loc user.visible_message(span_danger("Concentrated plasma discharges from [src] onto [user], burning them!"), span_userdanger("[src] malfunctions, spewing concentrated plasma onto you! It burns!")) @@ -305,9 +304,7 @@ AddElement(/datum/element/update_icon_blocker) . = ..() AddComponent(/datum/component/automatic_fire, 0.3 SECONDS) - -/obj/item/gun/energy/printer/emp_act() - return + AddElement(/datum/element/empprotection, EMP_PROTECT_ALL) /obj/item/gun/energy/temperature name = "temperature gun" diff --git a/code/modules/projectiles/guns/energy/stun.dm b/code/modules/projectiles/guns/energy/stun.dm index e099176ddd018..72148267b03c7 100644 --- a/code/modules/projectiles/guns/energy/stun.dm +++ b/code/modules/projectiles/guns/energy/stun.dm @@ -19,10 +19,11 @@ can_charge = FALSE use_cyborg_cell = TRUE -/obj/item/gun/energy/e_gun/advtaser/cyborg/add_seclight_point() - return +/obj/item/gun/energy/e_gun/advtaser/cyborg/Initialize(mapload) + . = ..() + AddElement(/datum/element/empprotection, EMP_PROTECT_ALL) -/obj/item/gun/energy/e_gun/advtaser/cyborg/emp_act() +/obj/item/gun/energy/e_gun/advtaser/cyborg/add_seclight_point() return /obj/item/gun/energy/disabler @@ -66,5 +67,6 @@ can_charge = FALSE use_cyborg_cell = TRUE -/obj/item/gun/energy/disabler/cyborg/emp_act() - return +/obj/item/gun/energy/disabler/cyborg/Initialize(mapload) + . = ..() + AddElement(/datum/element/empprotection, EMP_PROTECT_ALL) diff --git a/code/modules/projectiles/pins.dm b/code/modules/projectiles/pins.dm index c4b6f6fb4ce7e..6f80bf0e21435 100644 --- a/code/modules/projectiles/pins.dm +++ b/code/modules/projectiles/pins.dm @@ -387,4 +387,5 @@ /obj/item/firing_pin/Destroy() if(gun) gun.pin = null + gun = null return ..() diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 682baac7927e2..d1dd5364477ac 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -182,6 +182,8 @@ var/catastropic_dismemberment = FALSE //If TRUE, this projectile deals its damage to the chest if it dismembers a limb. var/impact_effect_type //what type of impact effect to show when hitting something var/log_override = FALSE //is this type spammed enough to not log? (KAs) + /// If true, the projectile won't cause any logging. Used for hallucinations and shit. + var/do_not_log = FALSE /// We ignore mobs with these factions. var/list/ignored_factions @@ -359,7 +361,7 @@ if(reagents?.reagent_list) reagent_note = "REAGENTS: [pretty_string_from_reagent_list(reagents.reagent_list)]" - if(ismob(firer)) + if(ismob(firer) && !do_not_log) log_combat(firer, living_target, "shot", src, reagent_note) return BULLET_ACT_HIT @@ -369,11 +371,12 @@ var/list/logging_mobs = firing_vehicle.return_controllers_with_flag(VEHICLE_CONTROL_EQUIPMENT) if(!LAZYLEN(logging_mobs)) logging_mobs = firing_vehicle.return_drivers() - for(var/mob/logged_mob as anything in logging_mobs) - log_combat(logged_mob, living_target, "shot", src, "from inside [firing_vehicle][logging_mobs.len > 1 ? " with multiple occupants" : null][reagent_note ? " and contained [reagent_note]" : null]") + if(!do_not_log) + for(var/mob/logged_mob as anything in logging_mobs) + log_combat(logged_mob, living_target, "shot", src, "from inside [firing_vehicle][logging_mobs.len > 1 ? " with multiple occupants" : null][reagent_note ? " and contained [reagent_note]" : null]") return BULLET_ACT_HIT - - living_target.log_message("has been shot by [firer] with [src][reagent_note ? " containing [reagent_note]" : null]", LOG_ATTACK, color="orange") + if(!do_not_log) + living_target.log_message("has been shot by [firer] with [src][reagent_note ? " containing [reagent_note]" : null]", LOG_ATTACK, color="orange") return BULLET_ACT_HIT /obj/projectile/proc/vol_by_damage() @@ -771,7 +774,7 @@ SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_BEFORE_FIRE, src, original) if(firer) SEND_SIGNAL(firer, COMSIG_PROJECTILE_FIRER_BEFORE_FIRE, src, fired_from, original) - if(!log_override && firer && original) + if(!log_override && firer && original && !do_not_log) log_combat(firer, original, "fired at", src, "from [get_area_name(src, TRUE)]") //note: mecha projectile logging is handled in /obj/item/mecha_parts/mecha_equipment/weapon/action(). try to keep these messages roughly the sameish just for consistency's sake. if(direct_target && (get_dist(direct_target, get_turf(src)) <= 1)) // point blank shots diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm index e902a0e142984..e75bbda4d8c5c 100644 --- a/code/modules/projectiles/projectile/beams.dm +++ b/code/modules/projectiles/projectile/beams.dm @@ -11,7 +11,7 @@ impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser light_system = OVERLAY_LIGHT light_range = 1 - light_power = 1 + light_power = 1.4 light_color = COLOR_SOFT_RED ricochets_max = 50 //Honk! ricochet_chance = 80 diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm index ae91fb6c60318..e52d38b3da111 100644 --- a/code/modules/projectiles/projectile/magic.dm +++ b/code/modules/projectiles/projectile/magic.dm @@ -362,24 +362,23 @@ /obj/projectile/magic/wipe/proc/possession_test(mob/living/carbon/target) var/datum/brain_trauma/special/imaginary_friend/trapped_owner/trauma = target.gain_trauma(/datum/brain_trauma/special/imaginary_friend/trapped_owner) - var/poll_message = "Do you want to play as [target.real_name]?" + var/poll_message = "Do you want to play as [span_danger(target.real_name)]?" if(target.mind) - poll_message = "[poll_message] Job:[target.mind.assigned_role.title]." + poll_message = "[poll_message] Job:[span_notice(target.mind.assigned_role.title)]." if(target.mind && target.mind.special_role) - poll_message = "[poll_message] Status:[target.mind.special_role]." + poll_message = "[poll_message] Status:[span_boldnotice(target.mind.special_role)]." else if(target.mind) var/datum/antagonist/A = target.mind.has_antag_datum(/datum/antagonist/) if(A) - poll_message = "[poll_message] Status:[A.name]." - var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates_for_mob(poll_message, check_jobban = ROLE_PAI, poll_time = 10 SECONDS, target_mob = target, pic_source = target, role_name_text = "bolt of possession") + poll_message = "[poll_message] Status:[span_boldnotice(A.name)]." + var/mob/chosen_one = SSpolling.poll_ghosts_for_target(poll_message, check_jobban = ROLE_PAI, poll_time = 10 SECONDS, checked_target = target, alert_pic = target, role_name_text = "bolt of possession") if(target.stat == DEAD)//boo. return - if(LAZYLEN(candidates)) - var/mob/dead/observer/ghost = pick(candidates) + if(chosen_one) to_chat(target, span_boldnotice("You have been noticed by a ghost and it has possessed you!")) var/oldkey = target.key target.ghostize(FALSE) - target.key = ghost.key + target.key = chosen_one.key trauma.friend.key = oldkey trauma.friend.reset_perspective(null) trauma.friend.Show() diff --git a/code/modules/reagents/chemistry/holder/holder.dm b/code/modules/reagents/chemistry/holder/holder.dm index d32cd92361b46..1d36747e8f46c 100644 --- a/code/modules/reagents/chemistry/holder/holder.dm +++ b/code/modules/reagents/chemistry/holder/holder.dm @@ -255,9 +255,9 @@ //if we reached here means we have found our specific reagent type so break if(!include_subtypes) - break + return total_removed_amount - return total_removed_amount + return round(total_removed_amount, CHEMICAL_VOLUME_ROUNDING) /** * Removes a reagent at random and by a random quantity till the specified amount has been removed. @@ -283,9 +283,8 @@ current_list_element = rand(1, cached_reagents.len) - while(total_removed != amount) - if(total_removed >= amount) - break + while(total_removed < amount) + // There's nothing left in the container if(total_volume <= 0 || !cached_reagents.len) break @@ -294,14 +293,16 @@ var/datum/reagent/target_holder = cached_reagents[current_list_element] var/remove_amt = min(amount - total_removed, round(amount / rand(2, initial_list_length), round(amount / 10, 0.01))) //double round to keep it at a somewhat even spread relative to amount without getting funky numbers. - //min ensures we don't go over amount. - remove_reagent(target_holder.type, remove_amt) + // If the logic above means removing really tiny amounts (or even zero if it's a remove amount of 10) instead choose a sensible smallish number + // so this proc will actually finish instead of looping forever + remove_amt = max(CHEMICAL_VOLUME_ROUNDING, remove_amt) + remove_amt = remove_reagent(target_holder.type, remove_amt) current_list_element++ total_removed += remove_amt - handle_reactions() - return total_removed //this should be amount unless the loop is prematurely broken, in which case it'll be lower. It shouldn't ever go OVER amount. + + return round(total_removed, CHEMICAL_VOLUME_ROUNDING) /** * Removes all reagents either proportionally(amount is the direct volume to remove) @@ -336,8 +337,8 @@ part /= total_volume for(var/datum/reagent/reagent as anything in cached_reagents) total_removed_amount += remove_reagent(reagent.type, reagent.volume * part) - handle_reactions() + return round(total_removed_amount, CHEMICAL_VOLUME_ROUNDING) /** diff --git a/code/modules/reagents/chemistry/machinery/chem_heater.dm b/code/modules/reagents/chemistry/machinery/chem_heater.dm index a07fd289f0784..e3208d8d461d7 100644 --- a/code/modules/reagents/chemistry/machinery/chem_heater.dm +++ b/code/modules/reagents/chemistry/machinery/chem_heater.dm @@ -25,15 +25,14 @@ create_reagents(200, NO_REACT) register_context() -/obj/machinery/chem_heater/on_deconstruction(disassembled) - beaker?.forceMove(drop_location()) - /obj/machinery/chem_heater/Destroy() if(beaker) UnregisterSignal(beaker.reagents, COMSIG_REAGENTS_REACTION_STEP) QDEL_NULL(beaker) return ..() +/obj/machinery/chem_heater/on_deconstruction(disassembled) + beaker?.forceMove(drop_location()) /obj/machinery/chem_heater/add_context(atom/source, list/context, obj/item/held_item, mob/user) if(isnull(held_item) || (held_item.item_flags & ABSTRACT) || (held_item.flags_1 & HOLOGRAM_1)) @@ -60,10 +59,74 @@ return NONE +/obj/machinery/chem_heater/examine(mob/user) + . = ..() + if(in_range(user, src) || isobserver(user)) + . += span_notice("The status display reads: Heating reagents at [heater_coefficient * 1000]% speed.") + if(!QDELETED(beaker)) + . += span_notice("It has a beaker of [beaker.reagents.total_volume] units capacity.") + if(beaker.reagents.is_reacting) + . += span_notice("Its contents are currently reacting.") + else + . += span_warning("There is no beaker inserted.") + . += span_notice("Its heating is turned [on ? "On" : "Off"].") + . += span_notice("The status display reads: Heating reagents at [heater_coefficient * 1000]% speed.") + if(panel_open) + . += span_notice("Its panel is open and can now be [EXAMINE_HINT("pried")] apart.") + else + . += span_notice("Its panel can be [EXAMINE_HINT("pried")] open") + /obj/machinery/chem_heater/update_icon_state() icon_state = "[base_icon_state][beaker ? 1 : 0]b" return ..() +/obj/machinery/chem_heater/Exited(atom/movable/gone, direction) + . = ..() + if(gone == beaker) + UnregisterSignal(beaker.reagents, COMSIG_REAGENTS_REACTION_STEP) + beaker = null + update_appearance() + +/obj/machinery/chem_heater/RefreshParts() + . = ..() + heater_coefficient = 0.1 + for(var/datum/stock_part/micro_laser/micro_laser in component_parts) + heater_coefficient *= micro_laser.tier + + +/obj/machinery/chem_heater/item_interaction(mob/living/user, obj/item/held_item, list/modifiers, is_right_clicking) + if((held_item.item_flags & ABSTRACT) || (held_item.flags_1 & HOLOGRAM_1)) + return ..() + + if(QDELETED(beaker)) + if(istype(held_item, /obj/item/reagent_containers/dropper) || istype(held_item, /obj/item/reagent_containers/syringe)) + var/obj/item/reagent_containers/injector = held_item + injector.afterattack(beaker, user, proximity_flag = TRUE) + return ITEM_INTERACT_SUCCESS + + if(is_reagent_container(held_item) && held_item.is_open_container()) + if(replace_beaker(user, held_item)) + ui_interact(user) + balloon_alert(user, "beaker added") + return ITEM_INTERACT_SUCCESS + + return ..() + +/obj/machinery/chem_heater/wrench_act(mob/living/user, obj/item/tool) + . = ITEM_INTERACT_BLOCKING + if(default_unfasten_wrench(user, tool) == SUCCESSFUL_UNFASTEN) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/chem_heater/screwdriver_act(mob/living/user, obj/item/tool) + . = ITEM_INTERACT_BLOCKING + if(default_deconstruction_screwdriver(user, "mixer0b", "[base_icon_state][beaker ? 1 : 0]b", tool)) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/chem_heater/crowbar_act(mob/living/user, obj/item/tool) + . = ITEM_INTERACT_BLOCKING + if(default_deconstruction_crowbar(tool)) + return ITEM_INTERACT_SUCCESS + /obj/machinery/chem_heater/attack_hand_secondary(mob/user, list/modifiers) . = ..() if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) @@ -73,13 +136,6 @@ replace_beaker(user) return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN -/obj/machinery/chem_heater/Exited(atom/movable/gone, direction) - . = ..() - if(gone == beaker) - UnregisterSignal(beaker.reagents, COMSIG_REAGENTS_REACTION_STEP) - beaker = null - update_appearance() - /obj/machinery/chem_heater/attack_robot_secondary(mob/user, list/modifiers) return attack_hand_secondary(user, modifiers) @@ -109,12 +165,6 @@ return TRUE -/obj/machinery/chem_heater/RefreshParts() - . = ..() - heater_coefficient = 0.1 - for(var/datum/stock_part/micro_laser/micro_laser in component_parts) - heater_coefficient *= micro_laser.tier - /** * Heats the reagents of the currently inserted beaker only if machine is on & beaker has some reagents inside * Arguments @@ -142,23 +192,6 @@ for(var/datum/tgui/ui in src.open_uis) ui.send_update() -/obj/machinery/chem_heater/examine(mob/user) - . = ..() - if(in_range(user, src) || isobserver(user)) - . += span_notice("The status display reads: Heating reagents at [heater_coefficient * 1000]% speed.") - if(!QDELETED(beaker)) - . += span_notice("It has a beaker of [beaker.reagents.total_volume] units capacity.") - if(beaker.reagents.is_reacting) - . += span_notice("Its contents are currently reacting.") - else - . += span_warning("There is no beaker inserted.") - . += span_notice("Its heating is turned [on ? "On" : "Off"].") - . += span_notice("The status display reads: Heating reagents at [heater_coefficient * 1000]% speed.") - if(panel_open) - . += span_notice("Its panel is open and can now be [EXAMINE_HINT("pried")] apart.") - else - . += span_notice("Its panel can be [EXAMINE_HINT("pried")] open") - /obj/machinery/chem_heater/process(seconds_per_tick) //is_reacting is handled in reaction_step() if(QDELETED(beaker) || beaker.reagents.is_reacting) @@ -172,39 +205,6 @@ for(var/datum/tgui/ui in src.open_uis) ui.send_update() -/obj/machinery/chem_heater/wrench_act(mob/living/user, obj/item/tool) - . = ITEM_INTERACT_BLOCKING - if(default_unfasten_wrench(user, tool) == SUCCESSFUL_UNFASTEN) - return ITEM_INTERACT_SUCCESS - -/obj/machinery/chem_heater/screwdriver_act(mob/living/user, obj/item/tool) - . = ITEM_INTERACT_BLOCKING - if(default_deconstruction_screwdriver(user, "mixer0b", "[base_icon_state][beaker ? 1 : 0]b", tool)) - return ITEM_INTERACT_SUCCESS - -/obj/machinery/chem_heater/crowbar_act(mob/living/user, obj/item/tool) - . = ITEM_INTERACT_BLOCKING - if(default_deconstruction_crowbar(tool)) - return ITEM_INTERACT_SUCCESS - -/obj/machinery/chem_heater/attackby(obj/item/held_item, mob/user, params) - if((held_item.item_flags & ABSTRACT) || (held_item.flags_1 & HOLOGRAM_1)) - return ..() - - if(beaker) - if(istype(held_item, /obj/item/reagent_containers/dropper) || istype(held_item, /obj/item/reagent_containers/syringe)) - var/obj/item/reagent_containers/injector = held_item - injector.afterattack(beaker, user, proximity_flag = TRUE) - return TRUE - - if(is_reagent_container(held_item) && held_item.is_open_container()) - if(replace_beaker(user, held_item)) - ui_interact(user) - balloon_alert(user, "beaker added!") - return TRUE - - return ..() - /obj/machinery/chem_heater/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) if(!ui) diff --git a/code/modules/reagents/chemistry/machinery/chem_mass_spec.dm b/code/modules/reagents/chemistry/machinery/chem_mass_spec.dm index c23a86ca9dc86..e9ffc91cd3464 100644 --- a/code/modules/reagents/chemistry/machinery/chem_mass_spec.dm +++ b/code/modules/reagents/chemistry/machinery/chem_mass_spec.dm @@ -1,21 +1,16 @@ - -#define BEAKER1 1 -#define BEAKER2 2 - /obj/machinery/chem_mass_spec name = "High-performance liquid chromatography machine" - desc = {"This machine can separate reagents based on charge, meaning it can clean reagents of some of their impurities, unlike the Chem Master 3000. -By selecting a range in the mass spectrograph certain reagents will be transferred from one beaker to another, which will clean it of any impurities up to a certain amount. -This will not clean any inverted reagents. Inverted reagents will still be correctly detected and displayed on the scanner, however. -\nLeft click with a beaker to add it to the input slot, Right click with a beaker to add it to the output slot. Alt + left/right click can let you quickly remove the corresponding beaker."} - density = TRUE - layer = BELOW_OBJ_LAYER + desc = "Allows you to purify reagents & seperate out inverse reagents" icon = 'icons/obj/medical/chemical.dmi' icon_state = "HPLC" base_icon_state = "HPLC" + density = TRUE + interaction_flags_atom = parent_type::interaction_flags_atom | INTERACT_ATOM_REQUIRES_ANCHORED idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.2 resistance_flags = FIRE_PROOF | ACID_PROOF + processing_flags = START_PROCESSING_MANUALLY circuit = /obj/item/circuitboard/machine/chem_mass_spec + ///If we're processing reagents or not var/processing_reagents = FALSE ///Time we started processing + the delay @@ -37,157 +32,244 @@ This will not clean any inverted reagents. Inverted reagents will still be corre /obj/machinery/chem_mass_spec/Initialize(mapload) . = ..() + ADD_TRAIT(src, TRAIT_DO_NOT_SPLASH, INNATE_TRAIT) + if(mapload) beaker2 = new /obj/item/reagent_containers/cup/beaker/large(src) - AddElement( \ - /datum/element/contextual_screentip_bare_hands, \ - lmb_text = "Add input beaker", \ - rmb_text = "Add output beaker", \ - ) + register_context() /obj/machinery/chem_mass_spec/Destroy() QDEL_NULL(beaker1) QDEL_NULL(beaker2) return ..() -/obj/machinery/chem_mass_spec/RefreshParts() +/obj/machinery/chem_mass_spec/on_deconstruction(disassembled) + var/location = drop_location() + beaker1?.forceMove(location) + beaker2?.forceMove(location) + +/obj/machinery/chem_mass_spec/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = NONE + + if(!QDELETED(beaker1)) + context[SCREENTIP_CONTEXT_ALT_LMB] = "Eject input beaker" + . = CONTEXTUAL_SCREENTIP_SET + if(!QDELETED(beaker2)) + context[SCREENTIP_CONTEXT_ALT_RMB] = "Eject output beaker" + . = CONTEXTUAL_SCREENTIP_SET + + if(isnull(held_item) || (held_item.item_flags & ABSTRACT) || (held_item.flags_1 & HOLOGRAM_1)) + return + + if(is_reagent_container(held_item)) + if(QDELETED(beaker1)) + context[SCREENTIP_CONTEXT_LMB] = "Insert input beaker" + else + context[SCREENTIP_CONTEXT_LMB] = "Replace input beaker" + + if(QDELETED(beaker2)) + context[SCREENTIP_CONTEXT_RMB] = "Insert output beaker" + else + context[SCREENTIP_CONTEXT_RMB] = "Replace output beaker" + + return CONTEXTUAL_SCREENTIP_SET + + if(held_item.tool_behaviour == TOOL_WRENCH) + context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Un" : ""]anchor" + return CONTEXTUAL_SCREENTIP_SET + else if(held_item.tool_behaviour == TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] panel" + return CONTEXTUAL_SCREENTIP_SET + else if(panel_open && held_item.tool_behaviour == TOOL_CROWBAR) + context[SCREENTIP_CONTEXT_LMB] = "Deconstruct" + return CONTEXTUAL_SCREENTIP_SET + +/obj/machinery/chem_mass_spec/examine(mob/user) . = ..() - cms_coefficient = 1 - for(var/datum/stock_part/micro_laser/laser in component_parts) - cms_coefficient /= laser.tier -/obj/machinery/chem_mass_spec/on_deconstruction(disassembled) - if(beaker1) - beaker1.forceMove(drop_location()) - beaker1 = null - if(beaker2) - beaker2.forceMove(drop_location()) - beaker2 = null + if(!QDELETED(beaker1)) + . += span_notice("Input beaker of [beaker1.reagents.maximum_volume]u capacity is inserted.") + . += span_notice("Its Input beaker Can be ejected with [EXAMINE_HINT("LMB Alt")] click.") + else + . += span_warning("Its missing an input beaker. insert with [EXAMINE_HINT("Left Click")].") + if(!QDELETED(beaker2)) + . += span_notice("Output beaker of [beaker2.reagents.maximum_volume]u capacity is inserted.") + . += span_notice("Its Output beaker can be ejected with [EXAMINE_HINT("RMB Alt")] click.") + else + . += span_warning("Its missing an output beaker, insert with [EXAMINE_HINT("Right Click")].") + + if(anchored) + . += span_notice("Its [EXAMINE_HINT("anchored")] in place.") + else + . += span_warning("Needs to be [EXAMINE_HINT("wrenched")] to use.") + . += span_notice("Its maintainence panel can be [EXAMINE_HINT("screwed")] [panel_open ? "closed" : "open"].") + if(panel_open) + . += span_notice("It can be [EXAMINE_HINT("pried")] apart.") /obj/machinery/chem_mass_spec/update_overlays() . = ..() + if(panel_open) . += mutable_appearance(icon, "[base_icon_state]_panel-o") + return -/obj/machinery/chem_mass_spec/wrench_act(mob/living/user, obj/item/tool) + if(!QDELETED(beaker1)) + . += "HPLC_beaker1" + if(!QDELETED(beaker2)) + . += "HPLC_beaker2" + + if(is_operational && !panel_open && anchored && !(machine_stat & (BROKEN | NOPOWER))) + if(processing_reagents) + . += "HPLC_graph_active" + else if (length(beaker1?.reagents.reagent_list)) + . += "HPLC_graph_idle" + +/obj/machinery/chem_mass_spec/update_icon_state() + if(is_operational && !panel_open && anchored && !(machine_stat & (BROKEN | NOPOWER))) + icon_state = "HPLC_on" + else + icon_state = "HPLC" + return ..() + +/obj/machinery/chem_mass_spec/Exited(atom/movable/gone, direction) + . = ..() + if(gone == beaker1) + beaker1 = null + if(gone == beaker2) + beaker2 = null + +/obj/machinery/chem_mass_spec/RefreshParts() . = ..() - default_unfasten_wrench(user, tool) - return ITEM_INTERACT_SUCCESS -/* beaker swapping/attack code */ -/obj/machinery/chem_mass_spec/attackby(obj/item/item, mob/user, params) - if(processing_reagents) - to_chat(user, " The [src] is currently processing a batch!") + cms_coefficient = 1 + for(var/datum/stock_part/micro_laser/laser in component_parts) + cms_coefficient /= laser.tier + +/obj/machinery/chem_mass_spec/item_interaction(mob/living/user, obj/item/item, list/modifiers, is_right_clicking) + if((item.item_flags & ABSTRACT) || (item.flags_1 & HOLOGRAM_1) || !can_interact(user) || !user.can_perform_action(src, FORBID_TELEKINESIS_REACH)) return ..() - if(default_deconstruction_screwdriver(user, icon_state, icon_state, item)) - update_appearance() - return + if(is_reagent_container(item) && item.is_open_container()) + if(processing_reagents) + balloon_alert(user, "still processing!") + return ITEM_INTERACT_BLOCKING - if(is_reagent_container(item) && !(item.item_flags & ABSTRACT) && item.is_open_container()) var/obj/item/reagent_containers/beaker = item - . = TRUE //no afterattack if(!user.transferItemToLoc(beaker, src)) - return - replace_beaker(user, BEAKER1, beaker) - to_chat(user, span_notice("You add [beaker] to [src].")) + return ITEM_INTERACT_BLOCKING + + replace_beaker(user, !is_right_clicking, beaker) + to_chat(user, span_notice("You add [beaker] to [is_right_clicking ? "output" : "input"] slot.")) update_appearance() ui_interact(user) - return - ..() + return ITEM_INTERACT_SUCCESS -/obj/machinery/chem_mass_spec/attackby_secondary(obj/item/item, mob/user, params) - . = ..() + return ..() +/obj/machinery/chem_mass_spec/wrench_act(mob/living/user, obj/item/tool) + . = ITEM_INTERACT_BLOCKING if(processing_reagents) - to_chat(user, " The [src] is currently processing a batch!") - return + balloon_alert(user, "still processing!") + return . - if(default_deconstruction_crowbar(item)) - return + if(default_unfasten_wrench(user, tool) == SUCCESSFUL_UNFASTEN) + return ITEM_INTERACT_SUCCESS - if(is_reagent_container(item) && !(item.item_flags & ABSTRACT) && item.is_open_container()) - var/obj/item/reagent_containers/beaker = item - if(!user.transferItemToLoc(beaker, src)) - return - replace_beaker(user, BEAKER2, beaker) - to_chat(user, span_notice("You add [beaker] to [src].")) - ui_interact(user) - . = SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN +/obj/machinery/chem_mass_spec/screwdriver_act(mob/living/user, obj/item/tool) + . = ITEM_INTERACT_BLOCKING + if(processing_reagents) + balloon_alert(user, "still processing!") + return . - update_appearance() + if(default_deconstruction_screwdriver(user, icon_state, icon_state, tool)) + update_appearance() + return ITEM_INTERACT_SUCCESS -/obj/machinery/chem_mass_spec/AltClick(mob/living/user) - . = ..() +/obj/machinery/chem_mass_spec/crowbar_act(mob/living/user, obj/item/tool) + . = ITEM_INTERACT_BLOCKING if(processing_reagents) - to_chat(user, " The [src] is currently processing a batch!") - return - if(!can_interact(user) || !user.can_perform_action(src, FORBID_TELEKINESIS_REACH)) - return ..() - replace_beaker(user, BEAKER1) + balloon_alert(user, "still processing!") + return . -/obj/machinery/chem_mass_spec/alt_click_secondary(mob/living/user) - . = ..() - if(processing_reagents) - to_chat(user, " The [src] is currently processing a batch!") - return - if(!can_interact(user) || !user.can_perform_action(src, FORBID_TELEKINESIS_REACH)) - return - replace_beaker(user, BEAKER2) + if(default_deconstruction_crowbar(tool)) + return ITEM_INTERACT_SUCCESS + + +/** + * Computes either the lightest or heaviest reagent in the input beaker + * Arguments + * + * * smallest - TRUE to find lightest reagent, FALSE to find heaviest reagent + */ +/obj/machinery/chem_mass_spec/proc/calculate_mass(smallest = TRUE) + PRIVATE_PROC(TRUE) + SHOULD_BE_PURE(TRUE) + + if(QDELETED(beaker1)) + return 0 + + var/result = 0 + for(var/datum/reagent/reagent as anything in beaker1?.reagents.reagent_list) + var/datum/reagent/target = reagent + if(!istype(reagent, /datum/reagent/inverse) && (reagent.inverse_chem_val > reagent.purity && reagent.inverse_chem)) + target = GLOB.chemical_reagents_list[reagent.inverse_chem] + + if(!result) + result = target.mass + else + result = smallest ? min(result, reagent.mass) : max(result, reagent.mass) + return smallest ? FLOOR(result, 50) : CEILING(result, 50) -///Gee how come you get two beakers? /* - * Similar to other replace beaker procs, except now there are two of them! - * When passed a beaker along with a position define it will swap a beaker in that slot (if there is one) with the beaker the machine is bonked with + * Replaces a beaker in the machine, either input or output + * Arguments * - * arguments: * * user - The one bonking the machine - * * target beaker - the define (BEAKER1/BEAKER2) of what position to replace + * * target beaker - the target beaker we are trying to replace * * new beaker - the new beaker to add/replace the slot with */ -/obj/machinery/chem_mass_spec/proc/replace_beaker(mob/living/user, target_beaker, obj/item/reagent_containers/new_beaker) - if(!user) - return FALSE - switch(target_beaker) - if(BEAKER1) - if(beaker1) - try_put_in_hand(beaker1, user) - beaker1 = null - beaker1 = new_beaker - lower_mass_range = calculate_smallest_mass() - upper_mass_range = calculate_largest_mass() - if(BEAKER2) - if(beaker2) - try_put_in_hand(beaker2, user) - beaker2 = null - beaker2 = new_beaker +/obj/machinery/chem_mass_spec/proc/replace_beaker(mob/living/user, is_input, obj/item/reagent_containers/new_beaker) + PRIVATE_PROC(TRUE) + + if(is_input) //replace input beaker + if(!QDELETED(beaker1)) + try_put_in_hand(beaker1, user) + beaker1 = new_beaker + lower_mass_range = calculate_mass(smallest = TRUE) + upper_mass_range = calculate_mass(smallest = FALSE) + estimate_time() + + else //replace output beaker + if(!QDELETED(beaker2)) + try_put_in_hand(beaker2, user) + beaker2 = new_beaker + update_appearance() - return TRUE -/* Icon code */ +///Computes time to purity reagents +/obj/machinery/chem_mass_spec/proc/estimate_time() + PRIVATE_PROC(TRUE) -/obj/machinery/chem_mass_spec/update_icon_state() - if(powered()) - icon_state = "HPLC_on" - else - icon_state = "HPLC" - return ..() + delay_time = 0 + if(QDELETED(beaker1)) + return -/obj/machinery/chem_mass_spec/update_overlays() - . = ..() - if(beaker1) - . += "HPLC_beaker1" - if(beaker2) - . += "HPLC_beaker2" - if(powered()) - if(processing_reagents) - . += "HPLC_graph_active" - else if (length(beaker1?.reagents.reagent_list)) - . += "HPLC_graph_idle" + for(var/datum/reagent/reagent as anything in beaker1.reagents.reagent_list) + //we don't bother about impure chems + if(istype(reagent, /datum/reagent/inverse) || (reagent.inverse_chem_val > reagent.purity && reagent.inverse_chem)) + continue + //out of our selected range + if(reagent.mass < lower_mass_range || reagent.mass > upper_mass_range) + continue + //already at max purity + if((initial(reagent.purity) - reagent.purity) <= 0) + continue + ///Roughly 10 - 30s? + delay_time += (((reagent.mass * reagent.volume) + (reagent.mass * reagent.get_inverse_purity() * 0.1)) * 0.0035) + 10 -/* UI Code */ + delay_time *= cms_coefficient /obj/machinery/chem_mass_spec/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) @@ -196,209 +278,214 @@ This will not clean any inverted reagents. Inverted reagents will still be corre ui.open() /obj/machinery/chem_mass_spec/ui_data(mob/user) - var/data = list() - data["graphLowerRange"] = 0 - data["lowerRange"] = lower_mass_range - data["upperRange"] = upper_mass_range - data["processing"] = processing_reagents - data["log"] = log - data["beaker1"] = beaker1 ? TRUE : FALSE - data["beaker2"] = beaker2 ? TRUE : FALSE - if(processing_reagents) - data["eta"] = delay_time - progress_time - else - data["eta"] = estimate_time() - - var/beakerContents[0] - if(beaker1 && beaker1.reagents) - for(var/datum/reagent/reagent as anything in beaker1.reagents.reagent_list) - var/in_range = TRUE - if(reagent.inverse_chem_val > reagent.purity && reagent.inverse_chem) - var/datum/reagent/inverse_reagent = GLOB.chemical_reagents_list[reagent.inverse_chem] - if(inverse_reagent.mass < lower_mass_range || inverse_reagent.mass > upper_mass_range) - in_range = FALSE - beakerContents.Add(list(list("name" = inverse_reagent.name, "volume" = round(reagent.volume, 0.01), "mass" = inverse_reagent.mass, "purity" = round(reagent.get_inverse_purity(), 0.000001)*100, "selected" = in_range, "color" = "#b60046", "type" = "Inverted"))) - data["peakHeight"] = max(data["peakHeight"], reagent.volume) - continue - if(reagent.mass < lower_mass_range || reagent.mass > upper_mass_range) - in_range = FALSE - ///We want to be sure that the impure chem appears after the parent chem in the list so that it always overshadows pure reagents - beakerContents.Add(list(list("name" = reagent.name, "volume" = round(reagent.volume, 0.01), "mass" = reagent.mass, "purity" = round(reagent.purity, 0.000001)*100, "selected" = in_range, "color" = "#3cf096", "type" = "Clean"))) - data["peakHeight"] = max(data["peakHeight"], reagent.volume) - - data["beaker1CurrentVolume"] = beaker1.reagents.total_volume - data["beaker1MaxVolume"] = beaker1.reagents.maximum_volume - data["beaker1Contents"] = beakerContents - data["graphUpperRange"] = calculate_largest_mass() //+10 because of the range on the peak - - beakerContents = list() - if(beaker2 && beaker2.reagents) - for(var/datum/reagent/reagent in beaker2.reagents.reagent_list) - ///Normal stuff - beakerContents.Add(list(list("name" = reagent.name, "volume" = round(reagent.volume, 0.01), "mass" = reagent.mass, "purity" = round(reagent.purity, 0.000001)*100, "color" = "#3cf096", "type" = "Clean", log = log[reagent.type]))) - data["beaker2CurrentVolume"] = beaker2.reagents.total_volume - data["beaker2MaxVolume"] = beaker2.reagents.maximum_volume - data["beaker2Contents"] = beakerContents - - return data - -/obj/machinery/chem_mass_spec/ui_act(action, params) + . = list() + .["lowerRange"] = lower_mass_range + .["upperRange"] = upper_mass_range + .["processing"] = processing_reagents + .["eta"] = delay_time - progress_time + .["peakHeight"] = 0 + + //input reagents + var/list/beaker1Data = null + if(!QDELETED(beaker1)) + beaker1Data = list() + var/datum/reagents/beaker_1_reagents = beaker1.reagents + beaker1Data["currentVolume"] = beaker_1_reagents.total_volume + beaker1Data["maxVolume"] = beaker_1_reagents.maximum_volume + var/list/beakerContents = list() + for(var/datum/reagent/reagent as anything in beaker_1_reagents.reagent_list) + var/log = "" + var/datum/reagent/target = reagent + var/purity = target.purity + var/is_inverse = FALSE + + if(istype(reagent, /datum/reagent/inverse)) + log = "Too impure to use" //we don't bother about impure chems + is_inverse = TRUE + else if(reagent.inverse_chem_val > reagent.purity && reagent.inverse_chem) + purity = target.get_inverse_purity() + target = GLOB.chemical_reagents_list[reagent.inverse_chem] + log = "Too impure to use" //we don't bother about impure chems + is_inverse = TRUE + else + var/initial_purity = initial(reagent.purity) + if((initial_purity - reagent.purity) <= 0) //already at max purity + log = "Cannot purify above [round(initial_purity * 100)]%" + else + log = "Ready" + + beakerContents += list(list( + "name" = target.name, + "volume" = round(reagent.volume, CHEMICAL_VOLUME_ROUNDING), + "mass" = target.mass, + "purity" = round(purity * 100), + "type" = is_inverse ? "Inverted" : "Clean", + "log" = log + )) + .["peakHeight"] = max(.["peakHeight"], reagent.volume) + beaker1Data["contents"] = beakerContents + .["beaker1"] = beaker1Data + + //+10 because of the range on the peak + .["graphUpperRange"] = calculate_mass(smallest = FALSE) + + //output reagents + var/list/beaker2Data = null + if(!QDELETED(beaker2)) + beaker2Data = list() + var/datum/reagents/beaker_2_reagents = beaker2.reagents + beaker2Data["currentVolume"] = beaker_2_reagents.total_volume + beaker2Data["maxVolume"] = beaker_2_reagents.maximum_volume + var/list/beakerContents = list() + for(var/datum/reagent/reagent as anything in beaker_2_reagents.reagent_list) + beakerContents += list(list( + "name" = reagent.name, + "volume" = round(reagent.volume, CHEMICAL_VOLUME_ROUNDING), + "mass" = reagent.mass, + "purity" = round(reagent.purity * 100), + "type" = "Clean", + "log" = log[reagent.type] + )) + beaker2Data["contents"] = beakerContents + .["beaker2"] = beaker2Data + +/obj/machinery/chem_mass_spec/ui_act(action, params, datum/tgui/ui, datum/ui_state/state) . = ..() if(.) return + + if(processing_reagents) + balloon_alert(ui.user, "still processing") + return ..() + switch(action) if("activate") - if(!beaker1 || !beaker2 || !is_operational) - say("This [src] is missing an output beaker!") + if(QDELETED(beaker1)) + say("Missing input beaker!") return - if(processing_reagents) - say("You shouldn't be seeing this message! Please report this bug to https://github.com/tgstation/tgstation/issues . Thank you!") - stack_trace("Someone managed to break the HPLC and tried to get it to activate when it's already activated!") + if(QDELETED(beaker2)) + say("Missing output beaker!") return - processing_reagents = TRUE - estimate_time() + + //adjust timer for purification progress_time = 0 - update_appearance() + estimate_time() + if(delay_time <= 0) + say("No work to be done!") + return + + //start the purification process + processing_reagents = TRUE begin_processing() - . = TRUE + update_appearance() + + return TRUE + if("leftSlider") - if(!is_operational || processing_reagents) + var/value = params["value"] + if(isnull(value)) return - var/current_center = (lower_mass_range + upper_mass_range)/2 - lower_mass_range = clamp(params["value"], calculate_smallest_mass(), current_center) - . = TRUE + + value = text2num(value) + if(isnull(value)) + return + + lower_mass_range = clamp(value, calculate_mass(smallest = TRUE), (lower_mass_range + upper_mass_range) / 2) + estimate_time() + return TRUE + if("rightSlider") - if(!is_operational || processing_reagents) + var/value = params["value"] + if(isnull(value)) + return + + value = text2num(value) + if(isnull(value)) return - var/current_center = (lower_mass_range + upper_mass_range)/2 - upper_mass_range = clamp(params["value"], current_center, calculate_largest_mass()) - . = TRUE + + upper_mass_range = clamp(value, (lower_mass_range + upper_mass_range) / 2, calculate_mass(smallest = FALSE)) + estimate_time() + return TRUE + if("centerSlider") - if(!is_operational || processing_reagents) + var/value = params["value"] + if(isnull(value)) return - var/current_center = (lower_mass_range + upper_mass_range)/2 - var/delta_center = current_center - params["value"] - var/lowest = calculate_smallest_mass() - var/highest = calculate_largest_mass() + + value = text2num(value) + if(isnull(value)) + return + + var/delta_center = ((lower_mass_range + upper_mass_range) / 2) - params["value"] + var/lowest = calculate_mass(smallest = TRUE) + var/highest = calculate_mass(smallest = FALSE) lower_mass_range = clamp(lower_mass_range - delta_center, lowest, highest) upper_mass_range = clamp(upper_mass_range - delta_center, lowest, highest) - . = TRUE + estimate_time() + + return TRUE + if("eject1") - if(processing_reagents) - return - replace_beaker(usr, BEAKER1) - . = TRUE + replace_beaker(ui.user, TRUE) + return TRUE + if("eject2") - if(processing_reagents) - return - replace_beaker(usr, BEAKER2) - . = TRUE + replace_beaker(ui.user, FALSE) + return TRUE -/* processing procs */ +/obj/machinery/chem_mass_spec/AltClick(mob/living/user) + . = ..() + if(!can_interact(user)) + return + if(processing_reagents) + balloon_alert(user, "still processing!") + return ..() + replace_beaker(user, TRUE) -///Increments time if it's progressing - if it's past time then it purifies and stops processing -/obj/machinery/chem_mass_spec/process(seconds_per_tick) +/obj/machinery/chem_mass_spec/alt_click_secondary(mob/living/user) . = ..() - if(!is_operational) - return FALSE + if(!can_interact(user)) + return + if(processing_reagents) + balloon_alert(user, "still processing!") + return ..() + replace_beaker(user, FALSE) + +/obj/machinery/chem_mass_spec/process(seconds_per_tick) if(!processing_reagents) - return TRUE + return PROCESS_KILL + + if(!is_operational || panel_open || !anchored || (machine_stat & (BROKEN | NOPOWER))) + return + use_power(active_power_usage) + + progress_time += seconds_per_tick if(progress_time >= delay_time) processing_reagents = FALSE progress_time = 0 - purify_reagents() - end_processing() - update_appearance() - return TRUE - progress_time += seconds_per_tick - return FALSE -/* - * Processing through the reagents in beaker 1 - * For all the reagents within the selected range - we will then purify them up to their initial purity (usually 75%). It will take away the relative reagent volume from the sum volume of the reagent however. - * If there are any inverted reagents - then it will instead just create a new reagent of the inverted type. This doesn't really do anything other than change the name of it, - * As it processes through the reagents, it saves what changes were applied to each reagent in a log var to show the results at the end - */ -/obj/machinery/chem_mass_spec/proc/purify_reagents() - log = list() - for(var/datum/reagent/reagent as anything in beaker1.reagents.reagent_list) - //Inverse first - var/volume = reagent.volume - if(reagent.inverse_chem_val > reagent.purity && reagent.inverse_chem) - var/datum/reagent/inverse_reagent = GLOB.chemical_reagents_list[reagent.inverse_chem] - if(inverse_reagent.mass < lower_mass_range || inverse_reagent.mass > upper_mass_range) + log.Cut() + for(var/datum/reagent/reagent as anything in beaker1.reagents.reagent_list) + //we don't bother about impure chems + if(istype(reagent, /datum/reagent/inverse) || (reagent.inverse_chem_val > reagent.purity && reagent.inverse_chem)) continue - log += list(inverse_reagent.type = "Cannot purify inverted") //Might as well make it do something - just updates the reagent's name - beaker2.reagents.add_reagent(reagent.inverse_chem, volume, reagtemp = beaker1.reagents.chem_temp, added_purity = reagent.get_inverse_purity()) - beaker1.reagents.remove_reagent(reagent.type, volume) - continue - - if(reagent.mass < lower_mass_range || reagent.mass > upper_mass_range) - continue - - var/delta_purity = initial(reagent.purity) - reagent.purity - if(delta_purity <= 0)//As pure as we can be - so lets not add more than we need - log += list(reagent.type = "Can't purify over [initial(reagent.purity)*100]%") - beaker2.reagents.add_reagent(reagent.type, volume, reagtemp = beaker1.reagents.chem_temp, added_purity = reagent.purity, added_ph = reagent.ph) - beaker1.reagents.remove_reagent(reagent.type, volume) - continue - - var/product_vol = reagent.volume * (1-delta_purity) - beaker2.reagents.add_reagent(reagent.type, product_vol, reagtemp = beaker1.reagents.chem_temp, added_purity = initial(reagent.purity), added_ph = reagent.ph) - beaker1.reagents.remove_reagent(reagent.type, reagent.volume) - log += list(reagent.type = "Purified to [initial(reagent.purity)*100]%") - -/* Mass spec graph calcs */ - -///Returns the largest mass to the nearest 50 (rounded up) -/obj/machinery/chem_mass_spec/proc/calculate_largest_mass() - if(!beaker1?.reagents) - return 0 - var/max_mass = 0 - for(var/datum/reagent/reagent as anything in beaker1.reagents.reagent_list) - if(reagent.inverse_chem_val > reagent.purity && reagent.inverse_chem) - var/datum/reagent/inverse_reagent = GLOB.chemical_reagents_list[reagent.inverse_chem] - max_mass = max(max_mass, inverse_reagent.mass) - continue - max_mass = max(max_mass, reagent.mass) - return CEILING(max_mass, 50) - -///Returns the smallest mass to the nearest 50 (rounded down) -/obj/machinery/chem_mass_spec/proc/calculate_smallest_mass() - if(!beaker1?.reagents) - return 0 - var/min_mass = 0 - for(var/datum/reagent/reagent as anything in beaker1.reagents.reagent_list) - if(reagent.inverse_chem_val > reagent.purity && reagent.inverse_chem) - var/datum/reagent/inverse_reagent = GLOB.chemical_reagents_list[reagent.inverse_chem] - min_mass = min(min_mass, inverse_reagent.mass) - continue - min_mass = min(min_mass, reagent.mass) - return FLOOR(min_mass, 50) - -/* - * Estimates how long the highlighted range will take to process - * The time will increase based off the reagent's volume, mass and purity. - * In most cases this is between 10 to 30s for a single reagent. - * This is why having a higher mass for a reagent is a balancing tool. - */ -/obj/machinery/chem_mass_spec/proc/estimate_time() - if(!beaker1?.reagents) - return 0 - var/time = 0 - for(var/datum/reagent/reagent as anything in beaker1.reagents.reagent_list) - if(reagent.inverse_chem_val > reagent.purity && reagent.inverse_chem) - var/datum/reagent/inverse_reagent = GLOB.chemical_reagents_list[reagent.inverse_chem] - if(inverse_reagent.mass < lower_mass_range || inverse_reagent.mass > upper_mass_range) + //out of our selected range + if(reagent.mass < lower_mass_range || reagent.mass > upper_mass_range) continue - time += (((inverse_reagent.mass * reagent.volume) + (inverse_reagent.mass * reagent.purity * 0.1)) * 0.003) + 10 ///Roughly 10 - 30s? - continue - if(reagent.mass < lower_mass_range || reagent.mass > upper_mass_range) - continue - time += (((reagent.mass * reagent.volume) + (reagent.mass * reagent.get_inverse_purity() * 0.1)) * 0.0035) + 10 ///Roughly 10 - 30s? - delay_time = (time * cms_coefficient) - return delay_time - -#undef BEAKER1 -#undef BEAKER2 + //already at max purity + var/delta_purity = initial(reagent.purity) - reagent.purity + if(delta_purity <= 0) + continue + //add the purified reagent. More impure reagents will yield smaller amounts + var/product_vol = reagent.volume + beaker1.reagents.remove_reagent(reagent.type, product_vol) + beaker2.reagents.add_reagent(reagent.type, product_vol * (1 - delta_purity), reagtemp = beaker1.reagents.chem_temp, added_purity = initial(reagent.purity), added_ph = reagent.ph) + log[reagent.type] = "Purified to [initial(reagent.purity) * 100]%" + + //recompute everything + lower_mass_range = calculate_mass(smallest = TRUE) + upper_mass_range = calculate_mass(smallest = FALSE) + estimate_time() + update_appearance() + return PROCESS_KILL diff --git a/code/modules/reagents/chemistry/machinery/portable_chem_mixer.dm b/code/modules/reagents/chemistry/machinery/portable_chem_mixer.dm index 791feb800390e..a6113d2f0c6e4 100644 --- a/code/modules/reagents/chemistry/machinery/portable_chem_mixer.dm +++ b/code/modules/reagents/chemistry/machinery/portable_chem_mixer.dm @@ -105,7 +105,7 @@ /obj/item/storage/portable_chem_mixer/ex_act(severity, target) return severity > EXPLODE_LIGHT ? ..() : FALSE -/obj/item/storage/portable_chem_mixer/attackby(obj/item/weapon, mob/user, params) +/obj/item/storage/portable_chem_mixer/item_interaction(mob/living/user, obj/item/weapon, list/modifiers, is_right_clicking) if (!atom_storage.locked || \ (weapon.item_flags & ABSTRACT) || \ (weapon.flags_1 & HOLOGRAM_1) || \ @@ -116,7 +116,7 @@ replace_beaker(user, weapon) update_appearance() - return TRUE + return ITEM_INTERACT_SUCCESS /** * Replaces the beaker of the portable chemical mixer with another beaker, or simply adds the new beaker if none is in currently @@ -185,11 +185,11 @@ beaker_data["maxVolume"] = beaker.volume beaker_data["transferAmounts"] = beaker.possible_transfer_amounts beaker_data["pH"] = round(beaker.reagents.ph, 0.01) - beaker_data["currentVolume"] = round(beaker.reagents.total_volume, 0.01) + beaker_data["currentVolume"] = round(beaker.reagents.total_volume, CHEMICAL_VOLUME_ROUNDING) var/list/beakerContents = list() if(length(beaker.reagents.reagent_list)) for(var/datum/reagent/reagent in beaker.reagents.reagent_list) - beakerContents += list(list("name" = reagent.name, "volume" = round(reagent.volume, 0.01))) // list in a list because Byond merges the first list... + beakerContents += list(list("name" = reagent.name, "volume" = round(reagent.volume, CHEMICAL_VOLUME_ROUNDING))) // list in a list because Byond merges the first list... beaker_data["contents"] = beakerContents .["beaker"] = beaker_data diff --git a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm index e7a6c9839eb7e..358ac29719c97 100644 --- a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm +++ b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm @@ -27,6 +27,7 @@ holdingitems = list() beaker = new /obj/item/reagent_containers/cup/beaker/large(src) warn_of_dust() + RegisterSignal(src, COMSIG_STORAGE_DUMP_CONTENT, PROC_REF(on_storage_dump)) /// Add a description to the current beaker warning of blended dust, if it doesn't already have that warning. /obj/machinery/reagentgrinder/proc/warn_of_dust() @@ -205,6 +206,24 @@ holdingitems[weapon] = TRUE return FALSE +/obj/machinery/reagentgrinder/proc/on_storage_dump(datum/source, datum/storage/storage, mob/user) + SIGNAL_HANDLER + + for(var/obj/item/to_dump in storage.real_location) + if(holdingitems.len >= limit) + break + + if(!to_dump.grind_results && !to_dump.juice_typepath) + continue + + if(!storage.attempt_remove(to_dump, src, silent = TRUE)) + continue + + holdingitems[to_dump] = TRUE + + to_chat(user, span_notice("You dump [storage.parent] into [src].")) + return STORAGE_DUMP_HANDLED + /obj/machinery/reagentgrinder/ui_interact(mob/user) // The microwave Menu //I am reasonably certain that this is not a microwave . = ..() @@ -216,7 +235,7 @@ if(beaker || length(holdingitems)) options["eject"] = radial_eject - if(isAI(user)) + if(HAS_AI_ACCESS(user)) if(machine_stat & NOPOWER) return options["examine"] = radial_examine @@ -236,10 +255,10 @@ for(var/key in options) choice = key else - choice = show_radial_menu(user, src, options, require_near = !issilicon(user)) + choice = show_radial_menu(user, src, options, require_near = !HAS_SILICON_ACCESS(user)) // post choice verification - if(operating || (isAI(user) && machine_stat & NOPOWER) || !user.can_perform_action(src, ALLOW_SILICON_REACH)) + if(operating || (HAS_AI_ACCESS(user) && machine_stat & NOPOWER) || !user.can_perform_action(src, ALLOW_SILICON_REACH)) return switch(choice) diff --git a/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm b/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm index 43430d0946916..e1211d30664a7 100644 --- a/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm @@ -55,8 +55,6 @@ affected_mob.adjust_temp_blindness(-2 SECONDS * REM * seconds_per_tick) var/need_mob_update switch(current_cycle) - if(1 to 20) - //nothing if(21 to 110) if(SPT_PROB(100 * (1 - (sqrt(110 - current_cycle) / 10)), seconds_per_tick)) need_mob_update = affected_mob.adjustOrganLoss(ORGAN_SLOT_EYES, -2 * REM * seconds_per_tick) @@ -639,7 +637,7 @@ . = ..() affected_mob.adjust_drowsiness(3 SECONDS * REM * seconds_per_tick) var/need_mob_update - switch(affected_mob.mob_mood.sanity_level) + switch(affected_mob.mob_mood.sanity) if (SANITY_INSANE to SANITY_CRAZY) need_mob_update = affected_mob.adjustStaminaLoss(3 * REM * seconds_per_tick, updating_stamina = FALSE) if (SANITY_UNSTABLE to SANITY_DISTURBED) diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm index 6363a9766a35a..10d69afa16108 100644 --- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm @@ -87,11 +87,8 @@ to_chat(affected_mob, span_notice("[smoke_message]")) affected_mob.add_mood_event("smoked", /datum/mood_event/smoked) affected_mob.remove_status_effect(/datum/status_effect/jitter) - affected_mob.AdjustStun(-50 * REM * seconds_per_tick) - affected_mob.AdjustKnockdown(-50 * REM * seconds_per_tick) - affected_mob.AdjustUnconscious(-50 * REM * seconds_per_tick) - affected_mob.AdjustParalyzed(-50 * REM * seconds_per_tick) - affected_mob.AdjustImmobilized(-50 * REM * seconds_per_tick) + affected_mob.AdjustAllImmobility(-50 * REM * seconds_per_tick) + return UPDATE_MOB_HEALTH /datum/reagent/drug/nicotine/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) @@ -177,11 +174,7 @@ if(SPT_PROB(2.5, seconds_per_tick)) to_chat(affected_mob, span_notice("[high_message]")) affected_mob.add_mood_event("tweaking", /datum/mood_event/stimulant_medium) - affected_mob.AdjustStun(-40 * REM * seconds_per_tick) - affected_mob.AdjustKnockdown(-40 * REM * seconds_per_tick) - affected_mob.AdjustUnconscious(-40 * REM * seconds_per_tick) - affected_mob.AdjustParalyzed(-40 * REM * seconds_per_tick) - affected_mob.AdjustImmobilized(-40 * REM * seconds_per_tick) + affected_mob.AdjustAllImmobility(-40 * REM * seconds_per_tick) var/need_mob_update need_mob_update = affected_mob.adjustStaminaLoss(-2 * REM * seconds_per_tick, updating_stamina = FALSE, required_biotype = affected_biotype) affected_mob.set_jitter_if_lower(4 SECONDS * REM * seconds_per_tick) @@ -459,11 +452,7 @@ /datum/reagent/drug/maint/tar/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() - affected_mob.AdjustStun(-10 * REM * seconds_per_tick) - affected_mob.AdjustKnockdown(-10 * REM * seconds_per_tick) - affected_mob.AdjustUnconscious(-10 * REM * seconds_per_tick) - affected_mob.AdjustParalyzed(-10 * REM * seconds_per_tick) - affected_mob.AdjustImmobilized(-10 * REM * seconds_per_tick) + affected_mob.AdjustAllImmobility(-10 * REM * seconds_per_tick) affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, 1.5 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) return UPDATE_MOB_HEALTH @@ -785,6 +774,24 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED addiction_types = list(/datum/addiction/stimulants = 20) +/datum/reagent/drug/kronkaine/on_new(data) + . = ..() + // Kronkaine also makes for a great fishing bait (found in "natural" baits) + if(!istype(holder?.my_atom, /obj/item/food)) + return + ADD_TRAIT(holder.my_atom, TRAIT_GREAT_QUALITY_BAIT, type) + RegisterSignal(holder, COMSIG_REAGENTS_CLEAR_REAGENTS, PROC_REF(on_reagents_clear)) + RegisterSignal(holder, COMSIG_REAGENTS_DEL_REAGENT, PROC_REF(on_reagent_delete)) + +/datum/reagent/drug/kronkaine/proc/on_reagents_clear(datum/reagents/reagents) + SIGNAL_HANDLER + REMOVE_TRAIT(holder.my_atom, TRAIT_GREAT_QUALITY_BAIT, type) + +/datum/reagent/drug/kronkaine/proc/on_reagent_delete(datum/reagents/reagents, datum/reagent/deleted_reagent) + SIGNAL_HANDLER + if(deleted_reagent == src) + REMOVE_TRAIT(holder.my_atom, TRAIT_GREAT_QUALITY_BAIT, type) + /datum/reagent/drug/kronkaine/on_mob_metabolize(mob/living/kronkaine_fiend) . = ..() kronkaine_fiend.add_actionspeed_modifier(/datum/actionspeed_modifier/kronkaine) diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index d20f2b786277d..d2bc311579b89 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -88,11 +88,8 @@ /datum/reagent/medicine/synaptizine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() affected_mob.adjust_drowsiness(-10 SECONDS * REM * seconds_per_tick) - affected_mob.AdjustStun(-20 * REM * seconds_per_tick) - affected_mob.AdjustKnockdown(-20 * REM * seconds_per_tick) - affected_mob.AdjustUnconscious(-20 * REM * seconds_per_tick) - affected_mob.AdjustImmobilized(-20 * REM * seconds_per_tick) - affected_mob.AdjustParalyzed(-20 * REM * seconds_per_tick) + affected_mob.AdjustAllImmobility(-20 * REM * seconds_per_tick) + if(holder.has_reagent(/datum/reagent/toxin/mindbreaker)) holder.remove_reagent(/datum/reagent/toxin/mindbreaker, 5 * REM * seconds_per_tick) affected_mob.adjust_hallucinations(-20 SECONDS * REM * seconds_per_tick) diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 188fbfe622ec9..6ab780ba4e39b 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -1276,6 +1276,9 @@ /datum/reagent/fuel/on_mob_life(mob/living/carbon/victim, seconds_per_tick, times_fired) . = ..() + var/obj/item/organ/internal/liver/liver = victim.get_organ_slot(ORGAN_SLOT_LIVER) + if(liver && HAS_TRAIT(liver, TRAIT_HUMAN_AI_METABOLISM)) + return if(victim.adjustToxLoss(0.5 * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) return UPDATE_MOB_HEALTH @@ -1409,6 +1412,9 @@ /datum/reagent/cyborg_mutation_nanomachines/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message = TRUE, touch_protection = 0) . = ..() + var/obj/item/organ/internal/liver/liver = exposed_mob.get_organ_slot(ORGAN_SLOT_LIVER) + if(liver && HAS_TRAIT(liver, TRAIT_HUMAN_AI_METABOLISM)) + return if((methods & (PATCH|INGEST|INJECT)) || ((methods & VAPOR) && prob(min(reac_volume,100)*(1 - touch_protection)))) exposed_mob.ForceContractDisease(new /datum/disease/transformation/robot(), FALSE, TRUE) diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm index e9bea91fbed64..c98cec7cc9e6a 100644 --- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm @@ -1045,11 +1045,11 @@ description = "A strong mineral acid with the molecular formula H2SO4." color = "#00FF32" toxpwr = 1 - var/acidpwr = 10 //the amount of protection removed from the armour taste_description = "acid" self_consuming = TRUE ph = 2.75 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + var/acidpwr = 10 //the amount of protection removed from the armour // ...Why? I mean, clearly someone had to have done this and thought, well, // acid doesn't hurt plants, but what brought us here, to this point? @@ -1062,6 +1062,9 @@ . = ..() if(!istype(exposed_carbon)) return + var/obj/item/organ/internal/liver/liver = exposed_carbon.get_organ_slot(ORGAN_SLOT_LIVER) + if(liver && HAS_TRAIT(liver, TRAIT_HUMAN_AI_METABOLISM)) + return reac_volume = round(reac_volume,0.1) if(methods & INGEST) exposed_carbon.adjustBruteLoss(min(6*toxpwr, reac_volume * toxpwr), required_bodytype = affected_bodytype) diff --git a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm index 8e74b0ad6f869..bc41e25090bf1 100644 --- a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm +++ b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm @@ -194,7 +194,7 @@ beeagents += R var/bee_amount = round(created_volume * 0.2) for(var/i in 1 to bee_amount) - var/mob/living/basic/bee/short/new_bee = new(location) + var/mob/living/basic/bee/timed/new_bee = new(location) if(LAZYLEN(beeagents)) new_bee.assign_reagent(pick(beeagents)) @@ -419,7 +419,7 @@ determin_ph_range = 6 temp_exponent_factor = 0.5 ph_exponent_factor = 1 - thermic_constant = -7.5 + thermic_constant = -1.5 H_ion_release = 0 rate_up_lim = 10 purity_min = 0.2 diff --git a/code/modules/reagents/chemistry/recipes/slime_extracts.dm b/code/modules/reagents/chemistry/recipes/slime_extracts.dm index f37f9ebb081c0..84a242227bccf 100644 --- a/code/modules/reagents/chemistry/recipes/slime_extracts.dm +++ b/code/modules/reagents/chemistry/recipes/slime_extracts.dm @@ -149,7 +149,7 @@ if(prob(5))//Fry it! food_item.AddElement(/datum/element/fried_item, rand(15, 60)) if(prob(5))//Grill it! - food_item.AddElement(/datum/element/grilled_item, rand(30, 100)) + food_item.AddElement(/datum/element/grilled_item, rand(30 SECONDS, 100 SECONDS)) if(prob(50)) for(var/j in 1 to rand(1, 3)) step(food_item, pick(NORTH,SOUTH,EAST,WEST)) diff --git a/code/modules/reagents/reagent_containers/condiment.dm b/code/modules/reagents/reagent_containers/condiment.dm index bbd0a84504fac..e34511e9e0d0e 100644 --- a/code/modules/reagents/reagent_containers/condiment.dm +++ b/code/modules/reagents/reagent_containers/condiment.dm @@ -424,6 +424,7 @@ /datum/reagent/consumable/bbqsauce = list("condi_bbq", "BBQ sauce", "Hand wipes not included."), /datum/reagent/consumable/peanut_butter = list("condi_peanutbutter", "Peanut Butter", "A creamy paste made from ground peanuts."), /datum/reagent/consumable/cherryjelly = list("condi_cherryjelly", "Cherry Jelly", "A jar of super-sweet cherry jelly."), + /datum/reagent/consumable/mayonnaise = list("condi_mayo", "Mayonnaise", "Not an instrument."), ) /// Can't use initial(name) for this. This stores the name set by condimasters. var/originalname = "condiment" @@ -516,3 +517,15 @@ originalname = "sugar" volume = 5 list_reagents = list(/datum/reagent/consumable/sugar = 5) + +/obj/item/reagent_containers/condiment/pack/soysauce + name = "soy sauce pack" + originalname = "soy sauce" + volume = 5 + list_reagents = list(/datum/reagent/consumable/soysauce = 5) + +/obj/item/reagent_containers/condiment/pack/mayonnaise + name = "mayonnaise pack" + originalname = "mayonnaise" + volume = 5 + list_reagents = list(/datum/reagent/consumable/mayonnaise = 5) diff --git a/code/modules/recycling/disposal/bin.dm b/code/modules/recycling/disposal/bin.dm index 78ffff197bce8..8841284b601ee 100644 --- a/code/modules/recycling/disposal/bin.dm +++ b/code/modules/recycling/disposal/bin.dm @@ -358,7 +358,7 @@ data["pressure_charging"] = pressure_charging data["panel_open"] = panel_open data["per"] = CLAMP01(air_contents.return_pressure() / (SEND_PRESSURE)) - data["isai"] = isAI(user) + data["isai"] = HAS_AI_ACCESS(user) return data /obj/machinery/disposal/bin/ui_act(action, params) diff --git a/code/modules/religion/festival/instrument_rites.dm b/code/modules/religion/festival/instrument_rites.dm index d8537f5845ea0..4b5c1afa18bbb 100644 --- a/code/modules/religion/festival/instrument_rites.dm +++ b/code/modules/religion/festival/instrument_rites.dm @@ -138,7 +138,7 @@ var/obj/effect/dummy/lighting_obj/moblight/performer_light_obj /datum/religion_rites/song_tuner/light/performer_start_effect(mob/living/carbon/human/performer, atom/song_source) - performer_light_obj = performer.mob_light(8, color = LIGHT_COLOR_DIM_YELLOW) + performer_light_obj = performer.mob_light(8, 1.5, color = LIGHT_COLOR_DIM_YELLOW) /datum/religion_rites/song_tuner/light/Destroy() QDEL_NULL(performer_light_obj) diff --git a/code/modules/religion/sparring/sparring_datum.dm b/code/modules/religion/sparring/sparring_datum.dm index bfaa94d65a8aa..78de2bd3a0bb0 100644 --- a/code/modules/religion/sparring/sparring_datum.dm +++ b/code/modules/religion/sparring/sparring_datum.dm @@ -301,4 +301,4 @@ return to_chat(loser, span_userdanger("You've lost ownership over your soul to [winner]!")) var/obj/item/soulstone/anybody/chaplain/sparring/shard = new(shard_turf) - shard.capture_soul(loser, winner, forced = TRUE) + INVOKE_ASYNC(shard, TYPE_PROC_REF(/obj/item/soulstone, capture_soul), loser, winner, forced = TRUE) diff --git a/code/modules/research/designs/autolathe/engineering_designs.dm b/code/modules/research/designs/autolathe/engineering_designs.dm index 6249f5c645a1f..945966035f3d1 100644 --- a/code/modules/research/designs/autolathe/engineering_designs.dm +++ b/code/modules/research/designs/autolathe/engineering_designs.dm @@ -426,3 +426,29 @@ RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MOUNTS, ) departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING + +/datum/design/tram_floor_dark + name = "Dark Tram Tile" + id = "tram_floor_dark" + build_type = PROTOLATHE + materials = list(/datum/material/plastic = SHEET_MATERIAL_AMOUNT * 0.25) + build_path = /obj/item/stack/thermoplastic + maxstack = 50 + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MATERIALS, + ) + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING + +/datum/design/tram_floor_light + name = "Light Tram Tile" + id = "tram_floor_light" + build_type = PROTOLATHE + materials = list(/datum/material/plastic = SHEET_MATERIAL_AMOUNT * 0.25) + build_path = /obj/item/stack/thermoplastic/light + maxstack = 50 + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MATERIALS, + ) + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING diff --git a/code/modules/research/designs/weapon_designs.dm b/code/modules/research/designs/weapon_designs.dm index e33a1a7f2ab28..00c7dba3946bd 100644 --- a/code/modules/research/designs/weapon_designs.dm +++ b/code/modules/research/designs/weapon_designs.dm @@ -204,6 +204,19 @@ departmental_flags = DEPARTMENT_BITFLAG_SECURITY autolathe_exportable = FALSE +/datum/design/ballistic_shield + name = "Ballistic Shield" + desc = "A heavy shield designed for blocking projectiles, weaker to melee." + id = "ballistic_shield" + build_type = PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 2, /datum/material/glass = SHEET_MATERIAL_AMOUNT * 2, /datum/material/titanium =SHEET_MATERIAL_AMOUNT) + build_path = /obj/item/shield/ballistic + category = list( + RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_MELEE + ) + departmental_flags = DEPARTMENT_BITFLAG_SECURITY + autolathe_exportable = FALSE + /datum/design/beamrifle name = "Beam Marksman Rifle Part Kit (Lethal)" desc = "The gunkit for a powerful long ranged anti-material rifle that fires charged particle beams to obliterate targets." diff --git a/code/modules/research/ordnance/doppler_array.dm b/code/modules/research/ordnance/doppler_array.dm index a6fe3c9f3ac0e..7afe201f36007 100644 --- a/code/modules/research/ordnance/doppler_array.dm +++ b/code/modules/research/ordnance/doppler_array.dm @@ -20,7 +20,7 @@ // Lighting system to better communicate the directions. light_system = OVERLAY_LIGHT_DIRECTIONAL light_range = 4 - light_power = 1 + light_power = 1.5 light_color = COLOR_RED /obj/machinery/doppler_array/Initialize(mapload) diff --git a/code/modules/research/rdconsole.dm b/code/modules/research/rdconsole.dm index b2b468db120c9..e529f97eb0c82 100644 --- a/code/modules/research/rdconsole.dm +++ b/code/modules/research/rdconsole.dm @@ -112,7 +112,7 @@ Nothing else in the console has ID requirements. if(stored_research.research_node_id(id)) say("Successfully researched [TN.display_name].") var/logname = "Unknown" - if(isAI(user)) + if(HAS_AI_ACCESS(user)) logname = "AI [user.name]" if(iscyborg(user)) logname = "CYBORG [user.name]" diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index acf17851eb34a..76e5085c5257e 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -121,6 +121,8 @@ "toy_armblade", "toy_balloon", "toygun", + "tram_floor_dark", + "tram_floor_light", "trapdoor_electronics", "turbine_part_compressor", "turbine_part_rotor", @@ -1065,6 +1067,18 @@ ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) +/datum/techweb_node/ai_basic/New() + . = ..() + if(HAS_TRAIT(SSstation, STATION_TRAIT_HUMAN_AI)) + design_ids -= list( + "aicore", + "borg_ai_control", + "intellicard", + "mecha_tracking_ai_control", + "aifixer", + "aiupload", + ) + /datum/techweb_node/ai_adv id = "ai_adv" display_name = "Advanced Artificial Intelligence" @@ -1625,6 +1639,7 @@ description = "Our researchers have found new ways to weaponize just about everything now." prereq_ids = list("engineering") design_ids = list( + "ballistic_shield", "pin_testing", "tele_shield", "lasershell", diff --git a/code/modules/research/xenobiology/xenobiology.dm b/code/modules/research/xenobiology/xenobiology.dm index 49f7bfa61f392..ba568c4bc74a7 100644 --- a/code/modules/research/xenobiology/xenobiology.dm +++ b/code/modules/research/xenobiology/xenobiology.dm @@ -688,7 +688,6 @@ desc = "A miraculous chemical mix that grants human like intelligence to living beings." icon = 'icons/obj/medical/chemical.dmi' icon_state = "potpink" - var/list/not_interested = list() var/being_used = FALSE var/sentience_type = SENTIENCE_ORGANIC @@ -704,16 +703,22 @@ if(!dumb_mob.compare_sentience_type(sentience_type)) // Will also return false if not a basic or simple mob, which are the only two we want anyway balloon_alert(user, "invalid creature!") return - + var/potion_reason = tgui_input_text(user, "For what reason?", "Intelligence Potion", multiline = TRUE, timeout = 2 MINUTES) + if(isnull(potion_reason)) + return balloon_alert(user, "offering...") being_used = TRUE - - var/datum/callback/to_call = CALLBACK(src, PROC_REF(on_poll_concluded), user, dumb_mob) - dumb_mob.AddComponent(/datum/component/orbit_poll, \ - ignore_key = POLL_IGNORE_SENTIENCE_POTION, \ - job_bans = ROLE_SENTIENCE, \ - to_call = to_call, \ + var/mob/chosen_one = SSpolling.poll_ghosts_for_target( + question = "[span_danger(user)] is offering [span_notice(dumb_mob)] an intelligence potion! Reason: [span_boldnotice(potion_reason)]", + check_jobban = ROLE_SENTIENCE, + poll_time = 20 SECONDS, + checked_target = dumb_mob, + ignore_category = POLL_IGNORE_SENTIENCE_POTION, + alert_pic = dumb_mob, + role_name_text = "intelligence potion", + chat_text_border_icon = src, ) + on_poll_concluded(user, dumb_mob, chosen_one) /// Assign the chosen ghost to the mob /obj/item/slimepotion/slime/sentience/proc/on_poll_concluded(mob/user, mob/living/dumb_mob, mob/dead/observer/ghost) diff --git a/code/modules/shuttle/battlecruiser_starfury.dm b/code/modules/shuttle/battlecruiser_starfury.dm index e95ff4243f5d4..a27cadacad2ec 100644 --- a/code/modules/shuttle/battlecruiser_starfury.dm +++ b/code/modules/shuttle/battlecruiser_starfury.dm @@ -135,7 +135,7 @@ */ /proc/summon_battlecruiser(datum/team/battlecruiser/team) - var/list/candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for battlecruiser crew?", check_jobban = ROLE_TRAITOR, pic_source = /obj/machinery/sleeper/syndie, role_name_text = "battlecruiser crew") + var/list/candidates = SSpolling.poll_ghost_candidates("Do you wish to be considered for [span_notice("battlecruiser crew")]?", check_jobban = ROLE_TRAITOR, alert_pic = /obj/machinery/sleeper/syndie, role_name_text = "battlecruiser crew") shuffle_inplace(candidates) var/datum/map_template/ship = SSmapping.map_templates["battlecruiser_starfury.dmm"] diff --git a/code/modules/shuttle/on_move.dm b/code/modules/shuttle/on_move.dm index c65a9ad123a59..9b267489100ee 100644 --- a/code/modules/shuttle/on_move.dm +++ b/code/modules/shuttle/on_move.dm @@ -21,26 +21,22 @@ All ShuttleMove procs go here return var/shuttle_dir = shuttle.dir - for(var/i in contents) - var/atom/movable/thing = i - if(ismob(thing)) - if(isliving(thing)) - var/mob/living/M = thing - if(M.buckled) - M.buckled.unbuckle_mob(M, 1) - if(M.pulledby) - M.pulledby.stop_pulling() - M.stop_pulling() - M.visible_message(span_warning("[shuttle] slams into [M]!")) - SSblackbox.record_feedback("tally", "shuttle_gib", 1, M.type) - log_shuttle("[key_name(M)] was shuttle gibbed by [shuttle].") - M.investigate_log("has been gibbed by [shuttle].", INVESTIGATE_DEATHS) - M.gib(DROP_ALL_REMAINS) - - - else //non-living mobs shouldn't be affected by shuttles, which is why this is an else - if(istype(thing, /obj/effect/abstract) || istype(thing, /obj/singularity) || istype(thing, /obj/energy_ball)) + for(var/atom/movable/thing as anything in contents) + if(thing.resistance_flags & SHUTTLE_CRUSH_PROOF) + continue + if(isliving(thing)) + var/mob/living/living_thing = thing + if(living_thing.incorporeal_move) // Don't crush incorporeal things continue + living_thing.buckled?.unbuckle_mob(living_thing, force = TRUE) + living_thing.pulledby?.stop_pulling() + living_thing.stop_pulling() + living_thing.visible_message(span_warning("[shuttle] slams into [living_thing]!")) + SSblackbox.record_feedback("tally", "shuttle_gib", 1, living_thing.type) + log_shuttle("[key_name(living_thing)] was shuttle gibbed by [shuttle].") + living_thing.investigate_log("has been gibbed by [shuttle].", INVESTIGATE_DEATHS) + living_thing.gib(DROP_ALL_REMAINS) + else if(!ismob(thing)) //non-living mobs shouldn't be affected by shuttles, which is why this is an else if(!thing.anchored) step(thing, shuttle_dir) else diff --git a/code/modules/shuttle/shuttle_events/player_controlled.dm b/code/modules/shuttle/shuttle_events/player_controlled.dm index 77fee390a6876..86d134f29f727 100644 --- a/code/modules/shuttle/shuttle_events/player_controlled.dm +++ b/code/modules/shuttle/shuttle_events/player_controlled.dm @@ -17,17 +17,12 @@ /// Attempt to grant control of a mob to ghosts before spawning it in. if spawn_anyway_if_no_player = TRUE, we spawn the mob even if there's no ghosts /datum/shuttle_event/simple_spawner/player_controlled/proc/try_grant_ghost_control(spawn_type) - var/list/candidates = SSpolling.poll_ghost_candidates(ghost_alert_string + " (Warning: you will not be able to return to your body!)", check_jobban = role_type, poll_time = 10 SECONDS, pic_source = spawn_type, role_name_text = "shot at shuttle") - - if(!candidates.len && !spawn_anyway_if_no_player) + var/mob/chosen_one = SSpolling.poll_ghost_candidates(ghost_alert_string + " (Warning: you will not be able to return to your body!)", check_jobban = role_type, poll_time = 10 SECONDS, alert_pic = spawn_type, role_name_text = "shot at shuttle", amount_to_pick = 1) + if(isnull(chosen_one) && !spawn_anyway_if_no_player) return - var/mob/living/new_mob = new spawn_type (get_turf(get_spawn_turf())) - - if(candidates.len) - var/mob/dead/observer/candidate = pick(candidates) - new_mob.ckey = candidate.ckey - post_spawn(new_mob) + new_mob.ckey = chosen_one.ckey + post_spawn(new_mob) ///BACK FOR REVENGE!!! /datum/shuttle_event/simple_spawner/player_controlled/alien_queen diff --git a/code/modules/shuttle/spaceship_navigation_beacon.dm b/code/modules/shuttle/spaceship_navigation_beacon.dm index 2c0b95239b604..eb613be54def9 100644 --- a/code/modules/shuttle/spaceship_navigation_beacon.dm +++ b/code/modules/shuttle/spaceship_navigation_beacon.dm @@ -39,9 +39,10 @@ START_PROCESSING(SSmachines, src) COOLDOWN_START(src, next_automatic_message_time, automatic_message_cooldown) -/obj/machinery/spaceship_navigation_beacon/emp_act() +/obj/machinery/spaceship_navigation_beacon/emp_act(severity) + . = ..() locked = TRUE - update_icon_state() + update_appearance(UPDATE_ICON_STATE) /obj/machinery/spaceship_navigation_beacon/Destroy() SSshuttle.beacon_list -= src diff --git a/code/modules/spells/spell_types/shapeshift/_shape_status.dm b/code/modules/spells/spell_types/shapeshift/_shape_status.dm index faa84835255a8..cffd9804ea588 100644 --- a/code/modules/spells/spell_types/shapeshift/_shape_status.dm +++ b/code/modules/spells/spell_types/shapeshift/_shape_status.dm @@ -189,6 +189,12 @@ // is no longer in control of the shapeshifted mob, such as mindswapping out of a shapeshift if(!QDELETED(source_spell) && source_spell.owner == owner) source_spell.Grant(caster_mob) + if(owner?.contents) + // Prevent round removal and consuming stuff when losing shapeshift + for(var/atom/movable/thing as anything in owner.contents) + if(thing == caster_mob) + continue + thing.forceMove(get_turf(owner)) for(var/datum/action/bodybound_action as anything in owner.actions) if(bodybound_action.target != caster_mob) diff --git a/code/modules/surgery/bodyparts/head.dm b/code/modules/surgery/bodyparts/head.dm index f1ea0c06645b4..0463349ca00ab 100644 --- a/code/modules/surgery/bodyparts/head.dm +++ b/code/modules/surgery/bodyparts/head.dm @@ -194,13 +194,9 @@ return -/obj/item/bodypart/head/talk_into(mob/holder, message, channel, spans, datum/language/language, list/message_mods) - var/mob/headholder = holder - if(istype(headholder)) - headholder.log_talk(message, LOG_SAY, tag = "beheaded talk") - - say(message, language, sanitize = FALSE) - return NOPASS +/obj/item/bodypart/head/Initialize(mapload) + . = ..() + AddElement(/datum/element/toy_talk) /obj/item/bodypart/head/GetVoice() return "The head of [real_name]" diff --git a/code/modules/surgery/bodyparts/parts.dm b/code/modules/surgery/bodyparts/parts.dm index ead03aa0f707c..22450ca793d7b 100644 --- a/code/modules/surgery/bodyparts/parts.dm +++ b/code/modules/surgery/bodyparts/parts.dm @@ -69,6 +69,15 @@ cavity_item = null return ..() +/// Sprite to show for photocopying mob butts +/obj/item/bodypart/chest/proc/get_butt_sprite() + if(!ishuman(owner)) + return null + var/mob/living/carbon/human/human_owner = owner + var/butt_sprite = human_owner.physique == FEMALE ? BUTT_SPRITE_HUMAN_FEMALE : BUTT_SPRITE_HUMAN_MALE + var/obj/item/organ/external/tail/tail = human_owner.get_organ_slot(ORGAN_SLOT_EXTERNAL_TAIL) + return tail?.get_butt_sprite() || butt_sprite + /obj/item/bodypart/chest/monkey icon = 'icons/mob/human/species/monkey/bodyparts.dmi' icon_static = 'icons/mob/human/species/monkey/bodyparts.dmi' diff --git a/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm index 8dccd4b4a2d5b..e6fffe1e6f91b 100644 --- a/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm @@ -10,6 +10,9 @@ is_dimorphic = TRUE wing_types = list(/obj/item/organ/external/wings/functional/dragon) +/obj/item/bodypart/chest/lizard/get_butt_sprite() + return BUTT_SPRITE_LIZARD + /obj/item/bodypart/arm/left/lizard icon_greyscale = 'icons/mob/human/species/lizard/bodyparts.dmi' limb_id = SPECIES_LIZARD diff --git a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm index 83758f920c4cb..6a83385b1a36e 100644 --- a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm @@ -60,6 +60,9 @@ should_draw_greyscale = FALSE wing_types = NONE +/obj/item/bodypart/chest/abductor/get_butt_sprite() + return BUTT_SPRITE_GREY + /obj/item/bodypart/arm/left/abductor limb_id = SPECIES_ABDUCTOR should_draw_greyscale = FALSE @@ -95,6 +98,9 @@ burn_modifier = 0.5 // = 1/2x generic burn damage wing_types = list(/obj/item/organ/external/wings/functional/slime) +/obj/item/bodypart/chest/jelly/get_butt_sprite() + return BUTT_SPRITE_SLIME + /obj/item/bodypart/arm/left/jelly biological_state = (BIO_FLESH|BIO_BLOODED) limb_id = SPECIES_JELLYPERSON @@ -230,6 +236,9 @@ burn_modifier = 1.25 wing_types = NONE +/obj/item/bodypart/chest/pod/get_butt_sprite() + return BUTT_SPRITE_FLOWERPOT + /obj/item/bodypart/arm/left/pod limb_id = SPECIES_PODPERSON unarmed_attack_verb = "slash" diff --git a/code/modules/surgery/bodyparts/species_parts/moth_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/moth_bodyparts.dm index faed53371af60..be8d601b1fb58 100644 --- a/code/modules/surgery/bodyparts/species_parts/moth_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/moth_bodyparts.dm @@ -16,6 +16,9 @@ should_draw_greyscale = FALSE wing_types = list(/obj/item/organ/external/wings/functional/moth/megamoth, /obj/item/organ/external/wings/functional/moth/mothra) +/obj/item/bodypart/chest/moth/get_butt_sprite() + return BUTT_SPRITE_FUZZY + /obj/item/bodypart/arm/left/moth icon = 'icons/mob/human/species/moth/bodyparts.dmi' icon_state = "moth_l_arm" diff --git a/code/modules/surgery/bodyparts/species_parts/plasmaman_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/plasmaman_bodyparts.dm index 8ba27c2cdf9d0..40bf4a51c042e 100644 --- a/code/modules/surgery/bodyparts/species_parts/plasmaman_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/plasmaman_bodyparts.dm @@ -26,6 +26,9 @@ bodypart_flags = BODYPART_UNHUSKABLE wing_types = NONE +/obj/item/bodypart/chest/plasmaman/get_butt_sprite() + return BUTT_SPRITE_PLASMA + /obj/item/bodypart/arm/left/plasmaman icon = 'icons/mob/human/species/plasmaman/bodyparts.dmi' icon_state = "plasmaman_l_arm" diff --git a/code/modules/surgery/organs/autosurgeon.dm b/code/modules/surgery/organs/autosurgeon.dm index b577b9f8ec048..6e3d9a0ee7ccb 100644 --- a/code/modules/surgery/organs/autosurgeon.dm +++ b/code/modules/surgery/organs/autosurgeon.dm @@ -157,15 +157,27 @@ /obj/item/autosurgeon/syndicate/thermal_eyes starting_organ = /obj/item/organ/internal/eyes/robotic/thermals +/obj/item/autosurgeon/syndicate/thermal_eyes/single_use + uses = 1 + /obj/item/autosurgeon/syndicate/xray_eyes starting_organ = /obj/item/organ/internal/eyes/robotic/xray +/obj/item/autosurgeon/syndicate/xray_eyes/single_use + uses = 1 + /obj/item/autosurgeon/syndicate/anti_stun starting_organ = /obj/item/organ/internal/cyberimp/brain/anti_stun +/obj/item/autosurgeon/syndicate/anti_stun/single_use + uses = 1 + /obj/item/autosurgeon/syndicate/reviver starting_organ = /obj/item/organ/internal/cyberimp/chest/reviver +/obj/item/autosurgeon/syndicate/reviver/single_use + uses = 1 + /obj/item/autosurgeon/syndicate/commsagent desc = "A device that automatically - painfully - inserts an implant. It seems someone's specially \ modified this one to only insert... tongues. Horrifying." @@ -177,3 +189,11 @@ /obj/item/autosurgeon/syndicate/emaggedsurgerytoolset starting_organ = /obj/item/organ/internal/cyberimp/arm/surgery/emagged + +/obj/item/autosurgeon/syndicate/emaggedsurgerytoolset/single_use + uses = 1 + +/obj/item/autosurgeon/syndicate/contraband_sechud + desc = "Contains a contraband SecHUD implant, undetectable by health scanners." + uses = 1 + starting_organ = /obj/item/organ/internal/cyberimp/eyes/hud/security/syndicate diff --git a/code/modules/surgery/organs/external/tails.dm b/code/modules/surgery/organs/external/tails.dm index 17be616ba7935..4234ca9b5aed4 100644 --- a/code/modules/surgery/organs/external/tails.dm +++ b/code/modules/surgery/organs/external/tails.dm @@ -126,6 +126,9 @@ UnregisterSignal(organ_owner, COMSIG_LIVING_DEATH) return succeeded +/obj/item/organ/external/tail/proc/get_butt_sprite() + return null + ///Tail parent type, with wagging functionality /datum/bodypart_overlay/mutant/tail layers = EXTERNAL_FRONT|EXTERNAL_BEHIND @@ -147,6 +150,9 @@ wag_flags = WAG_ABLE +/obj/item/organ/external/tail/cat/get_butt_sprite() + return BUTT_SPRITE_CAT + ///Cat tail bodypart overlay /datum/bodypart_overlay/mutant/tail/cat feature_key = "tail_cat" diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm b/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm index cec0241ece34c..cd9de70c4e23e 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm @@ -16,7 +16,7 @@ var/HUD_type = 0 var/HUD_trait = null /// Whether the HUD implant is on or off - var/toggled_on = TRUE + var/toggled_on = TRUE /obj/item/organ/internal/cyberimp/eyes/hud/proc/toggle_hud(mob/living/carbon/eye_owner) @@ -25,11 +25,13 @@ var/datum/atom_hud/hud = GLOB.huds[HUD_type] hud.hide_from(eye_owner) toggled_on = FALSE + balloon_alert(eye_owner, "hud disabled") else if(HUD_type) var/datum/atom_hud/hud = GLOB.huds[HUD_type] hud.show_to(eye_owner) toggled_on = TRUE + balloon_alert(eye_owner, "hud enabled") /obj/item/organ/internal/cyberimp/eyes/hud/Insert(mob/living/carbon/eye_owner, special = FALSE, movement_flags) . = ..() diff --git a/code/modules/surgery/organs/internal/eyes/_eyes.dm b/code/modules/surgery/organs/internal/eyes/_eyes.dm index c174d12260806..aaf79ba755369 100644 --- a/code/modules/surgery/organs/internal/eyes/_eyes.dm +++ b/code/modules/surgery/organs/internal/eyes/_eyes.dm @@ -357,8 +357,9 @@ tint = INFINITY var/obj/item/flashlight/eyelight/eye -/obj/item/organ/internal/eyes/robotic/flashlight/emp_act(severity) - return +/obj/item/organ/internal/eyes/robotic/flashlight/Initialize(mapload) + . = ..() + AddElement(/datum/element/empprotection, EMP_PROTECT_ALL) /obj/item/organ/internal/eyes/robotic/flashlight/on_mob_insert(mob/living/carbon/victim) . = ..() @@ -382,8 +383,9 @@ desc = "These reactive micro-shields will protect you from welders and flashes without obscuring your vision." flash_protect = FLASH_PROTECTION_WELDER -/obj/item/organ/internal/eyes/robotic/shield/emp_act(severity) - return +/obj/item/organ/internal/eyes/robotic/shield/Initialize(mapload) + . = ..() + AddElement(/datum/element/empprotection, EMP_PROTECT_ALL) #define MATCH_LIGHT_COLOR 1 #define USE_CUSTOM_COLOR 0 @@ -419,7 +421,7 @@ deactivate(close_ui = TRUE) QDEL_NULL(eye) -/obj/item/organ/internal/eyes/robotic/glow/emp_act() +/obj/item/organ/internal/eyes/robotic/glow/emp_act(severity) . = ..() if(!eye.light_on || . & EMP_PROTECT_SELF) return diff --git a/code/modules/surgery/organs/internal/liver/_liver.dm b/code/modules/surgery/organs/internal/liver/_liver.dm index 33dcaa83fd1f7..dd996a91f14c1 100755 --- a/code/modules/surgery/organs/internal/liver/_liver.dm +++ b/code/modules/surgery/organs/internal/liver/_liver.dm @@ -100,6 +100,8 @@ . += span_info("A half-digested rat's tail (somehow), disgusting sludge, and the faint smell of Grey Bull imply this is what remains of an assistant's liver.") if(HAS_TRAIT(src, TRAIT_CORONER_METABOLISM)) . += span_info("An aroma of pickles and sea water, along with being remarkably well-preserved, imply this is what remains of a coroner's liver.") + if(HAS_TRAIT(src, TRAIT_HUMAN_AI_METABOLISM)) + . += span_info("The liver appears barely human and entirely self-sufficient, implying this is what remains of a human AI's liver.") // royal trumps pretender royal if(HAS_TRAIT(src, TRAIT_ROYAL_METABOLISM)) diff --git a/code/modules/surgery/organs/internal/stomach/_stomach.dm b/code/modules/surgery/organs/internal/stomach/_stomach.dm index 6e985e26329b6..b055503c028b3 100644 --- a/code/modules/surgery/organs/internal/stomach/_stomach.dm +++ b/code/modules/surgery/organs/internal/stomach/_stomach.dm @@ -128,16 +128,11 @@ if(human.overeatduration < (200 SECONDS)) to_chat(human, span_notice("You feel fit again!")) REMOVE_TRAIT(human, TRAIT_FAT, OBESITY) - human.remove_movespeed_modifier(/datum/movespeed_modifier/obesity) - human.update_worn_undersuit() - human.update_worn_oversuit() + else if(human.overeatduration >= (200 SECONDS)) to_chat(human, span_danger("You suddenly feel blubbery!")) ADD_TRAIT(human, TRAIT_FAT, OBESITY) - human.add_movespeed_modifier(/datum/movespeed_modifier/obesity) - human.update_worn_undersuit() - human.update_worn_oversuit() // nutrition decrease and satiety if (human.nutrition > 0 && human.stat != DEAD) @@ -189,18 +184,6 @@ if(CONFIG_GET(flag/disable_human_mood)) handle_hunger_slowdown(human) - // If we did anything more then just set and throw alerts here I would add bracketing - // But well, it is all we do, so there's not much point bothering with it you get me? - switch(nutrition) - if(NUTRITION_LEVEL_FULL to INFINITY) - human.throw_alert(ALERT_NUTRITION, /atom/movable/screen/alert/fat) - if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_FULL) - human.clear_alert(ALERT_NUTRITION) - if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_HUNGRY) - human.throw_alert(ALERT_NUTRITION, /atom/movable/screen/alert/hungry) - if(0 to NUTRITION_LEVEL_STARVING) - human.throw_alert(ALERT_NUTRITION, /atom/movable/screen/alert/starving) - ///for when mood is disabled and hunger should handle slowdowns /obj/item/organ/internal/stomach/proc/handle_hunger_slowdown(mob/living/carbon/human/human) var/hungry = (500 - human.nutrition) / 5 //So overeat would be 100 and default level would be 80 @@ -262,13 +245,16 @@ disgusted.throw_alert(ALERT_DISGUST, /atom/movable/screen/alert/disgusted) disgusted.add_mood_event("disgust", /datum/mood_event/disgusted) +/obj/item/organ/internal/stomach/Insert(mob/living/carbon/receiver, special, movement_flags) + . = ..() + receiver.hud_used?.hunger?.update_appearance() + /obj/item/organ/internal/stomach/Remove(mob/living/carbon/stomach_owner, special, movement_flags) if(ishuman(stomach_owner)) var/mob/living/carbon/human/human_owner = owner human_owner.clear_alert(ALERT_DISGUST) human_owner.clear_mood_event("disgust") - human_owner.clear_alert(ALERT_NUTRITION) - + stomach_owner.hud_used?.hunger?.update_appearance() return ..() /obj/item/organ/internal/stomach/bone diff --git a/code/modules/surgery/organs/internal/stomach/stomach_ethereal.dm b/code/modules/surgery/organs/internal/stomach/stomach_ethereal.dm index 68f9d9428a04a..9f268b41c178a 100644 --- a/code/modules/surgery/organs/internal/stomach/stomach_ethereal.dm +++ b/code/modules/surgery/organs/internal/stomach/stomach_ethereal.dm @@ -33,7 +33,7 @@ SIGNAL_HANDLER adjust_charge(amount / 3.5) -/obj/item/organ/internal/stomach/ethereal/proc/on_electrocute(datum/source, shock_damage, siemens_coeff = 1, flags = NONE) +/obj/item/organ/internal/stomach/ethereal/proc/on_electrocute(datum/source, shock_damage, shock_source, siemens_coeff = 1, flags = NONE) SIGNAL_HANDLER if(flags & SHOCK_ILLUSION) return diff --git a/code/modules/surgery/tools.dm b/code/modules/surgery/tools.dm index fee7f389dcaef..042ea28be2d01 100644 --- a/code/modules/surgery/tools.dm +++ b/code/modules/surgery/tools.dm @@ -94,7 +94,8 @@ w_class = WEIGHT_CLASS_NORMAL toolspeed = 0.7 light_system = OVERLAY_LIGHT - light_range = 1 + light_range = 1.5 + light_power = 1.2 light_color = COLOR_SOFT_RED /obj/item/cautery/advanced/get_all_tool_behaviours() @@ -379,7 +380,8 @@ w_class = WEIGHT_CLASS_NORMAL toolspeed = 0.7 light_system = OVERLAY_LIGHT - light_range = 1 + light_range = 1.5 + light_power = 1.2 light_color = LIGHT_COLOR_BLUE sharpness = SHARP_EDGED @@ -413,7 +415,7 @@ set_light_range(2) else tool_behaviour = TOOL_SCALPEL - set_light_range(1) + set_light_range(1.5) balloon_alert(user, "[active ? "enabled" : "disabled"] bone-cutting mode") playsound(user ? user : src, 'sound/machines/click.ogg', 50, TRUE) diff --git a/code/modules/tgs/core/core.dm b/code/modules/tgs/core/core.dm index 8be96f27404a4..15622228e91fe 100644 --- a/code/modules/tgs/core/core.dm +++ b/code/modules/tgs/core/core.dm @@ -166,3 +166,11 @@ var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) if(api) return api.Visibility() + +/world/TgsTriggerEvent(event_name, list/parameters, wait_for_completion = FALSE) + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + if(!istype(parameters, /list)) + parameters = list() + + return api.TriggerEvent(event_name, parameters, wait_for_completion) diff --git a/code/modules/tgs/core/datum.dm b/code/modules/tgs/core/datum.dm index 07ce3b684584e..898516f12486f 100644 --- a/code/modules/tgs/core/datum.dm +++ b/code/modules/tgs/core/datum.dm @@ -17,7 +17,7 @@ TGS_DEFINE_AND_SET_GLOBAL(tgs, null) world.sleep_offline = FALSE // https://www.byond.com/forum/post/2894866 del(world) world.sleep_offline = FALSE // just in case, this is BYOND after all... - sleep(1) + sleep(world.tick_lag) TGS_DEBUG_LOG("BYOND DIDN'T TERMINATE THE WORLD!!! TICK IS: [world.time], sleep_offline: [world.sleep_offline]") /datum/tgs_api/latest @@ -69,3 +69,6 @@ TGS_PROTECT_DATUM(/datum/tgs_api) /datum/tgs_api/proc/Visibility() return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/TriggerEvent(event_name, list/parameters, wait_for_completion) + return FALSE diff --git a/code/modules/tgs/v4/api.dm b/code/modules/tgs/v4/api.dm index 945e2e4117671..7c87922750b9b 100644 --- a/code/modules/tgs/v4/api.dm +++ b/code/modules/tgs/v4/api.dm @@ -181,7 +181,7 @@ var/json = json_encode(data) while(requesting_new_port && !override_requesting_new_port) - sleep(1) + sleep(world.tick_lag) //we need some port open at this point to facilitate return communication if(!world.port) @@ -209,7 +209,7 @@ requesting_new_port = FALSE while(export_lock) - sleep(1) + sleep(world.tick_lag) export_lock = TRUE last_interop_response = null @@ -217,7 +217,7 @@ text2file(json, server_commands_json_path) for(var/I = 0; I < EXPORT_TIMEOUT_DS && !last_interop_response; ++I) - sleep(1) + sleep(world.tick_lag) if(!last_interop_response) TGS_ERROR_LOG("Failed to get export result for: [json]") diff --git a/code/modules/tgs/v5/__interop_version.dm b/code/modules/tgs/v5/__interop_version.dm index 616263098fd3e..f4806f7adb97c 100644 --- a/code/modules/tgs/v5/__interop_version.dm +++ b/code/modules/tgs/v5/__interop_version.dm @@ -1 +1 @@ -"5.8.0" +"5.9.0" diff --git a/code/modules/tgs/v5/_defines.dm b/code/modules/tgs/v5/_defines.dm index 1c7d67d20cdf6..92c7a8388a711 100644 --- a/code/modules/tgs/v5/_defines.dm +++ b/code/modules/tgs/v5/_defines.dm @@ -14,6 +14,7 @@ #define DMAPI5_BRIDGE_COMMAND_KILL 4 #define DMAPI5_BRIDGE_COMMAND_CHAT_SEND 5 #define DMAPI5_BRIDGE_COMMAND_CHUNK 6 +#define DMAPI5_BRIDGE_COMMAND_EVENT 7 #define DMAPI5_PARAMETER_ACCESS_IDENTIFIER "accessIdentifier" #define DMAPI5_PARAMETER_CUSTOM_COMMANDS "customCommands" @@ -34,6 +35,7 @@ #define DMAPI5_BRIDGE_PARAMETER_VERSION "version" #define DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE "chatMessage" #define DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL "minimumSecurityLevel" +#define DMAPI5_BRIDGE_PARAMETER_EVENT_INVOCATION "eventInvocation" #define DMAPI5_BRIDGE_RESPONSE_NEW_PORT "newPort" #define DMAPI5_BRIDGE_RESPONSE_RUNTIME_INFORMATION "runtimeInformation" @@ -81,6 +83,7 @@ #define DMAPI5_TOPIC_COMMAND_SEND_CHUNK 9 #define DMAPI5_TOPIC_COMMAND_RECEIVE_CHUNK 10 #define DMAPI5_TOPIC_COMMAND_RECEIVE_BROADCAST 11 +#define DMAPI5_TOPIC_COMMAND_COMPLETE_EVENT 12 #define DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE "commandType" #define DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND "chatCommand" @@ -116,3 +119,9 @@ #define DMAPI5_CUSTOM_CHAT_COMMAND_NAME "name" #define DMAPI5_CUSTOM_CHAT_COMMAND_HELP_TEXT "helpText" #define DMAPI5_CUSTOM_CHAT_COMMAND_ADMIN_ONLY "adminOnly" + +#define DMAPI5_EVENT_ID "eventId" + +#define DMAPI5_EVENT_INVOCATION_NAME "eventName" +#define DMAPI5_EVENT_INVOCATION_PARAMETERS "parameters" +#define DMAPI5_EVENT_INVOCATION_NOTIFY_COMPLETION "notifyCompletion" diff --git a/code/modules/tgs/v5/api.dm b/code/modules/tgs/v5/api.dm index a5c064a8eaf1e..95b8edd3ee5c2 100644 --- a/code/modules/tgs/v5/api.dm +++ b/code/modules/tgs/v5/api.dm @@ -27,6 +27,8 @@ var/chunked_requests = 0 var/list/chunked_topics = list() + var/list/pending_events = list() + var/detached = FALSE /datum/tgs_api/v5/New() @@ -46,6 +48,10 @@ var/datum/tgs_version/api_version = ApiVersion() version = null // we want this to be the TGS version, not the interop version + + // sleep once to prevent an issue where world.Export on the first tick can hang indefinitely + sleep(world.tick_lag) + var/list/bridge_response = Bridge(DMAPI5_BRIDGE_COMMAND_STARTUP, list(DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL = minimum_required_security_level, DMAPI5_BRIDGE_PARAMETER_VERSION = api_version.raw_parameter, DMAPI5_PARAMETER_CUSTOM_COMMANDS = ListCustomCommands(), DMAPI5_PARAMETER_TOPIC_PORT = GetTopicPort())) if(!istype(bridge_response)) TGS_ERROR_LOG("Failed initial bridge request!") @@ -125,7 +131,7 @@ TGS_DEBUG_LOG("RequireInitialBridgeResponse: Starting sleep") logged = TRUE - sleep(1) + sleep(world.tick_lag) TGS_DEBUG_LOG("RequireInitialBridgeResponse: Passed") @@ -249,6 +255,40 @@ WaitForReattach(TRUE) return chat_channels.Copy() +/datum/tgs_api/v5/TriggerEvent(event_name, list/parameters, wait_for_completion) + RequireInitialBridgeResponse() + WaitForReattach(TRUE) + + if(interop_version.minor < 9) + TGS_WARNING_LOG("Interop version too low for custom events!") + return FALSE + + var/str_parameters = list() + for(var/i in parameters) + str_parameters += "[i]" + + var/list/response = Bridge(DMAPI5_BRIDGE_COMMAND_EVENT, list(DMAPI5_BRIDGE_PARAMETER_EVENT_INVOCATION = list(DMAPI5_EVENT_INVOCATION_NAME = event_name, DMAPI5_EVENT_INVOCATION_PARAMETERS = str_parameters, DMAPI5_EVENT_INVOCATION_NOTIFY_COMPLETION = wait_for_completion))) + if(!response) + return FALSE + + var/event_id = response[DMAPI5_EVENT_ID] + if(!event_id) + return FALSE + + TGS_DEBUG_LOG("Created event ID: [event_id]") + if(!wait_for_completion) + return TRUE + + TGS_DEBUG_LOG("Waiting for completion of event ID: [event_id]") + + while(!pending_events[event_id]) + sleep(world.tick_lag) + + TGS_DEBUG_LOG("Completed wait on event ID: [event_id]") + pending_events -= event_id + + return TRUE + /datum/tgs_api/v5/proc/DecodeChannels(chat_update_json) TGS_DEBUG_LOG("DecodeChannels()") var/list/chat_channels_json = chat_update_json[DMAPI5_CHAT_UPDATE_CHANNELS] diff --git a/code/modules/tgs/v5/bridge.dm b/code/modules/tgs/v5/bridge.dm index a0ab359876704..0c5e701a32b60 100644 --- a/code/modules/tgs/v5/bridge.dm +++ b/code/modules/tgs/v5/bridge.dm @@ -65,7 +65,7 @@ if(detached) // Wait up to one minute for(var/i in 1 to 600) - sleep(1) + sleep(world.tick_lag) if(!detached && (!require_channels || length(chat_channels))) break @@ -77,8 +77,11 @@ /datum/tgs_api/v5/proc/PerformBridgeRequest(bridge_request) WaitForReattach(FALSE) + TGS_DEBUG_LOG("Bridge request start") // This is an infinite sleep until we get a response var/export_response = world.Export(bridge_request) + TGS_DEBUG_LOG("Bridge request complete") + if(!export_response) TGS_ERROR_LOG("Failed bridge request: [bridge_request]") return @@ -88,7 +91,7 @@ TGS_ERROR_LOG("Failed bridge request, missing content!") return - var/response_json = file2text(content) + var/response_json = TGS_FILE2TEXT_NATIVE(content) if(!response_json) TGS_ERROR_LOG("Failed bridge request, failed to load content!") return diff --git a/code/modules/tgs/v5/topic.dm b/code/modules/tgs/v5/topic.dm index 05e6c4e1b2146..e1f2cb6385789 100644 --- a/code/modules/tgs/v5/topic.dm +++ b/code/modules/tgs/v5/topic.dm @@ -176,6 +176,10 @@ var/list/reattach_response = TopicResponse(error_message) reattach_response[DMAPI5_PARAMETER_CUSTOM_COMMANDS] = ListCustomCommands() reattach_response[DMAPI5_PARAMETER_TOPIC_PORT] = GetTopicPort() + + for(var/eventId in pending_events) + pending_events[eventId] = TRUE + return reattach_response if(DMAPI5_TOPIC_COMMAND_SEND_CHUNK) @@ -276,6 +280,15 @@ TGS_WORLD_ANNOUNCE(message) return TopicResponse() + if(DMAPI5_TOPIC_COMMAND_COMPLETE_EVENT) + var/event_id = topic_parameters[DMAPI5_EVENT_ID] + if (!istext(event_id)) + return TopicResponse("Invalid or missing [DMAPI5_EVENT_ID]") + + TGS_DEBUG_LOG("Completing event ID [event_id]...") + pending_events[event_id] = TRUE + return TopicResponse() + return TopicResponse("Unknown command: [command]") /datum/tgs_api/v5/proc/WorldBroadcast(message) diff --git a/code/modules/tgs/v5/undefs.dm b/code/modules/tgs/v5/undefs.dm index d531d4b7b9dd1..237207fdfd056 100644 --- a/code/modules/tgs/v5/undefs.dm +++ b/code/modules/tgs/v5/undefs.dm @@ -14,6 +14,7 @@ #undef DMAPI5_BRIDGE_COMMAND_KILL #undef DMAPI5_BRIDGE_COMMAND_CHAT_SEND #undef DMAPI5_BRIDGE_COMMAND_CHUNK +#undef DMAPI5_BRIDGE_COMMAND_EVENT #undef DMAPI5_PARAMETER_ACCESS_IDENTIFIER #undef DMAPI5_PARAMETER_CUSTOM_COMMANDS @@ -34,6 +35,7 @@ #undef DMAPI5_BRIDGE_PARAMETER_VERSION #undef DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE #undef DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL +#undef DMAPI5_BRIDGE_PARAMETER_EVENT_INVOCATION #undef DMAPI5_BRIDGE_RESPONSE_NEW_PORT #undef DMAPI5_BRIDGE_RESPONSE_RUNTIME_INFORMATION @@ -81,6 +83,7 @@ #undef DMAPI5_TOPIC_COMMAND_SEND_CHUNK #undef DMAPI5_TOPIC_COMMAND_RECEIVE_CHUNK #undef DMAPI5_TOPIC_COMMAND_RECEIVE_BROADCAST +#undef DMAPI5_TOPIC_COMMAND_COMPLETE_EVENT #undef DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE #undef DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND @@ -116,3 +119,9 @@ #undef DMAPI5_CUSTOM_CHAT_COMMAND_NAME #undef DMAPI5_CUSTOM_CHAT_COMMAND_HELP_TEXT #undef DMAPI5_CUSTOM_CHAT_COMMAND_ADMIN_ONLY + +#undef DMAPI5_EVENT_ID + +#undef DMAPI5_EVENT_INVOCATION_NAME +#undef DMAPI5_EVENT_INVOCATION_PARAMETERS +#undef DMAPI5_EVENT_INVOCATION_NOTIFY_COMPLETION diff --git a/code/modules/tgui/states.dm b/code/modules/tgui/states.dm index 92fa3b5b5c190..dc3d543f14364 100644 --- a/code/modules/tgui/states.dm +++ b/code/modules/tgui/states.dm @@ -101,6 +101,11 @@ * return UI_state The state of the UI. */ /mob/living/proc/shared_living_ui_distance(atom/movable/src_object, viewcheck = TRUE, allow_tk = TRUE) + var/obj/item/item_in_hand = get_active_held_item() + if(istype(item_in_hand, /obj/item/machine_remote)) //snowflake, this lets you interact with all. + var/obj/item/machine_remote/remote = item_in_hand + if(remote.controlling_machine_or_bot == src_object) + return UI_INTERACTIVE // If the object is obscured, close it. if(viewcheck && !(src_object in view(src))) return UI_CLOSE diff --git a/code/modules/tgui_input/text.dm b/code/modules/tgui_input/text.dm index f97e0326d58ef..4b3e59a6028c7 100644 --- a/code/modules/tgui_input/text.dm +++ b/code/modules/tgui_input/text.dm @@ -32,9 +32,9 @@ if(!user.client.prefs.read_preference(/datum/preference/toggle/tgui_input)) if(encode) if(multiline) - return stripped_multiline_input(user, message, title, default, max_length) + return stripped_multiline_input(user, message, title, default, PREVENT_CHARACTER_TRIM_LOSS(max_length)) else - return stripped_input(user, message, title, default, max_length) + return stripped_input(user, message, title, default, PREVENT_CHARACTER_TRIM_LOSS(max_length)) else if(multiline) return input(user, message, title, default) as message|null @@ -162,4 +162,4 @@ /datum/tgui_input_text/proc/set_entry(entry) if(!isnull(entry)) var/converted_entry = encode ? html_encode(entry) : entry - src.entry = trim(converted_entry, max_length) + src.entry = trim(converted_entry, PREVENT_CHARACTER_TRIM_LOSS(max_length)) diff --git a/code/modules/transport/tram/tram_controller.dm b/code/modules/transport/tram/tram_controller.dm index 4dceecbfc4c26..9e95870ab6d6a 100644 --- a/code/modules/transport/tram/tram_controller.dm +++ b/code/modules/transport/tram/tram_controller.dm @@ -992,7 +992,7 @@ /obj/machinery/transport/tram_controller/ui_interact(mob/user, datum/tgui/ui) . = ..() - if(!cover_open && !issiliconoradminghost(user) && !isobserver(user)) + if(!cover_open && !HAS_SILICON_ACCESS(user) && !isobserver(user)) return if(!is_operational) diff --git a/code/modules/transport/tram/tram_floors.dm b/code/modules/transport/tram/tram_floors.dm index 1e1fad836c3b2..9f0b6907fe9c1 100644 --- a/code/modules/transport/tram/tram_floors.dm +++ b/code/modules/transport/tram/tram_floors.dm @@ -255,7 +255,7 @@ span_notice("You wedge \the [tool] into the tram panel's gap in the frame and start prying...")) if(tool.use_tool(src, user, 1 SECONDS, volume = 50)) to_chat(user, span_notice("The panel pops out of the frame.")) - var/obj/item/stack/thermoplastic/pulled_tile = new() + var/obj/item/stack/thermoplastic/pulled_tile = new floor_tile() pulled_tile.update_integrity(atom_integrity) user.put_in_hands(pulled_tile) qdel(src) @@ -283,8 +283,8 @@ icon = 'icons/obj/tiles.dmi' lefthand_file = 'icons/mob/inhands/items/tiles_lefthand.dmi' righthand_file = 'icons/mob/inhands/items/tiles_righthand.dmi' - icon_state = "tile_textured_white_large" - inhand_icon_state = "tile-neon-glow" + icon_state = "tile_tram_dark" + inhand_icon_state = "tile-tram" color = COLOR_TRAM_BLUE w_class = WEIGHT_CLASS_NORMAL force = 1 @@ -297,7 +297,9 @@ var/tile_type = /obj/structure/thermoplastic /obj/item/stack/thermoplastic/light + icon_state = "tile_tram_light" color = COLOR_TRAM_LIGHT_BLUE + merge_type = /obj/item/stack/thermoplastic/light tile_type = /obj/structure/thermoplastic/light /obj/item/stack/thermoplastic/Initialize(mapload, new_amount, merge = TRUE, list/mat_override=null, mat_amt=1) diff --git a/code/modules/transport/transport_module.dm b/code/modules/transport/transport_module.dm index 9fdfefc835cae..7fcfa53fba159 100644 --- a/code/modules/transport/transport_module.dm +++ b/code/modules/transport/transport_module.dm @@ -844,7 +844,6 @@ transport_id = TRANSPORT_TYPE_TRAM transport_controller_type = /datum/transport_controller/linear/tram radial_travel = FALSE - obj_flags = NONE /// Set by the tram control console in late initialize var/travelling = FALSE diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 61bc9ec6d4e6e..46b2470d647f4 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -247,6 +247,7 @@ #include "spell_mindswap.dm" #include "spell_names.dm" #include "spell_shapeshift.dm" +#include "spies.dm" #include "spritesheets.dm" #include "stack_singular_name.dm" #include "station_trait_tests.dm" diff --git a/code/modules/unit_tests/antag_conversion.dm b/code/modules/unit_tests/antag_conversion.dm index 01c05671ef72e..f499922bf8dbe 100644 --- a/code/modules/unit_tests/antag_conversion.dm +++ b/code/modules/unit_tests/antag_conversion.dm @@ -36,7 +36,7 @@ // Success state leader.ClickOn(peasant) - TEST_ASSERT(peasant.IsParalyzed(), "Peasant was not paralyzed after being flashed by the leader.") // Flash paralyze + TEST_ASSERT((peasant.get_timed_status_effect_duration(/datum/status_effect/confusion) > 0), "Peasant was not confused after being flashed by the leader.") // Flash confuse TEST_ASSERT(peasant.IsStun(), "Peasant was not stunned after being converted by the leader.") // Conversion stun TEST_ASSERT(IS_REVOLUTIONARY(peasant), "Peasant did not gain revolution antag datum on conversion.") TEST_ASSERT_EQUAL(length(revolution.members), 2, "Expected revolution to have 2 members after the leader flashes the peasant.") diff --git a/code/modules/unit_tests/bitrunning.dm b/code/modules/unit_tests/bitrunning.dm index 568eeeed8c133..44dd69390afdb 100644 --- a/code/modules/unit_tests/bitrunning.dm +++ b/code/modules/unit_tests/bitrunning.dm @@ -9,7 +9,7 @@ TEST_ASSERT_NOTNULL(vdom.key, "[path] should have a key") TEST_ASSERT_NOTNULL(vdom.map_name, "[path] should have a map name") - if(!length(vdom.extra_loot)) + if(!length(vdom.completion_loot)) continue - TEST_ASSERT_EQUAL(cache.spawn_loot(vdom.extra_loot), TRUE, "[path] didn't spawn loot. Extra loot should be an associative list") + TEST_ASSERT_EQUAL(cache.spawn_loot(vdom.completion_loot), TRUE, "[path] didn't spawn loot. Completion loot should be an associative list") diff --git a/code/modules/unit_tests/screenshots/screenshot_antag_icons_spy.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_spy.png new file mode 100644 index 0000000000000..103e9d60faf7c Binary files /dev/null and b/code/modules/unit_tests/screenshots/screenshot_antag_icons_spy.png differ diff --git a/code/modules/unit_tests/screenshots/screenshot_high_luminosity_eyes_light_on.png b/code/modules/unit_tests/screenshots/screenshot_high_luminosity_eyes_light_on.png index 272f6f2570761..8468461cc6e58 100644 Binary files a/code/modules/unit_tests/screenshots/screenshot_high_luminosity_eyes_light_on.png and b/code/modules/unit_tests/screenshots/screenshot_high_luminosity_eyes_light_on.png differ diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index a40723c2f6927..41b5cdf4767e9 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -43,7 +43,6 @@ /mob/living/simple_animal/hostile/asteroid/elite/pandora, /mob/living/simple_animal/hostile/asteroid/polarbear, /mob/living/simple_animal/hostile/asteroid/polarbear/lesser, - /mob/living/simple_animal/hostile/asteroid/wolf, /mob/living/simple_animal/hostile/dark_wizard, /mob/living/simple_animal/hostile/illusion, /mob/living/simple_animal/hostile/illusion/escape, diff --git a/code/modules/unit_tests/siunit.dm b/code/modules/unit_tests/siunit.dm index 3a7a25a98d3ee..7b98db497c8f6 100644 --- a/code/modules/unit_tests/siunit.dm +++ b/code/modules/unit_tests/siunit.dm @@ -12,4 +12,4 @@ TEST_ASSERT_EQUAL(siunit_pressure(999.9e3), "999.9 MPa" , "") TEST_ASSERT_EQUAL(siunit_pressure(999.9e3, 0), "1 GPa", "") TEST_ASSERT_EQUAL(siunit_pressure(1e6), "1 GPa", "") - TEST_ASSERT_EQUAL(siunit_pressure(3e17), "300000 PPa", "") + TEST_ASSERT_EQUAL(siunit_pressure(3e32), "300000 QPa", "") diff --git a/code/modules/unit_tests/spies.dm b/code/modules/unit_tests/spies.dm new file mode 100644 index 0000000000000..b4b1add333cb6 --- /dev/null +++ b/code/modules/unit_tests/spies.dm @@ -0,0 +1,41 @@ +/// Tests spy bounty setup +/datum/unit_test/spy_bounty + +/datum/unit_test/spy_bounty/Run() + var/mob/living/carbon/human/james_bond = allocate(/mob/living/carbon/human/consistent) + james_bond.mind_initialize() + james_bond.equipOutfit(/datum/outfit/job/assistant/consistent) + var/datum/antagonist/spy/spy = james_bond.mind.add_antag_datum(/datum/antagonist/spy) + + var/datum/component/spy_uplink/uplink = spy.uplink_weakref?.resolve() + TEST_ASSERT_NOTNULL(uplink, "Spy failed to be given an uplink!") + + var/datum/spy_bounty_handler/handler = uplink.handler + handler.num_attempts_override = 100 + + for(var/difficulty in handler.possible_uplink_items) + var/list/loot_pool = handler.possible_uplink_items[difficulty] + if(!length(loot_pool)) + TEST_FAIL("No rewards generated for spy bounty difficulty [difficulty]") + + for(var/difficulty in UNLINT(handler.bounty_types)) + var/list/bounty_type_pool = UNLINT(handler.bounty_types[difficulty]) + if(!length(bounty_type_pool)) + TEST_FAIL("No bounty types for spy bounty difficulty [difficulty] found") + + for(var/difficulty in UNLINT(handler.bounties)) + var/list/generated_bounties = UNLINT(handler.bounties[difficulty]) + if(difficulty == SPY_DIFFICULTY_HARD) + if(length(generated_bounties)) + TEST_FAIL("No [difficulty] bounties should not be generated on initial refresh!") + + else + if(!length(generated_bounties)) + TEST_FAIL("No bounties were generated on initial refresh for difficulty [difficulty]") + + handler.force_refresh() + + for(var/difficulty in UNLINT(handler.bounties)) + var/list/generated_bounties = UNLINT(handler.bounties[difficulty]) + if(!length(generated_bounties)) + TEST_FAIL("No bounties were generated on first refresh for difficulty [difficulty]") diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm index 414c4189bb4a1..332005070d5b6 100644 --- a/code/modules/unit_tests/unit_test.dm +++ b/code/modules/unit_tests/unit_test.dm @@ -245,6 +245,8 @@ GLOBAL_VAR_INIT(focused_tests, focused_tests()) //Both are abstract types meant to scream bloody murder if spawned in raw /obj/item/organ/external, /obj/item/organ/external/wings, + //Not meant to spawn without the machine wand + /obj/effect/bug_moving, ) // Everything that follows is a typesof() check. @@ -292,6 +294,10 @@ GLOBAL_VAR_INIT(focused_tests, focused_tests()) returnable_list += typesof(/obj/item/hilbertshotel) //this boi spawns turf changing stuff, and it stacks and causes pain. Let's just not 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) + returnable_list += typesof(/obj/effect/spawner/random/contraband/landmine) + returnable_list += typesof(/obj/item/minespawner) //Stacks baseturfs, can't be tested here returnable_list += typesof(/obj/effect/temp_visual/lava_warning) //Stacks baseturfs, can't be tested here diff --git a/code/modules/uplink/uplink_items.dm b/code/modules/uplink/uplink_items.dm index 65935f077e33d..bb76564e42c46 100644 --- a/code/modules/uplink/uplink_items.dm +++ b/code/modules/uplink/uplink_items.dm @@ -149,6 +149,34 @@ SEND_SIGNAL(uplink_handler, COMSIG_ON_UPLINK_PURCHASE, spawned_item, user) return spawned_item +/// Used to create the uplink's item for generic use, rather than use by a Syndie specifically +/// Can be used to "de-restrict" some items, such as Nukie guns spawning with Syndicate pins +/datum/uplink_item/proc/spawn_item_for_generic_use(mob/user) + var/atom/movable/created = new item(user.loc) + + if(isgun(created)) + replace_pin(created) + else if(istype(created, /obj/item/storage/toolbox/guncase)) + for(var/obj/item/gun/gun in created) + replace_pin(gun) + + if(isobj(created)) + var/obj/created_obj = created + LAZYREMOVE(created_obj.req_access, ACCESS_SYNDICATE) + LAZYREMOVE(created_obj.req_one_access, ACCESS_SYNDICATE) + + return created + +/// Used by spawn_item_for_generic_use to replace the pin of a gun with a normal one +/datum/uplink_item/proc/replace_pin(obj/item/gun/gun_reward) + PRIVATE_PROC(TRUE) + + if(!istype(gun_reward.pin, /obj/item/firing_pin/implant/pindicate)) + return + + QDEL_NULL(gun_reward.pin) + gun_reward.pin = new /obj/item/firing_pin(gun_reward) + ///For special overrides if an item can be bought or not. /datum/uplink_item/proc/can_be_bought(datum/uplink_handler/source) return TRUE @@ -168,6 +196,7 @@ //Discounts (dynamically filled above) /datum/uplink_item/discounts category = /datum/uplink_category/discounts + purchasable_from = parent_type::purchasable_from & ~UPLINK_SPY // Probably not necessary but just in case // Special equipment (Dynamically fills in uplink component) /datum/uplink_item/special_equipment @@ -176,6 +205,7 @@ desc = "Equipment necessary for accomplishing specific objectives. If you are seeing this, something has gone wrong." limited_stock = 1 illegal_tech = FALSE + purchasable_from = parent_type::purchasable_from & ~UPLINK_SPY // Ditto /datum/uplink_item/special_equipment/purchase(mob/user, datum/component/uplink/U) ..() diff --git a/code/modules/uplink/uplink_items/ammunition.dm b/code/modules/uplink/uplink_items/ammunition.dm index e88727812528d..5326880d31be6 100644 --- a/code/modules/uplink/uplink_items/ammunition.dm +++ b/code/modules/uplink/uplink_items/ammunition.dm @@ -53,5 +53,5 @@ For when you really need a lot of things dead." item = /obj/item/ammo_box/a357 cost = 4 - purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) //nukies get their own version + purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY) //nukies get their own version illegal_tech = FALSE diff --git a/code/modules/uplink/uplink_items/bundle.dm b/code/modules/uplink/uplink_items/bundle.dm index f236aa4da253a..b708af62b69c9 100644 --- a/code/modules/uplink/uplink_items/bundle.dm +++ b/code/modules/uplink/uplink_items/bundle.dm @@ -7,11 +7,12 @@ category = /datum/uplink_category/bundle surplus = 0 cant_discount = TRUE + purchasable_from = parent_type::purchasable_from & ~UPLINK_SPY /datum/uplink_item/bundles_tc/random name = "Random Item" desc = "Picking this will purchase a random item. Useful if you have some TC to spare or if you haven't decided on a strategy yet." - item = /obj/effect/gibspawner/generic // non-tangible item because techwebs use this path to determine illegal tech + item = ABSTRACT_UPLINK_ITEM cost = 0 cost_override_string = "Varies" @@ -61,7 +62,7 @@ item = /obj/item/storage/box/syndicate/bundle_a cost = 20 stock_key = UPLINK_SHARED_STOCK_KITS - purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) + purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY) /datum/uplink_item/bundles_tc/bundle_b name = "Syndi-kit Special" @@ -72,7 +73,7 @@ item = /obj/item/storage/box/syndicate/bundle_b cost = 20 stock_key = UPLINK_SHARED_STOCK_KITS - purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) + purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY) /datum/uplink_item/bundles_tc/surplus name = "Syndicate Surplus Crate" @@ -81,7 +82,7 @@ Contents are sorted to always be worth 30 TC. The Syndicate will only provide one surplus item per agent." item = /obj/structure/closet/crate // will be replaced in purchase() cost = 20 - purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) + purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY) stock_key = UPLINK_SHARED_STOCK_SURPLUS /// Value of items inside the crate in TC var/crate_tc_value = 30 @@ -170,5 +171,5 @@ The Syndicate will only provide one surplus item per agent." cost = 20 item = /obj/item/syndicrate_key - purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) + purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY) stock_key = UPLINK_SHARED_STOCK_SURPLUS diff --git a/code/modules/uplink/uplink_items/clownops.dm b/code/modules/uplink/uplink_items/clownops.dm index 852676dbcbb74..56c11fedc0cb8 100644 --- a/code/modules/uplink/uplink_items/clownops.dm +++ b/code/modules/uplink/uplink_items/clownops.dm @@ -8,7 +8,7 @@ cost = 10 item = /obj/item/pneumatic_cannon/pie/selfcharge surplus = 0 - purchasable_from = UPLINK_CLOWN_OPS + purchasable_from = UPLINK_CLOWN_OPS | UPLINK_SPY /datum/uplink_item/weapon_kits/bananashield name = "Bananium Energy Shield" @@ -18,7 +18,7 @@ item = /obj/item/shield/energy/bananium cost = 16 surplus = 0 - purchasable_from = UPLINK_CLOWN_OPS + purchasable_from = UPLINK_CLOWN_OPS | UPLINK_SPY /datum/uplink_item/weapon_kits/clownsword name = "Bananium Energy Sword" @@ -27,7 +27,7 @@ item = /obj/item/melee/energy/sword/bananium cost = 3 surplus = 0 - purchasable_from = UPLINK_CLOWN_OPS + purchasable_from = UPLINK_CLOWN_OPS | UPLINK_SPY /datum/uplink_item/weapon_kits/clownoppin name = "Ultra Hilarious Firing Pin" @@ -51,7 +51,7 @@ item = /obj/item/gun/ballistic/automatic/c20r/toy cost = 5 surplus = 0 - purchasable_from = UPLINK_CLOWN_OPS + purchasable_from = UPLINK_CLOWN_OPS | UPLINK_SPY /datum/uplink_item/weapon_kits/foammachinegun name = "Toy Machine Gun" @@ -60,7 +60,7 @@ item = /obj/item/gun/ballistic/automatic/l6_saw/toy cost = 10 surplus = 0 - purchasable_from = UPLINK_CLOWN_OPS + purchasable_from = UPLINK_CLOWN_OPS | UPLINK_SPY /datum/uplink_item/explosives/bombanana name = "Bombanana" @@ -69,7 +69,7 @@ item = /obj/item/food/grown/banana/bombanana cost = 4 //it is a bit cheaper than a minibomb because you have to take off your helmet to eat it, which is how you arm it surplus = 0 - purchasable_from = UPLINK_CLOWN_OPS + purchasable_from = UPLINK_CLOWN_OPS | UPLINK_SPY /datum/uplink_item/explosives/clown_bomb_clownops name = "Clown Bomb" @@ -81,7 +81,7 @@ item = /obj/item/sbeacondrop/clownbomb cost = 15 surplus = 0 - purchasable_from = UPLINK_CLOWN_OPS + purchasable_from = UPLINK_CLOWN_OPS | UPLINK_SPY /datum/uplink_item/explosives/clown_bomb_clownops/New() . = ..() @@ -94,7 +94,7 @@ item = /obj/item/grenade/chem_grenade/teargas/moustache cost = 3 surplus = 0 - purchasable_from = UPLINK_CLOWN_OPS + purchasable_from = UPLINK_CLOWN_OPS | UPLINK_SPY /datum/uplink_item/explosives/pinata name = "Weapons Grade Pinata Kit" @@ -158,4 +158,3 @@ cost = 1 purchasable_from = UPLINK_CLOWN_OPS illegal_tech = FALSE - diff --git a/code/modules/uplink/uplink_items/contractor.dm b/code/modules/uplink/uplink_items/contractor.dm index 6004caf97452e..7d261410e314d 100644 --- a/code/modules/uplink/uplink_items/contractor.dm +++ b/code/modules/uplink/uplink_items/contractor.dm @@ -13,7 +13,7 @@ item = /obj/item/storage/box/syndicate/contract_kit category = /datum/uplink_category/contractor cost = 20 - purchasable_from = ~(UPLINK_CLOWN_OPS | UPLINK_NUKE_OPS | UPLINK_TRAITORS) + purchasable_from = UPLINK_INFILTRATORS /datum/uplink_item/bundles_tc/contract_kit/purchase(mob/user, datum/uplink_handler/uplink_handler, atom/movable/source) . = ..() @@ -36,7 +36,7 @@ name = "Contract Reroll" desc = "Request a reroll of your current contract list. Will generate a new target, \ payment, and dropoff for the contracts you currently have available." - item = /obj/effect/gibspawner/generic + item = ABSTRACT_UPLINK_ITEM limited_stock = 2 cost = 0 diff --git a/code/modules/uplink/uplink_items/device_tools.dm b/code/modules/uplink/uplink_items/device_tools.dm index 66cb58c7b2899..7f87d93464e48 100644 --- a/code/modules/uplink/uplink_items/device_tools.dm +++ b/code/modules/uplink/uplink_items/device_tools.dm @@ -134,7 +134,7 @@ /datum/uplink_item/device_tools/failsafe name = "Failsafe Uplink Code" desc = "When entered the uplink will self-destruct immediately." - item = /obj/effect/gibspawner/generic + item = ABSTRACT_UPLINK_ITEM cost = 1 surplus = 0 restricted = TRUE diff --git a/code/modules/uplink/uplink_items/implant.dm b/code/modules/uplink/uplink_items/implant.dm index 87c9fd6c96c07..a2b21574f6f34 100644 --- a/code/modules/uplink/uplink_items/implant.dm +++ b/code/modules/uplink/uplink_items/implant.dm @@ -49,6 +49,7 @@ // An empty uplink is kinda useless. surplus = 0 restricted = TRUE + purchasable_from = parent_type::purchasable_from & ~UPLINK_SPY /datum/uplink_item/implants/uplink/spawn_item(spawn_path, mob/user, datum/uplink_handler/uplink_handler, atom/movable/source) var/obj/item/storage/box/syndie_kit/uplink_box = ..() diff --git a/code/modules/uplink/uplink_items/job.dm b/code/modules/uplink/uplink_items/job.dm index 3af8674e4fcf5..df4f235f50cca 100644 --- a/code/modules/uplink/uplink_items/job.dm +++ b/code/modules/uplink/uplink_items/job.dm @@ -28,7 +28,7 @@ /datum/uplink_item/role_restricted/bureaucratic_error name = "Organic Capital Disturbance Virus" desc = "Randomizes job positions presented to new hires. May lead to too many/too few security officers and/or clowns. Single use." - item = /obj/effect/gibspawner/generic + item = ABSTRACT_UPLINK_ITEM surplus = 0 limited_stock = 1 cost = 2 @@ -184,7 +184,7 @@ desc = "A bootleg copy of an collector item, this disk contains the procedure to perform advanced plastic surgery, allowing you to model someone's face and voice based on a picture taken by a camera on your offhand. \ All changes are superficial and does not change ones genetic makeup. \ Insert into an Operating Console to enable the procedure." - item = /obj/item/disk/surgery/brainwashing + item = /obj/item/disk/surgery/advanced_plastic_surgery restricted_roles = list(JOB_MEDICAL_DOCTOR, JOB_CHIEF_MEDICAL_OFFICER, JOB_ROBOTICIST) cost = 1 surplus = 50 @@ -286,6 +286,13 @@ restricted_roles = list(JOB_CLOWN) surplus = 10 +/datum/uplink_item/role_restricted/clowncar/spawn_item_for_generic_use(mob/user) + var/obj/vehicle/sealed/car/clowncar/car = ..() + car.enforce_clown_role = FALSE + var/obj/item/key = new car.key_type(user.loc) + car.visible_message(span_notice("[key] drops out of [car] onto the floor.")) + return car + /datum/uplink_item/role_restricted/his_grace name = "His Grace" desc = "An incredibly dangerous weapon recovered from a station overcome by the grey tide. Once activated, He will thirst for blood and must be used to kill to sate that thirst. \ @@ -298,6 +305,7 @@ cost = 20 surplus = 0 restricted_roles = list(JOB_CHAPLAIN) + purchasable_from = ~UPLINK_SPY /datum/uplink_item/role_restricted/concealed_weapon_bay name = "Concealed Weapon Bay" @@ -381,3 +389,4 @@ restricted_roles = list(JOB_MIME) restricted = TRUE refundable = FALSE + purchasable_from = parent_type::purchasable_from & ~UPLINK_SPY diff --git a/code/modules/uplink/uplink_items/nukeops.dm b/code/modules/uplink/uplink_items/nukeops.dm index d8bead5da6781..25c7ef9a2a2a8 100644 --- a/code/modules/uplink/uplink_items/nukeops.dm +++ b/code/modules/uplink/uplink_items/nukeops.dm @@ -68,7 +68,7 @@ /datum/uplink_item/weapon_kits/low_cost/shotgun name = "Bulldog Shotgun Case (Moderate)" - desc = "A fully-loaded semi-automatic drum-fed shotgun, complete with a secondary magazine you can hotswap. The gun has a handy label to explain how. \ + desc = "A fully-loaded 2-round burst fire drum-fed shotgun, complete with a secondary magazine you can hotswap. The gun has a handy label to explain how. \ Compatible with all 12g rounds. Designed for close quarter anti-personnel engagements. Comes with three spare magazines." item = /obj/item/storage/toolbox/guncase/bulldog @@ -76,26 +76,28 @@ name = "12g Buckshot Drum (Bulldog)" desc = "An additional 8-round buckshot magazine for use with the Bulldog shotgun. Front towards enemy." item = /obj/item/ammo_box/magazine/m12g + purchasable_from = parent_type::purchasable_from | UPLINK_SPY /datum/uplink_item/ammo_nuclear/basic/slug name = "12g Slug Drum (Bulldog)" desc = "An additional 8-round slug magazine for use with the Bulldog shotgun. \ Now 8 times less likely to shoot your pals." item = /obj/item/ammo_box/magazine/m12g/slug + purchasable_from = parent_type::purchasable_from | UPLINK_SPY /datum/uplink_item/ammo_nuclear/incendiary/dragon name = "12g Dragon's Breath Drum (Bulldog)" desc = "An alternative 8-round dragon's breath magazine for use in the Bulldog shotgun. \ 'I'm a fire starter, twisted fire starter!'" item = /obj/item/ammo_box/magazine/m12g/dragon - purchasable_from = UPLINK_NUKE_OPS + purchasable_from = parent_type::purchasable_from | UPLINK_SPY /datum/uplink_item/ammo_nuclear/special/meteor name = "12g Meteorslug Shells (Bulldog)" desc = "An alternative 8-round meteorslug magazine for use in the Bulldog shotgun. \ Great for blasting holes into the hull and knocking down enemies." item = /obj/item/ammo_box/magazine/m12g/meteor - purchasable_from = UPLINK_NUKE_OPS + purchasable_from = parent_type::purchasable_from | UPLINK_SPY // ~~ Ansem Pistol ~~ @@ -109,24 +111,28 @@ name = "10mm Handgun Magazine (Ansem)" desc = "An additional 8-round 10mm magazine, compatible with the Ansem pistol." item = /obj/item/ammo_box/magazine/m10mm + purchasable_from = parent_type::purchasable_from | UPLINK_SPY /datum/uplink_item/ammo_nuclear/ap/m10mm name = "10mm Armour Piercing Magazine (Ansem)" desc = "An additional 8-round 10mm magazine, compatible with the Ansem pistol. \ These rounds are less effective at injuring the target but penetrate protective gear." item = /obj/item/ammo_box/magazine/m10mm/ap + purchasable_from = parent_type::purchasable_from | UPLINK_SPY /datum/uplink_item/ammo_nuclear/hp/m10mm name = "10mm Hollow Point Magazine (Ansem)" desc = "An additional 8-round 10mm magazine, compatible with the Ansem pistol. \ These rounds are more damaging but ineffective against armour." item = /obj/item/ammo_box/magazine/m10mm/hp + purchasable_from = parent_type::purchasable_from | UPLINK_SPY /datum/uplink_item/ammo_nuclear/incendiary/m10mm name = "10mm Incendiary Magazine (Ansem)" desc = "An additional 8-round 10mm magazine, compatible with the Ansem pistol. \ Loaded with incendiary rounds which inflict less damage, but ignite the target." item = /obj/item/ammo_box/magazine/m10mm/fire + purchasable_from = parent_type::purchasable_from | UPLINK_SPY //Medium-cost: 14 TC each. Meant for more expensive purchases with a goal in mind. @@ -197,6 +203,7 @@ desc = "A speed loader that contains seven additional .357 Magnum rounds; usable with the Syndicate revolver. \ For when you really need a lot of things dead. Operatives get a discount from most of our agents!" item = /obj/item/ammo_box/a357 + purchasable_from = parent_type::purchasable_from | UPLINK_SPY /datum/uplink_item/ammo_nuclear/special/revolver/phasic name = ".357 Phasic Speed Loader (Revolver)" @@ -204,6 +211,7 @@ These bullets are made from an experimental alloy, 'Ghost Lead', that allows it to pass through almost any non-organic material. \ The name is a misnomer. It doesn't contain any lead whatsoever!" item = /obj/item/ammo_box/a357/phasic + purchasable_from = parent_type::purchasable_from | UPLINK_SPY /datum/uplink_item/ammo_nuclear/special/revolver/heartseeker name = ".357 Heartseeker Speed Loader (Revolver)" @@ -212,6 +220,7 @@ Brought to you by Roseus Galactic!" item = /obj/item/ammo_box/a357/heartseeker cost = 3 + purchasable_from = parent_type::purchasable_from | UPLINK_SPY // ~~ Grenade Launcher ~~ // 'If god had wanted you to live, he would not have created ME!' @@ -591,7 +600,7 @@ desc = "An upgraded, elite version of the Syndicate MODsuit. It features fireproofing, and also \ provides the user with superior armor and mobility compared to the standard Syndicate MODsuit." item = /obj/item/mod/control/pre_equipped/elite - purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS + purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY /datum/uplink_item/suits/energy_shield name = "MODsuit Energy Shield Module" @@ -599,28 +608,28 @@ before needing to recharge. Used wisely, this module will keep you alive for a lot longer." item = /obj/item/mod/module/energy_shield cost = 8 - purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS + purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY /datum/uplink_item/suits/emp_shield name = "MODsuit Advanced EMP Shield Module" desc = "An advanced EMP shield module for a MODsuit. It protects your entire body from electromagnetic pulses." item = /obj/item/mod/module/emp_shield/advanced cost = 5 - purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS + purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY /datum/uplink_item/suits/injector name = "MODsuit Injector Module" desc = "An injector module for a MODsuit. It is an extendable piercing injector with 30u capacity." item = /obj/item/mod/module/injector cost = 2 - purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS + purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY /datum/uplink_item/suits/holster name = "MODsuit Holster Module" desc = "A holster module for a MODsuit. It can stealthily store any not too heavy gun inside it." item = /obj/item/mod/module/holster cost = 2 - purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS + purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY /datum/uplink_item/device_tools/medgun_mod name = "Medbeam Gun Module" @@ -665,7 +674,7 @@ In its crowbar configuration, it can be used to force open airlocks. Very useful for entering the station or its departments." item = /obj/item/crowbar/power/syndicate cost = 4 - purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS + purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY /datum/uplink_item/device_tools/medkit name = "Syndicate Combat Medic Kit" @@ -692,7 +701,7 @@ desc = "A potion recovered at great risk by undercover Syndicate operatives and then subsequently modified with Syndicate technology. \ Using it will make any animal sentient, and bound to serve you, as well as implanting an internal radio for communication and an internal ID card for opening doors." cost = 4 - purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS + purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY restricted = TRUE // Implants @@ -717,6 +726,7 @@ This will permanently destroy your body, however." item = /obj/item/storage/box/syndie_kit/imp_microbomb cost = 2 + purchasable_from = UPLINK_NUKE_OPS | UPLINK_SPY /datum/uplink_item/implants/nuclear/macrobomb name = "Macrobomb Implant" @@ -732,8 +742,9 @@ Prevents collapsing from critical condition, but explodes after a while." item = /obj/item/storage/box/syndie_kit/imp_deniability cost = 6 + purchasable_from = UPLINK_NUKE_OPS | UPLINK_SPY -/datum/uplink_item/implants/nuclear/reviverplus +/datum/uplink_item/implants/nuclear/reviver name = "Reviver Implant" desc = "This implant will attempt to revive and heal you if you lose consciousness. Comes with an autosurgeon." item = /obj/item/autosurgeon/syndicate/reviver diff --git a/code/modules/uplink/uplink_items/species.dm b/code/modules/uplink/uplink_items/species.dm index 54ba353c00adb..5eb4bbdcb1776 100644 --- a/code/modules/uplink/uplink_items/species.dm +++ b/code/modules/uplink/uplink_items/species.dm @@ -4,7 +4,7 @@ /datum/uplink_item/species_restricted category = /datum/uplink_category/species - purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) + purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY) /datum/uplink_item/species_restricted/moth_lantern name = "Extra-Bright Lantern" diff --git a/code/modules/uplink/uplink_items/spy_unique.dm b/code/modules/uplink/uplink_items/spy_unique.dm new file mode 100644 index 0000000000000..b53cf60cefdeb --- /dev/null +++ b/code/modules/uplink/uplink_items/spy_unique.dm @@ -0,0 +1,138 @@ +/datum/uplink_category/spy_unique + name = "Spy Unique" + +// This is solely for uplink items that the spy can randomly obtain via bounties. +/datum/uplink_item/spy_unique + category = /datum/uplink_category/spy_unique + cant_discount = TRUE + surplus = FALSE + purchasable_from = UPLINK_SPY + // Cost doesn't really matter since it's free, but it determines which loot pool it falls into. + // By default, these fall into easy-medium spy bounty loot pool + cost = SPY_LOWER_COST_THRESHOLD + +/datum/uplink_item/spy_unique/syndie_bowman + name = "Syndicate Bowman" + desc = "A bowman headset for members of the Syndicate. Not very conspicuous." + item = /obj/item/radio/headset/syndicate/alt + cost = 1 + +/datum/uplink_item/spy_unique/megaphone + name = "Megaphone" + desc = "A megaphone. It's loud." + item = /obj/item/megaphone + cost = 1 + +/datum/uplink_item/spy_unique/combat_gloves + name = "Combat Gloves" + desc = "A pair of combat gloves. They're insulated!" + item = /obj/item/clothing/gloves/combat + cost = 1 + +/datum/uplink_item/spy_unique/krav_maga + name = "Combat Gloves Plus" + desc = "A pair of combat gloves plus. They're insulated AND you can do martial arts with it!" + item = /obj/item/clothing/gloves/krav_maga/combatglovesplus + +/datum/uplink_item/spy_unique/tackle_gloves + name = "Guerrilla Gloves" + desc = "A pair of Guerrilla gloves. They're insulated AND you can tackle people with it!" + item = /obj/item/clothing/gloves/tackler/combat/insulated + +/datum/uplink_item/spy_unique/kudzu + name = "Kudzu" + desc = "A packet of Kudzu - plant and forget, a great distraction." + item = /obj/item/seeds/kudzu + +/datum/uplink_item/spy_unique/big_knife + name = "Combat Knife" + desc = "A big knife. It's sharp." + item = /obj/item/knife/combat + +/datum/uplink_item/spy_unique/switchblade + name = "Switchblade" + desc = "A switchblade. Switches between not sharp and sharp." + item = /obj/item/switchblade + +/datum/uplink_item/spy_unique/sechud_implant + name = "SecHUD Implant" + desc = "A SecHUD implant. Shows you the ID of people you're looking at. It's also stealthy!" + item = /obj/item/autosurgeon/syndicate/contraband_sechud + +/datum/uplink_item/spy_unique/rifle_prime + name = "Bolt-Action Rifle" + desc = "A bolt-action rifle, with a scope. Won't jam, either." + item = /obj/item/gun/ballistic/rifle/boltaction/prime + cost = SPY_UPPER_COST_THRESHOLD + +/datum/uplink_item/spy_unique/cycler_shotgun + name = "Cycler Shotgun" + desc = "A cycler shotgun. It's a shotgun that cycles between two barrels." + item = /obj/item/gun/ballistic/shotgun/automatic/dual_tube/deadly + cost = SPY_UPPER_COST_THRESHOLD + +/datum/uplink_item/spy_unique/bulldog_shotgun + name = "Bulldog Shotgun" + desc = "A bulldog shotgun. It's a shotgun that shoots bulldogs." + item = /obj/item/gun/ballistic/shotgun/bulldog/unrestricted + cost = SPY_UPPER_COST_THRESHOLD + +/datum/uplink_item/spy_unique/ansem_pistol + name = "Ansem Pistol" + desc = "A pistol that's really good at making people sleep." + item = /obj/item/gun/ballistic/automatic/pistol/clandestine + cost = SPY_UPPER_COST_THRESHOLD + +/datum/uplink_item/spy_unique/rocket_launcher + name = "Rocket Launcher" + desc = "A rocket launcher. I would recommend against jumping with it." + item = /obj/item/gun/ballistic/rocketlauncher + cost = SPY_UPPER_COST_THRESHOLD - 1 // It's a meme item + +/datum/uplink_item/spy_unique/shotgun_ammo + name = "Box of Buckshot" + desc = "A box of buckshot rounds for a shotgun. For when you don't want to miss." + item = /obj/item/storage/box/lethalshot + cost = 1 + +/datum/uplink_item/spy_unique/shotgun_ammo/breacher_slug + name = "Box of Breacher Slugs" + desc = "A box of breacher slugs for a shotgun. For making a good first impression." + item = /obj/item/storage/box/breacherslug + +/datum/uplink_item/spy_unique/shotgun_ammo/slugs + name = "Box of Slugs" + desc = "A box of slugs for a shotgun. For big game hunting." + item = /obj/item/storage/box/slugs + +/datum/uplink_item/spy_unique/stealth_belt + name = "Stealth Belt" + desc = "A stealth belt that lets you sneak behind enemy lines." + item = /obj/item/shadowcloak/weaker + cost = SPY_UPPER_COST_THRESHOLD + +/datum/uplink_item/spy_unique/katana + name = "Katana" + desc = "A really sharp Katana. Did I mention it's sharp?" + item = /obj/item/katana + cost = /datum/uplink_item/dangerous/doublesword::cost // Puts it in the same pool as Desword + +/datum/uplink_item/spy_unique/medkit_lite + name = "Syndicate First Medic Kit" + desc = "A syndicate tactical combat medkit, but only stocked enough to do basic first aid." + item = /obj/item/storage/medkit/tactical_lite + +/datum/uplink_item/spy_unique/antistun + name = /datum/uplink_item/implants/nuclear/antistun::name + desc = /datum/uplink_item/implants/nuclear/antistun::desc + item = /obj/item/autosurgeon/syndicate/anti_stun/single_use + +/datum/uplink_item/spy_unique/reviver + name = /datum/uplink_item/implants/nuclear/reviver::name + desc = /datum/uplink_item/implants/nuclear/reviver::desc + item = /obj/item/autosurgeon/syndicate/reviver/single_use + +/datum/uplink_item/spy_unique/thermals + name = /datum/uplink_item/implants/nuclear/thermals::name + desc = /datum/uplink_item/implants/nuclear/thermals::desc + item = /obj/item/autosurgeon/syndicate/thermal_eyes/single_use diff --git a/code/modules/uplink/uplink_items/stealthy.dm b/code/modules/uplink/uplink_items/stealthy.dm index 793120fe56f34..fb450fb68df93 100644 --- a/code/modules/uplink/uplink_items/stealthy.dm +++ b/code/modules/uplink/uplink_items/stealthy.dm @@ -102,4 +102,4 @@ cost = 7 surplus = 50 limited_stock = 1 - purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_INFILTRATORS) + purchasable_from = UPLINK_TRAITORS | UPLINK_SPY diff --git a/code/modules/uplink/uplink_items/stealthy_tools.dm b/code/modules/uplink/uplink_items/stealthy_tools.dm index 60f007ebae772..59b8f6fca77e6 100644 --- a/code/modules/uplink/uplink_items/stealthy_tools.dm +++ b/code/modules/uplink/uplink_items/stealthy_tools.dm @@ -102,7 +102,7 @@ /datum/uplink_item/stealthy_tools/telecomm_blackout name = "Disable Telecomms" desc = "When purchased, a virus will be uploaded to the telecommunication processing servers to temporarily disable themselves." - item = /obj/effect/gibspawner/generic + item = ABSTRACT_UPLINK_ITEM surplus = 0 progression_minimum = 15 MINUTES limited_stock = 1 @@ -117,7 +117,7 @@ /datum/uplink_item/stealthy_tools/blackout name = "Trigger Stationwide Blackout" desc = "When purchased, a virus will be uploaded to the engineering processing servers to force a routine power grid check, forcing all APCs on the station to be temporarily disabled." - item = /obj/effect/gibspawner/generic + item = ABSTRACT_UPLINK_ITEM surplus = 0 progression_minimum = 20 MINUTES limited_stock = 1 diff --git a/code/modules/vehicles/cars/clowncar.dm b/code/modules/vehicles/cars/clowncar.dm index 37f6eb7efa501..30e01b3219edc 100644 --- a/code/modules/vehicles/cars/clowncar.dm +++ b/code/modules/vehicles/cars/clowncar.dm @@ -10,7 +10,7 @@ car_traits = CAN_KIDNAP key_type = /obj/item/bikehorn light_system = OVERLAY_LIGHT_DIRECTIONAL - light_range = 8 + light_range = 6 light_power = 2 light_on = FALSE access_provider_flags = VEHICLE_CONTROL_DRIVE|VEHICLE_CONTROL_KIDNAPPED diff --git a/code/modules/vehicles/cars/vim.dm b/code/modules/vehicles/cars/vim.dm index 53eee5105a97e..221c9268febbb 100644 --- a/code/modules/vehicles/cars/vim.dm +++ b/code/modules/vehicles/cars/vim.dm @@ -15,7 +15,7 @@ engine_sound_length = 0.3 SECONDS light_system = OVERLAY_LIGHT_DIRECTIONAL light_range = 4 - light_power = 2 + light_power = 1.5 light_on = FALSE engine_sound = 'sound/effects/servostep.ogg' ///Maximum size of a mob trying to enter the mech diff --git a/code/modules/vehicles/mecha/_mecha.dm b/code/modules/vehicles/mecha/_mecha.dm index e03847037c8db..85d408f5348c9 100644 --- a/code/modules/vehicles/mecha/_mecha.dm +++ b/code/modules/vehicles/mecha/_mecha.dm @@ -31,7 +31,7 @@ COOLDOWN_DECLARE(mecha_bump_smash) light_system = OVERLAY_LIGHT_DIRECTIONAL light_on = FALSE - light_range = 8 + light_range = 6 generic_canpass = FALSE hud_possible = list(DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_TRACK_HUD, DIAG_CAMERA_HUD) mouse_pointer = 'icons/effects/mouse_pointers/mecha_mouse.dmi' diff --git a/code/modules/vehicles/mecha/combat/durand.dm b/code/modules/vehicles/mecha/combat/durand.dm index 35b53c30ccfc8..f7da6ea90c8a2 100644 --- a/code/modules/vehicles/mecha/combat/durand.dm +++ b/code/modules/vehicles/mecha/combat/durand.dm @@ -166,7 +166,7 @@ own integrity back to max. Shield is automatically dropped if we run out of powe anchored = TRUE light_system = OVERLAY_LIGHT light_range = MINIMUM_USEFUL_LIGHT_RANGE - light_power = 5 + light_power = 2 light_color = LIGHT_COLOR_ELECTRIC_CYAN light_on = FALSE resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF //The shield should not take damage from fire, lava, or acid; that's the mech's job. diff --git a/code/modules/vehicles/mecha/mecha_control_console.dm b/code/modules/vehicles/mecha/mecha_control_console.dm index 635ec9425b1c8..3b465994e9e02 100644 --- a/code/modules/vehicles/mecha/mecha_control_console.dm +++ b/code/modules/vehicles/mecha/mecha_control_console.dm @@ -106,7 +106,7 @@ return answer -/obj/item/mecha_parts/mecha_tracking/emp_act() +/obj/item/mecha_parts/mecha_tracking/emp_act(severity) . = ..() if(!(. & EMP_PROTECT_SELF)) qdel(src) diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index f101c5b2f7486..c854b54e06277 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -14,8 +14,12 @@ premium = list() */ +/// List of vending machines that players can restock, so only vending machines that are on station or don't have a unique condition. +GLOBAL_LIST_EMPTY(vending_machines_to_restock) + /// Maximum amount of items in a storage bag that we're transferring items to the vendor from. #define MAX_VENDING_INPUT_AMOUNT 30 +#define CREDITS_DUMP_THRESHOLD 50 /** * # vending record datum * @@ -178,6 +182,8 @@ var/displayed_currency_name = " cr" ///Whether our age check is currently functional var/age_restrictions = TRUE + /// How many credits does this vending machine have? 20% of all sales go to this pool, and are given freely when the machine is restocked, or successfully tilted. Lost on deconstruction. + var/credits_contained = 0 /** * Is this item on station or not * @@ -256,6 +262,7 @@ onstation = FALSE if(circuit) circuit.onstation = onstation //sync up the circuit so the pricing schema is carried over if it's reconstructed. + else if(HAS_TRAIT(SSstation, STATION_TRAIT_VENDING_SHORTAGE)) for (var/datum/data/vending_product/product_record as anything in product_records + coin_records + hidden_records) /** @@ -264,20 +271,26 @@ */ var/max_amount = rand(CEILING(product_record.amount * 0.5, 1), product_record.amount) product_record.amount = rand(0, max_amount) + credits_contained += rand(0, 1) //randomly add a few credits to the machine to make it look like it's been used, proportional to the amount missing. if(tiltable && prob(6)) // 1 in 17 chance to start tilted (as an additional hint to the station trait behind it) INVOKE_ASYNC(src, PROC_REF(tilt), loc) + credits_contained = 0 // If it's tilted, it's been looted, so no credits for you. else if(circuit && (circuit.onstation != onstation)) //check if they're not the same to minimize the amount of edited values. onstation = circuit.onstation //if it was constructed outside mapload, sync the vendor up with the circuit's var so you can't bypass price requirements by moving / reconstructing it off station. + if(onstation && !onstation_override) + AddComponent(/datum/component/payment, 0, SSeconomy.get_dep_account(payment_department), PAYMENT_VENDING) + GLOB.vending_machines_to_restock += src //We need to keep track of the final onstation vending machines so we can keep them restocked. /obj/machinery/vending/Destroy() QDEL_NULL(wires) QDEL_NULL(coin) QDEL_NULL(bill) QDEL_NULL(sec_radio) + GLOB.vending_machines_to_restock -= src return ..() -/obj/machinery/vending/can_speak() - return !shut_up +/obj/machinery/vending/can_speak(allow_mimes) + return is_operational && !shut_up && ..() /obj/machinery/vending/emp_act(severity) . = ..() @@ -357,6 +370,7 @@ continue var/obj/obj_to_dump = new dump_path(loc) + on_dispense(obj_to_dump) step(obj_to_dump, pick(GLOB.alldirs)) found_anything = TRUE dump_amount++ @@ -611,6 +625,24 @@ else //no category found - dump it into standard stock products[record.product_path] = record.amount +/** + * Returns the total amount of items in the vending machine based on the product records and premium records, but not contraband + */ +/obj/machinery/vending/proc/total_loaded_stock() + var/total = 0 + for(var/datum/data/vending_product/record as anything in product_records + coin_records) + total += record.amount + return total + +/** + * Returns the total amount of items in the vending machine based on the product records and premium records, but not contraband + */ +/obj/machinery/vending/proc/total_max_stock() + var/total_max = 0 + for(var/datum/data/vending_product/record as anything in product_records + coin_records) + total_max += record.max_amount + return total_max + /obj/machinery/vending/crowbar_act(mob/living/user, obj/item/attack_item) if(!component_parts) return FALSE @@ -655,7 +687,11 @@ // instantiate canister if needed var/transferred = restock(canister) if(transferred) - to_chat(user, span_notice("You loaded [transferred] items in [src].")) + to_chat(user, span_notice("You loaded [transferred] items in [src][credits_contained > 0 ? ", and are rewarded [credits_contained] credits." : "."]")) + var/datum/bank_account/cargo_account = SSeconomy.get_dep_account(ACCOUNT_CAR) + cargo_account.adjust_money(round(credits_contained * 0.5), "Vending: Restock") + var/obj/item/holochip/payday = new(src, credits_contained) + try_put_in_hand(payday, user) else to_chat(user, span_warning("There's nothing to restock!")) return @@ -707,7 +743,7 @@ * freebies - number of free items to vend */ /obj/machinery/vending/proc/freebie(freebies) - visible_message(span_notice("[src] yields [freebies > 1 ? "several free goodies" : "a free goody"]!")) + visible_message(span_notice("[src] yields [freebies > 1 ? "several free goodies" : "a free goody"][credits_contained > 0 ? " and some credits" : ""]!")) for(var/i in 1 to freebies) playsound(src, 'sound/machines/machine_vend.ogg', 50, TRUE, extrarange = -3) @@ -719,13 +755,15 @@ if(!dump_path) continue if(record.amount > LAZYLEN(record.returned_products)) //always give out new stuff that costs before free returned stuff, because of the risk getting gibbed involved - new dump_path(get_turf(src)) + var/obj/item/free_stuff = new dump_path(get_turf(src)) + on_dispense(free_stuff) else var/obj/returned_obj_to_dump = LAZYACCESS(record.returned_products, LAZYLEN(record.returned_products)) //first in, last out LAZYREMOVE(record.returned_products, returned_obj_to_dump) returned_obj_to_dump.forceMove(get_turf(src)) record.amount-- break + deploy_credits() /** * Tilts ontop of the atom supplied, if crit is true some extra shit can happen. See [fall_and_crush] for return values. @@ -1115,7 +1153,7 @@ return TRUE /obj/machinery/vending/interact(mob/user) - if (!isAI(user)) + if (!HAS_AI_ACCESS(user)) if(seconds_electrified && !(machine_stat & NOPOWER)) if(shock(user, 100)) return @@ -1206,13 +1244,15 @@ /obj/machinery/vending/ui_data(mob/user) . = list() var/obj/item/card/id/card_used + var/held_cash = 0 if(isliving(user)) var/mob/living/living_user = user card_used = living_user.get_idcard(TRUE) + held_cash = living_user.tally_physical_credits() if(card_used?.registered_account) .["user"] = list() .["user"]["name"] = card_used.registered_account.account_holder - .["user"]["cash"] = fetch_balance_to_use(card_used) + .["user"]["cash"] = fetch_balance_to_use(card_used) + held_cash if(card_used.registered_account.account_job) .["user"]["job"] = card_used.registered_account.account_job.title .["user"]["department"] = card_used.registered_account.account_job.paycheck_department @@ -1333,25 +1373,12 @@ vend_ready = TRUE return if(onstation) + // Here we do additional handing ahead of the payment component's logic, such as age restrictions and additional logging var/obj/item/card/id/card_used + var/mob/living/living_user if(isliving(usr)) - var/mob/living/living_user = usr + living_user = usr card_used = living_user.get_idcard(TRUE) - if(!card_used) - speak("No card found.") - flick(icon_deny,src) - vend_ready = TRUE - return - else if (!card_used.registered_account) - speak("No account found.") - flick(icon_deny,src) - vend_ready = TRUE - return - else if(!card_used.registered_account.account_job) - speak("Departmental accounts have been blacklisted from personal expenses due to embezzlement.") - flick(icon_deny, src) - vend_ready = TRUE - return else if(age_restrictions && item_record.age_restricted && (!card_used.registered_age || card_used.registered_age < AGE_MINOR)) speak("You are not of legal age to purchase [item_record.name].") if(!(usr in GLOB.narcd_underages)) @@ -1365,7 +1392,7 @@ vend_ready = TRUE return - if(!proceed_payment(card_used, item_record, price_to_use)) + if(!proceed_payment(card_used, living_user, item_record, price_to_use)) return if(last_shopper != REF(usr) || purchase_message_cooldown < world.time) @@ -1381,6 +1408,7 @@ var/obj/item/vended_item if(!LAZYLEN(item_record.returned_products)) //always give out free returned stuff first, e.g. to avoid walling a traitor objective in a bag behind paid items vended_item = new item_record.product_path(get_turf(src)) + on_dispense(vended_item) else vended_item = LAZYACCESS(item_record.returned_products, LAZYLEN(item_record.returned_products)) //first in, last out LAZYREMOVE(item_record.returned_products, vended_item) @@ -1395,6 +1423,10 @@ SSblackbox.record_feedback("nested tally", "vending_machine_usage", 1, list("[type]", "[item_record.product_path]")) vend_ready = TRUE +///A proc meant to perform custom behavior on newly dispensed items. +/obj/machinery/vending/proc/on_dispense(obj/item/vended_item) + return + /** * Returns the balance that the vendor will use for proceeding payment. Most vendors would want to use the user's * card's account credits balance. @@ -1407,11 +1439,12 @@ /** * Handles payment processing: discounts, logging, balance change etc. * arguments: - * paying_id_card - the id card that will be billed for the product - * product_to_vend - the product record of the item we're trying to vend - * price_to_use - price of the item we're trying to vend + * paying_id_card - the id card that will be billed for the product. + * mob_paying - the mob that is trying to purchase the item. + * product_to_vend - the product record of the item we're trying to vend. + * price_to_use - price of the item we're trying to vend. */ -/obj/machinery/vending/proc/proceed_payment(obj/item/card/id/paying_id_card, datum/data/vending_product/product_to_vend, price_to_use) +/obj/machinery/vending/proc/proceed_payment(obj/item/card/id/paying_id_card, mob/living/mob_paying, datum/data/vending_product/product_to_vend, price_to_use) var/datum/bank_account/account = paying_id_card.registered_account if(account.account_job && account.account_job.paycheck_department == payment_department) price_to_use = max(round(price_to_use * DEPARTMENT_DISCOUNT), 1) //No longer free, but signifigantly cheaper. @@ -1419,7 +1452,7 @@ price_to_use = product_to_vend.custom_premium_price ? product_to_vend.custom_premium_price : extra_price if(LAZYLEN(product_to_vend.returned_products)) price_to_use = 0 //returned items are free - if(price_to_use && !account.adjust_money(-price_to_use, "Vending: [product_to_vend.name]")) + if(price_to_use && (attempt_charge(src, mob_paying, price_to_use) & COMPONENT_OBJ_CANCEL_CHARGE)) speak("You do not possess the funds to purchase [product_to_vend.name].") flick(icon_deny,src) vend_ready = TRUE @@ -1427,10 +1460,10 @@ //actual payment here var/datum/bank_account/paying_id_account = SSeconomy.get_dep_account(payment_department) if(paying_id_account) - paying_id_account.adjust_money(price_to_use) SSblackbox.record_feedback("amount", "vending_spent", price_to_use) SSeconomy.track_purchase(account, price_to_use, name) log_econ("[price_to_use] credits were inserted into [src] by [account.account_holder] to buy [product_to_vend].") + credits_contained += round(price_to_use * 0.2) return TRUE /obj/machinery/vending/process(seconds_per_tick) @@ -1569,6 +1602,15 @@ tilt(fatty=hit_atom) return ..() +/** Drop credits when the vendor is attacked.*/ +/obj/machinery/vending/proc/deploy_credits() + if(credits_contained <= 0) + return + var/credits_to_remove = min(CREDITS_DUMP_THRESHOLD, round(credits_contained)) + var/obj/item/holochip/holochip = new(loc, credits_to_remove) + credits_contained = max(0, credits_contained - credits_to_remove) + SSblackbox.record_feedback("amount", "vending machine looted", holochip.credits) + /obj/machinery/vending/custom name = "Custom Vendor" icon_state = "custom" diff --git a/code/modules/vending/assist.dm b/code/modules/vending/assist.dm index 229b19aeadb99..a043a365046e2 100644 --- a/code/modules/vending/assist.dm +++ b/code/modules/vending/assist.dm @@ -21,6 +21,7 @@ /obj/item/assembly/timer = 2, /obj/item/assembly/voice = 2, /obj/item/stock_parts/cell/high = 1, + /obj/item/market_uplink/blackmarket = 1, ) premium = list( /obj/item/assembly/igniter/condenser = 2, diff --git a/code/modules/vending/autodrobe.dm b/code/modules/vending/autodrobe.dm index 59bd06135bb83..2877a40334770 100644 --- a/code/modules/vending/autodrobe.dm +++ b/code/modules/vending/autodrobe.dm @@ -207,6 +207,7 @@ /obj/item/clothing/mask/muzzle = 2, /obj/item/clothing/shoes/ducky_shoes = 1, /obj/item/clothing/shoes/clown_shoes/meown_shoes = 1, + /obj/item/clothing/shoes/clown_shoes/moffers = 1, /obj/item/clothing/suit/costume/judgerobe = 1, /obj/item/clothing/head/costume/lobsterhat = 1, /obj/item/clothing/under/costume/lobster = 1, diff --git a/code/modules/vending/hotdog.dm b/code/modules/vending/hotdog.dm new file mode 100644 index 0000000000000..094beccb41331 --- /dev/null +++ b/code/modules/vending/hotdog.dm @@ -0,0 +1,56 @@ +///A special hotdog vending machine found in the cafeteria at the museum away mission, or during the hotdog holiday. +/obj/machinery/vending/hotdog + name = "\improper Hotdoggo-Vend" + desc = "An outdated hotdog vending machine, its prices stuck to those of 20 or so years ago." + icon_state = "hotdog-vendor" + icon_deny = "hotdog-vendor-deny" + panel_type = "panel17" + product_slogans = "Meatier than ever!;Now with 20% more MSG!;HOTDOGS!;Now Tirizan-friendly!" + product_ads = "Your best and only automatic hotdog dispenser!;Serving you the finest buns since 2469!;Comes in 12 different flavors!" + vend_reply = "Have a scrumptious meal!" + light_mask = "hotdog-vendor-light-mask" + default_price = PAYCHECK_LOWER + product_categories = list( + list( + "name" = "Hotdogs", + "icon" = "hotdog", + "products" = list( + /obj/item/food/hotdog = 8, + /obj/item/food/pigblanket = 4, + /obj/item/food/danish_hotdog = 4, + /obj/item/food/little_hawaii_hotdog = 4, + /obj/item/food/butterdog = 4, + /obj/item/food/plasma_dog_supreme = 2, + ), + ), + list( + name = "Sausages", + "icon" = FA_ICON_BACON, + "products" = list( + /obj/item/food/sausage = 8, + /obj/item/food/tiziran_sausage = 4, + /obj/item/food/fried_blood_sausage = 4, + ), + ), + list( + "name" = "Sauces", + "icon" = FA_ICON_BOWL_FOOD, + "products" = list( + /obj/item/reagent_containers/condiment/pack/ketchup = 4, + /obj/item/reagent_containers/condiment/pack/hotsauce = 4, + /obj/item/reagent_containers/condiment/pack/bbqsauce = 4, + /obj/item/reagent_containers/condiment/pack/soysauce = 4, + /obj/item/reagent_containers/condiment/pack/mayonnaise = 4, + ), + ), + ) + refill_canister = /obj/item/vending_refill/hotdog + +/obj/item/vending_refill/hotdog + machine_name = "\improper Hotdoggo-Vend" + icon_state = "refill_snack" + +/// Cute little thing that sets it apart from the other food vending mahicnes. I mean, you don't find this every day. +/obj/machinery/vending/hotdog/on_dispense(obj/item/vended_item) + if(istype(vended_item, /obj/item/food)) + ADD_TRAIT(vended_item, TRAIT_FOOD_CHEF_MADE, VENDING_MACHINE_TRAIT) diff --git a/code/modules/vending/sustenance.dm b/code/modules/vending/sustenance.dm index d822912149087..a1d11c307277e 100644 --- a/code/modules/vending/sustenance.dm +++ b/code/modules/vending/sustenance.dm @@ -48,7 +48,7 @@ return return ..() -/obj/machinery/vending/sustenance/labor_camp/proceed_payment(obj/item/card/id/paying_id_card, datum/data/vending_product/product_to_vend, price_to_use) +/obj/machinery/vending/sustenance/labor_camp/proceed_payment(obj/item/card/id/paying_id_card, mob/living/mob_paying, datum/data/vending_product/product_to_vend, price_to_use) if(!istype(paying_id_card, /obj/item/card/id/advanced/prisoner)) speak("I don't take bribes! Pay with labor points!") return FALSE diff --git a/code/modules/wiremod/shell/brain_computer_interface.dm b/code/modules/wiremod/shell/brain_computer_interface.dm index 57bb2ed45cbb3..308aea8742032 100644 --- a/code/modules/wiremod/shell/brain_computer_interface.dm +++ b/code/modules/wiremod/shell/brain_computer_interface.dm @@ -20,16 +20,29 @@ new /obj/item/circuit_component/bci_core, ), SHELL_CAPACITY_SMALL, starting_circuit = circuit) -/obj/item/organ/internal/cyberimp/bci/say(message, bubble_type, list/spans, sanitize, datum/language/language, ignore_spam, forced = null, filterproof = null, message_range = 7, datum/saymode/saymode = null) +/obj/item/organ/internal/cyberimp/bci/say( + message, + bubble_type, + list/spans = list(), + sanitize = TRUE, + datum/language/language, + ignore_spam = FALSE, + forced, + filterproof = FALSE, + message_range = 7, + datum/saymode/saymode, + list/message_mods = list(), +) if (owner) // Otherwise say_dead will be called. // It's intentional that a circuit for a dead person does not speak from the shell. if (owner.stat == DEAD) return - owner.say(message, forced = "circuit speech") - else - return ..() + forced = "circuit speech" + return owner.say(arglist(args)) + + return ..() /obj/item/organ/internal/cyberimp/bci/proc/action_comp_registered(datum/source, obj/item/circuit_component/equipment_action/action_comp) SIGNAL_HANDLER @@ -184,7 +197,7 @@ parent.cell.give(amount) -/obj/item/circuit_component/bci_core/proc/on_electrocute(datum/source, shock_damage, siemens_coefficient, flags) +/obj/item/circuit_component/bci_core/proc/on_electrocute(datum/source, shock_damage, shock_source, siemens_coefficient, flags) SIGNAL_HANDLER if (isnull(parent.cell)) diff --git a/config/arenas/README.md b/config/arenas/README.md deleted file mode 100644 index 9f31ce2349a93..0000000000000 --- a/config/arenas/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Add admin arena dmms here. - -**These are fully cached so keep this directory empty by default.** \ No newline at end of file diff --git a/config/lavaruinblacklist.txt b/config/lavaruinblacklist.txt index d40c4386d31de..fd0c0d85c64af 100644 --- a/config/lavaruinblacklist.txt +++ b/config/lavaruinblacklist.txt @@ -28,6 +28,7 @@ #_maps/RandomRuins/LavaRuins/lavaland_surface_cultaltar.dmm #_maps/RandomRuins/LavaRuins/lavaland_surface_elephant_graveyard.dmm #_maps/RandomRuins/LavaRuins/lavaland_surface_gaia.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_gas.dmm #_maps/RandomRuins/LavaRuins/lavaland_surface_hermit.dmm #_maps/RandomRuins/LavaRuins/lavaland_surface_hierophant.dmm #_maps/RandomRuins/LavaRuins/lavaland_surface_library.dmm diff --git a/html/changelogs/AutoChangeLog-pr-81502.yml b/html/changelogs/AutoChangeLog-pr-81502.yml deleted file mode 100644 index 67763b4fb43e8..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81502.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "IndieanaJones" -delete-after: True -changes: - - rscadd: "New Changeling Ability: Hive Head" - - bugfix: "Fixed bees having an improper sprite offset" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81534.yml b/html/changelogs/AutoChangeLog-pr-81534.yml deleted file mode 100644 index a100278dc5472..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81534.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Iamgoofball" -delete-after: True -changes: - - balance: "xenomorph stomachs will no longer destroy items directly, refactored it to use acid_act()" - - bugfix: "fixes xenomorph vore accidentally destroying mobs it wasn't supposed to destroy, im thinking this was modified list in place shenanigans" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81555.yml b/html/changelogs/AutoChangeLog-pr-81555.yml deleted file mode 100644 index 3e42a3e26b6be..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81555.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Ghommie" -delete-after: True -changes: - - balance: "\"Freshness Jars full of Natural Bait\" is now a goodie and costs 200 credits instead of 2000" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81562.yml b/html/changelogs/AutoChangeLog-pr-81562.yml deleted file mode 100644 index e6e40113f7cc9..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81562.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Ghommie" -delete-after: True -changes: - - rscadd: "Added a multi-dimensional bomb payload to the black market. It's very expensive." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81607.yml b/html/changelogs/AutoChangeLog-pr-81607.yml deleted file mode 100644 index 0acb3c6a1bbf7..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81607.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "JohnFulpWillard" -delete-after: True -changes: - - qol: "Intelligent monkeys now punch people instead of biting them." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81612.yml b/html/changelogs/AutoChangeLog-pr-81612.yml deleted file mode 100644 index 89d09d1d7bc33..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81612.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "PapaMichael" -delete-after: True -changes: - - balance: "Fugitive hunters will spawn early if the emergency shuttle is called." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81617.yml b/html/changelogs/AutoChangeLog-pr-81617.yml deleted file mode 100644 index fb9957e2adbe0..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81617.yml +++ /dev/null @@ -1,7 +0,0 @@ -author: "LT3" -delete-after: True -changes: - - code_imp: "Tram throwing now breaks grilles consistently" - - code_imp: "Tram malfunction lethality/throw chance are now a multiplier instead of flat value" - - code_imp: "Tram throw chance can be adjusted" - - code_imp: "Unlucky trait is now used in tram throw calculation" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81624.yml b/html/changelogs/AutoChangeLog-pr-81624.yml deleted file mode 100644 index ab0c85d5fd716..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81624.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "softcerv" -delete-after: True -changes: - - code_imp: "TRAIT_DEAF now works on non-carbon mobs" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81636.yml b/html/changelogs/AutoChangeLog-pr-81636.yml deleted file mode 100644 index 5d4f0d86e8081..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81636.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "00-Steven" -delete-after: True -changes: - - bugfix: "Lobby manifest shows the head/captain symbols next to heads and captains with custom titles, as long as they're registered with a head/captain trim." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81642.yml b/html/changelogs/AutoChangeLog-pr-81642.yml deleted file mode 100644 index 61bb755accacd..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81642.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Cheshify" -delete-after: True -changes: - - qol: "The chapel has been slightly overhauled on Birdshot, with the chaplain now having a place to preach sermons." - - bugfix: "Sparring chaplains are now able to operate on Birdshot!" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81643.yml b/html/changelogs/AutoChangeLog-pr-81643.yml deleted file mode 100644 index 0e4561dd8ab89..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81643.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Cheshify" -delete-after: True -changes: - - bugfix: "the north star's main intersections are brighter, the elevator is properly lit, and a single floating poster was moved." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81653.yml b/html/changelogs/AutoChangeLog-pr-81653.yml deleted file mode 100644 index 60612d931efd2..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81653.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "KingkumaArt" -delete-after: True -changes: - - rscadd: "a list of items called vendor_nocrush that vendors dont deal integrity damage to upon hitting them." - - bugfix: "Makes vending machines no longer crush chairs and conveyors." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81667.yml b/html/changelogs/AutoChangeLog-pr-81667.yml deleted file mode 100644 index 3f82bd14d8b74..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81667.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DrDiasyl" -delete-after: True -changes: - - rscadd: "NEW TRAIT JOB: Veteran Security Advisor! Advise HoS and Captain on Security matters, mentor Security Officers. Note that they are paraplegic and are suffering PTSD due to their past experience." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81670.yml b/html/changelogs/AutoChangeLog-pr-81670.yml deleted file mode 100644 index 3a7392ed6116d..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81670.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "00-Steven" -delete-after: True -changes: - - bugfix: "Fixed Bluespace RPEDs failing to apply circuits from a distance if you had to select between multiple." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81686.yml b/html/changelogs/AutoChangeLog-pr-81686.yml deleted file mode 100644 index 7d4795275b685..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81686.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Jacquerel" -delete-after: True -changes: - - rscadd: "Displaying the voting statistics is now optional on a per-poll basis, and is disabled for map voting." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81688.yml b/html/changelogs/AutoChangeLog-pr-81688.yml deleted file mode 100644 index cced53083f08a..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81688.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "ArcaneMusic" -delete-after: True -changes: - - image: "New sprites for plant grafts!" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81815.yml b/html/changelogs/AutoChangeLog-pr-81815.yml new file mode 100644 index 0000000000000..4664f0eac4f7b --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-81815.yml @@ -0,0 +1,4 @@ +author: "EEASAS" +delete-after: True +changes: + - rscadd: "New Lizard's Gas ruin, this time in lavaland" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81881.yml b/html/changelogs/AutoChangeLog-pr-81881.yml new file mode 100644 index 0000000000000..f985d0920d9d8 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-81881.yml @@ -0,0 +1,4 @@ +author: "vinylspiders" +delete-after: True +changes: + - bugfix: "fixes a spurious CI runtime caused by explosive mines quite literally blowing up the unit test area" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81883.yml b/html/changelogs/AutoChangeLog-pr-81883.yml new file mode 100644 index 0000000000000..c0db0d05a8bb4 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-81883.yml @@ -0,0 +1,4 @@ +author: "Melbert" +delete-after: True +changes: + - bugfix: "Silicons can use asterisks in binary without fear of saying something interesting." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81936.yml b/html/changelogs/AutoChangeLog-pr-81936.yml new file mode 100644 index 0000000000000..2a1da861ad4a9 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-81936.yml @@ -0,0 +1,4 @@ +author: "Melbert" +delete-after: True +changes: + - bugfix: "Some things which should pacify people (but aren't) now will properly, like hypnoflash" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81942.yml b/html/changelogs/AutoChangeLog-pr-81942.yml new file mode 100644 index 0000000000000..f35d8802f4bbf --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-81942.yml @@ -0,0 +1,4 @@ +author: "ArcaneMusic" +delete-after: True +changes: + - bugfix: "The black market illegal fish case now correctly initializes." \ No newline at end of file diff --git a/html/changelogs/archive/2024-02.yml b/html/changelogs/archive/2024-02.yml index 1d21410dcdf9a..e98a9540d4a2b 100644 --- a/html/changelogs/archive/2024-02.yml +++ b/html/changelogs/archive/2024-02.yml @@ -790,3 +790,79 @@ - bugfix: fixes cardboard cutouts not updating when held and using the crayon on them to change their appearance, and fixes the alt_appearance being added to the cutout instead of the mob holding it +2024-02-28: + 00-Steven: + - bugfix: Lobby manifest shows the head/captain symbols next to heads and captains + with custom titles, as long as they're registered with a head/captain trim. + - bugfix: Fixed Bluespace RPEDs failing to apply circuits from a distance if you + had to select between multiple. + ArcaneMusic: + - image: New sprites for plant grafts! + Cheshify: + - bugfix: the north star's main intersections are brighter, the elevator is properly + lit, and a single floating poster was moved. + - qol: The chapel has been slightly overhauled on Birdshot, with the chaplain now + having a place to preach sermons. + - bugfix: Sparring chaplains are now able to operate on Birdshot! + DrDiasyl: + - rscadd: 'NEW TRAIT JOB: Veteran Security Advisor! Advise HoS and Captain on Security + matters, mentor Security Officers. Note that they are paraplegic and are suffering + PTSD due to their past experience.' + Ghommie: + - rscadd: Added a multi-dimensional bomb payload to the black market. It's very + expensive. + - balance: '"Freshness Jars full of Natural Bait" is now a goodie and costs 200 + credits instead of 2000' + Iamgoofball: + - balance: xenomorph stomachs will no longer destroy items directly, refactored + it to use acid_act() + - bugfix: fixes xenomorph vore accidentally destroying mobs it wasn't supposed to + destroy, im thinking this was modified list in place shenanigans + IndieanaJones: + - rscadd: 'New Changeling Ability: Hive Head' + - bugfix: Fixed bees having an improper sprite offset + Jacquerel: + - rscadd: Displaying the voting statistics is now optional on a per-poll basis, + and is disabled for map voting. + JohnFulpWillard: + - qol: Intelligent monkeys now punch people instead of biting them. + KingkumaArt: + - rscadd: a list of items called vendor_nocrush that vendors dont deal integrity + damage to upon hitting them. + - bugfix: Makes vending machines no longer crush chairs and conveyors. + LT3: + - code_imp: Tram throwing now breaks grilles consistently + - code_imp: Tram malfunction lethality/throw chance are now a multiplier instead + of flat value + - code_imp: Tram throw chance can be adjusted + - code_imp: Unlucky trait is now used in tram throw calculation + Melbert: + - bugfix: Rat hearts apply their damage modifier malus correctly + PapaMichael: + - balance: Fugitive hunters will spawn early if the emergency shuttle is called. + softcerv: + - code_imp: TRAIT_DEAF now works on non-carbon mobs +2024-02-29: + 13spacemen: + - bugfix: You can build material airlocks again + A.C.M.O.: + - bugfix: Fixed the AI hologram's ability to copy the appearance of crew members. + Jacquerel: + - image: adds a visual effect for hive head bees despawning + Momo8289: + - bugfix: Fixes the slot machine's jackpot. It should now give all of the available + prize money + 10,000 credits as payout for a jackpot. + - refactor: Converts the slot machine's UI over to TGUI + - rscadd: The slot machine now has a whole new type of jackpot! This one's a banger! + SyncIt21: + - bugfix: Cryostylane reaction now has a moderate & not extreme cooling effect. + Helps you achieve more pure amounts of Cryostylane + TheVekter: + - rscadd: Added a new law to the Artist lawset in order to encourage Artist AIs + to build an audience. + mc-oofert: + - bugfix: Grilles dont break by just walking into them under any circumstances + necromanceranne: + - bugfix: Being in a Swat Suit appropriately protects you from collisions with a + body, rather than the body thrown at you having these protections protecting + YOU, the victim of the collision. diff --git a/html/changelogs/archive/2024-03.yml b/html/changelogs/archive/2024-03.yml new file mode 100644 index 0000000000000..525e488b3af9a --- /dev/null +++ b/html/changelogs/archive/2024-03.yml @@ -0,0 +1,355 @@ +2024-03-01: + Ben10Omintrix: + - rscadd: new virtual pet app on the pda + Cheshify: + - spellcheck: changed the fitness skill title to powerlifter + Echriser: + - qol: Allows dragging from boxes into All-In-One Grinders + Ghommie: + - bugfix: Fixed (cross)bows' strings not loosening once fired. + - bugfix: the multi-dimensional bomb payload now works as intended and doesn't break + once you select a theme. + - bugfix: Meat and other bloody things will not spread blood forever. + Higgin: + - balance: the shadow eyes of nightmares and shadowpeople more broadly are now sensitive + to light, requiring additional protection. + Melbert: + - bugfix: Fixes grabbing yourself when you tackle someone. + Momo8289: + - qol: HUD implants will now notify you when toggled on or off + Pickle-Coding: + - admin: Logs holosign swatting. + TheSmallBlue: + - qol: added an HUD button to go up and down floors + bigfatbananacyclops: + - rscadd: box with a set of floortile camo, which can be ordered in black market + uplink + - rscadd: also adds a backpack to camouflage + - bugfix: i had the crate under emagged console, should be fixed now. + mc-oofert: + - rscadd: Pipebombs + - rscdel: Improvised Firebombs + necromanceranne: + - bugfix: The M-90GL now correctly states that it accepts .223 toploader magazines. + vinylspiders: + - bugfix: fixes an issue where being gibbed while under the HARS mutation can sometimes + lead to the brain being deleted when it's not supposed to be +2024-03-02: + 13spacemen: + - refactor: Butt sprites are based on the chest bodypart for humans, instead of + the species + - image: Moths have their own special butt sprites + DrDiasyl: + - rscadd: Adds 2 new shields to the game! Ballistic Shield - researched by Science, + and Improvised Shield - made out of iron and sticky tape + - image: Riot, Strobe, Telescopic, Energy shields got new less flat sprites! + Ghommie: + - rscadd: Fishes love kronkaine. + - qol: Examining a fishing spot twice with sufficiently high fishing skill (or the + skillchip) will get you a list of fishes that can be caught. + Jacquerel: + - image: Bubblegum Hallucination Surround Charge, Wendigo Shockwave Scream, and + Ice Demon Floor Freeze all have more appropriate action icons. + - qol: Adds a tooltip to Ice Demon Afterimages ability. + - image: Cyborg view items now use the same sprites as their corresponding goggles + instead of old versions of those sprites. + JohnFulpWillard: + - bugfix: Buying the advanced plastic surgery disk from the uplink now gives you + advanced plastic surgery instead of brainwashing. + Melbert: + - rscadd: Spies may now roam the halls of Space Station 13. Watch your belongings + closely. + Rhials: + - bugfix: Pete can no longer eat vines while dead. +2024-03-03: + Rhials: + - bugfix: Fixes some tiles outside the Icebox AI satellite not getting hit by storms. + - code_imp: Splits up the nuclear operative antagonist datum folder. + xXPawnStarrXx: + - bugfix: makes custom pizzas dairy and vegetable free, unless you choose to add + them. +2024-03-04: + LT3: + - image: Tram frame is now tram girder, because it acts like one + Melbert: + - bugfix: Fixed spy stolen machines not dumping everything that needed to be dumped + Nerev4r: + - qol: Ethereal charging now loops when they're charging (from) APCs or from power + cells! + Singul0: + - rscadd: Adds a new changeling ability, "Darkness Adaptation". Making you more + translucent, especially in darkness and allowing you to see slightly better + in the dark + - balance: The changeling power "Chameleon Skin" has been buffed, Reduces the cost + to 1 and sped up the time it takes to turn invisible + ViktorKoL: + - bugfix: made some heretic descriptions more accurate + - spellcheck: improved english of the heretical eldritch patrons + starrm4nn: + - bugfix: Makes Metastation surgery access more consistent with other maps +2024-03-05: + DaliIsTaken: + - rscadd: added the lightbearer moth set, available in the character setup. + - image: added icons for the lightbearer set; new moth wings, antennae and markings. + LT3: + - bugfix: Tram floor tiles constructed inhand provides 4 instead of 1 + - bugfix: Tram floor tiles provide correct stack item when pulled up + - image: Tram floor tiles have their own inhand icons + - qol: Tram floor tiles available in the engineering protolathe + - bugfix: You can reconstruct deconstructed tram benches + - image: medical and improvised gauze are visibly different from cloth + Rerik007: + - bugfix: fixed the chances of living flesh actions + Rhials: + - bugfix: Some tiny tiny changes to the smoking room ruin to make it a little less + ugly. + ValuedEmployee: + - rscadd: Added new clown shoes "moffers" + - rscadd: Added moffers to the contraband list of the autodrobe + ViktorKoL: + - bugfix: fixed some issues when calculating the duration of moon smile's effects + Zergspower: + - admin: renames ruin names to have an identifier in front of it + - refactor: converts map plate and jump to ruin to tguilist + mc-oofert: + - bugfix: fixes deathmatch baseturfs (you cant crowbar the floor to breach to space) +2024-03-06: + KingkumaArt: + - rscadd: Shiny joke mi-go variant (not xenobio spawnable) + - rscdel: Removed unused mi-go static sprites + - image: Resprited mi-gos to not use plagarized art from CDDA + - image: Also allowed mi-gos to have directional facing instead of always facing + east + Melbert: + - rscadd: The animation that plays when an alert pops up on your screen is different. + - bugfix: Moving "down" as an observer is no longer janky. + - bugfix: All bibles are no longer suspiciously hollow + - bugfix: Extremely Minor Delta Morgue Fixes. See if you can spot them. + Seven: + - rscadd: Lockers and crates now shake when someone is attempting to resist out + of them. + SpaceLove: + - bugfix: Central Command Logistics department noticed the missing items on their + listings for robotics assembly crate. They have updated it! + Wallem: + - bugfix: The cursed coupon now only triggers a cursed event once, rather than infinite + times. + intercepti0n: + - bugfix: Pipe connector no longer appears on a hidden connector. + - bugfix: Re-wrenched atmospherics pipes no longer get extra offset. + - bugfix: All unary devices like injectors, passive vents etc. are centered while + hidden. + - image: Added smooth transition between hidden and visible pipes. +2024-03-07: + 00-Steven: + - bugfix: Alternate job titles such as chef and department security actually get + injected to the manifest as their respective ID trims, instead of being assigned + the job they're based off. + - bugfix: Alternate job titles such as chef and department security actually show + up under the right department on the manifest, instead of no department. + 13spacemen: + - rscdel: Removed Orbit Polling component, all orbit polls now use the Poll Alerts + system + - code_imp: Poll alerts support small border pictures in the chat message + - code_imp: Poll alert alert picture and jump target do not have to be the same + - qol: Slime intelligence potions ask the user for a reason, this will be shown + to ghosts + ArcaneMusic: + - balance: The stock market now fires slower, has stock market events occur more + often, and the stock market has fewer minerals that are available to buy in + a single purchase before restocking. + - balance: Materials sold on the stock market may be protected from being bought + if their prices drop too low, so make sure you watch your prices before they + run the risk of getting shut out! + - balance: Stock blocks now freeze the price of materials for 3 minutes, down from + 5. + - qol: Tweaks to the Galactic Material Market UI, with materials sorted based on + their rarity and a timer to show how long until it updates. + - rscadd: New Stock market events, one locks a material from being purchased, the + other maximizes the value and quantity of a material for sale. + Hatterhat: + - bugfix: Basic mobs no longer have the (unintended) ability to shoot out of containers, + like bluespace body bags. + Higgin: + - balance: personal flashes now Knockdown rather than Paralyze direct targets. + Jacquerel: + - admin: Made it easier for admins to adjust blood brother teams using admin tools. + - bugfix: Correct blood brother conversion logging. + - bugfix: AI-controlled spiders can correctly recognise where they can place webs. + - image: New sprites for most kinds of spider web + Majkl-J: + - bugfix: Prevents polymorphing deleting items by consuming them when transformed + then leaving the polymorph + Melbert: + - refactor: Food hunger bar has been refactored, and moved. Now it sits next to + the moodlet face. + - refactor: Food moodlets now update a lot more snappily. There is now a moodlet + tier between "being fat" and "being normal", to reduce accidentally gorging + yourself to "fatness" tier. + - rscdel: You can't hallucinate being hungry... for now. + - rscadd: Boulder refineries and smelters can refine Golems. + ViktorKoL: + - image: 'added unique icons for spells: caretaker''s refuge, apetra vulnera and + ascended shapechange' + aaaa1023: + - bugfix: Revenants can now again emag Medibots, Cleanbots, and Hygienebots. + san7890: + - qol: If your OOC message gets eaten due to some weird circumstance in how your + message is handled, it will feed the applicable message back to you so you can + copy-paste and try to send it again. +2024-03-08: + 2whatever2: + - bugfix: Tackle and wellcheers code for sanity values now function. + ArcaneMusic: + - rscadd: Vending machines now track how many credits have been spent on them, and + when restocked will pay out that saved portion to the restocker, with a 50% + match going to the cargo department. + - rscadd: Adds the restock tracker app, an NTOS app that tracks how well stocked + the station's vending machine units are at a glance as well as how much is contained + in each. + - refactor: Vending machines now use the payment component for money handling behavior, + meaning it will now accept held or pulled coins/cash/credits + - qol: Attacking vending machines can drop a portion of it's stored credits, at + the usual expected danger. + - balance: Tweaked the cost of various restock modules up and down. + - qol: Restock modules can now be sold for 50 credits. + Ben10Omintrix: + - bugfix: fixes ore vent spawned wolves being untammable + JohnFulpWillard: + - image: The minigames icon now has an icon for the deathmatch minigamee + Melbert: + - rscadd: You can talk via plushies, action figures, and toy mechs via `.l` or `.r`, + same as a ventriloquist dummy or decapitated dead + - rscdel: Talking via a plushie, action figure, toy mech, ventriloqiest dummy, decapitated + head, or marionette won't transmit over open mic comms + - refactor: Some say refactor and code cleanup. Vending machines now properly respect + being shut up. Report any oddities. + ValuedEmployee: + - sound: Added the new moffers sound effect and made moffers use it instead + cnleth: + - qol: Tramstation botany now has roundstart watering cans and syringes + intercepti0n: + - qol: ID cards in modular computers can now be swapped. + starrm4nn: + - bugfix: MetaStation Pharmacy is no longer accessible with general medical access, + Also changes the Chemistry and Pharmacy airlocks into medical ones. +2024-03-09: + 00-Steven: + - bugfix: Plexagon Access Management actually updates the shown template list on + authentication, avoiding needing to refresh/reopen/somesuch the program manually. + Drag: + - rscadd: Adds the Shark and Shork costume. Blahaj lovers rejoice! + Ghommie: + - bugfix: Fixed the tgui text input trimming the last character of the input if + it hits the maximum length. + - bugfix: This also fixes the PIN pad leading to the right wing of the museum away + mission. + Pickle-Coding: + - qol: Extended the metric prefixes. + mc-oofert: + - rscdel: Removed a nanomachine pizza from the deathmatch meat tower map that allowed + you to become a borg + necromanceranne: + - bugfix: Spies no longer have access to infinite use autosurgeons. + starrm4nn: + - qol: EVA can be given now in common access slots for non-command crewmembers. +2024-03-10: + 00-Steven: + - bugfix: When a carbon talks over robotic it uses their voice instead of visible + name. Meaning, voice changers work like they do over other comms regardless + of face covering. + Absolucy: + - refactor: Improved shuttle gibbing code, adding a new resistance flag, `SHUTTLE_CRUSH_PROOF`. + - bugfix: Immortality revival spectres can no longer be crushed by shuttles. + - rscadd: The ghost of Poly can no longer be shuttle-crushed, nor can anything incorporeal. + JohnFulpWillard, Tattax: + - rscadd: 'Adds a new station trait job: The Human AI.' + LemonInTheDark: + - rscadd: Tweaked the saturation, color and intensity of a bunch of lights + Momo8289: + - rscadd: Wheat and meatwheat can now be worn in the mask slot. Farmers rejoice! + - code_imp: Chem stun reductions are now applied more consistently. + - bugfix: Chem stun reductions should now more consistently apply to unconsciousness. + PapaMichael: + - bugfix: Removed erroneous information on some health analyzer's examine text. + Rhials: + - code_imp: Players now receive antag datums specific for reinforcements/support + borgs. + Shroopy: + - qol: Uninverted the inverted corner of the Icebox medbay treatment center. + Singul0: + - rscadd: Adds 3 new kits into the syndie-kit spawn pool, 2 for special and another + for tactical + SyncIt21: + - code_imp: Use a common list for acceptable silo materials for some stuff. Renamed + ore category into silo category. + - bugfix: fixes toolact screentips & balloon alerts for boulder machines & machine + frame + Thunder12345: + - rscadd: Added secondary objectives to bitrunning! + - rscadd: Pick up encrypted curiosities and return them to the safehouse to claim + their contents. + - rscadd: Glacier Grind has been given a secondary objective, look out for the limited + edition hat. + - rscadd: Bitrunning domains can now be modified during the round by admins. + Zergspower: + - qol: Curtains and shower curtains are no longer solid objects that defy common + sense + necromanceranne: + - balance: Bulldog Shotguns now have a 2-round burst fire. + - balance: Lone Operatives now come with some additional Bulldog Shotgun magazines. + - balance: Adjusts the values and contents of the Contraband Crate's item loot table. +2024-03-11: + LemonInTheDark: + - balance: Nerfs the burn times of flares and glowsticks, from 25/35 to 10/15 and + 50/60! to 20/25. + - balance: Glowsticks now dim as they burn + - rscadd: Glowsticks no longer leave an unusable action button sitting around then + they're lit + - bugfix: Glowsticks actually burn down now (The code was broken) + Pickle-Coding and Rhials: + - admin: RCD construction and deconstruction are logged. + - admin: Hallucinated projectiles no longer log. + - admin: Gives more detail to flamethrower logging. + - admin: More actions are logged for records consoles usage. + - admin: Frozen object shattering is logged. + Rhials: + - bugfix: Fugitive hunters no longer spawn after 1 minute of the fugitives' arrival. + SyncIt21: + - qol: adds examines & screentips for HPLC + - qol: HPLC will now display the status of each reagent if it can be purified or + not before starting the refining process. + - bugfix: HPLC won't accept hologram items. + - bugfix: HPLC uses the correct off icon state & pauses processing when opened, + powered off, unanchored or broken + - bugfix: HPLC will ignore reagents that are either already at max purity or are + inverse thus lowering eta & work done to purify your remaining reagents + - code_imp: merged procs to reduce code size. auto Doc procs for HPLC + - refactor: converted UI to typescript. + TiviPlus: + - bugfix: fixed some errors with the prepackaged tts + moocowswag: + - balance: Reinforced sections of station hull have gotten better at mitigating + explosive shockwaves. + necromanceranne: + - bugfix: A recent glitch within the Syndicate-coded uplinks resulted in a number + of Cybersun-branded cybernetics being distributed for absolutely no cost at + all, except to the company itself. This embarrassment has resulted in increased + tensions within the Syndicate as a culprit for this costly mistake is sought + out. +2024-03-12: + SyncIt21: + - qol: adds examines & screentips for grill (the machine for grilling food), converts + some chats to balloon alerts + - qol: foods that can be re-grilled any number of times + - qol: grill (the machine for grilling food) can now be made in the crafting menu. + Cargo pack cost for purchasing grill has now been halved. + - qol: monkey fuel & other reagents(see examines) can be added from any container + & not just from glass bottles to the grill (the machine for grilling food) + - bugfix: grills (the machine for grilling food) now don't burn foods into a mouldy + mess unlike the girdle + - bugfix: correctly computes grill times of items that were previously grilled + - bugfix: grills now have an upper fixed fuel limit + - code_imp: autodoc procs & vars for the grill (the machine for grilling food) + - refactor: grills (the machine for grilling food) has been refactored. report bugs + on github, also they only start processing after putting food/fuel into them. diff --git a/icons/ass/assalien.png b/icons/ass/assalien.png deleted file mode 100644 index 7ac184aa04be0..0000000000000 Binary files a/icons/ass/assalien.png and /dev/null differ diff --git a/icons/ass/asscat.png b/icons/ass/asscat.png deleted file mode 100644 index e37788e3dd734..0000000000000 Binary files a/icons/ass/asscat.png and /dev/null differ diff --git a/icons/ass/assdrone.png b/icons/ass/assdrone.png deleted file mode 100644 index 8a679f87c904d..0000000000000 Binary files a/icons/ass/assdrone.png and /dev/null differ diff --git a/icons/ass/assfemale.png b/icons/ass/assfemale.png deleted file mode 100644 index 22da27b71c281..0000000000000 Binary files a/icons/ass/assfemale.png and /dev/null differ diff --git a/icons/ass/assgrey.png b/icons/ass/assgrey.png deleted file mode 100644 index 60dde099510cf..0000000000000 Binary files a/icons/ass/assgrey.png and /dev/null differ diff --git a/icons/ass/asslizard.png b/icons/ass/asslizard.png deleted file mode 100644 index 38d82d9754c53..0000000000000 Binary files a/icons/ass/asslizard.png and /dev/null differ diff --git a/icons/ass/assmachine.png b/icons/ass/assmachine.png deleted file mode 100644 index 2ba447306c47c..0000000000000 Binary files a/icons/ass/assmachine.png and /dev/null differ diff --git a/icons/ass/assmale.png b/icons/ass/assmale.png deleted file mode 100644 index d215bc31e0979..0000000000000 Binary files a/icons/ass/assmale.png and /dev/null differ diff --git a/icons/ass/assplasma.png b/icons/ass/assplasma.png deleted file mode 100644 index 30294f65124bb..0000000000000 Binary files a/icons/ass/assplasma.png and /dev/null differ diff --git a/icons/ass/asspodperson.png b/icons/ass/asspodperson.png deleted file mode 100644 index 50e5a8644ab58..0000000000000 Binary files a/icons/ass/asspodperson.png and /dev/null differ diff --git a/icons/ass/assslime.png b/icons/ass/assslime.png deleted file mode 100644 index 9102dce7de10e..0000000000000 Binary files a/icons/ass/assslime.png and /dev/null differ diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi index fcb4e262d7c89..dab4f272d96ba 100644 Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ diff --git a/icons/effects/web.dmi b/icons/effects/web.dmi new file mode 100644 index 0000000000000..e21eb4d6ec34d Binary files /dev/null and b/icons/effects/web.dmi differ diff --git a/icons/hud/lobby/signup_button.dmi b/icons/hud/lobby/signup_button.dmi index daa2669069742..b1e7be603c9c7 100644 Binary files a/icons/hud/lobby/signup_button.dmi and b/icons/hud/lobby/signup_button.dmi differ diff --git a/icons/hud/screen_alert.dmi b/icons/hud/screen_alert.dmi index 7aa32c94b8e2a..e1c5db84d22ae 100644 Binary files a/icons/hud/screen_alert.dmi and b/icons/hud/screen_alert.dmi differ diff --git a/icons/hud/screen_clockwork.dmi b/icons/hud/screen_clockwork.dmi index aa815e957e4ae..0923e42e7e429 100644 Binary files a/icons/hud/screen_clockwork.dmi and b/icons/hud/screen_clockwork.dmi differ diff --git a/icons/hud/screen_detective.dmi b/icons/hud/screen_detective.dmi index d1d7e49a832d1..aed6e0d6572a5 100644 Binary files a/icons/hud/screen_detective.dmi and b/icons/hud/screen_detective.dmi differ diff --git a/icons/hud/screen_gen.dmi b/icons/hud/screen_gen.dmi index 608be00b2e1fb..2f4dd60b5d7ce 100644 Binary files a/icons/hud/screen_gen.dmi and b/icons/hud/screen_gen.dmi differ diff --git a/icons/hud/screen_ghost.dmi b/icons/hud/screen_ghost.dmi index b58680f025df4..13255a2a16890 100644 Binary files a/icons/hud/screen_ghost.dmi and b/icons/hud/screen_ghost.dmi differ diff --git a/icons/hud/screen_glass.dmi b/icons/hud/screen_glass.dmi index 29f8cb47bfd25..63ad3293921b8 100644 Binary files a/icons/hud/screen_glass.dmi and b/icons/hud/screen_glass.dmi differ diff --git a/icons/hud/screen_midnight.dmi b/icons/hud/screen_midnight.dmi index 9cfe8db9727c5..5483ddf4564a5 100644 Binary files a/icons/hud/screen_midnight.dmi and b/icons/hud/screen_midnight.dmi differ diff --git a/icons/hud/screen_operative.dmi b/icons/hud/screen_operative.dmi index b4b38782fe179..f2d60d394acc9 100644 Binary files a/icons/hud/screen_operative.dmi and b/icons/hud/screen_operative.dmi differ diff --git a/icons/hud/screen_plasmafire.dmi b/icons/hud/screen_plasmafire.dmi index 8225adbda6046..5423d3855b2b6 100644 Binary files a/icons/hud/screen_plasmafire.dmi and b/icons/hud/screen_plasmafire.dmi differ diff --git a/icons/hud/screen_retro.dmi b/icons/hud/screen_retro.dmi index a00d16cac5eb9..b4252109d6847 100644 Binary files a/icons/hud/screen_retro.dmi and b/icons/hud/screen_retro.dmi differ diff --git a/icons/hud/screen_slimecore.dmi b/icons/hud/screen_slimecore.dmi index b7e3a87e07ae9..a75fe55c37839 100644 Binary files a/icons/hud/screen_slimecore.dmi and b/icons/hud/screen_slimecore.dmi differ diff --git a/icons/hud/screen_trasenknox.dmi b/icons/hud/screen_trasenknox.dmi index 58c28d83e4be8..2569d2a635edd 100644 Binary files a/icons/hud/screen_trasenknox.dmi and b/icons/hud/screen_trasenknox.dmi differ diff --git a/icons/mob/actions/actions_animal.dmi b/icons/mob/actions/actions_animal.dmi index 64b1c700f414c..c13290716b75c 100644 Binary files a/icons/mob/actions/actions_animal.dmi and b/icons/mob/actions/actions_animal.dmi differ diff --git a/icons/mob/actions/actions_changeling.dmi b/icons/mob/actions/actions_changeling.dmi index 25a4e10aa578b..73aac72deb57d 100644 Binary files a/icons/mob/actions/actions_changeling.dmi and b/icons/mob/actions/actions_changeling.dmi differ diff --git a/icons/mob/actions/actions_ecult.dmi b/icons/mob/actions/actions_ecult.dmi index ac7575d279b9e..c7744749abb97 100644 Binary files a/icons/mob/actions/actions_ecult.dmi and b/icons/mob/actions/actions_ecult.dmi differ diff --git a/icons/mob/butts.dmi b/icons/mob/butts.dmi new file mode 100644 index 0000000000000..ae4b41961a1cd Binary files /dev/null and b/icons/mob/butts.dmi differ diff --git a/icons/mob/clothing/back.dmi b/icons/mob/clothing/back.dmi index 49e46149a670f..c3e5dc82069ad 100644 Binary files a/icons/mob/clothing/back.dmi and b/icons/mob/clothing/back.dmi differ diff --git a/icons/mob/clothing/back/backpack.dmi b/icons/mob/clothing/back/backpack.dmi index bf5207d85c409..afab07d9f13b2 100644 Binary files a/icons/mob/clothing/back/backpack.dmi and b/icons/mob/clothing/back/backpack.dmi differ diff --git a/icons/mob/clothing/feet.dmi b/icons/mob/clothing/feet.dmi index 5bd3005ab8e58..612d82302e714 100644 Binary files a/icons/mob/clothing/feet.dmi and b/icons/mob/clothing/feet.dmi differ diff --git a/icons/mob/clothing/head/costume.dmi b/icons/mob/clothing/head/costume.dmi index 06de36df19aac..4bce495a29143 100644 Binary files a/icons/mob/clothing/head/costume.dmi and b/icons/mob/clothing/head/costume.dmi differ diff --git a/icons/mob/clothing/head/hydroponics.dmi b/icons/mob/clothing/head/hydroponics.dmi index b710efc3d7d90..5e2a72a819f10 100644 Binary files a/icons/mob/clothing/head/hydroponics.dmi and b/icons/mob/clothing/head/hydroponics.dmi differ diff --git a/icons/mob/clothing/mask.dmi b/icons/mob/clothing/mask.dmi index db1259996c867..3a213d5275ccd 100644 Binary files a/icons/mob/clothing/mask.dmi and b/icons/mob/clothing/mask.dmi differ diff --git a/icons/mob/clothing/suits/costume.dmi b/icons/mob/clothing/suits/costume.dmi index 3cd5770fb9aee..3dfaf138da8dd 100644 Binary files a/icons/mob/clothing/suits/costume.dmi and b/icons/mob/clothing/suits/costume.dmi differ diff --git a/icons/mob/clothing/under/station_trait.dmi b/icons/mob/clothing/under/station_trait.dmi new file mode 100644 index 0000000000000..c8ed053981a66 Binary files /dev/null and b/icons/mob/clothing/under/station_trait.dmi differ diff --git a/icons/mob/huds/antag_hud.dmi b/icons/mob/huds/antag_hud.dmi index aa96f2338b250..90056e499fd2b 100644 Binary files a/icons/mob/huds/antag_hud.dmi and b/icons/mob/huds/antag_hud.dmi differ diff --git a/icons/mob/huds/hud.dmi b/icons/mob/huds/hud.dmi index 7ee3267280d2a..a3bbdf2ede075 100644 Binary files a/icons/mob/huds/hud.dmi and b/icons/mob/huds/hud.dmi differ diff --git a/icons/mob/human/species/moth/moth_antennae.dmi b/icons/mob/human/species/moth/moth_antennae.dmi index 9a4d237cc3621..a40013e93576f 100644 Binary files a/icons/mob/human/species/moth/moth_antennae.dmi and b/icons/mob/human/species/moth/moth_antennae.dmi differ diff --git a/icons/mob/human/species/moth/moth_markings.dmi b/icons/mob/human/species/moth/moth_markings.dmi index 2429b0aa12dfe..a6ebc2cedb517 100644 Binary files a/icons/mob/human/species/moth/moth_markings.dmi and b/icons/mob/human/species/moth/moth_markings.dmi differ diff --git a/icons/mob/human/species/moth/moth_wings.dmi b/icons/mob/human/species/moth/moth_wings.dmi index 65b8fba38d9b9..91c0d8b2b2cd1 100644 Binary files a/icons/mob/human/species/moth/moth_wings.dmi and b/icons/mob/human/species/moth/moth_wings.dmi differ diff --git a/icons/mob/inhands/clothing/suits_lefthand.dmi b/icons/mob/inhands/clothing/suits_lefthand.dmi index 8b9fa5256a932..a43756d743a9b 100644 Binary files a/icons/mob/inhands/clothing/suits_lefthand.dmi and b/icons/mob/inhands/clothing/suits_lefthand.dmi differ diff --git a/icons/mob/inhands/clothing/suits_righthand.dmi b/icons/mob/inhands/clothing/suits_righthand.dmi index c88f4d224444f..da837fdd8570e 100644 Binary files a/icons/mob/inhands/clothing/suits_righthand.dmi and b/icons/mob/inhands/clothing/suits_righthand.dmi differ diff --git a/icons/mob/inhands/equipment/briefcase_lefthand.dmi b/icons/mob/inhands/equipment/briefcase_lefthand.dmi index 5781ecdc992a7..9eb7d24a3ce12 100644 Binary files a/icons/mob/inhands/equipment/briefcase_lefthand.dmi and b/icons/mob/inhands/equipment/briefcase_lefthand.dmi differ diff --git a/icons/mob/inhands/equipment/briefcase_righthand.dmi b/icons/mob/inhands/equipment/briefcase_righthand.dmi index d48529a54e9ee..06ed8e2c8eca2 100644 Binary files a/icons/mob/inhands/equipment/briefcase_righthand.dmi and b/icons/mob/inhands/equipment/briefcase_righthand.dmi differ diff --git a/icons/mob/inhands/equipment/shields_lefthand.dmi b/icons/mob/inhands/equipment/shields_lefthand.dmi index d31dbb3f830b1..ce99a16d476c0 100644 Binary files a/icons/mob/inhands/equipment/shields_lefthand.dmi and b/icons/mob/inhands/equipment/shields_lefthand.dmi differ diff --git a/icons/mob/inhands/equipment/shields_righthand.dmi b/icons/mob/inhands/equipment/shields_righthand.dmi index dfd72809be71f..1c9c990b43dc9 100644 Binary files a/icons/mob/inhands/equipment/shields_righthand.dmi and b/icons/mob/inhands/equipment/shields_righthand.dmi differ diff --git a/icons/mob/inhands/items/tiles_lefthand.dmi b/icons/mob/inhands/items/tiles_lefthand.dmi index b73ded0c82c54..6bbe621db829a 100644 Binary files a/icons/mob/inhands/items/tiles_lefthand.dmi and b/icons/mob/inhands/items/tiles_lefthand.dmi differ diff --git a/icons/mob/inhands/items/tiles_righthand.dmi b/icons/mob/inhands/items/tiles_righthand.dmi index 1d668c01f070c..4d1d5073c98d6 100644 Binary files a/icons/mob/inhands/items/tiles_righthand.dmi and b/icons/mob/inhands/items/tiles_righthand.dmi differ diff --git a/icons/mob/silicon/robot_items.dmi b/icons/mob/silicon/robot_items.dmi index 9f8b0142e9c62..e18a9d08f8691 100644 Binary files a/icons/mob/silicon/robot_items.dmi and b/icons/mob/silicon/robot_items.dmi differ diff --git a/icons/mob/simple/animal.dmi b/icons/mob/simple/animal.dmi index 7fcf0e9d65e79..82943b798a941 100644 Binary files a/icons/mob/simple/animal.dmi and b/icons/mob/simple/animal.dmi differ diff --git a/icons/mob/simple/pets.dmi b/icons/mob/simple/pets.dmi index 9bd7d69c06bc5..311fff1e6b0dc 100644 Binary files a/icons/mob/simple/pets.dmi and b/icons/mob/simple/pets.dmi differ diff --git a/icons/obj/clothing/head/costume.dmi b/icons/obj/clothing/head/costume.dmi index 3cfbd3d21ef4f..e72c278b6eb37 100644 Binary files a/icons/obj/clothing/head/costume.dmi and b/icons/obj/clothing/head/costume.dmi differ diff --git a/icons/obj/clothing/shoes.dmi b/icons/obj/clothing/shoes.dmi index 0acc23d128217..37f561bb44b13 100644 Binary files a/icons/obj/clothing/shoes.dmi and b/icons/obj/clothing/shoes.dmi differ diff --git a/icons/obj/clothing/suits/costume.dmi b/icons/obj/clothing/suits/costume.dmi index ad68aea553f7f..f87a74c263f76 100644 Binary files a/icons/obj/clothing/suits/costume.dmi and b/icons/obj/clothing/suits/costume.dmi differ diff --git a/icons/obj/clothing/under/station_trait.dmi b/icons/obj/clothing/under/station_trait.dmi new file mode 100644 index 0000000000000..5fce62c31311a Binary files /dev/null and b/icons/obj/clothing/under/station_trait.dmi differ diff --git a/icons/obj/fluff/general.dmi b/icons/obj/fluff/general.dmi index 1aa7ae5c89857..f99cbaabc9a1d 100644 Binary files a/icons/obj/fluff/general.dmi and b/icons/obj/fluff/general.dmi differ diff --git a/icons/obj/fluff/puzzle_small.dmi b/icons/obj/fluff/puzzle_small.dmi index 1d94c0c8034ca..2f6ff9ebe3232 100644 Binary files a/icons/obj/fluff/puzzle_small.dmi and b/icons/obj/fluff/puzzle_small.dmi differ diff --git a/icons/obj/food/containers.dmi b/icons/obj/food/containers.dmi index d7e3d73f861ca..aea93b956fc3b 100644 Binary files a/icons/obj/food/containers.dmi and b/icons/obj/food/containers.dmi differ diff --git a/icons/obj/food/food.dmi b/icons/obj/food/food.dmi index c4d93c23b0b5d..2fb08c78be71a 100644 Binary files a/icons/obj/food/food.dmi and b/icons/obj/food/food.dmi differ diff --git a/icons/obj/machines/modular_console.dmi b/icons/obj/machines/modular_console.dmi index 2677dbb71220a..7b370a767852c 100644 Binary files a/icons/obj/machines/modular_console.dmi and b/icons/obj/machines/modular_console.dmi differ diff --git a/icons/obj/machines/vending.dmi b/icons/obj/machines/vending.dmi index a5db6caa6e2ba..bf3c42bde16a1 100644 Binary files a/icons/obj/machines/vending.dmi and b/icons/obj/machines/vending.dmi differ diff --git a/icons/obj/machines/wallmounts.dmi b/icons/obj/machines/wallmounts.dmi index 12a9c8e418f08..e849746eb0a66 100644 Binary files a/icons/obj/machines/wallmounts.dmi and b/icons/obj/machines/wallmounts.dmi differ diff --git a/icons/obj/medical/stack_medical.dmi b/icons/obj/medical/stack_medical.dmi index c4ec905786c69..b47cff516f211 100644 Binary files a/icons/obj/medical/stack_medical.dmi and b/icons/obj/medical/stack_medical.dmi differ diff --git a/icons/obj/modular_laptop.dmi b/icons/obj/modular_laptop.dmi index c8ad438d1a38f..1accc56f4c188 100644 Binary files a/icons/obj/modular_laptop.dmi and b/icons/obj/modular_laptop.dmi differ diff --git a/icons/obj/modular_pda.dmi b/icons/obj/modular_pda.dmi index 75553403ee2f8..6981b1de37ff6 100644 Binary files a/icons/obj/modular_pda.dmi and b/icons/obj/modular_pda.dmi differ diff --git a/icons/obj/pipes_n_cables/!pipe_gas_overlays.dmi b/icons/obj/pipes_n_cables/!pipe_gas_overlays.dmi index 0262adcaeb241..0ffed70fa5cc1 100644 Binary files a/icons/obj/pipes_n_cables/!pipe_gas_overlays.dmi and b/icons/obj/pipes_n_cables/!pipe_gas_overlays.dmi differ diff --git a/icons/obj/pipes_n_cables/!pipes_bitmask.dmi b/icons/obj/pipes_n_cables/!pipes_bitmask.dmi index 97643036fbe3b..e8bf7af5973a0 100644 Binary files a/icons/obj/pipes_n_cables/!pipes_bitmask.dmi and b/icons/obj/pipes_n_cables/!pipes_bitmask.dmi differ diff --git a/icons/obj/pipes_n_cables/pipe_template_pieces.dmi b/icons/obj/pipes_n_cables/pipe_template_pieces.dmi index d0d2f7ff7bb80..2316d7f3d9614 100644 Binary files a/icons/obj/pipes_n_cables/pipe_template_pieces.dmi and b/icons/obj/pipes_n_cables/pipe_template_pieces.dmi differ diff --git a/icons/obj/poster.dmi b/icons/obj/poster.dmi index c1120d0be0b7e..8193b38e1f21c 100644 Binary files a/icons/obj/poster.dmi and b/icons/obj/poster.dmi differ diff --git a/icons/obj/smooth_structures/stickyweb.dmi b/icons/obj/smooth_structures/stickyweb.dmi new file mode 100644 index 0000000000000..2c445260bd267 Binary files /dev/null and b/icons/obj/smooth_structures/stickyweb.dmi differ diff --git a/icons/obj/smooth_structures/stickyweb.png b/icons/obj/smooth_structures/stickyweb.png new file mode 100644 index 0000000000000..ae53aaa53b2b5 Binary files /dev/null and b/icons/obj/smooth_structures/stickyweb.png differ diff --git a/icons/obj/smooth_structures/stickyweb.png.toml b/icons/obj/smooth_structures/stickyweb.png.toml new file mode 100644 index 0000000000000..cf7f5ce339f0b --- /dev/null +++ b/icons/obj/smooth_structures/stickyweb.png.toml @@ -0,0 +1,14 @@ +output_name = "stickyweb" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 50 +y = 50 + +[output_icon_size] +x = 50 +y = 50 + +[cut_pos] +x = 25 +y = 25 diff --git a/icons/obj/smooth_structures/stickyweb_rotated.dmi b/icons/obj/smooth_structures/stickyweb_rotated.dmi new file mode 100644 index 0000000000000..11d3e5546ae34 Binary files /dev/null and b/icons/obj/smooth_structures/stickyweb_rotated.dmi differ diff --git a/icons/obj/smooth_structures/stickyweb_rotated.png b/icons/obj/smooth_structures/stickyweb_rotated.png new file mode 100644 index 0000000000000..6c5413ab792e9 Binary files /dev/null and b/icons/obj/smooth_structures/stickyweb_rotated.png differ diff --git a/icons/obj/smooth_structures/stickyweb_rotated.png.toml b/icons/obj/smooth_structures/stickyweb_rotated.png.toml new file mode 100644 index 0000000000000..61c615585e1c5 --- /dev/null +++ b/icons/obj/smooth_structures/stickyweb_rotated.png.toml @@ -0,0 +1,14 @@ +output_name = "stickyweb_rotated" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 50 +y = 50 + +[output_icon_size] +x = 50 +y = 50 + +[cut_pos] +x = 25 +y = 25 diff --git a/icons/obj/smooth_structures/stickyweb_spikes.dmi b/icons/obj/smooth_structures/stickyweb_spikes.dmi new file mode 100644 index 0000000000000..b3dcfcc83f944 Binary files /dev/null and b/icons/obj/smooth_structures/stickyweb_spikes.dmi differ diff --git a/icons/obj/smooth_structures/stickyweb_spikes.png b/icons/obj/smooth_structures/stickyweb_spikes.png new file mode 100644 index 0000000000000..98832695f67f5 Binary files /dev/null and b/icons/obj/smooth_structures/stickyweb_spikes.png differ diff --git a/icons/obj/smooth_structures/stickyweb_spikes.png.toml b/icons/obj/smooth_structures/stickyweb_spikes.png.toml new file mode 100644 index 0000000000000..ebd1260cfcf64 --- /dev/null +++ b/icons/obj/smooth_structures/stickyweb_spikes.png.toml @@ -0,0 +1,14 @@ +output_name = "stickyweb_spikes" +template = "bitmask/diagonal_32x32.toml" + +[icon_size] +x = 50 +y = 50 + +[output_icon_size] +x = 50 +y = 50 + +[cut_pos] +x = 25 +y = 25 diff --git a/icons/obj/smooth_structures/webwall.dmi b/icons/obj/smooth_structures/webwall.dmi new file mode 100644 index 0000000000000..e2308526cc96f Binary files /dev/null and b/icons/obj/smooth_structures/webwall.dmi differ diff --git a/icons/obj/smooth_structures/webwall.png b/icons/obj/smooth_structures/webwall.png new file mode 100644 index 0000000000000..207978b8aaf47 Binary files /dev/null and b/icons/obj/smooth_structures/webwall.png differ diff --git a/icons/obj/smooth_structures/webwall.png.toml b/icons/obj/smooth_structures/webwall.png.toml new file mode 100644 index 0000000000000..3f91f5f62e732 --- /dev/null +++ b/icons/obj/smooth_structures/webwall.png.toml @@ -0,0 +1,2 @@ +output_name = "webwall" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/webwall_dark.dmi b/icons/obj/smooth_structures/webwall_dark.dmi new file mode 100644 index 0000000000000..d3863a818144e Binary files /dev/null and b/icons/obj/smooth_structures/webwall_dark.dmi differ diff --git a/icons/obj/smooth_structures/webwall_dark.png b/icons/obj/smooth_structures/webwall_dark.png new file mode 100644 index 0000000000000..df36c5108bda0 Binary files /dev/null and b/icons/obj/smooth_structures/webwall_dark.png differ diff --git a/icons/obj/smooth_structures/webwall_dark.png.toml b/icons/obj/smooth_structures/webwall_dark.png.toml new file mode 100644 index 0000000000000..4b3b155f12614 --- /dev/null +++ b/icons/obj/smooth_structures/webwall_dark.png.toml @@ -0,0 +1,2 @@ +output_name = "webwall_dark" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/smooth_structures/webwall_reflector.dmi b/icons/obj/smooth_structures/webwall_reflector.dmi new file mode 100644 index 0000000000000..04547349d53cd Binary files /dev/null and b/icons/obj/smooth_structures/webwall_reflector.dmi differ diff --git a/icons/obj/smooth_structures/webwall_reflector.png b/icons/obj/smooth_structures/webwall_reflector.png new file mode 100644 index 0000000000000..881cb24fc0dad Binary files /dev/null and b/icons/obj/smooth_structures/webwall_reflector.png differ diff --git a/icons/obj/smooth_structures/webwall_reflector.png.toml b/icons/obj/smooth_structures/webwall_reflector.png.toml new file mode 100644 index 0000000000000..c47c554f8dabb --- /dev/null +++ b/icons/obj/smooth_structures/webwall_reflector.png.toml @@ -0,0 +1,2 @@ +output_name = "webwall_reflector" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/obj/storage/backpack.dmi b/icons/obj/storage/backpack.dmi index c61d9321611b6..e4364146a1c99 100644 Binary files a/icons/obj/storage/backpack.dmi and b/icons/obj/storage/backpack.dmi differ diff --git a/icons/obj/storage/case.dmi b/icons/obj/storage/case.dmi index 0320ce881f90e..a47c86eea9fb3 100644 Binary files a/icons/obj/storage/case.dmi and b/icons/obj/storage/case.dmi differ diff --git a/icons/obj/structures.dmi b/icons/obj/structures.dmi index 1e6a2ba68724e..50861b248d530 100644 Binary files a/icons/obj/structures.dmi and b/icons/obj/structures.dmi differ diff --git a/icons/obj/tiles.dmi b/icons/obj/tiles.dmi index fdddb793362a6..4e26f75aa99d1 100644 Binary files a/icons/obj/tiles.dmi and b/icons/obj/tiles.dmi differ diff --git a/icons/obj/tram/tram_wall.dmi b/icons/obj/tram/tram_wall.dmi deleted file mode 100644 index 42448dc77d318..0000000000000 Binary files a/icons/obj/tram/tram_wall.dmi and /dev/null differ diff --git a/icons/obj/weapons/grenade.dmi b/icons/obj/weapons/grenade.dmi index 40ab4d99e05fc..b3fb018bafa07 100644 Binary files a/icons/obj/weapons/grenade.dmi and b/icons/obj/weapons/grenade.dmi differ diff --git a/icons/obj/weapons/shields.dmi b/icons/obj/weapons/shields.dmi index 3f90af83196ba..7c4be107566ec 100644 Binary files a/icons/obj/weapons/shields.dmi and b/icons/obj/weapons/shields.dmi differ diff --git a/icons/ui_icons/virtualpet/pet_state.dmi b/icons/ui_icons/virtualpet/pet_state.dmi new file mode 100644 index 0000000000000..7ec865d104bc1 Binary files /dev/null and b/icons/ui_icons/virtualpet/pet_state.dmi differ diff --git a/sound/ambience/antag/spy.ogg b/sound/ambience/antag/spy.ogg new file mode 100644 index 0000000000000..1a5c64a3979b1 Binary files /dev/null and b/sound/ambience/antag/spy.ogg differ diff --git a/sound/ambience/license.txt b/sound/ambience/license.txt index 607dd6628e79b..a0b6efb24c5c1 100644 --- a/sound/ambience/license.txt +++ b/sound/ambience/license.txt @@ -1,4 +1,4 @@ -ambidet1.ogg is Fast Talking by Kevin Macleod. It has been licensed under the CC-BY 3.0 license. +ambidet1.ogg and spy.ogg is Fast Talking by Kevin Macleod. It has been licensed under the CC-BY 3.0 license. It has been cropped for use ingame. ambidet2.ogg is Night on the Docks, Piano by Kevin Macleod. It has been licensed under CC-BY 3.0 license. It has been cropped for use ingame, and also fades in. diff --git a/sound/attributions.txt b/sound/attributions.txt index 82486a5735da0..c81aa3e664b67 100644 --- a/sound/attributions.txt +++ b/sound/attributions.txt @@ -117,6 +117,9 @@ https://freesound.org/people/humanoide9000/sounds/330293/ reel1.ogg, reel2.ogg, reel3.ogg, reel4.ogg and reel5.ogg adapted from pixabay. Free for use under the Pixabay Content License (https://pixabay.com/service/license-summary/): https://pixabay.com/sound-effects/reel-78063/ +rattle1.ogg, rattle2.ogg and rattle3.ogg adapted from pixabay. Free for use under the Pixabay Content License (https://pixabay.com/service/license-summary/): +https://pixabay.com/sound-effects/chain-6073/ + throw.ogg, throwhard.ogg and throwsoft.ogg (Royalty-Free and Copyright-Free) are adapted from Jam FX, SmartSound FX and Epic Stock Media in : https://uppbeat.io/sfx/whoosh-swift-cut/7727/23617 https://uppbeat.io/sfx/whoosh-air-punch/114/1168 diff --git a/sound/effects/footstep/moffstep01.ogg b/sound/effects/footstep/moffstep01.ogg new file mode 100644 index 0000000000000..6350cb057bf0b Binary files /dev/null and b/sound/effects/footstep/moffstep01.ogg differ diff --git a/sound/items/orbie_level_up.ogg b/sound/items/orbie_level_up.ogg new file mode 100644 index 0000000000000..c876c9d78173a Binary files /dev/null and b/sound/items/orbie_level_up.ogg differ diff --git a/sound/items/orbie_notification_sound.ogg b/sound/items/orbie_notification_sound.ogg new file mode 100644 index 0000000000000..b43bba41ae5a6 Binary files /dev/null and b/sound/items/orbie_notification_sound.ogg differ diff --git a/sound/items/orbie_send_out.ogg b/sound/items/orbie_send_out.ogg new file mode 100644 index 0000000000000..aba3d84e18609 Binary files /dev/null and b/sound/items/orbie_send_out.ogg differ diff --git a/sound/items/orbie_trick_learned.ogg b/sound/items/orbie_trick_learned.ogg new file mode 100644 index 0000000000000..bc50cf41b1ced Binary files /dev/null and b/sound/items/orbie_trick_learned.ogg differ diff --git a/sound/items/rattle1.ogg b/sound/items/rattle1.ogg new file mode 100644 index 0000000000000..71c4110fafe46 Binary files /dev/null and b/sound/items/rattle1.ogg differ diff --git a/sound/items/rattle2.ogg b/sound/items/rattle2.ogg new file mode 100644 index 0000000000000..30f0e2d85ea93 Binary files /dev/null and b/sound/items/rattle2.ogg differ diff --git a/sound/items/rattle3.ogg b/sound/items/rattle3.ogg new file mode 100644 index 0000000000000..ef1cfc6bf6b6f Binary files /dev/null and b/sound/items/rattle3.ogg differ diff --git a/strings/antagonist_flavor/spy_objective.json b/strings/antagonist_flavor/spy_objective.json new file mode 100644 index 0000000000000..aa696baad6fa0 --- /dev/null +++ b/strings/antagonist_flavor/spy_objective.json @@ -0,0 +1,84 @@ +{ + "objective_body": [ + "Assassinate a high profile crewmember without being caught.", + "Cause a disaster to shake the station.", + "Cause a station evacuation.", + "Deprive the station of as many @pick(stealables) as you can.", + "Ensure @pick(department) is @pick(affected) by the end of the shift.", + "Ensure @pick(location) is @pick(affected) by the end of the shift.", + "Ensure no heads of staff @pick(escape) the station.", + "Ensure no members of @pick(department) @pick(escape) the station.", + "Ensure no rival @pick(rivals) @pick(escape) the station.", + "Frame a crewmember for a crime.", + "Free the station's AI from its laws.", + "Halt the station's @pick(happenings).", + "Invoke a mutiny against the heads of staff.", + "Make it difficult, but not impossible to @pick(escape) the station.", + "Sabotage the station's power grid or engine.", + "Steal as many @pick(stealables) as you can.", + "Take control of the station as the new Captain.", + "Take hostages of high value crewmembers and demand a ransom." + ], + "department": [ + "Security", + "Engineering", + "Medical", + "Science", + "Supply" + ], + "location": [ + "engineering", + "genetics", + "hydroponics", + "medbay", + "the bar", + "the bridge", + "the brig", + "the cargo bay", + "the chapel", + "the kitchen", + "the library", + "xenobiology" + ], + "happenings": [ + "research", + "cargo operations", + "communications", + "genetic research", + "mining operation" + ], + "affected": [ + "ablaze", + "burning", + "covered in blood", + "demolished", + "destroyed", + "engulfed in flames", + "obliterated", + "on fire", + "ruined", + "sabotaged", + "wrecked" + ], + "rivals": [ + "agents", + "moles", + "operatives", + "spies", + "traitors" + ], + "stealables": [ + "items", + "objects", + "things", + "tools", + "weapons" + ], + "escape": [ + "depart", + "escape", + "evacuate", + "flee", + "leave" + ] +} diff --git a/tgstation.dme b/tgstation.dme index 6fae686f69add..e5cf4b5480021 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -13,6 +13,7 @@ // END_PREFERENCES // BEGIN_INCLUDE +#include "__odlint.dm" #include "_maps\_basemap.dm" #include "code\__byond_version_compat.dm" #include "code\_compile_options.dm" @@ -618,6 +619,7 @@ #include "code\controllers\subsystem\atoms.dm" #include "code\controllers\subsystem\augury.dm" #include "code\controllers\subsystem\ban_cache.dm" +#include "code\controllers\subsystem\bitrunning.dm" #include "code\controllers\subsystem\blackbox.dm" #include "code\controllers\subsystem\blackmarket.dm" #include "code\controllers\subsystem\chat.dm" @@ -725,6 +727,7 @@ #include "code\controllers\subsystem\persistence\custom_outfits.dm" #include "code\controllers\subsystem\persistence\engravings.dm" #include "code\controllers\subsystem\persistence\photo_albums.dm" +#include "code\controllers\subsystem\persistence\piggy_banks.dm" #include "code\controllers\subsystem\persistence\recipes.dm" #include "code\controllers\subsystem\persistence\scars.dm" #include "code\controllers\subsystem\persistence\tattoos.dm" @@ -1127,7 +1130,6 @@ #include "code\datums\components\omen.dm" #include "code\datums\components\on_hit_effect.dm" #include "code\datums\components\onwear_mood.dm" -#include "code\datums\components\orbit_poll.dm" #include "code\datums\components\orbiter.dm" #include "code\datums\components\overlay_lighting.dm" #include "code\datums\components\palette.dm" @@ -1368,6 +1370,7 @@ #include "code\datums\elements\bugkiller_reagent.dm" #include "code\datums\elements\bump_click.dm" #include "code\datums\elements\can_barricade.dm" +#include "code\datums\elements\can_shatter.dm" #include "code\datums\elements\caseless.dm" #include "code\datums\elements\chemical_transfer.dm" #include "code\datums\elements\chewable.dm" @@ -1461,7 +1464,6 @@ #include "code\datums\elements\rust.dm" #include "code\datums\elements\selfknockback.dm" #include "code\datums\elements\series.dm" -#include "code\datums\elements\shatters_when_thrown.dm" #include "code\datums\elements\sideway_movement.dm" #include "code\datums\elements\simple_flying.dm" #include "code\datums\elements\skill_reward.dm" @@ -1479,6 +1481,7 @@ #include "code\datums\elements\tenacious.dm" #include "code\datums\elements\tiny_mob_hunter.dm" #include "code\datums\elements\tool_flash.dm" +#include "code\datums\elements\toy_talk.dm" #include "code\datums\elements\turf_transparency.dm" #include "code\datums\elements\undertile.dm" #include "code\datums\elements\unfriend_attacker.dm" @@ -1997,7 +2000,6 @@ #include "code\game\machinery\computer\accounting.dm" #include "code\game\machinery\computer\aifixer.dm" #include "code\game\machinery\computer\apc_control.dm" -#include "code\game\machinery\computer\arena.dm" #include "code\game\machinery\computer\atmos_alert.dm" #include "code\game\machinery\computer\buildandrepair.dm" #include "code\game\machinery\computer\camera.dm" @@ -2215,6 +2217,7 @@ #include "code\game\objects\items\botpad_remote.dm" #include "code\game\objects\items\boxcutter.dm" #include "code\game\objects\items\broom.dm" +#include "code\game\objects\items\busts_and_figurines.dm" #include "code\game\objects\items\cardboard_cutouts.dm" #include "code\game\objects\items\cards_ids.dm" #include "code\game\objects\items\chainsaw.dm" @@ -2261,6 +2264,7 @@ #include "code\game\objects\items\kitchen.dm" #include "code\game\objects\items\knives.dm" #include "code\game\objects\items\latexballoon.dm" +#include "code\game\objects\items\machine_wand.dm" #include "code\game\objects\items\mail.dm" #include "code\game\objects\items\maintenance_loot.dm" #include "code\game\objects\items\manuals.dm" @@ -2269,6 +2273,7 @@ #include "code\game\objects\items\paint.dm" #include "code\game\objects\items\paiwire.dm" #include "code\game\objects\items\pet_carrier.dm" +#include "code\game\objects\items\piggy_bank.dm" #include "code\game\objects\items\pillow.dm" #include "code\game\objects\items\pinpointer.dm" #include "code\game\objects\items\pitchfork.dm" @@ -2955,6 +2960,7 @@ #include "code\modules\antagonists\changeling\powers\augmented_eyesight.dm" #include "code\modules\antagonists\changeling\powers\biodegrade.dm" #include "code\modules\antagonists\changeling\powers\chameleon_skin.dm" +#include "code\modules\antagonists\changeling\powers\darkness_adaptation.dm" #include "code\modules\antagonists\changeling\powers\defib_grasp.dm" #include "code\modules\antagonists\changeling\powers\digitalcamo.dm" #include "code\modules\antagonists\changeling\powers\fakedeath.dm" @@ -3116,8 +3122,12 @@ #include "code\modules\antagonists\ninja\ninja_stars.dm" #include "code\modules\antagonists\ninja\ninjaDrainAct.dm" #include "code\modules\antagonists\ninja\outfit.dm" -#include "code\modules\antagonists\nukeop\nukeop.dm" #include "code\modules\antagonists\nukeop\outfits.dm" +#include "code\modules\antagonists\nukeop\datums\operative.dm" +#include "code\modules\antagonists\nukeop\datums\operative_leader.dm" +#include "code\modules\antagonists\nukeop\datums\operative_lone.dm" +#include "code\modules\antagonists\nukeop\datums\operative_reinforcement.dm" +#include "code\modules\antagonists\nukeop\datums\operative_team.dm" #include "code\modules\antagonists\nukeop\equipment\borgchameleon.dm" #include "code\modules\antagonists\nukeop\equipment\nuclear_authentication_disk.dm" #include "code\modules\antagonists\nukeop\equipment\nuclear_challenge.dm" @@ -3151,6 +3161,10 @@ #include "code\modules\antagonists\space_dragon\space_dragon.dm" #include "code\modules\antagonists\space_ninja\space_ninja.dm" #include "code\modules\antagonists\spiders\spiders.dm" +#include "code\modules\antagonists\spy\spy.dm" +#include "code\modules\antagonists\spy\spy_bounty.dm" +#include "code\modules\antagonists\spy\spy_bounty_handler.dm" +#include "code\modules\antagonists\spy\spy_uplink.dm" #include "code\modules\antagonists\survivalist\survivalist.dm" #include "code\modules\antagonists\syndicate_monkey\syndicate_monkey.dm" #include "code\modules\antagonists\traitor\balance_helper.dm" @@ -3416,6 +3430,7 @@ #include "code\modules\bitrunning\objects\hololadder.dm" #include "code\modules\bitrunning\objects\host_monitor.dm" #include "code\modules\bitrunning\objects\landmarks.dm" +#include "code\modules\bitrunning\objects\loot_box.dm" #include "code\modules\bitrunning\objects\loot_crate.dm" #include "code\modules\bitrunning\objects\netpod.dm" #include "code\modules\bitrunning\objects\quantum_console.dm" @@ -3801,6 +3816,7 @@ #include "code\modules\clothing\under\jobs\medical.dm" #include "code\modules\clothing\under\jobs\rnd.dm" #include "code\modules\clothing\under\jobs\security.dm" +#include "code\modules\clothing\under\jobs\station_trait.dm" #include "code\modules\clothing\under\jobs\civilian\civilian.dm" #include "code\modules\clothing\under\jobs\civilian\clown_mime.dm" #include "code\modules\clothing\under\jobs\civilian\curator.dm" @@ -4254,6 +4270,7 @@ #include "code\modules\jobs\job_types\spawner\zombie.dm" #include "code\modules\jobs\job_types\station_trait\bridge_assistant.dm" #include "code\modules\jobs\job_types\station_trait\cargo_gorilla.dm" +#include "code\modules\jobs\job_types\station_trait\human_ai.dm" #include "code\modules\jobs\job_types\station_trait\veteran_advisor.dm" #include "code\modules\keybindings\bindings_atom.dm" #include "code\modules\keybindings\bindings_client.dm" @@ -4689,6 +4706,9 @@ #include "code\modules\mob\living\basic\pets\dog\corgi.dm" #include "code\modules\mob\living\basic\pets\dog\dog_subtypes.dm" #include "code\modules\mob\living\basic\pets\dog\strippable_items.dm" +#include "code\modules\mob\living\basic\pets\orbie\orbie.dm" +#include "code\modules\mob\living\basic\pets\orbie\orbie_abilities.dm" +#include "code\modules\mob\living\basic\pets\orbie\orbie_ai.dm" #include "code\modules\mob\living\basic\pets\parrot\_parrot.dm" #include "code\modules\mob\living\basic\pets\parrot\parrot_items.dm" #include "code\modules\mob\living\basic\pets\parrot\parrot_subtypes.dm" @@ -4984,7 +5004,6 @@ #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\curse_blob.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\mining_mobs.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\polarbear.dm" -#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\wolf.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\elites\elite.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\elites\goliath_broodmother.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\elites\herald.dm" @@ -5090,6 +5109,7 @@ #include "code\modules\modular_computers\file_system\programs\powermonitor.dm" #include "code\modules\modular_computers\file_system\programs\radar.dm" #include "code\modules\modular_computers\file_system\programs\records.dm" +#include "code\modules\modular_computers\file_system\programs\restock_tracker.dm" #include "code\modules\modular_computers\file_system\programs\robocontrol.dm" #include "code\modules\modular_computers\file_system\programs\robotact.dm" #include "code\modules\modular_computers\file_system\programs\secureye.dm" @@ -5099,6 +5119,7 @@ #include "code\modules\modular_computers\file_system\programs\statusdisplay.dm" #include "code\modules\modular_computers\file_system\programs\techweb.dm" #include "code\modules\modular_computers\file_system\programs\theme_selector.dm" +#include "code\modules\modular_computers\file_system\programs\virtual_pet.dm" #include "code\modules\modular_computers\file_system\programs\wirecarp.dm" #include "code\modules\modular_computers\file_system\programs\antagonist\contractor_program.dm" #include "code\modules\modular_computers\file_system\programs\antagonist\dos.dm" @@ -5861,6 +5882,7 @@ #include "code\modules\uplink\uplink_items\nukeops.dm" #include "code\modules\uplink\uplink_items\special.dm" #include "code\modules\uplink\uplink_items\species.dm" +#include "code\modules\uplink\uplink_items\spy_unique.dm" #include "code\modules\uplink\uplink_items\stealthy.dm" #include "code\modules\uplink\uplink_items\stealthy_tools.dm" #include "code\modules\uplink\uplink_items\suits.dm" @@ -5930,6 +5952,7 @@ #include "code\modules\vending\engineering.dm" #include "code\modules\vending\engivend.dm" #include "code\modules\vending\games.dm" +#include "code\modules\vending\hotdog.dm" #include "code\modules\vending\liberation.dm" #include "code\modules\vending\liberation_toy.dm" #include "code\modules\vending\magivend.dm" diff --git a/tgui/packages/tgui/components/Dropdown.tsx b/tgui/packages/tgui/components/Dropdown.tsx index 4fabfbf0bbf2d..a103a7466d3f5 100644 --- a/tgui/packages/tgui/components/Dropdown.tsx +++ b/tgui/packages/tgui/components/Dropdown.tsx @@ -97,9 +97,9 @@ export function Dropdown(props: Props) { let newIndex = selectedIndex; if (direction === 'next') { - newIndex = selectedIndex === endIndex ? startIndex : selectedIndex++; + newIndex = selectedIndex === endIndex ? startIndex : ++selectedIndex; } else { - newIndex = selectedIndex === startIndex ? endIndex : selectedIndex--; + newIndex = selectedIndex === startIndex ? endIndex : --selectedIndex; } onSelected?.(getOptionValue(options[newIndex])); diff --git a/tgui/packages/tgui/interfaces/AntagInfoSpy.tsx b/tgui/packages/tgui/interfaces/AntagInfoSpy.tsx new file mode 100644 index 0000000000000..a26266bceb4d0 --- /dev/null +++ b/tgui/packages/tgui/interfaces/AntagInfoSpy.tsx @@ -0,0 +1,65 @@ +import { useBackend } from '../backend'; +import { Section, Stack } from '../components'; +import { Window } from '../layouts'; +import { Objective, ObjectivePrintout } from './common/Objectives'; + +const greenText = { + fontWeight: 'italics', + color: '#20b142', +}; + +const redText = { + fontWeight: 'italics', + color: '#e03c3c', +}; + +type Data = { + antag_name: string; + uplink_location: string | null; + objectives: Objective[]; +}; + +export const AntagInfoSpy = () => { + const { data } = useBackend(); + const { antag_name, uplink_location, objectives } = data; + return ( + + +
+ + + You have been equipped with a special uplink device disguised as{' '} + {uplink_location || 'something'} that will allow you to steal from + the station. + + + + Use it in hand to access your uplink, and{' '} + right click on bounty targets to steal them. + + + + + You may not be alone: There may be other spies on the station. + + + Work together or work against them: The choice is yours, but{' '} + you cannot share the rewards. + + + + + + +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ChemRecipeDebug.tsx b/tgui/packages/tgui/interfaces/ChemRecipeDebug.tsx index c108966bbb757..a1c53ffb8791f 100644 --- a/tgui/packages/tgui/interfaces/ChemRecipeDebug.tsx +++ b/tgui/packages/tgui/interfaces/ChemRecipeDebug.tsx @@ -368,7 +368,7 @@ export const ChemRecipeDebug = (props) => { step={0.1} stepPixelSize={3} value={editReaction?.editValue || 0} - minValue={0} + minValue={-1000} maxValue={1000} disabled={editReaction === null} onDrag={(e, value) => diff --git a/tgui/packages/tgui/interfaces/MassSpec.jsx b/tgui/packages/tgui/interfaces/MassSpec.jsx deleted file mode 100644 index 29d7fb8269dc3..0000000000000 --- a/tgui/packages/tgui/interfaces/MassSpec.jsx +++ /dev/null @@ -1,389 +0,0 @@ -import { round } from 'common/math'; - -import { useBackend } from '../backend'; -import { - Box, - Button, - Dimmer, - Icon, - Section, - Slider, - Table, -} from '../components'; -import { Window } from '../layouts'; - -export const MassSpec = (props) => { - const { act, data } = useBackend(); - const { - processing, - lowerRange, - upperRange, - graphUpperRange, - graphLowerRange, - eta, - beaker1CurrentVolume, - beaker2CurrentVolume, - beaker1MaxVolume, - beaker2MaxVolume, - peakHeight, - beaker1, - beaker2, - beaker1Contents = [], - beaker2Contents = [], - } = data; - - const centerValue = (lowerRange + upperRange) / 2; - - return ( - - - {!!processing && ( - - - {' Purifying... ' + round(eta) + 's'} - - )} -
act('activate')} - /> - } - > - {(beaker1Contents.length && ( - - )) || Please insert an input beaker with reagents!} -
- -
- {!!beaker1MaxVolume && ( - - {beaker1CurrentVolume} / {beaker1MaxVolume} units - - )} -
-
- {!!beaker2MaxVolume && ( - - {beaker2CurrentVolume} / {beaker2MaxVolume} units - - )} -
-
-
- ); -}; - -const BeakerMassProfile = (props) => { - const { loaded, details, beaker = [] } = props; - - return ( - - {(!loaded && No beaker loaded.) || - (beaker.length === 0 && Beaker is empty.) || ( - - - - Reagent - - - Volume - - - Mass - - - Purity - - - Type - - {!!details && ( - - Results - - )} - - {beaker.map((reagent) => ( - - - {reagent.name} - - - {reagent.volume} - - - {reagent.mass} - - - {`${reagent.purity}%`} - - - ▮{reagent.type} - - {!!details && {reagent.log}} - - ))} -
- )} -
- ); -}; - -const MassSpectroscopy = (props) => { - const { act, data } = useBackend(); - const { - lowerRange, - centerValue, - upperRange, - graphUpperRange, - graphLowerRange, - maxAbsorbance, - reagentPeaks = [], - } = props; - - const deltaRange = graphUpperRange - graphLowerRange; - - const graphIncrement = deltaRange * 0.2; - - return ( - <> - - - - {/* x axis*/} - - Mass (g) - - - {graphLowerRange} - - - {round(graphLowerRange + graphIncrement, 1)} - - - {round(graphLowerRange + graphIncrement * 2, 1)} - - - {round(graphLowerRange + graphIncrement * 3, 1)} - - - {round(graphLowerRange + graphIncrement * 4, 1)} - - - {graphUpperRange} - - {/* y axis*/} - - {round(maxAbsorbance, 1)} - - - {round(maxAbsorbance * 0.8, 1)} - - - {round(maxAbsorbance * 0.6, 1)} - - - {round(maxAbsorbance * 0.4, 1)} - - - {round(maxAbsorbance * 0.2, 1)} - - - 0 - - - - - Absorbance (AU) - - - - {reagentPeaks.map((peak) => ( - // Triangle peak - - ))} - - - - - - - - round(value)} - width={(centerValue / graphUpperRange) * 400 + 'px'} - value={lowerRange} - minValue={graphLowerRange} - maxValue={centerValue} - color={'invisible'} - onDrag={(e, value) => - act('leftSlider', { - value: value, - }) - } - > - {' '} - - round(value)} - step={graphUpperRange / 400} - width={400 - (centerValue / graphUpperRange) * 400 + 'px'} - value={upperRange} - minValue={centerValue} - maxValue={graphUpperRange} - color={'invisible'} - onDrag={(e, value) => - act('rightSlider', { - value: value, - }) - } - > - {' '} - - - round(value)} - width={400 + 'px'} - minValue={graphLowerRange + 1} - maxValue={graphUpperRange - 1} - color={'invisible'} - onDrag={(e, value) => - act('centerSlider', { - value: value, - }) - } - > - {' '} - - - - - ); -}; diff --git a/tgui/packages/tgui/interfaces/MassSpec.tsx b/tgui/packages/tgui/interfaces/MassSpec.tsx new file mode 100644 index 0000000000000..672157ad8d011 --- /dev/null +++ b/tgui/packages/tgui/interfaces/MassSpec.tsx @@ -0,0 +1,456 @@ +import { round } from 'common/math'; +import { BooleanLike } from 'common/react'; + +import { useBackend } from '../backend'; +import { + Box, + Button, + Dimmer, + Icon, + Section, + Slider, + Table, +} from '../components'; +import { Window } from '../layouts'; + +type Reagent = { + name: string; + volume: number; + mass: number; + purity: number; + type: string; + log: string; +}; + +type Beaker = { + currentVolume: number; + maxVolume: number; + contents: Reagent[]; +}; + +type Data = { + lowerRange: number; + upperRange: number; + processing: BooleanLike; + eta: number; + graphUpperRange: number; + peakHeight: number; + beaker1: Beaker; + beaker2: Beaker; +}; + +const GRAPH_MAX_WIDTH = 1060; +const GRAPH_MAX_HEIGHT = 250; + +export const MassSpec = (props) => { + const { act, data } = useBackend(); + const { + processing, + lowerRange, + upperRange, + graphUpperRange, + eta, + peakHeight, + beaker1, + beaker2, + } = data; + + const centerValue = (lowerRange + upperRange) / 2; + const beaker_1_has_contents = beaker1?.contents?.length > 0; + + return ( + + + {!!processing && ( + + + {' Purifying... ' + round(eta, 0) + 's'} + + )} +
act('activate')} + > + Start + + } + > + {(beaker_1_has_contents && ( + + )) || Please insert an input beaker with reagents!} +
+ +
+ { + + {beaker1.currentVolume} / {beaker1.maxVolume} units + + } + + + ) + } + > + + {!!beaker_1_has_contents && ( + {'Eta of selection: ' + round(eta, 0) + ' seconds'} + )} +
+
+ { + + {beaker2.currentVolume} / {beaker2.maxVolume} units + + } + + + ) + } + > + +
+
+
+ ); +}; + +type ProfileProps = { + lowerRange: number; + upperRange: number; + beaker: Beaker; +}; + +const BeakerMassProfile = (props: ProfileProps) => { + const { lowerRange, upperRange, beaker } = props; + + return ( + + {(!beaker && No beaker loaded.) || + (beaker.contents.length === 0 && ( + Beaker is empty. + )) || ( + + + + Reagent + + + Mass + + + Volume + + + Purity + + + Type + + + Status + + + {beaker.contents.map((reagent) => { + const selected = + reagent.mass >= lowerRange && reagent.mass <= upperRange; + const color = reagent.type === 'Inverted' ? '#b60046' : '#3cf096'; + + return ( + + + {reagent.name} + + + {reagent.mass} + + + {reagent.volume} + + + {`${reagent.purity}%`} + + + ▮{reagent.type} + + {{reagent.log}} + + ); + })} +
+ )} +
+ ); +}; + +type SpectroscopyProps = { + lowerRange: number; + centerValue: number; + upperRange: number; + graphUpperRange: number; + maxAbsorbance: number; + reagentPeaks: Reagent[]; +}; + +const MassSpectroscopy = (props: SpectroscopyProps) => { + const { act } = useBackend(); + const { + lowerRange, + centerValue, + upperRange, + graphUpperRange, + maxAbsorbance, + reagentPeaks = [], + } = props; + + const graphLowerRange = 0; + const deltaRange = graphUpperRange - graphLowerRange; + const graphIncrement = deltaRange * 0.2; + + const base_line = GRAPH_MAX_HEIGHT * 0.85; + const base_width = GRAPH_MAX_WIDTH - 123; + const x_scale = base_width / GRAPH_MAX_WIDTH; + const y_scale = base_line / GRAPH_MAX_HEIGHT; + + return ( + + + {/* x axis*/} + + + Mass (G) + + + {graphLowerRange} + + + {round(graphLowerRange + graphIncrement, 1)} + + + {round(graphLowerRange + graphIncrement * 2, 1)} + + + {round(graphLowerRange + graphIncrement * 3, 1)} + + + {round(graphLowerRange + graphIncrement * 4, 1)} + + + {graphUpperRange} + + + + + {/* y axis*/} + + + 0 + + + {round(maxAbsorbance * 0.2, 1)} + + + {round(maxAbsorbance * 0.4, 1)} + + + {round(maxAbsorbance * 0.6, 1)} + + + {round(maxAbsorbance * 0.8, 1)} + + + {round(maxAbsorbance, 1)} + + + + Absorbance (AU) + + + + {/* Graph */} + + {reagentPeaks.map((peak) => ( + <> + {/* Triangle peak */} + + + {/* Background */} + + + ))} + + + + {/* Sliders */} + round(value, 2)} + width={(centerValue / graphUpperRange) * base_width + 'px'} + value={lowerRange} + minValue={graphLowerRange} + maxValue={centerValue} + color={'invisible'} + onDrag={(e, value) => + act('leftSlider', { + value: value, + }) + } + /> + round(value, 2)} + step={graphUpperRange / base_width} + width={base_width - (centerValue / graphUpperRange) * base_width + 'px'} + value={upperRange} + minValue={centerValue} + maxValue={graphUpperRange} + color={'invisible'} + onDrag={(e, value) => + act('rightSlider', { + value: value, + }) + } + /> + round(value, 2)} + width={base_width + 'px'} + minValue={graphLowerRange + 1} + maxValue={graphUpperRange - 1} + color={'invisible'} + onDrag={(e, value) => + act('centerSlider', { + value: value, + }) + } + /> + + ); +}; diff --git a/tgui/packages/tgui/interfaces/MatMarket.tsx b/tgui/packages/tgui/interfaces/MatMarket.tsx index 41b25dedb0021..1f6d69c8d69e3 100644 --- a/tgui/packages/tgui/interfaces/MatMarket.tsx +++ b/tgui/packages/tgui/interfaces/MatMarket.tsx @@ -1,14 +1,23 @@ +import { sortBy } from 'common/collections'; import { BooleanLike } from 'common/react'; import { toTitleCase } from 'common/string'; import { useBackend } from '../backend'; -import { Button, Modal, Section, Stack } from '../components'; +import { + Button, + Collapsible, + Modal, + NoticeBox, + Section, + Stack, +} from '../components'; import { formatMoney } from '../format'; import { Window } from '../layouts'; type Material = { name: string; quantity: number; + rarity: number; trend: string; price: number; threshold: number; @@ -24,6 +33,7 @@ type Data = { materials: Material[]; catastrophe: BooleanLike; CARGO_CRATE_VALUE: number; + updateTime: number; }; export const MatMarket = (props) => { @@ -66,24 +76,35 @@ export const MatMarket = (props) => { ) } > - Buy orders for material sheets placed here will be ordered on the next - cargo shipment. -

- To sell materials, please insert sheets or similar stacks of - materials. All minerals sold on the market directly are subject to an - 20% market fee. To prevent market manipulation, all registered traders - can buy a total of 10 full stacks of materials at a time. -

- All new purchases will include the cost of the shipped crate, - which may be recycled afterwards. + + + Buy orders for material sheets placed here will be ordered on the + next cargo shipment. +

+ To sell materials, please insert sheets or similar stacks of + materials. All minerals sold on the market directly are subject to + an 20% market fee. To prevent market manipulation, all registered + traders can buy a total of 10 full stacks of materials at a time. +

+ All new purchases will include the cost of the shipped crate, + which may be recycled afterwards. +
+
- + Current Credit Balance: {formatMoney(creditBalance)} cr. - + Current Order Cost: {formatMoney(orderBalance)} cr. + 150 ? 'green' : '#ad7526'} + > + {Math.round(data.updateTime / 10)} seconds until next + update +
- {materials.map((material, i) => ( -
- - - - - {toTitleCase(material.name)} - - - - Trading at {formatMoney(material.price)} cr. - - {material.price < material.threshold ? ( - - Material price critical! -
Trading temporarily suspended. + {sortBy((tempmat: Material) => tempmat.rarity)(materials).map( + (material, i) => ( +
+ + + + + {toTitleCase(material.name)} - ) : ( - - {material.quantity || 'Zero'} sheets of{' '} - {material.name} trading.{' '} - {material.requested || 'Zero'} sheets ordered. + + + Trading at {formatMoney(material.price)} cr. - )} + {material.price < material.threshold ? ( + + Material price critical! +
Trading temporarily suspended. +
+ ) : ( + + {material.quantity || 'Zero'} sheets of{' '} + {material.name} trading.{' '} + {material.requested || 'Zero'} sheets ordered. + + )} - + {toTitleCase(material.name)} is trending{' '} + {material.trend}. + +
+
+ + - - - - - - {material.requested > 0 && ( - x {material.requested} - )} -
-
- ))} + Buy 1 + + + + + +
+ {material.requested > 0 && ( + x {material.requested} + )} +
+
+ ), + )} ); diff --git a/tgui/packages/tgui/interfaces/NtosRestock.tsx b/tgui/packages/tgui/interfaces/NtosRestock.tsx new file mode 100644 index 0000000000000..055dfda6e4e04 --- /dev/null +++ b/tgui/packages/tgui/interfaces/NtosRestock.tsx @@ -0,0 +1,12 @@ +import { NtosWindow } from '../layouts'; +import { RestockTracker } from './RestockTracker'; + +export const NtosRestock = (props) => { + return ( + + + + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/NtosVirtualPet.tsx b/tgui/packages/tgui/interfaces/NtosVirtualPet.tsx new file mode 100644 index 0000000000000..7b61f2b566e54 --- /dev/null +++ b/tgui/packages/tgui/interfaces/NtosVirtualPet.tsx @@ -0,0 +1,527 @@ +import { BooleanLike } from 'common/react'; +import { capitalize } from 'common/string'; +import { useState } from 'react'; + +import { useBackend } from '../backend'; +import { + Box, + Button, + Dropdown, + Flex, + Image, + Input, + LabeledList, + ProgressBar, + Section, + Stack, + Tabs, +} from '../components'; +import { NtosWindow } from '../layouts'; + +type Data = { + currently_summoned: BooleanLike; + pet_state: string; + hunger: number; + current_exp: number; + steps_counter: number; + required_exp: number; + happiness: number; + pet_area: string; + maximum_happiness: number; + maximum_hunger: number; + level: number; + preview_icon: string; + pet_color: string; + pet_hat: string; + pet_name: string; + selected_area: string; + can_reroll: BooleanLike; + pet_gender: string; + in_dropzone: BooleanLike; + can_summon: BooleanLike; + can_alter_appearance: BooleanLike; + possible_emotes: string[]; + pet_state_icons: Pet_State_Icons[]; + hat_selections: Hat_Selections[]; + possible_colors: Possible_Colors[]; + pet_updates: Pet_Updates[]; +}; + +type Pet_State_Icons = { + name: string; + icon: string; +}; + +type Hat_Selections = { + hat_id: string; + hat_name: string; +}; + +type Possible_Colors = { + color_name: string; + color_value: string; +}; + +type Pet_Updates = { + update_id: number; + update_name: string; + update_picture: string; + update_message: string; + update_likers: number; + update_already_liked: BooleanLike; +}; + +enum Tab { + Stats, + Customization, + Updates, + Tricks, +} + +enum PetGender { + male = 'male', + female = 'female', + neuter = 'neuter', +} + +export const NtosVirtualPet = (props) => { + const [tab, setTab] = useState(Tab.Stats); + + return ( + + + + setTab(Tab.Stats)} + > + Stats + + setTab(Tab.Customization)} + > + Customization + + setTab(Tab.Updates)} + > + Pet Updates + + setTab(Tab.Tricks)} + > + Tricks + + + {tab === Tab.Stats && } + {tab === Tab.Customization && } + {tab === Tab.Updates && } + {tab === Tab.Tricks && } + + + ); +}; + +const Stats = (props) => { + const { act, data } = useBackend(); + const { + currently_summoned, + pet_state, + hunger, + current_exp, + required_exp, + happiness, + maximum_happiness, + maximum_hunger, + level, + pet_area, + steps_counter, + selected_area, + can_reroll, + can_summon, + in_dropzone, + } = data; + return ( + <> +
+ + + + + + + Current Level: {level} + + Happiness: + + + + Exp Progress: + + + + Hunger: + + + + + +
+
+ + {pet_area} + + + + +
+ + +
+ + {selected_area} + + {(in_dropzone && ( + + )) || ( + + )} + + +
+
+ +
+ {' '} + Steps: {steps_counter} +
+
+
+ + ); +}; + +const PetTricks = (props) => { + const { act, data } = useBackend(); + const { possible_emotes } = data; + const [sequences, setSequences] = useState(['none', 'none', 'none', 'none']); + const [TrickName, setTrickName] = useState('Trick'); + + const UpdateSequence = (Index: number, Trick: string) => { + const NewSequence = [...sequences]; + NewSequence[Index] = Trick; + setSequences(NewSequence); + }; + + return ( +
setTrickName(value)} + > + Rename Trick + + } + > + + {sequences.map((sequence, index) => ( + + UpdateSequence(index, selected)} + /> + + ))} + + +
+ ); +}; + +const Customization = (props) => { + const { act, data } = useBackend(); + const { + preview_icon, + hat_selections = [], + possible_colors = [], + pet_hat, + pet_color, + pet_name, + pet_gender, + can_alter_appearance, + } = data; + + const hatSelectionList = {}; + for (const index in hat_selections) { + const hat = hat_selections[index]; + hatSelectionList[hat.hat_name] = hat; + } + + const possibleColorList = {}; + for (const index in possible_colors) { + const color = possible_colors[index]; + possibleColorList[color.color_name] = color; + } + + const [selectedHat, setSelectedHat] = useState(hatSelectionList[pet_hat]); + const [selectedGender, setSelectedGender] = useState(pet_gender); + const [selectedName, setSelectedName] = useState(pet_name); + const [selectedColor, setSelectedColor] = useState( + possibleColorList[pet_color], + ); + return ( + <> +
+ +
+ + +
+ setSelectedName(value)} + /> +
+
+ +
+ { + return selected_hat.hat_name; + })} + onSelected={(selected) => + setSelectedHat(hatSelectionList[selected]) + } + /> +
+
+
+ + +
+ { + return possible_color.color_name; + })} + onSelected={(selected) => + setSelectedColor(possibleColorList[selected]) + } + /> +
+
+ +
+ + +
+
+
+
+ +
+ + ); +}; + +const AllPetUpdates = (props) => { + const { act, data } = useBackend(); + const { pet_updates } = data; + + return ( +
+ + {pet_updates.map((update) => ( + + + + + + + {update.update_name.substring(0, 6)} + + + + + + + + {update.update_name.substring(0, 6)} {update.update_message} + + + + + + ))} + +
+ ); +}; + +const PetIcon = (props) => { + const { data } = useBackend(); + const { pet_state_icons = [] } = data; + const { our_pet_state } = props; + + let icon_display = pet_state_icons.find( + (pet_icon) => pet_icon.name === our_pet_state, + ); + + if (!icon_display) { + return null; + } + + return ( + + + + + {capitalize(our_pet_state)} + + ); +}; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/spy.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/spy.ts new file mode 100644 index 0000000000000..395baf8791504 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/spy.ts @@ -0,0 +1,24 @@ +import { multiline } from 'common/string'; + +import { Antagonist, Category } from '../base'; + +const Spy: Antagonist = { + key: 'spy', + name: 'Spy', + description: [ + multiline` + Your mission, should you choose to accept it: Infiltrate Space Station 13. + Disguise yourself as a member of their crew and steal vital equipment. + Should you be caught or killed, your employer will disavow any knowledge + of your actions. Good luck agent. + `, + + multiline` + Complete Spy Bounties to earn rewards from your employer. + Use these rewards to sow chaos and mischief! + `, + ], + category: Category.Roundstart, +}; + +export default Spy; diff --git a/tgui/packages/tgui/interfaces/QuantumConsole.tsx b/tgui/packages/tgui/interfaces/QuantumConsole.tsx index dca8f2dbf1554..7d64056307cd6 100644 --- a/tgui/packages/tgui/interfaces/QuantumConsole.tsx +++ b/tgui/packages/tgui/interfaces/QuantumConsole.tsx @@ -50,6 +50,7 @@ type Domain = { difficulty: number; id: string; is_modular: BooleanLike; + has_secondary_objectives: BooleanLike; name: string; reward: number | string; }; @@ -228,7 +229,16 @@ const AccessView = (props) => { const DomainEntry = (props: DomainEntryProps) => { const { - domain: { cost, desc, difficulty, id, is_modular, name, reward }, + domain: { + cost, + desc, + difficulty, + id, + is_modular, + has_secondary_objectives, + name, + reward, + }, } = props; const { act, data } = useBackend(); if (!isConnected(data)) { @@ -268,6 +278,9 @@ const DomainEntry = (props: DomainEntryProps) => { <> {name} {!!is_modular && name !== '???' && } + {!!has_secondary_objectives && name !== '???' && ( + + )} } > @@ -275,6 +288,7 @@ const DomainEntry = (props: DomainEntryProps) => { {desc} {!!is_modular && ' (Modular)'} + {!!has_secondary_objectives && ' (Secondary Objective Available)'} diff --git a/tgui/packages/tgui/interfaces/RestockTracker.jsx b/tgui/packages/tgui/interfaces/RestockTracker.jsx new file mode 100644 index 0000000000000..236f486069cb5 --- /dev/null +++ b/tgui/packages/tgui/interfaces/RestockTracker.jsx @@ -0,0 +1,86 @@ +import { sortBy } from 'common/collections'; +import { round } from 'common/math'; + +import { useBackend } from '../backend'; +import { ColorBox, ProgressBar, Section, Stack } from '../components'; +import { Window } from '../layouts'; + +export const Restock = (props) => { + return ( + + + + + + ); +}; + +export const RestockTracker = (props) => { + const { data } = useBackend(); + const vending_list = sortBy((vend) => vend.percentage)( + data.vending_list ?? [], + ); + return ( +
+ + + + Vending Name + + + Location + + + Stock % + + + Credits stored + + +
+ {vending_list?.map((vend) => ( + + + {vend.name} + + + {vend.location} + + 75 + ? 'left' + : vend.percentage > 45 + ? 'right' + : 'center' + } + > + + {round(vend.percentage, 0.01)} + + + 50 ? 'good' : 'bad'} + > + 50 ? 'good' : 'bad'} mr={'5%'} /> + {vend.credits} + + + ))} +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/SlotMachine.tsx b/tgui/packages/tgui/interfaces/SlotMachine.tsx new file mode 100644 index 0000000000000..6d6d464f9131e --- /dev/null +++ b/tgui/packages/tgui/interfaces/SlotMachine.tsx @@ -0,0 +1,151 @@ +import { useBackend } from '../backend'; +import { Button, Icon, Section } from '../components'; +import { Window } from '../layouts'; + +type IconInfo = { + value: number; + colour: string; + icon_name: string; +}; + +type BackendData = { + icons: IconInfo[]; + state: any[]; + balance: number; + working: boolean; + money: number; + cost: number; + plays: number; + jackpots: number; + jackpot: number; + paymode: number; +}; + +type SlotsTileProps = { + icon: string; + color?: string; + background?: string; +}; + +type SlotsReelProps = { + reel: IconInfo[]; +}; + +const pluralS = (amount: number) => { + return amount === 1 ? '' : 's'; +}; + +const SlotsReel = (props: SlotsReelProps) => { + const { reel } = props; + return ( +
+ {reel.map((slot, i) => ( + + ))} +
+ ); +}; + +const SlotsTile = (props: SlotsTileProps) => { + return ( +
+ +
+ ); +}; + +export const SlotMachine = (props) => { + const { act, data } = useBackend(); + // icons: The list of possible icons, including colour and name + // backendState: the current state of the slots according to the backend + const { + plays, + jackpots, + money, + cost, + state, + balance, + jackpot, + working: rolling, + paymode, + } = data; + + return ( + +
+
+

+ Only {cost} credit{pluralS(cost)} for a chance to win big! +

+

+ Available prize money:{' '} + + {money} credit{pluralS(money)} + {' '} +

+ {paymode === 1 && ( +

+ Current jackpot:{' '} + + {money + jackpot} credit{pluralS(money + jackpot)}! + +

+ )} +

+ So far people have spun{' '} + + {plays} time{pluralS(plays)}, + {' '} + and won{' '} + + {jackpots} jackpot{pluralS(jackpots)}! + +

+
+
+
+ {state.map((reel, i) => { + return ; + })} +
+
+ +
+ Balance: {balance} +
+ +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/SpyUplink.tsx b/tgui/packages/tgui/interfaces/SpyUplink.tsx new file mode 100644 index 0000000000000..87735c19ff701 --- /dev/null +++ b/tgui/packages/tgui/interfaces/SpyUplink.tsx @@ -0,0 +1,122 @@ +import { BooleanLike } from 'common/react'; + +import { useBackend } from '../backend'; +import { BlockQuote, Box, Dimmer, Icon, Section, Stack } from '../components'; +import { Window } from '../layouts'; + +type Bounty = { + name: string; + help: string; + difficulty: string; + reward: string; + claimed: BooleanLike; + can_claim: BooleanLike; +}; + +type Data = { + time_left: number; + bounties: Bounty[]; +}; + +const difficulty_to_color = { + easy: 'good', + medium: 'average', + hard: 'bad', +}; + +const BountyDimmer = (props: { text: string; color: string }) => { + return ( + + + + + + + {props.text} + + + + ); +}; + +const BountyDisplay = (props: { bounty: Bounty }) => { + const { bounty } = props; + + return ( +
+ {!!bounty.claimed && } + {!bounty.can_claim && !bounty.claimed && ( + + )} + + + + {bounty.name} + + + +
{bounty.help}
+
+ Reward: {bounty.reward} +
+
+ ); +}; + +// Formats a number of deciseconds into a string minutes:seconds +const format_deciseconds = (deciseconds: number) => { + const seconds = Math.floor(deciseconds / 10); + const minutes = Math.floor(seconds / 60); + + const seconds_left = seconds % 60; + const minutes_left = minutes % 60; + + const seconds_string = seconds_left.toString().padStart(2, '0'); + const minutes_string = minutes_left.toString().padStart(2, '0'); + + return `${minutes_string}:${seconds_string}`; +}; + +export const SpyUplink = () => { + const { data } = useBackend(); + const { bounties, time_left } = data; + + return ( + + +
+ Time until refresh: {format_deciseconds(time_left)} + + } + > + + + {bounties.map((bounty) => ( + + + + ))} + + +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/common/JobToIcon.ts b/tgui/packages/tgui/interfaces/common/JobToIcon.ts index 98369c7902827..4d6410fc72c8c 100644 --- a/tgui/packages/tgui/interfaces/common/JobToIcon.ts +++ b/tgui/packages/tgui/interfaces/common/JobToIcon.ts @@ -32,6 +32,7 @@ export const JOB2ICON = { Geneticist: 'dna', 'Head of Personnel': 'dog', 'Head of Security': 'user-shield', + 'Big Brother': 'eye', Janitor: 'soap', Lawyer: 'gavel', 'Medical Doctor': 'staff-snake', diff --git a/tools/UpdatePaths/Scripts/81465_puzzle machinery.txt b/tools/UpdatePaths/Scripts/81465_puzzle machinery.txt new file mode 100644 index 0000000000000..c6514417c60df --- /dev/null +++ b/tools/UpdatePaths/Scripts/81465_puzzle machinery.txt @@ -0,0 +1,2 @@ +/obj/machinery/puzzle_button/@SUBTYPES : /obj/machinery/puzzle/button/@SUBTYPES{@OLD} +/obj/machinery/puzzle_keycardpad/@SUBTYPES : /obj/machinery/puzzle/keycardpad/@SUBTYPES{@OLD} \ No newline at end of file diff --git a/tools/UpdatePaths/Scripts/81839_webs.txt b/tools/UpdatePaths/Scripts/81839_webs.txt new file mode 100644 index 0000000000000..10341bc2321b3 --- /dev/null +++ b/tools/UpdatePaths/Scripts/81839_webs.txt @@ -0,0 +1,3 @@ +/obj/structure/spider/solid : /obj/structure/spider/stickyweb/sealed/tough{@OLD} +/obj/structure/spider/sticky : /obj/structure/spider/stickyweb/very_sticky{@OLD} +/obj/structure/spider/reflector : /obj/structure/spider/stickyweb/sealed/reflector{@OLD} diff --git a/tools/ci/od_lints.dm b/tools/ci/od_lints.dm new file mode 100644 index 0000000000000..e339f5acb2d72 --- /dev/null +++ b/tools/ci/od_lints.dm @@ -0,0 +1,33 @@ +//1000-1999 +#pragma FileAlreadyIncluded error +#pragma MissingIncludedFile error +#pragma MisplacedDirective error +#pragma UndefineMissingDirective error +#pragma DefinedMissingParen error +#pragma ErrorDirective error +#pragma WarningDirective warning +#pragma MiscapitalizedDirective error + +//2000-2999 +#pragma SoftReservedKeyword error +#pragma DuplicateVariable error +#pragma DuplicateProcDefinition error +#pragma TooManyArguments error +#pragma PointlessParentCall error +#pragma PointlessBuiltinCall error +#pragma SuspiciousMatrixCall error +#pragma FallbackBuiltinArgument error +#pragma MalformedRange error +#pragma InvalidRange error +#pragma InvalidSetStatement error +#pragma InvalidOverride error +#pragma DanglingVarType error +#pragma MissingInterpolatedExpression error +#pragma AmbiguousResourcePath error + +//3000-3999 +#pragma EmptyBlock error +#pragma EmptyProc disabled +#pragma UnsafeClientAccess disabled +#pragma SuspiciousSwitchCase error +#pragma AssignmentInConditional error diff --git a/tools/tts/tts-api/Dockerfile b/tools/tts/tts-api/Dockerfile index 482cda7bae36e..a317b4ac0d993 100644 --- a/tools/tts/tts-api/Dockerfile +++ b/tools/tts/tts-api/Dockerfile @@ -24,6 +24,8 @@ SHELL ["conda", "run", "-n", "intel", "/bin/bash", "-c"] # Setup python requirements and install the TTS python module into the new intel anaconda environment. RUN pip install Flask &&\ pip install waitress &&\ + pip install pysbd &&\ + pip install pydub &&\ pip cache purge COPY . /root diff --git a/tools/tts/tts-api/tts-api.py b/tools/tts/tts-api/tts-api.py index e1a5880da5bc2..aae0201287176 100644 --- a/tools/tts/tts-api/tts-api.py +++ b/tools/tts/tts-api/tts-api.py @@ -15,7 +15,7 @@ segmenter = pysbd.Segmenter(language="en", clean=True) radio_starts = ["./on1.wav", "./on2.wav"] radio_ends = ["./off1.wav", "./off2.wav", "./off3.wav", "./off4.wav"] -authorization_token = os.getenv("TTS_AUTHORIZATION_TOKEN", "vote_goof_2024") +authorization_token = os.getenv("TTS_AUTHORIZATION_TOKEN", "coolio") def hhmmss_to_seconds(string): new_time = 0 separated_times = string.split(":")