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
"
- for(var/ckey in team_keys[team])
- var/player_status = "Not Present"
- var/mob/M = get_mob_by_key(ckey)
- if(M)
- //Should define waiting room upper/lower corner and check if they're there instead of generic live/dead check
- if(isobserver(M))
- player_status = "Ghosted"
- else
- player_status = M.stat == DEAD ? "Dead" : "Alive"
- dat += "
[ckey] - [player_status] - "
- dat += "FLW"
- dat += "Remove"
- //Add more per player features here
- dat += "
"
- 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 = {"
"}
-
- 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 "
"
+
+ 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 "
- 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.
+
+ );
+};
+
+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!
+