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.
- )) || ( + ) : ( <>
@@ -167,36 +176,21 @@ const CodewordsSection = (props) => {
); -}; +} + +enum Screen { + Intro, + Modules, +} + +export function AntagInfoMalf(props) { + const [antagInfoTab, setAntagInfoTab] = useState(Screen.Intro); -export const AntagInfoMalf = (props) => { - const { act, data } = useBackend(); - const { processingTime, categories } = data; - const [antagInfoTab, setAntagInfoTab] = useState(0); - const categoriesList: string[] = []; - const items: Item[] = []; - for (let i = 0; i < categories.length; i++) { - const category = categories[i]; - categoriesList.push(category.name); - for (let itemIndex = 0; itemIndex < category.items.length; itemIndex++) { - const item = category.items[itemIndex]; - items.push({ - id: item.name, - name: item.name, - icon: item.icon, - icon_state: item.icon_state, - category: category.name, - cost: `${item.cost} PT`, - desc: item.desc, - disabled: processingTime < item.cost, - }); - } - } return ( @@ -204,21 +198,21 @@ export const AntagInfoMalf = (props) => { setAntagInfoTab(0)} + selected={antagInfoTab === Screen.Intro} + onClick={() => setAntagInfoTab(Screen.Intro)} > Information setAntagInfoTab(1)} + selected={antagInfoTab === Screen.Modules} + onClick={() => setAntagInfoTab(Screen.Modules)} > Malfunction Modules - {(antagInfoTab === 0 && ( + {antagInfoTab === Screen.Intro ? ( <> @@ -234,15 +228,10 @@ export const AntagInfoMalf = (props) => { - )) || ( - -
- act('buy', { name: item.name })} - /> + ) : ( + +
+
)} @@ -250,4 +239,4 @@ export const AntagInfoMalf = (props) => { ); -}; +} diff --git a/tgui/packages/tgui/interfaces/ApcControl.tsx b/tgui/packages/tgui/interfaces/ApcControl.tsx index 7ed1ce31f2d3a..3bc8e9213d095 100644 --- a/tgui/packages/tgui/interfaces/ApcControl.tsx +++ b/tgui/packages/tgui/interfaces/ApcControl.tsx @@ -82,7 +82,7 @@ function ApcLoggedOut(props) { - Nanotrasen or its affiliants do not endorse this product. Risk of + Nanotrasen and its affiliates do not endorse this product. Risk of serious bodily injury or death is inherent in the use of any device that generates electricity. Nanotrasen is not responsible for any damages caused by the use of this product. diff --git a/tgui/packages/tgui/interfaces/ForceEvent.tsx b/tgui/packages/tgui/interfaces/ForceEvent.tsx index b8cee3cb2a39c..5989892e9e305 100644 --- a/tgui/packages/tgui/interfaces/ForceEvent.tsx +++ b/tgui/packages/tgui/interfaces/ForceEvent.tsx @@ -1,4 +1,5 @@ import { paginate } from 'common/collections'; +import { createContext, useContext, useState } from 'react'; import { Button, Icon, @@ -9,7 +10,7 @@ import { } from 'tgui-core/components'; import { BooleanLike } from 'tgui-core/react'; -import { useBackend, useLocalState } from '../backend'; +import { useBackend } from '../backend'; import { Window } from '../layouts'; const CATEGORY_PAGE_ITEMS = 4; @@ -20,7 +21,7 @@ const EVENT_PAGE_MAXCHARS = 48; * Same as paginate, but respecting event names with a character max length * that will also create a new page if created */ -const paginateEvents = (events: Event[], maxPerPage: number): Event[][] => { +function paginateEvents(events: Event[], maxPerPage: number): Event[][] { const pages: Event[][] = []; let page: Event[] = []; // conditions that make a new page @@ -52,7 +53,7 @@ const paginateEvents = (events: Event[], maxPerPage: number): Event[][] => { pages.push(page); } return pages; -}; +} type Event = { name: string; @@ -72,27 +73,40 @@ type ForceEventData = { events: Event[]; }; -export const ForceEvent = (props) => { +export function ForceEvent(props) { + const { data } = useBackend(); + const { categories } = data; + + const announceState = useState(true); + const categoryState = useState(categories[0]); + const searchQueryState = useState(''); + return ( - - - - - - - - + + + + + + + + + + ); -}; +} + +function PanelOptions(props) { + const { searchQueryState, announceState } = useForceEventContext(); -export const PanelOptions = (props) => { - const [searchQuery, setSearchQuery] = useLocalState('searchQuery', ''); + const [searchQuery, setSearchQuery] = searchQueryState; - const [announce, setAnnounce] = useLocalState('announce', true); + const [announce, setAnnounce] = announceState; return ( @@ -119,15 +133,18 @@ export const PanelOptions = (props) => { ); -}; +} -export const EventSection = (props) => { +function EventSection(props) { const { data, act } = useBackend(); - const { categories, events } = data; + const { events } = data; + + const { categoryState, searchQueryState, announceState } = + useForceEventContext(); - const [category] = useLocalState('category', categories[0]); - const [searchQuery] = useLocalState('searchQuery', ''); - const [announce] = useLocalState('announce', true); + const [category] = categoryState; + const [searchQuery] = searchQueryState; + const [announce] = announceState; const preparedEvents = paginateEvents( events.filter((event) => { @@ -189,13 +206,14 @@ export const EventSection = (props) => {
); -}; +} -export const EventTabs = (props) => { +function EventTabs(props) { const { data } = useBackend(); const { categories } = data; - const [category, setCategory] = useLocalState('category', categories[0]); + const { categoryState } = useForceEventContext(); + const [category, setCategory] = categoryState; const layerCats = paginate(categories, CATEGORY_PAGE_ITEMS); @@ -217,4 +235,21 @@ export const EventTabs = (props) => { ))} ); +} + +type ForceEventContextType = { + announceState: [boolean, (value: boolean) => void]; + categoryState: [Category, (value: Category) => void]; + searchQueryState: [string, (value: string) => void]; }; + +const ForceEventContext = createContext({ + announceState: [true, () => {}], + categoryState: [{ icon: '', name: '' }, () => {}], + searchQueryState: ['', () => {}], +}); + +/** Local state hook for ForceEvent */ +function useForceEventContext() { + return useContext(ForceEventContext); +} diff --git a/tgui/packages/tgui/interfaces/KeyComboModal.tsx b/tgui/packages/tgui/interfaces/KeyComboModal.tsx index e5368292e2e16..d37d4f6602d18 100644 --- a/tgui/packages/tgui/interfaces/KeyComboModal.tsx +++ b/tgui/packages/tgui/interfaces/KeyComboModal.tsx @@ -1,28 +1,29 @@ import { useState } from 'react'; import { Autofocus, Box, Button, Section, Stack } from 'tgui-core/components'; import { isEscape, KEY } from 'tgui-core/keys'; +import { BooleanLike } from 'tgui-core/react'; -import { useBackend, useLocalState } from '../backend'; +import { useBackend } from '../backend'; import { Window } from '../layouts'; import { InputButtons } from './common/InputButtons'; import { Loader } from './common/Loader'; type KeyInputData = { init_value: string; - large_buttons: boolean; + large_buttons: BooleanLike; message: string; timeout: number; title: string; }; -const isStandardKey = (event: React.KeyboardEvent): boolean => { +function isStandardKey(event: React.KeyboardEvent): boolean { return ( event.key !== KEY.Alt && event.key !== KEY.Control && event.key !== KEY.Shift && !isEscape(event.key) ); -}; +} const KEY_CODE_TO_BYOND: Record = { DEL: 'Delete', @@ -40,9 +41,9 @@ const KEY_CODE_TO_BYOND: Record = { const DOM_KEY_LOCATION_NUMPAD = 3; -const formatKeyboardEvent = ( +function formatKeyboardEvent( event: React.KeyboardEvent, -): string => { +): string { let text = ''; if (event.altKey) { @@ -67,20 +68,44 @@ const formatKeyboardEvent = ( } return text; -}; +} -export const KeyComboModal = (props) => { +export function KeyComboModal(props) { const { act, data } = useBackend(); const { init_value, large_buttons, message = '', title, timeout } = data; const [input, setInput] = useState(init_value); - const [binding, setBinding] = useLocalState('binding', true); + const [binding, setBinding] = useState(true); + + function handleKeyDown(event: React.KeyboardEvent) { + if (!binding) { + if (event.key === KEY.Enter) { + act('submit', { entry: input }); + } + if (isEscape(event.key)) { + act('cancel'); + } + return; + } + + event.preventDefault(); + + if (isStandardKey(event)) { + setValue(formatKeyboardEvent(event)); + setBinding(false); + return; + } else if (isEscape(event.key)) { + setValue(init_value); + setBinding(false); + return; + } + } - const setValue = (value: string) => { + function setValue(value: string) { if (value === input) { return; } setInput(value); - }; + } // Dynamically changes the window height based on the message. const windowHeight = @@ -91,31 +116,7 @@ export const KeyComboModal = (props) => { return ( {timeout && } - { - if (!binding) { - if (event.key === KEY.Enter) { - act('submit', { entry: input }); - } - if (isEscape(event.key)) { - act('cancel'); - } - return; - } - - event.preventDefault(); - - if (isStandardKey(event)) { - setValue(formatKeyboardEvent(event)); - setBinding(false); - return; - } else if (isEscape(event.key)) { - setValue(init_value); - setBinding(false); - return; - } - }} - > +
@@ -125,16 +126,15 @@ export const KeyComboModal = (props) => { @@ -144,4 +144,4 @@ export const KeyComboModal = (props) => { ); -}; +} diff --git a/tgui/packages/tgui/interfaces/LibraryAdmin.tsx b/tgui/packages/tgui/interfaces/LibraryAdmin.tsx deleted file mode 100644 index 5e9ead80a6054..0000000000000 --- a/tgui/packages/tgui/interfaces/LibraryAdmin.tsx +++ /dev/null @@ -1,495 +0,0 @@ -import { map, sortBy } from 'common/collections'; -import { useState } from 'react'; -import { - Box, - Button, - Dropdown, - Input, - NoticeBox, - Section, - Stack, - Table, - TextArea, -} from 'tgui-core/components'; -import { capitalize } from 'tgui-core/string'; - -import { useBackend, useLocalState } from '../backend'; -import { Window } from '../layouts'; -import { PageSelect } from './LibraryConsole'; - -export const LibraryAdmin = (props) => { - const [modifyMethod, setModifyMethod] = useLocalState('ModifyMethod', null); - return ( - - {modifyMethod ? : } - - ); -}; - -type ListingData = { - can_connect: boolean; - can_db_request: boolean; - our_page: number; - page_count: number; -}; - -const BookListing = (props) => { - const { act, data } = useBackend(); - const { can_connect, can_db_request, our_page, page_count } = data; - if (!can_connect) { - return ( - - Unable to retrieve book listings. Please contact your system - administrator for assistance. - - ); - } - return ( - - - - - - - - - - - act('switch_page', { - page: value, - }) - } - /> - - - ); -}; - -type Book = { - author: string; - category: string; - title: string; - id: number; -}; - -type AdminBook = Book & { - author_ckey: string; - deleted: boolean; -}; - -type DisplayAdminBook = AdminBook & { - key: number; -}; - -type DisplayData = { - can_db_request: boolean; - search_categories: string[]; - book_id: number; - title: string; - category: string; - author: string; - author_ckey: string; - params_changed: boolean; - view_raw: boolean; - show_deleted: boolean; - history: HistoryArray; - pages: Book[]; -}; - -const SearchAndDisplay = (props) => { - const { act, data } = useBackend(); - const [modifyMethod, setModifyMethod] = useLocalState('ModifyMethod', ''); - const [modifyTarget, setModifyTarget] = useLocalState('ModifyTarget', 0); - const { - can_db_request, - search_categories = [], - book_id, - title, - category, - author, - author_ckey, - pages, - params_changed, - view_raw, - show_deleted, - } = data; - const books = sortBy( - map( - pages, - (book, i) => - ({ - ...book, - // Generate a unique id - key: i, - }) as DisplayAdminBook, - ), - (book) => book.key, - ); - return ( -
- - - - - - act('set_search_id', { - id: value, - }) - } - /> - - - - act('set_search_category', { - category: value, - }) - } - /> - - - - act('set_search_title', { - title: value, - }) - } - /> - - - - act('set_search_author', { - author: value, - }) - } - /> - - - - act('set_search_ckey', { - ckey: value, - }) - } - /> - - - - - - - - - - -
- ); -}; - -const ModifyTypes = { - Delete: 'delete', - Restore: 'restore', -}; - -type HistoryEntry = { - // The id of this logged action - id: number; - // The book id this log applies to - book: number; - // The reason this action was enacted - reason: string; - // The admin who performed the action - ckey: string; - // The time of the action being performed - datetime: string; - // The action that ocurred - action: string; - // The ip address of the admin who performed the action - ip_addr: string; -}; - -type HistoryArray = { - [key: string]: HistoryEntry[]; -}; - -type ModalData = { - can_db_request: boolean; - view_raw: boolean; - history: HistoryArray; -}; - -const ModifyPage = (props) => { - const { act, data } = useBackend(); - - const { can_db_request, view_raw, history } = data; - const [modifyMethod, setModifyMethod] = useLocalState('ModifyMethod', ''); - const [modifyTarget, setModifyTarget] = useLocalState('ModifyTarget', 0); - const [reason, setReason] = useState('null'); - - const entries = history[modifyTarget.toString()] - ? history[modifyTarget.toString()].sort((a, b) => b.id - a.id) - : []; - - return ( - - - Heads Up! We do not allow you to fully delete books in game -
- What you're doing here is a "don't show this to - anyone" button -
- If you for whatever reason need to fully wipe a book, please speak to - your database administrator -
- - - Why do you want to {modifyMethod} this book? - - - - - -