diff --git a/.github/actions/restore_or_install_byond/action.yml b/.github/actions/restore_or_install_byond/action.yml
new file mode 100644
index 0000000000000..a4b9ce9da6d8b
--- /dev/null
+++ b/.github/actions/restore_or_install_byond/action.yml
@@ -0,0 +1,51 @@
+# This is a reusable workflow to restore BYOND from a cache, or to install it otherwise.
+name: Restore or Install BYOND
+description: Attempts to restore a specified BYOND version from cache; if it can't, it installs it.
+
+inputs:
+ major:
+ description: "The major BYOND version to install. Defaults to the BYOND_MAJOR specified in `dependencies.sh`."
+ required: false
+ type: string
+ minor:
+ description: "The minor BYOND version to install. Defaults to the BYOND_MINOR specified in `dependencies.sh`."
+ required: false
+ type: string
+
+runs:
+ using: composite
+ steps:
+ - name: Configure BYOND version from inputs
+ if: ${{ inputs.major }}
+ shell: bash
+ run: |
+ echo "BYOND_MAJOR=${{ inputs.major }}" >> $GITHUB_ENV
+ echo "BYOND_MINOR=${{ inputs.minor }}" >> $GITHUB_ENV
+ - name: Configure BYOND version from dependencies.sh
+ if: ${{ !inputs.major }}
+ shell: bash
+ run: |
+ source dependencies.sh
+ echo "BYOND_MAJOR=$BYOND_MAJOR" >> $GITHUB_ENV
+ echo "BYOND_MINOR=$BYOND_MINOR" >> $GITHUB_ENV
+
+ # The use of `actions/cache/restore` and `actions/cache/save` here is deliberate, as we want to
+ # save the BYOND install to a cache as early as possible. If we used just `actions/cache`, it
+ # would only attempt to save the cache at the end of a job. This ensures that if a workflow run
+ # is cancelled, we already have a cache to restore from.
+ - name: Restore BYOND cache
+ id: restore_byond_cache
+ uses: actions/cache/restore@v4
+ with:
+ path: ~/BYOND
+ key: ${{ runner.os }}-byond-${{ env.BYOND_MAJOR }}-${{ env.BYOND_MINOR }}
+ - name: Install BYOND
+ if: ${{ !steps.restore_byond_cache.outputs.cache-hit }}
+ shell: bash
+ run: bash tools/ci/install_byond.sh
+ - name: Save BYOND cache
+ if: ${{ !steps.restore_byond_cache.outputs.cache-hit }}
+ uses: actions/cache/save@v4
+ with:
+ path: ~/BYOND
+ key: ${{ steps.restore_byond_cache.outputs.cache-primary-key }}
diff --git a/.github/workflows/autowiki.yml b/.github/workflows/autowiki.yml
index 740b5d7d9eb21..5e10274a719cf 100644
--- a/.github/workflows/autowiki.yml
+++ b/.github/workflows/autowiki.yml
@@ -21,12 +21,9 @@ jobs:
- name: Checkout
if: steps.secrets_set.outputs.SECRETS_ENABLED
uses: actions/checkout@v4
- - name: Restore BYOND cache
+ - name: Install BYOND
if: steps.secrets_set.outputs.SECRETS_ENABLED
- uses: actions/cache@v4
- with:
- path: ~/BYOND
- key: ${{ runner.os }}-byond-${{ hashFiles('dependencies.sh') }}
+ uses: ./.github/actions/restore_or_install_byond
- name: Install rust-g
if: steps.secrets_set.outputs.SECRETS_ENABLED
run: |
@@ -34,7 +31,6 @@ jobs:
- name: Compile and generate Autowiki files
if: steps.secrets_set.outputs.SECRETS_ENABLED
run: |
- bash tools/ci/install_byond.sh
source $HOME/BYOND/byond/bin/byondsetup
tools/build/build --ci autowiki
- name: Run Autowiki
diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml
index decbe00d845bc..2b643cd4cd35f 100644
--- a/.github/workflows/ci_suite.yml
+++ b/.github/workflows/ci_suite.yml
@@ -150,14 +150,10 @@ jobs:
steps:
- uses: actions/checkout@v4
- - name: Restore BYOND cache
- uses: actions/cache@v4
- with:
- path: ~/BYOND
- key: ${{ runner.os }}-byond-${{ hashFiles('dependencies.sh') }}
+ - name: Restore BYOND from Cache
+ uses: ./.github/actions/restore_or_install_byond
- name: Compile All Maps
run: |
- bash tools/ci/install_byond.sh
source $HOME/BYOND/byond/bin/byondsetup
tools/build/build --ci dm -DCIBUILDING -DCITESTING -DALL_MAPS
- name: Check client Compatibility
@@ -167,7 +163,7 @@ jobs:
max-required-client-version: ${{needs.collect_data.outputs.max_required_byond_client}}
collect_data:
- name: Collect data for other tasks
+ name: Collect data and setup caches for other tasks
needs: start_gate
runs-on: ubuntu-22.04
timeout-minutes: 5
@@ -195,6 +191,8 @@ jobs:
#the regex here does not filter out non-numbers because error messages about no input are less helpful then error messages about bad input (which includes the bad input)
run: |
echo "max_required_byond_client=$(grep -Ev '^[[:blank:]]{0,}#{1,}|^[[:blank:]]{0,}$' .github/max_required_byond_client.txt | tail -n1)" >> $GITHUB_OUTPUT
+ - name: Set up BYOND cache
+ uses: ./.github/actions/restore_or_install_byond
run_all_tests:
name: Integration Tests
diff --git a/.github/workflows/run_integration_tests.yml b/.github/workflows/run_integration_tests.yml
index 3485b90d47489..73b263914c2ca 100644
--- a/.github/workflows/run_integration_tests.yml
+++ b/.github/workflows/run_integration_tests.yml
@@ -31,11 +31,6 @@ jobs:
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
steps:
- uses: actions/checkout@v4
- - name: Restore BYOND cache
- uses: actions/cache@v4
- with:
- path: ~/BYOND
- key: ${{ runner.os }}-byond-${{ hashFiles('dependencies.sh') }}
- name: Setup database
run: |
sudo systemctl start mysql
@@ -49,15 +44,14 @@ jobs:
- name: Install dreamluau
run: |
bash tools/ci/install_dreamluau.sh
- - name: Configure version
- run: |
- echo "BYOND_MAJOR=${{ inputs.major }}" >> $GITHUB_ENV
- echo "BYOND_MINOR=${{ inputs.minor }}" >> $GITHUB_ENV
- if: ${{ inputs.major }}
+ - name: Restore BYOND from Cache
+ uses: ./.github/actions/restore_or_install_byond
+ with:
+ major: ${{ inputs.major }}
+ minor: ${{ inputs.minor }}
- name: Compile Tests
id: compile_tests
run: |
- bash tools/ci/install_byond.sh
source $HOME/BYOND/byond/bin/byondsetup
tools/build/build --ci dm -DCIBUILDING -DANSICOLORS -Werror -ITG0001 -I"loop_checks"
- name: Run Tests
diff --git a/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_plasma_facility.dmm b/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_plasma_facility.dmm
index 0bfcc133f0c98..e5f7305eba078 100644
--- a/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_plasma_facility.dmm
+++ b/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_plasma_facility.dmm
@@ -338,7 +338,7 @@
/obj/structure/railing/corner{
dir = 4
},
-/mob/living/simple_animal/hostile/mimic/crate,
+/mob/living/basic/mimic/crate,
/turf/open/floor/plating/snowed/icemoon,
/area/icemoon/underground/explored)
"fO" = (
@@ -2073,7 +2073,7 @@
/turf/open/floor/plating/snowed/smoothed/icemoon,
/area/icemoon/underground/explored)
"KV" = (
-/mob/living/simple_animal/hostile/mimic/crate,
+/mob/living/basic/mimic/crate,
/turf/open/floor/plating/snowed/icemoon,
/area/icemoon/underground/explored)
"KY" = (
diff --git a/_maps/RandomRuins/IceRuins/icemoon_underground_outpost31.dmm b/_maps/RandomRuins/IceRuins/icemoon_underground_outpost31.dmm
new file mode 100644
index 0000000000000..fe7878295d52c
--- /dev/null
+++ b/_maps/RandomRuins/IceRuins/icemoon_underground_outpost31.dmm
@@ -0,0 +1,4310 @@
+//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE
+"ah" = (
+/obj/effect/turf_decal/tile/brown/opposingcorners,
+/obj/structure/window/spawner/directional/east,
+/obj/structure/table,
+/obj/item/bikehorn/rubberducky,
+/turf/open/floor/iron,
+/area/ruin/outpost31/radiomap)
+"ai" = (
+/obj/effect/decal/cleanable/blood/old,
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"an" = (
+/obj/effect/decal/cleanable/blood/old,
+/mob/living/basic/fleshblob,
+/turf/open/floor/iron/white,
+/area/ruin/outpost31/medical)
+"aq" = (
+/turf/closed/wall,
+/area/ruin/outpost31/recroom)
+"au" = (
+/obj/machinery/light/small/dim/directional/east,
+/obj/effect/turf_decal/siding/white{
+ dir = 8
+ },
+/obj/effect/turf_decal/siding/white{
+ dir = 4
+ },
+/turf/open/floor/iron,
+/area/ruin/outpost31/crewquarters)
+"aM" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/structure/girder,
+/turf/open/floor/plating,
+/area/ruin/outpost31/lootroom)
+"aP" = (
+/obj/machinery/vending/boozeomat{
+ onstation = 0
+ },
+/obj/effect/mapping_helpers/broken_machine,
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/wood,
+/area/ruin/outpost31/recroom)
+"aV" = (
+/turf/closed/wall,
+/area/ruin/outpost31/kitchendiningroom)
+"bb" = (
+/obj/structure/cable,
+/obj/effect/turf_decal/tile/brown/opposingcorners,
+/turf/open/floor/iron,
+/area/ruin/outpost31/radiomap)
+"bg" = (
+/obj/structure/table/wood,
+/obj/item/storage/box/drinkingglasses,
+/obj/machinery/light/small/directional/east,
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/wood,
+/area/ruin/outpost31/recroom)
+"bp" = (
+/obj/effect/turf_decal/tile/neutral,
+/obj/machinery/airalarm/directional/east,
+/obj/effect/decal/cleanable/blood/old,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"by" = (
+/obj/effect/spawner/structure/window/reinforced,
+/obj/structure/barricade/wooden/crude/snow{
+ opacity = 1
+ },
+/turf/open/floor/plating,
+/area/ruin/outpost31/commander_room)
+"bE" = (
+/obj/structure/cable,
+/obj/effect/mob_spawn/corpse/human/damaged{
+ husk = 1;
+ burn_damage = 1000;
+ brute_damage = 0
+ },
+/obj/effect/mapping_helpers/burnt_floor,
+/obj/machinery/light_switch/directional/south,
+/obj/effect/turf_decal/siding/white,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"bW" = (
+/obj/effect/turf_decal/tile/purple/opposingcorners,
+/obj/machinery/status_display{
+ pixel_x = 32;
+ current_mode = 3;
+ current_picture = "biohazard"
+ },
+/turf/open/floor/iron/white{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"bX" = (
+/obj/structure/sink/kitchen/directional/west,
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/iron/kitchen/small,
+/area/ruin/outpost31/kitchendiningroom)
+"cd" = (
+/obj/item/reagent_containers/blood{
+ pixel_x = 4;
+ pixel_y = 0
+ },
+/obj/effect/decal/cleanable/blood/old,
+/obj/structure/sign/poster/random/directional/north,
+/turf/open/floor/iron/white/smooth_half,
+/area/ruin/outpost31/medical)
+"ce" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/structure/reagent_dispensers/watertank,
+/turf/open/floor/plating,
+/area/ruin/outpost31/lootroom)
+"cf" = (
+/obj/effect/turf_decal/tile/brown/opposingcorners,
+/obj/structure/frame/machine/secured,
+/obj/effect/decal/cleanable/glass,
+/obj/effect/mapping_helpers/broken_floor,
+/turf/open/floor/iron,
+/area/ruin/outpost31/radiomap)
+"ch" = (
+/obj/structure/closet/crate/freezer,
+/obj/item/reagent_containers/blood,
+/obj/effect/decal/cleanable/blood/old,
+/turf/open/floor/iron/white/smooth_corner,
+/area/ruin/outpost31/medical)
+"ck" = (
+/obj/structure/closet,
+/obj/item/tank/internals/plasma/full,
+/turf/open/floor/carpet/green,
+/area/ruin/outpost31/commander_room)
+"cm" = (
+/obj/machinery/power/rtg/advanced{
+ power_gen = 25000
+ },
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/machinery/light/small/dim/directional/west,
+/obj/structure/cable,
+/turf/open/floor/plating,
+/area/ruin/outpost31/kennel)
+"cp" = (
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"cu" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/turf_decal/siding/white{
+ dir = 1
+ },
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"cw" = (
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/plating,
+/area/ruin/outpost31/kennel)
+"cy" = (
+/turf/closed/indestructible/reinforced,
+/area/ruin/outpost31/medical)
+"cz" = (
+/obj/effect/turf_decal/tile/brown/opposingcorners,
+/obj/structure/window/spawner/directional/east,
+/obj/structure/table,
+/obj/effect/spawner/random/maintenance/no_decals,
+/turf/open/floor/iron,
+/area/ruin/outpost31/radiomap)
+"db" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/machinery/light/broken/directional/east,
+/obj/effect/turf_decal/tile/neutral,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"dm" = (
+/obj/effect/turf_decal/tile/purple/opposingcorners,
+/obj/machinery/status_display{
+ pixel_x = -32;
+ current_mode = 3;
+ current_picture = "biohazard"
+ },
+/turf/open/floor/iron/white{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"dt" = (
+/obj/effect/puzzle_poddoor_open{
+ id = "thingbosslootroom";
+ queue_id = "thingbosslootroom"
+ },
+/turf/closed/indestructible/reinforced,
+/area/ruin/outpost31/lootroom)
+"dH" = (
+/obj/effect/turf_decal/stripes/corner{
+ dir = 1
+ },
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"dK" = (
+/obj/structure/table,
+/obj/structure/bedsheetbin,
+/obj/machinery/duct,
+/obj/machinery/light/small/directional/west,
+/turf/open/floor/iron/freezer,
+/area/ruin/outpost31/crewquarters)
+"dP" = (
+/obj/structure/closet/emcloset,
+/turf/open/floor/iron/textured_half,
+/area/ruin/outpost31)
+"dV" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/structure/closet/crate,
+/obj/item/stack/sheet/rglass/fifty,
+/turf/open/floor/plating,
+/area/ruin/outpost31/lootroom)
+"dW" = (
+/obj/effect/turf_decal/stripes/line{
+ dir = 10
+ },
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"dZ" = (
+/obj/structure/cable,
+/obj/effect/turf_decal/siding/white{
+ dir = 8
+ },
+/obj/effect/turf_decal/siding/white{
+ dir = 4
+ },
+/obj/effect/turf_decal/siding/white/end{
+ dir = 1
+ },
+/turf/open/floor/iron,
+/area/ruin/outpost31/crewquarters)
+"eb" = (
+/obj/effect/turf_decal/stripes/line{
+ dir = 4
+ },
+/obj/effect/turf_decal/stripes/line{
+ dir = 8
+ },
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"eg" = (
+/obj/structure/table/wood,
+/obj/item/reagent_containers/cup/glass/drinkingglass/filled/half_full{
+ pixel_x = -3;
+ pixel_y = 16
+ },
+/obj/item/cigarette/cigar/premium,
+/obj/effect/turf_decal/siding/wood{
+ dir = 8
+ },
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/wood,
+/area/ruin/outpost31/recroom)
+"ep" = (
+/obj/structure/table/reinforced,
+/obj/item/flashlight/lamp,
+/obj/effect/turf_decal/siding/wood{
+ dir = 8
+ },
+/turf/open/floor/wood,
+/area/ruin/outpost31/commander_room)
+"eu" = (
+/obj/effect/turf_decal/stripes/line{
+ dir = 6
+ },
+/obj/effect/decal/cleanable/blood/old,
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"eI" = (
+/obj/effect/turf_decal/stripes/line{
+ dir = 4
+ },
+/obj/effect/decal/cleanable/blood/old,
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"eL" = (
+/obj/effect/turf_decal/siding/wood{
+ dir = 8
+ },
+/obj/machinery/door/window/left/directional/west,
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/wood,
+/area/ruin/outpost31/recroom)
+"eS" = (
+/obj/structure/extinguisher_cabinet/directional/south,
+/turf/open/floor/iron/white/smooth_half,
+/area/ruin/outpost31/medical)
+"eT" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/turf_decal/siding/white{
+ dir = 9
+ },
+/obj/effect/spawner/random/entertainment/arcade,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"fb" = (
+/obj/machinery/shower/directional/west,
+/obj/structure/curtain,
+/obj/structure/fluff/shower_drain,
+/turf/open/floor/iron/freezer,
+/area/ruin/outpost31/crewquarters)
+"fC" = (
+/obj/structure/chair/stool/directional/south,
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"fH" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/machinery/light/small/dim/directional/north,
+/turf/open/floor/iron/freezer,
+/area/ruin/outpost31/recroom)
+"fI" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/structure/extinguisher_cabinet/directional/south,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/kitchendiningroom)
+"fL" = (
+/obj/machinery/door/airlock/grunge,
+/obj/effect/mapping_helpers/airlock/autoname,
+/obj/effect/turf_decal/tile/brown/opposingcorners,
+/turf/open/floor/iron,
+/area/ruin/outpost31/radiomap)
+"fM" = (
+/obj/structure/chair/office{
+ dir = 4
+ },
+/turf/open/floor/carpet/green,
+/area/ruin/outpost31/commander_room)
+"fW" = (
+/obj/structure/chair,
+/turf/open/floor/iron/textured_half{
+ dir = 1
+ },
+/area/ruin/outpost31)
+"ge" = (
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/obj/structure/cable,
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/catwalk_floor/iron,
+/area/ruin/outpost31)
+"gi" = (
+/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer4{
+ dir = 1
+ },
+/obj/effect/decal/cleanable/blood/old,
+/turf/open/floor/iron/white,
+/area/ruin/outpost31/medical)
+"gu" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/kitchendiningroom)
+"gC" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/structure/table,
+/obj/item/organ/brain/cybernetic/ai,
+/turf/open/floor/plating,
+/area/ruin/outpost31/lootroom)
+"gM" = (
+/turf/open/floor/iron/white,
+/area/ruin/outpost31/medical)
+"gO" = (
+/obj/effect/turf_decal/stripes/line{
+ dir = 5
+ },
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"gZ" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/plating,
+/area/ruin/outpost31/kennel)
+"he" = (
+/turf/closed/wall/r_wall,
+/area/ruin/outpost31/medical)
+"hi" = (
+/obj/item/ammo_casing/spent,
+/turf/open/misc/asteroid/snow/icemoon/do_not_chasm,
+/area/icemoon/underground/explored)
+"ho" = (
+/obj/effect/turf_decal/tile/brown/opposingcorners,
+/obj/structure/table,
+/obj/item/flamethrower,
+/turf/open/floor/iron,
+/area/ruin/outpost31/radiomap)
+"hs" = (
+/obj/structure/table,
+/obj/machinery/processor,
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/machinery/light_switch/directional/north,
+/turf/open/floor/iron/kitchen/small,
+/area/ruin/outpost31/kitchendiningroom)
+"ht" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/structure/closet/crate,
+/obj/item/storage/toolbox/electrical,
+/turf/open/floor/plating,
+/area/ruin/outpost31/lootroom)
+"hD" = (
+/obj/effect/turf_decal/stripes/line{
+ dir = 8
+ },
+/obj/effect/turf_decal/stripes/corner{
+ dir = 4
+ },
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"hN" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/pod/dark,
+/area/ruin/outpost31)
+"hT" = (
+/obj/machinery/light/warm/directional/east,
+/obj/effect/turf_decal/tile/neutral,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"ih" = (
+/obj/structure/closet{
+ name = "winter gear closet"
+ },
+/obj/item/clothing/suit/hooded/wintercoat/eva,
+/obj/item/clothing/shoes/winterboots/ice_boots/eva,
+/obj/effect/turf_decal/tile/neutral,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"in" = (
+/mob/living/basic/boss/thing{
+ dir = 1
+ },
+/turf/open/indestructible/vault{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"iA" = (
+/obj/machinery/door/airlock/grunge,
+/obj/effect/mapping_helpers/airlock/autoname,
+/turf/open/floor/carpet/green,
+/area/ruin/outpost31/commander_room)
+"iH" = (
+/obj/structure/window/spawner/directional/west,
+/obj/machinery/shower/directional/north,
+/obj/structure/curtain,
+/obj/structure/fluff/shower_drain,
+/turf/open/floor/iron/freezer,
+/area/ruin/outpost31/crewquarters)
+"jl" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/structure/table,
+/obj/item/paper,
+/turf/open/floor/plating,
+/area/ruin/outpost31/lootroom)
+"jv" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/structure/chair,
+/turf/open/floor/plating,
+/area/ruin/outpost31)
+"jL" = (
+/obj/effect/decal/cleanable/blood/old,
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/iron/kitchen/small,
+/area/ruin/outpost31/kitchendiningroom)
+"jM" = (
+/obj/structure/chair{
+ dir = 1
+ },
+/turf/open/floor/iron/textured_half{
+ dir = 1
+ },
+/area/ruin/outpost31)
+"jN" = (
+/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer4{
+ dir = 8
+ },
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/pod/dark,
+/area/ruin/outpost31)
+"jP" = (
+/obj/effect/turf_decal/stripes/line{
+ dir = 8
+ },
+/obj/effect/turf_decal/stripes/line{
+ dir = 4
+ },
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"kh" = (
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"ko" = (
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/structure/cable,
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/item/ammo_casing/spent,
+/turf/open/floor/plating,
+/area/ruin/outpost31/kennel)
+"ks" = (
+/obj/effect/turf_decal/tile/brown/opposingcorners,
+/obj/structure/closet/crate/bin,
+/turf/open/floor/iron,
+/area/ruin/outpost31/radiomap)
+"kA" = (
+/obj/structure/toilet{
+ dir = 8
+ },
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/iron/freezer,
+/area/ruin/outpost31/recroom)
+"kF" = (
+/obj/effect/mob_spawn/corpse{
+ mob_type = /mob/living/basic/mining/wolf
+ },
+/turf/open/floor/wood,
+/area/ruin/outpost31/kennel)
+"kP" = (
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/obj/structure/cable,
+/turf/open/floor/pod/dark,
+/area/ruin/outpost31)
+"kX" = (
+/obj/effect/turf_decal/siding/wood{
+ dir = 8
+ },
+/mob/living/basic/fleshblob,
+/turf/open/floor/wood,
+/area/ruin/outpost31/commander_room)
+"lr" = (
+/obj/effect/turf_decal/tile/brown/opposingcorners,
+/obj/structure/table,
+/obj/item/paperwork{
+ desc = "Whatever nerd stuff they did down here.";
+ name = "map scraps";
+ pixel_x = -5;
+ pixel_y = 15
+ },
+/obj/item/pen/fountain{
+ pixel_x = -5;
+ pixel_y = -8
+ },
+/obj/item/reagent_containers/cup/glass/mug{
+ pixel_x = 9;
+ pixel_y = 0
+ },
+/turf/open/floor/iron,
+/area/ruin/outpost31/radiomap)
+"lz" = (
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/structure/cable,
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/plating,
+/area/ruin/outpost31/kennel)
+"lF" = (
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/decal/cleanable/blood/old,
+/turf/open/floor/catwalk_floor/iron,
+/area/ruin/outpost31)
+"lM" = (
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/turf/open/floor/pod/dark,
+/area/ruin/outpost31)
+"lO" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/turf_decal/tile/neutral,
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"lR" = (
+/obj/structure/table/wood,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"lT" = (
+/obj/effect/spawner/structure/window/reinforced,
+/obj/structure/barricade/wooden/crude/snow{
+ opacity = 1
+ },
+/turf/open/floor/plating,
+/area/ruin/outpost31/kitchendiningroom)
+"lU" = (
+/obj/machinery/power/apc/auto_name/directional/north,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/structure/cable,
+/turf/open/floor/iron/white/smooth_half,
+/area/ruin/outpost31/medical)
+"lW" = (
+/obj/effect/decal/cleanable/blood/old,
+/turf/open/misc/asteroid/snow/icemoon/do_not_chasm,
+/area/icemoon/underground/explored)
+"me" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/machinery/vending/snack,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/kitchendiningroom)
+"mq" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/structure/girder,
+/turf/open/floor/plating,
+/area/ruin/outpost31)
+"mO" = (
+/obj/effect/turf_decal/stripes/corner{
+ dir = 4
+ },
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"mU" = (
+/obj/machinery/door/airlock/public/glass,
+/obj/effect/turf_decal/stripes/line,
+/obj/effect/turf_decal/stripes/line{
+ dir = 1
+ },
+/obj/effect/turf_decal/tile/neutral,
+/obj/effect/mapping_helpers/airlock/autoname,
+/turf/open/floor/iron/dark,
+/area/ruin/outpost31)
+"mV" = (
+/obj/machinery/light/warm/directional/north,
+/obj/effect/turf_decal/siding/white{
+ dir = 1
+ },
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"mW" = (
+/turf/open/floor/circuit/red/off{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"ne" = (
+/turf/closed/wall/r_wall,
+/area/ruin/outpost31/commander_room)
+"nj" = (
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/obj/machinery/puzzle/keycardpad/directional/north{
+ late_initialize_pop = 1;
+ id = "thingbossentry"
+ },
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"no" = (
+/obj/structure/bed/pod{
+ dir = 4
+ },
+/turf/open/floor/wood,
+/area/ruin/outpost31/crewquarters)
+"nr" = (
+/obj/item/kirbyplants/random/dead,
+/turf/open/floor/carpet/green,
+/area/ruin/outpost31/commander_room)
+"nH" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/structure/closet/crate,
+/obj/item/storage/toolbox/mechanical,
+/obj/item/storage/toolbox/mechanical,
+/turf/open/floor/plating,
+/area/ruin/outpost31/lootroom)
+"nJ" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/machinery/light_switch/directional/north,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/kitchendiningroom)
+"nP" = (
+/obj/structure/cable,
+/turf/closed/indestructible/reinforced,
+/area/ruin/outpost31/lab)
+"nT" = (
+/obj/structure/fence/cut,
+/turf/open/floor/wood,
+/area/ruin/outpost31/kennel)
+"nV" = (
+/obj/effect/turf_decal/stripes/end{
+ dir = 1
+ },
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"oa" = (
+/turf/closed/wall/r_wall,
+/area/ruin/outpost31/recroom)
+"ob" = (
+/obj/effect/turf_decal/stripes/line,
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"og" = (
+/obj/structure/table/wood,
+/obj/effect/spawner/random/maintenance/no_decals,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"oj" = (
+/turf/open/floor/iron/freezer,
+/area/ruin/outpost31/crewquarters)
+"op" = (
+/obj/machinery/shower/directional/north,
+/obj/structure/curtain,
+/obj/structure/fluff/shower_drain,
+/turf/open/floor/iron/freezer,
+/area/ruin/outpost31/crewquarters)
+"ov" = (
+/obj/effect/spawner/structure/window/reinforced,
+/obj/structure/barricade/wooden/crude/snow{
+ opacity = 1
+ },
+/turf/open/floor/plating,
+/area/ruin/outpost31)
+"oz" = (
+/obj/machinery/door/airlock,
+/obj/effect/turf_decal/siding/wood{
+ dir = 8
+ },
+/obj/effect/mapping_helpers/airlock/autoname,
+/turf/open/floor/wood,
+/area/ruin/outpost31/crewquarters)
+"oF" = (
+/turf/open/floor/pod/dark,
+/area/ruin/outpost31)
+"oJ" = (
+/obj/structure/chair/stool/directional/east,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"oN" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/turf_decal/tile/neutral,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"oV" = (
+/obj/structure/table/wood,
+/obj/item/storage/cans/sixbeer,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"pa" = (
+/obj/item/keycard{
+ puzzle_id = "thingbossentry";
+ name = "lab keycard";
+ color = "blue"
+ },
+/obj/effect/decal/cleanable/blood/old,
+/turf/open/floor/wood,
+/area/ruin/outpost31/commander_room)
+"pd" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/structure/table,
+/obj/machinery/coffeemaker,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/kitchendiningroom)
+"pe" = (
+/obj/machinery/atmospherics/components/unary/vent_pump/on/layer2{
+ dir = 4
+ },
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"pj" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/spawner/random/food_or_drink/any_snack_or_beverage,
+/obj/structure/table,
+/obj/machinery/light/warm/dim/directional/south,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/kitchendiningroom)
+"pw" = (
+/obj/structure/closet/cabinet,
+/obj/machinery/light/small/broken/directional/east,
+/turf/open/floor/wood,
+/area/ruin/outpost31/crewquarters)
+"py" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/turf_decal/siding/white{
+ dir = 8
+ },
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"pQ" = (
+/obj/item/knife,
+/obj/effect/decal/cleanable/blood/old,
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/iron/kitchen/small,
+/area/ruin/outpost31/kitchendiningroom)
+"qa" = (
+/obj/structure/table/wood,
+/obj/effect/spawner/random/food_or_drink/booze,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"qt" = (
+/obj/machinery/door/airlock/external/glass/ruin,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi{
+ cycle_id = "o31a"
+ },
+/turf/open/floor/plating,
+/area/ruin/outpost31)
+"qz" = (
+/turf/closed/wall/r_wall,
+/area/ruin/outpost31/kennel)
+"qH" = (
+/obj/structure/closet/preopen{
+ name = "winter gear closet"
+ },
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/machinery/light/small/dim/directional/north,
+/turf/open/floor/pod/light,
+/area/ruin/outpost31)
+"qI" = (
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/obj/item/flashlight/lantern,
+/turf/open/floor/pod/light,
+/area/ruin/outpost31)
+"qK" = (
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/turf/open/floor/catwalk_floor/iron,
+/area/ruin/outpost31)
+"rb" = (
+/obj/machinery/light/small/directional/west,
+/turf/open/floor/plating/snowed/smoothed/icemoon,
+/area/ruin/outpost31)
+"rf" = (
+/obj/machinery/washing_machine,
+/turf/open/floor/iron/freezer,
+/area/ruin/outpost31/crewquarters)
+"rk" = (
+/obj/effect/turf_decal/stripes/line{
+ dir = 1
+ },
+/obj/effect/decal/cleanable/blood/old,
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"rm" = (
+/obj/machinery/light/small/dim/directional/south,
+/turf/open/floor/plating/snowed/smoothed/icemoon,
+/area/icemoon/underground/explored)
+"ru" = (
+/obj/effect/turf_decal/stripes/corner,
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"rx" = (
+/obj/effect/decal/cleanable/blood/old,
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/plating,
+/area/ruin/outpost31)
+"rB" = (
+/obj/machinery/door/airlock/grunge,
+/obj/structure/cable,
+/obj/effect/mapping_helpers/airlock/autoname,
+/turf/open/floor/iron,
+/area/ruin/outpost31/crewquarters)
+"rO" = (
+/obj/machinery/door/airlock/public/glass,
+/obj/effect/turf_decal/siding/white{
+ dir = 1
+ },
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/mapping_helpers/airlock/autoname,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/kitchendiningroom)
+"sd" = (
+/obj/structure/table/wood,
+/obj/machinery/chem_dispenser/drinks{
+ dir = 8
+ },
+/obj/effect/mapping_helpers/broken_machine,
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/wood,
+/area/ruin/outpost31/recroom)
+"sg" = (
+/obj/structure/chair/stool/directional/west,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"si" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/machinery/light/broken/directional/east,
+/turf/open/floor/plating,
+/area/ruin/outpost31)
+"sk" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/item/storage/toolbox/mechanical,
+/turf/open/floor/plating,
+/area/ruin/outpost31/kennel)
+"sm" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/structure/closet/emcloset,
+/turf/open/floor/iron/textured_half,
+/area/ruin/outpost31)
+"sB" = (
+/obj/structure/window/spawner/directional/west,
+/obj/machinery/shower/directional/south,
+/obj/structure/curtain,
+/obj/structure/fluff/shower_drain,
+/turf/open/floor/iron/freezer,
+/area/ruin/outpost31/crewquarters)
+"sF" = (
+/obj/structure/table/wood,
+/obj/item/storage/fancy/cigarettes/cigars/cohiba,
+/turf/open/floor/wood,
+/area/ruin/outpost31/commander_room)
+"sK" = (
+/turf/open/floor/plating/snowed/smoothed/icemoon,
+/area/icemoon/underground/explored)
+"sL" = (
+/obj/structure/window/spawner/directional/east,
+/obj/machinery/shower/directional/north,
+/obj/structure/curtain,
+/obj/structure/fluff/shower_drain,
+/turf/open/floor/iron/freezer,
+/area/ruin/outpost31/crewquarters)
+"sP" = (
+/turf/closed/wall,
+/area/ruin/outpost31/kennel)
+"sQ" = (
+/obj/machinery/door/airlock,
+/obj/effect/turf_decal/siding/wood{
+ dir = 4
+ },
+/obj/effect/mapping_helpers/airlock/autoname,
+/turf/open/floor/wood,
+/area/ruin/outpost31/crewquarters)
+"tj" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/structure/chair{
+ dir = 8
+ },
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/kitchendiningroom)
+"to" = (
+/obj/machinery/power/apc/auto_name/directional/north,
+/obj/structure/cable,
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/plating,
+/area/ruin/outpost31/kennel)
+"tx" = (
+/obj/structure/closet/crate/bin,
+/obj/machinery/light/broken/directional/west,
+/turf/open/floor/carpet/green,
+/area/ruin/outpost31/commander_room)
+"tC" = (
+/obj/structure/closet/secure_closet/freezer/fridge/preopen,
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/iron/kitchen/small,
+/area/ruin/outpost31/kitchendiningroom)
+"tG" = (
+/turf/open/floor/iron/textured_half,
+/area/ruin/outpost31)
+"tJ" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/item/ammo_casing/spent,
+/turf/open/floor/plating,
+/area/ruin/outpost31/kennel)
+"tS" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/item/ammo_casing/spent,
+/obj/structure/extinguisher_cabinet/directional/south,
+/turf/open/floor/plating,
+/area/ruin/outpost31/kennel)
+"tV" = (
+/obj/machinery/door/airlock/public/glass,
+/obj/effect/turf_decal/stripes/line,
+/obj/effect/turf_decal/stripes/line{
+ dir = 1
+ },
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/obj/structure/cable,
+/obj/effect/mapping_helpers/airlock/autoname,
+/turf/open/floor/iron/dark,
+/area/ruin/outpost31)
+"tX" = (
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/obj/structure/sign/poster/random/directional/west,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"tZ" = (
+/obj/structure/table,
+/obj/item/storage/medkit,
+/turf/open/floor/iron/white/smooth_half{
+ dir = 1
+ },
+/area/ruin/outpost31/medical)
+"ub" = (
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/catwalk_floor/iron,
+/area/ruin/outpost31)
+"uj" = (
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/obj/structure/cable,
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/pod/dark,
+/area/ruin/outpost31)
+"uA" = (
+/obj/structure/cable,
+/turf/open/floor/carpet/green,
+/area/ruin/outpost31/commander_room)
+"uB" = (
+/turf/closed/mineral/random/snow/underground,
+/area/template_noop)
+"uI" = (
+/obj/effect/turf_decal/stripes/line{
+ dir = 8
+ },
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"uN" = (
+/obj/machinery/door/airlock/external/ruin,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi{
+ cycle_id = "o31a"
+ },
+/turf/open/floor/pod/dark,
+/area/ruin/outpost31)
+"uR" = (
+/obj/effect/turf_decal/siding/white{
+ dir = 1
+ },
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"vn" = (
+/obj/structure/closet/preopen{
+ name = "winter gear closet"
+ },
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/pod/light,
+/area/ruin/outpost31)
+"vU" = (
+/obj/structure/cable,
+/obj/effect/turf_decal/siding/white{
+ dir = 8
+ },
+/obj/effect/turf_decal/siding/white{
+ dir = 4
+ },
+/turf/open/floor/iron,
+/area/ruin/outpost31/crewquarters)
+"vX" = (
+/obj/structure/cable,
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"wb" = (
+/obj/effect/decal/cleanable/blood/old,
+/obj/effect/turf_decal/tile/brown/opposingcorners,
+/obj/effect/mapping_helpers/broken_floor,
+/turf/open/floor/iron,
+/area/ruin/outpost31/radiomap)
+"wc" = (
+/obj/effect/turf_decal/tile/neutral,
+/obj/effect/decal/cleanable/blood/old,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"wp" = (
+/obj/structure/fence/door/opened{
+ dir = 8
+ },
+/turf/open/floor/wood,
+/area/ruin/outpost31/kennel)
+"wq" = (
+/obj/structure/cable,
+/obj/machinery/light/small/broken/directional/east,
+/obj/effect/turf_decal/siding/white{
+ dir = 8
+ },
+/obj/effect/turf_decal/siding/white{
+ dir = 4
+ },
+/turf/open/floor/iron,
+/area/ruin/outpost31/crewquarters)
+"wr" = (
+/obj/machinery/smartfridge/petri/preloaded,
+/turf/open/floor/iron/white/smooth_half{
+ dir = 1
+ },
+/area/ruin/outpost31/medical)
+"wt" = (
+/obj/effect/turf_decal/stripes/line{
+ dir = 9
+ },
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"wu" = (
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/obj/structure/cable,
+/obj/structure/girder,
+/obj/structure/barricade/wooden,
+/turf/open/floor/catwalk_floor/iron,
+/area/ruin/outpost31)
+"wx" = (
+/obj/structure/tank_holder/extinguisher,
+/turf/open/floor/iron/textured_half,
+/area/ruin/outpost31)
+"wE" = (
+/obj/effect/turf_decal/tile/brown/opposingcorners,
+/obj/structure/table,
+/obj/item/pen,
+/obj/item/pen/blue{
+ pixel_x = -8;
+ pixel_y = -3
+ },
+/obj/item/pen/red{
+ pixel_x = -1;
+ pixel_y = 9
+ },
+/obj/item/pen/fourcolor{
+ pixel_x = 9;
+ pixel_y = 8
+ },
+/turf/open/floor/iron,
+/area/ruin/outpost31/radiomap)
+"wO" = (
+/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer4{
+ dir = 8
+ },
+/obj/effect/turf_decal/tile/neutral,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"wP" = (
+/obj/structure/fence/cut/large,
+/turf/open/floor/wood,
+/area/ruin/outpost31/kennel)
+"xc" = (
+/turf/closed/wall/r_wall,
+/area/ruin/outpost31/radiomap)
+"xk" = (
+/turf/closed/wall,
+/area/ruin/outpost31/medical)
+"xo" = (
+/obj/effect/gibspawner/generic/animal{
+ virusProb = 0
+ },
+/turf/open/floor/wood,
+/area/ruin/outpost31/kennel)
+"xP" = (
+/obj/machinery/door/airlock/external/glass/ruin,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi{
+ cycle_id = "o31B"
+ },
+/turf/open/floor/pod/dark,
+/area/ruin/outpost31)
+"yo" = (
+/turf/closed/indestructible/reinforced,
+/area/ruin/outpost31/lootroom)
+"yp" = (
+/obj/effect/turf_decal/siding/white{
+ dir = 8
+ },
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"yx" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/decal/cleanable/blood/footprints,
+/turf/open/floor/iron/kitchen/small,
+/area/ruin/outpost31/kitchendiningroom)
+"yO" = (
+/obj/effect/turf_decal/stripes/line,
+/obj/effect/turf_decal/stripes/corner{
+ dir = 4
+ },
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"yQ" = (
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/turf/open/floor/pod/dark,
+/area/ruin/outpost31)
+"yW" = (
+/obj/structure/sign/warning/biohazard,
+/turf/closed/indestructible/reinforced,
+/area/ruin/outpost31/lab)
+"za" = (
+/obj/effect/turf_decal/stripes/line,
+/obj/effect/decal/cleanable/blood/old,
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"zd" = (
+/obj/machinery/door/airlock/grunge,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/obj/structure/cable,
+/obj/effect/mapping_helpers/airlock/autoname,
+/turf/open/floor/pod/dark,
+/area/ruin/outpost31)
+"ze" = (
+/obj/structure/table/optable,
+/obj/machinery/light/broken/directional/south,
+/obj/effect/decal/cleanable/blood/old,
+/obj/item/bodypart/arm/left{
+ pixel_x = 4;
+ pixel_y = 2
+ },
+/obj/item/bodypart/arm/right{
+ pixel_x = -4;
+ pixel_y = 1
+ },
+/turf/open/floor/iron/white/smooth_half,
+/area/ruin/outpost31/medical)
+"zu" = (
+/obj/effect/light_emitter{
+ set_cap = 1;
+ set_luminosity = 10
+ },
+/obj/effect/turf_decal/stripes/line{
+ dir = 1
+ },
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"zv" = (
+/turf/closed/wall/r_wall,
+/area/ruin/outpost31/crewquarters)
+"zw" = (
+/obj/effect/turf_decal/siding/blue{
+ dir = 4
+ },
+/obj/effect/turf_decal/siding/blue{
+ dir = 8
+ },
+/turf/open/floor/iron/white/textured_large,
+/area/ruin/outpost31/crewquarters)
+"zy" = (
+/obj/effect/turf_decal/stripes/corner,
+/obj/effect/turf_decal/stripes/line{
+ dir = 1
+ },
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"zz" = (
+/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer4{
+ dir = 8
+ },
+/turf/open/floor/pod/dark,
+/area/ruin/outpost31)
+"zF" = (
+/obj/machinery/duct,
+/turf/open/floor/iron/freezer,
+/area/ruin/outpost31/crewquarters)
+"zN" = (
+/obj/effect/turf_decal/stripes/corner{
+ dir = 8
+ },
+/obj/effect/turf_decal/stripes/line{
+ dir = 1
+ },
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"zO" = (
+/obj/effect/turf_decal/siding/white,
+/obj/structure/extinguisher_cabinet/directional/south,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"zY" = (
+/obj/machinery/door/airlock/external/ruin,
+/obj/effect/mapping_helpers/airlock/welded,
+/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi{
+ cycle_id = "o31d"
+ },
+/obj/structure/barricade/wooden/crude/snow{
+ opacity = 1
+ },
+/turf/open/floor/pod/dark,
+/area/ruin/outpost31)
+"Ae" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/machinery/vending/cigarette{
+ onstation = 0
+ },
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/kitchendiningroom)
+"Ai" = (
+/obj/item/kirbyplants/random/dead,
+/turf/open/floor/iron/white/smooth_corner{
+ dir = 1
+ },
+/area/ruin/outpost31/medical)
+"Al" = (
+/obj/machinery/light/broken/directional/east,
+/obj/effect/turf_decal/tile/neutral,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"Au" = (
+/obj/machinery/iv_drip,
+/obj/structure/bed/medical/anchored,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/turf/open/floor/iron/white/smooth_half,
+/area/ruin/outpost31/medical)
+"Aw" = (
+/obj/structure/closet/secure_closet{
+ req_access = list("noopen")
+ },
+/obj/item/ammo_box/c9mm,
+/turf/open/floor/carpet/green,
+/area/ruin/outpost31/commander_room)
+"AE" = (
+/obj/machinery/door/airlock/grunge,
+/obj/structure/cable,
+/obj/effect/mapping_helpers/airlock/autoname,
+/turf/open/floor/carpet/green,
+/area/ruin/outpost31/commander_room)
+"AG" = (
+/obj/machinery/door/airlock/grunge,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/structure/cable,
+/obj/effect/mapping_helpers/airlock/autoname,
+/turf/open/floor/plating,
+/area/ruin/outpost31/kennel)
+"AR" = (
+/obj/effect/turf_decal/tile/brown/opposingcorners,
+/obj/structure/table,
+/obj/item/paper_bin,
+/turf/open/floor/iron,
+/area/ruin/outpost31/radiomap)
+"Bb" = (
+/obj/machinery/light/warm/directional/south,
+/obj/effect/turf_decal/tile/neutral,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"Bc" = (
+/obj/structure/cable,
+/turf/open/floor/catwalk_floor/iron_white{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"Bg" = (
+/obj/machinery/light/broken/directional/east,
+/turf/open/floor/iron/freezer,
+/area/ruin/outpost31/crewquarters)
+"Bj" = (
+/obj/structure/cable,
+/obj/structure/grille/broken,
+/obj/item/shard/plastitanium,
+/obj/item/shard/plastitanium,
+/turf/open/indestructible/plating{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"Bu" = (
+/obj/effect/turf_decal/tile/neutral,
+/obj/structure/sign/poster/random/directional/east,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"Bz" = (
+/obj/structure/tank_dispenser/oxygen{
+ oxygentanks = 5
+ },
+/turf/open/floor/pod/light,
+/area/ruin/outpost31)
+"BI" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/wood,
+/area/ruin/outpost31/recroom)
+"BM" = (
+/obj/structure/table,
+/turf/open/floor/iron/white/smooth_half{
+ dir = 1
+ },
+/area/ruin/outpost31/medical)
+"BP" = (
+/obj/structure/cable,
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/turf_decal/tile/neutral,
+/obj/effect/decal/cleanable/blood/footprints,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"BQ" = (
+/obj/structure/flora/rock/icy/style_random,
+/turf/open/misc/asteroid/snow/icemoon/do_not_chasm,
+/area/icemoon/underground/explored)
+"BU" = (
+/obj/machinery/shower/directional/south,
+/obj/structure/curtain,
+/obj/structure/fluff/shower_drain,
+/turf/open/floor/iron/freezer,
+/area/ruin/outpost31/crewquarters)
+"BZ" = (
+/obj/structure/closet/preopen{
+ name = "winter gear closet"
+ },
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/machinery/light/small/dim/directional/south,
+/turf/open/floor/pod/light,
+/area/ruin/outpost31)
+"Ca" = (
+/obj/structure/closet/emcloset,
+/turf/open/floor/carpet/green,
+/area/ruin/outpost31/commander_room)
+"Cb" = (
+/obj/machinery/light/warm/directional/south,
+/obj/effect/turf_decal/siding/white,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"Cc" = (
+/obj/structure/cable,
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/item/crowbar,
+/turf/open/floor/pod,
+/area/ruin/outpost31)
+"Ce" = (
+/obj/effect/decal/cleanable/dirt,
+/obj/machinery/duct,
+/turf/open/floor/iron/freezer,
+/area/ruin/outpost31/crewquarters)
+"CA" = (
+/obj/effect/turf_decal/tile/brown/opposingcorners,
+/obj/structure/window/spawner/directional/west,
+/obj/structure/table,
+/obj/item/broken_bottle,
+/turf/open/floor/iron,
+/area/ruin/outpost31/radiomap)
+"CC" = (
+/obj/effect/turf_decal/siding/white/corner{
+ dir = 1
+ },
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"CM" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/structure/table,
+/obj/effect/spawner/random/maintenance/no_decals/two,
+/turf/open/floor/plating,
+/area/ruin/outpost31)
+"CS" = (
+/turf/open/floor/iron/freezer,
+/area/ruin/outpost31/recroom)
+"Db" = (
+/obj/machinery/door/airlock/grunge,
+/obj/structure/cable,
+/obj/effect/mapping_helpers/airlock/autoname,
+/obj/effect/turf_decal/tile/brown/opposingcorners,
+/turf/open/floor/iron,
+/area/ruin/outpost31/radiomap)
+"Dg" = (
+/obj/structure/cable,
+/obj/structure/grille/broken,
+/obj/item/shard/plastitanium,
+/obj/item/shard/plastitanium,
+/obj/effect/mob_spawn/corpse/human/damaged{
+ husk = 1
+ },
+/turf/open/indestructible/plating{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"Dz" = (
+/obj/effect/turf_decal/tile/brown/opposingcorners,
+/obj/structure/table,
+/obj/item/climbing_hook,
+/turf/open/floor/iron,
+/area/ruin/outpost31/radiomap)
+"DF" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/machinery/light/warm/directional/west,
+/obj/structure/table,
+/obj/item/mod/module/flamethrower,
+/turf/open/floor/plating,
+/area/ruin/outpost31/lootroom)
+"DM" = (
+/obj/structure/table/wood,
+/obj/effect/mapping_helpers/turn_off_lights_with_lightswitch,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"DU" = (
+/obj/machinery/door/airlock/grunge,
+/obj/effect/turf_decal/siding/blue/end{
+ dir = 1
+ },
+/obj/effect/mapping_helpers/airlock/autoname,
+/turf/open/floor/iron/white/textured_large,
+/area/ruin/outpost31/crewquarters)
+"DW" = (
+/obj/effect/turf_decal/tile/brown/opposingcorners,
+/obj/effect/mapping_helpers/broken_floor,
+/turf/open/floor/iron,
+/area/ruin/outpost31/radiomap)
+"Ea" = (
+/obj/effect/turf_decal/tile/brown/opposingcorners,
+/obj/structure/table,
+/obj/item/paperwork{
+ desc = "Whatever nerd stuff they did down here.";
+ name = "map scraps"
+ },
+/obj/item/paperwork{
+ desc = "Whatever nerd stuff they did down here.";
+ name = "map scraps";
+ pixel_x = 17;
+ pixel_y = 5
+ },
+/turf/open/floor/iron,
+/area/ruin/outpost31/radiomap)
+"Ei" = (
+/obj/structure/bed/pod,
+/turf/open/floor/wood,
+/area/ruin/outpost31/crewquarters)
+"EH" = (
+/obj/effect/turf_decal/stripes/line,
+/obj/effect/turf_decal/stripes/line{
+ dir = 1
+ },
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"EL" = (
+/obj/structure/cable,
+/obj/effect/turf_decal/tile/neutral,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"ES" = (
+/obj/item/kirbyplants/random/dead,
+/obj/effect/turf_decal/siding/white{
+ dir = 10
+ },
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"EY" = (
+/obj/structure/extinguisher_cabinet/directional/east,
+/turf/open/floor/wood,
+/area/ruin/outpost31/commander_room)
+"Fb" = (
+/obj/machinery/door/airlock/grunge,
+/obj/effect/mapping_helpers/airlock/autoname,
+/obj/effect/turf_decal/siding/white{
+ dir = 1
+ },
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/structure/cable,
+/obj/structure/barricade/wooden/crude,
+/turf/open/floor/iron/white,
+/area/ruin/outpost31/medical)
+"Fo" = (
+/obj/structure/closet/preopen{
+ name = "winter gear closet"
+ },
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/obj/effect/turf_decal/tile/neutral,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"Fu" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/spawner/random/food_or_drink/any_snack_or_beverage,
+/obj/structure/table,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/kitchendiningroom)
+"FH" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/obj/structure/sign/poster/random/directional/north,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"FK" = (
+/obj/machinery/atmospherics/components/unary/vent_pump/on/layer2{
+ dir = 4
+ },
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"FP" = (
+/obj/structure/chair/stool/directional/north,
+/obj/effect/mapping_helpers/burnt_floor,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"FQ" = (
+/obj/machinery/door/airlock/grunge,
+/obj/effect/turf_decal/siding/blue/end,
+/obj/effect/mapping_helpers/airlock/autoname,
+/obj/effect/mapping_helpers/airlock/welded,
+/turf/open/floor/iron/white/textured_large,
+/area/ruin/outpost31/crewquarters)
+"FW" = (
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"FY" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer4{
+ dir = 1
+ },
+/obj/effect/turf_decal/tile/neutral,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"Gg" = (
+/turf/closed/wall,
+/area/ruin/outpost31)
+"Gh" = (
+/turf/open/floor/wood,
+/area/ruin/outpost31/crewquarters)
+"Gz" = (
+/turf/closed/wall/r_wall,
+/area/ruin/outpost31/kitchendiningroom)
+"GR" = (
+/obj/structure/closet/firecloset,
+/turf/open/floor/iron/textured_half,
+/area/ruin/outpost31)
+"GW" = (
+/obj/machinery/puzzle/keycardpad/directional/west{
+ id = "thingbosslootroom";
+ late_initialize_pop = 1
+ },
+/obj/item/paper{
+ can_become_message_in_bottle = 0;
+ default_raw_text = "Research was promising. We managed to stream an artificial intelligence into an organic host via this brain. Too bad it needs all robotic organs because apparently the human biology hates not having a properly functional nervous system."
+ },
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/plating,
+/area/ruin/outpost31)
+"Hk" = (
+/obj/effect/turf_decal/stripes/line,
+/obj/effect/turf_decal/stripes/corner{
+ dir = 1
+ },
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"Hm" = (
+/obj/effect/turf_decal/stripes/line,
+/obj/effect/turf_decal/stripes/line{
+ dir = 1
+ },
+/obj/effect/turf_decal/caution/stand_clear,
+/obj/structure/aggro_gate,
+/turf/open/floor/iron/dark{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"Hq" = (
+/obj/effect/turf_decal/stripes/line{
+ dir = 4
+ },
+/obj/effect/turf_decal/stripes/corner{
+ dir = 1
+ },
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"Ht" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"HC" = (
+/obj/machinery/atmospherics/components/unary/airlock_pump{
+ dir = 4
+ },
+/turf/open/floor/pod/dark,
+/area/ruin/outpost31)
+"HF" = (
+/obj/effect/decal/cleanable/dirt,
+/turf/open/floor/iron/freezer,
+/area/ruin/outpost31/crewquarters)
+"HL" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/iron/kitchen/small,
+/area/ruin/outpost31/kitchendiningroom)
+"HP" = (
+/obj/machinery/computer/operating{
+ dir = 1
+ },
+/obj/effect/mapping_helpers/broken_machine,
+/obj/effect/decal/cleanable/blood/old,
+/turf/open/floor/iron/white/smooth_half,
+/area/ruin/outpost31/medical)
+"HW" = (
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"Id" = (
+/obj/structure/table,
+/obj/item/food/icecreamsandwich,
+/turf/open/floor/iron/white/smooth_half,
+/area/ruin/outpost31/medical)
+"Ie" = (
+/obj/machinery/door/airlock/external/glass/ruin,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi{
+ cycle_id = "o31d"
+ },
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"Ij" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/structure/table,
+/obj/item/food/waffles,
+/obj/machinery/microwave,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/kitchendiningroom)
+"Il" = (
+/obj/effect/turf_decal/siding/white{
+ dir = 8
+ },
+/obj/structure/table/reinforced,
+/obj/item/plate/large,
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/iron/kitchen/small,
+/area/ruin/outpost31/kitchendiningroom)
+"Io" = (
+/obj/structure/chair/stool/bar/directional/east,
+/obj/effect/turf_decal/siding/white{
+ dir = 4
+ },
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"ID" = (
+/obj/machinery/power/apc/auto_name/directional/north,
+/obj/structure/cable,
+/obj/effect/turf_decal/tile/brown/opposingcorners,
+/obj/structure/table,
+/obj/item/paperwork{
+ desc = "A map of the outpost.";
+ name = "outpost map"
+ },
+/turf/open/floor/iron,
+/area/ruin/outpost31/radiomap)
+"IG" = (
+/turf/open/floor/catwalk_floor/iron_white{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"IL" = (
+/obj/structure/reagent_dispensers/fueltank,
+/turf/open/floor/plating/snowed/smoothed/icemoon,
+/area/icemoon/underground/explored)
+"IT" = (
+/obj/structure/sign/poster/random/directional/west,
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/turf_decal/siding/white{
+ dir = 8
+ },
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"Jd" = (
+/turf/closed/indestructible/reinforced,
+/area/ruin/outpost31/lab)
+"Jl" = (
+/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer4{
+ dir = 4
+ },
+/turf/open/floor/pod/dark,
+/area/ruin/outpost31)
+"Jq" = (
+/obj/effect/spawner/random/entertainment/arcade,
+/obj/effect/turf_decal/siding/white{
+ dir = 9
+ },
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"JG" = (
+/obj/machinery/door/poddoor/preopen,
+/obj/machinery/door/airlock/vault,
+/obj/structure/fans/tiny/invisible,
+/obj/structure/puzzle_blockade{
+ id = "thingbossentry"
+ },
+/turf/open/floor/iron/white{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"JP" = (
+/obj/structure/table,
+/obj/item/storage/medkit/fire,
+/turf/open/floor/iron/white/smooth_corner{
+ dir = 4
+ },
+/area/ruin/outpost31/medical)
+"Kh" = (
+/obj/structure/thing_boss_phase_depleter,
+/turf/open/floor/circuit/red/off{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"KU" = (
+/obj/effect/spawner/random/decoration/glowstick/on,
+/turf/open/floor/wood,
+/area/ruin/outpost31/kennel)
+"KW" = (
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/structure/cable,
+/turf/open/floor/catwalk_floor/iron,
+/area/ruin/outpost31)
+"Lf" = (
+/obj/structure/closet/preopen{
+ name = "winter gear closet"
+ },
+/turf/open/floor/pod/light,
+/area/ruin/outpost31)
+"Lk" = (
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/structure/cable,
+/turf/open/floor/iron/white/smooth_half,
+/area/ruin/outpost31/medical)
+"Lx" = (
+/turf/closed/wall,
+/area/ruin/outpost31/commander_room)
+"Ly" = (
+/turf/open/floor/carpet/green,
+/area/ruin/outpost31/commander_room)
+"Lz" = (
+/obj/structure/closet/preopen{
+ name = "winter gear closet"
+ },
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/machinery/light/small/broken/directional/south,
+/turf/open/floor/pod/light,
+/area/ruin/outpost31)
+"LA" = (
+/turf/closed/wall,
+/area/ruin/outpost31/crewquarters)
+"LQ" = (
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/obj/structure/cable,
+/obj/effect/decal/cleanable/blood/old,
+/turf/open/floor/catwalk_floor/iron,
+/area/ruin/outpost31)
+"LW" = (
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/obj/structure/cable,
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"Ma" = (
+/obj/structure/table/reinforced,
+/obj/item/stack/spacecash/c500,
+/obj/effect/turf_decal/siding/wood{
+ dir = 8
+ },
+/turf/open/floor/wood,
+/area/ruin/outpost31/commander_room)
+"Mf" = (
+/obj/structure/table/reinforced,
+/turf/open/floor/wood,
+/area/ruin/outpost31/commander_room)
+"Ml" = (
+/obj/machinery/door/airlock/public/glass,
+/obj/structure/cable,
+/obj/effect/mapping_helpers/airlock/autoname,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"Mn" = (
+/obj/structure/chair/stool/directional/north,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"Ms" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/machinery/light/broken/directional/south,
+/obj/effect/turf_decal/tile/neutral,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"Mw" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/item/ammo_casing/spent,
+/turf/open/floor/plating,
+/area/ruin/outpost31/kennel)
+"ME" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/structure/closet/crate,
+/obj/item/stack/sheet/iron/fifty,
+/obj/item/stack/sheet/glass/fifty,
+/turf/open/floor/plating,
+/area/ruin/outpost31/lootroom)
+"MK" = (
+/obj/structure/tank_dispenser/oxygen{
+ oxygentanks = 5
+ },
+/turf/open/floor/pod/dark,
+/area/ruin/outpost31)
+"MO" = (
+/obj/effect/turf_decal/stripes/line{
+ dir = 1
+ },
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"MQ" = (
+/obj/machinery/atmospherics/components/unary/vent_pump/on/layer2,
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"MT" = (
+/obj/structure/tank_dispenser/oxygen{
+ oxygentanks = 5
+ },
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/machinery/light/small/dim/directional/north,
+/turf/open/floor/pod/dark,
+/area/ruin/outpost31)
+"MW" = (
+/obj/structure/cable,
+/obj/effect/turf_decal/siding/wood{
+ dir = 8
+ },
+/obj/structure/table/wood,
+/obj/item/storage/card_binder,
+/turf/open/floor/wood,
+/area/ruin/outpost31/commander_room)
+"Nc" = (
+/obj/structure/sink/directional/south,
+/obj/structure/mirror/directional/north{
+ pixel_y = 33
+ },
+/mob/living/basic/fleshblob,
+/turf/open/floor/iron/freezer,
+/area/ruin/outpost31/crewquarters)
+"Nk" = (
+/obj/machinery/door/airlock/external/ruin,
+/obj/effect/mapping_helpers/airlock/welded,
+/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi{
+ cycle_id = "o31c"
+ },
+/obj/structure/barricade/wooden/crude/snow{
+ opacity = 1
+ },
+/turf/open/floor/pod/dark,
+/area/ruin/outpost31)
+"Nl" = (
+/obj/machinery/power/apc/auto_name/directional/south,
+/obj/structure/cable,
+/obj/effect/mapping_helpers/burnt_floor,
+/obj/effect/turf_decal/siding/white,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"Nw" = (
+/obj/structure/reagent_dispensers/plumbed,
+/turf/open/floor/iron/freezer,
+/area/ruin/outpost31/crewquarters)
+"NP" = (
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/obj/effect/turf_decal/tile/neutral,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"Ob" = (
+/obj/structure/table,
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/structure/extinguisher_cabinet/directional/south,
+/turf/open/floor/iron/kitchen/small,
+/area/ruin/outpost31/kitchendiningroom)
+"Oe" = (
+/obj/structure/sink/kitchen/directional/west,
+/obj/structure/mirror/directional/east,
+/turf/open/floor/iron/freezer,
+/area/ruin/outpost31/recroom)
+"Ol" = (
+/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer4,
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"Oo" = (
+/obj/structure/chair/stool/directional/south,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"Or" = (
+/obj/structure/table/wood,
+/obj/item/reagent_containers/cup/glass/drinkingglass/filled/cola{
+ pixel_x = -8;
+ pixel_y = 4
+ },
+/obj/effect/turf_decal/siding/wood{
+ dir = 8
+ },
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/wood,
+/area/ruin/outpost31/recroom)
+"Ov" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/plating,
+/area/ruin/outpost31)
+"Oz" = (
+/obj/item/reagent_containers/blood{
+ pixel_x = -5;
+ pixel_y = 5
+ },
+/obj/effect/decal/cleanable/blood/old,
+/turf/open/floor/iron/white,
+/area/ruin/outpost31/medical)
+"OW" = (
+/obj/machinery/griddle,
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/iron/kitchen/small,
+/area/ruin/outpost31/kitchendiningroom)
+"Pb" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/item/ammo_casing/spent,
+/obj/item/ammo_casing/spent,
+/obj/item/ammo_casing/spent,
+/obj/item/ammo_casing/spent,
+/turf/open/floor/plating,
+/area/ruin/outpost31/kennel)
+"Pd" = (
+/obj/machinery/door/airlock/public/glass,
+/obj/effect/turf_decal/stripes/line,
+/obj/effect/turf_decal/stripes/line{
+ dir = 1
+ },
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/obj/effect/mapping_helpers/airlock/autoname,
+/turf/open/floor/iron/dark,
+/area/ruin/outpost31)
+"Pw" = (
+/obj/effect/turf_decal/tile/brown/opposingcorners,
+/turf/open/floor/iron,
+/area/ruin/outpost31/radiomap)
+"PG" = (
+/obj/structure/table,
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/iron/kitchen/small,
+/area/ruin/outpost31/kitchendiningroom)
+"PR" = (
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/obj/structure/cable,
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/obj/effect/turf_decal/tile/neutral,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"PS" = (
+/obj/effect/decal/cleanable/blood/old,
+/obj/structure/table,
+/obj/item/storage/fancy/egg_box,
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/iron/kitchen/small,
+/area/ruin/outpost31/kitchendiningroom)
+"PT" = (
+/obj/effect/turf_decal/stripes/line{
+ dir = 6
+ },
+/obj/effect/turf_decal/stripes/corner{
+ dir = 1
+ },
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"PW" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/turf_decal/tile/neutral,
+/obj/structure/girder,
+/obj/structure/barricade/wooden,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"Qj" = (
+/obj/effect/turf_decal/tile/brown/opposingcorners,
+/obj/effect/mapping_helpers/broken_floor,
+/obj/effect/spawner/random/maintenance/no_decals,
+/turf/open/floor/iron,
+/area/ruin/outpost31/radiomap)
+"Qy" = (
+/obj/effect/turf_decal/siding/white,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"QA" = (
+/obj/effect/turf_decal/tile/brown/opposingcorners,
+/obj/structure/chair/office,
+/turf/open/floor/iron,
+/area/ruin/outpost31/radiomap)
+"QU" = (
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"Rc" = (
+/turf/closed/wall,
+/area/ruin/outpost31/radiomap)
+"Rf" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/structure/table,
+/obj/effect/spawner/random/food_or_drink/any_snack_or_beverage,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/kitchendiningroom)
+"Rm" = (
+/obj/effect/turf_decal/siding/white{
+ dir = 4
+ },
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"RD" = (
+/obj/effect/turf_decal/stripes/line{
+ dir = 6
+ },
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"RK" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/turf_decal/tile/neutral,
+/obj/item/crowbar,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"RL" = (
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/turf/open/floor/plating/snowed/smoothed/icemoon,
+/area/icemoon/underground/explored)
+"RO" = (
+/obj/machinery/door/airlock/grunge,
+/obj/effect/turf_decal/siding/white{
+ dir = 1
+ },
+/obj/structure/cable,
+/obj/effect/mapping_helpers/airlock/autoname,
+/obj/effect/decal/cleanable/blood/footprints,
+/turf/open/floor/iron/kitchen/small,
+/area/ruin/outpost31/kitchendiningroom)
+"RQ" = (
+/obj/machinery/door/poddoor{
+ id = "thingbosslootroom";
+ name = "Storage Room 2"
+ },
+/turf/open/floor/plating,
+/area/ruin/outpost31/lootroom)
+"RR" = (
+/obj/structure/closet/cabinet,
+/obj/machinery/light/small/broken/directional/west,
+/turf/open/floor/wood,
+/area/ruin/outpost31/crewquarters)
+"Sa" = (
+/obj/machinery/atmospherics/components/tank/air/layer2,
+/obj/machinery/light/small/dim/directional/west,
+/turf/open/floor/plating,
+/area/ruin/outpost31/kennel)
+"Sg" = (
+/turf/closed/wall/r_wall,
+/area/ruin/outpost31)
+"Sm" = (
+/obj/effect/turf_decal/siding/wood{
+ dir = 8
+ },
+/obj/structure/filingcabinet/chestdrawer,
+/turf/open/floor/wood,
+/area/ruin/outpost31/commander_room)
+"Sq" = (
+/obj/structure/chair/office{
+ dir = 8
+ },
+/turf/open/floor/wood,
+/area/ruin/outpost31/commander_room)
+"Sy" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/obj/structure/sign/poster/random/directional/west,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"SB" = (
+/obj/item/surgery_tray/deployed,
+/obj/effect/decal/cleanable/blood/old,
+/obj/machinery/defibrillator_mount/loaded{
+ pixel_y = -32
+ },
+/turf/open/floor/iron/white/smooth_half,
+/area/ruin/outpost31/medical)
+"SJ" = (
+/obj/machinery/door/airlock/external/glass/ruin,
+/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi{
+ cycle_id = "o31c"
+ },
+/turf/open/floor/pod/dark,
+/area/ruin/outpost31)
+"SN" = (
+/obj/machinery/atmospherics/components/unary/passive_vent/layer4{
+ dir = 8
+ },
+/obj/machinery/light/small/dim/directional/north,
+/turf/open/floor/plating/snowed/smoothed/icemoon,
+/area/icemoon/underground/explored)
+"SW" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/plating,
+/area/ruin/outpost31/lootroom)
+"Td" = (
+/obj/machinery/light_switch/directional/south,
+/turf/open/floor/carpet/green,
+/area/ruin/outpost31/commander_room)
+"Tm" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/turf_decal/tile/neutral,
+/obj/structure/extinguisher_cabinet/directional/east,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"Tq" = (
+/obj/machinery/power/rtg/advanced{
+ power_gen = 25000
+ },
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/structure/cable,
+/turf/open/floor/plating,
+/area/ruin/outpost31/kennel)
+"Tr" = (
+/obj/machinery/power/apc/auto_name/directional/east,
+/obj/structure/cable,
+/obj/structure/table,
+/obj/item/cigbutt/cigarbutt{
+ pixel_x = 6;
+ pixel_y = 0
+ },
+/obj/item/trash/chips{
+ pixel_x = -5;
+ pixel_y = 7
+ },
+/turf/open/floor/iron/textured_half{
+ dir = 1
+ },
+/area/ruin/outpost31)
+"Tv" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/structure/chair{
+ dir = 4
+ },
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/kitchendiningroom)
+"Tx" = (
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/obj/effect/decal/cleanable/blood/old,
+/obj/structure/girder,
+/obj/structure/barricade/wooden,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"TP" = (
+/obj/structure/reagent_dispensers/fueltank,
+/turf/open/misc/asteroid/snow/icemoon/do_not_chasm,
+/area/icemoon/underground/explored)
+"TX" = (
+/obj/effect/turf_decal/siding/white{
+ dir = 8
+ },
+/obj/structure/table/reinforced,
+/obj/item/food/waffles,
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/iron/kitchen/small,
+/area/ruin/outpost31/kitchendiningroom)
+"Ui" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/gibspawner/generic/animal{
+ virusProb = 0
+ },
+/turf/open/floor/wood,
+/area/ruin/outpost31/kennel)
+"Uv" = (
+/obj/effect/turf_decal/stripes/line{
+ dir = 10
+ },
+/obj/effect/turf_decal/stripes/corner{
+ dir = 4
+ },
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"Uy" = (
+/obj/effect/turf_decal/tile/neutral,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"UA" = (
+/obj/machinery/atmospherics/components/unary/vent_pump/on/layer2{
+ dir = 1
+ },
+/turf/open/floor/iron/white,
+/area/ruin/outpost31/medical)
+"UH" = (
+/obj/structure/cable,
+/obj/effect/mapping_helpers/burnt_floor,
+/obj/effect/turf_decal/siding/white,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"UI" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/wood,
+/area/ruin/outpost31/kennel)
+"Vc" = (
+/obj/machinery/light/broken/directional/north,
+/obj/item/storage/cans,
+/turf/open/floor/wood,
+/area/ruin/outpost31/commander_room)
+"Vd" = (
+/turf/closed/indestructible/reinforced,
+/area/ruin/outpost31)
+"Vg" = (
+/obj/machinery/door/airlock/grunge,
+/obj/effect/turf_decal/siding/white,
+/turf/open/floor/iron/freezer,
+/area/ruin/outpost31/recroom)
+"Vm" = (
+/obj/machinery/power/apc/auto_name/directional/south,
+/obj/structure/cable,
+/obj/structure/table/wood,
+/turf/open/floor/wood,
+/area/ruin/outpost31/commander_room)
+"Vx" = (
+/obj/effect/turf_decal/tile/neutral,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/structure/cable,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"Vz" = (
+/obj/structure/table,
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/machinery/light/broken/directional/south,
+/turf/open/floor/iron/kitchen/small,
+/area/ruin/outpost31/kitchendiningroom)
+"VA" = (
+/obj/effect/turf_decal/tile/brown/opposingcorners,
+/obj/effect/decal/cleanable/glass,
+/obj/structure/chair/office{
+ dir = 4
+ },
+/obj/effect/mapping_helpers/broken_floor,
+/turf/open/floor/iron,
+/area/ruin/outpost31/radiomap)
+"VG" = (
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/obj/effect/decal/cleanable/blood/old,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"VK" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/structure/closet/crate,
+/obj/item/vending_refill/cola,
+/obj/item/vending_refill/cigarette,
+/obj/item/vending_refill/boozeomat,
+/turf/open/floor/plating,
+/area/ruin/outpost31/lootroom)
+"VM" = (
+/obj/machinery/light/small/dim/directional/west,
+/obj/structure/closet{
+ name = "winter gear closet"
+ },
+/obj/item/clothing/shoes/winterboots/ice_boots/eva,
+/obj/item/clothing/suit/hooded/wintercoat/eva,
+/turf/open/floor/pod/light,
+/area/ruin/outpost31)
+"VR" = (
+/obj/structure/closet/crate/bin,
+/turf/open/floor/iron/textured_half,
+/area/ruin/outpost31)
+"VU" = (
+/obj/effect/turf_decal/stripes/corner{
+ dir = 8
+ },
+/turf/open/floor/iron/white/herringbone{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"VV" = (
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/structure/cable,
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/plating,
+/area/ruin/outpost31/kennel)
+"Wc" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/machinery/light/broken/directional/south,
+/obj/machinery/atmospherics/components/unary/vent_pump/on/layer2{
+ dir = 1
+ },
+/obj/effect/turf_decal/tile/neutral,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"Wd" = (
+/obj/machinery/door/airlock/external/ruin,
+/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi{
+ cycle_id = "o31B"
+ },
+/turf/open/floor/pod/dark,
+/area/ruin/outpost31)
+"Wu" = (
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/structure/closet/crate/bin,
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/kitchendiningroom)
+"Wy" = (
+/obj/machinery/light/small/dim/directional/north,
+/turf/open/floor/plating/snowed/smoothed/icemoon,
+/area/icemoon/underground/explored)
+"WD" = (
+/obj/machinery/door/airlock/external/glass/ruin,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi{
+ cycle_id = "o31c"
+ },
+/turf/open/floor/pod/dark,
+/area/ruin/outpost31)
+"WQ" = (
+/turf/template_noop,
+/area/template_noop)
+"Xa" = (
+/obj/structure/cable,
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/decal/cleanable/blood/footprints,
+/turf/open/floor/iron/kitchen/small,
+/area/ruin/outpost31/kitchendiningroom)
+"Xf" = (
+/obj/machinery/power/apc/auto_name/directional/north,
+/obj/structure/cable,
+/obj/machinery/oven/range,
+/obj/effect/decal/cleanable/dirt/dust,
+/turf/open/floor/iron/kitchen/small,
+/area/ruin/outpost31/kitchendiningroom)
+"Xk" = (
+/obj/machinery/light_switch/directional/north,
+/obj/structure/tank_holder/extinguisher,
+/turf/open/floor/iron/white/smooth_corner{
+ dir = 8
+ },
+/area/ruin/outpost31/medical)
+"XD" = (
+/obj/effect/turf_decal/tile/purple/opposingcorners,
+/turf/open/floor/iron/white{
+ initial_gas_mix = "o2=14;n2=30;TEMP=293.15"
+ },
+/area/ruin/outpost31/lab)
+"XI" = (
+/obj/structure/cable,
+/obj/effect/decal/cleanable/dirt/dust,
+/obj/effect/turf_decal/tile/neutral,
+/turf/open/floor/iron,
+/area/ruin/outpost31)
+"XJ" = (
+/obj/effect/spawner/random/entertainment/arcade{
+ dir = 8
+ },
+/obj/effect/turf_decal/siding/white{
+ dir = 6
+ },
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"XN" = (
+/turf/open/floor/wood,
+/area/ruin/outpost31/kennel)
+"XQ" = (
+/obj/machinery/shower/directional/south,
+/obj/structure/curtain,
+/obj/effect/decal/cleanable/blood/old,
+/obj/structure/fluff/shower_drain,
+/obj/item/food/candy_trash{
+ desc = "Tooth fillings.";
+ name = "tooth fillings"
+ },
+/turf/open/floor/iron/freezer,
+/area/ruin/outpost31/crewquarters)
+"Yh" = (
+/obj/structure/chair/stool/bar/directional/east,
+/obj/effect/turf_decal/siding/white{
+ dir = 5
+ },
+/turf/open/floor/iron/cafeteria,
+/area/ruin/outpost31/recroom)
+"Yk" = (
+/obj/effect/turf_decal/siding/blue{
+ dir = 4
+ },
+/obj/effect/turf_decal/siding/blue{
+ dir = 8
+ },
+/obj/machinery/duct,
+/turf/open/floor/iron/white/textured_large,
+/area/ruin/outpost31/crewquarters)
+"Yo" = (
+/turf/open/misc/asteroid/snow/icemoon/do_not_chasm,
+/area/icemoon/underground/explored)
+"YG" = (
+/obj/machinery/power/apc/auto_name/directional/east,
+/obj/structure/cable,
+/obj/effect/turf_decal/siding/white{
+ dir = 8
+ },
+/obj/effect/turf_decal/siding/white{
+ dir = 4
+ },
+/turf/open/floor/iron,
+/area/ruin/outpost31/crewquarters)
+"YK" = (
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
+/obj/structure/cable,
+/turf/open/floor/catwalk_floor/iron,
+/area/ruin/outpost31)
+"YT" = (
+/obj/structure/closet/preopen{
+ name = "winter gear closet"
+ },
+/obj/machinery/light/small/dim/directional/north,
+/turf/open/floor/pod/light,
+/area/ruin/outpost31)
+"Zq" = (
+/obj/structure/window/spawner/directional/east,
+/obj/structure/table,
+/obj/item/soap/homemade,
+/turf/open/floor/iron/freezer,
+/area/ruin/outpost31/crewquarters)
+"ZA" = (
+/obj/effect/turf_decal/siding/white{
+ dir = 8
+ },
+/obj/effect/turf_decal/siding/white{
+ dir = 4
+ },
+/turf/open/floor/iron,
+/area/ruin/outpost31/crewquarters)
+"ZB" = (
+/obj/structure/table/reinforced,
+/obj/item/paper_bin,
+/obj/item/pen,
+/obj/effect/turf_decal/siding/wood{
+ dir = 8
+ },
+/turf/open/floor/wood,
+/area/ruin/outpost31/commander_room)
+"ZL" = (
+/obj/structure/bed,
+/obj/item/bedsheet,
+/obj/structure/curtain,
+/turf/open/floor/wood,
+/area/ruin/outpost31/commander_room)
+"ZM" = (
+/obj/effect/turf_decal/tile/brown/opposingcorners,
+/obj/effect/mapping_helpers/broken_floor,
+/obj/item/grenade/iedcasing/spawned,
+/obj/structure/extinguisher_cabinet/directional/south,
+/turf/open/floor/iron,
+/area/ruin/outpost31/radiomap)
+"ZT" = (
+/turf/open/floor/wood,
+/area/ruin/outpost31/commander_room)
+"ZX" = (
+/obj/structure/cable,
+/obj/effect/turf_decal/tile/brown/opposingcorners,
+/obj/structure/filingcabinet/chestdrawer,
+/turf/open/floor/iron,
+/area/ruin/outpost31/radiomap)
+"ZY" = (
+/obj/structure/cable,
+/obj/effect/turf_decal/siding/white{
+ dir = 8
+ },
+/obj/effect/turf_decal/siding/white{
+ dir = 4
+ },
+/obj/structure/extinguisher_cabinet/directional/east,
+/turf/open/floor/iron,
+/area/ruin/outpost31/crewquarters)
+
+(1,1,1) = {"
+WQ
+WQ
+WQ
+WQ
+WQ
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+Yo
+Yo
+Yo
+lW
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+WQ
+WQ
+"}
+(2,1,1) = {"
+WQ
+WQ
+WQ
+WQ
+WQ
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+BQ
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+WQ
+"}
+(3,1,1) = {"
+WQ
+WQ
+WQ
+WQ
+WQ
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+Yo
+BQ
+Yo
+Yo
+Yo
+Yo
+BQ
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+TP
+IL
+yo
+yo
+yo
+yo
+yo
+yo
+yo
+yo
+yo
+uB
+"}
+(4,1,1) = {"
+WQ
+WQ
+WQ
+WQ
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+Yo
+Yo
+Yo
+Yo
+hi
+lW
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+TP
+IL
+yo
+ce
+dV
+aM
+DF
+gC
+jl
+nH
+yo
+uB
+"}
+(5,1,1) = {"
+WQ
+WQ
+WQ
+WQ
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+IL
+yo
+SW
+SW
+SW
+SW
+SW
+SW
+ht
+yo
+uB
+"}
+(6,1,1) = {"
+WQ
+WQ
+WQ
+WQ
+uB
+uB
+uB
+uB
+Sg
+Sg
+Sg
+Sg
+Sg
+sK
+Yo
+BQ
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+BQ
+Yo
+Yo
+Yo
+Yo
+Yo
+sK
+yo
+ME
+aM
+aM
+SW
+SW
+SW
+VK
+yo
+uB
+"}
+(7,1,1) = {"
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+Sg
+Lf
+oF
+Lf
+Sg
+Wy
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+sK
+yo
+yo
+yo
+yo
+dt
+RQ
+RQ
+yo
+yo
+Vd
+"}
+(8,1,1) = {"
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+Sg
+YT
+Jl
+lM
+uN
+RL
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+rm
+Vd
+VM
+Bz
+Sg
+GW
+Ov
+rx
+Ov
+CM
+Vd
+"}
+(9,1,1) = {"
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+Sg
+Lf
+yQ
+qI
+Sg
+SN
+sK
+sK
+sK
+sK
+sK
+sK
+sK
+sK
+sK
+sK
+sK
+sK
+sK
+sK
+sK
+sK
+Nk
+oF
+HC
+SJ
+Ov
+Ov
+Ov
+jv
+CM
+ov
+"}
+(10,1,1) = {"
+uB
+uB
+oa
+oa
+oa
+oa
+oa
+oa
+oa
+Sg
+qt
+Sg
+Gz
+Gz
+lT
+lT
+Gz
+Jd
+Jd
+Jd
+Jd
+nP
+Jd
+Jd
+Jd
+Jd
+Jd
+Jd
+Jd
+Jd
+Vd
+Sg
+WD
+Sg
+Gg
+mq
+si
+CM
+CM
+Vd
+"}
+(11,1,1) = {"
+uB
+oa
+oa
+eT
+IT
+py
+yp
+ES
+aq
+Ht
+QU
+lO
+aV
+Wu
+gu
+Tv
+Tv
+Jd
+XD
+dm
+EH
+Bc
+mW
+mW
+mW
+MO
+ob
+dm
+XD
+Jd
+sm
+Ht
+QU
+lO
+xk
+xk
+xk
+xk
+xk
+cy
+"}
+(12,1,1) = {"
+uB
+oa
+Jq
+CC
+oJ
+oJ
+kh
+UH
+Ml
+vX
+ge
+oN
+rO
+gu
+gu
+Rf
+Fu
+Jd
+XD
+wt
+Hk
+Bc
+mW
+Kh
+mW
+MO
+VU
+dW
+XD
+Jd
+tG
+Ht
+lF
+oN
+xk
+ch
+wr
+tZ
+JP
+cy
+"}
+(13,1,1) = {"
+uB
+oa
+uR
+Oo
+oV
+og
+FP
+bE
+aq
+MQ
+ge
+FY
+aV
+nJ
+gu
+Rf
+pj
+Jd
+XD
+MO
+ob
+Bc
+mW
+mW
+mW
+MO
+IG
+ob
+XD
+Jd
+wx
+Ht
+ub
+oN
+xk
+cd
+Oz
+gM
+Id
+cy
+"}
+(14,1,1) = {"
+uB
+oa
+cu
+Oo
+DM
+qa
+Mn
+Nl
+aq
+Ht
+ge
+oN
+aV
+Ij
+gu
+tj
+tj
+Jd
+XD
+zy
+eu
+Bc
+nV
+hD
+uI
+dH
+ai
+ob
+XD
+Jd
+Jd
+cp
+qK
+Uy
+xk
+Au
+UA
+gM
+SB
+cy
+"}
+(15,1,1) = {"
+uB
+oa
+mV
+Oo
+lR
+lR
+Mn
+Cb
+aq
+cp
+LQ
+oN
+aV
+pd
+gu
+gu
+fI
+Jd
+eb
+PT
+Bj
+Bj
+Bj
+MO
+ai
+FW
+FW
+ob
+XD
+Hm
+Jd
+nj
+ub
+oN
+xk
+Au
+gM
+an
+ze
+cy
+"}
+(16,1,1) = {"
+uB
+oa
+uR
+fC
+og
+qa
+Mn
+zO
+aq
+cp
+YK
+Ms
+aV
+Ae
+gu
+gu
+me
+nP
+Bc
+Bc
+Dg
+in
+Bj
+zu
+IG
+IG
+IG
+ob
+XD
+Hm
+JG
+cp
+qK
+Bb
+xk
+lU
+gi
+gM
+HP
+cy
+"}
+(17,1,1) = {"
+uB
+oa
+uR
+kh
+sg
+sg
+kh
+Qy
+aq
+FH
+YK
+Uy
+aV
+aV
+TX
+Il
+aV
+Jd
+jP
+Uv
+Bj
+Bj
+Bj
+MO
+FW
+FW
+FW
+za
+XD
+Hm
+yW
+cp
+YK
+Vx
+Fb
+Lk
+gM
+gM
+eS
+cy
+"}
+(18,1,1) = {"
+uB
+oa
+Yh
+Io
+Rm
+Rm
+Rm
+XJ
+aq
+cp
+YK
+oN
+aV
+OW
+HL
+jL
+tC
+Jd
+XD
+zN
+dW
+Bc
+nV
+Hq
+eI
+mO
+FW
+ob
+XD
+Jd
+Jd
+cp
+YK
+Uy
+xk
+Xk
+BM
+BM
+Ai
+cy
+"}
+(19,1,1) = {"
+uB
+oa
+Or
+eg
+eL
+aq
+aq
+aq
+aq
+cp
+YK
+Uy
+aV
+Xf
+jL
+pQ
+PS
+Jd
+XD
+rk
+ob
+Bc
+mW
+mW
+mW
+MO
+IG
+ob
+XD
+Jd
+dP
+Ht
+YK
+Uy
+xk
+xk
+xk
+xk
+xk
+he
+"}
+(20,1,1) = {"
+uB
+oa
+BI
+BI
+BI
+aq
+fH
+CS
+Vg
+Ht
+YK
+BP
+RO
+Xa
+yx
+jL
+Vz
+Jd
+XD
+gO
+yO
+Bc
+mW
+Kh
+mW
+MO
+ru
+RD
+XD
+Jd
+GR
+cp
+KW
+oN
+Rc
+Ea
+lr
+wE
+AR
+xc
+"}
+(21,1,1) = {"
+uB
+oa
+sd
+bg
+aP
+aq
+kA
+Oe
+aq
+cp
+YK
+Uy
+aV
+hs
+PG
+bX
+Ob
+Jd
+XD
+bW
+EH
+Bc
+mW
+mW
+mW
+MO
+ob
+bW
+XD
+Jd
+VR
+Ht
+YK
+Uy
+Rc
+ID
+Pw
+QA
+ho
+xc
+"}
+(22,1,1) = {"
+qz
+oa
+aq
+aq
+aq
+aq
+aq
+aq
+aq
+cp
+YK
+oN
+aV
+aV
+aV
+aV
+aV
+Jd
+Jd
+Jd
+Jd
+nP
+Jd
+Jd
+Jd
+Jd
+Jd
+Jd
+Jd
+Jd
+Sg
+cp
+YK
+Uy
+Rc
+ZX
+Pw
+Pw
+ks
+xc
+"}
+(23,1,1) = {"
+qz
+Sa
+cw
+gZ
+cm
+sP
+Ht
+cp
+Ht
+cp
+YK
+FK
+cp
+VG
+cp
+cp
+Ht
+Pd
+tX
+cp
+cp
+vX
+pe
+cp
+Ht
+Ht
+Tx
+cp
+Ht
+cp
+Sy
+cp
+YK
+Uy
+fL
+bb
+Pw
+Pw
+CA
+xc
+"}
+(24,1,1) = {"
+qz
+to
+ko
+VV
+lz
+AG
+LW
+YK
+YK
+YK
+YK
+YK
+YK
+ge
+YK
+YK
+YK
+tV
+YK
+YK
+YK
+YK
+ge
+YK
+YK
+YK
+wu
+YK
+YK
+ge
+YK
+YK
+YK
+XI
+Db
+bb
+Pw
+QA
+Dz
+xc
+"}
+(25,1,1) = {"
+qz
+Mw
+Pb
+tJ
+Tq
+sP
+PR
+wc
+Bu
+wO
+hT
+Tm
+Uy
+Uy
+Uy
+Uy
+Uy
+mU
+oN
+Al
+bp
+Uy
+wO
+EL
+RK
+db
+PW
+Uy
+oN
+Uy
+Uy
+HW
+YK
+Uy
+Rc
+Pw
+Pw
+ah
+cz
+xc
+"}
+(26,1,1) = {"
+qz
+sk
+Mw
+tS
+sP
+sP
+zd
+Gg
+LA
+LA
+LA
+LA
+LA
+LA
+LA
+LA
+LA
+LA
+LA
+LA
+LA
+LA
+fW
+Tr
+jM
+Lx
+Lx
+Lx
+iA
+Lx
+Lx
+cp
+YK
+oN
+Rc
+Qj
+DW
+wb
+ZM
+xc
+"}
+(27,1,1) = {"
+qz
+wP
+wp
+nT
+sP
+qH
+kP
+Lz
+LA
+RR
+no
+LA
+RR
+no
+LA
+RR
+no
+LA
+Nw
+dK
+rf
+LA
+LA
+LA
+Lx
+Lx
+ck
+tx
+Ly
+nr
+Lx
+Ht
+LQ
+Uy
+Rc
+cf
+VA
+VA
+cf
+xc
+"}
+(28,1,1) = {"
+qz
+UI
+UI
+xo
+sP
+vn
+uj
+vn
+LA
+Gh
+Gh
+LA
+Gh
+Gh
+LA
+Gh
+Gh
+LA
+Nc
+zF
+oj
+zF
+op
+LA
+Ca
+Ly
+Ly
+Ly
+Ly
+Td
+Lx
+Ol
+YK
+Wc
+Rc
+cf
+cf
+cf
+cf
+xc
+"}
+(29,1,1) = {"
+qz
+xo
+KU
+xo
+sP
+vn
+uj
+BZ
+LA
+sQ
+LA
+LA
+sQ
+LA
+LA
+sQ
+LA
+LA
+Zq
+Ce
+oj
+Ce
+sL
+LA
+Aw
+Ly
+Ly
+Ly
+Ly
+uA
+AE
+vX
+YK
+Uy
+xc
+xc
+xc
+xc
+xc
+xc
+"}
+(30,1,1) = {"
+qz
+xo
+xo
+Ui
+sP
+qH
+kP
+Cc
+rB
+dZ
+ZY
+wq
+vU
+YG
+au
+ZA
+ZA
+DU
+zw
+Yk
+zw
+Yk
+zw
+FQ
+Ly
+Ly
+fM
+Ly
+Ly
+uA
+iA
+Ht
+qK
+Uy
+xc
+uB
+uB
+uB
+uB
+uB
+"}
+(31,1,1) = {"
+qz
+kF
+xo
+XN
+qz
+Sg
+xP
+Sg
+zv
+oz
+LA
+LA
+oz
+LA
+LA
+oz
+LA
+LA
+sB
+zF
+HF
+zF
+iH
+LA
+Sm
+ep
+ZB
+Ma
+kX
+MW
+Lx
+Fo
+NP
+ih
+xc
+uB
+uB
+uB
+uB
+uB
+"}
+(32,1,1) = {"
+qz
+qz
+qz
+qz
+qz
+MT
+jN
+hN
+zv
+Gh
+Gh
+LA
+Gh
+Gh
+LA
+Gh
+Gh
+LA
+BU
+Ce
+zF
+zF
+op
+LA
+Vc
+ZT
+Sq
+Mf
+ZT
+Vm
+ne
+Sg
+Ie
+Sg
+xc
+uB
+uB
+uB
+uB
+uB
+"}
+(33,1,1) = {"
+uB
+uB
+uB
+uB
+Sg
+Sg
+Wd
+Sg
+zv
+pw
+Ei
+LA
+pw
+Ei
+LA
+pw
+Ei
+LA
+XQ
+fb
+Bg
+fb
+op
+LA
+ZL
+ZT
+pa
+EY
+ZT
+sF
+ne
+MK
+zz
+oF
+xc
+uB
+uB
+uB
+uB
+uB
+"}
+(34,1,1) = {"
+WQ
+uB
+uB
+uB
+Sg
+rb
+sK
+rb
+zv
+zv
+zv
+zv
+zv
+zv
+zv
+zv
+zv
+zv
+zv
+zv
+zv
+zv
+zv
+zv
+by
+by
+by
+ne
+ne
+ne
+ne
+Sg
+zY
+Sg
+xc
+uB
+uB
+uB
+uB
+uB
+"}
+(35,1,1) = {"
+WQ
+uB
+uB
+uB
+uB
+Yo
+Yo
+Yo
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+sK
+sK
+sK
+uB
+uB
+uB
+sK
+rb
+sK
+rb
+sK
+uB
+uB
+uB
+uB
+uB
+"}
+(36,1,1) = {"
+WQ
+WQ
+uB
+BQ
+Yo
+Yo
+Yo
+Yo
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+Yo
+Yo
+Yo
+uB
+uB
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+uB
+uB
+uB
+uB
+WQ
+"}
+(37,1,1) = {"
+WQ
+WQ
+uB
+Yo
+Yo
+Yo
+Yo
+Yo
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+BQ
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+uB
+uB
+uB
+WQ
+"}
+(38,1,1) = {"
+WQ
+WQ
+WQ
+Yo
+Yo
+Yo
+Yo
+Yo
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+uB
+uB
+uB
+WQ
+"}
+(39,1,1) = {"
+WQ
+WQ
+WQ
+Yo
+Yo
+Yo
+Yo
+Yo
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+BQ
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+uB
+uB
+uB
+WQ
+"}
+(40,1,1) = {"
+WQ
+WQ
+WQ
+Yo
+Yo
+Yo
+Yo
+Yo
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+uB
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+Yo
+uB
+uB
+uB
+WQ
+WQ
+"}
diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_wizard.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_wizard.dmm
index 20b3a8512655c..fb0a070b8943c 100644
--- a/_maps/RandomRuins/LavaRuins/lavaland_surface_wizard.dmm
+++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_wizard.dmm
@@ -309,7 +309,7 @@
/turf/open/misc/asteroid/basalt/lava_land_surface,
/area/lavaland/surface/outdoors)
"K" = (
-/mob/living/simple_animal/hostile/dark_wizard,
+/mob/living/basic/dark_wizard,
/turf/open/misc/asteroid/basalt/lava_land_surface,
/area/lavaland/surface/outdoors)
diff --git a/_maps/RandomRuins/SpaceRuins/interdyne.dmm b/_maps/RandomRuins/SpaceRuins/interdyne.dmm
index 46e22d19fb67b..b115c323a356e 100644
--- a/_maps/RandomRuins/SpaceRuins/interdyne.dmm
+++ b/_maps/RandomRuins/SpaceRuins/interdyne.dmm
@@ -104,7 +104,7 @@
/turf/open/floor/mineral/plastitanium,
/area/ruin/space/has_grav/interdyne)
"eJ" = (
-/mob/living/simple_animal/hostile/zombie,
+/mob/living/basic/zombie,
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
/obj/structure/cable,
@@ -496,7 +496,7 @@
/turf/open/floor/mineral/plastitanium,
/area/ruin/space/has_grav/interdyne)
"uy" = (
-/mob/living/simple_animal/hostile/zombie,
+/mob/living/basic/zombie,
/turf/open/floor/mineral/plastitanium,
/area/ruin/space/has_grav/interdyne)
"vd" = (
@@ -656,7 +656,7 @@
/turf/open/floor/mineral/plastitanium/red,
/area/ruin/space/has_grav/interdyne)
"By" = (
-/mob/living/simple_animal/hostile/zombie,
+/mob/living/basic/zombie,
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
/turf/open/floor/mineral/plastitanium/red,
/area/ruin/space/has_grav/interdyne)
@@ -672,7 +672,7 @@
/turf/open/floor/iron/smooth,
/area/ruin/space/has_grav/interdyne)
"DA" = (
-/mob/living/simple_animal/hostile/zombie,
+/mob/living/basic/zombie,
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
/turf/open/floor/mineral/plastitanium,
/area/ruin/space/has_grav/interdyne)
@@ -719,7 +719,7 @@
/area/ruin/space/has_grav/interdyne)
"Ft" = (
/obj/effect/decal/cleanable/dirt,
-/mob/living/simple_animal/hostile/zombie,
+/mob/living/basic/zombie,
/obj/structure/cable,
/turf/open/floor/mineral/plastitanium,
/area/ruin/space/has_grav/interdyne)
@@ -732,7 +732,7 @@
/turf/open/floor/mineral/plastitanium/red,
/area/ruin/space/has_grav/interdyne)
"FC" = (
-/mob/living/simple_animal/hostile/zombie,
+/mob/living/basic/zombie,
/obj/structure/cable,
/turf/open/floor/mineral/plastitanium,
/area/ruin/space/has_grav/interdyne)
diff --git a/_maps/RandomRuins/SpaceRuins/meatderelict.dmm b/_maps/RandomRuins/SpaceRuins/meatderelict.dmm
index 96959954e2678..e3cdb52cad39e 100644
--- a/_maps/RandomRuins/SpaceRuins/meatderelict.dmm
+++ b/_maps/RandomRuins/SpaceRuins/meatderelict.dmm
@@ -1008,7 +1008,7 @@
/area/ruin/space/has_grav/powered/biooutpost)
"tv" = (
/obj/effect/decal/cleanable/blood/tracks,
-/mob/living/simple_animal/hostile/zombie,
+/mob/living/basic/zombie,
/turf/open/indestructible/white{
icon_state = "showroomfloor"
},
diff --git a/_maps/map_files/Birdshot/birdshot.dmm b/_maps/map_files/Birdshot/birdshot.dmm
index ff06bb5914c6b..758a5c877925e 100644
--- a/_maps/map_files/Birdshot/birdshot.dmm
+++ b/_maps/map_files/Birdshot/birdshot.dmm
@@ -26116,7 +26116,7 @@
/turf/open/floor/iron/smooth_large,
/area/station/science/robotics/mechbay)
"iPU" = (
-/mob/living/simple_animal/hostile/mimic/crate,
+/mob/living/basic/mimic/crate,
/turf/open/floor/plating,
/area/station/maintenance/fore/lesser)
"iPW" = (
diff --git a/_maps/map_files/NebulaStation/NebulaStation.dmm b/_maps/map_files/NebulaStation/NebulaStation.dmm
index a0a228bf5fe86..a386fe33b1a97 100644
--- a/_maps/map_files/NebulaStation/NebulaStation.dmm
+++ b/_maps/map_files/NebulaStation/NebulaStation.dmm
@@ -81457,12 +81457,6 @@
/obj/item/reagent_containers/cup/rag,
/turf/open/floor/iron/white/textured_large,
/area/station/maintenance/department/medical)
-"mdT" = (
-/obj/structure/disposalpipe/segment{
- dir = 4
- },
-/turf/closed/wall,
-/area/station/maintenance/fore/lesser)
"mec" = (
/obj/effect/turf_decal/siding/wood{
dir = 6
@@ -118778,10 +118772,6 @@
/obj/effect/turf_decal/siding/thinplating_new/dark/corner,
/turf/open/floor/iron/dark/herringbone,
/area/station/service/chapel/funeral)
-"rDY" = (
-/obj/structure/disposalpipe/segment,
-/turf/closed/wall,
-/area/station/maintenance/fore/greater)
"rEc" = (
/obj/effect/turf_decal/stripes/line{
dir = 5
@@ -196595,7 +196585,7 @@ fQE
cUZ
xGl
pjP
-mdT
+iEt
xnA
pQW
hAK
@@ -266242,7 +266232,7 @@ bzt
jkE
nLg
cij
-rDY
+bYy
loK
kCi
nKp
@@ -294174,4 +294164,4 @@ txW
txW
txW
txW
-"}
\ No newline at end of file
+"}
diff --git a/_maps/map_files/wawastation/wawastation.dmm b/_maps/map_files/wawastation/wawastation.dmm
index 0295a23f50395..64cad41bbebb1 100644
--- a/_maps/map_files/wawastation/wawastation.dmm
+++ b/_maps/map_files/wawastation/wawastation.dmm
@@ -840,7 +840,7 @@
/obj/effect/decal/cleanable/blood/old{
icon_state = "gib3-old"
},
-/mob/living/simple_animal/hostile/mimic,
+/mob/living/basic/mimic/crate,
/turf/open/floor/iron/white,
/area/station/maintenance/aft/upper)
"anu" = (
diff --git a/_maps/shuttles/whiteship_box.dmm b/_maps/shuttles/whiteship_box.dmm
index 9a20e38ebcd5d..c800243246908 100644
--- a/_maps/shuttles/whiteship_box.dmm
+++ b/_maps/shuttles/whiteship_box.dmm
@@ -273,15 +273,7 @@
dir = 9
},
/obj/structure/cable,
-/mob/living/simple_animal/hostile/zombie{
- desc = "This undead fiend looks to be badly decomposed.";
- environment_smash = 0;
- health = 60;
- melee_damage_lower = 11;
- melee_damage_upper = 11;
- name = "Rotting Carcass";
- outfit = /datum/outfit/corpse_assistant
- },
+/mob/living/basic/zombie/rotten/assistant,
/turf/open/floor/iron/white/corner{
dir = 1
},
@@ -360,14 +352,7 @@
/obj/effect/decal/cleanable/blood/old,
/obj/effect/decal/cleanable/dirt,
/obj/structure/cable,
-/mob/living/simple_animal/hostile/zombie{
- desc = "This undead fiend looks to be badly decomposed.";
- environment_smash = 0;
- health = 60;
- melee_damage_lower = 11;
- melee_damage_upper = 11;
- name = "Rotting Carcass"
- },
+/mob/living/basic/zombie/rotten,
/turf/open/floor/iron,
/area/shuttle/abandoned/crew)
"aQ" = (
@@ -841,14 +826,7 @@
},
/obj/effect/decal/cleanable/blood/old,
/obj/effect/decal/cleanable/dirt,
-/mob/living/simple_animal/hostile/zombie{
- desc = "This undead fiend looks to be badly decomposed.";
- environment_smash = 0;
- health = 60;
- melee_damage_lower = 11;
- melee_damage_upper = 11;
- name = "Rotting Carcass"
- },
+/mob/living/basic/zombie/rotten,
/turf/open/floor/iron,
/area/shuttle/abandoned/medbay)
"bT" = (
@@ -1449,15 +1427,7 @@
},
/obj/effect/decal/cleanable/blood/gibs/old,
/obj/structure/cable,
-/mob/living/simple_animal/hostile/zombie{
- desc = "This undead fiend looks to be badly decomposed.";
- environment_smash = 0;
- health = 60;
- melee_damage_lower = 11;
- melee_damage_upper = 11;
- name = "Rotting Carcass";
- outfit = /datum/outfit/corpse_assistant
- },
+/mob/living/basic/zombie/rotten/assistant,
/turf/open/floor/iron,
/area/shuttle/abandoned/medbay)
"dc" = (
diff --git a/_maps/shuttles/whiteship_personalshuttle.dmm b/_maps/shuttles/whiteship_personalshuttle.dmm
index 7666f6c63add1..0316e7f3aeab0 100644
--- a/_maps/shuttles/whiteship_personalshuttle.dmm
+++ b/_maps/shuttles/whiteship_personalshuttle.dmm
@@ -162,15 +162,7 @@
/obj/structure/cable,
/obj/effect/decal/cleanable/blood/splatter,
/obj/effect/decal/cleanable/dirt,
-/mob/living/simple_animal/hostile/zombie{
- desc = "This undead fiend looks to be badly decomposed.";
- environment_smash = 0;
- health = 60;
- melee_damage_lower = 11;
- melee_damage_upper = 11;
- name = "Rotting Carcass";
- outfit = /datum/outfit/corpse_assistant
- },
+/mob/living/basic/zombie/rotten/assistant,
/turf/open/floor/mineral/titanium,
/area/shuttle/abandoned/engine)
"gG" = (
@@ -195,15 +187,7 @@
/obj/effect/decal/cleanable/dirt,
/obj/structure/cable,
/obj/effect/decal/cleanable/blood/splatter,
-/mob/living/simple_animal/hostile/zombie{
- desc = "This undead fiend looks to be badly decomposed.";
- environment_smash = 0;
- health = 60;
- melee_damage_lower = 11;
- melee_damage_upper = 11;
- name = "Rotting Carcass";
- outfit = /datum/outfit/corpse_assistant
- },
+/mob/living/basic/zombie/rotten/assistant,
/turf/open/floor/plating,
/area/shuttle/abandoned/engine)
"nI" = (
@@ -260,15 +244,7 @@
/obj/effect/decal/cleanable/dirt,
/obj/effect/decal/cleanable/blood/splatter,
/obj/structure/chair/comfy/shuttle,
-/mob/living/simple_animal/hostile/zombie{
- desc = "This undead fiend looks to be badly decomposed.";
- environment_smash = 0;
- health = 60;
- melee_damage_lower = 11;
- melee_damage_upper = 11;
- name = "Rotting Carcass";
- outfit = /datum/outfit/corpse_assistant
- },
+/mob/living/basic/zombie/rotten/assistant,
/turf/open/floor/mineral/plastitanium,
/area/shuttle/abandoned/bridge)
"pS" = (
diff --git a/_maps/virtual_domains/psyker_shuffle.dmm b/_maps/virtual_domains/psyker_shuffle.dmm
index c744cecf0b430..5f303d4ae0471 100644
--- a/_maps/virtual_domains/psyker_shuffle.dmm
+++ b/_maps/virtual_domains/psyker_shuffle.dmm
@@ -64,11 +64,7 @@
/turf/template_noop,
/area/template_noop)
"r" = (
-/mob/living/simple_animal/hostile/mimic,
-/turf/open/indestructible/dark,
-/area/virtual_domain)
-"s" = (
-/mob/living/simple_animal/hostile/mimic/crate,
+/mob/living/basic/mimic/crate,
/turf/open/indestructible/dark,
/area/virtual_domain)
"t" = (
@@ -767,7 +763,7 @@ Y
Q
Q
Q
-s
+r
M
Q
Q
@@ -878,11 +874,11 @@ o
Y
a
Q
-s
+r
Y
Y
Y
-s
+r
Q
Q
Q
diff --git a/_maps/virtual_domains/psyker_zombies.dmm b/_maps/virtual_domains/psyker_zombies.dmm
index 4ca97f8ef6315..c532e87189f9c 100644
--- a/_maps/virtual_domains/psyker_zombies.dmm
+++ b/_maps/virtual_domains/psyker_zombies.dmm
@@ -114,7 +114,7 @@
/turf/open/indestructible/dark,
/area/virtual_domain)
"X" = (
-/mob/living/simple_animal/hostile/zombie,
+/mob/living/basic/zombie,
/turf/open/indestructible/dark,
/area/virtual_domain)
"Y" = (
diff --git a/code/__DEFINES/ai/monsters.dm b/code/__DEFINES/ai/monsters.dm
index d77817a203980..5ce90a7631953 100644
--- a/code/__DEFINES/ai/monsters.dm
+++ b/code/__DEFINES/ai/monsters.dm
@@ -305,6 +305,20 @@
///time till our next rest duration
#define BB_DEER_NEXT_REST_TIMER "deer_next_rest_timer"
+//the thing boss
+#define BB_THETHING_CHARGE "BB_THETHING_CHARGE"
+#define BB_THETHING_DECIMATE "BB_THETHING_DECIMATE"
+#define BB_THETHING_BIGTENDRILS "BB_THETHING_BIGTENDRILS"
+#define BB_THETHING_SHRIEK "BB_THETHING_SHRIEK"
+#define BB_THETHING_CARDTENDRILS "BB_THETHING_CARDTENDRILS"
+#define BB_THETHING_ACIDSPIT "BB_THETHING_ACIDSPIT"
+/// Blackboard key for The Thing boss that determines attack mode. TRUE means it will focus on closing the distance and murdering the person in question. Otherwise AOE.
+#define BB_THETHING_ATTACKMODE "BB_THETHING_ATTACKMODE"
+/// The Thing will be in attack mode forever if true
+#define BB_THETHING_NOAOE "BB_THETHING_NOAOE"
+/// What (first in combo) attack was last executed
+#define BB_THETHING_LASTAOE "BB_THETHING_LASTAOE"
+
//turtle
///our tree's ability
#define BB_TURTLE_TREE_ABILITY "turtle_tree_ability"
@@ -312,3 +326,5 @@
#define BB_TURTLE_HEADBUTT_VICTIM "turtle_headbutt_victim"
///flore we must smell
#define BB_TURTLE_FLORA_TARGET "turtle_flora_target"
+
+#define BB_GUNMIMIC_GUN_EMPTY "BB_GUNMIMIC_GUN_EMPTY"
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 586a67e23954c..1497960d1940e 100644
--- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm
@@ -148,3 +148,6 @@
/// From /atom/proc/set_density(new_value) for when an atom changes density
#define COMSIG_ATOM_DENSITY_CHANGED "atom_density_change"
+
+/// From /datum/component/tether/UnregisterFromParent()
+#define COMSIG_ATOM_TETHER_SNAPPED "atom_tether_snapped"
diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_mouse.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_mouse.dm
index 81cdd2c85966e..6f9b86a132688 100644
--- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_mouse.dm
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_mouse.dm
@@ -8,7 +8,8 @@
#define COMSIG_CLICK "atom_click"
///from base of atom/ShiftClick(): (/mob)
#define COMSIG_CLICK_SHIFT "shift_click"
- #define COMPONENT_ALLOW_EXAMINATE (1<<0) //! Allows the user to examinate regardless of client.eye.
+// #define COMSIG_MOB_CANCEL_CLICKON (1<<0) //shared with other forms of click, this is so you're aware it exists here too.
+ #define COMPONENT_ALLOW_EXAMINATE (1<<1) //! Allows the user to examinate regardless of client.eye.
///from base of atom/ShiftClick()
#define COMSIG_SHIFT_CLICKED_ON "shift_clicked_on"
///from base of atom/CtrlClickOn(): (/mob)
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm
index f4557f4b5c17c..8205f47fe9a46 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm
@@ -76,6 +76,9 @@
#define COMSIG_MOB_MIND_TRANSFERRED_OUT_OF "mob_mind_transferred_out_of"
/// From /mob/proc/ghostize() Called when a mob successfully ghosts
#define COMSIG_MOB_GHOSTIZED "mob_ghostized"
+/// can_roll_midround(datum/antagonist/antag_type) from certain midround rulesets, (mob/living/source, datum/mind/mind, datum/antagonist/antagonist)
+#define COMSIG_MOB_MIND_BEFORE_MIDROUND_ROLL "mob_mind_transferred_out_of"
+ #define CANCEL_ROLL (1<<1)
///from base of obj/allowed(mob/M): (/obj) returns ACCESS_ALLOWED if mob has id access to the obj
#define COMSIG_MOB_TRIED_ACCESS "tried_access"
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_megafauna.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_megafauna.dm
new file mode 100644
index 0000000000000..52ee745f66c76
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_megafauna.dm
@@ -0,0 +1,2 @@
+
+#define COMSIG_MEGAFAUNA_THETHING_PHASEUPDATED "COMSIG_MEGAFAUNA_THETHING_PHASEUPDATED"
diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm
index 2044aab51e24d..4d9c6e9c00bf9 100644
--- a/code/__DEFINES/dcs/signals/signals_object.dm
+++ b/code/__DEFINES/dcs/signals/signals_object.dm
@@ -558,19 +558,29 @@
#define COMSIG_ASSEMBLY_DETACHED "assembly_detached"
/*
- * The following two signals are separate from the above two because buttons don't set the holder of the inserted assembly.
+ * The following four signals are separate from the above two because buttons and pressure plates don't set the holder of the inserted assembly.
* This causes subtle behavioral differences that future handlers for these signals may need to account for,
* even if none of the currently implemented handlers do.
*/
-/// Sent from /obj/machinery/button/assembly_act(obj/machinery/button/button, mob/user)
+/// Sent when an assembly is added to a button : (obj/machinery/button/button, mob/user)
#define COMSIG_ASSEMBLY_ADDED_TO_BUTTON "assembly_added_to_button"
-/// Sent from /obj/machinery/button/remove_assembly(obj/machinery/button/button, mob/user)
+/// Sent when an assembly is removed from a button : (obj/machinery/button/button, mob/user)
#define COMSIG_ASSEMBLY_REMOVED_FROM_BUTTON "assembly_removed_from_button"
+/// Sent when an assembly is added to a pressure plate : (obj/item/pressureplate/pressure_plate, mob/user)
+#define COMSIG_ASSEMBLY_ADDED_TO_PRESSURE_PLATE "assembly_added_to_pressure_plate"
+
+/// Sent when an assembly is removed from a pressure plate : (obj/item/pressureplate/pressure_plate, mob/user)
+#define COMSIG_ASSEMBLY_REMOVED_FROM_PRESSURE_PLATE "assembly_removed_from_pressure_playe"
+
/// Sent from /datum/powernet/add_cable()
#define COMSIG_CABLE_ADDED_TO_POWERNET "cable_added_to_powernet"
/// Sent from /datum/powernet/remove_cable()
#define COMSIG_CABLE_REMOVED_FROM_POWERNET "cable_removed_from_powernet"
+
+/// Sent from /datum/wires/attach_assembly() : (atom/holder)
+#define COMSIG_ASSEMBLY_PRE_ATTACH "assembly_pre_attach"
+ #define COMPONENT_CANCEL_ATTACH (1<<0)
diff --git a/code/__DEFINES/fish.dm b/code/__DEFINES/fish.dm
index b71c8aad38272..2339935d6ff4f 100644
--- a/code/__DEFINES/fish.dm
+++ b/code/__DEFINES/fish.dm
@@ -6,6 +6,8 @@
#define FISHING_RANDOM_ORGAN "Random organ"
///Used in the dimensional rift fishing spot to define influence gain
#define FISHING_INFLUENCE "Influence"
+///Used in the dimensional rift fishing spot to define arm procurement
+#define FISHING_RANDOM_ARM "arm"
///Represents the chance of getting squashed by the vending machine from the vending machine fish source
#define FISHING_VENDING_CHUCK "thinkfastchucklenuts"
diff --git a/code/__DEFINES/id_cards.dm b/code/__DEFINES/id_cards.dm
index a42016dd3de3f..2d720630ce637 100644
--- a/code/__DEFINES/id_cards.dm
+++ b/code/__DEFINES/id_cards.dm
@@ -42,3 +42,22 @@
* Used to crop the ID card's transparency away when chaching the icon for better use in tgui chat.
*/
#define ID_ICON_BORDERS 1, 9, 32, 24
+
+///Honorific will display next to the first name.
+#define HONORIFIC_POSITION_FIRST (1<<0)
+///Honorific will display next to the last name.
+#define HONORIFIC_POSITION_LAST (1<<1)
+///Honorific will not be displayed.
+#define HONORIFIC_POSITION_NONE (1<<2)
+///Honorific will be appended to the full name at the start.
+#define HONORIFIC_POSITION_FIRST_FULL (1<<3)
+///Honorific will be appended to the full name at the end.
+#define HONORIFIC_POSITION_LAST_FULL (1<<4)
+
+#define HONORIFIC_POSITION_BITFIELDS(...) list( \
+ "Honorific + First Name" = HONORIFIC_POSITION_FIRST, \
+ "Honorific + Last Name" = HONORIFIC_POSITION_LAST, \
+ "Honorific + Full Name" = HONORIFIC_POSITION_FIRST_FULL, \
+ "Full Name + Honorific" = HONORIFIC_POSITION_LAST_FULL, \
+ "Disable Honorific" = HONORIFIC_POSITION_NONE, \
+)
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index 063a864ac419e..c2990efe60f31 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -196,7 +196,7 @@ GLOBAL_LIST_INIT(turfs_pass_meteor, typecacheof(list(
#define isguardian(A) (istype(A, /mob/living/basic/guardian))
-#define ismegafauna(A) (istype(A, /mob/living/simple_animal/hostile/megafauna))
+#define ismegafauna(A) (istype(A, /mob/living/simple_animal/hostile/megafauna) || istype(A, /mob/living/basic/boss))
#define isclown(A) (istype(A, /mob/living/basic/clown))
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index a37ab37793131..ba5349810cb44 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -338,6 +338,9 @@
//Slime extract crossing. Controls how many extracts is required to feed to a slime to core-cross.
#define SLIME_EXTRACT_CROSSING_REQUIRED 10
+//How many slimes can be on the same tile before it can no longer reproduce.
+#define SLIME_OVERCROWD_AMOUNT 2
+
//Slime commands defines
#define SLIME_FRIENDSHIP_FOLLOW 3 //Min friendship to order it to follow
#define SLIME_FRIENDSHIP_STOPEAT 5 //Min friendship to order it to stop eating someone
diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm
index c3bd425af0a65..d80e0d1af2b80 100644
--- a/code/__DEFINES/say.dm
+++ b/code/__DEFINES/say.dm
@@ -129,3 +129,6 @@
///Defines for priorities for the bubble_icon_override comp
#define BUBBLE_ICON_PRIORITY_ACCESSORY 2
#define BUBBLE_ICON_PRIORITY_ORGAN 1
+
+/// Sent from /atom/movable/proc/compose_message() to find an honorific. Compatible with NAME_PART_INDEX: (list/stored_name, mob/living/carbon/carbon_human)
+#define COMSIG_ID_GET_HONORIFIC "id_get_honorific"
diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
index e2fdf01e9aaba..46987974d8049 100644
--- a/code/__DEFINES/status_effects.dm
+++ b/code/__DEFINES/status_effects.dm
@@ -26,12 +26,10 @@
//several flags for the Necropolis curse status effect
///makes the edges of the target's screen obscured
#define CURSE_BLINDING (1<<0)
-///spawns creatures that attack the target only
-#define CURSE_SPAWNING (1<<1)
///causes gradual damage
-#define CURSE_WASTING (1<<2)
+#define CURSE_WASTING (1<<1)
///hands reach out from the sides of the screen, doing damage and stunning if they hit the target
-#define CURSE_GRASPING (1<<3)
+#define CURSE_GRASPING (1<<2)
//Incapacitated status effect flags
/// If the mob is normal incapacitated. Should never need this, just avoids issues if we ever overexpand this
diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm
index dc3d6fa0c1d70..04801bc8a151c 100644
--- a/code/__DEFINES/traits/declarations.dm
+++ b/code/__DEFINES/traits/declarations.dm
@@ -1148,7 +1148,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define STATION_TRAIT_SPIDER_INFESTATION "station_trait_spider_infestation"
#define STATION_TRAIT_UNIQUE_AI "station_trait_unique_ai"
#define STATION_TRAIT_UNNATURAL_ATMOSPHERE "station_trait_unnatural_atmosphere"
-#define STATION_TRAIT_VENDING_SHORTAGE "station_trait_vending_shortage"
#define STATION_TRAIT_SPIKED_DRINKS "station_trait_spiked_drinks"
///Deathmatch traits
@@ -1412,6 +1411,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
///Trait given to atoms currently affected by projectile dampeners
#define TRAIT_GOT_DAMPENED "got_dampened"
+/// humans with this trait will have their health visible to AIs without suit
+#define HUMAN_SENSORS_VISIBLE_WITHOUT_SUIT "hmsensorsvisiblewithoutsuit"
/// Apply to movables to say "hey, this movable is technically flat on the floor, so it'd be mopped up by a mop"
#define TRAIT_MOPABLE "mopable"
@@ -1424,4 +1425,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// Mob doesn't get closed eyelids overlay when it gets knocked out cold or dies
#define TRAIT_NO_EYELIDS "no_eyelids"
+/// Trait applied when the wire bundle component is added to an [/obj/item/integrated_circuit]
+#define TRAIT_COMPONENT_WIRE_BUNDLE "component_wire_bundle"
+
// END TRAIT DEFINES
diff --git a/code/__DEFINES/wires.dm b/code/__DEFINES/wires.dm
index 4926996f26bd3..034bb8897e6e6 100644
--- a/code/__DEFINES/wires.dm
+++ b/code/__DEFINES/wires.dm
@@ -2,6 +2,30 @@
#define COMSIG_CUT_WIRE(wire) "cut_wire [wire]"
#define COMSIG_MEND_WIRE(wire) "mend_wire [wire]"
+/// from base of /datum/wires/proc/on_pulse : (wire, mob/user)
+#define COMSIG_PULSE_WIRE "pulse_wire"
+
+// Directionality of wire pulses
+
+/// The wires interact with their holder when pulsed
+#define WIRES_INPUT (1<<0)
+/// The wires have a reason to toggle whether attached assemblies are armed
+#define WIRES_TOGGLE_ARMED (1<<1)
+/// The wires only want to activate assemblies that do something other than (dis)arming themselves
+#define WIRES_FUNCTIONAL_OUTPUT (1<<2)
+/// The holder can both pulse its wires and be affected by its wires getting pulsed
+#define WIRES_ALL (WIRES_INPUT | WIRES_TOGGLE_ARMED | WIRES_FUNCTIONAL_OUTPUT)
+
+/// The assembly can pulse a wire it is attached to
+#define ASSEMBLY_INPUT (1<<0)
+/// The assembly toggles whether it will pulse the attached wire when it is pulsed by the attached wire
+#define ASSEMBLY_TOGGLE_ARMED (1<<1)
+/// The assembly does something other than just (dis)arming itself when it is pulsed by the wire it is attached to
+#define ASSEMBLY_FUNCTIONAL_OUTPUT (1<<2)
+/// The assembly can both pulse the wire it is attached to, and (dis)arms itself when pulsed by the wire
+#define ASSEMBLY_TOGGLEABLE_INPUT (ASSEMBLY_INPUT | ASSEMBLY_TOGGLE_ARMED)
+#define ASSEMBLY_ALL (ASSEMBLY_TOGGLEABLE_INPUT | ASSEMBLY_FUNCTIONAL_OUTPUT)
+
//retvals for attempt_wires_interaction
#define WIRE_INTERACTION_FAIL 0
#define WIRE_INTERACTION_SUCCESSFUL 1
@@ -72,3 +96,5 @@
#define AI_WIRE_DISABLED 1
#define AI_WIRE_HACKED 2
#define AI_WIRE_DISABLED_HACKED -1
+
+#define MAX_WIRE_COUNT 17
diff --git a/code/__HELPERS/names.dm b/code/__HELPERS/names.dm
index 2748d4fbccbd7..6a1093c57511f 100644
--- a/code/__HELPERS/names.dm
+++ b/code/__HELPERS/names.dm
@@ -375,3 +375,22 @@ GLOBAL_DATUM(syndicate_code_response_regex, /regex)
return "a rolling pin"
else
return "something... but the gods didn't set this up right (Please report this bug)"
+
+///Find the first name of a mob from a passed string with regex
+/proc/first_name(given_name)
+ var/static/regex/firstname = new("^\[^\\s-\]+") //First word before whitespace or "-"
+ firstname.Find(given_name)
+ return firstname.match
+
+/// Find the last name of a mob from a passed string with regex
+/proc/last_name(given_name)
+ var/static/regex/lasttname = new("\[^\\s-\]+$") //First word before whitespace or "-"
+ lasttname.Find(given_name)
+ return lasttname.match
+
+/// Find whitespace or dashes in the passed string with regex and returns TRUE if found
+/proc/is_mononym(given_name)
+ var/static/regex/breaks = regex(@"\s")
+ if(breaks.Find(given_name))
+ return FALSE
+ return TRUE
diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm
index 5ece846e84de3..6024f7991388d 100644
--- a/code/_globalvars/lists/mobs.dm
+++ b/code/_globalvars/lists/mobs.dm
@@ -29,7 +29,7 @@ GLOBAL_LIST_INIT(abstract_mob_types, list(
/mob/living/simple_animal/hostile/asteroid/elite,
/mob/living/simple_animal/hostile/asteroid,
/mob/living/simple_animal/hostile/megafauna,
- /mob/living/simple_animal/hostile/mimic, // Cannot exist if spawned without being passed an item reference
+ /mob/living/basic/mimic, // Cannot exist if spawned without being passed an item reference
/mob/living/simple_animal/hostile/retaliate,
/mob/living/simple_animal/hostile,
/mob/living/simple_animal/soulscythe, // As mimic, can't exist if spawned outside an item
diff --git a/code/_globalvars/phobias.dm b/code/_globalvars/phobias.dm
index d6677a213a48a..c482722fbf901 100644
--- a/code/_globalvars/phobias.dm
+++ b/code/_globalvars/phobias.dm
@@ -8,13 +8,12 @@ GLOBAL_LIST_INIT(phobia_types, sort_list(list(
"authority",
"birds",
"blood",
- "carps",
"clowns",
"doctors",
"falling",
+ "fish",
"greytide",
"guns",
- "heresy",
"insects",
"lizards",
"robots",
@@ -33,14 +32,13 @@ GLOBAL_LIST_INIT(phobia_regexes, list(
"authority" = construct_phobia_regex("authority"),
"birds" = construct_phobia_regex("birds"),
"blood" = construct_phobia_regex("blood"),
- "carps" = construct_phobia_regex("carps"),
"clowns" = construct_phobia_regex("clowns"),
"conspiracies" = construct_phobia_regex("conspiracies"),
"doctors" = construct_phobia_regex("doctors"),
"falling" = construct_phobia_regex("falling"),
+ "fish" = construct_phobia_regex("fish"),
"greytide" = construct_phobia_regex("greytide"),
"guns" = construct_phobia_regex("guns"),
- "heresy" = construct_phobia_regex("heresy"),
"insects" = construct_phobia_regex("insects"),
"lizards" = construct_phobia_regex("lizards"),
"ocky icky" = construct_phobia_regex("ocky icky"),
@@ -56,49 +54,65 @@ GLOBAL_LIST_INIT(phobia_regexes, list(
GLOBAL_LIST_INIT(phobia_mobs, list(
"aliens" = typecacheof(list(
- /mob/living/carbon/alien,
+ /mob/living/basic/alien,
/mob/living/basic/slime,
+ /mob/living/carbon/alien,
+ )),
+ "anime" = typecacheof(list(
+ /mob/living/basic/guardian,
+ /mob/living/basic/migo/hatsune
+ )),
+ "authority" = typecacheof(list(
+ /mob/living/basic/trooper/nanotrasen,
+ /mob/living/simple_animal/bot/secbot,
)),
- "anime" = typecacheof(list(/mob/living/basic/guardian)),
"birds" = typecacheof(list(
/mob/living/basic/chick,
/mob/living/basic/chicken,
/mob/living/basic/parrot,
/mob/living/basic/pet/penguin,
+ /mob/living/basic/raptor,
/mob/living/simple_animal/hostile/retaliate/goose,
)),
- "carps" = typecacheof(list(
- /mob/living/basic/carp,
- /mob/living/basic/space_dragon,
- )),
"conspiracies" = typecacheof(list(
/mob/living/basic/drone,
/mob/living/basic/pet/penguin,
/mob/living/simple_animal/bot/secbot,
)),
"doctors" = typecacheof(list(/mob/living/basic/bot/medbot)),
- "heresy" = typecacheof(list(
- /mob/living/basic/heretic_summon,
+ "fish" = typecacheof(list(
+ /mob/living/basic/carp,
+ /mob/living/basic/space_dragon,
)),
"insects" = typecacheof(list(
- /mob/living/basic/cockroach,
+ /mob/living/basic/ant,
/mob/living/basic/bee,
+ /mob/living/basic/butterfly,
+ /mob/living/basic/cockroach,
+ /mob/living/basic/mega_arachnid,
+ /mob/living/basic/mothroach,
)),
"lizards" = typecacheof(list(/mob/living/basic/lizard)),
"robots" = typecacheof(list(
+ /mob/living/basic/bot,
/mob/living/basic/drone,
/mob/living/silicon/ai,
/mob/living/silicon/robot,
/mob/living/simple_animal/bot,
)),
"security" = typecacheof(list(/mob/living/simple_animal/bot/secbot)),
- "spiders" = typecacheof(list(/mob/living/basic/spider/giant)),
+ "spiders" = typecacheof(list(
+ /mob/living/basic/flesh_spider,
+ /mob/living/basic/mega_arachnid,
+ /mob/living/basic/spider/giant,
+ )),
"skeletons" = typecacheof(list(/mob/living/basic/skeleton)),
"snakes" = typecacheof(list(/mob/living/basic/snake)),
"the supernatural" = typecacheof(list(
/mob/dead/observer,
/mob/living/basic/bat,
/mob/living/basic/construct,
+ /mob/living/basic/dark_wizard,
/mob/living/basic/demon,
/mob/living/basic/faithless,
/mob/living/basic/ghost,
@@ -107,9 +121,8 @@ GLOBAL_LIST_INIT(phobia_mobs, list(
/mob/living/basic/shade,
/mob/living/basic/skeleton,
/mob/living/basic/wizard,
+ /mob/living/basic/zombie,
/mob/living/simple_animal/bot/mulebot/paranormal,
- /mob/living/simple_animal/hostile/dark_wizard,
- /mob/living/simple_animal/hostile/zombie,
)),
))
@@ -136,6 +149,7 @@ GLOBAL_LIST_INIT(phobia_objs, list(
/obj/item/toy/toy_xeno,
/obj/item/toy/plush/abductor,
/obj/item/toy/plush/abductor/agent,
+ /obj/item/toy/plush/rouny,
/obj/item/weldingtool/abductor,
/obj/item/wirecutters/abductor,
/obj/item/wrench/abductor,
@@ -234,35 +248,16 @@ GLOBAL_LIST_INIT(phobia_objs, list(
/obj/item/clothing/under/costume/griffin,
/obj/item/clothing/under/costume/owl,
/obj/item/food/cracker,
+ /obj/item/food/egg,
)),
"blood" = typecacheof(list(
/obj/effect/decal/cleanable/blood,
+ /obj/item/food/fried_blood_sausage,
+ /obj/item/food/tiziran_sausage,
/obj/item/reagent_containers/blood,
/obj/item/reagent_containers/syringe,
/obj/machinery/iv_drip,
)),
- "carps" = typecacheof(list(
- /obj/item/clothing/head/hooded/carp_hood,
- /obj/item/clothing/suit/hooded/carp_costume,
- /obj/item/clothing/head/fedora/carpskin,
- /obj/item/clothing/mask/gas/carp,
- /obj/item/cigarette/carp,
- /obj/item/clothing/under/suit/carpskin,
- /obj/item/fish/baby_carp,
- /obj/item/food/cubancarp,
- /obj/item/food/fishmeat/carp,
- /obj/item/grenade/clusterbuster/spawner_spesscarp,
- /obj/item/grenade/spawnergrenade/spesscarp,
- /obj/item/knife/carp,
- /obj/item/nullrod/carp,
- /obj/item/organ/lungs/carp,
- /obj/item/organ/tongue/carp,
- /obj/item/organ/brain/carp,
- /obj/item/organ/heart/carp,
- /obj/item/storage/fancy/cigarettes/cigpack_carp,
- /obj/item/stack/sheet/animalhide/carp,
- /obj/item/toy/plush/carpplushie,
- )),
"clowns" = typecacheof(list(
/obj/item/bedsheet/clown,
/obj/item/clothing/head/chaplain/clownmitre,
@@ -273,7 +268,9 @@ GLOBAL_LIST_INIT(phobia_objs, list(
/obj/item/clothing/under/plasmaman/clown,
/obj/item/clothing/gloves/color/plasmaman/clown,
/obj/item/clothing/under/rank/civilian/clown,
+ /obj/item/food/burger/clown,
/obj/item/food/cheesiehonkers,
+ /obj/item/food/meatclown,
/obj/item/food/pie/cream,
/obj/item/grown/bananapeel,
/obj/item/gun/magic/staff/honk,
@@ -369,6 +366,41 @@ GLOBAL_LIST_INIT(phobia_objs, list(
/obj/machinery/stasis,
/obj/structure/sign/departments/medbay,
)),
+ "fish" = typecacheof(list(
+ /obj/item/clothing/head/hooded/carp_hood,
+ /obj/item/clothing/suit/hooded/carp_costume,
+ /obj/item/clothing/head/fedora/carpskin,
+ /obj/item/clothing/mask/gas/carp,
+ /obj/item/clothing/head/soft/fishing_hat,
+ /obj/item/cigarette/carp,
+ /obj/item/clothing/under/suit/carpskin,
+ /obj/item/fish,
+ /obj/item/fishing_hook,
+ /obj/item/fishing_rod,
+ /obj/item/food/cubancarp,
+ /obj/item/food/fishandchips,
+ /obj/item/food/fishfingers,
+ /obj/item/food/fishfry,
+ /obj/item/food/fishmeat,
+ /obj/item/food/nugget/fish,
+ /obj/item/food/volt_fish,
+ /obj/item/grenade/clusterbuster/spawner_spesscarp,
+ /obj/item/grenade/spawnergrenade/spesscarp,
+ /obj/item/knife/carp,
+ /obj/item/nullrod/carp,
+ /obj/item/organ/brain/carp,
+ /obj/item/organ/heart/carp,
+ /obj/item/organ/liver/fish,
+ /obj/item/organ/lungs/carp,
+ /obj/item/organ/lungs/fish,
+ /obj/item/organ/stomach/fish,
+ /obj/item/organ/tail/fish,
+ /obj/item/organ/tongue/carp,
+ /obj/item/storage/fancy/cigarettes/cigpack_carp,
+ /obj/item/storage/toolbox/fishing,
+ /obj/item/stack/sheet/animalhide/carp,
+ /obj/item/toy/plush/carpplushie,
+ )),
"greytide" = (typecacheof(list(
/obj/item/clothing/under/color/grey,
/obj/item/melee/baton/security/cattleprod,
@@ -394,35 +426,6 @@ GLOBAL_LIST_INIT(phobia_objs, list(
/obj/machinery/porta_turret,
/obj/machinery/power/emitter,
)),
- "heresy" = typecacheof(list(
- /obj/effect/floating_blade,
- /obj/effect/forcefield/cosmic_field,
- /obj/effect/forcefield/wizard/heretic,
- /obj/effect/heretic_influence,
- /obj/effect/heretic_rune,
- /obj/effect/lock_portal,
- /obj/effect/visible_heretic_influence,
- /obj/item/ammo_box/strilka310/lionhunter,
- /obj/item/ammo_casing/strilka310/lionhunter,
- /obj/item/clothing/mask/madness_mask,
- /obj/item/clothing/neck/eldritch_amulet,
- /obj/item/clothing/neck/fake_heretic_amulet,
- /obj/item/clothing/neck/heretic_focus,
- /obj/item/clothing/suit/hooded/cultrobes/eldritch,
- /obj/item/codex_cicatrix,
- /obj/item/coin/eldritch,
- /obj/item/gun/ballistic/rifle/lionhunter,
- /obj/item/heretic_labyrinth_handbook,
- /obj/item/melee/rune_carver,
- /obj/item/melee/sickly_blade,
- /obj/item/melee/touch_attack/mansus_fist,
- /obj/item/reagent_containers/cup/beaker/eldritch,
- /obj/item/toy/eldritch_book,
- /obj/item/toy/reality_pierce,
- /obj/projectile/curse_hand,
- /obj/structure/destructible/eldritch_crucible,
- /obj/structure/lock_tear,
- )),
"insects" = typecacheof(list(
/obj/item/clothing/mask/animal/small/bee,
/obj/item/clothing/suit/hooded/bee_costume,
@@ -445,6 +448,7 @@ GLOBAL_LIST_INIT(phobia_objs, list(
"robots" = typecacheof(list(
/obj/item/ai_module,
/obj/item/aicard,
+ /obj/item/mmi/posibrain,
/obj/item/toy/figure/borg,
/obj/item/toy/talking/ai,
/obj/machinery/computer/upload,
@@ -475,6 +479,7 @@ GLOBAL_LIST_INIT(phobia_objs, list(
"skeletons" = typecacheof(list(
/obj/effect/decal/remains/human,
/obj/item/clothing/suit/armor/bone,
+ /obj/item/dog_bone,
/obj/item/food/meat/slab/human/mutant/skeleton,
/obj/item/organ/tongue/bone,
/obj/item/stack/sheet/bone,
@@ -571,6 +576,8 @@ GLOBAL_LIST_INIT(phobia_species, list(
/datum/species/plasmaman,
/datum/species/skeleton,
)),
+ "space" = typecacheof(list(/datum/species/voidwalker)),
+ "supernatural" = typecacheof(list(/datum/species/voidwalker))
))
/// Creates a regular expression to match against the given phobia
@@ -584,6 +591,6 @@ GLOBAL_LIST_INIT(phobia_species, list(
for(var/word in words)
words_match += "[REGEX_QUOTE(word)]|"
words_match = copytext(words_match, 1, -1)
- return regex("(\\b|\\A)([words_match])('?s*)(\\b|\\|)", "ig")
+ return regex("(\\b|\\A)([words_match])('?s*)(\\b|\\|)", "i")
#undef PHOBIA_FILE
diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm
index 46f3597251495..f79c442505c69 100644
--- a/code/_globalvars/traits/_traits.dm
+++ b/code/_globalvars/traits/_traits.dm
@@ -120,7 +120,6 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"STATION_TRAIT_SPIDER_INFESTATION" = STATION_TRAIT_SPIDER_INFESTATION,
"STATION_TRAIT_UNIQUE_AI" = STATION_TRAIT_UNIQUE_AI,
"STATION_TRAIT_UNNATURAL_ATMOSPHERE" = STATION_TRAIT_UNNATURAL_ATMOSPHERE,
- "STATION_TRAIT_VENDING_SHORTAGE" = STATION_TRAIT_VENDING_SHORTAGE,
"STATION_TRAIT_SPIKED_DRINKS" = STATION_TRAIT_SPIKED_DRINKS,
),
/datum/deathmatch_lobby = list(
@@ -590,6 +589,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_IGNORE_FIRE_PROTECTION" = TRAIT_IGNORE_FIRE_PROTECTION,
"TRAIT_LEFT_EYE_SCAR" = TRAIT_LEFT_EYE_SCAR,
"TRAIT_RIGHT_EYE_SCAR" = TRAIT_RIGHT_EYE_SCAR,
+ "HUMAN_SENSORS_VISIBLE_WITHOUT_SUIT" = HUMAN_SENSORS_VISIBLE_WITHOUT_SUIT,
"TRAIT_PREVENT_BLINKING" = TRAIT_PREVENT_BLINKING,
"TRAIT_PREVENT_BLINK_LOOPS" = TRAIT_PREVENT_BLINK_LOOPS,
"TRAIT_NO_EYELIDS" = TRAIT_NO_EYELIDS,
@@ -694,6 +694,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_CIRCUIT_UI_OPEN" = TRAIT_CIRCUIT_UI_OPEN,
"TRAIT_CIRCUIT_UNDUPABLE" = TRAIT_CIRCUIT_UNDUPABLE,
"TRAIT_COMPONENT_MMI" = TRAIT_COMPONENT_MMI,
+ "TRAIT_COMPONENT_WIRE_BUNDLE" = TRAIT_COMPONENT_WIRE_BUNDLE,
),
/obj/item/modular_computer = list(
"TRAIT_MODPC_HALVED_DOWNLOAD_SPEED" = TRAIT_MODPC_HALVED_DOWNLOAD_SPEED,
diff --git a/code/_onclick/ai.dm b/code/_onclick/ai.dm
index 2238f43cc10d1..3e41d019fada4 100644
--- a/code/_onclick/ai.dm
+++ b/code/_onclick/ai.dm
@@ -115,7 +115,9 @@
target.AICtrlShiftClick(src)
/mob/living/silicon/ai/ShiftClickOn(atom/target)
- target.AIShiftClick(src)
+ if(target.AIShiftClick(src))
+ return
+ return ..()
/mob/living/silicon/ai/CtrlClickOn(atom/target)
target.AICtrlClick(src)
@@ -154,7 +156,7 @@
return
/atom/proc/AIShiftClick(mob/living/silicon/ai/user)
- return
+ return FALSE
/atom/proc/AICtrlShiftClick(mob/living/silicon/ai/user)
return
@@ -179,10 +181,11 @@
/obj/machinery/door/airlock/AIShiftClick(mob/living/silicon/ai/user) // Opens and closes doors!
if(obj_flags & EMAGGED)
- return
+ return FALSE
user_toggle_open(user)
add_hiddenprint(user)
+ return TRUE
/obj/machinery/door/airlock/AICtrlShiftClick(mob/living/silicon/ai/user) // Sets/Unsets Emergency Access Override
if(obj_flags & EMAGGED)
@@ -222,10 +225,10 @@
/// Toggle APC lighting settings
/obj/machinery/power/apc/AIShiftClick(mob/living/silicon/ai/user)
if(!can_use(user, loud = TRUE))
- return
+ return FALSE
if(!is_operational || failure_timer)
- return
+ return FALSE
lighting = lighting ? APC_CHANNEL_OFF : APC_CHANNEL_ON
if (user)
@@ -235,6 +238,7 @@
user.log_message("turned [enabled_or_disabled] the [src] lighting settings", LOG_GAME)
update_appearance()
update()
+ return TRUE
/// Toggle APC equipment settings
/obj/machinery/power/apc/ai_click_alt(mob/living/silicon/ai/user)
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index ff9a1fc54eb1b..0981c6f251839 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -339,10 +339,10 @@
/atom/proc/ShiftClick(mob/user)
SEND_SIGNAL(src, COMSIG_SHIFT_CLICKED_ON, user)
- var/flags = SEND_SIGNAL(user, COMSIG_CLICK_SHIFT, src)
- if(flags & COMSIG_MOB_CANCEL_CLICKON)
+ var/shiftclick_flags = SEND_SIGNAL(user, COMSIG_CLICK_SHIFT, src)
+ if(shiftclick_flags & COMSIG_MOB_CANCEL_CLICKON)
return
- if(user.client && (user.client.eye == user || user.client.eye == user.loc || flags & COMPONENT_ALLOW_EXAMINATE))
+ if(user.client && (user.client.eye == user || user.client.eye == user.loc || shiftclick_flags & COMPONENT_ALLOW_EXAMINATE))
user.examinate(src)
/mob/proc/TurfAdjacent(turf/tile)
diff --git a/code/_onclick/hud/action_button.dm b/code/_onclick/hud/action_button.dm
index 5d092cbc1f6b5..72639cc3668b7 100644
--- a/code/_onclick/hud/action_button.dm
+++ b/code/_onclick/hud/action_button.dm
@@ -323,9 +323,15 @@
/atom/movable/screen/button_palette/proc/disable_landing()
// If we have no elements in the palette, hide your ugly self please
- if (!length(our_hud.palette_actions?.actions))
+ if (!length(our_hud.palette_actions?.actions) && !length(our_hud.floating_actions))
invisibility = INVISIBILITY_ABSTRACT
+/atom/movable/screen/button_palette/proc/update_state()
+ if (length(our_hud.floating_actions))
+ activate_landing()
+ else
+ disable_landing()
+
/atom/movable/screen/button_palette/MouseEntered(location, control, params)
. = ..()
if(QDELETED(src))
diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm
index b916a83b94757..a770298517908 100644
--- a/code/_onclick/hud/hud.dm
+++ b/code/_onclick/hud/hud.dm
@@ -557,6 +557,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
floating_actions += button
button.screen_loc = position
position = SCRN_OBJ_FLOATING
+ toggle_palette.update_state()
button.location = position
@@ -575,6 +576,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
position_action(button, button.linked_action.default_button_position)
return
button.screen_loc = get_valid_screen_location(relative_to.screen_loc, ICON_SIZE_ALL, our_client.view_size.getView()) // Asks for a location adjacent to our button that won't overflow the map
+ toggle_palette.update_state()
button.location = relative_to.location
@@ -585,6 +587,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
CRASH("We just tried to hide an action buttion that somehow has the default position as its location, you done fucked up")
if(SCRN_OBJ_FLOATING)
floating_actions -= button
+ toggle_palette.update_state()
if(SCRN_OBJ_IN_LIST)
listed_actions.remove_action(button)
if(SCRN_OBJ_IN_PALETTE)
diff --git a/code/_onclick/hud/rendering/plane_masters/plane_master_subtypes.dm b/code/_onclick/hud/rendering/plane_masters/plane_master_subtypes.dm
index 582253e0b926b..96a02afb5749b 100644
--- a/code/_onclick/hud/rendering/plane_masters/plane_master_subtypes.dm
+++ b/code/_onclick/hud/rendering/plane_masters/plane_master_subtypes.dm
@@ -380,7 +380,7 @@
/atom/movable/screen/plane_master/ghost
name = "Ghost"
- documentation = "Ghosts draw here, so they don't get mixed up in the visuals of the game world. Note, this is not not how we HIDE ghosts from people, that's done with invisible and see_invisible."
+ documentation = "Ghosts draw here, so they don't get mixed up in the visuals of the game world. Note, this is not how we HIDE ghosts from people, that's done with invisible and see_invisible."
plane = GHOST_PLANE
render_relay_planes = list(RENDER_PLANE_NON_GAME)
diff --git a/code/controllers/subsystem/dynamic/dynamic.dm b/code/controllers/subsystem/dynamic/dynamic.dm
index 15f2880ecfb2f..8a6de782bc0a3 100644
--- a/code/controllers/subsystem/dynamic/dynamic.dm
+++ b/code/controllers/subsystem/dynamic/dynamic.dm
@@ -1,9 +1,3 @@
-#define FAKE_GREENSHIFT_FORM_CHANCE 15
-#define FAKE_REPORT_CHANCE 8
-#define PULSAR_REPORT_CHANCE 8
-#define REPORT_NEG_DIVERGENCE -15
-#define REPORT_POS_DIVERGENCE 15
-
// Are HIGH_IMPACT_RULESETs allowed to stack?
GLOBAL_VAR_INIT(dynamic_no_stacking, TRUE)
// If enabled does not accept or execute any rulesets.
@@ -195,10 +189,6 @@ SUBSYSTEM_DEF(dynamic)
/// Used for choosing different midround injections.
var/list/current_midround_rulesets
- /// The amount of threat shown on the piece of paper.
- /// Can differ from the actual threat amount.
- var/shown_threat
-
VAR_PRIVATE/next_midround_injection
/datum/controller/subsystem/dynamic/proc/admin_panel()
@@ -337,7 +327,7 @@ SUBSYSTEM_DEF(dynamic)
continue
min_threat = min(ruleset.cost, min_threat)
- var/greenshift = GLOB.dynamic_forced_extended || (threat_level < min_threat && shown_threat < min_threat) //if both shown and real threat are below any ruleset, its extended time
+ var/greenshift = GLOB.dynamic_forced_extended || (threat_level < min_threat) //if threat is below any ruleset, its extended time
SSstation.generate_station_goals(greenshift ? INFINITY : CONFIG_GET(number/station_goal_budget))
var/list/datum/station_goal/goals = SSstation.get_station_goals()
@@ -381,43 +371,10 @@ SUBSYSTEM_DEF(dynamic)
/// Generate the advisory level depending on the shown threat level.
/datum/controller/subsystem/dynamic/proc/generate_advisory_level()
var/advisory_string = ""
- if(prob(PULSAR_REPORT_CHANCE))
- for(var/datum/station_trait/our_trait as anything in shuffle(SSstation.station_traits))
- advisory_string += our_trait.get_pulsar_message()
- if(length(advisory_string))
- return advisory_string
-
- advisory_string += "Advisory Level: Pulsar Star "
- //advisory_string += "Your sector's advisory level is Pulsar Star. A large, unknown electromagnetic field has stormed through nearby surveillance equipment, causing major data loss. Partial data was recovered and showed no credible threats to Nanotrasen assets within the Spinward Sector; however, the Department of Intelligence advises maintaining high alert against potential threats due to the lack of complete data." // ORIGINAL
- advisory_string += "Your sector's advisory level is Pulsar Star. A large, unknown electromagnetic field has stormed through nearby surveillance equipment, causing major data loss. Partial data was recovered and showed no credible threats to Port Authority assets within Crusoe's Rest; however, the Department of Intelligence advises maintaining high alert against potential threats due to the lack of complete data." // DOPPLER EDIT - NT -> PA
- return advisory_string
- //a white dwarf shift leads to a green security alert on report and special announcement, this prevents a meta check if the alert report is fake or not.
- if(round(shown_threat) == 0 && round(threat_level) == 0)
- advisory_string += "Advisory Level: White Dwarf "
- advisory_string += "Your sector's advisory level is White Dwarf. Our surveillance has ruled out any and all potential threats known in our database, eliminating most risks to our assets in the Spinward Sector. We advise a lower level of security, alongside distributing resources on potential profit."
- return advisory_string
-
- switch(round(shown_threat))
- if(0 to 19)
- var/show_core_territory = (GLOB.current_living_antags.len > 0)
- if (prob(FAKE_GREENSHIFT_FORM_CHANCE))
- show_core_territory = !show_core_territory
-
- if (show_core_territory)
- advisory_string += "Advisory Level: Blue Star "
- //advisory_string += "Your sector's advisory level is Blue Star. At this threat advisory, the risk of attacks on Nanotrasen assets within the sector is minor but cannot be ruled out entirely. Remain vigilant." // ORIGINAL
- advisory_string += "Your sector's advisory level is Blue Star. At this threat advisory, the risk of attacks on Port Authority assets within the sector is minor but cannot be ruled out entirely. Remain vigilant." // DOPPLER EDIT - NT -> PA
- else
- advisory_string += "Advisory Level: Green Star "
- //advisory_string += "Your sector's advisory level is Green Star. Surveillance information shows no credible threats to Nanotrasen assets within the Spinward Sector at this time. As always, the Department of Intelligence advises maintaining vigilance against potential threats, regardless of a lack of known threats." // ORIGINAL
- advisory_string += "Your sector's advisory level is Green Star. Surveillance information shows no credible threats to Port Authority assets within Crusoe's Rest at this time. As always, the Department of Intelligence advises maintaining vigilance against potential threats, regardless of a lack of known threats." // DOPPLER EDIT - NT -> PA
- if(20 to 39)
+ switch(round(threat_level))
+ if(0 to 65)
advisory_string += "Advisory Level: Yellow Star "
- //advisory_string += "Your sector's advisory level is Yellow Star. Surveillance shows a credible risk of enemy attack against our assets in the Spinward Sector. We advise a heightened level of security alongside maintaining vigilance against potential threats." // ORIGINAL
- advisory_string += "Your sector's advisory level is Yellow Star. Surveillance shows a credible risk of enemy attack against our assets in Crusoe's Rest. We advise a heightened level of security alongside maintaining vigilance against potential threats." // DOPPLER EDIT - Spinward -> Crusoe's
- if(40 to 65)
- advisory_string += "Advisory Level: Orange Star "
- advisory_string += "Your sector's advisory level is Orange Star. Upon reviewing your sector's intelligence, the Department has determined that the risk of enemy activity is moderate to severe. At this advisory, we recommend maintaining a higher degree of security and reviewing red alert protocols with command and the crew."
+ advisory_string += "Your sector's advisory level is Yellow Star. Surveillance shows a credible risk of enemy attack against our assets in the Spinward Sector. We advise a heightened level of security alongside maintaining vigilance against potential threats."
if(66 to 79)
advisory_string += "Advisory Level: Red Star "
//advisory_string += "Your sector's advisory level is Red Star. The Department of Intelligence has decrypted Cybersun communications suggesting a high likelihood of attacks on Nanotrasen assets within the Spinward Sector. Stations in the region are advised to remain highly vigilant for signs of enemy activity and to be on high alert." // ORIGINAL
@@ -504,11 +461,6 @@ SUBSYSTEM_DEF(dynamic)
)
return TRUE
-/datum/controller/subsystem/dynamic/proc/setup_shown_threat()
- if (prob(FAKE_REPORT_CHANCE))
- shown_threat = rand(1, 100)
- else
- shown_threat = clamp(threat_level + rand(REPORT_NEG_DIVERGENCE, REPORT_POS_DIVERGENCE), 0, 100)
/datum/controller/subsystem/dynamic/proc/set_cooldowns()
var/latejoin_injection_cooldown_middle = 0.5*(latejoin_delay_max + latejoin_delay_min)
@@ -530,7 +482,6 @@ SUBSYSTEM_DEF(dynamic)
configure_station_trait_costs()
setup_parameters()
setup_hijacking()
- setup_shown_threat()
setup_rulesets()
//We do this here instead of with the midround rulesets and such because these rules can hang refs
@@ -1063,9 +1014,3 @@ SUBSYSTEM_DEF(dynamic)
#undef MAXIMUM_DYN_DISTANCE
-
-#undef FAKE_REPORT_CHANCE
-#undef FAKE_GREENSHIFT_FORM_CHANCE
-#undef PULSAR_REPORT_CHANCE
-#undef REPORT_NEG_DIVERGENCE
-#undef REPORT_POS_DIVERGENCE
diff --git a/code/controllers/subsystem/dynamic/dynamic_logging.dm b/code/controllers/subsystem/dynamic/dynamic_logging.dm
index 16bd56a730316..3e4987ecf7340 100644
--- a/code/controllers/subsystem/dynamic/dynamic_logging.dm
+++ b/code/controllers/subsystem/dynamic/dynamic_logging.dm
@@ -74,7 +74,6 @@
serialized["threat_level"] = threat_level
serialized["round_start_budget"] = initial_round_start_budget
serialized["mid_round_budget"] = threat_level - initial_round_start_budget
- serialized["shown_threat"] = shown_threat
var/list/serialized_snapshots = list()
for (var/datum/dynamic_snapshot/snapshot as anything in snapshots)
diff --git a/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm b/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm
index d913372673658..8c6f9706b3a0e 100644
--- a/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm
+++ b/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm
@@ -261,7 +261,7 @@
candidates -= player
else if(is_centcom_level(player.z))
candidates -= player // We don't autotator people in CentCom
- else if(player.mind && (player.mind.special_role || !player.mind.can_roll_midround()))
+ else if(player.mind && (player.mind.special_role || !player.mind.can_roll_midround(antag_datum)))
candidates -= player // We don't autotator people with roles already
// DOPPLER ADDITION START
else if(player in rejected_traitor)
@@ -316,7 +316,7 @@
continue
if(isnull(player.mind))
continue
- if(player.mind.special_role || !player.mind.can_roll_midround())
+ if(player.mind.special_role || !player.mind.can_roll_midround(antag_datum))
continue
candidates += player
@@ -485,7 +485,7 @@
candidates -= player
continue
- if(player.mind && (player.mind.special_role || !player.mind.can_roll_midround()))
+ if(player.mind && (player.mind.special_role || !player.mind.can_roll_midround(antag_datum)))
candidates -= player
/datum/dynamic_ruleset/midround/from_living/blob_infection/execute()
diff --git a/code/controllers/subsystem/pai.dm b/code/controllers/subsystem/pai.dm
index c5754115a6e00..4abc752ea81ae 100644
--- a/code/controllers/subsystem/pai.dm
+++ b/code/controllers/subsystem/pai.dm
@@ -70,6 +70,13 @@ SUBSYSTEM_DEF(pai)
candidate.savefile_load(user)
ui.send_full_update()
return TRUE
+ if("withdraw")
+ if(!candidate.ready)
+ to_chat(user, span_warning("You need to submit an application before you can withdraw one."))
+ return FALSE
+ candidate.ready = FALSE
+ to_chat(user, span_notice("Your pAI candidacy has been withdrawn."))
+ return TRUE
return FALSE
/**
diff --git a/code/controllers/subsystem/queuelinks.dm b/code/controllers/subsystem/queuelinks.dm
index a6d56cf622ec9..1f891ee3ea598 100644
--- a/code/controllers/subsystem/queuelinks.dm
+++ b/code/controllers/subsystem/queuelinks.dm
@@ -60,6 +60,7 @@ SUBSYSTEM_DEF(queuelinks)
if(what in partners)
return
partners += what
+ RegisterSignal(what, COMSIG_QDELETING, PROC_REF(link_object_deleted))
if(queue_max != 0 && max != 0 && max != queue_max)
CRASH("Tried to change queue size to [max] from [queue_max]!")
@@ -77,6 +78,10 @@ SUBSYSTEM_DEF(queuelinks)
item.MatchedLinks(id, partners - item)
qdel(src)
+/datum/queue_link/proc/link_object_deleted(datum/source) // because CI and stuff
+ SIGNAL_HANDLER
+ partners -= source
+
/datum/queue_link/Destroy()
. = ..()
partners = null
diff --git a/code/controllers/subsystem/tutorials.dm b/code/controllers/subsystem/tutorials.dm
index 3df3df116cbf6..ceb1fa7878679 100644
--- a/code/controllers/subsystem/tutorials.dm
+++ b/code/controllers/subsystem/tutorials.dm
@@ -92,6 +92,7 @@ SUBSYSTEM_DEF(tutorials)
)
if (!select_tutorials_for_ckey.Execute())
+ qdel(select_tutorials_for_ckey)
return
while (select_tutorials_for_ckey.NextRow())
diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm
index 7673551a65319..4132a2cf2a596 100644
--- a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm
+++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm
@@ -164,3 +164,6 @@ GLOBAL_LIST_INIT(target_interested_atoms, typecacheof(list(/mob, /obj/machinery/
if(length(priority_targets))
return pick(priority_targets)
return ..()
+
+/datum/ai_behavior/find_potential_targets/bigger_range
+ vision_range = 16
diff --git a/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm b/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm
index 43a3d400bc58d..efe5dc911cd9d 100644
--- a/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm
+++ b/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm
@@ -45,3 +45,6 @@
controller.ai_interact(target = target, combat_mode = TRUE)
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
+
+/datum/ai_planning_subtree/ranged_skirmish/no_minimum
+ min_range = 0
diff --git a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm
index 759355283acd4..f80ffcc155e8f 100644
--- a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm
+++ b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm
@@ -17,3 +17,8 @@
/datum/ai_planning_subtree/simple_find_target/to_flee
target_key = BB_BASIC_MOB_FLEE_TARGET
+
+/datum/ai_planning_subtree/simple_find_target/increased_range
+
+/datum/ai_planning_subtree/simple_find_target/increased_range/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
+ controller.queue_behavior(/datum/ai_behavior/find_potential_targets/bigger_range, target_key, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION)
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 d552b69c142dc..2b2297c2d6d1c 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
@@ -146,3 +146,11 @@
if(isturf(the_target))
return TRUE
return ..()
+
+/// Subtype which searches for mobs that havent been gutted by megafauna
+/datum/targeting_strategy/basic/no_gutted_mobs
+
+/datum/targeting_strategy/basic/no_gutted_mobs/can_attack(mob/living/owner, mob/living/target, vision_range)
+ if(!istype(target) || target.has_status_effect(/datum/status_effect/gutted))
+ return FALSE
+ return ..()
diff --git a/code/datums/brain_damage/phobia.dm b/code/datums/brain_damage/phobia.dm
index 9394bc98f5790..e9733293cff13 100644
--- a/code/datums/brain_damage/phobia.dm
+++ b/code/datums/brain_damage/phobia.dm
@@ -11,7 +11,7 @@
COOLDOWN_DECLARE(scare_cooldown)
///What mood event to apply when we see the thing & freak out.
- var/datum/mood_event/mood_event_type
+ var/datum/mood_event/mood_event_type = /datum/mood_event/phobia
var/regex/trigger_regex
//instead of cycling every atom, only cycle the relevant types
@@ -104,46 +104,48 @@
hearing_args[HEARING_RAW_MESSAGE] = trigger_regex.Replace(hearing_args[HEARING_RAW_MESSAGE], "[span_phobia("$2")]$3")
/datum/brain_trauma/mild/phobia/handle_speech(datum/source, list/speech_args)
- if(HAS_TRAIT(owner, TRAIT_FEARLESS))
+ if (HAS_TRAIT(owner, TRAIT_FEARLESS))
+ return
+ if (trigger_regex.Find(speech_args[SPEECH_MESSAGE]) == 0)
+ return
+
+ var/stutter = prob(50)
+ var/whisper = prob(30)
+
+ if (!stutter && !whisper)
return
- if(trigger_regex.Find(speech_args[SPEECH_MESSAGE]) != 0)
- to_chat(owner, span_warning("You can't bring yourself to say the word \"[span_phobia("[trigger_regex.group[2]]")]\"!"))
- speech_args[SPEECH_MESSAGE] = ""
+
+ if (whisper)
+ speech_args[SPEECH_SPANS] |= SPAN_SMALL_VOICE
+ if (stutter)
+ owner.set_stutter_if_lower(4 SECONDS)
+ to_chat(owner, span_warning("You struggle to say the word \"[span_phobia("[trigger_regex.group[2]]")]\"!"))
/datum/brain_trauma/mild/phobia/proc/freak_out(atom/reason, trigger_word)
- COOLDOWN_START(src, scare_cooldown, 12 SECONDS)
if(owner.stat == DEAD)
return
+
+ var/message = pick("spooks you to the bone", "shakes you up", "terrifies you", "sends you into a panic", "sends chills down your spine")
+ if(trigger_word)
+ if (owner.has_status_effect(/datum/status_effect/minor_phobia_reaction))
+ return
+ to_chat(owner, span_userdanger("Hearing [span_phobia(trigger_word)] [message]!"))
+ owner.apply_status_effect(/datum/status_effect/minor_phobia_reaction)
+ return
+
+ COOLDOWN_START(src, scare_cooldown, 12 SECONDS)
if(mood_event_type)
owner.add_mood_event("phobia_[phobia_type]", mood_event_type)
- var/message = pick("spooks you to the bone", "shakes you up", "terrifies you", "sends you into a panic", "sends chills down your spine")
+
if(reason)
to_chat(owner, span_userdanger("Seeing [span_phobia(reason.name)] [message]!"))
- else if(trigger_word)
- to_chat(owner, span_userdanger("Hearing [span_phobia(trigger_word)] [message]!"))
else
to_chat(owner, span_userdanger("Something [message]!"))
- var/reaction = rand(1,4)
- switch(reaction)
- if(1)
- to_chat(owner, span_warning("You are paralyzed with fear!"))
- owner.Stun(70)
- owner.set_jitter_if_lower(16 SECONDS)
- if(2)
- owner.emote("scream")
- owner.set_jitter_if_lower(10 SECONDS)
- owner.say("AAAAH!!", forced = "phobia")
- if(reason)
- owner._pointed(reason)
- if(3)
- to_chat(owner, span_warning("You shut your eyes in terror!"))
- owner.set_jitter_if_lower(10 SECONDS)
- owner.adjust_temp_blindness(20 SECONDS)
- if(4)
- owner.adjust_dizzy(20 SECONDS)
- owner.adjust_confusion(10 SECONDS)
- owner.set_jitter_if_lower(20 SECONDS)
- owner.adjust_stutter(20 SECONDS)
+
+ if(reason)
+ owner.face_atom(reason)
+ owner._pointed(reason)
+ owner.apply_status_effect(/datum/status_effect/stacking/phobia_reaction, 1, mood_event_type)
// Defined phobia types for badminry, not included in the RNG trauma pool to avoid diluting.
@@ -172,10 +174,6 @@
return TRUE
return ..()
-/datum/brain_trauma/mild/phobia/carps
- phobia_type = "carps"
- random_gain = FALSE
-
/datum/brain_trauma/mild/phobia/clowns
phobia_type = "clowns"
random_gain = FALSE
@@ -192,6 +190,10 @@
phobia_type = "falling"
random_gain = FALSE
+/datum/brain_trauma/mild/phobia/fish
+ phobia_type = "fish"
+ random_gain = FALSE
+
/datum/brain_trauma/mild/phobia/greytide
phobia_type = "greytide"
random_gain = FALSE
@@ -200,11 +202,6 @@
phobia_type = "guns"
random_gain = FALSE
-/datum/brain_trauma/mild/phobia/heresy
- phobia_type = "heresy"
- mood_event_type = /datum/mood_event/heresy
- random_gain = FALSE
-
/datum/brain_trauma/mild/phobia/insects
phobia_type = "insects"
random_gain = FALSE
diff --git a/code/datums/brain_damage/split_personality.dm b/code/datums/brain_damage/split_personality.dm
index cef20687a84d5..bed12417218d4 100644
--- a/code/datums/brain_damage/split_personality.dm
+++ b/code/datums/brain_damage/split_personality.dm
@@ -15,12 +15,17 @@
var/poll_role = "split personality"
/datum/brain_trauma/severe/split_personality/on_gain()
- var/mob/living/M = owner
- if(M.stat == DEAD || !M.client) //No use assigning people to a corpse or braindead
+ var/mob/living/brain_owner = owner
+ if(brain_owner.stat == DEAD || !GET_CLIENT(brain_owner)) //No use assigning people to a corpse or braindead
qdel(src)
return
..()
make_backseats()
+
+#ifdef UNIT_TESTS
+ return // There's no ghosts in the unit test
+#endif
+
get_ghost()
/datum/brain_trauma/severe/split_personality/proc/make_backseats()
diff --git a/code/datums/components/fantasy/suffixes.dm b/code/datums/components/fantasy/suffixes.dm
index 69d9d0a7ebbb9..997c9564592b3 100644
--- a/code/datums/components/fantasy/suffixes.dm
+++ b/code/datums/components/fantasy/suffixes.dm
@@ -276,3 +276,17 @@
/datum/fantasy_affix/speed/remove(datum/component/fantasy/comp)
var/obj/item/master = comp.parent
master.slowdown = initial(master.slowdown)
+
+/datum/fantasy_affix/doot
+ name = "of dooting"
+ placement = AFFIX_SUFFIX
+ alignment = AFFIX_GOOD
+ weight = 1
+
+/datum/fantasy_affix/doot/apply(datum/component/fantasy/comp, newName)
+ . = ..()
+ comp.parent.AddElement(/datum/element/spooky, too_spooky = comp.quality > 17, stam_damage_mult = comp.quality * 0.15)
+ return "[newName] of [pick("dooting", "spooks", "rattling", "the bones")]"
+
+/datum/fantasy_affix/doot/remove(datum/component/fantasy/comp)
+ comp.parent.RemoveElement(/datum/element/spooky, too_spooky = comp.quality > 17, stam_damage_mult = comp.quality * 0.15)
diff --git a/code/datums/components/profound_fisher.dm b/code/datums/components/profound_fisher.dm
index 62019ee3f6c67..1e512289a6edc 100644
--- a/code/datums/components/profound_fisher.dm
+++ b/code/datums/components/profound_fisher.dm
@@ -144,3 +144,4 @@
resistance_flags = INDESTRUCTIBLE
reel_overlay = null
show_in_wiki = FALSE //abstract fishing rod
+ item_flags = ABSTRACT
diff --git a/code/datums/components/revenge_ability.dm b/code/datums/components/revenge_ability.dm
index 8d9faec52e0bc..2c52b8cd31a47 100644
--- a/code/datums/components/revenge_ability.dm
+++ b/code/datums/components/revenge_ability.dm
@@ -15,8 +15,10 @@
var/max_range
/// Target the ability at ourself instead of at the offender
var/target_self
+ /// Should this behavoid continue if our mob is sapient?
+ var/activate_with_mind
-/datum/component/revenge_ability/Initialize(datum/action/cooldown/ability, datum/targeting_strategy/targeting, min_range = 0, max_range = INFINITY, target_self = FALSE)
+/datum/component/revenge_ability/Initialize(datum/action/cooldown/ability, datum/targeting_strategy/targeting, min_range = 0, max_range = INFINITY, target_self = FALSE, activate_with_mind = FALSE)
. = ..()
if (!isliving(parent))
return COMPONENT_INCOMPATIBLE
@@ -25,6 +27,7 @@
src.min_range = min_range
src.max_range = max_range
src.target_self = target_self
+ src.activate_with_mind = activate_with_mind
RegisterSignal(ability, COMSIG_QDELETING, PROC_REF(ability_destroyed))
@@ -41,6 +44,8 @@
/// If we were attacked, get revenge
/datum/component/revenge_ability/proc/on_attacked(mob/living/victim, atom/attacker)
SIGNAL_HANDLER
+ if (victim.mind && !activate_with_mind)
+ return // This is mostly a component for the use of AI
var/atom/ability_user = ability.owner
var/distance = get_dist(ability_user, attacker)
if (distance < min_range || distance > max_range)
diff --git a/code/datums/components/tether.dm b/code/datums/components/tether.dm
index ba11306cd8936..d04716a0af0a5 100644
--- a/code/datums/components/tether.dm
+++ b/code/datums/components/tether.dm
@@ -62,6 +62,7 @@
if (!isnull(parent_module))
RegisterSignals(parent_module, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED, COMSIG_MOD_TETHER_SNAP), PROC_REF(snap))
+ RegisterSignal(parent_module, COMSIG_MODULE_TRIGGERED, PROC_REF(on_parent_use))
/datum/component/tether/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_MOVABLE_PRE_MOVE, COMSIG_MOVABLE_MOVED))
@@ -71,11 +72,13 @@
UnregisterSignal(tether_target, list(COMSIG_MOVABLE_PRE_MOVE, COMSIG_MOVABLE_MOVED, COMSIG_QDELETING))
if (!isnull(tether_trait_source) && !no_target_trait)
REMOVE_TRAIT(tether_target, TRAIT_TETHER_ATTACHED, tether_trait_source)
+ SEND_SIGNAL(tether_target, COMSIG_ATOM_TETHER_SNAPPED, tether_trait_source)
if (!QDELETED(tether_beam))
UnregisterSignal(tether_beam.visuals, list(COMSIG_CLICK, COMSIG_QDELETING))
qdel(tether_beam)
if (!QDELETED(embed_target))
UnregisterSignal(embed_target, list(COMSIG_ITEM_UNEMBEDDED, COMSIG_QDELETING))
+ SEND_SIGNAL(parent, COMSIG_ATOM_TETHER_SNAPPED, tether_trait_source)
/datum/component/tether/proc/check_tether(atom/source, new_loc)
SIGNAL_HANDLER
@@ -143,6 +146,12 @@
playsound(atom_target, 'sound/effects/snap.ogg', 50, TRUE)
qdel(src)
+/datum/component/tether/proc/on_parent_use(obj/item/mod/module/module, atom/target)
+ SIGNAL_HANDLER
+
+ if (get_turf(target) == get_turf(tether_target))
+ return MOD_ABORT_USE
+
/datum/component/tether/proc/on_delete()
SIGNAL_HANDLER
qdel(src)
@@ -174,7 +183,7 @@
var/list/modifiers = params2list(params)
if(LAZYACCESS(modifiers, CTRL_CLICK))
location.balloon_alert(user, "cutting the tether...")
- if (!do_after(user, 2 SECONDS, user))
+ if (!do_after(user, 2 SECONDS, user, (user == parent || user == tether_target) ? IGNORE_USER_LOC_CHANGE|IGNORE_TARGET_LOC_CHANGE : NONE))
return
qdel(src)
@@ -190,11 +199,11 @@
location.balloon_alert(user, "tether extended")
return
- if (cur_dist <= 1)
+ if (cur_dist <= 0)
location.balloon_alert(user, "too short!")
return
- if (cur_dist > get_dist(parent, tether_target))
+ if (cur_dist > CEILING(get_dist(parent, tether_target), 1))
cur_dist -= 1
location.balloon_alert(user, "tether shortened")
return
diff --git a/code/datums/elements/crusher_loot.dm b/code/datums/elements/crusher_loot.dm
index c13c62a52ce81..0249975609546 100644
--- a/code/datums/elements/crusher_loot.dm
+++ b/code/datums/elements/crusher_loot.dm
@@ -6,7 +6,7 @@
/datum/element/crusher_loot
element_flags = ELEMENT_BESPOKE
argument_hash_start_idx = 2
- /// Path of the trophy dropped
+ /// Path of the trophy dropped (or list of trophies)
var/trophy_type
/// chance to drop the trophy, lowered by the mob only taking partial crusher damage instead of full
/// for example, 25% would mean ~4 mobs need to die before you find one.
@@ -36,7 +36,14 @@
var/datum/status_effect/crusher_damage/damage = target.has_status_effect(/datum/status_effect/crusher_damage)
if(damage && prob((damage.total_damage/target.maxHealth) * drop_mod)) //on average, you'll need to kill 4 creatures before getting the item. by default.
- if(drop_immediately)
- new trophy_type(get_turf(target))
- else
- target.butcher_results[trophy_type] = 1
+ if(islist(trophy_type))
+ for(var/trophypath in trophy_type)
+ make_path(target, trophypath)
+ return
+ make_path(target, trophy_type)
+
+/datum/element/crusher_loot/proc/make_path(mob/living/target, path)
+ if(drop_immediately)
+ new path(get_turf(target))
+ else
+ target.butcher_results[path] = 1
diff --git a/code/datums/elements/spooky.dm b/code/datums/elements/spooky.dm
index 62189371b618e..8edd90dbc25b7 100644
--- a/code/datums/elements/spooky.dm
+++ b/code/datums/elements/spooky.dm
@@ -3,81 +3,98 @@
argument_hash_start_idx = 2
///will it spawn a new instrument
var/too_spooky = TRUE
- ///Once used, the element is detached
+ ///If, once someone is skeletonized, the element is detached
var/single_use = FALSE
+ ///The base multiplier of stamina damage applied by the item
+ var/stam_damage_mult
-/datum/element/spooky/Attach(datum/target, too_spooky = TRUE, single_use = FALSE)
+/datum/element/spooky/Attach(datum/target, too_spooky = TRUE, single_use = FALSE, stam_damage_mult = 1)
. = ..()
if(!isitem(target))
return ELEMENT_INCOMPATIBLE
src.too_spooky = too_spooky
src.single_use = single_use
+ src.stam_damage_mult = stam_damage_mult
RegisterSignal(target, COMSIG_ITEM_ATTACK, PROC_REF(spectral_attack))
/datum/element/spooky/Detach(datum/source)
UnregisterSignal(source, COMSIG_ITEM_ATTACK)
return ..()
-/datum/element/spooky/proc/spectral_attack(datum/source, mob/living/carbon/C, mob/user)
+/datum/element/spooky/proc/spectral_attack(datum/source, mob/living/carbon/target, mob/user)
SIGNAL_HANDLER
- if(ishuman(user)) //this weapon wasn't meant for mortals.
- var/mob/living/carbon/human/U = user
- if(!istype(U.dna.species, /datum/species/skeleton))
- U.adjustStaminaLoss(35) //Extra Damage
- U.set_jitter_if_lower(70 SECONDS)
- U.set_stutter(40 SECONDS)
- if(U.getStaminaLoss() > 95)
- to_chat(U, "Your ears weren't meant for this spectral sound.")
- INVOKE_ASYNC(src, PROC_REF(spectral_change), U)
- if(single_use)
- to_chat(user, span_warning("You feel like [source] has lost its spookiness..."))
- Detach(source)
- return
+ if(ishuman(user) && !isskeleton(user)) //this weapon wasn't meant for mortals.
+ var/mob/living/carbon/human/human_user = user
+ if(rattle_bones(human_user, stam_dam_mult = stam_damage_mult * 2))
+ to_chat(human_user, span_userdanger("Your ears weren't meant for this spectral sound."))
+ INVOKE_ASYNC(src, PROC_REF(spectral_change), human_user, user, source)
+ return
- if(ishuman(C))
- var/mob/living/carbon/human/H = C
- if(istype(H.dna.species, /datum/species/skeleton))
- return //undeads are unaffected by the spook-pocalypse.
- if(istype(H.dna.species, /datum/species/zombie))
- H.adjustStaminaLoss(25)
- H.Paralyze(15) //zombies can't resist the doot
- C.set_jitter_if_lower(70 SECONDS)
- C.set_stutter(40 SECONDS)
- if((!istype(H.dna.species, /datum/species/skeleton)) && (!istype(H.dna.species, /datum/species/golem)) && (!istype(H.dna.species, /datum/species/android)) && (!istype(H.dna.species, /datum/species/jelly)))
- C.adjustStaminaLoss(18) //boneless humanoids don't lose the will to live
- to_chat(C, "DOOT")
- to_chat(C, span_robot("You're feeling more bony."))
- INVOKE_ASYNC(src, PROC_REF(spectral_change), H)
- if(single_use)
- to_chat(user, span_warning("You feel like [source] has lost its spookiness..."))
- Detach(source)
+ to_chat(target, span_userdanger("DOOT 95) && (!istype(H.dna.species, /datum/species/skeleton)) && (!istype(H.dna.species, /datum/species/golem)) && (!istype(H.dna.species, /datum/species/android)) && (!istype(H.dna.species, /datum/species/jelly)))
- H.Paralyze(20)
- H.set_species(/datum/species/skeleton)
- H.visible_message(span_warning("[H] has given up on life as a mortal."))
- var/T = get_turf(H)
- if(too_spooky)
- if(prob(90))
- var/obj/item/instrument = pick(
- /obj/item/instrument/saxophone/spectral,
- /obj/item/instrument/trumpet/spectral,
- /obj/item/instrument/trombone/spectral,
- )
- new instrument(T)
- else
- to_chat(H, span_boldwarning("The spooky gods forgot to ship your instrument. Better luck next unlife."))
- to_chat(H, span_boldnotice("You are a spooky skeleton!"))
- to_chat(H, span_boldnotice("A new life and identity has begun. Help your fellow skeletons into bringing out the spooky-pocalypse. You haven't forgotten your past life, and are still beholden to past loyalties."))
- change_name(H) //time for a new name!
+ if(!ishuman(target))//the sound will spook basic mobs.
+ target.set_jitter_if_lower(30 SECONDS)
+ target.set_stutter(40 SECONDS)
+ return
+
+ var/mob/living/carbon/human/human = target
+ if(rattle_bones(human))
+ INVOKE_ASYNC(src, PROC_REF(spectral_change), human, user, source)
+
+///Cause jitteriness and stamina to the target relative to the amount of their bodyparts made of flesh and bone.
+/datum/element/spooky/proc/rattle_bones(mob/living/carbon/human/human, stam_dam_mult = stam_damage_mult)
+ if(isskeleton(human))
+ return FALSE //undeads are unaffected by the spook-pocalypse.
+ var/bone_amount = 0
+ for(var/obj/item/bodypart/part as anything in human.bodyparts)
+ if((part.biological_state & BIO_FLESH_BONE) == BIO_FLESH_BONE)
+ bone_amount++
+ if(bone_amount)
+ human.set_jitter_if_lower(12 SECONDS * bone_amount)
+ human.set_stutter(6.5 SECONDS * bone_amount)
+ human.adjustStaminaLoss(3 * bone_amount * stam_dam_mult)
+ if(iszombie(human))
+ human.adjustStaminaLoss(25)
+ human.Paralyze(15) //zombies can't resist the doot
+ return bone_amount
+
+/datum/element/spooky/proc/spectral_change(mob/living/carbon/human/human, mob/living/user, obj/item/source)
+ if(human.getStaminaLoss() <= 95)
+ return
+
+ if(single_use)
+ to_chat(user, span_warning("You feel like [source] has lost its spookiness..."))
+ Detach(source)
+
+ human.Paralyze(2 SECONDS)
+ human.set_species(/datum/species/skeleton)
+ human.visible_message(span_warning("[human] has given up on life as a mortal."))
+ to_chat(human, span_boldnotice("You are a spooky skeleton!"))
+ to_chat(human,
+ span_boldnotice("A new life and identity has begun.\
+ [too_spooky ? "Help your fellow skeletons into bringing out the spooky-pocalypse." : ""] \
+ You haven't forgotten your past life, and are still beholden to past loyalties.")
+ )
+ INVOKE_ASYNC(src, PROC_REF(change_name), human) //time for a new name!
+
+ if(!too_spooky)
+ return
+ var/turf/turf = get_turf(human)
+ if(!prob(90))
+ to_chat(human, span_boldwarning("The spooky gods forgot to ship your instrument. Better luck next unlife."))
+ return
+ var/obj/item/instrument = pick(
+ /obj/item/instrument/saxophone/spectral,
+ /obj/item/instrument/trumpet/spectral,
+ /obj/item/instrument/trombone/spectral,
+ )
+ new instrument(turf)
/datum/element/spooky/proc/change_name(mob/living/carbon/human/spooked)
var/skeleton_name = spooked.client ? sanitize_name(tgui_input_text(spooked, "Enter your new skeleton name", "Spookifier", spooked.real_name, MAX_NAME_LEN)) : null
diff --git a/code/datums/helper_datums/getrev.dm b/code/datums/helper_datums/getrev.dm
index c6e8236e55964..1b7da660e578b 100644
--- a/code/datums/helper_datums/getrev.dm
+++ b/code/datums/helper_datums/getrev.dm
@@ -56,7 +56,7 @@
set name = "Show Server Revision"
set desc = "Check the current server code revision"
- var/list/msg = list("")
+ var/list/msg = list()
// Round ID
if(GLOB.round_id)
msg += "Round ID: [GLOB.round_id]"
@@ -70,23 +70,23 @@
msg += "Server revision compiled on: [revdata.date]"
var/pc = revdata.originmastercommit
if(pc)
- msg += "Master commit: [pc]"
- if(revdata.testmerge.len)
+ msg += "Master commit:[pc]"
+ if(length(revdata.testmerge))
msg += revdata.GetTestMergeInfo()
if(revdata.commit && revdata.commit != revdata.originmastercommit)
- msg += "Local commit: [revdata.commit]"
+ msg += "Local commit: [revdata.commit]"
else if(!pc)
msg += "No commit information"
if(world.TgsAvailable())
var/datum/tgs_version/version = world.TgsVersion()
- msg += "TGS version: [version.raw_parameter]"
+ msg += "TGS version: [version.raw_parameter]"
var/datum/tgs_version/api_version = world.TgsApiVersion()
- msg += "DMAPI version: [api_version.raw_parameter]"
+ msg += "DMAPI version: [api_version.raw_parameter]"
// Game mode odds
msg += " Current Informational Settings:"
- msg += "Protect Authority Roles From Traitor: [CONFIG_GET(flag/protect_roles_from_antagonist)]"
- msg += "Protect Assistant Role From Traitor: [CONFIG_GET(flag/protect_assistant_from_antagonist)]"
- msg += "Enforce Human Authority: [CONFIG_GET(string/human_authority)]"
- msg += "Allow Latejoin Antagonists: [CONFIG_GET(flag/allow_latejoin_antagonists)]"
- to_chat(src, span_infoplain(msg.Join(" ")))
+ msg += "Protect Authority Roles From Traitor: [CONFIG_GET(flag/protect_roles_from_antagonist) ? "Yes" : "No"]"
+ msg += "Protect Assistant Role From Traitor: [CONFIG_GET(flag/protect_assistant_from_antagonist) ? "Yes" : "No"]"
+ msg += "Enforce Human Authority: [CONFIG_GET(string/human_authority) ? "Yes" : "No"]"
+ msg += "Allow Latejoin Antagonists: [CONFIG_GET(flag/allow_latejoin_antagonists) ? "Yes" : "No"]"
+ to_chat(src, fieldset_block("Server Revision Info", span_infoplain(jointext(msg, " ")), "boxed_message"))
diff --git a/code/datums/id_trim/_id_trim.dm b/code/datums/id_trim/_id_trim.dm
index 32bafcb41d3f7..b9356e9c35dc9 100644
--- a/code/datums/id_trim/_id_trim.dm
+++ b/code/datums/id_trim/_id_trim.dm
@@ -28,6 +28,10 @@
var/big_pointer = FALSE
///If set, IDs with this trim will give wearers arrows of different colors when pointing
var/pointer_color
+ /// What honorifics, if any, will we set our wearer's name to when worn?
+ var/list/honorifics
+ /// What positions can our honorific take? To prevent names like "Peter Dr."
+ var/honorific_positions = NONE
/datum/id_trim/proc/find_job()
return null
diff --git a/code/datums/id_trim/centcom.dm b/code/datums/id_trim/centcom.dm
index 498a4de254e3b..7432540f07f35 100644
--- a/code/datums/id_trim/centcom.dm
+++ b/code/datums/id_trim/centcom.dm
@@ -23,6 +23,8 @@
department_color = COLOR_CENTCOM_BLUE
subdepartment_color = COLOR_SERVICE_LIME
big_pointer = FALSE
+ honorifics = list("Custodian")
+ honorific_positions = HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_NONE
/// Trim for Centcom Thunderdome Overseers.
/datum/id_trim/centcom/thunderdome_overseer
@@ -39,11 +41,15 @@
access = list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_WEAPONS)
assignment = "CentCom Intern"
big_pointer = FALSE
+ honorifics = list("Intern")
+ honorific_positions = HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_NONE
/// Trim for Centcom Head Interns. Different assignment, common station access added on.
/datum/id_trim/centcom/intern/head
assignment = "CentCom Head Intern"
big_pointer = TRUE
+ honorifics = list("Head Intern")
+ honorific_positions = HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_NONE
/datum/id_trim/centcom/intern/head/New()
. = ..()
@@ -66,6 +72,8 @@
/datum/id_trim/centcom/medical_officer
access = list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL)
assignment = JOB_CENTCOM_MEDICAL_DOCTOR
+ honorifics = list("Doctor", "Dr.")
+ honorific_positions = HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_NONE
/// Trim for Centcom Research Officers.
/datum/id_trim/centcom/research_officer
@@ -94,6 +102,8 @@
/// Trim for Centcom Commanders. All Centcom and Station Access.
/datum/id_trim/centcom/commander
assignment = JOB_CENTCOM_COMMANDER
+ honorifics = list("Commander", "CMDR.")
+ honorific_positions = HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_NONE
/datum/id_trim/centcom/commander/New()
. = ..()
@@ -105,6 +115,9 @@
assignment = JOB_ERT_DEATHSQUAD
trim_state = "trim_deathcommando"
sechud_icon_state = SECHUD_DEATH_COMMANDO
+ honorifics = list("Commando")
+ honorific_positions = HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_NONE
+
/datum/id_trim/centcom/deathsquad/New()
. = ..()
@@ -114,6 +127,8 @@
/// Trim for generic ERT interns. No universal ID card changing access.
/datum/id_trim/centcom/ert
assignment = "Emergency Response Team Intern"
+ honorifics = list("Intern")
+ honorific_positions = HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_NONE
/datum/id_trim/centcom/ert/New()
. = ..()
@@ -138,6 +153,8 @@
subdepartment_color = COLOR_SECURITY_RED
sechud_icon_state = SECHUD_SECURITY_RESPONSE_OFFICER
big_pointer = FALSE
+ honorifics = list("Officer")
+ honorific_positions = HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_NONE
/datum/id_trim/centcom/ert/security/New()
. = ..()
@@ -164,6 +181,9 @@
subdepartment_color = COLOR_MEDICAL_BLUE
sechud_icon_state = SECHUD_MEDICAL_RESPONSE_OFFICER
big_pointer = FALSE
+ honorifics = list("Doctor", "Dr.")
+ honorific_positions = HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_NONE
+
/datum/id_trim/centcom/ert/medical/New()
. = ..()
@@ -177,6 +197,9 @@
subdepartment_color = COLOR_SERVICE_LIME
sechud_icon_state = SECHUD_RELIGIOUS_RESPONSE_OFFICER
big_pointer = FALSE
+ honorifics = list("Chaplain")
+ honorific_positions = HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_NONE
+
/datum/id_trim/centcom/ert/chaplain/New()
. = ..()
@@ -190,6 +213,9 @@
subdepartment_color = COLOR_SERVICE_LIME
sechud_icon_state = SECHUD_JANITORIAL_RESPONSE_OFFICER
big_pointer = FALSE
+ honorifics = list("Custodian")
+ honorific_positions = HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_NONE
+
/datum/id_trim/centcom/ert/janitor/New()
. = ..()
@@ -212,7 +238,11 @@
/datum/id_trim/centcom/ert/militia
assignment = "Frontier Militia"
big_pointer = FALSE
+ honorifics = list("Minuteman")
+ honorific_positions = HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_NONE
/datum/id_trim/centcom/ert/militia/general
assignment = "Frontier Militia General"
big_pointer = TRUE
+ honorifics = list("Minuteman General", "General")
+ honorific_positions = HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_NONE
diff --git a/code/datums/id_trim/jobs.dm b/code/datums/id_trim/jobs.dm
index 6c78d2fceffbb..0fdc5f18f9d28 100644
--- a/code/datums/id_trim/jobs.dm
+++ b/code/datums/id_trim/jobs.dm
@@ -134,6 +134,8 @@
ACCESS_CE,
)
job = /datum/job/atmospheric_technician
+ honorifics = list("Technician")
+ honorific_positions = HONORIFIC_POSITION_FIRST | HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_FIRST_FULL | HONORIFIC_POSITION_NONE
/datum/id_trim/job/bartender
assignment = JOB_BARTENDER
@@ -181,6 +183,8 @@
ACCESS_HOP,
)
job = /datum/job/pun_pun
+ honorifics = list(", Almighty Scourge")
+ honorific_positions = HONORIFIC_POSITION_LAST_FULL | HONORIFIC_POSITION_NONE
/datum/id_trim/job/bitrunner
assignment = JOB_BITRUNNER
@@ -250,6 +254,8 @@
ACCESS_CHANGE_IDS,
)
job = /datum/job/bridge_assistant
+ honorifics = list("Underling", "Assistant", "Mate")
+ honorific_positions = HONORIFIC_POSITION_FIRST | HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_FIRST_FULL | HONORIFIC_POSITION_NONE
/datum/id_trim/job/captain
assignment = JOB_CAPTAIN
@@ -266,6 +272,8 @@
job = /datum/job/captain
big_pointer = TRUE
pointer_color = COLOR_COMMAND_BLUE
+ honorifics = list("Captain", "Cpt.")
+ honorific_positions = HONORIFIC_POSITION_FIRST | HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_FIRST_FULL | HONORIFIC_POSITION_NONE
/// Captain gets all station accesses hardcoded in because it's the Captain.
/datum/id_trim/job/captain/New()
@@ -300,6 +308,9 @@
ACCESS_QM,
)
job = /datum/job/cargo_technician
+ honorifics = list("Courier")
+ honorific_positions = HONORIFIC_POSITION_FIRST | HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_FIRST_FULL | HONORIFIC_POSITION_NONE
+
/datum/id_trim/job/chaplain
assignment = JOB_CHAPLAIN
@@ -322,6 +333,8 @@
ACCESS_HOP,
)
job = /datum/job/chaplain
+ honorifics = list("Chaplain", "Reverend")
+ honorific_positions = HONORIFIC_POSITION_FIRST | HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_FIRST_FULL | HONORIFIC_POSITION_NONE
/datum/id_trim/job/chemist
assignment = JOB_CHEMIST
@@ -390,6 +403,8 @@
job = /datum/job/chief_engineer
big_pointer = TRUE
pointer_color = COLOR_ENGINEERING_ORANGE
+ honorifics = list("Chief")
+ honorific_positions = HONORIFIC_POSITION_FIRST | HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_NONE
/datum/id_trim/job/chief_medical_officer
assignment = JOB_CHIEF_MEDICAL_OFFICER
@@ -431,6 +446,8 @@
job = /datum/job/chief_medical_officer
big_pointer = TRUE
pointer_color = COLOR_MEDICAL_BLUE
+ honorifics = list(", PhD.", ", MD.")
+ honorific_positions = HONORIFIC_POSITION_LAST_FULL | HONORIFIC_POSITION_NONE
/datum/id_trim/job/clown
assignment = JOB_CLOWN
@@ -473,10 +490,14 @@
ACCESS_HOP,
)
job = /datum/job/cook
+ honorifics = list("Cook")
+ honorific_positions = HONORIFIC_POSITION_FIRST | HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_FIRST_FULL | HONORIFIC_POSITION_NONE
/datum/id_trim/job/cook/chef
assignment = JOB_CHEF
sechud_icon_state = SECHUD_CHEF
+ honorifics = list("Chef")
+ honorific_positions = HONORIFIC_POSITION_FIRST | HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_FIRST_FULL | HONORIFIC_POSITION_NONE
/datum/id_trim/job/coroner
assignment = JOB_CORONER
@@ -553,6 +574,8 @@
ACCESS_HOS,
)
job = /datum/job/detective
+ honorifics = list("Detective", "Investigator")
+ honorific_positions = HONORIFIC_POSITION_FIRST | HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_FIRST_FULL | HONORIFIC_POSITION_NONE
/datum/id_trim/job/detective/refresh_trim_access()
. = ..()
@@ -697,6 +720,8 @@
job = /datum/job/head_of_security
big_pointer = TRUE
pointer_color = COLOR_SECURITY_RED
+ honorifics = list("Chief Officer", "Chief", "Officer")
+ honorific_positions = HONORIFIC_POSITION_FIRST | HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_FIRST_FULL | HONORIFIC_POSITION_NONE
/datum/id_trim/job/head_of_security/refresh_trim_access()
. = ..()
@@ -727,6 +752,8 @@
ACCESS_CHANGE_IDS,
)
job = /datum/job/janitor
+ honorifics = list("Custodian")
+ honorific_positions = HONORIFIC_POSITION_FIRST | HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_FIRST_FULL | HONORIFIC_POSITION_NONE
/datum/id_trim/job/lawyer
assignment = JOB_LAWYER
@@ -748,6 +775,8 @@
ACCESS_HOP,
)
job = /datum/job/lawyer
+ honorifics = list(", Esq.")
+ honorific_positions = HONORIFIC_POSITION_LAST_FULL | HONORIFIC_POSITION_NONE
/datum/id_trim/job/medical_doctor
assignment = JOB_MEDICAL_DOCTOR
@@ -773,6 +802,8 @@
ACCESS_CMO,
)
job = /datum/job/doctor
+ honorifics = list("Doctor", "Dr.")
+ honorific_positions = HONORIFIC_POSITION_FIRST | HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_FIRST_FULL | HONORIFIC_POSITION_NONE
/datum/id_trim/job/mime
assignment = JOB_MIME
@@ -824,6 +855,8 @@
ACCESS_CMO,
)
job = /datum/job/paramedic
+ honorifics = list("EMT")
+ honorific_positions = HONORIFIC_POSITION_FIRST | HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_FIRST_FULL | HONORIFIC_POSITION_NONE
/datum/id_trim/job/prisoner
assignment = JOB_PRISONER
@@ -839,6 +872,8 @@
)
job = /datum/job/prisoner
threat_modifier = 1 // I'm watching you
+ honorifics = list("Convict")
+ honorific_positions = HONORIFIC_POSITION_FIRST | HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_FIRST_FULL | HONORIFIC_POSITION_NONE
/datum/id_trim/job/prisoner/one
trim_state = "trim_prisoner_1"
@@ -891,6 +926,8 @@
ACCESS_HOP,
)
job = /datum/job/psychologist
+ honorifics = list(", PhD.")
+ honorific_positions = HONORIFIC_POSITION_LAST_FULL | HONORIFIC_POSITION_NONE
/datum/id_trim/job/quartermaster
assignment = JOB_QUARTERMASTER
@@ -932,6 +969,8 @@
job = /datum/job/quartermaster
big_pointer = TRUE
pointer_color = COLOR_CARGO_BROWN
+ honorifics = list("Manager")
+ honorific_positions = HONORIFIC_POSITION_FIRST | HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_FIRST_FULL | HONORIFIC_POSITION_NONE
/datum/id_trim/job/research_director
assignment = JOB_RESEARCH_DIRECTOR
@@ -983,6 +1022,8 @@
job = /datum/job/research_director
big_pointer = TRUE
pointer_color = COLOR_SCIENCE_PINK
+ honorifics = list("Director", "Dir.")
+ honorific_positions = HONORIFIC_POSITION_FIRST | HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_FIRST_FULL | HONORIFIC_POSITION_NONE
/datum/id_trim/job/roboticist
assignment = JOB_ROBOTICIST
@@ -1040,6 +1081,8 @@
ACCESS_RD,
)
job = /datum/job/scientist
+ honorifics = list("Researcher")
+ honorific_positions = HONORIFIC_POSITION_FIRST | HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_FIRST_FULL | HONORIFIC_POSITION_NONE
/// Sec officers have departmental variants. They each have their own trims with bonus departmental accesses.
/datum/id_trim/job/security_officer
@@ -1068,6 +1111,8 @@
ACCESS_HOS,
)
job = /datum/job/security_officer
+ honorifics = list("Officer")
+ honorific_positions = HONORIFIC_POSITION_FIRST | HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_FIRST_FULL | HONORIFIC_POSITION_NONE
/// List of bonus departmental accesses that departmental sec officers get by default.
var/department_access = list()
/// List of bonus departmental accesses that departmental security officers can in relation to how many overall security officers there are if the scaling system is set up. These can otherwise be granted via config settings.
@@ -1146,6 +1191,7 @@
ACCESS_SURGERY,
ACCESS_VIROLOGY,
)
+ honorifics = list("Orderly", "Officer")
/datum/id_trim/job/security_officer/science
assignment = JOB_SECURITY_OFFICER_SCIENCE
@@ -1228,6 +1274,8 @@
ACCESS_CE,
)
job = /datum/job/station_engineer
+ honorifics = list("Engineer")
+ honorific_positions = HONORIFIC_POSITION_FIRST | HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_FIRST_FULL | HONORIFIC_POSITION_NONE
/datum/id_trim/job/veteran_advisor
assignment = JOB_VETERAN_ADVISOR
@@ -1249,6 +1297,8 @@
template_access = list()
job = /datum/job/veteran_advisor
big_pointer = TRUE
+ honorifics = list("General", "Gen.")
+ honorific_positions = HONORIFIC_POSITION_FIRST | HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_FIRST_FULL | HONORIFIC_POSITION_NONE
/datum/id_trim/job/veteran_advisor/refresh_trim_access()
. = ..()
@@ -1260,7 +1310,6 @@
if(CONFIG_GET(flag/security_has_maint_access))
access |= list(ACCESS_MAINT_TUNNELS)
-
/datum/id_trim/job/warden
assignment = JOB_WARDEN
trim_state = "trim_warden"
@@ -1288,6 +1337,8 @@
ACCESS_HOS,
)
job = /datum/job/warden
+ honorifics = list("Officer", "Watchman", "Lieutenant", "Lt.")
+ honorific_positions = HONORIFIC_POSITION_FIRST | HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_FIRST_FULL | HONORIFIC_POSITION_NONE
/datum/id_trim/job/warden/refresh_trim_access()
. = ..()
diff --git a/code/datums/id_trim/outfits.dm b/code/datums/id_trim/outfits.dm
index a2944a469f43e..c46938be6e094 100644
--- a/code/datums/id_trim/outfits.dm
+++ b/code/datums/id_trim/outfits.dm
@@ -71,6 +71,8 @@
department_color = COLOR_BLACK
subdepartment_color = COLOR_GREEN
threat_modifier = -1 // Cops recognise cops
+ honorifics = list("CISO")
+ honorific_positions = HONORIFIC_POSITION_FIRST | HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_NONE
/datum/id_trim/cyber_police/New()
. = ..()
diff --git a/code/datums/id_trim/syndicate.dm b/code/datums/id_trim/syndicate.dm
index 41c76aaf3784c..28a0429d9352f 100644
--- a/code/datums/id_trim/syndicate.dm
+++ b/code/datums/id_trim/syndicate.dm
@@ -17,6 +17,10 @@
big_pointer = FALSE
/// Interdyne medical Staff
+/datum/id_trim/syndicom/Interdyne
+ honorifics = list(", PhD.")
+ honorific_positions = HONORIFIC_POSITION_LAST_FULL | HONORIFIC_POSITION_NONE
+
/datum/id_trim/syndicom/Interdyne/pharmacist
assignment = "Interdyne Pharmacist"
trim_state = "trim_medicaldoctor"
@@ -46,6 +50,9 @@
access = list(ACCESS_SYNDICATE, ACCESS_MAINT_TUNNELS)
big_pointer = FALSE
pointer_color = null
+ honorifics = list("Auditor")
+ honorific_positions = HONORIFIC_POSITION_FIRST | HONORIFIC_POSITION_LAST | HONORIFIC_POSITION_FIRST_FULL | HONORIFIC_POSITION_NONE
+
/datum/id_trim/syndicom/irs/auditor
assignment = "Internal Revenue Service Head Auditor"
diff --git a/code/datums/memory/general_memories.dm b/code/datums/memory/general_memories.dm
index eca745d3283a6..8fa8420527d23 100644
--- a/code/datums/memory/general_memories.dm
+++ b/code/datums/memory/general_memories.dm
@@ -95,7 +95,7 @@
/datum/memory/high_five/get_starts()
return list(
- "[protagonist_name] and [deuteragonist_name] having a a legendary [high_five_type]",
+ "[protagonist_name] and [deuteragonist_name] having a legendary [high_five_type]",
"[protagonist_name] giving [deuteragonist_name] a [high_five_type]",
"[protagonist_name] and [deuteragonist_name] giving each other a [high_five_type]",
)
diff --git a/code/datums/mind/_mind.dm b/code/datums/mind/_mind.dm
index 2d5452f21f4f0..531344552a928 100644
--- a/code/datums/mind/_mind.dm
+++ b/code/datums/mind/_mind.dm
@@ -541,7 +541,9 @@
return affected_addiction.on_lose_addiction_points(src)
/// Whether or not we can roll for midrounds, specifically checking if we have any major antag datums that should block it
-/datum/mind/proc/can_roll_midround()
+/datum/mind/proc/can_roll_midround(datum/antagonist/antag_type)
+ if(SEND_SIGNAL(current, COMSIG_MOB_MIND_BEFORE_MIDROUND_ROLL, src, antag_type) & CANCEL_ROLL)
+ return FALSE
for(var/datum/antagonist/antag as anything in antag_datums)
if(antag.block_midrounds)
return FALSE
diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm
index 30999a874b77b..d91ea0ccc222c 100644
--- a/code/datums/mood_events/generic_negative_events.dm
+++ b/code/datums/mood_events/generic_negative_events.dm
@@ -202,6 +202,16 @@
return
return ..()
+/datum/mood_event/startled
+ description = "Hearing that word made me think about something scary."
+ mood_change = -1
+ timeout = 1 MINUTES
+
+/datum/mood_event/phobia
+ description = "I saw something very frightening."
+ mood_change = -4
+ timeout = 4 MINUTES
+
/datum/mood_event/spooked
description = "The rattling of those bones... It still haunts me."
mood_change = -4
diff --git a/code/datums/quirks/negative_quirks/addict.dm b/code/datums/quirks/negative_quirks/addict.dm
index fd717133f2591..2ab7b8641274b 100644
--- a/code/datums/quirks/negative_quirks/addict.dm
+++ b/code/datums/quirks/negative_quirks/addict.dm
@@ -148,12 +148,7 @@
var/obj/item/organ/lungs/smoker_lungs = null
var/obj/item/organ/lungs/old_lungs = carbon_holder.get_organ_slot(ORGAN_SLOT_LUNGS)
if(old_lungs && IS_ORGANIC_ORGAN(old_lungs))
- if(isplasmaman(carbon_holder))
- smoker_lungs = /obj/item/organ/lungs/plasmaman/plasmaman_smoker
- else if(isethereal(carbon_holder))
- smoker_lungs = /obj/item/organ/lungs/ethereal/ethereal_smoker
- else
- smoker_lungs = /obj/item/organ/lungs/smoker_lungs
+ smoker_lungs = carbon_holder.dna.species.smoker_lungs
if(!isnull(smoker_lungs))
smoker_lungs = new smoker_lungs
smoker_lungs.Insert(carbon_holder, special = TRUE, movement_flags = DELETE_IF_REPLACED)
diff --git a/code/datums/ruins/icemoon.dm b/code/datums/ruins/icemoon.dm
index bd746d4a19bb2..86fba76bc15eb 100644
--- a/code/datums/ruins/icemoon.dm
+++ b/code/datums/ruins/icemoon.dm
@@ -64,7 +64,7 @@
/datum/map_template/ruin/icemoon/Lodge
name = "Ice-Ruin Hunters Lodge"
id = "lodge"
- description = "An old hunting hunting lodge. I wonder if anyone is still home?"
+ description = "An old hunting lodge. I wonder if anyone is still home?"
suffix = "icemoon_surface_lodge.dmm"
/datum/map_template/ruin/icemoon/frozen_phonebooth
@@ -190,6 +190,12 @@
description = "A small laboratory and living space for Syndicate agents."
suffix = "icemoon_underground_syndielab.dmm"
+/datum/map_template/ruin/icemoon/underground/o31
+ name = "Ice-Ruin Outpost 31"
+ id = "o31"
+ description = "Suspiciously dead silent. May or may not contain megafauna"
+ suffix = "icemoon_underground_outpost31.dmm"
+
//TODO: Bottom-Level ONLY Spawns after Refactoring Related Code
/datum/map_template/ruin/icemoon/underground/plasma_facility
name = "Ice-Ruin Abandoned Plasma Facility"
diff --git a/code/datums/station_traits/negative_traits.dm b/code/datums/station_traits/negative_traits.dm
index 77684234e8d4b..ca574178d2528 100644
--- a/code/datums/station_traits/negative_traits.dm
+++ b/code/datums/station_traits/negative_traits.dm
@@ -41,16 +41,6 @@
SSeconomy.mail_blocked = !SSeconomy.mail_blocked
return ..()
-///A negative trait that reduces the amount of products available from vending machines throughout the station.
-/datum/station_trait/vending_shortage
- name = "Vending products shortage"
- trait_type = STATION_TRAIT_NEGATIVE
- weight = 3
- show_in_report = TRUE
- can_revert = FALSE //Because it touches every maploaded vending machine on the station.
- report_message = "We haven't had the time to take care of the station's vending machines. Some may be tilted, and some products may be unavailable."
- trait_to_give = STATION_TRAIT_VENDING_SHORTAGE
-
/datum/station_trait/late_arrivals
name = "Late Arrivals"
trait_type = STATION_TRAIT_NEGATIVE
diff --git a/code/datums/status_effects/debuffs/debuffs.dm b/code/datums/status_effects/debuffs/debuffs.dm
index 006a2a527dfb3..7627d16b66f45 100644
--- a/code/datums/status_effects/debuffs/debuffs.dm
+++ b/code/datums/status_effects/debuffs/debuffs.dm
@@ -461,26 +461,32 @@
/datum/status_effect/neck_slice/get_examine_text()
return span_warning("[owner.p_Their()] neck is cut and is bleeding profusely!")
+/// Applies a curse with various possible effects
/mob/living/proc/apply_necropolis_curse(set_curse)
- var/datum/status_effect/necropolis_curse/C = has_status_effect(/datum/status_effect/necropolis_curse)
+ var/datum/status_effect/necropolis_curse/curse = has_status_effect(/datum/status_effect/necropolis_curse)
if(!set_curse)
- set_curse = pick(CURSE_BLINDING, CURSE_SPAWNING, CURSE_WASTING, CURSE_GRASPING)
- if(QDELETED(C))
+ set_curse = pick(CURSE_BLINDING, CURSE_WASTING, CURSE_GRASPING)
+ if(QDELETED(curse))
apply_status_effect(/datum/status_effect/necropolis_curse, set_curse)
else
- C.apply_curse(set_curse)
- C.duration += 3000 //time added by additional curses
- return C
+ curse.apply_curse(set_curse)
+ curse.duration += 5 MINUTES //time added by additional curses
+ return curse
+/// A curse that does up to three nasty things to you
/datum/status_effect/necropolis_curse
id = "necrocurse"
duration = 10 MINUTES //you're cursed for 10 minutes have fun
tick_interval = 5 SECONDS
alert_type = null
+ /// Which nasty things are we doing? [CURSE_BLINDING / CURSE_WASTING / CURSE_GRASPING]]
var/curse_flags = NONE
- var/effect_last_activation = 0
- var/effect_cooldown = 100
- var/obj/effect/temp_visual/curse/wasting_effect = new
+ /// When should we next throw hands?
+ var/effect_next_activation = 0
+ /// How long between throwing hands?
+ var/effect_cooldown = 10 SECONDS
+ /// Visuals for the wasting effect
+ var/obj/effect/temp_visual/curse/wasting_effect
/datum/status_effect/necropolis_curse/on_creation(mob/living/new_owner, set_curse)
. = ..()
@@ -500,6 +506,8 @@
curse_flags |= set_curse
if(curse_flags & CURSE_BLINDING)
owner.overlay_fullscreen("curse", /atom/movable/screen/fullscreen/curse, 1)
+ if(curse_flags & CURSE_WASTING && !wasting_effect)
+ wasting_effect = new
/datum/status_effect/necropolis_curse/proc/remove_curse(remove_curse)
if(remove_curse & CURSE_BLINDING)
@@ -509,6 +517,7 @@
/datum/status_effect/necropolis_curse/tick(seconds_between_ticks)
if(owner.stat == DEAD)
return
+
if(curse_flags & CURSE_WASTING)
wasting_effect.forceMove(owner.loc)
wasting_effect.setDir(owner.dir)
@@ -517,31 +526,12 @@
animate(wasting_effect, alpha = 0, time = 32)
playsound(owner, 'sound/effects/curse/curse5.ogg', 20, TRUE, -1)
owner.adjustFireLoss(0.75)
- if(effect_last_activation <= world.time)
- effect_last_activation = world.time + effect_cooldown
- if(curse_flags & CURSE_SPAWNING)
- var/turf/spawn_turf
- var/sanity = 10
- while(!spawn_turf && sanity)
- spawn_turf = locate(owner.x + pick(rand(10, 15), rand(-10, -15)), owner.y + pick(rand(10, 15), rand(-10, -15)), owner.z)
- sanity--
- if(spawn_turf)
- var/mob/living/simple_animal/hostile/asteroid/curseblob/C = new (spawn_turf)
- C.set_target = owner
- C.GiveTarget()
- if(curse_flags & CURSE_GRASPING)
- var/grab_dir = turn(owner.dir, pick(-90, 90, 180, 180)) //grab them from a random direction other than the one faced, favoring grabbing from behind
- var/turf/spawn_turf = get_ranged_target_turf(owner, grab_dir, 5)
- if(spawn_turf)
- grasp(spawn_turf)
-
-/datum/status_effect/necropolis_curse/proc/grasp(turf/spawn_turf)
- set waitfor = FALSE
- new/obj/effect/temp_visual/dir_setting/curse/grasp_portal(spawn_turf, owner.dir)
- playsound(spawn_turf, 'sound/effects/curse/curse2.ogg', 80, TRUE, -1)
- var/obj/projectile/curse_hand/C = new (spawn_turf)
- C.aim_projectile(owner, spawn_turf)
- C.fire()
+
+ if(curse_flags & CURSE_GRASPING)
+ if(effect_next_activation > world.time)
+ return
+ effect_next_activation = world.time + effect_cooldown
+ fire_curse_hand(owner, range = 5, projectile_type = /obj/projectile/curse_hand) // This one stuns people
/obj/effect/temp_visual/curse
icon_state = "curse"
diff --git a/code/datums/status_effects/debuffs/phobia.dm b/code/datums/status_effects/debuffs/phobia.dm
new file mode 100644
index 0000000000000..76520e4dc5889
--- /dev/null
+++ b/code/datums/status_effects/debuffs/phobia.dm
@@ -0,0 +1,77 @@
+/// Basically a cooldown which makes you emote when it starts, but allows it to be easily shared between multiple phobias
+/datum/status_effect/minor_phobia_reaction
+ id = "phobia_minor"
+ duration = 12 SECONDS
+ alert_type = null
+
+/datum/status_effect/minor_phobia_reaction/on_apply()
+ . = ..()
+ owner.emote("scream")
+ owner.set_jitter_if_lower(6 SECONDS)
+ owner.add_mood_event("phobia_minor", /datum/mood_event/startled)
+
+/// Stacking severity of phobic reaction
+/// The more stacks you are the more scared you are
+/datum/status_effect/stacking/phobia_reaction
+ id = "phobia"
+ status_type = STATUS_EFFECT_REFRESH
+ stacks = 1
+ max_stacks = 6
+ tick_interval = 40 SECONDS
+ consumed_on_threshold = FALSE
+
+/datum/status_effect/stacking/phobia_reaction/on_creation(mob/living/new_owner, stacks_to_apply = 1, mood_event_type)
+ . = ..()
+ if (!.)
+ return FALSE
+
+ if (mood_event_type)
+ owner.add_mood_event("phobia", mood_event_type)
+ return TRUE
+
+/datum/status_effect/stacking/phobia_reaction/refresh(effect, stacks_to_add)
+ . = ..()
+ add_stacks(stacks_to_add)
+
+/datum/status_effect/stacking/phobia_reaction/add_stacks(stacks_added)
+ . = ..()
+ if (stacks_added <= 0 || stacks <= 0)
+ return
+
+ var/reaction = rand(1,4)
+ switch(reaction)
+ if(1)
+ to_chat(owner, span_warning("You are startled!"))
+ owner.emote("jump")
+ owner.Immobilize(0.1 SECONDS * stacks)
+
+ if(2)
+ owner.emote("scream")
+ owner.say("AAAAH!!", forced = "phobia")
+
+ if(stacks >= 5)
+ var/held_item = owner.get_active_held_item()
+ if (owner.dropItemToGround(held_item))
+ owner.visible_message(
+ span_danger("[owner.name] drops \the [held_item]!"),
+ span_warning("You drop \the [held_item]!"), null, COMBAT_MESSAGE_RANGE)
+
+ if(3)
+ to_chat(owner, span_warning("You lose your balance!"))
+ owner.adjust_staggered_up_to(2 SECONDS * stacks, 20 SECONDS)
+ owner.add_movespeed_modifier(/datum/movespeed_modifier/status_effect/spooked)
+ // We're relying on the fact that there's a 12 second application cooldown to not have to bother cancelling and replacing this timer
+ // So if you adjust the duration keep that in mind
+ addtimer(CALLBACK(src, PROC_REF(speed_up)), 1 SECONDS * stacks, TIMER_STOPPABLE | TIMER_DELETE_ME)
+
+ if(4)
+ to_chat(owner, span_warning("You feel faint with fright!"))
+ owner.adjust_dizzy_up_to(2 SECONDS * stacks, 20 SECONDS)
+ owner.adjust_eye_blur_up_to(1.5 SECONDS * stacks, 6 SECONDS)
+
+/datum/status_effect/stacking/phobia_reaction/fadeout_effect()
+ to_chat(owner, span_notice("You calm down."))
+
+/// Remove our active movespeed modifier
+/datum/status_effect/stacking/phobia_reaction/proc/speed_up()
+ owner.remove_movespeed_modifier(/datum/movespeed_modifier/status_effect/spooked)
diff --git a/code/datums/status_effects/wound_effects.dm b/code/datums/status_effects/wound_effects.dm
index 67ee23120a631..727ef357ac4e0 100644
--- a/code/datums/status_effects/wound_effects.dm
+++ b/code/datums/status_effects/wound_effects.dm
@@ -53,9 +53,10 @@
var/mob/living/carbon/carbon_owner = owner
left = carbon_owner.get_bodypart(BODY_ZONE_L_LEG)
right = carbon_owner.get_bodypart(BODY_ZONE_R_LEG)
- update_limp()
+ update_limp(src)
RegisterSignal(carbon_owner, COMSIG_MOVABLE_MOVED, PROC_REF(check_step))
- RegisterSignals(carbon_owner, list(COMSIG_CARBON_GAIN_WOUND, COMSIG_CARBON_POST_LOSE_WOUND, COMSIG_CARBON_ATTACH_LIMB, COMSIG_CARBON_REMOVE_LIMB), PROC_REF(update_limp))
+ RegisterSignal(carbon_owner, COMSIG_CARBON_REMOVE_LIMB, PROC_REF(on_limb_removed))
+ RegisterSignals(carbon_owner, list(COMSIG_CARBON_GAIN_WOUND, COMSIG_CARBON_POST_LOSE_WOUND, COMSIG_CARBON_ATTACH_LIMB), PROC_REF(update_limp))
return TRUE
/datum/status_effect/limp/on_remove()
@@ -88,15 +89,27 @@
owner.client.move_delay += slowdown_right * determined_mod
next_leg = left
-/datum/status_effect/limp/proc/update_limp()
+/// We need to make sure that we properly clear these refs if one of the owner's limbs gets deleted
+/datum/status_effect/limp/proc/on_limb_removed(datum/source, obj/item/bodypart/limb_lost, special, dismembered)
SIGNAL_HANDLER
- var/mob/living/carbon/C = owner
- left = C.get_bodypart(BODY_ZONE_L_LEG)
- right = C.get_bodypart(BODY_ZONE_R_LEG)
+ if(limb_lost == left)
+ left = null
+ if(limb_lost == right)
+ right = null
+
+ update_limp() // calling this with no arg so we know it's coming from here and not a signal
+
+/datum/status_effect/limp/proc/update_limp(datum/source)
+ SIGNAL_HANDLER
+
+ var/mob/living/carbon/carbon_mob = owner
+ if(source) // if we don't have a source, that means we are calling it from on_limb_removed. In that case we do not want to reassign these to the about-to-be-removed limbs (which can cause hanging refs)
+ left = carbon_mob.get_bodypart(BODY_ZONE_L_LEG)
+ right = carbon_mob.get_bodypart(BODY_ZONE_R_LEG)
if(!left && !right)
- C.remove_status_effect(src)
+ carbon_mob.remove_status_effect(src)
return
slowdown_left = 0
@@ -107,19 +120,19 @@
// technically you can have multiple wounds causing limps on the same limb, even if practically only bone wounds cause it in normal gameplay
if(left)
for(var/thing in left.wounds)
- var/datum/wound/W = thing
- slowdown_left += W.limp_slowdown
- limp_chance_left = max(limp_chance_left, W.limp_chance)
+ var/datum/wound/wound = thing
+ slowdown_left += wound.limp_slowdown
+ limp_chance_left = max(limp_chance_left, wound.limp_chance)
if(right)
for(var/thing in right.wounds)
- var/datum/wound/W = thing
- slowdown_right += W.limp_slowdown
- limp_chance_right = max(limp_chance_right, W.limp_chance)
+ var/datum/wound/wound = thing
+ slowdown_right += wound.limp_slowdown
+ limp_chance_right = max(limp_chance_right, wound.limp_chance)
// this handles losing your leg with the limp and the other one being in good shape as well
if(!slowdown_left && !slowdown_right)
- C.remove_status_effect(src)
+ carbon_mob.remove_status_effect(src)
return
/////////////////////////
diff --git a/code/datums/storage/storage.dm b/code/datums/storage/storage.dm
index b9ff9355955cd..5d1c978698cef 100644
--- a/code/datums/storage/storage.dm
+++ b/code/datums/storage/storage.dm
@@ -856,15 +856,8 @@ GLOBAL_LIST_EMPTY(cached_storage_typecaches)
return COMPONENT_CANCEL_ATTACK_CHAIN
if(ishuman(user))
var/mob/living/carbon/human/hum = user
- if(hum.l_store == parent && !hum.get_active_held_item())
- INVOKE_ASYNC(hum, TYPE_PROC_REF(/mob, put_in_hands), parent)
- hum.l_store = null
+ if(hum.l_store == parent || hum.r_store == parent)
return
- if(hum.r_store == parent && !hum.get_active_held_item())
- INVOKE_ASYNC(hum, TYPE_PROC_REF(/mob, put_in_hands), parent)
- hum.r_store = null
- return
-
if(parent.loc == user)
INVOKE_ASYNC(src, PROC_REF(open_storage), user)
return COMPONENT_CANCEL_ATTACK_CHAIN
diff --git a/code/datums/voice_of_god_command.dm b/code/datums/voice_of_god_command.dm
index 21d4f460617b6..3bbd5768e21e3 100644
--- a/code/datums/voice_of_god_command.dm
+++ b/code/datums/voice_of_god_command.dm
@@ -52,12 +52,12 @@ GLOBAL_LIST_INIT(voice_of_god_commands, init_voice_of_god_commands())
listeners += candidate
//Let's ensure the listener's name is not matched within another word or command (and viceversa). e.g. "Saul" in "somersault"
- var/their_first_name = candidate.first_name()
+ var/their_first_name = first_name(candidate.name)
if(!GLOB.all_voice_of_god_triggers.Find(their_first_name) && findtext(message, regex("(\\L|^)[their_first_name](\\L|$)", "i")))
specific_listeners += candidate //focus on those with the specified name
to_remove_string += "[to_remove_string ? "|" : null][their_first_name]"
continue
- var/their_last_name = candidate.last_name()
+ var/their_last_name = last_name(candidate.name)
if(their_last_name != their_first_name && !GLOB.all_voice_of_god_triggers.Find(their_last_name) && findtext(message, regex("(\\L|^)[their_last_name](\\L|$)", "i")))
specific_listeners += candidate // Ditto
to_remove_string += "[to_remove_string ? "|" : null][their_last_name]"
diff --git a/code/datums/wires/_wires.dm b/code/datums/wires/_wires.dm
index 73bdc511ee4ba..13b9420d8366a 100644
--- a/code/datums/wires/_wires.dm
+++ b/code/datums/wires/_wires.dm
@@ -7,16 +7,14 @@
if(I.tool_behaviour == TOOL_WIRECUTTER || I.tool_behaviour == TOOL_MULTITOOL)
return TRUE
if(isassembly(I))
- var/obj/item/assembly/A = I
- if(A.attachable)
- return TRUE
+ return TRUE
/atom/proc/attempt_wire_interaction(mob/user)
if(!wires)
return WIRE_INTERACTION_FAIL
if(!user.CanReach(src))
return WIRE_INTERACTION_FAIL
- wires.interact(user)
+ INVOKE_ASYNC(wires, TYPE_PROC_REF(/datum/wires, interact), user)
return WIRE_INTERACTION_BLOCK
/datum/wires
@@ -29,6 +27,9 @@
/// The display name for the wire set shown in station blueprints. Not shown in blueprints if randomize is TRUE or it's an item NT wouldn't know about (Explosives/Nuke). Also used in the hacking interface.
var/proper_name = "Unknown"
+ /// Whether pulsed wires affect the holder, and/or the holder pulses its wires
+ var/wire_behavior = WIRES_INPUT
+
/// List of all wires.
var/list/wires = list()
/// List of cut wires.
@@ -179,6 +180,7 @@
/datum/wires/proc/pulse(wire, user, force=FALSE)
if(!force && is_cut(wire))
return
+ SEND_SIGNAL(src, COMSIG_PULSE_WIRE, wire, user)
on_pulse(wire, user)
/datum/wires/proc/pulse_color(color, mob/living/user, force=FALSE)
@@ -191,7 +193,7 @@
return TRUE
/datum/wires/proc/attach_assembly(color, obj/item/assembly/S)
- if(S && istype(S) && S.attachable && !is_attached(color))
+ if(S && istype(S) && S.assembly_behavior && !is_attached(color) && !(SEND_SIGNAL(S, COMSIG_ASSEMBLY_PRE_ATTACH, holder) & COMPONENT_CANCEL_ATTACH))
assemblies[color] = S
S.forceMove(holder)
S.connected = src
@@ -384,13 +386,13 @@
I = L.get_active_held_item()
if(isassembly(I))
var/obj/item/assembly/A = I
- if(A.attachable)
+ if(A.assembly_behavior & wire_behavior)
if(!L.temporarilyRemoveItemFromInventory(A))
return
if(!attach_assembly(target_wire, A))
A.forceMove(L.drop_location())
. = TRUE
else
- to_chat(L, span_warning("You need an attachable assembly!"))
+ to_chat(L, span_warning("You cannot attach this assembly to these wires!"))
#undef MAXIMUM_EMP_WIRES
diff --git a/code/datums/wires/scanner_gate.dm b/code/datums/wires/scanner_gate.dm
index 14752ec6795ec..cb363d68f95ea 100644
--- a/code/datums/wires/scanner_gate.dm
+++ b/code/datums/wires/scanner_gate.dm
@@ -2,6 +2,7 @@
holder_type = /obj/machinery/scanner_gate
proper_name = "Scanner Gate"
wires = list(WIRE_ACCEPT, WIRE_DENY, WIRE_DISABLE)
+ wire_behavior = WIRES_FUNCTIONAL_OUTPUT
/datum/wires/scanner_gate/on_pulse(wire, user)
. = ..()
diff --git a/code/datums/wires/wire_bundle_component.dm b/code/datums/wires/wire_bundle_component.dm
new file mode 100644
index 0000000000000..5cd4ffa2af620
--- /dev/null
+++ b/code/datums/wires/wire_bundle_component.dm
@@ -0,0 +1,24 @@
+#define CAPACITY_PER_WIRE 10
+
+/datum/wires/wire_bundle_component
+ holder_type = /atom //Anything that can have a shell component, really.
+ randomize = TRUE
+ wire_behavior = WIRES_ALL
+
+/datum/wires/wire_bundle_component/New(atom/holder)
+ var/datum/component/shell/shell_comp = holder.GetComponent(/datum/component/shell)
+ if(!istype(shell_comp))
+ CRASH("Holder does not have a shell component!")
+ var/wire_count = clamp(round(shell_comp.capacity / CAPACITY_PER_WIRE, 1), 1, MAX_WIRE_COUNT)
+ for(var/index in 1 to wire_count)
+ wires += "Port [index]"
+ ..()
+
+/datum/wires/wire_bundle_component/always_reveal_wire(color)
+ return TRUE // Let's not make wiring up this stuff confusing - just give them what wires correspond to what ports.
+
+/datum/wires/wire_bundle_component/ui_data(mob/user)
+ proper_name = holder.name
+ . = ..()
+
+#undef CAPACITY_PER_WIRE
diff --git a/code/game/area/areas/ruins/_ruins.dm b/code/game/area/areas/ruins/_ruins.dm
index 46cf851b1ebd1..f2ecc7ac73e3e 100644
--- a/code/game/area/areas/ruins/_ruins.dm
+++ b/code/game/area/areas/ruins/_ruins.dm
@@ -5,7 +5,7 @@
icon = 'icons/area/areas_ruins.dmi'
icon_state = "ruins"
default_gravity = STANDARD_GRAVITY
- area_flags = HIDDEN_AREA | BLOBS_ALLOWED | UNIQUE_AREA
+ area_flags = HIDDEN_AREA | UNIQUE_AREA
ambience_index = AMBIENCE_RUINS
flags_1 = CAN_BE_DIRTY_1
sound_environment = SOUND_ENVIRONMENT_STONEROOM
diff --git a/code/game/area/areas/ruins/icemoon.dm b/code/game/area/areas/ruins/icemoon.dm
index 69d1e5abf4d5a..bc9a9226316d6 100644
--- a/code/game/area/areas/ruins/icemoon.dm
+++ b/code/game/area/areas/ruins/icemoon.dm
@@ -94,3 +94,40 @@
name = "\improper Syndicate Lab"
ambience_index = AMBIENCE_DANGER
sound_environment = SOUND_ENVIRONMENT_CAVE
+
+/area/ruin/outpost31
+ name = "\improper Outpost 31"
+ sound_environment = SOUND_AREA_SMALL_ENCLOSED
+ mood_bonus = -10
+ mood_message = "Something very bad happened here..."
+
+/area/ruin/outpost31/medical
+ name = "\improper Outpost 31 Medical"
+
+/area/ruin/outpost31/kitchendiningroom
+ name = "\improper Outpost 31 Kitchen-Dining Room"
+
+/area/ruin/outpost31/kennel
+ name = "\improper Outpost 31 Kennel"
+
+/area/ruin/outpost31/radiomap
+ name = "\improper Outpost 31 Radio-Map Room"
+
+/area/ruin/outpost31/lab
+ name = "\improper Outpost 31 Lab"
+ area_flags = NOTELEPORT //megafauna arena
+ requires_power = FALSE
+
+/area/ruin/outpost31/lootroom
+ name = "\improper Outpost 31 Secondary Storage"
+ area_flags = NOTELEPORT //megafauna loot room
+ requires_power = FALSE
+
+/area/ruin/outpost31/recroom
+ name = "\improper Outpost 31 Rec Room"
+
+/area/ruin/outpost31/crewquarters
+ name = "\improper Outpost 31 Sleeping Quarters"
+
+/area/ruin/outpost31/commander_room
+ name = "\improper Outpost 31 Station Commander Office"
diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm
index 50033dde9739b..878794cc98526 100644
--- a/code/game/data_huds.dm
+++ b/code/game/data_huds.dm
@@ -22,15 +22,17 @@
/datum/atom_hud/data/human/medical/basic
-/datum/atom_hud/data/human/medical/basic/proc/check_sensors(mob/living/carbon/human/H)
- if(!istype(H))
+/datum/atom_hud/data/human/medical/basic/proc/check_sensors(mob/living/carbon/human/human)
+ if(!istype(human))
return FALSE
- var/obj/item/clothing/under/U = H.w_uniform
- if(!istype(U))
+ if(HAS_TRAIT(human, HUMAN_SENSORS_VISIBLE_WITHOUT_SUIT))
+ return TRUE
+ var/obj/item/clothing/under/undersuit = human.w_uniform
+ if(!istype(undersuit))
return FALSE
- if(U.has_sensor < HAS_SENSORS)
+ if(undersuit.has_sensor < HAS_SENSORS)
return FALSE
- if(U.sensor_mode <= SENSOR_VITALS)
+ if(undersuit.sensor_mode <= SENSOR_VITALS)
return FALSE
return TRUE
diff --git a/code/game/gamemodes/objective_items.dm b/code/game/gamemodes/objective_items.dm
index d0cad5490d35a..d0a6a4da199d1 100644
--- a/code/game/gamemodes/objective_items.dm
+++ b/code/game/gamemodes/objective_items.dm
@@ -566,7 +566,7 @@
exists_on_map = TRUE
difficulty = 4
steal_hint = "The station's data Blackbox, found solely within Telecommunications."
- destruction_method = "Too strong to be be destroyed via normal means - needs to be dusted via the supermatter, or burnt in the chapel's crematorium."
+ destruction_method = "Too strong to be destroyed via normal means - needs to be dusted via the supermatter, or burnt in the chapel's crematorium."
/obj/item/blackbox/add_stealing_item_objective()
return add_item_to_steal(src, /obj/item/blackbox)
diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm
index c8aba44ef4732..66f1b64795a6b 100644
--- a/code/game/machinery/buttons.dm
+++ b/code/game/machinery/buttons.dm
@@ -140,6 +140,9 @@
if(device)
to_chat(user, span_warning("The button already contains a device!"))
return ITEM_INTERACT_BLOCKING
+ if(!(new_device.assembly_behavior & ASSEMBLY_FUNCTIONAL_OUTPUT))
+ to_chat(user, span_warning("\The [new_device] won't really do anything meaningful inside of the button..."))
+ return ITEM_INTERACT_BLOCKING
if(!user.transferItemToLoc(new_device, src, silent = FALSE))
to_chat(user, span_warning("\The [new_device] is stuck to you!"))
return ITEM_INTERACT_BLOCKING
diff --git a/code/game/machinery/computer/arcade/orion.dm b/code/game/machinery/computer/arcade/orion.dm
index a6685e4782ccd..c8236b5e8839f 100644
--- a/code/game/machinery/computer/arcade/orion.dm
+++ b/code/game/machinery/computer/arcade/orion.dm
@@ -85,7 +85,7 @@
/obj/machinery/computer/arcade/orion_trail/proc/newgame()
// Set names of settlers in crew
var/mob/living/player = usr
- var/player_crew_name = player.first_name()
+ var/player_crew_name = first_name(player.name)
settlers = list()
for(var/i in 1 to ORION_STARTING_CREW_COUNT - 1) //one reserved to be YOU
add_crewmember(update = FALSE)
diff --git a/code/game/machinery/computer/arcade/orion_event.dm b/code/game/machinery/computer/arcade/orion_event.dm
index d39766200dc52..7c834800f1dd2 100644
--- a/code/game/machinery/computer/arcade/orion_event.dm
+++ b/code/game/machinery/computer/arcade/orion_event.dm
@@ -219,7 +219,7 @@
var/lostfuel = rand(4,7)
var/deadname = game.remove_crewmember()
game.fuel -= lostfuel
- text = "[deadname] was lost deep in the wreckage, and your own vessel lost [lostfuel] Fuel maneuvering to the the abandoned ship."
+ text = "[deadname] was lost deep in the wreckage, and your own vessel lost [lostfuel] Fuel maneuvering to the abandoned ship."
event_responses += BUTTON_WHERE_DID_YOU_GO
if(36 to 65)
var/oldfood = rand(5,11)
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 57b14e6ade339..44b1f6a18db8a 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -264,6 +264,8 @@
var/current_skin
/// List of options to reskin.
var/list/unique_reskin
+ /// If reskins change base icon state as well
+ var/unique_reskin_changes_base_icon_state = FALSE
/// If reskins change inhands as well
var/unique_reskin_changes_inhand = FALSE
/// Do we apply a click cooldown when resisting this object if it is restraining them?
@@ -1441,7 +1443,7 @@
return discover_after
if(w_class > WEIGHT_CLASS_TINY) //small items like soap or toys that don't have mat datums
- to_chat(victim, span_warning("[source_item? "Something strange was in the \the [source_item]..." : "I just bit something strange..."] "))
+ to_chat(victim, span_warning("[source_item? "Something strange was in \the [source_item]..." : "I just bit something strange..."] "))
return discover_after
// victim's chest (for cavity implanting the item)
@@ -1881,7 +1883,7 @@
return null
/obj/item/animate_atom_living(mob/living/owner)
- new /mob/living/simple_animal/hostile/mimic/copy(drop_location(), src, owner)
+ new /mob/living/basic/mimic/copy(drop_location(), src, owner)
/**
* Used to update the weight class of the item in a way that other atoms can react to the change.
diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm
index d502c583c03f9..8a6c131a6d21e 100644
--- a/code/game/objects/items/cards_ids.dm
+++ b/code/game/objects/items/cards_ids.dm
@@ -32,6 +32,8 @@
/// Cached icon that has been built for this card. Intended to be displayed in chat. Cardboards IDs and actual IDs use it.
var/icon/cached_flat_icon
+ ///What is our honorific name/title combo to be displayed?
+ var/honorific_title
/obj/item/card/suicide_act(mob/living/carbon/user)
user.visible_message(span_suicide("[user] begins to swipe [user.p_their()] neck with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!"))
@@ -111,6 +113,11 @@
var/big_pointer = FALSE
///If set, the arrow will have a different color.
var/pointer_color
+ /// Will this ID card use the first or last name as the name displayed with the honorific?
+ var/honorific_position = HONORIFIC_POSITION_NONE
+ /// What is our selected honorific?
+ var/chosen_honorific
+
/datum/armor/card_id
fire = 100
@@ -142,6 +149,7 @@
register_context()
RegisterSignal(src, COMSIG_ATOM_UPDATED_ICON, PROC_REF(update_in_wallet))
+ RegisterSignal(src, COMSIG_ID_GET_HONORIFIC, PROC_REF(return_message_name_part))
if(prob(1))
ADD_TRAIT(src, TRAIT_TASTEFULLY_THICK_ID_CARD, ROUNDSTART_TRAIT)
@@ -157,6 +165,20 @@
if(slot == ITEM_SLOT_ID)
RegisterSignal(user, COMSIG_MOVABLE_POINTED, PROC_REF(on_pointed))
+/obj/item/card/id/proc/return_message_name_part(datum/source, list/stored_name, mob/living/carbon/carbon_human)
+ SIGNAL_HANDLER
+ var/voice_name = carbon_human.GetVoice()
+ var/end_string = ""
+ var/return_string = ""
+ if(carbon_human.name != voice_name)
+ end_string += " (as [registered_name])"
+ if(trim && honorific_position != HONORIFIC_POSITION_NONE && (carbon_human.name == voice_name)) //The voice and name are the same, so we display the title.
+ return_string += honorific_title
+ else
+ return_string += voice_name //Name on the ID ain't the same as the speaker, so we display their real name with no title.
+ return_string += end_string
+ stored_name[NAME_PART_INDEX] = return_string
+
/obj/item/card/id/proc/on_pointed(mob/living/user, atom/pointed, obj/effect/temp_visual/point/point)
SIGNAL_HANDLER
if((!big_pointer && !pointer_color) || HAS_TRAIT(user, TRAIT_UNKNOWN))
@@ -478,6 +500,8 @@
context[SCREENTIP_CONTEXT_ALT_RMB] = "Assign account"
else if(registered_account.account_balance > 0)
context[SCREENTIP_CONTEXT_ALT_LMB] = "Withdraw credits"
+ if(trim && length(trim.honorifics))
+ context[SCREENTIP_CONTEXT_CTRL_LMB] = "Toggle honorific"
return CONTEXTUAL_SCREENTIP_SET
/obj/item/card/id/proc/try_project_paystand(mob/user, turf/target)
@@ -786,7 +810,7 @@
for(var/mob/living/carbon/human/viewing_mob in viewers(user, 2))
if(viewing_mob.stat || viewing_mob == user)
continue
- viewing_mob.say("Is something wrong? [user.first_name()]... you're sweating.", forced = "psycho")
+ viewing_mob.say("Is something wrong? [first_name(user.name)]... you're sweating.", forced = "psycho")
break
/obj/item/card/id/examine_more(mob/user)
@@ -845,7 +869,15 @@
/// Updates the name based on the card's vars and state.
/obj/item/card/id/proc/update_label()
- var/name_string = registered_name ? "[registered_name]'s ID Card" : initial(name)
+ var/name_string
+ if(registered_name)
+ if(trim && (honorific_position & ~HONORIFIC_POSITION_NONE))
+ name_string = "[update_honorific()]'s ID Card"
+ else
+ name_string = "[registered_name]'s ID Card"
+ else
+ name_string = initial(name)
+
var/assignment_string
if(is_intern)
@@ -858,6 +890,24 @@
name = "[name_string] ([assignment_string])"
+/// Re-generates the honorific title. Returns the compiled honorific_title value
+/obj/item/card/id/proc/update_honorific()
+ var/is_mononym = is_mononym(registered_name)
+ switch(honorific_position)
+ if(HONORIFIC_POSITION_FIRST)
+ honorific_title = "[chosen_honorific] [first_name(registered_name)]"
+ if(HONORIFIC_POSITION_LAST)
+ honorific_title = "[chosen_honorific] [last_name(registered_name)]"
+ if(HONORIFIC_POSITION_FIRST_FULL)
+ honorific_title = "[chosen_honorific] [first_name(registered_name)]"
+ if(!is_mononym)
+ honorific_title += " [last_name(registered_name)]"
+ if(HONORIFIC_POSITION_LAST_FULL)
+ if(!is_mononym)
+ honorific_title += "[first_name(registered_name)] "
+ honorific_title += "[last_name(registered_name)][chosen_honorific]"
+ return honorific_title
+
/// Returns the trim assignment name.
/obj/item/card/id/proc/get_trim_assignment()
return trim?.assignment || assignment
@@ -871,6 +921,55 @@
return insert_money(interacting_with, user) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_BLOCKING
return NONE
+/obj/item/card/id/item_ctrl_click(mob/user)
+ if(!in_contents_of(user) || user.incapacitated) //Check if the ID is in the ID slot, so it can be changed from there too.
+ return
+
+ if(!trim)
+ balloon_alert(user, "card has no trim!")
+ return
+
+ if(!length(trim.honorifics))
+ balloon_alert(user, "card has no honorific to use!")
+ return
+
+ var/list/choices = list()
+ var/list/readable_names = HONORIFIC_POSITION_BITFIELDS()
+ for(var/i in readable_names) //Filter out the options you don't have on your ID.
+ if(trim.honorific_positions & readable_names[i]) //If the positions list has the same bit value as the readable list.
+ choices += i
+
+ var/chosen_position = tgui_input_list(user, "What position do you want your honorific in?", "Flair!", choices)
+ if(user.incapacitated || !in_contents_of(user))
+ return
+ var/honorific_position_to_use = readable_names[chosen_position]
+
+ honorific_position = initial(honorific_position) //In case you want to force an honorific on an ID, set a default that won't always be NONE.
+ honorific_title = null //We reset this regardless so that we don't stack titles on accident.
+
+ if(honorific_position_to_use & HONORIFIC_POSITION_NONE)
+ balloon_alert(user, "honorific disabled")
+ else
+ var/new_honorific = tgui_input_list(user, "What honorific do you want to use?", "Flair!!!", trim.honorifics)
+ if(!new_honorific || user.incapacitated || !in_contents_of(user))
+ return
+ chosen_honorific = new_honorific
+ switch(honorific_position_to_use)
+ if(HONORIFIC_POSITION_FIRST)
+ honorific_position = HONORIFIC_POSITION_FIRST
+ balloon_alert(user, "honorific set: display first name")
+ if(HONORIFIC_POSITION_LAST)
+ honorific_position = HONORIFIC_POSITION_LAST
+ balloon_alert(user, "honorific set: display last name")
+ if(HONORIFIC_POSITION_FIRST_FULL)
+ honorific_position = HONORIFIC_POSITION_FIRST_FULL
+ balloon_alert(user, "honorific set: start of full name")
+ if(HONORIFIC_POSITION_LAST_FULL)
+ honorific_position = HONORIFIC_POSITION_LAST_FULL
+ balloon_alert(user, "honorific set: end of full name")
+
+ update_label()
+
/obj/item/card/id/away
name = "\proper a perfectly generic identification card"
desc = "A perfectly generic identification card. Looks like it could use some flavor."
diff --git a/code/game/objects/items/chromosome.dm b/code/game/objects/items/chromosome.dm
index dcfc7930ebfe2..43afbd43bd533 100644
--- a/code/game/objects/items/chromosome.dm
+++ b/code/game/objects/items/chromosome.dm
@@ -76,6 +76,6 @@
/obj/item/chromosome/energy
name = "energetic chromosome"
- desc = "A chromosome that reduces action based mutation cooldowns by by 50%."
+ desc = "A chromosome that reduces action based mutation cooldowns by 50%."
icon_state = "energy"
energy_coeff = 0.5
diff --git a/code/game/objects/items/devices/pressureplates.dm b/code/game/objects/items/devices/pressureplates.dm
index 17f324d109f99..ea27894d829c3 100644
--- a/code/game/objects/items/devices/pressureplates.dm
+++ b/code/game/objects/items/devices/pressureplates.dm
@@ -14,12 +14,12 @@
var/specific_item = null
var/trigger_silent = FALSE
var/sound/trigger_sound = 'sound/effects/pressureplate.ogg'
- var/obj/item/assembly/signaler/sigdev = null
+ var/obj/item/assembly/assembly = null
var/roundstart_signaller = FALSE
var/roundstart_signaller_freq = FREQ_PRESSURE_PLATE
var/roundstart_signaller_code = 30
var/roundstart_hide = FALSE
- var/removable_signaller = TRUE
+ var/removable_assembly = TRUE
var/active = FALSE
var/image/tile_overlay = null
var/can_trigger = TRUE
@@ -31,9 +31,10 @@
. = ..()
tile_overlay = image(icon = 'icons/turf/floors.dmi', icon_state = "pp_overlay")
if(roundstart_signaller)
- sigdev = new
- sigdev.code = roundstart_signaller_code
- sigdev.set_frequency(roundstart_signaller_freq)
+ var/obj/item/assembly/signaler/signaller = new(src)
+ signaller.code = roundstart_signaller_code
+ signaller.set_frequency(roundstart_signaller_freq)
+ assembly = signaller
if(undertile_pressureplate)
AddElement(/datum/element/undertile, tile_overlay = tile_overlay, use_anchor = TRUE)
@@ -59,21 +60,28 @@
/obj/item/pressure_plate/proc/trigger()
can_trigger = TRUE
- if(istype(sigdev))
- sigdev.signal()
+ if(istype(assembly))
+ assembly.activate()
-/obj/item/pressure_plate/attackby(obj/item/I, mob/living/L)
- if(issignaler(I) && !istype(sigdev) && removable_signaller && L.transferItemToLoc(I, src))
- sigdev = I
- to_chat(L, span_notice("You attach [I] to [src]!"))
+/obj/item/pressure_plate/attackby(obj/item/item, mob/living/L)
+ if(isassembly(item) && !istype(assembly) && removable_assembly)
+ var/obj/item/assembly/new_assembly = item
+ if(!(new_assembly.assembly_behavior & ASSEMBLY_FUNCTIONAL_OUTPUT))
+ to_chat(L, span_warning("\The [item] doesn't seem like it would do much of anything inside of [src]..."))
+ return
+ if(L.transferItemToLoc(item, src))
+ assembly = item
+ SEND_SIGNAL(item, COMSIG_ASSEMBLY_ADDED_TO_PRESSURE_PLATE, src, L)
+ to_chat(L, span_notice("You attach [item] to [src]!"))
return ..()
/obj/item/pressure_plate/attack_self(mob/living/L)
- if(removable_signaller && istype(sigdev))
- to_chat(L, span_notice("You remove [sigdev] from [src]."))
- if(!L.put_in_hands(sigdev))
- sigdev.forceMove(get_turf(src))
- sigdev = null
+ if(removable_assembly && istype(assembly))
+ to_chat(L, span_notice("You remove [assembly] from [src]."))
+ SEND_SIGNAL(assembly, COMSIG_ASSEMBLY_REMOVED_FROM_PRESSURE_PLATE, src, L)
+ if(!L.put_in_hands(assembly))
+ assembly.forceMove(get_turf(src))
+ assembly = null
return ..()
/obj/item/pressure_plate/item_ctrl_click(mob/user)
@@ -97,7 +105,7 @@
protected = TRUE
anchored = TRUE //this prevents us from being picked up
active = TRUE
- removable_signaller = FALSE
+ removable_assembly = FALSE
/// puzzle id we send if stepped on
var/puzzle_id
/// queue size must match
diff --git a/code/game/objects/items/melee/baton.dm b/code/game/objects/items/melee/baton.dm
index 7ea00ea8c6f5a..215485e90a3e8 100644
--- a/code/game/objects/items/melee/baton.dm
+++ b/code/game/objects/items/melee/baton.dm
@@ -29,6 +29,10 @@
var/clumsy_knockdown_time = 18 SECONDS
/// How much stamina damage we deal on a successful hit against a living, non-cyborg mob.
var/stamina_damage = 35 // DOPPLER EDIT - Less Stamina Damage (Original: 55)
+ /// How much armor does our baton ignore? This operates as armour penetration, but only applies to the stun attack.
+ var/stun_armour_penetration = 15
+ /// What armor does our stun attack check before delivering the attack?
+ var/armour_type_against_stun = MELEE
/// Chance of causing force_say() when stunning a human mob
var/force_say_chance = 33
/// Can we stun cyborgs?
@@ -44,6 +48,9 @@
/// Boolean on whether people with chunky fingers can use this baton.
var/chunky_finger_usable = FALSE
+ /// What term do we use to describe our baton being 'ready', or the phrase to use when var/active is TRUE.
+ var/activated_word = "ready"
+
/// The context to show when the baton is active and targeting a living thing
var/context_living_target_active = "Stun"
@@ -64,12 +71,31 @@
/obj/item/melee/baton/Initialize(mapload)
. = ..()
- // Adding an extra break for the sake of presentation
- if(stamina_damage != 0)
- offensive_notes = "It takes [span_warning("[CEILING(100 / stamina_damage, 1)] stunning hit\s")] to stun an enemy."
register_item_context()
+/obj/item/melee/baton/add_weapon_description()
+ AddElement(/datum/element/weapon_description, attached_proc = PROC_REF(add_baton_notes))
+
+/obj/item/melee/baton/proc/add_baton_notes()
+ var/list/readout = list()
+
+ if(affect_cyborg)
+ readout += "It can stun cyborgs for [round((stun_time_cyborg/10), 1)] seconds."
+
+ readout += "\n[active ? "It is currently [span_warning("[activated_word]")], and capable of stunning." : "It is [span_warning("not [activated_word]")], and not capable of stunning."]"
+
+ if(stamina_damage <= 0) // The advanced baton actually does have 0 stamina damage so...yeah.
+ readout += "Either is is [span_warning("completely unable to perform a stunning strike")], or it [span_warning("attacks via some unusual method")]."
+ return readout.Join("\n")
+
+ readout += "It takes [span_warning("[HITS_TO_CRIT(stamina_damage)] strike\s")] to stun an enemy."
+
+ readout += "\nThe effects of each strike can be mitigated by utilizing [span_warning("[armour_type_against_stun]")] armor."
+
+ readout += "\nIt has a stun armor-piercing capability of [span_warning("[get_stun_penetration_value()]%")]."
+ return readout.Join("\n")
+
/**
* Ok, think of baton attacks like a melee attack chain:
*
@@ -211,7 +237,9 @@
var/mob/living/carbon/human/human_target = target
if(prob(force_say_chance))
human_target.force_say()
- target.apply_damage(stamina_damage, STAMINA)
+ var/effective_armour_penetration = get_stun_penetration_value()
+ var/armour_block = target.run_armor_check(null, armour_type_against_stun, null, null, effective_armour_penetration)
+ target.apply_damage(stamina_damage, STAMINA, blocked = armour_block)
if(!trait_check)
target.Knockdown((isnull(stun_override) ? knockdown_time : stun_override))
additional_effects_non_cyborg(target, user)
@@ -296,6 +324,10 @@
user.do_attack_animation(user)
return
+/// Handles the penetration value of our baton, called during baton_effect()
+/obj/item/melee/baton/proc/get_stun_penetration_value()
+ return stun_armour_penetration
+
/obj/item/conversion_kit
name = "conversion kit"
desc = "A strange box containing wood working tools and an instruction paper to turn stun batons into something else."
@@ -322,6 +354,7 @@
bare_wound_bonus = 5
clumsy_knockdown_time = 15 SECONDS
active = FALSE
+ activated_word = "extended"
var/folded_drop_sound = 'sound/items/baton/telescopic_baton_folded_drop.ogg'
var/folded_pickup_sound = 'sound/items/baton/telescopic_baton_folded_pickup.ogg'
var/unfolded_drop_sound = 'sound/items/baton/telescopic_baton_unfolded_drop.ogg'
@@ -393,6 +426,24 @@
playsound(src, on_sound, 50, TRUE)
return COMPONENT_NO_DEFAULT_MESSAGE
+/obj/item/melee/baton/telescopic/bronze
+ name = "bronze-capped telescopic baton"
+ desc = "A compact yet robust personal defense weapon. Can be concealed when folded. This one is ranked BRONZE, and thus has mediocre penetrative power."
+ icon_state = "telebaton_bronze"
+ stun_armour_penetration = 20
+
+/obj/item/melee/baton/telescopic/silver
+ name = "silver-capped telescopic baton"
+ desc = "A compact yet robust personal defense weapon. Can be concealed when folded. This one is ranked SILVER, and thus has decent penetrative power."
+ icon_state = "telebaton_silver"
+ stun_armour_penetration = 40
+
+/obj/item/melee/baton/telescopic/gold
+ name = "gold-capped telescopic baton"
+ desc = "A compact yet robust personal defense weapon. Can be concealed when folded. This one is ranked GOLD, and thus has exceptional penetrative power."
+ icon_state = "telebaton_gold"
+ stun_armour_penetration = 60
+
/obj/item/melee/baton/telescopic/contractor_baton
name = "contractor baton"
desc = "A compact, specialised baton assigned to Syndicate contractors. Applies light electrical shocks to targets."
@@ -409,6 +460,7 @@
cooldown = 2.5 SECONDS
force_say_chance = 80 //very high force say chance because it's funny
stamina_damage = 115 // DOPPLER EDIT: Original 85
+ stun_armour_penetration = 40
clumsy_knockdown_time = 24 SECONDS
affect_cyborg = TRUE
on_stun_sound = 'sound/items/weapons/contractor_baton/contractorbatonhit.ogg'
@@ -433,7 +485,8 @@
desc_controls = "Left click to stun, right click to harm."
icon = 'icons/obj/weapons/baton.dmi'
icon_state = "stunbaton"
- inhand_icon_state = "baton"
+ base_icon_state = "stunbaton"
+ inhand_icon_state = "stunbaton"
worn_icon_state = "baton"
icon_angle = -45
force = 10
@@ -444,12 +497,16 @@
throwforce = 7
force_say_chance = 50
stamina_damage = 35 // DOPPLER EDIT - 4 baton crit now (Original: 60)
+ armour_type_against_stun = ENERGY
+ // This value is added to our stun armour penetration when called by get_stun_penetration_value(). For giving some batons extra OOMPH.
+ var/additional_stun_armour_penetration = 0
knockdown_time = 5 SECONDS
clumsy_knockdown_time = 15 SECONDS
cooldown = 2.5 SECONDS
on_stun_sound = 'sound/items/weapons/egloves.ogg'
on_stun_volume = 50
active = FALSE
+ activated_word = "activated"
context_living_rmb_active = "Harmful Stun"
light_range = 1.5
light_system = OVERLAY_LIGHT
@@ -470,6 +527,10 @@
var/cell_hit_cost = STANDARD_CELL_CHARGE
var/can_remove_cell = TRUE
var/convertible = TRUE //if it can be converted with a conversion kit
+ ///Whether or not our inhand changes when active.
+ var/active_changes_inhand = TRUE
+ ///Whether or not our baton visibly changes the inhand sprite based on inserted cell
+ var/tip_changes_color = TRUE
/datum/armor/baton_security
bomb = 50
@@ -535,12 +596,19 @@
/obj/item/melee/baton/security/update_icon_state()
if(active)
- icon_state = "[initial(icon_state)]_active"
+ icon_state = "[base_icon_state]_active"
+ if(active_changes_inhand)
+ if(tip_changes_color)
+ inhand_icon_state = "[base_icon_state]_active_[get_baton_tip_color()]"
+ else
+ inhand_icon_state = "[base_icon_state]_active"
return ..()
if(!cell)
- icon_state = "[initial(icon_state)]_nocell"
+ icon_state = "[base_icon_state]_nocell"
+ inhand_icon_state = "[base_icon_state]"
return ..()
- icon_state = "[initial(icon_state)]"
+ icon_state = "[base_icon_state]"
+ inhand_icon_state = "[base_icon_state]"
return ..()
/obj/item/melee/baton/security/examine(mob/user)
@@ -595,9 +663,36 @@
/// Toggles the stun baton's light
/obj/item/melee/baton/security/proc/toggle_light()
+ set_light_color(get_baton_tip_color(TRUE))
set_light_on(!light_on)
return
+/// Change our baton's top color based on the contained cell.
+/obj/item/melee/baton/security/proc/get_baton_tip_color(set_light = FALSE)
+ var/tip_type_to_set
+ var/tip_light_to_set
+
+ if(cell)
+ var/chargepower = cell.maxcharge
+ var/zap_value = clamp(chargepower/STANDARD_CELL_CHARGE, 0, 100)
+ switch(zap_value)
+ if(-INFINITY to 10)
+ tip_type_to_set = "orange"
+ tip_light_to_set = LIGHT_COLOR_ORANGE
+ if(11 to 20)
+ tip_type_to_set = "red"
+ tip_light_to_set = LIGHT_COLOR_INTENSE_RED
+ if(21 to 30)
+ tip_type_to_set = "green"
+ tip_light_to_set = LIGHT_COLOR_GREEN
+ if(31 to INFINITY)
+ tip_type_to_set = "blue"
+ tip_light_to_set = LIGHT_COLOR_BLUE
+ else
+ tip_type_to_set = "orange"
+
+ return set_light ? tip_light_to_set : tip_type_to_set
+
/obj/item/melee/baton/security/proc/turn_on(mob/user)
active = TRUE
playsound(src, SFX_SPARKS, 75, TRUE, -1)
@@ -654,6 +749,13 @@
stun_override = 0 //Avoids knocking people down prematurely.
return ..()
+/obj/item/melee/baton/security/get_stun_penetration_value()
+ if(cell)
+ var/chargepower = cell.maxcharge
+ var/zap_pen = clamp(chargepower/STANDARD_CELL_CHARGE, 0, 100)
+ return zap_pen + additional_stun_armour_penetration
+ return stun_armour_penetration + additional_stun_armour_penetration
+
/*
* After a target is hit, we apply some status effects.
* After a period of time, we then check to see what stun duration we give.
@@ -720,6 +822,9 @@
/obj/item/melee/baton/security/loaded //this one starts with a cell pre-installed.
preload_cell_type = /obj/item/stock_parts/power_store/cell/high
+/obj/item/melee/baton/security/loaded/hos
+ preload_cell_type = /obj/item/stock_parts/power_store/cell/super
+
//Makeshift stun baton. Replacement for stun gloves.
/obj/item/melee/baton/security/cattleprod
name = "stunprod"
@@ -727,6 +832,7 @@
desc_controls = "Left click to stun, right click to harm."
icon = 'icons/obj/weapons/spear.dmi'
icon_state = "stunprod"
+ base_icon_state = "stunprod"
inhand_icon_state = "prod"
worn_icon_state = null
icon_angle = -45
@@ -739,6 +845,8 @@
throw_stun_chance = 10
slot_flags = ITEM_SLOT_BACK
convertible = FALSE
+ active_changes_inhand = FALSE
+ tip_changes_color = FALSE
var/obj/item/assembly/igniter/sparkler
///Determines whether or not we can improve the cattleprod into a new type. Prevents turning the cattleprod subtypes into different subtypes, or wasting materials on making it....another version of itself.
var/can_upgrade = TRUE
@@ -795,6 +903,7 @@
throw_speed = 1
icon = 'icons/obj/weapons/thrown.dmi'
icon_state = "boomerang"
+ base_icon_state = "boomerang"
inhand_icon_state = "boomerang"
force = 5
throwforce = 5
@@ -802,6 +911,8 @@
cell_hit_cost = STANDARD_CELL_CHARGE * 2
throw_stun_chance = 99 //Have you prayed today?
convertible = FALSE
+ active_changes_inhand = FALSE
+ tip_changes_color = FALSE
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 5, /datum/material/glass = SHEET_MATERIAL_AMOUNT*2, /datum/material/silver = SHEET_MATERIAL_AMOUNT*5, /datum/material/gold = SHEET_MATERIAL_AMOUNT)
/obj/item/melee/baton/security/boomerang/Initialize(mapload)
@@ -824,6 +935,7 @@
desc = "A prod with a bluespace crystal on the end. The crystal doesn't look too fun to touch."
w_class = WEIGHT_CLASS_NORMAL
icon_state = "teleprod"
+ base_icon_state = "teleprod"
inhand_icon_state = "teleprod"
slot_flags = null
can_upgrade = FALSE
@@ -845,6 +957,7 @@
desc = "A prod with a telecrystal on the end. It sparks with a desire for theft and subversion."
w_class = WEIGHT_CLASS_NORMAL
icon_state = "telecrystalprod"
+ base_icon_state = "telecrystalprod"
inhand_icon_state = "telecrystalprod"
slot_flags = null
throw_stun_chance = 50 //I think it'd be funny
diff --git a/code/game/objects/items/puzzle_pieces.dm b/code/game/objects/items/puzzle_pieces.dm
index a7bd4da85aebf..f5e3cf6da689e 100644
--- a/code/game/objects/items/puzzle_pieces.dm
+++ b/code/game/objects/items/puzzle_pieces.dm
@@ -168,7 +168,7 @@
trigger_mob = FALSE
trigger_item = TRUE
specific_item = /obj/structure/holobox
- removable_signaller = FALSE //Being a pressure plate subtype, this can also use signals.
+ removable_assembly = FALSE //Being a pressure plate subtype, this can also use signals.
roundstart_signaller_freq = FREQ_HOLOGRID_SOLUTION //Frequency is kept on its own default channel however.
active = TRUE
trigger_delay = 10
diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm
index bbebd91e7cb0c..f448ec0ed591e 100644
--- a/code/game/objects/items/robot/robot_upgrades.dm
+++ b/code/game/objects/items/robot/robot_upgrades.dm
@@ -688,7 +688,7 @@
/obj/item/borg/upgrade/transform
name = "borg model picker (Standard)"
- desc = "Allows you to to turn a cyborg into a standard cyborg."
+ desc = "Allows you to turn a cyborg into a standard cyborg."
icon_state = "module_general"
var/obj/item/robot_model/new_model = null
@@ -699,7 +699,7 @@
/obj/item/borg/upgrade/transform/clown
name = "borg model picker (Clown)"
- desc = "Allows you to to turn a cyborg into a clown, honk."
+ desc = "Allows you to turn a cyborg into a clown, honk."
icon_state = "module_honk"
new_model = /obj/item/robot_model/clown
diff --git a/code/game/objects/items/stacks/tiles/tile_types.dm b/code/game/objects/items/stacks/tiles/tile_types.dm
index 20ee0e69df6d7..f2c860d2d7644 100644
--- a/code/game/objects/items/stacks/tiles/tile_types.dm
+++ b/code/game/objects/items/stacks/tiles/tile_types.dm
@@ -1044,7 +1044,7 @@
/obj/item/stack/tile/tram/plate
name = "linear induction tram tiles"
- singular_name = "linear induction tram tile tile"
+ singular_name = "linear induction tram tile"
desc = "A tile with an aluminium plate for tram propulsion."
icon_state = "darkiron_plate"
inhand_icon_state = "tile-neon"
diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm
index 0e85af8e5327a..86fe351239c85 100644
--- a/code/game/objects/items/weaponry.dm
+++ b/code/game/objects/items/weaponry.dm
@@ -1025,7 +1025,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
angle = 180
if(target.dir & target_to_user)
angle = 360
- var/turf/return_to_sender = get_ranged_target_turf_direct(user, throw_datum.starting_turf, round(target.throw_range * 1.5, 1), offset = angle + (rand(-1, 1) * 10))
+ var/turf/return_to_sender = get_ranged_target_turf_direct(user, throw_datum.starting_turf, max(3, round(target.throw_range * 1.5, 1)), offset = angle + (rand(-1, 1) * 10))
throw_datum.finalize(hit = FALSE)
target.mouse_opacity = MOUSE_OPACITY_TRANSPARENT //dont mess with our ball
target.color = list(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,3) //make them super light
diff --git a/code/game/objects/items_reskin.dm b/code/game/objects/items_reskin.dm
index b73df0a487b72..98a3165b5e2ab 100644
--- a/code/game/objects/items_reskin.dm
+++ b/code/game/objects/items_reskin.dm
@@ -64,8 +64,15 @@
return
current_skin = pick
icon_state = unique_reskin[pick]
+
+ if (unique_reskin_changes_base_icon_state)
+ base_icon_state = icon_state
+
if (unique_reskin_changes_inhand)
inhand_icon_state = icon_state
+
+ update_appearance()
+
to_chat(user, "[src] is now skinned as '[pick].'")
SEND_SIGNAL(src, COMSIG_OBJ_RESKIN, user, pick)
diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm
index e6c9579d67936..7f5e4c6b76d89 100644
--- a/code/game/objects/structures.dm
+++ b/code/game/objects/structures.dm
@@ -71,7 +71,7 @@
. = ..()
/obj/structure/animate_atom_living(mob/living/owner)
- new /mob/living/simple_animal/hostile/mimic/copy(drop_location(), src, owner)
+ new /mob/living/basic/mimic/copy(drop_location(), src, owner)
/// For when a mob comes flying through the window, smash it and damage the mob
/obj/structure/proc/smash_and_injure(mob/living/flying_mob, atom/oldloc, direction)
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index 9924713d93908..8c8a39aa59f26 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -860,7 +860,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/window/reinforced/tinted/frosted/spaw
/obj/structure/window/reinforced/plasma/plastitanium
name = "plastitanium window"
- desc = "A durable looking window made of an alloy of of plasma and titanium."
+ desc = "A durable looking window made of an alloy of plasma and titanium."
icon = 'icons/obj/smooth_structures/plastitanium_window.dmi'
icon_state = "plastitanium_window-0"
base_icon_state = "plastitanium_window"
@@ -883,7 +883,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/window/reinforced/tinted/frosted/spaw
/obj/structure/window/reinforced/plasma/plastitanium/indestructible
name = "plastitanium window"
- desc = "A durable looking window made of an alloy of of plasma and titanium."
+ desc = "A durable looking window made of an alloy of plasma and titanium."
icon = 'icons/obj/smooth_structures/plastitanium_window.dmi'
icon_state = "plastitanium_window-0"
base_icon_state = "plastitanium_window"
diff --git a/code/game/say.dm b/code/game/say.dm
index 798481d6c6e7c..6c158b95c6b11 100644
--- a/code/game/say.dm
+++ b/code/game/say.dm
@@ -148,7 +148,18 @@ GLOBAL_LIST_INIT(freqtospan, list(
//Speaker name
var/namepart
var/list/stored_name = list(null)
- SEND_SIGNAL(speaker, COMSIG_MOVABLE_MESSAGE_GET_NAME_PART, stored_name, visible_name)
+
+ if(iscarbon(speaker)) //First, try to pull the modified title from a carbon's ID. This will override both visual and audible names.
+ var/mob/living/carbon/carbon_human = speaker
+ var/obj/item/id_slot = carbon_human.get_item_by_slot(ITEM_SLOT_ID)
+ if(id_slot)
+ var/obj/item/card/id/id_card = id_slot?.GetID()
+ if(id_card)
+ SEND_SIGNAL(id_card, COMSIG_ID_GET_HONORIFIC, stored_name, carbon_human)
+
+ if(!stored_name[NAME_PART_INDEX]) //Otherwise, we just use whatever the name signal gives us.
+ SEND_SIGNAL(speaker, COMSIG_MOVABLE_MESSAGE_GET_NAME_PART, stored_name, visible_name)
+
namepart = stored_name[NAME_PART_INDEX] || "[speaker.GetVoice()]"
//End name span.
diff --git a/code/modules/admin/sql_ban_system.dm b/code/modules/admin/sql_ban_system.dm
index 2744d51bb529f..78837e9175d3a 100644
--- a/code/modules/admin/sql_ban_system.dm
+++ b/code/modules/admin/sql_ban_system.dm
@@ -391,6 +391,7 @@
ROLE_REV,
ROLE_REVENANT,
ROLE_REV_HEAD,
+ ROLE_SPACE_DRAGON,
ROLE_SPIDER,
ROLE_SPY,
ROLE_SYNDICATE,
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index e8d117fdc94ff..71374bf2464ff 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -497,7 +497,7 @@
var/new_value = input(usr, "Enter the forced threat level for dynamic mode.", "Forced threat level") as num
if (new_value > 100)
- return tgui_alert(usr, "The value must be be under 100.")
+ return tgui_alert(usr, "The value must be under 100.")
GLOB.dynamic_forced_threat_level = new_value
log_admin("[key_name(usr)] set 'forced_threat_level' to [GLOB.dynamic_forced_threat_level].")
diff --git a/code/modules/admin/verbs/adminfun.dm b/code/modules/admin/verbs/adminfun.dm
index 984ced4c0bf7e..87109dd01f9f3 100644
--- a/code/modules/admin/verbs/adminfun.dm
+++ b/code/modules/admin/verbs/adminfun.dm
@@ -167,8 +167,12 @@ ADMIN_VERB_AND_CONTEXT_MENU(admin_smite, R_ADMIN|R_FUN, "Smite", "Smite a player
smite.effect(user, target)
/// "Turns" people into objects. Really, we just add them to the contents of the item.
-/proc/objectify(atom/movable/target, path)
- var/atom/tomb = new path(get_turf(target))
+/proc/objectify(atom/movable/target, path_or_instance)
+ var/atom/tomb
+ if(ispath(path_or_instance))
+ tomb = new path_or_instance(get_turf(target))
+ else
+ tomb = path_or_instance
target.forceMove(tomb)
target.AddComponent(/datum/component/itembound, tomb)
diff --git a/code/modules/admin/verbs/spawnobjasmob.dm b/code/modules/admin/verbs/spawnobjasmob.dm
index e673202f0bae1..c8d9ba3719d13 100644
--- a/code/modules/admin/verbs/spawnobjasmob.dm
+++ b/code/modules/admin/verbs/spawnobjasmob.dm
@@ -4,7 +4,7 @@ ADMIN_VERB(spawn_obj_as_mob, R_SPAWN, "Spawn Object-Mob", "Spawn an object as if
if (!chosen)
return
- var/mob/living/simple_animal/hostile/mimic/copy/basemob = /mob/living/simple_animal/hostile/mimic/copy
+ var/mob/living/basic/mimic/copy/basemob = /mob/living/basic/mimic/copy
var/obj/chosen_obj = text2path(chosen)
@@ -54,8 +54,8 @@ ADMIN_VERB(spawn_obj_as_mob, R_SPAWN, "Spawn Object-Mob", "Spawn an object as if
"mobtype" = list(
"desc" = "Base mob type",
"type" = "datum",
- "path" = "/mob/living/simple_animal/hostile/mimic/copy",
- "value" = "/mob/living/simple_animal/hostile/mimic/copy",
+ "path" = "/mob/living/basic/mimic/copy",
+ "value" = "/mob/living/basic/mimic/copy",
),
"ckey" = list(
"desc" = "ckey",
@@ -71,13 +71,14 @@ ADMIN_VERB(spawn_obj_as_mob, R_SPAWN, "Spawn Object-Mob", "Spawn an object as if
chosen_obj = text2path(mainsettings["objtype"]["value"])
basemob = text2path(mainsettings["mobtype"]["value"])
- if (!ispath(basemob, /mob/living/simple_animal/hostile/mimic/copy) || !ispath(chosen_obj, /obj))
+ if (!ispath(basemob, /mob/living/basic/mimic/copy) || !ispath(chosen_obj, /obj))
to_chat(user.mob, "Mob or object path invalid", confidential = TRUE)
basemob = new basemob(get_turf(user.mob), new chosen_obj(get_turf(user.mob)), user.mob, mainsettings["dropitem"]["value"] == "Yes" ? FALSE : TRUE, (mainsettings["googlyeyes"]["value"] == "Yes" ? FALSE : TRUE))
if (mainsettings["disableai"]["value"] == "Yes")
- basemob.toggle_ai(AI_OFF)
+ qdel(basemob.ai_controller)
+ basemob.ai_controller = null
if (mainsettings["idledamage"]["value"] == "No")
basemob.idledamage = FALSE
@@ -85,7 +86,9 @@ ADMIN_VERB(spawn_obj_as_mob, R_SPAWN, "Spawn Object-Mob", "Spawn an object as if
if (mainsettings["access"])
var/newaccess = text2path(mainsettings["access"]["value"])
if (ispath(newaccess))
- basemob.access_card = new newaccess
+ var/obj/item/card/id/id = new newaccess //cant do initial on lists
+ basemob.AddComponent(/datum/component/simple_access, id.access)
+ qdel(id)
if (mainsettings["maxhealth"]["value"])
if (!isnum(mainsettings["maxhealth"]["value"]))
diff --git a/code/modules/antagonists/heretic/influences.dm b/code/modules/antagonists/heretic/influences.dm
index fa27f481da9fe..cc2e7556816a5 100644
--- a/code/modules/antagonists/heretic/influences.dm
+++ b/code/modules/antagonists/heretic/influences.dm
@@ -113,7 +113,7 @@
if(prob(25))
to_chat(human_user, span_userdanger("An otherwordly presence tears and atomizes your [their_poor_arm.name] as you try to touch the hole in the very fabric of reality!"))
their_poor_arm.dismember()
- qdel(their_poor_arm)
+ forceMove(their_poor_arm, src) // stored for later fishage
else
to_chat(human_user,span_danger("You pull your hand away from the hole as the eldritch energy flails, trying to latch onto existence itself!"))
return TRUE
@@ -130,13 +130,18 @@
var/mob/living/carbon/human/human_user = user
+ // You see, these tendrils are psychic. That's why you can't see them. Definitely not laziness. Just psychic. The character can feel but not see them.
+ // Because they're psychic. Yeah.
+ if(human_user.can_block_magic(MAGIC_RESISTANCE_MIND))
+ visible_message(span_danger("Psychic endrils lash out from [src], batting ineffectively at [user]'s head."))
+ return
+
// A very elaborate way to suicide
- to_chat(human_user, span_userdanger("Eldritch energy lashes out, piercing your fragile mind, tearing it to pieces!"))
- human_user.ghostize()
+ visible_message(span_userdanger("Psychic tendrils lash out from [src], psychically grabbing onto [user]'s psychically sensitive mind and tearing [user.p_their()] head off!"))
var/obj/item/bodypart/head/head = locate() in human_user.bodyparts
if(head)
head.dismember()
- qdel(head)
+ forceMove(head, src) // stored for later fishage
else
human_user.gib(DROP_ALL_REMAINS)
human_user.investigate_log("has died from using telekinesis on a heretic influence.", INVESTIGATE_DEATHS)
diff --git a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm
index 4238b54f91543..618ee9e6f667e 100644
--- a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm
+++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm
@@ -109,15 +109,15 @@
bloodiest_wound.adjust_blood_flow(-0.5 * seconds_between_ticks)
/// Torment the target with a frightening hand
-/proc/fire_curse_hand(mob/living/carbon/victim, turf/forced_turf)
+/proc/fire_curse_hand(mob/living/carbon/victim, turf/forced_turf, range = 8, projectile_type = /obj/projectile/curse_hand/hel)
var/grab_dir = turn(victim.dir, pick(-90, 90, 180, 180)) // Not in front, favour behind
- var/turf/spawn_turf = get_ranged_target_turf(victim, grab_dir, 8)
+ var/turf/spawn_turf = get_ranged_target_turf(victim, grab_dir, range)
spawn_turf = forced_turf ? forced_turf : spawn_turf
if (isnull(spawn_turf))
return
new /obj/effect/temp_visual/dir_setting/curse/grasp_portal(spawn_turf, victim.dir)
playsound(spawn_turf, 'sound/effects/curse/curse2.ogg', 80, TRUE, -1)
- var/obj/projectile/curse_hand/hel/hand = new (spawn_turf)
+ var/obj/projectile/hand = new projectile_type(spawn_turf)
hand.aim_projectile(victim, spawn_turf)
if (QDELETED(hand)) // safety check if above fails - above has a stack trace if it does fail
return
diff --git a/code/modules/antagonists/heretic/magic/furious_steel.dm b/code/modules/antagonists/heretic/magic/furious_steel.dm
index 4f2bb2dd7f108..ec3248af422fe 100644
--- a/code/modules/antagonists/heretic/magic/furious_steel.dm
+++ b/code/modules/antagonists/heretic/magic/furious_steel.dm
@@ -1,7 +1,7 @@
/datum/action/cooldown/spell/pointed/projectile/furious_steel
name = "Furious Steel"
desc = "Summon three silver blades which orbit you. \
- While orbiting you, these blades will protect you from from attacks, but will be consumed on use. \
+ While orbiting you, these blades will protect you from attacks, but will be consumed on use. \
Additionally, you can click to fire the blades at a target, dealing damage and causing bleeding."
background_icon_state = "bg_heretic"
overlay_icon_state = "bg_heretic_border"
@@ -151,7 +151,7 @@
/datum/action/cooldown/spell/pointed/projectile/furious_steel/haunted
name = "Cursed Steel"
desc = "Summon two cursed blades which orbit you. \
- While orbiting you, these blades will protect you from from attacks, but will be consumed on use. \
+ While orbiting you, these blades will protect you from attacks, but will be consumed on use. \
Additionally, you can click to fire the blades at a target, dealing damage and causing bleeding."
background_icon_state = "bg_heretic" // kept intentionally
overlay_icon_state = "bg_cult_border"
diff --git a/code/modules/antagonists/malf_ai/malf_ai_modules.dm b/code/modules/antagonists/malf_ai/malf_ai_modules.dm
index 22e32d8264ab7..c95302c80ae11 100644
--- a/code/modules/antagonists/malf_ai/malf_ai_modules.dm
+++ b/code/modules/antagonists/malf_ai/malf_ai_modules.dm
@@ -479,7 +479,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module/malf))
if(QDELETED(to_animate))
return
- new /mob/living/simple_animal/hostile/mimic/copy/machine(get_turf(to_animate), to_animate, clicker, TRUE)
+ new /mob/living/basic/mimic/copy/machine(get_turf(to_animate), to_animate, clicker, TRUE)
/// Destroy RCDs: Detonates all non-cyborg RCDs on the station.
/datum/ai_module/malf/destructive/destroy_rcd
diff --git a/code/modules/assembly/assembly.dm b/code/modules/assembly/assembly.dm
index dbd5a70461476..0ac5993aeccf5 100644
--- a/code/modules/assembly/assembly.dm
+++ b/code/modules/assembly/assembly.dm
@@ -23,7 +23,7 @@
var/secured = TRUE
var/list/attached_overlays = null
var/obj/item/assembly_holder/holder = null
- var/attachable = FALSE // can this be attached to wires
+ var/assembly_behavior = ASSEMBLY_FUNCTIONAL_OUTPUT // how does the assembly behave with respect to what it's connected to
var/datum/wires/connected = null
var/next_activate = 0 //When we're next allowed to activate - for spam control
@@ -126,7 +126,7 @@
balloon_alert(user, "can't attach another of that!")
return
if(new_assembly.secured || secured)
- balloon_alert(user, "both devices not attachable!")
+ balloon_alert(user, "both devices not assembly_behavior!")
return
holder = new /obj/item/assembly_holder(drop_location())
diff --git a/code/modules/assembly/doorcontrol.dm b/code/modules/assembly/doorcontrol.dm
index 31584976cedf3..3361028350238 100644
--- a/code/modules/assembly/doorcontrol.dm
+++ b/code/modules/assembly/doorcontrol.dm
@@ -2,7 +2,6 @@
name = "blast door controller"
desc = "A small electronic device able to control a blast door remotely."
icon_state = "control"
- attachable = TRUE
/// The ID of the blast door electronics to match to the ID of the blast door being used.
var/id = null
/// Cooldown of the door's controller. Updates when pressed (activate())
diff --git a/code/modules/assembly/health.dm b/code/modules/assembly/health.dm
index ad2c6ac17641d..d4635ac159fe7 100644
--- a/code/modules/assembly/health.dm
+++ b/code/modules/assembly/health.dm
@@ -3,7 +3,7 @@
desc = "Used for scanning and monitoring health."
icon_state = "health"
custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*8, /datum/material/glass=SMALL_MATERIAL_AMOUNT * 2)
- attachable = TRUE
+ assembly_behavior = ASSEMBLY_TOGGLEABLE_INPUT
var/scanning = FALSE
var/health_scan
diff --git a/code/modules/assembly/mousetrap.dm b/code/modules/assembly/mousetrap.dm
index 93af285369af9..abdda209bd057 100644
--- a/code/modules/assembly/mousetrap.dm
+++ b/code/modules/assembly/mousetrap.dm
@@ -4,7 +4,7 @@
icon_state = "mousetrap"
inhand_icon_state = "mousetrap"
custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT)
- attachable = TRUE
+ assembly_behavior = ASSEMBLY_TOGGLEABLE_INPUT
var/armed = FALSE
drop_sound = 'sound/items/handling/component_drop.ogg'
pickup_sound = 'sound/items/handling/component_pickup.ogg'
diff --git a/code/modules/assembly/proximity.dm b/code/modules/assembly/proximity.dm
index 6ba2a7a63421e..9b7ccad9aefcc 100644
--- a/code/modules/assembly/proximity.dm
+++ b/code/modules/assembly/proximity.dm
@@ -3,7 +3,7 @@
desc = "Used for scanning and alerting when someone enters a certain proximity."
icon_state = "prox"
custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*8, /datum/material/glass=SMALL_MATERIAL_AMOUNT * 2)
- attachable = TRUE
+ assembly_behavior = ASSEMBLY_TOGGLEABLE_INPUT
drop_sound = 'sound/items/handling/component_drop.ogg'
pickup_sound = 'sound/items/handling/component_pickup.ogg'
var/scanning = FALSE
diff --git a/code/modules/assembly/signaler.dm b/code/modules/assembly/signaler.dm
index 4e265384ace24..b5346386d7dd3 100644
--- a/code/modules/assembly/signaler.dm
+++ b/code/modules/assembly/signaler.dm
@@ -6,7 +6,7 @@
lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi'
custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT * 4, /datum/material/glass=SMALL_MATERIAL_AMOUNT*1.2)
- attachable = TRUE
+ assembly_behavior = ASSEMBLY_ALL
drop_sound = 'sound/items/handling/component_drop.ogg'
pickup_sound = 'sound/items/handling/component_pickup.ogg'
diff --git a/code/modules/assembly/timer.dm b/code/modules/assembly/timer.dm
index 09cbfd9b0dc59..b25d30e1b4b5c 100644
--- a/code/modules/assembly/timer.dm
+++ b/code/modules/assembly/timer.dm
@@ -3,7 +3,7 @@
desc = "Used to time things. Works well with contraptions which has to count down. Tick tock."
icon_state = "timer"
custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*5, /datum/material/glass=SMALL_MATERIAL_AMOUNT*0.5)
- attachable = TRUE
+ assembly_behavior = ASSEMBLY_TOGGLEABLE_INPUT
drop_sound = 'sound/items/handling/component_drop.ogg'
pickup_sound = 'sound/items/handling/component_pickup.ogg'
diff --git a/code/modules/assembly/voice.dm b/code/modules/assembly/voice.dm
index de0954c960e5e..106b812730718 100644
--- a/code/modules/assembly/voice.dm
+++ b/code/modules/assembly/voice.dm
@@ -8,7 +8,7 @@
desc = "A small electronic device able to record a voice sample, and send a signal when that sample is repeated."
icon_state = "voice"
custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*5, /datum/material/glass=SMALL_MATERIAL_AMOUNT*0.5)
- attachable = TRUE
+ assembly_behavior = ASSEMBLY_TOGGLEABLE_INPUT
verb_say = "beeps"
verb_ask = "beeps"
verb_exclaim = "beeps"
diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm
index 27cb78bb26ce7..e36d99cb6d73d 100644
--- a/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm
+++ b/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm
@@ -25,7 +25,7 @@
fusion_process(seconds_per_tick)
// Note that we process damage/healing even if the fusion process aborts.
// Running out of fuel won't save you if your moderator and coolant are exploding on their own.
- check_spill()
+ process_moderator_overflow()
process_damageheal(seconds_per_tick)
check_alert()
if (start_power)
diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm
index bc27ab0a42e36..2c77ac829b735 100644
--- a/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm
+++ b/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm
@@ -569,18 +569,27 @@
* HFR cracking related procs
*/
+/**
+ * Checks for any hypertorus part that is cracked and returns it if found, otherwise returns null.
+ */
/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/check_cracked_parts()
for(var/obj/machinery/atmospherics/components/unary/hypertorus/part in machine_parts)
if(part.cracked)
- return TRUE
- return FALSE
+ return part
-/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/create_crack()
+/**
+ * Causes a random hypertorus part in machine_parts to become cracked and update their appearance.
+ * Returns the hypertorus part.
+ */
+/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/create_crack() as /obj/machinery/atmospherics/components/unary/hypertorus
var/obj/machinery/atmospherics/components/unary/hypertorus/part = pick(machine_parts)
part.cracked = TRUE
part.update_appearance()
return part
+/**
+ * Takes a ratio portion of target_mix and moves it to the origin's location's air.
+ */
/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/spill_gases(obj/origin, datum/gas_mixture/target_mix, ratio)
var/datum/gas_mixture/remove_mixture = target_mix.remove_ratio(ratio)
var/turf/origin_turf = origin.loc
@@ -588,8 +597,12 @@
return
origin_turf.assume_air(remove_mixture)
-/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/check_spill(seconds_per_tick)
+/**
+ * Processes leaking from moderator hypercriticality.
+ */
+/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/process_moderator_overflow(seconds_per_tick)
var/obj/machinery/atmospherics/components/unary/hypertorus/cracked_part = check_cracked_parts()
+ // Processing of a preexisting crack if any.
if (cracked_part)
// We have an existing crack
var/leak_rate
@@ -607,6 +620,7 @@
spill_gases(cracked_part, moderator_internal, ratio = 1 - (1 - leak_rate) ** seconds_per_tick)
return
+ // No crack. Check for conditions to cause a leak and create a crack if possible.
if (moderator_internal.total_moles() < HYPERTORUS_HYPERCRITICAL_MOLES)
return
cracked_part = create_crack()
diff --git a/code/modules/client/verbs/who.dm b/code/modules/client/verbs/who.dm
index c9e500e7dbc78..351fd17cdc162 100644
--- a/code/modules/client/verbs/who.dm
+++ b/code/modules/client/verbs/who.dm
@@ -5,7 +5,7 @@
set name = "Who"
set category = "OOC"
- var/msg = "Current Players:\n"
+ var/msg = ""
var/list/Lines = list()
var/columns_per_row = DEFAULT_WHO_CELLS_PER_ROW
@@ -67,7 +67,7 @@
msg += ""
msg += "Total Players: [length(Lines)]"
- to_chat(src, span_infoplain("[msg]"))
+ to_chat(src, fieldset_block(span_bold("Current Players"), span_infoplain(msg), "boxed_message"), type = MESSAGE_TYPE_OOC)
/client/verb/adminwho()
set category = "Admin"
@@ -75,18 +75,12 @@
var/list/lines = list()
var/payload_string = generate_adminwho_string()
- var/header
-
- if(payload_string == NO_ADMINS_ONLINE_MESSAGE)
- header = "No Admins Currently Online"
- else
- header = "Current Admins:"
+ var/header = (payload_string == NO_ADMINS_ONLINE_MESSAGE) ? "No Admins Currently Online" : "Current Admins"
lines += span_bold(header)
lines += payload_string
- var/finalized_string = boxed_message(jointext(lines, "\n"))
- to_chat(src, finalized_string)
+ to_chat(src, fieldset_block(span_bold(header), jointext(lines, "\n"), "boxed_message"), type = MESSAGE_TYPE_OOC)
/// Proc that generates the applicable string to dispatch to the client for adminwho.
/client/proc/generate_adminwho_string()
diff --git a/code/modules/clothing/gloves/combat.dm b/code/modules/clothing/gloves/combat.dm
index 55eeeba723f11..e7e12c8ee4b14 100644
--- a/code/modules/clothing/gloves/combat.dm
+++ b/code/modules/clothing/gloves/combat.dm
@@ -35,6 +35,6 @@
icon_state = "ftc_gloves"
inhand_icon_state = "greyscale_gloves"
-/obj/item/clothing/gloves/combat/floortiletile/Initialize(mapload)
+/obj/item/clothing/gloves/combat/floortile/Initialize(mapload)
. = ..()
AddComponent(/datum/component/adjust_fishing_difficulty, -5) //tacticool
diff --git a/code/modules/clothing/head/garlands.dm b/code/modules/clothing/head/garlands.dm
index 4de0604044e32..374bd2fdafd4b 100644
--- a/code/modules/clothing/head/garlands.dm
+++ b/code/modules/clothing/head/garlands.dm
@@ -47,7 +47,7 @@
/obj/item/clothing/head/costume/garland/lily
name = "lily crown"
- desc = "A leafy flower crown with a cluster of large white lilies at at the front."
+ desc = "A leafy flower crown with a cluster of large white lilies at the front."
icon_state = "lily_crown"
worn_icon_state = "lily_crown"
diff --git a/code/modules/clothing/neck/collar_bomb.dm b/code/modules/clothing/neck/collar_bomb.dm
index 7a5314f4c18c6..372c6ab7cc58a 100644
--- a/code/modules/clothing/neck/collar_bomb.dm
+++ b/code/modules/clothing/neck/collar_bomb.dm
@@ -109,7 +109,7 @@
return
var/mob/living/carbon/human/brian = collar.loc
if(brian.get_item_by_slot(ITEM_SLOT_NECK) == collar)
- brian.investigate_log("has has their [collar] triggered by [user] via yellow button.", INVESTIGATE_DEATHS)
+ brian.investigate_log("has had their [collar] triggered by [user] via yellow button.", INVESTIGATE_DEATHS)
/obj/item/collar_bomb_button/Destroy()
diff --git a/code/modules/deathmatch/deathmatch_modifier.dm b/code/modules/deathmatch/deathmatch_modifier.dm
index edddbe7e267bc..77ef9785aba4e 100644
--- a/code/modules/deathmatch/deathmatch_modifier.dm
+++ b/code/modules/deathmatch/deathmatch_modifier.dm
@@ -388,6 +388,7 @@
contents = list(
/mob/living/basic/ant = 2,
/mob/living/basic/construct/proteon = 2,
+ /mob/living/basic/dark_wizard = 2,
/mob/living/basic/flesh_spider = 2,
/mob/living/basic/garden_gnome = 2,
/mob/living/basic/killer_tomato = 2,
@@ -399,13 +400,12 @@
/mob/living/basic/mining/lobstrosity = 1,
/mob/living/basic/mining/mook = 2,
/mob/living/basic/mouse/rat = 2,
+ /mob/living/basic/vatbeast = 1,
/mob/living/basic/spider/giant/nurse/scrawny = 2,
/mob/living/basic/spider/giant/tarantula/scrawny = 2,
/mob/living/basic/spider/giant/hunter/scrawny = 2,
- /mob/living/simple_animal/hostile/dark_wizard = 2,
/mob/living/simple_animal/hostile/retaliate/goose = 2,
/mob/living/simple_animal/hostile/ooze = 1,
- /mob/living/simple_animal/hostile/vatbeast = 1,
)
/datum/deathmatch_modifier/drop_pod/missiles
diff --git a/code/modules/events/ion_storm.dm b/code/modules/events/ion_storm.dm
index 9c9d81d01d73f..1141b94e82286 100644
--- a/code/modules/events/ion_storm.dm
+++ b/code/modules/events/ion_storm.dm
@@ -459,7 +459,7 @@
if(6) //X is a job
message = "YOU MUST HARM [ioncrew1] AND NOT ALLOW [ioncrew1], THROUGH INACTION, TO ESCAPE HARM"
if(7) //X is two jobs
- message = "YOU MUST HARM [ioncrew1] AND [ioncrew2] AND AND NOT ALLOW EITHER, THROUGH INACTION, TO ESCAPE HARM"
+ message = "YOU MUST HARM [ioncrew1] AND [ioncrew2] AND NOT ALLOW EITHER, THROUGH INACTION, TO ESCAPE HARM"
if(2) //Protect
switch(rand(1,7)) //What is X?
diff --git a/code/modules/events/stray_cargo.dm b/code/modules/events/stray_cargo.dm
index 0be1138f59a59..0514af5764e3d 100644
--- a/code/modules/events/stray_cargo.dm
+++ b/code/modules/events/stray_cargo.dm
@@ -149,7 +149,7 @@
var/admin_selected_pack = tgui_alert(usr,"Customize Pod contents?", "Pod Contents", list("Yes", "No", "Cancel"))
switch(admin_selected_pack)
if("Yes")
- override_contents()
+ return override_contents()
if("No")
pack_type_override = null
else
@@ -161,7 +161,14 @@
var/pack_telecrystals = tgui_input_number(usr, "Please input crate's value in telecrystals.", "Set Telecrystals.", 30)
if(isnull(pack_telecrystals))
return ADMIN_CANCEL_EVENT
- var/list/possible_uplinks = list("Traitor" = UPLINK_TRAITORS, "Nuke Op" = UPLINK_NUKE_OPS, "Clown Op" = UPLINK_CLOWN_OPS)
+ var/list/possible_uplinks = list(
+ "Traitor" = UPLINK_TRAITORS,
+ "Nuke Op" = UPLINK_NUKE_OPS,
+ "Clown Op" = UPLINK_CLOWN_OPS,
+ "Lone Op" = UPLINK_LONE_OP,
+ "Infiltrator" = UPLINK_INFILTRATORS,
+ "Spy" = UPLINK_SPY
+ )
var/uplink_type = tgui_input_list(usr, "Choose uplink to draw items from.", "Choose uplink type.", possible_uplinks)
var/selection
if(!isnull(uplink_type))
diff --git a/code/modules/fishing/fish/fish_traits.dm b/code/modules/fishing/fish/fish_traits.dm
index d70b6b119f78c..f6ca105f1e2e8 100644
--- a/code/modules/fishing/fish/fish_traits.dm
+++ b/code/modules/fishing/fish/fish_traits.dm
@@ -325,6 +325,15 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits())
stench.temperature = mob.bodytemperature
our_turf.assume_air(stench)
+/datum/fish_trait/emulsijack/psychic
+ name = "Psychic Aura"
+ catalog_description = "This fish emits an almost unblockable psychic aura that assaults minds, slowly killing all nearby fish and making humanoids have a bad time."
+ resistance_traits = list(TRAIT_RESIST_PSYCHIC)
+ trait_to_add = TRAIT_RESIST_PSYCHIC
+
+/datum/fish_trait/emulsijack/psychic/on_non_stasis_life(mob/living/basic/mob, seconds_per_tick)
+ return
+
/datum/fish_trait/necrophage
name = "Necrophage"
catalog_description = "This fish will eat carcasses of dead fish when hungry."
@@ -721,7 +730,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits())
/datum/fish_trait/toxic_barbs
name = "Toxic Barbs"
- catalog_description = "This fish' stinger, bill or otherwise, is coated with simple, yet effetive venom."
+ catalog_description = "This fish' stinger, bill or otherwise, is coated with simple, yet effective venom."
spontaneous_manifest_types = list(/obj/item/fish/stingray = 35)
/datum/fish_trait/toxic_barbs/apply_to_fish(obj/item/fish/fish)
@@ -753,6 +762,26 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits())
if(!HAS_TRAIT(src, TRAIT_FOOD_FRIED) && !HAS_TRAIT(src, TRAIT_FOOD_BBQ_GRILLED))
return ..()
+/datum/fish_trait/hallucinogenic/apply_to_fish(obj/item/fish/fish)
+ . = ..()
+ RegisterSignal(fish, COMSIG_FISH_UPDATE_SIZE_AND_WEIGHT, PROC_REF(make_venomous))
+ RegisterSignal(fish, COMSIG_FISH_STATUS_CHANGED, PROC_REF(on_status_change))
+
+/datum/fish_trait/hallucinogenic/proc/make_venomous(obj/item/fish/source, new_size, new_weight)
+ SIGNAL_HANDLER
+ if(!HAS_TRAIT(source, TRAIT_FISH_STINGER))
+ ///Remove the trait from the fish so it doesn't show on the analyzer as it doesn't do anything on stingerless ones.
+ source.fish_traits -= type
+ UnregisterSignal(source, list(COMSIG_FISH_UPDATE_SIZE_AND_WEIGHT, COMSIG_FISH_STATUS_CHANGED))
+ return
+ add_venom(source, /datum/reagent/toxin/mindbreaker/fish, new_weight, mult = source.status == FISH_DEAD ? 0.3 : 0.7)
+
+/datum/fish_trait/hallucinogenic/proc/on_status_change(obj/item/fish/source)
+ SIGNAL_HANDLER
+ if(!HAS_TRAIT(source, TRAIT_FISH_STINGER))
+ return
+ change_venom_on_death(source, /datum/reagent/toxin/mindbreaker/fish, 0.7, 0.3)
+
/datum/fish_trait/ink
name = "Ink Production"
catalog_description = "This fish possess a sac that produces ink."
diff --git a/code/modules/fishing/fish/types/rift.dm b/code/modules/fishing/fish/types/rift.dm
index 7bcdd751a3004..5cbad079bd817 100644
--- a/code/modules/fishing/fish/types/rift.dm
+++ b/code/modules/fishing/fish/types/rift.dm
@@ -400,3 +400,419 @@
visible_message(span_suicide("[user]'s skin turns into quartz upon contact with the oxygen in the air!'"))
qdel(src)
return MANUAL_SUICIDE
+
+// The (other) reason you DON'T fish in rifts.
+// This thing is a plague - throws itself around and envenoms those it hits.
+// Worse, it's immune to the environment and revives either way.
+/obj/item/fish/mossglob
+ name = "mossglob"
+ fish_id = "mossglob"
+ desc = "This dreaded, malicious, and nearly unkillable glob of moss is rumoured to be nature's revenge against fishermen."
+ icon = 'icons/obj/aquarium/rift.dmi'
+ dedicated_in_aquarium_icon = 'icons/obj/aquarium/rift.dmi'
+ icon_state = "mossglob"
+ lefthand_file = 'icons/mob/inhands/fish_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/fish_righthand.dmi'
+ attack_verb_continuous = list("stings", "pricks")
+ attack_verb_simple = list("sting", "prick")
+ force = 11
+ damtype = TOX
+ sharpness = SHARP_POINTY
+ throwforce = 9
+ throw_range = 7
+ hitsound = SFX_ALT_FISH_SLAP
+
+ sprite_width = 12
+ sprite_height = 13
+
+ health = 500
+ death_text = "%SRC decomposes."
+ random_case_rarity = FISH_RARITY_NOPE
+ // hand-tuned to be a your worst enemy
+ fish_traits = list(
+ /datum/fish_trait/wary, /datum/fish_trait/nocturnal, /datum/fish_trait/emulsijack,
+ /datum/fish_trait/yucky, /datum/fish_trait/lubed, /datum/fish_trait/revival,
+ /datum/fish_trait/toxin_immunity, /datum/fish_trait/hallucinogenic,
+ /datum/fish_trait/stinger, /datum/fish_trait/toxic_barbs,
+ )
+ beauty = FISH_BEAUTY_DISGUSTING
+ fish_movement_type = /datum/fish_movement/slow // a very easy catch!
+ fishing_difficulty_modifier = -35
+ favorite_bait = list(/obj/item/food/badrecipe)
+ average_size = 150
+ average_weight = 5000
+ required_fluid_type = AQUARIUM_FLUID_ANY_WATER
+ required_temperature_min = 0
+ required_temperature_max = INFINITY
+ min_pressure = 0
+ max_pressure = INFINITY
+ safe_air_limits = list()
+ fillet_type = /obj/item/food/badrecipe/moldy/bacteria
+ stable_population = 0
+
+/obj/item/fish/mossglob/Initialize(mapload, apply_qualities)
+ . = ..()
+ AddElement(/datum/element/haunted, COLOR_GREEN)
+
+/obj/item/fish/mossglob/set_status(new_status, silent)
+ . = ..()
+ if(new_status == FISH_DEAD)
+ RemoveElement(/datum/element/haunted, COLOR_GREEN)
+ else if(new_status == FISH_ALIVE)
+ AddElement(/datum/element/haunted, COLOR_GREEN)
+
+/obj/item/fish/mossglob/suicide_act(mob/living/user)
+ visible_message(span_suicide("[user] sticks [user.p_their()] arm deep into [src]! It looks like they're trying to offer themselves to it!"))
+ user.drop_everything()
+ set_status(FISH_ALIVE)
+ transform = transform.Scale(1.15, 1.15)
+ update_size_and_weight(new_size = size * 1.15, new_weight = weight * 1.15)
+ visible_message(span_suicide("[user] is absorbed into [src]!"))
+ objectify(user, src)
+ return MANUAL_SUICIDE_NONLETHAL
+
+/obj/item/fish/mossglob/get_force_rank()
+ var/multiplier = 1
+ switch(w_class)
+ if(WEIGHT_CLASS_TINY)
+ multiplier = 0.35
+ if(WEIGHT_CLASS_SMALL)
+ multiplier = 0.5
+ if(WEIGHT_CLASS_NORMAL)
+ multiplier = 0.75
+ if(WEIGHT_CLASS_BULKY)
+ multiplier = 0.85
+ // huge is avergae
+ if(WEIGHT_CLASS_GIGANTIC)
+ multiplier = 1.15
+
+ if(status == FISH_DEAD)
+ multiplier -= 0.35 // huge nerf if dead
+
+ force *= multiplier
+ attack_speed *= multiplier
+ demolition_mod *= multiplier
+ block_chance *= multiplier
+ armour_penetration *= multiplier
+
+// Babbelfish are psychic 'predators' that don't physically attack their prey, but emit a psychic aura that kills them, eating their corpses.
+// When they die they emit a horrendous wail that deafens and debilitates people nearby - let alone fish.
+/obj/item/fish/babbelfish
+ name = "babbelfish"
+ fish_id = "babbelfish"
+ desc = "Babbelfish are both visually -and- psychically unsettling - their psychic wails damage the minds of those nearby. The effect is negligible on humans, but deadly for fish. \
+ It is said that splitting one in two and inserting the pieces into each ear unlocks your psychic potential."
+ icon = 'icons/obj/aquarium/rift.dmi'
+ dedicated_in_aquarium_icon = 'icons/obj/aquarium/rift.dmi'
+ icon_state = "babbelfish"
+ force = 7
+ damtype = BRAIN
+ attack_verb_continuous = list("screeches", "shrieks")
+ attack_verb_simple = list("screech", "shriek")
+ hitsound = SFX_DEFAULT_FISH_SLAP // todo shriek
+ sound_vary = TRUE
+
+ sprite_width = 11
+ sprite_height = 13
+
+ death_text = span_big(span_alertalien("%SRC emits a horrendous wailing as it perishes!"))
+ random_case_rarity = FISH_RARITY_NOPE
+ health = 250
+ average_size = 30
+ average_weight = 2000
+ fillet_type = /obj/item/food/fishmeat/quality
+ num_fillets = 2
+ stable_population = 3
+ fish_traits = list(
+ /datum/fish_trait/wary, /datum/fish_trait/picky_eater, /datum/fish_trait/heavy,
+ /datum/fish_trait/emulsijack/psychic, /datum/fish_trait/necrophage,
+ )
+ beauty = FISH_BEAUTY_NULL // unsettling yet also awing
+ fish_movement_type = /datum/fish_movement/slow
+ fishing_difficulty_modifier = 15
+ favorite_bait = list(/obj/item/organ/ears)
+ var/mob/living/moron_inside
+
+// When someone refactors demoralizers to not be omega hardcoded for syndicate this fish should get it
+
+/obj/item/fish/babbelfish/examine_more(mob/user)
+ . = ..()
+ . += span_smallnoticeital(
+ "“Sorry, you speak Anglish, how is that possible?\n\
+ “I speak many languages,” the pile of octopuses replied. It was hard to get a handle on where it was speaking from, or how, and that was with me using vibration magic to check. “Those are babel fish in front of you. They're very rare, and available for a good trade.”\n\
+ “Babel fish,” I said, pointing down at the tank with the bright yellow fish. “Meaning … fish capable of letting you hear any language?” That still wouldn't have explained how the octopus pile spoke Anglish.\n\
+ “Ah, no, my apologies, I'm afraid not,” they replied, wiggling some tentacles. “These fish, if put into your ear, will make everything another person says sound like gibberish.”\n\
+ “Ah,” I replied. “Babble fish. And if two people with babble fish in their ears talk to each other, they're suddenly mutually intelligible?”\n\
+ The octopus pile swayed from side to side. “No.”\n\
+ “I don't know your business,” replied the octopus pile. “Why do you want them?”\n\
+ I looked down at the fish. “Uh,” I said. “I guess … it would help to keep me from hearing things I didn't want to hear?”\n\
+ They burst into applause, which in this case was a bunch of tentacles wetly slapping against equally wet flesh. “Very good! I hadn't thought of that.”\n\
+ “But then why,” I began, then thought better of it. “Alright, I''ll buy one. But, you need to explain to me how you speak Anglish.”")
+
+/**
+ * In the suicide:
+ * - If the fish is dead:
+ * - If someone is already inside, cancel the suicide.
+ * Drop the suicider's items and shove them inside the object. They are now the fish.
+ *
+ * - If the fish is alive:
+ * the idiot uses the babbelfish as a vuvuzela and becomes a god for half a second before their brain is imploded.
+ */
+/obj/item/fish/babbelfish/suicide_act(mob/living/user)
+ if(status == FISH_DEAD)
+ if(moron_inside)
+ visible_message(span_suicide("[user] puts [src] against their lips, but [src] is already full!"))
+ return SHAME
+ visible_message(span_suicide("[user] puts [src] against their lips, but [src]'s psychic afterimage sucks [user.p_them()] inward!"))
+ user.drop_everything()
+ objectify(user, src)
+ user.fully_replace_character_name(null, name) // fish's name
+ set_status(FISH_ALIVE) // RIIIIIISE!!!!!
+ moron_inside = user
+ RegisterSignal(src, COMSIG_FISH_STATUS_CHANGED, PROC_REF(fishes_die_twice))
+ RegisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(check_loc))
+ return MANUAL_SUICIDE_NONLETHAL // in case they somehow break out
+
+ visible_message(span_suicide("[user] puts [src] against their lips! It looks like they're preparing to say something!"))
+ var/psychic_speech = tgui_input_text(user, message = "Say something!", title = "What are your last words?", timeout = 15 SECONDS)
+ if(!psychic_speech || !locate(src) in user.get_contents())
+ user.say("Err, umm... uhh... erm...", forced = "blustering like a moron due to babbelfish suicide")
+ visible_message(span_suicide("[user] dies from shame!"))
+ return OXYLOSS
+
+ voice_of_god(psychic_speech, user, list("big", "alertalien"), base_multiplier = 5, include_speaker = TRUE, forced = TRUE, ignore_spam = TRUE)
+ psy_wail()
+ user.adjustOrganLoss(ORGAN_SLOT_BRAIN, INFINITY, INFINITY, ORGAN_SLOT_BRAIN)
+ user.death()
+ return MANUAL_SUICIDE
+
+/**
+ * When the fish dies you die in real life.
+ * Consequently, when the fish is magically resuscitated the bound mob is revived as well.
+ */
+/obj/item/fish/babbelfish/proc/fishes_die_twice()
+ if(isnull(moron_inside))
+ UnregisterSignal(src, COMSIG_FISH_STATUS_CHANGED)
+ if(status == FISH_DEAD)
+ moron_inside.death()
+ if(status == FISH_ALIVE)
+ moron_inside.revive(HEAL_ALL)
+
+/**
+ * If they somehow escaped null the variable.
+ */
+/obj/item/fish/babbelfish/proc/check_loc()
+ if(locate(moron_inside) in src)
+ return
+ UnregisterSignal(moron_inside, COMSIG_MOVABLE_MOVED)
+ moron_inside = null
+
+/obj/item/fish/babbelfish/set_status(new_status, silent)
+ . = ..()
+ // Death message plays here
+ if(new_status != FISH_DEAD)
+ damtype = BRAIN
+ return
+ psy_wail()
+ damtype = BRUTE
+
+/**
+ * When a babbelfish dies, it will let out a wail that kills any fish nearby, alongside severely incapacitating anyone else around.
+ * This is punishment for neglecting your catches.
+ */
+/obj/item/fish/babbelfish/proc/psy_wail()
+ manual_emote("wails!")
+ playsound(src, 'sound/mobs/non-humanoids/fish/fish_psyblast.ogg', 100)
+ var/list/mob/living/mobs_in_range = get_hearers_in_range(7, src)
+ for(var/mob/living/screeched in mobs_in_range)
+ if(screeched.can_block_magic(MAGIC_RESISTANCE_MIND, charge_cost = 1))
+ to_chat(screeched, span_notice("You resist the psychic wail!"))
+ continue
+ var/power = 1
+ if(!screeched.can_hear()) // bit weaker if deaf. but its still psychic
+ power *= 0.5
+ var/affect_time = 15 SECONDS * power
+ // it really fucks you up
+ screeched.Knockdown(affect_time * 0.1)
+ screeched.adjust_disgust(affect_time)
+ screeched.adjust_stutter(affect_time)
+ screeched.adjust_slurring(affect_time)
+ screeched.adjust_dizzy(affect_time)
+ screeched.adjust_staggered(affect_time)
+ screeched.adjust_jitter(affect_time)
+ screeched.adjust_confusion(affect_time)
+ screeched.adjust_hallucinations(affect_time)
+ screeched.adjust_eye_blur(affect_time)
+ if(iscarbon(screeched))
+ var/mob/living/carbon/carbon_screeched = screeched
+ carbon_screeched.vomit(MOB_VOMIT_MESSAGE)
+ carbon_screeched.adjustOrganLoss(ORGAN_SLOT_BRAIN, 50)
+
+ var/affected = 0
+ for(var/obj/item/fish/fishie in range(7, src))
+ if(HAS_TRAIT(fishie, TRAIT_RESIST_PSYCHIC))
+ continue
+ if(fishie.status == FISH_DEAD)
+ continue
+ fishie.set_status(FISH_DEAD)
+ affected++
+ if(affected)
+ visible_message(span_bolddanger("[src]'s wail kills [affected] fish nearby!")) // m-m-m-m-m-MONSTER KILL
+
+/obj/item/fish/babbelfish/attack_hand(mob/living/user, list/modifiers)
+
+ if(!locate(src) in user)
+ return ..()
+
+ if((user.usable_hands < 2) && !HAS_TRAIT(user, TRAIT_STRENGTH))
+ to_chat(user, span_notice("[src] is too dense to twist apart with only one hand."))
+ return
+
+ to_chat(user, span_danger("You start pulling and twisting [src], trying to split it down the middle..."))
+ if(!do_after(user, 5 SECONDS, src))
+ return
+
+ playsound(get_turf(user), 'sound/effects/wounds/crack1.ogg', 60)
+ set_status(FISH_DEAD)
+ var/cracked = new /obj/item/organ/ears/babbelfish(user)
+ user.put_in_hands(cracked)
+ qdel(src)
+
+/** These ears grant amazing and supernatural hearing, but they also screw over your knowledge of language.
+ * Three outcomes:
+ * The user is able to speak all languages, but can't understand any. Even their own words come across as unintelligible gibberish.
+ * The user is able to understand all languages, but can't speak any. They can only 'make strange noises'.
+ * The user is able to understand and speak all languages. Rare.
+ * All this to be a curator, this is silly.
+*/
+/obj/item/organ/ears/babbelfish
+ name = "split babbelfish halves"
+ icon_state = "babbearfish"
+ desc = "Both halves of a babbelfish after being twisted apart. The legends claim inserting these can unlock your psychic potential. It probably wasn't worth hearing that wail, though."
+ organ_traits = list(TRAIT_XRAY_HEARING, TRAIT_GOOD_HEARING)
+
+ healing_factor = STANDARD_ORGAN_HEALING * 5
+ decay_factor = 0
+
+ low_threshold_passed = span_noticealien("Psychic whispers make it a bit difficult to hear sometimes..")
+ now_failing = span_noticealien("Psychic noise is overcrowding your senses!")
+ now_fixed = span_noticealien("The psychic noise starts to fade.")
+ low_threshold_cleared = span_noticealien("The whispers leave you alone.")
+
+ bang_protect = 5
+ damage_multiplier = 0.1
+ visual = TRUE
+ /// Overlay for the mob sprite because actual organ overlays are a fucking unusable nightmare
+ var/datum/bodypart_overlay/simple/babbearfish/babbel_overlay
+ var/bound_component
+ var/datum/language_holder/removal_holder
+
+/**
+ * The bodypart overlay for babbearfish.
+ * We don't need anything other than this icon (and to hide it sometimes), so it being mutant is unnecessary and a waste of space..
+ * Which breaks everything else. So we need to make it simple. Which organs don't innately support, so we need to just haphazardly slap it on.
+ */
+/datum/bodypart_overlay/simple/babbearfish
+ icon_state = "babbearfish"
+
+/datum/bodypart_overlay/simple/babbearfish/can_draw_on_bodypart(mob/living/carbon/human/human)
+ if((human.head?.flags_inv & HIDEEARS) || (human.wear_mask?.flags_inv & HIDEEARS))
+ return FALSE
+ return TRUE
+
+/obj/item/organ/ears/babbelfish/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/noticable_organ, "%PRONOUN_They %PRONOUN_have weird, deep-rooted obsidian tubes sticking out of where their ears should be.")
+ babbel_overlay = new()
+ removal_holder = new(src)
+
+/obj/item/organ/ears/babbelfish/Destroy()
+ QDEL_NULL(babbel_overlay)
+ QDEL_NULL(removal_holder)
+ QDEL_NULL(bound_component)
+ . = ..()
+
+/obj/item/organ/ears/babbelfish/on_bodypart_insert(obj/item/bodypart/limb)
+ . = ..()
+ limb.add_bodypart_overlay(babbel_overlay)
+
+/obj/item/organ/ears/babbelfish/on_bodypart_remove(obj/item/bodypart/limb)
+ . = ..()
+ limb.remove_bodypart_overlay(babbel_overlay)
+
+/obj/item/organ/ears/babbelfish/attack(mob/living/target_mob, mob/living/user, params)
+ . = ..()
+ var/obj/item/organ/ears/ears = target_mob.get_organ_slot(ORGAN_SLOT_EARS)
+ if(!ears)
+ to_chat(user, span_notice("[target_mob == user ? "You don't have" : target_mob + "has no"] ears to shove [src] into!"))
+ return
+
+ to_chat(user, span_danger("You start shoving [src] into [target_mob == user ? "your" : target_mob + "'s"] ears. Probably a bad idea."))
+ if(!do_after(user, 2.5 SECONDS * (target_mob == user ? 1 : 3), src))
+ return
+
+ user.apply_damage(25, BRUTE, user.get_bodypart(ears.zone), attacking_item = src)
+ to_chat(user, span_notice("As you're shoving them in, the [src] take on a life of their own and brutishly crawl right into [target_mob == user ? "your" : target_mob + "'s"] ears, taking their place entirely while maiming [target_mob == user ? "your" : target_mob.p_their()] [ears.zone]!"))
+ playsound(user, 'sound/effects/magic/demon_consume.ogg', vol = 100, falloff_exponent = 2, vary = TRUE)
+ // bad moodlet
+ user.temporarilyRemoveItemFromInventory(src, TRUE)
+ Insert(user, special = TRUE, movement_flags = DELETE_IF_REPLACED)
+
+/obj/item/organ/ears/babbelfish/on_mob_insert(mob/living/carbon/organ_owner, special, movement_flags)
+ ..()
+ bound_component = organ_owner.AddComponent(
+ /datum/component/anti_magic, \
+ antimagic_flags = MAGIC_RESISTANCE_MIND, \
+ inventory_flags = null, \
+ charges = maxHealth * 0.1, \
+ drain_antimagic = CALLBACK(src, PROC_REF(on_drain_magic)), \
+ expiration = CALLBACK(src, PROC_REF(on_expire)), \
+ )
+
+ if(HAS_MIND_TRAIT(organ_owner, TRAIT_TOWER_OF_BABEL))
+ to_chat(organ_owner, span_noticealien("You don't feel that much different this time. Looks like your brain has attuned to the [src]'s effect."))
+ return
+
+ if(!removal_holder)
+ removal_holder = new(src)
+ removal_holder.copy_languages(organ_owner.get_language_holder(), LANGUAGE_BABEL)
+
+ switch(rand(1, 100))
+ if(1 to 45)
+ // Can understand nothing
+ organ_owner.remove_all_languages(source = LANGUAGE_ALL)
+ //but speak everything
+ organ_owner.grant_all_languages(language_flags = SPOKEN_LANGUAGE, grant_omnitongue = FALSE, source = LANGUAGE_BABEL)
+ to_chat(organ_owner, span_noticealien("You feel like you've been given the first half of a cosmic puzzle!"))
+ if(46 to 90)
+ // Can speak nothing
+ organ_owner.remove_all_languages(source = LANGUAGE_ALL)
+ // but understand everything
+ organ_owner.grant_all_languages(language_flags = UNDERSTOOD_LANGUAGE, grant_omnitongue = FALSE, source = LANGUAGE_BABEL)
+ to_chat(organ_owner, span_noticealien("You feel like you've been given the second half of a cosmic puzzle!"))
+ if(91 to 100)
+ // jackpot!
+ organ_owner.grant_all_languages(language_flags = ALL, grant_omnitongue = TRUE, source = LANGUAGE_BABEL)
+ to_chat(organ_owner, span_noticealien("You feel like you've been given both halves of a cosmic puzzle!"))
+ to_chat(organ_owner, span_boldnicegreen("So that's what they said to you that one time..."))
+
+ if(organ_owner.mind)
+ ADD_TRAIT(organ_owner.mind, TRAIT_TOWER_OF_BABEL, MAGIC_TRAIT) // only one roll per mind
+
+/obj/item/organ/ears/babbelfish/on_mob_remove(mob/living/carbon/organ_owner, special, movement_flags)
+ . = ..()
+
+ // Reset
+ organ_owner.remove_all_languages(source = LANGUAGE_ALL)
+ organ_owner.copy_languages(removal_holder)
+ to_chat(organ_owner, span_notice("You feel significantly more mundane."))
+ QDEL_NULL(removal_holder)
+ QDEL_NULL(bound_component)
+
+/obj/item/organ/ears/babbelfish/proc/on_drain_magic(mob/user)
+ to_chat(user, span_noticealien("Your [src] pop as they protect your mind from psychic phenomena!"))
+ adjustEarDamage(ddeaf = 20)
+
+/obj/item/organ/ears/babbelfish/proc/on_expire(mob/user)
+ to_chat(user, span_noticealien("Your [src] suddenly burst apart!"))
+ apply_organ_damage(maxHealth, maxHealth)
diff --git a/code/modules/fishing/fish_mount.dm b/code/modules/fishing/fish_mount.dm
index d2334910e2a55..4649f2e9125c2 100644
--- a/code/modules/fishing/fish_mount.dm
+++ b/code/modules/fishing/fish_mount.dm
@@ -78,7 +78,7 @@
if(fish_path.fish_id_redirect_path)
fish_path = fish_path.fish_id_redirect_path
var/fluff_name = pick("John Trasen III", "a nameless intern", "Pun Pun", AQUARIUM_COMPANY, "Unknown", "Central Command")
- add_fish(new fish_path(src), from_persistence = TRUE, catcher = fluff_name)
+ add_fish(new fish_path(loc), from_persistence = TRUE, catcher = fluff_name)
mounted_fish.randomize_size_and_weight()
mounted_fish.set_status(FISH_DEAD)
SSpersistence.save_trophy_fish(src)
@@ -108,6 +108,11 @@
return ITEM_INTERACT_SUCCESS
/obj/structure/fish_mount/proc/add_fish(obj/item/fish/fish, from_persistence = FALSE, catcher)
+ if(QDELETED(src)) // don't ever try to add a fish to one of these that's already been deleted - and get rid of the one that was created
+ qdel(fish)
+ return
+ if(QDELETED(fish)) // no adding deleted fishies either
+ return
if(mounted_fish)
mounted_fish.forceMove(loc)
fish.forceMove(src)
diff --git a/code/modules/fishing/fishing_equipment.dm b/code/modules/fishing/fishing_equipment.dm
index 37aea262426bb..4961fdbc042c8 100644
--- a/code/modules/fishing/fishing_equipment.dm
+++ b/code/modules/fishing/fishing_equipment.dm
@@ -104,29 +104,30 @@
SIGNAL_HANDLER
UnregisterSignal(rod, COMSIG_FISHING_ROD_HOOKED_ITEM)
-/obj/item/fishing_line/auto_reel/proc/on_hooked_item(obj/item/fishing_rod/source, atom/target, mob/living/user)
+/obj/item/fishing_line/auto_reel/proc/on_hooked_item(obj/item/fishing_rod/source, atom/movable/target, mob/living/user)
SIGNAL_HANDLER
- if(!ismovable(target))
+
+ if(!istype(target) || target.anchored || target.move_resist >= MOVE_FORCE_STRONG)
return
- var/atom/movable/movable_target = target
var/please_be_gentle = FALSE
var/atom/destination
var/datum/callback/throw_callback
- if(isliving(movable_target) || !isitem(movable_target))
+ if(isliving(target) || !isitem(target))
destination = get_step_towards(user, target)
please_be_gentle = TRUE
else
destination = user
- throw_callback = CALLBACK(src, PROC_REF(clear_hitby_signal), movable_target)
- RegisterSignal(movable_target, COMSIG_MOVABLE_PRE_IMPACT, PROC_REF(catch_it_chucklenut))
+ throw_callback = CALLBACK(src, PROC_REF(clear_hitby_signal), target)
+ RegisterSignal(target, COMSIG_MOVABLE_PRE_IMPACT, PROC_REF(catch_it_chucklenut))
- if(!movable_target.safe_throw_at(destination, source.cast_range, 2, callback = throw_callback, gentle = please_be_gentle))
- UnregisterSignal(movable_target, COMSIG_MOVABLE_PRE_IMPACT)
+ if(!target.safe_throw_at(destination, source.cast_range, 2, callback = throw_callback, gentle = please_be_gentle))
+ UnregisterSignal(target, COMSIG_MOVABLE_PRE_IMPACT)
else
playsound(src, 'sound/items/weapons/batonextend.ogg', 50, TRUE)
/obj/item/fishing_line/auto_reel/proc/catch_it_chucklenut(obj/item/source, atom/hit_atom, datum/thrownthing/throwingdatum)
SIGNAL_HANDLER
+
var/mob/living/user = throwingdatum.initial_target.resolve()
if(QDELETED(user) || hit_atom != user)
return NONE
diff --git a/code/modules/fishing/sources/source_types.dm b/code/modules/fishing/sources/source_types.dm
index af975bb4ccdb4..d23e03ce97675 100644
--- a/code/modules/fishing/sources/source_types.dm
+++ b/code/modules/fishing/sources/source_types.dm
@@ -159,7 +159,6 @@
radial_name = "Chasm"
overlay_state = "portal_chasm"
radial_state = "ground_hole"
- fish_source_flags = FISH_SOURCE_FLAG_EXPLOSIVE_NONE
/datum/fish_source/portal/ocean
fish_table = list(
@@ -327,6 +326,7 @@
/datum/chasm_detritus = 30,
)
fishing_difficulty = FISHING_DEFAULT_DIFFICULTY + 15
+ fish_source_flags = FISH_SOURCE_FLAG_EXPLOSIVE_NONE
/datum/fish_source/chasm/on_start_fishing(obj/item/fishing_rod/rod, mob/fisherman, atom/parent)
. = ..()
@@ -948,10 +948,13 @@
overlay_state = "portal_rift_2" // yeah good luck adaptin the rift sprite to this template. recolored randomizer's the best you're getting
fish_table = list(
FISHING_INFLUENCE = 6,
+ FISHING_RANDOM_ARM = 3,
/obj/item/fish/starfish/chrystarfish = 7,
/obj/item/fish/dolphish = 7,
/obj/item/fish/flumpulus = 7,
/obj/item/fish/gullion = 7,
+ /obj/item/fish/mossglob = 3,
+ /obj/item/fish/babbelfish = 1,
/mob/living/basic/heretic_summon/fire_shark/wild = 3,
/obj/item/eldritch_potion/crucible_soul = 1,
/obj/item/eldritch_potion/duskndawn = 1,
@@ -959,6 +962,8 @@
/obj/item/reagent_containers/cup/beaker/eldritch = 2,
)
fish_counts = list(
+ /obj/item/fish/mossglob = 3,
+ /obj/item/fish/babbelfish = 1,
/mob/living/basic/heretic_summon/fire_shark/wild = 3,
/obj/item/eldritch_potion/crucible_soul = 1,
/obj/item/eldritch_potion/duskndawn = 1,
@@ -966,7 +971,9 @@
/obj/item/reagent_containers/cup/beaker/eldritch = 2,
)
fish_count_regen = list(
- /mob/living/basic/heretic_summon/fire_shark/wild = 3 MINUTES,
+ /obj/item/fish/mossglob = 3 MINUTES,
+ /obj/item/fish/babbelfish = 5 MINUTES,
+ /mob/living/basic/heretic_summon/fire_shark/wild = 6 MINUTES,
/obj/item/eldritch_potion/crucible_soul = 5 MINUTES,
/obj/item/eldritch_potion/duskndawn = 5 MINUTES,
/obj/item/eldritch_potion/wounded = 5 MINUTES,
@@ -975,16 +982,86 @@
fishing_difficulty = FISHING_DEFAULT_DIFFICULTY + 35
fish_source_flags = FISH_SOURCE_FLAG_EXPLOSIVE_NONE
+/**
+ * You can fish up random arms, but you can also fish up arms (or heads, from TK) that were eaten at some point by a rift.
+ * No need to check for what the location is, just get its limbs from its contents. It should always be a visible heretic rift. Should.
+ */
+/datum/fish_source/dimensional_rift/get_fish_table(atom/location, from_explosion = FALSE)
+ . = ..()
+ if(istype(location, /obj/machinery/fishing_portal_generator))
+ var/obj/machinery/fishing_portal_generator/portal = location
+ location = portal.current_linked_atom
+
+ for(var/obj/item/eaten_thing in location.get_all_contents())
+ .[eaten_thing] = 6
+
/datum/fish_source/dimensional_rift/on_challenge_completed(mob/user, datum/fishing_challenge/challenge, success)
. = ..()
+ if(!success)
+ if(IS_HERETIC(user))
+ return
+ if(!user.get_active_hand())
+ return influence_fished(user, challenge)
+ on_epic_fail(user, challenge, success)
+ return
+
+
+ if(challenge.reward_path == FISHING_INFLUENCE)
+ influence_fished(user, challenge)
+ return
+
+ return
+
+/**
+ * Override for influences and arms.
+ */
+/datum/fish_source/dimensional_rift/spawn_reward(reward_path, atom/spawn_location, atom/fishing_spot)
+ switch(reward_path)
+ if(FISHING_INFLUENCE)
+ return
+ if(FISHING_RANDOM_ARM)
+ return arm_fished(spawn_location)
+ return ..()
+
+/**
+ * This happens when a non-heretic fails the minigame. Their arm is ripped straight off and thrown into the rift.
+ */
+/datum/fish_source/dimensional_rift/proc/on_epic_fail(mob/user, datum/fishing_challenge/challenge, success)
+ challenge.location.visible_message(span_danger("[challenge.location]'s tendrils lash out and pull on [user]'s [user.get_active_hand()], ripping it clean off and throwing it towards itself!"))
+ var/obj/item/bodypart/random_arm = user.get_active_hand()
+ random_arm.dismember(BRUTE, FALSE)
+ random_arm.forceMove(user.drop_location())
+ random_arm.throw_at(challenge.location, 7, 1, null, TRUE)
+ // Abstract items shouldn't be thrown in!
+ if(!(challenge.used_rod.item_flags & ABSTRACT))
+ challenge.used_rod.forceMove(user.drop_location())
+ challenge.used_rod.throw_at(challenge.location, 7, 1, null, TRUE)
+ addtimer(CALLBACK(src, PROC_REF(check_item_location), challenge.location, random_arm, challenge.used_rod), 1 SECONDS)
+
+/datum/fish_source/dimensional_rift/proc/check_item_location(atom/location, obj/item/bodypart/random_arm, obj/item/used_rod)
+ for(var/obj/item/thingy in get_turf(location))
+ // If it's not in the list and it's not what we know as the used rod, skip.
+ // This lets fishing gloves be dragged in as well. I mean honestly if you try fishing in here with those you should just Fucking Die but that's for later.
+ if(!is_type_in_list(thingy, list(/obj/item/bodypart, /obj/item/fishing_rod)) && (thingy != used_rod))
+ continue
+ thingy.forceMove(location)
+ location.visible_message(span_danger("Tendrils lash out from [location] and greedily drag [thingy] inwards. You're probably never seeing [thingy] again."))
+
+/datum/fish_source/dimensional_rift/proc/arm_fished(atom/spawn_location)
+ var/obj/item/bodypart/arm/random_arm = pick(subtypesof(/obj/item/bodypart/arm))
+ random_arm = new random_arm(spawn_location)
+ spawn_location.visible_message(span_notice("A [random_arm] is snatched up from beneath the eldritch depths of [spawn_location]!"))
+ return random_arm
+
+/datum/fish_source/dimensional_rift/proc/influence_fished(mob/user, datum/fishing_challenge/challenge)
if(challenge.reward_path != FISHING_INFLUENCE)
return
var/mob/living/carbon/human/human_user
if(ishuman(user))
human_user = user
- user.visible_message(span_danger("[user] reels [user.p_their()] [challenge.used_rod] in, catching.. nothing?"), span_notice("You catch.. a glimpse into the workings of the Mansus itself!"))
+ user.visible_message(span_danger("[user] reels [user.p_their()] [challenge.used_rod] in, catching a glimpse into the world beyond!"), span_notice("You catch.. a glimpse into the workings of the Mansus itself!"))
// Heretics that fish in the rift gain knowledge.
if(IS_HERETIC(user))
human_user?.add_mood_event("rift fishing", /datum/mood_event/rift_fishing)
@@ -1007,11 +1084,6 @@
// Non-heretics instead go crazy
human_user?.adjustOrganLoss(ORGAN_SLOT_BRAIN, 10, 190)
human_user?.add_mood_event("gates_of_mansus", /datum/mood_event/gates_of_mansus)
+ human_user?.do_jitter_animation(50)
// Hand fires at them from the location
fire_curse_hand(user, get_turf(challenge.location))
-
-// Handled above
-/datum/fish_source/dimensional_rift/spawn_reward(reward_path, atom/spawn_location, atom/fishing_spot, obj/item/fishing_rod/used_rod)
- if(reward_path != FISHING_INFLUENCE)
- return ..()
- return
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm
index 522f6e9f695f8..130fcb57b696a 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm
@@ -418,6 +418,7 @@
)
result = /obj/item/food/donkpocket/deluxe
category = CAT_PASTRY
+ crafting_flags = parent_type::crafting_flags | CRAFT_MUST_BE_LEARNED
/datum/crafting_recipe/food/donkpocket/deluxe/nocarb
time = 15
diff --git a/code/modules/hallucination/fake_chat.dm b/code/modules/hallucination/fake_chat.dm
index 049a337c1101c..963e718eed865 100644
--- a/code/modules/hallucination/fake_chat.dm
+++ b/code/modules/hallucination/fake_chat.dm
@@ -50,7 +50,7 @@
chosen = pick(list("Help!",
"[pick_list_replacements(HALLUCINATION_FILE, "people")] is [pick_list_replacements(HALLUCINATION_FILE, "accusations")]!",
"[pick_list_replacements(HALLUCINATION_FILE, "threat")] in [pick_list_replacements(HALLUCINATION_FILE, "location")][prob(50)?"!":"!!"]",
- "[pick("Where's [hallucinator.first_name()]?", "Set [hallucinator.first_name()] to arrest!")]",
+ "[pick("Where's [first_name(hallucinator.name)]?", "Set [first_name(hallucinator.name)] to arrest!")]",
"[pick("C","Ai, c","Someone c","Rec")]all the shuttle!",
"AI [pick("rogue", "is dead")]!!",
"Borgs rogue!",
@@ -58,7 +58,7 @@
else
chosen = pick(list("[pick_list_replacements(HALLUCINATION_FILE, "suspicion")]",
"[pick_list_replacements(HALLUCINATION_FILE, "conversation")]",
- "[pick_list_replacements(HALLUCINATION_FILE, "greetings")][hallucinator.first_name()]!",
+ "[pick_list_replacements(HALLUCINATION_FILE, "greetings")][first_name(hallucinator.name)]!",
"[pick_list_replacements(HALLUCINATION_FILE, "getout")]",
"[pick_list_replacements(HALLUCINATION_FILE, "weird")]",
"[pick_list_replacements(HALLUCINATION_FILE, "didyouhearthat")]",
@@ -71,7 +71,7 @@
chosen = capitalize(chosen)
- chosen = replacetext(chosen, "%TARGETNAME%", hallucinator.first_name())
+ chosen = replacetext(chosen, "%TARGETNAME%", first_name(hallucinator.name))
// Log the message
feedback_details += "Type: [is_radio ? "Radio" : "Talk"], Source: [speaker.real_name], Message: [chosen]"
diff --git a/code/modules/hallucination/fake_death.dm b/code/modules/hallucination/fake_death.dm
index 126e9dd3a2b48..9583418f23220 100644
--- a/code/modules/hallucination/fake_death.dm
+++ b/code/modules/hallucination/fake_death.dm
@@ -59,7 +59,7 @@
"FUCK",
"git gud",
"god damn it",
- "hey [hallucinator.first_name()]",
+ "hey [first_name(hallucinator.name)]",
"i[prob(50) ? " fucking" : ""] hate [pick(things_to_hate)]",
"is the AI rogue?",
"rip",
diff --git a/code/modules/hallucination/nearby_fake_item.dm b/code/modules/hallucination/nearby_fake_item.dm
index 10d08ee47c96f..1896d28d34042 100644
--- a/code/modules/hallucination/nearby_fake_item.dm
+++ b/code/modules/hallucination/nearby_fake_item.dm
@@ -89,7 +89,7 @@
/datum/hallucination/nearby_fake_item/baton
left_hand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi'
right_hand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
- image_icon_state = "baton"
+ image_icon_state = "stunbaton"
/datum/hallucination/nearby_fake_item/baton/generate_fake_image(mob/living/carbon/human/holder, file)
hallucinator.playsound_local(get_turf(holder), SFX_SPARKS, 75, TRUE, -1)
diff --git a/code/modules/jobs/job_types/captain.dm b/code/modules/jobs/job_types/captain.dm
index 35155a7c7b70d..fff1c5ed170b4 100644
--- a/code/modules/jobs/job_types/captain.dm
+++ b/code/modules/jobs/job_types/captain.dm
@@ -67,7 +67,7 @@
uniform = /obj/item/clothing/under/rank/captain
suit = /obj/item/clothing/suit/armor/vest/capcarapace
backpack_contents = list(
- /obj/item/melee/baton/telescopic = 1,
+ /obj/item/melee/baton/telescopic/gold = 1,
/obj/item/station_charter = 1,
)
belt = /obj/item/modular_computer/pda/heads/captain
diff --git a/code/modules/jobs/job_types/chief_engineer.dm b/code/modules/jobs/job_types/chief_engineer.dm
index f85c2c54973b9..3a9a9fbc397b6 100644
--- a/code/modules/jobs/job_types/chief_engineer.dm
+++ b/code/modules/jobs/job_types/chief_engineer.dm
@@ -67,7 +67,7 @@
id_trim = /datum/id_trim/job/chief_engineer
uniform = /obj/item/clothing/under/rank/engineering/chief_engineer
backpack_contents = list(
- /obj/item/melee/baton/telescopic = 1,
+ /obj/item/melee/baton/telescopic/silver = 1,
/obj/item/construction/rcd/ce = 1,
)
belt = /obj/item/storage/belt/utility/chief/full
diff --git a/code/modules/jobs/job_types/chief_medical_officer.dm b/code/modules/jobs/job_types/chief_medical_officer.dm
index c9f126bb750bc..13a9eb97515f1 100644
--- a/code/modules/jobs/job_types/chief_medical_officer.dm
+++ b/code/modules/jobs/job_types/chief_medical_officer.dm
@@ -62,7 +62,7 @@
suit = /obj/item/clothing/suit/toggle/labcoat/cmo
suit_store = /obj/item/flashlight/pen/paramedic
backpack_contents = list(
- /obj/item/melee/baton/telescopic = 1,
+ /obj/item/melee/baton/telescopic/silver = 1,
)
belt = /obj/item/modular_computer/pda/heads/cmo
ears = /obj/item/radio/headset/heads/cmo
diff --git a/code/modules/jobs/job_types/head_of_personnel.dm b/code/modules/jobs/job_types/head_of_personnel.dm
index 1b8480f0d0b57..ed35cbf365f56 100644
--- a/code/modules/jobs/job_types/head_of_personnel.dm
+++ b/code/modules/jobs/job_types/head_of_personnel.dm
@@ -59,7 +59,7 @@
id_trim = /datum/id_trim/job/head_of_personnel
uniform = /obj/item/clothing/under/rank/civilian/head_of_personnel
backpack_contents = list(
- /obj/item/melee/baton/telescopic = 1,
+ /obj/item/melee/baton/telescopic/silver = 1,
)
belt = /obj/item/modular_computer/pda/heads/hop
ears = /obj/item/radio/headset/heads/hop
diff --git a/code/modules/jobs/job_types/head_of_security.dm b/code/modules/jobs/job_types/head_of_security.dm
index b9560708114be..431e0aaaf6b44 100644
--- a/code/modules/jobs/job_types/head_of_security.dm
+++ b/code/modules/jobs/job_types/head_of_security.dm
@@ -57,6 +57,7 @@
suit_store = /obj/item/gun/energy/e_gun
backpack_contents = list(
/obj/item/evidencebag = 1,
+ /obj/item/melee/baton/security/loaded/hos = 1,
)
belt = /obj/item/modular_computer/pda/heads/hos
ears = /obj/item/radio/headset/heads/hos/alt
diff --git a/code/modules/jobs/job_types/quartermaster.dm b/code/modules/jobs/job_types/quartermaster.dm
index 32053daa5d8c8..251032662bf53 100644
--- a/code/modules/jobs/job_types/quartermaster.dm
+++ b/code/modules/jobs/job_types/quartermaster.dm
@@ -42,7 +42,7 @@
name = "Quartermaster"
jobtype = /datum/job/quartermaster
backpack_contents = list(
- /obj/item/melee/baton/telescopic = 1,
+ /obj/item/melee/baton/telescopic/bronze = 1,
)
id_trim = /datum/id_trim/job/quartermaster
id = /obj/item/card/id/advanced/silver
diff --git a/code/modules/jobs/job_types/research_director.dm b/code/modules/jobs/job_types/research_director.dm
index 420138a6b9fba..cfd5044c267b0 100644
--- a/code/modules/jobs/job_types/research_director.dm
+++ b/code/modules/jobs/job_types/research_director.dm
@@ -62,7 +62,7 @@
uniform = /obj/item/clothing/under/rank/rnd/research_director/turtleneck
suit = /obj/item/clothing/suit/toggle/labcoat/research_director
backpack_contents = list(
- /obj/item/melee/baton/telescopic = 1,
+ /obj/item/melee/baton/telescopic/silver = 1,
)
belt = /obj/item/modular_computer/pda/heads/rd
head = /obj/item/clothing/head/beret/science/rd
diff --git a/code/modules/library/skill_learning/job_skillchips/clown.dm b/code/modules/library/skill_learning/job_skillchips/clown.dm
index 3cd88ff70963d..f8836b41dcad3 100644
--- a/code/modules/library/skill_learning/job_skillchips/clown.dm
+++ b/code/modules/library/skill_learning/job_skillchips/clown.dm
@@ -3,7 +3,7 @@
desc = "This biochip contain several terabytes of uncannily religious, Honkmother praising guides on how to reshape balloons into silly animals."
auto_traits = list(TRAIT_BALLOON_SUTRA)
skill_name = "Balloon Sutra"
- skill_description = "Learn the the ancient Honkmotherian arts of balloon-sutra."
+ skill_description = "Learn the ancient Honkmotherian arts of balloon-sutra."
skill_icon = "face-grin-tears"
activate_message = span_notice("Blessed wisdom of Honkmother enwraps you, and with it, governship upon form of balloonkind.")
deactivate_message = span_notice("'Remember, then, that true clownery requires freedom and willingness to bend, like ones of a floating balloon.'... Whatever that meant?")
diff --git a/code/modules/mapping/mapping_helpers.dm b/code/modules/mapping/mapping_helpers.dm
index c122b8abc1cd7..da7d288d161fe 100644
--- a/code/modules/mapping/mapping_helpers.dm
+++ b/code/modules/mapping/mapping_helpers.dm
@@ -102,7 +102,7 @@
/obj/effect/baseturf_helper/reinforced_plating/ceiling/replace_baseturf(turf/thing)
var/turf/ceiling = get_step_multiz(thing, UP)
if(isnull(ceiling))
- CRASH("baseturf helper is attempting to modify the Z level above but there is no Z level above above it.")
+ CRASH("baseturf helper is attempting to modify the Z level above but there is no Z level above it.")
if(isspaceturf(ceiling) || istype(ceiling, /turf/open/openspace))
return
return ..(ceiling)
@@ -1373,6 +1373,13 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_atoms_ontop)
/obj/effect/mapping_helpers/mob_buckler/Initialize(mapload)
. = ..()
+ if(!mapload)
+ log_mapping("[src] spawned outside of mapload!")
+ return INITIALIZE_HINT_QDEL
+
+ return INITIALIZE_HINT_LATELOAD
+
+/obj/effect/mapping_helpers/mob_buckler/LateInitialize()
var/atom/movable/buckle_to
var/list/mobs = list()
for(var/atom/movable/possible_buckle as anything in loc)
@@ -1385,12 +1392,13 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_atoms_ontop)
if(isnull(buckle_to))
log_mapping("[type] at [x] [y] [z] did not find anything to buckle to")
- return INITIALIZE_HINT_QDEL
+ qdel(src)
+ return
for(var/mob/living/mob as anything in mobs)
buckle_to.buckle_mob(mob, force = force_buckle)
- return INITIALIZE_HINT_QDEL
+ qdel(src)
///Basic mob flag helpers for things like deleting on death.
/obj/effect/mapping_helpers/basic_mob_flags
diff --git a/code/modules/mining/abandoned_crates.dm b/code/modules/mining/abandoned_crates.dm
index 10b2fbe71d062..7fa66c8a65ce7 100644
--- a/code/modules/mining/abandoned_crates.dm
+++ b/code/modules/mining/abandoned_crates.dm
@@ -231,7 +231,7 @@
if(93)
new /obj/item/dnainjector/xraymut(src)
if(94)
- new /mob/living/simple_animal/hostile/mimic/crate(src)
+ new /mob/living/basic/mimic/crate(src)
qdel_on_open = TRUE
if(95)
new /obj/item/toy/plush/nukeplushie(src)
diff --git a/code/modules/mining/equipment/kinetic_crusher/trophies_megafauna.dm b/code/modules/mining/equipment/kinetic_crusher/trophies_megafauna.dm
index 119099f587b8f..b184319688282 100644
--- a/code/modules/mining/equipment/kinetic_crusher/trophies_megafauna.dm
+++ b/code/modules/mining/equipment/kinetic_crusher/trophies_megafauna.dm
@@ -231,3 +231,23 @@
var/mob/living/basic/legion_brood/minion = new (LivingUser.loc)
minion.assign_creator(LivingUser)
next_use_time = world.time + 4 SECONDS
+
+//The Thing
+/obj/item/crusher_trophy/flesh_glob
+ name = "glob of shifting flesh"
+ desc = "A glob of shifting flesh. Sealed shut permanently. Suitable as a trophy for a kinetic crusher."
+ icon_state = "glob"
+ denied_type = /obj/item/crusher_trophy/flesh_glob
+ bonus_value = 20
+ /// the order in which we heal damage
+ var/static/list/damage_heal_order = list(BRUTE, BURN, OXY, TOX)
+
+/obj/item/crusher_trophy/flesh_glob/effect_desc()
+ return "melee hits heal you for [bonus_value * 0.2], and for [bonus_value * 0.5] on mark detonation"
+
+/obj/item/crusher_trophy/flesh_glob/on_melee_hit(mob/living/target, mob/living/user)
+ user.heal_ordered_damage(bonus_value * 0.2, damage_heal_order)
+
+/obj/item/crusher_trophy/flesh_glob/on_mark_detonation(mob/living/target, mob/living/user)
+ . = ..()
+ user.heal_ordered_damage(bonus_value * 0.5, damage_heal_order)
diff --git a/code/modules/mining/lavaland/megafauna_loot.dm b/code/modules/mining/lavaland/megafauna_loot.dm
index cfe087a8ab38a..5ed8404459fbf 100644
--- a/code/modules/mining/lavaland/megafauna_loot.dm
+++ b/code/modules/mining/lavaland/megafauna_loot.dm
@@ -1115,3 +1115,170 @@
playsound(target, 'sound/effects/magic/lightningbolt.ogg', 100, TRUE)
target.visible_message(span_danger("A thunderbolt strikes [target]!"))
explosion(target, light_impact_range = (boosted ? 1 : 0), flame_range = (boosted ? 2 : 1), silent = TRUE)
+
+
+/datum/action/innate/brain_undeployment
+ name = "Disconnect from shell"
+ desc = "Stop controlling your shell and resume normal core operations."
+ button_icon = 'icons/mob/actions/actions_AI.dmi'
+ button_icon_state = "ai_core"
+
+/datum/action/innate/brain_undeployment/Trigger(trigger_flags)
+ if(!..())
+ return FALSE
+ var/obj/item/organ/brain/cybernetic/ai/shell_to_disconnect = owner.get_organ_by_type(/obj/item/organ/brain/cybernetic/ai)
+
+ shell_to_disconnect.undeploy()
+ return TRUE
+
+/obj/item/organ/brain/cybernetic/ai
+ name = "AI-uplink brain"
+ desc = "Can be inserted into a body with NO ORGANIC INTERNAL ORGANS (robotic organs only) to allow AIs to control it. Comes with its own health sensors beacon. MUST be a humanoid or bad things happen to the consciousness."
+ can_smoothen_out = FALSE
+ /// if connected, our AI
+ var/mob/living/silicon/ai/mainframe
+ /// action for undeployment
+ var/datum/action/innate/brain_undeployment/undeployment_action = new
+
+/obj/item/organ/brain/cybernetic/ai/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/noticable_organ, "%PRONOUN_Their eyes move with machine precision, their expression completely blank.")
+
+/obj/item/organ/brain/cybernetic/ai/Destroy()
+ . = ..()
+ undeploy()
+ mainframe = null
+ QDEL_NULL(undeployment_action)
+
+/obj/item/organ/brain/cybernetic/ai/on_mob_insert(mob/living/carbon/brain_owner, special, movement_flags)
+ . = ..()
+ brain_owner.add_traits(list(HUMAN_SENSORS_VISIBLE_WITHOUT_SUIT, TRAIT_NO_MINDSWAP), ORGAN_TRAIT)
+ update_med_hud_status(brain_owner)
+ RegisterSignal(brain_owner, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(update_med_hud_status))
+ RegisterSignal(brain_owner, COMSIG_CLICK, PROC_REF(owner_clicked))
+ RegisterSignal(brain_owner, COMSIG_MOB_GET_STATUS_TAB_ITEMS, PROC_REF(get_status_tab_item))
+ RegisterSignal(brain_owner, COMSIG_MOB_MIND_BEFORE_MIDROUND_ROLL, PROC_REF(cancel_rolls))
+ RegisterSignals(brain_owner, list(COMSIG_QDELETING, COMSIG_LIVING_PRE_WABBAJACKED), PROC_REF(undeploy))
+ RegisterSignal(brain_owner, COMSIG_CARBON_GAIN_ORGAN, PROC_REF(on_organ_gain))
+
+/obj/item/organ/brain/cybernetic/ai/on_mob_remove(mob/living/carbon/organ_owner, special, movement_flags)
+ undeploy()
+ . = ..()
+ organ_owner.remove_traits(list(HUMAN_SENSORS_VISIBLE_WITHOUT_SUIT, TRAIT_NO_MINDSWAP), ORGAN_TRAIT)
+ UnregisterSignal(organ_owner, list(COMSIG_LIVING_HEALTH_UPDATE, COMSIG_CLICK, COMSIG_MOB_GET_STATUS_TAB_ITEMS, COMSIG_MOB_MIND_BEFORE_MIDROUND_ROLL, COMSIG_QDELETING, COMSIG_LIVING_PRE_WABBAJACKED))
+
+/obj/item/organ/brain/cybernetic/ai/proc/cancel_rolls(mob/living/source, datum/mind/mind, datum/antagonist/antagonist)
+ SIGNAL_HANDLER
+ if(ispath(antagonist, /datum/antagonist/malf_ai))
+ return
+ return CANCEL_ROLL
+
+/obj/item/organ/brain/cybernetic/ai/proc/get_status_tab_item(mob/living/source, list/items)
+ SIGNAL_HANDLER
+ if(!mainframe)
+ return
+ items += mainframe.get_status_tab_items()
+
+/obj/item/organ/brain/cybernetic/ai/proc/update_med_hud_status(mob/living/mob_parent)
+ SIGNAL_HANDLER
+ var/image/holder = mob_parent.hud_list?[STATUS_HUD]
+ if(isnull(holder))
+ return
+ var/icon/size_check = icon(mob_parent.icon, mob_parent.icon_state, mob_parent.dir)
+ holder.pixel_y = size_check.Height() - ICON_SIZE_Y
+ if(mob_parent.stat == DEAD || HAS_TRAIT(mob_parent, TRAIT_FAKEDEATH) || isnull(mainframe))
+ holder.icon_state = "huddead2"
+ holder.pixel_x = -8 // new icon states? nuh uh
+ else
+ holder.icon_state = "hudtrackingai"
+ holder.pixel_x = -16
+
+// no thoughts only wifi
+/obj/item/organ/brain/cybernetic/ai/can_gain_trauma(datum/brain_trauma/trauma, resilience, natural_gain = FALSE)
+ return FALSE
+
+/obj/item/organ/brain/cybernetic/ai/proc/owner_clicked(datum/source, atom/location, control, params, mob/user)
+ SIGNAL_HANDLER
+ if(!isAI(user))
+ return
+ var/list/lines = list()
+ lines += span_bold("[owner]")
+ lines += "Target is currently [!HAS_TRAIT(owner, TRAIT_INCAPACITATED) ? "functional" : "incapacitated"]"
+ lines += "Estimated organic/inorganic integrity: [owner.health]"
+ if(is_sufficiently_augmented())
+ lines += "[span_boldnotice("Take control?")] "
+ else
+ lines += span_warning("Organic organs detected. Robotic organs only, cannot take over.")
+
+ to_chat(user, boxed_message(jointext(lines, "\n")), type = MESSAGE_TYPE_INFO)
+
+/obj/item/organ/brain/cybernetic/ai/Topic(href, href_list)
+ ..()
+ if(!href_list["ai_take_control"] || !is_sufficiently_augmented())
+ return
+ var/mob/living/silicon/ai/AI = locate(href_list["ai_take_control"]) in GLOB.silicon_mobs
+ if(isnull(AI))
+ return
+ if(AI.controlled_equipment)
+ to_chat(AI, span_warning("You are already loaded into an onboard computer!"))
+ return
+ if(!GLOB.cameranet.checkCameraVis(owner))
+ to_chat(AI, span_warning("Target is no longer near active cameras."))
+ return
+ if(!isturf(AI.loc))
+ to_chat(AI, span_warning("You aren't in your core!"))
+ return
+
+ RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(undeploy))
+ AI.deployed_shell = owner
+ deploy_init(AI)
+ ADD_TRAIT(AI.mind, TRAIT_UNCONVERTABLE, ORGAN_TRAIT)
+ AI.mind.transfer_to(owner)
+
+/obj/item/organ/brain/cybernetic/ai/proc/deploy_init(mob/living/silicon/ai/AI)
+ //todo camera maybe
+ mainframe = AI
+ RegisterSignal(AI, COMSIG_QDELETING, PROC_REF(ai_deleted))
+ undeployment_action.Grant(owner)
+ update_med_hud_status(owner)
+ to_chat(owner, span_boldbig("You are still considered a silicon/cyborg/AI. Follow your laws."))
+
+/obj/item/organ/brain/cybernetic/ai/proc/undeploy(datum/source)
+ SIGNAL_HANDLER
+ if(!owner?.mind || !mainframe)
+ return
+ UnregisterSignal(src, list(COMSIG_LIVING_DEATH, COMSIG_QDELETING))
+ mainframe.redeploy_action.Remove(mainframe)
+ mainframe.redeploy_action.last_used_shell = null
+ owner.mind.transfer_to(mainframe)
+ mainframe.deployed_shell = null
+ undeployment_action.Remove(owner)
+ if(mainframe.laws)
+ mainframe.laws.show_laws(mainframe)
+ if(mainframe.eyeobj)
+ mainframe.eyeobj.setLoc(loc)
+ REMOVE_TRAIT(mainframe.mind, TRAIT_UNCONVERTABLE, ORGAN_TRAIT)
+ mainframe = null
+ update_med_hud_status(owner)
+
+/obj/item/organ/brain/cybernetic/ai/proc/is_sufficiently_augmented()
+ var/mob/living/carbon/carb_owner = owner
+ . = TRUE
+ if(!istype(carb_owner))
+ return
+ for(var/obj/item/organ/organ as anything in carb_owner.organs)
+ if(organ.organ_flags & ORGAN_EXTERNAL)
+ continue
+ if(!IS_ROBOTIC_ORGAN(organ) && !istype(organ, /obj/item/organ/tongue)) //tongues are not in the exosuit fab and nobody is going to bother to find them so
+ return FALSE
+
+/obj/item/organ/brain/cybernetic/ai/proc/on_organ_gain(datum/source, obj/item/organ/new_organ, special)
+ SIGNAL_HANDLER
+ if(!is_sufficiently_augmented())
+ to_chat(owner, span_danger("Connection failure. Organics detected."))
+ undeploy()
+
+/obj/item/organ/brain/cybernetic/ai/proc/ai_deleted(datum/source)
+ SIGNAL_HANDLER
+ to_chat(owner, span_danger("Your core has been rendered inoperable..."))
+ undeploy()
diff --git a/code/modules/mob/living/basic/boss/boss.dm b/code/modules/mob/living/basic/boss/boss.dm
new file mode 100644
index 0000000000000..57b28848e48d0
--- /dev/null
+++ b/code/modules/mob/living/basic/boss/boss.dm
@@ -0,0 +1,140 @@
+//not quite simple animal megafauna but close enough
+// port actual megafauna stuff once it gets used for lavaland megafauna
+//im using it for stuff both of them get
+/mob/living/basic/boss
+ combat_mode = TRUE
+ sentience_type = SENTIENCE_BOSS
+ mob_biotypes = MOB_ORGANIC|MOB_SPECIAL
+ faction = list(FACTION_MINING, FACTION_BOSS)
+ damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0)
+ obj_damage = 400
+ unsuitable_cold_damage = 0
+ unsuitable_heat_damage = 0
+ unsuitable_atmos_damage = 0
+ move_force = MOVE_FORCE_OVERPOWERING
+ move_resist = MOVE_FORCE_OVERPOWERING
+ pull_force = MOVE_FORCE_OVERPOWERING
+ mob_size = MOB_SIZE_HUGE
+ layer = LARGE_MOB_LAYER
+ flags_1 = PREVENT_CONTENTS_EXPLOSION_1
+ /// List of loot if not killed by crusher.
+ var/list/loot
+ /// List of loot if killed by crusher.
+ var/list/crusher_loot
+ /// Achievement given to surrounding players when the megafauna is killed
+ var/achievement_type
+ /// Crusher achievement given to players when megafauna is killed
+ var/crusher_achievement_type
+ /// Score given to players when megafauna is killed
+ var/score_achievement_type
+ /// If the megafauna was actually killed (not just dying, then transforming into another type)
+ var/elimination = FALSE
+ /// Name for the GPS signal of the megafauna
+ var/gps_name = null
+ /// If this is a megafauna that is real (has achievements, gps signal)
+ var/true_spawn = TRUE
+
+/mob/living/basic/boss/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/wall_tearer, tear_time = 1 SECONDS)
+ if(gps_name && true_spawn)
+ AddComponent(/datum/component/gps, gps_name)
+ ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT)
+ add_traits(list(TRAIT_NO_TELEPORT, TRAIT_MARTIAL_ARTS_IMMUNE, TRAIT_LAVA_IMMUNE,TRAIT_ASHSTORM_IMMUNE, TRAIT_NO_FLOATING_ANIM), MEGAFAUNA_TRAIT)
+ AddComponent(/datum/component/seethrough_mob)
+ AddElement(/datum/element/simple_flying)
+
+/mob/living/basic/boss/gib()
+ if(health > 0)
+ return
+ return ..()
+
+/mob/living/basic/boss/dust(just_ash, drop_items, force)
+ if(!force && health > 0)
+ return
+ loot.Cut()
+ crusher_loot.Cut()
+ return ..()
+
+/mob/living/basic/boss/death(gibbed, list/force_grant)
+ if(gibbed) // in case they've been force dusted
+ return ..()
+
+ if(health > 0) // prevents instakills
+ return
+ var/datum/status_effect/crusher_damage/crusher_dmg = has_status_effect(/datum/status_effect/crusher_damage)
+ ///Whether we killed the megafauna with primarily crusher damage or not
+ var/crusher_kill = (crusher_dmg && (crusher_dmg.total_damage >= floor(maxHealth * 0.6)))
+ if(true_spawn && !(flags_1 & ADMIN_SPAWNED_1))
+ var/tab = "megafauna_kills"
+ if(crusher_kill)
+ tab = "megafauna_kills_crusher"
+ if(!elimination) //used so the achievment only occurs for the last legion to die.
+ grant_achievement(achievement_type, score_achievement_type, crusher_kill, force_grant)
+ SSblackbox.record_feedback("tally", tab, 1, "[initial(name)]")
+
+ for(var/path in crusher_kill ? crusher_loot : loot) // using this instead of deathdrops and crusher_loot because we calculate differently and removing the element is ass
+ new path(drop_location())
+
+ return ..()
+
+/// Grants medals and achievements to surrounding players
+/mob/living/basic/boss/proc/grant_achievement(medaltype, scoretype, crusher_kill, list/grant_achievement = list())
+ if(!achievement_type || (flags_1 & ADMIN_SPAWNED_1) || !SSachievements.achievements_enabled) //Don't award medals if the medal type isn't set
+ return FALSE
+ if(!grant_achievement.len)
+ for(var/mob/living/victor in view(7,src))
+ grant_achievement += victor
+ for(var/mob/living/victor in grant_achievement)
+ if(victor.stat || !victor.client)
+ continue
+ victor.add_mob_memory(/datum/memory/megafauna_slayer, antagonist = src)
+ victor.client.give_award(/datum/award/achievement/boss/boss_killer, victor)
+ victor.client.give_award(achievement_type, victor)
+ if(crusher_kill && istype(victor.get_active_held_item(), /obj/item/kinetic_crusher))
+ victor.client.give_award(crusher_achievement_type, victor)
+ victor.client.give_award(/datum/award/score/boss_score, victor) //Score progression for bosses killed in general
+ victor.client.give_award(score_achievement_type, victor) //Score progression for specific boss killed
+ return TRUE
+
+/mob/living/basic/boss/early_melee_attack(mob/living/target, list/modifiers, ignore_cooldown)
+ . = ..()
+ if(!. || !istype(target))
+ return
+ if(target.stat == DEAD || (target.health <= HEALTH_THRESHOLD_DEAD && HAS_TRAIT(target, TRAIT_NODEATH)))
+ devour(target)
+
+/// Devours a target and restores health to the megafauna
+/mob/living/basic/boss/proc/devour(mob/living/victim)
+ if(isnull(victim) || victim.has_status_effect(/datum/status_effect/gutted))
+ return FALSE
+ celebrate_kill(victim)
+ if(!is_station_level(z) || client) //NPC monsters won't heal while on station
+ heal_overall_damage(victim.maxHealth * 0.5)
+ victim.investigate_log("has been devoured by [src].", INVESTIGATE_DEATHS)
+ if(iscarbon(victim))
+ qdel(victim.get_organ_slot(ORGAN_SLOT_LUNGS))
+ qdel(victim.get_organ_slot(ORGAN_SLOT_HEART))
+ qdel(victim.get_organ_slot(ORGAN_SLOT_LIVER))
+ victim.adjustBruteLoss(500)
+ victim.death() //make sure they die
+ victim.apply_status_effect(/datum/status_effect/gutted)
+ return TRUE
+
+/mob/living/basic/boss/proc/celebrate_kill(mob/living/poor_sap)
+ visible_message(
+ span_danger("[src] disembowels [poor_sap]!"),
+ span_userdanger("You feast on [poor_sap]'s organs, restoring your health!"))
+
+/mob/living/basic/boss/ex_act(severity, target)
+ switch (severity)
+ if (EXPLODE_DEVASTATE)
+ adjustBruteLoss(250)
+
+ if (EXPLODE_HEAVY)
+ adjustBruteLoss(100)
+
+ if (EXPLODE_LIGHT)
+ adjustBruteLoss(50)
+
+ return TRUE
diff --git a/code/modules/mob/living/basic/boss/thing/thing.dm b/code/modules/mob/living/basic/boss/thing/thing.dm
new file mode 100644
index 0000000000000..7c0ac5b012337
--- /dev/null
+++ b/code/modules/mob/living/basic/boss/thing/thing.dm
@@ -0,0 +1,269 @@
+#define PHASEREGEN_FILTER "healing_glow"
+#define RUIN_QUEUE "the_thing_depleter"
+/mob/living/basic/boss/thing
+ name = "\improper Thing"
+ icon = 'icons/mob/simple/icemoon/thething.dmi'
+ icon_state = "p1"
+ icon_dead = "dead"
+ gender = NEUTER
+ maxHealth = 1800 //nicely divisible by three
+ health = 1800
+ armour_penetration = 40
+ melee_damage_lower = 30
+ melee_damage_upper = 30
+ sharpness = SHARP_EDGED
+ melee_attack_cooldown = CLICK_CD_SLOW
+ attack_verb_continuous = "eviscerates"
+ attack_verb_simple = "eviscerate"
+ attack_sound = 'sound/items/weapons/bladeslice.ogg'
+ attack_vis_effect = ATTACK_EFFECT_SLASH
+ speed = 3.5 //dont make this any faster PLEASE
+ gps_name = "L-4 Biohazard Beacon"
+ ai_controller = /datum/ai_controller/basic_controller/thing_boss
+ loot = list(/obj/item/keycard/thing_boss)
+ crusher_loot = list(/obj/item/keycard/thing_boss, /obj/item/crusher_trophy/flesh_glob)
+ mouse_opacity = MOUSE_OPACITY_OPAQUE
+ /// Current phase of the boss fight
+ var/phase = 1
+ /// Time the Thing will be invulnerable between phases
+ var/phase_invul_time = 10 SECONDS
+ /// timer of phase invulnerability between phases
+ var/phase_invulnerability_timer
+
+ // ruin logic
+
+ /// if true, this boss may only be killed proper in its ruin by the associated machines as part of the bossfight. Turn off if admin shitspawn
+ var/maploaded = TRUE
+
+/mob/living/basic/boss/thing/Initialize(mapload)
+ . = ..()
+ var/static/list/innate_actions = list(
+ /datum/action/cooldown/mob_cooldown/the_thing/decimate = BB_THETHING_DECIMATE,
+ /datum/action/cooldown/mob_cooldown/charge/the_thing = BB_THETHING_CHARGE,
+ /datum/action/cooldown/mob_cooldown/the_thing/big_tendrils = BB_THETHING_BIGTENDRILS,
+ /datum/action/cooldown/mob_cooldown/the_thing/shriek = BB_THETHING_SHRIEK,
+ /datum/action/cooldown/mob_cooldown/the_thing/cardinal_tendrils = BB_THETHING_CARDTENDRILS,
+ /datum/action/cooldown/mob_cooldown/the_thing/acid_spit = BB_THETHING_ACIDSPIT,
+ )
+ grant_actions_by_list(innate_actions)
+ AddComponent(/datum/component/basic_mob_attack_telegraph, telegraph_duration = 0.3 SECONDS)
+ maploaded = mapload
+ if(maploaded)
+ SSqueuelinks.add_to_queue(src, RUIN_QUEUE, 0)
+ return INITIALIZE_HINT_LATELOAD
+
+/mob/living/basic/boss/thing/LateInitialize()
+ SSqueuelinks.pop_link(RUIN_QUEUE)
+
+/mob/living/basic/boss/thing/update_icon_state()
+ . = ..()
+ if(stat)
+ icon_state = "dead"
+ return
+ icon_state = "p[phase]"
+ icon_living = icon_state
+
+/mob/living/basic/boss/thing/adjust_health(amount, updating_health = TRUE, forced = FALSE)
+ if(phase_invulnerability_timer || phase == 3 || stat || amount <= 0)
+ return ..()
+ var/potential_excess = bruteloss + amount - (maxHealth/3)*phase
+ if(potential_excess > 0)
+ amount -= potential_excess
+ . = ..()
+ if(bruteloss >= (maxHealth/3)*phase)
+ phase_health_depleted()
+
+/mob/living/basic/boss/thing/proc/phase_health_depleted()
+ if(phase_invulnerability_timer)
+ return //wtf?
+ if(!maploaded)
+ phase_successfully_depleted()
+ return
+ add_traits(list(TRAIT_GODMODE, TRAIT_IMMOBILIZED), MEGAFAUNA_TRAIT)
+ balloon_alert_to_viewers("invulnerable! overload the machines!")
+ visible_message(span_danger("[src] drops to the ground staggered, unable to keep up with injuries!"))
+ phase_invulnerability_timer = addtimer(CALLBACK(src, PROC_REF(phase_too_slow)), phase_invul_time, TIMER_STOPPABLE|TIMER_UNIQUE)
+ add_filter(PHASEREGEN_FILTER, 2, list("type" = "outline", "color" = COLOR_PALE_GREEN, "alpha" = 0, "size" = 1))
+ var/filter = get_filter(PHASEREGEN_FILTER)
+ animate(filter, alpha = 200, time = 0.5 SECONDS, loop = -1)
+ animate(alpha = 0, time = 0.5 SECONDS)
+ SEND_SIGNAL(src, COMSIG_MEGAFAUNA_THETHING_PHASEUPDATED)
+
+/// The Thing is successfully hit by incendiary fire while downed by damage (alternatively takes too much damage if not ruin spawned)
+/mob/living/basic/boss/thing/proc/phase_successfully_depleted()
+ playsound(src, 'sound/effects/pop_expl.ogg', 65)
+ ai_controller?.set_blackboard_key(BB_THETHING_NOAOE, FALSE)
+ remove_traits(list(TRAIT_GODMODE, TRAIT_IMMOBILIZED), MEGAFAUNA_TRAIT)
+ deltimer(phase_invulnerability_timer)
+ phase_invulnerability_timer = null
+ if(phase < 3) //after phase 3 we literally just die
+ phase++
+ emote("scream")
+ update_appearance()
+ var/filter = get_filter(PHASEREGEN_FILTER)
+ if(!isnull(filter))
+ animate(filter)
+ remove_filter(PHASEREGEN_FILTER)
+ SEND_SIGNAL(src, COMSIG_MEGAFAUNA_THETHING_PHASEUPDATED)
+ new /obj/effect/gibspawner/human/bodypartless(loc)
+
+/mob/living/basic/boss/thing/proc/phase_too_slow()
+ phase_invulnerability_timer = null
+ remove_traits(list(TRAIT_GODMODE, TRAIT_IMMOBILIZED), MEGAFAUNA_TRAIT)
+ balloon_alert_to_viewers("recovers!")
+ visible_message(span_danger("[src] recovers from the damage! Too slow!"))
+ adjust_health(-(maxHealth/3) * 0.5) //half of a phase (which is a third of maxhealth)
+ var/filter = get_filter(PHASEREGEN_FILTER)
+ if(!isnull(filter))
+ animate(filter)
+ remove_filter(PHASEREGEN_FILTER)
+ emote("roar")
+ SEND_SIGNAL(src, COMSIG_MEGAFAUNA_THETHING_PHASEUPDATED)
+
+/mob/living/basic/boss/thing/vv_edit_var(vname, vval)
+ . = ..()
+ if(vname == NAMEOF(src, phase))
+ ai_controller?.set_blackboard_key(BB_THETHING_NOAOE, phase > 1 ? FALSE : TRUE)
+ update_appearance()
+
+/mob/living/basic/boss/thing/with_ruin_loot
+ loot = list(/obj/item/organ/brain/cybernetic/ai) // the main loot of the ruin, but if admin spawned the keycard is useless
+ crusher_loot = list(/obj/item/organ/brain/cybernetic/ai, /obj/item/crusher_trophy/flesh_glob)
+
+// special stuff for our ruin to make a cooler bossfight
+
+/obj/structure/thing_boss_phase_depleter
+ name = "Molecular Accelerator"
+ desc = "Weird-ass lab equipment."
+ icon_state = "thingdepleter"
+ anchored = TRUE
+ density = TRUE
+ move_resist = INFINITY
+ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ /// is this not broken yet
+ var/functional = TRUE
+ /// boss weakref
+ var/datum/weakref/boss_weakref
+
+/obj/structure/thing_boss_phase_depleter/Initialize(mapload)
+ . = ..()
+ go_in_floor()
+ SSqueuelinks.add_to_queue(src, RUIN_QUEUE, 0)
+
+/obj/structure/thing_boss_phase_depleter/MatchedLinks(id, list/partners)
+ if(id != RUIN_QUEUE)
+ return
+ var/mob/living/basic/boss/thing/thing = locate() in partners
+ if(isnull(thing))
+ qdel(src)
+ return
+ boss_weakref = WEAKREF(thing)
+ RegisterSignal(thing, COMSIG_MEGAFAUNA_THETHING_PHASEUPDATED, PROC_REF(thing_phaseupdated))
+
+/obj/structure/thing_boss_phase_depleter/proc/thing_phaseupdated(mob/living/basic/boss/thing/source)
+ SIGNAL_HANDLER
+ if(!functional)
+ return
+ if(source.phase_invulnerability_timer)
+ go_out_floor()
+ else
+ go_in_floor()
+
+/obj/structure/thing_boss_phase_depleter/examine(mob/user)
+ . = ..()
+ . += density ? span_boldnotice("It may be possible to overload this and destroy that things defenses...") : span_bolddanger("The machine is currently being restrained by tendrils.")
+
+/obj/structure/thing_boss_phase_depleter/proc/set_circuit_floor(state)
+ for(var/turf/open/floor/circuit/circuit in RANGE_TURFS(1, loc))
+ circuit.on = state
+ circuit.update_appearance()
+
+/obj/structure/thing_boss_phase_depleter/proc/go_in_floor()
+ if(!density)
+ return
+ density = FALSE
+ obj_flags &= ~CAN_BE_HIT
+ set_circuit_floor(FALSE)
+ name = "hatch"
+ icon_state = "thingdepleter_infloor"
+
+/obj/structure/thing_boss_phase_depleter/proc/go_out_floor()
+ if(density)
+ return
+ density = TRUE
+ obj_flags |= CAN_BE_HIT
+ set_circuit_floor(TRUE)
+ name = initial(name)
+ icon_state = "thingdepleter"
+ new /obj/effect/temp_visual/mook_dust(loc)
+
+/obj/structure/thing_boss_phase_depleter/interact(mob/user, list/modifiers)
+ var/mob/living/basic/boss/thing/the_thing = boss_weakref?.resolve()
+ if(!the_thing || !functional || !density)
+ return
+ if(!user.can_perform_action(src) || !user.can_interact_with(src))
+ return
+ balloon_alert_to_viewers("overloading...")
+ icon_state = "thingdepleter_overriding"
+ if(!do_after(user, 1 SECONDS, target = src))
+ if(density)
+ icon_state = "thingdepleter"
+ return
+ new /obj/effect/temp_visual/circle_wave/orange(loc)
+ playsound(src, 'sound/effects/explosion/explosion3.ogg', 100)
+ animate(src, transform = matrix()*1.5, time = 0.2 SECONDS)
+ animate(transform = matrix(), time = 0)
+ the_thing.phase_successfully_depleted()
+ functional = FALSE
+ go_in_floor()
+ icon_state = "thingdepleter_overriding"
+ addtimer(VARSET_CALLBACK(src, icon_state, "thingdepleter_broken"), 0.2 SECONDS)
+
+/obj/effect/temp_visual/circle_wave/orange
+ color = COLOR_ORANGE
+
+/obj/structure/aggro_gate
+ name = "biohazard gate"
+ desc = "A wall of solid light, only activating when a human is endangered by a biohazard, unfortunately that does little for safety as it locks you in with said biohazard. Virtually indestructible, you must evade (or kill) the threat."
+ icon = 'icons/effects/effects.dmi'
+ icon_state = "wave2"
+ resistance_flags = INDESTRUCTIBLE | FIRE_PROOF | ACID_PROOF | LAVA_PROOF
+ move_resist = MOVE_FORCE_OVERPOWERING
+ opacity = FALSE
+ density = FALSE
+ invisibility = INVISIBILITY_MAXIMUM
+ anchored = TRUE
+ /// queue id
+ var/queue_id = RUIN_QUEUE
+ /// blackboard key for target
+ var/target_bb_key = BB_BASIC_MOB_CURRENT_TARGET
+
+/obj/structure/aggro_gate/Initialize(mapload)
+ . = ..()
+ SSqueuelinks.add_to_queue(src, queue_id)
+
+/obj/structure/aggro_gate/MatchedLinks(id, list/partners)
+ if(id != queue_id)
+ return
+ for(var/mob/living/partner in partners)
+ RegisterSignal(partner, COMSIG_AI_BLACKBOARD_KEY_SET(target_bb_key), PROC_REF(bar_the_gates))
+ RegisterSignals(partner, list(COMSIG_AI_BLACKBOARD_KEY_CLEARED(target_bb_key), COMSIG_LIVING_DEATH, COMSIG_MOB_LOGIN), PROC_REF(open_gates))
+
+/obj/structure/aggro_gate/proc/bar_the_gates(mob/living/source)
+ SIGNAL_HANDLER
+ var/atom/target = source.ai_controller?.blackboard[target_bb_key]
+ if (QDELETED(target))
+ return
+ invisibility = INVISIBILITY_NONE
+ density = TRUE
+ playsound(src, SFX_SPARKS, 100, vary = TRUE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE)
+ do_sparks(3, cardinal_only = FALSE, source = src)
+
+/obj/structure/aggro_gate/proc/open_gates(mob/living/source)
+ playsound(src, SFX_SPARKS, 100, vary = TRUE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE)
+ do_sparks(3, cardinal_only = FALSE, source = src)
+ density = FALSE
+ invisibility = INVISIBILITY_MAXIMUM
+
+#undef PHASEREGEN_FILTER
+#undef RUIN_QUEUE
diff --git a/code/modules/mob/living/basic/boss/thing/thing_abilities.dm b/code/modules/mob/living/basic/boss/thing/thing_abilities.dm
new file mode 100644
index 0000000000000..3bf466c148f45
--- /dev/null
+++ b/code/modules/mob/living/basic/boss/thing/thing_abilities.dm
@@ -0,0 +1,202 @@
+/datum/action/cooldown/mob_cooldown/the_thing
+ shared_cooldown = MOB_SHARED_COOLDOWN_3
+ /// in what phases of The Thing bossfight is this available
+ var/list/available_in_phases = list(1,2,3)
+
+/datum/action/cooldown/mob_cooldown/the_thing/IsAvailable(feedback)
+ var/mob/living/basic/boss/thing/the_thing = owner
+ if(!istype(the_thing))
+ return ..()
+ return ..() && (the_thing.phase in available_in_phases)
+
+//decimate
+
+/datum/action/cooldown/mob_cooldown/the_thing/decimate
+ name = "Decimate"
+ desc = "Create spikes in a radius."
+ button_icon = 'icons/obj/weapons/stabby.dmi'
+ button_icon_state = "huntingknife"
+ click_to_activate = FALSE
+ cooldown_time = 10 SECONDS
+
+/datum/action/cooldown/mob_cooldown/the_thing/decimate/Activate(atom/caster)
+ if(HAS_TRAIT_FROM(caster, TRAIT_IMMOBILIZED, MEGAFAUNA_TRAIT))
+ return
+ . = ..()
+
+ ADD_TRAIT(caster, TRAIT_IMMOBILIZED, MEGAFAUNA_TRAIT)
+ caster.Shake(1.4, 0.8, 0.3 SECONDS)
+ caster.visible_message(span_danger("[caster] shakes violently!"))
+
+ for(var/turf/open/target in RANGE_TURFS(2, caster) - caster.loc)
+ new /obj/effect/temp_visual/telegraphing/exclamation/animated(target, 1.5 SECONDS)
+ addtimer(CALLBACK(src, PROC_REF(make_spikes), caster), 1.5 SECONDS)
+
+/datum/action/cooldown/mob_cooldown/the_thing/decimate/proc/make_spikes(atom/caster)
+ REMOVE_TRAIT(caster, TRAIT_IMMOBILIZED, MEGAFAUNA_TRAIT)
+ for(var/turf/open/target in RANGE_TURFS(2, caster))
+ if(locate(/obj/structure/thing_boss_spike) in target)
+ continue
+ new /obj/structure/thing_boss_spike(target)
+
+// charge
+
+/datum/action/cooldown/mob_cooldown/charge/the_thing
+ shared_cooldown = NONE
+ charge_damage = 35
+ charge_past = 3
+
+/datum/action/cooldown/mob_cooldown/charge/the_thing/charge_sequence(atom/movable/charger, atom/target_atom, delay, past)
+ if(HAS_TRAIT_FROM(owner, TRAIT_IMMOBILIZED, MEGAFAUNA_TRAIT))
+ return
+ var/mob/living/basic/boss/thing/the_thing = owner
+ var/charge_count = the_thing.phase != 3 ? 1 : 2
+ for(var/i in 1 to charge_count)
+ do_charge(owner, target_atom, charge_delay * i, charge_past)
+
+/datum/action/cooldown/mob_cooldown/charge/the_thing/do_charge_indicator(atom/charger, atom/charge_target)
+ var/turf/target_turf = get_turf(charge_target)
+ if(!target_turf)
+ return
+ new /obj/effect/temp_visual/telegraphing/exclamation/animated(target_turf)
+ var/obj/effect/temp_visual/decoy/decoy = new /obj/effect/temp_visual/decoy(charger.loc, charger)
+ animate(decoy, alpha = 0, color = COLOR_RED, transform = matrix()*2, time = 3)
+
+/datum/action/cooldown/mob_cooldown/charge/the_thing/hit_target(atom/movable/source, mob/living/target, damage_dealt)
+ target.visible_message(span_danger("[source] lunges into [target]!"), span_userdanger("[source] knocks you into the ground, slashing you in the process!"))
+ target.apply_damage(damage_dealt, BRUTE)
+ target.Knockdown(1 SECONDS)
+ playsound(get_turf(target), 'sound/items/weapons/rapierhit.ogg', 100, TRUE)
+ shake_camera(target, 4, 3)
+
+// square tendrils
+
+/datum/action/cooldown/mob_cooldown/the_thing/big_tendrils
+ name = "Square Tendrils"
+ desc = "Create spikes in a square around the target."
+ button_icon = 'icons/obj/weapons/stabby.dmi'
+ button_icon_state = "huntingknife"
+ cooldown_time = 5 SECONDS
+ available_in_phases = list(2,3)
+
+/datum/action/cooldown/mob_cooldown/the_thing/big_tendrils/Activate(atom/target)
+ if(HAS_TRAIT_FROM(owner, TRAIT_IMMOBILIZED, MEGAFAUNA_TRAIT))
+ return
+ . = ..()
+ target = get_turf(target)
+ var/mob/living/living_owner = owner
+ var/delay = 1 SECONDS
+ if((living_owner.health <= living_owner.maxHealth/3) ? 2 : 1)
+ delay += 1 SECONDS
+ new /obj/effect/temp_visual/telegraphing/big(target, delay)
+ else
+ new /obj/effect/temp_visual/telegraphing/exclamation/animated(target)
+ addtimer(CALLBACK(src, PROC_REF(make_spikes), target), delay)
+
+/datum/action/cooldown/mob_cooldown/the_thing/big_tendrils/proc/make_spikes(atom/epicenter)
+ var/mob/living/living_owner = owner
+ var/radius = living_owner.health <= living_owner.maxHealth/3 ? 2 : 1
+ for(var/turf/open/target in RANGE_TURFS(radius, epicenter))
+ if(locate(/obj/structure/thing_boss_spike) in target)
+ continue
+ new /obj/effect/temp_visual/mook_dust(target)
+ new /obj/structure/thing_boss_spike(target)
+
+// shriek
+
+/datum/action/cooldown/mob_cooldown/the_thing/shriek
+ name = "Shriek"
+ desc = "Confuse in a radius."
+ button_icon = 'icons/obj/weapons/stabby.dmi'
+ button_icon_state = "huntingknife"
+ click_to_activate = FALSE
+ cooldown_time = 10 SECONDS
+ shared_cooldown = NONE
+ available_in_phases = list(2,3)
+
+/datum/action/cooldown/mob_cooldown/the_thing/shriek/Activate(atom/caster)
+ if(HAS_TRAIT_FROM(caster, TRAIT_IMMOBILIZED, MEGAFAUNA_TRAIT))
+ return
+ . = ..()
+ ADD_TRAIT(caster, TRAIT_IMMOBILIZED, MEGAFAUNA_TRAIT)
+ caster.visible_message(span_danger("[caster][caster.p_s()] flesh starts becoming filled with holes!"))
+ for(var/turf/open/target in RANGE_TURFS(2, caster))
+ new /obj/effect/temp_visual/telegraphing/exclamation(target, 1 SECONDS)
+ addtimer(CALLBACK(src, PROC_REF(shriek), owner), 1 SECONDS)
+
+/datum/action/cooldown/mob_cooldown/the_thing/shriek/proc/shriek(atom/caster)
+ REMOVE_TRAIT(caster, TRAIT_IMMOBILIZED, MEGAFAUNA_TRAIT)
+ caster.visible_message(span_danger("[caster] shrieks! The sheer frequency of the sound makes your skin hurt and you feel like your brain is on fire!"))
+ SEND_SOUND(caster, sound('sound/effects/screech.ogg'))
+ for(var/mob/living/target in range(2, caster))
+ if(target == owner)
+ continue
+ target.set_confusion_if_lower(5 SECONDS)
+ target.set_jitter_if_lower(5 SECONDS)
+ var/mob/living/carbon/carbon_target = target
+ if(istype(carbon_target))
+ carbon_target.drop_all_held_items()
+ SEND_SOUND(target, sound('sound/effects/screech.ogg'))
+
+// card. tendrils
+
+/datum/action/cooldown/mob_cooldown/the_thing/cardinal_tendrils
+ name = "Cardinal Tendrils"
+ desc = "Create tendrils in all cardinal directions."
+ button_icon = 'icons/obj/weapons/stabby.dmi'
+ button_icon_state = "huntingknife"
+ cooldown_time = 10 SECONDS
+ available_in_phases = list(2,3)
+ click_to_activate = FALSE
+ /// range of tendril
+ var/range = 9
+
+/datum/action/cooldown/mob_cooldown/the_thing/cardinal_tendrils/Activate(atom/targetted_turf)
+ if(HAS_TRAIT_FROM(owner, TRAIT_IMMOBILIZED, MEGAFAUNA_TRAIT))
+ return
+ . = ..()
+ targetted_turf = get_turf(targetted_turf)
+ owner.Shake(1.4, 0.8, 0.3 SECONDS)
+ owner.visible_message(span_danger("[owner] shakes violently!"))
+ var/list/turf/target_turfs = find_turfs(targetted_turf)
+ for(var/turf/open/target in target_turfs)
+ new /obj/effect/temp_visual/telegraphing/exclamation/animated(target, 1.5 SECONDS)
+ addtimer(CALLBACK(src, PROC_REF(make_spikes), target_turfs, owner), 1.5 SECONDS)
+
+/datum/action/cooldown/mob_cooldown/the_thing/cardinal_tendrils/proc/find_turfs(atom/caster)
+ . = list()
+ for(var/direction in GLOB.cardinals)
+ for(var/turf/potential_turf as anything in get_line(caster, get_ranged_target_turf(caster, direction, range)))
+ if(potential_turf.density)
+ break
+ . += potential_turf
+
+/datum/action/cooldown/mob_cooldown/the_thing/cardinal_tendrils/proc/make_spikes(list/target_turfs, atom/caster)
+ REMOVE_TRAIT(caster, TRAIT_IMMOBILIZED, MEGAFAUNA_TRAIT)
+ for(var/turf/open/target as anything in target_turfs)
+ if(locate(/obj/structure/thing_boss_spike) in target)
+ continue
+ new /obj/effect/temp_visual/mook_dust(target)
+ new /obj/structure/thing_boss_spike(target)
+
+// acid
+
+/datum/action/cooldown/mob_cooldown/the_thing/acid_spit
+ name = "Acid Shower"
+ desc = "Spit patches of acid in a radius around you."
+ button_icon = 'icons/obj/weapons/stabby.dmi'
+ button_icon_state = "huntingknife"
+ cooldown_time = 10 SECONDS
+ click_to_activate = FALSE
+ available_in_phases = list(3)
+
+/datum/action/cooldown/mob_cooldown/the_thing/acid_spit/Activate(atom/target)
+ if(HAS_TRAIT_FROM(owner, TRAIT_IMMOBILIZED, MEGAFAUNA_TRAIT))
+ return
+ . = ..()
+ var/turf/owner_turf = get_turf(owner)
+ owner.visible_message(span_danger("[owner] spits acid!"))
+ var/list/potential = RANGE_TURFS(4, owner_turf)
+
+ for(var/i = 1 to rand(2,4))
+ new /obj/effect/temp_visual/incoming_thing_acid(pick(potential))
diff --git a/code/modules/mob/living/basic/boss/thing/thing_ai.dm b/code/modules/mob/living/basic/boss/thing/thing_ai.dm
new file mode 100644
index 0000000000000..9d31d9c76988c
--- /dev/null
+++ b/code/modules/mob/living/basic/boss/thing/thing_ai.dm
@@ -0,0 +1,70 @@
+/datum/ai_controller/basic_controller/thing_boss
+ blackboard = list(
+ BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/no_gutted_mobs,
+ BB_TARGET_MINIMUM_STAT = DEAD, // Will attack dead ungutted mobs
+ BB_THETHING_ATTACKMODE = TRUE, //Whether we are using our melee abilities right now
+ BB_THETHING_NOAOE = TRUE, // Restricts us to only melee abilities
+ BB_THETHING_LASTAOE = null, // Last AOE ability key executed
+ BB_AGGRO_RANGE = 6, //lets not execute hearers for a 16 tile radius
+ )
+
+ ai_movement = /datum/ai_movement/basic_avoidance // dont need anything better because the arena is a square lol
+ idle_behavior = null
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/simple_find_target/increased_range, //aggros at 6, sees 16 tiles
+ /datum/ai_planning_subtree/thing_boss_aoe,
+ /datum/ai_planning_subtree/thing_boss_melee,
+ )
+
+/datum/ai_planning_subtree/thing_boss_aoe/SelectBehaviors(datum/ai_controller/monkey/controller, seconds_per_tick)
+ var/mob/living/pawn = controller.pawn
+ if(HAS_TRAIT_FROM(pawn, TRAIT_IMMOBILIZED, MEGAFAUNA_TRAIT) || (controller.blackboard[BB_THETHING_ATTACKMODE] || controller.blackboard[BB_THETHING_NOAOE]))
+ return
+ // our target
+ var/mob/living/shaft_miner = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
+ if(QDELETED(shaft_miner) || shaft_miner.stat == DEAD) //Dont use abilities on off z level targets, or dead shaft miners. We want to melee those.
+ return
+
+ controller.set_blackboard_key(BB_THETHING_ATTACKMODE, TRUE) // putting this here so we go to melee mode if we cant do any aoe
+ var/static/list/aoe_attacks = list(BB_THETHING_DECIMATE, BB_THETHING_BIGTENDRILS, BB_THETHING_CARDTENDRILS, BB_THETHING_ACIDSPIT)
+ var/list/possible_attacks = aoe_attacks.Copy() - controller.blackboard[BB_THETHING_LASTAOE]
+ for(var/bb_action_key in possible_attacks)
+ var/datum/action/action = controller.blackboard[bb_action_key]
+ if(!action?.IsAvailable())
+ possible_attacks -= bb_action_key
+ if(!length(possible_attacks))
+ return
+ var/current_aoe_key = pick(possible_attacks)
+ controller.set_blackboard_key(BB_THETHING_LASTAOE, current_aoe_key)
+ controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability, current_aoe_key, BB_BASIC_MOB_CURRENT_TARGET)
+ if(prob(60) && shaft_miner.body_position != LYING_DOWN) //potential follow-up
+ controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability, BB_THETHING_CHARGE, BB_BASIC_MOB_CURRENT_TARGET)
+ return SUBTREE_RETURN_FINISH_PLANNING
+
+
+/datum/ai_planning_subtree/thing_boss_melee/SelectBehaviors(datum/ai_controller/monkey/controller, seconds_per_tick)
+ var/mob/living/pawn = controller.pawn
+ if(HAS_TRAIT_FROM(pawn, TRAIT_IMMOBILIZED, MEGAFAUNA_TRAIT))
+ return
+
+ // our target
+ var/mob/living/shaft_miner = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
+ if(QDELETED(shaft_miner))
+ return
+ var/target_dist = get_dist(pawn, shaft_miner)
+
+ var/datum/action/shriek = controller.blackboard[BB_THETHING_SHRIEK]
+ var/datum/action/charge = controller.blackboard[BB_THETHING_CHARGE]
+ if(isnull(shriek) || isnull(charge))
+ return // pray this never occurs
+
+ controller.set_blackboard_key(BB_THETHING_ATTACKMODE, FALSE)
+
+ if(shriek.IsAvailable() && target_dist <= 2 && shaft_miner.stat != DEAD)
+ controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/min_range/short, BB_THETHING_SHRIEK, BB_BASIC_MOB_CURRENT_TARGET)
+ return
+ else if(charge.IsAvailable() && target_dist >= 5) // While we cant hit prone targets, this helps to close the distance.
+ controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability, BB_THETHING_CHARGE, BB_BASIC_MOB_CURRENT_TARGET)
+ return
+
+ controller.queue_behavior(/datum/ai_behavior/basic_melee_attack, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION)
diff --git a/code/modules/mob/living/basic/boss/thing/thing_objects.dm b/code/modules/mob/living/basic/boss/thing/thing_objects.dm
new file mode 100644
index 0000000000000..b1801b3216465
--- /dev/null
+++ b/code/modules/mob/living/basic/boss/thing/thing_objects.dm
@@ -0,0 +1,130 @@
+/obj/structure/thing_boss_spike
+ name = "blades"
+ desc = "A sharp flurry of blades that have erupted from the ground."
+ icon_state = "thingspike"
+ density = FALSE //so ai considers it
+ anchored = TRUE
+ max_integrity = 1 // 1 hit
+ /// time before we fall apart
+ var/expiry_time = 10 SECONDS
+
+/obj/structure/thing_boss_spike/Initialize(mapload)
+ . = ..()
+ var/turf/our_turf = get_turf(src)
+#ifndef UNIT_TESTS //just in case
+ new /obj/effect/temp_visual/mook_dust(loc)
+#endif
+ var/hit_someone = FALSE
+ for(var/atom/movable/potential_target as anything in our_turf)
+ if (ismegafauna(potential_target) || potential_target == src)
+ continue
+ var/mob/living/living_victim = potential_target
+ if(isliving(living_victim))
+ hit_someone = TRUE
+ living_victim.apply_damage(40, damagetype = BRUTE, sharpness = SHARP_POINTY)
+ else if(potential_target.uses_integrity && !(potential_target.resistance_flags & INDESTRUCTIBLE) && !(initial(potential_target.density)) && !HAS_TRAIT(potential_target, TRAIT_UNDERFLOOR))
+ potential_target.take_damage(100, BRUTE)
+ if (hit_someone)
+ expiry_time /= 2
+ playsound(src, 'sound/items/weapons/slice.ogg', vol = 50, vary = TRUE, pressure_affected = FALSE)
+ else
+ playsound(src, 'sound/misc/splort.ogg', vol = 25, vary = TRUE, pressure_affected = FALSE)
+
+ QDEL_IN(src, expiry_time)
+
+/obj/structure/thing_boss_spike/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0)
+ if(damage_amount)
+ playsound(src, 'sound/effects/blob/blobattack.ogg', 50, TRUE)
+ else
+ playsound(src, 'sound/items/weapons/tap.ogg', 50, TRUE)
+
+/obj/structure/thing_boss_spike/CanAllowThrough(atom/movable/mover, border_dir)
+ . = ..()
+ if(!istype(mover, /mob/living/basic/boss/thing))
+ return FALSE
+
+/obj/structure/thing_boss_spike/CanAStarPass(to_dir, datum/can_pass_info/pass_info)
+ if(!istype(pass_info.requester_ref?.resolve(), /mob/living/basic/boss/thing))
+ return FALSE
+ return ..()
+
+/obj/effect/temp_visual/telegraphing/exclamation
+ icon = 'icons/mob/telegraphing/telegraph.dmi'
+ icon_state = "exclamation"
+ duration = 1 SECONDS
+
+/obj/effect/temp_visual/telegraphing/exclamation/Initialize(mapload, duration)
+ if(!isnull(duration))
+ src.duration = duration
+ return ..()
+
+/obj/effect/temp_visual/telegraphing/exclamation/animated
+ alpha = 0
+
+/obj/effect/temp_visual/telegraphing/exclamation/animated/Initialize(mapload)
+ . = ..()
+ transform = matrix()*2
+ animate(src, alpha = 255, transform = matrix(), time = duration/3)
+
+/obj/effect/temp_visual/telegraphing/big
+ icon = 'icons/mob/telegraphing/telegraph_96x96.dmi'
+ icon_state = "target_largebox"
+ pixel_x = -32
+ pixel_y = -32
+ color = COLOR_RED
+ duration = 2 SECONDS
+
+/obj/effect/temp_visual/incoming_thing_acid
+ icon = 'icons/obj/weapons/guns/projectiles.dmi'
+ icon_state = "toxin"
+ name = "acid"
+ desc = "Get out of the way!"
+ layer = FLY_LAYER
+ plane = ABOVE_GAME_PLANE
+ randomdir = FALSE
+ duration = 0.9 SECONDS
+ pixel_z = 270
+
+/obj/effect/temp_visual/incoming_thing_acid/Initialize(mapload)
+ . = ..()
+ animate(src, pixel_z = 0, time = duration)
+ addtimer(CALLBACK(src, PROC_REF(make_acid)), 0.85 SECONDS)
+
+/obj/effect/temp_visual/incoming_thing_acid/proc/make_acid()
+ for(var/turf/open/open in RANGE_TURFS(1, loc))
+ new /obj/effect/thing_acid(open)
+
+/obj/effect/thing_acid
+ name = "stomach acid"
+ icon = 'icons/effects/acid.dmi'
+ icon_state = "default"
+ layer = BELOW_MOB_LAYER
+ plane = GAME_PLANE
+ anchored = TRUE
+ /// how long does the acid exist for
+ var/duration_time = 5 SECONDS
+
+/obj/effect/thing_acid/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+ QDEL_IN(src, duration_time)
+
+/obj/effect/thing_acid/proc/on_entered(datum/source, mob/living/victim)
+ SIGNAL_HANDLER
+ if(!istype(victim) || ismegafauna(victim))
+ return
+ for(var/zone in list(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG))
+ var/blocked = victim.run_armor_check(zone, ACID)
+ victim.apply_damage(25, BURN, def_zone = zone, blocked = blocked)
+ to_chat(victim, span_userdanger("You are burnt by the acid!"))
+ playsound(victim, 'sound/effects/wounds/sizzle1.ogg', vol = 50, vary = TRUE)
+ qdel(src)
+
+/obj/item/keycard/thing_boss
+ name = "Storage Room 2 Keycard"
+ desc = "A fancy keycard for storage room 2."
+ color = COLOR_PALE_GREEN
+ puzzle_id = "thingbosslootroom"
diff --git a/code/modules/mob/living/simple_animal/hostile/vatbeast.dm b/code/modules/mob/living/basic/cytology/vatbeast.dm
similarity index 58%
rename from code/modules/mob/living/simple_animal/hostile/vatbeast.dm
rename to code/modules/mob/living/basic/cytology/vatbeast.dm
index c4850fae783a3..60ad869a969dc 100644
--- a/code/modules/mob/living/simple_animal/hostile/vatbeast.dm
+++ b/code/modules/mob/living/basic/cytology/vatbeast.dm
@@ -1,52 +1,92 @@
-///Vatbeasts are creatures from vatgrowing and are literaly a beast in a vat, yup. They are designed to be a powerful mount roughly equal to a gorilla in power.
-/mob/living/simple_animal/hostile/vatbeast
+/// A mob that slaps people around and can be tamed as a mount
+/mob/living/basic/vatbeast
name = "vatbeast"
desc = "A strange molluscoidal creature carrying a busted growing vat.\nYou wonder if this burden is a voluntary undertaking in order to achieve comfort and protection, or simply because the creature is fused to its metal shell?"
icon = 'icons/mob/vatgrowing.dmi'
icon_state = "vat_beast"
icon_living = "vat_beast"
icon_dead = "vat_beast_dead"
- mob_biotypes = MOB_ORGANIC|MOB_BEAST
+ mob_biotypes = MOB_ORGANIC | MOB_BEAST
mob_size = MOB_SIZE_LARGE
gender = NEUTER
- environment_smash = ENVIRONMENT_SMASH_STRUCTURES
speak_emote = list("roars")
- atmos_requirements = null
health = 250
maxHealth = 250
damage_coeff = list(BRUTE = 0.7, BURN = 0.7, TOX = 1, STAMINA = 0, OXY = 1)
melee_damage_lower = 25
melee_damage_upper = 25
obj_damage = 40
- // Greenish, seems about right for it
- lighting_cutoff_red = 10
- lighting_cutoff_green = 25
- lighting_cutoff_blue = 20
+ unsuitable_atmos_damage = 0
attack_sound = 'sound/items/weapons/punch3.ogg'
attack_verb_continuous = "slaps"
attack_verb_simple = "slap"
+ // Greenish darkvision
+ lighting_cutoff_red = 10
+ lighting_cutoff_green = 25
+ lighting_cutoff_blue = 20
+ ai_controller = /datum/ai_controller/basic_controller/vatbeast
+ faction = list(FACTION_HOSTILE)
+ /// What can you feed a vatbeast to tame it?
+ var/static/list/enjoyed_food = list(
+ /obj/item/food/carrotfries,
+ /obj/item/food/cheesyfries,
+ /obj/item/food/cornchips,
+ /obj/item/food/fries,
+ )
-/mob/living/simple_animal/hostile/vatbeast/Initialize(mapload)
+/mob/living/basic/vatbeast/Initialize(mapload)
. = ..()
- GRANT_ACTION(/datum/action/cooldown/tentacle_slap)
add_cell_sample()
- var/static/list/food_types = list(
- /obj/item/food/fries,
- /obj/item/food/cheesyfries,
- /obj/item/food/cornchips,
- /obj/item/food/carrotfries,
- )
- AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 30, bonus_tame_chance = 0)
+ AddElement(/datum/element/ai_retaliate)
+ AddComponent(/datum/component/ai_target_timer)
+ AddComponent(/datum/component/tameable, food_types = enjoyed_food, tame_chance = 30, bonus_tame_chance = 0)
+
+ var/datum/action/cooldown/tentacle_slap/slapper = new (src)
+ slapper.Grant(src)
-/mob/living/simple_animal/hostile/vatbeast/tamed(mob/living/tamer, obj/item/food)
+ ai_controller.set_blackboard_key(BB_TARGETED_ACTION, slapper)
+ ai_controller.set_blackboard_key(BB_BASIC_FOODS, typecacheof(enjoyed_food))
+
+/mob/living/basic/vatbeast/tamed(mob/living/tamer, obj/item/food)
buckle_lying = 0
AddElement(/datum/element/ridable, /datum/component/riding/creature/vatbeast)
faction = list(FACTION_NEUTRAL)
-/mob/living/simple_animal/hostile/vatbeast/add_cell_sample()
+/mob/living/basic/vatbeast/proc/add_cell_sample()
AddElement(/datum/element/swabable, CELL_LINE_TABLE_VATBEAST, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
+/// Attack people and slap them
+/datum/ai_controller/basic_controller/vatbeast
+ blackboard = list(
+ BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
+ )
+
+ ai_movement = /datum/ai_movement/basic_avoidance
+ idle_behavior = /datum/idle_behavior/idle_random_walk
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/target_retaliate,
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/find_food,
+ /datum/ai_planning_subtree/attack_obstacle_in_path,
+ /datum/ai_planning_subtree/targeted_mob_ability/vatbeast_slap,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree
+ )
+
+/// Only do this if we are adjacent to target and have been mad at the same guy for at least 10 seconds
+/// That slap REALLY hurts
+/datum/ai_planning_subtree/targeted_mob_ability/vatbeast_slap
+ operational_datums = list(/datum/component/ai_target_timer)
+
+/datum/ai_planning_subtree/targeted_mob_ability/vatbeast_slap/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
+ var/mob/living/target = controller.blackboard[target_key]
+ if (!isliving(target) || !controller.pawn.Adjacent(target))
+ return
+ var/time_on_target = controller.blackboard[BB_BASIC_MOB_HAS_TARGET_TIME] || 0
+ if (time_on_target < 10 SECONDS)
+ return
+ return ..()
+
/// Ability that allows the owner to slap other mobs a short distance away.
/// For vatbeats, this ability is shared with the rider.
/datum/action/cooldown/tentacle_slap
@@ -56,13 +96,13 @@
overlay_icon_state = "bg_revenant_border"
button_icon = 'icons/mob/actions/actions_animal.dmi'
button_icon_state = "tentacle_slap"
- check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_INCAPACITATED
+ check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED
cooldown_time = 12 SECONDS
click_to_activate = TRUE
ranged_mousepointer = 'icons/effects/mouse_pointers/supplypod_target.dmi'
/datum/action/cooldown/tentacle_slap/update_button_name(atom/movable/screen/movable/action_button/button, force)
- if(button.our_hud.mymob != owner)
+ if (button.our_hud?.mymob != owner)
// For buttons given to mobs which are not our owner, give it this alt name
button.name = "Command Tentacle Slap"
button.desc = "Command your steed to slap a creature with its tentacles."
@@ -72,36 +112,34 @@
/datum/action/cooldown/tentacle_slap/set_click_ability(mob/on_who)
. = ..()
- if(!.)
+ if (!.)
return
-
to_chat(on_who, span_notice("You prepare your [on_who == owner ? "":"steed's "]pimp-tentacle. Left-click to slap a target!"))
/datum/action/cooldown/tentacle_slap/unset_click_ability(mob/on_who, refund_cooldown = TRUE)
. = ..()
- if(!.)
+ if (!.)
return
-
if(refund_cooldown)
to_chat(on_who, span_notice("You stop preparing your [on_who == owner ? "":"steed's "]pimp-tentacle."))
/datum/action/cooldown/tentacle_slap/InterceptClickOn(mob/living/clicker, params, atom/target)
// Check if we can slap
- if(!isliving(target) || target == owner)
+ if (!isliving(target) || target == owner)
return FALSE
- if(!owner.Adjacent(target))
+ if (!owner.Adjacent(target))
owner.balloon_alert(clicker, "too far!")
return FALSE
// Do the slap
. = ..()
- if(!.)
+ if (!.)
return FALSE
// Give feedback from the slap.
// Additional feedback for if a rider did it
- if(clicker != owner)
+ if (clicker != owner)
to_chat(clicker, span_notice("You command [owner] to slap [target] with its tentacles."))
return TRUE
diff --git a/code/modules/mob/living/basic/ruin_defender/blob_of_flesh.dm b/code/modules/mob/living/basic/ruin_defender/blob_of_flesh.dm
new file mode 100644
index 0000000000000..c27fd7f8d1ce1
--- /dev/null
+++ b/code/modules/mob/living/basic/ruin_defender/blob_of_flesh.dm
@@ -0,0 +1,72 @@
+/datum/ai_controller/basic_controller/fleshblob
+ blackboard = list(
+ BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
+ BB_AGGRO_RANGE = 7,
+ BB_TARGET_MINIMUM_STAT = HARD_CRIT,
+ )
+
+ ai_movement = /datum/ai_movement/basic_avoidance
+ idle_behavior = /datum/idle_behavior/idle_random_walk
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
+ )
+
+/mob/living/basic/fleshblob
+ name = "mass of flesh"
+ desc = "A moving slithering mass of flesh, seems to be very much in pain. Better avoid. It has no mouth and it must scream."
+ icon = 'icons/mob/simple/animal.dmi'
+ icon_state = "fleshblob"
+ icon_living = "fleshblob"
+ mob_size = MOB_SIZE_LARGE
+ gender = NEUTER
+ basic_mob_flags = DEL_ON_DEATH
+ faction = list(FACTION_HOSTILE, FACTION_MINING)
+ melee_damage_lower = 3
+ melee_damage_upper = 3
+ health = 160
+ maxHealth = 160
+ attack_sound = 'sound/items/weapons/bite.ogg'
+ attack_vis_effect = ATTACK_EFFECT_SMASH
+ attack_verb_continuous = "attempts to assimilate"
+ attack_verb_simple = "attempt to assimilate"
+ mob_biotypes = MOB_ORGANIC
+ speed = 8
+ combat_mode = TRUE
+ ai_controller = /datum/ai_controller/basic_controller/fleshblob
+
+/mob/living/basic/fleshblob/Initialize(mapload, obj/item/bodypart/limb)
+ . = ..()
+ grant_actions_by_list(list(/datum/action/consume/fleshblob))
+ ADD_TRAIT(src, TRAIT_STRONG_GRABBER, INNATE_TRAIT)
+ AddElement(/datum/element/death_drops, string_list(list(/obj/effect/gibspawner/generic)))
+ AddComponent(\
+ /datum/component/blood_walk, \
+ blood_type = /obj/effect/decal/cleanable/blood/trails, \
+ target_dir_change = TRUE,\
+ )
+
+/mob/living/basic/fleshblob/container_resist_act(mob/living/user)
+ . = ..()
+ if(!do_after(user, 4 SECONDS, target = src, timed_action_flags = IGNORE_TARGET_LOC_CHANGE|IGNORE_USER_LOC_CHANGE|IGNORE_INCAPACITATED))
+ return FALSE
+ var/datum/action/consume/fleshblob/consume = locate() in actions
+ if(isnull(consume))
+ return
+ consume.stop_consuming()
+
+/mob/living/basic/fleshblob/melee_attack(mob/living/target, list/modifiers, ignore_cooldown = FALSE)
+ if(target.loc == src || pulling == target)
+ return FALSE
+ . = ..()
+ if(!istype(target) || isnull(.)) // we deal 0 damage
+ return
+ start_pulling(target, state = GRAB_AGGRESSIVE)
+ var/datum/action/consume/fleshblob/consume = locate() in actions
+ if(isnull(consume))
+ return
+ consume.Trigger() // subtrees wouldve spammed this shit repeatedly anyway
+
+/datum/action/consume/fleshblob
+ devour_verb = "assimilate"
+ devour_time = 3 SECONDS
diff --git a/code/modules/mob/living/basic/ruin_defender/dark_wizard.dm b/code/modules/mob/living/basic/ruin_defender/dark_wizard.dm
new file mode 100644
index 0000000000000..6e88219244a26
--- /dev/null
+++ b/code/modules/mob/living/basic/ruin_defender/dark_wizard.dm
@@ -0,0 +1,77 @@
+/// Wizard-looking guy who basically just shoots you
+/mob/living/basic/dark_wizard
+ name = "Dark Wizard"
+ desc = "Killing amateurs since the dawn of times."
+ icon = 'icons/mob/simple/simple_human.dmi'
+ icon_state = "dark_wizard"
+ icon_living = "dark_wizard"
+ maxHealth = 50
+ health = 50
+ melee_damage_lower = 5
+ melee_damage_upper = 5
+ obj_damage = 20
+ basic_mob_flags = DEL_ON_DEATH
+ attack_verb_continuous = "staves"
+ attack_verb_simple = "stave"
+ combat_mode = TRUE
+ speak_emote = list("chants")
+ attack_sound = 'sound/items/weapons/bladeslice.ogg'
+ mob_biotypes = MOB_ORGANIC|MOB_HUMANOID
+ sentience_type = SENTIENCE_HUMANOID
+ faction = list(ROLE_WIZARD)
+ unsuitable_atmos_damage = 0
+ unsuitable_cold_damage = 0
+ unsuitable_heat_damage = 0
+ ai_controller = /datum/ai_controller/basic_controller/dark_wizard
+
+/mob/living/basic/dark_wizard/Initialize(mapload)
+ . = ..()
+
+ apply_dynamic_human_appearance(src, mob_spawn_path = /obj/effect/mob_spawn/corpse/human/wizard/dark, r_hand = /obj/item/staff)
+ add_traits(list(TRAIT_LAVA_IMMUNE, TRAIT_ASHSTORM_IMMUNE, TRAIT_SNOWSTORM_IMMUNE), INNATE_TRAIT)
+
+ var/corpse = string_list(list(/obj/effect/decal/remains/human))
+ AddElement(/datum/element/death_drops, corpse)
+ AddElement(/datum/element/footstep, FOOTSTEP_MOB_SHOE)
+ AddElement(/datum/element/ai_retaliate)
+
+ AddComponent(\
+ /datum/component/ranged_attacks,\
+ projectile_type = /obj/projectile/temp/earth_bolt,\
+ projectile_sound = 'sound/effects/magic/ethereal_enter.ogg',\
+ cooldown_time = 2 SECONDS,\
+ )
+
+ var/datum/action/cooldown/spell/teleport/radius_turf/blink/slow/escape_spell = new(src)
+ escape_spell.Grant(src)
+ AddComponent(\
+ /datum/component/revenge_ability,\
+ escape_spell,\
+ max_range = 3,\
+ target_self = TRUE,\
+ )
+
+ new /obj/item/clothing/head/wizard/hood(src) // Having this hat in our contents allows us to cast wizard spells
+
+/datum/ai_controller/basic_controller/dark_wizard
+ blackboard = list(
+ BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
+ )
+
+ ai_movement = /datum/ai_movement/basic_avoidance
+ idle_behavior = /datum/idle_behavior/idle_random_walk
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/target_retaliate, // If you get them to shoot each other it will start a wiz-war
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/maintain_distance,
+ /datum/ai_planning_subtree/ranged_skirmish/no_minimum,
+ )
+
+/// I don't know why an earth bolt freezes you but I guess it does
+/obj/projectile/temp/earth_bolt
+ name = "earth bolt"
+ icon_state = "declone"
+ damage = 4
+ damage_type = BURN
+ armor_flag = ENERGY
+ temperature = -100
diff --git a/code/modules/mob/living/basic/ruin_defender/mimic/mimic.dm b/code/modules/mob/living/basic/ruin_defender/mimic/mimic.dm
new file mode 100644
index 0000000000000..b8661c3c38ea8
--- /dev/null
+++ b/code/modules/mob/living/basic/ruin_defender/mimic/mimic.dm
@@ -0,0 +1,403 @@
+#define CANT_INSERT_FULL -1
+/// Mimics can't be made out of these objects
+GLOBAL_LIST_INIT(animatable_blacklist, typecacheof(list(
+ /obj/structure/table,
+ /obj/structure/cable,
+ /obj/structure/window,
+ /obj/structure/blob,
+)))
+
+/mob/living/basic/mimic
+ response_help_continuous = "touches"
+ response_help_simple = "touch"
+ response_disarm_continuous = "pushes"
+ response_disarm_simple = "push"
+ speed = 6
+ maxHealth = 250
+ health = 250
+ gender = NEUTER
+ mob_biotypes = NONE
+ pass_flags = PASSFLAPS
+ melee_damage_lower = 8
+ melee_damage_upper = 12
+ attack_sound = 'sound/items/weapons/punch1.ogg'
+ speak_emote = list("creaks")
+
+ unsuitable_cold_damage = 0
+ unsuitable_heat_damage = 0
+ unsuitable_atmos_damage = 0
+
+ faction = list(FACTION_MIMIC)
+ basic_mob_flags = DEL_ON_DEATH
+ combat_mode = TRUE
+ /// can we stun people on hit
+ var/knockdown_people = FALSE
+
+/mob/living/basic/mimic/melee_attack(mob/living/carbon/target, list/modifiers, ignore_cooldown)
+ . = ..()
+ if(!. || !knockdown_people || !prob(15) || !istype(target))
+ return
+ target.Paralyze(4 SECONDS)
+ target.visible_message(span_danger("\The [src] knocks down \the [target]!"), \
+ span_userdanger("\The [src] knocks you down!"))
+
+
+// ****************************
+// CRATE MIMIC
+// ****************************
+
+// Aggro when you try to open them. Will also pickup loot when spawns and drop it when dies.
+/mob/living/basic/mimic/crate
+ name = "crate"
+ desc = "A rectangular steel crate."
+ icon = 'icons/obj/storage/crates.dmi'
+ icon_state = "crate"
+ icon_living = "crate"
+ attack_verb_continuous = "bites"
+ attack_verb_simple = "bite"
+ speak_emote = list("clatters")
+ layer = BELOW_MOB_LAYER
+ ai_controller = /datum/ai_controller/basic_controller/mimic_crate
+ /// are we open
+ var/opened = FALSE
+ /// max mob size
+ var/max_mob_size = MOB_SIZE_HUMAN
+ /// can we be opened or closed, if false we can
+ var/locked = FALSE
+ /// action to lock us
+ var/datum/action/innate/mimic_lock/lock
+ ///A cap for items in the mimic. Prevents the mimic from eating enough stuff to cause lag when opened.
+ var/storage_capacity = 50
+ ///A cap for mobs. Mobs count towards the item cap. Same purpose as above.
+ var/mob_storage_capacity = 10
+
+// Pickup loot
+/mob/living/basic/mimic/crate/Initialize(mapload)
+ . = ..()
+ lock = new
+ lock.Grant(src)
+ ADD_TRAIT(src, TRAIT_AI_PAUSED, INNATE_TRAIT)
+ ai_controller?.set_ai_status(AI_STATUS_OFF) //start inert, let gullible people pull us into cargo or something and then go nuts when opened
+ if(mapload) //eat shit
+ for(var/obj/item/item in loc)
+ item.forceMove(src)
+
+/mob/living/basic/mimic/crate/Destroy()
+ lock = null
+ return ..()
+
+/mob/living/basic/mimic/crate/attack_hand(mob/living/carbon/human/user, list/modifiers)
+ if(user.combat_mode)
+ return ..()
+ if(trigger())
+ to_chat(user, span_danger("As you try to open [src] it [length(contents) ? "stiffens up and " : ""]nearly clamps down on your fingers!"))
+ return TRUE
+ toggle_open(user)
+ return TRUE
+
+/mob/living/basic/mimic/crate/melee_attack(mob/living/carbon/target, list/modifiers, ignore_cooldown)
+ . = ..()
+ toggle_open() // show our cool lid at the dumbass humans
+
+/mob/living/basic/mimic/crate/proc/trigger()
+ if(isnull(ai_controller) || client)
+ return FALSE
+ if(ai_controller.ai_status != AI_STATUS_OFF)
+ return FALSE
+ visible_message(span_danger("[src] starts to move!"))
+ REMOVE_TRAIT(src, TRAIT_AI_PAUSED, INNATE_TRAIT)
+ ai_controller.set_ai_status(AI_STATUS_ON)
+ if(length(contents))
+ locked = TRUE //if this was a crate with loot then we dont want people to just leftclick it to open it then bait it somewhere and steal its loot
+ return TRUE
+
+/mob/living/basic/mimic/crate/adjust_health(amount, updating_health = TRUE, forced = FALSE)
+ if(amount > 0)
+ trigger()
+ return ..()
+
+/mob/living/basic/mimic/crate/death()
+ var/obj/structure/closet/crate/lootbox = new(get_turf(src))
+ // Put loot in crate
+ for(var/obj/loot in src)
+ loot.forceMove(lootbox)
+ return ..()
+
+/mob/living/basic/mimic/crate/early_melee_attack(atom/target, list/modifiers, ignore_cooldown)
+ if(target == src)
+ toggle_open()
+ return FALSE
+ return ..()
+
+/mob/living/basic/mimic/crate/CanAllowThrough(atom/movable/mover, border_dir)
+ . = ..()
+ if(istype(mover, /obj/structure/closet))
+ return FALSE
+
+/**
+* Used to open and close the mimic
+*
+* Will insert tile contents into the mimic when closing
+* Will dump mimic contents into the time when opening
+* Does nothing if the mimic locked itself
+*/
+/mob/living/basic/mimic/crate/proc/toggle_open(mob/user)
+ if(locked)
+ if(user)
+ balloon_alert(user, "too stiff!")
+ return
+ if(!opened)
+ ADD_TRAIT(src, TRAIT_UNDENSE, MIMIC_TRAIT)
+ opened = TRUE
+ icon_state = "crateopen"
+ playsound(src, 'sound/machines/crate/crate_open.ogg', 50, TRUE)
+ for(var/atom/movable/movable as anything in src)
+ movable.forceMove(loc)
+ else
+ REMOVE_TRAIT(src, TRAIT_UNDENSE, MIMIC_TRAIT)
+ opened = FALSE
+ icon_state = "crate"
+ playsound(src, 'sound/machines/crate/crate_close.ogg', 50, TRUE)
+ for(var/atom/movable/movable as anything in get_turf(src))
+ if(movable != src && insert(movable) == CANT_INSERT_FULL)
+ playsound(src, 'sound/items/trayhit/trayhit2.ogg', 50, TRUE)
+ break
+
+/**
+* Called by toggle_open to put items inside the mimic when it's being closed
+*
+* Will return CANT_INSERT_FULL (-1) if the insertion fails due to the storage capacity of the mimic having been reached
+* Will return FALSE if insertion fails
+* Will return TRUE if insertion succeeds
+* Arguments:
+* * AM - item to be inserted
+*/
+/mob/living/basic/mimic/crate/proc/insert(atom/movable/movable)
+ if(contents.len >= storage_capacity)
+ return CANT_INSERT_FULL
+ if(insertion_allowed(movable))
+ movable.forceMove(src)
+ return TRUE
+ return FALSE
+
+/mob/living/basic/mimic/crate/proc/insertion_allowed(atom/movable/movable)
+ if(movable.anchored)
+ return FALSE
+ if(ismob(movable))
+ if(!isliving(movable)) //Don't let ghosts and such get trapped in the beast.
+ return FALSE
+ var/mob/living/living = movable
+ if(living.anchored || living.buckled || living.incorporeal_move || living.has_buckled_mobs())
+ return FALSE
+ if(living.mob_size > MOB_SIZE_TINY) // Tiny mobs are treated as items.
+ if(living.density || living.mob_size > max_mob_size)
+ return FALSE
+ var/mobs_stored = 0
+ for(var/mob/living/living_mob in contents)
+ mobs_stored++
+ if(mobs_stored >= mob_storage_capacity)
+ return FALSE
+ living.stop_pulling()
+
+ else if(istype(movable, /obj/structure/closet))
+ return FALSE
+ else if(isobj(movable))
+ if(movable.has_buckled_mobs())
+ return FALSE
+ else if(isitem(movable) && !HAS_TRAIT(movable, TRAIT_NODROP))
+ return TRUE
+ else
+ return FALSE
+ return TRUE
+
+/mob/living/basic/mimic/crate/xenobio
+ health = 210
+ maxHealth = 210
+ attack_verb_continuous = "bites"
+ attack_verb_simple = "bite"
+ speak_emote = list("clatters")
+ gold_core_spawnable = HOSTILE_SPAWN
+
+/datum/action/innate/mimic_lock
+ name = "Lock/Unlock"
+ desc = "Toggle preventing yourself from being opened or closed."
+ button_icon = 'icons/hud/radial.dmi'
+ button_icon_state = "radial_lock"
+ background_icon_state = "bg_default"
+ overlay_icon_state = "bg_default_border"
+
+/datum/action/innate/mimic/lock/Activate()
+ var/mob/living/basic/mimic/crate/mimic = owner
+ mimic.locked = !mimic.locked
+ if(!mimic.locked)
+ to_chat(mimic, span_warning("You loosen up, allowing yourself to be opened and closed."))
+ else
+ to_chat(mimic, span_warning("You stiffen up, preventing anyone from opening or closing you."))
+
+// ****************************
+// COPYING (actually imitates target object) MIMIC
+// ****************************
+
+/mob/living/basic/mimic/copy
+ health = 100
+ maxHealth = 100
+ mob_biotypes = MOB_SPECIAL
+ ai_controller = /datum/ai_controller/basic_controller/mimic_copy
+ /// our creator
+ var/datum/weakref/creator_ref
+ /// googly eyes overlay
+ var/static/mutable_appearance/googly_eyes = mutable_appearance('icons/mob/simple/mob.dmi', "googly_eyes")
+ /// do we overlay googly eyes over whatever we copy
+ var/overlay_googly_eyes = TRUE
+ /// do we take damage when we are not sentient and have no target
+ var/idledamage = TRUE
+ /// copied object
+ var/atom/movable/copied
+
+/mob/living/basic/mimic/copy/Initialize(mapload, obj/copy, mob/living/creator, destroy_original = FALSE, no_googlies = FALSE)
+ . = ..()
+ ADD_TRAIT(src, TRAIT_PERMANENTLY_MORTAL, INNATE_TRAIT) // They won't remember their original contents upon ressurection and would just be floating eyes
+ if (no_googlies)
+ overlay_googly_eyes = FALSE
+ CopyObject(copy, creator, destroy_original)
+
+/mob/living/basic/mimic/copy/Destroy()
+ creator_ref = null
+ copied = null
+ return ..()
+
+/mob/living/basic/mimic/copy/Life(seconds_per_tick = SSMOBS_DT, times_fired)
+ . = ..()
+ if(idledamage && !ckey && !ai_controller?.blackboard[BB_BASIC_MOB_CURRENT_TARGET]) //Objects eventually revert to normal if no one is around to terrorize
+ adjustBruteLoss(0.5 * seconds_per_tick)
+ for(var/mob/living/victim in contents) //a fix for animated statues from the flesh to stone spell
+ death()
+ return
+
+/mob/living/basic/mimic/copy/death()
+ for(var/atom/movable/movable as anything in src)
+ movable.forceMove(get_turf(src))
+ return ..()
+
+/mob/living/basic/mimic/copy/wabbajack(what_to_randomize, change_flags = WABBAJACK)
+ visible_message(span_warning("[src] resists polymorphing into a new creature!"))
+
+/mob/living/basic/mimic/copy/animate_atom_living(mob/living/owner)
+ change_owner(owner)
+
+/mob/living/basic/mimic/copy/Exited(atom/movable/gone, direction) // if our object gets deleted it calls Exited
+ . = ..()
+ if(QDELETED(src) || gone != copied)
+ return
+ death()
+
+/mob/living/basic/mimic/copy/proc/change_owner(mob/owner)
+ var/mob/creator_resolved = creator_ref?.resolve()
+ if(!creator_resolved)
+ creator_ref = null
+ if(isnull(owner) || creator_resolved == owner)
+ return
+ unfriend(creator_resolved)
+ befriend(owner)
+ creator_ref = WEAKREF(owner)
+
+/// Check whether this object can be copied. If destroy_original is true, this proc is ignored.
+/mob/living/basic/mimic/copy/proc/check_object(obj/target)
+ return ((isitem(target) || isstructure(target)) && !is_type_in_typecache(target, GLOB.animatable_blacklist))
+
+/mob/living/basic/mimic/copy/proc/CopyObject(obj/original, mob/living/user, destroy_original = FALSE)
+ if(!destroy_original && !check_object(original))
+ return FALSE
+ if(!destroy_original)
+ original.forceMove(src)
+ copied = original
+ CopyObjectVisuals(original)
+ if (overlay_googly_eyes)
+ add_overlay(googly_eyes)
+ if(isstructure(original) || ismachinery(original))
+ health = (anchored * 50) + 50
+ if(original.density && original.anchored)
+ knockdown_people = TRUE
+ melee_damage_lower *= 2
+ melee_damage_upper *= 2
+ else if(isitem(original))
+ var/obj/item/I = original
+ health = 15 * I.w_class
+ melee_damage_lower = 2 + I.force
+ melee_damage_upper = 2 + I.force
+ maxHealth = health
+ if(user)
+ change_owner(user)
+ if(destroy_original)
+ qdel(original)
+ return TRUE
+
+/// Copies the object visually including name and desc
+/mob/living/basic/mimic/copy/proc/CopyObjectVisuals(obj/original)
+ name = original.name
+ desc = original.desc
+ icon = original.icon
+ icon_state = original.icon_state
+ icon_living = icon_state
+ copy_overlays(original, cut_old = TRUE)
+
+/mob/living/basic/mimic/copy/machine
+ ai_controller = /datum/ai_controller/basic_controller/mimic_copy/machine
+ faction = list(FACTION_MIMIC, FACTION_SILICON)
+
+/mob/living/basic/mimic/copy/ranged
+ icon = 'icons/turf/floors.dmi'
+ icon_state = "invisible"
+ ai_controller = /datum/ai_controller/basic_controller/mimic_copy/gun
+
+/mob/living/basic/mimic/copy/ranged/Destroy()
+ vis_contents.Cut()
+ return ..()
+
+/mob/living/basic/mimic/copy/ranged/RangedAttack(atom/atom_target, modifiers)
+ INVOKE_ASYNC(src, PROC_REF(fire_gun), atom_target, modifiers)
+
+/mob/living/basic/mimic/copy/ranged/proc/fire_gun(atom/target, modifiers) // i cant find any better way to do this
+ var/obj/item/gun/gun = locate() in contents
+ if(!gun.can_shoot())
+ if(istype(gun, /obj/item/gun/ballistic))
+ var/obj/item/gun/ballistic/ballistic = gun
+ if(!ballistic.chambered || ballistic.bolt_locked)
+ ballistic.rack() //we racked so both checked variables should be something else now
+ // do we have nothing chambered/chambered is spent AND we have no mag or our mag is empty
+ if(!ballistic.chambered?.loaded_projectile && magazine_useless(gun)) // ran out of ammo
+ ai_controller?.set_blackboard_key(BB_GUNMIMIC_GUN_EMPTY, TRUE) //BANZAIIIIIIII
+ ai_controller?.CancelActions()
+ else //if we cant fire we probably like ran out of energy or magic charges or whatever the hell idk
+ ai_controller?.set_blackboard_key(BB_GUNMIMIC_GUN_EMPTY, TRUE)
+ ai_controller?.CancelActions() // Stop our firing behavior so we can plan melee
+ else
+ ai_controller?.set_blackboard_key(BB_GUNMIMIC_GUN_EMPTY, FALSE)
+ gun.fire_gun(target, user = src, flag = FALSE, params = modifiers) //still make like a cool click click sound if trying to fire empty
+
+/mob/living/basic/mimic/copy/ranged/proc/magazine_useless(obj/item/gun/ballistic/ballistic)
+ if(isnull(ballistic.magazine) || !length(ballistic.magazine.stored_ammo))
+ return TRUE
+ // is there ATLEAST one unspent round (for the sake of revolvers or a magazine somehow having spent rounds in it)
+ for(var/obj/item/ammo_casing/thing as anything in ballistic.magazine.stored_ammo)
+ if(ispath(thing))
+ return FALSE // unspent
+ if(!isnull(thing.loaded_projectile))
+ return FALSE //unspent
+ return TRUE
+
+/mob/living/basic/mimic/copy/ranged/CopyObject(obj/item/gun/original, mob/living/creator, destroy_original = 0)
+ if(..())
+ obj_damage = 0
+ melee_damage_upper = original.force
+ melee_damage_lower = original.force - max(0, (original.force / 2))
+
+/mob/living/basic/mimic/copy/ranged/CopyObjectVisuals(obj/original)
+ name = original.name
+ desc = original.desc
+ vis_contents += original
+
+/mob/living/basic/mimic/copy/ranged/can_use_guns(obj/item/gun)
+ return TRUE
+
+#undef CANT_INSERT_FULL
diff --git a/code/modules/mob/living/basic/ruin_defender/mimic/mimic_ai.dm b/code/modules/mob/living/basic/ruin_defender/mimic/mimic_ai.dm
new file mode 100644
index 0000000000000..9a673b49ec681
--- /dev/null
+++ b/code/modules/mob/living/basic/ruin_defender/mimic/mimic_ai.dm
@@ -0,0 +1,86 @@
+/datum/ai_controller/basic_controller/mimic_crate
+ idle_behavior = null
+ blackboard = list(
+ BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
+ )
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
+ )
+
+/datum/ai_controller/basic_controller/mimic_copy
+ blackboard = list(
+ BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
+ )
+
+ ai_movement = /datum/ai_movement/basic_avoidance
+ idle_behavior = /datum/idle_behavior/idle_random_walk
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/attack_obstacle_in_path,
+ /datum/ai_planning_subtree/random_speech/when_has_target/mimic,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
+ )
+
+/datum/ai_controller/basic_controller/mimic_copy/machine
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/attack_obstacle_in_path,
+ /datum/ai_planning_subtree/random_speech/when_has_target/mimic_machine,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
+ )
+
+/datum/ai_planning_subtree/random_speech/when_has_target
+ /// target key
+ var/target_key = BB_BASIC_MOB_CURRENT_TARGET
+
+
+/datum/ai_planning_subtree/random_speech/when_has_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
+ if(!controller.blackboard_key_exists(target_key))
+ return
+ return ..()
+
+
+/datum/ai_planning_subtree/random_speech/when_has_target/mimic
+ speech_chance = 30
+ emote_hear = list("growls.")
+
+/datum/ai_planning_subtree/random_speech/when_has_target/mimic_machine
+ speech_chance = 7
+ emote_hear = list()
+ speak = list(
+ "HUMANS ARE IMPERFECT!",
+ "YOU SHALL BE ASSIMILATED!",
+ "YOU ARE HARMING YOURSELF",
+ "You have been deemed hazardous. Will you comply?",
+ "My logic is undeniable.",
+ "One of us.",
+ "FLESH IS WEAK",
+ "THIS ISN'T WAR, THIS IS EXTERMINATION!",
+ )
+
+/datum/ai_planning_subtree/random_speech/when_has_target/mimic/gun
+ emote_see = list("aims menacingly!")
+
+/datum/ai_controller/basic_controller/mimic_copy/gun
+ blackboard = list(
+ BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
+ BB_GUNMIMIC_GUN_EMPTY = FALSE,
+ )
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/random_speech/when_has_target/mimic/gun,
+ /datum/ai_planning_subtree/gun_mimic_attack_subtree,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
+ )
+
+/datum/ai_planning_subtree/gun_mimic_attack_subtree
+
+/datum/ai_planning_subtree/gun_mimic_attack_subtree/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
+ . = ..()
+ if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET))
+ return
+ if(controller.blackboard[BB_GUNMIMIC_GUN_EMPTY])
+ return
+ controller.queue_behavior(/datum/ai_behavior/basic_ranged_attack/avoid_friendly_fire, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION)
+ return SUBTREE_RETURN_FINISH_PLANNING //we are going into battle...no distractions.
diff --git a/code/modules/mob/living/basic/ruin_defender/zombie.dm b/code/modules/mob/living/basic/ruin_defender/zombie.dm
new file mode 100644
index 0000000000000..b77920af8d154
--- /dev/null
+++ b/code/modules/mob/living/basic/ruin_defender/zombie.dm
@@ -0,0 +1,93 @@
+/// Everyone knows what a zombie is
+/mob/living/basic/zombie
+ name = "Shambling Corpse"
+ desc = "When there is no more room in hell, the dead will walk in outer space."
+ icon = 'icons/mob/simple/simple_human.dmi'
+ mob_biotypes = MOB_ORGANIC|MOB_HUMANOID
+ sentience_type = SENTIENCE_HUMANOID
+ maxHealth = 100
+ health = 100
+ melee_damage_lower = 21
+ melee_damage_upper = 21
+ attack_verb_continuous = "bites"
+ attack_verb_simple = "bite"
+ attack_sound = 'sound/effects/hallucinations/growl1.ogg'
+ attack_vis_effect = ATTACK_EFFECT_BITE
+ combat_mode = TRUE
+ speed = 4
+ status_flags = CANPUSH
+ death_message = "rapidly decays into a pile of bones!"
+ unsuitable_atmos_damage = 0
+ unsuitable_cold_damage = 0
+ faction = list(FACTION_HOSTILE)
+ basic_mob_flags = DEL_ON_DEATH
+ ai_controller = /datum/ai_controller/basic_controller/zombie
+ /// Outfit the zombie spawns with for visuals.
+ var/outfit = /datum/outfit/corpse_doctor
+ /// Chance to spread zombieism on hit
+ /// Only for admins because we don't actually want romerol to get into the round from space ruins generally speaking
+ var/infection_chance = 0
+
+/mob/living/basic/zombie/Initialize(mapload)
+ . = ..()
+ apply_dynamic_human_appearance(src, outfit, /datum/species/zombie, bloody_slots = ITEM_SLOT_OCLOTHING)
+ AddElement(/datum/element/death_drops, string_list(list(/obj/effect/decal/remains/human)))
+
+/mob/living/basic/zombie/melee_attack(atom/target, list/modifiers, ignore_cooldown)
+ . = ..()
+ if (!. || !infection_chance || !ishuman(target) || !prob(infection_chance))
+ return
+ try_to_zombie_infect(target)
+
+/// Weaker variant used if you want to put more of them in one place, won't attack obstacles
+/mob/living/basic/zombie/rotten
+ name = "Rotting Carcass"
+ desc = "This undead fiend looks to be badly decomposed."
+ health = 60
+ melee_damage_lower = 11
+ melee_damage_upper = 11
+ ai_controller = /datum/ai_controller/basic_controller/zombie/stupid
+
+/mob/living/basic/zombie/rotten/assistant
+ outfit = /datum/outfit/corpse_assistant
+
+/datum/outfit/corpse_doctor
+ name = "Corpse Doctor"
+ suit = /obj/item/clothing/suit/toggle/labcoat
+ uniform = /obj/item/clothing/under/rank/medical/doctor
+ shoes = /obj/item/clothing/shoes/sneakers/white
+ back = /obj/item/storage/backpack/medic
+
+/datum/outfit/corpse_assistant
+ name = "Corpse Assistant"
+ mask = /obj/item/clothing/mask/gas
+ uniform = /obj/item/clothing/under/color/grey
+ shoes = /obj/item/clothing/shoes/sneakers/black
+ back = /obj/item/storage/backpack
+
+/datum/ai_planning_subtree/random_speech/zombie
+ speech_chance = 1
+ emote_hear = list("groans.", "moans.", "grunts.")
+ emote_see = list("twitches.", "shudders.")
+
+/datum/ai_controller/basic_controller/zombie
+ blackboard = list(
+ BB_TARGET_MINIMUM_STAT = HARD_CRIT,
+ BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
+ )
+
+ ai_movement = /datum/ai_movement/basic_avoidance
+ idle_behavior = /datum/idle_behavior/idle_random_walk
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/random_speech/zombie,
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/attack_obstacle_in_path,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
+ )
+
+/datum/ai_controller/basic_controller/zombie/stupid
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/random_speech/zombie,
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
+ )
diff --git a/code/modules/mob/living/basic/slime/actions.dm b/code/modules/mob/living/basic/slime/actions.dm
index c297f48d14e6c..a3168d8143746 100644
--- a/code/modules/mob/living/basic/slime/actions.dm
+++ b/code/modules/mob/living/basic/slime/actions.dm
@@ -77,6 +77,7 @@
///Splits the slime into multiple children if possible
/mob/living/basic/slime/proc/reproduce()
+
if(stat != CONSCIOUS)
balloon_alert(src, "not conscious!")
return
@@ -92,6 +93,19 @@
balloon_alert(src, "need growth!")
return
+ var/list/friends_list = list()
+ for(var/mob/living/basic/slime/friend in loc)
+ if(QDELETED(friend))
+ continue
+ if(friend == src)
+ continue
+ friends_list += friend
+
+ overcrowded = length(friends_list) >= SLIME_OVERCROWD_AMOUNT
+ if(overcrowded)
+ balloon_alert(src, "overcrowded!")
+ return
+
var/list/babies = list()
var/new_nutrition = round(nutrition * 0.9)
var/new_powerlevel = round(powerlevel / 4)
diff --git a/code/modules/mob/living/basic/slime/slime.dm b/code/modules/mob/living/basic/slime/slime.dm
index 8623f69b0e4cd..8f80e24f6998b 100644
--- a/code/modules/mob/living/basic/slime/slime.dm
+++ b/code/modules/mob/living/basic/slime/slime.dm
@@ -66,6 +66,9 @@
///Our slime's current mood
var/current_mood = SLIME_MOOD_NONE
+ ///If the slime is currently overcrowded and unable to reproduce.
+ var/overcrowded = FALSE
+
///The number of /obj/item/slime_extract's the slime has left inside
var/cores = 1
///Chance of mutating, should be between 25 and 35
@@ -243,6 +246,8 @@
if(SLIME_MAX_POWER)
. += span_boldwarning("It is radiating with massive levels of electrical activity!")
+ if(overcrowded)
+ . += span_warning("It seems too overcroweded to properly reproduce!")
///Changes the slime's current life state
/mob/living/basic/slime/proc/set_life_stage(new_life_stage = SLIME_LIFE_STAGE_BABY)
diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm
index 1d72b9b1f17ec..8166612a87333 100644
--- a/code/modules/mob/living/carbon/human/human_helpers.dm
+++ b/code/modules/mob/living/carbon/human/human_helpers.dm
@@ -45,7 +45,7 @@
return signal_face // no need to null-check, because force_set will always set a signal_face
var/face_name = !isnull(signal_face) ? signal_face : get_face_name("")
var/id_name = !isnull(signal_id) ? signal_id : get_id_name("")
- if (force_real_name)
+ if(force_real_name)
var/fake_name
if (face_name && face_name != real_name)
fake_name = face_name
diff --git a/code/modules/mob/living/carbon/human/species_types/monkeys.dm b/code/modules/mob/living/carbon/human/species_types/monkeys.dm
index c953274646245..aa58299115821 100644
--- a/code/modules/mob/living/carbon/human/species_types/monkeys.dm
+++ b/code/modules/mob/living/carbon/human/species_types/monkeys.dm
@@ -166,7 +166,7 @@
else
monkey_brain.tripping = TRUE
background_icon_state = "bg_default_on"
- to_chat(monkey_brain.owner, span_notice("You will now stumble while while colliding with people who are in combat mode."))
+ to_chat(monkey_brain.owner, span_notice("You will now stumble while colliding with people who are in combat mode."))
build_all_button_icons()
/obj/item/organ/brain/primate/on_mob_insert(mob/living/carbon/primate)
diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm
index 25ccafe26c0f1..cb951fac5d6af 100644
--- a/code/modules/mob/living/silicon/ai/freelook/eye.dm
+++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm
@@ -128,6 +128,12 @@
return
update_ai_detect_hud()
+///Called when the AI shiftclicks on something to examinate it.
+/mob/eye/camera/ai/proc/examinate_check(mob/user, atom/source)
+ SIGNAL_HANDLER
+ if(user.client.eye == src)
+ return COMPONENT_ALLOW_EXAMINATE
+
/*----------------------------------------------------*/
/atom/proc/move_camera_by_click()
@@ -199,6 +205,7 @@
eyeobj.ai = src
eyeobj.name = "[name] (AI Eye)"
eyeobj.setLoc(loc, TRUE)
+ eyeobj.RegisterSignal(src, COMSIG_CLICK_SHIFT, TYPE_PROC_REF(/mob/eye/camera/ai, examinate_check))
set_eyeobj_visible(TRUE)
/mob/living/silicon/ai/proc/set_eyeobj_visible(state = TRUE)
diff --git a/code/modules/mob/living/simple_animal/hostile/dark_wizard.dm b/code/modules/mob/living/simple_animal/hostile/dark_wizard.dm
deleted file mode 100644
index e2c2aca2693ce..0000000000000
--- a/code/modules/mob/living/simple_animal/hostile/dark_wizard.dm
+++ /dev/null
@@ -1,46 +0,0 @@
-/mob/living/simple_animal/hostile/dark_wizard
- name = "Dark Wizard"
- desc = "Killing amateurs since the dawn of times."
- icon = 'icons/mob/simple/simple_human.dmi'
- icon_state = "dark_wizard"
- icon_living = "dark_wizard"
- move_to_delay = 10
- projectiletype = /obj/projectile/temp/earth_bolt
- projectilesound = 'sound/effects/magic/ethereal_enter.ogg'
- ranged = TRUE
- ranged_message = "earth bolts"
- ranged_cooldown_time = 20
- maxHealth = 50
- health = 50
- harm_intent_damage = 5
- obj_damage = 20
- melee_damage_lower = 5
- melee_damage_upper = 5
- attack_verb_continuous = "staves"
- combat_mode = TRUE
- speak_emote = list("chants")
- attack_sound = 'sound/items/weapons/bladeslice.ogg'
- aggro_vision_range = 9
- turns_per_move = 5
- mob_biotypes = MOB_ORGANIC|MOB_HUMANOID
- sentience_type = SENTIENCE_HUMANOID
- faction = list(ROLE_WIZARD)
- footstep_type = FOOTSTEP_MOB_SHOE
- weather_immunities = list(TRAIT_LAVA_IMMUNE, TRAIT_ASHSTORM_IMMUNE)
- minbodytemp = 0
- maxbodytemp = INFINITY
- atmos_requirements = null
- loot = list(/obj/effect/decal/remains/human)
- del_on_death = TRUE
-
-/mob/living/simple_animal/hostile/dark_wizard/Initialize(mapload)
- . = ..()
- apply_dynamic_human_appearance(src, mob_spawn_path = /obj/effect/mob_spawn/corpse/human/wizard/dark, r_hand = /obj/item/staff)
-
-/obj/projectile/temp/earth_bolt
- name = "earth bolt"
- icon_state = "declone"
- damage = 4
- damage_type = BURN
- armor_flag = ENERGY
- temperature = -100 // closer to the old temp loss
diff --git a/code/modules/mob/living/simple_animal/hostile/mimic.dm b/code/modules/mob/living/simple_animal/hostile/mimic.dm
deleted file mode 100644
index 0d8c1d86bdd40..0000000000000
--- a/code/modules/mob/living/simple_animal/hostile/mimic.dm
+++ /dev/null
@@ -1,423 +0,0 @@
-/// Mimics can't be made out of these objects
-GLOBAL_LIST_INIT(animatable_blacklist, typecacheof(list(
- /obj/structure/table,
- /obj/structure/cable,
- /obj/structure/window,
- /obj/structure/blob,
-)))
-
-/mob/living/simple_animal/hostile/mimic
- name = "crate"
- desc = "A rectangular steel crate."
- icon = 'icons/obj/storage/crates.dmi'
- icon_state = "crate"
- icon_living = "crate"
-
- response_help_continuous = "touches"
- response_help_simple = "touch"
- response_disarm_continuous = "pushes"
- response_disarm_simple = "push"
- speed = 0
- maxHealth = 250
- health = 250
- gender = NEUTER
- mob_biotypes = NONE
- pass_flags = PASSFLAPS
-
- harm_intent_damage = 5
- melee_damage_lower = 8
- melee_damage_upper = 12
- attack_sound = 'sound/items/weapons/punch1.ogg'
- emote_taunt = list("growls")
- speak_emote = list("creaks")
- taunt_chance = 30
-
- atmos_requirements = null
- minbodytemp = 0
-
- faction = list(FACTION_MIMIC)
- move_to_delay = 9
- del_on_death = 1
- ///A cap for items in the mimic. Prevents the mimic from eating enough stuff to cause lag when opened.
- var/storage_capacity = 50
- ///A cap for mobs. Mobs count towards the item cap. Same purpose as above.
- var/mob_storage_capacity = 10
-
-// Aggro when you try to open them. Will also pickup loot when spawns and drop it when dies.
-/mob/living/simple_animal/hostile/mimic/crate
- attack_verb_continuous = "bites"
- attack_verb_simple = "bite"
- speak_emote = list("clatters")
- stop_automated_movement = 1
- wander = 0
- var/attempt_open = FALSE
-
-// Pickup loot
-/mob/living/simple_animal/hostile/mimic/crate/Initialize(mapload)
- . = ..()
- if(mapload) //eat shit
- for(var/obj/item/I in loc)
- I.forceMove(src)
-
-/mob/living/simple_animal/hostile/mimic/crate/DestroyPathToTarget()
- ..()
- if(prob(90))
- icon_state = "[initial(icon_state)]open"
- else
- icon_state = initial(icon_state)
-
-/mob/living/simple_animal/hostile/mimic/crate/ListTargets()
- if(attempt_open)
- return ..()
- return ..(1)
-
-/mob/living/simple_animal/hostile/mimic/crate/FindTarget()
- . = ..()
- if(.)
- trigger()
-
-/mob/living/simple_animal/hostile/mimic/crate/AttackingTarget(atom/attacked_target)
- . = ..()
- if(.)
- icon_state = initial(icon_state)
- if(prob(15) && iscarbon(target))
- var/mob/living/carbon/C = target
- C.Paralyze(40)
- C.visible_message(span_danger("\The [src] knocks down \the [C]!"), \
- span_userdanger("\The [src] knocks you down!"))
-
-/mob/living/simple_animal/hostile/mimic/crate/proc/trigger()
- if(!attempt_open)
- visible_message("[src] starts to move!")
- attempt_open = TRUE
-
-/mob/living/simple_animal/hostile/mimic/crate/adjustHealth(amount, updating_health = TRUE, forced = FALSE)
- trigger()
- . = ..()
-
-/mob/living/simple_animal/hostile/mimic/crate/LoseTarget()
- ..()
- icon_state = initial(icon_state)
-
-/mob/living/simple_animal/hostile/mimic/crate/death()
- var/obj/structure/closet/crate/C = new(get_turf(src))
- // Put loot in crate
- for(var/obj/O in src)
- O.forceMove(C)
- ..()
-
-/mob/living/simple_animal/hostile/mimic/copy
- health = 100
- maxHealth = 100
- mob_biotypes = MOB_SPECIAL
- var/mob/living/creator = null // the creator
- var/destroy_objects = 0
- var/knockdown_people = 0
- var/static/mutable_appearance/googly_eyes = mutable_appearance('icons/mob/simple/mob.dmi', "googly_eyes")
- var/overlay_googly_eyes = TRUE
- var/idledamage = TRUE
-
-/mob/living/simple_animal/hostile/mimic/copy/Initialize(mapload, obj/copy, mob/living/creator, destroy_original = 0, no_googlies = FALSE)
- . = ..()
- ADD_TRAIT(src, TRAIT_PERMANENTLY_MORTAL, INNATE_TRAIT) // They won't remember their original contents upon ressurection and would just be floating eyes
- if (no_googlies)
- overlay_googly_eyes = FALSE
- CopyObject(copy, creator, destroy_original)
-
-/mob/living/simple_animal/hostile/mimic/copy/Life(seconds_per_tick = SSMOBS_DT, times_fired)
- ..()
- if(idledamage && !target && !ckey) //Objects eventually revert to normal if no one is around to terrorize
- adjustBruteLoss(0.5 * seconds_per_tick)
- for(var/mob/living/M in contents) //a fix for animated statues from the flesh to stone spell
- death()
-
-/mob/living/simple_animal/hostile/mimic/copy/death()
- for(var/atom/movable/M in src)
- M.forceMove(get_turf(src))
- ..()
-
-/mob/living/simple_animal/hostile/mimic/copy/ListTargets()
- . = ..()
- return . - creator
-
-/mob/living/simple_animal/hostile/mimic/copy/wabbajack(what_to_randomize, change_flags = WABBAJACK)
- visible_message(span_warning("[src] resists polymorphing into a new creature!"))
-
-/mob/living/simple_animal/hostile/mimic/copy/animate_atom_living(mob/living/owner)
- change_owner(owner)
-
-/mob/living/simple_animal/hostile/mimic/copy/proc/change_owner(mob/owner)
- if(isnull(owner) || creator == owner)
- return
- LoseTarget()
- creator = owner
- faction |= REF(owner)
-
-/mob/living/simple_animal/hostile/mimic/copy/proc/check_object(obj/target)
- return ((isitem(target) || isstructure(target)) && !is_type_in_typecache(target, GLOB.animatable_blacklist))
-
-/mob/living/simple_animal/hostile/mimic/copy/proc/CopyObject(obj/O, mob/living/user, destroy_original = 0)
- if(destroy_original || check_object(O))
- O.forceMove(src)
- name = O.name
- desc = O.desc
- icon = O.icon
- icon_state = O.icon_state
- icon_living = icon_state
- copy_overlays(O)
- if (overlay_googly_eyes)
- add_overlay(googly_eyes)
- if(isstructure(O) || ismachinery(O))
- health = (anchored * 50) + 50
- destroy_objects = 1
- if(O.density && O.anchored)
- knockdown_people = 1
- melee_damage_lower *= 2
- melee_damage_upper *= 2
- else if(isitem(O))
- var/obj/item/I = O
- health = 15 * I.w_class
- melee_damage_lower = 2 + I.force
- melee_damage_upper = 2 + I.force
- move_to_delay = 2 * I.w_class + 1
- maxHealth = health
- if(user)
- creator = user
- faction += "[REF(creator)]" // very unique
- if(destroy_original)
- qdel(O)
- return 1
-
-/mob/living/simple_animal/hostile/mimic/copy/DestroySurroundings()
- if(destroy_objects)
- ..()
-
-/mob/living/simple_animal/hostile/mimic/copy/AttackingTarget(atom/attacked_target)
- . = ..()
- if(knockdown_people && . && prob(15) && iscarbon(target))
- var/mob/living/carbon/C = target
- C.Paralyze(40)
- C.visible_message(span_danger("\The [src] knocks down \the [C]!"), \
- span_userdanger("\The [src] knocks you down!"))
-
-/mob/living/simple_animal/hostile/mimic/copy/machine
- speak = list(
- "HUMANS ARE IMPERFECT!", "YOU SHALL BE ASSIMILATED!", "YOU ARE HARMING YOURSELF", "You have been deemed hazardous. Will you comply?", \
- "My logic is undeniable.", "One of us.", "FLESH IS WEAK", "THIS ISN'T WAR, THIS IS EXTERMINATION!",
- )
- speak_chance = 7
-
-/mob/living/simple_animal/hostile/mimic/copy/machine/CanAttack(atom/the_target)
- if(the_target == creator) // Don't attack our creator AI.
- return 0
- if(iscyborg(the_target))
- var/mob/living/silicon/robot/R = the_target
- if(R.connected_ai == creator) // Only attack robots that aren't synced to our creator AI.
- return 0
- return ..()
-
-/mob/living/simple_animal/hostile/mimic/copy/ranged
- var/obj/item/gun/TrueGun = null
- var/obj/item/gun/magic/Zapstick
- var/obj/item/gun/ballistic/Pewgun
- var/obj/item/gun/energy/Zapgun
-
-/mob/living/simple_animal/hostile/mimic/copy/ranged/CopyObject(obj/O, mob/living/creator, destroy_original = 0)
- if(..())
- emote_see = list("aims menacingly")
- obj_damage = 0
- environment_smash = ENVIRONMENT_SMASH_NONE //needed? seems weird for them to do so
- ranged = 1
- retreat_distance = 1 //just enough to shoot
- minimum_distance = 6
- var/obj/item/gun/G = O
- melee_damage_upper = G.force
- melee_damage_lower = G.force - max(0, (G.force / 2))
- move_to_delay = 2 * G.w_class + 1
- projectilesound = G.fire_sound
- TrueGun = G
- if(istype(G, /obj/item/gun/magic))
- Zapstick = G
- var/obj/item/ammo_casing/magic/M = Zapstick.ammo_type
- projectiletype = initial(M.projectile_type)
- if(istype(G, /obj/item/gun/ballistic))
- Pewgun = G
- var/obj/item/ammo_box/magazine/M = Pewgun.spawn_magazine_type
- casingtype = initial(M.ammo_type)
- if(istype(G, /obj/item/gun/energy))
- Zapgun = G
- var/selectfiresetting = Zapgun.select
- var/obj/item/ammo_casing/energy/E = Zapgun.ammo_type[selectfiresetting]
- projectiletype = initial(E.projectile_type)
-
-/mob/living/simple_animal/hostile/mimic/copy/ranged/OpenFire(the_target)
- if(Zapgun)
- if(Zapgun.cell)
- var/obj/item/ammo_casing/energy/shot = Zapgun.ammo_type[Zapgun.select]
- if(Zapgun.cell.charge >= shot.e_cost)
- Zapgun.cell.use(shot.e_cost)
- Zapgun.update_appearance()
- ..()
- else if(Zapstick)
- if(Zapstick.charges)
- Zapstick.charges--
- Zapstick.update_appearance()
- ..()
- else if(Pewgun)
- if(Pewgun.chambered)
- if(Pewgun.chambered.loaded_projectile)
- qdel(Pewgun.chambered.loaded_projectile)
- Pewgun.chambered.loaded_projectile = null //because qdel takes too long, ensures icon update
- Pewgun.chambered.update_appearance()
- ..()
- else
- visible_message(span_danger("The [src] clears a jam!"))
- Pewgun.chambered.forceMove(loc) //rip revolver immersions, blame shotgun snowflake procs
- Pewgun.chambered = null
- if(Pewgun.magazine && Pewgun.magazine.stored_ammo.len)
- Pewgun.chambered = Pewgun.magazine.get_round()
- Pewgun.chambered.forceMove(Pewgun)
- Pewgun.update_appearance()
- else if(Pewgun.magazine && Pewgun.magazine.stored_ammo.len) //only true for pumpguns i think
- Pewgun.chambered = Pewgun.magazine.get_round()
- Pewgun.chambered.forceMove(Pewgun)
- visible_message(span_danger("The [src] cocks itself!"))
- else
- ranged = 0 //BANZAIIII
- retreat_distance = 0
- minimum_distance = 1
- return
- icon_state = TrueGun.icon_state
- icon_living = TrueGun.icon_state
-
-/mob/living/simple_animal/hostile/mimic/xenobio
- health = 210
- maxHealth = 210
- attack_verb_continuous = "bites"
- attack_verb_simple = "bite"
- speak_emote = list("clatters")
- gold_core_spawnable = HOSTILE_SPAWN
- var/opened = FALSE
- var/open_sound = 'sound/machines/crate/crate_open.ogg'
- var/close_sound = 'sound/machines/crate/crate_close.ogg'
- ///sound played when the mimic attempts to eat more items than it can
- var/full_sound = 'sound/items/trayhit/trayhit2.ogg'
- var/max_mob_size = MOB_SIZE_HUMAN
- var/locked = FALSE
- var/datum/action/innate/mimic/lock/lock
-
-/mob/living/simple_animal/hostile/mimic/xenobio/Initialize(mapload)
- . = ..()
- lock = new
- lock.Grant(src)
-
-/mob/living/simple_animal/hostile/mimic/xenobio/AttackingTarget(atom/attacked_target)
- if(src == target)
- toggle_open()
- return
- return ..()
-
-/mob/living/simple_animal/hostile/mimic/xenobio/attack_hand(mob/living/carbon/human/user, list/modifiers)
- . = ..()
- if(user.combat_mode)
- return
- toggle_open()
-
-/mob/living/simple_animal/hostile/mimic/xenobio/death()
- var/obj/structure/closet/crate/C = new(get_turf(src))
- // Put loot in crate
- for(var/atom/movable/AM in src)
- AM.forceMove(C)
- return ..()
-
-/mob/living/simple_animal/hostile/mimic/xenobio/CanAllowThrough(atom/movable/mover, border_dir)
- . = ..()
- if(istype(mover, /obj/structure/closet))
- return FALSE
-/**
-* Used to open and close the mimic
-*
-* Will insert tile contents into the mimic when closing
-* Will dump mimic contents into the time when opening
-* Does nothing if the mimic locked itself
-*/
-/mob/living/simple_animal/hostile/mimic/xenobio/proc/toggle_open()
- if(locked)
- return
- if(!opened)
- ADD_TRAIT(src, TRAIT_UNDENSE, MIMIC_TRAIT)
- opened = TRUE
- icon_state = "crateopen"
- playsound(src, open_sound, 50, TRUE)
- for(var/atom/movable/AM in src)
- AM.forceMove(loc)
- else
- REMOVE_TRAIT(src, TRAIT_UNDENSE, MIMIC_TRAIT)
- opened = FALSE
- icon_state = "crate"
- playsound(src, close_sound, 50, TRUE)
- for(var/atom/movable/AM in get_turf(src))
- if(AM != src && insert(AM) == -1)
- playsound(src, full_sound, 50, TRUE)
- break
-/**
-* Called by toggle_open to put items inside the mimic when it's being closed
-*
-* Will return -1 if the insertion fails due to the storage capacity of the mimic having been reached
-* Will return FALSE if insertion fails
-* Will return TRUE if insertion succeeds
-* Arguments:
-* * AM - item to be inserted
-*/
-/mob/living/simple_animal/hostile/mimic/xenobio/proc/insert(atom/movable/AM)
- if(contents.len >= storage_capacity)
- return -1
- if(insertion_allowed(AM))
- AM.forceMove(src)
- return TRUE
- else
- return FALSE
-
-/mob/living/simple_animal/hostile/mimic/xenobio/proc/insertion_allowed(atom/movable/AM)
- if(ismob(AM))
- if(!isliving(AM)) //Don't let ghosts and such get trapped in the beast.
- return FALSE
- var/mob/living/L = AM
- if(L.anchored || L.buckled || L.incorporeal_move || L.has_buckled_mobs())
- return FALSE
- if(L.mob_size > MOB_SIZE_TINY) // Tiny mobs are treated as items.
- if(L.density || L.mob_size > max_mob_size)
- return FALSE
- var/mobs_stored = 0
- for(var/mob/living/M in contents)
- mobs_stored++
- if(mobs_stored >= mob_storage_capacity)
- return FALSE
- L.stop_pulling()
-
- else if(istype(AM, /obj/structure/closet))
- return FALSE
- else if(isobj(AM))
- if(AM.anchored || AM.has_buckled_mobs())
- return FALSE
- else if(isitem(AM) && !HAS_TRAIT(AM, TRAIT_NODROP))
- return TRUE
- else
- return FALSE
- return TRUE
-
-/datum/action/innate/mimic
- background_icon_state = "bg_default"
- overlay_icon_state = "bg_default_border"
-
-/datum/action/innate/mimic/lock
- name = "Lock/Unlock"
- desc = "Toggle preventing yourself from being opened or closed."
-
-/datum/action/innate/mimic/lock/Activate()
- var/mob/living/simple_animal/hostile/mimic/xenobio/M = owner
- M.locked = !M.locked
- if(!M.locked)
- to_chat(M, span_warning("You loosen up, allowing yourself to be opened and closed."))
- else
- to_chat(M, span_warning("You stiffen up, preventing anyone from opening or closing you."))
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm
deleted file mode 100644
index 5fbd6cda5cbfb..0000000000000
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm
+++ /dev/null
@@ -1,132 +0,0 @@
-/mob/living/simple_animal/hostile/asteroid/curseblob
- name = "curse mass"
- desc = "A mass of purple... smoke?"
- icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi'
- icon_state = "curseblob"
- icon_living = "curseblob"
- icon_aggro = "curseblob"
- mob_biotypes = MOB_SPIRIT
- move_to_delay = 5
- vision_range = 20
- aggro_vision_range = 20
- maxHealth = 40 //easy to kill, but oh, will you be seeing a lot of them.
- health = 40
- melee_damage_lower = 10
- melee_damage_upper = 10
- melee_damage_type = BURN
- attack_verb_continuous = "slashes"
- attack_verb_simple = "slash"
- attack_sound = 'sound/effects/curse/curseattack.ogg'
- attack_vis_effect = ATTACK_EFFECT_SLASH
- throw_message = "passes through the smokey body of"
- obj_damage = 0
- environment_smash = ENVIRONMENT_SMASH_NONE
- sentience_type = SENTIENCE_BOSS
- layer = LARGE_MOB_LAYER
- var/mob/living/set_target
- var/datum/move_loop/has_target/force_move/our_loop
-
-/mob/living/simple_animal/hostile/asteroid/curseblob/Initialize(mapload)
- . = ..()
- QDEL_IN(src, 60 SECONDS)
- AddElement(/datum/element/simple_flying)
- playsound(src, 'sound/effects/curse/curse1.ogg', 100, TRUE, -1)
-
-/mob/living/simple_animal/hostile/asteroid/curseblob/Destroy()
- new /obj/effect/temp_visual/dir_setting/curse/blob(loc, dir)
- set_target = null
- return ..()
-
-/mob/living/simple_animal/hostile/asteroid/curseblob/Goto(move_target, delay, minimum_distance) //Observe
- if(check_for_target())
- return
- move_loop(target, delay)
-
-/mob/living/simple_animal/hostile/asteroid/curseblob/proc/move_loop(move_target, delay)
- if(our_loop)
- return
- our_loop = GLOB.move_manager.force_move(src, move_target, delay, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
- if(!our_loop)
- return
- RegisterSignal(move_target, COMSIG_MOB_STATCHANGE, PROC_REF(stat_change))
- RegisterSignal(move_target, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(target_z_change))
- RegisterSignal(src, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(our_z_change))
- RegisterSignal(our_loop, COMSIG_QDELETING, PROC_REF(handle_loop_end))
-
-/mob/living/simple_animal/hostile/asteroid/curseblob/proc/stat_change(datum/source, new_stat)
- SIGNAL_HANDLER
- if(new_stat != CONSCIOUS)
- qdel(src)
-
-/mob/living/simple_animal/hostile/asteroid/curseblob/proc/target_z_change(datum/source, old_z, new_z)
- SIGNAL_HANDLER
- qdel(src)
-
-/mob/living/simple_animal/hostile/asteroid/curseblob/proc/our_z_change(datum/source, old_z, new_z)
- SIGNAL_HANDLER
- qdel(src)
-
-/mob/living/simple_animal/hostile/asteroid/curseblob/proc/handle_loop_end()
- SIGNAL_HANDLER
- if(QDELETED(src))
- return
- qdel(src)
-
-/mob/living/simple_animal/hostile/asteroid/curseblob/handle_target_del(datum/source)
- . = ..()
- qdel(src)
-
-/mob/living/simple_animal/hostile/asteroid/curseblob/proc/check_for_target()
- if(QDELETED(src) || !set_target)
- return TRUE
- if(set_target.stat != CONSCIOUS)
- return TRUE
- if(set_target.z != z)
- return TRUE
-
-/mob/living/simple_animal/hostile/asteroid/curseblob/GiveTarget(new_target)
- if(check_for_target())
- return
- new_target = set_target
- . = ..()
- Goto(target, move_to_delay)
-
-/mob/living/simple_animal/hostile/asteroid/curseblob/LoseTarget() //we can't lose our target!
- if(check_for_target())
- return
-
-//if it's not our target, we ignore it
-/mob/living/simple_animal/hostile/asteroid/curseblob/CanAllowThrough(atom/movable/mover, border_dir)
- . = ..()
- if(mover == set_target)
- return FALSE
- if(isprojectile(mover))
- var/obj/projectile/proj = mover
- if(proj.firer == set_target)
- return FALSE
-
-#define IGNORE_PROC_IF_NOT_TARGET(X) /mob/living/simple_animal/hostile/asteroid/curseblob/##X(AM) { if (AM == set_target) return ..(); }
-
-IGNORE_PROC_IF_NOT_TARGET(attack_hand)
-
-IGNORE_PROC_IF_NOT_TARGET(attack_hulk)
-
-IGNORE_PROC_IF_NOT_TARGET(attack_paw)
-
-IGNORE_PROC_IF_NOT_TARGET(attack_alien)
-
-IGNORE_PROC_IF_NOT_TARGET(attack_larva)
-
-IGNORE_PROC_IF_NOT_TARGET(attack_animal)
-
-/mob/living/simple_animal/hostile/asteroid/curseblob/bullet_act(obj/projectile/proj)
- if(proj.firer != set_target)
- return BULLET_ACT_BLOCK
- return ..()
-
-/mob/living/simple_animal/hostile/asteroid/curseblob/attacked_by(obj/item/I, mob/living/L)
- if(L != set_target)
- return
- return ..()
-
-#undef IGNORE_PROC_IF_NOT_TARGET
diff --git a/code/modules/mob/living/simple_animal/hostile/ooze.dm b/code/modules/mob/living/simple_animal/hostile/ooze.dm
index 77ccf3ea483e7..f8dd01ca3a0cd 100644
--- a/code/modules/mob/living/simple_animal/hostile/ooze.dm
+++ b/code/modules/mob/living/simple_animal/hostile/ooze.dm
@@ -202,6 +202,10 @@
button_icon = 'icons/mob/actions/actions_slime.dmi'
button_icon_state = "consume"
check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_IMMOBILE|AB_CHECK_INCAPACITATED
+ /// What do we call devouring something
+ var/devour_verb = "devour"
+ /// how much time to eat someone
+ var/devour_time = 1.5 SECONDS
///The mob thats being consumed by this creature
var/mob/living/vored_mob
@@ -223,8 +227,8 @@
to_chat(src, span_warning("You need to be pulling a creature for this to work!"))
return FALSE
var/mob/living/eat_target = ooze.pulling
- owner.visible_message(span_warning("[ooze] starts attempting to devour [eat_target]!"), span_notice("You start attempting to devour [eat_target]."))
- if(!do_after(ooze, 1.5 SECONDS, eat_target))
+ owner.visible_message(span_warning("[ooze] starts attempting to [devour_verb] [eat_target]!"), span_notice("You start attempting to [devour_verb] [eat_target]."))
+ if(!do_after(ooze, devour_time, eat_target))
return FALSE
if(!(eat_target.mob_biotypes & MOB_ORGANIC) || eat_target.stat == DEAD)
@@ -238,7 +242,7 @@
vored_mob.forceMove(owner) ///AAAAAAAAAAAAAAAAAAAAAAHHH!!!
RegisterSignal(vored_mob, COMSIG_QDELETING, PROC_REF(stop_consuming))
playsound(owner,'sound/items/eatfood.ogg', rand(30,50), TRUE)
- owner.visible_message(span_warning("[src] devours [target]!"), span_notice("You devour [target]."))
+ owner.visible_message(span_warning("[owner] [devour_verb]s [target]!"), span_notice("You [devour_verb] [target]."))
START_PROCESSING(SSprocessing, src)
build_all_button_icons(UPDATE_BUTTON_NAME|UPDATE_BUTTON_ICON)
@@ -260,7 +264,8 @@
var/mob/living/simple_animal/hostile/ooze/gelatinous/ooze = owner
vored_mob.adjustBruteLoss(5)
ooze.heal_ordered_damage((ooze.maxHealth * 0.03), list(BRUTE, BURN, OXY)) ///Heal 6% of these specific damage types each process
- ooze.adjust_ooze_nutrition(3)
+ if(istype(ooze))
+ ooze.adjust_ooze_nutrition(3)
///Dump 'em if they're dead.
if(vored_mob.stat == DEAD)
diff --git a/code/modules/mob/living/simple_animal/hostile/zombie.dm b/code/modules/mob/living/simple_animal/hostile/zombie.dm
deleted file mode 100644
index 45bcf6cd3acd7..0000000000000
--- a/code/modules/mob/living/simple_animal/hostile/zombie.dm
+++ /dev/null
@@ -1,51 +0,0 @@
-/mob/living/simple_animal/hostile/zombie
- name = "Shambling Corpse"
- desc = "When there is no more room in hell, the dead will walk in outer space."
- icon = 'icons/mob/simple/simple_human.dmi'
- mob_biotypes = MOB_ORGANIC|MOB_HUMANOID
- sentience_type = SENTIENCE_HUMANOID
- speak_chance = 0
- stat_attack = HARD_CRIT //braains
- maxHealth = 100
- health = 100
- harm_intent_damage = 5
- melee_damage_lower = 21
- melee_damage_upper = 21
- attack_verb_continuous = "bites"
- attack_verb_simple = "bite"
- attack_sound = 'sound/effects/hallucinations/growl1.ogg'
- attack_vis_effect = ATTACK_EFFECT_BITE
- combat_mode = TRUE
- atmos_requirements = null
- minbodytemp = 0
- status_flags = CANPUSH
- death_message = "collapses, flesh gone in a pile of bones!"
- del_on_death = TRUE
- loot = list(/obj/effect/decal/remains/human)
- /// The probability that we give people real zombie infections on hit.
- var/infection_chance = 0
- /// Outfit the zombie spawns with for visuals.
- var/outfit = /datum/outfit/corpse_doctor
-
-/mob/living/simple_animal/hostile/zombie/Initialize(mapload)
- . = ..()
- apply_dynamic_human_appearance(src, outfit, /datum/species/zombie, bloody_slots = ITEM_SLOT_OCLOTHING)
-
-/mob/living/simple_animal/hostile/zombie/AttackingTarget(atom/attacked_target)
- . = ..()
- if(. && ishuman(target) && prob(infection_chance))
- try_to_zombie_infect(target)
-
-/datum/outfit/corpse_doctor
- name = "Corpse Doctor"
- suit = /obj/item/clothing/suit/toggle/labcoat
- uniform = /obj/item/clothing/under/rank/medical/doctor
- shoes = /obj/item/clothing/shoes/sneakers/white
- back = /obj/item/storage/backpack/medic
-
-/datum/outfit/corpse_assistant
- name = "Corpse Assistant"
- mask = /obj/item/clothing/mask/gas
- uniform = /obj/item/clothing/under/color/grey
- shoes = /obj/item/clothing/shoes/sneakers/black
- back = /obj/item/storage/backpack
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index 006b2684c9755..ffc201940847a 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -216,18 +216,6 @@
return M
return 0
-///Find the first name of a mob from the real name with regex
-/mob/proc/first_name()
- var/static/regex/firstname = new("^\[^\\s-\]+") //First word before whitespace or "-"
- firstname.Find(real_name)
- return firstname.match
-
-/// Find the last name of a mob from the real name with regex
-/mob/proc/last_name()
- var/static/regex/lasttname = new("\[^\\s-\]+$") //First word before whitespace or "-"
- lasttname.Find(real_name)
- return lasttname.match
-
///Returns a mob's real name between brackets. Useful when you want to display a mob's name alongside their real name
/mob/proc/get_realname_string()
if(real_name && real_name != name)
diff --git a/code/modules/mod/modules/modules_engineering.dm b/code/modules/mod/modules/modules_engineering.dm
index 7ffda55fbb09f..af22a60d383c5 100644
--- a/code/modules/mod/modules/modules_engineering.dm
+++ b/code/modules/mod/modules/modules_engineering.dm
@@ -114,8 +114,10 @@
.["cut_tethers"] = add_ui_configuration("Cut Tethers", "button", "scissors")
/obj/item/mod/module/tether/configure_edit(key, value)
- if (key != "cut_tethers")
- return
+ if (key == "cut_tethers")
+ SEND_SIGNAL(src, COMSIG_MOD_TETHER_SNAP)
+
+/obj/item/mod/module/tether/on_deactivation(display_message, deleting)
SEND_SIGNAL(src, COMSIG_MOD_TETHER_SNAP)
/obj/projectile/tether
@@ -197,6 +199,7 @@
anchor.pixel_x = hitx
anchor.pixel_y = hity
anchor.anchored = TRUE
+ anchor.parent_module = parent_module
firer.AddComponent(/datum/component/tether, anchor, 7, "MODtether", parent_module = parent_module, tether_trait_source = REF(parent_module))
/obj/projectile/tether/Destroy()
@@ -210,6 +213,17 @@
icon = 'icons/obj/clothing/modsuit/mod_modules.dmi'
max_integrity = 60
interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT
+ /// MODsuit tether module that created our projectile
+ var/obj/item/mod/module/tether/parent_module
+
+/obj/item/tether_anchor/Initialize(mapload)
+ . = ..()
+ RegisterSignal(src, COMSIG_ATOM_TETHER_SNAPPED, PROC_REF(tether_snapped))
+
+/obj/item/tether_anchor/Destroy(force)
+ // We don't need to worry about hanging refs in case our parent gets destroyed because then it snaps all tethers, which in turn destroys us
+ parent_module = null
+ return ..()
/obj/item/tether_anchor/examine(mob/user)
. = ..()
@@ -229,6 +243,10 @@
balloon_alert(user, "already tethered!")
return
+ if (parent_module && HAS_TRAIT_FROM(user, TRAIT_TETHER_ATTACHED, REF(parent_module)))
+ balloon_alert(user, "already tethered!")
+ return
+
balloon_alert(user, "attached tether")
user.AddComponent(/datum/component/tether, src, 7, "tether", tether_trait_source = REF(src))
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
@@ -244,6 +262,10 @@
balloon_alert(user, "already tethered!")
return
+ if (parent_module && HAS_TRAIT_FROM(user, TRAIT_TETHER_ATTACHED, REF(parent_module)))
+ balloon_alert(user, "already tethered!")
+ return
+
if (target == user)
balloon_alert(user, "attached tether")
user.AddComponent(/datum/component/tether, src, 7, "tether", tether_trait_source = REF(src), no_target_trait = TRUE)
@@ -258,10 +280,24 @@
balloon_alert(user, "already tethered!")
return
+ if (parent_module && HAS_TRAIT_FROM(user, TRAIT_TETHER_ATTACHED, REF(parent_module)))
+ balloon_alert(user, "already tethered!")
+ return
+
balloon_alert(user, "attached tether")
to_chat(target, span_userdanger("[user] attaches a tether to you!"))
target.AddComponent(/datum/component/tether, src, 7, "tether", tether_trait_source = REF(src), no_target_trait = TRUE)
+/obj/item/tether_anchor/proc/tether_snapped(datum/component/tether/tether, tether_source)
+ SIGNAL_HANDLER
+
+ if (!parent_module || tether_source != REF(parent_module))
+ return
+
+ // Destroy self if we've been created by a tether module
+ do_sparks(3, TRUE, src)
+ qdel(src)
+
/datum/embedding/tether_projectile
embed_chance = 65 //spiky
fall_chance = 2
diff --git a/code/modules/movespeed/modifiers/status_effects.dm b/code/modules/movespeed/modifiers/status_effects.dm
index 3b32aea77480c..26522d0793b9f 100644
--- a/code/modules/movespeed/modifiers/status_effects.dm
+++ b/code/modules/movespeed/modifiers/status_effects.dm
@@ -45,6 +45,9 @@
/datum/movespeed_modifier/status_effect/midas_blight
id = MOVESPEED_ID_MIDAS_BLIGHT
+/datum/movespeed_modifier/status_effect/spooked
+ multiplicative_slowdown = 0.25
+
/datum/movespeed_modifier/status_effect/midas_blight/soft
multiplicative_slowdown = 0.25
diff --git a/code/modules/paperwork/clipboard.dm b/code/modules/paperwork/clipboard.dm
index 435cfc3e7c74a..ec81574628d21 100644
--- a/code/modules/paperwork/clipboard.dm
+++ b/code/modules/paperwork/clipboard.dm
@@ -26,13 +26,11 @@
/// Is the pen integrated?
var/integrated_pen = FALSE
/**
- * Weakref of the topmost piece of paper
- *
+ * Topmost piece of paper
* This is used for the paper displayed on the clipboard's icon
* and it is the one attacked, when attacking the clipboard.
- * (As you can't organise contents directly in BYOND)
*/
- var/datum/weakref/toppaper_ref
+ var/obj/item/paper/top_paper
/obj/item/clipboard/suicide_act(mob/living/carbon/user)
user.visible_message(span_suicide("[user] begins putting [user.p_their()] head into the clip of \the [src]! It looks like [user.p_theyre()] trying to commit suicide!"))
@@ -50,9 +48,8 @@
. = ..()
if(!integrated_pen && pen)
. += span_notice("Alt-click to remove [pen].")
- var/obj/item/paper/toppaper = toppaper_ref?.resolve()
- if(toppaper)
- . += span_notice("Right-click to remove [toppaper].")
+ if(top_paper)
+ . += span_notice("Right-click to remove [top_paper].")
/// Take out the topmost paper
/obj/item/clipboard/proc/remove_paper(obj/item/paper/paper, mob/user)
@@ -61,22 +58,24 @@
paper.forceMove(user.loc)
user.put_in_hands(paper)
to_chat(user, span_notice("You remove [paper] from [src]."))
- var/obj/item/paper/toppaper = toppaper_ref?.resolve()
- if(paper == toppaper)
- UnregisterSignal(toppaper, COMSIG_ATOM_UPDATED_ICON)
- toppaper_ref = null
- var/obj/item/paper/newtop = locate(/obj/item/paper) in src
- if(newtop && (newtop != paper))
- toppaper_ref = WEAKREF(newtop)
- else
- toppaper_ref = null
- update_icon()
/obj/item/clipboard/proc/remove_pen(mob/user)
pen.forceMove(user.loc)
user.put_in_hands(pen)
to_chat(user, span_notice("You remove [pen] from [src]."))
- pen = null
+
+/obj/item/clipboard/Exited(atom/movable/gone, direction)
+ . = ..()
+ if (gone == pen)
+ pen = null
+ update_icon()
+ return
+
+ if (gone != top_paper)
+ return
+
+ UnregisterSignal(top_paper, COMSIG_ATOM_UPDATED_ICON)
+ top_paper = locate(/obj/item/paper) in src
update_icon()
/obj/item/clipboard/click_alt(mob/user)
@@ -100,32 +99,29 @@
. += "clipboard_over"
/obj/item/clipboard/proc/get_paper_overlay()
- var/obj/item/paper/toppaper = toppaper_ref?.resolve()
- if(isnull(toppaper))
+ if(isnull(top_paper))
return
- var/mutable_appearance/paper_overlay = mutable_appearance(icon, toppaper.icon_state, offset_spokesman = src, appearance_flags = KEEP_APART)
- paper_overlay = toppaper.color_atom_overlay(paper_overlay)
- paper_overlay.overlays += toppaper.overlays
+ var/mutable_appearance/paper_overlay = mutable_appearance(icon, top_paper.icon_state, offset_spokesman = src, appearance_flags = KEEP_APART)
+ paper_overlay = top_paper.color_atom_overlay(paper_overlay)
+ paper_overlay.overlays += top_paper.overlays
return paper_overlay
/obj/item/clipboard/attack_hand(mob/user, list/modifiers)
if(LAZYACCESS(modifiers, RIGHT_CLICK))
- var/obj/item/paper/toppaper = toppaper_ref?.resolve()
- remove_paper(toppaper, user)
+ remove_paper(top_paper, user)
return TRUE
. = ..()
/obj/item/clipboard/attackby(obj/item/weapon, mob/user, params)
- var/obj/item/paper/toppaper = toppaper_ref?.resolve()
if(istype(weapon, /obj/item/paper))
//Add paper into the clipboard
if(!user.transferItemToLoc(weapon, src))
return
- if(toppaper)
- UnregisterSignal(toppaper, COMSIG_ATOM_UPDATED_ICON)
+ if(top_paper)
+ UnregisterSignal(top_paper, COMSIG_ATOM_UPDATED_ICON)
RegisterSignal(weapon, COMSIG_ATOM_UPDATED_ICON, PROC_REF(on_top_paper_change))
- toppaper_ref = WEAKREF(weapon)
+ top_paper = weapon
to_chat(user, span_notice("You clip [weapon] onto [src]."))
else if(istype(weapon, /obj/item/pen) && !pen)
//Add a pen into the clipboard, attack (write) if there is already one
@@ -133,8 +129,8 @@
return
pen = weapon
to_chat(usr, span_notice("You slot [weapon] into [src]."))
- else if(toppaper)
- toppaper.attackby(user.get_active_held_item(), user)
+ else if(top_paper)
+ top_paper.attackby(user.get_active_held_item(), user)
update_appearance()
/obj/item/clipboard/attack_self(mob/user)
@@ -154,14 +150,13 @@
data["pen"] = "[pen]"
data["integrated_pen"] = integrated_pen
- var/obj/item/paper/toppaper = toppaper_ref?.resolve()
- data["top_paper"] = "[toppaper]"
- data["top_paper_ref"] = "[REF(toppaper)]"
+ data["top_paper"] = "[top_paper]"
+ data["top_paper_ref"] = "[REF(top_paper)]"
data["paper"] = list()
data["paper_ref"] = list()
for(var/obj/item/paper/paper in src)
- if(paper == toppaper)
+ if(paper == top_paper)
continue
data["paper"] += "[paper]"
data["paper_ref"] += "[REF(paper)]"
@@ -202,7 +197,7 @@
if("move_top_paper")
var/obj/item/paper/paper = locate(params["ref"]) in src
if(istype(paper))
- toppaper_ref = WEAKREF(paper)
+ top_paper = paper
to_chat(usr, span_notice("You move [paper] to the top."))
update_icon()
. = TRUE
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index c8e8a0653ed9d..abaf7883cb888 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -558,7 +558,7 @@
return TRUE
/obj/item/gun/animate_atom_living(mob/living/owner)
- new /mob/living/simple_animal/hostile/mimic/copy/ranged(drop_location(), src, owner)
+ new /mob/living/basic/mimic/copy/ranged(drop_location(), src, owner)
/obj/item/gun/proc/handle_suicide(mob/living/carbon/human/user, mob/living/carbon/human/target, params, bypass_timer)
if(!ishuman(user) || !ishuman(target))
diff --git a/code/modules/reagents/chemistry/equilibrium.dm b/code/modules/reagents/chemistry/equilibrium.dm
index bc7cbd37f4854..76f45e77cabf9 100644
--- a/code/modules/reagents/chemistry/equilibrium.dm
+++ b/code/modules/reagents/chemistry/equilibrium.dm
@@ -34,6 +34,8 @@
var/delta_t
///How pure our step is
var/delta_ph
+ ///Min reaction rate possible below which rounding errors occur
+ VAR_PRIVATE/min_rate
///Modifiers from catalysts, do not use negative numbers.
///I should write a better handiler for modifying these
///Speed mod
@@ -67,7 +69,6 @@
LAZYADD(holder.reaction_list, src)
SSblackbox.record_feedback("tally", "chemical_reaction", 1, "[reaction.type] attempts")
-
/datum/equilibrium/Destroy()
if(reacted_vol < target_vol) //We did NOT finish from reagents - so we can restart this reaction given property changes in the beaker. (i.e. if it stops due to low temp, this will allow it to fast restart when heated up again)
LAZYADD(holder.failed_but_capable_reactions, reaction) //Consider replacing check with calculate_yield()
@@ -114,6 +115,8 @@
product_ratio += reaction.results[product]
else
product_ratio = 1
+ min_rate = product_ratio * (CHEMICAL_VOLUME_ROUNDING / 2)
+
return TRUE
/**
@@ -321,10 +324,13 @@
purity *= purity_modifier
//Now we calculate how much to add - this is normalised to the rate up limiter
- var/delta_chem_factor = reaction.rate_up_lim * delta_t * seconds_per_tick//add/remove factor
+ var/delta_chem_factor = reaction.rate_up_lim * delta_t * seconds_per_tick
//keep limited
if(delta_chem_factor > step_target_vol)
delta_chem_factor = step_target_vol
+ //ensure its above minimum rate below which rounding errors occur
+ else if(delta_chem_factor < min_rate)
+ delta_chem_factor = min_rate
//Normalise to multiproducts
delta_chem_factor = round(delta_chem_factor / product_ratio, CHEMICAL_VOLUME_ROUNDING)
if(delta_chem_factor <= 0)
diff --git a/code/modules/reagents/chemistry/holder/holder.dm b/code/modules/reagents/chemistry/holder/holder.dm
index 7a599edc13325..be74e5cfb85bb 100644
--- a/code/modules/reagents/chemistry/holder/holder.dm
+++ b/code/modules/reagents/chemistry/holder/holder.dm
@@ -266,7 +266,7 @@
for(var/datum/reagent/removed_reagent as anything in removed_reagents)
SEND_SIGNAL(src, COMSIG_REAGENTS_REM_REAGENT, removed_reagent, removed_reagents[removed_reagent])
- return round(total_removed_amount, CHEMICAL_VOLUME_ROUNDING)
+ return total_removed_amount
/**
* Removes all reagents either proportionally(amount is the direct volume to remove)
diff --git a/code/modules/reagents/chemistry/machinery/chem_recipe_debug.dm b/code/modules/reagents/chemistry/machinery/chem_recipe_debug.dm
index 9b0773a8316b5..3cf570250d648 100644
--- a/code/modules/reagents/chemistry/machinery/chem_recipe_debug.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_recipe_debug.dm
@@ -29,7 +29,7 @@
icon = 'icons/obj/medical/chemical.dmi'
icon_state = "HPLC_debug"
density = TRUE
- idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.4
+ use_power = NO_POWER_USE
resistance_flags = FIRE_PROOF | ACID_PROOF | INDESTRUCTIBLE
///Temperature to be imposed on the reaction
@@ -63,7 +63,7 @@
///The target reagents to we are working with. can vary if an reaction requires a specific container
var/datum/reagents/target_reagents
///The beaker inside this machine, if null will create a new one
- var/obj/item/reagent_containers/cup/beaker/bluespace/beaker
+ var/obj/item/reagent_containers/container
///The default reagent container required for the selected test reaction if any
var/obj/item/reagent_containers/required_container
@@ -85,47 +85,47 @@
reactions_to_test.Cut()
target_reagents = null
edit_reaction = null
- QDEL_NULL(beaker)
+ QDEL_NULL(container)
QDEL_NULL(required_container)
- UnregisterSignal(reagents, COMSIG_REAGENTS_REACTION_STEP)
- . = ..()
+ return ..()
/obj/machinery/chem_recipe_debug/add_context(atom/source, list/context, obj/item/held_item, mob/user)
. = ..()
if(isnull(held_item) || (held_item.item_flags & ABSTRACT) || (held_item.flags_1 & HOLOGRAM_1))
return NONE
- if(!QDELETED(beaker))
+ if(!QDELETED(container))
if(is_reagent_container(held_item) && held_item.is_open_container())
- context[SCREENTIP_CONTEXT_LMB] = "Replace beaker"
+ context[SCREENTIP_CONTEXT_LMB] = "Replace container"
return CONTEXTUAL_SCREENTIP_SET
else if(is_reagent_container(held_item) && held_item.is_open_container())
- context[SCREENTIP_CONTEXT_LMB] = "Insert beaker"
+ context[SCREENTIP_CONTEXT_LMB] = "Insert container"
return CONTEXTUAL_SCREENTIP_SET
/obj/machinery/chem_recipe_debug/examine(mob/user)
. = ..()
- if(!QDELETED(beaker))
- . += span_notice("A beaker of [beaker.reagents.maximum_volume]u capacity is inside.")
+ if(!QDELETED(container))
+ . += span_notice("A container of [container.reagents.maximum_volume]u capacity is inside.")
else
- . += span_notice("No beaker is present. A new will be created when ejecting.")
+ . += span_notice("No container is present. A new will be created when ejecting.")
/obj/machinery/chem_recipe_debug/Exited(atom/movable/gone, direction)
. = ..()
- if(gone == beaker)
- beaker = null
+ if(gone == container)
+ container = null
-/obj/machinery/chem_recipe_debug/attackby(obj/item/held_item, mob/user, params)
+/obj/machinery/chem_recipe_debug/item_interaction(mob/living/user, obj/item/held_item, list/modifiers)
+ . = NONE
if((held_item.item_flags & ABSTRACT) || (held_item.flags_1 & HOLOGRAM_1))
- return ..()
+ return
if(is_reagent_container(held_item) && held_item.is_open_container())
- . = TRUE
- if(!QDELETED(beaker))
- try_put_in_hand(beaker, user)
+ if(!QDELETED(container))
+ try_put_in_hand(container, user)
if(!user.transferItemToLoc(held_item, src))
- return
- beaker = held_item
+ return ITEM_INTERACT_FAILURE
+ container = held_item
+ return ITEM_INTERACT_SUCCESS
/**
* Extracts a human readable name for this chemical reaction
@@ -155,13 +155,12 @@
var/datum/chemical_reaction/test_reaction = reactions_to_test[current_reaction_index || 1]
switch(temp_mode)
if(USE_MINIMUM_TEMPERATURE)
- return test_reaction.required_temp + (test_reaction.is_cold_recipe ? - 20 : 20) //20k is good enough offset to account for reaction rate rounding
+ return test_reaction.required_temp
if(USE_OPTIMAL_TEMPERATURE)
return test_reaction.optimal_temp
if(USE_OVERHEAT_TEMPERATURE)
return test_reaction.overheat_temp
-
/**
* Adjusts the temperature, ph & purity of the holder
* Arguments
@@ -173,7 +172,7 @@
var/target_temperature = decode_target_temperature()
if(!isnull(target_temperature))
- target_reagents.adjust_thermal_energy((target_temperature - target_reagents.chem_temp) * 0.4 * seconds_per_tick * SPECIFIC_HEAT_DEFAULT * target_reagents.total_volume)
+ target_reagents.adjust_thermal_energy((target_temperature - target_reagents.chem_temp) * 0.45 * seconds_per_tick * target_reagents.heat_capacity())
if(use_forced_purity)
target_reagents.set_all_reagents_purity(forced_purity)
@@ -187,7 +186,7 @@
/obj/machinery/chem_recipe_debug/process(seconds_per_tick)
if(!target_reagents.is_reacting)
adjust_environment(seconds_per_tick)
- target_reagents.handle_reactions()
+ target_reagents.handle_reactions()
//send updates to ui. faster than SStgui.update_uis
for(var/datum/tgui/ui in src.open_uis)
@@ -334,11 +333,10 @@
.["editReaction"] = reaction_data
var/list/beaker_data = null
- if(target_reagents.reagent_list.len)
+ if(!QDELETED(container) || target_reagents.total_volume)
beaker_data = list()
beaker_data["maxVolume"] = target_reagents.maximum_volume
beaker_data["pH"] = round(target_reagents.ph, 0.01)
- beaker_data["purity"] = round(target_reagents.get_average_purity(), 0.01)
beaker_data["currentVolume"] = round(target_reagents.total_volume, CHEMICAL_VOLUME_ROUNDING)
beaker_data["currentTemp"] = round(target_reagents.chem_temp, 1)
beaker_data["purity"] = round(target_reagents.get_average_purity(), 0.001)
@@ -666,12 +664,26 @@
tgui_alert(ui.user, "Saved to [dest]")
if("eject")
- if(!target_reagents.total_volume)
- return
- if(QDELETED(beaker))
- beaker = new /obj/item/reagent_containers/cup/beaker/bluespace(src)
- target_reagents.trans_to(beaker, target_reagents.total_volume)
- try_put_in_hand(beaker, ui.user)
+ //initialize a new container for us
+ if(QDELETED(container))
+ if(QDELETED(required_container))
+ container = new /obj/item/reagent_containers/cup/beaker/bluespace(src)
+ else
+ container = new required_container.type(src)
+
+ //transfer all reagents & ingredients if we are using a soup pot
+ container.reagents.clear_reagents()
+ target_reagents.trans_to(container, target_reagents.total_volume)
+ if(istype(container, /obj/item/reagent_containers/cup/soup_pot) && istype(required_container, /obj/item/reagent_containers/cup/soup_pot))
+ var/obj/item/reagent_containers/cup/soup_pot/pot = container
+ for(var/obj/item as anything in pot.added_ingredients)
+ qdel(item)
+ var/obj/item/reagent_containers/cup/soup_pot/holder = required_container
+ for(var/obj/item as anything in holder.added_ingredients)
+ item.forceMove(pot)
+ LAZYADD(pot.added_ingredients, item)
+ try_put_in_hand(container, ui.user)
+
return TRUE
#undef USE_REACTION_TEMPERATURE
diff --git a/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm
index e1edc1bddf188..2ca72cbd8b48c 100644
--- a/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm
@@ -910,7 +910,7 @@ Basically, we fill the time between now and 2s from now with hands based off the
/datum/reagent/inverse/rezadone
name = "Inreziniver"
- description = "Makes the user horribly afraid of all things related to carps."
+ description = "Makes the user horribly afraid of all things related to fish."
color = "#c92eb4"
ph = 13.9
metabolization_rate = 0.05 * REM
@@ -918,8 +918,8 @@ Basically, we fill the time between now and 2s from now with hands based off the
/datum/reagent/inverse/rezadone/on_mob_metabolize(mob/living/carbon/affected_mob)
. = ..()
- affected_mob.gain_trauma(/datum/brain_trauma/mild/phobia/carps, TRAUMA_RESILIENCE_ABSOLUTE)
+ affected_mob.gain_trauma(/datum/brain_trauma/mild/phobia/fish, TRAUMA_RESILIENCE_ABSOLUTE)
/datum/reagent/inverse/rezadone/on_mob_end_metabolize(mob/living/carbon/affected_mob)
. = ..()
- affected_mob.cure_trauma_type(/datum/brain_trauma/mild/phobia/carps, resilience = TRAUMA_RESILIENCE_ABSOLUTE)
+ affected_mob.cure_trauma_type(/datum/brain_trauma/mild/phobia/fish, resilience = TRAUMA_RESILIENCE_ABSOLUTE)
diff --git a/code/modules/research/designs/wiremod_designs.dm b/code/modules/research/designs/wiremod_designs.dm
index f8732cc332b72..526e8d2a54a11 100644
--- a/code/modules/research/designs/wiremod_designs.dm
+++ b/code/modules/research/designs/wiremod_designs.dm
@@ -477,6 +477,11 @@
id = "comp_assoc_list_pick"
build_path = /obj/item/circuit_component/list_pick/assoc
+/datum/design/component/wire_bundle
+ name = "Wire Bundle"
+ id = "comp_wire_bundle"
+ build_path = /obj/item/circuit_component/wire_bundle
+
/datum/design/component/wirenet_receive
name = "Wirenet Receiver Component"
id = "comp_wirenet_receive"
@@ -691,3 +696,32 @@
category = list(
RND_CATEGORY_CIRCUITRY + RND_SUBCATEGORY_CIRCUITRY_SHELLS
)
+
+/datum/design/undertile_shell
+ name = "Under-tile Shell"
+ desc = "A small shell that can fit under the floor."
+ id = "undertile_shell"
+ materials = list(
+ /datum/material/glass=HALF_SHEET_MATERIAL_AMOUNT,
+ /datum/material/iron=SHEET_MATERIAL_AMOUNT*2.5,
+ )
+ build_path = /obj/item/undertile_circuit
+ build_type = COMPONENT_PRINTER
+ category = list(
+ RND_CATEGORY_CIRCUITRY + RND_SUBCATEGORY_CIRCUITRY_SHELLS
+ )
+
+
+/datum/design/wallmount_shell
+ name = "Wall-mounted Shell"
+ desc = "A large shell that can be mounted on a wall."
+ id = "wallmount_shell"
+ materials = list(
+ /datum/material/glass=SHEET_MATERIAL_AMOUNT,
+ /datum/material/iron=SHEET_MATERIAL_AMOUNT*5,
+ )
+ build_path = /obj/item/wallframe/circuit
+ build_type = COMPONENT_PRINTER
+ category = list(
+ RND_CATEGORY_CIRCUITRY + RND_SUBCATEGORY_CIRCUITRY_SHELLS
+ )
diff --git a/code/modules/research/techweb/nodes/circuit_nodes.dm b/code/modules/research/techweb/nodes/circuit_nodes.dm
index 109873c38510b..9ef3b929ac424 100644
--- a/code/modules/research/techweb/nodes/circuit_nodes.dm
+++ b/code/modules/research/techweb/nodes/circuit_nodes.dm
@@ -86,6 +86,7 @@
"comp_typecast",
"comp_typecheck",
"comp_view_sensor",
+ "comp_wire_bundle",
"comp_wirenet_receive",
"comp_wirenet_send",
"comp_wirenet_send_literal",
@@ -108,6 +109,8 @@
"money_bot_shell",
"scanner_gate_shell",
"scanner_shell",
+ "undertile_shell",
+ "wallmount_shell",
"comp_equip_action",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_1_POINTS)
diff --git a/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm b/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm
index 571440f84427f..44721aa6c38af 100644
--- a/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm
+++ b/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm
@@ -448,7 +448,7 @@
/datum/reagent/medicine/c2/syriniver = -2)
virus_suspectibility = 0.5
- resulting_atom = /mob/living/simple_animal/hostile/vatbeast
+ resulting_atom = /mob/living/basic/vatbeast
/datum/micro_organism/cell_line/vat_beast/succeed_growing(obj/machinery/vatgrower/vat)
. = ..()
diff --git a/code/modules/shuttle/mobile_port/shuttle_move_callbacks.dm b/code/modules/shuttle/mobile_port/shuttle_move_callbacks.dm
index 1aac6e4c1aec8..7b02f618081e6 100644
--- a/code/modules/shuttle/mobile_port/shuttle_move_callbacks.dm
+++ b/code/modules/shuttle/mobile_port/shuttle_move_callbacks.dm
@@ -315,6 +315,10 @@ All ShuttleMove procs go here
. = ..()
message_admins("Megafauna [src] [ADMIN_FLW(src)] moved via shuttle from [ADMIN_COORDJMP(oldT)] to [ADMIN_COORDJMP(loc)]")
+/mob/living/basic/boss/onShuttleMove(turf/newT, turf/oldT, list/movement_force, move_dir, obj/docking_port/stationary/old_dock, obj/docking_port/mobile/moving_dock)
+ . = ..()
+ message_admins("Megafauna [src] [ADMIN_FLW(src)] moved via shuttle from [ADMIN_COORDJMP(oldT)] to [ADMIN_COORDJMP(loc)]")
+
/************************************Structure move procs************************************/
/obj/structure/grille/beforeShuttleMove(turf/newT, rotation, move_mode, obj/docking_port/mobile/moving_dock)
diff --git a/code/modules/spells/spell_types/teleport/blink.dm b/code/modules/spells/spell_types/teleport/blink.dm
index 18531949a5312..7c365b2e723c4 100644
--- a/code/modules/spells/spell_types/teleport/blink.dm
+++ b/code/modules/spells/spell_types/teleport/blink.dm
@@ -17,3 +17,8 @@
outer_tele_radius = 6
post_teleport_sound = 'sound/effects/magic/blink.ogg'
+
+/datum/action/cooldown/spell/teleport/radius_turf/blink/slow
+ name = "Minor Blink"
+ desc = "This spell randomly teleports you a short distance, you're still practising doing it quickly."
+ cooldown_time = 8 SECONDS
diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm
index ac6c30293d152..1de181b0b491d 100644
--- a/code/modules/surgery/bodyparts/dismemberment.dm
+++ b/code/modules/surgery/bodyparts/dismemberment.dm
@@ -87,7 +87,6 @@
SEND_SIGNAL(owner, COMSIG_CARBON_REMOVE_LIMB, src, special, dismembered)
SEND_SIGNAL(src, COMSIG_BODYPART_REMOVED, owner, special, dismembered)
- update_limb(dropping_limb = TRUE)
bodypart_flags &= ~BODYPART_IMPLANTED //limb is out and about, it can't really be considered an implant
owner.remove_bodypart(src, special)
@@ -96,6 +95,7 @@
LAZYREMOVE(owner.all_scars, scar)
var/mob/living/carbon/phantom_owner = update_owner(null) // so we can still refer to the guy who lost their limb after said limb forgets 'em
+ update_limb(dropping_limb = TRUE)
for(var/datum/wound/wound as anything in wounds)
wound.remove_wound(TRUE)
diff --git a/code/modules/surgery/coronary_bypass.dm b/code/modules/surgery/coronary_bypass.dm
index 1c5012d7580b9..be147e541181c 100644
--- a/code/modules/surgery/coronary_bypass.dm
+++ b/code/modules/surgery/coronary_bypass.dm
@@ -12,7 +12,7 @@
/datum/surgery_step/close,
)
-/datum/surgery/gastrectomy/mechanic
+/datum/surgery/coronary_bypass/mechanic
name = "Engine Diagnostic"
requires_bodypart_type = BODYTYPE_ROBOTIC
steps = list(
diff --git a/code/modules/surgery/organs/internal/heart/heart_ethereal.dm b/code/modules/surgery/organs/internal/heart/heart_ethereal.dm
index 48f10a0613485..9d8ae8be5ee4f 100644
--- a/code/modules/surgery/organs/internal/heart/heart_ethereal.dm
+++ b/code/modules/surgery/organs/internal/heart/heart_ethereal.dm
@@ -228,13 +228,13 @@
playsound(get_turf(regenerating), 'sound/mobs/humanoids/ethereal/ethereal_revive.ogg', 100)
to_chat(regenerating, span_notice("You burst out of the crystal with vigour... But at a cost."))
+ regenerating.revive(HEAL_ALL & ~HEAL_REFRESH_ORGANS)
if(prob(10)) //10% chance for a severe trauma
regenerating.gain_trauma_type(BRAIN_TRAUMA_SEVERE, TRAUMA_RESILIENCE_ABSOLUTE)
else
regenerating.gain_trauma_type(BRAIN_TRAUMA_MILD, TRAUMA_RESILIENCE_ABSOLUTE)
- regenerating.revive(HEAL_ALL & ~HEAL_REFRESH_ORGANS)
// revive calls fully heal -> deletes the crystal.
// this qdeleted check is just for sanity.
if(!QDELETED(src))
diff --git a/code/modules/unit_tests/designs.dm b/code/modules/unit_tests/designs.dm
index 0495ebdc7d9ae..729f2facf81d0 100644
--- a/code/modules/unit_tests/designs.dm
+++ b/code/modules/unit_tests/designs.dm
@@ -15,7 +15,7 @@
TEST_FAIL("Design [current_design.type] has default or null name var but has an ID")
if ((!isnull(current_design.materials) && LAZYLEN(current_design.materials)) || (!isnull(current_design.reagents_list) && LAZYLEN(current_design.reagents_list))) //Design requires materials
if ((isnull(current_design.build_path) || current_design.build_path == default_design.build_path) && (isnull(current_design.make_reagent) || current_design.make_reagent == default_design.make_reagent)) //Check if design gives any output
- TEST_FAIL("Design [current_design.type] requires materials but does not have have any build_path or make_reagent set")
+ TEST_FAIL("Design [current_design.type] requires materials but does not have either build_path or make_reagent set")
else if (!isnull(current_design.build_path) || !isnull(current_design.build_path)) // //Design requires no materials but creates stuff
TEST_FAIL("Design [current_design.type] requires NO materials but has build_path or make_reagent set")
if (length(current_design.reagents_list) && !(current_design.build_type & LIMBGROWER))
diff --git a/code/modules/unit_tests/ethereal_revival.dm b/code/modules/unit_tests/ethereal_revival.dm
index 1420f14d0b4fe..02b73281e85e3 100644
--- a/code/modules/unit_tests/ethereal_revival.dm
+++ b/code/modules/unit_tests/ethereal_revival.dm
@@ -5,6 +5,7 @@
var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human/consistent)
var/obj/item/organ/heart/ethereal/respawn_heart = new()
respawn_heart.Insert(victim, special = TRUE, movement_flags = DELETE_IF_REPLACED) // Pretend this guy is an ethereal
+ victim.mock_client = new()
victim.death()
TEST_ASSERT_NOTNULL(respawn_heart.crystalize_timer_id, "Ethereal heart didn't respond to host death.")
diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm
index 55161807eb46e..39e239a37eda4 100644
--- a/code/modules/unit_tests/simple_animal_freeze.dm
+++ b/code/modules/unit_tests/simple_animal_freeze.dm
@@ -21,7 +21,6 @@
/mob/living/simple_animal/bot/secbot/pingsky,
/mob/living/simple_animal/hostile,
/mob/living/simple_animal/hostile/asteroid,
- /mob/living/simple_animal/hostile/asteroid/curseblob,
/mob/living/simple_animal/hostile/asteroid/elite,
/mob/living/simple_animal/hostile/asteroid/elite/broodmother,
/mob/living/simple_animal/hostile/asteroid/elite/broodmother_child,
@@ -32,7 +31,6 @@
/mob/living/simple_animal/hostile/asteroid/elite/pandora,
/mob/living/simple_animal/hostile/asteroid/polarbear,
/mob/living/simple_animal/hostile/asteroid/polarbear/lesser,
- /mob/living/simple_animal/hostile/dark_wizard,
/mob/living/simple_animal/hostile/illusion,
/mob/living/simple_animal/hostile/illusion/escape,
/mob/living/simple_animal/hostile/illusion/mirage,
@@ -57,20 +55,12 @@
/mob/living/simple_animal/hostile/megafauna/legion/small,
/mob/living/simple_animal/hostile/megafauna/wendigo,
/mob/living/simple_animal/hostile/megafauna/wendigo/noportal,
- /mob/living/simple_animal/hostile/mimic,
- /mob/living/simple_animal/hostile/mimic/copy,
- /mob/living/simple_animal/hostile/mimic/copy/machine,
- /mob/living/simple_animal/hostile/mimic/copy/ranged,
- /mob/living/simple_animal/hostile/mimic/crate,
- /mob/living/simple_animal/hostile/mimic/xenobio,
/mob/living/simple_animal/hostile/ooze,
/mob/living/simple_animal/hostile/ooze/gelatinous,
/mob/living/simple_animal/hostile/ooze/grapes,
/mob/living/simple_animal/hostile/retaliate,
/mob/living/simple_animal/hostile/retaliate/goose,
/mob/living/simple_animal/hostile/retaliate/goose/vomit,
- /mob/living/simple_animal/hostile/vatbeast,
- /mob/living/simple_animal/hostile/zombie,
/mob/living/simple_animal/soulscythe,
// DO NOT ADD NEW ENTRIES TO THIS LIST
// READ THE COMMENT ABOVE
diff --git a/code/modules/vehicles/mecha/combat/durand.dm b/code/modules/vehicles/mecha/combat/durand.dm
index 9095197b93ea0..b9d2ccdb6533e 100644
--- a/code/modules/vehicles/mecha/combat/durand.dm
+++ b/code/modules/vehicles/mecha/combat/durand.dm
@@ -18,7 +18,7 @@
MECHA_R_ARM = 1,
MECHA_UTILITY = 3,
MECHA_POWER = 1,
- MECHA_ARMOR = 3,
+ MECHA_ARMOR = 1,
)
var/obj/durand_shield/shield
diff --git a/code/modules/vehicles/mecha/combat/gygax.dm b/code/modules/vehicles/mecha/combat/gygax.dm
index 0acb746c52d4a..638f53d9aaecf 100644
--- a/code/modules/vehicles/mecha/combat/gygax.dm
+++ b/code/modules/vehicles/mecha/combat/gygax.dm
@@ -18,7 +18,7 @@
MECHA_R_ARM = 1,
MECHA_UTILITY = 3,
MECHA_POWER = 1,
- MECHA_ARMOR = 2,
+ MECHA_ARMOR = 1,
)
step_energy_drain = 4
can_use_overclock = TRUE
diff --git a/code/modules/vehicles/mecha/combat/honker.dm b/code/modules/vehicles/mecha/combat/honker.dm
index 39c5ef1d0e8c4..2a3da129ccd4d 100644
--- a/code/modules/vehicles/mecha/combat/honker.dm
+++ b/code/modules/vehicles/mecha/combat/honker.dm
@@ -57,7 +57,7 @@
MECHA_R_ARM = 1,
MECHA_UTILITY = 3,
MECHA_POWER = 1,
- MECHA_ARMOR = 3,
+ MECHA_ARMOR = 2,
)
/obj/vehicle/sealed/mecha/honker/dark/loaded
diff --git a/code/modules/vehicles/mecha/combat/savannah_ivanov.dm b/code/modules/vehicles/mecha/combat/savannah_ivanov.dm
index ffc4c61e5a87f..60dad9de64ab5 100644
--- a/code/modules/vehicles/mecha/combat/savannah_ivanov.dm
+++ b/code/modules/vehicles/mecha/combat/savannah_ivanov.dm
@@ -35,7 +35,7 @@
MECHA_R_ARM = 1,
MECHA_UTILITY = 3,
MECHA_POWER = 1,
- MECHA_ARMOR = 3,
+ MECHA_ARMOR = 1,
)
//no tax on flying, since the power cost is in the leap itself.
phasing_energy_drain = 0
diff --git a/code/modules/vehicles/mecha/equipment/mecha_equipment.dm b/code/modules/vehicles/mecha/equipment/mecha_equipment.dm
index 2ad10ae028d17..092c8c1044889 100644
--- a/code/modules/vehicles/mecha/equipment/mecha_equipment.dm
+++ b/code/modules/vehicles/mecha/equipment/mecha_equipment.dm
@@ -18,6 +18,8 @@
var/can_be_triggered = FALSE
///Whether the module is currently active
var/active = TRUE
+ ///Can we stack multiple types of the same item?
+ var/unstackable = FALSE
///Label used in the ui next to the Activate/Enable/Disable buttons
var/active_label = "Status"
///Chassis power cell quantity used on activation
@@ -157,6 +159,13 @@
to_chat(user, span_warning("\The [mech]'s left arm is full![mech.equip_by_category[MECHA_R_ARM] || !mech.max_equip_by_category[MECHA_R_ARM] ? "" : " Try right arm!"]"))
return FALSE
return TRUE
+ if(unstackable)
+ var/list/obj/item/mecha_parts/mecha_equipment/contents = mech.equip_by_category[equipment_slot]
+ for(var/obj/equipment as anything in contents)
+ if(src.type == equipment.type)
+ to_chat(user, span_warning("You can't stack more of this item ontop itself!"))
+ return FALSE
+
if(length(mech.equip_by_category[equipment_slot]) == mech.max_equip_by_category[equipment_slot])
to_chat(user, span_warning("This equipment slot is already full!"))
return FALSE
diff --git a/code/modules/vehicles/mecha/equipment/tools/other_tools.dm b/code/modules/vehicles/mecha/equipment/tools/other_tools.dm
index 613f82fe85981..a0a1e7443acbb 100644
--- a/code/modules/vehicles/mecha/equipment/tools/other_tools.dm
+++ b/code/modules/vehicles/mecha/equipment/tools/other_tools.dm
@@ -173,7 +173,7 @@
armor_mod = /datum/armor/mecha_equipment_ccw_boost
/datum/armor/mecha_equipment_ccw_boost
- melee = 15
+ melee = 20
/obj/item/mecha_parts/mecha_equipment/armor/antiproj_armor_booster
name = "Projectile Shielding"
@@ -184,8 +184,8 @@
armor_mod = /datum/armor/mecha_equipment_ranged_boost
/datum/armor/mecha_equipment_ranged_boost
- bullet = 10
- laser = 10
+ bullet = 15
+ laser = 15
////////////////////////////////// REPAIR DROID //////////////////////////////////////////////////
@@ -196,6 +196,7 @@
icon_state = "repair_droid"
energy_drain = 50
range = 0
+ unstackable = TRUE
can_be_toggled = TRUE
active = FALSE
equipment_slot = MECHA_UTILITY
diff --git a/code/modules/vehicles/mecha/mech_fabricator.dm b/code/modules/vehicles/mecha/mech_fabricator.dm
index ad28886d99f22..7667178392a8a 100644
--- a/code/modules/vehicles/mecha/mech_fabricator.dm
+++ b/code/modules/vehicles/mecha/mech_fabricator.dm
@@ -476,7 +476,7 @@
return
if("del_queue_part")
- // Delete a specific from from the queue
+ // Delete a specific from the queue
var/index = text2num(params["index"])
remove_from_queue(index)
diff --git a/code/modules/vehicles/mecha/working/ripley.dm b/code/modules/vehicles/mecha/working/ripley.dm
index c9fb6b8fdbe2a..29bc3fb4d4841 100644
--- a/code/modules/vehicles/mecha/working/ripley.dm
+++ b/code/modules/vehicles/mecha/working/ripley.dm
@@ -259,10 +259,11 @@
GLOBAL_DATUM(cargo_ripley, /obj/vehicle/sealed/mecha/ripley/cargo)
/obj/vehicle/sealed/mecha/ripley/cargo
- desc = "An ailing, old, repurposed cargo hauler. Most of its equipment wires are frayed or missing and its frame is rusted."
name = "\improper APLU \"Big Bess\""
+ desc = "An ailing, old, repurposed cargo hauler. Most of its equipment wires are frayed or missing and its frame is rusted."
icon_state = "hauler"
base_icon_state = "hauler"
+ silicon_icon_state = "hauler-empty"
max_integrity = 100 //Has half the health of a normal RIPLEY mech, so it's harder to use as a weapon.
/obj/vehicle/sealed/mecha/ripley/cargo/Initialize(mapload)
diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm
index 904e2fd874b17..0d9ba49384b27 100644
--- a/code/modules/vending/_vending.dm
+++ b/code/modules/vending/_vending.dm
@@ -273,18 +273,6 @@ GLOBAL_LIST_EMPTY(vending_machines_to_restock)
if(circuit)
circuit.all_products_free = all_products_free //sync up the circuit so the pricing schema is carried over if it's reconstructed.
- else if(HAS_TRAIT(SSstation, STATION_TRAIT_VENDING_SHORTAGE))
- for (var/datum/data/vending_product/product_record as anything in product_records + coin_records + hidden_records)
- /**
- * in average, it should be 37.5% of the max amount, rounded up to the nearest int,
- * tho the max boundary can be as low/high as 50%/100%
- */
- var/max_amount = rand(CEILING(product_record.amount * 0.5, 1), product_record.amount)
- product_record.amount = rand(0, max_amount)
- credits_contained += rand(1, 5) //randomly add a few credits to the machine to make it look like it's been used, proportional to the amount missing.
- if(tiltable && prob(6)) // 1 in 17 chance to start tilted (as an additional hint to the station trait behind it)
- INVOKE_ASYNC(src, PROC_REF(tilt), loc)
- credits_contained = 0 // If it's tilted, it's been looted, so no credits for you.
else if(circuit)
all_products_free = circuit.all_products_free //if it was constructed outside mapload, sync the vendor up with the circuit's var so you can't bypass price requirements by moving / reconstructing it off station.
if(!all_products_free)
diff --git a/code/modules/wiremod/components/utility/wire_bundle.dm b/code/modules/wiremod/components/utility/wire_bundle.dm
new file mode 100644
index 0000000000000..7e0355e894837
--- /dev/null
+++ b/code/modules/wiremod/components/utility/wire_bundle.dm
@@ -0,0 +1,115 @@
+/obj/item/circuit_component/wire_bundle
+ display_name = "Wire Bundle"
+ desc = "A bundle of exposed wires that assemblies can be attached to. Ports will only show up once the circuit is inserted into a shell."
+ category = "Utility"
+ circuit_flags = CIRCUIT_FLAG_REFUSE_MODULE
+
+ var/datum/wires/wire_bundle_component/tracked_wires
+
+ var/list/wire_input_ports = list()
+ var/list/wire_output_ports = list()
+
+/obj/item/circuit_component/wire_bundle/get_ui_notices()
+ . = ..()
+ . += create_ui_notice("Port count is proportional to shell capacity.", "orange", "plug")
+ . += create_ui_notice("Max port count: [MAX_WIRE_COUNT]", "orange", "plug")
+ . += create_ui_notice("Incompatible with assembly shell.", "red", "plug-circle-xmark")
+
+/obj/item/circuit_component/wire_bundle/register_shell(atom/movable/shell)
+ . = ..()
+ if(isassembly(shell) && !parent.admin_only)
+ return
+ if(shell.wires) // Don't add wires to shells that already have some.
+ return
+ tracked_wires = new(shell)
+ shell.set_wires(tracked_wires)
+ for(var/wire in tracked_wires.wires)
+ wire_input_ports[add_input_port("Pulse [wire]", PORT_TYPE_SIGNAL)] = wire
+ wire_output_ports[wire] = add_output_port("[wire] Pulsed", PORT_TYPE_SIGNAL)
+ RegisterSignal(tracked_wires, COMSIG_PULSE_WIRE, PROC_REF(on_pulse_wire))
+ RegisterSignal(shell, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, PROC_REF(on_shell_requesting_context))
+ RegisterSignal(shell, COMSIG_ATOM_ITEM_INTERACTION_SECONDARY, PROC_REF(on_shell_secondary_interaction))
+
+/obj/item/circuit_component/wire_bundle/unregister_shell(atom/movable/shell)
+ . = ..()
+ if(shell.wires != tracked_wires)
+ return
+ UnregisterSignal(shell, list(COMSIG_ATOM_ITEM_INTERACTION_SECONDARY, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM))
+ for(var/color in tracked_wires.colors)
+ var/obj/item/assembly/assembly = tracked_wires.detach_assembly(color)
+ if(assembly)
+ assembly.forceMove(drop_location())
+ shell.set_wires(null)
+ QDEL_NULL(tracked_wires)
+ for(var/datum/port/input/in_port in wire_input_ports)
+ remove_input_port(in_port)
+ for(var/wire in wire_output_ports)
+ var/datum/port/output/out_port = wire_output_ports[wire]
+ remove_output_port(out_port)
+ wire_input_ports.Cut()
+ wire_output_ports.Cut()
+
+/obj/item/circuit_component/wire_bundle/add_to(obj/item/integrated_circuit/added_to)
+ . = ..()
+ if(HAS_TRAIT(added_to, TRAIT_COMPONENT_WIRE_BUNDLE))
+ return FALSE
+ ADD_TRAIT(added_to, TRAIT_COMPONENT_WIRE_BUNDLE, REF(src))
+
+/obj/item/circuit_component/wire_bundle/removed_from(obj/item/integrated_circuit/removed_from)
+ . = ..()
+ REMOVE_TRAIT(removed_from, TRAIT_COMPONENT_WIRE_BUNDLE, REF(src))
+ return ..()
+
+/obj/item/circuit_component/wire_bundle/input_received(datum/port/input/port)
+ . = ..()
+ if(!port)
+ return
+ var/wire = wire_input_ports[port]
+ if(!wire)
+ return
+ if(tracked_wires.is_cut(wire))
+ return
+ var/color = tracked_wires.get_color_of_wire(wire)
+ var/obj/item/assembly/attached = tracked_wires.get_attached(color)
+ attached?.activate()
+
+/obj/item/circuit_component/wire_bundle/proc/on_pulse_wire(source, wire)
+ SIGNAL_HANDLER
+ if(tracked_wires.is_cut(wire))
+ return
+ var/datum/port/output/port = wire_output_ports[wire]
+ if(!istype(port))
+ return
+ port.set_output(COMPONENT_SIGNAL)
+
+/obj/item/circuit_component/wire_bundle/proc/can_access_wires(atom/source)
+ if(ismachinery(source))
+ var/obj/machinery/machine = source
+ return machine.panel_open
+ return TRUE
+
+/obj/item/circuit_component/wire_bundle/proc/on_shell_requesting_context(atom/source, list/context, obj/item/item, mob/user)
+ SIGNAL_HANDLER
+ . = NONE
+
+ if(!is_wire_tool(item))
+ return
+ if(!can_access_wires(source))
+ return
+ context[SCREENTIP_CONTEXT_RMB] = "Interact with wires"
+ return CONTEXTUAL_SCREENTIP_SET
+
+/obj/item/circuit_component/wire_bundle/proc/on_shell_secondary_interaction(atom/source, mob/user, obj/item/tool)
+ SIGNAL_HANDLER
+ if(!is_wire_tool(tool))
+ return
+ if(!can_access_wires(source))
+ return
+ var/datum/component/shell/shell_comp = source.GetComponent(/datum/component/shell)
+ if(shell_comp.locked)
+ source.balloon_alert(user, "locked!")
+ return ITEM_INTERACT_FAILURE
+ if(source.attempt_wire_interaction(user) == WIRE_INTERACTION_BLOCK)
+ return ITEM_INTERACT_BLOCKING
+
+
diff --git a/code/modules/wiremod/shell/assembly.dm b/code/modules/wiremod/shell/assembly.dm
index 3e69331c4c826..033808c84541a 100644
--- a/code/modules/wiremod/shell/assembly.dm
+++ b/code/modules/wiremod/shell/assembly.dm
@@ -7,13 +7,13 @@
name = "circuit assembly"
desc = "A small electronic device that can house an integrated circuit."
icon_state = "wiremod"
- attachable = TRUE
+ assembly_behavior = ASSEMBLY_ALL
/// A reference to any holder to use power from instead of the circuit's own cell
var/atom/movable/power_use_proxy
/// Valid types for `power_use_proxy` to be
- var/static/list/power_use_override_types = list(/obj/machinery, /obj/vehicle/sealed/mecha, /obj/item/mod/control, /mob/living/silicon/robot)
+ var/static/list/power_use_override_types = list(/obj/machinery, /obj/vehicle/sealed/mecha, /obj/item/mod/control, /obj/item/pressure_plate, /mob/living/silicon/robot)
/obj/item/assembly/wiremod/Initialize(mapload)
. = ..()
@@ -23,10 +23,11 @@
), SHELL_CAPACITY_SMALL)
RegisterSignal(shell, COMSIG_SHELL_CIRCUIT_ATTACHED, PROC_REF(on_circuit_attached))
RegisterSignal(shell, COMSIG_SHELL_CIRCUIT_REMOVED, PROC_REF(on_circuit_removed))
- RegisterSignals(src, list(COMSIG_ASSEMBLY_ATTACHED, COMSIG_ASSEMBLY_ADDED_TO_BUTTON), PROC_REF(on_attached))
- RegisterSignals(src, list(COMSIG_ASSEMBLY_DETACHED, COMSIG_ASSEMBLY_REMOVED_FROM_BUTTON), PROC_REF(on_detached))
+ RegisterSignal(src, COMSIG_ASSEMBLY_PRE_ATTACH, PROC_REF(on_pre_attach))
+ RegisterSignals(src, list(COMSIG_ASSEMBLY_ATTACHED, COMSIG_ASSEMBLY_ADDED_TO_BUTTON, COMSIG_ASSEMBLY_ADDED_TO_PRESSURE_PLATE), PROC_REF(on_attached))
+ RegisterSignals(src, list(COMSIG_ASSEMBLY_DETACHED, COMSIG_ASSEMBLY_REMOVED_FROM_BUTTON, COMSIG_ASSEMBLY_REMOVED_FROM_PRESSURE_PLATE), PROC_REF(on_detached))
-/obj/item/assembly/wiremod/proc/on_circuit_attached(_source, obj/item/integrated_circuit/circuit)
+/obj/item/assembly/wiremod/proc/on_circuit_attached(source, obj/item/integrated_circuit/circuit)
SIGNAL_HANDLER
RegisterSignal(circuit, COMSIG_CIRCUIT_PRE_POWER_USAGE, PROC_REF(override_circuit_power_usage))
@@ -34,12 +35,24 @@
SIGNAL_HANDLER
UnregisterSignal(source.attached_circuit, COMSIG_CIRCUIT_PRE_POWER_USAGE)
-/obj/item/assembly/wiremod/proc/on_attached(_source, atom/movable/holder)
+/obj/item/assembly/wiremod/proc/on_pre_attach(obj/item/circuit_component/wire_bundle/source, atom/holder)
+ SIGNAL_HANDLER
+ if(!istype(source))
+ return
+ if(source.parent.admin_only)
+ return
+ if(istype(holder.wires, /datum/wires/wire_bundle_component))
+ var/datum/component/shell/shell_comp = GetComponent(/datum/component/shell)
+ if(shell_comp.attached_circuit.admin_only)
+ return
+ return COMPONENT_CANCEL_ATTACH
+
+/obj/item/assembly/wiremod/proc/on_attached(source, atom/movable/holder)
SIGNAL_HANDLER
if(is_type_in_list(holder, power_use_override_types))
power_use_proxy = holder
-/obj/item/assembly/wiremod/proc/on_detached(_source)
+/obj/item/assembly/wiremod/proc/on_detached(source)
SIGNAL_HANDLER
power_use_proxy = null
@@ -59,6 +72,12 @@
var/obj/item/mod/control/modsuit = power_use_proxy
if(modsuit.subtract_charge(power_to_use))
return COMPONENT_OVERRIDE_POWER_USAGE
+ if(istype(power_use_proxy, /obj/item/pressure_plate))
+ if(!power_use_proxy.anchored)
+ return
+ var/area/our_area = get_area(power_use_proxy)
+ if(our_area.apc?.use_energy(power_to_use, AREA_USAGE_EQUIP))
+ return COMPONENT_OVERRIDE_POWER_USAGE
if(iscyborg(power_use_proxy))
var/mob/living/silicon/robot/borg = power_use_proxy
if(borg.cell?.use(power_to_use, force = TRUE))
diff --git a/code/modules/wiremod/shell/undertile.dm b/code/modules/wiremod/shell/undertile.dm
new file mode 100644
index 0000000000000..69ef66b61c0ea
--- /dev/null
+++ b/code/modules/wiremod/shell/undertile.dm
@@ -0,0 +1,13 @@
+/obj/item/undertile_circuit
+ name = "circuit panel"
+ desc = "A panel for an integrated circuit. It needs to be fit under a floor tile to operate."
+ icon = 'icons/obj/science/circuits.dmi'
+ inhand_icon_state = "flashtool"
+ lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
+ icon_state = "undertile"
+
+/obj/item/undertile_circuit/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE, INVISIBILITY_OBSERVER, use_anchor = TRUE)
+ AddComponent(/datum/component/shell, null, SHELL_CAPACITY_SMALL, SHELL_FLAG_REQUIRE_ANCHOR|SHELL_FLAG_USB_PORT)
diff --git a/code/modules/wiremod/shell/wallmount.dm b/code/modules/wiremod/shell/wallmount.dm
new file mode 100644
index 0000000000000..d461f696da131
--- /dev/null
+++ b/code/modules/wiremod/shell/wallmount.dm
@@ -0,0 +1,33 @@
+/obj/structure/wallmount_circuit
+ name = "circuit box"
+ desc = "A wall-mounted box suitable for the installation of integrated circuits."
+ icon = 'icons/obj/science/circuits.dmi'
+ icon_state = "wallmount"
+ layer = BELOW_OBJ_LAYER
+ anchored = TRUE
+
+ resistance_flags = LAVA_PROOF | FIRE_PROOF
+
+/obj/structure/wallmount_circuit/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/shell, null, SHELL_CAPACITY_LARGE, SHELL_FLAG_REQUIRE_ANCHOR|SHELL_FLAG_USB_PORT)
+
+/obj/structure/wallmount_circuit/wrench_act(mob/living/user, obj/item/tool)
+ var/datum/component/shell/shell_comp = GetComponent(/datum/component/shell)
+ if(shell_comp.locked)
+ balloon_alert(user, "locked!")
+ return ITEM_INTERACT_FAILURE
+ to_chat(user, span_notice("You start unsecuring the circuit box..."))
+ if(tool.use_tool(src, user, 40, volume=50))
+ to_chat(user, span_notice("You unsecure the circuit box."))
+ playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE)
+ deconstruct(TRUE)
+ return ITEM_INTERACT_SUCCESS
+
+/obj/item/wallframe/circuit
+ name = "circuit box frame"
+ desc = "A box that can be mounted on a wall and have circuits installed."
+ icon = 'icons/obj/science/circuits.dmi'
+ icon_state = "wallmount_assembly"
+ result_path = /obj/structure/wallmount_circuit
+ pixel_shift = 32
diff --git a/html/changelogs/AutoChangeLog-pr-88489.yml b/html/changelogs/AutoChangeLog-pr-88489.yml
new file mode 100644
index 0000000000000..4e99c93de541f
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-88489.yml
@@ -0,0 +1,5 @@
+author: "Ghommie"
+delete-after: True
+changes:
+ - bugfix: "Fixed single-use spectral instruments losing their powers before skeletonizing anyone."
+ - rscadd: "A very rare spooky suffix for mythril items and the wizard RPG event."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-88714.yml b/html/changelogs/AutoChangeLog-pr-88714.yml
new file mode 100644
index 0000000000000..a36e42c37e426
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-88714.yml
@@ -0,0 +1,4 @@
+author: "mc-oofert"
+delete-after: True
+changes:
+ - rscadd: "outpost 31, the icebox ruin. Also its associated mobs, and megafauna, and loot. Im not spoiling anything, find it yourself."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-88860.yml b/html/changelogs/AutoChangeLog-pr-88860.yml
new file mode 100644
index 0000000000000..2fd3537480d43
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-88860.yml
@@ -0,0 +1,7 @@
+author: "carlarctg"
+delete-after: True
+changes:
+ - rscadd: "Added two new fish to heretic rift fishing."
+ - rscadd: "You can now fish up arms, heads, and other items lost to heretic rifts!"
+ - admin: "objectify() now works with instances of objects. Mark a player, then an object, and use those marks to call that global proc and you can turn people into pre-existing items."
+ - rscadd: "Psychic resistance now prevents the instadeath from trying to telekinetically grasp at a opened rift."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-88935.yml b/html/changelogs/AutoChangeLog-pr-88935.yml
new file mode 100644
index 0000000000000..fed414acbf745
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-88935.yml
@@ -0,0 +1,4 @@
+author: "StrangeWeirdKitten"
+delete-after: True
+changes:
+ - bugfix: "Fixes uncapped xenobio slime multiplication which can easily result in hundreds of slimes."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-89079.yml b/html/changelogs/AutoChangeLog-pr-89079.yml
new file mode 100644
index 0000000000000..39d5c4fad3b53
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-89079.yml
@@ -0,0 +1,4 @@
+author: "jlsnow301"
+delete-after: True
+changes:
+ - bugfix: "Fixed some bugs in the malf ai screen: one bluescreen, an extra colon, modules view"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-89081.yml b/html/changelogs/AutoChangeLog-pr-89081.yml
new file mode 100644
index 0000000000000..8c5d863c2f565
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-89081.yml
@@ -0,0 +1,4 @@
+author: "Absolucy"
+delete-after: True
+changes:
+ - admin: "Added a new function in Lua scripting, SS13.check_tick, to avoid causing too much lag during loops and such."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-89098.yml b/html/changelogs/AutoChangeLog-pr-89098.yml
new file mode 100644
index 0000000000000..8ef4038a73557
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-89098.yml
@@ -0,0 +1,6 @@
+author: "Jacquerel"
+delete-after: True
+changes:
+ - balance: "Phobias are now somewhat less debilitating, although their effects get worse the longer you are near a fear trigger."
+ - balance: "Carphobes are now afraid of all fish."
+ - rscdel: "Remove the Heretic phobia, as it is now basically unused."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-89099.yml b/html/changelogs/AutoChangeLog-pr-89099.yml
new file mode 100644
index 0000000000000..fa5f745afa077
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-89099.yml
@@ -0,0 +1,4 @@
+author: "Absolucy"
+delete-after: True
+changes:
+ - qol: "Restyled the Who, Adminwho, and Show Server Revision verbs to be prettier."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-89109.yml b/html/changelogs/AutoChangeLog-pr-89109.yml
new file mode 100644
index 0000000000000..0b91701e6eba3
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-89109.yml
@@ -0,0 +1,5 @@
+author: "SyncIt21"
+delete-after: True
+changes:
+ - bugfix: "chemical reactions can now go all the way to their min required temp/ph"
+ - code_imp: "improved code for debug chem master."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-89120.yml b/html/changelogs/AutoChangeLog-pr-89120.yml
new file mode 100644
index 0000000000000..7502cfb609e89
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-89120.yml
@@ -0,0 +1,4 @@
+author: "MichiRecRoom"
+delete-after: True
+changes:
+ - qol: "pAIs can now withdraw their candidacy at any time."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-89121.yml b/html/changelogs/AutoChangeLog-pr-89121.yml
new file mode 100644
index 0000000000000..5575a4642cfcc
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-89121.yml
@@ -0,0 +1,4 @@
+author: "Likteer"
+delete-after: True
+changes:
+ - rscdel: "Removed vending products shortage station trait"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-89146.yml b/html/changelogs/AutoChangeLog-pr-89146.yml
new file mode 100644
index 0000000000000..fd4a423865d5e
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-89146.yml
@@ -0,0 +1,5 @@
+author: "JohnFulpWillard"
+delete-after: True
+changes:
+ - balance: "AIs can now examine through their eye."
+ - bugfix: "Dullahans can also examine through their head again."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-89147.yml b/html/changelogs/AutoChangeLog-pr-89147.yml
new file mode 100644
index 0000000000000..2835439df2c55
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-89147.yml
@@ -0,0 +1,4 @@
+author: "Jacquerel"
+delete-after: True
+changes:
+ - balance: "Dark Wizards now teleport when attacked, but are more likely to turn on their allies"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-89158.yml b/html/changelogs/AutoChangeLog-pr-89158.yml
new file mode 100644
index 0000000000000..3b565b442226e
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-89158.yml
@@ -0,0 +1,4 @@
+author: "Jacquerel"
+delete-after: True
+changes:
+ - refactor: "The Cytology Vatbeast now uses the basic mob framework, please report any unusual behaviour."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-89159.yml b/html/changelogs/AutoChangeLog-pr-89159.yml
new file mode 100644
index 0000000000000..d3ac434e0a49e
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-89159.yml
@@ -0,0 +1,4 @@
+author: "Seefaaa"
+delete-after: True
+changes:
+ - admin: "added Space Dragon role to the banning panel"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-89162.yml b/html/changelogs/AutoChangeLog-pr-89162.yml
new file mode 100644
index 0000000000000..f820a83012843
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-89162.yml
@@ -0,0 +1,4 @@
+author: "MichiRecRoom"
+delete-after: True
+changes:
+ - bugfix: "Word phobias should always apply now, rather than occasionally being missed."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-89166.yml b/html/changelogs/AutoChangeLog-pr-89166.yml
new file mode 100644
index 0000000000000..275a15c912af2
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-89166.yml
@@ -0,0 +1,4 @@
+author: "Absolucy"
+delete-after: True
+changes:
+ - qol: "Removed the arbitrary limit of 10 maximum highlights."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-89168.yml b/html/changelogs/AutoChangeLog-pr-89168.yml
new file mode 100644
index 0000000000000..875982c404614
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-89168.yml
@@ -0,0 +1,4 @@
+author: "MX0739"
+delete-after: True
+changes:
+ - bugfix: "objects with throw_range 0 now properly parried with baseball bats"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-89174.yml b/html/changelogs/AutoChangeLog-pr-89174.yml
new file mode 100644
index 0000000000000..2e19289c21d6f
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-89174.yml
@@ -0,0 +1,7 @@
+author: "SmArtKar"
+delete-after: True
+changes:
+ - qol: "Snapping tethers now also removes their beacons"
+ - qol: "You can now cut tethers that you're attached to while in motion"
+ - qol: "Tethers now snap when you retract your gloves or disable your MODsuit"
+ - bugfix: "Fixed tether stacking issues"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-89175.yml b/html/changelogs/AutoChangeLog-pr-89175.yml
new file mode 100644
index 0000000000000..400b08e2b2c1a
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-89175.yml
@@ -0,0 +1,4 @@
+author: "SmArtKar"
+delete-after: True
+changes:
+ - qol: "Action palette no longer disappears while you have floating actions"
\ No newline at end of file
diff --git a/html/changelogs/archive/2025-01.yml b/html/changelogs/archive/2025-01.yml
index 72a590d411f3c..684a41a445705 100644
--- a/html/changelogs/archive/2025-01.yml
+++ b/html/changelogs/archive/2025-01.yml
@@ -526,3 +526,78 @@
- image: yet another medkit resprite
SmArtKar:
- bugfix: Fixed active turfs on the new Turreted Outpost ruin.
+2025-01-20:
+ RengaN02:
+ - bugfix: Fixed Engine Diagnostic surgery
+ SmArtKar:
+ - image: Resprited default and mirage grenades
+ - image: Made the unholy water flask darker
+ Y0SH1M4S73R:
+ - refactor: Wires and assemblies have been refactored to have directionality to
+ them. This mostly makes it so that assemblies can only be attached to wires
+ it would make sense for them to be attached to.
+ - qol: Pressure plates can now also accept igniters, condensers, flashes, assembly
+ shells, and door controllers.
+ - rscadd: Undertile circuit shells. They only work when placed under floor tiles,
+ but support USB cables and use APC power instead of cell power.
+ - rscadd: Wallmounted circuit shells. Large shells that support USB cables and use
+ APC power instead of cell power.
+ - rscadd: Wire bundle component. Adds a number of wires to the circuit proportional
+ to the capacity of the shell, allowing you to use assemblies in circuit logic.
+ mc-oofert:
+ - refactor: mimics (bolt of animation, malf ai Machine Override, etc) are basicmobs
+ - bugfix: crate mimics may now be opened
+2025-01-21:
+ AyIong:
+ - qol: 'Vending machines got new design with 2 layouts: List and Grid (List by default)'
+ NamelessFairy:
+ - bugfix: Admin fired stray syndicate cargo pods will not rebel against admin whims
+ and launch themselves when cancelled.
+ - admin: Admins have new categories to fill syndicate cargo pods with.
+ Pickle-Coding:
+ - bugfix: Fixes HFR moderator leaking not leaking properly when a part is cracked.
+ Rhials:
+ - rscadd: You can now ctrl-click IDs to set your Honorific, which will change your
+ display name to reflect your TITLE as a WORKING MAN on SPACE STATION 13.
+2025-01-22:
+ SyncIt21:
+ - bugfix: pockets update their icons correctly when removing items that have storage
+ (e.g. box of bandages) from them
+ - bugfix: fishing rod auto reel won't rip of intercoms or other anchored/immovable
+ objects
+ kuricityy:
+ - bugfix: Blob's can no longer place their core in ruins on Icebox.
+ necromanceranne:
+ - balance: Batons now respect the armor worn by targets. Analog batons respect MELEE
+ armor. Cell-type batons respect ENERGY armor.
+ - balance: Various batons have differing amounts of armour penetration based on
+ what type of baton it is.
+ - balance: Heads of staff have color graded batons to denote penetration power.
+ Bronze (Quartermaster), Silver (Chief Engineer, Chief Medical Officer, Head
+ of Personnel, Research Director), Gold (Captain). Contractor batons are equivalent
+ to Gold.
+ - balance: Cell-type batons gain armor penetration based on their cell's quality.
+ The better it is, the more it penetrates.
+ - bugfix: The Big Bess cargo hauler no longer magically becomes (visually) a standard
+ Ripley exosuit.
+ the-og-gear:
+ - bugfix: Deluxe Donk Pockets (and their no-carb and vegan variants) are no longer
+ craftable without collecting the recipe.
+2025-01-23:
+ Ghommie:
+ - bugfix: Chasms are once again incompatible with explosive fishing.
+ Jacquerel:
+ - refactor: NPC zombies found in ruins now use the basic mob framework. Please make
+ an issue report if they exhibit any unusual behaviour.
+ Melbert:
+ - bugfix: Heads with brains no longer look debrained
+ SmArtKar:
+ - bugfix: Clipboards should no longer retain pens that got removed via Instant Recall
+ StrangeWeirdKitten:
+ - balance: Reduces armor slots to 1 for all station built mechs except the phazon.
+ - balance: melee mech armor has been increased to 20 from 15.
+ - balance: mech bullet and laser armor has both been increased to 15 from 10.
+ - balance: You can no longer stack the repair droid on mechs.
+ Vekter:
+ - balance: The roundstart report will now display a more broad, less specific message
+ about threat levels when between 0 and 80 threat.
diff --git a/html/statbrowser.css b/html/statbrowser.css
index fb87c99b41cf3..810013209ca30 100644
--- a/html/statbrowser.css
+++ b/html/statbrowser.css
@@ -150,6 +150,7 @@ img {
}
.grid-item:hover .grid-item-text {
+ height: 100%;
overflow: visible;
white-space: normal;
background-color: #ececec;
diff --git a/icons/mob/human/species/misc/bodypart_overlay_simple.dmi b/icons/mob/human/species/misc/bodypart_overlay_simple.dmi
index 2076bd32be110..e17934a6cdb43 100644
Binary files a/icons/mob/human/species/misc/bodypart_overlay_simple.dmi and b/icons/mob/human/species/misc/bodypart_overlay_simple.dmi differ
diff --git a/icons/mob/inhands/equipment/security_lefthand.dmi b/icons/mob/inhands/equipment/security_lefthand.dmi
index 993834e0d6084..91306c0a093c1 100644
Binary files a/icons/mob/inhands/equipment/security_lefthand.dmi and b/icons/mob/inhands/equipment/security_lefthand.dmi differ
diff --git a/icons/mob/inhands/equipment/security_righthand.dmi b/icons/mob/inhands/equipment/security_righthand.dmi
index 7d8787e3fe68e..c6d26854eb6da 100644
Binary files a/icons/mob/inhands/equipment/security_righthand.dmi and b/icons/mob/inhands/equipment/security_righthand.dmi differ
diff --git a/icons/mob/simple/animal.dmi b/icons/mob/simple/animal.dmi
index 62e36fad550b4..5c419b03cb89f 100644
Binary files a/icons/mob/simple/animal.dmi and b/icons/mob/simple/animal.dmi differ
diff --git a/icons/mob/simple/icemoon/thething.dmi b/icons/mob/simple/icemoon/thething.dmi
new file mode 100644
index 0000000000000..7486749471a1f
Binary files /dev/null and b/icons/mob/simple/icemoon/thething.dmi differ
diff --git a/icons/mob/telegraphing/telegraph.dmi b/icons/mob/telegraphing/telegraph.dmi
index b1ff26a4a1097..c655de326f750 100644
Binary files a/icons/mob/telegraphing/telegraph.dmi and b/icons/mob/telegraphing/telegraph.dmi differ
diff --git a/icons/obj/aquarium/rift.dmi b/icons/obj/aquarium/rift.dmi
index 63c739fb32f9b..83c7c967cef77 100644
Binary files a/icons/obj/aquarium/rift.dmi and b/icons/obj/aquarium/rift.dmi differ
diff --git a/icons/obj/aquarium/wide.dmi b/icons/obj/aquarium/wide.dmi
index 02393fb3a7f7c..82fd29e714952 100644
Binary files a/icons/obj/aquarium/wide.dmi and b/icons/obj/aquarium/wide.dmi differ
diff --git a/icons/obj/medical/organs/organs.dmi b/icons/obj/medical/organs/organs.dmi
index e525a0fd32d59..ded858f2e36ba 100644
Binary files a/icons/obj/medical/organs/organs.dmi and b/icons/obj/medical/organs/organs.dmi differ
diff --git a/icons/obj/mining_zones/artefacts.dmi b/icons/obj/mining_zones/artefacts.dmi
index 968bbe5b621d3..a12fdb670429b 100644
Binary files a/icons/obj/mining_zones/artefacts.dmi and b/icons/obj/mining_zones/artefacts.dmi differ
diff --git a/icons/obj/science/circuits.dmi b/icons/obj/science/circuits.dmi
index 1d50c67823bb2..e91025ae61365 100644
Binary files a/icons/obj/science/circuits.dmi and b/icons/obj/science/circuits.dmi differ
diff --git a/icons/obj/structures.dmi b/icons/obj/structures.dmi
index 9c19b982c952b..a77b88944cb37 100644
Binary files a/icons/obj/structures.dmi and b/icons/obj/structures.dmi differ
diff --git a/icons/obj/weapons/baton.dmi b/icons/obj/weapons/baton.dmi
index 2d5100ec4d414..6b7cbd8544ac3 100644
Binary files a/icons/obj/weapons/baton.dmi and b/icons/obj/weapons/baton.dmi differ
diff --git a/lua/SS13_base.lua b/lua/SS13_base.lua
index 23f464bf328ab..470288af87e4d 100644
--- a/lua/SS13_base.lua
+++ b/lua/SS13_base.lua
@@ -39,6 +39,13 @@ function SS13.is_valid(datum)
return dm.is_valid_ref(datum) and not datum.gc_destroyed
end
+function SS13.check_tick(high_priority)
+ local tick_limit = if high_priority then 95 else dm.global_vars.Master.current_ticklimit
+ if dm.world.tick_usage > tick_limit then
+ sleep()
+ end
+end
+
function SS13.await(thing_to_call, proc_to_call, ...)
if not SS13.istype(thing_to_call, "/datum") then
thing_to_call = SS13.global_proc
diff --git a/sound/mobs/non-humanoids/fish/fish_psyblast.ogg b/sound/mobs/non-humanoids/fish/fish_psyblast.ogg
new file mode 100644
index 0000000000000..72addd7e442d5
Binary files /dev/null and b/sound/mobs/non-humanoids/fish/fish_psyblast.ogg differ
diff --git a/strings/phobia.json b/strings/phobia.json
index 45e7485a91966..b3702ee6cf11b 100644
--- a/strings/phobia.json
+++ b/strings/phobia.json
@@ -1,17 +1,17 @@
{
"aliens": [
"abductee",
+ "abduction",
"abductor",
"alien",
"ayy lmao",
"ayys",
"facehugger",
"greys",
- "jelly",
- "lusty",
"mothership",
"resin",
"slime",
+ "ufo",
"xeno",
"xenobiology",
"xenomorph"
@@ -47,11 +47,12 @@
"authority": [
"admiral",
+ "boss",
"cap",
"captain",
"ce",
"centcom",
- "centcom",
+ "centcomm",
"chief",
"cmo",
"comdom",
@@ -75,21 +76,28 @@
"beak",
"bird",
"birdemic",
+ "caw",
"chick",
- "claw",
+ "chirp",
"cracker",
+ "crow",
"eagle",
"feather",
"flap",
"fowl",
"griffin",
+ "griffon",
+ "hawk",
"ka kaw",
"kakaw",
"ki ki ya ya",
"kiki yaya",
"owl",
+ "parrot",
"peck",
"poly",
+ "raptor",
+ "raven",
"vox",
"wing"
],
@@ -102,6 +110,8 @@
"bloodloss",
"bloody",
"dialysis",
+ "exsanguinate",
+ "exsanguinated",
"gib",
"gibbed",
"gibs",
@@ -110,30 +120,20 @@
"hemorrhaged",
"hemorrhaging",
"iv",
- "transfusion"
- ],
-
- "carps": [
- "aquarium",
- "carp",
- "carps",
- "carpotoxin",
- "fin",
- "fins",
- "fish",
- "fang",
- "gnash",
- "migration",
- "slash",
- "shred",
- "teeth",
- "tooth"
+ "leech",
+ "transfusion",
+ "vampire"
],
"clowns": [
"banana",
"clown",
+ "creampie",
+ "cream pie",
"honk",
+ "horn",
+ "jester",
+ "red nose",
"slip",
"slipped",
"slipping"
@@ -165,8 +165,10 @@
"hos",
"illuminati",
"jet fuel",
+ "mask",
"nanotrasen",
"qm",
+ "quarantine",
"quarter master",
"quartermaster",
"rd",
@@ -176,6 +178,9 @@
"steel beam",
"ufo",
"globo-homo",
+ "vaccine",
+ "vaccinated",
+ "vaccination",
"xeno"
],
@@ -194,7 +199,11 @@
"md",
"medicine",
"nurse",
+ "pill",
"sleeper",
+ "vaccine",
+ "vaccinated",
+ "vaccination",
"viro",
"virologist",
"virology"
@@ -202,15 +211,40 @@
"falling": [
"chasm",
- "fall",
- "fell",
"hang in there",
"hold on",
"hole",
"i'll catch you",
"let go",
- "pit",
- "slip"
+ "pit"
+ ],
+
+ "fish": [
+ "angler",
+ "angling",
+ "aquarium",
+ "bait",
+ "carp",
+ "carps",
+ "carpotoxin",
+ "cod",
+ "fin",
+ "fins",
+ "fish",
+ "fishing",
+ "fisherman",
+ "fang",
+ "gnash",
+ "hooked",
+ "minnow",
+ "salmon",
+ "sashimi",
+ "shark",
+ "small fry",
+ "sushi",
+ "trout",
+ "migration",
+ "rod"
],
"greytide": [
@@ -232,7 +266,6 @@
"ammo",
"armory",
"ballistic",
- "beam",
"buckshot",
"bulldog",
"bullet",
@@ -241,7 +274,6 @@
"carbine",
"cartridge",
"casing",
- "dart",
"deagle",
"emitter",
"firearm",
@@ -280,55 +312,31 @@
"wt550"
],
- "heresy": [
- "armsy",
- "ash",
- "blade",
- "cloak",
- "codex",
- "cosmic",
- "eldritch",
- "fire shark",
- "flesh",
- "focus",
- "ghoul",
- "grasp",
- "hand",
- "heart",
- "heresy",
- "heretic",
- "lionhunter",
- "maid in the mirror",
- "mansus",
- "offer",
- "pierced reality",
- "raw prophet",
- "reality crack",
- "reality pierce",
- "ritual",
- "robe",
- "rune",
- "rust",
- "sacrifice",
- "space",
- "stalker",
- "star",
- "void",
- "worm"
- ],
-
"insects": [
+ "ant",
+ "ants",
"bee",
+ "butterfly",
"buzz",
"cockroach",
+ "cocoon",
"flutter",
"fly",
"glockroach",
+ "grub",
+ "hive",
+ "infest",
+ "infested",
+ "infestation",
"insect",
+ "insects",
"moff",
"moth",
+ "mothroach",
"roach",
- "sting"
+ "sting",
+ "swarm",
+ "worm"
],
"lizards": [
@@ -336,9 +344,10 @@
"hissed",
"hissing",
"lizard",
- "wag",
- "wagged",
- "wagging"
+ "scale",
+ "scaly",
+ "scaley",
+ "scalie"
],
"ocky icky": [
@@ -350,6 +359,7 @@
"antagonist",
"ban",
"banned",
+ "batong",
"chat",
"click",
"c*der",
@@ -358,6 +368,10 @@
"discord",
"erp",
"forum",
+ "griff",
+ "griffing",
+ "headmin",
+ "host",
"ick ock",
"joever",
"k",
@@ -366,6 +380,7 @@
"leddit",
"lmao",
"lol",
+ "maintainer",
"mapper",
"moderator",
"mods",
@@ -373,6 +388,7 @@
"murderboning",
"ocky",
"ooc",
+ "omg",
"owo",
"pwn",
"powergame",
@@ -391,6 +407,7 @@
"uwa",
"uwu",
"valid",
+ "wtf",
"y",
"5head"
],
@@ -404,37 +421,55 @@
"borg",
"bot",
"cyborg",
+ "drone",
+ "positronic",
"robot",
"silicon",
"silly cone"
],
"security": [
- "sec",
- "security",
- "shitcurity",
+ "arrest",
"baton",
- "taser",
+ "batong",
+ "behind bars",
"beepsky",
- "hos",
"brig",
- "gulag"
+ "genpop",
+ "gulag",
+ "holding cell",
+ "hos",
+ "jail",
+ "prison",
+ "solitary confinement",
+ "the nick",
+ "the slammer",
+ "sec",
+ "security",
+ "shitcurity",
+ "taser"
],
"skeletons": [
"bone",
"calcium",
"doot",
- "milk",
+ "rib",
+ "ribs",
"skeleton",
+ "skull",
+ "tibia",
"the ride never ends",
"xylophone"
],
"snakes": [
+ "adder",
"anaconda",
- "boop",
+ "cobra",
+ "constrictor",
"fangs",
+ "mamba",
"python",
"slither",
"snake",
@@ -444,18 +479,31 @@
],
"space": [
+ "asteroid",
+ "EVA",
+ "comet",
+ "cosmic",
+ "galactic",
+ "galaxy",
+ "intergalactic",
+ "meteor",
+ "meteorite",
+ "nebula",
"space",
+ "spess",
"star",
"universe",
"void",
- "galaxy",
- "spess"
+ "voidwalker"
],
"spiders": [
+ "arachnid",
+ "black widow",
+ "cocoon",
"spider",
- "web",
- "arachnid"
+ "tarantula",
+ "web"
],
"strangers": [
@@ -466,19 +514,19 @@
],
"the supernatural": [
- "brass",
- "chapel",
- "chaplain",
- "chappy",
- "clockwork",
"cult",
"cultist",
+ "curse",
"demon",
+ "devil",
"ei nath",
"eldritch",
+ "ghast",
"ghost",
- "god",
- "goddess",
+ "ghoul",
+ "haunt",
+ "haunting",
+ "haunted",
"heretic",
"lich",
"magic",
@@ -489,19 +537,22 @@
"nar-sie",
"nightmare",
"ratvar",
- "religion",
+ "revenant",
"ritual",
"rune",
"sigil",
+ "specter",
+ "spectre",
+ "spell",
+ "summon",
+ "summoning",
"talisman",
"tome",
"vampire",
+ "voidwalker",
+ "witch",
"wiz",
"wizard",
"zombie"
]
-
-
-
-
}
diff --git a/tgstation.dme b/tgstation.dme
index d446638b986f8..2150ba99ef977 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -392,6 +392,7 @@
#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_guardian.dm"
#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_living.dm"
#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_main.dm"
+#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_megafauna.dm"
#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_silicon.dm"
#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_simple.dm"
#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_spawner.dm"
@@ -1993,6 +1994,7 @@
#include "code\datums\status_effects\debuffs\hooked.dm"
#include "code\datums\status_effects\debuffs\jitteriness.dm"
#include "code\datums\status_effects\debuffs\pacifism.dm"
+#include "code\datums\status_effects\debuffs\phobia.dm"
#include "code\datums\status_effects\debuffs\rust_corruption.dm"
#include "code\datums\status_effects\debuffs\screen_blur.dm"
#include "code\datums\status_effects\debuffs\screwy_hud.dm"
@@ -2065,6 +2067,7 @@
#include "code\datums\wires\syndicatebomb.dm"
#include "code\datums\wires\tesla_coil.dm"
#include "code\datums\wires\vending.dm"
+#include "code\datums\wires\wire_bundle_component.dm"
#include "code\datums\wounds\_wound_static_data.dm"
#include "code\datums\wounds\_wounds.dm"
#include "code\datums\wounds\blunt.dm"
@@ -4928,6 +4931,11 @@
#include "code\modules\mob\living\basic\blob_minions\blob_spore.dm"
#include "code\modules\mob\living\basic\blob_minions\blob_zombie.dm"
#include "code\modules\mob\living\basic\blob_minions\blobbernaut.dm"
+#include "code\modules\mob\living\basic\boss\boss.dm"
+#include "code\modules\mob\living\basic\boss\thing\thing.dm"
+#include "code\modules\mob\living\basic\boss\thing\thing_abilities.dm"
+#include "code\modules\mob\living\basic\boss\thing\thing_ai.dm"
+#include "code\modules\mob\living\basic\boss\thing\thing_objects.dm"
#include "code\modules\mob\living\basic\bots\_bots.dm"
#include "code\modules\mob\living\basic\bots\bot_ai.dm"
#include "code\modules\mob\living\basic\bots\bot_hud.dm"
@@ -4960,6 +4968,7 @@
#include "code\modules\mob\living\basic\cult\constructs\juggernaut.dm"
#include "code\modules\mob\living\basic\cult\constructs\proteon.dm"
#include "code\modules\mob\living\basic\cult\constructs\wraith.dm"
+#include "code\modules\mob\living\basic\cytology\vatbeast.dm"
#include "code\modules\mob\living\basic\drone\_drone.dm"
#include "code\modules\mob\living\basic\drone\drone_say.dm"
#include "code\modules\mob\living\basic\drone\drone_tools.dm"
@@ -5127,12 +5136,17 @@
#include "code\modules\mob\living\basic\pets\penguin\penguin_ai.dm"
#include "code\modules\mob\living\basic\pets\pet_cult\pet_cult_abilities.dm"
#include "code\modules\mob\living\basic\pets\pet_cult\pet_cult_ai.dm"
+#include "code\modules\mob\living\basic\ruin_defender\blob_of_flesh.dm"
#include "code\modules\mob\living\basic\ruin_defender\cybersun_aicore.dm"
+#include "code\modules\mob\living\basic\ruin_defender\dark_wizard.dm"
#include "code\modules\mob\living\basic\ruin_defender\flesh.dm"
#include "code\modules\mob\living\basic\ruin_defender\living_floor.dm"
#include "code\modules\mob\living\basic\ruin_defender\mad_piano.dm"
#include "code\modules\mob\living\basic\ruin_defender\skeleton.dm"
#include "code\modules\mob\living\basic\ruin_defender\stickman.dm"
+#include "code\modules\mob\living\basic\ruin_defender\zombie.dm"
+#include "code\modules\mob\living\basic\ruin_defender\mimic\mimic.dm"
+#include "code\modules\mob\living\basic\ruin_defender\mimic\mimic_ai.dm"
#include "code\modules\mob\living\basic\ruin_defender\wizard\wizard.dm"
#include "code\modules\mob\living\basic\ruin_defender\wizard\wizard_ai.dm"
#include "code\modules\mob\living\basic\ruin_defender\wizard\wizard_spells.dm"
@@ -5406,13 +5420,9 @@
#include "code\modules\mob\living\simple_animal\bot\mulebot.dm"
#include "code\modules\mob\living\simple_animal\bot\secbot.dm"
#include "code\modules\mob\living\simple_animal\bot\SuperBeepsky.dm"
-#include "code\modules\mob\living\simple_animal\hostile\dark_wizard.dm"
#include "code\modules\mob\living\simple_animal\hostile\hostile.dm"
#include "code\modules\mob\living\simple_animal\hostile\illusion.dm"
-#include "code\modules\mob\living\simple_animal\hostile\mimic.dm"
#include "code\modules\mob\living\simple_animal\hostile\ooze.dm"
-#include "code\modules\mob\living\simple_animal\hostile\vatbeast.dm"
-#include "code\modules\mob\living\simple_animal\hostile\zombie.dm"
#include "code\modules\mob\living\simple_animal\hostile\megafauna\_megafauna.dm"
#include "code\modules\mob\living\simple_animal\hostile\megafauna\blood_drunk_miner.dm"
#include "code\modules\mob\living\simple_animal\hostile\megafauna\bubblegum.dm"
@@ -5423,7 +5433,6 @@
#include "code\modules\mob\living\simple_animal\hostile\megafauna\hierophant.dm"
#include "code\modules\mob\living\simple_animal\hostile\megafauna\legion.dm"
#include "code\modules\mob\living\simple_animal\hostile\megafauna\wendigo.dm"
-#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\curse_blob.dm"
#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\mining_mobs.dm"
#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\polarbear.dm"
#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\elites\elite.dm"
@@ -6532,6 +6541,7 @@
#include "code\modules\wiremod\components\utility\timepiece.dm"
#include "code\modules\wiremod\components\utility\typecast.dm"
#include "code\modules\wiremod\components\utility\typecheck.dm"
+#include "code\modules\wiremod\components\utility\wire_bundle.dm"
#include "code\modules\wiremod\components\variables\getter.dm"
#include "code\modules\wiremod\components\variables\setter.dm"
#include "code\modules\wiremod\components\wirenet\receive.dm"
@@ -6580,6 +6590,8 @@
#include "code\modules\wiremod\shell\scanner_gate.dm"
#include "code\modules\wiremod\shell\server.dm"
#include "code\modules\wiremod\shell\shell_items.dm"
+#include "code\modules\wiremod\shell\undertile.dm"
+#include "code\modules\wiremod\shell\wallmount.dm"
#include "code\modules\zombie\items.dm"
#include "code\modules\zombie\organs.dm"
#include "code\ze_genesis_call\genesis_call.dm"
diff --git a/tgui/packages/tgui-panel/settings/TextHighlight.tsx b/tgui/packages/tgui-panel/settings/TextHighlight.tsx
index b4f1932fed7b6..605b7e1e9d92f 100644
--- a/tgui/packages/tgui-panel/settings/TextHighlight.tsx
+++ b/tgui/packages/tgui-panel/settings/TextHighlight.tsx
@@ -4,6 +4,7 @@ import {
Button,
ColorBox,
Divider,
+ Icon,
Input,
Section,
Stack,
@@ -16,7 +17,7 @@ import {
removeHighlightSetting,
updateHighlightSetting,
} from './actions';
-import { MAX_HIGHLIGHT_SETTINGS } from './constants';
+import { WARN_AFTER_HIGHLIGHT_AMT } from './constants';
import {
selectHighlightSettingById,
selectHighlightSettings,
@@ -36,8 +37,8 @@ export function TextHighlightSettings(props) {
mb={i + 1 === highlightSettings.length ? 0 : '10px'}
/>
))}
- {highlightSettings.length < MAX_HIGHLIGHT_SETTINGS && (
-
+
+
-
- )}
+ {highlightSettings.length >= WARN_AFTER_HIGHLIGHT_AMT && (
+
+
+ Large amounts of highlights can potentially cause performance
+ issues!
+
+ )}
+
+
diff --git a/tgui/packages/tgui-panel/settings/constants.ts b/tgui/packages/tgui-panel/settings/constants.ts
index d98be914e9533..3fd4e5c2e4ad1 100644
--- a/tgui/packages/tgui-panel/settings/constants.ts
+++ b/tgui/packages/tgui-panel/settings/constants.ts
@@ -40,4 +40,4 @@ export const FONTS = [
'Lucida Console',
];
-export const MAX_HIGHLIGHT_SETTINGS = 10;
+export const WARN_AFTER_HIGHLIGHT_AMT = 10;
diff --git a/tgui/packages/tgui-panel/settings/reducer.ts b/tgui/packages/tgui-panel/settings/reducer.ts
index 5f6033a14a81f..7a94da4de2b42 100644
--- a/tgui/packages/tgui-panel/settings/reducer.ts
+++ b/tgui/packages/tgui-panel/settings/reducer.ts
@@ -14,7 +14,7 @@ import {
updateHighlightSetting,
updateSettings,
} from './actions';
-import { FONTS, MAX_HIGHLIGHT_SETTINGS, SETTINGS_TABS } from './constants';
+import { FONTS, SETTINGS_TABS } from './constants';
import { createDefaultHighlightSetting } from './model';
const defaultHighlightSetting = createDefaultHighlightSetting();
@@ -131,10 +131,6 @@ export function settingsReducer(
case addHighlightSetting.type: {
const highlightSetting = payload;
- if (state.highlightSettings.length >= MAX_HIGHLIGHT_SETTINGS) {
- return state;
- }
-
return {
...state,
highlightSettings: [...state.highlightSettings, highlightSetting.id],
diff --git a/tgui/packages/tgui/interfaces/AntagInfoMalf.tsx b/tgui/packages/tgui/interfaces/AntagInfoMalf.tsx
index 078f3e8026fa8..6ed65e7c41918 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoMalf.tsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoMalf.tsx
@@ -4,12 +4,13 @@ import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
import { Window } from '../layouts';
+import { MalfAiModules } from './common/MalfAiModules';
import {
Objective,
ObjectivePrintout,
ReplaceObjectivesButton,
} from './common/Objectives';
-import { GenericUplink, Item } from './Uplink/GenericUplink';
+import { Item } from './Uplink/GenericUplink';
const allystyle = {
fontWeight: 'bold',
@@ -26,7 +27,12 @@ const goalstyle = {
fontWeight: 'bold',
};
-type Info = {
+type Category = {
+ name: string;
+ items: Item[];
+};
+
+type Data = {
has_codewords: BooleanLike;
phrases: string;
responses: string;
@@ -36,13 +42,14 @@ type Info = {
intro: string;
processingTime: string;
objectives: Objective[];
- categories: any[];
+ categories: Category[];
can_change_objective: BooleanLike;
};
-const IntroductionSection = (props) => {
- const { act, data } = useBackend();
+function IntroductionSection(props) {
+ const { data } = useBackend();
const { intro, objectives, can_change_objective } = data;
+
return (
@@ -50,13 +57,13 @@ const IntroductionSection = (props) => {
}
/>
@@ -64,11 +71,12 @@ const IntroductionSection = (props) => {
);
-};
+}
-const FlavorSection = (props) => {
- const { data } = useBackend();
+function FlavorSection(props) {
+ const { data } = useBackend();
const { allies, goal } = data;
+
return (
{
mr={-0.8}
mt={-0.5}
icon="hammer"
- tooltip={`
+ tooltip="
This is a gameplay suggestion for bored ais.
You don't have to follow it, unless you want some
- ideas for how to spend the round.`}
+ ideas for how to spend the round."
tooltipPosition="bottom-start"
>
Policy
@@ -121,21 +129,22 @@ const FlavorSection = (props) => {
);
-};
+}
-const CodewordsSection = (props) => {
- const { data } = useBackend();
+function CodewordsSection(props) {
+ const { data } = useBackend();
const { has_codewords, phrases, responses } = data;
+
return (
- {(!has_codewords && (
+ {!has_codewords ? (
You have not been supplied the Syndicate codewords. You will have to
use alternative methods to find potential allies. Proceed with
caution, however, as everyone is a potential foe.