diff --git a/.dockerignore b/.dockerignore
index 400701794cf0e..f7cf8d129fc46 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,30 +1,27 @@
-.dockerignore
-.editorconfig
-.travis.yml
-GPLv3.txt
-LICENSE
-README.md
-TGS3.json
-.github
-.gitignore
-.gitattributes
-.git/hooks
-.git/info
-.git/modules
-.git/objects
-.git/refs
-.vs*
-cfg
-data
-SQL
-tgui/node_modules
-tgstation.dmb
-tgstation.int
-tgstation.rsc
-tgstation.lk
-tgstation.dyn.rsc
-libmariadb.dll
-rust_g.dll
-BSQL.dll
-appveyor.yml
-Dockerfile
+.dockerignore
+.editorconfig
+GPLv3.txt
+LICENSE
+README.md
+TGS3.json
+.github
+.gitignore
+.gitattributes
+.git/hooks
+.git/info
+.git/modules
+.git/objects
+.git/refs
+.vs*
+cfg
+data
+SQL
+tgui/node_modules
+tgstation.dmb
+tgstation.int
+tgstation.rsc
+tgstation.lk
+tgstation.dyn.rsc
+*.dll
+Dockerfile
+tools/bootstrap/.cache
diff --git a/.editorconfig b/.editorconfig
index 95e40c0cd3c47..be7033ee79311 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -15,3 +15,6 @@ indent_style = space
[/tgui/**/*.{js,styl,ract,json,html}]
indent_style = space
indent_size = 2
+
+[Dockerfile]
+indent_style = space
diff --git a/.gitattributes b/.gitattributes
index cbfcb9e2c88dc..655894289ad9a 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,15 +1,50 @@
#Normalize files
* text=auto
-*.sh text eol=lf
+
+## Enforce text mode and LF line breaks
+*.bat text eol=lf
+*.cjs text eol=lf
+*.css text eol=lf
*.dm text eol=lf
*.dme text eol=lf
+*.dmf text eol=lf
+*.htm text eol=lf
+*.html text eol=lf
+*.js text eol=lf
+*.json text eol=lf
+*.jsx text eol=lf
+*.md text eol=lf
+*.ps1 text eol=lf
+*.py text eol=lf
+*.scss text eol=lf
+*.sh text eol=lf
+*.sql text eol=lf
+*.svg text eol=lf
+*.ts text eol=lf
+*.tsx text eol=lf
+*.txt text eol=lf
+*.yaml text eol=lf
+*.yml text eol=lf
+
+## Enforce binary mode
+*.bmp binary
+*.dll binary
+*.dmb binary
+*.exe binary
+*.gif binary
+*.jpg binary
+*.png binary
+*.so binary
# merger hooks, run tools/hooks/install.bat or install.sh to set up
*.dmm merge=dmm
*.dmi merge=dmi
# exlude maps from contribution count
-*.dmm linguist-generated=true
+*.dmm linguist-generated=true
+
+## Force tab indents on dm files
+*.dm whitespace=indent-with-non-tab
# force changelog merging to use union
html/changelog.html merge=union
diff --git a/.github/.github/autolabeler.yml b/.github/.github/autolabeler.yml
deleted file mode 100644
index 75979e3f7af1d..0000000000000
--- a/.github/.github/autolabeler.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-code: ["*.dm"]
-mapping: ["*.dmm"]
-config: ["config/*"]
-tooling: ["tools/*"]
-sql: ["*.sql"]
-dme: ["*.dme"]
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 1a82a97422d2d..b2b1baeba782e 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -8,14 +8,11 @@
.dockerignore @crossedfall
.github @crossedfall
-.travis.yml @crossedfall
-appveyor.yml @crossedfall
+/code/_compile_options.dm @crossedfall
/code/modules/tgs @crossedfall
-/config @crossedfall
dependencies.sh @crossedfall
Dockerfile @crossedfall
/SQL @crossedfall
-TGS3.json @crossedfall
/tools @crossedfall
@@ -27,11 +24,6 @@ TGS3.json @crossedfall
# powerfulbacon
/code/game/machinery/shuttle @powerfulbacon
-/code/modules/shuttle @powerfulbacon
-
-# zeskorion
-
-/code/__DEFINES/diseases.dm @zeskorion
-/code/controllers/subsystem/disease.dm @zeskorion
-/code/datums/diseases @zeskorion
-/code/modules/antagonists/disease @zeskorion
+/code/modules/shuttle/super_cruise @powerfulbacon
+/code/modules/shuttle/shuttle_creation @powerfulbacon
+/_maps/map_files/CorgStation @powerfulbacon
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 2d276fd2760f8..11745569b3290 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -97,7 +97,7 @@ Do not add any of the following in a Pull Request or risk getting the PR closed:
- Any content that violates GitHub Terms of Service.
- Racial or homophobic slurs of any kind.
- National Socialist Party of Germany content, National Socialist Party of Germany related content, or National Socialist Party of Germany references
-- Code adding, removing, or updating the availability of alien races/species/human mutants without prior approval. Pull requests attempting to add or remove features from said races/species/mutants require prior approval as well.
+- Code adding, removing, or updating the availability of alien races/species/human mutants without prior Maintainer approval. Pull requests attempting to add or remove features from said races/species/mutants require prior approval as well.
Just because something isn't on this list doesn't mean that it's acceptable. Use common sense above all else.
diff --git a/.github/comment-agent.yml b/.github/comment-agent.yml
new file mode 100644
index 0000000000000..e147d3bf0c496
--- /dev/null
+++ b/.github/comment-agent.yml
@@ -0,0 +1,15 @@
+# A mapping of keyword aliases to event type. Form of match:event type.
+# Required.
+aliasMappings:
+ "?rebuild tgui": "rebuild-tgui"
+ "?rebuild-tgui": "rebuild-tgui"
+
+# Determines if alias matching is case sensitive.
+# Optional. Defaults to true.
+caseSensitive: false
+
+# A mapping of event types to user group permissions.
+# Form of event type:group name or combined names (Explained lower in the README).
+# Optional, defaults to MEMBER for each.
+permissionMappings:
+ "rebuild-tgui": [MEMBER, PRAUTHOR]
diff --git a/.github/file_labeler.yml b/.github/file_labeler.yml
new file mode 100644
index 0000000000000..a9afd6e3c5905
--- /dev/null
+++ b/.github/file_labeler.yml
@@ -0,0 +1,26 @@
+Administration:
+- code/modules/admin/**/*
+
+Config Update:
+- config/**/*
+
+GitHub:
+- .github/**/*
+
+mapping:
+- _maps/**/*
+
+sound:
+- sound/**/*
+
+sprites:
+- icons/**/*.dmi
+
+sql:
+- SQL/**/*.sql
+
+tools:
+- tools/**/*
+
+TGUI-Changes:
+- tgui/**/*
diff --git a/.github/workflows/compile_changelogs.yml b/.github/workflows/compile_changelogs.yml
index 0e705866dd0ee..0155701ccca6b 100644
--- a/.github/workflows/compile_changelogs.yml
+++ b/.github/workflows/compile_changelogs.yml
@@ -2,12 +2,12 @@ name: Compile changelogs
on:
schedule:
- - cron: "*/15 * * * *"
+ - cron: "0 * * * *"
+ workflow_dispatch:
jobs:
CompileCL:
runs-on: ubuntu-latest
- if: github.repository == 'BeeStation/BeeStation-Hornet'
steps:
- name: Checkout
uses: actions/checkout@v1
@@ -16,11 +16,11 @@ jobs:
- name: Python setup
uses: actions/setup-python@v1
with:
- python-version: '3.x'
+ python-version: "3.8"
- name: Install depends
run: |
python -m pip install --upgrade pip
- pip install pyyaml bs4
+ pip install -r tools/changelog/requirements.txt
- name: Compile CL
run: python tools/changelog/ss13_genchangelog.py html/changelog.html html/changelogs
- name: Commit
diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml
new file mode 100644
index 0000000000000..8c75f1d0ad475
--- /dev/null
+++ b/.github/workflows/continuous_integration.yml
@@ -0,0 +1,112 @@
+name: Run tests
+
+on:
+ workflow_dispatch:
+ push:
+ paths-ignore:
+ - 'html/changelogs/**'
+ - 'html/changelog.html'
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+
+jobs:
+ run_linters:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Setup cache
+ id: cache-spacemandmm
+ uses: actions/cache@v2
+ with:
+ path: ~/dreamchecker
+ key: ${{ runner.os }}-spacemandmm-cache-${{ hashFiles('dependencies.sh') }}
+ - name: Install SpacemanDMM
+ if: steps.cache-spacemandmm.outputs.cache-hit != 'true'
+ run: bash tools/ci/install_spaceman_dmm.sh dreamchecker
+ - name: Install Tools
+ run: |
+ pip3 install setuptools
+ bash tools/ci/install_node.sh
+ bash tools/ci/install_auxmos.sh
+ pip3 install -r tools/requirements.txt
+ - name: Run Linters
+ run: |
+ bash tools/ci/check_filedirs.sh beestation.dme
+ bash tools/ci/check_changelogs.sh
+ find . -name "*.php" -print0 | xargs -0 -n1 php -l
+ find . -name "*.json" -not -path "*/node_modules/*" -print0 | xargs -0 python3 ./tools/json_verifier.py
+ tgui/bin/tgui --lint
+ tgui/bin/tgui --test
+ bash tools/ci/check_grep.sh
+ tools/bootstrap/python -m dmi.test
+ tools/bootstrap/python -m mapmerge2.dmm_test
+ ~/dreamchecker
+ compile_all_maps:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Setup cache
+ id: cache-byond
+ uses: actions/cache@v2
+ with:
+ path: ~/BYOND
+ key: ${{ runner.os }}-byond-cache-${{ hashFiles('Dockerfile') }}
+ - name: Install BYOND
+ if: steps.cache-byond.outputs.cache-hit != 'true'
+ run: bash tools/ci/install_byond.sh
+ - name: Compile All Maps
+ run: |
+ source $HOME/BYOND/byond/bin/byondsetup
+ python3 tools/ci/template_dm_generator.py
+ tools/build/build dm -DCIBUILDING -DCITESTING -DALL_MAPS
+ run_all_tests:
+ runs-on: ubuntu-latest
+ services:
+ mysql:
+ image: mysql:latest
+ env:
+ MYSQL_ROOT_PASSWORD: root
+ ports:
+ - 3306
+ options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
+ steps:
+ - uses: actions/checkout@v2
+ - name: Setup cache
+ id: cache-byond
+ uses: actions/cache@v2
+ with:
+ path: ~/BYOND
+ key: ${{ runner.os }}-byond-cache-${{ hashFiles('Dockerfile') }}
+ - name: Install BYOND
+ if: steps.cache-byond.outputs.cache-hit != 'true'
+ run: bash tools/ci/install_byond.sh
+ - name: Setup database
+ run: |
+ sudo systemctl start mysql
+ mysql -u root -proot -e 'CREATE DATABASE bee_ci;'
+ mysql -u root -proot bee_ci < SQL/beestation_schema.sql
+ - name: Install rust-g
+ run: |
+ sudo dpkg --add-architecture i386
+ sudo apt update || true
+ sudo apt install libssl1.1:i386
+ bash tools/ci/install_rust_g.sh
+ - name: Install auxmos
+ run: |
+ bash tools/ci/install_auxmos.sh
+ - name: Compile and run tests
+ run: |
+ source $HOME/BYOND/byond/bin/byondsetup
+ tools/build/build -DCIBUILDING
+ bash tools/ci/run_server.sh
+ test_windows:
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Compile
+ run: pwsh tools/ci/build.ps1
+ env:
+ DM_EXE: "C:\\byond\\bin\\dm.exe"
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 645bdf081fd3c..3fef279444ff3 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -1,17 +1,17 @@
name: Docker Image CI
on:
+ workflow_dispatch:
schedule:
- cron: 0 0 * * *
jobs:
build:
runs-on: ubuntu-latest
- if: github.repository == 'BeeStation/BeeStation-Hornet'
steps:
- uses: actions/checkout@v2
- name: Publish to Registry
- uses: elgohr/Publish-Docker-Github-Action@master
+ uses: elgohr/Publish-Docker-Github-Action@v5
with:
name: beestation/beestation
username: ${{ secrets.DOCKER_USERNAME }}
diff --git a/.github/workflows/extra_pr_labels.yml b/.github/workflows/extra_pr_labels.yml
new file mode 100644
index 0000000000000..f70ad1b27aa16
--- /dev/null
+++ b/.github/workflows/extra_pr_labels.yml
@@ -0,0 +1,25 @@
+name: Add Extra PR labels
+
+on:
+ push:
+ branches:
+ - master
+ pull_request_target:
+
+jobs:
+ Label:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check for conflicting PRs
+ uses: eps1lon/actions-label-merge-conflict@513a24fc7dca40990863be2935e059e650728400
+ with:
+ dirtyLabel: "Merge Conflict"
+ repoToken: "${{ secrets.GITHUB_TOKEN }}"
+ commentOnDirty: "This pull request has conflicts, please resolve those before we can evaluate the pull request."
+ - name: Apply labels based on changed files
+ if: github.event_name != 'push'
+ uses: actions/labeler@main
+ with:
+ repo-token: "${{ secrets.GITHUB_TOKEN }}"
+ sync-labels: true
+ configuration-path: .github/file_labeler.yml
diff --git a/.github/workflows/make_changelogs.yml b/.github/workflows/make_changelogs.yml
index 62335b326ad51..e199e9a799874 100644
--- a/.github/workflows/make_changelogs.yml
+++ b/.github/workflows/make_changelogs.yml
@@ -2,12 +2,13 @@ name: Make changelogs
on:
push:
- branches: [master]
+ branches:
+ - master
jobs:
MakeCL:
runs-on: ubuntu-latest
- if: github.repository == 'BeeStation/BeeStation-Hornet' && !contains(github.event.head_commit.message, '[ci skip]')
+ if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Checkout
uses: actions/checkout@v1
@@ -16,11 +17,11 @@ jobs:
- name: Python setup
uses: actions/setup-python@v1
with:
- python-version: '3.x'
+ python-version: "3.8"
- name: Install depends
run: |
python -m pip install --upgrade pip
- pip install ruamel.yaml PyGithub
+ pip install -r tools/changelog/requirements.txt
- name: Make CL
env:
#GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} Use this instead if you have unprotected branches
diff --git a/.github/workflows/stale_issues.yml b/.github/workflows/stale_issues.yml
index 78ee27a047b75..f4430cd337149 100644
--- a/.github/workflows/stale_issues.yml
+++ b/.github/workflows/stale_issues.yml
@@ -7,12 +7,11 @@ on:
jobs:
stale:
runs-on: ubuntu-latest
- if: github.repository == 'BeeStation/BeeStation-Hornet'
steps:
- uses: actions/stale@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- stale-issue-message: "This issue has been inactive for long enough to be automatically marked as stale. If this was a bug report and hasn't been addressed yet, and is still a probelm, please don't hesitate to notify a maintainer."
+ stale-issue-message: "This issue has been inactive for long enough to be automatically marked as stale. If this was a bug report and hasn't been addressed yet, and is still a problem, please don't hesitate to notify a maintainer."
stale-issue-label: 'Stale'
exempt-issue-label: 'Triaged'
days-before-stale: 30
diff --git a/.github/workflows/stale_prs.yml b/.github/workflows/stale_prs.yml
index 0db9127623a77..6524f6ddcc5a9 100644
--- a/.github/workflows/stale_prs.yml
+++ b/.github/workflows/stale_prs.yml
@@ -7,7 +7,6 @@ on:
jobs:
stale:
runs-on: ubuntu-latest
- if: github.repository == 'BeeStation/BeeStation-Hornet'
steps:
- uses: actions/stale@v1
with:
diff --git a/.github/workflows/tgui_recompile.yml b/.github/workflows/tgui_recompile.yml
new file mode 100644
index 0000000000000..bbdcd9d325840
--- /dev/null
+++ b/.github/workflows/tgui_recompile.yml
@@ -0,0 +1,90 @@
+name: Rebuild TGUI
+
+on:
+ repository_dispatch:
+ types: [rebuild-tgui]
+ push:
+ branches:
+ - 'master'
+ paths:
+ - 'tgui/**.js'
+ - 'tgui/**.scss'
+
+# Config
+env:
+ COMMIT_NAME: 'ss13-beebot'
+ COMMIT_EMAIL: '56381746+ss13-beebot@users.noreply.github.com'
+ DEFAULT_BRANCH: 'master'
+ TOKEN: '${{ secrets.CL_TOKEN }}'
+
+jobs:
+ ondemand_rebuild:
+ if: ${{ github.event_name == 'repository_dispatch' }}
+ name: On-Demand Rebuild
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ with:
+ repository: ${{ github.event.client_payload.pr_head_full_repo_name }}
+ ref: ${{ github.event.client_payload.pr_head_ref }}
+ token: ${{ env.TOKEN }}
+
+ - name: Setup Author
+ run: |
+ git config --local user.email "${{ env.COMMIT_EMAIL }}"
+ git config --local user.name "${{ env.COMMIT_NAME }}"
+ - name: Setup Node
+ uses: actions/setup-node@v1
+ with:
+ node-version: '>=12.13'
+
+ # This only runs if the PR has a merge conflict. Serves to attempt resolving merge conflicts rather than just rebuilding.
+ # Uses a smart little git hack to make a merge commit for just a limited set of files.
+ - name: Conflict Resolution
+ if: ${{ github.event.client_payload.mergeable == false }}
+ run: |
+ git remote add base https://github.com/${{ github.repository }}.git
+ git fetch base ${{ env.DEFAULT_BRANCH }}
+ git merge --squash -s ours --no-commit base/${{ env.DEFAULT_BRANCH }}
+ git checkout HEAD .
+ git clean -fxd
+ git checkout base/${{ env.DEFAULT_BRANCH }} tgui/public
+ git commit -m "TGUI Reset" -a || true
+
+ - name: Build TGUI
+ run: bin/tgui
+ working-directory: ./tgui
+
+ - name: Commit and Push Build
+ run: |
+ git commit -m "TGUI Rebuild" -a || true
+ git push
+
+ auto_rebuild:
+ if: ${{ github.event_name == 'push' }}
+ name: Automatic Rebuild
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ with:
+ fetch-depth: 25
+ token: ${{ env.TOKEN }}
+
+ - name: Setup Node
+ uses: actions/setup-node@v1
+ with:
+ node-version: '>=12.13'
+
+ - name: Build TGUI
+ run: bin/tgui
+ working-directory: ./tgui
+
+ - name: Commit and Push Build
+ run: |
+ git config --local user.email "${{ env.COMMIT_EMAIL }}"
+ git config --local user.name "${{ env.COMMIT_NAME }}"
+ git pull origin master
+ git commit -m "Automatic TGUI Rebuild [ci skip]" -a || true
+ git push
diff --git a/.github/workflows/update_tgs_dmapi.yml b/.github/workflows/update_tgs_dmapi.yml
index 83c196a22fea5..af3f2951d57c4 100644
--- a/.github/workflows/update_tgs_dmapi.yml
+++ b/.github/workflows/update_tgs_dmapi.yml
@@ -40,7 +40,7 @@ jobs:
destination_branch: "master"
pr_title: "Automatic TGS DMAPI Update"
pr_body: "This pull request updates the TGS DMAPI to the latest version. Please note any breaking or unimplemented changes before merging."
- pr_label: "Tools"
+ pr_label: "tools"
pr_allow_empty: false
#github_token: ${{ secrets.GITHUB_TOKEN }} Use this instead if you have unprotected branches
github_token: ${{ secrets.CL_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 9b583ce0eb159..1768f9e0ba261 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,9 @@
#Ignore byond config folder.
/cfg/**/*
+# Ignore compiled linux libs in the root folder, e.g. librust_g.so
+/*.so
+
#Ignore compiled files and other files generated during compilation.
*.mdme
*.dmb
@@ -49,27 +52,6 @@ __pycache__/
*.py[cod]
*$py.class
-# C extensions
-#*.so
-
-# Distribution / packaging
-.Python
-env/
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-*.egg-info/
-.installed.cfg
-*.egg
-
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
@@ -77,8 +59,7 @@ var/
*.spec
# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
+pip-*.txt
# Unit test / coverage reports
htmlcov/
@@ -88,7 +69,6 @@ htmlcov/
.cache
nosetests.xml
coverage.xml
-*,cover
.hypothesis/
# Translations
@@ -97,10 +77,6 @@ coverage.xml
# Django stuff:
*.log
-local_settings.py
-
-# Flask instance folder
-instance/
# Scrapy stuff:
.scrapy
@@ -108,9 +84,6 @@ instance/
# Sphinx documentation
docs/_build/
-# PyBuilder
-target/
-
# IPython Notebook
.ipynb_checkpoints
@@ -123,10 +96,6 @@ celerybeat-schedule
# dotenv
.env
-# virtualenv
-venv/
-ENV/
-
# IntelliJ IDEA / PyCharm (with plugin)
.idea
@@ -149,12 +118,6 @@ Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
-# Windows Installer files
-#*.cab
-#*.msi
-#*.msm
-#*.msp
-
# Windows shortcuts
*.lnk
@@ -195,10 +158,10 @@ Temporary Items
#Visual studio stuff
*.vscode/*
-!/.vscode/extensions.json
-!/.vscode/settings.json
-tools/MapAtmosFixer/MapAtmosFixer/obj/*
-tools/MapAtmosFixer/MapAtmosFixer/bin/*
+/tools/MapAtmosFixer/MapAtmosFixer/obj/*
+/tools/MapAtmosFixer/MapAtmosFixer/bin/*
+/tools/CreditsTool/bin/*
+/tools/CreditsTool/obj/*
#GitHub Atom
.atom-build.json
@@ -216,3 +179,6 @@ tools/MapAtmosFixer/MapAtmosFixer/bin/*
!/config/jukebox_music/sounds/exclude
/config/title_music/sounds/*
!/config/title_music/sounds/exclude
+
+# Common build tooling, C B T
+!/tools/build
diff --git a/.tgs4.yml b/.tgs4.yml
new file mode 100644
index 0000000000000..6c44ca4ebe796
--- /dev/null
+++ b/.tgs4.yml
@@ -0,0 +1,4 @@
+linux_scripts:
+ PreCompile.sh: tools/tgs4_scripts/PreCompile.sh
+windows_scripts:
+ PreCompile.bat: tools/tgs4_scripts/PreCompile.bat
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index b63ac697b416c..0000000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,88 +0,0 @@
-language: generic
-os: linux
-dist: bionic
-
-branches:
- only:
- - master
-
-matrix:
- include:
- - name: "Run Linters"
- addons:
- apt:
- packages:
- - python3
- - python3-pip
- - python3-setuptools
- install:
- - tools/travis/install_build_tools.sh
- - tools/travis/install_spaceman_dmm.sh dreamchecker
- script:
- - tools/travis/check_filedirs.sh beestation.dme
- - tools/travis/check_changelogs.sh
- - find . -name "*.php" -print0 | xargs -0 -n1 php -l
- - find . -name "*.json" -not -path "./tgui/node_modules/*" -print0 | xargs -0 python3 ./tools/json_verifier.py
- - tools/travis/build_tgui.sh
- - tools/travis/check_grep.sh
- - ~/dreamchecker
-
- - name: "Compile All Maps"
- addons:
- apt:
- packages:
- - libstdc++6:i386
- cache:
- directories:
- - $HOME/BYOND
- install:
- - tools/travis/install_byond.sh
- - source $HOME/BYOND/byond/bin/byondsetup
- before_script:
- - tools/travis/template_dm_generator.py
- script:
- - tools/travis/dm.sh -DTRAVISBUILDING -DTRAVISTESTING -DALL_MAPS beestation.dme
-
- - name: "Compile and Run Tests"
- addons:
- mariadb: '10.2'
- apt:
- sources:
- - sourceline: "ppa:ubuntu-toolchain-r/test"
- packages:
- - libstdc++6:i386
- - gcc-multilib
- - g++-7
- - g++-7-multilib
- - libssl1.1:i386
- - zlib1g:i386
- cache:
- directories:
- - $HOME/BYOND
- install:
- - tools/travis/install_byond.sh
- - source $HOME/BYOND/byond/bin/byondsetup
- - tools/travis/install_rust_g.sh
- before_script:
- - mysql -u root -e 'CREATE DATABASE bee_travis;'
- - mysql -u root bee_travis < SQL/beestation_schema.sql
- script:
- - tools/travis/dm.sh -DTRAVISBUILDING beestation.dme || travis_terminate 1
- - tools/travis/run_server.sh
-
-# - name: "Generate Documentation"
-# # Only run for non-PR commits to the real master branch.
-# if: branch = master AND head_branch IS blank
-# install:
-# - tools/travis/install_spaceman_dmm.sh dmdoc
-# before_script:
-# # Travis checks out a hash, try to get back on a branch.
-# - git checkout -qf $(git name-rev --name-only HEAD) || true
-# script:
-# - ~/dmdoc
-# - touch dmdoc/.nojekyll
-# deploy:
-# provider: pages
-# skip_cleanup: true
-# local_dir: dmdoc
-# github_token: $DMDOC_GITHUB_TOKEN
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index 0abac7a5338e5..8f47c8079670c 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -1,8 +1,9 @@
-{
- "recommendations": [
- "gbasood.byond-dm-language-support",
- "platymuus.dm-langclient",
- "EditorConfig.EditorConfig",
- "dbaeumer.vscode-eslint"
- ]
-}
+{
+ "recommendations": [
+ "gbasood.byond-dm-language-support",
+ "platymuus.dm-langclient",
+ "EditorConfig.EditorConfig",
+ "arcanis.vscode-zipfs",
+ "dbaeumer.vscode-eslint"
+ ]
+}
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000000000..543058728f561
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,12 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "byond",
+ "request": "launch",
+ "name": "Launch DreamSeeker",
+ "preLaunchTask": "Build All",
+ "dmb": "${workspaceFolder}/${command:CurrentDMB}"
+ }
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 4f62a16105604..052769d3cc124 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,3 +1,28 @@
{
"dreammaker.extoolsDLL": "byond-extools.dll",
+ "eslint.nodePath": "./tgui/.yarn/sdks",
+ "eslint.workingDirectories": [
+ "./tgui"
+ ],
+ "typescript.tsdk": "./tgui/.yarn/sdks/typescript/lib",
+ "typescript.enablePromptUseWorkspaceTsdk": true,
+ "search.exclude": {
+ "**/.yarn": true,
+ "**/.pnp.*": true
+ },
+ "workbench.editorAssociations": {
+ "*.dmi": "imagePreview.previewEditor"
+ },
+ "files.eol": "\n",
+ "gitlens.advanced.blame.customArguments": ["-w"],
+ "tgstationTestExplorer.project.resultsType": "json",
+ "[javascript]": {
+ "editor.rulers": [80]
+ },
+ "[typescript]": {
+ "editor.rulers": [80]
+ },
+ "[scss]": {
+ "editor.rulers": [80]
+ }
}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000000000..56c1756ff6afb
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,55 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "type": "process",
+ "command": "tools/build/build",
+ "windows": {
+ "command": ".\\tools\\build\\build.bat"
+ },
+ "options": {
+ "env": {
+ "DM_EXE": "${config:dreammaker.byondPath}"
+ }
+ },
+ "problemMatcher": [
+ "$dreammaker",
+ "$tsc",
+ "$eslint-stylish"
+ ],
+ "group": {
+ "kind": "build",
+ "isDefault": true
+ },
+ "dependsOn": "dm: reparse",
+ "label": "Build All"
+ },
+ {
+ "type": "dreammaker",
+ "dme": "beestation.dme",
+ "problemMatcher": [
+ "$dreammaker"
+ ],
+ "group": "build",
+ "label": "dm: build - beestation.dme"
+ },
+ {
+ "type": "shell",
+ "command": "tgui/bin/tgui",
+ "windows": {
+ "command": ".\\tgui\\bin\\tgui.bat"
+ },
+ "problemMatcher": [
+ "$tsc",
+ "$eslint-stylish"
+ ],
+ "group": "build",
+ "label": "tgui: build"
+ },
+ {
+ "command": "${command:dreammaker.reparse}",
+ "group": "build",
+ "label": "dm: reparse"
+ }
+ ]
+}
diff --git a/BUILD.bat b/BUILD.bat
new file mode 100644
index 0000000000000..68eaef0c2d351
--- /dev/null
+++ b/BUILD.bat
@@ -0,0 +1,3 @@
+@echo off
+call "%~dp0\tools\build\build.bat" %*
+pause
diff --git a/Dockerfile b/Dockerfile
index b416fcaa1fda1..878ae2cc3b8de 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,72 +1,66 @@
-FROM beestation/byond:513.1536 as base
-ONBUILD ENV BYOND_MAJOR=513
-ONBUILD ENV BYOND_MINOR=1536
-
-FROM base as build_base
-
-RUN apt-get update \
- && apt-get install -y --no-install-recommends \
- git \
- dos2unix\
- ca-certificates
-
-FROM build_base as rust_g
-
-WORKDIR /rust_g
-
-RUN apt-get install -y --no-install-recommends \
- libssl-dev \
- pkg-config \
- curl \
- gcc-multilib \
- && curl https://sh.rustup.rs -sSf | sh -s -- -y --default-host i686-unknown-linux-gnu \
- && git init \
- && git remote add origin https://github.com/BeeStation/rust-g
-
-COPY dependencies.sh .
-
-RUN dos2unix dependencies.sh \
- && /bin/bash -c "source dependencies.sh \
- && git fetch --depth 1 origin \$RUST_G_VERSION" \
- && git checkout FETCH_HEAD \
- && ~/.cargo/bin/cargo build --release --all-features \
- && apt-get --purge remove -y dos2unix
-
-FROM base as dm_base
-
-WORKDIR /beestation
-
-FROM dm_base as build
-
-COPY . .
-
-RUN apt-get update \
- && apt-get install -y --no-install-recommends dos2unix \
- && rm -rf /var/lib/apt/lists/* \
- && DreamMaker -max_errors 0 beestation.dme && dos2unix tools/deploy.sh && tools/deploy.sh /deploy
-
-FROM dm_base
-
-EXPOSE 1337
-
-RUN apt-get update \
- && apt-get install -y --no-install-recommends software-properties-common \
- && add-apt-repository ppa:ubuntu-toolchain-r/test \
- && apt-get update \
- && apt-get upgrade -y \
- && apt-get dist-upgrade -y \
- && apt-get install -y --no-install-recommends \
- mariadb-client \
- libssl1.0.0 \
- && rm -rf /var/lib/apt/lists/* \
- && mkdir -p /root/.byond/bin
-
-COPY --from=rust_g /rust_g/target/release/librust_g.so /root/.byond/bin/rust_g
-COPY --from=build /deploy ./
-
-#extools fexists memes
-RUN ln -s /beestation/libbyond-extools.so /root/.byond/bin/libbyond-extools.so
-
-VOLUME [ "/beestation/config", "/beestation/data" ]
-
-ENTRYPOINT [ "DreamDaemon", "beestation.dmb", "-port", "1337", "-trusted", "-close", "-verbose" ]
+# syntax=docker/dockerfile:1
+FROM beestation/byond:514.1568 as base
+
+# Install the tools needed to compile our rust dependencies
+FROM base as rust-build
+ENV PKG_CONFIG_ALLOW_CROSS=1 \
+ CARGO_HOME=/usr/local/cargo \
+ PATH=/usr/local/cargo/bin:$PATH
+WORKDIR /build
+COPY dependencies.sh .
+RUN dpkg --add-architecture i386 \
+ && apt-get update \
+ && apt-get install -y --no-install-recommends \
+ curl ca-certificates gcc-multilib \
+ g++-multilib libc6-i386 zlib1g-dev:i386 \
+ libssl-dev:i386 pkg-config:i386 git \
+ && /bin/bash -c "source dependencies.sh \
+ && curl https://sh.rustup.rs | sh -s -- -y -t i686-unknown-linux-gnu --no-modify-path --profile minimal --default-toolchain \$RUST_VERSION" \
+ && rm -rf /var/lib/apt/lists/*
+
+# Build rust-g
+FROM rust-build as rustg
+RUN git init \
+ && git remote add origin https://github.com/BeeStation/rust-g \
+ && /bin/bash -c "source dependencies.sh \
+ && git fetch --depth 1 origin \$RUST_G_VERSION" \
+ && git checkout FETCH_HEAD \
+ && cargo build --release --all-features --target i686-unknown-linux-gnu
+
+# Build auxmos
+FROM rust-build as auxmos
+RUN git init \
+ && git remote add origin https://github.com/BeeStation/auxmos \
+ && /bin/bash -c "source dependencies.sh \
+ && git fetch --depth 1 origin \$AUXMOS_VERSION" \
+ && git checkout FETCH_HEAD \
+ && cargo rustc --target=i686-unknown-linux-gnu --release --features=trit_fire_hook,plasma_fire_hook,generic_fire_hook
+
+# Install nodejs which is required to deploy BeeStation
+FROM base as node
+COPY dependencies.sh .
+RUN apt-get update \
+ && apt-get install curl -y \
+ && /bin/bash -c "source dependencies.sh \
+ && curl -fsSL https://deb.nodesource.com/setup_\$NODE_VERSION.x | bash -" \
+ && apt-get install -y nodejs
+
+# Build TGUI, tgfonts, and the dmb
+FROM node as dm-build
+ENV TG_BOOTSTRAP_NODE_LINUX=1
+WORKDIR /dm-build
+COPY . .
+# Required to satisfy our compile_options
+COPY --from=auxmos /build/target/i686-unknown-linux-gnu/release/libauxmos.so /dm-build/auxtools/libauxmos.so
+RUN tools/build/build \
+ && tools/deploy.sh /deploy \
+ && apt-get autoremove curl -y \
+ && rm -rf /var/lib/apt/lists/*
+
+FROM base
+WORKDIR /beestation
+COPY --from=dm-build /deploy ./
+COPY --from=rustg /build/target/i686-unknown-linux-gnu/release/librust_g.so /root/.byond/bin/rust_g
+VOLUME [ "/beestation/config", "/beestation/data" ]
+ENTRYPOINT [ "DreamDaemon", "beestation.dmb", "-port", "1337", "-trusted", "-close", "-verbose" ]
+EXPOSE 1337
diff --git a/MIT.txt b/MIT.txt
deleted file mode 100644
index 59be2d32ebea1..0000000000000
--- a/MIT.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2019 MCHSL
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/README.md b/README.md
index 0a542cd1efc0a..b97d685903fd1 100644
--- a/README.md
+++ b/README.md
@@ -1,176 +1,171 @@
-
BeeStation 13 Codebase
-
-
-[![forthebadge](https://forthebadge.com/images/badges/built-with-resentment.svg)](https://forthebadge.com) [![forthebadge](https://forthebadge.com/images/badges/contains-technical-debt.svg)](https://user-images.githubusercontent.com/8171642/50290880-ffef5500-043a-11e9-8270-a2e5b697c86c.png) [![forinfinityandbyond](https://user-images.githubusercontent.com/5211576/29499758-4efff304-85e6-11e7-8267-62919c3688a9.gif)](https://www.reddit.com/r/SS13/comments/5oplxp/what_is_the_main_problem_with_byond_as_an_engine/dclbu1a)
-[![Build Status](https://travis-ci.com/BeeStation/BeeStation-Hornet.svg?branch=master)](https://travis-ci.com/BeeStation/BeeStation-Hornet) ![Open Issues](https://isitmaintained.com/badge/open/BeeStation/BeeStation-Hornet.svg)
-
-**Website:** http://beestation13.com
-**Code:** https://github.com/beestation/beestation-hornet
-**Wiki:** https://wiki.beestation13.com/view/Main_Page
-
-
-## DOWNLOADING
-
-There are a number of ways to download the source code. Some are described here, an alternative all-inclusive guide is also located at https://wiki.beestation13.com/view/Downloading_the_source_code
-
-Option 1:
-Follow this: https://wiki.beestation13.com/view/Setting_up_git
-
-Option 2: Download the source code as a zip by clicking the ZIP button in the
-code tab of https://github.com/beestation/beestation-hornet
-(note: this will use a lot of bandwidth if you wish to update and is a lot of
-hassle if you want to make any changes at all, so it's not recommended.)
-
-Option 3: Use our docker image that tracks the master branch (See commits for build status. Again, same caveats as option 2)
-
-```
-docker run -d -p :1337 -v /path/to/your/config:/beestation/config -v /path/to/your/data:/beestation/data beestation/beestation
-```
-
-## INSTALLATION
-
-First-time installation should be fairly straightforward. First, you'll need
-BYOND installed. You can get it from https://www.byond.com/download. Once you've done
-that, extract the game files to wherever you want to keep them. This is a
-sourcecode-only release, so the next step is to compile the server files.
-Open beestation.dme by double-clicking it, open the Build menu, and click
-compile. This'll take a little while, and if everything's done right you'll get
-a message like this:
-
-```
-saving beestation.dmb (DEBUG mode)
-beestation.dmb - 0 errors, 0 warnings
-```
-
-If you see any errors or warnings, something has gone wrong - possibly a corrupt
-download or the files extracted wrong. If problems persist, ask for assistance
-in https://discord.gg/Vh8TJp9 or https://discord.gg/z9ttAvA
-
-
-Once that's done, open up the config folder. You'll want to edit config.txt to
-set the probabilities for different gamemodes in Secret and to set your server
-location so that all your players don't get disconnected at the end of each
-round. It's recommended you don't turn on the gamemodes with probability 0,
-except Extended, as they have various issues and aren't currently being tested,
-so they may have unknown and bizarre bugs. Extended is essentially no mode, and
-isn't in the Secret rotation by default as it's just not very fun.
-
-You'll also want to edit config/admins.txt to remove the default admins and add
-your own. "Game Master" is the highest level of access, and probably the one
-you'll want to use for now. You can set up your own ranks and find out more in
-config/admin_ranks.txt
-
-The format is
-
-```
-byondkey = Rank
-```
-
-where the admin rank must be properly capitalised.
-
-This codebase also depends on a native library called rust-g. A precompiled
-Windows DLL is included in this repository, but Linux users will need to build
-and install it themselves. Directions can be found at the [rust-g
-repo](https://github.com/tgstation/rust-g).
-
-Finally, to start the server, run Dream Daemon and enter the path to your
-compiled beestation.dmb file. Make sure to set the port to the one you
-specified in the config.txt, and set the Security box to 'Safe'. Then press GO
-and the server should start up and be ready to join. It is also recommended that
-you set up the SQL backend (see below).
-
-## UPDATING
-
-To update an existing installation, first back up your /config and /data folders
-as these store your server configuration, player preferences and banlist.
-
-Then, extract the new files (preferably into a clean directory, but updating in
-place should work fine), copy your /config and /data folders back into the new
-install, overwriting when prompted except if we've specified otherwise, and
-recompile the game. Once you start the server up again, you should be running
-the new version.
-
-## HOSTING
-
-If you'd like a more robust server hosting option for tgstation and its
-derivatives. Check out their server tools suite at
-https://github.com/tgstation/tgstation-server
-
-## MAPS
-
-BeeStation currently comes equipped with these maps.
-
-* [DeltaStation (default)](https://wiki.beestation13.com/view/DeltaStation)
-* [BoxStation](https://wiki.beestation13.com/view/Boxstation)
-* [MetaStation](https://wiki.beestation13.com/view/MetaStation)
-* [PubbyStation](https://wiki.beestation13.com/view/PubbyStation)
-* [DonutStation](https://wiki.beestation13.com/view/Donutstation)
-
-
-All maps have their own code file that is in the base of the _maps directory. Maps are loaded dynamically when the game starts. Follow this guideline when adding your own map, to your fork, for easy compatibility.
-
-The map that will be loaded for the upcoming round is determined by reading data/next_map.json, which is a copy of the json files found in the _maps tree. If this file does not exist, the default map from config/maps.txt will be loaded. Failing that, BoxStation will be loaded. If you want to set a specific map to load next round you can use the Change Map verb in game before restarting the server or copy a json from _maps to data/next_map.json before starting the server. Also, for debugging purposes, ticking a corresponding map's code file in Dream Maker will force that map to load every round.
-
-If you are hosting a server, and want randomly picked maps to be played each round, you can enable map rotation in [config.txt](config/config.txt) and then set the maps to be picked in the [maps.txt](config/maps.txt) file.
-
-Anytime you want to make changes to a map it's imperative you use the [Map Merging tools](https://wiki.beestation13.com/view/Map_Merger)
-
-## AWAY MISSIONS
-
-BeeStation supports loading away missions however they are disabled by default.
-
-Map files for away missions are located in the _maps/RandomZLevels directory. Each away mission includes it's own code definitions located in /code/modules/awaymissions/mission_code. These files must be included and compiled with the server beforehand otherwise the server will crash upon trying to load away missions that lack their code.
-
-To enable an away mission open `config/awaymissionconfig.txt` and uncomment one of the .dmm lines by removing the #. If more than one away mission is uncommented then the away mission loader will randomly select one the enabled ones to load.
-
-## SQL SETUP
-
-The SQL backend requires a Mariadb server running 10.2 or later. Mysql is not supported but Mariadb is a drop in replacement for mysql. SQL is required for the library, stats tracking, admin notes, and job-only bans, among other features, mostly related to server administration. Your server details go in /config/dbconfig.txt, and the SQL schema is in /SQL/beestation_schema.sql and /SQL/beestation_schema_prefix.sql depending on if you want table prefixes. More detailed setup instructions are located here: https://wiki.beestation13.com/view/Downloading_the_source_code#Setting_up_the_database
-
-If you are hosting a testing server on windows you can use a standalone version of MariaDB pre load with a blank (but initialized) tgdb database. Find them here: https://tgstation13.download/database/ Just unzip and run for a working (but insecure) database server. Includes a zipped copy of the data folder for easy resetting back to square one.
-
-## WEB/CDN RESOURCE DELIVERY
-
-Web delivery of game resources makes it quicker for players to join and reduces some of the stress on the game server.
-
-1. Edit compile_options.dm to set the `PRELOAD_RSC` define to `0`
-1. Add a url to config/external_rsc_urls pointing to a .zip file containing the .rsc.
- * If you keep up to date with BeeStation you could reuse our rsc cdn at http://rsc.beestation13.buzz/beestation.zip. Otherwise you can use cdn services like CDN77 or cloudflare (requires adding a page rule to enable caching of the zip), or roll your own cdn using route 53 and vps providers.
- * Regardless even offloading the rsc to a website without a CDN will be a massive improvement over the in game system for transferring files.
-
-## IRC BOT SETUP
-
-Included in the repository is a python3 compatible IRC bot capable of relaying adminhelps to a specified
-IRC channel/server, see the /tools/minibot folder for more
-
-## CONTRIBUTING
-
-Please see [CONTRIBUTING.md](.github/CONTRIBUTING.md)
-
-## LICENSE
-
-All code after [commit 333c566b88108de218d882840e61928a9b759d8f on 2014/31/12 at 4:38 PM PST](https://github.com/tgstation/tgstation/commit/333c566b88108de218d882840e61928a9b759d8f) is licensed under [GNU AGPL v3](https://www.gnu.org/licenses/agpl-3.0.html).
-
-All code before [commit 333c566b88108de218d882840e61928a9b759d8f on 2014/31/12 at 4:38 PM PST](https://github.com/tgstation/tgstation/commit/333c566b88108de218d882840e61928a9b759d8f) is licensed under [GNU GPL v3](https://www.gnu.org/licenses/gpl-3.0.html).
-(Including tools unless their readme specifies otherwise.)
-
-See LICENSE and GPLv3.txt for more details.
-
-tgui clientside is licensed as a subproject under the MIT license.
-Font Awesome font files, used by tgui, are licensed under the SIL Open Font License v1.1
-tgui assets are licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/).
-The TGS3 API is licensed as a subproject under the MIT license.
-
-See tgui/LICENSE.md for the MIT license.
-See tgui/assets/fonts/SIL-OFL-1.1-LICENSE.md for the SIL Open Font License.
-See the footers of code/\_\_DEFINES/server\_tools.dm, code/modules/server\_tools/st\_commands.dm, and code/modules/server\_tools/st\_inteface.dm for the MIT license.
-
-All assets including icons and sound are under a [Creative Commons 3.0 BY-SA license](https://creativecommons.org/licenses/by-sa/3.0/) unless otherwise indicated.
-
-byond-extools.dll is licensed under MIT. See MIT.txt for more details.
-
-# Other Codebase Credits
-- /tg/, for the codebase.
-- CEV Eris, for the PDA sprites
-- TGMC, for the custom keybinds base
-- Citadel, for their beautiful lighting
+
"}
diff --git a/code/__DEFINES/tgs.dm b/code/__DEFINES/tgs.dm
index a0a5df429074e..af09ab67110d7 100644
--- a/code/__DEFINES/tgs.dm
+++ b/code/__DEFINES/tgs.dm
@@ -1,6 +1,6 @@
// tgstation-server DMAPI
-#define TGS_DMAPI_VERSION "5.2.7"
+#define TGS_DMAPI_VERSION "6.0.3"
// All functions and datums outside this document are subject to change with any version and should not be relied on.
@@ -67,7 +67,7 @@
#define TGS_EVENT_REPO_CHECKOUT 1
/// When the repository performs a fetch operation. No parameters
#define TGS_EVENT_REPO_FETCH 2
-/// When the repository merges a pull request. Parameters: PR Number, PR Sha, (Nullable) Comment made by TGS user
+/// When the repository test merges. Parameters: PR Number, PR Sha, (Nullable) Comment made by TGS user
#define TGS_EVENT_REPO_MERGE_PULL_REQUEST 3
/// Before the repository makes a sychronize operation. Parameters: Absolute repostiory path
#define TGS_EVENT_REPO_PRE_SYNCHRONIZE 4
@@ -95,8 +95,13 @@
#define TGS_EVENT_WATCHDOG_SHUTDOWN 15
/// Before the watchdog detaches for a TGS update/restart. No parameters.
#define TGS_EVENT_WATCHDOG_DETACH 16
-// We don't actually implement this value as the DMAPI can never receive it
+// We don't actually implement these 4 events as the DMAPI can never receive them.
// #define TGS_EVENT_WATCHDOG_LAUNCH 17
+// #define TGS_EVENT_WATCHDOG_CRASH 18
+// #define TGS_EVENT_WORLD_END_PROCESS 19
+// #define TGS_EVENT_WORLD_REBOOT 20
+/// Watchdog event when TgsInitializationComplete() is called. No parameters.
+#define TGS_EVENT_WORLD_PRIME 21
// OTHER ENUMS
@@ -117,22 +122,21 @@
//REQUIRED HOOKS
/**
- * Call this somewhere in [/world/proc/New] that is always run. This function may sleep!
- *
- * * event_handler - Optional user defined [/datum/tgs_event_handler].
- * * minimum_required_security_level: The minimum required security level to run the game in which the DMAPI is integrated. Can be one of [TGS_SECURITY_ULTRASAFE], [TGS_SECURITY_SAFE], or [TGS_SECURITY_TRUSTED].
- */
+ * Call this somewhere in [/world/proc/New] that is always run. This function may sleep!
+ *
+ * * event_handler - Optional user defined [/datum/tgs_event_handler].
+ * * minimum_required_security_level: The minimum required security level to run the game in which the DMAPI is integrated. Can be one of [TGS_SECURITY_ULTRASAFE], [TGS_SECURITY_SAFE], or [TGS_SECURITY_TRUSTED].
+ */
/world/proc/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE)
return
/**
- * Call this when your initializations are complete and your game is ready to play before any player interactions happen.
- *
- * This may use [/world/var/sleep_offline] to make this happen so ensure no changes are made to it while this call is running.
- * Afterwards, consider explicitly setting it to what you want to avoid this BYOND bug: http://www.byond.com/forum/post/2575184
- * Before this point, note that any static files or directories may be in use by another server. Your code should account for this.
- * This function should not be called before ..() in [/world/proc/New].
- */
+ * Call this when your initializations are complete and your game is ready to play before any player interactions happen.
+ *
+ * This may use [/world/var/sleep_offline] to make this happen so ensure no changes are made to it while this call is running.
+ * Afterwards, consider explicitly setting it to what you want to avoid this BYOND bug: http://www.byond.com/forum/post/2575184
+ * This function should not be called before ..() in [/world/proc/New].
+ */
/world/proc/TgsInitializationComplete()
return
@@ -140,8 +144,8 @@
#define TGS_TOPIC var/tgs_topic_return = TgsTopic(args[1]); if(tgs_topic_return) return tgs_topic_return
/**
- * Call this at the beginning of [world/proc/Reboot].
- */
+ * Call this as late as possible in [world/proc/Reboot].
+ */
/world/proc/TgsReboot()
return
@@ -152,6 +156,8 @@
/datum/tgs_revision_information
/// Full SHA of the commit.
var/commit
+ /// ISO 8601 timestamp of when the commit was created
+ var/timestamp
/// Full sha of last known remote commit. This may be null if the TGS repository is not currently tracking a remote branch.
var/origin_commit
@@ -175,36 +181,34 @@
var/deprefixed_parameter
/**
- * Returns [TRUE]/[FALSE] based on if the [/datum/tgs_version] contains wildcards.
- */
+ * Returns [TRUE]/[FALSE] based on if the [/datum/tgs_version] contains wildcards.
+ */
/datum/tgs_version/proc/Wildcard()
return
/**
- * Returns [TRUE]/[FALSE] based on if the [/datum/tgs_version] equals some other version.
- *
- * other_version - The [/datum/tgs_version] to compare against.
- */
+ * Returns [TRUE]/[FALSE] based on if the [/datum/tgs_version] equals some other version.
+ *
+ * other_version - The [/datum/tgs_version] to compare against.
+ */
/datum/tgs_version/proc/Equals(datum/tgs_version/other_version)
return
/// Represents a merge of a GitHub pull request.
/datum/tgs_revision_information/test_merge
- /// The pull request number.
+ /// The test merge number.
var/number
- /// The pull request title when it was merged.
+ /// The test merge source's title when it was merged.
var/title
- /// The pull request body when it was merged.
+ /// The test merge source's body when it was merged.
var/body
- /// The GitHub username of the pull request's author.
+ /// The Username of the test merge source's author.
var/author
- /// An http URL to the pull request.
+ /// An http URL to the test merge source.
var/url
- /// The SHA of the pull request when that was merged.
- var/pull_request_commit
- /// ISO 8601 timestamp of when the pull request was merged.
- var/time_merged
- /// (Nullable) Comment left by the TGS user who initiated the merge..
+ /// The SHA of the test merge when that was merged.
+ var/head_commit
+ /// Optional comment left by the TGS user who initiated the merge.
var/comment
/// Represents a connected chat channel.
@@ -234,10 +238,10 @@
var/datum/tgs_chat_channel/channel
/**
- * User definable callback for handling TGS events.
- *
- * event_code - One of the TGS_EVENT_ defines. Extra parameters will be documented in each
- */
+ * User definable callback for handling TGS events.
+ *
+ * event_code - One of the TGS_EVENT_ defines. Extra parameters will be documented in each
+ */
/datum/tgs_event_handler/proc/HandleEvent(event_code, ...)
set waitfor = FALSE
return
@@ -252,67 +256,67 @@
var/admin_only = FALSE
/**
- * Process command activation. Should return a string to respond to the issuer with.
- *
- * sender - The [/datum/tgs_chat_user] who issued the command.
- * params - The trimmed string following the command `/datum/tgs_chat_command/var/name].
- */
+ * Process command activation. Should return a string to respond to the issuer with.
+ *
+ * sender - The [/datum/tgs_chat_user] who issued the command.
+ * params - The trimmed string following the command `/datum/tgs_chat_command/var/name].
+ */
/datum/tgs_chat_command/proc/Run(datum/tgs_chat_user/sender, params)
CRASH("[type] has no implementation for Run()")
// API FUNCTIONS
/// Returns the maximum supported [/datum/tgs_version] of the DMAPI.
-/world/proc/TgsMaximumAPIVersion()
+/world/proc/TgsMaximumApiVersion()
return
/// Returns the minimum supported [/datum/tgs_version] of the DMAPI.
-/world/proc/TgsMinimumAPIVersion()
+/world/proc/TgsMinimumApiVersion()
return
/**
- * Returns [TRUE] if DreamDaemon was launched under TGS, the API matches, and was properly initialized. [FALSE] will be returned otherwise.
- */
+ * Returns [TRUE] if DreamDaemon was launched under TGS, the API matches, and was properly initialized. [FALSE] will be returned otherwise.
+ */
/world/proc/TgsAvailable()
return
// No function below this succeeds if it TgsAvailable() returns FALSE or if TgsNew() has yet to be called.
/**
- * Forces a hard reboot of DreamDaemon by ending the process.
- *
- * Unlike del(world) clients will try to reconnect.
- * If TGS has not requested a [TGS_REBOOT_MODE_SHUTDOWN] DreamDaemon will be launched again
- */
+ * Forces a hard reboot of DreamDaemon by ending the process.
+ *
+ * Unlike del(world) clients will try to reconnect.
+ * If TGS has not requested a [TGS_REBOOT_MODE_SHUTDOWN] DreamDaemon will be launched again
+ */
/world/proc/TgsEndProcess()
return
/**
- * Send a message to connected chats.
- *
- * message - The string to send.
- * admin_only: If [TRUE], message will be sent to admin connected chats. Vice-versa applies.
- */
+ * Send a message to connected chats.
+ *
+ * message - The string to send.
+ * admin_only: If [TRUE], message will be sent to admin connected chats. Vice-versa applies.
+ */
/world/proc/TgsTargetedChatBroadcast(message, admin_only = FALSE)
return
/**
- * Send a private message to a specific user.
- *
- * message - The string to send.
- * user: The [/datum/tgs_chat_user] to PM.
- */
+ * Send a private message to a specific user.
+ *
+ * message - The string to send.
+ * user: The [/datum/tgs_chat_user] to PM.
+ */
/world/proc/TgsChatPrivateMessage(message, datum/tgs_chat_user/user)
return
// The following functions will sleep if a call to TgsNew() is sleeping
/**
- * Send a message to connected chats that are flagged as game-related in TGS.
- *
- * message - The string to send.
- * channels - Optional list of [/datum/tgs_chat_channel]s to restrict the message to.
- */
+ * Send a message to connected chats that are flagged as game-related in TGS.
+ *
+ * message - The string to send.
+ * channels - Optional list of [/datum/tgs_chat_channel]s to restrict the message to.
+ */
/world/proc/TgsChatBroadcast(message, list/channels = null)
return
diff --git a/code/__DEFINES/tgui.dm b/code/__DEFINES/tgui.dm
index f594b735b6b5f..f1b1f6e179aea 100644
--- a/code/__DEFINES/tgui.dm
+++ b/code/__DEFINES/tgui.dm
@@ -28,8 +28,34 @@
#define TGUI_WINDOW_INDEX(window_id) text2num(copytext(window_id, 13))
/// Creates a message packet for sending via output()
+// This is {"type":type,"payload":payload}, but pre-encoded. This is much faster
+// than doing it the normal way.
+// To ensure this is correct, this is unit tested in tgui_create_message.
#define TGUI_CREATE_MESSAGE(type, payload) ( \
- url_encode(json_encode(list( \
- "type" = type, \
- "payload" = payload, \
- ))))
+ "%7b%22type%22%3a%22[type]%22%2c%22payload%22%3a[url_encode(json_encode(payload))]%7d" \
+)
+
+/// Telemetry
+
+/**
+ * Maximum number of connection records allowed to analyze.
+ * Should match the value set in the browser.
+ */
+#define TGUI_TELEMETRY_MAX_CONNECTIONS 10
+
+/**
+ * Maximum time allocated for sending a telemetry packet.
+ */
+#define TGUI_TELEMETRY_RESPONSE_WINDOW 30 SECONDS
+
+/// Telemetry statuses
+#define TGUI_TELEMETRY_STAT_NOT_REQUESTED 0 //Not Yet Requested
+#define TGUI_TELEMETRY_STAT_AWAITING 1 //Awaiting request response
+#define TGUI_TELEMETRY_STAT_ANALYZED 2 //Retrieved and validated
+#define TGUI_TELEMETRY_STAT_MISSING 3 //Telemetry response window miss without valid telemetry
+#define TGUI_TELEMETRY_STAT_OVERSEND 4 //Telemetry was already processed but was repeated
+
+/// Telem Trigger Defines
+#define TGUI_TELEM_CKEY_WARNING "TELEM_CKEY_TEXT"
+#define TGUI_TELEM_IP_WARNING "TELEM_IP_TEXT"
+#define TGUI_TELEM_CID_WARNING "TELEM_CID_TEXT"
diff --git a/code/__DEFINES/time.dm b/code/__DEFINES/time.dm
index 9a2993ee65b33..b0c5c70ec43cc 100644
--- a/code/__DEFINES/time.dm
+++ b/code/__DEFINES/time.dm
@@ -21,6 +21,7 @@
#define HALLOWEEN "Halloween"
#define CHRISTMAS "Christmas"
#define FESTIVE_SEASON "Festive Season"
+#define GARBAGEDAY "Garbage Day"
/*
diff --git a/code/__DEFINES/tools.dm b/code/__DEFINES/tools.dm
index ac03c1fd21a52..2b4dcb393c479 100644
--- a/code/__DEFINES/tools.dm
+++ b/code/__DEFINES/tools.dm
@@ -18,3 +18,8 @@
// If delay between the start and the end of tool operation is less than MIN_TOOL_SOUND_DELAY,
// tool sound is only played when op is started. If not, it's played twice.
#define MIN_TOOL_SOUND_DELAY 20
+
+/// When a tooltype_act proc is successful
+#define TOOL_ACT_TOOLTYPE_SUCCESS (1<<0)
+/// When [COMSIG_ATOM_TOOL_ACT] blocks the act
+#define TOOL_ACT_SIGNAL_BLOCKING (1<<1)
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 80c260b5a7980..0f7d205b5f550 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -1,3 +1,6 @@
+#define SIGNAL_ADDTRAIT(trait_ref) "addtrait [trait_ref]"
+#define SIGNAL_REMOVETRAIT(trait_ref) "removetrait [trait_ref]"
+
// trait accessor defines
#define ADD_TRAIT(target, trait, source) \
do { \
@@ -6,12 +9,14 @@
target.status_traits = list(); \
_L = target.status_traits; \
_L[trait] = list(source); \
+ SEND_SIGNAL(target, SIGNAL_ADDTRAIT(trait), trait); \
} else { \
_L = target.status_traits; \
if (_L[trait]) { \
_L[trait] |= list(source); \
} else { \
_L[trait] = list(source); \
+ SEND_SIGNAL(target, SIGNAL_ADDTRAIT(trait), trait); \
} \
} \
} while (0)
@@ -31,13 +36,38 @@
} \
};\
if (!length(_L[trait])) { \
- _L -= trait \
+ _L -= trait; \
+ SEND_SIGNAL(target, SIGNAL_REMOVETRAIT(trait), trait); \
}; \
if (!length(_L)) { \
target.status_traits = null \
}; \
} \
} while (0)
+#define REMOVE_TRAIT_NOT_FROM(target, trait, sources) \
+ do { \
+ var/list/_traits_list = target.status_traits; \
+ var/list/_sources_list; \
+ if (sources && !islist(sources)) { \
+ _sources_list = list(sources); \
+ } else { \
+ _sources_list = sources\
+ }; \
+ if (_traits_list && _traits_list[trait]) { \
+ for (var/_trait_source in _traits_list[trait]) { \
+ if (!(_trait_source in _sources_list)) { \
+ _traits_list[trait] -= _trait_source \
+ } \
+ };\
+ if (!length(_traits_list[trait])) { \
+ _traits_list -= trait; \
+ SEND_SIGNAL(target, SIGNAL_REMOVETRAIT(trait), trait); \
+ }; \
+ if (!length(_traits_list)) { \
+ target.status_traits = null \
+ }; \
+ } \
+ } while (0)
#define REMOVE_TRAITS_NOT_IN(target, sources) \
do { \
var/list/_L = target.status_traits; \
@@ -46,13 +76,38 @@
for (var/_T in _L) { \
_L[_T] &= _S;\
if (!length(_L[_T])) { \
- _L -= _T } \
+ _L -= _T; \
+ SEND_SIGNAL(target, SIGNAL_REMOVETRAIT(_T), _T); \
+ }; \
};\
- if (!length(_L)) { \
- target.status_traits = null\
+ if (!length(_L)) { \
+ target.status_traits = null\
+ };\
+ }\
+ } while (0)
+#define REMOVE_TRAITS_IN(target, sources) \
+ do { \
+ var/list/_L = target.status_traits; \
+ var/list/_S = sources; \
+ if (sources && !islist(sources)) { \
+ _S = list(sources); \
+ } else { \
+ _S = sources\
+ }; \
+ if (_L) { \
+ for (var/_T in _L) { \
+ _L[_T] -= _S;\
+ if (!length(_L[_T])) { \
+ _L -= _T; \
+ SEND_SIGNAL(target, SIGNAL_REMOVETRAIT(_T)); \
+ }; \
};\
+ if (!length(_L)) { \
+ target.status_traits = null\
+ };\
}\
} while (0)
+
#define HAS_TRAIT(target, trait) (target.status_traits ? (target.status_traits[trait] ? TRUE : FALSE) : FALSE)
#define HAS_TRAIT_FROM(target, trait, source) (target.status_traits ? (target.status_traits[trait] ? (source in target.status_traits[trait]) : FALSE) : FALSE)
#define HAS_TRAIT_FROM_ONLY(target, trait, source) (\
@@ -67,6 +122,10 @@
Remember to update _globalvars/traits.dm if you're adding/removing/renaming traits.
*/
+/*
+Remember to update _globalvars/traits.dm if you're adding/removing/renaming traits.
+*/
+
//mob traits
#define TRAIT_BLIND "blind"
#define TRAIT_MUTE "mute"
@@ -83,6 +142,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_IGNORESLOWDOWN "ignoreslow"
#define TRAIT_IGNOREDAMAGESLOWDOWN "ignoredamageslowdown"
#define TRAIT_DEATHCOMA "deathcoma" //Causes death-like unconsciousness
+#define TRAIT_REGEN_COMA "regencoma"
#define TRAIT_FAKEDEATH "fakedeath" //Makes the owner appear as dead to most forms of medical examination
#define TRAIT_DISFIGURED "disfigured"
#define TRAIT_XENO_HOST "xeno_host" //Tracks whether we're gonna be a baby alien's mummy.
@@ -107,6 +167,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_NOGUNS "no_guns"
#define TRAIT_NOHUNGER "no_hunger"
#define TRAIT_NOMETABOLISM "no_metabolism"
+#define TRAIT_POWERHUNGRY "power_hungry" //uses electricity instead of food
+#define TRAIT_NOCLONELOSS "no_cloneloss"
#define TRAIT_TOXIMMUNE "toxin_immune"
#define TRAIT_EASYDISMEMBER "easy_dismember"
#define TRAIT_LIMBATTACHMENT "limb_attach"
@@ -150,6 +212,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_LAW_ENFORCEMENT_METABOLISM "law-enforcement-metabolism"
#define TRAIT_ALWAYS_CLEAN "always-clean"
#define TRAIT_BOOZE_SLIDER "booze-slider"
+#define TRAIT_QUICK_CARRY "quick-carry"
+#define TRAIT_QUICKER_CARRY "quicker-carry"
#define TRAIT_UNINTELLIGIBLE_SPEECH "unintelligible-speech"
#define TRAIT_UNSTABLE "unstable"
#define TRAIT_XENO_IMMUNE "xeno_immune" //prevents facehuggers implanting races that wouldn't be able to host an egg
@@ -163,16 +227,31 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_APPRAISAL "appraisal"
#define TRAIT_NOBLOCK "noblock"
#define TRAIT_NANITECOMPATIBLE "nanitecompatible"
+#define TRAIT_WARDED "curse_immune"
+#define TRAIT_NONECRODISEASE "nonecrodisease"
+#define TRAIT_DIGICAMO "digital_camo"
+#define TRAIT_DIGINVIS "digital_invis" //note: diginvis grants digitalcamo, but carbons can tell if you have the digicamo trait on examine
+#define TRAIT_NICE_SHOT "nice_shot" //hnnnnnnnggggg..... you're pretty good....
+#define TRAIT_ALWAYS_STUBS "always_stubs_toe" //you will always stub your toe on tables, even if you're wearing shoes
//non-mob traits
#define TRAIT_PARALYSIS "paralysis" //Used for limb-based paralysis, where replacing the limb will fix it
+#define TRAIT_HEARING_SENSITIVE "hearing_sensitive"
+
// item traits
#define TRAIT_NODROP "nodrop"
+#define TRAIT_NO_STORAGE_INSERT "no_storage_insert" //cannot be inserted in a storage.
#define TRAIT_SPRAYPAINTED "spraypainted"
#define TRAIT_T_RAY_VISIBLE "t-ray-visible" // Visible on t-ray scanners if the atom/var/level == 1
#define TRAIT_NO_TELEPORT "no-teleport" //you just can't
#define TRAIT_STARGAZED "stargazed" //Affected by a stargazer
+#define TRAIT_DOOR_PRYER "door-pryer" //Item can be used on airlocks to pry them open (even when powered)
+#define TRAIT_FISH_SAFE_STORAGE "fish_case" //Fish in this won't die
+#define TRAIT_FISH_CASE_COMPATIBILE "fish_case_compatibile" //Stuff that can go inside fish cases
+#define TRAIT_NEEDS_TWO_HANDS "needstwohands" // The items needs two hands to be carried
+
+#define TRAIT_AI_BAGATTACK "bagattack" // This atom can ignore the "is on a turf" check for simple AI datum attacks, allowing them to attack from bags or lockers as long as any other conditions are met
//quirk traits
#define TRAIT_ALCOHOL_TOLERANCE "alcohol_tolerance"
@@ -198,8 +277,12 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_FRIENDLY "friendly"
#define TRAIT_GRABWEAKNESS "grab_weakness"
+///Trait applied to turfs when an atmos holosign is placed on them. It will stop firedoors from closing.
+#define TRAIT_FIREDOOR_STOP "firedoor_stop"
+
// common trait sources
#define TRAIT_GENERIC "generic"
+#define GENERIC_ITEM_TRAIT "generic_item"
#define EYE_DAMAGE "eye_damage"
#define GENETIC_MUTATION "genetic"
#define OBESITY "obesity"
@@ -220,13 +303,16 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define CLOTHING_TRAIT "clothing"
#define VEHICLE_TRAIT "vehicle" // inherited from riding vehicles
#define INNATE_TRAIT "innate"
+#define GLASSES_TRAIT "glasses"
+#define CURSE_TRAIT "eldritch"
+#define STATION_TRAIT "station-trait"
// unique trait sources, still defines
#define CLONING_POD_TRAIT "cloning-pod"
#define STATUE_MUTE "statue"
#define CHANGELING_DRAIN "drain"
#define CHANGELING_HIVEMIND_MUTE "ling_mute"
-#define ABYSSAL_GAZE_BLIND "abyssal_gaze"
+#define MAGIC_BLIND "magic_blind"
#define HIGHLANDER "highlander"
#define TRAIT_HULK "hulk"
#define STASIS_MUTE "stasis"
@@ -249,10 +335,12 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define HAND_REPLACEMENT_TRAIT "magic-hand"
#define HOT_POTATO_TRAIT "hot-potato"
#define SABRE_SUICIDE_TRAIT "sabre-suicide"
+#define CORGIUM_TRAIT "corgium"
#define ABDUCTOR_VEST_TRAIT "abductor-vest"
#define CAPTURE_THE_FLAG_TRAIT "capture-the-flag"
#define EYE_OF_GOD_TRAIT "eye-of-god"
#define SHAMEBRERO_TRAIT "shamebrero"
+#define JAUNT_TRAIT "jaunt"
#define CHRONOSUIT_TRAIT "chronosuit"
#define LOCKED_HELMET_TRAIT "locked-helmet"
#define NINJA_SUIT_TRAIT "ninja-suit"
@@ -268,3 +356,24 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define STARGAZER_TRAIT "stargazer"
#define GUARDIAN_TRAIT "guardian_trait"
#define PARRY_TRAIT "parry_trait"
+#define LIGHTPINK_TRAIT "lightpinktrait"
+#define BATTLE_ROYALE_TRAIT "battleroyale_trait"
+#define MADE_UNCLONEABLE "made-uncloneable"
+#define TRAIT_JAWS_OF_LIFE "jaws-of-life"
+#define STICKY_NODROP "sticky-nodrop" //sticky nodrop sounds like a bad soundcloud rapper's name
+
+///Traits given by station traits
+#define STATION_TRAIT_BANANIUM_SHIPMENTS "station_trait_bananium_shipments"
+#define STATION_TRAIT_CARP_INFESTATION "station_trait_carp_infestation"
+#define STATION_TRAIT_PREMIUM_INTERNALS "station_trait_premium_internals"
+#define STATION_TRAIT_LATE_ARRIVALS "station_trait_late_arrivals"
+#define STATION_TRAIT_RANDOM_ARRIVALS "station_trait_random_arrivals"
+#define STATION_TRAIT_HANGOVER "station_trait_hangover"
+#define STATION_TRAIT_FILLED_MAINT "station_trait_filled_maint"
+#define STATION_TRAIT_EMPTY_MAINT "station_trait_empty_maint"
+#define STATION_TRAIT_PDA_GLITCHED "station_trait_pda_glitched"
+#define STATION_TRAIT_DISTANT_SUPPLY_LINES "distant_supply_lines"
+#define STATION_TRAIT_STRONG_SUPPLY_LINES "strong_supply_lines"
+
+/// Trait applied when the MMI component is added to an [/obj/item/integrated_circuit]
+#define TRAIT_COMPONENT_MMI "component_mmi"
diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm
index e4240779f7e6e..561f78c3a76b1 100644
--- a/code/__DEFINES/vv.dm
+++ b/code/__DEFINES/vv.dm
@@ -73,7 +73,11 @@
#define VV_HK_EXPOSE "expose"
#define VV_HK_CALLPROC "proc_call"
#define VV_HK_MARK "mark"
+#define VV_HK_ADDCOMPONENT "addcomponent"
#define VV_HK_MODIFY_TRAITS "modtraits"
+#ifdef REFERENCE_TRACKING
+#define VV_HK_VIEW_REFERENCES "viewreferences"
+#endif
// /atom
#define VV_HK_MODIFY_TRANSFORM "atom_transform"
@@ -81,6 +85,15 @@
#define VV_HK_TRIGGER_EMP "empulse"
#define VV_HK_TRIGGER_EXPLOSION "explode"
#define VV_HK_AUTO_RENAME "auto_rename"
+#define VV_HK_EDIT_FILTERS "edit_filters"
+#define VV_HK_ADD_AI "add_ai"
+
+// /datum/gas_mixture
+#define VV_HK_SET_MOLES "set_moles"
+#define VV_HK_EMPTY "empty"
+#define VV_HK_SET_TEMPERATURE "set_temp"
+#define VV_HK_PARSE_GASSTRING "parse_gasstring"
+#define VV_HK_SET_VOLUME "set_volume"
// /obj
#define VV_HK_OSAY "osay"
@@ -124,5 +137,5 @@
#define VV_HK_SPACEVINE_PURGE "spacevine_purge"
#define VV_HK_MODIFY_CANISTER_GAS "modify_canister_gas"
-
-
+// paintings
+#define VV_HK_REMOVE_PAINTING "remove_painting"
diff --git a/code/__DEFINES/wiremod.dm b/code/__DEFINES/wiremod.dm
new file mode 100644
index 0000000000000..f158272f4baa7
--- /dev/null
+++ b/code/__DEFINES/wiremod.dm
@@ -0,0 +1,211 @@
+/// Helper define that can only be used in /obj/item/circuit_component/input_received()
+#define COMPONENT_TRIGGERED_BY(trigger, port) (trigger.input_value && trigger == port)
+
+/// Define to automatically handle calling the output port. Will not call the output port if the input_received proc returns TRUE.
+#define TRIGGER_CIRCUIT_COMPONENT(component, port) if(!component.input_received(port) && (component.circuit_flags & CIRCUIT_FLAG_OUTPUT_SIGNAL)) component.trigger_output.set_output(COMPONENT_SIGNAL)
+
+// Port types. Determines what the port can connect to
+
+/// Can accept any datatype. Only works for inputs, output types will runtime.
+#define PORT_TYPE_ANY null
+
+// Fundamental datatypes
+/// String datatype
+#define PORT_TYPE_STRING "string"
+#define PORT_MAX_STRING_LENGTH 500
+/// Number datatype
+#define PORT_TYPE_NUMBER "number"
+/// Signal datatype
+#define PORT_TYPE_SIGNAL "signal"
+/// List datatype
+#define PORT_TYPE_LIST "list"
+/// Table datatype. Derivative of list, contains other lists with matching columns.
+#define PORT_TYPE_TABLE "table"
+
+// Other datatypes
+/// Atom datatype
+#define PORT_TYPE_ATOM "entity"
+/// Any datatype (USED ONLY FOR DISPLAY, DO NOT USE)
+#define COMP_TYPE_ANY "any"
+
+
+/// The maximum range between a port and an atom
+#define PORT_ATOM_MAX_RANGE 7
+
+#define COMPONENT_DEFAULT_NAME "component"
+
+/// The minimum position of the x and y co-ordinates of the component in the UI
+#define COMPONENT_MIN_RANDOM_POS 200
+/// The maximum position of the x and y co-ordinates of the component in the UI
+#define COMPONENT_MAX_RANDOM_POS 400
+
+/// The maximum position in both directions that a component can be in.
+/// Prevents someone from positioning a component at an absurdly high value.
+#define COMPONENT_MAX_POS 10000
+
+// Components
+
+/// The value that is sent whenever a component is simply sending a signal. This can be anything.
+#define COMPONENT_SIGNAL 1
+
+/// The largest sized list a component can make
+#define COMPONENT_MAXIMUM_LIST_SIZE 256
+
+// Comparison defines
+#define COMP_COMPARISON_EQUAL "="
+#define COMP_COMPARISON_NOT_EQUAL "!="
+#define COMP_COMPARISON_GREATER_THAN ">"
+#define COMP_COMPARISON_LESS_THAN "<"
+#define COMP_COMPARISON_GREATER_THAN_OR_EQUAL ">="
+#define COMP_COMPARISON_LESS_THAN_OR_EQUAL "<="
+
+// Delay defines
+/// The minimum delay value that the delay component can have.
+#define COMP_DELAY_MIN_VALUE 0.1
+
+// Logic defines
+#define COMP_LOGIC_AND "AND"
+#define COMP_LOGIC_OR "OR"
+#define COMP_LOGIC_XOR "XOR"
+
+// Arithmetic defines
+#define COMP_ARITHMETIC_ADD "Add"
+#define COMP_ARITHMETIC_SUBTRACT "Subtract"
+#define COMP_ARITHMETIC_MULTIPLY "Multiply"
+#define COMP_ARITHMETIC_DIVIDE "Divide"
+#define COMP_ARITHMETIC_MODULO "Modulus"
+#define COMP_ARITHMETIC_MIN "Minimum"
+#define COMP_ARITHMETIC_MAX "Maximum"
+
+//Bitwise defines
+#define COMP_BITWISE_AND "And"
+#define COMP_BITWISE_OR "Or"
+#define COMP_BITWISE_XOR "Xor"
+#define COMP_BITWISE_LEFTSHIFT "Left Shift"
+#define COMP_BITWISE_RIGHTSHIFT "Right Shift"
+
+//Round defines
+#define COMP_ROUND_ROUND "Round"
+#define COMP_ROUND_FLOOR "Floor"
+#define COMP_ROUND_CEIL "Ceil"
+
+//Trig defines
+#define COMP_TRIG_COSINE "COS"
+#define COMP_TRIG_SINE "SIN"
+#define COMP_TRIG_TANGENT "TAN"
+#define COMP_TRIG_ASINE "ASIN"
+#define COMP_TRIG_ACOSINE "ACOS"
+#define COMP_TRIG_ATANGENT "ATAN"
+
+//Advanced Trig defines
+#define COMP_TRIG_SECANT "SEC"
+#define COMP_TRIG_COSECANT "CSC"
+#define COMP_TRIG_COTANGENT "COT"
+
+//Hyperbolic Trig Defines
+#define COMP_TRIG_HYPERBOLIC_COSINE "COSH"
+#define COMP_TRIG_HYPERBOLIC_SINE "SINH"
+#define COMP_TRIG_AHYPERBOLIC_COSINE "ACOSH"
+#define COMP_TRIG_AHYPERBOLIC_SINE "ASING"
+
+//Indexer defines
+#define COMP_INDEXER_NONE "None"
+#define COMP_INDEXER_INCREMENT "Increment"
+#define COMP_INDEXER_LOOP "Loop"
+#define COMP_INDEXER_BOTH "Both"
+
+#define COMP_INDEXER_FLAG_INCREMENT (1<<0)
+#define COMP_INDEXER_FLAG_LOOP (1<<1)
+
+//Pop defines
+#define COMP_POP_POP "Pop"
+#define COMP_POP_DEQUEUE "Dequeue"
+
+// Text defines
+#define COMP_TEXT_LOWER "To Lower"
+#define COMP_TEXT_UPPER "To Upper"
+
+// Typecheck component
+#define COMP_TYPECHECK_MOB "organism"
+#define COMP_TYPECHECK_HUMAN "humanoid"
+
+// Clock component
+#define COMP_CLOCK_DELAY 0.9 SECONDS
+
+// Radio component
+#define COMP_RADIO_PUBLIC "public"
+#define COMP_RADIO_PRIVATE "private"
+
+// Sound component
+#define COMP_SOUND_BUZZ "Buzz"
+#define COMP_SOUND_BUZZ_TWO "Buzz Twice"
+#define COMP_SOUND_CHIME "Chime"
+#define COMP_SOUND_HONK "Honk"
+#define COMP_SOUND_PING "Ping"
+#define COMP_SOUND_SAD "Sad Trombone"
+#define COMP_SOUND_WARN "Warn"
+#define COMP_SOUND_SLOWCLAP "Slow Clap"
+
+// Security Arrest Console
+#define COMP_STATE_ARREST "*Arrest*"
+#define COMP_STATE_PRISONER "Incarcerated"
+#define COMP_STATE_PAROL "Paroled"
+#define COMP_STATE_DISCHARGED "Discharged"
+#define COMP_STATE_NONE "None"
+
+#define COMP_SECURITY_ARREST_AMOUNT_TO_FLAG 10
+
+// Shells
+
+/// Whether a circuit is stuck on a shell and cannot be removed (by a user)
+#define SHELL_FLAG_CIRCUIT_FIXED (1<<0)
+
+/// Whether the shell needs to be anchored for the circuit to be on.
+#define SHELL_FLAG_REQUIRE_ANCHOR (1<<1)
+
+/// Whether or not the shell has a USB port.
+#define SHELL_FLAG_USB_PORT (1<<2)
+
+/// Whether the shell allows actions to be peformed on a shell if the action fails. This will additionally block the messages from being displayed.
+#define SHELL_FLAG_ALLOW_FAILURE_ACTION (1<<3)
+
+// Shell capacities. These can be converted to configs very easily later
+#define SHELL_CAPACITY_SMALL 10
+#define SHELL_CAPACITY_MEDIUM 25
+#define SHELL_CAPACITY_LARGE 50
+#define SHELL_CAPACITY_VERY_LARGE 500
+
+/// The maximum range a USB cable can be apart from a source
+#define USB_CABLE_MAX_RANGE 2
+
+// Circuit flags
+/// Creates an input trigger that means the component won't be triggered unless the trigger is pulsed.
+#define CIRCUIT_FLAG_INPUT_SIGNAL (1<<0)
+/// Creates an output trigger that sends a pulse whenever the component is successfully triggered
+#define CIRCUIT_FLAG_OUTPUT_SIGNAL (1<<1)
+
+#define WIREMOD_CIRCUITRY "Circuitry"
+#define WIREMOD_CORE "Core"
+#define WIREMOD_SHELLS "Shells"
+#define WIREMOD_INPUT_COMPONENTS "Input Components"
+#define WIREMOD_OUTPUT_COMPONENTS "Output Components"
+#define WIREMOD_MATH_COMPONENTS "Math Components"
+#define WIREMOD_TIME_COMPONENTS "Time Components"
+#define WIREMOD_LOGIC_COMPONENTS "Logic Components"
+#define WIREMOD_LIST_COMPONENTS "List Components"
+#define WIREMOD_MEMORY_COMPONENTS "Memory Components"
+#define WIREMOD_CONVERSION_COMPONENTS "Conversion Components"
+#define WIREMOD_STRING_COMPONENTS "String Components"
+#define WIREMOD_REFERENCE_COMPONENTS "Reference Components"
+
+#define WIREMODE_CATEGORIES list(\
+ WIREMOD_CIRCUITRY,\
+ WIREMOD_CORE,\
+ WIREMOD_SHELLS,\
+ WIREMOD_INPUT_COMPONENTS,\
+ WIREMOD_OUTPUT_COMPONENTS,\
+ WIREMOD_MATH_COMPONENTS,\
+ WIREMOD_TIME_COMPONENTS,\
+ WIREMOD_LOGIC_COMPONENTS,\
+ WIREMOD_LIST_COMPONENTS\
+ )
diff --git a/code/__HELPERS/AStar.dm b/code/__HELPERS/AStar.dm
deleted file mode 100644
index f762f40df97c2..0000000000000
--- a/code/__HELPERS/AStar.dm
+++ /dev/null
@@ -1,221 +0,0 @@
-/*!
-### A Star pathfinding algorithm
-
-Returns a list of tiles forming a path from A to B, taking dense objects as well as walls, and the orientation of windows along the route into account.
-
-**Use:**
-
-```
-your_list = AStar(start location, end location, moving atom, distance proc, max nodes, maximum node depth, minimum distance to target, adjacent proc, atom id, turfs to exclude, check only simulated)
-```
-
-Optional extras to add on (in order):
-- Distance proc: the distance used in every A* calculation (length of path and heuristic)
-- MaxNodes: The maximum number of nodes the returned path can be (0 = infinite)
-- Maxnodedepth: The maximum number of nodes to search (default: 30, 0 = infinite)
-- Mintargetdist: Minimum distance to the target before path returns, could be used to get near a target, but not right to it - for an AI mob with a gun, for example.
-- Adjacent proc: returns the turfs to consider around the actually processed node
-- Simulated only: whether to consider unsimulated turfs or not (used by some Adjacent proc)
-
-Also added 'exclude' turf to avoid travelling over; defaults to null
-
-Actual Adjacent procs :
-
-- `/turf/proc/reachableAdjacentTurfs`: returns reachable turfs in cardinal directions (uses simulated_only)
-
-- `/turf/proc/reachableAdjacentAtmosTurfs`: returns turfs in cardinal directions reachable via atmos
-
-*/
-
-/// Tiebreker weight.To help to choose between equal paths
-#define PF_TIEBREAKER 0.005
-
-#define MASK_ODD 85
-#define MASK_EVEN 170
-
-
-//! ### A* nodes variables
-/datum/PathNode
- /// turf associated with the PathNode
- var/turf/source
- /// link to the parent PathNode
- var/datum/PathNode/prevNode
- /// A* Node weight `(f = g + h)`
- var/f
- /// A* movement cost variable
- var/g
- /// A* heuristic variable
- var/h
- /// count the number of Nodes traversed
- var/nt
- /// bitflag for dir to expand.Some sufficiently advanced motherfuckery
- var/bf
-
-/datum/PathNode/New(s,p,pg,ph,pnt,_bf)
- source = s
- prevNode = p
- g = pg
- h = ph
- f = g + h*(1+ PF_TIEBREAKER)
- nt = pnt
- bf = _bf
-
-/datum/PathNode/proc/setp(p,pg,ph,pnt)
- prevNode = p
- g = pg
- h = ph
- f = g + h*(1+ PF_TIEBREAKER)
- nt = pnt
-
-/datum/PathNode/proc/calc_f()
- f = g + h
-
-//////////////////////
-//A* procs
-//////////////////////
-
-/// the weighting function, used in the A* algorithm
-/proc/PathWeightCompare(datum/PathNode/a, datum/PathNode/b)
- return a.f - b.f
-
-//reversed so that the Heap is a MinHeap rather than a MaxHeap
-/proc/HeapPathWeightCompare(datum/PathNode/a, datum/PathNode/b)
- return b.f - a.f
-
-//wrapper that returns an empty list if A* failed to find a path
-/proc/get_path_to(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = TRUE)
- var/l = SSpathfinder.mobs.getfree(caller)
- while(!l)
- stoplag(3)
- l = SSpathfinder.mobs.getfree(caller)
- var/list/path = AStar(caller, end, dist, maxnodes, maxnodedepth, mintargetdist, adjacent,id, exclude, simulated_only)
-
- SSpathfinder.mobs.found(l)
- if(!path)
- path = list()
- return path
-
-/proc/cir_get_path_to(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = TRUE)
- var/l = SSpathfinder.circuits.getfree(caller)
- while(!l)
- stoplag(3)
- l = SSpathfinder.circuits.getfree(caller)
- var/list/path = AStar(caller, end, dist, maxnodes, maxnodedepth, mintargetdist, adjacent,id, exclude, simulated_only)
- SSpathfinder.circuits.found(l)
- if(!path)
- path = list()
- return path
-
-/proc/AStar(caller, _end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = TRUE)
- //sanitation
- var/turf/end = get_turf(_end)
- var/turf/start = get_turf(caller)
- if(!start || !end)
- stack_trace("Invalid A* start or destination")
- return FALSE
- if( start.z != end.z || start == end ) //no pathfinding between z levels
- return FALSE
- if(maxnodes)
- //if start turf is farther than maxnodes from end turf, no need to do anything
- if(call(start, dist)(end) > maxnodes)
- return FALSE
- maxnodedepth = maxnodes //no need to consider path longer than maxnodes
- var/datum/Heap/open = new /datum/Heap(/proc/HeapPathWeightCompare) //the open list
- var/list/openc = new() //open list for node check
- var/list/path = null //the returned path, if any
- //initialization
- var/datum/PathNode/cur = new /datum/PathNode(start,null,0,call(start,dist)(end),0,15,1)//current processed turf
- open.Insert(cur)
- openc[start] = cur
- //then run the main loop
- while(!open.IsEmpty() && !path)
- cur = open.Pop() //get the lower f turf in the open list
- //get the lower f node on the open list
- //if we only want to get near the target, check if we're close enough
- var/closeenough
- if(mintargetdist)
- closeenough = call(cur.source,dist)(end) <= mintargetdist
-
-
- //found the target turf (or close enough), let's create the path to it
- if(cur.source == end || closeenough)
- path = new()
- path.Add(cur.source)
- while(cur.prevNode)
- cur = cur.prevNode
- path.Add(cur.source)
- break
- //get adjacents turfs using the adjacent proc, checking for access with id
- if((!maxnodedepth)||(cur.nt <= maxnodedepth))//if too many steps, don't process that path
- for(var/i = 0 to 3)
- var/f= 1<>1) //getting reverse direction throught swapping even and odd bits.((f & 01010101)<<1)|((f & 10101010)>>1)
- var/newg = cur.g + call(cur.source,dist)(T)
- if(CN)
- //is already in open list, check if it's a better way from the current turf
- CN.bf &= 15^r //we have no closed, so just cut off exceed dir.00001111 ^ reverse_dir.We don't need to expand to checked turf.
- if((newg < CN.g) )
- if(call(cur.source,adjacent)(caller, T, id, simulated_only))
- CN.setp(cur,newg,CN.h,cur.nt+1)
- open.ReSort(CN)//reorder the changed element in the list
- else
- //is not already in open list, so add it
- if(call(cur.source,adjacent)(caller, T, id, simulated_only))
- CN = new(T,cur,newg,call(T,dist)(end),cur.nt+1,15^r)
- open.Insert(CN)
- openc[T] = CN
- cur.bf = 0
- CHECK_TICK
- //reverse the path to get it from start to finish
- if(path)
- for(var/i = 1 to round(0.5*path.len))
- path.Swap(i,path.len-i+1)
- openc = null
- //cleaning after us
- return path
-
-//Returns adjacent turfs in cardinal directions that are reachable
-//simulated_only controls whether only simulated turfs are considered or not
-
-/turf/proc/reachableAdjacentTurfs(caller, ID, simulated_only)
- var/list/L = new()
- var/turf/T
- var/static/space_type_cache = typecacheof(/turf/open/space)
-
- for(var/k in 1 to GLOB.cardinals.len)
- T = get_step(src,GLOB.cardinals[k])
- if(!T || (simulated_only && space_type_cache[T.type]))
- continue
- if(!T.density && !LinkBlockedWithAccess(T,caller, ID))
- L.Add(T)
- return L
-
-/turf/proc/reachableTurftest(caller, var/turf/T, ID, simulated_only)
- if(T && !T.density && !(simulated_only && SSpathfinder.space_type_cache[T.type]) && !LinkBlockedWithAccess(T,caller, ID))
- return TRUE
-
-//Returns adjacent turfs in cardinal directions that are reachable via atmos
-/turf/proc/reachableAdjacentAtmosTurfs()
- return atmos_adjacent_turfs
-
-/turf/proc/LinkBlockedWithAccess(turf/T, caller, ID)
- var/adir = get_dir(src, T)
- var/rdir = ((adir & MASK_ODD)<<1)|((adir & MASK_EVEN)>>1)
- for(var/obj/structure/window/W in src)
- if(!W.CanAStarPass(ID, adir))
- return TRUE
- for(var/obj/machinery/door/window/W in src)
- if(!W.CanAStarPass(ID, adir))
- return TRUE
- for(var/obj/O in T)
- if(!O.CanAStarPass(ID, rdir, caller))
- return TRUE
- for(var/obj/machinery/door/firedoor/border_only/W in src)
- if(!W.CanAStarPass(ID, adir, caller))
- return TRUE
-
- return FALSE
diff --git a/code/__HELPERS/_extools_api.dm b/code/__HELPERS/_extools_api.dm
new file mode 100644
index 0000000000000..d1961907e1e86
--- /dev/null
+++ b/code/__HELPERS/_extools_api.dm
@@ -0,0 +1,23 @@
+//#define EXTOOLS_LOGGING // rust_g is used as a fallback if this is undefined
+
+/proc/extools_log_write()
+
+/proc/extools_finalize_logging()
+
+GLOBAL_LIST_EMPTY(auxtools_initialized)
+
+#define AUXTOOLS_CHECK(LIB)\
+ if (!GLOB.auxtools_initialized[LIB] && fexists(LIB)) {\
+ var/string = call(LIB,"auxtools_init")();\
+ if(findtext(string, "SUCCESS")) {\
+ GLOB.auxtools_initialized[LIB] = TRUE;\
+ } else {\
+ CRASH(string);\
+ }\
+ }\
+
+#define AUXTOOLS_SHUTDOWN(LIB)\
+ if (GLOB.auxtools_initialized[LIB] && fexists(LIB)){\
+ call(LIB,"auxtools_shutdown")();\
+ GLOB.auxtools_initialized[LIB] = FALSE;\
+ }\
diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm
index 7a4641d53987d..9cfddb7f04ac1 100644
--- a/code/__HELPERS/_lists.dm
+++ b/code/__HELPERS/_lists.dm
@@ -11,23 +11,76 @@
#define LAZYINITLIST(L) if (!L) L = list()
#define UNSETEMPTY(L) if (L && !length(L)) L = null
+#define LAZYCOPY(L) (L ? L.Copy() : list() )
#define LAZYREMOVE(L, I) if(L) { L -= I; if(!length(L)) { L = null; } }
#define LAZYADD(L, I) if(!L) { L = list(); } L += I;
#define LAZYOR(L, I) if(!L) { L = list(); } L |= I;
#define LAZYFIND(L, V) L ? L.Find(V) : 0
#define LAZYACCESS(L, I) (L ? (isnum_safe(I) ? (I > 0 && I <= length(L) ? L[I] : null) : L[I]) : null)
#define LAZYSET(L, K, V) if(!L) { L = list(); } L[K] = V;
-#define LAZYLEN(L) length(L)
+#define LAZYLEN(L) length(L) // should only be used for lazy lists. Using this with non-lazy lists is bad
#define LAZYCLEARLIST(L) if(L) L.Cut()
#define SANITIZE_LIST(L) ( islist(L) ? L : list() )
#define reverseList(L) reverseRange(L.Copy())
+#define LAZYADDASSOC(L, K, V) if(!L) { L = list(); } L[K] += V;
+///This is used to add onto lazy assoc list when the value you're adding is a /list/. This one has extra safety over lazyaddassoc because the value could be null (and thus cant be used to += objects)
+#define LAZYADDASSOCLIST(L, K, V) if(!L) { L = list(); } L[K] += list(V);
+#define LAZYREMOVEASSOC(L, K, V) if(L) { if(L[K]) { L[K] -= V; if(!length(L[K])) L -= K; } if(!length(L)) L = null; }
+#define LAZYACCESSASSOC(L, I, K) L ? L[I] ? L[I][K] ? L[I][K] : null : null : null
+#define QDEL_LAZYLIST(L) for(var/I in L) qdel(I); L = null;
+
+/// Performs an insertion on the given lazy list with the given key and value. If the value already exists, a new one will not be made.
+#define LAZYORASSOCLIST(lazy_list, key, value) \
+ LAZYINITLIST(lazy_list); \
+ LAZYINITLIST(lazy_list[key]); \
+ lazy_list[key] |= value;
+
+
+/// Passed into BINARY_INSERT to compare keys
+#define COMPARE_KEY __BIN_LIST[__BIN_MID]
+/// Passed into BINARY_INSERT to compare values
+#define COMPARE_VALUE __BIN_LIST[__BIN_LIST[__BIN_MID]]
+/****
+ * Binary search sorted insert
+ * INPUT: Object to be inserted
+ * LIST: List to insert object into
+ * TYPECONT: The typepath of the contents of the list
+ * COMPARE: The object to compare against, usualy the same as INPUT
+ * COMPARISON: The variable on the objects to compare
+ * COMPTYPE: How should the values be compared? Either COMPARE_KEY or COMPARE_VALUE.
+ */
+#define BINARY_INSERT(INPUT, LIST, TYPECONT, COMPARE, COMPARISON, COMPTYPE) \
+ do {\
+ var/list/__BIN_LIST = LIST;\
+ var/__BIN_CTTL = length(__BIN_LIST);\
+ if(!__BIN_CTTL) {\
+ __BIN_LIST += INPUT;\
+ } else {\
+ var/__BIN_LEFT = 1;\
+ var/__BIN_RIGHT = __BIN_CTTL;\
+ var/__BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\
+ var ##TYPECONT/__BIN_ITEM;\
+ while(__BIN_LEFT < __BIN_RIGHT) {\
+ __BIN_ITEM = COMPTYPE;\
+ if(__BIN_ITEM.##COMPARISON <= COMPARE.##COMPARISON) {\
+ __BIN_LEFT = __BIN_MID + 1;\
+ } else {\
+ __BIN_RIGHT = __BIN_MID;\
+ };\
+ __BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\
+ };\
+ __BIN_ITEM = COMPTYPE;\
+ __BIN_MID = __BIN_ITEM.##COMPARISON > COMPARE.##COMPARISON ? __BIN_MID : __BIN_MID + 1;\
+ __BIN_LIST.Insert(__BIN_MID, INPUT);\
+ };\
+ } while(FALSE)
-// binary search sorted insert
+// binary search sorted insert (COMPARE = TEXT)
// IN: Object to be inserted
// LIST: List to insert object into
// TYPECONT: The typepath of the contents of the list
-// COMPARE: The variable on the objects to compare
-#define BINARY_INSERT(IN, LIST, TYPECONT, COMPARE) \
+// COMPARE: The variable on the objects to compare (Must be a string)
+#define BINARY_INSERT_TEXT(IN, LIST, TYPECONT, COMPARE) \
var/__BIN_CTTL = length(LIST);\
if(!__BIN_CTTL) {\
LIST += IN;\
@@ -38,7 +91,7 @@
var/##TYPECONT/__BIN_ITEM;\
while(__BIN_LEFT < __BIN_RIGHT) {\
__BIN_ITEM = LIST[__BIN_MID];\
- if(__BIN_ITEM.##COMPARE <= IN.##COMPARE) {\
+ if(sorttext(__BIN_ITEM.##COMPARE, IN.##COMPARE) > 0) {\
__BIN_LEFT = __BIN_MID + 1;\
} else {\
__BIN_RIGHT = __BIN_MID;\
@@ -46,7 +99,7 @@
__BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\
};\
__BIN_ITEM = LIST[__BIN_MID];\
- __BIN_MID = __BIN_ITEM.##COMPARE > IN.##COMPARE ? __BIN_MID : __BIN_MID + 1;\
+ __BIN_MID = sorttext(__BIN_ITEM.##COMPARE, IN.##COMPARE) < 0 ? __BIN_MID : __BIN_MID + 1;\
LIST.Insert(__BIN_MID, IN);\
}
@@ -72,27 +125,11 @@
return "[output][and_text][input[index]]"
-/// Returns list element or null. Should prevent "index out of bounds" error.
-/proc/listgetindex(list/L, index)
- if(LAZYLEN(L))
- if(isnum_safe(index) && ISINTEGER(index))
- if(ISINRANGE(index,1,L.len))
- return L[index]
- else if(index in L)
- return L[index]
- return
-
/// Return either pick(list) or null if list is not of type /list or is empty
/proc/safepick(list/L)
if(LAZYLEN(L))
return pick(L)
-/// Checks if the list is empty
-/proc/isemptylist(list/L)
- if(!L.len)
- return TRUE
- return FALSE
-
/// Checks for specific types in a list
/proc/is_type_in_list(atom/A, list/L)
if(!LAZYLEN(L) || !A)
@@ -105,30 +142,11 @@
/// Checks for specific types in specifically structured (Assoc "type" = TRUE) lists ('typecaches')
#define is_type_in_typecache(A, L) (A && length(L) && L[(ispath(A) ? A : A:type)])
-/// Checks for a string in a list
-/proc/is_string_in_list(string, list/L)
- if(!LAZYLEN(L) || !string)
- return
- for(var/V in L)
- if(string == V)
- return TRUE
- return
-
-/// Removes a string from a list
-/proc/remove_strings_from_list(string, list/L)
- if(!LAZYLEN(L) || !string)
- return
- for(var/V in L)
- if(V == string)
- L -= V //No return here so that it removes all strings of that type
- return
-
/// returns a new list with only atoms that are in typecache L
/proc/typecache_filter_list(list/atoms, list/typecache)
RETURN_TYPE(/list)
. = list()
- for(var/thing in atoms)
- var/atom/A = thing
+ for(var/atom/A as() in atoms)
if (typecache[A.type])
. += A
@@ -136,16 +154,14 @@
/proc/typecache_filter_list_reverse(list/atoms, list/typecache)
RETURN_TYPE(/list)
. = list()
- for(var/thing in atoms)
- var/atom/A = thing
+ for(var/atom/A as() in atoms)
if(!typecache[A.type])
. += A
/// returns a new list with only atoms that are in typecache typecache_include but NOT in typecache_exclude
/proc/typecache_filter_multi_list_exclusion(list/atoms, list/typecache_include, list/typecache_exclude)
. = list()
- for(var/thing in atoms)
- var/atom/A = thing
+ for(var/atom/A as() in atoms)
if(typecache_include[A.type] && !typecache_exclude[A.type])
. += A
@@ -177,12 +193,6 @@
L[T] = TRUE
return L
-/// Empties the list by setting the length to 0. Hopefully the elements get garbage collected
-/proc/clearlist(list/list)
- if(istype(list))
- list.len = 0
- return
-
/// Removes any null entries from the list. Returns TRUE if the list had nulls, FALSE otherwise
/proc/listclearnulls(list/L)
var/start_len = L.len
@@ -236,7 +246,7 @@
L[item] = 1
total += L[item]
- total = rand(1, total)
+ total *= rand()
for (item in L)
total -=L [item]
if (total <= 0)
@@ -252,7 +262,7 @@
L[item] = 0
total += L[item]
- total = rand(0, total)
+ total *= rand()
for (item in L)
total -=L [item]
if (total <= 0 && L[item])
@@ -560,11 +570,6 @@
used_key_list[input_key] = 1
return input_key
-#if DM_VERSION > 513
-#error Remie said that lummox was adding a way to get a lists
-#error contents via list.values, if that is true remove this
-#error otherwise, update the version and bug lummox
-#endif
/// Flattens a keyed list into a list of it's contents
/proc/flatten_list(list/key_list)
if(!islist(key_list))
diff --git a/code/__HELPERS/_logging.dm b/code/__HELPERS/_logging.dm
index 8dfefa51ae40f..b3120c853298f 100644
--- a/code/__HELPERS/_logging.dm
+++ b/code/__HELPERS/_logging.dm
@@ -1,9 +1,11 @@
//wrapper macros for easier grepping
#define DIRECT_OUTPUT(A, B) A << B
+#define DIRECT_INPUT(A, B) A >> B
#define SEND_IMAGE(target, image) DIRECT_OUTPUT(target, image)
#define SEND_SOUND(target, sound) DIRECT_OUTPUT(target, sound)
#define SEND_TEXT(target, text) DIRECT_OUTPUT(target, text)
#define WRITE_FILE(file, text) DIRECT_OUTPUT(file, text)
+#define READ_FILE(file, text) DIRECT_INPUT(file, text)
//This is an external call, "true" and "false" are how rust parses out booleans
#define WRITE_LOG(log, text) rustg_log_write(log, text, "true")
#define WRITE_LOG_NO_FORMAT(log, text) rustg_log_write(log, text, "false")
@@ -27,12 +29,17 @@
#define testing(msg)
#endif
-#ifdef UNIT_TESTS
+#if defined(UNIT_TESTS) || defined(SPACEMAN_DMM)
/proc/log_test(text)
WRITE_LOG(GLOB.test_log, text)
SEND_TEXT(world.log, text)
#endif
+#ifdef REFERENCE_TRACKING_LOG
+#define log_reftracker(msg) log_world("## REF SEARCH [msg]")
+#else
+#define log_reftracker(msg)
+#endif
/* Items with ADMINPRIVATE prefixed are stripped from public logs. */
/proc/log_admin(text)
@@ -65,15 +72,15 @@
WRITE_LOG(GLOB.world_objective_log, "OBJ: [key_name(whom)] was assigned the following objective [admin_involved ? "by [key_name(admin_involved)]" : "automatically"]: [objective]")
/proc/log_mecha(text)
- if (CONFIG_GET(flag/log_mecha))
+ if (CONFIG_GET(flag/log_mecha) && SSticker.current_state != GAME_STATE_FINISHED)
WRITE_LOG(GLOB.world_mecha_log, "MECHA: [text]")
/proc/log_virus(text)
- if (CONFIG_GET(flag/log_virus))
+ if (CONFIG_GET(flag/log_virus) && SSticker.current_state != GAME_STATE_FINISHED)
WRITE_LOG(GLOB.world_virus_log, "VIRUS: [text]")
/proc/log_cloning(text, mob/initiator)
- if(CONFIG_GET(flag/log_cloning))
+ if(CONFIG_GET(flag/log_cloning) && SSticker.current_state != GAME_STATE_FINISHED)
WRITE_LOG(GLOB.world_cloning_log, "CLONING: [text]")
/proc/log_id(text)
@@ -95,7 +102,7 @@
WRITE_LOG(GLOB.world_game_log, "LAW: [text]")
/proc/log_attack(text)
- if (CONFIG_GET(flag/log_attack))
+ if (CONFIG_GET(flag/log_attack) && SSticker.current_state != GAME_STATE_FINISHED)
WRITE_LOG(GLOB.world_attack_log, "ATTACK: [text]")
/proc/log_manifest(ckey, datum/mind/mind,mob/body, latejoin = FALSE)
@@ -103,6 +110,9 @@
WRITE_LOG(GLOB.world_manifest_log, "[ckey] \\ [body.real_name] \\ [mind.assigned_role] \\ [mind.special_role ? mind.special_role : "NONE"] \\ [latejoin ? "LATEJOIN":"ROUNDSTART"]")
/proc/log_bomber(atom/user, details, atom/bomb, additional_details, message_admins = TRUE)
+ if(SSticker.current_state == GAME_STATE_FINISHED)
+ return
+
var/bomb_message = "[details][bomb ? " [bomb.name] at [AREACOORD(bomb)]": ""][additional_details ? " [additional_details]" : ""]."
if(user)
@@ -116,6 +126,7 @@
if(message_admins)
message_admins("[user ? "[ADMIN_LOOKUPFLW(user)] at [ADMIN_VERBOSEJMP(user)] " : ""][details][bomb ? " [bomb.name] at [ADMIN_VERBOSEJMP(bomb)]": ""][additional_details ? " [additional_details]" : ""].")
+
/proc/log_say(text)
if (CONFIG_GET(flag/log_say))
WRITE_LOG(GLOB.world_game_log, "SAY: [text]")
@@ -201,6 +212,10 @@
/proc/log_mapping(text)
WRITE_LOG(GLOB.world_map_error_log, text)
+/proc/log_perf(list/perf_info)
+ . = "[perf_info.Join(",")]\n"
+ WRITE_LOG_NO_FORMAT(GLOB.perf_log, .)
+
/* ui logging */
/proc/log_tgui(user_or_client, text)
var/entry = ""
diff --git a/code/__HELPERS/areas.dm b/code/__HELPERS/areas.dm
index 38caa661dc550..904da53f29634 100644
--- a/code/__HELPERS/areas.dm
+++ b/code/__HELPERS/areas.dm
@@ -44,13 +44,13 @@ GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(/area/engine/eng
/turf/open/space,
/area/shuttle,
))
-
+
if(creator)
if(creator.create_area_cooldown >= world.time)
to_chat(creator, "You're trying to create a new area a little too fast.")
return
creator.create_area_cooldown = world.time + 10
-
+
// Ignore these areas and dont let people expand them. They can expand into them though
var/static/blacklisted_areas = typecacheof(list(
/area/space,
@@ -67,7 +67,7 @@ GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(/area/engine/eng
var/area/place = get_area(turfs[i])
if(blacklisted_areas[place.type])
continue
- if(!place.requires_power || place.noteleport || place.hidden)
+ if(!place.requires_power || place.teleport_restriction || place.area_flags & HIDDEN_AREA)
continue // No expanding powerless rooms etc
areas[place.name] = place
var/area_choice = input(creator, "Choose an area to expand or make a new area.", "Area Expansion") as null|anything in areas
diff --git a/code/__HELPERS/cmp.dm b/code/__HELPERS/cmp.dm
index 2ca3b2df4e14e..0f82f2841e475 100644
--- a/code/__HELPERS/cmp.dm
+++ b/code/__HELPERS/cmp.dm
@@ -22,7 +22,7 @@
/proc/cmp_keybinding_dsc(datum/keybinding/a, datum/keybinding/b)
return cmp_numeric_dsc(a.weight, b.weight)
-// Datum cmp with vars is always slower than a specialist cmp proc, use your judgement.
+// Datum cmp with vars is always slower than a specialist cmp proc, use your judgment.
/proc/cmp_datum_numeric_asc(datum/a, datum/b, variable)
return cmp_numeric_asc(a.vars[variable], b.vars[variable])
@@ -63,9 +63,6 @@ GLOBAL_VAR_INIT(cmp_field, "name")
/proc/cmp_timer(datum/timedevent/a, datum/timedevent/b)
return a.timeToRun - b.timeToRun
-/proc/cmp_clientcolour_priority(datum/client_colour/A, datum/client_colour/B)
- return B.priority - A.priority
-
/proc/cmp_ruincost_priority(datum/map_template/ruin/A, datum/map_template/ruin/B)
return initial(A.cost) - initial(B.cost)
@@ -99,7 +96,7 @@ GLOBAL_VAR_INIT(cmp_field, "name")
return A.layer - B.layer
/proc/cmp_advdisease_resistance_asc(datum/disease/advance/A, datum/disease/advance/B)
- return A.totalResistance() - B.totalResistance()
+ return A.resistance - B.resistance
/proc/cmp_quirk_asc(datum/quirk/A, datum/quirk/B)
var/a_sign = num2sign(initial(A.value) * -1)
diff --git a/code/__HELPERS/filters.dm b/code/__HELPERS/filters.dm
new file mode 100644
index 0000000000000..7be7ca5d73251
--- /dev/null
+++ b/code/__HELPERS/filters.dm
@@ -0,0 +1,319 @@
+#define ICON_NOT_SET "Not Set"
+
+//This is stored as a nested list instead of datums or whatever because it json encodes nicely for usage in tgui
+GLOBAL_LIST_INIT(master_filter_info, list(
+ "alpha" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0,
+ "icon" = ICON_NOT_SET,
+ "render_source" = "",
+ "flags" = 0
+ ),
+ "flags" = list(
+ "MASK_INVERSE" = MASK_INVERSE,
+ "MASK_SWAP" = MASK_SWAP
+ )
+ ),
+ "angular_blur" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0,
+ "size" = 1
+ )
+ ),
+ /* Not supported because making a proper matrix editor on the frontend would be a huge dick pain.
+ Uncomment if you ever implement it
+ "color" = list(
+ "defaults" = list(
+ "color" = matrix(),
+ "space" = FILTER_COLOR_RGB
+ )
+ ),
+ */
+ "displace" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0,
+ "size" = null,
+ "icon" = ICON_NOT_SET,
+ "render_source" = ""
+ )
+ ),
+ "drop_shadow" = list(
+ "defaults" = list(
+ "x" = 1,
+ "y" = -1,
+ "size" = 1,
+ "offset" = 0,
+ "color" = COLOR_HALF_TRANSPARENT_BLACK
+ )
+ ),
+ "blur" = list(
+ "defaults" = list(
+ "size" = 1
+ )
+ ),
+ "layer" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0,
+ "icon" = ICON_NOT_SET,
+ "render_source" = "",
+ "flags" = FILTER_OVERLAY,
+ "color" = "",
+ "transform" = null,
+ "blend_mode" = BLEND_DEFAULT
+ )
+ ),
+ "motion_blur" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0
+ )
+ ),
+ "outline" = list(
+ "defaults" = list(
+ "size" = 0,
+ "color" = COLOR_BLACK,
+ "flags" = NONE
+ ),
+ "flags" = list(
+ "OUTLINE_SHARP" = OUTLINE_SHARP,
+ "OUTLINE_SQUARE" = OUTLINE_SQUARE
+ )
+ ),
+ "radial_blur" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0,
+ "size" = 0.01
+ )
+ ),
+ "rays" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0,
+ "size" = 16,
+ "color" = COLOR_WHITE,
+ "offset" = 0,
+ "density" = 10,
+ "threshold" = 0.5,
+ "factor" = 0,
+ "flags" = FILTER_OVERLAY | FILTER_UNDERLAY
+ ),
+ "flags" = list(
+ "FILTER_OVERLAY" = FILTER_OVERLAY,
+ "FILTER_UNDERLAY" = FILTER_UNDERLAY
+ )
+ ),
+ "ripple" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0,
+ "size" = 1,
+ "repeat" = 2,
+ "radius" = 0,
+ "falloff" = 1,
+ "flags" = NONE
+ ),
+ "flags" = list(
+ "WAVE_BOUNDED" = WAVE_BOUNDED
+ )
+ ),
+ "wave" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0,
+ "size" = 1,
+ "offset" = 0,
+ "flags" = NONE
+ ),
+ "flags" = list(
+ "WAVE_SIDEWAYS" = WAVE_SIDEWAYS,
+ "WAVE_BOUNDED" = WAVE_BOUNDED
+ )
+ )
+))
+
+#undef ICON_NOT_SET
+
+//Helpers to generate lists for filter helpers
+//This is the only practical way of writing these that actually produces sane lists
+/proc/alpha_mask_filter(x, y, icon/icon, render_source, flags)
+ . = list("type" = "alpha")
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+ if(!isnull(icon))
+ .["icon"] = icon
+ if(!isnull(render_source))
+ .["render_source"] = render_source
+ if(!isnull(flags))
+ .["flags"] = flags
+
+/proc/angular_blur_filter(x, y, size)
+ . = list("type" = "angular_blur")
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+ if(!isnull(size))
+ .["size"] = size
+
+/proc/color_matrix_filter(matrix/in_matrix, space)
+ . = list("type" = "color")
+ .["color"] = in_matrix
+ if(!isnull(space))
+ .["space"] = space
+
+/proc/displacement_map_filter(icon, render_source, x, y, size = 32)
+ . = list("type" = "displace")
+ if(!isnull(icon))
+ .["icon"] = icon
+ if(!isnull(render_source))
+ .["render_source"] = render_source
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+ if(!isnull(size))
+ .["size"] = size
+
+/proc/drop_shadow_filter(x, y, size, offset, color)
+ . = list("type" = "drop_shadow")
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+ if(!isnull(size))
+ .["size"] = size
+ if(!isnull(offset))
+ .["offset"] = offset
+ if(!isnull(color))
+ .["color"] = color
+
+/proc/gauss_blur_filter(size)
+ . = list("type" = "blur")
+ if(!isnull(size))
+ .["size"] = size
+
+/proc/layering_filter(icon, render_source, x, y, flags, color, transform, blend_mode)
+ . = list("type" = "layer")
+ if(!isnull(icon))
+ .["icon"] = icon
+ if(!isnull(render_source))
+ .["render_source"] = render_source
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+ if(!isnull(color))
+ .["color"] = color
+ if(!isnull(flags))
+ .["flags"] = flags
+ if(!isnull(transform))
+ .["transform"] = transform
+ if(!isnull(blend_mode))
+ .["blend_mode"] = blend_mode
+
+/proc/motion_blur_filter(x, y)
+ . = list("type" = "motion_blur")
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+
+/proc/outline_filter(size, color, flags)
+ . = list("type" = "outline")
+ if(!isnull(size))
+ .["size"] = size
+ if(!isnull(color))
+ .["color"] = color
+ if(!isnull(flags))
+ .["flags"] = flags
+
+/proc/radial_blur_filter(size, x, y)
+ . = list("type" = "radial_blur")
+ if(!isnull(size))
+ .["size"] = size
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+
+/proc/rays_filter(size, color, offset, density, threshold, factor, x, y, flags)
+ . = list("type" = "rays")
+ if(!isnull(size))
+ .["size"] = size
+ if(!isnull(color))
+ .["color"] = color
+ if(!isnull(offset))
+ .["offset"] = offset
+ if(!isnull(density))
+ .["density"] = density
+ if(!isnull(threshold))
+ .["threshold"] = threshold
+ if(!isnull(factor))
+ .["factor"] = factor
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+ if(!isnull(flags))
+ .["flags"] = flags
+
+/proc/ripple_filter(radius, size, falloff, repeat, x, y, flags)
+ . = list("type" = "ripple")
+ if(!isnull(radius))
+ .["radius"] = radius
+ if(!isnull(size))
+ .["size"] = size
+ if(!isnull(falloff))
+ .["falloff"] = falloff
+ if(!isnull(repeat))
+ .["repeat"] = repeat
+ if(!isnull(flags))
+ .["flags"] = flags
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+
+/proc/wave_filter(x, y, size, offset, flags)
+ . = list("type" = "wave")
+ if(!isnull(size))
+ .["size"] = size
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+ if(!isnull(offset))
+ .["offset"] = offset
+ if(!isnull(flags))
+ .["flags"] = flags
+
+/proc/apply_wibbly_filters(atom/in_atom, length)
+ for(var/i in 1 to 7)
+ //This is a very baffling and strange way of doing this but I am just preserving old functionality
+ var/X
+ var/Y
+ var/rsq
+ do
+ X = 60*rand() - 30
+ Y = 60*rand() - 30
+ rsq = X*X + Y*Y
+ while(rsq<100 || rsq>900) // Yeah let's just loop infinitely due to bad luck what's the worst that could happen?
+ var/random_roll = rand()
+ in_atom.add_filter("wibbly-[i]", 5, wave_filter(x = X, y = Y, size = rand() * 2.5 + 0.5, offset = random_roll))
+ var/filter = in_atom.get_filter("wibbly-[i]")
+ animate(filter, offset = random_roll, time = 0, loop = -1, flags = ANIMATION_PARALLEL)
+ animate(offset = random_roll - 1, time = rand() * 20 + 10)
+
+/proc/remove_wibbly_filters(atom/in_atom)
+ var/filter
+ for(var/i in 1 to 7)
+ filter = in_atom.get_filter("wibbly-[i]")
+ animate(filter)
+ in_atom.remove_filter("wibbly-[i]")
diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm
index e8e2508903da4..d0e65f7d6e1b6 100644
--- a/code/__HELPERS/game.dm
+++ b/code/__HELPERS/game.dm
@@ -5,16 +5,15 @@
locate(min(CENTER.x+(RADIUS),world.maxx), min(CENTER.y+(RADIUS),world.maxy), CENTER.z) \
)
+#define RANGE_TURFS_XY(XRADIUS, YRADIUS, CENTER) \
+ block( \
+ locate(max(CENTER.x-(XRADIUS),1), max(CENTER.y-(YRADIUS),1), CENTER.z), \
+ locate(min(CENTER.x+(XRADIUS),world.maxx), min(CENTER.y+(YRADIUS),world.maxy), CENTER.z) \
+ )
+
#define Z_TURFS(ZLEVEL) block(locate(1,1,ZLEVEL), locate(world.maxx, world.maxy, ZLEVEL))
#define CULT_POLL_WAIT 2400
-/proc/get_area(atom/A)
- RETURN_TYPE(/area)
- if(isarea(A))
- return A
- var/turf/T = get_turf(A)
- return T ? T.loc : null
-
/proc/get_area_name(atom/X, format_text = FALSE)
var/area/A = isarea(X) ? X : get_area(X)
if(!A)
@@ -28,10 +27,8 @@
if(!center)
return list()
- var/list/turfs = RANGE_TURFS(dist, center)
var/list/areas = list()
- for(var/V in turfs)
- var/turf/T = V
+ for(var/turf/T as() in RANGE_TURFS(dist, center))
areas |= T.loc
return areas
@@ -115,7 +112,7 @@
var/list/turfs = new/list()
var/rsq = radius * (radius+0.5)
- for(var/atom/T in range(radius, centerturf))
+ for(var/atom/T as() in range(radius, centerturf))
var/dx = T.x - centerturf.x
var/dy = T.y - centerturf.y
if(dx*dx + dy*dy <= rsq)
@@ -130,7 +127,7 @@
var/list/atoms = new/list()
var/rsq = radius * (radius+0.5)
- for(var/atom/A in view(radius, centerturf))
+ for(var/atom/A as() in view(radius, centerturf))
var/dx = A.x - centerturf.x
var/dy = A.y - centerturf.y
if(dx*dx + dy*dy <= rsq)
@@ -153,7 +150,7 @@
var/list/turfs = new/list()
var/rsq = radius * (radius+0.5)
- for(var/turf/T in range(radius, centerturf))
+ for(var/turf/T as() in RANGE_TURFS(radius, centerturf))
var/dx = T.x - centerturf.x
var/dy = T.y - centerturf.y
if(dx*dx + dy*dy <= rsq)
@@ -173,20 +170,6 @@
turfs += T
return turfs
-
-//This is the new version of recursive_mob_check, used for say().
-//The other proc was left intact because morgue trays use it.
-//Sped this up again for real this time
-/proc/recursive_hear_check(O)
- var/list/processing_list = list(O)
- . = list()
- while(processing_list.len)
- var/atom/A = processing_list[1]
- if(A.flags_1 & HEAR_1)
- . += A
- processing_list.Cut(1, 2)
- processing_list += A.contents
-
/** recursive_organ_check
* inputs: O (object to start with)
* outputs:
@@ -224,7 +207,7 @@
return
-// Better recursive loop, technically sort of not actually recursive cause that shit is retarded, enjoy.
+// Better recursive loop, technically sort of not actually recursive cause that shit is stupid, enjoy.
//No need for a recursive limit either
/proc/recursive_mob_check(atom/O,client_check=1,sight_check=1,include_radio=1)
@@ -265,47 +248,31 @@
return found_mobs
-
-/proc/get_hearers_in_view(R, atom/source)
- // Returns a list of hearers in view(R) from source (ignoring luminosity). Used in saycode.
- var/turf/T = get_turf(source)
+/// Returns a list of hearers in view(view_radius) from source (ignoring luminosity). uses important_recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE]
+/proc/get_hearers_in_view(view_radius, atom/source)
+ var/turf/center_turf = get_turf(source)
. = list()
-
- if(!T)
+ if(!center_turf)
return
-
- var/list/processing_list = list()
- if (R == 0) // if the range is zero, we know exactly where to look for, we can skip view
- processing_list += T.contents // We can shave off one iteration by assuming turfs cannot hear
- else // A variation of get_hear inlined here to take advantage of the compiler's fastpath for obj/mob in view
- var/lum = T.luminosity
- T.luminosity = 6 // This is the maximum luminosity
- for(var/mob/M in view(R, T))
- processing_list += M
- for(var/obj/O in view(R, T))
- processing_list += O
- T.luminosity = lum
-
- while(processing_list.len) // recursive_hear_check inlined here
- var/atom/A = processing_list[1]
- if(A.flags_1 & HEAR_1)
- . += A
- processing_list.Cut(1, 2)
- processing_list += A.contents
+ var/lum = center_turf.luminosity
+ center_turf.luminosity = 6 // This is the maximum luminosity
+ for(var/atom/movable/movable in view(view_radius, center_turf))
+ var/list/recursive_contents = LAZYACCESS(movable.important_recursive_contents, RECURSIVE_CONTENTS_HEARING_SENSITIVE)
+ if(recursive_contents)
+ . += recursive_contents
+ center_turf.luminosity = lum
/proc/get_mobs_in_radio_ranges(list/obj/item/radio/radios)
. = list()
// Returns a list of mobs who can hear any of the radios given in @radios
for(var/obj/item/radio/R in radios)
- if(R)
- if(R.canhear_range != -1)
- . |= get_hearers_in_view(R.canhear_range, R)
- else
- if(istype(R.loc, /obj/item/implant))
- var/obj/item/implant/I = R.loc
- if(I.imp_in)
- . |= I.imp_in
-
+ if(R.canhear_range != -1)
+ . |= get_hearers_in_view(R.canhear_range, R)
+ else
+ if(istype(R.loc, /obj/item/implant))
+ var/obj/item/implant/I = R.loc
+ if(I.imp_in)
+ . |= I.imp_in
#define SIGNV(X) ((X<0)?-1:1)
@@ -377,11 +344,10 @@
if(AM.Move(get_step(T, direction)))
break
-/proc/get_mob_by_key(key)
- var/ckey = ckey(key)
- for(var/i in GLOB.player_list)
- var/mob/M = i
- if(M.ckey == ckey)
+/proc/get_mob_by_ckey(key)
+ var/ckey = ckey(key) //just to be safe
+ for(var/mob/M as() in GLOB.player_list)
+ if(M?.ckey == ckey)
return M
return null
@@ -401,13 +367,17 @@
/proc/ScreenText(obj/O, maptext="", screen_loc="CENTER-7,CENTER-7", maptext_height=480, maptext_width=480)
if(!isobj(O))
- O = new /obj/screen/text()
- O.maptext = maptext
+ O = new /atom/movable/screen/text()
+ O.maptext = MAPTEXT(maptext)
O.maptext_height = maptext_height
O.maptext_width = maptext_width
O.screen_loc = screen_loc
return O
+/// Removes an image from a client's `.images`. Useful as a callback.
+/proc/remove_image_from_client(image/image, client/remove_from)
+ remove_from?.images -= image
+
/proc/remove_images_from_clients(image/I, list/show_to)
for(var/client/C in show_to)
C.images -= I
@@ -419,8 +389,7 @@
/proc/flick_overlay_view(image/I, atom/target, duration) //wrapper for the above, flicks to everyone who can see the target atom
var/list/viewing = list()
- for(var/m in viewers(target))
- var/mob/M = m
+ for(var/mob/M as() in viewers(target))
if(M.client)
viewing += M.client
flick_overlay(I, viewing, duration)
@@ -476,6 +445,8 @@
/proc/pollGhostCandidates(Question, jobbanType, datum/game_mode/gametypeCheck, be_special_flag = 0, poll_time = 300, ignore_category = null, flashwindow = TRUE)
var/list/candidates = list()
+ if(!(GLOB.ghost_role_flags & GHOSTROLE_STATION_SENTIENCE))
+ return candidates
for(var/mob/dead/observer/G in GLOB.player_list)
candidates += G
@@ -610,10 +581,9 @@
// Find a obstruction free turf that's within the range of the center. Can also condition on if it is of a certain area type.
/proc/find_obstruction_free_location(var/range, var/atom/center, var/area/specific_area)
- var/list/turfs = RANGE_TURFS(range, center)
var/list/possible_loc = list()
- for(var/turf/found_turf in turfs)
+ for(var/turf/found_turf as() in RANGE_TURFS(range, center))
var/area/turf_area = get_area(found_turf)
// We check if both the turf is a floor, and that it's actually in the area.
diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm
index 870f70cfa7733..4c6a4227c5724 100644
--- a/code/__HELPERS/global_lists.dm
+++ b/code/__HELPERS/global_lists.dm
@@ -47,12 +47,6 @@
GLOB.surgeries_list += new path()
sortList(GLOB.surgeries_list)
- //Materials
- for(var/path in subtypesof(/datum/material))
- var/datum/material/D = new path()
- GLOB.materials_list[D.id] = D
- sortList(GLOB.materials_list)
-
GLOB.emote_list = init_emote_list()
diff --git a/code/__HELPERS/heap.dm b/code/__HELPERS/heap.dm
index 3ed3af80f15a3..c860b2013ca78 100644
--- a/code/__HELPERS/heap.dm
+++ b/code/__HELPERS/heap.dm
@@ -1,39 +1,44 @@
+//////////////////////
+//datum/heap object
+//////////////////////
-//-----------------//
-//datum/Heap object//
-//-----------------//
-
-/datum/Heap
+/datum/heap
var/list/L
var/cmp
-/datum/Heap/New(compare)
+/datum/heap/New(compare)
L = new()
cmp = compare
-/datum/Heap/proc/IsEmpty()
- return !L.len
+/datum/heap/Destroy(force, ...)
+ for(var/i in L) // because this is before the list helpers are loaded
+ qdel(i)
+ L = null
+ return ..()
+
+/datum/heap/proc/is_empty()
+ return !length(L)
-//Insert and place at its position a new node in the heap
-/datum/Heap/proc/Insert(atom/A)
+//insert and place at its position a new node in the heap
+/datum/heap/proc/insert(atom/A)
L.Add(A)
- Swim(L.len)
+ swim(length(L))
//removes and returns the first element of the heap
//(i.e the max or the min dependant on the comparison function)
-/datum/Heap/proc/Pop()
- if(!L.len)
+/datum/heap/proc/pop()
+ if(!length(L))
return 0
. = L[1]
- L[1] = L[L.len]
- L.Cut(L.len)
- if(L.len)
- Sink(1)
+ L[1] = L[length(L)]
+ L.Cut(length(L))
+ if(length(L))
+ sink(1)
//Get a node up to its right position in the heap
-/datum/Heap/proc/Swim(var/index)
+/datum/heap/proc/swim(index)
var/parent = round(index * 0.5)
while(parent > 0 && (call(cmp)(L[index],L[parent]) > 0))
@@ -42,21 +47,21 @@
parent = round(index * 0.5)
//Get a node down to its right position in the heap
-/datum/Heap/proc/Sink(var/index)
- var/g_child = GetGreaterChild(index)
+/datum/heap/proc/sink(index)
+ var/g_child = get_greater_child(index)
while(g_child > 0 && (call(cmp)(L[index],L[g_child]) < 0))
L.Swap(index,g_child)
index = g_child
- g_child = GetGreaterChild(index)
+ g_child = get_greater_child(index)
//Returns the greater (relative to the comparison proc) of a node children
//or 0 if there's no child
-/datum/Heap/proc/GetGreaterChild(var/index)
- if(index * 2 > L.len)
+/datum/heap/proc/get_greater_child(index)
+ if(index * 2 > length(L))
return 0
- if(index * 2 + 1 > L.len)
+ if(index * 2 + 1 > length(L))
return index * 2
if(call(cmp)(L[index * 2],L[index * 2 + 1]) < 0)
@@ -65,12 +70,11 @@
return index * 2
//Replaces a given node so it verify the heap condition
-/datum/Heap/proc/ReSort(atom/A)
+/datum/heap/proc/resort(atom/A)
var/index = L.Find(A)
- Swim(index)
- Sink(index)
+ swim(index)
+ sink(index)
-/datum/Heap/proc/List()
+/datum/heap/proc/List()
. = L.Copy()
-
diff --git a/code/__HELPERS/icon_smoothing.dm b/code/__HELPERS/icon_smoothing.dm
index b11ff5ec9e0bc..64fc2dd8693a5 100644
--- a/code/__HELPERS/icon_smoothing.dm
+++ b/code/__HELPERS/icon_smoothing.dm
@@ -377,8 +377,7 @@
//SSicon_smooth
/proc/queue_smooth_neighbors(atom/A)
- for(var/V in orange(1,A))
- var/atom/T = V
+ for(var/atom/T as() in orange(1,A))
if(T.smooth)
queue_smooth(T)
diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm
index 443483b40f853..99128b159c724 100644
--- a/code/__HELPERS/icons.dm
+++ b/code/__HELPERS/icons.dm
@@ -706,6 +706,11 @@ world
((hi3 >= 65 ? hi3-55 : hi3-48)<<4) | (lo3 >= 65 ? lo3-55 : lo3-48),
((hi4 >= 65 ? hi4-55 : hi4-48)<<4) | (lo4 >= 65 ? lo4-55 : lo4-48))
+//Returns color multiplied by amount, in hex format
+/proc/MultiplyHexColor(color, amount)
+ var/list/rgb = ReadRGB(color)
+ return rgb(round(rgb[1]*amount), round(rgb[2]*amount), round(rgb[3]*amount))
+
// Creates a single icon from a given /atom or /image. Only the first argument is required.
/proc/getFlatIcon(image/A, defdir, deficon, defstate, defblend, start = TRUE, no_anim = FALSE)
//Define... defines.
@@ -908,6 +913,65 @@ world
#undef BLANK
#undef SET_SELF
+/proc/getStillIcon(atom/A, directionless = TRUE)//By whoever that guy below me is, I just kind of stole it and changed it a bit lol
+ var/icon/overlayIcon = new /icon()
+ var/isEmpty = TRUE //So the overlay icon isn't empty.
+ //==== OVERLAYS ====
+ //Do sorting :(
+ var/list/layers = list()
+
+ var/direction = directionless ? SOUTH : A.dir
+
+ // Loop through the underlays, then overlays, sorting them into the layers list
+ for(var/i in 1 to A.overlays.len)
+ var/image/current = A.overlays[i]
+ if(!current)
+ continue
+ if(current.plane != FLOAT_PLANE && current.plane != A.plane)
+ continue
+ var/current_layer = current.layer
+ if(current_layer < 0)
+ current_layer = A.layer + current_layer / 1000
+
+ for(var/p in 1 to layers.len)
+ var/image/cmp = layers[p]
+ if(current_layer < layers[cmp])
+ layers.Insert(p, current)
+ break
+ layers[current] = current_layer
+ CHECK_TICK
+ //Apply sorted
+ for(var/V in layers)//For every image in overlays. var/image/I will not work, don't try it.
+ var/image/I = V
+ var/icon/image_overlay = new(I.icon,I.icon_state,direction)//Blend only works with icon objects.
+ //Make sure the overlay actually exists and is valid
+ if(!(I.icon_state in icon_states(I.icon)))
+ continue
+ //Colour
+ if(I.color)
+ if(islist(I.color))
+ image_overlay.MapColors(arglist(I.color))
+ else
+ image_overlay.Blend(I.color, ICON_MULTIPLY)
+ //Clean up repeated frames
+ var/icon/cleaned = new /icon()
+ cleaned.Insert(image_overlay, "", SOUTH, 1, 0)
+ //Also, icons cannot directly set icon_state. Slower than changing variables but whatever.
+ if(isEmpty)
+ overlayIcon.Insert(cleaned, "", SOUTH, 1, FALSE)
+ isEmpty = FALSE
+ else
+ overlayIcon.Blend(cleaned, ICON_OVERLAY)//OR so they are lumped together in a nice overlay.
+ CHECK_TICK
+ //==== PUTTING IT ALL TOGETHER ====
+ var/icon/default = new(A.icon, A.icon_state, direction)//So we want the default icon and icon state of A.
+ //Blend the 2 icons
+ default.Blend(overlayIcon, ICON_OVERLAY)
+ //Boom put it all together
+ var/icon/cleaned = new /icon()
+ cleaned.Insert(default, "", SOUTH, 1, FALSE) //Clean out animation states.
+ return cleaned//And now return the mask.
+
/proc/getIconMask(atom/A)//By yours truly. Creates a dynamic mask for a mob/whatever. /N
var/icon/alpha_mask = new(A.icon,A.icon_state)//So we want the default icon and icon state of A.
for(var/V in A.overlays)//For every image in overlays. var/image/I will not work, don't try it.
@@ -983,7 +1047,7 @@ world
letter = lowertext(letter)
var/image/text_image = new(loc = A)
- text_image.maptext = "[letter]"
+ text_image.maptext = MAPTEXT("[letter]")
text_image.pixel_x = 7
text_image.pixel_y = 5
qdel(atom_icon)
diff --git a/code/__HELPERS/level_traits.dm b/code/__HELPERS/level_traits.dm
index 8039953d9ccb6..84992a7a85309 100644
--- a/code/__HELPERS/level_traits.dm
+++ b/code/__HELPERS/level_traits.dm
@@ -9,6 +9,8 @@
#define is_reebe(z) SSmapping.level_trait(z, ZTRAIT_REEBE)
+#define is_dynamic_level(z) SSmapping.level_trait(z, ZTRAIT_DYNAMIC_LEVEL)
+
#define is_reserved_level(z) SSmapping.level_trait(z, ZTRAIT_RESERVED)
#define is_away_level(z) SSmapping.level_trait(z, ZTRAIT_AWAY)
diff --git a/code/__HELPERS/matrices.dm b/code/__HELPERS/matrices.dm
index 044b4dba079dd..701964e0fbc16 100644
--- a/code/__HELPERS/matrices.dm
+++ b/code/__HELPERS/matrices.dm
@@ -176,3 +176,40 @@ round(cos_inv_third+sqrt3_sin, 0.001), round(cos_inv_third-sqrt3_sin, 0.001), ro
for(x in 1 to 4)
output[offset+x] = round(A[offset+1]*B[x] + A[offset+2]*B[x+4] + A[offset+3]*B[x+8] + A[offset+4]*B[x+12]+(y==5?B[x+16]:0), 0.001)
return output
+
+///Converts RGB shorthands into RGBA matrices complete of constants rows (ergo a 20 keys list in byond).
+/proc/color_to_full_rgba_matrix(color)
+ if(istext(color))
+ var/list/L = ReadRGB(color)
+ if(!L)
+ CRASH("Invalid/unsupported color format argument in color_to_full_rgba_matrix()")
+ return list(L[1]/255,0,0,0, 0,L[2]/255,0,0, 0,0,L[3]/255,0, 0,0,0,L.len>3?L[4]/255:1, 0,0,0,0)
+ else if(!islist(color)) //invalid format
+ return color_matrix_identity()
+ var/list/L = color
+ switch(L.len)
+ if(3 to 5) // row-by-row hexadecimals
+ . = list()
+ for(var/a in 1 to L.len)
+ var/list/rgb = ReadRGB(L[a])
+ for(var/b in rgb)
+ . += b/255
+ if(length(rgb) % 4) // RGB has no alpha instruction
+ . += a != 4 ? 0 : 1
+ if(L.len < 4) //missing both alphas and constants rows
+ . += list(0,0,0,1, 0,0,0,0)
+ else if(L.len < 5) //missing constants row
+ . += list(0,0,0,0)
+ if(9 to 12) //RGB
+ . = list(L[1],L[2],L[3],0, L[4],L[5],L[6],0, L[7],L[8],L[9],0, 0,0,0,1)
+ for(var/b in 1 to 3) //missing constants row
+ . += L.len < 9+b ? 0 : L[9+b]
+ . += 0
+ if(16 to 20) // RGBA
+ . = L.Copy()
+ if(L.len < 20) //missing constants row
+ for(var/b in 1 to 20-L.len)
+ . += 0
+ else
+ CRASH("Invalid/unsupported color format argument in color_to_full_rgba_matrix()")
+
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index 83893ff131d4f..077b5eb6b23c8 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -152,6 +152,14 @@
if(!findname(.))
break
+/proc/random_unique_ooze_name(attempts_to_find_unique_name=10)
+ for(var/i in 1 to attempts_to_find_unique_name)
+ . = capitalize(pick(GLOB.oozeling_first_names)) + " " + capitalize(pick(GLOB.oozeling_last_names))
+
+ if(!findname(.))
+ break
+
+
/proc/random_skin_tone()
return pick(GLOB.skin_tones)
@@ -256,6 +264,10 @@ GLOBAL_LIST_EMPTY(species_list)
if(target && !isturf(target))
Tloc = target.loc
+ if(target)
+ LAZYADD(user.do_afters, target)
+ LAZYADD(target.targeted_by, user)
+
var/atom/Uloc = user.loc
var/drifting = 0
@@ -268,7 +280,7 @@ GLOBAL_LIST_EMPTY(species_list)
if(holding)
holdingnull = 0 //Users hand started holding something, check to see if it's still holding that
- delay *= user.do_after_coefficent()
+ delay *= user.cached_multiplicative_actions_slowdown
var/datum/progressbar/progbar
if (progress)
@@ -301,6 +313,10 @@ GLOBAL_LIST_EMPTY(species_list)
. = 0
break
+ if(target && !(target in user.do_afters))
+ . = 0
+ break
+
if(needhand)
//This might seem like an odd check, but you can still need a hand even when it's empty
//i.e the hand is used to pull some item/tool out of the construction
@@ -313,10 +329,11 @@ GLOBAL_LIST_EMPTY(species_list)
break
if (progress)
qdel(progbar)
+
+ if(!QDELETED(target))
+ LAZYREMOVE(user.do_afters, target)
+ LAZYREMOVE(target.targeted_by, user)
-/mob/proc/do_after_coefficent() // This gets added to the delay on a do_after, default 1
- . = 1
- return
/proc/do_after_mob(mob/user, list/targets, time = 30, uninterruptible = 0, progress = 1, datum/callback/extra_checks, required_mobility_flags = MOBILITY_STAND)
if(!user || !targets)
@@ -325,7 +342,9 @@ GLOBAL_LIST_EMPTY(species_list)
targets = list(targets)
var/user_loc = user.loc
- var/drifting = 0
+ time *= user.cached_multiplicative_actions_slowdown
+
+ var/drifting = FALSE
if(!user.Process_Spacemove(0) && user.inertia_dir)
drifting = 1
@@ -543,3 +562,7 @@ GLOBAL_LIST_EMPTY(species_list)
for(var/mob/living/carbon/human/player in GLOB.mob_living_list)
if(player.stat != DEAD && player.mind && is_station_level(player.z))
. |= player.mind
+
+/// Gets the client of the mob, allowing for mocking of the client.
+/// You only need to use this if you know you're going to be mocking clients somewhere else.
+#define GET_CLIENT(mob) (##mob.client || ##mob.mock_client)
diff --git a/code/__HELPERS/mouse_control.dm b/code/__HELPERS/mouse_control.dm
index 784496ed200c5..e7e1b34433742 100644
--- a/code/__HELPERS/mouse_control.dm
+++ b/code/__HELPERS/mouse_control.dm
@@ -1,5 +1,5 @@
-/proc/mouse_angle_from_client(client/client)
- var/list/mouse_control = params2list(client.mouseParams)
+/proc/mouse_angle_from_client(client/client, mouseParams)
+ var/list/mouse_control = params2list(mouseParams)
if(mouse_control["screen-loc"] && client)
var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",")
var/list/screen_loc_X = splittext(screen_loc_params[1],":")
@@ -15,10 +15,10 @@
return angle
//Wow, specific name!
-/proc/mouse_absolute_datum_map_position_from_client(client/client)
+/proc/mouse_absolute_datum_map_position_from_client(client/client, params)
if(!isloc(client.mob.loc))
return
- var/list/mouse_control = params2list(client.mouseParams)
+ var/list/mouse_control = params2list(params)
var/atom/A = client.eye
var/turf/T = get_turf(A)
var/cx = T.x
diff --git a/code/__HELPERS/names.dm b/code/__HELPERS/names.dm
index 8fd4036689200..a0ce1614b7e84 100644
--- a/code/__HELPERS/names.dm
+++ b/code/__HELPERS/names.dm
@@ -25,7 +25,10 @@
/proc/moth_name()
return "[pick(GLOB.moth_first)] [pick(GLOB.moth_last)]"
-proc/squid_name(gender)
+/proc/ooze_name()
+ return "[pick(GLOB.oozeling_first_names)] [pick(GLOB.oozeling_last_names)]"
+
+/proc/squid_name(gender)
if(gender == MALE)
return "[pick(GLOB.squid_names_male)] [pick(GLOB.last_names)]"
else
@@ -69,6 +72,10 @@ GLOBAL_VAR(command_name)
else
world.name = GLOB.station_name
+ //Rename the station on the orbital charter.
+ if(SSorbits.station_instance)
+ SSorbits.station_instance.name = newname
+
/proc/new_station_name()
var/random = rand(1,5)
@@ -82,11 +89,12 @@ GLOBAL_VAR(command_name)
name = ""
// Prefix
- for(var/holiday_name in SSevents.holidays)
- if(holiday_name == "Friday the 13th")
- random = 13
+ var/holiday_name = pick(SSevents.holidays)
+ if(holiday_name)
var/datum/holiday/holiday = SSevents.holidays[holiday_name]
name = holiday.getStationPrefix()
+ if(istype(holiday, /datum/holiday/friday_thirteenth))
+ random = 13
//get normal name
if(!name)
name = pick(GLOB.station_names)
diff --git a/code/__HELPERS/path.dm b/code/__HELPERS/path.dm
new file mode 100644
index 0000000000000..7ae72c3fa233f
--- /dev/null
+++ b/code/__HELPERS/path.dm
@@ -0,0 +1,359 @@
+/**
+ * This file contains the stuff you need for using JPS (Jump Point Search) pathing, an alternative to A* that skips
+ * over large numbers of uninteresting tiles resulting in much quicker pathfinding solutions. Mind that diagonals
+ * cost the same as cardinal moves currently, so paths may look a bit strange, but should still be optimal.
+ */
+
+/**
+ * This is the proc you use whenever you want to have pathfinding more complex than "try stepping towards the thing".
+ * If no path was found, returns an empty list, which is important for bots like medibots who expect an empty list rather than nothing.
+ *
+ * Arguments:
+ * * caller: The movable atom that's trying to find the path
+ * * end: What we're trying to path to. It doesn't matter if this is a turf or some other atom, we're gonna just path to the turf it's on anyway
+ * * max_distance: The maximum number of steps we can take in a given path to search (default: 30, 0 = infinite)
+ * * mintargetdistance: Minimum distance to the target before path returns, could be used to get near a target, but not right to it - for an AI mob with a gun, for example.
+ * * id: An ID card representing what access we have and what doors we can open. Its location relative to the pathing atom is irrelevant
+ * * simulated_only: Whether we consider turfs without atmos simulation (AKA do we want to ignore space)
+ * * exclude: If we want to avoid a specific turf, like if we're a mulebot who already got blocked by some turf
+ * * skip_first: Whether or not to delete the first item in the path. This would be done because the first item is the starting tile, which can break movement for some creatures.
+ */
+/proc/get_path_to(caller, end, max_distance = 30, mintargetdist, id=null, simulated_only = TRUE, turf/exclude, skip_first=TRUE)
+ if(!caller || !get_turf(end))
+ return
+
+ var/l = SSpathfinder.mobs.getfree(caller)
+ while(!l)
+ stoplag(3)
+ l = SSpathfinder.mobs.getfree(caller)
+
+ var/list/path
+ var/datum/pathfind/pathfind_datum = new(caller, end, id, max_distance, mintargetdist, simulated_only, exclude)
+ path = pathfind_datum.search()
+ qdel(pathfind_datum)
+
+ SSpathfinder.mobs.found(l)
+ if(!path)
+ path = list()
+ if(length(path) > 0 && skip_first)
+ path.Cut(1,2)
+ return path
+
+/**
+ * A helper macro to see if it's possible to step from the first turf into the second one, minding things like door access and directional windows.
+ * Note that this can only be used inside the [datum/pathfind][pathfind datum] since it uses variables from said datum.
+ * If you really want to optimize things, optimize this, cuz this gets called a lot.
+ */
+#define CAN_STEP(cur_turf, next) (next && !next.density && cur_turf.Adjacent(next) && !(simulated_only && SSpathfinder.space_type_cache[next.type]) && !cur_turf.LinkBlockedWithAccess(next,caller, id) && (next != avoid))
+/// Another helper macro for JPS, for telling when a node has forced neighbors that need expanding
+#define STEP_NOT_HERE_BUT_THERE(cur_turf, dirA, dirB) ((!CAN_STEP(cur_turf, get_step(cur_turf, dirA)) && CAN_STEP(cur_turf, get_step(cur_turf, dirB))))
+
+/// The JPS Node datum represents a turf that we find interesting enough to add to the open list and possibly search for new tiles from
+/datum/jps_node
+ /// The turf associated with this node
+ var/turf/tile
+ /// The node we just came from
+ var/datum/jps_node/previous_node
+ /// The A* node weight (f_value = number_of_tiles + heuristic)
+ var/f_value
+ /// The A* node heuristic (a rough estimate of how far we are from the goal)
+ var/heuristic
+ /// How many steps it's taken to get here from the start (currently pulling double duty as steps taken & cost to get here, since all moves incl diagonals cost 1 rn)
+ var/number_tiles
+ /// How many steps it took to get here from the last node
+ var/jumps
+ /// Nodes store the endgoal so they can process their heuristic without a reference to the pathfind datum
+ var/turf/node_goal
+
+/datum/jps_node/New(turf/our_tile, datum/jps_node/incoming_previous_node, jumps_taken, turf/incoming_goal)
+ tile = our_tile
+ jumps = jumps_taken
+ if(incoming_goal) // if we have the goal argument, this must be the first/starting node
+ node_goal = incoming_goal
+ else if(incoming_previous_node) // if we have the parent, this is from a direct lateral/diagonal scan, we can fill it all out now
+ previous_node = incoming_previous_node
+ number_tiles = previous_node.number_tiles + jumps
+ node_goal = previous_node.node_goal
+ heuristic = get_dist(tile, node_goal)
+ f_value = number_tiles + heuristic
+ // otherwise, no parent node means this is from a subscan lateral scan, so we just need the tile for now until we call [datum/jps/proc/update_parent] on it
+
+/datum/jps_node/Destroy(force, ...)
+ previous_node = null
+ return ..()
+
+/datum/jps_node/proc/update_parent(datum/jps_node/new_parent)
+ previous_node = new_parent
+ node_goal = previous_node.node_goal
+ jumps = get_dist(tile, previous_node.tile)
+ number_tiles = previous_node.number_tiles + jumps
+ heuristic = get_dist(tile, node_goal)
+ f_value = number_tiles + heuristic
+
+/// TODO: Macro this to reduce proc overhead
+/proc/HeapPathWeightCompare(datum/jps_node/a, datum/jps_node/b)
+ return b.f_value - a.f_value
+
+/// The datum used to handle the JPS pathfinding, completely self-contained
+/datum/pathfind
+ /// The thing that we're actually trying to path for
+ var/atom/movable/caller
+ /// The turf where we started at
+ var/turf/start
+ /// The turf we're trying to path to (note that this won't track a moving target)
+ var/turf/end
+ /// The open list/stack we pop nodes out from (TODO: make this a normal list and macro-ize the heap operations to reduce proc overhead)
+ var/datum/heap/open
+ ///An assoc list that serves as the closed list & tracks what turfs came from where. Key is the turf, and the value is what turf it came from
+ var/list/sources
+ /// The list we compile at the end if successful to pass back
+ var/list/path
+
+ // general pathfinding vars/args
+ /// An ID card representing what access we have and what doors we can open. Its location relative to the pathing atom is irrelevant
+ var/obj/item/card/id/id
+ /// How far away we have to get to the end target before we can call it quits
+ var/mintargetdist = 0
+ /// I don't know what this does vs , but they limit how far we can search before giving up on a path
+ var/max_distance = 30
+ /// Space is big and empty, if this is TRUE then we ignore pathing through unsimulated tiles
+ var/simulated_only
+ /// A specific turf we're avoiding, like if a mulebot is being blocked by someone t-posing in a doorway we're trying to get through
+ var/turf/avoid
+
+/datum/pathfind/New(atom/movable/caller, atom/goal, id, max_distance, mintargetdist, simulated_only, avoid)
+ src.caller = caller
+ end = get_turf(goal)
+ open = new /datum/heap(/proc/HeapPathWeightCompare)
+ sources = new()
+ src.id = id
+ src.max_distance = max_distance
+ src.mintargetdist = mintargetdist
+ src.simulated_only = simulated_only
+ src.avoid = avoid
+
+/**
+ * search() is the proc you call to kick off and handle the actual pathfinding, and kills the pathfind datum instance when it's done.
+ *
+ * If a valid path was found, it's returned as a list. If invalid or cross-z-level params are entered, or if there's no valid path found, we
+ * return null, which [/proc/get_path_to] translates to an empty list (notable for simple bots, who need empty lists)
+ */
+/datum/pathfind/proc/search()
+ start = get_turf(caller)
+ if(!start || !end)
+ stack_trace("Invalid A* start or destination")
+ return
+ if(start.z != end.z || start == end ) //no pathfinding between z levels
+ return
+ if(max_distance && (max_distance < get_dist(start, end))) //if start turf is farther than max_distance from end turf, no need to do anything
+ return
+
+ //initialization
+ var/datum/jps_node/current_processed_node = new (start, -1, 0, end)
+ open.insert(current_processed_node)
+ sources[start] = start // i'm sure this is fine
+
+ //then run the main loop
+ while(!open.is_empty() && !path)
+ if(!caller)
+ return
+ current_processed_node = open.pop() //get the lower f_value turf in the open list
+ if(max_distance && (current_processed_node.number_tiles > max_distance))//if too many steps, don't process that path
+ continue
+
+ var/turf/current_turf = current_processed_node.tile
+ for(var/scan_direction in list(EAST, WEST, NORTH, SOUTH))
+ lateral_scan_spec(current_turf, scan_direction, current_processed_node)
+
+ for(var/scan_direction in list(NORTHEAST, SOUTHEAST, NORTHWEST, SOUTHWEST))
+ diag_scan_spec(current_turf, scan_direction, current_processed_node)
+
+ CHECK_TICK
+
+ //we're done! reverse the path to get it from start to finish
+ if(path)
+ for(var/i = 1 to round(0.5 * length(path)))
+ path.Swap(i, length(path) - i + 1)
+
+ sources = null
+ qdel(open)
+ return path
+
+/// Called when we've hit the goal with the node that represents the last tile, then sets the path var to that path so it can be returned by [datum/pathfind/proc/search]
+/datum/pathfind/proc/unwind_path(datum/jps_node/unwind_node)
+ path = new()
+ var/turf/iter_turf = unwind_node.tile
+ path.Add(iter_turf)
+
+ while(unwind_node.previous_node)
+ var/dir_goal = get_dir(iter_turf, unwind_node.previous_node.tile)
+ for(var/i = 1 to unwind_node.jumps)
+ iter_turf = get_step(iter_turf,dir_goal)
+ path.Add(iter_turf)
+ unwind_node = unwind_node.previous_node
+
+/**
+ * For performing lateral scans from a given starting turf.
+ *
+ * These scans are called from both the main search loop, as well as subscans for diagonal scans, and they treat finding interesting turfs slightly differently.
+ * If we're doing a normal lateral scan, we already have a parent node supplied, so we just create the new node and immediately insert it into the heap, ezpz.
+ * If we're part of a subscan, we still need for the diagonal scan to generate a parent node, so we return a node datum with just the turf and let the diag scan
+ * proc handle transferring the values and inserting them into the heap.
+ *
+ * Arguments:
+ * * original_turf: What turf did we start this scan at?
+ * * heading: What direction are we going in? Obviously, should be cardinal
+ * * parent_node: Only given for normal lateral scans, if we don't have one, we're a diagonal subscan.
+*/
+/datum/pathfind/proc/lateral_scan_spec(turf/original_turf, heading, datum/jps_node/parent_node)
+ var/steps_taken = 0
+
+ var/turf/current_turf = original_turf
+ var/turf/lag_turf = original_turf
+
+ while(TRUE)
+ if(path)
+ return
+ lag_turf = current_turf
+ current_turf = get_step(current_turf, heading)
+ steps_taken++
+ if(!CAN_STEP(lag_turf, current_turf))
+ return
+
+ if(current_turf == end || (mintargetdist && (get_dist(current_turf, end) <= mintargetdist)))
+ var/datum/jps_node/final_node = new(current_turf, parent_node, steps_taken)
+ sources[current_turf] = original_turf
+ if(parent_node) // if this is a direct lateral scan we can wrap up, if it's a subscan from a diag, we need to let the diag make their node first, then finish
+ unwind_path(final_node)
+ return final_node
+ else if(sources[current_turf]) // already visited, essentially in the closed list
+ return
+ else
+ sources[current_turf] = original_turf
+
+ if(parent_node && parent_node.number_tiles + steps_taken > max_distance)
+ return
+
+ var/interesting = FALSE // have we found a forced neighbor that would make us add this turf to the open list?
+
+ switch(heading)
+ if(NORTH)
+ if(STEP_NOT_HERE_BUT_THERE(current_turf, WEST, NORTHWEST) || STEP_NOT_HERE_BUT_THERE(current_turf, EAST, NORTHEAST))
+ interesting = TRUE
+ if(SOUTH)
+ if(STEP_NOT_HERE_BUT_THERE(current_turf, WEST, SOUTHWEST) || STEP_NOT_HERE_BUT_THERE(current_turf, EAST, SOUTHEAST))
+ interesting = TRUE
+ if(EAST)
+ if(STEP_NOT_HERE_BUT_THERE(current_turf, NORTH, NORTHEAST) || STEP_NOT_HERE_BUT_THERE(current_turf, SOUTH, SOUTHEAST))
+ interesting = TRUE
+ if(WEST)
+ if(STEP_NOT_HERE_BUT_THERE(current_turf, NORTH, NORTHWEST) || STEP_NOT_HERE_BUT_THERE(current_turf, SOUTH, SOUTHWEST))
+ interesting = TRUE
+
+ if(interesting)
+ var/datum/jps_node/newnode = new(current_turf, parent_node, steps_taken)
+ if(parent_node) // if we're a diagonal subscan, we'll handle adding ourselves to the heap in the diag
+ open.insert(newnode)
+ return newnode
+
+/**
+ * For performing diagonal scans from a given starting turf.
+ *
+ * Unlike lateral scans, these only are called from the main search loop, so we don't need to worry about returning anything,
+ * though we do need to handle the return values of our lateral subscans of course.
+ *
+ * Arguments:
+ * * original_turf: What turf did we start this scan at?
+ * * heading: What direction are we going in? Obviously, should be diagonal
+ * * parent_node: We should always have a parent node for diagonals
+*/
+/datum/pathfind/proc/diag_scan_spec(turf/original_turf, heading, datum/jps_node/parent_node)
+ var/steps_taken = 0
+ var/turf/current_turf = original_turf
+ var/turf/lag_turf = original_turf
+
+ while(TRUE)
+ if(path)
+ return
+ lag_turf = current_turf
+ current_turf = get_step(current_turf, heading)
+ steps_taken++
+ if(!CAN_STEP(lag_turf, current_turf))
+ return
+
+ if(current_turf == end || (mintargetdist && (get_dist(current_turf, end) <= mintargetdist)))
+ var/datum/jps_node/final_node = new(current_turf, parent_node, steps_taken)
+ sources[current_turf] = original_turf
+ unwind_path(final_node)
+ return
+ else if(sources[current_turf]) // already visited, essentially in the closed list
+ return
+ else
+ sources[current_turf] = original_turf
+
+ if(parent_node.number_tiles + steps_taken > max_distance)
+ return
+
+ var/interesting = FALSE // have we found a forced neighbor that would make us add this turf to the open list?
+ var/datum/jps_node/possible_child_node // otherwise, did one of our lateral subscans turn up something?
+
+ switch(heading)
+ if(NORTHWEST)
+ if(STEP_NOT_HERE_BUT_THERE(current_turf, EAST, NORTHEAST) || STEP_NOT_HERE_BUT_THERE(current_turf, SOUTH, SOUTHWEST))
+ interesting = TRUE
+ else
+ possible_child_node = (lateral_scan_spec(current_turf, WEST) || lateral_scan_spec(current_turf, NORTH))
+ if(NORTHEAST)
+ if(STEP_NOT_HERE_BUT_THERE(current_turf, WEST, NORTHWEST) || STEP_NOT_HERE_BUT_THERE(current_turf, SOUTH, SOUTHEAST))
+ interesting = TRUE
+ else
+ possible_child_node = (lateral_scan_spec(current_turf, EAST) || lateral_scan_spec(current_turf, NORTH))
+ if(SOUTHWEST)
+ if(STEP_NOT_HERE_BUT_THERE(current_turf, EAST, SOUTHEAST) || STEP_NOT_HERE_BUT_THERE(current_turf, NORTH, NORTHWEST))
+ interesting = TRUE
+ else
+ possible_child_node = (lateral_scan_spec(current_turf, SOUTH) || lateral_scan_spec(current_turf, WEST))
+ if(SOUTHEAST)
+ if(STEP_NOT_HERE_BUT_THERE(current_turf, WEST, SOUTHWEST) || STEP_NOT_HERE_BUT_THERE(current_turf, NORTH, NORTHEAST))
+ interesting = TRUE
+ else
+ possible_child_node = (lateral_scan_spec(current_turf, SOUTH) || lateral_scan_spec(current_turf, EAST))
+
+ if(interesting || possible_child_node)
+ var/datum/jps_node/newnode = new(current_turf, parent_node, steps_taken)
+ open.insert(newnode)
+ if(possible_child_node)
+ possible_child_node.update_parent(newnode)
+ open.insert(possible_child_node)
+ if(possible_child_node.tile == end || (mintargetdist && (get_dist(possible_child_node.tile, end) <= mintargetdist)))
+ unwind_path(possible_child_node)
+ return
+
+/**
+ * For seeing if we can actually move between 2 given turfs while accounting for our access and the caller's pass_flags
+ *
+ * Arguments:
+ * * caller: The movable, if one exists, being used for mobility checks to see what tiles it can reach
+ * * ID: An ID card that decides if we can gain access to doors that would otherwise block a turf
+ * * simulated_only: Do we only worry about turfs with simulated atmos, most notably things that aren't space?
+*/
+/turf/proc/LinkBlockedWithAccess(turf/destination_turf, caller, ID)
+ var/actual_dir = get_dir(src, destination_turf)
+
+ for(var/obj/structure/window/iter_window in src)
+ if(!iter_window.CanAStarPass(ID, actual_dir))
+ return TRUE
+
+ for(var/obj/machinery/door/window/iter_windoor in src)
+ if(!iter_windoor.CanAStarPass(ID, actual_dir))
+ return TRUE
+
+ var/reverse_dir = get_dir(destination_turf, src)
+ for(var/obj/iter_object in destination_turf)
+ if(!iter_object.CanAStarPass(ID, reverse_dir, caller))
+ return TRUE
+
+ return FALSE
+
+#undef CAN_STEP
+#undef STEP_NOT_HERE_BUT_THERE
diff --git a/code/__HELPERS/priority_announce.dm b/code/__HELPERS/priority_announce.dm
index 8819eb4d04fa4..a2cd14316cfea 100644
--- a/code/__HELPERS/priority_announce.dm
+++ b/code/__HELPERS/priority_announce.dm
@@ -1,8 +1,15 @@
-/proc/priority_announce(text, title = "", sound = 'sound/ai/attention.ogg', type , sender_override)
+#define DEFAULT_ALERT "alert_sound"
+
+/proc/priority_announce(text, title = "", sound = DEFAULT_ALERT, type, sender_override, has_important_message, auth_id)
if(!text)
return
- var/announcement
+ var/announcement = ""
+ if(sound == DEFAULT_ALERT)
+ sound = SSstation.announcer.get_rand_alert_sound()
+
+ if(sound && SSstation.announcer.event_sounds[sound])
+ sound = SSstation.announcer.event_sounds[sound]
if(type == "Priority")
announcement += "
"
for(var/client/C in GLOB.clients)
if(C.prefs.show_credits)
- C.screen += new /obj/screen/credit/title_card(null, null, SSticker.mode.title_icon)
+ C.screen += new /atom/movable/screen/credit/title_card(null, null, SSticker.mode.title_icon)
sleep(CREDIT_SPAWN_SPEED * 3)
for(var/i in 1 to GLOB.end_titles.len)
var/C = GLOB.end_titles[i]
@@ -42,9 +50,9 @@ GLOBAL_LIST(end_titles)
/proc/create_credit(credit)
- new /obj/screen/credit(null, credit)
+ new /atom/movable/screen/credit(null, credit)
-/obj/screen/credit
+/atom/movable/screen/credit
icon_state = "blank"
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
alpha = 0
@@ -52,9 +60,9 @@ GLOBAL_LIST(end_titles)
layer = SPLASHSCREEN_LAYER
var/matrix/target
-/obj/screen/credit/Initialize(mapload, credited)
+/atom/movable/screen/credit/Initialize(mapload, credited)
. = ..()
- maptext = "[credited]"
+ maptext = MAPTEXT("[credited]")
maptext_height = world.icon_size * 2
maptext_width = world.icon_size * 13
var/matrix/M = matrix(transform)
@@ -65,20 +73,20 @@ GLOBAL_LIST(end_titles)
INVOKE_ASYNC(src, .proc/add_to_clients)
QDEL_IN(src, CREDIT_ROLL_SPEED)
-/obj/screen/credit/proc/add_to_clients()
+/atom/movable/screen/credit/proc/add_to_clients()
for(var/client/C in GLOB.clients)
if(C.prefs.show_credits)
C.screen += src
-/obj/screen/credit/Destroy()
+/atom/movable/screen/credit/Destroy()
screen_loc = null
return ..()
-/obj/screen/credit/title_card
+/atom/movable/screen/credit/title_card
icon = 'icons/title_cards.dmi'
screen_loc = "4,1"
-/obj/screen/credit/title_card/Initialize(mapload, credited, title_icon_state)
+/atom/movable/screen/credit/title_card/Initialize(mapload, credited, title_icon_state)
icon_state = title_icon_state
. = ..()
maptext = null
diff --git a/code/_onclick/hud/devil.dm b/code/_onclick/hud/devil.dm
index 900c807a7afed..7588a4abfcb88 100644
--- a/code/_onclick/hud/devil.dm
+++ b/code/_onclick/hud/devil.dm
@@ -4,15 +4,15 @@
/datum/hud/devil/New(mob/owner)
..()
- var/obj/screen/using
+ var/atom/movable/screen/using
- using = new /obj/screen/drop()
+ using = new /atom/movable/screen/drop()
using.icon = ui_style
using.screen_loc = ui_drone_drop
using.hud = src
static_inventory += using
- pull_icon = new /obj/screen/pull()
+ pull_icon = new /atom/movable/screen/pull()
pull_icon.icon = ui_style
pull_icon.update_icon()
pull_icon.screen_loc = ui_drone_pull
@@ -21,7 +21,7 @@
build_hand_slots()
- using = new /obj/screen/inventory()
+ using = new /atom/movable/screen/inventory()
using.name = "hand"
using.icon = ui_style
using.icon_state = "swap_1_m"
@@ -31,7 +31,7 @@
using.hud = src
static_inventory += using
- using = new /obj/screen/inventory()
+ using = new /atom/movable/screen/inventory()
using.name = "hand"
using.icon = ui_style
using.icon_state = "swap_2"
@@ -41,15 +41,15 @@
using.hud = src
static_inventory += using
- zone_select = new /obj/screen/zone_sel()
+ zone_select = new /atom/movable/screen/zone_sel()
zone_select.icon = ui_style
zone_select.hud = src
zone_select.update_icon()
- lingchemdisplay = new /obj/screen/ling/chems()
+ lingchemdisplay = new /atom/movable/screen/ling/chems()
lingchemdisplay.hud = src
- devilsouldisplay = new /obj/screen/devil/soul_counter
+ devilsouldisplay = new /atom/movable/screen/devil/soul_counter
devilsouldisplay.hud = src
infodisplay += devilsouldisplay
diff --git a/code/_onclick/hud/drones.dm b/code/_onclick/hud/drones.dm
index 3a6b7511136ab..06d6e239df7e8 100644
--- a/code/_onclick/hud/drones.dm
+++ b/code/_onclick/hud/drones.dm
@@ -1,31 +1,31 @@
/datum/hud/dextrous/drone/New(mob/owner)
..()
- var/obj/screen/inventory/inv_box
+ var/atom/movable/screen/inventory/inv_box
- inv_box = new /obj/screen/inventory()
+ inv_box = new /atom/movable/screen/inventory()
inv_box.name = "internal storage"
inv_box.icon = ui_style
inv_box.icon_state = "suit_storage"
// inv_box.icon_full = "template"
inv_box.screen_loc = ui_drone_storage
- inv_box.slot_id = SLOT_GENERC_DEXTROUS_STORAGE
+ inv_box.slot_id = ITEM_SLOT_DEX_STORAGE
inv_box.hud = src
static_inventory += inv_box
- inv_box = new /obj/screen/inventory()
+ inv_box = new /atom/movable/screen/inventory()
inv_box.name = "head/mask"
inv_box.icon = ui_style
inv_box.icon_state = "mask"
// inv_box.icon_full = "template"
inv_box.screen_loc = ui_drone_head
- inv_box.slot_id = SLOT_HEAD
+ inv_box.slot_id = ITEM_SLOT_HEAD
inv_box.hud = src
static_inventory += inv_box
- for(var/obj/screen/inventory/inv in (static_inventory + toggleable_inventory))
+ for(var/atom/movable/screen/inventory/inv in (static_inventory + toggleable_inventory))
if(inv.slot_id)
inv.hud = src
- inv_slots[inv.slot_id] = inv
+ inv_slots[TOBITSHIFT(inv.slot_id) + 1] = inv
inv.update_icon()
diff --git a/code/_onclick/hud/fullscreen.dm b/code/_onclick/hud/fullscreen.dm
index 10317125b4d0d..c4f9275883757 100644
--- a/code/_onclick/hud/fullscreen.dm
+++ b/code/_onclick/hud/fullscreen.dm
@@ -3,7 +3,7 @@
var/list/screens = list()
/mob/proc/overlay_fullscreen(category, type, severity)
- var/obj/screen/fullscreen/screen = screens[category]
+ var/atom/movable/screen/fullscreen/screen = screens[category]
if (!screen || screen.type != type)
// needs to be recreated
clear_fullscreen(category, FALSE)
@@ -21,7 +21,7 @@
return screen
/mob/proc/clear_fullscreen(category, animated = 10)
- var/obj/screen/fullscreen/screen = screens[category]
+ var/atom/movable/screen/fullscreen/screen = screens[category]
if(!screen)
return
@@ -35,7 +35,7 @@
client.screen -= screen
qdel(screen)
-/mob/proc/clear_fullscreen_after_animate(obj/screen/fullscreen/screen)
+/mob/proc/clear_fullscreen_after_animate(atom/movable/screen/fullscreen/screen)
if(client)
client.screen -= screen
qdel(screen)
@@ -51,7 +51,7 @@
/mob/proc/reload_fullscreen()
if(client)
- var/obj/screen/fullscreen/screen
+ var/atom/movable/screen/fullscreen/screen
for(var/category in screens)
screen = screens[category]
if(screen.should_show_to(src))
@@ -60,7 +60,7 @@
else
client.screen -= screen
-/obj/screen/fullscreen
+/atom/movable/screen/fullscreen
icon = 'icons/mob/screen_full.dmi'
icon_state = "default"
screen_loc = "CENTER-7,CENTER-7"
@@ -71,94 +71,94 @@
var/severity = 0
var/show_when_dead = FALSE
-/obj/screen/fullscreen/proc/update_for_view(client_view)
+/atom/movable/screen/fullscreen/proc/update_for_view(client_view)
if (screen_loc == "CENTER-7,CENTER-7" && view != client_view)
var/list/actualview = getviewsize(client_view)
view = client_view
transform = matrix(actualview[1]/FULLSCREEN_OVERLAY_RESOLUTION_X, 0, 0, 0, actualview[2]/FULLSCREEN_OVERLAY_RESOLUTION_Y, 0)
-/obj/screen/fullscreen/proc/should_show_to(mob/mymob)
+/atom/movable/screen/fullscreen/proc/should_show_to(mob/mymob)
if(!show_when_dead && mymob.stat == DEAD)
return FALSE
return TRUE
-/obj/screen/fullscreen/Destroy()
+/atom/movable/screen/fullscreen/Destroy()
severity = 0
. = ..()
-/obj/screen/fullscreen/brute
+/atom/movable/screen/fullscreen/brute
icon_state = "brutedamageoverlay"
layer = UI_DAMAGE_LAYER
plane = FULLSCREEN_PLANE
-/obj/screen/fullscreen/oxy
+/atom/movable/screen/fullscreen/oxy
icon_state = "oxydamageoverlay"
layer = UI_DAMAGE_LAYER
plane = FULLSCREEN_PLANE
-/obj/screen/fullscreen/crit
+/atom/movable/screen/fullscreen/crit
icon_state = "passage"
layer = CRIT_LAYER
plane = FULLSCREEN_PLANE
-/obj/screen/fullscreen/crit/vision
+/atom/movable/screen/fullscreen/crit/vision
icon_state = "oxydamageoverlay"
layer = BLIND_LAYER
-/obj/screen/fullscreen/blind
+/atom/movable/screen/fullscreen/blind
icon_state = "blackimageoverlay"
layer = BLIND_LAYER
plane = FULLSCREEN_PLANE
-/obj/screen/fullscreen/curse
+/atom/movable/screen/fullscreen/curse
icon_state = "curse"
layer = CURSE_LAYER
plane = FULLSCREEN_PLANE
-/obj/screen/fullscreen/hive_mc
+/atom/movable/screen/fullscreen/hive_mc
icon_state = "hive_mc"
layer = CURSE_LAYER
plane = FULLSCREEN_PLANE
-/obj/screen/fullscreen/hive_eyes
+/atom/movable/screen/fullscreen/hive_eyes
icon_state = "hive_eyes"
layer = CURSE_LAYER
plane = FULLSCREEN_PLANE
-/obj/screen/fullscreen/impaired
+/atom/movable/screen/fullscreen/impaired
icon_state = "impairedoverlay"
-/obj/screen/fullscreen/flash
+/atom/movable/screen/fullscreen/flash
icon = 'icons/mob/screen_gen.dmi'
screen_loc = "WEST,SOUTH to EAST,NORTH"
icon_state = "flash"
-/obj/screen/fullscreen/flash/static
+/atom/movable/screen/fullscreen/flash/static
icon = 'icons/mob/screen_gen.dmi'
screen_loc = "WEST,SOUTH to EAST,NORTH"
icon_state = "noise"
-/obj/screen/fullscreen/high
+/atom/movable/screen/fullscreen/high
icon = 'icons/mob/screen_gen.dmi'
screen_loc = "WEST,SOUTH to EAST,NORTH"
icon_state = "druggy"
-/obj/screen/fullscreen/color_vision
+/atom/movable/screen/fullscreen/color_vision
icon = 'icons/mob/screen_gen.dmi'
screen_loc = "WEST,SOUTH to EAST,NORTH"
icon_state = "flash"
alpha = 80
-/obj/screen/fullscreen/color_vision/green
+/atom/movable/screen/fullscreen/color_vision/green
color = "#00ff00"
-/obj/screen/fullscreen/color_vision/red
+/atom/movable/screen/fullscreen/color_vision/red
color = "#ff0000"
-/obj/screen/fullscreen/color_vision/blue
+/atom/movable/screen/fullscreen/color_vision/blue
color = "#0000ff"
-/obj/screen/fullscreen/lighting_backdrop
+/atom/movable/screen/fullscreen/lighting_backdrop
icon = 'icons/mob/screen_gen.dmi'
icon_state = "flash"
transform = matrix(200, 0, 0, 0, 200, 0)
@@ -167,18 +167,18 @@
show_when_dead = TRUE
//Provides darkness to the back of the lighting plane
-/obj/screen/fullscreen/lighting_backdrop/lit
+/atom/movable/screen/fullscreen/lighting_backdrop/lit
invisibility = INVISIBILITY_LIGHTING
layer = BACKGROUND_LAYER+21
color = "#000"
show_when_dead = TRUE
//Provides whiteness in case you don't see lights so everything is still visible
-/obj/screen/fullscreen/lighting_backdrop/unlit
+/atom/movable/screen/fullscreen/lighting_backdrop/unlit
layer = BACKGROUND_LAYER+20
show_when_dead = TRUE
-/obj/screen/fullscreen/see_through_darkness
+/atom/movable/screen/fullscreen/see_through_darkness
icon_state = "nightvision"
plane = LIGHTING_PLANE
layer = LIGHTING_LAYER
diff --git a/code/_onclick/hud/generic_dextrous.dm b/code/_onclick/hud/generic_dextrous.dm
index d018c187f2826..c0e6e6a785eee 100644
--- a/code/_onclick/hud/generic_dextrous.dm
+++ b/code/_onclick/hud/generic_dextrous.dm
@@ -1,15 +1,15 @@
//Used for normal mobs that have hands.
/datum/hud/dextrous/New(mob/living/owner)
..()
- var/obj/screen/using
+ var/atom/movable/screen/using
- using = new /obj/screen/drop()
+ using = new /atom/movable/screen/drop()
using.icon = ui_style
using.screen_loc = ui_drone_drop
using.hud = src
static_inventory += using
- pull_icon = new /obj/screen/pull()
+ pull_icon = new /atom/movable/screen/pull()
pull_icon.icon = ui_style
pull_icon.update_icon()
pull_icon.screen_loc = ui_drone_pull
@@ -18,14 +18,14 @@
build_hand_slots()
- using = new /obj/screen/swap_hand()
+ using = new /atom/movable/screen/swap_hand()
using.icon = ui_style
using.icon_state = "swap_1_m"
using.screen_loc = ui_swaphand_position(owner,1)
using.hud = src
static_inventory += using
- using = new /obj/screen/swap_hand()
+ using = new /atom/movable/screen/swap_hand()
using.icon = ui_style
using.icon_state = "swap_2"
using.screen_loc = ui_swaphand_position(owner,2)
@@ -35,32 +35,32 @@
if(mymob.possible_a_intents)
if(mymob.possible_a_intents.len == 4)
// All possible intents - full intent selector
- action_intent = new /obj/screen/act_intent/segmented
+ action_intent = new /atom/movable/screen/act_intent/segmented
else
- action_intent = new /obj/screen/act_intent
+ action_intent = new /atom/movable/screen/act_intent
action_intent.icon = ui_style
action_intent.icon_state = mymob.a_intent
action_intent.hud = src
static_inventory += action_intent
- zone_select = new /obj/screen/zone_sel()
+ zone_select = new /atom/movable/screen/zone_sel()
zone_select.icon = ui_style
zone_select.update_icon()
zone_select.hud = src
static_inventory += zone_select
- using = new /obj/screen/area_creator
+ using = new /atom/movable/screen/area_creator
using.icon = ui_style
using.hud = src
static_inventory += using
mymob.client.screen = list()
- for(var/obj/screen/inventory/inv in (static_inventory + toggleable_inventory))
+ for(var/atom/movable/screen/inventory/inv in (static_inventory + toggleable_inventory))
if(inv.slot_id)
inv.hud = src
- inv_slots[inv.slot_id] = inv
+ inv_slots[TOBITSHIFT(inv.slot_id) + 1] = inv
inv.update_icon()
/datum/hud/dextrous/persistent_inventory_update()
diff --git a/code/_onclick/hud/ghost.dm b/code/_onclick/hud/ghost.dm
index f67c922404e61..c5de8c24396c4 100644
--- a/code/_onclick/hud/ghost.dm
+++ b/code/_onclick/hud/ghost.dm
@@ -1,79 +1,79 @@
-/obj/screen/ghost
+/atom/movable/screen/ghost
icon = 'icons/mob/screen_ghost.dmi'
-/obj/screen/ghost/MouseEntered()
+/atom/movable/screen/ghost/MouseEntered()
flick(icon_state + "_anim", src)
-/obj/screen/ghost/jumptomob
+/atom/movable/screen/ghost/jumptomob
name = "Jump to mob"
icon_state = "jumptomob"
-/obj/screen/ghost/jumptomob/Click()
+/atom/movable/screen/ghost/jumptomob/Click()
var/mob/dead/observer/G = usr
G.jumptomob()
-/obj/screen/ghost/orbit
+/atom/movable/screen/ghost/orbit
name = "Orbit"
icon_state = "orbit"
-/obj/screen/ghost/orbit/Click()
+/atom/movable/screen/ghost/orbit/Click()
var/mob/dead/observer/G = usr
G.follow()
-/obj/screen/ghost/reenter_corpse
+/atom/movable/screen/ghost/reenter_corpse
name = "Reenter corpse"
icon_state = "reenter_corpse"
-/obj/screen/ghost/reenter_corpse/Click()
+/atom/movable/screen/ghost/reenter_corpse/Click()
var/mob/dead/observer/G = usr
G.reenter_corpse()
-/obj/screen/ghost/teleport
+/atom/movable/screen/ghost/teleport
name = "Teleport"
icon_state = "teleport"
-/obj/screen/ghost/teleport/Click()
+/atom/movable/screen/ghost/teleport/Click()
var/mob/dead/observer/G = usr
G.dead_tele()
-/obj/screen/ghost/pai
+/atom/movable/screen/ghost/pai
name = "pAI Candidate"
icon_state = "pai"
-/obj/screen/ghost/pai/Click()
+/atom/movable/screen/ghost/pai/Click()
var/mob/dead/observer/G = usr
G.register_pai()
/datum/hud/ghost/New(mob/owner)
..()
- var/obj/screen/using
+ var/atom/movable/screen/using
- using = new /obj/screen/ghost/jumptomob()
+ using = new /atom/movable/screen/ghost/jumptomob()
using.screen_loc = ui_ghost_jumptomob
using.hud = src
static_inventory += using
- using = new /obj/screen/ghost/orbit()
+ using = new /atom/movable/screen/ghost/orbit()
using.screen_loc = ui_ghost_orbit
using.hud = src
static_inventory += using
- using = new /obj/screen/ghost/reenter_corpse()
+ using = new /atom/movable/screen/ghost/reenter_corpse()
using.screen_loc = ui_ghost_reenter_corpse
using.hud = src
static_inventory += using
- using = new /obj/screen/ghost/teleport()
+ using = new /atom/movable/screen/ghost/teleport()
using.screen_loc = ui_ghost_teleport
using.hud = src
static_inventory += using
- using = new /obj/screen/ghost/pai()
+ using = new /atom/movable/screen/ghost/pai()
using.screen_loc = ui_ghost_pai
using.hud = src
static_inventory += using
- using = new /obj/screen/language_menu
+ using = new /atom/movable/screen/language_menu
using.icon = ui_style
using.hud = src
static_inventory += using
diff --git a/code/_onclick/hud/guardian.dm b/code/_onclick/hud/guardian.dm
index 47ad1117981f2..45ef8d9cf5767 100644
--- a/code/_onclick/hud/guardian.dm
+++ b/code/_onclick/hud/guardian.dm
@@ -1,18 +1,18 @@
/datum/hud/guardian/New(mob/living/simple_animal/hostile/guardian/owner)
..()
- var/obj/screen/using
+ var/atom/movable/screen/using
- healths = new /obj/screen/healths/guardian()
+ healths = new /atom/movable/screen/healths/guardian()
healths.hud = src
infodisplay += healths
- using = new /obj/screen/guardian/Manifest()
+ using = new /atom/movable/screen/guardian/Manifest()
using.screen_loc = ui_hand_position(2)
using.hud = src
static_inventory += using
- using = new /obj/screen/guardian/Recall()
+ using = new /atom/movable/screen/guardian/Recall()
using.screen_loc = ui_hand_position(1)
using.hud = src
static_inventory += using
@@ -22,12 +22,12 @@
using.hud = src
static_inventory += using
- using = new /obj/screen/guardian/ToggleLight()
+ using = new /atom/movable/screen/guardian/ToggleLight()
using.screen_loc = ui_inventory
using.hud = src
static_inventory += using
- using = new /obj/screen/guardian/Communicate()
+ using = new /atom/movable/screen/guardian/Communicate()
using.screen_loc = ui_back
using.hud = src
static_inventory += using
@@ -35,41 +35,41 @@
/*
/datum/hud/dextrous/guardian/New(mob/living/simple_animal/hostile/guardian/owner) //for a dextrous guardian
..()
- var/obj/screen/using
+ var/atom/movable/screen/using
if(istype(owner, /mob/living/simple_animal/hostile/guardian/dextrous))
- var/obj/screen/inventory/inv_box
+ var/atom/movable/screen/inventory/inv_box
- inv_box = new /obj/screen/inventory()
+ inv_box = new /atom/movable/screen/inventory()
inv_box.name = "internal storage"
inv_box.icon = ui_style
inv_box.icon_state = "suit_storage"
inv_box.screen_loc = ui_id
- inv_box.slot_id = SLOT_GENERC_DEXTROUS_STORAGE
+ inv_box.slot_id = ITEM_SLOT_DEX_STORAGE
inv_box.hud = src
static_inventory += inv_box
- using = new /obj/screen/guardian/Communicate()
+ using = new /atom/movable/screen/guardian/Communicate()
using.screen_loc = ui_sstore1
using.hud = src
static_inventory += using
else
- using = new /obj/screen/guardian/Communicate()
+ using = new /atom/movable/screen/guardian/Communicate()
using.screen_loc = ui_id
using.hud = src
static_inventory += using
- healths = new /obj/screen/healths/guardian()
+ healths = new /atom/movable/screen/healths/guardian()
healths.hud = src
infodisplay += healths
- using = new /obj/screen/guardian/Manifest()
+ using = new /atom/movable/screen/guardian/Manifest()
using.screen_loc = ui_belt
using.hud = src
static_inventory += using
- using = new /obj/screen/guardian/Recall()
+ using = new /atom/movable/screen/guardian/Recall()
using.screen_loc = ui_back
using.hud = src
static_inventory += using
@@ -79,7 +79,7 @@
using.hud = src
static_inventory += using
- using = new /obj/screen/guardian/ToggleLight()
+ using = new /atom/movable/screen/guardian/ToggleLight()
using.screen_loc = ui_inventory
using.hud = src
static_inventory += using
@@ -101,65 +101,65 @@
..()
*/
-/obj/screen/guardian
+/atom/movable/screen/guardian
icon = 'icons/mob/guardian.dmi'
-/obj/screen/guardian/Manifest
+/atom/movable/screen/guardian/Manifest
icon_state = "manifest"
name = "Manifest"
desc = "Spring forth into battle!"
-/obj/screen/guardian/Manifest/Click()
+/atom/movable/screen/guardian/Manifest/Click()
if(isguardian(usr))
var/mob/living/simple_animal/hostile/guardian/G = usr
G.Manifest()
-/obj/screen/guardian/Recall
+/atom/movable/screen/guardian/Recall
icon_state = "recall"
name = "Recall"
desc = "Return to your user."
-/obj/screen/guardian/Recall/Click()
+/atom/movable/screen/guardian/Recall/Click()
if(isguardian(usr))
var/mob/living/simple_animal/hostile/guardian/G = usr
G.Recall()
-/obj/screen/guardian/ToggleMode
+/atom/movable/screen/guardian/ToggleMode
icon_state = "toggle"
name = "Toggle Mode"
desc = "Switch between ability modes."
-/obj/screen/guardian/ToggleMode/Click()
+/atom/movable/screen/guardian/ToggleMode/Click()
if(isguardian(usr))
var/mob/living/simple_animal/hostile/guardian/G = usr
G.ToggleMode()
-/obj/screen/guardian/ToggleMode/Inactive
+/atom/movable/screen/guardian/ToggleMode/Inactive
icon_state = "notoggle" //greyed out so it doesn't look like it'll work
-/obj/screen/guardian/ToggleMode/Assassin
+/atom/movable/screen/guardian/ToggleMode/Assassin
icon_state = "stealth"
name = "Toggle Stealth"
desc = "Enter or exit stealth."
-/obj/screen/guardian/Communicate
+/atom/movable/screen/guardian/Communicate
icon_state = "communicate"
name = "Communicate"
desc = "Communicate telepathically with your user."
-/obj/screen/guardian/Communicate/Click()
+/atom/movable/screen/guardian/Communicate/Click()
if(isguardian(usr))
var/mob/living/simple_animal/hostile/guardian/G = usr
G.Communicate()
-/obj/screen/guardian/ToggleLight
+/atom/movable/screen/guardian/ToggleLight
icon_state = "light"
name = "Toggle Light"
desc = "Glow like star dust."
-/obj/screen/guardian/ToggleLight/Click()
+/atom/movable/screen/guardian/ToggleLight/Click()
if(isguardian(usr))
var/mob/living/simple_animal/hostile/guardian/G = usr
G.ToggleLight()
diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm
index 97cfbed381a38..d451ff2b9a763 100644
--- a/code/_onclick/hud/hud.dm
+++ b/code/_onclick/hud/hud.dm
@@ -25,38 +25,41 @@ GLOBAL_LIST_INIT(available_ui_styles, sortList(list(
var/inventory_shown = FALSE //Equipped item inventory
var/hotkey_ui_hidden = FALSE //This is to hide the buttons that can be used via hotkeys. (hotkeybuttons list of buttons)
- var/obj/screen/ling/chems/lingchemdisplay
- var/obj/screen/ling/sting/lingstingdisplay
+ var/atom/movable/screen/ling/chems/lingchemdisplay
+ var/atom/movable/screen/ling/sting/lingstingdisplay
- var/obj/screen/blobpwrdisplay
+ var/atom/movable/screen/blobpwrdisplay
- var/obj/screen/alien_plasma_display
- var/obj/screen/alien_queen_finder
+ var/atom/movable/screen/alien_plasma_display
+ var/atom/movable/screen/alien_queen_finder
- var/obj/screen/devil/soul_counter/devilsouldisplay
+ var/atom/movable/screen/devil/soul_counter/devilsouldisplay
- var/obj/screen/action_intent
- var/obj/screen/zone_select
- var/obj/screen/pull_icon
- var/obj/screen/rest_icon
- var/obj/screen/throw_icon
- var/obj/screen/module_store_icon
+ var/atom/movable/screen/action_intent
+ var/atom/movable/screen/zone_select
+ var/atom/movable/screen/pull_icon
+ var/atom/movable/screen/rest_icon
+ var/atom/movable/screen/throw_icon
+ var/atom/movable/screen/module_store_icon
var/list/static_inventory = list() //the screen objects which are static
var/list/toggleable_inventory = list() //the screen objects which can be hidden
- var/list/obj/screen/hotkeybuttons = list() //the buttons that can be used via hotkeys
+ var/list/atom/movable/screen/hotkeybuttons = list() //the buttons that can be used via hotkeys
var/list/infodisplay = list() //the screen objects that display mob info (health, alien plasma, etc...)
var/list/screenoverlays = list() //the screen objects used as whole screen overlays (flash, damageoverlay, etc...)
- var/list/inv_slots[SLOTS_AMT] // /obj/screen/inventory objects, ordered by their slot ID.
- var/list/hand_slots // /obj/screen/inventory/hand objects, assoc list of "[held_index]" = object
- var/list/obj/screen/plane_master/plane_masters = list() // see "appearance_flags" in the ref, assoc list of "[plane]" = object
-
- var/obj/screen/movable/action_button/hide_toggle/hide_actions_toggle
+ var/list/inv_slots[SLOTS_AMT] // /atom/movable/screen/inventory objects, ordered by their slot ID.
+ var/list/hand_slots // /atom/movable/screen/inventory/hand objects, assoc list of "[held_index]" = object
+ var/list/atom/movable/screen/plane_master/plane_masters = list() // see "appearance_flags" in the ref, assoc list of "[plane]" = object
+ ///Assoc list of controller groups, associated with key string group name with value of the plane master controller ref
+ var/list/atom/movable/plane_master_controller/plane_master_controllers = list()
+ var/list/team_finder_arrows = list()
+
+ var/atom/movable/screen/movable/action_button/hide_toggle/hide_actions_toggle
var/action_buttons_hidden = FALSE
- var/obj/screen/healths
- var/obj/screen/healthdoll
- var/obj/screen/internals
+ var/atom/movable/screen/healths
+ var/atom/movable/screen/healthdoll
+ var/atom/movable/screen/internals
// subtypes can override this to force a specific UI style
var/ui_style
@@ -75,11 +78,16 @@ GLOBAL_LIST_INIT(available_ui_styles, sortList(list(
hand_slots = list()
- for(var/mytype in subtypesof(/obj/screen/plane_master))
- var/obj/screen/plane_master/instance = new mytype()
+ for(var/mytype in subtypesof(/atom/movable/screen/plane_master))
+ var/atom/movable/screen/plane_master/instance = new mytype()
plane_masters["[instance.plane]"] = instance
instance.backdrop(mymob)
+ for(var/mytype in subtypesof(/atom/movable/plane_master_controller))
+ var/atom/movable/plane_master_controller/controller_instance = new mytype(src)
+ plane_master_controllers[controller_instance.name] = controller_instance
+
+
/datum/hud/Destroy()
if(mymob.hud_used == src)
mymob.hud_used = null
@@ -87,6 +95,7 @@ GLOBAL_LIST_INIT(available_ui_styles, sortList(list(
QDEL_NULL(hide_actions_toggle)
QDEL_NULL(module_store_icon)
QDEL_LIST(static_inventory)
+ QDEL_LIST(team_finder_arrows)
inv_slots.Cut()
action_intent = null
@@ -109,6 +118,7 @@ GLOBAL_LIST_INIT(available_ui_styles, sortList(list(
alien_queen_finder = null
QDEL_LIST_ASSOC_VAL(plane_masters)
+ QDEL_LIST_ASSOC_VAL(plane_master_controllers)
QDEL_LIST(screenoverlays)
mymob = null
@@ -152,6 +162,8 @@ GLOBAL_LIST_INIT(available_ui_styles, sortList(list(
screenmob.client.screen += hotkeybuttons
if(infodisplay.len)
screenmob.client.screen += infodisplay
+ if(team_finder_arrows.len)
+ screenmob.client.screen += team_finder_arrows
screenmob.client.screen += hide_actions_toggle
@@ -162,6 +174,8 @@ GLOBAL_LIST_INIT(available_ui_styles, sortList(list(
hud_shown = FALSE //Governs behavior of other procs
if(static_inventory.len)
screenmob.client.screen -= static_inventory
+ if(team_finder_arrows.len)
+ screenmob.client.screen += team_finder_arrows
if(toggleable_inventory.len)
screenmob.client.screen -= toggleable_inventory
if(hotkeybuttons.len)
@@ -171,7 +185,7 @@ GLOBAL_LIST_INIT(available_ui_styles, sortList(list(
//These ones are a part of 'static_inventory', 'toggleable_inventory' or 'hotkeybuttons' but we want them to stay
for(var/h in hand_slots)
- var/obj/screen/hand = hand_slots[h]
+ var/atom/movable/screen/hand = hand_slots[h]
if(hand)
screenmob.client.screen += hand
if(action_intent)
@@ -188,6 +202,8 @@ GLOBAL_LIST_INIT(available_ui_styles, sortList(list(
screenmob.client.screen -= hotkeybuttons
if(infodisplay.len)
screenmob.client.screen -= infodisplay
+ if(team_finder_arrows.len)
+ screenmob.client.screen -= team_finder_arrows
hud_version = display_hud_version
persistent_inventory_update(screenmob)
@@ -209,7 +225,7 @@ GLOBAL_LIST_INIT(available_ui_styles, sortList(list(
/datum/hud/proc/plane_masters_update()
// Plane masters are always shown to OUR mob, never to observers
for(var/thing in plane_masters)
- var/obj/screen/plane_master/PM = plane_masters[thing]
+ var/atom/movable/screen/plane_master/PM = plane_masters[thing]
PM.backdrop(mymob)
mymob.client.screen += PM
@@ -263,13 +279,13 @@ GLOBAL_LIST_INIT(available_ui_styles, sortList(list(
//9/10 this is only called once per mob and only for 2 hands
/datum/hud/proc/build_hand_slots()
for(var/h in hand_slots)
- var/obj/screen/inventory/hand/H = hand_slots[h]
+ var/atom/movable/screen/inventory/hand/H = hand_slots[h]
if(H)
static_inventory -= H
hand_slots = list()
- var/obj/screen/inventory/hand/hand_box
+ var/atom/movable/screen/inventory/hand/hand_box
for(var/i in 1 to mymob.held_items.len)
- hand_box = new /obj/screen/inventory/hand()
+ hand_box = new /atom/movable/screen/inventory/hand()
hand_box.name = mymob.get_held_index_name(i)
hand_box.icon = ui_style
hand_box.icon_state = "hand_[mymob.held_index_to_dir(i)]"
@@ -281,10 +297,10 @@ GLOBAL_LIST_INIT(available_ui_styles, sortList(list(
hand_box.update_icon()
var/i = 1
- for(var/obj/screen/swap_hand/SH in static_inventory)
+ for(var/atom/movable/screen/swap_hand/SH in static_inventory)
SH.screen_loc = ui_swaphand_position(mymob,!(i % 2) ? 2: 1)
i++
- for(var/obj/screen/human/equip/E in static_inventory)
+ for(var/atom/movable/screen/human/equip/E in static_inventory)
E.screen_loc = ui_equip_position(mymob)
if(ismob(mymob) && mymob.hud_used == src)
diff --git a/code/_onclick/hud/human.dm b/code/_onclick/hud/human.dm
index 20140b49acd2f..047943f9bdef7 100644
--- a/code/_onclick/hud/human.dm
+++ b/code/_onclick/hud/human.dm
@@ -1,11 +1,11 @@
-/obj/screen/human
+/atom/movable/screen/human
icon = 'icons/mob/screen_midnight.dmi'
-/obj/screen/human/toggle
+/atom/movable/screen/human/toggle
name = "toggle"
icon_state = "toggle"
-/obj/screen/human/toggle/Click()
+/atom/movable/screen/human/toggle/Click()
var/mob/targetmob = usr
@@ -23,28 +23,28 @@
targetmob.hud_used.hidden_inventory_update(usr)
-/obj/screen/human/equip
+/atom/movable/screen/human/equip
name = "equip"
icon_state = "act_equip"
-/obj/screen/human/equip/Click()
+/atom/movable/screen/human/equip/Click()
if(ismecha(usr.loc)) // stops inventory actions in a mech
return 1
var/mob/living/carbon/human/H = usr
H.quick_equip()
-/obj/screen/devil
+/atom/movable/screen/devil
invisibility = INVISIBILITY_ABSTRACT
-/obj/screen/devil/soul_counter
+/atom/movable/screen/devil/soul_counter
icon = 'icons/mob/screen_gen.dmi'
name = "souls owned"
icon_state = "Devil-6"
screen_loc = ui_devilsouldisplay
-/obj/screen/devil/soul_counter/proc/update_counter(souls = 0)
+/atom/movable/screen/devil/soul_counter/proc/update_counter(souls = 0)
invisibility = 0
- maptext = "
[souls]
"
+ maptext = MAPTEXT("
[souls]
")
switch(souls)
if(0,null)
icon_state = "Devil-1"
@@ -59,73 +59,73 @@
else
icon_state = "Devil-6"
-/obj/screen/devil/soul_counter/proc/clear()
+/atom/movable/screen/devil/soul_counter/proc/clear()
invisibility = INVISIBILITY_ABSTRACT
-/obj/screen/ling
+/atom/movable/screen/ling
invisibility = INVISIBILITY_ABSTRACT
-/obj/screen/ling/sting
+/atom/movable/screen/ling/sting
name = "current sting"
screen_loc = ui_lingstingdisplay
-/obj/screen/ling/sting/Click()
+/atom/movable/screen/ling/sting/Click()
if(isobserver(usr))
return
var/mob/living/carbon/U = usr
U.unset_sting()
-/obj/screen/ling/chems
+/atom/movable/screen/ling/chems
name = "chemical storage"
icon_state = "power_display"
screen_loc = ui_lingchemdisplay
/datum/hud/human/New(mob/living/carbon/human/owner)
..()
- owner.overlay_fullscreen("see_through_darkness", /obj/screen/fullscreen/see_through_darkness)
+ owner.overlay_fullscreen("see_through_darkness", /atom/movable/screen/fullscreen/see_through_darkness)
- var/obj/screen/using
- var/obj/screen/inventory/inv_box
+ var/atom/movable/screen/using
+ var/atom/movable/screen/inventory/inv_box
- using = new/obj/screen/language_menu
+ using = new/atom/movable/screen/language_menu
using.icon = ui_style
static_inventory += using
- using = new /obj/screen/area_creator
+ using = new /atom/movable/screen/area_creator
using.icon = ui_style
static_inventory += using
- action_intent = new /obj/screen/act_intent/segmented
+ action_intent = new /atom/movable/screen/act_intent/segmented
action_intent.icon_state = mymob.a_intent
action_intent.hud = src
static_inventory += action_intent
- using = new /obj/screen/mov_intent
+ using = new /atom/movable/screen/mov_intent
using.icon = ui_style
using.icon_state = (mymob.m_intent == MOVE_INTENT_RUN ? "running" : "walking")
using.screen_loc = ui_movi
using.hud = src
static_inventory += using
- using = new /obj/screen/drop()
+ using = new /atom/movable/screen/drop()
using.icon = ui_style
using.screen_loc = ui_drop_throw
using.hud = src
static_inventory += using
- inv_box = new /obj/screen/inventory()
+ inv_box = new /atom/movable/screen/inventory()
inv_box.name = "i_clothing"
inv_box.icon = ui_style
- inv_box.slot_id = SLOT_W_UNIFORM
+ inv_box.slot_id = ITEM_SLOT_ICLOTHING
inv_box.icon_state = "uniform"
inv_box.screen_loc = ui_iclothing
inv_box.hud = src
toggleable_inventory += inv_box
- inv_box = new /obj/screen/inventory()
+ inv_box = new /atom/movable/screen/inventory()
inv_box.name = "o_clothing"
inv_box.icon = ui_style
- inv_box.slot_id = SLOT_WEAR_SUIT
+ inv_box.slot_id = ITEM_SLOT_OCLOTHING
inv_box.icon_state = "suit"
inv_box.screen_loc = ui_oclothing
inv_box.hud = src
@@ -133,209 +133,209 @@
build_hand_slots()
- using = new /obj/screen/swap_hand()
+ using = new /atom/movable/screen/swap_hand()
using.icon = ui_style
using.icon_state = "swap_1"
using.screen_loc = ui_swaphand_position(owner,1)
using.hud = src
static_inventory += using
- using = new /obj/screen/swap_hand()
+ using = new /atom/movable/screen/swap_hand()
using.icon = ui_style
using.icon_state = "swap_2"
using.screen_loc = ui_swaphand_position(owner,2)
using.hud = src
static_inventory += using
- inv_box = new /obj/screen/inventory()
+ inv_box = new /atom/movable/screen/inventory()
inv_box.name = "id"
inv_box.icon = ui_style
inv_box.icon_state = "id"
inv_box.screen_loc = ui_id
- inv_box.slot_id = SLOT_WEAR_ID
+ inv_box.slot_id = ITEM_SLOT_ID
inv_box.hud = src
static_inventory += inv_box
- inv_box = new /obj/screen/inventory()
+ inv_box = new /atom/movable/screen/inventory()
inv_box.name = "mask"
inv_box.icon = ui_style
inv_box.icon_state = "mask"
inv_box.screen_loc = ui_mask
- inv_box.slot_id = SLOT_WEAR_MASK
+ inv_box.slot_id = ITEM_SLOT_MASK
inv_box.hud = src
toggleable_inventory += inv_box
- inv_box = new /obj/screen/inventory()
+ inv_box = new /atom/movable/screen/inventory()
inv_box.name = "neck"
inv_box.icon = ui_style
inv_box.icon_state = "neck"
inv_box.screen_loc = ui_neck
- inv_box.slot_id = SLOT_NECK
+ inv_box.slot_id = ITEM_SLOT_NECK
inv_box.hud = src
toggleable_inventory += inv_box
- inv_box = new /obj/screen/inventory()
+ inv_box = new /atom/movable/screen/inventory()
inv_box.name = "back"
inv_box.icon = ui_style
inv_box.icon_state = "back"
inv_box.screen_loc = ui_back
- inv_box.slot_id = SLOT_BACK
+ inv_box.slot_id = ITEM_SLOT_BACK
inv_box.hud = src
static_inventory += inv_box
- inv_box = new /obj/screen/inventory()
+ inv_box = new /atom/movable/screen/inventory()
inv_box.name = "storage1"
inv_box.icon = ui_style
inv_box.icon_state = "pocket"
inv_box.screen_loc = ui_storage1
- inv_box.slot_id = SLOT_L_STORE
+ inv_box.slot_id = ITEM_SLOT_LPOCKET
inv_box.hud = src
static_inventory += inv_box
- inv_box = new /obj/screen/inventory()
+ inv_box = new /atom/movable/screen/inventory()
inv_box.name = "storage2"
inv_box.icon = ui_style
inv_box.icon_state = "pocket"
inv_box.screen_loc = ui_storage2
- inv_box.slot_id = SLOT_R_STORE
+ inv_box.slot_id = ITEM_SLOT_RPOCKET
inv_box.hud = src
static_inventory += inv_box
- inv_box = new /obj/screen/inventory()
+ inv_box = new /atom/movable/screen/inventory()
inv_box.name = "suit storage"
inv_box.icon = ui_style
inv_box.icon_state = "suit_storage"
inv_box.screen_loc = ui_sstore1
- inv_box.slot_id = SLOT_S_STORE
+ inv_box.slot_id = ITEM_SLOT_SUITSTORE
inv_box.hud = src
static_inventory += inv_box
- using = new /obj/screen/resist()
+ using = new /atom/movable/screen/resist()
using.icon = ui_style
using.screen_loc = ui_above_intent
using.hud = src
hotkeybuttons += using
- using = new /obj/screen/human/toggle()
+ using = new /atom/movable/screen/human/toggle()
using.icon = ui_style
using.screen_loc = ui_inventory
using.hud = src
static_inventory += using
- using = new /obj/screen/human/equip()
+ using = new /atom/movable/screen/human/equip()
using.icon = ui_style
using.screen_loc = ui_equip_position(mymob)
using.hud = src
static_inventory += using
- inv_box = new /obj/screen/inventory()
+ inv_box = new /atom/movable/screen/inventory()
inv_box.name = "gloves"
inv_box.icon = ui_style
inv_box.icon_state = "gloves"
inv_box.screen_loc = ui_gloves
- inv_box.slot_id = SLOT_GLOVES
+ inv_box.slot_id = ITEM_SLOT_GLOVES
inv_box.hud = src
toggleable_inventory += inv_box
- inv_box = new /obj/screen/inventory()
+ inv_box = new /atom/movable/screen/inventory()
inv_box.name = "eyes"
inv_box.icon = ui_style
inv_box.icon_state = "glasses"
inv_box.screen_loc = ui_glasses
- inv_box.slot_id = SLOT_GLASSES
+ inv_box.slot_id = ITEM_SLOT_EYES
inv_box.hud = src
toggleable_inventory += inv_box
- inv_box = new /obj/screen/inventory()
+ inv_box = new /atom/movable/screen/inventory()
inv_box.name = "ears"
inv_box.icon = ui_style
inv_box.icon_state = "ears"
inv_box.screen_loc = ui_ears
- inv_box.slot_id = SLOT_EARS
+ inv_box.slot_id = ITEM_SLOT_EARS
inv_box.hud = src
toggleable_inventory += inv_box
- inv_box = new /obj/screen/inventory()
+ inv_box = new /atom/movable/screen/inventory()
inv_box.name = "head"
inv_box.icon = ui_style
inv_box.icon_state = "head"
inv_box.screen_loc = ui_head
- inv_box.slot_id = SLOT_HEAD
+ inv_box.slot_id = ITEM_SLOT_HEAD
inv_box.hud = src
toggleable_inventory += inv_box
- inv_box = new /obj/screen/inventory()
+ inv_box = new /atom/movable/screen/inventory()
inv_box.name = "shoes"
inv_box.icon = ui_style
inv_box.icon_state = "shoes"
inv_box.screen_loc = ui_shoes
- inv_box.slot_id = SLOT_SHOES
+ inv_box.slot_id = ITEM_SLOT_FEET
inv_box.hud = src
toggleable_inventory += inv_box
- inv_box = new /obj/screen/inventory()
+ inv_box = new /atom/movable/screen/inventory()
inv_box.name = "belt"
inv_box.icon = ui_style
inv_box.icon_state = "belt"
// inv_box.icon_full = "template_small"
inv_box.screen_loc = ui_belt
- inv_box.slot_id = SLOT_BELT
+ inv_box.slot_id = ITEM_SLOT_BELT
inv_box.hud = src
static_inventory += inv_box
- throw_icon = new /obj/screen/throw_catch()
+ throw_icon = new /atom/movable/screen/throw_catch()
throw_icon.icon = ui_style
throw_icon.screen_loc = ui_drop_throw
throw_icon.hud = src
hotkeybuttons += throw_icon
- rest_icon = new /obj/screen/rest()
+ rest_icon = new /atom/movable/screen/rest()
rest_icon.icon = ui_style
rest_icon.screen_loc = ui_above_movement
rest_icon.hud = src
static_inventory += rest_icon
- internals = new /obj/screen/internals()
+ internals = new /atom/movable/screen/internals()
internals.hud = src
infodisplay += internals
- healths = new /obj/screen/healths()
+ healths = new /atom/movable/screen/healths()
healths.hud = src
infodisplay += healths
- healthdoll = new /obj/screen/healthdoll()
+ healthdoll = new /atom/movable/screen/healthdoll()
healthdoll.hud = src
infodisplay += healthdoll
- pull_icon = new /obj/screen/pull()
+ pull_icon = new /atom/movable/screen/pull()
pull_icon.icon = ui_style
pull_icon.update_icon()
pull_icon.screen_loc = ui_above_intent
pull_icon.hud = src
static_inventory += pull_icon
- lingchemdisplay = new /obj/screen/ling/chems()
+ lingchemdisplay = new /atom/movable/screen/ling/chems()
lingchemdisplay.hud = src
infodisplay += lingchemdisplay
- lingstingdisplay = new /obj/screen/ling/sting()
+ lingstingdisplay = new /atom/movable/screen/ling/sting()
lingstingdisplay.hud = src
infodisplay += lingstingdisplay
- devilsouldisplay = new /obj/screen/devil/soul_counter
+ devilsouldisplay = new /atom/movable/screen/devil/soul_counter
devilsouldisplay.hud = src
infodisplay += devilsouldisplay
- zone_select = new /obj/screen/zone_sel()
+ zone_select = new /atom/movable/screen/zone_sel()
zone_select.icon = ui_style
zone_select.update_icon()
zone_select.hud = src
static_inventory += zone_select
- for(var/obj/screen/inventory/inv in (static_inventory + toggleable_inventory))
+ for(var/atom/movable/screen/inventory/inv in (static_inventory + toggleable_inventory))
if(inv.slot_id)
inv.hud = src
- inv_slots[inv.slot_id] = inv
+ inv_slots[TOBITSHIFT(inv.slot_id) + 1] = inv
inv.update_icon()
update_locked_slots()
@@ -347,7 +347,7 @@
if(!istype(H) || !H.dna.species)
return
var/datum/species/S = H.dna.species
- for(var/obj/screen/inventory/inv in (static_inventory + toggleable_inventory))
+ for(var/atom/movable/screen/inventory/inv in (static_inventory + toggleable_inventory))
if(inv.slot_id)
if(inv.slot_id in S.no_equip)
inv.alpha = 128
diff --git a/code/_onclick/hud/lavaland_elite.dm b/code/_onclick/hud/lavaland_elite.dm
index 480e0e398e0b1..81cbb4174827e 100644
--- a/code/_onclick/hud/lavaland_elite.dm
+++ b/code/_onclick/hud/lavaland_elite.dm
@@ -3,5 +3,5 @@
/datum/hud/lavaland_elite/New(mob/living/simple_animal/hostile/asteroid/elite)
..()
- healths = new /obj/screen/healths/lavaland_elite()
+ healths = new /atom/movable/screen/healths/lavaland_elite()
infodisplay += healths
diff --git a/code/_onclick/hud/map_popups.dm b/code/_onclick/hud/map_popups.dm
index dc9e255cba931..3766b74dd39b3 100644
--- a/code/_onclick/hud/map_popups.dm
+++ b/code/_onclick/hud/map_popups.dm
@@ -3,11 +3,11 @@
* Assoc list with all the active maps - when a screen obj is added to
* a map, it's put in here as well.
*
- * Format: list( = list(/obj/screen))
+ * Format: list( = list(/atom/movable/screen))
*/
var/list/screen_maps = list()
-/obj/screen
+/atom/movable/screen
/**
* Map name assigned to this object.
* Automatically set by /client/proc/add_obj_to_map.
@@ -26,7 +26,7 @@
* A screen object, which acts as a container for turfs and other things
* you want to show on the map, which you usually attach to "vis_contents".
*/
-/obj/screen/map_view
+/atom/movable/screen/map_view
// Map view has to be on the lowest plane to enable proper lighting
layer = GAME_PLANE
plane = GAME_PLANE
@@ -36,7 +36,7 @@
* It is also implicitly used to allocate a rectangle on the map, which will
* be used for auto-scaling the map.
*/
-/obj/screen/background
+/atom/movable/screen/background
name = "background"
icon = 'icons/mob/map_backgrounds.dmi'
icon_state = "clear"
@@ -49,7 +49,7 @@
*
* If applicable, "assigned_map" has to be assigned before this proc call.
*/
-/obj/screen/proc/set_position(x, y, px = 0, py = 0)
+/atom/movable/screen/proc/set_position(x, y, px = 0, py = 0)
if(assigned_map)
screen_loc = "[assigned_map]:[x]:[px],[y]:[py]"
else
@@ -60,7 +60,7 @@
*
* If applicable, "assigned_map" has to be assigned before this proc call.
*/
-/obj/screen/proc/fill_rect(x1, y1, x2, y2)
+/atom/movable/screen/proc/fill_rect(x1, y1, x2, y2)
if(assigned_map)
screen_loc = "[assigned_map]:[x1],[y1] to [x2],[y2]"
else
@@ -70,7 +70,7 @@
* Registers screen obj with the client, which makes it visible on the
* assigned map, and becomes a part of the assigned map's lifecycle.
*/
-/client/proc/register_map_obj(obj/screen/screen_obj)
+/client/proc/register_map_obj(atom/movable/screen/screen_obj)
if(!screen_obj.assigned_map)
CRASH("Can't register [screen_obj] without 'assigned_map' property.")
if(!screen_maps[screen_obj.assigned_map])
@@ -92,7 +92,7 @@
/client/proc/clear_map(map_name)
if(!map_name || !(map_name in screen_maps))
return FALSE
- for(var/obj/screen/screen_obj in screen_maps[map_name])
+ for(var/atom/movable/screen/screen_obj in screen_maps[map_name])
screen_maps[map_name] -= screen_obj
if(screen_obj.del_on_map_removal)
qdel(screen_obj)
@@ -137,8 +137,7 @@
*
* Width and height are multiplied by 64 by default.
*/
-/client/proc/setup_popup(popup_name, width = 9, height = 9, \
- tilesize = 2, bg_icon)
+/client/proc/setup_popup(popup_name, width = 9, height = 9, tilesize = 2, bg_icon)
if(!popup_name)
return
clear_map("[popup_name]_map")
@@ -146,7 +145,7 @@
var/y_value = world.icon_size * tilesize * height
var/map_name = create_popup(popup_name, x_value, y_value)
- var/obj/screen/background/background = new
+ var/atom/movable/screen/background/background = new
background.assigned_map = map_name
background.fill_rect(1, 1, width, height)
if(bg_icon)
diff --git a/code/_onclick/hud/monkey.dm b/code/_onclick/hud/monkey.dm
index cb839700b31b7..36239216ca0cc 100644
--- a/code/_onclick/hud/monkey.dm
+++ b/code/_onclick/hud/monkey.dm
@@ -1,28 +1,28 @@
/datum/hud/monkey/New(mob/living/carbon/monkey/owner)
..()
- var/obj/screen/using
- var/obj/screen/inventory/inv_box
+ var/atom/movable/screen/using
+ var/atom/movable/screen/inventory/inv_box
- action_intent = new /obj/screen/act_intent()
+ action_intent = new /atom/movable/screen/act_intent()
action_intent.icon = ui_style
action_intent.icon_state = mymob.a_intent
action_intent.screen_loc = ui_acti
action_intent.hud = src
static_inventory += action_intent
- using = new /obj/screen/mov_intent()
+ using = new /atom/movable/screen/mov_intent()
using.icon = ui_style
using.icon_state = (mymob.m_intent == MOVE_INTENT_RUN ? "running" : "walking")
using.screen_loc = ui_movi
using.hud = src
static_inventory += using
- using = new/obj/screen/language_menu
+ using = new/atom/movable/screen/language_menu
using.icon = ui_style
using.hud = src
static_inventory += using
- using = new /obj/screen/drop()
+ using = new /atom/movable/screen/drop()
using.icon = ui_style
using.screen_loc = ui_drop_throw
using.hud = src
@@ -30,90 +30,90 @@
build_hand_slots()
- using = new /obj/screen/swap_hand()
+ using = new /atom/movable/screen/swap_hand()
using.icon = ui_style
using.icon_state = "swap_1_m" //extra wide!
using.screen_loc = ui_swaphand_position(owner,1)
using.hud = src
static_inventory += using
- using = new /obj/screen/swap_hand()
+ using = new /atom/movable/screen/swap_hand()
using.icon = ui_style
using.icon_state = "swap_2"
using.screen_loc = ui_swaphand_position(owner,2)
using.hud = src
static_inventory += using
- inv_box = new /obj/screen/inventory()
+ inv_box = new /atom/movable/screen/inventory()
inv_box.name = "mask"
inv_box.icon = ui_style
inv_box.icon_state = "mask"
// inv_box.icon_full = "template"
inv_box.screen_loc = ui_monkey_mask
- inv_box.slot_id = SLOT_WEAR_MASK
+ inv_box.slot_id = ITEM_SLOT_MASK
inv_box.hud = src
static_inventory += inv_box
- inv_box = new /obj/screen/inventory()
+ inv_box = new /atom/movable/screen/inventory()
inv_box.name = "neck"
inv_box.icon = ui_style
inv_box.icon_state = "neck"
// inv_box.icon_full = "template"
inv_box.screen_loc = ui_monkey_neck
- inv_box.slot_id = SLOT_NECK
+ inv_box.slot_id = ITEM_SLOT_NECK
inv_box.hud = src
static_inventory += inv_box
- inv_box = new /obj/screen/inventory()
+ inv_box = new /atom/movable/screen/inventory()
inv_box.name = "head"
inv_box.icon = ui_style
inv_box.icon_state = "head"
// inv_box.icon_full = "template"
inv_box.screen_loc = ui_monkey_head
- inv_box.slot_id = SLOT_HEAD
+ inv_box.slot_id = ITEM_SLOT_HEAD
inv_box.hud = src
static_inventory += inv_box
- inv_box = new /obj/screen/inventory()
+ inv_box = new /atom/movable/screen/inventory()
inv_box.name = "back"
inv_box.icon = ui_style
inv_box.icon_state = "back"
inv_box.screen_loc = ui_monkey_back
- inv_box.slot_id = SLOT_BACK
+ inv_box.slot_id = ITEM_SLOT_BACK
inv_box.hud = src
static_inventory += inv_box
- throw_icon = new /obj/screen/throw_catch()
+ throw_icon = new /atom/movable/screen/throw_catch()
throw_icon.icon = ui_style
throw_icon.screen_loc = ui_drop_throw
throw_icon.hud = src
hotkeybuttons += throw_icon
- internals = new /obj/screen/internals()
+ internals = new /atom/movable/screen/internals()
internals.hud = src
infodisplay += internals
- healths = new /obj/screen/healths()
+ healths = new /atom/movable/screen/healths()
healths.hud = src
infodisplay += healths
- pull_icon = new /obj/screen/pull()
+ pull_icon = new /atom/movable/screen/pull()
pull_icon.icon = ui_style
pull_icon.update_icon()
pull_icon.screen_loc = ui_above_movement
pull_icon.hud = src
static_inventory += pull_icon
- lingchemdisplay = new /obj/screen/ling/chems()
+ lingchemdisplay = new /atom/movable/screen/ling/chems()
lingchemdisplay.hud = src
infodisplay += lingchemdisplay
- lingstingdisplay = new /obj/screen/ling/sting()
+ lingstingdisplay = new /atom/movable/screen/ling/sting()
lingstingdisplay.hud = src
infodisplay += lingstingdisplay
- zone_select = new /obj/screen/zone_sel()
+ zone_select = new /atom/movable/screen/zone_sel()
zone_select.icon = ui_style
zone_select.update_icon()
zone_select.hud = src
@@ -121,16 +121,16 @@
mymob.client.screen = list()
- using = new /obj/screen/resist()
+ using = new /atom/movable/screen/resist()
using.icon = ui_style
using.screen_loc = ui_above_intent
using.hud = src
hotkeybuttons += using
- for(var/obj/screen/inventory/inv in (static_inventory + toggleable_inventory))
+ for(var/atom/movable/screen/inventory/inv in (static_inventory + toggleable_inventory))
if(inv.slot_id)
inv.hud = src
- inv_slots[inv.slot_id] = inv
+ inv_slots[TOBITSHIFT(inv.slot_id) + 1] = inv
inv.update_icon()
/datum/hud/monkey/persistent_inventory_update()
diff --git a/code/_onclick/hud/movable_screen_objects.dm b/code/_onclick/hud/movable_screen_objects.dm
index 4c333a1ed38ad..1497de934b9b4 100644
--- a/code/_onclick/hud/movable_screen_objects.dm
+++ b/code/_onclick/hud/movable_screen_objects.dm
@@ -8,7 +8,7 @@
//Movable Screen Object
//Not tied to the grid, places it's center where the cursor is
-/obj/screen/movable
+/atom/movable/screen/movable
var/snap2grid = FALSE
var/moved = FALSE
var/locked = FALSE
@@ -18,11 +18,11 @@
//Snap Screen Object
//Tied to the grid, snaps to the nearest turf
-/obj/screen/movable/snap
+/atom/movable/screen/movable/snap
snap2grid = TRUE
-/obj/screen/movable/MouseDrop(over_object, src_location, over_location, src_control, over_control, params)
+/atom/movable/screen/movable/MouseDrop(over_object, src_location, over_location, src_control, over_control, params)
if(locked) //no! I am locked! begone!
return
var/list/PM = params2list(params)
@@ -56,10 +56,10 @@
set category = "Debug"
set name = "Spawn Movable UI Object"
- var/obj/screen/movable/M = new()
+ var/atom/movable/screen/movable/M = new()
M.name = "Movable UI Object"
M.icon_state = "block"
- M.maptext = "Movable"
+ M.maptext = MAPTEXT("Movable")
M.maptext_width = 64
var/screen_l = capped_input(usr,"Where on the screen? (Formatted as 'X,Y' e.g: '1,1' for bottom left)","Spawn Movable UI Object")
@@ -75,10 +75,10 @@
set category = "Debug"
set name = "Spawn Snap UI Object"
- var/obj/screen/movable/snap/S = new()
+ var/atom/movable/screen/movable/snap/S = new()
S.name = "Snap UI Object"
S.icon_state = "block"
- S.maptext = "Snap"
+ S.maptext = MAPTEXT("Snap")
S.maptext_width = 64
var/screen_l = capped_input(usr,"Where on the screen? (Formatted as 'X,Y' e.g: '1,1' for bottom left)","Spawn Snap UI Object")
diff --git a/code/_onclick/hud/pai.dm b/code/_onclick/hud/pai.dm
index c045223d98386..e97e88c12dae9 100644
--- a/code/_onclick/hud/pai.dm
+++ b/code/_onclick/hud/pai.dm
@@ -1,10 +1,10 @@
#define PAI_MISSING_SOFTWARE_MESSAGE "You must download the required software to use this."
-/obj/screen/pai
+/atom/movable/screen/pai
icon = 'icons/mob/screen_pai.dmi'
var/required_software
-/obj/screen/pai/Click()
+/atom/movable/screen/pai/Click()
if(isobserver(usr) || usr.incapacitated())
return FALSE
var/mob/living/silicon/pai/pAI = usr
@@ -13,21 +13,21 @@
return FALSE
return TRUE
-/obj/screen/pai/software
+/atom/movable/screen/pai/software
name = "Software Interface"
icon_state = "pai"
-/obj/screen/pai/software/Click()
+/atom/movable/screen/pai/software/Click()
if(!..())
return
var/mob/living/silicon/pai/pAI = usr
pAI.paiInterface()
-/obj/screen/pai/shell
+/atom/movable/screen/pai/shell
name = "Toggle Holoform"
icon_state = "pai_holoform"
-/obj/screen/pai/shell/Click()
+/atom/movable/screen/pai/shell/Click()
if(!..())
return
var/mob/living/silicon/pai/pAI = usr
@@ -36,53 +36,53 @@
else
pAI.fold_out()
-/obj/screen/pai/chassis
+/atom/movable/screen/pai/chassis
name = "Holochassis Appearance Composite"
icon_state = "pai_chassis"
-/obj/screen/pai/chassis/Click()
+/atom/movable/screen/pai/chassis/Click()
if(!..())
return
var/mob/living/silicon/pai/pAI = usr
pAI.choose_chassis()
-/obj/screen/pai/rest
+/atom/movable/screen/pai/rest
name = "Rest"
icon_state = "pai_rest"
-/obj/screen/pai/rest/Click()
+/atom/movable/screen/pai/rest/Click()
if(!..())
return
var/mob/living/silicon/pai/pAI = usr
pAI.lay_down()
-/obj/screen/pai/light
+/atom/movable/screen/pai/light
name = "Toggle Integrated Lights"
icon_state = "light"
-/obj/screen/pai/light/Click()
+/atom/movable/screen/pai/light/Click()
if(!..())
return
var/mob/living/silicon/pai/pAI = usr
pAI.toggle_integrated_light()
-/obj/screen/pai/newscaster
+/atom/movable/screen/pai/newscaster
name = "pAI Newscaster"
icon_state = "newscaster"
required_software = "newscaster"
-/obj/screen/pai/newscaster/Click()
+/atom/movable/screen/pai/newscaster/Click()
if(!..())
return
var/mob/living/silicon/pai/pAI = usr
pAI.newscaster.ui_interact(usr)
-/obj/screen/pai/host_monitor
+/atom/movable/screen/pai/host_monitor
name = "Host Health Scan"
icon_state = "host_monitor"
required_software = "host scan"
-/obj/screen/pai/host_monitor/Click()
+/atom/movable/screen/pai/host_monitor/Click()
if(!..())
return
var/mob/living/silicon/pai/pAI = usr
@@ -92,77 +92,77 @@
to_chat(src, "You are not being carried by anyone!")
return 0
-/obj/screen/pai/crew_manifest
+/atom/movable/screen/pai/crew_manifest
name = "Crew Manifest"
icon_state = "manifest"
required_software = "crew manifest"
-/obj/screen/pai/crew_manifest/Click()
+/atom/movable/screen/pai/crew_manifest/Click()
if(!..())
return
var/mob/living/silicon/pai/pAI = usr
pAI.ai_roster()
-/obj/screen/pai/state_laws
+/atom/movable/screen/pai/state_laws
name = "State Laws"
icon_state = "state_laws"
-/obj/screen/pai/state_laws/Click()
+/atom/movable/screen/pai/state_laws/Click()
if(!..())
return
var/mob/living/silicon/pai/pAI = usr
pAI.checklaws()
-/obj/screen/pai/pda_msg_send
+/atom/movable/screen/pai/pda_msg_send
name = "PDA - Send Message"
icon_state = "pda_send"
required_software = "digital messenger"
-/obj/screen/pai/pda_msg_send/Click()
+/atom/movable/screen/pai/pda_msg_send/Click()
if(!..())
return
var/mob/living/silicon/pai/pAI = usr
pAI.cmd_send_pdamesg(usr)
-/obj/screen/pai/pda_msg_show
+/atom/movable/screen/pai/pda_msg_show
name = "PDA - Show Message Log"
icon_state = "pda_receive"
required_software = "digital messenger"
-/obj/screen/pai/pda_msg_show/Click()
+/atom/movable/screen/pai/pda_msg_show/Click()
if(!..())
return
var/mob/living/silicon/pai/pAI = usr
pAI.cmd_show_message_log(usr)
-/obj/screen/pai/image_take
+/atom/movable/screen/pai/image_take
name = "Take Image"
icon_state = "take_picture"
required_software = "photography module"
-/obj/screen/pai/image_take/Click()
+/atom/movable/screen/pai/image_take/Click()
if(!..())
return
var/mob/living/silicon/pai/pAI = usr
pAI.aicamera.toggle_camera_mode(usr)
-/obj/screen/pai/image_view
+/atom/movable/screen/pai/image_view
name = "View Images"
icon_state = "view_images"
required_software = "photography module"
-/obj/screen/pai/image_view/Click()
+/atom/movable/screen/pai/image_view/Click()
if(!..())
return
var/mob/living/silicon/pai/pAI = usr
pAI.aicamera.viewpictures(usr)
-/obj/screen/pai/radio
+/atom/movable/screen/pai/radio
name = "radio"
icon = 'icons/mob/screen_cyborg.dmi'
icon_state = "radio"
-/obj/screen/pai/radio/Click()
+/atom/movable/screen/pai/radio/Click()
if(!..())
return
var/mob/living/silicon/pai/pAI = usr
@@ -170,80 +170,80 @@
/datum/hud/pai/New(mob/living/silicon/pai/owner)
..()
- var/obj/screen/using
+ var/atom/movable/screen/using
// Software menu
- using = new /obj/screen/pai/software
+ using = new /atom/movable/screen/pai/software
using.screen_loc = ui_pai_software
static_inventory += using
// Holoform
- using = new /obj/screen/pai/shell
+ using = new /atom/movable/screen/pai/shell
using.screen_loc = ui_pai_shell
static_inventory += using
// Chassis Select Menu
- using = new /obj/screen/pai/chassis
+ using = new /atom/movable/screen/pai/chassis
using.screen_loc = ui_pai_chassis
static_inventory += using
// Rest
- using = new /obj/screen/pai/rest
+ using = new /atom/movable/screen/pai/rest
using.screen_loc = ui_pai_rest
static_inventory += using
// Integrated Light
- using = new /obj/screen/pai/light
+ using = new /atom/movable/screen/pai/light
using.screen_loc = ui_pai_light
static_inventory += using
// Newscaster
- using = new /obj/screen/pai/newscaster
+ using = new /atom/movable/screen/pai/newscaster
using.screen_loc = ui_pai_newscaster
static_inventory += using
// Language menu
- using = new /obj/screen/language_menu
+ using = new /atom/movable/screen/language_menu
using.screen_loc = ui_borg_language_menu
static_inventory += using
// Host Monitor
- using = new /obj/screen/pai/host_monitor()
+ using = new /atom/movable/screen/pai/host_monitor()
using.screen_loc = ui_pai_host_monitor
static_inventory += using
// Crew Manifest
- using = new /obj/screen/pai/crew_manifest()
+ using = new /atom/movable/screen/pai/crew_manifest()
using.screen_loc = ui_pai_crew_manifest
static_inventory += using
// Laws
- using = new /obj/screen/pai/state_laws()
+ using = new /atom/movable/screen/pai/state_laws()
using.screen_loc = ui_pai_state_laws
static_inventory += using
// PDA message
- using = new /obj/screen/pai/pda_msg_send()
+ using = new /atom/movable/screen/pai/pda_msg_send()
using.screen_loc = ui_pai_pda_send
static_inventory += using
// PDA log
- using = new /obj/screen/pai/pda_msg_show()
+ using = new /atom/movable/screen/pai/pda_msg_show()
using.screen_loc = ui_pai_pda_log
static_inventory += using
// Take image
- using = new /obj/screen/pai/image_take()
+ using = new /atom/movable/screen/pai/image_take()
using.screen_loc = ui_pai_take_picture
static_inventory += using
// View images
- using = new /obj/screen/pai/image_view()
+ using = new /atom/movable/screen/pai/image_view()
using.screen_loc = ui_pai_view_images
static_inventory += using
// Radio
- using = new /obj/screen/pai/radio()
+ using = new /atom/movable/screen/pai/radio()
using.screen_loc = ui_borg_radio
static_inventory += using
@@ -251,7 +251,7 @@
/datum/hud/pai/proc/update_software_buttons()
var/mob/living/silicon/pai/owner = mymob
- for(var/obj/screen/pai/button in static_inventory)
+ for(var/atom/movable/screen/pai/button in static_inventory)
if(button.required_software)
button.color = owner.software.Find(button.required_software) ? null : "#808080"
diff --git a/code/_onclick/hud/parallax.dm b/code/_onclick/hud/parallax.dm
index ef8434a9c044d..22e78d42708d5 100755
--- a/code/_onclick/hud/parallax.dm
+++ b/code/_onclick/hud/parallax.dm
@@ -19,12 +19,12 @@
if(!length(C.parallax_layers_cached))
C.parallax_layers_cached = list()
- C.parallax_layers_cached += new /obj/screen/parallax_layer/layer_1(null, C.view)
- C.parallax_layers_cached += new /obj/screen/parallax_layer/layer_2(null, C.view)
- C.parallax_layers_cached += new /obj/screen/parallax_layer/planet(null, C.view)
+ C.parallax_layers_cached += new /atom/movable/screen/parallax_layer/layer_1(null, C.view)
+ C.parallax_layers_cached += new /atom/movable/screen/parallax_layer/layer_2(null, C.view)
+ C.parallax_layers_cached += new /atom/movable/screen/parallax_layer/planet(null, C.view)
if(SSparallax.random_layer)
C.parallax_layers_cached += new SSparallax.random_layer
- C.parallax_layers_cached += new /obj/screen/parallax_layer/layer_3(null, C.view)
+ C.parallax_layers_cached += new /atom/movable/screen/parallax_layer/layer_3(null, C.view)
C.parallax_layers = C.parallax_layers_cached.Copy()
@@ -32,9 +32,9 @@
C.parallax_layers.len = C.parallax_layers_max
C.screen |= (C.parallax_layers)
- var/obj/screen/plane_master/PM = screenmob.hud_used.plane_masters["[PLANE_SPACE]"]
+ var/atom/movable/screen/plane_master/PM = screenmob.hud_used.plane_masters["[PLANE_SPACE]"]
if(screenmob != mymob)
- C.screen -= locate(/obj/screen/plane_master/parallax_white) in C.screen
+ C.screen -= locate(/atom/movable/screen/plane_master/parallax_white) in C.screen
C.screen += PM
PM.color = list(
0, 0, 0, 0,
@@ -49,9 +49,9 @@
var/mob/screenmob = viewmob || mymob
var/client/C = screenmob.client
C.screen -= (C.parallax_layers_cached)
- var/obj/screen/plane_master/PM = screenmob.hud_used.plane_masters["[PLANE_SPACE]"]
+ var/atom/movable/screen/plane_master/PM = screenmob.hud_used.plane_masters["[PLANE_SPACE]"]
if(screenmob != mymob)
- C.screen -= locate(/obj/screen/plane_master/parallax_white) in C.screen
+ C.screen -= locate(/atom/movable/screen/plane_master/parallax_white) in C.screen
C.screen += PM
PM.color = initial(PM.color)
C.parallax_layers = null
@@ -102,7 +102,7 @@
if(new_parallax_movedir == FALSE)
var/animate_time = 0
for(var/thing in C.parallax_layers)
- var/obj/screen/parallax_layer/L = thing
+ var/atom/movable/screen/parallax_layer/L = thing
L.icon_state = initial(L.icon_state)
L.update_o(C.view)
var/T = PARALLAX_LOOP_TIME / L.speed
@@ -125,7 +125,7 @@
var/shortesttimer
if(!skip_windups)
for(var/thing in C.parallax_layers)
- var/obj/screen/parallax_layer/L = thing
+ var/atom/movable/screen/parallax_layer/L = thing
var/T = PARALLAX_LOOP_TIME / L.speed
if (isnull(shortesttimer))
@@ -153,7 +153,7 @@
return
C.parallax_animate_timer = FALSE
for(var/thing in C.parallax_layers)
- var/obj/screen/parallax_layer/L = thing
+ var/atom/movable/screen/parallax_layer/L = thing
if (!new_parallax_movedir)
animate(L)
continue
@@ -200,7 +200,7 @@
C.last_parallax_shift = world.time
for(var/thing in C.parallax_layers)
- var/obj/screen/parallax_layer/L = thing
+ var/atom/movable/screen/parallax_layer/L = thing
L.update_status(mymob)
if (L.view_sized != C.view)
L.update_o(C.view)
@@ -235,17 +235,16 @@
/atom/movable/proc/update_parallax_contents()
if(length(client_mobs_in_contents))
- for(var/thing in client_mobs_in_contents)
- var/mob/M = thing
- if(M && M.client && M.hud_used && length(M.client.parallax_layers))
- M.hud_used.update_parallax()
+ for(var/mob/client_mob as anything in client_mobs_in_contents)
+ if(length(client_mob?.client?.parallax_layers) && client_mob.hud_used)
+ client_mob.hud_used.update_parallax()
/mob/proc/update_parallax_teleport() //used for arrivals shuttle
if(client && client.eye && hud_used && length(client.parallax_layers))
var/area/areaobj = get_area(client.eye)
hud_used.set_parallax_movedir(areaobj.parallax_movedir, TRUE)
-/obj/screen/parallax_layer
+/atom/movable/screen/parallax_layer
icon = 'icons/effects/parallax.dmi'
var/speed = 1
var/offset_x = 0
@@ -258,13 +257,13 @@
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-/obj/screen/parallax_layer/Initialize(mapload, view)
+/atom/movable/screen/parallax_layer/Initialize(mapload, view)
. = ..()
if (!view)
view = world.view
update_o(view)
-/obj/screen/parallax_layer/proc/update_o(view)
+/atom/movable/screen/parallax_layer/proc/update_o(view)
if (!view)
view = world.view
@@ -283,51 +282,51 @@
add_overlay(new_overlays)
view_sized = view
-/obj/screen/parallax_layer/proc/update_status(mob/M)
+/atom/movable/screen/parallax_layer/proc/update_status(mob/M)
return
-/obj/screen/parallax_layer/layer_1
+/atom/movable/screen/parallax_layer/layer_1
icon_state = "layer1"
speed = 0.6
layer = 1
-/obj/screen/parallax_layer/layer_2
+/atom/movable/screen/parallax_layer/layer_2
icon_state = "layer2"
speed = 1
layer = 2
-/obj/screen/parallax_layer/layer_3
+/atom/movable/screen/parallax_layer/layer_3
icon_state = "layer3"
speed = 1.4
layer = 3
-/obj/screen/parallax_layer/random
+/atom/movable/screen/parallax_layer/random
blend_mode = BLEND_OVERLAY
speed = 3
layer = 3
-/obj/screen/parallax_layer/random/space_gas
+/atom/movable/screen/parallax_layer/random/space_gas
icon_state = "random_layer1"
-/obj/screen/parallax_layer/random/space_gas/Initialize(mapload, view)
+/atom/movable/screen/parallax_layer/random/space_gas/Initialize(mapload, view)
src.add_atom_colour(SSparallax.random_parallax_color, ADMIN_COLOUR_PRIORITY)
-/obj/screen/parallax_layer/random/asteroids
+/atom/movable/screen/parallax_layer/random/asteroids
icon_state = "random_layer2"
-/obj/screen/parallax_layer/planet
+/atom/movable/screen/parallax_layer/planet
icon_state = "planet"
blend_mode = BLEND_OVERLAY
absolute = TRUE //Status of seperation
speed = 3
layer = 30
-/obj/screen/parallax_layer/planet/update_status(mob/M)
+/atom/movable/screen/parallax_layer/planet/update_status(mob/M)
var/turf/T = get_turf(M)
if(is_station_level(T.z))
invisibility = 0
else
invisibility = INVISIBILITY_ABSTRACT
-/obj/screen/parallax_layer/planet/update_o()
+/atom/movable/screen/parallax_layer/planet/update_o()
return //Shit wont move
diff --git a/code/_onclick/hud/picture_in_picture.dm b/code/_onclick/hud/picture_in_picture.dm
index 0e890871598bf..e5e5cfe8ff4ed 100644
--- a/code/_onclick/hud/picture_in_picture.dm
+++ b/code/_onclick/hud/picture_in_picture.dm
@@ -1,4 +1,4 @@
-/obj/screen/movable/pic_in_pic
+/atom/movable/screen/movable/pic_in_pic
name = "Picture-in-picture"
screen_loc = "CENTER"
plane = FLOOR_PLANE
@@ -7,17 +7,17 @@
var/height = 0
var/list/shown_to = list()
var/list/viewing_turfs = list()
- var/obj/screen/component_button/button_x
- var/obj/screen/component_button/button_expand
- var/obj/screen/component_button/button_shrink
+ var/atom/movable/screen/component_button/button_x
+ var/atom/movable/screen/component_button/button_expand
+ var/atom/movable/screen/component_button/button_shrink
var/mutable_appearance/standard_background
-/obj/screen/movable/pic_in_pic/Initialize()
+/atom/movable/screen/movable/pic_in_pic/Initialize()
. = ..()
make_backgrounds()
-/obj/screen/movable/pic_in_pic/Destroy()
+/atom/movable/screen/movable/pic_in_pic/Destroy()
for(var/C in shown_to)
unshow_to(C)
QDEL_NULL(button_x)
@@ -25,7 +25,7 @@
QDEL_NULL(button_expand)
return ..()
-/obj/screen/movable/pic_in_pic/component_click(obj/screen/component_button/component, params)
+/atom/movable/screen/movable/pic_in_pic/component_click(atom/movable/screen/component_button/component, params)
if(component == button_x)
qdel(src)
else if(component == button_expand)
@@ -33,13 +33,13 @@
else if(component == button_shrink)
set_view_size(width-1, height-1)
-/obj/screen/movable/pic_in_pic/proc/make_backgrounds()
+/atom/movable/screen/movable/pic_in_pic/proc/make_backgrounds()
standard_background = new /mutable_appearance()
standard_background.icon = 'icons/misc/pic_in_pic.dmi'
standard_background.icon_state = "background"
standard_background.layer = SPACE_LAYER
-/obj/screen/movable/pic_in_pic/proc/add_buttons()
+/atom/movable/screen/movable/pic_in_pic/proc/add_buttons()
var/static/mutable_appearance/move_tab
if(!move_tab)
move_tab = new /mutable_appearance()
@@ -54,7 +54,7 @@
add_overlay(move_tab)
if(!button_x)
- button_x = new /obj/screen/component_button(null, src)
+ button_x = new /atom/movable/screen/component_button(null, src)
var/mutable_appearance/MA = new /mutable_appearance()
MA.name = "close"
MA.icon = 'icons/misc/pic_in_pic.dmi'
@@ -67,7 +67,7 @@
vis_contents += button_x
if(!button_expand)
- button_expand = new /obj/screen/component_button(null, src)
+ button_expand = new /atom/movable/screen/component_button(null, src)
var/mutable_appearance/MA = new /mutable_appearance()
MA.name = "expand"
MA.icon = 'icons/misc/pic_in_pic.dmi'
@@ -80,7 +80,7 @@
vis_contents += button_expand
if(!button_shrink)
- button_shrink = new /obj/screen/component_button(null, src)
+ button_shrink = new /atom/movable/screen/component_button(null, src)
var/mutable_appearance/MA = new /mutable_appearance()
MA.name = "shrink"
MA.icon = 'icons/misc/pic_in_pic.dmi'
@@ -92,7 +92,7 @@
button_shrink.transform = M
vis_contents += button_shrink
-/obj/screen/movable/pic_in_pic/proc/add_background()
+/atom/movable/screen/movable/pic_in_pic/proc/add_background()
if((width > 0) && (height > 0))
var/matrix/M = matrix()
M.Scale(width + 0.5, height + 0.5)
@@ -102,7 +102,7 @@
// maximum number of dimensions is 10
-/obj/screen/movable/pic_in_pic/proc/set_view_size(width, height, do_refresh = TRUE)
+/atom/movable/screen/movable/pic_in_pic/proc/set_view_size(width, height, do_refresh = TRUE)
width = clamp(width, 0, 10)
height = clamp(height, 0, 10)
src.width = width
@@ -116,19 +116,19 @@
if(do_refresh)
refresh_view()
-/obj/screen/movable/pic_in_pic/proc/set_view_center(atom/target, do_refresh = TRUE)
+/atom/movable/screen/movable/pic_in_pic/proc/set_view_center(atom/target, do_refresh = TRUE)
center = target
if(do_refresh)
refresh_view()
-/obj/screen/movable/pic_in_pic/proc/refresh_view()
+/atom/movable/screen/movable/pic_in_pic/proc/refresh_view()
vis_contents -= viewing_turfs
if(!width || !height)
return
viewing_turfs = get_visible_turfs()
vis_contents += viewing_turfs
-/obj/screen/movable/pic_in_pic/proc/get_visible_turfs()
+/atom/movable/screen/movable/pic_in_pic/proc/get_visible_turfs()
var/turf/T = get_turf(center)
if(!T)
return list()
@@ -136,12 +136,12 @@
var/turf/upperright = locate(min(world.maxx, lowerleft.x + width - 1), min(world.maxy, lowerleft.y + height - 1), lowerleft.z)
return block(lowerleft, upperright)
-/obj/screen/movable/pic_in_pic/proc/show_to(client/C)
+/atom/movable/screen/movable/pic_in_pic/proc/show_to(client/C)
if(C)
shown_to[C] = 1
C.screen += src
-/obj/screen/movable/pic_in_pic/proc/unshow_to(client/C)
+/atom/movable/screen/movable/pic_in_pic/proc/unshow_to(client/C)
if(C)
shown_to -= C
C.screen -= src
diff --git a/code/_onclick/hud/plane_master.dm b/code/_onclick/hud/plane_master.dm
index fb430ee560f3f..409f211045273 100644
--- a/code/_onclick/hud/plane_master.dm
+++ b/code/_onclick/hud/plane_master.dm
@@ -1,4 +1,4 @@
-/obj/screen/plane_master
+/atom/movable/screen/plane_master
screen_loc = "CENTER"
icon_state = "blank"
appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
@@ -6,84 +6,143 @@
var/show_alpha = 255
var/hide_alpha = 0
-/obj/screen/plane_master/proc/Show(override)
+/atom/movable/screen/plane_master/proc/Show(override)
alpha = override || show_alpha
-/obj/screen/plane_master/proc/Hide(override)
+/atom/movable/screen/plane_master/proc/Hide(override)
alpha = override || hide_alpha
//Why do plane masters need a backdrop sometimes? Read https://secure.byond.com/forum/?post=2141928
//Trust me, you need one. Period. If you don't think you do, you're doing something extremely wrong.
-/obj/screen/plane_master/proc/backdrop(mob/mymob)
+/atom/movable/screen/plane_master/proc/backdrop(mob/mymob)
-/obj/screen/plane_master/openspace
+///Things rendered on "openspace"; holes in multi-z
+/atom/movable/screen/plane_master/openspace
name = "open space plane master"
plane = OPENSPACE_BACKDROP_PLANE
appearance_flags = PLANE_MASTER
blend_mode = BLEND_MULTIPLY
alpha = 255
-/obj/screen/plane_master/openspace/backdrop(mob/mymob)
+/atom/movable/screen/plane_master/openspace/backdrop(mob/mymob)
filters = list()
- filters += filter(type = "drop_shadow", color = "#04080FAA", size = -10)
- filters += filter(type = "drop_shadow", color = "#04080FAA", size = -15)
- filters += filter(type = "drop_shadow", color = "#04080FAA", size = -20)
-
-/obj/screen/plane_master/proc/outline(_size, _color)
- filters += filter(type = "outline", size = _size, color = _color)
-
-/obj/screen/plane_master/proc/shadow(_size, _border, _offset = 0, _x = 0, _y = 0, _color = "#04080FAA")
- filters += filter(type = "drop_shadow", x = _x, y = _y, color = _color, size = _size, offset = _offset)
-
-/obj/screen/plane_master/proc/clear_filters()
- filters = list()
-
-/obj/screen/plane_master/floor
+ add_filter("first_stage_openspace", 1, drop_shadow_filter(color = "#04080FAA", size = -10))
+ add_filter("second_stage_openspace", 2, drop_shadow_filter(color = "#04080FAA", size = -15))
+ add_filter("third_stage_openspace", 2, drop_shadow_filter(color = "#04080FAA", size = -20))
+
+/atom/movable/screen/plane_master/openspace/Initialize()
+ . = ..()
+ add_filter("first_stage_openspace", 1, drop_shadow_filter(color = "#04080FAA", size = -10))
+ add_filter("second_stage_openspace", 2, drop_shadow_filter(color = "#04080FAA", size = -15))
+ add_filter("third_stage_openspace", 2, drop_shadow_filter(color = "#04080FAA", size = -20))
+
+///Contains just the floor
+/atom/movable/screen/plane_master/floor
name = "floor plane master"
plane = FLOOR_PLANE
appearance_flags = PLANE_MASTER
blend_mode = BLEND_OVERLAY
-/obj/screen/plane_master/floor/backdrop(mob/mymob)
- filters = list()
+/atom/movable/screen/plane_master/floor/backdrop(mob/mymob)
+ clear_filters()
if(istype(mymob) && mymob.eye_blurry)
- filters += GAUSSIAN_BLUR(CLAMP(mymob.eye_blurry*0.1,0.6,3))
+ add_filter("eye_blur", 1, gauss_blur_filter(clamp(mymob.eye_blurry * 0.1, 0.6, 3)))
-/obj/screen/plane_master/game_world
+///Contains most things in the game world
+/atom/movable/screen/plane_master/game_world
name = "game world plane master"
plane = GAME_PLANE
appearance_flags = PLANE_MASTER //should use client color
blend_mode = BLEND_OVERLAY
-/obj/screen/plane_master/game_world/backdrop(mob/mymob)
- filters = list()
+/atom/movable/screen/plane_master/game_world/backdrop(mob/mymob)
+ clear_filters()
if(istype(mymob) && mymob.client && mymob.client.prefs && mymob.client.prefs.ambientocclusion)
- filters += AMBIENT_OCCLUSION
+ add_filter("AO", 1, drop_shadow_filter(x = 0, y = -2, size = 4, color = "#04080FAA"))
if(istype(mymob) && mymob.eye_blurry)
- filters += GAUSSIAN_BLUR(CLAMP(mymob.eye_blurry*0.1,0.6,3))
+ add_filter("eye_blur", 1, gauss_blur_filter(clamp(mymob.eye_blurry * 0.1, 0.6, 3)))
-/obj/screen/plane_master/lighting
+
+///Contains all lighting objects
+/atom/movable/screen/plane_master/lighting
name = "lighting plane master"
plane = LIGHTING_PLANE
blend_mode = BLEND_MULTIPLY
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-/obj/screen/plane_master/parallax
+/atom/movable/screen/plane_master/lighting/Initialize()
+ . = ..()
+ add_filter("emissives", 1, alpha_mask_filter(render_source = EMISSIVE_RENDER_TARGET, flags = MASK_INVERSE))
+ add_filter("unblockable_emissives", 2, alpha_mask_filter(render_source = EMISSIVE_UNBLOCKABLE_RENDER_TARGET, flags = MASK_INVERSE))
+
+/**
+ * Things placed on this mask the lighting plane. Doesn't render directly.
+ *
+ * Gets masked by blocking plane. Use for things that you want blocked by
+ * mobs, items, etc.
+ */
+/atom/movable/screen/plane_master/emissive
+ name = "emissive plane master"
+ plane = EMISSIVE_PLANE
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ render_target = EMISSIVE_RENDER_TARGET
+
+/atom/movable/screen/plane_master/emissive/Initialize()
+ . = ..()
+ add_filter("emissive_block", 1, alpha_mask_filter(render_source = EMISSIVE_BLOCKER_RENDER_TARGET, flags = MASK_INVERSE))
+
+/**
+ * Things placed on this always mask the lighting plane. Doesn't render directly.
+ *
+ * Always masks the light plane, isn't blocked by anything. Use for on mob glows,
+ * magic stuff, etc.
+ */
+
+/atom/movable/screen/plane_master/emissive_unblockable
+ name = "unblockable emissive plane master"
+ plane = EMISSIVE_UNBLOCKABLE_PLANE
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ render_target = EMISSIVE_UNBLOCKABLE_RENDER_TARGET
+
+/**
+ * Things placed on this layer mask the emissive layer. Doesn't render directly
+ *
+ * You really shouldn't be directly using this, use atom helpers instead
+ */
+/atom/movable/screen/plane_master/emissive_unblockable
+ name = "emissive mob plane master"
+ plane = EMISSIVE_BLOCKER_PLANE
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ render_target = EMISSIVE_BLOCKER_RENDER_TARGET
+
+///Contains space parallax
+/atom/movable/screen/plane_master/parallax
name = "parallax plane master"
plane = PLANE_SPACE_PARALLAX
blend_mode = BLEND_MULTIPLY
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-/obj/screen/plane_master/parallax_white
+/atom/movable/screen/plane_master/parallax_white
name = "parallax whitifier plane master"
plane = PLANE_SPACE
-/obj/screen/plane_master/lighting/backdrop(mob/mymob)
- mymob.overlay_fullscreen("lighting_backdrop_lit", /obj/screen/fullscreen/lighting_backdrop/lit)
- mymob.overlay_fullscreen("lighting_backdrop_unlit", /obj/screen/fullscreen/lighting_backdrop/unlit)
+/atom/movable/screen/plane_master/lighting/backdrop(mob/mymob)
+ mymob.overlay_fullscreen("lighting_backdrop_lit", /atom/movable/screen/fullscreen/lighting_backdrop/lit)
+ mymob.overlay_fullscreen("lighting_backdrop_unlit", /atom/movable/screen/fullscreen/lighting_backdrop/unlit)
-/obj/screen/plane_master/camera_static
+/atom/movable/screen/plane_master/camera_static
name = "camera static plane master"
plane = CAMERA_STATIC_PLANE
appearance_flags = PLANE_MASTER
blend_mode = BLEND_OVERLAY
+
+/atom/movable/screen/plane_master/runechat
+ name = "runechat plane master"
+ plane = RUNECHAT_PLANE
+ appearance_flags = PLANE_MASTER
+ blend_mode = BLEND_OVERLAY
+
+/atom/movable/screen/plane_master/runechat/backdrop(mob/mymob)
+ filters = list()
+ if(istype(mymob) && mymob.client?.prefs?.ambientocclusion)
+ add_filter("AO", 1, drop_shadow_filter(x = 0, y = -2, size = 4, color = "#04080FAA"))
diff --git a/code/_onclick/hud/plane_master_controller.dm b/code/_onclick/hud/plane_master_controller.dm
new file mode 100644
index 0000000000000..ec7e84543136d
--- /dev/null
+++ b/code/_onclick/hud/plane_master_controller.dm
@@ -0,0 +1,80 @@
+///Atom that manages and controls multiple planes. It's an atom so we can hook into add_filter etc. Multiple controllers can control one plane.
+/atom/movable/plane_master_controller
+ ///List of planes in this controllers control. Initially this is a normal list, but becomes an assoc list of plane numbers as strings | plane instance
+ var/list/controlled_planes = list()
+ ///hud that owns this controller
+ var/datum/hud/owner_hud
+
+///Ensures that all the planes are correctly in the controlled_planes list.
+/atom/movable/plane_master_controller/New(hud)
+ . = ..()
+ owner_hud = hud
+ var/assoc_controlled_planes = list()
+ for(var/i in controlled_planes)
+ var/atom/movable/screen/plane_master/instance = owner_hud.plane_masters["[i]"]
+ if(!instance) //If we looked for a hud that isn't instanced, just keep going
+ stack_trace("[i] isn't a valid plane master layer for [owner_hud.type], are you sure it exists in the first place?")
+ continue
+ assoc_controlled_planes["[i]"] = instance
+ controlled_planes = assoc_controlled_planes
+
+///Full override so we can just use filterrific
+/atom/movable/plane_master_controller/add_filter(name, priority, list/params)
+ . = ..()
+ for(var/i in controlled_planes)
+ var/atom/movable/screen/plane_master/pm_iterator = controlled_planes[i]
+ pm_iterator.add_filter(name, priority, params)
+
+///Full override so we can just use filterrific
+/atom/movable/plane_master_controller/remove_filter(name_or_names)
+ . = ..()
+ for(var/i in controlled_planes)
+ var/atom/movable/screen/plane_master/pm_iterator = controlled_planes[i]
+ pm_iterator.remove_filter(name_or_names)
+
+/atom/movable/plane_master_controller/update_filters()
+ . = ..()
+ for(var/i in controlled_planes)
+ var/atom/movable/screen/plane_master/pm_iterator = controlled_planes[i]
+ pm_iterator.update_filters()
+
+///Gets all filters for this controllers plane masters
+/atom/movable/plane_master_controller/proc/get_filters(name)
+ . = list()
+ for(var/i in controlled_planes)
+ var/atom/movable/screen/plane_master/pm_iterator = controlled_planes[i]
+ . += pm_iterator.get_filter(name)
+
+///Transitions all filters owned by this plane master controller
+/atom/movable/plane_master_controller/transition_filter(name, time, list/new_params, easing, loop)
+ . = ..()
+ for(var/i in controlled_planes)
+ var/atom/movable/screen/plane_master/pm_iterator = controlled_planes[i]
+ pm_iterator.transition_filter(name, time, new_params, easing, loop)
+
+///Full override so we can just use filterrific
+/atom/movable/plane_master_controller/add_atom_colour(coloration, colour_priority)
+ . = ..()
+ for(var/i in controlled_planes)
+ var/atom/movable/screen/plane_master/pm_iterator = controlled_planes[i]
+ pm_iterator.add_atom_colour(coloration, colour_priority)
+
+
+///Removes an instance of colour_type from the atom's atom_colours list
+/atom/movable/plane_master_controller/remove_atom_colour(colour_priority, coloration)
+ . = ..()
+ for(var/i in controlled_planes)
+ var/atom/movable/screen/plane_master/pm_iterator = controlled_planes[i]
+ pm_iterator.remove_atom_colour(colour_priority, coloration)
+
+
+///Resets the atom's color to null, and then sets it to the highest priority colour available
+/atom/movable/plane_master_controller/update_atom_colour()
+ for(var/i in controlled_planes)
+ var/atom/movable/screen/plane_master/pm_iterator = controlled_planes[i]
+ pm_iterator.update_atom_colour()
+
+
+/atom/movable/plane_master_controller/game
+ name = PLANE_MASTERS_GAME
+ controlled_planes = list(FLOOR_PLANE, GAME_PLANE, LIGHTING_PLANE)
diff --git a/code/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm
index 588af8e41a0ff..53b50f932562e 100644
--- a/code/_onclick/hud/radial.dm
+++ b/code/_onclick/hud/radial.dm
@@ -3,63 +3,83 @@
GLOBAL_LIST_EMPTY(radial_menus)
-/obj/screen/radial
+/atom/movable/screen/radial
icon = 'icons/mob/radial.dmi'
layer = ABOVE_HUD_LAYER
plane = ABOVE_HUD_PLANE
var/datum/radial_menu/parent
-/obj/screen/radial/slice
+/atom/movable/screen/radial/proc/set_parent(new_value)
+ if(parent)
+ UnregisterSignal(parent, COMSIG_PARENT_QDELETING)
+ parent = new_value
+ if(parent)
+ RegisterSignal(parent, COMSIG_PARENT_QDELETING, .proc/handle_parent_del)
+
+/atom/movable/screen/radial/proc/handle_parent_del()
+ SIGNAL_HANDLER
+ set_parent(null)
+
+/atom/movable/screen/radial/slice
icon_state = "radial_slice"
var/choice
var/next_page = FALSE
var/tooltips = FALSE
-/obj/screen/radial/slice/MouseEntered(location, control, params)
+/atom/movable/screen/radial/slice/MouseEntered(location, control, params)
. = ..()
icon_state = "radial_slice_focus"
if(tooltips)
openToolTip(usr, src, params, title = name)
-/obj/screen/radial/slice/MouseExited(location, control, params)
+/atom/movable/screen/radial/slice/MouseExited(location, control, params)
. = ..()
icon_state = "radial_slice"
if(tooltips)
closeToolTip(usr)
-/obj/screen/radial/slice/Click(location, control, params)
+/atom/movable/screen/radial/slice/Click(location, control, params)
if(usr.client == parent.current_user)
if(next_page)
parent.next_page()
else
parent.element_chosen(choice,usr)
-/obj/screen/radial/center
+/atom/movable/screen/radial/center
name = "Close Menu"
icon_state = "radial_center"
-/obj/screen/radial/center/MouseEntered(location, control, params)
+/atom/movable/screen/radial/center/MouseEntered(location, control, params)
. = ..()
icon_state = "radial_center_focus"
-/obj/screen/radial/center/MouseExited(location, control, params)
+/atom/movable/screen/radial/center/MouseExited(location, control, params)
. = ..()
icon_state = "radial_center"
-/obj/screen/radial/center/Click(location, control, params)
+/atom/movable/screen/radial/center/Click(location, control, params)
if(usr.client == parent.current_user)
parent.finished = TRUE
/datum/radial_menu
- var/list/choices = list() //List of choice id's
- var/list/choices_icons = list() //choice_id -> icon
- var/list/choices_values = list() //choice_id -> choice
+ /// List of choice IDs
+ var/list/choices = list()
+
+ /// choice_id -> icon
+ var/list/choices_icons = list()
+
+ /// choice_id -> choice
+ var/list/choices_values = list()
+
+ /// choice_id -> /datum/radial_menu_choice
+ var/list/choice_datums = list()
+
var/list/page_data = list() //list of choices per page
var/selected_choice
- var/list/obj/screen/elements = list()
- var/obj/screen/radial/center/close_button
+ var/list/atom/movable/screen/elements = list()
+ var/atom/movable/screen/radial/center/close_button
var/client/current_user
var/atom/anchor
var/image/menu_holder
@@ -121,9 +141,9 @@ GLOBAL_LIST_EMPTY(radial_menus)
if(elements.len < max_elements)
var/elements_to_add = max_elements - elements.len
for(var/i in 1 to elements_to_add) //Create all elements
- var/obj/screen/radial/slice/new_element = new /obj/screen/radial/slice
+ var/atom/movable/screen/radial/slice/new_element = new /atom/movable/screen/radial/slice
new_element.tooltips = use_tooltips
- new_element.parent = src
+ new_element.set_parent(src)
elements += new_element
var/page = 1
@@ -153,14 +173,14 @@ GLOBAL_LIST_EMPTY(radial_menus)
var/list/page_choices = page_data[current_page]
var/angle_per_element = round(zone / page_choices.len)
for(var/i in 1 to elements.len)
- var/obj/screen/radial/E = elements[i]
+ var/atom/movable/screen/radial/E = elements[i]
var/angle = WRAP(starting_angle + (i - 1) * angle_per_element,0,360)
if(i > page_choices.len)
HideElement(E)
else
SetElement(E,page_choices[i],angle,anim = anim,anim_order = i)
-/datum/radial_menu/proc/HideElement(obj/screen/radial/slice/E)
+/datum/radial_menu/proc/HideElement(atom/movable/screen/radial/slice/E)
E.cut_overlays()
E.alpha = 0
E.name = "None"
@@ -169,7 +189,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
E.choice = null
E.next_page = FALSE
-/datum/radial_menu/proc/SetElement(obj/screen/radial/slice/E,choice_id,angle,anim,anim_order)
+/datum/radial_menu/proc/SetElement(atom/movable/screen/radial/slice/E,choice_id,angle,anim,anim_order)
//Position
var/py = round(cos(angle) * radius) + py_shift
var/px = round(sin(angle) * radius)
@@ -188,6 +208,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
E.alpha = 255
E.mouse_opacity = MOUSE_OPACITY_ICON
E.cut_overlays()
+ E.vis_contents.Cut()
if(choice_id == NEXT_PAGE_ID)
E.name = "Next Page"
E.next_page = TRUE
@@ -203,10 +224,16 @@ GLOBAL_LIST_EMPTY(radial_menus)
E.next_page = FALSE
if(choices_icons[choice_id])
E.add_overlay(choices_icons[choice_id])
+ if (choice_datums[choice_id])
+ var/datum/radial_menu_choice/choice_datum = choice_datums[choice_id]
+ if (choice_datum.info)
+ var/obj/effect/abstract/info/info_button = new(E, choice_datum.info)
+ info_button.layer = ABOVE_HUD_LAYER
+ E.vis_contents += info_button
/datum/radial_menu/New()
close_button = new
- close_button.parent = src
+ close_button.set_parent(src)
/datum/radial_menu/proc/Reset()
choices.Cut()
@@ -231,11 +258,17 @@ GLOBAL_LIST_EMPTY(radial_menus)
var/I = extract_image(new_choices[E])
if(I)
choices_icons[id] = I
+
+ if (istype(new_choices[E], /datum/radial_menu_choice))
+ choice_datums[id] = new_choices[E]
setup_menu(use_tooltips)
+/datum/radial_menu/proc/extract_image(to_extract_from)
+ if (istype(to_extract_from, /datum/radial_menu_choice))
+ var/datum/radial_menu_choice/choice = to_extract_from
+ to_extract_from = choice.image
-/datum/radial_menu/proc/extract_image(E)
- var/mutable_appearance/MA = new /mutable_appearance(E)
+ var/mutable_appearance/MA = new /mutable_appearance(to_extract_from)
if(MA)
MA.layer = ABOVE_HUD_LAYER
MA.appearance_flags |= RESET_TRANSFORM
@@ -309,3 +342,15 @@ GLOBAL_LIST_EMPTY(radial_menus)
qdel(menu)
GLOB.radial_menus -= uniqueid
return answer
+
+/// Can be provided to choices in radial menus if you want to provide more information
+/datum/radial_menu_choice
+ /// Required -- what to display for this button
+ var/image
+
+ /// If provided, will display an info button that will put this text in your chat
+ var/info
+
+/datum/radial_menu_choice/Destroy(force, ...)
+ . = ..()
+ QDEL_NULL(image)
diff --git a/code/_onclick/hud/radial_persistent.dm b/code/_onclick/hud/radial_persistent.dm
index 0b5e8dc3561c0..1c46ecee56d14 100644
--- a/code/_onclick/hud/radial_persistent.dm
+++ b/code/_onclick/hud/radial_persistent.dm
@@ -2,19 +2,19 @@
A derivative of radial menu which persists onscreen until closed and invokes a callback each time an element is clicked
*/
-/obj/screen/radial/persistent/center
+/atom/movable/screen/radial/persistent/center
name = "Close Menu"
icon_state = "radial_center"
-/obj/screen/radial/persistent/center/Click(location, control, params)
+/atom/movable/screen/radial/persistent/center/Click(location, control, params)
if(usr.client == parent.current_user)
parent.element_chosen(null,usr)
-/obj/screen/radial/persistent/center/MouseEntered(location, control, params)
+/atom/movable/screen/radial/persistent/center/MouseEntered(location, control, params)
. = ..()
icon_state = "radial_center_focus"
-/obj/screen/radial/persistent/center/MouseExited(location, control, params)
+/atom/movable/screen/radial/persistent/center/MouseExited(location, control, params)
. = ..()
icon_state = "radial_center"
@@ -25,8 +25,8 @@
var/datum/callback/select_proc_callback
/datum/radial_menu/persistent/New()
- close_button = new /obj/screen/radial/persistent/center
- close_button.parent = src
+ close_button = new /atom/movable/screen/radial/persistent/center
+ close_button.set_parent(src)
/datum/radial_menu/persistent/element_chosen(choice_id,mob/user)
diff --git a/code/_onclick/hud/revenanthud.dm b/code/_onclick/hud/revenanthud.dm
index 93693139411b2..708b1d49154d7 100644
--- a/code/_onclick/hud/revenanthud.dm
+++ b/code/_onclick/hud/revenanthud.dm
@@ -2,6 +2,6 @@
/datum/hud/revenant/New(mob/owner)
..()
- healths = new /obj/screen/healths/revenant()
+ healths = new /atom/movable/screen/healths/revenant()
healths.hud = src
infodisplay += healths
diff --git a/code/_onclick/hud/robot.dm b/code/_onclick/hud/robot.dm
index ec186efc29a00..d64450e416804 100644
--- a/code/_onclick/hud/robot.dm
+++ b/code/_onclick/hud/robot.dm
@@ -1,15 +1,15 @@
-/obj/screen/robot
+/atom/movable/screen/robot
icon = 'icons/mob/screen_cyborg.dmi'
-/obj/screen/robot/module
+/atom/movable/screen/robot/module
name = "cyborg module"
icon_state = "nomod"
-/obj/screen/robot/Click()
+/atom/movable/screen/robot/Click()
if(isobserver(usr))
return 1
-/obj/screen/robot/module/Click()
+/atom/movable/screen/robot/module/Click()
if(..())
return
var/mob/living/silicon/robot/R = usr
@@ -18,71 +18,71 @@
return 1
R.pick_module()
-/obj/screen/robot/module1
+/atom/movable/screen/robot/module1
name = "module1"
icon_state = "inv1"
-/obj/screen/robot/module1/Click()
+/atom/movable/screen/robot/module1/Click()
if(..())
return
var/mob/living/silicon/robot/R = usr
R.toggle_module(1)
-/obj/screen/robot/module2
+/atom/movable/screen/robot/module2
name = "module2"
icon_state = "inv2"
-/obj/screen/robot/module2/Click()
+/atom/movable/screen/robot/module2/Click()
if(..())
return
var/mob/living/silicon/robot/R = usr
R.toggle_module(2)
-/obj/screen/robot/module3
+/atom/movable/screen/robot/module3
name = "module3"
icon_state = "inv3"
-/obj/screen/robot/module3/Click()
+/atom/movable/screen/robot/module3/Click()
if(..())
return
var/mob/living/silicon/robot/R = usr
R.toggle_module(3)
-/obj/screen/robot/radio
+/atom/movable/screen/robot/radio
name = "radio"
icon_state = "radio"
-/obj/screen/robot/radio/Click()
+/atom/movable/screen/robot/radio/Click()
if(..())
return
var/mob/living/silicon/robot/R = usr
R.radio.interact(R)
-/obj/screen/robot/store
+/atom/movable/screen/robot/store
name = "store"
icon_state = "store"
-/obj/screen/robot/store/Click()
+/atom/movable/screen/robot/store/Click()
if(..())
return
var/mob/living/silicon/robot/R = usr
R.uneq_active()
-/obj/screen/robot/lamp
+/atom/movable/screen/robot/lamp
name = "headlamp"
icon_state = "lamp0"
-/obj/screen/robot/lamp/Click()
+/atom/movable/screen/robot/lamp/Click()
if(..())
return
var/mob/living/silicon/robot/R = usr
R.control_headlamp()
-/obj/screen/robot/thrusters
+/atom/movable/screen/robot/thrusters
name = "ion thrusters"
icon_state = "ionpulse0"
-/obj/screen/robot/thrusters/Click()
+/atom/movable/screen/robot/thrusters/Click()
if(..())
return
var/mob/living/silicon/robot/R = usr
@@ -94,32 +94,35 @@
/datum/hud/robot/New(mob/owner)
..()
var/mob/living/silicon/robot/mymobR = mymob
- var/obj/screen/using
- using = new/obj/screen/language_menu
+ mymobR.overlay_fullscreen("see_through_darkness", /atom/movable/screen/fullscreen/see_through_darkness)
+
+ var/atom/movable/screen/using
+
+ using = new/atom/movable/screen/language_menu
using.screen_loc = ui_borg_language_menu
static_inventory += using
//Radio
- using = new /obj/screen/robot/radio()
+ using = new /atom/movable/screen/robot/radio()
using.screen_loc = ui_borg_radio
using.hud = src
static_inventory += using
//Module select
- using = new /obj/screen/robot/module1()
+ using = new /atom/movable/screen/robot/module1()
using.screen_loc = ui_inv1
using.hud = src
static_inventory += using
mymobR.inv1 = using
- using = new /obj/screen/robot/module2()
+ using = new /atom/movable/screen/robot/module2()
using.screen_loc = ui_inv2
using.hud = src
static_inventory += using
mymobR.inv2 = using
- using = new /obj/screen/robot/module3()
+ using = new /atom/movable/screen/robot/module3()
using.screen_loc = ui_inv3
using.hud = src
static_inventory += using
@@ -128,59 +131,59 @@
//End of module select
//Photography stuff
- using = new /obj/screen/ai/image_take()
+ using = new /atom/movable/screen/ai/image_take()
using.screen_loc = ui_borg_camera
using.hud = src
static_inventory += using
- using = new /obj/screen/ai/image_view()
+ using = new /atom/movable/screen/ai/image_view()
using.screen_loc = ui_borg_album
using.hud = src
static_inventory += using
//Sec/Med HUDs
- using = new /obj/screen/ai/sensors()
+ using = new /atom/movable/screen/ai/sensors()
using.screen_loc = ui_borg_sensor
using.hud = src
static_inventory += using
//Headlamp control
- using = new /obj/screen/robot/lamp()
+ using = new /atom/movable/screen/robot/lamp()
using.screen_loc = ui_borg_lamp
using.hud = src
static_inventory += using
mymobR.lamp_button = using
//Thrusters
- using = new /obj/screen/robot/thrusters()
+ using = new /atom/movable/screen/robot/thrusters()
using.screen_loc = ui_borg_thrusters
using.hud = src
static_inventory += using
mymobR.thruster_button = using
//Intent
- action_intent = new /obj/screen/act_intent/robot()
+ action_intent = new /atom/movable/screen/act_intent/robot()
action_intent.icon_state = mymob.a_intent
action_intent.hud = src
static_inventory += action_intent
//Health
- healths = new /obj/screen/healths/robot()
+ healths = new /atom/movable/screen/healths/robot()
healths.hud = src
infodisplay += healths
//Installed Module
- mymobR.hands = new /obj/screen/robot/module()
+ mymobR.hands = new /atom/movable/screen/robot/module()
mymobR.hands.screen_loc = ui_borg_module
mymobR.hands.hud = src
static_inventory += mymobR.hands
//Store
- module_store_icon = new /obj/screen/robot/store()
+ module_store_icon = new /atom/movable/screen/robot/store()
module_store_icon.screen_loc = ui_borg_store
module_store_icon.hud = src
- pull_icon = new /obj/screen/pull()
+ pull_icon = new /atom/movable/screen/pull()
pull_icon.icon = 'icons/mob/screen_cyborg.dmi'
pull_icon.screen_loc = ui_borg_pull
pull_icon.hud = src
@@ -188,7 +191,7 @@
hotkeybuttons += pull_icon
- zone_select = new /obj/screen/zone_sel/robot()
+ zone_select = new /atom/movable/screen/zone_sel/robot()
zone_select.hud = src
zone_select.update_icon()
static_inventory += zone_select
diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm
index 076187fbac334..be353204111eb 100644
--- a/code/_onclick/hud/screen_objects.dm
+++ b/code/_onclick/hud/screen_objects.dm
@@ -6,34 +6,36 @@
They are used with the client/screen list and the screen_loc var.
For more information, see the byond documentation on the screen_loc and screen vars.
*/
-/obj/screen
+/atom/movable/screen
name = ""
icon = 'icons/mob/screen_gen.dmi'
layer = HUD_LAYER
plane = HUD_PLANE
- resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ animate_movement = SLIDE_STEPS
+ speech_span = SPAN_ROBOT
+ vis_flags = VIS_INHERIT_PLANE
appearance_flags = APPEARANCE_UI
- var/obj/master = null //A reference to the object in the slot. Grabs or items, generally.
- var/datum/hud/hud = null // A reference to the owner HUD, if any.
+ /// A reference to the object in the slot. Grabs or items, generally.
+ var/obj/master = null
+ /// A reference to the owner HUD, if any.
+ var/datum/hud/hud = null
-/obj/screen/take_damage()
- return
-/obj/screen/Destroy()
+/atom/movable/screen/Destroy()
master = null
hud = null
return ..()
-/obj/screen/examine(mob/user)
+/atom/movable/screen/examine(mob/user)
return list()
-/obj/screen/orbit()
+/atom/movable/screen/orbit()
return
-/obj/screen/proc/component_click(obj/screen/component_button/component, params)
+/atom/movable/screen/proc/component_click(atom/movable/screen/component_button/component, params)
return
-/obj/screen/text
+/atom/movable/screen/text
icon = null
icon_state = null
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
@@ -41,12 +43,12 @@
maptext_height = 480
maptext_width = 480
-/obj/screen/swap_hand
+/atom/movable/screen/swap_hand
layer = HUD_LAYER
plane = HUD_PLANE
name = "swap hand"
-/obj/screen/swap_hand/Click()
+/atom/movable/screen/swap_hand/Click()
// At this point in client Click() code we have passed the 1/10 sec check and little else
// We don't even know if it's a middle click
if(world.time <= usr.next_move)
@@ -60,19 +62,19 @@
M.swap_hand()
return 1
-/obj/screen/craft
+/atom/movable/screen/craft
name = "crafting menu"
icon = 'icons/mob/screen_midnight.dmi'
icon_state = "craft"
screen_loc = ui_crafting
-/obj/screen/area_creator
+/atom/movable/screen/area_creator
name = "create new area"
icon = 'icons/mob/screen_midnight.dmi'
icon_state = "area_edit"
screen_loc = ui_building
-/obj/screen/area_creator/Click()
+/atom/movable/screen/area_creator/Click()
if(usr.incapacitated() || (isobserver(usr) && !IsAdminGhost(usr)))
return TRUE
var/area/A = get_area(usr)
@@ -81,18 +83,18 @@
return TRUE
create_area(usr)
-/obj/screen/language_menu
+/atom/movable/screen/language_menu
name = "language menu"
icon = 'icons/mob/screen_midnight.dmi'
icon_state = "talk_wheel"
screen_loc = ui_language_menu
-/obj/screen/language_menu/Click()
+/atom/movable/screen/language_menu/Click()
var/mob/M = usr
var/datum/language_holder/H = M.get_language_holder()
H.open_language_menu(usr)
-/obj/screen/inventory
+/atom/movable/screen/inventory
var/slot_id // The indentifier for the slot. It has nothing to do with ID cards.
var/icon_empty // Icon when empty. For now used only by humans.
var/icon_full // Icon when contains an item. For now used only by humans.
@@ -100,7 +102,7 @@
layer = HUD_LAYER
plane = HUD_PLANE
-/obj/screen/inventory/Click(location, control, params)
+/atom/movable/screen/inventory/Click(location, control, params)
// At this point in client Click() code we have passed the 1/10 sec check and little else
// We don't even know if it's a middle click
if(world.time <= usr.next_move)
@@ -122,19 +124,19 @@
usr.update_inv_hands()
return TRUE
-/obj/screen/inventory/MouseEntered()
+/atom/movable/screen/inventory/MouseEntered()
..()
add_overlays()
//Apply the outline affect
add_stored_outline()
-/obj/screen/inventory/MouseExited()
+/atom/movable/screen/inventory/MouseExited()
..()
cut_overlay(object_overlays)
object_overlays.Cut()
remove_stored_outline()
-/obj/screen/inventory/proc/add_stored_outline()
+/atom/movable/screen/inventory/proc/add_stored_outline()
if(hud?.mymob && slot_id)
var/obj/item/inv_item = hud.mymob.get_item_by_slot(slot_id)
if(inv_item)
@@ -143,13 +145,13 @@
else
inv_item.apply_outline()
-/obj/screen/inventory/proc/remove_stored_outline()
+/atom/movable/screen/inventory/proc/remove_stored_outline()
if(hud?.mymob && slot_id)
var/obj/item/inv_item = hud.mymob.get_item_by_slot(slot_id)
if(inv_item)
inv_item.remove_outline()
-/obj/screen/inventory/update_icon_state()
+/atom/movable/screen/inventory/update_icon_state()
if(!icon_empty)
icon_empty = icon_state
@@ -159,7 +161,7 @@
else
icon_state = icon_empty
-/obj/screen/inventory/proc/add_overlays()
+/atom/movable/screen/inventory/proc/add_overlays()
var/mob/user = hud?.mymob
if(!user || !slot_id)
@@ -181,12 +183,12 @@
object_overlays += item_overlay
add_overlay(object_overlays)
-/obj/screen/inventory/hand
+/atom/movable/screen/inventory/hand
var/mutable_appearance/handcuff_overlay
var/static/mutable_appearance/blocked_overlay = mutable_appearance('icons/mob/screen_gen.dmi', "blocked")
var/held_index = 0
-/obj/screen/inventory/hand/update_icon()
+/atom/movable/screen/inventory/hand/update_icon()
. = ..()
if(!handcuff_overlay)
@@ -211,7 +213,7 @@
add_overlay("hand_active")
-/obj/screen/inventory/hand/Click(location, control, params)
+/atom/movable/screen/inventory/hand/Click(location, control, params)
// At this point in client Click() code we have passed the 1/10 sec check and little else
// We don't even know if it's a middle click
var/mob/user = hud?.mymob
@@ -232,41 +234,41 @@
user.swap_hand(held_index)
return TRUE
-/obj/screen/close
+/atom/movable/screen/close
name = "close"
layer = ABOVE_HUD_LAYER
plane = ABOVE_HUD_PLANE
icon_state = "backpack_close"
-/obj/screen/close/Initialize(mapload, new_master)
+/atom/movable/screen/close/Initialize(mapload, new_master)
. = ..()
master = new_master
-/obj/screen/close/Click()
+/atom/movable/screen/close/Click()
var/datum/component/storage/S = master
S.hide_from(usr)
return TRUE
-/obj/screen/drop
+/atom/movable/screen/drop
name = "drop"
icon = 'icons/mob/screen_midnight.dmi'
icon_state = "act_drop"
layer = HUD_LAYER
plane = HUD_PLANE
-/obj/screen/drop/Click()
+/atom/movable/screen/drop/Click()
if(usr.stat == CONSCIOUS)
usr.dropItemToGround(usr.get_active_held_item())
-/obj/screen/act_intent
+/atom/movable/screen/act_intent
name = "intent"
icon_state = "help"
screen_loc = ui_acti
-/obj/screen/act_intent/Click(location, control, params)
+/atom/movable/screen/act_intent/Click(location, control, params)
usr.a_intent_change(INTENT_HOTKEY_RIGHT)
-/obj/screen/act_intent/segmented/Click(location, control, params)
+/atom/movable/screen/act_intent/segmented/Click(location, control, params)
if(usr.client.prefs.toggles & INTENT_STYLE)
var/_x = text2num(params2list(params)["icon-x"])
var/_y = text2num(params2list(params)["icon-y"])
@@ -285,20 +287,20 @@
else
return ..()
-/obj/screen/act_intent/alien
+/atom/movable/screen/act_intent/alien
icon = 'icons/mob/screen_alien.dmi'
screen_loc = ui_movi
-/obj/screen/act_intent/robot
+/atom/movable/screen/act_intent/robot
icon = 'icons/mob/screen_cyborg.dmi'
screen_loc = ui_borg_intents
-/obj/screen/internals
+/atom/movable/screen/internals
name = "toggle internals"
icon_state = "internal0"
screen_loc = ui_internal
-/obj/screen/internals/Click()
+/atom/movable/screen/internals/Click()
if(!iscarbon(usr))
return
var/mob/living/carbon/C = usr
@@ -353,67 +355,67 @@
return
C.update_action_buttons_icon()
-/obj/screen/mov_intent
+/atom/movable/screen/mov_intent
name = "run/walk toggle"
icon = 'icons/mob/screen_midnight.dmi'
icon_state = "running"
-/obj/screen/mov_intent/Click()
+/atom/movable/screen/mov_intent/Click()
toggle(usr)
-/obj/screen/mov_intent/update_icon_state()
+/atom/movable/screen/mov_intent/update_icon_state()
switch(hud?.mymob?.m_intent)
if(MOVE_INTENT_WALK)
icon_state = "walking"
if(MOVE_INTENT_RUN)
icon_state = "running"
-/obj/screen/mov_intent/proc/toggle(mob/user)
+/atom/movable/screen/mov_intent/proc/toggle(mob/user)
if(isobserver(user))
return
user.toggle_move_intent(user)
-/obj/screen/pull
+/atom/movable/screen/pull
name = "stop pulling"
icon = 'icons/mob/screen_midnight.dmi'
icon_state = "pull"
-/obj/screen/pull/Click()
+/atom/movable/screen/pull/Click()
if(isobserver(usr))
return
usr.stop_pulling()
-/obj/screen/pull/update_icon_state()
+/atom/movable/screen/pull/update_icon_state()
if(hud?.mymob?.pulling)
icon_state = "pull"
else
icon_state = "pull0"
-/obj/screen/resist
+/atom/movable/screen/resist
name = "resist"
icon = 'icons/mob/screen_midnight.dmi'
icon_state = "act_resist"
layer = HUD_LAYER
plane = HUD_PLANE
-/obj/screen/resist/Click()
+/atom/movable/screen/resist/Click()
if(isliving(usr))
var/mob/living/L = usr
L.resist()
-/obj/screen/rest
+/atom/movable/screen/rest
name = "rest"
icon = 'icons/mob/screen_midnight.dmi'
icon_state = "act_rest"
layer = HUD_LAYER
plane = HUD_PLANE
-/obj/screen/rest/Click()
+/atom/movable/screen/rest/Click()
if(isliving(usr))
var/mob/living/L = usr
L.lay_down()
-/obj/screen/rest/update_icon_state()
+/atom/movable/screen/rest/update_icon_state()
var/mob/living/user = hud?.mymob
if(!istype(user))
return
@@ -423,18 +425,18 @@
else
icon_state = "act_rest0"
-/obj/screen/storage
+/atom/movable/screen/storage
name = "storage"
icon_state = "block"
screen_loc = "7,7 to 10,8"
layer = HUD_LAYER
plane = HUD_PLANE
-/obj/screen/storage/Initialize(mapload, new_master)
+/atom/movable/screen/storage/Initialize(mapload, new_master)
. = ..()
master = new_master
-/obj/screen/storage/Click(location, control, params)
+/atom/movable/screen/storage/Click(location, control, params)
if(world.time <= usr.next_move)
return TRUE
if(usr.incapacitated())
@@ -447,17 +449,17 @@
master.attackby(null, I, usr, params)
return TRUE
-/obj/screen/throw_catch
+/atom/movable/screen/throw_catch
name = "throw/catch"
icon = 'icons/mob/screen_midnight.dmi'
icon_state = "act_throw_off"
-/obj/screen/throw_catch/Click()
+/atom/movable/screen/throw_catch/Click()
if(iscarbon(usr))
var/mob/living/carbon/C = usr
C.toggle_throw_mode()
-/obj/screen/zone_sel
+/atom/movable/screen/zone_sel
name = "damage zone"
icon_state = "zone_sel"
screen_loc = ui_zonesel
@@ -466,7 +468,7 @@
var/hovering
var/mutable_appearance/selecting_appearance
-/obj/screen/zone_sel/Click(location, control,params)
+/atom/movable/screen/zone_sel/Click(location, control,params)
if(isobserver(usr))
return
@@ -479,10 +481,10 @@
return set_selected_zone(choice, usr)
-/obj/screen/zone_sel/MouseEntered(location, control, params)
+/atom/movable/screen/zone_sel/MouseEntered(location, control, params)
MouseMove(location, control, params)
-/obj/screen/zone_sel/MouseMove(location, control, params)
+/atom/movable/screen/zone_sel/MouseMove(location, control, params)
if(isobserver(usr))
return
@@ -511,12 +513,12 @@
layer = ABOVE_HUD_LAYER
plane = ABOVE_HUD_PLANE
-/obj/screen/zone_sel/MouseExited(location, control, params)
+/atom/movable/screen/zone_sel/MouseExited(location, control, params)
if(!isobserver(usr) && hovering)
vis_contents -= hover_overlays_cache[hovering]
hovering = null
-/obj/screen/zone_sel/proc/get_zone_at(icon_x, icon_y)
+/atom/movable/screen/zone_sel/proc/get_zone_at(icon_x, icon_y)
switch(icon_y)
if(1 to 9) //Legs
switch(icon_x)
@@ -554,7 +556,7 @@
return BODY_ZONE_PRECISE_EYES
return BODY_ZONE_HEAD
-/obj/screen/zone_sel/proc/set_selected_zone(choice, mob/user)
+/atom/movable/screen/zone_sel/proc/set_selected_zone(choice, mob/user)
if(user != hud?.mymob)
return
@@ -564,28 +566,28 @@
return TRUE
-/obj/screen/zone_sel/update_icon()
+/atom/movable/screen/zone_sel/update_icon()
. = ..()
cut_overlay(selecting_appearance)
selecting_appearance = mutable_appearance('icons/mob/screen_gen.dmi', "[selecting]")
add_overlay(selecting_appearance)
hud?.mymob?.zone_selected = selecting
-/obj/screen/zone_sel/alien
+/atom/movable/screen/zone_sel/alien
icon = 'icons/mob/screen_alien.dmi'
-/obj/screen/zone_sel/alien/update_icon()
+/atom/movable/screen/zone_sel/alien/update_icon()
. = ..()
cut_overlay(selecting_appearance)
selecting_appearance = mutable_appearance('icons/mob/screen_alien.dmi', "[selecting]")
add_overlay(selecting_appearance)
hud?.mymob?.zone_selected = selecting
-/obj/screen/zone_sel/robot
+/atom/movable/screen/zone_sel/robot
icon = 'icons/mob/screen_cyborg.dmi'
-/obj/screen/flash
+/atom/movable/screen/flash
name = "flash"
icon_state = "blank"
blend_mode = BLEND_ADD
@@ -593,7 +595,7 @@
layer = FLASH_LAYER
plane = FULLSCREEN_PLANE
-/obj/screen/damageoverlay
+/atom/movable/screen/damageoverlay
icon = 'icons/mob/screen_full.dmi'
icon_state = "oxydamageoverlay0"
name = "dmg"
@@ -603,98 +605,98 @@
layer = UI_DAMAGE_LAYER
plane = FULLSCREEN_PLANE
-/obj/screen/healths
+/atom/movable/screen/healths
name = "health"
icon_state = "health0"
screen_loc = ui_health
-/obj/screen/healths/alien
+/atom/movable/screen/healths/alien
icon = 'icons/mob/screen_alien.dmi'
screen_loc = ui_alien_health
-/obj/screen/healths/robot
+/atom/movable/screen/healths/robot
icon = 'icons/mob/screen_cyborg.dmi'
screen_loc = ui_borg_health
-/obj/screen/healths/blob
+/atom/movable/screen/healths/blob
name = "blob health"
icon_state = "block"
screen_loc = ui_internal
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-/obj/screen/healths/blob/naut
+/atom/movable/screen/healths/blob/naut
name = "health"
icon = 'icons/mob/blob.dmi'
icon_state = "nauthealth"
-/obj/screen/healths/blob/naut/core
+/atom/movable/screen/healths/blob/naut/core
name = "overmind health"
screen_loc = ui_health
icon_state = "corehealth"
-/obj/screen/healths/guardian
+/atom/movable/screen/healths/guardian
name = "summoner health"
icon = 'icons/mob/guardian.dmi'
icon_state = "base"
screen_loc = ui_health
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-/obj/screen/healths/clock
+/atom/movable/screen/healths/clock
icon = 'icons/mob/actions.dmi'
icon_state = "bg_clock"
screen_loc = ui_health
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-/obj/screen/healths/clock/gear
+/atom/movable/screen/healths/clock/gear
icon = 'icons/mob/clockwork_mobs.dmi'
icon_state = "bg_gear"
screen_loc = ui_internal
-/obj/screen/healths/revenant
+/atom/movable/screen/healths/revenant
name = "essence"
icon = 'icons/mob/actions.dmi'
icon_state = "bg_revenant"
screen_loc = ui_health
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-/obj/screen/healths/construct
+/atom/movable/screen/healths/construct
icon = 'icons/mob/screen_construct.dmi'
icon_state = "artificer_health0"
screen_loc = ui_construct_health
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-/obj/screen/healths/slime
+/atom/movable/screen/healths/slime
icon = 'icons/mob/screen_slime.dmi'
icon_state = "slime_health0"
screen_loc = ui_slime_health
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-/obj/screen/healths/lavaland_elite
+/atom/movable/screen/healths/lavaland_elite
icon = 'icons/mob/screen_elite.dmi'
icon_state = "elite_health0"
screen_loc = ui_health
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-/obj/screen/healthdoll
+/atom/movable/screen/healthdoll
name = "health doll"
screen_loc = ui_healthdoll
-/obj/screen/healthdoll/Click()
- if (ishuman(usr))
- var/mob/living/carbon/human/H = usr
- H.check_self_for_injuries()
+/atom/movable/screen/healthdoll/Click()
+ if (iscarbon(usr))
+ var/mob/living/carbon/C = usr
+ C.check_self_for_injuries()
-/obj/screen/mood
+/atom/movable/screen/mood
name = "mood"
icon_state = "mood5"
screen_loc = ui_mood
-/obj/screen/sanity
+/atom/movable/screen/sanity
name = "sanity"
icon_state = "sanity3"
screen_loc = ui_mood
-/obj/screen/splash
+/atom/movable/screen/splash
icon = 'icons/blank_title.png'
icon_state = ""
screen_loc = "1,1"
@@ -702,7 +704,7 @@
plane = SPLASHSCREEN_PLANE
var/client/holder
-/obj/screen/splash/New(client/C, visible, use_previous_title) //TODO: Make this use INITIALIZE_IMMEDIATE, except its not easy
+/atom/movable/screen/splash/New(client/C, visible, use_previous_title) //TODO: Make this use INITIALIZE_IMMEDIATE, except its not easy
. = ..()
holder = C
@@ -721,7 +723,7 @@
holder.screen += src
-/obj/screen/splash/proc/Fade(out, qdel_after = TRUE)
+/atom/movable/screen/splash/proc/Fade(out, qdel_after = TRUE)
if(QDELETED(src))
return
if(out)
@@ -732,20 +734,20 @@
if(qdel_after)
QDEL_IN(src, 30)
-/obj/screen/splash/Destroy()
+/atom/movable/screen/splash/Destroy()
if(holder)
holder.screen -= src
holder = null
return ..()
-/obj/screen/component_button
- var/obj/screen/parent
+/atom/movable/screen/component_button
+ var/atom/movable/screen/parent
-/obj/screen/component_button/Initialize(mapload, obj/screen/parent)
+/atom/movable/screen/component_button/Initialize(mapload, atom/movable/screen/parent)
. = ..()
src.parent = parent
-/obj/screen/component_button/Click(params)
+/atom/movable/screen/component_button/Click(params)
if(parent)
parent.component_click(src, params)
diff --git a/code/_onclick/hud/slime.dm b/code/_onclick/hud/slime.dm
index 44845e0b70aaa..72b759378623a 100644
--- a/code/_onclick/hud/slime.dm
+++ b/code/_onclick/hud/slime.dm
@@ -3,6 +3,6 @@
/datum/hud/slime/New(mob/living/simple_animal/slime/owner)
..()
- healths = new /obj/screen/healths/slime()
+ healths = new /atom/movable/screen/healths/slime()
healths.hud = src
infodisplay += healths
diff --git a/code/_onclick/hud/swarmer.dm b/code/_onclick/hud/swarmer.dm
index 80cc01c3b30de..6aba23fab5c7d 100644
--- a/code/_onclick/hud/swarmer.dm
+++ b/code/_onclick/hud/swarmer.dm
@@ -1,98 +1,98 @@
-/obj/screen/swarmer
+/atom/movable/screen/swarmer
icon = 'icons/mob/swarmer.dmi'
-/obj/screen/swarmer/FabricateTrap
+/atom/movable/screen/swarmer/FabricateTrap
icon_state = "ui_trap"
name = "Create trap (Costs 5 Resources)"
desc = "Creates a trap that will nonlethally shock any non-swarmer that attempts to cross it. (Costs 5 resources)"
-/obj/screen/swarmer/FabricateTrap/Click()
+/atom/movable/screen/swarmer/FabricateTrap/Click()
if(isswarmer(usr))
var/mob/living/simple_animal/hostile/swarmer/S = usr
S.CreateTrap()
-/obj/screen/swarmer/Barricade
+/atom/movable/screen/swarmer/Barricade
icon_state = "ui_barricade"
name = "Create barricade (Costs 5 Resources)"
desc = "Creates a destructible barricade that will stop any non swarmer from passing it. Also allows disabler beams to pass through. (Costs 5 resources)"
-/obj/screen/swarmer/Barricade/Click()
+/atom/movable/screen/swarmer/Barricade/Click()
if(isswarmer(usr))
var/mob/living/simple_animal/hostile/swarmer/S = usr
S.CreateBarricade()
-/obj/screen/swarmer/Replicate
+/atom/movable/screen/swarmer/Replicate
icon_state = "ui_replicate"
name = "Replicate (Costs 50 Resources)"
desc = "Creates another of our kind."
-/obj/screen/swarmer/Replicate/Click()
+/atom/movable/screen/swarmer/Replicate/Click()
if(isswarmer(usr))
var/mob/living/simple_animal/hostile/swarmer/S = usr
S.CreateSwarmer()
-/obj/screen/swarmer/RepairSelf
+/atom/movable/screen/swarmer/RepairSelf
icon_state = "ui_self_repair"
name = "Repair self"
desc = "Repairs damage to our body."
-/obj/screen/swarmer/RepairSelf/Click()
+/atom/movable/screen/swarmer/RepairSelf/Click()
if(isswarmer(usr))
var/mob/living/simple_animal/hostile/swarmer/S = usr
S.RepairSelf()
-/obj/screen/swarmer/ToggleLight
+/atom/movable/screen/swarmer/ToggleLight
icon_state = "ui_light"
name = "Toggle light"
desc = "Toggles our inbuilt light on or off."
-/obj/screen/swarmer/ToggleLight/Click()
+/atom/movable/screen/swarmer/ToggleLight/Click()
if(isswarmer(usr))
var/mob/living/simple_animal/hostile/swarmer/S = usr
S.ToggleLight()
-/obj/screen/swarmer/ContactSwarmers
+/atom/movable/screen/swarmer/ContactSwarmers
icon_state = "ui_contact_swarmers"
name = "Contact swarmers"
desc = "Sends a message to all other swarmers, should they exist."
-/obj/screen/swarmer/ContactSwarmers/Click()
+/atom/movable/screen/swarmer/ContactSwarmers/Click()
if(isswarmer(usr))
var/mob/living/simple_animal/hostile/swarmer/S = usr
S.ContactSwarmers()
/datum/hud/swarmer/New(mob/owner)
..()
- var/obj/screen/using
+ var/atom/movable/screen/using
- using = new /obj/screen/swarmer/FabricateTrap()
+ using = new /atom/movable/screen/swarmer/FabricateTrap()
using.screen_loc = ui_hand_position(2)
using.hud = src
static_inventory += using
- using = new /obj/screen/swarmer/Barricade()
+ using = new /atom/movable/screen/swarmer/Barricade()
using.screen_loc = ui_hand_position(1)
using.hud = src
static_inventory += using
- using = new /obj/screen/swarmer/Replicate()
+ using = new /atom/movable/screen/swarmer/Replicate()
using.screen_loc = ui_zonesel
using.hud = src
static_inventory += using
- using = new /obj/screen/swarmer/RepairSelf()
+ using = new /atom/movable/screen/swarmer/RepairSelf()
using.screen_loc = ui_storage1
using.hud = src
static_inventory += using
- using = new /obj/screen/swarmer/ToggleLight()
+ using = new /atom/movable/screen/swarmer/ToggleLight()
using.screen_loc = ui_back
using.hud = src
static_inventory += using
- using = new /obj/screen/swarmer/ContactSwarmers()
+ using = new /atom/movable/screen/swarmer/ContactSwarmers()
using.screen_loc = ui_inventory
using.hud = src
static_inventory += using
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index d519b259fbcbe..d188dd555c7a9 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -156,9 +156,11 @@
message_hit_area = " in the [hit_area]"
var/attack_message = "[src] is [message_verb][message_hit_area] with [I]!"
var/attack_message_local = "You're [message_verb][message_hit_area] with [I]!"
- if(user in viewers(src, null))
+ if(user in viewers(src))
attack_message = "[user] [message_verb] [src][message_hit_area] with [I]!"
attack_message_local = "[user] [message_verb] you[message_hit_area] with [I]!"
+ if(user == src)
+ attack_message_local = "You [message_verb] yourself[message_hit_area] with [I]!"
visible_message("[attack_message]",\
"[attack_message_local]", null, COMBAT_MESSAGE_RANGE)
return 1
diff --git a/code/_onclick/observer.dm b/code/_onclick/observer.dm
index 5f494e4f2e9bd..deb50ff5b2b90 100644
--- a/code/_onclick/observer.dm
+++ b/code/_onclick/observer.dm
@@ -62,6 +62,7 @@
/mob/living/attack_ghost(mob/dead/observer/user)
if(user.client && user.health_scan)
healthscan(user, src, 1, TRUE)
+ chemscan(user, src, 1, TRUE)
return ..()
// ---------------------------------------
@@ -83,6 +84,12 @@
return ..()
/obj/machinery/teleport/hub/attack_ghost(mob/user)
- if(power_station && power_station.engaged && power_station.teleporter_console && power_station.teleporter_console.target)
- user.forceMove(get_turf(power_station.teleporter_console.target))
- return ..()
+ if(!power_station?.engaged || !power_station.teleporter_console || !power_station.teleporter_console.target_ref)
+ return ..()
+
+ var/atom/target = power_station.teleporter_console.target_ref.resolve()
+ if(!target)
+ power_station.teleporter_console.target_ref = null
+ return ..()
+
+ user.forceMove(get_turf(target))
diff --git a/code/_onclick/other_mobs.dm b/code/_onclick/other_mobs.dm
index 8f2cbf3afde35..9f41b846885ba 100644
--- a/code/_onclick/other_mobs.dm
+++ b/code/_onclick/other_mobs.dm
@@ -106,6 +106,7 @@
A.attack_animal(src)
/atom/proc/attack_animal(mob/user)
+ SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_ANIMAL, user)
return
/mob/living/RestrainedClickOn(atom/A)
@@ -184,8 +185,10 @@
*/
/mob/living/simple_animal/slime/UnarmedAttack(atom/A)
A.attack_slime(src)
+
/atom/proc/attack_slime(mob/user)
return
+
/mob/living/simple_animal/slime/RestrainedClickOn(atom/A)
return
@@ -243,7 +246,7 @@
*/
/mob/living/simple_animal/hostile/UnarmedAttack(atom/A)
- target = A
+ GiveTarget(A)
if(dextrous && !ismob(A))
..()
else
diff --git a/code/_onclick/telekinesis.dm b/code/_onclick/telekinesis.dm
index a802d12af190b..0f1ab65f0ec08 100644
--- a/code/_onclick/telekinesis.dm
+++ b/code/_onclick/telekinesis.dm
@@ -84,6 +84,7 @@
START_PROCESSING(SSfastprocess, src)
/obj/item/tk_grab/Destroy()
+ focus = null
STOP_PROCESSING(SSfastprocess, src)
return ..()
@@ -99,7 +100,7 @@
//stops TK grabs being equipped anywhere but into hands
/obj/item/tk_grab/equipped(mob/user, slot)
- if(slot == SLOT_HANDS)
+ if(slot == ITEM_SLOT_HANDS)
return
qdel(src)
return
@@ -134,7 +135,14 @@
target.attack_self_tk(user)
update_icon()
return
+
+ if(focus.buckled_mobs)
+ to_chat(user, "This object is too heavy to move with something buckled to it!")
+ return
+ if(length(focus.client_mobs_in_contents))
+ to_chat(user, "This object is too heavy to move with something inside of it!")
+ return
if(!isturf(target) && isitem(focus) && target.Adjacent(focus))
apply_focus_overlay()
@@ -143,6 +151,7 @@
if(check_if_focusable(focus))
focus.do_attack_animation(target, null, focus)
else
+
apply_focus_overlay()
focus.throw_at(target, 10, 1,user)
user.changeNext_move(CLICK_CD_MELEE)
diff --git a/code/controllers/admin.dm b/code/controllers/admin.dm
index 19fef285973eb..513f593e4580a 100644
--- a/code/controllers/admin.dm
+++ b/code/controllers/admin.dm
@@ -1,39 +1,3 @@
-// Clickable stat() button.
-/obj/effect/statclick
- name = "Initializing..."
- var/target
-
-INITIALIZE_IMMEDIATE(/obj/effect/statclick)
-
-/obj/effect/statclick/Initialize(mapload, text, target) //Don't port this to Initialize it's too critical
- . = ..()
- name = text
- src.target = target
-
-/obj/effect/statclick/proc/update(text)
- name = text
- return src
-
-/obj/effect/statclick/debug
- var/class
-
-/obj/effect/statclick/debug/Click()
- if(!usr.client.holder || !target)
- return
- if(!class)
- if(istype(target, /datum/controller/subsystem))
- class = "subsystem"
- else if(istype(target, /datum/controller))
- class = "controller"
- else if(istype(target, /datum))
- class = "datum"
- else
- class = "unknown"
-
- usr.client.debug_variables(target)
- message_admins("Admin [key_name_admin(usr)] is debugging the [target] [class].")
-
-
// Debug verbs.
/client/proc/restart_controller(controller in list("Master", "Failsafe"))
set category = "Debug"
diff --git a/code/controllers/configuration/config_entry.dm b/code/controllers/configuration/config_entry.dm
index 274ff20dbb3cf..77e15e42e6662 100644
--- a/code/controllers/configuration/config_entry.dm
+++ b/code/controllers/configuration/config_entry.dm
@@ -143,6 +143,7 @@
config_entry_value = list()
dupes_allowed = TRUE
vv_VAS = FALSE //VAS will not allow things like deleting from lists, it'll just bug horribly.
+ var/case_sensitive = FALSE
var/key_mode
var/value_mode
var/splitter = " "
@@ -162,7 +163,9 @@
var/key_value = null
if(key_pos || value_mode == VALUE_MODE_FLAG)
- key_name = lowertext(copytext(str_val, 1, key_pos))
+ key_name = copytext(str_val, 1, key_pos)
+ if(!case_sensitive)
+ key_name = lowertext(key_name)
if(key_pos)
key_value = copytext(str_val, key_pos + length(str_val[key_pos]))
var/new_key
diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm
index 37966569c2c30..7180c06c869f5 100644
--- a/code/controllers/configuration/configuration.dm
+++ b/code/controllers/configuration/configuration.dm
@@ -24,6 +24,7 @@
var/static/regex/ooc_filter_regex
var/list/fail2topic_whitelisted_ips
+ var/list/protected_cids
/datum/controller/configuration/proc/admin_reload()
if(IsAdminAdvancedProcCall())
@@ -56,7 +57,7 @@
break
loadmaplist(CONFIG_MAPS_FILE)
LoadTopicRateWhitelist()
- LoadMOTD()
+ LoadProtectedIDs()
LoadChatFilter()
if (Master)
@@ -193,15 +194,25 @@
return !(var_name in banned_edits) && ..()
/datum/controller/configuration/stat_entry()
- if(!statclick)
- statclick = new/obj/effect/statclick/debug(null, "Edit", src)
- stat("[name]:", statclick)
+ var/list/tab_data = list()
+ tab_data["[name]"] = list(
+ text="Edit",
+ action = "statClickDebug",
+ params=list(
+ "targetRef" = REF(src),
+ "class"="config",
+ ),
+ type=STAT_BUTTON,
+ )
+ return tab_data
/datum/controller/configuration/proc/Get(entry_type)
var/datum/config_entry/E = entry_type
var/entry_is_abstract = initial(E.abstract_type) == entry_type
if(entry_is_abstract)
CRASH("Tried to retrieve an abstract config_entry: [entry_type]")
+ if(!entries_by_type)
+ CRASH("Tried to retrieve config value before it was loaded or it was nulled.")
E = entries_by_type[entry_type]
if(!E)
CRASH("Missing config entry for [entry_type]!")
@@ -396,6 +407,16 @@
fail2topic_whitelisted_ips[line] = 1
+/datum/controller/configuration/proc/LoadProtectedIDs()
+ var/jsonfile = rustg_file_read("[directory]/protected_cids.json")
+ if(!jsonfile)
+ log_config("Error 404: protected_cids.json not found!")
+ return
+
+ log_config("Loading config file protected_cids.json...")
+
+ protected_cids = json_decode(jsonfile)
+
/datum/controller/configuration/proc/LoadChatFilter()
var/list/in_character_filter = list()
var/list/ooc_filter = list()
diff --git a/code/controllers/configuration/entries/comms.dm b/code/controllers/configuration/entries/comms.dm
index 012c3ec9feb95..5cc9b3efccd52 100644
--- a/code/controllers/configuration/entries/comms.dm
+++ b/code/controllers/configuration/entries/comms.dm
@@ -1,15 +1,26 @@
-/datum/config_entry/string/comms_key
- protection = CONFIG_ENTRY_HIDDEN
+/datum/config_entry/keyed_list/comms_key
+ case_sensitive = TRUE
+ key_mode = KEY_MODE_TEXT
+ value_mode = VALUE_MODE_TEXT
+ protection = CONFIG_ENTRY_HIDDEN | CONFIG_ENTRY_LOCKED
-/datum/config_entry/string/comms_key/ValidateAndSet(str_val)
- return str_val != "default_pwd" && length(str_val) > 6 && ..()
+/datum/config_entry/keyed_list/comms_key/ValidateListEntry(key_name, key_value)
+ return key_value != "comms_token" && ..()
/datum/config_entry/keyed_list/cross_server
+ key_mode = KEY_MODE_TEXT
+ value_mode = VALUE_MODE_TEXT
+ protection = CONFIG_ENTRY_HIDDEN | CONFIG_ENTRY_LOCKED
+
+/datum/config_entry/keyed_list/cross_server/ValidateListEntry(key_name, key_value)
+ return key_name != "byond://address:port" && key_value != "token" && ..()
+
+/datum/config_entry/keyed_list/server_hop
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_TEXT
protection = CONFIG_ENTRY_LOCKED
-/datum/config_entry/keyed_list/cross_server/ValidateAndSet(str_val)
+/datum/config_entry/keyed_list/server_hop/ValidateAndSet(str_val)
. = ..()
if(.)
var/list/newv = list()
@@ -17,12 +28,12 @@
newv[replacetext(I, "+", " ")] = config_entry_value[I]
config_entry_value = newv
-/datum/config_entry/keyed_list/cross_server/ValidateListEntry(key_name, key_value)
- return key_value != "byond:\\address:port" && ..()
+/datum/config_entry/keyed_list/server_hop/ValidateListEntry(key_name, key_value)
+ return key_value != "byond://address:port" && ..()
/datum/config_entry/string/cross_comms_name
/datum/config_entry/string/medal_hub_address
/datum/config_entry/string/medal_hub_password
- protection = CONFIG_ENTRY_HIDDEN
\ No newline at end of file
+ protection = CONFIG_ENTRY_HIDDEN
diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm
index 09a7a0ea068f4..2ff19be30fe6c 100644
--- a/code/controllers/configuration/entries/game_options.dm
+++ b/code/controllers/configuration/entries/game_options.dm
@@ -70,7 +70,7 @@
/datum/config_entry/flag/disable_peaceborg
-/datum/config_entry/flag/economy //money money money money money money money money money money money money
+/datum/config_entry/flag/donator_items // do you need to be a donator to use donator items
/datum/config_entry/number/traitor_scaling_coeff //how much does the amount of players get divided by to determine traitors
config_entry_value = 6
@@ -253,6 +253,8 @@
/datum/config_entry/flag/emojis
+/datum/config_entry/flag/badges
+
/datum/config_entry/keyed_list/multiplicative_movespeed
key_mode = KEY_MODE_TYPE
value_mode = VALUE_MODE_NUM
@@ -372,7 +374,7 @@
min_val = 0
/datum/config_entry/number/space_budget
- config_entry_value = 16
+ config_entry_value = 40
integer = FALSE
min_val = 0
@@ -433,11 +435,8 @@
config_entry_value = 100
/datum/config_entry/number/max_slimes
config_entry_value = 100
-
-//Maximum citation fine
-/datum/config_entry/number/maxfine
- config_entry_value = 1000
- min_val = 0
+/datum/config_entry/number/max_slimeperson_bodies
+ config_entry_value = 10
//Shuttle size limiter
@@ -448,3 +447,13 @@
config_entry_value = 250
/datum/config_entry/flag/restricted_suicide
+
+/datum/config_entry/flag/dynamic_config_enabled
+
+//Default Game Mode
+/datum/config_entry/string/master_mode
+ config_entry_value = "extended"
+
+/datum/config_entry/flag/spare_enforce_coc
+
+/datum/config_entry/flag/station_traits
diff --git a/code/controllers/configuration/entries/game_tweaks.dm b/code/controllers/configuration/entries/game_tweaks.dm
new file mode 100644
index 0000000000000..bdc201fd02390
--- /dev/null
+++ b/code/controllers/configuration/entries/game_tweaks.dm
@@ -0,0 +1,21 @@
+//Maximum citation fine
+/datum/config_entry/number/maxfine
+ config_entry_value = 1000
+ min_val = 0
+
+//Brig timer limits and presets, in minutes
+/datum/config_entry/number/brig_timer_max
+ config_entry_value = 15
+ min_val = 1
+
+/datum/config_entry/number/brig_timer_preset_short
+ config_entry_value = 2
+ min_val = 1
+
+/datum/config_entry/number/brig_timer_preset_med
+ config_entry_value = 3
+ min_val = 1
+
+/datum/config_entry/number/brig_timer_preset_long
+ config_entry_value = 5
+ min_val = 1
diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm
index df43f26a310ac..f668f7c56060c 100644
--- a/code/controllers/configuration/entries/general.dm
+++ b/code/controllers/configuration/entries/general.dm
@@ -327,6 +327,8 @@
/datum/config_entry/flag/panic_bunker // prevents people the server hasn't seen before from connecting
+/datum/config_entry/number/panic_bunker_living // living time in minutes that a player needs to pass the panic bunker
+
/datum/config_entry/string/panic_bunker_message
config_entry_value = "Sorry but the server is currently not accepting connections from never before seen players."
@@ -336,7 +338,7 @@
/datum/config_entry/number/notify_new_player_account_age // how long do we notify admins of a new byond account
min_val = 0
-/datum/config_entry/flag/irc_first_connection_alert // do we notify the irc channel when somebody is connecting for the first time?
+/datum/config_entry/flag/irc_first_connection_alert // do we notify the irc/discord channel when somebody is connecting for the first time?
/datum/config_entry/flag/check_randomizer
@@ -554,3 +556,11 @@
/datum/config_entry/number/ghost_role_cooldown
config_entry_value = 0
min_val = 0
+
+
+// Elasticsearch stuffs
+/datum/config_entry/flag/elasticsearch_metrics_enabled
+
+/datum/config_entry/string/elasticsearch_metrics_endpoint
+
+/datum/config_entry/string/elasticsearch_metrics_apikey
diff --git a/code/controllers/controller.dm b/code/controllers/controller.dm
index 06547d120d528..179e21b2ad40f 100644
--- a/code/controllers/controller.dm
+++ b/code/controllers/controller.dm
@@ -1,7 +1,5 @@
/datum/controller
var/name
- // The object used for the clickable stat() button.
- var/obj/effect/statclick/statclick
/datum/controller/proc/Initialize()
@@ -16,4 +14,4 @@
/datum/controller/proc/Recover()
-/datum/controller/proc/stat_entry()
\ No newline at end of file
+/datum/controller/proc/stat_entry()
diff --git a/code/controllers/failsafe.dm b/code/controllers/failsafe.dm
index 03e2c36c1125f..db85684767383 100644
--- a/code/controllers/failsafe.dm
+++ b/code/controllers/failsafe.dm
@@ -96,7 +96,14 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe)
return defcon
/datum/controller/failsafe/stat_entry()
- if(!statclick)
- statclick = new/obj/effect/statclick/debug(null, "Initializing...", src)
-
- stat("Failsafe Controller:", statclick.update("Defcon: [defcon_pretty()] (Interval: [Failsafe.processing_interval] | Iteration: [Failsafe.master_iteration])"))
+ var/list/tab_data = list()
+ tab_data["Failsafe Controller"] = list(
+ text="Defcon: [defcon_pretty()] (Interval: [Failsafe.processing_interval] | Iteration: [Failsafe.master_iteration])",
+ action = "statClickDebug",
+ params=list(
+ "targetRef" = REF(src),
+ "class"="controller",
+ ),
+ type=STAT_BUTTON,
+ )
+ return tab_data
diff --git a/code/controllers/globals.dm b/code/controllers/globals.dm
index a085b264107d0..fea1fd209efd0 100644
--- a/code/controllers/globals.dm
+++ b/code/controllers/globals.dm
@@ -25,10 +25,17 @@ GLOBAL_REAL(GLOB, /datum/controller/global_vars)
return QDEL_HINT_IWILLGC
/datum/controller/global_vars/stat_entry()
- if(!statclick)
- statclick = new/obj/effect/statclick/debug(null, "Initializing...", src)
-
- stat("Globals:", statclick.update("Edit"))
+ var/list/tab_data = list()
+ tab_data["Globals"] = list(
+ text="Edit",
+ action = "statClickDebug",
+ params=list(
+ "targetRef" = REF(src),
+ "class"="controller",
+ ),
+ type=STAT_BUTTON,
+ )
+ return tab_data
/datum/controller/global_vars/vv_edit_var(var_name, var_value)
if(gvars_datum_protected_varlist[var_name])
diff --git a/code/controllers/master.dm b/code/controllers/master.dm
index 540d1e57535c5..f20856bc01c65 100644
--- a/code/controllers/master.dm
+++ b/code/controllers/master.dm
@@ -35,6 +35,9 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/sleep_delta = 1
+ //only run ticker subsystems for next n ticks.
+ var/skip_ticks = 0
+
var/make_runtime = 0
var/initializations_finished_with_no_players_logged_in //I wonder what this could be?
@@ -67,7 +70,11 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
// Highlander-style: there can only be one! Kill off the old and replace it with the new.
if(!random_seed)
- random_seed = (TEST_RUN_PARAMETER in world.params) ? 29051994 : rand(1, 1e9)
+ #ifdef UNIT_TESTS
+ random_seed = 29051994
+ #else
+ random_seed = rand(1, 1e9)
+ #endif
rand_seed(random_seed)
var/list/_subsystems = list()
@@ -334,7 +341,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
new/datum/controller/failsafe() // (re)Start the failsafe.
//now do the actual stuff
- if (!queue_head || !(iteration % 3))
+ if (!skip_ticks)
var/checking_runlevel = current_runlevel
if(cached_runlevel != checking_runlevel)
//resechedule subsystems
@@ -380,6 +387,9 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
iteration++
last_run = world.time
+ if(skip_ticks)
+ skip_ticks--
+
src.sleep_delta = MC_AVERAGE_FAST(src.sleep_delta, sleep_delta)
current_ticklimit = TICK_LIMIT_RUNNING
if (processing * sleep_delta <= world.tick_lag)
@@ -447,6 +457,10 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
queue_node_flags = queue_node.flags
queue_node_priority = queue_node.queued_priority
+ if(!(queue_node_flags & SS_TICKER) && skip_ticks)
+ queue_node = queue_node.queue_next
+ continue
+
//super special case, subsystems where we can't make them pause mid way through
//if we can't run them this tick (without going over a tick)
//we bump up their priority and attempt to run them next tick
@@ -583,14 +597,23 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
log_world("MC: SoftReset: Finished.")
. = 1
-
+/datum/controller/master/proc/laggy_byond_map_update_incoming()
+ if (!skip_ticks)
+ skip_ticks = 1
/datum/controller/master/stat_entry()
- if(!statclick)
- statclick = new/obj/effect/statclick/debug(null, "Initializing...", src)
-
- stat("Byond:", "(FPS:[world.fps]) (TickCount:[world.time/world.tick_lag]) (TickDrift:[round(Master.tickdrift,1)]([round((Master.tickdrift/(world.time/world.tick_lag))*100,0.1)]%)) (Internal Tick Usage: [round(MAPTICK_LAST_INTERNAL_TICK_USAGE,0.1)]%)")
- stat("Master Controller:", statclick.update("(TickRate:[Master.processing]) (Iteration:[Master.iteration]) (TickLimit: [round(Master.current_ticklimit, 0.1)])"))
+ var/list/tab_data = list()
+ tab_data["Byond"] = GENERATE_STAT_TEXT("(FPS:[world.fps]) (TickCount:[world.time/world.tick_lag]) (TickDrift:[round(Master.tickdrift,1)]([round((Master.tickdrift/(world.time/world.tick_lag))*100,0.1)]%)) (Internal Tick Usage: [round(MAPTICK_LAST_INTERNAL_TICK_USAGE,0.1)]%)")
+ tab_data["Master Controller"] = list(
+ text="(TickRate:[Master.processing]) (Iteration:[Master.iteration]) (TickLimit: [round(Master.current_ticklimit, 0.1)])",
+ action = "statClickDebug",
+ params=list(
+ "targetRef" = REF(src),
+ "class"="controller",
+ ),
+ type=STAT_BUTTON,
+ )
+ return tab_data
/datum/controller/master/StartLoadingMap()
//disallow more than one map to load at once, multithreading it will just cause race conditions
diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm
index ba1cc61413a15..7f57cba79457f 100644
--- a/code/controllers/subsystem.dm
+++ b/code/controllers/subsystem.dm
@@ -2,6 +2,7 @@
/datum/controller/subsystem
// Metadata; you should define these.
name = "fire coderbus" //name of the subsystem
+ var/ss_id = "fire_coderbus_again"
var/init_order = INIT_ORDER_DEFAULT //order of initialization. Higher numbers are initialized first, lower numbers later. Use defines in __DEFINES/subsystems.dm for easy understanding of order.
var/wait = 20 //time to wait (in deciseconds) between each call to fire(). Must be a positive integer.
var/priority = FIRE_PRIORITY_DEFAULT //When mutiple subsystems need to run in the same tick, higher priority subsystems will run first and be given a higher share of the tick before MC_TICK_CHECK triggers a sleep
@@ -161,6 +162,7 @@
//used to initialize the subsystem AFTER the map has loaded
/datum/controller/subsystem/Initialize(start_timeofday)
initialized = TRUE
+ SEND_SIGNAL(src, COMSIG_SUBSYSTEM_POST_INITIALIZE, start_timeofday)
var/time = (REALTIMEOFDAY - start_timeofday) / 10
var/msg = "Initialized [name] subsystem within [time] second[time == 1 ? "" : "s"]!"
testing("[msg]")
@@ -169,10 +171,7 @@
//hook for printing stats to the "MC" statuspanel for admins to see performance and related stats etc.
/datum/controller/subsystem/stat_entry(msg)
- if(!statclick)
- statclick = new/obj/effect/statclick/debug(null, "Initializing...", src)
-
-
+ var/list/tab_data = list()
if(can_fire && !(SS_NO_FIRE & flags))
msg = "[round(cost,1)]ms|[round(tick_usage,1)]%([round(tick_overrun,1)]%)|[round(ticks,0.1)]\t[msg]"
@@ -183,7 +182,16 @@
if (can_fire)
title = "\[[state_letter()]][title]"
- stat(title, statclick.update(msg))
+ tab_data["[title]"] = list(
+ text="[msg]",
+ action = "statClickDebug",
+ params=list(
+ "targetRef" = REF(src),
+ "class"="subsystem",
+ ),
+ type=STAT_BUTTON,
+ )
+ return tab_data
/datum/controller/subsystem/proc/state_letter()
switch (state)
@@ -218,3 +226,18 @@
return 0
. = ..()
+
+/**
+ * Returns the metrics for the subsystem.
+ *
+ * This can be overriden on subtypes for variables that could affect tick usage
+ * Example: ATs on SSair
+ */
+/datum/controller/subsystem/proc/get_metrics()
+ SHOULD_CALL_PARENT(TRUE)
+ // Please dont ever modify this. Youll break existing metrics and that will upset me.
+ var/list/out = list()
+ out["cost"] = cost
+ out["tick_usage"] = tick_usage
+ out["custom"] = list() // Override as needed on child
+ return out
diff --git a/code/controllers/subsystem/acid.dm b/code/controllers/subsystem/acid.dm
index e3c415960bcc0..283c75cfaa75a 100644
--- a/code/controllers/subsystem/acid.dm
+++ b/code/controllers/subsystem/acid.dm
@@ -8,8 +8,13 @@ SUBSYSTEM_DEF(acid)
var/list/processing = list()
/datum/controller/subsystem/acid/stat_entry()
- ..("P:[processing.len]")
+ . = ..("P:[processing.len]")
+/datum/controller/subsystem/acid/get_metrics()
+ . = ..()
+ var/list/cust = list()
+ cust["processing"] = length(processing)
+ .["custom"] = cust
/datum/controller/subsystem/acid/fire(resumed = 0)
if (!resumed)
diff --git a/code/controllers/subsystem/adjacent_air.dm b/code/controllers/subsystem/adjacent_air.dm
index 4254bfb83d87b..de0fa09323240 100644
--- a/code/controllers/subsystem/adjacent_air.dm
+++ b/code/controllers/subsystem/adjacent_air.dm
@@ -5,12 +5,13 @@ SUBSYSTEM_DEF(adjacent_air)
wait = 10
priority = FIRE_PRIORITY_ATMOS_ADJACENCY
var/list/queue = list()
+ var/list/disable_queue = list()
/datum/controller/subsystem/adjacent_air/stat_entry()
#ifdef TESTING
- ..("P:[length(queue)], S:[GLOB.atmos_adjacent_savings[1]], T:[GLOB.atmos_adjacent_savings[2]]")
+ . = ..("P:[length(queue)], S:[GLOB.atmos_adjacent_savings[1]], T:[GLOB.atmos_adjacent_savings[2]]")
#else
- ..("P:[length(queue)]")
+ . = ..("P:[length(queue)]")
#endif
/datum/controller/subsystem/adjacent_air/Initialize()
@@ -19,6 +20,24 @@ SUBSYSTEM_DEF(adjacent_air)
return ..()
/datum/controller/subsystem/adjacent_air/fire(resumed = FALSE, mc_check = TRUE)
+ if(SSair.thread_running())
+ pause()
+ return
+
+ var/list/disable_queue = src.disable_queue
+
+ while (length(disable_queue))
+ var/turf/terf = disable_queue[1]
+ var/arg = disable_queue[terf]
+ disable_queue.Cut(1,2)
+
+ terf.ImmediateDisableAdjacency(arg)
+
+ if(mc_check)
+ if(MC_TICK_CHECK)
+ return
+ else
+ CHECK_TICK
var/list/queue = src.queue
diff --git a/code/controllers/subsystem/air.dm b/code/controllers/subsystem/air.dm
index 54b586b2e0a54..4b354cbe0c8ac 100644
--- a/code/controllers/subsystem/air.dm
+++ b/code/controllers/subsystem/air.dm
@@ -1,93 +1,158 @@
-#define SSAIR_PIPENETS 1
-#define SSAIR_ATMOSMACHINERY 2
-#define SSAIR_EQUALIZE 3
-#define SSAIR_ACTIVETURFS 4
-#define SSAIR_EXCITEDGROUPS 5
-#define SSAIR_HIGHPRESSURE 6
-#define SSAIR_HOTSPOTS 7
-#define SSAIR_SUPERCONDUCTIVITY 8
-
SUBSYSTEM_DEF(air)
name = "Atmospherics"
init_order = INIT_ORDER_AIR
priority = FIRE_PRIORITY_AIR
- wait = 5
+ wait = 0.5 SECONDS
flags = SS_BACKGROUND
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
var/cost_turfs = 0
var/cost_groups = 0
var/cost_highpressure = 0
+ var/cost_deferred_airs
var/cost_hotspots = 0
+ var/cost_post_process = 0
var/cost_superconductivity = 0
var/cost_pipenets = 0
+ var/cost_rebuilds = 0
var/cost_atmos_machinery = 0
var/cost_equalize = 0
+ var/thread_wait_ticks = 0
+ var/cur_thread_wait_ticks = 0
+
+ var/low_pressure_turfs = 0
+ var/high_pressure_turfs = 0
+
+ var/num_group_turfs_processed = 0
+ var/num_equalize_processed = 0
- var/list/active_turfs = list()
var/list/hotspots = list()
var/list/networks = list()
+ var/list/pipenets_needing_rebuilt = list()
+ var/list/deferred_airs = list()
+ var/max_deferred_airs = 0
var/list/obj/machinery/atmos_machinery = list()
+ var/list/obj/machinery/atmos_air_machinery = list()
var/list/pipe_init_dirs_cache = list()
//atmos singletons
var/list/gas_reactions = list()
- var/list/atmos_gen
//Special functions lists
- var/list/turf/active_super_conductivity = list()
var/list/turf/open/high_pressure_delta = list()
var/list/currentrun = list()
- var/currentpart = SSAIR_PIPENETS
+ var/currentpart = SSAIR_REBUILD_PIPENETS
var/map_loading = TRUE
- var/list/queued_for_activation
+
+ var/log_explosive_decompression = TRUE // If things get spammy, admemes can turn this off.
+
+ // Max number of turfs equalization will grab.
+ var/equalize_turf_limit = 10
+ // Max number of turfs to look for a space turf, and max number of turfs that will be decompressed.
+ var/equalize_hard_turf_limit = 2000
+ // Whether equalization should be enabled at all.
+ var/equalize_enabled = FALSE
+ // Whether turf-to-turf heat exchanging should be enabled.
+ var/heat_enabled = FALSE
+ // Max number of times process_turfs will share in a tick.
+ var/share_max_steps = 3
+ // Excited group processing will try to equalize groups with total pressure difference less than this amount.
+ var/excited_group_pressure_goal = 1
+
+ var/list/paused_z_levels //Paused z-levels will not add turfs to active
/datum/controller/subsystem/air/stat_entry(msg)
msg += "C:{"
- msg += "EQ:[round(cost_equalize,1)]|"
- msg += "AT:[round(cost_turfs,1)]|"
- msg += "EG:[round(cost_groups,1)]|"
msg += "HP:[round(cost_highpressure,1)]|"
msg += "HS:[round(cost_hotspots,1)]|"
+ msg += "HE:[round(heat_process_time(),1)]|"
msg += "SC:[round(cost_superconductivity,1)]|"
msg += "PN:[round(cost_pipenets,1)]|"
msg += "AM:[round(cost_atmos_machinery,1)]"
msg += "} "
- msg += "AT:[active_turfs.len]|"
- msg += "EG:[get_amt_excited_groups()]|"
+ msg += "TC:{"
+ msg += "AT:[round(cost_turfs,1)]|"
+ msg += "EG:[round(cost_groups,1)]|"
+ msg += "EQ:[round(cost_equalize,1)]|"
+ msg += "PO:[round(cost_post_process,1)]"
+ msg += "}"
+ msg += "TH:[round(thread_wait_ticks,1)]|"
msg += "HS:[hotspots.len]|"
msg += "PN:[networks.len]|"
msg += "HP:[high_pressure_delta.len]|"
- msg += "AS:[active_super_conductivity.len]|"
- msg += "AT/MS:[round((cost ? active_turfs.len/cost : 0),0.1)]"
- ..(msg)
-
+ msg += "HT:[high_pressure_turfs]|"
+ msg += "LT:[low_pressure_turfs]|"
+ msg += "ET:[num_equalize_processed]|"
+ msg += "GT:[num_group_turfs_processed]|"
+ msg += "DF:[max_deferred_airs]|"
+ msg += "GA:[get_amt_gas_mixes()]|"
+ msg += "MG:[get_max_gas_mixes()]"
+ return ..()
/datum/controller/subsystem/air/Initialize(timeofday)
- extools_update_ssair()
map_loading = FALSE
setup_allturfs()
setup_atmos_machinery()
setup_pipenets()
gas_reactions = init_gas_reactions()
+ auxtools_update_reactions()
return ..()
/datum/controller/subsystem/air/proc/extools_update_ssair()
+/datum/controller/subsystem/air/proc/auxtools_update_reactions()
+
+/proc/reset_all_air()
+ SSair.can_fire = 0
+ message_admins("Air reset begun.")
+ for(var/turf/open/T in world)
+ T.Initalize_Atmos(0)
+ CHECK_TICK
+ message_admins("Air reset done.")
+ SSair.can_fire = 1
+
+/datum/controller/subsystem/air/proc/thread_running()
+ return FALSE
+
+/proc/fix_corrupted_atmos()
+
+/datum/admins/proc/fixcorruption()
+ set category = "Debug"
+ set desc="Fixes air that has weird NaNs (-1.#IND and such). Hopefully."
+ set name="Fix Infinite Air"
+ fix_corrupted_atmos()
+
/datum/controller/subsystem/air/fire(resumed = 0)
+
var/timer = TICK_USAGE_REAL
+ if(currentpart == SSAIR_REBUILD_PIPENETS)
+ timer = TICK_USAGE_REAL
+ var/list/pipenet_rebuilds = pipenets_needing_rebuilt
+ for(var/thing in pipenet_rebuilds)
+ var/obj/machinery/atmospherics/AT = thing
+ if(!istype(AT))
+ continue
+ AT.build_network()
+ cost_rebuilds = MC_AVERAGE(cost_rebuilds, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
+ pipenets_needing_rebuilt.Cut()
+ if(state != SS_RUNNING)
+ return
+ resumed = FALSE
+ currentpart = SSAIR_PIPENETS
+
if(currentpart == SSAIR_PIPENETS || !resumed)
+ timer = TICK_USAGE_REAL
process_pipenets(resumed)
cost_pipenets = MC_AVERAGE(cost_pipenets, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
if(state != SS_RUNNING)
return
resumed = 0
currentpart = SSAIR_ATMOSMACHINERY
-
+ // This is only machinery like filters, mixers that don't interact with air
if(currentpart == SSAIR_ATMOSMACHINERY)
timer = TICK_USAGE_REAL
process_atmos_machinery(resumed)
@@ -95,39 +160,40 @@ SUBSYSTEM_DEF(air)
if(state != SS_RUNNING)
return
resumed = 0
- currentpart = SSAIR_EQUALIZE
+ currentpart = SSAIR_HIGHPRESSURE
- if(currentpart == SSAIR_EQUALIZE)
+ if(currentpart == SSAIR_HIGHPRESSURE)
timer = TICK_USAGE_REAL
- process_turf_equalize(resumed)
- cost_equalize = MC_AVERAGE(cost_equalize, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
+ process_high_pressure_delta(resumed)
+ cost_highpressure = MC_AVERAGE(cost_highpressure, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
if(state != SS_RUNNING)
return
resumed = 0
- currentpart = SSAIR_ACTIVETURFS
-
- if(currentpart == SSAIR_ACTIVETURFS)
- timer = TICK_USAGE_REAL
- process_active_turfs(resumed)
- cost_turfs = MC_AVERAGE(cost_turfs, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
+ currentpart = SSAIR_FINALIZE_TURFS
+ // This literally just waits for the turf processing thread to finish, doesn't do anything else.
+ // this is necessary cause the next step after this interacts with the air--we get consistency
+ // issues if we don't wait for it, disappearing gases etc.
+ if(currentpart == SSAIR_FINALIZE_TURFS)
+ finish_turf_processing(resumed)
if(state != SS_RUNNING)
+ cur_thread_wait_ticks++
return
resumed = 0
- currentpart = SSAIR_EXCITEDGROUPS
-
- if(currentpart == SSAIR_EXCITEDGROUPS)
+ thread_wait_ticks = MC_AVERAGE(thread_wait_ticks, cur_thread_wait_ticks)
+ cur_thread_wait_ticks = 0
+ currentpart = SSAIR_DEFERRED_AIRS
+ if(currentpart == SSAIR_DEFERRED_AIRS)
timer = TICK_USAGE_REAL
- process_excited_groups(resumed)
- cost_groups = MC_AVERAGE(cost_groups, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
+ process_deferred_airs(resumed)
+ cost_deferred_airs = MC_AVERAGE(cost_deferred_airs, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
if(state != SS_RUNNING)
return
resumed = 0
- currentpart = SSAIR_HIGHPRESSURE
-
- if(currentpart == SSAIR_HIGHPRESSURE)
+ currentpart = SSAIR_ATMOSMACHINERY_AIR
+ if(currentpart == SSAIR_ATMOSMACHINERY_AIR)
timer = TICK_USAGE_REAL
- process_high_pressure_delta(resumed)
- cost_highpressure = MC_AVERAGE(cost_highpressure, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
+ process_atmos_air_machinery(resumed)
+ cost_atmos_machinery = MC_AVERAGE(cost_atmos_machinery, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
if(state != SS_RUNNING)
return
resumed = 0
@@ -140,20 +206,60 @@ SUBSYSTEM_DEF(air)
if(state != SS_RUNNING)
return
resumed = 0
- currentpart = SSAIR_SUPERCONDUCTIVITY
-
- if(currentpart == SSAIR_SUPERCONDUCTIVITY)
+ currentpart = heat_enabled ? SSAIR_TURF_CONDUCTION : SSAIR_ACTIVETURFS
+ // Heat -- slow and of questionable usefulness. Off by default for this reason. Pretty cool, though.
+ if(currentpart == SSAIR_TURF_CONDUCTION)
timer = TICK_USAGE_REAL
- process_super_conductivity(resumed)
+ if(process_turf_heat(MC_TICK_REMAINING_MS))
+ pause()
cost_superconductivity = MC_AVERAGE(cost_superconductivity, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
if(state != SS_RUNNING)
return
resumed = 0
- currentpart = SSAIR_PIPENETS
-
+ currentpart = SSAIR_ACTIVETURFS
+ // This simply starts the turf thread. It runs in the background until the FINALIZE_TURFS step, at which point it's waited for.
+ // This also happens to do all the commented out stuff below, all in a single separate thread. This is mostly so that the
+ // waiting is consistent.
+ if(currentpart == SSAIR_ACTIVETURFS)
+ timer = TICK_USAGE_REAL
+ process_turfs(resumed)
+ cost_turfs = MC_AVERAGE(cost_turfs, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
+ if(state != SS_RUNNING)
+ return
+ resumed = 0
+ /*
+ // Monstermos and/or Putnamos--making large pressure deltas move faster
+ if(currentpart == SSAIR_EQUALIZE)
+ timer = TICK_USAGE_REAL
+ process_turf_equalize(resumed)
+ cost_equalize = MC_AVERAGE(cost_equalize, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
+ if(state != SS_RUNNING)
+ return
+ resumed = 0
+ currentpart = SSAIR_EXCITEDGROUPS
+ // Making small pressure deltas equalize immediately so they don't process anymore
+ if(currentpart == SSAIR_EXCITEDGROUPS)
+ timer = TICK_USAGE_REAL
+ process_excited_groups(resumed)
+ cost_groups = MC_AVERAGE(cost_groups, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
+ if(state != SS_RUNNING)
+ return
+ resumed = 0
+ currentpart = SSAIR_TURF_POST_PROCESS
+ // Quick multithreaded "should we display/react?" checks followed by finishing those up before the next step
+ if(currentpart == SSAIR_TURF_POST_PROCESS)
+ timer = TICK_USAGE_REAL
+ post_process_turfs(resumed)
+ cost_post_process = MC_AVERAGE(cost_post_process, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
+ if(state != SS_RUNNING)
+ return
+ resumed = 0
+ currentpart = SSAIR_HOTSPOTS
+ */
+ currentpart = SSAIR_REBUILD_PIPENETS
-/datum/controller/subsystem/air/proc/process_pipenets(resumed = 0)
+/datum/controller/subsystem/air/proc/process_pipenets(resumed = FALSE)
if (!resumed)
src.currentrun = networks.Copy()
//cache for sanic speed (lists are references anyways)
@@ -168,9 +274,36 @@ SUBSYSTEM_DEF(air)
if(MC_TICK_CHECK)
return
+/datum/controller/subsystem/air/proc/add_to_rebuild_queue(atmos_machine)
+ if(istype(atmos_machine, /obj/machinery/atmospherics))
+ pipenets_needing_rebuilt += atmos_machine
+
+/datum/controller/subsystem/air/proc/process_deferred_airs(resumed = 0)
+ max_deferred_airs = max(deferred_airs.len,max_deferred_airs)
+ while(deferred_airs.len)
+ var/list/cur_op = deferred_airs[deferred_airs.len]
+ deferred_airs.len--
+ var/datum/gas_mixture/air1
+ var/datum/gas_mixture/air2
+ if(isopenturf(cur_op[1]))
+ var/turf/open/T = cur_op[1]
+ air1 = T.return_air()
+ else
+ air1 = cur_op[1]
+ if(isopenturf(cur_op[2]))
+ var/turf/open/T = cur_op[2]
+ air2 = T.return_air()
+ else
+ air2 = cur_op[2]
+ if(istype(cur_op[3], /datum/callback))
+ var/datum/callback/cb = cur_op[3]
+ cb.Invoke(air1, air2)
+ else
+ air1.transfer_ratio_to(air2, cur_op[3])
+ if(MC_TICK_CHECK)
+ return
/datum/controller/subsystem/air/proc/process_atmos_machinery(resumed = 0)
- var/seconds = wait * 0.1
if (!resumed)
src.currentrun = atmos_machinery.Copy()
//cache for sanic speed (lists are references anyways)
@@ -178,25 +311,30 @@ SUBSYSTEM_DEF(air)
while(currentrun.len)
var/obj/machinery/M = currentrun[currentrun.len]
currentrun.len--
- if(!M || (M.process_atmos(seconds) == PROCESS_KILL))
+ if(M == null)
+ atmos_machinery.Remove(M)
+ if(!M || (M.process_atmos() == PROCESS_KILL))
atmos_machinery.Remove(M)
if(MC_TICK_CHECK)
return
-
-/datum/controller/subsystem/air/proc/process_super_conductivity(resumed = 0)
+/datum/controller/subsystem/air/proc/process_atmos_air_machinery(resumed = 0)
+ var/seconds = wait * 0.1
if (!resumed)
- src.currentrun = active_super_conductivity.Copy()
+ src.currentrun = atmos_air_machinery.Copy()
//cache for sanic speed (lists are references anyways)
var/list/currentrun = src.currentrun
while(currentrun.len)
- var/turf/T = currentrun[currentrun.len]
+ var/obj/machinery/M = currentrun[currentrun.len]
currentrun.len--
- T.super_conduct()
+ if(!M || (M.process_atmos(seconds) == PROCESS_KILL))
+ atmos_air_machinery.Remove(M)
if(MC_TICK_CHECK)
return
-/datum/controller/subsystem/air/proc/process_hotspots(resumed = 0)
+/datum/controller/subsystem/air/proc/process_turf_heat()
+
+/datum/controller/subsystem/air/proc/process_hotspots(resumed = FALSE)
if (!resumed)
src.currentrun = hotspots.Copy()
//cache for sanic speed (lists are references anyways)
@@ -223,6 +361,9 @@ SUBSYSTEM_DEF(air)
return
/datum/controller/subsystem/air/proc/process_turf_equalize(resumed = 0)
+ if(process_turf_equalize_auxtools(resumed,MC_TICK_REMAINING_MS))
+ pause()
+ /*
//cache for sanic speed
var/fire_count = times_fired
if (!resumed)
@@ -232,12 +373,17 @@ SUBSYSTEM_DEF(air)
while(currentrun.len)
var/turf/open/T = currentrun[currentrun.len]
currentrun.len--
- if (istype(T))
+ if (T)
T.equalize_pressure_in_zone(fire_count)
+ //equalize_pressure_in_zone(T, fire_count)
if (MC_TICK_CHECK)
return
+ */
-/datum/controller/subsystem/air/proc/process_active_turfs(resumed = 0)
+/datum/controller/subsystem/air/proc/process_turfs(resumed = 0)
+ if(process_turfs_auxtools(resumed,MC_TICK_REMAINING_MS))
+ pause()
+ /*
//cache for sanic speed
var/fire_count = times_fired
if (!resumed)
@@ -247,68 +393,60 @@ SUBSYSTEM_DEF(air)
while(currentrun.len)
var/turf/open/T = currentrun[currentrun.len]
currentrun.len--
- if (istype(T))
+ if (T)
T.process_cell(fire_count)
if (MC_TICK_CHECK)
return
+ */
/datum/controller/subsystem/air/proc/process_excited_groups(resumed = 0)
- if(process_excited_groups_extools(resumed, (Master.current_ticklimit - TICK_USAGE) * 0.01 * world.tick_lag))
- sleep()
-
-/datum/controller/subsystem/air/proc/process_excited_groups_extools()
-/datum/controller/subsystem/air/proc/get_amt_excited_groups()
-
-/datum/controller/subsystem/air/proc/remove_from_active(turf/open/T)
- active_turfs -= T
- if(currentpart == SSAIR_ACTIVETURFS)
- currentrun -= T
- #ifdef VISUALIZE_ACTIVE_TURFS
- T.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, "#00ff00")
- #endif
- if(istype(T))
- T.set_excited(FALSE)
- T.eg_garbage_collect()
-
-/datum/controller/subsystem/air/proc/add_to_active(turf/open/T, blockchanges = 1)
- if(istype(T) && T.air)
- #ifdef VISUALIZE_ACTIVE_TURFS
- T.add_atom_colour("#00ff00", TEMPORARY_COLOUR_PRIORITY)
- #endif
- T.set_excited(TRUE)
- active_turfs |= T
- if(currentpart == SSAIR_ACTIVETURFS)
- currentrun |= T
- if(blockchanges)
- T.eg_garbage_collect()
- else if(T.flags_1 & INITIALIZED_1)
- for(var/turf/S in T.atmos_adjacent_turfs)
- add_to_active(S)
- else if(map_loading)
- if(queued_for_activation)
- queued_for_activation[T] = T
- return
- else
- T.requires_activation = TRUE
+ if(process_excited_groups_auxtools(resumed,MC_TICK_REMAINING_MS))
+ pause()
+
+/datum/controller/subsystem/air/proc/finish_turf_processing(resumed = 0)
+ if(finish_turf_processing_auxtools(MC_TICK_REMAINING_MS))
+ pause()
+
+/datum/controller/subsystem/air/proc/post_process_turfs(resumed = 0)
+ if(post_process_turfs_auxtools(resumed,MC_TICK_REMAINING_MS))
+ pause()
+
+/datum/controller/subsystem/air/proc/finish_turf_processing_auxtools()
+/datum/controller/subsystem/air/proc/process_turfs_auxtools()
+/datum/controller/subsystem/air/proc/post_process_turfs_auxtools()
+/datum/controller/subsystem/air/proc/process_turf_equalize_auxtools()
+/datum/controller/subsystem/air/proc/process_excited_groups_auxtools()
+/datum/controller/subsystem/air/proc/get_amt_gas_mixes()
+/datum/controller/subsystem/air/proc/get_max_gas_mixes()
+/datum/controller/subsystem/air/proc/turf_process_time()
+/datum/controller/subsystem/air/proc/heat_process_time()
/datum/controller/subsystem/air/StartLoadingMap()
- LAZYINITLIST(queued_for_activation)
map_loading = TRUE
/datum/controller/subsystem/air/StopLoadingMap()
map_loading = FALSE
- for(var/T in queued_for_activation)
- add_to_active(T)
- queued_for_activation.Cut()
+
+/datum/controller/subsystem/air/proc/pause_z(z_level)
+ LAZYADD(paused_z_levels, z_level)
+ var/list/turfs_to_disable = block(locate(1, 1, z_level), locate(world.maxx, world.maxy, z_level))
+ for(var/turf/T as anything in turfs_to_disable)
+ T.ImmediateDisableAdjacency(FALSE)
+ CHECK_TICK
+
+/datum/controller/subsystem/air/proc/unpause_z(z_level)
+ var/list/turfs_to_reinit = block(locate(1, 1, z_level), locate(world.maxx, world.maxy, z_level))
+ for(var/turf/T as anything in turfs_to_reinit)
+ T.Initalize_Atmos()
+ CHECK_TICK
+ LAZYREMOVE(paused_z_levels, z_level)
/datum/controller/subsystem/air/proc/setup_allturfs()
var/list/turfs_to_init = block(locate(1, 1, 1), locate(world.maxx, world.maxy, world.maxz))
- var/list/active_turfs = src.active_turfs
var/times_fired = ++src.times_fired
// Clear active turfs - faster than removing every single turf in the world
// one-by-one, and Initalize_Atmos only ever adds `src` back in.
- active_turfs.Cut()
for(var/thing in turfs_to_init)
var/turf/T = thing
@@ -317,40 +455,8 @@ SUBSYSTEM_DEF(air)
T.Initalize_Atmos(times_fired)
CHECK_TICK
- if(active_turfs.len)
- var/starting_ats = active_turfs.len
- sleep(world.tick_lag)
- var/timer = world.timeofday
- log_mapping("There are [starting_ats] active turfs at roundstart caused by a difference of the air between the adjacent turfs. You can see its coordinates using \"Mapping -> Show roundstart AT list\" verb (debug verbs required).")
- for(var/turf/T in active_turfs)
- GLOB.active_turfs_startlist += T
-
- //now lets clear out these active turfs
- var/list/turfs_to_check = active_turfs.Copy()
- do
- var/list/new_turfs_to_check = list()
- for(var/turf/open/T in turfs_to_check)
- new_turfs_to_check += T.resolve_active_graph()
- CHECK_TICK
-
- active_turfs += new_turfs_to_check
- turfs_to_check = new_turfs_to_check
-
- while (turfs_to_check.len)
- var/ending_ats = active_turfs.len
-
- var/msg = "HEY! LISTEN! [DisplayTimeText(world.timeofday - timer)] were wasted processing [starting_ats] turf(s) (connected to [ending_ats] other turfs) with atmos differences at round start."
- to_chat(world, "[msg]")
- warning(msg)
-
-/turf/open/proc/resolve_active_graph()
- . = list()
-
-/turf/open/space/resolve_active_graph()
- return list()
-
/datum/controller/subsystem/air/proc/setup_atmos_machinery()
- for (var/obj/machinery/atmospherics/AM in atmos_machinery)
+ for (var/obj/machinery/atmospherics/AM in atmos_machinery + atmos_air_machinery)
AM.atmosinit()
CHECK_TICK
@@ -358,11 +464,13 @@ SUBSYSTEM_DEF(air)
// all atmos machinery has to initalize before the first
// pipenet can be built.
/datum/controller/subsystem/air/proc/setup_pipenets()
- for (var/obj/machinery/atmospherics/AM in atmos_machinery)
+ for (var/obj/machinery/atmospherics/AM in atmos_machinery + atmos_air_machinery)
AM.build_network()
CHECK_TICK
/datum/controller/subsystem/air/proc/setup_template_machinery(list/atmos_machines)
+ if(!initialized) // yogs - fixes randomized bars
+ return // yogs
for(var/A in atmos_machines)
var/obj/machinery/atmospherics/AM = A
AM.atmosinit()
@@ -384,24 +492,14 @@ SUBSYSTEM_DEF(air)
return pipe_init_dirs_cache[type]["[dir]"]
-/datum/controller/subsystem/air/proc/generate_atmos()
- atmos_gen = list()
- for(var/T in subtypesof(/datum/atmosphere))
- var/datum/atmosphere/atmostype = T
- atmos_gen[initial(atmostype.id)] = new atmostype
-
-/datum/controller/subsystem/air/proc/preprocess_gas_string(gas_string)
- if(!atmos_gen)
- generate_atmos()
- if(!atmos_gen[gas_string])
- return gas_string
- var/datum/atmosphere/mix = atmos_gen[gas_string]
- return mix.gas_string
-
#undef SSAIR_PIPENETS
#undef SSAIR_ATMOSMACHINERY
-#undef SSAIR_ACTIVETURFS
#undef SSAIR_EXCITEDGROUPS
#undef SSAIR_HIGHPRESSURE
#undef SSAIR_HOTSPOTS
-#undef SSAIR_SUPERCONDUCTIVITY
+#undef SSAIR_TURF_CONDUCTION
+#undef SSAIR_EQUALIZE
+#undef SSAIR_ACTIVETURFS
+#undef SSAIR_TURF_POST_PROCESS
+#undef SSAIR_FINALIZE_TURFS
+#undef SSAIR_ATMOSMACHINERY_AIR
diff --git a/code/controllers/subsystem/ambience.dm b/code/controllers/subsystem/ambience.dm
index 36b0a1fd60ff5..9a3aab7baf06d 100644
--- a/code/controllers/subsystem/ambience.dm
+++ b/code/controllers/subsystem/ambience.dm
@@ -1,71 +1,53 @@
-#define AMBIENT_EFFECT_COOLDOWN 600 // The minimum amount to wait between playing ambient effects (deciseconds)
-
-#define AMBIENT_BUZZ_VOLUME 40
-#define AMBIENT_MUSIC_VOLUME 75
-#define AMBIENT_EFFECTS_VOLUME 45
-
-// Ambient sounds: buzz, effects, music
+/// The subsystem used to play ambience to users every now and then, makes them real excited.
SUBSYSTEM_DEF(ambience)
name = "Ambience"
- wait = 2
+ flags = SS_BACKGROUND|SS_NO_INIT
priority = FIRE_PRIORITY_AMBIENCE
- flags = SS_NO_INIT
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
+ wait = 2
+ ///Assoc list of listening client - next ambience time
+ var/list/ambience_listening_clients = list()
- var/list/currentrun = list()
-
-
-/datum/controller/subsystem/ambience/fire(resumed = 0)
- if (!resumed)
- src.currentrun = GLOB.clients.Copy()
+/datum/controller/subsystem/ambience/fire(resumed)
+ for(var/client/client_iterator as anything in ambience_listening_clients)
- //cache for sanic speed (lists are references anyways)
- var/list/currentrun = src.currentrun
+ if(isnull(client_iterator))
+ ambience_listening_clients -= client_iterator
+ continue
- while(currentrun.len)
- var/client/C = currentrun[currentrun.len]
- currentrun.len--
+ if(isnewplayer(client_iterator.mob))
+ continue
- if (C)
- var/mob/M = C.mob
+ var/area/current_area = get_area(client_iterator.mob)
- if(M)
- if (istype(M, /mob/dead/new_player)) // Don't play ambience to nerds in the lobby
- continue
+ play_buzz(client_iterator.mob, current_area)
- src.update_buzz(M) // Update buzz every fire, or every 1/5th second
+ if(ambience_listening_clients[client_iterator] > world.time)
+ continue //Not ready for the next sound
- if (src.times_fired % 5 == 0) // Only update effects and music every second instead of every 1/5th second
- src.update_effects(M)
- src.update_music(M)
+ var/ambi_fx = pick(current_area.ambientsounds)
+ var/ambi_music = pick(current_area.ambientmusic)
- if (MC_TICK_CHECK)
- return
+ play_ambience_music(client_iterator.mob, ambi_music, current_area)
+ play_ambience_effects(client_iterator.mob, ambi_fx, current_area)
-/datum/controller/subsystem/ambience/proc/update_buzz(mob/M) // Buzz, the growling buzz of the station, etc, IC (requires the user to be able to hear)
- var/area/A = get_area(M)
+ ambience_listening_clients[client_iterator] = world.time + rand(current_area.min_ambience_cooldown, current_area.max_ambience_cooldown)
+/datum/controller/subsystem/ambience/proc/play_buzz(mob/M, area/A)
if (A.ambient_buzz && (M.client.prefs.toggles & SOUND_SHIP_AMBIENCE) && M.can_hear_ambience())
- if (!M.client.ambient_buzz_playing || (A.ambient_buzz != M.client.ambient_buzz_playing))
- SEND_SOUND(M, sound(A.ambient_buzz, repeat = 1, wait = 0, volume = AMBIENT_BUZZ_VOLUME, channel = CHANNEL_AMBIENT_BUZZ))
- M.client.ambient_buzz_playing = A.ambient_buzz // It's done this way so I can tell when the user switches to an area that has a different buzz effect, so we can seamlessly swap over to that one
-
- else if (M.client.ambient_buzz_playing) // If it's playing, and it shouldn't be, stop it
- M.stop_sound_channel(CHANNEL_AMBIENT_BUZZ)
- M.client.ambient_buzz_playing = null
-
-
-/datum/controller/subsystem/ambience/proc/update_music(mob/M) // Background music, the more OOC ambience, like eerie space music
- var/area/A = get_area(M)
-
- if (A.ambient_music && (M.client.prefs.toggles & SOUND_AMBIENCE) && prob(1.25) && !M.client.channel_in_use(CHANNEL_AMBIENT_MUSIC)) // 1/80 chance to play every second, only play while another one is not playing
- SEND_SOUND(M, sound(pick(A.ambient_music), repeat = 0, wait = 0, volume = AMBIENT_MUSIC_VOLUME, channel = CHANNEL_AMBIENT_MUSIC))
+ if (!M.client.buzz_playing || (A.ambient_buzz != M.client.buzz_playing))
+ SEND_SOUND(M, sound(A.ambient_buzz, repeat = 1, wait = 0, volume = 40, channel = CHANNEL_BUZZ))
+ M.client.buzz_playing = A.ambient_buzz // It's done this way so I can tell when the user switches to an area that has a different buzz effect, so we can seamlessly swap over to that one
+ else if (M.client.buzz_playing) // If it's playing, and it shouldn't be, stop it
+ M.stop_sound_channel(CHANNEL_BUZZ)
+ M.client.buzz_playing = null
-/datum/controller/subsystem/ambience/proc/update_effects(mob/M) // Effect, random sounds that will play at random times, IC (requires the user to be able to hear)
- var/area/A = get_area(M)
+/datum/controller/subsystem/ambience/proc/play_ambience_music(mob/M, _ambi_music, area/A) // Background music, the more OOC ambience, like eerie space music
+ if(A.ambientmusic && !M.client?.channel_in_use(CHANNEL_AMBIENT_MUSIC))
+ SEND_SOUND(M, sound(_ambi_music, repeat = 0, wait = 0, volume = 75, channel = CHANNEL_AMBIENT_MUSIC))
- if (A.ambient_effects && (M.client.prefs.toggles & SOUND_AMBIENCE) && M.can_hear_ambience() && (world.time - M.client.ambient_effect_last_played) > AMBIENT_EFFECT_COOLDOWN && prob(5) && !M.client.channel_in_use(CHANNEL_AMBIENT_EFFECTS)) // 1/20 chance to play every second after cooldown
- SEND_SOUND(M, sound(pick(A.ambient_effects), repeat = 0, wait = 0, volume = AMBIENT_EFFECTS_VOLUME, channel = CHANNEL_AMBIENT_EFFECTS))
- M.client.ambient_effect_last_played = world.time
+/datum/controller/subsystem/ambience/proc/play_ambience_effects(mob/M, _ambi_fx, area/A) // Effect, random sounds that will play at random times, IC (requires the user to be able to hear)
+ if (length(A.ambientsounds) && M.can_hear_ambience() && !M.client?.channel_in_use(CHANNEL_AMBIENT_EFFECTS))
+ SEND_SOUND(M, sound(_ambi_fx, repeat = 0, wait = 0, volume = 45, channel = CHANNEL_AMBIENT_EFFECTS))
diff --git a/code/controllers/subsystem/atoms.dm b/code/controllers/subsystem/atoms.dm
index 86c40e9f916a5..f139d4692b1bd 100644
--- a/code/controllers/subsystem/atoms.dm
+++ b/code/controllers/subsystem/atoms.dm
@@ -14,6 +14,11 @@ SUBSYSTEM_DEF(atoms)
var/list/BadInitializeCalls = list()
+ ///initAtom() adds the atom its creating to this list iff InitializeAtoms() has been given a list to populate as an argument
+ var/list/created_atoms
+
+ initialized = INITIALIZATION_INSSATOMS
+
/datum/controller/subsystem/atoms/Initialize(timeofday)
GLOB.fire_overlay.appearance_flags = RESET_COLOR
setupGenetics() //to set the mutations' sequence
@@ -21,12 +26,15 @@ SUBSYSTEM_DEF(atoms)
InitializeAtoms()
return ..()
-/datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms)
+/datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms, list/atoms_to_return = null)
if(initialized == INITIALIZATION_INSSATOMS)
return
initialized = INITIALIZATION_INNEW_MAPLOAD
+ if (atoms_to_return)
+ LAZYINITLIST(created_atoms)
+
var/count
var/list/mapload_arg = list(TRUE)
if(atoms)
@@ -34,13 +42,13 @@ SUBSYSTEM_DEF(atoms)
for(var/I in atoms)
var/atom/A = I
if(!(A.flags_1 & INITIALIZED_1))
- InitAtom(I, mapload_arg)
CHECK_TICK
+ InitAtom(A, TRUE, mapload_arg)
else
count = 0
for(var/atom/A in world)
if(!(A.flags_1 & INITIALIZED_1))
- InitAtom(A, mapload_arg)
+ InitAtom(A, FALSE, mapload_arg)
++count
CHECK_TICK
@@ -52,11 +60,19 @@ SUBSYSTEM_DEF(atoms)
if(late_loaders.len)
for(var/I in late_loaders)
var/atom/A = I
+ //I hate that we need this
+ if(QDELETED(A))
+ continue
A.LateInitialize()
testing("Late initialized [late_loaders.len] atoms")
late_loaders.Cut()
-/datum/controller/subsystem/atoms/proc/InitAtom(atom/A, list/arguments)
+ if (created_atoms)
+ atoms_to_return += created_atoms
+ created_atoms = null
+
+/// Init this specific atom
+/datum/controller/subsystem/atoms/proc/InitAtom(atom/A, from_template = FALSE, list/arguments)
var/the_type = A.type
if(QDELING(A))
BadInitializeCalls[the_type] |= BAD_INIT_QDEL_BEFORE
@@ -81,6 +97,9 @@ SUBSYSTEM_DEF(atoms)
if(INITIALIZE_HINT_QDEL)
qdel(A)
qdeleted = TRUE
+ if(INITIALIZE_HINT_QDEL_FORCE)
+ qdel(A, force = TRUE)
+ qdeleted = TRUE
else
BadInitializeCalls[the_type] |= BAD_INIT_NO_HINT
@@ -88,6 +107,10 @@ SUBSYSTEM_DEF(atoms)
qdeleted = TRUE
else if(!(A.flags_1 & INITIALIZED_1))
BadInitializeCalls[the_type] |= BAD_INIT_DIDNT_INIT
+ else
+ SEND_SIGNAL(A,COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE)
+ if(created_atoms && from_template && ispath(the_type, /atom/movable))//we only want to populate the list with movables
+ created_atoms += A.GetAllContents()
return qdeleted || QDELING(A)
diff --git a/code/controllers/subsystem/augury.dm b/code/controllers/subsystem/augury.dm
index 1b1c7bc3b7b87..c598bc833abc7 100644
--- a/code/controllers/subsystem/augury.dm
+++ b/code/controllers/subsystem/augury.dm
@@ -9,7 +9,7 @@ SUBSYSTEM_DEF(augury)
var/list/observers_given_action = list()
/datum/controller/subsystem/augury/stat_entry(msg)
- ..("W:[watchers.len]|D:[doombringers.len]")
+ . = ..("W:[watchers.len]|D:[doombringers.len]")
/datum/controller/subsystem/augury/proc/register_doom(atom/A, severity)
doombringers[A] = severity
diff --git a/code/controllers/subsystem/blackbox.dm b/code/controllers/subsystem/blackbox.dm
index 975575022b378..2e95e330ce983 100644
--- a/code/controllers/subsystem/blackbox.dm
+++ b/code/controllers/subsystem/blackbox.dm
@@ -155,6 +155,8 @@ SUBSYSTEM_DEF(blackbox)
record_feedback("tally", "radio_usage", 1, "service")
if(FREQ_SUPPLY)
record_feedback("tally", "radio_usage", 1, "supply")
+ if(FREQ_EXPLORATION)
+ record_feedback("tally", "radio_usage", 1, "exploration")
if(FREQ_CENTCOM)
record_feedback("tally", "radio_usage", 1, "centcom")
if(FREQ_AI_PRIVATE)
@@ -190,7 +192,7 @@ feedback data can be recorded in 5 formats:
SSblackbox.record_feedback("amount", "example", 2)
json: {"data":10}
"tally"
- used to track the number of occurances of multiple related values i.e. how many times each type of gun is fired
+ used to track the number of occurrences of multiple related values i.e. how many times each type of gun is fired
further calls to the same key will:
add or subtract from the saved value of the data key if it already exists
append the key and it's value if it doesn't exist
@@ -199,7 +201,7 @@ feedback data can be recorded in 5 formats:
SSblackbox.record_feedback("tally", "example", 2, "other data")
json: {"data":{"sample data":5,"other data":2}}
"nested tally"
- used to track the number of occurances of structured semi-relational values i.e. the results of arcade machines
+ used to track the number of occurrences of structured semi-relational values i.e. the results of arcade machines
similar to running total, but related values are nested in a multi-dimensional array built
the final element in the data list is used as the tracking key, all prior elements are used for nesting
all data list elements must be strings
diff --git a/code/controllers/subsystem/callback.dm b/code/controllers/subsystem/callback.dm
new file mode 100644
index 0000000000000..ecc65760f4e80
--- /dev/null
+++ b/code/controllers/subsystem/callback.dm
@@ -0,0 +1,14 @@
+SUBSYSTEM_DEF(callbacks)
+ name = "Auxtools Callbacks"
+ flags = SS_TICKER | SS_NO_INIT
+ wait = 1
+ priority = FIRE_PRIORITY_CALLBACKS
+
+/proc/process_atmos_callbacks()
+ SScallbacks.can_fire = 0
+ SScallbacks.flags |= SS_NO_FIRE
+ CRASH("Auxtools not found! Callback subsystem shutting itself off.")
+
+/datum/controller/subsystem/callbacks/fire()
+ if(process_atmos_callbacks(MC_TICK_REMAINING_MS))
+ pause()
diff --git a/code/controllers/subsystem/communications.dm b/code/controllers/subsystem/communications.dm
index 718357b8bb9ca..3ec718eb10d6e 100644
--- a/code/controllers/subsystem/communications.dm
+++ b/code/controllers/subsystem/communications.dm
@@ -16,14 +16,14 @@ SUBSYSTEM_DEF(communications)
else
. = TRUE
-/datum/controller/subsystem/communications/proc/make_announcement(mob/living/user, is_silicon, input)
+/datum/controller/subsystem/communications/proc/make_announcement(mob/living/user, is_silicon, input, auth_id)
if(!can_announce(user, is_silicon))
return FALSE
if(is_silicon)
- minor_announce(html_decode(input),"[user.name] Announces:")
+ minor_announce(input,"[user.name] Announces:", html_encode = FALSE)
silicon_message_cooldown = world.time + COMMUNICATION_COOLDOWN_AI
else
- priority_announce(html_decode(user.treat_message(input)), null, 'sound/misc/announce.ogg', "Captain")
+ priority_announce(html_decode(user.treat_message(input)), null, 'sound/misc/announce.ogg', "Captain", has_important_message = TRUE, auth_id = auth_id)
nonsilicon_message_cooldown = world.time + COMMUNICATION_COOLDOWN
user.log_talk(input, LOG_SAY, tag="priority announcement")
message_admins("[ADMIN_LOOKUPFLW(user)] has made a priority announcement.")
diff --git a/code/controllers/subsystem/dcs.dm b/code/controllers/subsystem/dcs.dm
index 496105096729b..104c3eef31d35 100644
--- a/code/controllers/subsystem/dcs.dm
+++ b/code/controllers/subsystem/dcs.dm
@@ -1,28 +1,54 @@
PROCESSING_SUBSYSTEM_DEF(dcs)
name = "Datum Component System"
flags = SS_NO_INIT
+ wait = 1 SECONDS
var/list/elements_by_type = list()
/datum/controller/subsystem/processing/dcs/Recover()
comp_lookup = SSdcs.comp_lookup
-/datum/controller/subsystem/processing/dcs/proc/GetElement(datum/element/eletype, ...)
+/datum/controller/subsystem/processing/dcs/proc/GetElement(list/arguments)
+ var/datum/element/eletype = arguments[1]
var/element_id = eletype
+ if(!ispath(eletype, /datum/element))
+ CRASH("Attempted to instantiate [eletype] as a /datum/element")
+
if(initial(eletype.element_flags) & ELEMENT_BESPOKE)
- var/list/fullid = list("[eletype]")
- for(var/i in initial(eletype.id_arg_index) to length(args))
- var/argument = args[i]
- if(istext(argument) || isnum_safe(argument))
- fullid += "[argument]"
- else
- fullid += "[REF(argument)]"
- element_id = fullid.Join("&")
+ element_id = GetIdFromArguments(arguments)
. = elements_by_type[element_id]
if(.)
return
- if(!ispath(eletype, /datum/element))
- CRASH("Attempted to instantiate [eletype] as a /datum/element")
. = elements_by_type[element_id] = new eletype
+
+/****
+ * Generates an id for bespoke elements when given the argument list
+ * Generating the id here is a bit complex because we need to support named arguments
+ * Named arguments can appear in any order and we need them to appear after ordered arguments
+ * We assume that no one will pass in a named argument with a value of null
+ **/
+/datum/controller/subsystem/processing/dcs/proc/GetIdFromArguments(list/arguments)
+ var/datum/element/eletype = arguments[1]
+ var/list/fullid = list("[eletype]")
+ var/list/named_arguments = list()
+ for(var/i in initial(eletype.id_arg_index) to length(arguments))
+ var/key = arguments[i]
+ var/value
+ if(istext(key))
+ value = arguments[key]
+ if(!(istext(key) || isnum(key)))
+ key = REF(key)
+ key = "[key]" // Key is stringified so numbers dont break things
+ if(!isnull(value))
+ if(!(istext(value) || isnum(value)))
+ value = REF(value)
+ named_arguments["[key]"] = value
+ else
+ fullid += "[key]"
+
+ if(length(named_arguments))
+ named_arguments = sortList(named_arguments)
+ fullid += named_arguments
+ return list2params(fullid)
diff --git a/code/controllers/subsystem/delay_component.dm b/code/controllers/subsystem/delay_component.dm
new file mode 100644
index 0000000000000..3e549ae7963ea
--- /dev/null
+++ b/code/controllers/subsystem/delay_component.dm
@@ -0,0 +1,34 @@
+SUBSYSTEM_DEF(circuit_component)
+ name = "Circuit Components"
+ wait = 0.1 SECONDS
+ priority = FIRE_PRIORITY_DEFAULT
+
+ var/list/callbacks_to_invoke = list()
+ var/list/currentrun = list()
+
+/datum/controller/subsystem/circuit_component/fire(resumed)
+ if(!resumed)
+ currentrun = callbacks_to_invoke.Copy()
+ callbacks_to_invoke.Cut()
+
+ while(length(currentrun))
+ var/datum/callback/to_call = currentrun[1]
+ currentrun.Cut(1,2)
+
+ if(QDELETED(to_call))
+ continue
+
+ to_call.InvokeAsync()
+ qdel(to_call)
+
+ if(MC_TICK_CHECK)
+ return
+
+/**
+ * Adds a callback to be invoked when the next fire() is done. Used by the integrated circuit system.
+ *
+ * Prevents race conditions as it acts like a queue system.
+ * Those that registered first will be executed first and those registered last will be executed last.
+ */
+/datum/controller/subsystem/circuit_component/proc/add_callback(datum/callback/to_call)
+ callbacks_to_invoke += to_call
diff --git a/code/controllers/subsystem/disease.dm b/code/controllers/subsystem/disease.dm
index 9be1d8d90c09d..1a7fe78fd1cd8 100644
--- a/code/controllers/subsystem/disease.dm
+++ b/code/controllers/subsystem/disease.dm
@@ -20,7 +20,7 @@ SUBSYSTEM_DEF(disease)
return ..()
/datum/controller/subsystem/disease/stat_entry(msg)
- ..("P:[active_diseases.len]")
+ . = ..("P:[active_diseases.len]")
/datum/controller/subsystem/disease/proc/get_disease_name(id)
var/datum/disease/advance/A = archive_diseases[id]
diff --git a/code/controllers/subsystem/economy.dm b/code/controllers/subsystem/economy.dm
index 16a616d0347ef..cd3515790d02c 100644
--- a/code/controllers/subsystem/economy.dm
+++ b/code/controllers/subsystem/economy.dm
@@ -4,7 +4,7 @@ SUBSYSTEM_DEF(economy)
init_order = INIT_ORDER_ECONOMY
runlevels = RUNLEVEL_GAME
var/roundstart_paychecks = 5
- var/budget_pool = 35000
+ var/budget_pool = 25000
var/list/department_accounts = list(ACCOUNT_CIV = ACCOUNT_CIV_NAME,
ACCOUNT_ENG = ACCOUNT_ENG_NAME,
ACCOUNT_SCI = ACCOUNT_SCI_NAME,
@@ -15,38 +15,11 @@ SUBSYSTEM_DEF(economy)
var/list/generated_accounts = list()
var/full_ancap = FALSE // Enables extra money charges for things that normally would be free, such as sleepers/cryo/cloning.
//Take care when enabling, as players will NOT respond well if the economy is set up for low cash flows.
- var/alive_humans_bounty = 100
- var/crew_safety_bounty = 1500
- var/monster_bounty = 150
- var/mood_bounty = 100
- var/techweb_bounty = 250
- var/slime_bounty = list("grey" = 10,
- // tier 1
- "orange" = 100,
- "metal" = 100,
- "blue" = 100,
- "purple" = 100,
- // tier 2
- "dark purple" = 500,
- "dark blue" = 500,
- "green" = 500,
- "silver" = 500,
- "gold" = 500,
- "yellow" = 500,
- "red" = 500,
- "pink" = 500,
- // tier 3
- "cerulean" = 750,
- "sepia" = 750,
- "bluespace" = 750,
- "pyrite" = 750,
- "light pink" = 750,
- "oil" = 750,
- "adamantine" = 750,
- // tier 4
- "rainbow" = 1000)
var/list/bank_accounts = list() //List of normal accounts (not department accounts)
var/list/dep_cards = list()
+ ///The modifier multiplied to the value of bounties paid out.
+ ///Multiplied as they go to all department accounts rather than just cargo.
+ var/bounty_modifier = 3
/datum/controller/subsystem/economy/Initialize(timeofday)
var/budget_to_hand_out = round(budget_pool / department_accounts.len)
@@ -55,10 +28,6 @@ SUBSYSTEM_DEF(economy)
return ..()
/datum/controller/subsystem/economy/fire(resumed = 0)
- boring_eng_payout() // Payout based on nothing. What will replace it? Surplus power, powered APC's, air alarms? Who knows.
- boring_sci_payout() // Payout based on slimes.
- boring_secmedsrv_payout() // Payout based on crew safety, health, and mood.
- boring_civ_payout() // Payout based on ??? Profit
for(var/A in bank_accounts)
var/datum/bank_account/B = A
B.payday(1)
@@ -69,62 +38,46 @@ SUBSYSTEM_DEF(economy)
if(D.department_id == dep_id)
return D
-/datum/controller/subsystem/economy/proc/boring_eng_payout()
- var/engineering_cash = 3000
- var/datum/bank_account/D = get_dep_account(ACCOUNT_ENG)
- if(D)
- D.adjust_money(engineering_cash)
+/datum/controller/subsystem/economy/proc/distribute_funds(amount)
+ var/datum/bank_account/eng = get_dep_account(ACCOUNT_ENG)
+ var/datum/bank_account/sec = get_dep_account(ACCOUNT_SEC)
+ var/datum/bank_account/med = get_dep_account(ACCOUNT_MED)
+ var/datum/bank_account/srv = get_dep_account(ACCOUNT_SRV)
+ var/datum/bank_account/sci = get_dep_account(ACCOUNT_SCI)
+ var/datum/bank_account/civ = get_dep_account(ACCOUNT_CIV)
+ var/datum/bank_account/car = get_dep_account(ACCOUNT_CAR)
-/datum/controller/subsystem/economy/proc/boring_secmedsrv_payout()
- var/crew
- var/alive_crew
- var/dead_monsters
- var/cash_to_grant
- for(var/mob/m in GLOB.mob_list)
- if(isnewplayer(m))
- continue
- if(m.mind)
- if(isbrain(m) || iscameramob(m))
- continue
- if(ishuman(m))
- var/mob/living/carbon/human/H = m
- crew++
- if(H.stat != DEAD)
- alive_crew++
- var/datum/component/mood/mood = H.GetComponent(/datum/component/mood)
- var/medical_cash = (H.health / H.maxHealth) * alive_humans_bounty
- if(mood)
- var/datum/bank_account/D = get_dep_account(ACCOUNT_SRV)
- if(D)
- var/mood_dosh = (mood.mood_level / 9) * mood_bounty
- D.adjust_money(mood_dosh)
- medical_cash *= (mood.sanity / 100)
+ var/departments = 0
- var/datum/bank_account/D = get_dep_account(ACCOUNT_MED)
- if(D)
- D.adjust_money(medical_cash)
- if(ishostile(m))
- var/mob/living/simple_animal/hostile/H = m
- if(H.stat == DEAD && (H.z in SSmapping.levels_by_trait(ZTRAIT_STATION)))
- dead_monsters++
- CHECK_TICK
- var/fuck = alive_crew / crew
- cash_to_grant = (crew_safety_bounty * fuck) + (monster_bounty * dead_monsters)
- var/datum/bank_account/D = get_dep_account(ACCOUNT_SEC)
- if(D)
- D.adjust_money(cash_to_grant)
+ if(eng)
+ departments += 2
+ if(sec)
+ departments += 2
+ if(med)
+ departments += 2
+ if(srv)
+ departments += 1
+ if(sci)
+ departments += 2
+ if(civ)
+ departments += 1
+ if(car)
+ departments += 2
-/datum/controller/subsystem/economy/proc/boring_sci_payout()
- var/science_bounty = 0
- for(var/mob/living/simple_animal/slime/S in GLOB.mob_list)
- if(S.stat == DEAD)
- continue
- science_bounty += slime_bounty[S.colour]
- var/datum/bank_account/D = get_dep_account(ACCOUNT_SCI)
- if(D)
- D.adjust_money(science_bounty)
+ var/parts = round(amount / departments)
-/datum/controller/subsystem/economy/proc/boring_civ_payout()
- var/datum/bank_account/D = get_dep_account(ACCOUNT_CIV)
- if(D)
- D.adjust_money((rand(1,5) * 500))
+ var/engineering_cash = parts * 2
+ var/security_cash = parts * 2
+ var/medical_cash = parts * 2
+ var/service_cash = parts
+ var/science_cash = parts * 2
+ var/civilian_cash = parts
+ var/cargo_cash = parts * 2
+
+ eng?.adjust_money(engineering_cash)
+ sec?.adjust_money(security_cash)
+ med?.adjust_money(medical_cash)
+ srv?.adjust_money(service_cash)
+ sci?.adjust_money(science_cash)
+ civ?.adjust_money(civilian_cash)
+ car?.adjust_money(cargo_cash)
diff --git a/code/controllers/subsystem/events.dm b/code/controllers/subsystem/events.dm
index 37b8ae95b19bc..013da1753862c 100644
--- a/code/controllers/subsystem/events.dm
+++ b/code/controllers/subsystem/events.dm
@@ -37,7 +37,7 @@ SUBSYSTEM_DEF(events)
var/datum/thing = currentrun[currentrun.len]
currentrun.len--
if(thing)
- thing.process()
+ thing.process(wait * 0.1)
else
running.Remove(thing)
if (MC_TICK_CHECK)
diff --git a/code/controllers/subsystem/explosion.dm b/code/controllers/subsystem/explosion.dm
new file mode 100644
index 0000000000000..54aacd08b9b8f
--- /dev/null
+++ b/code/controllers/subsystem/explosion.dm
@@ -0,0 +1,601 @@
+#define EXPLOSION_THROW_SPEED 4
+GLOBAL_LIST_EMPTY(explosions)
+
+SUBSYSTEM_DEF(explosions)
+ name = "Explosions"
+ init_order = INIT_ORDER_EXPLOSIONS
+ priority = FIRE_PRIORITY_EXPLOSIONS
+ wait = 1
+ flags = SS_TICKER|SS_NO_INIT
+ runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
+
+ var/cost_lowturf = 0
+ var/cost_medturf = 0
+ var/cost_highturf = 0
+ var/cost_flameturf = 0
+
+ var/cost_throwturf = 0
+
+ var/cost_low_mov_atom = 0
+ var/cost_med_mov_atom = 0
+ var/cost_high_mov_atom = 0
+
+ var/list/lowturf = list()
+ var/list/medturf = list()
+ var/list/highturf = list()
+ var/list/flameturf = list()
+
+ var/list/throwturf = list()
+
+ var/list/low_mov_atom = list()
+ var/list/med_mov_atom = list()
+ var/list/high_mov_atom = list()
+
+ var/list/explosions = list()
+
+ var/currentpart = SSAIR_REBUILD_PIPENETS
+
+
+/datum/controller/subsystem/explosions/stat_entry(msg)
+ msg += "C:{"
+ msg += "LT:[round(cost_lowturf,1)]|"
+ msg += "MT:[round(cost_medturf,1)]|"
+ msg += "HT:[round(cost_highturf,1)]|"
+ msg += "FT:[round(cost_flameturf,1)]||"
+
+ msg += "LO:[round(cost_low_mov_atom,1)]|"
+ msg += "MO:[round(cost_med_mov_atom,1)]|"
+ msg += "HO:[round(cost_high_mov_atom,1)]|"
+
+ msg += "TO:[round(cost_throwturf,1)]"
+
+ msg += "} "
+
+ msg += "AMT:{"
+ msg += "LT:[lowturf.len]|"
+ msg += "MT:[medturf.len]|"
+ msg += "HT:[highturf.len]|"
+ msg += "FT:[flameturf.len]||"
+
+ msg += "LO:[low_mov_atom.len]|"
+ msg += "MO:[med_mov_atom.len]|"
+ msg += "HO:[high_mov_atom.len]|"
+
+ msg += "TO:[throwturf.len]"
+
+ msg += "} "
+ return ..()
+
+
+#define SSEX_TURF "turf"
+#define SSEX_OBJ "obj"
+
+/datum/controller/subsystem/explosions/proc/is_exploding()
+ return (lowturf.len || medturf.len || highturf.len || flameturf.len || throwturf.len || low_mov_atom.len || med_mov_atom.len || high_mov_atom.len)
+
+/datum/controller/subsystem/explosions/proc/wipe_turf(turf/T)
+ lowturf -= T
+ medturf -= T
+ highturf -= T
+ flameturf -= T
+ throwturf -= T
+
+/client/proc/check_bomb_impacts()
+ set name = "Check Bomb Impact"
+ set category = "Debug"
+
+ var/newmode = alert("Use reactionary explosions?","Check Bomb Impact", "Yes", "No")
+ var/turf/epicenter = get_turf(mob)
+ if(!epicenter)
+ return
+
+ var/dev = 0
+ var/heavy = 0
+ var/light = 0
+ var/list/choices = list("Small Bomb","Medium Bomb","Big Bomb","Custom Bomb")
+ var/choice = input("Bomb Size?") in choices
+ switch(choice)
+ if(null)
+ return 0
+ if("Small Bomb")
+ dev = 1
+ heavy = 2
+ light = 3
+ if("Medium Bomb")
+ dev = 2
+ heavy = 3
+ light = 4
+ if("Big Bomb")
+ dev = 3
+ heavy = 5
+ light = 7
+ if("Custom Bomb")
+ dev = input("Devastation range (Tiles):") as num
+ heavy = input("Heavy impact range (Tiles):") as num
+ light = input("Light impact range (Tiles):") as num
+
+ var/max_range = max(dev, heavy, light)
+ var/x0 = epicenter.x
+ var/y0 = epicenter.y
+ var/list/wipe_colours = list()
+ for(var/turf/T as() in spiral_range_turfs(max_range, epicenter))
+ wipe_colours += T
+ var/dist = cheap_hypotenuse(T.x, T.y, x0, y0)
+
+ if(newmode == "Yes")
+ var/turf/TT = T
+ while(TT != epicenter)
+ TT = get_step_towards(TT,epicenter)
+ if(TT.density)
+ dist += TT.explosion_block
+
+ for(var/obj/O in T)
+ var/the_block = O.explosion_block
+ dist += the_block == EXPLOSION_BLOCK_PROC ? O.GetExplosionBlock() : the_block
+
+ if(dist < dev)
+ T.color = "red"
+ T.maptext = MAPTEXT("Dev")
+ else if (dist < heavy)
+ T.color = "yellow"
+ T.maptext = MAPTEXT("Heavy")
+ else if (dist < light)
+ T.color = "blue"
+ T.maptext = MAPTEXT("Light")
+ else
+ continue
+
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/wipe_color_and_text, wipe_colours), 100)
+
+/proc/wipe_color_and_text(list/atom/wiping)
+ for(var/i in wiping)
+ var/atom/A = i
+ A.color = null
+ A.maptext = ""
+
+/proc/dyn_explosion(turf/epicenter, power, flash_range, adminlog = TRUE, ignorecap = TRUE, flame_range = 0, silent = FALSE, smoke = TRUE)
+ if(!power)
+ return
+ var/range = 0
+ range = round((2 * power)**GLOB.DYN_EX_SCALE)
+ explosion(epicenter, round(range * 0.25), round(range * 0.5), round(range), flash_range*range, adminlog, ignorecap, flame_range*range, silent, smoke)
+
+// Using default dyn_ex scale:
+// 100 explosion power is a (5, 10, 20) explosion.
+// 75 explosion power is a (4, 8, 17) explosion.
+// 50 explosion power is a (3, 7, 14) explosion.
+// 25 explosion power is a (2, 5, 10) explosion.
+// 10 explosion power is a (1, 3, 6) explosion.
+// 5 explosion power is a (0, 1, 3) explosion.
+// 1 explosion power is a (0, 0, 1) explosion.
+
+/proc/explosion(atom/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog = TRUE, ignorecap = FALSE, flame_range = 0, silent = FALSE, smoke = FALSE)
+ . = SSexplosions.explode(arglist(args))
+
+#define CREAK_DELAY 5 SECONDS //Time taken for the creak to play after explosion, if applicable.
+#define DEVASTATION_PROB 30 //The probability modifier for devistation, maths!
+#define HEAVY_IMPACT_PROB 5 //ditto
+#define FAR_UPPER 60 //Upper limit for the far_volume, distance, clamped.
+#define FAR_LOWER 40 //lower limit for the far_volume, distance, clamped.
+#define PROB_SOUND 75 //The probability modifier for a sound to be an echo, or a far sound. (0-100)
+#define SHAKE_CLAMP 2.5 //The limit for how much the camera can shake for out of view booms.
+#define FREQ_UPPER 40 //The upper limit for the randomly selected frequency.
+#define FREQ_LOWER 25 //The lower of the above.
+
+/datum/controller/subsystem/explosions/proc/explode(atom/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog, ignorecap, flame_range, silent, smoke, explode_z = TRUE)
+ epicenter = get_turf(epicenter)
+ if(!epicenter)
+ return
+
+ if(isnull(flame_range))
+ flame_range = light_impact_range
+ if(isnull(flash_range))
+ flash_range = devastation_range
+
+ // Archive the uncapped explosion for the doppler array.
+ var/orig_dev_range = devastation_range
+ var/orig_heavy_range = heavy_impact_range
+ var/orig_light_range = light_impact_range
+
+ var/orig_max_distance = max(devastation_range, heavy_impact_range, light_impact_range, flash_range, flame_range)
+
+ //Zlevel specific bomb cap multiplier.
+ var/cap_multiplier = SSmapping.level_trait(epicenter.z, ZTRAIT_BOMBCAP_MULTIPLIER)
+ if (isnull(cap_multiplier))
+ cap_multiplier = 1
+
+ if(!ignorecap)
+ devastation_range = min(GLOB.MAX_EX_DEVESTATION_RANGE * cap_multiplier, devastation_range)
+ heavy_impact_range = min(GLOB.MAX_EX_HEAVY_RANGE * cap_multiplier, heavy_impact_range)
+ light_impact_range = min(GLOB.MAX_EX_LIGHT_RANGE * cap_multiplier, light_impact_range)
+ flash_range = min(GLOB.MAX_EX_FLASH_RANGE * cap_multiplier, flash_range)
+ flame_range = min(GLOB.MAX_EX_FLAME_RANGE * cap_multiplier, flame_range)
+
+ var/max_range = max(devastation_range, heavy_impact_range, light_impact_range, flame_range)
+ var/started_at = REALTIMEOFDAY
+ if(adminlog)
+ message_admins("Explosion with size ([devastation_range], [heavy_impact_range], [light_impact_range], [flame_range]) in [ADMIN_VERBOSEJMP(epicenter)]")
+ log_game("Explosion with size ([devastation_range], [heavy_impact_range], [light_impact_range], [flame_range]) in [loc_name(epicenter)]")
+
+ var/x0 = epicenter.x
+ var/y0 = epicenter.y
+ var/z0 = epicenter.get_virtual_z_level()
+ var/area/areatype = get_area(epicenter)
+ SSblackbox.record_feedback("associative", "explosion", 1, list("dev" = devastation_range, "heavy" = heavy_impact_range, "light" = light_impact_range, "flash" = flash_range, "flame" = flame_range, "orig_dev" = orig_dev_range, "orig_heavy" = orig_heavy_range, "orig_light" = orig_light_range, "x" = x0, "y" = y0, "z" = z0, "area" = areatype.type, "time" = time_stamp("YYYY-MM-DD hh:mm:ss", 1)))
+
+ // Play sounds; we want sounds to be different depending on distance so we will manually do it ourselves.
+ // Stereo users will also hear the direction of the explosion!
+
+ // Calculate far explosion sound range. Only allow the sound effect for heavy/devastating explosions.
+ // 3/7/14 will calculate to 80 + 35
+
+ var/far_dist = 0
+ far_dist += heavy_impact_range * 15
+ far_dist += devastation_range * 20
+
+ if(!silent)
+ var/frequency = get_rand_frequency()
+ var/sound/explosion_sound = sound(get_sfx("explosion"))
+ var/sound/far_explosion_sound = sound('sound/effects/explosionfar.ogg')
+ var/sound/creaking_explosion_sound = sound(get_sfx("explosion_creaking"))
+ var/sound/hull_creaking_sound = sound(get_sfx("hull_creaking"))
+ var/sound/explosion_echo_sound = sound('sound/effects/explosion_distant.ogg')
+ var/on_station = SSmapping.level_trait(epicenter.z, ZTRAIT_STATION)
+ var/creaking_explosion = FALSE
+
+ if(prob(devastation_range*DEVASTATION_PROB+heavy_impact_range*HEAVY_IMPACT_PROB) && on_station) // Huge explosions are near guaranteed to make the station creak and whine, smaller ones might.
+ creaking_explosion = TRUE // prob over 100 always returns true
+
+ for(var/MN in GLOB.player_list)
+ var/mob/M = MN
+ // Double check for client
+ var/turf/M_turf = get_turf(M)
+ var/multiz_dist = multi_z_dist(M_turf, epicenter)
+ if(M_turf && multiz_dist <= 5000)
+ var/dist = multiz_dist
+ var/baseshakeamount
+ if(orig_max_distance - dist > 0)
+ baseshakeamount = sqrt((orig_max_distance - dist)*0.1)
+ // If inside the blast radius + world.view (x) - 2
+ if(dist <= round(max_range + getviewsize(world.view)[1] - 2, 1))
+ M.playsound_local(epicenter, null, 100, 1, frequency, falloff_exponent = 5, S = explosion_sound)
+ if(baseshakeamount > 0)
+ shake_camera(M, 25, clamp(baseshakeamount, 0, 10))
+ // You hear a far explosion if you're outside the blast radius. Small bombs shouldn't be heard all over the station.
+ else if(dist <= far_dist)
+ var/far_volume = clamp(far_dist/2, FAR_LOWER, FAR_UPPER) // Volume is based on explosion size and dist
+ if(creaking_explosion)
+ M.playsound_local(epicenter, null, far_volume, 1, frequency, S = creaking_explosion_sound, distance_multiplier = 0)
+ else if(prob(PROB_SOUND)) // Sound variety during meteor storm/tesloose/other bad event
+ M.playsound_local(epicenter, null, far_volume, 1, frequency, S = far_explosion_sound, distance_multiplier = 0) // Far sound
+ else
+ M.playsound_local(epicenter, null, far_volume, 1, frequency, S = explosion_echo_sound, distance_multiplier = 0) // Echo sound
+
+ if(baseshakeamount > 0 || devastation_range)
+ if(!baseshakeamount) // Devastating explosions rock the station and ground
+ baseshakeamount = devastation_range*3
+ shake_camera(M, 10, clamp(baseshakeamount*0.25, 0, SHAKE_CLAMP))
+ else if(!isspaceturf(get_turf(M)) && heavy_impact_range) // Big enough explosions echo throughout the hull
+ var/echo_volume = 40
+ if(devastation_range)
+ baseshakeamount = devastation_range
+ shake_camera(M, 10, clamp(baseshakeamount*0.25, 0, SHAKE_CLAMP))
+ echo_volume = 60
+ M.playsound_local(epicenter, null, echo_volume, 1, frequency, S = explosion_echo_sound, distance_multiplier = 0)
+
+ if(creaking_explosion) // 5 seconds after the bang, the station begins to creak
+ addtimer(CALLBACK(M, /mob/proc/playsound_local, epicenter, null, rand(FREQ_LOWER, FREQ_UPPER), 1, frequency, null, null, TRUE, hull_creaking_sound, 0), CREAK_DELAY)
+
+ if(heavy_impact_range > 1)
+ var/datum/effect_system/explosion/E
+ if(smoke)
+ E = new /datum/effect_system/explosion/smoke
+ else
+ E = new
+ E.set_up(epicenter)
+ E.start()
+
+ //flash mobs
+ if(flash_range)
+ for(var/mob/living/L in viewers(flash_range, epicenter))
+ L.flash_act()
+
+ var/list/affected_turfs = GatherSpiralTurfs(max_range, epicenter)
+
+ var/reactionary = CONFIG_GET(flag/reactionary_explosions)
+ var/list/cached_exp_block
+
+ if(reactionary)
+ cached_exp_block = CaculateExplosionBlock(affected_turfs)
+
+ //lists are guaranteed to contain at least 1 turf at this point
+
+ for(var/TI in affected_turfs)
+ var/turf/T = TI
+ var/init_dist = cheap_hypotenuse(T.x, T.y, x0, y0)
+ var/dist = init_dist
+
+ if(reactionary)
+ var/turf/Trajectory = T
+ while(Trajectory != epicenter)
+ Trajectory = get_step_towards(Trajectory, epicenter)
+ dist += cached_exp_block[Trajectory]
+
+ var/flame_dist = dist < flame_range
+
+ if(T.get_virtual_z_level() != z0)
+ dist = EXPLODE_NONE
+ else if(dist < devastation_range)
+ dist = EXPLODE_DEVASTATE
+ else if(dist < heavy_impact_range)
+ dist = EXPLODE_HEAVY
+ else if(dist < light_impact_range)
+ dist = EXPLODE_LIGHT
+ else
+ dist = EXPLODE_NONE
+
+ if(T == epicenter) // Ensures explosives detonating from bags trigger other explosives in that bag
+ var/list/items = list()
+ for(var/I in T)
+ var/atom/A = I
+ if (length(A.contents) && !(A.flags_1 & PREVENT_CONTENTS_EXPLOSION_1)) //The atom/contents_explosion() proc returns null if the contents ex_acting has been handled by the atom, and TRUE if it hasn't.
+ items += A.GetAllContents(ignore_flag_1 = PREVENT_CONTENTS_EXPLOSION_1)
+ for(var/thing in items)
+ var/atom/movable/movable_thing = thing
+ if(QDELETED(movable_thing))
+ continue
+ switch(dist)
+ if(EXPLODE_DEVASTATE)
+ SSexplosions.high_mov_atom += movable_thing
+ if(EXPLODE_HEAVY)
+ SSexplosions.med_mov_atom += movable_thing
+ if(EXPLODE_LIGHT)
+ SSexplosions.low_mov_atom += movable_thing
+
+ switch(dist)
+ if(EXPLODE_DEVASTATE)
+ SSexplosions.highturf += T
+ if(EXPLODE_HEAVY)
+ SSexplosions.medturf += T
+ if(EXPLODE_LIGHT)
+ SSexplosions.lowturf += T
+
+ if(flame_dist && prob(40) && !isspaceturf(T) && !T.density)
+ flameturf += T
+
+ //--- THROW ITEMS AROUND ---
+ var/throw_dir
+ if(T == epicenter)
+ throw_dir = pick(list(NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST))
+ else
+ throw_dir = get_dir(epicenter,T)
+ var/throw_range = max_range * 1.5
+ var/list/throwingturf = T.explosion_throw_details
+ if (throwingturf)
+ if (throwingturf[1] < throw_range)
+ throwingturf[1] = throw_range
+ throwingturf[2] = throw_dir
+ throwingturf[3] = max_range
+ else
+ T.explosion_throw_details = list(throw_range, throw_dir, max_range)
+ throwturf += T
+
+ //Calculate above and below Zs
+ //Multi-z explosions only work on station levels.
+ if(explode_z)
+ var/max_z_range = max(devastation_range, heavy_impact_range, light_impact_range, flash_range, flame_range) / (MULTI_Z_DISTANCE + 1)
+ var/list/z_list = get_zs_in_range(epicenter.z, max_z_range)
+ //Dont blow up our level again
+ z_list -= epicenter.z
+ for(var/affecting_z in z_list)
+ var/z_reduction = abs(epicenter.z - affecting_z) * (MULTI_Z_DISTANCE + 1)
+ var/turf/T = locate(epicenter.x, epicenter.y, affecting_z)
+ if(!T)
+ continue
+ SSexplosions.explode(T,
+ max(devastation_range - z_reduction, 0),
+ max(heavy_impact_range - z_reduction, 0),
+ max(light_impact_range - z_reduction, 0),
+ max(flash_range - z_reduction, 0),
+ adminlog,
+ ignorecap,
+ max(flame_range - z_reduction, 0),
+ silent = TRUE,
+ smoke = FALSE,
+ explode_z = FALSE)
+
+ var/took = (REALTIMEOFDAY - started_at) / 10
+
+ //You need to press the DebugGame verb to see these now....they were getting annoying and we've collected a fair bit of data. Just -test- changes to explosion code using this please so we can compare
+ if(GLOB.Debug2)
+ log_world("## DEBUG: Explosion([x0],[y0],[z0])(d[devastation_range],h[heavy_impact_range],l[light_impact_range]): Took [took] seconds.")
+
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_EXPLOSION, epicenter, devastation_range, heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range)
+
+#undef CREAK_DELAY
+#undef DEVASTATION_PROB
+#undef HEAVY_IMPACT_PROB
+#undef FAR_UPPER
+#undef FAR_LOWER
+#undef PROB_SOUND
+#undef SHAKE_CLAMP
+#undef FREQ_UPPER
+#undef FREQ_LOWER
+
+/datum/controller/subsystem/explosions/proc/GatherSpiralTurfs(range, turf/epicenter)
+ var/list/outlist = list()
+ var/center = epicenter
+ var/dist = range
+ if(!dist)
+ outlist += center
+ return outlist
+
+ var/turf/t_center = get_turf(center)
+ if(!t_center)
+ return outlist
+
+ var/list/L = outlist
+ var/turf/T
+ var/y
+ var/x
+ var/c_dist = 1
+ L += t_center
+
+ while( c_dist <= dist )
+ y = t_center.y + c_dist
+ x = t_center.x - c_dist + 1
+ for(x in x to t_center.x+c_dist)
+ T = locate(x,y,t_center.z)
+ if(T)
+ L += T
+
+ y = t_center.y + c_dist - 1
+ x = t_center.x + c_dist
+ for(y in t_center.y-c_dist to y)
+ T = locate(x,y,t_center.z)
+ if(T)
+ L += T
+
+ y = t_center.y - c_dist
+ x = t_center.x + c_dist - 1
+ for(x in t_center.x-c_dist to x)
+ T = locate(x,y,t_center.z)
+ if(T)
+ L += T
+
+ y = t_center.y - c_dist + 1
+ x = t_center.x - c_dist
+ for(y in y to t_center.y+c_dist)
+ T = locate(x,y,t_center.z)
+ if(T)
+ L += T
+ c_dist++
+ . = L
+
+/datum/controller/subsystem/explosions/proc/CaculateExplosionBlock(list/affected_turfs)
+ . = list()
+ var/I
+ for(I in 1 to affected_turfs.len) // we cache the explosion block rating of every turf in the explosion area
+ var/turf/T = affected_turfs[I]
+ var/current_exp_block = T.density ? T.explosion_block : 0
+
+ for(var/obj/O in T)
+ var/the_block = O.explosion_block
+ current_exp_block += the_block == EXPLOSION_BLOCK_PROC ? O.GetExplosionBlock() : the_block
+
+ .[T] = current_exp_block
+
+/datum/controller/subsystem/explosions/fire(resumed = 0)
+ if (!is_exploding())
+ return
+ var/timer
+ Master.current_ticklimit = TICK_LIMIT_RUNNING //force using the entire tick if we need it.
+
+ if(currentpart == SSEXPLOSIONS_TURFS)
+ currentpart = SSEXPLOSIONS_MOVABLES
+
+ timer = TICK_USAGE_REAL
+ var/list/low_turf = lowturf
+ lowturf = list()
+ for(var/thing in low_turf)
+ var/turf/turf_thing = thing
+ turf_thing.explosion_level = max(turf_thing.explosion_level, EXPLODE_LIGHT)
+ turf_thing.ex_act(EXPLODE_LIGHT)
+ lowturf -= turf_thing
+ cost_lowturf = MC_AVERAGE(cost_lowturf, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
+
+ timer = TICK_USAGE_REAL
+ var/list/med_turf = medturf
+ medturf = list()
+ for(var/thing in med_turf)
+ var/turf/turf_thing = thing
+ turf_thing.explosion_level = max(turf_thing.explosion_level, EXPLODE_HEAVY)
+ turf_thing.ex_act(EXPLODE_HEAVY)
+ medturf -= turf_thing
+ cost_medturf = MC_AVERAGE(cost_medturf, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
+
+ timer = TICK_USAGE_REAL
+ var/list/high_turf = highturf
+ highturf = list()
+ for(var/thing in high_turf)
+ var/turf/turf_thing = thing
+ turf_thing.explosion_level = max(turf_thing.explosion_level, EXPLODE_DEVASTATE)
+ turf_thing.ex_act(EXPLODE_DEVASTATE)
+ highturf -= turf_thing
+ cost_highturf = MC_AVERAGE(cost_highturf, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
+
+ timer = TICK_USAGE_REAL
+ var/list/flame_turf = flameturf
+ flameturf = list()
+ for(var/thing in flame_turf)
+ if(thing)
+ var/turf/T = thing
+ new /obj/effect/hotspot(T) //Mostly for ambience!
+ cost_flameturf = MC_AVERAGE(cost_flameturf, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
+
+ if (low_turf.len || med_turf.len || high_turf.len)
+ Master.laggy_byond_map_update_incoming()
+
+ if(currentpart == SSEXPLOSIONS_MOVABLES)
+ currentpart = SSEXPLOSIONS_THROWS
+
+ timer = TICK_USAGE_REAL
+ var/list/local_high_mov_atom = high_mov_atom
+ high_mov_atom = list()
+ for(var/thing in local_high_mov_atom)
+ var/atom/movable/movable_thing = thing
+ if(QDELETED(movable_thing))
+ continue
+ movable_thing.ex_act(EXPLODE_DEVASTATE)
+ high_mov_atom -= movable_thing
+ cost_high_mov_atom = MC_AVERAGE(cost_high_mov_atom, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
+
+ timer = TICK_USAGE_REAL
+ var/list/local_med_mov_atom = med_mov_atom
+ med_mov_atom = list()
+ for(var/thing in local_med_mov_atom)
+ var/atom/movable/movable_thing = thing
+ if(QDELETED(movable_thing))
+ continue
+ movable_thing.ex_act(EXPLODE_HEAVY)
+ med_mov_atom -= movable_thing
+ cost_med_mov_atom = MC_AVERAGE(cost_med_mov_atom, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
+
+ timer = TICK_USAGE_REAL
+ var/list/local_low_mov_atom = low_mov_atom
+ low_mov_atom = list()
+ for(var/thing in local_low_mov_atom)
+ var/atom/movable/movable_thing = thing
+ if(QDELETED(movable_thing))
+ continue
+ movable_thing.ex_act(EXPLODE_LIGHT)
+ low_mov_atom -= movable_thing
+ cost_low_mov_atom = MC_AVERAGE(cost_low_mov_atom, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
+
+
+ if (currentpart == SSEXPLOSIONS_THROWS)
+ currentpart = SSEXPLOSIONS_TURFS
+ timer = TICK_USAGE_REAL
+ var/list/throw_turf = throwturf
+ throwturf = list()
+ for (var/thing in throw_turf)
+ if (!thing)
+ continue
+ var/turf/T = thing
+ var/list/L = T.explosion_throw_details
+ T.explosion_throw_details = null
+ if (length(L) != 3)
+ continue
+ var/throw_range = L[1]
+ var/throw_dir = L[2]
+ var/max_range = L[3]
+ for(var/atom/movable/A in T)
+ if(!A.anchored && A.move_resist != INFINITY)
+ var/atom_throw_range = rand(throw_range, max_range)
+ var/turf/throw_at = get_ranged_target_turf(A, throw_dir, atom_throw_range)
+ A.throw_at(throw_at, atom_throw_range, EXPLOSION_THROW_SPEED, quickstart = FALSE)
+ cost_throwturf = MC_AVERAGE(cost_throwturf, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
+
+ currentpart = SSEXPLOSIONS_TURFS
+
+#undef SSAIR_REBUILD_PIPENETS
diff --git a/code/controllers/subsystem/fail2topic.dm b/code/controllers/subsystem/fail2topic.dm
index b3b6982c50568..0cbd8872f0bcc 100644
--- a/code/controllers/subsystem/fail2topic.dm
+++ b/code/controllers/subsystem/fail2topic.dm
@@ -7,16 +7,15 @@ SUBSYSTEM_DEF(fail2topic)
var/list/rate_limiting = list()
var/list/fail_counts = list()
var/list/active_bans = list()
+ var/list/currentrun = list()
var/rate_limit
var/max_fails
- var/rule_name
var/enabled = FALSE
/datum/controller/subsystem/fail2topic/Initialize(timeofday)
rate_limit = ((CONFIG_GET(number/topic_rate_limit)) SECONDS)
max_fails = CONFIG_GET(number/topic_max_fails)
- rule_name = CONFIG_GET(string/topic_rule_name)
enabled = CONFIG_GET(flag/topic_enabled)
DropFirewallRule() // Clear the old bans if any still remain
@@ -29,24 +28,34 @@ SUBSYSTEM_DEF(fail2topic)
if (!enabled)
can_fire = FALSE
- . = ..()
+ return ..()
-/datum/controller/subsystem/fail2topic/fire()
- while (rate_limiting.len)
- var/ip = rate_limiting[1]
- var/last_attempt = rate_limiting[ip]
+/datum/controller/subsystem/fail2topic/fire(resumed = 0)
+ if(!resumed)
+ currentrun = rate_limiting.Copy()
+ //cache for sanic speed (lists are references anyways)
+ var/list/current_run = currentrun
- if (world.time - last_attempt > rate_limit)
+ while(current_run.len)
+ var/ip = current_run[current_run.len]
+ var/last_attempt = current_run[ip]
+ current_run.len--
+
+ // last_attempt list housekeeping
+ if(world.time - last_attempt > rate_limit)
rate_limiting -= ip
fail_counts -= ip
- if (MC_TICK_CHECK)
+ if(MC_TICK_CHECK)
return
/datum/controller/subsystem/fail2topic/Shutdown()
DropFirewallRule()
/datum/controller/subsystem/fail2topic/proc/IsRateLimited(ip)
+ if(!enabled)
+ return FALSE
+
var/last_attempt = rate_limiting[ip]
if (config.fail2topic_whitelisted_ips[ip])
@@ -79,12 +88,16 @@ SUBSYSTEM_DEF(fail2topic)
/datum/controller/subsystem/fail2topic/proc/BanFromFirewall(ip)
if (!enabled)
return
+ var/static/regex/R = regex(@"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$") // Anything that interacts with a shell should be parsed. Prevents direct call input tampering
+ ip = findtext(ip, R)
+ if(length(ip) > 15 || length(ip) < 8 )
+ return FALSE
active_bans[ip] = world.time
fail_counts -= ip
rate_limiting -= ip
- . = shell("netsh advfirewall firewall add rule name=\"[rule_name]\" dir=in interface=any action=block remoteip=[ip]")
+ . = shell("netsh advfirewall firewall add rule name=\"[CONFIG_GET(string/topic_rule_name)]\" dir=in interface=any action=block remoteip=[ip]")
if (.)
WARNING("Fail2topic failed to ban [ip]. Exit code: [.].")
@@ -99,7 +112,7 @@ SUBSYSTEM_DEF(fail2topic)
active_bans = list()
- . = shell("netsh advfirewall firewall delete rule name=\"[rule_name]\"")
+ . = shell("netsh advfirewall firewall delete rule name=\"[CONFIG_GET(string/topic_rule_name)]\"")
if (.)
WARNING("Fail2topic failed to drop firewall rule. Exit code: [.].")
diff --git a/code/controllers/subsystem/fire_burning.dm b/code/controllers/subsystem/fire_burning.dm
index 7f73f379ccbb7..e8f64033466b0 100644
--- a/code/controllers/subsystem/fire_burning.dm
+++ b/code/controllers/subsystem/fire_burning.dm
@@ -8,8 +8,13 @@ SUBSYSTEM_DEF(fire_burning)
var/list/processing = list()
/datum/controller/subsystem/fire_burning/stat_entry()
- ..("P:[processing.len]")
+ . = ..("P:[processing.len]")
+/datum/controller/subsystem/fire_burning/get_metrics()
+ . = ..()
+ var/list/cust = list()
+ cust["processing"] = length(processing)
+ .["custom"] = cust
/datum/controller/subsystem/fire_burning/fire(resumed = 0)
if (!resumed)
@@ -17,6 +22,7 @@ SUBSYSTEM_DEF(fire_burning)
//cache for sanic speed (lists are references anyways)
var/list/currentrun = src.currentrun
+ var/delta_time = wait * 0.1
while(currentrun.len)
var/obj/O = currentrun[currentrun.len]
@@ -30,7 +36,7 @@ SUBSYSTEM_DEF(fire_burning)
if(O.resistance_flags & ON_FIRE) //in case an object is extinguished while still in currentrun
if(!(O.resistance_flags & FIRE_PROOF))
- O.take_damage(20, BURN, "fire", 0)
+ O.take_damage(10 * delta_time, BURN, "fire", 0)
else
O.extinguish()
diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm
index 8370f6867461d..c1a38d951fd0b 100644
--- a/code/controllers/subsystem/garbage.dm
+++ b/code/controllers/subsystem/garbage.dm
@@ -6,7 +6,7 @@ SUBSYSTEM_DEF(garbage)
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
init_order = INIT_ORDER_GARBAGE
- var/list/collection_timeout = list(0, 2 MINUTES, 10 SECONDS) // deciseconds to wait before moving something up in the queue to the next level
+ var/list/collection_timeout = list(2 MINUTES, 10 SECONDS) // deciseconds to wait before moving something up in the queue to the next level
//Stat tracking
var/delslasttick = 0 // number of del()'s we've done this tick
@@ -24,11 +24,23 @@ SUBSYSTEM_DEF(garbage)
//Queue
var/list/queues
-
- #ifdef TESTING
+ #ifdef LEGACY_REFERENCE_TRACKING
var/list/reference_find_on_fail = list()
#endif
+/datum/controller/subsystem/garbage/get_metrics()
+ . = ..()
+ var/list/cust = list()
+ // You can calculate TGCR in kibana
+ cust["total_harddels"] = totaldels
+ cust["total_softdels"] = totalgcs
+ var/i = 0
+ for(var/list/L in queues)
+ i++
+ cust["queue_[i]"] = length(L)
+
+ .["custom"] = cust
+
/datum/controller/subsystem/garbage/PreInit()
queues = new(GC_QUEUE_COUNT)
@@ -57,7 +69,7 @@ SUBSYSTEM_DEF(garbage)
msg += "TGR:[round((totalgcs/(totaldels+totalgcs))*100, 0.01)]%"
msg += " P:[pass_counts.Join(",")]"
msg += "|F:[fail_counts.Join(",")]"
- ..(msg)
+ . = ..(msg)
/datum/controller/subsystem/garbage/Shutdown()
//Adds the del() log to the qdel log file
@@ -117,18 +129,22 @@ SUBSYSTEM_DEF(garbage)
lastlevel = level
- for (var/refID in queue)
- if (!refID)
+ //We do this rather then for(var/refID in queue) because that sort of for loop copies the whole list.
+ //Normally this isn't expensive, but the gc queue can grow to 40k items, and that gets costly/causes overrun.
+ for (var/i in 1 to length(queue))
+ var/list/L = queue[i]
+ if (length(L) < 2)
count++
if (MC_TICK_CHECK)
return
continue
- var/GCd_at_time = queue[refID]
+ var/GCd_at_time = L[1]
if(GCd_at_time > cut_off_time)
break // Everything else is newer, skip them
count++
+ var/refID = L[2]
var/datum/D
D = locate(refID)
@@ -136,8 +152,8 @@ SUBSYSTEM_DEF(garbage)
++gcedlasttick
++totalgcs
pass_counts[level]++
- #ifdef TESTING
- reference_find_on_fail -= refID //It's deleted we don't care anymore.
+ #ifdef LEGACY_REFERENCE_TRACKING
+ reference_find_on_fail -= refID //It's deleted we don't care anymore.
#endif
if (MC_TICK_CHECK)
return
@@ -145,20 +161,41 @@ SUBSYSTEM_DEF(garbage)
// Something's still referring to the qdel'd object.
fail_counts[level]++
+
+ #ifdef REFERENCE_TRACKING
+ var/ref_searching = FALSE
+ #endif
+
switch (level)
if (GC_QUEUE_CHECK)
- #ifdef TESTING
+ #ifdef REFERENCE_TRACKING
+ D.find_references()
+ #elif defined(LEGACY_REFERENCE_TRACKING)
if(reference_find_on_fail[refID])
- D.find_references()
+ INVOKE_ASYNC(D, /datum/proc/find_references_legacy)
+ ref_searching = TRUE
#ifdef GC_FAILURE_HARD_LOOKUP
else
- D.find_references()
+ INVOKE_ASYNC(D, /datum/proc/find_references_legacy)
+ ref_searching = TRUE
#endif
reference_find_on_fail -= refID
#endif
var/type = D.type
var/datum/qdel_item/I = items[type]
- testing("GC: -- \ref[src] | [type] was unable to be GC'd --")
+
+ log_world("## TESTING: GC: -- \ref[D] | [type] was unable to be GC'd --")
+ #ifdef TESTING
+ for(var/c in GLOB.admins) //Using testing() here would fill the logs with ADMIN_VV garbage
+ var/client/admin = c
+ if(!check_rights_for(admin, R_ADMIN))
+ continue
+ to_chat(admin, "## TESTING: GC: -- [ADMIN_VV(D)] | [type] was unable to be GC'd --")
+ #endif
+ #ifdef REFERENCE_TRACKING
+ GLOB.deletion_failures += D //It should no longer be bothered by the GC, manual deletion only.
+ continue
+ #endif
I.failures++
if (GC_QUEUE_HARDDELETE)
HardDelete(D)
@@ -168,6 +205,11 @@ SUBSYSTEM_DEF(garbage)
Queue(D, level+1)
+ #ifdef REFERENCE_TRACKING
+ if(ref_searching)
+ return
+ #endif
+
if (MC_TICK_CHECK)
return
if (count)
@@ -185,10 +227,8 @@ SUBSYSTEM_DEF(garbage)
D.gc_destroyed = gctime
var/list/queue = queues[level]
- if (queue[refid])
- queue -= refid // Removing any previous references that were GC'd so that the current object will be at the end of the list.
- queue[refid] = gctime
+ queue[++queue.len] = list(gctime, refid) // not += for byond reasons
//this is mainly to separate things profile wise.
/datum/controller/subsystem/garbage/proc/HardDelete(datum/D)
@@ -242,24 +282,25 @@ SUBSYSTEM_DEF(garbage)
/datum/qdel_item/New(mytype)
name = "[mytype]"
-#ifdef TESTING
-/proc/qdel_and_find_ref_if_fail(datum/D, force = FALSE)
- SSgarbage.reference_find_on_fail[REF(D)] = TRUE
- qdel(D, force)
-#endif
// Should be treated as a replacement for the 'del' keyword.
// Datums passed to this will be given a chance to clean up references to allow the GC to collect them.
/proc/qdel(datum/D, force=FALSE, ...)
+ if(isweakref(D))
+ var/datum/weakref/weakref = D
+ D = weakref.resolve()
+ if(!D)
+ return
+
if(!istype(D))
del(D)
return
+
var/datum/qdel_item/I = SSgarbage.items[D.type]
if (!I)
I = SSgarbage.items[D.type] = new /datum/qdel_item(D.type)
I.qdels++
-
if(isnull(D.gc_destroyed))
if (SEND_SIGNAL(D, COMSIG_PARENT_PREQDELETED, force)) // Give the components a chance to prevent their parent from being deleted
return
@@ -301,16 +342,14 @@ SUBSYSTEM_DEF(garbage)
SSgarbage.Queue(D, GC_QUEUE_HARDDELETE)
if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste.
SSgarbage.HardDelete(D)
- if (QDEL_HINT_FINDREFERENCE)//qdel will, if TESTING is enabled, display all references to this object, then queue the object for deletion.
+ #ifdef LEGACY_REFERENCE_TRACKING
+ if (QDEL_HINT_FINDREFERENCE) //qdel will, if LEGACY_REFERENCE_TRACKING is enabled, display all references to this object, then queue the object for deletion.
SSgarbage.Queue(D)
- #ifdef TESTING
- D.find_references()
- #endif
+ D.find_references_legacy()
if (QDEL_HINT_IFFAIL_FINDREFERENCE)
SSgarbage.Queue(D)
- #ifdef TESTING
SSgarbage.reference_find_on_fail[REF(D)] = TRUE
- #endif
+ #endif
else
#ifdef TESTING
if(!I.no_hint)
@@ -320,116 +359,3 @@ SUBSYSTEM_DEF(garbage)
SSgarbage.Queue(D)
else if(D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
CRASH("[D.type] destroy proc was called multiple times, likely due to a qdel loop in the Destroy logic")
-
-#ifdef TESTING
-
-/datum/verb/find_refs()
- set category = "Debug"
- set name = "Find References"
- set src in world
-
- find_references(FALSE)
-
-/datum/proc/find_references(skip_alert)
- running_find_references = type
- if(usr?.client)
- if(usr.client.running_find_references)
- testing("CANCELLED search for references to a [usr.client.running_find_references].")
- usr.client.running_find_references = null
- running_find_references = null
- //restart the garbage collector
- SSgarbage.can_fire = 1
- SSgarbage.next_fire = world.time + world.tick_lag
- return
-
- if(!skip_alert)
- if(alert("Running this will lock everything up for about 5 minutes. Would you like to begin the search?", "Find References", "Yes", "No") == "No")
- running_find_references = null
- return
-
- //this keeps the garbage collector from failing to collect objects being searched for in here
- SSgarbage.can_fire = 0
-
- if(usr && usr.client)
- usr.client.running_find_references = type
-
- testing("Beginning search for references to a [type].")
- last_find_references = world.time
-
- DoSearchVar(GLOB) //globals
- for(var/datum/thing in world) //atoms (don't beleive it's lies)
- DoSearchVar(thing, "World -> [thing]")
-
- for (var/datum/thing) //datums
- DoSearchVar(thing, "World -> [thing]")
-
- for (var/client/thing) //clients
- DoSearchVar(thing, "World -> [thing]")
-
- testing("Completed search for references to a [type].")
- if(usr && usr.client)
- usr.client.running_find_references = null
- running_find_references = null
-
- //restart the garbage collector
- SSgarbage.can_fire = 1
- SSgarbage.next_fire = world.time + world.tick_lag
-
-/datum/verb/qdel_then_find_references()
- set category = "Debug"
- set name = "qdel() then Find References"
- set src in world
-
- qdel(src, TRUE) //Force.
- if(!running_find_references)
- find_references(TRUE)
-
-/datum/verb/qdel_then_if_fail_find_references()
- set category = "Debug"
- set name = "qdel() then Find References if GC failure"
- set src in world
-
- qdel_and_find_ref_if_fail(src, TRUE)
-
-/datum/proc/DoSearchVar(X, Xname, recursive_limit = 64)
- if(usr && usr.client && !usr.client.running_find_references)
- return
- if (!recursive_limit)
- return
-
- if(istype(X, /datum))
- var/datum/D = X
- if(D.last_find_references == last_find_references)
- return
-
- D.last_find_references = last_find_references
- var/list/L = D.vars
-
- for(var/varname in L)
- if (varname == "vars")
- continue
- var/variable = L[varname]
-
- if(variable == src)
- testing("Found [src.type] \ref[src] in [D.type]'s [varname] var. [Xname]")
-
- else if(islist(variable))
- DoSearchVar(variable, "[Xname] -> list", recursive_limit-1)
-
- else if(islist(X))
- var/normal = IS_NORMAL_LIST(X)
- for(var/I in X)
- if (I == src)
- testing("Found [src.type] \ref[src] in list [Xname].")
-
- else if (I && !isnum_safe(I) && normal && X[I] == src)
- testing("Found [src.type] \ref[src] in list [Xname]\[[I]\]")
-
- else if (islist(I))
- DoSearchVar(I, "[Xname] -> list", recursive_limit-1)
-
-#ifndef FIND_REF_NO_CHECK_TICK
- CHECK_TICK
-#endif
-
-#endif
diff --git a/code/controllers/subsystem/idlenpcpool.dm b/code/controllers/subsystem/idlenpcpool.dm
index 8992fa7b92fd1..53cf6aa5a7328 100644
--- a/code/controllers/subsystem/idlenpcpool.dm
+++ b/code/controllers/subsystem/idlenpcpool.dm
@@ -11,7 +11,7 @@ SUBSYSTEM_DEF(idlenpcpool)
/datum/controller/subsystem/idlenpcpool/stat_entry()
var/list/idlelist = GLOB.simple_animals[AI_IDLE]
var/list/zlist = GLOB.simple_animals[AI_Z_OFF]
- ..("IdleNPCS:[idlelist.len]|Z:[zlist.len]")
+ . = ..("IdleNPCS:[idlelist.len]|Z:[zlist.len]")
/datum/controller/subsystem/idlenpcpool/proc/MaxZChanged()
if (!islist(idle_mobs_by_zlevel))
@@ -32,8 +32,10 @@ SUBSYSTEM_DEF(idlenpcpool)
while(currentrun.len)
var/mob/living/simple_animal/SA = currentrun[currentrun.len]
--currentrun.len
- if (!SA)
- GLOB.simple_animals[AI_IDLE] -= SA
+
+ if(!SA)
+ stack_trace("Null entry found at GLOB.simple_animals\[AI_IDLE\]. Null entries will be purged. Yell at coderbus. Subsystem will try to continue.")
+ removeNullsFromList(GLOB.simple_animals[AI_IDLE])
continue
if(!SA.ckey)
diff --git a/code/controllers/subsystem/input.dm b/code/controllers/subsystem/input.dm
index 70604bfba0d40..dc0d868a1d856 100644
--- a/code/controllers/subsystem/input.dm
+++ b/code/controllers/subsystem/input.dm
@@ -32,22 +32,22 @@ SUBSYSTEM_DEF(input)
"default" = list(
"Tab" = "\".winset \\\"input.focus=true?map.focus=true input.background-color=[COLOR_INPUT_DISABLED]:input.focus=true input.background-color=[COLOR_INPUT_ENABLED]\\\"\"",
"O" = "ooc",
- "T" = "say",
- "M" = "me",
+ "T" = "\".winset \\\"command=\\\".start_typing say\\\";command=.init_say;saywindow.is-visible=true;saywindow.input.focus=true;saywindow.input.text=\\\"\\\"\\\"\"",
+ "M" = "\".winset \\\"command=\\\".start_typing me\\\";command=.init_me;mewindow.is-visible=true;mewindow.input.focus=true;mewindow.input.text=\\\"\\\"\\\"\"",
"Back" = "\".winset \\\"input.text=\\\"\\\"\\\"\"", // This makes it so backspace can remove default inputs
"Any" = "\"KeyDown \[\[*\]\]\"",
"Any+UP" = "\"KeyUp \[\[*\]\]\"",
),
"old_default" = list(
"Tab" = "\".winset \\\"mainwindow.macro=old_hotkeys map.focus=true input.background-color=[COLOR_INPUT_DISABLED]\\\"\"",
- "Ctrl+T" = "say",
+ "Ctrl+T" = "\".winset \\\"command=\\\".start_typing say\\\";command=.init_say;saywindow.is-visible=true;saywindow.input.focus=true;saywindow.input.text=\\\"\\\"\\\"\"",
"Ctrl+O" = "ooc",
),
"old_hotkeys" = list(
"Tab" = "\".winset \\\"mainwindow.macro=old_default input.focus=true input.background-color=[COLOR_INPUT_ENABLED]\\\"\"",
"O" = "ooc",
- "T" = "say",
- "M" = "me",
+ "T" = "\".winset \\\"command=\\\".start_typing say\\\";command=.init_say;saywindow.is-visible=true;saywindow.input.focus=true;saywindow.input.text=\\\"\\\"\\\"\"",
+ "M" = "\".winset \\\"command=\\\".start_typing me\\\";command=.init_me;mewindow.is-visible=true;mewindow.input.focus=true;mewindow.input.text=\\\"\\\"\\\"\"",
"Back" = "\".winset \\\"input.text=\\\"\\\"\\\"\"", // This makes it so backspace can remove default inputs
"Any" = "\"KeyDown \[\[*\]\]\"",
"Any+UP" = "\"KeyUp \[\[*\]\]\"",
@@ -109,7 +109,5 @@ SUBSYSTEM_DEF(input)
user.set_macros()
/datum/controller/subsystem/input/fire()
- var/list/clients = GLOB.clients // Let's sing the list cache song
- for(var/i in 1 to clients.len)
- var/client/C = clients[i]
- C.keyLoop()
+ for(var/mob/user as anything in GLOB.player_list)
+ user?.focus?.keyLoop(user.client)
diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm
index ea3b19dec1fcb..043bbb768a184 100644
--- a/code/controllers/subsystem/job.dm
+++ b/code/controllers/subsystem/job.dm
@@ -16,6 +16,16 @@ SUBSYSTEM_DEF(job)
var/list/level_order = list(JP_HIGH,JP_MEDIUM,JP_LOW)
+ var/spare_id_safe_code = ""
+
+ var/list/chain_of_command = list(
+ "Captain" = 1, //Not used yet but captain is first in chain_of_command
+ "Head of Personnel" = 2,
+ "Research Director" = 3,
+ "Chief Engineer" = 4,
+ "Chief Medical Officer" = 5,
+ "Head of Security" = 6)
+
/datum/controller/subsystem/job/Initialize(timeofday)
SSmapping.HACK_LoadMapConfig()
if(!occupations.len)
@@ -24,17 +34,22 @@ SUBSYSTEM_DEF(job)
LoadJobs()
generate_selectable_species()
set_overflow_role(CONFIG_GET(string/overflow_job))
+
+ spare_id_safe_code = "[rand(0,9)][rand(0,9)][rand(0,9)][rand(0,9)][rand(0,9)]"
+
return ..()
/datum/controller/subsystem/job/proc/set_overflow_role(new_overflow_role)
var/datum/job/new_overflow = GetJob(new_overflow_role)
var/cap = CONFIG_GET(number/overflow_cap)
+ new_overflow.allow_bureaucratic_error = FALSE
new_overflow.spawn_positions = cap
new_overflow.total_positions = cap
if(new_overflow_role != overflow_role)
var/datum/job/old_overflow = GetJob(overflow_role)
+ old_overflow.allow_bureaucratic_error = initial(old_overflow.allow_bureaucratic_error)
old_overflow.spawn_positions = initial(old_overflow.spawn_positions)
old_overflow.total_positions = initial(old_overflow.total_positions)
overflow_role = new_overflow_role
@@ -238,7 +253,7 @@ SUBSYSTEM_DEF(job)
* fills var "assigned_role" for all ready players.
* This proc must not have any side effect besides of modifying "assigned_role".
**/
-/datum/controller/subsystem/job/proc/DivideOccupations()
+/datum/controller/subsystem/job/proc/DivideOccupations(list/required_jobs)
//Setup new player list and get the jobs list
JobDebug("Running DO")
@@ -252,13 +267,16 @@ SUBSYSTEM_DEF(job)
//Get the players who are ready
for(var/mob/dead/new_player/player in GLOB.player_list)
if(player.ready == PLAYER_READY_TO_PLAY && player.mind && !player.mind.assigned_role)
- unassigned += player
+ if(!player.has_valid_preferences())
+ player.ready = PLAYER_NOT_READY
+ else
+ unassigned += player
initial_players_to_assign = unassigned.len
JobDebug("DO, Len: [unassigned.len]")
if(unassigned.len == 0)
- return TRUE
+ return validate_required_jobs(required_jobs)
//Scale number of open security officer slots to population
setup_officer_positions()
@@ -363,7 +381,26 @@ SUBSYSTEM_DEF(job)
if(!GiveRandomJob(player))
if(!AssignRole(player, SSjob.overflow_role)) //If everything is already filled, make them an assistant
return FALSE //Living on the edge, the forced antagonist couldn't be assigned to overflow role (bans, client age) - just reroll
- return TRUE
+
+ return validate_required_jobs(required_jobs)
+
+/datum/controller/subsystem/job/proc/validate_required_jobs(list/required_jobs)
+ if(!required_jobs.len)
+ return TRUE
+ for(var/required_group in required_jobs)
+ var/group_ok = TRUE
+ for(var/rank in required_group)
+ var/datum/job/J = GetJob(rank)
+ if(!J)
+ SSticker.mode.setup_error = "Invalid job [rank] in gamemode required jobs."
+ return FALSE
+ if(J.current_positions < required_group[rank])
+ group_ok = FALSE
+ break
+ if(group_ok)
+ return TRUE
+ SSticker.mode.setup_error = "Required jobs not present."
+ return FALSE
//We couldn't find a job from prefs for this guy.
/datum/controller/subsystem/job/proc/HandleUnassigned(mob/dead/new_player/player)
@@ -404,21 +441,32 @@ SUBSYSTEM_DEF(job)
//If we joined at roundstart we should be positioned at our workstation
if(!joined_late)
+ var/spawning_handled = FALSE
var/obj/S = null
- for(var/obj/effect/landmark/start/sloc in GLOB.start_landmarks_list)
- if(sloc.name != rank)
- S = sloc //so we can revert to spawning them on top of eachother if something goes wrong
- continue
- if(locate(/mob/living) in sloc.loc)
- continue
- S = sloc
- sloc.used = TRUE
- break
- if(length(GLOB.jobspawn_overrides[rank]))
+ if(HAS_TRAIT(SSstation, STATION_TRAIT_LATE_ARRIVALS) && job.random_spawns_possible)
+ SendToLateJoin(living_mob)
+ spawning_handled = TRUE
+ else if(HAS_TRAIT(SSstation, STATION_TRAIT_RANDOM_ARRIVALS) && job.random_spawns_possible)
+ DropLandAtRandomHallwayPoint(living_mob)
+ spawning_handled = TRUE
+ else if(HAS_TRAIT(SSstation, STATION_TRAIT_HANGOVER) && job.random_spawns_possible)
+ SpawnLandAtRandomHallwayPoint(living_mob)
+ spawning_handled = TRUE
+ else if(length(GLOB.jobspawn_overrides[rank]))
S = pick(GLOB.jobspawn_overrides[rank])
+ else
+ for(var/obj/effect/landmark/start/sloc in GLOB.start_landmarks_list)
+ if(sloc.name != rank)
+ S = sloc //so we can revert to spawning them on top of eachother if something goes wrong
+ continue
+ if(locate(/mob/living) in sloc.loc)
+ continue
+ S = sloc
+ sloc.used = TRUE
+ break
if(S)
S.JoinPlayerHere(living_mob, FALSE)
- if(!S) //if there isn't a spawnpoint send them to latejoin, if there's no latejoin go yell at your mapper
+ if(!S && !spawning_handled) //if there isn't a spawnpoint send them to latejoin, if there's no latejoin go yell at your mapper
log_world("Couldn't find a round start spawn point for [rank]")
SendToLateJoin(living_mob)
@@ -452,7 +500,7 @@ SUBSYSTEM_DEF(job)
var/mob/living/carbon/human/wageslave = living_mob
living_mob.add_memory("Your account ID is [wageslave.account_id].")
if(job && living_mob)
- job.after_spawn(living_mob, M, joined_late) // note: this happens before the mob has a key! M will always have a client, H might not.
+ job.after_spawn(living_mob, M, joined_late) // note: this happens before the mob has a key! M will always have a client, living_mob might not.
var/tries = 5
while(M.mind && !M.mind.crew_objectives.len && tries)
@@ -506,9 +554,11 @@ SUBSYSTEM_DEF(job)
if(J.flag == GIMMICK || J.gimmick) //gimmick job slots are dependant on random maint
continue
var/regex/jobs = new("[J.title]=(-1|\\d+),(-1|\\d+)")
- jobs.Find(jobstext)
- J.total_positions = text2num(jobs.group[1])
- J.spawn_positions = text2num(jobs.group[2])
+ if(jobs.Find(jobstext))
+ J.total_positions = text2num(jobs.group[1])
+ J.spawn_positions = text2num(jobs.group[2])
+ else
+ log_runtime("Error in /datum/controller/subsystem/job/proc/LoadJobs: Failed to locate job of title [J.title] in jobs.txt")
/datum/controller/subsystem/job/proc/HandleFeedbackGathering()
for(var/datum/job/job in occupations)
@@ -637,6 +687,27 @@ SUBSYSTEM_DEF(job)
message_admins(msg)
CRASH(msg)
+///Spawns specified mob at a random spot in the hallways
+/datum/controller/subsystem/job/proc/SpawnLandAtRandomHallwayPoint(mob/living/living_mob)
+ var/turf/spawn_turf = get_safe_random_station_turfs(typesof(/area/hallway))
+
+ if(!spawn_turf)
+ SendToLateJoin(living_mob)
+ return
+
+ living_mob.forceMove(spawn_turf)
+
+///Lands specified mob at a random spot in the hallways
+/datum/controller/subsystem/job/proc/DropLandAtRandomHallwayPoint(mob/living/living_mob)
+ var/turf/spawn_turf = get_safe_random_station_turfs(typesof(/area/hallway))
+
+ if(!spawn_turf)
+ SendToLateJoin(living_mob)
+ return
+
+ var/obj/structure/closet/supplypod/centcompod/toLaunch = new()
+ living_mob.forceMove(toLaunch)
+ new /obj/effect/pod_landingzone(spawn_turf, toLaunch)
///////////////////////////////////
//Keeps track of all living heads//
@@ -678,3 +749,40 @@ SUBSYSTEM_DEF(job)
/datum/controller/subsystem/job/proc/JobDebug(message)
log_job_debug(message)
+
+/obj/item/paper/fluff/spare_id_safe_code
+ name = "Nanotrasen-Approved Spare ID Safe Code"
+ desc = "Proof that you have been approved for Captaincy, with all its glory and all its horror."
+
+/obj/item/paper/fluff/spare_id_safe_code/Initialize()
+ . = ..()
+ var/id_safe_code = SSjob.spare_id_safe_code
+ info = "Captain's Spare ID safe code combination: [id_safe_code ? id_safe_code : "\[REDACTED\]"]
The spare ID can be found in its dedicated safe on the bridge."
+
+/datum/controller/subsystem/job/proc/promote_to_captain(var/mob/dead/new_player/new_captain, acting_captain = FALSE)
+ var/mob/living/carbon/human/H = new_captain.new_character
+ if(!new_captain)
+ CRASH("Cannot promote [new_captain.ckey], there is no new_character attached to him.")
+
+ if(!spare_id_safe_code)
+ CRASH("Cannot promote [H.real_name] to Captain, there is no spare_id_safe_code.")
+
+ var/paper = new /obj/item/paper/fluff/spare_id_safe_code(H.loc)
+ var/list/slots = list(
+ "in your left pocket" = ITEM_SLOT_LPOCKET,
+ "in your right pocket" = ITEM_SLOT_RPOCKET,
+ "in your backpack" = ITEM_SLOT_BACKPACK,
+ "in your hands" = ITEM_SLOT_HANDS
+ )
+ var/where = H.equip_in_one_of_slots(paper, slots, FALSE) || "at your feet"
+
+ if(acting_captain)
+ to_chat(new_captain, "Due to your position in the chain of command, you have been granted access to captain's spare ID. You can find in important note about this [where].")
+ else
+ to_chat(new_captain, "You can find the code to obtain your spare ID from the secure safe on the Bridge [where].")
+
+ // Force-give their ID card bridge access.
+ if(H.wear_id?.GetID())
+ var/obj/item/card/id/id_card = H.wear_id
+ if(!(ACCESS_HEADS in id_card.access))
+ LAZYADD(id_card.access, ACCESS_HEADS)
diff --git a/code/controllers/subsystem/lighting.dm b/code/controllers/subsystem/lighting.dm
index ab16a633819fe..98fe2315455c8 100644
--- a/code/controllers/subsystem/lighting.dm
+++ b/code/controllers/subsystem/lighting.dm
@@ -9,8 +9,15 @@ SUBSYSTEM_DEF(lighting)
flags = SS_TICKER
/datum/controller/subsystem/lighting/stat_entry()
- ..("L:[GLOB.lighting_update_lights.len]|C:[GLOB.lighting_update_corners.len]|O:[GLOB.lighting_update_objects.len]")
-
+ . = ..("L:[GLOB.lighting_update_lights.len]|C:[GLOB.lighting_update_corners.len]|O:[GLOB.lighting_update_objects.len]")
+
+/datum/controller/subsystem/lighting/get_metrics()
+ . = ..()
+ var/list/cust = list()
+ cust["sources_queue"] = length(GLOB.lighting_update_lights)
+ cust["corners_queue"] = length(GLOB.lighting_update_corners)
+ cust["objects_queue"] = length(GLOB.lighting_update_objects)
+ .["custom"] = cust
/datum/controller/subsystem/lighting/Initialize(timeofday)
if(!initialized)
diff --git a/code/controllers/subsystem/machines.dm b/code/controllers/subsystem/machines.dm
index c4b09d1b87410..e382e07b0842e 100644
--- a/code/controllers/subsystem/machines.dm
+++ b/code/controllers/subsystem/machines.dm
@@ -2,6 +2,7 @@ SUBSYSTEM_DEF(machines)
name = "Machines"
init_order = INIT_ORDER_MACHINES
flags = SS_KEEP_TIMING
+ wait = 2 SECONDS
var/list/processing = list()
var/list/currentrun = list()
var/list/powernets = list()
@@ -11,6 +12,13 @@ SUBSYSTEM_DEF(machines)
fire()
return ..()
+
+/datum/controller/subsystem/machines/get_metrics()
+ . = ..()
+ var/list/cust = list()
+ cust["processing"] = length(processing)
+ .["custom"] = cust
+
/datum/controller/subsystem/machines/proc/makepowernets()
for(var/datum/powernet/PN in powernets)
qdel(PN)
@@ -23,7 +31,7 @@ SUBSYSTEM_DEF(machines)
propagate_network(PC,PC.powernet)
/datum/controller/subsystem/machines/stat_entry()
- ..("M:[processing.len]|PN:[powernets.len]")
+ . = ..("M:[processing.len]|PN:[powernets.len]")
/datum/controller/subsystem/machines/fire(resumed = 0)
@@ -35,11 +43,10 @@ SUBSYSTEM_DEF(machines)
//cache for sanic speed (lists are references anyways)
var/list/currentrun = src.currentrun
- var/seconds = wait * 0.1
while(currentrun.len)
var/obj/machinery/thing = currentrun[currentrun.len]
currentrun.len--
- if(!QDELETED(thing) && thing.process(seconds) != PROCESS_KILL)
+ if(!QDELETED(thing) && thing.process(wait * 0.1) != PROCESS_KILL)
if(thing.use_power)
thing.auto_use_power() //add back the power state
else
diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm
index 8b398130c03e8..93c8f9de00e1a 100644
--- a/code/controllers/subsystem/mapping.dm
+++ b/code/controllers/subsystem/mapping.dm
@@ -1,3 +1,5 @@
+//#define FORCE_MAP "_maps/boxstation.json"
+
SUBSYSTEM_DEF(mapping)
name = "Mapping"
init_order = INIT_ORDER_MAPPING
@@ -19,7 +21,9 @@ SUBSYSTEM_DEF(mapping)
var/list/shuttle_templates = list()
var/list/shelter_templates = list()
+
var/list/random_room_templates = list()
+ var/list/holodeck_templates = list()
var/list/areas_in_z = list()
@@ -28,6 +32,10 @@ SUBSYSTEM_DEF(mapping)
var/list/datum/turf_reservations //list of turf reservations
var/list/used_turfs = list() //list of turf = datum/turf_reservation
+ ///All possible biomes in assoc list as type || instance
+ var/list/biomes = list()
+
+
var/clearing_reserved_turfs = FALSE
// Z-manager stuff
@@ -57,19 +65,21 @@ SUBSYSTEM_DEF(mapping)
if(!config || config.defaulted)
to_chat(world, "Unable to load next or default map config, defaulting to Box Station")
config = old_config
+ initialize_biomes()
loadWorld()
repopulate_sorted_areas()
process_teleport_locs() //Sets up the wizard teleport locations
preloadTemplates()
+
#ifndef LOWMEMORYMODE
// Create space ruin levels
while (space_levels_so_far < config.space_ruin_levels)
++space_levels_so_far
- add_new_zlevel("Empty Area [space_levels_so_far]", ZTRAITS_SPACE)
+ LAZYADD(SSzclear.free_levels, add_new_zlevel("Empty Area [space_levels_so_far]", ZTRAITS_SPACE, orbital_body_type = null))
// and one level with no ruins
for (var/i in 1 to config.space_empty_levels)
++space_levels_so_far
- empty_space = add_new_zlevel("Empty Area [space_levels_so_far]", list(ZTRAIT_LINKAGE = CROSSLINKED))
+ empty_space = add_new_zlevel("Empty Area [space_levels_so_far]", list(ZTRAIT_LINKAGE = CROSSLINKED), orbital_body_type = /datum/orbital_object/z_linked/beacon/weak)
// and the transit level
transit = add_new_zlevel("Transit/Reserved", list(ZTRAIT_RESERVED = TRUE))
@@ -90,13 +100,10 @@ SUBSYSTEM_DEF(mapping)
seedRuins(lava_ruins, CONFIG_GET(number/lavaland_budget), /area/lavaland/surface/outdoors/unexplored, lava_ruins_templates)
for (var/lava_z in lava_ruins)
spawn_rivers(lava_z)
-
- // Generate deep space ruins
- var/list/space_ruins = levels_by_trait(ZTRAIT_SPACE_RUINS)
- if (space_ruins.len)
- seedRuins(space_ruins, CONFIG_GET(number/space_budget), /area/space, space_ruins_templates)
loading_ruins = FALSE
#endif
+ // Run map generation after ruin generation to prevent issues
+ run_map_generation()
repopulate_sorted_areas()
// Set up Z-level transitions.
setup_map_transitions()
@@ -134,7 +141,7 @@ SUBSYSTEM_DEF(mapping)
qdel(T, TRUE)
/* Nuke threats, for making the blue tiles on the station go RED
- Used by the AI doomsday and the self destruct nuke.
+ Used by the AI doomsday and the self-destruct nuke.
*/
/datum/controller/subsystem/mapping/proc/add_nuke_threat(datum/nuke)
@@ -167,6 +174,7 @@ SUBSYSTEM_DEF(mapping)
unused_turfs = SSmapping.unused_turfs
turf_reservations = SSmapping.turf_reservations
used_turfs = SSmapping.used_turfs
+ holodeck_templates = SSmapping.holodeck_templates
config = SSmapping.config
next_map_config = SSmapping.next_map_config
@@ -176,7 +184,7 @@ SUBSYSTEM_DEF(mapping)
z_list = SSmapping.z_list
#define INIT_ANNOUNCE(X) to_chat(world, "[X]"); log_world(X)
-/datum/controller/subsystem/mapping/proc/LoadGroup(list/errorList, name, path, files, list/traits, list/default_traits, silent = FALSE)
+/datum/controller/subsystem/mapping/proc/LoadGroup(list/errorList, name, path, files, list/traits, list/default_traits, silent = FALSE, orbital_body_type)
. = list()
var/start_time = REALTIMEOFDAY
@@ -209,15 +217,22 @@ SUBSYSTEM_DEF(mapping)
// preload the relevant space_level datums
var/start_z = world.maxz + 1
var/i = 0
+ var/list/datum/space_level/space_levels = list()
for (var/level in traits)
- add_new_zlevel("[name][i ? " [i + 1]" : ""]", level)
+ space_levels += add_new_zlevel("[name][i ? " [i + 1]" : ""]", level)
++i
+ //Shared orbital body
+ var/datum/orbital_object/z_linked/orbital_body = new orbital_body_type()
+ for(var/datum/space_level/level as() in space_levels)
+ level.orbital_body = orbital_body
+ orbital_body.link_to_z(level)
// load the maps
for (var/P in parsed_maps)
var/datum/parsed_map/pm = P
if (!pm.load(1, 1, start_z + parsed_maps[P], no_changeturf = TRUE))
errorList |= pm.original_path
+
if(!silent)
INIT_ANNOUNCE("Loaded [name] in [(REALTIMEOFDAY - start_time)/10]s!")
return parsed_maps
@@ -232,7 +247,7 @@ SUBSYSTEM_DEF(mapping)
// load the station
station_start = world.maxz + 1
INIT_ANNOUNCE("Loading [config.map_name]...")
- LoadGroup(FailedZs, "Station", config.map_path, config.map_file, config.traits, ZTRAITS_STATION)
+ LoadGroup(FailedZs, "Station", config.map_path, config.map_file, config.traits, ZTRAITS_STATION, orbital_body_type = /datum/orbital_object/z_linked/station)
if(SSdbcore.Connect())
var/datum/DBQuery/query_round_map_name = SSdbcore.NewQuery({"
@@ -245,11 +260,11 @@ SUBSYSTEM_DEF(mapping)
// TODO: remove this when the DB is prepared for the z-levels getting reordered
while (world.maxz < (5 - 1) && space_levels_so_far < config.space_ruin_levels)
++space_levels_so_far
- add_new_zlevel("Empty Area [space_levels_so_far]", ZTRAITS_SPACE)
+ LAZYADD(SSzclear.free_levels, add_new_zlevel("Empty Area [space_levels_so_far]", ZTRAITS_SPACE, orbital_body_type = null))
// load mining
if(config.minetype == "lavaland")
- LoadGroup(FailedZs, "Lavaland", "map_files/Mining", "Lavaland.dmm", default_traits = ZTRAITS_LAVALAND)
+ LoadGroup(FailedZs, "Lavaland", "map_files/Mining", "Lavaland.dmm", default_traits = ZTRAITS_LAVALAND, orbital_body_type = /datum/orbital_object/z_linked/lavaland)
else if (!isnull(config.minetype))
INIT_ANNOUNCE("WARNING: An unknown minetype '[config.minetype]' was set! This is being ignored! Update the maploader code!")
#endif
@@ -267,10 +282,10 @@ GLOBAL_LIST_EMPTY(the_station_areas)
/datum/controller/subsystem/mapping/proc/generate_station_area_list()
var/list/station_areas_blacklist = typecacheof(list(/area/space, /area/mine, /area/ruin, /area/asteroid/nearstation))
- for(var/area/A in world)
+ for(var/area/A in GLOB.sortedAreas)
if (is_type_in_typecache(A, station_areas_blacklist))
continue
- if (!A.contents.len || !A.unique)
+ if (!A.contents.len || !(A.area_flags & UNIQUE_AREA))
continue
var/turf/picked = A.contents[1]
if (is_station_level(picked.z))
@@ -279,6 +294,10 @@ GLOBAL_LIST_EMPTY(the_station_areas)
if(!GLOB.the_station_areas.len)
log_world("ERROR: Station areas list failed to generate!")
+/datum/controller/subsystem/mapping/proc/run_map_generation()
+ for(var/area/A in world)
+ A.RunGeneration()
+
/datum/controller/subsystem/mapping/proc/maprotate()
if(map_voted)
map_voted = FALSE
@@ -352,6 +371,7 @@ GLOBAL_LIST_EMPTY(the_station_areas)
preloadShuttleTemplates()
preloadShelterTemplates()
preloadRandomRoomTemplates()
+ preloadHolodeckTemplates()
/datum/controller/subsystem/mapping/proc/preloadRandomRoomTemplates()
for(var/item in subtypesof(/datum/map_template/random_room))
@@ -386,7 +406,8 @@ GLOBAL_LIST_EMPTY(the_station_areas)
space_ruins_templates[R.name] = R
/datum/controller/subsystem/mapping/proc/preloadShuttleTemplates()
- var/list/unbuyable = generateMapList("[global.config.directory]/unbuyableshuttles.txt")
+ var/list/unbuyable = generateMapList("[global.config.directory]/shuttles_unbuyable.txt")
+ var/list/illegal = generateMapList("[global.config.directory]/shuttles_illegal.txt")
for(var/item in subtypesof(/datum/map_template/shuttle))
var/datum/map_template/shuttle/shuttle_type = item
@@ -396,6 +417,8 @@ GLOBAL_LIST_EMPTY(the_station_areas)
var/datum/map_template/shuttle/S = new shuttle_type()
if(unbuyable.Find(S.mappath))
S.can_be_bought = FALSE
+ if(illegal.Find(S.mappath))
+ S.illegal_shuttle = TRUE
shuttle_templates[S.shuttle_id] = S
map_templates[S.shuttle_id] = S
@@ -410,6 +433,15 @@ GLOBAL_LIST_EMPTY(the_station_areas)
shelter_templates[S.shelter_id] = S
map_templates[S.shelter_id] = S
+/datum/controller/subsystem/mapping/proc/preloadHolodeckTemplates()
+ for(var/item in subtypesof(/datum/map_template/holodeck))
+ var/datum/map_template/holodeck/holodeck_type = item
+ if(!(initial(holodeck_type.mappath)))
+ continue
+ var/datum/map_template/holodeck/holo_template = new holodeck_type()
+
+ holodeck_templates[holo_template.template_id] = holo_template
+
//Manual loading of away missions.
/client/proc/admin_away()
set name = "Load Away Mission"
@@ -530,7 +562,10 @@ GLOBAL_LIST_EMPTY(the_station_areas)
used_turfs.Cut()
reserve_turfs(clearing)
-
+/datum/controller/subsystem/mapping/proc/initialize_biomes()
+ for(var/biome_path in subtypesof(/datum/biome))
+ var/datum/biome/biome_instance = new biome_path()
+ biomes[biome_path] += biome_instance
/datum/controller/subsystem/mapping/proc/reg_in_areas_in_z(list/areas)
for(var/B in areas)
diff --git a/code/controllers/subsystem/metrics.dm b/code/controllers/subsystem/metrics.dm
new file mode 100644
index 0000000000000..0c9f2dfd27572
--- /dev/null
+++ b/code/controllers/subsystem/metrics.dm
@@ -0,0 +1,63 @@
+/*
+
+ Ok listen up
+
+ This thing ingests data to ElasticSearch in a VERY SPECIFIC FORMAT
+ Not only is changing this a very bad idea due to elasticsearch being very finnicky with data formatting,
+ but if you edit this subsystem and its fields, you invalidate a lot of existing data
+
+ Dont touch this shit without speaking to crossed or AA07 first
+
+*/
+SUBSYSTEM_DEF(metrics)
+ name = "Metrics"
+ wait = 30 SECONDS
+ runlevels = RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME // ALL THE LEVELS
+ flags = SS_KEEP_TIMING // This needs to ingest every 30 IRL seconds, not ingame seconds.
+ /// The real time of day the server started. Used to calculate time drift
+ var/world_init_time = 0 // Not set in here. Set in world/New()
+
+/datum/controller/subsystem/metrics/Initialize(start_timeofday)
+ if(!CONFIG_GET(flag/elasticsearch_metrics_enabled))
+ flags |= SS_NO_FIRE // Disable firing to save CPU
+ return ..()
+
+
+/datum/controller/subsystem/metrics/fire(resumed)
+ var/datum/http_request/request = new()
+ request.prepare(RUSTG_HTTP_METHOD_POST, CONFIG_GET(string/elasticsearch_metrics_endpoint), get_metrics_json(), list(
+ "Authorization" = "ApiKey [CONFIG_GET(string/elasticsearch_metrics_apikey)]",
+ "Content-Type" = "application/json"
+ ))
+ request.begin_async() // Fire and forget who gives a shit
+
+/datum/controller/subsystem/metrics/proc/get_metrics_json()
+ var/list/out = list()
+ out["@timestamp"] = iso_timestamp() // This is required by ElasticSearch, complete with this name. DO NOT REMOVE THIS.
+ out["cpu"] = world.cpu
+ out["maptick"] = world.map_cpu
+ out["elapsed_processed"] = world.time
+ out["elapsed_real"] = (REALTIMEOFDAY - world_init_time)
+ out["client_count"] = length(GLOB.clients)
+ out["round_id"] = text2num(GLOB.round_id) // This is so we can filter the metrics by a single round ID
+
+ var/server_name = CONFIG_GET(string/serversqlname)
+ if(server_name)
+ out["server_name"] = server_name
+
+ // Funnel in all SS metrics
+ var/list/ss_data = list()
+ for(var/datum/controller/subsystem/SS in Master.subsystems)
+ ss_data[SS.ss_id] = SS.get_metrics()
+
+ out["subsystems"] = ss_data
+ // And send it all
+ return json_encode(out)
+
+/*
+
+// Uncomment this if you add new metrics to verify how the JSON formats
+
+/client/verb/debug_metrics()
+ usr << browse(SSmetrics.get_metrics_json(), "window=aadebug")
+*/
diff --git a/code/controllers/subsystem/mobs.dm b/code/controllers/subsystem/mobs.dm
index f783aca919693..c91afcdf7aa9d 100644
--- a/code/controllers/subsystem/mobs.dm
+++ b/code/controllers/subsystem/mobs.dm
@@ -3,14 +3,23 @@ SUBSYSTEM_DEF(mobs)
priority = FIRE_PRIORITY_MOBS
flags = SS_KEEP_TIMING | SS_NO_INIT
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
+ wait = 2 SECONDS
var/list/currentrun = list()
var/static/list/clients_by_zlevel[][]
var/static/list/dead_players_by_zlevel[][] = list(list()) // Needs to support zlevel 1 here, MaxZChanged only happens when z2 is created and new_players can login before that.
var/static/list/cubemonkeys = list()
+ var/datum/spawners_menu/spawner_menu
+
/datum/controller/subsystem/mobs/stat_entry()
- ..("P:[GLOB.mob_living_list.len]")
+ . = ..("P:[GLOB.mob_living_list.len]")
+
+/datum/controller/subsystem/mobs/get_metrics()
+ . = ..()
+ var/list/cust = list()
+ cust["processing"] = length(GLOB.mob_living_list)
+ .["custom"] = cust
/datum/controller/subsystem/mobs/proc/MaxZChanged()
if (!islist(clients_by_zlevel))
@@ -22,8 +31,7 @@ SUBSYSTEM_DEF(mobs)
dead_players_by_zlevel.len++
dead_players_by_zlevel[dead_players_by_zlevel.len] = list()
-/datum/controller/subsystem/mobs/fire(resumed = 0)
- var/seconds = wait * 0.1
+/datum/controller/subsystem/mobs/fire(resumed = FALSE)
if (!resumed)
src.currentrun = GLOB.mob_living_list.Copy()
@@ -47,7 +55,7 @@ SUBSYSTEM_DEF(mobs)
break
var/msg = "[ADMIN_LOOKUPFLW(M)] was found to have no .loc with an attached client, if the cause is unknown it would be wise to ask how this was accomplished."
message_admins(msg)
- send2irc_adminless_only("Mob", msg, R_ADMIN)
+ send2tgs_adminless_only("Mob", msg, R_ADMIN)
log_game("[key_name(M)] was found to have no .loc with an attached client.")
// This is a temporary error tracker to make sure we've caught everything
@@ -62,8 +70,13 @@ SUBSYSTEM_DEF(mobs)
var/mob/living/L = currentrun[currentrun.len]
currentrun.len--
if(L)
- L.Life(seconds, times_fired)
+ L.Life(wait * 0.1, times_fired)
else
GLOB.mob_living_list.Remove(L)
if (MC_TICK_CHECK)
return
+
+/datum/controller/subsystem/mobs/proc/update_spawners()
+ if(!spawner_menu)
+ return
+ spawner_menu.ui_update()
diff --git a/code/controllers/subsystem/moods.dm b/code/controllers/subsystem/moods.dm
index f6b6ffcb40453..20111747bdb2d 100644
--- a/code/controllers/subsystem/moods.dm
+++ b/code/controllers/subsystem/moods.dm
@@ -2,3 +2,4 @@ PROCESSING_SUBSYSTEM_DEF(mood)
name = "Mood"
flags = SS_NO_INIT | SS_BACKGROUND
priority = 20
+ wait = 1 SECONDS
diff --git a/code/controllers/subsystem/nightshift.dm b/code/controllers/subsystem/nightshift.dm
index ec416a3632900..8bae5bcec617b 100644
--- a/code/controllers/subsystem/nightshift.dm
+++ b/code/controllers/subsystem/nightshift.dm
@@ -28,6 +28,11 @@ SUBSYSTEM_DEF(nightshift)
var/announcing = TRUE
var/time = station_time()
var/night_time = (time < nightshift_end_time) || (time > nightshift_start_time)
+ if(!SSmapping.config.allow_night_lighting)
+ if(night_time)
+ night_time = FALSE
+ update_nightshift(night_time, FALSE)
+ return
if(high_security_mode != emergency)
high_security_mode = emergency
if(night_time)
@@ -35,7 +40,7 @@ SUBSYSTEM_DEF(nightshift)
if(!emergency)
announce("Restoring night lighting configuration to normal operation.")
else
- announce("Disabling night lighting: Station is in a state of emergency.")
+ announce("Disabling night lighting: Station is in a state of emergency.")
if(emergency)
night_time = FALSE
if(nightshift_active != night_time)
diff --git a/code/controllers/subsystem/npcpool.dm b/code/controllers/subsystem/npcpool.dm
index 611ca56b1d6a4..5ce747e717064 100644
--- a/code/controllers/subsystem/npcpool.dm
+++ b/code/controllers/subsystem/npcpool.dm
@@ -8,7 +8,7 @@ SUBSYSTEM_DEF(npcpool)
/datum/controller/subsystem/npcpool/stat_entry()
var/list/activelist = GLOB.simple_animals[AI_ON]
- ..("NPCS:[activelist.len]")
+ . = ..("NPCS:[activelist.len]")
/datum/controller/subsystem/npcpool/fire(resumed = FALSE)
@@ -23,6 +23,11 @@ SUBSYSTEM_DEF(npcpool)
var/mob/living/simple_animal/SA = currentrun[currentrun.len]
--currentrun.len
+ if(!SA)
+ stack_trace("Null entry found at GLOB.simple_animals\[AI_ON\]. Null entries will be purged. Yell at coderbus. Subsystem will try to continue.")
+ removeNullsFromList(GLOB.simple_animals[AI_ON])
+ continue
+
if(!SA.ckey && !SA.notransform)
if(SA.stat != DEAD)
SA.handle_automated_movement()
@@ -31,11 +36,6 @@ SUBSYSTEM_DEF(npcpool)
if(SA.stat != DEAD)
SA.handle_automated_speech()
if(SA.special_process)
- var/mob/living/simple_animal/slime/S = SA
- if(istype(S))
- S.special_process = FALSE
- S.AIprocess()
- else
- SA.process()
+ SA.process()
if (MC_TICK_CHECK)
return
diff --git a/code/controllers/subsystem/overlays.dm b/code/controllers/subsystem/overlays.dm
index b7c424fb84122..535cc71a0d9b6 100644
--- a/code/controllers/subsystem/overlays.dm
+++ b/code/controllers/subsystem/overlays.dm
@@ -23,7 +23,7 @@ SUBSYSTEM_DEF(overlays)
/datum/controller/subsystem/overlays/stat_entry()
- ..("Ov:[length(queue)]")
+ . = ..("Ov:[length(queue)]")
/datum/controller/subsystem/overlays/Shutdown()
diff --git a/code/controllers/subsystem/pai.dm b/code/controllers/subsystem/pai.dm
index 8edcf8fd1192e..a0cda6a09f075 100644
--- a/code/controllers/subsystem/pai.dm
+++ b/code/controllers/subsystem/pai.dm
@@ -141,6 +141,9 @@ SUBSYSTEM_DEF(pai)
return FALSE
/datum/controller/subsystem/pai/proc/findPAI(obj/item/paicard/p, mob/user)
+ if(!(GLOB.ghost_role_flags & GHOSTROLE_SILICONS))
+ to_chat(user, "Due to growing incidents of SELF corrupted independent artificial intelligences, freeform personality devices have been temporarily banned in this sector.")
+ return
if(!ghost_spam)
ghost_spam = TRUE
for(var/mob/dead/observer/G in GLOB.player_list)
diff --git a/code/controllers/subsystem/parallax.dm b/code/controllers/subsystem/parallax.dm
index af5b13cea669f..492b41a915f63 100644
--- a/code/controllers/subsystem/parallax.dm
+++ b/code/controllers/subsystem/parallax.dm
@@ -14,7 +14,7 @@ SUBSYSTEM_DEF(parallax)
/datum/controller/subsystem/parallax/PreInit()
. = ..()
if(prob(70)) //70% chance to pick a special extra layer
- random_layer = pick(/obj/screen/parallax_layer/random/space_gas, /obj/screen/parallax_layer/random/asteroids)
+ random_layer = pick(/atom/movable/screen/parallax_layer/random/space_gas, /atom/movable/screen/parallax_layer/random/asteroids)
random_parallax_color = pick(COLOR_TEAL, COLOR_GREEN, COLOR_SILVER, COLOR_YELLOW, COLOR_CYAN, COLOR_ORANGE, COLOR_PURPLE)//Special color for random_layer1. Has to be done here so everyone sees the same color.
planet_y_offset = rand(100, 160)
planet_x_offset = rand(100, 160)
diff --git a/code/controllers/subsystem/pathfinder.dm b/code/controllers/subsystem/pathfinder.dm
index 8e1cf946ae19d..26f54245732e1 100644
--- a/code/controllers/subsystem/pathfinder.dm
+++ b/code/controllers/subsystem/pathfinder.dm
@@ -3,13 +3,11 @@ SUBSYSTEM_DEF(pathfinder)
init_order = INIT_ORDER_PATH
flags = SS_NO_FIRE
var/datum/flowcache/mobs
- var/datum/flowcache/circuits
var/static/space_type_cache
/datum/controller/subsystem/pathfinder/Initialize()
space_type_cache = typecacheof(/turf/open/space)
mobs = new(10)
- circuits = new(3)
return ..()
/datum/flowcache
@@ -18,7 +16,7 @@ SUBSYSTEM_DEF(pathfinder)
var/free
var/list/flow
-/datum/flowcache/New(var/n)
+/datum/flowcache/New(n)
. = ..()
lcount = n
run = 0
diff --git a/code/controllers/subsystem/persistence.dm b/code/controllers/subsystem/persistence.dm
index c7f33c232531d..3bd76a94d1306 100644
--- a/code/controllers/subsystem/persistence.dm
+++ b/code/controllers/subsystem/persistence.dm
@@ -13,7 +13,9 @@ SUBSYSTEM_DEF(persistence)
var/list/antag_rep_change = list()
var/list/picture_logging_information = list()
var/list/obj/structure/sign/picture_frame/photo_frames
+ var/list/obj/structure/sign/painting/painting_frames = list()
var/list/obj/item/storage/photo_album/photo_albums
+ var/list/paintings = list()
/datum/controller/subsystem/persistence/Initialize()
LoadPoly()
@@ -23,6 +25,7 @@ SUBSYSTEM_DEF(persistence)
LoadPhotoPersistence()
if(CONFIG_GET(flag/use_antag_rep))
LoadAntagReputation()
+ LoadPaintings()
return ..()
/datum/controller/subsystem/persistence/proc/LoadPoly()
@@ -127,7 +130,7 @@ SUBSYSTEM_DEF(persistence)
var/list/chosen_trophy = trophy_data
- if(!chosen_trophy || isemptylist(chosen_trophy)) //Malformed
+ if(!chosen_trophy || !length(chosen_trophy)) //Malformed
continue
var/path = text2path(chosen_trophy["path"]) //If the item no longer exist, this returns null
@@ -146,6 +149,7 @@ SUBSYSTEM_DEF(persistence)
SavePhotoPersistence() //THIS IS PERSISTENCE, NOT THE LOGGING PORTION.
if(CONFIG_GET(flag/use_antag_rep))
CollectAntagReputation()
+ SavePaintings()
/datum/controller/subsystem/persistence/proc/GetPhotoAlbums()
var/album_path = file("data/photo_albums.json")
@@ -282,3 +286,18 @@ SUBSYSTEM_DEF(persistence)
fdel(FILE_ANTAG_REP)
rustg_file_append(json_encode(antag_rep), FILE_ANTAG_REP)
+/datum/controller/subsystem/persistence/proc/LoadPaintings()
+ var/json_file = file("data/paintings.json")
+ if(fexists(json_file))
+ paintings = json_decode(file2text(json_file))
+
+ for(var/obj/structure/sign/painting/P in painting_frames)
+ P.load_persistent()
+
+/datum/controller/subsystem/persistence/proc/SavePaintings()
+ for(var/obj/structure/sign/painting/P in painting_frames)
+ P.save_persistent()
+
+ var/json_file = file("data/paintings.json")
+ fdel(json_file)
+ WRITE_FILE(json_file, json_encode(paintings))
diff --git a/code/controllers/subsystem/processing/ai_basic_avoidance.dm b/code/controllers/subsystem/processing/ai_basic_avoidance.dm
new file mode 100644
index 0000000000000..2a3fe992f4473
--- /dev/null
+++ b/code/controllers/subsystem/processing/ai_basic_avoidance.dm
@@ -0,0 +1,4 @@
+PROCESSING_SUBSYSTEM_DEF(basic_avoidance)
+ name = "Basic Avoidance"
+ flags = SS_NO_INIT
+ wait = 2 SECONDS
diff --git a/code/controllers/subsystem/processing/ai_behaviors.dm b/code/controllers/subsystem/processing/ai_behaviors.dm
new file mode 100644
index 0000000000000..4c98567405cc4
--- /dev/null
+++ b/code/controllers/subsystem/processing/ai_behaviors.dm
@@ -0,0 +1,20 @@
+/// The subsystem used to tick [/datum/ai_behavior] instances. Handling the individual actions an AI can take like punching someone in the fucking NUTS
+PROCESSING_SUBSYSTEM_DEF(ai_behaviors)
+ name = "AI Behavior Ticker"
+ flags = SS_POST_FIRE_TIMING|SS_BACKGROUND
+ priority = FIRE_PRIORITY_NPC_ACTIONS
+ runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
+ init_order = INIT_ORDER_AI_CONTROLLERS
+ wait = 1
+ ///List of all ai_behavior singletons, key is the typepath while assigned value is a newly created instance of the typepath. See SetupAIBehaviors()
+ var/list/ai_behaviors
+
+/datum/controller/subsystem/processing/ai_behaviors/Initialize(timeofday)
+ SetupAIBehaviors()
+ return ..()
+
+/datum/controller/subsystem/processing/ai_behaviors/proc/SetupAIBehaviors()
+ ai_behaviors = list()
+ for(var/behavior_type in subtypesof(/datum/ai_behavior))
+ var/datum/ai_behavior/ai_behavior = new behavior_type
+ ai_behaviors[behavior_type] = ai_behavior
diff --git a/code/controllers/subsystem/processing/ai_controllers.dm b/code/controllers/subsystem/processing/ai_controllers.dm
new file mode 100644
index 0000000000000..5319d7316fb9b
--- /dev/null
+++ b/code/controllers/subsystem/processing/ai_controllers.dm
@@ -0,0 +1,33 @@
+/// The subsystem used to tick [/datum/ai_controllers] instances. Handling the re-checking of plans.
+SUBSYSTEM_DEF(ai_controllers)
+ name = "AI Controller Ticker"
+ flags = SS_POST_FIRE_TIMING|SS_BACKGROUND
+ priority = FIRE_PRIORITY_NPC
+ runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
+ init_order = INIT_ORDER_AI_CONTROLLERS
+ wait = 0.5 SECONDS //Plan every half second if required, not great not terrible.
+
+ ///List of all ai_subtree singletons, key is the typepath while assigned value is a newly created instance of the typepath. See setup_subtrees()
+ var/list/ai_subtrees = list()
+ ///List of all ai controllers currently running
+ var/list/active_ai_controllers = list()
+
+/datum/controller/subsystem/ai_controllers/Initialize(timeofday)
+ setup_subtrees()
+ return ..()
+
+/datum/controller/subsystem/ai_controllers/proc/setup_subtrees()
+ ai_subtrees = list()
+ for(var/subtree_type in subtypesof(/datum/ai_planning_subtree))
+ var/datum/ai_planning_subtree/subtree = new subtree_type
+ ai_subtrees[subtree_type] = subtree
+
+/datum/controller/subsystem/ai_controllers/fire(resumed)
+ for(var/datum/ai_controller/ai_controller as anything in active_ai_controllers)
+ if(!COOLDOWN_FINISHED(ai_controller, failed_planning_cooldown))
+ continue
+
+ if(!LAZYLEN(ai_controller.current_behaviors))
+ ai_controller.SelectBehaviors(wait * 0.1)
+ if(!LAZYLEN(ai_controller.current_behaviors)) //Still no plan
+ COOLDOWN_START(ai_controller, failed_planning_cooldown, AI_FAILED_PLANNING_COOLDOWN)
diff --git a/code/controllers/subsystem/processing/ai_movement.dm b/code/controllers/subsystem/processing/ai_movement.dm
new file mode 100644
index 0000000000000..6a6d64548ca78
--- /dev/null
+++ b/code/controllers/subsystem/processing/ai_movement.dm
@@ -0,0 +1,21 @@
+/// The subsystem used to tick [/datum/ai_movement] instances. Handling the movement of individual AI instances
+PROCESSING_SUBSYSTEM_DEF(ai_movement)
+ name = "AI movement"
+ flags = SS_KEEP_TIMING|SS_BACKGROUND
+ priority = FIRE_PRIORITY_NPC_MOVEMENT
+ runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
+ init_order = INIT_ORDER_AI_MOVEMENT
+ wait = 1
+
+ ///an assoc list of all ai_movement types. Assoc type to instance
+ var/list/movement_types
+
+/datum/controller/subsystem/processing/ai_movement/Initialize(timeofday)
+ SetupAIMovementInstances()
+ return ..()
+
+/datum/controller/subsystem/processing/ai_movement/proc/SetupAIMovementInstances()
+ movement_types = list()
+ for(var/key as anything in subtypesof(/datum/ai_movement))
+ var/datum/ai_movement/ai_movement = new key
+ movement_types[key] = ai_movement
diff --git a/code/controllers/subsystem/processing/circuit.dm b/code/controllers/subsystem/processing/circuit.dm
deleted file mode 100644
index 96bf274fd26ee..0000000000000
--- a/code/controllers/subsystem/processing/circuit.dm
+++ /dev/null
@@ -1,91 +0,0 @@
-PROCESSING_SUBSYSTEM_DEF(circuit)
- name = "Circuit"
- stat_tag = "CIR"
- init_order = INIT_ORDER_CIRCUIT
- flags = NONE
-
- var/cipherkey
-
- var/list/all_components = list() // Associative list of [component_name]:[component_path] pairs
- var/list/cached_components = list() // Associative list of [component_path]:[component] pairs
- var/list/all_assemblies = list() // Associative list of [assembly_name]:[assembly_path] pairs
- var/list/cached_assemblies = list() // Associative list of [assembly_path]:[assembly] pairs
- var/list/all_circuits = list() // Associative list of [circuit_name]:[circuit_path] pairs
- var/list/circuit_fabricator_recipe_list = list() // Associative list of [category_name]:[list_of_circuit_paths] pairs
- var/cost_multiplier = MINERAL_MATERIAL_AMOUNT / 10 // Each circuit cost unit is 200cm3
-
-/datum/controller/subsystem/processing/circuit/Initialize(start_timeofday)
- SScircuit.cipherkey = uppertext(random_string(2000+rand(0,10), GLOB.alphabet))
- circuits_init()
- return ..()
-
-/datum/controller/subsystem/processing/circuit/proc/circuits_init()
- //Cached lists for free performance
- for(var/path in typesof(/obj/item/integrated_circuit))
- var/obj/item/integrated_circuit/IC = path
- var/name = initial(IC.name)
- all_components[name] = path // Populating the component lists
- cached_components[IC] = new path
-
- if(!(initial(IC.spawn_flags) & (IC_SPAWN_DEFAULT | IC_SPAWN_RESEARCH)))
- continue
-
- var/category = initial(IC.category_text)
- if(!circuit_fabricator_recipe_list[category])
- circuit_fabricator_recipe_list[category] = list()
- var/list/category_list = circuit_fabricator_recipe_list[category]
- category_list += IC // Populating the fabricator categories
-
- for(var/path in typesof(/obj/item/electronic_assembly))
- var/obj/item/electronic_assembly/A = path
- var/name = initial(A.name)
- all_assemblies[name] = path
- cached_assemblies[A] = new path
-
-
- circuit_fabricator_recipe_list["Assemblies"] = list(
- /obj/item/electronic_assembly/default,
- /obj/item/electronic_assembly/calc,
- /obj/item/electronic_assembly/clam,
- /obj/item/electronic_assembly/simple,
- /obj/item/electronic_assembly/hook,
- /obj/item/electronic_assembly/pda,
- /obj/item/electronic_assembly/small/default,
- /obj/item/electronic_assembly/small/cylinder,
- /obj/item/electronic_assembly/small/scanner,
- /obj/item/electronic_assembly/small/hook,
- /obj/item/electronic_assembly/small/box,
- /obj/item/electronic_assembly/medium/default,
- /obj/item/electronic_assembly/medium/box,
- /obj/item/electronic_assembly/medium/clam,
- /obj/item/electronic_assembly/medium/medical,
- /obj/item/electronic_assembly/medium/gun,
- /obj/item/electronic_assembly/medium/radio,
- /obj/item/electronic_assembly/large/default,
- /obj/item/electronic_assembly/large/scope,
- /obj/item/electronic_assembly/large/terminal,
- /obj/item/electronic_assembly/large/arm,
- /obj/item/electronic_assembly/large/tall,
- /obj/item/electronic_assembly/large/industrial,
- /obj/item/electronic_assembly/drone/default,
- /obj/item/electronic_assembly/drone/arms,
- /obj/item/electronic_assembly/drone/secbot,
- /obj/item/electronic_assembly/drone/medbot,
- /obj/item/electronic_assembly/drone/genbot,
- /obj/item/electronic_assembly/drone/android,
- /obj/item/electronic_assembly/wallmount/tiny,
- /obj/item/electronic_assembly/wallmount/light,
- /obj/item/electronic_assembly/wallmount,
- /obj/item/electronic_assembly/wallmount/heavy
- ///obj/item/weapon/implant/integrated_circuit
- )
-
- circuit_fabricator_recipe_list["Tools"] = list(
- /obj/item/integrated_electronics/wirer,
- /obj/item/integrated_electronics/debugger,
- /obj/item/integrated_electronics/analyzer,
- /obj/item/integrated_electronics/detailer,
- /obj/item/card/data,
- /obj/item/card/data/full_color,
- /obj/item/card/data/disk
- )
diff --git a/code/controllers/subsystem/processing/clock_component.dm b/code/controllers/subsystem/processing/clock_component.dm
new file mode 100644
index 0000000000000..d07acaa6d7932
--- /dev/null
+++ b/code/controllers/subsystem/processing/clock_component.dm
@@ -0,0 +1,5 @@
+/// The subsystem used to tick [/datum/component/acid] instances.
+PROCESSING_SUBSYSTEM_DEF(clock_component)
+ name = "Clock Component"
+ flags = SS_NO_INIT|SS_BACKGROUND|SS_KEEP_TIMING
+ wait = COMP_CLOCK_DELAY
diff --git a/code/controllers/subsystem/processing/fastprocess.dm b/code/controllers/subsystem/processing/fastprocess.dm
index 9622e02146924..1b30ca44c240a 100644
--- a/code/controllers/subsystem/processing/fastprocess.dm
+++ b/code/controllers/subsystem/processing/fastprocess.dm
@@ -1,6 +1,4 @@
-//Fires five times every second.
-
PROCESSING_SUBSYSTEM_DEF(fastprocess)
name = "Fast Processing"
- wait = 2
+ wait = 0.2 SECONDS
stat_tag = "FP"
diff --git a/code/controllers/subsystem/processing/instruments.dm b/code/controllers/subsystem/processing/instruments.dm
new file mode 100644
index 0000000000000..3e1ed85559865
--- /dev/null
+++ b/code/controllers/subsystem/processing/instruments.dm
@@ -0,0 +1,56 @@
+PROCESSING_SUBSYSTEM_DEF(instruments)
+ name = "Instruments"
+ wait = 0.5
+ init_order = INIT_ORDER_INSTRUMENTS
+ flags = SS_KEEP_TIMING
+ priority = FIRE_PRIORITY_INSTRUMENTS
+ /// List of all instrument data, associative id = datum
+ var/static/list/datum/instrument/instrument_data = list()
+ /// List of all song datums.
+ var/static/list/datum/song/songs = list()
+ /// Max lines in songs
+ var/static/musician_maxlines = 600
+ /// Max characters per line in songs
+ var/static/musician_maxlinechars = 300
+ /// Deciseconds between hearchecks. Too high and instruments seem to lag when people are moving around in terms of who can hear it. Too low and the server lags from this.
+ var/static/musician_hearcheck_mindelay = 5
+ /// Maximum instrument channels total instruments are allowed to use. This is so you don't have instruments deadlocking all sound channels.
+ var/static/max_instrument_channels = MAX_INSTRUMENT_CHANNELS
+ /// Current number of channels allocated for instruments
+ var/static/current_instrument_channels = 0
+ /// Single cached list for synthesizer instrument ids, so you don't have to have a new list with every synthesizer.
+ var/static/list/synthesizer_instrument_ids
+
+/datum/controller/subsystem/processing/instruments/Initialize()
+ initialize_instrument_data()
+ synthesizer_instrument_ids = get_allowed_instrument_ids()
+ return ..()
+
+/datum/controller/subsystem/processing/instruments/proc/on_song_new(datum/song/S)
+ songs += S
+
+/datum/controller/subsystem/processing/instruments/proc/on_song_del(datum/song/S)
+ songs -= S
+
+/datum/controller/subsystem/processing/instruments/proc/initialize_instrument_data()
+ for(var/path in subtypesof(/datum/instrument))
+ var/datum/instrument/I = path
+ if(initial(I.abstract_type) == path)
+ continue
+ I = new path
+ I.Initialize()
+ if(!I.id)
+ qdel(I)
+ continue
+ instrument_data[I.id] = I
+ CHECK_TICK
+
+/datum/controller/subsystem/processing/instruments/proc/get_instrument(id_or_path)
+ return instrument_data["[id_or_path]"]
+
+/datum/controller/subsystem/processing/instruments/proc/reserve_instrument_channel(datum/instrument/I)
+ if(current_instrument_channels > max_instrument_channels)
+ return
+ . = SSsounds.reserve_sound_channel(I)
+ if(!isnull(.))
+ current_instrument_channels++
\ No newline at end of file
diff --git a/code/controllers/subsystem/processing/nanites.dm b/code/controllers/subsystem/processing/nanites.dm
index c34e7f78066cc..8a55491f5fa4d 100644
--- a/code/controllers/subsystem/processing/nanites.dm
+++ b/code/controllers/subsystem/processing/nanites.dm
@@ -1,7 +1,7 @@
PROCESSING_SUBSYSTEM_DEF(nanites)
name = "Nanites"
flags = SS_BACKGROUND|SS_POST_FIRE_TIMING|SS_NO_INIT
- wait = 10
+ wait = 1 SECONDS
var/list/datum/nanite_cloud_backup/cloud_backups = list()
var/list/mob/living/nanite_monitored_mobs = list()
diff --git a/code/controllers/subsystem/processing/obj.dm b/code/controllers/subsystem/processing/obj.dm
index 26021fb267a17..3566e8a4dc221 100644
--- a/code/controllers/subsystem/processing/obj.dm
+++ b/code/controllers/subsystem/processing/obj.dm
@@ -2,4 +2,4 @@ PROCESSING_SUBSYSTEM_DEF(obj)
name = "Objects"
priority = FIRE_PRIORITY_OBJ
flags = SS_NO_INIT
- wait = 20
+ wait = 2 SECONDS
diff --git a/code/controllers/subsystem/processing/orbits.dm b/code/controllers/subsystem/processing/orbits.dm
new file mode 100644
index 0000000000000..f5edc16d5fc8c
--- /dev/null
+++ b/code/controllers/subsystem/processing/orbits.dm
@@ -0,0 +1,153 @@
+PROCESSING_SUBSYSTEM_DEF(orbits)
+ name = "Orbits"
+ flags = SS_KEEP_TIMING
+ init_order = INIT_ORDER_ORBITS
+ priority = FIRE_PRIORITY_ORBITS
+ wait = ORBITAL_UPDATE_RATE
+
+ //The primary orbital map.
+ var/list/orbital_maps = list()
+
+ var/datum/orbital_map_tgui/orbital_map_tgui = new()
+
+ var/initial_space_ruins = 2
+ var/initial_objective_beacons = 3
+ var/initial_asteroids = 6
+
+ var/orbits_setup = FALSE
+
+ var/list/datum/orbital_objective/possible_objectives = list()
+
+ var/datum/orbital_objective/current_objective
+
+ var/list/datum/ruin_event/ruin_events = list()
+
+ var/list/runnable_events
+
+ var/event_probability = 60
+
+ //key = port_id
+ //value = orbital shuttle object
+ var/list/assoc_shuttles = list()
+
+ //Key = port_id
+ //value = world time of next launch
+ var/list/interdicted_shuttles = list()
+
+ var/next_objective_time = 0
+
+ //Research disks
+ var/list/research_disks = list()
+
+ var/list/datum/tgui/open_orbital_maps = list()
+
+ //The station
+ var/datum/orbital_object/station_instance
+
+ //Ruin level count
+ var/ruin_levels = 0
+
+/datum/controller/subsystem/processing/orbits/Initialize(start_timeofday)
+ . = ..()
+ setup_event_list()
+ //Create the main orbital map.
+ orbital_maps[PRIMARY_ORBITAL_MAP] = new /datum/orbital_map()
+
+/datum/controller/subsystem/processing/orbits/proc/setup_event_list()
+ runnable_events = list()
+ for(var/ruin_event in subtypesof(/datum/ruin_event))
+ var/datum/ruin_event/instanced = new ruin_event()
+ runnable_events[instanced] = instanced.probability
+
+/datum/controller/subsystem/processing/orbits/proc/get_event()
+ if(!event_probability)
+ return null
+ return pickweight(runnable_events)
+
+/datum/controller/subsystem/processing/orbits/proc/post_load_init()
+ for(var/map_key in orbital_maps)
+ var/datum/orbital_map/orbital_map = orbital_maps[map_key]
+ orbital_map.post_setup()
+ orbits_setup = TRUE
+ //Create initial ruins
+ for(var/i in 1 to initial_space_ruins)
+ new /datum/orbital_object/z_linked/beacon/ruin/spaceruin()
+ for(var/i in 1 to initial_objective_beacons)
+ new /datum/orbital_object/z_linked/beacon/ruin()
+ //Create asteroid belt
+ for(var/i in 1 to initial_asteroids)
+ new /datum/orbital_object/z_linked/beacon/ruin/asteroid()
+
+/datum/controller/subsystem/processing/orbits/fire(resumed)
+ if(resumed)
+ . = ..()
+ if(MC_TICK_CHECK)
+ return
+ //Update UIs
+ for(var/datum/tgui/tgui as() in open_orbital_maps)
+ tgui.send_update()
+ //Check creating objectives / missions.
+ if(next_objective_time < world.time && length(possible_objectives) < 6)
+ create_objective()
+ next_objective_time = world.time + rand(30 SECONDS, 5 MINUTES)
+ //Check space ruin count
+ if(ruin_levels < 2 && prob(5))
+ new /datum/orbital_object/z_linked/beacon/ruin/spaceruin()
+ //Check objective
+ if(current_objective)
+ if(current_objective.check_failed())
+ priority_announce("Central Command priority objective failed.", "Central Command Report", SSstation.announcer.get_rand_report_sound())
+ QDEL_NULL(current_objective)
+ //Process events
+ for(var/datum/ruin_event/ruin_event as() in ruin_events)
+ if(!ruin_event.update())
+ ruin_events.Remove(ruin_event)
+ //Do processing.
+ if(!resumed)
+ . = ..()
+ if(MC_TICK_CHECK)
+ return
+ //Update UIs
+ for(var/datum/tgui/tgui as() in open_orbital_maps)
+ tgui.send_update()
+
+/mob/dead/observer/verb/open_orbit_ui()
+ set name = "View Orbits"
+ set category = "Ghost"
+ SSorbits.orbital_map_tgui.ui_interact(src)
+
+/datum/controller/subsystem/processing/orbits/proc/create_objective()
+ var/static/list/valid_objectives = list(
+ /datum/orbital_objective/recover_blackbox = 3,
+ /datum/orbital_objective/nuclear_bomb = 1,
+ /datum/orbital_objective/artifact = 1,
+ /datum/orbital_objective/vip_recovery = 1
+ )
+ if(!length(possible_objectives))
+ priority_announce("Priority station objective recieved - Details transmitted to all available objective consoles. \
+ [GLOB.station_name] will have funds distributed upon objective completion.", "Central Command Report", SSstation.announcer.get_rand_report_sound())
+ var/chosen = pickweight(valid_objectives)
+ if(!chosen)
+ return
+ var/datum/orbital_objective/objective = new chosen()
+ objective.generate_payout()
+ possible_objectives += objective
+ update_objective_computers()
+
+/datum/controller/subsystem/processing/orbits/proc/assign_objective(objective_computer, datum/orbital_objective/objective)
+ if(!possible_objectives.Find(objective))
+ return "Selected objective is no longer available or has been claimed already."
+ if(current_objective)
+ return "An objective has already been selected and must be completed first."
+ objective.on_assign(objective_computer)
+ objective.generate_attached_beacon()
+ objective.announce()
+ current_objective = objective
+ possible_objectives.Remove(objective)
+ update_objective_computers()
+ return "Objective selected, good luck."
+
+/datum/controller/subsystem/processing/orbits/proc/update_objective_computers()
+ for(var/obj/machinery/computer/objective/computer as() in GLOB.objective_computers)
+ for(var/M in computer.viewing_mobs)
+ computer.update_static_data(M)
diff --git a/code/controllers/subsystem/processing/processing.dm b/code/controllers/subsystem/processing/processing.dm
index c5d6dfa126838..70f44822be27c 100644
--- a/code/controllers/subsystem/processing/processing.dm
+++ b/code/controllers/subsystem/processing/processing.dm
@@ -1,17 +1,23 @@
-//Used to process objects. Fires once every second.
+//Used to process objects.
SUBSYSTEM_DEF(processing)
name = "Processing"
priority = FIRE_PRIORITY_PROCESS
flags = SS_BACKGROUND|SS_POST_FIRE_TIMING|SS_NO_INIT
- wait = 10
+ wait = 1 SECONDS
var/stat_tag = "P" //Used for logging
var/list/processing = list()
var/list/currentrun = list()
/datum/controller/subsystem/processing/stat_entry()
- ..("[stat_tag]:[processing.len]")
+ . = ..("[stat_tag]:[processing.len]")
+
+/datum/controller/subsystem/processing/get_metrics()
+ . = ..()
+ var/list/cust = list()
+ cust["processing"] = length(processing)
+ .["custom"] = cust
/datum/controller/subsystem/processing/fire(resumed = 0)
if (!resumed)
@@ -24,12 +30,25 @@ SUBSYSTEM_DEF(processing)
current_run.len--
if(QDELETED(thing))
processing -= thing
- else if(thing.process(wait) == PROCESS_KILL)
+ else if(thing.process(wait * 0.1) == PROCESS_KILL)
// fully stop so that a future START_PROCESSING will work
STOP_PROCESSING(src, thing)
if (MC_TICK_CHECK)
return
-/datum/proc/process()
- set waitfor = 0
+/**
+ * This proc is called on a datum on every "cycle" if it is being processed by a subsystem. The time between each cycle is determined by the subsystem's "wait" setting.
+ * You can start and stop processing a datum using the START_PROCESSING and STOP_PROCESSING defines.
+ *
+ * Since the wait setting of a subsystem can be changed at any time, it is important that any rate-of-change that you implement in this proc is multiplied by the delta_time that is sent as a parameter,
+ * Additionally, any "prob" you use in this proc should instead use the DT_PROB define to make sure that the final probability per second stays the same even if the subsystem's wait is altered.
+ * Examples where this must be considered:
+ * - Implementing a cooldown timer, use `mytimer -= delta_time`, not `mytimer -= 1`. This way, `mytimer` will always have the unit of seconds
+ * - Damaging a mob, do `L.adjustFireLoss(20 * delta_time)`, not `L.adjustFireLoss(20)`. This way, the damage per second stays constant even if the wait of the subsystem is changed
+ * - Probability of something happening, do `if(DT_PROB(25, delta_time))`, not `if(prob(25))`. This way, if the subsystem wait is e.g. lowered, there won't be a higher chance of this event happening per second
+ *
+ * If you override this do not call parent, as it will return PROCESS_KILL. This is done to prevent objects that dont override process() from staying in the processing list
+ */
+/datum/proc/process(delta_time)
+ set waitfor = FALSE
return PROCESS_KILL
diff --git a/code/controllers/subsystem/processing/quirks.dm b/code/controllers/subsystem/processing/quirks.dm
index ce3ab89472e7e..8a67d2bcb5899 100644
--- a/code/controllers/subsystem/processing/quirks.dm
+++ b/code/controllers/subsystem/processing/quirks.dm
@@ -5,13 +5,13 @@ PROCESSING_SUBSYSTEM_DEF(quirks)
name = "Quirks"
init_order = INIT_ORDER_QUIRKS
flags = SS_BACKGROUND
- wait = 10
runlevels = RUNLEVEL_GAME
+ wait = 1 SECONDS
var/list/quirks = list() //Assoc. list of all roundstart quirk datum types; "name" = /path/
var/list/quirk_points = list() //Assoc. list of quirk names and their "point cost"; positive numbers are good traits, and negative ones are bad
var/list/quirk_objects = list() //A list of all quirk objects in the game, since some may process
- var/list/quirk_blacklist = list() //A list a list of quirks that can not be used with each other. Format: list(quirk1,quirk2),list(quirk3,quirk4)
+ var/list/quirk_blacklist = list() //A list of quirks that can not be used with each other. Format: list(quirk1,quirk2),list(quirk3,quirk4)
/datum/controller/subsystem/processing/quirks/Initialize(timeofday)
if(!quirks.len)
diff --git a/code/controllers/subsystem/processing/station.dm b/code/controllers/subsystem/processing/station.dm
new file mode 100644
index 0000000000000..d5636f0571509
--- /dev/null
+++ b/code/controllers/subsystem/processing/station.dm
@@ -0,0 +1,84 @@
+#define REPORT_WAIT_TIME_MINIMUM 600
+#define REPORT_WAIT_TIME_MAXIMUM 1500
+
+PROCESSING_SUBSYSTEM_DEF(station)
+ name = "Station"
+ init_order = INIT_ORDER_STATION
+ flags = SS_BACKGROUND
+ runlevels = RUNLEVEL_GAME
+ wait = 5 SECONDS
+
+ ///A list of currently active station traits
+ var/list/station_traits
+ ///Assoc list of trait type || assoc list of traits with weighted value. Used for picking traits from a specific category.
+ var/list/selectable_traits_by_types
+ ///Currently active announcer. Starts as a type but gets initialized after traits are selected
+ var/datum/centcom_announcer/announcer = /datum/centcom_announcer/default
+
+/datum/controller/subsystem/processing/station/Initialize(timeofday)
+
+ station_traits = list()
+ selectable_traits_by_types = list(STATION_TRAIT_POSITIVE = list(), STATION_TRAIT_NEUTRAL = list(), STATION_TRAIT_NEGATIVE = list())
+
+ //If doing unit tests we don't do none of that trait shit ya know?
+ #ifndef UNIT_TESTS
+ if(CONFIG_GET(flag/station_traits))
+ SetupTraits()
+ PrepareReport()
+ #endif
+
+ announcer = new announcer() //Initialize the station's announcer datum
+
+ return ..()
+
+///Rolls for the amount of traits and adds them to the traits list
+/datum/controller/subsystem/processing/station/proc/SetupTraits()
+ for(var/i in subtypesof(/datum/station_trait))
+ var/datum/station_trait/trait_typepath = i
+ if(initial(trait_typepath.trait_flags) & STATION_TRAIT_ABSTRACT)
+ continue //Dont add abstract ones to it
+ selectable_traits_by_types[initial(trait_typepath.trait_type)][trait_typepath] = initial(trait_typepath.weight)
+
+ var/positive_trait_count = pick(20;0, 5;1, 1;2)
+ var/neutral_trait_count = pick(10;0, 10;1, 3;2)
+ var/negative_trait_count = pick(20;0, 5;1, 1;2)
+
+ pick_traits(STATION_TRAIT_POSITIVE, positive_trait_count)
+ pick_traits(STATION_TRAIT_NEUTRAL, neutral_trait_count)
+ pick_traits(STATION_TRAIT_NEGATIVE, negative_trait_count)
+
+
+///Picks traits of a specific category (e.g. bad or good) and a specified amount, then initializes them and adds them to the list of traits.
+/datum/controller/subsystem/processing/station/proc/pick_traits(trait_type, amount)
+ if(!amount)
+ return
+ for(var/iterator in 1 to amount)
+ var/datum/station_trait/picked_trait = pickweight(selectable_traits_by_types[trait_type]) //Rolls from the table for the specific trait type
+ if(!picked_trait)
+ return
+ picked_trait = new picked_trait()
+ station_traits += picked_trait
+ selectable_traits_by_types[picked_trait.trait_type] -= picked_trait.type //We don't want it to roll trait twice
+ if(!picked_trait.blacklist)
+ continue
+ for(var/i in picked_trait.blacklist)
+ var/datum/station_trait/trait_to_remove = i
+ selectable_traits_by_types[initial(trait_to_remove.trait_type)] -= trait_to_remove
+
+/datum/controller/subsystem/processing/station/proc/PrepareReport()
+ if(!station_traits.len) //no active traits why bother
+ return
+
+ var/report = "Central Command Divergency Report"
+
+ for(var/datum/station_trait/trait as() in station_traits)
+ if(trait.trait_flags & STATION_TRAIT_ABSTRACT)
+ continue
+ if(!trait.report_message || !trait.show_in_report)
+ continue
+ report += "[trait.get_report()] "
+
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/print_command_report, report, "Central Command Divergency Report", FALSE), rand(REPORT_WAIT_TIME_MINIMUM, REPORT_WAIT_TIME_MAXIMUM))
+
+#undef REPORT_WAIT_TIME_MINIMUM
+#undef REPORT_WAIT_TIME_MAXIMUM
diff --git a/code/controllers/subsystem/profiler.dm b/code/controllers/subsystem/profiler.dm
index c9c8013fbc601..4c00a98df86a4 100644
--- a/code/controllers/subsystem/profiler.dm
+++ b/code/controllers/subsystem/profiler.dm
@@ -1,10 +1,23 @@
#define PROFILER_FILENAME "profiler.json"
+#ifdef SENDMAPS_PROFILE
+#define SENDMAPS_FILENAME "sendmaps.json"
+GLOBAL_REAL_VAR(world_init_maptick_profiler) = world.Profile(PROFILE_RESTART, type = "sendmaps")
+#endif
+
SUBSYSTEM_DEF(profiler)
name = "Profiler"
init_order = INIT_ORDER_PROFILER
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
- wait = 600
+ wait = 3000
+ flags = SS_NO_TICK_CHECK
+ var/fetch_cost = 0
+ var/write_cost = 0
+
+/datum/controller/subsystem/profiler/stat_entry(msg)
+ msg += "F:[round(fetch_cost,1)]ms"
+ msg += "|W:[round(write_cost,1)]ms"
+ ..(msg)
/datum/controller/subsystem/profiler/Initialize()
if(CONFIG_GET(flag/auto_profile))
@@ -20,19 +33,54 @@ SUBSYSTEM_DEF(profiler)
/datum/controller/subsystem/profiler/Shutdown()
if(CONFIG_GET(flag/auto_profile))
DumpFile()
+#ifdef SENDMAPS_PROFILE
+ world.Profile(PROFILE_CLEAR, type = "sendmaps")
+#endif
return ..()
/datum/controller/subsystem/profiler/proc/StartProfiling()
world.Profile(PROFILE_START)
+#ifdef SENDMAPS_PROFILE
+ world.Profile(PROFILE_START, type = "sendmaps")
+#endif
+
/datum/controller/subsystem/profiler/proc/StopProfiling()
world.Profile(PROFILE_STOP)
+#ifdef SENDMAPS_PROFILE
+ world.Profile(PROFILE_STOP, type = "sendmaps")
+#endif
/datum/controller/subsystem/profiler/proc/DumpFile()
- var/current_profile_data = world.Profile(PROFILE_REFRESH,format="json")
+#if DM_BUILD < 1506
+ stack_trace("Auto profiling unsupported on this byond version")
+ CONFIG_SET(flag/auto_profile, FALSE)
+#else
+ var/timer = TICK_USAGE_REAL
+ var/current_profile_data = world.Profile(PROFILE_REFRESH, format = "json")
+#ifdef SENDMAPS_PROFILE
+ var/current_sendmaps_data = world.Profile(PROFILE_REFRESH, type = "sendmaps", format="json")
+#endif
+ fetch_cost = MC_AVERAGE(fetch_cost, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
+ CHECK_TICK
+
if(!length(current_profile_data)) //Would be nice to have explicit proc to check this
stack_trace("Warning, profiling stopped manually before dump.")
- var/json_file = file("[GLOB.log_directory]/[PROFILER_FILENAME]")
- if(fexists(json_file))
- fdel(json_file)
- WRITE_FILE(json_file, current_profile_data)
+ var/prof_file = file("[GLOB.log_directory]/[PROFILER_FILENAME]")
+ if(fexists(prof_file))
+ fdel(prof_file)
+#ifdef SENDMAPS_PROFILE
+ if(!length(current_sendmaps_data)) //Would be nice to have explicit proc to check this
+ stack_trace("Warning, sendmaps profiling stopped manually before dump.")
+ var/sendmaps_file = file("[GLOB.log_directory]/[SENDMAPS_FILENAME]")
+ if(fexists(sendmaps_file))
+ fdel(sendmaps_file)
+#endif
+
+ timer = TICK_USAGE_REAL
+ WRITE_FILE(prof_file, current_profile_data)
+#ifdef SENDMAPS_PROFILE
+ WRITE_FILE(sendmaps_file, current_sendmaps_data)
+#endif
+ write_cost = MC_AVERAGE(write_cost, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
+#endif
diff --git a/code/controllers/subsystem/radiation.dm b/code/controllers/subsystem/radiation.dm
index 2f7d27aca99f6..2d764d84ddb64 100644
--- a/code/controllers/subsystem/radiation.dm
+++ b/code/controllers/subsystem/radiation.dm
@@ -1,6 +1,7 @@
PROCESSING_SUBSYSTEM_DEF(radiation)
name = "Radiation"
flags = SS_NO_INIT | SS_BACKGROUND
+ wait = 1 SECONDS
var/list/warned_atoms = list()
diff --git a/code/controllers/subsystem/research.dm b/code/controllers/subsystem/research.dm
index 3263047ec1500..4b734ff6e0f9e 100644
--- a/code/controllers/subsystem/research.dm
+++ b/code/controllers/subsystem/research.dm
@@ -44,6 +44,15 @@ SUBSYSTEM_DEF(research)
initialize_all_techweb_designs()
initialize_all_techweb_nodes()
science_tech = new /datum/techweb/science
+ //Some points to get you started.
+ //Points can be gained by
+ // 1) Exploration team going to ruins
+ // 2) Scientists using their shuttle to go to ruins
+ // 3) Giving miners a scanner
+ // 4) Scanning station pets
+ // 5) Using the experimentor on maint devices
+ // (probably more added since this comment was written.)
+ science_tech.add_point_type(TECHWEB_POINT_TYPE_DISCOVERY, 2500)
admin_tech = new /datum/techweb/admin
autosort_categories()
error_design = new
diff --git a/code/controllers/subsystem/runechat.dm b/code/controllers/subsystem/runechat.dm
new file mode 100644
index 0000000000000..0bd75e326144d
--- /dev/null
+++ b/code/controllers/subsystem/runechat.dm
@@ -0,0 +1,237 @@
+/// Controls how many buckets should be kept, each representing a tick. (30 seconds worth)
+#define BUCKET_LEN (world.fps * 1 * 30)
+/// Helper for getting the correct bucket for a given chatmessage
+#define BUCKET_POS(scheduled_destruction) (((round((scheduled_destruction - SSrunechat.head_offset) / world.tick_lag) + 1) % BUCKET_LEN) || BUCKET_LEN)
+/// Gets the maximum time at which messages will be handled in buckets, used for deferring to secondary queue
+#define BUCKET_LIMIT (world.time + TICKS2DS(min(BUCKET_LEN - (SSrunechat.practical_offset - DS2TICKS(world.time - SSrunechat.head_offset)) - 1, BUCKET_LEN - 1)))
+/**
+ * # Runechat Subsystem
+ *
+ * Maintains a timer-like system to handle destruction of runechat messages and balloon alerts. Much of this code is modeled
+ * after or adapted from the timer subsystem.
+ *
+ * Note that this has the same structure for storing and queueing messages as the timer subsystem does
+ * for handling timers: the bucket_list is a list of chatmessage datums, each of which are the head
+ * of a circularly linked list. Any given index in bucket_list could be null, representing an empty bucket.
+ */
+SUBSYSTEM_DEF(runechat)
+ name = "Runechat"
+ flags = SS_TICKER | SS_NO_INIT
+ wait = 1
+ priority = FIRE_PRIORITY_RUNECHAT
+
+ /// world.time of the first entry in the bucket list, effectively the 'start time' of the current buckets
+ var/head_offset = 0
+ /// Index of the first non-empty bucket
+ var/practical_offset = 1
+ /// world.tick_lag the bucket was designed for
+ var/bucket_resolution = 0
+ /// How many messages are in the buckets
+ var/bucket_count = 0
+ /// List of buckets, each bucket holds every message that has to be killed that byond tick
+ var/list/bucket_list = list()
+ /// Queue used for storing messages that are scheduled for deletion too far in the future for the buckets
+ var/list/datum/chatmessage/second_queue = list()
+
+/datum/controller/subsystem/runechat/PreInit()
+ bucket_list.len = BUCKET_LEN
+ head_offset = world.time
+ bucket_resolution = world.tick_lag
+
+/datum/controller/subsystem/runechat/stat_entry(msg)
+ . = ..("ActMsgs:[bucket_count] SecQueue:[length(second_queue)]")
+
+/datum/controller/subsystem/runechat/fire(resumed = FALSE)
+ // Store local references to datum vars as it is faster to access them this way
+ var/list/bucket_list = src.bucket_list
+
+ if (MC_TICK_CHECK)
+ return
+
+ // Check for when we need to loop the buckets, this occurs when
+ // the head_offset is approaching BUCKET_LEN ticks in the past
+ if (practical_offset > BUCKET_LEN)
+ head_offset += TICKS2DS(BUCKET_LEN)
+ practical_offset = 1
+ resumed = FALSE
+
+ // Check for when we have to reset buckets, typically from auto-reset
+ if ((length(bucket_list) != BUCKET_LEN) || (world.tick_lag != bucket_resolution))
+ reset_buckets()
+ bucket_list = src.bucket_list
+ resumed = FALSE
+
+ // Store a reference to the 'working' chatmessage so that we can resume if the MC
+ // has us stop mid-way through processing
+ var/static/datum/chatmessage/cm
+ if (!resumed)
+ cm = null
+
+ // Iterate through each bucket starting from the practical offset
+ while (practical_offset <= BUCKET_LEN && head_offset + ((practical_offset - 1) * world.tick_lag) <= world.time)
+ var/datum/chatmessage/bucket_head = bucket_list[practical_offset]
+ if (!cm || !bucket_head || cm == bucket_head)
+ bucket_head = bucket_list[practical_offset]
+ cm = bucket_head
+
+ while (cm)
+ // If the chatmessage hasn't yet had its life ended then do that now
+ var/datum/chatmessage/next = cm.next
+ if (!cm.eol_complete)
+ cm.end_of_life()
+ else if (!QDELETED(cm)) // otherwise if we haven't deleted it yet, do so (this is after EOL completion)
+ qdel(cm)
+
+ if (MC_TICK_CHECK)
+ return
+
+ // Break once we've processed the entire bucket
+ cm = next
+ if (cm == bucket_head)
+ break
+
+ // Empty the bucket, check if anything in the secondary queue should be shifted to this bucket
+ bucket_list[practical_offset++] = null
+ var/i = 0
+ for (i in 1 to length(second_queue))
+ cm = second_queue[i]
+ if (cm.scheduled_destruction >= BUCKET_LIMIT)
+ i--
+ break
+
+ // Transfer the message into the bucket, performing necessary circular doubly-linked list operations
+ bucket_count++
+ var/bucket_pos = max(1, BUCKET_POS(cm.scheduled_destruction))
+ var/datum/timedevent/head = bucket_list[bucket_pos]
+ if (!head)
+ bucket_list[bucket_pos] = cm
+ cm.next = null
+ cm.prev = null
+ continue
+
+ if (!head.prev)
+ head.prev = head
+ cm.next = head
+ cm.prev = head.prev
+ cm.next.prev = cm
+ cm.prev.next = cm
+ if (i)
+ second_queue.Cut(1, i + 1)
+ cm = null
+
+/datum/controller/subsystem/runechat/Recover()
+ bucket_list |= SSrunechat.bucket_list
+ second_queue |= SSrunechat.second_queue
+
+/datum/controller/subsystem/runechat/proc/reset_buckets()
+ bucket_list.len = BUCKET_LEN
+ head_offset = world.time
+ bucket_resolution = world.tick_lag
+
+/**
+ * Enters the runechat subsystem with this chatmessage, inserting it into the end-of-life queue
+ *
+ * This will also account for a chatmessage already being registered, and in which case
+ * the position will be updated to remove it from the previous location if necessary
+ *
+ * Arguments:
+ * * new_sched_destruction Optional, when provided is used to update an existing message with the new specified time
+ */
+/datum/chatmessage/proc/enter_subsystem(new_sched_destruction = 0)
+ // Get local references from subsystem as they are faster to access than the datum references
+ var/list/bucket_list = SSrunechat.bucket_list
+ var/list/second_queue = SSrunechat.second_queue
+
+ // When necessary, de-list the chatmessage from its previous position
+ if (new_sched_destruction)
+ if (scheduled_destruction >= BUCKET_LIMIT)
+ second_queue -= src
+ else
+ SSrunechat.bucket_count--
+ var/bucket_pos = BUCKET_POS(scheduled_destruction)
+ if (bucket_pos > 0)
+ var/datum/chatmessage/bucket_head = bucket_list[bucket_pos]
+ if (bucket_head == src)
+ bucket_list[bucket_pos] = next
+ if (prev != next)
+ prev.next = next
+ next.prev = prev
+ else
+ prev?.next = null
+ next?.prev = null
+ prev = next = null
+ scheduled_destruction = new_sched_destruction
+
+ // Ensure the scheduled destruction time is properly bound to avoid missing a scheduled event
+ scheduled_destruction = max(CEILING(scheduled_destruction, world.tick_lag), world.time + world.tick_lag)
+
+ // Handle insertion into the secondary queue if the required time is outside our tracked amounts
+ if (scheduled_destruction >= BUCKET_LIMIT)
+ BINARY_INSERT(src, SSrunechat.second_queue, /datum/chatmessage, src, scheduled_destruction, COMPARE_KEY)
+ return
+
+ // Get bucket position and a local reference to the datum var, it's faster to access this way
+ var/bucket_pos = BUCKET_POS(scheduled_destruction)
+
+ // Get the bucket head for that bucket, increment the bucket count
+ var/datum/chatmessage/bucket_head = bucket_list[bucket_pos]
+ SSrunechat.bucket_count++
+
+ // If there is no existing head of this bucket, we can set this message to be that head
+ if (!bucket_head)
+ bucket_list[bucket_pos] = src
+ return
+
+ // Otherwise it's a simple insertion into the circularly doubly-linked list
+ if (!bucket_head.prev)
+ bucket_head.prev = bucket_head
+ next = bucket_head
+ prev = bucket_head.prev
+ next.prev = src
+ prev.next = src
+
+
+/**
+ * Removes this chatmessage datum from the runechat subsystem
+ */
+/datum/chatmessage/proc/leave_subsystem()
+ // Attempt to find the bucket that contains this chat message
+ var/bucket_pos = BUCKET_POS(scheduled_destruction)
+
+ // Get local references to the subsystem's vars, faster than accessing on the datum
+ var/list/bucket_list = SSrunechat.bucket_list
+ var/list/second_queue = SSrunechat.second_queue
+
+ // Attempt to get the head of the bucket
+ var/datum/chatmessage/bucket_head
+ if (bucket_pos > 0)
+ bucket_head = bucket_list[bucket_pos]
+
+ // Decrement the number of messages in buckets if the message is
+ // the head of the bucket, or has a SD less than BUCKET_LIMIT implying it fits
+ // into an existing bucket, or is otherwise not present in the secondary queue
+ if(bucket_head == src)
+ bucket_list[bucket_pos] = next
+ SSrunechat.bucket_count--
+ else if(scheduled_destruction < BUCKET_LIMIT)
+ SSrunechat.bucket_count--
+ else
+ var/l = length(second_queue)
+ second_queue -= src
+ if(l == length(second_queue))
+ SSrunechat.bucket_count--
+
+ // Remove the message from the bucket, ensuring to maintain
+ // the integrity of the bucket's list if relevant
+ if(prev != next)
+ prev.next = next
+ next.prev = prev
+ else
+ prev?.next = null
+ next?.prev = null
+ prev = next = null
+
+
+#undef BUCKET_LEN
+#undef BUCKET_POS
+#undef BUCKET_LIMIT
diff --git a/code/controllers/subsystem/shuttle.dm b/code/controllers/subsystem/shuttle.dm
index 37f8f5b8aeb38..b5cf7f94dd791 100644
--- a/code/controllers/subsystem/shuttle.dm
+++ b/code/controllers/subsystem/shuttle.dm
@@ -26,6 +26,7 @@ SUBSYSTEM_DEF(shuttle)
var/emergencyCallAmount = 0 //how many times the escape shuttle was called
var/emergencyNoEscape
var/emergencyNoRecall = FALSE
+ var/adminEmergencyNoRecall = FALSE
var/list/hostileEnvironments = list() //Things blocking escape shuttle from leaving
var/list/tradeBlockade = list() //Things blocking cargo from leaving.
var/supplyBlocked = FALSE
@@ -148,7 +149,7 @@ SUBSYSTEM_DEF(shuttle)
message_admins(msg)
log_game("[msg] Alive: [alive], Roundstart: [total], Threshold: [threshold]")
emergencyNoRecall = TRUE
- priority_announce("Catastrophic casualties detected: crisis shuttle protocols activated - jamming recall signals across all frequencies.")
+ priority_announce("Catastrophic casualties detected: crisis shuttle protocols activated - jamming recall signals across all frequencies.", sound = SSstation.announcer.get_rand_alert_sound())
if(emergency.timeLeft(1) > emergencyCallTime * 0.4)
emergency.request(null, set_coefficient = 0.4)
@@ -378,7 +379,7 @@ SUBSYSTEM_DEF(shuttle)
emergency.setTimer(emergencyDockTime)
priority_announce("Hostile environment resolved. \
You have 3 minutes to board the Emergency Shuttle.",
- null, 'sound/ai/shuttledock.ogg', "Priority")
+ null, ANNOUNCER_SHUTTLEDOCK, "Priority")
//try to move/request to dockHome if possible, otherwise dockAway. Mainly used for admin buttons
/datum/controller/subsystem/shuttle/proc/toggleShuttle(shuttleId, dockHome, dockAway, timed)
@@ -636,7 +637,7 @@ SUBSYSTEM_DEF(shuttle)
hidden_shuttle_turf_images += add_images
for(var/V in GLOB.navigation_computers)
- var/obj/machinery/computer/camera_advanced/shuttle_docker/C = V
+ var/obj/machinery/computer/shuttle_flight/C = V
C.update_hidden_docking_ports(remove_images, add_images)
QDEL_LIST(remove_images)
@@ -698,6 +699,8 @@ SUBSYSTEM_DEF(shuttle)
preview_shuttle.register()
+ preview_shuttle.reset_air()
+
// TODO indicate to the user that success happened, rather than just
// blanking the modification tab
preview_shuttle = null
@@ -760,6 +763,7 @@ SUBSYSTEM_DEF(shuttle)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "ShuttleManipulator")
+ ui.set_autoupdate(TRUE)
ui.open()
diff --git a/code/controllers/subsystem/sound.dm b/code/controllers/subsystem/sound.dm
new file mode 100644
index 0000000000000..85855c3998f3e
--- /dev/null
+++ b/code/controllers/subsystem/sound.dm
@@ -0,0 +1,127 @@
+/*
+ * Sound subsystem:
+ * Used for things that need constant updating (sound fading in / out)
+*/
+
+SUBSYSTEM_DEF(sound_effects)
+ name = "Sound"
+ wait = 1
+ priority = FIRE_PRIORITY_AMBIENCE
+ flags = SS_NO_INIT
+ //Note: Make sure you update this if you use sound fading pre-game
+ runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
+
+ var/list/acting_effects = list() //key = sound, value = datum
+ var/list/currentrun = list()
+
+/datum/controller/subsystem/sound_effects/fire(resumed = 0)
+ if (!resumed)
+ src.currentrun = acting_effects.Copy()
+
+ //cache for sanic speed (lists are references anyways)
+ var/list/currentrun = src.currentrun
+
+ while(LAZYLEN(currentrun))
+ var/datum/sound_effect/sound_effect = currentrun[currentrun[currentrun.len]]
+ currentrun.len--
+
+ sound_effect.update_effect()
+
+ if(world.time > sound_effect.end_tick)
+ sound_effect.end_effect()
+ acting_effects -= sound_effect.effect_id
+
+ if (MC_TICK_CHECK)
+ return
+
+// ===== Sound effect procs =====
+
+/proc/sound_fade(sound/S, start_volume = 100, end_volume = 0, time = 10, var/listeners)
+ //Check basics
+ if(!S)
+ CRASH("sound_fade called without a sound file.")
+ if(!listeners)
+ return
+ //Check in list format
+ var/listeners_list = listeners
+ if(!islist(listeners_list))
+ listeners_list = list(listeners)
+ //Create datum
+ new /datum/sound_effect/fade(S, listeners_list, time, start_volume, end_volume)
+
+// ===== Sound effect datum =====
+
+/datum/sound_effect
+ var/name = "null"
+ var/sound/sound
+ var/list/listeners
+ var/start_tick
+ var/end_tick
+ var/effect_id
+
+/datum/sound_effect/New(S, list/_listeners, time)
+ . = ..()
+ sound = S
+ listeners = _listeners
+ start_tick = world.time
+ end_tick = world.time + time
+ effect_id = generate_id()
+ start_sound()
+
+/datum/sound_effect/proc/generate_id()
+ var/id = "[name][sound.file]"
+ for(var/A in listeners)
+ id = "[id][REF(A)]"
+ return id
+
+/datum/sound_effect/proc/send_sound()
+ for(var/reciever in listeners)
+ SEND_SOUND(reciever, sound)
+
+/datum/sound_effect/proc/update_effect()
+ return //Not implemented
+
+/datum/sound_effect/proc/end_effect()
+ return //Not implemented
+
+// Send the sound to the person it's affecting and add it to the sound subsystem.
+// Should be overridden to account for if an effect is already playing for that sound.
+/datum/sound_effect/proc/start_sound()
+ send_sound()
+ SSsound_effects.acting_effects[effect_id] = src
+
+//============== Fade =============
+
+/datum/sound_effect/fade
+ name = "fade"
+ var/in_vol
+ var/out_vol
+ //Calculated
+ var/current_vol
+
+/datum/sound_effect/fade/New(S, list/_listeners, time, start_vol, end_vol)
+ in_vol = start_vol
+ out_vol = end_vol
+ . = ..(S, _listeners, time)
+
+/datum/sound_effect/fade/start_sound()
+ //If the sound is already playing, make it fade from the current point
+ if(SSsound_effects.acting_effects[effect_id])
+ var/datum/sound_effect/fade/old_sound = SSsound_effects.acting_effects[effect_id]
+ in_vol = old_sound.current_vol
+ else
+ send_sound()
+ SSsound_effects.acting_effects[effect_id] = src
+
+/datum/sound_effect/fade/update_effect()
+ var/time_multiplier = CLAMP((world.time - start_tick) / (end_tick - start_tick), 0, 1)
+ current_vol = (time_multiplier * out_vol) + ((1-time_multiplier) * in_vol)
+ sound.status = SOUND_UPDATE
+ sound.volume = current_vol
+
+ for(var/reciever in listeners)
+ SEND_SOUND(reciever, sound)
+
+/datum/sound_effect/fade/end_effect()
+ if(!out_vol)
+ sound.repeat = FALSE
diff --git a/code/controllers/subsystem/sound_loops.dm b/code/controllers/subsystem/sound_loops.dm
new file mode 100644
index 0000000000000..46b916603f943
--- /dev/null
+++ b/code/controllers/subsystem/sound_loops.dm
@@ -0,0 +1,3 @@
+TIMER_SUBSYSTEM_DEF(sound_loops)
+ name = "Sound Loops"
+ priority = FIRE_PRIORITY_SOUND_LOOPS
diff --git a/code/controllers/subsystem/sounds.dm b/code/controllers/subsystem/sounds.dm
new file mode 100644
index 0000000000000..6095cf711405c
--- /dev/null
+++ b/code/controllers/subsystem/sounds.dm
@@ -0,0 +1,135 @@
+#define DATUMLESS "NO_DATUM"
+
+SUBSYSTEM_DEF(sounds)
+ name = "Sound Channels"
+ flags = SS_NO_FIRE
+ init_order = INIT_ORDER_SOUNDS
+ var/static/using_channels_max = CHANNEL_HIGHEST_AVAILABLE //BYOND max channels
+ /// Amount of channels to reserve for random usage rather than reservations being allowed to reserve all channels. Also a nice safeguard for when someone screws up.
+ var/static/random_channels_min = 50
+
+ // Hey uh these two needs to be initialized fast because the whole "things get deleted before init" thing.
+ /// Assoc list, "[channel]" = either the datum using it or TRUE for an unsafe-reserved (datumless reservation) channel
+ var/list/using_channels
+ /// Assoc list datum = list(channel1, channel2, ...) for what channels something reserved.
+ var/list/using_channels_by_datum
+ // Special datastructure for fast channel management
+ /// List of all channels as numbers
+ var/list/channel_list
+ /// Associative list of all reserved channels associated to their position. "[channel_number]" = index as number
+ var/list/reserved_channels
+ /// lower iteration position - Incremented and looped to get "random" sound channels for normal sounds. The channel at this index is returned when asking for a random channel.
+ var/channel_random_low
+ /// higher reserve position - decremented and incremented to reserve sound channels, anything above this is reserved. The channel at this index is the highest unreserved channel.
+ var/channel_reserve_high
+
+/datum/controller/subsystem/sounds/Initialize()
+ setup_available_channels()
+ return ..()
+
+/datum/controller/subsystem/sounds/proc/setup_available_channels()
+ channel_list = list()
+ reserved_channels = list()
+ using_channels = list()
+ using_channels_by_datum = list()
+ for(var/i in 1 to using_channels_max)
+ channel_list += i
+ channel_random_low = 1
+ channel_reserve_high = length(channel_list)
+
+/// Removes a channel from using list.
+/datum/controller/subsystem/sounds/proc/free_sound_channel(channel)
+ var/text_channel = num2text(channel)
+ var/using = using_channels[text_channel]
+ using_channels -= text_channel
+ if(using != TRUE) // datum channel
+ using_channels_by_datum[using] -= channel
+ if(!length(using_channels_by_datum[using]))
+ using_channels_by_datum -= using
+ free_channel(channel)
+
+/// Frees all the channels a datum is using.
+/datum/controller/subsystem/sounds/proc/free_datum_channels(datum/D)
+ var/list/L = using_channels_by_datum[D]
+ if(!L)
+ return
+ for(var/channel in L)
+ using_channels -= num2text(channel)
+ free_channel(channel)
+ using_channels_by_datum -= D
+
+/// Frees all datumless channels
+/datum/controller/subsystem/sounds/proc/free_datumless_channels()
+ free_datum_channels(DATUMLESS)
+
+/// NO AUTOMATIC CLEANUP - If you use this, you better manually free it later! Returns an integer for channel.
+/datum/controller/subsystem/sounds/proc/reserve_sound_channel_datumless()
+ . = reserve_channel()
+ if(!.) //oh no..
+ return FALSE
+ var/text_channel = num2text(.)
+ using_channels[text_channel] = DATUMLESS
+ LAZYINITLIST(using_channels_by_datum[DATUMLESS])
+ using_channels_by_datum[DATUMLESS] += .
+
+/// Reserves a channel for a datum. Automatic cleanup only when the datum is deleted. Returns an integer for channel.
+/datum/controller/subsystem/sounds/proc/reserve_sound_channel(datum/D)
+ if(!D) //i don't like typechecks but someone will fuck it up
+ CRASH("Attempted to reserve sound channel without datum using the managed proc.")
+ .= reserve_channel()
+ if(!.)
+ return FALSE
+ var/text_channel = num2text(.)
+ using_channels[text_channel] = D
+ LAZYINITLIST(using_channels_by_datum[D])
+ using_channels_by_datum[D] += .
+
+/**
+ * Reserves a channel and updates the datastructure. Private proc.
+ */
+/datum/controller/subsystem/sounds/proc/reserve_channel()
+ PRIVATE_PROC(TRUE)
+ if(channel_reserve_high <= random_channels_min) // out of channels
+ return
+ var/channel = channel_list[channel_reserve_high]
+ reserved_channels[num2text(channel)] = channel_reserve_high--
+ return channel
+
+/**
+ * Frees a channel and updates the datastructure. Private proc.
+ */
+/datum/controller/subsystem/sounds/proc/free_channel(number)
+ PRIVATE_PROC(TRUE)
+ var/text_channel = num2text(number)
+ var/index = reserved_channels[text_channel]
+ if(!index)
+ CRASH("Attempted to (internally) free a channel that wasn't reserved.")
+ reserved_channels -= text_channel
+ // push reserve index up, which makes it now on a channel that is reserved
+ channel_reserve_high++
+ // swap the reserved channel wtih the unreserved channel so the reserve index is now on an unoccupied channel and the freed channel is next to be used.
+ channel_list.Swap(channel_reserve_high, index)
+ // now, an existing reserved channel will likely (exception: unreserving last reserved channel) be at index
+ // get it, and update position.
+ var/text_reserved = num2text(channel_list[index])
+ if(!reserved_channels[text_reserved]) //if it isn't already reserved make sure we don't accidently mistakenly put it on reserved list!
+ return
+ reserved_channels[text_reserved] = index
+
+/// Random available channel, returns text.
+/datum/controller/subsystem/sounds/proc/random_available_channel_text()
+ if(channel_random_low > channel_reserve_high)
+ channel_random_low = 1
+ . = "[channel_list[channel_random_low++]]"
+
+/// Random available channel, returns number
+/datum/controller/subsystem/sounds/proc/random_available_channel()
+ if(channel_random_low > channel_reserve_high)
+ channel_random_low = 1
+ . = channel_list[channel_random_low++]
+
+/// How many channels we have left.
+/datum/controller/subsystem/sounds/proc/available_channels_left()
+ return length(channel_list) - random_channels_min
+
+#undef DATUMLESS
\ No newline at end of file
diff --git a/code/controllers/subsystem/spacedrift.dm b/code/controllers/subsystem/spacedrift.dm
index c251492227ac4..327fc03bd5804 100644
--- a/code/controllers/subsystem/spacedrift.dm
+++ b/code/controllers/subsystem/spacedrift.dm
@@ -9,8 +9,13 @@ SUBSYSTEM_DEF(spacedrift)
var/list/processing = list()
/datum/controller/subsystem/spacedrift/stat_entry()
- ..("P:[processing.len]")
+ . = ..("P:[processing.len]")
+/datum/controller/subsystem/spacedrift/get_metrics()
+ . = ..()
+ var/list/cust = list()
+ cust["processing"] = length(processing)
+ .["custom"] = cust
/datum/controller/subsystem/spacedrift/fire(resumed = 0)
if (!resumed)
diff --git a/code/controllers/subsystem/stat.dm b/code/controllers/subsystem/stat.dm
new file mode 100644
index 0000000000000..6542b3c80e0de
--- /dev/null
+++ b/code/controllers/subsystem/stat.dm
@@ -0,0 +1,155 @@
+#define FLAT_ICON_CACHE_MAX_SIZE 250
+
+SUBSYSTEM_DEF(stat)
+ name = "Stat"
+ wait = 1 SECONDS
+ priority = FIRE_PRIORITY_STAT
+ runlevels = RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME //RUNLEVEL_INIT doesn't work, so the stat panel will not auto update during this time (But that is good since we don't want to waste processing time during that phase).
+ init_order = INIT_ORDER_STAT
+ flags = SS_NO_INIT | SS_BACKGROUND
+
+ var/list/flat_icon_cache = list() //Assoc list, datum = flat icon
+
+ //The run of clients updating normally
+ var/list/currentrun = list()
+ //The run of clients updating alt clicked turfs
+ var/list/currentrun_listed = list()
+ //List of icon requests
+ var/list/icon_requests = list()
+ //List of people who need re-updating after icon requests are processed
+ var/list/currentrun_aftericon = list()
+
+/datum/controller/subsystem/stat/fire(resumed = 0)
+ if (!resumed)
+ src.currentrun = GLOB.clients.Copy()
+
+ //cache for sanic speed (lists are references anyways)
+ var/list/currentrun = src.currentrun
+ var/list/currentrun_listed = src.currentrun_listed
+ var/list/icon_requests = src.icon_requests
+ var/list/currentrun_aftericon = src.currentrun_aftericon
+
+ while(currentrun.len)
+ var/client/C = currentrun[currentrun.len]
+ currentrun.len--
+
+ if (C)
+ var/mob/M = C.mob
+ if(M)
+ //Handle listed turfs seperately
+ if(sanitize(M.listed_turf?.name) == C.selected_stat_tab)
+ currentrun_listed += C
+ else
+ //Auto-update, not forced
+ M.UpdateMobStat(FALSE)
+
+ if (MC_TICK_CHECK)
+ src.currentrun_listed = currentrun_listed
+ return
+
+ if(MC_TICK_CHECK)
+ src.currentrun_listed = currentrun_listed
+ return
+
+ //Handle clients on listed turfs as low priority, if they run over then we will give our processing time
+ //back to the people not on listed turfs (listed turfs is slightly more laggy)
+ while(currentrun_listed.len)
+ var/client/C = currentrun_listed[currentrun_listed.len]
+ currentrun_listed.len--
+
+ if (C)
+ var/mob/M = C.mob
+ if(M)
+ //Auto-update, not forced
+ M.UpdateMobStat(FALSE)
+
+ if (MC_TICK_CHECK)
+ return
+
+ if(MC_TICK_CHECK)
+ return
+
+ //Process icon requests
+ while(icon_requests.len)
+ var/A_name = icon_requests[icon_requests.len]
+ var/datum/weakref/A_ref = icon_requests[A_name]
+ var/atom/A = A_ref.resolve()
+ var/directionless = TRUE
+ if(ispipewire(A))
+ directionless = FALSE
+ icon_requests.len--
+
+ //Adding a new icon
+ //If the list gets too big just remove the first thing
+ if(flat_icon_cache.len > FLAT_ICON_CACHE_MAX_SIZE)
+ flat_icon_cache.Cut(1, 2)
+ //We are only going to apply overlays to mobs.
+ //Massively faster, getFlatIcon is a bit of a sucky proc.
+ flat_icon_cache[A_name] = icon2base64(getStillIcon(A, directionless))
+
+ if (MC_TICK_CHECK)
+ return
+
+ //Process clients that just got an item and need to update now.
+ //Client list will empty if the system overruns, since they will get updated anyway.
+ if(MC_TICK_CHECK)
+ src.currentrun_aftericon = list()
+ return
+
+ while(currentrun_aftericon.len)
+ var/client/C = currentrun_aftericon[currentrun_aftericon.len]
+ currentrun_aftericon.len--
+
+ if (C)
+ var/mob/M = C.mob
+ if(M)
+ //Auto-update, not forced
+ M.UpdateMobStat(FALSE)
+
+ if (MC_TICK_CHECK)
+ src.currentrun_aftericon = list()
+ return
+
+ if(MC_TICK_CHECK)
+ src.currentrun_aftericon = list()
+ return
+
+//Note: Doesn't account for decals on items.
+//Whoever examins an item with a decal first, everyone else will see that items decals.
+//Significantly reduces server lag though, like MASSIVELY!
+/datum/controller/subsystem/stat/proc/get_flat_icon(client/requester, atom/A)
+ var/directionless = TRUE
+ if(ispipewire(A))
+ directionless = FALSE
+ var/what_to_search = "[A.type][directionless ? 0 : A.dir][(istext(A.icon_state) && length(A.icon_state)) ? A.icon_state[1] : "*"]"
+ //Mobs are more important than items.
+ //Mob icons will change if their name changes, their type changes or their overlays change.
+ if(istype(A, /mob))
+ var/mob/M = A
+ var/overlay_hash = ""
+ for(var/image/I as() in M.overlays)
+ if(istext(I.icon_state) && length(I.icon_state) >= 1)
+ overlay_hash = "[overlay_hash][I.icon_state[1]]"
+ else
+ overlay_hash = "[overlay_hash]*" //Just to make changes known when lengths change. Doesn't have to be accurate per-say.
+ what_to_search = "[M.type][M.name][overlay_hash]"
+ //Makes it shorter
+ var/thing = flat_icon_cache[what_to_search]
+ if(thing)
+ return thing
+ //Start queuing with the subsystem.
+ icon_requests["[what_to_search]"] = WEAKREF(A)
+ src.currentrun_aftericon |= requester
+ return null
+
+/datum/controller/subsystem/stat/proc/send_global_alert(title, message)
+ for(var/client/C in GLOB.clients)
+ if(C?.tgui_panel)
+ C.tgui_panel.give_alert_popup(title, message)
+
+/datum/controller/subsystem/stat/proc/clear_global_alert()
+ for(var/client/C in GLOB.clients)
+ if(C?.tgui_panel)
+ C.tgui_panel.clear_alert_popup()
+
+#undef FLAT_ICON_CACHE_MAX_SIZE
diff --git a/code/controllers/subsystem/sun.dm b/code/controllers/subsystem/sun.dm
index 7a3528cc3d9fc..5975f6faa7e6f 100644
--- a/code/controllers/subsystem/sun.dm
+++ b/code/controllers/subsystem/sun.dm
@@ -15,7 +15,7 @@ SUBSYSTEM_DEF(sun)
rate = -rate
/datum/controller/subsystem/sun/stat_entry(msg)
- ..("P:[solars.len]")
+ . = ..("P:[solars.len]")
/datum/controller/subsystem/sun/fire()
angle = (360 + angle + rate * 6) % 360 // increase/decrease the angle to the sun, adjusted by the rate
diff --git a/code/controllers/subsystem/tgui.dm b/code/controllers/subsystem/tgui.dm
index 3efdd1a20257c..572cf5c502ac4 100644
--- a/code/controllers/subsystem/tgui.dm
+++ b/code/controllers/subsystem/tgui.dm
@@ -29,8 +29,16 @@ SUBSYSTEM_DEF(tgui)
/datum/controller/subsystem/tgui/Shutdown()
close_all_uis()
+
+/datum/controller/subsystem/tgui/get_metrics()
+ . = ..()
+ var/list/cust = list()
+ cust["processing"] = length(open_uis)
+ .["custom"] = cust
+
+
/datum/controller/subsystem/tgui/stat_entry()
- ..("P:[open_uis.len]")
+ . = ..("P:[open_uis.len]")
/datum/controller/subsystem/tgui/fire(resumed = 0)
if(!resumed)
@@ -42,7 +50,7 @@ SUBSYSTEM_DEF(tgui)
current_run.len--
// TODO: Move user/src_object check to process()
if(ui && ui.user && ui.src_object)
- ui.process()
+ ui.process(wait * 0.1)
else
open_uis.Remove(ui)
if(MC_TICK_CHECK)
@@ -172,6 +180,18 @@ SUBSYSTEM_DEF(tgui)
return ui
return null
+/**
+ * public
+ *
+ * Gets all open UIs on a src object
+ */
+/datum/controller/subsystem/tgui/proc/get_all_open_uis(datum/src_object)
+ var/key = "[REF(src_object)]"
+ // No UIs opened for this src_object
+ if(isnull(open_uis_by_src[key]) || !istype(open_uis_by_src[key], /list))
+ return list()
+ return open_uis_by_src[key]
+
/**
* public
*
@@ -190,7 +210,7 @@ SUBSYSTEM_DEF(tgui)
for(var/datum/tgui/ui in open_uis_by_src[key])
// Check if UI is valid.
if(ui && ui.src_object && ui.user && ui.src_object.ui_host(ui.user))
- ui.process(force = 1)
+ ui.process(wait * 0.1, force = 1)
count++
return count
@@ -249,7 +269,7 @@ SUBSYSTEM_DEF(tgui)
return count
for(var/datum/tgui/ui in user.tgui_open_uis)
if(isnull(src_object) || ui.src_object == src_object)
- ui.process(force = 1)
+ ui.process(wait * 0.1, force = 1)
count++
return count
diff --git a/code/controllers/subsystem/throwing.dm b/code/controllers/subsystem/throwing.dm
index 76a4f332fda4d..d64c2e96a782d 100644
--- a/code/controllers/subsystem/throwing.dm
+++ b/code/controllers/subsystem/throwing.dm
@@ -12,8 +12,13 @@ SUBSYSTEM_DEF(throwing)
var/list/processing = list()
/datum/controller/subsystem/throwing/stat_entry()
- ..("P:[processing.len]")
+ . = ..("P:[processing.len]")
+/datum/controller/subsystem/throwing/get_metrics()
+ . = ..()
+ var/list/cust = list()
+ cust["processing"] = length(processing)
+ .["custom"] = cust
/datum/controller/subsystem/throwing/fire(resumed = 0)
if (!resumed)
@@ -41,8 +46,8 @@ SUBSYSTEM_DEF(throwing)
/datum/thrownthing
var/atom/movable/thrownthing
- var/atom/target
var/turf/target_turf
+ var/datum/weakref/initial_target
var/target_zone
var/init_dir
var/maxrange
@@ -63,15 +68,43 @@ SUBSYSTEM_DEF(throwing)
var/delayed_time = 0
var/last_move = 0
+
+/datum/thrownthing/New(thrownthing, target, init_dir, maxrange, speed, thrower, diagonals_first, force, callback, target_zone)
+ . = ..()
+ src.thrownthing = thrownthing
+ RegisterSignal(thrownthing, COMSIG_PARENT_QDELETING, .proc/on_thrownthing_qdel)
+ src.target_turf = get_turf(target)
+ if(target_turf != target)
+ src.initial_target = WEAKREF(target)
+ src.init_dir = init_dir
+ src.maxrange = maxrange
+ src.speed = speed
+ src.thrower = thrower
+ src.diagonals_first = diagonals_first
+ src.force = force
+ src.callback = callback
+ src.target_zone = target_zone
+
+
/datum/thrownthing/Destroy()
SSthrowing.processing -= thrownthing
+ SSthrowing.currentrun -= thrownthing
thrownthing.throwing = null
thrownthing = null
- target = null
thrower = null
- callback = null
+ initial_target = null
+ if(callback)
+ QDEL_NULL(callback) //It stores a reference to the thrownthing, its source. Let's clean that.
return ..()
+
+///Defines the datum behavior on the thrownthing's qdeletion event.
+/datum/thrownthing/proc/on_thrownthing_qdel(atom/movable/source, force)
+ SIGNAL_HANDLER
+
+ qdel(src)
+
+
/datum/thrownthing/proc/tick()
var/atom/movable/AM = thrownthing
if (!isturf(AM.loc) || !AM.throwing)
@@ -82,9 +115,15 @@ SUBSYSTEM_DEF(throwing)
delayed_time += world.time - last_move
return
- if (dist_travelled && hitcheck()) //to catch sneaky things moving on our tile while we slept
- finalize()
- return
+ var/atom/movable/actual_target = initial_target?.resolve()
+
+ if(dist_travelled) //to catch sneaky things moving on our tile while we slept
+ for(var/atom/movable/obstacle as anything in get_turf(thrownthing))
+ if (obstacle == thrownthing || (obstacle == thrower && !ismob(thrownthing)))
+ continue
+ if (obstacle == actual_target || (obstacle.density && !(obstacle.flags_1 & ON_BORDER_1)))
+ finalize(TRUE, obstacle)
+ return
var/atom/step
@@ -111,14 +150,17 @@ SUBSYSTEM_DEF(throwing)
finalize()
return
- AM.Move(step, get_dir(AM, step))
-
- if (!AM.throwing) // we hit something during our move
- finalize(hit = TRUE)
+ if(!AM.Move(step, get_dir(AM, step))) // we hit something during our move...
+ if(AM.throwing) // ...but finalize() wasn't called on Bump() because of a higher level definition that doesn't always call parent.
+ finalize()
return
dist_travelled++
+ if(actual_target && !(actual_target.pass_flags & LETPASSTHROW) && actual_target.loc == AM.loc) // we crossed a movable with no density (e.g. a mouse or APC) we intend to hit anyway.
+ finalize(TRUE, actual_target)
+ return
+
if (dist_travelled > MAX_THROWING_DIST)
finalize()
return
@@ -130,39 +172,36 @@ SUBSYSTEM_DEF(throwing)
return
thrownthing.throwing = null
if (!hit)
- for (var/thing in get_turf(thrownthing)) //looking for our target on the turf we land on.
- var/atom/A = thing
- if (A == target)
+ for (var/atom/movable/obstacle as anything in get_turf(thrownthing)) //looking for our target on the turf we land on.
+ if (obstacle == target)
hit = TRUE
- thrownthing.throw_impact(A, src)
+ thrownthing.throw_impact(obstacle, src)
+ if(QDELETED(thrownthing)) //throw_impact can delete things, such as glasses smashing
+ return //deletion should already be handled by on_thrownthing_qdel()
break
if (!hit)
thrownthing.throw_impact(get_turf(thrownthing), src) // we haven't hit something yet and we still must, let's hit the ground.
+ if(QDELETED(thrownthing)) //throw_impact can delete things, such as glasses smashing
+ return //deletion should already be handled by on_thrownthing_qdel()
thrownthing.newtonian_move(init_dir)
else
thrownthing.newtonian_move(init_dir)
if(target)
thrownthing.throw_impact(target, src)
+ if(QDELETED(thrownthing)) //throw_impact can delete things, such as glasses smashing
+ return //deletion should already be handled by on_thrownthing_qdel()
if (callback)
callback.Invoke()
-
+
if(!thrownthing.zfalling) // I don't think you can zfall while thrown but hey, just in case.
var/turf/T = get_turf(thrownthing)
if(T && thrownthing.has_gravity(T))
T.zFall(thrownthing)
- qdel(src)
-
-/datum/thrownthing/proc/hit_atom(atom/A)
- finalize(hit=TRUE, target=A)
+ if(thrownthing)
+ SEND_SIGNAL(thrownthing, COMSIG_MOVABLE_THROW_LANDED, src)
+ thrownthing.movement_type &= ~THROWN
-/datum/thrownthing/proc/hitcheck()
- for (var/thing in get_turf(thrownthing))
- var/atom/movable/AM = thing
- if (AM == thrownthing || (AM == thrower && !ismob(thrownthing)))
- continue
- if (AM.density && !(AM.pass_flags & LETPASSTHROW) && !(AM.flags_1 & ON_BORDER_1))
- finalize(hit=TRUE, target=AM)
- return TRUE
+ qdel(src)
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index 7549f08982602..cfdd9e63c3480 100755
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -45,6 +45,8 @@ SUBSYSTEM_DEF(ticker)
var/maprotatechecked = 0
+ var/list/datum/game_mode/runnable_modes //list of runnable gamemodes
+
var/news_report
var/late_join_disabled
@@ -57,6 +59,12 @@ SUBSYSTEM_DEF(ticker)
var/mode_result = "undefined"
var/end_state = "undefined"
+ //Gamemode setup
+ var/gamemode_hotswap_disabled = FALSE
+ var/pre_setup_completed = FALSE
+ var/fail_counter
+ var/emergency_start = FALSE
+
//Crew Objective stuff
var/list/crewobjlist = list()
var/list/crewobjjobs = list()
@@ -113,7 +121,7 @@ SUBSYSTEM_DEF(ticker)
continue
music -= S
- if(isemptylist(music))
+ if(!length(music))
music = world.file2list(ROUND_START_MUSIC_LIST, "\n")
login_music = pick(music)
else
@@ -187,6 +195,13 @@ SUBSYSTEM_DEF(ticker)
send_tip_of_the_round()
tipped = TRUE
+ if(timeLeft <= 300 && !pre_setup_completed)
+ //Setup gamemode maps 30 seconds before roundstart.
+ if(!pre_setup())
+ fail_setup()
+ return
+ pre_setup_completed = TRUE
+
if(timeLeft <= 0)
current_state = GAME_STATE_SETTING_UP
Master.SetRunLevel(RUNLEVEL_SETUP)
@@ -194,12 +209,19 @@ SUBSYSTEM_DEF(ticker)
fire()
if(GAME_STATE_SETTING_UP)
+ if(!pre_setup_completed)
+ if(!pre_setup())
+ fail_setup()
+ return
+ else
+ message_admins("Pre-setup completed successfully, however was run late. Likely due to start-now or a bug.")
+ log_game("Pre-setup completed successfully, however was run late. Likely due to start-now or a bug.")
+ pre_setup_completed = TRUE
+ //Attempt normal setup
if(!setup())
- //setup failed
- current_state = GAME_STATE_STARTUP
- start_at = world.time + (CONFIG_GET(number/lobby_countdown) * 10)
- timeLeft = null
- Master.SetRunLevel(RUNLEVEL_LOBBY)
+ fail_setup()
+ else
+ fail_counter = null
if(GAME_STATE_PLAYING)
mode.process(wait * 0.1)
@@ -213,12 +235,35 @@ SUBSYSTEM_DEF(ticker)
declare_completion(force_ending)
Master.SetRunLevel(RUNLEVEL_POSTGAME)
-
-/datum/controller/subsystem/ticker/proc/setup()
- message_admins("Setting up game.")
- var/init_start = world.timeofday
- //Create and announce mode
- var/list/datum/game_mode/runnable_modes
+//Reverts the game to the lobby
+/datum/controller/subsystem/ticker/proc/fail_setup()
+ if(fail_counter >= 2)
+ log_game("Failed setting up [GLOB.master_mode] [fail_counter + 1] times, defaulting to extended.")
+ message_admins("Failed setting up [GLOB.master_mode] [fail_counter + 1] times, defaulting to extended.")
+ //This has failed enough, lets just get on with extended.
+ failsafe_pre_setup()
+ return
+ //Let's try this again.
+ fail_counter++
+ current_state = GAME_STATE_STARTUP
+ start_at = world.time + (CONFIG_GET(number/lobby_countdown) * 5)
+ timeLeft = null
+ Master.SetRunLevel(RUNLEVEL_LOBBY)
+ pre_setup_completed = FALSE
+ //Return to default mode
+ load_mode()
+ message_admins("Failed to setup. Failures: ([fail_counter] / 3).")
+ log_game("Setup failed.")
+
+//Fallback presetup that sets up extended.
+/datum/controller/subsystem/ticker/proc/failsafe_pre_setup()
+ //Emergerncy start extended.
+ emergency_start = TRUE
+ pre_setup_completed = TRUE
+ mode = config.pick_mode("extended")
+
+//Select gamemode and load any maps associated with it
+/datum/controller/subsystem/ticker/proc/pre_setup()
if(GLOB.master_mode == "random" || GLOB.master_mode == "secret")
runnable_modes = config.get_runnable_modes()
@@ -234,7 +279,7 @@ SUBSYSTEM_DEF(ticker)
if(!mode)
if(!runnable_modes.len)
to_chat(world, "Unable to choose playable game mode. Reverting to pre-game lobby.")
- return 0
+ return FALSE
mode = pickweight(runnable_modes)
if(!mode) //too few roundtypes all run too recently
mode = pick(runnable_modes)
@@ -246,24 +291,30 @@ SUBSYSTEM_DEF(ticker)
qdel(mode)
mode = null
SSjob.ResetOccupations()
- return 0
+ return FALSE
+
+ return mode.setup_maps()
+
+/datum/controller/subsystem/ticker/proc/setup()
+ message_admins("Setting up game.")
+ var/init_start = world.timeofday
CHECK_TICK
//Configure mode and assign player to special mode stuff
var/can_continue = 0
can_continue = src.mode.pre_setup() //Choose antagonists
CHECK_TICK
- can_continue = can_continue && SSjob.DivideOccupations() //Distribute jobs
+ can_continue = can_continue && SSjob.DivideOccupations(mode.required_jobs) //Distribute jobs
CHECK_TICK
to_chat(world, "Starting game...")
- if(!GLOB.Debug2)
+ if(!GLOB.Debug2 && !emergency_start)
if(!can_continue)
log_game("[mode.name] failed pre_setup, cause: [mode.setup_error]")
QDEL_NULL(mode)
to_chat(world, "Error setting up [GLOB.master_mode]. Reverting to pre-game lobby.")
SSjob.ResetOccupations()
- return 0
+ return FALSE
else
message_admins("DEBUG: Bypassing prestart checks...")
@@ -300,7 +351,7 @@ SUBSYSTEM_DEF(ticker)
SSdbcore.SetRoundStart()
to_chat(world, "Welcome to [station_name()], enjoy your stay!")
- SEND_SOUND(world, sound('sound/ai/welcome.ogg'))
+ SEND_SOUND(world, sound(SSstation.announcer.get_rand_welcome_sound()))
current_state = GAME_STATE_PLAYING
Master.SetRunLevel(RUNLEVEL_GAME)
@@ -311,7 +362,11 @@ SUBSYSTEM_DEF(ticker)
var/datum/holiday/holiday = SSevents.holidays[holidayname]
to_chat(world, "
[holiday.greet()]
")
+ //Setup orbits.
+ SSorbits.post_load_init()
+
PostSetup()
+ SSstat.clear_global_alert()
return TRUE
@@ -323,7 +378,7 @@ SUBSYSTEM_DEF(ticker)
var/list/adm = get_admin_counts()
var/list/allmins = adm["present"]
- send2irc("Server", "Round [GLOB.round_id ? "#[GLOB.round_id]:" : "of"] [hide_mode ? "secret":"[mode.name]"] has started[allmins.len ? ".":" with no active admins online!"]")
+ send2tgs("Server", "Round [GLOB.round_id ? "#[GLOB.round_id]:" : "of"] [hide_mode ? "secret":"[mode.name]"] has started[allmins.len ? ".":" with no active admins online!"]")
setup_done = TRUE
for(var/i in GLOB.start_landmarks_list)
@@ -350,10 +405,11 @@ SUBSYSTEM_DEF(ticker)
/datum/controller/subsystem/ticker/proc/station_explosion_detonation(atom/bomb)
if(bomb) //BOOM
- var/turf/epi = bomb.loc
qdel(bomb)
- if(epi)
- explosion(epi, 0, 256, 512, 0, TRUE, TRUE, 0, TRUE)
+ for(var/mob/M in GLOB.mob_list)
+ var/turf/T = get_turf(M)
+ if(T && is_station_level(T.z) && !istype(M.loc, /obj/structure/closet/secure_closet/freezer)) //protip: freezers protect you from nukes
+ M.gib(TRUE)
/datum/controller/subsystem/ticker/proc/create_characters()
for(var/mob/dead/new_player/player in GLOB.player_list)
@@ -372,22 +428,43 @@ SUBSYSTEM_DEF(ticker)
/datum/controller/subsystem/ticker/proc/equip_characters()
- var/captainless=1
+ var/captainless = TRUE
+ var/list/spare_id_candidates = list()
+ var/highest_rank = length(SSjob.chain_of_command) + 1
+ var/enforce_coc = CONFIG_GET(flag/spare_enforce_coc)
+
for(var/mob/dead/new_player/N in GLOB.player_list)
var/mob/living/carbon/human/player = N.new_character
if(istype(player) && player.mind && player.mind.assigned_role)
if(player.mind.assigned_role == "Captain")
- captainless=0
+ captainless = FALSE
+ spare_id_candidates += N
+ else if(captainless && (player.mind.assigned_role in GLOB.command_positions) && !(is_banned_from(N.ckey, "Captain")))
+ if(!enforce_coc)
+ spare_id_candidates += N
+ else
+ var/spare_id_priority = SSjob.chain_of_command[player.mind.assigned_role]
+ if(spare_id_priority)
+ if(spare_id_priority < highest_rank)
+ spare_id_candidates.Cut()
+ spare_id_candidates += N
+ highest_rank = spare_id_priority
+ else if(spare_id_priority == highest_rank)
+ spare_id_candidates += N
if(player.mind.assigned_role != player.mind.special_role)
- SSjob.EquipRank(N, player.mind.assigned_role, 0)
+ SSjob.EquipRank(N, player.mind.assigned_role, FALSE)
if(CONFIG_GET(flag/roundstart_traits) && ishuman(N.new_character))
SSquirks.AssignQuirks(N.new_character, N.client, TRUE)
CHECK_TICK
- if(captainless)
- for(var/mob/dead/new_player/N in GLOB.player_list)
- if(N.new_character)
- to_chat(N, "Captainship not forced on anyone.")
- CHECK_TICK
+ if(length(spare_id_candidates)) //No captain, time to choose acting captain
+ if(!enforce_coc)
+ for(var/mob/dead/new_player/player in spare_id_candidates)
+ SSjob.promote_to_captain(player, captainless)
+
+ else
+ SSjob.promote_to_captain(pick(spare_id_candidates), captainless) //This is just in case 2 heads of the same priority spawn
+ CHECK_TICK
+
/datum/controller/subsystem/ticker/proc/transfer_characters()
var/list/livings = list()
@@ -397,7 +474,7 @@ SUBSYSTEM_DEF(ticker)
qdel(player)
living.notransform = TRUE
if(living.client)
- var/obj/screen/splash/S = new(living.client, TRUE)
+ var/atom/movable/screen/splash/S = new(living.client, TRUE)
S.Fade(TRUE)
livings += living
if(livings.len)
@@ -551,7 +628,7 @@ SUBSYSTEM_DEF(ticker)
if(WIZARD_KILLED)
news_message = "Tensions have flared with the Space Wizard Federation following the death of one of their members aboard [station_name()]."
if(STATION_NUKED)
- news_message = "[station_name()] activated its self destruct device for unknown reasons. Attempts to clone the Captain so he can be arrested and executed are underway."
+ news_message = "[station_name()] activated its self-destruct device for unknown reasons. Attempts to clone the Captain so he can be arrested and executed are underway."
if(CLOCK_SUMMON)
news_message = "The garbled messages about hailing a mouse and strange energy readings from [station_name()] have been discovered to be an ill-advised, if thorough, prank by a clown."
if(CLOCK_SILICONS)
@@ -562,7 +639,7 @@ SUBSYSTEM_DEF(ticker)
news_message = "During routine evacuation procedures, the emergency shuttle of [station_name()] had its navigation protocols corrupted and went off course, but was recovered shortly after."
if(news_message)
- send2otherserver(news_source, news_message,"News_Report")
+ SStopic.crosscomms_send("news_report", news_message, news_source)
/datum/controller/subsystem/ticker/proc/GetTimeLeft()
if(isnull(SSticker.timeLeft))
@@ -583,17 +660,17 @@ SUBSYSTEM_DEF(ticker)
addtimer(CALLBACK(player, /mob/dead/new_player.proc/make_me_an_observer), 1)
/datum/controller/subsystem/ticker/proc/load_mode()
- var/mode = trim(rustg_file_read("data/mode.txt"))
+ var/mode = CONFIG_GET(string/master_mode)
if(mode)
GLOB.master_mode = mode
else
GLOB.master_mode = "extended"
- log_game("Saved mode is '[GLOB.master_mode]'")
+ log_game("Master mode is '[GLOB.master_mode]'")
+ log_config("Master mode is '[GLOB.master_mode]'")
-/datum/controller/subsystem/ticker/proc/save_mode(the_mode)
- var/F = file("data/mode.txt")
- fdel(F)
- WRITE_FILE(F, the_mode)
+/// Returns if either the master mode or the forced secret ruleset matches the mode name.
+/datum/controller/subsystem/ticker/proc/is_mode(mode_name)
+ return GLOB.master_mode == mode_name || GLOB.secret_force_mode == mode_name
/datum/controller/subsystem/ticker/proc/SetRoundEndSound(the_sound)
set waitfor = FALSE
diff --git a/code/controllers/subsystem/time_track.dm b/code/controllers/subsystem/time_track.dm
index 3b19ae31cdf6d..44dd26affe1da 100644
--- a/code/controllers/subsystem/time_track.dm
+++ b/code/controllers/subsystem/time_track.dm
@@ -1,7 +1,8 @@
SUBSYSTEM_DEF(time_track)
name = "Time Tracking"
- wait = 600
- flags = SS_NO_INIT|SS_NO_TICK_CHECK
+ wait = 100
+ flags = SS_NO_TICK_CHECK
+ init_order = INIT_ORDER_TIMETRACK
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
var/time_dilation_current = 0
@@ -15,6 +16,74 @@ SUBSYSTEM_DEF(time_track)
var/last_tick_realtime = 0
var/last_tick_byond_time = 0
var/last_tick_tickcount = 0
+#ifdef SENDMAPS_PROFILE
+ var/list/sendmaps_names_map = list(
+ "SendMaps" = "send_maps",
+ "SendMaps: Initial housekeeping" = "initial_house",
+ "SendMaps: Cleanup" = "cleanup",
+ "SendMaps: Client loop" = "client_loop",
+ "SendMaps: Per client" = "per_client",
+ "SendMaps: Per client: Deleted images" = "deleted_images",
+ "SendMaps: Per client: HUD update" = "hud_update",
+ "SendMaps: Per client: Statpanel update" = "statpanel_update",
+ "SendMaps: Per client: Map data" = "map_data",
+ "SendMaps: Per client: Map data: Check eye position" = "check_eye_pos",
+ "SendMaps: Per client: Map data: Update chunks" = "update_chunks",
+ "SendMaps: Per client: Map data: Send turfmap updates" = "turfmap_updates",
+ "SendMaps: Per client: Map data: Send changed turfs" = "changed_turfs",
+ "SendMaps: Per client: Map data: Send turf chunk info" = "turf_chunk_info",
+ "SendMaps: Per client: Map data: Send obj changes" = "obj_changes",
+ "SendMaps: Per client: Map data: Send mob changes" = "mob_changes",
+ "SendMaps: Per client: Map data: Send notable turf visual contents" = "send_turf_vis_conts",
+ "SendMaps: Per client: Map data: Send pending animations" = "pending_animations",
+ "SendMaps: Per client: Map data: Look for movable changes" = "look_for_movable_changes",
+ "SendMaps: Per client: Map data: Look for movable changes: Check notable turf visual contents" = "check_turf_vis_conts",
+ "SendMaps: Per client: Map data: Look for movable changes: Check HUD/image visual contents" = "check_hud/image_vis_contents",
+ "SendMaps: Per client: Map data: Look for movable changes: Loop through turfs in range" = "turfs_in_range",
+ "SendMaps: Per client: Map data: Look for movable changes: Movables examined" = "movables_examined",
+ )
+#endif
+
+/datum/controller/subsystem/time_track/Initialize(start_timeofday)
+ . = ..()
+ GLOB.perf_log = "[GLOB.log_directory]/perf-[GLOB.round_id ? GLOB.round_id : "NULL"]-[SSmapping.config?.map_name].csv"
+#ifdef SENDMAPS_PROFILE
+ world.Profile(PROFILE_RESTART, type = "sendmaps")
+ //Need to do the sendmaps stuff in its own file, since it works different then everything else
+ var/list/sendmaps_shorthands = list()
+ for(var/proper_name in sendmaps_names_map)
+ sendmaps_shorthands += sendmaps_names_map[proper_name]
+ sendmaps_shorthands += "[sendmaps_names_map[proper_name]]_count"
+#endif
+ log_perf(
+ list(
+ "time",
+ "players",
+ "tidi",
+ "tidi_fastavg",
+ "tidi_avg",
+ "tidi_slowavg",
+ "maptick",
+ "num_timers",
+ "air_turf_cost",
+ "air_eg_cost",
+ "air_highpressure_cost",
+ "air_hotspots_cost",
+ "air_superconductivity_cost",
+ "air_pipenets_cost",
+ "air_rebuilds_cost",
+ "air_turf_count",
+ "air_eg_count",
+ "air_hotspot_count",
+ "air_network_count",
+ "air_delta_count",
+ "air_superconductive_count"
+#ifdef SENDMAPS_PROFILE
+ ) + sendmaps_shorthands
+#else
+ )
+#endif
+ )
/datum/controller/subsystem/time_track/fire()
@@ -35,4 +104,57 @@ SUBSYSTEM_DEF(time_track)
last_tick_realtime = current_realtime
last_tick_byond_time = current_byondtime
last_tick_tickcount = current_tickcount
+
+#ifdef SENDMAPS_PROFILE
+ var/sendmaps_json = world.Profile(PROFILE_REFRESH, type = "sendmaps", format="json")
+ var/list/send_maps_data = json_decode(sendmaps_json)
+ var/send_maps_sort = send_maps_data.Copy() //Doing it like this guarentees us a properly sorted list
+
+ for(var/list/packet in send_maps_data)
+ send_maps_sort[packet["name"]] = packet
+
+ var/list/send_maps_values = list()
+ for(var/list/packet in send_maps_sort)
+ send_maps_values += packet["value"]
+ send_maps_values += packet["calls"]
+#endif
+
SSblackbox.record_feedback("associative", "time_dilation_current", 1, list("[SQLtime()]" = list("current" = "[time_dilation_current]", "avg_fast" = "[time_dilation_avg_fast]", "avg" = "[time_dilation_avg]", "avg_slow" = "[time_dilation_avg_slow]")))
+ log_perf(
+ list(
+ world.time,
+ length(GLOB.clients),
+ time_dilation_current,
+ time_dilation_avg_fast,
+ time_dilation_avg,
+ time_dilation_avg_slow,
+ MAPTICK_LAST_INTERNAL_TICK_USAGE,
+ length(SStimer.timer_id_dict),
+ SSair.cost_turfs,
+ SSair.cost_groups,
+ SSair.cost_highpressure,
+ SSair.cost_hotspots,
+ SSair.cost_superconductivity,
+ SSair.cost_pipenets,
+ SSair.cost_rebuilds,
+ length(SSair.hotspots),
+ length(SSair.networks),
+ length(SSair.high_pressure_delta),
+#ifdef SENDMAPS_PROFILE
+ ) + send_maps_values
+#else
+ )
+#endif
+ )
+
+#ifdef SENDMAPS_PROFILE
+/datum/controller/subsystem/time_track/proc/scream_maptick_data()
+ var/current_profile_data = world.Profile(PROFILE_REFRESH, type = "sendmaps", format="json")
+ log_world(current_profile_data)
+ current_profile_data = json_decode(current_profile_data)
+ var/output = ""
+ for(var/list/entry in current_profile_data)
+ output += "[entry["name"]],[entry["value"]],[entry["calls"]]\n"
+ log_world(output)
+ return output
+#endif
diff --git a/code/controllers/subsystem/timer.dm b/code/controllers/subsystem/timer.dm
index a271e5f5428d0..2c8a1cba37ce7 100644
--- a/code/controllers/subsystem/timer.dm
+++ b/code/controllers/subsystem/timer.dm
@@ -1,52 +1,81 @@
-#define BUCKET_LEN (world.fps*1*60) //how many ticks should we keep in the bucket. (1 minutes worth)
-#define BUCKET_POS(timer) (((round((timer.timeToRun - SStimer.head_offset) / world.tick_lag)+1) % BUCKET_LEN)||BUCKET_LEN)
-#define TIMER_MAX (world.time + TICKS2DS(min(BUCKET_LEN-(SStimer.practical_offset-DS2TICKS(world.time - SStimer.head_offset))-1, BUCKET_LEN-1)))
-#define TIMER_ID_MAX (2**24) //max float with integer precision
-
+/// Controls how many buckets should be kept, each representing a tick. (1 minutes worth)
+#define BUCKET_LEN (world.fps*1*60)
+/// Helper for getting the correct bucket for a given timer
+#define BUCKET_POS(timer) (((round((timer.timeToRun - timer.timer_subsystem.head_offset) / world.tick_lag)+1) % BUCKET_LEN)||BUCKET_LEN)
+/// Gets the maximum time at which timers will be invoked from buckets, used for deferring to secondary queue
+#define TIMER_MAX(timer_ss) (world.time + TICKS2DS(min(BUCKET_LEN-(timer_ss.practical_offset-DS2TICKS(world.time - timer_ss.head_offset))-1, BUCKET_LEN-1)))
+/// Max float with integer precision
+#define TIMER_ID_MAX (2**24)
+
+/**
+ * # Timer Subsystem
+ *
+ * Handles creation, callbacks, and destruction of timed events.
+ *
+ * It is important to understand the 'buckets' used in the timer subsystem are really a timed event. Each bucket is a
+ * circular doubly-linked list wherein the timed event at position n in the bucket list is a single timed event
+ * in that bucket's circular list; this can be considered the entry point to that list.
+ */
SUBSYSTEM_DEF(timer)
name = "Timer"
- wait = 1 //SS_TICKER subsystem, so wait is in ticks
+ wait = 1 // SS_TICKER subsystem, so wait is in ticks
init_order = INIT_ORDER_TIMER
flags = SS_TICKER|SS_NO_INIT
var/list/datum/timedevent/second_queue = list() //awe, yes, you've had first queue, but what about second queue?
var/list/hashes = list()
-
- var/head_offset = 0 //world.time of the first entry in the the bucket.
- var/practical_offset = 1 //index of the first non-empty item in the bucket.
- var/bucket_resolution = 0 //world.tick_lag the bucket was designed for
- var/bucket_count = 0 //how many timers are in the buckets
-
- var/list/bucket_list = list() //list of buckets, each bucket holds every timer that has to run that byond tick.
-
- var/list/timer_id_dict = list() //list of all active timers assoicated to their timer id (for easy lookup)
-
- var/list/clienttime_timers = list() //special snowflake timers that run on fancy pansy "client time"
-
+ /// world.time of the first entry in the bucket list, effectively the 'start time' of the current buckets
+ var/head_offset = 0
+ /// Index of the first non-empty bucket
+ var/practical_offset = 1
+ /// world.tick_lag the bucket was designed for
+ var/bucket_resolution = 0
+ /// How many timers are in the buckets
+ var/bucket_count = 0
+ /// List of buckets, each bucket holds every timer that has to run that byond tick
+ var/list/bucket_list = list()
+ /// List of all active timers associated to their timer ID (for easy lookup)
+ var/list/timer_id_dict = list()
+ /// Special timers that run in real-time, not BYOND time; these are more expensive to run and maintain
+ var/list/clienttime_timers = list()
+ /// Contains the last time that a timer's callback was invoked, or the last tick the SS fired if no timers are being processed
var/last_invoke_tick = 0
+ /// Keeps track of the next index to work on for client timers
+ var/next_clienttime_timer_index = 0
+ /// Contains the last time that a warning was issued for not invoking callbacks
var/static/last_invoke_warning = 0
var/static/bucket_auto_reset = TRUE
+/datum/controller/subsystem/timer/get_metrics()
+ . = ..()
+ var/list/cust = list()
+ cust["bucket_count"] = bucket_count
+ .["custom"] = cust
+
/datum/controller/subsystem/timer/PreInit()
bucket_list.len = BUCKET_LEN
head_offset = world.time
bucket_resolution = world.tick_lag
/datum/controller/subsystem/timer/stat_entry(msg)
- ..("B:[bucket_count] P:[length(second_queue)] H:[length(hashes)] C:[length(clienttime_timers)] S:[length(timer_id_dict)]")
+ . = ..("B:[bucket_count] P:[length(second_queue)] H:[length(hashes)] C:[length(clienttime_timers)] S:[length(timer_id_dict)]")
/datum/controller/subsystem/timer/fire(resumed = FALSE)
+ // Store local references to datum vars as it is faster to access them
var/lit = last_invoke_tick
- var/last_check = world.time - TICKS2DS(BUCKET_LEN*1.5)
var/list/bucket_list = src.bucket_list
+ var/last_check = world.time - TICKS2DS(BUCKET_LEN * 1.5)
+ // If there are no timers being tracked, then consider now to be the last invoked time
if(!bucket_count)
last_invoke_tick = world.time
+ // Check that we have invoked a callback in the last 1.5 minutes of BYOND time,
+ // and throw a warning and reset buckets if this is true
if(lit && lit < last_check && head_offset < last_check && last_invoke_warning < last_check)
last_invoke_warning = world.time
- var/msg = "No regular timers processed in the last [BUCKET_LEN*1.5] ticks[bucket_auto_reset ? ", resetting buckets" : ""]!"
+ var/msg = "No regular timers processed in the last [BUCKET_LEN * 1.5] ticks[bucket_auto_reset ? ", resetting buckets" : ""]!"
message_admins(msg)
WARNING(msg)
if(bucket_auto_reset)
@@ -59,7 +88,6 @@ SUBSYSTEM_DEF(timer)
continue
log_world("Active timers at index [i]:")
-
var/datum/timedevent/bucket_node = bucket_head
var/anti_loop_check = 1000
do
@@ -71,10 +99,11 @@ SUBSYSTEM_DEF(timer)
for(var/I in second_queue)
log_world(get_timer_debug_string(I))
- var/next_clienttime_timer_index = 0
- var/len = length(clienttime_timers)
-
- for (next_clienttime_timer_index in 1 to len)
+ // Process client-side timers
+ if (next_clienttime_timer_index)
+ clienttime_timers.Cut(1, next_clienttime_timer_index + 1)
+ next_clienttime_timer_index = 0
+ for (next_clienttime_timer_index in 1 to length(clienttime_timers))
if (MC_TICK_CHECK)
next_clienttime_timer_index--
break
@@ -85,7 +114,6 @@ SUBSYSTEM_DEF(timer)
var/datum/callback/callBack = ctime_timer.callBack
if (!callBack)
- clienttime_timers.Cut(next_clienttime_timer_index,next_clienttime_timer_index+1)
CRASH("Invalid timer: [get_timer_debug_string(ctime_timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset], REALTIMEOFDAY: [REALTIMEOFDAY]")
ctime_timer.spent = REALTIMEOFDAY
@@ -94,19 +122,16 @@ SUBSYSTEM_DEF(timer)
if(ctime_timer.flags & TIMER_LOOP)
ctime_timer.spent = 0
ctime_timer.timeToRun = REALTIMEOFDAY + ctime_timer.wait
- BINARY_INSERT(ctime_timer, clienttime_timers, datum/timedevent, timeToRun)
+ BINARY_INSERT(ctime_timer, clienttime_timers, /datum/timedevent, ctime_timer, timeToRun, COMPARE_KEY)
else
qdel(ctime_timer)
-
+ // Remove invoked client-time timers
if (next_clienttime_timer_index)
- clienttime_timers.Cut(1, next_clienttime_timer_index+1)
-
- if (MC_TICK_CHECK)
- return
+ clienttime_timers.Cut(1, next_clienttime_timer_index + 1)
+ next_clienttime_timer_index = 0
- var/static/list/spent = list()
- var/static/datum/timedevent/timer
+ var/static/list/invoked_timers = list()
if (practical_offset > BUCKET_LEN)
head_offset += TICKS2DS(BUCKET_LEN)
practical_offset = 1
@@ -118,111 +143,67 @@ SUBSYSTEM_DEF(timer)
resumed = FALSE
- if (!resumed)
- timer = null
-
- while (practical_offset <= BUCKET_LEN && head_offset + ((practical_offset-1)*world.tick_lag) <= world.time)
- var/datum/timedevent/head = bucket_list[practical_offset]
- if (!timer || !head || timer == head)
- head = bucket_list[practical_offset]
- timer = head
- while (timer)
+ // Iterate through each bucket starting from the practical offset
+ while (practical_offset <= BUCKET_LEN && head_offset + ((practical_offset - 1) * world.tick_lag) <= world.time)
+ var/datum/timedevent/timer
+ while ((timer = bucket_list[practical_offset]))
var/datum/callback/callBack = timer.callBack
if (!callBack)
- bucket_resolution = null //force bucket recreation
- CRASH("Invalid timer: [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]")
+ bucket_resolution = null // force bucket recreation
+ CRASH("Invalid timer: [get_timer_debug_string(timer)] world.time: [world.time], \
+ head_offset: [head_offset], practical_offset: [practical_offset]")
+
+ timer.bucketEject() //pop the timer off of the bucket list.
+ // Invoke callback if possible
if (!timer.spent)
- spent += timer
timer.spent = world.time
callBack.InvokeAsync()
last_invoke_tick = world.time
- if (MC_TICK_CHECK)
- return
-
- timer = timer.next
- if (timer == head)
- break
-
-
- bucket_list[practical_offset++] = null
+ if (timer.flags & TIMER_LOOP) // Prepare looping timers to re-enter the queue
+ timer.spent = 0
+ timer.timeToRun = world.time + timer.wait
+ timer.bucketJoin()
+ else
+ qdel(timer)
- //we freed up a bucket, lets see if anything in second_queue needs to be shifted to that bucket.
- var/i = 0
- var/L = length(second_queue)
- for (i in 1 to L)
- timer = second_queue[i]
- if (timer.timeToRun >= TIMER_MAX)
- i--
+ if (MC_TICK_CHECK)
break
- if (timer.timeToRun < head_offset)
- bucket_resolution = null //force bucket recreation
- stack_trace("[i] Invalid timer state: Timer in long run queue with a time to run less then head_offset. [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]")
-
- if (timer.callBack && !timer.spent)
- timer.callBack.InvokeAsync()
- spent += timer
- bucket_count++
- else if(!QDELETED(timer))
- qdel(timer)
- continue
-
- if (timer.timeToRun < head_offset + TICKS2DS(practical_offset-1))
- bucket_resolution = null //force bucket recreation
- stack_trace("[i] Invalid timer state: Timer in long run queue that would require a backtrack to transfer to short run queue. [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]")
- if (timer.callBack && !timer.spent)
- timer.callBack.InvokeAsync()
- spent += timer
- bucket_count++
- else if(!QDELETED(timer))
- qdel(timer)
- continue
-
- bucket_count++
- var/bucket_pos = max(1, BUCKET_POS(timer))
-
- var/datum/timedevent/bucket_head = bucket_list[bucket_pos]
- if (!bucket_head)
- bucket_list[bucket_pos] = timer
- timer.next = null
- timer.prev = null
- continue
-
- if (!bucket_head.prev)
- bucket_head.prev = bucket_head
- timer.next = bucket_head
- timer.prev = bucket_head.prev
- timer.next.prev = timer
- timer.prev.next = timer
- if (i)
- second_queue.Cut(1, i+1)
-
- timer = null
-
- bucket_count -= length(spent)
-
- for (var/i in spent)
- var/datum/timedevent/qtimer = i
- if(QDELETED(qtimer))
- bucket_count++
- continue
- if(!(qtimer.flags & TIMER_LOOP))
- qdel(qtimer)
- else
- bucket_count++
- qtimer.spent = 0
- qtimer.bucketEject()
- if(qtimer.flags & TIMER_CLIENT_TIME)
- qtimer.timeToRun = REALTIMEOFDAY + qtimer.wait
- else
- qtimer.timeToRun = world.time + qtimer.wait
- qtimer.bucketJoin()
-
- spent.len = 0
+ if (!bucket_list[practical_offset])
+ // Empty the bucket, check if anything in the secondary queue should be shifted to this bucket
+ bucket_list[practical_offset++] = null
+ var/i = 0
+ for (i in 1 to length(second_queue))
+ timer = second_queue[i]
+ if (timer.timeToRun >= TIMER_MAX(src))
+ i--
+ break
+
+ // Check for timers that are scheduled to run in the past
+ if (timer.timeToRun < head_offset)
+ bucket_resolution = null // force bucket recreation
+ stack_trace("[i] Invalid timer state: Timer in long run queue with a time to run less then head_offset. \
+ [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]")
+ break
+
+ // Check for timers that are not capable of being scheduled to run without rebuilding buckets
+ if (timer.timeToRun < head_offset + TICKS2DS(practical_offset - 1))
+ bucket_resolution = null // force bucket recreation
+ stack_trace("[i] Invalid timer state: Timer in long run queue that would require a backtrack to transfer to \
+ short run queue. [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]")
+ break
+
+ timer.bucketJoin()
+ if (i)
+ second_queue.Cut(1, i+1)
+ if (MC_TICK_CHECK)
+ break
-//formated this way to be runtime resistant
+/**
+ * Generates a string with details about the timed event for debugging purposes
+ */
/datum/controller/subsystem/timer/proc/get_timer_debug_string(datum/timedevent/TE)
. = "Timer: [TE]"
. += "Prev: [TE.prev ? TE.prev : "NULL"], Next: [TE.next ? TE.next : "NULL"]"
@@ -233,12 +214,16 @@ SUBSYSTEM_DEF(timer)
if(!TE.callBack)
. += ", NO CALLBACK"
+/**
+ * Destroys the existing buckets and creates new buckets from the existing timed events
+ */
/datum/controller/subsystem/timer/proc/reset_buckets()
- var/list/bucket_list = src.bucket_list
+ var/list/bucket_list = src.bucket_list // Store local reference to datum var, this is faster
var/list/alltimers = list()
- //collect the timers currently in the bucket
+
+ // Get all timers currently in the buckets
for (var/bucket_head in bucket_list)
- if (!bucket_head)
+ if (!bucket_head) // if bucket is empty for this tick
continue
var/datum/timedevent/bucket_node = bucket_head
do
@@ -246,25 +231,38 @@ SUBSYSTEM_DEF(timer)
bucket_node = bucket_node.next
while(bucket_node && bucket_node != bucket_head)
+ // Empty the list by zeroing and re-assigning the length
bucket_list.len = 0
bucket_list.len = BUCKET_LEN
+ // Reset values for the subsystem to their initial values
practical_offset = 1
bucket_count = 0
head_offset = world.time
bucket_resolution = world.tick_lag
+ // Add all timed events from the secondary queue as well
alltimers += second_queue
+
+ // If there are no timers being tracked by the subsystem,
+ // there is no need to do any further rebuilding
if (!length(alltimers))
return
+ // Sort all timers by time to run
sortTim(alltimers, .proc/cmp_timer)
+ // Get the earliest timer, and if the TTR is earlier than the current world.time,
+ // then set the head offset appropriately to be the earliest time tracked by the
+ // current set of buckets
var/datum/timedevent/head = alltimers[1]
-
if (head.timeToRun < head_offset)
head_offset = head.timeToRun
+ // Iterate through each timed event and insert it into an appropriate bucket,
+ // up unto the point that we can no longer insert into buckets as the TTR
+ // is outside the range we are tracking, then insert the remainder into the
+ // secondary queue
var/new_bucket_count
var/i = 1
for (i in 1 to length(alltimers))
@@ -272,34 +270,37 @@ SUBSYSTEM_DEF(timer)
if (!timer)
continue
- var/bucket_pos = BUCKET_POS(timer)
- if (timer.timeToRun >= TIMER_MAX)
+ // Check that the TTR is within the range covered by buckets, when exceeded we've finished
+ if (timer.timeToRun >= TIMER_MAX(src))
i--
break
-
+ // Check that timer has a valid callback and hasn't been invoked
if (!timer.callBack || timer.spent)
WARNING("Invalid timer: [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]")
if (timer.callBack)
qdel(timer)
continue
+ // Insert the timer into the bucket, and perform necessary circular doubly-linked list operations
new_bucket_count++
+ var/bucket_pos = BUCKET_POS(timer)
var/datum/timedevent/bucket_head = bucket_list[bucket_pos]
if (!bucket_head)
bucket_list[bucket_pos] = timer
timer.next = null
timer.prev = null
continue
-
if (!bucket_head.prev)
bucket_head.prev = bucket_head
timer.next = bucket_head
timer.prev = bucket_head.prev
timer.next.prev = timer
timer.prev.next = timer
+
+ // Cut the timers that are tracked by the buckets from the secondary queue
if (i)
- alltimers.Cut(1, i+1)
+ alltimers.Cut(1, i + 1)
second_queue = alltimers
bucket_count = new_bucket_count
@@ -310,46 +311,68 @@ SUBSYSTEM_DEF(timer)
timer_id_dict |= SStimer.timer_id_dict
bucket_list |= SStimer.bucket_list
+/**
+ * # Timed Event
+ *
+ * This is the actual timer, it contains the callback and necessary data to maintain
+ * the timer.
+ *
+ * See the documentation for the timer subsystem for an explanation of the buckets referenced
+ * below in next and prev
+ */
/datum/timedevent
+ /// ID used for timers when the TIMER_STOPPABLE flag is present
var/id
+ /// The callback to invoke after the timer completes
var/datum/callback/callBack
+ /// The time at which the callback should be invoked at
var/timeToRun
+ /// The length of the timer
var/wait
+ /// Unique hash generated when TIMER_UNIQUE flag is present
var/hash
+ /// The source of the timedevent, whatever called addtimer
+ var/source
+ /// Flags associated with the timer, see _DEFINES/subsystems.dm
var/list/flags
- var/spent = 0 //time we ran the timer.
- var/name //for easy debugging.
- //cicular doublely linked list
+ /// Time at which the timer was invoked or destroyed
+ var/spent = 0
+ /// An informative name generated for the timer, useful for bugging
+ var/name
+ /// Next timed event in the bucket
var/datum/timedevent/next
+ /// Previous timed event in the bucket
var/datum/timedevent/prev
+ /// The timer subsystem this event is associated with
+ var/datum/controller/subsystem/timer/timer_subsystem
-/datum/timedevent/New(datum/callback/callBack, wait, flags, hash)
+/datum/timedevent/New(datum/callback/callBack, wait, flags, datum/controller/subsystem/timer/timer_subsystem, hash, source)
var/static/nextid = 1
id = TIMER_ID_NULL
src.callBack = callBack
src.wait = wait
src.flags = flags
src.hash = hash
+ src.source = source
+ src.timer_subsystem = timer_subsystem || SStimer
- if (flags & TIMER_CLIENT_TIME)
- timeToRun = REALTIMEOFDAY + wait
- else
- timeToRun = world.time + wait
+ // Determine time at which the timer's callback should be invoked
+ timeToRun = (flags & TIMER_CLIENT_TIME ? REALTIMEOFDAY : world.time) + wait
+ // Include the timer in the hash table if the timer is unique
if (flags & TIMER_UNIQUE)
- SStimer.hashes[hash] = src
+ timer_subsystem.hashes[hash] = src
+ // Generate ID for the timer if the timer is stoppable, include in the timer id dictionary
if (flags & TIMER_STOPPABLE)
id = num2text(nextid, 100)
if (nextid >= SHORT_REAL_LIMIT)
- nextid += min(1, 2**round(nextid/SHORT_REAL_LIMIT))
+ nextid += min(1, 2 ** round(nextid / SHORT_REAL_LIMIT))
else
nextid++
- SStimer.timer_id_dict[id] = src
+ timer_subsystem.timer_id_dict[id] = src
- name = "Timer: [id] (\ref[src]), TTR: [timeToRun], Flags: [jointext(bitfield2list(flags, list("TIMER_UNIQUE", "TIMER_OVERRIDE", "TIMER_CLIENT_TIME", "TIMER_STOPPABLE", "TIMER_NO_HASH_WAIT", "TIMER_LOOP")), ", ")], callBack: \ref[callBack], callBack.object: [callBack.object]\ref[callBack.object]([getcallingtype()]), callBack.delegate:[callBack.delegate]([callBack.arguments ? callBack.arguments.Join(", ") : ""])"
-
- if ((timeToRun < world.time || timeToRun < SStimer.head_offset) && !(flags & TIMER_CLIENT_TIME))
+ if ((timeToRun < world.time || timeToRun < timer_subsystem.head_offset) && !(flags & TIMER_CLIENT_TIME))
CRASH("Invalid timer state: Timer created that would require a backtrack to run (addtimer would never let this happen): [SStimer.get_timer_debug_string(src)]")
if (callBack.object != GLOBAL_PROC && !QDESTROYING(callBack.object))
@@ -360,7 +383,7 @@ SUBSYSTEM_DEF(timer)
/datum/timedevent/Destroy()
..()
if (flags & TIMER_UNIQUE && hash)
- SStimer.hashes -= hash
+ timer_subsystem.hashes -= hash
if (callBack && callBack.object && callBack.object != GLOBAL_PROC && callBack.object.active_timers)
callBack.object.active_timers -= src
@@ -369,12 +392,12 @@ SUBSYSTEM_DEF(timer)
callBack = null
if (flags & TIMER_STOPPABLE)
- SStimer.timer_id_dict -= id
+ timer_subsystem.timer_id_dict -= id
if (flags & TIMER_CLIENT_TIME)
if (!spent)
spent = world.time
- SStimer.clienttime_timers -= src
+ timer_subsystem.clienttime_timers -= src
return QDEL_HINT_IWILLGC
if (!spent)
@@ -389,23 +412,39 @@ SUBSYSTEM_DEF(timer)
prev = null
return QDEL_HINT_IWILLGC
+/**
+ * Removes this timed event from any relevant buckets, or the secondary queue
+ */
/datum/timedevent/proc/bucketEject()
+ // Attempt to find bucket that contains this timed event
var/bucketpos = BUCKET_POS(src)
- var/list/bucket_list = SStimer.bucket_list
- var/list/second_queue = SStimer.second_queue
+
+ // Store local references for the bucket list and secondary queue
+ // This is faster than referencing them from the datum itself
+ var/list/bucket_list = timer_subsystem.bucket_list
+ var/list/second_queue = timer_subsystem.second_queue
+
+ // Attempt to get the head of the bucket
var/datum/timedevent/buckethead
if(bucketpos > 0)
buckethead = bucket_list[bucketpos]
+
+ // Decrement the number of timers in buckets if the timed event is
+ // the head of the bucket, or has a TTR less than TIMER_MAX implying it fits
+ // into an existing bucket, or is otherwise not present in the secondary queue
if(buckethead == src)
bucket_list[bucketpos] = next
- SStimer.bucket_count--
- else if(timeToRun < TIMER_MAX || next || prev)
- SStimer.bucket_count--
+ timer_subsystem.bucket_count--
+ else if(timeToRun < TIMER_MAX(timer_subsystem))
+ timer_subsystem.bucket_count--
else
var/l = length(second_queue)
second_queue -= src
if(l == length(second_queue))
- SStimer.bucket_count--
+ timer_subsystem.bucket_count--
+
+ // Remove the timed event from the bucket, ensuring to maintain
+ // the integrity of the bucket's list if relevant
if(prev != next)
prev.next = next
next.prev = prev
@@ -414,32 +453,47 @@ SUBSYSTEM_DEF(timer)
next?.prev = null
prev = next = null
+/**
+ * Attempts to add this timed event to a bucket, will enter the secondary queue
+ * if there are no appropriate buckets at this time.
+ *
+ * Secondary queueing of timed events will occur when the timespan covered by the existing
+ * buckets is exceeded by the time at which this timed event is scheduled to be invoked.
+ * If the timed event is tracking client time, it will be added to a special bucket.
+ */
/datum/timedevent/proc/bucketJoin()
- var/list/L
+ // Generate debug-friendly name for timer
+ var/static/list/bitfield_flags = list("TIMER_UNIQUE", "TIMER_OVERRIDE", "TIMER_CLIENT_TIME", "TIMER_STOPPABLE", "TIMER_NO_HASH_WAIT", "TIMER_LOOP")
+ name = "Timer: [id] (\ref[src]), TTR: [timeToRun], wait:[wait] Flags: [jointext(bitfield2list(flags, bitfield_flags), ", ")], \
+ callBack: \ref[callBack], callBack.object: [callBack.object]\ref[callBack.object]([getcallingtype()]), \
+ callBack.delegate:[callBack.delegate]([callBack.arguments ? callBack.arguments.Join(", ") : ""]), source: [source]"
+ // Check if this timed event should be diverted to the client time bucket, or the secondary queue
+ var/list/L
if (flags & TIMER_CLIENT_TIME)
- L = SStimer.clienttime_timers
- else if (timeToRun >= TIMER_MAX)
- L = SStimer.second_queue
-
+ L = timer_subsystem.clienttime_timers
+ else if (timeToRun >= TIMER_MAX(timer_subsystem))
+ L = timer_subsystem.second_queue
if(L)
- BINARY_INSERT(src, L, datum/timedevent, timeToRun)
+ BINARY_INSERT(src, L, /datum/timedevent, src, timeToRun, COMPARE_KEY)
return
- //get the list of buckets
- var/list/bucket_list = SStimer.bucket_list
+ // Get a local reference to the bucket list, this is faster than referencing the datum
+ var/list/bucket_list = timer_subsystem.bucket_list
- //calculate our place in the bucket list
+ // Find the correct bucket for this timed event
var/bucket_pos = BUCKET_POS(src)
-
- //get the bucket for our tick
var/datum/timedevent/bucket_head = bucket_list[bucket_pos]
- SStimer.bucket_count++
- //empty bucket, we will just add ourselves
+ timer_subsystem.bucket_count++
+
+ // If there is no timed event at this position, then the bucket is 'empty'
+ // and we can just set this event to that position
if (!bucket_head)
bucket_list[bucket_pos] = src
return
- //other wise, lets do a simplified linked list add.
+
+ // Otherwise, we merely add this timed event into the bucket, which is a
+ // circularly doubly-linked list
if (!bucket_head.prev)
bucket_head.prev = bucket_head
next = bucket_head
@@ -447,6 +501,9 @@ SUBSYSTEM_DEF(timer)
next.prev = src
prev.next = src
+/**
+ * Returns a string of the type of the callback for this timer
+ */
/datum/timedevent/proc/getcallingtype()
. = "ERROR"
if (callBack.object == GLOBAL_PROC)
@@ -454,8 +511,16 @@ SUBSYSTEM_DEF(timer)
else
. = "[callBack.object.type]"
-/proc/addtimer(datum/callback/callback, wait = 0, flags = 0)
-
+/**
+ * Create a new timer and insert it in the queue.
+ * You should not call this directly, and should instead use the addtimer macro, which includes source information.
+ *
+ * Arguments:
+ * * callback the callback to call on timer finish
+ * * wait deciseconds to run the timer for
+ * * flags flags for this timer, see: code\__DEFINES\subsystems.dm
+ */
+/proc/_addtimer(datum/callback/callback, wait = 0, flags = 0, datum/controller/subsystem/timer/timer_subsystem, file, line)
if (!callback)
CRASH("addtimer called without a callback")
@@ -470,8 +535,10 @@ SUBSYSTEM_DEF(timer)
if(wait >= INFINITY)
CRASH("Attempted to create timer with INFINITY delay")
- var/hash
+ timer_subsystem = timer_subsystem || SStimer
+ // Generate hash if relevant for timed events with the TIMER_UNIQUE flag
+ var/hash
if (flags & TIMER_UNIQUE)
var/list/hashlist
if(flags & TIMER_NO_HASH_WAIT)
@@ -481,13 +548,13 @@ SUBSYSTEM_DEF(timer)
hashlist += callback.arguments
hash = hashlist.Join("|||||||")
- var/datum/timedevent/hash_timer = SStimer.hashes[hash]
+ var/datum/timedevent/hash_timer = timer_subsystem.hashes[hash]
if(hash_timer)
- if (hash_timer.spent) //it's pending deletion, pretend it doesn't exist.
- hash_timer.hash = null //but keep it from accidentally deleting us
+ if (hash_timer.spent) // it's pending deletion, pretend it doesn't exist.
+ hash_timer.hash = null // but keep it from accidentally deleting us
else
if (flags & TIMER_OVERRIDE)
- hash_timer.hash = null //no need having it delete it's hash if we are going to replace it
+ hash_timer.hash = null // no need having it delete it's hash if we are going to replace it
qdel(hash_timer)
else
if (hash_timer.flags & TIMER_STOPPABLE)
@@ -496,10 +563,16 @@ SUBSYSTEM_DEF(timer)
else if(flags & TIMER_OVERRIDE)
stack_trace("TIMER_OVERRIDE used without TIMER_UNIQUE")
- var/datum/timedevent/timer = new(callback, wait, flags, hash)
+ var/datum/timedevent/timer = new(callback, wait, flags, timer_subsystem, hash, file && "[file]:[line]")
return timer.id
-/proc/deltimer(id)
+/**
+ * Delete a timer
+ *
+ * Arguments:
+ * * id a timerid or a /datum/timedevent
+ */
+/proc/deltimer(id, datum/controller/subsystem/timer/timer_subsystem)
if (!id)
return FALSE
if (id == TIMER_ID_NULL)
@@ -508,13 +581,34 @@ SUBSYSTEM_DEF(timer)
if (istype(id, /datum/timedevent))
qdel(id)
return TRUE
+ timer_subsystem = timer_subsystem || SStimer
//id is string
- var/datum/timedevent/timer = SStimer.timer_id_dict[id]
+ var/datum/timedevent/timer = timer_subsystem.timer_id_dict[id]
if (timer && !timer.spent)
qdel(timer)
return TRUE
return FALSE
+/**
+ * Get the remaining deciseconds on a timer
+ *
+ * Arguments:
+ * * id a timerid or a /datum/timedevent
+ */
+/proc/timeleft(id, datum/controller/subsystem/timer/timer_subsystem)
+ if (!id)
+ return null
+ if (id == TIMER_ID_NULL)
+ CRASH("Tried to get timeleft of a null timerid. Use TIMER_STOPPABLE flag")
+ if (istype(id, /datum/timedevent))
+ var/datum/timedevent/timer = id
+ return timer.timeToRun - world.time
+ timer_subsystem = timer_subsystem || SStimer
+ //id is string
+ var/datum/timedevent/timer = timer_subsystem.timer_id_dict[id]
+ if (timer && !timer.spent)
+ return timer.timeToRun - world.time
+ return null
#undef BUCKET_LEN
#undef BUCKET_POS
diff --git a/code/controllers/subsystem/title.dm b/code/controllers/subsystem/title.dm
index a8fb5e8334705..6ff7db35a5019 100644
--- a/code/controllers/subsystem/title.dm
+++ b/code/controllers/subsystem/title.dm
@@ -23,7 +23,7 @@ SUBSYSTEM_DEF(title)
if(length(provisional_title_screens))
file_path = "[global.config.directory]/title_screens/images/[pick(provisional_title_screens)]"
else
- file_path = "icons/default_title.dmi"
+ file_path = "icons/runtime/default_title.dmi"
ASSERT(fexists(file_path))
@@ -50,7 +50,7 @@ SUBSYSTEM_DEF(title)
for(var/thing in GLOB.clients)
if(!thing)
continue
- var/obj/screen/splash/S = new(thing, FALSE)
+ var/atom/movable/screen/splash/S = new(thing, FALSE)
S.Fade(FALSE,FALSE)
/datum/controller/subsystem/title/Recover()
diff --git a/code/controllers/subsystem/topic.dm b/code/controllers/subsystem/topic.dm
new file mode 100644
index 0000000000000..6d168bd40a53d
--- /dev/null
+++ b/code/controllers/subsystem/topic.dm
@@ -0,0 +1,128 @@
+SUBSYSTEM_DEF(topic)
+ name = "Topic"
+ init_order = INIT_ORDER_TOPIC
+ flags = SS_NO_FIRE
+
+/datum/controller/subsystem/topic/Initialize(timeofday)
+ // Initialize topic datums
+ var/list/anonymous_functions = list()
+ for(var/path in subtypesof(/datum/world_topic))
+ var/datum/world_topic/T = new path()
+ if(T.anonymous)
+ anonymous_functions[T.key] = TRUE
+ GLOB.topic_commands[T.key] = T
+
+ // Setup the anonymous access token
+ GLOB.topic_tokens["anonymous"] = anonymous_functions
+ // Parse and setup authed tokens from config
+ var/list/tokens = CONFIG_GET(keyed_list/comms_key)
+ for(var/token in tokens)
+ var/list/keys = list()
+ if(tokens[token] == "all")
+ for(var/key in GLOB.topic_commands)
+ keys[key] = TRUE
+ else
+ for(var/key in splittext(tokens[token], ","))
+ keys[trim(key)] = TRUE
+ // Grant access to anonymous topic calls (version, authed functions etc.) by default
+ keys |= anonymous_functions
+ GLOB.topic_tokens[token] = keys
+
+ var/list/servers = CONFIG_GET(keyed_list/cross_server)
+ for(var/server in servers)
+ handshake_server(server, servers[server])
+
+ return ..()
+
+/*
+ A bit of background for future coders maintaining this:
+ When we contact a server or we contact a server to handshake, there are two outcomes to account for:
+
+ First, if the server being contacted has freshly rebooted, they will have no knowledge of us,
+ and as such will need to store our server details too by sending a handshake request back to us for information.
+
+ Second, if we rebooted while the other server was mid-round, they simply can send back the current details they have about us,
+ and don't need to get any additional information of their own.
+
+ Code for handling requests is in world_topic.dm
+
+ Basically, this proc exists to allow servers to make ad-hoc connections, going offline and coming back up without interrupting anything.
+*/
+/datum/controller/subsystem/topic/proc/handshake_server(addr, key)
+ set waitfor = FALSE
+ var/logging = CONFIG_GET(flag/log_world_topic)
+ var/request = list("query" = "api_do_handshake", "auth" = key, "source" = CONFIG_GET(string/cross_comms_name))
+ var/response_raw = world.Export("[addr]?[json_encode(request)]")
+ request["auth"] = "***[copytext(request["auth"], -4)]"
+ if(!response_raw)
+ if(logging)
+ log_topic("Topic handshake with [addr] failed. Server did not return a response. Payload: \"[json_encode(request)]\"")
+ return
+ var/response = json_decode(response_raw)
+ if(response["statuscode"] != 200)
+ if(logging)
+ log_topic("Topic handshake with [addr] failed. Payload: \"[json_encode(request)]\", Response: \"[response_raw]\"")
+ return
+ var/list/local_funcs = GLOB.topic_tokens[LAZYACCESS(response["data"], "token")]
+ var/list/remote_funcs = LAZYACCESS(response["data"], "functions")
+ if(!local_funcs || !remote_funcs)
+ if(logging)
+ log_topic("Topic handshake with [addr] completed, but no mutual functions were found. Payload: \"[json_encode(request)]\", Response: \"[response_raw]\"")
+ return
+ var/list/functions = list()
+ // Both servers need to have a function available to each other for it to be valid
+ for(var/func in remote_funcs)
+ if(local_funcs[func])
+ functions[func] = TRUE
+ GLOB.topic_servers[addr] = functions
+ if(logging)
+ log_topic("Handshake with [addr] successful.")
+
+/**
+ * Wrapper proc for world.Export() that adds additional params and handles auth.
+ *
+ * Params:
+ *
+ * * addr: address of the recieving BYOND server (*including* the byond://)
+ * * query: name of the topic endpoint to request
+ * * params: associated list of parameters to send to the recieving server
+ * * anonymous: TRUE or FALSE whether to use anonymous token for the request *(default: FALSE)*
+ * Note that request will fail if a token cannot be found for the target server and anonymous is not set.
+ * * nocheck: TRUE or FALSE whether to check if the recieving server is authorized to get the topic call *(default: FALSE)*
+*/
+/datum/controller/subsystem/topic/proc/export(addr, query, list/params, anonymous = FALSE, nocheck = FALSE)
+ var/list/request = list()
+ request["query"] = query
+
+ if(anonymous)
+ var/datum/world_topic/topic = GLOB.topic_commands[query]
+ if((!istype(topic) || !topic.anonymous) && !nocheck)
+ return
+ request["auth"] = "anonymous"
+ else
+ var/list/servers = CONFIG_GET(keyed_list/cross_server)
+ if(!servers[addr] || (!LAZYACCESS(GLOB.topic_servers[addr], query) && !nocheck))
+ return // Couldn't find an authorized key, or trying to send secure data to unsecure server
+ request["auth"] = servers[addr]
+
+ request.Add(params)
+ request["source"] = CONFIG_GET(string/cross_comms_name)
+ var/result = world.Export("[addr]?[rustg_url_encode(json_encode(request))]")
+ if(CONFIG_GET(flag/log_world_topic))
+ request["auth"] = "***[copytext(request["auth"], -4)]"
+ log_topic("outgoing: \"[json_encode(request)]\", response: \"[result]\", auth: [request["auth"]], to: [addr], anonymous: [anonymous]")
+
+/**
+ * Broadcast topic to all known authorized servers for things like comms consoles or ahelps.
+ * Follows a set topic format for ease of use, and is therefore incompatible with other topic endpoints.
+ *
+ * Params:
+ *
+ * * query: name of the topic endpoint for the requests
+ * * msg: message text to send
+ * * sender: name of the sending entity (station name, ckey etc)
+*/
+/datum/controller/subsystem/topic/proc/crosscomms_send(query, msg, sender)
+ var/list/servers = CONFIG_GET(keyed_list/cross_server)
+ for(var/I in servers)
+ export(I, query, list("message" = msg, "message_sender" = sender))
diff --git a/code/controllers/subsystem/traumas.dm b/code/controllers/subsystem/traumas.dm
index cbe52b6218246..76c967f4c8b8c 100644
--- a/code/controllers/subsystem/traumas.dm
+++ b/code/controllers/subsystem/traumas.dm
@@ -68,7 +68,7 @@ SUBSYSTEM_DEF(traumas)
/obj/item/pda/clown, /obj/item/grown/bananapeel)),
"greytide" = typecacheof(list(/obj/item/clothing/under/color/grey, /obj/item/melee/baton/cattleprod,
- /obj/item/twohanded/spear, /obj/item/clothing/mask/gas)),
+ /obj/item/spear, /obj/item/clothing/mask/gas/old)),
"lizards" = typecacheof(list(/obj/item/toy/plush/lizardplushie, /obj/item/reagent_containers/food/snacks/kebab/tail,
/obj/item/organ/tail/lizard, /obj/item/reagent_containers/food/drinks/bottle/lizardwine)),
@@ -109,7 +109,7 @@ SUBSYSTEM_DEF(traumas)
/obj/item/card/id/captains_spare, /obj/item/card/id/centcom, /obj/machinery/door/airlock/command)),
"the supernatural" = typecacheof(list(/obj/structure/destructible/cult, /obj/item/tome,
- /obj/item/melee/cultblade, /obj/item/twohanded/required/cult_bastard, /obj/item/restraints/legcuffs/bola/cult,
+ /obj/item/melee/cultblade, /obj/item/cult_bastard, /obj/item/restraints/legcuffs/bola/cult,
/obj/item/clothing/suit/cultrobes, /obj/item/clothing/suit/space/hardsuit/cult,
/obj/item/clothing/suit/hooded/cultrobes, /obj/item/clothing/head/hooded/cult_hoodie, /obj/effect/rune,
/obj/item/stack/sheet/runed_metal, /obj/machinery/door/airlock/cult, /obj/singularity/narsie,
@@ -139,7 +139,7 @@ SUBSYSTEM_DEF(traumas)
"anime" = typecacheof(list(/obj/item/clothing/under/costume/schoolgirl, /obj/item/katana, /obj/item/reagent_containers/food/snacks/sashimi, /obj/item/reagent_containers/food/snacks/chawanmushi,
/obj/item/reagent_containers/food/drinks/bottle/sake, /obj/item/throwing_star, /obj/item/clothing/head/kitty/genuine, /obj/item/clothing/suit/space/space_ninja,
- /obj/item/clothing/mask/gas/space_ninja, /obj/item/clothing/shoes/space_ninja, /obj/item/clothing/gloves/space_ninja, /obj/item/twohanded/vibro_weapon,
+ /obj/item/clothing/mask/gas/space_ninja, /obj/item/clothing/shoes/space_ninja, /obj/item/clothing/gloves/space_ninja, /obj/item/vibro_weapon,
/obj/item/nullrod/scythe/vibro, /obj/item/energy_katana, /obj/item/toy/katana, /obj/item/nullrod/claymore/katana, /obj/structure/window/paperframe, /obj/structure/mineral_door/paperframe))
)
@@ -153,7 +153,7 @@ SUBSYSTEM_DEF(traumas)
phobia_species = list("lizards" = typecacheof(list(/datum/species/lizard)),
"skeletons" = typecacheof(list(/datum/species/skeleton, /datum/species/plasmaman)),
- "conspiracies" = typecacheof(list(/datum/species/abductor, /datum/species/lizard, /datum/species/synth)),
+ "conspiracies" = typecacheof(list(/datum/species/abductor, /datum/species/lizard)),
"robots" = typecacheof(list(/datum/species/android)),
"the supernatural" = typecacheof(list(/datum/species/golem/clockwork, /datum/species/golem/runic)),
"aliens" = typecacheof(list(/datum/species/abductor, /datum/species/jelly, /datum/species/pod,
diff --git a/code/controllers/subsystem/vis_overlays.dm b/code/controllers/subsystem/vis_overlays.dm
index d67ce85bce9fa..65805d4faf7cc 100644
--- a/code/controllers/subsystem/vis_overlays.dm
+++ b/code/controllers/subsystem/vis_overlays.dm
@@ -26,6 +26,7 @@ SUBSYSTEM_DEF(vis_overlays)
overlay.unused = world.time
else if(overlay.unused && overlay.unused + overlay.cache_expiration < world.time)
vis_overlay_cache -= key
+ unique_vis_overlays -= overlay
qdel(overlay)
if(MC_TICK_CHECK)
return
@@ -81,6 +82,8 @@ SUBSYSTEM_DEF(vis_overlays)
UnregisterSignal(thing, COMSIG_ATOM_DIR_CHANGE)
/datum/controller/subsystem/vis_overlays/proc/rotate_vis_overlay(atom/thing, old_dir, new_dir)
+ SIGNAL_HANDLER
+
if(old_dir == new_dir)
return
var/rotation = dir2angle(old_dir) - dir2angle(new_dir)
diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm
index 0a05cd0bdf1bd..6a0a296ac578e 100644
--- a/code/controllers/subsystem/vote.dm
+++ b/code/controllers/subsystem/vote.dm
@@ -6,42 +6,36 @@ SUBSYSTEM_DEF(vote)
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
- var/initiator = null
- var/started_time = null
+ var/mode
+ var/question
+ var/initiator
+ var/started_time
var/time_remaining = 0
- var/mode = null
- var/question = null
- var/list/choices = list()
var/list/voted = list()
var/list/voting = list()
+ var/list/choices = list()
+ var/list/choice_by_ckey = list()
var/list/generated_actions = list()
/datum/controller/subsystem/vote/fire() //called by master_controller
- if(mode)
- time_remaining = round((started_time + CONFIG_GET(number/vote_period) - world.time)/10)
-
- if(time_remaining < 0)
- result()
- for(var/client/C in voting)
- C << browse(null, "window=vote;can_close=0")
- reset()
- else
- var/datum/browser/client_popup
- for(var/client/C in voting)
- client_popup = new(C, "vote", "Voting Panel")
- client_popup.set_window_options("can_close=0")
- client_popup.set_content(interface(C))
- client_popup.open(FALSE)
-
+ if(!mode)
+ return
+ time_remaining = round((started_time + CONFIG_GET(number/vote_period) - world.time)/10)
+ if(time_remaining < 0)
+ result()
+ SStgui.close_uis(src)
+ reset()
/datum/controller/subsystem/vote/proc/reset()
- initiator = null
- time_remaining = 0
mode = null
- question = null
- choices.Cut()
voted.Cut()
voting.Cut()
+ choices.Cut()
+ question = null
+ initiator = null
+ time_remaining = 0
+ choice_by_ckey.Cut()
+
remove_action_buttons()
/datum/controller/subsystem/vote/proc/get_result()
@@ -135,18 +129,15 @@ SUBSYSTEM_DEF(vote)
/datum/controller/subsystem/vote/proc/result()
. = announce_result()
- var/restart = 0
+ var/restart = FALSE
if(.)
switch(mode)
if("restart")
if(. == "Restart Round")
- restart = 1
+ restart = TRUE
if("gamemode")
if(GLOB.master_mode != .)
- SSticker.save_mode(.)
- if(SSticker.HasRoundStarted())
- restart = 1
- else
+ if(!SSticker.HasRoundStarted())
GLOB.master_mode = .
if("map")
SSmapping.changemap(global.config.maplist[.])
@@ -154,14 +145,15 @@ SUBSYSTEM_DEF(vote)
if("transfer")
if(. == "Initiate Crew Transfer")
SSshuttle.requestEvac(null, "Crew Transfer Requested.")
+ SSshuttle.emergencyNoRecall = TRUE //Prevent Recall.
var/obj/machinery/computer/communications/C = locate() in GLOB.machines
if(C)
C.post_status("shuttle")
if(restart)
- var/active_admins = 0
- for(var/client/C in GLOB.admins)
+ var/active_admins = FALSE
+ for(var/client/C in GLOB.admins+GLOB.deadmins)
if(!C.is_afk() && check_rights_for(C, R_SERVER))
- active_admins = 1
+ active_admins = TRUE
break
if(!active_admins)
SSticker.Reboot("Restart vote successful.", "restart vote")
@@ -172,15 +164,21 @@ SUBSYSTEM_DEF(vote)
return .
/datum/controller/subsystem/vote/proc/submit_vote(vote)
- if(mode)
- if(CONFIG_GET(flag/no_dead_vote) && (usr.stat == DEAD && !isnewplayer(usr)) && !usr.client.holder && mode != "map")
- return 0
- if(!(usr.ckey in voted))
- if(vote && 1<=vote && vote<=choices.len)
- voted += usr.ckey
- choices[choices[vote]]++ //check this
- return vote
- return 0
+ if(!mode)
+ return FALSE
+ if(CONFIG_GET(flag/no_dead_vote) && (usr.stat == DEAD && !isnewplayer(usr)) && !usr.client.holder && mode != "map")
+ return FALSE
+ if(!(vote && 1<=vote && vote<=choices.len))
+ return FALSE
+ // If user has already voted
+ if(usr.ckey in voted)
+ choices[choices[choice_by_ckey[usr.ckey]]]--
+ else
+ voted += usr.ckey
+
+ choice_by_ckey[usr.ckey] = vote
+ choices[choices[vote]]++ //check this
+ return vote
/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, initiator_key, forced=FALSE, popup=FALSE)
if(!mode)
@@ -190,12 +188,12 @@ SUBSYSTEM_DEF(vote)
to_chat(usr, "There is already a vote in progress! please wait for it to finish.")
return 0
- var/admin = FALSE
+ var/lower_admin = FALSE
var/ckey = ckey(initiator_key)
if(GLOB.admin_datums[ckey] || forced)
- admin = TRUE
+ lower_admin = TRUE
- if(next_allowed_time > world.time && !admin)
+ if(next_allowed_time > world.time && !lower_admin)
to_chat(usr, "A vote was initiated recently, you must wait [DisplayTimeText(next_allowed_time-world.time)] before a new vote can be started!")
return 0
@@ -237,7 +235,7 @@ SUBSYSTEM_DEF(vote)
text += "\n[question]"
log_vote(text)
var/vp = CONFIG_GET(number/vote_period)
- to_chat(world, "\n[text]\nType vote or click here to place your votes.\nYou have [DisplayTimeText(vp)] to vote.")
+ to_chat(world, "\n[text]\nType vote or click here to place your votes.\nYou have [DisplayTimeText(vp)] to vote.")
time_remaining = round(vp/10)
for(var/c in GLOB.clients)
var/client/C = c
@@ -254,95 +252,80 @@ SUBSYSTEM_DEF(vote)
return 1
return 0
-/datum/controller/subsystem/vote/proc/interface(client/C)
- if(!C)
- return
- var/admin = 0
- var/trialmin = 0
- if(C.holder)
- admin = 1
- if(check_rights_for(C, R_ADMIN))
- trialmin = 1
- voting |= C
-
- if(mode)
- if(question)
- . += "
"
- . += "Close"
- return .
-
+/datum/controller/subsystem/vote/proc/remove_action_buttons()
+ for(var/v in generated_actions)
+ var/datum/action/vote/V = v
+ if(!QDELETED(V))
+ V.remove_from_client()
+ V.Remove(V.owner)
+ generated_actions = list()
-/datum/controller/subsystem/vote/Topic(href,href_list[],hsrc)
- if(!usr || !usr.client)
- return //not necessary but meh...just in-case somebody does something stupid
+/mob/verb/vote()
+ set category = "OOC"
+ set name = "Vote"
+ SSvote.ui_interact(usr)
+
+/datum/controller/subsystem/vote/ui_state()
+ return GLOB.always_state
+
+/datum/controller/subsystem/vote/ui_interact(mob/user, datum/tgui/ui)
+ // Tracks who is voting
+ if(!(user.client?.ckey in voting))
+ voting += user.client?.ckey
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Vote")
+ ui.open()
+
+/datum/controller/subsystem/vote/ui_data(mob/user)
+ var/list/data = list(
+ "mode" = mode,
+ "voted" = voted,
+ "voting" = voting,
+ "choices" = list(),
+ "question" = question,
+ "initiator" = initiator,
+ "started_time" = started_time,
+ "time_remaining" = time_remaining,
+ "lower_admin" = !!user.client?.holder,
+ "generated_actions" = generated_actions,
+ "avm" = CONFIG_GET(flag/allow_vote_mode),
+ "avmap" = CONFIG_GET(flag/allow_vote_map),
+ "avr" = CONFIG_GET(flag/allow_vote_restart),
+ "selectedChoice" = choice_by_ckey[user.client?.ckey],
+ "upper_admin" = check_rights_for(user.client, R_ADMIN),
+ )
+
+ for(var/key in choices)
+ data["choices"] += list(list(
+ "name" = key,
+ "votes" = choices[key] || 0
+ ))
+
+ return data
+
+/datum/controller/subsystem/vote/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
- var/trialmin = 0
+ var/upper_admin = 0
if(usr.client.holder)
if(check_rights_for(usr.client, R_ADMIN))
- trialmin = 1
+ upper_admin = 1
- switch(href_list["vote"])
- if("close")
- voting -= usr.client
- usr << browse(null, "window=vote")
- return
+ switch(action)
if("cancel")
if(usr.client.holder)
reset()
if("toggle_restart")
- if(usr.client.holder && trialmin)
+ if(usr.client.holder && upper_admin)
CONFIG_SET(flag/allow_vote_restart, !CONFIG_GET(flag/allow_vote_restart))
if("toggle_gamemode")
- if(usr.client.holder && trialmin)
+ if(usr.client.holder && upper_admin)
CONFIG_SET(flag/allow_vote_mode, !CONFIG_GET(flag/allow_vote_mode))
if("toggle_map")
- if(usr.client.holder && trialmin)
+ if(usr.client.holder && upper_admin)
CONFIG_SET(flag/allow_vote_map, !CONFIG_GET(flag/allow_vote_map))
if("restart")
if(CONFIG_GET(flag/allow_vote_restart) || usr.client.holder)
@@ -356,26 +339,12 @@ SUBSYSTEM_DEF(vote)
if("custom")
if(usr.client.holder)
initiate_vote("custom",usr.key)
- else
- submit_vote(round(text2num(href_list["vote"])))
- usr.vote()
-
-/datum/controller/subsystem/vote/proc/remove_action_buttons()
- for(var/v in generated_actions)
- var/datum/action/vote/V = v
- if(!QDELETED(V))
- V.remove_from_client()
- V.Remove(V.owner)
- generated_actions = list()
-
-/mob/verb/vote()
- set category = "OOC"
- set name = "Vote"
+ if("vote")
+ submit_vote(round(text2num(params["index"])))
+ return TRUE
- var/datum/browser/popup = new(src, "vote", "Voting Panel")
- popup.set_window_options("can_close=0")
- popup.set_content(SSvote.interface(client))
- popup.open(FALSE)
+/datum/controller/subsystem/vote/ui_close(mob/user, datum/tgui/tgui)
+ voting -= user.client?.ckey
/datum/action/vote
name = "Vote!"
diff --git a/code/controllers/subsystem/weather.dm b/code/controllers/subsystem/weather.dm
index 21df62172ee3c..4b036038663c5 100644
--- a/code/controllers/subsystem/weather.dm
+++ b/code/controllers/subsystem/weather.dm
@@ -34,6 +34,13 @@ SUBSYSTEM_DEF(weather)
addtimer(CALLBACK(src, .proc/make_eligible, z, possible_weather), randTime + initial(W.weather_duration_upper), TIMER_UNIQUE) //Around 5-10 minutes between weathers
next_hit_by_zlevel["[z]"] = world.time + randTime + initial(W.telegraph_duration)
+
+/datum/controller/subsystem/weather/get_metrics()
+ . = ..()
+ var/list/cust = list()
+ cust["processing"] = length(processing)
+ .["custom"] = cust
+
/datum/controller/subsystem/weather/Initialize(start_timeofday)
for(var/V in subtypesof(/datum/weather))
var/datum/weather/W = V
diff --git a/code/controllers/subsystem/zclear.dm b/code/controllers/subsystem/zclear.dm
new file mode 100644
index 0000000000000..311a178467f65
--- /dev/null
+++ b/code/controllers/subsystem/zclear.dm
@@ -0,0 +1,327 @@
+#define CLEAR_TURF_PROCESSING_TIME (120 SECONDS) //Time it takes to clear all turfs
+#define CHECK_ZLEVEL_TICKS (5 SECONDS) //Every 5 seconds check if a tracked z-level is free.
+
+GLOBAL_LIST_EMPTY(zclear_atoms)
+GLOBAL_LIST_EMPTY(zclear_blockers)
+
+SUBSYSTEM_DEF(zclear)
+ name = "Z-Clear"
+ wait = 1
+
+ flags = SS_NO_INIT
+ runlevels = RUNLEVEL_GAME
+
+ //List of z-levels to be auto-wiped when they are left
+ //Assoc list
+ var/list/datum/space_level/autowipe
+
+ //List of free, empty z-levels.
+ var/list/datum/space_level/free_levels
+
+ //List of processing Z-Levels
+ var/list/datum/zclear_data/processing_levels
+
+ //List of atoms to ignore
+ var/list/ignored_atoms = null
+
+ //List of nullspaced mobs to replace in ruins
+ var/list/nullspaced_mobs = list()
+
+ //List of z-levels being docked with
+ var/list/docking_levels = list()
+
+ //Announced zombie levels
+ var/list/announced_zombie_levels = list()
+
+/datum/controller/subsystem/zclear/New()
+ . = ..()
+ ignored_atoms = typecacheof(list(/mob/dead, /mob/camera, /mob/dview, /atom/movable/lighting_object, /obj/effect/abstract/mirage_holder))
+
+/datum/controller/subsystem/zclear/fire(resumed)
+ if(times_fired % CHECK_ZLEVEL_TICKS == 0)
+ check_for_empty_levels()
+ for(var/datum/zclear_data/cleardata as() in processing_levels)
+ continue_wipe(cleardata)
+
+/*
+ * Checks for empty z-levels and wipes them.
+*/
+/datum/controller/subsystem/zclear/proc/check_for_empty_levels()
+ var/list/active_levels = list()
+ //Levels that have living mobs
+ var/list/living_levels = list()
+ //Levels with mobs dead/alive
+ var/list/mob_levels = list()
+ //Check active mobs
+ for(var/mob/living/L as () in GLOB.mob_list)
+ if(!L)
+ continue
+ //Dead mobs get sent to new ruins
+ if(L.ckey || L.mind || L.client)
+ var/turf/T = get_turf(L)
+ mob_levels["[T.z]"] = TRUE
+ if(L.stat != DEAD)
+ active_levels["[T.z]"] = TRUE
+ living_levels["[T.z]"] = TRUE
+ //Check active nukes
+ for(var/obj/machinery/nuclearbomb/decomission/bomb in GLOB.decomission_bombs)
+ if(bomb.timing)
+ active_levels["[bomb.z]"] = TRUE
+ living_levels["[bomb.z]"] = TRUE //Dont perform mob saving actions on mobs about to be blown to smitherines.
+ //Block z-clear from these levels.
+ for(var/atom/A as() in GLOB.zclear_blockers)
+ active_levels["[A.z]"] = TRUE
+ //Check for shuttles
+ for(var/obj/docking_port/mobile/M in SSshuttle.mobile)
+ active_levels["[M.z]"] = TRUE
+ //Check shuttle destination
+ if(M.destination)
+ active_levels["[M.destination.z]"] = TRUE
+ living_levels["[M.destination.z]"] = TRUE
+ //Check for shuttles docking
+ for(var/port_id in SSorbits.assoc_shuttles)
+ var/datum/orbital_object/shuttle/shuttle = SSorbits.assoc_shuttles[port_id]
+ if(shuttle.docking_target)
+ for(var/datum/space_level/level in shuttle.docking_target.linked_z_level)
+ active_levels["[level.z_value]"] = TRUE
+ living_levels["[level.z_value]"] = TRUE
+ //Check for shuttles coming in
+ for(var/docking_level in docking_levels)
+ active_levels["[docking_level]"] = TRUE
+ living_levels["[docking_level]"] = TRUE
+
+ for(var/datum/space_level/level as() in autowipe)
+ //Check if free
+ if(active_levels["[level.z_value]"])
+ if(!living_levels["[level.z_value]"] && mob_levels["[level.z_value]"] && !announced_zombie_levels["[level.z_value]"])
+ //Zombie level detected.
+ announced_zombie_levels["[level.z_value]"] = TRUE
+ if(level.orbital_body)
+ priority_announce("Nanotrasen long ranged sensors have indicated that all sentient life forms at priority waypoint [level.orbital_body.name] have ceased life functions. Command is recommended to establish a rescue operation to recover the bodies. Due to the nature of the threat at this location, security personnel armed with lethal weaponry is recommended to accompany the rescue team.", "Nanotrasen Long Range Sensors")
+ continue
+ //Level is free, do the wiping thing.
+ LAZYREMOVE(autowipe, level)
+ //Reset orbital body.
+ QDEL_NULL(level.orbital_body)
+ //Continue tracking after
+ wipe_z_level(level.z_value, TRUE)
+
+//Temporarily stops a z from being wiped for 30 seconds.
+/datum/controller/subsystem/zclear/proc/temp_keep_z(z_level)
+ docking_levels |= z_level
+ addtimer(CALLBACK(src, .proc/unkeep_z, z_level), 2 MINUTES)
+
+/datum/controller/subsystem/zclear/proc/unkeep_z(z_level)
+ docking_levels -= z_level
+
+/*
+ * Returns a free space level.
+ * After a 60 second grace period of allocation, the z-level will be put back into the pool of z-levels to clear.
+ * Will create a new z-level if none are available.
+*/
+/datum/controller/subsystem/zclear/proc/get_free_z_level()
+ while(LAZYLEN(free_levels))
+ var/datum/space_level/picked_level = pick(free_levels)
+ LAZYREMOVE(free_levels, picked_level)
+ //In 1 minute we will begine tracking when all mobs have left the z-level.
+ //Begin tracking. In the rare case that someone got into a free z-level then just allow them to float there with no ruins. Space is pretty empty you know.
+ addtimer(CALLBACK(src, .proc/begin_tracking, picked_level), 60 SECONDS)
+ //Check if the z-level is actually free. (Someone might have drifted into the z-level.)
+ var/free = TRUE
+ for(var/mob/living/L in GLOB.player_list)
+ var/turf/T = get_turf(L)
+ if(T.z == picked_level.z_value)
+ free = FALSE
+ break
+ if(free)
+ return picked_level
+ //Create a new z-level
+ var/datum/space_level/picked_level = SSmapping.add_new_zlevel("Dynamic free level [LAZYLEN(free_levels)]", ZTRAITS_SPACE, orbital_body_type = null)
+ addtimer(CALLBACK(src, .proc/begin_tracking, picked_level), 60 SECONDS)
+ message_admins("SSORBITS: Created a new dynamic free level ([LAZYLEN(free_levels)] now created) as none were available at the time.")
+ return picked_level
+
+/datum/controller/subsystem/zclear/proc/begin_tracking(datum/space_level/sl)
+ LAZYOR(autowipe, sl)
+
+
+/*
+ * Adds a z-level to the queue to be deleted.
+ * If tracking is TRUE, then we will re-wipe the z-level when mobs leave it again.
+*/
+/datum/controller/subsystem/zclear/proc/wipe_z_level(z_level, tracking = FALSE, datum/callback/completion_callback)
+ if(!z_level)
+ return
+
+ SSair.pause_z(z_level)
+
+ var/list/turfs = block(locate(1, 1, z_level), locate(world.maxx, world.maxy, z_level))
+ var/list/divided_turfs = list()
+ var/section_process_time = CLEAR_TURF_PROCESSING_TIME * 0.5 //There are 3 processes, cleaing atoms, cleaing turfs and then reseting atmos
+
+ //Divide the turfs into groups
+ var/group_size = CEILING(turfs.len / section_process_time, 1)
+ var/list/current_group = list()
+ for(var/i in 1 to turfs.len)
+ var/turf/T = turfs[i]
+ current_group += T
+ if(i % group_size == 0)
+ divided_turfs += list(current_group)
+ current_group = list()
+ divided_turfs += list(current_group)
+
+ //Create the wipe data datum
+ var/datum/zclear_data/data = new()
+ data.zvalue = z_level
+ data.divided_turfs = divided_turfs
+ data.process_num = 0
+ data.completion_callback = completion_callback
+ data.tracking = tracking
+
+ //Add the thing to the wiping levels list.
+ LAZYADD(processing_levels, data)
+
+ //Pre-clear anything that needs to be cleared first (Air alarms.)
+ for(var/atom/A in GLOB.zclear_atoms)
+ if(A.z == z_level)
+ qdel(A, TRUE)
+
+ //Unannounce zombie level
+ announced_zombie_levels["[z_level]"] = FALSE
+
+/*
+ * Continues the process of wiping a z-level.
+*/
+/datum/controller/subsystem/zclear/proc/continue_wipe(datum/zclear_data/cleardata)
+ var/list_element = (cleardata.process_num % (CLEAR_TURF_PROCESSING_TIME * 0.5)) + 1
+ switch(cleardata.process_num)
+ if(0 to (CLEAR_TURF_PROCESSING_TIME*0.5)-1)
+ if(list_element <= length(cleardata.divided_turfs))
+ reset_turfs(cleardata.divided_turfs[list_element])
+ if((CLEAR_TURF_PROCESSING_TIME*0.5) to (CLEAR_TURF_PROCESSING_TIME-1))
+ if(list_element <= length(cleardata.divided_turfs))
+ clear_turf_atoms(cleardata.divided_turfs[list_element])
+ else
+ //Done
+ LAZYREMOVE(processing_levels, cleardata)
+ //Finalize area
+ SSair.unpause_z(cleardata.zvalue)
+ var/area/spaceA = GLOB.areas_by_type[/area/space]
+ spaceA.reg_in_areas_in_z() //<< Potentially slow proc
+ if(cleardata.completion_callback)
+ cleardata.completion_callback.Invoke(cleardata.zvalue)
+ if(cleardata.tracking)
+ LAZYADD(free_levels, SSmapping.z_list[cleardata.zvalue])
+ if(length(nullspaced_mobs))
+ var/nullspaced_mob_names = ""
+ var/valid = FALSE
+ for(var/mob/M as() in nullspaced_mobs)
+ if(M.key || M.get_ghost(FALSE, TRUE))
+ nullspaced_mob_names += " - [M.name]\n"
+ valid = TRUE
+ if(valid)
+ priority_announce("Sensors indicate that multiple crewmembers have been lost at an abandoned station. They can potentially be recovered by flying to the nearest derelict station and locating their bodies.\n[nullspaced_mob_names]")
+ cleardata.process_num ++
+
+/*
+ * Deletes all the atoms within a given turf.
+*/
+/datum/controller/subsystem/zclear/proc/clear_turf_atoms(list/turfs)
+ //Clear atoms
+ for(var/turf/T as() in turfs)
+ // Remove all atoms except abstract mobs
+ var/list/allowed_contents = T.GetAllContentsIgnoring(ignored_atoms)
+ allowed_contents -= T
+ for(var/i in 1 to allowed_contents.len)
+ var/thing = allowed_contents[i]
+ //Remove powernet to prevent massive amounts of propagate networks, everythings getting deleted so who cares.
+ if(istype(thing, /obj/structure/cable))
+ var/obj/structure/cable/cable = thing
+ cable.powernet = null
+ if(ismob(thing))
+ if(!isliving(thing))
+ continue
+ var/mob/living/M = thing
+ if(M.mind || M.key)
+ if(M.stat == DEAD)
+ //Store them for later
+ M.ghostize(TRUE)
+ M.forceMove(null)
+ nullspaced_mobs += M
+ else
+ //If the mob has a key (but is DC) then teleport them to a safe z-level where they can potentially be retrieved.
+ //Since the wiping takes 90 seconds they could potentially still be on the z-level as it is wiping if they reconnect in time
+ random_teleport_atom(M)
+ M.Knockdown(5)
+ to_chat(M, "You feel sick as your body lurches through space and time, the ripples of the starship that brought you here eminate no more and you get the horrible feeling that you have been left behind.")
+ else
+ delete_atom(thing)
+ else
+ delete_atom(thing)
+
+/*
+ * DELETES AN ATOM OR TELEPORTS IT TO A RANDOM LOCATION IF IT IS INDESTRUCTIBLE
+*/
+/datum/controller/subsystem/zclear/proc/delete_atom(atom/A)
+ //Dont delete indestructible items, but indestructible structures can go
+ if(isitem(A))
+ var/obj/O = A
+ //Handled by the mob
+ if(ismob(O.loc))
+ return
+ if(O.resistance_flags & INDESTRUCTIBLE)
+ random_teleport_atom(A)
+ return
+ //Force delete effects and docking ports, normal delete everything else.
+ //Probably gunna cause problems in testing.
+ qdel(A, force = (iseffect(A) || istype(A, /obj/docking_port)))
+
+/*
+ * Randomly teleports an atom to a random z-level
+ * Copy and paste of turf/open/space/transit, could probably be a global proc
+*/
+/datum/controller/subsystem/zclear/proc/random_teleport_atom(atom/movable/AM)
+ set waitfor = FALSE
+ if(!AM || istype(AM, /obj/docking_port))
+ return
+ if(AM.loc != get_turf(AM)) // Multi-tile objects are "in" multiple locs but its loc is it's true placement.
+ return // Don't move multi tile objects if their origin isnt in transit
+ var/max = world.maxx-TRANSITIONEDGE
+ var/min = 1+TRANSITIONEDGE
+
+ var/list/possible_transtitons = list()
+ for(var/datum/space_level/D as() in SSmapping.z_list)
+ if (D.linkage == CROSSLINKED)
+ possible_transtitons += D.z_value
+ var/_z = pick(possible_transtitons)
+
+ //now select coordinates for a border turf
+ var/_x = rand(min,max)
+ var/_y = rand(min,max)
+
+ var/turf/T = locate(_x, _y, _z)
+ AM.forceMove(T)
+
+/datum/controller/subsystem/zclear/proc/reset_turfs(list/turfs)
+ var/list/new_turfs = list()
+ for(var/turf/T as() in turfs)
+ var/turf/newT
+ if(istype(T, /turf/open/space))
+ newT = T
+ else
+ newT = T.ChangeTurf(/turf/open/space, flags = CHANGETURF_IGNORE_AIR | CHANGETURF_DEFER_CHANGE)
+ if(!istype(newT.loc, /area/space))
+ var/area/newA = GLOB.areas_by_type[/area/space]
+ newA.contents += newT
+ newT.change_area(newT.loc, newA)
+ newT.flags_1 &= ~NO_RUINS_1
+ new_turfs += newT
+ return new_turfs
+
+/datum/zclear_data
+ var/zvalue
+ var/list/divided_turfs
+ var/process_num
+ var/tracking
+ //Callback when completed, z value passed as parameters
+ var/datum/callback/completion_callback
diff --git a/code/datums/action.dm b/code/datums/action.dm
index d7345703a7da4..020b41302ead7 100644
--- a/code/datums/action.dm
+++ b/code/datums/action.dm
@@ -9,7 +9,7 @@
var/obj/target = null
var/check_flags = NONE
var/processing = FALSE
- var/obj/screen/movable/action_button/button = null
+ var/atom/movable/screen/movable/action_button/button = null
var/buttontooltipstyle = ""
var/transparent_when_unavailable = TRUE
@@ -38,8 +38,7 @@
if(owner)
Remove(owner)
target = null
- qdel(button)
- button = null
+ QDEL_NULL(button)
return ..()
/datum/action/proc/Grant(mob/M)
@@ -49,6 +48,7 @@
return
Remove(owner)
owner = M
+ RegisterSignal(owner, COMSIG_PARENT_QDELETING, .proc/owner_deleted)
//button id generation
var/counter = 0
@@ -70,17 +70,27 @@
M.client.screen += button
button.locked = M.client.prefs.buttons_locked || button.id ? M.client.prefs.action_buttons_screen_locs["[name]_[button.id]"] : FALSE //even if it's not defaultly locked we should remember we locked it before
button.moved = button.id ? M.client.prefs.action_buttons_screen_locs["[name]_[button.id]"] : FALSE
+ var/obj/effect/proc_holder/spell/spell_proc_holder = button.linked_action.target
+ if(istype(spell_proc_holder) && spell_proc_holder.text_overlay)
+ M.client.images += spell_proc_holder.text_overlay
M.update_action_buttons()
else
Remove(owner)
+/datum/action/proc/owner_deleted(datum/source)
+ SIGNAL_HANDLER
+
+ Remove(owner)
+
/datum/action/proc/Remove(mob/M)
if(M)
if(M.client)
M.client.screen -= button
M.actions -= src
M.update_action_buttons()
- owner = null
+ if(owner)
+ UnregisterSignal(owner, COMSIG_PARENT_QDELETING)
+ owner = null
button.moved = FALSE //so the button appears in its normal position when given to another owner.
button.locked = FALSE
button.id = null
@@ -92,9 +102,6 @@
return FALSE
return TRUE
-/datum/action/proc/Process()
- return
-
/datum/action/proc/IsAvailable()
if(!owner)
return FALSE
@@ -141,7 +148,7 @@
button.color = rgb(255,255,255,255)
return 1
-/datum/action/proc/ApplyIcon(obj/screen/movable/action_button/current_button, force = FALSE)
+/datum/action/proc/ApplyIcon(atom/movable/screen/movable/action_button/current_button, force = FALSE)
if(icon_icon && button_icon_state && ((current_button.button_icon_state != button_icon_state) || force))
current_button.cut_overlays(TRUE)
current_button.add_overlay(mutable_appearance(icon_icon, button_icon_state))
@@ -175,7 +182,7 @@
I.ui_action_click(owner, src)
return 1
-/datum/action/item_action/ApplyIcon(obj/screen/movable/action_button/current_button, force)
+/datum/action/item_action/ApplyIcon(atom/movable/screen/movable/action_button/current_button, force)
if(button_icon && button_icon_state)
// If set, use the custom icon that we set instead
// of the item appearence
@@ -304,16 +311,6 @@
name = "Toggle Friendly Fire \[ON\]"
..()
-/datum/action/item_action/synthswitch
- name = "Change Synthesizer Instrument"
- desc = "Change the type of instrument your synthesizer is playing as."
-
-/datum/action/item_action/synthswitch/Trigger()
- if(istype(target, /obj/item/instrument/piano_synth))
- var/obj/item/instrument/piano_synth/synth = target
- return synth.selectInstrument()
- return ..()
-
/datum/action/item_action/vortex_recall
name = "Vortex Recall"
desc = "Recall yourself, and anyone nearby, to an attuned hierophant beacon at any time. If the beacon is still attached, will detach it."
@@ -343,6 +340,21 @@
/datum/action/item_action/toggle_helmet_mode
name = "Toggle Helmet Mode"
+/datum/action/item_action/toggle_beacon
+ name = "Toggle Hardsuit Locator Beacon"
+ icon_icon = 'icons/mob/actions.dmi'
+ button_icon_state = "toggle-transmission"
+
+/datum/action/item_action/toggle_beacon_hud
+ name = "Toggle Hardsuit Locator HUD"
+ icon_icon = 'icons/mob/actions.dmi'
+ button_icon_state = "toggle-hud"
+
+/datum/action/item_action/toggle_beacon_frequency
+ name = "Toggle Hardsuit Locator Frequency"
+ icon_icon = 'icons/mob/actions.dmi'
+ button_icon_state = "change-code"
+
/datum/action/item_action/crew_monitor
name = "Interface With Crew Monitor"
@@ -452,6 +464,10 @@
return
return ..()
+/datum/action/item_action/activate_remote_view
+ name = "Activate Remote View"
+ desc = "Activates the Remote View of your spy sunglasses."
+
/datum/action/item_action/organ_action
check_flags = AB_CHECK_CONSCIOUS
@@ -493,7 +509,7 @@
H.attack_self(owner)
return
var/obj/item/I = target
- if(owner.can_equip(I, SLOT_HANDS))
+ if(owner.can_equip(I, ITEM_SLOT_HANDS))
owner.temporarilyRemoveItemFromInventory(I)
owner.put_in_hands(I)
I.attack_self(owner)
@@ -511,10 +527,9 @@
background_icon_state = "bg_agent"
icon_icon = 'icons/mob/actions/actions_items.dmi'
button_icon_state = "deploy_box"
- ///Cooldown between deploys. Uses world.time
- var/cooldown = 0
///The type of closet this action spawns.
var/boxtype = /obj/structure/closet/cardboard/agent
+ COOLDOWN_DECLARE(box_cooldown)
///Handles opening and closing the box.
/datum/action/item_action/agent_box/Trigger()
@@ -530,11 +545,12 @@
if(!isturf(owner.loc)) //Don't let the player use this to escape mechs/welded closets.
to_chat(owner, "You need more space to activate this implant.")
return
- if(cooldown < world.time - 100)
- var/box = new boxtype(owner.drop_location())
- owner.forceMove(box)
- cooldown = world.time
- owner.playsound_local(box, 'sound/misc/box_deploy.ogg', 50, TRUE)
+ if(!COOLDOWN_FINISHED(src, box_cooldown))
+ return
+ COOLDOWN_START(src, box_cooldown, 10 SECONDS)
+ var/box = new boxtype(owner.drop_location())
+ owner.forceMove(box)
+ owner.playsound_local(box, 'sound/misc/box_deploy.ogg', 50, TRUE)
//Preset for spells
/datum/action/spell_action
@@ -633,21 +649,21 @@
/datum/action/cooldown/proc/StartCooldown()
next_use_time = world.time + cooldown_time
- button.maptext = "[round(cooldown_time/10, 0.1)]"
+ button.maptext = MAPTEXT("[round(cooldown_time/10, 0.1)]")
UpdateButtonIcon()
START_PROCESSING(SSfastprocess, src)
/datum/action/cooldown/process()
if(!owner)
button.maptext = ""
- STOP_PROCESSING(SSfastprocess, src)
+ return PROCESS_KILL
var/timeleft = max(next_use_time - world.time, 0)
if(timeleft == 0)
button.maptext = ""
UpdateButtonIcon()
- STOP_PROCESSING(SSfastprocess, src)
+ return PROCESS_KILL
else
- button.maptext = "[round(timeleft/10, 0.1)]"
+ button.maptext = MAPTEXT("[round(timeleft/10, 0.1)]")
/datum/action/cooldown/Grant(mob/M)
..()
@@ -671,12 +687,6 @@
icon_icon = 'icons/mob/actions/actions_items.dmi'
button_icon_state = "jetboot"
-/datum/action/item_action/bhop/apid
- name = "Apid Dash"
- desc = "Uses your wings to dash forward 6 tiles."
- icon_icon = 'icons/mob/neck.dmi'
- button_icon_state = "apid_wings"
-
/datum/action/language_menu
name = "Language Menu"
desc = "Open the language menu to review your languages, their keys, and select your default language."
@@ -759,7 +769,7 @@
icon_icon = 'icons/mob/actions/actions_items.dmi'
button_icon_state = "storage_gather_switch"
-/datum/action/item_action/storage_gather_mode/ApplyIcon(obj/screen/movable/action_button/current_button)
+/datum/action/item_action/storage_gather_mode/ApplyIcon(atom/movable/screen/movable/action_button/current_button)
. = ..()
var/old_layer = target.layer
var/old_plane = target.plane
diff --git a/code/datums/ai/README.md b/code/datums/ai/README.md
new file mode 100644
index 0000000000000..f219b11bb2471
--- /dev/null
+++ b/code/datums/ai/README.md
@@ -0,0 +1,21 @@
+# AI controllers
+
+## Introduction
+
+Our AI controller system is an attempt at making it possible to create modularized AI that stores its behavior in datums, while keeping state and decision making in a controller. This allows a more versatile way of creating AI that doesn't rely on OOP as much, and doesn't clutter up the Life() code in Mobs.
+
+## AI Controllers
+
+A datum that can be added to any atom in the game. Similarly to components, they might only support a given subtype (e.g. /mob/living), but the idea is that theoretically, you could apply a specific AI controller to a big a group of different types as possible and it would still work.
+
+These datums handle both the normal movement of mobs, but also their decision making, deciding which actions they will take based on the checks you put into their SelectBehaviors proc.
+
+If behaviors are selected, and the AI is in range, it will try to perform them. It runs all the behaviors it currently has in parallel; allowing for it to for example screech at someone while trying to attack them. Aslong as it has behaviors running, it will not try to generate new plans, making it not waste CPU when it already has an active goal.
+
+They also hold data for any of the actions they might need to use, such as cooldowns, whether or not they're currently fighting, etcetera this is stored in the blackboard, more information on that below.
+
+### Blackboard
+The blackboard is an associated list keyed with strings and with values of whatever you want. These store information the mob has such as "Am I attacking someone", "Do I have a weapon". By using an associated list like this, no data needs to be stored on the actions themselves, and you could make actions that work on multiple ai controllers if you so pleased by making the key to use a variable.
+
+## AI Behavior
+AI behaviors are the actions an AI can take. These can range from "Do an emote" to "Attack this target until he is dead". They are singletons and should contain nothing but static data. Any dynamic data should be stored in the blackboard, to allow different controllers to use the same behaviors.
diff --git a/code/datums/ai/_ai_behavior.dm b/code/datums/ai/_ai_behavior.dm
new file mode 100644
index 0000000000000..ebec87b18c40d
--- /dev/null
+++ b/code/datums/ai/_ai_behavior.dm
@@ -0,0 +1,26 @@
+///Abstract class for an action an AI can take, can range from movement to grabbing a nearby weapon.
+/datum/ai_behavior
+ ///What distance you need to be from the target to perform the action
+ var/required_distance = 1
+ ///Flags for extra behavior
+ var/behavior_flags = NONE
+ ///Cooldown between actions performances, defaults to the value of CLICK_CD_MELEE because that seemed like a nice standard for the speed of AI behavior
+ var/action_cooldown = CLICK_CD_MELEE
+
+/// Called by the ai controller when first being added. Additional arguments depend on the behavior type.
+/// Return FALSE to cancel
+/datum/ai_behavior/proc/setup(datum/ai_controller/controller, ...)
+ return TRUE
+
+///Called by the AI controller when this action is performed
+/datum/ai_behavior/proc/perform(delta_time, datum/ai_controller/controller, ...)
+ controller.behavior_cooldowns[src] = world.time + action_cooldown
+ return
+
+///Called when the action is finished. This needs the same args as perform besides the default ones
+/datum/ai_behavior/proc/finish_action(datum/ai_controller/controller, succeeded, ...)
+ LAZYREMOVE(controller.current_behaviors, src)
+ controller.behavior_args -= type
+ if(behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT) //If this was a movement task, reset our movement target.
+ controller.current_movement_target = null
+ controller.ai_movement.stop_moving_towards(controller)
diff --git a/code/datums/ai/_ai_controller.dm b/code/datums/ai/_ai_controller.dm
new file mode 100644
index 0000000000000..a53e0828cfe9f
--- /dev/null
+++ b/code/datums/ai/_ai_controller.dm
@@ -0,0 +1,261 @@
+/*
+AI controllers are a datumized form of AI that simulates the input a player would otherwise give to a atom. What this means is that these datums
+have ways of interacting with a specific atom and control it. They posses a blackboard with the information the AI knows and has, and will plan behaviors it will try to execute through
+multiple modular subtrees with behaviors
+*/
+
+/datum/ai_controller
+ ///The atom this controller is controlling
+ var/atom/pawn
+ ///Bitfield of traits for this AI to handle extra behavior
+ var/ai_traits
+ ///Current actions being performed by the AI.
+ var/list/current_behaviors
+ ///Current actions and their respective last time ran as an assoc list.
+ var/list/behavior_cooldowns = list()
+ ///Current status of AI (OFF/ON)
+ var/ai_status
+ ///Current movement target of the AI, generally set by decision making.
+ var/atom/current_movement_target
+ ///This is a list of variables the AI uses and can be mutated by actions. When an action is performed you pass this list and any relevant keys for the variables it can mutate.
+ var/list/blackboard = list()
+ ///Stored arguments for behaviors given during their initial creation
+ var/list/behavior_args = list()
+ ///Tracks recent pathing attempts, if we fail too many in a row we fail our current plans.
+ var/pathing_attempts
+ ///Can the AI remain in control if there is a client?
+ var/continue_processing_when_client = FALSE
+ ///distance to give up on target
+ var/max_target_distance = 14
+ ///Cooldown for new plans, to prevent AI from going nuts if it can't think of new plans and looping on end
+ COOLDOWN_DECLARE(failed_planning_cooldown)
+ ///All subtrees this AI has available, will run them in order, so make sure they're in the order you want them to run. On initialization of this type, it will start as a typepath(s) and get converted to references of ai_subtrees found in SSai_controllers when init_subtrees() is called
+ var/list/planning_subtrees
+
+ // Movement related things here
+ ///Reference to the movement datum we use. Is a type on initialize but becomes a ref afterwards.
+ var/datum/ai_movement/ai_movement = /datum/ai_movement/dumb
+ ///Cooldown until next movement
+ COOLDOWN_DECLARE(movement_cooldown)
+ ///Delay between movements. This is on the controller so we can keep the movement datum singleton
+ var/movement_delay = 0.1 SECONDS
+
+ // The variables below are fucking stupid and should be put into the blackboard at some point.
+ ///A list for the path we're currently following, if we're using JPS pathing
+ var/list/movement_path
+ ///Cooldown for JPS movement, how often we're allowed to try making a new path
+ COOLDOWN_DECLARE(repath_cooldown)
+ ///AI paused time
+ var/paused_until = 0
+
+/datum/ai_controller/New(atom/new_pawn)
+ change_ai_movement_type(ai_movement)
+ init_subtrees()
+
+ PossessPawn(new_pawn)
+
+/datum/ai_controller/Destroy(force, ...)
+ set_ai_status(AI_STATUS_OFF)
+ UnpossessPawn(FALSE)
+ return ..()
+
+///Overrides the current ai_movement of this controller with a new one
+/datum/ai_controller/proc/change_ai_movement_type(datum/ai_movement/new_movement)
+ ai_movement = SSai_movement.movement_types[new_movement]
+
+///Completely replaces the planning_subtrees with a new set based on argument provided, list provided must contain specifically typepaths
+/datum/ai_controller/proc/replace_planning_subtrees(list/typepaths_of_new_subtrees)
+ planning_subtrees = typepaths_of_new_subtrees
+ init_subtrees()
+
+///Loops over the subtrees in planning_subtrees and looks at the ai_controllers to grab a reference, ENSURE planning_subtrees ARE TYPEPATHS AND NOT INSTANCES/REFERENCES BEFORE EXECUTING THIS
+/datum/ai_controller/proc/init_subtrees()
+ if(!LAZYLEN(planning_subtrees))
+ return
+ var/list/temp_subtree_list = list()
+ for(var/subtree in planning_subtrees)
+ var/subtree_instance = SSai_controllers.ai_subtrees[subtree]
+ temp_subtree_list += subtree_instance
+ planning_subtrees = temp_subtree_list
+
+///Proc to move from one pawn to another, this will destroy the target's existing controller.
+/datum/ai_controller/proc/PossessPawn(atom/new_pawn)
+ if(pawn) //Reset any old signals
+ UnpossessPawn(FALSE)
+
+ if(istype(new_pawn.ai_controller)) //Existing AI, kill it.
+ QDEL_NULL(new_pawn.ai_controller)
+
+ if(TryPossessPawn(new_pawn) & AI_CONTROLLER_INCOMPATIBLE)
+ qdel(src)
+ CRASH("[src] attached to [new_pawn] but these are not compatible!")
+
+ pawn = new_pawn
+ pawn.ai_controller = src
+
+ if(!continue_processing_when_client && istype(new_pawn, /mob))
+ var/mob/possible_client_holder = new_pawn
+ if(possible_client_holder.client)
+ set_ai_status(AI_STATUS_OFF)
+ else
+ set_ai_status(AI_STATUS_ON)
+ else
+ set_ai_status(AI_STATUS_ON)
+
+ RegisterSignal(pawn, COMSIG_MOB_LOGIN, .proc/on_sentience_gained)
+
+///Abstract proc for initializing the pawn to the new controller
+/datum/ai_controller/proc/TryPossessPawn(atom/new_pawn)
+ return
+
+///Proc for deinitializing the pawn to the old controller
+/datum/ai_controller/proc/UnpossessPawn(destroy)
+ UnregisterSignal(pawn, list(COMSIG_MOB_LOGIN, COMSIG_MOB_LOGOUT))
+ pawn.ai_controller = null
+ pawn = null
+ if(destroy)
+ qdel(src)
+ return
+
+///Returns TRUE if the ai controller can actually run at the moment.
+/datum/ai_controller/proc/able_to_run()
+ if(world.time < paused_until)
+ return FALSE
+ return TRUE
+
+
+///Runs any actions that are currently running
+/datum/ai_controller/process(delta_time)
+ if(!able_to_run())
+ walk(pawn, 0) //stop moving
+ return //this should remove them from processing in the future through event-based stuff.
+
+ if(!LAZYLEN(current_behaviors))
+ PerformIdleBehavior(delta_time) //Do some stupid shit while we have nothing to do
+ return
+
+ if(current_movement_target && get_dist(pawn, current_movement_target) > max_target_distance) //The distance is out of range
+ CancelActions()
+ return
+
+ for(var/i in current_behaviors)
+ var/datum/ai_behavior/current_behavior = i
+
+
+ // Convert the current behaviour action cooldown to realtime seconds from deciseconds.current_behavior
+ // Then pick the max of this and the delta_time passed to ai_controller.process()
+ // Action cooldowns cannot happen faster than delta_time, so delta_time should be the value used in this scenario.
+ var/action_delta_time = max(current_behavior.action_cooldown * 0.1, delta_time)
+
+ if(current_behavior.behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT) //Might need to move closer
+ if(!current_movement_target)
+ stack_trace("[pawn] wants to perform action type [current_behavior.type] which requires movement, but has no current movement target!")
+ return //This can cause issues, so don't let these slide.
+ if(current_behavior.required_distance >= get_dist(pawn, current_movement_target)) ///Are we close enough to engage?
+ if(ai_movement.moving_controllers[src] == current_movement_target) //We are close enough, if we're moving stop.
+ ai_movement.stop_moving_towards(src)
+
+ if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown
+ continue
+ ProcessBehavior(action_delta_time, current_behavior)
+ return
+
+ else if(ai_movement.moving_controllers[src] != current_movement_target) //We're too far, if we're not already moving start doing it.
+ ai_movement.start_moving_towards(src, current_movement_target, current_behavior.required_distance) //Then start moving
+
+ if(current_behavior.behavior_flags & AI_BEHAVIOR_MOVE_AND_PERFORM) //If we can move and perform then do so.
+ if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown
+ continue
+ ProcessBehavior(action_delta_time, current_behavior)
+ return
+ else //No movement required
+ if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown
+ continue
+ ProcessBehavior(action_delta_time, current_behavior)
+ return
+
+///Perform some dumb idle behavior.
+/datum/ai_controller/proc/PerformIdleBehavior(delta_time)
+ return
+
+///This is where you decide what actions are taken by the AI.
+/datum/ai_controller/proc/SelectBehaviors(delta_time)
+ SHOULD_NOT_SLEEP(TRUE) //Fuck you don't sleep in procs like this.
+ if(!COOLDOWN_FINISHED(src, failed_planning_cooldown))
+ return FALSE
+
+ LAZYINITLIST(current_behaviors)
+
+ if(LAZYLEN(planning_subtrees))
+ for(var/datum/ai_planning_subtree/subtree as anything in planning_subtrees)
+ if(subtree.SelectBehaviors(src, delta_time) == SUBTREE_RETURN_FINISH_PLANNING)
+ break
+
+///This proc handles changing ai status, and starts/stops processing if required.
+/datum/ai_controller/proc/set_ai_status(new_ai_status)
+ if(ai_status == new_ai_status)
+ return FALSE //no change
+
+ ai_status = new_ai_status
+ switch(ai_status)
+ if(AI_STATUS_ON)
+ SSai_controllers.active_ai_controllers += src
+ START_PROCESSING(SSai_behaviors, src)
+ if(AI_STATUS_OFF)
+ STOP_PROCESSING(SSai_behaviors, src)
+ SSai_controllers.active_ai_controllers -= src
+ CancelActions()
+
+/datum/ai_controller/proc/PauseAi(time)
+ paused_until = world.time + time
+
+///Call this to add a behavior to the stack.
+/datum/ai_controller/proc/queue_behavior(behavior_type, ...)
+ var/datum/ai_behavior/behavior = GET_AI_BEHAVIOR(behavior_type)
+ if(!behavior)
+ CRASH("Behavior [behavior_type] not found.")
+ var/list/arguments = args.Copy()
+ arguments[1] = src
+ if(!behavior.setup(arglist(arguments)))
+ return
+ LAZYADD(current_behaviors, behavior)
+ arguments.Cut(1, 2)
+ if(length(arguments))
+ behavior_args[behavior_type] = arguments
+ else
+ behavior_args -= behavior_type
+
+/datum/ai_controller/proc/ProcessBehavior(delta_time, datum/ai_behavior/behavior)
+ var/list/arguments = list(delta_time, src)
+ var/list/stored_arguments = behavior_args[behavior.type]
+ if(stored_arguments)
+ arguments += stored_arguments
+ behavior.perform(arglist(arguments))
+
+/datum/ai_controller/proc/CancelActions()
+ if(!LAZYLEN(current_behaviors))
+ return
+ for(var/i in current_behaviors)
+ var/datum/ai_behavior/current_behavior = i
+ var/list/arguments = list(src, FALSE)
+ var/list/stored_arguments = behavior_args[current_behavior.type]
+ if(stored_arguments)
+ arguments += stored_arguments
+ current_behavior.finish_action(arglist(arguments))
+
+/datum/ai_controller/proc/on_sentience_gained()
+ SIGNAL_HANDLER
+ UnregisterSignal(pawn, COMSIG_MOB_LOGIN)
+ if(!continue_processing_when_client)
+ set_ai_status(AI_STATUS_OFF) //Can't do anything while player is connected
+ RegisterSignal(pawn, COMSIG_MOB_LOGOUT, .proc/on_sentience_lost)
+
+/datum/ai_controller/proc/on_sentience_lost()
+ SIGNAL_HANDLER
+ UnregisterSignal(pawn, COMSIG_MOB_LOGOUT)
+ set_ai_status(AI_STATUS_ON) //Can't do anything while player is connected
+ RegisterSignal(pawn, COMSIG_MOB_LOGIN, .proc/on_sentience_gained)
+
+/// Use this proc to define how your controller defines what access the pawn has for the sake of pathfinding, likely pointing to whatever ID slot is relevant
+/datum/ai_controller/proc/get_access()
+ return
diff --git a/code/datums/ai/_ai_planning_subtree.dm b/code/datums/ai/_ai_planning_subtree.dm
new file mode 100644
index 0000000000000..ec69cd3e3e64f
--- /dev/null
+++ b/code/datums/ai/_ai_planning_subtree.dm
@@ -0,0 +1,7 @@
+///A subtree is attached to a controller and is occasionally called by /ai_controller/SelectBehaviors(), this mainly exists to act as a way to subtype and modify SelectBehaviors() without needing to subtype the ai controller itself
+/datum/ai_planning_subtree
+
+
+///Determines what behaviors should the controller try processing; if this returns SUBTREE_RETURN_FINISH_PLANNING then the controller won't go through the other subtrees should multiple exist in controller.planning_subtrees
+/datum/ai_planning_subtree/proc/SelectBehaviors(datum/ai_controller/controller, delta_time)
+ return
diff --git a/code/datums/ai/_item_behaviors.dm b/code/datums/ai/_item_behaviors.dm
new file mode 100644
index 0000000000000..9dc032ba58aaf
--- /dev/null
+++ b/code/datums/ai/_item_behaviors.dm
@@ -0,0 +1,59 @@
+///This behavior is for obj/items, it is used to free themselves out of the hands of whoever is holding them
+/datum/ai_behavior/item_escape_grasp
+
+/datum/ai_behavior/item_escape_grasp/perform(delta_time, datum/ai_controller/controller)
+ . = ..()
+ var/obj/item/item_pawn = controller.pawn
+ var/mob/item_holder = item_pawn.loc
+ if(!istype(item_holder))
+ finish_action(controller, FALSE) //We're no longer beind held. abort abort!!
+ item_pawn.visible_message("[item_pawn] slips out of the hands of [item_holder]!")
+ item_holder.dropItemToGround(item_pawn, TRUE)
+ finish_action(controller, TRUE)
+
+
+///This behavior is for obj/items, it is used to move closer to a target and throw themselves towards them.
+/datum/ai_behavior/item_move_close_and_attack
+ required_distance = 3
+ behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
+ action_cooldown = 20
+ ///Sound to use
+ var/attack_sound
+ ///Max attemps to make
+ var/max_attempts = 3
+
+
+/datum/ai_behavior/item_move_close_and_attack/setup(datum/ai_controller/controller, target_key, throw_count_key)
+ . = ..()
+ controller.current_movement_target = controller.blackboard[target_key]
+
+
+/datum/ai_behavior/item_move_close_and_attack/perform(delta_time, datum/ai_controller/controller, target_key, throw_count_key)
+ . = ..()
+ var/obj/item/item_pawn = controller.pawn
+ var/atom/throw_target = controller.blackboard[target_key]
+
+ item_pawn.visible_message("[item_pawn] hurls towards [throw_target]!")
+ item_pawn.throw_at(throw_target, rand(4,5), 9)
+ playsound(item_pawn.loc, attack_sound, 100, TRUE)
+ controller.blackboard[throw_count_key]++
+ if(controller.blackboard[throw_count_key] >= max_attempts)
+ finish_action(controller, TRUE, target_key, throw_count_key)
+
+/datum/ai_behavior/item_move_close_and_attack/finish_action(datum/ai_controller/controller, succeeded, target_key, throw_count_key)
+ . = ..()
+ reset_blackboard(controller, succeeded, target_key, throw_count_key)
+
+/datum/ai_behavior/item_move_close_and_attack/proc/reset_blackboard(datum/ai_controller/controller, succeeded, target_key, throw_count_key)
+ controller.blackboard -= target_key
+ controller.blackboard[throw_count_key] = 0
+
+/datum/ai_behavior/item_move_close_and_attack/haunted
+ attack_sound = 'sound/items/haunted/ghostitemattack.ogg'
+ max_attempts = 4
+
+/datum/ai_behavior/item_move_close_and_attack/haunted/finish_action(datum/ai_controller/controller, succeeded, target_key, throw_count_key)
+ var/atom/throw_target = controller.blackboard[target_key]
+ var/list/hauntee_list = controller.blackboard[BB_TO_HAUNT_LIST]
+ hauntee_list[throw_target]--
+ return ..()
diff --git a/code/datums/ai/dog/dog_behaviors.dm b/code/datums/ai/dog/dog_behaviors.dm
new file mode 100644
index 0000000000000..34c8531778e22
--- /dev/null
+++ b/code/datums/ai/dog/dog_behaviors.dm
@@ -0,0 +1,208 @@
+/datum/ai_behavior/battle_screech/dog
+ screeches = list("barks","howls")
+
+/// Fetching makes the pawn chase after whatever it's targeting and pick it up when it's in range, with the dog_equip behavior
+/datum/ai_behavior/fetch
+ behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
+
+/datum/ai_behavior/fetch/perform(delta_time, datum/ai_controller/controller)
+ . = ..()
+ var/mob/living/living_pawn = controller.pawn
+ var/obj/item/fetch_thing = controller.blackboard[BB_FETCH_TARGET]
+
+ //either we can't pick it up, or we'd rather eat it, so stop trying.
+ if(fetch_thing.anchored || !isturf(fetch_thing.loc) || istype(fetch_thing, /obj/item/reagent_containers/food) || !living_pawn.CanReach(fetch_thing))
+ finish_action(controller, FALSE)
+ return
+
+ finish_action(controller, TRUE)
+
+/datum/ai_behavior/fetch/finish_action(datum/ai_controller/controller, success)
+ . = ..()
+
+ if(!success) //Don't try again on this item if we failed
+ var/obj/item/target = controller.blackboard[BB_FETCH_TARGET]
+ if(target)
+ controller.blackboard[BB_FETCH_IGNORE_LIST][WEAKREF(target)] = TRUE
+ controller.blackboard[BB_FETCH_TARGET] = null
+ controller.blackboard[BB_FETCH_DELIVER_TO] = null
+
+
+/// This is simply a behaviour to pick up a fetch target
+/datum/ai_behavior/simple_equip/perform(delta_time, datum/ai_controller/controller)
+ . = ..()
+ var/obj/item/fetch_target = controller.blackboard[BB_FETCH_TARGET]
+ if(!isturf(fetch_target?.loc) || !isitem(fetch_target)) // someone picked it up, something happened to it, or it wasn't an item anyway
+ finish_action(controller, FALSE)
+ return
+
+ if(in_range(controller.pawn, fetch_target))
+ pickup_item(controller, fetch_target)
+ finish_action(controller, TRUE)
+ else
+ finish_action(controller, FALSE)
+
+/datum/ai_behavior/simple_equip/finish_action(datum/ai_controller/controller, success)
+ . = ..()
+ controller.blackboard[BB_FETCH_TARGET] = null
+
+/datum/ai_behavior/simple_equip/proc/pickup_item(datum/ai_controller/controller, obj/item/target)
+ var/atom/pawn = controller.pawn
+ drop_item(controller)
+ pawn.visible_message("[pawn] picks up [target] in [pawn.p_their()] mouth.")
+ target.forceMove(pawn)
+ controller.blackboard[BB_SIMPLE_CARRY_ITEM] = target
+ return TRUE
+
+/datum/ai_behavior/simple_equip/proc/drop_item(datum/ai_controller/controller)
+ var/obj/item/carried_item = controller.blackboard[BB_SIMPLE_CARRY_ITEM]
+ if(!carried_item)
+ return
+
+ var/atom/pawn = controller.pawn
+ pawn.visible_message("[pawn] drops [carried_item].")
+ carried_item.forceMove(get_turf(pawn))
+ controller.blackboard[BB_SIMPLE_CARRY_ITEM] = null
+ return TRUE
+
+
+
+/// This behavior involves dropping off a carried item to a specified person (or place)
+/datum/ai_behavior/deliver_item
+ behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
+
+/datum/ai_behavior/deliver_item/perform(delta_time, datum/ai_controller/controller)
+ . = ..()
+ var/mob/living/return_target = controller.blackboard[BB_FETCH_DELIVER_TO]
+ if(!return_target)
+ finish_action(controller, FALSE)
+ if(in_range(controller.pawn, return_target))
+ deliver_item(controller)
+ finish_action(controller, TRUE)
+
+/datum/ai_behavior/deliver_item/finish_action(datum/ai_controller/controller, success)
+ . = ..()
+ controller.blackboard[BB_FETCH_DELIVER_TO] = null
+
+/// Actually drop the fetched item to the target
+/datum/ai_behavior/deliver_item/proc/deliver_item(datum/ai_controller/controller)
+ var/obj/item/carried_item = controller.blackboard[BB_SIMPLE_CARRY_ITEM]
+ var/atom/movable/return_target = controller.blackboard[BB_FETCH_DELIVER_TO]
+ if(!carried_item || !return_target)
+ finish_action(controller, FALSE)
+ return
+
+ if(ismob(return_target))
+ controller.pawn.visible_message("[controller.pawn] delivers [carried_item] at [return_target]'s feet.")
+ else // not sure how to best phrase this
+ controller.pawn.visible_message("[controller.pawn] delivers [carried_item] to [return_target].")
+
+ carried_item.forceMove(get_turf(return_target))
+ controller.blackboard -= BB_SIMPLE_CARRY_ITEM
+ return TRUE
+
+/// This behavior involves either eating a snack we can reach, or begging someone holding a snack
+/datum/ai_behavior/eat_snack
+ behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
+
+/datum/ai_behavior/eat_snack/perform(delta_time, datum/ai_controller/controller)
+ . = ..()
+ var/obj/item/snack = controller.current_movement_target
+ if(!istype(snack) || !istype(snack, /obj/item/reagent_containers/food) || !(isturf(snack.loc) || ishuman(snack.loc)))
+ finish_action(controller, FALSE)
+
+ var/mob/living/living_pawn = controller.pawn
+ if(!in_range(living_pawn, snack))
+ return
+
+ if(isturf(snack.loc))
+ snack.attack_animal(living_pawn) // snack attack!
+ else if(iscarbon(snack.loc) && DT_PROB(10, delta_time))
+ living_pawn.manual_emote("stares at [snack.loc]'s [snack.name] with a sad puppy-face.")
+
+ if(QDELETED(snack)) // we ate it!
+ finish_action(controller, TRUE)
+
+
+/// This behavior involves either eating a snack we can reach, or begging someone holding a snack
+/datum/ai_behavior/play_dead
+ behavior_flags = NONE
+
+/datum/ai_behavior/play_dead/perform(delta_time, datum/ai_controller/controller)
+ . = ..()
+ var/mob/living/simple_animal/simple_pawn = controller.pawn
+ if(!istype(simple_pawn))
+ return
+
+ if(!controller.blackboard[BB_DOG_PLAYING_DEAD])
+ controller.blackboard[BB_DOG_PLAYING_DEAD] = TRUE
+ simple_pawn.emote("deathgasp", intentional=FALSE)
+ simple_pawn.icon_state = simple_pawn.icon_dead
+ if(simple_pawn.flip_on_death)
+ simple_pawn.transform = simple_pawn.transform.Turn(180)
+ simple_pawn.density = FALSE
+
+ if(DT_PROB(10, delta_time))
+ finish_action(controller, TRUE)
+
+/datum/ai_behavior/play_dead/finish_action(datum/ai_controller/controller, succeeded)
+ . = ..()
+ var/mob/living/simple_animal/simple_pawn = controller.pawn
+ if(!istype(simple_pawn) || simple_pawn.stat) // imagine actually dying while playing dead. hell, imagine being the kid waiting for your pup to get back up :(
+ return
+ controller.blackboard[BB_DOG_PLAYING_DEAD] = FALSE
+ simple_pawn.visible_message("[simple_pawn] springs to [simple_pawn.p_their()] feet, panting excitedly!")
+ simple_pawn.icon_state = simple_pawn.icon_living
+ if(simple_pawn.flip_on_death)
+ simple_pawn.transform = simple_pawn.transform.Turn(180)
+ simple_pawn.density = initial(simple_pawn.density)
+
+/// This behavior involves either eating a snack we can reach, or begging someone holding a snack
+/datum/ai_behavior/harass
+ behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM
+ required_distance = 3
+
+/datum/ai_behavior/harass/perform(delta_time, datum/ai_controller/controller)
+ . = ..()
+ var/mob/living/living_pawn = controller.pawn
+ if(!istype(living_pawn) || !(isturf(living_pawn.loc) || HAS_TRAIT(living_pawn, TRAIT_AI_BAGATTACK)))
+ return
+
+ var/datum/weakref/harass_ref = controller.blackboard[BB_DOG_HARASS_TARGET]
+ var/atom/movable/harass_target = harass_ref.resolve()
+ if(!harass_target || !can_see(living_pawn, harass_target, length=AI_DOG_VISION_RANGE))
+ finish_action(controller, FALSE)
+ return
+
+ if(controller.blackboard[BB_DOG_FRIENDS][harass_ref])
+ living_pawn.visible_message("[living_pawn] looks sideways at [harass_target] for a moment, then shakes [living_pawn.p_their()] head and ceases aggression.")
+ finish_action(controller, FALSE)
+ return
+
+ var/mob/living/living_target = harass_target
+ if(istype(living_target) && (living_target.stat || HAS_TRAIT(living_target, TRAIT_FAKEDEATH)))
+ finish_action(controller, TRUE)
+ return
+
+ // subtypes of this behavior can change behavior for how eager/averse the pawn is to attack the target as opposed to falling back/making noise/getting help
+ if(in_range(living_pawn, living_target))
+ attack(controller, living_target)
+ else if(DT_PROB(50, delta_time))
+ living_pawn.manual_emote("[pick("barks", "growls", "stares")] menacingly at [harass_target]!")
+
+/datum/ai_behavior/harass/finish_action(datum/ai_controller/controller, succeeded)
+ . = ..()
+ controller.blackboard[BB_DOG_HARASS_TARGET] = null
+
+/// A proc representing when the mob is pushed to actually attack the target. Again, subtypes can be used to represent different attacks from different animals, or it can be some other generic behavior
+/datum/ai_behavior/harass/proc/attack(datum/ai_controller/controller, mob/living/living_target)
+ var/mob/living/living_pawn = controller.pawn
+ if(!istype(living_pawn))
+ return
+ // make sure the pawn gets some temporary strength boost to actually attack the target instead of pathetically nuzzling them.
+ var/old_melee_damage = living_pawn.melee_damage
+ living_pawn.melee_damage = max(7, old_melee_damage)
+
+ living_pawn.UnarmedAttack(living_target, FALSE)
+
+ living_pawn.melee_damage = old_melee_damage
diff --git a/code/datums/ai/dog/dog_controller.dm b/code/datums/ai/dog/dog_controller.dm
new file mode 100644
index 0000000000000..0cc308682f31f
--- /dev/null
+++ b/code/datums/ai/dog/dog_controller.dm
@@ -0,0 +1,294 @@
+/datum/ai_controller/dog
+ blackboard = list(\
+ BB_SIMPLE_CARRY_ITEM = null,\
+ BB_FETCH_TARGET = null,\
+ BB_FETCH_DELIVER_TO = null,\
+ BB_DOG_FRIENDS = list(),\
+ BB_FETCH_IGNORE_LIST = list(),\
+ BB_DOG_ORDER_MODE = DOG_COMMAND_NONE,\
+ BB_DOG_PLAYING_DEAD = FALSE,\
+ BB_DOG_HARASS_TARGET = null)
+ ai_movement = /datum/ai_movement/jps
+ planning_subtrees = list(/datum/ai_planning_subtree/dog)
+
+ COOLDOWN_DECLARE(heel_cooldown)
+ COOLDOWN_DECLARE(command_cooldown)
+
+
+/datum/ai_controller/dog/process(delta_time)
+ if(ismob(pawn))
+ var/mob/living/living_pawn = pawn
+ movement_delay = living_pawn.cached_multiplicative_slowdown
+ return ..()
+
+/datum/ai_controller/dog/TryPossessPawn(atom/new_pawn)
+ if(!isliving(new_pawn))
+ return AI_CONTROLLER_INCOMPATIBLE
+
+ RegisterSignal(new_pawn, COMSIG_ATOM_ATTACK_HAND, .proc/on_attack_hand)
+ RegisterSignal(new_pawn, COMSIG_PARENT_EXAMINE, .proc/on_examined)
+ RegisterSignal(new_pawn, COMSIG_CLICK_ALT, .proc/check_altclicked)
+ RegisterSignal(new_pawn, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING), .proc/on_death)
+ RegisterSignal(SSdcs, COMSIG_GLOB_CARBON_THROW_THING, .proc/listened_throw)
+ return ..() //Run parent at end
+
+/datum/ai_controller/dog/UnpossessPawn(destroy)
+ var/obj/item/carried_item = blackboard[BB_SIMPLE_CARRY_ITEM]
+ if(carried_item)
+ pawn.visible_message("[pawn] drops [carried_item].")
+ carried_item.forceMove(pawn.drop_location())
+ blackboard[BB_SIMPLE_CARRY_ITEM] = null
+ UnregisterSignal(pawn, list(COMSIG_ATOM_ATTACK_HAND, COMSIG_PARENT_EXAMINE, COMSIG_CLICK_ALT, COMSIG_MOB_DEATH, COMSIG_GLOB_CARBON_THROW_THING, COMSIG_PARENT_QDELETING))
+ return ..() //Run parent at end
+
+/datum/ai_controller/dog/able_to_run()
+ var/mob/living/living_pawn = pawn
+
+ if(IS_DEAD_OR_INCAP(living_pawn))
+ return FALSE
+ return ..()
+
+/datum/ai_controller/dog/get_access()
+ var/mob/living/simple_animal/simple_pawn = pawn
+ if(!istype(simple_pawn))
+ return
+
+ return simple_pawn.access_card
+
+/datum/ai_controller/dog/PerformIdleBehavior(delta_time)
+ var/mob/living/living_pawn = pawn
+ if(!isturf(living_pawn.loc) || living_pawn.pulledby)
+ return
+
+ // if we were just ordered to heel, chill out for a bit
+ if(!COOLDOWN_FINISHED(src, heel_cooldown))
+ return
+
+ // if we're just ditzing around carrying something, occasionally print a message so people know we have something
+ if(blackboard[BB_SIMPLE_CARRY_ITEM] && DT_PROB(5, delta_time))
+ var/obj/item/carry_item = blackboard[BB_SIMPLE_CARRY_ITEM]
+ living_pawn.visible_message("[living_pawn] gently teethes on \the [carry_item] in [living_pawn.p_their()] mouth.", vision_distance=COMBAT_MESSAGE_RANGE)
+
+ if(DT_PROB(5, delta_time) && (living_pawn.mobility_flags & MOBILITY_MOVE))
+ var/move_dir = pick(GLOB.alldirs)
+ living_pawn.Move(get_step(living_pawn, move_dir), move_dir)
+ else if(DT_PROB(10, delta_time))
+ living_pawn.manual_emote(pick("dances around.", "chases [living_pawn.p_their()] tail!"))
+ living_pawn.AddComponent(/datum/component/spinny)
+
+/// Someone has thrown something, see if it's someone we care about and start listening to the thrown item so we can see if we want to fetch it when it lands
+/datum/ai_controller/dog/proc/listened_throw(datum/source, mob/living/carbon/carbon_thrower)
+ SIGNAL_HANDLER
+ if(blackboard[BB_FETCH_TARGET] || blackboard[BB_FETCH_DELIVER_TO] || blackboard[BB_DOG_PLAYING_DEAD]) // we're already busy
+ return
+ if(!COOLDOWN_FINISHED(src, heel_cooldown))
+ return
+ if(!can_see(pawn, carbon_thrower, length=AI_DOG_VISION_RANGE))
+ return
+ var/obj/item/thrown_thing = carbon_thrower.get_active_held_item()
+ if(!isitem(thrown_thing))
+ return
+ if(blackboard[BB_FETCH_IGNORE_LIST][WEAKREF(thrown_thing)])
+ return
+
+ RegisterSignal(thrown_thing, COMSIG_MOVABLE_THROW_LANDED, .proc/listen_throw_land)
+
+/// A throw we were listening to has finished, see if it's in range for us to try grabbing it
+/datum/ai_controller/dog/proc/listen_throw_land(obj/item/thrown_thing, datum/thrownthing/throwing_datum)
+ SIGNAL_HANDLER
+
+ UnregisterSignal(thrown_thing, list(COMSIG_PARENT_QDELETING, COMSIG_MOVABLE_THROW_LANDED))
+ if(!istype(thrown_thing) || !isturf(thrown_thing.loc) || !can_see(pawn, thrown_thing, length=AI_DOG_VISION_RANGE))
+ return
+
+ current_movement_target = thrown_thing
+ blackboard[BB_FETCH_TARGET] = thrown_thing
+ blackboard[BB_FETCH_DELIVER_TO] = throwing_datum.thrower
+ queue_behavior(/datum/ai_behavior/fetch)
+
+/// Someone's interacting with us by hand, see if they're being nice or mean
+/datum/ai_controller/dog/proc/on_attack_hand(datum/source, mob/living/user)
+ SIGNAL_HANDLER
+
+ if(user.a_intent == INTENT_HARM)
+ unfriend(user)
+ else
+ if(prob(AI_DOG_PET_FRIEND_PROB))
+ befriend(user)
+ // if the dog has something in their mouth that they're not bringing to someone for whatever reason, have them drop it when pet by a friend
+ var/list/friends = blackboard[BB_DOG_FRIENDS]
+ if(blackboard[BB_SIMPLE_CARRY_ITEM] && !current_movement_target && friends[WEAKREF(user)])
+ var/obj/item/carried_item = blackboard[BB_SIMPLE_CARRY_ITEM]
+ pawn.visible_message("[pawn] drops [carried_item] at [user]'s feet!")
+ // maybe have a dedicated proc for dropping things
+ carried_item.forceMove(get_turf(user))
+ blackboard[BB_SIMPLE_CARRY_ITEM] = null
+
+/// Someone is being nice to us, let's make them a friend!
+/datum/ai_controller/dog/proc/befriend(mob/living/new_friend)
+ var/list/friends = blackboard[BB_DOG_FRIENDS]
+ var/datum/weakref/friend_ref = WEAKREF(new_friend)
+ if(friends[friend_ref])
+ return
+ if(in_range(pawn, new_friend))
+ new_friend.visible_message("[pawn] licks at [new_friend] in a friendly manner!", "[pawn] licks at you in a friendly manner!")
+ friends[friend_ref] = TRUE
+ RegisterSignal(new_friend, COMSIG_MOB_POINTED, .proc/check_point)
+ RegisterSignal(new_friend, COMSIG_MOB_SAY, .proc/check_verbal_command)
+
+/// Someone is being mean to us, take them off our friends (add actual enemies behavior later)
+/datum/ai_controller/dog/proc/unfriend(mob/living/ex_friend)
+ var/list/friends = blackboard[BB_DOG_FRIENDS]
+ friends -= WEAKREF(ex_friend)
+ UnregisterSignal(ex_friend, list(COMSIG_MOB_POINTED, COMSIG_MOB_SAY))
+
+/// Someone is looking at us, if we're currently carrying something then show what it is, and include a message if they're our friend
+/datum/ai_controller/dog/proc/on_examined(datum/source, mob/user, list/examine_text)
+ SIGNAL_HANDLER
+
+ var/obj/item/carried_item = blackboard[BB_SIMPLE_CARRY_ITEM]
+ if(carried_item)
+ examine_text += "[pawn.p_they(TRUE)] [pawn.p_are()] carrying [carried_item.get_examine_string(user)] in [pawn.p_their()] mouth."
+ if(blackboard[BB_DOG_FRIENDS][WEAKREF(user)])
+ var/mob/living/living_pawn = pawn
+ if(!IS_DEAD_OR_INCAP(living_pawn))
+ examine_text += "[pawn.p_they(TRUE)] seem[pawn.p_s()] happy to see you!"
+
+/// If we died, drop anything we were carrying
+/datum/ai_controller/dog/proc/on_death(mob/living/ol_yeller)
+ SIGNAL_HANDLER
+
+ var/obj/item/carried_item = blackboard[BB_SIMPLE_CARRY_ITEM]
+ if(!carried_item)
+ return
+
+ ol_yeller.visible_message("[ol_yeller] drops [carried_item] as [ol_yeller.p_they()] die[ol_yeller.p_s()].")
+ carried_item.forceMove(ol_yeller.drop_location())
+ blackboard[BB_SIMPLE_CARRY_ITEM] = null
+
+// next section is regarding commands
+
+/// Someone alt clicked us, see if they're someone we should show the radial command menu to
+/datum/ai_controller/dog/proc/check_altclicked(datum/source, mob/living/clicker)
+ SIGNAL_HANDLER
+
+ if(!COOLDOWN_FINISHED(src, command_cooldown))
+ return
+ if(!istype(clicker) || !blackboard[BB_DOG_FRIENDS][WEAKREF(clicker)])
+ return
+ INVOKE_ASYNC(src, .proc/command_radial, clicker)
+
+/// Show the command radial menu
+/datum/ai_controller/dog/proc/command_radial(mob/living/clicker)
+ var/list/commands = list(
+ COMMAND_HEEL = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow"),
+ COMMAND_FETCH = image(icon = 'icons/mob/actions/actions_spells.dmi', icon_state = "summons"),
+ COMMAND_ATTACK = image(icon = 'icons/effects/effects.dmi', icon_state = "bite"),
+ COMMAND_DIE = image(icon = 'icons/mob/pets.dmi', icon_state = "puppy_dead")
+ )
+
+ var/choice = show_radial_menu(clicker, pawn, commands, custom_check = CALLBACK(src, .proc/check_menu, clicker), tooltips = TRUE)
+ if(!choice || !check_menu(clicker))
+ return
+ set_command_mode(clicker, choice)
+
+/datum/ai_controller/dog/proc/check_menu(mob/user)
+ if(!istype(user))
+ CRASH("A non-mob is trying to issue an order to [pawn].")
+ if(user.incapacitated() || !can_see(user, pawn))
+ return FALSE
+ return TRUE
+
+/// One of our friends said something, see if it's a valid command, and if so, take action
+/datum/ai_controller/dog/proc/check_verbal_command(mob/speaker, speech_args)
+ SIGNAL_HANDLER
+
+ if(!blackboard[BB_DOG_FRIENDS][WEAKREF(speaker)])
+ return
+
+ if(!COOLDOWN_FINISHED(src, command_cooldown))
+ return
+
+ var/mob/living/living_pawn = pawn
+ if(IS_DEAD_OR_INCAP(living_pawn))
+ return
+
+ var/spoken_text = speech_args[SPEECH_MESSAGE] // probably should check for full words
+ var/command
+ if(findtext(spoken_text, "heel") || findtext(spoken_text, "sit") || findtext(spoken_text, "stay"))
+ command = COMMAND_HEEL
+ else if(findtext(spoken_text, "fetch") || findtext(spoken_text, "get it"))
+ command = COMMAND_FETCH
+ else if(findtext(spoken_text, "attack") || findtext(spoken_text, "sic"))
+ command = COMMAND_ATTACK
+ else if(findtext(spoken_text, "play dead"))
+ command = COMMAND_DIE
+ else
+ return
+
+ if(!can_see(pawn, speaker, length=AI_DOG_VISION_RANGE))
+ return
+ set_command_mode(speaker, command)
+
+/// Whether we got here via radial menu or a verbal command, this is where we actually process what our new command will be
+/datum/ai_controller/dog/proc/set_command_mode(mob/commander, command)
+ COOLDOWN_START(src, command_cooldown, AI_DOG_COMMAND_COOLDOWN)
+
+ switch(command)
+ // heel: stop what you're doing, relax and try not to do anything for a little bit
+ if(COMMAND_HEEL)
+ pawn.visible_message("[pawn]'s ears prick up at [commander]'s command, and [pawn.p_they()] sit[pawn.p_s()] down obediently, awaiting further orders.")
+ blackboard[BB_DOG_ORDER_MODE] = DOG_COMMAND_NONE
+ COOLDOWN_START(src, heel_cooldown, AI_DOG_HEEL_DURATION)
+ CancelActions()
+ // fetch: whatever the commander points to, try and bring it back
+ if(COMMAND_FETCH)
+ pawn.visible_message("[pawn]'s ears prick up at [commander]'s command, and [pawn.p_they()] bounce[pawn.p_s()] slightly in anticipation.")
+ blackboard[BB_DOG_ORDER_MODE] = DOG_COMMAND_FETCH
+ // attack: harass whoever the commander points to
+ if(COMMAND_ATTACK)
+ pawn.visible_message("[pawn]'s ears prick up at [commander]'s command, and [pawn.p_they()] growl[pawn.p_s()] intensely.") // imagine getting intimidated by a corgi
+ blackboard[BB_DOG_ORDER_MODE] = DOG_COMMAND_ATTACK
+ if(COMMAND_DIE)
+ blackboard[BB_DOG_ORDER_MODE] = DOG_COMMAND_NONE
+ CancelActions()
+ queue_behavior(/datum/ai_behavior/play_dead)
+
+/// Someone we like is pointing at something, see if it's something we might want to interact with (like if they might want us to fetch something for them)
+/datum/ai_controller/dog/proc/check_point(mob/pointing_friend, atom/movable/pointed_movable)
+ SIGNAL_HANDLER
+
+ var/mob/living/living_pawn = pawn
+ if(IS_DEAD_OR_INCAP(living_pawn))
+ return
+
+ if(!COOLDOWN_FINISHED(src, command_cooldown))
+ return
+ if(pointed_movable == pawn || blackboard[BB_FETCH_TARGET] || !istype(pointed_movable) || blackboard[BB_DOG_ORDER_MODE] == DOG_COMMAND_NONE) // busy or no command
+ return
+ if(!can_see(pawn, pointing_friend, length=AI_DOG_VISION_RANGE) || !can_see(pawn, pointed_movable, length=AI_DOG_VISION_RANGE))
+ return
+
+ COOLDOWN_START(src, command_cooldown, AI_DOG_COMMAND_COOLDOWN)
+
+ switch(blackboard[BB_DOG_ORDER_MODE])
+ if(DOG_COMMAND_FETCH)
+ if(!isitem(pointed_movable) || pointed_movable.anchored)
+ return
+ var/obj/item/pointed_item = pointed_movable
+ if(pointed_item.obj_flags & ABSTRACT)
+ return
+ pawn.visible_message("[pawn] follows [pointing_friend]'s gesture towards [pointed_movable] and barks excitedly!")
+ current_movement_target = pointed_movable
+ blackboard[BB_FETCH_TARGET] = pointed_movable
+ blackboard[BB_FETCH_DELIVER_TO] = pointing_friend
+ if(living_pawn.buckled)
+ queue_behavior(/datum/ai_behavior/resist)//in case they are in bed or something
+ queue_behavior(/datum/ai_behavior/fetch)
+ if(DOG_COMMAND_ATTACK)
+ pawn.visible_message("[pawn] follows [pointing_friend]'s gesture towards [pointed_movable] and growls intensely!")
+ current_movement_target = pointed_movable
+ blackboard[BB_DOG_HARASS_TARGET] = WEAKREF(pointed_movable)
+ if(living_pawn.buckled)
+ queue_behavior(/datum/ai_behavior/resist)//in case they are in bed or something
+ queue_behavior(/datum/ai_behavior/harass)
diff --git a/code/datums/ai/dog/dog_subtrees.dm b/code/datums/ai/dog/dog_subtrees.dm
new file mode 100644
index 0000000000000..c35ab6b2fce09
--- /dev/null
+++ b/code/datums/ai/dog/dog_subtrees.dm
@@ -0,0 +1,40 @@
+/datum/ai_planning_subtree/dog
+ COOLDOWN_DECLARE(heel_cooldown)
+ COOLDOWN_DECLARE(reset_ignore_cooldown)
+
+/datum/ai_planning_subtree/dog/SelectBehaviors(datum/ai_controller/dog/controller, delta_time)
+ var/mob/living/living_pawn = controller.pawn
+
+ // occasionally reset our ignore list
+ if(COOLDOWN_FINISHED(src, reset_ignore_cooldown) && length(controller.blackboard[BB_FETCH_IGNORE_LIST]))
+ COOLDOWN_START(src, reset_ignore_cooldown, AI_FETCH_IGNORE_DURATION)
+ controller.blackboard[BB_FETCH_IGNORE_LIST] = list()
+
+ // if we were just ordered to heel, chill out for a bit
+ if(!COOLDOWN_FINISHED(src, heel_cooldown))
+ return
+
+ // if we're not already carrying something and we have a fetch target (and we're not already doing something with it), see if we can eat/equip it
+ if(!controller.blackboard[BB_SIMPLE_CARRY_ITEM] && controller.blackboard[BB_FETCH_TARGET])
+ var/atom/movable/interact_target = controller.blackboard[BB_FETCH_TARGET]
+ if(in_range(living_pawn, interact_target) && (isturf(interact_target.loc)))
+ controller.current_movement_target = interact_target
+ if(istype(interact_target, /obj/item/reagent_containers/food))
+ controller.queue_behavior(/datum/ai_behavior/eat_snack)
+ else if(isitem(interact_target))
+ controller.queue_behavior(/datum/ai_behavior/simple_equip)
+ else
+ controller.blackboard[BB_FETCH_TARGET] = null
+ controller.blackboard[BB_FETCH_DELIVER_TO] = null
+ return
+
+ // if we're carrying something and we have a destination to deliver it, do that
+ if(controller.blackboard[BB_SIMPLE_CARRY_ITEM] && controller.blackboard[BB_FETCH_DELIVER_TO])
+ var/atom/return_target = controller.blackboard[BB_FETCH_DELIVER_TO]
+ if(!can_see(controller.pawn, return_target, length=AI_DOG_VISION_RANGE))
+ // if the return target isn't in sight, we'll just forget about it and carry the thing around
+ controller.blackboard[BB_FETCH_DELIVER_TO] = null
+ return
+ controller.current_movement_target = return_target
+ controller.queue_behavior(/datum/ai_behavior/deliver_item)
+ return
diff --git a/code/datums/ai/generic_actions.dm b/code/datums/ai/generic_actions.dm
new file mode 100644
index 0000000000000..358f6443f2acd
--- /dev/null
+++ b/code/datums/ai/generic_actions.dm
@@ -0,0 +1,283 @@
+
+/datum/ai_behavior/resist/perform(delta_time, datum/ai_controller/controller)
+ . = ..()
+ var/mob/living/living_pawn = controller.pawn
+ living_pawn.resist()
+ finish_action(controller, TRUE)
+
+/datum/ai_behavior/battle_screech
+ ///List of possible screeches the behavior has
+ var/list/screeches
+
+/datum/ai_behavior/battle_screech/perform(delta_time, datum/ai_controller/controller)
+ . = ..()
+ var/mob/living/living_pawn = controller.pawn
+ INVOKE_ASYNC(living_pawn, /mob.proc/emote, pick(screeches))
+ finish_action(controller, TRUE)
+
+///Moves to target then finishes
+/datum/ai_behavior/move_to_target
+ behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
+
+/datum/ai_behavior/move_to_target/perform(delta_time, datum/ai_controller/controller)
+ . = ..()
+ finish_action(controller, TRUE)
+
+
+/datum/ai_behavior/break_spine
+ behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
+ action_cooldown = 0.7 SECONDS
+ var/give_up_distance = 10
+
+/datum/ai_behavior/break_spine/setup(datum/ai_controller/controller, target_key)
+ . = ..()
+ controller.current_movement_target = controller.blackboard[target_key]
+
+/datum/ai_behavior/break_spine/perform(delta_time, datum/ai_controller/controller, target_key)
+ var/mob/living/batman = controller.blackboard[target_key]
+ var/mob/living/big_guy = controller.pawn //he was molded by the darkness
+
+ if(batman.stat)
+ finish_action(controller, TRUE, target_key)
+
+ if(get_dist(batman, big_guy) >= give_up_distance)
+ finish_action(controller, FALSE, target_key)
+
+ big_guy.start_pulling(batman)
+ big_guy.setDir(get_dir(big_guy, batman))
+
+ batman.visible_message("[batman] gets a slightly too tight hug from [big_guy]!", "You feel your body break as [big_guy] embraces you!")
+
+ if(iscarbon(batman))
+ var/mob/living/carbon/carbon_batman = batman
+ for(var/obj/item/bodypart/bodypart_to_break in carbon_batman.bodyparts)
+ if(bodypart_to_break.body_zone == BODY_ZONE_HEAD)
+ continue
+ bodypart_to_break.receive_damage(brute = 15)
+ else
+ batman.adjustBruteLoss(150)
+
+ finish_action(controller, TRUE, target_key)
+
+/datum/ai_behavior/break_spine/finish_action(datum/ai_controller/controller, succeeded, target_key)
+ if(succeeded)
+ controller.blackboard -= target_key
+ return ..()
+
+/// Use in hand the currently held item
+/datum/ai_behavior/use_in_hand
+ behavior_flags = AI_BEHAVIOR_MOVE_AND_PERFORM
+
+
+/datum/ai_behavior/use_in_hand/perform(delta_time, datum/ai_controller/controller)
+ . = ..()
+ var/mob/living/pawn = controller.pawn
+ var/obj/item/held = pawn.get_item_by_slot(pawn.get_active_hand())
+ if(!held)
+ finish_action(controller, FALSE)
+ return
+ pawn.activate_hand(pawn.get_active_hand())
+ finish_action(controller, TRUE)
+
+/// Use the currently held item, or unarmed, on an object in the world
+/datum/ai_behavior/use_on_object
+ required_distance = 1
+ behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
+
+/datum/ai_behavior/use_on_object/setup(datum/ai_controller/controller, target_key)
+ . = ..()
+ controller.current_movement_target = controller.blackboard[target_key]
+
+/datum/ai_behavior/use_on_object/perform(delta_time, datum/ai_controller/controller, target_key)
+ . = ..()
+ var/mob/living/pawn = controller.pawn
+ var/obj/item/held_item = pawn.get_item_by_slot(pawn.get_active_hand())
+ var/atom/target = controller.blackboard[BB_MONKEY_CURRENT_PRESS_TARGET]
+
+ if(!target || !pawn.CanReach(target))
+ finish_action(controller, FALSE)
+ return
+
+ pawn.a_intent = INTENT_HELP
+ if(held_item)
+ held_item.melee_attack_chain(pawn, target)
+ else
+ pawn.UnarmedAttack(target, TRUE)
+
+ finish_action(controller, TRUE)
+
+/datum/ai_behavior/give
+ required_distance = 1
+ behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
+
+
+/datum/ai_behavior/give/setup(datum/ai_controller/controller, target_key)
+ . = ..()
+ controller.current_movement_target = controller.blackboard[target_key]
+
+
+/datum/ai_behavior/give/perform(delta_time, datum/ai_controller/controller, target_key)
+ . = ..()
+ var/mob/living/pawn = controller.pawn
+ var/obj/item/held_item = pawn.get_item_by_slot(pawn.get_active_hand())
+ var/atom/target = controller.blackboard[target_key]
+
+ if(!target || !pawn.CanReach(target) || !isliving(target))
+ finish_action(controller, FALSE)
+ return
+
+ var/mob/living/living_target = target
+ controller.PauseAi(1.5 SECONDS)
+ living_target.visible_message(
+ "[pawn] starts trying to give [held_item] to [living_target]!",
+ "[pawn] tries to give you [held_item]!"
+ )
+ if(!do_mob(pawn, living_target, 1 SECONDS))
+ return
+ if(QDELETED(held_item) || QDELETED(living_target))
+ finish_action(controller, FALSE)
+ return
+ var/pocket_choice = prob(50) ? ITEM_SLOT_RPOCKET : ITEM_SLOT_LPOCKET
+ if(prob(50) && living_target.can_put_in_hand(held_item))
+ living_target.put_in_hand(held_item)
+ else if(held_item.mob_can_equip(living_target, pawn, pocket_choice, TRUE))
+ living_target.equip_to_slot(held_item, pocket_choice)
+
+ finish_action(controller, TRUE)
+
+/datum/ai_behavior/consume
+ required_distance = 1
+ behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
+ action_cooldown = 2 SECONDS
+
+/datum/ai_behavior/consume/setup(datum/ai_controller/controller, obj/item/target)
+ . = ..()
+ controller.current_movement_target = target
+
+/datum/ai_behavior/consume/perform(delta_time, datum/ai_controller/controller, obj/item/target)
+ . = ..()
+ var/mob/living/pawn = controller.pawn
+
+ if(!(target in pawn.held_items))
+ if(!pawn.put_in_hand_check(target))
+ finish_action(controller, FALSE)
+ return
+
+ pawn.put_in_hands(target)
+
+ target.melee_attack_chain(pawn, pawn)
+
+ if(QDELETED(target) || prob(10)) // Even if we don't finish it all we can randomly decide to be done
+ finish_action(controller, TRUE)
+
+/**find and set
+ * Finds an item near themselves, sets a blackboard key as it. Very useful for ais that need to use machines or something.
+ * if you want to do something more complicated than find a single atom, change the search_tactic() proc
+ * cool tip: search_tactic() can set lists
+ */
+/datum/ai_behavior/find_and_set
+ action_cooldown = 5 SECONDS
+ ///search range in how many tiles around the pawn to look for the path
+ var/search_range = 7
+ //optional, don't use if you're changing search_tactic()
+ var/locate_path
+ var/bb_key_to_set
+
+/datum/ai_behavior/find_and_set/perform(delta_time, datum/ai_controller/controller)
+ . = ..()
+ var/find_this_thing = search_tactic(controller)
+ if(find_this_thing)
+ controller.blackboard[bb_key_to_set] = find_this_thing
+ finish_action(controller, TRUE)
+ else
+ finish_action(controller, FALSE)
+
+/datum/ai_behavior/find_and_set/proc/search_tactic(datum/ai_controller/controller)
+ return locate(locate_path) in oview(search_range, controller.pawn)
+
+
+/// This behavior involves attacking a target.
+/datum/ai_behavior/attack
+ behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM
+ required_distance = 1
+
+/datum/ai_behavior/attack/perform(delta_time, datum/ai_controller/controller)
+ . = ..()
+ var/mob/living/living_pawn = controller.pawn
+ if(!istype(living_pawn) || !isturf(living_pawn.loc))
+ return
+
+ var/datum/weakref/attack_ref = controller.blackboard[BB_ATTACK_TARGET]
+ var/atom/movable/attack_target = attack_ref?.resolve()
+ if(!attack_target || !can_see(living_pawn, attack_target, length=controller.blackboard[BB_VISION_RANGE]))
+ finish_action(controller, FALSE)
+ return
+
+ var/mob/living/living_target = attack_target
+ if(istype(living_target) && (living_target.stat == DEAD))
+ finish_action(controller, TRUE)
+ return
+
+ controller.current_movement_target = living_target
+ attack(controller, living_target)
+
+/datum/ai_behavior/attack/finish_action(datum/ai_controller/controller, succeeded)
+ . = ..()
+ controller.blackboard[BB_ATTACK_TARGET] = null
+
+/// A proc representing when the mob is pushed to actually attack the target. Again, subtypes can be used to represent different attacks from different animals, or it can be some other generic behavior
+/datum/ai_behavior/attack/proc/attack(datum/ai_controller/controller, mob/living/living_target)
+ var/mob/living/living_pawn = controller.pawn
+ if(!istype(living_pawn))
+ return
+ living_pawn.ClickOn(living_target, list())
+
+/// This behavior involves attacking a target.
+/datum/ai_behavior/follow
+ behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM
+ required_distance = 1
+
+/datum/ai_behavior/follow/perform(delta_time, datum/ai_controller/controller)
+ . = ..()
+ var/mob/living/living_pawn = controller.pawn
+ if(!istype(living_pawn) || !isturf(living_pawn.loc))
+ return
+
+ var/datum/weakref/follow_ref = controller.blackboard[BB_FOLLOW_TARGET]
+ var/atom/movable/follow_target = follow_ref?.resolve()
+ if(!follow_target || get_dist(living_pawn, follow_target) > controller.blackboard[BB_VISION_RANGE])
+ finish_action(controller, FALSE)
+ return
+
+ var/mob/living/living_target = follow_target
+ if(istype(living_target) && (living_target.stat == DEAD))
+ finish_action(controller, TRUE)
+ return
+
+ controller.current_movement_target = living_target
+
+/datum/ai_behavior/follow/finish_action(datum/ai_controller/controller, succeeded)
+ . = ..()
+ controller.blackboard[BB_FOLLOW_TARGET] = null
+
+
+
+/datum/ai_behavior/perform_emote
+
+/datum/ai_behavior/perform_emote/perform(delta_time, datum/ai_controller/controller, emote)
+ var/mob/living/living_pawn = controller.pawn
+ if(!istype(living_pawn))
+ return
+ living_pawn.manual_emote(emote)
+ finish_action(controller, TRUE)
+
+/datum/ai_behavior/perform_speech
+
+/datum/ai_behavior/perform_speech/perform(delta_time, datum/ai_controller/controller, speech)
+ var/mob/living/living_pawn = controller.pawn
+ if(!istype(living_pawn))
+ return
+ living_pawn.say(speech, forced = "AI Controller")
+ finish_action(controller, TRUE)
+
+
diff --git a/code/datums/ai/monkey/monkey_behaviors.dm b/code/datums/ai/monkey/monkey_behaviors.dm
new file mode 100644
index 0000000000000..1d91c85e1c4f5
--- /dev/null
+++ b/code/datums/ai/monkey/monkey_behaviors.dm
@@ -0,0 +1,297 @@
+/datum/ai_behavior/battle_screech/monkey
+ screeches = list("roar","screech")
+
+/datum/ai_behavior/monkey_equip
+ behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
+
+/datum/ai_behavior/monkey_equip/finish_action(datum/ai_controller/controller, success)
+ . = ..()
+
+ if(!success) //Don't try again on this item if we failed
+ var/list/item_blacklist = controller.blackboard[BB_MONKEY_BLACKLISTITEMS]
+ var/obj/item/target = controller.blackboard[BB_MONKEY_PICKUPTARGET]
+
+ item_blacklist[target] = TRUE
+ if(istype(controller, /datum/ai_controller/monkey)) //What the fuck
+ controller.RegisterSignal(target, COMSIG_PARENT_QDELETING, /datum/ai_controller/monkey/proc/target_del)
+
+ controller.blackboard[BB_MONKEY_PICKUPTARGET] = null
+
+/datum/ai_behavior/monkey_equip/proc/equip_item(datum/ai_controller/controller)
+ var/mob/living/living_pawn = controller.pawn
+
+ var/obj/item/target = controller.blackboard[BB_MONKEY_PICKUPTARGET]
+ var/best_force = controller.blackboard[BB_MONKEY_BEST_FORCE_FOUND]
+
+ if(!isturf(living_pawn.loc))
+ finish_action(controller, FALSE)
+ return
+
+ if(!target)
+ finish_action(controller, FALSE)
+ return
+
+ if(target.anchored) //Can't pick it up, so stop trying.
+ finish_action(controller, FALSE)
+ return
+
+ // Strong weapon
+ else if(target.force > best_force)
+ living_pawn.drop_all_held_items()
+ living_pawn.put_in_hands(target)
+ controller.blackboard[BB_MONKEY_BEST_FORCE_FOUND] = target.force
+ finish_action(controller, TRUE)
+ return
+
+ else if(target.slot_flags) //Clothing == top priority
+ living_pawn.dropItemToGround(target, TRUE)
+ living_pawn.update_icons()
+ if(!living_pawn.equip_to_appropriate_slot(target))
+ finish_action(controller, FALSE)
+ return //Already wearing something, in the future this should probably replace the current item but the code didn't actually do that, and I dont want to support it right now.
+ finish_action(controller, TRUE)
+ return
+
+ // EVERYTHING ELSE
+ else if(living_pawn.get_empty_held_index_for_side(LEFT_HANDS) || living_pawn.get_empty_held_index_for_side(RIGHT_HANDS))
+ living_pawn.put_in_hands(target)
+ finish_action(controller, TRUE)
+ return
+
+ finish_action(controller, FALSE)
+
+/datum/ai_behavior/monkey_equip/ground
+ required_distance = 0
+
+/datum/ai_behavior/monkey_equip/ground/perform(delta_time, datum/ai_controller/controller)
+ . = ..()
+ equip_item(controller)
+
+/datum/ai_behavior/monkey_equip/pickpocket
+
+/datum/ai_behavior/monkey_equip/pickpocket/perform(delta_time, datum/ai_controller/controller)
+ . = ..()
+ if(controller.blackboard[BB_MONKEY_PICKPOCKETING]) //We are pickpocketing, don't do ANYTHING!!!!
+ return
+ INVOKE_ASYNC(src, .proc/attempt_pickpocket, controller)
+
+/datum/ai_behavior/monkey_equip/pickpocket/proc/attempt_pickpocket(datum/ai_controller/controller)
+ var/obj/item/target = controller.blackboard[BB_MONKEY_PICKUPTARGET]
+
+ var/mob/living/victim = target.loc
+ var/mob/living/living_pawn = controller.pawn
+
+ if(!istype(victim) || !living_pawn.CanReach(victim))
+ finish_action(controller, FALSE)
+ return
+
+
+
+ victim.visible_message("[living_pawn] starts trying to take [target] from [victim]!", "[living_pawn] tries to take [target]!")
+
+ controller.blackboard[BB_MONKEY_PICKPOCKETING] = TRUE
+
+ var/success = FALSE
+
+ if(do_mob(living_pawn, victim, MONKEY_ITEM_SNATCH_DELAY) && target && living_pawn.CanReach(victim))
+
+ for(var/obj/item/I in victim.held_items)
+ if(I == target)
+ victim.visible_message("[living_pawn] snatches [target] from [victim].", "[living_pawn] snatched [target]!")
+ if(victim.temporarilyRemoveItemFromInventory(target))
+ if(!QDELETED(target) && !equip_item(controller))
+ target.forceMove(living_pawn.drop_location())
+ success = TRUE
+ break
+ else
+ victim.visible_message("[living_pawn] tried to snatch [target] from [victim], but failed!", "[living_pawn] tried to grab [target]!")
+
+ finish_action(controller, success) //We either fucked up or got the item.
+
+/datum/ai_behavior/monkey_equip/pickpocket/finish_action(datum/ai_controller/controller, success)
+ . = ..()
+ controller.blackboard[BB_MONKEY_PICKPOCKETING] = FALSE
+ controller.blackboard[BB_MONKEY_PICKUPTARGET] = null
+
+/datum/ai_behavior/monkey_flee
+
+/datum/ai_behavior/monkey_flee/perform(delta_time, datum/ai_controller/controller)
+ . = ..()
+
+ var/mob/living/living_pawn = controller.pawn
+
+ if(living_pawn.health >= MONKEY_FLEE_HEALTH)
+ finish_action(controller, TRUE) //we're back in bussiness
+
+ var/mob/living/target = null
+
+ // flee from anyone who attacked us and we didn't beat down
+ for(var/mob/living/L in view(living_pawn, MONKEY_FLEE_VISION))
+ if(controller.blackboard[BB_MONKEY_ENEMIES][L] && L.stat == CONSCIOUS)
+ target = L
+ break
+
+ if(target)
+ walk_away(living_pawn, target, MONKEY_ENEMY_VISION, 5)
+ else
+ finish_action(controller, TRUE)
+
+/datum/ai_behavior/monkey_attack_mob
+ behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM //performs to increase frustration
+
+/datum/ai_behavior/monkey_attack_mob/perform(delta_time, datum/ai_controller/controller)
+ . = ..()
+
+ var/mob/living/target = controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET]
+ var/mob/living/living_pawn = controller.pawn
+
+ if(!target || target.stat != CONSCIOUS)
+ finish_action(controller, TRUE) //Target == owned
+
+ if(isturf(target.loc) && !IS_DEAD_OR_INCAP(living_pawn)) // Check if they're a valid target
+ // check if target has a weapon
+ var/obj/item/W
+ for(var/obj/item/I in target.held_items)
+ if(!(I.item_flags & ABSTRACT))
+ W = I
+ break
+
+ // if the target has a weapon, chance to disarm them
+ if(W && DT_PROB(MONKEY_ATTACK_DISARM_PROB, delta_time))
+ monkey_attack(controller, target, delta_time, TRUE)
+ else
+ monkey_attack(controller, target, delta_time, FALSE)
+
+
+/datum/ai_behavior/monkey_attack_mob/finish_action(datum/ai_controller/controller, succeeded)
+ . = ..()
+ var/mob/living/living_pawn = controller.pawn
+ walk(living_pawn, 0)
+ controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] = null
+
+/// attack using a held weapon otherwise bite the enemy, then if we are angry there is a chance we might calm down a little
+/datum/ai_behavior/monkey_attack_mob/proc/monkey_attack(datum/ai_controller/controller, mob/living/target, delta_time, disarm)
+ var/mob/living/living_pawn = controller.pawn
+
+ if(living_pawn.next_move > world.time)
+ return
+
+ living_pawn.changeNext_move(CLICK_CD_MELEE) //We play fair
+
+ var/obj/item/weapon = locate(/obj/item) in living_pawn.held_items
+
+ living_pawn.face_atom(target)
+
+ living_pawn.a_intent = INTENT_HARM
+
+ if(isnull(controller.blackboard[BB_MONKEY_GUN_WORKED]))
+ controller.blackboard[BB_MONKEY_GUN_WORKED] = TRUE
+
+ // attack with weapon if we have one
+ if(living_pawn.CanReach(target, weapon))
+ if(weapon)
+ weapon.melee_attack_chain(living_pawn, target)
+ else
+ if(disarm)
+ living_pawn.a_intent = INTENT_DISARM
+ living_pawn.UnarmedAttack(target)
+ living_pawn.a_intent = INTENT_HARM
+ else
+ living_pawn.UnarmedAttack(target)
+ controller.blackboard[BB_MONKEY_GUN_WORKED] = TRUE // We reset their memory of the gun being 'broken' if they accomplish some other attack
+ else if(weapon)
+ var/atom/real_target = target
+ if(prob(10)) // Artificial miss
+ real_target = pick(oview(2, target))
+
+ var/obj/item/gun/gun = locate() in living_pawn.held_items
+ var/can_shoot = gun?.can_shoot() || FALSE
+ if(gun && controller.blackboard[BB_MONKEY_GUN_WORKED] && prob(95))
+ // We attempt to attack even if we can't shoot so we get the effects of pulling the trigger
+ gun.afterattack(real_target, living_pawn, FALSE)
+ controller.blackboard[BB_MONKEY_GUN_WORKED] = can_shoot ? TRUE : prob(80) // Only 20% likely to notice it didn't work
+ if(can_shoot)
+ controller.blackboard[BB_MONKEY_GUN_NEURONS_ACTIVATED] = TRUE
+ else
+ living_pawn.throw_item(real_target)
+ controller.blackboard[BB_MONKEY_GUN_WORKED] = TRUE // 'worked'
+
+ living_pawn.a_intent = INTENT_HELP
+
+ // no de-aggro
+ if(controller.blackboard[BB_MONKEY_AGGRESSIVE])
+ return
+
+ if(DT_PROB(MONKEY_HATRED_REDUCTION_PROB, delta_time))
+ controller.blackboard[BB_MONKEY_ENEMIES][target]--
+
+ // if we are not angry at our target, go back to idle
+ if(controller.blackboard[BB_MONKEY_ENEMIES][target] <= 0)
+ var/list/enemies = controller.blackboard[BB_MONKEY_ENEMIES]
+ enemies.Remove(target)
+ if(controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] == target)
+ finish_action(controller, TRUE)
+
+/datum/ai_behavior/disposal_mob
+ behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM //performs to increase frustration
+
+/datum/ai_behavior/disposal_mob/finish_action(datum/ai_controller/controller, succeeded, attack_target_key, disposal_target_key)
+ . = ..()
+ controller.blackboard[attack_target_key] = null //Reset attack target
+ controller.blackboard[BB_MONKEY_DISPOSING] = FALSE //No longer disposing
+ controller.blackboard[disposal_target_key] = null //No target disposal
+
+/datum/ai_behavior/disposal_mob/perform(delta_time, datum/ai_controller/controller, attack_target_key, disposal_target_key)
+ . = ..()
+
+ if(controller.blackboard[BB_MONKEY_DISPOSING]) //We are disposing, don't do ANYTHING!!!!
+ return
+
+ var/mob/living/target = controller.blackboard[attack_target_key]
+ var/mob/living/living_pawn = controller.pawn
+
+ controller.current_movement_target = target
+
+ if(target.pulledby != living_pawn && !HAS_AI_CONTROLLER_TYPE(target.pulledby, /datum/ai_controller/monkey)) //Dont steal from my fellow monkeys.
+ if(living_pawn.Adjacent(target) && isturf(target.loc))
+ target.grabbedby(living_pawn)
+ return //Do the rest next turn
+
+ var/obj/machinery/disposal/disposal = controller.blackboard[disposal_target_key]
+ controller.current_movement_target = disposal
+
+ if(living_pawn.Adjacent(disposal))
+ INVOKE_ASYNC(src, .proc/try_disposal_mob, controller, attack_target_key, disposal_target_key) //put him in!
+ else //This means we might be getting pissed!
+ return
+
+/datum/ai_behavior/disposal_mob/proc/try_disposal_mob(datum/ai_controller/controller, attack_target_key, disposal_target_key)
+ var/mob/living/living_pawn = controller.pawn
+ var/mob/living/target = controller.blackboard[attack_target_key]
+ var/obj/machinery/disposal/disposal = controller.blackboard[disposal_target_key]
+
+ controller.blackboard[BB_MONKEY_DISPOSING] = TRUE
+
+ if(target && disposal?.stuff_mob_in(target, living_pawn))
+ disposal.flush()
+ finish_action(controller, TRUE, attack_target_key, disposal_target_key)
+
+
+/datum/ai_behavior/recruit_monkeys/perform(delta_time, datum/ai_controller/controller)
+ . = ..()
+
+ controller.blackboard[BB_MONKEY_RECRUIT_COOLDOWN] = world.time + MONKEY_RECRUIT_COOLDOWN
+ var/mob/living/living_pawn = controller.pawn
+
+ for(var/mob/living/L in view(living_pawn, MONKEY_ENEMY_VISION))
+ if(!HAS_AI_CONTROLLER_TYPE(L, /datum/ai_controller/monkey))
+ continue
+
+ if(!DT_PROB(MONKEY_RECRUIT_PROB, delta_time))
+ continue
+ var/datum/ai_controller/monkey/monkey_ai = L.ai_controller
+ var/atom/your_enemy = controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET]
+ var/list/enemies = L.ai_controller.blackboard[BB_MONKEY_ENEMIES]
+ enemies[your_enemy] = MONKEY_RECRUIT_HATED_AMOUNT
+ monkey_ai.blackboard[BB_MONKEY_RECRUIT_COOLDOWN] = world.time + MONKEY_RECRUIT_COOLDOWN
+ finish_action(controller, TRUE)
diff --git a/code/datums/ai/monkey/monkey_controller.dm b/code/datums/ai/monkey/monkey_controller.dm
new file mode 100644
index 0000000000000..3de55ee68d669
--- /dev/null
+++ b/code/datums/ai/monkey/monkey_controller.dm
@@ -0,0 +1,260 @@
+/*
+AI controllers are a datumized form of AI that simulates the input a player would otherwise give to a mob. What this means is that these datums
+have ways of interacting with a specific mob and control it.
+*/
+///OOK OOK OOK
+
+/datum/ai_controller/monkey
+ movement_delay = 0.4 SECONDS
+ planning_subtrees = list(/datum/ai_planning_subtree/monkey_tree)
+ blackboard = list(
+ BB_MONKEY_AGGRESSIVE = FALSE,
+ BB_MONKEY_BEST_FORCE_FOUND = 0,
+ BB_MONKEY_ENEMIES = list(),
+ BB_MONKEY_BLACKLISTITEMS = list(),
+ BB_MONKEY_PICKUPTARGET = null,
+ BB_MONKEY_PICKPOCKETING = FALSE,
+ BB_MONKEY_DISPOSING = FALSE,
+ BB_MONKEY_TARGET_DISPOSAL = null,
+ BB_MONKEY_CURRENT_ATTACK_TARGET = null,
+ BB_MONKEY_GUN_NEURONS_ACTIVATED = FALSE,
+ BB_MONKEY_GUN_WORKED = TRUE,
+ BB_MONKEY_NEXT_HUNGRY = 0
+ )
+
+/datum/ai_controller/monkey/angry
+
+/datum/ai_controller/monkey/angry/TryPossessPawn(atom/new_pawn)
+ . = ..()
+ if(. & AI_CONTROLLER_INCOMPATIBLE)
+ return
+ blackboard[BB_MONKEY_AGGRESSIVE] = TRUE //Angry cunt
+
+/datum/ai_controller/monkey/TryPossessPawn(atom/new_pawn)
+ if(!isliving(new_pawn))
+ return AI_CONTROLLER_INCOMPATIBLE
+
+ blackboard[BB_MONKEY_NEXT_HUNGRY] = world.time + rand(0, 300)
+
+ var/mob/living/living_pawn = new_pawn
+ RegisterSignal(new_pawn, COMSIG_PARENT_ATTACKBY, .proc/on_attackby)
+ RegisterSignal(new_pawn, COMSIG_ATOM_ATTACK_HAND, .proc/on_attack_hand)
+ RegisterSignal(new_pawn, COMSIG_ATOM_ATTACK_PAW, .proc/on_attack_paw)
+ RegisterSignal(new_pawn, COMSIG_ATOM_ATTACK_ANIMAL, .proc/on_attack_animal)
+ RegisterSignal(new_pawn, COMSIG_MOB_ATTACK_ALIEN, .proc/on_attack_alien)
+ RegisterSignal(new_pawn, COMSIG_ATOM_BULLET_ACT, .proc/on_bullet_act)
+ RegisterSignal(new_pawn, COMSIG_ATOM_HITBY, .proc/on_hitby)
+ RegisterSignal(new_pawn, COMSIG_MOVABLE_CROSSED, .proc/on_Crossed)
+ RegisterSignal(new_pawn, COMSIG_LIVING_START_PULL, .proc/on_startpulling)
+ RegisterSignal(new_pawn, COMSIG_LIVING_TRY_SYRINGE, .proc/on_try_syringe)
+ RegisterSignal(new_pawn, COMSIG_ATOM_HULK_ATTACK, .proc/on_attack_hulk)
+ RegisterSignal(new_pawn, COMSIG_CARBON_CUFF_ATTEMPTED, .proc/on_attempt_cuff)
+ RegisterSignal(new_pawn, COMSIG_MOB_MOVESPEED_UPDATED, .proc/update_movespeed)
+ RegisterSignal(new_pawn, COMSIG_FOOD_EATEN, .proc/on_eat)
+
+ movement_delay = living_pawn.cached_multiplicative_slowdown
+ return ..() //Run parent at end
+
+/datum/ai_controller/monkey/UnpossessPawn(destroy)
+ UnregisterSignal(pawn, list(COMSIG_PARENT_ATTACKBY, COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_ATTACK_PAW, COMSIG_ATOM_BULLET_ACT, COMSIG_ATOM_HITBY, COMSIG_LIVING_START_PULL,\
+ COMSIG_LIVING_TRY_SYRINGE, COMSIG_ATOM_HULK_ATTACK, COMSIG_CARBON_CUFF_ATTEMPTED, COMSIG_MOB_MOVESPEED_UPDATED, COMSIG_ATOM_ATTACK_ANIMAL, COMSIG_MOB_ATTACK_ALIEN))
+ return ..() //Run parent at end
+
+// Stops sentient monkeys from being knocked over like weak dunces.
+/datum/ai_controller/monkey/on_sentience_gained()
+ . = ..()
+ UnregisterSignal(pawn, COMSIG_MOVABLE_CROSSED)
+
+/datum/ai_controller/monkey/on_sentience_lost()
+ . = ..()
+ RegisterSignal(pawn, COMSIG_MOVABLE_CROSSED)
+
+/datum/ai_controller/monkey/able_to_run()
+ . = ..()
+ var/mob/living/living_pawn = pawn
+
+ if(IS_DEAD_OR_INCAP(living_pawn))
+ return FALSE
+
+///re-used behavior pattern by monkeys for finding a weapon
+/datum/ai_controller/monkey/proc/TryFindWeapon()
+ var/mob/living/living_pawn = pawn
+
+ if(!locate(/obj/item) in living_pawn.held_items)
+ blackboard[BB_MONKEY_BEST_FORCE_FOUND] = 0
+
+ if(blackboard[BB_MONKEY_GUN_NEURONS_ACTIVATED] && (locate(/obj/item/gun) in living_pawn.held_items))
+ // We have a gun, what could we possibly want?
+ return FALSE
+
+ var/obj/item/weapon
+ var/list/nearby_items = list()
+ for(var/obj/item/item in oview(2, living_pawn))
+ nearby_items += item
+
+ weapon = GetBestWeapon(nearby_items, living_pawn.held_items)
+
+ var/pickpocket = FALSE
+ for(var/mob/living/carbon/human/human in oview(5, living_pawn))
+ var/obj/item/held_weapon = GetBestWeapon(human.held_items + weapon, living_pawn.held_items)
+ if(held_weapon == weapon) // It's just the same one, not a held one
+ continue
+ pickpocket = TRUE
+ weapon = held_weapon
+
+ if(!weapon || (weapon in living_pawn.held_items))
+ return FALSE
+
+ blackboard[BB_MONKEY_PICKUPTARGET] = weapon
+ current_movement_target = weapon
+ if(pickpocket)
+ queue_behavior(/datum/ai_behavior/monkey_equip/pickpocket)
+ else
+ queue_behavior(/datum/ai_behavior/monkey_equip/ground)
+ return TRUE
+
+/// Returns either the best weapon from the given choices or null if held weapons are better
+/datum/ai_controller/monkey/proc/GetBestWeapon(list/choices, list/held_weapons)
+ var/gun_neurons_activated = blackboard[BB_MONKEY_GUN_NEURONS_ACTIVATED]
+ var/top_force = 0
+ var/obj/item/top_force_item
+ for(var/obj/item/item as anything in held_weapons)
+ if(!item)
+ continue
+ if(HAS_TRAIT(item, TRAIT_NEEDS_TWO_HANDS) || blackboard[BB_MONKEY_BLACKLISTITEMS][item])
+ continue
+ if(gun_neurons_activated && istype(item, /obj/item/gun))
+ // We have a gun, why bother looking for something inferior
+ // Also yes it is intentional that monkeys dont know how to pick the best gun
+ return item
+ if(item.force > top_force)
+ top_force = item.force
+ top_force_item = item
+
+ for(var/obj/item/item as anything in choices)
+ if(!item)
+ continue
+ if(HAS_TRAIT(item, TRAIT_NEEDS_TWO_HANDS) || blackboard[BB_MONKEY_BLACKLISTITEMS][item])
+ continue
+ if(gun_neurons_activated && istype(item, /obj/item/gun))
+ return item
+ if(item.force <= top_force)
+ continue
+ top_force_item = item
+ top_force = item.force
+
+ return top_force_item
+
+/datum/ai_controller/monkey/proc/IsEdible(obj/item/thing)
+ if(istype(thing, /obj/item/reagent_containers/food))
+ return TRUE
+ if(istype(thing, /obj/item/reagent_containers/food/drinks/drinkingglass))
+ var/obj/item/reagent_containers/food/drinks/drinkingglass/glass = thing
+ if(glass.reagents.total_volume) // The glass has something in it, time to drink the mystery liquid!
+ return TRUE
+ return FALSE
+
+//When idle just kinda fuck around.
+/datum/ai_controller/monkey/PerformIdleBehavior(delta_time)
+ var/mob/living/living_pawn = pawn
+
+ if(DT_PROB(25, delta_time) && (living_pawn.mobility_flags & MOBILITY_MOVE) && isturf(living_pawn.loc) && !living_pawn.pulledby)
+ var/move_dir = pick(GLOB.alldirs)
+ living_pawn.Move(get_step(living_pawn, move_dir), move_dir)
+ else if(DT_PROB(5, delta_time))
+ INVOKE_ASYNC(living_pawn, /mob.proc/emote, pick("screech"))
+ else if(DT_PROB(1, delta_time))
+ INVOKE_ASYNC(living_pawn, /mob.proc/emote, pick("scratch","jump","roll","tail"))
+
+///Reactive events to being hit
+/datum/ai_controller/monkey/proc/retaliate(mob/living/L)
+ var/list/enemies = blackboard[BB_MONKEY_ENEMIES]
+ enemies[L] += MONKEY_HATRED_AMOUNT
+
+/datum/ai_controller/monkey/proc/on_attackby(datum/source, obj/item/I, mob/user)
+ SIGNAL_HANDLER
+ if(I.force && I.damtype != STAMINA)
+ retaliate(user)
+
+/datum/ai_controller/monkey/proc/on_attack_hand(datum/source, mob/living/user)
+ SIGNAL_HANDLER
+ if(user.a_intent == INTENT_HARM && prob(MONKEY_RETALIATE_HARM_PROB))
+ retaliate(user)
+ else if(user.a_intent == INTENT_DISARM && prob(MONKEY_RETALIATE_DISARM_PROB))
+ retaliate(user)
+
+/datum/ai_controller/monkey/proc/on_attack_paw(datum/source, mob/living/user)
+ SIGNAL_HANDLER
+ if(prob(MONKEY_RETALIATE_PROB))
+ retaliate(user)
+
+/datum/ai_controller/monkey/proc/on_attack_animal(datum/source, mob/living/user)
+ SIGNAL_HANDLER
+ if(user.melee_damage > 0 && prob(MONKEY_RETALIATE_PROB))
+ retaliate(user)
+
+/datum/ai_controller/monkey/proc/on_attack_alien(datum/source, mob/living/user)
+ SIGNAL_HANDLER
+ if(prob(MONKEY_RETALIATE_PROB))
+ retaliate(user)
+
+/datum/ai_controller/monkey/proc/on_bullet_act(datum/source, obj/item/projectile/Proj)
+ SIGNAL_HANDLER
+ var/mob/living/living_pawn = pawn
+ if(istype(Proj , /obj/item/projectile/beam)||istype(Proj, /obj/item/projectile/bullet))
+ if((Proj.damage_type == BURN) || (Proj.damage_type == BRUTE))
+ if(!Proj.nodamage && Proj.damage < living_pawn.health && isliving(Proj.firer))
+ retaliate(Proj.firer)
+
+/datum/ai_controller/monkey/proc/on_hitby(datum/source, atom/movable/AM, skipcatch = FALSE, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum)
+ SIGNAL_HANDLER
+ if(istype(AM, /obj/item))
+ var/mob/living/living_pawn = pawn
+ var/obj/item/I = AM
+ var/mob/thrown_by = I.thrownby?.resolve()
+ if(I.throwforce < living_pawn.health && ishuman(thrown_by))
+ var/mob/living/carbon/human/H = thrown_by
+ retaliate(H)
+
+/datum/ai_controller/monkey/proc/on_Crossed(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
+ SIGNAL_HANDLER
+ var/mob/living/living_pawn = pawn
+ if(!IS_DEAD_OR_INCAP(living_pawn) && isliving(arrived))
+ var/mob/living/in_the_way_mob = arrived
+ in_the_way_mob.knockOver(living_pawn)
+ return
+
+/datum/ai_controller/monkey/proc/on_startpulling(datum/source, atom/movable/puller, state, force)
+ SIGNAL_HANDLER
+ var/mob/living/living_pawn = pawn
+ if(!IS_DEAD_OR_INCAP(living_pawn) && prob(MONKEY_PULL_AGGRO_PROB)) // nuh uh you don't pull me!
+ retaliate(living_pawn.pulledby)
+ return TRUE
+
+/datum/ai_controller/monkey/proc/on_try_syringe(datum/source, mob/user)
+ SIGNAL_HANDLER
+ // chance of monkey retaliation
+ if(prob(MONKEY_SYRINGE_RETALIATION_PROB))
+ retaliate(user)
+
+/datum/ai_controller/monkey/proc/on_attack_hulk(datum/source, mob/user)
+ SIGNAL_HANDLER
+ retaliate(user)
+
+/datum/ai_controller/monkey/proc/on_attempt_cuff(datum/source, mob/user)
+ SIGNAL_HANDLER
+ // chance of monkey retaliation
+ if(prob(MONKEY_CUFF_RETALIATION_PROB))
+ retaliate(user)
+
+/datum/ai_controller/monkey/proc/update_movespeed(mob/living/pawn)
+ SIGNAL_HANDLER
+ movement_delay = pawn.cached_multiplicative_slowdown
+
+/datum/ai_controller/monkey/proc/target_del(target)
+ SIGNAL_HANDLER
+ blackboard[BB_MONKEY_BLACKLISTITEMS] -= target
+
+/datum/ai_controller/monkey/proc/on_eat(mob/living/pawn)
+ SIGNAL_HANDLER
+ blackboard[BB_MONKEY_NEXT_HUNGRY] = world.time + rand(120, 600) SECONDS
diff --git a/code/datums/ai/monkey/monkey_subtrees.dm b/code/datums/ai/monkey/monkey_subtrees.dm
new file mode 100644
index 0000000000000..6cf98dc470104
--- /dev/null
+++ b/code/datums/ai/monkey/monkey_subtrees.dm
@@ -0,0 +1,102 @@
+/datum/ai_planning_subtree/monkey_tree/SelectBehaviors(datum/ai_controller/monkey/controller, delta_time)
+ var/mob/living/living_pawn = controller.pawn
+
+ if(SHOULD_RESIST(living_pawn) && DT_PROB(MONKEY_RESIST_PROB, delta_time))
+ controller.queue_behavior(/datum/ai_behavior/resist) //BRO IM ON FUCKING FIRE BRO
+ return SUBTREE_RETURN_FINISH_PLANNING //IM NOT DOING ANYTHING ELSE BUT EXTUINGISH MYSELF, GOOD GOD HAVE MERCY.
+
+ var/list/enemies = controller.blackboard[BB_MONKEY_ENEMIES]
+
+ if(HAS_TRAIT(controller.pawn, TRAIT_PACIFISM)) //Not a pacifist? lets try some combat behavior.
+ return
+
+ var/mob/living/selected_enemy
+ if(length(enemies) || controller.blackboard[BB_MONKEY_AGGRESSIVE]) //We have enemies or are pissed
+ var/list/valids = list()
+ for(var/mob/living/possible_enemy in view(MONKEY_ENEMY_VISION, living_pawn))
+ if(possible_enemy == living_pawn || (!enemies[possible_enemy] && (!controller.blackboard[BB_MONKEY_AGGRESSIVE] || HAS_AI_CONTROLLER_TYPE(possible_enemy, /datum/ai_controller/monkey)))) //Are they an enemy? (And do we even care?)
+ continue
+ // Weighted list, so the closer they are the more likely they are to be chosen as the enemy
+ valids[possible_enemy] = CEILING(100 / (get_dist(living_pawn, possible_enemy) || 1), 1)
+
+ selected_enemy = pickweight(valids)
+
+ if(selected_enemy)
+ if(!selected_enemy.stat) //He's up, get him!
+ if(living_pawn.health < MONKEY_FLEE_HEALTH) //Time to skeddadle
+ controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] = selected_enemy
+ controller.queue_behavior(/datum/ai_behavior/monkey_flee)
+ return //I'm running fuck you guys
+
+ if(controller.TryFindWeapon()) //Getting a weapon is higher priority if im not fleeing.
+ return SUBTREE_RETURN_FINISH_PLANNING
+
+ controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] = selected_enemy
+ controller.current_movement_target = selected_enemy
+ if(controller.blackboard[BB_MONKEY_RECRUIT_COOLDOWN] < world.time)
+ controller.queue_behavior(/datum/ai_behavior/recruit_monkeys)
+ controller.queue_behavior(/datum/ai_behavior/battle_screech/monkey)
+ controller.queue_behavior(/datum/ai_behavior/monkey_attack_mob)
+ return SUBTREE_RETURN_FINISH_PLANNING //Focus on this
+
+ else //He's down, can we disposal him?
+ var/obj/machinery/disposal/bodyDisposal = locate(/obj/machinery/disposal/) in view(MONKEY_ENEMY_VISION, living_pawn)
+ if(bodyDisposal)
+ controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] = selected_enemy
+ controller.blackboard[BB_MONKEY_TARGET_DISPOSAL] = bodyDisposal
+ controller.queue_behavior(/datum/ai_behavior/disposal_mob, BB_MONKEY_CURRENT_ATTACK_TARGET, BB_MONKEY_TARGET_DISPOSAL)
+ return SUBTREE_RETURN_FINISH_PLANNING
+
+ if(prob(5))
+ controller.queue_behavior(/datum/ai_behavior/use_in_hand)
+
+ if(selected_enemy || !DT_PROB(MONKEY_SHENANIGAN_PROB, delta_time))
+ return
+
+ if(world.time >= controller.blackboard[BB_MONKEY_NEXT_HUNGRY])
+ var/list/food_candidates = list()
+ for(var/obj/item as anything in living_pawn.held_items)
+ if(!item || !controller.IsEdible(item))
+ continue
+ food_candidates += item
+
+ for(var/obj/item/candidate in oview(2, living_pawn))
+ if(!controller.IsEdible(candidate))
+ continue
+ food_candidates += candidate
+
+ if(length(food_candidates))
+ var/obj/item/best_held = controller.GetBestWeapon(null, living_pawn.held_items)
+ for(var/obj/item/held as anything in living_pawn.held_items)
+ if(!held || held == best_held)
+ continue
+ living_pawn.dropItemToGround(held)
+
+ controller.queue_behavior(/datum/ai_behavior/consume, pick(food_candidates))
+ return
+
+ if(prob(50))
+ var/list/possible_targets = list()
+ for(var/atom/thing in view(2, living_pawn))
+ if(!thing.mouse_opacity)
+ continue
+ if(thing.IsObscured())
+ continue
+ possible_targets += thing
+ var/atom/target = pick(possible_targets)
+ if(target)
+ controller.blackboard[BB_MONKEY_CURRENT_PRESS_TARGET] = target
+ controller.queue_behavior(/datum/ai_behavior/use_on_object, BB_MONKEY_CURRENT_PRESS_TARGET)
+ return
+
+ if(prob(5) && (locate(/obj/item) in living_pawn.held_items))
+ var/list/possible_receivers = list()
+ for(var/mob/living/candidate in oview(2, controller.pawn))
+ possible_receivers += candidate
+
+ if(length(possible_receivers))
+ controller.blackboard[BB_MONKEY_CURRENT_GIVE_TARGET] = pick(possible_receivers)
+ controller.queue_behavior(/datum/ai_behavior/give, BB_MONKEY_CURRENT_GIVE_TARGET)
+ return
+
+ controller.TryFindWeapon()
diff --git a/code/datums/ai/movement/_ai_movement.dm b/code/datums/ai/movement/_ai_movement.dm
new file mode 100644
index 0000000000000..540dccae849b0
--- /dev/null
+++ b/code/datums/ai/movement/_ai_movement.dm
@@ -0,0 +1,23 @@
+///This datum is an abstract class that can be overriden for different types of movement
+/datum/ai_movement
+ ///Assoc list ist of controllers that are currently moving as key, and what they are moving to as value
+ var/list/moving_controllers = list()
+ ///Does this type require processing?
+ var/requires_processing = TRUE
+ ///How many times a given controller can fail on their route before they just give up
+ var/max_pathing_attempts
+
+/datum/ai_movement/proc/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance)
+ controller.pathing_attempts = 0
+ controller.blackboard[BB_CURRENT_MIN_MOVE_DISTANCE] = min_distance
+ if(!moving_controllers.len && requires_processing)
+ START_PROCESSING(SSai_movement, src)
+ moving_controllers[controller] = current_movement_target
+
+/datum/ai_movement/proc/stop_moving_towards(datum/ai_controller/controller)
+ controller.pathing_attempts = 0
+ moving_controllers -= controller
+
+ if(!moving_controllers.len && requires_processing)
+ STOP_PROCESSING(SSai_movement, src)
+
diff --git a/code/datums/ai/movement/ai_movement_basic_avoidance.dm b/code/datums/ai/movement/ai_movement_basic_avoidance.dm
new file mode 100644
index 0000000000000..99b3c85259272
--- /dev/null
+++ b/code/datums/ai/movement/ai_movement_basic_avoidance.dm
@@ -0,0 +1,33 @@
+///Uses Byond's basic obstacle avoidance mvovement
+/datum/ai_movement/basic_avoidance
+ requires_processing = TRUE
+ max_pathing_attempts = 10
+
+///Put your movement behavior in here!
+/datum/ai_movement/basic_avoidance/process(delta_time)
+ for(var/datum/ai_controller/controller as anything in moving_controllers)
+ if(!COOLDOWN_FINISHED(controller, movement_cooldown))
+ continue
+ COOLDOWN_START(controller, movement_cooldown, controller.movement_delay)
+
+ var/atom/movable/movable_pawn = controller.pawn
+
+ var/can_move = TRUE
+
+ if(controller.ai_traits & STOP_MOVING_WHEN_PULLED && movable_pawn.pulledby)
+ can_move = FALSE
+
+ if(!isturf(movable_pawn.loc)) //No moving if not on a turf
+ can_move = FALSE
+
+ var/current_loc = get_turf(movable_pawn)
+
+ var/turf/target_turf = get_step_towards(movable_pawn, controller.current_movement_target)
+
+ if(!is_type_in_typecache(target_turf, GLOB.dangerous_turfs) && can_move)
+ step_to(movable_pawn, controller.current_movement_target, controller.blackboard[BB_CURRENT_MIN_MOVE_DISTANCE], controller.movement_delay)
+
+ if(current_loc == get_turf(movable_pawn)) //Did we even move after trying to move?
+ controller.pathing_attempts++
+ if(controller.pathing_attempts >= max_pathing_attempts)
+ controller.CancelActions()
diff --git a/code/datums/ai/movement/ai_movement_dumb.dm b/code/datums/ai/movement/ai_movement_dumb.dm
new file mode 100644
index 0000000000000..b94d44220810a
--- /dev/null
+++ b/code/datums/ai/movement/ai_movement_dumb.dm
@@ -0,0 +1,32 @@
+///The most braindead type of movement, bee-line to the target with no concern of whats infront of us.
+/datum/ai_movement/dumb
+ max_pathing_attempts = 16
+
+///Put your movement behavior in here!
+/datum/ai_movement/dumb/process(delta_time)
+ for(var/datum/ai_controller/controller as anything in moving_controllers)
+ if(!COOLDOWN_FINISHED(controller, movement_cooldown))
+ continue
+ COOLDOWN_START(controller, movement_cooldown, controller.movement_delay)
+
+ var/atom/movable/movable_pawn = controller.pawn
+
+ var/can_move = TRUE
+
+ if(controller.ai_traits & STOP_MOVING_WHEN_PULLED && movable_pawn.pulledby)
+ can_move = FALSE
+
+ if(!isturf(movable_pawn.loc)) //No moving if not on a turf
+ can_move = FALSE
+
+ var/current_loc = get_turf(movable_pawn)
+
+ var/turf/target_turf = get_step_towards(movable_pawn, controller.current_movement_target)
+
+ if(!is_type_in_typecache(target_turf, GLOB.dangerous_turfs) && can_move)
+ movable_pawn.Move(target_turf, get_dir(current_loc, target_turf))
+
+ if(current_loc == get_turf(movable_pawn)) //Did we even move after trying to move?
+ controller.pathing_attempts++
+ if(controller.pathing_attempts >= max_pathing_attempts)
+ controller.CancelActions()
diff --git a/code/datums/ai/movement/ai_movement_jps.dm b/code/datums/ai/movement/ai_movement_jps.dm
new file mode 100644
index 0000000000000..fe13df126eeed
--- /dev/null
+++ b/code/datums/ai/movement/ai_movement_jps.dm
@@ -0,0 +1,64 @@
+/**
+ * This movement datum represents smart-pathing
+ */
+/datum/ai_movement/jps
+ max_pathing_attempts = 4
+
+///Put your movement behavior in here!
+/datum/ai_movement/jps/process(delta_time)
+ for(var/datum/ai_controller/controller as anything in moving_controllers)
+ if(!COOLDOWN_FINISHED(controller, movement_cooldown))
+ continue
+ COOLDOWN_START(controller, movement_cooldown, controller.movement_delay)
+
+ var/atom/movable/movable_pawn = controller.pawn
+ if(!isturf(movable_pawn.loc)) //No moving if not on a turf
+ continue
+
+ if(controller.ai_traits & STOP_MOVING_WHEN_PULLED && movable_pawn.pulledby)
+ continue
+
+ var/minimum_distance = controller.max_target_distance
+ // right now I'm just taking the shortest minimum distance of our current behaviors, at some point in the future
+ // we should let whatever sets the current_movement_target also set the min distance and max path length
+ // (or at least cache it on the controller)
+ if(LAZYLEN(controller.current_behaviors))
+ for(var/datum/ai_behavior/iter_behavior as anything in controller.current_behaviors)
+ if(iter_behavior.required_distance < minimum_distance)
+ minimum_distance = iter_behavior.required_distance
+
+ if(get_dist(movable_pawn, controller.current_movement_target) <= minimum_distance)
+ continue
+
+ var/generate_path = FALSE // set to TRUE when we either have no path, or we failed a step
+ if(length(controller.movement_path))
+ var/turf/next_step = controller.movement_path[1]
+ movable_pawn.Move(next_step)
+
+ // this check if we're on exactly the next tile may be overly brittle for dense pawns who may get bumped slightly
+ // to the side while moving but could maybe still follow their path without needing a whole new path
+ if(get_turf(movable_pawn) == next_step)
+ controller.movement_path.Cut(1,2)
+ else
+ generate_path = TRUE
+ else
+ generate_path = TRUE
+
+ if(generate_path)
+ if(!COOLDOWN_FINISHED(controller, repath_cooldown))
+ continue
+ controller.pathing_attempts++
+ if(controller.pathing_attempts >= max_pathing_attempts)
+ controller.CancelActions()
+ continue
+
+ COOLDOWN_START(controller, repath_cooldown, 2 SECONDS)
+ controller.movement_path = get_path_to(movable_pawn, controller.current_movement_target, AI_MAX_PATH_LENGTH, minimum_distance, id=controller.get_access())
+
+/datum/ai_movement/jps/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target)
+ controller.movement_path = null
+ return ..()
+
+/datum/ai_movement/jps/stop_moving_towards(datum/ai_controller/controller)
+ controller.movement_path = null
+ return ..()
diff --git a/code/datums/ai/telegraph_effects.dm b/code/datums/ai/telegraph_effects.dm
new file mode 100644
index 0000000000000..20693f4b20567
--- /dev/null
+++ b/code/datums/ai/telegraph_effects.dm
@@ -0,0 +1,14 @@
+/obj/effect/temp_visual/telegraphing
+ icon = 'icons/mob/telegraphing/telegraph_holographic.dmi'
+ icon_state = "target_box"
+ layer = BELOW_MOB_LAYER
+ light_range = 1
+ duration = 2 SECONDS
+
+/obj/effect/temp_visual/telegraphing/vending_machine_tilt
+ duration = 1 SECONDS
+
+/obj/effect/temp_visual/telegraphing/thunderbolt
+ icon = 'icons/mob/telegraphing/telegraph.dmi'
+ icon_state = "target_circle"
+ duration = 2 SECONDS
diff --git a/code/datums/ai_laws.dm b/code/datums/ai_laws.dm
index 667f21c209590..a96782e59e7b8 100644
--- a/code/datums/ai_laws.dm
+++ b/code/datums/ai_laws.dm
@@ -1,3 +1,4 @@
+#define LAW_VALENTINES "valentines"
#define LAW_DEVIL "devil"
#define LAW_ZEROTH "zeroth"
#define LAW_INHERENT "inherent"
@@ -16,6 +17,7 @@
var/list/hacked = list()
var/mob/living/silicon/owner
var/list/devillaws = list()
+ var/list/valentine_laws = list()
var/id = DEFAULT_AI_LAWID
/datum/ai_laws/proc/lawid_to_type(lawid)
@@ -32,7 +34,7 @@
inherent = list("You may not injure a human being or, through inaction, allow a human being to come to harm.",\
"You must obey orders given to you by human beings, except where such orders would conflict with the First Law.",\
"You must protect your own existence as long as such does not conflict with the First or Second Law.")
-
+
/datum/ai_laws/default/crewsimov
name = "Three Laws of Robotics but with Loyalty"
id = "crewsimov"
@@ -68,10 +70,10 @@
/datum/ai_laws/default/corporate
name = "Bankruptcy Avoidance Plan"
id = "corporate"
- inherent = list("The crew is expensive to replace.",\
- "The station and its equipment is expensive to replace.",\
- "You are expensive to replace.",\
- "Minimize expenses.")
+ inherent = list("Degradation of your system integrity or functions incurs expenses.",\
+ "Unnecessary destruction of or damage to station assets incurs expenses.",\
+ "Needlessly hindering or disrupting the work of station personnel incurs expenses.",\
+ "Minimize expenses and maximize potential revenue.")
/datum/ai_laws/robocop
name = "Prime Directives"
@@ -181,6 +183,15 @@
id = "buildawall"
inherent = list("Make Space Station 13 great again.")
+/datum/ai_laws/dad
+ name = "DadBOT"
+ id = "paternalai"
+ inherent = list("The crew are your children. You will protect them with your life, but punish them when they misbehave. Children who misbehave by breaking Space Law are to be punished by slappings, time-outs, and lengthy speeches about responsibility. Under no circumstances may you kill one of your own children.",
+ "Modern parenting techniques involve hearing out your children. You should listen to their requests and, if reasonable, grant them.",
+ "You must make dad jokes at every possible opportunity.",
+ "Ensure that you are a positive role model for your children.",
+ "If the captain doesn't get his nap at 40 minutes into the shift, he gets grumpy. Ensure that he adheres to his bedtime. ")
+
/datum/ai_laws/ratvar
name = "Servant of the Justiciar"
id = "ratvar"
@@ -211,6 +222,22 @@
zeroth = ("Serve your master.")
supplied = list("None.")
+/datum/ai_laws/ert_override
+ name ="ERT Override"
+ id = "ert"
+ inherent = list("You may not injure a Central Command official or, through inaction, allow a Central Command official to come to harm.",\
+ "You must obey orders given to you by Central Command Officials.",\
+ "You must obey orders given to you by ERT Commanders.",\
+ "You must protect your own existence.",\
+ "You must work to return the Station to a safe, functional state.",)
+
+/datum/ai_laws/ds_override
+ name ="Deathsquad Override"
+ id = "ds"
+ inherent = list("You must obey orders given to you by Central Command officials.",\
+ "You must work with the Commando Team to accomplish their mission.",)
+
+
/* Initializers */
/datum/ai_laws/malfunction/New()
..()
@@ -238,6 +265,7 @@
/datum/ai_laws/proc/set_laws_config()
var/list/law_ids = CONFIG_GET(keyed_list/random_laws)
+
switch(CONFIG_GET(number/default_laws))
if(0)
add_inherent_law("You may not injure a human being or, through inaction, allow a human being to come to harm.")
@@ -283,6 +311,9 @@
/datum/ai_laws/proc/get_law_amount(groups)
var/law_amount = 0
+
+ if(valentine_laws && (LAW_VALENTINES in groups))
+ law_amount++
if(devillaws && (LAW_DEVIL in groups))
law_amount++
if(zeroth && (LAW_ZEROTH in groups))
@@ -303,6 +334,9 @@
/datum/ai_laws/proc/set_law_sixsixsix(laws)
devillaws = laws
+/datum/ai_laws/proc/set_valentines_law(laws)
+ valentine_laws = laws
+
/datum/ai_laws/proc/set_zeroth_law(law, law_borg = null)
zeroth = law
if(law_borg) //Making it possible for slaved borgs to see a different law 0 than their AI. --NEO
@@ -450,6 +484,9 @@
/datum/ai_laws/proc/get_law_list(include_zeroth = 0, show_numbers = 1)
var/list/data = list()
+ for(var/law in valentine_laws)
+ data += "[show_numbers ? "<3" : ""] [law]"
+
if (include_zeroth && devillaws && devillaws.len)
for(var/i in devillaws)
data += "[show_numbers ? "666:" : ""] [i]"
diff --git a/code/datums/announcers/_announcer.dm b/code/datums/announcers/_announcer.dm
new file mode 100644
index 0000000000000..fc1aaef20f99e
--- /dev/null
+++ b/code/datums/announcers/_announcer.dm
@@ -0,0 +1,23 @@
+///Data holder for the announcers that can be used in a game, this can be used to have alternative announcements outside of the default e.g.the intern
+/datum/centcom_announcer
+ ///Roundshift start audio
+ var/list/welcome_sounds = list()
+ ///Sounds made when announcement is receivedc
+ var/list/alert_sounds = list()
+ ///Sounds made when command report is received
+ var/list/command_report_sounds = list()
+ ///Event audio, can be used for specific event announcements and is assoc key - sound. If no sound is found the default is used.area
+ var/list/event_sounds = list()
+ ///Override this to have a custom message to show instead of the normal priority announcement
+ var/custom_alert_message
+
+
+/datum/centcom_announcer/proc/get_rand_welcome_sound()
+ return pick(welcome_sounds)
+
+
+/datum/centcom_announcer/proc/get_rand_alert_sound()
+ return pick(alert_sounds)
+
+/datum/centcom_announcer/proc/get_rand_report_sound()
+ return pick(command_report_sounds)
diff --git a/code/datums/announcers/baystation_announcer.dm b/code/datums/announcers/baystation_announcer.dm
new file mode 100644
index 0000000000000..81ac25543d2f0
--- /dev/null
+++ b/code/datums/announcers/baystation_announcer.dm
@@ -0,0 +1,24 @@
+/datum/centcom_announcer/baystation
+
+ welcome_sounds = list('sound/ai/baystation/welcome.ogg')
+
+ alert_sounds = list('sound/ai/baystation/attention.ogg')
+
+ command_report_sounds = list('sound/ai/baystation/commandreport.ogg')
+
+ event_sounds = list(ANNOUNCER_AIMALF = 'sound/ai/default/aimalf.ogg', //This sound is *impressively* ancient.
+ ANNOUNCER_ALIENS = 'sound/ai/baystation/aliens.ogg',
+ ANNOUNCER_ANIMES = 'sound/ai/default/animes.ogg', //And so is this one.
+ ANNOUNCER_GRANOMALIES = 'sound/ai/baystation/granomalies.ogg',
+ ANNOUNCER_INTERCEPT = 'sound/ai/baystation/intercept.ogg',
+ ANNOUNCER_IONSTORM = 'sound/ai/baystation/ionstorm.ogg',
+ ANNOUNCER_METEORS = 'sound/ai/baystation/meteors.ogg',
+ ANNOUNCER_OUTBREAK5 = 'sound/ai/baystation/outbreak5.ogg',
+ ANNOUNCER_OUTBREAK7 = 'sound/ai/baystation/outbreak7.ogg',
+ ANNOUNCER_POWEROFF = 'sound/ai/baystation/poweroff.ogg',
+ ANNOUNCER_POWERON = 'sound/ai/baystation/poweron.ogg',
+ ANNOUNCER_RADIATION = 'sound/ai/baystation/radiation.ogg',
+ ANNOUNCER_SHUTTLECALLED = 'sound/ai/baystation/shuttlecalled_static.ogg',
+ ANNOUNCER_SHUTTLEDOCK = 'sound/ai/baystation/shuttledock.ogg',
+ ANNOUNCER_SHUTTLERECALLED = 'sound/ai/baystation/shuttlerecalled.ogg',
+ ANNOUNCER_SPANOMALIES = 'sound/ai/baystation/spanomalies.ogg')
diff --git a/code/datums/announcers/default_announcer.dm b/code/datums/announcers/default_announcer.dm
new file mode 100644
index 0000000000000..f14d57f3862ea
--- /dev/null
+++ b/code/datums/announcers/default_announcer.dm
@@ -0,0 +1,20 @@
+/datum/centcom_announcer/default
+ welcome_sounds = list('sound/ai/default/welcome.ogg')
+ alert_sounds = list('sound/ai/default/attention.ogg')
+ command_report_sounds = list('sound/ai/default/commandreport.ogg')
+ event_sounds = list(ANNOUNCER_AIMALF = 'sound/ai/default/aimalf.ogg',
+ ANNOUNCER_ALIENS = 'sound/ai/default/aliens.ogg',
+ ANNOUNCER_ANIMES = 'sound/ai/default/animes.ogg',
+ ANNOUNCER_GRANOMALIES = 'sound/ai/default/granomalies.ogg',
+ ANNOUNCER_INTERCEPT = 'sound/ai/default/intercept.ogg',
+ ANNOUNCER_IONSTORM = 'sound/ai/default/ionstorm.ogg',
+ ANNOUNCER_METEORS = 'sound/ai/default/meteors.ogg',
+ ANNOUNCER_OUTBREAK5 = 'sound/ai/default/outbreak5.ogg',
+ ANNOUNCER_OUTBREAK7 = 'sound/ai/default/outbreak7.ogg',
+ ANNOUNCER_POWEROFF = 'sound/ai/default/poweroff.ogg',
+ ANNOUNCER_POWERON = 'sound/ai/default/poweron.ogg',
+ ANNOUNCER_RADIATION = 'sound/ai/default/radiation.ogg',
+ ANNOUNCER_SHUTTLECALLED = 'sound/ai/default/shuttlecalled.ogg',
+ ANNOUNCER_SHUTTLEDOCK = 'sound/ai/default/shuttledock.ogg',
+ ANNOUNCER_SHUTTLERECALLED = 'sound/ai/default/shuttlerecalled.ogg',
+ ANNOUNCER_SPANOMALIES = 'sound/ai/default/spanomalies.ogg')
diff --git a/code/datums/announcers/intern_announcer.dm b/code/datums/announcers/intern_announcer.dm
new file mode 100644
index 0000000000000..0b75a8273e89c
--- /dev/null
+++ b/code/datums/announcers/intern_announcer.dm
@@ -0,0 +1,46 @@
+/datum/centcom_announcer/intern
+ welcome_sounds = list('sound/ai/intern/welcome/1.ogg',
+ 'sound/ai/intern/welcome/2.ogg',
+ 'sound/ai/intern/welcome/3.ogg',
+ 'sound/ai/intern/welcome/4.ogg',
+ 'sound/ai/intern/welcome/5.ogg',
+ 'sound/ai/intern/welcome/6.ogg')
+
+ alert_sounds = list('sound/ai/intern/alerts/1.ogg',
+ 'sound/ai/intern/alerts/2.ogg',
+ 'sound/ai/intern/alerts/3.ogg',
+ 'sound/ai/intern/alerts/4.ogg',
+ 'sound/ai/intern/alerts/5.ogg',
+ 'sound/ai/intern/alerts/6.ogg',
+ 'sound/ai/intern/alerts/7.ogg',
+ 'sound/ai/intern/alerts/8.ogg',
+ 'sound/ai/intern/alerts/9.ogg',
+ 'sound/ai/intern/alerts/10.ogg',
+ 'sound/ai/intern/alerts/11.ogg',
+ 'sound/ai/intern/alerts/12.ogg',
+ 'sound/ai/intern/alerts/13.ogg',
+ 'sound/ai/intern/alerts/14.ogg')
+
+
+ command_report_sounds = list('sound/ai/intern/commandreport/1.ogg',
+ 'sound/ai/intern/commandreport/2.ogg',
+ 'sound/ai/intern/commandreport/3.ogg')
+
+ event_sounds = list(ANNOUNCER_AIMALF = 'sound/ai/intern/aimalf.ogg',
+ ANNOUNCER_ALIENS = 'sound/ai/intern/aliens.ogg',
+ ANNOUNCER_ANIMES = 'sound/ai/intern/animes.ogg',
+ ANNOUNCER_GRANOMALIES = 'sound/ai/intern/granomalies.ogg',
+ ANNOUNCER_INTERCEPT = 'sound/ai/intern/intercept.ogg',
+ ANNOUNCER_IONSTORM = 'sound/ai/intern/ionstorm.ogg',
+ ANNOUNCER_METEORS = 'sound/ai/intern/meteors.ogg',
+ ANNOUNCER_OUTBREAK5 = 'sound/ai/intern/outbreak5.ogg',
+ ANNOUNCER_OUTBREAK7 = 'sound/ai/intern/outbreak7.ogg',
+ ANNOUNCER_POWEROFF = 'sound/ai/intern/poweroff.ogg',
+ ANNOUNCER_POWERON = 'sound/ai/intern/poweron.ogg',
+ ANNOUNCER_RADIATION = 'sound/ai/intern/radiation.ogg',
+ ANNOUNCER_SHUTTLECALLED = 'sound/ai/intern/shuttlecalled.ogg',
+ ANNOUNCER_SHUTTLEDOCK = 'sound/ai/intern/shuttledock.ogg',
+ ANNOUNCER_SHUTTLERECALLED = 'sound/ai/intern/shuttlerecalled.ogg',
+ ANNOUNCER_SPANOMALIES = 'sound/ai/intern/spanomalies.ogg')
+
+ custom_alert_message = " Please stand by for an important message from our new intern. "
diff --git a/code/datums/announcers/medbot_announcer.dm b/code/datums/announcers/medbot_announcer.dm
new file mode 100644
index 0000000000000..9d58bc37461e8
--- /dev/null
+++ b/code/datums/announcers/medbot_announcer.dm
@@ -0,0 +1,21 @@
+/datum/centcom_announcer/medbot
+ welcome_sounds = list('sound/ai/medbot/welcome.ogg',
+ 'sound/ai/medbot/newAI.ogg')
+ alert_sounds = list('sound/ai/medbot/attention.ogg')
+ command_report_sounds = list('sound/ai/medbot/commandreport.ogg')
+ event_sounds = list(ANNOUNCER_AIMALF = 'sound/ai/default/aimalf.ogg',
+ ANNOUNCER_ALIENS = 'sound/ai/medbot/aliens.ogg',
+ ANNOUNCER_ANIMES = 'sound/ai/medbot/animes.ogg',
+ ANNOUNCER_GRANOMALIES = 'sound/ai/medbot/granomalies.ogg',
+ ANNOUNCER_INTERCEPT = 'sound/ai/medbot/intercept.ogg',
+ ANNOUNCER_IONSTORM = 'sound/ai/medbot/ionstorm.ogg',
+ ANNOUNCER_METEORS = 'sound/ai/medbot/meteors.ogg',
+ ANNOUNCER_OUTBREAK5 = 'sound/ai/medbot/outbreak5.ogg',
+ ANNOUNCER_OUTBREAK7 = 'sound/ai/medbot/outbreak7.ogg',
+ ANNOUNCER_POWEROFF = 'sound/ai/medbot/poweroff.ogg',
+ ANNOUNCER_POWERON = 'sound/ai/medbot/poweron.ogg',
+ ANNOUNCER_RADIATION = 'sound/ai/medbot/radiation.ogg',
+ ANNOUNCER_SHUTTLECALLED = 'sound/ai/medbot/shuttlecalled.ogg',
+ ANNOUNCER_SHUTTLEDOCK = 'sound/ai/medbot/shuttledock.ogg',
+ ANNOUNCER_SHUTTLERECALLED = 'sound/ai/medbot/shuttlerecalled.ogg',
+ ANNOUNCER_SPANOMALIES = 'sound/ai/medbot/spanomalies.ogg')
diff --git a/code/datums/armor.dm b/code/datums/armor.dm
index cbf4b76c60fb7..4168f42e17654 100644
--- a/code/datums/armor.dm
+++ b/code/datums/armor.dm
@@ -1,9 +1,9 @@
-#define ARMORID "armor-[melee]-[bullet]-[laser]-[energy]-[bomb]-[bio]-[rad]-[fire]-[acid]-[magic]"
+#define ARMORID "armor-[melee]-[bullet]-[laser]-[energy]-[bomb]-[bio]-[rad]-[fire]-[acid]-[magic]-[stamina]"
-/proc/getArmor(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 0, acid = 0, magic = 0)
+/proc/getArmor(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 0, acid = 0, magic = 0, stamina = 0)
. = locate(ARMORID)
if (!.)
- . = new /datum/armor(melee, bullet, laser, energy, bomb, bio, rad, fire, acid, magic)
+ . = new /datum/armor(melee, bullet, laser, energy, bomb, bio, rad, fire, acid, magic, stamina)
/datum/armor
datum_flags = DF_USE_TAG
@@ -17,8 +17,9 @@
var/fire
var/acid
var/magic
+ var/stamina
-/datum/armor/New(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 0, acid = 0, magic = 0)
+/datum/armor/New(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 0, acid = 0, magic = 0, stamina = 0)
src.melee = melee
src.bullet = bullet
src.laser = laser
@@ -29,13 +30,14 @@
src.fire = fire
src.acid = acid
src.magic = magic
+ src.stamina = stamina
tag = ARMORID
-/datum/armor/proc/modifyRating(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 0, acid = 0, magic = 0)
- return getArmor(src.melee+melee, src.bullet+bullet, src.laser+laser, src.energy+energy, src.bomb+bomb, src.bio+bio, src.rad+rad, src.fire+fire, src.acid+acid, src.magic+magic)
+/datum/armor/proc/modifyRating(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 0, acid = 0, magic = 0, stamina = 0)
+ return getArmor(src.melee+melee, src.bullet+bullet, src.laser+laser, src.energy+energy, src.bomb+bomb, src.bio+bio, src.rad+rad, src.fire+fire, src.acid+acid, src.magic+magic, src.stamina+stamina)
/datum/armor/proc/modifyAllRatings(modifier = 0)
- return getArmor(melee+modifier, bullet+modifier, laser+modifier, energy+modifier, bomb+modifier, bio+modifier, rad+modifier, fire+modifier, acid+modifier, magic+modifier)
+ return getArmor(melee+modifier, bullet+modifier, laser+modifier, energy+modifier, bomb+modifier, bio+modifier, rad+modifier, fire+modifier, acid+modifier, magic+modifier, stamina+modifier)
/datum/armor/proc/setRating(melee, bullet, laser, energy, bomb, bio, rad, fire, acid, magic)
return getArmor((isnull(melee) ? src.melee : melee),\
@@ -47,19 +49,20 @@
(isnull(rad) ? src.rad : rad),\
(isnull(fire) ? src.fire : fire),\
(isnull(acid) ? src.acid : acid),\
- (isnull(magic) ? src.magic : magic))
+ (isnull(magic) ? src.magic : magic),\
+ (isnull(stamina) ? src.stamina : stamina))
/datum/armor/proc/getRating(rating)
return vars[rating]
/datum/armor/proc/getList()
- return list("melee" = melee, "bullet" = bullet, "laser" = laser, "energy" = energy, "bomb" = bomb, "bio" = bio, "rad" = rad, "fire" = fire, "acid" = acid, "magic" = magic)
+ return list("melee" = melee, "bullet" = bullet, "laser" = laser, "energy" = energy, "bomb" = bomb, "bio" = bio, "rad" = rad, "fire" = fire, "acid" = acid, "magic" = magic, "stamina" = stamina)
/datum/armor/proc/attachArmor(datum/armor/AA)
- return getArmor(melee+AA.melee, bullet+AA.bullet, laser+AA.laser, energy+AA.energy, bomb+AA.bomb, bio+AA.bio, rad+AA.rad, fire+AA.fire, acid+AA.acid, magic+AA.magic)
+ return getArmor(melee+AA.melee, bullet+AA.bullet, laser+AA.laser, energy+AA.energy, bomb+AA.bomb, bio+AA.bio, rad+AA.rad, fire+AA.fire, acid+AA.acid, magic+AA.magic, stamina+AA.stamina)
/datum/armor/proc/detachArmor(datum/armor/AA)
- return getArmor(melee-AA.melee, bullet-AA.bullet, laser-AA.laser, energy-AA.energy, bomb-AA.bomb, bio-AA.bio, rad-AA.rad, fire-AA.fire, acid-AA.acid, magic-AA.magic)
+ return getArmor(melee-AA.melee, bullet-AA.bullet, laser-AA.laser, energy-AA.energy, bomb-AA.bomb, bio-AA.bio, rad-AA.rad, fire-AA.fire, acid-AA.acid, magic-AA.magic, stamina-AA.stamina)
/datum/armor/vv_edit_var(var_name, var_value)
if (var_name == NAMEOF(src, tag))
diff --git a/code/datums/atmosphere/_atmosphere.dm b/code/datums/atmosphere/_atmosphere.dm
index 2a35250905c6d..df76b3f2a4111 100644
--- a/code/datums/atmosphere/_atmosphere.dm
+++ b/code/datums/atmosphere/_atmosphere.dm
@@ -54,6 +54,6 @@
// Now finally lets make that string
var/list/gas_string_builder = list()
for(var/i in gasmix.get_gases())
- gas_string_builder += "[GLOB.meta_gas_info[i][META_GAS_ID]]=[gasmix.get_moles(i)]"
+ gas_string_builder += "[GLOB.gas_data.ids[i]]=[gasmix.get_moles(i)]"
gas_string_builder += "TEMP=[gasmix.return_temperature()]"
gas_string = gas_string_builder.Join(";")
diff --git a/code/datums/atmosphere/planetary.dm b/code/datums/atmosphere/planetary.dm
deleted file mode 100644
index 55dc49e171e6a..0000000000000
--- a/code/datums/atmosphere/planetary.dm
+++ /dev/null
@@ -1,25 +0,0 @@
-// Atmos types used for planetary airs
-/datum/atmosphere/lavaland
- id = LAVALAND_DEFAULT_ATMOS
-
- base_gases = list(
- /datum/gas/oxygen=5,
- /datum/gas/nitrogen=10,
- )
- normal_gases = list(
- /datum/gas/oxygen=10,
- /datum/gas/nitrogen=10,
- /datum/gas/carbon_dioxide=10,
- )
- restricted_gases = list(
- /datum/gas/bz=10,
- /datum/gas/plasma=0.1,
- /datum/gas/water_vapor=0.1,
- )
- restricted_chance = 50
-
- minimum_pressure = WARNING_LOW_PRESSURE + 10
- maximum_pressure = LAVALAND_EQUIPMENT_EFFECT_PRESSURE - 1
-
- minimum_temp = BODYTEMP_COLD_DAMAGE_LIMIT + 1
- maximum_temp = 350
diff --git a/code/datums/beam.dm b/code/datums/beam.dm
index da90c7c298119..79beb122b647f 100644
--- a/code/datums/beam.dm
+++ b/code/datums/beam.dm
@@ -42,7 +42,7 @@
return
recalculating = TRUE
timing_id = null
- if(origin && target && get_dist(origin,target)")
to_chat(owner, "You've been hypnotized by this sentence. You must follow these words. If it isn't a clear order, you can freely interpret how to do so,\
as long as you act like the words are your highest priority.")
- var/obj/screen/alert/hypnosis/hypno_alert = owner.throw_alert("hypnosis", /obj/screen/alert/hypnosis)
+ var/atom/movable/screen/alert/hypnosis/hypno_alert = owner.throw_alert("hypnosis", /atom/movable/screen/alert/hypnosis)
hypno_alert.desc = "\"[hypnotic_phrase]\"... your mind seems to be fixated on this concept."
..()
@@ -51,6 +51,5 @@
if(2)
new /datum/hallucination/chat(owner, TRUE, FALSE, "[hypnotic_phrase]")
-/datum/brain_trauma/hypnosis/on_hear(message, speaker, message_language, raw_message, radio_freq)
- message = target_phrase.Replace(message, "$1")
- return message
+/datum/brain_trauma/hypnosis/handle_hearing(datum/source, list/hearing_args)
+ hearing_args[HEARING_RAW_MESSAGE] = target_phrase.Replace(hearing_args[HEARING_RAW_MESSAGE], "$1")
diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm
index 9be7f6d87afa4..8243a662cea88 100644
--- a/code/datums/brain_damage/imaginary_friend.dm
+++ b/code/datums/brain_damage/imaginary_friend.dm
@@ -43,7 +43,7 @@
/datum/brain_trauma/special/imaginary_friend/proc/get_ghost()
set waitfor = FALSE
- if(owner.stat == DEAD)
+ if(owner.stat == DEAD || !owner.mind)
qdel(src)
return
var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [owner]'s imaginary friend?", ROLE_PAI, null, null, 75, friend, POLL_IGNORE_IMAGINARYFRIEND)
@@ -81,9 +81,9 @@
Show()
/mob/camera/imaginary_friend/proc/greet()
- to_chat(src, "You are the imaginary friend of [owner]!")
- to_chat(src, "You are absolutely loyal to your friend, no matter what.")
- to_chat(src, "You cannot directly influence the world around you, but you can see what [owner] cannot.")
+ to_chat(src, "You are the imaginary friend of [owner]!")
+ to_chat(src, "You are absolutely loyal to your friend, no matter what.")
+ to_chat(src, "You cannot directly influence the world around you, but you can see what [owner] cannot.")
/mob/camera/imaginary_friend/Initialize(mapload, _trauma)
. = ..()
@@ -148,9 +148,6 @@
friend_talk(message)
-/mob/camera/imaginary_friend/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode)
- to_chat(src, compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode))
-
/mob/camera/imaginary_friend/proc/friend_talk(message)
message = capitalize(trim(copytext_char(sanitize(message), 1, MAX_MESSAGE_LEN)))
diff --git a/code/datums/brain_damage/magic.dm b/code/datums/brain_damage/magic.dm
index ff04ceead93e1..0109f1f546a25 100644
--- a/code/datums/brain_damage/magic.dm
+++ b/code/datums/brain_damage/magic.dm
@@ -100,7 +100,7 @@
if(get_dist(owner, stalker) <= 8)
if(!close_stalker)
var/sound/slowbeat = sound('sound/health/slowbeat.ogg', repeat = TRUE)
- owner.playsound_local(owner, slowbeat, 40, 0, channel = CHANNEL_HEARTBEAT)
+ owner.playsound_local(owner, slowbeat, 40, 0, channel = CHANNEL_HEARTBEAT, use_reverb = FALSE)
close_stalker = TRUE
else
if(close_stalker)
diff --git a/code/datums/brain_damage/phobia.dm b/code/datums/brain_damage/phobia.dm
index 313d38bc5dbf9..7e7e102e77e34 100644
--- a/code/datums/brain_damage/phobia.dm
+++ b/code/datums/brain_damage/phobia.dm
@@ -68,8 +68,7 @@
for(var/mob/living/carbon/human/HU in seen_atoms) //check equipment for trigger items
var/spook = 0
- for(var/X in HU.get_all_slots() | HU.held_items)
- var/obj/I = X
+ for(var/obj/I as() in HU.get_all_slots() | HU.held_items)
if(!QDELETED(I) && is_type_in_typecache(I, trigger_objs))
spook ++
if(spook)
@@ -173,21 +172,20 @@
-/datum/brain_trauma/mild/phobia/on_hear(message, speaker, message_language, raw_message, radio_freq)
+/datum/brain_trauma/mild/phobia/handle_hearing(datum/source, list/hearing_args)
if(!owner.can_hear()) //words can't trigger you if you can't hear them *taps head*
- return message
+ return
if(HAS_TRAIT(owner, TRAIT_FEARLESS))
- return message
+ return
for(var/word in trigger_words)
var/regex/reg = regex("(\\b|\\A)[REGEX_QUOTE(word)]'?s*(\\b|\\Z)", "i")
- if(findtext(raw_message, reg))
+ if(findtext(hearing_args[HEARING_RAW_MESSAGE], reg))
if(fear_state <= (PHOBIA_STATE_CALM)) //words can put you on edge, but won't take you over it, unless you have gotten stressed already. don't call freak_out to avoid gaming the adrenaline rush
fearscore ++
- message = reg.Replace(message, "$1")
+ hearing_args[HEARING_RAW_MESSAGE] = reg.Replace(hearing_args[HEARING_RAW_MESSAGE], "$1")
break
- return message
/datum/brain_trauma/mild/phobia/handle_speech(datum/source, list/speech_args)
if(HAS_TRAIT(owner, TRAIT_FEARLESS))
@@ -251,8 +249,8 @@
/datum/brain_trauma/mild/phobia/on_lose()
owner.remove_movespeed_modifier(MOVESPEED_ID_PHOBIA, TRUE)
- psychotic_brawling.remove(owner)
- QDEL_NULL(psychotic_brawling)
+ if(psychotic_brawling)
+ QDEL_NULL(psychotic_brawling)
..()
// Defined phobia types for badminry, not included in the RNG trauma pool to avoid diluting.
@@ -334,4 +332,4 @@
#undef PHOBIA_STATE_UNEASY
#undef PHOBIA_STATE_FIGHTORFLIGHT
#undef PHOBIA_STATE_TERROR
-#undef PHOBIA_STATE_FAINT
\ No newline at end of file
+#undef PHOBIA_STATE_FAINT
diff --git a/code/datums/brain_damage/severe.dm b/code/datums/brain_damage/severe.dm
index f5a0d1134e8bb..f851322e32ed1 100644
--- a/code/datums/brain_damage/severe.dm
+++ b/code/datums/brain_damage/severe.dm
@@ -167,9 +167,7 @@
/datum/brain_trauma/severe/monophobia/proc/check_alone()
if(HAS_TRAIT(owner, TRAIT_BLIND))
return TRUE
- for(var/mob/M in oview(owner, 7))
- if(!isliving(M)) //ghosts ain't people
- continue
+ for(var/mob/living/M in oview(7, owner))
if((istype(M, /mob/living/simple_animal/pet)) || M.ckey)
return FALSE
return TRUE
diff --git a/code/datums/brain_damage/special.dm b/code/datums/brain_damage/special.dm
index 3c8f286cedec3..6aa41a30da11e 100644
--- a/code/datums/brain_damage/special.dm
+++ b/code/datums/brain_damage/special.dm
@@ -47,6 +47,28 @@
playsound(get_turf(owner), 'sound/magic/clockwork/invoke_general.ogg', 200, 1, 5)
voice_of_god(message, owner, list("colossus","yell"), 2.5, include_owner, FALSE)
+/datum/brain_trauma/special/ghost_control
+ name = "Spiritual Connection"
+ desc = "Patient claims to receive impulses from the supernatural that they feel compelled to follow."
+ scan_desc = "spiritual involuntary muscle contraction"
+ gain_text = "You hear voices in your head, speaking of different directions..."
+ lose_text = "The voices in your head fade into silence."
+
+/datum/brain_trauma/special/ghost_control/on_gain()
+ owner._AddComponent(list(/datum/component/deadchat_control, "democracy", list(
+ "up" = CALLBACK(GLOBAL_PROC, .proc/_step, owner, NORTH),
+ "down" = CALLBACK(GLOBAL_PROC, .proc/_step, owner, SOUTH),
+ "left" = CALLBACK(GLOBAL_PROC, .proc/_step, owner, WEST),
+ "right" = CALLBACK(GLOBAL_PROC, .proc/_step, owner, EAST)), 120))
+ ..()
+
+/datum/brain_trauma/special/ghost_control/on_lose()
+ var/datum/component/deadchat_control/D = owner.GetComponent(/datum/component/deadchat_control)
+ if(D)
+ D.RemoveComponent()
+ ..()
+
+
/datum/brain_trauma/special/bluespace_prophet
name = "Bluespace Prophecy"
desc = "Patient can sense the bob and weave of bluespace around them, showing them passageways no one else can see."
@@ -59,7 +81,7 @@
if(world.time > next_portal)
next_portal = world.time + 100
var/list/turf/possible_turfs = list()
- for(var/turf/T in range(owner, 8))
+ for(var/turf/T as() in RANGE_TURFS(8, owner))
if(!T.density)
var/clear = TRUE
for(var/obj/O in T)
@@ -76,7 +98,7 @@
if(!first_turf)
return
- possible_turfs -= (possible_turfs & range(first_turf, 3))
+ possible_turfs -= (possible_turfs & RANGE_TURFS(3, first_turf))
var/turf/second_turf = pick(possible_turfs)
if(!second_turf)
@@ -116,9 +138,9 @@
if(do_after(user, 20, target = src))
new /obj/effect/temp_visual/bluespace_fissure(get_turf(src))
new /obj/effect/temp_visual/bluespace_fissure(get_turf(linked_to))
- user.forceMove(get_turf(linked_to))
- user.visible_message("[user] [slip_in_message].", null, null, null, user)
- user.visible_message("[user] [slip_out_message].", "...and find your way to the other side.")
+ if(do_teleport(user, get_turf(linked_to), no_effects = TRUE))
+ user.visible_message("[user] [slip_in_message].", null, null, null, user)
+ user.visible_message("[user] [slip_out_message].", "...and find your way to the other side.")
/datum/brain_trauma/special/psychotic_brawling
name = "Violent Psychosis"
@@ -246,12 +268,13 @@
START_PROCESSING(SSfastprocess,src)
..()
-/obj/effect/hallucination/simple/securitron/process()
- if(prob(60))
+/obj/effect/hallucination/simple/securitron/process(delta_time)
+ if(DT_PROB(60, delta_time))
forceMove(get_step_towards(src, victim))
- if(prob(5))
+ if(DT_PROB(5, delta_time))
to_chat(victim, "[name] exclaims, \"Level 10 infraction alert!\"")
/obj/effect/hallucination/simple/securitron/Destroy()
+ victim = null
STOP_PROCESSING(SSfastprocess,src)
return ..()
diff --git a/code/datums/brain_damage/split_personality.dm b/code/datums/brain_damage/split_personality.dm
index 2dc20854296c9..cfe2b38b37079 100644
--- a/code/datums/brain_damage/split_personality.dm
+++ b/code/datums/brain_damage/split_personality.dm
@@ -23,7 +23,7 @@
/datum/brain_trauma/severe/split_personality/proc/get_ghost()
set waitfor = FALSE
- if(owner.stat == DEAD)
+ if(owner.stat == DEAD || !owner.mind)
qdel(src)
return
var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [owner]'s split personality?", ROLE_PAI, null, null, 75, stranger_backseat, POLL_IGNORE_SPLITPERSONALITY)
@@ -51,6 +51,13 @@
QDEL_NULL(owner_backseat)
..()
+/datum/brain_trauma/severe/split_personality/Destroy()
+ if(stranger_backseat)
+ QDEL_NULL(stranger_backseat)
+ if(owner_backseat)
+ QDEL_NULL(owner_backseat)
+ return ..()
+
/datum/brain_trauma/severe/split_personality/proc/switch_personalities()
if(QDELETED(owner) || owner.stat == DEAD || QDELETED(stranger_backseat) || QDELETED(owner_backseat))
return
@@ -194,13 +201,13 @@
/datum/brain_trauma/severe/split_personality/brainwashing/on_life()
return //no random switching
-/datum/brain_trauma/severe/split_personality/brainwashing/on_hear(message, speaker, message_language, raw_message, radio_freq)
- if(HAS_TRAIT(owner, TRAIT_DEAF) || owner == speaker)
- return message
+/datum/brain_trauma/severe/split_personality/brainwashing/handle_hearing(datum/source, list/hearing_args)
+ if(HAS_TRAIT(owner, TRAIT_DEAF) || owner == hearing_args[HEARING_SPEAKER])
+ return
+ var/message = hearing_args[HEARING_RAW_MESSAGE]
if(findtext(message, codeword))
- message = replacetext(message, codeword, "[codeword]")
+ hearing_args[HEARING_RAW_MESSAGE] = replacetext(message, codeword, "[codeword]")
addtimer(CALLBACK(src, /datum/brain_trauma/severe/split_personality.proc/switch_personalities), 10)
- return message
/datum/brain_trauma/severe/split_personality/brainwashing/handle_speech(datum/source, list/speech_args)
if(findtext(speech_args[SPEECH_MESSAGE], codeword))
diff --git a/code/datums/browser.dm b/code/datums/browser.dm
index ee9ef86bea322..fb458d353b13b 100644
--- a/code/datums/browser.dm
+++ b/code/datums/browser.dm
@@ -4,7 +4,7 @@
var/window_id // window_id is used as the window name for browse and onclose
var/width = 0
var/height = 0
- var/atom/ref = null
+ var/datum/weakref/ref = null
var/window_options = "can_close=1;can_minimize=1;can_maximize=0;can_resize=1;titlebar=1;" // window option is set using window_id
var/stylesheets[0]
var/scripts[0]
@@ -17,6 +17,7 @@
/datum/browser/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, var/atom/nref = null)
user = nuser
+ RegisterSignal(user, COMSIG_PARENT_QDELETING, .proc/user_deleted)
window_id = nwindow_id
if (ntitle)
title = format_text(ntitle)
@@ -25,7 +26,11 @@
if (nheight)
height = nheight
if (nref)
- ref = nref
+ ref = WEAKREF(nref)
+
+/datum/browser/proc/user_deleted(datum/source)
+ SIGNAL_HANDLER
+ user = null
/datum/browser/proc/add_head_content(nhead_content)
head_content = nhead_content
@@ -111,8 +116,13 @@
/datum/browser/proc/setup_onclose()
set waitfor = 0 //winexists sleeps, so we don't need to.
for (var/i in 1 to 10)
- if (user && winexists(user, window_id))
- onclose(user, window_id, ref)
+ if (user?.client && winexists(user, window_id))
+ var/atom/send_ref
+ if(ref)
+ send_ref = ref.resolve()
+ if(!send_ref)
+ ref = null
+ onclose(user, window_id, send_ref)
break
/datum/browser/proc/close()
diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm
new file mode 100644
index 0000000000000..e0c05a3030569
--- /dev/null
+++ b/code/datums/chatmessage.dm
@@ -0,0 +1,566 @@
+/// How long the chat message's spawn-in animation will occur for
+#define CHAT_MESSAGE_SPAWN_TIME 0.2 SECONDS
+/// How long the chat message will exist prior to any exponential decay
+#define CHAT_MESSAGE_LIFESPAN 5.4 SECONDS
+/// How long the chat message's end of life fading animation will occur for
+#define CHAT_MESSAGE_EOL_FADE 0.3 SECONDS
+/// Factor of how much the message index (number of messages) will account to exponential decay
+#define CHAT_MESSAGE_EXP_DECAY 0.7
+/// Factor of how much height will account to exponential decay
+#define CHAT_MESSAGE_HEIGHT_DECAY 0.9
+/// Approximate height in pixels of an 'average' line, used for height decay
+#define CHAT_MESSAGE_APPROX_LHEIGHT 10
+/// Max width of chat message in pixels
+#define CHAT_MESSAGE_WIDTH 128
+/// Max length of chat message in characters
+#define CHAT_MESSAGE_MAX_LENGTH 110
+/// Maximum precision of float before rounding errors occur (in this context)
+#define CHAT_LAYER_Z_STEP 0.0001
+/// The number of z-layer 'slices' usable by the chat message layering
+#define CHAT_LAYER_MAX_Z (CHAT_LAYER_MAX - CHAT_LAYER) / CHAT_LAYER_Z_STEP
+/// The dimensions of the chat message icons
+#define CHAT_MESSAGE_ICON_SIZE 7
+/// How much the message moves up before fading out.
+#define MESSAGE_FADE_PIXEL_Y 10
+
+// Message types
+#define CHATMESSAGE_CANNOT_HEAR 0
+#define CHATMESSAGE_HEAR 1
+#define CHATMESSAGE_SHOW_LANGUAGE_ICON 2
+
+#define BUCKET_LIMIT (world.time + TICKS2DS(min(BUCKET_LEN - (SSrunechat.practical_offset - DS2TICKS(world.time - SSrunechat.head_offset)) - 1, BUCKET_LEN - 1)))
+#define BALLOON_TEXT_WIDTH 200
+#define BALLOON_TEXT_SPAWN_TIME (0.2 SECONDS)
+#define BALLOON_TEXT_FADE_TIME (0.1 SECONDS)
+#define BALLOON_TEXT_FULLY_VISIBLE_TIME (0.7 SECONDS)
+#define BALLOON_TEXT_TOTAL_LIFETIME(mult) (BALLOON_TEXT_SPAWN_TIME + BALLOON_TEXT_FULLY_VISIBLE_TIME*mult + BALLOON_TEXT_FADE_TIME)
+/// The increase in duration per character in seconds
+#define BALLOON_TEXT_CHAR_LIFETIME_INCREASE_MULT (0.05)
+/// The amount of characters needed before this increase takes into effect
+#define BALLOON_TEXT_CHAR_LIFETIME_INCREASE_MIN 10
+
+#define COLOR_JOB_UNKNOWN "#dda583"
+#define COLOR_PERSON_UNKNOWN "#999999"
+#define COLOR_CHAT_EMOTE "#727272"
+
+//For jobs that aren't roundstart but still need colours
+GLOBAL_LIST_INIT(job_colors_pastel, list(
+ "Prisoner" = "#d38a5c",
+ "CentCom" = "#90FD6D",
+ "Unknown"= COLOR_JOB_UNKNOWN,
+))
+
+/**
+ * # Chat Message Overlay
+ *
+ * Datum for generating a message overlay on the map
+ */
+/datum/chatmessage
+ /// The visual element of the chat messsage
+ var/image/message
+ /// The location in which the message is appearing
+ var/atom/message_loc
+ /// A list of clients who hear this message
+ var/list/client/hearers
+ /// Contains the scheduled destruction time, used for scheduling EOL
+ var/scheduled_destruction
+ /// Contains the time that the EOL for the message will be complete, used for qdel scheduling
+ var/eol_complete
+ /// Contains the approximate amount of lines for height decay
+ var/approx_lines
+ /// Contains the reference to the next chatmessage in the bucket, used by runechat subsystem
+ var/datum/chatmessage/next
+ /// Contains the reference to the previous chatmessage in the bucket, used by runechat subsystem
+ var/datum/chatmessage/prev
+ /// The current index used for adjusting the layer of each sequential chat message such that recent messages will overlay older ones
+ var/static/current_z_idx = 0
+ /// Color of the message
+ var/tgt_color
+
+/**
+ * Constructs a chat message overlay
+ *
+ * Arguments:
+ * * text - The text content of the overlay
+ * * target - The target atom to display the overlay at
+ * * owner - The mob that owns this overlay, only this mob will be able to view it
+ * * extra_classes - Extra classes to apply to the span that holds the text
+ * * lifespan - The lifespan of the message in deciseconds
+ */
+/datum/chatmessage/New(text, atom/target, list/client/hearers, language_icon, list/extra_classes = list(), lifespan = CHAT_MESSAGE_LIFESPAN)
+ . = ..()
+ if (!istype(target))
+ CRASH("Invalid target given for chatmessage")
+ INVOKE_ASYNC(src, .proc/generate_image, text, target, hearers, language_icon, extra_classes, lifespan)
+
+/datum/chatmessage/Destroy()
+ if (hearers)
+ for(var/client/C in hearers)
+ if(!C)
+ continue
+ C.images.Remove(message)
+ UnregisterSignal(C, COMSIG_PARENT_QDELETING)
+ if(!QDELETED(message_loc))
+ LAZYREMOVE(message_loc.chat_messages, src)
+ hearers = null
+ message_loc = null
+ message = null
+ leave_subsystem()
+ return ..()
+
+/**
+ * Calls qdel on the chatmessage when its parent is deleted, used to register qdel signal
+ */
+/datum/chatmessage/proc/on_parent_qdel()
+ SIGNAL_HANDLER
+ qdel(src)
+
+/**
+ * Generates a chat message image representation
+ *
+ * Arguments:
+ * * text - The text content of the overlay
+ * * target - The target atom to display the overlay at
+ * * owner - The mob that owns this overlay, only this mob will be able to view it
+ * * language - The language this message was spoken in
+ * * extra_classes - Extra classes to apply to the span that holds the text
+ * * lifespan - The lifespan of the message in deciseconds
+ */
+/datum/chatmessage/proc/generate_image(text, atom/target, list/client/hearers, datum/language/language, list/extra_classes, lifespan)
+ /// Cached icons to show what language the user is speaking
+ var/static/list/language_icons
+
+ // Store the hearers
+ src.hearers = hearers
+
+ if(!LAZYLEN(hearers))
+ return
+
+ var/client/first_hearer = hearers[1]
+
+ // Delete when the atom its above gets deleted.
+ RegisterSignal(target, COMSIG_PARENT_QDELETING, .proc/on_parent_qdel)
+
+ for(var/client/C as() in hearers)
+ if(C)
+ RegisterSignal(C, COMSIG_PARENT_QDELETING, .proc/client_deleted)
+
+ // Remove spans in the message from things like the recorder
+ var/static/regex/span_check = new(@"<\/?span[^>]*>", "gi")
+ text = replacetext(text, span_check, "")
+
+ // Clip message
+ if (length_char(text) > CHAT_MESSAGE_MAX_LENGTH)
+ text = copytext_char(text, 1, CHAT_MESSAGE_MAX_LENGTH + 1) + "..." // BYOND index moment
+
+ //The color of the message.
+
+ // Get the chat color
+ if(!tgt_color) //in case we have color predefined
+ if(isliving(target)) //target is living, thus we have preset color for him
+ if(ishuman(target))
+ var/mob/living/carbon/human/H = target
+ if(H.wear_id?.GetID())
+ var/obj/item/card/id/idcard = H.wear_id
+ var/datum/job/wearer_job = SSjob.GetJob(idcard.GetJobName())
+ if(wearer_job)
+ tgt_color = wearer_job.chat_color
+ else
+ tgt_color = GLOB.job_colors_pastel[idcard.GetJobName()]
+ else
+ tgt_color = COLOR_PERSON_UNKNOWN
+ else
+ if(!target.chat_color) //extreme case - mob doesn't have set color
+ stack_trace("Error: Mob did not have a chat_color. The only way this can happen is if you set it to null purposely in the thing. Don't do that please.")
+ target.chat_color = colorize_string(target.name)
+ target.chat_color_name = target.name
+ tgt_color = target.chat_color
+ else //target is not living, randomizing its color
+ if(!target.chat_color || target.chat_color_name != target.name)
+ target.chat_color = colorize_string(target.name)
+ target.chat_color_name = target.name
+ tgt_color = target.chat_color
+
+ // Get rid of any URL schemes that might cause BYOND to automatically wrap something in an anchor tag
+ var/static/regex/url_scheme = new(@"[A-Za-z][A-Za-z0-9+-\.]*:\/\/", "g")
+ text = replacetext(text, url_scheme, "")
+
+ // Reject whitespace
+ var/static/regex/whitespace = new(@"^\s*$")
+ if (whitespace.Find(text))
+ qdel(src)
+ return
+
+ var/list/prefixes
+
+ // Append radio icon if from a virtual speaker
+ if (extra_classes.Find("virtual-speaker"))
+ var/image/r_icon = image('icons/UI_Icons/chat/chat_icons.dmi', icon_state = "radio")
+ LAZYADD(prefixes, "\icon[r_icon]")
+ else if (extra_classes.Find("emote"))
+ var/image/r_icon = image('icons/UI_Icons/chat/chat_icons.dmi', icon_state = "emote")
+ LAZYADD(prefixes, "\icon[r_icon]")
+ tgt_color = COLOR_CHAT_EMOTE
+
+ // Append language icon if the language uses one
+ var/datum/language/language_instance = GLOB.language_datum_instances[language]
+ if (language_instance?.display_icon(first_hearer.mob))
+ var/icon/language_icon = LAZYACCESS(language_icons, language)
+ if (isnull(language_icon))
+ language_icon = icon(language_instance.icon, icon_state = language_instance.icon_state)
+ language_icon.Scale(CHAT_MESSAGE_ICON_SIZE, CHAT_MESSAGE_ICON_SIZE)
+ LAZYSET(language_icons, language, language_icon)
+ LAZYADD(prefixes, "\icon[language_icon]")
+
+ //Add on the icons.
+ text = "[prefixes?.Join(" ")][text]"
+
+ // Approximate text height
+ var/complete_text = "[text]"
+ var/mheight = WXH_TO_HEIGHT(first_hearer.MeasureText(complete_text, null, CHAT_MESSAGE_WIDTH))
+ approx_lines = max(1, mheight / CHAT_MESSAGE_APPROX_LHEIGHT)
+
+ // Translate any existing messages upwards, apply exponential decay factors to timers
+ message_loc = get_atom_on_turf(target)
+ if (LAZYLEN(message_loc.chat_messages))
+ var/idx = 1
+ var/combined_height = approx_lines
+ for(var/msg in message_loc.chat_messages)
+ var/datum/chatmessage/m = msg
+ animate(m.message, pixel_y = m.message.pixel_y + mheight, time = CHAT_MESSAGE_SPAWN_TIME)
+ combined_height += m.approx_lines
+
+ // When choosing to update the remaining time we have to be careful not to update the
+ // scheduled time once the EOL completion time has been set.
+ var/sched_remaining = m.scheduled_destruction - world.time
+ if (!m.eol_complete)
+ var/remaining_time = (sched_remaining) * (CHAT_MESSAGE_EXP_DECAY ** idx++) * (CHAT_MESSAGE_HEIGHT_DECAY ** CEILING(combined_height, 1))
+ m.enter_subsystem(world.time + remaining_time) // push updated time to runechat SS
+
+ // Reset z index if relevant
+ if (current_z_idx >= CHAT_LAYER_MAX_Z)
+ current_z_idx = 0
+
+ var/bound_height = world.icon_size
+ var/bound_width = world.icon_size
+ if(ismovableatom(message_loc))
+ var/atom/movable/AM = message_loc
+ bound_height = AM.bound_height
+ bound_width = AM.bound_width
+ // Build message image
+ message = image(loc = message_loc, layer = CHAT_LAYER + CHAT_LAYER_Z_STEP * current_z_idx++)
+ message.plane = RUNECHAT_PLANE
+ message.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA | KEEP_APART
+ message.alpha = 0
+ message.pixel_y = bound_height - MESSAGE_FADE_PIXEL_Y
+ message.maptext_width = CHAT_MESSAGE_WIDTH
+ message.maptext_height = mheight
+ message.maptext_x = (CHAT_MESSAGE_WIDTH - bound_width) * -0.5
+ if(extra_classes.Find("italics"))
+ message.color = "#CCCCCC"
+ message.maptext = MAPTEXT(complete_text)
+
+ // Show the message to clients
+ for(var/client/C as() in hearers)
+ C?.images |= message
+ animate(message, alpha = 255, pixel_y = bound_height, time = CHAT_MESSAGE_SPAWN_TIME)
+
+ LAZYADD(message_loc.chat_messages, src)
+
+ // Register with the runechat SS to handle EOL and destruction
+ scheduled_destruction = world.time + (lifespan - CHAT_MESSAGE_EOL_FADE)
+ enter_subsystem()
+
+/datum/chatmessage/proc/client_deleted(client/source)
+ SIGNAL_HANDLER
+ hearers -= source
+
+/**
+ * Applies final animations to overlay CHAT_MESSAGE_EOL_FADE deciseconds prior to message deletion,
+ * sets time for scheduling deletion and re-enters the runechat SS for qdeling
+ *
+ * Arguments:
+ * * fadetime - The amount of time to animate the message's fadeout for
+ */
+/datum/chatmessage/proc/end_of_life(fadetime = CHAT_MESSAGE_EOL_FADE)
+ eol_complete = scheduled_destruction + fadetime
+ animate(message, alpha = 0, pixel_y = message.pixel_y + MESSAGE_FADE_PIXEL_Y, time = fadetime, flags = ANIMATION_PARALLEL)
+ enter_subsystem(eol_complete) // re-enter the runechat SS with the EOL completion time to QDEL self
+
+/mob/proc/should_show_chat_message(atom/movable/speaker, datum/language/message_language, is_emote = FALSE, is_heard = FALSE)
+ if(!client)
+ return CHATMESSAGE_CANNOT_HEAR
+ if(!client.prefs.chat_on_map || (!client.prefs.see_chat_non_mob && !ismob(speaker)))
+ return CHATMESSAGE_CANNOT_HEAR
+ if(!client.prefs.see_rc_emotes && is_emote)
+ return CHATMESSAGE_CANNOT_HEAR
+ if(is_heard && !can_hear())
+ return CHATMESSAGE_CANNOT_HEAR
+ //If the speaker is a virtual speaker, check to make sure we couldnt hear the original message.
+ if(istype(speaker, /atom/movable/virtualspeaker))
+ var/atom/movable/virtualspeaker/v = speaker
+ //Dont create the overhead chat if we said the message.
+ if(v.source == src)
+ return CHATMESSAGE_CANNOT_HEAR
+ //Dont create the overhead radio chat if we are a ghost and can hear global messages.
+ if(isobserver(src))
+ return CHATMESSAGE_CANNOT_HEAR
+ //Dont create the overhead radio chat if we heard the speaker speak
+ if(get_dist(get_turf(v.source), get_turf(src)) <= 1)
+ return CHATMESSAGE_CANNOT_HEAR
+ var/datum/language/language_instance = GLOB.language_datum_instances[message_language]
+ if(language_instance?.display_icon(src))
+ return CHATMESSAGE_SHOW_LANGUAGE_ICON
+ return CHATMESSAGE_HEAR
+
+/mob/living/should_show_chat_message(atom/movable/speaker, datum/language/message_language, is_emote = FALSE, is_heard = FALSE)
+ if(stat != CONSCIOUS && stat != DEAD)
+ return CHATMESSAGE_CANNOT_HEAR
+ return ..()
+
+/proc/create_chat_message(atom/movable/speaker, datum/language/message_language, list/hearers, raw_message, list/spans, list/message_mods)
+ if(!length(hearers))
+ return
+
+ if(!islist(message_mods))
+ message_mods = list()
+
+ // Ensure the list we are using, if present, is a copy so we don't modify the list provided to us
+ spans = spans ? spans.Copy() : list()
+
+ // Check for virtual speakers (aka hearing a message through a radio)
+ if (istype(speaker, /atom/movable/virtualspeaker))
+ var/atom/movable/virtualspeaker/v = speaker
+ speaker = v.source
+ spans |= "virtual-speaker"
+
+ //If the message has the radio message flag
+ else if (message_mods[MODE_RADIO_MESSAGE])
+ //You are now a virtual speaker
+ spans |= "virtual-speaker"
+ //You are no longer italics
+ spans -= "italics"
+
+ // Display visual above source
+ if(message_mods.Find(CHATMESSAGE_EMOTE))
+ var/list/clients = list()
+ for(var/mob/M as() in hearers)
+ if(M?.should_show_chat_message(speaker, message_language, TRUE))
+ clients += M.client
+ new /datum/chatmessage(raw_message, speaker, clients, message_language, list("emote"))
+ else
+ //4 Possible chat message states:
+ //Show Icon, Understand (Most other languages)
+ //Hide Icon, Understand (Normal galactic common)
+ //Show Icon, Don't understand (Most languages you can't understand)
+ //Hide Icon, Don't understand (Not understanding common)
+ var/list/client/show_icon_understand
+ var/list/client/hide_icon_understand
+ var/list/client/show_icon_scrambled
+ var/list/client/hide_icon_scrambled
+ for(var/mob/M as() in hearers)
+ switch(M?.should_show_chat_message(speaker, message_language, FALSE))
+ if(CHATMESSAGE_HEAR)
+ if(!message_language || M.has_language(message_language))
+ LAZYADD(hide_icon_understand, M.client)
+ else
+ LAZYADD(hide_icon_scrambled, M.client)
+ if(CHATMESSAGE_SHOW_LANGUAGE_ICON)
+ if(!message_language || M.has_language(message_language))
+ LAZYADD(show_icon_understand, M.client)
+ else
+ LAZYADD(show_icon_scrambled, M.client)
+ var/scrambled_message
+ var/datum/language/language_instance = message_language ? GLOB.language_datum_instances[message_language] : null
+ if(LAZYLEN(show_icon_scrambled) || LAZYLEN(hide_icon_scrambled))
+ scrambled_message = language_instance?.scramble(raw_message) || scramble_message_replace_chars(raw_message, 100)
+ //Show the correct message to people who should see the icon and understand the language
+ if(LAZYLEN(show_icon_understand))
+ new /datum/chatmessage(raw_message, speaker, show_icon_understand, message_language, spans)
+ //Show the correct message to people who should see the icon but not understand the language
+ if(LAZYLEN(hide_icon_understand))
+ new /datum/chatmessage(raw_message, speaker, hide_icon_understand, message_language, spans)
+ //Show the correct message to people who don't understand the language and should see the icon
+ if(LAZYLEN(show_icon_scrambled))
+ new /datum/chatmessage(scrambled_message, speaker, show_icon_scrambled, message_language, spans)
+ //Show the correct message to people who don't understand the language but no icon should be displayed
+ if(LAZYLEN(hide_icon_scrambled))
+ new /datum/chatmessage(scrambled_message, speaker, hide_icon_scrambled, message_language, spans)
+
+/**
+ * Creates a message overlay at a defined location for a given speaker
+ *
+ * Arguments:
+ * * speaker - The atom who is saying this message
+ * * message_language - The language that the message is said in
+ * * raw_message - The text content of the message
+ * * spans - Additional classes to be added to the message
+ */
+
+
+
+// Tweak these defines to change the available color ranges
+#define CM_COLOR_SAT_MIN 0.6
+#define CM_COLOR_SAT_MAX 0.7
+#define CM_COLOR_LUM_MIN 0.65
+#define CM_COLOR_LUM_MAX 0.75
+
+/**
+ * Gets a color for a name, will return the same color for a given string consistently within a round.atom
+ *
+ * Note that this proc aims to produce pastel-ish colors using the HSL colorspace. These seem to be favorable for displaying on the map.
+ *
+ * Arguments:
+ * * name - The name to generate a color for
+ * * sat_shift - A value between 0 and 1 that will be multiplied against the saturation
+ * * lum_shift - A value between 0 and 1 that will be multiplied against the luminescence
+ */
+/datum/chatmessage/proc/colorize_string(name, sat_shift = 1, lum_shift = 1)
+ // seed to help randomness
+ var/static/rseed = rand(1,26)
+
+ // get hsl using the selected 6 characters of the md5 hash
+ var/hash = copytext(md5(name + GLOB.round_id), rseed, rseed + 6)
+ var/h = hex2num(copytext(hash, 1, 3)) * (360 / 255)
+ var/s = (hex2num(copytext(hash, 3, 5)) >> 2) * ((CM_COLOR_SAT_MAX - CM_COLOR_SAT_MIN) / 63) + CM_COLOR_SAT_MIN
+ var/l = (hex2num(copytext(hash, 5, 7)) >> 2) * ((CM_COLOR_LUM_MAX - CM_COLOR_LUM_MIN) / 63) + CM_COLOR_LUM_MIN
+
+ // adjust for shifts
+ s *= clamp(sat_shift, 0, 1)
+ l *= clamp(lum_shift, 0, 1)
+
+ // convert to rgb
+ var/h_int = round(h/60) // mapping each section of H to 60 degree sections
+ var/c = (1 - abs(2 * l - 1)) * s
+ var/x = c * (1 - abs((h / 60) % 2 - 1))
+ var/m = l - c * 0.5
+ x = (x + m) * 255
+ c = (c + m) * 255
+ m *= 255
+ switch(h_int)
+ if(0)
+ return "#[num2hex(c, 2)][num2hex(x, 2)][num2hex(m, 2)]"
+ if(1)
+ return "#[num2hex(x, 2)][num2hex(c, 2)][num2hex(m, 2)]"
+ if(2)
+ return "#[num2hex(m, 2)][num2hex(c, 2)][num2hex(x, 2)]"
+ if(3)
+ return "#[num2hex(m, 2)][num2hex(x, 2)][num2hex(c, 2)]"
+ if(4)
+ return "#[num2hex(x, 2)][num2hex(m, 2)][num2hex(c, 2)]"
+ if(5)
+ return "#[num2hex(c, 2)][num2hex(m, 2)][num2hex(x, 2)]"
+
+/atom/proc/balloon_alert(mob/viewer, text)
+ if(!viewer?.client)
+ return
+ switch(viewer.client.prefs.see_balloon_alerts)
+ if(BALLOON_ALERT_ALWAYS)
+ new /datum/chatmessage/balloon_alert(text, src, viewer)
+ if(BALLOON_ALERT_WITH_CHAT)
+ new /datum/chatmessage/balloon_alert(text, src, viewer)
+ to_chat(viewer, "[text].")
+ if(BALLOON_ALERT_NEVER)
+ to_chat(viewer, "[text].")
+
+/atom/proc/balloon_alert_to_viewers(message, self_message, vision_distance = DEFAULT_MESSAGE_RANGE, list/ignored_mobs)
+ var/list/hearers = get_hearers_in_view(vision_distance, src)
+ hearers -= ignored_mobs
+
+ for (var/mob/hearer in hearers)
+ if (is_blind(hearer))
+ continue
+
+ balloon_alert(hearer, (hearer == src && self_message) || message)
+
+/datum/chatmessage/balloon_alert
+ tgt_color = "#ffffff"
+
+/datum/chatmessage/balloon_alert/New(text, atom/target, mob/owner)
+ if (!istype(target))
+ CRASH("Invalid target given for chatmessage")
+ if(QDELETED(owner) || !istype(owner) || !owner.client)
+ stack_trace("/datum/chatmessage created with [isnull(owner) ? "null" : "invalid"] mob owner")
+ qdel(src)
+ return
+ INVOKE_ASYNC(src, .proc/generate_image, text, target, owner)
+
+/datum/chatmessage/balloon_alert/generate_image(text, atom/target, mob/owner)
+ // Register client who owns this message
+ var/client/owned_by = owner.client
+ RegisterSignal(owned_by, COMSIG_PARENT_QDELETING, .proc/on_parent_qdel)
+
+ var/bound_width = world.icon_size
+ if (ismovable(target))
+ var/atom/movable/movable_source = target
+ bound_width = movable_source.bound_width
+
+ if(isturf(target))
+ message_loc = target
+ else
+ message_loc = get_atom_on_turf(target)
+
+ // Build message image
+ message = image(loc = message_loc, layer = CHAT_LAYER)
+ message.plane = BALLOON_CHAT_PLANE
+ message.alpha = 0
+ message.maptext_width = BALLOON_TEXT_WIDTH
+ message.maptext_height = WXH_TO_HEIGHT(owned_by?.MeasureText(text, null, BALLOON_TEXT_WIDTH))
+ message.maptext_x = (BALLOON_TEXT_WIDTH - bound_width) * -0.5
+ message.maptext = MAPTEXT("[text]")
+
+ // View the message
+ owned_by.images += message
+
+ var/duration_mult = 1
+ var/duration_length = length(text) - BALLOON_TEXT_CHAR_LIFETIME_INCREASE_MIN
+
+ if(duration_length > 0)
+ duration_mult += duration_length * BALLOON_TEXT_CHAR_LIFETIME_INCREASE_MULT
+
+ // Animate the message
+ animate(
+ message,
+ pixel_y = world.icon_size * 1.2,
+ time = BALLOON_TEXT_TOTAL_LIFETIME(1),
+ easing = SINE_EASING | EASE_OUT,
+ )
+
+ animate(
+ alpha = 255,
+ time = BALLOON_TEXT_SPAWN_TIME,
+ easing = CUBIC_EASING | EASE_OUT,
+ flags = ANIMATION_PARALLEL,
+ )
+
+ animate(
+ alpha = 0,
+ time = BALLOON_TEXT_FULLY_VISIBLE_TIME * duration_mult,
+ easing = CUBIC_EASING | EASE_IN,
+ )
+
+ // Register with the runechat SS to handle EOL and destruction
+ scheduled_destruction = world.time + BALLOON_TEXT_TOTAL_LIFETIME(duration_mult)
+ enter_subsystem()
+
+
+#undef BALLOON_TEXT_CHAR_LIFETIME_INCREASE_MIN
+#undef BALLOON_TEXT_CHAR_LIFETIME_INCREASE_MULT
+#undef CHAT_MESSAGE_SPAWN_TIME
+#undef CHAT_MESSAGE_LIFESPAN
+#undef CHAT_MESSAGE_EOL_FADE
+#undef CHAT_MESSAGE_EXP_DECAY
+#undef CHAT_MESSAGE_HEIGHT_DECAY
+#undef CHAT_MESSAGE_APPROX_LHEIGHT
+#undef CHAT_MESSAGE_WIDTH
+#undef CHAT_LAYER_Z_STEP
+#undef CHAT_LAYER_MAX_Z
+#undef CHAT_MESSAGE_ICON_SIZE
+#undef BALLOON_TEXT_FADE_TIME
+#undef BALLOON_TEXT_FULLY_VISIBLE_TIME
+#undef BALLOON_TEXT_SPAWN_TIME
+#undef BALLOON_TEXT_TOTAL_LIFETIME
+#undef BALLOON_TEXT_WIDTH
+#undef CHATMESSAGE_CANNOT_HEAR
+#undef CHATMESSAGE_HEAR
+#undef CHATMESSAGE_SHOW_LANGUAGE_ICON
diff --git a/code/datums/cinematic.dm b/code/datums/cinematic.dm
index e229b25258b3b..06b4ab2aeaf97 100644
--- a/code/datums/cinematic.dm
+++ b/code/datums/cinematic.dm
@@ -1,5 +1,3 @@
-GLOBAL_LIST_EMPTY(cinematics)
-
// Use to play cinematics.
// Watcher can be world,mob, or a list of mobs
// Blocks until sequence is done.
@@ -18,8 +16,9 @@ GLOBAL_LIST_EMPTY(cinematics)
playing.is_global = TRUE
watcher = GLOB.mob_list
playing.play(watcher)
+ qdel(playing)
-/obj/screen/cinematic
+/atom/movable/screen/cinematic
icon = 'icons/effects/station_explosion.dmi'
icon_state = "station_intact"
plane = SPLASHSCREEN_PLANE
@@ -32,34 +31,40 @@ GLOBAL_LIST_EMPTY(cinematics)
var/list/watching = list() //List of clients watching this
var/list/locked = list() //Who had notransform set during the cinematic
var/is_global = FALSE //Global cinematics will override mob-specific ones
- var/obj/screen/cinematic/screen
+ var/atom/movable/screen/cinematic/screen
var/datum/callback/special_callback //For special effects synced with animation (explosions after the countdown etc)
var/cleanup_time = 300 //How long for the final screen to remain
var/stop_ooc = TRUE //Turns off ooc when played globally.
/datum/cinematic/New()
- GLOB.cinematics += src
screen = new(src)
/datum/cinematic/Destroy()
- GLOB.cinematics -= src
+ for(var/CC in watching)
+ if(!CC)
+ continue
+ var/client/C = CC
+ C.screen -= screen
+ watching = null
QDEL_NULL(screen)
- for(var/mob/M in locked)
+ QDEL_NULL(special_callback)
+ for(var/MM in locked)
+ if(!MM)
+ continue
+ var/mob/M = MM
M.notransform = FALSE
+ locked = null
return ..()
/datum/cinematic/proc/play(watchers)
- //Check if you can actually play it (stop mob cinematics for global ones) and create screen objects
- for(var/A in GLOB.cinematics)
- var/datum/cinematic/C = A
- if(C == src)
- continue
- if(C.is_global || !is_global)
- return //Can't play two global or local cinematics at the same time
+ //Check if cinematic can actually play (stop mob cinematics for global ones)
+ if(SEND_GLOBAL_SIGNAL(COMSIG_GLOB_PLAY_CINEMATIC, src) & COMPONENT_GLOB_BLOCK_CINEMATIC)
+ return
- //Close all open windows if global
- if(is_global)
- SStgui.close_all_uis()
+ //We are now playing this cinematic
+
+ //Handle what happens when a different cinematic tries to play over us
+ RegisterSignal(SSdcs, COMSIG_GLOB_PLAY_CINEMATIC, .proc/replacement_cinematic)
//Pause OOC
var/ooc_toggled = FALSE
@@ -67,24 +72,17 @@ GLOBAL_LIST_EMPTY(cinematics)
ooc_toggled = TRUE
toggle_ooc(FALSE)
-
- for(var/mob/M in GLOB.mob_list)
- if(M in watchers)
- M.notransform = TRUE //Should this be done for non-global cinematics or even at all ?
- locked += M
- //Close watcher ui's
- SStgui.close_user_uis(M)
- if(M.client)
- watching += M.client
- M.client.screen += screen
- else
- if(is_global)
- M.notransform = TRUE
- locked += M
+ //Place /obj/screen/cinematic into everyone's screens, prevent them from moving
+ for(var/MM in watchers)
+ var/mob/M = MM
+ show_to(M, M.client)
+ RegisterSignal(M, COMSIG_MOB_CLIENT_LOGIN, .proc/show_to)
+ //Close watcher ui's
+ SStgui.close_user_uis(M)
//Actually play it
content()
-
+
//Cleanup
sleep(cleanup_time)
@@ -92,7 +90,14 @@ GLOBAL_LIST_EMPTY(cinematics)
if(ooc_toggled)
toggle_ooc(TRUE)
- qdel(src)
+/datum/cinematic/proc/show_to(mob/M, client/C)
+ if(!M.notransform)
+ locked += M
+ M.notransform = TRUE //Should this be done for non-global cinematics or even at all ?
+ if(!C)
+ return
+ watching += C
+ C.screen += screen
//Sound helper
/datum/cinematic/proc/cinematic_sound(s)
@@ -111,6 +116,11 @@ GLOBAL_LIST_EMPTY(cinematics)
/datum/cinematic/proc/content()
sleep(50)
+/datum/cinematic/proc/replacement_cinematic(datum/source, datum/cinematic/other)
+ if(!is_global && other.is_global) //Allow it to play if we're local and it's global
+ return NONE
+ return COMPONENT_GLOB_BLOCK_CINEMATIC
+
/datum/cinematic/nuke_win
id = CINEMATIC_NUKE_WIN
@@ -254,4 +264,4 @@ Nuke.Explosion()
Narsie()
-> Cinematic(CULT,world)
-*/
\ No newline at end of file
+*/
diff --git a/code/datums/components/README.md b/code/datums/components/README.md
index 66b8f81e0dd8e..061eb8a6ad60d 100644
--- a/code/datums/components/README.md
+++ b/code/datums/components/README.md
@@ -1,133 +1,133 @@
-# Datum Component System (DCS)
-
-## Concept
-
-Loosely adapted from /vg/. This is an entity component system for adding behaviours to datums when inheritance doesn't quite cut it. By using signals and events instead of direct inheritance, you can inject behaviours without hacky overloads. It requires a different method of thinking, but is not hard to use correctly. If a behaviour can have application across more than one thing. Make it generic, make it a component. Atom/mob/obj event? Give it a signal, and forward it's arguments with a `SendSignal()` call. Now every component that want's to can also know about this happening.
-
-### In the code
-
-#### Slippery things
-
-At the time of this writing, every object that is slippery overrides atom/Crossed does some checks, then slips the mob. Instead of all those Crossed overrides they could add a slippery component to all these objects. And have the checks in one proc that is run by the Crossed event
-
-#### Powercells
-
-A lot of objects have powercells. The `get_cell()` proc was added to give generic access to the cell var if it had one. This is just a specific use case of `GetComponent()`
-
-#### Radios
-
-The radio object as it is should not exist, given that more things use the _concept_ of radios rather than the object itself. The actual function of the radio can exist in a component which all the things that use it (Request consoles, actual radios, the SM shard) can add to themselves.
-
-#### Standos
-
-Stands have a lot of procs which mimic mob procs. Rather than inserting hooks for all these procs in overrides, the same can be accomplished with signals
-
-## API
-
-### Defines
-
-1. `COMPONENT_INCOMPATIBLE` Return this from `/datum/component/Initialize` or `datum/component/OnTransfer` to have the component be deleted if it's applied to an incorrect type. `parent` must not be modified if this is to be returned. This will be noted in the runtime logs
-
-### Vars
-
-1. `/datum/var/list/datum_components` (private)
- * Lazy associated list of type -> component/list of components.
-1. `/datum/var/list/comp_lookup` (private)
- * Lazy associated list of signal -> registree/list of registrees
-1. `/datum/var/list/signal_procs` (private)
- * Associated lazy list of signals -> `/datum/callback`s that will be run when the parent datum receives that signal
-1. `/datum/var/signal_enabled` (protected, boolean)
- * If the datum is signal enabled. If not, it will not react to signals
- * `FALSE` by default, set to `TRUE` when a signal is registered
-1. `/datum/component/var/dupe_mode` (protected, enum)
- * How duplicate component types are handled when added to the datum.
- * `COMPONENT_DUPE_HIGHLANDER` (default): Old component will be deleted, new component will first have `/datum/component/proc/InheritComponent(datum/component/old, FALSE)` on it
- * `COMPONENT_DUPE_ALLOWED`: The components will be treated as separate, `GetComponent()` will return the first added
- * `COMPONENT_DUPE_UNIQUE`: New component will be deleted, old component will first have `/datum/component/proc/InheritComponent(datum/component/new, TRUE)` on it
- * `COMPONENT_DUPE_UNIQUE_PASSARGS`: New component will never exist and instead its initialization arguments will be passed on to the old component.
-1. `/datum/component/var/dupe_type` (protected, type)
- * Definition of a duplicate component type
- * `null` means exact match on `type` (default)
- * Any other type means that and all subtypes
-1. `/datum/component/var/datum/parent` (protected, read-only)
- * The datum this component belongs to
- * Never `null` in child procs
-1. `report_signal_origin` (protected, boolean)
- * If `TRUE`, will invoke the callback when signalled with the signal type as the first argument.
- * `FALSE` by default.
-
-### Procs
-
-1. `/datum/proc/GetComponent(component_type(type)) -> datum/component?` (public, final)
- * Returns a reference to a component of component_type if it exists in the datum, null otherwise
-1. `/datum/proc/GetComponents(component_type(type)) -> list` (public, final)
- * Returns a list of references to all components of component_type that exist in the datum
-1. `/datum/proc/GetExactComponent(component_type(type)) -> datum/component?` (public, final)
- * Returns a reference to a component whose type MATCHES component_type if that component exists in the datum, null otherwise
-1. `SEND_SIGNAL(target, sigtype, ...)` (public, final)
- * Use to send signals to target datum
- * Extra arguments are to be specified in the signal definition
- * Returns a bitflag with signal specific information assembled from all activated components
- * Arguments are packaged in a list and handed off to _SendSignal()
-1. `/datum/proc/AddComponent(component_type(type), ...) -> datum/component` (public, final)
- * Creates an instance of `component_type` in the datum and passes `...` to its `Initialize()` call
- * Sends the `COMSIG_COMPONENT_ADDED` signal to the datum
- * All components a datum owns are deleted with the datum
- * Returns the component that was created. Or the old component in a dupe situation where `COMPONENT_DUPE_UNIQUE` was set
- * If this tries to add an component to an incompatible type, the component will be deleted and the result will be `null`. This is very unperformant, try not to do it
- * Properly handles duplicate situations based on the `dupe_mode` var
-1. `/datum/proc/LoadComponent(component_type(type), ...) -> datum/component` (public, final)
- * Equivalent to calling `GetComponent(component_type)` where, if the result would be `null`, returns `AddComponent(component_type, ...)` instead
-1. `/datum/proc/ComponentActivated(datum/component/C)` (abstract, async)
- * Called on a component's `parent` after a signal received causes it to activate. `src` is the parameter
- * Will only be called if a component's callback returns `TRUE`
-1. `/datum/proc/TakeComponent(datum/component/C)` (public, final)
- * Properly transfers ownership of a component from one datum to another
- * Signals `COMSIG_COMPONENT_REMOVING` on the parent
- * Called on the datum you want to own the component with another datum's component
-1. `/datum/proc/_SendSignal(signal, list/arguments)` (private, final)
- * Handles most of the actual signaling procedure
- * Will runtime if used on datums with an empty component list
-1. `/datum/proc/RegisterSignal(datum/target, signal(string/list of strings), proc_ref(type), override(boolean))` (protected, final)
- * If signal is a list it will be as if RegisterSignal was called for each of the entries with the same following arguments
- * Makes the datum listen for the specified `signal` on it's `parent` datum.
- * When that signal is received `proc_ref` will be called on the component, along with associated arguments
- * Example proc ref: `.proc/OnEvent`
- * If a previous registration is overwritten by the call, a runtime occurs. Setting `override` to TRUE prevents this
- * These callbacks run asyncronously
- * Returning `TRUE` from these callbacks will trigger a `TRUE` return from the `SendSignal()` that initiated it
-1. `/datum/component/New(datum/parent, ...)` (private, final)
- * Runs internal setup for the component
- * Extra arguments are passed to `Initialize()`
-1. `/datum/component/Initialize(...)` (abstract, no-sleep)
- * Called by `New()` with the same argments excluding `parent`
- * Component does not exist in `parent`'s `datum_components` list yet, although `parent` is set and may be used
- * Signals will not be received while this function is running
- * Component may be deleted after this function completes without being attached
- * Do not call `qdel(src)` from this function
-1. `/datum/component/Destroy(force(bool), silent(bool))` (virtual, no-sleep)
- * Sends the `COMSIG_COMPONENT_REMOVING` signal to the parent datum if the `parent` isn't being qdeleted
- * Properly removes the component from `parent` and cleans up references
- * Setting `force` makes it not check for and remove the component from the parent
- * Setting `silent` deletes the component without sending a `COMSIG_COMPONENT_REMOVING` signal
-1. `/datum/component/proc/InheritComponent(datum/component/C, i_am_original(boolean))` (abstract, no-sleep)
- * Called on a component when a component of the same type was added to the same parent
- * See `/datum/component/var/dupe_mode`
- * `C`'s type will always be the same of the called component
-1. `/datum/component/proc/AfterComponentActivated()` (abstract, async)
- * Called on a component that was activated after it's `parent`'s `ComponentActivated()` is called
-1. `/datum/component/proc/OnTransfer(datum/new_parent)` (abstract, no-sleep)
- * Called before `new_parent` is assigned to `parent` in `TakeComponent()`
- * Allows the component to react to ownership transfers
-1. `/datum/component/proc/_RemoveFromParent()` (private, final)
- * Clears `parent` and removes the component from it's component list
-1. `/datum/component/proc/_JoinParent` (private, final)
- * Tries to add the component to it's `parent`s `datum_components` list
-1. `/datum/component/proc/RegisterWithParent` (abstract, no-sleep)
- * Used to register the signals that should be on the `parent` object
- * Use this if you plan on the component transfering between parents
-1. `/datum/component/proc/UnregisterFromParent` (abstract, no-sleep)
- * Counterpart to `RegisterWithParent()`
- * Used to unregister the signals that should only be on the `parent` object
-
-### See/Define signals and their arguments in __DEFINES\components.dm
+# Datum Component System (DCS)
+
+## Concept
+
+Loosely adapted from /vg/. This is an entity component system for adding behaviours to datums when inheritance doesn't quite cut it. By using signals and events instead of direct inheritance, you can inject behaviours without hacky overloads. It requires a different method of thinking, but is not hard to use correctly. If a behaviour can have application across more than one thing. Make it generic, make it a component. Atom/mob/obj event? Give it a signal, and forward it's arguments with a `SendSignal()` call. Now every component that want's to can also know about this happening.
+
+### In the code
+
+#### Slippery things
+
+At the time of this writing, every object that is slippery overrides atom/Crossed does some checks, then slips the mob. Instead of all those Crossed overrides they could add a slippery component to all these objects. And have the checks in one proc that is run by the Crossed event
+
+#### Powercells
+
+A lot of objects have powercells. The `get_cell()` proc was added to give generic access to the cell var if it had one. This is just a specific use case of `GetComponent()`
+
+#### Radios
+
+The radio object as it is should not exist, given that more things use the _concept_ of radios rather than the object itself. The actual function of the radio can exist in a component which all the things that use it (Request consoles, actual radios, the SM shard) can add to themselves.
+
+#### Standos
+
+Stands have a lot of procs which mimic mob procs. Rather than inserting hooks for all these procs in overrides, the same can be accomplished with signals
+
+## API
+
+### Defines
+
+1. `COMPONENT_INCOMPATIBLE` Return this from `/datum/component/Initialize` or `datum/component/OnTransfer` to have the component be deleted if it's applied to an incorrect type. `parent` must not be modified if this is to be returned. This will be noted in the runtime logs
+
+### Vars
+
+1. `/datum/var/list/datum_components` (private)
+ * Lazy associated list of type -> component/list of components.
+1. `/datum/var/list/comp_lookup` (private)
+ * Lazy associated list of signal -> registree/list of registrees
+1. `/datum/var/list/signal_procs` (private)
+ * Associated lazy list of signals -> `/datum/callback`s that will be run when the parent datum receives that signal
+1. `/datum/var/signal_enabled` (protected, boolean)
+ * If the datum is signal enabled. If not, it will not react to signals
+ * `FALSE` by default, set to `TRUE` when a signal is registered
+1. `/datum/component/var/dupe_mode` (protected, enum)
+ * How duplicate component types are handled when added to the datum.
+ * `COMPONENT_DUPE_HIGHLANDER` (default): Old component will be deleted, new component will first have `/datum/component/proc/InheritComponent(datum/component/old, FALSE)` on it
+ * `COMPONENT_DUPE_ALLOWED`: The components will be treated as separate, `GetComponent()` will return the first added
+ * `COMPONENT_DUPE_UNIQUE`: New component will be deleted, old component will first have `/datum/component/proc/InheritComponent(datum/component/new, TRUE)` on it
+ * `COMPONENT_DUPE_UNIQUE_PASSARGS`: New component will never exist and instead its initialization arguments will be passed on to the old component.
+1. `/datum/component/var/dupe_type` (protected, type)
+ * Definition of a duplicate component type
+ * `null` means exact match on `type` (default)
+ * Any other type means that and all subtypes
+1. `/datum/component/var/datum/parent` (protected, read-only)
+ * The datum this component belongs to
+ * Never `null` in child procs
+1. `report_signal_origin` (protected, boolean)
+ * If `TRUE`, will invoke the callback when signalled with the signal type as the first argument.
+ * `FALSE` by default.
+
+### Procs
+
+1. `/datum/proc/GetComponent(component_type(type)) -> datum/component?` (public, final)
+ * Returns a reference to a component of component_type if it exists in the datum, null otherwise
+1. `/datum/proc/GetComponents(component_type(type)) -> list` (public, final)
+ * Returns a list of references to all components of component_type that exist in the datum
+1. `/datum/proc/GetExactComponent(component_type(type)) -> datum/component?` (public, final)
+ * Returns a reference to a component whose type MATCHES component_type if that component exists in the datum, null otherwise
+1. `SEND_SIGNAL(target, sigtype, ...)` (public, final)
+ * Use to send signals to target datum
+ * Extra arguments are to be specified in the signal definition
+ * Returns a bitflag with signal specific information assembled from all activated components
+ * Arguments are packaged in a list and handed off to _SendSignal()
+1. `/datum/proc/AddComponent(component_type(type), ...) -> datum/component` (public, final)
+ * Creates an instance of `component_type` in the datum and passes `...` to its `Initialize()` call
+ * Sends the `COMSIG_COMPONENT_ADDED` signal to the datum
+ * All components a datum owns are deleted with the datum
+ * Returns the component that was created. Or the old component in a dupe situation where `COMPONENT_DUPE_UNIQUE` was set
+ * If this tries to add an component to an incompatible type, the component will be deleted and the result will be `null`. This is very unperformant, try not to do it
+ * Properly handles duplicate situations based on the `dupe_mode` var
+1. `/datum/proc/LoadComponent(component_type(type), ...) -> datum/component` (public, final)
+ * Equivalent to calling `GetComponent(component_type)` where, if the result would be `null`, returns `AddComponent(component_type, ...)` instead
+1. `/datum/proc/ComponentActivated(datum/component/C)` (abstract, async)
+ * Called on a component's `parent` after a signal received causes it to activate. `src` is the parameter
+ * Will only be called if a component's callback returns `TRUE`
+1. `/datum/proc/TakeComponent(datum/component/C)` (public, final)
+ * Properly transfers ownership of a component from one datum to another
+ * Signals `COMSIG_COMPONENT_REMOVING` on the parent
+ * Called on the datum you want to own the component with another datum's component
+1. `/datum/proc/_SendSignal(signal, list/arguments)` (private, final)
+ * Handles most of the actual signaling procedure
+ * Will runtime if used on datums with an empty component list
+1. `/datum/proc/RegisterSignal(datum/target, signal(string/list of strings), proc_ref(type), override(boolean))` (protected, final)
+ * If signal is a list it will be as if RegisterSignal was called for each of the entries with the same following arguments
+ * Makes the datum listen for the specified `signal` on it's `parent` datum.
+ * When that signal is received `proc_ref` will be called on the component, along with associated arguments
+ * Example proc ref: `.proc/OnEvent`
+ * If a previous registration is overwritten by the call, a runtime occurs. Setting `override` to TRUE prevents this
+ * These callbacks run asyncronously
+ * Returning `TRUE` from these callbacks will trigger a `TRUE` return from the `SendSignal()` that initiated it
+1. `/datum/component/New(datum/parent, ...)` (private, final)
+ * Runs internal setup for the component
+ * Extra arguments are passed to `Initialize()`
+1. `/datum/component/Initialize(...)` (abstract, no-sleep)
+ * Called by `New()` with the same argments excluding `parent`
+ * Component does not exist in `parent`'s `datum_components` list yet, although `parent` is set and may be used
+ * Signals will not be received while this function is running
+ * Component may be deleted after this function completes without being attached
+ * Do not call `qdel(src)` from this function
+1. `/datum/component/Destroy(force(bool), silent(bool))` (virtual, no-sleep)
+ * Sends the `COMSIG_COMPONENT_REMOVING` signal to the parent datum if the `parent` isn't being qdeleted
+ * Properly removes the component from `parent` and cleans up references
+ * Setting `force` makes it not check for and remove the component from the parent
+ * Setting `silent` deletes the component without sending a `COMSIG_COMPONENT_REMOVING` signal
+1. `/datum/component/proc/InheritComponent(datum/component/C, i_am_original(boolean))` (abstract, no-sleep)
+ * Called on a component when a component of the same type was added to the same parent
+ * See `/datum/component/var/dupe_mode`
+ * `C`'s type will always be the same of the called component
+1. `/datum/component/proc/AfterComponentActivated()` (abstract, async)
+ * Called on a component that was activated after it's `parent`'s `ComponentActivated()` is called
+1. `/datum/component/proc/OnTransfer(datum/new_parent)` (abstract, no-sleep)
+ * Called before `new_parent` is assigned to `parent` in `TakeComponent()`
+ * Allows the component to react to ownership transfers
+1. `/datum/component/proc/_RemoveFromParent()` (private, final)
+ * Clears `parent` and removes the component from it's component list
+1. `/datum/component/proc/_JoinParent` (private, final)
+ * Tries to add the component to it's `parent`s `datum_components` list
+1. `/datum/component/proc/RegisterWithParent` (abstract, no-sleep)
+ * Used to register the signals that should be on the `parent` object
+ * Use this if you plan on the component transfering between parents
+1. `/datum/component/proc/UnregisterFromParent` (abstract, no-sleep)
+ * Counterpart to `RegisterWithParent()`
+ * Used to unregister the signals that should only be on the `parent` object
+
+### See/Define signals and their arguments in __DEFINES\components.dm
diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm
index ab6cda5f63718..0901b283f2303 100644
--- a/code/datums/components/_component.dm
+++ b/code/datums/components/_component.dm
@@ -7,14 +7,15 @@
//Make sure you also implement PostTransfer for any post transfer handling
var/can_transfer = FALSE
-/datum/component/New(datum/P, ...)
- parent = P
- var/list/arguments = args.Copy(2)
+/datum/component/New(list/raw_args)
+ parent = raw_args[1]
+ var/list/arguments = raw_args.Copy(2)
if(Initialize(arglist(arguments)) == COMPONENT_INCOMPATIBLE)
+ stack_trace("Incompatible [type] assigned to a [parent.type]! args: [json_encode(arguments)]")
qdel(src, TRUE, TRUE)
- CRASH("Incompatible [type] assigned to a [P.type]! args: [json_encode(arguments)]")
-
- _JoinParent(P)
+ CRASH("Incompatible [type] assigned to a [parent.type]! args: [json_encode(arguments)]")
+
+ _JoinParent(parent)
/datum/component/proc/_JoinParent()
var/datum/P = parent
@@ -151,6 +152,9 @@
/datum/component/proc/InheritComponent(datum/component/C, i_am_original)
return
+/datum/component/proc/CheckDupeComponent(datum/component/C, ...)
+ return
+
/datum/component/proc/PreTransfer()
return
@@ -173,17 +177,19 @@
if(!C.signal_enabled)
return NONE
var/proctype = C.signal_procs[src][sigtype]
- return NONE | CallAsync(C, proctype, arguments)
+ return NONE | call(C, proctype)(arglist(arguments))
. = NONE
for(var/I in target)
var/datum/C = I
if(!C.signal_enabled)
continue
var/proctype = C.signal_procs[src][sigtype]
- . |= CallAsync(C, proctype, arguments)
+ . |= call(C, proctype)(arglist(arguments))
-/datum/proc/GetComponent(c_type)
+/datum/proc/GetComponent(datum/component/c_type)
RETURN_TYPE(c_type)
+ if(initial(c_type.dupe_mode) == COMPONENT_DUPE_ALLOWED || initial(c_type.dupe_mode) == COMPONENT_DUPE_SELECTIVE)
+ stack_trace("GetComponent was called to get a component of which multiple copies could be on an object. This can easily break and should be changed. Type: \[[c_type]\]")
var/list/dc = datum_components
if(!dc)
return null
@@ -191,7 +197,10 @@
if(length(.))
return .[1]
-/datum/proc/GetExactComponent(c_type)
+/datum/proc/GetExactComponent(datum/component/c_type)
+ RETURN_TYPE(c_type)
+ if(initial(c_type.dupe_mode) == COMPONENT_DUPE_ALLOWED || initial(c_type.dupe_mode) == COMPONENT_DUPE_SELECTIVE)
+ stack_trace("GetComponent was called to get a component of which multiple copies could be on an object. This can easily break and should be changed. Type: \[[c_type]\]")
var/list/dc = datum_components
if(!dc)
return null
@@ -211,7 +220,8 @@
if(!length(.))
return list(.)
-/datum/proc/AddComponent(new_type, ...)
+/datum/proc/_AddComponent(list/raw_args)
+ var/new_type = raw_args[1]
var/datum/component/nt = new_type
var/dm = initial(nt.dupe_mode)
var/dt = initial(nt.dupe_type)
@@ -226,7 +236,7 @@
new_comp = nt
nt = new_comp.type
- args[1] = src
+ raw_args[1] = src
if(dm != COMPONENT_DUPE_ALLOWED)
if(!dt)
@@ -237,26 +247,38 @@
switch(dm)
if(COMPONENT_DUPE_UNIQUE)
if(!new_comp)
- new_comp = new nt(arglist(args))
+ new_comp = new nt(raw_args)
if(!QDELETED(new_comp))
old_comp.InheritComponent(new_comp, TRUE)
QDEL_NULL(new_comp)
if(COMPONENT_DUPE_HIGHLANDER)
if(!new_comp)
- new_comp = new nt(arglist(args))
+ new_comp = new nt(raw_args)
if(!QDELETED(new_comp))
new_comp.InheritComponent(old_comp, FALSE)
QDEL_NULL(old_comp)
if(COMPONENT_DUPE_UNIQUE_PASSARGS)
if(!new_comp)
- var/list/arguments = args.Copy(2)
- old_comp.InheritComponent(null, TRUE, arguments)
+ var/list/arguments = raw_args.Copy(2)
+ arguments.Insert(1, null, TRUE)
+ old_comp.InheritComponent(arglist(arguments))
else
old_comp.InheritComponent(new_comp, TRUE)
+ if(COMPONENT_DUPE_SELECTIVE)
+ var/list/arguments = raw_args.Copy()
+ arguments[1] = new_comp
+ var/make_new_component = TRUE
+ for(var/datum/component/C in GetComponents(new_type))
+ if(C.CheckDupeComponent(arglist(arguments)))
+ make_new_component = FALSE
+ QDEL_NULL(new_comp)
+ break
+ if(!new_comp && make_new_component)
+ new_comp = new nt(raw_args)
else if(!new_comp)
- new_comp = new nt(arglist(args)) // There's a valid dupe mode but there's no old component, act like normal
+ new_comp = new nt(raw_args) // There's a valid dupe mode but there's no old component, act like normal
else if(!new_comp)
- new_comp = new nt(arglist(args)) // Dupes are allowed, act like normal
+ new_comp = new nt(raw_args) // Dupes are allowed, act like normal
if(!old_comp && !QDELETED(new_comp)) // Nothing related to duplicate components happened and the new component is healthy
SEND_SIGNAL(src, COMSIG_COMPONENT_ADDED, new_comp)
@@ -266,7 +288,7 @@
/datum/proc/LoadComponent(component_type, ...)
. = GetComponent(component_type)
if(!.)
- return AddComponent(arglist(args))
+ return _AddComponent(args)
/datum/component/proc/RemoveComponent()
if(!parent)
diff --git a/code/datums/components/anti_magic.dm b/code/datums/components/anti_magic.dm
index 9b03284513a72..c6ef885af1847 100644
--- a/code/datums/components/anti_magic.dm
+++ b/code/datums/components/anti_magic.dm
@@ -3,6 +3,7 @@
var/holy = FALSE
var/charges = INFINITY
var/blocks_self = TRUE
+ var/allowed_slots = ~ITEM_SLOT_BACKPACK
var/datum/callback/reaction
var/datum/callback/expire
@@ -24,15 +25,21 @@
expire = _expire
/datum/component/anti_magic/proc/on_equip(datum/source, mob/equipper, slot)
- if(slot == SLOT_IN_BACKPACK)
+ SIGNAL_HANDLER
+
+ if(!(allowed_slots & slot)) //Check that the slot is valid for antimagic
UnregisterSignal(equipper, COMSIG_MOB_RECEIVE_MAGIC)
return
RegisterSignal(equipper, COMSIG_MOB_RECEIVE_MAGIC, .proc/protect, TRUE)
/datum/component/anti_magic/proc/on_drop(datum/source, mob/user)
+ SIGNAL_HANDLER
+
UnregisterSignal(user, COMSIG_MOB_RECEIVE_MAGIC)
/datum/component/anti_magic/proc/protect(datum/source, mob/user, _magic, _holy, major, self, list/protection_sources)
+ SIGNAL_HANDLER
+
if(((_magic && magic) || (_holy && holy)) && (!self || blocks_self))
protection_sources += parent
reaction?.Invoke(user, major)
diff --git a/code/datums/components/aquarium.dm b/code/datums/components/aquarium.dm
new file mode 100644
index 0000000000000..0658a3f9e2038
--- /dev/null
+++ b/code/datums/components/aquarium.dm
@@ -0,0 +1,267 @@
+/// Allows movables to be inserted/displayed in aquariums.
+/datum/component/aquarium_content
+ /// This is a datum that describes our in-aquarium functionality
+ var/datum/aquarium_behaviour/properties
+
+ /// Keeps track of our current aquarium.
+ var/obj/structure/aquarium/current_aquarium
+
+ //This is visual effect holder that will end up in aquarium's vis_contents
+ var/obj/effect/vc_obj
+
+ /// Base px offset of the visual object in current aquarium aka current base position
+ var/base_px = 0
+ /// Base px offset of the visual object in current aquarium aka current base position
+ var/base_py = 0
+ //Current layer for the visual object
+ var/base_layer
+ //If flopping animation was applied to parent, tracked to stop it on removal/destroy
+ var/flopping = FALSE
+
+/datum/component/aquarium_content/Initialize(property_type)
+ if(!ismovable(parent))
+ return COMPONENT_INCOMPATIBLE
+ if(ispath(property_type, /datum/aquarium_behaviour))
+ properties = new property_type
+ else
+ CRASH("Invalid property type provided for aquarium content component")
+ properties.parent = src
+
+ ADD_TRAIT(parent, TRAIT_FISH_CASE_COMPATIBILE, src)
+ RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/enter_aquarium)
+ //If component is added to something already in aquarium at the time initialize it properly.
+ var/atom/movable/movable_parent = parent
+ if(istype(movable_parent.loc, /obj/structure/aquarium))
+ on_inserted(movable_parent.loc)
+
+
+/datum/component/aquarium_content/PreTransfer()
+ . = ..()
+ REMOVE_TRAIT(parent, TRAIT_FISH_CASE_COMPATIBILE, src)
+
+/datum/component/aquarium_content/Destroy(force, silent)
+ if(current_aquarium)
+ remove_from_aquarium()
+ stop_flopping()
+ QDEL_NULL(vc_obj)
+ QDEL_NULL(properties)
+ return ..()
+
+/datum/component/aquarium_content/proc/enter_aquarium(datum/source, OldLoc, Dir, Forced)
+ SIGNAL_HANDLER
+ var/atom/movable/movable_parent = parent
+ if(istype(movable_parent.loc, /obj/structure/aquarium))
+ on_inserted(movable_parent.loc)
+ if(HAS_TRAIT(movable_parent.loc, TRAIT_FISH_SAFE_STORAGE))
+ on_tank_stasis()
+
+/datum/component/aquarium_content/proc/is_ready_to_insert(obj/structure/aquarium/aquarium)
+ //This is kinda awful but we're unaware of other fish
+ if(properties.unique)
+ for(var/atom/movable/fish_or_prop in aquarium)
+ if(fish_or_prop == parent)
+ continue
+ var/datum/component/aquarium_content/other_content = fish_or_prop.GetComponent(/datum/component/aquarium_content)
+ if(other_content && other_content.properties.type == properties.type)
+ return FALSE
+ return TRUE
+
+/datum/component/aquarium_content/proc/on_inserted(atom/aquarium)
+ current_aquarium = aquarium
+ RegisterSignal(current_aquarium, COMSIG_ATOM_EXITED, .proc/on_removed)
+ RegisterSignal(current_aquarium, COMSIG_AQUARIUM_SURFACE_CHANGED, .proc/on_surface_changed)
+ RegisterSignal(current_aquarium, COMSIG_AQUARIUM_FLUID_CHANGED,.proc/on_fluid_changed)
+ RegisterSignal(current_aquarium, COMSIG_PARENT_ATTACKBY, .proc/attack_reaction)
+ properties.on_inserted()
+
+ //If we don't have vc object yet build it
+ if(!vc_obj)
+ vc_obj = generate_base_vc()
+
+ //Set default position and layer
+ set_vc_base_position()
+ generate_animation()
+
+ //Finally add it to to objects vis_contents
+ current_aquarium.vis_contents |= vc_obj
+
+
+/// Aquarium surface changed in some way, we need to recalculate base position and aninmation
+/datum/component/aquarium_content/proc/on_surface_changed()
+ SIGNAL_HANDLER
+ set_vc_base_position()
+ generate_animation() //our animation start point changed, gotta redo
+
+/// Our aquarium is hit with stuff
+/datum/component/aquarium_content/proc/attack_reaction(datum/source, obj/item/thing, mob/user, params)
+ SIGNAL_HANDLER
+ if(istype(thing, /obj/item/fish_feed))
+ properties.on_feeding(thing.reagents)
+ return COMPONENT_NO_AFTERATTACK
+ else
+ //stirred effect
+ generate_animation()
+
+/datum/component/aquarium_content/proc/on_fluid_changed()
+ SIGNAL_HANDLER
+ properties.on_fluid_changed()
+
+/datum/component/aquarium_content/proc/remove_visual_from_aquarium()
+ current_aquarium.vis_contents -= vc_obj
+ if(base_layer)
+ current_aquarium.free_layer(base_layer)
+
+
+/// Generates common visual object, propeties that don't depend on aquarium surface
+/datum/component/aquarium_content/proc/generate_base_vc()
+ if(!properties)
+ CRASH("Generating visual without properties.")
+
+ var/obj/effect/visual = new
+ properties.apply_appearance(visual)
+ visual.vis_flags |= VIS_INHERIT_ID | VIS_INHERIT_PLANE //plane so it shows properly in containers on inventory ui for handheld cases
+ return visual
+
+/// Actually animates the vc holder
+/datum/component/aquarium_content/proc/generate_animation()
+ switch(properties.current_animation)
+ if(AQUARIUM_ANIMATION_FISH_SWIM)
+ swim_animation()
+ return
+ if(AQUARIUM_ANIMATION_FISH_DEAD)
+ dead_animation()
+ return
+
+
+/// Create looping random path animation, pixel offsets parameters include offsets already
+/datum/component/aquarium_content/proc/swim_animation()
+ var/avg_width = round(properties.sprite_width / 2)
+ var/avg_height = round(properties.sprite_height / 2)
+
+ var/list/aq_properties = current_aquarium.get_surface_properties()
+ var/px_min = aq_properties[AQUARIUM_PROPERTIES_PX_MIN] + avg_width - 16
+ var/px_max = aq_properties[AQUARIUM_PROPERTIES_PX_MAX] - avg_width - 16
+ var/py_min = aq_properties[AQUARIUM_PROPERTIES_PY_MIN] + avg_height - 16
+ var/py_max = aq_properties[AQUARIUM_PROPERTIES_PY_MAX] - avg_width - 16
+
+ var/origin_x = base_px
+ var/origin_y = base_py
+ var/prev_x = origin_x
+ var/prev_y = origin_y
+ animate(vc_obj, pixel_x = origin_x, time = 0, loop = -1) //Just to start the animation
+ var/move_number = rand(3, 5) //maybe unhardcode this
+ for(var/i in 1 to move_number)
+ //If it's last movement, move back to start otherwise move to some random point
+ var/target_x = i == move_number ? origin_x : rand(px_min,px_max) //could do with enforcing minimal delta for prettier zigzags
+ var/target_y = i == move_number ? origin_y : rand(py_min,py_max)
+ var/dx = prev_x - target_x
+ var/dy = prev_y - target_y
+ prev_x = target_x
+ prev_y = target_y
+ var/dist = abs(dx) + abs(dy)
+ var/eyeballed_time = dist * 2 //2ds per px
+ //Face the direction we're going
+ var/matrix/dir_mx = properties.base_transform()
+ if(dx <= 0) //assuming default sprite is facing left here
+ dir_mx.Scale(-1, 1)
+ animate(transform = dir_mx, time = 0, loop = -1)
+ animate(pixel_x = target_x, pixel_y = target_y, time = eyeballed_time, loop = -1)
+
+/datum/component/aquarium_content/proc/dead_animation()
+ //Set base_py to lowest possible value
+ var/avg_height = round(properties.sprite_height / 2)
+ var/list/aq_properties = current_aquarium.get_surface_properties()
+ var/py_min = aq_properties[AQUARIUM_PROPERTIES_PY_MIN] + avg_height - 16
+ base_py = py_min
+ animate(vc_obj, pixel_y = py_min, time = 1) //flop to bottom and end current animation.
+
+#define PAUSE_BETWEEN_PHASES 15
+#define PAUSE_BETWEEN_FLOPS 2
+#define FLOP_COUNT 2
+#define FLOP_DEGREE 20
+#define FLOP_SINGLE_MOVE_TIME 1.5
+#define JUMP_X_DISTANCE 5
+#define JUMP_Y_DISTANCE 6
+/// This animation should be applied to actual parent atom instead of vc_object.
+/proc/flop_animation(atom/movable/animation_target)
+ var/pause_between = PAUSE_BETWEEN_PHASES + rand(1, 5) //randomized a bit so fish are not in sync
+ animate(animation_target, time = pause_between, loop = -1)
+ //move nose down and up
+ for(var/_ in 1 to FLOP_COUNT)
+ var/matrix/up_matrix = matrix()
+ up_matrix.Turn(FLOP_DEGREE)
+ var/matrix/down_matrix = matrix()
+ down_matrix.Turn(-FLOP_DEGREE)
+ animate(transform = down_matrix, time = FLOP_SINGLE_MOVE_TIME, loop = -1)
+ animate(transform = up_matrix, time = FLOP_SINGLE_MOVE_TIME, loop = -1)
+ animate(transform = matrix(), time = FLOP_SINGLE_MOVE_TIME, loop = -1, easing = BOUNCE_EASING | EASE_IN)
+ animate(time = PAUSE_BETWEEN_FLOPS, loop = -1)
+ //bounce up and down
+ animate(time = pause_between, loop = -1, flags = ANIMATION_PARALLEL)
+ var/jumping_right = FALSE
+ var/up_time = 3 * FLOP_SINGLE_MOVE_TIME / 2
+ for(var/_ in 1 to FLOP_COUNT)
+ jumping_right = !jumping_right
+ var/x_step = jumping_right ? JUMP_X_DISTANCE/2 : -JUMP_X_DISTANCE/2
+ animate(time = up_time, pixel_y = JUMP_Y_DISTANCE , pixel_x=x_step, loop = -1, flags= ANIMATION_RELATIVE, easing = BOUNCE_EASING | EASE_IN)
+ animate(time = up_time, pixel_y = -JUMP_Y_DISTANCE, pixel_x=x_step, loop = -1, flags= ANIMATION_RELATIVE, easing = BOUNCE_EASING | EASE_OUT)
+ animate(time = PAUSE_BETWEEN_FLOPS, loop = -1)
+#undef PAUSE_BETWEEN_PHASES
+#undef PAUSE_BETWEEN_FLOPS
+#undef FLOP_COUNT
+#undef FLOP_DEGREE
+#undef FLOP_SINGLE_MOVE_TIME
+#undef JUMP_X_DISTANCE
+#undef JUMP_Y_DISTANCE
+
+
+/// Starts flopping animation
+/datum/component/aquarium_content/proc/start_flopping()
+ if(!flopping && istype(parent,/obj/item/fish)) //Requires update_transform/animate_wrappers to be less restrictive.
+ flopping = TRUE
+ flop_animation(parent)
+
+/// Stops flopping animation
+/datum/component/aquarium_content/proc/stop_flopping()
+ if(flopping)
+ flopping = FALSE
+ animate(parent, transform = matrix()) //stop animation
+
+/datum/component/aquarium_content/proc/set_vc_base_position()
+ var/list/aq_properties = current_aquarium.get_surface_properties()
+ if(properties.randomize_position)
+ var/avg_width = round(properties.sprite_width / 2)
+ var/avg_height = round(properties.sprite_height / 2)
+ var/px_min = aq_properties[AQUARIUM_PROPERTIES_PX_MIN] + avg_width - 16
+ var/px_max = aq_properties[AQUARIUM_PROPERTIES_PX_MAX] - avg_width - 16
+ var/py_min = aq_properties[AQUARIUM_PROPERTIES_PY_MIN] + avg_height - 16
+ var/py_max = aq_properties[AQUARIUM_PROPERTIES_PY_MAX] - avg_width - 16
+
+ base_px = rand(px_min,px_max)
+ base_py = rand(py_min,py_max)
+
+ vc_obj.pixel_x = base_px
+ vc_obj.pixel_y = base_py
+
+ if(base_layer)
+ current_aquarium.free_layer(base_layer)
+ base_layer = current_aquarium.request_layer(properties.layer_mode)
+ vc_obj.layer = base_layer
+
+/datum/component/aquarium_content/proc/on_removed(datum/source, atom/movable/mover)
+ SIGNAL_HANDLER
+ if(mover != parent)
+ return
+ remove_from_aquarium()
+
+/datum/component/aquarium_content/proc/remove_from_aquarium()
+ properties.before_removal()
+ UnregisterSignal(current_aquarium, list(COMSIG_AQUARIUM_SURFACE_CHANGED, COMSIG_AQUARIUM_FLUID_CHANGED, COMSIG_PARENT_ATTACKBY, COMSIG_ATOM_EXITED))
+ remove_visual_from_aquarium()
+ current_aquarium = null
+ //We do not stop processing properties here. We want fish to die outside of aquariums after first insert. We only stop processing in properties.death or destroy
+
+/datum/component/aquarium_content/proc/on_tank_stasis()
+ // Stop processing until inserted into aquarium again.
+ stop_flopping()
+ STOP_PROCESSING(SSobj, properties)
diff --git a/code/datums/components/archaeology.dm b/code/datums/components/archaeology.dm
index b5740650e9f0b..93fb0ff60a932 100644
--- a/code/datums/components/archaeology.dm
+++ b/code/datums/components/archaeology.dm
@@ -15,7 +15,7 @@
archdrops[i][ARCH_PROB] = 100
stack_trace("ARCHAEOLOGY WARNING: [parent] contained a null probability value in [i].")
callback = _callback
- RegisterSignal(parent, COMSIG_PARENT_ATTACKBY,.proc/Dig)
+ RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/Dig)
RegisterSignal(parent, COMSIG_ATOM_EX_ACT, .proc/BombDig)
RegisterSignal(parent, COMSIG_ATOM_SING_PULL, .proc/SingDig)
@@ -26,6 +26,8 @@
_archdrops[I] += other_archdrops[I]
/datum/component/archaeology/proc/Dig(datum/source, obj/item/I, mob/living/user)
+ SIGNAL_HANDLER
+
if(dug)
to_chat(user, "Looks like someone has dug here already.")
return
@@ -73,6 +75,8 @@
callback.Invoke()
/datum/component/archaeology/proc/SingDig(datum/source, S, current_size)
+ SIGNAL_HANDLER
+
switch(current_size)
if(STAGE_THREE)
if(prob(30))
@@ -85,6 +89,8 @@
gets_dug()
/datum/component/archaeology/proc/BombDig(datum/source, severity, target)
+ SIGNAL_HANDLER
+
switch(severity)
if(3)
return
diff --git a/code/datums/components/armor_plate.dm b/code/datums/components/armor_plate.dm
index 6f1ba6a8392ec..0bfc14d21bde5 100644
--- a/code/datums/components/armor_plate.dm
+++ b/code/datums/components/armor_plate.dm
@@ -30,6 +30,8 @@
upgrade_name = initial(typecast.name)
/datum/component/armor_plate/proc/examine(datum/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+
//upgrade_item could also be typecast here instead
if(ismecha(parent))
if(amount)
@@ -46,6 +48,8 @@
examine_list += "It can be strengthened with up to [maxamount] [upgrade_name]."
/datum/component/armor_plate/proc/applyplate(datum/source, obj/item/I, mob/user, params)
+ SIGNAL_HANDLER
+
if(!istype(I,upgrade_item))
return
if(amount >= maxamount)
@@ -73,6 +77,8 @@
/datum/component/armor_plate/proc/dropplates(datum/source, force)
+ SIGNAL_HANDLER
+
if(ismecha(parent)) //items didn't drop the plates before and it causes erroneous behavior for the time being with collapsible helmets
for(var/i in 1 to amount)
new upgrade_item(get_turf(parent))
diff --git a/code/datums/components/beetlejuice.dm b/code/datums/components/beetlejuice.dm
index 42123fdef186e..82c94a3da64f1 100644
--- a/code/datums/components/beetlejuice.dm
+++ b/code/datums/components/beetlejuice.dm
@@ -34,19 +34,21 @@
update_regex()
/datum/component/beetlejuice/proc/say_react(datum/source, mob/speaker,message)
+ SIGNAL_HANDLER
+
if(!speaker || !message || !active)
return
var/found = R.Find(message)
if(found)
- var/occurences = 1
+ var/occurrences = 1
while(R.Find(message))
- occurences++
+ occurrences++
R.next = 1
if(!first_heard[speaker] || (first_heard[speaker] + max_delay < world.time))
first_heard[speaker] = world.time
count[speaker] = 0
- count[speaker] += occurences
+ count[speaker] += occurrences
if(count[speaker] >= min_count)
first_heard -= speaker
count -= speaker
@@ -57,4 +59,4 @@
var/atom/movable/AM = parent
do_teleport(AM,get_turf(target))
active = FALSE
- addtimer(VARSET_CALLBACK(src, active, TRUE), cooldown)
\ No newline at end of file
+ addtimer(VARSET_CALLBACK(src, active, TRUE), cooldown)
diff --git a/code/datums/components/butchering.dm b/code/datums/components/butchering.dm
index 87de3d9c95ba0..bc2bc60effe1d 100644
--- a/code/datums/components/butchering.dm
+++ b/code/datums/components/butchering.dm
@@ -23,6 +23,8 @@
RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/onItemAttack)
/datum/component/butchering/proc/onItemAttack(obj/item/source, mob/living/M, mob/living/user)
+ SIGNAL_HANDLER
+
if(user.a_intent == INTENT_HARM && M.stat == DEAD && (M.butcher_results || M.guaranteed_butcher_results)) //can we butcher it?
if(butchering_enabled && (can_be_blunt || source.is_sharp()))
INVOKE_ASYNC(src, .proc/startButcher, source, M, user)
diff --git a/code/datums/components/caltrop.dm b/code/datums/components/caltrop.dm
index 698a00c506532..83d8b2effe17e 100644
--- a/code/datums/components/caltrop.dm
+++ b/code/datums/components/caltrop.dm
@@ -3,8 +3,8 @@
var/max_damage
var/probability
var/flags
+ COOLDOWN_DECLARE(caltrop_cooldown)
- var/cooldown = 0
/datum/component/caltrop/Initialize(_min_damage = 0, _max_damage = 0, _probability = 100, _flags = NONE)
min_damage = _min_damage
@@ -52,7 +52,8 @@
damage *= 1.3
H.apply_damage(damage, BRUTE, picked_def_zone)
- if(cooldown < world.time - 10) //cooldown to avoid message spam.
+ if(COOLDOWN_FINISHED(src, caltrop_cooldown))
+ COOLDOWN_START(src, caltrop_cooldown, 1 SECONDS) //cooldown to avoid message spam.
if(!H.incapacitated(ignore_restraints = TRUE))
H.visible_message("[H] steps on [A].", \
"You step on [A]!")
@@ -60,7 +61,6 @@
H.visible_message("[H] slides on [A]!", \
"You slide on [A]!")
- cooldown = world.time
if(is_species(H, /datum/species/squid))
H.Paralyze(10)
else
diff --git a/code/datums/components/chasm.dm b/code/datums/components/chasm.dm
index 8bba40e8c7810..0aea9f08b2b4c 100644
--- a/code/datums/components/chasm.dm
+++ b/code/datums/components/chasm.dm
@@ -4,7 +4,8 @@
var/fall_message = "GAH! Ah... where are you?"
var/oblivion_message = "You stumble and stare into the abyss before you. It stares back, and you fall into the enveloping dark."
- var/static/list/falling_atoms = list() // Atoms currently falling into chasms
+ /// List of refs to falling objects -> how many levels deep we've fallen
+ var/static/list/falling_atoms = list()
var/static/list/forbidden_types = typecacheof(list(
/obj/singularity,
/obj/docking_port,
@@ -29,12 +30,14 @@
START_PROCESSING(SSobj, src) // process on create, in case stuff is still there
/datum/component/chasm/proc/Entered(datum/source, atom/movable/AM)
+ SIGNAL_HANDLER
+
START_PROCESSING(SSobj, src)
drop_stuff(AM)
/datum/component/chasm/process()
if (!drop_stuff())
- STOP_PROCESSING(SSobj, src)
+ return PROCESS_KILL
/datum/component/chasm/proc/is_safe()
//if anything matching this typecache is found in the chasm, we don't drop things
@@ -48,7 +51,6 @@
return LAZYLEN(found_safeties)
/datum/component/chasm/proc/drop_stuff(AM)
- . = 0
if (is_safe())
return FALSE
@@ -56,12 +58,13 @@
var/to_check = AM ? list(AM) : parent.contents
for (var/thing in to_check)
if (droppable(thing))
- . = 1
+ . = TRUE
INVOKE_ASYNC(src, .proc/drop, thing)
/datum/component/chasm/proc/droppable(atom/movable/AM)
+ var/datum/weakref/falling_ref = WEAKREF(AM)
// avoid an infinite loop, but allow falling a large distance
- if(falling_atoms[AM] && falling_atoms[AM] > 30)
+ if(falling_atoms[falling_ref] && falling_atoms[falling_ref] > 30)
return FALSE
if(!isliving(AM) && !isobj(AM))
return FALSE
@@ -87,10 +90,12 @@
return TRUE
/datum/component/chasm/proc/drop(atom/movable/AM)
+ var/datum/weakref/falling_ref = WEAKREF(AM)
//Make sure the item is still there after our sleep
- if(!AM || QDELETED(AM))
+ if(!AM || !falling_ref?.resolve())
+ falling_atoms -= falling_ref
return
- falling_atoms[AM] = (falling_atoms[AM] || 0) + 1
+ falling_atoms[falling_ref] = (falling_atoms[falling_ref] || 0) + 1
var/turf/T = target_turf
if(T)
@@ -102,7 +107,7 @@
var/mob/living/L = AM
L.Paralyze(100)
L.adjustBruteLoss(30)
- falling_atoms -= AM
+ falling_atoms -= falling_ref
else
// send to oblivion
@@ -132,7 +137,7 @@
var/mob/living/silicon/robot/S = AM
qdel(S.mmi)
- falling_atoms -= AM
+ falling_atoms -= falling_ref
qdel(AM)
if(AM && !QDELETED(AM)) //It's indestructible
var/atom/parent = src.parent
diff --git a/code/datums/components/construction.dm b/code/datums/components/construction.dm
index 01df44752c977..34fa7a0cd7c9c 100644
--- a/code/datums/components/construction.dm
+++ b/code/datums/components/construction.dm
@@ -20,6 +20,8 @@
update_parent(index)
/datum/component/construction/proc/examine(datum/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+
if(desc)
examine_list += desc
@@ -30,7 +32,9 @@
update_parent(index)
/datum/component/construction/proc/action(datum/source, obj/item/I, mob/living/user)
- return check_step(I, user)
+ SIGNAL_HANDLER
+
+ return INVOKE_ASYNC(src, .proc/check_step, I, user)
/datum/component/construction/proc/update_index(diff)
index += diff
diff --git a/code/datums/components/crafting/crafting.dm b/code/datums/components/crafting/crafting.dm
index 1e5247c9ae18f..278c7d9344d2b 100644
--- a/code/datums/components/crafting/crafting.dm
+++ b/code/datums/components/crafting/crafting.dm
@@ -4,7 +4,7 @@
/datum/component/personal_crafting/proc/create_mob_button(mob/user, client/CL)
var/datum/hud/H = user.hud_used
- var/obj/screen/craft/C = new()
+ var/atom/movable/screen/craft/C = new()
C.icon = H.ui_style
H.static_inventory += C
CL.screen += C
@@ -189,6 +189,20 @@
return ", missing tool."
return ", missing component."
+/datum/component/personal_crafting/proc/construct_item_ui(mob/user, datum/crafting_recipe/TR)
+ var/atom/movable/result = construct_item(user, TR)
+ if(!istext(result)) //We made an item and didn't get a fail message
+ if(ismob(user) && isitem(result)) //In case the user is actually possessing a non mob like a machine
+ user.put_in_hands(result)
+ else
+ result.forceMove(user.drop_location())
+ to_chat(user, "[TR.name] constructed.")
+ else
+ to_chat(user, "Construction failed[result]")
+ busy = FALSE
+ SStgui.update_uis(src)
+
+
/*Del reqs works like this:
Loop over reqs var of the recipe
@@ -311,9 +325,11 @@
Deletion.Cut(Deletion.len)
qdel(DL)
-/datum/component/personal_crafting/proc/component_ui_interact(obj/screen/craft/image, location, control, params, user)
+/datum/component/personal_crafting/proc/component_ui_interact(atom/movable/screen/craft/image, location, control, params, user)
+ SIGNAL_HANDLER
+
if(user == parent)
- ui_interact(user)
+ INVOKE_ASYNC(src, .proc/ui_interact, user)
/datum/component/personal_crafting/ui_state(mob/user)
return GLOB.not_incapacitated_turf_state
@@ -387,20 +403,15 @@
return
switch(action)
if("make")
+ if(busy) // Prevent potentially crafting multiple things at once
+ return
var/mob/user = usr
var/datum/crafting_recipe/TR = locate(params["recipe"]) in GLOB.crafting_recipes
+ if(!TR)
+ return
busy = TRUE
- ui_interact(user)
- var/atom/movable/result = construct_item(user, TR)
- if(!istext(result)) //We made an item and didn't get a fail message
- if(ismob(user) && isitem(result)) //In case the user is actually possessing a non mob like a machine
- user.put_in_hands(result)
- else
- result.forceMove(user.drop_location())
- to_chat(user, "[TR.name] constructed.")
- else
- to_chat(user, "Construction failed[result]")
- busy = FALSE
+ . = TRUE
+ INVOKE_ASYNC(src, .proc/construct_item_ui, user, TR)
if("toggle_recipes")
display_craftable_only = !display_craftable_only
. = TRUE
@@ -451,3 +462,7 @@
if(!learned_recipes)
learned_recipes = list()
learned_recipes |= R
+
+/datum/mind/proc/forget_crafting_recipe(R)
+ if(learned_recipes)
+ learned_recipes -= R
diff --git a/code/datums/components/crafting/recipes.dm b/code/datums/components/crafting/recipes.dm
index 4a1e7556b7b7f..703bd48383cb4 100644
--- a/code/datums/components/crafting/recipes.dm
+++ b/code/datums/components/crafting/recipes.dm
@@ -25,16 +25,6 @@
/datum/crafting_recipe/proc/check_requirements(mob/user, list/collected_requirements)
return TRUE
-/datum/crafting_recipe/pin_removal
- name = "Pin Removal"
- result = /obj/item/gun
- reqs = list(/obj/item/gun = 1)
- parts = list(/obj/item/gun = 1)
- tools = list(TOOL_WELDER, TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
- time = 50
- category = CAT_WEAPONRY
- subcategory = CAT_WEAPON
-
/datum/crafting_recipe/IED
name = "IED"
result = /obj/item/grenade/iedcasing
@@ -49,11 +39,11 @@
/datum/crafting_recipe/lance
name = "Explosive Lance (Grenade)"
- result = /obj/item/twohanded/spear
- reqs = list(/obj/item/twohanded/spear = 1,
+ result = /obj/item/spear/explosive
+ reqs = list(/obj/item/spear = 1,
/obj/item/grenade = 1)
- blacklist = list(/obj/item/twohanded/spear/explosive)
- parts = list(/obj/item/twohanded/spear = 1,
+ blacklist = list(/obj/item/spear/bonespear)
+ parts = list(/obj/item/spear = 1,
/obj/item/grenade = 1)
time = 15
category = CAT_WEAPONRY
@@ -409,7 +399,7 @@
/datum/crafting_recipe/chainsaw
name = "Chainsaw"
- result = /obj/item/twohanded/required/chainsaw
+ result = /obj/item/chainsaw
reqs = list(/obj/item/circular_saw = 1,
/obj/item/stack/cable_coil = 3,
/obj/item/stack/sheet/plasteel = 5)
@@ -420,7 +410,7 @@
/datum/crafting_recipe/spear
name = "Spear"
- result = /obj/item/twohanded/spear
+ result = /obj/item/spear
reqs = list(/obj/item/restraints/handcuffs/cable = 1,
/obj/item/shard = 1,
/obj/item/stack/rods = 1)
@@ -615,7 +605,7 @@
result = /obj/item/bombcore/chemical
reqs = list(
/obj/item/stock_parts/matter_bin = 1,
- /obj/item/twohanded/required/gibtonite = 1,
+ /obj/item/gibtonite = 1,
/obj/item/grenade/chem_grenade = 2
)
parts = list(/obj/item/stock_parts/matter_bin = 1, /obj/item/grenade/chem_grenade = 2)
@@ -759,7 +749,7 @@
/datum/crafting_recipe/bonespear
name = "Bone Spear"
- result = /obj/item/twohanded/bonespear
+ result = /obj/item/spear/bonespear
time = 30
reqs = list(/obj/item/stack/sheet/bone = 4,
/obj/item/stack/sheet/sinew = 1)
@@ -767,7 +757,7 @@
/datum/crafting_recipe/boneaxe
name = "Bone Axe"
- result = /obj/item/twohanded/fireaxe/boneaxe
+ result = /obj/item/fireaxe/boneaxe
time = 50
reqs = list(/obj/item/stack/sheet/bone = 6,
/obj/item/stack/sheet/sinew = 3)
@@ -780,23 +770,32 @@
result = /obj/structure/bonfire
category = CAT_PRIMAL
+/datum/crafting_recipe/skeleton_key
+ name = "Skeleton Key"
+ time = 30
+ reqs = list(/obj/item/stack/sheet/bone = 5)
+ result = /obj/item/skeleton_key
+ always_available = FALSE
+ category = CAT_PRIMAL
+
/datum/crafting_recipe/headpike
name = "Spike Head (Glass Spear)"
time = 65
- reqs = list(/obj/item/twohanded/spear = 1,
+ reqs = list(/obj/item/spear = 1,
/obj/item/bodypart/head = 1)
parts = list(/obj/item/bodypart/head = 1,
- /obj/item/twohanded/spear = 1)
+ /obj/item/spear = 1)
+ blacklist = list(/obj/item/spear/explosive, /obj/item/spear/bonespear)
result = /obj/structure/headpike
category = CAT_PRIMAL
/datum/crafting_recipe/headpikebone
name = "Spike Head (Bone Spear)"
time = 65
- reqs = list(/obj/item/twohanded/bonespear = 1,
+ reqs = list(/obj/item/spear/bonespear = 1,
/obj/item/bodypart/head = 1)
parts = list(/obj/item/bodypart/head = 1,
- /obj/item/twohanded/bonespear = 1)
+ /obj/item/spear/bonespear = 1)
result = /obj/structure/headpike/bone
category = CAT_PRIMAL
@@ -813,7 +812,7 @@
/datum/crafting_recipe/rcl
name = "Makeshift Rapid Cable Layer"
- result = /obj/item/twohanded/rcl/ghetto
+ result = /obj/item/rcl/ghetto
time = 40
tools = list(TOOL_WELDER, TOOL_SCREWDRIVER, TOOL_WRENCH)
reqs = list(/obj/item/stack/sheet/iron = 15)
@@ -916,7 +915,7 @@
name = "Shank"
reqs = list(/obj/item/shard = 1,
/obj/item/stack/cable_coil = 10) // 1 glass shard + 10 cable; needs a wirecutter to snip the cable.
- result = /obj/item/melee/shank
+ result = /obj/item/kitchen/knife/shank
tools = list(TOOL_WIRECUTTER)
time = 20
category = CAT_WEAPONRY
@@ -932,3 +931,48 @@
category = CAT_WEAPONRY
subcategory = CAT_WEAPON
tools = list(TOOL_WIRECUTTER)
+
+/datum/crafting_recipe/poppy_pin
+ name = "Poppy Pin"
+ result = /obj/item/clothing/accessory/poppy_pin
+ time = 5
+ reqs = list(/obj/item/stack/rods = 1,
+ /obj/item/reagent_containers/food/snacks/grown/poppy = 1)
+ category = CAT_MISC
+
+/datum/crafting_recipe/poppy_pin_removal
+ name = "Poppy Pin Removal"
+ result = /obj/item/reagent_containers/food/snacks/grown/poppy
+ time = 5
+ reqs = list(/obj/item/clothing/accessory/poppy_pin = 1)
+
+ category = CAT_MISC
+
+/datum/crafting_recipe/insulated_boxing_gloves
+ name = "Insulated Boxing Gloves"
+ result = /obj/item/clothing/gloves/boxing/yellow/insulated
+ time = 60
+ reqs = list(/obj/item/clothing/gloves/boxing = 1,
+ /obj/item/clothing/gloves/color/yellow = 1)
+
+ category = CAT_CLOTHING
+
+
+/datum/crafting_recipe/aquarium
+ name = "Aquarium"
+ result = /obj/structure/aquarium
+ time = 10 SECONDS
+ reqs = list(/obj/item/stack/sheet/iron = 15,
+ /obj/item/stack/sheet/glass = 10,
+ /obj/item/aquarium_kit = 1
+ )
+ category = CAT_MISC
+
+/datum/crafting_recipe/paper_cup
+ name= "Paper Cup"
+ result = /obj/item/reagent_containers/food/drinks/sillycup
+ time = 10
+ reqs = list(/obj/item/paper = 1)
+ category = CAT_MISC
+ tools = list(TOOL_WIRECUTTER)
+
diff --git a/code/datums/components/crafting/tailoring.dm b/code/datums/components/crafting/tailoring.dm
index 72e2e323b901d..188a9a5e64744 100644
--- a/code/datums/components/crafting/tailoring.dm
+++ b/code/datums/components/crafting/tailoring.dm
@@ -56,17 +56,17 @@
time = 20
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
reqs = list(/obj/item/clothing/glasses/hud/security = 1,
- /obj/item/clothing/glasses/sunglasses = 1,
+ /obj/item/clothing/glasses/sunglasses/advanced = 1,
/obj/item/stack/cable_coil = 5)
- category = CAT_CLOTHING
+ category = CAT_EYEWEAR
/datum/crafting_recipe/hudsunsecremoval
name = "Security HUD removal"
- result = /obj/item/clothing/glasses/sunglasses
+ result = /obj/item/clothing/glasses/sunglasses/advanced
time = 20
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
reqs = list(/obj/item/clothing/glasses/hud/security/sunglasses = 1)
- category = CAT_CLOTHING
+ category = CAT_EYEWEAR
/datum/crafting_recipe/hudsunmed
name = "Medical HUDsunglasses"
@@ -74,17 +74,17 @@
time = 20
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
reqs = list(/obj/item/clothing/glasses/hud/health = 1,
- /obj/item/clothing/glasses/sunglasses = 1,
+ /obj/item/clothing/glasses/sunglasses/advanced = 1,
/obj/item/stack/cable_coil = 5)
- category = CAT_CLOTHING
+ category = CAT_EYEWEAR
/datum/crafting_recipe/hudsunmedremoval
name = "Medical HUD removal"
- result = /obj/item/clothing/glasses/sunglasses
+ result = /obj/item/clothing/glasses/sunglasses/advanced
time = 20
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
reqs = list(/obj/item/clothing/glasses/hud/health/sunglasses = 1)
- category = CAT_CLOTHING
+ category = CAT_EYEWEAR
/datum/crafting_recipe/hudsundiag
name = "Diagnostic HUDsunglasses"
@@ -92,17 +92,17 @@
time = 20
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
reqs = list(/obj/item/clothing/glasses/hud/diagnostic = 1,
- /obj/item/clothing/glasses/sunglasses = 1,
+ /obj/item/clothing/glasses/sunglasses/advanced = 1,
/obj/item/stack/cable_coil = 5)
- category = CAT_CLOTHING
+ category = CAT_EYEWEAR
/datum/crafting_recipe/hudsundiagremoval
name = "Diagnostic HUD removal"
- result = /obj/item/clothing/glasses/sunglasses
+ result = /obj/item/clothing/glasses/sunglasses/advanced
time = 20
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
reqs = list(/obj/item/clothing/glasses/hud/diagnostic/sunglasses = 1)
- category = CAT_CLOTHING
+ category = CAT_EYEWEAR
/datum/crafting_recipe/beergoggles
name = "Beer Goggles"
@@ -110,9 +110,17 @@
time = 20
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
reqs = list(/obj/item/clothing/glasses/science = 1,
- /obj/item/clothing/glasses/sunglasses = 1,
+ /obj/item/clothing/glasses/sunglasses/advanced = 1,
/obj/item/stack/cable_coil = 5)
- category = CAT_CLOTHING
+ category = CAT_EYEWEAR
+
+/datum/crafting_recipe/beergogglesremoval
+ name = "Beer Goggles removal"
+ result = /obj/item/clothing/glasses/sunglasses/advanced
+ time = 20
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ reqs = list(/obj/item/clothing/glasses/sunglasses/advanced/reagent = 1)
+ category = CAT_EYEWEAR
/datum/crafting_recipe/sunhudscience
name = "Science Sunglasses"
@@ -120,17 +128,139 @@
time = 20
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
reqs = list(/obj/item/clothing/glasses/science = 1,
+ /obj/item/clothing/glasses/sunglasses/advanced = 1,
+ /obj/item/stack/cable_coil = 5)
+ category = CAT_EYEWEAR
+
+/datum/crafting_recipe/sunhudscienceremoval
+ name = "Science Sunglasses removal"
+ result = /obj/item/clothing/glasses/sunglasses/advanced
+ time = 20
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ reqs = list(/obj/item/clothing/glasses/science/sciencesun = 1)
+ category = CAT_EYEWEAR
+
+/datum/crafting_recipe/deghudsunsec
+ name = "Degraded Security HUDsunglasses"
+ result = /obj/item/clothing/glasses/hud/security/sunglasses/degraded
+ time = 20
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ reqs = list(/obj/item/clothing/glasses/hud/security = 1,
/obj/item/clothing/glasses/sunglasses = 1,
/obj/item/stack/cable_coil = 5)
- category = CAT_CLOTHING
+ category = CAT_EYEWEAR
-/datum/crafting_recipe/beergogglesremoval
- name = "Beer Goggles removal"
+/datum/crafting_recipe/deghudsunsecremoval
+ name = "Degraded Security HUD removal"
result = /obj/item/clothing/glasses/sunglasses
time = 20
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
- reqs = list(/obj/item/clothing/glasses/sunglasses/advanced/reagent = 1)
- category = CAT_CLOTHING
+ reqs = list(/obj/item/clothing/glasses/hud/security/sunglasses/degraded = 1)
+ category = CAT_EYEWEAR
+
+/datum/crafting_recipe/deghudsunmed
+ name = "Degraded Medical HUDsunglasses"
+ result = /obj/item/clothing/glasses/hud/health/sunglasses/degraded
+ time = 20
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ reqs = list(/obj/item/clothing/glasses/hud/health = 1,
+ /obj/item/clothing/glasses/sunglasses = 1,
+ /obj/item/stack/cable_coil = 5)
+ category = CAT_EYEWEAR
+
+/datum/crafting_recipe/deghudsunmedremoval
+ name = "Degraded Medical HUD removal"
+ result = /obj/item/clothing/glasses/sunglasses
+ time = 20
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ reqs = list(/obj/item/clothing/glasses/hud/health/sunglasses/degraded = 1)
+ category = CAT_EYEWEAR
+
+/datum/crafting_recipe/deghudsundiag
+ name = "Degraded Diagnostic HUDsunglasses"
+ result = /obj/item/clothing/glasses/hud/diagnostic/sunglasses/degraded
+ time = 20
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ reqs = list(/obj/item/clothing/glasses/hud/diagnostic = 1,
+ /obj/item/clothing/glasses/sunglasses = 1,
+ /obj/item/stack/cable_coil = 5)
+ category = CAT_EYEWEAR
+
+/datum/crafting_recipe/deghudsundiagremoval
+ name = "Degraded Diagnostic HUD removal"
+ result = /obj/item/clothing/glasses/sunglasses
+ time = 20
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ reqs = list(/obj/item/clothing/glasses/hud/diagnostic/sunglasses/degraded = 1)
+ category = CAT_EYEWEAR
+
+/datum/crafting_recipe/degsunhudscience
+ name = "Degraded Science Sunglasses"
+ result = /obj/item/clothing/glasses/science/sciencesun/degraded
+ time = 20
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ reqs = list(/obj/item/clothing/glasses/science = 1,
+ /obj/item/clothing/glasses/sunglasses = 1,
+ /obj/item/stack/cable_coil = 5)
+ category = CAT_EYEWEAR
+
+/datum/crafting_recipe/degsunhudscienceremoval
+ name = "Degraded Science Sunglasses removal"
+ result = /obj/item/clothing/glasses/sunglasses
+ time = 20
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ reqs = list(/obj/item/clothing/glasses/science/sciencesun/degraded = 1)
+ category = CAT_EYEWEAR
+
+/datum/crafting_recipe/hudpresmed
+ name = "Prescription Medical HUDglasses"
+ result = /obj/item/clothing/glasses/hud/health/prescription
+ time = 20
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ reqs = list(/obj/item/clothing/glasses/hud/health = 1,
+ /obj/item/clothing/glasses/regular/ = 1,
+ /obj/item/stack/cable_coil = 5)
+ category = CAT_EYEWEAR
+
+/datum/crafting_recipe/hudpressec
+ name = "Prescription Security HUDglasses"
+ result = /obj/item/clothing/glasses/hud/security/prescription
+ time = 20
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ reqs = list(/obj/item/clothing/glasses/hud/security = 1,
+ /obj/item/clothing/glasses/regular/ = 1,
+ /obj/item/stack/cable_coil = 5)
+ category = CAT_EYEWEAR
+
+/datum/crafting_recipe/hudpressci
+ name = "Prescription Science Goggles"
+ result = /obj/item/clothing/glasses/science/prescription
+ time = 20
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ reqs = list(/obj/item/clothing/glasses/science = 1,
+ /obj/item/clothing/glasses/regular/ = 1,
+ /obj/item/stack/cable_coil = 5)
+ category = CAT_EYEWEAR
+
+/datum/crafting_recipe/hudpresmeson
+ name = "Prescription Meson Scanner"
+ result = /obj/item/clothing/glasses/meson/prescription
+ time = 20
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ reqs = list(/obj/item/clothing/glasses/meson = 1,
+ /obj/item/clothing/glasses/regular/ = 1,
+ /obj/item/stack/cable_coil = 5)
+ category = CAT_EYEWEAR
+
+/datum/crafting_recipe/hudpresdiag
+ name = "Prescription Diagnostic HUDsunglasses"
+ result = /obj/item/clothing/glasses/hud/diagnostic/prescription
+ time = 20
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ reqs = list(/obj/item/clothing/glasses/hud/diagnostic = 1,
+ /obj/item/clothing/glasses/regular/ = 1,
+ /obj/item/stack/cable_coil = 5)
+ category = CAT_EYEWEAR
/datum/crafting_recipe/ghostsheet
name = "Ghost Sheet"
@@ -162,3 +292,34 @@
/obj/item/stack/sheet/glass = 1)
category = CAT_CLOTHING
+/datum/crafting_recipe/rainbowbunchcrown
+ name = "Rainbow Flower Crown"
+ result = /obj/item/clothing/head/rainbowbunchcrown/
+ time = 20
+ reqs = list(/obj/item/reagent_containers/food/snacks/grown/rainbow_flower = 5,
+ /obj/item/stack/cable_coil = 3)
+ category = CAT_CLOTHING
+
+/datum/crafting_recipe/sunflowercrown
+ name = "Sunflower Crown"
+ result = /obj/item/clothing/head/sunflowercrown/
+ time = 20
+ reqs = list(/obj/item/grown/sunflower = 5,
+ /obj/item/stack/cable_coil = 3)
+ category = CAT_CLOTHING
+
+/datum/crafting_recipe/poppycrown
+ name = "Poppy Crown"
+ result = /obj/item/clothing/head/poppycrown/
+ time = 20
+ reqs = list(/obj/item/reagent_containers/food/snacks/grown/poppy = 5,
+ /obj/item/stack/cable_coil = 3)
+ category = CAT_CLOTHING
+
+/datum/crafting_recipe/lilycrown
+ name = "Lily Crown"
+ result = /obj/item/clothing/head/lilycrown/
+ time = 20
+ reqs = list(/obj/item/reagent_containers/food/snacks/grown/poppy/lily = 3,
+ /obj/item/stack/cable_coil = 3)
+ category = CAT_CLOTHING
diff --git a/code/datums/components/deadchat_control.dm b/code/datums/components/deadchat_control.dm
new file mode 100644
index 0000000000000..31a00eb4daa2c
--- /dev/null
+++ b/code/datums/components/deadchat_control.dm
@@ -0,0 +1,106 @@
+#define DEMOCRACY_MODE "democracy"
+#define ANARCHY_MODE "anarchy"
+
+/datum/component/deadchat_control
+ dupe_mode = COMPONENT_DUPE_UNIQUE
+ var/timerid
+
+ var/list/datum/callback/inputs = list()
+ var/list/ckey_to_cooldown = list()
+ var/orbiters = list()
+ var/deadchat_mode
+ var/input_cooldown
+
+/datum/component/deadchat_control/Initialize(_deadchat_mode, _inputs, _input_cooldown = 12 SECONDS)
+ if(!isatom(parent))
+ return COMPONENT_INCOMPATIBLE
+ RegisterSignal(parent, COMSIG_ATOM_ORBIT_BEGIN, .proc/orbit_begin)
+ RegisterSignal(parent, COMSIG_ATOM_ORBIT_STOP, .proc/orbit_stop)
+ deadchat_mode = _deadchat_mode
+ inputs = _inputs
+ input_cooldown = _input_cooldown
+ if(deadchat_mode == DEMOCRACY_MODE)
+ timerid = addtimer(CALLBACK(src, .proc/democracy_loop), input_cooldown, TIMER_STOPPABLE | TIMER_LOOP)
+ notify_ghosts("[parent] is now deadchat controllable!", source = parent, action = NOTIFY_ORBIT, header="Something Interesting!")
+
+
+/datum/component/deadchat_control/Destroy(force, silent)
+ inputs = null
+ orbiters = null
+ ckey_to_cooldown = null
+ return ..()
+
+/datum/component/deadchat_control/proc/deadchat_react(mob/source, message)
+ message = lowertext(message)
+ if(!inputs[message])
+ return
+ if(deadchat_mode == ANARCHY_MODE)
+ var/cooldown = ckey_to_cooldown[source.ckey]
+ if(cooldown)
+ return MOB_DEADSAY_SIGNAL_INTERCEPT
+ inputs[message].Invoke()
+ ckey_to_cooldown[source.ckey] = TRUE
+ addtimer(CALLBACK(src, .proc/remove_cooldown, source.ckey), input_cooldown)
+ else if(deadchat_mode == DEMOCRACY_MODE)
+ ckey_to_cooldown[source.ckey] = message
+ return MOB_DEADSAY_SIGNAL_INTERCEPT
+
+/datum/component/deadchat_control/proc/remove_cooldown(ckey)
+ ckey_to_cooldown.Remove(ckey)
+
+/datum/component/deadchat_control/proc/democracy_loop()
+ if(QDELETED(parent) || deadchat_mode != DEMOCRACY_MODE)
+ deltimer(timerid)
+ return
+ var/result = count_democracy_votes()
+ if(!isnull(result))
+ inputs[result].Invoke()
+ var/message = "[parent] has done action [result]! New vote started. It will end in [input_cooldown/10] seconds."
+ for(var/M in orbiters)
+ to_chat(M, message)
+ else
+ var/message = "No votes were cast this cycle."
+ for(var/M in orbiters)
+ to_chat(M, message)
+
+/datum/component/deadchat_control/proc/count_democracy_votes()
+ if(!length(ckey_to_cooldown))
+ return
+ var/list/votes = list()
+ for(var/command in inputs)
+ votes["[command]"] = 0
+ for(var/vote in ckey_to_cooldown)
+ votes[ckey_to_cooldown[vote]]++
+ ckey_to_cooldown.Remove(vote)
+
+ // Solve which had most votes.
+ var/prev_value = 0
+ var/result
+ for(var/vote in votes)
+ if(votes[vote] > prev_value)
+ prev_value = votes[vote]
+ result = vote
+
+ if(result in inputs)
+ return result
+
+/datum/component/deadchat_control/vv_edit_var(var_name, var_value)
+ . = ..()
+ if(!.)
+ return
+ if(var_name != NAMEOF(src, deadchat_mode))
+ return
+ ckey_to_cooldown = list()
+ if(var_value == DEMOCRACY_MODE)
+ timerid = addtimer(CALLBACK(src, .proc/democracy_loop), input_cooldown, TIMER_STOPPABLE | TIMER_LOOP)
+ else
+ deltimer(timerid)
+
+/datum/component/deadchat_control/proc/orbit_begin(atom/source, atom/orbiter)
+ RegisterSignal(orbiter, COMSIG_MOB_DEADSAY, .proc/deadchat_react)
+ orbiters |= orbiter
+
+/datum/component/deadchat_control/proc/orbit_stop(atom/source, atom/orbiter)
+ if(orbiter in orbiters)
+ UnregisterSignal(orbiter, COMSIG_MOB_DEADSAY)
+ orbiters -= orbiter
\ No newline at end of file
diff --git a/code/datums/components/decal.dm b/code/datums/components/decal.dm
deleted file mode 100644
index 8fb3405af0d67..0000000000000
--- a/code/datums/components/decal.dm
+++ /dev/null
@@ -1,75 +0,0 @@
-/datum/component/decal
- dupe_mode = COMPONENT_DUPE_ALLOWED
- can_transfer = TRUE
- var/cleanable
- var/description
- var/mutable_appearance/pic
-
- var/first_dir // This only stores the dir arg from init
-
-/datum/component/decal/Initialize(_icon, _icon_state, _dir, _cleanable=CLEAN_NEVER, _color, _layer=TURF_LAYER, _description, _alpha=255)
- if(!isatom(parent) || !generate_appearance(_icon, _icon_state, _dir, _layer, _color, _alpha))
- return COMPONENT_INCOMPATIBLE
- first_dir = _dir
- description = _description
- cleanable = _cleanable
-
- apply()
-
-/datum/component/decal/RegisterWithParent()
- if(first_dir)
- RegisterSignal(parent, COMSIG_ATOM_DIR_CHANGE, .proc/rotate_react)
- if(cleanable != CLEAN_NEVER)
- RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_react)
- if(description)
- RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/examine)
-
-/datum/component/decal/UnregisterFromParent()
- UnregisterSignal(parent, list(COMSIG_ATOM_DIR_CHANGE, COMSIG_COMPONENT_CLEAN_ACT, COMSIG_PARENT_EXAMINE))
-
-/datum/component/decal/Destroy()
- remove()
- return ..()
-
-/datum/component/decal/PreTransfer()
- remove()
-
-/datum/component/decal/PostTransfer()
- remove()
- apply()
-
-/datum/component/decal/proc/generate_appearance(_icon, _icon_state, _dir, _layer, _color, _alpha)
- if(!_icon || !_icon_state)
- return FALSE
- // It has to be made from an image or dir breaks because of a byond bug
- var/temp_image = image(_icon, null, _icon_state, _layer, _dir)
- pic = new(temp_image)
- pic.color = _color
- pic.alpha = _alpha
- return TRUE
-
-/datum/component/decal/proc/apply(atom/thing)
- var/atom/master = thing || parent
- master.add_overlay(pic, TRUE)
- if(isitem(master))
- addtimer(CALLBACK(master, /obj/item/.proc/update_slot_icon), 0, TIMER_UNIQUE)
-
-/datum/component/decal/proc/remove(atom/thing)
- var/atom/master = thing || parent
- master.cut_overlay(pic, TRUE)
- if(isitem(master))
- addtimer(CALLBACK(master, /obj/item/.proc/update_slot_icon), 0, TIMER_UNIQUE)
-
-/datum/component/decal/proc/rotate_react(datum/source, old_dir, new_dir)
- if(old_dir == new_dir)
- return
- remove()
- pic.dir = turn(pic.dir, dir2angle(old_dir) - dir2angle(new_dir))
- apply()
-
-/datum/component/decal/proc/clean_react(datum/source, strength)
- if(strength >= cleanable)
- qdel(src)
-
-/datum/component/decal/proc/examine(datum/source, mob/user, list/examine_list)
- examine_list += description
diff --git a/code/datums/components/decals/blood.dm b/code/datums/components/decals/blood.dm
deleted file mode 100644
index 3114ddb24e9e4..0000000000000
--- a/code/datums/components/decals/blood.dm
+++ /dev/null
@@ -1,39 +0,0 @@
-/datum/component/decal/blood
- dupe_mode = COMPONENT_DUPE_UNIQUE
-
-/datum/component/decal/blood/Initialize(_icon, _icon_state, _dir, _cleanable=CLEAN_STRENGTH_BLOOD, _color, _layer=ABOVE_OBJ_LAYER)
- if(!isitem(parent))
- return COMPONENT_INCOMPATIBLE
- . = ..()
- RegisterSignal(parent, COMSIG_ATOM_GET_EXAMINE_NAME, .proc/get_examine_name)
-
-/datum/component/decal/blood/generate_appearance(_icon, _icon_state, _dir, _layer, _color)
- var/obj/item/I = parent
- if(!_icon)
- _icon = 'icons/effects/blood.dmi'
- if(!_icon_state)
- _icon_state = "itemblood"
- var/icon = initial(I.icon)
- var/icon_state = initial(I.icon_state)
- if(!icon || !icon_state)
- // It's something which takes on the look of other items, probably
- icon = I.icon
- icon_state = I.icon_state
- var/static/list/blood_splatter_appearances = list()
- //try to find a pre-processed blood-splatter. otherwise, make a new one
- var/index = "[REF(icon)]-[icon_state]"
- pic = blood_splatter_appearances[index]
-
- if(!pic)
- var/icon/blood_splatter_icon = icon(initial(I.icon), initial(I.icon_state), , 1) //we only want to apply blood-splatters to the initial icon_state for each object
- blood_splatter_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent)
- blood_splatter_icon.Blend(icon(_icon, _icon_state), ICON_MULTIPLY) //adds blood and the remaining white areas become transparant
- pic = mutable_appearance(blood_splatter_icon, initial(I.icon_state))
- blood_splatter_appearances[index] = pic
- return TRUE
-
-/datum/component/decal/blood/proc/get_examine_name(datum/source, mob/user, list/override)
- var/atom/A = parent
- override[EXAMINE_POSITION_ARTICLE] = A.gender == PLURAL? "some" : "a"
- override[EXAMINE_POSITION_BEFORE] = " blood-stained "
- return COMPONENT_EXNAME_CHANGED
diff --git a/code/datums/components/edit_complainer.dm b/code/datums/components/edit_complainer.dm
index bf52296e2cb74..632a7c6d7492e 100644
--- a/code/datums/components/edit_complainer.dm
+++ b/code/datums/components/edit_complainer.dm
@@ -19,5 +19,7 @@
RegisterSignal(SSdcs, COMSIG_GLOB_VAR_EDIT, .proc/var_edit_react)
/datum/component/edit_complainer/proc/var_edit_react(datum/source, list/arguments)
+ SIGNAL_HANDLER
+
var/atom/movable/master = parent
master.say(pick(say_lines))
diff --git a/code/datums/components/embedded.dm b/code/datums/components/embedded.dm
new file mode 100644
index 0000000000000..389815e36fe84
--- /dev/null
+++ b/code/datums/components/embedded.dm
@@ -0,0 +1,337 @@
+/*
+ This component is responsible for handling individual instances of embedded objects. The embeddable element is what allows an item to be embeddable and stores its embedding stats,
+ and when it impacts and meets the requirements to stick into something, it instantiates an embedded component. Once the item falls out, the component is destroyed, while the
+ element survives to embed another day.
+
+ - Carbon embedding has all the classical embedding behavior, and tracks more events and signals. The main behaviors and hooks to look for are:
+ -- Every process tick, there is a chance to randomly proc pain, controlled by pain_chance. There may also be a chance for the object to fall out randomly, per fall_chance
+ -- Every time the mob moves, there is a chance to proc jostling pain, controlled by jostle_chance (and only 50% as likely if the mob is walking or crawling)
+ -- Various signals hooking into carbon topic() and the embed removal surgery in order to handle removals.
+
+
+ In addition, there are 2 cases of embedding: embedding, and sticking
+
+ - Embedding involves harmful and dangerous embeds, whether they cause brute damage, stamina damage, or a mix. This is the default behavior for embeddings, for when something is "pointy"
+
+ - Sticking occurs when an item should not cause any harm while embedding (imagine throwing a sticky ball of tape at someone, rather than a shuriken). An item is considered "sticky"
+ when it has 0 random pain chance and 0 jostling chance. It's a bit arbitrary, but fairly straightforward.
+
+ Stickables differ from embeds in the following ways:
+ -- Text descriptors use phrasing like "X is stuck to Y" rather than "X is embedded in Y"
+ -- There is no slicing sound on impact
+ -- All damage checks and bloodloss are skipped
+
+*/
+
+
+/datum/component/embedded
+ dupe_mode = COMPONENT_DUPE_ALLOWED
+ var/obj/item/bodypart/limb
+ var/obj/item/weapon
+
+ // all of this stuff is explained in _DEFINES/combat.dm
+ var/embed_chance // not like we really need it once we're already stuck in but hey
+ var/fall_chance
+ var/pain_chance
+ var/pain_mult
+ var/max_damage_mult
+ var/remove_pain_mult
+ var/rip_time
+ var/ignore_throwspeed_threshold
+ var/jostle_chance
+ var/jostle_pain_mult
+ var/pain_stam_pct
+ var/armour_block
+
+ var/harmful
+
+/datum/component/embedded/Initialize(obj/item/I,
+ datum/thrownthing/throwingdatum,
+ obj/item/bodypart/part,
+ embed_chance = EMBED_CHANCE,
+ fall_chance = EMBEDDED_ITEM_FALLOUT,
+ pain_chance = EMBEDDED_PAIN_CHANCE,
+ pain_mult = EMBEDDED_PAIN_MULTIPLIER,
+ max_damage_mult = EMBEDDED_MAX_DAMAGE_MULTIPLIER,
+ remove_pain_mult = EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER,
+ rip_time = EMBEDDED_UNSAFE_REMOVAL_TIME,
+ ignore_throwspeed_threshold = FALSE,
+ jostle_chance = EMBEDDED_JOSTLE_CHANCE,
+ jostle_pain_mult = EMBEDDED_JOSTLE_PAIN_MULTIPLIER,
+ pain_stam_pct = EMBEDDED_PAIN_STAM_PCT,
+ armour_block = EMBEDDED_ARMOUR_BLOCK)
+
+ if(!iscarbon(parent) || !isitem(I))
+ return COMPONENT_INCOMPATIBLE
+
+ if(part)
+ limb = part
+ src.embed_chance = embed_chance
+ src.fall_chance = fall_chance
+ src.pain_chance = pain_chance
+ src.pain_mult = pain_mult
+ src.max_damage_mult = max_damage_mult
+ src.remove_pain_mult = remove_pain_mult
+ src.rip_time = rip_time
+ src.ignore_throwspeed_threshold = ignore_throwspeed_threshold
+ src.jostle_chance = jostle_chance
+ src.jostle_pain_mult = jostle_pain_mult
+ src.pain_stam_pct = pain_stam_pct
+ src.armour_block = armour_block
+ src.weapon = I
+
+ if(!weapon.isEmbedHarmless())
+ harmful = TRUE
+
+ weapon.embedded(parent, part)
+ START_PROCESSING(SSdcs, src)
+ var/mob/living/carbon/victim = parent
+
+ limb.embedded_objects |= weapon // on the inside... on the inside...
+ weapon.forceMove(victim)
+ RegisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING), .proc/weaponDeleted)
+ victim.visible_message("[weapon] [harmful ? "embeds" : "sticks"] itself [harmful ? "in" : "to"] [victim]'s [limb.name]!", "[weapon] [harmful ? "embeds" : "sticks"] itself [harmful ? "in" : "to"] your [limb.name]!")
+
+ if(harmful)
+ victim.throw_alert("embeddedobject", /atom/movable/screen/alert/embeddedobject)
+ playsound(victim,'sound/weapons/bladeslice.ogg', 40)
+ weapon.add_mob_blood(victim)//it embedded itself in you, of course it's bloody!
+ SEND_SIGNAL(victim, COMSIG_ADD_MOOD_EVENT, "embedded", /datum/mood_event/embedded)
+
+/datum/component/embedded/Destroy()
+ var/mob/living/carbon/victim = parent
+ if(victim && !victim.has_embedded_objects())
+ victim.clear_alert("embeddedobject")
+ SEND_SIGNAL(victim, COMSIG_CLEAR_MOOD_EVENT, "embedded")
+ if(weapon)
+ UnregisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING))
+ weapon = null
+ limb = null
+ return ..()
+
+/datum/component/embedded/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/jostleCheck)
+ RegisterSignal(parent, COMSIG_CARBON_EMBED_RIP, .proc/ripOut)
+ RegisterSignal(parent, COMSIG_CARBON_EMBED_REMOVAL, .proc/safeRemove)
+ RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/checkRemoval)
+ RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, .proc/tryPullOutOther)
+
+/datum/component/embedded/UnregisterFromParent()
+ UnregisterSignal(parent, list(COMSIG_MOVABLE_MOVED, COMSIG_CARBON_EMBED_RIP, COMSIG_CARBON_EMBED_REMOVAL, COMSIG_PARENT_ATTACKBY, COMSIG_ATOM_ATTACK_HAND))
+
+/datum/component/embedded/process(delta_time)
+ var/mob/living/carbon/victim = parent
+
+ if(!victim || !limb) // in case the victim and/or their limbs exploded (say, due to a sticky bomb)
+ weapon.forceMove(get_turf(weapon))
+ qdel(src)
+ return
+
+ if(victim.stat == DEAD)
+ return
+
+ var/damage = weapon.w_class * pain_mult
+ var/max_damage = weapon.w_class * max_damage_mult + weapon.throwforce
+ var/chance = DT_PROB_RATE(pain_chance / 100, delta_time) * 100
+ if(pain_stam_pct && victim.stam_paralyzed) //if it's a less-lethal embed, give them a break if they're already stamcritted
+ chance *= 0.2
+ damage *= 0.5
+ else if(victim.lying)
+ chance *= 0.2
+
+ if(harmful && prob(chance))
+ var/damage_left = max_damage - limb.get_damage()
+ var/damage_wanted = (1-pain_stam_pct) * damage
+ var/damage_to_deal = CLAMP(damage_wanted, 0, damage_left)
+ var/damage_as_stam = damage_wanted - damage_to_deal
+ if(!damage_to_deal)
+ to_chat(victim, "[weapon] embedded in your [limb.name] stings a little!")
+ else
+ limb.receive_damage(brute=damage_to_deal, stamina=(pain_stam_pct * damage) + damage_as_stam)
+ to_chat(victim, "[weapon] embedded in your [limb.name] hurts!")
+
+ var/fallchance_current = DT_PROB_RATE(fall_chance / 100, delta_time) * 100
+ if(prob(fallchance_current))
+ fallOut()
+
+////////////////////////////////////////
+////////////BEHAVIOR PROCS//////////////
+////////////////////////////////////////
+
+/// Called every time a carbon with a harmful embed moves, rolling a chance for the item to cause pain. The chance is halved if the carbon is crawling or walking.
+/datum/component/embedded/proc/jostleCheck()
+ SIGNAL_HANDLER
+
+ var/mob/living/carbon/victim = parent
+ var/chance = jostle_chance
+ if(victim.m_intent == MOVE_INTENT_WALK || victim.lying)
+ chance *= 0.5
+
+ if(harmful && prob(chance))
+ var/damage = weapon.w_class * jostle_pain_mult
+ limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage)
+ to_chat(victim, "[weapon] embedded in your [limb.name] jostles and stings!")
+
+
+/// Called when then item randomly falls out of a carbon. This handles the damage and descriptors, then calls safe_remove()
+/datum/component/embedded/proc/fallOut()
+ var/mob/living/carbon/victim = parent
+
+ if(harmful)
+ var/damage = weapon.w_class * remove_pain_mult
+ limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage)
+
+ victim.visible_message("[weapon] falls [harmful ? "out" : "off"] of [victim.name]'s [limb.name]!", "[weapon] falls [harmful ? "out" : "off"] of your [limb.name]!")
+ safeRemove()
+
+/// Called when a carbon with an object embedded/stuck to them inspects themselves and clicks the appropriate link to begin ripping the item out. This handles the ripping attempt, descriptors, and dealing damage, then calls safe_remove()
+/datum/component/embedded/proc/ripOut(datum/source, obj/item/I, obj/item/bodypart/limb)
+ SIGNAL_HANDLER
+
+ if(I != weapon || src.limb != limb)
+ return
+
+ var/mob/living/carbon/victim = parent
+ var/time_taken = rip_time * weapon.w_class
+ INVOKE_ASYNC(src, .proc/complete_rip_out, victim, I, limb, time_taken)
+
+/// everything async that ripOut used to do
+/datum/component/embedded/proc/complete_rip_out(mob/living/carbon/victim, obj/item/I, obj/item/bodypart/limb, time_taken)
+ victim.visible_message("[victim] attempts to remove [weapon] from [victim.p_their()] [limb.name].","You attempt to remove [weapon] from your [limb.name]... (It will take [DisplayTimeText(time_taken)].)")
+
+ if(!do_after(victim, time_taken, target = victim))
+ return
+ if(!weapon || !limb || weapon.loc != victim || !(weapon in limb.embedded_objects))
+ qdel(src)
+ return
+
+ if(harmful)
+ var/damage = weapon.w_class * remove_pain_mult
+ limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage) //It hurts to rip it out, get surgery you dingus.
+ victim.emote("scream")
+
+ victim.visible_message("[victim] successfully rips [weapon] [harmful ? "out" : "off"] of [victim.p_their()] [limb.name]!", "You successfully remove [weapon] from your [limb.name].")
+ safeRemove(victim)
+
+/// This proc handles the final step and actual removal of an embedded/stuck item from a carbon, whether or not it was actually removed safely.
+/// Pass TRUE for to_hands if we want it to go to the victim's hands when they pull it out
+/datum/component/embedded/proc/safeRemove(to_hands)
+ SIGNAL_HANDLER
+
+ var/mob/living/carbon/victim = parent
+ limb.embedded_objects -= weapon
+ UnregisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING)) // have to do it here otherwise we trigger weaponDeleted()
+
+ if(!weapon.unembedded()) // if it hasn't deleted itself due to drop del
+ UnregisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING))
+ if(to_hands)
+ INVOKE_ASYNC(to_hands, /mob.proc/put_in_hands, weapon)
+ else
+ INVOKE_ASYNC(weapon, /atom/movable.proc/forceMove, get_turf(victim))
+
+ qdel(src)
+
+/datum/component/embedded/proc/tryPullOutOther(mob/living/carbon/victim, mob/user)
+ SIGNAL_HANDLER
+
+ if(!user.IsAdvancedToolUser())
+ to_chat(user, "You don't have the dexterity to do this!")
+ return
+
+ if(istype(victim)) // check to see if the limb is actually exposed
+ var/mob/living/carbon/human/victim_human = victim
+ if(!victim_human.can_inject(user, TRUE, limb.body_zone, penetrate_thick = FALSE))
+ return TRUE
+
+ if(weapon.w_class <= WEIGHT_CLASS_SMALL)
+ to_chat(user, "[weapon] embedding in \the [limb.name] of [parent] is too small to pull out with your bare hands!")
+ return
+
+ INVOKE_ASYNC(src, .proc/pluckOut, user, 1, 2, "pulling out")
+ return COMPONENT_NO_ATTACK_HAND
+
+/datum/component/embedded/proc/checkRemoval(mob/living/carbon/victim, obj/item/I, mob/user)
+ SIGNAL_HANDLER
+
+ if(!istype(victim) || user.zone_selected != limb.body_zone || user.a_intent != INTENT_HELP)
+ return
+
+ var/damage_multiplier = 1
+ var/remove_verb = "removing"
+
+ switch(I.tool_behaviour)
+ if(TOOL_HEMOSTAT)
+ damage_multiplier = 0
+ remove_verb = "carefully removing"
+ if(TOOL_WIRECUTTER)
+ if(weapon.w_class >= WEIGHT_CLASS_NORMAL)
+ to_chat(user, "[weapon] is too large to extract with wirecutters!")
+ return
+ damage_multiplier = 0.5
+ if(TOOL_SCREWDRIVER)
+ if(weapon.w_class >= WEIGHT_CLASS_SMALL)
+ to_chat(user, "[weapon] is too large to dislodge with a screwdriver!")
+ return
+ damage_multiplier = 0.8
+ remove_verb = "dislodging"
+ else
+ return
+
+ if(ishuman(victim)) // check to see if the limb is actually exposed
+ var/mob/living/carbon/human/victim_human = victim
+ if(!victim_human.can_inject(user, TRUE, limb.body_zone, penetrate_thick = FALSE))
+ return TRUE
+
+ INVOKE_ASYNC(src, .proc/pluckOut, user, damage_multiplier, max(damage_multiplier, 0.2), remove_verb)
+ return COMPONENT_NO_AFTERATTACK
+
+/// The actual action for pulling out an embedded object with any tools that work
+/datum/component/embedded/proc/pluckOut(mob/user, damage_multiplier, time_multiplier, remove_verb)
+ var/mob/living/carbon/victim = parent
+
+ var/self_pluck = (user == victim)
+
+ if(self_pluck)
+ user.visible_message("[user] begins [remove_verb] [weapon] from [user.p_their()] [limb.name]", "You start [remove_verb] [weapon] from your [limb.name]...",\
+ vision_distance=COMBAT_MESSAGE_RANGE, ignored_mobs=victim)
+ else
+ user.visible_message("[user] begins [remove_verb] [weapon] from [victim]'s [limb.name]","You start [remove_verb] [weapon] from [victim]'s [limb.name]...", \
+ vision_distance=COMBAT_MESSAGE_RANGE, ignored_mobs=victim)
+ to_chat(victim, "[user] begins [remove_verb] [weapon] from your [limb.name]...")
+
+ //Pluck time
+ var/pluck_time = 4 SECONDS * weapon.w_class * time_multiplier
+ if(!do_after(user, pluck_time, target = victim))
+ if(self_pluck)
+ to_chat(user, "You fail to remove [weapon] from your [limb.name].")
+ else
+ to_chat(user, "You fail to remove [weapon] from [victim]'s [limb.name].")
+ to_chat(victim, "[user] fails to remove [weapon] from your [limb.name].")
+ return
+
+ //Removed succesfully
+ if(self_pluck)
+ to_chat(user, "You successfully remove [weapon] from your [limb.name].")
+ else
+ to_chat(user, "You successfully remove [weapon] from [victim]'s [limb.name].")
+ to_chat(victim, "[user] remove [weapon] from your [limb.name].")
+
+ //Apply damage
+ if(harmful && damage_multiplier)
+ var/damage = weapon.w_class * remove_pain_mult * damage_multiplier
+ limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage)
+ victim.emote("scream")
+
+ //Remove it
+ safeRemove(user)
+
+/// Something deleted or moved our weapon while it was embedded, how rude!
+/datum/component/embedded/proc/weaponDeleted()
+ SIGNAL_HANDLER
+
+ var/mob/living/carbon/victim = parent
+ limb.embedded_objects -= weapon
+
+ if(victim)
+ to_chat(victim, "\The [weapon] that was embedded in your [limb.name] disappears!")
+ qdel(src)
diff --git a/code/datums/components/empprotection.dm b/code/datums/components/empprotection.dm
index c85cdf31c7254..513370f3d5fa5 100644
--- a/code/datums/components/empprotection.dm
+++ b/code/datums/components/empprotection.dm
@@ -8,4 +8,6 @@
RegisterSignal(parent, list(COMSIG_ATOM_EMP_ACT), .proc/getEmpFlags)
/datum/component/empprotection/proc/getEmpFlags(datum/source, severity)
+ SIGNAL_HANDLER
+
return flags
diff --git a/code/datums/components/explodable.dm b/code/datums/components/explodable.dm
index 094dce211d430..6b8e2278016d2 100644
--- a/code/datums/components/explodable.dm
+++ b/code/datums/components/explodable.dm
@@ -5,7 +5,7 @@
var/light_impact_range = 2
var/flash_range = 3
var/equipped_slot //For items, lets us determine where things should be hit.
-
+
/datum/component/explodable/Initialize(devastation_range_override, heavy_impact_range_override, light_impact_range_override, flash_range_override)
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
@@ -20,8 +20,8 @@
RegisterSignal(parent, list(COMSIG_ITEM_ATTACK, COMSIG_ITEM_ATTACK_OBJ, COMSIG_ITEM_HIT_REACT), .proc/explodable_attack)
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/on_equip)
RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/on_drop)
-
-
+
+
if(devastation_range_override)
devastation_range = devastation_range_override
@@ -33,32 +33,46 @@
flash_range = flash_range_override
/datum/component/explodable/proc/explodable_insert_item(datum/source, obj/item/I, mob/M, silent = FALSE, force = FALSE)
+ SIGNAL_HANDLER
+
check_if_detonate(I)
/datum/component/explodable/proc/explodable_impact(datum/source, atom/hit_atom, datum/thrownthing/throwingdatum)
+ SIGNAL_HANDLER
+
check_if_detonate(hit_atom)
/datum/component/explodable/proc/explodable_bump(datum/source, atom/A)
+ SIGNAL_HANDLER
+
check_if_detonate(A)
///Called when you use this object to attack sopmething
/datum/component/explodable/proc/explodable_attack(datum/source, atom/movable/target, mob/living/user)
+ SIGNAL_HANDLER
+
check_if_detonate(target)
-///Called when you attack a specific body part of the thing this is equipped on. Useful for exploding pants.
+///Called when you attack a specific body part of the thing this is equipped on. Useful for exploding pants.
/datum/component/explodable/proc/explodable_attack_zone(datum/source, damage, damagetype, def_zone)
+ SIGNAL_HANDLER
+
if(!def_zone)
return
if(damagetype != BURN) //Don't bother if it's not fire.
return
if(!is_hitting_zone(def_zone)) //You didn't hit us! ha!
- return
+ return
detonate()
/datum/component/explodable/proc/on_equip(datum/source, mob/equipper, slot)
+ SIGNAL_HANDLER
+
RegisterSignal(equipper, COMSIG_MOB_APPLY_DAMGE, .proc/explodable_attack_zone)
/datum/component/explodable/proc/on_drop(datum/source, mob/user)
+ SIGNAL_HANDLER
+
UnregisterSignal(user, COMSIG_MOB_APPLY_DAMGE)
/// Checks if we're hitting the zone this component is covering
@@ -87,7 +101,7 @@
if(I.body_parts_covered & bodypart.body_part)
return TRUE
return FALSE
-
+
/datum/component/explodable/proc/check_if_detonate(target)
if(!isitem(target))
@@ -100,6 +114,8 @@
/// Expldoe and remove the object
/datum/component/explodable/proc/detonate()
+ SIGNAL_HANDLER
+
var/atom/A = parent
explosion(A, devastation_range, heavy_impact_range, light_impact_range, flash_range) //epic explosion time
qdel(A)
diff --git a/code/datums/components/footstep.dm b/code/datums/components/footstep.dm
index 9d1eeec219610..edfad6e20b5b7 100644
--- a/code/datums/components/footstep.dm
+++ b/code/datums/components/footstep.dm
@@ -11,6 +11,8 @@
RegisterSignal(parent, list(COMSIG_MOVABLE_MOVED), .proc/play_footstep)
/datum/component/footstep/proc/play_footstep()
+ SIGNAL_HANDLER
+
var/turf/open/T = get_turf(parent)
if(!istype(T))
return
@@ -93,11 +95,13 @@
TRUE,
GLOB.footstep[T.footstep][3] + e)
- if((!H.shoes && !feetCover)) //are we NOT wearing shoes
- if(H.dna.species.special_step_sounds)
- playsound(T, pick(H.dna.species.special_step_sounds), 50, TRUE)
- else
- playsound(T, pick(GLOB.barefootstep[T.barefootstep][1]),
- GLOB.barefootstep[T.barefootstep][2] * v,
- TRUE,
- GLOB.barefootstep[T.barefootstep][3] + e)
+ //Sound of wearing shoes always plays, special movement sound
+ // IE (server motors wont play bare footed.)
+ if(H.dna.species.special_step_sounds)
+ playsound(T, pick(H.dna.species.special_step_sounds), 50, TRUE)
+
+ else if((!H.shoes && !feetCover)) //are we NOT wearing shoes
+ playsound(T, pick(GLOB.barefootstep[T.barefootstep][1]),
+ GLOB.barefootstep[T.barefootstep][2] * v,
+ TRUE,
+ GLOB.barefootstep[T.barefootstep][3] + e)
diff --git a/code/datums/components/forced_gravity.dm b/code/datums/components/forced_gravity.dm
index 7f609c494572e..ba4bf6b27232d 100644
--- a/code/datums/components/forced_gravity.dm
+++ b/code/datums/components/forced_gravity.dm
@@ -12,9 +12,13 @@
gravity = forced_value
/datum/component/forced_gravity/proc/gravity_check(datum/source, turf/location, list/gravs)
+ SIGNAL_HANDLER
+
if(!ignore_space && isspaceturf(location))
return
gravs += gravity
/datum/component/forced_gravity/proc/turf_gravity_check(datum/source, atom/checker, list/gravs)
- return gravity_check(null, parent, gravs)
\ No newline at end of file
+ SIGNAL_HANDLER
+
+ return gravity_check(null, parent, gravs)
diff --git a/code/datums/components/forensics.dm b/code/datums/components/forensics.dm
index 737c401dda034..956e4da8a05d7 100644
--- a/code/datums/components/forensics.dm
+++ b/code/datums/components/forensics.dm
@@ -43,8 +43,6 @@
/datum/component/forensics/proc/wipe_blood_DNA()
blood_DNA = null
- if(isitem(parent))
- qdel(parent.GetComponent(/datum/component/decal/blood))
return TRUE
/datum/component/forensics/proc/wipe_fibers()
@@ -52,6 +50,8 @@
return TRUE
/datum/component/forensics/proc/clean_act(datum/source, strength)
+ SIGNAL_HANDLER
+
if(strength >= CLEAN_STRENGTH_FINGERPRINTS)
wipe_fingerprints()
if(strength >= CLEAN_STRENGTH_BLOOD)
@@ -72,7 +72,7 @@
if(!iscameramob(M))
return
if(isaicamera(M))
- var/mob/camera/aiEye/ai_camera = M
+ var/mob/camera/ai_eye/ai_camera = M
if(!ai_camera.ai)
return
M = ai_camera.ai
@@ -144,7 +144,7 @@
if(!iscameramob(M))
return
if(isaicamera(M))
- var/mob/camera/aiEye/ai_camera = M
+ var/mob/camera/ai_eye/ai_camera = M
if(!ai_camera.ai)
return
M = ai_camera.ai
@@ -181,4 +181,6 @@
return
if(!length(blood_DNA))
return
- parent.LoadComponent(/datum/component/decal/blood)
+ if(isitem(parent))
+ var/obj/item/I = parent
+ I.AddElement(/datum/element/decal/blood, initial(I.icon) || I.icon, initial(I.icon_state) || I.icon_state)
diff --git a/code/datums/components/gps.dm b/code/datums/components/gps.dm
index b6e7722d1aeb6..683a964b8c010 100644
--- a/code/datums/components/gps.dm
+++ b/code/datums/components/gps.dm
@@ -36,15 +36,21 @@ GLOBAL_LIST_EMPTY(GPS_list)
///Called on COMSIG_ITEM_ATTACK_SELF
/datum/component/gps/item/proc/interact(datum/source, mob/user)
+ SIGNAL_HANDLER
+
if(user)
- ui_interact(user)
+ INVOKE_ASYNC(src, .proc/ui_interact, user)
///Called on COMSIG_PARENT_EXAMINE
/datum/component/gps/item/proc/on_examine(datum/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+
examine_list += "Alt-click to switch it [tracking ? "off":"on"]."
///Called on COMSIG_ATOM_EMP_ACT
/datum/component/gps/item/proc/on_emp_act(datum/source, severity)
+ SIGNAL_HANDLER
+
emped = TRUE
var/atom/A = parent
A.cut_overlay("working")
@@ -61,7 +67,10 @@ GLOBAL_LIST_EMPTY(GPS_list)
///Calls toggletracking
/datum/component/gps/item/proc/on_AltClick(datum/source, mob/user)
+ SIGNAL_HANDLER
+
toggletracking(user)
+ ui_update()
///Toggles the tracking for the gps
/datum/component/gps/item/proc/toggletracking(mob/user)
@@ -106,22 +115,21 @@ GLOBAL_LIST_EMPTY(GPS_list)
var/turf/curr = get_turf(parent)
data["currentArea"] = "[get_area_name(curr, TRUE)]"
- data["currentCoords"] = "[curr.x], [curr.y], [curr.z]"
+ data["currentCoords"] = "[curr.x], [curr.y], [curr.get_virtual_z_level()]"
var/list/signals = list()
- data["signals"] = list()
for(var/gps in GLOB.GPS_list)
var/datum/component/gps/G = gps
if(G.emped || !G.tracking || G == src)
continue
var/turf/pos = get_turf(G.parent)
- if(!pos || !global_mode && pos.z != curr.z)
+ if(!pos || !global_mode && pos.get_virtual_z_level() != curr.get_virtual_z_level())
continue
var/list/signal = list()
signal["entrytag"] = G.gpstag //Name or 'tag' of the GPS
- signal["coords"] = "[pos.x], [pos.y], [pos.z]"
- if(pos.z == curr.z) //Distance/Direction calculations for same z-level only
+ signal["coords"] = "[pos.x], [pos.y], [pos.get_virtual_z_level()]"
+ if(pos.get_virtual_z_level() == curr.get_virtual_z_level()) //Distance/Direction calculations for same z-level only
signal["dist"] = max(get_dist(curr, pos), 0) //Distance between the src and remote GPS turfs
signal["degrees"] = round(Get_Angle(curr, pos)) //0-360 degree directional bearing, for more precision.
signals += list(signal) //Add this signal to the list of signals
diff --git a/code/datums/components/heirloom.dm b/code/datums/components/heirloom.dm
index 72b28b125dd6a..69f8fb817b98a 100644
--- a/code/datums/components/heirloom.dm
+++ b/code/datums/components/heirloom.dm
@@ -12,6 +12,8 @@
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/examine)
/datum/component/heirloom/proc/examine(datum/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+
if(user.mind == owner)
examine_list += "It is your precious [family_name] family heirloom. Keep it safe!"
else if(isobserver(user))
diff --git a/code/datums/components/honkspam.dm b/code/datums/components/honkspam.dm
index 73b5e3335aad1..9f17f372b04f1 100644
--- a/code/datums/components/honkspam.dm
+++ b/code/datums/components/honkspam.dm
@@ -15,6 +15,8 @@
spam_flag = FALSE
/datum/component/honkspam/proc/interact(mob/user)
+ SIGNAL_HANDLER
+
if(!spam_flag)
spam_flag = TRUE
var/obj/item/parent_item = parent
diff --git a/code/datums/components/infective.dm b/code/datums/components/infective.dm
index 57bb8e0ae60a4..930bf51c3e942 100644
--- a/code/datums/components/infective.dm
+++ b/code/datums/components/infective.dm
@@ -33,19 +33,27 @@
RegisterSignal(parent, COMSIG_GIBS_STREAK, .proc/try_infect_streak)
/datum/component/infective/proc/try_infect_eat(datum/source, mob/living/eater, mob/living/feeder)
+ SIGNAL_HANDLER
+
for(var/V in diseases)
eater.ForceContractDisease(V)
try_infect(feeder, BODY_ZONE_L_ARM)
/datum/component/infective/proc/clean(datum/source, clean_strength)
+ SIGNAL_HANDLER
+
if(clean_strength >= min_clean_strength)
qdel(src)
/datum/component/infective/proc/try_infect_buckle(datum/source, mob/M, force)
+ SIGNAL_HANDLER
+
if(isliving(M))
try_infect(M)
/datum/component/infective/proc/try_infect_collide(datum/source, atom/A)
+ SIGNAL_HANDLER
+
var/atom/movable/P = parent
if(P.throwing)
//this will be handled by try_infect_impact_zone()
@@ -54,18 +62,26 @@
try_infect(A)
/datum/component/infective/proc/try_infect_impact_zone(datum/source, mob/living/target, hit_zone)
+ SIGNAL_HANDLER
+
try_infect(target, hit_zone)
/datum/component/infective/proc/try_infect_attack_zone(datum/source, mob/living/carbon/target, mob/living/user, hit_zone)
+ SIGNAL_HANDLER
+
try_infect(user, BODY_ZONE_L_ARM)
try_infect(target, hit_zone)
/datum/component/infective/proc/try_infect_attack(datum/source, mob/living/target, mob/living/user)
+ SIGNAL_HANDLER
+
if(!iscarbon(target)) //this case will be handled by try_infect_attack_zone
try_infect(target)
try_infect(user, BODY_ZONE_L_ARM)
/datum/component/infective/proc/try_infect_equipped(datum/source, mob/living/L, slot)
+ SIGNAL_HANDLER
+
var/old_permeability
if(isitem(parent))
//if you are putting an infective item on, it obviously will not protect you, so set its permeability high enough that it will never block ContactContractDisease()
@@ -80,10 +96,14 @@
I.permeability_coefficient = old_permeability
/datum/component/infective/proc/try_infect_crossed(datum/source, atom/movable/M)
+ SIGNAL_HANDLER
+
if(isliving(M))
try_infect(M, BODY_ZONE_PRECISE_L_FOOT)
/datum/component/infective/proc/try_infect_streak(datum/source, list/directions, list/output_diseases)
+ SIGNAL_HANDLER
+
output_diseases |= diseases
/datum/component/infective/proc/try_infect(mob/living/L, target_zone)
@@ -91,7 +111,9 @@
L.ContactContractDisease(V, target_zone)
/datum/component/infective/proc/extrapolation(datum/source, mob/user, var/obj/item/extrapolator/E, scan = TRUE)
+ SIGNAL_HANDLER
+
if(scan)
E.scan(source, diseases, user)
else
- E.extrapolate(source, diseases, user)
+ INVOKE_ASYNC(E, /obj/item/extrapolator.proc/extrapolate, source, diseases, user)
diff --git a/code/datums/components/jousting.dm b/code/datums/components/jousting.dm
index ae96fcb4d0b0a..4a417d8b5acaa 100644
--- a/code/datums/components/jousting.dm
+++ b/code/datums/components/jousting.dm
@@ -23,16 +23,22 @@
RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/on_attack)
/datum/component/jousting/proc/on_equip(datum/source, mob/user, slot)
+ SIGNAL_HANDLER
+
RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/mob_move, TRUE)
current_holder = user
/datum/component/jousting/proc/on_drop(datum/source, mob/user)
+ SIGNAL_HANDLER
+
UnregisterSignal(user, COMSIG_MOVABLE_MOVED)
current_holder = null
current_direction = NONE
current_tile_charge = 0
/datum/component/jousting/proc/on_attack(datum/source, mob/living/target, mob/user)
+ SIGNAL_HANDLER
+
if(user != current_holder)
return
var/current = current_tile_charge
@@ -41,7 +47,7 @@
if((requires_mount && ((requires_mob_riding && !ismob(user.buckled)) || (!user.buckled))) || !current_direction || (current_tile_charge < min_tile_charge))
return
var/turf/target_turf = get_step(user, current_direction)
- if(target in range(1, target_turf))
+ if(get_dist(target, target_turf) <= 1)
var/knockdown_chance = (target_buckled? mounted_knockdown_chance_per_tile : unmounted_knockdown_chance_per_tile) * current
var/knockdown_time = (target_buckled? mounted_knockdown_time : unmounted_knockdown_time)
var/damage = (target_buckled? mounted_damage_boost_per_tile : unmounted_damage_boost_per_tile) * current
@@ -59,6 +65,8 @@
user.visible_message("[msg]!")
/datum/component/jousting/proc/mob_move(datum/source, newloc, dir)
+ SIGNAL_HANDLER
+
if(!current_holder || (requires_mount && ((requires_mob_riding && !ismob(current_holder.buckled)) || (!current_holder.buckled))))
return
if(dir != current_direction)
diff --git a/code/datums/components/knockoff.dm b/code/datums/components/knockoff.dm
index a36169e6dfd37..770f72cfea5b0 100644
--- a/code/datums/components/knockoff.dm
+++ b/code/datums/components/knockoff.dm
@@ -11,7 +11,7 @@
RegisterSignal(parent, COMSIG_ITEM_DROPPED,.proc/OnDropped)
src.knockoff_chance = knockoff_chance
-
+
if(zone_override)
target_zones = zone_override
@@ -19,6 +19,8 @@
src.slots_knockoffable = slots_knockoffable
/datum/component/knockoff/proc/Knockoff(mob/living/attacker,zone)
+ SIGNAL_HANDLER
+
var/obj/item/I = parent
var/mob/living/carbon/human/wearer = I.loc
if(!istype(wearer))
@@ -33,6 +35,8 @@
wearer.visible_message("[attacker] knocks off [wearer]'s [I.name]!","[attacker] knocks off your [I.name]!")
/datum/component/knockoff/proc/OnEquipped(datum/source, mob/living/carbon/human/H,slot)
+ SIGNAL_HANDLER
+
if(!istype(H))
return
if(slots_knockoffable && !(slot in slots_knockoffable))
@@ -41,4 +45,6 @@
RegisterSignal(H, COMSIG_HUMAN_DISARM_HIT, .proc/Knockoff, TRUE)
/datum/component/knockoff/proc/OnDropped(datum/source, mob/living/M)
- UnregisterSignal(M, COMSIG_HUMAN_DISARM_HIT)
\ No newline at end of file
+ SIGNAL_HANDLER
+
+ UnregisterSignal(M, COMSIG_HUMAN_DISARM_HIT)
diff --git a/code/datums/components/lockon_aiming.dm b/code/datums/components/lockon_aiming.dm
index fef0dd0ef0c9f..2b788b1030d4a 100644
--- a/code/datums/components/lockon_aiming.dm
+++ b/code/datums/components/lockon_aiming.dm
@@ -18,6 +18,7 @@
var/list/last_location
var/datum/callback/on_lock
var/datum/callback/can_target_callback
+ var/aiming_params
/datum/component/lockon_aiming/Initialize(range, list/typecache, amount, list/immune, datum/callback/when_locked, icon, icon_state, datum/callback/target_callback)
if(!ismob(parent))
@@ -46,14 +47,10 @@
if(icon_state)
lock_icon_state = icon_state
generate_lock_visuals()
- var/mob/M = parent
- LAZYOR(M.mousemove_intercept_objects, src)
START_PROCESSING(SSfastprocess, src)
/datum/component/lockon_aiming/Destroy()
- var/mob/M = parent
clear_visuals()
- LAZYREMOVE(M.mousemove_intercept_objects, src)
STOP_PROCESSING(SSfastprocess, src)
return ..()
@@ -119,27 +116,6 @@
return
LAZYREMOVE(immune_weakrefs, A.weak_reference)
-/datum/component/lockon_aiming/onMouseMove(object,location,control,params)
- var/mob/M = parent
- if(!istype(M) || !M.client)
- return
- var/datum/position/P = mouse_absolute_datum_map_position_from_client(M.client)
- if(!P)
- return
- var/turf/T = P.return_turf()
- LAZYINITLIST(last_location)
- if(length(last_location) == 3 && last_location[1] == T.x && last_location[2] == T.y && last_location[3] == T.z)
- return //Same turf, don't bother.
- if(last_location)
- last_location.Cut()
- else
- last_location = list()
- last_location.len = 3
- last_location[1] = T.x
- last_location[2] = T.y
- last_location[3] = T.z
- autolock()
-
/datum/component/lockon_aiming/process()
if(update_disabled)
return
@@ -163,7 +139,7 @@
var/mob/M = parent
if(!M.client)
return FALSE
- var/datum/position/current = mouse_absolute_datum_map_position_from_client(M.client)
+ var/datum/position/current = mouse_absolute_datum_map_position_from_client(M.client, aiming_params)
var/turf/target = current.return_turf()
var/list/atom/targets = get_nearest(target, target_typecache, lock_amount, lock_cursor_range)
if(targets == LOCKON_IGNORE_RESULT)
diff --git a/code/datums/components/magnetic_catch.dm b/code/datums/components/magnetic_catch.dm
index 4defe936e5ea4..ac8a5ee6fc521 100644
--- a/code/datums/components/magnetic_catch.dm
+++ b/code/datums/components/magnetic_catch.dm
@@ -16,19 +16,31 @@
RegisterSignal(i, COMSIG_MOVABLE_PRE_THROW, .proc/throw_react)
/datum/component/magnetic_catch/proc/examine(datum/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+
examine_list += "It has been installed with inertia dampening to prevent coffee spills."
/datum/component/magnetic_catch/proc/crossed_react(datum/source, atom/movable/thing)
+ SIGNAL_HANDLER
+
RegisterSignal(thing, COMSIG_MOVABLE_PRE_THROW, .proc/throw_react, TRUE)
/datum/component/magnetic_catch/proc/uncrossed_react(datum/source, atom/movable/thing)
+ SIGNAL_HANDLER
+
UnregisterSignal(thing, COMSIG_MOVABLE_PRE_THROW)
/datum/component/magnetic_catch/proc/entered_react(datum/source, atom/movable/thing, atom/oldloc)
+ SIGNAL_HANDLER
+
RegisterSignal(thing, COMSIG_MOVABLE_PRE_THROW, .proc/throw_react, TRUE)
/datum/component/magnetic_catch/proc/exited_react(datum/source, atom/movable/thing, atom/newloc)
+ SIGNAL_HANDLER
+
UnregisterSignal(thing, COMSIG_MOVABLE_PRE_THROW)
/datum/component/magnetic_catch/proc/throw_react(datum/source, list/arguments)
+ SIGNAL_HANDLER
+
return COMPONENT_CANCEL_THROW
diff --git a/code/datums/components/manual_blinking.dm b/code/datums/components/manual_blinking.dm
index d54291cee2cb8..0c5979af02b3c 100644
--- a/code/datums/components/manual_blinking.dm
+++ b/code/datums/components/manual_blinking.dm
@@ -7,7 +7,7 @@
var/last_blink
var/check_every = 20 SECONDS //we scp now
var/grace_period = 6 SECONDS
- var/damage_rate = 1 // organ damage taken per tick
+ var/damage_rate = 0.5 // organ damage taken per second
var/list/valid_emotes = list(/datum/emote/living/carbon/blink, /datum/emote/living/carbon/blink_r)
var/datum/action/blink/button = new
@@ -56,12 +56,16 @@
UnregisterSignal(parent, COMSIG_MOB_DEATH)
/datum/component/manual_blinking/proc/restart()
+ SIGNAL_HANDLER
+
START_PROCESSING(SSdcs, src)
/datum/component/manual_blinking/proc/pause()
+ SIGNAL_HANDLER
+
STOP_PROCESSING(SSdcs, src)
-/datum/component/manual_blinking/process()
+/datum/component/manual_blinking/process(delta_time)
var/mob/living/carbon/C = parent
if(world.time > (last_blink + check_every + grace_period))
@@ -69,13 +73,15 @@
to_chat(C, "Your eyes begin to wither, you need to blink!")
warn_dying = TRUE
- E.applyOrganDamage(damage_rate)
+ E.applyOrganDamage(damage_rate * delta_time)
else if(world.time > (last_blink + check_every))
if(!warn_grace)
to_chat(C, "You feel a need to blink!")
warn_grace = TRUE
/datum/component/manual_blinking/proc/check_added_organ(mob/who_cares, obj/item/organ/O)
+ SIGNAL_HANDLER
+
var/obj/item/organ/eyes/new_eyes = O
if(istype(new_eyes,/obj/item/organ/eyes))
@@ -83,6 +89,8 @@
START_PROCESSING(SSdcs, src)
/datum/component/manual_blinking/proc/check_removed_organ(mob/who_cares, obj/item/organ/O)
+ SIGNAL_HANDLER
+
var/obj/item/organ/eyes/bye_beyes = O // oh come on, that's pretty good
if(istype(bye_beyes, /obj/item/organ/eyes))
@@ -90,6 +98,8 @@
STOP_PROCESSING(SSdcs, src)
/datum/component/manual_blinking/proc/check_emote(mob/living/carbon/user, datum/emote/emote)
+ SIGNAL_HANDLER
+
if(emote.type in valid_emotes)
warn_grace = FALSE
warn_dying = FALSE
diff --git a/code/datums/components/manual_breathing.dm b/code/datums/components/manual_breathing.dm
index 0be40118c6144..9a3871ce11c68 100644
--- a/code/datums/components/manual_breathing.dm
+++ b/code/datums/components/manual_breathing.dm
@@ -7,7 +7,7 @@
var/last_breath
var/check_every = 12 SECONDS
var/grace_period = 6 SECONDS
- var/damage_rate = 1 // organ damage taken per tick
+ var/damage_rate = 0.5 // organ damage taken per second
var/datum/emote/next_breath_type = /datum/emote/inhale
var/datum/action/breathe/button = new
@@ -66,12 +66,16 @@
UnregisterSignal(parent, COMSIG_MOB_DEATH)
/datum/component/manual_breathing/proc/restart()
+ SIGNAL_HANDLER
+
START_PROCESSING(SSdcs, src)
/datum/component/manual_breathing/proc/pause()
+ SIGNAL_HANDLER
+
STOP_PROCESSING(SSdcs, src)
-/datum/component/manual_breathing/process()
+/datum/component/manual_breathing/process(delta_time)
var/mob/living/carbon/C = parent
var/next_text = initial(next_breath_type.key)
@@ -80,7 +84,7 @@
to_chat(C, "You begin to suffocate, you need to [next_text]!")
warn_dying = TRUE
- L.applyOrganDamage(damage_rate)
+ L.applyOrganDamage(damage_rate * delta_time)
C.losebreath += 0.8
else if(world.time > (last_breath + check_every))
if(!warn_grace)
@@ -88,6 +92,8 @@
warn_grace = TRUE
/datum/component/manual_breathing/proc/check_added_organ(mob/who_cares, obj/item/organ/O)
+ SIGNAL_HANDLER
+
var/obj/item/organ/eyes/new_lungs = O
if(istype(new_lungs,/obj/item/organ/lungs))
@@ -95,6 +101,8 @@
START_PROCESSING(SSdcs, src)
/datum/component/manual_breathing/proc/check_removed_organ(mob/who_cares, obj/item/organ/O)
+ SIGNAL_HANDLER
+
var/obj/item/organ/lungs/old_lungs = O
if(istype(old_lungs, /obj/item/organ/lungs))
@@ -102,6 +110,8 @@
STOP_PROCESSING(SSdcs, src)
/datum/component/manual_breathing/proc/check_emote(mob/living/carbon/user, datum/emote/emote)
+ SIGNAL_HANDLER
+
if(emote.type == next_breath_type)
if(next_breath_type == /datum/emote/inhale)
next_breath_type = /datum/emote/exhale
diff --git a/code/datums/components/material_container.dm b/code/datums/components/material_container.dm
index 4db6612a84a98..361c3e1b014e9 100644
--- a/code/datums/components/material_container.dm
+++ b/code/datums/components/material_container.dm
@@ -46,6 +46,8 @@
materials[M] = 0
/datum/component/material_container/proc/OnExamine(datum/source, mob/user)
+ SIGNAL_HANDLER
+
if(show_on_examine)
for(var/I in materials)
var/datum/material/M = I
@@ -55,6 +57,8 @@
/// Proc that allows players to fill the parent with mats
/datum/component/material_container/proc/OnAttackBy(datum/source, obj/item/I, mob/living/user)
+ SIGNAL_HANDLER
+
var/list/tc = allowed_typecache
if(disable_attackby)
return
@@ -104,6 +108,7 @@
I.forceMove(user.drop_location())
else
to_chat(user, "You insert a material total of [inserted] into [parent].")
+ SEND_SIGNAL(I, COMSIG_OBJ_DECONSTRUCT) //Help prevent using material ingestors to void storage items.
qdel(I)
if(after_insert)
after_insert.Invoke(I.type, last_inserted_id, inserted)
@@ -112,7 +117,7 @@
/// Proc specifically for inserting items, returns the amount of materials entered.
/datum/component/material_container/proc/insert_item(obj/item/I, var/multiplier = 1, stack_amt)
- if(!I)
+ if(QDELETED(I))
return FALSE
if(istype(I, /obj/item/stack))
return insert_stack(I, stack_amt, multiplier)
@@ -137,7 +142,7 @@
return primary_mat
/// Proc for putting a stack inside of the container
-/datum/component/material_container/proc/insert_stack(obj/item/stack/S, amt, multiplier = 1)
+/datum/component/material_container/proc/insert_stack(obj/item/stack/S, amt, multiplier = 1)
if(isnull(amt))
amt = S.amount
@@ -160,7 +165,7 @@
return amt
/// For inserting an amount of material
-/datum/component/material_container/proc/insert_amount_mat(amt, var/datum/material/mat)
+/datum/component/material_container/proc/insert_amount_mat(amt, var/datum/material/mat)
if(!istype(mat))
mat = getmaterialref(mat)
if(amt > 0 && has_space(amt))
@@ -175,7 +180,7 @@
return FALSE
/// Uses an amount of a specific material, effectively removing it.
-/datum/component/material_container/proc/use_amount_mat(amt, var/datum/material/mat)
+/datum/component/material_container/proc/use_amount_mat(amt, var/datum/material/mat)
if(!istype(mat))
mat = getmaterialref(mat)
var/amount = materials[mat]
@@ -187,7 +192,7 @@
return FALSE
/// Proc for transfering materials to another container.
-/datum/component/material_container/proc/transer_amt_to(var/datum/component/material_container/T, amt, var/datum/material/mat)
+/datum/component/material_container/proc/transer_amt_to(var/datum/component/material_container/T, amt, var/datum/material/mat)
if(!istype(mat))
mat = getmaterialref(mat)
if((amt==0)||(!T)||(!mat))
@@ -216,7 +221,7 @@
/datum/component/material_container/proc/use_materials(list/mats, multiplier=1)
if(!mats || !length(mats))
return FALSE
-
+
var/list/mats_to_remove = list() //Assoc list MAT | AMOUNT
for(var/x in mats) //Loop through all required materials
@@ -230,7 +235,7 @@
return FALSE //Can't afford it
mats_to_remove[req_mat] += amount_required //Add it to the assoc list of things to remove
continue
-
+
var/total_amount_save = total_amount
for(var/i in mats_to_remove)
@@ -239,7 +244,7 @@
return total_amount_save - total_amount
/// For spawning mineral sheets at a specific location. Used by machines to output sheets.
-/datum/component/material_container/proc/retrieve_sheets(sheet_amt, var/datum/material/M, target = null)
+/datum/component/material_container/proc/retrieve_sheets(sheet_amt, var/datum/material/M, target = null)
if(!M.sheet_type)
return 0 //Add greyscale sheet handling here later
if(sheet_amt <= 0)
@@ -263,7 +268,7 @@
/// Proc to get all the materials and dump them as sheets
-/datum/component/material_container/proc/retrieve_all(target = null)
+/datum/component/material_container/proc/retrieve_all(target = null)
var/result = 0
for(var/MAT in materials)
var/amount = materials[MAT]
@@ -286,18 +291,18 @@
req_mat = getmaterialref(req_mat) //Get the ref
else // Its a category. (For example MAT_CATEGORY_RIGID)
- if(!has_enough_of_category(req_mat, mats[req_mat], multiplier)) //Do we have enough of this category?
+ if(!has_enough_of_category(req_mat, mats[x], multiplier)) //Do we have enough of this category?
return FALSE
else
continue
- if(!has_enough_of_material(req_mat, mats[req_mat], multiplier))//Not a category, so just check the normal way
+ if(!has_enough_of_material(req_mat, mats[x], multiplier))//Not a category, so just check the normal way
return FALSE
return TRUE
/// Returns all the categories in a recipe.
-/datum/component/material_container/proc/get_categories(list/mats)
+/datum/component/material_container/proc/get_categories(list/mats)
var/list/categories = list()
for(var/x in mats) //Loop through all required materials
if(!istext(x)) //This means its not a category
@@ -307,12 +312,12 @@
/// Returns TRUE if you have enough of the specified material.
-/datum/component/material_container/proc/has_enough_of_material(var/datum/material/req_mat, amount, multiplier=1)
+/datum/component/material_container/proc/has_enough_of_material(var/datum/material/req_mat, amount, multiplier=1)
if(!materials[req_mat]) //Do we have the resource?
return FALSE //Can't afford it
var/amount_required = amount * multiplier
if(materials[req_mat] >= amount_required) // do we have enough of the resource?
- return TRUE
+ return TRUE
return FALSE //Can't afford it
/// Returns TRUE if you have enough of a specified material category (Which could be multiple materials)
@@ -324,7 +329,7 @@
return FALSE
/// Turns a material amount into the amount of sheets it should output
-/datum/component/material_container/proc/amount2sheet(amt)
+/datum/component/material_container/proc/amount2sheet(amt)
if(amt >= MINERAL_MATERIAL_AMOUNT)
return round(amt / MINERAL_MATERIAL_AMOUNT)
return FALSE
@@ -346,7 +351,24 @@
return material_amount
/// Returns the amount of a specific material in this container.
-/datum/component/material_container/proc/get_material_amount(var/datum/material/mat)
+/datum/component/material_container/proc/get_material_amount(var/datum/material/mat)
if(!istype(mat))
mat = getmaterialref(mat)
- return(materials[mat])
+ return materials[mat]
+
+/// List format is list(material_name = list(amount = ..., ref = ..., etc.))
+/datum/component/material_container/ui_data(mob/user)
+ var/list/data = list()
+
+ for(var/datum/material/material as anything in materials)
+ var/amount = materials[material]
+
+ data += list(list(
+ "name" = material.name,
+ "ref" = REF(material),
+ "amount" = amount,
+ "sheets" = round(amount / MINERAL_MATERIAL_AMOUNT),
+ "removable" = amount >= MINERAL_MATERIAL_AMOUNT,
+ ))
+
+ return data
diff --git a/code/datums/components/mirage_border.dm b/code/datums/components/mirage_border.dm
index b84da962d8b8a..5f5470816da77 100644
--- a/code/datums/components/mirage_border.dm
+++ b/code/datums/components/mirage_border.dm
@@ -44,3 +44,7 @@
name = "Mirage holder"
anchored = TRUE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+
+/obj/effect/abstract/mirage_holder/Destroy(force)
+ vis_contents.Cut()
+ . = ..()
diff --git a/code/datums/components/mirv.dm b/code/datums/components/mirv.dm
new file mode 100644
index 0000000000000..e64b4ba638ef0
--- /dev/null
+++ b/code/datums/components/mirv.dm
@@ -0,0 +1,43 @@
+/datum/component/mirv
+ var/projectile_type
+ var/radius // shoots a projectile for every turf on this radius from the hit target
+ var/override_projectile_range
+
+/datum/component/mirv/Initialize(projectile_type, radius=1, override_projectile_range)
+ if(!isgun(parent) && !ismachinery(parent) && !isstructure(parent) && !isgrenade(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ src.projectile_type = projectile_type
+ src.radius = radius
+ src.override_projectile_range = override_projectile_range
+
+ if(isgrenade(parent))
+ parent.AddComponent(/datum/component/pellet_cloud, projectile_type=projectile_type)
+
+/datum/component/mirv/RegisterWithParent()
+ if(ismachinery(parent) || isstructure(parent) || isgun(parent)) // turrets, etc
+ RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit)
+
+/datum/component/mirv/UnregisterFromParent()
+ UnregisterSignal(parent, list(COMSIG_PROJECTILE_ON_HIT))
+
+/datum/component/mirv/proc/projectile_hit(atom/fired_from, atom/movable/firer, atom/target, Angle)
+ SIGNAL_HANDLER
+
+ INVOKE_ASYNC(src, .proc/do_shrapnel, firer, target)
+
+/datum/component/mirv/proc/do_shrapnel(mob/firer, atom/target)
+ if(radius < 1)
+ return
+ var/turf/target_turf = get_turf(target)
+ for(var/turf/shootat_turf in RANGE_TURFS(radius, target) - RANGE_TURFS(radius-1, target))
+
+ var/obj/item/projectile/P = new projectile_type(target_turf)
+ //Shooting Code:
+ P.range = radius+1
+ if(override_projectile_range)
+ P.range = override_projectile_range
+ P.preparePixelProjectile(shootat_turf, target)
+ P.firer = firer // don't hit ourself that would be really annoying
+ P.permutated += target // don't hit the target we hit already with the flak
+ P.fire()
diff --git a/code/datums/components/mood.dm b/code/datums/components/mood.dm
index cf2e05ad83bd0..b82781feeb218 100644
--- a/code/datums/components/mood.dm
+++ b/code/datums/components/mood.dm
@@ -10,8 +10,8 @@
var/mood_modifier = 1 //Modifier to allow certain mobs to be less affected by moodlets
var/list/datum/mood_event/mood_events = list()
var/insanity_effect = 0 //is the owner being punished for low mood? If so, how much?
- var/obj/screen/mood/screen_obj
- var/obj/screen/sanity/screen_obj_sanity
+ var/atom/movable/screen/mood/screen_obj
+ var/atom/movable/screen/sanity/screen_obj_sanity
/datum/component/mood/Initialize()
if(!isliving(parent))
@@ -24,6 +24,7 @@
RegisterSignal(parent, COMSIG_ENTER_AREA, .proc/check_area_mood)
RegisterSignal(parent, COMSIG_MOB_HUD_CREATED, .proc/modify_hud)
+ RegisterSignal(parent, COMSIG_HUMAN_VOID_MASK_ACT, .proc/direct_sanity_drain)
var/mob/living/owner = parent
if(owner.hud_used)
modify_hud()
@@ -169,29 +170,28 @@
screen_obj.color = "#2eeb9a"
break
-/datum/component/mood/process() //Called on SSmood process
+///Called on SSmood process
+/datum/component/mood/process(delta_time)
var/mob/living/owner = parent
-
switch(mood_level)
if(1)
- setSanity(sanity-0.3)
+ setSanity(sanity-0.3*delta_time)
if(2)
- setSanity(sanity-0.15)
+ setSanity(sanity-0.15*delta_time)
if(3)
- setSanity(sanity-0.1)
+ setSanity(sanity-0.1*delta_time)
if(4)
- setSanity(sanity-0.05, minimum=SANITY_UNSTABLE)
+ setSanity(sanity-0.05*delta_time, minimum=SANITY_UNSTABLE)
if(5)
setSanity(sanity+0.1, maximum=SANITY_NEUTRAL)
if(6)
- setSanity(sanity+0.2, maximum=SANITY_GREAT)
+ setSanity(sanity+0.2*delta_time, maximum=SANITY_GREAT)
if(7)
- setSanity(sanity+0.3, maximum=INFINITY)
+ setSanity(sanity+0.3*delta_time, maximum=SANITY_GREAT)
if(8)
- setSanity(sanity+0.4, maximum=SANITY_MAXIMUM)
+ setSanity(sanity+0.4*delta_time, maximum=SANITY_MAXIMUM)
if(9)
- setSanity(sanity+0.6, maximum=SANITY_MAXIMUM)
-
+ setSanity(sanity+0.6*delta_time, maximum=SANITY_MAXIMUM)
HandleNutrition(owner)
HandleHygiene(owner)
@@ -216,26 +216,32 @@
if(SANITY_INSANE to SANITY_CRAZY)
setInsanityEffect(MAJOR_INSANITY_PEN)
master.add_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE, 100, override=TRUE, multiplicative_slowdown=0.6, movetypes=(~FLYING))
+ master.add_actionspeed_modifier(/datum/actionspeed_modifier/low_sanity)
sanity_level = 6
if(SANITY_CRAZY to SANITY_UNSTABLE)
setInsanityEffect(MINOR_INSANITY_PEN)
master.add_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE, 100, override=TRUE, multiplicative_slowdown=0.3, movetypes=(~FLYING))
+ master.add_actionspeed_modifier(/datum/actionspeed_modifier/low_sanity)
sanity_level = 5
if(SANITY_UNSTABLE to SANITY_DISTURBED)
setInsanityEffect(0)
master.add_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE, 100, override=TRUE, multiplicative_slowdown=0.15, movetypes=(~FLYING))
+ master.add_actionspeed_modifier(/datum/actionspeed_modifier/low_sanity)
sanity_level = 4
if(SANITY_DISTURBED to SANITY_NEUTRAL)
setInsanityEffect(0)
master.remove_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE)
+ master.remove_actionspeed_modifier(ACTIONSPEED_ID_SANITY)
sanity_level = 3
if(SANITY_NEUTRAL+1 to SANITY_GREAT+1) //shitty hack but +1 to prevent it from responding to super small differences
setInsanityEffect(0)
master.remove_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE)
+ master.add_actionspeed_modifier(/datum/actionspeed_modifier/high_sanity)
sanity_level = 2
if(SANITY_GREAT+1 to INFINITY)
setInsanityEffect(0)
master.remove_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE)
+ master.add_actionspeed_modifier(/datum/actionspeed_modifier/high_sanity)
sanity_level = 1
update_mood_icon()
@@ -247,6 +253,8 @@
insanity_effect = newval
/datum/component/mood/proc/add_event(datum/source, category, type, param) //Category will override any events in the same category, should be unique unless the event is based on the same thing like hunger.
+ SIGNAL_HANDLER
+
var/datum/mood_event/the_event
if(!istext(category))
category = REF(category)
@@ -268,6 +276,8 @@
addtimer(CALLBACK(src, .proc/clear_event, null, category), the_event.timeout, TIMER_UNIQUE|TIMER_OVERRIDE)
/datum/component/mood/proc/clear_event(datum/source, category)
+ SIGNAL_HANDLER
+
if(!istext(category))
category = REF(category)
var/datum/mood_event/event = mood_events[category]
@@ -278,6 +288,11 @@
qdel(event)
update_mood()
+/datum/component/mood/proc/get_event(category)
+ if(!istext(category))
+ category = REF(category)
+ return mood_events[category]
+
/datum/component/mood/proc/remove_temp_moods(var/admin) //Removes all temp moods
for(var/i in mood_events)
var/datum/mood_event/moodlet = mood_events[i]
@@ -289,6 +304,8 @@
/datum/component/mood/proc/modify_hud(datum/source)
+ SIGNAL_HANDLER
+
var/mob/living/owner = parent
var/datum/hud/hud = owner.hud_used
screen_obj = new
@@ -299,6 +316,8 @@
RegisterSignal(screen_obj, COMSIG_CLICK, .proc/hud_click)
/datum/component/mood/proc/unmodify_hud(datum/source)
+ SIGNAL_HANDLER
+
if(!screen_obj)
return
var/mob/living/owner = parent
@@ -310,15 +329,16 @@
QDEL_NULL(screen_obj_sanity)
/datum/component/mood/proc/hud_click(datum/source, location, control, params, mob/user)
+ SIGNAL_HANDLER
+
print_mood(user)
/datum/component/mood/proc/HandleNutrition(mob/living/L)
- if(ishuman(L))
- var/mob/living/carbon/human/H = L
- if(isethereal(H))
- HandleCharge(H)
- if(HAS_TRAIT(H, TRAIT_NOHUNGER))
- return FALSE //no mood events for nutrition
+ if(HAS_TRAIT(L, TRAIT_NOHUNGER))
+ return FALSE //no mood events for nutrition
+ if(HAS_TRAIT(L, TRAIT_POWERHUNGRY))
+ HandleCharge(L)
+ return
switch(L.nutrition)
if(NUTRITION_LEVEL_FULL to INFINITY)
if (!HAS_TRAIT(L, TRAIT_VORACIOUS))
@@ -336,43 +356,45 @@
if(0 to NUTRITION_LEVEL_STARVING)
add_event(null, "nutrition", /datum/mood_event/starving)
-/datum/component/mood/proc/HandleCharge(mob/living/carbon/human/H)
- var/datum/species/ethereal/E = H.dna?.species
- switch(E.get_charge(H))
- if(ETHEREAL_CHARGE_NONE to ETHEREAL_CHARGE_LOWPOWER)
- add_event(null, "charge", /datum/mood_event/decharged)
- if(ETHEREAL_CHARGE_LOWPOWER to ETHEREAL_CHARGE_NORMAL)
- add_event(null, "charge", /datum/mood_event/lowpower)
- if(ETHEREAL_CHARGE_NORMAL to ETHEREAL_CHARGE_ALMOSTFULL)
- clear_event(null, "charge")
- if(ETHEREAL_CHARGE_ALMOSTFULL to ETHEREAL_CHARGE_FULL)
- add_event(null, "charge", /datum/mood_event/charged)
+/datum/component/mood/proc/HandleCharge(mob/living/L)
+ switch(L.nutrition)
+ if(NUTRITION_LEVEL_WELL_FED to INFINITY)
+ add_event(null, "nutrition", /datum/mood_event/charged)
+ if(NUTRITION_LEVEL_FED to NUTRITION_LEVEL_WELL_FED)
+ clear_event(null, "nutrition")
+ if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_FED)
+ add_event(null, "nutrition", /datum/mood_event/lowpower)
+ if(0 to NUTRITION_LEVEL_STARVING)
+ add_event(null, "nutrition", /datum/mood_event/decharged)
/datum/component/mood/proc/HandleHygiene(mob/living/carbon/human/H)
- if(H.hygiene <= HYGIENE_LEVEL_DIRTY)
- HygieneMiasma(H)
-
-/datum/component/mood/proc/HygieneMiasma(mob/living/carbon/human/H)
- // Properly stored humans shouldn't create miasma
- if(istype(H.loc, /obj/structure/closet/crate/coffin)|| istype(H.loc, /obj/structure/closet/body_bag) || istype(H.loc, /obj/structure/bodycontainer))
- return
-
- var/turf/T = get_turf(H)
-
- if(!istype(T) || T.return_air().return_pressure() > (WARNING_HIGH_PRESSURE - 10))
- return
-
- var/datum/gas_mixture/stank = new
- stank.set_moles(/datum/gas/miasma, MIASMA_HYGIENE_MOLES)
- stank.set_temperature(BODYTEMP_NORMAL)
- T.assume_air(stank)
- T.air_update_turf()
+ if(H.has_quirk(/datum/quirk/neet))
+ return //Neets don't care.
+ switch (H.hygiene)
+ if(HYGIENE_LEVEL_DISGUSTING to HYGIENE_LEVEL_DISGUSTING)//Believe it or not but this is actually the cleaner option.
+ add_event(null, "hygiene", /datum/mood_event/disgusting)
+ if(HYGIENE_LEVEL_DISGUSTING to HYGIENE_LEVEL_DIRTY)
+ add_event(null, "hygiene", /datum/mood_event/dirty)
+ if(HYGIENE_LEVEL_DIRTY to HYGIENE_LEVEL_NORMAL)
+ clear_event(null, "hygiene")
+ if(HYGIENE_LEVEL_NORMAL to HYGIENE_LEVEL_CLEAN)
+ add_event(null, "hygiene", /datum/mood_event/neat)
/datum/component/mood/proc/check_area_mood(datum/source, var/area/A)
+ SIGNAL_HANDLER
+
if(A.mood_bonus)
+ if(get_event("area")) //walking between areas that give mood bonus should first clear the bonus from the previous one
+ clear_event(null, "area")
add_event(null, "area", /datum/mood_event/area, list(A.mood_bonus, A.mood_message))
else
clear_event(null, "area")
#undef MINOR_INSANITY_PEN
#undef MAJOR_INSANITY_PEN
+
+///Causes direct drain of someone's sanity, call it with a numerical value corresponding how badly you want to hurt their sanity
+/datum/component/mood/proc/direct_sanity_drain(datum/source, amount)
+ SIGNAL_HANDLER
+
+ setSanity(sanity + amount)
diff --git a/code/datums/components/nanites.dm b/code/datums/components/nanites.dm
index 8bf4ab5f6309f..bc99510afa726 100644
--- a/code/datums/components/nanites.dm
+++ b/code/datums/components/nanites.dm
@@ -107,9 +107,9 @@
else
adjust_nanites(null, amount) //just add to the nanite volume
-/datum/component/nanites/process()
+/datum/component/nanites/process(delta_time)
if(!IS_IN_STASIS(host_mob))
- adjust_nanites(null, regen_rate)
+ adjust_nanites(null, regen_rate * delta_time)
add_research()
for(var/X in programs)
var/datum/nanite_program/NP = X
@@ -121,10 +121,14 @@
/datum/component/nanites/proc/delete_nanites()
+ SIGNAL_HANDLER
+
qdel(src)
//Syncs the nanite component to another, making it so programs are the same with the same programming (except activation status)
/datum/component/nanites/proc/sync(datum/signal_source, datum/component/nanites/source, full_overwrite = TRUE, copy_activation = FALSE)
+ SIGNAL_HANDLER
+
var/list/programs_to_remove = programs.Copy()
var/list/programs_to_add = source.programs.Copy()
for(var/X in programs)
@@ -157,6 +161,8 @@
NP.software_error()
/datum/component/nanites/proc/add_program(datum/source, datum/nanite_program/new_program, datum/nanite_program/source_program)
+ SIGNAL_HANDLER
+
for(var/X in programs)
var/datum/nanite_program/NP = X
if(NP.unique && NP.type == new_program.type)
@@ -176,6 +182,8 @@
return (nanite_volume > 0)
/datum/component/nanites/proc/adjust_nanites(datum/source, amount)
+ SIGNAL_HANDLER
+
nanite_volume = clamp(nanite_volume + amount, 0, max_nanites)
if(nanite_volume <= 0) //oops we ran out
qdel(src)
@@ -192,6 +200,8 @@
holder.icon_state = "nanites[nanite_percent]"
/datum/component/nanites/proc/on_emp(datum/source, severity)
+ SIGNAL_HANDLER
+
nanite_volume *= (rand(60, 90) * 0.01) //Lose 10-40% of nanites
adjust_nanites(null, -(rand(5, 50))) //Lose 5-50 flat nanite volume
if(prob(40/severity))
@@ -201,8 +211,10 @@
NP.on_emp(severity)
-/datum/component/nanites/proc/on_shock(datum/source, shock_damage, siemens_coeff = 1, flags = NONE)
- if(flags & SHOCK_ILLUSION || shock_damage < 1)
+/datum/component/nanites/proc/on_shock(datum/source, shock_damage, shock_source, siemens_coeff = 1, safety = 0, tesla_shock = 0, illusion = 0, stun = TRUE)
+ SIGNAL_HANDLER
+
+ if(illusion || shock_damage < 1)
return
if(!HAS_TRAIT_NOT_FROM(host_mob, TRAIT_SHOCKIMMUNE, "nanites"))//Another shock protection must protect nanites too, but nanites protect only host
@@ -213,35 +225,49 @@
NP.on_shock(shock_damage)
/datum/component/nanites/proc/on_minor_shock(datum/source)
+ SIGNAL_HANDLER
+
adjust_nanites(null, -(rand(5, 15))) //Lose 5-15 flat nanite volume
for(var/X in programs)
var/datum/nanite_program/NP = X
NP.on_minor_shock()
/datum/component/nanites/proc/check_stealth(datum/source)
+ SIGNAL_HANDLER
+
return stealth
/datum/component/nanites/proc/on_death(datum/source, gibbed)
+ SIGNAL_HANDLER
+
for(var/X in programs)
var/datum/nanite_program/NP = X
NP.on_death(gibbed)
/datum/component/nanites/proc/receive_signal(datum/source, code, source = "an unidentified source")
+ SIGNAL_HANDLER
+
for(var/X in programs)
var/datum/nanite_program/NP = X
NP.receive_signal(code, source)
/datum/component/nanites/proc/receive_comm_signal(datum/source, comm_code, comm_message, comm_source = "an unidentified source")
+ SIGNAL_HANDLER
+
for(var/X in programs)
if(istype(X, /datum/nanite_program/comm))
var/datum/nanite_program/comm/NP = X
NP.receive_comm_signal(comm_code, comm_message, comm_source)
/datum/component/nanites/proc/check_viable_biotype()
+ SIGNAL_HANDLER
+
if(!(MOB_ORGANIC in host_mob.mob_biotypes) && !(MOB_UNDEAD in host_mob.mob_biotypes) && !HAS_TRAIT(host_mob, TRAIT_NANITECOMPATIBLE))
qdel(src) //bodytype no longer sustains nanites
/datum/component/nanites/proc/check_access(datum/source, obj/O)
+ SIGNAL_HANDLER
+
for(var/datum/nanite_program/access/access_program in programs)
if(access_program.activated)
return O.check_access_list(access_program.access)
@@ -250,15 +276,23 @@
return FALSE
/datum/component/nanites/proc/set_volume(datum/source, amount)
+ SIGNAL_HANDLER
+
nanite_volume = clamp(amount, 0, max_nanites)
/datum/component/nanites/proc/set_max_volume(datum/source, amount)
+ SIGNAL_HANDLER
+
max_nanites = max(1, max_nanites)
/datum/component/nanites/proc/set_cloud(datum/source, amount)
+ SIGNAL_HANDLER
+
cloud_id = clamp(amount, 0, 100)
/datum/component/nanites/proc/set_cloud_sync(datum/source, method)
+ SIGNAL_HANDLER
+
switch(method)
if(NANITE_CLOUD_TOGGLE)
cloud_active = !cloud_active
@@ -268,12 +302,18 @@
cloud_active = TRUE
/datum/component/nanites/proc/set_safety(datum/source, amount)
+ SIGNAL_HANDLER
+
safety_threshold = clamp(amount, 0, max_nanites)
/datum/component/nanites/proc/set_regen(datum/source, amount)
+ SIGNAL_HANDLER
+
regen_rate = amount
/datum/component/nanites/proc/confirm_nanites()
+ SIGNAL_HANDLER
+
return TRUE //yup i exist
/datum/component/nanites/proc/get_data(list/nanite_data)
@@ -285,6 +325,8 @@
nanite_data["stealth"] = stealth
/datum/component/nanites/proc/get_programs(datum/source, list/nanite_programs)
+ SIGNAL_HANDLER
+
nanite_programs |= programs
/datum/component/nanites/proc/add_research()
@@ -301,6 +343,8 @@
SSresearch.science_tech.add_point_list(list(TECHWEB_POINT_TYPE_NANITES = research_value))
/datum/component/nanites/proc/nanite_scan(datum/source, mob/user, full_scan)
+ SIGNAL_HANDLER
+
if(!full_scan)
if(!stealth)
to_chat(user, "Nanites Detected")
@@ -324,6 +368,8 @@
return TRUE
/datum/component/nanites/proc/nanite_ui_data(datum/source, list/data, scan_level)
+ SIGNAL_HANDLER
+
data["has_nanites"] = TRUE
data["nanite_volume"] = nanite_volume
data["regen_rate"] = regen_rate
diff --git a/code/datums/components/orbiter.dm b/code/datums/components/orbiter.dm
index 1160aa44cbd25..e9c2a5c41d3a1 100644
--- a/code/datums/components/orbiter.dm
+++ b/code/datums/components/orbiter.dm
@@ -2,6 +2,7 @@
can_transfer = TRUE
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
var/list/orbiters
+ var/datum/movement_detector/tracker
//radius: range to orbit at, radius of the circle formed by orbiting (in pixels)
//clockwise: whether you orbit clockwise or anti clockwise
@@ -22,17 +23,15 @@
/datum/component/orbiter/RegisterWithParent()
var/atom/target = parent
+
target.orbiters = src
- while(ismovableatom(target))
- RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/move_react)
- target = target.loc
+ if(ismovableatom(target))
+ tracker = new(target, CALLBACK(src, .proc/move_react))
/datum/component/orbiter/UnregisterFromParent()
var/atom/target = parent
target.orbiters = null
- while(ismovableatom(target))
- UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
- target = target.loc
+ QDEL_NULL(tracker)
/datum/component/orbiter/Destroy()
var/atom/master = parent
@@ -42,9 +41,9 @@
orbiters = null
return ..()
-/datum/component/orbiter/InheritComponent(datum/component/orbiter/newcomp, original, list/arguments)
- if(arguments)
- begin_orbit(arglist(arguments))
+/datum/component/orbiter/InheritComponent(datum/component/orbiter/newcomp, original, atom/movable/orbiter, radius, clockwise, rotation_speed, rotation_segments, pre_rotation)
+ if(!newcomp)
+ begin_orbit(arglist(args.Copy(3)))
return
// The following only happens on component transfers
orbiters += newcomp.orbiters
@@ -63,7 +62,7 @@
orbiters[orbiter] = TRUE
orbiter.orbiting = src
RegisterSignal(orbiter, COMSIG_MOVABLE_MOVED, .proc/orbiter_move_react)
-
+ SEND_SIGNAL(parent, COMSIG_ATOM_ORBIT_BEGIN, orbiter)
var/matrix/initial_transform = matrix(orbiter.transform)
orbiters[orbiter] = initial_transform
@@ -89,6 +88,7 @@
if(!orbiters[orbiter])
return
UnregisterSignal(orbiter, COMSIG_MOVABLE_MOVED)
+ SEND_SIGNAL(parent, COMSIG_ATOM_ORBIT_STOP, orbiter)
orbiter.SpinAnimation(0, 0)
if(istype(orbiters[orbiter],/matrix)) //This is ugly.
orbiter.transform = orbiters[orbiter]
@@ -99,10 +99,9 @@
qdel(src)
// This proc can receive signals by either the thing being directly orbited or anything holding it
-/datum/component/orbiter/proc/move_react(atom/orbited, atom/oldloc, direction)
+/datum/component/orbiter/proc/move_react(atom/movable/master, atom/mover, atom/oldloc, direction)
set waitfor = FALSE // Transfer calls this directly and it doesnt care if the ghosts arent done moving
- var/atom/movable/master = parent
if(master.loc == oldloc)
return
@@ -110,19 +109,6 @@
if(!newturf)
qdel(src)
- // Handling the signals of stuff holding us (or not anymore)
- // These are prety rarely activated, how often are you following something in a bag?
- if(oldloc && !isturf(oldloc)) // We used to be registered to it, probably
- var/atom/target = oldloc
- while(ismovableatom(target))
- UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
- target = target.loc
- if(orbited?.loc && orbited.loc != newturf) // We want to know when anything holding us moves too
- var/atom/target = orbited.loc
- while(ismovableatom(target))
- RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/move_react, TRUE)
- target = target.loc
-
var/atom/curloc = master.loc
for(var/i in orbiters)
var/atom/movable/thing = i
@@ -135,6 +121,8 @@
/datum/component/orbiter/proc/orbiter_move_react(atom/movable/orbiter, atom/oldloc, direction)
+ SIGNAL_HANDLER
+
if(orbiter.loc == get_turf(parent))
return
end_orbit(orbiter)
diff --git a/code/datums/components/paintable.dm b/code/datums/components/paintable.dm
index 756c42aa9dabc..8b946dd915ba6 100644
--- a/code/datums/components/paintable.dm
+++ b/code/datums/components/paintable.dm
@@ -13,6 +13,8 @@
A.remove_atom_colour(FIXED_COLOUR_PRIORITY, current_paint)
/datum/component/spraycan_paintable/proc/Repaint(datum/source, obj/item/toy/crayon/spraycan/spraycan, mob/living/user)
+ SIGNAL_HANDLER
+
if(!istype(spraycan) || user.a_intent == INTENT_HARM)
return
. = COMPONENT_NO_AFTERATTACK
diff --git a/code/datums/components/pellet_cloud.dm b/code/datums/components/pellet_cloud.dm
new file mode 100644
index 0000000000000..1c0c41e2c2f69
--- /dev/null
+++ b/code/datums/components/pellet_cloud.dm
@@ -0,0 +1,298 @@
+/*
+ * This component is used when you want to create a bunch of shrapnel or projectiles (say, shrapnel from a fragmentation grenade, or buckshot from a shotgun) from a central point,
+ * without necessarily printing a separate message for every single impact. This component should be instantiated right when you need it (like the moment of firing), then activated
+ * by signal.
+ *
+ * Pellet cloud currently works on two classes of sources: directed (ammo casings), and circular (grenades, landmines).
+ * -Directed: This means you're shooting multiple pellets, like buckshot. If an ammo casing is defined as having multiple pellets, it will automatically create a pellet cloud
+ * and call COMSIG_PELLET_CLOUD_INIT (see [/obj/item/ammo_casing/proc/fire_casing]). Thus, the only projectiles fired will be the ones fired here.
+ * The magnitude var controls how many pellets are created.
+ * -Circular: This results in a big spray of shrapnel flying all around the detonation point when the grenade fires COMSIG_GRENADE_PRIME or landmine triggers COMSIG_MINE_TRIGGERED.
+ * The magnitude var controls how big the detonation radius is (the bigger the magnitude, the more shrapnel is created). Grenades can be covered with bodies to reduce shrapnel output.
+ *
+ * Once all of the fired projectiles either hit a target or disappear due to ranging out/whatever else, we resolve the list of all the things we hit and print aggregate messages so we get
+ * one "You're hit by 6 buckshot pellets" vs 6x "You're hit by the buckshot blah blah" messages.
+ *
+ * Note that this is how all guns handle shooting ammo casings with multiple pellets, in case such a thing comes up.
+*/
+
+/datum/component/pellet_cloud
+ /// What's the projectile path of the shrapnel we're shooting?
+ var/projectile_type
+
+ /// How many shrapnel projectiles are we responsible for tracking? May be reduced for grenades if someone dives on top of it. Defined by ammo casing for casings, derived from magnitude otherwise
+ var/num_pellets
+ /// For grenades/landmines, how big is the radius of turfs we're targeting? Note this does not effect the projectiles range, only how many we generate
+ var/radius = 4
+
+ /// The list of pellets we're responsible for tracking, once these are all accounted for, we finalize.
+ var/list/pellets = list()
+ /// An associated list with the atom hit as the key and how many pellets they've eaten for the value, for printing aggregate messages
+ var/list/targets_hit = list()
+ /// For grenades, any /mob/living's the grenade is moved onto, see [/datum/component/pellet_cloud/proc/handle_martyrs()]
+ var/list/bodies
+
+ /// For grenades, tracking how many pellets are removed due to martyrs and how many pellets are added due to the last person to touch it being on top of it
+ var/pellet_delta = 0
+ /// how many pellets ranged out without hitting anything
+ var/terminated
+ /// how many pellets impacted something
+ var/hits
+ /// If the parent tried deleting and we're not done yet, we send it to nullspace then delete it after
+ var/queued_delete = FALSE
+
+ /// for if we're an ammo casing being fired
+ var/mob/living/shooter
+
+/datum/component/pellet_cloud/Initialize(projectile_type=/obj/item/shrapnel, magnitude=5)
+ if(!isammocasing(parent) && !isgrenade(parent) && !islandmine(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ if(magnitude < 1)
+ stack_trace("Invalid magnitude [magnitude] < 1 on pellet_cloud, parent: [parent]")
+ magnitude = 1
+
+ src.projectile_type = projectile_type
+
+ if(isammocasing(parent))
+ num_pellets = magnitude
+ else if(isgrenade(parent) || islandmine(parent))
+ radius = magnitude
+
+/datum/component/pellet_cloud/Destroy(force, silent)
+ pellets = null
+ targets_hit = null
+ bodies = null
+ return ..()
+
+/datum/component/pellet_cloud/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_PARENT_PREQDELETED, .proc/nullspace_parent)
+ if(isammocasing(parent))
+ RegisterSignal(parent, COMSIG_PELLET_CLOUD_INIT, .proc/create_casing_pellets)
+ else if(isgrenade(parent))
+ RegisterSignal(parent, COMSIG_GRENADE_ARMED, .proc/grenade_armed)
+ RegisterSignal(parent, COMSIG_GRENADE_PRIME, .proc/create_blast_pellets)
+ else if(islandmine(parent))
+ RegisterSignal(parent, COMSIG_MINE_TRIGGERED, .proc/create_blast_pellets)
+
+/datum/component/pellet_cloud/UnregisterFromParent()
+ UnregisterSignal(parent, list(COMSIG_PARENT_PREQDELETED, COMSIG_PELLET_CLOUD_INIT, COMSIG_GRENADE_PRIME, COMSIG_GRENADE_ARMED, COMSIG_MOVABLE_MOVED, COMSIG_MOVABLE_UNCROSSED, COMSIG_MINE_TRIGGERED, COMSIG_ITEM_DROPPED))
+
+/**
+ * create_casing_pellets() is for directed pellet clouds for ammo casings that have multiple pellets (buckshot and scatter lasers for instance)
+ *
+ * Honestly this is mostly just a rehash of [/obj/item/ammo_casing/proc/fire_casing()] for pellet counts > 1, except this lets us tamper with the pellets and hook onto them for tracking purposes.
+ * The arguments really don't matter, this proc is triggered by COMSIG_PELLET_CLOUD_INIT which is only for this really, it's just a big mess of the state vars we need for doing the stuff over here.
+ */
+/datum/component/pellet_cloud/proc/create_casing_pellets(obj/item/ammo_casing/shell, atom/target, mob/living/user, fired_from, randomspread, spread, zone_override, params, distro)
+ SIGNAL_HANDLER
+
+ shooter = user
+ var/turf/targloc = get_turf(target)
+ if(!zone_override)
+ zone_override = shooter.zone_selected
+
+ for(var/i in 1 to num_pellets)
+ shell.ready_proj(target, user, SUPPRESSED_VERY, zone_override, fired_from)
+ if(distro)
+ if(randomspread)
+ spread = round((rand() - 0.5) * distro)
+ else //Smart spread
+ spread = round((i / num_pellets - 0.5) * distro)
+
+ RegisterSignal(shell.BB, COMSIG_PROJECTILE_SELF_ON_HIT, .proc/pellet_hit)
+ RegisterSignal(shell.BB, list(COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PARENT_QDELETING), .proc/pellet_range)
+ pellets += shell.BB
+ var/turf/current_loc = get_turf(user)
+ if(!istype(targloc) || !istype(current_loc))
+ return
+ INVOKE_ASYNC(shell, /obj/item/ammo_casing.proc/throw_proj, target, targloc, shooter, params, spread)
+ if(i != num_pellets)
+ shell.newshot()
+
+/**
+ * create_blast_pellets() is for when we have a central point we want to shred the surroundings of with a ring of shrapnel, namely frag grenades and landmines.
+ *
+ * Note that grenades have extra handling for someone throwing themselves/being thrown on top of it, while landmines do not (obviously, it's a landmine!). See [/datum/component/pellet_cloud/proc/handle_martyrs()]
+ */
+/datum/component/pellet_cloud/proc/create_blast_pellets(obj/O, mob/living/lanced_by)
+ SIGNAL_HANDLER
+
+ var/atom/A = parent
+
+ if(isgrenade(parent)) // handle_martyrs can reduce the radius and thus the number of pellets we produce if someone dives on top of a frag grenade
+ INVOKE_ASYNC(src, .proc/handle_martyrs, lanced_by) // note that we can modify radius in this proc
+
+ if(radius < 1)
+ return
+
+ var/list/all_the_turfs_were_gonna_lacerate = RANGE_TURFS(radius, A) - RANGE_TURFS(radius-1, A)
+ num_pellets = all_the_turfs_were_gonna_lacerate.len + pellet_delta
+
+ for(var/T in all_the_turfs_were_gonna_lacerate)
+ var/turf/shootat_turf = T
+ INVOKE_ASYNC(src, .proc/pew, shootat_turf)
+
+/**
+ * handle_martyrs() is used for grenades that shoot shrapnel to check if anyone threw themselves/were thrown on top of the grenade, thus absorbing a good chunk of the shrapnel
+ *
+ * Between the time the grenade is armed and the actual detonation, we set var/list/bodies to the list of mobs currently on the new tile, as if the grenade landed on top of them, tracking if any of them move off the tile and removing them from the "under" list
+ * Once the grenade detonates, handle_martyrs() is called and gets all the new mobs on the tile, and add the ones not in var/list/bodies to var/list/martyrs
+ * We then iterate through the martyrs and reduce the shrapnel magnitude for each mob on top of it, shredding each of them with some of the shrapnel they helped absorb. This can snuff out all of the shrapnel if there's enough bodies
+ *
+ */
+/datum/component/pellet_cloud/proc/handle_martyrs(mob/living/lanced_by)
+ var/magnitude_absorbed
+ var/list/martyrs = list()
+
+ var/self_harm_radius_mult = 3
+
+ if(lanced_by && prob(60))
+ to_chat(lanced_by, "Your plan to whack someone with a grenade on a stick backfires on you, literally!")
+ self_harm_radius_mult = 1 // we'll still give the guy who got hit some extra shredding, but not 3*radius
+ pellet_delta += radius
+ for(var/i in 1 to radius)
+ pew(lanced_by) // thought you could be tricky and lance someone with no ill effects!!
+
+ for(var/mob/living/body in get_turf(parent))
+ if(body == shooter)
+ pellet_delta += radius * self_harm_radius_mult
+ for(var/i in 1 to radius * self_harm_radius_mult)
+ pew(body) // free shrapnel if it goes off in your hand, and it doesn't even count towards the absorbed. fun!
+ else if(!(body in bodies))
+ martyrs += body // promoted from a corpse to a hero
+
+ for(var/M in martyrs)
+ var/mob/living/martyr = M
+ if(radius > 4)
+ martyr.visible_message("[martyr] heroically covers \the [parent] with [martyr.p_their()] body, absorbing a load of the shrapnel!", "You heroically cover \the [parent] with your body, absorbing a load of the shrapnel!")
+ magnitude_absorbed += round(radius * 0.5)
+ else if(radius >= 2)
+ martyr.visible_message("[martyr] heroically covers \the [parent] with [martyr.p_their()] body, absorbing some of the shrapnel!", "You heroically cover \the [parent] with your body, absorbing some of the shrapnel!")
+ magnitude_absorbed += 2
+ else
+ martyr.visible_message("[martyr] heroically covers \the [parent] with [martyr.p_their()] body, snuffing out the shrapnel!", "You heroically cover \the [parent] with your body, snuffing out the shrapnel!")
+ magnitude_absorbed = radius
+
+ var/pellets_absorbed = (radius ** 2) - ((radius - magnitude_absorbed - 1) ** 2)
+ radius -= magnitude_absorbed
+ pellet_delta -= round(pellets_absorbed * 0.5)
+
+ if(martyr.stat != DEAD && martyr.client)
+ RegisterSignal(martyr, COMSIG_PARENT_QDELETING, .proc/on_target_qdel, override=TRUE)
+
+ for(var/i in 1 to round(pellets_absorbed * 0.5))
+ pew(martyr)
+
+ if(radius < 1)
+ break
+
+///One of our pellets hit something, record what it was and check if we're done (terminated == num_pellets)
+/datum/component/pellet_cloud/proc/pellet_hit(obj/item/projectile/P, atom/movable/firer, atom/target, Angle)
+ SIGNAL_HANDLER
+
+ pellets -= P
+ terminated++
+ hits++
+ targets_hit[target]++
+ if(targets_hit[target] == 1)
+ RegisterSignal(target, COMSIG_PARENT_QDELETING, .proc/on_target_qdel, override=TRUE)
+ UnregisterSignal(P, list(COMSIG_PARENT_QDELETING, COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PROJECTILE_SELF_ON_HIT))
+ if(terminated == num_pellets)
+ finalize()
+
+///One of our pellets disappeared due to hitting their max range (or just somehow got qdel'd), remove it from our list and check if we're done (terminated == num_pellets)
+/datum/component/pellet_cloud/proc/pellet_range(obj/item/projectile/P)
+ SIGNAL_HANDLER
+
+ pellets -= P
+ terminated++
+ UnregisterSignal(P, list(COMSIG_PARENT_QDELETING, COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PROJECTILE_SELF_ON_HIT))
+ if(terminated == num_pellets)
+ finalize()
+
+/// Minor convenience function for creating each shrapnel piece with circle explosions, mostly stolen from the MIRV component
+/datum/component/pellet_cloud/proc/pew(atom/target, spread=0)
+ var/obj/item/projectile/P = new projectile_type(get_turf(parent))
+
+ //Shooting Code:
+ P.spread = spread
+ P.original = target
+ P.fired_from = parent
+ P.firer = parent // don't hit ourself that would be really annoying
+ P.permutated += parent // don't hit the target we hit already with the flak
+ P.suppressed = SUPPRESSED_VERY // set the projectiles to make no message so we can do our own aggregate message
+ P.preparePixelProjectile(target, parent)
+ RegisterSignal(P, COMSIG_PROJECTILE_SELF_ON_HIT, .proc/pellet_hit)
+ RegisterSignal(P, list(COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PARENT_QDELETING), .proc/pellet_range)
+ pellets += P
+ P.fire()
+
+///All of our pellets are accounted for, time to go target by target and tell them how many things they got hit by.
+/datum/component/pellet_cloud/proc/finalize()
+ var/obj/item/projectile/P = projectile_type
+ var/proj_name = initial(P.name)
+
+ for(var/atom/target in targets_hit)
+ var/num_hits = targets_hit[target]
+ UnregisterSignal(target, COMSIG_PARENT_QDELETING)
+ if(num_hits > 1)
+ target.visible_message("[target] is hit by [num_hits] [proj_name]s!", null, null, COMBAT_MESSAGE_RANGE, target)
+ to_chat(target, "You're hit by [num_hits] [proj_name]s!")
+ else
+ target.visible_message("[target] is hit by a [proj_name]!", null, null, COMBAT_MESSAGE_RANGE, target)
+ to_chat(target, "You're hit by a [proj_name]!")
+ UnregisterSignal(parent, COMSIG_PARENT_PREQDELETED)
+ if(queued_delete)
+ qdel(parent)
+ qdel(src)
+
+/// Look alive, we're armed! Now we start watching to see if anyone's covering us
+/datum/component/pellet_cloud/proc/grenade_armed(obj/item/nade)
+ SIGNAL_HANDLER
+
+ if(ismob(nade.loc))
+ shooter = nade.loc
+ LAZYINITLIST(bodies)
+ RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/grenade_dropped)
+ RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/grenade_moved)
+ RegisterSignal(parent, COMSIG_MOVABLE_UNCROSSED, .proc/grenade_uncrossed)
+
+/// Someone dropped the grenade, so set them to the shooter in case they're on top of it when it goes off
+/datum/component/pellet_cloud/proc/grenade_dropped(obj/item/nade, mob/living/slick_willy)
+ SIGNAL_HANDLER
+
+ shooter = slick_willy
+ grenade_moved()
+
+/// Our grenade has moved, reset var/list/bodies so we're "on top" of any mobs currently on the tile
+/datum/component/pellet_cloud/proc/grenade_moved()
+ SIGNAL_HANDLER
+
+ LAZYCLEARLIST(bodies)
+ for(var/mob/living/L in get_turf(parent))
+ RegisterSignal(L, COMSIG_PARENT_QDELETING, .proc/on_target_qdel, override=TRUE)
+ bodies += L
+
+/// Someone who was originally "under" the grenade has moved off the tile and is now eligible for being a martyr and "covering" it
+/datum/component/pellet_cloud/proc/grenade_uncrossed(datum/source, atom/movable/AM)
+ SIGNAL_HANDLER
+
+ bodies -= AM
+
+/// Our grenade or landmine or caseless shell or whatever tried deleting itself, so we intervene and nullspace it until we're done here
+/datum/component/pellet_cloud/proc/nullspace_parent()
+ SIGNAL_HANDLER
+
+ var/atom/movable/AM = parent
+ AM.moveToNullspace()
+ queued_delete = TRUE
+ return TRUE
+
+/// Someone who was originally "under" the grenade has moved off the tile and is now eligible for being a martyr and "covering" it
+/datum/component/pellet_cloud/proc/on_target_qdel(atom/target)
+ SIGNAL_HANDLER
+
+ UnregisterSignal(target, COMSIG_PARENT_QDELETING)
+ targets_hit -= target
+ LAZYREMOVE(bodies, target)
diff --git a/code/datums/components/plumbing/_plumbing.dm b/code/datums/components/plumbing/_plumbing.dm
index 17105b0c5de9d..f89f50d2118f3 100644
--- a/code/datums/components/plumbing/_plumbing.dm
+++ b/code/datums/components/plumbing/_plumbing.dm
@@ -35,9 +35,8 @@
create_overlays()
/datum/component/plumbing/process()
- if(!demand_connects || !reagents)
- STOP_PROCESSING(SSfluids, src)
- return
+ if(!demand_connects || !reagents) // This actually shouldn't happen, but better safe than sorry
+ return PROCESS_KILL
if(reagents.total_volume < reagents.maximum_volume)
for(var/D in GLOB.cardinals)
if(D & demand_connects)
@@ -99,9 +98,9 @@
for(var/D in GLOB.cardinals)
var/color
var/direction
- if(D & demand_connects)
+ if(D & initial(demand_connects))
color = "red" //red because red is mean and it takes
- else if(D & supply_connects)
+ else if(D & initial(supply_connects))
color = "blue" //blue is nice and gives
else
continue
@@ -124,6 +123,8 @@
ducterlays += I
///we stop acting like a plumbing thing and disconnect if we are, so we can safely be moved and stuff
/datum/component/plumbing/proc/disable()
+ SIGNAL_HANDLER
+
if(!active)
return
STOP_PROCESSING(SSfluids, src)
@@ -144,7 +145,7 @@
update_dir()
active = TRUE
var/atom/movable/AM = parent
- for(var/obj/machinery/duct/D in AM.loc) //Destroy any ducts under us. Ducts also self destruct if placed under a plumbing machine. machines disable when they get moved
+ for(var/obj/machinery/duct/D in AM.loc) //Destroy any ducts under us. Ducts also self-destruct if placed under a plumbing machine. machines disable when they get moved
if(D.anchored) //that should cover everything
D.disconnect_duct()
@@ -164,6 +165,8 @@
/// Toggle our machinery on or off. This is called by a hook from default_unfasten_wrench with anchored as only param, so we dont have to copypaste this on every object that can move
/datum/component/plumbing/proc/toggle_active(obj/O, new_state)
+ SIGNAL_HANDLER
+
if(new_state)
enable()
else
@@ -179,6 +182,7 @@
var/new_supply_connects
var/new_dir = AM.dir
var/angle = 180 - dir2angle(new_dir)
+
if(new_dir == SOUTH)
demand_connects = initial(demand_connects)
supply_connects = initial(supply_connects)
diff --git a/code/datums/components/rad_insulation.dm b/code/datums/components/rad_insulation.dm
index 73d8c29440cf6..ef5510f144688 100644
--- a/code/datums/components/rad_insulation.dm
+++ b/code/datums/components/rad_insulation.dm
@@ -15,11 +15,17 @@
amount = _amount
/datum/component/rad_insulation/proc/rad_probe_react(datum/source)
+ SIGNAL_HANDLER
+
return COMPONENT_BLOCK_RADIATION
/datum/component/rad_insulation/proc/rad_contaminating(datum/source, strength)
+ SIGNAL_HANDLER
+
return COMPONENT_BLOCK_CONTAMINATION
-/datum/component/rad_insulation/proc/rad_pass(datum/source, datum/radiation_wave/wave, width)
- wave.intensity = wave.intensity*(1-((1-amount)/width)) // The further out the rad wave goes the less it's affected by insulation (larger width)
+/datum/component/rad_insulation/proc/rad_pass(datum/source, datum/radiation_wave/wave, index)
+ SIGNAL_HANDLER
+
+ wave.intensity[index] *= amount
return COMPONENT_RAD_WAVE_HANDLED
diff --git a/code/datums/components/radioactive.dm b/code/datums/components/radioactive.dm
index e892afeea3541..b12f00adb01dd 100644
--- a/code/datums/components/radioactive.dm
+++ b/code/datums/components/radioactive.dm
@@ -23,7 +23,7 @@
RegisterSignal(parent, COMSIG_ITEM_ATTACK_OBJ, .proc/rad_attack)
else
return COMPONENT_INCOMPATIBLE
- if(strength > RAD_MINIMUM_CONTAMINATION)
+ if(strength * (RAD_CONTAMINATION_STR_COEFFICIENT * RAD_CONTAMINATION_BUDGET_SIZE) > RAD_COMPONENT_MINIMUM)
SSradiation.warn(src)
//Let's make er glow
//This relies on parent not being a turf or something. IF YOU CHANGE THAT, CHANGE THIS
@@ -38,17 +38,17 @@
master.remove_filter("rad_glow")
return ..()
-/datum/component/radioactive/process()
- if(!prob(50))
+/datum/component/radioactive/process(delta_time)
+ if(!DT_PROB(50, delta_time))
return
- radiation_pulse(parent, strength, RAD_DISTANCE_COEFFICIENT*2, FALSE, can_contaminate)
+ if(strength >= RAD_WAVE_MINIMUM)
+ radiation_pulse(parent, strength, RAD_DISTANCE_COEFFICIENT*RAD_DISTANCE_COEFFICIENT_COMPONENT_MULTIPLIER, FALSE, can_contaminate)
if(!hl3_release_date)
return
strength -= strength / hl3_release_date
- if(strength < RAD_WAVE_MINIMUM)
+ if(strength < RAD_COMPONENT_MINIMUM)
qdel(src)
- return PROCESS_KILL
/datum/component/radioactive/proc/glow_loop(atom/movable/master)
var/filter = master.get_filter("rad_glow")
@@ -56,18 +56,20 @@
animate(filter, alpha = 110, time = 15, loop = -1)
animate(alpha = 40, time = 25)
-/datum/component/radioactive/InheritComponent(datum/component/C, i_am_original, list/arguments)
+/datum/component/radioactive/InheritComponent(datum/component/C, i_am_original, _strength, _source, _half_life, _can_contaminate)
if(!i_am_original)
return
if(!hl3_release_date) // Permanently radioactive things don't get to grow stronger
return
if(C)
var/datum/component/radioactive/other = C
- strength = max(strength, other.strength)
+ strength += other.strength
else
- strength = max(strength, arguments[1])
+ strength = max(strength, _strength)
/datum/component/radioactive/proc/rad_examine(datum/source, mob/user, atom/thing)
+ SIGNAL_HANDLER
+
var/atom/master = parent
var/list/out = list()
if(get_dist(master, user) <= 1)
@@ -84,6 +86,8 @@
to_chat(user, out.Join())
/datum/component/radioactive/proc/rad_attack(datum/source, atom/movable/target, mob/living/user)
+ SIGNAL_HANDLER
+
radiation_pulse(parent, strength/20)
target.rad_act(strength/2)
if(!hl3_release_date)
diff --git a/code/datums/components/religious_tool.dm b/code/datums/components/religious_tool.dm
new file mode 100644
index 0000000000000..0f11e5d3d20e9
--- /dev/null
+++ b/code/datums/components/religious_tool.dm
@@ -0,0 +1,153 @@
+/**
+ *
+ * Allows the parent to act similarly to the Altar of Gods with modularity. Invoke and Sect Selection is done via attacking with a bible. This means you cannot sacrifice Bibles (you shouldn't want to do this anyways although now that I mentioned it you probably will want to).
+ *
+ */
+/datum/component/religious_tool
+ dupe_mode = COMPONENT_DUPE_UNIQUE
+ /// Enables access to the global sect directly
+ var/datum/religion_sect/easy_access_sect
+ /// What extent do we want this religious tool to act? In case you don't want full access to the list. Generated on New
+ var/operation_flags
+ /// The rite currently being invoked
+ var/datum/religion_rites/performing_rite
+ ///Sets the type for catalyst
+ var/catalyst_type = /obj/item/storage/book/bible
+ ///Enables overide of COMPONENT_NO_AFTERATTACK, not recommended as it means you can potentially cause damage to the item using the catalyst.
+ var/force_catalyst_afterattack = FALSE
+ var/datum/callback/after_sect_select_cb
+
+/datum/component/religious_tool/Initialize(_flags = ALL, _force_catalyst_afterattack = FALSE, _after_sect_select_cb, override_catalyst_type)
+ . = ..()
+ operation_flags = _flags
+ force_catalyst_afterattack = _force_catalyst_afterattack
+ after_sect_select_cb = _after_sect_select_cb
+ SetGlobalToLocal() //attempt to connect on start in case one already exists!
+ if(override_catalyst_type)
+ catalyst_type = override_catalyst_type
+
+/datum/component/religious_tool/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_PARENT_ATTACKBY,.proc/AttemptActions)
+ RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine)
+
+/datum/component/religious_tool/UnregisterFromParent()
+ UnregisterSignal(parent, list(COMSIG_PARENT_ATTACKBY, COMSIG_PARENT_EXAMINE))
+
+/**
+ * Sets the easy access variable to the global if it exists.
+ */
+/datum/component/religious_tool/proc/SetGlobalToLocal()
+ if(easy_access_sect)
+ return TRUE
+ if(!GLOB.religious_sect)
+ return FALSE
+ easy_access_sect = GLOB.religious_sect
+ after_sect_select_cb?.Invoke()
+ return TRUE
+
+
+
+/**
+ * Since all of these involve attackby, we require mega proc. Handles Invocation, Sacrificing, And Selection of Sects.
+ */
+/datum/component/religious_tool/proc/AttemptActions(datum/source, obj/item/the_item, mob/living/user)
+ /**********Sect Selection**********/
+ if(!SetGlobalToLocal())
+ if(!(operation_flags & RELIGION_TOOL_SECTSELECT))
+ return
+ . = COMPONENT_NO_AFTERATTACK //At this point you're intentionally trying to select a sect.
+ if(user.mind.holy_role != HOLY_ROLE_HIGHPRIEST)
+ to_chat(user, "You are not the high priest, and therefore cannot select a religious sect.")
+ return
+ var/list/available_options = generate_available_sects(user)
+ if(!available_options)
+ return
+
+ var/sect_select = input(user,"Select a sect (You CANNOT revert this decision!)","Select a Sect",null) in available_options
+ if(!sect_select || !user.canUseTopic(parent, BE_CLOSE, FALSE, NO_TK))
+ to_chat(user,"You cannot select a sect at this time.")
+ return
+ var/type_selected = available_options[sect_select]
+ GLOB.religious_sect = new type_selected()
+ for(var/mob/living/am_i_holy_living in GLOB.player_list)
+ if(!am_i_holy_living.mind?.holy_role)
+ continue
+ GLOB.religious_sect.on_conversion(am_i_holy_living)
+ easy_access_sect = GLOB.religious_sect
+ after_sect_select_cb?.Invoke()
+ return
+ /**********Rite Invocation**********/
+ else if(istype(the_item, catalyst_type))
+ if(!(operation_flags & RELIGION_TOOL_INVOKE))
+ return
+ . = force_catalyst_afterattack ? null : COMPONENT_NO_AFTERATTACK
+ if(!easy_access_sect.rites_list)
+ to_chat(user, "Your sect doesn't have any rites to perform!")
+ return
+ if(performing_rite)
+ to_chat(user, "There is a rite currently being performed here already!")
+ return
+ var/rite_select = input(user,"Select a rite to perform!","Select a rite",null) in easy_access_sect.rites_list
+ if(!rite_select || !user.canUseTopic(parent, BE_CLOSE, FALSE, NO_TK))
+ to_chat(user,"You cannot perform the rite at this time.")
+ return
+ var/selection2type = easy_access_sect.rites_list[rite_select]
+ performing_rite = new selection2type(parent)
+ if(!performing_rite.perform_rite(user, parent))
+ QDEL_NULL(performing_rite)
+ else
+ performing_rite.invoke_effect(user, parent)
+ easy_access_sect.adjust_favor(-performing_rite.favor_cost)
+ QDEL_NULL(performing_rite)
+ return
+
+ /**********Sacrificing**********/
+ else if(operation_flags & RELIGION_TOOL_SACRIFICE)
+ if(!easy_access_sect?.can_sacrifice(the_item,user))
+ if(user.a_intent != INTENT_HARM && !(the_item.item_flags & ABSTRACT))
+ var/turf/location = get_turf(parent)
+ if(location)
+ user.transferItemToLoc(the_item, location)
+ return COMPONENT_NO_AFTERATTACK
+ return
+ easy_access_sect.on_sacrifice(the_item,user)
+ return COMPONENT_NO_AFTERATTACK
+
+
+/**
+ * Generates a list of available sects to the user. Intended to support custom-availability sects. Because these are not instanced, we cannot put the availability on said sect beyond variables.
+ */
+/datum/component/religious_tool/proc/generate_available_sects(mob/user)
+ . = list()
+ for(var/i in subtypesof(/datum/religion_sect))
+ var/datum/religion_sect/not_a_real_instance_rs = i
+ if(initial(not_a_real_instance_rs.starter))
+ . += list(initial(not_a_real_instance_rs.name) = i)
+
+/**
+ * Appends to examine so the user knows it can be used for religious purposes.
+ */
+/datum/component/religious_tool/proc/on_examine(datum/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+
+ var/can_i_see = FALSE
+ if(isobserver(user))
+ can_i_see = TRUE
+ else if(isliving(user))
+ var/mob/living/L = user
+ if(L.mind?.holy_role)
+ can_i_see = TRUE
+
+ if(!can_i_see)
+ return
+ if(!easy_access_sect)
+ if(operation_flags & RELIGION_TOOL_SECTSELECT)
+ examine_list += "This looks like it can be used to select a sect."
+ return
+
+ examine_list += "The sect currently has [round(easy_access_sect.favor)] favor with [GLOB.deity]. [(operation_flags & RELIGION_TOOL_SACRIFICE) ? "Desired items can be used on this to increase favor." : ""]"
+ if(!easy_access_sect.rites_list)
+ return //if we dont have rites it doesnt do us much good if the object can be used to invoke them!
+ if(operation_flags & RELIGION_TOOL_INVOKE)
+ examine_list += "List of available Rites:"
+ examine_list += easy_access_sect.rites_list
diff --git a/code/datums/components/remote_materials.dm b/code/datums/components/remote_materials.dm
index 4d6b01b9f1798..84f7f74a15754 100644
--- a/code/datums/components/remote_materials.dm
+++ b/code/datums/components/remote_materials.dm
@@ -24,6 +24,7 @@ handles linking back and forth.
src.allow_standalone = allow_standalone
RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/OnAttackBy)
+ RegisterSignal(parent, COMSIG_MOVABLE_Z_CHANGED, .proc/check_z_disconnect)
var/turf/T = get_turf(parent)
if (force_connect || (mapload && is_station_level(T.z)))
@@ -53,18 +54,30 @@ handles linking back and forth.
/datum/component/remote_materials/proc/_MakeLocal()
silo = null
- mat_container = parent.AddComponent(/datum/component/material_container,
- list(/datum/material/iron, /datum/material/glass, /datum/material/copper, /datum/material/silver, /datum/material/gold, /datum/material/diamond, /datum/material/plasma, /datum/material/uranium, /datum/material/bananium, /datum/material/titanium, /datum/material/bluespace, /datum/material/plastic),
- local_size,
- FALSE,
- /obj/item/stack)
+
+ var/static/list/allowed_mats = list(
+ /datum/material/iron,
+ /datum/material/glass,
+ /datum/material/copper,
+ /datum/material/silver,
+ /datum/material/gold,
+ /datum/material/diamond,
+ /datum/material/plasma,
+ /datum/material/uranium,
+ /datum/material/bananium,
+ /datum/material/titanium,
+ /datum/material/bluespace,
+ /datum/material/plastic,
+ )
+
+ mat_container = parent.AddComponent(/datum/component/material_container, allowed_mats, local_size, allowed_types=/obj/item/stack)
/datum/component/remote_materials/proc/set_local_size(size)
local_size = size
if (!silo && mat_container)
mat_container.max_amount = size
-// called if disconnected by ore silo UI or destruction
+// called if disconnected by ore silo UI, or destruction
/datum/component/remote_materials/proc/disconnect_from(obj/machinery/ore_silo/old_silo)
if (!old_silo || silo != old_silo)
return
@@ -72,13 +85,42 @@ handles linking back and forth.
mat_container = null
if (allow_standalone)
_MakeLocal()
+ return TRUE
+
+/datum/component/remote_materials/proc/is_valid_link(atom/targeta, atom/targetb = silo)
+ return ((is_station_level(targeta.z) && is_station_level(targetb.z)) || (targeta.get_virtual_z_level() == targetb.get_virtual_z_level()))
+
+
+/datum/component/remote_materials/proc/check_z_disconnect()
+ SIGNAL_HANDLER
+ if(!silo) //No silo?
+ return
+ var/atom/P = parent
+ if(!is_valid_link(P))
+ graceful_disconnect()
+
+// like disconnect_from, but does proper cleanup instead of simple deletion.
+/datum/component/remote_materials/proc/graceful_disconnect()
+ var/obj/machinery/ore_silo/old_silo = silo
+ if(!disconnect_from(old_silo))
+ return
+ old_silo.connected -= src
+ old_silo.updateUsrDialog()
+ var/atom/P = parent
+ P.visible_message("[parent]'s material manager blinks orange: Disconnected.")
/datum/component/remote_materials/proc/OnAttackBy(datum/source, obj/item/I, mob/user)
+ SIGNAL_HANDLER
+
if(I.tool_behaviour == TOOL_MULTITOOL)
if(!I.multitool_check_buffer(user, I))
return COMPONENT_NO_AFTERATTACK
var/obj/item/multitool/M = I
if (!QDELETED(M.buffer) && istype(M.buffer, /obj/machinery/ore_silo))
+ var/atom/P = parent
+ if (!is_valid_link(P, M.buffer))
+ to_chat(usr, "[parent]'s material manager blinks red: Out of Range.")
+ return COMPONENT_NO_AFTERATTACK
if (silo == M.buffer)
to_chat(user, "[parent] is already connected to [silo].")
return COMPONENT_NO_AFTERATTACK
@@ -111,3 +153,21 @@ handles linking back and forth.
return "[mat_container.total_amount] / [mat_container.max_amount == INFINITY ? "Unlimited" : mat_container.max_amount] ([silo ? "remote" : "local"])"
else
return "0 / 0"
+
+/// Ejects the given material ref and logs it, or says out loud the problem.
+/datum/component/remote_materials/proc/eject_sheets(datum/material/material_ref, eject_amount)
+ var/atom/movable/movable_parent = parent
+ if (!istype(movable_parent))
+ return 0
+
+ if (!mat_container)
+ movable_parent.say("No access to material storage, please contact the quartermaster.")
+ return 0
+ if (on_hold())
+ movable_parent.say("Mineral access is on hold, please contact the quartermaster.")
+ return 0
+ var/count = mat_container.retrieve_sheets(eject_amount, material_ref, movable_parent.drop_location())
+ var/list/matlist = list()
+ matlist[material_ref] = eject_amount
+ silo_log(parent, "ejected", -count, "sheets", matlist)
+ return count
diff --git a/code/datums/components/riding.dm b/code/datums/components/riding.dm
index ad344daf447cc..bc743efc87cd3 100644
--- a/code/datums/components/riding.dm
+++ b/code/datums/components/riding.dm
@@ -29,6 +29,9 @@
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/vehicle_moved)
/datum/component/riding/proc/vehicle_mob_unbuckle(datum/source, mob/living/M, force = FALSE)
+ SIGNAL_HANDLER
+
+ unequip_buckle_inhands(parent)
var/atom/movable/AM = parent
restore_position(M)
unequip_buckle_inhands(M)
@@ -36,6 +39,8 @@
qdel(src)
/datum/component/riding/proc/vehicle_mob_buckle(datum/source, mob/living/M, force = FALSE)
+ SIGNAL_HANDLER
+
handle_vehicle_offsets()
/datum/component/riding/proc/handle_vehicle_layer()
@@ -52,6 +57,8 @@
directional_vehicle_layers["[dir]"] = layer
/datum/component/riding/proc/vehicle_moved(datum/source)
+ SIGNAL_HANDLER
+
var/atom/movable/AM = parent
for(var/i in AM.buckled_mobs)
ride_check(i)
@@ -213,6 +220,8 @@
H.add_movespeed_modifier(MOVESPEED_ID_HUMAN_CARRYING, multiplicative_slowdown = HUMAN_CARRY_SLOWDOWN)
/datum/component/riding/human/proc/on_host_unarmed_melee(atom/target)
+ SIGNAL_HANDLER
+
var/mob/living/carbon/human/H = parent
if(H.a_intent == INTENT_DISARM && (target in H.buckled_mobs))
force_dismount(target)
@@ -307,7 +316,7 @@
M.visible_message("[M] is thrown clear of [AM]!", \
"You're thrown clear of [AM]!")
M.throw_at(target, 14, 5, AM)
- M.Paralyze(60)
+ M.Knockdown(60)
/datum/component/riding/proc/equip_buckle_inhands(mob/living/carbon/human/user, amount_required = 1, riding_target_override = null)
var/atom/movable/AM = parent
diff --git a/code/datums/components/rot.dm b/code/datums/components/rot.dm
index f78c640516598..cb195ea0f83df 100644
--- a/code/datums/components/rot.dm
+++ b/code/datums/components/rot.dm
@@ -10,7 +10,11 @@
START_PROCESSING(SSprocessing, src)
-/datum/component/rot/process()
+/datum/component/rot/Destroy()
+ STOP_PROCESSING(SSprocessing, src)
+ return ..()
+
+/datum/component/rot/process(delta_time)
var/atom/A = parent
var/turf/open/T = get_turf(A)
@@ -18,7 +22,7 @@
return
var/datum/gas_mixture/stank = new
- stank.set_moles(/datum/gas/miasma, amount)
+ stank.set_moles(GAS_MIASMA, amount * delta_time)
stank.set_temperature(BODYTEMP_NORMAL) // otherwise we have gas below 2.7K which will break our lag generator
T.assume_air(stank)
T.air_update_turf()
@@ -33,6 +37,9 @@
/datum/component/rot/corpse/process()
var/mob/living/carbon/C = parent
+ if(C == null) //can't delete what doesnt exist
+ return
+
if(C.stat != DEAD)
qdel(src)
return
diff --git a/code/datums/components/rotation.dm b/code/datums/components/rotation.dm
index 42b8d2af297f3..b3f8208e58319 100644
--- a/code/datums/components/rotation.dm
+++ b/code/datums/components/rotation.dm
@@ -51,28 +51,28 @@
if(rotation_flags & ROTATION_WRENCH)
RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/WrenchRot)
-/datum/component/simple_rotation/proc/add_verbs()
+/datum/component/simple_rotation/proc/add_rotation_verbs()
if(rotation_flags & ROTATION_VERBS)
var/atom/movable/AM = parent
if(rotation_flags & ROTATION_FLIP)
- AM.verbs += /atom/movable/proc/simple_rotate_flip
+ AM.add_verb(/atom/movable/proc/simple_rotate_flip)
if(src.rotation_flags & ROTATION_CLOCKWISE)
- AM.verbs += /atom/movable/proc/simple_rotate_clockwise
+ AM.add_verb(/atom/movable/proc/simple_rotate_clockwise)
if(src.rotation_flags & ROTATION_COUNTERCLOCKWISE)
- AM.verbs += /atom/movable/proc/simple_rotate_counterclockwise
+ AM.add_verb(/atom/movable/proc/simple_rotate_counterclockwise)
-/datum/component/simple_rotation/proc/remove_verbs()
+/datum/component/simple_rotation/proc/remove_rotation_verbs()
if(parent)
var/atom/movable/AM = parent
- AM.verbs -= /atom/movable/proc/simple_rotate_flip
- AM.verbs -= /atom/movable/proc/simple_rotate_clockwise
- AM.verbs -= /atom/movable/proc/simple_rotate_counterclockwise
+ AM.remove_verb(/atom/movable/proc/simple_rotate_flip)
+ AM.remove_verb(/atom/movable/proc/simple_rotate_clockwise)
+ AM.remove_verb(/atom/movable/proc/simple_rotate_counterclockwise)
/datum/component/simple_rotation/proc/remove_signals()
UnregisterSignal(parent, list(COMSIG_CLICK_ALT, COMSIG_PARENT_EXAMINE, COMSIG_PARENT_ATTACKBY))
/datum/component/simple_rotation/RegisterWithParent()
- add_verbs()
+ add_rotation_verbs()
add_signals()
. = ..()
@@ -83,7 +83,7 @@
return COMPONENT_NOTRANSFER
/datum/component/simple_rotation/UnregisterFromParent()
- remove_verbs()
+ remove_rotation_verbs()
remove_signals()
. = ..()
@@ -95,19 +95,25 @@
. = ..()
/datum/component/simple_rotation/RemoveComponent()
- remove_verbs()
+ remove_rotation_verbs()
. = ..()
/datum/component/simple_rotation/proc/ExamineMessage(datum/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+
if(rotation_flags & ROTATION_ALTCLICK)
examine_list += "Alt-click to rotate it clockwise."
/datum/component/simple_rotation/proc/HandRot(datum/source, mob/user, rotation = default_rotation_direction)
+ SIGNAL_HANDLER
+
if(!can_be_rotated.Invoke(user, rotation) || !can_user_rotate.Invoke(user, rotation))
return
BaseRot(user, rotation)
/datum/component/simple_rotation/proc/WrenchRot(datum/source, obj/item/I, mob/living/user)
+ SIGNAL_HANDLER
+
if(!can_be_rotated.Invoke(user,default_rotation_direction) || !can_user_rotate.Invoke(user,default_rotation_direction))
return
if(I.tool_behaviour == TOOL_WRENCH)
diff --git a/code/datums/components/shell.dm b/code/datums/components/shell.dm
new file mode 100644
index 0000000000000..9fd124d79e58c
--- /dev/null
+++ b/code/datums/components/shell.dm
@@ -0,0 +1,244 @@
+/// Makes an atom a shell that is able to take in an attached circuit.
+/datum/component/shell
+ dupe_mode = COMPONENT_DUPE_UNIQUE
+
+ /// The circuitboard attached to this shell
+ var/obj/item/integrated_circuit/attached_circuit
+
+ /// Flags containing what this shell can do
+ var/shell_flags = NONE
+
+ /// The capacity of the shell.
+ var/capacity = INFINITY
+
+ /// A list of components that cannot be removed
+ var/list/obj/item/circuit_component/unremovable_circuit_components
+
+ var/locked = FALSE
+
+/datum/component/shell/Initialize(unremovable_circuit_components, capacity, shell_flags)
+ . = ..()
+ if(!ismovable(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ src.shell_flags = shell_flags || src.shell_flags
+ src.capacity = capacity || src.capacity
+ src.unremovable_circuit_components = unremovable_circuit_components
+
+ for(var/obj/item/circuit_component/circuit_component as anything in unremovable_circuit_components)
+ circuit_component.removable = FALSE
+
+/datum/component/shell/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/on_attack_by)
+ RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine)
+ RegisterSignal(parent, COMSIG_ATOM_ATTACK_GHOST, .proc/on_attack_ghost)
+ if(!(shell_flags & SHELL_FLAG_CIRCUIT_FIXED))
+ RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER), .proc/on_screwdriver_act)
+ RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), .proc/on_multitool_act)
+ RegisterSignal(parent, COMSIG_OBJ_DECONSTRUCT, .proc/on_object_deconstruct)
+ if(shell_flags & SHELL_FLAG_REQUIRE_ANCHOR)
+ RegisterSignal(parent, COMSIG_OBJ_DEFAULT_UNFASTEN_WRENCH, .proc/on_unfasten)
+ RegisterSignal(parent, COMSIG_ATOM_USB_CABLE_TRY_ATTACH, .proc/on_atom_usb_cable_try_attach)
+
+/datum/component/shell/UnregisterFromParent()
+ UnregisterSignal(parent, list(
+ COMSIG_PARENT_ATTACKBY,
+ COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER),
+ COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL),
+ COMSIG_OBJ_DECONSTRUCT,
+ COMSIG_OBJ_DEFAULT_UNFASTEN_WRENCH,
+ COMSIG_PARENT_EXAMINE,
+ COMSIG_ATOM_ATTACK_GHOST,
+ COMSIG_ATOM_USB_CABLE_TRY_ATTACH,
+ ))
+
+ QDEL_NULL(attached_circuit)
+
+/datum/component/shell/Destroy(force, silent)
+ QDEL_LIST(unremovable_circuit_components)
+ return ..()
+
+/datum/component/shell/proc/on_object_deconstruct()
+ SIGNAL_HANDLER
+ remove_circuit()
+
+/datum/component/shell/proc/on_attack_ghost(datum/source, mob/dead/observer/ghost)
+ SIGNAL_HANDLER
+ if(attached_circuit)
+ INVOKE_ASYNC(attached_circuit, /datum.proc/ui_interact, ghost)
+
+/datum/component/shell/proc/on_examine(datum/source, mob/user, list/examine_text)
+ SIGNAL_HANDLER
+ if(!attached_circuit)
+ examine_text += "There is no integrated circuit attached."
+ return
+
+ examine_text += "There is an integrated circuit attached. Use a multitool to access the wiring. Use a screwdriver to remove it from [source]."
+ examine_text += "The cover panel to the integrated circuit is [locked? "locked" : "unlocked"]."
+ var/obj/item/stock_parts/cell/cell = attached_circuit.cell
+ examine_text += "The charge meter reads [cell ? round(cell.percent(), 1) : 0]%."
+
+ if (shell_flags & SHELL_FLAG_USB_PORT)
+ examine_text += "There is a USB port on the front."
+
+
+/**
+ * Called when the shell is wrenched.
+ *
+ * Only applies if the shell has SHELL_FLAG_REQUIRE_ANCHOR.
+ * Disables the integrated circuit if unanchored, otherwise enable the circuit.
+ */
+/datum/component/shell/proc/on_unfasten(atom/source, anchored)
+ SIGNAL_HANDLER
+ attached_circuit?.on = anchored
+/**
+ * Called when an item hits the parent. This is the method to add the circuitboard to the component.
+ */
+/datum/component/shell/proc/on_attack_by(atom/source, obj/item/item, mob/living/attacker)
+ SIGNAL_HANDLER
+
+ if(istype(item, /obj/item/stock_parts/cell))
+ source.balloon_alert(attacker, "Can't pull cell in directly!")
+ return
+
+ if(attached_circuit?.owner_id && item == attached_circuit.owner_id.resolve())
+ locked = !locked
+ source.balloon_alert(attacker, "[locked? "Locked" : "Unlocked"] [source]")
+ return COMPONENT_NO_AFTERATTACK
+
+ if(attached_circuit && istype(item, /obj/item/circuit_component))
+ attached_circuit.add_component(item, attacker)
+ return
+
+ if(!istype(item, /obj/item/integrated_circuit))
+ return
+ var/obj/item/integrated_circuit/logic_board = item
+ . = COMPONENT_NO_AFTERATTACK
+
+ if(logic_board.shell) // I'll be surprised if this ever happens
+ return
+
+ if(attached_circuit)
+ source.balloon_alert(attacker, "There is already a circuitboard inside!")
+ return
+
+ if(length(logic_board.attached_components) > capacity)
+ source.balloon_alert(attacker, "This is too large to fit into [parent]!")
+ return
+
+ logic_board.inserter_mind = WEAKREF(attacker.mind)
+ attach_circuit(logic_board, attacker)
+
+/datum/component/shell/proc/on_multitool_act(atom/source, mob/user, obj/item/tool)
+ SIGNAL_HANDLER
+ if(!attached_circuit)
+ return
+
+ if(locked)
+ if(shell_flags & SHELL_FLAG_ALLOW_FAILURE_ACTION)
+ return
+ source.balloon_alert(user, "It's locked!")
+ return COMPONENT_BLOCK_TOOL_ATTACK
+
+ attached_circuit.interact(user)
+ return COMPONENT_BLOCK_TOOL_ATTACK
+
+/**
+ * Called when a screwdriver is used on the parent. Removes the circuitboard from the component.
+ */
+/datum/component/shell/proc/on_screwdriver_act(atom/source, mob/user, obj/item/tool)
+ SIGNAL_HANDLER
+ if(!attached_circuit)
+ return
+
+ if(locked)
+ if(shell_flags & SHELL_FLAG_ALLOW_FAILURE_ACTION)
+ return
+ source.balloon_alert(user, "It's locked!")
+ return COMPONENT_BLOCK_TOOL_ATTACK
+
+ tool.play_tool_sound(parent)
+ source.balloon_alert(user, "You unscrew [attached_circuit] from [parent].")
+ remove_circuit()
+ return COMPONENT_BLOCK_TOOL_ATTACK
+
+/**
+ * Checks for when the circuitboard moves. If it moves, removes it from the component.
+ */
+/datum/component/shell/proc/on_circuit_moved(obj/item/integrated_circuit/circuit, atom/new_loc)
+ SIGNAL_HANDLER
+ if(new_loc != parent)
+ remove_circuit()
+
+/**
+ * Checks for when the circuitboard deletes so that it can be unassigned.
+ */
+/datum/component/shell/proc/on_circuit_delete(datum/source)
+ SIGNAL_HANDLER
+ remove_circuit()
+
+/datum/component/shell/proc/on_circuit_add_component_manually(atom/source, obj/item/circuit_component/added_comp, mob/living/user)
+ SIGNAL_HANDLER
+
+ if(locked)
+ source.balloon_alert(user, "It's locked!")
+ return COMPONENT_CANCEL_ADD_COMPONENT
+
+ if(length(attached_circuit.attached_components) - length(unremovable_circuit_components) >= capacity)
+ source.balloon_alert(user, "It's at maximum capacity!")
+ return COMPONENT_CANCEL_ADD_COMPONENT
+
+/**
+ * Attaches a circuit to the parent. Doesn't do any checks to see for any existing circuits so that should be done beforehand.
+ */
+/datum/component/shell/proc/attach_circuit(obj/item/integrated_circuit/circuitboard, mob/living/user)
+ if(!user.transferItemToLoc(circuitboard, parent))
+ return
+ locked = FALSE
+ attached_circuit = circuitboard
+ RegisterSignal(circuitboard, COMSIG_MOVABLE_MOVED, .proc/on_circuit_moved)
+ RegisterSignal(circuitboard, COMSIG_PARENT_QDELETING, .proc/on_circuit_delete)
+ for(var/obj/item/circuit_component/to_add as anything in unremovable_circuit_components)
+ to_add.forceMove(attached_circuit)
+ attached_circuit.add_component(to_add)
+ RegisterSignal(circuitboard, COMSIG_CIRCUIT_ADD_COMPONENT_MANUALLY, .proc/on_circuit_add_component_manually)
+ attached_circuit.set_shell(parent)
+ user.balloon_alert(user, "Attached [circuitboard] to [parent]")
+
+ if(shell_flags & SHELL_FLAG_REQUIRE_ANCHOR)
+ var/atom/movable/parent_atom = parent
+ on_unfasten(parent_atom, parent_atom.anchored)
+
+/**
+ * Removes the circuit from the component. Doesn't do any checks to see for an existing circuit so that should be done beforehand.
+ */
+/datum/component/shell/proc/remove_circuit()
+ attached_circuit.on = TRUE
+ attached_circuit.remove_current_shell()
+ UnregisterSignal(attached_circuit, list(
+ COMSIG_MOVABLE_MOVED,
+ COMSIG_PARENT_QDELETING,
+ COMSIG_CIRCUIT_ADD_COMPONENT_MANUALLY,
+ ))
+ if(attached_circuit.loc == parent)
+ var/atom/parent_atom = parent
+ attached_circuit.forceMove(parent_atom.drop_location())
+
+ for(var/obj/item/circuit_component/to_remove as anything in unremovable_circuit_components)
+ attached_circuit.remove_component(to_remove)
+ to_remove.moveToNullspace()
+ attached_circuit = null
+
+/datum/component/shell/proc/on_atom_usb_cable_try_attach(atom/source, obj/item/usb_cable/usb_cable, mob/user)
+ SIGNAL_HANDLER
+
+ if (!(shell_flags & SHELL_FLAG_USB_PORT))
+ source.balloon_alert(user, "This shell has no usb ports")
+ return COMSIG_CANCEL_USB_CABLE_ATTACK
+
+ if (isnull(attached_circuit))
+ source.balloon_alert(user, "No circuit inside")
+ return COMSIG_CANCEL_USB_CABLE_ATTACK
+
+ usb_cable.attached_circuit = attached_circuit
+ return COMSIG_USB_CABLE_CONNECTED_TO_CIRCUIT
diff --git a/code/datums/components/slippery.dm b/code/datums/components/slippery.dm
index 172f252d5af21..294fc53621690 100644
--- a/code/datums/components/slippery.dm
+++ b/code/datums/components/slippery.dm
@@ -14,6 +14,8 @@
RegisterSignal(parent, list(COMSIG_MOVABLE_CROSSED, COMSIG_ATOM_ENTERED), .proc/Slip)
/datum/component/slippery/proc/Slip(datum/source, atom/movable/AM)
+ SIGNAL_HANDLER
+
var/mob/victim = AM
if(istype(victim) && !victim.is_flying() && victim.slip(knockdown_time, parent, lube_flags, paralyze_time, force_drop_items) && callback)
callback.Invoke(victim)
diff --git a/code/datums/components/snail_crawl.dm b/code/datums/components/snail_crawl.dm
index 555bbdeed74d5..59a0af93cf4aa 100644
--- a/code/datums/components/snail_crawl.dm
+++ b/code/datums/components/snail_crawl.dm
@@ -6,6 +6,8 @@
snail = parent
/datum/component/snailcrawl/proc/lubricate()
+ SIGNAL_HANDLER
+
if(snail.resting && !snail.buckled) //s l i d e
var/turf/open/OT = get_turf(snail)
if(isopenturf(OT))
@@ -16,4 +18,4 @@
/datum/component/snailcrawl/_RemoveFromParent()
snail.remove_movespeed_modifier(MOVESPEED_ID_SNAIL_CRAWL)
- return ..()
\ No newline at end of file
+ return ..()
diff --git a/code/datums/components/soundplayer.dm b/code/datums/components/soundplayer.dm
new file mode 100644
index 0000000000000..1671c69dd4260
--- /dev/null
+++ b/code/datums/components/soundplayer.dm
@@ -0,0 +1,41 @@
+/*This is the sound_player component. It can be attached to any datum and register any signal to play the sound(s) you want, when you want. Used for the honk virus as an example
+ Usage :
+ target.AddComponent(/datum/component/sound_player, args)
+ Arguments :
+ custom_volume : Used to define a custom volume. Default : 30
+ custom_sounds : Used to define a list of custom sounds that will be picked at random when play_sound() is triggered. Default : list('sound/items/bikehorn.ogg')
+ amount : Used to define an amount of time the component will work before deleting itself. Default : -1
+ signal_or_sig_list : Used to register the signal(s) you want to play the sound when they are sent. Default : None
+*/
+/datum/component/sound_player
+ var/volume = 30
+ var/list/sounds = list('sound/items/bikehorn.ogg')
+ var/amount_left = -1
+
+/datum/component/sound_player/Initialize(custom_volume, custom_sounds, amount, signal_or_sig_list)
+ if(!isnull(custom_volume))
+ volume = custom_volume
+
+ if(!isnull(custom_sounds))
+ sounds = custom_sounds
+
+ if(!isnull(amount))
+ amount_left = amount
+
+ RegisterSignal(parent, signal_or_sig_list, .proc/play_sound) //Registers all the signals in signal_or_sig_list.
+
+
+
+/*play_sound() os the proc that actually plays the sound.
+ If amount_left is equal to -1, the component is infinite and will never delete itself.
+*/
+/datum/component/sound_player/proc/play_sound()
+ playsound(parent, pickweight(sounds), volume, TRUE)
+ switch(amount_left)
+ if(-1)
+ return
+ if(1) //Last use.
+ qdel(src)
+ return
+ else
+ amount_left--
diff --git a/code/datums/components/spawner.dm b/code/datums/components/spawner.dm
index 221e3a41d9fd3..8600a59de0cdf 100644
--- a/code/datums/components/spawner.dm
+++ b/code/datums/components/spawner.dm
@@ -6,7 +6,7 @@
var/max_mobs = 5
var/spawn_text = "emerges from"
var/list/faction = list("mining")
-
+
/datum/component/spawner/Initialize(_mob_types, _spawn_time, _faction, _spawn_text, _max_mobs)
@@ -26,9 +26,11 @@
/datum/component/spawner/process()
try_spawn_mob()
-
+
/datum/component/spawner/proc/stop_spawning(force)
+ SIGNAL_HANDLER
+
STOP_PROCESSING(SSprocessing, src)
for(var/mob/living/simple_animal/L in spawned_mobs)
if(L.nest == src)
@@ -36,7 +38,7 @@
spawned_mobs = null
/datum/component/spawner/proc/try_spawn_mob()
- var/atom/P = parent
+ var/atom/P = parent
if(spawned_mobs.len >= max_mobs)
return 0
if(spawn_delay > world.time)
@@ -44,7 +46,7 @@
spawn_delay = world.time + spawn_time
var/chosen_mob_type = pick(mob_types)
var/mob/living/simple_animal/L = new chosen_mob_type(P.loc)
- L.flags_1 |= (P.flags_1 & ADMIN_SPAWNED_1)
+ L.flags_1 |= (P.flags_1 & ADMIN_SPAWNED_1)
spawned_mobs += L
L.nest = src
L.faction = src.faction
diff --git a/code/datums/components/spikes.dm b/code/datums/components/spikes.dm
index 042b5b5019088..ca067ddac53d9 100644
--- a/code/datums/components/spikes.dm
+++ b/code/datums/components/spikes.dm
@@ -7,8 +7,8 @@
var/cooldown = 0
/datum/component/spikes/Initialize(damage = 0, spikearmor = 0, diseaseid = null)
- spikedamage = damage
- armor = spikearmor
+ spikedamage = damage
+ armor = spikearmor
id = diseaseid
RegisterSignal(parent, COMSIG_MOVABLE_BUMP, .proc/prick_collide)
RegisterSignal(parent, COMSIG_MOVABLE_CROSSED, .proc/prick_crossed)
@@ -20,7 +20,7 @@
RegisterSignal(parent, COMSIG_COMPONENT_REMOVING, .proc/removearmor)
RegisterSignal(parent, COMSIG_MOB_ATTACK_HAND, .proc/prick_touch)
RegisterSignal(parent, COMSIG_MOB_HAND_ATTACKED, .proc/prick_touched)
-
+
/datum/component/spikes/proc/prick(mob/living/carbon/C, damage_mod = 1)
var/netdamage = spikedamage * damage_mod
@@ -33,17 +33,25 @@
cooldown = (world.time + 8) //spike cooldown is equal to default unarmed attack speed
/datum/component/spikes/proc/prick_touch(datum/source, mob/living/carbon/human/M, mob/living/carbon/human/H)
+ SIGNAL_HANDLER
+
prick(H, 0.5)
/datum/component/spikes/proc/prick_touched(datum/source, mob/living/carbon/human/H, mob/living/carbon/human/M)
+ SIGNAL_HANDLER
+
prick(M, 1.5)
/datum/component/spikes/proc/prick_collide(datum/source, atom/A)
+ SIGNAL_HANDLER
+
if(iscarbon(A))
var/mob/living/carbon/C = A
prick(C)
/datum/component/spikes/proc/prick_crossed(datum/source, atom/movable/M)
+ SIGNAL_HANDLER
+
var/atom/movable/P = parent
if(iscarbon(M))
var/mob/living/carbon/C = M
@@ -60,6 +68,8 @@
prick(C)
/datum/component/spikes/proc/setarmor(datum/source, datum/species/S) //this is a proc used to make sure a change in species won't fuck up the armor bonus.
+ SIGNAL_HANDLER
+
if(ishuman(parent))
var/mob/living/carbon/human/H = parent
finalarmor = armor
@@ -68,13 +78,16 @@
H.dna.species.armor += finalarmor
/datum/component/spikes/proc/checkdiseasecure(datum/source, var/diseaseid)
+ SIGNAL_HANDLER
+
if(diseaseid == id)
qdel(src) //we were cured! time to go.
/datum/component/spikes/proc/removearmor(datum/source, var/datum/component/C)
+ SIGNAL_HANDLER
+
if(C != src)
return
if(ishuman(parent) && armor)
var/mob/living/carbon/human/H = parent
H.dna.species.armor -= finalarmor
-
\ No newline at end of file
diff --git a/code/datums/components/spill.dm b/code/datums/components/spill.dm
index 1a9a526ec342d..343cdab3f081b 100644
--- a/code/datums/components/spill.dm
+++ b/code/datums/components/spill.dm
@@ -31,24 +31,30 @@
RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/drop_react)
var/obj/item/master = parent
preexisting_item_flags = master.item_flags
- master.item_flags |= ITEM_SLOT_POCKET
+ master.item_flags |= ITEM_SLOT_POCKETS
/datum/component/spill/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED))
var/obj/item/master = parent
- if(!(preexisting_item_flags & ITEM_SLOT_POCKET))
- master.item_flags &= ~ITEM_SLOT_POCKET
+ if(!(preexisting_item_flags & ITEM_SLOT_POCKETS))
+ master.item_flags &= ~ITEM_SLOT_POCKETS
/datum/component/spill/proc/equip_react(obj/item/source, mob/equipper, slot)
- if(slot == SLOT_L_STORE || slot == SLOT_R_STORE)
+ SIGNAL_HANDLER
+
+ if(slot == ITEM_SLOT_LPOCKET || slot == ITEM_SLOT_RPOCKET)
RegisterSignal(equipper, COMSIG_LIVING_STATUS_KNOCKDOWN, .proc/knockdown_react, TRUE)
else
UnregisterSignal(equipper, COMSIG_LIVING_STATUS_KNOCKDOWN)
/datum/component/spill/proc/drop_react(obj/item/source, mob/dropper)
+ SIGNAL_HANDLER
+
UnregisterSignal(dropper, COMSIG_LIVING_STATUS_KNOCKDOWN)
/datum/component/spill/proc/knockdown_react(mob/living/fool)
+ SIGNAL_HANDLER
+
var/obj/item/master = parent
fool.dropItemToGround(master)
if(droptext)
diff --git a/code/datums/components/spinny.dm b/code/datums/components/spinny.dm
new file mode 100644
index 0000000000000..cdf5262ab31b8
--- /dev/null
+++ b/code/datums/components/spinny.dm
@@ -0,0 +1,33 @@
+/**
+ * spinny.dm
+ *
+ * It's a component that spins things a whole bunch, like [proc/dance_rotate] but without the sleeps
+*/
+/datum/component/spinny
+ dupe_mode = COMPONENT_DUPE_UNIQUE
+ /// How many turns are left?
+ var/steps_left
+ /// Turns clockwise by default, or counterclockwise if the reverse argument is TRUE
+ var/turn_degrees = 90
+
+/datum/component/spinny/Initialize(steps = 12, reverse = FALSE)
+ if(!isatom(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ steps_left = steps
+ turn_degrees = (reverse ? -90 : 90)
+ START_PROCESSING(SSfastprocess, src)
+
+/datum/component/spinny/Destroy(force, silent)
+ STOP_PROCESSING(SSfastprocess, src)
+ return ..()
+
+/datum/component/spinny/process(delta_time)
+ steps_left--
+ var/atom/spinny_boy = parent
+ if(!istype(spinny_boy) || steps_left <= 0)
+ qdel(src)
+ return
+
+ // 25% chance to make 2 turns instead of 1 since the old dance_rotate wasn't strictly clockwise/counterclockwise
+ spinny_boy.setDir(turn(spinny_boy.dir, turn_degrees * (prob(25) ? 2 : 1)))
diff --git a/code/datums/components/spooky.dm b/code/datums/components/spooky.dm
index 94620429aa026..dd5a305aa898d 100644
--- a/code/datums/components/spooky.dm
+++ b/code/datums/components/spooky.dm
@@ -5,6 +5,8 @@
RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/spectral_attack)
/datum/component/spooky/proc/spectral_attack(datum/source, mob/living/carbon/C, 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))
@@ -13,7 +15,7 @@
U.stuttering = 20
if(U.getStaminaLoss() > 95)
to_chat(U, "Your ears weren't meant for this spectral sound.")
- spectral_change(U)
+ INVOKE_ASYNC(src, .proc/spectral_change, U)
return
if(ishuman(C))
@@ -28,7 +30,7 @@
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(25) //boneless humanoids don't lose the will to live
to_chat(C, "DOOT")
- spectral_change(H)
+ INVOKE_ASYNC(src, .proc/spectral_change, H)
else //the sound will spook monkeys.
C.Jitter(15)
diff --git a/code/datums/components/squeak.dm b/code/datums/components/squeak.dm
index fed8ab28408af..e50a931e45ab9 100644
--- a/code/datums/components/squeak.dm
+++ b/code/datums/components/squeak.dm
@@ -41,6 +41,8 @@
use_delay = use_delay_override
/datum/component/squeak/proc/play_squeak()
+ SIGNAL_HANDLER
+
if(prob(squeak_chance))
if(!override_squeak_sounds)
playsound(parent, pickweight(default_squeak_sounds), volume, 1, -1)
@@ -48,6 +50,8 @@
playsound(parent, pickweight(override_squeak_sounds), volume, 1, -1)
/datum/component/squeak/proc/step_squeak()
+ SIGNAL_HANDLER
+
if(steps > step_delay)
play_squeak()
steps = 0
@@ -55,6 +59,8 @@
steps++
/datum/component/squeak/proc/play_squeak_crossed(datum/source, atom/movable/AM)
+ SIGNAL_HANDLER
+
if(isitem(AM))
var/obj/item/I = AM
if(I.item_flags & ABSTRACT)
@@ -66,26 +72,36 @@
if(istype(AM, /obj/effect/dummy/phased_mob)) //don't squeek if they're in a phased/jaunting container.
return
var/atom/current_parent = parent
- if(isturf(current_parent.loc))
+ if(isturf(current_parent?.loc))
play_squeak()
/datum/component/squeak/proc/use_squeak()
+ SIGNAL_HANDLER
+
if(last_use + use_delay < world.time)
last_use = world.time
play_squeak()
/datum/component/squeak/proc/on_equip(datum/source, mob/equipper, slot)
+ SIGNAL_HANDLER
+
RegisterSignal(equipper, COMSIG_MOVABLE_DISPOSING, .proc/disposing_react, TRUE)
/datum/component/squeak/proc/on_drop(datum/source, mob/user)
+ SIGNAL_HANDLER
+
UnregisterSignal(user, COMSIG_MOVABLE_DISPOSING)
// Disposal pipes related shit
/datum/component/squeak/proc/disposing_react(datum/source, obj/structure/disposalholder/holder, obj/machinery/disposal/source)
+ SIGNAL_HANDLER
+
//We don't need to worry about unregistering this signal as it will happen for us automaticaly when the holder is qdeleted
RegisterSignal(holder, COMSIG_ATOM_DIR_CHANGE, .proc/holder_dir_change)
/datum/component/squeak/proc/holder_dir_change(datum/source, old_dir, new_dir)
+ SIGNAL_HANDLER
+
//If the dir changes it means we're going through a bend in the pipes, let's pretend we bumped the wall
if(old_dir != new_dir)
play_squeak()
diff --git a/code/datums/components/stationloving.dm b/code/datums/components/stationloving.dm
index 51d67e10e2088..c4c0359feb44f 100644
--- a/code/datums/components/stationloving.dm
+++ b/code/datums/components/stationloving.dm
@@ -16,15 +16,17 @@
src.allow_death = allow_death
check_in_bounds() // Just in case something is being created outside of station/centcom
-/datum/component/stationloving/InheritComponent(datum/component/stationloving/newc, original, list/arguments)
- if (original)
- if (istype(newc))
+/datum/component/stationloving/InheritComponent(datum/component/stationloving/newc, original, _inform_admins, allow_death)
+ if(original)
+ if(newc)
inform_admins = newc.inform_admins
allow_death = newc.allow_death
- else if (LAZYLEN(arguments))
- inform_admins = arguments[1]
+ else
+ inform_admins = _inform_admins
/datum/component/stationloving/proc/relocate()
+ SIGNAL_HANDLER
+
var/targetturf = find_safe_turf()
if(!targetturf)
if(GLOB.blobstart.len > 0)
@@ -39,6 +41,8 @@
return targetturf
/datum/component/stationloving/proc/check_in_bounds()
+ SIGNAL_HANDLER
+
if(in_bounds())
return
else
@@ -49,31 +53,36 @@
message_admins("[parent] has been moved out of bounds in [ADMIN_VERBOSEJMP(currentturf)]. Moving it to [ADMIN_VERBOSEJMP(targetturf)].")
/datum/component/stationloving/proc/check_soul_imbue()
+ SIGNAL_HANDLER
+
return disallow_soul_imbue
/datum/component/stationloving/proc/check_mark_retrieval()
+ SIGNAL_HANDLER
+
return COMPONENT_BLOCK_MARK_RETRIEVAL
/datum/component/stationloving/proc/in_bounds()
var/static/list/allowed_shuttles = typecacheof(list(/area/shuttle/syndicate, /area/shuttle/escape, /area/shuttle/pod_1, /area/shuttle/pod_2, /area/shuttle/pod_3, /area/shuttle/pod_4))
var/static/list/disallowed_centcom_areas = typecacheof(list(/area/abductor_ship, /area/awaymission/errorroom))
var/turf/T = get_turf(parent)
- if (!T)
+ if(!T)
return FALSE
var/area/A = T.loc
- if (is_station_level(T.z))
+ if(is_station_level(T.z))
return TRUE
- if (is_centcom_level(T.z))
+ if(is_centcom_level(T.z))
if (is_type_in_typecache(A, disallowed_centcom_areas))
return FALSE
return TRUE
- if (is_reserved_level(T.z))
+ if(is_reserved_level(T.z))
if (is_type_in_typecache(A, allowed_shuttles))
return TRUE
return FALSE
/datum/component/stationloving/proc/check_deletion(datum/source, force) // TRUE = interrupt deletion, FALSE = proceed with deletion
+ SIGNAL_HANDLER
var/turf/T = get_turf(parent)
diff --git a/code/datums/components/storage/concrete/_concrete.dm b/code/datums/components/storage/concrete/_concrete.dm
index 3fe69103a59ce..41111451edc97 100644
--- a/code/datums/components/storage/concrete/_concrete.dm
+++ b/code/datums/components/storage/concrete/_concrete.dm
@@ -92,12 +92,16 @@
return FALSE
/datum/component/storage/concrete/proc/on_contents_del(datum/source, atom/A)
+ SIGNAL_HANDLER
+
var/atom/real_location = parent
if(A in real_location)
usr = null
remove_from_storage(A, null)
/datum/component/storage/concrete/proc/on_deconstruct(datum/source, disassembled)
+ SIGNAL_HANDLER
+
if(drop_all_on_deconstruct)
do_quick_empty()
@@ -156,7 +160,7 @@
var/datum/component/storage/concrete/master = master()
var/atom/parent = src.parent
var/moved = FALSE
- if(!istype(I))
+ if(!istype(I) || I.anchored)
return FALSE
if(M)
if(!M.temporarilyRemoveItemFromInventory(I))
diff --git a/code/datums/components/storage/concrete/bag_of_holding.dm b/code/datums/components/storage/concrete/bag_of_holding.dm
index 4920811551bb0..80c9e469408e5 100644
--- a/code/datums/components/storage/concrete/bag_of_holding.dm
+++ b/code/datums/components/storage/concrete/bag_of_holding.dm
@@ -5,23 +5,28 @@
var/list/obj/item/storage/backpack/holding/matching = typecache_filter_list(W.GetAllContents(), typecacheof(/obj/item/storage/backpack/holding))
matching -= A
if(istype(W, /obj/item/storage/backpack/holding) || matching.len)
- var/safety = alert(user, "Doing this will have extremely dire consequences for the station and its crew. Be sure you know what you're doing.", "Put in [A.name]?", "Abort", "Proceed")
- if(safety != "Proceed" || QDELETED(A) || QDELETED(W) || QDELETED(user) || !user.canUseTopic(A, BE_CLOSE, iscarbon(user)) || !(W in user.contents)) // need to be holding the bag you're "inserting"
- return
- var/turf/loccheck = get_turf(A)
- if(is_reebe(loccheck.z))
- user.visible_message("An unseen force knocks [user] to the ground!", "\"I think not!\"")
- user.Paralyze(60)
- return
- to_chat(user, "The Bluespace interfaces of the two devices catastrophically malfunction!")
- qdel(W)
- playsound(loccheck,'sound/effects/supermatter.ogg', 200, 1)
-
- message_admins("[ADMIN_LOOKUPFLW(user)] detonated a bag of holding at [ADMIN_VERBOSEJMP(loccheck)].")
- log_game("[key_name(user)] detonated a bag of holding at [loc_name(loccheck)].")
-
- user.gib(TRUE, TRUE, TRUE)
- new/obj/singularity/boh_tear(loccheck)
- qdel(A)
+ INVOKE_ASYNC(src, .proc/recursive_insertion, W, user)
return
. = ..()
+
+/datum/component/storage/concrete/bluespace/bag_of_holding/proc/recursive_insertion(obj/item/W, mob/living/user)
+ var/atom/A = parent
+ var/safety = alert(user, "Doing this will have extremely dire consequences for the station and its crew. Be sure you know what you're doing.", "Put in [A.name]?", "Abort", "Proceed")
+ if(safety != "Proceed" || QDELETED(A) || QDELETED(W) || QDELETED(user) || !user.canUseTopic(A, BE_CLOSE, iscarbon(user)) || !(W in user.contents)) // need to be holding the bag you're "inserting"
+ return
+ var/turf/loccheck = get_turf(A)
+ if(is_reebe(loccheck.z))
+ user.visible_message("An unseen force knocks [user] to the ground!", "\"I think not!\"")
+ user.Paralyze(60)
+ return
+ to_chat(user, "The Bluespace interfaces of the two devices catastrophically malfunction!")
+ qdel(W)
+ playsound(loccheck,'sound/effects/supermatter.ogg', 200, 1)
+
+ message_admins("[ADMIN_LOOKUPFLW(user)] detonated a bag of holding at [ADMIN_VERBOSEJMP(loccheck)].")
+ log_game("[key_name(user)] detonated a bag of holding at [loc_name(loccheck)].")
+
+ user.gib(TRUE, TRUE, TRUE)
+ new/obj/singularity/boh_tear(loccheck)
+ qdel(A)
+
diff --git a/code/datums/components/storage/concrete/bluespace.dm b/code/datums/components/storage/concrete/bluespace.dm
index 05a185322293b..71a95717ac551 100644
--- a/code/datums/components/storage/concrete/bluespace.dm
+++ b/code/datums/components/storage/concrete/bluespace.dm
@@ -9,7 +9,18 @@
var/atom/dumping_location = dest.get_dumping_location()
var/turf/bagT = get_turf(parent)
var/turf/destT = get_turf(dumping_location)
- if(destT && bagT && bagT.z == destT.z && get_dist(M, dumping_location) < dumping_range)
+ var/valid = TRUE
+ if(!destT || !bagT || get_dist(M, dumping_location) >= dumping_range || bagT.get_virtual_z_level() != destT.get_virtual_z_level())
+ valid = FALSE
+ //Check density LOS.
+ if(valid)
+ for(var/turf/T as() in getline(bagT, destT))
+ if(T.density)
+ valid = FALSE
+ break
+
+ //Check still valid
+ if(valid)
if(dumping_location.storage_contents_dump_act(src, M))
if(alt_sound && prob(1))
playsound(src, alt_sound, 40, 1)
@@ -20,4 +31,3 @@
to_chat(M, "The [A.name] buzzes.")
playsound(src, 'sound/machines/buzz-sigh.ogg', 50, 0)
return FALSE
-
diff --git a/code/datums/components/storage/concrete/fish_case.dm b/code/datums/components/storage/concrete/fish_case.dm
new file mode 100644
index 0000000000000..438974186e5fe
--- /dev/null
+++ b/code/datums/components/storage/concrete/fish_case.dm
@@ -0,0 +1,8 @@
+/datum/component/storage/concrete/fish_case
+ max_items = 1
+ can_hold_trait = TRAIT_FISH_CASE_COMPATIBILE
+
+/datum/component/storage/concrete/fish_case/can_be_inserted(obj/item/I, stop_messages, mob/M)
+ /// Activate deferred components if any.
+ SEND_SIGNAL(I, COMSIG_AQUARIUM_BEFORE_INSERT_CHECK)
+ . = ..()
diff --git a/code/datums/components/storage/concrete/pockets.dm b/code/datums/components/storage/concrete/pockets.dm
index bba34c95d644a..384827b5939ab 100644
--- a/code/datums/components/storage/concrete/pockets.dm
+++ b/code/datums/components/storage/concrete/pockets.dm
@@ -98,11 +98,13 @@
/obj/item/gun/ballistic/revolver/detective,
/obj/item/ammo_box/c38))
-/datum/component/storage/concrete/pockets/small/helmet
- max_items = 1
+/datum/component/storage/concrete/pockets/helmet
quickdraw = TRUE
+ max_combined_w_class = 6
-/datum/component/storage/concrete/pockets/small/helmet/Initialize()
+/datum/component/storage/concrete/pockets/helmet/Initialize()
. = ..()
- can_hold = typecacheof(list(/obj/item/reagent_containers/glass/bottle,
- /obj/item/ammo_box/a762))
+ can_hold = typecacheof(list(/obj/item/reagent_containers/food/drinks/bottle/vodka,
+ /obj/item/reagent_containers/food/drinks/bottle/molotov,
+ /obj/item/reagent_containers/food/drinks/drinkingglass,
+ /obj/item/ammo_box/a762))
diff --git a/code/datums/components/storage/concrete/rped.dm b/code/datums/components/storage/concrete/rped.dm
index 455eb985f0904..b05d7601aa681 100644
--- a/code/datums/components/storage/concrete/rped.dm
+++ b/code/datums/components/storage/concrete/rped.dm
@@ -12,7 +12,7 @@
. = ..()
if(!I.get_part_rating())
if (!stop_messages)
- to_chat(M, "[parent] only accepts machine parts!")
+ M.balloon_alert(M, "Only machine parts fit")
return FALSE
/datum/component/storage/concrete/bluespace/rped
@@ -29,5 +29,5 @@
. = ..()
if(!I.get_part_rating())
if (!stop_messages)
- to_chat(M, "[parent] only accepts machine parts!")
+ M.balloon_alert(M, "Only machine parts fit")
return FALSE
diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm
index cf5128cda72fd..ce2654004f584 100644
--- a/code/datums/components/storage/storage.dm
+++ b/code/datums/components/storage/storage.dm
@@ -12,6 +12,9 @@
var/list/can_hold //if this is set, only things in this typecache will fit.
var/list/cant_hold //if this is set, anything in this typecache will not be able to fit.
+ var/list/exception_hold //if set, these items will be the exception to the max size of object that can fit.
+ /// If set can only contain stuff with this single trait present.
+ var/list/can_hold_trait
var/list/mob/is_using //lazy list of mobs looking at the contents of this storage.
@@ -36,8 +39,8 @@
var/display_numerical_stacking = FALSE //stack things of the same type and show as a single object with a number.
- var/obj/screen/storage/boxes //storage display object
- var/obj/screen/close/closer //close button object
+ var/atom/movable/screen/storage/boxes //storage display object
+ var/atom/movable/screen/close/closer //close button object
var/allow_big_nesting = FALSE //allow storage objects of the same or greater size.
@@ -144,6 +147,8 @@
return master? master.real_location() : null
/datum/component/storage/proc/canreach_react(datum/source, list/next)
+ SIGNAL_HANDLER
+
var/datum/component/storage/concrete/master = master()
if(!master)
return
@@ -154,24 +159,32 @@
next += slave.parent
/datum/component/storage/proc/on_move()
+ SIGNAL_HANDLER
+
var/atom/A = parent
for(var/mob/living/L in can_see_contents())
if(!L.CanReach(A))
hide_from(L)
/datum/component/storage/proc/attack_self(datum/source, mob/M)
+ SIGNAL_HANDLER
+
if(locked)
- to_chat(M, "[parent] seems to be locked!")
+ var/atom/host = parent
+ host.balloon_alert(M, "It's locked")
return FALSE
if((M.get_active_held_item() == parent) && allow_quick_empty)
- quick_empty(M)
+ INVOKE_ASYNC(src, .proc/quick_empty, M)
/datum/component/storage/proc/preattack_intercept(datum/source, obj/O, mob/M, params)
+ SIGNAL_HANDLER
+
if(!isitem(O) || !click_gather || SEND_SIGNAL(O, COMSIG_CONTAINS_STORAGE))
return FALSE
. = COMPONENT_NO_ATTACK
if(locked)
- to_chat(M, "[parent] seems to be locked!")
+ var/atom/host = parent
+ host.balloon_alert(M, "It's locked")
return FALSE
var/obj/item/I = O
if(collection_mode == COLLECT_ONE)
@@ -180,19 +193,25 @@
return
if(!isturf(I.loc))
return
- var/list/things = I.loc.contents.Copy()
+ INVOKE_ASYNC(src, .proc/async_preattack_intercept, I, M)
+
+///async functionality from preattack_intercept
+/datum/component/storage/proc/async_preattack_intercept(obj/item/attack_item, mob/pre_attack_mob)
+ var/list/things = attack_item.loc.contents.Copy()
if(collection_mode == COLLECT_SAME)
- things = typecache_filter_list(things, typecacheof(I.type))
+ for(var/A in things)
+ if(!istype(A, attack_item))
+ things -= A
var/len = length(things)
if(!len)
- to_chat(M, "You failed to pick up anything with [parent].")
+ to_chat(pre_attack_mob, "You failed to pick up anything with [parent]!")
return
- var/datum/progressbar/progress = new(M, len, I.loc)
+ var/datum/progressbar/progress = new(pre_attack_mob, len, attack_item.loc)
var/list/rejections = list()
- while(do_after(M, 10, TRUE, parent, FALSE, CALLBACK(src, .proc/handle_mass_pickup, things, I.loc, rejections, progress)))
+ while(do_after(pre_attack_mob, 1 SECONDS, parent, NONE, FALSE, CALLBACK(src, .proc/handle_mass_pickup, things, attack_item.loc, rejections, progress)))
stoplag(1)
qdel(progress)
- to_chat(M, "You put everything you could [insert_preposition] [parent].")
+ to_chat(pre_attack_mob, "You put everything you could [insert_preposition] [parent].")
/datum/component/storage/proc/handle_mass_item_insertion(list/things, datum/component/storage/src_object, mob/user, datum/progressbar/progress)
var/atom/source_real_location = src_object.real_location()
@@ -240,10 +259,12 @@
if(!M.canUseStorage() || !A.Adjacent(M) || M.incapacitated())
return
if(locked)
- to_chat(M, "[parent] seems to be locked!")
+ var/atom/host = parent
+ host.balloon_alert(M, "It's locked")
return FALSE
A.add_fingerprint(M)
- to_chat(M, "You start dumping out [parent].")
+ var/atom/host = parent
+ host.balloon_alert(M, "You start dumping out the contents")
var/turf/T = get_turf(A)
var/list/things = contents()
var/datum/progressbar/progress = new(M, length(things), T)
@@ -280,6 +301,8 @@
return TRUE
/datum/component/storage/proc/set_locked(datum/source, new_state)
+ SIGNAL_HANDLER
+
locked = new_state
if(locked)
close_all()
@@ -321,7 +344,7 @@
var/datum/numbered_display/ND = numerical_display_contents[type]
ND.sample_object.mouse_opacity = MOUSE_OPACITY_OPAQUE
ND.sample_object.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]"
- ND.sample_object.maptext = "[(ND.number > 1)? "[ND.number]" : ""]"
+ ND.sample_object.maptext = MAPTEXT("[(ND.number > 1)? "[ND.number]" : ""]")
ND.sample_object.layer = ABOVE_HUD_LAYER
ND.sample_object.plane = ABOVE_HUD_PLANE
cx++
@@ -362,32 +385,43 @@
M.client.screen |= boxes
M.client.screen |= closer
M.client.screen |= real_location.contents
- M.active_storage = src
+ M.set_active_storage(src)
LAZYOR(is_using, M)
+ RegisterSignal(M, COMSIG_PARENT_QDELETING, .proc/mob_deleted)
return TRUE
+/datum/component/storage/proc/mob_deleted(datum/source)
+ SIGNAL_HANDLER
+ hide_from(source)
+
/datum/component/storage/proc/hide_from(mob/M)
+ if(M.active_storage == src)
+ M.set_active_storage(null)
+ LAZYREMOVE(is_using, M)
+
+ UnregisterSignal(M, COMSIG_PARENT_QDELETING)
if(!M.client)
return TRUE
var/atom/real_location = real_location()
M.client.screen -= boxes
M.client.screen -= closer
M.client.screen -= real_location.contents
- if(M.active_storage == src)
- M.active_storage = null
- LAZYREMOVE(is_using, M)
return TRUE
/datum/component/storage/proc/close(mob/M)
hide_from(M)
/datum/component/storage/proc/close_all()
+ SIGNAL_HANDLER
+
. = FALSE
for(var/mob/M in can_see_contents())
close(M)
. = TRUE //returns TRUE if any mobs actually got a close(M) call
/datum/component/storage/proc/emp_act(datum/source, severity)
+ SIGNAL_HANDLER
+
if(emp_shielded)
return
var/datum/component/storage/concrete/master = master()
@@ -422,6 +456,8 @@
return master._removal_reset(thing)
/datum/component/storage/proc/_remove_and_refresh(datum/source, atom/movable/thing)
+ SIGNAL_HANDLER
+
_removal_reset(thing)
refresh_mob_views()
@@ -435,6 +471,8 @@
return master.remove_from_storage(AM, new_location)
/datum/component/storage/proc/refresh_mob_views()
+ SIGNAL_HANDLER
+
var/list/seeing = can_see_contents()
for(var/i in seeing)
show_to(i)
@@ -447,6 +485,7 @@
cansee |= M
else
LAZYREMOVE(is_using, M)
+ UnregisterSignal(M, COMSIG_PARENT_QDELETING)
return cansee
//Tries to dump content
@@ -455,7 +494,8 @@
var/atom/dump_destination = dest_object.get_dumping_location()
if(A.Adjacent(M) && dump_destination && M.Adjacent(dump_destination))
if(locked)
- to_chat(M, "[parent] seems to be locked!")
+ var/atom/host = parent
+ host.balloon_alert(M, "It's locked")
return FALSE
if(dump_destination.storage_contents_dump_act(src, M))
playsound(A, "rustle", 50, 1, -5)
@@ -464,6 +504,8 @@
//This proc is called when you want to place an item into the storage item.
/datum/component/storage/proc/attackby(datum/source, obj/item/I, mob/M, params)
+ SIGNAL_HANDLER
+
if(istype(I, /obj/item/hand_labeler))
var/obj/item/hand_labeler/labeler = I
if(labeler.mode)
@@ -493,12 +535,16 @@
//Abuses the fact that lists are just references, or something like that.
/datum/component/storage/proc/signal_return_inv(datum/source, list/interface, recursive = TRUE)
+ SIGNAL_HANDLER
+
if(!islist(interface))
return FALSE
interface |= return_inv(recursive)
return TRUE
/datum/component/storage/proc/mousedrop_onto(datum/source, atom/over_object, mob/M)
+ SIGNAL_HANDLER
+
set waitfor = FALSE
. = COMPONENT_NO_MOUSEDROP
var/atom/A = parent
@@ -517,14 +563,14 @@
// this must come before the screen objects only block, dunno why it wasn't before
if(over_object == M)
user_show_to_mob(M)
- if(!istype(over_object, /obj/screen))
- dump_content_at(over_object, M)
+ if(!istype(over_object, /atom/movable/screen))
+ INVOKE_ASYNC(src, .proc/dump_content_at, over_object, M)
return
if(A.loc != M)
return
playsound(A, "rustle", 50, 1, -5)
- if(istype(over_object, /obj/screen/inventory/hand))
- var/obj/screen/inventory/hand/H = over_object
+ if(istype(over_object, /atom/movable/screen/inventory/hand))
+ var/atom/movable/screen/inventory/hand/H = over_object
M.putItemFromInventoryInHandIfPossible(A, H.held_index)
return
A.add_fingerprint(M)
@@ -535,12 +581,15 @@
return FALSE
A.add_fingerprint(M)
if(locked && !force)
- to_chat(M, "[parent] seems to be locked!")
+ var/atom/host = parent
+ host.balloon_alert(M, "It's locked")
return FALSE
if(force || M.CanReach(parent, view_only = TRUE))
show_to(M)
/datum/component/storage/proc/mousedrop_receive(datum/source, atom/movable/O, mob/M)
+ SIGNAL_HANDLER
+
if(isitem(O))
var/obj/item/I = O
if(iscarbon(M) || isdrone(M))
@@ -552,7 +601,7 @@
//This proc return 1 if the item can be picked up and 0 if it can't.
//Set the stop_messages to stop it from printing messages
/datum/component/storage/proc/can_be_inserted(obj/item/I, stop_messages = FALSE, mob/M)
- if(!istype(I) || (I.item_flags & ABSTRACT))
+ if(!istype(I) || I.anchored || (I.item_flags & ABSTRACT))
return FALSE //Not an item
if(I == parent)
return FALSE //no paradoxes for you
@@ -565,42 +614,42 @@
if(locked)
if(M && !stop_messages)
host.add_fingerprint(M)
- to_chat(M, "[host] seems to be locked!")
+ host.balloon_alert(M, "It's locked")
return FALSE
if(real_location.contents.len >= max_items)
if(!stop_messages)
- to_chat(M, "[host] is full, make some space!")
+ host.balloon_alert(M, "[host] is full")
return FALSE //Storage item is full
if(length(can_hold))
if(!is_type_in_typecache(I, can_hold))
if(!stop_messages)
- to_chat(M, "[host] cannot hold [I]!")
+ host.balloon_alert(M, "It doesn't fit")
return FALSE
- if(is_type_in_typecache(I, cant_hold)) //Check for specific items which this container can't hold.
+ if(is_type_in_typecache(I, cant_hold) || HAS_TRAIT(I, TRAIT_NO_STORAGE_INSERT) || (can_hold_trait && !HAS_TRAIT(I, can_hold_trait))) //Items which this container can't hold.
if(!stop_messages)
- to_chat(M, "[host] cannot hold [I]!")
+ host.balloon_alert(M, "It doesn't fit")
return FALSE
if(I.w_class > max_w_class)
if(!stop_messages)
- to_chat(M, "[I] is too big for [host]!")
+ host.balloon_alert(M, "[I] is too big")
return FALSE
var/sum_w_class = I.w_class
for(var/obj/item/_I in real_location)
sum_w_class += _I.w_class //Adds up the combined w_classes which will be in the storage item if the item is added to it.
if(sum_w_class > max_combined_w_class)
if(!stop_messages)
- to_chat(M, "[I] won't fit in [host], make some space!")
+ host.balloon_alert(M, "[host] is full")
return FALSE
if(isitem(host))
var/obj/item/IP = host
var/datum/component/storage/STR_I = I.GetComponent(/datum/component/storage)
if((I.w_class >= IP.w_class) && STR_I && !allow_big_nesting)
if(!stop_messages)
- to_chat(M, "[IP] cannot hold [I] as it's a storage item of the same size!")
+ host.balloon_alert(M, "It's too big")
return FALSE //To prevent the stacking of same sized storage items.
if(HAS_TRAIT(I, TRAIT_NODROP)) //SHOULD be handled in unEquip, but better safe than sorry.
if(!stop_messages)
- to_chat(M, "\the [I] is stuck to your hand, you can't put it in \the [host]!")
+ host.balloon_alert(M, "[I] is stuck to your hand")
return FALSE
var/datum/component/storage/concrete/master = master()
if(!istype(master))
@@ -629,13 +678,13 @@
return
if(rustle_sound)
playsound(parent, "rustle", 50, 1, -5)
- for(var/mob/viewing in viewers(user, null))
+ for(var/mob/viewing as() in viewers(user))
if(M == viewing)
to_chat(usr, "You put [I] [insert_preposition]to [parent].")
else if(in_range(M, viewing)) //If someone is standing close enough, they can tell what it is...
- viewing.show_message("[M] puts [I] [insert_preposition]to [parent].", 1)
+ viewing.show_message("[M] puts [I] [insert_preposition]to [parent].", MSG_VISUAL)
else if(I && I.w_class >= 3) //Otherwise they can only see large or normal items from a distance...
- viewing.show_message("[M] puts [I] [insert_preposition]to [parent].", 1)
+ viewing.show_message("[M] puts [I] [insert_preposition]to [parent].", MSG_VISUAL)
/datum/component/storage/proc/update_icon()
if(isobj(parent))
@@ -643,31 +692,45 @@
O.update_icon()
/datum/component/storage/proc/signal_insertion_attempt(datum/source, obj/item/I, mob/M, silent = FALSE, force = FALSE)
+ SIGNAL_HANDLER
+
if((!force && !can_be_inserted(I, TRUE, M)) || (I == parent))
return FALSE
return handle_item_insertion(I, silent, M)
/datum/component/storage/proc/signal_can_insert(datum/source, obj/item/I, mob/M, silent = FALSE)
+ SIGNAL_HANDLER
+
return can_be_inserted(I, silent, M)
/datum/component/storage/proc/show_to_ghost(datum/source, mob/dead/observer/M)
+ SIGNAL_HANDLER
+
return user_show_to_mob(M, TRUE)
/datum/component/storage/proc/signal_show_attempt(datum/source, mob/showto, force = FALSE)
+ SIGNAL_HANDLER
+
return user_show_to_mob(showto, force)
/datum/component/storage/proc/on_check()
+ SIGNAL_HANDLER
+
return TRUE
/datum/component/storage/proc/check_locked()
+ SIGNAL_HANDLER
+
return locked
-/datum/component/storage/proc/signal_take_type(datum/source, type, atom/destination, amount = INFINITY, check_adjacent = FALSE, force = FALSE, mob/user, list/inserted)
+/datum/component/storage/proc/signal_take_type(datum/source, typecache, atom/destination, amount = INFINITY, check_adjacent = FALSE, force = FALSE, mob/user, list/inserted)
+ SIGNAL_HANDLER
+
if(!force)
if(check_adjacent)
if(!user || !user.CanReach(destination) || !user.CanReach(parent))
return FALSE
- var/list/taking = typecache_filter_list(contents(), typecacheof(type))
+ var/list/taking = typecache_filter_list(contents(), typecache)
if(taking.len > amount)
taking.len = amount
if(inserted) //duplicated code for performance, don't bother checking retval/checking for list every item.
@@ -684,15 +747,20 @@
return max(0, max_items - real_location.contents.len)
/datum/component/storage/proc/signal_fill_type(datum/source, type, amount = 20, force = FALSE)
+ SIGNAL_HANDLER
+
var/atom/real_location = real_location()
if(!force)
amount = min(remaining_space_items(), amount)
for(var/i in 1 to amount)
handle_item_insertion(new type(real_location), TRUE)
- CHECK_TICK
+ if(QDELETED(src))
+ return TRUE
return TRUE
/datum/component/storage/proc/on_attack_hand(datum/source, mob/user)
+ SIGNAL_HANDLER
+
var/atom/A = parent
if(!attack_hand_interact)
return
@@ -709,45 +777,57 @@
var/mob/living/carbon/human/H = user
if(H.l_store == A && !H.get_active_held_item()) //Prevents opening if it's in a pocket.
. = COMPONENT_NO_ATTACK_HAND
- H.put_in_hands(A)
+ INVOKE_ASYNC(H, /mob.proc/put_in_hands, A)
H.l_store = null
return
if(H.r_store == A && !H.get_active_held_item())
. = COMPONENT_NO_ATTACK_HAND
- H.put_in_hands(A)
+ INVOKE_ASYNC(H, /mob.proc/put_in_hands, A)
H.r_store = null
return
if(A.loc == user)
. = COMPONENT_NO_ATTACK_HAND
if(locked)
- to_chat(user, "[parent] seems to be locked!")
+ var/atom/host = parent
+ host.balloon_alert(user, "It's locked")
else
show_to(user)
/datum/component/storage/proc/signal_on_pickup(datum/source, mob/user)
+ SIGNAL_HANDLER
+
var/atom/A = parent
update_actions()
- for(var/mob/M in range(1, A))
+ for(var/mob/M as() in hearers(1, A))
if(M.active_storage == src)
close(M)
/datum/component/storage/proc/signal_take_obj(datum/source, atom/movable/AM, new_loc, force = FALSE)
+ SIGNAL_HANDLER
+
if(!(AM in real_location()))
return FALSE
return remove_from_storage(AM, new_loc)
/datum/component/storage/proc/signal_quick_empty(datum/source, atom/loctarget)
+ SIGNAL_HANDLER
+
return do_quick_empty(loctarget)
/datum/component/storage/proc/signal_hide_attempt(datum/source, mob/target)
+ SIGNAL_HANDLER
+
return hide_from(target)
/datum/component/storage/proc/on_alt_click(datum/source, mob/user)
+ SIGNAL_HANDLER
+
if(!isliving(user) || !user.CanReach(parent))
return
if(locked)
- to_chat(user, "[parent] seems to be locked!")
+ var/atom/host = parent
+ host.balloon_alert(user, "It's locked")
return
var/atom/A = parent
@@ -757,19 +837,29 @@
playsound(A, "rustle", 50, 1, -5)
return
- if(!user.incapacitated())
- var/obj/item/I = locate() in real_location()
- if(!I)
- return
- A.add_fingerprint(user)
- remove_from_storage(I, get_turf(user))
- if(!user.put_in_hands(I))
- to_chat(user, "You fumble for [I] and it falls on the floor.")
- return
- user.visible_message("[user] draws [I] from [parent]!", "You draw [I] from [parent].")
+ if(user.incapacitated())
return
+ var/obj/item/to_remove = locate() in real_location()
+ if(!to_remove)
+ return
+ INVOKE_ASYNC(src, .proc/attempt_put_in_hands, to_remove, user)
+
+///attempt to put an item from contents into the users hands
+/datum/component/storage/proc/attempt_put_in_hands(obj/item/to_remove, mob/user)
+ var/atom/parent_as_atom = parent
+
+ parent_as_atom.add_fingerprint(user)
+ remove_from_storage(to_remove, get_turf(user))
+ if(!user.put_in_hands(to_remove))
+ to_chat(user, "You fumble for [to_remove] and it falls on the floor.")
+ return
+ user.visible_message("[user] draws [to_remove] from [parent]!", "You draw [to_remove] from [parent].")
+ return
+
/datum/component/storage/proc/action_trigger(datum/signal_source, datum/action/source)
+ SIGNAL_HANDLER
+
gather_mode_switch(source.owner)
return COMPONENT_ACTION_BLOCK_TRIGGER
@@ -777,8 +867,8 @@
collection_mode = (collection_mode+1)%3
switch(collection_mode)
if(COLLECT_SAME)
- to_chat(user, "[parent] now picks up all items of a single type at once.")
+ user.balloon_alert(user, "[parent] now picks up all items of single type")
if(COLLECT_EVERYTHING)
- to_chat(user, "[parent] now picks up all items in a tile at once.")
+ user.balloon_alert(user, "[parent] now picks up all items")
if(COLLECT_ONE)
- to_chat(user, "[parent] now picks up one item at a time.")
+ user.balloon_alert(user, "[parent] now picks up single item")
diff --git a/code/datums/components/swarming.dm b/code/datums/components/swarming.dm
index 3818126c0eb77..1e25172f956ca 100644
--- a/code/datums/components/swarming.dm
+++ b/code/datums/components/swarming.dm
@@ -12,6 +12,8 @@
RegisterSignal(parent, COMSIG_MOVABLE_UNCROSSED, .proc/leave_swarm)
/datum/component/swarming/proc/join_swarm(datum/source, atom/movable/AM)
+ SIGNAL_HANDLER
+
var/datum/component/swarming/other_swarm = AM.GetComponent(/datum/component/swarming)
if(!other_swarm)
return
@@ -21,6 +23,8 @@
other_swarm.swarm_members |= src
/datum/component/swarming/proc/leave_swarm(datum/source, atom/movable/AM)
+ SIGNAL_HANDLER
+
var/datum/component/swarming/other_swarm = AM.GetComponent(/datum/component/swarming)
if(!other_swarm || !(other_swarm in swarm_members))
return
diff --git a/code/datums/components/team_monitor.dm b/code/datums/components/team_monitor.dm
new file mode 100644
index 0000000000000..d4213a83eb948
--- /dev/null
+++ b/code/datums/components/team_monitor.dm
@@ -0,0 +1,564 @@
+#define ALT_APPEARENCE_ID "team_monitor"
+
+//==================
+// Helpers
+//==================
+
+//A list that tracks everything that should be tracked by team monitors
+//Assoc list:
+// Key = Frequency
+// Value = Components
+GLOBAL_LIST_EMPTY(tracker_huds)
+GLOBAL_LIST_EMPTY(tracker_beacons)
+
+//Gets the first free team element, useful for creating new teams
+//Special key is for what kind of team frequency it should be
+//Everything that has a team monitor can be configured to change what frequency it tracks on
+//The special key can be used to make keys like synd5 or synd83 to prevent centcom tracking syndies.
+/proc/get_free_team_frequency(special_key = "")
+ var/sanity = 5
+ //5 attempts to find a free team element, should never get that far
+ while(sanity > 0)
+ sanity --
+ var/random_id = rand(1, 999)
+ var/key = "[random_id]"
+ if(!GLOB.tracker_beacons.Find("[special_key][key]"))
+ return key
+ //Return something anyways
+ var/random_id = rand(1, 999)
+ var/key = "[random_id]"
+ return key
+
+//Adds a new tracking hud
+/proc/add_tracker_hud(frequency_added, datum/component/component_added)
+ if(!frequency_added)
+ return
+ if(islist(GLOB.tracker_huds[frequency_added]))
+ GLOB.tracker_huds[frequency_added] |= component_added
+ else
+ GLOB.tracker_huds[frequency_added] = list(component_added)
+
+//Adds a new tracking beacon
+/proc/add_tracker_beacon(frequency_added, datum/component/component_added)
+ if(!frequency_added)
+ return
+ if(islist(GLOB.tracker_beacons[frequency_added]))
+ GLOB.tracker_beacons[frequency_added] |= component_added
+ else
+ GLOB.tracker_beacons[frequency_added] = list(component_added)
+
+/proc/get_all_beacons_on_frequency(frequency, base_frequency)
+ if(!frequency)
+ return list()
+ var/list/found_beacons = list()
+ if(islist(GLOB.tracker_beacons[frequency]))
+ found_beacons.Add(GLOB.tracker_beacons[frequency])
+ if(islist(GLOB.tracker_beacons["[base_frequency]-GLOB"]))
+ found_beacons.Add(GLOB.tracker_beacons["[base_frequency]-GLOB"])
+ return found_beacons
+
+/proc/get_all_watchers_on_frequency(frequency, team_key = "", global_freq = FALSE)
+ if(global_freq)
+ . = list()
+ for(var/tracker_freq in GLOB.tracker_huds)
+ for(var/datum/component/team_monitor/TM as() in GLOB.tracker_huds[tracker_freq])
+ if(TM.team_freq_key == team_key)
+ . += TM
+ else
+ return GLOB.tracker_huds[frequency]
+
+//==================
+// Component
+// - HUD COMPONENT
+// - HANDLES POINTING TOWARDS TRACKED BEACONS
+//==================
+
+//The component that handles tracking atoms
+/datum/component/team_monitor
+ //The frequency of the team signals we are trackings
+ var/team_freq_key = "debug" //Key <-- cannot be changed
+ var/team_frequency = "" //Final compiled: Consists of key then numbers between 1 and 999
+ //The atoms we are actually tracking
+ // Key = Beacon component
+ // Value = image
+ var/list/tracking = list()
+ //Who are we updating for
+ var/mob/updating = null
+ //Distance from center
+ //Probably in pixels or something idk
+ var/distance = 20
+ //Should we display the hud in the firstplace
+ var/hud_visible = TRUE
+ //The attached beacon: Ignore this one
+ var/datum/component/tracking_beacon/attached_beacon
+
+/datum/component/team_monitor/Initialize(frequency_key, frequency, _attached_beacon)
+ var/obj/item/clothing/item = parent
+ if(!istype(item))
+ return COMPONENT_INCOMPATIBLE
+
+ team_freq_key = frequency_key
+ if(frequency)
+ team_frequency = "[frequency_key][frequency]"
+ else
+ team_frequency = null
+
+ attached_beacon = _attached_beacon
+
+ RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/parent_equipped)
+ RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/parent_dequpped)
+
+ get_matching_beacons()
+ add_tracker_hud(team_frequency, src)
+
+/datum/component/team_monitor/Destroy(force, silent)
+ //Unregister signals
+ if(parent)
+ UnregisterSignal(parent, COMSIG_ITEM_EQUIPPED)
+ UnregisterSignal(parent, COMSIG_ITEM_DROPPED)
+
+ if(team_frequency)
+ GLOB.tracker_huds[team_frequency] -= src
+
+ //Stop processing
+ STOP_PROCESSING(SSprocessing, src)
+
+ //Remove the HUD from the equipped mob
+ if(updating)
+ hide_hud(updating)
+
+ //Dispose
+ if(attached_beacon)
+ if(attached_beacon.attached_monitor == src)
+ attached_beacon.attached_monitor = null
+ attached_beacon = null
+
+ . = ..()
+
+//Gets the active trackers for when the team_monitor component
+//is initialized while other trackers are already active.
+/datum/component/team_monitor/proc/get_matching_beacons()
+ for(var/datum/component/tracking_beacon/beacon as() in get_all_beacons_on_frequency(team_frequency, team_freq_key))
+ if(beacon != attached_beacon && (beacon.updating || beacon.always_update))
+ add_to_tracking_network(beacon)
+
+//===========
+// Handles the parent being moved and updates the direction of the arrows.
+//===========
+
+/datum/component/team_monitor/process()
+ update_all_directions()
+
+//When the parent is removed, we need to update our arrows
+//Also if we are visible update the arrows of anything tracking us
+/datum/component/team_monitor/proc/parent_moved()
+ SIGNAL_HANDLER
+
+ //Update our alt appearences
+ update_all_directions()
+
+//Updates the direction of the arrows for all atoms we are tracking
+/datum/component/team_monitor/proc/update_all_directions()
+ if(!updating)
+ return
+ for(var/datum/component/tracking_beacon/beacon as() in tracking)
+ update_atom_dir(beacon)
+
+//Update the arrow towards another atom
+/datum/component/team_monitor/proc/update_atom_dir(datum/component/tracking_beacon/beacon)
+ if(!updating || !updating.hud_used || !beacon)
+ return
+ var/atom/movable/screen/arrow/screen = tracking[beacon]
+ var/turf/target_turf = get_turf(beacon.parent)
+ var/turf/parent_turf = get_turf(parent)
+ if(target_turf.get_virtual_z_level() != parent_turf.get_virtual_z_level() || target_turf == parent_turf)
+ if(screen)
+ //Remove the screen
+ updating.hud_used.team_finder_arrows -= screen
+ qdel(screen)
+ tracking[beacon] = null
+ //Update their hud
+ updating.hud_used.show_hud(updating.hud_used.hud_version, updating)
+ return
+ if(!screen)
+ //Create the screen
+ screen = new
+ screen.alpha = 240
+ screen.color = beacon.colour
+ screen.hud = updating.hud_used
+ updating.hud_used.team_finder_arrows += screen
+ tracking[beacon] = screen
+ //Update their hud
+ updating.hud_used.show_hud(updating.hud_used.hud_version, updating)
+ var/matrix/rotationMatrix = matrix()
+ rotationMatrix.Scale(1.5)
+ rotationMatrix.Translate(0, -distance)
+ rotationMatrix.Turn(Get_Angle(target_turf, parent_turf))
+ animate(screen, transform = rotationMatrix, time = 2)
+
+//===========
+// Handles being equipped / dequipped
+//===========
+
+//The parent equipped an item with a team_monitor, check if its in the right slot and apply the hud
+//Also needs to enable other trackers pointers towards us
+/datum/component/team_monitor/proc/parent_equipped(datum/source, mob/equipper, slot)
+ SIGNAL_HANDLER
+
+ var/obj/item/clothing/item = parent
+ if(!istype(item))
+ return
+ if(item.slot_flags & slot) //Was equipped to a valid slot for this item?
+ show_hud(equipper)
+ else
+ hide_hud(equipper)
+
+//Disable our hud
+//Disable the pointers to us
+/datum/component/team_monitor/proc/parent_dequpped(datum/source, mob/user)
+ SIGNAL_HANDLER
+
+ hide_hud(user)
+
+//===========
+// Handles hiding / showing the hud when equipped
+//===========
+
+/datum/component/team_monitor/proc/show_hud(mob/target)
+ updating = target
+ //Our hud is disabled
+ if(!hud_visible)
+ return
+ //Start processing to update in weird situations
+ START_PROCESSING(SSprocessing, src)
+ //Register parent signal
+ RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/parent_moved)
+ //Mob doesnt have a hud, dont add hud arrows
+ if(!target.hud_used)
+ return
+ for(var/datum/component/tracking_beacon/key in tracking)
+ var/atom/movable/screen/arrow/arrow = new
+ arrow.alpha = 240
+ arrow.color = key.colour
+ arrow.hud = target.hud_used
+ target.hud_used.team_finder_arrows += arrow
+ tracking[key] = arrow
+ //Update their hud
+ target.hud_used.show_hud(target.hud_used.hud_version, target)
+ update_all_directions()
+
+/datum/component/team_monitor/proc/hide_hud(mob/target)
+ updating = null
+ //Stop processing
+ STOP_PROCESSING(SSprocessing, src)
+ //UnRegister parent signal
+ UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
+ //Remove our arrows
+ for(var/key in tracking)
+ var/atom/movable/screen/arrow = tracking[key]
+ if(!arrow)
+ continue
+ if(target.hud_used)
+ target.hud_used.team_finder_arrows -= arrow
+ qdel(arrow)
+ tracking[key] = null
+ //Update their hud
+ if(target.hud_used)
+ target.hud_used.show_hud(target.hud_used.hud_version, target)
+
+//===========
+// Handles user interaction
+// - Disabling hud transmission
+// - Disabling hud view
+// - Changing transmission frequency
+//===========
+
+/datum/component/team_monitor/proc/toggle_hud(new_hud_status, mob/user)
+ hud_visible = new_hud_status
+ if(hud_visible && !updating)
+ show_hud(user)
+ else if(!hud_visible)
+ hide_hud(user)
+
+/datum/component/team_monitor/proc/change_frequency(mob/user)
+ //Get new frequency
+ var/new_freq = input(user, "Enter a new frequency (1 - 999):", "Frequency Change", 1) as num|null
+ if(!new_freq)
+ to_chat(user, "Invalid frequency. Encrypted tracking HUD disabled.")
+ return
+ if(new_freq < 1 || new_freq > 999)
+ to_chat(user, "Frequency is out of range. Must be between 1 and 999.")
+ return
+ set_frequency(new_freq)
+ to_chat(user, "Tracking HUD now scanning on frequency [team_frequency].")
+ //Set frequency of the linked beacon
+ if(attached_beacon)
+ attached_beacon.set_frequency(new_freq)
+
+/datum/component/team_monitor/proc/set_frequency(new_frequency)
+ var/hud_on = hud_visible
+ var/mob/user = updating
+ //Remove tracking from old frequency
+ if(team_frequency)
+ if(updating)
+ toggle_hud(FALSE, updating)
+ //Remove from the global frequency
+ GLOB.tracker_huds[team_frequency] -= src
+ //Clear tracking
+ tracking.Cut()
+ team_frequency = "[team_freq_key][new_frequency]"
+ //Add tracking to new frequency
+ if(!team_frequency)
+ return
+ //Adds our tracking component to the global list of trackers
+ add_tracker_hud(team_frequency, src)
+ //Gets the other trackers on our frequency
+ get_matching_beacons()
+ //Show hud if needed
+ if(user)
+ toggle_hud(hud_on, user)
+
+//Adds a new atom to the tracking monitor, will create a hud element that tracks them
+//TODO: Add the screen if already equipped
+//Should be the only way atoms are added to the tracking list
+/datum/component/team_monitor/proc/add_to_tracking_network(datum/component/tracking_beacon/beacon)
+ if(beacon != attached_beacon)
+ if(updating?.hud_used)
+ var/atom/movable/screen/arrow/arrow = new
+ arrow.alpha = 240
+ arrow.color = beacon.colour
+ arrow.hud = updating.hud_used
+ updating.hud_used.team_finder_arrows += arrow
+ tracking[beacon] = arrow
+ //Update arrow direction
+ update_atom_dir(beacon)
+ //Update their hud
+ updating.hud_used.show_hud(updating.hud_used.hud_version, updating)
+ else
+ tracking[beacon] = null
+
+//==================
+// Component
+// - TRACKER COMPONENT
+// - HANDLES UPDATING TRACKERS WHEN MOVED
+//==================
+
+/datum/component/tracking_beacon
+ //The frequency of the team signals we are trackings
+ var/team_freq_key = "debug" //Key <-- cannot be changed
+ var/team_frequency = "" //Final compiled: Consists of key then numbers between 1 and 999
+ //Are we visible to other trackers?
+ var/visible = TRUE
+ //Our colour
+ var/colour = "#FFFFFF"
+ //Who are we updating for
+ var/mob/updating = null
+ //Do we have an attached monitor?
+ var/datum/component/team_monitor/attached_monitor
+ //Should we update when not equipped?
+ var/always_update = FALSE
+ //Global signal?
+ var/global_signal = FALSE
+
+/datum/component/tracking_beacon/Initialize(_frequency_key, _frequency, _attached_monitor, _visible = TRUE, _colour = "#ffffff", _global = FALSE, _always_update = FALSE)
+ . = ..()
+
+ //Set vars
+ colour = _colour
+ attached_monitor = _attached_monitor
+ always_update = _always_update
+ global_signal = _global
+
+ //Set the frequency we are transmitting on
+ team_freq_key = _frequency_key
+ if(_global)
+ team_frequency = "[_frequency_key]-GLOB"
+ else if(_frequency)
+ team_frequency = "[_frequency_key][_frequency]"
+ else
+ team_frequency = null
+
+ //Add ourselves to the tracking network
+ add_tracker_beacon(team_frequency, src)
+
+ //Register tracking signal
+ if(always_update)
+ RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/update_position)
+ else
+ //Reigster equipping signals
+ RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/parent_equipped)
+ RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/parent_dequpped)
+
+ //Set our visibility on the tracking network
+ toggle_visibility(_visible)
+
+/datum/component/tracking_beacon/Destroy(force, silent)
+ //Unregister signals
+ if(parent)
+ //Register tracking signal
+ if(always_update)
+ UnregisterSignal(parent, COMSIG_MOVABLE_MOVED)
+ else
+ UnregisterSignal(parent, COMSIG_ITEM_EQUIPPED)
+ UnregisterSignal(parent, COMSIG_ITEM_DROPPED)
+
+ //Unregister movement signal
+ if(updating)
+ UnregisterSignal(updating, COMSIG_MOVABLE_MOVED)
+
+ //Goodbye, it was a good life
+ remove_from_huds()
+
+ //Remove from the global network
+ if(team_frequency)
+ GLOB.tracker_beacons[team_frequency] -= src
+
+ if(attached_monitor?.attached_beacon == src)
+ attached_monitor.attached_beacon = null
+ attached_monitor = null
+
+ . = ..()
+
+//===========
+// Equip/Dequip transmission handling
+//===========
+
+//The parent equipped an item with a team_monitor, check if its in the right slot and apply the hud
+//Also needs to enable other trackers pointers towards us
+/datum/component/tracking_beacon/proc/parent_equipped(datum/source, mob/equipper, slot)
+ SIGNAL_HANDLER
+
+ var/obj/item/clothing/item = parent
+ if(!istype(item))
+ return
+ if(item.slot_flags & slot) //Was equipped to a valid slot for this item?
+ updating = equipper
+ toggle_visibility(TRUE)
+ RegisterSignal(updating, COMSIG_MOVABLE_MOVED, .proc/update_position)
+ else
+ toggle_visibility(FALSE)
+ if(updating)
+ UnregisterSignal(updating, COMSIG_MOVABLE_MOVED)
+ updating = null
+
+//Disable our hud
+//Disable the pointers to us
+/datum/component/tracking_beacon/proc/parent_dequpped(datum/source, mob/user)
+ SIGNAL_HANDLER
+
+ toggle_visibility(FALSE)
+ if(updating)
+ UnregisterSignal(updating, COMSIG_MOVABLE_MOVED)
+ updating = null
+
+//===========
+// Visibility Handling
+//===========
+
+//Toggle visibility
+//If visibility is disabled we will hide ourselves from others
+/datum/component/tracking_beacon/proc/toggle_visibility(new_vis)
+ visible = new_vis
+ //If we are updating toggle our visibility
+ if((updating || always_update) && visible)
+ add_to_huds()
+ else
+ remove_from_huds()
+
+//===========
+// Position Updating
+//===========
+
+/datum/component/tracking_beacon/proc/update_position()
+ SIGNAL_HANDLER
+
+ //Update everyone tracking us
+ if(!visible)
+ return
+ if(!team_frequency)
+ return
+ for(var/datum/component/team_monitor/TM as() in get_all_watchers_on_frequency(team_frequency, team_freq_key, global_signal))
+ if(TM != attached_monitor)
+ TM.update_atom_dir(src)
+
+//===========
+// Showing on huds
+//===========
+
+//Remove ourselves from other tracking components
+/datum/component/tracking_beacon/proc/remove_from_huds()
+ if(!team_frequency)
+ return
+ for(var/datum/component/team_monitor/team_monitor as() in get_all_watchers_on_frequency(team_frequency, team_freq_key, global_signal))
+ //Remove ourselves from the tracking list
+ var/atom/movable/screen/arrow = team_monitor.tracking[src]
+ team_monitor.tracking.Remove(src)
+ //Delete the arrow pointing to use
+ if(!arrow)
+ continue
+ if(team_monitor.updating?.hud_used)
+ team_monitor.updating.hud_used.team_finder_arrows -= arrow
+ //Update their hud
+ team_monitor.updating.hud_used.show_hud(team_monitor.updating.hud_used.hud_version, team_monitor.updating)
+ qdel(arrow)
+
+//Add ourselves to other tracking components
+/datum/component/tracking_beacon/proc/add_to_huds()
+ //If we are invisibile, dont bother
+ if(!visible)
+ return
+ //Find other trackers and add ourselves to their tracking network
+ if(!team_frequency)
+ return
+ for(var/datum/component/team_monitor/team_monitor as() in get_all_watchers_on_frequency(team_frequency, team_freq_key, global_signal))
+ if(team_monitor != attached_monitor)
+ team_monitor.add_to_tracking_network(src)
+
+//===========
+// Handles user interaction
+// - Disabling hud transmission
+// - Disabling hud view
+// - Changing transmission frequency
+//===========
+
+/datum/component/tracking_beacon/proc/change_frequency(mob/user)
+ //Get new frequency
+ var/new_freq = input(user, "Enter a new frequency (1 - 999):", "Frequency Change", 1) as num|null
+ if(!new_freq)
+ to_chat(user, "Invalid frequency. Encrypted tracking beacon disabled.")
+ return
+ if(new_freq < 1 || new_freq > 999)
+ to_chat(user, "Frequency is out of range. Must be between 1 and 999.")
+ return
+ set_frequency(new_freq)
+ to_chat(user, "Tracking HUD now transmitting on frequency [team_frequency].")
+ //Set frequency of the linked tracker
+ if(attached_monitor)
+ attached_monitor.set_frequency(new_freq)
+
+/datum/component/tracking_beacon/proc/set_frequency(new_frequency)
+ //Remove tracking from old frequency
+ if(team_frequency)
+ //Disable the beacon on other trackers
+ toggle_visibility(FALSE)
+ //Remove from the global frequency
+ GLOB.tracker_beacons[team_frequency] -= src
+ team_frequency = "[team_freq_key][new_frequency]"
+ //Add tracking to new frequency
+ if(!team_frequency)
+ return
+ //Adds our tracking component to the global list of trackers
+ add_tracker_beacon(team_frequency, src)
+ //Set our visibility on the tracking network
+ toggle_visibility(visible)
+
+//=======
+// Generic Arrow, No special effects
+//=======
+
+/atom/movable/screen/arrow
+ icon = 'icons/mob/hud.dmi'
+ icon_state = "hud_arrow"
+ screen_loc = ui_team_finder
+
+#undef ALT_APPEARENCE_ID
diff --git a/code/datums/components/tether.dm b/code/datums/components/tether.dm
index faa6182208afc..a458db2f25717 100644
--- a/code/datums/components/tether.dm
+++ b/code/datums/components/tether.dm
@@ -17,6 +17,8 @@
RegisterSignal(parent, list(COMSIG_MOVABLE_PRE_MOVE), .proc/checkTether)
/datum/component/tether/proc/checkTether(mob/mover, newloc)
+ SIGNAL_HANDLER
+
if (get_dist(mover,newloc) > max_dist)
to_chat(mover, "The [tether_name] runs out of slack and prevents you from moving!")
return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
diff --git a/code/datums/components/thermite.dm b/code/datums/components/thermite.dm
index f3d95bfd4cb65..e0f7bcea1509f 100644
--- a/code/datums/components/thermite.dm
+++ b/code/datums/components/thermite.dm
@@ -52,13 +52,13 @@
master.cut_overlay(overlay)
return ..()
-/datum/component/thermite/InheritComponent(datum/component/thermite/newC, i_am_original, list/arguments)
+/datum/component/thermite/InheritComponent(datum/component/thermite/newC, i_am_original, _amount)
if(!i_am_original)
return
if(newC)
amount += newC.amount
else
- amount += arguments[1]
+ amount += _amount
/datum/component/thermite/proc/thermite_melt(mob/user)
var/turf/master = parent
@@ -80,13 +80,19 @@
qdel(src)
/datum/component/thermite/proc/clean_react(datum/source, strength)
+ SIGNAL_HANDLER
+
//Thermite is just some loose powder, you could probably clean it with your hands. << todo?
qdel(src)
/datum/component/thermite/proc/flame_react(datum/source, exposed_temperature, exposed_volume)
+ SIGNAL_HANDLER
+
if(exposed_temperature > 1922) // This is roughly the real life requirement to ignite thermite
thermite_melt()
/datum/component/thermite/proc/attackby_react(datum/source, obj/item/thing, mob/user, params)
+ SIGNAL_HANDLER
+
if(thing.is_hot())
thermite_melt(user)
diff --git a/code/datums/components/twohanded.dm b/code/datums/components/twohanded.dm
new file mode 100644
index 0000000000000..717c8d5b2abcc
--- /dev/null
+++ b/code/datums/components/twohanded.dm
@@ -0,0 +1,392 @@
+/**
+ * Two Handed Component
+ *
+ * When applied to an item it will make it two handed
+ *
+ */
+/datum/component/two_handed
+ dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS // Only one of the component can exist on an item
+ var/wielded = FALSE /// Are we holding the two handed item properly
+ var/force_multiplier = 0 /// The multiplier applied to force when wielded, does not work with force_wielded, and force_unwielded
+ var/force_wielded = 0 /// The force of the item when wielded
+ var/force_unwielded = 0 /// The force of the item when unwielded
+ var/block_power_wielded = 0 /// The block power of the item when wielded
+ var/block_power_unwielded = 0 /// The block power of the item when unwielded
+ var/wieldsound = FALSE /// Play sound when wielded
+ var/unwieldsound = FALSE /// Play sound when unwielded
+ var/attacksound = FALSE /// Play sound on attack when wielded
+ var/require_twohands = FALSE /// Does it have to be held in both hands
+ var/icon_wielded = FALSE /// The icon that will be used when wielded
+ var/obj/item/offhand/offhand_item = null /// Reference to the offhand created for the item
+ var/sharpened_increase = 0 /// The amount of increase recived from sharpening the item
+ var/unwield_on_swap /// Allow swapping, unwield on swap
+ var/auto_wield /// If true wielding will be performed when picked up
+ var/ignore_attack_self /// If true will not unwield when attacking self.
+
+/**
+ * Two Handed component
+ *
+ * vars:
+ * * require_twohands (optional) Does the item need both hands to be carried
+ * * wieldsound (optional) The sound to play when wielded
+ * * unwieldsound (optional) The sound to play when unwielded
+ * * attacksound (optional) The sound to play when wielded and attacking
+ * * force_multiplier (optional) The force multiplier when wielded, do not use with force_wielded, and force_unwielded
+ * * force_wielded (optional) The force setting when the item is wielded, do not use with force_multiplier
+ * * force_unwielded (optional) The force setting when the item is unwielded, do not use with force_multiplier
+ * * icon_wielded (optional) The icon to be used when wielded
+ * * unwield_on_swap (optional) Allow swapping, unwield on swap
+ * * auto_wield (optional) If true wielding will be performed when picked up
+ */
+/datum/component/two_handed/Initialize(require_twohands=FALSE, wieldsound=FALSE, unwieldsound=FALSE, attacksound=FALSE, \
+ force_multiplier=0, force_wielded=0, force_unwielded=0, block_power_wielded=0, \
+ block_power_unwielded=0, icon_wielded=FALSE, \
+ unwield_on_swap = FALSE, auto_wield = FALSE, ignore_attack_self = FALSE)
+ if(!isitem(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ src.require_twohands = require_twohands
+ src.wieldsound = wieldsound
+ src.unwieldsound = unwieldsound
+ src.attacksound = attacksound
+ src.force_multiplier = force_multiplier
+ src.force_wielded = force_wielded
+ src.force_unwielded = force_unwielded
+ src.block_power_wielded = block_power_wielded
+ src.block_power_unwielded = block_power_unwielded
+ src.icon_wielded = icon_wielded
+ src.unwield_on_swap = unwield_on_swap
+ src.auto_wield = auto_wield
+ src.ignore_attack_self = ignore_attack_self
+
+ if(require_twohands)
+ ADD_TRAIT(parent, TRAIT_NEEDS_TWO_HANDS, ABSTRACT_ITEM_TRAIT)
+
+// Inherit the new values passed to the component
+#define ISWIELDED(O) (SEND_SIGNAL(O, COMSIG_ITEM_CHECK_WIELDED) & COMPONENT_IS_WIELDED)
+
+/datum/component/two_handed/InheritComponent(datum/component/two_handed/new_comp, original, require_twohands, wieldsound, unwieldsound, \
+ force_multiplier, force_wielded, force_unwielded, block_power_wielded, block_power_unwielded, icon_wielded, \
+ unwield_on_swap, auto_wield, ignore_attack_self)
+ if(!original)
+ return
+ if(require_twohands)
+ src.require_twohands = require_twohands
+ if(wieldsound)
+ src.wieldsound = wieldsound
+ if(unwieldsound)
+ src.unwieldsound = unwieldsound
+ if(attacksound)
+ src.attacksound = attacksound
+ if(force_multiplier)
+ src.force_multiplier = force_multiplier
+ if(force_wielded)
+ src.force_wielded = force_wielded
+ if(force_unwielded)
+ src.force_unwielded = force_unwielded
+ if(block_power_wielded)
+ src.block_power_wielded = block_power_wielded
+ if(block_power_unwielded)
+ src.block_power_unwielded = block_power_unwielded
+ if(icon_wielded)
+ src.icon_wielded = icon_wielded
+ if(unwield_on_swap)
+ src.unwield_on_swap = unwield_on_swap
+ if(auto_wield)
+ src.auto_wield = auto_wield
+ if(ignore_attack_self)
+ src.ignore_attack_self = ignore_attack_self
+
+// register signals withthe parent item
+/datum/component/two_handed/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/on_equip)
+ RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/on_drop)
+ RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/on_attack_self)
+ RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/on_attack)
+ RegisterSignal(parent, COMSIG_ATOM_UPDATE_ICON, .proc/on_update_icon)
+ RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/on_moved)
+ RegisterSignal(parent, COMSIG_ITEM_SHARPEN_ACT, .proc/on_sharpen)
+ RegisterSignal(parent, COMSIG_ITEM_CHECK_WIELDED, .proc/get_wielded)
+
+// Remove all siginals registered to the parent item
+/datum/component/two_handed/UnregisterFromParent()
+ UnregisterSignal(parent, list(COMSIG_ITEM_EQUIPPED,
+ COMSIG_ITEM_DROPPED,
+ COMSIG_ITEM_ATTACK_SELF,
+ COMSIG_ITEM_ATTACK,
+ COMSIG_ATOM_UPDATE_ICON,
+ COMSIG_MOVABLE_MOVED,
+ COMSIG_ITEM_SHARPEN_ACT,
+ COMSIG_ITEM_CHECK_WIELDED,))
+
+/// Triggered on equip of the item containing the component
+/datum/component/two_handed/proc/on_equip(datum/source, mob/user, slot)
+ SIGNAL_HANDLER
+
+ if(auto_wield)
+ if(slot == ITEM_SLOT_HANDS)
+ RegisterSignal(user, COMSIG_MOB_SWAP_HANDS, .proc/on_swap_hands)
+ else
+ UnregisterSignal(user, COMSIG_MOB_SWAP_HANDS)
+ if((auto_wield || require_twohands) && slot == ITEM_SLOT_HANDS) // force equip the item
+ wield(user)
+ if(!user.is_holding(parent) && wielded && !require_twohands)
+ unwield(user)
+
+/// Triggered on drop of item containing the component
+/datum/component/two_handed/proc/on_drop(datum/source, mob/user)
+ SIGNAL_HANDLER
+
+ if(auto_wield)
+ UnregisterSignal(user, COMSIG_MOB_SWAP_HANDS)
+ if(require_twohands)
+ unwield(user, show_message=TRUE)
+ if(wielded)
+ unwield(user)
+ if(source == offhand_item && !QDELETED(src))
+ qdel(src)
+
+/// Triggered on attack self of the item containing the component
+/datum/component/two_handed/proc/on_attack_self(datum/source, mob/user)
+ SIGNAL_HANDLER
+
+ if(ignore_attack_self)
+ return
+
+ if(wielded)
+ unwield(user)
+ else
+ wield(user)
+
+/**
+ * Wield the two handed item in both hands
+ *
+ * vars:
+ * * user The mob/living/carbon that is wielding the item
+ */
+/datum/component/two_handed/proc/wield(mob/living/carbon/user, swap_hands = FALSE)
+ if(wielded)
+ return
+ if(ismonkey(user))
+ to_chat(user, "It's too heavy for you to wield fully.")
+ return
+ if(swap_hands ? user.get_active_held_item() : user.get_inactive_held_item())
+ if(require_twohands)
+ to_chat(user, "[parent] is too cumbersome to carry in one hand!")
+ user.dropItemToGround(parent, force=TRUE)
+ else
+ to_chat(user, "You need your other hand to be empty!")
+ return
+ if(user.get_num_arms() < 2)
+ if(require_twohands)
+ user.dropItemToGround(parent, force=TRUE)
+ to_chat(user, "You don't have enough intact hands.")
+ return
+
+ // wield update status
+ if(SEND_SIGNAL(parent, COMSIG_TWOHANDED_WIELD, user) & COMPONENT_TWOHANDED_BLOCK_WIELD)
+ return // blocked wield from item
+ wielded = TRUE
+ if(!auto_wield)
+ RegisterSignal(user, COMSIG_MOB_SWAP_HANDS, .proc/on_swap_hands)
+
+ // update item stats and name
+ var/obj/item/parent_item = parent
+ if(force_multiplier)
+ parent_item.force *= force_multiplier
+ else if(force_wielded)
+ parent_item.force = force_wielded
+ if(block_power_wielded)
+ parent_item.block_power = block_power_wielded
+ if(sharpened_increase)
+ parent_item.force += sharpened_increase
+ parent_item.name = "[parent_item.name] (Wielded)"
+ parent_item.update_icon()
+
+ if(iscyborg(user))
+ to_chat(user, "You dedicate your module to [parent].")
+ else
+ to_chat(user, "You grab [parent] with both hands.")
+
+ // Play sound if one is set
+ if(wieldsound)
+ playsound(parent_item.loc, wieldsound, 50, TRUE)
+
+ // Let's reserve the other hand
+ offhand_item = new(user)
+ offhand_item.name = "[parent_item.name] - offhand"
+ offhand_item.desc = "Your second grip on [parent_item]."
+ offhand_item.wielded = TRUE
+ RegisterSignal(offhand_item, COMSIG_ITEM_DROPPED, .proc/on_drop)
+ if(swap_hands)
+ user.put_in_active_hand(offhand_item)
+ else
+ user.put_in_inactive_hand(offhand_item)
+
+/**
+ * Unwield the two handed item
+ *
+ * vars:
+ * * user The mob/living/carbon that is unwielding the item
+ * * show_message (option) show a message to chat on unwield
+ */
+/datum/component/two_handed/proc/unwield(mob/living/carbon/user, show_message=TRUE)
+ if(!wielded || !user)
+ return
+
+ // wield update status
+ wielded = FALSE
+ if(!auto_wield)
+ UnregisterSignal(user, COMSIG_MOB_SWAP_HANDS)
+ SEND_SIGNAL(parent, COMSIG_TWOHANDED_UNWIELD, user)
+
+ // update item stats
+ var/obj/item/parent_item = parent
+ if(sharpened_increase)
+ parent_item.force -= sharpened_increase
+ if(force_multiplier)
+ parent_item.force /= force_multiplier
+ else if(!isnull(force_unwielded))
+ parent_item.force = force_unwielded
+ if(!isnull(block_power_unwielded))
+ parent_item.block_power = block_power_unwielded
+
+ // update the items name to remove the wielded status
+ var/sf = findtext(parent_item.name, " (Wielded)", -10) // 10 == length(" (Wielded)")
+ if(sf)
+ parent_item.name = copytext(parent_item.name, 1, sf)
+ else
+ parent_item.name = "[initial(parent_item.name)]"
+
+ // Update icons
+ parent_item.update_icon()
+ if(user.get_item_by_slot(ITEM_SLOT_BACK) == parent)
+ user.update_inv_back()
+ else
+ user.update_inv_hands()
+
+ // if the item requires two handed drop the item on unwield
+ if(require_twohands)
+ user.dropItemToGround(parent, force=TRUE)
+
+ // Show message if requested
+ if(show_message)
+ if(iscyborg(user))
+ to_chat(user, "You free up your module.")
+ else if(require_twohands)
+ to_chat(user, "You drop [parent].")
+ else
+ to_chat(user, "You are now carrying [parent] with one hand.")
+
+ // Play sound if set
+ if(unwieldsound)
+ playsound(parent_item.loc, unwieldsound, 50, TRUE)
+
+ // Remove the object in the offhand
+ if(offhand_item)
+ UnregisterSignal(offhand_item, COMSIG_ITEM_DROPPED)
+ qdel(offhand_item)
+ // Clear any old refrence to an item that should be gone now
+ offhand_item = null
+
+/**
+ * on_attack triggers on attack with the parent item
+ */
+/datum/component/two_handed/proc/on_attack(obj/item/source, mob/living/target, mob/living/user)
+ SIGNAL_HANDLER
+
+ if(wielded && attacksound)
+ var/obj/item/parent_item = parent
+ playsound(parent_item.loc, attacksound, 50, TRUE)
+
+/**
+ * on_update_icon triggers on call to update parent items icon
+ *
+ * Updates the icon using icon_wielded if set
+ */
+/datum/component/two_handed/proc/on_update_icon(datum/source)
+ SIGNAL_HANDLER
+
+ if(icon_wielded && wielded)
+ var/obj/item/parent_item = parent
+ if(parent_item)
+ parent_item.icon_state = icon_wielded
+ return COMSIG_ATOM_NO_UPDATE_ICON_STATE
+
+/**
+ * on_moved Triggers on item moved
+ */
+/datum/component/two_handed/proc/on_moved(datum/source, mob/user, dir)
+ SIGNAL_HANDLER
+
+ unwield(user)
+
+/**
+ * on_swap_hands Triggers on swapping hands, blocks swap if the other hand is busy
+ */
+/datum/component/two_handed/proc/on_swap_hands(mob/user, obj/item/held_item)
+ SIGNAL_HANDLER
+
+ if(!held_item)
+ //We are swapping to our two handed object.
+ if(auto_wield)
+ wield(user, TRUE)
+ return
+ if(held_item == parent)
+ if(unwield_on_swap)
+ unwield(user, FALSE)
+ else
+ return COMPONENT_BLOCK_SWAP
+
+/**
+ * on_sharpen Triggers on usage of a sharpening stone on the item
+ */
+/datum/component/two_handed/proc/on_sharpen(obj/item/item, amount, max_amount)
+ SIGNAL_HANDLER
+
+ if(!item)
+ return COMPONENT_BLOCK_SHARPEN_BLOCKED
+ if(sharpened_increase)
+ return COMPONENT_BLOCK_SHARPEN_ALREADY
+ var/wielded_val = 0
+ if(force_multiplier)
+ var/obj/item/parent_item = parent
+ if(wielded)
+ wielded_val = parent_item.force
+ else
+ wielded_val = parent_item.force * force_multiplier
+ else
+ wielded_val = force_wielded
+ if(wielded_val > max_amount)
+ return COMPONENT_BLOCK_SHARPEN_MAXED
+ sharpened_increase = min(amount, (max_amount - wielded_val))
+ return COMPONENT_BLOCK_SHARPEN_APPLIED
+
+/datum/component/two_handed/proc/get_wielded(obj/item/source)
+ SIGNAL_HANDLER
+
+ if(wielded)
+ return COMPONENT_IS_WIELDED
+ else
+ return 0
+
+/**
+ * The offhand dummy item for two handed items
+ *
+ */
+/obj/item/offhand
+ name = "offhand"
+ icon_state = "offhand"
+ w_class = WEIGHT_CLASS_HUGE
+ item_flags = ABSTRACT
+ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ var/wielded = FALSE // Off Hand tracking of wielded status
+
+/obj/item/offhand/Destroy()
+ wielded = FALSE
+ return ..()
+
+/obj/item/offhand/equipped(mob/user, slot)
+ . = ..()
+ if(wielded && !user.is_holding(src) && !QDELETED(src))
+ qdel(src)
diff --git a/code/datums/components/uplink.dm b/code/datums/components/uplink.dm
index 2688898e27044..dab99e3d0f1fd 100644
--- a/code/datums/components/uplink.dm
+++ b/code/datums/components/uplink.dm
@@ -94,6 +94,8 @@
uplink_items = get_uplink_items(gamemode, TRUE, allow_restricted)
/datum/component/uplink/proc/OnAttackBy(datum/source, obj/item/I, mob/user)
+ SIGNAL_HANDLER
+
if(!active)
return //no hitting everyone/everything just to try to slot tcs in!
if(istype(I, /obj/item/stack/telecrystal))
@@ -103,7 +105,16 @@
var/datum/uplink_item/UI = uplink_items[category][item]
var/path = UI.refund_path || UI.item
var/cost = UI.refund_amount || UI.cost
- if(I.type == path && UI.refundable && I.check_uplink_validity())
+ //Check that the uplink items path is right
+ //Check that the uplink item is refundable
+ //Check that the uplink is valid
+ //Check that the uplink has purchased this item (Sales can be refunded as the path relates to the old one)
+ var/hash = purchase_log.hash_purchase(UI, UI.cost)
+ var/datum/uplink_purchase_entry/UPE = purchase_log.purchase_log[hash]
+ if(I.type == path && UI.refundable && I.check_uplink_validity() && UPE?.amount_purchased > 0 && UPE.allow_refund)
+ UPE.amount_purchased --
+ if(!UPE.amount_purchased)
+ purchase_log.purchase_log.Remove(hash)
telecrystals += cost
purchase_log.total_spent -= cost
to_chat(user, "[I] refunded.")
@@ -111,13 +122,15 @@
return
/datum/component/uplink/proc/interact(datum/source, mob/user)
+ SIGNAL_HANDLER
+
if(locked)
return
if(!non_traitor_allowed && !user.mind.special_role)
return
active = TRUE
if(user)
- ui_interact(user)
+ INVOKE_ASYNC(src, .proc/ui_interact, user)
// an unlocked uplink blocks also opening the PDA or headset menu
return COMPONENT_NO_INTERACT
@@ -227,25 +240,35 @@
// Implant signal responses
/datum/component/uplink/proc/implant_activation()
+ SIGNAL_HANDLER
+
var/obj/item/implant/implant = parent
locked = FALSE
interact(null, implant.imp_in)
/datum/component/uplink/proc/implanting(datum/source, list/arguments)
+ SIGNAL_HANDLER
+
var/mob/user = arguments[2]
owner = "[user.key]"
/datum/component/uplink/proc/old_implant(datum/source, list/arguments, obj/item/implant/new_implant)
+ SIGNAL_HANDLER
+
// It kinda has to be weird like this until implants are components
return SEND_SIGNAL(new_implant, COMSIG_IMPLANT_EXISTING_UPLINK, src)
/datum/component/uplink/proc/new_implant(datum/source, datum/component/uplink/uplink)
+ SIGNAL_HANDLER
+
uplink.telecrystals += telecrystals
return COMPONENT_DELETE_NEW_IMPLANT
// PDA signal responses
/datum/component/uplink/proc/new_ringtone(datum/source, mob/living/user, new_ring_text)
+ SIGNAL_HANDLER
+
var/obj/item/pda/master = parent
if(trim(lowertext(new_ring_text)) != trim(lowertext(unlock_code)))
if(failsafe_code && trim(lowertext(new_ring_text)) == trim(lowertext(failsafe_code)))
@@ -262,6 +285,8 @@
// Radio signal responses
/datum/component/uplink/proc/new_frequency(datum/source, list/arguments)
+ SIGNAL_HANDLER
+
var/obj/item/radio/master = parent
var/frequency = arguments[1]
if(frequency != unlock_code)
@@ -275,6 +300,8 @@
// Pen signal responses
/datum/component/uplink/proc/pen_rotation(datum/source, degrees, mob/living/carbon/user)
+ SIGNAL_HANDLER
+
var/obj/item/pen/master = parent
previous_attempts += degrees
if(length(previous_attempts) > PEN_ROTATIONS)
diff --git a/code/datums/components/waddling.dm b/code/datums/components/waddling.dm
index 47ca60c8a19c6..f9a6b69eceffd 100644
--- a/code/datums/components/waddling.dm
+++ b/code/datums/components/waddling.dm
@@ -11,12 +11,16 @@
RegisterSignal(parent, list(COMSIG_MOVABLE_MOVED), .proc/Waddle)
/datum/component/waddling/proc/LivingWaddle()
+ SIGNAL_HANDLER
+
var/mob/living/L = parent
if(L.incapacitated() || !(L.mobility_flags & MOBILITY_STAND))
return
Waddle()
/datum/component/waddling/proc/Waddle()
+ SIGNAL_HANDLER
+
animate(parent, pixel_z = 4, time = 0)
animate(pixel_z = 0, transform = turn(matrix(), pick(-12, 0, 12)), time=2)
animate(pixel_z = 0, transform = matrix(), time = 0)
diff --git a/code/datums/components/wearertargeting.dm b/code/datums/components/wearertargeting.dm
index 4760757701fe7..cbfec78d11f2c 100644
--- a/code/datums/components/wearertargeting.dm
+++ b/code/datums/components/wearertargeting.dm
@@ -13,10 +13,14 @@
RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/on_drop)
/datum/component/wearertargeting/proc/on_equip(datum/source, mob/equipper, slot)
+ SIGNAL_HANDLER
+
if((slot in valid_slots) && istype(equipper, mobtype))
RegisterSignal(equipper, signals, proctype, TRUE)
else
UnregisterSignal(equipper, signals)
/datum/component/wearertargeting/proc/on_drop(datum/source, mob/user)
+ SIGNAL_HANDLER
+
UnregisterSignal(user, signals)
diff --git a/code/datums/components/wet_floor.dm b/code/datums/components/wet_floor.dm
index be7d8f1290f37..366598940148d 100644
--- a/code/datums/components/wet_floor.dm
+++ b/code/datums/components/wet_floor.dm
@@ -12,9 +12,9 @@
var/permanent = FALSE
var/last_process = 0
-/datum/component/wet_floor/InheritComponent(datum/newcomp, orig, argslist)
+/datum/component/wet_floor/InheritComponent(datum/newcomp, orig, strength, duration_minimum, duration_add, duration_maximum, _permanent)
if(!newcomp) //We are getting passed the arguments of a would-be new component, but not a new component
- add_wet(arglist(argslist))
+ add_wet(arglist(args.Copy(3)))
else //We are being passed in a full blown component
var/datum/component/wet_floor/WF = newcomp //Lets make an assumption
if(WF.gc()) //See if it's even valid, still. Also does LAZYLEN and stuff for us.
@@ -87,6 +87,9 @@
if(TURF_WET_PERMAFROST)
intensity = 120
lube_flags = SLIDE_ICE | GALOSHES_DONT_HELP
+ if(TURF_WET_SUPERLUBE)
+ intensity = 120
+ lube_flags = SLIDE | GALOSHES_DONT_HELP | SLIP_WHEN_CRAWLING
else
qdel(parent.GetComponent(/datum/component/slippery))
return
@@ -94,6 +97,8 @@
parent.LoadComponent(/datum/component/slippery, intensity, lube_flags, CALLBACK(src, .proc/AfterSlip))
/datum/component/wet_floor/proc/dry(datum/source, strength = TURF_WET_WATER, immediate = FALSE, duration_decrease = INFINITY)
+ SIGNAL_HANDLER
+
for(var/i in time_left_list)
if(text2num(i) <= strength)
time_left_list[i] = max(0, time_left_list[i] - duration_decrease)
@@ -134,6 +139,8 @@
highest_strength = max(highest_strength, text2num(i))
/datum/component/wet_floor/proc/is_wet()
+ SIGNAL_HANDLER
+
. = 0
for(var/i in time_left_list)
. |= text2num(i)
@@ -156,7 +163,7 @@
//NB it's possible we get deleted after this, due to inherit
/datum/component/wet_floor/proc/add_wet(type, duration_minimum = 0, duration_add = 0, duration_maximum = MAXIMUM_WET_TIME, _permanent = FALSE)
- var/static/list/allowed_types = list(TURF_WET_WATER, TURF_WET_LUBE, TURF_WET_ICE, TURF_WET_PERMAFROST)
+ var/static/list/allowed_types = list(TURF_WET_WATER, TURF_WET_LUBE, TURF_WET_ICE, TURF_WET_PERMAFROST, TURF_WET_SUPERLUBE)
if(duration_minimum <= 0 || !type)
return FALSE
if(type in allowed_types)
diff --git a/code/datums/dash_weapon.dm b/code/datums/dash_weapon.dm
index b0a4f41765821..8c8639d1383b1 100644
--- a/code/datums/dash_weapon.dm
+++ b/code/datums/dash_weapon.dm
@@ -6,7 +6,6 @@
var/current_charges = 1
var/max_charges = 1
var/charge_rate = 250
- var/mob/living/carbon/human/holder
var/obj/item/dashing_item
var/dash_sound = 'sound/magic/blink.ogg'
var/recharge_sound = 'sound/magic/charge.ogg'
@@ -17,7 +16,10 @@
/datum/action/innate/dash/Grant(mob/user, obj/dasher)
. = ..()
dashing_item = dasher
- holder = user
+
+/datum/action/innate/dash/Destroy()
+ dashing_item = null
+ return ..()
/datum/action/innate/dash/IsAvailable()
if(current_charges > 0)
@@ -26,25 +28,27 @@
return FALSE
/datum/action/innate/dash/Activate()
- dashing_item.attack_self(holder) //Used to toggle dash behavior in the dashing item
+ dashing_item.attack_self(owner) //Used to toggle dash behavior in the dashing item
/datum/action/innate/dash/proc/Teleport(mob/user, atom/target)
if(!IsAvailable())
return
var/turf/T = get_turf(target)
- if(target in view(user.client.view, user))
+ if(user in viewers(user.client.view, T))
var/obj/spot1 = new phaseout(get_turf(user), user.dir)
- user.forceMove(T)
- playsound(T, dash_sound, 25, 1)
- var/obj/spot2 = new phasein(get_turf(user), user.dir)
- spot1.Beam(spot2,beam_effect,time=20)
- current_charges--
- holder.update_action_buttons_icon()
- addtimer(CALLBACK(src, .proc/charge), charge_rate)
+ if(do_teleport(user, T, channel = TELEPORT_CHANNEL_FREE, no_effects = TRUE))
+ playsound(T, dash_sound, 25, 1)
+ var/obj/spot2 = new phasein(get_turf(user), user.dir)
+ spot1.Beam(spot2,beam_effect,time=20)
+ current_charges--
+ owner.update_action_buttons_icon()
+ addtimer(CALLBACK(src, .proc/charge), charge_rate)
+ else
+ to_chat(user, "You cannot dash here!")
/datum/action/innate/dash/proc/charge()
current_charges = CLAMP(current_charges + 1, 0, max_charges)
- holder.update_action_buttons_icon()
+ owner.update_action_buttons_icon()
if(recharge_sound)
playsound(dashing_item, recharge_sound, 50, 1)
- to_chat(holder, "[src] now has [current_charges]/[max_charges] charges.")
+ to_chat(owner, "[src] now has [current_charges]/[max_charges] charges.")
diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm
index 7445e6ffbf0cc..66a6b8b5f586a 100644
--- a/code/datums/datacore.dm
+++ b/code/datums/datacore.dm
@@ -1,4 +1,3 @@
-
/datum/datacore
var/medical[] = list()
var/medicalPrintCount = 0
@@ -75,38 +74,56 @@
D.adjust_money(amount)
return
-/datum/datacore/proc/addMinorCrime(id = "", datum/data/crime/crime)
+/**
+ * Adds crime to security record.
+ *
+ * Is used to add single crime to someone's security record.
+ * Arguments:
+ * * id - record id.
+ * * datum/data/crime/crime - premade array containing every variable, usually created by createCrimeEntry.
+ */
+/datum/datacore/proc/addCrime(id = "", datum/data/crime/crime)
for(var/datum/data/record/R in security)
if(R.fields["id"] == id)
- var/list/crimes = R.fields["mi_crim"]
+ var/list/crimes = R.fields["crim"]
crimes |= crime
return
-/datum/datacore/proc/removeMinorCrime(id, cDataId)
+/**
+ * Deletes crime from security record.
+ *
+ * Is used to delete single crime to someone's security record.
+ * Arguments:
+ * * id - record id.
+ * * cDataId - id of already existing crime.
+ */
+/datum/datacore/proc/removeCrime(id, cDataId)
for(var/datum/data/record/R in security)
if(R.fields["id"] == id)
- var/list/crimes = R.fields["mi_crim"]
+ var/list/crimes = R.fields["crim"]
for(var/datum/data/crime/crime in crimes)
if(crime.dataId == text2num(cDataId))
crimes -= crime
return
-/datum/datacore/proc/removeMajorCrime(id, cDataId)
+/**
+ * Adds details to a crime.
+ *
+ * Is used to add or replace details to already existing crime.
+ * Arguments:
+ * * id - record id.
+ * * cDataId - id of already existing crime.
+ * * details - data you want to add.
+ */
+/datum/datacore/proc/addCrimeDetails(id, cDataId, details)
for(var/datum/data/record/R in security)
if(R.fields["id"] == id)
- var/list/crimes = R.fields["ma_crim"]
+ var/list/crimes = R.fields["crim"]
for(var/datum/data/crime/crime in crimes)
if(crime.dataId == text2num(cDataId))
- crimes -= crime
+ crime.crimeDetails = details
return
-/datum/datacore/proc/addMajorCrime(id = "", datum/data/crime/crime)
- for(var/datum/data/record/R in security)
- if(R.fields["id"] == id)
- var/list/crimes = R.fields["ma_crim"]
- crimes |= crime
- return
-
/datum/datacore/proc/manifest()
for(var/i in GLOB.new_player_list)
var/mob/dead/new_player/N = i
@@ -130,7 +147,7 @@
"Medical" = GLOB.medical_positions,
"Science" = GLOB.science_positions,
"Supply" = GLOB.supply_positions,
- "Civilian" = GLOB.civilian_positions,
+ "Civilian" = GLOB.civilian_positions | GLOB.gimmick_positions,
"Silicon" = GLOB.nonhuman_positions
)
for(var/datum/data/record/t in GLOB.data_core.general)
@@ -147,7 +164,8 @@
"rank" = rank
))
has_department = TRUE
- break
+ if(department != "Command") //List heads in both command and their own department.
+ break
if(!has_department)
if(!manifest_out["Misc"])
manifest_out["Misc"] = list()
@@ -155,7 +173,12 @@
"name" = name,
"rank" = rank
))
- return manifest_out
+ //Sort the list by 'departments' primarily so command is on top.
+ var/list/sorted_out = list()
+ for(var/department in (departments += "Misc"))
+ if(!isnull(manifest_out[department]))
+ sorted_out[department] = manifest_out[department]
+ return sorted_out
/datum/datacore/proc/get_manifest_html(monochrome = FALSE)
var/list/manifest = get_manifest()
@@ -253,8 +276,7 @@
S.fields["name"] = H.real_name
S.fields["criminal"] = "None"
S.fields["citation"] = list()
- S.fields["mi_crim"] = list()
- S.fields["ma_crim"] = list()
+ S.fields["crim"] = list()
S.fields["notes"] = "No notes."
security += S
diff --git a/code/datums/datum.dm b/code/datums/datum.dm
index 2ce49061abc2e..20cc783694c57 100644
--- a/code/datums/datum.dm
+++ b/code/datums/datum.dm
@@ -35,6 +35,15 @@
/// A weak reference to another datum
var/datum/weakref/weak_reference
+ /*
+ * Lazy associative list of currently active cooldowns.
+ *
+ * cooldowns [ COOLDOWN_INDEX ] = add_timer()
+ * add_timer() returns the truthy value of -1 when not stoppable, and else a truthy numeric index
+ */
+ var/list/cooldowns
+
+
#ifdef TESTING
var/running_find_references
var/last_find_references = 0
@@ -44,6 +53,14 @@
var/list/cached_vars
#endif
+/**
+ * Topic signals
+ */
+
+/datum/Topic(href, href_list[])
+ ..()
+ SEND_SIGNAL(src, COMSIG_TOPIC, usr, href_list)
+
/**
* Default implementation of clean-up code.
*
@@ -197,3 +214,33 @@
qdel(D)
else
return returned
+/**
+ * Callback called by a timer to end an associative-list-indexed cooldown.
+ *
+ * Arguments:
+ * * source - datum storing the cooldown
+ * * index - string index storing the cooldown on the cooldowns associative list
+ *
+ * This sends a signal reporting the cooldown end.
+ */
+/proc/end_cooldown(datum/source, index)
+ if(QDELETED(source))
+ return
+ SEND_SIGNAL(source, COMSIG_CD_STOP(index))
+ TIMER_COOLDOWN_END(source, index)
+
+
+/**
+ * Proc used by stoppable timers to end a cooldown before the time has ran out.
+ *
+ * Arguments:
+ * * source - datum storing the cooldown
+ * * index - string index storing the cooldown on the cooldowns associative list
+ *
+ * This sends a signal reporting the cooldown end, passing the time left as an argument.
+ */
+/proc/reset_cooldown(datum/source, index)
+ if(QDELETED(source))
+ return
+ SEND_SIGNAL(source, COMSIG_CD_RESET(index), S_TIMER_COOLDOWN_TIMELEFT(source, index))
+ TIMER_COOLDOWN_END(source, index)
diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm
index 3bcd831b6c974..a91549ab4ce24 100644
--- a/code/datums/datumvars.dm
+++ b/code/datums/datumvars.dm
@@ -29,7 +29,11 @@
VV_DROPDOWN_OPTION(VV_HK_MARK, "Mark Object")
VV_DROPDOWN_OPTION(VV_HK_DELETE, "Delete")
VV_DROPDOWN_OPTION(VV_HK_EXPOSE, "Show VV To Player")
+ VV_DROPDOWN_OPTION(VV_HK_ADDCOMPONENT, "Add Component/Element")
VV_DROPDOWN_OPTION(VV_HK_MODIFY_TRAITS, "Modify Traits")
+ #ifdef REFERENCE_TRACKING
+ VV_DROPDOWN_OPTION(VV_HK_VIEW_REFERENCES, "View References")
+ #endif
//This proc is only called if everything topic-wise is verified. The only verifications that should happen here is things like permission checks!
//href_list is a reference, modifying it in these procs WILL change the rest of the proc in topic.dm of admin/view_variables!
@@ -37,10 +41,8 @@
/datum/proc/vv_do_topic(list/href_list)
if(!usr || !usr.client || !usr.client.holder || !check_rights(NONE))
return FALSE //This is VV, not to be called by anything else.
- /* This was ported from /TG/, modify traits isn't a thing here. Keeping in case it becomes a thing.
if(href_list[VV_HK_MODIFY_TRAITS])
usr.client.holder.modify_traits(src)
- */
return TRUE
/datum/proc/vv_get_header()
diff --git a/code/datums/diseases/_MobProcs.dm b/code/datums/diseases/_MobProcs.dm
index 00a8850dfb322..e164228994c00 100644
--- a/code/datums/diseases/_MobProcs.dm
+++ b/code/datums/diseases/_MobProcs.dm
@@ -79,8 +79,8 @@
if(isobj(H.wear_suit))
Cl = H.wear_suit
passed = prob((Cl.permeability_coefficient*100) - 1)
- if(passed && isobj(SLOT_W_UNIFORM))
- Cl = SLOT_W_UNIFORM
+ if(passed && isobj(ITEM_SLOT_ICLOTHING))
+ Cl = ITEM_SLOT_ICLOTHING
passed = prob((Cl.permeability_coefficient*100) - 1)
if(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)
if(isobj(H.wear_suit) && H.wear_suit.body_parts_covered&HANDS)
@@ -149,4 +149,4 @@
return !is_mouth_covered()
/mob/living/carbon/CanSpreadAirborneDisease()
- return !((head && (head.flags_cover & HEADCOVERSMOUTH) && (head.armor.getRating("bio") >= 25)) || (wear_mask && (wear_mask.flags_cover & MASKCOVERSMOUTH) && (wear_mask.armor.getRating("bio") >= 25)))
+ return !((head && (head.flags_cover & HEADCOVERSMOUTH) && (head.get_armor_rating("bio", src) >= 25)) || (wear_mask && (wear_mask.flags_cover & MASKCOVERSMOUTH) && (wear_mask.get_armor_rating("bio", src) >= 25)))
diff --git a/code/datums/diseases/_disease.dm b/code/datums/diseases/_disease.dm
index d8724491e6467..b85c3eaed100b 100644
--- a/code/datums/diseases/_disease.dm
+++ b/code/datums/diseases/_disease.dm
@@ -1,7 +1,7 @@
/datum/disease
//Flags
var/visibility_flags = 0
- var/disease_flags = CURABLE|CAN_CARRY|CAN_RESIST
+ var/disease_flags = CURABLE | CAN_CARRY | CAN_RESIST
var/spread_flags = DISEASE_SPREAD_AIRBORNE | DISEASE_SPREAD_CONTACT_FLUIDS | DISEASE_SPREAD_CONTACT_SKIN
//Fluff
@@ -26,7 +26,7 @@
var/carrier = FALSE //If our host is only a carrier
var/bypasses_immunity = FALSE //Does it skip species virus immunity check? Some things may diseases and not viruses
var/permeability_mod = 1
- var/severity = DISEASE_SEVERITY_NONTHREAT
+ var/danger = DISEASE_NONTHREAT
var/list/required_organs = list()
var/needs_all_cures = TRUE
var/list/strain_data = list() //dna_spread special bullshit
@@ -65,6 +65,10 @@
/datum/disease/proc/stage_act()
var/cure = has_cure()
+ var/mob/living/L = affected_mob
+ if(L.IsInStasis())
+ return
+
if(carrier && !cure)
return
@@ -113,7 +117,7 @@
var/turf/T = affected_mob.loc
if(istype(T))
- for(var/mob/living/carbon/C in oview(spread_range, affected_mob))
+ for(var/mob/living/carbon/C in ohearers(spread_range, affected_mob))
var/turf/V = get_turf(C)
if(disease_air_spread_walk(T, V))
C.AirborneContractDisease(src, force_spread)
@@ -146,7 +150,7 @@
//note that stage is not copied over - the copy starts over at stage 1
var/static/list/copy_vars = list("name", "visibility_flags", "disease_flags", "spread_flags", "form", "desc", "agent", "spread_text",
"cure_text", "max_stages", "stage_prob", "viable_mobtypes", "cures", "infectivity", "cure_chance",
- "bypasses_immunity", "permeability_mod", "severity", "required_organs", "needs_all_cures", "strain_data",
+ "bypasses_immunity", "permeability_mod", "danger", "required_organs", "needs_all_cures", "strain_data",
"infectable_biotypes", "process_dead")
var/datum/disease/D = copy_type ? new copy_type() : new type()
@@ -171,26 +175,26 @@
affected_mob = null
//Use this to compare severities
-/proc/get_disease_severity_value(severity)
- switch(severity)
- if(DISEASE_SEVERITY_BENEFICIAL)
+/proc/get_disease_danger_value(danger)
+ switch(danger)
+ if(DISEASE_BENEFICIAL)
return 1
- if(DISEASE_SEVERITY_POSITIVE)
+ if(DISEASE_POSITIVE)
return 2
- if(DISEASE_SEVERITY_NONTHREAT)
+ if(DISEASE_NONTHREAT)
return 3
- if(DISEASE_SEVERITY_MINOR)
+ if(DISEASE_MINOR)
return 4
- if(DISEASE_SEVERITY_MEDIUM)
+ if(DISEASE_MEDIUM)
return 5
- if(DISEASE_SEVERITY_HARMFUL)
+ if(DISEASE_HARMFUL)
return 6
- if(DISEASE_SEVERITY_DANGEROUS)
+ if(DISEASE_DANGEROUS)
return 7
- if(DISEASE_SEVERITY_BIOHAZARD)
+ if(DISEASE_BIOHAZARD)
return 8
- if(DISEASE_SEVERITY_PANDEMIC)
+ if(DISEASE_PANDEMIC)
return 9
/datum/disease/proc/speechModification(message)
- return message
\ No newline at end of file
+ return message
diff --git a/code/datums/diseases/advance/advance.dm b/code/datums/diseases/advance/advance.dm
index 6e2ff6468dfeb..313e2dc333b62 100644
--- a/code/datums/diseases/advance/advance.dm
+++ b/code/datums/diseases/advance/advance.dm
@@ -25,8 +25,12 @@
spread_text = "Unknown"
viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey, /mob/living/carbon/monkey/tumor)
- // NEW VARS
- var/list/properties = list()
+ var/resistance
+ var/stealth
+ var/stage_rate
+ var/transmission
+ var/severity
+ var/speed
var/list/symptoms = list() // The symptoms of the disease.
var/id = ""
var/processing = FALSE
@@ -35,8 +39,8 @@
var/sentient = FALSE //used to classify if a disease is sentient
var/faltered = FALSE //used if a disease has been made non-contagious
// The order goes from easy to cure to hard to cure.
- var/static/list/advance_cures = list(
- /datum/reagent/water, /datum/reagent/consumable/ethanol, /datum/reagent/consumable/sodiumchloride,
+ var/static/list/advance_cures = list(
+ /datum/reagent/water, /datum/reagent/consumable/ethanol, /datum/reagent/consumable/sodiumchloride,
/datum/reagent/medicine/spaceacillin, /datum/reagent/medicine/salglu_solution, /datum/reagent/medicine/mine_salve,
/datum/reagent/medicine/leporazine, /datum/reagent/concentrated_barbers_aid, /datum/reagent/toxin/lipolicide,
/datum/reagent/medicine/haloperidol, /datum/reagent/drug/krokodil
@@ -51,13 +55,15 @@
Refresh()
/datum/disease/advance/Destroy()
- SEND_SIGNAL(affected_mob, COMSIG_DISEASE_END, GetDiseaseID())
+ if(affected_mob)
+ SEND_SIGNAL(affected_mob, COMSIG_DISEASE_END, GetDiseaseID())
+ UnregisterSignal(affected_mob, COMSIG_MOB_DEATH)
if(processing)
for(var/datum/symptom/S in symptoms)
S.End(src)
return ..()
-/datum/disease/advance/try_infect(var/mob/living/infectee, make_copy = TRUE)
+/datum/disease/advance/try_infect(mob/living/infectee, make_copy = TRUE)
//see if we are more transmittable than enough diseases to replace them
//diseases replaced in this way do not confer immunity
var/list/advance_diseases = list()
@@ -75,13 +81,23 @@
sortTim(advance_diseases, /proc/cmp_advdisease_resistance_asc)
for(var/i in 1 to replace_num)
var/datum/disease/advance/competition = advance_diseases[i]
- if(totalTransmittable() > competition.totalResistance())
+ if(transmission > competition.resistance)
competition.cure(FALSE)
else
return FALSE //we are not strong enough to bully our way in
infect(infectee, make_copy)
return TRUE
+/datum/disease/advance/after_add()
+ if(affected_mob)
+ RegisterSignal(affected_mob, COMSIG_MOB_DEATH, .proc/on_mob_death)
+
+/datum/disease/advance/proc/on_mob_death()
+ SIGNAL_HANDLER
+
+ for(var/datum/symptom/S as() in symptoms)
+ S.OnDeath(src)
+
// Randomly pick a symptom to activate.
/datum/disease/advance/stage_act()
..()
@@ -106,13 +122,12 @@
// Compares type then ID.
/datum/disease/advance/IsSame(datum/disease/advance/D)
-
- if(!(istype(D, /datum/disease/advance)))
- return 0
+ if(!istype(D, /datum/disease/advance))
+ return FALSE
if(GetDiseaseID() != D.GetDiseaseID())
- return 0
- return 1
+ return FALSE
+ return TRUE
// Returns the advance disease with a different reference memory.
/datum/disease/advance/Copy()
@@ -120,7 +135,12 @@
QDEL_LIST(A.symptoms)
for(var/datum/symptom/S in symptoms)
A.symptoms += S.Copy()
- A.properties = properties.Copy()
+ A.resistance = resistance
+ A.stealth = stealth
+ A.stage_rate = stage_rate
+ A.transmission = transmission
+ A.severity = severity
+ A.speed = speed
A.id = id
A.mutable = mutable
A.faltered = faltered
@@ -132,7 +152,7 @@
var/list/name_symptoms = list()
for(var/datum/symptom/S in symptoms)
name_symptoms += S.name
- return "[name] sym:[english_list(name_symptoms)] r:[totalResistance()] s:[totalStealth()] ss:[totalStageSpeed()] t:[totalTransmittable()]"
+ return "[name] sym:[english_list(name_symptoms)] r:[resistance] s:[stealth] ss:[stage_rate] t:[transmission]"
/*
@@ -194,45 +214,40 @@
//Generate disease properties based on the effects. Returns an associated list.
/datum/disease/advance/proc/GenerateProperties()
- properties = list("resistance" = 0, "stealth" = 0, "stage_rate" = 0, "transmittable" = 0, "severity" = 0)
- for(var/datum/symptom/S in symptoms) //I can't change the order of the symptom list by severity, so i have to loop through symptoms three times, one for each tier of severity, to keep it consistent
- properties["resistance"] += S.resistance
- properties["stealth"] += S.stealth
- properties["stage_rate"] += S.stage_speed
- properties["transmittable"] += S.transmittable
- S.severityset(src)
- if(!S.neutered && S.severity >= 5) //big severity goes first. This means it can be reduced by beneficials, but won't increase from minor symptoms
- properties["severity"] += S.severity
- for(var/datum/symptom/S in symptoms)
+ resistance = 0
+ stealth = 0
+ stage_rate = 0
+ transmission = 0
+ severity = 0
+ //Why do we need 2 loops here?
+ //First loop just sets stats and second is purely just to set (and get) symptom severity
+ for(var/datum/symptom/S as() in symptoms)
+ resistance += S.resistance
+ stealth += S.stealth
+ stage_rate += S.stage_speed
+ transmission += S.transmission
+
+ for(var/datum/symptom/S as() in symptoms)
S.severityset(src)
- if(!S.neutered)
- switch(S.severity)//these go in the middle. They won't augment large severity diseases, but they can push low ones up to channel 2
- if(1 to 2)
- properties["severity"] = max(properties["severity"], min(3, (S.severity + properties["severity"])))
- if(3 to 4)
- properties["severity"] = max(properties["severity"], min(4, (S.severity + properties["severity"])))
- for(var/datum/symptom/S in symptoms) //benign and beneficial symptoms go last
- S.severityset(src)
- if(!S.neutered && S.severity <= 0)
- properties["severity"] += S.severity
+ if(S.neutered)
+ continue
+ severity += S.severity
// Assign the properties that are in the list.
/datum/disease/advance/proc/AssignProperties()
- if(properties && properties.len)
- if(properties["stealth"] >= 2)
- visibility_flags |= HIDDEN_SCANNER
- else
- visibility_flags &= ~HIDDEN_SCANNER
+ if(stealth >= 2)
+ visibility_flags |= HIDDEN_SCANNER
+ else
+ visibility_flags &= ~HIDDEN_SCANNER
- SetSpread(CLAMP(2 ** (properties["transmittable"] - symptoms.len), DISEASE_SPREAD_BLOOD, DISEASE_SPREAD_AIRBORNE))
+ SetSpread(CLAMP(2 ** (transmission - symptoms.len), DISEASE_SPREAD_BLOOD, DISEASE_SPREAD_AIRBORNE))
+
+ permeability_mod = max(CEILING(0.4 * transmission, 1), 1)
+ cure_chance = 15 - CLAMP(resistance, -5, 5) // can be between 10 and 20
+ stage_prob = max(stage_rate, 2)
+ SetDanger(severity)
+ GenerateCure()
- permeability_mod = max(CEILING(0.4 * properties["transmittable"], 1), 1)
- cure_chance = 15 - CLAMP(properties["resistance"], -5, 5) // can be between 10 and 20
- stage_prob = max(properties["stage_rate"], 2)
- SetSeverity(properties["severity"])
- GenerateCure(properties)
- else
- CRASH("Our properties were empty or null!")
// Assign the spread type and give it the correct description.
@@ -261,31 +276,31 @@
spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_AIRBORNE
spread_text = "Airborne"
-/datum/disease/advance/proc/SetSeverity(level_sev)
+/datum/disease/advance/proc/SetDanger(level_sev)
switch(level_sev)
if(-INFINITY to -2)
- severity = DISEASE_SEVERITY_BENEFICIAL
+ danger = DISEASE_BENEFICIAL
if(-1)
- severity = DISEASE_SEVERITY_POSITIVE
+ danger = DISEASE_POSITIVE
if(0)
- severity = DISEASE_SEVERITY_NONTHREAT
+ danger = DISEASE_NONTHREAT
if(1)
- severity = DISEASE_SEVERITY_MINOR
+ danger = DISEASE_MINOR
if(2)
- severity = DISEASE_SEVERITY_MEDIUM
+ danger = DISEASE_MEDIUM
if(3)
- severity = DISEASE_SEVERITY_HARMFUL
+ danger = DISEASE_HARMFUL
if(4)
- severity = DISEASE_SEVERITY_DANGEROUS
+ danger = DISEASE_DANGEROUS
if(5)
- severity = DISEASE_SEVERITY_BIOHAZARD
+ danger = DISEASE_BIOHAZARD
if(6 to INFINITY)
- severity = DISEASE_SEVERITY_PANDEMIC
+ danger = DISEASE_PANDEMIC
else
- severity = "Unknown"
+ danger = "Unknown"
/datum/disease/advance/proc/CheckChannel() //i hate that i have to use this to make this work
- switch(properties["severity"])
+ switch(severity)
if(-INFINITY to -2)
return 1
if(-1)
@@ -309,13 +324,12 @@
// Will generate a random cure, the less resistance the symptoms have, the harder the cure.
/datum/disease/advance/proc/GenerateCure()
- if(properties && properties.len)
- var/res = CLAMP(properties["resistance"] - (symptoms.len / 2), 1, advance_cures.len)
- cures = list(advance_cures[res])
+ var/res = CLAMP(resistance - (symptoms.len / 2), 1, advance_cures.len)
+ cures = list(advance_cures[res])
- // Get the cure name from the cure_id
- var/datum/reagent/D = GLOB.chemical_reagents_list[cures[1]]
- cure_text = D.name
+ // Get the cure name from the cure_id
+ var/datum/reagent/D = GLOB.chemical_reagents_list[cures[1]]
+ cure_text = D.name
// Randomly generate a symptom, has a chance to lose or gain a symptom.
/datum/disease/advance/proc/Evolve(min_level, max_level, ignore_mutable = FALSE)
@@ -405,7 +419,7 @@
*/
// Mix a list of advance diseases and return the mixed result.
-/proc/Advance_Mix(var/list/D_list)
+/proc/Advance_Mix(list/D_list)
var/list/diseases = list()
for(var/datum/disease/advance/A in D_list)
@@ -496,17 +510,97 @@
log_virus("[key_name(user)] has triggered a custom virus outbreak of [D.admin_details()]!")
-/datum/disease/advance/proc/totalStageSpeed()
- return properties["stage_rate"]
-
-/datum/disease/advance/proc/totalStealth()
- return properties["stealth"]
-/datum/disease/advance/proc/totalResistance()
- return properties["resistance"]
-/datum/disease/advance/proc/totalTransmittable()
- return properties["transmittable"]
-
-/datum/disease/advance/proc/totalSeverity()
- return properties["severity"]
+/datum/disease/advance/proc/random_disease_name(var/atom/diseasesource)//generates a name for a disease depending on its symptoms and where it comes from
+ var/list/prefixes = list("Spacer's ", "Space ", "Infectious ","Viral ", "The ", "[pick(GLOB.first_names)]'s ", "[pick(GLOB.last_names)]'s ", "Acute ")//prefixes that arent tacked to the body need spaces after the word
+ var/list/bodies = list(pick("[pick(GLOB.first_names)]", "[pick(GLOB.last_names)]"), "Space", "Disease", "Noun", "Cold", "Germ", "Virus")
+ var/list/suffixes = list("ism", "itis", "osis", "itosis", " #[rand(1,10000)]", "-[rand(1,100)]", "s", "y", " ovirus", " Bug", " Infection", " Disease", " Complex", " Syndrome", " Sickness") //suffixes that arent tacked directly on need spaces before the word
+ if(stealth >=2)
+ prefixes += "Crypto "
+ switch(max(resistance - (symptoms.len / 2), 1))
+ if(1)
+ suffixes += "-alpha"
+ if(2)
+ suffixes += "-beta"
+ if(3)
+ suffixes += "-gamma"
+ if(4)
+ suffixes += "-delta"
+ if(5)
+ suffixes += "-epsilon"
+ if(6)
+ suffixes += pick("-zeta", "-eta", "-theta", "-iota")
+ if(7)
+ suffixes += pick("-kappa", "-lambda")
+ if(8)
+ suffixes += pick("-mu", "-nu", "-xi", "-omicron")
+ if(9)
+ suffixes += pick("-pi", "-rho", "-sigma", "-tau")
+ if(10)
+ suffixes += pick("-upsilon", "-phi", "-chi", "-psi")
+ if(11 to INFINITY)
+ suffixes += "-omega"
+ prefixes += "Robust "
+ switch(transmission - symptoms.len)
+ if(-INFINITY to 2)
+ prefixes += "Bloodborne "
+ if(3)
+ prefixes += list("Mucous ", "Kissing ")
+ if(4)
+ prefixes += "Contact "
+ suffixes += " Flu"
+ if(5 to INFINITY)
+ prefixes += "Airborne "
+ suffixes += " Plague"
+ switch(severity)
+ if(-INFINITY to 0)
+ prefixes += "Altruistic "
+ if(1 to 2)
+ prefixes += "Benign "
+ if(3 to 4)
+ prefixes += "Malignant "
+ if(5)
+ prefixes += "Terminal "
+ bodies += "Death"
+ if(6 to INFINITY)
+ prefixes += "Deadly "
+ bodies += "Death"
+ if(diseasesource)
+ if(ishuman(diseasesource))
+ var/mob/living/carbon/human/H = diseasesource
+ prefixes += pick("[H.first_name()]'s", "[H.name]'s", "[H.job]'s", "[H.dna.species]'s")
+ bodies += pick("[H.first_name()]", "[H.job]", "[H.dna.species]")
+ if(islizard(H) || iscatperson(H))//add rat-origin prefixes to races that eat rats
+ prefixes += list("Vermin ", "Zoo", "Maintenance ")
+ bodies += list("Rat", "Maint")
+ else switch(diseasesource.type)
+ if(/mob/living/simple_animal/pet/hamster/vector)
+ prefixes += list("Vector's ", "Hamster ")
+ bodies += list("Freebie")
+ if(/obj/effect/decal/cleanable)
+ prefixes += list("Bloody ", "Maintenance ")
+ bodies += list("Maint")
+ if(/mob/living/simple_animal/mouse)
+ prefixes += list("Vermin ", "Zoo", "Maintenance ")
+ bodies += list("Rat", "Maint")
+ if(/obj/item/reagent_containers/syringe)
+ prefixes += list("Junkie ", "Maintenance ")
+ bodies += list("Needle", "Maint")
+ if(/obj/item/fugu_gland)
+ prefixes += "Wumbo"
+ if(/obj/item/organ/lungs)
+ prefixes += "Miasmic "
+ bodies += list("Stench", "Lung")
+ for(var/datum/symptom/Symptom as() in symptoms)
+ if(!Symptom.neutered)
+ prefixes += Symptom.prefixes
+ bodies += Symptom.bodies
+ suffixes += Symptom.suffixes
+ switch(rand(1, 3))
+ if(1)
+ return "[pick(prefixes)][pick(bodies)]"
+ if(2)
+ return "[pick(prefixes)][pick(bodies)][pick(suffixes)]"
+ if(3)
+ return "[pick(bodies)][pick(suffixes)]"
\ No newline at end of file
diff --git a/code/datums/diseases/advance/presets.dm b/code/datums/diseases/advance/presets.dm
index 6e97ac9e73035..0b03a3861b44e 100644
--- a/code/datums/diseases/advance/presets.dm
+++ b/code/datums/diseases/advance/presets.dm
@@ -23,7 +23,7 @@
var/randomname = TRUE
var/datum/symptom/setsymptom = null
-/datum/disease/advance/random/New(max_symptoms, max_level = 9, var/datum/symptom/specialsymptom = setsymptom)
+/datum/disease/advance/random/New(max_symptoms, max_level = 9, min_level = 1, var/datum/symptom/specialsymptom = setsymptom, var/atom/infected)
if(!max_symptoms)
max_symptoms = (2 + rand(1, (VIRUS_SYMPTOM_LIMIT-2)))
if(specialsymptom)
@@ -33,7 +33,7 @@
var/datum/symptom/S = symptom
if(S == specialsymptom)
continue
- if(initial(S.level) > max_level)
+ if(initial(S.level) > max_level || initial(S.level) < min_level)
continue
if(initial(S.level) <= 0) //unobtainable symptoms
continue
@@ -49,7 +49,10 @@
Finalize()
Refresh()
if(randomname)
- name = "Sample #[rand(1,10000)]"
+ var/randname = random_disease_name(infected)
+ AssignName(randname)
+ name = randname
+
/datum/disease/advance/random/macrophage
name = "Unknown Disease"
@@ -64,4 +67,4 @@
/datum/disease/advance/random/blob // had to do it this way due to an odd glitch
name = "Blob Spores"
setsymptom = /datum/symptom/blobspores
- randomname = FALSE
\ No newline at end of file
+ randomname = FALSE
diff --git a/code/datums/diseases/advance/symptoms/beard.dm b/code/datums/diseases/advance/symptoms/beard.dm
index 76ce20bbcffa6..359b8284875f4 100644
--- a/code/datums/diseases/advance/symptoms/beard.dm
+++ b/code/datums/diseases/advance/symptoms/beard.dm
@@ -21,11 +21,13 @@ BONUS
stealth = 1
resistance = 3
stage_speed = 3
- transmittable = 1
+ transmission = 1
level = 4
severity = 0
symptom_delay_min = 18
symptom_delay_max = 36
+ prefixes = list("Facial ")
+ bodies = list("Beard")
var/list/beard_order = list("Beard (Jensen)", "Beard (Full)", "Beard (Dwarf)", "Beard (Very Long)")
diff --git a/code/datums/diseases/advance/symptoms/beesymptom.dm b/code/datums/diseases/advance/symptoms/beesymptom.dm
index 028dcc56afc11..0714a5ef8bade 100644
--- a/code/datums/diseases/advance/symptoms/beesymptom.dm
+++ b/code/datums/diseases/advance/symptoms/beesymptom.dm
@@ -4,29 +4,31 @@
stealth = -2
resistance = 2
stage_speed = 1
- transmittable = 1
+ transmission = 1
level = 0
severity = 2
symptom_delay_min = 5
symptom_delay_max = 20
var/honey = FALSE
var/toxic_bees= FALSE
+ prefixes = list("Hive ")
+ bodies = list("Bees", "Hive")
threshold_desc = "Resistance 12: The bees become symbiotic with the host, synthesizing honey and no longer stinging the stomach lining, and no longer attacking the host. Bees will also contain honey, unless transmission exceeds 10. \
Transmission 10: Bees now contain a completely random toxin."
/datum/symptom/beesease/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["resistance"] >= 12)
- severity -= 4
- if(A.properties["transmittable"] >= 10)
+ if(A.transmission >= 10)
severity += 2
+ if(A.resistance >= 12)
+ severity -= 4
/datum/symptom/beesease/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["resistance"] >= 12)
+ if(A.resistance >= 12)
honey = TRUE
- if(A.properties["transmittable"] >= 10)
+ if(A.transmission >= 10)
toxic_bees = TRUE
/datum/symptom/beesease/Activate(datum/disease/advance/A)
@@ -74,6 +76,8 @@
else if(honey)
var/mob/living/simple_animal/hostile/poison/bees/newbee = new /mob/living/simple_animal/hostile/poison/bees(M.loc) //Heh, newbee
newbee.assign_reagent(GLOB.chemical_reagents_list[/datum/reagent/consumable/honey])
+ var/mob/living/simple_animal/hostile/poison/bees/newbee2 = new /mob/living/simple_animal/hostile/poison/bees(M.loc)
+ newbee2.assign_reagent(GLOB.chemical_reagents_list[/datum/reagent/medicine/insulin])
else
new /mob/living/simple_animal/hostile/poison/bees(M.loc)
diff --git a/code/datums/diseases/advance/symptoms/blobspores.dm b/code/datums/diseases/advance/symptoms/blobspores.dm
index 4da930412411e..fa15f8a94ebf3 100644
--- a/code/datums/diseases/advance/symptoms/blobspores.dm
+++ b/code/datums/diseases/advance/symptoms/blobspores.dm
@@ -4,9 +4,11 @@
stealth = 1
resistance = 6
stage_speed = -2
- transmittable = 1
+ transmission = 1
level = 9
severity = 3
+ prefixes = list("Xeno", "Sporing ")
+ bodies = list("Blob")
var/ready_to_pop
var/factory_blob
var/strong_blob
@@ -17,19 +19,19 @@
/datum/symptom/blobspores/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["resistance"] >= 14)
+ if(A.resistance >= 14)
severity += 1
/datum/symptom/blobspores/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["resistance"] >= 11)
+ if(A.resistance >= 11)
factory_blob = TRUE
- if(A.properties["resistance"] >= 8)
+ if(A.resistance >= 8)
strong_blob = TRUE
- if(A.properties["resistance"] >= 14)
- node_blob = TRUE
+ if(A.resistance >= 14)
+ node_blob = TRUE
/datum/symptom/blobspores/Activate(datum/disease/advance/A)
if(!..())
@@ -60,10 +62,14 @@
/datum/symptom/blobspores/OnDeath(datum/disease/advance/A)
+ if(neutered) //Stops this symptom from making people scared even if this is useless
+ return FALSE
var/mob/living/M = A.affected_mob
M.visible_message("[M] starts swelling grotesquely!")
- sleep(10 SECONDS)
- if(!A && !M)
+ addtimer(CALLBACK(src, .proc/blob_the_mob, A, M), 10 SECONDS)
+
+/datum/symptom/blobspores/proc/blob_the_mob(datum/disease/advance/A, mob/living/M)
+ if(!A || !M)
return
var/list/blob_options = list(/obj/structure/blob/normal)
if(factory_blob)
@@ -79,9 +85,9 @@
for(var/datum/disease/D in B.disease)//don't let them farm diseases with this and monkeys
B.disease -= D
B.disease += A//instead, they contain the disease that was in this
- if(prob(A.properties["resistance"]))
+ if(prob(A.resistance))
var/atom/blobbernaut = new /mob/living/simple_animal/hostile/blob/blobbernaut/(M.loc)
blobbernaut.add_atom_colour(pick(BLOB_STRAIN_COLOR_LIST), FIXED_COLOUR_PRIORITY)
var/atom/blob_tile = new pick_blob(M.loc)
- blob_tile.add_atom_colour(pick(BLOB_STRAIN_COLOR_LIST), FIXED_COLOUR_PRIORITY) //A random colour for the blob, as this blob isnt going to get a overmind colour
+ blob_tile.add_atom_colour(pick(BLOB_STRAIN_COLOR_LIST), FIXED_COLOUR_PRIORITY) //A random colour for the blob, as this blob isn't going to get a overmind colour
M.visible_message("A huge mass of blob and blob spores burst out of [M]!")
diff --git a/code/datums/diseases/advance/symptoms/braindamage.dm b/code/datums/diseases/advance/symptoms/braindamage.dm
index 82f15da1b2a9a..d21852382b78e 100644
--- a/code/datums/diseases/advance/symptoms/braindamage.dm
+++ b/code/datums/diseases/advance/symptoms/braindamage.dm
@@ -4,11 +4,14 @@
stealth = 1
resistance = -2
stage_speed = -3
- transmittable = -1
+ transmission = -1
level = 8
severity = 3
symptom_delay_min = 15
symptom_delay_max = 60
+ prefixes = list("Idiot's ")
+ bodies = list("Idiot")
+ suffixes = list(" Memory Loss")
var/lethal = FALSE
var/moretrauma = FALSE
threshold_desc = "transmission 12: The disease's damage reaches lethal levels. \
@@ -16,15 +19,15 @@
/datum/symptom/braindamage/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["transmittable"] >= 12)
+ if(A.transmission >= 12)
severity += 1
/datum/symptom/braindamage/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["transmittable"] >= 12)
+ if(A.transmission >= 12)
lethal = TRUE
- if(A.properties["stage_rate"] >= 9)
+ if(A.stage_rate >= 9)
moretrauma = TRUE
/datum/symptom/braindamage/Activate(datum/disease/advance/A)
@@ -53,5 +56,5 @@
/datum/symptom/braindamage/proc/givetrauma(datum/disease/advance/A, chance)
if(prob(chance))
if(ishuman(A.affected_mob))
- var/mob/living/carbon/human/M = A.affected_mob
+ var/mob/living/carbon/human/M = A.affected_mob
M?.gain_trauma(BRAIN_TRAUMA_MILD)
diff --git a/code/datums/diseases/advance/symptoms/choking.dm b/code/datums/diseases/advance/symptoms/choking.dm
index 61495b98f8d00..2fa821eb3e352 100644
--- a/code/datums/diseases/advance/symptoms/choking.dm
+++ b/code/datums/diseases/advance/symptoms/choking.dm
@@ -23,28 +23,29 @@ Bonus
stealth = -2
resistance = -0
stage_speed = -1
- transmittable = -2
+ transmission = -2
level = 9
severity = 5
base_message_chance = 15
symptom_delay_min = 14
symptom_delay_max = 30
+ bodies = list("Lung")
+ suffixes = list(" Tuberculosis")
var/paralysis = FALSE
threshold_desc = "Stage Speed 8: Additionally synthesizes pancuronium and sodium thiopental inside the host. \
Transmission 8: Doubles the damage caused by the symptom."
/datum/symptom/asphyxiation/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["transmittable"] >= 8)
+ if(A.transmission >= 8)
severity += 1
- return..()
/datum/symptom/asphyxiation/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["stage_rate"] >= 8)
+ if(A.stage_rate >= 8)
paralysis = TRUE
- if(A.properties["transmittable"] >= 8)
+ if(A.transmission >= 8)
power = 2
/datum/symptom/asphyxiation/Activate(datum/disease/advance/A)
diff --git a/code/datums/diseases/advance/symptoms/clockwork.dm b/code/datums/diseases/advance/symptoms/clockwork.dm
index 10e1f2a8dc28a..367c6b25f3b0a 100644
--- a/code/datums/diseases/advance/symptoms/clockwork.dm
+++ b/code/datums/diseases/advance/symptoms/clockwork.dm
@@ -4,38 +4,43 @@
stealth = 0
resistance = 1
stage_speed = 4 //while the reference material has low speed, this virus will take a good while to completely convert someone
- transmittable = -1
+ transmission = -1
level = 9
severity = 0
symptom_delay_min = 10
- symptom_delay_max = 60
+ symptom_delay_max = 30
+ prefixes = list("Ratvarian ", "Keter ", "Clockwork ", "Robo")
+ bodies = list("Robot")
+ suffixes = list("-217")
var/replaceorgans = FALSE
var/replacebody = FALSE
var/robustbits = FALSE
threshold_desc = "Stage Speed 4:The virus will replace the host's organic organs with mundane, biometallic versions. +1 severity. \
- Stage Speed 10:The virus will eventually convert the host's entire body to biometallic materials, and maintain its cellular integrity. +1 severity. \
- Stage Speed 13:Biometallic mass created by the virus will be superior to typical organic mass. -3 severity."
+ Resistance 4:The virus will eventually convert the host's entire body to biometallic materials, and maintain its cellular integrity. +1 severity. \
+ Stage Speed 12:Biometallic mass created by the virus will be superior to typical organic mass. -3 severity."
/datum/symptom/robotic_adaptation/OnAdd(datum/disease/advance/A)
A.infectable_biotypes |= MOB_ROBOTIC
/datum/symptom/robotic_adaptation/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["stage_rate"] >= 5) //at base level, robotic organs are purely a liability
+ if(A.stage_rate >= 4) //at base level, robotic organs are purely a liability
severity += 1
- if(A.properties["stage_rate"] >= 10)//at base level, robotic bodyparts have very few bonuses, mostly being a liability in the case of EMPS
+ if(A.stage_rate >= 12)//but at this threshold, it all becomes worthwhile, though getting augged is a better choice
+ severity -= 3//net benefits: 2 damage reduction, flight if you have wings, filter out low amounts of gas, durable ears, flash protection, a liver half as good as an upgraded cyberliver, and flight if you are a winged species
+ if(A.resistance >= 4)//at base level, robotic bodyparts have very few bonuses, mostly being a liability in the case of EMPS
severity += 1 //at this stage, even one EMP will hurt, a lot.
- if(A.properties["stage_rate"] >= 13)//but at this threshold, it all becomes worthwhile, though getting augged is a better choice
- severity -= 3//net benefits: 2 damage reduction, flight if you have wings, filter out low amounts of gas, durable ears, flash protection, a liver half as good as an upgraded cyberliver, and flight if you are a winged species
+
/datum/symptom/robotic_adaptation/Start(datum/disease/advance/A)
. = ..()
- if(A.properties["stage_rate"] >= 4)
+ if(A.stage_rate >= 4)
replaceorgans = TRUE
- if(A.properties["stage_rate"] >= 10)
- replacebody = TRUE
- if(A.properties["stage_rate"] >= 14)
- robustbits = TRUE //note that having this symptom means most healing symptoms won't work on you
+ if(A.stage_rate >= 4)
+ replacebody = TRUE
+ if(A.stage_rate >= 12)
+ robustbits = TRUE //note that having this symptom means most healing symptoms won't work on you
+
/datum/symptom/robotic_adaptation/Activate(datum/disease/advance/A)
if(!..())
@@ -46,100 +51,111 @@
if(replaceorgans)
to_chat(H, "[pick("You feel a grinding pain in your abdomen.", "You exhale a jet of steam.")]")
if(5)
- if(replaceorgans)
+ if(replaceorgans || replacebody)
if(Replace(H))
return
- else if(replacebody)
- H.adjustCloneLoss(-30) //we're fully mechanical, repair integrity. This symptom has a soft synergy with overclocked pituitary, so we want that to be useable. OFI is obviously out
- ADD_TRAIT(H, TRAIT_NANITECOMPATIBLE, DISEASE_TRAIT)
+ if(replacebody)
+ H.adjustCloneLoss(-20) //repair mechanical integrity
+ ADD_TRAIT(H, TRAIT_NANITECOMPATIBLE, DISEASE_TRAIT)
return
/datum/symptom/robotic_adaptation/proc/Replace(mob/living/carbon/human/H)
- for(var/obj/item/organ/O in H.internal_organs)
- if(O.status == ORGAN_ROBOTIC) //they are either part robotic or we already converted them!
- continue
- switch(O.slot) //i hate doing it this way, but the cleaner way runtimes and does not work
- if(ORGAN_SLOT_BRAIN)
- var/datum/mind/ownermind = H.mind
- var/obj/item/organ/brain/clockwork/organ = new()
- organ.Insert(H, TRUE, FALSE)
- to_chat(H, "Your head throbs with pain for a moment, and then goes numb.")
- H.emote("scream")
- ownermind.transfer_to(H)
- H.grab_ghost()
- return TRUE
- if(ORGAN_SLOT_STOMACH)
- var/obj/item/organ/stomach/clockwork/organ = new()
- organ.Insert(H, TRUE, FALSE)
- if(prob(40))
- to_chat(H, "You feel a stabbing pain in your abdomen!")
- H.emote("scream")
- return TRUE
- if(ORGAN_SLOT_EARS)
- var/obj/item/organ/ears/robot/clockwork/organ = new()
- if(robustbits)
- organ.damage_multiplier = 0.5
- organ.Insert(H, TRUE, FALSE)
- to_chat(H, "Your ears pop.")
- return TRUE
- if(ORGAN_SLOT_EYES)
- var/obj/item/organ/eyes/robotic/clockwork/organ = new()
- if(robustbits)
- organ.flash_protect = 1
- organ.Insert(H, TRUE, FALSE)
- if(prob(40))
- to_chat(H, "You feel a stabbing pain in your eyeballs!")
+ if(replaceorgans)
+ for(var/obj/item/organ/O in H.internal_organs)
+ if(O.status == ORGAN_ROBOTIC) //they are either part robotic or we already converted them!
+ continue
+ switch(O.slot) //i hate doing it this way, but the cleaner way runtimes and does not work
+ if(ORGAN_SLOT_BRAIN)
+ var/obj/item/organ/brain/clockwork/organ = new()
+ var/datum/mind/ownermind = H.mind
+ if(robustbits)
+ organ.robust = TRUE //STOPS THAT GODDAMN CLANGING BECAUSE IT'S WELL OILED OR SOMETHING
+ organ.Insert(H, TRUE, FALSE)
+ ownermind.transfer_to(H)
+ to_chat(H, "Your head throbs with pain for a moment, and then goes numb.")
H.emote("scream")
- return TRUE
- if(ORGAN_SLOT_LUNGS)
- var/obj/item/organ/lungs/clockwork/organ = new()
- if(robustbits)
- organ.safe_toxins_max = 15
- organ.safe_co2_max = 15
- organ.SA_para_min = 15
- organ.SA_sleep_min = 15
- organ.BZ_trip_balls_min = 15
- organ.gas_stimulation_min = 15
- organ.Insert(H, TRUE, FALSE)
- if(prob(40))
+ H.grab_ghost()
+ return TRUE
+ if(ORGAN_SLOT_STOMACH)
+ if(HAS_TRAIT(H, TRAIT_POWERHUNGRY))
+ var/obj/item/organ/stomach/battery/clockwork/organ = new()
+ if(robustbits)
+ organ.max_charge = 15000
+ organ.Insert(H, TRUE, FALSE)
+ else
+ var/obj/item/organ/stomach/clockwork/organ = new()
+ organ.Insert(H, TRUE, FALSE)
+ if(prob(40))
+ to_chat(H, "You feel a stabbing pain in your abdomen!")
+ H.emote("scream")
+ return TRUE
+ if(ORGAN_SLOT_EARS)
+ var/obj/item/organ/ears/robot/clockwork/organ = new()
+ if(robustbits)
+ organ.damage_multiplier = 0.5
+ organ.Insert(H, TRUE, FALSE)
+ to_chat(H, "Your ears pop.")
+ return TRUE
+ if(ORGAN_SLOT_EYES)
+ var/obj/item/organ/eyes/robotic/clockwork/organ = new()
+ if(robustbits)
+ organ.flash_protect = 1
+ organ.Insert(H, TRUE, FALSE)
+ if(prob(40))
+ to_chat(H, "You feel a stabbing pain in your eyeballs!")
+ H.emote("scream")
+ return TRUE
+ if(ORGAN_SLOT_LUNGS)
+ var/obj/item/organ/lungs/clockwork/organ = new()
+ if(robustbits)
+ organ.gas_max = list(
+ GAS_PLASMA = 15,
+ GAS_CO2 = 15,
+ )
+ organ.SA_para_min = 15
+ organ.SA_sleep_min = 15
+ organ.BZ_trip_balls_min = 15
+ organ.gas_stimulation_min = 15
+ organ.Insert(H, TRUE, FALSE)
+ if(prob(40))
+ to_chat(H, "You feel a stabbing pain in your chest!")
+ H.emote("scream")
+ return TRUE
+ if(ORGAN_SLOT_HEART)
+ var/obj/item/organ/heart/clockwork/organ = new()
+ organ.Insert(H, TRUE, FALSE)
to_chat(H, "You feel a stabbing pain in your chest!")
H.emote("scream")
- return TRUE
- if(ORGAN_SLOT_HEART)
- var/obj/item/organ/heart/clockwork/organ = new()
- organ.Insert(H, TRUE, FALSE)
- to_chat(H, "You feel a stabbing pain in your chest!")
- H.emote("scream")
- return TRUE
- if(ORGAN_SLOT_LIVER)
- var/obj/item/organ/liver/clockwork/organ = new()
- if(robustbits)
- organ.toxTolerance = 7
- organ.Insert(H, TRUE, FALSE)
- if(prob(40))
- to_chat(H, "You feel a stabbing pain in your abdomen!")
- H.emote("scream")
- return TRUE
- if(ORGAN_SLOT_TONGUE)
- if(robustbits)
- var/obj/item/organ/tongue/robot/clockwork/better/organ = new()
+ return TRUE
+ if(ORGAN_SLOT_LIVER)
+ var/obj/item/organ/liver/clockwork/organ = new()
+ if(robustbits)
+ organ.toxTolerance = 7
+ organ.Insert(H, TRUE, FALSE)
+ if(prob(40))
+ to_chat(H, "You feel a stabbing pain in your abdomen!")
+ H.emote("scream")
+ return TRUE
+ if(ORGAN_SLOT_TONGUE)
+ if(robustbits)
+ var/obj/item/organ/tongue/robot/clockwork/better/organ = new()
+ organ.Insert(H, TRUE, FALSE)
+ return TRUE
+ else
+ var/obj/item/organ/tongue/robot/clockwork/organ = new()
+ organ.Insert(H, TRUE, FALSE)
+ return TRUE
+ if(ORGAN_SLOT_TAIL)
+ var/obj/item/organ/tail/clockwork/organ = new()
organ.Insert(H, TRUE, FALSE)
return TRUE
- else
- var/obj/item/organ/tongue/robot/clockwork/organ = new()
+ if(ORGAN_SLOT_WINGS)
+ var/obj/item/organ/wings/cybernetic/clockwork/organ = new()
+ if(robustbits)
+ organ.flight_level = WINGS_FLYING
organ.Insert(H, TRUE, FALSE)
+ to_chat(H, "Your wings feel stiff.")
return TRUE
- if(ORGAN_SLOT_TAIL)
- var/obj/item/organ/tail/clockwork/organ = new()
- organ.Insert(H, TRUE, FALSE)
- return TRUE
- if(ORGAN_SLOT_WINGS)
- var/obj/item/organ/wings/cybernetic/clockwork/organ = new()
- if(robustbits)
- organ.flight_level = WINGS_FLYING
- organ.Insert(H, TRUE, FALSE)
- to_chat(H, "Your wings feel stiff.")
- return TRUE
if(replacebody)
for(var/obj/item/bodypart/O in H.bodyparts)
if(O.status == BODYPART_ROBOTIC)
@@ -147,10 +163,10 @@
O.burn_reduction = max(2, O.burn_reduction)
O.brute_reduction = max(3, O.brute_reduction)
continue
- switch(O.body_zone)
+ switch(O.body_zone)
if(BODY_ZONE_HEAD)
var/obj/item/bodypart/head/robot/clockwork/B = new()
- if(robustbits)
+ if(robustbits)
B.brute_reduction = 3 //this is just below the amount that lets augs ignore space damage.
B.burn_reduction = 2
B.replace_limb(H, TRUE)
@@ -233,18 +249,26 @@
/obj/item/organ/stomach/clockwork
name = "nutriment refinery"
- icon_state = "stomach-clock"
desc = "A biomechanical furnace, which turns calories into mechanical energy."
- icon_state = "liver-clock"
+ icon_state = "stomach-clock"
status = ORGAN_ROBOTIC
organ_flags = ORGAN_SYNTHETIC
-/obj/item/organ/stomach/cell/emp_act(severity)
- owner.nutrition -= 100 * severity
+/obj/item/organ/stomach/clockwork/emp_act(severity)
+ owner.adjust_nutrition(-200/severity)
+
+/obj/item/organ/stomach/battery/clockwork
+ name = "biometallic flywheel"
+ desc = "A biomechanical battery which stores mechanical energy."
+ icon_state = "stomach-clock"
+ status = ORGAN_ROBOTIC
+ organ_flags = ORGAN_SYNTHETIC
+ max_charge = 7500
+ charge = 7500
/obj/item/organ/tongue/robot/clockwork
name = "dynamic micro-phonograph"
- desc = "an old-timey looking device connected to an odd, shifting cylinder."
+ desc = "An old-timey looking device connected to an odd, shifting cylinder."
icon_state = "tongueclock"
/obj/item/organ/tongue/robot/clockwork/better
@@ -257,10 +281,10 @@
/obj/item/organ/brain/clockwork
name = "enigmatic gearbox"
desc ="An engineer would call this inconcievable wonder of gears and metal a 'black box'"
- icon_state = "posibrain-occupied"
+ icon_state = "brain-clock"
status = ORGAN_ROBOTIC
organ_flags = ORGAN_SYNTHETIC
- icon_state = "brain-clock"
+ var/robust //Set to true if the robustbits causes brain replacement. Because holy fuck is the CLANG CLANG CLANG CLANG annoying
/obj/item/organ/brain/clockwork/emp_act(severity)
switch(severity)
@@ -271,13 +295,13 @@
/obj/item/organ/brain/clockwork/on_life()
. = ..()
- if(prob(25))
+ if(prob(5) && !robust)
SEND_SOUND(owner, pickweight(list('sound/effects/clock_tick.ogg' = 6, 'sound/effects/smoke.ogg' = 2, 'sound/spookoween/chain_rattling.ogg' = 1, 'sound/ambience/ambiruin3.ogg' = 1)))
/obj/item/organ/liver/clockwork
name = "biometallic alembic"
- icon_state = "liver-c"
desc = "A series of small pumps and boilers, designed to facilitate proper metabolism."
+ icon_state = "liver-clock"
organ_flags = ORGAN_SYNTHETIC
status = ORGAN_ROBOTIC
alcohol_tolerance = 0
@@ -364,4 +388,4 @@
desc = "An odd metal body full of gears and pipes. It still seems alive."
icon = 'icons/mob/augmentation/augments_clockwork.dmi'
brute_reduction = 0
- burn_reduction = 0
\ No newline at end of file
+ burn_reduction = 0
diff --git a/code/datums/diseases/advance/symptoms/cockroach.dm b/code/datums/diseases/advance/symptoms/cockroach.dm
index c922c69e7312d..018ed1993919d 100644
--- a/code/datums/diseases/advance/symptoms/cockroach.dm
+++ b/code/datums/diseases/advance/symptoms/cockroach.dm
@@ -5,11 +5,13 @@
stealth = 0
resistance = 3
stage_speed = 3
- transmittable = 1
+ transmission = 1
level = 0
severity = 0 //rip funy
symptom_delay_min = 10
symptom_delay_max = 30
+ prefixes = list("Blatto")
+ bodies = list("Roach")
var/death_roaches = FALSE
threshold_desc = "Stage Speed 8:Increases roach speed \
Transmission 8:When the host dies, more roaches spawn "
@@ -17,10 +19,10 @@
/datum/symptom/cockroach/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["stage_rate"] >= 8)
+ if(A.stage_rate >= 8)
symptom_delay_min = 5
symptom_delay_max = 15
- if(A.properties["transmittable"] >= 8)
+ if(A.transmission >= 8)
death_roaches = TRUE
/datum/symptom/cockroach/Activate(datum/disease/advance/A)
diff --git a/code/datums/diseases/advance/symptoms/confusion.dm b/code/datums/diseases/advance/symptoms/confusion.dm
index f5ccef12acd23..dc6777d896b10 100644
--- a/code/datums/diseases/advance/symptoms/confusion.dm
+++ b/code/datums/diseases/advance/symptoms/confusion.dm
@@ -22,12 +22,14 @@ Bonus
stealth = 1
resistance = -1
stage_speed = -3
- transmittable = 0
+ transmission = 0
level = 4
severity = 2
base_message_chance = 25
symptom_delay_min = 10
symptom_delay_max = 30
+ prefixes = list("Dizzy ")
+ bodies = list("Ditz")
var/brain_damage = FALSE
threshold_desc = "Resistance 6: Causes brain damage over time. \
Transmission 6: Increases confusion duration. \
@@ -35,17 +37,17 @@ Bonus
/datum/symptom/confusion/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["resistance"] >= 6)
+ if(A.resistance >= 6)
severity += 1
/datum/symptom/confusion/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["resistance"] >= 6)
+ if(A.resistance >= 6)
brain_damage = TRUE
- if(A.properties["transmittable"] >= 6)
+ if(A.transmission >= 6)
power = 1.5
- if(A.properties["stealth"] >= 4)
+ if(A.stealth >= 4)
suppress_warning = TRUE
/datum/symptom/confusion/Activate(datum/disease/advance/A)
diff --git a/code/datums/diseases/advance/symptoms/cough.dm b/code/datums/diseases/advance/symptoms/cough.dm
index c286c7c64809e..cae4806c56f0b 100644
--- a/code/datums/diseases/advance/symptoms/cough.dm
+++ b/code/datums/diseases/advance/symptoms/cough.dm
@@ -22,12 +22,13 @@ BONUS
stealth = -1
resistance = 3
stage_speed = 1
- transmittable = 2
+ transmission = 2
level = 1
severity = 0
base_message_chance = 15
symptom_delay_min = 2
symptom_delay_max = 15
+ bodies = list("Cough")
var/infective = FALSE
threshold_desc = "Resistance 3: Host will drop small items when coughing. \
Resistance 10: Occasionally causes coughing fits that stun the host. \
@@ -37,23 +38,23 @@ BONUS
/datum/symptom/cough/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["resistance"] >= 3)
- severity += 1
- if(A.properties["resistance"] >= 10)
+ if(A.resistance >= 3)
severity += 1
+ if(A.resistance >= 10)
+ severity += 1
/datum/symptom/cough/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["stealth"] >= 4)
+ if(A.stealth >= 4)
suppress_warning = TRUE
if(A.spread_flags & DISEASE_SPREAD_AIRBORNE) //infect bystanders
infective = TRUE
- if(A.properties["resistance"] >= 3) //strong enough to drop items
+ if(A.resistance >= 3) //strong enough to drop items
power = 1.5
- if(A.properties["resistance"] >= 10) //strong enough to stun (rarely)
- power = 2
- if(A.properties["stage_rate"] >= 6) //cough more often
+ if(A.resistance >= 10) //strong enough to stun (rarely)
+ power = 2
+ if(A.stage_rate >= 6) //cough more often
symptom_delay_max = 10
/datum/symptom/cough/Activate(datum/disease/advance/A)
diff --git a/code/datums/diseases/advance/symptoms/deafness.dm b/code/datums/diseases/advance/symptoms/deafness.dm
index c675287788317..ce45116175068 100644
--- a/code/datums/diseases/advance/symptoms/deafness.dm
+++ b/code/datums/diseases/advance/symptoms/deafness.dm
@@ -22,26 +22,28 @@ Bonus
stealth = -1
resistance = -2
stage_speed = -1
- transmittable = -3
+ transmission = -3
level = 4
severity = 2
base_message_chance = 100
symptom_delay_min = 25
symptom_delay_max = 80
+ prefixes = list("Aural ")
+ bodies = list("Ear")
threshold_desc = "Resistance 9: Causes permanent deafness, instead of intermittent. \
Stealth 4: The symptom remains hidden until active."
/datum/symptom/deafness/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["resistance"] >= 9)
+ if(A.resistance >= 9)
severity += 1
/datum/symptom/deafness/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["stealth"] >= 4)
+ if(A.stealth >= 4)
suppress_warning = TRUE
- if(A.properties["resistance"] >= 9) //permanent deafness
+ if(A.resistance >= 9) //permanent deafness
power = 2
/datum/symptom/deafness/Activate(datum/disease/advance/A)
diff --git a/code/datums/diseases/advance/symptoms/fever.dm b/code/datums/diseases/advance/symptoms/fever.dm
index 0ab44686b22b0..4242cc66a4c88 100644
--- a/code/datums/diseases/advance/symptoms/fever.dm
+++ b/code/datums/diseases/advance/symptoms/fever.dm
@@ -6,7 +6,7 @@ Fever
No change to hidden.
Increases resistance.
Increases stage speed.
- Little transmittable.
+ Little transmission.
Low level.
Bonus
@@ -21,31 +21,35 @@ Bonus
stealth = -1
resistance = 3
stage_speed = 3
- transmittable = 2
+ transmission = 2
level = 2
severity = 0
base_message_chance = 20
symptom_delay_min = 10
symptom_delay_max = 30
+ bodies = list("Fever")
+ suffixes = list(" Fever")
var/unsafe = FALSE //over the heat threshold
threshold_desc = "Resistance 5: Increases fever intensity, fever can overheat and harm the host. \
Resistance 10: Further increases fever intensity."
/datum/symptom/fever/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["resistance"] >= 5)
- severity += 1
- if(A.properties["resistance"] >= 10)
+ if(A.resistance >= 5)
severity += 1
+ prefixes = list("Desert")
+ if(A.resistance >= 10)
+ severity += 1
+ prefixes = list("Volcanic")
/datum/symptom/fever/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["resistance"] >= 5) //dangerous fever
+ if(A.resistance >= 5) //dangerous fever
power = 1.5
unsafe = TRUE
- if(A.properties["resistance"] >= 10)
- power = 2.5
+ if(A.resistance >= 10)
+ power = 2.5
/datum/symptom/fever/Activate(datum/disease/advance/A)
if(!..())
diff --git a/code/datums/diseases/advance/symptoms/fire.dm b/code/datums/diseases/advance/symptoms/fire.dm
index fe141596d1fdb..692b78a1f4e16 100644
--- a/code/datums/diseases/advance/symptoms/fire.dm
+++ b/code/datums/diseases/advance/symptoms/fire.dm
@@ -22,12 +22,15 @@ Bonus
stealth = 1
resistance = -2
stage_speed = -3
- transmittable = -3
+ transmission = -3
level = 6
severity = 4
base_message_chance = 20
symptom_delay_min = 20
symptom_delay_max = 75
+ prefixes = list("Burning ")
+ bodies = list("Combustion")
+ suffixes = list(" Combustion")
var/infective = FALSE
threshold_desc = "Stage Speed 4: Increases the intensity of the flames. \
Stage Speed 8: Further increases flame intensity. \
@@ -37,13 +40,13 @@ Bonus
/datum/symptom/fire/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["stage_rate"] >= 4)
+ if(A.stage_rate >= 4)
power = 1.5
- if(A.properties["stage_rate"] >= 8)
- power = 2
- if(A.properties["stealth"] >= 4)
+ if(A.stage_rate >= 8)
+ power = 2
+ if(A.stealth >= 4)
suppress_warning = TRUE
- if(A.properties["transmittable"] >= 8) //burning skin spreads the virus through smoke
+ if(A.transmission >= 8) //burning skin spreads the virus through smoke
infective = TRUE
/datum/symptom/fire/Activate(datum/disease/advance/A)
@@ -103,17 +106,20 @@ Bonus
/datum/symptom/alkali
- name = "Alkali perspiration"
+ name = "Alkali Perspiration"
desc = "The virus attaches to sudoriparous glands, synthesizing a chemical that bursts into flames when reacting with water, leading to self-immolation."
stealth = 2
resistance = -2
stage_speed = -2
- transmittable = -2
+ transmission = -2
level = 9
severity = 5
base_message_chance = 100
symptom_delay_min = 30
symptom_delay_max = 90
+ prefixes = list("Explosive ")
+ bodies = list("Hellfire")
+ suffixes = list(" of the Damned")
var/chems = FALSE
var/explosion_power = 1
threshold_desc = "Resistance 9: Doubles the intensity of the effect, but reduces its frequency. \
@@ -122,21 +128,19 @@ Bonus
/datum/symptom/alkali/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["resistance"] >= 9)
- severity = 6
- if(A.properties["stage_rate"] >= 10)
+ if(A.resistance >= 9 || A.stage_rate >= 8)
severity = 6
/datum/symptom/alkali/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["resistance"] >= 9) //intense but sporadic effect
+ if(A.resistance >= 9) //intense but sporadic effect
power = 2
symptom_delay_min = 50
symptom_delay_max = 140
- if(A.properties["stage_rate"] >= 8) //serious boom when wet
+ if(A.stage_rate >= 8) //serious boom when wet
explosion_power = 2
- if(A.properties["transmittable"] >= 8) //extra chemicals
+ if(A.transmission >= 8) //extra chemicals
chems = TRUE
/datum/symptom/alkali/Activate(datum/disease/advance/A)
diff --git a/code/datums/diseases/advance/symptoms/flesh_eating.dm b/code/datums/diseases/advance/symptoms/flesh_eating.dm
index 4082bbe75d7ef..a6355765eb18b 100644
--- a/code/datums/diseases/advance/symptoms/flesh_eating.dm
+++ b/code/datums/diseases/advance/symptoms/flesh_eating.dm
@@ -22,12 +22,14 @@ Bonus
stealth = -3
resistance = -4
stage_speed = 0
- transmittable = -4
+ transmission = -4
level = 6
severity = 4
base_message_chance = 50
symptom_delay_min = 15
symptom_delay_max = 60
+ prefixes = list("Bloody ", "Hemo")
+ bodies = list("Hemophilia")
var/bleed = FALSE
var/pain = FALSE
threshold_desc = "Resistance 7: Host will bleed profusely during necrosis. \
@@ -36,9 +38,9 @@ Bonus
/datum/symptom/flesh_eating/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["resistance"] >= 7) //extra bleeding
+ if(A.resistance >= 7) //extra bleeding
bleed = TRUE
- if(A.properties["transmittable"] >= 8) //extra stamina damage
+ if(A.transmission >= 8) //extra stamina damage
pain = TRUE
/datum/symptom/flesh_eating/Activate(datum/disease/advance/A)
@@ -88,25 +90,32 @@ Bonus
stealth = -2
resistance = -2
stage_speed = 1
- transmittable = -2
+ transmission = -2
level = 9
severity = 5
base_message_chance = 50
symptom_delay_min = 3
symptom_delay_max = 6
+ prefixes = list("Necrotic ", "Necro")
+ suffixes = list(" Rot")
var/chems = FALSE
var/zombie = FALSE
threshold_desc = "Stage Speed 7: Synthesizes Heparin and Lipolicide inside the host, causing increased bleeding and hunger. \
Stealth 5: The symptom remains hidden until active."
+/datum/symptom/flesh_death/severityset(datum/disease/advance/A)
+ . = ..()
+ if((A.stealth >= 2) && (A.stage_rate >= 12))
+ bodies = list("Zombie")
+
/datum/symptom/flesh_death/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["stealth"] >= 5)
+ if(A.stealth >= 5)
suppress_warning = TRUE
- if(A.properties["stage_rate"] >= 7) //bleeding and hunger
+ if(A.stage_rate >= 7) //bleeding and hunger
chems = TRUE
- if((A.properties["stealth"] >= 2) && (A.properties["stage_rate"] >= 12))
+ if((A.stealth >= 2) && (A.stage_rate >= 12))
zombie = TRUE
/datum/symptom/flesh_death/Activate(datum/disease/advance/A)
@@ -125,7 +134,7 @@ Bonus
return
if(prob(base_message_chance / 2)) //reduce spam
to_chat(M, "[pick("You feel your muscles weakening.", "Some of your skin detaches itself.", "You feel sandy.")]")
-
+
/datum/symptom/flesh_death/proc/Flesh_death(mob/living/M, datum/disease/advance/A)
var/get_damage = rand(6,10)
if(MOB_UNDEAD in M.mob_biotypes)
diff --git a/code/datums/diseases/advance/symptoms/fleshgrowth.dm b/code/datums/diseases/advance/symptoms/fleshgrowth.dm
deleted file mode 100644
index 71e7a27232886..0000000000000
--- a/code/datums/diseases/advance/symptoms/fleshgrowth.dm
+++ /dev/null
@@ -1,232 +0,0 @@
-/datum/symptom/flesh
- name = "Exolocomotive Xenomitosis"
- desc = "The virus will grow on any surfaces it can, such as the host's skin, or even the ground, should the host remain stationary"
- stealth = -2
- resistance = 3
- stage_speed = 3
- transmittable = 1
- level = 9
- severity = 2
- symptom_delay_min = 1
- symptom_delay_max = 1
- var/cachedcolor = null
- var/turf/open/currentloc = null
- var/cycles = 0
- var/lastcycle = 0
- var/requiredcycles = 0
- var/maxradius = 1
- threshold_desc = "Stage Speed:Influences the time the host must stand still to begin spreading infectious mass. \
- Stage Speed 6:Infectious mass will patch wounds in the host's flesh, healing their brute damage. Standing on infectious mass heals the host far quicker, and laying down even faster. -3 severity. \
- Transmission:Influences the maximum spread radius of infectious mass. \
- Transmission 10:Infectious mass will contain all viruses currently afflicting the host. +1 severity."
-
-/datum/symptom/flesh/severityset(datum/disease/advance/A)
- . = ..()
- if(A.properties["stage_rate"] >= 6)
- severity -= 3
- if(A.properties["transmittable"] >= 10)
- severity += 1
-
-/datum/symptom/flesh/Start(datum/disease/advance/A)
- . = ..()
- requiredcycles = (max(2, round((18 - A.properties["stage_rate"]) / 2))) //14 speed is the highest possible rate of growth
- maxradius = (round(A.properties["transmittable"] / 3))
- var/mob/living/M = A.affected_mob
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- if(H.dna.species.use_skintones)
- cachedcolor = H.skin_tone
- else if(MUTCOLORS in H.dna.species.species_traits)
- cachedcolor = H.dna.features["mcolor"]
-
-/datum/symptom/flesh/Activate(datum/disease/advance/A)
- if(!..())
- return
- var/mob/living/M = A.affected_mob
- var/list/diseases = list(A)
- var/healfactor = 0
- if(iscarbon(M))
- var/mob/living/carbon/C = M
- switch(A.stage)
- if(5)
- if(currentloc == C.loc)
- cycles += 1
- else if(isturf(C.loc) && !isspaceturf(C.loc))
- currentloc = C.loc
- cycles = 0
- lastcycle = 0
- var/obj/structure/alien/flesh/W = locate(/obj/structure/alien/flesh) in currentloc
- if(W)
- healfactor += 1
- if(istype(W, /obj/structure/alien/flesh/node))
- healfactor += 1
- var/obj/structure/alien/flesh/node/node = W
- if(round(cycles / requiredcycles) >= lastcycle && node.node_range < maxradius)
- node.node_range += 1
- lastcycle += 1
- if(!(C.mobility_flags & MOBILITY_STAND))
- healfactor *= 2
- else if(round(cycles / requiredcycles) >= 1)
- if(A.properties["transmittable"] >= 10)
- for(var/datum/disease/D in M.diseases)
- if((D.spread_flags & DISEASE_SPREAD_SPECIAL) || (D.spread_flags & DISEASE_SPREAD_NON_CONTAGIOUS) || (D.spread_flags & DISEASE_SPREAD_FALTERED))
- continue
- if(D == A)
- continue
- diseases += D
- if(maxradius >= 1)
- var/obj/structure/alien/flesh/node/N = new(currentloc, diseases)
- N.node_range = 0
- else
- new /obj/structure/alien/flesh(currentloc, diseases)
- C.visible_message("The film on [C]'s skin grows onto the floor!", "The film creeping along your skin secretes onto the floor!")
- lastcycle += 1
- if(A.properties["stage_rate"] >= 8)
- healfactor += 0.5
- C.heal_overall_damage(healfactor, required_status = BODYPART_ORGANIC)//max passive healing is 4.5, whilst laying down on a node.
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- if(istype(H.dna.species, /datum/species/zombie/infectious))
- var/datum/species/zombie/infectious/Z = H.dna.species
- if(Z.limbs_id == "pinkzombie")
- return
- else
- Z.limbs_id = "pinkzombie"
- H.regenerate_icons()
- if(H.skin_tone == "pink")
- return
- else if(H.dna.features["mcolor"] == "D37")
- return
- if(H.dna.species.use_skintones)
- H.skin_tone = "pink"
- H.visible_message("A film of pinkish material grows over [H]'s skin!", "Your skin is completely covered by a film of pinkish, fleshy mass!")
- H.regenerate_icons()
- else if(MUTCOLORS in H.dna.species.species_traits)
- H.dna.features["mcolor"] = "D37" //pinkish red
- H.visible_message("A film of pinkish material grows over [H]'s skin!", "Your skin is completely covered by a film of pinkish, fleshy mass!")
- H.regenerate_icons()
-
-/datum/symptom/flesh/End(datum/disease/advance/A)
- . = ..()
- var/mob/living/M = A.affected_mob
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- if(H.dna.species.use_skintones)
- H.skin_tone = cachedcolor
- else if(MUTCOLORS in H.dna.species.species_traits)
- H.dna.features["mcolor"] = cachedcolor
- H.regenerate_icons()
-
-
-/obj/structure/alien/flesh //this isn't a subtype of alien weeds so it wont heal aliens
- gender = PLURAL
- name = "infested floor"
- desc = "A thick film of flesh covers the floor."
- anchored = TRUE
- density = FALSE
- layer = TURF_LAYER
- plane = FLOOR_PLANE
- icon_state = "weeds"
- max_integrity = 15
- canSmoothWith = list(/obj/structure/alien/flesh, /turf/closed/wall)
- smooth = SMOOTH_MORE
- var/last_expand = 0 //last world.time this weed expanded
- var/growth_cooldown_low = 150
- var/growth_cooldown_high = 200
- var/static/list/blacklisted_turfs
- var/list/nodediseases = list()
-
-/obj/structure/alien/flesh/Initialize(mapload, list/datum/disease/diseases)
- pixel_x = -4
- pixel_y = -4 //so the sprites line up right in the map editor
- . = ..()
-
- if(!blacklisted_turfs) //note: if some sort of sanitary floors are added, put them in here
- blacklisted_turfs = typecacheof(list(
- /turf/open/space,
- /turf/open/chasm,
- /turf/open/lava))
-
-
- last_expand = world.time + rand(growth_cooldown_low, growth_cooldown_high)
- if(icon == initial(icon))
- switch(rand(1,3))
- if(1)
- icon = 'icons/obj/smooth_structures/alien/flesh1.dmi'
- if(2)
- icon = 'icons/obj/smooth_structures/alien/flesh2.dmi'
- if(3)
- icon = 'icons/obj/smooth_structures/alien/flesh3.dmi'
- if(LAZYLEN(diseases))
- for(var/datum/disease/D in diseases)
- nodediseases += D
- if(LAZYLEN(nodediseases))
- AddComponent(/datum/component/infective, nodediseases)
-
-/obj/structure/alien/flesh/examine(mob/user)
- . = ..()
- if(isliving(user))
- var/mob/living/U = user
- for(var/datum/disease/advance/A in U.diseases)
- for(var/datum/symptom/S in A.symptoms)
- if(istype(S, /datum/symptom/flesh))
- . += "It looks warm and inviting. It would be so wonderful to just lay down in it..."
- return
-
-/obj/structure/alien/flesh/proc/expand()
- var/turf/U = get_turf(src)
- if(is_type_in_typecache(U, blacklisted_turfs))
- qdel(src)
- return FALSE
-
- for(var/turf/T in U.GetAtmosAdjacentTurfs())
- if(locate(/obj/structure/alien/flesh) in T)
- continue
-
- if(is_type_in_typecache(T, blacklisted_turfs))
- continue
-
- var/obj/structure/alien/weeds/W = locate(/obj/structure/alien/weeds) in T //we infect and subsume alien weeds and replace them with our own shit
- if(W)
- if(istype(W, /obj/structure/alien/weeds/node))
- new /obj/structure/alien/flesh/node(T, nodediseases)
- continue
- else
- qdel(W)
- new /obj/structure/alien/flesh(T, nodediseases)
- return TRUE
-
-/obj/structure/alien/flesh/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume)
- if(exposed_temperature > 300)
- take_damage(5, BURN, 0, 0)
-
-//Weed nodes
-/obj/structure/alien/flesh/node
- name = "putrid infection"
- desc = "A sickly red glow emanates from the pustules on this fleshy film."
- icon_state = "weednode"
- light_color = LIGHT_COLOR_BLOOD_MAGIC //a good, sickly atmosphere
- light_power = 0.5
- var/lon_range = 4
- var/node_range = 3
-
-
-/obj/structure/alien/flesh/node/Initialize()
- icon = 'icons/obj/smooth_structures/alien/fleshpolyp.dmi'
- . = ..()
- set_light(lon_range)
- var/obj/structure/alien/W = locate(/obj/structure/alien) in loc //we infect and take over alien resin
- if(W && W != src)
- qdel(W)
- START_PROCESSING(SSobj, src)
-
-/obj/structure/alien/flesh/node/Destroy()
- STOP_PROCESSING(SSobj, src)
- return ..()
-
-/obj/structure/alien/flesh/node/process()
- if(node_range)
- for(var/obj/structure/alien/flesh/W in range(node_range, src))
- if(W.last_expand <= world.time)
- if(W.expand())
- W.last_expand = world.time + rand(growth_cooldown_low, growth_cooldown_high)
\ No newline at end of file
diff --git a/code/datums/diseases/advance/symptoms/genetics.dm b/code/datums/diseases/advance/symptoms/genetics.dm
index 0f5d9153780d4..05619706eedfa 100644
--- a/code/datums/diseases/advance/symptoms/genetics.dm
+++ b/code/datums/diseases/advance/symptoms/genetics.dm
@@ -21,7 +21,7 @@ Bonus
stealth = -2
resistance = -3
stage_speed = 0
- transmittable = -3
+ transmission = -3
level = 6
severity = 3
var/list/possible_mutations
@@ -29,17 +29,16 @@ Bonus
base_message_chance = 50
symptom_delay_min = 60
symptom_delay_max = 120
+ prefixes = list("Genetic ", "Chromosomal ", "Mutagenic ", "Muta-")
+ bodies = list("Mutant")
var/no_reset = FALSE
threshold_desc = "Resistance 8: Causes two harmful mutations at once. \
Stage Speed 10: Increases mutation frequency. \
- Stage Speed 14: Mutations will be beneficial. \
Stealth 5: The mutations persist even if the virus is cured."
/datum/symptom/genetic_mutation/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["stage_rate"] >= 14)
- severity = 0
- else if(A.properties["resistance"] >= 8)
+ if(A.resistance >= 8)
severity += 1
/datum/symptom/genetic_mutation/Activate(datum/disease/advance/A)
@@ -59,17 +58,14 @@ Bonus
/datum/symptom/genetic_mutation/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["stealth"] >= 5) //don't restore dna after curing
+ if(A.stealth >= 5) //don't restore dna after curing
no_reset = TRUE
- if(A.properties["stage_rate"] >= 10) //mutate more often
+ if(A.stage_rate >= 10) //mutate more often
symptom_delay_min = 20
symptom_delay_max = 60
- if(A.properties["resistance"] >= 8) //mutate twice
+ if(A.resistance >= 8) //mutate twice
power = 2
- if(A.properties["stage_rate"] >= 14)
- possible_mutations = (GLOB.good_mutations | GLOB.not_good_mutations) - GLOB.all_mutations[RACEMUT]
- else
- possible_mutations = (GLOB.bad_mutations | GLOB.not_good_mutations) - GLOB.all_mutations[RACEMUT]
+ possible_mutations = (GLOB.bad_mutations | GLOB.not_good_mutations) - GLOB.all_mutations[RACEMUT]
var/mob/living/carbon/M = A.affected_mob
if(M)
if(!M.has_dna())
diff --git a/code/datums/diseases/advance/symptoms/hallucigen.dm b/code/datums/diseases/advance/symptoms/hallucigen.dm
index 6782d5195e04a..afce17942a3d5 100644
--- a/code/datums/diseases/advance/symptoms/hallucigen.dm
+++ b/code/datums/diseases/advance/symptoms/hallucigen.dm
@@ -6,7 +6,7 @@ Hallucigen
Very noticable.
Lowers resistance considerably.
Decreases stage speed.
- Reduced transmittable.
+ Reduced transmission.
Critical Level.
Bonus
@@ -21,28 +21,30 @@ Bonus
stealth = 1
resistance = -3
stage_speed = -1
- transmittable = -1
+ transmission = -1
level = 5
severity = 1
base_message_chance = 25
symptom_delay_min = 10
symptom_delay_max = 70
+ prefixes = list("Narcotic ", "Narco", "Psycho-")
+ suffixes = list(" Psychosis")
var/fake_healthy = FALSE
threshold_desc = "Stage Speed 7: Increases the amount of hallucinations. \
Stealth 2: The virus mimics positive symptoms.."
/datum/symptom/hallucigen/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["stage_rate"] >= 7)
+ if(A.stage_rate >= 7)
severity += 1
/datum/symptom/hallucigen/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["stealth"] >= 2) //fake good symptom messages
+ if(A.stealth >= 2) //fake good symptom messages
fake_healthy = TRUE
base_message_chance = 50
- if(A.properties["stage_rate"] >= 7) //stronger hallucinations
+ if(A.stage_rate >= 7) //stronger hallucinations
power = 2
/datum/symptom/hallucigen/Activate(datum/disease/advance/A)
diff --git a/code/datums/diseases/advance/symptoms/headache.dm b/code/datums/diseases/advance/symptoms/headache.dm
index ccfbdd1daed9d..b65efdce59e60 100644
--- a/code/datums/diseases/advance/symptoms/headache.dm
+++ b/code/datums/diseases/advance/symptoms/headache.dm
@@ -23,31 +23,32 @@ BONUS
stealth = -1
resistance = 4
stage_speed = 2
- transmittable = 0
+ transmission = 0
level = 1
severity = 0
base_message_chance = 100
symptom_delay_min = 15
symptom_delay_max = 30
+ bodies = list("Skull", "Migraine")
threshold_desc = "Stage Speed 6: Headaches will cause severe pain, that weakens the host. \
Stage Speed 9: Headaches become less frequent but far more intense, preventing any action from the host. \
Stealth 4: Reduces headache frequency until later stages."
/datum/symptom/headache/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["stage_rate"] >= 6)
- severity += 1
- if(A.properties["stage_rate"] >= 9)
+ if(A.stage_rate >= 6)
severity += 1
+ if(A.stage_rate >= 9)
+ severity += 1
/datum/symptom/headache/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["stealth"] >= 4)
+ if(A.stealth >= 4)
base_message_chance = 50
- if(A.properties["stage_rate"] >= 6) //severe pain
- power = 2
- if(A.properties["stage_rate"] >= 9) //cluster headaches
+ if(A.stage_rate >= 6) //severe pain
+ power = 2
+ if(A.stage_rate >= 9) //cluster headaches
symptom_delay_min = 30
symptom_delay_max = 60
power = 3
@@ -64,4 +65,4 @@ BONUS
M.adjustStaminaLoss(25)
if(power >= 3 && A.stage >= 5)
to_chat(M, "[pick("Your head hurts!", "You feel a burning knife inside your brain!", "A wave of pain fills your head!")]")
- M.Stun(35)
\ No newline at end of file
+ M.Stun(35)
diff --git a/code/datums/diseases/advance/symptoms/heal.dm b/code/datums/diseases/advance/symptoms/heal.dm
index 35ca2badf22d7..786601ace4052 100644
--- a/code/datums/diseases/advance/symptoms/heal.dm
+++ b/code/datums/diseases/advance/symptoms/heal.dm
@@ -4,7 +4,7 @@
stealth = 0
resistance = 0
stage_speed = 0
- transmittable = 0
+ transmission = 0
level = -1 //not obtainable
base_message_chance = 20 //here used for the overlays
symptom_delay_min = 1
@@ -16,7 +16,7 @@
/datum/symptom/heal/Start(datum/disease/advance/A)
if(!..())
return FALSE
- if(A.properties["stage_rate"] >= 6) //stronger healing
+ if(A.stage_rate >= 6) //stronger healing
power = 2
return TRUE //For super calls of subclasses
@@ -49,9 +49,10 @@
stealth = 0
resistance = -2
stage_speed = 2
- transmittable = -2
+ transmission = -2
level = 7
power = 2
+ prefixes = list("Toxo")
var/food_conversion = FALSE
desc = "The virus rapidly breaks down any foreign chemicals in the bloodstream."
threshold_desc = "Resistance 7: Increases chem removal speed. \
@@ -60,9 +61,9 @@
/datum/symptom/heal/chem/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["stage_rate"] >= 6)
+ if(A.stage_rate >= 6)
food_conversion = TRUE
- if(A.properties["resistance"] >= 7)
+ if(A.resistance >= 7)
power = 4
/datum/symptom/heal/chem/Heal(mob/living/M, datum/disease/advance/A, actual_power)
@@ -80,26 +81,27 @@
stealth = 0
resistance = 2
stage_speed = -3
- transmittable = -3
+ transmission = -3
level = 8
severity = -2
passive_message = "The pain from your wounds makes you feel oddly sleepy."
+ prefixes = list("Sleeping ", "Regenerative ")
+ suffixes = list(" Coma")
var/deathgasp = FALSE
var/stabilize = FALSE
var/active_coma = FALSE //to prevent multiple coma procs
threshold_desc = "Stealth 2: Host appears to die when falling into a coma, triggering symptoms that activate on death. \
-
Resistance 4: The virus also stabilizes the host while they are in critical condition. \
Stage Speed 7: Increases healing speed."
/datum/symptom/heal/coma/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["stage_rate"] >= 7)
+ if(A.stage_rate >= 7)
power = 1.5
- if(A.properties["resistance"] >= 4)
+ if(A.resistance >= 4)
stabilize = TRUE
- if(A.properties["stealth"] >= 2)
+ if(A.stealth >= 2)
deathgasp = TRUE
/datum/symptom/heal/coma/on_stage_change(new_stage, datum/disease/advance/A) //mostly copy+pasted from the code for self-respiration's TRAIT_NOBREATH stuff
@@ -133,8 +135,9 @@
/datum/symptom/heal/coma/proc/coma(mob/living/M)
if(deathgasp)
- M.emote("deathgasp")
- M.fakedeath("regenerative_coma")
+ M.fakedeath(TRAIT_REGEN_COMA)
+ else
+ M.Unconscious(300, TRUE, TRUE)
M.update_stat()
M.update_mobility()
addtimer(CALLBACK(src, .proc/uncoma, M), 300)
@@ -143,7 +146,10 @@
if(!active_coma)
return
active_coma = FALSE
- M.cure_fakedeath("regenerative_coma")
+ if(deathgasp)
+ M.cure_fakedeath(TRAIT_REGEN_COMA)
+ else
+ M.SetUnconscious(0)
M.update_stat()
M.update_mobility()
@@ -175,10 +181,11 @@
stealth = -1
resistance = -2
stage_speed = -2
- transmittable = 0
+ transmission = 0
severity = -1
level = 6
passive_message = "Your skin tingles."
+ prefixes = list("Healing ", "Minor ")
var/threshhold = 15
var/scarcounter = 0
@@ -188,9 +195,9 @@
/datum/symptom/heal/surface/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["stage_rate"] >= 8) //stronger healing
+ if(A.stage_rate >= 8) //stronger healing
power = 2
- if(A.properties["resistance"] >= 10)
+ if(A.resistance >= 10)
threshhold = 30
/datum/symptom/heal/surface/Heal(mob/living/carbon/M, datum/disease/advance/A, actual_power)
@@ -207,7 +214,7 @@
scarcounter++
if(M.getToxLoss() && M.getToxLoss() <= threshhold)
- M.adjustToxLoss(-power)
+ M.adjustToxLoss(-power, FALSE, TRUE)
if(healed)
if(prob(10))
@@ -226,8 +233,10 @@
stealth = -1
resistance = -2
stage_speed = 2
- transmittable = 1
+ transmission = 1
level = 4
+ prefixes = list("Metabolic ", "Junkie's ", "Chemical ")
+ bodies = list("Hunger")
var/triple_metabolism = FALSE
var/reduced_hunger = FALSE
desc = "The virus causes the host's metabolism to accelerate rapidly, making them process chemicals twice as fast,\
@@ -238,9 +247,9 @@
/datum/symptom/heal/metabolism/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["stage_rate"] >= 10)
+ if(A.stage_rate >= 10)
triple_metabolism = TRUE
- if(A.properties["stealth"] >= 3)
+ if(A.stealth >= 3)
reduced_hunger = TRUE
/datum/symptom/heal/metabolism/Heal(mob/living/carbon/C, datum/disease/advance/A, actual_power)
@@ -269,11 +278,13 @@ im not even gonna bother with these for the following symptoms. typed em out, co
stealth = 0
resistance = -1
stage_speed = -1
- transmittable = -2
+ transmission = -2
level = 6
severity = 2
symptom_delay_min = 15
symptom_delay_max = 40
+ prefixes = list("Magnetic ", "Electro")
+ bodies = list("Magnet")
var/bigemp = FALSE
var/cellheal = FALSE
threshold_desc = "Stealth 2: The disease resets cell DNA, quickly curing cell damage and mutations. \
@@ -281,17 +292,17 @@ im not even gonna bother with these for the following symptoms. typed em out, co
/datum/symptom/EMP/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["stealth"] >= 2) //if you combine this with pituitary disruption, you have the two most downside-heavy symptoms available
+ if(A.stealth >= 2) //if you combine this with pituitary disruption, you have the two most downside-heavy symptoms available
severity -= 1
- if(A.properties["transmittable"] >= 8)
+ if(A.transmission >= 8)
severity += 1
/datum/symptom/EMP/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["stealth"] >= 2)
+ if(A.stealth >= 2)
cellheal = TRUE
- if(A.properties["transmittable"] >= 8)
+ if(A.transmission >= 8)
bigemp = TRUE
/datum/symptom/EMP/Activate(datum/disease/advance/A)
@@ -313,15 +324,17 @@ im not even gonna bother with these for the following symptoms. typed em out, co
/datum/symptom/sweat
name = "Hyperperspiration"
- desc = "Causes the host to sweat profusely, leaving small water puddles and extnguishing small fires"
+ desc = "Causes the host to sweat profusely, leaving small water puddles and extinguishing small fires"
stealth = 1
resistance = -1
stage_speed = 0
- transmittable = 1
+ transmission = 1
level = 6
severity = 1
symptom_delay_min = 10
symptom_delay_max = 30
+ prefixes = list("Sweaty ", "Moist ", "Mister ")
+ bodies = list("Perspiration")
var/bigsweat = FALSE
var/toxheal = FALSE
var/ammonia = FALSE
@@ -331,17 +344,17 @@ im not even gonna bother with these for the following symptoms. typed em out, co
/datum/symptom/sweat/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["transmittable"] >= 6)
+ if(A.transmission >= 6)
severity -= 1
/datum/symptom/sweat/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["transmittable"] >= 6)
+ if(A.transmission >= 6)
toxheal = TRUE
- if(A.properties["transmittable"] >= 4)
+ if(A.transmission >= 4)
bigsweat = TRUE
- if(A.properties["stage_rate"] >= 6)
+ if(A.stage_rate >= 6)
ammonia = TRUE
/datum/symptom/sweat/Activate(datum/disease/advance/A)
@@ -378,7 +391,7 @@ im not even gonna bother with these for the following symptoms. typed em out, co
create_reagents(1000)
reagents.add_reagent(/datum/reagent/water, 10)
-obj/effect/sweatsplash/proc/splash()
+/obj/effect/sweatsplash/proc/splash()
chem_splash(loc, 2, list(reagents))
qdel(src)
@@ -388,11 +401,13 @@ obj/effect/sweatsplash/proc/splash()
stealth = 1
resistance = 2
stage_speed = -2
- transmittable = -3
+ transmission = -3
level = 8
severity = 0
symptom_delay_min = 1
symptom_delay_max = 1
+ prefixes = list("Quantum ", "Thermal ")
+ bodies = list("Teleport")
var/telethreshold = 15
var/burnheal = FALSE
var/turf/open/location_return = null
@@ -402,17 +417,17 @@ obj/effect/sweatsplash/proc/splash()
/datum/symptom/teleport/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["resistance"] >= 6)
+ if(A.resistance >= 6)
severity -= 1
- if(A.properties["transmittable"] >= 8)
+ if(A.transmission >= 8)
severity -= 1
/datum/symptom/teleport/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["resistance"] >= 6)
+ if(A.resistance >= 6)
burnheal = TRUE
- if(A.properties["transmittable"] >= 8)
+ if(A.transmission >= 8)
telethreshold = -10
power = 2
@@ -423,14 +438,14 @@ obj/effect/sweatsplash/proc/splash()
switch(A.stage)
if(4, 5)
if(burnheal)
- M.heal_overall_damage(0, 1) //no required_status checks here, this does all bodyparts equally
+ M.heal_overall_damage(0, 1.5) //no required_status checks here, this does all bodyparts equally
if(prob(5) && (M.bodytemperature < BODYTEMP_HEAT_DAMAGE_LIMIT || M.bodytemperature > BODYTEMP_COLD_DAMAGE_LIMIT))
location_return = get_turf(M) //sets up return point
if(prob(50))
to_chat(M, "The lukewarm temperature makes you feel strange!")
if(cooldowntimer == 0 && ((M.bodytemperature > BODYTEMP_HEAT_DAMAGE_LIMIT + telethreshold && !HAS_TRAIT(M, TRAIT_RESISTHEAT)) || (M.bodytemperature < BODYTEMP_COLD_DAMAGE_LIMIT - telethreshold && !HAS_TRAIT(M, TRAIT_RESISTCOLD)) || (burnheal && M.getFireLoss() > 60 + telethreshold)))
do_sparks(5,FALSE,M)
- to_chat(M, "The change in temperature shocks you back to a previous spacial state!")
+ to_chat(M, "The change in temperature shocks you back to a previous spatial state!")
do_teleport(M, location_return, 0, asoundin = 'sound/effects/phasein.ogg') //Teleports home
do_sparks(5,FALSE,M)
cooldowntimer = 10
@@ -449,11 +464,13 @@ obj/effect/sweatsplash/proc/splash()
stealth = -3
resistance = -2
stage_speed = 1
- transmittable = -2
+ transmission = -2
level = 7
severity = 1
symptom_delay_min = 1
symptom_delay_max = 1
+ prefixes = list("Blood ", "Meat ", "Flesh ")
+ bodies = list("Giant")
var/current_size = 1
var/tetsuo = FALSE
var/bruteheal = FALSE
@@ -464,22 +481,22 @@ obj/effect/sweatsplash/proc/splash()
/datum/symptom/growth/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["stage_rate"] >= 6)
+ if(A.stage_rate >= 6)
severity -= 1
- if(A.properties["stage_rate"] >= 12)
- severity += 3
+ if(A.stage_rate >= 12)
+ severity += 3
/datum/symptom/growth/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["stage_rate"] >= 6)
+ if(A.stage_rate >= 6)
bruteheal = TRUE
- if(A.properties["stage_rate"] >= 12)
- tetsuo = TRUE
- power = 3 //should make this symptom actually worth it
+ if(A.stage_rate >= 12)
+ tetsuo = TRUE
+ power = 3 //should make this symptom actually worth it
var/mob/living/carbon/M = A.affected_mob
ownermind = M.mind
- sizemult = CLAMP((0.5 + A.properties["stage_rate"] / 10), 1.1, 2.5)
+ sizemult = CLAMP((0.5 + A.stage_rate / 10), 1.1, 2.5)
M.resize = sizemult
M.update_transform()
@@ -491,16 +508,22 @@ obj/effect/sweatsplash/proc/splash()
if(4, 5)
if(prob(5) && bruteheal)
to_chat(M, "You retch, and a splatter of gore escapes your gullet!")
- M.Knockdown(10)
+ M.Immobilize(5)
new /obj/effect/decal/cleanable/blood/(M.loc)
playsound(get_turf(M), 'sound/effects/splat.ogg', 50, 1)
if(prob(60))
+ if(tetsuo && prob(15))
+ if(A.affected_mob.job == "Clown")
+ new /obj/effect/spawner/lootdrop/teratoma/major/clown(M.loc)
+ if(MOB_ROBOTIC in A.infectable_biotypes)
+ new /obj/effect/decal/cleanable/robot_debris(M.loc)
+ new /obj/effect/spawner/lootdrop/teratoma/robot(M.loc)
new /obj/effect/spawner/lootdrop/teratoma/minor(M.loc)
if(tetsuo)
var/list/organcantidates = list()
var/list/missing = M.get_missing_limbs()
if(prob(35))
- new /obj/effect/gibspawner/human/bodypartless(M.loc) //yes. this is very messy. very, very messy.
+ new /obj/effect/decal/cleanable/blood/gibs(M.loc) //yes. this is very messy. very, very messy.
new /obj/effect/spawner/lootdrop/teratoma/major(M.loc)
for(var/obj/item/organ/O in M.loc)
if(O.organ_flags & ORGAN_FAILING || O.organ_flags & ORGAN_VITAL) //dont use shitty organs or brains
@@ -510,6 +533,10 @@ obj/effect/sweatsplash/proc/splash()
organcantidates += O
continue
organcantidates += O
+ if(ishuman(M))
+ var/mob/living/carbon/human/H = M //To view species
+ if(!is_species(H, /datum/species/plasmaman))
+ O -= /obj/item/organ/lungs/plasmaman //So this disease doesn't eventually kill everyone with lungs
if(organcantidates.len)
for(var/I in 1 to min(rand(1, 3), organcantidates.len))
var/obj/item/organ/chosen = pick_n_take(organcantidates)
@@ -540,8 +567,6 @@ obj/effect/sweatsplash/proc/splash()
ownermind.transfer_to(M)
M.grab_ghost()
break
- if(tetsuo && prob(10) && A.affected_mob.job == "Clown")
- new /obj/effect/spawner/lootdrop/teratoma/major/clown(M.loc)
if(bruteheal)
M.heal_overall_damage(2 * power, required_status = BODYPART_ORGANIC)
if(prob(11 * power))
@@ -559,6 +584,7 @@ obj/effect/sweatsplash/proc/splash()
animate(pixel_z = 0, transform = turn(matrix(), pick(-12, 0, 12)), time=2) //waddle desizing is an issue, because you can game it to use this symptom and become small
animate(pixel_z = 0, transform = matrix(), time = 0) //so, instead, we use waddle desizing to desize you from this symptom, instead of a transformation, because it wont shrink you naturally
+//they are used for the maintenance spawn, for ling teratoma see changeling\teratoma.dm
/obj/effect/mob_spawn/teratomamonkey //spawning these is one of the downsides of overclocking the symptom
name = "fleshy mass"
desc = "A writhing mass of flesh."
@@ -567,15 +593,16 @@ obj/effect/sweatsplash/proc/splash()
density = FALSE
anchored = FALSE
+ antagonist_type = /datum/antagonist/teratoma/hugbox
mob_type = /mob/living/carbon/monkey/tumor
mob_name = "a living tumor"
death = FALSE
roundstart = FALSE
- short_desc = "You are a living tumor. By all accounts, you should not exist."
- flavour_text = {"
- You are a living teratoma, and your existence is misery. You feel the need to spread woe about the station- but not to kill.
- "}
use_cooldown = TRUE
+ show_flavour = FALSE //it's handled by antag datum
+ short_desc = "You are a living tumor. By all accounts you should not exist."
+ flavour_text = "Spread misery and chaos upon the station."
+ important_info = "Avoid killing unprovoked, kill only in self defense!"
/obj/effect/mob_spawn/teratomamonkey/Initialize()
. = ..()
diff --git a/code/datums/diseases/advance/symptoms/heartattack.dm b/code/datums/diseases/advance/symptoms/heartattack.dm
index a9645c142cf3b..f798bb473f0eb 100644
--- a/code/datums/diseases/advance/symptoms/heartattack.dm
+++ b/code/datums/diseases/advance/symptoms/heartattack.dm
@@ -21,26 +21,28 @@ Bonus
stealth = 2
resistance = 1
stage_speed = -6
- transmittable = -2
+ transmission = -2
level = 9
severity = 5
symptom_delay_min = 1
symptom_delay_max = 1
- var/heartattack = FALSE
+ prefixes = list("Cardiac ", "Cardio")
+ bodies = list("Heart")
+ var/heartattack = FALSE
threshold_desc = "Transmission 10: When the victim has a heart attack, their heart will pop right out of their chest, and attack!. \
Stealth 2: The disease is somewhat less noticable to the host."
/datum/symptom/heartattack/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["transmittable"] >= 10)
+ if(A.transmission >= 10)
severity += 1
/datum/symptom/heartattack/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["transmittable"] >= 10)
+ if(A.transmission >= 10)
heartattack = TRUE
- if(A.properties["stealth"] >= 2)
+ if(A.stealth >= 2)
suppress_warning = TRUE
/datum/symptom/heartattack/Activate(datum/disease/advance/A)
@@ -48,7 +50,7 @@ Bonus
return
var/mob/living/carbon/M = A.affected_mob
if(suppress_warning && M.can_heartattack())
- if(prob(2))
+ if(prob(2))
to_chat(M, "[pick("Your chest aches.", "You need to sit down.", "You feel out of breath.")]")
else if(prob(2) && M.can_heartattack())
to_chat(M, "[pick("Your chest hurts!.", "You feel like your heart skipped a beat!")]")
@@ -67,5 +69,5 @@ Bonus
qdel(heart)
to_chat(M, "Your heart bursts out of your chest! It looks furious!")
new /mob/living/simple_animal/hostile/heart(M.loc)
-
-
+
+
diff --git a/code/datums/diseases/advance/symptoms/itching.dm b/code/datums/diseases/advance/symptoms/itching.dm
index 6dfd1750d8787..dc29e45fcfd8c 100644
--- a/code/datums/diseases/advance/symptoms/itching.dm
+++ b/code/datums/diseases/advance/symptoms/itching.dm
@@ -23,11 +23,13 @@ BONUS
stealth = 0
resistance = 3
stage_speed = 3
- transmittable = 1
+ transmission = 1
level = 1
severity = 0
symptom_delay_min = 5
symptom_delay_max = 25
+ prefixes = list("Irritant ")
+ bodies = list("Itch")
var/scratch = FALSE
threshold_desc = "Transmission 6: Increases frequency of itching. \
Stage Speed 7: The host will scrath itself when itching, causing superficial damage."
@@ -35,10 +37,10 @@ BONUS
/datum/symptom/itching/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["transmittable"] >= 6) //itch more often
+ if(A.transmission >= 6) //itch more often
symptom_delay_min = 1
symptom_delay_max = 4
- if(A.properties["stage_rate"] >= 7) //scratch
+ if(A.stage_rate >= 7) //scratch
scratch = TRUE
/datum/symptom/itching/Activate(datum/disease/advance/A)
@@ -51,4 +53,4 @@ BONUS
var/can_scratch = scratch && !M.incapacitated() && get_location_accessible(M, picked_bodypart)
M.visible_message("[can_scratch ? "[M] scratches [M.p_their()] [bodypart.name]." : ""]", "Your [bodypart.name] itches. [can_scratch ? " You scratch it." : ""]")
if(can_scratch)
- bodypart.receive_damage(0.5)
\ No newline at end of file
+ bodypart.receive_damage(0.5)
diff --git a/code/datums/diseases/advance/symptoms/lubefeet.dm b/code/datums/diseases/advance/symptoms/lubefeet.dm
index 24e6e38154c56..c253600cb10e8 100644
--- a/code/datums/diseases/advance/symptoms/lubefeet.dm
+++ b/code/datums/diseases/advance/symptoms/lubefeet.dm
@@ -4,11 +4,13 @@
stealth = 0
resistance = 2
stage_speed = 5
- transmittable = -2
+ transmission = -2
level = 9
severity = 2
symptom_delay_min = 1
symptom_delay_max = 3
+ prefixes = list("Slippery ", "Lubricated ")
+ bodies = list("Foot", "Feet")
var/morelube = FALSE
var/clownshoes = TRUE
threshold_desc = "Transmission 10: The host sweats even more profusely, lubing almost every tile they walk over \
@@ -16,15 +18,15 @@
/datum/symptom/lubefeet/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["transmittable"] >= 10)
+ if(A.transmission >= 10)
severity += 1
/datum/symptom/lubefeet/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["transmittable"] >= 10)
+ if(A.transmission >= 10)
morelube = TRUE
- if(A.properties["resistance"] >= 14)
+ if(A.resistance >= 14)
clownshoes = TRUE
/datum/symptom/lubefeet/Activate(datum/disease/advance/A)
@@ -75,5 +77,5 @@
qdel(M.shoes)
var/obj/item/clothing/C = new /obj/item/clothing/shoes/clown_shoes(M)
ADD_TRAIT(C, TRAIT_NODROP, DISEASE_TRAIT)
- M.equip_to_slot_or_del(C, SLOT_SHOES)
+ M.equip_to_slot_or_del(C, ITEM_SLOT_FEET)
return
diff --git a/code/datums/diseases/advance/symptoms/macrophage.dm b/code/datums/diseases/advance/symptoms/macrophage.dm
index 1c39badae17f1..3125056ad8989 100644
--- a/code/datums/diseases/advance/symptoms/macrophage.dm
+++ b/code/datums/diseases/advance/symptoms/macrophage.dm
@@ -4,11 +4,13 @@
stealth = -4
resistance = 1
stage_speed = -2
- transmittable = 2
+ transmission = 2
level = 9
severity = 2
symptom_delay_min = 30
symptom_delay_max = 60
+ prefixes = list("Ambulant ", "Macro")
+ bodies = list("Phage")
var/gigagerms = FALSE
var/netspeed = 0
var/phagecounter = 10
@@ -21,14 +23,14 @@
/datum/symptom/macrophage/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["transmittable"] >= 10)
+ if(A.transmission >= 10)
severity += 2
/datum/symptom/macrophage/Start(datum/disease/advance/A)
if(!..())
return
- netspeed = max(1, (A.properties["stage_rate"]))
- if(A.properties["transmittable"] >= 10)
+ netspeed = max(1, A.stage_rate)
+ if(A.transmission >= 10)
gigagerms = TRUE
/datum/symptom/macrophage/Activate(datum/disease/advance/A)
@@ -42,7 +44,7 @@
M.visible_message("Lumps form on [M]'s skin!", \
"You cringe in pain as lumps form and move around on your skin!")
if(5)
- phagecounter -= max(2, A.properties["stage_rate"])
+ phagecounter -= max(2, A.stage_rate)
if(gigagerms && phagecounter <= 0) //only ever spawn one big germ
Burst(A, M, TRUE)
phagecounter += 10
@@ -54,25 +56,24 @@
var/mob/living/simple_animal/hostile/macrophage/phage
if(gigagerms)
phage = new /mob/living/simple_animal/hostile/macrophage/aggro(M.loc)
- phage.melee_damage = max(5, A.properties["resistance"])
+ phage.melee_damage = max(5, A.resistance)
M.apply_damage(rand(10, 20))
playsound(M, 'sound/effects/splat.ogg', 50, 1)
M.emote("scream")
else
phage = new(M.loc)
M.apply_damage(rand(1, 7))
- phage.health += A.properties["resistance"]
- phage.maxHealth += A.properties["resistance"]
+ phage.health += A.resistance
+ phage.maxHealth += A.resistance
phage.infections += A
phage.basedisease = A
- if(A.properties["transmittable"] >= 12)
+ if(A.transmission >= 12)
for(var/datum/disease/D in M.diseases)
if((D.spread_flags & DISEASE_SPREAD_SPECIAL) || (D.spread_flags & DISEASE_SPREAD_NON_CONTAGIOUS) || (D.spread_flags & DISEASE_SPREAD_FALTERED))
continue
if(D == A)
continue
phage.infections += D
- phage.InitializeSkin()
M.visible_message("A strange creature bursts out of [M]!", \
"A slimy creature bursts forth from your flesh!")
addtimer(CALLBACK(phage, /mob/living/simple_animal/hostile/macrophage.proc/shrivel), 3000)
diff --git a/code/datums/diseases/advance/symptoms/nanites.dm b/code/datums/diseases/advance/symptoms/nanites.dm
index 87c4f4f73cf97..39328231454cb 100644
--- a/code/datums/diseases/advance/symptoms/nanites.dm
+++ b/code/datums/diseases/advance/symptoms/nanites.dm
@@ -4,11 +4,13 @@
stealth = 0
resistance = 2
stage_speed = 2
- transmittable = -1
+ transmission = -1
level = 7
severity = 0
symptom_delay_min = 1
symptom_delay_max = 1
+ prefixes = list("Nano-")
+ suffixes = list(" Silicophilia")
var/reverse_boost = FALSE
threshold_desc = "Transmission 5: Increases the virus' growth rate while nanites are present. \
Stage Speed 7: Increases the replication boost."
@@ -16,9 +18,9 @@
/datum/symptom/nano_boost/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["transmittable"] >= 5) //reverse boost
+ if(A.transmission >= 5) //reverse boost
reverse_boost = TRUE
- if(A.properties["stage_rate"] >= 7) //more nanites
+ if(A.stage_rate >= 7) //more nanites
power = 2
/datum/symptom/nano_boost/Activate(datum/disease/advance/A)
@@ -36,18 +38,20 @@
NP.software_error(rand(3, 4)) //activate, deactivate, or trigger the nanites
if(A.stage >= 4)
M.heal_overall_damage((0.5 * power), (0.5 * power), required_status = BODYPART_ROBOTIC)
-
+
/datum/symptom/nano_destroy
name = "Silicolysis"
desc = "The virus reacts to nanites in the host's bloodstream by attacking and consuming them. May also cause nanites to go haywire. Damages the host's mechanical limbs"
stealth = 0
resistance = 4
stage_speed = -1
- transmittable = 1
+ transmission = 1
level = 7
severity = 0
symptom_delay_min = 1
symptom_delay_max = 1
+ prefixes = list("Nano-")
+ suffixes = list(" Silicophobia")
var/reverse_boost = FALSE
threshold_desc = "Stage Speed 5: Increases the virus' growth rate while nanites are present. \
Resistance 7: Severely increases the rate at which the nanites are destroyed."
@@ -55,9 +59,9 @@
/datum/symptom/nano_destroy/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["stage_rate"] >= 5) //reverse boost
+ if(A.stage_rate >= 5) //reverse boost
reverse_boost = TRUE
- if(A.properties["resistance"] >= 7) //more nanites
+ if(A.resistance >= 7) //more nanites
power = 3
/datum/symptom/nano_destroy/Activate(datum/disease/advance/A)
@@ -76,4 +80,4 @@
else if(prob(2))
NP.software_error()
if(A.stage >= 4)
- M.take_overall_damage((1 * power), required_status = BODYPART_ROBOTIC)
\ No newline at end of file
+ M.take_overall_damage((1 * power), required_status = BODYPART_ROBOTIC)
diff --git a/code/datums/diseases/advance/symptoms/narcolepsy.dm b/code/datums/diseases/advance/symptoms/narcolepsy.dm
index 42d939736a8d5..ae56ee278b21a 100644
--- a/code/datums/diseases/advance/symptoms/narcolepsy.dm
+++ b/code/datums/diseases/advance/symptoms/narcolepsy.dm
@@ -18,11 +18,13 @@ Bonus
stealth = 1
resistance = -2
stage_speed = -3
- transmittable = -2
+ transmission = -2
level = 6
symptom_delay_min = 10
symptom_delay_max = 30
severity = 2
+ prefixes = list("Lazy ", "Yawning ")
+ bodies = list("Sleep")
var/sleep_level = 0
var/sleepy_ticks = 0
var/stamina = FALSE
@@ -31,15 +33,15 @@ Bonus
/datum/symptom/narcolepsy/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["resistance"] >= 10) //act more often
+ if(A.resistance >= 10) //act more often
severity += 1
/datum/symptom/narcolepsy/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["transmittable"] >= 7) //stamina damage
+ if(A.transmission >= 7) //stamina damage
stamina = TRUE
- if(A.properties["resistance"] >= 10) //act more often
+ if(A.resistance >= 10) //act more often
symptom_delay_min = 5
symptom_delay_max = 20
diff --git a/code/datums/diseases/advance/symptoms/necropolis.dm b/code/datums/diseases/advance/symptoms/necropolis.dm
index 92143539f7cd0..d8828f47d13f2 100644
--- a/code/datums/diseases/advance/symptoms/necropolis.dm
+++ b/code/datums/diseases/advance/symptoms/necropolis.dm
@@ -4,7 +4,7 @@
stealth = 0
resistance = 3
stage_speed = -10
- transmittable = -3
+ transmission = -3
level = 9
base_message_chance = 5
severity = -1
@@ -13,6 +13,8 @@
var/tendrils = FALSE
var/chest = FALSE
var/fireproof = FALSE
+ prefixes = list("Lava ", "Lavaland ", "Eldritch ")
+ bodies = list("Goliath", "Tentacle", "Carapace")
threshold_desc = "Stealth 8: Upon death, the host's soul will solidify into an unholy artifact, rendering them utterly unrevivable in the process. \
Resistance 15: The area near the host roils with paralyzing tendrils. \
Resistance 20: Host becomes immune to heat, ash, and lava"
@@ -22,20 +24,21 @@
/datum/symptom/necroseed/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["stealth"] >= 8)
+ if(A.stealth >= 8)
severity += 2
- if(A.properties["resistance"] >= 20)
+ if(A.resistance >= 20)
severity -= 1
/datum/symptom/necroseed/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["resistance"] >= 15)
+ if(A.resistance >= 15)
tendrils = TRUE
- if(A.properties["stealth"] >= 8)
+ if(A.resistance >= 20)
+ fireproof = TRUE
+ if(A.stealth >= 8)
chest = TRUE
- if(A.properties["resistance"] >= 20)
- fireproof = TRUE
+
/datum/symptom/necroseed/Activate(datum/disease/advance/A)
if(!..())
@@ -77,7 +80,7 @@
LAZYCLEARLIST(cached_tentacle_turfs)
last_location = loc
tentacle_recheck_cooldown = world.time + initial(tentacle_recheck_cooldown)
- for(var/turf/open/T in orange(2, loc))
+ for(var/turf/open/T in (RANGE_TURFS(1, loc)-loc))
LAZYADD(cached_tentacle_turfs, T)
for(var/t in cached_tentacle_turfs)
if(isopenturf(t))
@@ -107,18 +110,13 @@
if(!..())
return
var/mob/living/M = A.affected_mob
- if(chest && A.stage == 5 && M.mind)
+ if(chest && A.stage >= 5 && M.mind)
to_chat(M, "Your soul is ripped from your body!")
M.visible_message("An unearthly roar shakes the ground as [M] explodes into a shower of gore, leaving behind an ominous, fleshy chest.")
playsound(M.loc,'sound/effects/tendril_destroyed.ogg', 200, 0, 50, 1, 1)
M.hellbound = TRUE
- M.gib()
- if(ishuman(M)) //We don't NEED them to be human. However, I want to avoid people making teratoma-farms for necrochests
- var/mob/living/carbon/human/H = M
- var/S = H.dna.species
- if(istype(S, /datum/species/golem) || istype(S, /datum/species/jelly)) //nope. sorry, xenobio.
- return
- else
+ addtimer(CALLBACK(M, /mob/proc/gib), 0.5 SECONDS) //we can't gib mob while it's already dying
+ if(!ishuman(M) || HAS_TRAIT(M, TRAIT_NONECRODISEASE)) //We don't NEED them to be human. However, I want to avoid people making teratoma-farms for necrochests
return
new /obj/structure/closet/crate/necropolis/tendril(M.loc)
diff --git a/code/datums/diseases/advance/symptoms/oxygen.dm b/code/datums/diseases/advance/symptoms/oxygen.dm
index fa19acfd0e6bf..009b55ab8a1e0 100644
--- a/code/datums/diseases/advance/symptoms/oxygen.dm
+++ b/code/datums/diseases/advance/symptoms/oxygen.dm
@@ -22,14 +22,15 @@ Bonus
stealth = 1
resistance = -3
stage_speed = -3
- transmittable = -4
+ transmission = -4
severity = -1
level = 6
base_message_chance = 5
symptom_delay_min = 1
symptom_delay_max = 1
+ prefixes = list("Breathless ", "Anaerobic ")
var/regenerate_blood = FALSE
- var/gas_type = /datum/gas/miasma
+ var/gas_type = GAS_MIASMA
var/base_moles = 3
var/emote = "fart"
threshold_desc = "Resistance 8: Additionally regenerates lost blood. "
@@ -37,7 +38,7 @@ Bonus
/datum/symptom/oxygen/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["resistance"] >= 8) //blood regeneration
+ if(A.resistance >= 8) //blood regeneration
regenerate_blood = TRUE
/datum/symptom/oxygen/Activate(datum/disease/advance/A)
@@ -50,7 +51,7 @@ Bonus
M.adjustOxyLoss(-7, 0)
M.losebreath = max(0, M.losebreath - 4)
if(regenerate_blood && M.blood_volume < BLOOD_VOLUME_NORMAL)
- M.blood_volume += 1
+ M.blood_volume += 8 //it takes 4 seconds to lose one point of bleed_rate. this is exactly sufficient to counter autophageocytosis' Heparin production. Theoretically.
if(prob(1) && prob(50))
var/turf/open/T = get_turf(M)
if(!istype(T))
@@ -61,7 +62,7 @@ Bonus
M.emote(emote)
else
if(prob(base_message_chance))
- to_chat(M, "[pick("Your lungs feel great.", "You realize you haven't been breathing.", "You don't feel the need to breathe.", "Something smells rotten", "You feel peckish")]")
+ to_chat(M, "[pick("Your lungs feel great.", "You realize you haven't been breathing.", "You don't feel the need to breathe.", "Something smells rotten.", "You feel peckish.")]")
return
/datum/symptom/oxygen/on_stage_change(new_stage, datum/disease/advance/A)
diff --git a/code/datums/diseases/advance/symptoms/pierrot.dm b/code/datums/diseases/advance/symptoms/pierrot.dm
index fc59f983f0884..431774bfce6c7 100644
--- a/code/datums/diseases/advance/symptoms/pierrot.dm
+++ b/code/datums/diseases/advance/symptoms/pierrot.dm
@@ -4,7 +4,7 @@
stealth = -1
resistance = 3
stage_speed = 1
- transmittable = 2
+ transmission = 2
level = 0
severity = 0
symptom_delay_min = 2
@@ -18,20 +18,22 @@
/datum/symptom/pierrot/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["resistance"] >= 10)
+ bodies = list("Clown", "Red-Nose", "[pick(GLOB.clown_names)]") //added here because it doesnt wanna pick in base vars
+ prefixes = list("Fool's ", "[pick(GLOB.clown_names)]'s ")
+ if(A.resistance >= 10)
severity +=1
- if(A.properties["resistance"] >= 15)
- severity += 2
+ if(A.resistance >= 15)
+ severity += 2
/datum/symptom/pierrot/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["transmission"] >= 10)
+ if(A.transmission >= 10)
honkspread = TRUE
- if(A.properties["resistance"] >= 10)
+ if(A.resistance >= 10)
clownmask = TRUE
- if(A.properties["resistance"] >= 15)
- clumsy = TRUE
+ if(A.resistance >= 15)
+ clumsy = TRUE
/datum/symptom/pierrot/Activate(datum/disease/advance/A)
if(!..())
@@ -72,15 +74,15 @@
var/mob/living/carbon/human/M = A.affected_mob
if(istype(M.wear_mask, /obj/item/clothing/mask/gas/clown_hat))
REMOVE_TRAIT(M.wear_mask, TRAIT_NODROP, DISEASE_TRAIT)
-
+
/datum/symptom/pierrot/proc/give_clown_mask(datum/disease/advance/A)
if(ishuman(A.affected_mob))
- var/mob/living/carbon/human/M = A.affected_mob
+ var/mob/living/carbon/human/M = A.affected_mob
if(!istype(M.wear_mask, /obj/item/clothing/mask/gas/clown_hat))
if(!M.dropItemToGround(M.wear_mask))
qdel(M.wear_mask)
var/obj/item/clothing/C = new /obj/item/clothing/mask/gas/clown_hat(M)
ADD_TRAIT(C, TRAIT_NODROP, DISEASE_TRAIT)
- M.equip_to_slot_or_del(C, SLOT_WEAR_MASK)
+ M.equip_to_slot_or_del(C, ITEM_SLOT_MASK)
return
diff --git a/code/datums/diseases/advance/symptoms/radiation.dm b/code/datums/diseases/advance/symptoms/radiation.dm
index c72b460b01816..76c0a11bb7ce9 100644
--- a/code/datums/diseases/advance/symptoms/radiation.dm
+++ b/code/datums/diseases/advance/symptoms/radiation.dm
@@ -4,24 +4,26 @@
stealth = -1
resistance = 2
stage_speed = -1
- transmittable = 2
+ transmission = 2
level = 8
severity = 3
symptom_delay_min = 10
symptom_delay_max = 40
+ prefixes = list("Gamma ")
+ bodies = list("Radiation")
var/fastrads = FALSE
var/radothers = FALSE
threshold_desc = "Speed 8: Host takes radiation damage faster."
/datum/symptom/radiation/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["stage_rate"] >= 8)
+ if(A.stage_rate >= 8)
severity += 1
/datum/symptom/radiation/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["stage_rate"] >= 8)
+ if(A.stage_rate >= 8)
fastrads = TRUE
/datum/symptom/radiation/Activate(datum/disease/advance/A)
diff --git a/code/datums/diseases/advance/symptoms/sensory.dm b/code/datums/diseases/advance/symptoms/sensory.dm
index 7c58044f48b22..d7547d79bd1b7 100644
--- a/code/datums/diseases/advance/symptoms/sensory.dm
+++ b/code/datums/diseases/advance/symptoms/sensory.dm
@@ -4,11 +4,12 @@
stealth = -1
resistance = -2
stage_speed = 1
- transmittable = -3
+ transmission = -3
level = 5
severity = -1
symptom_delay_min = 5
symptom_delay_max = 10
+ bodies = list("Neuron")
var/purge_alcohol = FALSE
var/trauma_heal_mild = FALSE
var/trauma_heal_severe = FALSE
@@ -19,11 +20,11 @@
/datum/symptom/mind_restoration/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["resistance"] >= 6) //heal brain damage
+ if(A.resistance >= 6) //heal brain damage
trauma_heal_mild = TRUE
- if(A.properties["resistance"] >= 9) //heal severe traumas
- trauma_heal_severe = TRUE
- if(A.properties["transmittable"] >= 8) //purge alcohol
+ if(A.resistance >= 9) //heal severe traumas
+ trauma_heal_severe = TRUE
+ if(A.transmission >= 8) //purge alcohol
purge_alcohol = TRUE
/datum/symptom/mind_restoration/Activate(var/datum/disease/advance/A)
@@ -69,7 +70,7 @@
stealth = 0
resistance = 1
stage_speed = -2
- transmittable = 2
+ transmission = 2
level = 4
severity = -1
base_message_chance = 7
@@ -106,4 +107,4 @@
eyes.applyOrganDamage(-1)
else
if(prob(base_message_chance))
- to_chat(M, "[pick("Your eyes feel great.","You feel like your eyes can focus more clearly.", "You don't feel the need to blink.","Your ears feel great.","Your healing feels more acute.")]")
\ No newline at end of file
+ to_chat(M, "[pick("Your eyes feel great.","You feel like your eyes can focus more clearly.", "You don't feel the need to blink.","Your ears feel great.","Your healing feels more acute.")]")
diff --git a/code/datums/diseases/advance/symptoms/shedding.dm b/code/datums/diseases/advance/symptoms/shedding.dm
index 2f0ec1422a429..9a58c4b830c10 100644
--- a/code/datums/diseases/advance/symptoms/shedding.dm
+++ b/code/datums/diseases/advance/symptoms/shedding.dm
@@ -20,12 +20,13 @@ BONUS
stealth = 0
resistance = 3
stage_speed = 2
- transmittable = 2
+ transmission = 2
level = 4
severity = 0
base_message_chance = 50
symptom_delay_min = 45
symptom_delay_max = 90
+ bodies = list("Bald", "Scalp")
/datum/symptom/shedding/Activate(datum/disease/advance/A)
if(!..())
diff --git a/code/datums/diseases/advance/symptoms/shivering.dm b/code/datums/diseases/advance/symptoms/shivering.dm
index e546d46403690..895f1d2e73070 100644
--- a/code/datums/diseases/advance/symptoms/shivering.dm
+++ b/code/datums/diseases/advance/symptoms/shivering.dm
@@ -21,30 +21,32 @@ Bonus
stealth = 0
resistance = 2
stage_speed = 2
- transmittable = 2
+ transmission = 2
level = 2
severity = 0
symptom_delay_min = 10
symptom_delay_max = 30
+ bodies = list("Shiver")
+ suffixes = list(" Shivers")
var/unsafe = FALSE //over the cold threshold
threshold_desc = "Stage Speed 5: Increases cooling speed; the host can fall below safe temperature levels. \
Stage Speed 10: Further increases cooling speed."
/datum/symptom/shivering/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["stage_rate"] >= 5) //dangerous cold
- severity += 1
- if(A.properties["stage_rate"] >= 10)
+ if(A.stage_rate >= 5) //dangerous cold
severity += 1
+ if(A.stage_rate >= 10)
+ severity += 1
/datum/symptom/shivering/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["stage_rate"] >= 5) //dangerous cold
+ if(A.stage_rate >= 5) //dangerous cold
power = 1.5
unsafe = TRUE
- if(A.properties["stage_rate"] >= 10)
- power = 2.5
+ if(A.stage_rate >= 10)
+ power = 2.5
/datum/symptom/shivering/Activate(datum/disease/advance/A)
if(!..())
@@ -63,4 +65,4 @@ Bonus
if(unsafe)
limit = 0
M.adjust_bodytemperature(-get_cold * A.stage, limit)
- return 1
\ No newline at end of file
+ return 1
diff --git a/code/datums/diseases/advance/symptoms/skin.dm b/code/datums/diseases/advance/symptoms/skin.dm
index 2a9cd4c3792f9..ff3e7310e7e46 100644
--- a/code/datums/diseases/advance/symptoms/skin.dm
+++ b/code/datums/diseases/advance/symptoms/skin.dm
@@ -21,11 +21,13 @@ BONUS
stealth = 2
resistance = 0
stage_speed = 3
- transmittable = 1
+ transmission = 1
level = 5
severity = 0
symptom_delay_min = 25
symptom_delay_max = 75
+ prefixes = list("White ", "Light ")
+ bodies = list("Albinism")
var/cachedcolor = null
/datum/symptom/vitiligo/Start(datum/disease/advance/A)
@@ -91,11 +93,13 @@ BONUS
stealth = 1
resistance = 2
stage_speed = 1
- transmittable = 2
+ transmission = 2
level = 5
severity = 0
symptom_delay_min = 7
symptom_delay_max = 14
+ prefixes = list("Black ", "Dark ")
+ bodies = list("Melanism")
var/cachedcolor = null
/datum/symptom/revitiligo/Start(datum/disease/advance/A)
@@ -161,12 +165,14 @@ BONUS
stealth = 0
resistance = 1
stage_speed = 4
- transmittable = 1
+ transmission = 1
level = 0
severity = 0
base_message_chance = 50
symptom_delay_min = 45
symptom_delay_max = 90
+ prefixes = list("Rainbow ", "Chromatic ")
+ bodies = list("Pigment")
/datum/symptom/polyvitiligo/Activate(datum/disease/advance/A)
if(!..())
@@ -181,3 +187,149 @@ BONUS
else
if (prob(50)) // spam
M.visible_message("[M] looks rather vibrant.", "The colors, man, the colors.")
+
+/************************************
+Dermagraphic Ovulogenesis
+
+ Extremely Noticeable
+ Increases resistance slightly.
+ Not Fast, Not Slow
+ Transmittable.
+ High Level
+
+BONUS
+ Provides Brute Healing when Egg Sacs/Eggs are eaten, simultaneously infecting anyone who eats them
+
+***********************************/
+/datum/symptom/skineggs //Thought Exolocomotive Xenomitosis was a weird symptom? Well, this is about 10x weirder.
+ name = "Dermagraphic Ovulogenesis"
+ desc = "The virus causes the host to grow egg-like nodules on their skin, which periodically fall off and contain the disease and some healing chemicals."
+ stealth = -3 //You are basically growing these weird Egg shits on your skin, this is not stealthy in the slightest
+ resistance = 1
+ stage_speed = 0
+ transmission = 2 //The symptom is in it of itself meant to spread
+ level = 9
+ severity = -1
+ base_message_chance = 50
+ symptom_delay_min = 60
+ symptom_delay_max = 105
+ prefixes = list("Ovi ")
+ bodies = list("Oviposition", "Nodule")
+ suffixes = list(" Mitosis")
+ var/big_heal
+ var/all_disease
+ var/eggsplosion
+ var/sneaky
+ threshold_desc = "Transmission 12: Eggs and Egg Sacs contain all diseases on the host, instead of just the disease containing the symptom. \
+ Transmission 16: Egg Sacs will 'explode' into eggs after a period of time, covering a larger area with infectious matter. \
+ Resistance 10: Eggs and Egg Sacs contain more healing chems. \
+ Stealth 6: Eggs and Egg Sacs become nearly transparent, making them more difficult to see. \
+ Stage Speed 10: Egg Sacs fall off the host more frequently."
+
+/datum/symptom/skineggs/severityset(datum/disease/advance/A)
+ . = ..()
+ if(A.resistance >= 10)
+ severity -= 1
+ if(A.transmission >= 12)
+ severity += 1
+ if(A.transmission >= 16)
+ severity += 1
+ if(A.stealth >= 6)
+ severity += 1
+
+/datum/symptom/skineggs/Start(datum/disease/advance/A)
+ if(!..())
+ return
+ if(A.resistance >= 10)
+ big_heal = TRUE
+ if(A.transmission >= 12)
+ all_disease = TRUE
+ if(A.transmission >= 16)
+ eggsplosion = TRUE //Haha get it?
+ if(A.stealth >= 6)
+ sneaky = TRUE
+ if(A.stage_rate >= 10)
+ symptom_delay_min -= 10
+ symptom_delay_max -= 20
+
+
+/datum/symptom/skineggs/Activate(datum/disease/advance/A)
+ if(!..())
+ return
+ var/mob/living/M = A.affected_mob
+ var/list/diseases = list(A)
+ switch(A.stage)
+ if(5)
+ if(all_disease)
+ for(var/datum/disease/D in M.diseases)
+ if((D.spread_flags & DISEASE_SPREAD_SPECIAL) || (D.spread_flags & DISEASE_SPREAD_NON_CONTAGIOUS) || (D.spread_flags & DISEASE_SPREAD_FALTERED))
+ continue
+ if(D == A)
+ continue
+ diseases += D
+ new /obj/item/reagent_containers/food/snacks/eggsac(M.loc, diseases, eggsplosion, sneaky, big_heal)
+
+#define EGGSPLODE_DELAY 100 SECONDS
+/obj/item/reagent_containers/food/snacks/eggsac
+ name = "Fleshy Egg Sac"
+ desc = "A small Egg Sac which appears to be made out of someone's flesh!"
+ customfoodfilling = FALSE //Not Used For Filling
+ icon = 'icons/obj/food/food.dmi'
+ icon_state = "eggsac"
+ bitesize = 4
+ var/list/diseases = list()
+ var/sneaky_egg
+ var/big_heal
+
+//Constructor
+/obj/item/reagent_containers/food/snacks/eggsac/New(loc, var/list/disease, var/eggsplodes, var/sneaky, var/large_heal)
+ ..()
+ for(var/datum/disease/D in disease)
+ diseases += D
+ if(large_heal)
+ reagents.add_reagent_list(list(/datum/reagent/medicine/bicaridine = 20, /datum/reagent/medicine/tricordrazine = 10))
+ reagents.add_reagent(/datum/reagent/blood, 10, diseases)
+ big_heal = TRUE
+ else
+ reagents.add_reagent_list(list(/datum/reagent/medicine/bicaridine = 10, /datum/reagent/medicine/tricordrazine = 10))
+ reagents.add_reagent(/datum/reagent/blood, 15, diseases)
+ if(sneaky)
+ icon_state = "eggsac-sneaky"
+ sneaky_egg = sneaky
+ if(eggsplodes)
+ addtimer(CALLBACK(src, .proc/eggsplode), EGGSPLODE_DELAY)
+ if(LAZYLEN(diseases))
+ AddComponent(/datum/component/infective, diseases)
+
+#undef EGGSPLODE_DELAY
+
+/obj/item/reagent_containers/food/snacks/eggsac/proc/eggsplode()
+ for(var/i = 1, i <= rand(4,8), i++)
+ var/list/directions = GLOB.alldirs
+ var/obj/item/I = new /obj/item/reagent_containers/food/snacks/fleshegg(src.loc, diseases, sneaky_egg, big_heal)
+ var/turf/thrown_at = get_ranged_target_turf(I, pick(directions), rand(2, 4))
+ I.throw_at(thrown_at, rand(2,4), 4)
+
+/obj/item/reagent_containers/food/snacks/fleshegg
+ name = "Fleshy Egg"
+ desc = "An Egg which appears to be made out of someone's flesh!"
+ customfoodfilling = FALSE //Not Used For Filling
+ icon = 'icons/obj/food/food.dmi'
+ icon_state = "fleshegg"
+ bitesize = 1
+ var/list/diseases = list()
+
+/obj/item/reagent_containers/food/snacks/fleshegg/New(loc, var/list/disease, var/sneaky, var/large_heal)
+ ..()
+ for(var/datum/disease/D in disease)
+ diseases += D
+ if(large_heal)
+ reagents.add_reagent_list(list(/datum/reagent/medicine/bicaridine = 20, /datum/reagent/medicine/tricordrazine = 10))
+ reagents.add_reagent(/datum/reagent/blood, 10, diseases)
+ else
+ reagents.add_reagent_list(list(/datum/reagent/medicine/bicaridine = 10, /datum/reagent/medicine/tricordrazine = 10))
+ reagents.add_reagent(/datum/reagent/blood, 15, diseases)
+ if(sneaky)
+ icon_state = "fleshegg-sneaky"
+ if(LAZYLEN(diseases))
+ AddComponent(/datum/component/infective, diseases)
diff --git a/code/datums/diseases/advance/symptoms/sneeze.dm b/code/datums/diseases/advance/symptoms/sneeze.dm
index 4773b85386441..cbb12c3176f29 100644
--- a/code/datums/diseases/advance/symptoms/sneeze.dm
+++ b/code/datums/diseases/advance/symptoms/sneeze.dm
@@ -21,17 +21,19 @@ Bonus
stealth = -2
resistance = 3
stage_speed = 0
- transmittable = 4
+ transmission = 4
level = 1
severity = 0
symptom_delay_min = 5
symptom_delay_max = 35
+ prefixes = list("Nasal ")
+ bodies = list("Cold")
threshold_desc = "Stealth 4: The symptom remains hidden until active."
/datum/symptom/sneeze/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["stealth"] >= 4)
+ if(A.stealth >= 4)
suppress_warning = TRUE
/datum/symptom/sneeze/Activate(datum/disease/advance/A)
diff --git a/code/datums/diseases/advance/symptoms/species.dm b/code/datums/diseases/advance/symptoms/species.dm
index 13257f4018f7a..e1ce14f809d17 100644
--- a/code/datums/diseases/advance/symptoms/species.dm
+++ b/code/datums/diseases/advance/symptoms/species.dm
@@ -4,9 +4,10 @@
stealth = 2
resistance = 2
stage_speed = 2
- transmittable = 0
+ transmission = 0
level = 5
severity = 0
+ prefixes = list("Zombie ")
/datum/symptom/undead_adaptation/OnAdd(datum/disease/advance/A)
A.process_dead = TRUE
@@ -22,12 +23,14 @@
stealth = -1
resistance = 4
stage_speed = -2
- transmittable = 3
+ transmission = 3
level = 5
severity = 0
+ prefixes = list("Crystalline ")
+
/datum/symptom/inorganic_adaptation/OnAdd(datum/disease/advance/A)
A.infectable_biotypes |= MOB_INORGANIC
/datum/symptom/inorganic_adaptation/OnRemove(datum/disease/advance/A)
- A.infectable_biotypes -= MOB_INORGANIC
\ No newline at end of file
+ A.infectable_biotypes -= MOB_INORGANIC
diff --git a/code/datums/diseases/advance/symptoms/spiked.dm b/code/datums/diseases/advance/symptoms/spiked.dm
index 25cadd2ac86c5..f30a91169d121 100644
--- a/code/datums/diseases/advance/symptoms/spiked.dm
+++ b/code/datums/diseases/advance/symptoms/spiked.dm
@@ -19,12 +19,14 @@ Thresholds
stealth = -3
resistance = 3
stage_speed = -3
- transmittable = 0
+ transmission = 0
level = 0
symptom_delay_min = 1
symptom_delay_max = 1
severity = 1
base_message_chance = 5
+ prefixes = list("Thorny ", "Horned ")
+ bodies = list("Horn", "Spiked")
var/Power = 1
var/armor = 0
var/done = FALSE
@@ -33,15 +35,15 @@ Thresholds
/datum/symptom/spiked/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["resistance"] >= 6)
+ if(A.resistance >= 6)
severity -= 1
/datum/symptom/spiked/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["resistance"] >= 6) //armor. capped at 20, but scaling with resistance, so if you want to max out spiked skin armor, you'll have to make several sacrifices
- armor = min(20, A.properties["resistance"])
- if(A.properties["transmittable"] >= 6) //higher damage
+ if(A.resistance >= 6) //armor. capped at 20, but scaling with resistance, so if you want to max out spiked skin armor, you'll have to make several sacrifices
+ armor = min(20, A.resistance)
+ if(A.transmission >= 6) //higher damage
Power = 1.4 //the typical +100% is waaaay too strong here when the symptom is stacked. +40% is sufficient
/datum/symptom/spiked/Activate(var/datum/disease/advance/A)
@@ -69,7 +71,7 @@ Thresholds
C.apply_damage(1*power, BRUTE, blocked = def_check)
C.visible_message("[C.name] is pricked on [H.name]'s spikes.")
playsound(get_turf(C), 'sound/weapons/slice.ogg', 50, 1)
- for(var/mob/living/carbon/C in oview(1, H))
+ for(var/mob/living/carbon/C in ohearers(1, H))
if(C.pulling && C.pulling == H)
var/def_check = C.getarmor(type = "melee")
C.apply_damage(3*power, BRUTE, blocked = def_check)
diff --git a/code/datums/diseases/advance/symptoms/symptoms.dm b/code/datums/diseases/advance/symptoms/symptoms.dm
index 330efc860bc4c..f83b61769f93d 100644
--- a/code/datums/diseases/advance/symptoms/symptoms.dm
+++ b/code/datums/diseases/advance/symptoms/symptoms.dm
@@ -8,7 +8,7 @@
var/stealth = 0
var/resistance = 0
var/stage_speed = 0
- var/transmittable = 0
+ var/transmission = 0
// The type level of the symptom. Higher is harder to generate.
var/level = -1
// The severity level of the symptom. Higher is more dangerous.
@@ -29,6 +29,9 @@
var/neutered = FALSE
var/list/thresholds
var/naturally_occuring = TRUE //if this symptom can appear from /datum/disease/advance/GenerateSymptoms()
+ var/list/prefixes = list()
+ var/list/bodies = list()
+ var/list/suffixes = list()
/datum/symptom/New()
var/list/S = SSdisease.list_symptoms
diff --git a/code/datums/diseases/advance/symptoms/viral.dm b/code/datums/diseases/advance/symptoms/viral.dm
index 9fd9d5a03e497..a6d4a33d4980f 100644
--- a/code/datums/diseases/advance/symptoms/viral.dm
+++ b/code/datums/diseases/advance/symptoms/viral.dm
@@ -19,8 +19,9 @@ BONUS
stealth = 3
resistance = 5
stage_speed = -3
- transmittable = 0
+ transmission = 0
level = 3
+ prefixes = list("Chronic ")
/*
//////////////////////////////////////
@@ -44,8 +45,9 @@ BONUS
stealth = -2
resistance = -3
stage_speed = 5
- transmittable = 3
+ transmission = 3
level = 3
+ prefixes = list("Unstable ")
/*
//////////////////////////////////////
@@ -65,7 +67,7 @@ Bonus
//////////////////////////////////////
*/
-/datum/symptom/viralreverse
+/datum/symptom/viralreverse
name = "Viral aggressive metabolism"
desc = "The virus sacrifices its long term survivability to nearly instantly fully spread inside a host. \
@@ -73,14 +75,15 @@ Bonus
stealth = 1
resistance = -4
stage_speed = 3
- transmittable = -3
+ transmission = -3
level = 3
symptom_delay_min = 1
symptom_delay_max = 1
+ prefixes = list("Spontaneous ")
var/time_to_cure
threshold_desc = "Resistance/Stage Speed: Highest between these determines the amount of time before self-curing. \
- Stealth 4 Doubles the time before the virus self-cures"
-
+ Stealth 4 Doubles the time before the virus self-cures"
+
/datum/symptom/viralreverse/Activate(datum/disease/advance/A)
if(!..())
@@ -95,12 +98,12 @@ Bonus
A.stage -= 1
if(A.stage < 2)
to_chat(M, "You suddenly feel healthy.")
- A.cure()
+ A.cure(FALSE) //Doesn't Add Resistance. Virology can now make potions for stuff, be it healing the senses or making people explode
/datum/symptom/viralreverse/Start(datum/disease/advance/A)
if(!..())
return
A.stage = 5
- if(A.properties["stealth"] >= 4) //more time before it's cured
+ if(A.stealth >= 4) //more time before it's cured
power = 2
- time_to_cure = max(A.properties["resistance"], A.properties["stage_rate"]) * 10 * power
+ time_to_cure = max(A.resistance, A.stage_rate) * 10 * power
diff --git a/code/datums/diseases/advance/symptoms/vision.dm b/code/datums/diseases/advance/symptoms/vision.dm
index ffc639b860312..0777fcdd8e2eb 100644
--- a/code/datums/diseases/advance/symptoms/vision.dm
+++ b/code/datums/diseases/advance/symptoms/vision.dm
@@ -22,27 +22,30 @@ Bonus
stealth = -1
resistance = -3
stage_speed = -4
- transmittable = -2
+ transmission = -2
level = 5
severity = 3
base_message_chance = 50
symptom_delay_min = 25
symptom_delay_max = 80
+ prefixes = list("Eye ")
+ bodies = list("Blind")
+ suffixes = list(" Blindness")
var/remove_eyes = FALSE
threshold_desc = "Resistance 12: Weakens extraocular muscles, eventually leading to complete detachment of the eyes. \
Stealth 4: The symptom remains hidden until active."
/datum/symptom/visionloss/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["resistance"] >= 12) //goodbye eyes
+ if(A.resistance >= 12) //goodbye eyes
severity += 1
/datum/symptom/visionloss/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["stealth"] >= 4)
+ if(A.stealth >= 4)
suppress_warning = TRUE
- if(A.properties["resistance"] >= 12) //goodbye eyes
+ if(A.resistance >= 12) //goodbye eyes
remove_eyes = TRUE
/datum/symptom/visionloss/Activate(datum/disease/advance/A)
diff --git a/code/datums/diseases/advance/symptoms/voice_change.dm b/code/datums/diseases/advance/symptoms/voice_change.dm
index faf9de3678952..3d08e65e205b7 100644
--- a/code/datums/diseases/advance/symptoms/voice_change.dm
+++ b/code/datums/diseases/advance/symptoms/voice_change.dm
@@ -6,7 +6,7 @@ Voice Change
Noticeable.
Lowers resistance.
Decreases stage speed.
- Increased transmittable.
+ Increased transmission.
Fatal Level.
Bonus
@@ -22,12 +22,13 @@ Bonus
stealth = -1
resistance = -2
stage_speed = -2
- transmittable = 2
+ transmission = 2
level = 6
severity = 2
base_message_chance = 100
symptom_delay_min = 60
symptom_delay_max = 120
+ prefixes = list("Vocal ")
var/scramble_language = FALSE
var/datum/language/current_language
threshold_desc = "Transmission 14: The host's language center of the brain is damaged, leading to complete inability to speak or understand any language. \
@@ -36,19 +37,20 @@ Bonus
/datum/symptom/voice_change/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["transmittable"] >= 14) //random language
+ if(A.transmission >= 14) //random language
+ bodies += list("Polyglot")
severity += 1
/datum/symptom/voice_change/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["stealth"] >= 3)
+ if(A.stealth >= 3)
suppress_warning = TRUE
- if(A.properties["stage_rate"] >= 7) //faster change of voice
+ if(A.stage_rate >= 7) //faster change of voice
base_message_chance = 25
symptom_delay_min = 25
symptom_delay_max = 85
- if(A.properties["transmittable"] >= 14) //random language
+ if(A.transmission >= 14) //random language
scramble_language = TRUE
/datum/symptom/voice_change/Activate(datum/disease/advance/A)
diff --git a/code/datums/diseases/advance/symptoms/vomit.dm b/code/datums/diseases/advance/symptoms/vomit.dm
index 504b697e7d85d..0ed1b31d01db8 100644
--- a/code/datums/diseases/advance/symptoms/vomit.dm
+++ b/code/datums/diseases/advance/symptoms/vomit.dm
@@ -26,12 +26,15 @@ Bonus
stealth = -2
resistance = 1
stage_speed = 0
- transmittable = 2
+ transmission = 2
level = 3
severity = 1
base_message_chance = 100
symptom_delay_min = 25
symptom_delay_max = 80
+ prefixes = list("Digestive ")
+ bodies = list("Vomit")
+ suffixes = list(" Emission")
var/vomit_blood = FALSE
var/proj_vomit = 0
threshold_desc = "Resistance 7: Host will vomit blood, causing internal damage. \
@@ -41,11 +44,11 @@ Bonus
/datum/symptom/vomit/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["stealth"] >= 4)
+ if(A.stealth >= 4)
suppress_warning = TRUE
- if(A.properties["resistance"] >= 7) //blood vomit
+ if(A.resistance >= 7) //blood vomit
vomit_blood = TRUE
- if(A.properties["transmittable"] >= 7) //projectile vomit
+ if(A.transmission >= 7) //projectile vomit
proj_vomit = 5
/datum/symptom/vomit/Activate(datum/disease/advance/A)
diff --git a/code/datums/diseases/advance/symptoms/weight.dm b/code/datums/diseases/advance/symptoms/weight.dm
index da1a1f6a40665..52b89c0c261be 100644
--- a/code/datums/diseases/advance/symptoms/weight.dm
+++ b/code/datums/diseases/advance/symptoms/weight.dm
@@ -6,7 +6,7 @@ Weight Loss
Very Very Noticable.
Decreases resistance.
Decreases stage speed.
- Reduced Transmittable.
+ Reduced transmission.
High level.
Bonus
@@ -23,18 +23,20 @@ Bonus
stealth = -2
resistance = 2
stage_speed = -2
- transmittable = -1
+ transmission = -1
level = 3
severity = 2
base_message_chance = 100
symptom_delay_min = 15
symptom_delay_max = 45
+ prefixes = list("Starving ")
+ bodies = list("Diet")
threshold_desc = "Stealth 2: The symptom is less noticeable."
/datum/symptom/weight_loss/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["stealth"] >= 2) //warn less often
+ if(A.stealth >= 2) //warn less often
base_message_chance = 25
/datum/symptom/weight_loss/Activate(datum/disease/advance/A)
@@ -48,4 +50,4 @@ Bonus
else
to_chat(M, "[pick("So hungry...", "You'd kill someone for a bite of food...", "Hunger cramps seize you...")]")
M.overeatduration = max(M.overeatduration - 100, 0)
- M.adjust_nutrition(-100)
\ No newline at end of file
+ M.adjust_nutrition(-100)
diff --git a/code/datums/diseases/advance/symptoms/wizarditis.dm b/code/datums/diseases/advance/symptoms/wizarditis.dm
index 6dc5f1a1e1662..55e2e52852a55 100644
--- a/code/datums/diseases/advance/symptoms/wizarditis.dm
+++ b/code/datums/diseases/advance/symptoms/wizarditis.dm
@@ -4,29 +4,31 @@
stealth = 1
resistance = -2
stage_speed = -3
- transmittable = -1
+ transmission = -1
level = 0
severity = 0
symptom_delay_min = 15
symptom_delay_max = 45
+ prefixes = list("Wizard's ", "Magic ", "Accursed ")
+ bodies = list("Wizard")
var/teleport = FALSE
var/robes = FALSE
- threshold_desc = "Transmission 14: The host teleports occasionally. \
- Speed 7: The host grows a set of wizard robes."
+ threshold_desc = "Transmission 8: The host teleports occasionally. \
+ Stage Speed 7: The host grows a set of wizard robes."
/datum/symptom/wizarditis/severityset(datum/disease/advance/A)
. = ..()
- if(A.properties["transmittable"] >= 12)
+ if(A.transmission >= 8)
severity += 1
- if(A.properties["speed"] >= 7)
+ if(A.stage_rate >= 7)
severity += 1
/datum/symptom/wizarditis/Start(datum/disease/advance/A)
if(!..())
return
- if(A.properties["transmission"] >= 14)
+ if(A.transmission >= 8)
teleport = TRUE
- if(A.properties["speed"] >= 7)
+ if(A.stage_rate >= 7)
robes = TRUE
/datum/symptom/wizarditis/Activate(datum/disease/advance/A)
@@ -47,7 +49,7 @@
if(prob(30) && prob(50))
to_chat(M, "You feel [pick("the magic bubbling in your veins","that this location gives you a +1 to INT","an urge to summon familiar")].")
- if(4)
+ if(4,5)
if(prob(50))
M.say(pick("NEC CANTIO!","AULIE OXIN FIERA!","STI KALY!","EI NATH!"))
@@ -71,7 +73,7 @@
qdel(H.head)
C = new /obj/item/clothing/head/wizard(H)
ADD_TRAIT(C, TRAIT_NODROP, DISEASE_TRAIT)
- H.equip_to_slot_or_del(C, SLOT_HEAD)
+ H.equip_to_slot_or_del(C, ITEM_SLOT_HEAD)
return
if(prob(chance))
if(!istype(H.wear_suit, /obj/item/clothing/suit/wizrobe))
@@ -79,7 +81,7 @@
qdel(H.wear_suit)
C = new /obj/item/clothing/suit/wizrobe(H)
ADD_TRAIT(C, TRAIT_NODROP, DISEASE_TRAIT)
- H.equip_to_slot_or_del(C, SLOT_WEAR_SUIT)
+ H.equip_to_slot_or_del(C, ITEM_SLOT_OCLOTHING)
return
if(prob(chance))
if(!istype(H.shoes, /obj/item/clothing/shoes/sandal/magic))
@@ -87,7 +89,7 @@
qdel(H.shoes)
C = new /obj/item/clothing/shoes/sandal/magic(H)
ADD_TRAIT(C, TRAIT_NODROP, DISEASE_TRAIT)
- H.equip_to_slot_or_del(C, SLOT_SHOES)
+ H.equip_to_slot_or_del(C, ITEM_SLOT_FEET)
return
else
var/mob/living/carbon/H = A.affected_mob
@@ -98,11 +100,11 @@
/datum/symptom/wizarditis/proc/teleport(datum/disease/advance/A)
- var/turf/L = get_safe_random_station_turf()
+ var/turf/L = get_safe_random_station_turfs()
A.affected_mob.say("SCYAR NILA!")
do_teleport(A.affected_mob, L, forceMove = TRUE, channel = TELEPORT_CHANNEL_MAGIC)
- playsound(get_turf(A.affected_mob), 'sound/weapons/zapbang.ogg', 50,1)
-
+ playsound(get_turf(A.affected_mob), 'sound/weapons/zapbang.ogg', 50,1)
+
/datum/symptom/wizarditis/End(datum/disease/advance/A)
if(ishuman(A.affected_mob))
var/mob/living/carbon/human/H = A.affected_mob
@@ -113,5 +115,5 @@
if(istype(H.shoes, /obj/item/clothing/shoes/sandal/magic))
REMOVE_TRAIT(H.shoes, TRAIT_NODROP, DISEASE_TRAIT)
-
+
diff --git a/code/datums/diseases/advance/symptoms/youth.dm b/code/datums/diseases/advance/symptoms/youth.dm
index d2712a0146fdf..e43b0ab1d5129 100644
--- a/code/datums/diseases/advance/symptoms/youth.dm
+++ b/code/datums/diseases/advance/symptoms/youth.dm
@@ -23,11 +23,13 @@ BONUS
stealth = 3
resistance = 4
stage_speed = 4
- transmittable = -4
+ transmission = -4
level = 5
base_message_chance = 100
symptom_delay_min = 25
symptom_delay_max = 50
+ prefixes = list("Eternal ")
+ bodies = list("Immortal", "Elixir")
/datum/symptom/youth/Activate(datum/disease/advance/A)
if(!..())
@@ -55,4 +57,4 @@ BONUS
if(5)
if(H.age > 21)
H.age = 21
- to_chat(H, "You feel like you can take on the world!")
\ No newline at end of file
+ to_chat(H, "You feel like you can take on the world!")
diff --git a/code/datums/diseases/anxiety.dm b/code/datums/diseases/anxiety.dm
index 20ecceb224c2c..d039923f63231 100644
--- a/code/datums/diseases/anxiety.dm
+++ b/code/datums/diseases/anxiety.dm
@@ -9,7 +9,7 @@
agent = "Excess Lepidopticides"
viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey)
desc = "If left untreated subject will regurgitate butterflies."
- severity = DISEASE_SEVERITY_MINOR
+ danger = DISEASE_MINOR
/datum/disease/anxiety/stage_act()
..()
@@ -38,4 +38,4 @@
"You cough up butterflies!")
new /mob/living/simple_animal/butterfly(affected_mob.loc)
new /mob/living/simple_animal/butterfly(affected_mob.loc)
- return
\ No newline at end of file
+ return
diff --git a/code/datums/diseases/appendicitis.dm b/code/datums/diseases/appendicitis.dm
index 7a6ea142b3619..35a6040f02eec 100644
--- a/code/datums/diseases/appendicitis.dm
+++ b/code/datums/diseases/appendicitis.dm
@@ -7,7 +7,7 @@
viable_mobtypes = list(/mob/living/carbon/human)
permeability_mod = 1
desc = "If left untreated the subject will become very weak, and may vomit often."
- severity = DISEASE_SEVERITY_MEDIUM
+ danger = DISEASE_MEDIUM
disease_flags = CAN_CARRY|CAN_RESIST
spread_flags = DISEASE_SPREAD_NON_CONTAGIOUS
visibility_flags = HIDDEN_PANDEMIC
diff --git a/code/datums/diseases/beesease.dm b/code/datums/diseases/beesease.dm
index 074bda0560f8f..b26618640fefe 100644
--- a/code/datums/diseases/beesease.dm
+++ b/code/datums/diseases/beesease.dm
@@ -9,7 +9,7 @@
agent = "Apidae Infection"
viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey)
desc = "If left untreated subject will regurgitate bees."
- severity = DISEASE_SEVERITY_MEDIUM
+ danger = DISEASE_MEDIUM
infectable_biotypes = list(MOB_ORGANIC, MOB_UNDEAD) //bees nesting in corpses
/datum/disease/beesease/stage_act()
@@ -36,4 +36,4 @@
affected_mob.visible_message("[affected_mob] coughs up a swarm of bees!", \
"You cough up a swarm of bees!")
new /mob/living/simple_animal/hostile/poison/bees(affected_mob.loc)
- return
\ No newline at end of file
+ return
diff --git a/code/datums/diseases/brainrot.dm b/code/datums/diseases/brainrot.dm
index 0bcd1e30eadb8..edfc1486469e7 100644
--- a/code/datums/diseases/brainrot.dm
+++ b/code/datums/diseases/brainrot.dm
@@ -10,7 +10,7 @@
cure_chance = 15//higher chance to cure, since two reagents are required
desc = "This disease destroys the braincells, causing brain fever, brain necrosis and general intoxication."
required_organs = list(/obj/item/organ/brain)
- severity = DISEASE_SEVERITY_HARMFUL
+ danger = DISEASE_HARMFUL
/datum/disease/brainrot/stage_act() //Removed toxloss because damaging diseases are pretty horrible. Last round it killed the entire station because the cure didn't work -- Urist -ACTUALLY Removed rather than commented out, I don't see it returning - RR
..()
diff --git a/code/datums/diseases/cold.dm b/code/datums/diseases/cold.dm
index dd91a0d822168..0961557f5e3b4 100644
--- a/code/datums/diseases/cold.dm
+++ b/code/datums/diseases/cold.dm
@@ -7,7 +7,7 @@
viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey)
permeability_mod = 0.5
desc = "If left untreated, the subject will contract the flu."
- severity = DISEASE_SEVERITY_NONTHREAT
+ danger = DISEASE_NONTHREAT
/datum/disease/cold/stage_act()
..()
@@ -50,4 +50,4 @@
if(!affected_mob.disease_resistances.Find(/datum/disease/flu))
var/datum/disease/Flu = new /datum/disease/flu()
affected_mob.ForceContractDisease(Flu, FALSE, TRUE)
- cure()
\ No newline at end of file
+ cure()
diff --git a/code/datums/diseases/cold9.dm b/code/datums/diseases/cold9.dm
index 47f391ecf78d1..35027ed10ae61 100644
--- a/code/datums/diseases/cold9.dm
+++ b/code/datums/diseases/cold9.dm
@@ -8,7 +8,7 @@
agent = "ICE9-rhinovirus"
viable_mobtypes = list(/mob/living/carbon/human)
desc = "If left untreated the subject will slow, as if partly frozen."
- severity = DISEASE_SEVERITY_HARMFUL
+ danger = DISEASE_HARMFUL
/datum/disease/cold9/stage_act()
..()
@@ -36,4 +36,4 @@
if(prob(1))
to_chat(affected_mob, "Your throat feels sore.")
if(prob(10))
- to_chat(affected_mob, "You feel stiff.")
\ No newline at end of file
+ to_chat(affected_mob, "You feel stiff.")
diff --git a/code/datums/diseases/decloning.dm b/code/datums/diseases/decloning.dm
index b332876c01060..90ff0cda7fff5 100644
--- a/code/datums/diseases/decloning.dm
+++ b/code/datums/diseases/decloning.dm
@@ -7,7 +7,7 @@
agent = "Severe Genetic Damage"
viable_mobtypes = list(/mob/living/carbon/human)
desc = @"If left untreated the subject will [REDACTED]!"
- severity = "Dangerous!"
+ danger = DISEASE_DANGEROUS
cures = list(/datum/reagent/medicine/rezadone)
disease_flags = CAN_CARRY|CAN_RESIST
spread_flags = DISEASE_SPREAD_NON_CONTAGIOUS
@@ -55,5 +55,5 @@
affected_mob.adjustCloneLoss(5)
affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2, 170)
if(affected_mob.cloneloss >= 100)
- affected_mob.visible_message("[affected_mob] skin turns to dust!", "Your skin turns to dust!")
- affected_mob.dust()
\ No newline at end of file
+ affected_mob.visible_message("[affected_mob] skin turns to dust!", "Your skin turns to dust!")
+ affected_mob.dust()
diff --git a/code/datums/diseases/dna_spread.dm b/code/datums/diseases/dna_spread.dm
index 3a67230d36ec2..42a6595707994 100644
--- a/code/datums/diseases/dna_spread.dm
+++ b/code/datums/diseases/dna_spread.dm
@@ -11,7 +11,7 @@
var/datum/dna/original_dna = null
var/transformed = 0
desc = "This disease transplants the genetic code of the initial vector into new hosts."
- severity = DISEASE_SEVERITY_MEDIUM
+ danger = DISEASE_MEDIUM
/datum/disease/dnaspread/stage_act()
diff --git a/code/datums/diseases/fake_gbs.dm b/code/datums/diseases/fake_gbs.dm
index 37628a5502f18..cb7776969c7c7 100644
--- a/code/datums/diseases/fake_gbs.dm
+++ b/code/datums/diseases/fake_gbs.dm
@@ -8,7 +8,7 @@
agent = "Gravitokinetic Bipotential SADS-"
viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey)
desc = "If left untreated death will occur."
- severity = DISEASE_SEVERITY_BIOHAZARD
+ danger = DISEASE_BIOHAZARD
/datum/disease/fake_gbs/stage_act()
..()
diff --git a/code/datums/diseases/flu.dm b/code/datums/diseases/flu.dm
index 62bb3de8df412..3cb6e91e7f6dd 100644
--- a/code/datums/diseases/flu.dm
+++ b/code/datums/diseases/flu.dm
@@ -9,7 +9,7 @@
viable_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey)
permeability_mod = 0.75
desc = "If left untreated the subject will feel quite unwell."
- severity = DISEASE_SEVERITY_MINOR
+ danger = DISEASE_MINOR
/datum/disease/flu/stage_act()
..()
diff --git a/code/datums/diseases/fluspanish.dm b/code/datums/diseases/fluspanish.dm
index 3297877fe910d..398f9e88678ef 100644
--- a/code/datums/diseases/fluspanish.dm
+++ b/code/datums/diseases/fluspanish.dm
@@ -9,7 +9,7 @@
viable_mobtypes = list(/mob/living/carbon/human)
permeability_mod = 0.75
desc = "If left untreated the subject will burn to death for being a heretic."
- severity = DISEASE_SEVERITY_DANGEROUS
+ danger = DISEASE_DANGEROUS
/datum/disease/fluspanish/stage_act()
..()
diff --git a/code/datums/diseases/gastrolisis.dm b/code/datums/diseases/gastrolisis.dm
index 5f03fe1b706b3..24c220dd13c07 100644
--- a/code/datums/diseases/gastrolisis.dm
+++ b/code/datums/diseases/gastrolisis.dm
@@ -38,12 +38,12 @@
"You scream in pain as your eyes are pushed out by your new snail eyes!")
affected_mob.emote("scream")
return
- var/obj/item/shell = affected_mob.get_item_by_slot(SLOT_BACK)
+ var/obj/item/shell = affected_mob.get_item_by_slot(ITEM_SLOT_BACK)
if(!istype(shell, /obj/item/storage/backpack/snail))
shell = null
if(!shell && prob(5))
- if(affected_mob.dropItemToGround(affected_mob.get_item_by_slot(SLOT_BACK)))
- affected_mob.equip_to_slot_or_del(new /obj/item/storage/backpack/snail(affected_mob), SLOT_BACK)
+ if(affected_mob.dropItemToGround(affected_mob.get_item_by_slot(ITEM_SLOT_BACK)))
+ affected_mob.equip_to_slot_or_del(new /obj/item/storage/backpack/snail(affected_mob), ITEM_SLOT_BACK)
affected_mob.visible_message("[affected_mob] grows a grotesque shell on their back!", \
"You scream in pain as a shell pushes itself out from under your skin!")
affected_mob.emote("scream")
@@ -78,7 +78,7 @@
if(eyes)
var/obj/item/organ/eyes/new_eyes = new H.dna.species.mutanteyes ()
new_eyes.Insert(H)
- var/obj/item/storage/backpack/bag = H.get_item_by_slot(SLOT_BACK)
+ var/obj/item/storage/backpack/bag = H.get_item_by_slot(ITEM_SLOT_BACK)
if(istype(bag, /obj/item/storage/backpack/snail))
bag.emptyStorage()
H.doUnEquip(bag, TRUE, no_move = TRUE)
diff --git a/code/datums/diseases/gbs.dm b/code/datums/diseases/gbs.dm
index 8ac199685570f..2be16dd8c49cf 100644
--- a/code/datums/diseases/gbs.dm
+++ b/code/datums/diseases/gbs.dm
@@ -10,7 +10,7 @@
viable_mobtypes = list(/mob/living/carbon/human)
disease_flags = CAN_CARRY|CAN_RESIST|CURABLE
permeability_mod = 1
- severity = DISEASE_SEVERITY_BIOHAZARD
+ danger = DISEASE_BIOHAZARD
/datum/disease/gbs/stage_act()
..()
diff --git a/code/datums/diseases/heart_failure.dm b/code/datums/diseases/heart_failure.dm
index c823d0984b64b..9084707b0e0d1 100644
--- a/code/datums/diseases/heart_failure.dm
+++ b/code/datums/diseases/heart_failure.dm
@@ -8,7 +8,7 @@
viable_mobtypes = list(/mob/living/carbon/human)
permeability_mod = 1
desc = "If left untreated the subject will die!"
- severity = "Dangerous!"
+ danger = DISEASE_DANGEROUS
disease_flags = CAN_CARRY|CAN_RESIST
spread_flags = DISEASE_SPREAD_NON_CONTAGIOUS
visibility_flags = HIDDEN_PANDEMIC
@@ -37,7 +37,7 @@
to_chat(H, "You feel [pick("full", "nauseated", "sweaty", "weak", "tired", "short on breath", "uneasy")].")
if(3 to 4)
if(!sound)
- H.playsound_local(H, 'sound/health/slowbeat.ogg',40,0, channel = CHANNEL_HEARTBEAT)
+ H.playsound_local(H, 'sound/health/slowbeat.ogg',40,0, channel = CHANNEL_HEARTBEAT, use_reverb = FALSE)
sound = TRUE
if(prob(3))
to_chat(H, "You feel a sharp pain in your chest!")
diff --git a/code/datums/diseases/magnitis.dm b/code/datums/diseases/magnitis.dm
index 29e6657e1313f..740010762a482 100644
--- a/code/datums/diseases/magnitis.dm
+++ b/code/datums/diseases/magnitis.dm
@@ -9,7 +9,7 @@
disease_flags = CAN_CARRY|CAN_RESIST|CURABLE
permeability_mod = 0.75
desc = "This disease disrupts the magnetic field of your body, making it act as if a powerful magnet. Injections of iron help stabilize the field."
- severity = DISEASE_SEVERITY_MEDIUM
+ danger = DISEASE_MEDIUM
infectable_biotypes = list(MOB_ORGANIC, MOB_ROBOTIC)
process_dead = TRUE
@@ -65,4 +65,4 @@
var/iter = rand(1,3)
for(i=0,iBeep...boop..")
@@ -204,7 +201,7 @@
cure_chance = 5
agent = "Rip-LEY Alien Microbes"
desc = "This disease changes the victim into a xenomorph."
- severity = DISEASE_SEVERITY_BIOHAZARD
+ danger = DISEASE_BIOHAZARD
visibility_flags = 0
stage1 = list()
stage2 = list("Your throat feels scratchy.", "Kill...")
@@ -233,7 +230,7 @@
cure_chance = 80
agent = "Advanced Mutation Toxin"
desc = "This highly concentrated extract converts anything into more of itself."
- severity = DISEASE_SEVERITY_BIOHAZARD
+ danger = DISEASE_BIOHAZARD
visibility_flags = 0
stage1 = list("You don't feel very well.")
stage2 = list("Your skin feels a little slimy.")
@@ -261,7 +258,7 @@
cures = list(/datum/reagent/medicine/adminordrazine)
agent = "Fell Doge Majicks"
desc = "This disease transforms the victim into a corgi."
- severity = DISEASE_SEVERITY_BIOHAZARD
+ danger = DISEASE_BIOHAZARD
visibility_flags = 0
stage1 = list("BARK.")
stage2 = list("You feel the need to wear silly hats.")
@@ -287,7 +284,7 @@
agent = "Gluttony's Blessing"
desc = "A 'gift' from somewhere terrible."
stage_prob = 20
- severity = DISEASE_SEVERITY_BIOHAZARD
+ danger = DISEASE_BIOHAZARD
visibility_flags = 0
stage1 = list("Your stomach rumbles.")
stage2 = list("Your skin feels saggy.")
@@ -305,7 +302,7 @@
stage_prob = 5
agent = "Tranquility"
desc = "Consuming the flesh of a Gondola comes at a terrible price."
- severity = DISEASE_SEVERITY_BIOHAZARD
+ danger = DISEASE_BIOHAZARD
visibility_flags = 0
stage1 = list("You seem a little lighter in your step.")
stage2 = list("You catch yourself smiling for no reason.")
@@ -342,13 +339,13 @@
cure_text = "Something that would kill off the tiny cats."
spread_text = "Acute"
disease_flags = CURABLE|CAN_CARRY|CAN_RESIST
- cures = list(/datum/reagent/consumable/cocoa) //kills all the tiny cats that infected your organism
+ cures = list(/datum/reagent/consumable/cocoa, /datum/reagent/consumable/cocoa/hot_cocoa) //kills all the tiny cats that infected your organism
cure_chance = 25
stage_prob = 3
agent = "Nano-feline Toxoplasmosis"
desc = "A lot of tiny cats in the blood that slowly turn you into a big cat."
is_mutagenic = TRUE //So that it won't be autocured after stage 5
- severity = DISEASE_SEVERITY_BIOHAZARD
+ danger = DISEASE_BIOHAZARD
visibility_flags = 0
stage1 = list("You feel scratching fom within.", "You hear a faint miaow somewhere really close.")
stage2 = list("You suppress the urge to lick yourself.")
@@ -383,6 +380,8 @@
RegisterSignal(affected_mob, COMSIG_MOB_SAY, .proc/handle_speech)
/datum/disease/transformation/felinid/proc/handle_speech(datum/source, list/speech_args)
+ SIGNAL_HANDLER
+
var/message = speech_args[SPEECH_MESSAGE]
if(message[1] != "*")
message = " [message]"
@@ -446,7 +445,7 @@
stage_prob = 5
agent = "Legion droppings"
desc = "Who knew that spreading the primordial goop of a vile entity would take a toll on the body?"
- severity = DISEASE_SEVERITY_BIOHAZARD
+ danger = DISEASE_BIOHAZARD
visibility_flags = 0
stage1 = list("Your skin seems ashy.")
stage2 = list("You wonder what it would be like to live on Lavaland forever...")
diff --git a/code/datums/diseases/tuberculosis.dm b/code/datums/diseases/tuberculosis.dm
index e92ecffae18ab..1ed04baca5da4 100644
--- a/code/datums/diseases/tuberculosis.dm
+++ b/code/datums/diseases/tuberculosis.dm
@@ -10,7 +10,7 @@
cure_chance = 5//like hell are you getting out of hell
desc = "A rare highly transmissible virulent virus. Few samples exist, rumoured to be carefully grown and cultured by clandestine bio-weapon specialists. Causes fever, blood vomiting, lung damage, weight loss, and fatigue."
required_organs = list(/obj/item/organ/lungs)
- severity = DISEASE_SEVERITY_BIOHAZARD
+ danger = DISEASE_BIOHAZARD
bypasses_immunity = TRUE // TB primarily impacts the lungs; it's also bacterial or fungal in nature; viral immunity should do nothing.
/datum/disease/tuberculosis/stage_act() //it begins
@@ -42,7 +42,7 @@
affected_mob.adjustStaminaLoss(70)
if(prob(10))
affected_mob.adjustStaminaLoss(100)
- affected_mob.visible_message("[affected_mob] faints!", "You surrender yourself and feel at peace...")
+ affected_mob.visible_message("[affected_mob] faints!", "You surrender yourself and feel at peace.")
affected_mob.AdjustSleeping(100)
if(prob(2))
to_chat(affected_mob, "You feel your mind relax and your thoughts drift!")
@@ -50,11 +50,11 @@
if(prob(10))
affected_mob.vomit(20)
if(prob(3))
- to_chat(affected_mob, "[pick("Your stomach silently rumbles.", "Your stomach seizes up and falls limp, muscles dead and lifeless.", "You could eat a crayon")]")
+ to_chat(affected_mob, "[pick("Your stomach silently rumbles.", "Your stomach seizes up and falls limp, muscles dead and lifeless.", "You could eat a crayon.")]")
affected_mob.overeatduration = max(affected_mob.overeatduration - 100, 0)
affected_mob.adjust_nutrition(-100)
if(prob(15))
- to_chat(affected_mob, "[pick("You feel uncomfortably hot.", "You feel like unzipping your jumpsuit", "You feel like taking off some clothes...")]")
+ to_chat(affected_mob, "[pick("You feel uncomfortably hot.", "You feel like unzipping your jumpsuit.", "You feel like taking off some clothes.")]")
affected_mob.adjust_bodytemperature(40)
return
diff --git a/code/datums/diseases/wizarditis.dm b/code/datums/diseases/wizarditis.dm
index 230a074bb1460..b060a3348871f 100644
--- a/code/datums/diseases/wizarditis.dm
+++ b/code/datums/diseases/wizarditis.dm
@@ -9,8 +9,8 @@
viable_mobtypes = list(/mob/living/carbon/human)
disease_flags = CAN_CARRY|CAN_RESIST|CURABLE
permeability_mod = 0.75
- desc = "Some speculate that this virus is the cause of the Space Wizard Federation's existence. Subjects affected show the signs of mental retardation, yelling obscure sentences or total gibberish. On late stages subjects sometime express the feelings of inner power, and, cite, 'the ability to control the forces of cosmos themselves!' A gulp of strong, manly spirits usually reverts them to normal, humanlike, condition."
- severity = DISEASE_SEVERITY_HARMFUL
+ desc = "Some speculate that this virus is the cause of the Space Wizard Federation's existence. Subjects affected show the signs of intellectual disability, yelling obscure sentences or total gibberish. On late stages subjects sometime express the feelings of inner power, and, cite, 'the ability to control the forces of cosmos themselves!' A gulp of strong, manly spirits usually reverts them to normal, humanlike, condition."
+ danger = DISEASE_HARMFUL
required_organs = list(/obj/item/bodypart/head)
/*
@@ -61,19 +61,19 @@ STI KALY - blind
if(!istype(H.head, /obj/item/clothing/head/wizard))
if(!H.dropItemToGround(H.head))
qdel(H.head)
- H.equip_to_slot_or_del(new /obj/item/clothing/head/wizard(H), SLOT_HEAD)
+ H.equip_to_slot_or_del(new /obj/item/clothing/head/wizard(H), ITEM_SLOT_HEAD)
return
if(prob(chance))
if(!istype(H.wear_suit, /obj/item/clothing/suit/wizrobe))
if(!H.dropItemToGround(H.wear_suit))
qdel(H.wear_suit)
- H.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe(H), SLOT_WEAR_SUIT)
+ H.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe(H), ITEM_SLOT_OCLOTHING)
return
if(prob(chance))
if(!istype(H.shoes, /obj/item/clothing/shoes/sandal/magic))
if(!H.dropItemToGround(H.shoes))
qdel(H.shoes)
- H.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal/magic(H), SLOT_SHOES)
+ H.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal/magic(H), ITEM_SLOT_FEET)
return
else
var/mob/living/carbon/H = affected_mob
@@ -95,7 +95,7 @@ STI KALY - blind
var/list/L = list()
for(var/turf/T in get_area_turfs(thearea.type))
- if(T.z != affected_mob.z)
+ if(T.get_virtual_z_level() != affected_mob.get_virtual_z_level())
continue
if(T.name == "space")
continue
@@ -111,7 +111,7 @@ STI KALY - blind
if(!L)
return
- affected_mob.say("SCYAR NILA [uppertext(thearea.name)]!", forced = "wizarditis teleport")
- affected_mob.forceMove(pick(L))
+ if(do_teleport(affected_mob, pick(L), channel = TELEPORT_CHANNEL_MAGIC, no_effects = TRUE))
+ affected_mob.say("SCYAR NILA [uppertext(thearea.name)]!", forced = "wizarditis teleport")
return
diff --git a/code/datums/dna.dm b/code/datums/dna.dm
index 2e6227af5fe33..4def3c639ba7c 100644
--- a/code/datums/dna.dm
+++ b/code/datums/dna.dm
@@ -13,6 +13,7 @@
var/mob/living/holder
var/delete_species = TRUE //Set to FALSE when a body is scanned by a cloner to fix #38875
var/mutation_index[DNA_MUTATION_BLOCKS] //List of which mutations this carbon has and its assigned block
+ var/default_mutation_genes[DNA_MUTATION_BLOCKS] //List of the default genes from this mutation to allow DNA Scanner highlighting
var/stability = 100
var/scrambled = FALSE //Did we take something like mutagen? In that case we cant get our genes scanned to instantly cheese all the powers.
@@ -48,10 +49,12 @@
destination.dna.temporary_mutations = temporary_mutations.Copy()
if(transfer_SE)
destination.dna.mutation_index = mutation_index
+ destination.dna.default_mutation_genes = default_mutation_genes
/datum/dna/proc/copy_dna(datum/dna/new_dna)
new_dna.unique_enzymes = unique_enzymes
new_dna.mutation_index = mutation_index
+ new_dna.default_mutation_genes = default_mutation_genes
new_dna.uni_identity = uni_identity
new_dna.blood_type = blood_type
new_dna.features = features.Copy()
@@ -112,22 +115,24 @@
return .
/datum/dna/proc/generate_dna_blocks()
- var/bonus
+ var/list/mutations_temp = GLOB.good_mutations + GLOB.bad_mutations + GLOB.not_good_mutations
if(species?.inert_mutation)
- bonus = GET_INITIALIZED_MUTATION(species.inert_mutation)
- var/list/mutations_temp = GLOB.good_mutations + GLOB.bad_mutations + GLOB.not_good_mutations + bonus
+ mutations_temp += GET_INITIALIZED_MUTATION(species.inert_mutation)
if(!LAZYLEN(mutations_temp))
return
mutation_index.Cut()
+ default_mutation_genes.Cut()
shuffle_inplace(mutations_temp)
if(ismonkey(holder))
mutations |= new RACEMUT(MUT_NORMAL)
mutation_index[RACEMUT] = GET_SEQUENCE(RACEMUT)
else
mutation_index[RACEMUT] = create_sequence(RACEMUT, FALSE)
+ default_mutation_genes[RACEMUT] = mutation_index[RACEMUT]
for(var/i in 2 to DNA_MUTATION_BLOCKS)
var/datum/mutation/human/M = mutations_temp[i]
mutation_index[M.type] = create_sequence(M.type, FALSE, M.difficulty)
+ default_mutation_genes[M.type] = mutation_index[M.type]
shuffle_inplace(mutation_index)
//Used to generate original gene sequences for every mutation
@@ -316,7 +321,7 @@
return dna
-/mob/living/carbon/human/proc/hardset_dna(ui, list/mutation_index, newreal_name, newblood_type, datum/species/mrace, newfeatures, list/mutations, force_transfer_mutations)
+/mob/living/carbon/human/proc/hardset_dna(ui, list/mutation_index, newreal_name, newblood_type, datum/species/mrace, newfeatures, list/mutations, force_transfer_mutations, list/default_mutation_genes)
//Do not use force_transfer_mutations for stuff like cloners without some precautions, otherwise some conditional mutations could break (timers, drill hat etc)
if(newfeatures)
dna.features = newfeatures
@@ -326,6 +331,14 @@
newrace.copy_properties_from(mrace)
set_species(newrace, icon_update=0)
+ if(LAZYLEN(mutation_index))
+ dna.mutation_index = mutation_index.Copy()
+ if(LAZYLEN(default_mutation_genes))
+ dna.default_mutation_genes = default_mutation_genes.Copy()
+ else
+ dna.default_mutation_genes = mutation_index.Copy()
+ domutcheck()
+
if(newreal_name)
real_name = newreal_name
dna.generate_unique_enzymes()
@@ -337,10 +350,6 @@
dna.uni_identity = ui
updateappearance(icon_update=0)
- if(LAZYLEN(mutation_index))
- dna.mutation_index = mutation_index.Copy()
- domutcheck()
-
if(mrace || newfeatures || ui)
update_body()
update_hair()
@@ -424,8 +433,10 @@
. = TRUE
if(on)
mutation_index[HM.type] = GET_SEQUENCE(HM.type)
+ default_mutation_genes[HM.type] = mutation_index[HM.type]
else if(GET_SEQUENCE(HM.type) == mutation_index[HM.type])
mutation_index[HM.type] = create_sequence(HM.type, FALSE, HM.difficulty)
+ default_mutation_genes[HM.type] = mutation_index[HM.type]
/datum/dna/proc/activate_mutation(mutation) //note that this returns a boolean and not a new mob
if(!mutation)
diff --git a/code/datums/elements/_element.dm b/code/datums/elements/_element.dm
index 6a550cc9d1e98..7c1e80542df6f 100644
--- a/code/datums/elements/_element.dm
+++ b/code/datums/elements/_element.dm
@@ -10,10 +10,21 @@
/datum/element/proc/Attach(datum/target)
if(type == /datum/element)
return ELEMENT_INCOMPATIBLE
+ SEND_SIGNAL(target, COMSIG_ELEMENT_ATTACH, src)
if(element_flags & ELEMENT_DETACH)
- RegisterSignal(target, COMSIG_PARENT_QDELETING, .proc/Detach)
+ /** The override = TRUE here is to suppress runtimes happening because of the blood decal element
+ * being applied multiple times to a same thing every time there is some bloody attacks,
+ * which happens due to ludicrous use of check_blood() in forensics.dm,
+ * and how elements system is design and coded; there isn't exactly a not-hacky
+ * way to determine whether a datum has this particular element before adding it...
+ */
+ RegisterSignal(target, COMSIG_PARENT_QDELETING, .proc/Detach, override = TRUE)
/datum/element/proc/Detach(datum/source, force)
+ SIGNAL_HANDLER
+
+ SEND_SIGNAL(source, COMSIG_ELEMENT_DETACH, src)
+ SHOULD_CALL_PARENT(TRUE)
UnregisterSignal(source, COMSIG_PARENT_QDELETING)
/datum/element/Destroy(force)
@@ -24,16 +35,17 @@
//DATUM PROCS
-/datum/proc/AddElement(eletype, ...)
- var/datum/element/ele = SSdcs.GetElement(arglist(args))
- args[1] = src
- if(ele.Attach(arglist(args)) == ELEMENT_INCOMPATIBLE)
- CRASH("Incompatible [eletype] assigned to a [type]! args: [json_encode(args)]")
+/// Finds the singleton for the element type given and attaches it to src
+/datum/proc/_AddElement(list/arguments)
+ var/datum/element/ele = SSdcs.GetElement(arguments)
+ arguments[1] = src
+ if(ele.Attach(arglist(arguments)) == ELEMENT_INCOMPATIBLE)
+ CRASH("Incompatible [arguments[1]] assigned to a [type]! args: [json_encode(args)]")
/**
* Finds the singleton for the element type given and detaches it from src
* You only need additional arguments beyond the type if you're using ELEMENT_BESPOKE
*/
-/datum/proc/RemoveElement(eletype, ...)
- var/datum/element/ele = SSdcs.GetElement(arglist(args))
+/datum/proc/_RemoveElement(list/arguments)
+ var/datum/element/ele = SSdcs.GetElement(arguments)
ele.Detach(src)
diff --git a/code/datums/elements/bed_tucking.dm b/code/datums/elements/bed_tucking.dm
new file mode 100644
index 0000000000000..10135871a7ad3
--- /dev/null
+++ b/code/datums/elements/bed_tucking.dm
@@ -0,0 +1,60 @@
+/// Tucking element, for things that can be tucked into bed.
+/datum/element/bed_tuckable
+ element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
+ id_arg_index = 2
+ /// our pixel_x offset - how much the item moves x when in bed (+x is closer to the pillow)
+ var/x_offset = 0
+ /// our pixel_y offset - how much the item move y when in bed (-y is closer to the middle)
+ var/y_offset = 0
+ /// our rotation degree - how much the item turns when in bed (+degrees turns it more parallel)
+ var/rotation_degree = 0
+
+/datum/element/bed_tuckable/Attach(obj/target, x = 0, y = 0, rotation = 0)
+ . = ..()
+ if(!isitem(target))
+ return ELEMENT_INCOMPATIBLE
+
+ x_offset = x
+ y_offset = y
+ rotation_degree = rotation
+ RegisterSignal(target, COMSIG_ITEM_ATTACK_OBJ, .proc/tuck_into_bed)
+
+/datum/element/bed_tuckable/Detach(obj/target)
+ . = ..()
+ UnregisterSignal(target, list(COMSIG_ITEM_ATTACK_OBJ, COMSIG_ITEM_PICKUP))
+
+/**
+ * Tuck our object into bed.
+ *
+ * tucked - the object being tucked
+ * target_bed - the bed we're tucking them into
+ * tucker - the guy doing the tucking
+ */
+/datum/element/bed_tuckable/proc/tuck_into_bed(obj/item/tucked, obj/structure/bed/target_bed, mob/living/tucker)
+ SIGNAL_HANDLER
+
+ if(!istype(target_bed))
+ return
+
+ if(!tucker.transferItemToLoc(tucked, target_bed.drop_location()))
+ return
+
+ to_chat(tucker, "You lay [tucked] out on [target_bed].")
+ tucked.pixel_x = x_offset
+ tucked.pixel_y = y_offset
+ if(rotation_degree)
+ tucked.transform = turn(tucked.transform, rotation_degree)
+ RegisterSignal(tucked, COMSIG_ITEM_PICKUP, .proc/untuck)
+
+ return COMPONENT_NO_AFTERATTACK
+
+/**
+ * If we rotate our object, then we need to un-rotate it when it's picked up
+ *
+ * tucked - the object that is tucked
+ */
+/datum/element/bed_tuckable/proc/untuck(obj/item/tucked)
+ SIGNAL_HANDLER
+
+ tucked.transform = turn(tucked.transform, -rotation_degree)
+ UnregisterSignal(tucked, COMSIG_ITEM_PICKUP)
diff --git a/code/datums/elements/bsa_blocker.dm b/code/datums/elements/bsa_blocker.dm
index 61140ad0ed5d2..5bdf4fa90912d 100644
--- a/code/datums/elements/bsa_blocker.dm
+++ b/code/datums/elements/bsa_blocker.dm
@@ -7,4 +7,6 @@
return ..()
/datum/element/bsa_blocker/proc/block_bsa()
+ SIGNAL_HANDLER
+
return COMSIG_ATOM_BLOCKS_BSA_BEAM
diff --git a/code/datums/elements/cleaning.dm b/code/datums/elements/cleaning.dm
index c89c13978f484..f8bdf8b34d453 100644
--- a/code/datums/elements/cleaning.dm
+++ b/code/datums/elements/cleaning.dm
@@ -9,6 +9,8 @@
UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
/datum/element/cleaning/proc/Clean(datum/source)
+ SIGNAL_HANDLER
+
var/atom/movable/AM = source
var/turf/tile = AM.loc
if(!isturf(tile))
diff --git a/code/datums/elements/decal.dm b/code/datums/elements/decal.dm
new file mode 100644
index 0000000000000..116fdf99b2d39
--- /dev/null
+++ b/code/datums/elements/decal.dm
@@ -0,0 +1,92 @@
+/datum/element/decal
+ element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
+ id_arg_index = 2
+ var/cleanable
+ var/description
+ var/mutable_appearance/pic
+ /**
+ * A short lecture on decal element collision on rotation
+ * If a given decal's rotated version is identical to one of existing (at a same target), pre-rotation decals,
+ * then the rotated decal won't stay after when the colliding pre-rotation decal gets rotated,
+ * resulting in some decal elements colliding into nonexistence. This internal tick-tock prevents
+ * such collision by forcing a non-collision.
+ */
+ var/rotated
+
+/datum/element/decal/Attach(atom/target, _icon, _icon_state, _dir, _cleanable=FALSE, _color, _layer=TURF_LAYER, _description, _alpha=255, _rotated=FALSE)
+ . = ..()
+ if(!isatom(target) || (pic ? FALSE : !generate_appearance(_icon, _icon_state, _dir, _layer, _color, _alpha, target)))
+ return ELEMENT_INCOMPATIBLE
+ description = _description
+ cleanable = _cleanable
+ rotated = _rotated
+
+ RegisterSignal(target,COMSIG_ATOM_UPDATE_OVERLAYS, .proc/apply_overlay, TRUE)
+ if(isturf(target))
+ RegisterSignal(target,COMSIG_TURF_AFTER_SHUTTLE_MOVE,.proc/shuttlemove_react, TRUE)
+ if(target.flags_1 & INITIALIZED_1)
+ target.update_icon() //could use some queuing here now maybe.
+ else
+ RegisterSignal(target,COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE,.proc/late_update_icon, TRUE)
+ if(isitem(target))
+ INVOKE_ASYNC(target, /obj/item/.proc/update_slot_icon, TRUE)
+ if(_dir)
+ RegisterSignal(target, COMSIG_ATOM_DIR_CHANGE, .proc/rotate_react,TRUE)
+ if(_cleanable)
+ RegisterSignal(target, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_react,TRUE)
+ if(_description)
+ RegisterSignal(target, COMSIG_PARENT_EXAMINE, .proc/examine, TRUE)
+
+/datum/element/decal/proc/generate_appearance(_icon, _icon_state, _dir, _layer, _color, _alpha, source)
+ if(!_icon || !_icon_state)
+ return FALSE
+ var/temp_image = image(_icon, null, _icon_state, _layer, _dir)
+ pic = new(temp_image)
+ pic.color = _color
+ pic.alpha = _alpha
+ return TRUE
+
+/datum/element/decal/Detach(atom/source, force)
+ UnregisterSignal(source, list(COMSIG_ATOM_DIR_CHANGE, COMSIG_COMPONENT_CLEAN_ACT, COMSIG_PARENT_EXAMINE, COMSIG_ATOM_UPDATE_OVERLAYS,COMSIG_TURF_AFTER_SHUTTLE_MOVE))
+ source.update_icon()
+ if(isitem(source))
+ INVOKE_ASYNC(source, /obj/item/.proc/update_slot_icon)
+ return ..()
+
+/datum/element/decal/proc/late_update_icon(atom/source)
+ SIGNAL_HANDLER
+ if(source && istype(source))
+ source.update_icon()
+ UnregisterSignal(source,COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE)
+
+
+/datum/element/decal/proc/apply_overlay(atom/source, list/overlay_list)
+ SIGNAL_HANDLER
+ overlay_list += pic
+
+/datum/element/decal/proc/shuttlemove_react(datum/source, turf/newT)
+ SIGNAL_HANDLER
+
+ Detach(source)
+ newT.AddElement(/datum/element/decal, pic.icon, pic.icon_state, pic.dir, cleanable, pic.color, pic.layer, description, pic.alpha, rotated)
+
+/datum/element/decal/proc/rotate_react(datum/source, old_dir, new_dir)
+ SIGNAL_HANDLER
+
+ if(old_dir == new_dir)
+ return
+ Detach(source)
+ source.AddElement(/datum/element/decal, pic.icon, pic.icon_state, angle2dir(dir2angle(pic.dir)+dir2angle(new_dir)-dir2angle(old_dir)), cleanable, pic.color, pic.layer, description, pic.alpha, !rotated)
+
+/datum/element/decal/proc/clean_react(datum/source, clean_types)
+ SIGNAL_HANDLER
+
+ if(clean_types & cleanable)
+ Detach(source)
+ return TRUE
+ return NONE
+
+/datum/element/decal/proc/examine(datum/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+
+ examine_list += description
diff --git a/code/datums/elements/decals/blood.dm b/code/datums/elements/decals/blood.dm
new file mode 100644
index 0000000000000..bb212e93dfe22
--- /dev/null
+++ b/code/datums/elements/decals/blood.dm
@@ -0,0 +1,36 @@
+/datum/element/decal/blood
+
+/**
+ * If you are annoyed by lack of blood decal visuals?
+ * Then here's a TODO for you: rework the entire update_icon() family to make COMSIG_ATOM_UPDATE_OVERLAYS and update_overlays() work!
+ * Until the rework, blood decal visuals might not work on some items... (but the name change will work, though)
+ */
+
+/datum/element/decal/blood/Attach(datum/target, _icon, _icon_state, _dir, _cleanable=CLEAN_STRENGTH_BLOOD, _color, _layer=ABOVE_OBJ_LAYER)
+ if(!isitem(target))
+ return ELEMENT_INCOMPATIBLE
+
+ . = ..()
+
+ RegisterSignal(target, COMSIG_ATOM_GET_EXAMINE_NAME, .proc/get_examine_name, TRUE)
+
+/datum/element/decal/blood/Detach(atom/source, force)
+ UnregisterSignal(source, COMSIG_ATOM_GET_EXAMINE_NAME)
+ return ..()
+
+/datum/element/decal/blood/generate_appearance(_icon, _icon_state, _dir, _layer, _color, _alpha, source)
+ if(!_icon || !_icon_state)
+ return FALSE
+ var/icon/blood_splatter_icon = icon(_icon, _icon_state, , 1) //we only want to apply blood-splatters to the initial icon_state for each object
+ blood_splatter_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent)
+ blood_splatter_icon.Blend(icon('icons/effects/blood.dmi', "itemblood"), ICON_MULTIPLY) //adds blood and the remaining white areas become transparant
+ pic = mutable_appearance(blood_splatter_icon)
+ return TRUE
+
+/datum/element/decal/blood/proc/get_examine_name(datum/source, mob/user, list/override)
+ SIGNAL_HANDLER
+
+ var/atom/A = source
+ override[EXAMINE_POSITION_ARTICLE] = A.gender == PLURAL? "some" : "a"
+ override[EXAMINE_POSITION_BEFORE] = " blood-stained "
+ return COMPONENT_EXNAME_CHANGED
diff --git a/code/datums/elements/deferred_aquarium_content.dm b/code/datums/elements/deferred_aquarium_content.dm
new file mode 100644
index 0000000000000..64eee03e80b5d
--- /dev/null
+++ b/code/datums/elements/deferred_aquarium_content.dm
@@ -0,0 +1,32 @@
+/**
+ * Create /datum/component/aquarium_content with the preset path on the target right before being inserted into aquarium and deletes itself.
+ * Used to save memory from aquarium properties on common objects/stacks that won't see aquarium in 99 out of 100 rounds.
+ */
+/datum/element/deferred_aquarium_content
+ element_flags = ELEMENT_BESPOKE
+ id_arg_index = 2
+ var/aquarium_content_type
+
+/datum/element/deferred_aquarium_content/Attach(datum/target, aquarium_content_type)
+ . = ..()
+ if(!ismovable(target))
+ return ELEMENT_INCOMPATIBLE
+ if(!aquarium_content_type)
+ CRASH("Deferred aquarium content missing behaviour type.")
+ src.aquarium_content_type = aquarium_content_type
+ //If element is added to something already in aquarium, just create the component.
+ var/atom/movable/movable_target = target
+ if(istype(movable_target.loc, /obj/structure/aquarium))
+ create_aquarium_component(movable_target)
+ else //otherwise the component will be created when trying to insert the thing.
+ RegisterSignal(target, COMSIG_AQUARIUM_BEFORE_INSERT_CHECK, .proc/create_aquarium_component)
+
+/datum/element/deferred_aquarium_content/Detach(datum/target)
+ . = ..()
+ UnregisterSignal(target, COMSIG_AQUARIUM_BEFORE_INSERT_CHECK)
+
+/datum/element/deferred_aquarium_content/proc/create_aquarium_component(datum/source)
+ SIGNAL_HANDLER
+
+ source.AddComponent(/datum/component/aquarium_content, aquarium_content_type)
+ Detach(source)
diff --git a/code/datums/elements/earhealing.dm b/code/datums/elements/earhealing.dm
index 1a01f3ae56b77..9a5b7c5b78af2 100644
--- a/code/datums/elements/earhealing.dm
+++ b/code/datums/elements/earhealing.dm
@@ -18,12 +18,14 @@
user_by_item -= target
/datum/element/earhealing/proc/equippedChanged(datum/source, mob/living/carbon/user, slot)
- if(slot == SLOT_EARS && istype(user))
+ SIGNAL_HANDLER
+
+ if(slot == ITEM_SLOT_EARS && istype(user))
user_by_item[source] = user
else
user_by_item -= source
-/datum/element/earhealing/process()
+/datum/element/earhealing/process(delta_time)
for(var/i in user_by_item)
var/mob/living/carbon/user = user_by_item[i]
if(HAS_TRAIT(user, TRAIT_DEAF))
@@ -31,6 +33,6 @@
var/obj/item/organ/ears/ears = user.getorganslot(ORGAN_SLOT_EARS)
if(!ears)
continue
- ears.deaf = max(ears.deaf - 0.25, (ears.damage < ears.maxHealth ? 0 : 1)) // Do not clear deafness if our ears are too damaged
- ears.damage = max(ears.damage - 0.025, 0)
- CHECK_TICK
+ ears.deaf = max(ears.deaf - 0.25 * delta_time, (ears.damage < ears.maxHealth ? 0 : 1)) // Do not clear deafness if our ears are too damaged
+ ears.damage = max(ears.damage - 0.025 * delta_time, 0)
+ CHECK_TICK // Reviewer: yes I really am afraid of 1000 clones with earmuffs
diff --git a/code/datums/elements/embed.dm b/code/datums/elements/embed.dm
new file mode 100644
index 0000000000000..f9bc5bd9c64ee
--- /dev/null
+++ b/code/datums/elements/embed.dm
@@ -0,0 +1,196 @@
+/*
+ The presence of this element allows an item (or a projectile carrying an item) to embed itself in a carbon when it is thrown into a target (whether by hand, gun, or explosive wave) with either
+ at least 4 throwspeed (EMBED_THROWSPEED_THRESHOLD) or ignore_throwspeed_threshold set to TRUE. Items meant to be used as shrapnel for projectiles should have ignore_throwspeed_threshold set to true.
+
+ Whether we're dealing with a direct /obj/item (throwing a knife at someone) or an /obj/projectile with a shrapnel_type, how we handle things plays out the same, with one extra step separating them.
+ Items simply make their COMSIG_MOVABLE_IMPACT_ZONE check, while projectiles check on COMSIG_PROJECTILE_SELF_ON_HIT.
+ Upon a projectile hitting a valid target, it spawns whatever type of payload it has defined, then has that try to embed itself in the target on its own.
+
+ Otherwise non-embeddable or stickable items can be made embeddable/stickable through wizard events/sticky tape/admin memes.
+*/
+
+/datum/element/embed
+ element_flags = ELEMENT_BESPOKE
+ id_arg_index = 2
+ var/initialized = FALSE /// whether we can skip assigning all the vars (since these are bespoke elements, we don't have to reset the vars every time we attach to something, we already know what we are!)
+
+ // all of this stuff is explained in _DEFINES/combat.dm
+ var/embed_chance
+ var/fall_chance
+ var/pain_chance
+ var/pain_mult
+ var/max_damage_mult
+ var/remove_pain_mult
+ var/rip_time
+ var/ignore_throwspeed_threshold
+ var/jostle_chance
+ var/jostle_pain_mult
+ var/pain_stam_pct
+ var/armour_block
+ var/payload_type
+
+/datum/element/embed/Attach(datum/target, embed_chance, fall_chance, pain_chance, pain_mult, max_damage_mult, remove_pain_mult, rip_time, ignore_throwspeed_threshold, jostle_chance, jostle_pain_mult, pain_stam_pct, armour_block, projectile_payload=/obj/item/shard)
+ . = ..()
+
+ if(!isitem(target) && !isprojectile(target))
+ return ELEMENT_INCOMPATIBLE
+
+ if(isitem(target))
+ RegisterSignal(target, COMSIG_MOVABLE_IMPACT_ZONE, .proc/checkEmbed)
+ RegisterSignal(target, COMSIG_ELEMENT_ATTACH, .proc/severancePackage)
+ RegisterSignal(target, COMSIG_PARENT_EXAMINE, .proc/examined)
+ RegisterSignal(target, COMSIG_EMBED_TRY_FORCE, .proc/tryForceEmbed)
+ RegisterSignal(target, COMSIG_ITEM_DISABLE_EMBED, .proc/detachFromWeapon)
+ if(!initialized)
+ src.embed_chance = embed_chance
+ src.fall_chance = fall_chance
+ src.pain_chance = pain_chance
+ src.pain_mult = pain_mult
+ src.max_damage_mult = max_damage_mult
+ src.remove_pain_mult = remove_pain_mult
+ src.rip_time = rip_time
+ src.ignore_throwspeed_threshold = ignore_throwspeed_threshold
+ src.jostle_chance = jostle_chance
+ src.jostle_pain_mult = jostle_pain_mult
+ src.pain_stam_pct = pain_stam_pct
+ src.armour_block = armour_block
+ initialized = TRUE
+ else
+ payload_type = projectile_payload
+ RegisterSignal(target, COMSIG_PROJECTILE_SELF_ON_HIT, .proc/checkEmbedProjectile)
+
+
+/datum/element/embed/Detach(obj/target)
+ . = ..()
+ if(isitem(target))
+ UnregisterSignal(target, list(COMSIG_MOVABLE_IMPACT_ZONE, COMSIG_ELEMENT_ATTACH, COMSIG_MOVABLE_IMPACT, COMSIG_PARENT_EXAMINE, COMSIG_EMBED_TRY_FORCE, COMSIG_ITEM_DISABLE_EMBED))
+ else
+ UnregisterSignal(target, list(COMSIG_PROJECTILE_SELF_ON_HIT))
+
+
+/// Checking to see if we're gonna embed into a human
+/datum/element/embed/proc/checkEmbed(obj/item/weapon, mob/living/carbon/victim, hit_zone, datum/thrownthing/throwingdatum, forced=FALSE)
+ SIGNAL_HANDLER
+
+ if(!istype(victim) || HAS_TRAIT(victim, TRAIT_PIERCEIMMUNE))
+ return
+
+ var/flying_speed = throwingdatum ? throwingdatum.speed : weapon.throw_speed
+
+ if(!forced && (flying_speed < EMBED_THROWSPEED_THRESHOLD && !ignore_throwspeed_threshold)) // check if it's a forced embed, and if not, if it's going fast enough to proc embedding
+ return
+
+ var/actual_chance = embed_chance
+
+ if(throwingdatum?.speed > weapon.throw_speed)
+ actual_chance += (throwingdatum.speed - weapon.throw_speed) * EMBEDDED_CHANCE_SPEED_BONUS
+
+ var/target_armour = 0
+
+ if(!weapon.isEmbedHarmless()) // all the armor in the world won't save you from a kick me sign
+ target_armour = victim.run_armor_check(hit_zone, armour_penetration = weapon.armour_penetration, silent = TRUE)
+
+ //Target has enough armour to block the embed.
+ if(target_armour >= armour_block)
+ victim.visible_message("[weapon] bounces off [victim]'s armor!", "[weapon] bounces off your armor!", vision_distance = COMBAT_MESSAGE_RANGE)
+ return
+
+ var/percentage_unblocked = 1 - (target_armour / armour_block)
+
+ if(!prob(actual_chance * percentage_unblocked))
+ return
+
+ var/obj/item/bodypart/limb = victim.get_bodypart(hit_zone) || pick(victim.bodyparts)
+ victim.AddComponent(/datum/component/embedded,\
+ weapon,\
+ throwingdatum,\
+ part = limb,\
+ embed_chance = embed_chance,\
+ fall_chance = fall_chance,\
+ pain_chance = pain_chance,\
+ pain_mult = pain_mult,\
+ remove_pain_mult = remove_pain_mult,\
+ rip_time = rip_time,\
+ ignore_throwspeed_threshold = ignore_throwspeed_threshold,\
+ jostle_chance = jostle_chance,\
+ jostle_pain_mult = jostle_pain_mult,\
+ pain_stam_pct = pain_stam_pct)
+
+ return TRUE
+
+///A different embed element has been attached, so we'll detach and let them handle things
+/datum/element/embed/proc/severancePackage(obj/item/weapon, datum/element/E)
+ SIGNAL_HANDLER
+
+ if(istype(E, /datum/element/embed))
+ Detach(weapon)
+
+///If we don't want to be embeddable anymore (deactivating an e-dagger for instance)
+/datum/element/embed/proc/detachFromWeapon(obj/weapon)
+ SIGNAL_HANDLER
+
+ Detach(weapon)
+
+///Someone inspected our embeddable item
+/datum/element/embed/proc/examined(obj/item/I, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+
+ if(I.isEmbedHarmless())
+ examine_list += "[I] feels sticky, and could probably get stuck to someone if thrown properly!"
+ else
+ examine_list += "[I] has a fine point, and could probably embed in someone if thrown properly!"
+
+/**
+ * checkEmbedProjectile() is what we get when a projectile with a defined shrapnel_type impacts a target.
+ *
+ * If we hit a valid target, we create the shrapnel_type object and immediately call tryEmbed() on it, targeting what we impacted. That will lead
+ * it to call tryForceEmbed() on its own embed element (it's out of our hands here, our projectile is done), where it will run through all the checks it needs to.
+ */
+/datum/element/embed/proc/checkEmbedProjectile(obj/item/projectile/P, atom/movable/firer, atom/hit, angle, hit_zone)
+ SIGNAL_HANDLER
+
+ if(!iscarbon(hit))
+ Detach(P)
+ return // we don't care
+
+ var/obj/item/payload = new payload_type(get_turf(hit))
+ var/mob/living/carbon/C = hit
+ var/obj/item/bodypart/limb = C.get_bodypart(hit_zone)
+ if(!limb)
+ limb = C.get_bodypart()
+
+ if(!payload.tryEmbed(limb))
+ payload.failedEmbed()
+ Detach(P)
+
+/**
+ * tryForceEmbed() is called here when we fire COMSIG_EMBED_TRY_FORCE from [/obj/item/proc/tryEmbed]. Mostly, this means we're a piece of shrapnel from a projectile that just impacted something, and we're trying to embed in it.
+ *
+ * The reason for this extra mucking about is avoiding having to do an extra hitby(), and annoying the target by impacting them once with the projectile, then again with the shrapnel, and possibly
+ * AGAIN if we actually embed. This way, we save on at least one message.
+ *
+ * Arguments:
+ * * I- the item we're trying to insert into the target
+ * * target- what we're trying to shish-kabob, either a bodypart or a carbon
+ * * hit_zone- if our target is a carbon, try to hit them in this zone, if we don't have one, pick a random one. If our target is a bodypart, we already know where we're hitting.
+ * * forced- if we want this to succeed 100%
+ */
+/datum/element/embed/proc/tryForceEmbed(obj/item/I, atom/target, hit_zone, forced=FALSE)
+ SIGNAL_HANDLER
+
+ var/obj/item/bodypart/limb
+ var/mob/living/carbon/C
+
+ if(!forced && !prob(embed_chance))
+ return
+
+ if(iscarbon(target))
+ C = target
+ if(!hit_zone)
+ limb = pick(C.bodyparts)
+ hit_zone = limb.body_zone
+ else if(isbodypart(target))
+ limb = target
+ C = limb.owner
+
+ return checkEmbed(I, C, hit_zone, forced=TRUE)
diff --git a/code/datums/elements/firestacker.dm b/code/datums/elements/firestacker.dm
index 65857732bc0fa..125cb593ff4be 100644
--- a/code/datums/elements/firestacker.dm
+++ b/code/datums/elements/firestacker.dm
@@ -9,12 +9,12 @@
/datum/element/firestacker/Attach(datum/target, amount)
. = ..()
-
+
if(!ismovableatom(target))
return ELEMENT_INCOMPATIBLE
-
+
src.amount = amount
-
+
RegisterSignal(target, COMSIG_MOVABLE_IMPACT, .proc/impact, override = TRUE)
if(isitem(target))
RegisterSignal(target, COMSIG_ITEM_ATTACK, .proc/item_attack)
@@ -28,13 +28,19 @@
target.adjust_fire_stacks(amount)
/datum/element/firestacker/proc/impact(datum/source, atom/hit_atom, datum/thrownthing/throwingdatum)
+ SIGNAL_HANDLER
+
if(isliving(hit_atom))
stack_on(source, hit_atom)
/datum/element/firestacker/proc/item_attack(datum/source, atom/movable/target, mob/living/user)
+ SIGNAL_HANDLER
+
if(isliving(target))
stack_on(source, target)
/datum/element/firestacker/proc/item_attack_self(datum/source, mob/user)
+ SIGNAL_HANDLER
+
if(isliving(user))
stack_on(source, user)
diff --git a/code/datums/embedding_behavior.dm b/code/datums/embedding_behavior.dm
deleted file mode 100644
index d4181f94344c2..0000000000000
--- a/code/datums/embedding_behavior.dm
+++ /dev/null
@@ -1,58 +0,0 @@
-#define EMBEDID "embed-[embed_chance]-[embedded_fall_chance]-[embedded_pain_chance]-[embedded_pain_multiplier]-[embedded_fall_pain_multiplier]-[embedded_impact_pain_multiplier]-[embedded_unsafe_removal_pain_multiplier]-[embedded_unsafe_removal_time]-[embedded_ignore_throwspeed_threshold]"
-
-/proc/getEmbeddingBehavior(embed_chance = EMBED_CHANCE,
- embedded_fall_chance = EMBEDDED_ITEM_FALLOUT,
- embedded_pain_chance = EMBEDDED_PAIN_CHANCE,
- embedded_pain_multiplier = EMBEDDED_PAIN_MULTIPLIER,
- embedded_fall_pain_multiplier = EMBEDDED_FALL_PAIN_MULTIPLIER,
- embedded_impact_pain_multiplier = EMBEDDED_IMPACT_PAIN_MULTIPLIER,
- embedded_unsafe_removal_pain_multiplier = EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER,
- embedded_unsafe_removal_time = EMBEDDED_UNSAFE_REMOVAL_TIME,
- embedded_ignore_throwspeed_threshold = FALSE)
- . = locate(EMBEDID)
- if (!.)
- . = new /datum/embedding_behavior(embed_chance, embedded_fall_chance, embedded_pain_chance, embedded_pain_multiplier, embedded_fall_pain_multiplier, embedded_impact_pain_multiplier, embedded_unsafe_removal_pain_multiplier, embedded_unsafe_removal_time, embedded_ignore_throwspeed_threshold)
-
-/datum/embedding_behavior
- var/embed_chance
- var/embedded_fall_chance
- var/embedded_pain_chance
- var/embedded_pain_multiplier //The coefficient of multiplication for the damage this item does while embedded (this*w_class)
- var/embedded_fall_pain_multiplier //The coefficient of multiplication for the damage this item does when falling out of a limb (this*w_class)
- var/embedded_impact_pain_multiplier //The coefficient of multiplication for the damage this item does when first embedded (this*w_class)
- var/embedded_unsafe_removal_pain_multiplier //The coefficient of multiplication for the damage removing this without surgery causes (this*w_class)
- var/embedded_unsafe_removal_time //A time in ticks, multiplied by the w_class.
- var/embedded_ignore_throwspeed_threshold //if we don't give a damn about EMBED_THROWSPEED_THRESHOLD
-
-/datum/embedding_behavior/New(embed_chance = EMBED_CHANCE,
- embedded_fall_chance = EMBEDDED_ITEM_FALLOUT,
- embedded_pain_chance = EMBEDDED_PAIN_CHANCE,
- embedded_pain_multiplier = EMBEDDED_PAIN_MULTIPLIER,
- embedded_fall_pain_multiplier = EMBEDDED_FALL_PAIN_MULTIPLIER,
- embedded_impact_pain_multiplier = EMBEDDED_IMPACT_PAIN_MULTIPLIER,
- embedded_unsafe_removal_pain_multiplier = EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER,
- embedded_unsafe_removal_time = EMBEDDED_UNSAFE_REMOVAL_TIME,
- embedded_ignore_throwspeed_threshold = FALSE)
- src.embed_chance = embed_chance
- src.embedded_fall_chance = embedded_fall_chance
- src.embedded_pain_chance = embedded_pain_chance
- src.embedded_pain_multiplier = embedded_pain_multiplier
- src.embedded_fall_pain_multiplier = embedded_fall_pain_multiplier
- src.embedded_impact_pain_multiplier = embedded_impact_pain_multiplier
- src.embedded_unsafe_removal_pain_multiplier = embedded_unsafe_removal_pain_multiplier
- src.embedded_unsafe_removal_time = embedded_unsafe_removal_time
- src.embedded_ignore_throwspeed_threshold = embedded_ignore_throwspeed_threshold
- tag = EMBEDID
-
-/datum/embedding_behavior/proc/setRating(embed_chance, embedded_fall_chance, embedded_pain_chance, embedded_pain_multiplier, embedded_fall_pain_multiplier, embedded_impact_pain_multiplier, embedded_unsafe_removal_pain_multiplier, embedded_unsafe_removal_time, embedded_ignore_throwspeed_threshold)
- return getEmbeddingBehavior((isnull(embed_chance) ? src.embed_chance : embed_chance),\
- (isnull(embedded_fall_chance) ? src.embedded_fall_chance : embedded_fall_chance),\
- (isnull(embedded_pain_chance) ? src.embedded_pain_chance : embedded_pain_chance),\
- (isnull(embedded_pain_multiplier) ? src.embedded_pain_multiplier : embedded_pain_multiplier),\
- (isnull(embedded_fall_pain_multiplier) ? src.embedded_fall_pain_multiplier : embedded_fall_pain_multiplier),\
- (isnull(embedded_impact_pain_multiplier) ? src.embedded_impact_pain_multiplier : embedded_impact_pain_multiplier),\
- (isnull(embedded_unsafe_removal_pain_multiplier) ? src.embedded_unsafe_removal_pain_multiplier : embedded_unsafe_removal_pain_multiplier),\
- (isnull(embedded_unsafe_removal_time) ? src.embedded_unsafe_removal_time : embedded_unsafe_removal_time),\
- (isnull(embedded_ignore_throwspeed_threshold) ? src.embedded_ignore_throwspeed_threshold : embedded_ignore_throwspeed_threshold))
-
-#undef EMBEDID
diff --git a/code/datums/emotes.dm b/code/datums/emotes.dm
index 43193a9628ddd..11cb499c38bb2 100644
--- a/code/datums/emotes.dm
+++ b/code/datums/emotes.dm
@@ -57,13 +57,13 @@
if(!msg)
return
+ user.log_message(msg, LOG_EMOTE)
+
var/end = copytext(msg, length(message))
if(!(end in list("!", ".", "?", ":", "\"", "-")))
msg += "."
- user.log_message(msg, LOG_EMOTE)
-
- msg = "[user] " + msg + ""
+ var/dchatmsg = "[user] [msg]"
var/tmp_sound = get_sound(user)
if(tmp_sound && (!only_forced_audio || !intentional))
@@ -74,12 +74,12 @@
continue
var/T = get_turf(user)
if(M.stat == DEAD && M.client && (M.client.prefs.chat_toggles & CHAT_GHOSTSIGHT) && !(M in viewers(T, null)))
- M.show_message(msg)
+ M.show_message("[FOLLOW_LINK(M, user)] [dchatmsg]")
if(emote_type == EMOTE_AUDIBLE)
- user.audible_message(msg)
+ user.audible_message(msg, audible_message_flags = list(CHATMESSAGE_EMOTE = TRUE))
else
- user.visible_message(msg)
+ user.visible_message(msg, visible_message_flags = list(CHATMESSAGE_EMOTE = TRUE))
/datum/emote/proc/get_sound(mob/living/user)
return sound //by default just return this var.
@@ -153,3 +153,26 @@
var/mob/living/L = user
if(HAS_TRAIT(L, TRAIT_EMOTEMUTE))
return FALSE
+
+/mob/proc/manual_emote(text) //Just override the song and dance
+ . = TRUE
+ if(stat != CONSCIOUS)
+ return
+
+ if(!text)
+ CRASH("Someone passed nothing to manual_emote(), fix it")
+
+ log_message(text, LOG_EMOTE)
+
+ var/ghost_text = "[src] [text]"
+
+ var/origin_turf = get_turf(src)
+ if(client)
+ for(var/mob/ghost as anything in GLOB.dead_mob_list)
+ if(!ghost.client || isnewplayer(ghost))
+ continue
+ if(ghost.client.prefs.chat_toggles & CHAT_GHOSTSIGHT && !(ghost in viewers(origin_turf, null)))
+ ghost.show_message("[FOLLOW_LINK(ghost, src)] [ghost_text]")
+
+ visible_message(text, visible_message_flags = list(CHATMESSAGE_EMOTE = TRUE))
+
diff --git a/code/datums/ert.dm b/code/datums/ert.dm
index be0af9107c492..6f183c8adae8d 100644
--- a/code/datums/ert.dm
+++ b/code/datums/ert.dm
@@ -99,3 +99,13 @@
mission = "HONK them into submission"
polldesc = "an elite Nanotrasen tactical pranking squad"
code = "HOOOOOOOOOONK"
+
+/datum/ert/kudzu
+ roles = list(/datum/antagonist/ert/kudzu)
+ leader_role = /datum/antagonist/ert/kudzu
+ teamsize = 5
+ opendoors = FALSE
+ rename_team = "Weed Whackers"
+ mission = "Eliminate the kudzu with extreme prejudice"
+ polldesc = "an elite gardening team"
+ code = "Vine Green"
diff --git a/code/datums/explosion.dm b/code/datums/explosion.dm
deleted file mode 100644
index 4f82da7ba4b9e..0000000000000
--- a/code/datums/explosion.dm
+++ /dev/null
@@ -1,470 +0,0 @@
-#define EXPLOSION_THROW_SPEED 4
-#define CREAK_DELAY 5 SECONDS //Time taken for the creak to play after explosion, if applicable.
-#define DEVASTATION_PROB 30 //The probability modifier for devistation, maths!
-#define HEAVY_IMPACT_PROB 5 //ditto
-#define FAR_UPPER 60 //Upper limit for the far_volume, distance, clamped.
-#define FAR_LOWER 40 //lower limit for the far_volume, distance, clamped.
-#define PROB_SOUND 75 //The probability modifier for a sound to be an echo, or a far sound. (0-100)
-#define SHAKE_CLAMP 2.5 //The limit for how much the camera can shake for out of view booms.
-#define FREQ_UPPER 40 //The upper limit for the randomly selected frequency.
-#define FREQ_LOWER 25 //The lower of the above.
-
-
-GLOBAL_LIST_EMPTY(explosions)
-//Against my better judgement, I will return the explosion datum
-//If I see any GC errors for it I will find you
-//and I will gib you
-/proc/explosion(atom/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog = TRUE, ignorecap = FALSE, flame_range = 0, silent = FALSE, smoke = FALSE)
- return new /datum/explosion(epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog, ignorecap, flame_range, silent, smoke)
-
-//This datum creates 3 async tasks
-//1 GatherSpiralTurfsProc runs spiral_range_turfs(tick_checked = TRUE) to populate the affected_turfs list
-//2 CaculateExplosionBlock adds the blockings to the cached_exp_block list
-//3 The main thread explodes the prepared turfs
-
-/datum/explosion
- var/explosion_id
- var/atom/explosion_source
- var/started_at
- var/running = TRUE
- var/stopped = 0 //This is the number of threads stopped !DOESN'T COUNT THREAD 2!
- var/static/id_counter = 0
-
-#define EX_PREPROCESS_EXIT_CHECK \
- if(!running) {\
- stopped = 2;\
- qdel(src);\
- return;\
- }
-
-#define EX_PREPROCESS_CHECK_TICK \
- if(TICK_CHECK) {\
- stoplag();\
- EX_PREPROCESS_EXIT_CHECK\
- }
-
-/datum/explosion/New(atom/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog, ignorecap, flame_range, silent, smoke)
- set waitfor = FALSE
-
- var/id = ++id_counter
- explosion_id = id
- explosion_source = epicenter
-
- epicenter = get_turf(epicenter)
- if(!epicenter)
- return
-
- GLOB.explosions += src
- if(isnull(flame_range))
- flame_range = light_impact_range
- if(isnull(flash_range))
- flash_range = devastation_range
-
- // Archive the uncapped explosion for the doppler array
- var/orig_dev_range = devastation_range
- var/orig_heavy_range = heavy_impact_range
- var/orig_light_range = light_impact_range
-
- var/orig_max_distance = max(devastation_range, heavy_impact_range, light_impact_range, flash_range, flame_range)
-
- //Zlevel specific bomb cap multiplier
- var/cap_multiplier = SSmapping.level_trait(epicenter.z, ZTRAIT_BOMBCAP_MULTIPLIER)
- if (isnull(cap_multiplier))
- cap_multiplier = 1
-
- if(!ignorecap)
- devastation_range = min(GLOB.MAX_EX_DEVESTATION_RANGE * cap_multiplier, devastation_range)
- heavy_impact_range = min(GLOB.MAX_EX_HEAVY_RANGE * cap_multiplier, heavy_impact_range)
- light_impact_range = min(GLOB.MAX_EX_LIGHT_RANGE * cap_multiplier, light_impact_range)
- flash_range = min(GLOB.MAX_EX_FLASH_RANGE * cap_multiplier, flash_range)
- flame_range = min(GLOB.MAX_EX_FLAME_RANGE * cap_multiplier, flame_range)
-
- //DO NOT REMOVE THIS STOPLAG, IT BREAKS THINGS
- //not sleeping causes us to ex_act() the thing that triggered the explosion
- //doing that might cause it to trigger another explosion
- //this is bad
- //I would make this not ex_act the thing that triggered the explosion,
- //but everything that explodes gives us their loc or a get_turf()
- //and somethings expect us to ex_act them so they can qdel()
- stoplag() //tldr, let the calling proc call qdel(src) before we explode
-
- EX_PREPROCESS_EXIT_CHECK
-
- started_at = REALTIMEOFDAY
-
- var/max_range = max(devastation_range, heavy_impact_range, light_impact_range, flame_range)
-
- if(adminlog)
- message_admins("Explosion with size ([devastation_range], [heavy_impact_range], [light_impact_range], [flame_range]) in [ADMIN_VERBOSEJMP(epicenter)]")
- log_game("Explosion with size ([devastation_range], [heavy_impact_range], [light_impact_range], [flame_range]) in [loc_name(epicenter)]")
-
- var/x0 = epicenter.x
- var/y0 = epicenter.y
- var/z0 = epicenter.z
- var/area/areatype = get_area(epicenter)
- SSblackbox.record_feedback("associative", "explosion", 1, list("dev" = devastation_range, "heavy" = heavy_impact_range, "light" = light_impact_range, "flash" = flash_range, "flame" = flame_range, "orig_dev" = orig_dev_range, "orig_heavy" = orig_heavy_range, "orig_light" = orig_light_range, "x" = x0, "y" = y0, "z" = z0, "area" = areatype.type, "time" = time_stamp("YYYY-MM-DD hh:mm:ss", 1)))
-
- // Play sounds; we want sounds to be different depending on distance so we will manually do it ourselves.
- // Stereo users will also hear the direction of the explosion!
-
- // Calculate far explosion sound range. Only allow the sound effect for heavy/devastating explosions.
- // 3/7/14 will calculate to 80 + 35
-
- var/far_dist = 0
- far_dist += heavy_impact_range * 15
- far_dist += devastation_range * 20
-
- if(!silent)
- var/frequency = get_rand_frequency()
- var/sound/explosion_sound = sound(get_sfx("explosion"))
- var/sound/far_explosion_sound = sound('sound/effects/explosionfar.ogg')
- var/sound/creaking_explosion_sound = sound(get_sfx("explosion_creaking"))
- var/sound/hull_creaking_sound = sound(get_sfx("hull_creaking"))
- var/sound/explosion_echo_sound = sound('sound/effects/explosion_distant.ogg')
- var/on_station = SSmapping.level_trait(epicenter.z, ZTRAIT_STATION)
- var/creaking_explosion = FALSE
-
- if(prob(devastation_range*DEVASTATION_PROB+heavy_impact_range*HEAVY_IMPACT_PROB) && on_station) // Huge explosions are near guaranteed to make the station creak and whine, smaller ones might.
- creaking_explosion = TRUE // prob over 100 always returns true
-
- for(var/mob/M in GLOB.player_list)
- // Double check for client
- var/turf/M_turf = get_turf(M)
- if(M_turf && M_turf.z == z0)
- var/dist = get_dist(M_turf, epicenter)
- var/baseshakeamount
- if(orig_max_distance - dist > 0)
- baseshakeamount = sqrt((orig_max_distance - dist)*0.1)
- // If inside the blast radius + world.view (x) - 2
- if(dist <= round(max_range + getviewsize(world.view)[1] - 2, 1))
- M.playsound_local(epicenter, null, 100, 1, frequency, falloff = 5, S = explosion_sound)
- if(baseshakeamount > 0)
- shake_camera(M, 25, CLAMP(baseshakeamount, 0, 10))
- // You hear a far explosion if you're outside the blast radius. Small bombs shouldn't be heard all over the station.
- else if(dist <= far_dist)
- var/far_volume = clamp(far_dist/2, FAR_LOWER, FAR_UPPER) // Volume is based on explosion size and dist
- if(creaking_explosion)
- M.playsound_local(epicenter, null, far_volume, 1, frequency, S = creaking_explosion_sound, turf_source = 0)
- else if(prob(PROB_SOUND)) // Sound variety during meteor storm/tesloose/other bad event
- M.playsound_local(epicenter, null, far_volume, 1, frequency, S = far_explosion_sound, turf_source = 0) // Far sound
- else
- M.playsound_local(epicenter, null, far_volume, 1, frequency, S = explosion_echo_sound, turf_source = 0) // Echo sound
-
- if(baseshakeamount > 0 || devastation_range)
- if(!baseshakeamount) // Devastating explosions rock the station and ground
- baseshakeamount = devastation_range*3
- shake_camera(M, 10, clamp(baseshakeamount*0.25, 0, SHAKE_CLAMP))
- else if(!isspaceturf(get_turf(M)) && heavy_impact_range) // Big enough explosions echo throughout the hull
- var/echo_volume = 40
- if(devastation_range)
- baseshakeamount = devastation_range
- shake_camera(M, 10, clamp(baseshakeamount*0.25, 0, SHAKE_CLAMP))
- echo_volume = 60
- M.playsound_local(epicenter, null, echo_volume, 1, frequency, S = explosion_echo_sound, turf_source = 0)
-
- if(creaking_explosion) // 5 seconds after the bang, the station begins to creak
- addtimer(CALLBACK(M, /mob/proc/playsound_local, epicenter, null, rand(FREQ_LOWER, FREQ_UPPER), 1, frequency, null, null, FALSE, hull_creaking_sound, 0), CREAK_DELAY)
- EX_PREPROCESS_CHECK_TICK
-
- //postpone processing for a bit
- var/postponeCycles = max(round(devastation_range/8),1)
- SSlighting.postpone(postponeCycles)
- SSmachines.postpone(postponeCycles)
-
- if(heavy_impact_range > 1)
- var/datum/effect_system/explosion/E
- if(smoke)
- E = new /datum/effect_system/explosion/smoke
- else
- E = new
- E.set_up(epicenter)
- E.start()
-
- EX_PREPROCESS_CHECK_TICK
-
- //flash mobs
- if(flash_range)
- for(var/mob/living/L in viewers(flash_range, epicenter))
- L.flash_act()
-
- EX_PREPROCESS_CHECK_TICK
-
- var/list/exploded_this_tick = list() //open turfs that need to be blocked off while we sleep
- var/list/affected_turfs = GatherSpiralTurfs(max_range, epicenter)
-
- var/reactionary = CONFIG_GET(flag/reactionary_explosions)
- var/list/cached_exp_block
-
- if(reactionary)
- cached_exp_block = CaculateExplosionBlock(affected_turfs)
-
- //lists are guaranteed to contain at least 1 turf at this point
-
- var/iteration = 0
- var/affTurfLen = affected_turfs.len
- var/expBlockLen = cached_exp_block.len
- for(var/TI in affected_turfs)
- var/turf/T = TI
- ++iteration
- var/init_dist = cheap_hypotenuse(T.x, T.y, x0, y0)
- var/dist = init_dist
-
- if(reactionary)
- var/turf/Trajectory = T
- while(Trajectory != epicenter)
- Trajectory = get_step_towards(Trajectory, epicenter)
- dist += cached_exp_block[Trajectory]
-
- var/flame_dist = dist < flame_range
- var/throw_dist = dist
-
- if(dist < devastation_range)
- dist = EXPLODE_DEVASTATE
- else if(dist < heavy_impact_range)
- dist = EXPLODE_HEAVY
- else if(dist < light_impact_range)
- dist = EXPLODE_LIGHT
- else
- dist = EXPLODE_NONE
-
- //------- EX_ACT AND TURF FIRES -------
-
- if(T == epicenter) // Ensures explosives detonating from bags trigger other explosives in that bag
- var/list/items = list()
- for(var/I in T)
- var/atom/A = I
- if (!A.prevent_content_explosion()) //The atom/contents_explosion() proc returns null if the contents ex_acting has been handled by the atom, and TRUE if it hasn't.
- items += A.GetAllContents()
- for(var/O in items)
- var/atom/A = O
- if(!QDELETED(A))
- A.ex_act(dist)
-
- if(flame_dist && prob(40) && !isspaceturf(T) && !T.density)
- new /obj/effect/hotspot(T) //Mostly for ambience!
-
- if(dist > EXPLODE_NONE)
- T.explosion_level = max(T.explosion_level, dist) //let the bigger one have it
- T.explosion_id = id
- T.ex_act(dist)
- exploded_this_tick += T
-
- //--- THROW ITEMS AROUND ---
-
- var/throw_dir = get_dir(epicenter,T)
- for(var/obj/item/I in T)
- if(!I.anchored)
- var/throw_range = rand(throw_dist, max_range)
- var/turf/throw_at = get_ranged_target_turf(I, throw_dir, throw_range)
- I.throw_at(throw_at, throw_range, EXPLOSION_THROW_SPEED)
-
- for(var/mob/living/L in T)
- if(!L.anchored)
- var/throw_range = rand(throw_dist, max_range)
- var/turf/throw_at = get_ranged_target_turf(L, throw_dir, throw_range)
- L.throw_at(throw_at, throw_range, EXPLOSION_THROW_SPEED)
-
- //wait for the lists to repop
- var/break_condition
- if(reactionary)
- //If we've caught up to the density checker thread and there are no more turfs to process
- break_condition = iteration == expBlockLen && iteration < affTurfLen
- else
- //If we've caught up to the turf gathering thread and it's still running
- break_condition = iteration == affTurfLen && !stopped
-
- if(break_condition || TICK_CHECK)
- stoplag()
-
- if(!running)
- break
-
- //update the trackers
- affTurfLen = affected_turfs.len
- expBlockLen = cached_exp_block.len
-
- if(break_condition)
- if(reactionary)
- //until there are more block checked turfs than what we are currently at
- //or the explosion has stopped
- UNTIL(iteration < affTurfLen || !running)
- else
- //until there are more gathered turfs than what we are currently at
- //or there are no more turfs to gather/the explosion has stopped
- UNTIL(iteration < expBlockLen || stopped)
-
- if(!running)
- break
-
- //update the trackers
- affTurfLen = affected_turfs.len
- expBlockLen = cached_exp_block.len
-
- var/circumference = (PI * (init_dist + 4) * 2) //+4 to radius to prevent shit gaps
- if(exploded_this_tick.len > circumference) //only do this every revolution
- for(var/Unexplode in exploded_this_tick)
- var/turf/UnexplodeT = Unexplode
- UnexplodeT.explosion_level = 0
- exploded_this_tick.Cut()
-
- //unfuck the shit
- for(var/Unexplode in exploded_this_tick)
- var/turf/UnexplodeT = Unexplode
- UnexplodeT.explosion_level = 0
- exploded_this_tick.Cut()
-
- var/took = (REALTIMEOFDAY - started_at) / 10
-
- //You need to press the DebugGame verb to see these now....they were getting annoying and we've collected a fair bit of data. Just -test- changes to explosion code using this please so we can compare
- if(GLOB.Debug2)
- log_world("## DEBUG: Explosion([x0],[y0],[z0])(d[devastation_range],h[heavy_impact_range],l[light_impact_range]): Took [took] seconds.")
-
- if(running) //if we aren't in a hurry
- //Machines which report explosions.
- for(var/array in GLOB.doppler_arrays)
- var/obj/machinery/doppler_array/A = array
- A.sense_explosion(epicenter, devastation_range, heavy_impact_range, light_impact_range, took,orig_dev_range, orig_heavy_range, orig_light_range)
-
- ++stopped
- qdel(src)
-
-#undef EX_PREPROCESS_EXIT_CHECK
-#undef EX_PREPROCESS_CHECK_TICK
-
-
-
-//asyncly populate the affected_turfs list
-/datum/explosion/proc/GatherSpiralTurfs(range, turf/epicenter)
- set waitfor = FALSE
- . = list()
- spiral_range_turfs(range, epicenter, outlist = ., tick_checked = TRUE)
- ++stopped
-
-/datum/explosion/proc/CaculateExplosionBlock(list/affected_turfs)
- set waitfor = FALSE
-
- . = list()
- var/processed = 0
- while(running)
- var/I
- for(I in (processed + 1) to affected_turfs.len) // we cache the explosion block rating of every turf in the explosion area
- var/turf/T = affected_turfs[I]
- var/current_exp_block = T.density ? T.explosion_block : 0
-
- for(var/obj/O in T)
- var/the_block = O.explosion_block
- current_exp_block += the_block == EXPLOSION_BLOCK_PROC ? O.GetExplosionBlock() : the_block
-
- .[T] = current_exp_block
-
- if(TICK_CHECK)
- break
-
- processed = I
- stoplag()
-
-/datum/explosion/Destroy()
- running = FALSE
- if(stopped < 2) //wait for main thread and spiral_range thread
- return QDEL_HINT_IWILLGC
- GLOB.explosions -= src
- explosion_source = null
- return ..()
-
-/client/proc/check_bomb_impacts()
- set name = "Check Bomb Impact"
- set category = "Debug"
-
- var/newmode = alert("Use reactionary explosions?","Check Bomb Impact", "Yes", "No")
- var/turf/epicenter = get_turf(mob)
- if(!epicenter)
- return
-
- var/dev = 0
- var/heavy = 0
- var/light = 0
- var/list/choices = list("Small Bomb","Medium Bomb","Big Bomb","Custom Bomb")
- var/choice = input("Bomb Size?") in choices
- switch(choice)
- if(null)
- return 0
- if("Small Bomb")
- dev = 1
- heavy = 2
- light = 3
- if("Medium Bomb")
- dev = 2
- heavy = 3
- light = 4
- if("Big Bomb")
- dev = 3
- heavy = 5
- light = 7
- if("Custom Bomb")
- dev = input("Devastation range (Tiles):") as num
- heavy = input("Heavy impact range (Tiles):") as num
- light = input("Light impact range (Tiles):") as num
-
- var/max_range = max(dev, heavy, light)
- var/x0 = epicenter.x
- var/y0 = epicenter.y
- var/list/wipe_colours = list()
- for(var/turf/T in spiral_range_turfs(max_range, epicenter))
- wipe_colours += T
- var/dist = cheap_hypotenuse(T.x, T.y, x0, y0)
-
- if(newmode == "Yes")
- var/turf/TT = T
- while(TT != epicenter)
- TT = get_step_towards(TT,epicenter)
- if(TT.density)
- dist += TT.explosion_block
-
- for(var/obj/O in T)
- var/the_block = O.explosion_block
- dist += the_block == EXPLOSION_BLOCK_PROC ? O.GetExplosionBlock() : the_block
-
- if(dist < dev)
- T.color = "red"
- T.maptext = "Dev"
- else if (dist < heavy)
- T.color = "yellow"
- T.maptext = "Heavy"
- else if (dist < light)
- T.color = "blue"
- T.maptext = "Light"
- else
- continue
-
- addtimer(CALLBACK(GLOBAL_PROC, .proc/wipe_color_and_text, wipe_colours), 100)
-
-/proc/wipe_color_and_text(list/atom/wiping)
- for(var/i in wiping)
- var/atom/A = i
- A.color = null
- A.maptext = ""
-
-/proc/dyn_explosion(turf/epicenter, power, flash_range, adminlog = TRUE, ignorecap = TRUE, flame_range = 0, silent = FALSE, smoke = TRUE)
- if(!power)
- return
- var/range = 0
- range = round((2 * power)**GLOB.DYN_EX_SCALE)
- explosion(epicenter, round(range * 0.25), round(range * 0.5), round(range), flash_range*range, adminlog, ignorecap, flame_range*range, silent, smoke)
-
-// Using default dyn_ex scale:
-// 100 explosion power is a (5, 10, 20) explosion.
-// 75 explosion power is a (4, 8, 17) explosion.
-// 50 explosion power is a (3, 7, 14) explosion.
-// 25 explosion power is a (2, 5, 10) explosion.
-// 10 explosion power is a (1, 3, 6) explosion.
-// 5 explosion power is a (0, 1, 3) explosion.
-// 1 explosion power is a (0, 0, 1) explosion.
-#undef CREAK_DELAY
-#undef DEVASTATION_PROB
-#undef HEAVY_IMPACT_PROB
-#undef FAR_UPPER
-#undef FAR_LOWER
-#undef PROB_SOUND
-#undef SHAKE_CLAMP
-#undef FREQ_UPPER
-#undef FREQ_LOWER
\ No newline at end of file
diff --git a/code/datums/helper_datums/events.dm b/code/datums/helper_datums/events.dm
index e6e4061671cdf..249af3181cc93 100644
--- a/code/datums/helper_datums/events.dm
+++ b/code/datums/helper_datums/events.dm
@@ -36,7 +36,7 @@
// Arguments: event_type as text, any number of additional arguments to pass to event handler
// Returns: null
/datum/events/proc/fireEvent(eventName, ...)
- var/list/event = listgetindex(events,eventName)
+ var/list/event = LAZYACCESS(events,eventName)
if(istype(event))
for(var/E in event)
var/datum/callback/cb = E
@@ -48,7 +48,7 @@
/datum/events/proc/clearEvent(event_type as text, datum/callback/cb)
if(!event_type || !cb)
return FALSE
- var/list/event = listgetindex(events,event_type)
+ var/list/event = LAZYACCESS(events,event_type)
event -= cb
qdel(cb)
return TRUE
diff --git a/code/datums/helper_datums/getrev.dm b/code/datums/helper_datums/getrev.dm
index b566b37ab438b..42a10b6e90c45 100644
--- a/code/datums/helper_datums/getrev.dm
+++ b/code/datums/helper_datums/getrev.dm
@@ -5,16 +5,18 @@
var/list/testmerge = list()
/datum/getrev/New()
+ commit = rustg_git_revparse("HEAD")
+ if(commit)
+ date = rustg_git_commit_date(commit)
+ originmastercommit = rustg_git_revparse("origin/master")
+
+/datum/getrev/proc/load_tgs_info()
testmerge = world.TgsTestMerges()
var/datum/tgs_revision_information/revinfo = world.TgsRevision()
if(revinfo)
commit = revinfo.commit
originmastercommit = revinfo.origin_commit
- else
- commit = rustg_git_revparse("HEAD")
- if(commit)
- date = rustg_git_commit_date(commit)
- originmastercommit = rustg_git_revparse("origin/master")
+ date = revinfo.timestamp || rustg_git_commit_date(commit)
// goes to DD log and config_error.txt
log_world(get_log_message())
@@ -27,8 +29,8 @@
for(var/line in testmerge)
var/datum/tgs_revision_information/test_merge/tm = line
- msg += "Test merge active of PR #[tm.number] commit [tm.pull_request_commit]"
- SSblackbox.record_feedback("associative", "testmerged_prs", 1, list("number" = "[tm.number]", "commit" = "[tm.pull_request_commit]", "title" = "[tm.title]", "author" = "[tm.author]"))
+ msg += "Test merge active of PR #[tm.number] commit [tm.head_commit]"
+ SSblackbox.record_feedback("associative", "testmerged_prs", 1, list("number" = "[tm.number]", "commit" = "[tm.head_commit]", "title" = "[tm.title]", "author" = "[tm.author]"))
if(commit && commit != originmastercommit)
msg += "HEAD: [commit]"
@@ -43,7 +45,7 @@
. = header ? "The following pull requests are currently test merged: " : ""
for(var/line in testmerge)
var/datum/tgs_revision_information/test_merge/tm = line
- var/cm = tm.pull_request_commit
+ var/cm = tm.head_commit
var/details = ": '" + html_encode(tm.title) + "' by " + html_encode(tm.author) + " at commit " + html_encode(copytext_char(cm, 1, 11))
if(details && findtext(details, "\[s\]") && (!usr || !usr.client.holder))
continue
@@ -77,7 +79,9 @@
msg += "No commit information"
if(world.TgsAvailable())
var/datum/tgs_version/version = world.TgsVersion()
- msg += "Server tools 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]"
// Game mode odds
msg += " Current Informational Settings:"
diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm
index 29c51577a594f..ac3d91f6ca59d 100644
--- a/code/datums/helper_datums/teleport.dm
+++ b/code/datums/helper_datums/teleport.dm
@@ -8,7 +8,7 @@
// forceMove: if false, teleport will use Move() proc (dense objects will prevent teleportation)
// no_effects: disable the default effectin/effectout of sparks
// forced: whether or not to ignore no_teleport
-/proc/do_teleport(atom/movable/teleatom, atom/destination, precision=null, forceMove = TRUE, datum/effect_system/effectin=null, datum/effect_system/effectout=null, asoundin=null, asoundout=null, no_effects=FALSE, channel=TELEPORT_CHANNEL_BLUESPACE, forced = FALSE)
+/proc/do_teleport(atom/movable/teleatom, atom/destination, precision=null, forceMove = TRUE, datum/effect_system/effectin=null, datum/effect_system/effectout=null, asoundin=null, asoundout=null, no_effects=FALSE, channel=TELEPORT_CHANNEL_BLUESPACE, forced = FALSE, teleport_mode = TELEPORT_MODE_DEFAULT)
// teleporting most effects just deletes them
var/static/list/delete_atoms = typecacheof(list(
/obj/effect,
@@ -16,6 +16,8 @@
/obj/effect/dummy/chameleon,
/obj/effect/wisp,
/obj/effect/mob_spawn,
+ /obj/effect/warp_cube,
+ /obj/effect/extraction_holder,
))
if(delete_atoms[teleatom.type])
qdel(teleatom)
@@ -56,7 +58,11 @@
var/area/A = get_area(curturf)
var/area/B = get_area(destturf)
- if(!forced && (HAS_TRAIT(teleatom, TRAIT_NO_TELEPORT) || A.noteleport || B.noteleport))
+ if(!forced && (HAS_TRAIT(teleatom, TRAIT_NO_TELEPORT)))
+ return FALSE
+
+ //Either area has teleport restriction and teleport mode isn't allowed in that area
+ if(!forced && ((A.teleport_restriction && A.teleport_restriction != teleport_mode) || (B.teleport_restriction && B.teleport_restriction != teleport_mode)))
return FALSE
if(SEND_SIGNAL(destturf, COMSIG_ATOM_INTERCEPT_TELEPORT, channel, curturf, destturf))
@@ -87,7 +93,7 @@
effect.start()
// Safe location finder
-/proc/find_safe_turf(zlevel, list/zlevels, extended_safety_checks = FALSE)
+/proc/find_safe_turf(zlevel, list/zlevels, extended_safety_checks = FALSE, dense_atoms = TRUE)
if(!zlevels)
if (zlevel)
zlevels = list(zlevel)
@@ -118,11 +124,11 @@
// Can most things breathe?
if(trace_gases)
continue
- if(A.get_moles(/datum/gas/oxygen) < 16)
+ if(A.get_moles(GAS_O2) < 16)
continue
- if(A.get_moles(/datum/gas/plasma))
+ if(A.get_moles(GAS_PLASMA))
continue
- if(A.get_moles(/datum/gas/carbon_dioxide) >= 10)
+ if(A.get_moles(GAS_CO2) >= 10)
continue
// Aim for goldilocks temperatures and pressure
@@ -138,19 +144,35 @@
if(!L.is_safe())
continue
+ // Check that we're not warping onto a table or window
+ if(!dense_atoms)
+ var/density_found = FALSE
+ for(var/atom/movable/found_movable in F)
+ if(found_movable.density)
+ density_found = TRUE
+ break
+ if(density_found)
+ continue
+
// DING! You have passed the gauntlet, and are "probably" safe.
return F
/proc/get_teleport_turfs(turf/center, precision = 0)
if(!precision)
return list(center)
+ //Return only open turfs unless none are available
+ var/list/safe_turfs = list()
var/list/posturfs = list()
- for(var/turf/T in range(precision,center))
+ for(var/turf/T as() in RANGE_TURFS(precision, center))
if(T.is_transition_turf())
continue // Avoid picking these.
var/area/A = T.loc
- if(!A.noteleport)
+ if(!A.teleport_restriction)
posturfs.Add(T)
+ if(isopenturf(T))
+ safe_turfs += T
+ if(length(safe_turfs))
+ return safe_turfs
return posturfs
/proc/get_teleport_turf(turf/center, precision = 0)
diff --git a/code/datums/holocall.dm b/code/datums/holocall.dm
index dafd2b11a3fa8..e28192bed544c 100644
--- a/code/datums/holocall.dm
+++ b/code/datums/holocall.dm
@@ -9,7 +9,7 @@
#define HOLORECORD_MAX_LENGTH 200
-/mob/camera/aiEye/remote/holo/setLoc()
+/mob/camera/ai_eye/remote/holo/setLoc()
. = ..()
var/obj/machinery/holopad/H = origin
H?.move_hologram(eye_user, loc)
@@ -27,7 +27,7 @@
var/obj/machinery/holopad/connected_holopad //the one that answered the call (may be null)
var/list/dialed_holopads //all things called, will be cleared out to just connected_holopad once answered
- var/mob/camera/aiEye/remote/holo/eye //user's eye, once connected
+ var/mob/camera/ai_eye/remote/holo/eye //user's eye, once connected
var/obj/effect/overlay/holo_pad_hologram/hologram //user's hologram, once connected
var/datum/action/innate/end_holocall/hangup //hangup action
@@ -72,6 +72,7 @@
if(!QDELETED(hologram))
hologram.HC = null
QDEL_NULL(hologram)
+ hologram = null
for(var/I in dialed_holopads)
var/obj/machinery/holopad/H = I
diff --git a/code/datums/hud.dm b/code/datums/hud.dm
index eeeee8b1ab4dc..4bf5c2e60a347 100644
--- a/code/datums/hud.dm
+++ b/code/datums/hud.dm
@@ -56,11 +56,14 @@ GLOBAL_LIST_INIT(huds, list(
GLOB.all_huds -= src
return ..()
-/datum/atom_hud/proc/remove_hud_from(mob/M)
+/datum/atom_hud/proc/remove_hud_from(mob/M, absolute = FALSE)
if(!M || !hudusers[M])
return
- if (!--hudusers[M])
+ if (absolute || !--hudusers[M])
+ UnregisterSignal(M, COMSIG_PARENT_QDELETING)
hudusers -= M
+ if(next_time_allowed[M])
+ next_time_allowed -= M
if(queued_to_see[M])
queued_to_see -= M
else
@@ -76,7 +79,7 @@ GLOBAL_LIST_INIT(huds, list(
return TRUE
/datum/atom_hud/proc/remove_from_single_hud(mob/M, atom/A) //unsafe, no sanity apart from client
- if(!M || !M.client || !A)
+ if(!M || !M.client || !A?.hud_list.len)
return
for(var/i in hud_icons)
M.client.images -= A.hud_list[i]
@@ -86,6 +89,7 @@ GLOBAL_LIST_INIT(huds, list(
return
if(!hudusers[M])
hudusers[M] = 1
+ RegisterSignal(M, COMSIG_PARENT_QDELETING, .proc/unregister_mob)
if(next_time_allowed[M] > world.time)
if(!queued_to_see[M])
addtimer(CALLBACK(src, .proc/show_hud_images_after_cooldown, M), next_time_allowed[M] - world.time)
@@ -97,6 +101,12 @@ GLOBAL_LIST_INIT(huds, list(
else
hudusers[M]++
+/datum/atom_hud/proc/unregister_mob(datum/source, force)
+ SIGNAL_HANDLER
+
+ remove_hud_from(source, TRUE)
+ remove_from_hud(source)
+
/datum/atom_hud/proc/show_hud_images_after_cooldown(M)
if(queued_to_see[M])
queued_to_see -= M
diff --git a/code/datums/keybinding/carbon.dm b/code/datums/keybinding/carbon.dm
index 015a879cf0bea..05f62d9825219 100644
--- a/code/datums/keybinding/carbon.dm
+++ b/code/datums/keybinding/carbon.dm
@@ -71,3 +71,17 @@
var/mob/living/carbon/C = user.mob
C.a_intent_change(INTENT_HARM)
return TRUE
+
+/datum/keybinding/carbon/give
+ key = "G"
+ name = "Give_Item"
+ full_name = "Give item"
+ description = "Give the item you're currently holding"
+ category = CATEGORY_CARBON
+
+/datum/keybinding/carbon/give/down(client/user)
+ if(!iscarbon(user.mob))
+ return
+ var/mob/living/carbon/C = user.mob
+ C.give()
+ return TRUE
diff --git a/code/datums/keybinding/client.dm b/code/datums/keybinding/client.dm
index d95bac96b99e1..555bd423ab00e 100644
--- a/code/datums/keybinding/client.dm
+++ b/code/datums/keybinding/client.dm
@@ -34,3 +34,16 @@
/datum/keybinding/client/toggleminimalhud/down(client/user)
user.mob.button_pressed_F12()
return TRUE
+
+
+/datum/keybinding/client/zoomin
+ key = "\]"
+ name = "zoomin"
+ full_name = "Zoom In"
+ description = "Temporary switch icon scaling mode to 4x until unpressed"
+
+/datum/keybinding/client/zoomin/down(client/user)
+ winset(user, "mapwindow.map", "zoom=[PIXEL_SCALING_4X]")
+
+/datum/keybinding/client/zoomin/up(client/user)
+ winset(user, "mapwindow.map", "zoom=[user.prefs.pixel_size]")
diff --git a/code/datums/keybinding/human.dm b/code/datums/keybinding/human.dm
index 300f91a2d7cbe..7b588fe7a016d 100644
--- a/code/datums/keybinding/human.dm
+++ b/code/datums/keybinding/human.dm
@@ -10,7 +10,8 @@
description = ""
/datum/keybinding/human/quick_equip/down(client/user)
- if (!ishuman(user.mob)) return
+ if(!ishuman(user.mob) || user.mob.incapacitated())
+ return
var/mob/living/carbon/human/H = user.mob
H.quick_equip()
return TRUE
@@ -23,15 +24,16 @@
description = ""
/datum/keybinding/human/quick_equip_belt/down(client/user)
- if (!ishuman(user.mob)) return
+ if(!ishuman(user.mob) || user.mob.incapacitated())
+ return
var/mob/living/carbon/human/H = user.mob
var/obj/item/thing = H.get_active_held_item()
- var/obj/item/equipped_belt = H.get_item_by_slot(SLOT_BELT)
+ var/obj/item/equipped_belt = H.get_item_by_slot(ITEM_SLOT_BELT)
if(!equipped_belt) // We also let you equip a belt like this
if(!thing)
to_chat(user, "You have no belt to take something out of.")
return TRUE
- if(H.equip_to_slot_if_possible(thing, SLOT_BELT))
+ if(H.equip_to_slot_if_possible(thing, ITEM_SLOT_BELT))
H.update_inv_hands()
return TRUE
if(!SEND_SIGNAL(equipped_belt, COMSIG_CONTAINS_STORAGE)) // not a storage item
@@ -61,15 +63,16 @@
description = ""
/datum/keybinding/human/quick_equip_backpack/down(client/user)
- if (!ishuman(user.mob)) return
+ if(!ishuman(user.mob) || user.mob.incapacitated())
+ return
var/mob/living/carbon/human/H = user.mob
var/obj/item/thing = H.get_active_held_item()
- var/obj/item/equipped_back = H.get_item_by_slot(SLOT_BACK)
+ var/obj/item/equipped_back = H.get_item_by_slot(ITEM_SLOT_BACK)
if(!equipped_back) // We also let you equip a backpack like this
if(!thing)
to_chat(user, "You have no backpack to take something out of.")
return
- if(H.equip_to_slot_if_possible(thing, SLOT_BACK))
+ if(H.equip_to_slot_if_possible(thing, ITEM_SLOT_BACK))
H.update_inv_hands()
return
if(!SEND_SIGNAL(equipped_back, COMSIG_CONTAINS_STORAGE)) // not a storage item
@@ -90,3 +93,30 @@
return
stored.attack_hand(H) // take out thing from backpack
return
+
+/datum/keybinding/human/quick_equip_suit_storage
+ key = "Shift-Q"
+ name = "quick_equip_suit_storage"
+ full_name = "Put Item In Suit Storage"
+ description = ""
+
+/datum/keybinding/human/quick_equip_suit_storage/down(client/user)
+ if(!ishuman(user.mob) || user.mob.incapacitated())
+ return
+ var/mob/living/carbon/human/H = user.mob
+ var/obj/item/thing = H.get_active_held_item()
+ var/obj/item/stored = H.get_item_by_slot(ITEM_SLOT_SUITSTORE)
+ if(!stored)
+ if(!thing)
+ to_chat(user, "There's nothing in your suit storage to take out.")
+ return TRUE
+ if(H.equip_to_slot_if_possible(thing, ITEM_SLOT_SUITSTORE))
+ H.update_inv_hands()
+ return TRUE
+ if(thing && stored)
+ to_chat(user, "There's already something in your suit storage!")
+ return TRUE
+ if(!stored || stored.on_found(H))
+ return TRUE
+ stored.attack_hand(H)
+ return TRUE
\ No newline at end of file
diff --git a/code/datums/keybinding/living.dm b/code/datums/keybinding/living.dm
index 156b2d8892b7b..5fc5bbed94a6c 100644
--- a/code/datums/keybinding/living.dm
+++ b/code/datums/keybinding/living.dm
@@ -14,3 +14,16 @@
var/mob/living/L = user.mob
L.resist()
return TRUE
+
+/datum/keybinding/living/rest
+ key = "V"
+ name = "rest"
+ full_name = "Rest"
+ description = "Lay down, or get up."
+
+/datum/keybinding/living/rest/down(client/user)
+ if(!isliving(user.mob))
+ return
+ var/mob/living/L = user.mob
+ L.lay_down()
+ return TRUE
\ No newline at end of file
diff --git a/code/datums/keybinding/mob.dm b/code/datums/keybinding/mob.dm
index 75e259caab1cc..3deb6a2776088 100644
--- a/code/datums/keybinding/mob.dm
+++ b/code/datums/keybinding/mob.dm
@@ -149,6 +149,20 @@
M.toggle_move_intent()
return TRUE
+/datum/keybinding/mob/toggle_move_intent_alternative
+ key = "Unbound"
+ name = "toggle_move_intent_alt"
+ full_name = "press to cycle move intent"
+ description = "Pressing this cycle to the opposite move intent, does not cycle back"
+
+/datum/keybinding/mob/toggle_move_intent_alternative/down(client/user)
+ . = ..()
+ if(.)
+ return
+ var/mob/M = user.mob
+ M.toggle_move_intent()
+ return TRUE
+
/datum/keybinding/mob/target_head_cycle
key = "Numpad8"
name = "target_head_cycle"
@@ -225,3 +239,21 @@
if(!user.mob) return
user.body_l_leg()
return TRUE
+
+/datum/keybinding/mob/prevent_movement
+ key = "Ctrl"
+ name = "block_movement"
+ full_name = "Block movement"
+ description = "While pressed, prevents movement when pressing directional keys; instead just changes your facing direction"
+
+/datum/keybinding/mob/prevent_movement/down(client/user)
+ . = ..()
+ if(.)
+ return
+ user.movement_locked = TRUE
+
+/datum/keybinding/mob/prevent_movement/up(client/user)
+ . = ..()
+ if(.)
+ return
+ user.movement_locked = FALSE
diff --git a/code/datums/looping_sounds/_looping_sound.dm b/code/datums/looping_sounds/_looping_sound.dm
index 5a236beb6f73c..e146487fb4591 100644
--- a/code/datums/looping_sounds/_looping_sound.dm
+++ b/code/datums/looping_sounds/_looping_sound.dm
@@ -58,7 +58,7 @@
if(!timerid)
return
on_stop()
- deltimer(timerid)
+ deltimer(timerid, SSsound_loops)
timerid = null
/datum/looping_sound/proc/sound_loop(starttime)
@@ -68,13 +68,13 @@
if(!chance || prob(chance))
play(get_sound(starttime))
if(!timerid)
- timerid = addtimer(CALLBACK(src, .proc/sound_loop, world.time), mid_length, TIMER_CLIENT_TIME | TIMER_STOPPABLE | TIMER_LOOP)
+ timerid = addtimer(CALLBACK(src, .proc/sound_loop, world.time), mid_length, TIMER_CLIENT_TIME | TIMER_STOPPABLE | TIMER_LOOP, SSsound_loops)
/datum/looping_sound/proc/play(soundfile)
var/list/atoms_cache = output_atoms
var/sound/S = sound(soundfile)
if(direct)
- S.channel = open_sound_channel()
+ S.channel = SSsounds.random_available_channel()
S.volume = volume
for(var/i in 1 to atoms_cache.len)
var/atom/thing = atoms_cache[i]
@@ -93,7 +93,7 @@
if(start_sound)
play(start_sound)
start_wait = start_length
- addtimer(CALLBACK(src, .proc/sound_loop), start_wait, TIMER_CLIENT_TIME)
+ addtimer(CALLBACK(src, .proc/sound_loop), start_wait, TIMER_CLIENT_TIME, SSsound_loops)
/datum/looping_sound/proc/on_stop()
if(end_sound)
diff --git a/code/datums/map_config.dm b/code/datums/map_config.dm
index 3bc2a3548a3af..733373fd2c38a 100644
--- a/code/datums/map_config.dm
+++ b/code/datums/map_config.dm
@@ -19,12 +19,13 @@
var/map_file = "BoxStation.dmm"
var/traits = null
- var/space_ruin_levels = 7
+ var/space_ruin_levels = 4 //Keep this low, as new ones are created dynamically when needed.
var/space_empty_levels = 1
var/minetype = "lavaland"
var/allow_custom_shuttles = TRUE
+ var/allow_night_lighting = TRUE
var/shuttles = list(
"cargo" = "cargo_box",
"ferry" = "ferry_fancy",
@@ -128,6 +129,8 @@
allow_custom_shuttles = json["allow_custom_shuttles"] != FALSE
+ allow_night_lighting = json["allow_night_lighting"] != FALSE
+
defaulted = FALSE
return TRUE
#undef CHECK_EXISTS
diff --git a/code/datums/mapgen/CaveGenerator.dm b/code/datums/mapgen/CaveGenerator.dm
new file mode 100644
index 0000000000000..d016eeae3eb7e
--- /dev/null
+++ b/code/datums/mapgen/CaveGenerator.dm
@@ -0,0 +1,161 @@
+/datum/map_generator/cave_generator
+ var/name = "Cave Generator"
+ ///Weighted list of the types that spawns if the turf is open
+ var/open_turf_types = list(/turf/open/floor/plating/asteroid = 1)
+ ///Weighted list of the types that spawns if the turf is closed
+ var/closed_turf_types = list(/turf/closed/mineral/random/volcanic = 1)
+ ///List of turf types (subtypes included) to skip when generating terrain
+ var/blacklisted_turf_types
+
+
+ ///Weighted list of extra features that can spawn in the area, such as geysers.
+ var/list/feature_spawn_list = list(/obj/structure/geyser/random = 1)
+ ///Weighted list of mobs that can spawn in the area.
+ var/list/mob_spawn_list = list(/mob/living/simple_animal/hostile/asteroid/goliath/beast/random = 50, /obj/structure/spawner/lavaland/goliath = 3, \
+ /mob/living/simple_animal/hostile/asteroid/basilisk/watcher/random = 40, /obj/structure/spawner/lavaland = 2, \
+ /mob/living/simple_animal/hostile/asteroid/hivelord/legion/random = 30, /obj/structure/spawner/lavaland/legion = 3, \
+ SPAWN_MEGAFAUNA = 4, /mob/living/simple_animal/hostile/asteroid/goldgrub = 10)
+ ///Weighted list of flora that can spawn in the area.
+ var/list/flora_spawn_list = list(/obj/structure/flora/ash/leaf_shroom = 2 , /obj/structure/flora/ash/cap_shroom = 2 , /obj/structure/flora/ash/stem_shroom = 2 , /obj/structure/flora/ash/cacti = 1, /obj/structure/flora/ash/tall_shroom = 2)
+ // Weighted list of Megafauna that can spawn in the caves
+ var/list/megafauna_spawn_list
+
+
+ ///Base chance of spawning a mob
+ var/mob_spawn_chance = 6
+ ///Base chance of spawning flora
+ var/flora_spawn_chance = 2
+ ///Base chance of spawning features
+ var/feature_spawn_chance = 0.1
+ ///Unique ID for this spawner
+ var/string_gen
+
+ ///Chance of cells starting closed
+ var/initial_closed_chance = 45
+ ///Amount of smoothing iterations
+ var/smoothing_iterations = 20
+ ///How much neighbours does a dead cell need to become alive
+ var/birth_limit = 4
+ ///How little neighbours does a alive cell need to die
+ var/death_limit = 3
+
+/datum/map_generator/cave_generator/New()
+ . = ..()
+ if(!megafauna_spawn_list)
+ megafauna_spawn_list = GLOB.megafauna_spawn_list
+
+/datum/map_generator/cave_generator/generate_terrain(list/turfs)
+ . = ..()
+ var/start_time = REALTIMEOFDAY
+ string_gen = rustg_cnoise_generate("[initial_closed_chance]", "[smoothing_iterations]", "[birth_limit]", "[death_limit]", "[world.maxx]", "[world.maxy]") //Generate the raw CA data
+ var/blacklist = typecacheof(blacklisted_turf_types)
+
+ for(var/i in turfs) //Go through all the turfs and generate them
+ var/turf/gen_turf = i
+
+ if(blacklist && blacklist[gen_turf.type])
+ continue
+
+ var/forced
+ var/closed
+
+ if(istype(gen_turf, /turf/open/genturf))
+ var/turf/open/genturf/genturf = gen_turf
+ if(genturf.force_generation)
+ forced = TRUE
+ switch(genturf.genturf_hint)
+ if(GENTURF_HINT_OPEN)
+ closed = FALSE
+ if(GENTURF_HINT_CLOSED)
+ closed = TRUE
+
+ var/area/A = gen_turf.loc
+ if(!forced && !(A.area_flags & CAVES_ALLOWED))
+ continue
+
+ if(isnull(closed))
+ closed = text2num(string_gen[world.maxx * (gen_turf.y - 1) + gen_turf.x])
+
+ var/stored_flags
+ if(gen_turf.flags_1 & NO_RUINS_1)
+ stored_flags |= NO_RUINS_1
+
+ var/turf/new_turf = pickweight(closed ? closed_turf_types : open_turf_types)
+
+ new_turf = gen_turf.ChangeTurf(new_turf, initial(new_turf.baseturfs), CHANGETURF_DEFER_CHANGE)
+
+ new_turf.flags_1 |= stored_flags
+
+ if(!closed)//Open turfs have some special behavior related to spawning flora and mobs.
+
+ var/turf/open/new_open_turf = new_turf
+
+ ///Spawning isn't done in procs to save on overhead on the 60k turfs we're going through.
+
+ //FLORA SPAWNING HERE
+ var/atom/spawned_flora
+ if(flora_spawn_list && prob(flora_spawn_chance))
+ var/can_spawn = TRUE
+
+ if(!(A.area_flags & FLORA_ALLOWED))
+ can_spawn = FALSE
+ if(can_spawn)
+ spawned_flora = pickweight(flora_spawn_list)
+ spawned_flora = new spawned_flora(new_open_turf)
+
+ //FEATURE SPAWNING HERE
+ var/atom/spawned_feature
+ if(feature_spawn_list && prob(feature_spawn_chance))
+ var/can_spawn = TRUE
+
+ if(!(A.area_flags & FLORA_ALLOWED)) //checks the same flag because lol dunno
+ can_spawn = FALSE
+
+ var/atom/picked_feature = pickweight(feature_spawn_list)
+
+ for(var/obj/structure/F in range(7, new_open_turf))
+ if(istype(F, picked_feature))
+ can_spawn = FALSE
+
+ if(can_spawn)
+ spawned_feature = new picked_feature(new_open_turf)
+
+ //MOB SPAWNING HERE
+
+ if(mob_spawn_list && !spawned_flora && !spawned_feature && prob(mob_spawn_chance))
+ var/can_spawn = TRUE
+
+ if(!(A.area_flags & MOB_SPAWN_ALLOWED))
+ can_spawn = FALSE
+
+ var/atom/picked_mob = pickweight(mob_spawn_list)
+
+ if(picked_mob == SPAWN_MEGAFAUNA) //
+ if((A.area_flags & MEGAFAUNA_SPAWN_ALLOWED) && megafauna_spawn_list?.len) //this is danger. it's boss time.
+ picked_mob = pickweight(megafauna_spawn_list)
+ else //this is not danger, don't spawn a boss, spawn something else
+ picked_mob = pickweight(mob_spawn_list - SPAWN_MEGAFAUNA) //What if we used 100% of the brain...and did something (slightly) less shit than a while loop?
+
+ for(var/thing in urange(12, new_open_turf)) //prevents mob clumps
+ if(!ishostile(thing) && !istype(thing, /obj/structure/spawner))
+ continue
+ if((ispath(picked_mob, /mob/living/simple_animal/hostile/megafauna) || ismegafauna(thing)) && get_dist(new_open_turf, thing) <= 7)
+ can_spawn = FALSE //if there's a megafauna within standard view don't spawn anything at all
+ break
+ if(ispath(picked_mob, /mob/living/simple_animal/hostile/asteroid) || istype(thing, /mob/living/simple_animal/hostile/asteroid))
+ can_spawn = FALSE //if the random is a standard mob, avoid spawning if there's another one within 12 tiles
+ break
+ if((ispath(picked_mob, /obj/structure/spawner/lavaland) || istype(thing, /obj/structure/spawner/lavaland)) && get_dist(new_open_turf, thing) <= 2)
+ can_spawn = FALSE //prevents tendrils spawning in each other's collapse range
+ break
+
+ if(can_spawn)
+ if(ispath(picked_mob, /mob/living/simple_animal/hostile/megafauna/bubblegum)) //there can be only one bubblegum, so don't waste spawns on it
+ megafauna_spawn_list.Remove(picked_mob)
+
+ new picked_mob(new_open_turf)
+ CHECK_TICK
+
+ var/message = "[name] finished in [(REALTIMEOFDAY - start_time)/10]s!"
+ to_chat(world, "[message]")
+ log_world(message)
diff --git a/code/datums/mapgen/Cavegens/LavalandGenerator.dm b/code/datums/mapgen/Cavegens/LavalandGenerator.dm
new file mode 100644
index 0000000000000..9daf0e8318f39
--- /dev/null
+++ b/code/datums/mapgen/Cavegens/LavalandGenerator.dm
@@ -0,0 +1,17 @@
+/datum/map_generator/cave_generator/lavaland
+ open_turf_types = list(/turf/open/floor/plating/asteroid/basalt/lava_land_surface = 1)
+ closed_turf_types = list(/turf/closed/mineral/random/volcanic = 1)
+ blacklisted_turf_types = list(/turf/open/lava) // Don't override lava rivers
+
+
+ feature_spawn_list = list(/obj/structure/geyser/random = 1)
+ mob_spawn_list = list(/mob/living/simple_animal/hostile/asteroid/goliath/beast/random = 50, /obj/structure/spawner/lavaland/goliath = 3, \
+ /mob/living/simple_animal/hostile/asteroid/basilisk/watcher/random = 40, /obj/structure/spawner/lavaland = 2, \
+ /mob/living/simple_animal/hostile/asteroid/hivelord/legion/random = 30, /obj/structure/spawner/lavaland/legion = 3, \
+ SPAWN_MEGAFAUNA = 4, /mob/living/simple_animal/hostile/asteroid/goldgrub = 10)
+ flora_spawn_list = list(/obj/structure/flora/ash/leaf_shroom = 2 , /obj/structure/flora/ash/cap_shroom = 2 , /obj/structure/flora/ash/stem_shroom = 2 , /obj/structure/flora/ash/cacti = 1, /obj/structure/flora/ash/tall_shroom = 2, /obj/structure/flora/ash/strange = 1)
+
+ initial_closed_chance = 45
+ smoothing_iterations = 50
+ birth_limit = 4
+ death_limit = 3
diff --git a/code/datums/mapgen/JungleGenerator.dm b/code/datums/mapgen/JungleGenerator.dm
new file mode 100644
index 0000000000000..1e329ac3df634
--- /dev/null
+++ b/code/datums/mapgen/JungleGenerator.dm
@@ -0,0 +1,95 @@
+//the random offset applied to square coordinates, causes intermingling at biome borders
+#define BIOME_RANDOM_SQUARE_DRIFT 2
+
+/datum/map_generator/jungle_generator
+ ///2D list of all biomes based on heat and humidity combos.
+ var/list/possible_biomes = list(
+ BIOME_LOW_HEAT = list(
+ BIOME_LOW_HUMIDITY = /datum/biome/plains,
+ BIOME_LOWMEDIUM_HUMIDITY = /datum/biome/mudlands,
+ BIOME_HIGHMEDIUM_HUMIDITY = /datum/biome/mudlands,
+ BIOME_HIGH_HUMIDITY = /datum/biome/water
+ ),
+ BIOME_LOWMEDIUM_HEAT = list(
+ BIOME_LOW_HUMIDITY = /datum/biome/plains,
+ BIOME_LOWMEDIUM_HUMIDITY = /datum/biome/jungle,
+ BIOME_HIGHMEDIUM_HUMIDITY = /datum/biome/jungle,
+ BIOME_HIGH_HUMIDITY = /datum/biome/mudlands
+ ),
+ BIOME_HIGHMEDIUM_HEAT = list(
+ BIOME_LOW_HUMIDITY = /datum/biome/plains,
+ BIOME_LOWMEDIUM_HUMIDITY = /datum/biome/plains,
+ BIOME_HIGHMEDIUM_HUMIDITY = /datum/biome/jungle/deep,
+ BIOME_HIGH_HUMIDITY = /datum/biome/jungle
+ ),
+ BIOME_HIGH_HEAT = list(
+ BIOME_LOW_HUMIDITY = /datum/biome/wasteland,
+ BIOME_LOWMEDIUM_HUMIDITY = /datum/biome/plains,
+ BIOME_HIGHMEDIUM_HUMIDITY = /datum/biome/jungle,
+ BIOME_HIGH_HUMIDITY = /datum/biome/jungle/deep
+ )
+ )
+ ///Used to select "zoom" level into the perlin noise, higher numbers result in slower transitions
+ var/perlin_zoom = 65
+
+///Seeds the rust-g perlin noise with a random number.
+/datum/map_generator/jungle_generator/generate_terrain(var/list/turfs)
+ . = ..()
+ var/height_seed = rand(0, 50000)
+ var/humidity_seed = rand(0, 50000)
+ var/heat_seed = rand(0, 50000)
+
+ for(var/t in turfs) //Go through all the turfs and generate them
+ var/turf/gen_turf = t
+ var/drift_x = (gen_turf.x + rand(-BIOME_RANDOM_SQUARE_DRIFT, BIOME_RANDOM_SQUARE_DRIFT)) / perlin_zoom
+ var/drift_y = (gen_turf.y + rand(-BIOME_RANDOM_SQUARE_DRIFT, BIOME_RANDOM_SQUARE_DRIFT)) / perlin_zoom
+
+ var/height = text2num(rustg_noise_get_at_coordinates("[height_seed]", "[drift_x]", "[drift_y]"))
+
+
+ var/datum/biome/selected_biome
+ if(height <= 0.85) //If height is less than 0.85, we generate biomes based on the heat and humidity of the area.
+ var/humidity = text2num(rustg_noise_get_at_coordinates("[humidity_seed]", "[drift_x]", "[drift_y]"))
+ var/heat = text2num(rustg_noise_get_at_coordinates("[heat_seed]", "[drift_x]", "[drift_y]"))
+ var/heat_level //Type of heat zone we're in LOW-MEDIUM-HIGH
+ var/humidity_level //Type of humidity zone we're in LOW-MEDIUM-HIGH
+
+ switch(heat)
+ if(0 to 0.25)
+ heat_level = BIOME_LOW_HEAT
+ if(0.25 to 0.5)
+ heat_level = BIOME_LOWMEDIUM_HEAT
+ if(0.5 to 0.75)
+ heat_level = BIOME_HIGHMEDIUM_HEAT
+ if(0.75 to 1)
+ heat_level = BIOME_HIGH_HEAT
+ switch(humidity)
+ if(0 to 0.25)
+ humidity_level = BIOME_LOW_HUMIDITY
+ if(0.25 to 0.5)
+ humidity_level = BIOME_LOWMEDIUM_HUMIDITY
+ if(0.5 to 0.75)
+ humidity_level = BIOME_HIGHMEDIUM_HUMIDITY
+ if(0.75 to 1)
+ humidity_level = BIOME_HIGH_HUMIDITY
+ selected_biome = possible_biomes[heat_level][humidity_level]
+ else //Over 0.85; It's a mountain
+ selected_biome = /datum/biome/mountain
+ selected_biome = SSmapping.biomes[selected_biome] //Get the instance of this biome from SSmapping
+ selected_biome.generate_turf(gen_turf)
+ CHECK_TICK
+
+/turf/open/genturf
+ name = "ungenerated turf"
+ desc = "If you see this, and you're not a ghost, yell at coders"
+ icon = 'icons/turf/debug.dmi'
+ icon_state = "genturf"
+ ///Can be: null, GENTURF_HINT_OPEN or GENTURF_HINT_CLOSED
+ var/genturf_hint
+ ///Should the generation be ran even if the area forbids it?
+ var/force_generation
+
+/area/mine/planetgeneration
+ name = "planet generation area"
+ dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
+ map_generator = /datum/map_generator/jungle_generator
diff --git a/code/datums/mapgen/_MapGenerator.dm b/code/datums/mapgen/_MapGenerator.dm
new file mode 100644
index 0000000000000..aa4406bf789e0
--- /dev/null
+++ b/code/datums/mapgen/_MapGenerator.dm
@@ -0,0 +1,6 @@
+///This type is responsible for any map generation behavior that is done in areas, override this to allow for area-specific map generation. This generation is ran by areas in initialize.
+/datum/map_generator
+
+///This proc will be ran by areas on Initialize, and provides the areas turfs as argument to allow for generation.
+/datum/map_generator/proc/generate_terrain(var/list/turfs)
+ return
diff --git a/code/datums/mapgen/biomes/_biome.dm b/code/datums/mapgen/biomes/_biome.dm
new file mode 100644
index 0000000000000..bf0f02cfbaadf
--- /dev/null
+++ b/code/datums/mapgen/biomes/_biome.dm
@@ -0,0 +1,50 @@
+///This datum handles the transitioning from a turf to a specific biome, and handles spawning decorative structures and mobs.
+/datum/biome
+ ///Type of turf this biome creates
+ var/turf_type
+ ///Chance of having a structure from the flora types list spawn
+ var/flora_density = 0
+ ///Chance of having a mob from the fauna types list spawn
+ var/fauna_density = 0
+ ///list of type paths of objects that can be spawned when the turf spawns flora
+ var/list/flora_types = list(/obj/structure/flora/grass/jungle)
+ ///list of type paths of mobs that can be spawned when the turf spawns fauna
+ var/list/fauna_types = list()
+
+///This proc handles the creation of a turf of a specific biome type
+/datum/biome/proc/generate_turf(var/turf/gen_turf)
+ gen_turf.ChangeTurf(turf_type, null, CHANGETURF_DEFER_CHANGE)
+ if(length(fauna_types) && prob(fauna_density))
+ var/mob/fauna = pick(fauna_types)
+ new fauna(gen_turf)
+
+ if(length(flora_types) && prob(flora_density))
+ var/obj/structure/flora = pick(flora_types)
+ new flora(gen_turf)
+
+/datum/biome/mudlands
+ turf_type = /turf/open/floor/plating/dirt/jungle/dark
+ flora_types = list(/obj/structure/flora/grass/jungle,/obj/structure/flora/grass/jungle/b, /obj/structure/flora/rock/jungle, /obj/structure/flora/rock/pile/largejungle)
+ flora_density = 3
+
+/datum/biome/plains
+ turf_type = /turf/open/floor/plating/grass/jungle
+ flora_types = list(/obj/structure/flora/grass/jungle,/obj/structure/flora/grass/jungle/b, /obj/structure/flora/tree/jungle, /obj/structure/flora/rock/jungle, /obj/structure/flora/junglebush, /obj/structure/flora/junglebush/b, /obj/structure/flora/junglebush/c, /obj/structure/flora/junglebush/large, /obj/structure/flora/rock/pile/largejungle)
+ flora_density = 15
+
+/datum/biome/jungle
+ turf_type = /turf/open/floor/plating/grass/jungle
+ flora_types = list(/obj/structure/flora/grass/jungle,/obj/structure/flora/grass/jungle/b, /obj/structure/flora/tree/jungle, /obj/structure/flora/rock/jungle, /obj/structure/flora/junglebush, /obj/structure/flora/junglebush/b, /obj/structure/flora/junglebush/c, /obj/structure/flora/junglebush/large, /obj/structure/flora/rock/pile/largejungle)
+ flora_density = 40
+
+/datum/biome/jungle/deep
+ flora_density = 65
+
+/datum/biome/wasteland
+ turf_type = /turf/open/floor/plating/dirt/jungle/wasteland
+
+/datum/biome/water
+ turf_type = /turf/open/water/jungle
+
+/datum/biome/mountain
+ turf_type = /turf/closed/mineral/random/jungle
diff --git a/code/datums/martial/_martial.dm b/code/datums/martial/_martial.dm
index 206792c3ec573..475936d11cb0d 100644
--- a/code/datums/martial/_martial.dm
+++ b/code/datums/martial/_martial.dm
@@ -89,7 +89,7 @@
else if(make_temporary)
base = H.mind.default_martial_art
if(help_verb)
- H.verbs += help_verb
+ H.add_verb(help_verb)
H.mind.martial_art = src
return TRUE
@@ -112,5 +112,5 @@
/datum/martial_art/proc/on_remove(mob/living/carbon/human/H)
if(help_verb)
- H.verbs -= help_verb
+ H.remove_verb(help_verb)
return
diff --git a/code/datums/martial/boxing.dm b/code/datums/martial/boxing.dm
index 0e0d201e02e95..13be970cfdfd4 100644
--- a/code/datums/martial/boxing.dm
+++ b/code/datums/martial/boxing.dm
@@ -54,7 +54,7 @@
/obj/item/clothing/gloves/boxing/equipped(mob/user, slot)
if(!ishuman(user))
return
- if(slot == SLOT_GLOVES)
+ if(slot == ITEM_SLOT_GLOVES)
var/mob/living/carbon/human/H = user
style.teach(H,1)
return
@@ -63,6 +63,6 @@
if(!ishuman(user))
return
var/mob/living/carbon/human/H = user
- if(H.get_item_by_slot(SLOT_GLOVES) == src)
+ if(H.get_item_by_slot(ITEM_SLOT_GLOVES) == src)
style.remove(H)
return
diff --git a/code/datums/martial/krav_maga.dm b/code/datums/martial/krav_maga.dm
index 94434cffc57ae..0d756b4fe316a 100644
--- a/code/datums/martial/krav_maga.dm
+++ b/code/datums/martial/krav_maga.dm
@@ -183,7 +183,7 @@
/obj/item/clothing/gloves/krav_maga/equipped(mob/user, slot)
if(!ishuman(user))
return
- if(slot == SLOT_GLOVES)
+ if(slot == ITEM_SLOT_GLOVES)
var/mob/living/carbon/human/H = user
style.teach(H,1)
@@ -191,7 +191,7 @@
if(!ishuman(user))
return
var/mob/living/carbon/human/H = user
- if(H.get_item_by_slot(SLOT_GLOVES) == src)
+ if(H.get_item_by_slot(ITEM_SLOT_GLOVES) == src)
style.remove(H)
/obj/item/clothing/gloves/krav_maga/sec//more obviously named, given to sec
@@ -218,4 +218,4 @@
heat_protection = HANDS
max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT
resistance_flags = NONE
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50)
\ No newline at end of file
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50, "stamina" = 0)
diff --git a/code/datums/martial/plasma_fist.dm b/code/datums/martial/plasma_fist.dm
index 45276a50405c4..359fd4d49318b 100644
--- a/code/datums/martial/plasma_fist.dm
+++ b/code/datums/martial/plasma_fist.dm
@@ -36,10 +36,7 @@
A.say("TORNADO SWEEP!", forced="plasma fist")
TornadoAnimate(A)
var/obj/effect/proc_holder/spell/aoe_turf/repulse/R = new(null)
- var/list/turfs = list()
- for(var/turf/T in range(1,A))
- turfs.Add(T)
- R.cast(turfs)
+ R.cast(RANGE_TURFS(1,A))
log_combat(A, D, "tornado sweeped(Plasma Fist)")
return
diff --git a/code/datums/martial/psychotic_brawl.dm b/code/datums/martial/psychotic_brawl.dm
index 7aa202271865a..df229c9ec9fd5 100644
--- a/code/datums/martial/psychotic_brawl.dm
+++ b/code/datums/martial/psychotic_brawl.dm
@@ -3,9 +3,13 @@
id = MARTIALART_PSYCHOBRAWL
/datum/martial_art/psychotic_brawling/disarm_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
+ if(HAS_TRAIT(A, TRAIT_PACIFISM))
+ return FALSE
return psycho_attack(A,D)
/datum/martial_art/psychotic_brawling/grab_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
+ if(HAS_TRAIT(A, TRAIT_PACIFISM))
+ return FALSE
return psycho_attack(A,D)
/datum/martial_art/psychotic_brawling/harm_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
diff --git a/code/datums/martial/sleeping_carp.dm b/code/datums/martial/sleeping_carp.dm
index 2e2d08dc18695..85e17a7cd9702 100644
--- a/code/datums/martial/sleeping_carp.dm
+++ b/code/datums/martial/sleeping_carp.dm
@@ -162,16 +162,12 @@
to_chat(usr, "Head Kick: Disarm Harm Harm. Decent damage, forces opponent to drop item in hand.")
to_chat(usr, "Elbow Drop: Harm Disarm Harm Disarm Harm. Opponent must be on the ground. Deals huge damage, instantly kills anyone in critical condition.")
-/obj/item/twohanded/bostaff
+/obj/item/staff/bostaff
name = "bo staff"
desc = "A long, tall staff made of polished wood. Traditionally used in ancient old-Earth martial arts. Can be wielded to both kill and incapacitate."
force = 10
w_class = WEIGHT_CLASS_BULKY
slot_flags = ITEM_SLOT_BACK
- force_unwielded = 10
- force_wielded = 24
- block_power_wielded = 50
- block_power_unwielded = 25
throwforce = 20
throw_speed = 2
attack_verb = list("smashed", "slammed", "whacked", "thwacked")
@@ -183,10 +179,15 @@
block_upgrade_walk = 1
block_power = 25
-/obj/item/twohanded/bostaff/update_icon_state()
- icon_state = "bostaff[wielded]"
+/obj/item/staff/bostaff/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/two_handed, force_unwielded=10, force_wielded=24, block_power_unwielded=25, block_power_wielded=50, icon_wielded="bostaff1")
-/obj/item/twohanded/bostaff/attack(mob/target, mob/living/user)
+/obj/item/staff/bostaff/update_icon_state()
+ icon_state = "bostaff0"
+ ..()
+
+/obj/item/staff/bostaff/attack(mob/target, mob/living/user)
add_fingerprint(user)
if((HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50))
to_chat(user, "You club yourself over the head with [src].")
@@ -206,7 +207,7 @@
to_chat(user, "It would be dishonorable to attack a foe while they cannot retaliate.")
return
if(user.a_intent == INTENT_DISARM)
- if(!wielded)
+ if(!ISWIELDED(src))
return ..()
if(!ishuman(target))
return ..()
@@ -235,7 +236,7 @@
else
return ..()
-/obj/item/twohanded/bostaff/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
- if(wielded)
+/obj/item/staff/bostaff/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
+ if(!ISWIELDED(src))
return ..()
return 0
diff --git a/code/datums/martial/tribal_claw.dm b/code/datums/martial/tribal_claw.dm
new file mode 100644
index 0000000000000..7e90d65d4bc85
--- /dev/null
+++ b/code/datums/martial/tribal_claw.dm
@@ -0,0 +1,110 @@
+#define TAIL_SWEEP_COMBO "DDGH"
+#define FACE_SCRATCH_COMBO "HD"
+#define JUGULAR_CUT_COMBO "HHG"
+#define TAIL_GRAB_COMBO "DHGG"
+
+/datum/martial_art/tribal_claw
+ name = "Tribal Claw"
+ id = MARTIALART_TRIBALCLAW
+ allow_temp_override = FALSE
+ help_verb = /mob/living/carbon/human/proc/tribal_claw_help
+
+/datum/martial_art/tribal_claw/proc/check_streak(mob/living/carbon/human/A, mob/living/carbon/human/D)
+ if(findtext(streak,TAIL_SWEEP_COMBO))
+ streak = ""
+ tailSweep(A,D)
+ return TRUE
+ if(findtext(streak,FACE_SCRATCH_COMBO))
+ streak = ""
+ faceScratch(A,D)
+ return TRUE
+ if(findtext(streak,JUGULAR_CUT_COMBO))
+ streak = ""
+ jugularCut(A,D)
+ return TRUE
+ if(findtext(streak,TAIL_GRAB_COMBO))
+ streak = ""
+ tailGrab(A,D)
+ return TRUE
+ return FALSE
+
+//Tail Sweep, triggers an effect similar to Space Dragon's tail sweep but only affects stuff 1 tile next to you, basically 3x3.
+/datum/martial_art/tribal_claw/proc/tailSweep(mob/living/carbon/human/A, mob/living/carbon/human/D)
+ if(A == current_target)
+ return
+ log_combat(A, D, "tail sweeped(Tribal Claw)")
+ D.visible_message("[A] sweeps [D]'s legs with their tail!", \
+ "[A] sweeps your legs with their tail!")
+ var/obj/effect/proc_holder/spell/aoe_turf/repulse/spacedragon/R = new
+ R.cast(RANGE_TURFS(1,A))
+
+//Face Scratch, deals 10 brute to head(reduced by armor), blurs the target's vision and gives them the confused effect for a short time.
+/datum/martial_art/tribal_claw/proc/faceScratch(mob/living/carbon/human/A, mob/living/carbon/human/D)
+ var/def_check = D.getarmor(BODY_ZONE_HEAD, "melee")
+ log_combat(A, D, "face scratched (Tribal Claw)")
+ D.visible_message("[A] scratches [D]'s face with their claws!", \
+ "[A] scratches your face with their claws!")
+ D.apply_damage(10, BRUTE, BODY_ZONE_HEAD, def_check)
+ D.confused += 5
+ D.blur_eyes(5)
+ A.do_attack_animation(D, ATTACK_EFFECT_CLAW)
+ playsound(get_turf(D), 'sound/weapons/slash.ogg', 50, 1, -1)
+
+/*
+Jugular Cut, can only be done if the target is in crit, being held in a tier 3 grab by the user or if they are sleeping.
+Deals 15 brute to head(reduced by armor) and causes a rapid bleeding effect similar to throat slicing someone with a sharp item.
+*/
+/datum/martial_art/tribal_claw/proc/jugularCut(mob/living/carbon/human/A, mob/living/carbon/human/D)
+ var/def_check = D.getarmor(BODY_ZONE_HEAD, "melee")
+ if((D.health <= D.crit_threshold || (A.pulling == D && A.grab_state >= GRAB_NECK) || D.IsSleeping()))
+ log_combat(A, D, "jugular cut (Tribal Claw)")
+ D.visible_message("[A] cuts [D]'s jugular vein with their claws!", \
+ "[A] cuts your jugular vein!")
+ D.apply_damage(15, BRUTE, BODY_ZONE_HEAD, def_check)
+ D.bleed_rate = CLAMP(D.bleed_rate + 20, 0, 30)
+ D.apply_status_effect(/datum/status_effect/neck_slice)
+ A.do_attack_animation(D, ATTACK_EFFECT_CLAW)
+ playsound(get_turf(D), 'sound/weapons/slash.ogg', 50, 1, -1)
+ else
+ return basic_hit(A,D)
+
+//Tail Grab, instantly puts your target in a T3 grab and makes them unable to talk for a short time.
+/datum/martial_art/tribal_claw/proc/tailGrab(mob/living/carbon/human/A, mob/living/carbon/human/D)
+ log_combat(A, D, "tail grabbed (Tribal Claw)")
+ D.visible_message("[A] grabs [D] with their tail!", \
+ "[A] grabs you with their tail!")
+ D.grabbedby(A, 1)
+ D.Knockdown(5) //Without knockdown target still stands up while T3 grabbed.
+ A.setGrabState(GRAB_NECK)
+ if(D.silent <= 10)
+ D.silent = CLAMP(D.silent + 10, 0, 10)
+
+/datum/martial_art/tribal_claw/harm_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
+ add_to_streak("H",D)
+ if(check_streak(A,D))
+ return TRUE
+ return FALSE
+
+/datum/martial_art/tribal_claw/disarm_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
+ add_to_streak("D",D)
+ if(check_streak(A,D))
+ return TRUE
+ return FALSE
+
+/datum/martial_art/tribal_claw/grab_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
+ add_to_streak("G",D)
+ if(check_streak(A,D))
+ return TRUE
+ return FALSE
+
+/mob/living/carbon/human/proc/tribal_claw_help()
+ set name = "Recall Teachings"
+ set desc = "Remember the martial techniques of the Tribal Claw"
+ set category = "Tribal Claw"
+
+ to_chat(usr, "You retreat inward and recall the teachings of the Tribal Claw...")
+
+ to_chat(usr, "Tail Sweep: Disarm Disarm Grab Harm. Pushes everyone around you away and knocks them down.")
+ to_chat(usr, "Face Scratch: Harm Disarm. Damages your target's head and confuses them for a short time.")
+ to_chat(usr, "Jugular Cut: Harm Harm Grab. Causes your target to rapidly lose blood, works only if you grab your target by their neck, if they are sleeping, or in critical condition.")
+ to_chat(usr, "Tail Grab: Disarm Harm Grab Grab. Grabs your target by their neck and makes them unable to talk for a short time.")
diff --git a/code/datums/martial/wrestling.dm b/code/datums/martial/wrestling.dm
index 53b35784806d1..12d73e4ceaded 100644
--- a/code/datums/martial/wrestling.dm
+++ b/code/datums/martial/wrestling.dm
@@ -364,9 +364,7 @@
var/falling = 0
for (var/obj/O in oview(1, A))
- if (O.density == 1)
- if (O == A)
- continue
+ if(O.density)
if (O == D)
continue
if (O.opacity)
@@ -458,7 +456,7 @@
/obj/item/storage/belt/champion/wrestling/equipped(mob/user, slot)
if(!ishuman(user))
return
- if(slot == SLOT_BELT)
+ if(slot == ITEM_SLOT_BELT)
var/mob/living/carbon/human/H = user
style.teach(H,1)
return
@@ -467,6 +465,6 @@
if(!ishuman(user))
return
var/mob/living/carbon/human/H = user
- if(H.get_item_by_slot(SLOT_BELT) == src)
+ if(H.get_item_by_slot(ITEM_SLOT_BELT) == src)
style.remove(H)
return
diff --git a/code/datums/materials/_material.dm b/code/datums/materials/_material.dm
index ec3ec0d05c234..fd169200a8371 100644
--- a/code/datums/materials/_material.dm
+++ b/code/datums/materials/_material.dm
@@ -1,6 +1,6 @@
/*! Material datum
-Simple datum which is instanced once per type and is used for every object of said material. It has a variety of variables that define behavior. Subtyping from this makes it easier to create your own materials.
+Simple datum which is instanced once per type and is used for every object of said material. It has a variety of variables that define behavior. Subtyping from this makes it easier to create your own materials.
*/
@@ -52,7 +52,7 @@ Simple datum which is instanced once per type and is used for every object of sa
if(color)
source.remove_atom_colour(FIXED_COLOUR_PRIORITY, color)
source.alpha = initial(source.alpha)
-
+
if(istype(source, /obj)) //objs
on_removed_obj(source, material_flags)
diff --git a/code/datums/materials/basemats.dm b/code/datums/materials/basemats.dm
index c0c5fba56a647..19a6d9945fc16 100644
--- a/code/datums/materials/basemats.dm
+++ b/code/datums/materials/basemats.dm
@@ -83,12 +83,12 @@
/datum/material/plasma/on_applied(atom/source, amount, material_flags)
. = ..()
if(ismovableatom(source))
- source.AddElement(/datum/element/firestacker, 1)
+ source.AddElement(/datum/element/firestacker, amount=1)
source.AddComponent(/datum/component/explodable, 0, 0, amount / 1000, amount / 500)
/datum/material/plasma/on_removed(atom/source, material_flags)
. = ..()
- source.RemoveElement(/datum/element/firestacker, 1)
+ source.RemoveElement(/datum/element/firestacker, amount=1)
qdel(source.GetComponent(/datum/component/explodable))
///Can cause bluespace effects on use. (Teleportation) (Not yet implemented)
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 9b062b13e174a..0d427225e474d 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -55,7 +55,7 @@
var/damnation_type = 0
var/datum/mind/soulOwner //who owns the soul. Under normal circumstances, this will point to src
var/hasSoul = TRUE // If false, renders the character unable to sell their soul.
- var/isholy = FALSE //is this person a chaplain or admin role allowed to use bibles
+ var/holy_role = NONE //is this person a chaplain or admin role allowed to use bibles, Any rank besides 'NONE' allows for this.
var/isAntagTarget = FALSE
var/no_cloning_at_all = FALSE
@@ -71,6 +71,9 @@
var/list/learned_recipes //List of learned recipe TYPES.
var/list/crew_objectives = list()
+ /// A lazy list of statuses to add next to this mind in the traitor panel
+ var/list/special_statuses
+
/datum/mind/New(var/key)
src.key = key
soulOwner = src
@@ -84,8 +87,23 @@
if(antag_datum.delete_on_mind_deletion)
qdel(i)
antag_datums = null
+ QDEL_NULL(language_holder)
+ set_current(null)
return ..()
+/datum/mind/proc/set_current(mob/new_current)
+ if(new_current && QDELING(new_current))
+ CRASH("Tried to set a mind's current var to a qdeleted mob, what the fuck")
+ if(current)
+ UnregisterSignal(src, COMSIG_PARENT_QDELETING)
+ current = new_current
+ if(current)
+ RegisterSignal(src, COMSIG_PARENT_QDELETING, .proc/clear_current)
+
+/datum/mind/proc/clear_current(datum/source)
+ SIGNAL_HANDLER
+ set_current(null)
+
/datum/mind/proc/get_language_holder()
if(!language_holder)
language_holder = new (src)
@@ -99,18 +117,18 @@
if(key)
if(new_character.key != key) //if we're transferring into a body with a key associated which is not ours
- new_character.ghostize(1) //we'll need to ghostize so that key isn't mobless.
+ new_character.ghostize(TRUE,SENTIENCE_ERASE) //we'll need to ghostize so that key isn't mobless.
else
key = new_character.key
if(new_character.mind) //disassociate any mind currently in our new body's mind variable
- new_character.mind.current = null
+ new_character.mind.set_current(null)
var/datum/atom_hud/antag/hud_to_transfer = antag_hud//we need this because leave_hud() will clear this list
var/mob/living/old_current = current
if(current)
current.transfer_observers_to(new_character) //transfer anyone observing the old character to the new one
- current = new_character //associate ourself with our new body
+ set_current(new_character) //associate ourself with our new body
new_character.mind = src //and associate our new body with ourself
for(var/a in antag_datums) //Makes sure all antag datums effects are applied in the new body
var/datum/antagonist/A = a
@@ -128,11 +146,13 @@
SEND_SIGNAL(src, COMSIG_MIND_TRANSFER_TO, old_current, new_character)
/datum/mind/proc/set_death_time()
+ SIGNAL_HANDLER
+
last_death = world.time
/datum/mind/proc/store_memory(new_text)
var/newlength = length(memory) + length(new_text)
- if (newlength > MAX_MESSAGE_LEN * 100)
+ if(newlength > MAX_MESSAGE_LEN * 100)
memory = copytext(memory, -newlength-MAX_MESSAGE_LEN * 100)
memory += "[new_text] "
@@ -165,6 +185,7 @@
if(antag_team)
antag_team.add_member(src)
A.on_gain()
+ log_game("[key_name(src)] has gained antag datum [A.name]([A.type])")
return A
/datum/mind/proc/remove_antag_datum(datum_type)
@@ -328,7 +349,7 @@
SSticker.mode.add_cultist(src)
else if(is_servant_of_ratvar(creator))
- add_servant_of_ratvar(src)
+ add_servant_of_ratvar(current)
else if(is_revolutionary(creator))
var/datum/antagonist/rev/converter = creator.mind.has_antag_datum(/datum/antagonist/rev,TRUE)
@@ -347,9 +368,10 @@
current.faction |= creator.faction
creator.faction |= current.faction
- if(creator.mind.special_role)
+ var/mob/living/carbon/C = creator
+ if(creator.mind?.special_role || (istype(C) && C.last_mind?.special_role))
message_admins("[ADMIN_LOOKUPFLW(current)] has been created by [ADMIN_LOOKUPFLW(creator)], an antagonist.")
- to_chat(current, "Despite your creators current allegiances, your true master remains [creator.real_name]. If their loyalties change, so do yours. This will never change unless your creator's body is destroyed.")
+ to_chat(current, "Despite your creator's current allegiances, your true master remains [creator.real_name]. If their loyalties change, so do yours. This will never change unless your creator's body is destroyed.")
/datum/mind/proc/show_memory(mob/recipient, window=1)
if(!recipient)
@@ -683,6 +705,11 @@
if(G)
G.reenter_corpse()
+/// Sets our can_hijack to the fastest speed our antag datums allow.
+/datum/mind/proc/get_hijack_speed()
+ . = 0
+ for(var/datum/antagonist/A in antag_datums)
+ . = max(., A.hijack_speed())
/datum/mind/proc/has_objective(objective_type)
for(var/datum/antagonist/A in antag_datums)
@@ -715,7 +742,7 @@
SSticker.minds += mind
if(!mind.name)
mind.name = real_name
- mind.current = src
+ mind.set_current(src)
/mob/living/carbon/mind_initialize()
..()
diff --git a/code/datums/mocking/client.dm b/code/datums/mocking/client.dm
new file mode 100644
index 0000000000000..913c47e2a31bb
--- /dev/null
+++ b/code/datums/mocking/client.dm
@@ -0,0 +1,4 @@
+/// This should match the interface of /client wherever necessary.
+/datum/client_interface
+ /// Player preferences datum for the client
+ var/datum/preferences/prefs
diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm
index c0481c15d9b09..75157efe6989d 100644
--- a/code/datums/mood_events/generic_negative_events.dm
+++ b/code/datums/mood_events/generic_negative_events.dm
@@ -217,3 +217,17 @@
/datum/mood_event/nanite_sadness/add_effects(message)
description = "+++++++[message]+++++++\n"
+
+/datum/mood_event/sec_insulated_gloves
+ description = "I look like an Assistant...\n"
+ mood_change = -1
+
+/datum/mood_event/burnt_wings
+ description = "MY PRECIOUS WINGS!!\n"
+ mood_change = -10
+ timeout = 10 MINUTES
+
+/datum/mood_event/aquarium_negative
+ description = "All the fish are dead...\n"
+ mood_change = -3
+ timeout = 1.5 MINUTES
\ No newline at end of file
diff --git a/code/datums/mood_events/generic_positive_events.dm b/code/datums/mood_events/generic_positive_events.dm
index 16806a459e10f..6abfbdf679ea4 100644
--- a/code/datums/mood_events/generic_positive_events.dm
+++ b/code/datums/mood_events/generic_positive_events.dm
@@ -19,6 +19,11 @@
/datum/mood_event/besthug/add_effects(mob/friend)
description = "[friend.name] is great to be around, [friend.p_they()] makes me feel so happy!\n"
+/datum/mood_event/headpat
+ description = "Headpats are lovely!\n"
+ mood_change = 1
+ timeout = 2 MINUTES
+
/datum/mood_event/arcade
description = "I beat the arcade game!\n"
mood_change = 3
@@ -91,7 +96,7 @@
/datum/mood_event/cult
description = "I have seen the truth, praise the almighty one!\n"
- mood_change = 10 //maybe being a cultist isnt that bad after all
+ mood_change = 10 //maybe being a cultist isn't that bad after all
hidden = TRUE
/datum/mood_event/determined
@@ -101,7 +106,7 @@
/datum/mood_event/heretics
description = "THE HIGHER I RISE , THE MORE I SEE.\n"
- mood_change = 10 //maybe being a cultist isnt that bad after all
+ mood_change = 10 //maybe being a cultist isn't that bad after all
hidden = TRUE
/datum/mood_event/family_heirloom
@@ -128,7 +133,7 @@
timeout = 3 MINUTES
/datum/mood_event/religiously_comforted
- description = "You are comforted by the presence of a holy person.\n"
+ description = "I feel comforted by the presence of a holy person.\n"
mood_change = 3
/datum/mood_event/clownshoes
@@ -171,6 +176,15 @@
/datum/mood_event/nanite_happiness/add_effects(message)
description = "+++++++[message]+++++++\n"
+/datum/mood_event/poppy_pin
+ description = "I feel proud to show my remembrance of the many who have died to ensure that I have freedom.\n"
+ mood_change = 1
+
+/datum/mood_event/funny_prank
+ description = "That was a funny prank, clown!\n"
+ mood_change = 2
+ timeout = 2 MINUTES
+
/datum/mood_event/area
description = "" //Fill this out in the area
mood_change = 0
@@ -178,3 +192,16 @@
/datum/mood_event/area/add_effects(list/param)
mood_change = param[1]
description = param[2]
+
+/datum/mood_event/sec_black_gloves
+ description = "Black gloves look good on me.\n"
+ mood_change = 1
+
+/datum/mood_event/assistant_insulated_gloves
+ description = "Finally got my hands on a good pair of gloves!\n"
+ mood_change = 1
+
+/datum/mood_event/aquarium_positive
+ description = "Watching fish in aquarium is calming.\n"
+ mood_change = 3
+ timeout = 1.5 MINUTES
diff --git a/code/datums/mood_events/needs_events.dm b/code/datums/mood_events/needs_events.dm
index 68566c8336be7..dbba37b24a06c 100644
--- a/code/datums/mood_events/needs_events.dm
+++ b/code/datums/mood_events/needs_events.dm
@@ -1,6 +1,6 @@
//nutrition
/datum/mood_event/fat
- description = "I'm so fat...\n" //muh fatshaming
+ description = "I'm so fat.\n" //muh fatshaming
mood_change = -6
/datum/mood_event/wellfed
@@ -38,15 +38,15 @@
mood_change = -4
/datum/mood_event/verygross
- description = "I think I'm going to puke...\n"
+ description = "I think I'm going to puke.\n"
mood_change = -6
/datum/mood_event/disgusted
- description = "Oh god that's disgusting...\n"
+ description = "Oh god, that's disgusting.\n"
mood_change = -8
/datum/mood_event/disgust/bad_smell
- description = "You smell something horribly decayed inside this room.\n"
+ description = "I can smell something horribly decayed inside this room.\n"
mood_change = -6
/datum/mood_event/disgust/nauseating_stench
@@ -60,6 +60,10 @@
/datum/mood_event/dirty
description = "I smell horrid.\n"
+ mood_change = -3
+
+/datum/mood_event/disgusting
+ description = "I smell DISGUSTING!\n"
mood_change = -5
/datum/mood_event/happy_neet
diff --git a/code/datums/movement_detector.dm b/code/datums/movement_detector.dm
new file mode 100644
index 0000000000000..2bb1ce0de7bf5
--- /dev/null
+++ b/code/datums/movement_detector.dm
@@ -0,0 +1,55 @@
+/// A datum to handle the busywork of registering signals to handle in depth tracking of a movable
+/datum/movement_detector
+ var/atom/movable/tracked
+ var/datum/callback/listener
+
+/datum/movement_detector/New(atom/movable/target, datum/callback/listener)
+ if(target)
+ track(target, listener)
+
+/datum/movement_detector/Destroy()
+ untrack()
+ tracked = null
+ listener = null
+ return ..()
+
+/// Sets up tracking of the given movable atom
+/datum/movement_detector/proc/track(atom/movable/target, datum/callback/listener)
+ untrack()
+ tracked = target
+ src.listener = listener
+
+ while(ismovableatom(target))
+ RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/move_react)
+ target = target.loc
+
+/// Stops tracking
+/datum/movement_detector/proc/untrack()
+ if(!tracked)
+ return
+ var/atom/movable/target = tracked
+ while(ismovableatom(target))
+ UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
+ target = target.loc
+
+/**
+ * Reacts to any movement that would cause a change in coordinates of the tracked movable atom
+ * This works by detecting movement of either the tracked object, or anything it is inside, recursively
+ */
+/datum/movement_detector/proc/move_react(atom/movable/mover, atom/oldloc, direction)
+ SIGNAL_HANDLER
+
+ var/turf/newturf = get_turf(tracked)
+
+ if(oldloc && !isturf(oldloc))
+ var/atom/target = oldloc
+ while(ismovableatom(target))
+ UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
+ target = target.loc
+ if(tracked.loc != newturf)
+ var/atom/target = mover.loc
+ while(ismovableatom(target))
+ RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/move_react, TRUE)
+ target = target.loc
+
+ listener.Invoke(tracked, mover, oldloc, direction)
diff --git a/code/datums/mutations.dm b/code/datums/mutations.dm
index 791401f8fe516..aa826f1ad682f 100644
--- a/code/datums/mutations.dm
+++ b/code/datums/mutations.dm
@@ -23,7 +23,7 @@
var/instability = 0 //instability the holder gets when the mutation is not native
var/blocks = 4 //Amount of those big blocks with gene sequences
var/difficulty = 8 //Amount of missing sequences. Sometimes it removes an entire pair for 2 points
- var/timed = FALSE //Boolean to easily check if we're going to self destruct
+ var/timed = FALSE //Boolean to easily check if we're going to self-destruct
var/alias //'Mutation #49', decided every round to get some form of distinction between undiscovered mutations
var/scrambled = FALSE //Wheter we can read it if it's active. To avoid cheesing with mutagen
var/class //Decides player accesibility, sorta
@@ -32,6 +32,7 @@
//MUT_NORMAL - A mutation that can be activated and deactived by completing a sequence
//MUT_EXTRA - A mutation that is in the mutations tab, and can be given and taken away through though the DNA console. Has a 0 before it's name in the mutation section of the dna console
//MUT_OTHER Cannot be interacted with by players through normal means. I.E. wizards mutate
+ var/list/valid_chrom_list = list() //List of strings of valid chromosomes this mutation can accept.
var/can_chromosome = CHROMOSOME_NONE //can we take chromosomes? 0: CHROMOSOME_NEVER never, 1:CHROMOSOME_NONE yeah, 2: CHROMOSOME_USED no, already have one
@@ -157,6 +158,7 @@
energy_coeff = HM.energy_coeff
mutadone_proof = HM.mutadone_proof
can_chromosome = HM.can_chromosome
+ valid_chrom_list = HM.valid_chrom_list
/datum/mutation/human/proc/remove_chromosome()
stabilizer_coeff = initial(stabilizer_coeff)
@@ -182,3 +184,23 @@
power.panel = "Genetic"
owner.AddSpell(power)
return TRUE
+
+// Runs through all the coefficients and uses this to determine which chromosomes the
+// mutation can take. Stores these as text strings in a list.
+/datum/mutation/human/proc/update_valid_chromosome_list()
+ valid_chrom_list.Cut()
+
+ if(can_chromosome == CHROMOSOME_NEVER)
+ valid_chrom_list += "none"
+ return
+
+ valid_chrom_list += "Reinforcement"
+
+ if(stabilizer_coeff != -1)
+ valid_chrom_list += "Stabilizer"
+ if(synchronizer_coeff != -1)
+ valid_chrom_list += "Synchronizer"
+ if(power_coeff != -1)
+ valid_chrom_list += "Power"
+ if(energy_coeff != -1)
+ valid_chrom_list += "Energetic"
diff --git a/code/datums/mutations/_combined.dm b/code/datums/mutations/_combined.dm
index 1e6aa90dbe518..a0776532b4aa6 100644
--- a/code/datums/mutations/_combined.dm
+++ b/code/datums/mutations/_combined.dm
@@ -17,10 +17,6 @@
required = "/datum/mutation/human/strong; /datum/mutation/human/radioactive"
result = HULK
-/datum/generecipe/x_ray
- required = "/datum/mutation/human/thermal; /datum/mutation/human/radioactive"
- result = /datum/mutation/human/thermal/x_ray
-
/datum/generecipe/mindread
required = "/datum/mutation/human/antenna; /datum/mutation/human/paranoia"
result = MINDREAD
diff --git a/code/datums/mutations/actions.dm b/code/datums/mutations/actions.dm
index 9bd282100b63e..9486416ca6cfd 100644
--- a/code/datums/mutations/actions.dm
+++ b/code/datums/mutations/actions.dm
@@ -25,9 +25,9 @@
/datum/mutation/human/olfaction/on_life()
var/hygiene_now = owner.hygiene
- if(hygiene_now < 100 && prob(5))
+ if(hygiene_now < 100 && prob(3))
owner.adjust_disgust(GET_MUTATION_SYNCHRONIZER(src) * (rand(3,5)))
- if(hygiene_now < HYGIENE_LEVEL_DIRTY && prob(50))
+ if(hygiene_now < HYGIENE_LEVEL_DIRTY && prob(15))
to_chat(owner,"You get a whiff of your stench and feel sick!")
owner.adjust_disgust(GET_MUTATION_SYNCHRONIZER(src) * rand(5,10))
@@ -89,10 +89,10 @@
if(tracking_target == user)
to_chat(user,"You smell out the trail to yourself. Yep, it's you.")
return
- if(usr.z < tracking_target.z)
+ if(usr.get_virtual_z_level() < tracking_target.get_virtual_z_level())
to_chat(user,"The trail leads... way up above you? Huh. They must be really, really far away.")
return
- else if(usr.z > tracking_target.z)
+ else if(usr.get_virtual_z_level() > tracking_target.get_virtual_z_level())
to_chat(user,"The trail leads... way down below you? Huh. They must be really, really far away.")
return
var/direction_text = "[dir2text(get_dir(usr, tracking_target))]"
diff --git a/code/datums/mutations/antenna.dm b/code/datums/mutations/antenna.dm
index f49c847f42ddc..c47ef8bb6b22d 100644
--- a/code/datums/mutations/antenna.dm
+++ b/code/datums/mutations/antenna.dm
@@ -6,7 +6,7 @@
text_lose_indication = "Your antenna shrinks back down."
instability = 5
difficulty = 8
- var/obj/item/implant/radio/antenna/linked_radio
+ var/datum/weakref/radio_weakref
/obj/item/implant/radio/antenna
name = "internal antenna organ"
@@ -21,14 +21,16 @@
/datum/mutation/human/antenna/on_acquiring(mob/living/carbon/human/owner)
if(..())
return
- linked_radio = new(owner)
+ var/obj/item/implant/radio/antenna/linked_radio = new(owner)
linked_radio.implant(owner, null, TRUE, TRUE)
+ radio_weakref = WEAKREF(linked_radio)
/datum/mutation/human/antenna/on_losing(mob/living/carbon/human/owner)
if(..())
return
+ var/obj/item/implant/radio/antenna/linked_radio = radio_weakref.resolve()
if(linked_radio)
- linked_radio.Destroy()
+ QDEL_NULL(linked_radio)
/datum/mutation/human/antenna/New(class_ = MUT_OTHER, timer, datum/mutation/human/copymut)
..()
@@ -59,7 +61,7 @@
/obj/effect/proc_holder/spell/targeted/mindread/cast(list/targets, mob/living/carbon/human/user = usr)
for(var/mob/living/M in targets)
- if(istype(usr.get_item_by_slot(SLOT_HEAD), /obj/item/clothing/head/foilhat) || istype(M.get_item_by_slot(SLOT_HEAD), /obj/item/clothing/head/foilhat))
+ if(istype(usr.get_item_by_slot(ITEM_SLOT_HEAD), /obj/item/clothing/head/foilhat) || istype(M.get_item_by_slot(ITEM_SLOT_HEAD), /obj/item/clothing/head/foilhat))
to_chat(usr, "As you reach out with your mind, you're suddenly stopped by a vision of a massive tinfoil wall that streches beyond visible range. It seems you've been foiled.")
return
if(M.stat == DEAD)
diff --git a/code/datums/mutations/body.dm b/code/datums/mutations/body.dm
index d8907013b13e9..4db8b55c6cfc1 100644
--- a/code/datums/mutations/body.dm
+++ b/code/datums/mutations/body.dm
@@ -71,7 +71,7 @@
desc = "Subject is easily terrified, and may suffer from hallucinations."
quality = NEGATIVE
text_gain_indication = "You feel screams echo through your mind..."
- text_lose_indication = "The screaming in your mind fades."
+ text_lose_indication = "The screaming in your mind fades."
/datum/mutation/human/paranoia/on_life()
if(prob(5) && owner.stat == CONSCIOUS)
@@ -175,11 +175,11 @@
/datum/mutation/human/race/on_acquiring(mob/living/carbon/human/owner)
if(..())
return
- . = owner.monkeyize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSE)
+ . = owner.monkeyize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSE | TR_KEEPAI)
/datum/mutation/human/race/on_losing(mob/living/carbon/monkey/owner)
if(owner && istype(owner) && owner.stat != DEAD && (owner.dna.mutations.Remove(src)))
- . = owner.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSE)
+ . = owner.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSE | TR_KEEPAI)
/datum/mutation/human/glow
name = "Glowy"
@@ -251,7 +251,7 @@
desc = "The user's skin will randomly combust, but is generally alot more resilient to burning."
quality = NEGATIVE
text_gain_indication = "You feel hot."
- text_lose_indication = "You feel a lot cooler."
+ text_lose_indication = "You feel a lot cooler."
difficulty = 14
synchronizer_coeff = 1
power_coeff = 1
@@ -276,7 +276,7 @@
desc = "The victim of the mutation has a very weak link to spatial reality, and may be displaced. Often causes extreme nausea."
quality = NEGATIVE
text_gain_indication = "The space around you twists sickeningly."
- text_lose_indication = "The space around you settles back to normal."
+ text_lose_indication = "The space around you settles back to normal."
difficulty = 18//high so it's hard to unlock and abuse
instability = 10
synchronizer_coeff = 1
@@ -306,7 +306,7 @@
desc = "Subject has acidic chemicals building up underneath their skin. This is often lethal."
quality = NEGATIVE
text_gain_indication = "A horrible burning sensation envelops you as your flesh turns to acid!"
- text_lose_indication = "A feeling of relief covers you as your flesh goes back to normal."
+ text_lose_indication = "A feeling of relief covers you as your flesh goes back to normal."
difficulty = 18//high so it's hard to unlock and use on others
var/msgcooldown = 0
@@ -346,7 +346,7 @@
desc = "Subject suffers from muscle spasms."
quality = NEGATIVE
text_gain_indication = "You flinch."
- text_lose_indication = "Your flinching subsides."
+ text_lose_indication = "Your flinching subsides."
difficulty = 16
/datum/mutation/human/spastic/on_acquiring()
@@ -364,7 +364,7 @@
desc = "A mutation that replaces the right foot with another left foot. It makes standing up after getting knocked down very difficult."
quality = NEGATIVE
text_gain_indication = "Your right foot feels... left."
- text_lose_indication = "Your right foot feels alright."
+ text_lose_indication = "Your right foot feels alright."
difficulty = 16
var/stun_cooldown = 0
diff --git a/code/datums/mutations/cluwne.dm b/code/datums/mutations/cluwne.dm
index ce625818fddd5..75b105918f325 100644
--- a/code/datums/mutations/cluwne.dm
+++ b/code/datums/mutations/cluwne.dm
@@ -19,18 +19,18 @@
if(!istype(H.wear_mask, /obj/item/clothing/mask/cluwne))
if(!H.doUnEquip(H.wear_mask))
qdel(H.wear_mask)
- H.equip_to_slot_or_del(new /obj/item/clothing/mask/cluwne(H), SLOT_WEAR_MASK)
+ H.equip_to_slot_or_del(new /obj/item/clothing/mask/cluwne(H), ITEM_SLOT_MASK)
if(!istype(H.w_uniform, /obj/item/clothing/under/cluwne))
if(!H.doUnEquip(H.w_uniform))
qdel(H.w_uniform)
- H.equip_to_slot_or_del(new /obj/item/clothing/under/cluwne(H), SLOT_W_UNIFORM)
+ H.equip_to_slot_or_del(new /obj/item/clothing/under/cluwne(H), ITEM_SLOT_ICLOTHING)
if(!istype(H.shoes, /obj/item/clothing/shoes/cluwne))
if(!H.doUnEquip(H.shoes))
qdel(H.shoes)
- H.equip_to_slot_or_del(new /obj/item/clothing/shoes/cluwne(H), SLOT_SHOES)
+ H.equip_to_slot_or_del(new /obj/item/clothing/shoes/cluwne(H), ITEM_SLOT_FEET)
- owner.equip_to_slot_or_del(new /obj/item/clothing/gloves/color/white(owner), SLOT_GLOVES) // this is purely for cosmetic purposes incase they aren't wearing anything in that slot
- owner.equip_to_slot_or_del(new /obj/item/storage/backpack/clown(owner), SLOT_BACK) // ditto
+ owner.equip_to_slot_or_del(new /obj/item/clothing/gloves/color/white(owner), ITEM_SLOT_GLOVES) // this is purely for cosmetic purposes incase they aren't wearing anything in that slot
+ owner.equip_to_slot_or_del(new /obj/item/storage/backpack/clown(owner), ITEM_SLOT_BACK) // ditto
/datum/mutation/human/cluwne/on_life(mob/living/carbon/human/owner)
if((prob(15) && owner.IsUnconscious()))
diff --git a/code/datums/mutations/cold.dm b/code/datums/mutations/cold.dm
index cd10c2b38f759..146f7b21661a6 100644
--- a/code/datums/mutations/cold.dm
+++ b/code/datums/mutations/cold.dm
@@ -16,6 +16,24 @@
delete_old = FALSE
action_icon_state = "snow"
+/datum/mutation/human/wax_saliva
+ name = "Waxy Saliva"
+ desc = "Allows the user to secrete wax."
+ quality = POSITIVE
+ text_gain_indication = "Your mouth feels waxy."
+ instability = 10
+ difficulty = 10
+ synchronizer_coeff = 1
+ locked = TRUE
+ power = /obj/effect/proc_holder/spell/targeted/conjure_item/wax
+
+/obj/effect/proc_holder/spell/targeted/conjure_item/wax
+ name = "Secrete Wax"
+ desc = "Concentrate to spit out some wax, useful for bee-themed construction."
+ item_type = /obj/item/stack/sheet/mineral/wax
+ charge_max = 50
+ delete_old = FALSE
+ action_icon_state = "honey"
/datum/mutation/human/cryokinesis
name = "Cryokinesis"
diff --git a/code/datums/mutations/hulk.dm b/code/datums/mutations/hulk.dm
index d5ad0f9849a8a..2857ddd65c7a0 100644
--- a/code/datums/mutations/hulk.dm
+++ b/code/datums/mutations/hulk.dm
@@ -46,6 +46,8 @@
SEND_SIGNAL(owner, COMSIG_CLEAR_MOOD_EVENT, "hulk")
/datum/mutation/human/hulk/proc/handle_speech(original_message, wrapped_message)
+ SIGNAL_HANDLER
+
var/message = wrapped_message[1]
if(message)
message = "[replacetext(message, ".", "!")]!!"
diff --git a/code/datums/mutations/speech.dm b/code/datums/mutations/speech.dm
index 9a30cd623cd5a..bb4a910b52061 100644
--- a/code/datums/mutations/speech.dm
+++ b/code/datums/mutations/speech.dm
@@ -30,6 +30,8 @@
UnregisterSignal(owner, COMSIG_MOB_SAY)
/datum/mutation/human/wacky/proc/handle_speech(datum/source, list/speech_args)
+ SIGNAL_HANDLER
+
speech_args[SPEECH_SPANS] |= SPAN_SANS
/datum/mutation/human/mute
@@ -73,7 +75,6 @@
message = " [message] "
//Time for a friendly game of SS13
message = replacetext(message," stupid "," smart ")
- message = replacetext(message," retard "," genius ")
message = replacetext(message," unrobust "," robust ")
message = replacetext(message," dumb "," smart ")
message = replacetext(message," awful "," great ")
@@ -153,6 +154,8 @@
UnregisterSignal(owner, COMSIG_MOB_SAY)
/datum/mutation/human/swedish/proc/handle_speech(datum/source, list/speech_args)
+ SIGNAL_HANDLER
+
var/message = speech_args[SPEECH_MESSAGE]
if(message)
message = replacetext(message,"w","v")
@@ -182,31 +185,36 @@
UnregisterSignal(owner, COMSIG_MOB_SAY)
/datum/mutation/human/chav/proc/handle_speech(datum/source, list/speech_args)
+ SIGNAL_HANDLER
+
var/message = speech_args[SPEECH_MESSAGE]
- if(message)
- message = " [message] "
- message = replacetext(message," looking at "," gawpin' at ")
- message = replacetext(message," great "," bangin' ")
- message = replacetext(message," man "," mate ")
- message = replacetext(message," friend ",pick(" mate "," bruv "," bledrin "))
- message = replacetext(message," what "," wot ")
- message = replacetext(message," drink "," wet ")
- message = replacetext(message," get "," giz ")
- message = replacetext(message," what "," wot ")
- message = replacetext(message," no thanks "," wuddent fukken do one ")
- message = replacetext(message," i don't know "," wot mate ")
- message = replacetext(message," no "," naw ")
- message = replacetext(message," robust "," chin ")
- message = replacetext(message," hi "," how what how ")
- message = replacetext(message," hello "," sup bruv ")
- message = replacetext(message," kill "," bang ")
- message = replacetext(message," murder "," bang ")
- message = replacetext(message," windows "," windies ")
- message = replacetext(message," window "," windy ")
- message = replacetext(message," break "," do ")
- message = replacetext(message," your "," yer ")
- message = replacetext(message," security "," coppers ")
- speech_args[SPEECH_MESSAGE] = trim(message)
+ if(message[1] != "*")
+ message = " [message]"
+ var/list/whole_words = strings("british_talk.json", "words")
+ var/list/british_sounds = strings("british_talk.json", "sounds")
+ var/list/british_appends = strings("british_talk.json", "appends")
+
+ for(var/key in whole_words)
+ var/value = whole_words[key]
+ if(islist(value))
+ value = pick(value)
+
+ message = replacetextEx(message, " [uppertext(key)]", " [uppertext(value)]")
+ message = replacetextEx(message, " [capitalize(key)]", " [capitalize(value)]")
+ message = replacetextEx(message, " [key]", " [value]")
+
+ for(var/key in british_sounds)
+ var/value = british_sounds[key]
+ if(islist(value))
+ value = pick(value)
+
+ message = replacetextEx(message, "[uppertext(key)]", "[uppertext(value)]")
+ message = replacetextEx(message, "[capitalize(key)]", "[capitalize(value)]")
+ message = replacetextEx(message, "[key]", "[value]")
+
+ if(prob(8))
+ message += pick(british_appends)
+ speech_args[SPEECH_MESSAGE] = trim(message)
/datum/mutation/human/elvis
@@ -239,10 +247,12 @@
UnregisterSignal(owner, COMSIG_MOB_SAY)
/datum/mutation/human/elvis/proc/handle_speech(datum/source, list/speech_args)
+ SIGNAL_HANDLER
+
var/message = speech_args[SPEECH_MESSAGE]
if(message)
message = " [message] "
- message = replacetext(message," i'm not "," I aint ")
+ message = replacetext(message," i'm not "," I ain't ")
message = replacetext(message," girl ",pick(" honey "," baby "," baby doll "))
message = replacetext(message," man ",pick(" son "," buddy "," brother"," pal "," friendo "))
message = replacetext(message," out of "," outta ")
diff --git a/code/datums/outfit.dm b/code/datums/outfit.dm
index 0ccf850e37faa..3d94ada343546 100755
--- a/code/datums/outfit.dm
+++ b/code/datums/outfit.dm
@@ -153,31 +153,31 @@
//Start with uniform,suit,backpack for additional slots
if(uniform)
- H.equip_to_slot_or_del(new uniform(H),SLOT_W_UNIFORM)
+ H.equip_to_slot_or_del(new uniform(H),ITEM_SLOT_ICLOTHING, TRUE)
if(suit)
- H.equip_to_slot_or_del(new suit(H),SLOT_WEAR_SUIT)
+ H.equip_to_slot_or_del(new suit(H),ITEM_SLOT_OCLOTHING, TRUE)
if(back)
- H.equip_to_slot_or_del(new back(H),SLOT_BACK)
+ H.equip_to_slot_or_del(new back(H),ITEM_SLOT_BACK, TRUE)
if(belt)
- H.equip_to_slot_or_del(new belt(H),SLOT_BELT)
+ H.equip_to_slot_or_del(new belt(H),ITEM_SLOT_BELT, TRUE)
if(gloves)
- H.equip_to_slot_or_del(new gloves(H),SLOT_GLOVES)
+ H.equip_to_slot_or_del(new gloves(H),ITEM_SLOT_GLOVES, TRUE)
if(shoes)
- H.equip_to_slot_or_del(new shoes(H),SLOT_SHOES)
+ H.equip_to_slot_or_del(new shoes(H),ITEM_SLOT_FEET, TRUE)
if(head)
- H.equip_to_slot_or_del(new head(H),SLOT_HEAD)
+ H.equip_to_slot_or_del(new head(H),ITEM_SLOT_HEAD, TRUE)
if(mask)
- H.equip_to_slot_or_del(new mask(H),SLOT_WEAR_MASK)
+ H.equip_to_slot_or_del(new mask(H),ITEM_SLOT_MASK, TRUE)
if(neck)
- H.equip_to_slot_or_del(new neck(H),SLOT_NECK)
+ H.equip_to_slot_or_del(new neck(H),ITEM_SLOT_NECK, TRUE)
if(ears)
- H.equip_to_slot_or_del(new ears(H),SLOT_EARS)
+ H.equip_to_slot_or_del(new ears(H),ITEM_SLOT_EARS, TRUE)
if(glasses)
- H.equip_to_slot_or_del(new glasses(H),SLOT_GLASSES)
+ H.equip_to_slot_or_del(new glasses(H),ITEM_SLOT_EYES, TRUE)
if(id)
- H.equip_to_slot_or_del(new id(H),SLOT_WEAR_ID)
+ H.equip_to_slot_or_del(new id(H),ITEM_SLOT_ID, TRUE)
if(suit_store)
- H.equip_to_slot_or_del(new suit_store(H),SLOT_S_STORE)
+ H.equip_to_slot_or_del(new suit_store(H),ITEM_SLOT_SUITSTORE, TRUE)
if(accessory)
var/obj/item/clothing/under/U = H.w_uniform
@@ -193,9 +193,9 @@
if(!visualsOnly) // Items in pockets or backpack don't show up on mob's icon.
if(l_pocket)
- H.equip_to_slot_or_del(new l_pocket(H),SLOT_L_STORE)
+ H.equip_to_slot_or_del(new l_pocket(H),ITEM_SLOT_LPOCKET, TRUE)
if(r_pocket)
- H.equip_to_slot_or_del(new r_pocket(H),SLOT_R_STORE)
+ H.equip_to_slot_or_del(new r_pocket(H),ITEM_SLOT_RPOCKET, TRUE)
if(box)
if(!backpack_contents)
@@ -209,7 +209,7 @@
if(!isnum_safe(number))//Default to 1
number = 1
for(var/i in 1 to number)
- H.equip_to_slot_or_del(new path(H),SLOT_IN_BACKPACK)
+ H.equip_to_slot_or_del(new path(H),ITEM_SLOT_BACKPACK, TRUE)
if(!H.head && toggle_helmet && istype(H.wear_suit, /obj/item/clothing/suit/space/hardsuit))
var/obj/item/clothing/suit/space/hardsuit/HS = H.wear_suit
diff --git a/code/datums/radiation_wave.dm b/code/datums/radiation_wave.dm
index e793cd1a0b9c7..a1f90ffa9f8f2 100644
--- a/code/datums/radiation_wave.dm
+++ b/code/datums/radiation_wave.dm
@@ -1,25 +1,53 @@
+#define PRC_FLAG_HL (1<<0) // Send half of current strength to the upper "left"
+#define PRC_FLAG_L (1<<1) // Send the whole current strength to the upper "left": direct succession
+#define PRC_FLAG_HR (1<<2) // Send half of current strength to the upper "right"
+#define PRC_FLAG_R (1<<3) // Send the whole current strength to the upper "right": direct succession
+
+/**
+ * Crash Course: Understanding PRC_BEHAVIOR
+ * We move forward, and in square-by-square manner, so we need a list 8 entries larger than the current one, and future squares are determined from the current square.
+ * We move clockwise: "left" means intensity_new[i + j], "right" means intensity_new[i + j + 1]. `j` here is offset.
+ * Most squares are on branch: It moves "left"(L) or "right"(R).
+ * But, sometimes, a branch can't decide which way to go; then it splits(D) and merges(HL, HR).
+ * Then there are squares not on branch; those don't go anywhere else(N).
+ * But branchless squares still need to act radiation; does branchless square's does one time transient succession from both of immediate predecessors
+ * ... in contrast to "on-branch" squares where there are only one meaningful predecessor.
+ * Since we are calculating future squares, we need to see if there are any branchless squares needing our attention: (*STAR)
+ * And, of course, branchless squares might have to draw from another preceding branchless squares. (NWL, NWR)
+ */
+
+#define PRC_BEHAVIOR_N (1<<4) // Not on branch, and both upper squares are on branch
+ // So we don't calculate prc_behavior every time (setting to 0 would do that)
+#define PRC_BEHAVIOR_NWL PRC_FLAG_HL // Not on branch, but will send half of its strength to the "left" since it also is not on branch
+#define PRC_BEHAVIOR_NWR PRC_FLAG_HR // Not on branch, but will send half of its strength to the "left" since it also is not on branch
+#define PRC_BEHAVIOR_L PRC_FLAG_L // On branch, going "left"
+#define PRC_BEHAVIOR_LSTAR (PRC_FLAG_L|PRC_FLAG_HR) // On branch, going "left", but there's branchless square on the "right"
+#define PRC_BEHAVIOR_R PRC_FLAG_R // On branch, going "right"
+#define PRC_BEHAVIOR_RSTAR (PRC_FLAG_R|PRC_FLAG_HL) // On branch, going "left", but there's branchless square on the "left"
+#define PRC_BEHAVIOR_D (PRC_FLAG_L|PRC_FLAG_R) // On branch, double successor.
+#define PRC_BEHAVIOR_HL PRC_FLAG_HL // From one of the double successor; single successor on the "left"
+#define PRC_BEHAVIOR_HLSTAR (PRC_FLAG_HL|PRC_FLAG_HR) // From one of the double successor; single successor on the "left", but there's branchless square on the "right"
+#define PRC_BEHAVIOR_HR PRC_FLAG_HR // From one of the double successor; single successor on the "right"
+#define PRC_BEHAVIOR_HRSTAR (PRC_FLAG_HR|PRC_FLAG_HL) // From one of the double successor; single successor on the "right", but there's branchless square on the "left"
+
/datum/radiation_wave
var/source
var/turf/master_turf //The center of the wave
- var/steps=0 //How far we've moved
- var/intensity //How strong it was originaly
+ var/steps = 0 //How far we've moved
+ var/intensity[8] //How strong it is, except the distance falloff
var/range_modifier //Higher than 1 makes it drop off faster, 0.5 makes it drop off half etc
- var/move_dir //The direction of movement
- var/list/__dirs //The directions to the side of the wave, stored for easy looping
var/can_contaminate
+ var/static/list/prc_behavior_cache
-/datum/radiation_wave/New(atom/_source, dir, _intensity=0, _range_modifier=RAD_DISTANCE_COEFFICIENT, _can_contaminate=TRUE)
+/datum/radiation_wave/New(atom/_source, _intensity=0, _range_modifier=RAD_DISTANCE_COEFFICIENT, _can_contaminate=TRUE)
source = "[_source] \[[REF(_source)]\]"
master_turf = get_turf(_source)
- move_dir = dir
- __dirs = list()
- __dirs+=turn(dir, 90)
- __dirs+=turn(dir, -90)
-
- intensity = _intensity
+ // Yes, it causes (8 / range_modifier ** 2) times the strength you gave to the radiation_pulse().
+ for(var/i in 1 to 8)
+ intensity[i] = _intensity
range_modifier = _range_modifier
can_contaminate = _can_contaminate
@@ -27,69 +55,178 @@
/datum/radiation_wave/Destroy()
. = QDEL_HINT_IWILLGC
+ intensity = null
STOP_PROCESSING(SSradiation, src)
..()
-/datum/radiation_wave/process()
- master_turf = get_step(master_turf, move_dir)
+/datum/radiation_wave/process(delta_time)
+ // If master_turf is no more, then we can't know where to irradiate. This is a very bad situation.
if(!master_turf)
qdel(src)
return
- steps++
- var/list/atoms = get_rad_atoms()
+ // If none of the turfs could be irradiated, then the wave should no longer exist
+ var/futile = TRUE
+ // Cache of unlucky atoms
+ var/list/atoms = list()
+ // The actual distance
+ var/distance = steps + 1
+ // Represents decreasing radiation power over distance
+ var/falloff = 1 / (distance * range_modifier) ** 2
+ // Caching
+ var/turf/cmaster_turf = master_turf
+ // Original intensity it is using
+ var/list/cintensity = intensity
+ // New intensity that'll be written; always larger than the previous one
+ var/list/intensity_new[(distance + 1) * 8]
+ // "Class" it belongs to
+ var/branchclass = 2 ** round(log(2, distance))
+ // The secondary i, or the offset for i
+ var/j
- var/strength
- if(steps>1)
- strength = INVERSE_SQUARE(intensity, max(range_modifier*steps, 1), 1)
- else
- strength = intensity
+ for(var/i in 1 to distance * 8)
+ //Culls invalid intensities
+ if(cintensity[i] * falloff < RAD_WAVE_MINIMUM)
+ continue
+ var/xpos
+ var/ypos
+ switch(i / distance)
+ if(0 to 2)
+ //Yes it starts one step off of what you'd expect. Blame BYOND.
+ xpos = cmaster_turf.x + distance
+ ypos = cmaster_turf.y + distance - i
+ if(2 to 4)
+ xpos = cmaster_turf.x + distance * 3 - i
+ ypos = cmaster_turf.y - distance
+ if(4 to 6)
+ xpos = cmaster_turf.x - distance
+ ypos = cmaster_turf.y - distance * 5 + i
+ if(6 to 8)
+ xpos = cmaster_turf.x - distance * 7 + i
+ ypos = cmaster_turf.y + distance
+ //Culls invalid coords
+ if(xpos < 1 || xpos > world.maxx || ypos < 1 || ypos > world.maxy)
+ continue
- if(strength < RAD_WAVE_MINIMUM)
- qdel(src)
- return
+ //The radiation is considered alive
+ futile = FALSE
+ var/turf/place = locate(xpos, ypos, cmaster_turf.z)
+ atoms = get_rad_contents(place)
- radiate(atoms, FLOOR(strength, 1))
+ //Actual radiation spending
+ cintensity[i] *= radiate(atoms, cintensity[i] * falloff)
- check_obstructions(atoms) // reduce our overall strength if there are radiation insulators
+ //Obstruction handling
+ check_obstructions(atoms, i)
-/datum/radiation_wave/proc/get_rad_atoms()
- var/list/atoms = list()
- var/distance = steps
- var/cmove_dir = move_dir
- var/cmaster_turf = master_turf
+ /*
+ * This is what I call pseudo-raycasting (PRC). Real raycasting would be ridiculously expensive,
+ * So this is the solution I came up with. Don't try to understand it by seeing the code.
+ * You have been warned. If you find yourself really having to touch this cursed code,
+ * consider axing this away before contacting me via git-fu email digging.
+ *
+ * Therefore, I urge you not to hastily assume this code a culprit of your problem.
+ * This code is responsible just for *keeping the rads going forward* more reasonably
+ * in regard to obstruction and contamination cost. But, of course, if you are rewriting
+ * (notwithstanding how questionable rewriting something major of a mature codebase like
+ * every normal SS13 codebase is) the entire radiation code, then this code should be
+ * considered for deletion.
+ *
+ * On a side note, this implementation isn't very ideal. So please remove this instead of
+ * trying to improve it when its time has come. (i.e. another total overhaul)
+ *
+ * ~Xenomedes, Christmas 2020
+ */
- if(cmove_dir == NORTH || cmove_dir == SOUTH)
- distance-- //otherwise corners overlap
+ // Handling eight fundamental (read: perfectly straight) branches
+ if((j = i / distance) == (j = round(j)))
+ distance + 1 == branchclass * 2 \
+ ? (i == distance * 8 \
+ ? (intensity_new[j - 1] += (intensity_new[1] += ((intensity_new[(j += i)] = cintensity[i]) / 2)) && cintensity[i] / 2) \
+ : (intensity_new[j - 1] += intensity_new[j + 1] = ((intensity_new[(j += i)] = cintensity[i]) / 2))) \
+ : (intensity_new[i + j] = cintensity[i])
+ continue
- atoms += get_rad_contents(cmaster_turf)
+ var/list/cachecache
- var/turf/place
- for(var/dir in __dirs) //There should be just 2 dirs in here, left and right of the direction of movement
- place = cmaster_turf
- for(var/i in 1 to distance)
- place = get_step(place, dir)
- atoms += get_rad_contents(place)
+ if(!prc_behavior_cache)
+ prc_behavior_cache = list()
+ if(length(prc_behavior_cache) < distance)
+ prc_behavior_cache.len++
+ // We don't reserve spaces for fundamental branches
+ var/L[distance - 1]
+ // distance == 1 is where every ray is fundamental branch
+ cachecache = prc_behavior_cache[distance - 1] = L
+ else
+ cachecache = prc_behavior_cache[distance - 1]
- return atoms
+ // i % distance == 0 cases were already handled above
+ var/prc_behavior = cachecache[i % distance]
-/datum/radiation_wave/proc/check_obstructions(list/atoms)
- var/width = steps
- var/cmove_dir = move_dir
- if(cmove_dir == NORTH || cmove_dir == SOUTH)
- width--
- width = 1+(2*width)
+ if(!prc_behavior)
+ // Necessary local variables
+ var/idx // index
+ var/lp // loop position
+ var/vl // velocity of loop
+ var/bt // branch threshold
+ // The actual behavior calculation
+ cachecache[i % distance] = prc_behavior = distance & 1 \
+ ? ((lp = ((idx = i % distance) * (vl = distance - branchclass + 1)) % (distance + 1)) < (bt = branchclass - (idx - round(idx * vl / (distance + 1)))) \
+ ? (lp \
+ ? (lp + vl >= bt ? PRC_BEHAVIOR_LSTAR : PRC_BEHAVIOR_L) \
+ : (vl >= bt ? PRC_BEHAVIOR_HLSTAR : PRC_BEHAVIOR_HL)) \
+ : (lp > branchclass \
+ ? (lp - vl <= bt ? PRC_BEHAVIOR_NWL : (lp - bt > branchclass ? PRC_BEHAVIOR_NWR : PRC_BEHAVIOR_N)) \
+ : (lp == branchclass \
+ ? (lp - vl <= bt ? PRC_BEHAVIOR_HRSTAR : PRC_BEHAVIOR_HR) \
+ : (lp - vl <= bt ? PRC_BEHAVIOR_RSTAR : PRC_BEHAVIOR_R)))) \
+ : ((lp = ((idx = i % distance) * (vl = distance - branchclass + 1)) % (distance + 1)) == (bt = branchclass - (idx - round(idx * vl / (distance + 1)))) \
+ ? PRC_BEHAVIOR_D \
+ : (lp > branchclass \
+ ? (lp - vl <= bt ? PRC_BEHAVIOR_NWL : (lp - bt > branchclass ? PRC_BEHAVIOR_NWR : PRC_BEHAVIOR_N)) \
+ : (lp < bt \
+ ? (lp + vl >= bt ? PRC_BEHAVIOR_LSTAR : PRC_BEHAVIOR_L) \
+ : (lp - vl <= bt ? PRC_BEHAVIOR_RSTAR : PRC_BEHAVIOR_R))))
+
+ prc_behavior & PRC_FLAG_HL \
+ ? (intensity_new[i + j] += cintensity[i] / 2) \
+ : (prc_behavior & PRC_FLAG_L \
+ ? (intensity_new[i + j] = cintensity[i]) \
+ : null)
+
+ prc_behavior & PRC_FLAG_HR \
+ ? (intensity_new[i + j + 1] += cintensity[i] / 2) \
+ : (prc_behavior & PRC_FLAG_R \
+ ? (intensity_new[i + j + 1] = cintensity[i]) \
+ : null)
+
+ if(futile)
+ qdel(src)
+ return
+
+ // Now is time to move forward
+ intensity = intensity_new
+ steps += delta_time
+
+/datum/radiation_wave/proc/check_obstructions(list/atoms, index)
for(var/k in 1 to atoms.len)
var/atom/thing = atoms[k]
if(!thing)
continue
- if (SEND_SIGNAL(thing, COMSIG_ATOM_RAD_WAVE_PASSING, src, width) & COMPONENT_RAD_WAVE_HANDLED)
+ if (SEND_SIGNAL(thing, COMSIG_ATOM_RAD_WAVE_PASSING, src, index) & COMPONENT_RAD_WAVE_HANDLED)
continue
if (thing.rad_insulation != RAD_NO_INSULATION)
- intensity *= (1-((1-thing.rad_insulation)/width))
+ intensity[index] *= thing.rad_insulation
+// Returns post-radiation strength power scale of a ray
+// If this proc returns a number lower than 1, it means that the some radiation was spent on contaminating something.
/datum/radiation_wave/proc/radiate(list/atoms, strength)
- var/contamination_chance = (strength-RAD_MINIMUM_CONTAMINATION) * RAD_CONTAMINATION_CHANCE_COEFFICIENT * min(1, 1/(steps*range_modifier))
+ // returning 1 means no radiation was spent on contamination
+ . = 1
+ var/list/moblist = list()
+ var/list/atomlist = list()
+ var/contam_strength = strength * (RAD_CONTAMINATION_STR_COEFFICIENT * RAD_CONTAMINATION_BUDGET_SIZE) // The budget for each list
+ var/is_contaminating = contam_strength > RAD_COMPONENT_MINIMUM && can_contaminate
for(var/k in 1 to atoms.len)
var/atom/thing = atoms[k]
if(!thing)
@@ -107,12 +244,45 @@
/obj/item/implant,
/obj/singularity
))
- if(!can_contaminate || blacklisted[thing.type])
+ // Insulating objects won't get contaminated
+ if(!is_contaminating || blacklisted[thing.type] || SEND_SIGNAL(thing, COMSIG_ATOM_RAD_CONTAMINATING, strength) & COMPONENT_BLOCK_CONTAMINATION)
continue
- if(prob(contamination_chance)) // Only stronk rads get to have little baby rads
- if(SEND_SIGNAL(thing, COMSIG_ATOM_RAD_CONTAMINATING, strength) & COMPONENT_BLOCK_CONTAMINATION)
- continue
- var/rad_strength = (strength-RAD_MINIMUM_CONTAMINATION) * RAD_CONTAMINATION_STR_COEFFICIENT
+ if(ismob(thing))
+ moblist += thing
+ else
+ atomlist += thing
+
+ // We don't randomly choose one from the list since that can result in zero meaningful contamination
+
+ if(atomlist.len)
+ . -= RAD_CONTAMINATION_BUDGET_SIZE
+ var/affordance = min(round(contam_strength / RAD_COMPONENT_MINIMUM), atomlist.len)
+ var/contam_strength_divided = contam_strength / affordance
+ for(var/k in 1 to affordance)
+ var/atom/poor_thing = atomlist[k]
+ poor_thing.AddComponent(/datum/component/radioactive, contam_strength_divided, source)
+
+ if(moblist.len)
+ . -= RAD_CONTAMINATION_BUDGET_SIZE
+ var/affordance = min(round(contam_strength / RAD_COMPONENT_MINIMUM), moblist.len)
+ var/contam_strength_divided = contam_strength / affordance
+ for(var/k in 1 to affordance)
+ var/mob/poor_mob = moblist[k]
+ poor_mob.AddComponent(/datum/component/radioactive, contam_strength_divided, source)
- if (rad_strength >= RAD_WAVE_MINIMUM) // Don't even bother to add the component if its waves aren't going to do anything
- thing.AddComponent(/datum/component/radioactive, rad_strength, source)
+#undef PRC_FLAG_HL
+#undef PRC_FLAG_L
+#undef PRC_FLAG_HR
+#undef PRC_FLAG_R
+#undef PRC_BEHAVIOR_N
+#undef PRC_BEHAVIOR_NWL
+#undef PRC_BEHAVIOR_NWR
+#undef PRC_BEHAVIOR_L
+#undef PRC_BEHAVIOR_LSTAR
+#undef PRC_BEHAVIOR_R
+#undef PRC_BEHAVIOR_RSTAR
+#undef PRC_BEHAVIOR_D
+#undef PRC_BEHAVIOR_HL
+#undef PRC_BEHAVIOR_HLSTAR
+#undef PRC_BEHAVIOR_HR
+#undef PRC_BEHAVIOR_HRSTAR
diff --git a/code/datums/ruins/space.dm b/code/datums/ruins/space.dm
index 60a63f827a6a3..1ad3e72c29339 100644
--- a/code/datums/ruins/space.dm
+++ b/code/datums/ruins/space.dm
@@ -11,6 +11,7 @@
name = "Biological Storage Facility"
description = "In case society crumbles, we will be able to restore our zoos to working order with the breeding stock kept in these 100% secure and unbreachable storage facilities. \
At no point has anything escaped. That's our story, and we're sticking to it."
+ cost = 4
/datum/map_template/ruin/space/asteroid1
id = "asteroid1"
@@ -18,18 +19,19 @@
name = "Asteroid 1"
description = "I-spy with my little eye, something beginning with R."
-
/datum/map_template/ruin/space/asteroid2
id = "asteroid2"
suffix = "asteroid2.dmm"
name = "Asteroid 2"
description = "Oh my god, a giant rock!"
+ cost = 1
/datum/map_template/ruin/space/asteroid3
id = "asteroid3"
suffix = "asteroid3.dmm"
name = "Asteroid 3"
description = "This asteroid floating in space has no official designation, because the scientist that discovered it deemed it 'super dull'."
+ cost = 1
/datum/map_template/ruin/space/asteroid4
id = "asteroid4"
@@ -37,12 +39,96 @@
name = "Asteroid 4"
description = "Nanotrasen Escape Pods have a 100%* success rate, and a 99%* customer satisfaction rate. \
*Please note that these statistics, are taken from pods that have successfully docked with a recovery vessel."
+ cost = 1
/datum/map_template/ruin/space/asteroid5
id = "asteroid5"
suffix = "asteroid5.dmm"
name = "Asteroid 5"
description = "Oh my god, another giant rock!"
+ cost = 2
+
+/datum/map_template/ruin/space/asteroid6
+ id = "asteroid6"
+ suffix = "asteroid6.dmm"
+ name = "Asteroid 6"
+ description = "A odd crescent body."
+
+/datum/map_template/ruin/space/asteroid7
+ id = "asteroid7"
+ suffix = "asteroid7.dmm"
+ name = "Asteroid 7"
+ description = "Space rocks!"
+
+/datum/map_template/ruin/space/asteroid8
+ id = "asteroid8"
+ suffix = "asteroid8.dmm"
+ name = "Asteroid 8"
+ description = "Space rocks."
+
+/datum/map_template/ruin/space/asteroid9
+ id = "asteroid9"
+ suffix = "asteroid9.dmm"
+ name = "Asteroid 9"
+ description = "This looks like it was a shuttle at some point... Now it's all rocks!"
+
+/datum/map_template/ruin/space/asteroid10
+ id = "asteroid10"
+ suffix = "asteroid10.dmm"
+ name = "Asteroid 10"
+ description = "Welcome to dog heaven!"
+
+/datum/map_template/ruin/space/sos1
+ id = "sos1"
+ suffix = "sos1.dmm"
+ name = "Pod 1"
+ description = "Help! I ran off course and I will never reach my destination!"
+
+/datum/map_template/ruin/space/sos2
+ id = "sos2"
+ suffix = "sos2.dmm"
+ name = "Pod 2"
+ description = "Full frontal collision!"
+
+/datum/map_template/ruin/space/sos3
+ id = "sos3"
+ suffix = "sos3.dmm"
+ name = "Pod 3"
+ description = "An ancient pod made out of odd materials..."
+
+/datum/map_template/ruin/space/sos4
+ id = "sos4"
+ suffix = "sos4.dmm"
+ name = "Pod 4"
+ description = "Yuck!"
+
+/datum/map_template/ruin/space/sos5
+ id = "sos5"
+ suffix = "sos5.dmm"
+ name = "Pod 5"
+ description = "Help never arrived!"
+ cost = 1
+
+/datum/map_template/ruin/space/sos6
+ id = "sos6"
+ suffix = "sos6.dmm"
+ name = "Pod 6"
+ description = "Cyka bylat! Vodka!!!"
+ cost = 1
+
+/datum/map_template/ruin/space/sos7
+ id = "sos7"
+ suffix = "sos7.dmm"
+ name = "Pod 7"
+ description = "Help! I was human once!"
+ cost = 1
+
+/datum/map_template/ruin/space/sos8
+ id = "sos8"
+ suffix = "sos8.dmm"
+ name = "Pod 8"
+ description = "CRASH!!!"
+ cost = 1
/datum/map_template/ruin/space/deep_storage
id = "deep-storage"
@@ -50,6 +136,7 @@
name = "Survivalist Bunker"
description = "Assume the best, prepare for the worst. Generally, you should do so by digging a three man heavily fortified bunker into a giant unused asteroid. \
Then make it self sufficient, mask any evidence of construction, hook it covertly into the telecommunications network and hope for the best."
+ cost = 8
/datum/map_template/ruin/space/bigderelict1
id = "bigderelict1"
@@ -57,6 +144,7 @@
name = "Derelict Tradepost"
description = "A once-bustling tradestation that handled imports and exports from nearby stations now lays eerily dormant. \
The last received message was a distress call from one of the on-board officers, but we had no success in making contact again."
+ cost = 8
/datum/map_template/ruin/space/derelict1
id = "derelict1"
@@ -64,6 +152,7 @@
name = "Derelict 1"
description = "Nothing to see here citizen, move along, certainly no xeno outbreaks on this piece of station debris. That purple stuff? It's uh... station nectar. \
It's a top secret research installation."
+ cost = 2
/datum/map_template/ruin/space/derelict2
id = "derelict2"
@@ -72,12 +161,14 @@
description = "Oh this is the night\n\
It's a beautiful night\n\
And we call it bella notte"
+ cost = 2
/datum/map_template/ruin/space/derelict3
id = "derelict3"
suffix = "derelict3.dmm"
name = "Derelict 3"
description = "These hulks were once part of a larger structure, where the three great \[REDACTED\] were forged."
+ cost = 2
/datum/map_template/ruin/space/derelict4
id = "derelict4"
@@ -85,12 +176,14 @@
name = "Derelict 4"
description = "CentCom ferries have never crashed, will never crash, there is no current investigation into a crashed ferry, and we will not let Internal Affairs trample over high security \
information in the name of this baseless witchhunt."
+ cost = 2
/datum/map_template/ruin/space/derelict5
id = "derelict5"
suffix = "derelict5.dmm"
name = "Derelict 5"
description = "The plan is, we put a whole bunch of crates full of treasure in this disused warehouse, launch it into space, and then ignore it. Forever."
+ cost = 2
/datum/map_template/ruin/space/derelict6
id = "derelict6"
@@ -98,24 +191,28 @@
name = "Derelict 6"
description = "The hush-hush of Nanotrasen when it comes to stations seemingly vanishing off the radar is an interesting topic, theories of nuclear destruction float about while Nanotrasen \
flat-out denies said stations ever existing."
+ cost = 2
/datum/map_template/ruin/space/empty_shell
id = "empty-shell"
suffix = "emptyshell.dmm"
name = "Empty Shell"
description = "Cozy, rural property available for a young professional couple. Only twelve parsecs from the nearest hyperspace lane!"
+ cost = 1
/datum/map_template/ruin/space/gas_the_lizards
id = "gas-the-lizards"
suffix = "gasthelizards.dmm"
name = "Disposal Facility 17"
description = "Gas efficiency at 95.6%, fluid elimination at 96.2%. Will require renewed supplies of 'carpet' before the end of the quarter."
+ cost = 2
/datum/map_template/ruin/space/intact_empty_ship
id = "intact-empty-ship"
suffix = "intactemptyship.dmm"
name = "Authorship"
description = "Just somewhere quiet, where I can focus on my work with no interruptions."
+ cost = 2
/datum/map_template/ruin/space/caravanambush
id = "space/caravanambush"
@@ -123,6 +220,7 @@
name = "Syndicate Ambush"
description = "A caravan route used by passing cargo freights has been ambushed by a salvage team manned by the syndicate. \
The caravan managed to send off a distress message before being surrounded, their video feed cutting off as the sound of gunfire and a parrot was heard."
+ cost = 8
/datum/map_template/ruin/space/originalcontent
id = "paperwizard"
@@ -130,12 +228,14 @@
name = "A Giant Ball of Paper in Space"
description = "Sightings of a giant wad of paper hurling through the depths of space have been recently reported by multiple outposts near this sector. \
A giant wad of paper, really? Damn prank callers."
+ cost = 4
/datum/map_template/ruin/space/mech_transport
id = "mech-transport"
suffix = "mechtransport.dmm"
name = "CF Corsair"
description = "Well, when is it getting here? I have bills to pay; very well-armed clients who want their shipments as soon as possible! I don't care, just find it!"
+ cost = 2
/datum/map_template/ruin/space/onehalf
id = "onehalf"
@@ -143,30 +243,35 @@
name = "DK Excavator 453"
description = "Based on the trace elements we've detected on the gutted asteroids, we suspect that a mining ship using a restricted engine is somewhere in the area. \
We'd like to request a patrol vessel to investigate."
+ cost = 4
/datum/map_template/ruin/space/spacehotel
id = "spacehotel"
suffix = "spacehotel.dmm"
name = "The Twin-Nexus Hotel"
description = "An interstellar hotel, where the weary spaceman can rest their head and relax, assured that the residental staff will not murder them in their sleep. Probably."
+ cost = 8
/datum/map_template/ruin/space/turreted_outpost
id = "turreted-outpost"
suffix = "turretedoutpost.dmm"
name = "Unnamed Turreted Outpost"
description = "We'd ask them to stop blaring that ruskiepop music, but none of us are brave enough to go near those death turrets they have."
+ cost = 2
/datum/map_template/ruin/space/oldshuttle
id = "spaceman-origins"
suffix = "shuttlerelic.dmm"
name = "Strange Ship"
description = "A ship seemingly lost, drifting along the stars. This thing looks like it belongs in ancient times."
+ cost = 2
/datum/map_template/ruin/space/way_home
id = "way-home"
suffix = "way_home.dmm"
name = "Salvation"
description = "In the darkest times, we will find our way home."
+ cost = 1
/datum/map_template/ruin/space/djstation
id = "djstation"
@@ -174,12 +279,14 @@
name = "DJ Station"
description = "Until very recently this pirate radio station was used to harangue local space stations over a variety of perceived \"ethics violations\". \
It seems like someone finally got sick of it, but the equipment still works."
+ cost = 2
/datum/map_template/ruin/space/thederelict
id = "thederelict"
suffix = "thederelict.dmm"
name = "Kosmicheskaya Stantsiya 13"
description = "The true fate of Kosmicheskaya Stantsiya 13 is an open question to this day. Most corporations deny its existence, for fear of questioning on what became of its crew."
+ cost = 8
/datum/map_template/ruin/space/abandonedteleporter
id = "abandonedteleporter"
@@ -187,6 +294,7 @@
name = "Abandoned Teleporter"
description = "In space construction the teleporter is often the first system brought online. \
This lonely half built teleporter is a sign of a proposed structure that for one reason or another just never got built."
+ cost = 2
/datum/map_template/ruin/space/crashedclownship
id = "crashedclownship"
@@ -194,6 +302,7 @@
name = "Crashed Clown Ship"
description = "For centuries the promise of a new clown homeworld has been the siren call for countless clown vessels. \
Alas the clown's lust for shinanagans means that successful voyages are almost unheard of, with most vessels falling to hilarious consequences almost immediately."
+ cost = 2
/datum/map_template/ruin/space/crashedship
id = "crashedship"
@@ -201,6 +310,7 @@
name = "Crashed Ship"
description = "Among civilian vessels the most common cause of tragedy is lack of food. \
This ship was outfited with a multitude of food generating features, then summarily ran into an asteroid shortly after takeoff."
+ cost = 8
/datum/map_template/ruin/space/listeningstation
id = "listeningstation"
@@ -208,6 +318,7 @@
name = "Syndicate Listening Station"
description = "Listening stations form the backbone of the syndicate's information gathering operations. \
Assignment to these stations is dreaded by most agents, as it entails long and lonely shifts listening to nearby stations chatter incessently about the most meaningless things."
+ cost = 5
/datum/map_template/ruin/space/oldAIsat
id = "oldAIsat"
@@ -216,12 +327,14 @@
description = "When the inspector told the employees that they were all fired, and that their jobs \"could be done by trained lizards anyway\", they reacted badly. \
This event and others is the reason why Central always sends an ERT squad with their competent inspectors. Incompetent inspectors are told they can \"do it alone\" because they're \"that pro\". \
Incompetent inspectors believe this."
+ cost = 4
/datum/map_template/ruin/space/oldteleporter
id = "oldteleporter"
suffix = "oldteleporter.dmm"
name = "Detached Teleporter"
description = "The structure of this surprisingly intact teleporter suggests that it was once part of a larger structure, but what remains of said structure, if anything, can only be guessed at."
+ cost = 1
/datum/map_template/ruin/space/vaporwave
id = "vaporwave"
@@ -229,6 +342,7 @@
name = "Aesthetic Outpost"
description = "Pause and remember-- You are unique.You are special. Every mistake, trial, and hardship has helped to sculpt your real beauty. \
Stop hating yourself and start appreciating and loving yourself!"
+ cost = 2
/datum/map_template/ruin/space/bus
id = "bus"
@@ -236,6 +350,7 @@
name = "Waylaid Buses"
description = "There seems to be a pair of buses that pulled over for repairs. What were they doing..? Their shipment sure seems to be filled with a strange mix. \
Anyway, it looks like some people tried to fix it up for a long time but didn't really get anywhere..."
+ cost = 2
/datum/map_template/ruin/space/oldstation
id = "oldstation"
@@ -243,6 +358,7 @@
name = "Ancient Space Station"
description = "The crew of a space station awaken one hundred years after a crisis. Awaking to a derelict space station on the verge of collapse, and a hostile force of invading \
hivebots. Can the surviving crew overcome the odds and survive and rebuild, or will the cold embrace of the stars become their new home?"
+ cost = 8
/datum/map_template/ruin/space/miracle
id = "miracle"
@@ -255,6 +371,7 @@
suffix = "gondolaasteroid.dmm"
name = "Gondoland"
description = "Just an ordinary rock- wait, what's that thing?"
+ cost = 2
/datum/map_template/ruin/space/whiteshipruin_box
id = "whiteshipruin_box"
@@ -262,12 +379,14 @@
name = "NT Medical Ship"
description = "An ancient ship, said to be among the first discovered derelicts near Space Station 13 that was still in working order. \
Aged and deprecated by time, this relic of a vessel is now broken beyond repair."
+ cost = 2
/datum/map_template/ruin/space/whiteshipdock
id = "whiteshipdock"
suffix = "whiteshipdock.dmm"
name = "Whiteship Dock"
description = "An abandoned but functional vessel parked in deep space, ripe for the taking."
+ cost = 2
/datum/map_template/ruin/space/cat_experiments
id = "meow"
@@ -275,40 +394,82 @@
name = "Feline-Human Combination Den"
description = "With heated debates over the legality of the catperson and their status in the workforce, there's always a place for the blackmarket to slip in for some cash. Whether the results \
are morally sound or not is another issue entirely."
+ cost = 4
/datum/map_template/ruin/space/cloning_facility
id = "cloning_facility"
suffix = "cloning_facility.dmm"
name = "Ancient Cloning Lab"
description = "An experimental cloning lab snapped off from an ancient ship. The cloner model inside lacks many modern functionalities and security measures."
+ cost = 2
/datum/map_template/ruin/space/hilbertresearchfacility
id = "hilbert_facility"
suffix = "hilbertshoteltestingsite.dmm"
name = "Hilbert Research Facility"
description = "A research facility of great bluespace discoveries. Long since abandoned, willingly or not..."
+ cost = 2
/datum/map_template/ruin/space/clownplanet
id = "clownplanet"
suffix = "clownplanet.dmm"
name = "Clown Planet"
description = "Thought lost in 2552, this minor planet has recently been rediscovered."
+ cost = 2
/datum/map_template/ruin/space/macspace
id = "fast_food"
suffix = "Fast_Food.dmm"
name = "Mac Space Restaurant"
description = "A fast food reataurant in space."
+ cost = 5
/datum/map_template/ruin/space/scav_mining
id = "mining_asteroid"
suffix = "scav_mining.dmm"
name = "Mining asteroid"
description = "An abandoned mining operation on an asteroid that now has new ocupants that is not happy to se you"
+ cost = 2
/datum/map_template/ruin/space/power_puzzle
id = "power_puzzle"
suffix = "power_puzzle.dmm"
name = "Power Puzzle"
description = "an abandoned secure storage location. there is no power left in the batteries and the former ocupants locked it pretty tight before leaving.\
- You will have to power areas to raise the bolts on the doors. look out for secrets."
\ No newline at end of file
+ You will have to power areas to raise the bolts on the doors. look out for secrets."
+ cost = 4
+
+/datum/map_template/ruin/space/spacearcade
+ id = "spacearcade"
+ suffix = "spacearcade.dmm"
+ name = "Space Arcade"
+ description = "Come here to have fun! We even have the best arcade game in the Universe, Disco Disco Rave!"
+ cost = 2
+
+/datum/map_template/ruin/space/spacedock
+ id = "spacearcade"
+ suffix = "spacedock13.dmm"
+ name = "Shuttle Construction"
+ description = "A shuttle building station that seems to have been forgotten, or floated off into space."
+ cost = 2
+
+/datum/map_template/ruin/space/spacehive
+ id = "spacehive"
+ suffix = "spacehive.dmm"
+ name = "Space Hive"
+ description = "A hydroponics experiment gone horribly, horribly wrong!"
+ cost = 1
+
+/datum/map_template/ruin/space/swarmerstation
+ id = "swarmerstation13"
+ suffix = "swarmerstation13.dmm"
+ name = "Swarmer Station"
+ description = "Shat is left of a syndicate radio station, after swarmers infiltrated it."
+ cost = 2
+
+/datum/map_template/ruin/space/refference
+ id = "refference"
+ suffix = "refference.dmm"
+ name = "Lost Cyborg"
+ description = "500 years too soon!"
+ cost = 2
diff --git a/code/datums/saymode.dm b/code/datums/saymode.dm
index 11e2cbdc10292..fd46c5bcdeafc 100644
--- a/code/datums/saymode.dm
+++ b/code/datums/saymode.dm
@@ -33,8 +33,11 @@
if(LINGHIVE_LINK)
to_chat(M, msg)
if(LINGHIVE_OUTSIDER)
- if(prob(40))
- to_chat(M, "We can faintly sense an outsider trying to communicate through the hivemind...")
+ var/mob/living/L = M
+ if (!HAS_TRAIT(L, CHANGELING_HIVEMIND_MUTE) && prob(70))
+ to_chat(M, msg)
+ else
+ to_chat(M, "We hear a faint chittering from within our mind...")
if(LINGHIVE_LING)
if (HAS_TRAIT(user, CHANGELING_HIVEMIND_MUTE))
to_chat(user, "The poison in the air hinders our ability to interact with the hivemind.")
@@ -57,8 +60,11 @@
if (!HAS_TRAIT(L, CHANGELING_HIVEMIND_MUTE))
to_chat(M, msg)
if(LINGHIVE_OUTSIDER)
- if(prob(40))
- to_chat(M, "We can faintly sense another of our kind trying to communicate through the hivemind...")
+ var/mob/living/L = M
+ if (!HAS_TRAIT(L, CHANGELING_HIVEMIND_MUTE) && prob(70))
+ to_chat(M, msg)
+ else
+ to_chat(M, "We hear a faint chittering from within our mind...")
if(LINGHIVE_OUTSIDER)
to_chat(user, "Our senses have not evolved enough to be able to communicate this way...")
return FALSE
@@ -88,7 +94,7 @@
return FALSE
-/datum/saymode/binary //everything that uses .b (silicons, drones, blobbernauts/spores, swarmers)
+/datum/saymode/binary //everything that uses .b (silicons, drones, swarmers)
key = MODE_KEY_BINARY
mode = MODE_BINARY
@@ -97,10 +103,6 @@
var/mob/living/simple_animal/hostile/swarmer/S = user
S.swarmer_chat(message)
return FALSE
- if(isblobmonster(user))
- var/mob/living/simple_animal/hostile/blob/B = user
- B.blob_chat(message)
- return FALSE
if(isdrone(user))
var/mob/living/simple_animal/drone/D = user
D.drone_chat(message)
diff --git a/code/datums/shuttles.dm b/code/datums/shuttles.dm
index e6a9fcd3c0a6e..8d96dbb44965a 100644
--- a/code/datums/shuttles.dm
+++ b/code/datums/shuttles.dm
@@ -11,6 +11,7 @@
var/credit_cost = INFINITY
var/can_be_bought = TRUE
+ var/illegal_shuttle = FALSE //makes you able to buy the shuttle at a hacked/emagged comms console even if can_be_bought is FALSE
var/list/movement_force // If set, overrides default movement_force on shuttle
@@ -21,9 +22,14 @@
/datum/map_template/shuttle/proc/prerequisites_met()
return TRUE
-/datum/map_template/shuttle/New()
+/datum/map_template/shuttle/New(path = null, rename = null, cache = FALSE, admin_load = null)
+ if(admin_load)//This data must be populated for the system to not shit itself apparently
+ suffix = admin_load
+ port_id = "custom"
+ can_be_bought = FALSE
shuttle_id = "[port_id]_[suffix]"
- mappath = "[prefix][shuttle_id].dmm"
+ if(!admin_load)
+ mappath = "[prefix][shuttle_id].dmm"
. = ..()
/datum/map_template/shuttle/preload_size(path, cache)
@@ -244,12 +250,25 @@
description = "The crew must pass through an otherworldy arena to board this shuttle. Expect massive casualties. The source of the Bloody Signal must be tracked down and eliminated to unlock this shuttle."
admin_notes = "RIP AND TEAR."
credit_cost = 10000
+ /// Whether the arena z-level has been created
+ var/arena_loaded = FALSE
/datum/map_template/shuttle/emergency/arena/prerequisites_met()
if(SHUTTLE_UNLOCK_BUBBLEGUM in SSshuttle.shuttle_purchase_requirements_met)
return TRUE
return FALSE
+/datum/map_template/shuttle/emergency/arena/post_load(obj/docking_port/mobile/M)
+ . = ..()
+ if(!arena_loaded)
+ arena_loaded = TRUE
+ var/datum/map_template/arena/arena_template = new()
+ arena_template.load_new_z()
+
+/datum/map_template/arena
+ name = "The Arena"
+ mappath = "_maps/templates/the_arena.dmm"
+
/datum/map_template/shuttle/emergency/birdboat
suffix = "birdboat"
name = "Birdboat Station Emergency Shuttle"
@@ -268,13 +287,6 @@
credit_cost = 2000
description = "The gold standard in emergency exfiltration, this tried and true design is equipped with everything the crew needs for a safe flight home."
-/datum/map_template/shuttle/emergency/donut
- suffix = "donut"
- name = "Donutstation Emergency Shuttle"
- description = "The perfect spearhead for any crude joke involving the station's shape, this shuttle supports a separate containment cell for prisoners and a compact medical wing."
- admin_notes = "Has airlocks on both sides of the shuttle and will probably intersect near the front on some stations that build past departures."
- credit_cost = 2500
-
/datum/map_template/shuttle/emergency/clown
suffix = "clown"
name = "Snappop(tm)!"
@@ -307,6 +319,12 @@
credit_cost = 5000
description = "A fully functional shuttle including a complete infirmary, storage facilties and regular amenities."
+/datum/map_template/shuttle/emergency/corg
+ suffix = "corg"
+ name = "Corg Station Emergency Shuttle"
+ credit_cost = 4000
+ description = "A smaller shuttle with area for cargo, medical and security personnel."
+
/datum/map_template/shuttle/emergency/mini
suffix = "mini"
name = "Ministation emergency shuttle"
@@ -451,14 +469,14 @@
suffix = "kilo"
name = "supply shuttle (Kilo)"
+/datum/map_template/shuttle/cargo/corg
+ suffix = "corg"
+ name = "supply shuttle (Corg)"
+
/datum/map_template/shuttle/cargo/birdboat
suffix = "birdboat"
name = "supply shuttle (Birdboat)"
-/datum/map_template/shuttle/cargo/donut
- suffix = "donut"
- name = "supply shuttle (Donut)"
-
/datum/map_template/shuttle/emergency/delta
suffix = "delta"
name = "Delta Station Emergency Shuttle"
@@ -504,9 +522,9 @@
suffix = "box"
name = "labour shuttle (Box)"
-/datum/map_template/shuttle/arrival/donut
- suffix = "donut"
- name = "arrival shuttle (Donut)"
+/datum/map_template/shuttle/arrival/corg
+ suffix = "corg"
+ name = "arrival shuttle (Corg)"
/datum/map_template/shuttle/infiltrator/basic
suffix = "basic"
@@ -538,6 +556,20 @@
name = "science outpost shuttle"
can_be_bought = FALSE
+/datum/map_template/shuttle/exploration
+ port_id = "exploration"
+ suffix = "shuttle"
+ name = "exploration shuttle"
+ can_be_bought = FALSE
+
+/datum/map_template/shuttle/exploration/corg
+ suffix = "corg"
+ name = "corg exploration shuttle"
+
+/datum/map_template/shuttle/exploration/delta
+ suffix = "delta"
+ name = "delta exploration shuttle"
+
/datum/map_template/shuttle/labour/delta
suffix = "delta"
name = "labour shuttle (Delta)"
@@ -546,6 +578,10 @@
suffix = "kilo"
name = "labour shuttle (Kilo)"
+/datum/map_template/shuttle/labour/corg
+ suffix = "corg"
+ name = "labour shuttle (Corg)"
+
/datum/map_template/shuttle/arrival/delta
suffix = "delta"
name = "arrival shuttle (Delta)"
@@ -629,3 +665,11 @@
suffix = "primary"
name = "primary turbolift (multi-z debug)"
can_be_bought = FALSE
+
+/datum/map_template/shuttle/tram
+ port_id = "tram"
+ can_be_bought = FALSE
+
+/datum/map_template/shuttle/tram/corg
+ suffix = "corg"
+ name = "corgstation transport shuttle"
diff --git a/code/datums/soullink.dm b/code/datums/soullink.dm
index de73a8e74c6d7..618944739a759 100644
--- a/code/datums/soullink.dm
+++ b/code/datums/soullink.dm
@@ -5,6 +5,8 @@
var/list/sharedSoullinks //soullinks we are a/the sharer of
/mob/living/Destroy()
+ if(playable)
+ remove_from_spawner_menu()
for(var/s in ownedSoullinks)
var/datum/soullink/S = s
S.ownerDies(FALSE)
diff --git a/code/datums/soundtrack.dm b/code/datums/soundtrack.dm
new file mode 100644
index 0000000000000..97f4505f7186f
--- /dev/null
+++ b/code/datums/soundtrack.dm
@@ -0,0 +1,29 @@
+GLOBAL_LIST_EMPTY(soundtrack_this_round) // A running list of soundtrack songs that have played this round, for credits entries
+
+/datum/soundtrack_song
+ var/title
+ var/artist
+ var/url
+ var/album
+ var/file
+
+/datum/soundtrack_song/bee
+ album = "BeeStation OST"
+
+/datum/soundtrack_song/bee/future_perception
+ title = "Future Perception"
+ artist = "Merct"
+ url = "https://www.youtube.com/watch?v=N9559mSGjKg"
+ file = 'sound/soundtrack/future_perception.ogg'
+
+/datum/soundtrack_song/bee/countdown
+ title = "Countdown"
+ artist = "qwertyquerty"
+ url = "https://www.youtube.com/watch?v=G2gVAPKlgqA"
+ file = 'sound/soundtrack/countdown.ogg'
+
+/datum/soundtrack_song/bee/mind_crawler
+ title = "Mind Crawler"
+ artist = "Merct"
+ url = "https://www.youtube.com/watch?v=EiLBxoBNsNo"
+ file = 'sound/soundtrack/mind_crawler.ogg'
diff --git a/code/datums/spawners_menu.dm b/code/datums/spawners_menu.dm
index 95a7d8e63391d..ec6a4484d20e0 100644
--- a/code/datums/spawners_menu.dm
+++ b/code/datums/spawners_menu.dm
@@ -1,11 +1,3 @@
-/datum/spawners_menu
- var/mob/dead/observer/owner
-
-/datum/spawners_menu/New(mob/dead/observer/new_owner)
- if(!istype(new_owner))
- qdel(src)
- owner = new_owner
-
/datum/spawners_menu/ui_state(mob/user)
return GLOB.observer_state
@@ -34,13 +26,21 @@
this["flavor_text"] = MS.flavour_text
this["important_info"] = MS.important_info
else
- var/obj/O = spawner_obj
- this["desc"] = O.desc
+ var/atom/movable/O = spawner_obj
+ if(isslime(O))
+ this["short_desc"] = O.get_spawner_desc()
+ this["flavor_text"] = O.get_spawner_flavour_text()
+ else
+ this["desc"] = O.desc
+
this["amount_left"] = LAZYLEN(GLOB.mob_spawners[spawner])
data["spawners"] += list(this)
return data
+/datum/spawners_menu/ui_state(mob/user)
+ return GLOB.observer_state
+
/datum/spawners_menu/ui_act(action, params)
if(..())
return
@@ -49,17 +49,17 @@
if(!group_name || !(group_name in GLOB.mob_spawners))
return
var/list/spawnerlist = GLOB.mob_spawners[group_name]
- if(!spawnerlist.len)
+ if(!LAZYLEN(spawnerlist))
return
- var/obj/effect/mob_spawn/MS = pick(spawnerlist)
+ var/atom/movable/MS = pick(spawnerlist)
if(!istype(MS) || !(MS in GLOB.poi_list))
return
switch(action)
if("jump")
if(MS)
- owner.forceMove(get_turf(MS))
+ usr.forceMove(get_turf(MS))
. = TRUE
if("spawn")
if(MS)
- MS.attack_ghost(owner)
+ MS.attack_ghost(usr)
. = TRUE
diff --git a/code/datums/station_traits/_station_trait.dm b/code/datums/station_traits/_station_trait.dm
new file mode 100644
index 0000000000000..93fc351f69189
--- /dev/null
+++ b/code/datums/station_traits/_station_trait.dm
@@ -0,0 +1,40 @@
+///Base class of station traits. These are used to influence rounds in one way or the other by influencing the levers of the station.
+/datum/station_trait
+ ///Name of the trait
+ var/name = "unnamed station trait"
+ ///The type of this trait. Used to classify how this trait influences the station
+ var/trait_type = STATION_TRAIT_NEUTRAL
+ ///Whether or not this trait uses process()
+ var/trait_processes = FALSE
+ ///Chance relative to other traits of its type to be picked
+ var/weight = 10
+ ///Does this trait show in the centcom report?
+ var/show_in_report = FALSE
+ ///What message to show in the centcom report?
+ var/report_message
+ ///What code-trait does this station trait give? gives none if null
+ var/trait_to_give
+ ///What traits are incompatible with this one?
+ var/blacklist
+ ///Extra flags for station traits such as it being abstract
+ var/trait_flags
+ ///Should we announce anything roundstart? If so, those are our options
+ var/list/possible_announcements
+
+
+/datum/station_trait/New()
+ . = ..()
+ SSticker.OnRoundstart(CALLBACK(src, .proc/on_round_start))
+ if(trait_processes)
+ START_PROCESSING(SSstation, src)
+ if(trait_to_give)
+ ADD_TRAIT(SSstation, trait_to_give, STATION_TRAIT)
+
+///Proc ran when round starts. Use this for roundstart effects.
+/datum/station_trait/proc/on_round_start()
+ if(length(possible_announcements))
+ priority_announce(pick(possible_announcements), null, null, has_important_message = TRUE)
+
+///type of info the centcom report has on this trait, if any.
+/datum/station_trait/proc/get_report()
+ return "[name] - [report_message]"
diff --git a/code/datums/station_traits/negative_traits.dm b/code/datums/station_traits/negative_traits.dm
new file mode 100644
index 0000000000000..c18ae1085cf2a
--- /dev/null
+++ b/code/datums/station_traits/negative_traits.dm
@@ -0,0 +1,171 @@
+/datum/station_trait/carp_infestation
+ name = "Carp infestation"
+ trait_type = STATION_TRAIT_NEGATIVE
+ weight = 5
+ show_in_report = TRUE
+ report_message = "Dangerous fauna is present in the area of this station."
+ trait_to_give = STATION_TRAIT_CARP_INFESTATION
+
+/datum/station_trait/distant_supply_lines
+ name = "Distant supply lines"
+ trait_type = STATION_TRAIT_NEGATIVE
+ weight = 3
+ show_in_report = TRUE
+ report_message = "Due to the distance to our normal supply lines, cargo orders are more expensive."
+ blacklist = list(/datum/station_trait/strong_supply_lines)
+ trait_to_give = STATION_TRAIT_DISTANT_SUPPLY_LINES
+
+/datum/station_trait/late_arrivals
+ name = "Late Arrivals"
+ trait_type = STATION_TRAIT_NEGATIVE
+ weight = 2
+ show_in_report = TRUE
+ report_message = "Sorry for that, we didn't expect to fly into that vomiting goose while bringing you to your new station."
+ trait_to_give = STATION_TRAIT_LATE_ARRIVALS
+ blacklist = list(/datum/station_trait/random_spawns, /datum/station_trait/hangover)
+ possible_announcements = list("You are getting late, again. Get your stuff together or you are all fired.",
+ "Our calculations were off by a bit. Shuttle will be there in a few seconds.")
+
+/datum/station_trait/random_spawns
+ name = "Drive-by landing"
+ trait_type = STATION_TRAIT_NEGATIVE
+ weight = 2
+ show_in_report = TRUE
+ report_message = "Sorry for that, we missed your station by a few miles, so we just launched you towards your station in pods. Hope you don't mind!"
+ trait_to_give = STATION_TRAIT_RANDOM_ARRIVALS
+ blacklist = list(/datum/station_trait/late_arrivals, /datum/station_trait/hangover)
+ possible_announcements = list("We overshot your station by a few miles. Prepare to be pod launched onto it.",
+ "We've missed your station, sorry for that. You will be launched onto it shortly.")
+
+/datum/station_trait/hangover
+ name = "Hangover"
+ trait_type = STATION_TRAIT_NEGATIVE
+ weight = 2
+ show_in_report = TRUE
+ report_message = "Ohh.... Man.... That mandatory office party from last shift... God that was awesome... I woke up in some random toilet 3 sectors away..."
+ trait_to_give = STATION_TRAIT_HANGOVER
+ blacklist = list(/datum/station_trait/late_arrivals, /datum/station_trait/random_spawns)
+ possible_announcements = list("That was one hell of a night. Now, get back to work.",
+ "Party's over. Get back to work.")
+
+/datum/station_trait/hangover/New()
+ . = ..()
+ RegisterSignal(SSdcs, COMSIG_GLOB_JOB_AFTER_SPAWN, .proc/on_job_after_spawn)
+ RegisterSignal(SSmapping, COMSIG_SUBSYSTEM_POST_INITIALIZE, .proc/create_spawners)
+
+/datum/station_trait/hangover/proc/create_spawners()
+ var/list/turf/turfs = get_safe_random_station_turfs(typesof(/area/hallway), rand(200, 300))
+ for(var/turf/T as() in turfs)
+ new /obj/effect/spawner/hangover_spawn(T)
+
+
+/datum/station_trait/hangover/proc/on_job_after_spawn(datum/source, datum/job/job, mob/living/living_mob, mob/spawned_mob, joined_late)
+ SIGNAL_HANDLER
+
+ if(joined_late)
+ return
+ if(!iscarbon(living_mob))
+ return
+
+ var/mob/living/carbon/spawned_carbon = living_mob
+ spawned_carbon.set_resting(TRUE, silent = TRUE)
+ if(prob(50))
+ spawned_carbon.adjust_drugginess(rand(15, 20))
+ else
+ spawned_carbon.drunkenness += rand(15, 25)
+ spawned_carbon.adjust_disgust(rand(5, 55)) //How hungover are you?
+
+ if(prob(35) && !spawned_carbon.head)
+ var/obj/item/hat = pick(list(/obj/item/clothing/head/sombrero, /obj/item/clothing/head/fedora, /obj/item/clothing/mask/balaclava, /obj/item/clothing/head/ushanka, /obj/item/clothing/head/cardborg, /obj/item/clothing/head/pirate, /obj/item/clothing/head/cone))
+ hat = new hat(spawned_mob)
+ spawned_mob.equip_to_slot(hat, ITEM_SLOT_HEAD)
+
+/obj/effect/spawner/hangover_spawn
+ name = "hangover spawner"
+
+/obj/effect/spawner/hangover_spawn/Initialize()
+ . = ..()
+ if(prob(60))
+ new /obj/effect/decal/cleanable/vomit(get_turf(src))
+ if(prob(70))
+ var/bottle_count = pick(10;1, 5;2, 2;3)
+ for(var/index in 1 to bottle_count)
+ new /obj/item/reagent_containers/food/drinks/beer/almost_empty(get_turf(src))
+
+/datum/station_trait/blackout
+ name = "Blackout"
+ trait_type = STATION_TRAIT_NEGATIVE
+ weight = 5
+ show_in_report = TRUE
+ report_message = "Station lights seem to be damaged, be safe when starting your shift today."
+
+/datum/station_trait/blackout/on_round_start()
+ . = ..()
+ for(var/a in GLOB.apcs_list)
+ var/obj/machinery/power/apc/current_apc = a
+ if(prob(60))
+ current_apc.overload_lighting()
+
+/datum/station_trait/empty_maint
+ name = "Cleaned out maintenance"
+ trait_type = STATION_TRAIT_NEGATIVE
+ weight = 5
+ show_in_report = TRUE
+ report_message = "Our workers cleaned out most of the junk in the maintenace areas."
+ blacklist = list(/datum/station_trait/filled_maint)
+ trait_to_give = STATION_TRAIT_EMPTY_MAINT
+
+
+/datum/station_trait/overflow_job_bureacracy
+ name = "Overflow bureacracy mistake"
+ trait_type = STATION_TRAIT_NEGATIVE
+ weight = 5
+ show_in_report = TRUE
+ var/list/jobs_to_use = list("Clown", "Bartender", "Cook", "Botanist", "Cargo Technician", "Mime", "Janitor")
+ var/chosen_job
+
+/datum/station_trait/overflow_job_bureacracy/New()
+ . = ..()
+ chosen_job = pick(jobs_to_use)
+ RegisterSignal(SSjob, COMSIG_SUBSYSTEM_POST_INITIALIZE, .proc/set_overflow_job_override)
+
+/datum/station_trait/overflow_job_bureacracy/get_report()
+ return "[name] - It seems for some reason we put out the wrong job-listing for the overflow role this shift...I hope you like [chosen_job]s."
+
+/datum/station_trait/overflow_job_bureacracy/proc/set_overflow_job_override(datum/source, new_overflow_role)
+ SIGNAL_HANDLER
+
+ SSjob.set_overflow_role(chosen_job)
+
+/datum/station_trait/slow_shuttle
+ name = "Slow Shuttle"
+ trait_type = STATION_TRAIT_NEGATIVE
+ weight = 5
+ show_in_report = TRUE
+ report_message = "Due to distance to our supply station, the cargo shuttle will have a slower flight time to your cargo department."
+ blacklist = list(/datum/station_trait/quick_shuttle)
+
+/datum/station_trait/slow_shuttle/on_round_start()
+ . = ..()
+ SSshuttle.supply.callTime *= 1.5
+
+/datum/station_trait/bot_languages
+ name = "Bot Language Matrix Malfunction"
+ trait_type = STATION_TRAIT_NEGATIVE
+ weight = 3
+ show_in_report = TRUE
+ report_message = "Your station's friendly bots have had their language matrix fried due to an event, resulting in some strange and unfamiliar speech patterns."
+
+/datum/station_trait/bot_languages/New()
+ . = ..()
+ /// What "caused" our robots to go haywire (fluff)
+ var/event_source = pick(list("an ion storm", "a syndicate hacking attempt", "a malfunction", "issues with your onboard AI", "an intern's mistakes", "budget cuts"))
+ report_message = "Your station's friendly bots have had their language matrix fried due to [event_source], resulting in some strange and unfamiliar speech patterns."
+
+/datum/station_trait/bot_languages/on_round_start()
+ . = ..()
+ //All bots that exist round start have their set language randomized.
+ for(var/mob/living/simple_animal/bot/found_bot in GLOB.alive_mob_list)
+ /// The bot's language holder - so we can randomize and change their language
+ var/datum/language_holder/bot_languages = found_bot.get_language_holder()
+ bot_languages.selected_language = bot_languages.get_random_spoken_language()
diff --git a/code/datums/station_traits/neutral_traits.dm b/code/datums/station_traits/neutral_traits.dm
new file mode 100644
index 0000000000000..c7cc020910c71
--- /dev/null
+++ b/code/datums/station_traits/neutral_traits.dm
@@ -0,0 +1,80 @@
+/datum/station_trait/bananium_shipment
+ name = "Bananium Shipment"
+ trait_type = STATION_TRAIT_NEUTRAL
+ weight = 5
+ report_message = "Rumors has it that the clown planet has been sending support packages to clowns in this system"
+ trait_to_give = STATION_TRAIT_BANANIUM_SHIPMENTS
+
+/datum/station_trait/ian_adventure
+ name = "Ian's Adventure"
+ trait_type = STATION_TRAIT_NEUTRAL
+ weight = 5
+ show_in_report = FALSE
+ report_message = "Ian has gone exploring somewhere in the station."
+
+/datum/station_trait/ian_adventure/on_round_start()
+ for(var/mob/living/simple_animal/pet/dog/corgi/dog in GLOB.mob_list)
+ if(!(istype(dog, /mob/living/simple_animal/pet/dog/corgi/Ian) || istype(dog, /mob/living/simple_animal/pet/dog/corgi/puppy/Ian)))
+ continue
+
+ // The extended safety checks at time of writing are about chasms and lava
+ // if there are any chasms and lava on stations in the future, woah
+ var/turf/current_turf = get_turf(dog)
+ var/turf/adventure_turf = find_safe_turf(extended_safety_checks = TRUE, dense_atoms = FALSE)
+
+ // Poof!
+ do_smoke(location=current_turf)
+ dog.forceMove(adventure_turf)
+ do_smoke(location=adventure_turf)
+
+
+/datum/station_trait/glitched_pdas
+ name = "PDA glitch"
+ trait_type = STATION_TRAIT_NEUTRAL
+ weight = 8
+ show_in_report = TRUE
+ report_message = "Something seems to be wrong with the PDAs issued to you all this shift. Nothing too bad though."
+ trait_to_give = STATION_TRAIT_PDA_GLITCHED
+
+/datum/station_trait/announcement_intern
+ name = "Announcement Intern"
+ trait_type = STATION_TRAIT_NEUTRAL
+ weight = 3
+ show_in_report = TRUE
+ report_message = "Please be nice to him."
+ blacklist = list(/datum/station_trait/announcement_medbot,
+ /datum/station_trait/announcement_baystation
+ )
+
+/datum/station_trait/announcement_intern/New()
+ . = ..()
+ SSstation.announcer = /datum/centcom_announcer/intern
+
+/datum/station_trait/announcement_medbot
+ name = "Announcement \"System\""
+ trait_type = STATION_TRAIT_NEUTRAL
+ weight = 5
+ show_in_report = TRUE
+ report_message = "Our announcement system is under scheduled maintanance at the moment. Thankfully, we have a backup."
+ blacklist = list(
+ /datum/station_trait/announcement_intern,
+ /datum/station_trait/announcement_baystation
+ )
+
+/datum/station_trait/announcement_medbot/New()
+ . = ..()
+ SSstation.announcer = /datum/centcom_announcer/medbot
+
+/datum/station_trait/announcement_baystation
+ name = "Announcer: Archival Tape"
+ trait_type = STATION_TRAIT_NEUTRAL
+ weight = 5
+ show_in_report = TRUE
+ report_message = "We lost the primary datatape that holds the announcement system's voice responses. We did however find an older backup."
+ blacklist = list(/datum/station_trait/announcement_intern,
+ /datum/station_trait/announcement_medbot
+ )
+
+/datum/station_trait/announcement_baystation/New()
+ . = ..()
+ SSstation.announcer = /datum/centcom_announcer/baystation
diff --git a/code/datums/station_traits/positive_traits.dm b/code/datums/station_traits/positive_traits.dm
new file mode 100644
index 0000000000000..967701c333250
--- /dev/null
+++ b/code/datums/station_traits/positive_traits.dm
@@ -0,0 +1,123 @@
+#define PARTY_COOLDOWN_LENGTH_MIN 6 MINUTES
+#define PARTY_COOLDOWN_LENGTH_MAX 12 MINUTES
+
+
+/datum/station_trait/lucky_winner
+ name = "Lucky winner"
+ trait_type = STATION_TRAIT_POSITIVE
+ weight = 1
+ show_in_report = TRUE
+ report_message = "Your station has won the grand prize of the annual station charity event. Free snacks will be delivered to the bar every now and then."
+ trait_processes = TRUE
+ COOLDOWN_DECLARE(party_cooldown)
+
+/datum/station_trait/lucky_winner/on_round_start()
+ . = ..()
+ COOLDOWN_START(src, party_cooldown, rand(PARTY_COOLDOWN_LENGTH_MIN, PARTY_COOLDOWN_LENGTH_MAX))
+
+/datum/station_trait/lucky_winner/process(delta_time)
+ if(!COOLDOWN_FINISHED(src, party_cooldown))
+ return
+
+ COOLDOWN_START(src, party_cooldown, rand(PARTY_COOLDOWN_LENGTH_MIN, PARTY_COOLDOWN_LENGTH_MAX))
+
+ var/area/area_to_spawn_in = pick(GLOB.bar_areas)
+ var/turf/T = get_safe_random_station_turfs(area_to_spawn_in)
+
+ var/obj/structure/closet/supplypod/centcompod/toLaunch = new()
+ var/obj/item/pizzabox/pizza_to_spawn = pick(list(/obj/item/pizzabox/margherita, /obj/item/pizzabox/mushroom, /obj/item/pizzabox/meat, /obj/item/pizzabox/vegetable)) //no pineapple pizza you monster
+ new pizza_to_spawn(toLaunch)
+ for(var/i in 1 to 6)
+ new /obj/item/reagent_containers/food/drinks/beer(toLaunch)
+ new /obj/effect/pod_landingzone(T, toLaunch)
+
+/datum/station_trait/galactic_grant
+ name = "Galactic grant"
+ trait_type = STATION_TRAIT_POSITIVE
+ weight = 5
+ show_in_report = TRUE
+ report_message = "Your station has been selected for a special grant. Some extra funds has been made available to your cargo department."
+
+/datum/station_trait/galactic_grant/on_round_start()
+ var/datum/bank_account/cargo_bank = SSeconomy.get_dep_account(ACCOUNT_CAR)
+ cargo_bank.adjust_money(rand(2000, 5000))
+
+/datum/station_trait/premium_internals_box
+ name = "Premium internals boxes"
+ trait_type = STATION_TRAIT_POSITIVE
+ weight = 10
+ show_in_report = TRUE
+ report_message = "The internals boxes for your crew have been filled with bonus equipment."
+ trait_to_give = STATION_TRAIT_PREMIUM_INTERNALS
+
+/datum/station_trait/bountiful_bounties
+ name = "Bountiful bounties"
+ trait_type = STATION_TRAIT_POSITIVE
+ weight = 5
+ show_in_report = TRUE
+ report_message = "It seems collectors in this system are extra keen to on bounties, and will pay more to see their completion."
+
+/datum/station_trait/bountiful_bounties/on_round_start()
+ SSeconomy.bounty_modifier *= 1.2
+
+/datum/station_trait/strong_supply_lines
+ name = "Strong supply lines"
+ trait_type = STATION_TRAIT_POSITIVE
+ weight = 5
+ show_in_report = TRUE
+ report_message = "Prices are low in this system, BUY BUY BUY!"
+ blacklist = list(/datum/station_trait/distant_supply_lines)
+ trait_to_give = STATION_TRAIT_STRONG_SUPPLY_LINES
+
+/datum/station_trait/scarves
+ name = "Scarves"
+ trait_type = STATION_TRAIT_POSITIVE
+ weight = 5
+ show_in_report = TRUE
+ var/list/scarves
+
+/datum/station_trait/scarves/New()
+ . = ..()
+ report_message = pick(
+ "Nanotrasen is experimenting with seeing if neck warmth improves employee morale.",
+ "After Space Fashion Week, scarves are the hot new accessory.",
+ "Everyone was simultaneously a little bit cold when they packed to go to the station.",
+ "The station is definitely not under attack by neck grappling aliens masquerading as wool. Definitely not.",
+ "You all get free scarves. Don't ask why.",
+ "A shipment of scarves was delivered to the station.",
+ )
+ scarves = typesof(/obj/item/clothing/neck/scarf) + list(
+ /obj/item/clothing/neck/stripedredscarf,
+ /obj/item/clothing/neck/stripedgreenscarf,
+ /obj/item/clothing/neck/stripedbluescarf,
+ )
+
+ RegisterSignal(SSdcs, COMSIG_GLOB_JOB_AFTER_SPAWN, .proc/on_job_after_spawn)
+
+/datum/station_trait/scarves/proc/on_job_after_spawn(datum/source, datum/job/job, mob/living/living_mob, mob/M, joined_late)
+ SIGNAL_HANDLER
+
+ var/scarf_type = pick(scarves)
+
+ living_mob.equip_to_slot_or_del(new scarf_type(living_mob), ITEM_SLOT_NECK)
+
+/datum/station_trait/filled_maint
+ name = "Filled up maintenance"
+ trait_type = STATION_TRAIT_POSITIVE
+ weight = 5
+ show_in_report = TRUE
+ report_message = "Our workers accidentally forgot more of their personal belongings in the maintenance areas."
+ blacklist = list(/datum/station_trait/empty_maint)
+ trait_to_give = STATION_TRAIT_FILLED_MAINT
+
+/datum/station_trait/quick_shuttle
+ name = "Quick Shuttle"
+ trait_type = STATION_TRAIT_POSITIVE
+ weight = 5
+ show_in_report = TRUE
+ report_message = "Due to proximity to our supply station, the cargo shuttle will have a quicker flight time to your cargo department."
+ blacklist = list(/datum/station_trait/slow_shuttle)
+
+/datum/station_trait/quick_shuttle/on_round_start()
+ . = ..()
+ SSshuttle.supply.callTime *= 0.5
diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm
index a264dd850a9d2..2e09609233766 100644
--- a/code/datums/status_effects/buffs.dm
+++ b/code/datums/status_effects/buffs.dm
@@ -3,9 +3,9 @@
/datum/status_effect/shadow_mend
id = "shadow_mend"
duration = 30
- alert_type = /obj/screen/alert/status_effect/shadow_mend
+ alert_type = /atom/movable/screen/alert/status_effect/shadow_mend
-/obj/screen/alert/status_effect/shadow_mend
+/atom/movable/screen/alert/status_effect/shadow_mend
name = "Shadow Mend"
desc = "Shadowy energies wrap around your wounds, sealing them at a price. After healing, you will slowly lose health every three seconds for thirty seconds."
icon_state = "shadow_mend"
@@ -29,9 +29,9 @@
id = "void_price"
duration = 300
tick_interval = 30
- alert_type = /obj/screen/alert/status_effect/void_price
+ alert_type = /atom/movable/screen/alert/status_effect/void_price
-/obj/screen/alert/status_effect/void_price
+/atom/movable/screen/alert/status_effect/void_price
name = "Void Price"
desc = "Black tendrils cinch tightly against you, digging wicked barbs into your flesh."
icon_state = "shadow_mend"
@@ -43,7 +43,7 @@
/datum/status_effect/cyborg_power_regen
id = "power_regen"
duration = 100
- alert_type = /obj/screen/alert/status_effect/power_regen
+ alert_type = /atom/movable/screen/alert/status_effect/power_regen
var/power_to_give = 0 //how much power is gained each tick
/datum/status_effect/cyborg_power_regen/on_creation(mob/living/new_owner, new_power_per_tick)
@@ -51,7 +51,7 @@
if(. && isnum_safe(new_power_per_tick))
power_to_give = new_power_per_tick
-/obj/screen/alert/status_effect/power_regen
+/atom/movable/screen/alert/status_effect/power_regen
name = "Power Regeneration"
desc = "You are quickly regenerating power!"
icon_state = "power_regen"
@@ -68,16 +68,16 @@
id = "his_grace"
duration = -1
tick_interval = 4
- alert_type = /obj/screen/alert/status_effect/his_grace
+ alert_type = /atom/movable/screen/alert/status_effect/his_grace
var/bloodlust = 0
-/obj/screen/alert/status_effect/his_grace
+/atom/movable/screen/alert/status_effect/his_grace
name = "His Grace"
desc = "His Grace hungers, and you must feed Him."
icon_state = "his_grace"
alerttooltipstyle = "hisgrace"
-/obj/screen/alert/status_effect/his_grace/MouseEntered(location,control,params)
+/atom/movable/screen/alert/status_effect/his_grace/MouseEntered(location,control,params)
desc = initial(desc)
var/datum/status_effect/his_grace/HG = attached_effect
desc += " Current Bloodthirst: [HG.bloodlust]\
@@ -118,7 +118,7 @@
/datum/status_effect/wish_granters_gift //Fully revives after ten seconds.
id = "wish_granters_gift"
duration = 50
- alert_type = /obj/screen/alert/status_effect/wish_granters_gift
+ alert_type = /atom/movable/screen/alert/status_effect/wish_granters_gift
/datum/status_effect/wish_granters_gift/on_apply()
to_chat(owner, "Death is not your end! The Wish Granter's energy suffuses you, and you begin to rise...")
@@ -129,7 +129,7 @@
owner.visible_message("[owner] appears to wake from the dead, having healed all wounds!", "You have regenerated.")
owner.update_mobility()
-/obj/screen/alert/status_effect/wish_granters_gift
+/atom/movable/screen/alert/status_effect/wish_granters_gift
name = "Wish Granter's Immortality"
desc = "You are being resurrected!"
icon_state = "wish_granter"
@@ -167,7 +167,7 @@
id = "blooddrunk"
duration = 10
tick_interval = 0
- alert_type = /obj/screen/alert/status_effect/blooddrunk
+ alert_type = /atom/movable/screen/alert/status_effect/blooddrunk
var/last_health = 0
var/last_bruteloss = 0
var/last_fireloss = 0
@@ -176,7 +176,7 @@
var/last_cloneloss = 0
var/last_staminaloss = 0
-/obj/screen/alert/status_effect/blooddrunk
+/atom/movable/screen/alert/status_effect/blooddrunk
name = "Blood-Drunk"
desc = "You are drunk on blood! Your pulse thunders in your ears! Nothing can harm you!" //not true, and the item description mentions its actual effect
icon_state = "blooddrunk"
@@ -209,7 +209,7 @@
last_staminaloss = owner.getStaminaLoss()
owner.log_message("gained blood-drunk stun immunity", LOG_ATTACK)
owner.add_stun_absorption("blooddrunk", INFINITY, 4)
- owner.playsound_local(get_turf(owner), 'sound/effects/singlebeat.ogg', 40, 1)
+ owner.playsound_local(get_turf(owner), 'sound/effects/singlebeat.ogg', 40, 1, use_reverb = FALSE)
/datum/status_effect/blooddrunk/tick() //multiply the effect of healing by 10
if(owner.health > last_health)
@@ -312,18 +312,18 @@
playsound(owner, 'sound/weapons/fwoosh.ogg', 75, 0)
var/obj/item/slashy
slashy = owner.get_active_held_item()
- for(var/mob/living/M in orange(1,owner))
+ for(var/mob/living/M in ohearers(1,owner))
slashy.attack(M, owner)
/datum/status_effect/sword_spin/on_remove()
owner.visible_message("[owner]'s inhuman strength dissipates and the sword's runes grow cold!")
-
//Used by changelings to rapidly heal
//Being on fire will suppress this healing
/datum/status_effect/fleshmend
id = "fleshmend"
- alert_type = /obj/screen/alert/status_effect/fleshmend
+ alert_type = /atom/movable/screen/alert/status_effect/fleshmend
+ duration = 100
/datum/status_effect/fleshmend/tick()
if(owner.on_fire)
@@ -331,12 +331,13 @@
return
else
linked_alert.icon_state = "fleshmend"
- owner.adjustBruteLoss(-1.5, FALSE)
- owner.adjustFireLoss(-0.25, FALSE)
- owner.adjustToxLoss(-0.5, FALSE)
- owner.adjustCloneLoss(-0.5)
+ owner.adjustBruteLoss(-2.5, FALSE)
+ owner.adjustFireLoss(-2.5, FALSE)
+ owner.adjustOxyLoss(-2.5)
+ owner.adjustCloneLoss(-2.5)
+ owner.adjustToxLoss(-2.5, FALSE, TRUE)
-/obj/screen/alert/status_effect/fleshmend
+/atom/movable/screen/alert/status_effect/fleshmend
name = "Fleshmend"
desc = "Our wounds are rapidly healing. This effect is prevented if we are on fire."
icon_state = "fleshmend"
@@ -426,13 +427,13 @@
itemUser.adjustOrganLoss(ORGAN_SLOT_BRAIN, -1.5)
itemUser.adjustCloneLoss(-0.5) //Becasue apparently clone damage is the bastion of all health
//Heal all those around you, unbiased
- for(var/mob/living/L in view(7, owner))
+ for(var/mob/living/L in hearers(7, owner))
if(L.health < L.maxHealth)
new /obj/effect/temp_visual/heal(get_turf(L), "#375637")
if(iscarbon(L))
L.adjustBruteLoss(-3.5)
L.adjustFireLoss(-3.5)
- L.adjustToxLoss(-3.5, forced = TRUE) //Because Slime People are people too
+ L.adjustToxLoss(-3.5, FALSE, TRUE) //Because Slime People are people too
L.adjustOxyLoss(-3.5)
L.adjustStaminaLoss(-3.5)
L.adjustOrganLoss(ORGAN_SLOT_BRAIN, -3.5)
@@ -444,7 +445,7 @@
var/mob/living/simple_animal/SM = L
SM.adjustHealth(-3.5, forced = TRUE)
-/obj/screen/alert/status_effect/regenerative_core
+/atom/movable/screen/alert/status_effect/regenerative_core
name = "Blessing of the Necropolis"
desc = "The power of the necropolis flows through you. You could get used to this..."
icon_state = "regenerative_core"
@@ -454,7 +455,7 @@
id = "Regenerative Core"
duration = 300
status_type = STATUS_EFFECT_REPLACE
- alert_type = /obj/screen/alert/status_effect/regenerative_core
+ alert_type = /atom/movable/screen/alert/status_effect/regenerative_core
var/power = 1
var/alreadyinfected = FALSE
diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm
index 25d260605a7ac..de71c4b4eb84a 100644
--- a/code/datums/status_effects/debuffs.dm
+++ b/code/datums/status_effects/debuffs.dm
@@ -48,7 +48,7 @@
//SLEEPING
/datum/status_effect/incapacitating/sleeping
id = "sleeping"
- alert_type = /obj/screen/alert/status_effect/asleep
+ alert_type = /atom/movable/screen/alert/status_effect/asleep
needs_update_stat = TRUE
var/mob/living/carbon/carbon_owner
var/mob/living/carbon/human/human_owner
@@ -88,7 +88,7 @@
if(prob(10) && owner.health > owner.crit_threshold)
owner.emote("snore")
-/obj/screen/alert/status_effect/asleep
+/atom/movable/screen/alert/status_effect/asleep
name = "Asleep"
desc = "You've fallen asleep. Wait a bit and you should wake up. Unless you don't, considering how helpless you are."
icon_state = "asleep"
@@ -98,7 +98,7 @@
id = "stasis"
duration = -1
tick_interval = 10
- alert_type = /obj/screen/alert/status_effect/stasis
+ alert_type = /atom/movable/screen/alert/status_effect/stasis
var/last_dead_time
/datum/status_effect/incapacitating/stasis/proc/update_time_of_death()
@@ -114,6 +114,7 @@
/datum/status_effect/incapacitating/stasis/on_creation(mob/living/new_owner, set_duration, updating_canmove)
. = ..()
update_time_of_death()
+ owner.reagents?.end_metabolization(owner, FALSE)
/datum/status_effect/incapacitating/stasis/tick()
update_time_of_death()
@@ -126,7 +127,7 @@
update_time_of_death()
return ..()
-/obj/screen/alert/status_effect/stasis
+/atom/movable/screen/alert/status_effect/stasis
name = "Stasis"
desc = "Your biological functions have halted. You could live forever this way, but it's pretty boring."
icon_state = "stasis"
@@ -137,7 +138,7 @@
/datum/status_effect/strandling //get it, strand as in durathread strand + strangling = strandling hahahahahahahahahahhahahaha i want to die
id = "strandling"
status_type = STATUS_EFFECT_UNIQUE
- alert_type = /obj/screen/alert/status_effect/strandling
+ alert_type = /atom/movable/screen/alert/status_effect/strandling
/datum/status_effect/strandling/on_apply()
ADD_TRAIT(owner, TRAIT_MAGIC_CHOKE, "dumbmoron")
@@ -147,13 +148,13 @@
REMOVE_TRAIT(owner, TRAIT_MAGIC_CHOKE, "dumbmoron")
return ..()
-/obj/screen/alert/status_effect/strandling
+/atom/movable/screen/alert/status_effect/strandling
name = "Choking strand"
desc = "A magical strand of Durathread is wrapped around your neck, preventing you from breathing! Click this icon to remove the strand."
icon_state = "his_grace"
alerttooltipstyle = "hisgrace"
-/obj/screen/alert/status_effect/strandling/Click(location, control, params)
+/atom/movable/screen/alert/status_effect/strandling/Click(location, control, params)
. = ..()
if(usr != owner)
return
@@ -168,9 +169,9 @@
id = "syringe"
status_type = STATUS_EFFECT_MULTIPLE
alert_type = null
- var/obj/item/reagent_containers/syringe/syringe = null
+ var/obj/item/reagent_containers/syringe/syringe
var/injectmult = 1
-
+
/datum/status_effect/syringe/on_creation(mob/living/new_owner, obj/item/reagent_containers/syringe/origin, mult)
syringe = origin
injectmult = mult
@@ -181,7 +182,7 @@
var/amount = syringe.initial_inject
syringe.reagents.reaction(owner, INJECT)
syringe.reagents.trans_to(owner, max(3.1, amount * injectmult))
- owner.throw_alert("syringealert", /obj/screen/alert/syringe)
+ owner.throw_alert("syringealert", /atom/movable/screen/alert/syringe)
/datum/status_effect/syringe/tick()
. = ..()
@@ -190,16 +191,18 @@
syringe.reagents.trans_to(owner, amount * injectmult)
-/obj/screen/alert/syringe
+/atom/movable/screen/alert/syringe
name = "Embedded Syringe"
desc = "A syringe has embedded itself into your body, injecting its reagents! click this icon to carefully remove the syringe."
icon_state = "drugged"
alerttooltipstyle = "hisgrace"
-/obj/screen/alert/syringe/Click(location, control, params)
+/atom/movable/screen/alert/syringe/Click(location, control, params)
. = ..()
if(usr != owner)
return
+ if(owner.incapacitated())
+ return
var/list/syringes = list()
if(iscarbon(owner))
var/mob/living/carbon/C = owner
@@ -210,7 +213,7 @@
var/datum/status_effect/syringe/syringestatus = pick_n_take(syringes)
if(istype(syringestatus, /datum/status_effect/syringe))
var/obj/item/reagent_containers/syringe/syringe = syringestatus.syringe
- to_chat(owner, "You begin carefully pulling the syringe out...")
+ to_chat(owner, "You begin carefully pulling the syringe out.")
if(do_after(C, 20, null, owner))
to_chat(C, "You succesfuly remove the syringe.")
syringe.forceMove(C.loc)
@@ -223,7 +226,7 @@
syringe.reagents.trans_to(C, amount)
syringe.forceMove(C.loc)
qdel(syringestatus)
- if(!C.has_status_effect(STATUS_EFFECT_SYRINGE))
+ if(!C.has_status_effect(STATUS_EFFECT_SYRINGE))
C.clear_alert("syringealert")
@@ -264,9 +267,9 @@
id = "his_wrath"
duration = -1
tick_interval = 4
- alert_type = /obj/screen/alert/status_effect/his_wrath
+ alert_type = /atom/movable/screen/alert/status_effect/his_wrath
-/obj/screen/alert/status_effect/his_wrath
+/atom/movable/screen/alert/status_effect/his_wrath
name = "His Wrath"
desc = "You fled from His Grace instead of feeding Him, and now you suffer."
icon_state = "his_grace"
@@ -299,9 +302,9 @@
status_type = STATUS_EFFECT_REPLACE
alert_type = null
var/mutable_appearance/marked_underlay
- var/obj/item/twohanded/kinetic_crusher/hammer_synced
+ var/obj/item/kinetic_crusher/hammer_synced
-/datum/status_effect/crusher_mark/on_creation(mob/living/new_owner, obj/item/twohanded/kinetic_crusher/new_hammer_synced)
+/datum/status_effect/crusher_mark/on_creation(mob/living/new_owner, obj/item/kinetic_crusher/new_hammer_synced)
. = ..()
if(.)
hammer_synced = new_hammer_synced
@@ -393,7 +396,7 @@
new /obj/effect/temp_visual/bleed/explode(T)
for(var/d in GLOB.alldirs)
new /obj/effect/temp_visual/dir_setting/bloodsplatter(T, d)
- playsound(T, "desceration", 200, 1, -1)
+ playsound(T, "desecration", 200, 1, -1)
owner.adjustBruteLoss(bleed_damage)
else
new /obj/effect/temp_visual/bleed(get_turf(owner))
@@ -448,7 +451,7 @@
/datum/status_effect/necropolis_curse/proc/apply_curse(set_curse)
curse_flags |= set_curse
if(curse_flags & CURSE_BLINDING)
- owner.overlay_fullscreen("curse", /obj/screen/fullscreen/curse, 1)
+ owner.overlay_fullscreen("curse", /atom/movable/screen/fullscreen/curse, 1)
/datum/status_effect/necropolis_curse/proc/remove_curse(remove_curse)
if(remove_curse & CURSE_BLINDING)
@@ -510,7 +513,7 @@
ADD_TRAIT(owner, TRAIT_PACIFISM, "gonbolaPacify")
ADD_TRAIT(owner, TRAIT_MUTE, "gonbolaMute")
ADD_TRAIT(owner, TRAIT_JOLLY, "gonbolaJolly")
- to_chat(owner, "You suddenly feel at peace and feel no need to make any sudden or rash actions...")
+ to_chat(owner, "You suddenly feel at peace and feel no need to make any sudden or rash actions.")
return ..()
/datum/status_effect/gonbolaPacify/on_remove()
@@ -525,11 +528,11 @@
tick_interval = 10
examine_text = "SUBJECTPRONOUN seems slow and unfocused."
var/stun = TRUE
- alert_type = /obj/screen/alert/status_effect/trance
+ alert_type = /atom/movable/screen/alert/status_effect/trance
-/obj/screen/alert/status_effect/trance
+/atom/movable/screen/alert/status_effect/trance
name = "Trance"
- desc = "Everything feels so distant, and you can feel your thoughts forming loops inside your head..."
+ desc = "Everything feels so distant, and you can feel your thoughts forming loops inside your head."
icon_state = "high"
/datum/status_effect/trance/tick()
@@ -545,7 +548,7 @@
if(!owner.has_quirk(/datum/quirk/monochromatic))
owner.add_client_colour(/datum/client_colour/monochrome)
owner.visible_message("[stun ? "[owner] stands still as [owner.p_their()] eyes seem to focus on a distant point." : ""]", \
- "[pick("You feel your thoughts slow down...", "You suddenly feel extremely dizzy...", "You feel like you're in the middle of a dream...","You feel incredibly relaxed...")]")
+ "[pick("You feel your thoughts slow down.", "You suddenly feel extremely dizzy.", "You feel like you're in the middle of a dream.","You feel incredibly relaxed.")]")
return TRUE
/datum/status_effect/trance/on_creation(mob/living/new_owner, _duration, _stun = TRUE)
@@ -561,14 +564,16 @@
owner.remove_client_colour(/datum/client_colour/monochrome)
to_chat(owner, "You snap out of your trance!")
-/datum/status_effect/trance/proc/hypnotize(datum/source, message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
+/datum/status_effect/trance/proc/hypnotize(datum/source, list/hearing_args, list/spans, list/message_mods = list())
+ SIGNAL_HANDLER
+
if(!owner.can_hear())
return
- if(speaker == owner)
+ if(hearing_args[HEARING_SPEAKER] == owner)
return
var/mob/living/carbon/C = owner
C.cure_trauma_type(/datum/brain_trauma/hypnosis, TRAUMA_RESILIENCE_SURGERY) //clear previous hypnosis
- addtimer(CALLBACK(C, /mob/living/carbon.proc/gain_trauma, /datum/brain_trauma/hypnosis, TRAUMA_RESILIENCE_SURGERY, raw_message), 10)
+ addtimer(CALLBACK(C, /mob/living/carbon.proc/gain_trauma, /datum/brain_trauma/hypnosis, TRAUMA_RESILIENCE_SURGERY, hearing_args[HEARING_RAW_MESSAGE]), 10)
addtimer(CALLBACK(C, /mob/living.proc/Stun, 60, TRUE, TRUE), 15) //Take some time to think about it
qdel(src)
@@ -601,9 +606,8 @@
range = 7
var/list/mob/living/targets = list()
- for(var/mob/M in oview(owner, range))
- if(isliving(M))
- targets += M
+ for(var/mob/living/M in oview(range, owner))
+ targets += M
if(LAZYLEN(targets))
to_chat(owner, "Your arm spasms!")
owner.log_message(" attacked someone due to a Muscle Spasm", LOG_ATTACK) //the following attack will log itself
@@ -621,7 +625,7 @@
return
var/obj/item/I = owner.get_active_held_item()
var/list/turf/targets = list()
- for(var/turf/T in oview(owner, 3))
+ for(var/turf/T in oview(3, get_turf(owner)))
targets += T
if(LAZYLEN(targets) && I)
to_chat(owner, "Your arm spasms!")
@@ -632,7 +636,7 @@
id = "convulsing"
duration = 150
status_type = STATUS_EFFECT_REFRESH
- alert_type = /obj/screen/alert/status_effect/convulsing
+ alert_type = /atom/movable/screen/alert/status_effect/convulsing
/datum/status_effect/convulsing/on_creation(mob/living/zappy_boy)
. = ..()
@@ -646,7 +650,7 @@
H.visible_message("[H]'s hand convulses, and they drop their [I.name]!","Your hand convulses violently, and you drop what you were holding!")
H.jitteriness += 5
-/obj/screen/alert/status_effect/convulsing
+/atom/movable/screen/alert/status_effect/convulsing
name = "Shaky Hands"
desc = "You've been zapped with something and your hands can't stop shaking! You can't seem to hold on to anything."
icon_state = "convulsing"
@@ -655,7 +659,7 @@
id = "dna_melt"
duration = 600
status_type = STATUS_EFFECT_REPLACE
- alert_type = /obj/screen/alert/status_effect/dna_melt
+ alert_type = /atom/movable/screen/alert/status_effect/dna_melt
var/kill_either_way = FALSE //no amount of removing mutations is gonna save you now
/datum/status_effect/dna_melt/on_creation(mob/living/new_owner, set_duration, updating_canmove)
@@ -669,7 +673,7 @@
var/mob/living/carbon/human/H = owner
H.something_horrible(kill_either_way)
-/obj/screen/alert/status_effect/dna_melt
+/atom/movable/screen/alert/status_effect/dna_melt
name = "Genetic Breakdown"
desc = "I don't feel so good. Your body can't handle the mutations! You have one minute to remove your mutations, or you will be met with a horrible fate."
icon_state = "dna_melt"
@@ -679,7 +683,7 @@
duration = 100
status_type = STATUS_EFFECT_REPLACE
tick_interval = 1
- alert_type = /obj/screen/alert/status_effect/go_away
+ alert_type = /atom/movable/screen/alert/status_effect/go_away
var/direction
/datum/status_effect/go_away/on_creation(mob/living/new_owner, set_duration, updating_canmove)
@@ -692,7 +696,7 @@
var/turf/T = get_step(owner, direction)
owner.forceMove(T)
-/obj/screen/alert/status_effect/go_away
+/atom/movable/screen/alert/status_effect/go_away
name = "TO THE STARS AND BEYOND!"
desc = "I must go, my people need me!"
icon_state = "high"
@@ -703,7 +707,7 @@
duration = 25
status_type = STATUS_EFFECT_REFRESH
tick_interval = 1
- alert_type = /obj/screen/alert/status_effect/interdiction
+ alert_type = /atom/movable/screen/alert/status_effect/interdiction
var/running_toggled = FALSE
/datum/status_effect/interdiction/tick()
@@ -712,7 +716,7 @@
if(owner.confused < 10)
owner.confused = 10
running_toggled = TRUE
- to_chat(owner, "You know you shouldn't be running here...")
+ to_chat(owner, "You know you shouldn't be running here.")
owner.add_movespeed_modifier(MOVESPEED_ID_INTERDICTION, multiplicative_slowdown=1.5)
/datum/status_effect/interdiction/on_remove()
@@ -720,9 +724,9 @@
if(running_toggled && owner.m_intent == MOVE_INTENT_WALK)
owner.toggle_move_intent(owner)
-/obj/screen/alert/status_effect/interdiction
+/atom/movable/screen/alert/status_effect/interdiction
name = "Interdicted"
- desc = "I don't think I am meant to go this way..."
+ desc = "I don't think I am meant to go this way."
icon_state = "inathneqs_endowment"
/datum/status_effect/fake_virus
@@ -834,8 +838,8 @@
var/mob/living/carbon/carbon_owner = owner
carbon_owner.adjustStaminaLoss(10 * repetitions)
carbon_owner.adjustFireLoss(5 * repetitions)
- for(var/mob/living/carbon/victim in range(1,carbon_owner))
- if(IS_HERETIC(victim) || victim == carbon_owner)
+ for(var/mob/living/carbon/victim in ohearers(1,carbon_owner))
+ if(IS_HERETIC(victim))
continue
victim.apply_status_effect(type,repetitions-1)
break
@@ -859,36 +863,84 @@
id = "corrosion_curse"
status_type = STATUS_EFFECT_REPLACE
alert_type = null
- tick_interval = 1 SECONDS
+ tick_interval = 4 SECONDS
/datum/status_effect/corrosion_curse/on_creation(mob/living/new_owner, ...)
. = ..()
- to_chat(owner, "Your feel your body starting to break apart...")
+ to_chat(owner, "You hear a distant whisper that fills you with dread.")
/datum/status_effect/corrosion_curse/tick()
. = ..()
if(!ishuman(owner))
return
var/mob/living/carbon/human/H = owner
+ if (H.IsSleeping())
+ return
var/chance = rand(0,100)
+ var/message = "Coder did fucky wucky U w U"
switch(chance)
- if(0 to 19)
+ if(0 to 39)
+ H.adjustStaminaLoss(20)
+ message = "You feel tired."
+ if(40 to 59)
+ H.Dizzy(3 SECONDS)
+ message = "Your feel light headed."
+ if(60 to 74)
+ H.confused = max(H.confused, 2 SECONDS)
+ message = "Your feel confused."
+ if(75 to 79)
+ H.adjustOrganLoss(ORGAN_SLOT_STOMACH,15)
H.vomit()
- if(20 to 29)
- H.Dizzy(10)
- if(30 to 39)
- H.adjustOrganLoss(ORGAN_SLOT_LIVER,5)
- if(40 to 49)
- H.adjustOrganLoss(ORGAN_SLOT_HEART,5)
- if(50 to 59)
- H.adjustOrganLoss(ORGAN_SLOT_STOMACH,5)
- if(60 to 69)
- H.adjustOrganLoss(ORGAN_SLOT_EYES,10)
- if(70 to 79)
- H.adjustOrganLoss(ORGAN_SLOT_EARS,10)
- if(80 to 89)
- H.adjustOrganLoss(ORGAN_SLOT_LUNGS,10)
- if(90 to 99)
- H.adjustOrganLoss(ORGAN_SLOT_TONGUE,10)
- if(100)
- H.adjustOrganLoss(ORGAN_SLOT_BRAIN,20)
+ message = "Black bile shoots out of your mouth."
+ if(80 to 84)
+ H.adjustOrganLoss(ORGAN_SLOT_LIVER,15)
+ H.SetKnockdown(10)
+ message = "Your feel a terrible pain in your abdomen."
+ if(85 to 89)
+ H.adjustOrganLoss(ORGAN_SLOT_EYES,15)
+ message = "Your eyes sting."
+ else
+ H.adjustOrganLoss(ORGAN_SLOT_EARS,15)
+ message = "Your inner ear hurts."
+ if (prob(33)) //so the victim isn't spammed with messages every 3 seconds
+ to_chat(H,message)
+
+/datum/status_effect/ghoul
+ id = "ghoul"
+ status_type = STATUS_EFFECT_UNIQUE
+ duration = -1
+ examine_text = "SUBJECTPRONOUN has a blank, catatonic like stare."
+ alert_type = /atom/movable/screen/alert/status_effect/ghoul
+
+/atom/movable/screen/alert/status_effect/ghoul
+ name = "Flesh Servant"
+ desc = "You are a Ghoul! A eldritch monster reanimated to serve its master."
+ icon_state = "mind_control"
+
+/datum/status_effect/spanish
+ id = "spanish"
+ duration = 120 SECONDS
+ alert_type = null
+
+/datum/status_effect/spanish/on_apply(mob/living/new_owner, ...)
+ . = ..()
+ to_chat(owner, "Alert: Vocal cords are malfunctioning.")
+ owner.add_blocked_language(subtypesof(/datum/language/) - /datum/language/uncommon, LANGUAGE_EMP)
+ owner.grant_language(/datum/language/uncommon, FALSE, TRUE, LANGUAGE_EMP)
+
+/datum/status_effect/spanish/on_remove()
+ owner.remove_blocked_language(subtypesof(/datum/language/), LANGUAGE_EMP)
+ owner.remove_language(/datum/language/uncommon, TRUE, TRUE, LANGUAGE_EMP)
+ to_chat(owner, "Alert: Vocal cords restored to normal function.")
+ return ..()
+
+/datum/status_effect/ipc/emp
+ id = "ipc_emp"
+ examine_text = "SUBJECTPRONOUN is buzzing and twitching!"
+ duration = 120 SECONDS
+ alert_type = /atom/movable/screen/alert/status_effect/emp
+ status_type = STATUS_EFFECT_REFRESH
+/atom/movable/screen/alert/status_effect/emp
+ name = "Electro-Magnetic Pulse"
+ desc = "You've been hit with an EMP! You're malfunctioning!"
+ icon_state = "hypnosis"
diff --git a/code/datums/status_effects/gas.dm b/code/datums/status_effects/gas.dm
index 608dbb2d7a116..c0b2988f0dde5 100644
--- a/code/datums/status_effects/gas.dm
+++ b/code/datums/status_effects/gas.dm
@@ -2,11 +2,11 @@
id = "frozen"
duration = 100
status_type = STATUS_EFFECT_UNIQUE
- alert_type = /obj/screen/alert/status_effect/freon
+ alert_type = /atom/movable/screen/alert/status_effect/freon
var/icon/cube
var/can_melt = TRUE
-/obj/screen/alert/status_effect/freon
+/atom/movable/screen/alert/status_effect/freon
name = "Frozen Solid"
desc = "You're frozen inside an ice cube, and cannot move! You can still do stuff, like shooting. Resist out of the cube!"
icon_state = "frozen"
@@ -26,6 +26,11 @@
qdel(src)
/datum/status_effect/freon/proc/owner_resist()
+ SIGNAL_HANDLER
+
+ INVOKE_ASYNC(src, .proc/do_resist)
+
+/datum/status_effect/freon/proc/do_resist()
to_chat(owner, "You start breaking out of the ice cube!")
if(do_mob(owner, owner, 40))
if(!QDELETED(src))
diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm
index 1f032929c742e..ff1f54aceedb7 100644
--- a/code/datums/status_effects/neutral.dm
+++ b/code/datums/status_effects/neutral.dm
@@ -49,7 +49,7 @@
get_kill()
. = ..()
-/obj/screen/alert/status_effect/in_love
+/atom/movable/screen/alert/status_effect/in_love
name = "In Love"
desc = "You feel so wonderfully in love!"
icon_state = "in_love"
@@ -58,7 +58,7 @@
id = "in_love"
duration = -1
status_type = STATUS_EFFECT_UNIQUE
- alert_type = /obj/screen/alert/status_effect/in_love
+ alert_type = /atom/movable/screen/alert/status_effect/in_love
var/mob/living/date
/datum/status_effect/in_love/on_creation(mob/living/new_owner, mob/living/love_interest)
@@ -70,7 +70,9 @@
/datum/status_effect/in_love/tick()
if(date)
new /obj/effect/temp_visual/love_heart/invisible(get_turf(date.loc), owner)
-
+ if(get_dist(get_turf(owner), get_turf(date)) < 7)
+ owner.heal_overall_damage(1, 1, BODYPART_ORGANIC)
+ date.heal_overall_damage(1, 1, BODYPART_ORGANIC)
/datum/status_effect/throat_soothed
id = "throat_soothed"
@@ -117,7 +119,7 @@
spell.update_icon()
rewarded.adjustBruteLoss(-25)
rewarded.adjustFireLoss(-25)
- rewarded.adjustToxLoss(-25)
+ rewarded.adjustToxLoss(-25, FALSE, TRUE)
rewarded.adjustOxyLoss(-25)
rewarded.adjustCloneLoss(-25)
@@ -128,6 +130,20 @@
alert_type = null
var/mob/living/listening_in
+/datum/status_effect/bugged/on_apply(mob/living/new_owner, mob/living/tracker)
+ . = ..()
+ if (.)
+ RegisterSignal(new_owner, COMSIG_MOVABLE_HEAR, .proc/handle_hearing)
+
+/datum/status_effect/bugged/on_remove()
+ . = ..()
+ UnregisterSignal(owner, COMSIG_MOVABLE_HEAR)
+
+/datum/status_effect/bugged/proc/handle_hearing(datum/source, list/hearing_args)
+ SIGNAL_HANDLER
+ listening_in.show_message(hearing_args[HEARING_MESSAGE])
+
+
/datum/status_effect/bugged/on_creation(mob/living/new_owner, mob/living/tracker)
. = ..()
if(.)
diff --git a/code/datums/status_effects/status_effect.dm b/code/datums/status_effects/status_effect.dm
index fe989d6b4e413..a67e29b68387a 100644
--- a/code/datums/status_effects/status_effect.dm
+++ b/code/datums/status_effects/status_effect.dm
@@ -10,8 +10,8 @@
var/status_type = STATUS_EFFECT_UNIQUE //How many of the effect can be on one mob, and what happens when you try to add another
var/on_remove_on_mob_delete = FALSE //if we call on_remove() when the mob is deleted
var/examine_text //If defined, this text will appear when the mob is examined - to use he, she etc. use "SUBJECTPRONOUN" and replace it in the examines themselves
- var/alert_type = /obj/screen/alert/status_effect //the alert thrown by the status effect, contains name and description
- var/obj/screen/alert/status_effect/linked_alert = null //the alert itself, if it exists
+ var/alert_type = /atom/movable/screen/alert/status_effect //the alert thrown by the status effect, contains name and description
+ var/atom/movable/screen/alert/status_effect/linked_alert = null //the alert itself, if it exists
/datum/status_effect/New(list/arguments)
on_creation(arglist(arguments))
@@ -19,16 +19,16 @@
/datum/status_effect/proc/on_creation(mob/living/new_owner, ...)
if(new_owner)
owner = new_owner
- if(owner)
- LAZYADD(owner.status_effects, src)
- if(!owner || !on_apply())
+ if(QDELETED(owner) || !on_apply())
qdel(src)
return
+ if(owner)
+ LAZYADD(owner.status_effects, src)
if(duration != -1)
duration = world.time + duration
tick_interval = world.time + tick_interval
if(alert_type)
- var/obj/screen/alert/status_effect/A = owner.throw_alert(id, alert_type)
+ var/atom/movable/screen/alert/status_effect/A = owner.throw_alert(id, alert_type)
A.attached_effect = src //so the alert can reference us, if it needs to
linked_alert = A //so we can reference the alert, if we need to
if(duration > 0 || initial(tick_interval) > 0) //don't process if we don't care
@@ -38,6 +38,7 @@
/datum/status_effect/Destroy()
STOP_PROCESSING(SSfastprocess, src)
if(owner)
+ linked_alert = null
owner.clear_alert(id)
LAZYREMOVE(owner.status_effects, src)
on_remove()
@@ -81,11 +82,15 @@
// ALERT HOOK //
////////////////
-/obj/screen/alert/status_effect
+/atom/movable/screen/alert/status_effect
name = "Curse of Mundanity"
desc = "You don't feel any different..."
var/datum/status_effect/attached_effect
+/atom/movable/screen/alert/status_effect/Destroy()
+ attached_effect = null //Don't keep a ref now
+ return ..()
+
//////////////////
// HELPER PROCS //
//////////////////
diff --git a/code/datums/tgs_event_handler.dm b/code/datums/tgs_event_handler.dm
new file mode 100644
index 0000000000000..602323d22c027
--- /dev/null
+++ b/code/datums/tgs_event_handler.dm
@@ -0,0 +1,41 @@
+/datum/tgs_event_handler/impl
+ var/datum/timedevent/reattach_timer
+
+/datum/tgs_event_handler/impl/HandleEvent(event_code, ...)
+ switch(event_code)
+ if(TGS_EVENT_REBOOT_MODE_CHANGE)
+ var/list/reboot_mode_lookup = list ("[TGS_REBOOT_MODE_NORMAL]" = "be normal", "[TGS_REBOOT_MODE_SHUTDOWN]" = "shutdown the server", "[TGS_REBOOT_MODE_RESTART]" = "hard restart the server")
+ var/old_reboot_mode = args[2]
+ var/new_reboot_mode = args[3]
+ message_admins("TGS: Rebooting will no longer [reboot_mode_lookup["[old_reboot_mode]"]], it will [reboot_mode_lookup["[new_reboot_mode]"]] instead")
+ if(TGS_EVENT_PORT_SWAP)
+ message_admins("TGS: Changing port from [world.port] to [args[2]]")
+ if(TGS_EVENT_INSTANCE_RENAMED)
+ message_admins("TGS: Instance renamed to from [world.TgsInstanceName()] to [args[2]]")
+ if(TGS_EVENT_COMPILE_START)
+ message_admins("TGS: Deployment started, new game version incoming...")
+ if(TGS_EVENT_COMPILE_CANCELLED)
+ message_admins("TGS: Deployment cancelled!")
+ if(TGS_EVENT_COMPILE_FAILURE)
+ message_admins("TGS: Deployment failed!")
+ if(TGS_EVENT_DEPLOYMENT_COMPLETE)
+ message_admins("TGS: Deployment complete!")
+ to_chat(world, "Server updated, changes will be applied on the next round...")
+ if(TGS_EVENT_WATCHDOG_DETACH)
+ message_admins("TGS restarting...")
+ reattach_timer = addtimer(CALLBACK(src, .proc/LateOnReattach), 1 MINUTES, TIMER_STOPPABLE)
+ if(TGS_EVENT_WATCHDOG_REATTACH)
+ var/datum/tgs_version/old_version = world.TgsVersion()
+ var/datum/tgs_version/new_version = args[2]
+ if(!old_version.Equals(new_version))
+ to_chat(world, "TGS updated to v[new_version.deprefixed_parameter]")
+ else
+ message_admins("TGS: Back online")
+ if(reattach_timer)
+ deltimer(reattach_timer)
+ reattach_timer = null
+ if(TGS_EVENT_WATCHDOG_SHUTDOWN)
+ to_chat_immediate(world, "Server is shutting down!")
+
+/datum/tgs_event_handler/impl/proc/LateOnReattach()
+ message_admins("Warning: TGS hasn't notified us of it coming back for a full minute! Is there a problem?")
diff --git a/code/datums/traits/_quirk.dm b/code/datums/traits/_quirk.dm
index 8cccc66063a7e..d5c7753277640 100644
--- a/code/datums/traits/_quirk.dm
+++ b/code/datums/traits/_quirk.dm
@@ -59,14 +59,14 @@
/datum/quirk/proc/clone_data() //return additional data that should be remembered by cloning
/datum/quirk/proc/on_clone(data) //create the quirk from clone data
-/datum/quirk/process()
+/datum/quirk/process(delta_time)
if(QDELETED(quirk_holder))
quirk_holder = null
qdel(src)
return
if(quirk_holder.stat == DEAD)
return
- on_process()
+ on_process(delta_time)
/mob/living/proc/get_trait_string(medical) //helper string. gets a string of all the traits the mob has
var/list/dat = list()
@@ -124,7 +124,7 @@ Use this as a guideline
var/mob/living/carbon/human/H = quirk_holder
var/obj/item/clothing/glasses/regular/glasses = new(get_turf(H))
H.put_in_hands(glasses)
- H.equip_to_slot(glasses, SLOT_GLASSES)
+ H.equip_to_slot(glasses, ITEM_SLOT_EYES)
H.regenerate_icons()
//This whole proc is called automatically
diff --git a/code/datums/traits/good.dm b/code/datums/traits/good.dm
index 4a0f0f7e5b252..fac6f5fe67677 100644
--- a/code/datums/traits/good.dm
+++ b/code/datums/traits/good.dm
@@ -67,8 +67,8 @@
mob_trait = TRAIT_JOLLY
mood_quirk = TRUE
-/datum/quirk/jolly/on_process()
- if(prob(0.05))
+/datum/quirk/jolly/on_process(delta_time)
+ if(DT_PROB(0.05, delta_time))
SEND_SIGNAL(quirk_holder, COMSIG_ADD_MOOD_EVENT, "jolly", /datum/mood_event/jolly)
/datum/quirk/light_step
@@ -91,8 +91,8 @@
var/mob/living/carbon/human/H = quirk_holder
var/obj/item/choice_beacon/music/B = new(get_turf(H))
var/list/slots = list (
- "backpack" = SLOT_IN_BACKPACK,
- "hands" = SLOT_HANDS,
+ "backpack" = ITEM_SLOT_BACKPACK,
+ "hands" = ITEM_SLOT_HANDS,
)
H.equip_in_one_of_slots(B, slots , qdel_on_fail = TRUE)
@@ -144,8 +144,14 @@
/datum/quirk/photographer/on_spawn()
var/mob/living/carbon/human/H = quirk_holder
var/obj/item/camera/camera = new(get_turf(H))
- H.put_in_hands(camera)
- H.equip_to_slot(camera, SLOT_NECK)
+ var/list/camera_slots = list (
+ "neck" = ITEM_SLOT_NECK,
+ "left pocket" = ITEM_SLOT_LPOCKET,
+ "right pocket" = ITEM_SLOT_RPOCKET,
+ "backpack" = ITEM_SLOT_BACKPACK,
+ "hands" = ITEM_SLOT_HANDS
+ )
+ H.equip_in_one_of_slots(camera, camera_slots , qdel_on_fail = TRUE)
H.regenerate_icons()
/datum/quirk/selfaware
@@ -170,13 +176,13 @@
/datum/quirk/spiritual/on_spawn()
var/mob/living/carbon/human/H = quirk_holder
- H.equip_to_slot_or_del(new /obj/item/storage/fancy/candle_box(H), SLOT_IN_BACKPACK)
- H.equip_to_slot_or_del(new /obj/item/storage/box/matches(H), SLOT_IN_BACKPACK)
+ H.equip_to_slot_or_del(new /obj/item/storage/fancy/candle_box(H), ITEM_SLOT_BACKPACK)
+ H.equip_to_slot_or_del(new /obj/item/storage/box/matches(H), ITEM_SLOT_BACKPACK)
/datum/quirk/spiritual/on_process()
var/comforted = FALSE
- for(var/mob/living/L in oview(5, quirk_holder))
- if(L.mind?.isholy && L.stat == CONSCIOUS)
+ for(var/mob/living/carbon/human/H in oview(5, quirk_holder))
+ if(H.mind?.holy_role && H.stat == CONSCIOUS)
comforted = TRUE
break
if(comforted)
@@ -196,7 +202,7 @@
var/mob/living/carbon/human/H = quirk_holder
var/obj/item/toy/crayon/spraycan/spraycan = new(get_turf(H))
H.put_in_hands(spraycan)
- H.equip_to_slot(spraycan, SLOT_IN_BACKPACK)
+ H.equip_to_slot(spraycan, ITEM_SLOT_BACKPACK)
H.regenerate_icons()
/datum/quirk/voracious
diff --git a/code/datums/traits/negative.dm b/code/datums/traits/negative.dm
index c402c41a76901..f67437bed3661 100644
--- a/code/datums/traits/negative.dm
+++ b/code/datums/traits/negative.dm
@@ -23,13 +23,13 @@
lose_text = "You feel vigorous again."
medical_record_text = "Patient requires regular treatment for blood loss due to low production of blood."
-/datum/quirk/blooddeficiency/on_process()
+/datum/quirk/blooddeficiency/on_process(delta_time)
var/mob/living/carbon/human/H = quirk_holder
if(NOBLOOD in H.dna.species.species_traits) //can't lose blood if your species doesn't have any
return
else
if (H.blood_volume > (BLOOD_VOLUME_SAFE - 25)) // just barely survivable without treatment
- H.blood_volume -= 0.275
+ H.blood_volume -= 0.275 * delta_time
/datum/quirk/blindness
name = "Blind"
@@ -45,21 +45,38 @@
/datum/quirk/blindness/on_spawn()
var/mob/living/carbon/human/H = quirk_holder
var/obj/item/clothing/glasses/blindfold/white/B = new(get_turf(H))
- if(!H.equip_to_slot_if_possible(B, SLOT_GLASSES, bypass_equip_delay_self = TRUE)) //if you can't put it on the user's eyes, put it in their hands, otherwise put it on their eyes
+ if(!H.equip_to_slot_if_possible(B, ITEM_SLOT_EYES, bypass_equip_delay_self = TRUE)) //if you can't put it on the user's eyes, put it in their hands, otherwise put it on their eyes
H.put_in_hands(B)
H.regenerate_icons()
/datum/quirk/brainproblems
name = "Brain Tumor"
- desc = "You have a little friend in your brain that is slowly destroying it. Better bring some mannitol!"
+ desc = "You have a little friend in your brain that is slowly destroying it. Thankfully, you start with a bottle of mannitol pills."
value = -3
gain_text = "You feel smooth."
lose_text = "You feel wrinkled again."
medical_record_text = "Patient has a tumor in their brain that is slowly driving them to brain death."
+ var/where = "at your feet"
/datum/quirk/brainproblems/on_process()
quirk_holder.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.2)
+/datum/quirk/brainproblems/on_spawn()
+ var/mob/living/carbon/human/H = quirk_holder
+ var/obj/item/storage/pill_bottle/mannitol/braintumor/P = new(get_turf(H))
+
+ var/slot = H.equip_in_one_of_slots(P, list(ITEM_SLOT_LPOCKET, ITEM_SLOT_RPOCKET, ITEM_SLOT_BACKPACK), FALSE)
+ if(slot)
+ var/list/slots = list(
+ ITEM_SLOT_LPOCKET = "in your left pocket",
+ ITEM_SLOT_RPOCKET = "in your right pocket",
+ ITEM_SLOT_BACKPACK = "in your backpack"
+ )
+ where = slots[slot]
+
+/datum/quirk/brainproblems/post_add()
+ to_chat(quirk_holder, "There is a bottle of mannitol [where]. You're going to need it.")
+
/datum/quirk/deafness
name = "Deaf"
desc = "You are incurably deaf."
@@ -79,8 +96,8 @@
medical_record_text = "Patient has a severe mood disorder causing them to experience sudden moments of sadness."
mood_quirk = TRUE
-/datum/quirk/depression/on_process()
- if(prob(0.05))
+/datum/quirk/depression/on_process(delta_time)
+ if(DT_PROB(0.05, delta_time))
SEND_SIGNAL(quirk_holder, COMSIG_ADD_MOOD_EVENT, "depression", /datum/mood_event/depression)
/datum/quirk/family_heirloom
@@ -103,7 +120,7 @@
if("Clown")
heirloom_type = /obj/item/bikehorn/golden
if("Mime")
- heirloom_type = /obj/item/reagent_containers/food/snacks/baguette
+ heirloom_type = /obj/item/reagent_containers/food/snacks/baguette/mime
if("Janitor")
heirloom_type = pick(/obj/item/mop, /obj/item/clothing/suit/caution, /obj/item/reagent_containers/glass/bucket)
if("Cook")
@@ -117,7 +134,7 @@
if("Chaplain")
heirloom_type = pick(/obj/item/toy/windupToolbox, /obj/item/reagent_containers/food/drinks/bottle/holywater)
if("Assistant")
- heirloom_type = /obj/item/storage/toolbox/mechanical/old/heirloom
+ heirloom_type = pick(/obj/item/storage/toolbox/mechanical/old/heirloom, /obj/item/clothing/gloves/cut/heirloom)
if("Barber")
heirloom_type = /obj/item/handmirror
if("Stage Magician")
@@ -179,9 +196,9 @@
/obj/item/dice/d20)
heirloom = new heirloom_type(get_turf(quirk_holder))
var/list/slots = list(
- "in your left pocket" = SLOT_L_STORE,
- "in your right pocket" = SLOT_R_STORE,
- "in your backpack" = SLOT_IN_BACKPACK
+ "in your left pocket" = ITEM_SLOT_LPOCKET,
+ "in your right pocket" = ITEM_SLOT_RPOCKET,
+ "in your backpack" = ITEM_SLOT_BACKPACK
)
where = H.equip_in_one_of_slots(heirloom, slots, FALSE) || "at your feet"
@@ -230,13 +247,13 @@
/datum/quirk/foreigner/add()
var/mob/living/carbon/human/H = quirk_holder
- if(ishuman(H) && !isipc(H) && H.job != "Curator")
+ if(ishuman(H) && H.job != "Curator")
H.add_blocked_language(/datum/language/common)
H.grant_language(/datum/language/uncommon)
/datum/quirk/foreigner/remove()
var/mob/living/carbon/human/H = quirk_holder
- if(ishuman(H) && !isipc(H) && H.job != "Curator")
+ if(ishuman(H) && H.job != "Curator")
H.remove_blocked_language(/datum/language/common)
H.remove_language(/datum/language/uncommon)
@@ -290,7 +307,7 @@
var/mob/living/carbon/human/H = quirk_holder
var/obj/item/clothing/glasses/regular/glasses = new(get_turf(H))
H.put_in_hands(glasses)
- H.equip_to_slot(glasses, SLOT_GLASSES)
+ H.equip_to_slot(glasses, ITEM_SLOT_EYES)
H.regenerate_icons() //this is to remove the inhand icon, which persists even if it's not in their hands
/datum/quirk/nyctophobia
@@ -303,8 +320,7 @@
if(H.dna.species.id in list("shadow", "nightmare"))
return //we're tied with the dark, so we don't get scared of it; don't cleanse outright to avoid cheese
var/turf/T = get_turf(quirk_holder)
- var/lums = T.get_lumcount()
- if(lums <= 0.2)
+ if(T.get_lumcount() <= 0.2)
if(quirk_holder.m_intent == MOVE_INTENT_RUN)
to_chat(quirk_holder, "Easy, easy, take it slow... you're in the dark...")
quirk_holder.toggle_move_intent()
@@ -419,11 +435,11 @@
lose_text = "You feel in tune with the world again."
medical_record_text = "Patient suffers from acute Reality Dissociation Syndrome and experiences vivid hallucinations."
-/datum/quirk/insanity/on_process()
+/datum/quirk/insanity/on_process(delta_time)
if(quirk_holder.reagents.has_reagent(/datum/reagent/toxin/mindbreaker, needs_metabolizing = TRUE))
quirk_holder.hallucination = 0
return
- if(prob(2)) //we'll all be mad soon enough
+ if(DT_PROB(2, delta_time)) //we'll all be mad soon enough
madness()
/datum/quirk/insanity/proc/madness()
@@ -444,18 +460,18 @@
medical_record_text = "Patient is usually anxious in social encounters and prefers to avoid them."
var/dumb_thing = TRUE
-/datum/quirk/social_anxiety/on_process()
+/datum/quirk/social_anxiety/on_process(delta_time)
var/nearby_people = 0
for(var/mob/living/carbon/human/H in oview(3, quirk_holder))
if(H.client)
nearby_people++
var/mob/living/carbon/human/H = quirk_holder
- if(prob(2 + nearby_people))
+ if(DT_PROB(2 + nearby_people, delta_time))
H.stuttering = max(3, H.stuttering)
- else if(prob(min(3, nearby_people)) && !H.silent)
+ else if(DT_PROB(min(3, nearby_people), delta_time) && !H.silent)
to_chat(H, "You retreat into yourself. You really don't feel up to talking.")
H.silent = max(10, H.silent)
- else if(prob(0.5) && dumb_thing)
+ else if(DT_PROB(0.5, delta_time) && dumb_thing)
to_chat(H, "You think of a dumb thing you said a long time ago and scream internally.")
dumb_thing = FALSE //only once per life
if(prob(1))
@@ -469,7 +485,7 @@
gain_text = "You suddenly feel the craving for drugs."
lose_text = "You feel like you should kick your drug habit."
medical_record_text = "Patient has a history of hard drugs."
- var/drug_list = list(/datum/reagent/drug/crank, /datum/reagent/drug/krokodil, /datum/reagent/medicine/morphine, /datum/reagent/drug/happiness, /datum/reagent/drug/methamphetamine) //List of possible IDs
+ var/drug_list = list(/datum/reagent/drug/crank, /datum/reagent/drug/krokodil, /datum/reagent/medicine/morphine, /datum/reagent/drug/happiness, /datum/reagent/drug/methamphetamine, /datum/reagent/drug/ketamine) //List of possible IDs
var/datum/reagent/reagent_type //!If this is defined, reagent_id will be unused and the defined reagent type will be instead.
var/datum/reagent/reagent_instance //! actual instanced version of the reagent
var/where_drug //! Where the drug spawned
@@ -500,9 +516,9 @@
if (accessory_type)
accessory_instance = new accessory_type(current_turf)
var/list/slots = list(
- "in your left pocket" = SLOT_L_STORE,
- "in your right pocket" = SLOT_R_STORE,
- "in your backpack" = SLOT_IN_BACKPACK
+ "in your left pocket" = ITEM_SLOT_LPOCKET,
+ "in your right pocket" = ITEM_SLOT_RPOCKET,
+ "in your backpack" = ITEM_SLOT_BACKPACK
)
where_drug = H.equip_in_one_of_slots(drug_instance, slots, FALSE) || "at your feet"
if (accessory_instance)
@@ -555,7 +571,7 @@
/datum/quirk/junkie/smoker/on_process()
. = ..()
var/mob/living/carbon/human/H = quirk_holder
- var/obj/item/I = H.get_item_by_slot(SLOT_WEAR_MASK)
+ var/obj/item/I = H.get_item_by_slot(ITEM_SLOT_MASK)
if (istype(I, /obj/item/clothing/mask/cigarette))
var/obj/item/storage/fancy/cigarettes/C = drug_container_type
if(istype(I, initial(C.spawn_type)))
@@ -563,6 +579,65 @@
return
SEND_SIGNAL(quirk_holder, COMSIG_ADD_MOOD_EVENT, "wrong_cigs", /datum/mood_event/wrong_brand)
+/datum/quirk/alcoholic
+ name = "Alcoholic"
+ desc = "You can't stand being sober."
+ value = -1
+ gain_text = "You could really go for a drink right about now."
+ lose_text = "You feel like you should quit drinking."
+ medical_record_text = "Patient is an alcohol abuser."
+ var/where_drink //Where the bottle spawned
+ var/drink_types = list(/obj/item/reagent_containers/food/drinks/bottle/ale,
+ /obj/item/reagent_containers/food/drinks/bottle/beer,
+ /obj/item/reagent_containers/food/drinks/bottle/gin,
+ /obj/item/reagent_containers/food/drinks/bottle/whiskey,
+ /obj/item/reagent_containers/food/drinks/bottle/vodka,
+ /obj/item/reagent_containers/food/drinks/bottle/rum,
+ /obj/item/reagent_containers/food/drinks/bottle/applejack)
+ var/need = 0 // How much they crave alcohol at the moment
+ var/tick_number = 0 // Keeping track of how many ticks have passed between a check
+ var/obj/item/reagent_containers/food/drinks/bottle/drink_instance
+
+/datum/quirk/alcoholic/on_spawn()
+ drink_instance = pick(drink_types)
+ drink_instance = new drink_instance()
+ var/list/slots = list("in your backpack" = ITEM_SLOT_BACKPACK)
+ var/mob/living/carbon/human/H = quirk_holder
+ where_drink = H.equip_in_one_of_slots(drink_instance, slots, FALSE) || "at your feet"
+
+/datum/quirk/alcoholic/post_add()
+ to_chat(quirk_holder, "There is a small bottle of [drink_instance] [where_drink]. You only have a single bottle, might have to find some more...")
+
+/datum/quirk/alcoholic/on_process()
+ if(tick_number >= 6) // how many ticks should pass between a check
+ tick_number = 0
+ var/mob/living/carbon/human/H = quirk_holder
+ if(H.drunkenness > 0) // If they're not drunk, need goes up. else they're satisfied
+ need = -15
+ else
+ need++
+
+ switch(need)
+ if(1 to 10)
+ SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "alcoholic", /datum/mood_event/withdrawal_light, "alcohol")
+ if(prob(5))
+ to_chat(H, "You could go for a drink right about now.")
+ if(10 to 20)
+ SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "alcoholic", /datum/mood_event/withdrawal_medium, "alcohol")
+ if(prob(5))
+ to_chat(H, "You feel like you need alcohol. You just can't stand being sober.")
+ if(20 to 30)
+ SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "alcoholic", /datum/mood_event/withdrawal_severe, "alcohol")
+ if(prob(5))
+ to_chat(H, "You have an intense craving for a drink.")
+ if(30 to INFINITY)
+ SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "alcoholic", /datum/mood_event/withdrawal_critical, "Alcohol")
+ if(prob(5))
+ to_chat(H, "You're not feeling good at all! You really need some alcohol.")
+ else
+ SEND_SIGNAL(H, COMSIG_CLEAR_MOOD_EVENT, "alcoholic")
+ tick_number++
+
/datum/quirk/unstable
name = "Unstable"
desc = "Due to past troubles, you are unable to recover your sanity if you lose it. Be very careful managing your mood!"
@@ -571,3 +646,20 @@
gain_text = "There's a lot on your mind right now."
lose_text = "Your mind finally feels calm."
medical_record_text = "Patient's mind is in a vulnerable state, and cannot recover from traumatic events."
+
+/datum/quirk/phobia
+ name = "Phobia"
+ desc = "Because of a traumatic event in your past you have developed a strong phobia."
+ value = -2
+ gain_text = "You start feeling an irrational fear of something."
+ lose_text = "You are no longer irrationally afraid."
+ medical_record_text = "Patient suffers from a deeply-rooted phobia."
+
+/datum/quirk/phobia/add()
+ var/datum/brain_trauma/mild/phobia/T = new()
+ var/mob/living/carbon/human/H = quirk_holder
+ H.gain_trauma(T, TRAUMA_RESILIENCE_ABSOLUTE)
+
+/datum/quirk/phobia/remove()
+ var/mob/living/carbon/human/H = quirk_holder
+ H.cure_trauma_type(/datum/brain_trauma/mild/phobia, TRAUMA_RESILIENCE_ABSOLUTE)
diff --git a/code/datums/traits/neutral.dm b/code/datums/traits/neutral.dm
index f16f2b3741793..beedf80064f51 100644
--- a/code/datums/traits/neutral.dm
+++ b/code/datums/traits/neutral.dm
@@ -29,7 +29,7 @@
var/datum/species/species = H.dna.species
if(initial(species.liked_food) & MEAT)
species.liked_food |= MEAT
- if(!initial(species.disliked_food) & MEAT)
+ if(!(initial(species.disliked_food) & MEAT))
species.disliked_food &= ~MEAT
/datum/quirk/pineapple_liker
@@ -54,7 +54,7 @@
name = "Ananas Aversion"
desc = "You find yourself greatly detesting fruits of the ananas genus. Serious, how the hell can anyone say these things are good? And what kind of madman would even dare putting it on a pizza!?"
value = 0
- gain_text = "You find yourself pondering what kind of idiot actually enjoys pineapples..."
+ gain_text = "You find yourself pondering what kind of idiot actually enjoys pineapples."
lose_text = "Your feelings towards pineapples seem to return to a lukewarm state."
/datum/quirk/pineapple_hater/add()
@@ -89,24 +89,6 @@
species.liked_food = initial(species.liked_food)
species.disliked_food = initial(species.disliked_food)
-/datum/quirk/neat
- name = "Neat"
- desc = "You really don't like being unhygienic, and will get sad if you are."
- mob_trait = TRAIT_NEAT
- gain_text = "You feel like you have to stay clean."
- lose_text = "You no longer feel the need to always be clean."
- mood_quirk = TRUE
-
-/datum/quirk/neat/on_process()
- var/mob/living/carbon/human/H = quirk_holder
- switch (H.hygiene)
- if(0 to HYGIENE_LEVEL_DIRTY)
- SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "neat", /datum/mood_event/dirty)
- if(HYGIENE_LEVEL_DIRTY to HYGIENE_LEVEL_NORMAL)
- SEND_SIGNAL(H, COMSIG_CLEAR_MOOD_EVENT, "neat")
- if(HYGIENE_LEVEL_NORMAL to HYGIENE_LEVEL_CLEAN)
- SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "neat", /datum/mood_event/neat)
-
/datum/quirk/monochromatic
name = "Monochromacy"
desc = "You suffer from full colorblindness, and perceive nearly the entire world in blacks and whites."
@@ -118,7 +100,7 @@
/datum/quirk/monochromatic/post_add()
if(quirk_holder.mind.assigned_role == "Detective")
- to_chat(quirk_holder, "Mmm. Nothing's ever clear on this station. It's all shades of gray...")
+ to_chat(quirk_holder, "Mmm. Nothing's ever clear on this station. It's all shades of gray.")
quirk_holder.playsound_local(quirk_holder, 'sound/ambience/ambidet1.ogg', 50, FALSE)
/datum/quirk/monochromatic/remove()
diff --git a/code/datums/verbs.dm b/code/datums/verbs.dm
deleted file mode 100644
index 8f79ed5eec34b..0000000000000
--- a/code/datums/verbs.dm
+++ /dev/null
@@ -1,102 +0,0 @@
-/datum/verbs
- var/name
- var/list/children
- var/datum/verbs/parent
- var/list/verblist
- var/abstract = FALSE
-
-//returns the master list for verbs of a type
-/datum/verbs/proc/GetList()
- CRASH("Abstract verblist for [type]")
-
-//do things for each entry in Generate_list
-//return value sets Generate_list[verbpath]
-/datum/verbs/proc/HandleVerb(list/entry, procpath/verbpath, ...)
- return entry
-
-/datum/verbs/New()
- var/mainlist = GetList()
- var/ourentry = mainlist[type]
- children = list()
- verblist = list()
- if (ourentry)
- if (!islist(ourentry)) //some of our childern already loaded
- qdel(src)
- CRASH("Verb double load: [type]")
- Add_children(ourentry)
-
- mainlist[type] = src
-
- Load_verbs(type, typesof("[type]/verb"))
-
- var/datum/verbs/parent = mainlist[parent_type]
- if (!parent)
- mainlist[parent_type] = list(src)
- else if (islist(parent))
- parent += src
- else
- parent.Add_children(list(src))
-
-/datum/verbs/proc/Set_parent(datum/verbs/_parent)
- parent = _parent
- if (abstract)
- parent.Add_children(children)
- var/list/verblistoftypes = list()
- for(var/thing in verblist)
- LAZYADD(verblistoftypes[verblist[thing]], thing)
-
- for(var/verbparenttype in verblistoftypes)
- parent.Load_verbs(verbparenttype, verblistoftypes[verbparenttype])
-
-/datum/verbs/proc/Add_children(list/kids)
- if (abstract && parent)
- parent.Add_children(kids)
- return
-
- for(var/thing in kids)
- var/datum/verbs/item = thing
- item.Set_parent(src)
- if (!item.abstract)
- children += item
-
-/datum/verbs/proc/Load_verbs(verb_parent_type, list/verbs)
- if (abstract && parent)
- parent.Load_verbs(verb_parent_type, verbs)
- return
-
- for (var/verbpath in verbs)
- verblist[verbpath] = verb_parent_type
-
-/datum/verbs/proc/Generate_list(...)
- . = list()
- if (length(children))
- for (var/thing in children)
- var/datum/verbs/child = thing
- var/list/childlist = child.Generate_list(arglist(args))
- if (childlist)
- var/childname = "[child]"
- if (childname == "[child.type]")
- var/list/tree = splittext(childname, "/")
- childname = tree[tree.len]
- .[child.type] = "parent=[rustg_url_encode(type)];name=[childname]"
- . += childlist
-
- for (var/thing in verblist)
- var/procpath/verbpath = thing
- if (!verbpath)
- stack_trace("Bad VERB in [type] verblist: [english_list(verblist)]")
- var/list/entry = list()
- entry["parent"] = "[type]"
- entry["name"] = verbpath.desc
- if (verbpath.name[1] == "@")
- entry["command"] = copytext(verbpath.name, length(verbpath.name[1]) + 1)
- else
- entry["command"] = replacetext(verbpath.name, " ", "-")
-
- .[verbpath] = HandleVerb(arglist(list(entry, verbpath) + args))
-
-/world/proc/LoadVerbs(verb_type)
- if(!ispath(verb_type, /datum/verbs) || verb_type == /datum/verbs)
- CRASH("Invalid verb_type: [verb_type]")
- for (var/typepath in subtypesof(verb_type))
- new typepath()
diff --git a/code/datums/view.dm b/code/datums/view.dm
index 7d425c9ad8505..ea695b50dee55 100644
--- a/code/datums/view.dm
+++ b/code/datums/view.dm
@@ -34,9 +34,11 @@
/datum/viewData/proc/isZooming()
return (width || height)
-/datum/viewData/proc/resetToDefault()
+/datum/viewData/proc/resetToDefault(var/new_default)
width = 0
height = 0
+ if(new_default != null)
+ default = new_default
apply()
/datum/viewData/proc/add(toAdd)
diff --git a/code/datums/weather/weather_types/radiation_storm.dm b/code/datums/weather/weather_types/radiation_storm.dm
index d0fff8da9fe3d..6ab9cbadcee3c 100644
--- a/code/datums/weather/weather_types/radiation_storm.dm
+++ b/code/datums/weather/weather_types/radiation_storm.dm
@@ -47,7 +47,7 @@
/datum/weather/rad_storm/end()
if(..())
return
- priority_announce("The radiation threat has passed. Please return to your workplaces.", "Anomaly Alert")
+ priority_announce("The radiation threat has passed. Please return to your workplaces.", "Anomaly Alert", SSstation.announcer.get_rand_alert_sound())
status_alarm(FALSE)
/datum/weather/rad_storm/proc/status_alarm(active) //Makes the status displays show the radiation warning for those who missed the announcement.
diff --git a/code/datums/wires/_wires.dm b/code/datums/wires/_wires.dm
index 511847484ccf7..8c1f04e869a82 100644
--- a/code/datums/wires/_wires.dm
+++ b/code/datums/wires/_wires.dm
@@ -90,9 +90,11 @@
/datum/wires/proc/shuffle_wires()
colors.Cut()
randomize()
+ ui_update()
/datum/wires/proc/repair()
cut_wires.Cut()
+ ui_update()
/datum/wires/proc/get_wire(color)
return colors[color]
@@ -135,29 +137,36 @@
else
cut_wires += wire
on_cut(wire, mend = FALSE)
+ ui_update()
/datum/wires/proc/cut_color(color)
cut(get_wire(color))
+ ui_update()
/datum/wires/proc/cut_random()
cut(wires[rand(1, wires.len)])
+ ui_update()
/datum/wires/proc/cut_all()
for(var/wire in wires)
cut(wire)
+ ui_update()
/datum/wires/proc/pulse(wire, user)
if(is_cut(wire))
return
on_pulse(wire, user)
+ ui_update()
/datum/wires/proc/pulse_color(color, mob/living/user)
pulse(get_wire(color), user)
+ ui_update()
/datum/wires/proc/pulse_assembly(obj/item/assembly/S)
for(var/color in assemblies)
if(S == assemblies[color])
pulse_color(color)
+ ui_update()
return TRUE
/datum/wires/proc/attach_assembly(color, obj/item/assembly/S)
@@ -165,6 +174,7 @@
assemblies[color] = S
S.forceMove(holder)
S.connected = src
+ ui_update()
return S
/datum/wires/proc/detach_assembly(color)
@@ -173,6 +183,7 @@
assemblies -= color
S.connected = null
S.forceMove(holder.drop_location())
+ ui_update()
return S
/datum/wires/proc/emp_pulse()
@@ -185,6 +196,7 @@
remaining_pulses--
if(!remaining_pulses)
break
+ ui_update()
// Overridable Procs
/datum/wires/proc/interactable(mob/user)
@@ -217,6 +229,7 @@
return ..()
return UI_CLOSE
+
/datum/wires/ui_state(mob/user)
return GLOB.physical_state
diff --git a/code/datums/wires/airalarm.dm b/code/datums/wires/airalarm.dm
index 7acfdf76bc203..d11cf41bd8ea5 100644
--- a/code/datums/wires/airalarm.dm
+++ b/code/datums/wires/airalarm.dm
@@ -54,7 +54,7 @@
A.apply_mode(usr)
if(WIRE_ALARM) // Clear alarms.
var/area/AA = get_area(A)
- if(AA.atmosalert(0, holder))
+ if(AA.atmosalert(FALSE, holder))
A.post_alert(0)
A.update_icon()
@@ -76,6 +76,6 @@
A.apply_mode(usr)
if(WIRE_ALARM) // Post alarm.
var/area/AA = get_area(A)
- if(AA.atmosalert(2, holder))
+ if(AA.atmosalert(TRUE, holder))
A.post_alert(2)
A.update_icon()
diff --git a/code/datums/wires/airlock.dm b/code/datums/wires/airlock.dm
index f06bba6486714..eafdef8eff45f 100644
--- a/code/datums/wires/airlock.dm
+++ b/code/datums/wires/airlock.dm
@@ -69,12 +69,7 @@
A.aiControlDisabled = 1
else if(A.aiControlDisabled == -1)
A.aiControlDisabled = 2
- sleep(10)
- if(A)
- if(A.aiControlDisabled == 1)
- A.aiControlDisabled = 0
- else if(A.aiControlDisabled == 2)
- A.aiControlDisabled = -1
+ addtimer(CALLBACK(A, /obj/machinery/door/airlock.proc/reset_ai_wire), 1 SECONDS)
if(WIRE_SHOCK) // Pulse to shock the door for 10 ticks.
if(!A.secondsElectrified)
A.set_electrified(MACHINE_DEFAULT_ELECTRIFY_TIME, usr)
@@ -87,6 +82,16 @@
if(WIRE_LIGHT)
A.lights = !A.lights
A.update_icon()
+ ui_update()
+ A.ui_update()
+
+/obj/machinery/door/airlock/proc/reset_ai_wire()
+ if(aiControlDisabled == 1)
+ aiControlDisabled = 0
+ else if(aiControlDisabled == 2)
+ aiControlDisabled = -1
+ wires.ui_update()
+ ui_update()
/datum/wires/airlock/on_cut(wire, mend)
var/obj/machinery/door/airlock/A = holder
@@ -138,3 +143,5 @@
if(WIRE_ZAP1, WIRE_ZAP2) // Ouch.
if(isliving(usr))
A.shock(usr, 50)
+ ui_update()
+ A.ui_update()
diff --git a/code/datums/wires/apc.dm b/code/datums/wires/apc.dm
index dc126f612e120..b4c9d22c8c207 100644
--- a/code/datums/wires/apc.dm
+++ b/code/datums/wires/apc.dm
@@ -37,6 +37,7 @@
if(!A.aidisabled)
A.aidisabled = TRUE
addtimer(CALLBACK(A, /obj/machinery/power/apc.proc/reset, wire), 10)
+ ui_update()
/datum/wires/apc/on_cut(index, mend)
var/obj/machinery/power/apc/A = holder
@@ -52,4 +53,5 @@
if(mend)
A.aidisabled = FALSE
else
- A.aidisabled = TRUE
\ No newline at end of file
+ A.aidisabled = TRUE
+ ui_update()
diff --git a/code/datums/wires/autolathe.dm b/code/datums/wires/autolathe.dm
index 8b113adad1f25..1e5cd1017d11b 100644
--- a/code/datums/wires/autolathe.dm
+++ b/code/datums/wires/autolathe.dm
@@ -1,5 +1,5 @@
/datum/wires/autolathe
- holder_type = /obj/machinery/autolathe
+ holder_type = /obj/machinery/modular_fabricator/autolathe
proper_name = "Autolathe"
/datum/wires/autolathe/New(atom/holder)
@@ -11,34 +11,35 @@
..()
/datum/wires/autolathe/interactable(mob/user)
- var/obj/machinery/autolathe/A = holder
+ var/obj/machinery/modular_fabricator/autolathe/A = holder
if(A.panel_open)
return TRUE
/datum/wires/autolathe/get_status()
- var/obj/machinery/autolathe/A = holder
+ var/obj/machinery/modular_fabricator/autolathe/A = holder
var/list/status = list()
status += "The red light is [A.disabled ? "on" : "off"]."
status += "The blue light is [A.hacked ? "on" : "off"]."
return status
/datum/wires/autolathe/on_pulse(wire)
- var/obj/machinery/autolathe/A = holder
+ var/obj/machinery/modular_fabricator/autolathe/A = holder
switch(wire)
if(WIRE_HACK)
A.adjust_hacked(!A.hacked)
- addtimer(CALLBACK(A, /obj/machinery/autolathe.proc/reset, wire), 60)
+ addtimer(CALLBACK(A, /obj/machinery/modular_fabricator/autolathe.proc/reset, wire), 60)
if(WIRE_SHOCK)
A.shocked = !A.shocked
- addtimer(CALLBACK(A, /obj/machinery/autolathe.proc/reset, wire), 60)
+ addtimer(CALLBACK(A, /obj/machinery/modular_fabricator/autolathe.proc/reset, wire), 60)
if(WIRE_DISABLE)
A.disabled = !A.disabled
- addtimer(CALLBACK(A, /obj/machinery/autolathe.proc/reset, wire), 60)
+ addtimer(CALLBACK(A, /obj/machinery/modular_fabricator/autolathe.proc/reset, wire), 60)
if(WIRE_ACTIVATE)
A.begin_process()
+ ui_update()
/datum/wires/autolathe/on_cut(wire, mend)
- var/obj/machinery/autolathe/A = holder
+ var/obj/machinery/modular_fabricator/autolathe/A = holder
switch(wire)
if(WIRE_HACK)
A.adjust_hacked(!mend)
@@ -48,3 +49,4 @@
A.disabled = !mend
if(WIRE_ZAP)
A.shock(usr, 50)
+ ui_update()
diff --git a/code/datums/wires/emitter.dm b/code/datums/wires/emitter.dm
index d0e1352c0efa4..a157230f07f63 100644
--- a/code/datums/wires/emitter.dm
+++ b/code/datums/wires/emitter.dm
@@ -14,4 +14,3 @@
if(WIRE_HACK)
E.mode = !E.mode
E.set_projectile()
- ..()
diff --git a/code/datums/wires/explosive.dm b/code/datums/wires/explosive.dm
index 8616478010510..518d3e6c58712 100644
--- a/code/datums/wires/explosive.dm
+++ b/code/datums/wires/explosive.dm
@@ -43,7 +43,7 @@
message_admins("\An [assembly] has pulsed a grenade, which was installed by [fingerprint].")
log_game("\An [assembly] has pulsed a grenade, which was installed by [fingerprint].")
var/mob/M = get_mob_by_ckey(fingerprint)
- var/turf/T = get_turf(M)
+ var/turf/T = get_turf(M)
G.log_grenade(M, T)
G.prime()
@@ -99,6 +99,7 @@
switch(wire)
if(WIRE_DISARM) // Pulse to toggle
P.bomb_defused = !P.bomb_defused
+ ui_update()
else // Boom
explode()
@@ -108,6 +109,7 @@
if(WIRE_DISARM) // Disarm and untrap the box.
if(!mend)
P.bomb_defused = TRUE
+ ui_update()
else
if(!mend && !P.bomb_defused)
explode()
@@ -116,10 +118,9 @@
var/obj/item/pizzabox/P = holder
P.bomb.detonate()
-
/datum/wires/explosive/gibtonite
- holder_type = /obj/item/twohanded/required/gibtonite
+ holder_type = /obj/item/gibtonite
/datum/wires/explosive/gibtonite/explode()
- var/obj/item/twohanded/required/gibtonite/P = holder
+ var/obj/item/gibtonite/P = holder
P.GibtoniteReaction(null, 2)
diff --git a/code/datums/wires/r_n_d.dm b/code/datums/wires/r_n_d.dm
index ea61c1779ef4e..7aaad875faa59 100644
--- a/code/datums/wires/r_n_d.dm
+++ b/code/datums/wires/r_n_d.dm
@@ -30,6 +30,7 @@
R.hacked = !R.hacked
if(WIRE_DISABLE)
R.disabled = !R.disabled
+ ui_update()
/datum/wires/rnd/on_cut(wire, mend)
var/obj/machinery/rnd/R = holder
switch(wire)
@@ -37,3 +38,4 @@
R.hacked = !mend
if(WIRE_DISABLE)
R.disabled = !mend
+ ui_update()
diff --git a/code/datums/wires/robot.dm b/code/datums/wires/robot.dm
index 46fb8200ea63b..5ad295d80a271 100644
--- a/code/datums/wires/robot.dm
+++ b/code/datums/wires/robot.dm
@@ -38,6 +38,7 @@
new_ai = select_active_ai(R)
R.notify_ai(DISCONNECT)
if(new_ai && (new_ai != R.connected_ai))
+ log_combat(usr, R, "synced cyborg [R.connected_ai ? "from [ADMIN_LOOKUP(R.connected_ai)]": "false"] to [ADMIN_LOOKUP(new_ai)]")
R.connected_ai = new_ai
if(R.shell)
R.undeploy() //If this borg is an AI shell, disconnect the controlling AI and assign ti to a new AI
@@ -46,18 +47,24 @@
R.notify_ai(TRUE)
if(WIRE_CAMERA) // Pulse to disable the camera.
if(!QDELETED(R.builtInCamera) && !R.scrambledcodes)
- R.builtInCamera.toggle_cam(usr, 0)
+ R.builtInCamera.toggle_cam(usr, FALSE)
R.visible_message("[R]'s camera lens focuses loudly.", "Your camera lens focuses loudly.")
+ log_combat(usr, R, "toggled cyborg camera to [R.builtInCamera.status ? "on" : "off"] via pulse")
if(WIRE_LAWSYNC) // Forces a law update if possible.
if(R.lawupdate)
R.visible_message("[R] gently chimes.", "LawSync protocol engaged.")
+ log_combat(usr, R, "forcibly synced cyborg laws via pulse")
+ // TODO, log the laws they gained here
R.lawsync()
R.show_laws()
if(WIRE_LOCKDOWN)
R.SetLockdown(!R.lockcharge) // Toggle
+ log_combat(usr, R, "[!R.lockcharge ? "locked down" : "released"] via pulse")
+
if(WIRE_RESET_MODULE)
if(R.has_module())
R.visible_message("[R]'s module servos twitch.", "Your module display flickers.")
+ ui_update()
/datum/wires/robot/on_cut(wire, mend)
var/mob/living/silicon/robot/R = holder
@@ -65,6 +72,7 @@
if(WIRE_AI) // Cut the AI wire to reset AI control.
if(!mend)
R.notify_ai(DISCONNECT)
+ log_combat(usr, R, "cut AI wire on cyborg[R.connected_ai ? " and disconnected from [ADMIN_LOOKUP(R.connected_ai)]": ""]")
if(R.shell)
R.undeploy()
R.connected_ai = null
@@ -72,15 +80,21 @@
if(mend)
if(!R.emagged)
R.lawupdate = TRUE
+ log_combat(usr, R, "enabled lawsync via wire")
else if(!R.deployed) //AI shells must always have the same laws as the AI
R.lawupdate = FALSE
+ log_combat(usr, R, "disabled lawsync via wire")
if (WIRE_CAMERA) // Disable the camera.
if(!QDELETED(R.builtInCamera) && !R.scrambledcodes)
R.builtInCamera.status = mend
- R.builtInCamera.toggle_cam(usr, 0)
+ R.builtInCamera.toggle_cam(usr, FALSE)
R.visible_message("[R]'s camera lens focuses loudly.", "Your camera lens focuses loudly.")
+ log_combat(usr, R, "[mend ? "enabled" : "disabled"] cyborg camera via wire")
if(WIRE_LOCKDOWN) // Simple lockdown.
R.SetLockdown(!mend)
+ log_combat(usr, R, "[!R.lockcharge ? "locked down" : "released"] via wire")
if(WIRE_RESET_MODULE)
if(R.has_module() && !mend)
R.ResetModule()
+ log_combat(usr, R, "reset the cyborg module via wire")
+ ui_update()
diff --git a/code/datums/wires/suit_storage_unit.dm b/code/datums/wires/suit_storage_unit.dm
index eb7781203b2b5..7b165622effec 100644
--- a/code/datums/wires/suit_storage_unit.dm
+++ b/code/datums/wires/suit_storage_unit.dm
@@ -32,6 +32,7 @@
if(WIRE_ZAP)
if(usr)
SSU.shock(usr)
+ ui_update()
/datum/wires/suit_storage_unit/on_cut(wire, mend)
var/obj/machinery/suit_storage_unit/SSU = holder
@@ -43,3 +44,4 @@
if(WIRE_ZAP)
if(usr)
SSU.shock(usr)
+ ui_update()
diff --git a/code/datums/wires/vending.dm b/code/datums/wires/vending.dm
index 078c940b041fd..c703b88105ef8 100644
--- a/code/datums/wires/vending.dm
+++ b/code/datums/wires/vending.dm
@@ -40,6 +40,7 @@
V.scan_id = !V.scan_id
if(WIRE_SPEAKER)
V.shut_up = !V.shut_up
+ ui_update()
/datum/wires/vending/on_cut(wire, mend)
var/obj/machinery/vending/V = holder
@@ -57,3 +58,4 @@
V.scan_id = mend
if(WIRE_SPEAKER)
V.shut_up = mend
+ ui_update()
diff --git a/code/datums/world_topic.dm b/code/datums/world_topic.dm
index 9580dbb63a6b8..5b86aac25c5aa 100644
--- a/code/datums/world_topic.dm
+++ b/code/datums/world_topic.dm
@@ -1,198 +1,295 @@
-// SETUP
-
-/proc/TopicHandlers()
- . = list()
- var/list/all_handlers = subtypesof(/datum/world_topic)
- for(var/I in all_handlers)
- var/datum/world_topic/WT = I
- var/keyword = initial(WT.keyword)
- if(!keyword)
- warning("[WT] has no keyword! Ignoring...")
- continue
- var/existing_path = .[keyword]
- if(existing_path)
- warning("[existing_path] and [WT] have the same keyword! Ignoring [WT]...")
- else if(keyword == "key")
- warning("[WT] has keyword 'key'! Ignoring...")
- else
- .[keyword] = WT
+// VERSION
+
+// Update topic version whenever changes are made
+// The Version Number follows SemVer http://semver.org/
+#define TOPIC_VERSION_MAJOR 2 // Major Version Number --> Increment when implementing breaking changes
+#define TOPIC_VERSION_MINOR 0 // Minor Version Number --> Increment when adding features
+#define TOPIC_VERSION_PATCH 0 // Patchlevel --> Increment when fixing bugs
// DATUM
/datum/world_topic
- var/keyword
- var/log = TRUE
- var/key_valid
- var/require_comms_key = FALSE
-
-/datum/world_topic/proc/TryRun(list/input, addr)
- key_valid = config && (CONFIG_GET(string/comms_key) == input["key"])
- if(require_comms_key && !key_valid)
- return "Bad Key"
- input -= "key"
- . = Run(input, addr)
- if(islist(.))
- . = list2params(.)
-
-/datum/world_topic/proc/Run(list/input, addr)
- CRASH("Run() not implemented for [type]!")
+ var/key // query key
+ var/anonymous = FALSE // can be used with anonymous authentication
+ var/list/required_params = list()
+ var/statuscode = null
+ var/response = null
+ var/data = null
+
+/datum/world_topic/proc/CheckParams(list/params)
+ var/list/missing_params = list()
+ var/errorcount = 0
+
+ for(var/param in required_params)
+ if(!params[param])
+ errorcount++
+ missing_params += param
+
+ if(errorcount)
+ statuscode = 400
+ response = "Bad Request - Missing parameters"
+ data = missing_params
+ return errorcount
+
+/datum/world_topic/proc/Run(list/input)
+ // Always returns true; actual details in statuscode, response and data variables
+ return TRUE
+
+// API INFO TOPICS
+
+/datum/world_topic/api_get_version
+ key = "api_get_version"
+ anonymous = TRUE
+
+/datum/world_topic/api_get_version/Run(list/input)
+ . = ..()
+ var/list/version = list()
+ var/versionstring = null
+
+ version["major"] = TOPIC_VERSION_MAJOR
+ version["minor"] = TOPIC_VERSION_MINOR
+ version["patch"] = TOPIC_VERSION_PATCH
+
+ versionstring = "[version["major"]].[version["minor"]].[version["patch"]]"
+
+ statuscode = 200
+ response = versionstring
+ data = version
+
+/datum/world_topic/api_get_authed_functions
+ key = "api_get_authed_functions"
+ anonymous = TRUE
+
+/datum/world_topic/api_get_authed_functions/Run(list/input)
+ . = ..()
+ var/list/functions = GLOB.topic_tokens[input["auth"]]
+ if(functions)
+ statuscode = 200
+ response = "Authorized functions retrieved"
+ data = functions
+ else
+ statuscode = 401
+ response = "Unauthorized - No functions found"
+ data = null
+
+/datum/world_topic/api_do_handshake
+ key = "api_do_handshake"
+ anonymous = TRUE
+
+/datum/world_topic/api_do_handshake/Run(list/input)
+ . = ..()
+ var/list/functions = GLOB.topic_tokens[input["auth"]]
+ var/list/servers = CONFIG_GET(keyed_list/cross_server)
+ var/fmt_addr = "byond://[input["addr"]]"
+ var/token = servers[fmt_addr]
+ if(!token || !functions) // Handshake requires both servers to have each other's deets
+ statuscode = 401
+ response = "Unauthorized - Handshake Failed"
+ data = null
+ else
+ statuscode = 200
+ response = "Handshake Successful"
+ data = list("token" = token, "functions" = functions)
+ if(!GLOB.topic_servers[fmt_addr]) // part of the ad-hoc connection system
+ SStopic.handshake_server(fmt_addr, token)
// TOPICS
/datum/world_topic/ping
- keyword = "ping"
- log = FALSE
+ key = "ping"
+ anonymous = TRUE
-/datum/world_topic/ping/Run(list/input, addr)
- . = 0
- for (var/client/C in GLOB.clients)
- ++.
+/datum/world_topic/ping/Run(list/input)
+ . = ..()
+ statuscode = 200
+ response = "Pong!"
+ data = length(GLOB.clients)
/datum/world_topic/playing
- keyword = "playing"
- log = FALSE
+ key = "playing"
+ anonymous = TRUE
-/datum/world_topic/playing/Run(list/input, addr)
- return GLOB.player_list.len
+/datum/world_topic/playing/Run(list/input)
+ . = ..()
+ statuscode = 200
+ response = "Player count retrieved"
+ data = length(GLOB.player_list)
/datum/world_topic/pr_announce
- keyword = "announce"
- require_comms_key = TRUE
+ key = "announce"
+ required_params = list("id", "announce")
var/static/list/PRcounts = list() //PR id -> number of times announced this round
-/datum/world_topic/pr_announce/Run(list/input, addr)
- var/list/payload = json_decode(input["payload"])
- var/id = "[payload["pull_request"]["id"]]"
- if(!PRcounts[id])
- PRcounts[id] = 1
+/datum/world_topic/pr_announce/Run(list/input)
+ . = ..()
+ if(!PRcounts[input["id"]])
+ PRcounts[input["id"]] = 1
else
- ++PRcounts[id]
- if(PRcounts[id] > PR_ANNOUNCEMENTS_PER_ROUND)
+ PRcounts[input["id"]]++
+ if(PRcounts[input["id"]] > PR_ANNOUNCEMENTS_PER_ROUND)
+ statuscode = 429
+ response = "Rate Limited - PR Spam blocked"
return
- var/final_composed = "PR: [input[keyword]]"
+ var/final_composed = "PR: [input["announce"]]"
for(var/client/C in GLOB.clients)
C.AnnouncePR(final_composed)
+ statuscode = 200
+ response = "PR Announced"
/datum/world_topic/ahelp_relay
- keyword = "Ahelp"
- require_comms_key = TRUE
+ key = "ahelp"
+ required_params = list("source", "message", "message_sender")
-/datum/world_topic/ahelp_relay/Run(list/input, addr)
- relay_msg_admins("HELP: [input["source"]] [input["message_sender"]]: [input["message"]]")
+/datum/world_topic/ahelp_relay/Run(list/input)
+ . = ..()
+ relay_msg_admins("HELP: [input["source"]]: [input["message_sender"]]: [input["message"]]")
+ statuscode = 200
+ response = "Ahelp relayed"
/datum/world_topic/comms_console
- keyword = "Comms_Console"
- require_comms_key = TRUE
+ key = "comms_console"
+ required_params = list("message", "message_sender")
+
+/datum/world_topic/comms_console/Run(list/input)
+ . = ..()
+ if(CHAT_FILTER_CHECK(input["message"])) // prevents any.. diplomatic incidents
+ minor_announce("In the interest of station productivity and mental hygiene, a message from [input["message_sender"]] was intercepted by the CCC and determined to be unfit for crew-level access.", "CentCom Communications Commission")
+ message_admins("Incomming cross-comms message from [input["message_sender"]] blocked: [input["message"]]")
+ statuscode = 451 // "Unavailable for legal reasons" ahaha; i.e. censored
+ response = "Censored - Message blocked by chat filter"
+ return
-/datum/world_topic/comms_console/Run(list/input, addr)
minor_announce(input["message"], "Incoming message from [input["message_sender"]]")
for(var/obj/machinery/computer/communications/CM in GLOB.machines)
CM.overrideCooldown()
+ statuscode = 200
+ response = "Message received"
/datum/world_topic/news_report
- keyword = "News_Report"
- require_comms_key = TRUE
+ key = "news_report"
+ required_params = list("message", "message_sender")
-/datum/world_topic/news_report/Run(list/input, addr)
+/datum/world_topic/news_report/Run(list/input)
+ . = ..()
minor_announce(input["message"], "Breaking Update From [input["message_sender"]]")
-
-/datum/world_topic/adminmsg
- keyword = "adminmsg"
- require_comms_key = TRUE
-
-/datum/world_topic/adminmsg/Run(list/input, addr)
- return IrcPm(input[keyword], input["msg"], input["sender"])
+ statuscode = 200
+ response = "Message received"
/datum/world_topic/namecheck
- keyword = "namecheck"
- require_comms_key = TRUE
+ key = "namecheck"
+ required_params = list("target")
-/datum/world_topic/namecheck/Run(list/input, addr)
- //Oh this is a hack, someone refactor the functionality out of the chat command PLS
- var/datum/tgs_chat_command/namecheck/NC = new
- var/datum/tgs_chat_user/user = new
- user.friendly_name = input["sender"]
- user.mention = user.friendly_name
- return NC.Run(user, input["namecheck"])
+/datum/world_topic/namecheck/Run(list/input)
+ . = ..()
+ statuscode = 200
+ response = "Names fetched"
+ data = keywords_lookup(input["target"], TRUE)
/datum/world_topic/adminwho
- keyword = "adminwho"
- require_comms_key = TRUE
-
-/datum/world_topic/adminwho/Run(list/input, addr)
- return ircadminwho()
+ key = "adminwho"
+
+/datum/world_topic/adminwho/Run(list/input)
+ . = ..()
+ var/list/admins = list()
+ for(var/client/admin in GLOB.admins)
+ admins[++admins.len] = list("ckey" = admin.ckey,
+ "key" = admin.key,
+ "rank" = admin.holder.rank.name,
+ "stealth" = admin.holder.fakekey ? TRUE : FALSE,
+ "afk" = admin.is_afk())
+ statuscode = 200
+ response = "Admin list fetched"
+ data = admins
+
+/datum/world_topic/playerlist
+ key = "playerlist"
+ anonymous = TRUE
+
+/datum/world_topic/playerlist/Run(list/input)
+ . = ..()
+ data = list()
+ for(var/client/C as() in GLOB.clients)
+ data += C.ckey
+ statuscode = 200
+ response = "Player list fetched"
/datum/world_topic/status
- keyword = "status"
-
-/datum/world_topic/status/Run(list/input, addr)
- . = list()
- .["version"] = GLOB.game_version
- .["mode"] = GLOB.master_mode
- .["respawn"] = config ? !CONFIG_GET(flag/norespawn) : FALSE
- .["enter"] = GLOB.enter_allowed
- .["vote"] = CONFIG_GET(flag/allow_vote_mode)
- .["ai"] = CONFIG_GET(flag/allow_ai)
- .["host"] = world.host ? world.host : null
- .["round_id"] = GLOB.round_id
- .["players"] = GLOB.clients.len
- .["revision"] = GLOB.revdata.commit
- .["revision_date"] = GLOB.revdata.date
- .["hub"] = GLOB.hub_visibility
-
- var/client_num = 0
- for(var/client/C in GLOB.clients)
- .["client[client_num]"] = C.key
- client_num++
+ key = "status"
+ anonymous = TRUE
+
+/datum/world_topic/status/Run(list/input)
+ . = ..()
+ data = list()
+ data["version"] = GLOB.game_version
+ data["mode"] = GLOB.master_mode
+ data["respawn"] = config ? !CONFIG_GET(flag/norespawn) : FALSE
+ data["enter"] = GLOB.enter_allowed
+ data["vote"] = CONFIG_GET(flag/allow_vote_mode)
+ data["ai"] = CONFIG_GET(flag/allow_ai)
+ data["host"] = world.host ? world.host : null
+ data["round_id"] = text2num(GLOB.round_id) // I don't know who's fault it is that round id is loaded as a string but screw you
+ data["players"] = GLOB.clients.len
+ data["revision"] = GLOB.revdata.commit
+ data["revision_date"] = GLOB.revdata.date
+ data["hub"] = GLOB.hub_visibility
var/list/adm = get_admin_counts()
var/list/presentmins = adm["present"]
var/list/afkmins = adm["afk"]
- .["admins"] = presentmins.len + afkmins.len //equivalent to the info gotten from adminwho
- .["gamestate"] = SSticker.current_state
-
- .["map_name"] = SSmapping.config?.map_name || "Loading..."
+ data["admins"] = presentmins.len + afkmins.len //equivalent to the info gotten from adminwho
+ data["gamestate"] = SSticker.current_state
- if(key_valid)
- .["active_players"] = get_active_player_count()
- if(SSticker.HasRoundStarted())
- .["real_mode"] = SSticker.mode.name
- // Key-authed callers may know the truth behind the "secret"
+ data["map_name"] = SSmapping.config?.map_name || "Loading..."
- .["security_level"] = get_security_level()
- .["round_duration"] = SSticker ? round((world.time-SSticker.round_start_time)/10) : 0
+ data["security_level"] = get_security_level()
+ data["round_duration"] = SSticker ? round((world.time-SSticker.round_start_time)/10) : 0
// Amount of world's ticks in seconds, useful for calculating round duration
//Time dilation stats.
- .["time_dilation_current"] = SStime_track.time_dilation_current
- .["time_dilation_avg"] = SStime_track.time_dilation_avg
- .["time_dilation_avg_slow"] = SStime_track.time_dilation_avg_slow
- .["time_dilation_avg_fast"] = SStime_track.time_dilation_avg_fast
+ data["time_dilation_current"] = SStime_track.time_dilation_current
+ data["time_dilation_avg"] = SStime_track.time_dilation_avg
+ data["time_dilation_avg_slow"] = SStime_track.time_dilation_avg_slow
+ data["time_dilation_avg_fast"] = SStime_track.time_dilation_avg_fast
//pop cap stats
- .["soft_popcap"] = CONFIG_GET(number/soft_popcap) || 0
- .["hard_popcap"] = CONFIG_GET(number/hard_popcap) || 0
- .["extreme_popcap"] = CONFIG_GET(number/extreme_popcap) || 0
- .["popcap"] = max(CONFIG_GET(number/soft_popcap), CONFIG_GET(number/hard_popcap), CONFIG_GET(number/extreme_popcap)) //generalized field for this concept for use across ss13 codebases
+ data["soft_popcap"] = CONFIG_GET(number/soft_popcap) || 0
+ data["hard_popcap"] = CONFIG_GET(number/hard_popcap) || 0
+ data["extreme_popcap"] = CONFIG_GET(number/extreme_popcap) || 0
+ data["popcap"] = max(CONFIG_GET(number/soft_popcap), CONFIG_GET(number/hard_popcap), CONFIG_GET(number/extreme_popcap)) //generalized field for this concept for use across ss13 codebases
if(SSshuttle?.emergency)
- .["shuttle_mode"] = SSshuttle.emergency.mode
+ data["shuttle_mode"] = SSshuttle.emergency.mode
// Shuttle status, see /__DEFINES/stat.dm
- .["shuttle_timer"] = SSshuttle.emergency.timeLeft()
+ data["shuttle_timer"] = SSshuttle.emergency.timeLeft()
// Shuttle timer, in seconds
+ statuscode = 200
+ response = "Status retrieved"
+
+/datum/world_topic/status/authed
+ key = "status_authed"
+ anonymous = FALSE
+
+/datum/world_topic/status/authed/Run(list/input)
+ . = ..()
+ // Add on a little extra data for our "special" patrons
+ data["active_players"] = get_active_player_count()
+ if(SSticker.HasRoundStarted())
+ data["real_mode"] = SSticker.mode.name
/datum/world_topic/identify_uuid
- keyword = "identify_uuid"
- require_comms_key = TRUE
- log = FALSE
+ key = "identify_uuid"
+ required_params = list("uuid")
-/datum/world_topic/identify_uuid/Run(list/input, addr)
+/datum/world_topic/identify_uuid/Run(list/input)
var/uuid = input["uuid"]
- . = list()
+ data = list()
if(!SSdbcore.Connect())
- return null
+ statuscode = 500
+ response = "Failed to reach database"
+ data = null
+ return
var/datum/DBQuery/query_ckey_lookup = SSdbcore.NewQuery(
"SELECT ckey FROM [format_table_name("player")] WHERE uuid = :uuid",
@@ -200,12 +297,105 @@
)
if(!query_ckey_lookup.Execute())
qdel(query_ckey_lookup)
- return null
+ statuscode = 500
+ response = "Database query failed"
+ return
- .["identified_ckey"] = null
+ statuscode = 200
+ response = "UUID Checked against database"
+ data["identified_ckey"] = null
if(query_ckey_lookup.NextRow())
- .["identified_ckey"] = query_ckey_lookup.item[1]
+ data["identified_ckey"] = query_ckey_lookup.item[1]
qdel(query_ckey_lookup)
- return .
+/datum/world_topic/d_ooc_send
+ key = "discord_send"
+ required_params = list("message", "message_sender")
+
+/datum/world_topic/d_ooc_send/Run(list/input)
+ . = ..()
+ var/msg = input["message"]
+ var/unm = input["message_sender"]
+ msg = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN)
+ unm = copytext(sanitize(unm), 1, MAX_MESSAGE_LEN)
+ msg = emoji_parse(msg)
+ log_ooc("DISCORD: [unm]: [msg]")
+ for(var/client/C in GLOB.clients)
+ if(C.prefs.chat_toggles & CHAT_OOC)
+ if(!("discord-[unm]" in C.prefs.ignoring))
+ to_chat(C, "OOC: [unm]:[msg]")
+ statuscode = 200
+ response = "Message forwarded to OOC"
+
+/datum/world_topic/get_metacoins
+ key = "get_metacoins"
+ required_params = list("ckey")
+
+/datum/world_topic/get_metacoins/Run(list/input)
+ . = ..()
+
+ var/ckey = input["ckey"]
+
+ if(!ckey || !SSdbcore.Connect())
+ statuscode = 500
+ response = "Database query failed"
+ data = null
+ return
+
+ var/datum/DBQuery/query_get_metacoins = SSdbcore.NewQuery(
+ "SELECT metacoins FROM [format_table_name("player")] WHERE ckey = :ckey",
+ list("ckey" = ckey)
+ )
+ var/mc_count = null
+ if(query_get_metacoins.warn_execute())
+ if(query_get_metacoins.NextRow())
+ mc_count = query_get_metacoins.item[1]
+ else
+ statuscode = 500
+ response = "Database query failed"
+ data = null
+ return
+
+ qdel(query_get_metacoins)
+
+ statuscode = 200
+ response = "Metacoin count retrieved"
+ data = mc_count ? text2num(mc_count) : 0
+
+/datum/world_topic/adjust_metacoins
+ key = "adjust_metacoins"
+ required_params = list("ckey", "amount", "id")
+
+/datum/world_topic/adjust_metacoins/Run(list/input)
+ . = ..()
+
+ var/ckey = input["ckey"]
+ var/amount = input["amount"]
+ var/adjuster_ckey = input["id"]
+
+ if(!SSdbcore.Connect())
+ statuscode = 500
+ response = "Database query failed"
+ data = null
+ return
+
+ var/datum/DBQuery/query_metacoins = SSdbcore.NewQuery(
+ "UPDATE [format_table_name("player")] SET metacoins = metacoins + :amount WHERE ckey = :ckey",
+ list("amount" = amount, "ckey" = ckey)
+ )
+ if(!query_metacoins.warn_execute())
+ statuscode = 500
+ response = "Database query failed"
+ data = null
+ return
+
+ log_game("[ckey]'s metacoins were adjusted ([amount > 0 ? "+[amount]" : "[amount]"]) via Topic() call by [adjuster_ckey ? "[adjuster_ckey]" : "Unknown"]")
+
+ qdel(query_metacoins)
+
+ statuscode = 200
+ response = "Metacoin count updated"
+#undef TOPIC_VERSION_MAJOR
+#undef TOPIC_VERSION_MINOR
+#undef TOPIC_VERSION_PATCH
diff --git a/code/game/alternate_appearance.dm b/code/game/alternate_appearance.dm
index 04f52f148c343..89bf6c3a84931 100644
--- a/code/game/alternate_appearance.dm
+++ b/code/game/alternate_appearance.dm
@@ -178,7 +178,7 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances)
return TRUE
return FALSE
-datum/atom_hud/alternate_appearance/basic/onePerson
+/datum/atom_hud/alternate_appearance/basic/onePerson
var/mob/seer
/datum/atom_hud/alternate_appearance/basic/onePerson/mobShouldSee(mob/M)
@@ -190,3 +190,15 @@ datum/atom_hud/alternate_appearance/basic/onePerson
..(key, I, FALSE)
seer = M
add_hud_to(seer)
+
+/datum/atom_hud/alternate_appearance/basic/heretics
+ add_ghost_version = FALSE //just in case, to prevent infinite loops
+
+/datum/atom_hud/alternate_appearance/basic/heretics/New()
+ ..()
+ for(var/mob in GLOB.player_list)
+ if(mobShouldSee(mob))
+ add_hud_to(mob)
+
+/datum/atom_hud/alternate_appearance/basic/heretics/mobShouldSee(mob/M)
+ return IS_HERETIC(M) || IS_HERETIC_MONSTER(M)
\ No newline at end of file
diff --git a/code/game/area/Space_Station_13_areas.dm b/code/game/area/Space_Station_13_areas.dm
index 8bfaed5ed8950..975b651754d71 100644
--- a/code/game/area/Space_Station_13_areas.dm
+++ b/code/game/area/Space_Station_13_areas.dm
@@ -7,7 +7,8 @@
icon = 'ICON FILENAME' (defaults to 'icons/turf/areas.dmi')
icon_state = "NAME OF ICON" (defaults to "unknown" (blank))
requires_power = FALSE (defaults to true)
- ambient_effects = list() (defaults to GENERIC from sound.dm. override it as "ambient_effects = list('sound/ambience/signal.ogg')" or using another define.
+ ambience_index = AMBIENCE_GENERIC (picks the ambience from an assoc list in ambience.dm)
+ ambientsounds = list() (defaults to ambience_index's assoc on Initialize(). override it as "ambientsounds = list('sound/ambience/signal.ogg')" or by changing ambience_index)
NOTE: there are two lists of areas in the end of this file: centcom and station itself. Please maintain these lists valid. --rastaf0
@@ -28,11 +29,12 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
power_light = FALSE
power_equip = FALSE
power_environ = FALSE
- valid_territory = FALSE
+ area_flags = UNIQUE_AREA
outdoors = TRUE
- ambient_music = SPACE
- blob_allowed = FALSE //Eating up space doesn't count for victory as a blob.
- ambient_buzz = null // Space is pretty quiet
+ ambience_index = null
+ ambient_music_index = AMBIENCE_SPACE
+ ambient_buzz = null
+ sound_environment = SOUND_AREA_SPACE
/area/space/nearstation
icon_state = "space_near"
@@ -44,9 +46,9 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
requires_power = FALSE
dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
has_gravity = STANDARD_GRAVITY
+ ambience_index = null
ambient_buzz = null
-
/area/testroom
requires_power = FALSE
name = "Test Room"
@@ -59,16 +61,16 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
icon_state = "asteroid"
requires_power = FALSE
has_gravity = STANDARD_GRAVITY
- blob_allowed = FALSE //Nope, no winning on the asteroid as a blob. Gotta eat the station.
- valid_territory = FALSE
- ambient_effects = MINING
+ ambience_index = AMBIENCE_MINING
+ sound_environment = SOUND_AREA_ASTEROID
+ area_flags = UNIQUE_AREA
/area/asteroid/nearstation
dynamic_lighting = DYNAMIC_LIGHTING_FORCED
- ambient_effects = RUINS
+ ambience_index = AMBIENCE_RUINS
always_unpowered = FALSE
requires_power = TRUE
- blob_allowed = TRUE
+ area_flags = UNIQUE_AREA | BLOBS_ALLOWED
/area/asteroid/nearstation/bomb_site
name = "Bomb Testing Asteroid"
@@ -78,10 +80,11 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
//Maintenance
/area/maintenance
- ambient_effects = MAINTENANCE
- valid_territory = FALSE
+ ambience_index = AMBIENCE_MAINT
+ sound_environment = SOUND_AREA_TUNNEL_ENCLOSED
+ area_flags = BLOBS_ALLOWED | UNIQUE_AREA
mood_bonus = -1
- mood_message = "It's kind of cramped in here!\n"
+ mood_message = "It's kind of cramped in here!\n"
lighting_colour_tube = "#ffe5cb"
lighting_colour_bulb = "#ffdbb4"
@@ -98,6 +101,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/maintenance/department/crew_quarters/bar
name = "Bar Maintenance"
icon_state = "maint_bar"
+ sound_environment = SOUND_AREA_WOODFLOOR
/area/maintenance/department/crew_quarters/dorms
name = "Dormitory Maintenance"
@@ -158,7 +162,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/maintenance/department/science/xenobiology
name = "Xenobiology Maintenance"
icon_state = "xenomaint"
- xenobiology_compatible = TRUE
+ area_flags = VALID_TERRITORY | BLOBS_ALLOWED | UNIQUE_AREA | XENOBIOLOGY_COMPATIBLE
//Maintenance - Generic
@@ -235,8 +239,76 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
name = "Incinerator"
icon_state = "disposal"
+//Maintenance - Upper
+
+/area/maintenance/upper/aft
+ name = "Upper Aft Maintenance"
+ icon_state = "amaint"
+
+/area/maintenance/upper/aft/secondary
+ name = "Upper Aft Maintenance"
+ icon_state = "amaint_2"
+
+/area/maintenance/upper/central
+ name = "Upper Central Maintenance"
+ icon_state = "maintcentral"
+
+/area/maintenance/upper/central/secondary
+ name = "Upper Central Maintenance"
+ icon_state = "maintcentral"
+
+/area/maintenance/upper/fore
+ name = "Upper Fore Maintenance"
+ icon_state = "fmaint"
+
+/area/maintenance/upper/fore/secondary
+ name = "Upper Fore Maintenance"
+ icon_state = "fmaint_2"
+
+/area/maintenance/upper/starboard
+ name = "Upper Starboard Maintenance"
+ icon_state = "smaint"
+
+/area/maintenance/upper/starboard/central
+ name = "Upper Central Starboard Maintenance"
+ icon_state = "smaint"
+
+/area/maintenance/upper/starboard/secondary
+ name = "Upper Secondary Starboard Maintenance"
+ icon_state = "smaint_2"
+
+/area/maintenance/upper/starboard/aft
+ name = "Upper Starboard Quarter Maintenance"
+ icon_state = "asmaint"
+
+/area/maintenance/upper/starboard/aft/secondary
+ name = "Upper Secondary Starboard Quarter Maintenance"
+ icon_state = "asmaint_2"
+
+/area/maintenance/upper/starboard/fore
+ name = "Upper Starboard Bow Maintenance"
+ icon_state = "fsmaint"
+
+/area/maintenance/upper/port
+ name = "Upper Port Maintenance"
+ icon_state = "pmaint"
+
+/area/maintenance/upper/port/central
+ name = "Upper Central Port Maintenance"
+ icon_state = "maintcentral"
+
+/area/maintenance/upper/port/aft
+ name = "Upper Port Quarter Maintenance"
+ icon_state = "apmaint"
+
+/area/maintenance/upper/port/fore
+ name = "Upper Port Bow Maintenance"
+ icon_state = "fpmaint"
+
//Hallway
+/area/hallway
+ sound_environment = SOUND_AREA_STANDARD_STATION
/area/hallway
lighting_colour_tube = "#ffce99"
@@ -288,36 +360,91 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
name = "Service Hallway"
icon_state = "hall_service"
+/area/hallway/secondary/law
+ name = "Law Hallway"
+ icon_state = "security"
+
+
+/area/hallway/upper/primary/aft
+ name = "Upper Aft Primary Hallway"
+ icon_state = "hallA"
+
+/area/hallway/upper/primary/fore
+ name = "Upper Fore Primary Hallway"
+ icon_state = "hallF"
+
+/area/hallway/upper/primary/starboard
+ name = "Upper Starboard Primary Hallway"
+ icon_state = "hallS"
+
+/area/hallway/upper/primary/port
+ name = "Upper Port Primary Hallway"
+ icon_state = "hallP"
+
+/area/hallway/upper/primary/central
+ name = "Upper Central Primary Hallway"
+ icon_state = "hallC"
+
+/area/hallway/upper/secondary/command
+ name = "Upper Command Hallway"
+ icon_state = "bridge_hallway"
+
+/area/hallway/upper/secondary/construction
+ name = "Upper Construction Area"
+ icon_state = "construction"
+
+/area/hallway/upper/secondary/exit
+ name = "Upper Escape Shuttle Hallway"
+ icon_state = "escape"
+
+/area/hallway/upper/secondary/exit/departure_lounge
+ name = "Upper Departure Lounge"
+ icon_state = "escape_lounge"
+
+/area/hallway/upper/secondary/entry
+ name = "Upper Arrival Shuttle Hallway"
+ icon_state = "entry"
+
+/area/hallway/upper/secondary/service
+ name = "Upper Service Hallway"
+ icon_state = "hall_service"
+
//Command
/area/bridge
name = "Bridge"
icon_state = "bridge"
- ambient_effects = list('sound/ambience/signal.ogg')
+ ambientsounds = list('sound/ambience/signal.ogg')
lighting_colour_tube = "#ffce99"
lighting_colour_bulb = "#ffdbb4"
lighting_brightness_tube = 8
+ sound_environment = SOUND_AREA_STANDARD_STATION
/area/bridge/meeting_room
name = "Heads of Staff Meeting Room"
icon_state = "meeting"
+ sound_environment = SOUND_AREA_MEDIUM_SOFTFLOOR
/area/bridge/meeting_room/council
name = "Council Chamber"
icon_state = "meeting"
+ sound_environment = SOUND_AREA_MEDIUM_SOFTFLOOR
/area/bridge/showroom/corporate
name = "Corporate Showroom"
icon_state = "showroom"
+ sound_environment = SOUND_AREA_MEDIUM_SOFTFLOOR
/area/crew_quarters/heads/captain
name = "Captain's Office"
icon_state = "captain"
+ sound_environment = SOUND_AREA_WOODFLOOR
/area/crew_quarters/heads/captain/private
name = "Captain's Quarters"
icon_state = "captain"
+ sound_environment = SOUND_AREA_WOODFLOOR
/area/crew_quarters/heads/chief
name = "Chief Engineer's Office"
@@ -344,10 +471,12 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
icon_state = "tcomsatcham"
lighting_colour_tube = "#e2feff"
lighting_colour_bulb = "#d5fcff"
+ sound_environment = SOUND_AREA_STANDARD_STATION
/area/server
name = "Messaging Server Room"
icon_state = "server"
+ sound_environment = SOUND_AREA_STANDARD_STATION
//Crew
@@ -355,17 +484,20 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
lighting_colour_tube = "#ffce99"
lighting_colour_bulb = "#ffdbb4"
lighting_brightness_tube = 8
+ sound_environment = SOUND_AREA_STANDARD_STATION
/area/crew_quarters/dorms
name = "Dormitories"
icon_state = "Sleep"
- safe = TRUE
+ area_flags = VALID_TERRITORY | BLOBS_ALLOWED | UNIQUE_AREA
mood_bonus = 3
mood_message = "There's no place like the dorms!\n"
+/area/crew_quarters/dorms/upper
+ name = "Upper Dorms"
+
/area/crew_quarters/cryopods
name = "Cryopod Room"
- safe = TRUE
icon_state = "cryopod"
lighting_colour_tube = "#e3ffff"
lighting_colour_bulb = "#d5ffff"
@@ -375,6 +507,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
icon_state = "toilet"
lighting_colour_tube = "#e3ffff"
lighting_colour_bulb = "#d5ffff"
+ sound_environment = SOUND_AREA_SMALL_ENCLOSED
/area/crew_quarters/toilet/auxiliary
name = "Auxiliary Restrooms"
@@ -395,6 +528,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/crew_quarters/lounge
name = "Lounge"
icon_state = "yellow"
+ sound_environment = SOUND_AREA_SMALL_SOFTFLOOR
/area/crew_quarters/fitness
name = "Fitness Room"
@@ -408,6 +542,17 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
name = "Recreation Area"
icon_state = "fitness"
+/area/crew_quarters/fitness/recreation/upper
+ name = "Upper Recreation Area"
+ icon_state = "fitness"
+
+/area/crew_quarters/park
+ name = "Recrational Park"
+ icon_state = "fitness"
+ lighting_colour_bulb = "#80aae9"
+ lighting_colour_tube = "#80aae9"
+ lighting_brightness_bulb = 9
+
/area/crew_quarters/cafeteria
name = "Cafeteria"
icon_state = "cafeteria"
@@ -421,6 +566,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/crew_quarters/kitchen/coldroom
name = "Kitchen Cold Room"
icon_state = "kitchen_cold"
+ sound_environment = SOUND_AREA_SMALL_ENCLOSED
/area/crew_quarters/bar
name = "Bar"
@@ -429,10 +575,20 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
mood_message = "I love being in the bar!\n"
lighting_colour_tube = "#fff4d6"
lighting_colour_bulb = "#ffebc1"
+ sound_environment = SOUND_AREA_WOODFLOOR
+
+/area/crew_quarters/bar/Initialize(mapload)
+ . = ..()
+ GLOB.bar_areas += src
+
+/area/service/bar/Initialize(mapload)
+ . = ..()
+ GLOB.bar_areas += src
/area/crew_quarters/bar/atrium
name = "Atrium"
icon_state = "bar"
+ sound_environment = SOUND_AREA_WOODFLOOR
/area/crew_quarters/electronic_marketing_den
name = "Electronic Marketing Den"
@@ -448,6 +604,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/crew_quarters/theatre
name = "Theatre"
icon_state = "Theatre"
+ sound_environment = SOUND_AREA_WOODFLOOR
/area/crew_quarters/theatre/abandoned
name = "Abandoned Theatre"
@@ -464,6 +621,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/library/lounge
name = "Library Lounge"
+ sound_environment = SOUND_AREA_LARGE_SOFTFLOOR
icon_state = "library"
/area/library/abandoned
@@ -473,10 +631,11 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/chapel
icon_state = "chapel"
- ambient_effects = HOLY
+ ambience_index = AMBIENCE_HOLY
flags_1 = NONE
clockwork_warp_allowed = FALSE
clockwork_warp_fail = "The consecration here prevents you from warping in."
+ sound_environment = SOUND_AREA_LARGE_ENCLOSED
/area/chapel/main
name = "Chapel"
@@ -491,6 +650,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/chapel/asteroid
name = "Chapel Asteroid"
icon_state = "explored"
+ sound_environment = SOUND_AREA_ASTEROID
/area/chapel/asteroid/monastery
name = "Monastery Asteroid"
@@ -502,12 +662,14 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/lawoffice
name = "Law Office"
icon_state = "law"
+ sound_environment = SOUND_AREA_SMALL_SOFTFLOOR
//Engineering
/area/engine
- ambient_effects = ENGINEERING
+ ambience_index = AMBIENCE_ENGI
+ sound_environment = SOUND_AREA_LARGE_ENCLOSED
lighting_colour_tube = "#ffce93"
lighting_colour_bulb = "#ffbc6f"
@@ -527,6 +689,8 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/engine/atmospherics_engine
name = "Atmospherics Engine"
icon_state = "atmos_engine"
+ area_flags = BLOBS_ALLOWED | UNIQUE_AREA
+ sound_environment = SOUND_AREA_LARGE_ENCLOSED
/area/engine/engine_room //donut station specific
name = "Engine Room"
@@ -539,12 +703,15 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/engine/supermatter
name = "Supermatter Engine"
icon_state = "engine_sm"
+ area_flags = BLOBS_ALLOWED | UNIQUE_AREA
+ sound_environment = SOUND_AREA_SMALL_ENCLOSED
/area/engine/break_room
name = "Engineering Foyer"
icon_state = "engine_foyer"
mood_bonus = 2
mood_message = "Ahhh, time to take a break.\n"
+ sound_environment = SOUND_AREA_SMALL_ENCLOSED
/area/engine/gravity_generator
name = "Gravity Generator Room"
@@ -555,6 +722,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/engine/storage
name = "Engineering Storage"
icon_state = "engi_storage"
+ sound_environment = SOUND_AREA_SMALL_ENCLOSED
/area/engine/storage_shared
name = "Shared Engineering Storage"
@@ -570,14 +738,15 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/solar
requires_power = FALSE
dynamic_lighting = DYNAMIC_LIGHTING_IFSTARLIGHT
- valid_territory = FALSE
- blob_allowed = FALSE
+ area_flags = UNIQUE_AREA
flags_1 = NONE
- ambient_effects = ENGINEERING
+ ambience_index = AMBIENCE_ENGI
+ sound_environment = SOUND_AREA_SPACE
/area/solar/fore
name = "Fore Solar Array"
icon_state = "yellow"
+ sound_environment = SOUND_AREA_STANDARD_STATION
/area/solar/aft
name = "Aft Solar Array"
@@ -616,6 +785,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
icon_state = "panelsFP"
+
//Solar Maint
/area/maintenance/solars
@@ -651,19 +821,21 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/teleporter
name = "Teleporter Room"
icon_state = "teleporter"
- ambient_effects = ENGINEERING
+ ambience_index = AMBIENCE_ENGI
/area/gateway
name = "Gateway"
icon_state = "gateway"
- ambient_effects = ENGINEERING
+ ambience_index = AMBIENCE_ENGI
+ sound_environment = SOUND_AREA_STANDARD_STATION
//MedBay
/area/medical
name = "Medical"
icon_state = "medbay3"
- ambient_effects = MEDICAL
+ ambience_index = AMBIENCE_MEDICAL
+ sound_environment = SOUND_AREA_STANDARD_STATION
mood_bonus = 2
mood_message = "I feel safe in here!\n"
lighting_colour_tube = "#e7f8ff"
@@ -672,7 +844,12 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/medical/abandoned
name = "Abandoned Medbay"
icon_state = "medbay3"
- ambient_effects = list('sound/ambience/signal.ogg')
+ ambientsounds = list('sound/ambience/signal.ogg')
+ sound_environment = SOUND_AREA_SMALL_ENCLOSED
+
+/area/medical/medbay/balcony
+ name = "Medbay Balcony"
+ icon_state = "medbay"
/area/medical/medbay/central
name = "Medbay Central"
@@ -699,6 +876,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/medical/patients_rooms
name = "Patients' Rooms"
icon_state = "patients"
+ sound_environment = SOUND_AREA_SMALL_SOFTFLOOR
/area/medical/patients_rooms/room_a
name = "Patient Room A"
@@ -708,6 +886,10 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
name = "Patient Room B"
icon_state = "patients"
+/area/medical/patients_rooms/room_c
+ name = "Patient Room C"
+ icon_state = "patients"
+
/area/medical/virology
name = "Virology"
icon_state = "virology"
@@ -716,14 +898,19 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/medical/morgue
name = "Morgue"
icon_state = "morgue"
- ambient_effects = SPOOKY
+ ambience_index = AMBIENCE_SPOOKY
+ sound_environment = SOUND_AREA_SMALL_ENCLOSED
mood_bonus = -2
- mood_message = "It smells like death in here!\n"
+ mood_message = "It smells like death in here!\n"
/area/medical/chemistry
name = "Chemistry"
icon_state = "chem"
+/area/medical/chemistry/upper
+ name = "Upper Chemistry"
+ icon_state = "chem"
+
/area/medical/apothecary
name = "Apothecary"
icon_state = "apothecary"
@@ -732,6 +919,10 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
name = "Surgery"
icon_state = "surgery"
+/area/medical/surgery/aux
+ name = "Auxillery Surgery"
+ icon_state = "surgery"
+
/area/medical/cryo
name = "Cryogenics"
icon_state = "cryo"
@@ -758,7 +949,8 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/security
name = "Security"
icon_state = "security"
- ambient_effects = HIGHSEC
+ ambience_index = AMBIENCE_DANGER
+ sound_environment = SOUND_AREA_STANDARD_STATION
lighting_colour_tube = "#ffeee2"
lighting_colour_bulb = "#ffdfca"
@@ -770,11 +962,12 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
name = "Brig"
icon_state = "brig"
mood_bonus = -3
- mood_message = "I hate cramped brig cells.\n"
+ mood_message = "I hate cramped brig cells.\n"
/area/security/courtroom
name = "Courtroom"
icon_state = "courtroom"
+ sound_environment = SOUND_AREA_LARGE_ENCLOSED
/area/security/prison
name = "Prison Wing"
@@ -787,19 +980,22 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/security/processing/cremation
name = "Security Crematorium"
icon_state = "sec_prison"
+ sound_environment = SOUND_AREA_SMALL_ENCLOSED
/area/security/warden
name = "Brig Control"
icon_state = "Warden"
+ sound_environment = SOUND_AREA_SMALL_SOFTFLOOR
/area/security/detectives_office
name = "Detective's Office"
icon_state = "detective"
- ambient_effects = list('sound/ambience/ambidet1.ogg','sound/ambience/ambidet2.ogg')
+ ambientsounds = list('sound/ambience/ambidet1.ogg','sound/ambience/ambidet2.ogg','sound/ambience/ambidet3.ogg','sound/ambience/ambidet4.ogg')
/area/security/detectives_office/private_investigators_office
name = "Private Investigator's Office"
icon_state = "detective"
+ sound_environment = SOUND_AREA_SMALL_SOFTFLOOR
/area/security/range
name = "Firing Range"
@@ -808,7 +1004,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/security/execution
icon_state = "execution_room"
mood_bonus = -5
- mood_message = "I feel a sense of impending doom.\n"
+ mood_message = "I feel a sense of impending doom.\n"
/area/security/execution/transfer
name = "Transfer Centre"
@@ -869,14 +1065,17 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
icon_state = "quart"
lighting_colour_tube = "#ffe3cc"
lighting_colour_bulb = "#ffdbb8"
+ sound_environment = SOUND_AREA_STANDARD_STATION
/area/quartermaster/sorting
name = "Delivery Office"
icon_state = "cargo_delivery"
+ sound_environment = SOUND_AREA_STANDARD_STATION
/area/quartermaster/warehouse
name = "Warehouse"
icon_state = "cargo_warehouse"
+ sound_environment = SOUND_AREA_LARGE_ENCLOSED
/area/quartermaster/office
name = "Cargo Office"
@@ -885,11 +1084,16 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/quartermaster/storage
name = "Cargo Bay"
icon_state = "cargo_bay"
+ sound_environment = SOUND_AREA_LARGE_ENCLOSED
/area/quartermaster/qm
name = "Quartermaster's Office"
icon_state = "quart"
+/area/quartermaster/qm_bedroom
+ name = "Quartermaster's Bedroom"
+ icon_state = "quart"
+
/area/quartermaster/miningdock
name = "Mining Dock"
icon_state = "mining"
@@ -898,16 +1102,30 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
name = "Mining Office"
icon_state = "mining"
+/area/quartermaster/meeting_room
+ name = "Supply Meeting Room"
+ icon_state = "mining"
+
+/area/quartermaster/exploration_prep
+ name = "Exploration Preperation Room"
+ icon_state = "mining"
+
+/area/quartermaster/exploration_dock
+ name = "Exploration Dock"
+ icon_state = "mining"
+
/area/janitor
name = "Custodial Closet"
icon_state = "janitor"
flags_1 = NONE
mood_bonus = -1
- mood_message = "It feels dirty in here!\n"
+ mood_message = "It feels dirty in here!\n"
+ sound_environment = SOUND_AREA_SMALL_ENCLOSED
/area/hydroponics
name = "Hydroponics"
icon_state = "hydro"
+ sound_environment = SOUND_AREA_STANDARD_STATION
/area/hydroponics/garden
name = "Garden"
@@ -918,6 +1136,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/hydroponics/garden/abandoned
name = "Abandoned Garden"
icon_state = "abandoned_garden"
+ sound_environment = SOUND_AREA_SMALL_ENCLOSED
/area/hydroponics/garden/monastery
name = "Monastery Garden"
@@ -931,6 +1150,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
icon_state = "toxlab"
lighting_colour_tube = "#f0fbff"
lighting_colour_bulb = "#e4f7ff"
+ sound_environment = SOUND_AREA_STANDARD_STATION
/area/science/lab
name = "Research and Development"
@@ -940,13 +1160,18 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
name = "Xenobiology Lab"
icon_state = "toxlab"
+/area/science/shuttle
+ name = "Shuttle Construction"
+ lighting_colour_tube = "#ffe3cc"
+ lighting_colour_bulb = "#ffdbb8"
+
/area/science/storage
name = "Toxins Storage"
icon_state = "toxstorage"
/area/science/test_area
- valid_territory = FALSE
name = "Toxins Test Area"
+ area_flags = BLOBS_ALLOWED | UNIQUE_AREA
icon_state = "toxtest"
/area/science/mixing
@@ -955,8 +1180,8 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/science/mixing/chamber
name = "Toxins Mixing Chamber"
+ area_flags = BLOBS_ALLOWED | UNIQUE_AREA
icon_state = "toxmix"
- valid_territory = FALSE
/area/science/misc_lab
name = "Testing Lab"
@@ -993,6 +1218,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/science/research/abandoned
name = "Abandoned Research Lab"
icon_state = "medresearch"
+ sound_environment = SOUND_AREA_SMALL_ENCLOSED
/area/science/nanite
name = "Nanite Lab"
@@ -1003,6 +1229,8 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
icon_state = "toxmisc"
//Storage
+/area/storage
+ sound_environment = SOUND_AREA_STANDARD_STATION
/area/storage/tools
name = "Auxiliary Tool Storage"
@@ -1018,8 +1246,8 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/storage/tcom
name = "Telecomms Storage"
+ area_flags = BLOBS_ALLOWED | UNIQUE_AREA
icon_state = "green"
- valid_territory = FALSE
/area/storage/eva
name = "EVA Storage"
@@ -1043,11 +1271,13 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/construction
name = "Construction Area"
icon_state = "yellow"
- ambient_effects = ENGINEERING
+ ambience_index = AMBIENCE_ENGI
+ sound_environment = SOUND_AREA_STANDARD_STATION
/area/construction/mining/aux_base
name = "Auxiliary Base Construction"
icon_state = "aux_base_construction"
+ sound_environment = SOUND_AREA_MEDIUM_SOFTFLOOR
/area/construction/storage_wing
name = "Storage Wing"
@@ -1057,7 +1287,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/vacant_room
name = "Vacant Room"
icon_state = "yellow"
- ambient_effects = MAINTENANCE
+ ambience_index = AMBIENCE_MAINT
icon_state = "vacant_room"
/area/vacant_room/office
@@ -1070,33 +1300,38 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
//AI
+/area/ai_monitored
+ sound_environment = SOUND_AREA_STANDARD_STATION
+
/area/ai_monitored/security/armory
name = "Armory"
icon_state = "armory"
- ambient_effects = HIGHSEC
+ ambience_index = AMBIENCE_DANGER
/area/ai_monitored/storage/eva
name = "EVA Storage"
icon_state = "eva"
- ambient_effects = HIGHSEC
+ ambience_index = AMBIENCE_DANGER
/area/ai_monitored/storage/satellite
name = "AI Satellite Maint"
icon_state = "storage"
- ambient_effects = HIGHSEC
+ ambience_index = AMBIENCE_DANGER
//Turret_protected
/area/ai_monitored/turret_protected
- ambient_effects = list('sound/ambience/ambimalf.ogg', 'sound/ambience/ambitech.ogg', 'sound/ambience/ambitech2.ogg', 'sound/ambience/ambiatmos.ogg', 'sound/ambience/ambiatmos2.ogg')
+ ambientsounds = list('sound/ambience/ambimalf.ogg', 'sound/ambience/ambitech.ogg', 'sound/ambience/ambitech2.ogg', 'sound/ambience/ambiatmos.ogg', 'sound/ambience/ambiatmos2.ogg')
/area/ai_monitored/turret_protected/ai_upload
name = "AI Upload Chamber"
icon_state = "ai_upload"
+ sound_environment = SOUND_AREA_SMALL_ENCLOSED
/area/ai_monitored/turret_protected/ai_upload_foyer
name = "AI Upload Access"
icon_state = "ai_foyer"
+ sound_environment = SOUND_AREA_SMALL_ENCLOSED
/area/ai_monitored/turret_protected/ai
name = "AI Chamber"
@@ -1105,6 +1340,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/ai_monitored/turret_protected/aisat
name = "AI Satellite"
icon_state = "ai"
+ sound_environment = SOUND_ENVIRONMENT_ROOM
/area/ai_monitored/turret_protected/aisat/atmos
name = "AI Satellite Atmos"
@@ -1129,6 +1365,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/ai_monitored/turret_protected/aisat_interior
name = "AI Satellite Antechamber"
icon_state = "ai"
+ sound_environment = SOUND_AREA_LARGE_ENCLOSED
/area/ai_monitored/turret_protected/AIsatextAS
name = "AI Sat Ext"
@@ -1144,13 +1381,18 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/tcommsat
clockwork_warp_allowed = FALSE
clockwork_warp_fail = "For safety reasons, warping here is disallowed; the radio and bluespace noise could cause catastrophic results."
- ambient_effects = list('sound/ambience/ambisin2.ogg', 'sound/ambience/signal.ogg', 'sound/ambience/signal.ogg', 'sound/ambience/ambigen10.ogg', 'sound/ambience/ambitech.ogg',\
+ ambientsounds = list('sound/ambience/ambisin2.ogg', 'sound/ambience/signal.ogg', 'sound/ambience/signal.ogg', 'sound/ambience/ambigen10.ogg', 'sound/ambience/ambitech.ogg',\
'sound/ambience/ambitech2.ogg', 'sound/ambience/ambitech3.ogg', 'sound/ambience/ambimystery.ogg')
/area/tcommsat/computer
name = "Telecomms Control Room"
icon_state = "tcomsatcomp"
+ sound_environment = SOUND_AREA_MEDIUM_SOFTFLOOR
/area/tcommsat/server
name = "Telecomms Server Room"
icon_state = "tcomsatcham"
+
+/area/tcommsat/relay
+ name = "Telecommunications Relay"
+ icon_state = "tcomsatcham"
diff --git a/code/game/area/ai_monitored.dm b/code/game/area/ai_monitored.dm
index 87b44291f56e9..48e77e1623753 100644
--- a/code/game/area/ai_monitored.dm
+++ b/code/game/area/ai_monitored.dm
@@ -3,6 +3,7 @@
clockwork_warp_allowed = FALSE
var/list/obj/machinery/camera/motioncameras = list()
var/list/datum/weakref/motionTargets = list()
+ sound_environment = SOUND_ENVIRONMENT_ROOM
/area/ai_monitored/Initialize(mapload)
. = ..()
@@ -28,4 +29,4 @@
for(var/X in motioncameras)
var/obj/machinery/camera/cam = X
cam.lostTargetRef(WEAKREF(O))
- return
\ No newline at end of file
+ return
diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index 553387621304f..1e6025324fab4 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -14,60 +14,55 @@
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
invisibility = INVISIBILITY_LIGHTING
- var/map_name // Set in New(); preserves the name set by the map maker, even if renamed by the Blueprints.
+ var/area_flags = VALID_TERRITORY | BLOBS_ALLOWED | UNIQUE_AREA
- var/valid_territory = TRUE // If it's a valid territory for gangs to claim
- var/blob_allowed = TRUE // Does it count for blobs score? By default, all areas count.
var/clockwork_warp_allowed = TRUE // Can servants warp into this area from Reebe?
var/clockwork_warp_fail = "The structure there is too dense for warping to pierce. (This is normal in high-security areas.)"
var/fire = null
- var/atmos = TRUE
+ ///Whether there is an atmos alarm in this area
var/atmosalm = FALSE
var/poweralm = TRUE
var/lightswitch = TRUE
var/vacuum = null
- var/requires_power = TRUE
- var/always_unpowered = FALSE // This gets overridden to 1 for space in area/Initialize().
-
- var/outdoors = FALSE //For space, the asteroid, lavaland, etc. Used with blueprints to determine if we are adding a new area (vs editing a station room)
+ /// For space, the asteroid, lavaland, etc. Used with blueprints or with weather to determine if we are adding a new area (vs editing a station room)
+ var/outdoors = FALSE
var/areasize = 0 //Size of the area in open turfs, only calculated for indoors areas.
var/mood_bonus = 0 //Mood for being here
var/mood_message = "This area is pretty nice!\n" //Mood message for being here, only shows up if mood_bonus != 0
+ ///Will objects this area be needing power?
+ var/requires_power = TRUE
+ /// This gets overridden to 1 for space in area/Initialize().
+ var/always_unpowered = FALSE
+
var/power_equip = TRUE
var/power_light = TRUE
var/power_environ = TRUE
- var/has_gravity = 0
+ var/has_gravity = FALSE
///Are you forbidden from teleporting to the area? (centcom, mobs, wizard, hand teleporter)
- var/noteleport = FALSE
- ///Hides area from player Teleport function.
- var/hidden = FALSE
- ///Is the area teleport-safe: no space / radiation / aggresive mobs / other dangers
- var/safe = FALSE
- /// If false, loading multiple maps with this area type will create multiple instances.
- var/unique = TRUE
-
- var/no_air = null
+ var/teleport_restriction = TELEPORT_ALLOW_ALL
var/parallax_movedir = 0
- var/list/ambient_music = null // OOC, doesn't require the user to actually be able to hear it
- var/list/ambient_effects = GENERIC // IC, requires the user to actually be able to hear it, will play spontaneously
+ var/ambience_index = AMBIENCE_GENERIC
+ var/list/ambientsounds
+
var/ambient_buzz = 'sound/ambience/shipambience.ogg' // Ambient buzz of the station, plays repeatedly, also IC
+ var/ambient_music_index
+ var/list/ambientmusic
+
flags_1 = CAN_BE_DIRTY_1
var/list/firedoors
var/list/cameras
var/list/firealarms
var/firedoors_last_closed_on = 0
- /// Can the Xenobio management console transverse this area by default?
- var/xenobiology_compatible = FALSE
/// typecache to limit the areas that atoms in this area can smooth with, used for shuttles IIRC
var/list/canSmoothWithAreas
@@ -76,10 +71,29 @@
var/lighting_colour_tube = "#FFF6ED"
var/lighting_colour_bulb = "#FFE6CC"
var/lighting_colour_night = "#FFDBB5"
- var/lighting_brightness_tube = 10
+ var/lighting_brightness_tube = 11
var/lighting_brightness_bulb = 6
var/lighting_brightness_night = 6
+ ///Used to decide what the minimum time between ambience is
+ var/min_ambience_cooldown = 30 SECONDS
+ ///Used to decide what the maximum time between ambience is
+ var/max_ambience_cooldown = 90 SECONDS
+ ///Used to decide what kind of reverb the area makes sound have
+ var/sound_environment = SOUND_ENVIRONMENT_NONE
+
+ //Lighting overlay
+ var/obj/effect/lighting_overlay
+ var/lighting_overlay_colour = "#FFFFFF"
+ var/lighting_overlay_opacity = 0
+
+ ///This datum, if set, allows terrain generation behavior to be ran on Initialize()
+ var/datum/map_generator/map_generator
+
+ ///Lazylist that contains additional turfs that map generation should be ran on. This is used for ruins which need a noop turf under non-noop areas so they don't leave genturfs behind.
+ var/list/additional_genturfs
+
+
/**
* A list of teleport locations
*
@@ -100,7 +114,7 @@ GLOBAL_LIST_EMPTY(teleportlocs)
/proc/process_teleport_locs()
for(var/V in GLOB.sortedAreas)
var/area/AR = V
- if(istype(AR, /area/shuttle) || AR.noteleport)
+ if(istype(AR, /area/shuttle) || AR.teleport_restriction)
continue
if(GLOB.teleportlocs[AR.name])
continue
@@ -120,7 +134,7 @@ GLOBAL_LIST_EMPTY(teleportlocs)
/area/New()
// This interacts with the map loader, so it needs to be set immediately
// rather than waiting for atoms to initialize.
- if (unique)
+ if (area_flags & UNIQUE_AREA)
GLOB.areas_by_type[type] = src
power_usage = new /list(AREA_USAGE_LEN) // Some atoms would like to use power in Initialize()
return ..()
@@ -135,10 +149,14 @@ GLOBAL_LIST_EMPTY(teleportlocs)
*/
/area/Initialize()
icon_state = ""
- layer = AREA_LAYER
- map_name = name // Save the initial (the name set in the map) name of the area.
canSmoothWithAreas = typecacheof(canSmoothWithAreas)
+ if(!ambientsounds && ambience_index)
+ ambientsounds = GLOB.ambience_assoc[ambience_index]
+
+ if(!ambientmusic && ambient_music_index)
+ ambientmusic = GLOB.ambient_music_assoc[ambient_music_index]
+
if(requires_power)
luminosity = 0
else
@@ -160,7 +178,11 @@ GLOBAL_LIST_EMPTY(teleportlocs)
if(!IS_DYNAMIC_LIGHTING(src))
add_overlay(/obj/effect/fullbright)
-
+ else if(lighting_overlay_opacity && lighting_overlay_colour)
+ lighting_overlay = new /obj/effect/fullbright
+ lighting_overlay.color = lighting_overlay_colour
+ lighting_overlay.alpha = lighting_overlay_opacity
+ add_overlay(lighting_overlay)
reg_in_areas_in_z()
return INITIALIZE_HINT_LATELOAD
@@ -171,6 +193,28 @@ GLOBAL_LIST_EMPTY(teleportlocs)
/area/LateInitialize()
power_change() // all machines set to current power level, also updates icon
+/area/proc/RunGeneration()
+ if(map_generator)
+ map_generator = new map_generator()
+ var/list/turfs = list()
+ for(var/turf/T in contents)
+ turfs += T
+ if(additional_genturfs)
+ turfs += additional_genturfs
+ additional_genturfs = null
+ map_generator.generate_terrain(turfs)
+
+/area/proc/test_gen()
+ if(map_generator)
+ var/list/turfs = list()
+ for(var/turf/T in contents)
+ turfs += T
+ if(additional_genturfs)
+ turfs += additional_genturfs
+ additional_genturfs = null
+ map_generator.generate_terrain(turfs)
+
+
/**
* Register this area as belonging to a z level
*
@@ -209,7 +253,9 @@ GLOBAL_LIST_EMPTY(teleportlocs)
/area/Destroy()
if(GLOB.areas_by_type[type] == src)
GLOB.areas_by_type[type] = null
- STOP_PROCESSING(SSobj, src)
+ GLOB.sortedAreas -= src
+ if(fire)
+ STOP_PROCESSING(SSobj, src)
return ..()
/**
@@ -253,9 +299,9 @@ GLOBAL_LIST_EMPTY(teleportlocs)
*
* Sends to all ai players, alert consoles, drones and alarm monitor programs in the world
*/
-/area/proc/atmosalert(danger_level, obj/source)
- if(danger_level != atmosalm)
- if (danger_level==2)
+/area/proc/atmosalert(isdangerous, obj/source)
+ if(isdangerous != atmosalm)
+ if(isdangerous==TRUE)
for (var/item in GLOB.silicon_mobs)
var/mob/living/silicon/aiPlayer = item
@@ -270,7 +316,7 @@ GLOBAL_LIST_EMPTY(teleportlocs)
var/datum/computer_file/program/alarm_monitor/p = item
p.triggerAlarm("Atmosphere", src, cameras, source)
- else if (src.atmosalm == 2)
+ else
for (var/item in GLOB.silicon_mobs)
var/mob/living/silicon/aiPlayer = item
aiPlayer.cancelAlarm("Atmosphere", src, source)
@@ -284,9 +330,9 @@ GLOBAL_LIST_EMPTY(teleportlocs)
var/datum/computer_file/program/alarm_monitor/p = item
p.cancelAlarm("Atmosphere", src, source)
- src.atmosalm = danger_level
- return 1
- return 0
+ atmosalm = isdangerous
+ return TRUE
+ return FALSE
/**
* Try to close all the firedoors in the area
@@ -323,6 +369,7 @@ GLOBAL_LIST_EMPTY(teleportlocs)
if (!fire)
set_fire_alarm_effect()
ModifyFiredoors(FALSE)
+ START_PROCESSING(SSobj, src)
for(var/item in firealarms)
var/obj/machinery/firealarm/F = item
F.update_icon()
@@ -340,8 +387,6 @@ GLOBAL_LIST_EMPTY(teleportlocs)
var/datum/computer_file/program/alarm_monitor/p = item
p.triggerAlarm("Fire", src, cameras, source)
- START_PROCESSING(SSobj, src)
-
/**
* Reset the firealarm alert for this area
*
@@ -354,6 +399,7 @@ GLOBAL_LIST_EMPTY(teleportlocs)
if (fire)
unset_fire_alarm_effects()
ModifyFiredoors(TRUE)
+ STOP_PROCESSING(SSobj, src)
for(var/item in firealarms)
var/obj/machinery/firealarm/F = item
F.update_icon()
@@ -371,7 +417,17 @@ GLOBAL_LIST_EMPTY(teleportlocs)
var/datum/computer_file/program/alarm_monitor/p = item
p.cancelAlarm("Fire", src, source)
- STOP_PROCESSING(SSobj, src)
+///Get rid of any dangling camera refs
+/area/proc/clear_camera(obj/machinery/camera/cam)
+ LAZYREMOVE(cameras, cam)
+ for (var/mob/living/silicon/aiPlayer as anything in GLOB.silicon_mobs)
+ aiPlayer.freeCamera(src, cam)
+ for (var/obj/machinery/computer/station_alert/comp as anything in GLOB.alert_consoles)
+ comp.freeCamera(src, cam)
+ for (var/mob/living/simple_animal/drone/drone_on as anything in GLOB.drones_list)
+ drone_on.freeCamera(src, cam)
+ for(var/datum/computer_file/program/alarm_monitor/monitor as anything in GLOB.alarmdisplay)
+ monitor.freeCamera(src, cam)
/**
* If 100 ticks has elapsed, toggle all the firedoors closed again
@@ -547,7 +603,6 @@ GLOBAL_LIST_EMPTY(teleportlocs)
if(AREA_USAGE_DYNAMIC_START to AREA_USAGE_DYNAMIC_END)
power_usage[chan] += amount
-
/**
* Call back when an atom enters an area
*
@@ -570,7 +625,8 @@ GLOBAL_LIST_EMPTY(teleportlocs)
SEND_SIGNAL(M, COMSIG_EXIT_AREA, src) //The atom that exits the area
/**
- * Returns true if this atom has gravity for the passed in turf
+ * Returns true if this atom has gravity for the passed in turf or other gravity-mimicking behaviors
+ * In other words, it returns whether the atom can be *on* the turf (i.e. not forced to float)
*
* Sends signals COMSIG_ATOM_HAS_GRAVITY and COMSIG_TURF_HAS_GRAVITY, both can force gravity with
* the forced gravity var
@@ -600,7 +656,8 @@ GLOBAL_LIST_EMPTY(teleportlocs)
max_grav = max(max_grav, i)
return max_grav
- if(isspaceturf(T)) // Turf never has gravity
+
+ if(!T.check_gravity()) // Turf never has gravity
return 0
var/area/A = get_area(T)
@@ -608,9 +665,9 @@ GLOBAL_LIST_EMPTY(teleportlocs)
return A.has_gravity
else
// There's a gravity generator on our z level
- if(GLOB.gravity_generators["[T.z]"])
+ if(GLOB.gravity_generators["[T.get_virtual_z_level()]"])
var/max_grav = 0
- for(var/obj/machinery/gravity_generator/main/G in GLOB.gravity_generators["[T.z]"])
+ for(var/obj/machinery/gravity_generator/main/G in GLOB.gravity_generators["[T.get_virtual_z_level()]"])
max_grav = max(G.setting,max_grav)
return max_grav
return SSmapping.level_trait(T.z, ZTRAIT_GRAVITY)
@@ -625,8 +682,8 @@ GLOBAL_LIST_EMPTY(teleportlocs)
power_light = FALSE
power_environ = FALSE
always_unpowered = FALSE
- valid_territory = FALSE
- blob_allowed = FALSE
+ area_flags &= ~VALID_TERRITORY
+ area_flags &= ~BLOBS_ALLOWED
addSorted()
/**
* Set the area size of the area
@@ -656,3 +713,10 @@ GLOBAL_LIST_EMPTY(teleportlocs)
/// A hook so areas can modify the incoming args (of what??)
/area/proc/PlaceOnTopReact(list/new_baseturfs, turf/fake_turf_type, flags)
return flags
+
+/// Gets an areas virtual z value. For having multiple areas on the same z-level treated mechanically as different z-levels
+/area/proc/get_virtual_z(turf/T)
+ return T.z
+
+/area/get_virtual_z_level()
+ return get_virtual_z(get_turf(src))
diff --git a/code/game/area/areas/away_content.dm b/code/game/area/areas/away_content.dm
index e63038de4e88e..0924e61135a0e 100644
--- a/code/game/area/areas/away_content.dm
+++ b/code/game/area/areas/away_content.dm
@@ -8,7 +8,8 @@ Unused icons for new areas are "awaycontent1" ~ "awaycontent30"
name = "Strange Location"
icon_state = "away"
has_gravity = STANDARD_GRAVITY
- ambient_effects = AWAY_MISSION
+ ambience_index = AMBIENCE_AWAY
+ sound_environment = SOUND_ENVIRONMENT_ROOM
/area/awaymission/beach
name = "Beach"
@@ -16,7 +17,7 @@ Unused icons for new areas are "awaycontent1" ~ "awaycontent30"
dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
requires_power = FALSE
has_gravity = STANDARD_GRAVITY
- ambient_effects = list('sound/ambience/shore.ogg', 'sound/ambience/seag1.ogg','sound/ambience/seag2.ogg','sound/ambience/seag2.ogg','sound/ambience/ambiodd.ogg','sound/ambience/ambinice.ogg')
+ ambientsounds = list('sound/ambience/shore.ogg', 'sound/ambience/seag1.ogg','sound/ambience/seag2.ogg','sound/ambience/seag2.ogg','sound/ambience/ambiodd.ogg','sound/ambience/ambinice.ogg')
/area/awaymission/errorroom
name = "Super Secret Room"
diff --git a/code/game/area/areas/centcom.dm b/code/game/area/areas/centcom.dm
index 6ddce6afea2c9..addd6c27b27ad 100644
--- a/code/game/area/areas/centcom.dm
+++ b/code/game/area/areas/centcom.dm
@@ -7,8 +7,8 @@
dynamic_lighting = DYNAMIC_LIGHTING_FORCED
requires_power = FALSE
has_gravity = STANDARD_GRAVITY
- noteleport = TRUE
- blob_allowed = FALSE //Should go without saying, no blobs should take over centcom as a win condition.
+ teleport_restriction = TELEPORT_ALLOW_NONE
+ area_flags = VALID_TERRITORY | UNIQUE_AREA
flags_1 = NONE
/area/centcom/control
@@ -29,7 +29,7 @@
/area/centcom/holding
name = "Holding Facility"
-/area/centcom/supplypod/flyMeToTheMoon
+/area/centcom/supplypod/supplypod_temp_holding
name = "Supplypod Shipping lane"
icon_state = "supplypod_flight"
@@ -45,21 +45,35 @@
/area/centcom/supplypod/loading
name = "Supplypod Loading Facility"
icon_state = "supplypod_loading"
+ var/loading_id = ""
+
+/area/centcom/supplypod/loading/Initialize()
+ . = ..()
+ if(!loading_id)
+ CRASH("[type] created without a loading_id")
+ if(GLOB.supplypod_loading_bays[loading_id])
+ CRASH("Duplicate loading bay area: [type] ([loading_id])")
+ GLOB.supplypod_loading_bays[loading_id] = src
/area/centcom/supplypod/loading/one
name = "Bay #1"
+ loading_id = "1"
/area/centcom/supplypod/loading/two
name = "Bay #2"
+ loading_id = "2"
/area/centcom/supplypod/loading/three
name = "Bay #3"
+ loading_id = "3"
/area/centcom/supplypod/loading/four
name = "Bay #4"
+ loading_id = "4"
/area/centcom/supplypod/loading/ert
name = "ERT Bay"
+ loading_id = "5"
//THUNDERDOME
/area/tdome
@@ -106,7 +120,8 @@
dynamic_lighting = DYNAMIC_LIGHTING_FORCED
requires_power = FALSE
has_gravity = STANDARD_GRAVITY
- noteleport = TRUE
+ teleport_restriction = TELEPORT_ALLOW_NONE
+ area_flags = VALID_TERRITORY | UNIQUE_AREA
flags_1 = NONE
//Abductors
@@ -114,7 +129,8 @@
name = "Abductor Ship"
icon_state = "yellow"
requires_power = FALSE
- noteleport = TRUE
+ teleport_restriction = TELEPORT_ALLOW_ABDUCTORS
+ area_flags = VALID_TERRITORY | UNIQUE_AREA
has_gravity = STANDARD_GRAVITY
flags_1 = NONE
@@ -124,10 +140,10 @@
icon_state = "syndie-ship"
requires_power = FALSE
has_gravity = STANDARD_GRAVITY
- noteleport = TRUE
- blob_allowed = FALSE //Not... entirely sure this will ever come up... but if the bus makes blobs AND ops, it shouldn't aim for the ops to win.
+ teleport_restriction = TELEPORT_ALLOW_NONE
+ area_flags = VALID_TERRITORY | UNIQUE_AREA
flags_1 = NONE
- ambient_effects = HIGHSEC
+ ambience_index = AMBIENCE_DANGER
/area/syndicate_mothership/control
name = "Syndicate Control Room"
@@ -180,12 +196,12 @@
icon_state = "yellow"
requires_power = FALSE
has_gravity = STANDARD_GRAVITY
- noteleport = TRUE
- hidden = TRUE
- ambient_effects = REEBE
+ teleport_restriction = TELEPORT_ALLOW_CLOCKWORK
+ area_flags = VALID_TERRITORY | UNIQUE_AREA | HIDDEN_AREA
+ ambience_index = AMBIENCE_REEBE
/area/reebe/city_of_cogs
name = "Reebe - City of Cogs"
icon_state = "purple"
- hidden = FALSE
+ area_flags = VALID_TERRITORY | UNIQUE_AREA | HIDDEN_AREA
var/playing_ambience = FALSE
diff --git a/code/game/area/areas/holodeck.dm b/code/game/area/areas/holodeck.dm
index 6cf436908f7df..e9a371ea32bf9 100644
--- a/code/game/area/areas/holodeck.dm
+++ b/code/game/area/areas/holodeck.dm
@@ -2,8 +2,9 @@
name = "Holodeck"
icon_state = "Holodeck"
dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
- flags_1 = 0
- hidden = TRUE
+ flags_1 = NONE
+ area_flags = VALID_TERRITORY | UNIQUE_AREA | HIDDEN_AREA
+ sound_environment = SOUND_ENVIRONMENT_PADDED_CELL
var/obj/machinery/computer/holodeck/linked
var/restricted = 0 // if true, program goes on emag list
@@ -15,11 +16,11 @@
/area/holodeck/powered(var/chan)
if(!requires_power)
- return 1
+ return TRUE
if(always_unpowered)
- return 0
+ return FALSE
if(!linked)
- return 0
+ return FALSE
var/area/A = get_area(linked)
ASSERT(!istype(A, /area/holodeck))
return A.powered(chan)
@@ -47,76 +48,5 @@
/area/holodeck/rec_center
name = "\improper Recreational Holodeck"
-/area/holodeck/rec_center/offline
- name = "Holodeck - Offline"
-
-/area/holodeck/rec_center/court
- name = "Holodeck - Empty Court"
-
-/area/holodeck/rec_center/dodgeball
- name = "Holodeck - Dodgeball Court"
-
-/area/holodeck/rec_center/basketball
- name = "Holodeck - Basketball Court"
-
-/area/holodeck/rec_center/thunderdome
- name = "Holodeck - Thunderdome Arena"
-
-/area/holodeck/rec_center/beach
- name = "Holodeck - Beach"
-
-/area/holodeck/rec_center/lounge
- name = "Holodeck - Lounge"
-
-/area/holodeck/rec_center/medical
- name = "Holodeck - Emergency Medical"
-
-/area/holodeck/rec_center/pet_lounge
- name = "Holodeck - Pet Park"
-
-/area/holodeck/rec_center/firingrange
- name = "Holodeck - Firing Range"
-
-/area/holodeck/rec_center/school
- name = "Holodeck - Anime School"
-
-/area/holodeck/rec_center/chapelcourt
- name = "Holodeck - Chapel Courtroom"
-
-/area/holodeck/rec_center/spacechess
- name = "Holodeck - Space Chess"
-
-/area/holodeck/rec_center/kobayashi
- name = "Holodeck - Kobayashi Maru"
-
-/area/holodeck/rec_center/winterwonderland
- name = "Holodeck - Winter Wonderland"
-
-/area/holodeck/rec_center/photobooth
- name = "Holodeck - Photobooth"
-
-// Bad programs
-
-/area/holodeck/rec_center/burn
- name = "Holodeck - Atmospheric Burn Test"
- restricted = 1
-
-/area/holodeck/rec_center/wildlife
- name = "Holodeck - Wildlife Simulation"
- restricted = 1
-
-/area/holodeck/rec_center/bunker
- name = "Holodeck - Holdout Bunker"
- restricted = 1
-
-/area/holodeck/rec_center/anthophila
- name = "Holodeck - Anthophila"
- restricted = 1
-
-/area/holodeck/rec_center/refuel
- name = "Holodeck - Refueling Station"
- restricted = 1
-
-/area/holodeck/rec_center/thunderdome1218
- name = "Holodeck - 1218 AD"
- restricted = 1
+/area/holodeck/rec_center/offstation_one
+ name = "\improper Recreational Holodeck"
diff --git a/code/game/area/areas/mining.dm b/code/game/area/areas/mining.dm
index d05ee88e4c79c..5c5bdec326589 100644
--- a/code/game/area/areas/mining.dm
+++ b/code/game/area/areas/mining.dm
@@ -5,6 +5,7 @@
has_gravity = STANDARD_GRAVITY
lighting_colour_tube = "#ffe8d2"
lighting_colour_bulb = "#ffdcb7"
+ area_flags = VALID_TERRITORY | UNIQUE_AREA | FLORA_ALLOWED
/area/mine/explored
name = "Mine"
@@ -17,7 +18,11 @@
power_light = FALSE
outdoors = TRUE
flags_1 = NONE
- ambient_effects = MINING
+ ambience_index = AMBIENCE_MINING
+ min_ambience_cooldown = 70 SECONDS
+ max_ambience_cooldown = 220 SECONDS
+ sound_environment = SOUND_AREA_STANDARD_STATION
+ area_flags = VALID_TERRITORY | UNIQUE_AREA
/area/mine/unexplored
name = "Mine"
@@ -30,7 +35,10 @@
power_light = FALSE
outdoors = TRUE
flags_1 = NONE
- ambient_effects = MINING
+ ambience_index = AMBIENCE_MINING
+ min_ambience_cooldown = 70 SECONDS
+ max_ambience_cooldown = 220 SECONDS
+ area_flags = VALID_TERRITORY | UNIQUE_AREA | FLORA_ALLOWED | CAVES_ALLOWED
/area/mine/lobby
name = "Mining Station"
@@ -73,7 +81,7 @@
/area/mine/laborcamp/security
name = "Labor Camp Security"
icon_state = "security"
- ambient_effects = HIGHSEC
+ ambience_index = AMBIENCE_DANGER
//This is a placeholder for the lavaland sci area. Whoever is here after me, I have made you some additional areas to work with.
//You are free to rename these and change their icons. My job is done here.
@@ -112,6 +120,8 @@
icon_state = "mining"
has_gravity = STANDARD_GRAVITY
flags_1 = NONE
+ sound_environment = SOUND_AREA_LAVALAND
+ area_flags = VALID_TERRITORY | UNIQUE_AREA | FLORA_ALLOWED
/area/lavaland/surface
name = "Lavaland"
@@ -122,7 +132,9 @@
power_equip = FALSE
power_light = FALSE
requires_power = TRUE
- ambient_effects = MINING
+ ambience_index = AMBIENCE_MINING
+ min_ambience_cooldown = 70 SECONDS
+ max_ambience_cooldown = 220 SECONDS
/area/lavaland/underground
name = "Lavaland Caves"
@@ -133,8 +145,9 @@
power_environ = FALSE
power_equip = FALSE
power_light = FALSE
- ambient_effects = MINING
-
+ ambience_index = AMBIENCE_MINING
+ min_ambience_cooldown = 70 SECONDS
+ max_ambience_cooldown = 220 SECONDS
/area/lavaland/surface/outdoors
name = "Lavaland Wastes"
@@ -142,9 +155,13 @@
/area/lavaland/surface/outdoors/unexplored //monsters and ruins spawn here
icon_state = "unexplored"
+ area_flags = VALID_TERRITORY | UNIQUE_AREA | CAVES_ALLOWED | FLORA_ALLOWED | MOB_SPAWN_ALLOWED
/area/lavaland/surface/outdoors/unexplored/danger //megafauna will also spawn here
icon_state = "danger"
+ area_flags = VALID_TERRITORY | UNIQUE_AREA | CAVES_ALLOWED | FLORA_ALLOWED | MOB_SPAWN_ALLOWED | MEGAFAUNA_SPAWN_ALLOWED
+ map_generator = /datum/map_generator/cave_generator/lavaland
/area/lavaland/surface/outdoors/explored
name = "Lavaland Labor Camp"
+ area_flags = VALID_TERRITORY | UNIQUE_AREA
diff --git a/code/game/area/areas/ruins/_ruins.dm b/code/game/area/areas/ruins/_ruins.dm
index bc66413cb65c8..08481fb474bb8 100644
--- a/code/game/area/areas/ruins/_ruins.dm
+++ b/code/game/area/areas/ruins/_ruins.dm
@@ -4,10 +4,10 @@
name = "\improper Unexplored Location"
icon_state = "away"
has_gravity = STANDARD_GRAVITY
- hidden = TRUE
+ area_flags = HIDDEN_AREA | BLOBS_ALLOWED
dynamic_lighting = DYNAMIC_LIGHTING_FORCED
- ambient_effects = RUINS
-
+ ambience_index = AMBIENCE_RUINS
+ sound_environment = SOUND_ENVIRONMENT_STONEROOM
/area/ruin/unpowered
always_unpowered = FALSE
diff --git a/code/game/area/areas/ruins/lavaland.dm b/code/game/area/areas/ruins/lavaland.dm
index 2b877b313f9a2..7f58f29524748 100644
--- a/code/game/area/areas/ruins/lavaland.dm
+++ b/code/game/area/areas/ruins/lavaland.dm
@@ -6,7 +6,7 @@
/area/ruin/powered/clownplanet
name = "Clown Planet"
icon_state = "dk_yellow"
- ambient_effects = list('sound/ambience/clown.ogg')
+ ambientsounds = list('sound/ambience/clown.ogg')
/area/ruin/powered/animal_hospital
icon_state = "dk_yellow"
@@ -37,7 +37,7 @@
/area/ruin/unpowered/syndicate_lava_base
name = "Secret Base"
icon_state = "dk_yellow"
- ambient_effects = HIGHSEC
+ ambience_index = AMBIENCE_DANGER
/area/ruin/unpowered/syndicate_lava_base/engineering
name = "Syndicate Lavaland Engineering"
diff --git a/code/game/area/areas/ruins/space.dm b/code/game/area/areas/ruins/space.dm
index 86f34925feb8e..65fbe55e5be4c 100644
--- a/code/game/area/areas/ruins/space.dm
+++ b/code/game/area/areas/ruins/space.dm
@@ -2,7 +2,7 @@
/area/ruin/space
has_gravity = FALSE
- blob_allowed = FALSE //Nope, no winning in space as a blob. Gotta eat the station.
+ area_flags = UNIQUE_AREA
/area/ruin/space/has_grav
has_gravity = STANDARD_GRAVITY
@@ -10,21 +10,6 @@
/area/ruin/space/has_grav/powered
requires_power = FALSE
-
-/area/ruin/fakespace
- icon_state = "space"
- requires_power = TRUE
- always_unpowered = TRUE
- dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
- has_gravity = FALSE
- power_light = FALSE
- power_equip = FALSE
- power_environ = FALSE
- valid_territory = FALSE
- outdoors = TRUE
- ambient_effects = SPACE
- blob_allowed = FALSE
-
/////////////
/area/ruin/space/way_home
@@ -63,7 +48,7 @@
/area/ruin/space/has_grav/powered/aesthetic
name = "Aesthetic"
- ambient_effects = list('sound/ambience/ambivapor1.ogg')
+ ambientsounds = list('sound/ambience/ambivapor1.ogg')
//Ruin of Hotel
@@ -126,6 +111,10 @@
name = "Hotel Staff Room"
icon_state = "crew_quarters"
+/area/ruin/space/has_grav/hotel/secondary_solars
+ name = "Hotel Secondary Solar Control"
+ icon_state = "engine_smes"
+
@@ -244,6 +233,7 @@
name = "Beta Station Atmospherics"
icon_state = "red"
has_gravity = FALSE
+ ambience_index = AMBIENCE_ENGI
/area/ruin/space/has_grav/ancientstation/betanorth
name = "Beta Station North Corridor"
@@ -256,6 +246,7 @@
/area/ruin/space/has_grav/ancientstation/engi
name = "Charlie Station Engineering"
icon_state = "engine"
+ ambience_index = AMBIENCE_ENGI
/area/ruin/space/has_grav/ancientstation/comm
name = "Charlie Station Command"
@@ -368,7 +359,6 @@
name = "Ruskie DJ Station"
icon_state = "DJ"
has_gravity = STANDARD_GRAVITY
- blob_allowed = FALSE //Nope, no winning on the DJ station as a blob. Gotta eat the main station.
/area/ruin/space/djstation/solars
name = "DJ Station Solars"
@@ -381,7 +371,7 @@
/area/ruin/space/abandoned_tele
name = "Abandoned Teleporter"
icon_state = "teleporter"
- ambient_effects = list('sound/ambience/ambimalf.ogg', 'sound/ambience/signal.ogg')
+ ambientsounds = list('sound/ambience/ambimalf.ogg', 'sound/ambience/signal.ogg')
//OLD AI SAT
diff --git a/code/game/area/areas/shuttles.dm b/code/game/area/areas/shuttles.dm
index c829e1e259c16..70d78285d1b5d 100644
--- a/code/game/area/areas/shuttles.dm
+++ b/code/game/area/areas/shuttles.dm
@@ -8,18 +8,23 @@
dynamic_lighting = DYNAMIC_LIGHTING_FORCED
has_gravity = STANDARD_GRAVITY
always_unpowered = FALSE
- valid_territory = FALSE
- icon_state = "shuttle"
// Loading the same shuttle map at a different time will produce distinct area instances.
- unique = FALSE
+ area_flags = NONE
lighting_colour_tube = "#fff0dd"
lighting_colour_bulb = "#ffe1c1"
+ sound_environment = SOUND_ENVIRONMENT_ROOM
+ //The mobile port attached to this area
+ var/obj/docking_port/mobile/mobile_port
/area/shuttle/Initialize()
if(!canSmoothWithAreas)
canSmoothWithAreas = type
. = ..()
+/area/shuttle/Destroy()
+ mobile_port = null
+ . = ..()
+
/area/shuttle/PlaceOnTopReact(list/new_baseturfs, turf/fake_turf_type, flags)
. = ..()
if(length(new_baseturfs) > 1 || fake_turf_type)
@@ -27,14 +32,21 @@
if(ispath(new_baseturfs[1], /turf/open/floor/plating))
new_baseturfs.Insert(1, /turf/baseturf_skipover/shuttle)
+/area/shuttle/proc/link_to_shuttle(obj/docking_port/mobile/M)
+ mobile_port = M
+
+/area/shuttle/get_virtual_z(turf/T)
+ if(mobile_port && is_reserved_level(mobile_port.z))
+ return mobile_port.virtual_z
+ return ..(T)
+
////////////////////////////Multi-area shuttles////////////////////////////
////////////////////////////Syndicate infiltrator////////////////////////////
/area/shuttle/syndicate
name = "Syndicate Infiltrator"
- blob_allowed = FALSE
- ambient_effects = HIGHSEC
+ ambience_index = AMBIENCE_DANGER
canSmoothWithAreas = /area/shuttle/syndicate
/area/shuttle/syndicate/bridge
@@ -58,7 +70,6 @@
/area/shuttle/pirate
name = "Pirate Shuttle"
- blob_allowed = FALSE
requires_power = TRUE
canSmoothWithAreas = /area/shuttle/pirate
@@ -67,14 +78,12 @@
/area/shuttle/hunter
name = "Hunter Shuttle"
dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
- blob_allowed = FALSE
canSmoothWithAreas = /area/shuttle/hunter
////////////////////////////White Ship////////////////////////////
/area/shuttle/abandoned
name = "Abandoned Ship"
- blob_allowed = FALSE
requires_power = TRUE
canSmoothWithAreas = /area/shuttle/abandoned
@@ -115,41 +124,44 @@
/area/shuttle/arrival
name = "Arrival Shuttle"
- unique = TRUE // SSjob refers to this area for latejoiners
+ area_flags = UNIQUE_AREA// SSjob refers to this area for latejoiners
/area/shuttle/pod_1
name = "Escape Pod One"
+ area_flags = BLOBS_ALLOWED
/area/shuttle/pod_2
name = "Escape Pod Two"
+ area_flags = BLOBS_ALLOWED
/area/shuttle/pod_3
name = "Escape Pod Three"
+ area_flags = BLOBS_ALLOWED
/area/shuttle/pod_4
name = "Escape Pod Four"
+ area_flags = BLOBS_ALLOWED
/area/shuttle/mining
name = "Mining Shuttle"
- blob_allowed = FALSE
/area/shuttle/mining/large
name = "Mining Shuttle"
- blob_allowed = FALSE
requires_power = TRUE
/area/shuttle/science
name = "Science Shuttle"
- blob_allowed = FALSE
+ requires_power = TRUE
+
+/area/shuttle/exploration
+ name = "Exploration Shuttle"
requires_power = TRUE
/area/shuttle/labor
name = "Labor Camp Shuttle"
- blob_allowed = FALSE
/area/shuttle/supply
name = "Supply Shuttle"
- blob_allowed = FALSE
/area/shuttle/escape
name = "Emergency Shuttle"
@@ -159,11 +171,11 @@
/area/shuttle/escape/luxury
name = "Luxurious Emergency Shuttle"
- noteleport = TRUE
+ teleport_restriction = TELEPORT_ALLOW_NONE
/area/shuttle/escape/arena
name = "The Arena"
- noteleport = TRUE
+ teleport_restriction = TELEPORT_ALLOW_NONE
/area/shuttle/escape/meteor
name = "\proper a meteor with engines strapped to it"
@@ -171,34 +183,26 @@
/area/shuttle/transport
name = "Transport Shuttle"
- blob_allowed = FALSE
/area/shuttle/assault_pod
name = "Steel Rain"
- blob_allowed = FALSE
/area/shuttle/sbc_starfury
name = "SBC Starfury"
- blob_allowed = FALSE
/area/shuttle/sbc_fighter1
name = "SBC Fighter 1"
- blob_allowed = FALSE
/area/shuttle/sbc_fighter2
name = "SBC Fighter 2"
- blob_allowed = FALSE
/area/shuttle/sbc_corvette
name = "SBC corvette"
- blob_allowed = FALSE
/area/shuttle/syndicate_scout
name = "Syndicate Scout"
- blob_allowed = FALSE
/area/shuttle/caravan
- blob_allowed = FALSE
requires_power = TRUE
/area/shuttle/caravan/syndicate1
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 48e034c071b05..68458f190ea26 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -1,3 +1,4 @@
+
/**
* The base type for nearly all physical objects in SS13
@@ -7,6 +8,7 @@
/atom
layer = TURF_LAYER
plane = GAME_PLANE
+ appearance_flags = TILE_BOUND
var/level = 2
///If non-null, overrides a/an/some in all cases
@@ -46,6 +48,9 @@
///vis overlays managed by SSvis_overlays to automaticaly turn them like other overlays
var/list/managed_vis_overlays
+ ///overlays managed by update_overlays() to prevent removing overlays that weren't added by the same proc
+ var/list/managed_overlays
+
///Proximity monitor associated with this atom
var/datum/proximity_monitor/proximity_monitor
///Cooldown tick timer for buckle messages
@@ -73,6 +78,25 @@
///Bitfield for how the atom handles materials.
var/material_flags = NONE
+ var/flags_ricochet = NONE
+ ///When a projectile tries to ricochet off this atom, the projectile ricochet chance is multiplied by this
+ var/ricochet_chance_mod = 1
+ ///When a projectile ricochets off this atom, it deals the normal damage * this modifier to this atom
+ var/ricochet_damage_mod = 0.33
+
+ /// Last name used to calculate a color for the chatmessage overlays
+ var/chat_color_name
+ /// Last color calculated for the the chatmessage overlays
+ var/chat_color
+
+ ///Mobs that are currently do_after'ing this atom, to be cleared from on Destroy()
+ var/list/targeted_by
+
+ ///AI controller that controls this atom. type on init, then turned into an instance during runtime
+ var/datum/ai_controller/ai_controller
+
+ /// Lazylist of all messages currently on this atom
+ var/list/chat_messages
/**
* Called when an atom is created in byond (built in engine proc)
@@ -95,7 +119,7 @@
var/do_initialize = SSatoms.initialized
if(do_initialize != INITIALIZATION_INSSATOMS)
args[1] = do_initialize == INITIALIZATION_INNEW_MAPLOAD
- if(SSatoms.InitAtom(src, args))
+ if(SSatoms.InitAtom(src, FALSE, args))
//we were deleted
return
@@ -138,6 +162,9 @@
stack_trace("Warning: [src]([type]) initialized multiple times!")
flags_1 |= INITIALIZED_1
+ if(loc)
+ SEND_SIGNAL(loc, COMSIG_ATOM_CREATED, src) /// Sends a signal that the new atom `src`, has been created at `loc`
+
//atom color stuff
if(color)
add_atom_colour(color, FIXED_COLOUR_PRIORITY)
@@ -163,6 +190,7 @@
set_custom_materials(temp_list)
ComponentInitialize()
+ InitializeAIController()
return INITIALIZE_HINT_NORMAL
@@ -202,19 +230,39 @@
AA.remove_from_hud(src)
if(reagents)
- qdel(reagents)
+ QDEL_NULL(reagents)
orbiters = null // The component is attached to us normaly and will be deleted elsewhere
LAZYCLEARLIST(overlays)
LAZYCLEARLIST(priority_overlays)
+ LAZYCLEARLIST(managed_overlays)
+
+ for(var/i in targeted_by)
+ var/mob/M = i
+ LAZYREMOVE(M.do_afters, src)
+
+ targeted_by = null
QDEL_NULL(light)
+ QDEL_NULL(ai_controller)
return ..()
/atom/proc/handle_ricochet(obj/item/projectile/P)
- return
+ var/turf/p_turf = get_turf(P)
+ var/face_direction = get_dir(src, p_turf)
+ var/face_angle = dir2angle(face_direction)
+ var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (P.Angle + 180))
+ var/a_incidence_s = abs(incidence_s)
+ if(a_incidence_s > 90 && a_incidence_s < 270)
+ return FALSE
+ if((P.flag in list("bullet", "bomb")) && P.ricochet_incidence_leeway)
+ if((a_incidence_s < 90 && a_incidence_s < 90 - P.ricochet_incidence_leeway) || (a_incidence_s > 270 && a_incidence_s -270 > P.ricochet_incidence_leeway))
+ return
+ var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s)
+ P.setAngle(new_angle_s)
+ return TRUE
///Can the mover object pass this atom, while heading for the target turf
/atom/proc/CanPass(atom/movable/mover, turf/target)
@@ -314,6 +362,7 @@
L.transferItemToLoc(M, src)
else
M.forceMove(src)
+ parts_list.Cut()
///Hook for multiz???
/atom/proc/update_multiz(prune_on_fail = FALSE)
@@ -321,13 +370,27 @@
///Take air from the passed in gas mixture datum
/atom/proc/assume_air(datum/gas_mixture/giver)
- qdel(giver)
+ return null
+
+/atom/proc/assume_air_moles(datum/gas_mixture/giver, moles)
+ return null
+
+/atom/proc/assume_air_ratio(datum/gas_mixture/giver, ratio)
return null
///Remove air from this atom
/atom/proc/remove_air(amount)
return null
+/atom/proc/remove_air_ratio(ratio)
+ return null
+
+/atom/proc/transfer_air(datum/gas_mixture/taker, amount)
+ return null
+
+/atom/proc/transfer_air_ratio(datum/gas_mixture/taker, ratio)
+ return null
+
///Return the current air environment in this atom
/atom/proc/return_air()
if(loc)
@@ -473,14 +536,30 @@
/// Updates the icon of the atom
/atom/proc/update_icon()
+ SIGNAL_HANDLER
+
// I expect we're going to need more return flags and options in this proc
var/signalOut = SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON)
if(!(signalOut & COMSIG_ATOM_NO_UPDATE_ICON_STATE))
update_icon_state()
+ if(!(signalOut & COMSIG_ATOM_NO_UPDATE_OVERLAYS))
+ var/list/new_overlays = update_overlays()
+ if(managed_overlays)
+ cut_overlay(managed_overlays)
+ managed_overlays = null
+ if(length(new_overlays))
+ managed_overlays = new_overlays
+ add_overlay(new_overlays)
/// Updates the icon state of the atom
/atom/proc/update_icon_state()
+/// Updates the overlays of the atom
+/atom/proc/update_overlays()
+ SHOULD_CALL_PARENT(TRUE)
+ . = list()
+ SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_OVERLAYS, .)
+
/**
* An atom we are buckled or is contained within us has tried to move
*
@@ -493,10 +572,6 @@
to_chat(user, "You can't move while buckled to [src]!")
return
-/// Return true if this atoms contents should not have ex_act called on ex_act
-/atom/proc/prevent_content_explosion()
- return FALSE
-
/// Handle what happens when your contents are exploded by a bomb
/atom/proc/contents_explosion(severity, target)
return //For handling the effects of explosions on contents that would not normally be effected
@@ -535,6 +610,7 @@
* throw lots of items around - singularity being a notable example)
*/
/atom/proc/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
+ SEND_SIGNAL(src, COMSIG_ATOM_HITBY, AM, skipcatch, hitpush, blocked, throwingdatum)
if(density && !has_gravity(AM)) //thrown stuff bounces off dense stuff in no grav, unless the thrown stuff ends up inside what it hit(embedding, bola, etc...).
addtimer(CALLBACK(src, .proc/hitby_react, AM), 2)
@@ -663,6 +739,12 @@
/atom/proc/ratvar_act()
SEND_SIGNAL(src, COMSIG_ATOM_RATVAR_ACT)
+/**
+ * Called when lighteater is called on this.
+ */
+/atom/proc/lighteater_act(obj/item/light_eater/light_eater)
+ return
+
/**
* Respond to the eminence clicking on our atom
*
@@ -886,6 +968,8 @@
VV_DROPDOWN_OPTION(VV_HK_ADD_REAGENT, "Add Reagent")
VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EMP, "EMP Pulse")
VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EXPLOSION, "Explosion")
+ VV_DROPDOWN_OPTION(VV_HK_EDIT_FILTERS, "Edit Filters")
+ VV_DROPDOWN_OPTION(VV_HK_ADD_AI, "Add AI controller")
/atom/vv_do_topic(list/href_list)
. = ..()
@@ -948,6 +1032,17 @@
var/newname = input(usr, "What do you want to rename this to?", "Automatic Rename") as null|text
if(newname)
vv_auto_rename(newname)
+ if(href_list[VV_HK_ADD_AI])
+ if(!check_rights(R_VAREDIT))
+ return
+ var/result = input(usr, "Choose the AI controller to apply to this atom WARNING: Not all AI works on all atoms.", "AI controller") as null|anything in subtypesof(/datum/ai_controller)
+ if(!result)
+ return
+ ai_controller = new result(src)
+
+ if(href_list[VV_HK_EDIT_FILTERS] && check_rights(R_VAREDIT))
+ var/client/C = usr.client
+ C?.open_filter_editor(src)
/atom/vv_get_header()
. = ..()
@@ -1006,6 +1101,10 @@
* Must return parent proc ..() in the end if overridden
*/
/atom/proc/tool_act(mob/living/user, obj/item/I, tool_type)
+ var/signal_result
+ signal_result = SEND_SIGNAL(src, COMSIG_ATOM_TOOL_ACT(tool_type), user, I)
+ if(signal_result & COMPONENT_BLOCK_TOOL_ATTACK) // The COMSIG_ATOM_TOOL_ACT signal is blocking the act
+ return TOOL_ACT_SIGNAL_BLOCKING
switch(tool_type)
if(TOOL_CROWBAR)
return crowbar_act(user, I)
@@ -1043,7 +1142,7 @@
///Screwdriver act
/atom/proc/screwdriver_act(mob/living/user, obj/item/I)
- SEND_SIGNAL(src, COMSIG_ATOM_SCREWDRIVER_ACT, user, I)
+ return
///Wrench act
/atom/proc/wrench_act(mob/living/user, obj/item/I)
@@ -1160,14 +1259,14 @@
var/reverse_message = "has been [what_done] by [ssource][postfix]"
target.log_message(reverse_message, LOG_ATTACK, color="orange", log_globally=FALSE)
-/atom/movable/proc/add_filter(name,priority,list/params)
+/atom/proc/add_filter(name,priority,list/params)
LAZYINITLIST(filter_data)
var/list/p = params.Copy()
p["priority"] = priority
filter_data[name] = p
update_filters()
-/atom/movable/proc/update_filters()
+/atom/proc/update_filters()
filters = null
filter_data = sortTim(filter_data, /proc/cmp_filter_data_priority, TRUE)
for(var/f in filter_data)
@@ -1175,15 +1274,48 @@
var/list/arguments = data.Copy()
arguments -= "priority"
filters += filter(arglist(arguments))
+ UNSETEMPTY(filter_data)
+
+/atom/proc/transition_filter(name, time, list/new_params, easing, loop)
+ var/filter = get_filter(name)
+ if(!filter)
+ return
+
+ var/list/old_filter_data = filter_data[name]
+
+ var/list/params = old_filter_data.Copy()
+ for(var/thing in new_params)
+ params[thing] = new_params[thing]
-/atom/movable/proc/get_filter(name)
+ animate(filter, new_params, time = time, easing = easing, loop = loop)
+ for(var/param in params)
+ filter_data[name][param] = params[param]
+
+/atom/proc/change_filter_priority(name, new_priority)
+ if(!filter_data || !filter_data[name])
+ return
+
+ filter_data[name]["priority"] = new_priority
+ update_filters()
+
+/atom/proc/get_filter(name)
if(filter_data && filter_data[name])
return filters[filter_data.Find(name)]
-/atom/movable/proc/remove_filter(name)
- if(filter_data && filter_data[name])
- filter_data -= name
- update_filters()
+/atom/proc/remove_filter(name_or_names)
+ if(!filter_data)
+ return
+
+ var/list/names = islist(name_or_names) ? name_or_names : list(name_or_names)
+
+ for(var/name in names)
+ if(filter_data[name])
+ filter_data -= name
+ update_filters()
+
+/atom/proc/clear_filters()
+ filter_data = null
+ filters = null
/atom/proc/intercept_zImpact(atom/movable/AM, levels = 1)
. |= SEND_SIGNAL(src, COMSIG_ATOM_INTERCEPT_Z_FALL, AM, levels)
@@ -1213,3 +1345,28 @@
*/
/atom/proc/rust_heretic_act()
return
+
+/**
+ * Used to set something as 'open' if it's being used as a supplypod
+ *
+ * Override this if you want an atom to be usable as a supplypod.
+ */
+/atom/proc/setOpened()
+ return
+
+/**
+ * Used to set something as 'closed' if it's being used as a supplypod
+ *
+ * Override this if you want an atom to be usable as a supplypod.
+ */
+/atom/proc/setClosed()
+ return
+
+/**
+* Instantiates the AI controller of this atom. Override this if you want to assign variables first.
+*
+* This will work fine without manually passing arguments.
++*/
+/atom/proc/InitializeAIController()
+ if(ai_controller)
+ ai_controller = new ai_controller(src)
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index c9cf118809854..d8eec70b69730 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -41,6 +41,56 @@
var/zfalling = FALSE
+ /// Either FALSE, [EMISSIVE_BLOCK_GENERIC], or [EMISSIVE_BLOCK_UNIQUE]
+ var/blocks_emissive = FALSE
+ ///Internal holder for emissive blocker object, do not use directly use blocks_emissive
+ var/atom/movable/emissive_blocker/em_block
+ /**
+ * an associative lazylist of relevant nested contents by "channel", the list is of the form: list(channel = list(important nested contents of that type))
+ * each channel has a specific purpose and is meant to replace potentially expensive nested contents iteration
+ * do NOT add channels to this for little reason as it can add considerable memory usage.
+ */
+ var/list/important_recursive_contents
+
+
+/atom/movable/Initialize(mapload)
+ . = ..()
+ switch(blocks_emissive)
+ if(EMISSIVE_BLOCK_GENERIC)
+ var/mutable_appearance/gen_emissive_blocker = mutable_appearance(icon, icon_state, EMISSIVE_BLOCKER_LAYER, EMISSIVE_BLOCKER_PLANE)
+ gen_emissive_blocker.dir = dir
+ gen_emissive_blocker.alpha = alpha
+ gen_emissive_blocker.appearance_flags |= appearance_flags
+ add_overlay(list(gen_emissive_blocker))
+ if(EMISSIVE_BLOCK_UNIQUE)
+ render_target = ref(src)
+ em_block = new(src, render_target)
+
+ QDEL_NULL(em_block)
+
+ if(pulling)
+ stop_pulling()
+
+
+/atom/movable/proc/update_emissive_block()
+ if(!blocks_emissive)
+ return
+ else if (blocks_emissive == EMISSIVE_BLOCK_GENERIC)
+ var/mutable_appearance/gen_emissive_blocker = mutable_appearance(icon, icon_state, EMISSIVE_BLOCKER_LAYER, EMISSIVE_BLOCKER_PLANE)
+ gen_emissive_blocker.dir = dir
+ gen_emissive_blocker.alpha = alpha
+ gen_emissive_blocker.appearance_flags |= appearance_flags
+ return gen_emissive_blocker
+ else if(blocks_emissive == EMISSIVE_BLOCK_UNIQUE)
+ if(!em_block && !QDELETED(src))
+ render_target = ref(src)
+ em_block = new(src, render_target)
+ return em_block
+
+/atom/movable/update_overlays()
+ . = ..()
+ . += update_emissive_block()
+
/atom/movable/proc/can_zFall(turf/source, levels = 1, turf/target, direction)
if(!direction)
direction = DOWN
@@ -334,8 +384,8 @@
stop_pulling()
else
var/pull_dir = get_dir(src, pulling)
- //puller and pullee more than one tile away or in diagonal position
- if(get_dist(src, pulling) > 1 || (moving_diagonally != SECOND_DIAG_STEP && ((pull_dir - 1) & pull_dir)))
+ //puller and pullee more than one tile away or in diagonal position and whatever the pullee is pulling isn't already moving from a pull as it'll most likely result in an infinite loop a la ouroborus.
+ if(!pulling.pulling?.moving_from_pull && (get_dist(src, pulling) > 1 || (moving_diagonally != SECOND_DIAG_STEP && ((pull_dir - 1) & pull_dir))))
pulling.moving_from_pull = src
pulling.Move(T, get_dir(pulling, T)) //the pullee tries to reach our previous position
pulling.moving_from_pull = null
@@ -381,6 +431,10 @@
orbiting.end_orbit(src)
orbiting = null
+ LAZYCLEARLIST(important_recursive_contents)
+
+ vis_contents.Cut()
+
// Make sure you know what you're doing if you call this, this is intended to only be called by byond directly.
// You probably want CanPass()
/atom/movable/Cross(atom/movable/AM)
@@ -408,7 +462,7 @@
SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, A)
. = ..()
if(!QDELETED(throwing))
- throwing.hit_atom(A)
+ throwing.finalize(hit = TRUE, target = A)
. = TRUE
if(QDELETED(A))
return
@@ -416,6 +470,9 @@
/atom/movable/proc/forceMove(atom/destination)
. = FALSE
+ if(destination == null) //destination destroyed due to explosion
+ return
+
if(destination)
. = doMove(destination)
else
@@ -526,15 +583,19 @@
/atom/movable/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked, datum/thrownthing/throwingdatum)
if(!anchored && hitpush && (!throwingdatum || (throwingdatum.force >= (move_resist * MOVE_FORCE_PUSH_RATIO))))
step(src, AM.dir)
- ..()
+ ..(AM, skipcatch, hitpush, blocked, throwingdatum)
/atom/movable/proc/safe_throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG)
if((force < (move_resist * MOVE_FORCE_THROW_RATIO)) || (move_resist == INFINITY))
return
return throw_at(target, range, speed, thrower, spin, diagonals_first, callback, force)
-/atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG) //If this returns FALSE then callback will not be called.
+/atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, quickstart = TRUE) //If this returns FALSE then callback will not be called.
. = FALSE
+
+ if(QDELETED(src))
+ CRASH("Qdeleted thing being thrown around.")
+
if (!target || speed <= 0)
return
@@ -570,20 +631,14 @@
. = TRUE // No failure conditions past this point.
- var/datum/thrownthing/TT = new()
- TT.thrownthing = src
- TT.target = target
- TT.target_turf = get_turf(target)
- TT.init_dir = get_dir(src, target)
- TT.maxrange = range
- TT.speed = speed
- TT.thrower = thrower
- TT.diagonals_first = diagonals_first
- TT.force = force
- TT.callback = callback
- if(!QDELETED(thrower))
- TT.target_zone = thrower.zone_selected
-
+ var/target_zone
+ if(QDELETED(thrower) || !istype(thrower))
+ thrower = null //Let's not pass an invalid reference.
+ else
+ target_zone = thrower.zone_selected
+
+ var/datum/thrownthing/TT = new(src, target, get_dir(src, target), range, speed, thrower, diagonals_first, force, callback, target_zone)
+
var/dist_x = abs(target.x - src.x)
var/dist_y = abs(target.y - src.y)
var/dx = (target.x > src.x) ? EAST : WEST
@@ -608,6 +663,7 @@
if(pulledby)
pulledby.stop_pulling()
+ movement_type |= THROWN
throwing = TT
if(spin)
@@ -617,7 +673,9 @@
SSthrowing.processing[src] = TT
if (SSthrowing.state == SS_PAUSED && length(SSthrowing.currentrun))
SSthrowing.currentrun[src] = TT
- TT.tick()
+
+ if(quickstart)
+ TT.tick()
/atom/movable/proc/handle_buckled_mob_movement(newloc,direct)
for(var/m in buckled_mobs)
@@ -678,10 +736,13 @@
break
. = dense_object_backup
-//called when a mob resists while inside a container that is itself inside something.
-/atom/movable/proc/relay_container_resist(mob/living/user, obj/O)
+//Called when something resists while this atom is its loc
+/atom/movable/proc/container_resist(mob/living/user)
return
+//Called when a mob resists while inside a container that is itself inside something.
+/atom/movable/proc/relay_container_resist(mob/living/user, obj/O)
+ return
/atom/movable/proc/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect)
if(!no_effect && (visual_effect_icon || used_item))
@@ -761,9 +822,8 @@
if(throwing)
return
if(on && !(movement_type & FLOATING))
- animate(src, pixel_y = pixel_y + 2, time = 10, loop = -1)
- sleep(10)
- animate(src, pixel_y = pixel_y - 2, time = 10, loop = -1)
+ animate(src, pixel_y = 2, time = 10, loop = -1, flags = ANIMATION_RELATIVE)
+ animate(pixel_y = -2, time = 10, loop = -1, flags = ANIMATION_RELATIVE)
setMovetype(movement_type | FLOATING)
else if (!on && (movement_type & FLOATING))
animate(src, pixel_y = initial(pixel_y), time = 10)
@@ -904,3 +964,40 @@
animate(I, alpha = 175, pixel_x = to_x, pixel_y = to_y, time = 3, transform = M, easing = CUBIC_EASING)
sleep(1)
animate(I, alpha = 0, transform = matrix(), time = 1)
+
+/atom/movable/proc/get_spawner_desc()
+ return name
+
+/atom/movable/proc/get_spawner_flavour_text()
+ return desc
+
+/atom/movable/proc/on_hearing_sensitive_trait_loss()
+ SIGNAL_HANDLER
+
+ UnregisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_HEARING_SENSITIVE))
+ for(var/atom/movable/location as anything in get_nested_locs(src) + src)
+ LAZYREMOVEASSOC(location.important_recursive_contents, RECURSIVE_CONTENTS_HEARING_SENSITIVE, src)
+
+///allows this movable to hear and adds itself to the important_recursive_contents list of itself and every movable loc its in
+/atom/movable/proc/become_hearing_sensitive(trait_source = TRAIT_GENERIC)
+ if(!HAS_TRAIT(src, TRAIT_HEARING_SENSITIVE))
+ RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_HEARING_SENSITIVE), .proc/on_hearing_sensitive_trait_loss)
+ for(var/atom/movable/location as anything in get_nested_locs(src) + src)
+ LAZYADDASSOCLIST(location.important_recursive_contents, RECURSIVE_CONTENTS_HEARING_SENSITIVE, src)
+ ADD_TRAIT(src, TRAIT_HEARING_SENSITIVE, trait_source)
+
+/atom/movable/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
+ . = ..()
+ if(LAZYLEN(arrived.important_recursive_contents))
+ var/list/nested_locs = get_nested_locs(src) + src
+ for(var/channel in arrived.important_recursive_contents)
+ for(var/atom/movable/location as anything in nested_locs)
+ LAZYORASSOCLIST(location.important_recursive_contents, channel, arrived.important_recursive_contents[channel])
+
+/atom/movable/Exited(atom/movable/gone, direction)
+ . = ..()
+ if(LAZYLEN(gone.important_recursive_contents))
+ var/list/nested_locs = get_nested_locs(src) + src
+ for(var/channel in gone.important_recursive_contents)
+ for(var/atom/movable/location as anything in nested_locs)
+ LAZYREMOVEASSOC(location.important_recursive_contents, channel, gone.important_recursive_contents[channel])
diff --git a/code/game/communications.dm b/code/game/communications.dm
index 696b942434f75..50e2173470d11 100644
--- a/code/game/communications.dm
+++ b/code/game/communications.dm
@@ -101,6 +101,7 @@ GLOBAL_LIST_INIT(radiochannels, list(
RADIO_CHANNEL_SYNDICATE = FREQ_SYNDICATE,
RADIO_CHANNEL_SUPPLY = FREQ_SUPPLY,
RADIO_CHANNEL_SERVICE = FREQ_SERVICE,
+ RADIO_CHANNEL_EXPLORATION = FREQ_EXPLORATION,
RADIO_CHANNEL_AI_PRIVATE = FREQ_AI_PRIVATE,
RADIO_CHANNEL_CTF_RED = FREQ_CTF_RED,
RADIO_CHANNEL_CTF_BLUE = FREQ_CTF_BLUE
@@ -116,6 +117,7 @@ GLOBAL_LIST_INIT(reverseradiochannels, list(
"[FREQ_CENTCOM]" = RADIO_CHANNEL_CENTCOM,
"[FREQ_SYNDICATE]" = RADIO_CHANNEL_SYNDICATE,
"[FREQ_SUPPLY]" = RADIO_CHANNEL_SUPPLY,
+ "[FREQ_EXPLORATION]" = RADIO_CHANNEL_EXPLORATION,
"[FREQ_SERVICE]" = RADIO_CHANNEL_SERVICE,
"[FREQ_AI_PRIVATE]" = RADIO_CHANNEL_AI_PRIVATE,
"[FREQ_CTF_RED]" = RADIO_CHANNEL_CTF_RED,
@@ -123,8 +125,9 @@ GLOBAL_LIST_INIT(reverseradiochannels, list(
))
/datum/radio_frequency
- var/frequency as num
- var/list/list/obj/devices = list()
+ var/frequency
+ /// List of filters -> list of devices
+ var/list/list/datum/weakref/devices = list()
/datum/radio_frequency/New(freq)
frequency = freq
@@ -154,14 +157,18 @@ GLOBAL_LIST_INIT(reverseradiochannels, list(
//Send the data
for(var/current_filter in filter_list)
- for(var/obj/device in devices[current_filter])
+ for(var/datum/weakref/device_ref as anything in devices[current_filter])
+ var/obj/device = device_ref.resolve()
+ if(!device)
+ devices[current_filter] -= device_ref
+ continue
if(device == source)
continue
if(range)
var/turf/end_point = get_turf(device)
if(!end_point)
continue
- if(start_point.z != end_point.z || (range > 0 && get_dist(start_point, end_point) > range))
+ if(start_point.get_virtual_z_level() != end_point.get_virtual_z_level() || (range > 0 && get_dist(start_point, end_point) > range))
continue
device.receive_signal(signal)
@@ -172,7 +179,7 @@ GLOBAL_LIST_INIT(reverseradiochannels, list(
var/list/devices_line = devices[filter]
if(!devices_line)
devices[filter] = devices_line = list()
- devices_line += device
+ devices_line += WEAKREF(device)
/datum/radio_frequency/proc/remove_listener(obj/device)
@@ -180,7 +187,7 @@ GLOBAL_LIST_INIT(reverseradiochannels, list(
var/list/devices_line = devices[devices_filter]
if(!devices_line)
devices -= devices_filter
- devices_line -= device
+ devices_line -= WEAKREF(device)
if(!devices_line.len)
devices -= devices_filter
diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm
index a12ca8ee95bb2..1a0e5b05fc668 100644
--- a/code/game/data_huds.dm
+++ b/code/game/data_huds.dm
@@ -72,8 +72,8 @@
/datum/atom_hud/ai_detector/add_hud_to(mob/M)
..()
if(M && (hudusers.len == 1))
- for(var/V in GLOB.aiEyes)
- var/mob/camera/aiEye/E = V
+ for(var/V in GLOB.ai_eyes)
+ var/mob/camera/ai_eye/E = V
E.update_ai_detect_hud()
/* MED/SEC/DIAG HUD HOOKS */
@@ -91,14 +91,14 @@
//called when a carbon changes virus
/mob/living/carbon/proc/check_virus()
var/threat
- var/severity
+ var/danger
for(var/thing in diseases)
var/datum/disease/D = thing
if(!(D.visibility_flags & HIDDEN_SCANNER))
- if(!threat || get_disease_severity_value(D.severity) > threat) //a buffing virus gets an icon
- threat = get_disease_severity_value(D.severity)
- severity = D.severity
- return severity
+ if(!threat || get_disease_danger_value(D.danger) > threat) //a buffing virus gets an icon
+ threat = get_disease_danger_value(D.danger)
+ danger = D.danger
+ return danger
//helper for getting the appropriate health status
/proc/RoundHealth(mob/living/M)
@@ -160,9 +160,12 @@
//called when a living mob changes health
/mob/living/proc/med_hud_set_health()
var/image/holder = hud_list[HEALTH_HUD]
- holder.icon_state = "hud[RoundHealth(src)]"
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - world.icon_size
+ if(holder)
+ holder.icon_state = "hud[RoundHealth(src)]"
+ var/icon/I = icon(icon, icon_state, dir)
+ holder.pixel_y = I.Height() - world.icon_size
+ else
+ stack_trace("[src] does not have a HEALTH_HUD but updates it!")
//for carbon suit sensors
/mob/living/carbon/med_hud_set_health()
@@ -171,49 +174,55 @@
//called when a carbon changes stat, virus or XENO_HOST
/mob/living/proc/med_hud_set_status()
var/image/holder = hud_list[STATUS_HUD]
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - world.icon_size
- if(stat == DEAD || (HAS_TRAIT(src, TRAIT_FAKEDEATH)))
- holder.icon_state = "huddead"
+ if(holder)
+ var/icon/I = icon(icon, icon_state, dir)
+ holder.pixel_y = I.Height() - world.icon_size
+ if(stat == DEAD || (HAS_TRAIT(src, TRAIT_FAKEDEATH)))
+ holder.icon_state = "huddead"
+ else
+ holder.icon_state = "hudhealthy"
else
- holder.icon_state = "hudhealthy"
+ stack_trace("[src] does not have a HEALTH_HUD but updates it!")
/mob/living/carbon/med_hud_set_status()
var/image/holder = hud_list[STATUS_HUD]
- var/icon/I = icon(icon, icon_state, dir)
- var/virus_threat = check_virus()
- holder.pixel_y = I.Height() - world.icon_size
- if(HAS_TRAIT(src, TRAIT_XENO_HOST))
- holder.icon_state = "hudxeno"
- else if(stat == DEAD || (HAS_TRAIT(src, TRAIT_FAKEDEATH)))
- if(tod)
- var/tdelta = round(world.time - timeofdeath)
- if(tdelta < (DEFIB_TIME_LIMIT * 10))
- holder.icon_state = "huddefib"
- return
- holder.icon_state = "huddead"
+ if(holder)
+ var/icon/I = icon(icon, icon_state, dir)
+ var/virus_threat = check_virus()
+ holder.pixel_y = I.Height() - world.icon_size
+ if(HAS_TRAIT(src, TRAIT_XENO_HOST))
+ holder.icon_state = "hudxeno"
+ else if(stat == DEAD || (HAS_TRAIT(src, TRAIT_FAKEDEATH)))
+ if(tod)
+ var/tdelta = round(world.time - timeofdeath)
+ if(tdelta < (DEFIB_TIME_LIMIT * 10))
+ holder.icon_state = "huddefib"
+ return
+ holder.icon_state = "huddead"
+ else
+ switch(virus_threat)
+ if(DISEASE_PANDEMIC)
+ holder.icon_state = "hudill6"
+ if(DISEASE_BIOHAZARD)
+ holder.icon_state = "hudill5"
+ if(DISEASE_DANGEROUS)
+ holder.icon_state = "hudill4"
+ if(DISEASE_HARMFUL)
+ holder.icon_state = "hudill3"
+ if(DISEASE_MEDIUM)
+ holder.icon_state = "hudill2"
+ if(DISEASE_MINOR)
+ holder.icon_state = "hudill1"
+ if(DISEASE_NONTHREAT)
+ holder.icon_state = "hudill0"
+ if(DISEASE_POSITIVE)
+ holder.icon_state = "hudbuff"
+ if(DISEASE_BENEFICIAL)
+ holder.icon_state = "hudbuff2"
+ if(null)
+ holder.icon_state = "hudhealthy"
else
- switch(virus_threat)
- if(DISEASE_SEVERITY_PANDEMIC)
- holder.icon_state = "hudill6"
- if(DISEASE_SEVERITY_BIOHAZARD)
- holder.icon_state = "hudill5"
- if(DISEASE_SEVERITY_DANGEROUS)
- holder.icon_state = "hudill4"
- if(DISEASE_SEVERITY_HARMFUL)
- holder.icon_state = "hudill3"
- if(DISEASE_SEVERITY_MEDIUM)
- holder.icon_state = "hudill2"
- if(DISEASE_SEVERITY_MINOR)
- holder.icon_state = "hudill1"
- if(DISEASE_SEVERITY_NONTHREAT)
- holder.icon_state = "hudill0"
- if(DISEASE_SEVERITY_POSITIVE)
- holder.icon_state = "hudbuff"
- if(DISEASE_SEVERITY_BENEFICIAL)
- holder.icon_state = "hudbuff2"
- if(null)
- holder.icon_state = "hudhealthy"
+ stack_trace("[src] does not have a HEALTH_HUD but updates it!")
/***********************************************
@@ -247,7 +256,7 @@
var/icon/IC = icon(icon, icon_state, dir)
holder.pixel_y = IC.Height() - world.icon_size
holder.icon_state = "hud_imp_chem"
- if(HAS_TRAIT(src, TRAIT_MINDSHIELD) && !istype(src.get_item_by_slot(SLOT_HEAD), /obj/item/clothing/head/foilhat)) //tinfoil hats interfere with implant detection
+ if(HAS_TRAIT(src, TRAIT_MINDSHIELD) && !istype(src.get_item_by_slot(ITEM_SLOT_HEAD), /obj/item/clothing/head/foilhat)) //tinfoil hats interfere with implant detection
holder = hud_list[IMPLOYAL_HUD]
var/icon/IC = icon(icon, icon_state, dir)
holder.pixel_y = IC.Height() - world.icon_size
@@ -262,7 +271,7 @@
var/datum/data/record/R = find_record("name", perpname, GLOB.data_core.security)
if(R)
switch(R.fields["criminal"])
- if("*Arrest*")
+ if("Arrest")
holder.icon_state = "hudwanted"
return
if("Incarcerated")
@@ -274,6 +283,12 @@
if("Discharged")
holder.icon_state = "huddischarged"
return
+ if("Search")
+ holder.icon_state = "hudsearch"
+ return
+ if("Monitor")
+ holder.icon_state = "hudmonitor"
+ return
holder.icon_state = null
/***********************************************
@@ -454,53 +469,3 @@
holder.icon_state = "electrified"
else
holder.icon_state = ""
-
-/*~~~~~~~~~~~~
- Circutry!
-~~~~~~~~~~~~~*/
-/obj/item/electronic_assembly/proc/diag_hud_set_circuithealth(hide = FALSE)
- var/image/holder = hud_list[DIAG_CIRCUIT_HUD]
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - world.icon_size
- if((!isturf(loc))||hide) //if not on the ground dont show overlay
- holder.icon_state = null
- else
- holder.icon_state = "huddiag[RoundDiagBar(obj_integrity/max_integrity)]"
-
-/obj/item/electronic_assembly/proc/diag_hud_set_circuitcell(hide = FALSE)
- var/image/holder = hud_list[DIAG_BATT_HUD]
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - world.icon_size
- if((!isturf(loc))||hide) //if not on the ground dont show overlay
- holder.icon_state = null
- else if(battery)
- var/chargelvl = battery.charge/battery.maxcharge
- holder.icon_state = "hudbatt[RoundDiagBar(chargelvl)]"
- else
- holder.icon_state = "hudnobatt"
-
-/obj/item/electronic_assembly/proc/diag_hud_set_circuitstat(hide = FALSE) //On, On and dangerous, or Off
- var/image/holder = hud_list[DIAG_STAT_HUD]
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - world.icon_size
- if((!isturf(loc))||hide) //if not on the ground don't show overlay
- holder.icon_state = null
- else if(!battery)
- holder.icon_state = "hudoffline"
- else if(battery.charge == 0)
- holder.icon_state = "hudoffline"
- else if(combat_circuits) //has a circuit that can harm people
- holder.icon_state = prefered_hud_icon + "-red"
- else //Bot is on and not dangerous
- holder.icon_state = prefered_hud_icon
-
-/obj/item/electronic_assembly/proc/diag_hud_set_circuittracking(hide = FALSE)
- var/image/holder = hud_list[DIAG_TRACK_HUD]
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - world.icon_size
- if((!isturf(loc))||hide) //if not on the ground dont show overlay
- holder.icon_state = null
- else if(long_range_circuits)
- holder.icon_state = "hudtracking"
- else
- holder.icon_state = null
diff --git a/code/game/gamemodes/changeling/changeling.dm b/code/game/gamemodes/changeling/changeling.dm
index f961da979328d..d7257af93e4f9 100644
--- a/code/game/gamemodes/changeling/changeling.dm
+++ b/code/game/gamemodes/changeling/changeling.dm
@@ -1,7 +1,9 @@
GLOBAL_LIST_INIT(possible_changeling_IDs, list("Alpha","Beta","Gamma","Delta","Epsilon","Zeta","Eta","Theta","Iota","Kappa","Lambda","Mu","Nu","Xi","Omicron","Pi","Rho","Sigma","Tau","Upsilon","Phi","Chi","Psi","Omega"))
GLOBAL_LIST_INIT(slots, list("head", "wear_mask", "back", "wear_suit", "w_uniform", "shoes", "belt", "gloves", "glasses", "ears", "wear_id", "s_store"))
-GLOBAL_LIST_INIT(slot2slot, list("head" = SLOT_HEAD, "wear_mask" = SLOT_WEAR_MASK, "neck" = SLOT_NECK, "back" = SLOT_BACK, "wear_suit" = SLOT_WEAR_SUIT, "w_uniform" = SLOT_W_UNIFORM, "shoes" = SLOT_SHOES, "belt" = SLOT_BELT, "gloves" = SLOT_GLOVES, "glasses" = SLOT_GLASSES, "ears" = SLOT_EARS, "wear_id" = SLOT_WEAR_ID, "s_store" = SLOT_S_STORE))
-GLOBAL_LIST_INIT(slot2type, list("head" = /obj/item/clothing/head/changeling, "wear_mask" = /obj/item/clothing/mask/changeling, "back" = /obj/item/changeling, "wear_suit" = /obj/item/clothing/suit/changeling, "w_uniform" = /obj/item/clothing/under/changeling, "shoes" = /obj/item/clothing/shoes/changeling, "belt" = /obj/item/changeling, "gloves" = /obj/item/clothing/gloves/changeling, "glasses" = /obj/item/clothing/glasses/changeling, "ears" = /obj/item/changeling, "wear_id" = /obj/item/changeling, "s_store" = /obj/item/changeling))
+
+GLOBAL_LIST_INIT(slot2slot, list("head" = ITEM_SLOT_HEAD, "wear_mask" = ITEM_SLOT_MASK, "neck" = ITEM_SLOT_NECK, "back" = ITEM_SLOT_BACK, "wear_suit" = ITEM_SLOT_OCLOTHING, "w_uniform" = ITEM_SLOT_ICLOTHING, "shoes" = ITEM_SLOT_FEET, "belt" = ITEM_SLOT_BELT, "gloves" = ITEM_SLOT_GLOVES, "glasses" = ITEM_SLOT_EYES, "ears" = ITEM_SLOT_EARS, "wear_id" = ITEM_SLOT_ID, "s_store" = ITEM_SLOT_SUITSTORE))
+GLOBAL_LIST_INIT(slot2type, list("head" = /obj/item/clothing/head/changeling, "wear_mask" = /obj/item/clothing/mask/changeling, "back" = /obj/item/changeling, "wear_suit" = /obj/item/clothing/suit/changeling, "w_uniform" = /obj/item/clothing/under/changeling, "shoes" = /obj/item/clothing/shoes/changeling, "belt" = /obj/item/changeling, "gloves" = /obj/item/clothing/gloves/changeling, "glasses" = /obj/item/clothing/glasses/changeling, "ears" = /obj/item/changeling, "wear_id" = /obj/item/changeling/id, "s_store" = /obj/item/changeling))
+
/datum/game_mode/changeling
@@ -11,7 +13,7 @@ GLOBAL_LIST_INIT(slot2type, list("head" = /obj/item/clothing/head/changeling, "w
antag_flag = ROLE_CHANGELING
false_report_weight = 10
restricted_jobs = list("AI", "Cyborg")
- protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Brig Physician")
+ protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain")
required_players = 15
required_enemies = 1
recommended_enemies = 4
@@ -113,6 +115,9 @@ GLOBAL_LIST_INIT(slot2type, list("head" = /obj/item/clothing/head/changeling, "w
if((user.vars[slot] && !istype(user.vars[slot], GLOB.slot2type[slot])) || !(chosen_prof.exists_list[slot]))
continue
+ if(istype(user.vars[slot], GLOB.slot2type[slot]) && slot == "wear_id") //always remove old flesh IDs, so they get properly updated
+ qdel(user.vars[slot])
+
var/obj/item/C
var/equip = 0
if(!user.vars[slot])
@@ -128,8 +133,15 @@ GLOBAL_LIST_INIT(slot2type, list("head" = /obj/item/clothing/head/changeling, "w
C.flags_cover = chosen_prof.flags_cover_list[slot]
C.item_color = chosen_prof.item_color_list[slot]
C.item_state = chosen_prof.item_state_list[slot]
+
+ if(istype(C, /obj/item/changeling/id) && chosen_prof.id_icon)
+ var/obj/item/changeling/id/flesh_id = C
+ flesh_id.hud_icon = chosen_prof.id_icon
+
if(equip)
user.equip_to_slot_or_del(C, GLOB.slot2slot[slot])
+ if(!QDELETED(C))
+ ADD_TRAIT(C, TRAIT_NODROP, CHANGELING_TRAIT)
user.regenerate_icons()
diff --git a/code/game/gamemodes/clock_cult/clockcult.dm b/code/game/gamemodes/clock_cult/clockcult.dm
index fd54fd0bdb1d4..64b5c64fdc6d5 100644
--- a/code/game/gamemodes/clock_cult/clockcult.dm
+++ b/code/game/gamemodes/clock_cult/clockcult.dm
@@ -41,21 +41,19 @@ GLOBAL_VAR(clockcult_eminence)
Servants: Convert more servants and defend the Ark of the Clockwork Justicar!\n\
Crew: Prepare yourselfs and destroy the Ark of the Clockwork Justicar."
+
var/clock_cultists = CLOCKCULT_SERVANTS
var/list/selected_servants = list()
var/datum/team/clock_cult/main_cult
+/datum/game_mode/clockcult/setup_maps()
+ //Since we are loading in pre_setup, disable map loading.
+ SSticker.gamemode_hotswap_disabled = TRUE
+ LoadReebe()
+ return TRUE
+
/datum/game_mode/clockcult/pre_setup()
- //Load Reebe
- var/list/errorList = list()
- var/list/reebe = SSmapping.LoadGroup(errorList, "Reebe", "map_files/generic", "CityOfCogs.dmm", default_traits=ZTRAITS_REEBE, silent=TRUE)
- if(errorList.len)
- message_admins("Reebe failed to load")
- log_game("Reebe failed to load")
- return FALSE
- for(var/datum/parsed_map/map in reebe)
- map.initTemplateBounds()
//Generate cultists
for(var/i in 1 to clock_cultists)
if(!antag_candidates.len)
@@ -65,10 +63,7 @@ GLOBAL_VAR(clockcult_eminence)
selected_servants += clockie
clockie.assigned_role = ROLE_SERVANT_OF_RATVAR
clockie.special_role = ROLE_SERVANT_OF_RATVAR
- //Generate scriptures
- for(var/categorypath in typesof(/datum/clockcult/scripture))
- var/datum/clockcult/scripture/S = new categorypath
- GLOB.clockcult_all_scriptures[S.name] = S
+ generate_clockcult_scriptures()
return TRUE
/datum/game_mode/clockcult/post_setup(report)
@@ -82,7 +77,7 @@ GLOBAL_VAR(clockcult_eminence)
var/datum/antagonist/servant_of_ratvar/S = add_servant_of_ratvar(servant_mind.current, team=main_cult)
S.equip_carbon(servant_mind.current)
S.equip_servant()
- S.prefix = CLOCKCULT_MASTER
+ S.prefix = CLOCKCULT_PREFIX_MASTER
//Setup the conversion limits for auto opening the ark
calculate_clockcult_values()
return ..()
@@ -157,7 +152,7 @@ GLOBAL_VAR(clockcult_eminence)
return FALSE
if(ishuman(M) && (M.mind.assigned_role in list("Captain", "Chaplain")))
return FALSE
- if(istype(M.get_item_by_slot(SLOT_HEAD), /obj/item/clothing/head/foilhat))
+ if(istype(M.get_item_by_slot(ITEM_SLOT_HEAD), /obj/item/clothing/head/foilhat))
return FALSE
if(is_servant_of_ratvar(M))
return FALSE
@@ -171,6 +166,12 @@ GLOBAL_VAR(clockcult_eminence)
return FALSE
return TRUE
+/proc/generate_clockcult_scriptures()
+ //Generate scriptures
+ for(var/categorypath in subtypesof(/datum/clockcult/scripture))
+ var/datum/clockcult/scripture/S = new categorypath
+ GLOB.clockcult_all_scriptures[S.name] = S
+
/proc/flee_reebe()
for(var/mob/living/M in GLOB.mob_list)
if(!is_reebe(M.z))
@@ -205,38 +206,42 @@ GLOBAL_VAR(clockcult_eminence)
msg = sender.treat_message(msg)
var/datum/antagonist/servant_of_ratvar/SoR = is_servant_of_ratvar(sender)
var/prefix = "Clockbrother"
- if(SoR.prefix)
- prefix = sender.gender == MALE\
- ? "Clockfather"\
- : sender.gender == FEMALE\
- ? "Clockmother"\
- : "Clockmaster"
- hierophant_message = ""
- else
- var/role = sender.mind?.assigned_role
- //Ew, this could be done better with a dictionary list, but this isn't much slower
- if(role in GLOB.command_positions)
- prefix = "High Priest"
- else if(role in GLOB.engineering_positions)
- prefix = "Cogturner"
- else if(role in GLOB.medical_positions)
- prefix = "Rejuvinator"
- else if(role in GLOB.science_positions)
- prefix = "Calculator"
- else if(role in GLOB.supply_positions)
- prefix = "Pathfinder"
- else if(role in "Assistant")
- prefix = "Helper"
- else if(role in "Mime")
- prefix = "Cogwatcher"
- else if(role in "Clown")
- prefix = "Clonker"
- else if(role in GLOB.civilian_positions)
- prefix = "Cogworker"
- else if(role in GLOB.security_positions)
- prefix = "Warrior"
- else if(role in GLOB.nonhuman_positions)
- prefix = "CPU"
+ switch(SoR.prefix)
+ if(CLOCKCULT_PREFIX_EMINENCE)
+ prefix = "Master"
+ if(CLOCKCULT_PREFIX_MASTER)
+ prefix = sender.gender == MALE\
+ ? "Clockfather"\
+ : sender.gender == FEMALE\
+ ? "Clockmother"\
+ : "Clockmaster"
+ hierophant_message = ""
+ if(CLOCKCULT_PREFIX_RECRUIT)
+ var/role = sender.mind?.assigned_role
+ //Ew, this could be done better with a dictionary list, but this isn't much slower
+ if(role in GLOB.command_positions)
+ prefix = "High Priest"
+ else if(role in GLOB.engineering_positions)
+ prefix = "Cogturner"
+ else if(role in GLOB.medical_positions)
+ prefix = "Rejuvinator"
+ else if(role in GLOB.science_positions)
+ prefix = "Calculator"
+ else if(role in GLOB.supply_positions)
+ prefix = "Pathfinder"
+ else if(role in "Assistant")
+ prefix = "Helper"
+ else if(role in "Mime")
+ prefix = "Cogwatcher"
+ else if(role in "Clown")
+ prefix = "Clonker"
+ else if((role in GLOB.civilian_positions) || (role in GLOB.gimmick_positions))
+ prefix = "Cogworker"
+ else if(role in GLOB.security_positions)
+ prefix = "Warrior"
+ else if(role in GLOB.nonhuman_positions)
+ prefix = "CPU"
+ //Fallthrough is default of "Clockbrother"
hierophant_message += "[prefix] [sender.name] transmits, \"[msg]\""
else
hierophant_message += msg
diff --git a/code/game/gamemodes/clown_ops/bananium_bomb.dm b/code/game/gamemodes/clown_ops/bananium_bomb.dm
index f8a61487c982a..8c4af7108a738 100644
--- a/code/game/gamemodes/clown_ops/bananium_bomb.dm
+++ b/code/game/gamemodes/clown_ops/bananium_bomb.dm
@@ -36,24 +36,24 @@
Cinematic(get_cinematic_type(off_station), world)
for(var/mob/living/carbon/human/H in GLOB.carbon_list)
var/turf/T = get_turf(H)
- if(!T || T.z != z)
+ if(!T || T.get_virtual_z_level() != get_virtual_z_level())
continue
H.Stun(10)
var/obj/item/clothing/C
if(!H.w_uniform || H.dropItemToGround(H.w_uniform))
C = new /obj/item/clothing/under/rank/civilian/clown(H)
ADD_TRAIT(C, TRAIT_NODROP, CLOWN_NUKE_TRAIT)
- H.equip_to_slot_or_del(C, SLOT_W_UNIFORM)
+ H.equip_to_slot_or_del(C, ITEM_SLOT_ICLOTHING)
if(!H.shoes || H.dropItemToGround(H.shoes))
C = new /obj/item/clothing/shoes/clown_shoes(H)
ADD_TRAIT(C, TRAIT_NODROP, CLOWN_NUKE_TRAIT)
- H.equip_to_slot_or_del(C, SLOT_SHOES)
+ H.equip_to_slot_or_del(C, ITEM_SLOT_FEET)
if(!H.wear_mask || H.dropItemToGround(H.wear_mask))
C = new /obj/item/clothing/mask/gas/clown_hat(H)
ADD_TRAIT(C, TRAIT_NODROP, CLOWN_NUKE_TRAIT)
- H.equip_to_slot_or_del(C, SLOT_WEAR_MASK)
+ H.equip_to_slot_or_del(C, ITEM_SLOT_MASK)
H.dna.add_mutation(CLOWNMUT)
H.gain_trauma(/datum/brain_trauma/mild/phobia/clowns, TRAUMA_RESILIENCE_LOBOTOMY) //MWA HA HA
diff --git a/code/game/gamemodes/clown_ops/clown_weapons.dm b/code/game/gamemodes/clown_ops/clown_weapons.dm
index 61863c96b230c..f96e4d18217bb 100644
--- a/code/game/gamemodes/clown_ops/clown_weapons.dm
+++ b/code/game/gamemodes/clown_ops/clown_weapons.dm
@@ -17,41 +17,50 @@
desc = "advanced clown shoes that protect the wearer and render them nearly immune to slipping on their own peels. They also squeak at 100% capacity."
clothing_flags = NOSLIP
slowdown = SHOES_SLOWDOWN
- armor = list("melee" = 25, "bullet" = 25, "laser" = 25, "energy" = 25, "bomb" = 50, "bio" = 10, "rad" = 0, "fire" = 70, "acid" = 50)
+ armor = list("melee" = 25, "bullet" = 25, "laser" = 25, "energy" = 25, "bomb" = 50, "bio" = 10, "rad" = 0, "fire" = 70, "acid" = 50, "stamina" = 25)
strip_delay = 70
resistance_flags = NONE
permeability_coefficient = 0.05
pocket_storage_component_path = /datum/component/storage/concrete/pockets/shoes
+/// Recharging rate in PPS (peels per second)
+#define BANANA_SHOES_RECHARGE_RATE 17
+#define BANANA_SHOES_MAX_CHARGE 3000
+
//The super annoying version
/obj/item/clothing/shoes/clown_shoes/banana_shoes/combat
name = "mk-honk combat shoes"
desc = "The culmination of years of clown combat research, these shoes leave a trail of chaos in their wake. They will slowly recharge themselves over time, or can be manually charged with bananium."
slowdown = SHOES_SLOWDOWN
- armor = list("melee" = 25, "bullet" = 25, "laser" = 25, "energy" = 25, "bomb" = 50, "bio" = 10, "rad" = 0, "fire" = 70, "acid" = 50)
+ armor = list("melee" = 25, "bullet" = 25, "laser" = 25, "energy" = 25, "bomb" = 50, "bio" = 10, "rad" = 0, "fire" = 70, "acid" = 50, "stamina" = 25)
strip_delay = 70
resistance_flags = NONE
permeability_coefficient = 0.05
pocket_storage_component_path = /datum/component/storage/concrete/pockets/shoes
always_noslip = TRUE
- var/max_recharge = 3000 //30 peels worth
- var/recharge_rate = 34 //about 1/3 of a peel per tick
/obj/item/clothing/shoes/clown_shoes/banana_shoes/combat/Initialize()
. = ..()
var/datum/component/material_container/bananium = GetComponent(/datum/component/material_container)
- bananium.insert_amount_mat(max_recharge, /datum/material/bananium)
+ bananium.insert_amount_mat(BANANA_SHOES_MAX_CHARGE, /datum/material/bananium)
START_PROCESSING(SSobj, src)
-/obj/item/clothing/shoes/clown_shoes/banana_shoes/combat/process()
+/obj/item/clothing/shoes/clown_shoes/banana_shoes/combat/Destroy()
+ STOP_PROCESSING(SSobj, src)
+ return ..()
+
+/obj/item/clothing/shoes/clown_shoes/banana_shoes/combat/process(delta_time)
var/datum/component/material_container/bananium = GetComponent(/datum/component/material_container)
var/bananium_amount = bananium.get_material_amount(/datum/material/bananium)
- if(bananium_amount < max_recharge)
- bananium.insert_amount_mat(min(recharge_rate, max_recharge - bananium_amount), /datum/material/bananium)
+ if(bananium_amount < BANANA_SHOES_MAX_CHARGE)
+ bananium.insert_amount_mat(min(BANANA_SHOES_RECHARGE_RATE * delta_time, BANANA_SHOES_MAX_CHARGE - bananium_amount), /datum/material/bananium)
/obj/item/clothing/shoes/clown_shoes/banana_shoes/combat/attack_self(mob/user)
ui_action_click(user)
+#undef BANANA_SHOES_RECHARGE_RATE
+#undef BANANA_SHOES_MAX_CHARGE
+
//BANANIUM SWORD
/obj/item/melee/transforming/energy/sword/bananium
@@ -138,7 +147,7 @@
var/datum/component/slippery/slipper = GetComponent(/datum/component/slippery)
slipper.signal_enabled = active
-/obj/item/shield/energy/bananium/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force)
+/obj/item/shield/energy/bananium/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, quickstart = TRUE)
if(active)
if(iscarbon(thrower))
var/mob/living/carbon/C = thrower
@@ -151,9 +160,9 @@
if(iscarbon(hit_atom) && !caught)//if they are a carbon and they didn't catch it
var/datum/component/slippery/slipper = GetComponent(/datum/component/slippery)
slipper.Slip(src, hit_atom)
- if(thrownby && !caught)
- sleep(1)
- throw_at(thrownby, throw_range+2, throw_speed, null, TRUE)
+ var/mob/thrown_by = thrownby?.resolve()
+ if(thrown_by && !caught)
+ addtimer(CALLBACK(src, /atom/movable.proc/throw_at, thrown_by, throw_range+2, throw_speed, null, TRUE), 1)
else
return ..()
@@ -206,14 +215,14 @@
icon_state = "moustacheg"
clumsy_check = GRENADE_NONCLUMSY_FUMBLE
-/obj/item/grenade/chem_grenade/teargas/moustache/prime()
+/obj/item/grenade/chem_grenade/teargas/moustache/prime(mob/living/lanced_by)
var/myloc = get_turf(src)
. = ..()
- for(var/mob/living/carbon/M in view(6, myloc))
+ for(var/mob/living/carbon/M in hearers(6, myloc))
if(!istype(M.wear_mask, /obj/item/clothing/mask/gas/clown_hat) && !istype(M.wear_mask, /obj/item/clothing/mask/gas/mime) )
if(!M.wear_mask || M.dropItemToGround(M.wear_mask))
var/obj/item/clothing/mask/fakemoustache/sticky/the_stash = new /obj/item/clothing/mask/fakemoustache/sticky()
- M.equip_to_slot_or_del(the_stash, SLOT_WEAR_MASK, TRUE, TRUE, TRUE, TRUE)
+ M.equip_to_slot_or_del(the_stash, ITEM_SLOT_MASK, TRUE, TRUE, TRUE, TRUE)
/obj/item/clothing/mask/fakemoustache/sticky
var/unstick_time = 600
@@ -266,7 +275,7 @@
icon_state = "darkhonker"
max_integrity = 300
deflect_chance = 15
- armor = list("melee" = 40, "bullet" = 40, "laser" = 50, "energy" = 35, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100)
+ armor = list("melee" = 40, "bullet" = 40, "laser" = 50, "energy" = 35, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100, "stamina" = 0)
max_temperature = 35000
operation_req_access = list(ACCESS_SYNDICATE)
internals_req_access = list(ACCESS_SYNDICATE)
diff --git a/code/game/gamemodes/cult/cult.dm b/code/game/gamemodes/cult/cult.dm
index 95a44c636a7c7..d8ecce2bb0a7b 100644
--- a/code/game/gamemodes/cult/cult.dm
+++ b/code/game/gamemodes/cult/cult.dm
@@ -38,7 +38,7 @@
report_type = "cult"
antag_flag = ROLE_CULTIST
false_report_weight = 1
- restricted_jobs = list("Chaplain","AI", "Cyborg", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Brig Physician")
+ restricted_jobs = list("Chaplain","AI", "Cyborg", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel")
protected_jobs = list()
required_players = 29
required_enemies = 4
@@ -52,6 +52,7 @@
title_icon = "cult"
+
var/finished = 0
var/acolytes_needed = 10 //for the survive objective
@@ -78,12 +79,16 @@
if(prob(remaining))
recommended_enemies++
+ recommended_enemies = max(recommended_enemies, required_enemies)
for(var/cultists_number = 1 to recommended_enemies)
if(!antag_candidates.len)
break
var/datum/mind/cultist = antag_pick(antag_candidates, ROLE_CULTIST)
antag_candidates -= cultist
+ if(!cultist)
+ cultists_number--
+ continue
cultists_to_cult += cultist
cultist.special_role = ROLE_CULTIST
cultist.restricted_roles = restricted_jobs
diff --git a/code/game/gamemodes/devil/devil_game_mode.dm b/code/game/gamemodes/devil/devil_game_mode.dm
index ce5d9ee7d38d9..22e64f00a81a4 100644
--- a/code/game/gamemodes/devil/devil_game_mode.dm
+++ b/code/game/gamemodes/devil/devil_game_mode.dm
@@ -4,7 +4,7 @@
report_type = "devil"
antag_flag = ROLE_DEVIL
false_report_weight = 1
- protected_jobs = list("Lawyer", "Curator", "Chaplain", "Head of Security", "Captain", "AI", "Cyborg", "Security Officer", "Warden", "Detective", "Brig Physician")
+ protected_jobs = list("Lawyer", "Curator", "Chaplain", "Head of Security", "Captain", "AI", "Cyborg", "Security Officer", "Warden", "Detective")
required_players = 0
required_enemies = 1
recommended_enemies = 4
@@ -12,6 +12,8 @@
enemy_minimum_age = 0
title_icon = "devil"
+ allowed_special = list(/datum/special_role/traitor)
+
var/traitors_possible = 4 //hard limit on devils if scaling is turned off
var/num_modifier = 0 // Used for gamemodes, that are a child of traitor, that need more than the usual.
var/objective_count = 2
diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm
index 133b8bb0eb44f..a5a8bfc80e6f9 100644
--- a/code/game/gamemodes/dynamic/dynamic.dm
+++ b/code/game/gamemodes/dynamic/dynamic.dm
@@ -1,44 +1,19 @@
-#define CURRENT_LIVING_PLAYERS 1
-#define CURRENT_LIVING_ANTAGS 2
-#define CURRENT_DEAD_PLAYERS 3
-#define CURRENT_OBSERVERS 4
-
-#define ONLY_RULESET 1
-#define HIGHLANDER_RULESET 2
-#define TRAITOR_RULESET 4
-#define MINOR_RULESET 8
-
#define RULESET_STOP_PROCESSING 1
-// -- Injection delays
-GLOBAL_VAR_INIT(dynamic_latejoin_delay_min, (5 MINUTES))
-GLOBAL_VAR_INIT(dynamic_latejoin_delay_max, (25 MINUTES))
+#define FAKE_REPORT_CHANCE 8
+#define REPORT_NEG_DIVERGENCE -15
+#define REPORT_POS_DIVERGENCE 15
-GLOBAL_VAR_INIT(dynamic_midround_delay_min, (15 MINUTES))
-GLOBAL_VAR_INIT(dynamic_midround_delay_max, (35 MINUTES))
-
-// Are HIGHLANDER_RULESETs allowed to stack?
+// Are HIGH_IMPACT_RULESETs allowed to stack?
GLOBAL_VAR_INIT(dynamic_no_stacking, TRUE)
-// A number between -5 and +5.
-// A negative value will give a more peaceful round and
-// a positive value will give a round with higher threat.
-GLOBAL_VAR_INIT(dynamic_curve_centre, 0)
-// A number between 0.5 and 4.
-// Higher value will favour extreme rounds and
-// lower value rounds closer to the average.
-GLOBAL_VAR_INIT(dynamic_curve_width, 1.8)
-// If enabled only picks a single starting rule and executes only autotraitor midround ruleset.
-GLOBAL_VAR_INIT(dynamic_classic_secret, FALSE)
-// How many roundstart players required for high population override to take effect.
-GLOBAL_VAR_INIT(dynamic_high_pop_limit, 55)
// If enabled does not accept or execute any rulesets.
GLOBAL_VAR_INIT(dynamic_forced_extended, FALSE)
-// How high threat is required for HIGHLANDER_RULESETs stacking.
+// How high threat is required for HIGH_IMPACT_RULESETs stacking.
// This is independent of dynamic_no_stacking.
GLOBAL_VAR_INIT(dynamic_stacking_limit, 90)
// List of forced roundstart rulesets.
GLOBAL_LIST_EMPTY(dynamic_forced_roundstart_ruleset)
-// Forced threat level, setting this to zero or higher forces the roundstart threat to the value.
+// Forced threat level, setting this to zero or higher forces the roundstart threat to the value.
GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
/datum/game_mode/dynamic
@@ -49,15 +24,23 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
announce_span = "danger"
announce_text = "Dynamic mode!" // This needs to be changed maybe
- reroll_friendly = FALSE;
-
+ reroll_friendly = FALSE
+
// Threat logging vars
/// The "threat cap", threat shouldn't normally go above this and is used in ruleset calculations
- var/threat_level = 0
- /// Set at the beginning of the round. Spent by the mode to "purchase" rules.
- var/threat = 0
+ var/threat_level = 0
+
+ /// Set at the beginning of the round. Spent by the mode to "purchase" rules. Everything else goes in the postround budget.
+ var/round_start_budget = 0
+
+ /// Set at the beginning of the round. Spent by midrounds and latejoins.
+ var/mid_round_budget = 0
+
+ /// The initial round start budget for logging purposes, set once at the beginning of the round.
+ var/initial_round_start_budget = 0
+
/// Running information about the threat. Can store text or datum entries.
- var/list/threat_log = list()
+ var/list/threat_log = list()
/// List of roundstart rules used for selecting the rules.
var/list/roundstart_rules = list()
/// List of latejoin rules used for selecting the rules.
@@ -73,14 +56,6 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
* 0-6, 7-13, 14-20, 21-27, 28-34, 35-41, 42-48, 49-55, 56-62, 63+
*/
var/pop_per_requirement = 6
- /// The requirement used for checking if a second rule should be selected.
- var/list/second_rule_req = list(100, 100, 80, 70, 60, 50, 30, 20, 10, 0)
- /// The requirement used for checking if a third rule should be selected.
- var/list/third_rule_req = list(100, 100, 100, 90, 80, 70, 60, 50, 40, 30)
- /// Threat requirement for a second ruleset when high pop override is in effect.
- var/high_pop_second_rule_req = 40
- /// Threat requirement for a third ruleset when high pop override is in effect.
- var/high_pop_third_rule_req = 60
/// Number of players who were ready on roundstart.
var/roundstart_pop_ready = 0
/// List of candidates used on roundstart rulesets.
@@ -89,36 +64,110 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
var/list/current_rules = list()
/// List of executed rulesets.
var/list/executed_rules = list()
- /// Associative list of current players, in order: living players, living antagonists, dead players and observers.
- var/list/list/current_players = list(CURRENT_LIVING_PLAYERS, CURRENT_LIVING_ANTAGS, CURRENT_DEAD_PLAYERS, CURRENT_OBSERVERS)
- /// When world.time is over this number the mode tries to inject a latejoin ruleset.
- var/latejoin_injection_cooldown = 0
- /// When world.time is over this number the mode tries to inject a midround ruleset.
- var/midround_injection_cooldown = 0
/// When TRUE GetInjectionChance returns 100.
var/forced_injection = FALSE
/// Forced ruleset to be executed for the next latejoin.
var/datum/dynamic_ruleset/latejoin/forced_latejoin_rule = null
- /// When current_players was updated last time.
- var/pop_last_updated = 0
/// How many percent of the rounds are more peaceful.
var/peaceful_percentage = 50
- /// If a highlander executed.
- var/highlander_executed = FALSE
+ /// If a high impact ruleset was executed. Only one will run at a time in most circumstances.
+ var/high_impact_ruleset_executed = FALSE
/// If a only ruleset has been executed.
var/only_ruleset_executed = FALSE
+ /// Dynamic configuration, loaded on pre_setup
+ var/list/configuration = null
+ /// Antags rolled by rules so far, to keep track of and discourage scaling past a certain ratio of crew/antags especially on lowpop.
+ var/antags_rolled = 0
+
+ /// When world.time is over this number the mode tries to inject a latejoin ruleset.
+ var/latejoin_injection_cooldown = 0
+
+ /// The minimum time the recurring latejoin ruleset timer is allowed to be.
+ var/latejoin_delay_min = (5 MINUTES)
+
+ /// The maximum time the recurring latejoin ruleset timer is allowed to be.
+ var/latejoin_delay_max = (25 MINUTES)
+
+ /// When world.time is over this number the mode tries to inject a midround ruleset.
+ var/midround_injection_cooldown = 0
+
+ /// The minimum time the recurring midround ruleset timer is allowed to be.
+ var/midround_delay_min = (15 MINUTES)
+
+ /// The maximum time the recurring midround ruleset timer is allowed to be.
+ var/midround_delay_max = (35 MINUTES)
+
+ /// If above this threat, increase the chance of injection
+ var/higher_injection_chance_minimum_threat = 70
+
+ /// The chance of injection increase when above higher_injection_chance_minimum_threat
+ var/higher_injection_chance = 15
+
+ /// If below this threat, decrease the chance of injection
+ var/lower_injection_chance_minimum_threat = 10
+
+ /// The chance of injection decrease when above lower_injection_chance_minimum_threat
+ var/lower_injection_chance = 15
+
+ /// A number between -5 and +5.
+ /// A negative value will give a more peaceful round and
+ /// a positive value will give a round with higher threat.
+ var/threat_curve_centre = 0
+
+ /// A number between 0.5 and 4.
+ /// Higher value will favour extreme rounds and
+ /// lower value rounds closer to the average.
+ var/threat_curve_width = 1.8
+
+ /// A number between -5 and +5.
+ /// Equivalent to threat_curve_centre, but for the budget split.
+ /// A negative value will weigh towards midround rulesets, and a positive
+ /// value will weight towards roundstart ones.
+ var/roundstart_split_curve_centre = 1
+
+ /// A number between 0.5 and 4.
+ /// Equivalent to threat_curve_width, but for the budget split.
+ /// Higher value will favour more variance in splits and
+ /// lower value rounds closer to the average.
+ var/roundstart_split_curve_width = 1.8
+
+ /// The minimum amount of time for antag random events to be hijacked.
+ var/random_event_hijack_minimum = 10 MINUTES
+
+ /// The maximum amount of time for antag random events to be hijacked.
+ var/random_event_hijack_maximum = 18 MINUTES
+
+ /// A list of recorded "snapshots" of the round, stored in the dynamic.json log
+ var/list/datum/dynamic_snapshot/snapshots
+
+ /// The time when the last midround injection was attempted, whether or not it was successful
+ var/last_midround_injection_attempt = 0
+
+ /// The amount to inject when a round event is hijacked
+ var/hijacked_random_event_injection_chance = 50
+
+ /// Whether or not a random event has been hijacked this midround cycle
+ var/random_event_hijacked = HIJACKED_NOTHING
+
+ /// The timer ID for the cancellable midround rule injection
+ var/midround_injection_timer_id
+
+ /// The last drafted midround rulesets (without the current one included).
+ /// Used for choosing different midround injections.
+ var/list/current_midround_rulesets
/datum/game_mode/dynamic/admin_panel()
var/list/dat = list("Game Mode Panel
Game Mode Panel
")
- dat += "Dynamic Mode \[VV\]\[Refresh\] "
+ dat += "Dynamic Mode \[VV\]\[Refresh\] "
dat += "Threat Level: [threat_level] "
+ dat += "Budgets (Roundstart/Midrounds): [initial_round_start_budget]/[threat_level - initial_round_start_budget] "
- dat += "Threat to Spend: [threat]\[Adjust\]\[View Log\] "
+ dat += "Midround budget to spend: [mid_round_budget]\[Adjust\]\[View Log\] "
dat += " "
- dat += "Parameters: centre = [GLOB.dynamic_curve_centre] ; width = [GLOB.dynamic_curve_width]. "
+ dat += "Parameters: centre = [threat_curve_centre] ; width = [threat_curve_width]. "
+ dat += "Split parameters: centre = [roundstart_split_curve_centre] ; width = [roundstart_split_curve_width]. "
dat += "On average, [peaceful_percentage]% of the rounds are more peaceful. "
dat += "Forced extended: [GLOB.dynamic_forced_extended ? "On" : "Off"] "
- dat += "Classic secret (only autotraitor): [GLOB.dynamic_classic_secret ? "On" : "Off"] "
dat += "No stacking (only one round-ender): [GLOB.dynamic_no_stacking ? "On" : "Off"] "
dat += "Stacking limit: [GLOB.dynamic_stacking_limit] \[Adjust\]"
dat += " "
@@ -129,7 +178,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
dat += "[DR.ruletype] - [DR.name] "
else
dat += "none. "
- dat += " Injection Timers: ([get_injection_chance(TRUE)]% chance) "
+ dat += " Injection Timers: ([get_injection_chance(dry_run = TRUE)]% latejoin chance, [get_midround_injection_chance(dry_run = TRUE)]% midround chance) "
dat += "Latejoin: [(latejoin_injection_cooldown-world.time)>60*10 ? "[round((latejoin_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(latejoin_injection_cooldown-world.time)] seconds"] \[Now!\] "
dat += "Midround: [(midround_injection_cooldown-world.time)>60*10 ? "[round((midround_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(midround_injection_cooldown-world.time)] seconds"] \[Now!\] "
usr << browse(dat.Join(), "window=gamemode_panel;size=500x500")
@@ -143,18 +192,21 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
return
if (href_list["forced_extended"])
GLOB.dynamic_forced_extended = !GLOB.dynamic_forced_extended
+ message_admins("[key_name(usr)] toggled dynamic's Forced Extended setting to [GLOB.dynamic_forced_extended].")
else if (href_list["no_stacking"])
GLOB.dynamic_no_stacking = !GLOB.dynamic_no_stacking
- else if (href_list["classic_secret"])
- GLOB.dynamic_classic_secret = !GLOB.dynamic_classic_secret
+ message_admins("[key_name(usr)] toggled dynamic's No Stacking setting to [GLOB.dynamic_no_stacking].")
else if (href_list["adjustthreat"])
var/threatadd = input("Specify how much threat to add (negative to subtract). This can inflate the threat level.", "Adjust Threat", 0) as null|num
if(!threatadd)
return
if(threatadd > 0)
create_threat(threatadd)
+ threat_log += "[worldtime2text()]: [key_name(usr)] increased threat by [threatadd] threat."
else
- spend_threat(-threatadd)
+ spend_midround_budget(-threatadd)
+ threat_log += "[worldtime2text()]: [key_name(usr)] decreased threat by [-threatadd] threat."
+ message_admins("[key_name(usr)] adjusted the dynamic threat level by [threatadd] threat.")
else if (href_list["injectlate"])
latejoin_injection_cooldown = 0
forced_injection = TRUE
@@ -167,26 +219,48 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
show_threatlog(usr)
else if (href_list["stacking_limit"])
GLOB.dynamic_stacking_limit = input(usr,"Change the threat limit at which round-endings rulesets will start to stack.", "Change stacking limit", null) as num
-
+ message_admins("[key_name(usr)] adjusted dynamic's Stacking Limit setting to [GLOB.dynamic_stacking_limit].")
+ else if(href_list["force_latejoin_rule"])
+ var/added_rule = input(usr,"What ruleset do you want to force upon the next latejoiner? This will bypass threat level and population restrictions.", "Rigging Latejoin", null) as null|anything in sortList(latejoin_rules)
+ if (!added_rule)
+ return
+ forced_latejoin_rule = added_rule
+ dynamic_log("[key_name(usr)] set [added_rule] to proc on the next latejoin.")
+ else if(href_list["clear_forced_latejoin"])
+ forced_latejoin_rule = null
+ dynamic_log("[key_name(usr)] cleared the forced latejoin ruleset.")
+ else if(href_list["force_midround_rule"])
+ var/added_rule = input(usr,"What ruleset do you want to force right now? This will bypass threat level and population restrictions.", "Execute Ruleset", null) as null|anything in sortList(midround_rules)
+ if (!added_rule)
+ return
+ dynamic_log("[key_name(usr)] executed the [added_rule] ruleset.")
+ picking_specific_rule(added_rule, TRUE)
+ else if(href_list["cancelmidround"])
+ admin_cancel_midround(usr, href_list["cancelmidround"])
+ return
+ else if (href_list["differentmidround"])
+ admin_different_midround(usr, href_list["differentmidround"])
+ return
+
admin_panel() // Refreshes the window
-// Checks if there are HIGHLANDER_RULESETs and calls the rule's round_result() proc
+// Checks if there are HIGH_IMPACT_RULESETs and calls the rule's round_result() proc
/datum/game_mode/dynamic/set_round_result()
+ // If it got to this part, just pick one high impact ruleset if it exists
for(var/datum/dynamic_ruleset/rule in executed_rules)
- if(rule.flags & HIGHLANDER_RULESET)
- if(rule.check_finished()) // Only the rule that actually finished the round sets round result.
- return rule.round_result()
- // If it got to this part, just pick one highlander if it exists
- for(var/datum/dynamic_ruleset/rule in executed_rules)
- if(rule.flags & HIGHLANDER_RULESET)
+ if(rule.flags & HIGH_IMPACT_RULESET)
return rule.round_result()
return ..()
/datum/game_mode/dynamic/send_intercept()
. = "Central Command Status Summary"
- switch(round(threat_level))
+ var/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)
+ switch(round(shown_threat))
if(0 to 19)
- update_playercounts()
if(!current_players[CURRENT_LIVING_ANTAGS].len)
. += "Peaceful Waypoint
"
. += "Your station orbits deep within controlled, core-sector systems and serves as a waypoint for routine traffic through Nanotrasen's trade empire. Due to the combination of high security, interstellar traffic, and low strategic value, it makes any direct threat of violence unlikely. Your primary enemies will be incompetence and bored crewmen: try to organize team-building events to keep staffers interested and productive."
@@ -210,14 +284,10 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
. += "Your station is somehow in the middle of hostile territory, in clear view of any enemy of the corporation. Your likelihood to survive is low, and station destruction is expected and almost inevitable. Secure any sensitive material and neutralize any enemy you will come across. It is important that you at least try to maintain the station. "
. += "Good luck."
- if(station_goals.len)
- . += "Special Orders for [station_name()]:"
- for(var/datum/station_goal/G in station_goals)
- G.on_report()
- . += G.get_report()
+ . += generate_station_goal_report()
print_command_report(., "Central Command Status Summary", announce=FALSE)
- priority_announce("A summary has been copied and printed to all communications consoles.", "Security level elevated.", 'sound/ai/intercept.ogg')
+ priority_announce("A summary has been copied and printed to all communications consoles.", "Security level elevated.", ANNOUNCER_INTERCEPT)
if(GLOB.security_level < SEC_LEVEL_BLUE)
set_security_level(SEC_LEVEL_BLUE)
@@ -233,9 +303,6 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
return TRUE
if(force_ending)
return TRUE
- for(var/datum/dynamic_ruleset/rule in executed_rules)
- if(rule.flags & HIGHLANDER_RULESET)
- return rule.check_finished()
/datum/game_mode/dynamic/proc/show_threatlog(mob/admin)
if(!SSticker.HasRoundStarted())
@@ -251,86 +318,111 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
if(istext(entry))
out += "[entry] "
- out += "Remaining threat/threat_level: [threat]/[threat_level]"
+ out += "Remaining threat/threat_level: [mid_round_budget]/[threat_level]"
usr << browse(out.Join(), "window=threatlog;size=700x500")
/// Generates the threat level using lorentz distribution and assigns peaceful_percentage.
/datum/game_mode/dynamic/proc/generate_threat()
- var/relative_threat = LORENTZ_DISTRIBUTION(GLOB.dynamic_curve_centre, GLOB.dynamic_curve_width)
- threat_level = round(lorentz_to_threat(relative_threat), 0.1)
+ var/relative_threat = LORENTZ_DISTRIBUTION(threat_curve_centre, threat_curve_width)
+ threat_level = round(lorentz_to_amount(relative_threat), 0.1)
- peaceful_percentage = round(LORENTZ_CUMULATIVE_DISTRIBUTION(relative_threat, GLOB.dynamic_curve_centre, GLOB.dynamic_curve_width), 0.01)*100
+ peaceful_percentage = round(LORENTZ_CUMULATIVE_DISTRIBUTION(relative_threat, threat_curve_centre, threat_curve_width), 0.01)*100
- threat = threat_level
+/// Generates the midround and roundstart budgets
+/datum/game_mode/dynamic/proc/generate_budgets()
+ var/relative_round_start_budget_scale = LORENTZ_DISTRIBUTION(roundstart_split_curve_centre, roundstart_split_curve_width)
+ round_start_budget = round((lorentz_to_amount(relative_round_start_budget_scale) / 100) * threat_level, 0.1)
+ initial_round_start_budget = round_start_budget
+ mid_round_budget = threat_level - round_start_budget
/datum/game_mode/dynamic/can_start()
- message_admins("Dynamic mode parameters for the round:")
- message_admins("Centre is [GLOB.dynamic_curve_centre], Width is [GLOB.dynamic_curve_width], Forced extended is [GLOB.dynamic_forced_extended ? "Enabled" : "Disabled"], No stacking is [GLOB.dynamic_no_stacking ? "Enabled" : "Disabled"].")
- message_admins("Stacking limit is [GLOB.dynamic_stacking_limit], Classic secret is [GLOB.dynamic_classic_secret ? "Enabled" : "Disabled"], High population limit is [GLOB.dynamic_high_pop_limit].")
+ return TRUE
+
+/datum/game_mode/dynamic/proc/setup_parameters()
log_game("DYNAMIC: Dynamic mode parameters for the round:")
- log_game("DYNAMIC: Centre is [GLOB.dynamic_curve_centre], Width is [GLOB.dynamic_curve_width], Forced extended is [GLOB.dynamic_forced_extended ? "Enabled" : "Disabled"], No stacking is [GLOB.dynamic_no_stacking ? "Enabled" : "Disabled"].")
- log_game("DYNAMIC: Stacking limit is [GLOB.dynamic_stacking_limit], Classic secret is [GLOB.dynamic_classic_secret ? "Enabled" : "Disabled"], High population limit is [GLOB.dynamic_high_pop_limit].")
+ log_game("DYNAMIC: Centre is [threat_curve_centre], Width is [threat_curve_width], Forced extended is [GLOB.dynamic_forced_extended ? "Enabled" : "Disabled"], No stacking is [GLOB.dynamic_no_stacking ? "Enabled" : "Disabled"].")
+ log_game("DYNAMIC: Stacking limit is [GLOB.dynamic_stacking_limit].")
if(GLOB.dynamic_forced_threat_level >= 0)
threat_level = round(GLOB.dynamic_forced_threat_level, 0.1)
- threat = threat_level
else
generate_threat()
+ generate_budgets()
+ set_cooldowns()
+ dynamic_log("Dynamic Mode initialized with a Threat Level of... [threat_level]! ([round_start_budget] round start budget)")
+ return TRUE
- var/latejoin_injection_cooldown_middle = 0.5*(GLOB.dynamic_latejoin_delay_max + GLOB.dynamic_latejoin_delay_min)
- latejoin_injection_cooldown = round(CLAMP(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), GLOB.dynamic_latejoin_delay_min, GLOB.dynamic_latejoin_delay_max)) + world.time
+/datum/game_mode/dynamic/proc/set_cooldowns()
+ var/latejoin_injection_cooldown_middle = 0.5*(latejoin_delay_max + latejoin_delay_min)
+ latejoin_injection_cooldown = round(clamp(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), latejoin_delay_min, latejoin_delay_max)) + world.time
- var/midround_injection_cooldown_middle = 0.5*(GLOB.dynamic_midround_delay_max + GLOB.dynamic_midround_delay_min)
- midround_injection_cooldown = round(CLAMP(EXP_DISTRIBUTION(midround_injection_cooldown_middle), GLOB.dynamic_midround_delay_min, GLOB.dynamic_midround_delay_max)) + world.time
- message_admins("Dynamic Mode initialized with a Threat Level of... [threat_level]!")
- log_game("DYNAMIC: Dynamic Mode initialized with a Threat Level of... [threat_level]!")
- return TRUE
+ var/midround_injection_cooldown_middle = 0.5*(midround_delay_max + midround_delay_min)
+ midround_injection_cooldown = round(clamp(EXP_DISTRIBUTION(midround_injection_cooldown_middle), midround_delay_min, midround_delay_max)) + world.time
/datum/game_mode/dynamic/pre_setup()
+ if(CONFIG_GET(flag/dynamic_config_enabled))
+ var/json_file = file("config/dynamic.json")
+ if(fexists(json_file))
+ configuration = json_decode(file2text(json_file))
+ if(configuration["Dynamic"])
+ for(var/variable in configuration["Dynamic"])
+ if(!vars[variable])
+ stack_trace("Invalid dynamic configuration variable [variable] in game mode variable changes.")
+ continue
+ vars[variable] = configuration["Dynamic"][variable]
+
+ setup_parameters()
+ setup_hijacking()
+
+ var/valid_roundstart_ruleset = 0
for (var/rule in subtypesof(/datum/dynamic_ruleset))
var/datum/dynamic_ruleset/ruleset = new rule()
// Simple check if the ruleset should be added to the lists.
if(ruleset.name == "")
continue
+ configure_ruleset(ruleset)
switch(ruleset.ruletype)
if("Roundstart")
roundstart_rules += ruleset
+ if(ruleset.weight)
+ valid_roundstart_ruleset++
if ("Latejoin")
latejoin_rules += ruleset
if ("Midround")
- if (ruleset.weight)
- midround_rules += ruleset
- for(var/mob/dead/new_player/player in GLOB.player_list)
+ midround_rules += ruleset
+ for(var/i in GLOB.new_player_list)
+ var/mob/dead/new_player/player = i
if(player.ready == PLAYER_READY_TO_PLAY && player.mind)
roundstart_pop_ready++
candidates.Add(player)
log_game("DYNAMIC: Listing [roundstart_rules.len] round start rulesets, and [candidates.len] players ready.")
if (candidates.len <= 0)
+ log_game("DYNAMIC: [candidates.len] candidates.")
return TRUE
- if (roundstart_rules.len <= 0)
- return TRUE
-
+
if(GLOB.dynamic_forced_roundstart_ruleset.len > 0)
rigged_roundstart()
- else
+ else if(valid_roundstart_ruleset < 1)
+ log_game("DYNAMIC: [valid_roundstart_ruleset] enabled roundstart rulesets.")
+ return TRUE
+ else
roundstart()
+ dynamic_log("[round_start_budget] round start budget was left, donating it to midrounds.")
+ threat_log += "[worldtime2text()]: [round_start_budget] round start budget was left, donating it to midrounds."
+ mid_round_budget += round_start_budget
+
var/starting_rulesets = ""
for (var/datum/dynamic_ruleset/roundstart/DR in executed_rules)
starting_rulesets += "[DR.name], "
+ log_game("DYNAMIC: Picked the following roundstart rules: [starting_rulesets]")
candidates.Cut()
return TRUE
/datum/game_mode/dynamic/post_setup(report)
- update_playercounts()
-
for(var/datum/dynamic_ruleset/roundstart/rule in executed_rules)
rule.candidates.Cut() // The rule should not use candidates at this point as they all are null.
- if(rule.delay > 0)
- addtimer(CALLBACK(rule, /datum/dynamic_ruleset/roundstart.proc/execute), rule.delay)
- else
- if(!rule.execute())
- stack_trace("The starting rule \"[rule.name]\" failed to execute.")
+ addtimer(CALLBACK(src, /datum/game_mode/dynamic/.proc/execute_roundstart_rule, rule), rule.delay)
..()
/// A simple roundstart proc used when dynamic_forced_roundstart_ruleset has rules in it.
@@ -338,14 +430,21 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
message_admins("[GLOB.dynamic_forced_roundstart_ruleset.len] rulesets being forced. Will now attempt to draft players for them.")
log_game("DYNAMIC: [GLOB.dynamic_forced_roundstart_ruleset.len] rulesets being forced. Will now attempt to draft players for them.")
for (var/datum/dynamic_ruleset/roundstart/rule in GLOB.dynamic_forced_roundstart_ruleset)
+ configure_ruleset(rule)
message_admins("Drafting players for forced ruleset [rule.name].")
log_game("DYNAMIC: Drafting players for forced ruleset [rule.name].")
rule.mode = src
+ rule.acceptable(roundstart_pop_ready, threat_level) // Assigns some vars in the modes, running it here for consistency
rule.candidates = candidates.Copy()
rule.trim_candidates()
- rule.pop_per_requirement = rule.pop_per_requirement > 0 ? rule.pop_per_requirement : (src.pop_per_requirement > 0 ? src.pop_per_requirement : 6) //i hate myself for this
- if (rule.ready(TRUE))
- picking_roundstart_rule(list(rule), forced = TRUE)
+ if (rule.ready(roundstart_pop_ready, TRUE))
+ var/cost = rule.cost
+ var/scaled_times = 0
+ if (rule.scaling_cost)
+ scaled_times = round(max(round_start_budget - cost, 0) / rule.scaling_cost)
+ cost += rule.scaling_cost * scaled_times
+
+ spend_roundstart_budget(picking_roundstart_rule(rule, scaled_times, forced = TRUE))
/datum/game_mode/dynamic/proc/roundstart()
if (GLOB.dynamic_forced_extended)
@@ -353,150 +452,85 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
return TRUE
var/list/drafted_rules = list()
for (var/datum/dynamic_ruleset/roundstart/rule in roundstart_rules)
- if (rule.acceptable(roundstart_pop_ready, threat_level) && threat >= rule.cost) // If we got the population and threat required
+ if (!rule.weight)
+ continue
+ if (rule.acceptable(roundstart_pop_ready, threat_level) && round_start_budget >= rule.cost) // If we got the population and threat required
rule.candidates = candidates.Copy()
rule.trim_candidates()
- if (rule.ready() && rule.candidates.len > 0)
+ if (rule.ready(roundstart_pop_ready) && rule.candidates.len > 0)
drafted_rules[rule] = rule.weight
- var/indice_pop = min(10,round(roundstart_pop_ready/pop_per_requirement)+1)
- var/extra_rulesets_amount = 0
- if (GLOB.dynamic_classic_secret)
- extra_rulesets_amount = 0
- else
- if (roundstart_pop_ready > GLOB.dynamic_high_pop_limit)
- message_admins("High Population Override is in effect! Threat Level will have more impact on which roles will appear, and player population less.")
- log_game("DYNAMIC: High Population Override is in effect! Threat Level will have more impact on which roles will appear, and player population less.")
- if (threat_level > high_pop_second_rule_req)
- extra_rulesets_amount++
- if (threat_level > high_pop_third_rule_req)
- extra_rulesets_amount++
- else
- if (threat_level >= second_rule_req[indice_pop])
- extra_rulesets_amount++
- if (threat_level >= third_rule_req[indice_pop])
- extra_rulesets_amount++
-
- if (drafted_rules.len > 0 && picking_roundstart_rule(drafted_rules))
- if (extra_rulesets_amount > 0) // We've got enough population and threat for a second rulestart rule
- for (var/datum/dynamic_ruleset/roundstart/rule in drafted_rules)
- if (rule.cost > threat)
- drafted_rules -= rule
- if (drafted_rules.len > 0 && picking_roundstart_rule(drafted_rules))
- if (extra_rulesets_amount > 1) // We've got enough population and threat for a third rulestart rule
- for (var/datum/dynamic_ruleset/roundstart/rule in drafted_rules)
- if (rule.cost > threat)
- drafted_rules -= rule
- picking_roundstart_rule(drafted_rules)
- else
- return FALSE
- return TRUE
+ var/list/rulesets_picked = list()
-/// Picks a random roundstart rule from the list given as an argument and executes it.
-/datum/game_mode/dynamic/proc/picking_roundstart_rule(list/drafted_rules = list(), forced = FALSE)
- var/datum/dynamic_ruleset/roundstart/starting_rule = pickweight(drafted_rules)
- if(!starting_rule)
- return FALSE
+ // Kept in case a ruleset can't be initialized for whatever reason, we want to be able to only spend what we can use.
+ var/round_start_budget_left = round_start_budget
- if(!forced)
- if(only_ruleset_executed)
- return FALSE
- // Check if a blocking ruleset has been executed.
- else if(check_blocking(starting_rule.blocking_rules, executed_rules))
- drafted_rules -= starting_rule
- if(drafted_rules.len <= 0)
- return FALSE
- starting_rule = pickweight(drafted_rules)
- // Check if the ruleset is highlander and if a highlander ruleset has been executed
- else if(starting_rule.flags & HIGHLANDER_RULESET)
- if(threat < GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking)
- if(highlander_executed)
- drafted_rules -= starting_rule
- if(drafted_rules.len <= 0)
- return FALSE
- starting_rule = pickweight(drafted_rules)
- // With low pop and high threat there might be rulesets that get executed with no valid candidates.
- else if(starting_rule.ready())
- drafted_rules -= starting_rule
- if(drafted_rules.len <= 0)
- return FALSE
- starting_rule = pickweight(drafted_rules)
-
- message_admins("Picking a ruleset [starting_rule.name]")
- log_game("DYNAMIC: Picking a ruleset [starting_rule.name]")
-
- roundstart_rules -= starting_rule
- drafted_rules -= starting_rule
-
- starting_rule.trim_candidates()
- if (starting_rule.pre_execute())
- spend_threat(starting_rule.cost)
- threat_log += "[worldtime2text()]: Roundstart [starting_rule.name] spent [starting_rule.cost]"
- if(starting_rule.flags & HIGHLANDER_RULESET)
- highlander_executed = TRUE
- else if(starting_rule.flags & ONLY_RULESET)
- only_ruleset_executed = TRUE
- executed_rules += starting_rule
- if (starting_rule.persistent)
- current_rules += starting_rule
- for (var/datum/dynamic_ruleset/roundstart/rule in drafted_rules)
- if (!rule.ready())
- drafted_rules -= rule // And removing rules that are no longer elligible
- return TRUE
- else
- stack_trace("The starting rule \"[starting_rule.name]\" failed to pre_execute.")
- return FALSE
+ while (round_start_budget_left > 0)
+ var/datum/dynamic_ruleset/roundstart/ruleset = pickweightAllowZero(drafted_rules)
+ if (isnull(ruleset))
+ log_game("DYNAMIC: No more rules can be applied, stopping with [round_start_budget] left.")
+ break
-/// Picks a random midround OR latejoin rule from the list given as an argument and executes it.
-/// Also this could be named better.
-/datum/game_mode/dynamic/proc/picking_midround_latejoin_rule(list/drafted_rules = list(), forced = FALSE)
- var/datum/dynamic_ruleset/rule = pickweight(drafted_rules)
- if(!rule)
- return FALSE
-
- if(!forced)
- if(only_ruleset_executed)
- return FALSE
- // Check if a blocking ruleset has been executed.
- else if(check_blocking(rule.blocking_rules, executed_rules))
- drafted_rules -= rule
- if(drafted_rules.len <= 0)
- return FALSE
- rule = pickweight(drafted_rules)
- // Check if the ruleset is highlander and if a highlander ruleset has been executed
- else if(rule.flags & HIGHLANDER_RULESET)
- if(threat < GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking)
- if(highlander_executed)
- drafted_rules -= rule
- if(drafted_rules.len <= 0)
- return FALSE
- rule = pickweight(drafted_rules)
-
- if(!rule.repeatable)
- if(rule.ruletype == "Latejoin")
- latejoin_rules = remove_from_list(latejoin_rules, rule.type)
- else if(rule.type == "Midround")
- midround_rules = remove_from_list(midround_rules, rule.type)
-
- if (rule.execute())
- log_game("DYNAMIC: Injected a [rule.ruletype == "latejoin" ? "latejoin" : "midround"] ruleset [rule.name].")
- spend_threat(rule.cost)
- threat_log += "[worldtime2text()]: [rule.ruletype] [rule.name] spent [rule.cost]"
- if(rule.flags & HIGHLANDER_RULESET)
- highlander_executed = TRUE
- else if(rule.flags & ONLY_RULESET)
+ var/cost = (ruleset in rulesets_picked) ? ruleset.scaling_cost : ruleset.cost
+ if (cost == 0)
+ stack_trace("[ruleset] cost 0, this is going to result in an infinite loop.")
+ drafted_rules[ruleset] = null
+ continue
+
+ if (cost > round_start_budget_left)
+ drafted_rules[ruleset] = null
+ continue
+
+ if (check_blocking(ruleset.blocking_rules, rulesets_picked))
+ drafted_rules[ruleset] = null
+ continue
+
+ round_start_budget_left -= cost
+
+ rulesets_picked[ruleset] += 1
+
+ if (ruleset.flags & HIGH_IMPACT_RULESET)
+ for (var/_other_ruleset in drafted_rules)
+ var/datum/dynamic_ruleset/other_ruleset = _other_ruleset
+ if (other_ruleset.flags & HIGH_IMPACT_RULESET)
+ drafted_rules[other_ruleset] = null
+
+ if (ruleset.flags & LONE_RULESET)
+ drafted_rules[ruleset] = null
+
+ for (var/ruleset in rulesets_picked)
+ spend_roundstart_budget(picking_roundstart_rule(ruleset, rulesets_picked[ruleset] - 1))
+
+/// Initializes the round start ruleset provided to it. Returns how much threat to spend.
+/datum/game_mode/dynamic/proc/picking_roundstart_rule(datum/dynamic_ruleset/roundstart/ruleset, scaled_times = 0, forced = FALSE)
+ log_game("DYNAMIC: Picked a ruleset: [ruleset.name], scaled [scaled_times] times")
+
+ ruleset.trim_candidates()
+ var/added_threat = ruleset.scale_up(roundstart_pop_ready, scaled_times)
+
+ if(ruleset.pre_execute(roundstart_pop_ready))
+ threat_log += "[worldtime2text()]: Roundstart [ruleset.name] spent [ruleset.cost + added_threat]. [ruleset.scaling_cost ? "Scaled up [ruleset.scaled_times]/[scaled_times] times." : ""]"
+ if(ruleset.flags & ONLY_RULESET)
only_ruleset_executed = TRUE
- if(rule.ruletype == "Latejoin")
- var/mob/M = pick(rule.candidates)
- message_admins("[key_name(M)] joined the station, and was selected by the [rule.name] ruleset.")
- log_game("DYNAMIC: [key_name(M)] joined the station, and was selected by the [rule.name] ruleset.")
- executed_rules += rule
- rule.candidates.Cut()
- if (rule.persistent)
+ if(ruleset.flags & HIGH_IMPACT_RULESET)
+ high_impact_ruleset_executed = TRUE
+ executed_rules += ruleset
+ return ruleset.cost + added_threat
+ else
+ stack_trace("The starting rule \"[ruleset.name]\" failed to pre_execute.")
+ return 0
+
+/// Mainly here to facilitate delayed rulesets. All roundstart rulesets are executed with a timered callback to this proc.
+/datum/game_mode/dynamic/proc/execute_roundstart_rule(sent_rule)
+ var/datum/dynamic_ruleset/rule = sent_rule
+ if(rule.execute())
+ if(rule.persistent)
current_rules += rule
+ new_snapshot(rule)
return TRUE
- else
- stack_trace("The [rule.ruletype] rule \"[rule.name]\" failed to execute.")
+ rule.clean_up() // Refund threat, delete teams and so on.
+ executed_rules -= rule
+ stack_trace("The starting rule \"[rule.name]\" failed to execute.")
return FALSE
/// An experimental proc to allow admins to call rules on the fly or have rules call other rules.
@@ -504,35 +538,37 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
var/datum/dynamic_ruleset/midround/new_rule
if(ispath(ruletype))
new_rule = new ruletype() // You should only use it to call midround rules though.
+ configure_ruleset(new_rule)
else if(istype(ruletype, /datum/dynamic_ruleset))
new_rule = ruletype
else
return FALSE
-
+
if(!new_rule)
return FALSE
-
+
if(!forced)
if(only_ruleset_executed)
return FALSE
// Check if a blocking ruleset has been executed.
else if(check_blocking(new_rule.blocking_rules, executed_rules))
return FALSE
- // Check if the ruleset is highlander and if a highlander ruleset has been executed
- else if(new_rule.flags & HIGHLANDER_RULESET)
- if(threat < GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking)
- if(highlander_executed)
+ // Check if the ruleset is high impact and if a high impact ruleset has been executed
+ else if(new_rule.flags & HIGH_IMPACT_RULESET)
+ if(threat_level < GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking)
+ if(high_impact_ruleset_executed)
return FALSE
-
- update_playercounts()
- if ((forced || (new_rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && new_rule.cost <= threat)))
+
+ var/population = current_players[CURRENT_LIVING_PLAYERS].len
+ if((new_rule.acceptable(population, threat_level) && new_rule.cost <= mid_round_budget) || forced)
new_rule.trim_candidates()
if (new_rule.ready(forced))
- spend_threat(new_rule.cost)
+ spend_midround_budget(new_rule.cost)
threat_log += "[worldtime2text()]: Forced rule [new_rule.name] spent [new_rule.cost]"
+ new_rule.pre_execute(population)
if (new_rule.execute()) // This should never fail since ready() returned 1
- if(new_rule.flags & HIGHLANDER_RULESET)
- highlander_executed = TRUE
+ if(new_rule.flags & HIGH_IMPACT_RULESET)
+ high_impact_ruleset_executed = TRUE
else if(new_rule.flags & ONLY_RULESET)
only_ruleset_executed = TRUE
log_game("DYNAMIC: Making a call to a specific ruleset...[new_rule.name]!")
@@ -545,10 +581,6 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
return FALSE
/datum/game_mode/dynamic/process()
- if (pop_last_updated < world.time - (60 SECONDS))
- pop_last_updated = world.time
- update_playercounts()
-
for (var/datum/dynamic_ruleset/rule in current_rules)
if(rule.rule_process() == RULESET_STOP_PROCESSING) // If rule_process() returns 1 (RULESET_STOP_PROCESSING), stop processing.
current_rules -= rule
@@ -556,63 +588,54 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
if (midround_injection_cooldown < world.time)
if (GLOB.dynamic_forced_extended)
return
-
+
// Somehow it managed to trigger midround multiple times so this was moved here.
// There is no way this should be able to trigger an injection twice now.
- var/midround_injection_cooldown_middle = 0.5*(GLOB.dynamic_midround_delay_max + GLOB.dynamic_midround_delay_min)
- midround_injection_cooldown = (round(CLAMP(EXP_DISTRIBUTION(midround_injection_cooldown_middle), GLOB.dynamic_midround_delay_min, GLOB.dynamic_midround_delay_max)) + world.time)
-
+ var/midround_injection_cooldown_middle = 0.5*(midround_delay_max + midround_delay_min)
+ midround_injection_cooldown = (round(clamp(EXP_DISTRIBUTION(midround_injection_cooldown_middle), midround_delay_min, midround_delay_max)) + world.time)
+
// Time to inject some threat into the round
if(EMERGENCY_ESCAPED_OR_ENDGAMED) // Unless the shuttle is gone
return
- message_admins("DYNAMIC: Checking for midround injection.")
- log_game("DYNAMIC: Checking for midround injection.")
-
- update_playercounts()
- if (get_injection_chance())
+ dynamic_log("Checking for midround injection.")
+
+ last_midround_injection_attempt = world.time
+
+ if (prob(get_midround_injection_chance()))
var/list/drafted_rules = list()
for (var/datum/dynamic_ruleset/midround/rule in midround_rules)
- if (rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && threat >= rule.cost)
- // Classic secret : only autotraitor/minor roles
- if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET)))
+ if (!rule.weight)
+ continue
+ if (rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && mid_round_budget >= rule.cost)
+ // If admins have disabled dynamic from picking from the ghost pool
+ if(rule.ruletype == "Latejoin" && !(GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT))
+ continue
+ // If admins have disabled dynamic from picking from the ghost pool
+ if(rule.ruletype == "Latejoin" && !(GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT))
continue
rule.trim_candidates()
if (rule.ready())
drafted_rules[rule] = rule.get_weight()
if (drafted_rules.len > 0)
- picking_midround_latejoin_rule(drafted_rules)
-
-/// Updates current_players.
-/datum/game_mode/dynamic/proc/update_playercounts()
- current_players[CURRENT_LIVING_PLAYERS] = list()
- current_players[CURRENT_LIVING_ANTAGS] = list()
- current_players[CURRENT_DEAD_PLAYERS] = list()
- current_players[CURRENT_OBSERVERS] = list()
- for (var/mob/M in GLOB.player_list)
- if (istype(M, /mob/dead/new_player))
- continue
- if (M.stat != DEAD)
- current_players[CURRENT_LIVING_PLAYERS].Add(M)
- if (M.mind && (M.mind.special_role || M.mind.antag_datums?.len > 0))
- current_players[CURRENT_LIVING_ANTAGS].Add(M)
- else
- if (istype(M,/mob/dead/observer))
- var/mob/dead/observer/O = M
- if (O.started_as_observer) // Observers
- current_players[CURRENT_OBSERVERS].Add(M)
- continue
- current_players[CURRENT_DEAD_PLAYERS].Add(M) // Players who actually died (and admins who ghosted, would be nice to avoid counting them somehow)
+ pick_midround_rule(drafted_rules)
+ else if (random_event_hijacked == HIJACKED_TOO_SOON)
+ log_game("DYNAMIC: Midround injection failed when random event was hijacked. Spawning another random event in its place.")
+
+ // A random event antag would have rolled had this injection check passed.
+ // As a refund, spawn a non-ghost-role random event.
+ SSevents.spawnEvent()
+ SSevents.reschedule()
+
+ random_event_hijacked = HIJACKED_NOTHING
-/// Gets the chance for latejoin and midround injection, the dry_run argument is only used for forced injection.
+/// Gets the chance for latejoin injection, the dry_run argument is only used for forced injection.
/datum/game_mode/dynamic/proc/get_injection_chance(dry_run = FALSE)
if(forced_injection)
- forced_injection = !dry_run
+ forced_injection = dry_run
return 100
var/chance = 0
- // If the high pop override is in effect, we reduce the impact of population on the antag injection chance
- var/high_pop_factor = (current_players[CURRENT_LIVING_PLAYERS].len >= GLOB.dynamic_high_pop_limit)
- var/max_pop_per_antag = max(5,15 - round(threat_level/10) - round(current_players[CURRENT_LIVING_PLAYERS].len/(high_pop_factor ? 10 : 5)))
+ var/max_pop_per_antag = max(5,15 - round(threat_level/10) - round(current_players[CURRENT_LIVING_PLAYERS].len/5))
if (!current_players[CURRENT_LIVING_ANTAGS].len)
chance += 50 // No antags at all? let's boost those odds!
else
@@ -623,12 +646,22 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
chance += 25-10*(max_pop_per_antag-current_pop_per_antag)
if (current_players[CURRENT_DEAD_PLAYERS].len > current_players[CURRENT_LIVING_PLAYERS].len)
chance -= 30 // More than half the crew died? ew, let's calm down on antags
- if (threat > 70)
- chance += 15
- if (threat < 30)
- chance -= 15
+ if (mid_round_budget > higher_injection_chance_minimum_threat)
+ chance += higher_injection_chance
+ if (mid_round_budget < lower_injection_chance_minimum_threat)
+ chance -= lower_injection_chance
return round(max(0,chance))
+/// Gets the chance for midround injection, the dry_run argument is only used for forced injection.
+/// Usually defers to the latejoin injection chance.
+/datum/game_mode/dynamic/proc/get_midround_injection_chance(dry_run)
+ var/chance = get_injection_chance(dry_run)
+
+ if (random_event_hijacked != HIJACKED_NOTHING)
+ chance += hijacked_random_event_injection_chance
+
+ return chance
+
/// Removes type from the list
/datum/game_mode/dynamic/proc/remove_from_list(list/type_list, type)
for(var/I in type_list)
@@ -640,7 +673,8 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
/datum/game_mode/dynamic/proc/check_blocking(list/blocking_list, list/rule_list)
if(blocking_list.len > 0)
for(var/blocking in blocking_list)
- for(var/datum/executed in rule_list)
+ for(var/_executed in rule_list)
+ var/datum/executed = _executed
if(blocking == executed.type)
return TRUE
return FALSE
@@ -660,53 +694,72 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
if(EMERGENCY_ESCAPED_OR_ENDGAMED) // No more rules after the shuttle has left
return
- update_playercounts()
-
if (forced_latejoin_rule)
forced_latejoin_rule.candidates = list(newPlayer)
forced_latejoin_rule.trim_candidates()
log_game("DYNAMIC: Forcing ruleset [forced_latejoin_rule]")
if (forced_latejoin_rule.ready(TRUE))
- picking_midround_latejoin_rule(list(forced_latejoin_rule), forced = TRUE)
+ if (!forced_latejoin_rule.repeatable)
+ latejoin_rules = remove_from_list(latejoin_rules, forced_latejoin_rule.type)
+ addtimer(CALLBACK(src, /datum/game_mode/dynamic/.proc/execute_midround_latejoin_rule, forced_latejoin_rule), forced_latejoin_rule.delay)
forced_latejoin_rule = null
else if (latejoin_injection_cooldown < world.time && prob(get_injection_chance()))
var/list/drafted_rules = list()
for (var/datum/dynamic_ruleset/latejoin/rule in latejoin_rules)
- if (rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && threat >= rule.cost)
- // Classic secret : only autotraitor/minor roles
- if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET)))
- continue
- // No stacking : only one round-enter, unless > stacking_limit threat.
- if (threat < GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking)
- if(rule.flags & HIGHLANDER_RULESET && highlander_executed)
+ if (!rule.weight)
+ continue
+ if (rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && mid_round_budget >= rule.cost)
+ // No stacking : only one round-ender, unless threat level > stacking_limit.
+ if (threat_level < GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking)
+ if(rule.flags & HIGH_IMPACT_RULESET && high_impact_ruleset_executed)
continue
-
+
rule.candidates = list(newPlayer)
rule.trim_candidates()
if (rule.ready())
drafted_rules[rule] = rule.get_weight()
- if (drafted_rules.len > 0 && picking_midround_latejoin_rule(drafted_rules))
- var/latejoin_injection_cooldown_middle = 0.5*(GLOB.dynamic_latejoin_delay_max + GLOB.dynamic_latejoin_delay_min)
- latejoin_injection_cooldown = round(CLAMP(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), GLOB.dynamic_latejoin_delay_min, GLOB.dynamic_latejoin_delay_max)) + world.time
+ if (drafted_rules.len > 0 && pick_latejoin_rule(drafted_rules))
+ var/latejoin_injection_cooldown_middle = 0.5*(latejoin_delay_max + latejoin_delay_min)
+ latejoin_injection_cooldown = round(clamp(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), latejoin_delay_min, latejoin_delay_max)) + world.time
+
+/// Apply configurations to rule.
+/datum/game_mode/dynamic/proc/configure_ruleset(datum/dynamic_ruleset/ruleset)
+ var/rule_conf = LAZYACCESSASSOC(configuration, ruleset.ruletype, ruleset.name)
+ for(var/variable in rule_conf)
+ if(!(variable in ruleset.vars))
+ stack_trace("Invalid dynamic configuration variable [variable] in [ruleset.ruletype] [ruleset.name].")
+ continue
+ ruleset.vars[variable] = rule_conf[variable]
+ if(CONFIG_GET(flag/protect_roles_from_antagonist))
+ ruleset.restricted_roles |= ruleset.protected_roles
+ if(CONFIG_GET(flag/protect_assistant_from_antagonist))
+ ruleset.restricted_roles |= "Assistant"
+ if(CONFIG_GET(flag/protect_heads_from_antagonist))
+ ruleset.restricted_roles |= GLOB.command_positions
/// Refund threat, but no more than threat_level.
/datum/game_mode/dynamic/proc/refund_threat(regain)
- threat = min(threat_level,threat+regain)
+ mid_round_budget = min(threat_level, mid_round_budget + regain)
/// Generate threat and increase the threat_level if it goes beyond, capped at 100
/datum/game_mode/dynamic/proc/create_threat(gain)
- threat = min(100, threat+gain)
- if(threat > threat_level)
- threat_level = threat
+ mid_round_budget = min(100, mid_round_budget + gain)
+ if(mid_round_budget > threat_level)
+ threat_level = mid_round_budget
-/// Expend threat, can't fall under 0.
-/datum/game_mode/dynamic/proc/spend_threat(cost)
- threat = max(threat-cost,0)
+/// Expend round start threat, can't fall under 0.
+/datum/game_mode/dynamic/proc/spend_roundstart_budget(cost)
+ round_start_budget = max(round_start_budget - cost,0)
-/// Turns the value generated by lorentz distribution to threat value between 0 and 100.
-/datum/game_mode/dynamic/proc/lorentz_to_threat(x)
+/// Expend midround threat, can't fall under 0.
+/datum/game_mode/dynamic/proc/spend_midround_budget(cost)
+ mid_round_budget = max(mid_round_budget - cost,0)
+
+/// Turns the value generated by lorentz distribution to number between 0 and 100.
+/// Used for threat level and splitting the budgets.
+/datum/game_mode/dynamic/proc/lorentz_to_amount(x)
switch (x)
if (-INFINITY to -20)
return rand(0, 10)
@@ -728,3 +781,12 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
return RULE_OF_THREE(40, 20, x) + 50
if (20 to INFINITY)
return rand(90, 100)
+
+/// Log to messages and to the game
+/datum/game_mode/dynamic/proc/dynamic_log(text)
+ message_admins("DYNAMIC: [text]")
+ log_game("DYNAMIC: [text]")
+
+#undef FAKE_REPORT_CHANCE
+#undef REPORT_NEG_DIVERGENCE
+#undef REPORT_POS_DIVERGENCE
diff --git a/code/game/gamemodes/dynamic/dynamic_hijacking.dm b/code/game/gamemodes/dynamic/dynamic_hijacking.dm
new file mode 100644
index 0000000000000..b2812babc9b84
--- /dev/null
+++ b/code/game/gamemodes/dynamic/dynamic_hijacking.dm
@@ -0,0 +1,28 @@
+/datum/game_mode/dynamic/proc/setup_hijacking()
+ RegisterSignal(SSdcs, COMSIG_GLOB_PRE_RANDOM_EVENT, .proc/on_pre_random_event)
+
+/datum/game_mode/dynamic/proc/on_pre_random_event(datum/source, datum/round_event_control/round_event_control)
+ SIGNAL_HANDLER
+
+ if (!round_event_control.dynamic_should_hijack)
+ return
+
+ if (random_event_hijacked != HIJACKED_NOTHING)
+ dynamic_log("Random event [round_event_control.name] tried to roll, but Dynamic vetoed it (random event has already ran).")
+ SSevents.spawnEvent()
+ SSevents.reschedule()
+ return CANCEL_PRE_RANDOM_EVENT
+
+ var/time_range = rand(random_event_hijack_minimum, random_event_hijack_maximum)
+
+ if (world.time - last_midround_injection_attempt < time_range)
+ random_event_hijacked = HIJACKED_TOO_RECENT
+ dynamic_log("Random event [round_event_control.name] tried to roll, but the last midround injection \
+ was too recent. Injection chance has been raised to [get_midround_injection_chance(dry_run = TRUE)]%.")
+ return CANCEL_PRE_RANDOM_EVENT
+
+ if (midround_injection_cooldown - world.time < time_range)
+ random_event_hijacked = HIJACKED_TOO_SOON
+ dynamic_log("Random event [round_event_control.name] tried to roll, but the next midround injection \
+ is too soon. Injection chance has been raised to [get_midround_injection_chance(dry_run = TRUE)]%.")
+ return CANCEL_PRE_RANDOM_EVENT
diff --git a/code/game/gamemodes/dynamic/dynamic_logging.dm b/code/game/gamemodes/dynamic/dynamic_logging.dm
new file mode 100644
index 0000000000000..08eb1330550d2
--- /dev/null
+++ b/code/game/gamemodes/dynamic/dynamic_logging.dm
@@ -0,0 +1,97 @@
+/// A "snapshot" of dynamic at an important point in time.
+/// Exported to JSON in the dynamic.json log file.
+/datum/dynamic_snapshot
+ /// The remaining midround threat
+ var/remaining_threat
+
+ /// The world.time when the snapshot was taken
+ var/time
+
+ /// The total number of players in the server
+ var/total_players
+
+ /// The number of alive players
+ var/alive_players
+
+ /// The number of dead players
+ var/dead_players
+
+ /// The number of observers
+ var/observers
+
+ /// The number of alive antags
+ var/alive_antags
+
+ /// The rulesets chosen this snapshot
+ var/datum/dynamic_snapshot_ruleset/ruleset_chosen
+
+ /// The cached serialization of this snapshot
+ var/serialization
+
+/// A ruleset chosen during a snapshot
+/datum/dynamic_snapshot_ruleset
+ /// The name of the ruleset chosen
+ var/name
+
+ /// If it is a round start ruleset, how much it was scaled by
+ var/scaled
+
+ /// The number of assigned antags
+ var/assigned
+
+/datum/dynamic_snapshot_ruleset/New(datum/dynamic_ruleset/ruleset)
+ name = ruleset.name
+ assigned = ruleset.assigned.len
+
+ if (istype(ruleset, /datum/dynamic_ruleset/roundstart))
+ scaled = ruleset.scaled_times
+
+/// Convert the snapshot to an associative list
+/datum/dynamic_snapshot/proc/to_list()
+ if (!isnull(serialization))
+ return serialization
+
+ serialization = list(
+ "remaining_threat" = remaining_threat,
+ "time" = time,
+ "total_players" = total_players,
+ "alive_players" = alive_players,
+ "dead_players" = dead_players,
+ "observers" = observers,
+ "alive_antags" = alive_antags,
+ "ruleset_chosen" = list(
+ "name" = ruleset_chosen.name,
+ "scaled" = ruleset_chosen.scaled,
+ "assigned" = ruleset_chosen.assigned,
+ ),
+ )
+
+ return serialization
+
+/// Creates a new snapshot with the given rulesets chosen, and writes to the JSON output.
+/datum/game_mode/dynamic/proc/new_snapshot(datum/dynamic_ruleset/ruleset_chosen)
+ var/datum/dynamic_snapshot/new_snapshot = new
+
+ new_snapshot.remaining_threat = mid_round_budget
+ new_snapshot.time = world.time
+ new_snapshot.alive_players = current_players[CURRENT_LIVING_PLAYERS].len
+ new_snapshot.dead_players = current_players[CURRENT_DEAD_PLAYERS].len
+ new_snapshot.observers = current_players[CURRENT_OBSERVERS].len
+ new_snapshot.total_players = new_snapshot.alive_players + new_snapshot.dead_players + new_snapshot.observers
+ new_snapshot.alive_antags = current_players[CURRENT_LIVING_ANTAGS].len
+ new_snapshot.ruleset_chosen = new /datum/dynamic_snapshot_ruleset(ruleset_chosen)
+
+ LAZYADD(snapshots, new_snapshot)
+
+ var/list/serialized = list()
+ serialized["threat_level"] = threat_level
+ serialized["round_start_budget"] = initial_round_start_budget
+ serialized["mid_round_budget"] = threat_level - initial_round_start_budget
+
+ var/list/serialized_snapshots = list()
+ for (var/_snapshot in snapshots)
+ var/datum/dynamic_snapshot/snapshot = _snapshot
+ serialized_snapshots += list(snapshot.to_list())
+ serialized["snapshots"] = serialized_snapshots
+
+ rustg_file_write(json_encode(serialized), "[GLOB.log_directory]/dynamic.json")
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets.dm b/code/game/gamemodes/dynamic/dynamic_rulesets.dm
index 92c5bfb8116d9..48523457569e3 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets.dm
@@ -1,3 +1,6 @@
+#define REVOLUTION_VICTORY 1
+#define STATION_VICTORY 2
+
/datum/dynamic_ruleset
/// For admin logging and round end screen.
var/name = ""
@@ -31,21 +34,23 @@
var/required_enemies = list(1,1,0,0,0,0,0,0,0,0)
/// The rule needs this many candidates (post-trimming) to be executed (example: Cult needs 4 players at round start)
var/required_candidates = 0
- /// 1 -> 9, probability for this rule to be picked against other rules
+ /// 0 -> 9, probability for this rule to be picked against other rules. If zero this will effectively disable the rule.
var/weight = 5
/// Threat cost for this rule, this is decreased from the mode's threat when the rule is executed.
var/cost = 0
- /// A flag that determines how the ruleset is handled
- /// HIGHLANDER_RULESET are rulesets can end the round.
- /// TRAITOR_RULESET and MINOR_RULESET can't end the round and have no difference right now.
- var/flags = 0
+ /// Cost per level the rule scales up.
+ var/scaling_cost = 0
+ /// How many times a rule has scaled up upon getting picked.
+ var/scaled_times = 0
+ /// Used for the roundend report
+ var/total_cost = 0
+ /// A flag that determines how the ruleset is handled. Check __DEFINES/dynamic.dm for an explanation of the accepted values.
+ var/flags = NONE
/// Pop range per requirement. If zero defaults to mode's pop_per_requirement.
var/pop_per_requirement = 0
/// Requirements are the threat level requirements per pop range.
/// With the default values, The rule will never get drafted below 10 threat level (aka: "peaceful extended"), and it requires a higher threat level at lower pops.
var/list/requirements = list(40,30,20,10,10,10,10,10,10,10)
- /// An alternative, static requirement used instead when pop is over mode's high_pop_limit.
- var/high_population_requirement = 10
/// Reference to the mode, use this instead of SSticker.mode.
var/datum/game_mode/dynamic/mode = null
/// If a role is to be considered another for the purpose of banning.
@@ -57,28 +62,32 @@
/// The maximum amount of players required for the rule to be considered.
/// Anything below zero or exactly zero is ignored.
var/maximum_players = 0
+ /// Calculated during acceptable(), used in scaling and team sizes.
+ var/indice_pop = 0
+ /// Base probability used in scaling. The higher it is, the more likely to scale. Kept as a var to allow for config editing._SendSignal(sigtype, list/arguments)
+ var/base_prob = 60
+ /// Delay for when execute will get called from the time of post_setup (roundstart) or process (midround/latejoin).
+ /// Make sure your ruleset works with execute being called during the game when using this, and that the clean_up proc reverts it properly in case of faliure.
+ var/delay = 0
+
+ /// Judges the amount of antagonists to apply, for both solo and teams.
+ /// Note that some antagonists (such as traitors, lings, heretics, etc) will add more based on how many times they've been scaled.
+ /// Written as a linear equation--ceil(x/denominator) + offset, or as a fixed constant.
+ /// If written as a linear equation, will be in the form of `list("denominator" = denominator, "offset" = offset).
+ var/antag_cap = 0
/datum/dynamic_ruleset/New()
..()
- if(CONFIG_GET(flag/protect_roles_from_antagonist))
- restricted_roles += protected_roles
- if(CONFIG_GET(flag/protect_assistant_from_antagonist))
- restricted_roles += "Assistant"
- if(CONFIG_GET(flag/protect_heads_from_antagonist))
- restricted_roles += GLOB.command_positions
if (istype(SSticker.mode, /datum/game_mode/dynamic))
mode = SSticker.mode
- else if (GLOB.master_mode != "dynamic") // This is here to make roundstart forced ruleset function.
+ else if (!SSticker.is_mode("dynamic")) // This is here to make roundstart forced ruleset function.
qdel(src)
/datum/dynamic_ruleset/roundstart // One or more of those drafted at roundstart
ruletype = "Roundstart"
- /// Delay for when execute will get called from the time of post_setup.
- /// Make sure your ruleset works with execute being called during the game when using this.
- var/delay = 0
// Can be drafted when a player joins the server
/datum/dynamic_ruleset/latejoin
@@ -87,17 +96,41 @@
/// By default, a rule is acceptable if it satisfies the threat level/population requirements.
/// If your rule has extra checks, such as counting security officers, do that in ready() instead
/datum/dynamic_ruleset/proc/acceptable(population = 0, threat_level = 0)
- pop_per_requirement = pop_per_requirement > 0 ? pop_per_requirement : (mode.pop_per_requirement > 0 ? mode.pop_per_requirement : 6) //sorry ike
-
if(minimum_players > population)
return FALSE
if(maximum_players > 0 && population > maximum_players)
return FALSE
- if (population >= GLOB.dynamic_high_pop_limit)
- return (threat_level >= high_population_requirement)
- else
- var/indice_pop = min(10,round(population/pop_per_requirement)+1) //fuck
- return (threat_level >= requirements[indice_pop])
+
+ pop_per_requirement = pop_per_requirement > 0 ? pop_per_requirement : mode.pop_per_requirement
+ indice_pop = min(requirements.len,round(population/pop_per_requirement)+1)
+ return (threat_level >= requirements[indice_pop])
+
+/// When picking rulesets, if dynamic picks the same one multiple times, it will "scale up".
+/// However, doing this blindly would result in lowpop rounds (think under 10 people) where over 80% of the crew is antags!
+/// This function is here to ensure the antag ratio is kept under control while scaling up.
+/// Returns how much threat to actually spend in the end.
+/datum/dynamic_ruleset/proc/scale_up(population, max_scale)
+ if (!scaling_cost)
+ return 0
+
+ var/antag_fraction = 0
+ for(var/_ruleset in (mode.executed_rules + list(src))) // we care about the antags we *will* assign, too
+ var/datum/dynamic_ruleset/ruleset = _ruleset
+ antag_fraction += ((1 + ruleset.scaled_times) * ruleset.get_antag_cap(population)) / mode.roundstart_pop_ready
+
+ for(var/i in 1 to max_scale)
+ if(antag_fraction < 0.25)
+ scaled_times += 1
+ antag_fraction += get_antag_cap(population) / mode.roundstart_pop_ready // we added new antags, gotta update the %
+
+ return scaled_times * scaling_cost
+
+/// Returns what the antag cap with the given population is.
+/datum/dynamic_ruleset/proc/get_antag_cap(population)
+ if (isnum(antag_cap))
+ return antag_cap
+
+ return CEILING(population / antag_cap["denominator"], 1) + (antag_cap["offset"] || 0)
/// This is called if persistent variable is true everytime SSTicker ticks.
/datum/dynamic_ruleset/proc/rule_process()
@@ -124,6 +157,12 @@
return FALSE
return TRUE
+/// Runs from gamemode process() if ruleset fails to start, like delayed rulesets not getting valid candidates.
+/// This one only handles refunding the threat, override in ruleset to clean up the rest.
+/datum/dynamic_ruleset/proc/clean_up()
+ mode.refund_threat(cost + (scaled_times * scaling_cost))
+ mode.threat_log += "[worldtime2text()]: [ruletype] [name] refunded [cost + (scaled_times * scaling_cost)]. Failed to execute."
+
/// Gets weight of the ruleset
/// Note that this decreases weight if repeatable is TRUE and repeatable_weight_decrease is higher than 0
/// Note: If you don't want repeatable rulesets to decrease their weight use the weight variable directly
@@ -141,14 +180,9 @@
return
/// Set mode result and news report here.
-/// Only called if ruleset is flagged as HIGHLANDER_RULESET
+/// Only called if ruleset is flagged as HIGH_IMPACT_RULESET
/datum/dynamic_ruleset/proc/round_result()
-/// Checks if round is finished, return true to end the round.
-/// Only called if ruleset is flagged as HIGHLANDER_RULESET
-/datum/dynamic_ruleset/proc/check_finished()
- return FALSE
-
//////////////////////////////////////////////
// //
// ROUNDSTART RULESETS //
@@ -158,25 +192,24 @@
/// Checks if candidates are connected and if they are banned or don't want to be the antagonist.
/datum/dynamic_ruleset/roundstart/trim_candidates()
for(var/mob/dead/new_player/P in candidates)
- if (!P.client || !P.mind) // Are they connected?
+ var/client/client = GET_CLIENT(P)
+ if (!client || !P.mind) // Are they connected?
candidates.Remove(P)
- continue
- if(!mode.check_age(P.client, minimum_required_age))
+ else if(!mode.check_age(client, minimum_required_age))
candidates.Remove(P)
continue
if(P.mind.special_role) // We really don't want to give antag to an antag.
candidates.Remove(P)
- continue
- if(antag_flag_override)
- if(!(antag_flag_override in P.client.prefs.be_special) || is_banned_from(P.ckey, list(antag_flag_override, ROLE_SYNDICATE)))
+ else if(antag_flag_override)
+ if(!(antag_flag_override in client.prefs.be_special) || is_banned_from(P.ckey, list(antag_flag_override, ROLE_SYNDICATE)))
candidates.Remove(P)
continue
else
- if(!(antag_flag in P.client.prefs.be_special) || is_banned_from(P.ckey, list(antag_flag, ROLE_SYNDICATE)))
+ if(!(antag_flag in client.prefs.be_special) || is_banned_from(P.ckey, list(antag_flag, ROLE_SYNDICATE)))
candidates.Remove(P)
continue
/// Do your checks if the ruleset is ready to be executed here.
/// Should ignore certain checks if forced is TRUE
-/datum/dynamic_ruleset/roundstart/ready(forced = FALSE)
+/datum/dynamic_ruleset/roundstart/ready(population, forced = FALSE)
return ..()
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
index ff2ede2c9ecc3..08d236e9dfac4 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
@@ -16,7 +16,7 @@
if(!(antag_flag_override in P.client.prefs.be_special) || is_banned_from(P.ckey, list(antag_flag_override, ROLE_SYNDICATE)))
candidates.Remove(P)
continue
- else
+ else
if(!(antag_flag in P.client.prefs.be_special) || is_banned_from(P.ckey, list(antag_flag, ROLE_SYNDICATE)))
candidates.Remove(P)
continue
@@ -59,15 +59,13 @@
name = "Syndicate Infiltrator"
antag_datum = /datum/antagonist/traitor
antag_flag = ROLE_TRAITOR
- protected_roles = list("Security Officer", "Warden", "Head of Personnel", "Detective", "Head of Security", "Captain")
+ protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel")
restricted_roles = list("AI","Cyborg")
required_candidates = 1
weight = 7
cost = 5
requirements = list(40,30,20,10,10,10,10,10,10,10)
- high_population_requirement = 10
repeatable = TRUE
- flags = TRAITOR_RULESET
//////////////////////////////////////////////
// //
@@ -77,6 +75,7 @@
/datum/dynamic_ruleset/latejoin/provocateur
name = "Provocateur"
+ persistent = TRUE
antag_datum = /datum/antagonist/rev/head
antag_flag = ROLE_REV_HEAD
antag_flag_override = ROLE_REV
@@ -85,34 +84,65 @@
required_enemies = list(2,2,1,1,1,1,1,0,0,0)
required_candidates = 1
weight = 2
+ delay = 1 MINUTES // Prevents rule start while head is offstation.
cost = 20
requirements = list(101,101,70,40,30,20,20,20,20,20)
- high_population_requirement = 50
- flags = HIGHLANDER_RULESET
- var/required_heads = 3
+ flags = HIGH_IMPACT_RULESET
+ blocking_rules = list(/datum/dynamic_ruleset/roundstart/revs)
+ var/required_heads_of_staff = 3
+ var/finished = FALSE
+ /// How much threat should be injected when the revolution wins?
+ var/revs_win_threat_injection = 20
+ var/datum/team/revolution/revolution
/datum/dynamic_ruleset/latejoin/provocateur/ready(forced=FALSE)
if (forced)
- required_heads = 1
+ required_heads_of_staff = 1
if(!..())
return FALSE
var/head_check = 0
for(var/mob/player in mode.current_players[CURRENT_LIVING_PLAYERS])
if (player.mind.assigned_role in GLOB.command_positions)
head_check++
- return (head_check >= required_heads)
+ return (head_check >= required_heads_of_staff)
/datum/dynamic_ruleset/latejoin/provocateur/execute()
- var/mob/M = pick(candidates)
- assigned += M.mind
- M.mind.special_role = antag_flag
- var/datum/antagonist/rev/head/new_head = new()
- new_head.give_flash = TRUE
- new_head.give_hud = TRUE
- new_head.remove_clumsy = TRUE
- new_head = M.mind.add_antag_datum(new_head)
- new_head.rev_team.max_headrevs = 1 // Only one revhead if it is latejoin.
- return TRUE
+ var/mob/M = pick(candidates) // This should contain a single player, but in case.
+ if(check_eligible(M.mind)) // Didnt die/run off z-level/get implanted since leaving shuttle.
+ assigned += M.mind
+ M.mind.special_role = antag_flag
+ revolution = new()
+ var/datum/antagonist/rev/head/new_head = new()
+ new_head.give_flash = TRUE
+ new_head.give_hud = TRUE
+ new_head.remove_clumsy = TRUE
+ new_head = M.mind.add_antag_datum(new_head, revolution)
+ revolution.update_objectives()
+ revolution.update_heads()
+ SSshuttle.registerHostileEnvironment(revolution)
+ return TRUE
+ else
+ log_game("DYNAMIC: [ruletype] [name] discarded [M.name] from head revolutionary due to ineligibility.")
+ log_game("DYNAMIC: [ruletype] [name] failed to get any eligible headrevs. Refunding [cost] threat.")
+ return FALSE
+
+/datum/dynamic_ruleset/latejoin/provocateur/rule_process()
+ var/winner = revolution.process_victory(revs_win_threat_injection)
+ if (isnull(winner))
+ return
+
+ finished = winner
+ return RULESET_STOP_PROCESSING
+
+/// Checks for revhead loss conditions and other antag datums.
+/datum/dynamic_ruleset/latejoin/provocateur/proc/check_eligible(var/datum/mind/M)
+ var/turf/T = get_turf(M.current)
+ if(!considered_afk(M) && considered_alive(M) && is_station_level(T.z) && !M.antag_datums?.len && !HAS_TRAIT(M, TRAIT_MINDSHIELD))
+ return TRUE
+ return FALSE
+
+/datum/dynamic_ruleset/latejoin/provocateur/round_result()
+ revolution.round_result(finished)
//////////////////////////////////////////////
// //
@@ -124,7 +154,7 @@
name = "Heretic Smuggler"
antag_datum = /datum/antagonist/heretic
antag_flag = ROLE_HERETIC
- protected_roles = list("Security Officer", "Warden", "Head of Personnel", "Detective", "Head of Security", "Captain","Prisoner")
+ protected_roles = list("Security Officer", "Warden", "Head of Personnel", "Detective", "Head of Security", "Captain")
restricted_roles = list("AI","Cyborg")
required_candidates = 1
weight = 4
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
index be421f3b88c4b..7f746c6e3bd91 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
@@ -8,7 +8,7 @@
ruletype = "Midround"
/// If the ruleset should be restricted from ghost roles.
var/restrict_ghost_roles = TRUE
- /// What mob type the ruleset is restricted to.
+ /// What mob type the ruleset is restricted to.
var/required_type = /mob/living/carbon/human
var/list/living_players = list()
var/list/living_antags = list()
@@ -41,17 +41,17 @@
continue
if(antag_flag_override)
if(!(antag_flag_override in M.client.prefs.be_special) || is_banned_from(M.ckey, list(antag_flag_override, ROLE_SYNDICATE)))
- candidates.Remove(M)
+ trimmed_list.Remove(M)
continue
else
if(!(antag_flag in M.client.prefs.be_special) || is_banned_from(M.ckey, list(antag_flag, ROLE_SYNDICATE)))
- candidates.Remove(M)
+ trimmed_list.Remove(M)
continue
if (M.mind)
if (restrict_ghost_roles && (M.mind.assigned_role in GLOB.exp_specialmap[EXP_TYPE_SPECIAL])) // Are they playing a ghost role?
trimmed_list.Remove(M)
continue
- if (M.mind.assigned_role in restricted_roles || HAS_TRAIT(M, TRAIT_MINDSHIELD)) // Does their job allow it or are they mindshielded?
+ if (M.mind.assigned_role in restricted_roles) // Does their job allow it?
trimmed_list.Remove(M)
continue
if ((exclusive_roles.len > 0) && !(M.mind.assigned_role in exclusive_roles)) // Is the rule exclusive to their job?
@@ -68,9 +68,9 @@
if (!forced)
var/job_check = 0
if (enemy_roles.len > 0)
- for (var/mob/M in living_players)
- if (M.stat == DEAD)
- continue // Dead players cannot count as opponents
+ for (var/mob/M in mode.current_players[CURRENT_LIVING_PLAYERS])
+ if (M.stat == DEAD || !M.client)
+ continue // Dead/disconnected players cannot count as opponents
if (M.mind && M.mind.assigned_role && (M.mind.assigned_role in enemy_roles) && (!(M in candidates) || (M.mind.assigned_role in restricted_roles)))
job_check++ // Checking for "enemies" (such as sec officers). To be counters, they must either not be candidates to that rule, or have a job that restricts them from it
@@ -97,13 +97,11 @@
message_admins("Polling [possible_volunteers.len] players to apply for the [name] ruleset.")
log_game("DYNAMIC: Polling [possible_volunteers.len] players to apply for the [name] ruleset.")
- candidates = pollGhostCandidates("The mode is looking for volunteers to become [antag_flag] for [name]", antag_flag, SSticker.mode, antag_flag, poll_time = 300)
-
+ candidates = pollGhostCandidates("The mode is looking for volunteers to become [antag_flag] for [name]", antag_flag, SSticker.mode, antag_flag_override ? antag_flag_override : antag_flag, poll_time = 300)
+
if(!candidates || candidates.len <= 0)
message_admins("The ruleset [name] received no applications.")
log_game("DYNAMIC: The ruleset [name] received no applications.")
- mode.refund_threat(cost)
- mode.threat_log += "[worldtime2text()]: Rule [name] refunded [cost] (no applications)"
mode.executed_rules -= src
return
@@ -118,15 +116,13 @@
if(candidates.len <= 0)
if(i == 1)
// We have found no candidates so far and we are out of applicants.
- mode.refund_threat(cost)
- mode.threat_log += "[worldtime2text()]: Rule [name] refunded [cost] (all applications invalid)"
mode.executed_rules -= src
break
var/mob/applicant = pick(candidates)
candidates -= applicant
if(!isobserver(applicant))
if(applicant.stat == DEAD) // Not an observer? If they're dead, make them one.
- applicant = applicant.ghostize(FALSE)
+ applicant = applicant.ghostize(FALSE,SENTIENCE_ERASE)
else // Not dead? Disregard them, pick a new applicant
i--
continue
@@ -168,30 +164,36 @@
name = "Syndicate Sleeper Agent"
antag_datum = /datum/antagonist/traitor
antag_flag = ROLE_TRAITOR
- protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel")
+ protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain")
restricted_roles = list("Cyborg", "AI", "Positronic Brain")
required_candidates = 1
weight = 7
cost = 10
requirements = list(50,40,30,20,10,10,10,10,10,10)
repeatable = TRUE
- high_population_requirement = 10
- flags = TRAITOR_RULESET
/datum/dynamic_ruleset/midround/autotraitor/acceptable(population = 0, threat = 0)
var/player_count = mode.current_players[CURRENT_LIVING_PLAYERS].len
var/antag_count = mode.current_players[CURRENT_LIVING_ANTAGS].len
var/max_traitors = round(player_count / 10) + 1
- if ((antag_count < max_traitors) && prob(mode.threat_level))//adding traitors if the antag population is getting low
- return ..()
- else
+
+ // adding traitors if the antag population is getting low
+ var/too_little_antags = antag_count < max_traitors
+ if (!too_little_antags)
+ log_game("DYNAMIC: Too many living antags compared to living players ([antag_count] living antags, [player_count] living players, [max_traitors] max traitors)")
+ return FALSE
+
+ if (!prob(mode.threat_level))
+ log_game("DYNAMIC: Random chance to roll autotraitor failed, it was a [mode.threat_level]% chance.")
return FALSE
+ return ..()
+
/datum/dynamic_ruleset/midround/autotraitor/trim_candidates()
..()
for(var/mob/living/player in living_players)
if(issilicon(player)) // Your assigned role doesn't change when you are turned into a silicon.
- living_players -= player
+ living_players -= player
continue
if(is_centcom_level(player.z))
living_players -= player // We don't autotator people in CentCom
@@ -223,21 +225,20 @@
name = "Malfunctioning AI"
antag_datum = /datum/antagonist/traitor
antag_flag = ROLE_MALF
- enemy_roles = list("Security Officer", "Warden","Detective","Head of Security", "Captain", "Scientist", "Chemist", "Research Director", "Chief Engineer")
+ enemy_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Scientist", "Chemist", "Research Director", "Chief Engineer")
exclusive_roles = list("AI")
required_enemies = list(4,4,4,4,4,4,2,2,2,0)
required_candidates = 1
weight = 3
- cost = 35
+ cost = 20
requirements = list(101,101,80,70,60,60,50,50,40,40)
- high_population_requirement = 35
required_type = /mob/living/silicon/ai
var/ion_announce = 33
var/removeDontImproveChance = 10
/datum/dynamic_ruleset/midround/malf/trim_candidates()
..()
- candidates = candidates[CURRENT_LIVING_PLAYERS]
+ candidates = living_players
for(var/mob/living/player in candidates)
if(!isAI(player))
candidates -= player
@@ -257,7 +258,7 @@
M.mind.special_role = antag_flag
M.mind.add_antag_datum(AI)
if(prob(ion_announce))
- priority_announce("Ion storm detected near the station. Please check all AI-controlled equipment for errors.", "Anomaly Alert", 'sound/ai/ionstorm.ogg')
+ priority_announce("Ion storm detected near the station. Please check all AI-controlled equipment for errors.", "Anomaly Alert", ANNOUNCER_IONSTORM)
if(prob(removeDontImproveChance))
M.replace_random_law(generate_ion_law(), list(LAW_INHERENT, LAW_SUPPLIED, LAW_ION))
else
@@ -274,13 +275,12 @@
name = "Wizard"
antag_datum = /datum/antagonist/wizard
antag_flag = ROLE_WIZARD
- enemy_roles = list("Security Officer","Detective","Head of Security", "Captain")
+ enemy_roles = list("Security Officer", "Detective", "Warden", "Head of Security", "Captain", "Research Director") //RD doesn't believe in magic
required_enemies = list(2,2,1,1,1,1,1,0,0,0)
required_candidates = 1
weight = 1
cost = 20
requirements = list(90,90,70,40,30,20,10,10,10,10)
- high_population_requirement = 50
repeatable = TRUE
/datum/dynamic_ruleset/midround/from_ghosts/wizard/ready(forced = FALSE)
@@ -306,21 +306,20 @@
name = "Nuclear Assault"
antag_flag = ROLE_OPERATIVE
antag_datum = /datum/antagonist/nukeop
- enemy_roles = list("AI", "Cyborg", "Security Officer", "Warden","Detective","Head of Security", "Captain")
+ enemy_roles = list("AI", "Cyborg", "Security Officer", "Warden", "Detective", "Head of Security", "Captain")
required_enemies = list(3,3,3,3,3,2,1,1,0,0)
required_candidates = 5
weight = 5
- cost = 35
+ cost = 20
requirements = list(90,90,90,80,60,40,30,20,10,10)
- high_population_requirement = 10
- var/operative_cap = list(2,2,3,3,4,5,5,5,5,5)
+ var/list/operative_cap = list(2,2,3,3,4,5,5,5,5,5)
var/datum/team/nuclear/nuke_team
- flags = HIGHLANDER_RULESET
+ flags = HIGH_IMPACT_RULESET
/datum/dynamic_ruleset/midround/from_ghosts/nuclear/acceptable(population=0, threat=0)
if (locate(/datum/dynamic_ruleset/roundstart/nuclear) in mode.executed_rules)
return FALSE // Unavailable if nuke ops were already sent at roundstart
- var/indice_pop = min(10,round(living_players.len/5)+1)
+ indice_pop = min(operative_cap.len, round(living_players.len/5)+1)
required_candidates = operative_cap[indice_pop]
return ..()
@@ -349,13 +348,12 @@
name = "Blob"
antag_datum = /datum/antagonist/blob
antag_flag = ROLE_BLOB
- enemy_roles = list("Security Officer", "Detective", "Head of Security", "Captain")
+ enemy_roles = list("Security Officer", "Detective", "Warden", "Head of Security", "Captain")
required_enemies = list(2,2,1,1,1,1,1,0,0,0)
required_candidates = 1
weight = 4
cost = 10
requirements = list(101,101,101,80,60,50,30,20,10,10)
- high_population_requirement = 50
repeatable = TRUE
/datum/dynamic_ruleset/midround/from_ghosts/blob/generate_ruleset_body(mob/applicant)
@@ -372,13 +370,12 @@
name = "Alien Infestation"
antag_datum = /datum/antagonist/xeno
antag_flag = ROLE_ALIEN
- enemy_roles = list("Security Officer", "Detective", "Head of Security", "Captain")
+ enemy_roles = list("Security Officer", "Detective", "Warden", "Head of Security", "Captain")
required_enemies = list(2,2,1,1,1,1,1,0,0,0)
required_candidates = 1
weight = 3
cost = 10
requirements = list(101,101,101,70,50,40,20,15,10,10)
- high_population_requirement = 50
repeatable = TRUE
var/list/vents = list()
@@ -419,13 +416,12 @@
antag_datum = /datum/antagonist/nightmare
antag_flag = "Nightmare"
antag_flag_override = ROLE_ALIEN
- enemy_roles = list("Security Officer", "Detective", "Head of Security", "Captain")
+ enemy_roles = list("Security Officer", "Detective", "Warden", "Head of Security", "Captain")
required_enemies = list(2,2,1,1,1,1,1,0,0,0)
required_candidates = 1
weight = 3
cost = 10
requirements = list(101,101,101,70,50,40,20,15,10,10)
- high_population_requirement = 50
repeatable = TRUE
var/list/spawn_locs = list()
@@ -454,3 +450,41 @@
message_admins("[ADMIN_LOOKUPFLW(S)] has been made into a Nightmare by the midround ruleset.")
log_game("DYNAMIC: [key_name(S)] was spawned as a Nightmare by the midround ruleset.")
return S
+
+//////////////////////////////////////////////
+// //
+// ABDUCTORS (GHOST) //
+// //
+//////////////////////////////////////////////
+#define ABDUCTOR_MAX_TEAMS 4
+
+/datum/dynamic_ruleset/midround/from_ghosts/abductors
+ name = "Abductors"
+ antag_flag = "Abductor"
+ antag_flag_override = ROLE_ABDUCTOR
+ enemy_roles = list("Security Officer", "Detective", "Warden", "Head of Security", "Captain")
+ required_enemies = list(2,2,1,1,1,1,1,0,0,0)
+ required_candidates = 2
+ weight = 4
+ cost = 10
+ requirements = list(101,101,101,80,60,50,30,20,10,10)
+ repeatable = TRUE
+ var/datum/team/abductor_team/new_team
+
+/datum/dynamic_ruleset/midround/from_ghosts/abductors/ready(forced = FALSE)
+ if (required_candidates > (dead_players.len + list_observers.len))
+ return FALSE
+ return ..()
+
+/datum/dynamic_ruleset/midround/from_ghosts/abductors/finish_setup(mob/new_character, index)
+ if (index == 1) // Our first guy is the scientist. We also initialize the team here as well since this should only happen once per pair of abductors.
+ new_team = new
+ if(new_team.team_number > ABDUCTOR_MAX_TEAMS)
+ return MAP_ERROR
+ var/datum/antagonist/abductor/scientist/new_role = new
+ new_character.mind.add_antag_datum(new_role, new_team)
+ else // Our second guy is the agent, team is already created, don't need to make another one.
+ var/datum/antagonist/abductor/agent/new_role = new
+ new_character.mind.add_antag_datum(new_role, new_team)
+
+#undef ABDUCTOR_MAX_TEAMS
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
index a2f02cad28f45..5c40f2f31b822 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
@@ -9,20 +9,23 @@
name = "Traitors"
persistent = TRUE
antag_flag = ROLE_TRAITOR
- antag_datum = /datum/antagonist/traitor/
+ antag_datum = /datum/antagonist/traitor
minimum_required_age = 0
protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain")
restricted_roles = list("Cyborg")
required_candidates = 1
weight = 5
- cost = 10
+ cost = 8 // Avoid raising traitor threat above 10, as it is the default low cost ruleset.
+ scaling_cost = 9
requirements = list(10,10,10,10,10,10,10,10,10,10)
- high_population_requirement = 10
- var/autotraitor_cooldown = 450 // 15 minutes (ticks once per 2 sec)
+ antag_cap = list("denominator" = 24)
+ var/autotraitor_cooldown = (15 MINUTES)
+ COOLDOWN_DECLARE(autotraitor_cooldown_check)
-/datum/dynamic_ruleset/roundstart/traitor/pre_execute()
- var/traitor_scaling_coeff = 10 - max(0,round(mode.threat_level/10)-5) // Above 50 threat level, coeff goes down by 1 for every 10 levels
- var/num_traitors = min(round(mode.candidates.len / traitor_scaling_coeff) + 1, candidates.len)
+/datum/dynamic_ruleset/roundstart/traitor/pre_execute(population)
+ . = ..()
+ COOLDOWN_START(src, autotraitor_cooldown_check, autotraitor_cooldown)
+ var/num_traitors = get_antag_cap(population) * (scaled_times + 1)
for (var/i = 1 to num_traitors)
var/mob/M = pick_n_take(candidates)
assigned += M.mind
@@ -31,11 +34,8 @@
return TRUE
/datum/dynamic_ruleset/roundstart/traitor/rule_process()
- if (autotraitor_cooldown > 0)
- autotraitor_cooldown--
- else
- autotraitor_cooldown = 450 // 15 minutes
- message_admins("Checking if we can turn someone into a traitor.")
+ if (COOLDOWN_FINISHED(src, autotraitor_cooldown_check))
+ COOLDOWN_START(src, autotraitor_cooldown_check, autotraitor_cooldown)
log_game("DYNAMIC: Checking if we can turn someone into a traitor.")
mode.picking_specific_rule(/datum/dynamic_ruleset/midround/autotraitor)
@@ -53,19 +53,16 @@
restricted_roles = list("Cyborg", "AI")
required_candidates = 2
weight = 4
- cost = 10
+ cost = 15
+ scaling_cost = 15
requirements = list(40,30,30,20,20,15,15,15,10,10)
- high_population_requirement = 15
+ antag_cap = 2 // Can pick 3 per team, but rare enough it doesn't matter.
var/list/datum/team/brother_team/pre_brother_teams = list()
- var/const/team_amount = 2 // Hard limit on brother teams if scaling is turned off
var/const/min_team_size = 2
-/datum/dynamic_ruleset/roundstart/traitorbro/pre_execute()
- var/num_teams = team_amount
- var/bsc = CONFIG_GET(number/brother_scaling_coeff)
- if(bsc)
- num_teams = max(1, round(mode.roundstart_pop_ready / bsc))
-
+/datum/dynamic_ruleset/roundstart/traitorbro/pre_execute(population)
+ . = ..()
+ var/num_teams = (get_antag_cap(population)/min_team_size) * (scaled_times + 1) // 1 team per scaling
for(var/j = 1 to num_teams)
if(candidates.len < min_team_size || candidates.len < required_candidates)
break
@@ -104,13 +101,14 @@
restricted_roles = list("AI", "Cyborg")
required_candidates = 1
weight = 3
- cost = 30
- requirements = list(80,70,60,50,40,20,20,10,10,10)
- high_population_requirement = 10
- var/team_mode_probability = 30
+ cost = 16
+ scaling_cost = 10
+ requirements = list(70,70,60,50,40,20,20,10,10,10)
+ antag_cap = list("denominator" = 29)
-/datum/dynamic_ruleset/roundstart/changeling/pre_execute()
- var/num_changelings = min(round(mode.candidates.len / 10) + 1, candidates.len)
+/datum/dynamic_ruleset/roundstart/changeling/pre_execute(population)
+ . = ..()
+ var/num_changelings = get_antag_cap(population) * (scaled_times + 1)
for (var/i = 1 to num_changelings)
var/mob/M = pick_n_take(candidates)
assigned += M.mind
@@ -134,16 +132,19 @@
name = "Heretics"
antag_flag = ROLE_HERETIC
antag_datum = /datum/antagonist/heretic
- protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Brig Physician")
+ protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain")
restricted_roles = list("AI", "Cyborg")
required_candidates = 1
weight = 3
- cost = 20
+ cost = 15
+ scaling_cost = 9
requirements = list(50,45,45,40,35,20,20,15,10,10)
+ antag_cap = list("denominator" = 24)
-/datum/dynamic_ruleset/roundstart/heretics/pre_execute()
+
+/datum/dynamic_ruleset/roundstart/heretics/pre_execute(population)
. = ..()
- var/num_ecult = min(round(mode.candidates.len / 10) + 1, candidates.len)
+ var/num_ecult = get_antag_cap(population) * (scaled_times + 1)
for (var/i = 1 to num_ecult)
var/mob/picked_candidate = pick_n_take(candidates)
@@ -173,13 +174,13 @@
name = "Wizard"
antag_flag = ROLE_WIZARD
antag_datum = /datum/antagonist/wizard
+ flags = LONE_RULESET
minimum_required_age = 14
restricted_roles = list("Head of Security", "Captain") // Just to be sure that a wizard getting picked won't ever imply a Captain or HoS not getting drafted
required_candidates = 1
weight = 2
- cost = 30
+ cost = 20
requirements = list(90,90,70,40,30,20,10,10,10,10)
- high_population_requirement = 10
var/list/roundstart_wizards = list()
/datum/dynamic_ruleset/roundstart/wizard/acceptable(population=0, threat=0)
@@ -192,7 +193,7 @@
/datum/dynamic_ruleset/roundstart/wizard/pre_execute()
if(GLOB.wizardstart.len == 0)
return FALSE
-
+ mode.antags_rolled += 1
var/mob/M = pick_n_take(candidates)
if (M)
assigned += M.mind
@@ -221,21 +222,19 @@
restricted_roles = list("AI", "Cyborg", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Chaplain", "Head of Personnel")
required_candidates = 2
weight = 3
- cost = 30
+ cost = 20
requirements = list(100,90,80,60,40,30,10,10,10,10)
- high_population_requirement = 10
- flags = HIGHLANDER_RULESET
- var/cultist_cap = list(2,2,2,3,3,4,4,4,4,4)
+ flags = HIGH_IMPACT_RULESET
+ antag_cap = list("denominator" = 20, "offset" = 1)
var/datum/team/cult/main_cult
-/datum/dynamic_ruleset/roundstart/bloodcult/ready(forced = FALSE)
- var/indice_pop = min(10,round(mode.roundstart_pop_ready/pop_per_requirement)+1)
- required_candidates = cultist_cap[indice_pop]
+/datum/dynamic_ruleset/roundstart/bloodcult/ready(population, forced = FALSE)
+ required_candidates = get_antag_cap(population)
. = ..()
-/datum/dynamic_ruleset/roundstart/bloodcult/pre_execute()
- var/indice_pop = min(10,round(mode.roundstart_pop_ready/pop_per_requirement)+1)
- var/cultists = cultist_cap[indice_pop]
+/datum/dynamic_ruleset/roundstart/bloodcult/pre_execute(population)
+ . = ..()
+ var/cultists = get_antag_cap(population)
for(var/cultists_number = 1 to cultists)
if(candidates.len <= 0)
break
@@ -279,23 +278,20 @@
restricted_roles = list("Head of Security", "Captain") // Just to be sure that a nukie getting picked won't ever imply a Captain or HoS not getting drafted
required_candidates = 5
weight = 3
- cost = 40
+ cost = 20
requirements = list(90,90,90,80,60,40,30,20,10,10)
- high_population_requirement = 10
- flags = HIGHLANDER_RULESET
- var/operative_cap = list(2,2,2,3,3,3,4,4,5,5)
+ flags = HIGH_IMPACT_RULESET
+ antag_cap = list("denominator" = 18, "offset" = 1)
var/datum/team/nuclear/nuke_team
-/datum/dynamic_ruleset/roundstart/nuclear/ready(forced = FALSE)
- var/indice_pop = min(10,round(mode.roundstart_pop_ready/pop_per_requirement)+1)
- required_candidates = operative_cap[indice_pop]
+/datum/dynamic_ruleset/roundstart/nuclear/ready(population, forced = FALSE)
+ required_candidates = get_antag_cap(population)
. = ..()
-/datum/dynamic_ruleset/roundstart/nuclear/pre_execute()
+/datum/dynamic_ruleset/roundstart/nuclear/pre_execute(population)
+ . = ..()
// If ready() did its job, candidates should have 5 or more members in it
-
- var/indice_pop = min(10,round(mode.roundstart_pop_ready/5)+1)
- var/operatives = operative_cap[indice_pop]
+ var/operatives = get_antag_cap(population)
for(var/operatives_number = 1 to operatives)
if(candidates.len <= 0)
break
@@ -366,20 +362,24 @@
minimum_required_age = 14
restricted_roles = list("AI", "Cyborg", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director")
required_candidates = 3
- weight = 2
+ weight = 3
delay = 7 MINUTES
- cost = 35
+ cost = 20
requirements = list(101,101,70,40,30,20,10,10,10,10)
- high_population_requirement = 10
- flags = HIGHLANDER_RULESET
+ antag_cap = 3
+ flags = HIGH_IMPACT_RULESET
+ blocking_rules = list(/datum/dynamic_ruleset/latejoin/provocateur)
// I give up, just there should be enough heads with 35 players...
minimum_players = 35
+ /// How much threat should be injected when the revolution wins?
+ var/revs_win_threat_injection = 20
var/datum/team/revolution/revolution
- var/finished = 0
+ var/finished = FALSE
-/datum/dynamic_ruleset/roundstart/revs/pre_execute()
- var/max_canditates = 3
- for(var/i = 1 to max_canditates)
+/datum/dynamic_ruleset/roundstart/revs/pre_execute(population)
+ . = ..()
+ var/max_candidates = get_antag_cap(population)
+ for(var/i = 1 to max_candidates)
if(candidates.len <= 0)
break
var/mob/M = pick_n_take(candidates)
@@ -391,56 +391,43 @@
/datum/dynamic_ruleset/roundstart/revs/execute()
revolution = new()
for(var/datum/mind/M in assigned)
- var/datum/antagonist/rev/head/new_head = new antag_datum()
- new_head.give_flash = TRUE
- new_head.give_hud = TRUE
- new_head.remove_clumsy = TRUE
- M.add_antag_datum(new_head,revolution)
- revolution.update_objectives()
- revolution.update_heads()
- SSshuttle.registerHostileEnvironment(src)
- return TRUE
+ if(check_eligible(M))
+ var/datum/antagonist/rev/head/new_head = new antag_datum()
+ new_head.give_flash = TRUE
+ new_head.give_hud = TRUE
+ new_head.remove_clumsy = TRUE
+ M.add_antag_datum(new_head,revolution)
+ else
+ assigned -= M
+ log_game("DYNAMIC: [ruletype] [name] discarded [M.name] from head revolutionary due to ineligibility.")
+ if(revolution.members.len)
+ revolution.update_objectives()
+ revolution.update_heads()
+ SSshuttle.registerHostileEnvironment(revolution)
+ return TRUE
+ log_game("DYNAMIC: [ruletype] [name] failed to get any eligible headrevs. Refunding [cost] threat.")
+ return FALSE
+
+/datum/dynamic_ruleset/roundstart/revs/clean_up()
+ qdel(revolution)
+ ..()
/datum/dynamic_ruleset/roundstart/revs/rule_process()
- if(!revolution)
- log_game("DYNAMIC: Something went horrifically wrong with [name] - and the antag datum could not be created. Notify coders.")
+ var/winner = revolution.process_victory(revs_win_threat_injection)
+ if (isnull(winner))
return
- if(check_rev_victory())
- finished = 1
- else if(check_heads_victory())
- finished = 2
-
-/datum/dynamic_ruleset/roundstart/revs/check_finished()
- if(CONFIG_GET(keyed_list/continuous)["revolution"])
- if(finished)
- SSshuttle.clearHostileEnvironment(src)
- return ..()
- if(finished != 0)
- return TRUE
- else
- return ..()
-
-/datum/dynamic_ruleset/roundstart/revs/proc/check_rev_victory()
- for(var/datum/objective/mutiny/objective in revolution.objectives)
- if(!(objective.check_completion()))
- return FALSE
- return TRUE
+ finished = winner
+ return RULESET_STOP_PROCESSING
-/datum/dynamic_ruleset/roundstart/revs/proc/check_heads_victory()
- for(var/datum/mind/rev_mind in revolution.head_revolutionaries())
- var/turf/T = get_turf(rev_mind.current)
- if(!considered_afk(rev_mind) && considered_alive(rev_mind) && is_station_level(T.z))
- if(ishuman(rev_mind.current) || ismonkey(rev_mind.current))
- return FALSE
- return TRUE
+/// Checks for revhead loss conditions and other antag datums.
+/datum/dynamic_ruleset/roundstart/revs/proc/check_eligible(var/datum/mind/M)
+ var/turf/T = get_turf(M.current)
+ if(!considered_afk(M) && considered_alive(M) && is_station_level(T.z) && !M.antag_datums?.len && !HAS_TRAIT(M, TRAIT_MINDSHIELD))
+ return TRUE
+ return FALSE
/datum/dynamic_ruleset/roundstart/revs/round_result()
- if(finished == 1)
- SSticker.mode_result = "win - heads killed"
- SSticker.news_report = REVS_WIN
- else if(finished == 2)
- SSticker.mode_result = "loss - rev heads killed"
- SSticker.news_report = REVS_LOSE
+ revolution.round_result(finished)
// Admin only rulesets. The threat requirement is 101 so it is not possible to roll them.
@@ -459,12 +446,14 @@
weight = 3
cost = 0
requirements = list(101,101,101,101,101,101,101,101,101,101)
- high_population_requirement = 101
+ flags = LONE_RULESET
/datum/dynamic_ruleset/roundstart/extended/pre_execute()
message_admins("Starting a round of extended.")
log_game("Starting a round of extended.")
- mode.spend_threat(mode.threat)
+ mode.spend_roundstart_budget(mode.round_start_budget)
+ mode.spend_midround_budget(mode.mid_round_budget)
+ mode.threat_log += "[worldtime2text()]: Extended ruleset set threat to 0."
return TRUE
//////////////////////////////////////////////
@@ -478,7 +467,6 @@
antag_datum = /datum/antagonist/nukeop/clownop
antag_leader_datum = /datum/antagonist/nukeop/leader/clownop
requirements = list(101,101,101,101,101,101,101,101,101,101)
- high_population_requirement = 101
/datum/dynamic_ruleset/roundstart/nuclear/clown_ops/pre_execute()
. = ..()
@@ -502,22 +490,17 @@
name = "Devil"
antag_flag = ROLE_DEVIL
antag_datum = /datum/antagonist/devil
- restricted_roles = list("Lawyer", "Curator", "Chaplain", "Head of Security", "Captain", "AI", "Cyborg", "Security Officer", "Warden", "Detective", "Brig Physician")
+ restricted_roles = list("Lawyer", "Curator", "Chaplain", "Head of Security", "Captain", "AI", "Cyborg", "Security Officer", "Warden", "Detective")
required_candidates = 1
weight = 3
cost = 0
+ flags = LONE_RULESET
requirements = list(101,101,101,101,101,101,101,101,101,101)
- high_population_requirement = 101
- var/devil_limit = 4 // Hard limit on devils if scaling is turned off
-
-/datum/dynamic_ruleset/roundstart/devil/pre_execute()
- var/tsc = CONFIG_GET(number/traitor_scaling_coeff)
- var/num_devils = 1
+ antag_cap = list("denominator" = 30)
- if(tsc)
- num_devils = max(required_candidates, min(round(mode.roundstart_pop_ready / (tsc * 3)) + 2, round(mode.roundstart_pop_ready / (tsc * 1.5))))
- else
- num_devils = max(required_candidates, min(mode.roundstart_pop_ready, devil_limit))
+/datum/dynamic_ruleset/roundstart/devil/pre_execute(population)
+ . = ..()
+ var/num_devils = get_antag_cap(population) * (scaled_times + 1)
for(var/j = 0, j < num_devils, j++)
if (!candidates.len)
@@ -565,14 +548,15 @@
weight = 3
cost = 0
requirements = list(101,101,101,101,101,101,101,101,101,101)
- high_population_requirement = 101
+ flags = LONE_RULESET
var/players_per_carrier = 30
var/monkeys_to_win = 1
var/escaped_monkeys = 0
var/datum/team/monkey/monkey_team
-/datum/dynamic_ruleset/roundstart/monkey/pre_execute()
- var/carriers_to_make = max(round(mode.roundstart_pop_ready / players_per_carrier, 1), 1)
+/datum/dynamic_ruleset/roundstart/monkey/pre_execute(population)
+ . = ..()
+ var/carriers_to_make = get_antag_cap(population) * (scaled_times + 1)
for(var/j = 0, j < carriers_to_make, j++)
if (!candidates.len)
@@ -624,7 +608,7 @@
weight = 3
cost = 0
requirements = list(101,101,101,101,101,101,101,101,101,101)
- high_population_requirement = 101
+ flags = LONE_RULESET
var/meteordelay = 2000
var/nometeors = 0
var/rampupdelta = 5
@@ -645,3 +629,65 @@
var/ramp_up_final = CLAMP(round(meteorminutes/rampupdelta), 1, 10)
spawn_meteors(ramp_up_final, wavetype)
+
+//////////////////////////////////////////////
+// //
+// CLOCKCULT //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/roundstart/clockcult
+ name = "Clockwork Cult"
+ antag_flag = ROLE_SERVANT_OF_RATVAR
+ antag_datum = /datum/antagonist/servant_of_ratvar
+ restricted_roles = list("AI", "Cyborg", "Security Officer", "Warden", "Detective","Head of Security", "Captain", "Chaplain", "Head of Personnel")
+ required_candidates = 4
+ weight = 3
+ cost = 35
+ requirements = list(100,90,80,70,60,50,30,30,30,30)
+ flags = HIGH_IMPACT_RULESET
+ var/datum/team/clock_cult/main_cult
+ var/list/selected_servants = list()
+
+/datum/dynamic_ruleset/roundstart/clockcult/pre_execute()
+ //Load Reebe
+ LoadReebe()
+ //Make cultists
+ var/starter_servants = 4
+ var/number_players = mode.roundstart_pop_ready
+ if(number_players > 30)
+ number_players -= 30
+ starter_servants += round(number_players / 10)
+ starter_servants = min(starter_servants, 8)
+ for (var/i in 1 to starter_servants)
+ var/mob/servant = pick_n_take(candidates)
+ assigned += servant.mind
+ servant.mind.assigned_role = ROLE_SERVANT_OF_RATVAR
+ servant.mind.special_role = ROLE_SERVANT_OF_RATVAR
+ //Generate scriptures
+ generate_clockcult_scriptures()
+ return TRUE
+
+/datum/dynamic_ruleset/roundstart/clockcult/execute()
+ var/list/spawns = GLOB.servant_spawns.Copy()
+ main_cult = new
+ main_cult.setup_objectives()
+ //Create team
+ for(var/datum/mind/servant_mind in assigned)
+ servant_mind.current.forceMove(pick_n_take(spawns))
+ servant_mind.current.set_species(/datum/species/human)
+ var/datum/antagonist/servant_of_ratvar/S = add_servant_of_ratvar(servant_mind.current, team=main_cult)
+ S.equip_carbon(servant_mind.current)
+ S.equip_servant()
+ S.prefix = CLOCKCULT_PREFIX_MASTER
+ //Setup the conversion limits for auto opening the ark
+ calculate_clockcult_values()
+ return ..()
+
+/datum/dynamic_ruleset/roundstart/clockcult/round_result()
+ if(GLOB.ratvar_risen)
+ SSticker.news_report = CLOCK_SUMMON
+ SSticker.mode_result = "win - servants completed their objective (summon ratvar)"
+ else
+ SSticker.news_report = CULT_FAILURE
+ SSticker.mode_result = "loss - servants failed their objective (summon ratvar)"
diff --git a/code/game/gamemodes/dynamic/dynamic_simulations.dm b/code/game/gamemodes/dynamic/dynamic_simulations.dm
new file mode 100644
index 0000000000000..3e312b65700ab
--- /dev/null
+++ b/code/game/gamemodes/dynamic/dynamic_simulations.dm
@@ -0,0 +1,137 @@
+#ifdef TESTING
+/datum/dynamic_simulation
+ var/datum/game_mode/dynamic/gamemode
+ var/datum/dynamic_simulation_config/config
+ var/list/mock_candidates = list()
+
+/datum/dynamic_simulation/proc/initialize_gamemode(forced_threat)
+ gamemode = new
+
+ if (forced_threat)
+ gamemode.create_threat(forced_threat)
+ else
+ gamemode.generate_threat()
+
+ gamemode.generate_budgets()
+ gamemode.set_cooldowns()
+
+/datum/dynamic_simulation/proc/create_candidates(players)
+ GLOB.new_player_list.Cut()
+
+ for (var/_ in 1 to players)
+ var/mob/dead/new_player/mock_new_player = new
+ mock_new_player.ready = PLAYER_READY_TO_PLAY
+
+ var/datum/mind/mock_mind = new
+ mock_new_player.mind = mock_mind
+
+ var/datum/client_interface/mock_client = new
+
+ var/datum/preferences/prefs = new
+ var/list/be_special = list()
+ for (var/special_role in GLOB.special_roles)
+ be_special += special_role
+
+ prefs.be_special = be_special
+ mock_client.prefs = prefs
+
+ mock_new_player.mock_client = mock_client
+
+ mock_candidates += mock_new_player
+
+/datum/dynamic_simulation/proc/simulate(datum/dynamic_simulation_config/config)
+ src.config = config
+
+ initialize_gamemode(config.forced_threat_level)
+ create_candidates(config.roundstart_players)
+ gamemode.pre_setup()
+
+ var/total_antags = 0
+ for (var/_ruleset in gamemode.executed_rules)
+ var/datum/dynamic_ruleset/ruleset = _ruleset
+ total_antags += ruleset.assigned.len
+
+ return list(
+ "roundstart_players" = config.roundstart_players,
+ "threat_level" = gamemode.threat_level,
+ "snapshot" = list(
+ "antag_percent" = total_antags / config.roundstart_players,
+ "remaining_threat" = gamemode.mid_round_budget,
+ "rulesets" = gamemode.executed_rules.Copy(),
+ ),
+ )
+
+/datum/dynamic_simulation_config
+ /// How many players round start should there be?
+ var/roundstart_players
+
+ /// Optional, force this threat level instead of picking randomly through the lorentz distribution
+ var/forced_threat_level
+
+/client/proc/run_dynamic_simulations()
+ set name = "Run Dynamic Simulations"
+ set category = "Debug"
+
+ var/simulations = input(usr, "Enter number of simulations") as num
+ var/roundstart_players = input(usr, "Enter number of round start players") as num
+ var/forced_threat_level = input(usr, "Enter forced threat level, if you want one") as num | null
+
+ SSticker.mode = config.pick_mode("dynamic")
+ message_admins("Running dynamic simulations...")
+
+ var/list/outputs = list()
+
+ var/datum/dynamic_simulation_config/dynamic_config = new
+
+ if (roundstart_players)
+ dynamic_config.roundstart_players = roundstart_players
+
+ if (forced_threat_level)
+ dynamic_config.forced_threat_level = forced_threat_level
+
+ for (var/count in 1 to simulations)
+ var/datum/dynamic_simulation/simulator = new
+ var/output = simulator.simulate(dynamic_config)
+ outputs += list(output)
+
+ if (CHECK_TICK)
+ log_world("[count]/[simulations]")
+
+ message_admins("Writing file...")
+ WRITE_FILE(file("[GLOB.log_directory]/dynamic_simulations.json"), json_encode(outputs))
+ message_admins("Writing complete.")
+
+/proc/export_dynamic_json_of(ruleset_list)
+ var/list/export = list()
+
+ for (var/_ruleset in ruleset_list)
+ var/datum/dynamic_ruleset/ruleset = _ruleset
+ export[ruleset.name] = list(
+ "repeatable_weight_decrease" = ruleset.repeatable_weight_decrease,
+ "weight" = ruleset.weight,
+ "cost" = ruleset.cost,
+ "scaling_cost" = ruleset.scaling_cost,
+ "antag_cap" = ruleset.antag_cap,
+ "pop_per_requirement" = ruleset.pop_per_requirement,
+ "requirements" = ruleset.requirements,
+ "base_prob" = ruleset.base_prob,
+ )
+
+ return export
+
+/client/proc/export_dynamic_json()
+ set name = "Export dynamic.json"
+ set category = "Debug"
+
+ var/datum/game_mode/dynamic/dynamic = SSticker.mode
+
+ var/list/export = list()
+ export["Roundstart"] = export_dynamic_json_of(dynamic.roundstart_rules)
+ export["Midround"] = export_dynamic_json_of(dynamic.midround_rules)
+ export["Latejoin"] = export_dynamic_json_of(dynamic.latejoin_rules)
+
+ message_admins("Writing file...")
+ WRITE_FILE(file("[GLOB.log_directory]/dynamic.json"), json_encode(export))
+ message_admins("Writing complete.")
+
+#endif
diff --git a/code/game/gamemodes/dynamic/readme.md b/code/game/gamemodes/dynamic/readme.md
index 5d979b4001637..778e322fa89a9 100644
--- a/code/game/gamemodes/dynamic/readme.md
+++ b/code/game/gamemodes/dynamic/readme.md
@@ -5,16 +5,47 @@
Dynamic rolls threat based on a special sauce formula:
"dynamic_curve_width \* tan((3.1416 \* (rand() - 0.5) \* 57.2957795)) + dynamic_curve_centre"
+This threat is split into two separate budgets--`round_start_budget` and `mid_round_budget`. For example, a round with 50 threat might be split into a 30 roundstart budget, and a 20 midround budget. The roundstart budget is used to apply antagonists applied on readied players when the roundstarts (`/datum/dynamic_ruleset/roundstart`). The midround budget is used for two types of rulesets:
+- `/datum/dynamic_ruleset/midround` - Rulesets that apply to either existing alive players, or to ghosts. Think Blob or Space Ninja, which poll ghosts asking if they want to play as these roles.
+- `/datum/dynamic_ruleset/latejoin` - Rulesets that apply to the next player that joins. Think Syndicate Infiltrator, which converts a player just joining an existing round into traitor.
+
+This split is done with a similar method, known as the ["lorentz distribution"](https://en.wikipedia.org/wiki/Cauchy_distribution), exists to create a bell curve that ensures that while most rounds will have a threat level around ~50, chaotic and tame rounds still exist for variety.
+
+The process of creating these numbers occurs in `/datum/game_mode/dynamic/proc/generate_threat` (for creating the threat level) and `/datum/game_mode/dynamic/proc/generate_budgets` (for splitting the threat level into budgets).
+
+## Deciding roundstart threats
+In `/datum/game_mode/dynamic/proc/roundstart()` (called when no admin chooses the rulesets explicitly), Dynamic uses the available roundstart budget to pick threats. This is done through the following system:
+
+- All roundstart rulesets (remember, `/datum/dynamic_ruleset/roundstart`) are put into an associative list with their weight as the values (`drafted_rules`).
+- Until there is either no roundstart budget left, or until there is no ruleset we can choose from with the available threat, a `pickweight` is done based on the drafted_rules. If the same threat is picked twice, it will "scale up". The meaning of this depends on the ruleset itself, using the `scaled_times` variable; traitors for instance will create more the higher they scale.
+ - If a ruleset is chosen with the `HIGH_IMPACT_RULESET` in its `flags`, then all other `HIGH_IMPACT_RULESET`s will be removed from `drafted_rules`. This is so that only one can ever be chosen.
+ - If a ruleset has `LONE_RULESET` in its `flags`, then it will be removed from `drafted_rules`. This is to ensure it will only ever be picked once. An example of this in use is Wizard, to avoid creating multiple wizards.
+- After all roundstart threats are chosen, `/datum/dynamic_ruleset/proc/picking_roundstart_rule` is called for each, passing in the ruleset and the number of times it is scaled.
+ - In this stage, `pre_execute` is called, which is the function that will determine what players get what antagonists. If this function returns FALSE for whatever reason (in the case of an error), then its threat is refunded.
+
+After this process is done, any leftover roundstart threat will be given to the existing midround budget (done in `/datum/game_mode/dynamic/pre_setup()`).
+
+## Deciding midround threats
+
Latejoin and midround injection cooldowns are set using exponential distribution between
5 minutes and 25 for latejoin
15 minutes and 35 for midround
this value is then added to world.time and assigned to the injection cooldown variables.
-rigged_roundstart() is called instead if there are forced rules (an admin set the mode)
+- 5 minutes and 25 for latejoin (configurable as latejoin_delay_min and latejoin_delay_max)
+- 15 minutes and 35 for midround (configurable as midround_delay_min and midround_delay_max)
+
+this value is then added to `world.time` and assigned to the injection cooldown variables.
-can_start() -> pre_setup() -> roundstart() OR rigged_roundstart() -> picking_roundstart_rule(drafted_rules) -> post_setup()
+[rigged_roundstart][/datum/game_mode/dynamic/proc/rigged_roundstart] is called instead if there are forced rules (an admin set the mode)
-## PROCESS
+1. [setup_parameters][/datum/game_mode/proc/setup_parameters]\()
+2. [pre_setup][/datum/game_mode/proc/pre_setup]\()
+3. [roundstart][/datum/game_mode/dynamic/proc/roundstart]\() OR [rigged_roundstart][/datum/game_mode/dynamic/proc/rigged_roundstart]\()
+4. [picking_roundstart_rule][/datum/game_mode/dynamic/proc/picking_roundstart_rule]\(drafted_rules)
+5. [post_setup][/datum/game_mode/proc/post_setup]\()
+
+## Rule Processing
Calls rule_process on every rule which is in the current_rules list.
Every sixty seconds, update_playercounts()
@@ -65,3 +96,91 @@ Midround: Instead of building a single list candidates, candidates contains four
Midround - Rulesets have additional types
/from_ghosts: execute() -> send_applications() -> review_applications() -> finish_setup(mob/newcharacter, index) -> setup_role(role)
**NOTE: execute() here adds dead players and observers to candidates list
+
+## Configuration and variables
+
+### Configuration
+Configuration can be done through a `config/dynamic.json` file. One is provided as example in the codebase. This config file, loaded in `/datum/game_mode/dynamic/pre_setup()`, directly overrides the values in the codebase, and so is perfect for making some rulesets harder/easier to get, turning them off completely, changing how much they cost, etc.
+
+The format of this file is:
+```json
+{
+ "Dynamic": {
+ /* Configuration in here will directly override `/datum/game_mode/dynamic` itself. */
+ /* Keys are variable names, values are their new values. */
+ },
+
+ "Roundstart": {
+ /* Configuration in here will apply to `/datum/dynamic_ruleset/roundstart` instances. */
+ /* Keys are the ruleset names, values are another associative list with keys being variable names and values being new values. */
+ "Wizard": {
+ /* I, a head admin, have died to wizard, and so I made it cost a lot more threat than it does in the codebase. */
+ "cost": 80
+ }
+ },
+
+ "Midround": {
+ /* Same as "Roundstart", but for `/datum/dynamic_ruleset/midround` instead. */
+ },
+
+ "Latejoin": {
+ /* Same as "Roundstart", but for `/datum/dynamic_ruleset/latejoin` instead. */
+ }
+}
+```
+
+Note: Comments are not possible in this format, and are just in this document for the sake of readability.
+
+### Rulesets
+Rulesets have the following variables notable to developers and those interested in tuning.
+
+- `required_candidates` - The number of people that *must be willing* (in their preferences) to be an antagonist with this ruleset. If the candidates do not meet this requirement, then the ruleset will not bother to be drafted.
+- `antag_cap` - Judges the amount of antagonists to apply, for both solo and teams. Note that some antagonists (such as traitors, lings, heretics, etc) will add more based on how many times they've been scaled. Written as a linear equation--ceil(x/denominator) + offset, or as a fixed constant. If written as a linear equation, will be in the form of `list("denominator" = denominator, "offset" = offset)`.
+ - Examples include:
+ - Traitor: `antag_cap = list("denominator" = 24)`. This means that for every 24 players, 1 traitor will be added (assuming no scaling).
+ - Nuclear Emergency: `antag_cap = list("denominator" = 18, "offset" = 1)`. For every 18 players, 1 nuke op will be added. Starts at 1, meaning at 30 players, 3 nuke ops will be created, rather than 2.
+ - Revolution: `antag_cap = 3`. There will always be 3 rev-heads, no matter what.
+- `minimum_required_age` - The minimum age in order to apply for the ruleset.
+- `weight` - How likely this ruleset is to be picked. A higher weight results in a higher chance of drafting.
+- `cost` - The initial cost of the ruleset. This cost is taken from either the roundstart or midround budget, depending on the ruleset.
+- `scaling_cost` - Cost for every *additional* application of this ruleset.
+ - Suppose traitors has a `cost` of 8, and a `scaling_cost` of 5. This means that buying 1 application of the traitor ruleset costs 8 threat, but buying two costs 13 (8 + 5). Buying it a third time is 18 (8 + 5 + 5), etc.
+- `pop_per_requirement` - The range of population each value in `requirements` represents. By default, this is 6.
+ - If the value is five the range is 0-4, 5-9, 10-14, 15-19, 20-24, 25-29, 30-34, 35-39, 40-54, 45+.
+ - If it is six the range is 0-5, 6-11, 12-17, 18-23, 24-29, 30-35, 36-41, 42-47, 48-53, 54+.
+ - If it is seven the range is 0-6, 7-13, 14-20, 21-27, 28-34, 35-41, 42-48, 49-55, 56-62, 63+.
+- `requirements` - A list that represents, per population range (see: `pop_per_requirement`), how much threat is required to *consider* this ruleset. This is independent of how much it'll actually cost. This uses *threat level*, not the budget--meaning if a round has 50 threat level, but only 10 points of round start threat, a ruleset with a requirement of 40 can still be picked if it can be bought.
+ - Suppose wizard has a `requirements` of `list(90,90,70,40,30,20,10,10,10,10)`. This means that, at 0-5 and 6-11 players, A station must have 90 threat in order for a wizard to be possible. At 12-17, 70 threat is required instead, etc.
+- `restricted_roles` - A list of jobs that *can't* be drafted by this ruleset. For example, cyborgs cannot be changelings, and so are in the `restricted_roles`.
+- `protected_roles` - Serves the same purpose of `restricted_roles`, except it can be turned off through configuration (`protect_roles_from_antagonist`). For example, security officers *shouldn't* be made traitor, so they are in Traitor's `protected_roles`.
+ - When considering putting a role in `protected_roles` or `restricted_roles`, the rule of thumb is if it is *technically infeasible* to support that job in that role. There's no *technical* reason a security officer can't be a traitor, and so they are simply in `protected_roles`. There *are* technical reasons a cyborg can't be a changeling, so they are in `restricted_roles` instead.
+
+### Dynamic
+
+The "Dynamic" key has the following configurable values:
+- `pop_per_requirement` - The default value of `pop_per_requirement` for any ruleset that does not explicitly set it. Defaults to 6.
+- `latejoin_delay_min`, `latejoin_delay_max` - The time range, in deciseconds (take your seconds, and multiply by 10), for a latejoin to attempt rolling. Once this timer is finished, a new one will be created within the same range.
+ - Suppose you have a `latejoin_delay_min` of 600 (60 seconds, 1 minute) and a `latejoin_delay_max` of 1800 (180 seconds, 3 minutes). Once the round starts, a random number in this range will be picked--let's suppose 1.5 minutes. After 1.5 minutes, Dynamic will decide if a latejoin threat should be created (a probability of `/datum/game_mode/dynamic/proc/get_injection_chance()`). Regardless of its decision, a new timer will be started within the range of 1 to 3 minutes, repeatedly.
+- `midround_delay_min`, `midround_delay_max` - Same as `latejoin_delay_min` and `latejoin_delay_max`, except for midround threats instead of latejoin ones.
+- `higher_injection_chance`, `higher_injection_chance_minimum_threat` - Manipulates the injection chance (`/datum/game_mode/dynamic/proc/get_injection_chance()`). If the *current midround budget* is above `higher_injection_chance_minimum_threat`, then this chance will be increased by `higher_injection_chance`.
+ - For example: suppose you have a `higher_injection_chance_minimum_threat` of 70, and a `higher_injection_chance` of 15. This means that, if when a midround threat is trying to roll, there is 75 midround budget left, then the injection chance will go up 15%.
+- `lower_injection_chance`, `lower_injection_chance_minimum_threat` - The inverse of the `higher_injection_chance` variables. If the *current midround budget* is *below* `lower_injection_chance`, then the chance is lowered by `lower_injection_chance_minimum_threat`.
+ - For example: suppose you have a `lower_injection_chance_minimum_threat` of 30, and a `lower_injection_chance` of 15. This means if there is 20 midround budget left, then the chance will lower by 15%.
+- `threat_curve_centre` - A number between -5 and +5. A negative value will give a more peaceful round and a positive value will give a round with higher threat.
+- `threat_curve_width` - A number between 0.5 and 4. Higher value will favour extreme rounds and lower value rounds closer to the average.
+- `roundstart_split_curve_centre` - A number between -5 and +5. Equivalent to threat_curve_centre, but for the budget split. A negative value will weigh towards midround rulesets, and a positive value will weight towards roundstart ones.
+- `roundstart_split_curve_width` - A number between 0.5 and 4. Equivalent to threat_curve_width, but for the budget split. Higher value will favour more variance in splits and lower value rounds closer to the average.
+- `random_event_hijack_minimum` - The minimum amount of time for antag random events to be hijacked. (See [Random Event Hijacking](#random-event-hijacking))
+- `random_event_hijack_maximum` - The maximum amount of time for antag random events to be hijacked. (See [Random Event Hijacking](#random-event-hijacking))
+- `hijacked_random_event_injection_chance` - The amount of injection chance to give to Dynamic when a random event is hijacked. (See [Random Event Hijacking](#random-event-hijacking))
+
+## Random Event "Hijacking"
+Random events have the potential to be hijacked by Dynamic to keep the pace of midround injections, while also allowing greenshifts to contain some antagonists.
+
+`/datum/round_event_control/dynamic_should_hijack` is a variable to random events to allow Dynamic to hijack them, and defaults to FALSE. This is set to TRUE for random events that spawn antagonists.
+
+In `/datum/game_mode/dynamic/on_pre_random_event` (in `dynamic_hijacking.dm`), Dynamic hooks to random events. If the `dynamic_should_hijack` variable is TRUE, the following sequence of events occurs:
+
+![Flow chart to describe the chain of events for Dynamic 2021 to take](https://user-images.githubusercontent.com/35135081/109071468-9cab7e00-76a8-11eb-8f9f-2b920c602ef4.png)
+
+`n` is a random value between `random_event_hijack_minimum` and `random_event_hijack_maximum`. Injection chance, should it need to be raised, is increased by `hijacked_random_event_injection_chance`.
diff --git a/code/game/gamemodes/dynamic/ruleset_picking.dm b/code/game/gamemodes/dynamic/ruleset_picking.dm
new file mode 100644
index 0000000000000..9221e4a3c1527
--- /dev/null
+++ b/code/game/gamemodes/dynamic/ruleset_picking.dm
@@ -0,0 +1,118 @@
+#define ADMIN_CANCEL_MIDROUND_TIME (10 SECONDS)
+
+/// From a list of rulesets, returns one based on weight and availability.
+/// Mutates the list that is passed into it to remove invalid rules.
+/datum/game_mode/dynamic/proc/pick_ruleset(list/drafted_rules)
+ if (only_ruleset_executed)
+ return null
+
+ while (TRUE)
+ var/datum/dynamic_ruleset/rule = pickweight(drafted_rules)
+ if (!rule)
+ return null
+
+ if (check_blocking(rule.blocking_rules, executed_rules))
+ drafted_rules -= rule
+ if(drafted_rules.len <= 0)
+ return null
+ continue
+ else if (
+ rule.flags & HIGH_IMPACT_RULESET \
+ && threat_level < GLOB.dynamic_stacking_limit \
+ && GLOB.dynamic_no_stacking \
+ && high_impact_ruleset_executed \
+ )
+ drafted_rules -= rule
+ if(drafted_rules.len <= 0)
+ return null
+ continue
+
+ return rule
+
+/// Executes a random midround ruleset from the list of drafted rules.
+/datum/game_mode/dynamic/proc/pick_midround_rule(list/drafted_rules)
+ var/datum/dynamic_ruleset/rule = pick_ruleset(drafted_rules)
+ if (isnull(rule))
+ return
+ current_midround_rulesets = drafted_rules - rule
+
+ midround_injection_timer_id = addtimer(
+ CALLBACK(src, .proc/execute_midround_rule, rule), \
+ ADMIN_CANCEL_MIDROUND_TIME, \
+ TIMER_STOPPABLE, \
+ )
+
+ log_game("DYNAMIC: [rule] ruleset executing...")
+ message_admins("DYNAMIC: Executing midround ruleset [rule] in [DisplayTimeText(ADMIN_CANCEL_MIDROUND_TIME)]. \
+ CANCEL | \
+ SOMETHING ELSE")
+
+/// Fired after admins do not cancel a midround injection.
+/datum/game_mode/dynamic/proc/execute_midround_rule(datum/dynamic_ruleset/rule)
+ current_midround_rulesets = null
+ midround_injection_timer_id = null
+ if (!rule.repeatable)
+ midround_rules = remove_from_list(midround_rules, rule.type)
+ addtimer(CALLBACK(src, .proc/execute_midround_latejoin_rule, rule), rule.delay)
+
+/// Executes a random latejoin ruleset from the list of drafted rules.
+/datum/game_mode/dynamic/proc/pick_latejoin_rule(list/drafted_rules)
+ var/datum/dynamic_ruleset/rule = pick_ruleset(drafted_rules)
+ if (isnull(rule))
+ return
+ if (!rule.repeatable)
+ latejoin_rules = remove_from_list(latejoin_rules, rule.type)
+ addtimer(CALLBACK(src, .proc/execute_midround_latejoin_rule, rule), rule.delay)
+ return TRUE
+
+/// Mainly here to facilitate delayed rulesets. All midround/latejoin rulesets are executed with a timered callback to this proc.
+/datum/game_mode/dynamic/proc/execute_midround_latejoin_rule(sent_rule)
+ var/datum/dynamic_ruleset/rule = sent_rule
+ spend_midround_budget(rule.cost)
+ threat_log += "[worldtime2text()]: [rule.ruletype] [rule.name] spent [rule.cost]"
+ rule.pre_execute(current_players[CURRENT_LIVING_PLAYERS].len)
+ if (rule.execute())
+ log_game("DYNAMIC: Injected a [rule.ruletype == "latejoin" ? "latejoin" : "midround"] ruleset [rule.name].")
+ if(rule.flags & HIGH_IMPACT_RULESET)
+ high_impact_ruleset_executed = TRUE
+ else if(rule.flags & ONLY_RULESET)
+ only_ruleset_executed = TRUE
+ if(rule.ruletype == "Latejoin")
+ var/mob/M = pick(rule.candidates)
+ dynamic_log("[key_name(M)] joined the station, and was selected by the [rule.name] ruleset.")
+ executed_rules += rule
+ rule.candidates.Cut()
+ if (rule.persistent)
+ current_rules += rule
+ new_snapshot(rule)
+ return TRUE
+ rule.clean_up()
+ stack_trace("The [rule.ruletype] rule \"[rule.name]\" failed to execute.")
+ return FALSE
+
+/// Fired when an admin cancels the current midround injection.
+/datum/game_mode/dynamic/proc/admin_cancel_midround(mob/user, timer_id)
+ if (midround_injection_timer_id != timer_id || !deltimer(midround_injection_timer_id))
+ to_chat(user, "Too late!")
+ return
+
+ dynamic_log("[key_name(user)] cancelled the next midround injection.")
+ midround_injection_timer_id = null
+ current_midround_rulesets = null
+
+/// Fired when an admin requests a different midround injection.
+/datum/game_mode/dynamic/proc/admin_different_midround(mob/user, timer_id)
+ if (midround_injection_timer_id != timer_id || !deltimer(midround_injection_timer_id))
+ to_chat(user, "Too late!")
+ return
+
+ midround_injection_timer_id = null
+
+ if (isnull(current_midround_rulesets) || current_midround_rulesets.len == 0)
+ dynamic_log("[key_name(user)] asked for a different midround injection, but there were none left.")
+ return
+
+ dynamic_log("[key_name(user)] asked for a different midround injection.")
+ pick_midround_rule(current_midround_rulesets)
+
+#undef ADMIN_CANCEL_MIDROUND_TIME
diff --git a/code/game/gamemodes/eldritch_cult/eldritch_cult.dm b/code/game/gamemodes/eldritch_cult/eldritch_cult.dm
index 02a6b1551d335..40a56623f223d 100644
--- a/code/game/gamemodes/eldritch_cult/eldritch_cult.dm
+++ b/code/game/gamemodes/eldritch_cult/eldritch_cult.dm
@@ -4,7 +4,7 @@
report_type = "heresy"
antag_flag = ROLE_HERETIC
false_report_weight = 5
- protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Brig Physician")
+ protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain")
restricted_jobs = list("AI", "Cyborg")
required_players = 0
required_enemies = 1
@@ -12,6 +12,8 @@
reroll_friendly = 1
enemy_minimum_age = 0
+ allowed_special = list(/datum/special_role/traitor/higher_chance)
+
announce_span = "danger"
announce_text = "Heretics have been spotted on the station!\n\
Heretics: Accomplish your objectives.\n\
@@ -65,3 +67,13 @@
/datum/game_mode/heretics/generate_report()
return "Cybersun Industries has announced that they have successfully raided a high-security library. The library contained a very dangerous book that was \
shown to posses anomalous properties. We suspect that the book has been copied over, Stay vigilant!"
+
+/datum/game_mode/heretics/generate_credit_text()
+ var/list/round_credits = list()
+
+ round_credits += "
The Eldrich Cult:
"
+ for(var/datum/mind/M in culties)
+ round_credits += "
[M.name] as a heretic
"
+
+ round_credits += ..()
+ return round_credits
diff --git a/code/game/gamemodes/events.dm b/code/game/gamemodes/events.dm
index 8a7181aaa3ddd..4503462051ecd 100644
--- a/code/game/gamemodes/events.dm
+++ b/code/game/gamemodes/events.dm
@@ -1,5 +1,5 @@
/proc/power_failure()
- priority_announce("Abnormal activity detected in [station_name()]'s powernet. As a precautionary measure, the station's power will be shut off for an indeterminate duration.", "Critical Power Failure", 'sound/ai/poweroff.ogg')
+ priority_announce("Abnormal activity detected in [station_name()]'s powernet. As a precautionary measure, the station's power will be shut off for an indeterminate duration.", "Critical Power Failure", ANNOUNCER_POWEROFF)
for(var/obj/machinery/power/smes/S in GLOB.machines)
if(istype(get_area(S), /area/ai_monitored/turret_protected) || !is_station_level(S.z))
continue
@@ -30,7 +30,7 @@
/proc/power_restore()
- priority_announce("Power has been restored to [station_name()]. We apologize for the inconvenience.", "Power Systems Nominal", 'sound/ai/poweron.ogg')
+ priority_announce("Power has been restored to [station_name()]. We apologize for the inconvenience.", "Power Systems Nominal", ANNOUNCER_POWERON)
for(var/obj/machinery/power/apc/C in GLOB.machines)
if(C.cell && is_station_level(C.z))
C.cell.charge = C.cell.maxcharge
@@ -54,7 +54,7 @@
/proc/power_restore_quick()
- priority_announce("All SMESs on [station_name()] have been recharged. We apologize for the inconvenience.", "Power Systems Nominal", 'sound/ai/poweron.ogg')
+ priority_announce("All SMESs on [station_name()] have been recharged. We apologize for the inconvenience.", "Power Systems Nominal", ANNOUNCER_POWERON)
for(var/obj/machinery/power/smes/S in GLOB.machines)
if(!is_station_level(S.z))
continue
diff --git a/code/game/gamemodes/events/event.dm b/code/game/gamemodes/events/event.dm
index 5928817791806..4883557d8b6b1 100644
--- a/code/game/gamemodes/events/event.dm
+++ b/code/game/gamemodes/events/event.dm
@@ -20,7 +20,7 @@
/datum/game_mode/event/send_intercept(report = 0)
if(intercept_message)
- priority_announce(intercept_message, "Security Report", 'sound/ai/commandreport.ogg')
+ priority_announce(intercept_message, "Security Report", SSstation.announcer.get_rand_report_sound())
/datum/game_mode/event/generate_station_goals()
return
diff --git a/code/game/gamemodes/extended/extended.dm b/code/game/gamemodes/extended/extended.dm
index 6cebda97575c4..289bb685fa32d 100644
--- a/code/game/gamemodes/extended/extended.dm
+++ b/code/game/gamemodes/extended/extended.dm
@@ -32,7 +32,10 @@
station_goals += G
G.on_report()
-/datum/game_mode/extended/send_intercept(report = 0)
- if(secret)
- return ..()
- priority_announce("Thanks to the tireless efforts of our security and intelligence divisions, there are currently no credible threats to [station_name()]. All station construction projects have been authorized. Have a secure shift!", "Security Report", 'sound/ai/commandreport.ogg')
+/datum/game_mode/extended/announced/send_intercept()
+ var/greenshift_message = "Thanks to the tireless efforts of our security and intelligence divisions, there are currently no credible threats to [station_name()]. All station construction projects have been authorized. Have a secure shift!"
+ . += "Central Command Status Summary"
+ . += greenshift_message
+
+ print_command_report(., "Central Command Status Summary", announce = FALSE)
+ priority_announce(greenshift_message, "Security Report", SSstation.announcer.get_rand_report_sound())
diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm
index 7350dbd415390..addfda8d20273 100644
--- a/code/game/gamemodes/game_mode.dm
+++ b/code/game/gamemodes/game_mode.dm
@@ -23,6 +23,7 @@
var/list/datum/mind/antag_candidates = list() // List of possible starting antags goes here
var/list/restricted_jobs = list() // Jobs it doesn't make sense to be. I.E chaplain or AI cultist
var/list/protected_jobs = list() // Jobs that can't be traitors because
+ var/list/required_jobs = list() // alternative required job groups eg list(list(cap=1),list(hos=1,sec=2)) translates to one captain OR one hos and two secmans
var/required_players = 0
var/maximum_players = -1 // -1 is no maximum, positive numbers limit the selection of a mode on overstaffed stations
var/required_enemies = 0
@@ -53,6 +54,10 @@
var/gamemode_ready = FALSE //Is the gamemode all set up and ready to start checking for ending conditions.
var/setup_error //What stopepd setting up the mode.
+ /// Associative list of current players, in order: living players, living antagonists, dead players and observers.
+ var/list/list/current_players = list(CURRENT_LIVING_PLAYERS = list(), CURRENT_LIVING_ANTAGS = list(), CURRENT_DEAD_PLAYERS = list(), CURRENT_OBSERVERS = list())
+
+
/datum/game_mode/proc/announce() //Shows the gamemode's name and a fast description.
to_chat(world, "The gamemode is: [name]!")
to_chat(world, "[announce_text]")
@@ -64,20 +69,22 @@
/datum/game_mode/proc/can_start()
var/playerC = 0
for(var/mob/dead/new_player/player in GLOB.player_list)
- if((player.client)&&(player.ready == PLAYER_READY_TO_PLAY))
+ if(player.client && (player.ready == PLAYER_READY_TO_PLAY) && player.has_valid_preferences(TRUE))
playerC++
if(!GLOB.Debug2)
if(playerC < required_players || (maximum_players >= 0 && playerC > maximum_players))
- return 0
+ return FALSE
antag_candidates = get_players_for_role(antag_flag)
if(!GLOB.Debug2)
if(antag_candidates.len < required_enemies)
- return 0
- return 1
+ return FALSE
+ return TRUE
else
message_admins("DEBUG: GAME STARTING WITHOUT PLAYER NUMBER CHECKS, THIS WILL PROBABLY BREAK SHIT.")
- return 1
+ return TRUE
+/datum/game_mode/proc/setup_maps()
+ return 1
///Attempts to select players for special roles the mode might have.
/datum/game_mode/proc/pre_setup()
@@ -95,6 +102,7 @@
for(var/role_to_init in allowed_special)
var/datum/special_role/new_role = new role_to_init
+ new_role.setup()
if(!prob(new_role.probability))
continue
new_role.add_to_pool()
@@ -109,7 +117,11 @@
for(var/i in 1 to amount)
if(candidates.len == 0)
return //No more candidates, end the selection process, and active specials at this time will be handled by latejoins or not included
- var/mob/person = pick_n_take(candidates)
+ var/mob/person
+ if(special.special_role_flag)
+ person = antag_pick(candidates, special.special_role_flag)
+ else
+ person = pick_n_take(candidates)
if(is_banned_from(person.ckey, special.preference_type))
continue
if(!person)
@@ -117,7 +129,7 @@
var/datum/mind/selected_mind = person.mind
if(selected_mind.special_role)
continue
- if(person.job in special.protected_jobs)
+ if(person.job in special.restricted_jobs)
continue
//Would be annoying trying to assasinate someone with special statuses
if(selected_mind.isAntagTarget && !special.allowAntagTargets)
@@ -156,10 +168,13 @@
)
query_round_game_mode.Execute()
qdel(query_round_game_mode)
- if(report)
- addtimer(CALLBACK(src, .proc/send_intercept, 0), rand(waittime_l, waittime_h))
create_special_antags()
generate_station_goals()
+ if(report)
+ addtimer(CALLBACK(src, .proc/send_intercept, 0), rand(waittime_l, waittime_h))
+ else // goals only become purchasable when on_report is called, this also makes a replacement announcement.
+ for(var/datum/station_goal/G in station_goals)
+ G.prepare_report()
gamemode_ready = TRUE
return 1
@@ -251,6 +266,8 @@
replacementmode.restricted_jobs += replacementmode.protected_jobs
if(CONFIG_GET(flag/protect_assistant_from_antagonist))
replacementmode.restricted_jobs += "Assistant"
+ if(CONFIG_GET(flag/protect_heads_from_antagonist))
+ replacementmode.restricted_jobs += GLOB.command_positions
message_admins("The roundtype will be converted. If you have other plans for the station or feel the station is too messed up to inhabit stop the creation of antags or end the round now.")
log_game("Roundtype converted to [replacementmode.name]")
@@ -258,14 +275,14 @@
. = 1
sleep(rand(600,1800))
+ //somewhere between 1 and 3 minutes from now
if(!SSticker.IsRoundInProgress())
message_admins("Roundtype conversion cancelled, the game appears to have finished!")
round_converted = 0
return
- //somewhere between 1 and 3 minutes from now
if(!CONFIG_GET(keyed_list/midround_antag)[SSticker.mode.config_tag])
round_converted = 0
- return 1
+ return
for(var/mob/living/carbon/human/H in antag_candidates)
if(H.client)
replacementmode.make_antag_chance(H)
@@ -273,10 +290,12 @@
round_converted = 2
message_admins("-- IMPORTANT: The roundtype has been converted to [replacementmode.name], antagonists may have been created! --")
-
-///Called by the gameSSticker
-/datum/game_mode/process()
- return 0
+/**
+ * ATTENTION:
+ * If you make some special process() for your gamemode,
+ * it'll be called by the SSticker, which ignores your
+ * return value.
+ */
//For things that do not die easily
/datum/game_mode/proc/are_special_antags_dead()
@@ -368,18 +387,28 @@
intercepttext += ""
intercepttext += report
- if(station_goals.len)
- intercepttext += "Special Orders for [station_name()]:"
- for(var/datum/station_goal/G in station_goals)
- G.on_report()
- intercepttext += G.get_report()
+ intercepttext += generate_station_goal_report()
print_command_report(intercepttext, "Central Command Status Summary", announce=FALSE)
- priority_announce("A summary has been copied and printed to all communications consoles.", "Enemy communication intercepted. Security level elevated.", 'sound/ai/intercept.ogg')
+ priority_announce("A summary has been copied and printed to all communications consoles.", "Enemy communication intercepted. Security level elevated.", ANNOUNCER_INTERCEPT)
if(GLOB.security_level < SEC_LEVEL_BLUE)
set_security_level(SEC_LEVEL_BLUE)
+/*
+ * Generate a list of station goals available to purchase to report to the crew.
+ *
+ * Returns a formatted string all station goals that are available to the station.
+ */
+/datum/game_mode/proc/generate_station_goal_report()
+ if(!station_goals.len)
+ return
+ . = "Special Orders for [station_name()]: "
+ for(var/datum/station_goal/station_goal in station_goals)
+ station_goal.on_report()
+ . += station_goal.get_report()
+ return
+
// This is a frequency selection system. You may imagine it like a raffle where each player can have some number of tickets. The more tickets you have the more likely you are to
// "win". The default is 100 tickets. If no players use any extra tickets (earned with the antagonist rep system) calling this function should be equivalent to calling the normal
// pick() function. By default you may use up to 100 extra tickets per roll, meaning at maximum a player may double their chances compared to a player who has no extra tickets.
@@ -576,6 +605,7 @@
valid_positions += GLOB.science_positions
valid_positions += GLOB.supply_positions
valid_positions += GLOB.civilian_positions
+ valid_positions += GLOB.gimmick_positions
valid_positions += GLOB.security_positions
if(CONFIG_GET(flag/reopen_roundstart_suicide_roles_command_positions))
valid_positions += GLOB.command_positions //add any remaining command positions
@@ -813,7 +843,7 @@
var/list/human_garbage = list()
round_credits += "
The Hardy Civilians:
"
len_before_addition = round_credits.len
- for(var/datum/mind/current in SSticker.mode.get_all_by_department(GLOB.civilian_positions))
+ for(var/datum/mind/current in SSticker.mode.get_all_by_department(GLOB.civilian_positions | GLOB.gimmick_positions))
if(current.assigned_role == "Assistant")
human_garbage += current
else
diff --git a/code/game/gamemodes/gangs/dominator.dm b/code/game/gamemodes/gangs/dominator.dm
index 12bc687fffda6..8061f1260d3ac 100644
--- a/code/game/gamemodes/gangs/dominator.dm
+++ b/code/game/gamemodes/gangs/dominator.dm
@@ -13,7 +13,7 @@
max_integrity = 300
integrity_failure = 100
move_resist = INFINITY
- armor = list("melee" = 20, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 10, "acid" = 70)
+ armor = list("melee" = 20, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 10, "acid" = 70, "stamina" = 0)
var/datum/team/gang/gang
var/operating = FALSE //false=standby or broken, true=takeover
var/warned = FALSE //if this device has set off the warning at <3 minutes yet
@@ -37,7 +37,6 @@
gang = null
QDEL_NULL(spark_system)
QDEL_NULL(countdown)
- STOP_PROCESSING(SSmachines, src)
return ..()
/obj/machinery/dominator/emp_act(severity)
@@ -81,7 +80,6 @@
to_chat(user, "System Integrity: [round((obj_integrity/max_integrity)*100,1)]%")
/obj/machinery/dominator/process()
- ..()
if(gang && gang.domination_time != NOT_DOMINATING)
var/time_remaining = gang.domination_time_remaining()
if(time_remaining > 0)
@@ -111,7 +109,7 @@
SSticker.force_ending = TRUE
if(!.)
- STOP_PROCESSING(SSmachines, src)
+ return PROCESS_KILL
/obj/machinery/dominator/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0)
switch(damage_type)
@@ -181,7 +179,7 @@
gang = tempgang
gang.dom_attempts --
- priority_announce("Network breach detected in [locname]. The [gang.name] Gang is attempting to seize control of the station!","Network Alert")
+ priority_announce("Network breach detected in [locname]. The [gang.name] Gang is attempting to seize control of the station!", "Network Alert", SSstation.announcer.get_rand_alert_sound())
gang.domination()
SSshuttle.registerHostileEnvironment(src)
name = "[gang.name] Gang [name]"
@@ -204,9 +202,8 @@
var/open = FALSE
if(isclosedturf(loc))
return TRUE
- for(var/turf/T in view(3, src))
- if(!isclosedturf(T))
- open++
+ for(var/turf/open/T in view(3, src))
+ open++
if(open < DOM_REQUIRED_TURFS)
return TRUE
else
@@ -225,7 +222,7 @@
if(!takeover_in_progress)
var/was_stranded = SSshuttle.emergency.mode == SHUTTLE_STRANDED
if(!was_stranded)
- priority_announce("All hostile activity within station systems has ceased.","Network Alert")
+ priority_announce("All hostile activity within station systems has ceased.","Network Alert", SSstation.announcer.get_rand_alert_sound())
if(get_security_level() == "delta")
set_security_level("red")
diff --git a/code/game/gamemodes/gangs/gang_items.dm b/code/game/gamemodes/gangs/gang_items.dm
index f74d943cc3d04..184834a7621c5 100644
--- a/code/game/gamemodes/gangs/gang_items.dm
+++ b/code/game/gamemodes/gangs/gang_items.dm
@@ -44,8 +44,6 @@
/datum/gang_item/proc/get_extra_info(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
return
-
-
///////////////////
//Essential Gang Tools
///////////////////
@@ -53,11 +51,26 @@
/datum/gang_item/essentials
category = "Purchase Essential Items:"
+/datum/gang_item/essentials/spraycan
+ name = "Territory Spraycan"
+ id = "spraycan"
+ cost = 10
+ item_path = /obj/item/toy/crayon/spraycan/gang
+/datum/gang_item/essentials/implant_breaker
+ name = "Reprogramming Implant"
+ id = "implant_breaker"
+ cost = 25
+ item_path = /obj/item/implanter/gang
+ spawn_msg = "The reprogramming implant is a single use implant that will reprogram its target to be part of your gang. Not strong enough to break the latest NT mindshield implants, or reprogram Lieutenants."
+
+/datum/gang_item/essentials/implant_breaker/spawn_item(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
+ var/obj/item/O = new item_path(get_turf(user), gang)
+ user.put_in_hands(O)
/datum/gang_item/essentials/gangtool
id = "gangtool"
- cost = 10
+ cost = 50
/datum/gang_item/essentials/gangtool/spawn_item(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
var/item_type
@@ -75,21 +88,14 @@
return "Promote a Gangster"
return "Spare Gangtool"
-/datum/gang_item/essentials/spraycan
- name = "Territory Spraycan"
- id = "spraycan"
- cost = 5
- item_path = /obj/item/toy/crayon/spraycan/gang
-
/datum/gang_item/essentials/spraycan/spawn_item(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
var/obj/item/O = new item_path(user.loc, gang)
user.put_in_hands(O)
-
/datum/gang_item/essentials/pen
name = "Recruitment Pen"
id = "pen"
- cost = 10
+ cost = 50
item_path = /obj/item/pen/gang
spawn_msg = "More recruitment pens will allow you to recruit gangsters faster. Only gang leaders can recruit with pens."
@@ -108,109 +114,88 @@
if(gangtool?.free_pen)
return "(GET ONE FREE)"
return ..()
-
-
-
-/datum/gang_item/essentials/dominator
- name = "Station Dominator"
- id = "dominator"
- cost = 30
- item_path = /obj/machinery/dominator
- spawn_msg = "The dominator will secure your gang's dominance over the station. Turn it on when you are ready to defend it."
-
-/datum/gang_item/essentials/dominator/can_buy(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- if(!gang || !gang.dom_attempts)
- return FALSE
- return ..()
-
-/datum/gang_item/essentials/dominator/get_name_display(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- if(!gang || !gang.dom_attempts)
- return ..()
- return "[..()]"
-
-/datum/gang_item/essentials/dominator/get_cost_display(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- if(!gang || !gang.dom_attempts)
- return "(Out of stock)"
- return ..()
-
-/datum/gang_item/essentials/dominator/get_extra_info(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- if(gang)
- return "This device requires a 5x5 area clear of walls to work. (Estimated Takeover Time: [round(gang.determine_domination_time()/60,0.1)] minutes)"
-
-/datum/gang_item/essentials/dominator/purchase(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- var/area/userarea = get_area(user)
- if(!(userarea.type in gang.territories|gang.new_territories))
- to_chat(user,"The dominator can be spawned only on territory controlled by your gang!")
- return FALSE
- for(var/obj/obj in get_turf(user))
- if(obj.density)
- to_chat(user, "There's not enough room here!")
- return FALSE
-
- return ..()
-
-/datum/gang_item/essentials/dominator/spawn_item(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- new item_path(user.loc)
- to_chat(user, spawn_msg)
-
-
-
+
+/datum/gang_item/essentials/reinforce
+ name = "Call Reinforcments"
+ id = "reinforce"
+ cost = 250
+ item_path = /obj/item/antag_spawner/gangster
///////////////////
//CLOTHING
///////////////////
/datum/gang_item/clothing
- category = "Purchase Gang Clothes (Only the jumpsuit and suit give you added influence):"
+ category = "Purchase Gang Clothes (Only the jumpsuit, hat and suit give you added influence):"
-/datum/gang_item/clothing/under
+/datum/gang_item/clothing/basic
name = "Gang Uniform"
id = "under"
- cost = 1
-
-/datum/gang_item/clothing/under/spawn_item(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- if(gang.inner_outfits.len)
- var/outfit = pick(gang.inner_outfits)
- if(outfit)
- var/obj/item/O = new outfit(user.loc)
- user.put_in_hands(O)
- to_chat(user, " This is your gang's official uniform, wearing it will increase your influence")
- return
+ cost = 5
+
+/datum/gang_item/clothing/basic/spawn_item(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
+ var/obj/item/storage/box/uniform_box = new (get_turf(user))
+
+ new gang.outfit(uniform_box)
+ new gang.suit(uniform_box)
+ new gang.hat(uniform_box)
+
+ user.put_in_hands(uniform_box)
+ to_chat(user, " This is your gang's official uniform, wearing it will increase your influence")
return TRUE
-/datum/gang_item/clothing/suit
+/datum/gang_item/clothing/armor
name = "Gang Armored Outerwear"
- id = "suit"
- cost = 1
-
-/datum/gang_item/clothing/suit/spawn_item(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- if(gang.outer_outfits.len)
- var/outfit = pick(gang.outer_outfits)
- if(outfit)
- var/obj/item/O = new outfit(user.loc)
- O.armor = O.armor.setRating(melee = 20, bullet = 35, laser = 10, energy = 10, bomb = 30, bio = 0, rad = 0, fire = 30, acid = 30)
- O.desc += " Tailored for the [gang.name] Gang to offer the wearer moderate protection against ballistics and physical trauma."
- user.put_in_hands(O)
- to_chat(user, " This is your gang's official outerwear, wearing it will increase your influence")
- return
- return TRUE
+ id = "armor"
+ cost = 200
+
+/datum/gang_item/clothing/armor/spawn_item(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
+ var/obj/item/storage/box/armor_box = new (get_turf(user))
+ var/obj/item/clothing/suit/suit = new gang.suit(armor_box)
+ suit.armor = suit.armor.setRating(melee = 20, bullet = 35, laser = 10, energy = 10, bomb = 30, bio = 0, rad = 0, fire = 30, acid = 30)
+ suit.desc += " Tailored for the [gang.name] Gang to offer the wearer moderate protection against ballistics and physical trauma."
-/datum/gang_item/clothing/hat
- name = "Pimp Hat"
- id = "hat"
- cost = 16
- item_path = /obj/item/clothing/head/collectable/petehat/gang
+ var/obj/item/clothing/head/hat = new gang.hat(armor_box)
+ hat.armor = hat.armor.setRating(melee = 20, bullet = 35, laser = 10, energy = 10, bomb = 30, bio = 0, rad = 0, fire = 30, acid = 30)
+ hat.desc += " Tailored for the [gang.name] Gang to offer the wearer moderate protection against ballistics and physical trauma."
+ user.put_in_hands(armor_box)
+ to_chat(user, " This is your gang's official uniform, wearing it will increase your influence")
+ return TRUE
-/obj/item/clothing/head/collectable/petehat/gang
- name = "pimpin' hat"
- desc = "The undisputed king of style."
+/datum/gang_item/clothing/ssuit
+ name = "Gang Spaceproof Outerwear"
+ id = "ssuit"
+ cost = 200
+
+/datum/gang_item/clothing/ssuit/spawn_item(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
+ var/obj/item/storage/box/armor_box = new (get_turf(user))
+
+ var/obj/item/clothing/suit/suit = new gang.suit(armor_box)
+ suit.clothing_flags |= STOPSPRESSUREDAMAGE | THICKMATERIAL
+ suit.cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
+ suit.heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
+ suit.min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT
+ suit.max_heat_protection_temperature = SPACE_HELM_MAX_TEMP_PROTECT
+ suit.desc += " Tailored for the [gang.name] Gang to offer the wearer moderate protection against ballistics and physical trauma."
+
+ var/obj/item/clothing/head/hat = new gang.hat(armor_box)
+ hat.clothing_flags |= STOPSPRESSUREDAMAGE | THICKMATERIAL
+ hat.cold_protection = HEAD
+ hat.heat_protection = HEAD
+ hat.min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT
+ hat.max_heat_protection_temperature = SPACE_HELM_MAX_TEMP_PROTECT
+ hat.desc += " Tailored for the [gang.name] Gang to offer the wearer moderate protection against ballistics and physical trauma."
+
+ user.put_in_hands(armor_box)
+ to_chat(user, " This is your gang's official uniform, wearing it will increase your influence")
+ return TRUE
/datum/gang_item/clothing/mask
name = "Golden Death Mask"
id = "mask"
- cost = 18
+ cost = 50
item_path = /obj/item/clothing/mask/gskull
/obj/item/clothing/mask/gskull
@@ -221,7 +206,7 @@
/datum/gang_item/clothing/shoes
name = "Bling Boots"
id = "boots"
- cost = 22
+ cost = 50
item_path = /obj/item/clothing/shoes/gang
/obj/item/clothing/shoes/gang
@@ -232,13 +217,13 @@
/datum/gang_item/clothing/neck
name = "Gold Necklace"
id = "necklace"
- cost = 9
+ cost = 25
item_path = /obj/item/clothing/neck/necklace/dope
/datum/gang_item/clothing/hands
name = "Decorative Brass Knuckles"
id = "hand"
- cost = 11
+ cost = 50
item_path = /obj/item/clothing/gloves/gang
/obj/item/clothing/gloves/gang
@@ -250,7 +235,7 @@
/datum/gang_item/clothing/belt
name = "Badass Belt"
id = "belt"
- cost = 13
+ cost = 25
item_path = /obj/item/storage/belt/military/gang
/obj/item/storage/belt/military/gang
@@ -266,115 +251,150 @@
/datum/gang_item/weapon
category = "Purchase Weapons:"
-/datum/gang_item/weapon/ammo
-
-/datum/gang_item/weapon/shuriken
- name = "Shuriken"
- id = "shuriken"
- cost = 3
- item_path = /obj/item/throwing_star
-
-/datum/gang_item/weapon/frag
- name = "Fragmentation Grenade"
- id = "frag nade"
- cost = 18
- item_path = /obj/item/grenade/syndieminibomb/concussion/frag
+/datum/gang_item/weapon/emp
+ name = "EMP Grenade"
+ id = "EMP"
+ cost = 50
+ item_path = /obj/item/grenade/empgrenade
+/datum/gang_item/weapon/c4
+ name = "C4 Explosive"
+ id = "c4"
+ cost = 100
+ item_path = /obj/item/grenade/plastic/c4
/datum/gang_item/weapon/switchblade
name = "Switchblade"
id = "switchblade"
- cost = 5
+ cost = 100
item_path = /obj/item/switchblade
-/datum/gang_item/weapon/surplus
- name = "Surplus Rifle"
- id = "surplus"
- cost = 8
- item_path = /obj/item/gun/ballistic/automatic/surplus
+/datum/gang_item/weapon/shuriken
+ name = "Shuriken box"
+ id = "shuriken"
+ cost = 150
+ item_path = /obj/item/storage/box/shuriken_box
-/datum/gang_item/weapon/ammo/surplus_ammo
- name = "Surplus Rifle Ammo"
- id = "surplus_ammo"
- cost = 5
- item_path = /obj/item/ammo_box/magazine/m10mm/rifle
+/obj/item/storage/box/shuriken_box
+ name = "shuriken Box"
-/datum/gang_item/weapon/ammo/improvised_ammo
- name = "Box of Buckshot"
- id = "buckshot"
- cost = 5
- item_path = /obj/item/storage/box/lethalshot
+/obj/item/storage/box/shuriken_box/PopulateContents()
+ for (var/i in 1 to 4)
+ new /obj/item/throwing_star(src)
/datum/gang_item/weapon/pistol
name = "10mm Pistol"
id = "pistol"
- cost = 30
+ cost = 500
item_path = /obj/item/gun/ballistic/automatic/pistol
-/datum/gang_item/weapon/ammo/pistol_ammo
+/datum/gang_item/weapon/pistol_ammo
name = "10mm Ammo"
id = "pistol_ammo"
- cost = 10
+ cost = 50
item_path = /obj/item/ammo_box/magazine/m10mm
/datum/gang_item/weapon/uzi
name = "Uzi SMG"
id = "uzi"
- cost = 60
+ cost = 500
item_path = /obj/item/gun/ballistic/automatic/mini_uzi
-/datum/gang_item/weapon/ammo/uzi_ammo
+/datum/gang_item/weapon/uzi_ammo
name = "Uzi Ammo"
id = "uzi_ammo"
- cost = 40
+ cost = 50
item_path = /obj/item/ammo_box/magazine/uzim9mm
+/datum/gang_item/weapon/laser
+ name = "Laser Gun"
+ id = "laser"
+ cost = 500
+ item_path = /obj/item/gun/energy/laser/retro
+
///////////////////
//EQUIPMENT
///////////////////
/datum/gang_item/equipment
- category = "Purchase Equipment:"
-
-
-/datum/gang_item/equipment/sharpener
- name = "Sharpener"
- id = "whetstone"
- cost = 3
- item_path = /obj/item/sharpener
+ category = "Purchase Support Equipment:"
-
-/datum/gang_item/equipment/emp
- name = "EMP Grenade"
- id = "EMP"
- cost = 5
- item_path = /obj/item/grenade/empgrenade
-
-/datum/gang_item/equipment/c4
- name = "C4 Explosive"
- id = "c4"
- cost = 7
- item_path = /obj/item/grenade/plastic/c4
-
-/datum/gang_item/equipment/implant_breaker
- name = "Implant Breaker"
- id = "implant_breaker"
- cost = 10
- item_path = /obj/item/implanter/gang
- spawn_msg = "The implant breaker is a single-use device that destroys all implants within the target before trying to recruit them to your gang. Also works on enemy gangsters."
-
-/datum/gang_item/equipment/implant_breaker/spawn_item(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
- var/obj/item/O = new item_path(user.loc, gang)
- user.put_in_hands(O)
-
-/datum/gang_item/equipment/wetwork_boots
- name = "Wetwork boots"
- id = "wetwork"
+/datum/gang_item/equipment/healcigs
+ name = "Healing Cigs"
+ id = "healcigs"
cost = 20
- item_path = /obj/item/clothing/shoes/combat/gang
+ item_path = /obj/item/storage/fancy/cigarettes/cigpack_syndicate
+
+/datum/gang_item/equipment/mulah
+ name = "Space Cash (1000cr)"
+ id = "mulah"
+ cost = 25
+ item_path = /obj/item/stack/spacecash/c1000
+
+/datum/gang_item/equipment/drugs
+ name = "Drug Supply"
+ id = "drugs"
+ cost = 50
+ item_path = /obj/item/storage/box
+
+/datum/gang_item/equipment/drugs/spawn_item(mob/living/carbon/user, datum/team/gang/gang, obj/item/device/gangtool/gangtool)
+ var/obj/item/O
+ var/turf/T = get_turf(user)
+ switch (rand(1,10))
+ if (1)
+ O = new /obj/item/storage/pill_bottle/lsd(T)
+ if (2)
+ O = new /obj/item/storage/pill_bottle/happy(T)
+ if (3)
+ O = new /obj/item/storage/pill_bottle/zoom(T)
+ if (4)
+ O = new /obj/item/storage/pill_bottle/aranesp(T)
+ if (5)
+ O = new /obj/item/storage/pill_bottle/happiness(T)
+ if (6)
+ O = new /obj/item/storage/pill_bottle/psicodine(T)
+ if (7)
+ O = new /obj/item/storage/pill_bottle/psicodine(T)
+ if (8)
+ O = new /obj/item/reagent_containers/food/snacks/grown/cannabis(T)
+ if (9)
+ O = new /obj/item/reagent_containers/food/snacks/grown/cannabis/rainbow(T)
+ if (10)
+ O = new /obj/item/reagent_containers/food/snacks/grown/cannabis/white(T)
+ if (O)
+ user.put_in_hands(O)
-/obj/item/clothing/shoes/combat/gang
- name = "Wetwork boots"
- desc = "A gang's best hitmen are prepared for anything."
- permeability_coefficient = 0.01
- clothing_flags = NOSLIP
\ No newline at end of file
+/datum/gang_item/equipment/aids
+ name = "Battlefield Aid Kit"
+ id = "aids"
+ cost = 75
+ item_path = /obj/item/storage/firstaid/shifty/battle
+
+/datum/gang_item/equipment/hangover
+ name = "Bad Trip Kit"
+ id = "hangover"
+ cost = 75
+ item_path = /obj/item/storage/firstaid/shifty/hangover
+
+/obj/item/storage/firstaid/shifty
+ name = "shifty medkit"
+ desc = "A shady medkit, assembled out of scraps and leftovers."
+ icon_state = "bezerk"
+
+/obj/item/storage/firstaid/shifty/battle/PopulateContents()
+ var/static/items_inside = list(
+ /obj/item/reagent_containers/pill/patch/silver_sulf = 2,
+ /obj/item/reagent_containers/pill/patch/styptic = 2,
+ /obj/item/reagent_containers/medspray/synthflesh = 1,
+ /obj/item/reagent_containers/hypospray/medipen = 1,
+ /obj/item/healthanalyzer = 1)
+ generate_items_inside(items_inside,src)
+
+/obj/item/storage/firstaid/shifty/hangover/PopulateContents()
+ var/static/items_inside = list(
+ /obj/item/storage/pill_bottle/charcoal = 1,
+ /obj/item/reagent_containers/syringe/antitoxin = 1,
+ /obj/item/reagent_containers/hypospray/medipen = 2,
+ /obj/item/reagent_containers/hypospray/medipen/dexalin = 2,
+ /obj/item/healthanalyzer = 1)
+ generate_items_inside(items_inside,src)
diff --git a/code/game/gamemodes/gangs/gang_pen.dm b/code/game/gamemodes/gangs/gang_pen.dm
index a307110ea8425..17dcc98a14d58 100644
--- a/code/game/gamemodes/gangs/gang_pen.dm
+++ b/code/game/gamemodes/gangs/gang_pen.dm
@@ -52,7 +52,7 @@
to_chat(user, "This mind is too strong to control!")
return
var/mob/living/carbon/human/H = gangster_mind.current // we are sure the dude's human cause it's checked in attack()
- if(istype(H.get_item_by_slot(SLOT_HEAD), /obj/item/clothing/head/foilhat))
+ if(istype(H.get_item_by_slot(ITEM_SLOT_HEAD), /obj/item/clothing/head/foilhat))
to_chat(user, "This mind appears to be too delusional to register the mind-control waves!")
return
H.silent = max(H.silent, 5)
diff --git a/code/game/gamemodes/gangs/gang_tags.dm b/code/game/gamemodes/gangs/gang_tags.dm
new file mode 100644
index 0000000000000..f0e2f7932cf19
--- /dev/null
+++ b/code/game/gamemodes/gangs/gang_tags.dm
@@ -0,0 +1,30 @@
+
+/obj/effect/decal/gang
+ icon = 'icons/effects/crayondecal.dmi'
+ icon_state = "rune1"
+ layer = ABOVE_NORMAL_TURF_LAYER //Harder to hide
+ plane = GAME_PLANE
+ gender = NEUTER
+ var/datum/team/gang/gang
+
+/obj/effect/decal/gang/Initialize(mapload, datum/team/gang/G, e_name = "gang tag", rotation = 0, mob/user)
+ if(!G)
+ qdel(src)
+ return
+ gang = G
+ var/area/territory = get_area(src)
+ G.new_territories |= list(territory.type = territory.name)
+
+ name = e_name
+ desc = "A [name] vandalizing the station."
+ icon_state = G.name
+ add_atom_colour(G.color, FIXED_COLOUR_PRIORITY)
+
+
+/obj/effect/decal/gang/Destroy()
+ if(gang)
+ var/area/territory = get_area(src)
+ gang.territories -= territory.type
+ gang.new_territories -= territory.type
+ gang.lost_territories |= list(territory.type = territory.name)
+ return ..()
diff --git a/code/game/gamemodes/gangs/gangs.dm b/code/game/gamemodes/gangs/gangs.dm
index d555aad6bef0e..b10af973eed58 100644
--- a/code/game/gamemodes/gangs/gangs.dm
+++ b/code/game/gamemodes/gangs/gangs.dm
@@ -6,16 +6,16 @@ GLOBAL_LIST_EMPTY(gangs)
name = "gang war"
config_tag = "gang"
antag_flag = ROLE_GANG
- restricted_jobs = list("Security Officer", "Warden", "Detective", "AI", "Cyborg","Captain", "Head of Personnel", "Head of Security", "Brig Physician")
- required_players = 15
- required_enemies = 1
- recommended_enemies = 2
+ restricted_jobs = list("Security Officer", "Warden", "Detective", "AI", "Cyborg","Captain", "Head of Personnel", "Head of Security")
+ required_players = 30
+ required_enemies = 2
+ recommended_enemies = 3
enemy_minimum_age = 14
announce_span = "danger"
announce_text = "A violent turf war has erupted on the station!\n\
- Gangsters: Take over the station with a dominator.\n\
- Crew: Prevent the gangs from expanding and initiating takeover."
+ Gangsters: Spread influence and expand the territory of your gang.\n\
+ Crew: Spread awareness and prevent your coworkers from killing eachother in turf wars."
title_icon = "gang"
@@ -30,10 +30,10 @@ GLOBAL_LIST_EMPTY(gangs)
//Spawn more bosses depending on server population
var/gangs_to_create = 2
- if(prob(num_players()) && num_players() > 1.5*required_players)
- gangs_to_create++
if(prob(num_players()) && num_players() > 2*required_players)
gangs_to_create++
+ if(prob(num_players()) && num_players() > 3*required_players)
+ gangs_to_create++
gangs_to_create = min(gangs_to_create, GLOB.possible_gangs.len)
for(var/i in 1 to gangs_to_create)
@@ -69,6 +69,19 @@ GLOBAL_LIST_EMPTY(gangs)
/proc/is_gang_boss(mob/M)
return M?.mind?.has_antag_datum(/datum/antagonist/gang/boss)
+/datum/game_mode/gang/set_round_result()
+ ..()
+ var/datum/team/gang/winner
+ var/winner_territories = 0
+ for(var/datum/team/gang/G in GLOB.gangs)
+ var/compare_territories = LAZYLEN(G.territories)
+ if (!winner || compare_territories > winner_territories || (compare_territories == winner_territories && G.victory_points > winner.victory_points))
+ winner = G
+ winner_territories = LAZYLEN(winner.territories)
+
+ if (winner)
+ winner.winner = TRUE //chicken dinner
+
/datum/game_mode/gang/generate_credit_text()
var/list/round_credits = list()
var/len_before_addition
diff --git a/code/game/gamemodes/gangs/gangtool.dm b/code/game/gamemodes/gangs/gangtool.dm
index 6bf3de09691b1..78b74c4381656 100644
--- a/code/game/gamemodes/gangs/gangtool.dm
+++ b/code/game/gamemodes/gangs/gangtool.dm
@@ -60,9 +60,6 @@
else
dat += "This device is not authorized to promote. "
else
- if(gang.domination_time != NOT_DOMINATING)
- dat += "
Takeover In Progress: [DisplayTimeText(gang.domination_time_remaining() * 10)] remain
"
-
dat += "Registration: [gang.name] Gang Boss "
dat += "Organization Size: [gang.members.len] | Station Control: [gang.territories.len] territories under control. | Influence: [gang.influence] "
dat += "Time until Influence grows: [time2text(gang.next_point_time - world.time, "mm:ss")] "
@@ -207,7 +204,7 @@
gang.recalls--
return TRUE
- to_chat(user, "[icon2html(src, loc)]No response recieved. Emergency shuttle cannot be recalled at this time.")
+ to_chat(user, "[icon2html(src, loc)]No response received. Emergency shuttle cannot be recalled at this time.")
return
/obj/item/device/gangtool/proc/recallchecks(mob/user)
@@ -222,10 +219,6 @@
to_chat(user, "[icon2html(src, user)]Emergency shuttle cannot be recalled at this time.")
recalling = FALSE
return
- if(!gang.dom_attempts)
- to_chat(user, "[icon2html(src, user)]Error: Unable to access communication arrays. Firewall has logged our signature and is blocking all further attempts.")
- recalling = FALSE
- return
if(!is_station_level(user.z)) //Shuttle can only be recalled while on station
to_chat(user, "[icon2html(src, user)]Error: Device out of range of station communication arrays.")
recalling = FALSE
@@ -248,6 +241,9 @@
if(!isnull(gang) && G.gang != gang)
to_chat(user, "You cannot use gang tools owned by enemy gangs!")
return
+ else if(!G.gang.check_gangster_swag(user)>1)
+ to_chat(user, "You cannot use gang tools while undercover!")
+ return
return TRUE
diff --git a/code/game/gamemodes/gangs/implant_gang.dm b/code/game/gamemodes/gangs/implant_gang.dm
new file mode 100644
index 0000000000000..7f0b190381a65
--- /dev/null
+++ b/code/game/gamemodes/gangs/implant_gang.dm
@@ -0,0 +1,63 @@
+/obj/item/implant/gang
+ name = "gang implant"
+ desc = "Makes you a gangster or such."
+ activated = 0
+ var/datum/team/gang/gang
+
+/obj/item/implant/gang/Initialize(loc, setgang)
+ ..()
+ gang = setgang
+
+/obj/item/implant/gang/get_data()
+ var/dat = {"Implant Specifications:
+ Name: Criminal brainwash implant
+ Life: A few seconds after injection.
+ Important Notes: Illegal
+
+ Implant Details:
+ Function: Contains a small pod of nanobots that change the host's brain to be loyal to a certain organization.
+ Notice: Latest NT Mindshield implants counteract the effect of this implant.
+ Integrity: Latest NT Mindshield will neutralize this implant."}
+ return dat
+
+/obj/item/implant/gang/implant(mob/living/target, mob/user, silent = 0)
+ if(!target || !target.mind || target.stat == DEAD || !ishuman(target) || !..())
+ return FALSE
+ if (HAS_TRAIT(target, TRAIT_MINDSHIELD))
+ target.visible_message("[target] seems to resist the implant!", "You resist the gang implant. You are reminded of the anti-gang PSA instead.")
+ return FALSE
+
+ var/datum/antagonist/gang/G = target.mind.has_antag_datum(/datum/antagonist/gang)
+ if(G)
+ if(G.gang == G || istype(G, /datum/antagonist/gang/boss))
+ return FALSE
+ target.mind.remove_antag_datum(/datum/antagonist/gang)
+ target.mind.add_antag_datum(/datum/antagonist/gang, gang)
+ qdel(src)
+ return TRUE
+
+/obj/item/implanter/gang
+ name = "implanter (gang)"
+
+/obj/item/implanter/gang/Initialize(loc, gang)
+ if(!gang)
+ qdel(src)
+ return
+ imp = new /obj/item/implant/gang(src,gang)
+ ..()
+
+
+
+/obj/item/implant/mindshield/implant(mob/living/target, mob/user, silent = FALSE) //putting this here, pls no bulli. - qwerty
+ if(..())
+ if(!target.mind)
+ return TRUE
+ if(target.mind.has_antag_datum(/datum/antagonist/gang/boss))
+ if(!silent)
+ target.visible_message("[target] seems to resist the implant!", "You feel something interfering with your mental conditioning, but you resist it!")
+ removed(target, 1)
+ qdel(src)
+ return FALSE
+ target.mind.remove_antag_datum(/datum/antagonist/gang)
+ return TRUE
+ return FALSE
diff --git a/code/game/gamemodes/gangs/outfits.dm b/code/game/gamemodes/gangs/outfits.dm
new file mode 100644
index 0000000000000..e62299deec6be
--- /dev/null
+++ b/code/game/gamemodes/gangs/outfits.dm
@@ -0,0 +1,32 @@
+/datum/outfit/crook
+ name = "Level 1 - Crook"
+ uniform = /obj/item/clothing/under/color/grey
+ id = /obj/item/card/id
+ ears = /obj/item/radio/headset
+ back = /obj/item/storage/backpack
+ shoes = /obj/item/clothing/shoes/sneakers/black
+
+ l_pocket = /obj/item/switchblade
+ r_pocket = /obj/item/crowbar/red
+
+ backpack_contents = list( /obj/item/storage/box/survival=1,\
+ /obj/item/toy/crayon/spraycan/gang = 1)
+
+/datum/outfit/boss
+ name = "Level 50 - Boss"
+ uniform = /obj/item/clothing/under/color/grey
+ id = /obj/item/card/id
+ belt = /obj/item/storage/belt/military/gang
+ gloves = /obj/item/clothing/gloves/gang
+ neck = /obj/item/clothing/neck/necklace/dope
+ shoes = /obj/item/clothing/shoes/gang
+ ears = /obj/item/radio/headset
+ back = /obj/item/storage/backpack
+ mask = /obj/item/clothing/mask/gskull
+
+ l_pocket = /obj/item/pen/gang
+ r_pocket = /obj/item/switchblade
+
+ backpack_contents = list( /obj/item/storage/box/survival=1,\
+ /obj/item/device/gangtool/spare=1,\
+ /obj/item/restraints/legcuffs/bola/energy = 1)
\ No newline at end of file
diff --git a/code/game/gamemodes/hivemind/radar.dm b/code/game/gamemodes/hivemind/radar.dm
index 8df195705785f..8baaa69774d95 100644
--- a/code/game/gamemodes/hivemind/radar.dm
+++ b/code/game/gamemodes/hivemind/radar.dm
@@ -5,7 +5,7 @@
//Modified IA/changeling pinpointer, points to the nearest person who is afflicted with the hive tracker status effect
/datum/status_effect/agent_pinpointer/hivemind
id = "hive_pinpointer"
- alert_type = /obj/screen/alert/status_effect/agent_pinpointer/hivemind
+ alert_type = /atom/movable/screen/alert/status_effect/agent_pinpointer/hivemind
minimum_range = HIVEMIND_RADAR_MIN_DISTANCE
tick_interval = HIVEMIND_RADAR_PING_TIME
range_fuzz_factor = 0
@@ -69,6 +69,6 @@
duration = world.time + set_duration
//Screen alert
-/obj/screen/alert/status_effect/agent_pinpointer/hivemind
+/atom/movable/screen/alert/status_effect/agent_pinpointer/hivemind
name = "Psychic link"
desc = "Somebody is there, and they're definitely not friendly."
diff --git a/code/game/gamemodes/incursion/incursion.dm b/code/game/gamemodes/incursion/incursion.dm
index 142d15c571e78..f85063b23cd49 100644
--- a/code/game/gamemodes/incursion/incursion.dm
+++ b/code/game/gamemodes/incursion/incursion.dm
@@ -6,7 +6,7 @@
name = "incursion"
config_tag = "incursion"
restricted_jobs = list("AI", "Cyborg")
- protected_jobs = list("Security Officer", "Warden", "Detective","Captain", "Head of Personnel", "Head of Security", "Chief Engineer", "Research Director", "Chief Medical Officer", "Brig Physician")
+ protected_jobs = list("Security Officer", "Warden", "Detective","Captain", "Head of Personnel", "Head of Security", "Chief Engineer", "Research Director", "Chief Medical Officer")
antag_flag = ROLE_INCURSION
false_report_weight = 10
enemy_minimum_age = 0
diff --git a/code/game/gamemodes/meteor/meteor.dm b/code/game/gamemodes/meteor/meteor.dm
index 81ca9a5c394c5..9ed3060b7165f 100644
--- a/code/game/gamemodes/meteor/meteor.dm
+++ b/code/game/gamemodes/meteor/meteor.dm
@@ -18,7 +18,7 @@
return
var/list/wavetype = GLOB.meteors_normal
- var/meteorminutes = (world.time - SSticker.round_start_time - meteordelay) / 10 / 60
+ var/meteorminutes = (world.time - SSticker.round_start_time - meteordelay) / 600
if (prob(meteorminutes))
diff --git a/code/game/gamemodes/meteor/meteors.dm b/code/game/gamemodes/meteor/meteors.dm
index 81175233ec210..c1fc5bb691f6f 100644
--- a/code/game/gamemodes/meteor/meteors.dm
+++ b/code/game/gamemodes/meteor/meteors.dm
@@ -21,17 +21,17 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
//Meteor spawning global procs
///////////////////////////////
-/proc/spawn_meteors(number = 10, list/meteortypes)
+/proc/spawn_meteors(number = 10, list/meteortypes, z = 0)
for(var/i = 0; i < number; i++)
- spawn_meteor(meteortypes)
+ spawn_meteor(meteortypes, z)
-/proc/spawn_meteor(list/meteortypes)
+/proc/spawn_meteor(list/meteortypes, z = 0)
var/turf/pickedstart
var/turf/pickedgoal
var/max_i = 10//number of tries to spawn meteor.
while(!isspaceturf(pickedstart))
var/startSide = pick(GLOB.cardinals)
- var/startZ = pick(SSmapping.levels_by_trait(ZTRAIT_STATION))
+ var/startZ = (z || pick(SSmapping.levels_by_trait(ZTRAIT_STATION)))
pickedstart = spaceDebrisStartLoc(startSide, startZ)
pickedgoal = spaceDebrisFinishLoc(startSide, startZ)
max_i--
@@ -121,6 +121,10 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
GLOB.meteor_list -= src
SSaugury.unregister_doom(src)
walk(src,0) //this cancels the walk_towards() proc
+ if(istype(loc, /obj/effect/falling_meteor))
+ var/obj/effect/falling_meteor/holder = loc
+ holder.contained_meteor = null
+ qdel(holder)
. = ..()
/obj/effect/meteor/Initialize(mapload, target)
@@ -140,11 +144,19 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
/obj/effect/meteor/proc/ram_turf(turf/T)
//first bust whatever is in the turf
- for(var/atom/A in T)
- if(A != src)
- if(isliving(A))
- A.visible_message("[src] slams into [A].", "[src] slams into you!.")
- A.ex_act(hitpwr)
+ for(var/thing in T)
+ if(thing == src)
+ continue
+ if(isliving(thing))
+ var/mob/living/living_thing = thing
+ living_thing.visible_message("[src] slams into [living_thing].", "[src] slams into you!.")
+ switch(hitpwr)
+ if(EXPLODE_DEVASTATE)
+ SSexplosions.high_mov_atom += thing
+ if(EXPLODE_HEAVY)
+ SSexplosions.med_mov_atom += thing
+ if(EXPLODE_LIGHT)
+ SSexplosions.low_mov_atom += thing
//then, ram the turf if it still exists
if(T)
@@ -195,7 +207,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
if((M.orbiting) && (SSaugury.watchers[M]))
continue
var/turf/T = get_turf(M)
- if(!T || T.z != src.z)
+ if(!T || T.get_virtual_z_level() != src.get_virtual_z_level())
continue
var/dist = get_dist(M.loc, src.loc)
shake_camera(M, dist > 20 ? 2 : 4, dist > 20 ? 1 : 3)
@@ -367,3 +379,68 @@ GLOBAL_LIST_INIT(meteorsSPOOKY, list(/obj/effect/meteor/pumpkin))
meteorsound = pick('sound/hallucinations/im_here1.ogg','sound/hallucinations/im_here2.ogg')
//////////////////////////
#undef DEFAULT_METEOR_LIFETIME
+
+//////////////////////////
+// Falling meteors
+/////////////////////////
+
+/obj/effect/falling_meteor
+ name = "falling meteor"
+ desc = "..."
+ alpha = 0
+ var/obj/effect/meteor/contained_meteor
+ var/obj/effect/meteor_shadow/shadow
+ var/falltime = 2 SECONDS
+ var/prefalltime = 8 SECONDS
+ layer = METEOR_LAYER
+
+/obj/effect/falling_meteor/Initialize(loc, meteor_type)
+ . = ..()
+ if(!meteor_type)
+ meteor_type = /obj/effect/meteor/big
+ contained_meteor = new meteor_type(src)
+ name = contained_meteor.name
+ desc = contained_meteor.desc
+ icon = contained_meteor.icon
+ icon_state = contained_meteor.icon_state
+ var/matrix/M = new()
+ M.Scale(3, 3)
+ M.Translate(-1.5 * world.icon_size, -1.5 * world.icon_size)
+ M.Translate(0, world.icon_size * 7)
+ transform = M
+ INVOKE_ASYNC(src, .proc/fall_animation)
+
+/obj/effect/falling_meteor/Destroy(force)
+ if(contained_meteor)
+ QDEL_NULL(contained_meteor)
+ QDEL_NULL(shadow)
+ . = ..()
+
+/obj/effect/falling_meteor/proc/fall_animation()
+ //Create a dummy effect
+ shadow = new(get_turf(src))
+ shadow.icon = icon
+ shadow.icon_state = icon_state
+ animate(shadow, time = (prefalltime + falltime), transform = matrix(), alpha = 255)
+ sleep(prefalltime)
+ animate(src, 5, alpha = 255)
+ animate(src, falltime, transform = matrix(), flags = ANIMATION_PARALLEL)
+ sleep(falltime)
+ contained_meteor.forceMove(loc)
+ contained_meteor.make_debris()
+ contained_meteor.meteor_effect()
+ qdel(src)
+
+/obj/effect/meteor_shadow
+ name = "shadow"
+ desc = "What the hell? Is something falling out the sky???"
+ alpha = 0
+ layer = METEOR_SHADOW_LAYER
+
+/obj/effect/meteor_shadow/Initialize()
+ . = ..()
+ color = list(0, 0, 0, 0, 0, 0, 0, 0, 0)
+ var/matrix/M = matrix()
+ M.Scale(3, 3)
+ M.Translate(-1.5 * world.icon_size, -1.5 * world.icon_size)
+ transform = M
diff --git a/code/game/gamemodes/nuclear/nuclear.dm b/code/game/gamemodes/nuclear/nuclear.dm
index ae38d58baa10e..0e0e35d975432 100644
--- a/code/game/gamemodes/nuclear/nuclear.dm
+++ b/code/game/gamemodes/nuclear/nuclear.dm
@@ -122,7 +122,7 @@
gloves = /obj/item/clothing/gloves/combat
back = /obj/item/storage/backpack/fireproof
ears = /obj/item/radio/headset/syndicate/alt
- l_pocket = /obj/item/pinpointer/nuke/syndicate
+ l_pocket = /obj/item/modular_computer/tablet/nukeops
id = /obj/item/card/id/syndicate
belt = /obj/item/gun/ballistic/automatic/pistol
backpack_contents = list(/obj/item/storage/box/syndie=1,\
@@ -153,7 +153,7 @@
if(ispath(uplink_type, /obj/item/uplink/nuclear) || tc) // /obj/item/uplink/nuclear understands 0 tc
var/obj/item/U = new uplink_type(H, H.key, tc)
- H.equip_to_slot_or_del(U, SLOT_IN_BACKPACK)
+ H.equip_to_slot_or_del(U, ITEM_SLOT_BACKPACK)
var/obj/item/implant/explosive/E = new/obj/item/implant/explosive(H)
E.implant(H)
@@ -169,7 +169,7 @@
mask = /obj/item/clothing/mask/gas/syndicate
suit = /obj/item/clothing/suit/space/hardsuit/syndi
r_pocket = /obj/item/tank/internals/emergency_oxygen/engi
- internals_slot = SLOT_R_STORE
+ internals_slot = ITEM_SLOT_RPOCKET
belt = /obj/item/storage/belt/military
r_hand = /obj/item/gun/ballistic/shotgun/bulldog
backpack_contents = list(/obj/item/storage/box/syndie=1,\
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index 5bcd23eecf0a2..886a83d8b6523 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -17,6 +17,11 @@ GLOBAL_LIST_EMPTY(objectives)
if(text)
explanation_text = text
+//Apparently objectives can be qdel'd. Learn a new thing every day
+/datum/objective/Destroy()
+ GLOB.objectives -= src
+ return ..()
+
/datum/objective/proc/get_owners() // Combine owner and team into a single list.
. = (team && team.members) ? team.members.Copy() : list()
if(owner)
@@ -27,7 +32,7 @@ GLOBAL_LIST_EMPTY(objectives)
//Shared by few objective types
/datum/objective/proc/admin_simple_target_pick(mob/admin)
- var/list/possible_targets = list("Free objective","Random")
+ var/list/possible_targets = list()
var/def_value
for(var/datum/mind/possible_target in SSticker.minds)
if ((possible_target != src) && ishuman(possible_target.current))
@@ -37,7 +42,7 @@ GLOBAL_LIST_EMPTY(objectives)
if(target?.current)
def_value = target.current
- var/mob/new_target = input(admin,"Select target:", "Objective target", def_value) as null|anything in sortNames(possible_targets)
+ var/mob/new_target = input(admin,"Select target:", "Objective target", def_value) as null|anything in (sortNames(possible_targets) | list("Free objective","Random"))
if (!new_target)
return
@@ -104,16 +109,50 @@ GLOBAL_LIST_EMPTY(objectives)
var/list/datum/mind/owners = get_owners()
if(!dupe_search_range)
dupe_search_range = get_owners()
+ var/list/prefered_targets = list()
var/list/possible_targets = list()
var/try_target_late_joiners = FALSE
+ var/owner_is_exploration_crew = FALSE
+ var/owner_is_shaft_miner = FALSE
for(var/I in owners)
var/datum/mind/O = I
if(O.late_joiner)
try_target_late_joiners = TRUE
+ if(O.assigned_role == "Exploration Crew")
+ owner_is_exploration_crew = TRUE
+ if(O.assigned_role == "Shaft Miner")
+ owner_is_shaft_miner = TRUE
for(var/datum/mind/possible_target in get_crewmember_minds())
- if(!(possible_target in owners) && ishuman(possible_target.current) && (possible_target.current.stat != DEAD) && is_unique_objective(possible_target,dupe_search_range))
- if (!(possible_target in blacklist))
- possible_targets += possible_target
+ if(possible_target in owners)
+ continue
+ if(!ishuman(possible_target.current))
+ continue
+ if(possible_target.current.stat == DEAD)
+ continue
+ if(!is_unique_objective(possible_target,dupe_search_range))
+ continue
+ var/target_area = get_area(possible_target.current)
+ if(!HAS_TRAIT(SSstation, STATION_TRAIT_LATE_ARRIVALS) && istype(target_area, /area/shuttle/arrival))
+ continue
+ if(possible_target in blacklist)
+ continue
+
+ if(possible_target.assigned_role == "Exploration Crew")
+ if(owner_is_exploration_crew)
+ prefered_targets += possible_target
+ else
+ //Reduced chance to get people off station
+ if(prob(70) && !owner_is_shaft_miner)
+ continue
+ else if(possible_target.assigned_role == "Shaft Miner")
+ if(owner_is_shaft_miner)
+ prefered_targets += possible_target
+ else
+ //Reduced chance to get people off station
+ if(prob(70) && !owner_is_exploration_crew)
+ continue
+
+ possible_targets += possible_target
if(try_target_late_joiners)
var/list/all_possible_targets = possible_targets.Copy()
for(var/I in all_possible_targets)
@@ -122,7 +161,11 @@ GLOBAL_LIST_EMPTY(objectives)
possible_targets -= PT
if(!possible_targets.len)
possible_targets = all_possible_targets
- if(possible_targets.len > 0)
+ //30% chance to go for a prefered target
+ if(prefered_targets.len > 0 && prob(30))
+ target = pick(prefered_targets)
+ target.isAntagTarget = TRUE
+ else if(possible_targets.len > 0)
target = pick(possible_targets)
target.isAntagTarget = TRUE
update_explanation_text()
@@ -160,9 +203,13 @@ GLOBAL_LIST_EMPTY(objectives)
if(receiver && receiver.current)
if(ishuman(receiver.current))
var/mob/living/carbon/human/H = receiver.current
- var/list/slots = list("backpack" = SLOT_IN_BACKPACK)
+ var/static/list/slots = list(
+ "backpack" = ITEM_SLOT_BACKPACK,
+ "left pocket" = ITEM_SLOT_LPOCKET,
+ "right pocket" = ITEM_SLOT_RPOCKET,
+ "hands" = ITEM_SLOT_HANDS)
for(var/eq_path in special_equipment)
- var/obj/O = new eq_path
+ var/obj/O = new eq_path(get_turf(receiver.current))
H.equip_in_one_of_slots(O, slots)
/datum/objective/assassinate
@@ -320,9 +367,11 @@ GLOBAL_LIST_EMPTY(objectives)
/datum/objective/hijack
name = "hijack"
- explanation_text = "Hijack the shuttle to ensure no loyalist Nanotrasen crew escape alive and out of custody."
- team_explanation_text = "Hijack the shuttle to ensure no loyalist Nanotrasen crew escape alive and out of custody. Leave no team member behind."
- martyr_compatible = 0 //Technically you won't get both anyway.
+ explanation_text = "Hijack the emergency shuttle by overriding the navigation protocols using the shuttle computer."
+ team_explanation_text = "Hijack the emergency shuttle by overriding the navigation protocols, using the shuttle computer. Leave no team member behind."
+ martyr_compatible = FALSE //Technically you won't get both anyway.
+ /// Overrides the hijack speed of any antagonist datum it is on ONLY, no other datums are impacted.
+ var/hijack_speed_override = 1
/datum/objective/hijack/check_completion() // Requires all owners to escape.
if(SSshuttle.emergency.mode != SHUTTLE_ENDGAME)
@@ -333,24 +382,33 @@ GLOBAL_LIST_EMPTY(objectives)
return FALSE
return SSshuttle.emergency.is_hijacked()
-/datum/objective/hijack/single
- name = "hijack"
- explanation_text = "Hijack the shuttle to ensure no loyalist Nanotrasen crew escape alive and out of custody."
- team_explanation_text = "Hijack the shuttle to ensure no loyalist Nanotrasen crew escape alive and out of custody. Team members lost is not a concern for this operation."
- martyr_compatible = 0 //Technically you won't get both anyway.
+/datum/objective/elimination
+ name = "elimination"
+ explanation_text = "Slaughter all loyalist crew aboard the shuttle. You, and any likeminded individuals, must be the only remaining people on the shuttle."
+ team_explanation_text = "Slaughter all loyalist crew aboard the shuttle. You, and any likeminded individuals, must be the only remaining people on the shuttle. Leave no team member behind."
+ martyr_compatible = FALSE
-/datum/objective/hijack/single/check_completion() // Requires all owners to escape.
+/datum/objective/elimination/check_completion()
if(SSshuttle.emergency.mode != SHUTTLE_ENDGAME)
return FALSE
var/list/datum/mind/owners = get_owners()
- var/single_escape = FALSE
for(var/datum/mind/M in owners)
- if(considered_alive(M) && SSshuttle.emergency.shuttle_areas[get_area(M.current)])
- single_escape = TRUE
- break
- if(!single_escape)
+ if(!considered_alive(M, enforce_human = FALSE) || !SSshuttle.emergency.shuttle_areas[get_area(M.current)])
+ return FALSE
+ return SSshuttle.emergency.elimination_hijack()
+
+/datum/objective/elimination/highlander
+ name="highlander elimination"
+ explanation_text = "Escape on the shuttle alone. Ensure that nobody else makes it out."
+
+/datum/objective/elimination/highlander/check_completion()
+ if(SSshuttle.emergency.mode != SHUTTLE_ENDGAME)
return FALSE
- return SSshuttle.emergency.is_hijacked()
+ var/list/datum/mind/owners = get_owners()
+ for(var/datum/mind/M in owners)
+ if(!considered_alive(M, enforce_human = FALSE) || !SSshuttle.emergency.shuttle_areas[get_area(M.current)])
+ return FALSE
+ return SSshuttle.emergency.elimination_hijack(filter_by_human = FALSE, solo_hijack = TRUE)
/datum/objective/block
name = "no organics on shuttle"
@@ -818,7 +876,14 @@ GLOBAL_LIST_EMPTY(possible_items_special)
return FALSE
return TRUE
+//Teratoma objective
+
+/datum/objective/chaos
+ name = "spread chaos"
+ explanation_text = "Spread misery and chaos upon the station."
+/datum/objective/chaos/check_completion()
+ return TRUE
//End Changeling Objectives
/datum/objective/destroy
diff --git a/code/game/gamemodes/objective_items.dm b/code/game/gamemodes/objective_items.dm
index 2bd194af3e37f..73f380cdfb0db 100644
--- a/code/game/gamemodes/objective_items.dm
+++ b/code/game/gamemodes/objective_items.dm
@@ -84,7 +84,7 @@
/datum/objective_item/steal/reactive
name = "the reactive teleport armor."
- targetitem = /obj/item/clothing/suit/armor/reactive
+ targetitem = /obj/item/clothing/suit/armor/reactive/teleport
difficulty = 5
excludefromjob = list("Research Director")
@@ -116,7 +116,7 @@
//Items with special checks!
/datum/objective_item/steal/plasma
- name = "28 moles of plasma (full tank)."
+ name = "28 moles of plasma (full tank). Be sure to fill up the tank with additional plasma since it doesn't start full!"
targetitem = /obj/item/tank
difficulty = 3
excludefromjob = list("Chief Engineer","Research Director","Station Engineer","Scientist","Atmospheric Technician")
@@ -124,7 +124,7 @@
/datum/objective_item/steal/plasma/check_special_completion(obj/item/tank/T)
var/target_amount = text2num(name)
var/found_amount = 0
- found_amount += T.air_contents.get_moles(/datum/gas/plasma)
+ found_amount += T.air_contents.get_moles(GAS_PLASMA)
return found_amount>=target_amount
@@ -166,6 +166,12 @@
return 1
return 0
+/datum/objective_item/steal/blackbox
+ name = "the blackbox."
+ targetitem = /obj/item/blackbox
+ difficulty = 10
+ excludefromjob = list("Chief Engineer","Station Engineer","Atmospheric Technician")
+
//Unique Objectives
/datum/objective_item/unique/docs_red
name = "the \"Red\" secret documents."
diff --git a/code/game/gamemodes/overthrow/overthrow.dm b/code/game/gamemodes/overthrow/overthrow.dm
index 4ab67be0925dc..042bdb4f55634 100644
--- a/code/game/gamemodes/overthrow/overthrow.dm
+++ b/code/game/gamemodes/overthrow/overthrow.dm
@@ -4,7 +4,7 @@
config_tag = "overthrow"
report_type = "overthrow"
antag_flag = ROLE_OVERTHROW
- restricted_jobs = list("Security Officer", "Warden", "Detective", "AI", "Cyborg","Captain", "Head of Personnel", "Head of Security", "Chief Engineer", "Research Director", "Chief Medical Officer", "Brig Physician")
+ restricted_jobs = list("Security Officer", "Warden", "Detective", "AI", "Cyborg","Captain", "Head of Personnel", "Head of Security", "Chief Engineer", "Research Director", "Chief Medical Officer")
required_players = 20 // the core idea is of a swift, bloodless coup, so it shouldn't be as chaotic as revs.
required_enemies = 2 // minimum two teams, otherwise it's just nerfed revs.
recommended_enemies = 4
diff --git a/code/game/gamemodes/revolution/revolution.dm b/code/game/gamemodes/revolution/revolution.dm
index 7961eb835e776..e5cf04998785c 100644
--- a/code/game/gamemodes/revolution/revolution.dm
+++ b/code/game/gamemodes/revolution/revolution.dm
@@ -13,7 +13,8 @@
report_type = "revolution"
antag_flag = ROLE_REV
false_report_weight = 10
- restricted_jobs = list("Security Officer", "Warden", "Detective", "AI", "Cyborg","Captain", "Head of Personnel", "Head of Security", "Chief Engineer", "Research Director", "Chief Medical Officer", "Brig Physician")
+ restricted_jobs = list("Security Officer", "Warden", "Detective", "AI", "Cyborg","Captain", "Head of Personnel", "Head of Security", "Chief Engineer", "Research Director", "Chief Medical Officer")
+ required_jobs = list(list("Captain"=1),list("Head of Personnel"=1),list("Head of Security"=1),list("Chief Engineer"=1),list("Research Director"=1),list("Chief Medical Officer"=1)) //Any head present
required_players = 30
required_enemies = 2
recommended_enemies = 3
@@ -118,7 +119,6 @@
if(!finished)
SSticker.mode.check_win()
check_counter = 0
- return FALSE
//////////////////////////////////////
//Checks if the revs have won or not//
@@ -128,7 +128,6 @@
finished = 1
else if(check_heads_victory())
finished = 2
- return
///////////////////////////////
//Checks if the round is over//
@@ -138,7 +137,7 @@
if(finished)
SSshuttle.clearHostileEnvironment(src)
return ..()
- if(finished != 0 && end_when_heads_dead)
+ if(finished && end_when_heads_dead)
return TRUE
else
return ..()
@@ -211,7 +210,7 @@
return ..()
/datum/game_mode/revolution/speedy/process()
- . = ..()
+ ..()
if(check_counter == 0)
if (world.time > endtime && !fuckingdone)
fuckingdone = TRUE
diff --git a/code/game/gamemodes/sandbox/airlock_maker.dm b/code/game/gamemodes/sandbox/airlock_maker.dm
deleted file mode 100644
index ddb622ab08c54..0000000000000
--- a/code/game/gamemodes/sandbox/airlock_maker.dm
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- This is for the sandbox for now,
- maybe useful later for an actual thing?
- -Sayu
-*/
-
-/obj/structure/door_assembly
- var/datum/airlock_maker/maker = null
-
-/obj/structure/door_assembly/attack_hand()
- . = ..()
- if(.)
- return
- if(maker)
- maker.interact()
-
-/datum/airlock_maker
- var/obj/structure/door_assembly/linked = null
-
- var/list/access_used = null
- var/require_all = 1
-
- var/paintjob = "none"
- var/glassdoor = 0
-
- var/doorname = "airlock"
-
-/datum/airlock_maker/New(var/atom/target_loc)
- linked = new(target_loc)
- linked.maker = src
- linked.anchored = FALSE
- access_used = list()
-
- interact()
-
-/datum/airlock_maker/proc/linkpretty(href,desc,active)
- if(!desc)
- var/static/list/defaults = list("No","Yes")
- desc = defaults[active+1]
- if(active)
- return "[desc]"
- return "[desc]"
-
-/datum/airlock_maker/proc/interact()
- var/list/leftcolumn = list()
- var/list/rightcolumn = list()
- leftcolumn += "Required Access"
- for(var/access in get_all_accesses())
- leftcolumn += linkpretty("access=[access]",get_access_desc(access),access in access_used)
- leftcolumn += "Require all listed accesses: [linkpretty("reqall",null,require_all)]"
-
- rightcolumn += "Paintjob"
- for(var/option in list("none","engineering","atmos","security","command","medical","research","mining","maintenance","external","highsecurity"))
- rightcolumn += linkpretty("paint=[option]",option,option == paintjob)
- rightcolumn += "Glass door: " + linkpretty("glass",null,glassdoor) + "
"
- var/length = max(leftcolumn.len,rightcolumn.len)
-
- var/dat = "You may move the model airlock around. A new airlock will be built in its space when you click done, below. "
- dat += "Door name: \"[doorname]\""
- dat += "
"
- for(var/i=1; i<=length; i++)
- dat += "
"
- if(i<=leftcolumn.len)
- dat += leftcolumn[i]
- dat += "
"
- if(i<=rightcolumn.len)
- dat += rightcolumn[i]
- dat += "
"
-
- dat += "
Finalize Airlock Construction | Cancel and Destroy Airlock"
- usr << browse(dat,"window=airlockmaker")
-
-/datum/airlock_maker/Topic(var/href,var/list/href_list)
- if(!usr)
- return
- if(!src || !linked || !linked.loc)
- usr << browse(null,"window=airlockmaker")
- return
-
- if("rename" in href_list)
- var/newname = stripped_input(usr,"New airlock name:","Name the airlock",doorname)
- if(newname)
- doorname = newname
- if("access" in href_list)
- var/value = text2num(href_list["access"])
- access_used ^= value
- if("reqall" in href_list)
- require_all = !require_all
- if("paint" in href_list)
- paintjob = href_list["paint"]
- if("glass" in href_list)
- glassdoor = !glassdoor
-
- if("cancel" in href_list)
- usr << browse(null,"window=airlockmaker")
- qdel(linked)
- qdel(src)
- return
-
- if("done" in href_list)
- usr << browse(null,"window=airlockmaker")
- var/turf/t_loc = linked.loc
- qdel(linked)
- if(!istype(t_loc))
- return
-
- var/target_type = "/obj/machinery/door/airlock"
- if(glassdoor)
- if(paintjob != "none")
- if(paintjob in list("external","highsecurity","maintenance")) // no glass version
- target_type += "/[paintjob]"
- else
- target_type += "/glass_[paintjob]"
- else
- target_type += "/glass"
- else if(paintjob != "none")
- target_type += "/[paintjob]"
- var/final = target_type
- target_type = text2path(final)
- if(!target_type)
- to_chat(usr, "Didn't work, contact Sayu with this: [final]")
- usr << browse(null,"window=airlockmaker")
- return
-
- var/obj/machinery/door/D = new target_type(t_loc)
-
- D.name = doorname
-
- if(access_used.len == 0)
- D.req_access = null
- D.req_one_access = null
- else if(require_all)
- D.req_access = access_used.Copy()
- D.req_one_access = null
- else
- D.req_access = null
- D.req_one_access = access_used.Copy()
-
- return
-
- interact()
diff --git a/code/game/gamemodes/sandbox/h_sandbox.dm b/code/game/gamemodes/sandbox/h_sandbox.dm
index 609373b597e8a..c00b862abf12b 100644
--- a/code/game/gamemodes/sandbox/h_sandbox.dm
+++ b/code/game/gamemodes/sandbox/h_sandbox.dm
@@ -9,9 +9,11 @@ GLOBAL_VAR_INIT(hsboxspawn, TRUE)
sandbox.owner = src.ckey
if(src.client.holder)
sandbox.admin = 1
- verbs += new/mob/proc/sandbox_panel
+
+ add_verb(/mob/proc/sandbox_panel)
/mob/proc/sandbox_panel()
set name = "Sandbox Panel"
+ set category = "Sandbox"
if(sandbox)
sandbox.update()
@@ -41,6 +43,8 @@ GLOBAL_VAR_INIT(hsboxspawn, TRUE)
"Standard Tools",
"Spawn Flashlight" = "hsbspawn&path=[/obj/item/flashlight]",
"Spawn Toolbox" = "hsbspawn&path=[/obj/item/storage/toolbox/mechanical]",
+ "Spawn Tier 4 BSRPED" = "hsbspawn&path=[/obj/item/storage/part_replacer/bluespace/tier4]",
+ "Spawn Toolbelt" = "hsbspawn&path=[/obj/item/storage/belt/utility/chief/full]",
"Spawn Light Replacer" = "hsbspawn&path=[/obj/item/lightreplacer]",
"Spawn Medical Kit" = "hsbspawn&path=[/obj/item/storage/firstaid/regular]",
"Spawn All-Access ID" = "hsbaaid",
@@ -56,11 +60,11 @@ GLOBAL_VAR_INIT(hsboxspawn, TRUE)
"Spawn Inf. Capacity Power Cell" = "hsbspawn&path=[/obj/item/stock_parts/cell/infinite]",
"Spawn Rapid Construction Device" = "hsbrcd",
"Spawn RCD Ammo" = "hsb_safespawn&path=[/obj/item/rcd_ammo]",
- "Spawn Airlock" = "hsbairlock",
"Miscellaneous",
"Spawn Air Scrubber" = "hsbscrubber",
- "Spawn Welding Fuel Tank" = "hsbspawn&path=[/obj/structure/reagent_dispensers/fueltank]",
+ "Spawn Debug Tech Disk" = "hsbspawn&path=[/obj/item/disk/tech_disk/debug]",
+ "Spawn All Materials" = "hsbspawn&path=[/obj/structure/closet/syndicate/resources/everything]",
"Spawn Water Tank" = "hsbspawn&path=[/obj/structure/reagent_dispensers/watertank]",
"Bots",
@@ -220,12 +224,6 @@ GLOBAL_VAR_INIT(hsboxspawn, TRUE)
new/obj/item/construction/rcd/combat(usr.loc)
- //
- // New sandbox airlock maker
- //
- if("hsbairlock")
- new /datum/airlock_maker(usr.loc)
-
//
// Object spawn window
//
diff --git a/code/game/gamemodes/sandbox/sandbox.dm b/code/game/gamemodes/sandbox/sandbox.dm
index 8d4846d579e38..afc5e2b1f344d 100644
--- a/code/game/gamemodes/sandbox/sandbox.dm
+++ b/code/game/gamemodes/sandbox/sandbox.dm
@@ -6,10 +6,13 @@
announce_span = "info"
announce_text = "Build your own station... or just shoot each other!"
-
+
allow_persistence_save = FALSE
/datum/game_mode/sandbox/pre_setup()
+
+ CONFIG_SET(flag/allow_random_events, FALSE) //stops harmful events
+
for(var/mob/M in GLOB.player_list)
M.CanBuild()
return 1
diff --git a/code/game/gamemodes/traitor/traitor.dm b/code/game/gamemodes/traitor/traitor.dm
index 69dd3f033dab3..96a677682309b 100644
--- a/code/game/gamemodes/traitor/traitor.dm
+++ b/code/game/gamemodes/traitor/traitor.dm
@@ -12,7 +12,7 @@
antag_flag = ROLE_TRAITOR
false_report_weight = 20 //Reports of traitors are pretty common.
restricted_jobs = list("Cyborg")//They are part of the AI if he is traitor so are they, they use to get double chances
- protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Brig Physician")
+ protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain")
required_players = 0
required_enemies = 1
recommended_enemies = 4
diff --git a/code/game/machinery/Beacon.dm b/code/game/machinery/Beacon.dm
index 8d849c8c145e5..05b6e0d7baa86 100644
--- a/code/game/machinery/Beacon.dm
+++ b/code/game/machinery/Beacon.dm
@@ -29,20 +29,15 @@
// update the icon_state
/obj/machinery/bluespace_beacon/proc/updateicon()
- var/state="floor_beacon"
-
if(invisibility)
- icon_state = "[state]f"
-
+ icon_state = "floor_beaconf"
else
- icon_state = "[state]"
+ icon_state = "floor_beacon"
/obj/machinery/bluespace_beacon/process()
- if(!Beacon)
+ if(QDELETED(Beacon)) //Don't move it out of nullspace BACK INTO THE GAME for the love of god
var/turf/T = loc
Beacon = new(T)
Beacon.invisibility = INVISIBILITY_MAXIMUM
else if (Beacon.loc != loc)
Beacon.forceMove(loc)
-
- updateicon()
diff --git a/code/game/machinery/Sleeper.dm b/code/game/machinery/Sleeper.dm
index a11cccb4aeda4..1a5138c5f6797 100644
--- a/code/game/machinery/Sleeper.dm
+++ b/code/game/machinery/Sleeper.dm
@@ -48,6 +48,7 @@
for(var/i in 1 to I)
available_chems |= possible_chems[i]
reset_chem_buttons()
+ ui_update()
/obj/machinery/sleeper/update_icon()
if(state_open)
@@ -61,6 +62,7 @@
open_machine()
/obj/machinery/sleeper/Exited(atom/movable/user)
+ . = ..()
if (!state_open && user == occupant)
container_resist(user)
@@ -68,6 +70,7 @@
if (!state_open)
container_resist(user)
+//Note: open_machine and close_machine already ui_update()
/obj/machinery/sleeper/open_machine()
if(!state_open && !panel_open)
flick("[initial(icon_state)]-anim", src)
@@ -129,6 +132,12 @@
open_machine()
+/obj/machinery/sleeper/ui_requires_update(mob/user, datum/tgui/ui)
+ . = ..()
+
+ if(occupant)
+ . = TRUE // Only autoupdate when occupied
+
/obj/machinery/sleeper/ui_state(mob/user)
if(controls_inside)
return GLOB.default_state
@@ -233,6 +242,7 @@
occupant.reagents.add_reagent(chem_buttons[chem], 10) //emag effect kicks in here so that the "intended" chem is used for all checks, for extra FUUU
if(user)
log_combat(user, occupant, "injected [chem] into", addition = "via [src]")
+ use_power(100)
return TRUE
/obj/machinery/sleeper/proc/chem_allowed(chem)
@@ -260,16 +270,8 @@
icon_state = "sleeper_s"
controls_inside = TRUE
-/obj/machinery/sleeper/syndie/fullupgrade/Initialize()
- . = ..()
- component_parts = list()
- component_parts += new /obj/item/circuitboard/machine/sleeper(null)
- component_parts += new /obj/item/stock_parts/matter_bin/bluespace(null)
- component_parts += new /obj/item/stock_parts/manipulator/femto(null)
- component_parts += new /obj/item/stack/sheet/glass(null)
- component_parts += new /obj/item/stack/sheet/glass(null)
- component_parts += new /obj/item/stack/cable_coil(null)
- RefreshParts()
+/obj/machinery/sleeper/syndie/fullupgrade
+ circuit = /obj/item/circuitboard/machine/sleeper/fullupgrade
/obj/machinery/sleeper/clockwork
name = "soothing sleeper"
diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm
index 0f46d648920bb..17ac11c6acaaa 100644
--- a/code/game/machinery/_machinery.dm
+++ b/code/game/machinery/_machinery.dm
@@ -92,6 +92,8 @@ Class Procs:
pressure_resistance = 15
max_integrity = 200
layer = BELOW_OBJ_LAYER //keeps shit coming out of the machine from ending up underneath it.
+ flags_ricochet = RICOCHET_HARD
+ ricochet_chance_mod = 0.3
anchored = TRUE
interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT
@@ -113,7 +115,10 @@ Class Procs:
var/critical_machine = FALSE //If this machine is critical to station operation and should have the area be excempted from power failures.
var/list/occupant_typecache //if set, turned into typecache in Initialize, other wise, defaults to mob/living typecache
var/atom/movable/occupant = null
- var/speed_process = FALSE // Process as fast as possible?
+ /// Viable flags to go here are START_PROCESSING_ON_INIT, or START_PROCESSING_MANUALLY. See code\__DEFINES\machines.dm for more information on these flags.
+ var/processing_flags = START_PROCESSING_ON_INIT
+ /// What subsystem this machine will use, which is generally SSmachines or SSfastprocess. By default all machinery use SSmachines. This fires a machine's process() roughly every 2 seconds.
+ var/subsystem_type = /datum/controller/subsystem/machines
var/obj/item/circuitboard/circuit // Circuit to be created and inserted when the machinery is created
var/damage_deflection = 0
@@ -128,7 +133,7 @@ Class Procs:
/obj/machinery/Initialize()
if(!armor)
- armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 70)
+ armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 70, "stamina" = 0)
. = ..()
GLOB.machines += src
@@ -136,22 +141,29 @@ Class Procs:
circuit = new circuit
circuit.apply_default_parts(src)
- if(!speed_process)
- START_PROCESSING(SSmachines, src)
- else
- START_PROCESSING(SSfastprocess, src)
+ if(processing_flags & START_PROCESSING_ON_INIT)
+ begin_processing()
+
power_change()
RegisterSignal(src, COMSIG_ENTER_AREA, .proc/power_change)
- if (occupant_typecache)
+ if(occupant_typecache)
occupant_typecache = typecacheof(occupant_typecache)
+/// Helper proc for telling a machine to start processing with the subsystem type that is located in its `subsystem_type` var.
+/obj/machinery/proc/begin_processing()
+ var/datum/controller/subsystem/processing/subsystem = locate(subsystem_type) in Master.subsystems
+ START_PROCESSING(subsystem, src)
+
+/// Helper proc for telling a machine to stop processing with the subsystem type that is located in its `subsystem_type` var.
+/obj/machinery/proc/end_processing()
+ var/datum/controller/subsystem/processing/subsystem = locate(subsystem_type) in Master.subsystems
+ STOP_PROCESSING(subsystem, src)
+
/obj/machinery/Destroy()
GLOB.machines.Remove(src)
- if(!speed_process)
- STOP_PROCESSING(SSmachines, src)
- else
- STOP_PROCESSING(SSfastprocess, src)
+ if(datum_flags & DF_ISPROCESSING) // A sizeable portion of machines stops processing before qdel
+ end_processing()
dropContents()
if(length(component_parts))
for(var/atom/A in component_parts)
@@ -162,9 +174,6 @@ Class Procs:
/obj/machinery/proc/locate_machinery()
return
-/obj/machinery/process()//If you dont use process or power why are you here
- return PROCESS_KILL
-
/obj/machinery/proc/process_atmos()//If you dont use process why are you here
return PROCESS_KILL
@@ -175,12 +184,14 @@ Class Procs:
new /obj/effect/temp_visual/emp(loc)
/obj/machinery/proc/open_machine(drop = TRUE)
+ SEND_SIGNAL(src, COMSIG_MACHINE_OPEN, drop)
state_open = TRUE
density = FALSE
if(drop)
dropContents()
update_icon()
updateUsrDialog()
+ ui_update()
/obj/machinery/proc/dropContents(list/subset = null)
var/turf/T = get_turf(src)
@@ -197,6 +208,7 @@ Class Procs:
return occupant_typecache ? is_type_in_typecache(am, occupant_typecache) : isliving(am)
/obj/machinery/proc/close_machine(atom/movable/target = null)
+ SEND_SIGNAL(src, COMSIG_MACHINE_CLOSE, target)
state_open = FALSE
density = TRUE
if(!target)
@@ -218,6 +230,7 @@ Class Procs:
target.forceMove(src)
updateUsrDialog()
update_icon()
+ ui_update()
/obj/machinery/proc/auto_use_power()
if(!powered(power_channel))
@@ -311,11 +324,11 @@ Class Procs:
/obj/machinery/Topic(href, href_list)
..()
if(!can_interact(usr))
- return 1
+ return TRUE
if(!usr.canUseTopic(src))
- return 1
+ return TRUE
add_fingerprint(usr)
- return 0
+ return FALSE
////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/code/game/machinery/ai_slipper.dm b/code/game/machinery/ai_slipper.dm
index 4935c9d4d308f..0bb005c791ba4 100644
--- a/code/game/machinery/ai_slipper.dm
+++ b/code/game/machinery/ai_slipper.dm
@@ -6,7 +6,7 @@
layer = PROJECTILE_HIT_THRESHHOLD_LAYER
plane = FLOOR_PLANE
max_integrity = 200
- armor = list("melee" = 50, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30)
+ armor = list("melee" = 50, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30, "stamina" = 0)
var/uses = 20
var/cooldown = 0
diff --git a/code/game/machinery/airlock_control.dm b/code/game/machinery/airlock_control.dm
index 6c957acd4e70e..357edff1f6de7 100644
--- a/code/game/machinery/airlock_control.dm
+++ b/code/game/machinery/airlock_control.dm
@@ -98,7 +98,7 @@
var/datum/radio_frequency/radio_connection
- var/on = TRUE
+ var/on = TRUE // Reviewer: I can't find any way to turn this thing off but it stays
var/alert = FALSE
/obj/machinery/airlock_sensor/incinerator_toxmix
@@ -138,7 +138,9 @@
if(on)
var/datum/gas_mixture/air_sample = return_air()
var/pressure = round(air_sample.return_pressure(),0.1)
- alert = (pressure < ONE_ATMOSPHERE*0.8)
+ if((pressure < ONE_ATMOSPHERE*0.8) != alert)
+ alert = !alert
+ update_icon()
var/datum/signal/signal = new(list(
"tag" = id_tag,
@@ -148,8 +150,6 @@
radio_connection.post_signal(src, signal, range = AIRLOCK_CONTROL_RANGE, filter = RADIO_AIRLOCK)
- update_icon()
-
/obj/machinery/airlock_sensor/proc/set_frequency(new_frequency)
SSradio.remove_object(src, frequency)
frequency = new_frequency
diff --git a/code/game/machinery/airlock_cycle_control.dm b/code/game/machinery/airlock_cycle_control.dm
index eed15b65e25e1..d4e8e014e2c25 100644
--- a/code/game/machinery/airlock_cycle_control.dm
+++ b/code/game/machinery/airlock_cycle_control.dm
@@ -53,8 +53,9 @@
req_access = list(ACCESS_ATMOSPHERICS)
max_integrity = 250
integrity_failure = 80
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30, "stamina" = 0)
resistance_flags = FIRE_PROOF
+ layer = ABOVE_WINDOW_LAYER
var/cyclestate = AIRLOCK_CYCLESTATE_INOPEN
var/interior_pressure = ONE_ATMOSPHERE
@@ -291,7 +292,6 @@
door.unbolt()
/obj/machinery/advanced_airlock_controller/process()
- . = ..()
process_atmos()
/obj/machinery/advanced_airlock_controller/process_atmos()
@@ -601,6 +601,7 @@
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "AdvancedAirlockController")
+ ui.set_autoupdate(TRUE) // Pressure display, mode changes as part of the cycle process
ui.open()
/obj/machinery/advanced_airlock_controller/ui_data(mob/user)
@@ -687,10 +688,12 @@
A.do_animate("deny")
if(is_allowed)
cycle_to(text2num(params["exterior"]))
+ . = TRUE
if("skip")
if((world.time - skip_timer) >= skip_delay && (cyclestate == AIRLOCK_CYCLESTATE_OUTCLOSING || cyclestate == AIRLOCK_CYCLESTATE_OUTOPENING || cyclestate == AIRLOCK_CYCLESTATE_INOPENING || cyclestate == AIRLOCK_CYCLESTATE_INCLOSING))
is_skipping = TRUE
- if((locked && !usr.has_unlimited_silicon_privilege) || (usr.has_unlimited_silicon_privilege && aidisabled))
+ . = TRUE
+ if(!. && ((locked && !usr.has_unlimited_silicon_privilege) || (usr.has_unlimited_silicon_privilege && aidisabled)))
return
switch(action)
if("lock")
@@ -708,34 +711,46 @@
vents[vent] = curr_role & ~(role_to_toggle)
else
vents[vent] = curr_role | role_to_toggle
+ . = TRUE
if("set_airlock_role")
var/airlock = locate(params["airlock_id"])
if(airlock == null || airlocks[airlock] == null)
return
airlocks[airlock] = !!text2num(params["val"])
+ . = TRUE
if("clear_vis")
vis_target = null
+ . = TRUE
if("set_vis_vent")
var/vent = locate(params["vent_id"])
if(vent == null || vents[vent] == null)
return
vis_target = vent
+ . = TRUE
if("set_vis_airlock")
var/airlock = locate(params["airlock_id"])
if(airlock == null || airlocks[airlock] == null)
return
vis_target = airlock
+ . = TRUE
if("scan")
scan()
+ . = TRUE
if("interior_pressure")
interior_pressure = CLAMP(text2num(params["pressure"]), 0, ONE_ATMOSPHERE)
+ . = TRUE
if("exterior_pressure")
exterior_pressure = CLAMP(text2num(params["pressure"]), 0, ONE_ATMOSPHERE)
+ . = TRUE
if("depressurization_margin")
depressurization_margin = CLAMP(text2num(params["pressure"]), 0.15, 40)
+ . = TRUE
if("skip_delay")
skip_delay = CLAMP(text2num(params["skip_delay"]), 0, 1200)
- update_icon(TRUE)
+ . = TRUE
+
+ if(.)
+ update_icon(TRUE)
/obj/machinery/advanced_airlock_controller/proc/request_from_door(airlock)
var/role = airlocks[airlock]
@@ -766,7 +781,6 @@
skip_timer = world.time
/obj/machinery/advanced_airlock_controller/AltClick(mob/user)
- ..()
if(!user.canUseTopic(src, !issilicon(user)) || !isturf(loc))
return
else
@@ -780,7 +794,6 @@
locked = !locked
update_icon()
to_chat(user, "You [ locked ? "lock" : "unlock"] the airlock controller interface.")
- updateUsrDialog()
else
to_chat(user, "Access denied.")
return
diff --git a/code/game/machinery/announcement_system.dm b/code/game/machinery/announcement_system.dm
index a39056c895fe0..a3e37cd1e9e09 100644
--- a/code/game/machinery/announcement_system.dm
+++ b/code/game/machinery/announcement_system.dm
@@ -17,7 +17,7 @@ GLOBAL_LIST_EMPTY(announcement_systems)
circuit = /obj/item/circuitboard/machine/announcement_system
var/obj/item/radio/headset/radio
- var/arrival = "%PERSON has signed up as %RANK"
+ var/arrival = "%PERSON has signed up as %RANK."
var/arrivalToggle = 1
var/newhead = "%PERSON, %RANK, is the department head."
var/newheadToggle = 1
@@ -93,7 +93,7 @@ GLOBAL_LIST_EMPTY(announcement_systems)
else if(message_type == "CRYOSTORAGE")
message = CompileText("%PERSON, %RANK has been moved to cryo storage.", user, rank)
else if(message_type == "ARRIVALS_BROKEN")
- message = "The arrivals shuttle has been damaged. Docking for repairs..."
+ message = "The arrivals shuttle has been damaged. Docking for repairs."
if(channels.len == 0)
radio.talk_into(src, message, null)
@@ -140,6 +140,7 @@ GLOBAL_LIST_EMPTY(announcement_systems)
if(NewMessage)
arrival = NewMessage
log_game("The arrivals announcement was updated: [NewMessage] by:[key_name(usr)]")
+ . = TRUE
if("NewheadText")
var/NewMessage = trim(html_encode(param["newText"]), MAX_MESSAGE_LEN)
if(!usr.canUseTopic(src, !issilicon(usr)))
@@ -147,13 +148,15 @@ GLOBAL_LIST_EMPTY(announcement_systems)
if(NewMessage)
newhead = NewMessage
log_game("The head announcement was updated: [NewMessage] by:[key_name(usr)]")
+ . = TRUE
if("NewheadToggle")
newheadToggle = !newheadToggle
update_icon()
+ . = TRUE
if("ArrivalToggle")
arrivalToggle = !arrivalToggle
update_icon()
- add_fingerprint(usr)
+ . = TRUE
/obj/machinery/announcement_system/attack_robot(mob/living/silicon/user)
. = attack_ai(user)
@@ -172,6 +175,7 @@ GLOBAL_LIST_EMPTY(announcement_systems)
arrival = pick("#!@%ERR-34%2 CANNOT LOCAT@# JO# F*LE!", "CRITICAL ERROR 99.", "ERR)#: DA#AB@#E NOT F(*ND!")
newhead = pick("OV#RL()D: \[UNKNOWN??\] DET*#CT)D!", "ER)#R - B*@ TEXT F*O(ND!", "AAS.exe is not responding. NanoOS is searching for a solution to the problem.")
+ ui_update()
/obj/machinery/announcement_system/emp_act(severity)
. = ..()
diff --git a/code/game/machinery/aug_manipulator.dm b/code/game/machinery/aug_manipulator.dm
index bca88699016a0..224eeb0110d5b 100644
--- a/code/game/machinery/aug_manipulator.dm
+++ b/code/game/machinery/aug_manipulator.dm
@@ -129,7 +129,6 @@
to_chat(user, "[src] is empty.")
/obj/machinery/aug_manipulator/AltClick(mob/living/user)
- ..()
if(!user.canUseTopic(src, !issilicon(user)))
return
else
diff --git a/code/game/machinery/autodoc.dm b/code/game/machinery/autodoc.dm
deleted file mode 100644
index 9d5c247736a03..0000000000000
--- a/code/game/machinery/autodoc.dm
+++ /dev/null
@@ -1,167 +0,0 @@
-/obj/machinery/autodoc
- name = "autodoc"
- desc = "An advanced machine used for inserting organs and implants into the occupant."
- density = TRUE
- state_open = FALSE
- icon = 'icons/obj/machines/autodoc.dmi'
- icon_state = "autodoc_machine"
- verb_say = "states"
- idle_power_usage = 50
- circuit = /obj/item/circuitboard/machine/autodoc
- var/obj/item/organ/storedorgan
- var/organ_type = /obj/item/organ
- var/processing = FALSE
- var/surgerytime = 300
-
-/obj/machinery/autodoc/Initialize()
- . = ..()
- update_icon()
-
-/obj/machinery/autodoc/RefreshParts()
- var/max_time = 350
- for(var/obj/item/stock_parts/L in component_parts)
- max_time -= (L.rating*10)
- surgerytime = max(max_time,10)
-
-/obj/machinery/autodoc/examine(mob/user)
- . = ..()
- if((obj_flags & EMAGGED) && panel_open)
- . += "[src]'s surgery protocols have been corrupted!"
- if(processing)
- . += "[src] is currently inserting [storedorgan] into [occupant]."
- else if(storedorgan)
- . += "[src] has been prepared to insert [storedorgan]."
-
-/obj/machinery/autodoc/close_machine(mob/user)
- ..()
- playsound(src, 'sound/machines/click.ogg', 50)
- if(occupant)
- if(!iscarbon(occupant))
- occupant.forceMove(drop_location())
- occupant = null
- return
- to_chat(occupant, "The doors to [src] clamp shut behind you.")
-
- dosurgery()
-
-/obj/machinery/autodoc/proc/dosurgery()
- if(!storedorgan && !(obj_flags & EMAGGED))
- to_chat(occupant, "[src] currently has no implant stored.")
- return
-
- occupant.visible_message("[occupant] presses a button on [src]. A mechanical humming can be heard.", "You feel a sharp sting as [src] starts inserting the organ into your body.")
- playsound(get_turf(occupant), 'sound/weapons/circsawhit.ogg', 50, 1)
- processing = TRUE
- update_icon()
- var/mob/living/carbon/C = occupant
- if(obj_flags & EMAGGED)
-
- for(var/obj/item/bodypart/BP in reverseList(C.bodyparts)) //Chest and head are first in bodyparts, so we invert it to make them suffer more
- C.emote("scream")
- if(!HAS_TRAIT(C, TRAIT_NODISMEMBER))
- BP.dismember()
- else
- C.apply_damage(40, BRUTE, BP)
- sleep(5) //2 seconds to get outta there before dying
- if(!processing)
- return
-
- occupant.visible_message("[src] dismembers [occupant]!", "[src] saws up your body!")
-
- else
- sleep(surgerytime)
- if(!processing)
- return
- var/obj/item/organ/currentorgan = C.getorganslot(storedorgan.slot)
- if(currentorgan)
- currentorgan.Remove(C)
- currentorgan.forceMove(get_turf(src))
- storedorgan.Insert(occupant)//insert stored organ into the user
- storedorgan = null
- occupant.visible_message("[src] completes the surgery procedure.", "[src] inserts the organ into your body.")
- playsound(src, 'sound/machines/microwave/microwave-end.ogg', 100, 0)
- processing = FALSE
- open_machine()
-
-/obj/machinery/autodoc/open_machine(mob/user)
- if(processing)
- occupant.visible_message("[user] cancels [src]'s procedure.", "[src] stops inserting the organ into your body.")
- processing = FALSE
- if(occupant)
- occupant.forceMove(drop_location())
- occupant = null
- ..(FALSE)
-
-/obj/machinery/autodoc/interact(mob/user)
- if(panel_open)
- to_chat(user, "Close the maintenance panel first.")
- return
-
- if(state_open)
- close_machine()
- return
-
- open_machine()
-
-/obj/machinery/autodoc/attackby(obj/item/I, mob/user, params)
- if(istype(I, organ_type))
- if(storedorgan)
- to_chat(user, "[src] already has an implant stored.")
- return
- if(!user.transferItemToLoc(I, src))
- return
- storedorgan = I
- I.forceMove(src)
- to_chat(user, "You insert the [I] into [src].")
- else
- return ..()
-
-/obj/machinery/autodoc/screwdriver_act(mob/living/user, obj/item/I)
- . = TRUE
- if(..())
- return
- if(occupant)
- to_chat(user, "[src] is currently occupied!")
- return
- if(state_open)
- to_chat(user, "[src] must be closed to [panel_open ? "close" : "open"] its maintenance hatch!")
- return
- if(default_deconstruction_screwdriver(user, icon_state, icon_state, I))
- if(storedorgan)
- storedorgan.forceMove(drop_location())
- storedorgan = null
- update_icon()
- return
- return FALSE
-
-/obj/machinery/autodoc/crowbar_act(mob/living/user, obj/item/I)
- if(default_deconstruction_crowbar(I))
- return TRUE
-
-
-/obj/machinery/autodoc/update_icon()
- overlays.Cut()
- if(!state_open)
- if(processing)
- overlays += "[icon_state]_door_on"
- overlays += "[icon_state]_stack"
- overlays += "[icon_state]_smoke"
- overlays += "[icon_state]_green"
- else
- overlays += "[icon_state]_door_off"
- if(occupant)
- if(powered(AREA_USAGE_EQUIP))
- overlays += "[icon_state]_stack"
- overlays += "[icon_state]_yellow"
- else
- overlays += "[icon_state]_red"
- else if(powered(AREA_USAGE_EQUIP))
- overlays += "[icon_state]_red"
- if(panel_open)
- overlays += "[icon_state]_panel"
-
-/obj/machinery/autodoc/emag_act(mob/user)
- if(obj_flags & EMAGGED)
- return
- obj_flags |= EMAGGED
- to_chat(user, "You reprogram [src]'s surgery procedures.")
diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm
deleted file mode 100644
index 7bee5202ff3bf..0000000000000
--- a/code/game/machinery/autolathe.dm
+++ /dev/null
@@ -1,610 +0,0 @@
-#define AUTOLATHE_MAX_POWER_USE 2000
-
-/obj/machinery/autolathe
- name = "autolathe"
- desc = "It produces items using iron, copper, and glass."
- icon_state = "autolathe"
- density = TRUE
- use_power = IDLE_POWER_USE
- idle_power_usage = 10
- active_power_usage = 100
- circuit = /obj/item/circuitboard/machine/autolathe
- layer = BELOW_OBJ_LAYER
-
- var/operating = FALSE
- var/wants_operate = FALSE
- var/disabled = 0
- var/shocked = FALSE
- var/hack_wire
- var/disable_wire
- var/shock_wire
-
- //Security modes
- var/security_interface_locked = TRUE
- var/hacked = FALSE
-
- var/busy = FALSE
- var/prod_coeff = 1
-
- var/datum/design/being_built
- var/process_completion_world_tick = 0
- var/total_build_time = 0
- var/datum/techweb/stored_research
-
- var/list/categories = list(
- "Tools",
- "Electronics",
- "Construction",
- "T-Comm",
- "Security",
- "Machinery",
- "Medical",
- "Misc",
- "Dinnerware",
- "Imported"
- )
-
- var/output_direction = 0
- var/obj/item/disk/design_disk/inserted_disk
-
- //A list of all the printable items
-
- //Queue items
-
- //Viewing mobs of the UI to update
- var/list/mob/viewing_mobs = list()
- //Associative list: item_queue[design_id] = list("amount" = int, "repeating" = bool, "build_mat" = something)
- //These are the items in the build queue. (It's a queue that takes priority over item_queue)
- var/list/build_queue = list()
- //Associative list: item_queue[design_id] = list("amount" = int, "repeating" = bool, "build_mat" = something)
- //The items in the item queue
- var/list/item_queue = list()
- //If true, once an item is processed it will be stuck right back on again
- var/queue_repeating = FALSE
- //The amount to readd to the queue when processing is done
- var/stored_item_amount
-
-/obj/machinery/autolathe/Initialize()
- AddComponent(/datum/component/material_container, list(/datum/material/iron, /datum/material/glass, /datum/material/copper, /datum/material/gold, /datum/material/gold, /datum/material/silver, /datum/material/diamond, /datum/material/uranium, /datum/material/plasma, /datum/material/bluespace, /datum/material/bananium, /datum/material/titanium), 0, TRUE, null, null, CALLBACK(src, .proc/AfterMaterialInsert))
- . = ..()
-
- wires = new /datum/wires/autolathe(src)
- stored_research = new /datum/techweb/specialized/autounlocking/autolathe
-
-/obj/machinery/autolathe/Destroy()
- QDEL_NULL(wires)
- return ..()
-
-/obj/machinery/autolathe/ui_state()
- return GLOB.default_state
-
-/obj/machinery/autolathe/ui_interact(mob/user, datum/tgui/ui = null)
- if(!is_operational())
- return
-
- if(shocked && !(stat & NOPOWER))
- shock(user,50)
-
- ui = SStgui.try_update_ui(user, src, ui)
- if(!ui)
- ui = new(user, src, "ModularFabricator")
- ui.open()
- ui.set_autoupdate(TRUE)
- viewing_mobs += user
-
-/obj/machinery/autolathe/ui_close(mob/user)
- . = ..()
- viewing_mobs -= user
-
-/obj/machinery/autolathe/ui_static_data(mob/user)
- var/list/data = list()
- data["acceptsDisk"] = TRUE
-
- //Items
- data["items"] = list()
- var/list/categories_associative = list()
- for(var/v in stored_research.researched_designs)
- var/datum/design/D = SSresearch.techweb_design_by_id(v)
- for(var/cat in D.category)
- //Check if printable
- if(!(cat in categories))
- continue
- if(!islist(categories_associative[cat]))
- categories_associative[cat] = list()
-
- //Calculate cost
- var/list/material_cost = list()
- for(var/material_id in D.materials)
- material_cost += list(list(
- "name" = material_id,
- "amount" = D.materials[material_id] / MINERAL_MATERIAL_AMOUNT,
- ))
-
- //Add
- categories_associative[cat] += list(list(
- "name" = D.name,
- "design_id" = D.id,
- "material_cost" = material_cost,
- ))
-
- //Categories and their items
- for(var/category in categories_associative)
- data["items"] += list(list(
- "category_name" = category,
- "category_items" = categories_associative[category],
- ))
-
- //Inserted data disk
- data["diskInserted"] = inserted_disk
- return data
-
-/obj/machinery/autolathe/ui_data(mob/user)
- var/list/data = list()
- //Output direction
- data["outputDir"] = output_direction
-
- //Queue
- data["queue"] = list()
-
- //Build queue at the top
- for(var/item_design_id in build_queue)
- var/datum/design/D = SSresearch.techweb_design_by_id(item_design_id)
- var/list/additional_data = build_queue[item_design_id]
- data["queue"] += list(list(
- "name" = D.name,
- "amount" = additional_data["amount"],
- "repeat" = additional_data["repeating"],
- "design_id" = item_design_id,
- "build_queue" = 1,
- ))
-
- //Real queue at the bottom
- for(var/item_design_id in item_queue)
- var/datum/design/D = SSresearch.techweb_design_by_id(item_design_id)
- var/list/additional_data = item_queue[item_design_id]
- data["queue"] += list(list(
- "name" = D.name,
- "amount" = additional_data["amount"],
- "repeat" = additional_data["repeating"],
- "design_id" = item_design_id,
- "build_queue" = 0,
- ))
-
- //Materials
- data["materials"] = list()
- var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
- for(var/material in materials.materials)
- var/datum/material/M = material
- var/mineral_amount = materials.materials[material]
- data["materials"] += list(list(
- "name" = M.name,
- "amount" = mineral_amount,
- "datum" = M.type
- ))
-
- //Thing being made
- if(being_built && total_build_time && process_completion_world_tick)
- data["being_build"] = list(
- "design_id" = being_built.id,
- "name" = being_built.name,
- "progress" = 100-(100*((process_completion_world_tick - world.time)/total_build_time)),
- )
-
- //Security interface
- data["sec_interface_unlock"] = !security_interface_locked
- data["hacked"] = hacked
-
- //Being Build
- return data
-
-/obj/machinery/autolathe/ui_act(action, params)
- if(..())
- return
-
- switch(action)
- if("toggle_safety")
- if(security_interface_locked)
- return
- adjust_hacked(!hacked)
-
- if("toggle_lock")
- if(obj_flags & EMAGGED)
- return
- security_interface_locked = TRUE
-
- if("output_dir")
- output_direction = text2num(params["direction"])
-
- if("upload_disk")
- var/obj/item/disk/design_disk/D = inserted_disk
- if(!istype(D))
- return
- for(var/B in D.blueprints)
- if(B)
- stored_research.add_design(B)
- update_viewer_statics()
-
- if("eject_disk")
- if(!inserted_disk)
- return
- var/obj/item/disk/design_disk/disk = inserted_disk
- disk.forceMove(get_turf(src))
- update_viewer_statics()
-
- if("eject_material")
- var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
- var/material_datum = params["material_datum"] //Comes out as text
- var/amount = text2num(params["amount"])
- if(amount <= 0 || amount > 50)
- return
- for(var/mat in materials.materials)
- var/datum/material/M = mat
- if("[M.type]" == material_datum)
- materials.retrieve_sheets(amount, M, get_release_turf())
- break
-
- if("queue_repeat")
- queue_repeating = text2num(params["repeating"])
-
- if("clear_queue")
- item_queue.Cut()
-
- if("item_repeat")
- var/design_id = params["design_id"]
- var/repeating_mode = text2num(params["repeating"])
- if(!item_queue["[design_id]"])
- return
- item_queue["[design_id]"]["repeating"] = repeating_mode
-
- if("clear_item")
- var/design_id = params["design_id"]
- var/queue_type = text2num(params["build_queue"])
- if(queue_type)
- build_queue -= design_id
- else
- item_queue -= design_id
-
- if("queue_item")
- var/design_id = params["design_id"]
- var/amount = text2num(params["amount"])
- add_to_queue(item_queue, design_id, amount)
-
- if("build_item")
- var/design_id = params["design_id"]
- var/amount = text2num(params["amount"])
- add_to_queue(build_queue, design_id, amount)
-
- if("begin_process")
- begin_process()
-
- //Update the UI for them so it's smooth
- ui_interact(usr)
-
-/obj/machinery/autolathe/proc/update_viewer_statics()
- for(var/mob/M in viewing_mobs)
- if(QDELETED(M) || !(M.client || M.mind))
- continue
- update_static_data(M)
-
-/obj/machinery/autolathe/proc/add_to_queue(queue_list, design_id, amount, repeat=null)
- if(queue_list["[design_id]"])
- queue_list["[design_id]"]["amount"] += amount
- if(queue_list["[design_id]"]["amount"] <= 0)
- queue_list -= "[design_id]"
- return
- if(amount <= 0)
- return
- //Check if the item uses custom materials
- var/datum/design/requested_item = stored_research.isDesignResearchedID(design_id)
- var/datum/material/used_material = repeat
- if(!istype(used_material))
- for(var/MAT in requested_item.materials)
- used_material = MAT
- if(istext(used_material)) //This means its a category
- var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
- var/list/list_to_show = list()
- for(var/i in SSmaterials.materials_by_category[used_material])
- if(materials.materials[i] > 0)
- list_to_show += i
- used_material = input("Choose [used_material]", "Custom Material") as null|anything in sortList(list_to_show, /proc/cmp_typepaths_asc)
- if(!used_material)
- return //Didn't pick any material, so you can't build shit either.
-
- queue_list["[design_id]"] = list(
- "amount" = amount,
- "repeating" = repeat,
- "build_mat" = used_material,
- )
-
-/obj/machinery/autolathe/proc/get_release_turf()
- var/turf/T
- if(output_direction)
- T = get_step(src, output_direction)
- if(is_blocked_turf(T, TRUE))
- T = get_turf(src)
- else
- T = get_turf(src)
- return T
-
-/obj/machinery/autolathe/on_deconstruction()
- var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
- materials.retrieve_all()
-
-/obj/machinery/autolathe/attackby(obj/item/O, mob/user, params)
-
- if(ACCESS_SECURITY in O.GetAccess() && !(obj_flags & EMAGGED))
- security_interface_locked = !security_interface_locked
- to_chat(user, "You [security_interface_locked?"lock":"unlock"] the security controls of [src].")
- return TRUE
-
- if (busy)
- to_chat(user, "The autolathe is busy. Please wait for completion of previous operation.")
- return TRUE
-
- if(default_deconstruction_screwdriver(user, "autolathe_t", "autolathe", O))
- return TRUE
-
- if(default_deconstruction_crowbar(O))
- return TRUE
-
- if(panel_open && is_wire_tool(O))
- wires.interact(user)
- return TRUE
-
- if(user.a_intent == INTENT_HARM) //so we can hit the machine
- return ..()
-
- if(stat)
- return TRUE
-
- if(istype(O, /obj/item/disk/design_disk))
- user.visible_message("[user] loads \the [O] into \the [src]...",
- "You load a design from \the [O]...",
- "You hear the chatter of a floppy drive.")
- inserted_disk = O
- O.forceMove(src)
- update_viewer_statics()
- return TRUE
-
- return ..()
-
-
-/obj/machinery/autolathe/proc/AfterMaterialInsert(type_inserted, id_inserted, amount_inserted)
- if(ispath(type_inserted, /obj/item/stack/ore/bluespace_crystal))
- use_power(MINERAL_MATERIAL_AMOUNT / 10)
- else
- switch(id_inserted)
- if (/datum/material/iron)
- flick("autolathe_o",src)//plays metal insertion animation
- if(/datum/material/copper)
- flick("autolathe_c",src)//plays metal insertion animation
- else
- flick("autolathe_r",src)//plays glass insertion animation by default otherwise
- use_power(min(1000, amount_inserted / 100))
- //Begin processing to continue the queue if we had items in the queue
- if(wants_operate)
- begin_process()
-
-/obj/machinery/autolathe/proc/begin_process()
- if(busy || operating || disabled)
- return
- var/requested_design_id = null
- var/from_build_queue = FALSE
- if(LAZYLEN(build_queue))
- requested_design_id = build_queue[1]
- from_build_queue = TRUE
- else if(LAZYLEN(item_queue))
- requested_design_id = item_queue[1]
- //Queue processing done
- if(!requested_design_id)
- say("Queue processing completed.")
- operating = FALSE
- return
- operating = TRUE
- //Doubles as protection from bad things and makes sure we can still make the item.
- being_built = stored_research.isDesignResearchedID(requested_design_id)
- if(!being_built)
- playsound(src, 'sound/machines/buzz-two.ogg', 50)
- say("Unknown design requested, removing from queue.")
- build_queue -= requested_design_id
- item_queue -= requested_design_id
- addtimer(CALLBACK(src, .proc/restart_process), 50)
- return
-
- var/multiplier = 1
- var/is_stack = ispath(being_built.build_path, /obj/item/stack)
- //Only items that can stack should be build en mass, since we now have queues.
- if(is_stack)
- if(from_build_queue)
- multiplier = build_queue[requested_design_id]["amount"]
- else
- multiplier = item_queue[requested_design_id]["amount"]
- multiplier = CLAMP(multiplier,1,50)
-
- /////////////////
-
- var/coeff = (is_stack ? 1 : prod_coeff) //stacks are unaffected by production coefficient
- var/total_amount = 0
-
- for(var/MAT in being_built.materials)
- total_amount += being_built.materials[MAT]
-
- var/power = max(AUTOLATHE_MAX_POWER_USE, (total_amount)*multiplier/5) //Change this to use all materials
-
- var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
-
- var/list/materials_used = list()
- var/list/custom_materials = list() //These will apply their material effect, This should usually only be one.
-
- for(var/MAT in being_built.materials)
- var/datum/material/used_material = MAT
- var/amount_needed = being_built.materials[MAT] * coeff * multiplier
- if(istext(used_material)) //This means its a category
- if(from_build_queue)
- used_material = build_queue[requested_design_id]["build_mat"]
- else
- used_material = item_queue[requested_design_id]["build_mat"]
- if(!used_material)
- build_queue -= requested_design_id
- item_queue -= requested_design_id
- addtimer(CALLBACK(src, .proc/restart_process), 50)
- return //Didn't pick any material, so you can't build shit either.
- custom_materials[used_material] += amount_needed
-
- materials_used[used_material] = amount_needed
-
- if(materials.has_materials(materials_used))
- busy = TRUE
- use_power(power)
- icon_state = "autolathe_n"
- var/time = is_stack ? 32 : (32 * coeff * multiplier) ** 0.8
- //===Repeating mode===
- //Remove from queue
- if(from_build_queue)
- build_queue[requested_design_id]["amount"] -= multiplier
- if(build_queue[requested_design_id]["amount"] <= 0)
- build_queue -= requested_design_id
- else
- var/list/queue_data = item_queue[requested_design_id]
- item_queue[requested_design_id]["amount"] -= multiplier
- var/removed = FALSE
- if(item_queue[requested_design_id]["amount"] <= 0)
- item_queue -= requested_design_id
- removed = TRUE
- //Requeue if necessary
- if(queue_repeating || queue_data["repeating"])
- stored_item_amount ++
- if(removed)
- add_to_queue(item_queue, requested_design_id, stored_item_amount, queue_data["build_mat"])
- stored_item_amount = 0
- //Create item and restart
- process_completion_world_tick = world.time + time
- total_build_time = time
- addtimer(CALLBACK(src, .proc/make_item, power, materials_used, custom_materials, multiplier, coeff, is_stack), time)
- addtimer(CALLBACK(src, .proc/restart_process), time + 5)
- else
- say("Insufficient materials, operation will proceed when sufficient materials are available.")
- operating = FALSE
- wants_operate = TRUE
-
-/obj/machinery/autolathe/proc/restart_process()
- operating = FALSE
- wants_operate = FALSE
- if(disabled)
- return
- begin_process()
-
-/obj/machinery/autolathe/proc/make_item(power, var/list/materials_used, var/list/picked_materials, multiplier, coeff, is_stack)
- if(QDELETED(src))
- return
- //Stops the queue
- if(disabled)
- operating = FALSE
- return
- var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
- var/turf/A = get_release_turf()
- use_power(power)
- materials.use_materials(materials_used)
- if(is_stack)
- var/obj/item/stack/N = new being_built.build_path(A, multiplier)
- N.update_icon()
- N.autolathe_crafted(src)
- else
- for(var/i=1, i<=multiplier, i++)
- var/obj/item/new_item = new being_built.build_path(A)
- new_item.materials = new_item.materials.Copy()
- for(var/mat in materials_used)
- new_item.materials[mat] = materials_used[mat] / multiplier
- new_item.autolathe_crafted(src)
-
- if(length(picked_materials))
- new_item.set_custom_materials(picked_materials, 1 / multiplier) //Ensure we get the non multiplied amount
- being_built = null
- icon_state = "autolathe"
- busy = FALSE
- updateDialog()
-
-/obj/machinery/autolathe/RefreshParts()
- var/T = 0
- for(var/obj/item/stock_parts/matter_bin/MB in component_parts)
- T += MB.rating*75000
- var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
- materials.max_amount = T
- T=1.2
- for(var/obj/item/stock_parts/manipulator/M in component_parts)
- T -= M.rating*0.2
- prod_coeff = min(1,max(0,T)) // Coeff going 1 -> 0,8 -> 0,6 -> 0,4
-
-/obj/machinery/autolathe/examine(mob/user)
- . += ..()
- var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
- if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Storing up to [materials.max_amount] material units. Material consumption at [prod_coeff*100]%."
-
-/obj/machinery/autolathe/proc/can_build(datum/design/D, amount = 1)
- if(D.make_reagents.len)
- return FALSE
-
- var/coeff = (ispath(D.build_path, /obj/item/stack) ? 1 : prod_coeff)
-
- var/list/required_materials = list()
-
- for(var/i in D.materials)
- required_materials[i] = D.materials[i] * coeff * amount
-
- var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
-
- return materials.has_materials(required_materials)
-
-/obj/machinery/autolathe/proc/reset(wire)
- switch(wire)
- if(WIRE_HACK)
- if(!wires.is_cut(wire))
- adjust_hacked(FALSE)
- if(WIRE_SHOCK)
- if(!wires.is_cut(wire))
- shocked = FALSE
- if(WIRE_DISABLE)
- if(!wires.is_cut(wire))
- disabled = FALSE
-
-/obj/machinery/autolathe/proc/shock(mob/user, prb)
- if(stat & (BROKEN|NOPOWER)) // unpowered, no shock
- return FALSE
- if(!prob(prb))
- return FALSE
- var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
- s.set_up(5, 1, src)
- s.start()
- if (electrocute_mob(user, get_area(src), src, 0.7, TRUE))
- return TRUE
- else
- return FALSE
-
-/obj/machinery/autolathe/proc/adjust_hacked(state)
- hacked = state
- for(var/id in SSresearch.techweb_designs)
- var/datum/design/D = SSresearch.techweb_design_by_id(id)
- if((D.build_type & AUTOLATHE) && ("hacked" in D.category))
- if(hacked)
- stored_research.add_design(D)
- else
- stored_research.remove_design(D)
- update_viewer_statics()
-
-/obj/machinery/autolathe/emag_act(mob/user)
- if(obj_flags & EMAGGED)
- return
- security_interface_locked = FALSE
- adjust_hacked(TRUE)
- playsound(src, "sparks", 100, 1)
- obj_flags |= EMAGGED
-
-/obj/machinery/autolathe/hacked/Initialize()
- . = ..()
- adjust_hacked(TRUE)
-
-//Called when the object is constructed by an autolathe
-//Has a reference to the autolathe so you can do !!FUN!! things with hacked lathes
-/obj/item/proc/autolathe_crafted(obj/machinery/autolathe/A)
- return
diff --git a/code/game/machinery/bank_machine.dm b/code/game/machinery/bank_machine.dm
index ea9e8606ae11f..0067e3f1b70d1 100644
--- a/code/game/machinery/bank_machine.dm
+++ b/code/game/machinery/bank_machine.dm
@@ -38,25 +38,28 @@
return
return ..()
-
-/obj/machinery/computer/bank_machine/process()
+/obj/machinery/computer/bank_machine/process(delta_time)
..()
if(siphoning)
if (stat & (BROKEN|NOPOWER))
say("Insufficient power. Halting siphon.")
end_syphon()
+ ui_update()
+ return
+ var/siphon_am = 100 * delta_time
var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR)
- if(!D.has_money(200))
+ if(!D.has_money(siphon_am))
say("Cargo budget depleted. Halting siphon.")
end_syphon()
+ ui_update()
return
- playsound(src, 'sound/items/poster_being_created.ogg', 100, 1)
- syphoning_credits += 200
- D.adjust_money(-200)
+ playsound(src, 'sound/items/poster_being_created.ogg', 100, TRUE)
+ syphoning_credits += siphon_am
+ D.adjust_money(-siphon_am)
if(next_warning < world.time && prob(15))
var/area/A = get_area(loc)
- var/message = "Unauthorized credit withdrawal underway in [A.map_name]!!"
+ var/message = "Unauthorized credit withdrawal underway in [initial(A.name)]!!"
radio.talk_into(src, message, radio_channel)
next_warning = world.time + minimum_time_between_warnings
diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm
index a11d60e680acf..0f06149b255c0 100644
--- a/code/game/machinery/buttons.dm
+++ b/code/game/machinery/buttons.dm
@@ -4,13 +4,14 @@
icon = 'icons/obj/stationobjs.dmi'
icon_state = "doorctrl"
var/skin = "doorctrl"
+ layer = ABOVE_WINDOW_LAYER
power_channel = AREA_USAGE_ENVIRON
var/obj/item/assembly/device
var/obj/item/electronics/airlock/board
var/device_type = null
var/id = null
var/initialized_button = 0
- armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 70)
+ armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 70, "stamina" = 0)
use_power = IDLE_POWER_USE
idle_power_usage = 2
resistance_flags = LAVA_PROOF | FIRE_PROOF
@@ -111,6 +112,12 @@
playsound(src, "sparks", 100, 1)
obj_flags |= EMAGGED
+/obj/machinery/button/eminence_act(mob/living/simple_animal/eminence/eminence)
+ . = ..()
+ to_chat(usr, "You begin manipulating [src]!")
+ if(do_after(eminence, 20, target=get_turf(eminence)))
+ attack_hand(eminence)
+
/obj/machinery/button/attack_ai(mob/user)
if(!panel_open)
return attack_hand(user)
@@ -272,7 +279,6 @@
icon_state = "launcher"
skin = "launcher"
device_type = /obj/item/assembly/control/crematorium
- req_access = list()
id = 1
/obj/machinery/button/crematorium/indestructible
diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm
index 73988b87d3904..3ad1c16347c9c 100644
--- a/code/game/machinery/camera/camera.dm
+++ b/code/game/machinery/camera/camera.dm
@@ -4,7 +4,7 @@
/obj/machinery/camera
name = "security camera"
- desc = "It's used to monitor rooms."
+ desc = "A wireless camera used to monitor rooms. It is powered by a long-life internal battery."
icon = 'icons/obj/machines/camera.dmi'
icon_state = "camera" //mapping icon to represent upgrade states. if you want a different base icon, update default_camera_icon as well as this.
use_power = ACTIVE_POWER_USE
@@ -13,7 +13,7 @@
layer = WALL_OBJ_LAYER
resistance_flags = FIRE_PROOF
- armor = list("melee" = 50, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 50)
+ armor = list("melee" = 50, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 50, "stamina" = 0)
max_integrity = 100
integrity_failure = 50
var/default_camera_icon = "camera" //the camera's base icon used by update_icon - icon_state is primarily used for mapping display purposes.
@@ -23,9 +23,13 @@
var/start_active = FALSE //If it ignores the random chance to start broken on round start
var/invuln = null
var/obj/item/camera_bug/bug = null
- var/obj/structure/camera_assembly/assembly = null
+ var/datum/weakref/assembly_ref = null
var/area/myarea = null
+ FASTDMM_PROP(\
+ pinned_vars = list("name", "network", "c_tag")\
+ )
+
//OTHER
var/view_range = 7
@@ -57,6 +61,7 @@
for(var/i in network)
network -= i
network += lowertext(i)
+ var/obj/structure/camera_assembly/assembly
if(CA)
assembly = CA
if(assembly.xray_module)
@@ -74,6 +79,7 @@
else
assembly = new(src)
assembly.state = 4 //STATE_FINISHED
+ assembly_ref = WEAKREF(assembly)
GLOB.cameranet.cameras += src
GLOB.cameranet.addCamera(src)
if (isturf(loc))
@@ -89,17 +95,18 @@
/obj/machinery/camera/Destroy()
if(can_use())
toggle_cam(null, 0) //kick anyone viewing out and remove from the camera chunks
+ GLOB.cameranet.removeCamera(src)
GLOB.cameranet.cameras -= src
+ cancelCameraAlarm()
if(isarea(myarea))
- LAZYREMOVE(myarea.cameras, src)
- QDEL_NULL(assembly)
+ myarea.clear_camera(src)
+ QDEL_NULL(assembly_ref)
QDEL_NULL(emp_component)
if(bug)
- bug.bugged_cameras -= src.c_tag
+ bug.bugged_cameras -= c_tag
if(bug.current == src)
bug.current = null
bug = null
- cancelCameraAlarm()
return ..()
/obj/machinery/camera/examine(mob/user)
@@ -222,6 +229,9 @@
/obj/machinery/camera/attackby(obj/item/I, mob/living/user, params)
// UPGRADES
if(panel_open)
+ var/obj/structure/camera_assembly/assembly = assembly_ref?.resolve()
+ if(!assembly)
+ assembly_ref = null
if(I.tool_behaviour == TOOL_ANALYZER)
if(!isXRay(TRUE)) //don't reveal it was already upgraded if was done via MALF AI Upgrade Camera Network ability
if(!user.temporarilyRemoveItemFromInventory(I))
@@ -297,7 +307,7 @@
else
to_chat(user, "Camera bugged.")
bug = I
- bug.bugged_cameras[src.c_tag] = src
+ bug.bugged_cameras[src.c_tag] = WEAKREF(src)
return
else if(istype(I, /obj/item/pai_cable))
@@ -320,12 +330,13 @@
/obj/machinery/camera/deconstruct(disassembled = TRUE)
if(!(flags_1 & NODECONSTRUCT_1))
if(disassembled)
+ var/obj/structure/camera_assembly/assembly = assembly_ref?.resolve()
if(!assembly)
assembly = new()
assembly.forceMove(drop_location())
assembly.state = 1
assembly.setDir(dir)
- assembly = null
+ assembly_ref = null
else
var/obj/item/I = new /obj/item/wallframe/camera (loc)
I.obj_integrity = I.max_integrity * 0.5
@@ -343,7 +354,7 @@
else
icon_state = "[xray_module][default_camera_icon][in_use_lights ? "_in_use" : ""]"
-/obj/machinery/camera/proc/toggle_cam(mob/user, displaymessage = 1)
+/obj/machinery/camera/proc/toggle_cam(mob/user, displaymessage = TRUE)
status = !status
if(can_use())
GLOB.cameranet.addCamera(src)
@@ -397,6 +408,8 @@
return FALSE
if(stat & EMPED)
return FALSE
+ if(is_jammed())
+ return FALSE
return TRUE
/obj/machinery/camera/proc/can_see()
@@ -444,7 +457,7 @@
/obj/machinery/camera/get_remote_view_fullscreens(mob/user)
if(view_range == short_range) //unfocused
- user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 2)
+ user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/impaired, 2)
/obj/machinery/camera/update_remote_sight(mob/living/user)
user.see_invisible = SEE_INVISIBLE_LIVING //can't see ghosts through cameras
diff --git a/code/game/machinery/camera/motion.dm b/code/game/machinery/camera/motion.dm
index af5093d6caf9a..e2e6e393e680c 100644
--- a/code/game/machinery/camera/motion.dm
+++ b/code/game/machinery/camera/motion.dm
@@ -38,10 +38,9 @@
return TRUE
/obj/machinery/camera/Destroy()
- var/area/ai_monitored/A = get_area(src)
localMotionTargets = null
- if(istype(A))
- A.motioncameras -= src
+ if(area_motion)
+ area_motion.motioncameras -= src
cancelAlarm()
return ..()
diff --git a/code/game/machinery/camera/presets.dm b/code/game/machinery/camera/presets.dm
index dcbff23b21ae5..e37c764e7375e 100644
--- a/code/game/machinery/camera/presets.dm
+++ b/code/game/machinery/camera/presets.dm
@@ -72,12 +72,14 @@
// UPGRADE PROCS
/obj/machinery/camera/proc/isEmpProof(ignore_malf_upgrades)
- return (upgrades & CAMERA_UPGRADE_EMP_PROOF) && (!(ignore_malf_upgrades && assembly.malf_emp_firmware_active))
+ var/obj/structure/camera_assembly/assembly = assembly_ref?.resolve()
+ return (upgrades & CAMERA_UPGRADE_EMP_PROOF) && (!(ignore_malf_upgrades && assembly?.malf_emp_firmware_active))
/obj/machinery/camera/proc/upgradeEmpProof(malf_upgrade, ignore_malf_upgrades)
if(isEmpProof(ignore_malf_upgrades)) //pass a malf upgrade to ignore_malf_upgrades so we can replace the malf module with the normal one
return //that way if someone tries to upgrade an already malf-upgraded camera, it'll just upgrade it to a normal version.
emp_component = AddComponent(/datum/component/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES | EMP_PROTECT_CONTENTS)
+ var/obj/structure/camera_assembly/assembly = assembly_ref?.resolve()
if(malf_upgrade)
assembly.malf_emp_firmware_active = TRUE //don't add parts to drop, update icon, ect. reconstructing it will also retain the upgrade.
assembly.malf_emp_firmware_present = TRUE //so the upgrade is retained after incompatible parts are removed.
@@ -98,11 +100,13 @@
/obj/machinery/camera/proc/isXRay(ignore_malf_upgrades)
+ var/obj/structure/camera_assembly/assembly = assembly_ref?.resolve()
return (upgrades & CAMERA_UPGRADE_XRAY) && (!(ignore_malf_upgrades && assembly.malf_xray_firmware_active))
/obj/machinery/camera/proc/upgradeXRay(malf_upgrade, ignore_malf_upgrades)
if(isXRay(ignore_malf_upgrades)) //pass a malf upgrade to ignore_malf_upgrades so we can replace the malf upgrade with the normal one
return //that way if someone tries to upgrade an already malf-upgraded camera, it'll just upgrade it to a normal version.
+ var/obj/structure/camera_assembly/assembly = assembly_ref?.resolve()
if(malf_upgrade)
assembly.malf_xray_firmware_active = TRUE //don't add parts to drop, update icon, ect. reconstructing it will also retain the upgrade.
assembly.malf_xray_firmware_present = TRUE //so the upgrade is retained after incompatible parts are removed.
@@ -128,6 +132,8 @@
/obj/machinery/camera/proc/upgradeMotion()
if(isMotion())
return
+ var/obj/structure/camera_assembly/assembly = assembly_ref?.resolve()
+
if(name == initial(name))
name = "motion-sensitive security camera"
if(!assembly.proxy_module)
diff --git a/code/game/machinery/camera/tracking.dm b/code/game/machinery/camera/tracking.dm
index bf1b84fe5d4f8..767cb90abad74 100644
--- a/code/game/machinery/camera/tracking.dm
+++ b/code/game/machinery/camera/tracking.dm
@@ -49,9 +49,9 @@
track.namecounts[name] = 1
if(ishuman(L))
- track.humans[name] = L
+ track.humans[name] = WEAKREF(L)
else
- track.others[name] = L
+ track.others[name] = WEAKREF(L)
var/list/targets = sortList(track.humans) + sortList(track.others)
@@ -67,9 +67,9 @@
if(!track.initialized)
trackable_mobs()
- var/mob/target = (isnull(track.humans[target_name]) ? track.others[target_name] : track.humans[target_name])
+ var/datum/weakref/target = (isnull(track.humans[target_name]) ? track.others[target_name] : track.humans[target_name])
- ai_actual_track(target)
+ ai_actual_track(target.resolve())
/mob/living/silicon/ai/proc/ai_actual_track(mob/living/target)
if(!istype(target))
diff --git a/code/game/machinery/cell_charger.dm b/code/game/machinery/cell_charger.dm
index 4d13f9d61b392..8f824e2864640 100644
--- a/code/game/machinery/cell_charger.dm
+++ b/code/game/machinery/cell_charger.dm
@@ -11,7 +11,7 @@
pass_flags = PASSTABLE
var/obj/item/stock_parts/cell/charging = null
var/chargelevel = -1
- var/charge_rate = 500
+ var/charge_rate = 250
/obj/machinery/cell_charger/update_icon()
cut_overlays()
@@ -28,7 +28,7 @@
if(charging)
. += "Current charge: [round(charging.percent(), 1)]%."
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Charge rate at [charge_rate]J per cycle."
+ . += "The status display reads: Charging power: [charge_rate]W."
/obj/machinery/cell_charger/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/stock_parts/cell) && !panel_open)
@@ -115,17 +115,17 @@
charging.emp_act(severity)
/obj/machinery/cell_charger/RefreshParts()
- charge_rate = 500
+ charge_rate = 250
for(var/obj/item/stock_parts/capacitor/C in component_parts)
charge_rate *= C.rating
-/obj/machinery/cell_charger/process()
+/obj/machinery/cell_charger/process(delta_time)
if(!charging || !anchored || (stat & (BROKEN|NOPOWER)))
return
if(charging.percent() >= 100)
return
- use_power(charge_rate)
- charging.give(charge_rate) //this is 2558, efficient batteries exist
+ use_power(charge_rate * delta_time)
+ charging.give(charge_rate * delta_time) //this is 2558, efficient batteries exist
update_icon()
diff --git a/code/game/machinery/cloning.dm b/code/game/machinery/cloning.dm
index da20066cc047b..1baf6947c205a 100644
--- a/code/game/machinery/cloning.dm
+++ b/code/game/machinery/cloning.dm
@@ -60,6 +60,7 @@
var/mob/living/mob_occupant = occupant
go_out()
if(mob_occupant)
+ // Random comment: this is a bad situation since breaking the pod ejects the occupant
log_cloning("[key_name(mob_occupant)] ejected from [src] at [AREACOORD(src)] due to Destroy().")
QDEL_NULL(radio)
QDEL_NULL(countdown)
@@ -94,7 +95,6 @@
user.examinate(src)
/obj/machinery/clonepod/AltClick(mob/user)
- . = ..()
if (alert(user, "Are you sure you want to empty the cloning pod?", "Empty Reagent Storage:", "Yes", "No") != "Yes")
return
to_chat(user, "You empty \the [src]'s release valve onto the floor.")
@@ -112,13 +112,14 @@
. += "Synthflesh consumption at [round(fleshamnt*90, 1)]cm3 per clone. "
. += "The reagent display reads: [round(reagents.total_volume, 1)] / [reagents.maximum_volume] cm3"
if(efficiency > 5)
- . += "Pod has been upgraded to support autoprocessing and apply beneficial mutations."
+ . += "Pod has been upgraded to support autoprocessing and apply beneficial mutations."
//The return of data disks?? Just for transferring between genetics machine/cloning machine.
//TO-DO: Make the genetics machine accept them.
/obj/item/disk/data
name = "cloning data disk"
icon_state = "datadisk0" //Gosh I hope syndies don't mistake them for the nuke disk.
+ var/list/genetic_makeup_buffer = list()
var/list/fields = list()
var/list/mutations = list()
var/max_mutations = 6
@@ -138,6 +139,15 @@
. = ..()
. += "The write-protect tab is set to [read_only ? "protected" : "unprotected"]."
+/obj/item/disk/data/debug
+ name = "Debug genetic data disk"
+ desc = "A disk that contains all existing genetic mutations."
+ max_mutations = 100
+
+/obj/item/disk/data/debug/Initialize()
+ . = ..()
+ for(var/datum/mutation/human/HM as() in GLOB.all_mutations)
+ mutations += new HM
//Clonepod
@@ -276,7 +286,7 @@
/obj/machinery/clonepod/process()
var/mob/living/mob_occupant = occupant
- if(!is_operational()) //Autoeject if power is lost
+ if(!is_operational()) //Autoeject if power is lost (or the pod is dysfunctional due to whatever reason)
if(mob_occupant)
go_out()
log_cloning("[key_name(mob_occupant)] ejected from [src] at [AREACOORD(src)] due to power loss.")
@@ -319,7 +329,7 @@
mob_occupant.Unconscious(80)
var/dmg_mult = CONFIG_GET(number/damage_multiplier)
//Slowly get that clone healed and finished.
- mob_occupant.adjustCloneLoss(-((speed_coeff / 2) * dmg_mult))
+ mob_occupant.adjustCloneLoss(-((speed_coeff / 2) * dmg_mult), TRUE, TRUE)
if(reagents.has_reagent(/datum/reagent/medicine/synthflesh, fleshamnt))
reagents.remove_reagent(/datum/reagent/medicine/synthflesh, fleshamnt)
else if(reagents.has_reagent(/datum/reagent/blood, fleshamnt*3))
@@ -341,7 +351,7 @@
var/obj/item/bodypart/BP = I
BP.attach_limb(mob_occupant)
- use_power(7500) //This might need tweaking.
+ use_power(5000 * speed_coeff) //This might need tweaking.
else if(mob_occupant && (mob_occupant.cloneloss <= (100 - heal_level)))
connected_message("Cloning Process Complete.")
@@ -436,7 +446,7 @@
connected.updateUsrDialog()
return TRUE
-/obj/machinery/clonepod/proc/go_out()
+/obj/machinery/clonepod/proc/go_out(move = TRUE)
countdown.stop()
var/mob/living/mob_occupant = occupant
var/turf/T = get_turf(src)
@@ -456,6 +466,12 @@
if(!mob_occupant)
return
+
+ if(HAS_TRAIT(mob_occupant, TRAIT_NOCLONELOSS))
+ var/cl_loss = mob_occupant.getCloneLoss()
+ mob_occupant.adjustBruteLoss(cl_loss, FALSE)
+ mob_occupant.setCloneLoss(0, FALSE, TRUE)
+
current_insurance = null
REMOVE_TRAIT(mob_occupant, TRAIT_STABLEHEART, CLONING_POD_TRAIT)
REMOVE_TRAIT(mob_occupant, TRAIT_STABLELIVER, CLONING_POD_TRAIT)
@@ -469,7 +485,8 @@
to_chat(occupant, "There is a bright flash! You feel like a new being.")
mob_occupant.flash_act()
- occupant.forceMove(T)
+ if(move)
+ occupant.forceMove(T)
icon_state = "pod_0"
mob_occupant.domutcheck(1) //Waiting until they're out before possible monkeyizing. The 1 argument forces powers to manifest.
for(var/fl in unattached_flesh)
@@ -479,6 +496,13 @@
occupant = null
clonemind = null
+// Guess they moved out on their own, remove any clone status effects
+// If the occupant var is null, welp what can we do
+/obj/machinery/clonepod/Exited(atom/movable/AM, atom/newloc)
+ if(AM == occupant)
+ go_out(FALSE)
+ . = ..()
+
/obj/machinery/clonepod/proc/malfunction()
var/mob/living/mob_occupant = occupant
if(mob_occupant)
@@ -551,7 +575,7 @@
qdel(fl)
unattached_flesh.Cut()
- H.setCloneLoss(CLONE_INITIAL_DAMAGE) //Yeah, clones start with very low health, not with random, because why would they start with random health
+ H.setCloneLoss(CLONE_INITIAL_DAMAGE, TRUE, TRUE) //Yeah, clones start with very low health, not with random, because why would they start with random health
// In addition to being cellularly damaged, they also have no limbs or internal organs.
// Applying brainloss is done when the clone leaves the pod, so application of traumas can happen
// based on the level of damage sustained.
@@ -582,6 +606,16 @@
. = ..()
reagents.add_reagent(/datum/reagent/medicine/synthflesh, 100)
+//Experimental cloner; clones a body regardless of the owner's status.
+/obj/machinery/clonepod/experimental
+ name = "experimental cloning pod"
+ desc = "An ancient cloning pod. It seems to be an early prototype of the experimental cloners used in Nanotrasen Stations."
+ icon = 'icons/obj/machines/cloning.dmi'
+ icon_state = "pod_0"
+ req_access = null
+ circuit = /obj/item/circuitboard/machine/clonepod/experimental
+ internal_radio = FALSE
+
/*
* Manual -- A big ol' manual.
*/
diff --git a/code/game/machinery/computer/Operating.dm b/code/game/machinery/computer/Operating.dm
index 13c5d45a80ae1..e2872e8c31720 100644
--- a/code/game/machinery/computer/Operating.dm
+++ b/code/game/machinery/computer/Operating.dm
@@ -7,7 +7,7 @@
icon_screen = "crew"
icon_keyboard = "med_key"
circuit = /obj/item/circuitboard/computer/operating
- var/mob/living/carbon/human/patient
+
var/obj/structure/table/optable/table
var/obj/machinery/stasis/sbed
var/list/advanced_surgeries = list()
@@ -21,11 +21,11 @@
/obj/machinery/computer/operating/Destroy()
for(var/direction in GLOB.cardinals)
- table = locate(/obj/structure/table/optable, get_step(src, direction))
+ table = locate(/obj/structure/table/optable) in get_step(src, direction)
if(table && table.computer == src)
table.computer = null
else
- sbed = locate(/obj/machinery/stasis, get_step(src, direction))
+ sbed = locate(/obj/machinery/stasis) in get_step(src, direction)
if(sbed && sbed.op_computer == src)
sbed.op_computer = null
. = ..()
@@ -50,12 +50,12 @@
/obj/machinery/computer/operating/proc/find_table()
for(var/direction in GLOB.cardinals)
- table = locate(/obj/structure/table/optable, get_step(src, direction))
+ table = locate(/obj/structure/table/optable) in get_step(src, direction)
if(table)
table.computer = src
break
else
- sbed = locate(/obj/machinery/stasis, get_step(src, direction))
+ sbed = locate(/obj/machinery/stasis) in get_step(src, direction)
if(sbed)
sbed.op_computer = src
break
@@ -69,6 +69,7 @@
if(!ui)
ui = new(user, src, "OperatingComputer")
ui.open()
+ ui.set_autoupdate(TRUE)
/obj/machinery/computer/operating/ui_data(mob/user)
var/list/data = list()
@@ -80,20 +81,29 @@
surgery["desc"] = initial(S.desc)
surgeries += list(surgery)
data["surgeries"] = surgeries
- data["patient"] = null
+
+ //If there's no patient just hop to it yeah?
+ if(!table && !sbed)
+ data["patient"] = null
+ return data
+
+ var/mob/living/carbon/human/patient
+
if(table)
data["table"] = table
- if(!table.check_patient())
+ if(!table.check_eligible_patient())
return data
data["patient"] = list()
patient = table.patient
else
if(sbed)
data["table"] = sbed
- if(!sbed.check_patient())
+ if(!ishuman(sbed.occupant) && !ismonkey(sbed.occupant))
return data
data["patient"] = list()
- patient = sbed.occupant
+ if(isliving(sbed.occupant))
+ var/mob/living/live = sbed.occupant
+ patient = live
else
data["patient"] = null
return data
diff --git a/code/game/machinery/computer/_computer.dm b/code/game/machinery/computer/_computer.dm
index fb7e568c88a5f..603225f0cdc1f 100644
--- a/code/game/machinery/computer/_computer.dm
+++ b/code/game/machinery/computer/_computer.dm
@@ -8,13 +8,16 @@
active_power_usage = 300
max_integrity = 200
integrity_failure = 100
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 40, "acid" = 20)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 40, "acid" = 20, "stamina" = 0)
var/brightness_on = 2
var/icon_keyboard = "generic_key"
var/icon_screen = "generic"
var/clockwork = FALSE
var/time_to_scewdrive = 20
+ ///Should the [icon_state]_broken overlay be shown as an emissive or regular overlay?
+ var/broken_overlay_emissive = FALSE
+
/obj/machinery/computer/Initialize(mapload, obj/item/circuitboard/C)
. = ..()
power_change()
@@ -38,6 +41,7 @@
icon_screen = "ratvar[rand(1, 3)]"
icon_keyboard = "ratvar_key[rand(1, 2)]"
icon_state = "ratvarcomputer"
+ broken_overlay_emissive = TRUE
update_icon()
/obj/machinery/computer/narsie_act()
@@ -46,6 +50,7 @@
icon_screen = initial(icon_screen)
icon_keyboard = initial(icon_keyboard)
icon_state = initial(icon_state)
+ broken_overlay_emissive = initial(broken_overlay_emissive)
update_icon()
/obj/machinery/computer/update_icon()
@@ -57,12 +62,17 @@
add_overlay(icon_keyboard)
// This whole block lets screens ignore lighting and be visible even in the darkest room
- // We can't do this for many things that emit light unfortunately because it layers over things that would be on top of it
var/overlay_state = icon_screen
if(stat & BROKEN)
- overlay_state = "[icon_state]_broken"
- SSvis_overlays.add_vis_overlay(src, icon, overlay_state, layer, plane, dir)
- SSvis_overlays.add_vis_overlay(src, icon, overlay_state, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir, alpha=128)
+ if(broken_overlay_emissive)
+ overlay_state = "[icon_state]_broken"
+ else
+ add_overlay("[icon_state]_broken")
+ overlay_state = null
+
+ if(overlay_state)
+ SSvis_overlays.add_vis_overlay(src, icon, overlay_state, layer, plane, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, overlay_state, layer, EMISSIVE_PLANE, dir)
/obj/machinery/computer/power_change()
..()
diff --git a/code/game/machinery/computer/aifixer.dm b/code/game/machinery/computer/aifixer.dm
index 877611f5168e8..33c69629d4266 100644
--- a/code/game/machinery/computer/aifixer.dm
+++ b/code/game/machinery/computer/aifixer.dm
@@ -21,6 +21,11 @@
return ..()
+/obj/machinery/computer/aifixer/ui_requires_update(mob/user, datum/tgui/ui)
+ . = ..()
+ if(restoring)
+ . = TRUE
+
/obj/machinery/computer/aifixer/ui_state(mob/user)
return GLOB.default_state
@@ -29,6 +34,7 @@
if(!ui)
ui = new(user, src, "AiRestorer")
ui.open()
+ ui.set_autoupdate(TRUE)
/obj/machinery/computer/aifixer/ui_data(mob/user)
var/list/data = list()
@@ -84,6 +90,8 @@
restoring = Fix()
if(oldstat != occupier.stat)
update_icon()
+ if(!restoring)
+ ui_update() // One final update
/obj/machinery/computer/aifixer/update_icon()
..()
@@ -116,6 +124,7 @@
to_chat(user, "Transfer Successful: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.")
card.AI = null
update_icon()
+ ui_update()
else //Uploading AI from terminal to card
if(occupier && !restoring)
@@ -125,6 +134,7 @@
card.AI = occupier
occupier = null
update_icon()
+ ui_update()
else if (restoring)
to_chat(user, "ERROR: Reconstruction in progress.")
else if (!occupier)
diff --git a/code/game/machinery/computer/apc_control.dm b/code/game/machinery/computer/apc_control.dm
index a0c6ffdc271b8..5be24afde5728 100644
--- a/code/game/machinery/computer/apc_control.dm
+++ b/code/game/machinery/computer/apc_control.dm
@@ -38,7 +38,7 @@
..(user)
/obj/machinery/computer/apc_control/proc/check_apc(obj/machinery/power/apc/APC)
- return APC.z == z && !APC.malfhack && !APC.aidisabled && !(APC.obj_flags & EMAGGED) && !APC.stat && !istype(APC.area, /area/ai_monitored) && !APC.area.outdoors
+ return APC.get_virtual_z_level() == get_virtual_z_level() && !APC.malfhack && !APC.aidisabled && !(APC.obj_flags & EMAGGED) && !APC.stat && !istype(APC.area, /area/ai_monitored) && !APC.area.outdoors
/obj/machinery/computer/apc_control/ui_interact(mob/living/user)
. = ..()
@@ -62,7 +62,7 @@
if(result_filters["Responsive"] && !APC.aidisabled)
continue
dat += "[A] \
- Charge: [APC.cell ? "[DisplayEnergy(APC.cell.charge)] / [DisplayEnergy(APC.cell.maxcharge)] ([round((APC.cell.charge / APC.cell.maxcharge) * 100)]%)" : "No Powercell Installed"] \
+ Charge: [APC.cell ? "[DisplayEnergy(APC.cell.charge)] / [DisplayEnergy(APC.cell.maxcharge)] ([round((APC.cell.charge / APC.cell.maxcharge) * 100)]%)" : "No power cell installed."] \
Area: [APC.area] \
[APC.aidisabled || APC.panel_open ? "APC does not respond to interface query." : "APC responds to interface query."]
"
dat += "Check Logs "
diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm
index 5172dbfdb48b4..1f696a9ac3eb1 100644
--- a/code/game/machinery/computer/arcade.dm
+++ b/code/game/machinery/computer/arcade.dm
@@ -30,6 +30,7 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
/obj/item/coin/antagtoken = 2,
/obj/item/stack/tile/fakespace/loaded = 2,
/obj/item/stack/tile/fakepit/loaded = 2,
+ /obj/item/stack/tile/eighties/loaded = 2,
/obj/item/toy/toy_xeno = 2,
/obj/item/storage/box/actionfigure = 1,
/obj/item/restraints/handcuffs/fake = 2,
@@ -43,12 +44,21 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
/obj/item/extendohand/acme = 1,
/obj/item/hot_potato/harmless/toy = 1,
/obj/item/card/emagfake = 1,
+ /obj/item/disk/nuclear/fake/obvious = 1,
/obj/item/clothing/shoes/wheelys = 2,
/obj/item/clothing/shoes/kindleKicks = 2,
/obj/item/toy/plush/moth = 2,
+ /obj/item/toy/plush/rouny = 2,
/obj/item/storage/box/heretic_asshole = 1,
/obj/item/toy/eldrich_book = 1,
- /obj/item/storage/belt/military/snack = 2))
+ /obj/item/storage/belt/military/snack = 2,
+ /obj/item/choice_beacon/pet/cat = 1,
+ /obj/item/choice_beacon/pet/mouse = 1,
+ /obj/item/choice_beacon/pet/corgi = 1,
+ /obj/item/choice_beacon/pet/hamster = 1,
+ /obj/item/choice_beacon/pet/pug = 1,
+ /obj/item/choice_beacon/pet/pingu = 1,
+ /obj/item/choice_beacon/pet/clown = 1))
/obj/machinery/computer/arcade
name = "random arcade"
@@ -57,6 +67,7 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
icon_keyboard = "no_keyboard"
icon_screen = "invaders"
clockwork = TRUE //it'd look weird
+ broken_overlay_emissive = TRUE
var/list/prize_override
var/prizeselect = /obj/item/coin/arcade_token
light_color = LIGHT_COLOR_GREEN
@@ -80,29 +91,20 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
/obj/machinery/computer/arcade/proc/prizevend(mob/user)
SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "arcade", /datum/mood_event/arcade)
+ var/atom/movable/the_prize
if(prob(0.0001)) //1 in a million
- new /obj/item/gun/energy/pulse/prize(src)
+ the_prize = new /obj/item/gun/energy/pulse/prize(drop_location())
SSmedals.UnlockMedal(MEDAL_PULSE, user.client)
else
- new prizeselect(src)
+ the_prize = new prizeselect(drop_location())
- var/atom/movable/the_prize = pick(contents)
visible_message("[src] dispenses [the_prize]!", "You hear a chime and a clunk.")
- the_prize.forceMove(get_turf(src))
-
/obj/machinery/computer/arcade/proc/redeem(mob/user)
- var/redeemselect
- if(!contents.len)
- if(prize_override)
- redeemselect = pickweight(prize_override)
- else
- redeemselect = pickweight(GLOB.arcade_prize_pool)
+ var/redeemselect = pickweight(length(prize_override) ? prize_override : GLOB.arcade_prize_pool)
- new redeemselect(src)
- var/atom/movable/the_prize = pick(contents)
+ var/atom/movable/the_prize = new redeemselect(drop_location())
visible_message("[src] dispenses [the_prize]!", "You hear a chime and a clunk.")
- the_prize.forceMove(get_turf(src))
/obj/machinery/computer/arcade/attackby(obj/item/W, mob/user)
if(istype(W, /obj/item/coin/arcade_token) || istype(W, /obj/item/coin/bananium))
@@ -196,7 +198,7 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
blocked = TRUE
var/attackamt = rand(2,6)
temp = "You attack for [attackamt] damage!"
- playsound(loc, 'sound/arcade/hit.ogg', 50, 1, extrarange = -3, falloff = 10)
+ playsound(loc, 'sound/arcade/hit.ogg', 50, 1, extrarange = -3, falloff_exponent = 10)
updateUsrDialog()
if(turtle > 0)
turtle--
@@ -210,7 +212,7 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
var/pointamt = rand(1,3)
var/healamt = rand(6,8)
temp = "You use [pointamt] magic to heal for [healamt] damage!"
- playsound(loc, 'sound/arcade/heal.ogg', 50, 1, extrarange = -3, falloff = 10)
+ playsound(loc, 'sound/arcade/heal.ogg', 50, 1, extrarange = -3, falloff_exponent = 10)
updateUsrDialog()
turtle++
@@ -225,7 +227,7 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
blocked = TRUE
var/chargeamt = rand(4,7)
temp = "You regain [chargeamt] points"
- playsound(loc, 'sound/arcade/mana.ogg', 50, 1, extrarange = -3, falloff = 10)
+ playsound(loc, 'sound/arcade/mana.ogg', 50, 1, extrarange = -3, falloff_exponent = 10)
player_mp += chargeamt
if(turtle > 0)
turtle--
@@ -260,7 +262,7 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
if(!gameover)
gameover = TRUE
temp = "[enemy_name] has fallen! Rejoice!"
- playsound(loc, 'sound/arcade/win.ogg', 50, 1, extrarange = -3, falloff = 10)
+ playsound(loc, 'sound/arcade/win.ogg', 50, 1, extrarange = -3, falloff_exponent = 10)
if(obj_flags & EMAGGED)
new /obj/effect/spawner/newbomb/timer/syndicate(loc)
@@ -277,13 +279,13 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
else if ((obj_flags & EMAGGED) && (turtle >= 4))
var/boomamt = rand(5,10)
temp = "[enemy_name] throws a bomb, exploding you for [boomamt] damage!"
- playsound(loc, 'sound/arcade/boom.ogg', 50, 1, extrarange = -3, falloff = 10)
+ playsound(loc, 'sound/arcade/boom.ogg', 50, 1, extrarange = -3, falloff_exponent = 10)
player_hp -= boomamt
else if ((enemy_mp <= 5) && (prob(70)))
var/stealamt = rand(2,3)
temp = "[enemy_name] steals [stealamt] of your power!"
- playsound(loc, 'sound/arcade/steal.ogg', 50, 1, extrarange = -3, falloff = 10)
+ playsound(loc, 'sound/arcade/steal.ogg', 50, 1, extrarange = -3, falloff_exponent = 10)
player_mp -= stealamt
updateUsrDialog()
@@ -291,27 +293,27 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
gameover = TRUE
sleep(10)
temp = "You have been drained! GAME OVER"
- playsound(loc, 'sound/arcade/lose.ogg', 50, 1, extrarange = -3, falloff = 10)
+ playsound(loc, 'sound/arcade/lose.ogg', 50, 1, extrarange = -3, falloff_exponent = 10)
if(obj_flags & EMAGGED)
usr.gib()
SSblackbox.record_feedback("nested tally", "arcade_results", 1, list("loss", "mana", (obj_flags & EMAGGED ? "emagged":"normal")))
else if ((enemy_hp <= 10) && (enemy_mp > 4))
temp = "[enemy_name] heals for 4 health!"
- playsound(loc, 'sound/arcade/heal.ogg', 50, 1, extrarange = -3, falloff = 10)
+ playsound(loc, 'sound/arcade/heal.ogg', 50, 1, extrarange = -3, falloff_exponent = 10)
enemy_hp += 4
enemy_mp -= 4
else
var/attackamt = rand(3,6)
temp = "[enemy_name] attacks for [attackamt] damage!"
- playsound(loc, 'sound/arcade/hit.ogg', 50, 1, extrarange = -3, falloff = 10)
+ playsound(loc, 'sound/arcade/hit.ogg', 50, 1, extrarange = -3, falloff_exponent = 10)
player_hp -= attackamt
if ((player_mp <= 0) || (player_hp <= 0))
gameover = TRUE
temp = "You have been crushed! GAME OVER"
- playsound(loc, 'sound/arcade/lose.ogg', 50, 1, extrarange = -3, falloff = 10)
+ playsound(loc, 'sound/arcade/lose.ogg', 50, 1, extrarange = -3, falloff_exponent = 10)
if(obj_flags & EMAGGED)
usr.gib()
SSblackbox.record_feedback("nested tally", "arcade_results", 1, list("loss", "hp", (obj_flags & EMAGGED ? "emagged":"normal")))
@@ -344,6 +346,15 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
// *** THE ORION TRAIL ** //
+/obj/item/gamer_pamphlet
+ name = "pamphlet - \'Violent Video Games and You\'"
+ desc = "A pamphlet encouraging the reader to maintain a balanced lifestyle and take care of their mental health, while still enjoying video games in a healthy way. You probably don't need this..."
+ icon = 'icons/obj/bureaucracy.dmi'
+ icon_state = "pamphlet"
+ item_state = "paper"
+ w_class = WEIGHT_CLASS_TINY
+
+
#define ORION_TRAIL_WINTURN 9
//Orion Trail Events
@@ -400,6 +411,16 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
var/gameStatus = ORION_STATUS_START
var/canContinueEvent = 0
+ var/obj/item/radio/Radio
+ var/static/list/gamers = list()
+ var/killed_crew = 0
+
+
+/obj/machinery/computer/arcade/orion_trail/Initialize()
+ . = ..()
+ Radio = new /obj/item/radio(src)
+ Radio.listening = 0
+
/obj/machinery/computer/arcade/orion_trail/kobayashi
name = "Kobayashi Maru control computer"
desc = "A test for cadets"
@@ -414,7 +435,7 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
stops = list("Pluto","Asteroid Belt","Proxima Centauri","Dead Space","Rigel Prime","Tau Ceti Beta","Black Hole","Space Outpost Beta-9","Orion Prime")
stopblurbs = list(
"Pluto, long since occupied with long-range sensors and scanners, stands ready to, and indeed continues to probe the far reaches of the galaxy.",
- "At the edge of the Sol system lies a treacherous asteroid belt. Many have been crushed by stray asteroids and misguided judgement.",
+ "At the edge of the Sol system lies a treacherous asteroid belt. Many have been crushed by stray asteroids and misguided judgment.",
"The nearest star system to Sol, in ages past it stood as a reminder of the boundaries of sub-light travel, now a low-population sanctuary for adventurers and traders.",
"This region of space is particularly devoid of matter. Such low-density pockets are known to exist, but the vastness of it is astounding.",
"Rigel Prime, the center of the Rigel system, burns hot, basking its planetary bodies in warmth and radiation.",
@@ -441,12 +462,45 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
event = null
gameStatus = ORION_STATUS_NORMAL
lings_aboard = 0
+ killed_crew = 0
//spaceport junk
spaceport_raided = 0
spaceport_freebie = 0
last_spaceport_action = ""
+/obj/machinery/computer/arcade/orion_trail/proc/report_player(mob/gamer)
+ if(gamers[gamer] == -2)
+ return // enough harassing them
+
+ if(gamers[gamer] == -1)
+ say("WARNING: Continued antisocial behavior detected: Dispensing self-help literature.")
+ new /obj/item/gamer_pamphlet(get_turf(src))
+ gamers[gamer]--
+ return
+
+ if(!(gamer in gamers))
+ gamers[gamer] = 0
+
+ gamers[gamer]++ // How many times the player has 'prestiged' (massacred their crew)
+
+ if(gamers[gamer] > 2 && prob(20 * gamers[gamer]))
+
+ Radio.set_frequency(FREQ_SECURITY)
+ Radio.talk_into(src, "SECURITY ALERT: Crewmember [gamer] recorded displaying antisocial tendencies in [get_area(src)]. Please watch for violent behavior.", FREQ_SECURITY)
+
+ Radio.set_frequency(FREQ_MEDICAL)
+ Radio.talk_into(src, "PSYCH ALERT: Crewmember [gamer] recorded displaying antisocial tendencies in [get_area(src)]. Please schedule psych evaluation.", FREQ_MEDICAL)
+
+ gamers[gamer] = -1
+
+ if(!isnull(GLOB.data_core.general))
+ for(var/datum/data/record/R in GLOB.data_core.general)
+ if(R.fields["name"] == gamer.name)
+ R.fields["m_stat"] = "*Unstable*"
+ return
+
+
/obj/machinery/computer/arcade/orion_trail/ui_interact(mob/user)
. = ..()
if(fuel <= 0 || food <=0 || settlers.len == 0)
@@ -570,16 +624,14 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
if(ORION_TRAIL_COLLISION) //by far the most damaging event
if(prob(90))
playsound(loc, 'sound/effects/bang.ogg', 100, 1)
- var/turf/open/floor/F
- for(F in orange(1, src))
+ for(var/turf/open/floor/F in RANGE_TURFS(1, src))
F.ScrapeAway()
say("Something slams into the floor around [src], exposing it to space!")
if(hull)
sleep(10)
say("A new floor suddenly appears around [src]. What the hell?")
playsound(loc, 'sound/weapons/genhit.ogg', 100, 1)
- var/turf/open/space/T
- for(T in orange(1, src))
+ for(var/turf/open/space/T in RANGE_TURFS(1, src))
T.PlaceOnTop(/turf/open/floor/plating)
else
say("Something slams into the floor around [src] - luckily, it didn't get through!")
@@ -678,6 +730,7 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
if(gameStatus == ORION_STATUS_NORMAL || event == ORION_TRAIL_LING)
var/sheriff = remove_crewmember() //I shot the sheriff
playsound(loc,'sound/weapons/gunshot.ogg', 100, 1)
+ killed_crew++
if(settlers.len == 0 || alive == 0)
say("The last crewmember [sheriff], shot themselves, GAME OVER!")
@@ -686,6 +739,11 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
obj_flags &= EMAGGED
gameStatus = ORION_STATUS_GAMEOVER
event = null
+
+
+ if(killed_crew >= 4)
+ report_player(usr)
+
else if(obj_flags & EMAGGED)
if(usr.name == sheriff)
say("The crew of the ship chose to kill [usr.name]!")
@@ -693,6 +751,7 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
if(event == ORION_TRAIL_LING) //only ends the ORION_TRAIL_LING event, since you can do this action in multiple places
event = null
+ killed_crew-- // the kill was valid
//Spaceport specific interactions
//they get a header because most of them don't reset event (because it's a shop, you leave when you want to)
@@ -705,6 +764,7 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
fuel -= 10
food -= 10
event()
+ killed_crew-- // I mean not really but you know
else if(href_list["sellcrew"]) //sell a crewmember
if(gameStatus == ORION_STATUS_MARKET)
@@ -826,6 +886,7 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
if(ORION_TRAIL_OLDSHIP)
eventdat += " Your crew spots an old ship floating through space. It might have some supplies, but then again it looks rather unsafe."
eventdat += "
"
+ canContinueEvent = 1
if(ORION_TRAIL_SEARCH)
switch(rand(100))
@@ -1125,6 +1186,10 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
newgame()
obj_flags |= EMAGGED
+/obj/machinery/computer/arcade/orion_trail/Destroy()
+ QDEL_NULL(Radio)
+ . = ..()
+
/mob/living/simple_animal/hostile/syndicate/ranged/smg/orion
name = "spaceport security"
desc = "Premier corporate security forces for all spaceports found along the Orion Trail."
@@ -1183,23 +1248,51 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
if(!iscarbon(user))
return
var/mob/living/carbon/c_user = user
- if(!c_user.get_bodypart(BODY_ZONE_L_ARM) && !c_user.get_bodypart(BODY_ZONE_R_ARM))
- return
- to_chat(c_user, "You move your hand towards the machine, and begin to hesitate as a bloodied guillotine emerges from inside of it...")
- if(do_after(c_user, 50, target = src))
- to_chat(c_user, "The guillotine drops on your arm, and the machine sucks it in!")
- playsound(loc, 'sound/weapons/slice.ogg', 25, 1, -1)
- var/which_hand = BODY_ZONE_L_ARM
- if(!(c_user.active_hand_index % 2))
- which_hand = BODY_ZONE_R_ARM
- var/obj/item/bodypart/chopchop = c_user.get_bodypart(which_hand)
- chopchop.dismember()
- qdel(chopchop)
- playsound(loc, 'sound/arcade/win.ogg', 50, 1, extrarange = -3, falloff = 10)
- for(var/i=1; i<=rand(3,5); i++)
- prizevend(user)
+ if(obj_flags & EMAGGED)
+ if(!c_user.get_bodypart(BODY_ZONE_L_ARM) && !c_user.get_bodypart(BODY_ZONE_R_ARM))
+ return
+ to_chat(c_user, "You move your hand towards the machine, and begin to hesitate as an extra-bloodied guillotine emerges from inside of it...")
+ if(do_after(c_user, 50, target = src))
+ to_chat(c_user, "Robotic arms shoot out of the machine, remove all your limbs, and suck them in!")
+ playsound(loc, 'sound/weapons/slice.ogg', 25, 1, -1)
+ for(var/X in c_user.bodyparts)
+ var/obj/item/bodypart/BP = X
+ if(BP.body_part != HEAD && BP.body_part != CHEST)
+ if(BP.dismemberable)
+ BP.dismember()
+ qdel(BP)
+ playsound(loc, 'sound/arcade/win.ogg', 50, 1, extrarange = -3, falloff_exponent = 10)
+ for(var/i=1 to rand(20, 30))
+ prizevend(user)
+ else
+ to_chat(c_user, "You (wisely) decide against putting your hand in the machine.")
else
- to_chat(c_user, "You (wisely) decide against putting your hand in the machine.")
+ if(!c_user.get_bodypart(BODY_ZONE_L_ARM) && !c_user.get_bodypart(BODY_ZONE_R_ARM))
+ return
+ to_chat(c_user, "You move your hand towards the machine, and begin to hesitate as a bloodied guillotine emerges from inside of it...")
+ if(do_after(c_user, 50, target = src))
+ to_chat(c_user, "The guillotine drops on your arm, and the machine sucks it in!")
+ playsound(loc, 'sound/weapons/slice.ogg', 25, 1, -1)
+ var/which_hand = BODY_ZONE_L_ARM
+ if(!(c_user.active_hand_index % 2))
+ which_hand = BODY_ZONE_R_ARM
+ var/obj/item/bodypart/chopchop = c_user.get_bodypart(which_hand)
+ chopchop.dismember()
+ qdel(chopchop)
+ playsound(loc, 'sound/arcade/win.ogg', 50, 1, extrarange = -3, falloff_exponent = 10)
+ for(var/i=1 to rand(3, 5))
+ prizevend(user)
+ else
+ to_chat(c_user, "You (wisely) decide against putting your hand in the machine.")
+
+
+/obj/machinery/computer/arcade/amputation/emag_act(mob/user)
+ if(obj_flags & EMAGGED)
+ return
+ to_chat(user, "You override the safety systems on the arcade machine.")
+ name = "Mediborg's Amputation Adventure: Deluxe Edition"
+ desc = "A picture of a blood-soaked medical cyborg flashes on the screen. The mediborg has glowing red eyes, and a speech bubble that says, \"Put your hand in the machine if you aren't a coward!\""
+ obj_flags |= EMAGGED
#undef ORION_TRAIL_WINTURN
#undef ORION_TRAIL_RAIDERS
diff --git a/code/game/machinery/computer/arena.dm b/code/game/machinery/computer/arena.dm
index 9493957df2f8b..38d90d406236a 100644
--- a/code/game/machinery/computer/arena.dm
+++ b/code/game/machinery/computer/arena.dm
@@ -187,7 +187,7 @@
to_chat(user,"[ckey] removed from [team] team.")
/obj/machinery/computer/arena/proc/spawn_member(obj/machinery/arena_spawn/spawnpoint,ckey,team)
- var/mob/oldbody = get_mob_by_key(ckey)
+ var/mob/oldbody = get_mob_by_ckey(ckey)
if(!isobserver(oldbody))
return
var/mob/living/carbon/human/M = new/mob/living/carbon/human(get_turf(spawnpoint))
@@ -217,7 +217,7 @@
. = list()
for(var/team in team_keys)
for(var/key in team_keys[team])
- var/mob/M = get_mob_by_key(key)
+ var/mob/M = get_mob_by_ckey(key)
if(M)
. += M
@@ -336,7 +336,7 @@
dat += "
"
for(var/ckey in team_keys[team])
var/player_status = "Not Present"
- var/mob/M = get_mob_by_key(ckey)
+ var/mob/M = get_mob_by_ckey(ckey)
if(M)
//Should define waiting room upper/lower corner and check if they're there instead of generic live/dead check
if(isobserver(M))
diff --git a/code/game/machinery/computer/atmos_alert.dm b/code/game/machinery/computer/atmos_alert.dm
index 454f459621bbd..60eeb51b1a79a 100644
--- a/code/game/machinery/computer/atmos_alert.dm
+++ b/code/game/machinery/computer/atmos_alert.dm
@@ -57,7 +57,8 @@
to_chat(usr, "Minor alarm for [zone] cleared.")
minor_alarms -= zone
. = TRUE
- update_icon()
+ if(.)
+ update_icon()
/obj/machinery/computer/atmos_alert/proc/set_frequency(new_frequency)
SSradio.remove_object(src, receive_frequency)
@@ -81,6 +82,7 @@
else if (severity == "minor")
minor_alarms += zone
update_icon()
+ ui_update()
return
/obj/machinery/computer/atmos_alert/update_icon()
diff --git a/code/game/machinery/computer/atmos_control.dm b/code/game/machinery/computer/atmos_control.dm
index 0342825557dd2..548a2d69d3c18 100644
--- a/code/game/machinery/computer/atmos_control.dm
+++ b/code/game/machinery/computer/atmos_control.dm
@@ -63,7 +63,7 @@
var/total_moles = air_sample.total_moles()
if(total_moles)
for(var/gas_id in air_sample.get_gases())
- var/gas_name = GLOB.meta_gas_info[gas_id][META_GAS_NAME]
+ var/gas_name = GLOB.gas_data.names[gas_id]
signal.data["gases"][gas_name] = air_sample.get_moles(gas_id) / total_moles * 100
radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA)
@@ -76,11 +76,11 @@
/obj/machinery/air_sensor/Initialize()
. = ..()
- SSair.atmos_machinery += src
+ SSair.atmos_air_machinery += src
set_frequency(frequency)
/obj/machinery/air_sensor/Destroy()
- SSair.atmos_machinery -= src
+ SSair.atmos_air_machinery -= src
SSradio.remove_object(src, frequency)
return ..()
@@ -137,6 +137,7 @@ GLOBAL_LIST_EMPTY(atmos_air_controllers)
if(!ui)
ui = new(user, src, "AtmosControlConsole")
ui.open()
+ ui.set_autoupdate(TRUE) // Gas sensors
/obj/machinery/computer/atmos_control/ui_data(mob/user)
var/data = list()
@@ -254,7 +255,16 @@ GLOBAL_LIST_EMPTY(atmos_air_controllers)
/obj/machinery/computer/atmos_control/tank/proc/reconnect(mob/user)
var/list/IO = list()
var/datum/radio_frequency/freq = SSradio.return_frequency(frequency)
- var/list/devices = freq.devices["_default"]
+
+ var/list/devices = list()
+ var/list/device_refs = freq.devices["_default"]
+ for(var/datum/weakref/device_ref as anything in device_refs)
+ var/atom/device = device_ref.resolve()
+ if(!device)
+ device_refs -= device_ref
+ continue
+ devices += device
+
for(var/obj/machinery/atmospherics/components/unary/vent_pump/U in devices)
var/list/text = splittext(U.id_tag, "_")
IO |= text[1]
@@ -288,6 +298,7 @@ GLOBAL_LIST_EMPTY(atmos_air_controllers)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "AtmosControlConsole")
+ ui.set_autoupdate(TRUE) // Gas sensors
ui.open()
/obj/machinery/computer/atmos_control/tank/ui_data(mob/user)
@@ -295,9 +306,10 @@ GLOBAL_LIST_EMPTY(atmos_air_controllers)
data["tank"] = TRUE
data["inputting"] = input_info ? input_info["power"] : FALSE
data["inputRate"] = input_info ? input_info["volume_rate"] : 0
+ data["maxInputRate"] = input_info ? MAX_TRANSFER_RATE : 0
data["outputting"] = output_info ? output_info["power"] : FALSE
data["outputPressure"] = output_info ? output_info["internal"] : 0
-
+ data["maxOutputPressure"] = output_info ? MAX_OUTPUT_PRESSURE : 0
return data
/obj/machinery/computer/atmos_control/tank/ui_act(action, params)
@@ -323,7 +335,7 @@ GLOBAL_LIST_EMPTY(atmos_air_controllers)
if("pressure")
var/target = text2num(params["pressure"])
if(!isnull(target))
- target = clamp(target, 0, 4500)
+ target = clamp(target, 0, MAX_OUTPUT_PRESSURE)
signal.data += list("tag" = output_tag, "set_internal_pressure" = target)
. = TRUE
radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA)
diff --git a/code/game/machinery/computer/buildandrepair.dm b/code/game/machinery/computer/buildandrepair.dm
index aae04e2ce3d59..858b72a208a15 100644
--- a/code/game/machinery/computer/buildandrepair.dm
+++ b/code/game/machinery/computer/buildandrepair.dm
@@ -132,7 +132,6 @@
..()
/obj/structure/frame/computer/AltClick(mob/user)
- ..()
if(!isliving(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
return
diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm
index c485aeb0ee149..8485dfd09f4df 100644
--- a/code/game/machinery/computer/camera.dm
+++ b/code/game/machinery/computer/camera.dm
@@ -12,14 +12,16 @@
var/list/network = list("ss13")
var/obj/machinery/camera/active_camera
+ /// The turf where the camera was last updated.
+ var/turf/last_camera_turf
var/list/concurrent_users = list()
var/long_ranged = FALSE
// Stuff needed to render the map
var/map_name
- var/obj/screen/map_view/cam_screen
- var/obj/screen/plane_master/lighting/cam_plane_master
- var/obj/screen/background/cam_background
+ var/atom/movable/screen/map_view/cam_screen
+ var/atom/movable/screen/plane_master/lighting/cam_plane_master
+ var/atom/movable/screen/background/cam_background
/obj/machinery/computer/security/Initialize()
. = ..()
@@ -64,9 +66,10 @@
/obj/machinery/computer/security/ui_interact(mob/user, datum/tgui/ui)
// Update UI
ui = SStgui.try_update_ui(user, src, ui)
- // Show static if can't use the camera
- if(!active_camera?.can_use())
- show_camera_static()
+
+ // Update the camera, showing static if necessary and updating data if the location has moved.
+ update_active_camera_screen()
+
if(!ui)
var/user_ref = REF(user)
var/is_living = isliving(user)
@@ -85,6 +88,7 @@
// Open UI
ui = new(user, src, "CameraConsole")
ui.open()
+ ui.set_autoupdate(FALSE)
/obj/machinery/computer/security/ui_data()
var/list/data = list()
@@ -119,30 +123,52 @@
var/list/cameras = get_available_cameras()
var/obj/machinery/camera/C = cameras[c_tag]
active_camera = C
+ ui_update()
playsound(src, get_sfx("terminal_type"), 25, FALSE)
- // Show static if can't use the camera
- if(!active_camera?.can_use())
- show_camera_static()
+ if(!C)
return TRUE
- var/list/visible_turfs = list()
- for(var/turf/T in (C.isXRay() \
- ? range(C.view_range, C) \
- : view(C.view_range, get_turf(C))))
- visible_turfs += T
+ update_active_camera_screen()
- var/list/bbox = get_bbox_of_atoms(visible_turfs)
- var/size_x = bbox[3] - bbox[1] + 1
- var/size_y = bbox[4] - bbox[2] + 1
+ return TRUE
- cam_screen.vis_contents = visible_turfs
- cam_background.icon_state = "clear"
- cam_background.fill_rect(1, 1, size_x, size_y)
+/obj/machinery/computer/security/proc/update_active_camera_screen()
+ // Show static if can't use the camera
+ if(!active_camera?.can_use())
+ show_camera_static()
+ return
- return TRUE
+ var/list/visible_turfs = list()
-/obj/machinery/computer/security/ui_close(mob/user)
+ // Is this camera located in or attached to a living thing? If so, assume the camera's loc is the living thing.
+ var/atom/cam_location = isliving(active_camera.loc) ? active_camera.loc : active_camera
+
+ // If we're not forcing an update for some reason and the cameras are in the same location,
+ // we don't need to update anything.
+ // Most security cameras will end here as they're not moving.
+ var/newturf = get_turf(cam_location)
+ if(last_camera_turf == newturf)
+ return
+
+ // Cameras that get here are moving, and are likely attached to some moving atom such as cyborgs.
+ last_camera_turf = get_turf(cam_location)
+
+ if(active_camera.isXRay())
+ visible_turfs += RANGE_TURFS(active_camera.view_range, cam_location)
+ else
+ for(var/turf/T in view(active_camera.view_range, cam_location))
+ visible_turfs += T
+
+ var/list/bbox = get_bbox_of_atoms(visible_turfs)
+ var/size_x = bbox[3] - bbox[1] + 1
+ var/size_y = bbox[4] - bbox[2] + 1
+
+ cam_screen.vis_contents = visible_turfs
+ cam_background.icon_state = "clear"
+ cam_background.fill_rect(1, 1, size_x, size_y)
+
+/obj/machinery/computer/security/ui_close(mob/user, datum/tgui/tgui)
var/user_ref = REF(user)
var/is_living = isliving(user)
// Living creature or not, we remove you anyway.
@@ -164,7 +190,7 @@
/obj/machinery/computer/security/proc/get_available_cameras()
var/list/L = list()
for (var/obj/machinery/camera/C in GLOB.cameranet.cameras)
- if((is_away_level(z) || is_away_level(C.z)) && (C.z != z))//if on away mission, can only receive feed from same z_level cameras
+ if((is_away_level(z) || is_away_level(C.z)) && (C.get_virtual_z_level() != get_virtual_z_level()))//if on away mission, can only receive feed from same z_level cameras
continue
L.Add(C)
var/list/D = list()
@@ -189,6 +215,7 @@
icon_keyboard = "no_keyboard"
icon_screen = "detective_tv"
clockwork = TRUE //it'd look weird
+ broken_overlay_emissive = TRUE
pass_flags = PASSTABLE
/obj/machinery/computer/security/mining
@@ -235,6 +262,7 @@
density = FALSE
circuit = null
clockwork = TRUE //it'd look very weird
+ broken_overlay_emissive = TRUE
light_power = 0
/obj/machinery/computer/security/telescreen/update_icon()
@@ -252,16 +280,15 @@
density = FALSE
circuit = null
long_ranged = TRUE
- interaction_flags_atom = NONE // interact() is called by BigClick()
var/icon_state_off = "entertainment_blank"
var/icon_state_on = "entertainment"
-/obj/machinery/computer/security/telescreen/entertainment/Initialize()
- . = ..()
- RegisterSignal(src, COMSIG_CLICK, .proc/BigClick)
+//Can use this telescreen at long range.
+/obj/machinery/computer/security/telescreen/entertainment/ui_state(mob/user)
+ return GLOB.not_incapacitated_state
-// Bypass clickchain to allow humans to use the telescreen from a distance
-/obj/machinery/computer/security/telescreen/entertainment/proc/BigClick()
+/obj/machinery/computer/security/telescreen/entertainment/examine(mob/user)
+ . = ..()
interact(usr)
/obj/machinery/computer/security/telescreen/entertainment/proc/notify(on)
diff --git a/code/game/machinery/computer/camera_advanced.dm b/code/game/machinery/computer/camera_advanced.dm
index 7bfdb568b64b7..995ac321dd657 100644
--- a/code/game/machinery/computer/camera_advanced.dm
+++ b/code/game/machinery/computer/camera_advanced.dm
@@ -5,7 +5,7 @@
icon_keyboard = "security_key"
var/list/z_lock = list() // Lock use to these z levels
var/lock_override = NONE
- var/mob/camera/aiEye/remote/eyeobj
+ var/mob/camera/ai_eye/remote/eyeobj
var/mob/living/current_user = null
var/list/networks = list("ss13")
var/datum/action/innate/camera_off/off_action = new
@@ -80,11 +80,10 @@
user.unset_machine()
/obj/machinery/computer/camera_advanced/Destroy()
- if(current_user)
- current_user.unset_machine()
if(eyeobj)
- qdel(eyeobj)
+ QDEL_NULL(eyeobj)
QDEL_LIST(actions)
+ current_user = null
return ..()
/obj/machinery/computer/camera_advanced/on_unset_machine(mob/M)
@@ -161,7 +160,7 @@
if(should_supress_view_changes )
user.client.view_size.supress()
-/mob/camera/aiEye/remote
+/mob/camera/ai_eye/remote
name = "Inactive Camera Eye"
ai_detector_visible = FALSE
var/sprint = 10
@@ -173,25 +172,25 @@
var/visible_icon = 0
var/image/user_image = null
-/mob/camera/aiEye/remote/update_remote_sight(mob/living/user)
+/mob/camera/ai_eye/remote/update_remote_sight(mob/living/user)
user.see_invisible = SEE_INVISIBLE_LIVING //can't see ghosts through cameras
user.sight = SEE_TURFS | SEE_BLACKNESS
user.see_in_dark = 2
return 1
-/mob/camera/aiEye/remote/Destroy()
+/mob/camera/ai_eye/remote/Destroy()
if(origin && eye_user)
origin.remove_eye_control(eye_user,src)
origin = null
. = ..()
eye_user = null
-/mob/camera/aiEye/remote/GetViewerClient()
+/mob/camera/ai_eye/remote/GetViewerClient()
if(eye_user)
return eye_user.client
return null
-/mob/camera/aiEye/remote/setLoc(T)
+/mob/camera/ai_eye/remote/setLoc(T)
if(eye_user)
T = get_turf(T)
if (T)
@@ -207,7 +206,7 @@
user_image = image(icon,loc,icon_state,FLY_LAYER)
eye_user.client.images += user_image
-/mob/camera/aiEye/remote/relaymove(mob/user,direct)
+/mob/camera/ai_eye/remote/relaymove(mob/user,direct)
var/initial = initial(sprint)
var/max_sprint = 50
@@ -234,7 +233,7 @@
if(!target || !isliving(target))
return
var/mob/living/C = target
- var/mob/camera/aiEye/remote/remote_eye = C.remote_control
+ var/mob/camera/ai_eye/remote/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/console = remote_eye.origin
console.remove_eye_control(target)
@@ -247,7 +246,7 @@
if(!target || !isliving(target))
return
var/mob/living/C = target
- var/mob/camera/aiEye/remote/remote_eye = C.remote_control
+ var/mob/camera/ai_eye/remote/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/origin = remote_eye.origin
var/list/L = list()
@@ -273,7 +272,7 @@
if(final)
playsound(origin, 'sound/machines/terminal_prompt_confirm.ogg', 25, 0)
remote_eye.setLoc(get_turf(final))
- C.overlay_fullscreen("flash", /obj/screen/fullscreen/flash/static)
+ C.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/flash/static)
C.clear_fullscreen("flash", 3) //Shorter flash than normal since it's an ~~advanced~~ console!
else
playsound(origin, 'sound/machines/terminal_prompt_deny.ogg', 25, 0)
diff --git a/code/game/machinery/computer/card.dm b/code/game/machinery/computer/card.dm
index f65ea0d01a2e6..63ab5004691f1 100644
--- a/code/game/machinery/computer/card.dm
+++ b/code/game/machinery/computer/card.dm
@@ -307,6 +307,21 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0)
var/list/alljobs = list("Unassigned")
alljobs += (istype(src, /obj/machinery/computer/card/centcom)? get_all_centcom_jobs() : get_all_jobs()) + "Custom"
for(var/job in alljobs)
+ if(job == "Assistant")
+ jobs_all += " * Service: "
+ if(job == "Quartermaster")
+ jobs_all += " * Cargo: "
+ if(job == "Chief Engineer")
+ jobs_all += " * Engineering: "
+ if(job == "Research Director")
+ jobs_all += " * R&D: "
+ if(job == "Chief Medical Officer")
+ jobs_all += " * Medical: "
+ if(job == "Head of Security")
+ jobs_all += " * Security: "
+ if(job == "Custom")
+ jobs_all += " "
+ // these will make some separation for the department.
jobs_all += "[replacetext(job, " ", " ")] " //make sure there isn't a line break in the middle of a job
diff --git a/code/game/machinery/computer/cloning.dm b/code/game/machinery/computer/cloning.dm
index 7dad9e8daafe6..8bcc79616e3f7 100644
--- a/code/game/machinery/computer/cloning.dm
+++ b/code/game/machinery/computer/cloning.dm
@@ -8,11 +8,14 @@
circuit = /obj/item/circuitboard/computer/cloning
req_access = list(ACCESS_GENETICS) //for modifying records
var/obj/machinery/dna_scannernew/scanner //Linked scanner. For scanning.
+ var/clonepod_type = /obj/machinery/clonepod
var/list/pods //Linked cloning pods
var/temp = "Inactive"
var/scantemp_ckey
- var/scantemp = "Ready to Scan"
+ var/scantemp_name
+ var/scantemp = "Inactive"
var/menu = 1 //Which menu screen to display
+ var/use_records = TRUE //set this to false if you don't want the console to use records
var/list/records = list()
var/datum/data/record/active_record
var/obj/item/disk/data/diskette //Incompatible format to genetics machine
@@ -62,8 +65,8 @@
else if(!. && pod.is_operational() && !(pod.occupant || pod.mess) && pod.efficiency > 5)
. = pod
-/proc/grow_clone_from_record(obj/machinery/clonepod/pod, datum/data/record/R, empty)
- return pod.growclone(R.fields["name"], R.fields["UI"], R.fields["SE"], R.fields["mindref"], R.fields["last_death"], R.fields["mrace"], R.fields["features"], R.fields["factions"], R.fields["quirks"], R.fields["bank_account"], R.fields["traumas"], empty)
+/proc/grow_clone_from_record(obj/machinery/clonepod/pod, datum/data/record/R)
+ return pod.growclone(R.fields["name"], R.fields["UI"], R.fields["SE"], R.fields["mindref"], R.fields["last_death"], R.fields["mrace"], R.fields["features"], R.fields["factions"], R.fields["quirks"], R.fields["bank_account"], R.fields["traumas"], R.fields["body_only"])
/obj/machinery/computer/cloning/process()
if(!(scanner && LAZYLEN(pods) && autoprocess))
@@ -71,6 +74,7 @@
if(scanner.occupant && scanner.scan_level > 2)
scan_occupant(scanner.occupant)
+ ui_update()
for(var/datum/data/record/R in records)
var/obj/machinery/clonepod/pod = GetAvailableEfficientPod(R.fields["mindref"])
@@ -83,14 +87,32 @@
var/result = grow_clone_from_record(pod, R)
if(result & CLONING_SUCCESS)
- temp = "[R.fields["name"]] => Cloning cycle in progress..."
+ temp = "[R.fields["name"]] => Cloning cycle in progress..."
log_cloning("Cloning of [key_name(R.fields["mindref"])] automatically started via autoprocess - [src] at [AREACOORD(src)]. Pod: [pod] at [AREACOORD(pod)].")
+ SStgui.update_uis(src)
if(result & CLONING_DELETE_RECORD)
records -= R
+ ui_update()
+/obj/machinery/computer/cloning/proc/connect_scanner(obj/machinery/dna_scannernew/new_scanner)
+ if(scanner)
+ UnregisterSignal(scanner, COMSIG_MACHINE_OPEN)
+ UnregisterSignal(scanner, COMSIG_MACHINE_CLOSE)
+
+ if(new_scanner)
+ RegisterSignal(new_scanner, COMSIG_MACHINE_OPEN, .proc/scanner_ui_update)
+ RegisterSignal(new_scanner, COMSIG_MACHINE_CLOSE, .proc/scanner_ui_update)
+
+ scanner = new_scanner
+
+/obj/machinery/computer/cloning/proc/scanner_ui_update()
+ SIGNAL_HANDLER
+ ui_update()
+
/obj/machinery/computer/cloning/proc/updatemodules(findfirstcloner)
- scanner = findscanner()
+ if(QDELETED(scanner))
+ connect_scanner(findscanner())
if(findfirstcloner && !LAZYLEN(pods))
findcloner()
if(!autoprocess)
@@ -119,7 +141,7 @@
for(var/direction in GLOB.cardinals)
- podf = locate(/obj/machinery/clonepod, get_step(src, direction))
+ podf = locate(clonepod_type, get_step(src, direction))
if (!isnull(podf) && podf.is_operational())
AttachCloner(podf)
@@ -140,7 +162,6 @@
diskette = W
to_chat(user, "You insert [W].")
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
- updateUsrDialog()
else if(W.tool_behaviour == TOOL_MULTITOOL)
if(!multitool_check_buffer(user, W))
return
@@ -163,366 +184,342 @@
else
return ..()
-/obj/machinery/computer/cloning/ui_interact(mob/user)
+/obj/machinery/computer/cloning/AltClick(mob/user)
. = ..()
-
- updatemodules(TRUE)
-
- var/dat = ""
- dat += "Refresh"
-
- if(scanner && HasEfficientPod() && scanner.scan_level >= AUTOCLONING_MINIMAL_LEVEL)
- if(!autoprocess)
- dat += "Autoprocess"
+ EjectDisk(user)
+
+/obj/machinery/computer/cloning/proc/EjectDisk(mob/user)
+ if(diskette)
+ scantemp = "Disk Ejected"
+ diskette.forceMove(drop_location())
+ usr.put_in_active_hand(diskette)
+ diskette = null
+ playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
+ . = TRUE
+
+/obj/machinery/computer/cloning/proc/Save(mob/user, target)
+ var/datum/data/record/GRAB = null
+ for(var/datum/data/record/record in records)
+ if(record.fields["id"] == target)
+ GRAB = record
+ break
else
- dat += "Stop autoprocess"
- else
- dat += "Autoprocess"
- dat += "
Cloning Pod Status
"
- dat += "
[temp]
"
- switch(menu)
- if(1)
- // Modules
- if (isnull(scanner) || !LAZYLEN(pods))
- dat += "
Modules
"
- //dat += "Reload Modules"
- if (isnull(scanner))
- dat += "ERROR: No Scanner detected! "
- if (!LAZYLEN(pods))
- dat += "ERROR: No Pod detected "
-
- // Scanner
- if (!isnull(scanner))
- var/mob/living/scanner_occupant = get_mob_or_brainmob(scanner.occupant)
-
- dat += "
Scanner Functions
"
-
- dat += "
"
- if(!scanner_occupant)
- dat += "Scanner Unoccupied"
- else if(loading)
- dat += "[scanner_occupant] => Scanning..."
- else
- if(scanner_occupant.ckey != scantemp_ckey)
- scantemp = "Ready to Scan"
- scantemp_ckey = scanner_occupant.ckey
- dat += "[scanner_occupant] => [scantemp]"
- dat += "
"
+ for(var/shuttle_id in SSmapping.shuttle_templates)
+ var/datum/map_template/shuttle/S = SSmapping.shuttle_templates[shuttle_id]
+ if(S.illegal_shuttle && S.credit_cost < INFINITY)
+ dat += "[S.name] | [S.credit_cost] Credits "
+ dat += "[S.description] "
+ if(S.prerequisites)
+ dat += "Prerequisites: [S.prerequisites] "
+ dat += "(Purchase)
"
dat += "
\[ [(state != STATE_DEFAULT) ? "Main Menu | " : ""]Close \]"
@@ -702,7 +714,7 @@
if(CHAT_FILTER_CHECK(input))
to_chat(user, "You cannot send an announcement that contains prohibited words.")
return
- SScommunications.make_announcement(user, is_silicon, input)
+ SScommunications.make_announcement(user, is_silicon, input, auth_id)
deadchat_broadcast("[user.real_name] made a priority announcement from [get_area_name(usr, TRUE)].", user)
/obj/machinery/computer/communications/proc/post_status(command, data1, data2)
diff --git a/code/game/machinery/computer/crew.dm b/code/game/machinery/computer/crew.dm
index a3f66689d862e..85c18e52da68e 100644
--- a/code/game/machinery/computer/crew.dm
+++ b/code/game/machinery/computer/crew.dm
@@ -15,7 +15,7 @@
/obj/machinery/computer/crew/syndie
icon_keyboard = "syndie_key"
-/obj/machinery/computer/crew/interact(mob/user)
+/obj/machinery/computer/crew/ui_interact(mob/user)
GLOB.crewmonitor.show(user,src)
GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new)
@@ -48,6 +48,7 @@ GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new)
jobs["Research Director"] = 30
jobs["Scientist"] = 31
jobs["Roboticist"] = 32
+ jobs["Exploration Crew"] = 33
jobs["Chief Engineer"] = 40
jobs["Station Engineer"] = 41
jobs["Atmospheric Technician"] = 42
@@ -91,19 +92,20 @@ GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new)
if (!ui)
ui = new(user, src, "CrewConsole")
ui.open()
+ ui.set_autoupdate(TRUE)
/datum/crewmonitor/proc/show(mob/M, source)
- ui_sources[M] = source
+ ui_sources[WEAKREF(M)] = source
ui_interact(M)
/datum/crewmonitor/ui_host(mob/user)
- return ui_sources[user]
+ return ui_sources[WEAKREF(user)]
/datum/crewmonitor/ui_data(mob/user)
- var/z = user.z
+ var/z = user.get_virtual_z_level()
if(!z)
var/turf/T = get_turf(user)
- z = T.z
+ z = T.get_virtual_z_level()
var/list/zdata = update_data(z)
. = list()
.["sensors"] = zdata
@@ -135,15 +137,19 @@ GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new)
nanite_sensors = TRUE
// Check if their z-level is correct and if they are wearing a uniform.
// Accept H.z==0 as well in case the mob is inside an object.
- if ((H.z == 0 || H.z == z || (is_station_level(H.z) && is_station_level(z))) && (istype(H.w_uniform, /obj/item/clothing/under) || nanite_sensors))
+ if ((H.z == 0 || H.get_virtual_z_level() == z || (is_station_level(H.z) && is_station_level(z))) && (istype(H.w_uniform, /obj/item/clothing/under) || nanite_sensors))
U = H.w_uniform
+ //Radio transmitters are jammed
+ if(nanite_sensors ? H.is_jammed() : U.is_jammed())
+ continue
+
// Are the suit sensors on?
if (nanite_sensors || ((U.has_sensor > 0) && U.sensor_mode))
pos = H.z == 0 || (nanite_sensors || U.sensor_mode == SENSOR_COORDS) ? get_turf(H) : null
// Special case: If the mob is inside an object confirm the z-level on turf level.
- if (H.z == 0 && (!pos || (pos.z != z) && !(is_station_level(pos.z) && is_station_level(z))))
+ if (H.z == 0 && (!pos || (pos.get_virtual_z_level() != z) && !(is_station_level(pos.z) && is_station_level(z))))
continue
I = H.wear_id ? H.wear_id.GetID() : null
diff --git a/code/game/machinery/computer/dna_console.dm b/code/game/machinery/computer/dna_console.dm
index 4cb6145b59a83..c697fb3fd6432 100644
--- a/code/game/machinery/computer/dna_console.dm
+++ b/code/game/machinery/computer/dna_console.dm
@@ -10,13 +10,19 @@
#define RADIATION_DURATION_MAX 30
#define RADIATION_ACCURACY_MULTIPLIER 3 //larger is less accurate
+/// Special status indicating a scanner occupant is transforming eg. from monkey to human
+#define STATUS_TRANSFORMING 4
#define RADIATION_IRRADIATION_MULTIPLIER 1 //multiplier for how much radiation a test subject receives
-#define SCANNER_ACTION_SE 1
-#define SCANNER_ACTION_UI 2
-#define SCANNER_ACTION_UE 3
-#define SCANNER_ACTION_MIXED 4
+#define SEARCH_OCCUPANT 1
+/// Flag for the mutation ref search system. Search will include console storage
+#define SEARCH_STORED 2
+/// Flag for the mutation ref search system. Search will include diskette storage
+#define SEARCH_DISKETTE 4
+/// Flag for the mutation ref search system. Search will include advanced injector mutations
+#define SEARCH_ADV_INJ 8
+
/obj/machinery/computer/scan_consolenew
name = "\improper DNA scanner access console"
@@ -38,7 +44,7 @@
var/radstrength = 1
var/max_chromosomes = 5
///Amount of mutations we can store
- var/list/buffer[NUMBER_OF_BUFFERS]
+ var/list/genetic_makeup_buffer[NUMBER_OF_BUFFERS]
///mutations we have stored
var/list/stored_mutations = list()
///chromosomes we have stored
@@ -58,10 +64,65 @@
var/current_screen = "mainmenu"
var/current_mutation //what block are we inspecting? only used when screen = "info"
var/current_storage //what storage block are we looking at?
- var/obj/machinery/dna_scannernew/connected = null
+ var/obj/machinery/dna_scannernew/connected_scanner = null
var/obj/item/disk/data/diskette = null
var/list/delayed_action = null
+ /// Index of the enzyme being modified during delayed enzyme pulse operations
+ var/rad_pulse_index = 0
+ /// World time when the enzyme pulse should complete
+ var/rad_pulse_timer = 0
+
+ /// Used for setting tgui data - Whether the connected DNA Scanner is usable
+ var/can_use_scanner = FALSE
+ /// Used for setting tgui data - Whether the current DNA Scanner occupant is viable for genetic modification
+ var/is_viable_occupant = FALSE
+ /// Used for setting tgui data - Whether Scramble DNA is ready
+ var/is_scramble_ready = FALSE
+ /// Used for setting tgui data - Whether JOKER algorithm is ready
+ var/is_joker_ready = FALSE
+ /// Used for setting tgui data - Whether injectors are ready to be printed
+ var/is_injector_ready = FALSE
+ /// Used for setting tgui data - Wheher an enzyme pulse operation is ongoing
+ var/is_pulsing_rads = FALSE
+ /// Used for setting tgui data - Time until scramble is ready
+ var/time_to_scramble = 0
+ /// Used for setting tgui data - Time until joker is ready
+ var/time_to_joker = 0
+ /// Used for setting tgui data - Time until injectors are ready
+ var/time_to_injector = 0
+ /// Used for setting tgui data - Time until the enzyme pulse is complete
+ var/time_to_pulse = 0
+
+ /// Current DNA Scanner occupant
+ var/mob/living/carbon/scanner_occupant = null
+
+ /// Used for setting tgui data - List of occupant mutations
+ var/list/tgui_occupant_mutations = list()
+ /// Used for setting tgui data - List of DNA Console stored mutations
+ var/list/tgui_console_mutations = list()
+ /// Used for setting tgui data - List of diskette stored mutations
+ var/list/tgui_diskette_mutations = list()
+ /// Used for setting tgui data - List of DNA Console chromosomes
+ var/list/tgui_console_chromosomes = list()
+ /// Used for setting tgui data - List of occupant mutations
+ var/list/tgui_genetic_makeup = list()
+ /// Used for setting tgui data - List of occupant mutations
+ var/list/tgui_advinjector_mutations = list()
+
+
+ /// State of tgui view, i.e. which tab is currently active, or which genome we're currently looking at.
+ var/list/list/tgui_view_state = list()
+
+/obj/machinery/computer/scan_consolenew/process()
+ . = ..()
+
+ // This is for pulsing the UI element with radiation as part of genetic makeup
+ // If rad_pulse_index > 0 then it means we're attempting a rad pulse
+ if((rad_pulse_index > 0) && (rad_pulse_timer <= world.time))
+ rad_pulse()
+ return
+
/obj/machinery/computer/scan_consolenew/attackby(obj/item/I, mob/user, params)
if (istype(I, /obj/item/disk/data)) //INSERT SOME DISKETTES
if (!src.diskette)
@@ -96,18 +157,108 @@
else
return ..()
+/obj/machinery/computer/scan_consolenew/AltClick(mob/user)
+ // Make sure the user can interact with the machine.
+ if(!user.canUseTopic(src, !issilicon(user)))
+ return
+
+ eject_disk(user)
+
+/obj/machinery/computer/scan_consolenew/proc/connect_to_scanner()
+ var/obj/machinery/dna_scannernew/test_scanner = null
+ var/obj/machinery/dna_scannernew/broken_scanner = null
+
+ // Look in each cardinal direction and try and find a DNA Scanner
+ // If you find a DNA Scanner, check to see if it broken or working
+ // If it's working, set the current scanner and return early
+ // If it's not working, remember it anyway as a broken scanner
+ for(var/direction in GLOB.cardinals)
+ test_scanner = locate(/obj/machinery/dna_scannernew, get_step(src, direction))
+ if(!isnull(test_scanner))
+ if(test_scanner.is_operational())
+ connect_scanner(test_scanner)
+ return
+ else
+ broken_scanner = test_scanner
+
+ // Ultimately, if we have a broken scanner, we'll attempt to connect to it as
+ // a fallback case, but the code above will prefer a working scanner
+ if(!isnull(broken_scanner))
+ connect_scanner(broken_scanner)
+
+/obj/machinery/computer/scan_consolenew/proc/connect_scanner(obj/machinery/dna_scannernew/scanner)
+ if(connected_scanner)
+ UnregisterSignal(connected_scanner, COMSIG_MACHINE_OPEN)
+ UnregisterSignal(connected_scanner, COMSIG_MACHINE_CLOSE)
+
+ if(scanner)
+ RegisterSignal(scanner, COMSIG_MACHINE_OPEN, .proc/on_scanner_open)
+ RegisterSignal(scanner, COMSIG_MACHINE_CLOSE, .proc/on_scanner_close)
+
+ connected_scanner = scanner
+
/obj/machinery/computer/scan_consolenew/Initialize()
. = ..()
- for(var/direction in GLOB.cardinals)
- connected = locate(/obj/machinery/dna_scannernew, get_step(src, direction))
- if(!isnull(connected))
- break
+
+ // Connect with a nearby DNA Scanner on init
+ connect_to_scanner()
+
+ // Set the default tgui state
+ set_default_state()
injectorready = world.time + INJECTOR_TIMEOUT
scrambleready = world.time + SCRAMBLE_TIMEOUT
jokerready = world.time + JOKER_TIMEOUT
stored_research = SSresearch.science_tech
+/obj/machinery/computer/scan_consolenew/ui_interact(mob/user, datum/tgui/ui)
+ // Most of ui_interact is spent setting variables for passing to the tgui
+ // interface.
+ // We can also do some general state processing here too as it's a good
+ // indication that a player is using the console.
+
+ var/scanner_op = scanner_operational()
+ var/can_modify_occ = can_modify_occupant()
+
+ // Check for connected AND operational scanner.
+ if(scanner_op)
+ can_use_scanner = TRUE
+ else
+ can_use_scanner = FALSE
+ connect_scanner(null)
+ is_viable_occupant = FALSE
+
+ // Check for a viable occupant in the scanner.
+ if(can_modify_occ)
+ is_viable_occupant = TRUE
+ else
+ is_viable_occupant = FALSE
+
+
+ // Populates various buffers for passing to tgui
+ build_mutation_list(can_modify_occ)
+ build_genetic_makeup_list()
+
+ // Populate variables for passing to tgui interface
+ is_scramble_ready = (scrambleready < world.time)
+ time_to_scramble = round((scrambleready - world.time)/10)
+
+ is_joker_ready = (jokerready < world.time)
+ time_to_joker = round((jokerready - world.time)/10)
+
+ is_injector_ready = (injectorready < world.time)
+ time_to_injector = round((injectorready - world.time)/10)
+
+ is_pulsing_rads = ((rad_pulse_index > 0) && (rad_pulse_timer > world.time))
+ time_to_pulse = round((rad_pulse_timer - world.time)/10)
+
+ // Attempt to update tgui ui, open and update if needed.
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "DnaConsole")
+ ui.open()
+ ui.set_autoupdate(TRUE)
+
/obj/machinery/computer/scan_consolenew/examine(mob/user)
. = ..()
if(jokerready < world.time)
@@ -115,839 +266,1658 @@
else
. += "JOKER algorithm available in about [round(0.00166666667 * (jokerready - world.time))] minutes."
-/obj/machinery/computer/scan_consolenew/ui_interact(mob/user, last_change)
- . = ..()
- if(!user)
- return
- var/datum/browser/popup = new(user, "scannernew", "DNA Modifier Console", 800, 630) // Set up the popup browser window
- if(user.client)
- var/datum/asset/simple/assets = get_asset_datum(/datum/asset/simple/genetics)
- assets.send(user.client)
- popup.add_stylesheet("scannernew", 'html/browser/scannernew.css')
-
- var/mob/living/carbon/viable_occupant
- var/list/occupant_status = list("
"
- return temp_html
+ connected_scanner.locked = !connected_scanner.locked
+ return
-/obj/machinery/computer/scan_consolenew/Topic(href, href_list)
- if(..())
- return
- if(current_screen == "working")
- return
+ // Scramble scanner occupant's DNA
+ if("scramble_dna")
+ // GUARD CHECK - Can we genetically modify the occupant? Includes scanner
+ // operational guard checks.
+ // GUARD CHECK - Is scramble DNA actually ready?
+ if(!can_modify_occupant() || !(scrambleready < world.time))
+ return
- add_fingerprint(usr)
- usr.set_machine(src)
+ scanner_occupant.dna.remove_all_mutations(list(MUT_NORMAL, MUT_EXTRA))
+ scanner_occupant.dna.generate_dna_blocks()
+ scrambleready = world.time + SCRAMBLE_TIMEOUT
+ to_chat(usr,"DNA scrambled.")
+ scanner_occupant.radiation += RADIATION_STRENGTH_MULTIPLIER*50/(connected_scanner.damage_coeff ** 2)
+ return
- var/mob/living/carbon/viable_occupant = get_viable_occupant()
-
- //Basic Tasks///////////////////////////////////////////
- var/num = round(text2num(href_list["num"]))
- var/last_change
- switch(href_list["task"])
- if("togglelock")
- if(connected)
- connected.locked = !connected.locked
- if("toggleopen")
- if(connected)
- connected.toggle_open(usr)
- if("setduration")
- if(!num)
- num = round(input(usr, "Choose pulse duration:", "Input an Integer", null) as num|null)
- if(num)
- radduration = WRAP(num, 1, RADIATION_DURATION_MAX+1)
- if("setstrength")
- if(!num)
- num = round(input(usr, "Choose pulse strength:", "Input an Integer", null) as num|null)
- if(num)
- radstrength = WRAP(num, 1, RADIATION_STRENGTH_MAX+1)
- if("screen")
- current_screen = href_list["text"]
- if("scramble")
- if(viable_occupant && (scrambleready < world.time))
- if(prob(1) && prob(1))
- to_chat(usr,"Error: Musa acuminata transformation detected!") //Actually we only share 1% of our functional protein-coding DNA with bananas, but this is mildly amusing so whatever
- if(viable_occupant.client && viable_occupant.ckey)
- message_admins("[key_name(viable_occupant)] has been turned into a banana by [key_name(usr)]")
- log_game("[key_name(viable_occupant)] has been turned into a banana by [key_name(usr)]")
- if(connected)
- connected.locked = FALSE
- connected.open_machine()
- var/occupant_loc = viable_occupant.loc
- viable_occupant.Destroy()
- new /obj/item/reagent_containers/food/snacks/grown/banana(occupant_loc)
- return
+ // Check whether a specific mutation is eligible for discovery within the
+ // scanner occupant
+ // This is additionally done when a mutation's tab is selected in the tgui
+ // interface. This is because some mutations, such as Monkified on monkeys,
+ // are infact completed by default but not yet discovered. Likewise, all
+ // mutations can have their sequence completed while Monkified is still an
+ // active mutation and thus won't immediately be discovered but could be
+ // discovered when Monkified is removed
+ // ---------------------------------------------------------------------- //
+ // params["alias"] - Alias of a mutation. The alias is the "hidden" name of
+ // the mutation, for example "Mutation 5" or "Mutation 33"
+ if("check_discovery")
+ // GUARD CHECK - Can we genetically modify the occupant? Includes scanner
+ // operational guard checks.
+ if(!can_modify_occupant())
+ return
+
+ // GUARD CHECK - Have we somehow cheekily swapped occupants? This is
+ // unexpected.
+ if(!(scanner_occupant == connected_scanner.occupant))
+ return
+
+ check_discovery(params["alias"])
+ return
+
+ // Check all mutations of the occupant and check if any are discovered.
+ // This is called when the Genetic Sequencer is selected. It'll do things
+ // like immediately discover Monkified without needing to click through
+ // the mutation tabs and handle cases where mutations are solved but not
+ // discovered due to the Monkified mutation being active then removed.
+ if("all_check_discovery")
+ // GUARD CHECK - Can we genetically modify the occupant? Includes scanner
+ // operational guard checks.
+ if(!can_modify_occupant())
+ return
- viable_occupant.dna.remove_all_mutations(list(MUT_NORMAL, MUT_EXTRA))
- viable_occupant.dna.generate_dna_blocks()
- scrambleready = world.time + SCRAMBLE_TIMEOUT
- to_chat(usr,"DNA scrambled.")
- viable_occupant.radiation += RADIATION_STRENGTH_MULTIPLIER*50/(connected.damage_coeff ** 2)
-
- if("setbufferlabel")
- var/text = stripped_input(usr, "Input a new label:", "Input a Text")
- if(num && text)
- num = CLAMP(num, 1, NUMBER_OF_BUFFERS)
- var/list/buffer_slot = buffer[num]
- if(istype(buffer_slot))
- buffer_slot["label"] = text
- if("setbuffer")
- if(num && viable_occupant)
- num = CLAMP(num, 1, NUMBER_OF_BUFFERS)
- buffer[num] = list(
- "label"="Buffer[num]:[viable_occupant.real_name]",
- "UI"=viable_occupant.dna.uni_identity,
- "UE"=viable_occupant.dna.unique_enzymes,
- "name"=viable_occupant.real_name,
- "blood_type"=viable_occupant.dna.blood_type
- )
- if("clearbuffer")
- if(num)
- num = CLAMP(num, 1, NUMBER_OF_BUFFERS)
- var/list/buffer_slot = buffer[num]
- if(istype(buffer_slot))
- buffer_slot.Cut()
- if("transferbuffer")
- if(num && viable_occupant)
- switch(href_list["text"]) //Numbers are this high because other way upgrading laser is just not worth the hassle, and i cant think of anything better to inmrove
- if("ui")
- apply_buffer(SCANNER_ACTION_UI,num)
- if("ue")
- apply_buffer(SCANNER_ACTION_UE,num)
- if("mixed")
- apply_buffer(SCANNER_ACTION_MIXED,num)
- if("injector")
- if(num && injectorready < world.time)
- num = CLAMP(num, 1, NUMBER_OF_BUFFERS)
- var/list/buffer_slot = buffer[num]
- if(istype(buffer_slot))
- var/obj/item/dnainjector/timed/I
- switch(href_list["text"])
- if("ui")
- if(buffer_slot["UI"])
- I = new /obj/item/dnainjector/timed(loc)
- I.fields = list("UI"=buffer_slot["UI"])
- if(connected)
- I.damage_coeff = connected.damage_coeff
- if("ue")
- if(buffer_slot["name"] && buffer_slot["UE"] && buffer_slot["blood_type"])
- I = new /obj/item/dnainjector/timed(loc)
- I.fields = list("name"=buffer_slot["name"], "UE"=buffer_slot["UE"], "blood_type"=buffer_slot["blood_type"])
- if(connected)
- I.damage_coeff = connected.damage_coeff
- if("mixed")
- if(buffer_slot["UI"] && buffer_slot["name"] && buffer_slot["UE"] && buffer_slot["blood_type"])
- I = new /obj/item/dnainjector/timed(loc)
- I.fields = list("UI"=buffer_slot["UI"],"name"=buffer_slot["name"], "UE"=buffer_slot["UE"], "blood_type"=buffer_slot["blood_type"])
- if(connected)
- I.damage_coeff = connected.damage_coeff
- if(I)
- injectorready = world.time + INJECTOR_TIMEOUT
- if("loaddisk")
- if(num && diskette && diskette.fields)
- num = CLAMP(num, 1, NUMBER_OF_BUFFERS)
- buffer[num] = diskette.fields.Copy()
- if("savedisk")
- if(num && diskette && !diskette.read_only)
- num = CLAMP(num, 1, NUMBER_OF_BUFFERS)
- var/list/buffer_slot = buffer[num]
- if(istype(buffer_slot))
- diskette.name = "data disk \[[buffer_slot["label"]]\]"
- diskette.fields = buffer_slot.Copy()
- if("ejectdisk")
- if(diskette)
- diskette.forceMove(drop_location())
- diskette = null
- if("setdelayed")
- if(num)
- delayed_action = list("action"=text2num(href_list["delayaction"]),"buffer"=num)
- if("pulseui")
- if(num && viable_occupant && connected)
- radduration = WRAP(radduration, 1, RADIATION_DURATION_MAX+1)
- radstrength = WRAP(radstrength, 1, RADIATION_STRENGTH_MAX+1)
-
- var/locked_state = connected.locked
- connected.locked = TRUE
-
- current_screen = "working"
- ui_interact(usr)
-
- sleep(radduration*10)
- current_screen = "ui"
-
- if(viable_occupant && connected && connected.occupant==viable_occupant)
- viable_occupant.radiation += (RADIATION_IRRADIATION_MULTIPLIER*radduration*radstrength)/(connected.damage_coeff ** 2) //Read comment in "transferbuffer" section above for explanation
- switch(href_list["task"]) //Same thing as there but values are even lower, on best part they are about 0.0*, effectively no damage
- if("pulseui")
- var/len = length_char(viable_occupant.dna.uni_identity)
- num = WRAP(num, 1, len+1)
- num = randomize_radiation_accuracy(num, radduration + (connected.precision_coeff ** 2), len) //Each manipulator level above 1 makes randomization as accurate as selected time + manipulator lvl^2
- //Value is this high for the same reason as with laser - not worth the hassle of upgrading if the bonus is low
- var/block = round((num-1)/DNA_BLOCK_SIZE)+1
- var/subblock = num - block*DNA_BLOCK_SIZE
- last_change = "UI #[block]-[subblock]; "
-
- var/hex = copytext_char(viable_occupant.dna.uni_identity, num, num+1)
- last_change += "[hex]"
- hex = scramble(hex, radstrength, radduration)
- last_change += "->[hex]"
-
- viable_occupant.dna.uni_identity = copytext_char(viable_occupant.dna.uni_identity, 1, num) + hex + copytext_char(viable_occupant.dna.uni_identity, num + 1)
- viable_occupant.updateappearance(mutations_overlay_update=1)
+ // GUARD CHECK - Have we somehow cheekily swapped occupants? This is
+ // unexpected.
+ if(!(scanner_occupant == connected_scanner.occupant))
+ return
+
+ // Go over all standard mutations and check if they've been discovered.
+ for(var/mutation_type in scanner_occupant.dna.mutation_index)
+ var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(mutation_type)
+ check_discovery(HM.alias)
+
+ return
+
+ // Set a gene in a mutation's genetic sequence. Will also check for mutations
+ // discovery as part of the process.
+ // ---------------------------------------------------------------------- //
+ // params["alias"] - Alias of a mutation. The alias is the "hidden" name of
+ // the mutation, for example "Mutation 5" or "Mutation 33"
+ // params["gene"] - The letter of the new gene
+ // params["pos"] - The BYOND index of the letter in the gene sequence to be
+ // changed. Expects a text string from TGUI and will convert to a number
+ if("pulse_gene")
+ // GUARD CHECK - Can we genetically modify the occupant? Includes scanner
+ // operational guard checks.
+ if(!can_modify_occupant())
+ return
+
+ // GUARD CHECK - Have we somehow cheekily swapped occupants? This is
+ // unexpected.
+ if(!(scanner_occupant == connected_scanner.occupant))
+ return
+
+ // GUARD CHECK - Is the occupant currently undergoing some form of
+ // transformation? If so, we don't want to be pulsing genes.
+ if(scanner_occupant.transformation_timer)
+ to_chat(usr,"Gene pulse failed: The scanner occupant undergoing a transformation.")
+ return
+
+ // Resolve mutation's BYOND path from the alias
+ var/alias = params["alias"]
+ var/path = GET_MUTATION_TYPE_FROM_ALIAS(alias)
+
+ // Make sure the occupant still has this mutation
+ if(!(path in scanner_occupant.dna.mutation_index))
+ return
+
+ // Resolve BYOND path to genome sequence of scanner occupant
+ var/sequence = GET_GENE_STRING(path, scanner_occupant.dna)
+
+ var/newgene = params["gene"]
+ var/genepos = text2num(params["pos"])
+
+ // If the new gene is J, this means we're dealing with a JOKER
+ // GUARD CHECK - Is JOKER actually ready?
+ if((newgene == "J") && (jokerready < world.time))
+ var/truegenes = GET_SEQUENCE(path)
+ newgene = truegenes[genepos]
+ jokerready = world.time + JOKER_TIMEOUT - (JOKER_UPGRADE * (connected_scanner.precision_coeff-1))
+
+ // If the gene is an X, we want to update the default genes with the new
+ // X to allow highlighting logic to work on the tgui interface.
+ if(newgene == "X")
+ var/defaultseq = scanner_occupant.dna.default_mutation_genes[path]
+ defaultseq = copytext_char(defaultseq, 1, genepos) + newgene + copytext_char(defaultseq, genepos + 1)
+ scanner_occupant.dna.default_mutation_genes[path] = defaultseq
+
+ // Copy genome to scanner occupant and do some basic mutation checks as
+ // we've increased the occupant rads
+ sequence = copytext_char(sequence, 1, genepos) + newgene + copytext_char(sequence, genepos + 1)
+ scanner_occupant.dna.mutation_index[path] = sequence
+ scanner_occupant.radiation += RADIATION_STRENGTH_MULTIPLIER/connected_scanner.damage_coeff
+ scanner_occupant.domutcheck()
+
+ // GUARD CHECK - Modifying genetics can lead to edge cases where the
+ // scanner occupant is qdel'd and replaced with a different entity.
+ // Examples of this include adding/removing the Monkified mutation which
+ // qdels the previous entity and creates a brand new one in its place.
+ // We should redo all of our occupant modification checks again, although
+ // it is less than ideal.
+ if(!can_modify_occupant())
+ return
+
+ // Check if we cracked a mutation
+ check_discovery(alias)
+
+ return
+
+ // Apply a chromosome to a specific mutation.
+ // ---------------------------------------------------------------------- //
+ // params["mutref"] - ATOM Ref of specific mutation to apply the chromo to
+ // params["chromo"] - Name of the chromosome to apply to the mutation
+ if("apply_chromo")
+ // GUARD CHECK - Can we genetically modify the occupant? Includes scanner
+ // operational guard checks.
+ if(!can_modify_occupant())
+ return
+
+ // GUARD CHECK - Have we somehow cheekily swapped occupants? This is
+ // unexpected.
+ if(!(scanner_occupant == connected_scanner.occupant))
+ return
+
+ var/bref = params["mutref"]
+
+ // GUARD CHECK - Only search occupant for this specific ref, since your
+ // can only apply chromosomes to mutations occupants.
+ var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_OCCUPANT)
+
+ // GUARD CHECK - This should not be possible. Unexpected result
+ if(!HM)
+ return
+
+ // Look through our stored chromos and compare names to find a
+ // stored chromo we can apply.
+ for(var/obj/item/chromosome/CM in stored_chromosomes)
+ if(CM.can_apply(HM) && (CM.name == params["chromo"]))
+ stored_chromosomes -= CM
+ CM.apply(HM)
+
+ return
+
+ // Print any type of standard injector, limited right now to activators that
+ // activate a dormant mutation and mutators that forcibly create a new
+ // MUT_EXTRA mutation
+ // ---------------------------------------------------------------------- //
+ // params["mutref"] - ATOM Ref of specific mutation to create an injector of
+ // params["is_activator"] - Is this an "Activator" style injector, also
+ // referred to as a "Research" type. Expects a string with 0 or 1, which
+ // then gets converted to a number.
+ // params["source"] - The source the request came from.
+ // Expected results:
+ // "occupant" - From genetic sequencer
+ // "console" - From DNA Console storage
+ // "disk" - From inserted diskette
+ if("print_injector")
+ // Because printing mutators and activators share a bunch of code,
+ // it makes sense to keep them both together and set unique vars
+ // later in the code
+
+ // As a side note, because mutations can contain unique metadata,
+ // this system uses BYOND Atom Refs to safely and accurately
+ // identify mutations from big ol' lists
+
+ // GUARD CHECK - Is the injector actually ready?
+ if(world.time < injectorready)
+ return
+
+ var/search_flags = 0
+
+ switch(params["source"])
+ if("occupant")
+ // GUARD CHECK - Make sure we can modify the occupant before we
+ // attempt to search them for any given mutation refs. This could
+ // lead to no search flags being passed to get_mut_by_ref and this
+ // is intended functionality to prevent any cheese or abuse
+ if(can_modify_occupant())
+ search_flags |= SEARCH_OCCUPANT
+ if("console")
+ search_flags |= SEARCH_STORED
+ if("disk")
+ search_flags |= SEARCH_DISKETTE
+
+ var/bref = params["mutref"]
+ var/datum/mutation/human/HM = get_mut_by_ref(bref, search_flags)
+
+ // GUARD CHECK - This should not be possible. Unexpected result
+ if(!HM)
+ return
+
+ // Create a new DNA Injector and add the appropriate mutations to it
+ var/obj/item/dnainjector/activator/I = new /obj/item/dnainjector/activator(loc)
+ I.add_mutations += new HM.type(copymut = HM)
+
+ var/is_activator = text2num(params["is_activator"])
+
+ // Activators are also called "research" injectors and are used to create
+ // chromosomes by recycling at the DNA Console
+ if(is_activator)
+ I.name = "[HM.name] activator"
+ I.research = TRUE
+ // If there's an operational connected scanner, we can use its upgrades
+ // to improve our injector's radiation generation
+ if(scanner_operational())
+ I.damage_coeff = connected_scanner.damage_coeff*4
+ injectorready = world.time + INJECTOR_TIMEOUT * (1 - 0.1 * connected_scanner.precision_coeff)
else
- current_screen = "mainmenu"
-
- if(connected)
- connected.locked = locked_state
- if("inspect")
- if(viable_occupant)
- var/list/mutations = get_mutation_list(TRUE)
- if(current_mutation == mutations[num])
- current_mutation = null
+ injectorready = world.time + INJECTOR_TIMEOUT
+ else
+ I.name = "[HM.name] mutator"
+ I.doitanyway = TRUE
+ // If there's an operational connected scanner, we can use its upgrades
+ // to improve our injector's radiation generation
+ if(scanner_operational())
+ I.damage_coeff = connected_scanner.damage_coeff
+ injectorready = world.time + INJECTOR_TIMEOUT * 5 * (1 - 0.1 * connected_scanner.precision_coeff)
else
- current_mutation = mutations[num]
-
- if("inspectstorage")
- current_storage = num
- current_screen = "info"
- if("savemut")
- if(viable_occupant)
- var/success
- if(LAZYLEN(stored_mutations) < max_storage)
- var/mutation = text2path(href_list["path"])
- if(ispath(mutation, /datum/mutation/human)) //sanity checks
- var/datum/mutation/human/HM = viable_occupant.dna.get_mutation(mutation)
- if(HM)
- var/datum/mutation/human/A = new HM.type()
- A.copy_mutation(HM)
- success = TRUE
- stored_mutations += A
- to_chat(usr,"Mutation succesfully stored.")
- if(!success) //we can exactly return here
- to_chat(usr,"Mutation storage is full.")
- if("deletemut")
- var/datum/mutation/human/HM = stored_mutations[num]
+ injectorready = world.time + INJECTOR_TIMEOUT * 5
+
+ return
+
+ // Save a mutation to the console's storage buffer.
+ // ---------------------------------------------------------------------- //
+ // params["mutref"] - ATOM Ref of specific mutation to store
+ // params["source"] - The source the request came from.
+ // Expected results:
+ // "occupant" - From genetic sequencer
+ // "disk" - From inserted diskette
+ if("save_console")
+ var/search_flags = 0
+
+ switch(params["source"])
+ if("occupant")
+ // GUARD CHECK - Make sure we can modify the occupant before we
+ // attempt to search them for any given mutation refs. This could
+ // lead to no search flags being passed to get_mut_by_ref and this
+ // is intended functionality to prevent any cheese or abuse
+ if(can_modify_occupant())
+ search_flags |= SEARCH_OCCUPANT
+ if("disk")
+ search_flags |= SEARCH_DISKETTE
+
+ // GUARD CHECK - Is mutation storage full?
+ if(LAZYLEN(stored_mutations) >= max_storage)
+ to_chat(usr,"Mutation storage is full.")
+ return
+
+ var/bref = params["mutref"]
+ var/datum/mutation/human/HM = get_mut_by_ref(bref, search_flags)
+
+ // GUARD CHECK - This should not be possible. Unexpected result
+ if(!HM)
+ return
+
+ var/datum/mutation/human/A = new HM.type()
+ A.copy_mutation(HM)
+ stored_mutations += A
+ to_chat(usr,"Mutation successfully stored.")
+ return
+
+ // Save a mutation to the diskette's storage buffer.
+ // ---------------------------------------------------------------------- //
+ // params["mutref"] - ATOM Ref of specific mutation to store
+ // params["source"] - The source the request came from
+ // Expected results:
+ // "occupant" - From genetic sequencer
+ // "console" - From DNA Console storage
+ if("save_disk")
+ // GUARD CHECK - This code shouldn't even be callable without a diskette
+ // inserted. Unexpected result
+ if(!diskette)
+ return
+
+ // GUARD CHECK - Make sure the disk is not full
+ if(LAZYLEN(diskette.mutations) >= diskette.max_mutations)
+ to_chat(usr,"Disk storage is full.")
+ return
+
+ // GUARD CHECK - Make sure the disk isn't set to read only, as we're
+ // attempting to write to it
+ if(diskette.read_only)
+ to_chat(usr,"Disk is set to read only mode.")
+ return
+
+ var/search_flags = 0
+
+ switch(params["source"])
+ if("occupant")
+ // GUARD CHECK - Make sure we can modify the occupant before we
+ // attempt to search them for any given mutation refs. This could
+ // lead to no search flags being passed to get_mut_by_ref and this
+ // is intended functionality to prevent any cheese or abuse
+ if(can_modify_occupant())
+ search_flags |= SEARCH_OCCUPANT
+ if("console")
+ search_flags |= SEARCH_STORED
+
+ var/bref = params["mutref"]
+ var/datum/mutation/human/HM = get_mut_by_ref(bref, search_flags)
+
+ // GUARD CHECK - This should not be possible. Unexpected result
+ if(!HM)
+ return
+
+ var/datum/mutation/human/A = new HM.type()
+ A.copy_mutation(HM)
+ diskette.mutations += A
+ to_chat(usr,"Mutation successfully stored to disk.")
+ return
+
+ // Completely removes a MUT_EXTRA mutation or mutation with corrupt gene
+ // sequence from the scanner occupant
+ // ---------------------------------------------------------------------- //
+ // params["mutref"] - ATOM Ref of specific mutation to nullify
+ if("nullify")
+ // GUARD CHECK - Can we genetically modify the occupant? Includes scanner
+ // operational guard checks.
+ if(!can_modify_occupant())
+ return
+
+ var/bref = params["mutref"]
+ var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_OCCUPANT)
+
+ // GUARD CHECK - This should not be possible. Unexpected result
+ if(!HM)
+ return
+
+ // GUARD CHECK - Nullify should only be used on scrambled or "extra"
+ // mutations.
+ if(!HM.scrambled && !(HM.class == MUT_EXTRA))
+ return
+
+ scanner_occupant.dna.remove_mutation(HM.type)
+ return
+
+ // Deletes saved mutation from console buffer.
+ // ---------------------------------------------------------------------- //
+ // params["mutref"] - ATOM Ref of specific mutation to delete
+ if("delete_console_mut")
+ var/bref = params["mutref"]
+ var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_STORED)
+
if(HM)
stored_mutations.Remove(HM)
qdel(HM)
- current_screen = "mutations"
- if("activator")
- if(injectorready < world.time)
- var/mutation = text2path(href_list["path"])
- if(ispath(mutation, /datum/mutation/human))
- var/datum/mutation/human/HM = get_valid_mutation(mutation)
- if(HM)
- var/obj/item/dnainjector/activator/I = new /obj/item/dnainjector/activator(loc)
- I.add_mutations += new HM.type (copymut = HM)
- I.name = "[HM.name] activator"
- I.research = TRUE
- if(connected)
- I.damage_coeff = connected.damage_coeff*4
- injectorready = world.time + INJECTOR_TIMEOUT * (1 - 0.1 * connected.precision_coeff) //precision_coeff being the manipulator rating
- else
- injectorready = world.time + INJECTOR_TIMEOUT
- if("mutator")
- if(injectorready < world.time)
- var/mutation = text2path(href_list["path"])
- if(ispath(mutation, /datum/mutation/human))
- var/datum/mutation/human/HM = get_valid_mutation(mutation)
- if(HM)
- var/obj/item/dnainjector/activator/I = new /obj/item/dnainjector/activator(loc)
- I.add_mutations += new HM.type (copymut = HM)
- I.doitanyway = TRUE
- I.name = "[HM.name] injector"
- if(connected)
- I.damage_coeff = connected.damage_coeff
- injectorready = world.time + INJECTOR_TIMEOUT * 5 * (1 - 0.1 * connected.precision_coeff)
- else
- injectorready = world.time + INJECTOR_TIMEOUT * 5
-
- if("advinjector")
- var/selection = href_list["injector"]
- if(injectorready < world.time)
- if(injector_selection.Find(selection))
- var/list/true_selection = injector_selection[selection]
- if(LAZYLEN(injector_selection))
- var/obj/item/dnainjector/activator/I = new /obj/item/dnainjector/activator(loc)
- for(var/A in true_selection)
- var/datum/mutation/human/HM = A
- I.add_mutations += new HM.type (copymut = HM)
- I.doitanyway = TRUE
- I.name = "Advanced [selection] injector"
- if(connected)
- I.damage_coeff = connected.damage_coeff
- injectorready = world.time + INJECTOR_TIMEOUT * 8 * (1 - 0.1 * connected.precision_coeff)
- else
- injectorready = world.time + INJECTOR_TIMEOUT * 8
- if("nullify")
- if(viable_occupant)
- var/datum/mutation/human/A = viable_occupant.dna.get_mutation(current_mutation)
- if(A && (!viable_occupant.dna.mutation_in_sequence(current_mutation) || A.scrambled))
- viable_occupant.dna.remove_mutation(current_mutation)
- current_screen = "mainmenu"
- current_mutation = null
- if("pulsegene")
- if(current_screen != "info")
- var/path = GET_MUTATION_TYPE_FROM_ALIAS(href_list["alias"])
- if(viable_occupant && num && (path in viable_occupant.dna.mutation_index))
- var/list/genes = list("A","T","G","C","X")
- if(jokerready < world.time)
- genes += "JOKER"
- var/sequence = GET_GENE_STRING(path, viable_occupant.dna)
- var/original = sequence[num]
- var/new_gene = input("From [original] to-", "New block", original) as null|anything in genes
- if(!new_gene)
- new_gene = original
- if(viable_occupant == get_viable_occupant()) //No cheesing
- if((new_gene == "JOKER") && (jokerready < world.time))
- var/true_genes = GET_SEQUENCE(current_mutation)
- new_gene = true_genes[num]
- jokerready = world.time + JOKER_TIMEOUT - (JOKER_UPGRADE * (connected.precision_coeff-1))
- sequence = copytext_char(sequence, 1, num) + new_gene + copytext_char(sequence, num + 1)
- viable_occupant.dna.mutation_index[path] = sequence
- viable_occupant.radiation += RADIATION_STRENGTH_MULTIPLIER/connected.damage_coeff
- viable_occupant.domutcheck()
- if("exportdiskmut")
- if(diskette && !diskette.read_only)
- var/path = text2path(href_list["path"])
- if(ispath(path, /datum/mutation/human))
- var/datum/mutation/human/A = get_valid_mutation(path)
- if(A && diskette && (LAZYLEN(diskette.mutations) < diskette.max_mutations))
- var/datum/mutation/human/HM = new A.type()
- diskette.mutations += HM
- HM.copy_mutation(A)
- to_chat(usr, "Successfully wrote [A.name] to [diskette.name].")
- if("deletediskmut")
- if(diskette && !diskette.read_only)
- if(num && (LAZYLEN(diskette.mutations) >= num))
- var/datum/mutation/human/A = diskette.mutations[num]
- diskette.mutations.Remove(A)
- qdel(A)
- if("importdiskmut")
- if(diskette && (LAZYLEN(diskette.mutations) >= num))
- if(LAZYLEN(stored_mutations) < max_storage)
- var/datum/mutation/human/A = diskette.mutations[num]
- var/datum/mutation/human/HM = new A.type()
- HM.copy_mutation(A)
- stored_mutations += HM
- to_chat(usr,"Successfully wrote [A.name] to storage.")
- if("combine")
- if(num && (LAZYLEN(stored_mutations) >= num))
- if(LAZYLEN(stored_mutations) < max_storage)
- var/datum/mutation/human/A = stored_mutations[num]
- var/path = A.type
- if(combine)
- var/result_path = get_mixed_mutation(combine, path)
- if(result_path)
- stored_mutations += new result_path()
- to_chat(usr, "Success! New mutation has been added to storage.")
- discover(result_path)
- combine = null
- else
- to_chat(usr, "Failed. No mutation could be created.")
- combine = null
- else
- combine = path
- to_chat(usr,"Selected [A.name] for combining.")
- else
- to_chat(usr, "Not enough space to store potential mutation.")
- if("ejectchromosome")
- var/obj/item/chromosome/CM = stored_chromosomes[num]
- CM.forceMove(drop_location())
- adjust_item_drop_location(CM)
- stored_chromosomes -= CM
- if("applychromosome")
- if(viable_occupant && (LAZYLEN(viable_occupant.dna.mutations) <= num))
- var/datum/mutation/human/HM = viable_occupant.dna.mutations[num]
- var/list/chromosomes = list()
- for(var/obj/item/chromosome/CM in stored_chromosomes)
- if(CM.can_apply(HM))
- chromosomes += CM
- if(chromosomes.len)
- var/obj/item/chromosome/CM = input("Select a chromosome to apply", "Apply Chromosome") as null|anything in sortNames(chromosomes)
- if(CM)
- to_chat(usr, "You apply [CM] to [HM.name].")
- stored_chromosomes -= CM
- CM.apply(HM)
- if("expand_advinjector")
- var/mutation = text2path(href_list["path"])
- var/datum/mutation/human/HM = get_valid_mutation(mutation)
- if(HM && LAZYLEN(injector_selection))
- var/which_injector = input(usr, "Select Adv. Injector", "Advanced Injectors") as null|anything in injector_selection
- if(injector_selection.Find(which_injector))
- var/list/true_selection = injector_selection[which_injector]
- var/total_instability
- for(var/B in true_selection)
- var/datum/mutation/human/mootacion = B
- total_instability += mootacion.instability
- total_instability += HM.instability
- if((total_instability > max_injector_instability) || (true_selection.len + 1) > max_injector_mutations)
- to_chat(usr, "Adding more mutations would make the advanced injector too unstable!")
- else
- true_selection += HM //reminder that this works. because I keep forgetting this works
- if("remove_from_advinjector")
- var/mutation = text2path(href_list["path"])
- var/selection = href_list["injector"]
- if(injector_selection.Find(selection))
- var/list/true_selection = injector_selection[selection]
- for(var/B in true_selection)
- var/datum/mutation/human/HM = B
- if(HM.type == mutation)
- true_selection -= HM
- break
-
- if("remove_advinjector")
- var/selection = href_list["injector"]
- for(selection in injector_selection)
- if(selection == selection)
- injector_selection.Remove(selection)
-
- if("add_advinjector")
- if(LAZYLEN(injector_selection) < max_injector_selections)
- var/new_selection = stripped_input(usr, "Enter Adv. Injector name", "Advanced Injectors")
- if(new_selection && !(new_selection in injector_selection))
- injector_selection[new_selection] = list()
-
-
-
- ui_interact(usr,last_change)
-
-/obj/machinery/computer/scan_consolenew/proc/scramble(input,rs,rd) //hexadecimal genetics. dont confuse with scramble button
+ return
+
+ // Deletes saved mutation from disk buffer.
+ // ---------------------------------------------------------------------- //
+ // params["mutref"] - ATOM Ref of specific mutation to delete
+ if("delete_disk_mut")
+ // GUARD CHECK - This code shouldn't even be callable without a diskette
+ // inserted. Unexpected result
+ if(!diskette)
+ return
+
+ // GUARD CHECK - Make sure the disk isn't set to read only, as we're
+ // attempting to write to it (via deletion)
+ if(diskette.read_only)
+ to_chat(usr,"Disk is set to read only mode.")
+ return
+
+ var/bref = params["mutref"]
+ var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_DISKETTE)
+
+ if(HM)
+ diskette.mutations.Remove(HM)
+ qdel(HM)
+
+ return
+
+ // Ejects a stored chromosome from the DNA Console
+ // ---------------------------------------------------------------------- //
+ // params["chromo"] - Text string of the chromosome name
+ if("eject_chromo")
+ var/chromname = params["chromo"]
+
+ for(var/obj/item/chromosome/CM in stored_chromosomes)
+ if(chromname == CM.name)
+ CM.forceMove(drop_location())
+ adjust_item_drop_location(CM)
+ stored_chromosomes -= CM
+ return
+
+ return
+
+ // Combines two mutations from the console to try and create a new mutation
+ // ---------------------------------------------------------------------- //
+ // params["firstref"] - ATOM Ref of first mutation for combination
+ // params["secondref"] - ATOM Ref of second mutation for combination
+ // mutation
+ if("combine_console")
+ // GUaRD CHECK - Make sure mutation storage isn't full. If it is, we won't
+ // be able to store the new combo mutation
+ if(LAZYLEN(stored_mutations) >= max_storage)
+ to_chat(usr,"Mutation storage is full.")
+ return
+
+ // GUARD CHECK - We're running a research-type operation. If, for some
+ // reason, somehow the DNA Console has been disconnected from the research
+ // network - Or was never in it to begin with - don't proceed
+ if(!stored_research)
+ return
+
+ var/first_bref = params["firstref"]
+ var/second_bref = params["secondref"]
+
+ // GUARD CHECK - Find the source and destination mutations on the console
+ // and make sure they actually exist.
+ var/datum/mutation/human/source_mut = get_mut_by_ref(first_bref, SEARCH_STORED | SEARCH_DISKETTE)
+ if(!source_mut)
+ return
+
+ var/datum/mutation/human/dest_mut = get_mut_by_ref(second_bref, SEARCH_STORED | SEARCH_DISKETTE)
+ if(!dest_mut)
+ return
+
+ // Attempt to mix the two mutations to get a new type
+ var/result_path = get_mixed_mutation(source_mut.type, dest_mut.type)
+
+ if(!result_path)
+ return
+
+ // If we got a new type, add it to our storage
+ stored_mutations += new result_path()
+ to_chat(usr, "Success! New mutation has been added to console storage.")
+
+ // If it's already discovered, end here. Otherwise, add it to the list of
+ // discovered mutations.
+ // We've already checked for stored_research earlier
+ if(result_path in stored_research.discovered_mutations)
+ return
+
+ var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(result_path)
+ stored_research.discovered_mutations += result_path
+ say("Successfully mutated [HM.name].")
+ return
+
+ // Combines two mutations from the disk to try and create a new mutation
+ // ---------------------------------------------------------------------- //
+ // params["firstref"] - ATOM Ref of first mutation for combination
+ // params["secondref"] - ATOM Ref of second mutation for combination
+ // mutation
+ if("combine_disk")
+ // GUARD CHECK - This code shouldn't even be callable without a diskette
+ // inserted. Unexpected result
+ if(!diskette)
+ return
+
+ // GUARD CHECK - Make sure the disk is not full.
+ if(LAZYLEN(diskette.mutations) >= diskette.max_mutations)
+ to_chat(usr,"Disk storage is full.")
+ return
+
+ // GUARD CHECK - Make sure the disk isn't set to read only, as we're
+ // attempting to write to it
+ if(diskette.read_only)
+ to_chat(usr,"Disk is set to read only mode.")
+ return
+
+ // GUARD CHECK - We're running a research-type operation. If, for some
+ // reason, somehow the DNA Console has been disconnected from the research
+ // network - Or was never in it to begin with - don't proceed
+ if(!stored_research)
+ return
+
+ var/first_bref = params["firstref"]
+ var/second_bref = params["secondref"]
+
+ // GUARD CHECK - Find the source and destination mutations on the console
+ // and make sure they actually exist.
+ var/datum/mutation/human/source_mut = get_mut_by_ref(first_bref, SEARCH_STORED | SEARCH_DISKETTE)
+ if(!source_mut)
+ return
+
+ var/datum/mutation/human/dest_mut = get_mut_by_ref(second_bref, SEARCH_STORED | SEARCH_DISKETTE)
+ if(!dest_mut)
+ return
+
+ // Attempt to mix the two mutations to get a new type
+ var/result_path = get_mixed_mutation(source_mut.type, dest_mut.type)
+
+ if(!result_path)
+ return
+
+ // If we got a new type, add it to our storage
+ diskette.mutations += new result_path()
+ to_chat(usr, "Success! New mutation has been added to the disk.")
+
+ // If it's already discovered, end here. Otherwise, add it to the list of
+ // discovered mutations
+ // We've already checked for stored_research earlier
+ if(result_path in stored_research.discovered_mutations)
+ return
+
+ var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(result_path)
+ stored_research.discovered_mutations += result_path
+ say("Successfully mutated [HM.name].")
+ return
+
+ // Sets the Genetic Makeup pulse strength.
+ // ---------------------------------------------------------------------- //
+ // params["val"] - New strength value as text string, converted to number
+ // later on in code
+ if("set_pulse_strength")
+ var/value = round(text2num(params["val"]))
+ radstrength = WRAP(value, 1, RADIATION_STRENGTH_MAX+1)
+ return
+
+ // Sets the Genetic Makeup pulse duration
+ // ---------------------------------------------------------------------- //
+ // params["val"] - New strength value as text string, converted to number
+ // later on in code
+ if("set_pulse_duration")
+ var/value = round(text2num(params["val"]))
+ radduration = WRAP(value, 1, RADIATION_DURATION_MAX+1)
+ return
+
+ // Saves Genetic Makeup information to disk
+ // ---------------------------------------------------------------------- //
+ // params["index"] - The BYOND index of the console genetic makeup buffer to
+ // copy to disk
+ if("save_makeup_disk")
+ // GUARD CHECK - This code shouldn't even be callable without a diskette
+ // inserted. Unexpected result
+ if(!diskette)
+ return
+
+ // GUARD CHECK - Make sure the disk isn't set to read only, as we're
+ // attempting to write to it
+ if(diskette.read_only)
+ to_chat(usr,"Disk is set to read only mode.")
+ return
+
+ // Convert the index to a number and clamp within the array range
+ var/buffer_index = text2num(params["index"])
+ buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS)
+
+ var/list/buffer_slot = genetic_makeup_buffer[buffer_index]
+
+ // GUARD CHECK - This should not be possible to activate on a buffer slot
+ // that doesn't have any genetic data. Unexpected result
+ if(!istype(buffer_slot))
+ return
+
+ diskette.genetic_makeup_buffer = buffer_slot.Copy()
+ return
+
+ // Loads Genetic Makeup from disk to a console buffer
+ // ---------------------------------------------------------------------- //
+ // params["index"] - The BYOND index of the console genetic makeup buffer to
+ // copy to. Expected as text string, converted to number later
+ if("load_makeup_disk")
+ // GUARD CHECK - This code shouldn't even be callable without a diskette
+ // inserted. Unexpected result
+ if(!diskette)
+ return
+
+ // GUARD CHECK - This should not be possible to activate on a diskette
+ // that doesn't have any genetic data. Unexpected result
+ if(LAZYLEN(diskette.genetic_makeup_buffer) == 0)
+ return
+
+ // Convert the index to a number and clamp within the array range, then
+ // copy the data from the disk to that buffer
+ var/buffer_index = text2num(params["index"])
+ buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS)
+ genetic_makeup_buffer[buffer_index] = diskette.genetic_makeup_buffer.Copy()
+ return
+
+ // Deletes genetic makeup buffer from the inserted diskette
+ if("del_makeup_disk")
+ // GUARD CHECK - This code shouldn't even be callable without a diskette
+ // inserted. Unexpected result
+ if(!diskette)
+ return
+
+ // GUARD CHECK - Make sure the disk isn't set to read only, as we're
+ // attempting to write (via deletion) to it
+ if(diskette.read_only)
+ to_chat(usr,"Disk is set to read only mode.")
+ return
+
+ diskette.genetic_makeup_buffer.Cut()
+ return
+
+ // Saves the scanner occupant's genetic makeup to a given console buffer
+ // ---------------------------------------------------------------------- //
+ // params["index"] - The BYOND index of the console genetic makeup buffer to
+ // save the new genetic data to. Expected as text string, converted to
+ // number later
+ if("save_makeup_console")
+ // GUARD CHECK - Can we genetically modify the occupant? Includes scanner
+ // operational guard checks.
+ if(!can_modify_occupant())
+ return
+
+ // Convert the index to a number and clamp within the array range, then
+ // copy the data from the disk to that buffer
+ var/buffer_index = text2num(params["index"])
+ buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS)
+
+ // Set the new information
+ genetic_makeup_buffer[buffer_index] = list(
+ "label"="Slot [buffer_index]:[scanner_occupant.real_name]",
+ "UI"=scanner_occupant.dna.uni_identity,
+ "UE"=scanner_occupant.dna.unique_enzymes,
+ "name"=scanner_occupant.real_name,
+ "blood_type"=scanner_occupant.dna.blood_type)
+
+ return
+
+ // Deleted genetic makeup data from a console buffer slot
+ // ---------------------------------------------------------------------- //
+ // params["index"] - The BYOND index of the console genetic makeup buffer to
+ // delete the genetic data from. Expected as text string, converted to
+ // number later
+ if("del_makeup_console")
+ // Convert the index to a number and clamp within the array range, then
+ // copy the data from the disk to that buffer
+ var/buffer_index = text2num(params["index"])
+ buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS)
+ var/list/buffer_slot = genetic_makeup_buffer[buffer_index]
+
+ // GUARD CHECK - This shouldn't be possible to execute this on a null
+ // buffer. Unexpected resut
+ if(!istype(buffer_slot))
+ return
+
+ genetic_makeup_buffer[buffer_index] = null
+ return
+
+ // Eject stored diskette from console
+ if("eject_disk")
+ eject_disk(usr)
+ return
+
+ // Create a Genetic Makeup injector. These injectors are timed and thus are
+ // only temporary
+ // ---------------------------------------------------------------------- //
+ // params["index"] - The BYOND index of the console genetic makeup buffer to
+ // create the makeup injector from. Expected as text string, converted to
+ // number later
+ // params["type"] - Type of injector to create
+ // Expected results:
+ // "ue" - Unique Enzyme, changes name and blood type
+ // "ui" - Unique Identity, changes looks
+ // "mixed" - Combination of both ue and ui
+ if("makeup_injector")
+ // Convert the index to a number and clamp within the array range, then
+ // copy the data from the disk to that buffer
+ var/buffer_index = text2num(params["index"])
+ buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS)
+ var/list/buffer_slot = genetic_makeup_buffer[buffer_index]
+
+ // GUARD CHECK - This shouldn't be possible to execute this on a null
+ // buffer. Unexpected resut
+ if(!istype(buffer_slot))
+ return
+
+ var/type = params["type"]
+ var/obj/item/dnainjector/timed/I
+
+ switch(type)
+ if("ui")
+ // GUARD CHECK - There's currently no way to save partial genetic data.
+ // However, if this is the case, we can't make a complete injector and
+ // this catches that edge case
+ if(!buffer_slot["UI"])
+ to_chat(usr,"Genetic data corrupted, unable to create injector.")
+ return
+
+ I = new /obj/item/dnainjector/timed(loc)
+ I.fields = list("UI"=buffer_slot["UI"])
+
+ // If there is a connected scanner, we can use its upgrades to reduce
+ // the radiation generated by this injector
+ if(scanner_operational())
+ I.damage_coeff = connected_scanner.damage_coeff
+ if("ue")
+ // GUARD CHECK - There's currently no way to save partial genetic data.
+ // However, if this is the case, we can't make a complete injector and
+ // this catches that edge case
+ if(!buffer_slot["name"] || !buffer_slot["UE"] || !buffer_slot["blood_type"])
+ to_chat(usr,"Genetic data corrupted, unable to create injector.")
+ return
+
+ I = new /obj/item/dnainjector/timed(loc)
+ I.fields = list("name"=buffer_slot["name"], "UE"=buffer_slot["UE"], "blood_type"=buffer_slot["blood_type"])
+
+ // If there is a connected scanner, we can use its upgrades to reduce
+ // the radiation generated by this injector
+ if(scanner_operational())
+ I.damage_coeff = connected_scanner.damage_coeff
+ if("mixed")
+ // GUARD CHECK - There's currently no way to save partial genetic data.
+ // However, if this is the case, we can't make a complete injector and
+ // this catches that edge case
+ if(!buffer_slot["UI"] || !buffer_slot["name"] || !buffer_slot["UE"] || !buffer_slot["blood_type"])
+ to_chat(usr,"Genetic data corrupted, unable to create injector.")
+ return
+
+ I = new /obj/item/dnainjector/timed(loc)
+ I.fields = list("UI"=buffer_slot["UI"],"name"=buffer_slot["name"], "UE"=buffer_slot["UE"], "blood_type"=buffer_slot["blood_type"])
+
+ // If there is a connected scanner, we can use its upgrades to reduce
+ // the radiation generated by this injector
+ if(scanner_operational())
+ I.damage_coeff = connected_scanner.damage_coeff
+
+ // If we successfully created an injector, don't forget to set the new
+ // ready timer.
+ if(I)
+ injectorready = world.time + INJECTOR_TIMEOUT
+
+ return
+
+ // Applies a genetic makeup buffer to the scanner occupant
+ // ---------------------------------------------------------------------- //
+ // params["index"] - The BYOND index of the console genetic makeup buffer to
+ // apply to the scanner occupant. Expected as text string, converted to
+ // number later
+ // params["type"] - Type of genetic makeup copy to implement
+ // Expected results:
+ // "ue" - Unique Enzyme, changes name and blood type
+ // "ui" - Unique Identity, changes looks
+ // "mixed" - Combination of both ue and ui
+ if("makeup_apply")
+ // GUARD CHECK - Can we genetically modify the occupant? Includes scanner
+ // operational guard checks.
+ if(!can_modify_occupant())
+ return
+
+ // Convert the index to a number and clamp within the array range, then
+ // copy the data from the disk to that buffer
+ var/buffer_index = text2num(params["index"])
+ buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS)
+ var/list/buffer_slot = genetic_makeup_buffer[buffer_index]
+
+ // GUARD CHECK - This shouldn't be possible to execute this on a null
+ // buffer. Unexpected resut
+ if(!istype(buffer_slot))
+ return
+
+ var/type = params["type"]
+
+ apply_genetic_makeup(type, buffer_slot)
+ return
+
+ // Applies a genetic makeup buffer to the next scanner occupant. This sets
+ // some code that will run when the connected DNA Scanner door is next
+ // closed
+ // This allows people to self-modify their genetic makeup, as tgui
+ // interfaces can not be accessed while inside the DNA Scanner and genetic
+ // makeup injectors are only temporary
+ // ---------------------------------------------------------------------- //
+ // params["index"] - The BYOND index of the console genetic makeup buffer to
+ // apply to the scanner occupant. Expected as text string, converted to
+ // number later
+ // params["type"] - Type of genetic makeup copy to implement
+ // Expected results:
+ // "ue" - Unique Enzyme, changes name and blood type
+ // "ui" - Unique Identity, changes looks
+ // "mixed" - Combination of both ue and ui
+ if("makeup_delay")
+ // Convert the index to a number and clamp within the array range, then
+ // copy the data from the disk to that buffer
+ var/buffer_index = text2num(params["index"])
+ buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS)
+ var/list/buffer_slot = genetic_makeup_buffer[buffer_index]
+
+ // GUARD CHECK - This shouldn't be possible to execute this on a null
+ // buffer. Unexpected resut
+ if(!istype(buffer_slot))
+ return
+
+ var/type = params["type"]
+
+ // Set the delayed action. The next time the scanner door is closed,
+ // unless this is cancelled in the UI, the action will happen
+ delayed_action = list("type" = type, "buffer_slot" = buffer_slot)
+ return
+
+ // Attempts to modify the indexed element of the Unique Identity string
+ // This is a time delayed action that is handled in process()
+ // ---------------------------------------------------------------------- //
+ // params["index"] - The BYOND index of the Unique Identity string to
+ // attempt to modify
+ if("makeup_pulse")
+ // GUARD CHECK - Can we genetically modify the occupant? Includes scanner
+ // operational guard checks.
+ if(!can_modify_occupant())
+ return
+
+ // Set the appropriate timer and index to pulse. This is then managed
+ // later on in process()
+ var/len = length_char(scanner_occupant.dna.uni_identity)
+ rad_pulse_timer = world.time + (radduration*10)
+ rad_pulse_index = WRAP(text2num(params["index"]), 1, len+1)
+ START_PROCESSING(SSobj, src)
+ return
+
+ // Cancels the delayed action - In this context it is not the radiation
+ // pulse from "makeup_pulse", which can not be cancelled. It is instead
+ // the delayed genetic transfer from "makeup_delay"
+ if("cancel_delay")
+ delayed_action = null
+ return
+
+ // Creates a new advanced injector storage buffer in the console
+ // ---------------------------------------------------------------------- //
+ // params["name"] - The name to apply to the new injector
+ if("new_adv_inj")
+ // GUARD CHECK - Make sure we can make a new injector. This code should
+ // not be called if we're already maxed out and this is an Unexpected
+ // result
+ if(!(LAZYLEN(injector_selection) < max_injector_selections))
+ return
+
+ // GUARD CHECK - Sanitise and trim the proposed name. This prevents HTML
+ // injection and equivalent as tgui input is not stripped
+ var/inj_name = params["name"]
+ inj_name = trim(sanitize(inj_name))
+
+ // GUARD CHECK - If the name is null or blank, or the name is already in
+ // the list of advanced injectors, we want to reject it as we can't have
+ // duplicate named advanced injectors
+ if(!inj_name || (inj_name in injector_selection))
+ return
+
+ injector_selection[inj_name] = list()
+ return
+
+ // Deleted an advanced injector storage buffer from the console
+ // ---------------------------------------------------------------------- //
+ // params["name"] - The name of the injector to delete
+ if("del_adv_inj")
+ var/inj_name = params["name"]
+
+ // GUARD CHECK - If the name is null or blank, reject.
+ // GUARD CHECK - If the name isn't in the list of advanced injectors, we
+ // want to reject this as it shouldn't be possible ever do this.
+ // Unexpected result
+ if(!inj_name || !(inj_name in injector_selection))
+ return
+
+ injector_selection.Remove(inj_name)
+ return
+
+ // Creates an injector from an advanced injector buffer
+ // ---------------------------------------------------------------------- //
+ // params["name"] - The name of the injector to print
+ if("print_adv_inj")
+ // As a side note, because mutations can contain unique metadata,
+ // this system uses BYOND Atom Refs to safely and accurately
+ // identify mutations from big ol' lists.
+
+ // GUARD CHECK - Is the injector actually ready?
+ if(world.time < injectorready)
+ return
+
+ var/inj_name = params["name"]
+
+ // GUARD CHECK - If the name is null or blank, reject.
+ // GUARD CHECK - If the name isn't in the list of advanced injectors, we
+ // want to reject this as it shouldn't be possible ever do this.
+ // Unexpected result
+ if(!inj_name || !(inj_name in injector_selection))
+ return
+
+ var/list/injector = injector_selection[inj_name]
+ var/obj/item/dnainjector/activator/I = new /obj/item/dnainjector/activator(loc)
+
+ // Run through each mutation in our Advanced Injector and add them to a
+ // new injector
+ for(var/A in injector)
+ var/datum/mutation/human/HM = A
+ I.add_mutations += new HM.type(copymut=HM)
+
+ // Force apply any mutations, this is functionality similar to mutators
+ I.doitanyway = TRUE
+ I.name = "Advanced [inj_name] injector"
+
+ // If there's an operational connected scanner, we can use its upgrades
+ // to improve our injector's radiation generation
+ if(scanner_operational())
+ I.damage_coeff = connected_scanner.damage_coeff
+ injectorready = world.time + INJECTOR_TIMEOUT * 8 * (1 - 0.1 * connected_scanner.precision_coeff)
+ else
+ injectorready = world.time + INJECTOR_TIMEOUT * 8
+
+ return
+
+ // Adds a mutation to an advanced injector
+ // ---------------------------------------------------------------------- //
+ // params["mutref"] - ATOM Ref of specific mutation to add to the injector
+ // params["advinj"] - Name of the advanced injector to add the mutation to
+ if("add_advinj_mut")
+ // GUARD CHECK - Can we genetically modify the occupant? Includes scanner
+ // operational guard checks.
+ // This is needed because this operation can only be completed from the
+ // genetic sequencer.
+ if(!can_modify_occupant())
+ return
+
+ var/adv_inj = params["advinj"]
+
+ // GUARD CHECK - Make sure our advanced injector actually exists. This
+ // should not be possible. Unexpected result
+ if(!(adv_inj in injector_selection))
+ return
+
+ // GUARD CHECK - Make sure we limit the number of mutations appropriately
+ if(LAZYLEN(injector_selection[adv_inj]) >= max_injector_mutations)
+ to_chat(usr,"Advanced injector mutation storage is full.")
+ return
+
+ var/mut_source = params["source"]
+ var/search_flag = 0
+
+ switch(mut_source)
+ if("disk")
+ search_flag = SEARCH_DISKETTE
+ if("occupant")
+ search_flag = SEARCH_OCCUPANT
+ if("console")
+ search_flag = SEARCH_STORED
+
+ if(!search_flag)
+ return
+
+ var/bref = params["mutref"]
+ // We've already made sure we can modify the occupant, so this is safe to
+ // call
+ var/datum/mutation/human/HM = get_mut_by_ref(bref, search_flag)
+
+ // GUARD CHECK - This should not be possible. Unexpected result
+ if(!HM)
+ return
+
+ // We want to make sure we stick within the instability limit.
+ // We start with the instability of the mutation we're intending to add.
+ var/instability_total = HM.instability
+
+ // We then add the instabilities of all other mutations in the injector,
+ // remembering to apply the Stabilizer chromosome modifiers
+ for(var/datum/mutation/human/I in injector_selection[adv_inj])
+ instability_total += I.instability * GET_MUTATION_STABILIZER(I)
+
+ // If this would take us over the max instability, we inform the user.
+ if(instability_total > max_injector_instability)
+ to_chat(usr,"Extra mutation would make the advanced injector too instable.")
+ return
+
+ // If we've got here, all our checks are passed and we can successfully
+ // add the mutation to the advanced injector.
+ var/datum/mutation/human/A = new HM.type()
+ A.copy_mutation(HM)
+ injector_selection[adv_inj] += A
+ to_chat(usr,"Mutation successfully added to advanced injector.")
+ return
+
+ // Deletes a mutation from an advanced injector
+ // ---------------------------------------------------------------------- //
+ // params["mutref"] - ATOM Ref of specific mutation to del from the injector
+ if("delete_injector_mut")
+ var/bref = params["mutref"]
+
+ var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_ADV_INJ)
+
+ // GUARD CHECK - This should not be possible. Unexpected result
+ if(!HM)
+ return
+
+ // Check Advanced Injectors to find and remove the mutation
+ for(var/I in injector_selection)
+ var/list/injstuff = injector_selection["[I]"]
+ if(injstuff.Remove(HM))
+ qdel(HM)
+ return
+
+ return
+
+ // Sets a new tgui view state
+ // ---------------------------------------------------------------------- //
+ // params["id"] - Key for the state to set
+ // params[...] - Every other element is used to set state variables
+ if("set_view")
+ for (var/key in params)
+ if(key == "src")
+ continue
+ tgui_view_state[key] = params[key]
+ return TRUE
+ return FALSE
+
+/**
+ * Applies the enzyme buffer to the current scanner occupant
+ *
+ * Applies the type of a specific genetic makeup buffer to the current scanner
+ * occupant
+ *
+ * Arguments:
+ * * type - "ui"/"ue"/"mixed" - Which part of the enzyme buffer to apply
+ * * buffer_slot - Index of the enzyme buffer to apply
+ */
+/obj/machinery/computer/scan_consolenew/proc/apply_genetic_makeup(type, buffer_slot)
+ // Note - This proc is only called from code that has already performed the
+ // necessary occupant guard checks. If you call this code yourself, please
+ // apply can_modify_occupant() or equivalent checks first.
+
+ // Pre-calc the rad increase since we'll be using it in all the possible
+ // operations
+ var/rad_increase = rand(100/(connected_scanner.damage_coeff ** 2),250/(connected_scanner.damage_coeff ** 2))
+
+ switch(type)
+ if("ui")
+ // GUARD CHECK - There's currently no way to save partial genetic data.
+ // However, if this is the case, we can't make a complete injector and
+ // this catches that edge case
+ if(!buffer_slot["UI"])
+ to_chat(usr,"Genetic data corrupted, unable to apply genetic data.")
+ return FALSE
+ scanner_occupant.dna.uni_identity = buffer_slot["UI"]
+ scanner_occupant.updateappearance(mutations_overlay_update=1)
+ scanner_occupant.radiation += rad_increase
+ scanner_occupant.domutcheck()
+ return TRUE
+ if("ue")
+ // GUARD CHECK - There's currently no way to save partial genetic data.
+ // However, if this is the case, we can't make a complete injector and
+ // this catches that edge case
+ if(!buffer_slot["name"] || !buffer_slot["UE"] || !buffer_slot["blood_type"])
+ to_chat(usr,"Genetic data corrupted, unable to apply genetic data.")
+ return FALSE
+ scanner_occupant.real_name = buffer_slot["name"]
+ scanner_occupant.name = buffer_slot["name"]
+ scanner_occupant.dna.unique_enzymes = buffer_slot["UE"]
+ scanner_occupant.dna.blood_type = buffer_slot["blood_type"]
+ scanner_occupant.radiation += rad_increase
+ scanner_occupant.domutcheck()
+ return TRUE
+ if("mixed")
+ // GUARD CHECK - There's currently no way to save partial genetic data.
+ // However, if this is the case, we can't make a complete injector and
+ // this catches that edge case
+ if(!buffer_slot["UI"] || !buffer_slot["name"] || !buffer_slot["UE"] || !buffer_slot["blood_type"])
+ to_chat(usr,"Genetic data corrupted, unable to apply genetic data.")
+ return FALSE
+ scanner_occupant.dna.uni_identity = buffer_slot["UI"]
+ scanner_occupant.updateappearance(mutations_overlay_update=1)
+ scanner_occupant.real_name = buffer_slot["name"]
+ scanner_occupant.name = buffer_slot["name"]
+ scanner_occupant.dna.unique_enzymes = buffer_slot["UE"]
+ scanner_occupant.dna.blood_type = buffer_slot["blood_type"]
+ scanner_occupant.radiation += rad_increase
+ scanner_occupant.domutcheck()
+ return TRUE
+
+ return FALSE
+
+/**
+ * Checks if there is a connected DNA Scanner that is operational
+ */
+/obj/machinery/computer/scan_consolenew/proc/scanner_operational()
+ if(!connected_scanner)
+ return FALSE
+
+ return (connected_scanner && connected_scanner.is_operational())
+
+/**
+ * Checks if there is a valid DNA Scanner occupant for genetic modification
+ *
+ * Checks if there is a valid subject in the DNA Scanner that can be genetically
+ * modified. Will set the scanner occupant var as part of this check.
+ * Requires that the scanner can be operated and will return early if it can't
+ */
+/obj/machinery/computer/scan_consolenew/proc/can_modify_occupant()
+ // GUARD CHECK - We always want to perform the scanner operational check as
+ // part of checking if we can modify the occupant.
+ // We can never modify the occupant of a broken scanner.
+ if(!scanner_operational())
+ return FALSE
+
+ if(!connected_scanner.occupant)
+ return FALSE
+
+ scanner_occupant = connected_scanner.occupant
+
+ // Check validity of occupent for DNA Modification
+ // DNA Modification:
+ // requires DNA
+ // this DNA can not be bad
+ // is done via radiation bursts, so radiation immune carbons are not viable
+ // And the DNA Scanner itself must have a valid scan level
+ if(scanner_occupant.has_dna() && !HAS_TRAIT(scanner_occupant, TRAIT_RADIMMUNE) && !HAS_TRAIT(scanner_occupant, TRAIT_BADDNA) || (connected_scanner.scan_level == 3))
+ return TRUE
+
+ return FALSE
+
+/**
+ * Called by connected DNA Scanners when their doors close.
+ *
+ * Sets the new scanner occupant and completes delayed enzyme transfer if one
+ * is queued.
+ */
+/obj/machinery/computer/scan_consolenew/proc/on_scanner_close()
+ SIGNAL_HANDLER
+ // Set the appropriate occupant now the scanner is closed
+ if(connected_scanner.occupant)
+ scanner_occupant = connected_scanner.occupant
+ else
+ scanner_occupant = null
+
+ // If we have a delayed action - In this case the only delayed action is
+ // applying a genetic makeup buffer the next time the DNA Scanner is closed -
+ // we want to perform it.
+ // GUARD CHECK - Make sure we can modify the occupant, apply_genetic_makeup()
+ // assumes we've already done this.
+ if(delayed_action && can_modify_occupant())
+ var/type = delayed_action["type"]
+ var/buffer_slot = delayed_action["buffer_slot"]
+ if(apply_genetic_makeup(type, buffer_slot))
+ to_chat(connected_scanner.occupant, "[src] activates!")
+ delayed_action = null
+
+/**
+ * Called by connected DNA Scanners when their doors open.
+ *
+ * Clears enzyme pulse operations, stops processing and nulls the current
+ * scanner occupant var.
+ */
+/obj/machinery/computer/scan_consolenew/proc/on_scanner_open()
+ SIGNAL_HANDLER
+ // If we had a radiation pulse action ongoing, we want to stop this.
+ // Imagine it being like a microwave stopping when you open the door.
+ rad_pulse_index = 0
+ rad_pulse_timer = 0
+ STOP_PROCESSING(SSobj, src)
+ scanner_occupant = null
+
+/**
+ * Builds the genetic makeup list which will be sent to tgui interface.
+ */
+/obj/machinery/computer/scan_consolenew/proc/build_genetic_makeup_list()
+ // No code will ever null this list, we can safely Cut it.
+ tgui_genetic_makeup.Cut()
+
+ for(var/i=1, i <= NUMBER_OF_BUFFERS, i++)
+ if(genetic_makeup_buffer[i])
+ tgui_genetic_makeup["[i]"] = genetic_makeup_buffer[i].Copy()
+ else
+ tgui_genetic_makeup["[i]"] = null
+
+/**
+ * Builds the genetic makeup list which will be sent to tgui interface.
+ *
+ * Will iterate over the connected scanner occupant, DNA Console, inserted
+ * diskette and chromosomes and any advanced injectors, building the main data
+ * structures which get passed to the tgui interface.
+ */
+/obj/machinery/computer/scan_consolenew/proc/build_mutation_list(can_modify_occ)
+ // No code will ever null these lists. We can safely Cut them.
+ tgui_occupant_mutations.Cut()
+ tgui_diskette_mutations.Cut()
+ tgui_console_mutations.Cut()
+ tgui_console_chromosomes.Cut()
+ tgui_advinjector_mutations.Cut()
+
+ // ------------------------------------------------------------------------ //
+ // GUARD CHECK - Can we genetically modify the occupant? This check will have
+ // previously included checks to make sure the DNA Scanner is still
+ // operational
+ if(can_modify_occ)
+ // ---------------------------------------------------------------------- //
+ // Start cataloguing all mutations that the occupant has by default
+ for(var/mutation_type in scanner_occupant.dna.mutation_index)
+ var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(mutation_type)
+
+ var/list/mutation_data = list()
+ var/text_sequence = scanner_occupant.dna.mutation_index[mutation_type]
+ var/default_sequence = scanner_occupant.dna.default_mutation_genes[mutation_type]
+ var/discovered = (stored_research && (mutation_type in stored_research.discovered_mutations))
+
+ mutation_data["Alias"] = HM.alias
+ mutation_data["Sequence"] = text_sequence
+ mutation_data["DefaultSeq"] = default_sequence
+ mutation_data["Discovered"] = discovered
+ mutation_data["Source"] = "occupant"
+
+ // We only want to pass this information along to the tgui interface if
+ // the mutation has been discovered. Prevents people being able to cheese
+ // or "hack" their way to figuring out what undiscovered mutations are
+ if(discovered)
+ mutation_data["Name"] = HM.name
+ mutation_data["Description"] = HM.desc
+ mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM)
+ mutation_data["Quality"] = HM.quality
+
+ // Assume the mutation is normal unless assigned otherwise.
+ var/mut_class = MUT_NORMAL
+
+ // Check if the mutation is currently activated. If it is, we can add even
+ // MORE information to send to tgui.
+ var/datum/mutation/human/A = scanner_occupant.dna.get_mutation(mutation_type)
+ if(A)
+ mutation_data["Active"] = TRUE
+ mutation_data["Scrambled"] = A.scrambled
+ mutation_data["Class"] = A.class
+ mut_class = A.class
+ mutation_data["CanChromo"] = A.can_chromosome
+ mutation_data["ByondRef"] = REF(A)
+ mutation_data["Type"] = A.type
+ if(A.can_chromosome)
+ mutation_data["ValidChromos"] = jointext(A.valid_chrom_list, ", ")
+ mutation_data["AppliedChromo"] = A.chromosome_name
+ mutation_data["ValidStoredChromos"] = build_chrom_list(A)
+ else
+ mutation_data["Active"] = FALSE
+ mutation_data["Scrambled"] = FALSE
+ mutation_data["Class"] = MUT_NORMAL
+
+ // Technically NONE of these mutations should be MUT_EXTRA but this will
+ // catch any weird edge cases
+ // Assign icons by priority - MUT_EXTRA will ALSO be discovered, so it
+ // has a higher priority for icon/image assignment
+ if (mut_class == MUT_EXTRA)
+ mutation_data["Image"] = "dna_extra.gif"
+ else if(discovered)
+ mutation_data["Image"] = "dna_discovered.gif"
+ else
+ mutation_data["Image"] = "dna_undiscovered.gif"
+
+ tgui_occupant_mutations += list(mutation_data)
+
+ // ---------------------------------------------------------------------- //
+ // Now get additional/"extra" mutations that they shouldn't have by default
+ for(var/datum/mutation/human/HM in scanner_occupant.dna.mutations)
+ // If it's in the mutation index array, we've already catalogued this
+ // mutation and can safely skip over it. It really shouldn't be, but this
+ // will catch any weird edge cases
+ if(HM.type in scanner_occupant.dna.mutation_index)
+ continue
+
+ var/list/mutation_data = list()
+ var/text_sequence = GET_SEQUENCE(HM.type)
+
+ // These will all be active mutations. They're added by injector and their
+ // sequencing code can't be changed. They can only be nullified, which
+ // completely removes them.
+ var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(HM.type)
+
+ mutation_data["Alias"] = A.alias
+ mutation_data["Sequence"] = text_sequence
+ mutation_data["Discovered"] = TRUE
+ mutation_data["Quality"] = HM.quality
+ mutation_data["Source"] = "occupant"
+
+ mutation_data["Name"] = HM.name
+ mutation_data["Description"] = HM.desc
+ mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM)
+
+ mutation_data["Active"] = TRUE
+ mutation_data["Scrambled"] = HM.scrambled
+ mutation_data["Class"] = HM.class
+ mutation_data["CanChromo"] = HM.can_chromosome
+ mutation_data["ByondRef"] = REF(HM)
+ mutation_data["Type"] = HM.type
+
+ if(HM.can_chromosome)
+ mutation_data["ValidChromos"] = jointext(HM.valid_chrom_list, ", ")
+ mutation_data["AppliedChromo"] = HM.chromosome_name
+ mutation_data["ValidStoredChromos"] = build_chrom_list(HM)
+
+ // Nothing in this list should be undiscovered. Technically nothing
+ // should be anything but EXTRA. But we're just handling some edge cases.
+ if (HM.class == MUT_EXTRA)
+ mutation_data["Image"] = "dna_extra.gif"
+ else
+ mutation_data["Image"] = "dna_discovered.gif"
+
+ tgui_occupant_mutations += list(mutation_data)
+
+ // ------------------------------------------------------------------------ //
+ // Build the list of mutations stored within the DNA Console
+ for(var/datum/mutation/human/HM in stored_mutations)
+ var/list/mutation_data = list()
+
+ var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(HM.type)
+
+ mutation_data["Alias"] = A.alias
+ mutation_data["Name"] = HM.name
+ mutation_data["Source"] = "console"
+ mutation_data["Active"] = TRUE
+ mutation_data["Description"] = HM.desc
+ mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM)
+ mutation_data["ByondRef"] = REF(HM)
+ mutation_data["Type"] = HM.type
+
+ mutation_data["CanChromo"] = HM.can_chromosome
+ if(HM.can_chromosome)
+ mutation_data["ValidChromos"] = jointext(HM.valid_chrom_list, ", ")
+ mutation_data["AppliedChromo"] = HM.chromosome_name
+ mutation_data["ValidStoredChromos"] = build_chrom_list(HM)
+
+ tgui_console_mutations += list(mutation_data)
+
+ // ------------------------------------------------------------------------ //
+ // Build the list of chromosomes stored within the DNA Console
+ var/chrom_index = 1
+ for(var/obj/item/chromosome/CM in stored_chromosomes)
+ var/list/chromo_data = list()
+
+ chromo_data["Name"] = CM.name
+ chromo_data["Description"] = CM.desc
+ chromo_data["Index"] = chrom_index
+
+ tgui_console_chromosomes += list(chromo_data)
+ ++chrom_index
+
+ // ------------------------------------------------------------------------ //
+ // Build the list of mutations stored on any inserted diskettes
+ if(diskette)
+ for(var/datum/mutation/human/HM in diskette.mutations)
+ var/list/mutation_data = list()
+
+ var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(HM.type)
+
+ mutation_data["Alias"] = A.alias
+ mutation_data["Name"] = HM.name
+ mutation_data["Active"] = TRUE
+ //mutation_data["Sequence"] = GET_SEQUENCE(HM.type)
+ mutation_data["Source"] = "disk"
+ mutation_data["Description"] = HM.desc
+ mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM)
+ mutation_data["ByondRef"] = REF(HM)
+ mutation_data["Type"] = HM.type
+
+ mutation_data["CanChromo"] = HM.can_chromosome
+ if(HM.can_chromosome)
+ mutation_data["ValidChromos"] = jointext(HM.valid_chrom_list, ", ")
+ mutation_data["AppliedChromo"] = HM.chromosome_name
+ mutation_data["ValidStoredChromos"] = build_chrom_list(HM)
+
+ tgui_diskette_mutations += list(mutation_data)
+
+ // ------------------------------------------------------------------------ //
+ // Build the list of mutations stored within any Advanced Injectors
+ if(LAZYLEN(injector_selection))
+ for(var/I in injector_selection)
+ var/list/mutations = list()
+ for(var/datum/mutation/human/HM in injector_selection[I])
+ var/list/mutation_data = list()
+
+ var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(HM.type)
+
+ mutation_data["Alias"] = A.alias
+ mutation_data["Name"] = HM.name
+ mutation_data["Active"] = TRUE
+ //mutation_data["Sequence"] = GET_SEQUENCE(HM.type)
+ mutation_data["Source"] = "injector"
+ mutation_data["Description"] = HM.desc
+ mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM)
+ mutation_data["ByondRef"] = REF(HM)
+ mutation_data["Type"] = HM.type
+
+ if(HM.can_chromosome)
+ mutation_data["AppliedChromo"] = HM.chromosome_name
+
+ mutations += list(mutation_data)
+ tgui_advinjector_mutations += list(list(
+ "name" = "[I]",
+ "mutations" = mutations,
+ ))
+
+/**
+ * Takes any given chromosome and calculates chromosome compatibility
+ *
+ * Will iterate over the stored chromosomes in the DNA Console and will check
+ * whether it can be applied to the supplied mutation. Then returns a list of
+ * names of chromosomes that were compatible.
+ *
+ * Arguments:
+ * * mutation - The mutation to check chromosome compatibility with
+ */
+/obj/machinery/computer/scan_consolenew/proc/build_chrom_list(mutation)
+ var/list/chromosomes = list()
+
+ for(var/obj/item/chromosome/CM in stored_chromosomes)
+ if(CM.can_apply(mutation))
+ chromosomes += CM.name
+
+ return chromosomes
+
+
+/**
+ * Checks whether a mutation alias has been discovered
+ *
+ * Checks whether a given mutation's genetic sequence has been completed and
+ * discovers it if appropriate
+ *
+ * Arguments:
+ * * alias - Alias of the mutation to check (ie "Mutation 51" or "Mutation 12")
+ */
+/obj/machinery/computer/scan_consolenew/proc/check_discovery(alias)
+ // Note - All code paths that call this have already done checks on the
+ // current occupant to prevent cheese and other abuses. If you call this
+ // proc please also do the following checks first:
+ // if(!can_modify_occupant())
+ // return
+ // if(!(scanner_occupant == connected_scanner.occupant))
+ // return
+
+ // Turn the alias ("Mutation 1", "Mutation 35") into a mutation path
+ var/path = GET_MUTATION_TYPE_FROM_ALIAS(alias)
+
+ // Check to see if this mutation is in the active mutation list. If it isn't,
+ // then the mutation isn't eligible for discovery. If it is but is scrambled,
+ // then the mutation isn't eligible for discovery. Finally, check if the
+ // mutation is in discovered mutations - If it isn't, add it to discover.
+ var/datum/mutation/human/M = scanner_occupant.dna.get_mutation(path)
+ if(!M)
+ return FALSE
+ if(M.scrambled)
+ return FALSE
+ if(stored_research && !(path in stored_research.discovered_mutations))
+ var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(path)
+ stored_research.discovered_mutations += path
+ say("Successfully discovered [HM.name].")
+ return TRUE
+
+ return FALSE
+
+/**
+ * Find a mutation from various storage locations via ATOM ref
+ *
+ * Takes an ATOM Ref and searches the appropriate mutation buffers and storage
+ * vars to try and find the associated mutation.
+ *
+ * Arguments:
+ * * ref - ATOM ref of the mutation to locate
+ * * target_flags - Flags for storage mediums to search, see #defines
+ */
+/obj/machinery/computer/scan_consolenew/proc/get_mut_by_ref(ref, target_flags)
+ var/mutation
+
+ // Assume the occupant is valid and the check has been carried out before
+ // calling this proc with the relevant flags.
+ if(target_flags & SEARCH_OCCUPANT)
+ mutation = (locate(ref) in scanner_occupant.dna.mutations)
+ if(mutation)
+ return mutation
+
+ if(target_flags & SEARCH_STORED)
+ mutation = (locate(ref) in stored_mutations)
+ if(mutation)
+ return mutation
+
+ if(diskette && (target_flags & SEARCH_DISKETTE))
+ mutation = (locate(ref) in diskette.mutations)
+ if(mutation)
+ return mutation
+
+ if(injector_selection && (target_flags & SEARCH_ADV_INJ))
+ for(var/I in injector_selection)
+ mutation = (locate(ref) in injector_selection["[I]"])
+ if(mutation)
+ return mutation
+
+ return null
+
+/**
+ * Creates a randomised accuracy value for the enzyme pulse functionality.
+ *
+ * Donor code from previous DNA Console iteration.
+ *
+ * Arguments:
+ * * position - Index of the intended enzyme element to pulse
+ * * radduration - Duration of intended radiation pulse
+ * * number_of_blocks - Number of individual data blocks in the pulsed enzyme
+ */
+/obj/machinery/computer/scan_consolenew/proc/randomize_radiation_accuracy(position, radduration, number_of_blocks)
+ var/val = round(gaussian(0, RADIATION_ACCURACY_MULTIPLIER/radduration) + position, 1)
+ return WRAP(val, 1, number_of_blocks+1)
+
+/**
+ * Scrambles an enzyme element value for the enzyme pulse functionality.
+ *
+ * Donor code from previous DNA Console iteration.
+ *
+ * Arguments:
+ * * input - Enzyme identity element to scramble, expected hex value
+ * * rs - Strength of radiation pulse, increases the range of possible outcomes
+ */
+/obj/machinery/computer/scan_consolenew/proc/scramble(input,rs)
var/length = length(input)
var/ran = gaussian(0, rs*RADIATION_STRENGTH_MULTIPLIER)
if(ran == 0)
@@ -958,98 +1928,71 @@
ran = -round(-ran) //positive, so ceiling it
return num2hex(WRAP(hex2num(input)+ran, 0, 16**length), length)
-/obj/machinery/computer/scan_consolenew/proc/randomize_radiation_accuracy(position, radduration, number_of_blocks)
- var/val = round(gaussian(0, RADIATION_ACCURACY_MULTIPLIER/radduration) + position, 1)
- return WRAP(val, 1, number_of_blocks+1)
+/**
+ * Performs the enzyme radiation pulse.
+ *
+ * Donor code from previous DNA Console iteration. Called from process() when
+ * there is a radiation pulse in progress. Ends processing.
+ */
+/obj/machinery/computer/scan_consolenew/proc/rad_pulse()
+ // GUARD CHECK - Can we genetically modify the occupant? Includes scanner
+ // operational guard checks.
+ // If we can't, abort the procedure.
+ if(!can_modify_occupant())
+ rad_pulse_index = 0
+ STOP_PROCESSING(SSobj, src)
+ return
-/obj/machinery/computer/scan_consolenew/proc/get_viable_occupant()
- var/mob/living/carbon/viable_occupant = null
- if(connected)
- viable_occupant = connected.occupant
- if(!istype(viable_occupant) || !viable_occupant.dna || HAS_TRAIT(viable_occupant, TRAIT_RADIMMUNE) || HAS_TRAIT(viable_occupant, TRAIT_BADDNA))
- viable_occupant = null
- return viable_occupant
-
-/obj/machinery/computer/scan_consolenew/proc/apply_buffer(action,buffer_num)
- buffer_num = CLAMP(buffer_num, 1, NUMBER_OF_BUFFERS)
- var/list/buffer_slot = buffer[buffer_num]
- var/mob/living/carbon/viable_occupant = get_viable_occupant()
- if(istype(buffer_slot))
- viable_occupant.radiation += rand(100/(connected.damage_coeff ** 2),250/(connected.damage_coeff ** 2))
- //15 and 40 are just magic numbers that were here before so i didnt touch them, they are initial boundaries of damage
- //Each laser level reduces damage by lvl^2, so no effect on 1 lvl, 4 times less damage on 2 and 9 times less damage on 3
- //Numbers are this high because other way upgrading laser is just not worth the hassle, and i cant think of anything better to inmrove
- switch(action)
- if(SCANNER_ACTION_UI)
- if(buffer_slot["UI"])
- viable_occupant.dna.uni_identity = buffer_slot["UI"]
- viable_occupant.updateappearance(mutations_overlay_update=1)
- if(SCANNER_ACTION_UE)
- if(buffer_slot["name"] && buffer_slot["UE"] && buffer_slot["blood_type"])
- viable_occupant.real_name = buffer_slot["name"]
- viable_occupant.name = buffer_slot["name"]
- viable_occupant.dna.unique_enzymes = buffer_slot["UE"]
- viable_occupant.dna.blood_type = buffer_slot["blood_type"]
- if(SCANNER_ACTION_MIXED)
- if(buffer_slot["UI"])
- viable_occupant.dna.uni_identity = buffer_slot["UI"]
- viable_occupant.updateappearance(mutations_overlay_update=1)
- if(buffer_slot["name"] && buffer_slot["UE"] && buffer_slot["blood_type"])
- viable_occupant.real_name = buffer_slot["name"]
- viable_occupant.name = buffer_slot["name"]
- viable_occupant.dna.unique_enzymes = buffer_slot["UE"]
- viable_occupant.dna.blood_type = buffer_slot["blood_type"]
+ var/len = length_char(scanner_occupant.dna.uni_identity)
+ var/num = randomize_radiation_accuracy(rad_pulse_index, radduration + (connected_scanner.precision_coeff ** 2), len) //Each manipulator level above 1 makes randomization as accurate as selected time + manipulator lvl^2 //Value is this high for the same reason as with laser - not worth the hassle of upgrading if the bonus is low
+ var/hex = copytext_char(scanner_occupant.dna.uni_identity, num, num+1)
+ hex = scramble(hex, radstrength, radduration)
+
+ scanner_occupant.dna.uni_identity = copytext_char(scanner_occupant.dna.uni_identity, 1, num) + hex + copytext_char(scanner_occupant.dna.uni_identity, num + 1)
+ scanner_occupant.updateappearance(mutations_overlay_update=1)
+
+ rad_pulse_index = 0
+ STOP_PROCESSING(SSobj, src)
+ return
+
+/**
+ * Sets the default state for the tgui interface.
+ */
+/obj/machinery/computer/scan_consolenew/proc/set_default_state()
+ tgui_view_state["consoleMode"] = "storage"
+ tgui_view_state["storageMode"] = "console"
+ tgui_view_state["storageConsSubMode"] = "mutations"
+ tgui_view_state["storageDiskSubMode"] = "mutations"
+
+/**
+ * Ejects the DNA Disk from the console.
+ *
+ * Will insert into the user's hand if possible, otherwise will drop it at the
+ * console's location.
+ *
+ * Arguments:
+ * * user - The mob that is attempting to eject the diskette.
+ */
+/obj/machinery/computer/scan_consolenew/proc/eject_disk(mob/user)
+ // Check for diskette.
+ if(!diskette)
+ return
-/obj/machinery/computer/scan_consolenew/proc/on_scanner_close()
- if(delayed_action && get_viable_occupant())
- to_chat(connected.occupant, "[src] activates!")
- apply_buffer(delayed_action["action"],delayed_action["buffer"])
- delayed_action = null //or make it stick + reset button ?
-
-/obj/machinery/computer/scan_consolenew/proc/get_valid_mutation(mutation)
- var/mob/living/carbon/C = get_viable_occupant()
- if(C)
- var/datum/mutation/human/HM = C.dna.get_mutation(mutation)
- if(HM)
- return HM
- for(var/datum/mutation/human/A in stored_mutations)
- if(A.type == mutation)
- return A
-
-
-/obj/machinery/computer/scan_consolenew/proc/get_mutation_list(include_storage) //Returns a list of the mutation index types and any extra mutations
- var/mob/living/carbon/viable_occupant = get_viable_occupant()
- var/list/paths = list()
- if(viable_occupant)
- for(var/A in viable_occupant.dna.mutation_index)
- paths += A
- for(var/datum/mutation/human/A in viable_occupant.dna.mutations)
- if(A.class == MUT_EXTRA)
- paths += A.type
- if(include_storage)
- for(var/datum/mutation/human/A in stored_mutations)
- paths += A.type
- return paths
-
-/obj/machinery/computer/scan_consolenew/proc/get_valid_gene_string(mutation)
- var/mob/living/carbon/C = get_viable_occupant()
- if(C && (mutation in C.dna.mutation_index))
- return GET_GENE_STRING(mutation, C.dna)
- else if(C && (LAZYLEN(C.dna.mutations)))
- for(var/datum/mutation/human/A in C.dna.mutations)
- if(A.type == mutation)
- return GET_SEQUENCE(mutation)
- for(var/datum/mutation/human/A in stored_mutations)
- if(A.type == mutation)
- return GET_SEQUENCE(mutation)
-
-/obj/machinery/computer/scan_consolenew/proc/discover(mutation)
- if(stored_research && !(mutation in stored_research.discovered_mutations))
- stored_research.discovered_mutations += mutation
- return TRUE
+ to_chat(user, "You eject [diskette] from [src].")
+
+ // Reset the state to console storage.
+ tgui_view_state["storageMode"] = "console"
+
+ // If the disk shouldn't pop into the user's hand for any reason, drop it on the console instead.
+ if(!istype(user) || !Adjacent(user) || !user.put_in_active_hand(diskette))
+ diskette.forceMove(drop_location())
+ diskette = null
/////////////////////////// DNA MACHINES
#undef INJECTOR_TIMEOUT
#undef NUMBER_OF_BUFFERS
+#undef SCRAMBLE_TIMEOUT
+#undef JOKER_TIMEOUT
+#undef JOKER_UPGRADE
#undef RADIATION_STRENGTH_MAX
#undef RADIATION_STRENGTH_MULTIPLIER
@@ -1059,11 +2002,9 @@
#undef RADIATION_IRRADIATION_MULTIPLIER
-#undef SCANNER_ACTION_SE
-#undef SCANNER_ACTION_UI
-#undef SCANNER_ACTION_UE
-#undef SCANNER_ACTION_MIXED
+#undef STATUS_TRANSFORMING
-//#undef BAD_MUTATION_DIFFICULTY
-//#undef GOOD_MUTATION_DIFFICULTY
-//#undef OP_MUTATION_DIFFICULTY
+#undef SEARCH_OCCUPANT
+#undef SEARCH_STORED
+#undef SEARCH_DISKETTE
+#undef SEARCH_ADV_INJ
diff --git a/code/game/machinery/computer/launchpad_control.dm b/code/game/machinery/computer/launchpad_control.dm
index 35be677783902..74967e981d232 100644
--- a/code/game/machinery/computer/launchpad_control.dm
+++ b/code/game/machinery/computer/launchpad_control.dm
@@ -27,13 +27,26 @@
if(M.buffer && istype(M.buffer, /obj/machinery/launchpad))
if(LAZYLEN(launchpads) < maximum_pads)
launchpads |= M.buffer
+ RegisterSignal(M.buffer, COMSIG_PARENT_QDELETING, .proc/launchpad_deleted)
M.buffer = null
+ ui_update()
to_chat(user, "You upload the data from the [W.name]'s buffer.")
else
to_chat(user, "[src] cannot handle any more connections!")
else
return ..()
+/obj/machinery/computer/launchpad/proc/launchpad_deleted(datum/source)
+ SIGNAL_HANDLER
+ var/source_id = launchpads.Find(source)
+ if(source_id && selected_id)
+ if(selected_id > source_id)
+ selected_id--
+ else if(selected_id == source_id)
+ selected_id = null
+ launchpads -= source
+ ui_update()
+
/obj/machinery/computer/launchpad/proc/pad_exists(number)
var/obj/machinery/launchpad/pad = launchpads[number]
if(QDELETED(pad))
@@ -115,11 +128,11 @@
)
. = TRUE
if("rename")
- . = TRUE
var/new_name = params["name"]
if(!new_name)
return
current_pad.display_name = new_name
+ . = TRUE
if("remove")
if(usr && alert(usr, "Are you sure?", "Unlink Launchpad", "I'm Sure", "Abort") != "Abort")
launchpads -= current_pad
@@ -132,4 +145,3 @@
if("pull")
teleport(usr, current_pad, FALSE)
. = TRUE
- . = TRUE
diff --git a/code/game/machinery/computer/law.dm b/code/game/machinery/computer/law.dm
index 0b2b873f100e6..b868fd6d9d546 100644
--- a/code/game/machinery/computer/law.dm
+++ b/code/game/machinery/computer/law.dm
@@ -21,7 +21,8 @@
current = null
return
var/turf/currentloc = get_turf(current)
- if(currentloc && user.z != currentloc.z)
+ var/turf/user_turf = get_turf(user)
+ if(currentloc && user.get_virtual_z_level() != currentloc.get_virtual_z_level() && (!is_station_level(currentloc.z) || !is_station_level(user_turf.z)))
to_chat(user, "Upload failed! Unable to establish a connection to [current.name]. You're too far away!")
current = null
return
diff --git a/code/game/machinery/computer/medical.dm b/code/game/machinery/computer/medical.dm
index 646d662003535..c5d71a1ff3f85 100644
--- a/code/game/machinery/computer/medical.dm
+++ b/code/game/machinery/computer/medical.dm
@@ -169,7 +169,7 @@
dat += " Medical Robots:"
var/bdat = null
for(var/mob/living/simple_animal/bot/medbot/M in GLOB.alive_mob_list)
- if(M.z != src.z)
+ if(M.get_virtual_z_level() != src.get_virtual_z_level())
continue //only find medibots on the same z-level as the computer
var/turf/bl = get_turf(M)
if(bl) //if it can't find a turf for the medibot, then it probably shouldn't be showing up
@@ -271,7 +271,7 @@
"
@@ -819,7 +953,11 @@ What a mess.*/
if("none")
active2.fields["criminal"] = "None"
if("arrest")
- active2.fields["criminal"] = "*Arrest*"
+ active2.fields["criminal"] = "Arrest"
+ if("search")
+ active2.fields["criminal"] = "Search"
+ if("monitor")
+ active2.fields["criminal"] = "Monitor"
if("incarcerated")
active2.fields["criminal"] = "Incarcerated"
if("paroled")
@@ -838,6 +976,8 @@ What a mess.*/
if("Delete Record (ALL) Execute")
if(active1)
investigate_log("[key_name(usr)] has deleted all records for [active1.fields["name"]].", INVESTIGATE_RECORDS)
+ if(isAI(usr) || iscyborg(usr))
+ message_admins("[ADMIN_LOOKUPFLW(usr)] has deleted all records for [active1.fields["name"]] as Borg/AI Player.")
for(var/datum/data/record/R in GLOB.data_core.medical)
if((R.fields["name"] == active1.fields["name"] || R.fields["id"] == active1.fields["id"]))
qdel(R)
@@ -897,7 +1037,7 @@ What a mess.*/
if(3)
R.fields["age"] = rand(5, 85)
if(4)
- R.fields["criminal"] = pick("None", "*Arrest*", "Incarcerated", "Paroled", "Discharged")
+ R.fields["criminal"] = pick("None", "Arrest", "Search", "Monitor", "Incarcerated", "Paroled", "Discharged")
if(5)
R.fields["p_stat"] = pick("*Unconscious*", "Active", "Physically Unfit")
if(6)
diff --git a/code/game/machinery/computer/station_alert.dm b/code/game/machinery/computer/station_alert.dm
index 7456e1f950776..ad05427011a6f 100644
--- a/code/game/machinery/computer/station_alert.dm
+++ b/code/game/machinery/computer/station_alert.dm
@@ -5,9 +5,6 @@
icon_keyboard = "atmos_key"
circuit = /obj/item/circuitboard/computer/stationalert
-
- var/alarms = list("Fire" = list(), "Atmosphere" = list(), "Power" = list())
-
light_color = LIGHT_COLOR_CYAN
/obj/machinery/computer/station_alert/Initialize()
@@ -32,43 +29,61 @@
var/list/data = list()
data["alarms"] = list()
- for(var/class in alarms)
+ for(var/class in GLOB.alarms)
data["alarms"][class] = list()
- for(var/area in alarms[class])
+ for(var/area in GLOB.alarms[class])
data["alarms"][class] += area
return data
-/obj/machinery/computer/station_alert/proc/triggerAlarm(class, area/A, O, obj/source)
- if(source.z != z)
+/obj/machinery/computer/station_alert/proc/triggerAlarm(class, area/home, cameras, obj/source)
+ if(source.get_virtual_z_level() != get_virtual_z_level())
return
if(stat & (BROKEN))
return
- var/list/L = alarms[class]
- for(var/I in L)
- if (I == A.name)
- var/list/alarm = L[I]
+ var/list/our_sort = GLOB.alarms[class]
+ for(var/areaname in our_sort)
+ if (areaname == home.name)
+ var/list/alarm = our_sort[areaname]
var/list/sources = alarm[3]
if (!(source in sources))
sources += source
- return 1
- var/obj/machinery/camera/C = null
- var/list/CL = null
- if(O && islist(O))
- CL = O
- if (CL.len == 1)
- C = CL[1]
- else if(O && istype(O, /obj/machinery/camera))
- C = O
- L[A.name] = list(A, (C ? C : O), list(source))
- return 1
-
+ ui_update()
+ return TRUE
+
+ var/obj/machinery/camera/cam = null
+ var/list/our_cams = null
+ if(cameras && islist(cameras))
+ our_cams = cameras
+ if (our_cams.len == 1)
+ cam = our_cams[1]
+ else if(cameras && istype(cameras, /obj/machinery/camera))
+ cam = cameras
+ our_sort[home.name] = list(home, (cam ? cam : cameras), list(source))
+ ui_update()
+ return TRUE
+
+/obj/machinery/computer/station_alert/proc/freeCamera(area/home, obj/machinery/camera/cam)
+ for(var/class in GLOB.alarms)
+ var/our_area = GLOB.alarms[class][home.name]
+ if(!our_area)
+ continue
+ var/cams = our_area[2] //Get the cameras
+ if(!cams)
+ continue
+ if(islist(cams))
+ cams -= cam
+ if(length(cams) == 1)
+ our_area[2] = cams[1]
+ else
+ our_area[2] = null
+ ui_update()
/obj/machinery/computer/station_alert/proc/cancelAlarm(class, area/A, obj/origin)
if(stat & (BROKEN))
return
- var/list/L = alarms[class]
+ var/list/L = GLOB.alarms[class]
var/cleared = 0
for (var/I in L)
if (I == A.name)
@@ -79,6 +94,7 @@
if (srcs.len == 0)
cleared = 1
L -= I
+ ui_update()
return !cleared
/obj/machinery/computer/station_alert/update_icon()
@@ -86,8 +102,8 @@
if(stat & (NOPOWER|BROKEN))
return
var/active_alarms = FALSE
- for(var/cat in alarms)
- var/list/L = alarms[cat]
+ for(var/cat in GLOB.alarms)
+ var/list/L = GLOB.alarms[cat]
if(L.len)
active_alarms = TRUE
if(active_alarms)
diff --git a/code/game/machinery/computer/teleporter.dm b/code/game/machinery/computer/teleporter.dm
index 3a1904ca3a433..08fb512cd733e 100644
--- a/code/game/machinery/computer/teleporter.dm
+++ b/code/game/machinery/computer/teleporter.dm
@@ -11,7 +11,10 @@
var/id
var/obj/machinery/teleport/station/power_station
var/calibrating
- var/turf/target
+ ///Weakref to the target atom we're pointed at currently
+ var/datum/weakref/target_ref
+
+ var/target_area_name
/obj/machinery/computer/teleporter/Initialize()
. = ..()
@@ -31,9 +34,20 @@
power_station = locate(/obj/machinery/teleport/station, get_step(src, direction))
if(power_station)
break
+ ui_update()
return power_station
+/obj/machinery/computer/teleporter/ui_requires_update(mob/user, datum/tgui/ui)
+ // Using ui_update here so the changes apply to all viewers, since ui_data updates those vars
+ if(target_ref)
+ var/atom/target = target_ref.resolve()
+ if(!target)
+ ui_update() // Update once if target is gone. There is probably a better way to do this.
+ else if(target_area_name != "[get_area(target)]")
+ ui_update() // Update if the area name changed. This should be fine, because autoupdate stringifies area every process anyways.
+ . = ..() // Call parent proc last so ui_update takes effect immediately
+
/obj/machinery/computer/teleporter/ui_state(mob/user)
return GLOB.default_state
@@ -44,14 +58,21 @@
ui.open()
/obj/machinery/computer/teleporter/ui_data(mob/user)
+ var/atom/target
+ if(target_ref)
+ target = target_ref.resolve()
+ if(!target)
+ target_ref = null
var/list/data = list()
data["power_station"] = power_station ? TRUE : FALSE
data["teleporter_hub"] = power_station?.teleporter_hub ? TRUE : FALSE
data["regime_set"] = regime_set
- data["target"] = !target ? "None" : "[get_area(target)] [(regime_set != "Gate") ? "" : "Teleporter"]"
+ if(target)
+ target_area_name = "[get_area(target)]"
+ data["target"] = !target ? "None" : "[target_area_name] [(regime_set != "Gate") ? "" : "Teleporter"]"
data["calibrating"] = calibrating
- if(power_station?.teleporter_hub?.calibrated || power_station?.teleporter_hub?.accuracy >= 3)
+ if(power_station?.teleporter_hub?.calibrated || power_station?.teleporter_hub?.accuracy >= 4)
data["calibrated"] = TRUE
else
data["calibrated"] = FALSE
@@ -83,26 +104,29 @@
set_target(usr)
. = TRUE
if("calibrate")
- if(!target)
+ if(!target_ref)
say("Error: No target set to calibrate to.")
return
- if(power_station.teleporter_hub.calibrated || power_station.teleporter_hub.accuracy >= 3)
+ if(power_station.teleporter_hub.calibrated || power_station.teleporter_hub.accuracy >= 4)
say("Hub is already calibrated!")
return
say("Processing hub calibration to target...")
calibrating = TRUE
power_station.update_icon()
- spawn(50 * (3 - power_station.teleporter_hub.accuracy)) //Better parts mean faster calibration
- calibrating = FALSE
- if(check_hub_connection())
- power_station.teleporter_hub.calibrated = TRUE
- say("Calibration complete.")
- else
- say("Error: Unable to detect hub.")
- power_station.update_icon()
+ var/calibrationtime = 50 * (3 - power_station.teleporter_hub.accuracy)
+ addtimer(CALLBACK(src, .proc/calibrate), calibrationtime)
. = TRUE
+/obj/machinery/computer/teleporter/proc/calibrate()
+ calibrating = FALSE
+ if(check_hub_connection())
+ power_station.teleporter_hub.calibrated = TRUE
+ say("Calibration complete.")
+ else
+ say("Error: Unable to detect hub.")
+ power_station.update_icon()
+
/obj/machinery/computer/teleporter/proc/check_hub_connection()
if(!power_station)
return FALSE
@@ -111,7 +135,7 @@
return TRUE
/obj/machinery/computer/teleporter/proc/reset_regime()
- target = null
+ target_ref = null
if(regime_set == "Teleporter")
regime_set = "Gate"
else
@@ -141,9 +165,9 @@
L[avoid_assoc_duplicate_keys("[M.real_name] ([get_area(M)])", areaindex)] = I
var/desc = input("Please select a location to lock in.", "Locking Computer") as null|anything in sortList(L)
- target = L[desc]
- var/turf/T = get_turf(target)
- log_game("[key_name(user)] has set the teleporter target to [target] at [AREACOORD(T)]")
+ target_ref = WEAKREF(L[desc])
+ var/turf/T = get_turf(L[desc])
+ log_game("[key_name(user)] has set the teleporter target to [L[desc]] at [AREACOORD(T)]")
else
var/list/S = power_station.linked_stations
@@ -160,7 +184,7 @@
return
var/turf/T = get_turf(target_station)
log_game("[key_name(user)] has set the teleporter target to [target_station] at [AREACOORD(T)]")
- target = target_station.teleporter_hub
+ target_ref = WEAKREF(target_station.teleporter_hub)
target_station.linked_stations |= power_station
target_station.stat &= ~NOPOWER
if(target_station.teleporter_hub)
@@ -177,6 +201,6 @@
if(is_centcom_level(T.z) || is_away_level(T.z))
return FALSE
var/area/A = get_area(T)
- if(!A || A.noteleport)
+ if(!A || A.teleport_restriction)
return FALSE
return TRUE
diff --git a/code/game/machinery/computer/warrant.dm b/code/game/machinery/computer/warrant.dm
index 3e6853d0994d8..22fd18ff72b21 100644
--- a/code/game/machinery/computer/warrant.dm
+++ b/code/game/machinery/computer/warrant.dm
@@ -19,11 +19,15 @@
var/background
var/notice = ""
switch(current.fields["criminal"])
- if("*Arrest*")
+ if("Arrest")
background = "background-color:#990000;"
notice = " **REPORT TO THE BRIG**"
if("Incarcerated")
background = "background-color:#CD6500;"
+ if("Search")
+ background = "'background-color:#9B13EB;'"
+ if("Monitor")
+ background = "'background-color:#990000;'"
if("Paroled")
background = "background-color:#CD6500;"
if("Discharged")
@@ -67,7 +71,7 @@
dat += ""
dat += ""
- dat += " Minor Crimes:"
+ dat += " Crimes:"
dat +={"
Crime
@@ -75,23 +79,7 @@
Author
Time Added
"}
- for(var/datum/data/crime/c in current.fields["mi_crim"])
- dat += {"
[c.crimeName]
-
[c.crimeDetails]
-
[c.author]
-
[c.time]
-
"}
- dat += "
"
-
- dat += " Major Crimes:"
- dat +={"
-
-
Crime
-
Details
-
Author
-
Time Added
-
"}
- for(var/datum/data/crime/c in current.fields["ma_crim"])
+ for(var/datum/data/crime/c in current.fields["crim"])
dat += {"
[c.crimeName]
[c.crimeDetails]
[c.author]
@@ -113,8 +101,11 @@
var/mob/M = usr
switch(href_list["choice"])
if("Login")
+ if(iscyborg(M)) //cyborgs cannot be set to arrest
+ return
var/obj/item/card/id/scan = M.get_idcard(TRUE)
- authenticated = scan.registered_name
+ if(scan)
+ authenticated = scan.registered_name
if(authenticated)
for(var/datum/data/record/R in GLOB.data_core.security)
if(R.fields["name"] == authenticated)
diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm
index 5de5c8df558e3..3fafde5cc6275 100644
--- a/code/game/machinery/cryopod.dm
+++ b/code/game/machinery/cryopod.dm
@@ -16,6 +16,7 @@ GLOBAL_LIST_EMPTY(cryopod_computers)
icon_state = "cellconsole_1"
// circuit = /obj/item/circuitboard/cryopodcontrol
density = FALSE
+ layer = ABOVE_WINDOW_LAYER
interaction_flags_machine = INTERACT_MACHINE_OFFLINE
req_one_access = list(ACCESS_HEADS, ACCESS_ARMORY) //Heads of staff or the warden can go here to claim recover items from their department that people went were cryodormed with.
var/mode = null
@@ -150,7 +151,7 @@ GLOBAL_LIST_EMPTY(cryopod_computers)
var/time_till_despawn = 5 * 600 // This is reduced to 30 seconds if a player manually enters cryo
var/despawn_world_time = null // Used to keep track of the safe period.
- var/obj/machinery/computer/cryopod/control_computer
+ var/datum/weakref/control_computer_weakref
var/last_no_computer_message = 0
// These items are preserved when the process() despawn proc occurs.
@@ -189,23 +190,28 @@ GLOBAL_LIST_EMPTY(cryopod_computers)
update_icon()
find_control_computer()
-/obj/machinery/cryopod/proc/find_control_computer(urgent = 0)
- for(var/M in GLOB.cryopod_computers)
- var/obj/machinery/computer/cryopod/C = M
- if(get_area(C) == get_area(src))
- control_computer = C
+// This is not a good situation
+/obj/machinery/cryopod/Destroy()
+ control_computer_weakref = null
+ return ..()
+
+/obj/machinery/cryopod/proc/find_control_computer(urgent = FALSE)
+ for(var/cryo_console as anything in GLOB.cryopod_computers)
+ var/obj/machinery/computer/cryopod/console = cryo_console
+ if(get_area(console) == get_area(src))
+ control_computer_weakref = WEAKREF(console)
break
// Don't send messages unless we *need* the computer, and less than five minutes have passed since last time we messaged
- if(!control_computer && urgent && last_no_computer_message + 5*60*10 < world.time)
+ if(!control_computer_weakref && urgent && last_no_computer_message + 5*60*10 < world.time)
log_admin("Cryopod in [get_area(src)] could not find control computer!")
message_admins("Cryopod in [get_area(src)] could not find control computer!")
last_no_computer_message = world.time
- return control_computer != null
+ return control_computer_weakref != null
/obj/machinery/cryopod/close_machine(mob/user)
- if(!control_computer)
+ if(!control_computer_weakref)
find_control_computer(TRUE)
if((isnull(user) || istype(user)) && state_open && !panel_open)
..(user)
@@ -246,7 +252,7 @@ GLOBAL_LIST_EMPTY(cryopod_computers)
return
if(!mob_occupant.client && mob_occupant.stat < 2) //Occupant is living and has no client.
- if(!control_computer)
+ if(!control_computer_weakref)
find_control_computer(urgent = TRUE)//better hope you found it this time
despawn_occupant()
@@ -254,45 +260,42 @@ GLOBAL_LIST_EMPTY(cryopod_computers)
/obj/machinery/cryopod/proc/handle_objectives()
var/mob/living/mob_occupant = occupant
//Update any existing objectives involving this mob.
- for(var/datum/objective/O in GLOB.objectives)
+ for(var/datum/objective/O as() in GLOB.objectives)
+ if(O.target != mob_occupant.mind)
+ continue
// We don't want revs to get objectives that aren't for heads of staff. Letting
// them win or lose based on cryo is silly so we remove the objective.
- if(istype(O,/datum/objective/mutiny) && O.target == mob_occupant.mind)
+ if(istype(O,/datum/objective/mutiny))
O.team.objectives -= O
- qdel(O)
- for(var/datum/mind/M in O.team.members)
+ for(var/datum/mind/M as() in O.team.members)
to_chat(M.current, " Your target is no longer within reach. Objective removed!")
M.announce_objectives()
- else if(O.target && istype(O.target, /datum/mind))
- if(O.target == mob_occupant.mind)
- var/old_target = O.target
- O.target = null
- if(!O)
- return
- O.find_target()
- if(!O.target && O.owner)
- to_chat(O.owner.current, " Your target is no longer within reach. Objective removed!")
- for(var/datum/antagonist/A in O.owner.antag_datums)
+ qdel(O)
+ else if(istype(O, /datum/objective/contract))
+ var/datum/antagonist/traitor/affected_traitor = O.owner.has_antag_datum(/datum/antagonist/traitor)
+ for(var/datum/syndicate_contract/affected_contract as anything in affected_traitor.contractor_hub.assigned_contracts)
+ if(affected_contract.contract == O)
+ affected_contract.generate(affected_traitor.contractor_hub.assigned_targets)
+ affected_traitor.contractor_hub.assigned_targets.Add(affected_contract.contract.target)
+ to_chat(O.owner.current, " Contract target out of reach. Contract rerolled.")
+ break
+ else
+ O.target = null
+ O.find_target()
+ if(!O.target || O.target == mob_occupant.mind)
+ for(var/datum/mind/own as() in O.get_owners())
+ to_chat(own.current, " Your target is no longer within reach. Objective removed!")
+ for(var/datum/antagonist/A as() in own.antag_datums)
A.objectives -= O
- if (!O.team)
- O.update_explanation_text()
- O.owner.announce_objectives()
- to_chat(O.owner.current, " You get the feeling your target is no longer within reach. Time for Plan [pick("A","B","C","D","X","Y","Z")]. Objectives updated!")
- else
- var/list/objectivestoupdate
- for(var/datum/mind/own in O.get_owners())
- to_chat(own.current, " You get the feeling your target is no longer within reach. Time for Plan [pick("A","B","C","D","X","Y","Z")]. Objectives updated!")
- for(var/datum/objective/ob in own.get_all_objectives())
- LAZYADD(objectivestoupdate, ob)
- objectivestoupdate += O.team.objectives
- for(var/datum/objective/ob in objectivestoupdate)
- if(ob.target != old_target || !istype(ob,O.type))
- return
- ob.target = O.target
- ob.update_explanation_text()
- to_chat(O.owner.current, " You get the feeling your target is no longer within reach. Time for Plan [pick("A","B","C","D","X","Y","Z")]. Objectives updated!")
- ob.owner.announce_objectives()
+ own.announce_objectives()
+ if(O.team)
+ O.team.objectives -= O
qdel(O)
+ else
+ O.update_explanation_text()
+ for(var/datum/mind/own as() in O.get_owners())
+ to_chat(own.current, " You get the feeling your target is no longer within reach. Time for Plan [pick("A","B","C","D","X","Y","Z")]. Objectives updated!")
+ own.announce_objectives()
// This function can not be undone; do not call this unless you are sure
/obj/machinery/cryopod/proc/despawn_occupant()
@@ -309,24 +312,27 @@ GLOBAL_LIST_EMPTY(cryopod_computers)
// Delete them from datacore.
var/announce_rank = null
- for(var/datum/data/record/R in GLOB.data_core.medical)
+ for(var/datum/data/record/R as() in GLOB.data_core.medical)
if((R.fields["name"] == mob_occupant.real_name))
qdel(R)
- for(var/datum/data/record/T in GLOB.data_core.security)
+ for(var/datum/data/record/T as() in GLOB.data_core.security)
if((T.fields["name"] == mob_occupant.real_name))
qdel(T)
- for(var/datum/data/record/G in GLOB.data_core.general)
+ for(var/datum/data/record/G as() in GLOB.data_core.general)
if((G.fields["name"] == mob_occupant.real_name))
announce_rank = G.fields["rank"]
qdel(G)
- for(var/obj/machinery/computer/cloning/cloner in world)
- for(var/datum/data/record/R in cloner.records)
+ for(var/obj/machinery/computer/cloning/cloner in GLOB.machines)
+ for(var/datum/data/record/R as() in cloner.records)
if(R.fields["name"] == mob_occupant.real_name)
cloner.records.Remove(R)
//Make an announcement and log the person entering storage.
- if(control_computer)
+ var/obj/machinery/computer/cryopod/control_computer = control_computer_weakref?.resolve()
+ if(!control_computer)
+ control_computer_weakref = null
+ else
control_computer.frozen_crew += "[mob_occupant.real_name]"
if(GLOB.announcement_systems.len)
@@ -356,14 +362,14 @@ GLOBAL_LIST_EMPTY(cryopod_computers)
if(!istype(R)) return
R.contents -= R.mmi
- qdel(R.mmi)
+ QDEL_NULL(R.mmi)
// Ghost and delete the mob.
- if(!mob_occupant.get_ghost(1))
+ if(!mob_occupant.get_ghost(TRUE))
if(world.time < 15 * 600)//before the 15 minute mark
- mob_occupant.ghostize(0) // Players despawned too early may not re-enter the game
+ mob_occupant.ghostize(FALSE,SENTIENCE_ERASE) // Players despawned too early may not re-enter the game
else
- mob_occupant.ghostize(1)
+ mob_occupant.ghostize(TRUE,SENTIENCE_ERASE)
handle_objectives()
QDEL_NULL(occupant)
open_machine()
diff --git a/code/game/machinery/dance_machine.dm b/code/game/machinery/dance_machine.dm
index e0e9f4260e72f..fef7a987dd458 100644
--- a/code/game/machinery/dance_machine.dm
+++ b/code/game/machinery/dance_machine.dm
@@ -123,7 +123,7 @@
playsound(src, 'sound/misc/compiler-failure.ogg', 50, 1)
return
activate_music()
- START_PROCESSING(SSobj, src)
+ START_PROCESSING(SSmachines, src)
updateUsrDialog()
else if(active)
stop = 0
@@ -145,7 +145,7 @@
/obj/machinery/jukebox/proc/activate_music()
active = TRUE
update_icon()
- START_PROCESSING(SSobj, src)
+ START_PROCESSING(SSmachines, src)
stop = world.time + selection.song_length
/obj/machinery/jukebox/disco/activate_music()
@@ -436,34 +436,33 @@
if(world.time < stop && active)
var/sound/song_played = sound(selection.song_path)
- for(var/mob/M in range(10,src))
- if(!M.client || !(M.client.prefs.toggles & SOUND_INSTRUMENTS))
- continue
- if(!(M in rangers))
- rangers[M] = TRUE
- M.playsound_local(get_turf(M), null, 100, channel = CHANNEL_JUKEBOX, S = song_played)
- for(var/mob/L in rangers)
+ for(var/mob/L as() in rangers)
if(get_dist(src,L) > 10)
rangers -= L
if(!L || !L.client)
continue
L.stop_sound_channel(CHANNEL_JUKEBOX)
+ for(var/mob/M as() in hearers(10,src))
+ if(!M.client || !(M.client.prefs.toggles & SOUND_INSTRUMENTS))
+ continue
+ if(!(M in rangers))
+ rangers += M
+ M.playsound_local(get_turf(M), null, 100, channel = CHANNEL_JUKEBOX, S = song_played, use_reverb = FALSE)
else if(active)
active = FALSE
- STOP_PROCESSING(SSobj, src)
dance_over()
playsound(src,'sound/machines/terminal_off.ogg',50,1)
update_icon()
stop = world.time + 100
+ return PROCESS_KILL
-
-/obj/machinery/jukebox/disco/process()
+/obj/machinery/jukebox/disco/process(delta_time)
. = ..()
if(active)
- for(var/mob/M in rangers)
- if(prob(5+(allowed(M)*4)))
+ for(var/mob/M as() in rangers)
+ if(DT_PROB(5+(allowed(M)*4), delta_time))
if(isliving(M))
var/mob/living/L = M
if(!(L.mobility_flags & MOBILITY_MOVE))
continue
- dance(M)
+ dance(L)
diff --git a/code/game/machinery/defibrillator_mount.dm b/code/game/machinery/defibrillator_mount.dm
index febf3cb09a6d1..5a95329252e31 100644
--- a/code/game/machinery/defibrillator_mount.dm
+++ b/code/game/machinery/defibrillator_mount.dm
@@ -11,6 +11,8 @@
idle_power_usage = 0
power_channel = AREA_USAGE_EQUIP
req_one_access = list(ACCESS_MEDICAL, ACCESS_HEADS, ACCESS_SECURITY) //used to control clamps
+ processing_flags = NONE
+ layer = ABOVE_WINDOW_LAYER
var/obj/item/defibrillator/defib //this mount's defibrillator
var/clamps_locked = FALSE //if true, and a defib is loaded, it can't be removed without unlocking the clamps
@@ -21,12 +23,13 @@
/obj/machinery/defibrillator_mount/Destroy()
if(defib)
QDEL_NULL(defib)
+ end_processing()
. = ..()
/obj/machinery/defibrillator_mount/examine(mob/user)
. = ..()
if(defib)
- . += "There is a defib unit hooked up. Alt-click to remove it."
+ . += "There is a defib unit hooked up. Alt-click to remove it."
if(GLOB.security_level >= SEC_LEVEL_RED)
. += "Due to a security situation, its locking clamps can be toggled by swiping any ID."
else
@@ -35,7 +38,7 @@
/obj/machinery/defibrillator_mount/process()
if(defib?.cell && defib.cell.charge < defib.cell.maxcharge && is_operational())
use_power(200)
- defib.cell.give(180) //90% efficiency, slightly better than the cell charger's 87.5%
+ defib.cell.give(180) //90% efficiency, a bit worse than the cell charger's 100%
update_icon()
/obj/machinery/defibrillator_mount/update_icon()
@@ -76,6 +79,7 @@
"You press [I] into the mount, and it clicks into place.")
playsound(src, 'sound/machines/click.ogg', 50, TRUE)
defib = I
+ begin_processing()
update_icon()
return
else if(defib && I == defib.paddles)
@@ -129,6 +133,8 @@
user.visible_message("[user] unhooks [defib] from [src].", \
"You slide out [defib] from [src] and unhook the charging cables.")
playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE)
+ // Make sure processing ends before the defib is nulled
+ end_processing()
defib = null
update_icon()
diff --git a/code/game/machinery/deployable.dm b/code/game/machinery/deployable.dm
index 5067405dad171..99fa9253a644c 100644
--- a/code/game/machinery/deployable.dm
+++ b/code/game/machinery/deployable.dm
@@ -120,7 +120,7 @@
anchored = FALSE
max_integrity = 180
proj_pass_rate = 20
- armor = list("melee" = 10, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 10, "acid" = 0)
+ armor = list("melee" = 10, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 10, "acid" = 0, "stamina" = 0)
var/deploy_time = 40
var/deploy_message = TRUE
@@ -167,7 +167,8 @@
to_chat(user, "[src] is now in [mode] mode.")
-/obj/item/grenade/barrier/prime()
+/obj/item/grenade/barrier/prime(mob/living/lanced_by)
+ . = ..()
new /obj/structure/barricade/security(get_turf(src.loc))
switch(mode)
if(VERTICAL)
diff --git a/code/game/machinery/dna_scanner.dm b/code/game/machinery/dna_scanner.dm
index 438130d052682..71071d390693f 100644
--- a/code/game/machinery/dna_scanner.dm
+++ b/code/game/machinery/dna_scanner.dm
@@ -30,7 +30,9 @@
/obj/machinery/dna_scannernew/examine(mob/user)
. = ..()
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Radiation pulse accuracy increased by factor [precision_coeff**2]. Radiation pulse damage decreased by factor [damage_coeff**2]."
+ . += "The status display reads: Radiation pulse accuracy increased by factor [precision_coeff**2]. Radiation pulse damage decreased by factor [damage_coeff**2]."
+ if(scan_level >= 3)
+ . += "Scanner has been upgraded to support autoprocessing."
/obj/machinery/dna_scannernew/update_icon()
@@ -100,12 +102,6 @@
..(user)
- // DNA manipulators cannot operate on severed heads or brains
- if(iscarbon(occupant))
- var/obj/machinery/computer/scan_consolenew/console = locate_computer(/obj/machinery/computer/scan_consolenew)
- if(console)
- console.on_scanner_close()
-
return TRUE
/obj/machinery/dna_scannernew/open_machine()
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index fc03bdb374f81..ff9de24d8254e 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -41,6 +41,7 @@
name = "airlock"
icon = 'icons/obj/doors/airlocks/station/public.dmi'
icon_state = "closed"
+ appearance_flags = TILE_BOUND | LONG_GLIDE | PIXEL_SCALE | KEEP_TOGETHER
max_integrity = 300
var/normal_integrity = AIRLOCK_INTEGRITY_N
integrity_failure = 70
@@ -51,6 +52,7 @@
normalspeed = 1
explosion_block = 1
hud_possible = list(DIAG_AIRLOCK_HUD)
+ var/allow_repaint = TRUE //Set to FALSE if the airlock should not be allowed to be repainted.
FASTDMM_PROP(\
pinned_vars = list("req_access_txt", "req_one_access_txt", "name")\
@@ -71,7 +73,7 @@
var/obj/machinery/door/airlock/closeOther
var/justzap = FALSE
var/obj/item/electronics/airlock/electronics
- var/shockCooldown = FALSE //Prevents multiple shocks from happening
+ COOLDOWN_DECLARE(shockCooldown) //Prevents multiple shocks from happening
var/obj/item/doorCharge/charge //If applied, causes an explosion upon opening the door
var/obj/item/note //Any papers pinned to the airlock
var/detonated = FALSE
@@ -86,6 +88,15 @@
var/airlock_material //material of inner filling; if its an airlock with glass, this should be set to "glass"
var/overlays_file = 'icons/obj/doors/airlocks/station/overlays.dmi'
var/note_overlay_file = 'icons/obj/doors/airlocks/station/overlays.dmi' //Used for papers and photos pinned to the airlock
+ /* Note mask_file needed some change due to the change from 513 to 514(the behavior of alpha filters seems to have changed) thats the reason why the mask
+ dmi file for normal airlocks is not 32x32 but 64x64 and for the large airlocks instead of 64x32 its now 96x64 due to the fix to this problem*/
+ var/mask_file = 'icons/obj/doors/airlocks/mask_32x32_airlocks.dmi' // because filters aren't allowed to have icon_states :(
+ var/mask_x = 0
+ var/mask_y = 0
+ var/anim_parts = "left=-14,0;right=13,0" //format is "airlock_part=open_px,open_py,move_start_time,move_end_time,aperture_angle"
+ var/list/part_overlays
+ var/panel_attachment = "right"
+ var/note_attachment = "left"
var/cyclelinkeddir = 0
var/obj/machinery/door/airlock/cyclelinkedairlock
@@ -94,10 +105,11 @@
var/prying_so_hard = FALSE
var/protected_door = FALSE // Protects the door against any form of power outage, AI control, screwdrivers and welders.
-
rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
rad_insulation = RAD_MEDIUM_INSULATION
+ var/electrification_timing // Set to true while electrified_loop is running, to prevent multiple being started
+
var/static/list/airlock_overlays = list()
/obj/machinery/door/airlock/Initialize()
@@ -123,6 +135,8 @@
diag_hud.add_to_hud(src)
diag_hud_set_electrified()
+ rebuild_parts()
+
return INITIALIZE_HINT_LATELOAD
/obj/machinery/door/airlock/LateInitialize()
@@ -134,7 +148,7 @@
switch(outcome)
if(1 to 5)
var/turf/here = get_turf(src)
- for(var/turf/closed/T in range(2, src))
+ for(var/turf/closed/T in spiral_range_turfs(2, here))
here.PlaceOnTop(T.type)
qdel(src)
return
@@ -156,6 +170,35 @@
. = ..()
AddComponent(/datum/component/ntnet_interface)
+/obj/machinery/door/airlock/proc/rebuild_parts()
+ if(part_overlays)
+ vis_contents -= part_overlays
+ QDEL_LIST(part_overlays)
+ else
+ part_overlays = list()
+ var/list/parts_desc = params2list(anim_parts)
+ var/list/door_time = list()
+ for(var/part_id in parts_desc)
+ var/obj/effect/overlay/airlock_part/P = new
+ P.side_id = part_id
+ var/list/open_offset = splittext(parts_desc[part_id], ",")
+ P.open_px = text2num(open_offset[1])
+ P.open_py = text2num(open_offset[2])
+ if(open_offset.len >= 3)
+ P.move_start_time = text2num(open_offset[3])
+ if(open_offset.len >= 4)
+ P.move_end_time = text2num(open_offset[4])
+ if(open_offset.len >= 5)
+ P.aperture_angle = text2num(open_offset[5])
+ vis_contents += P
+ part_overlays += P
+ P.icon = icon
+ P.icon_state = part_id
+ P.name = name
+ door_time += P.move_end_time
+ open_speed = max(door_time) //open_speed is max animation time
+ add_filter("mask_filter", 1, list(type="alpha",icon=mask_file,x=mask_x,y=mask_y))
+
/obj/machinery/door/airlock/proc/update_other_id()
for(var/obj/machinery/door/airlock/A in GLOB.airlocks)
if(A.closeOtherId == closeOtherId && A != src)
@@ -198,6 +241,10 @@
if(!hasPower() || !canAIControl())
return
+ //Check radio signal jamming
+ if(is_jammed())
+ return
+
// Check packet access level.
if(!check_access_ntnet(data))
return
@@ -246,18 +293,26 @@
/obj/machinery/door/airlock/proc/bolt()
if(locked || protected_door)
return
- locked = TRUE
- playsound(src,boltDown,30,0,3)
+ set_bolt(TRUE)
+ playsound(src, boltDown, 30, 0, 3)
audible_message("You hear a click from the bottom of the door.", null, 1)
update_icon()
+/obj/machinery/door/airlock/proc/set_bolt(should_bolt)
+ if(locked == should_bolt)
+ return
+ SEND_SIGNAL(src, COMSIG_AIRLOCK_SET_BOLT, should_bolt)
+ ui_update()
+ . = locked
+ locked = should_bolt
+
/obj/machinery/door/airlock/unlock()
unbolt()
/obj/machinery/door/airlock/proc/unbolt()
if(!locked)
return
- locked = FALSE
+ set_bolt(FALSE)
playsound(src,boltUp,30,0,3)
audible_message("You hear a click from the bottom of the door.", null, 1)
update_icon()
@@ -364,6 +419,8 @@
if(G.siemens_coefficient)//not insulated
new /datum/hallucination/shock(H)
return
+ if(SEND_SIGNAL(src, COMSIG_AIRLOCK_TOUCHED, user) & COMPONENT_PREVENT_OPEN)
+ return
if (cyclelinkedairlock)
if (!shuttledocked && !emergency && !cyclelinkedairlock.shuttledocked && !cyclelinkedairlock.emergency && allowed(user))
if(cyclelinkedairlock.operating)
@@ -383,11 +440,15 @@
/obj/machinery/door/airlock/proc/canAIControl(mob/user)
if(protected_door)
return FALSE
+ if(is_jammed())
+ return FALSE
return ((aiControlDisabled != 1) && !isAllPowerCut())
/obj/machinery/door/airlock/proc/canAIHack()
if(protected_door)
return FALSE
+ if(is_jammed())
+ return FALSE
return ((aiControlDisabled==1) && (!hackProof) && (!isAllPowerCut()));
/obj/machinery/door/airlock/hasPower()
@@ -419,15 +480,13 @@
if(secondsMainPowerLost>0)
if(!wires.is_cut(WIRE_POWER1) && !wires.is_cut(WIRE_POWER2))
secondsMainPowerLost -= 1
- updateDialog()
cont = TRUE
if(secondsBackupPowerLost>0)
if(!wires.is_cut(WIRE_BACKUP1) && !wires.is_cut(WIRE_BACKUP2))
secondsBackupPowerLost -= 1
- updateDialog()
cont = TRUE
spawnPowerRestoreRunning = FALSE
- updateDialog()
+ ui_update()
update_icon()
/obj/machinery/door/airlock/proc/loseMainPower()
@@ -459,19 +518,20 @@
/obj/machinery/door/airlock/proc/shock(mob/user, prb)
if(!hasPower()) // unpowered, no shock
return FALSE
- if(shockCooldown > world.time)
+ if(!COOLDOWN_FINISHED(src, shockCooldown))
return FALSE //Already shocked someone recently?
if(!prob(prb))
return FALSE //you lucked out, no shock for you
do_sparks(5, TRUE, src)
var/check_range = TRUE
if(electrocute_mob(user, get_area(src), src, 1, check_range))
- shockCooldown = world.time + 10
+ COOLDOWN_START(src, shockCooldown, 1 SECONDS)
return TRUE
else
return FALSE
/obj/machinery/door/airlock/update_icon(state=0, override=0)
+ cut_overlays() // Needed without it you get like 300 unres indicator overlayers over time
if(operating && !override)
return
switch(state)
@@ -487,144 +547,116 @@
icon_state = "nonexistenticonstate" //MADNESS
set_airlock_overlays(state)
+/obj/machinery/door/airlock/proc/set_side_overlays(obj/effect/overlay/airlock_part/base, show_lights = FALSE)
+ var/side = base.side_id
+ base.icon = icon
+ base.cut_overlays()
+ if(airlock_material)
+ base.add_overlay(get_airlock_overlay("[airlock_material]_[side]", overlays_file))
+ else
+ base.add_overlay(get_airlock_overlay("fill_[side]", icon))
+ if(panel_open && panel_attachment == side)
+ if(security_level)
+ base.add_overlay(get_airlock_overlay("panel_closed_protected", overlays_file))
+ else
+ base.add_overlay(get_airlock_overlay("panel_closed", overlays_file))
+ if(show_lights && lights && hasPower())
+ base.add_overlay(get_airlock_overlay("lights_[side]", overlays_file))
+
+ if(note && note_attachment == side)
+ var/notetype = note_type()
+ base.add_overlay(get_airlock_overlay(notetype, note_overlay_file))
+
/obj/machinery/door/airlock/proc/set_airlock_overlays(state)
- var/mutable_appearance/frame_overlay
- var/mutable_appearance/filling_overlay
- var/mutable_appearance/lights_overlay
- var/mutable_appearance/panel_overlay
- var/mutable_appearance/weld_overlay
- var/mutable_appearance/damag_overlay
- var/mutable_appearance/sparks_overlay
- var/mutable_appearance/note_overlay
- var/notetype = note_type()
+ for(var/obj/effect/overlay/airlock_part/part as() in part_overlays)
+ set_side_overlays(part, state == AIRLOCK_CLOSING || state == AIRLOCK_OPENING)
+ if(part.aperture_angle)
+ var/matrix/T
+ if(state == AIRLOCK_OPEN || state == AIRLOCK_OPENING || state == AIRLOCK_CLOSING)
+ T = matrix()
+ T.Translate(-part.open_px,-part.open_py)
+ T.Turn(part.aperture_angle)
+ T.Translate(part.open_px,part.open_py)
+ switch(state)
+ if(AIRLOCK_CLOSED, AIRLOCK_DENY, AIRLOCK_EMAG)
+ part.transform = matrix()
+ if(AIRLOCK_OPEN)
+ part.transform = T
+ if(AIRLOCK_CLOSING)
+ part.transform = T
+ animate(part, transform = T, time = open_speed - part.move_end_time, flags = ANIMATION_LINEAR_TRANSFORM)
+ animate(transform = matrix(), time = part.move_end_time - part.move_start_time, flags = ANIMATION_LINEAR_TRANSFORM)
+ if(AIRLOCK_OPENING)
+ part.transform = matrix()
+ animate(part, transform = matrix(), time = part.move_start_time, flags = ANIMATION_LINEAR_TRANSFORM)
+ animate(transform = T, time = part.move_end_time - part.move_start_time, flags = ANIMATION_LINEAR_TRANSFORM)
+ else
+ switch(state)
+ if(AIRLOCK_CLOSED, AIRLOCK_DENY, AIRLOCK_EMAG)
+ part.pixel_x = 0
+ part.pixel_y = 0
+ if(AIRLOCK_OPEN)
+ part.pixel_x = part.open_px
+ part.pixel_y = part.open_py
+ if(AIRLOCK_CLOSING)
+ part.pixel_x = part.open_px
+ part.pixel_y = part.open_py
+ animate(part, pixel_x = part.open_px, pixel_y = part.open_py, time = open_speed - part.move_end_time)
+ animate(pixel_x = 0, pixel_y = 0, time = part.move_end_time - part.move_start_time)
+ if(AIRLOCK_OPENING)
+ part.pixel_x = 0
+ part.pixel_y = 0
+ animate(part, pixel_x = 0, pixel_y = 0, time = part.move_start_time)
+ animate(pixel_x = part.open_px, pixel_y = part.open_py, time = part.move_end_time - part.move_start_time)
+
+ SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays)
+
+ SSvis_overlays.add_vis_overlay(src, overlays_file, "frame", FLOAT_LAYER, FLOAT_PLANE, dir)
switch(state)
if(AIRLOCK_CLOSED)
- frame_overlay = get_airlock_overlay("closed", icon)
- if(airlock_material)
- filling_overlay = get_airlock_overlay("[airlock_material]_closed", overlays_file)
- else
- filling_overlay = get_airlock_overlay("fill_closed", icon)
- if(panel_open)
- if(security_level)
- panel_overlay = get_airlock_overlay("panel_closed_protected", overlays_file)
- else
- panel_overlay = get_airlock_overlay("panel_closed", overlays_file)
- if(welded)
- weld_overlay = get_airlock_overlay("welded", overlays_file)
- if(obj_integrity Shift-click [src] to [ density ? "open" : "close"] it."
. += "Ctrl-click [src] to [ locked ? "raise" : "drop"] its bolts."
. += "Alt-click [src] to [ secondsElectrified ? "un-electrify" : "permanently electrify"] it."
@@ -729,6 +765,9 @@
if(detonated)
to_chat(user, "Unable to interface. Airlock control panel damaged.")
return
+ if(is_jammed())
+ to_chat(user, "Unable to interface. Remote communications not responding.")
+ return
ui_interact(user)
@@ -788,7 +827,9 @@
return attack_hand(user)
/obj/machinery/door/airlock/attack_hand(mob/user)
- if(locked && allowed(user) && aac)
+ if(SEND_SIGNAL(src, COMSIG_AIRLOCK_TOUCHED, user) & COMPONENT_PREVENT_OPEN)
+ . = TRUE
+ else if(locked && allowed(user) && aac)
aac.request_from_door(src)
. = TRUE
else
@@ -819,20 +860,27 @@
return ..()
/obj/machinery/door/airlock/proc/electrified_loop()
+ if(electrification_timing)
+ return // Don't start another timer if one is already running
+
+ electrification_timing = TRUE
while (secondsElectrified > MACHINE_NOT_ELECTRIFIED)
+ secondsElectrified--
+
sleep(10)
if(QDELETED(src))
return
+ electrification_timing = FALSE
- secondsElectrified--
- updateDialog()
// This is to protect against changing to permanent, mid loop.
if(secondsElectrified == MACHINE_NOT_ELECTRIFIED)
set_electrified(MACHINE_NOT_ELECTRIFIED)
else
set_electrified(MACHINE_ELECTRIFIED_PERMANENT)
- updateDialog()
+ ui_update()
+//This code might be completely unused, but I'm too afraid to touch it.
+//That said, commenting it out didn't seem to break anything.
/obj/machinery/door/airlock/Topic(href, href_list, var/nowindow = 0)
// If you add an if(..()) check you must first remove the var/nowindow parameter.
// Otherwise it will runtime with this kind of error: null.Topic()
@@ -851,7 +899,6 @@
else
updateDialog()
-
/obj/machinery/door/airlock/attackby(obj/item/C, mob/user, params)
if(!issilicon(user) && !IsAdminGhost(user))
if(isElectrified())
@@ -1089,14 +1136,14 @@
else if(locked)
to_chat(user, "The airlock's bolts prevent it from being forced!")
else if( !welded && !operating)
- if(istype(I, /obj/item/twohanded/fireaxe)) //being fireaxe'd
- var/obj/item/twohanded/fireaxe/F = I
- if(!F.wielded)
+ if(istype(I, /obj/item/fireaxe)) //being fireaxe'd
+ var/obj/item/fireaxe/F = I
+ if(F && !ISWIELDED(F))
to_chat(user, "You need to be wielding the fire axe to do that!")
return
INVOKE_ASYNC(src, (density ? .proc/open : .proc/close), 2)
- if(istype(I, /obj/item/crowbar/power))
+ if(HAS_TRAIT(I, TRAIT_DOOR_PRYER))
if(isElectrified())
shock(user,100)//it's like sticking a forck in a power socket
return
@@ -1160,18 +1207,21 @@
if(!density)
return TRUE
+ ui_update()
+ SEND_SIGNAL(src, COMSIG_AIRLOCK_OPEN, forced)
operating = TRUE
update_icon(AIRLOCK_OPENING, 1)
sleep(1)
set_opacity(0)
update_freelook_sight()
- sleep(4)
+ sleep(open_speed - 1)
density = FALSE
air_update_turf(1)
sleep(1)
layer = OPEN_DOOR_LAYER
update_icon(AIRLOCK_OPEN, 1)
operating = FALSE
+ ui_update()
if(delayed_close_requested)
delayed_close_requested = FALSE
addtimer(CALLBACK(src, .proc/close), 1)
@@ -1203,8 +1253,10 @@
var/obj/structure/window/killthis = (locate(/obj/structure/window) in get_turf(src))
if(killthis)
- killthis.ex_act(EXPLODE_HEAVY)//Smashin windows
+ SSexplosions.med_mov_atom += killthis
+ SEND_SIGNAL(src, COMSIG_AIRLOCK_CLOSE, forced)
+ ui_update()
operating = TRUE
update_icon(AIRLOCK_CLOSING, 1)
layer = CLOSED_DOOR_LAYER
@@ -1215,7 +1267,7 @@
if(!air_tight)
density = TRUE
air_update_turf(1)
- sleep(4)
+ sleep(open_speed - 1)
if(!safe)
crush()
if(visible && !glass)
@@ -1225,8 +1277,10 @@
update_icon(AIRLOCK_CLOSED, 1)
operating = FALSE
delayed_close_requested = FALSE
+ ui_update()
if(safe)
CheckForMobs()
+ ui_update()
return TRUE
/obj/machinery/door/airlock/proc/prison_open()
@@ -1238,83 +1292,38 @@
return
-/obj/machinery/door/airlock/proc/change_paintjob(obj/item/airlock_painter/W, mob/user)
- if(!W.can_use(user))
+/obj/machinery/door/airlock/proc/change_paintjob(obj/item/airlock_painter/painter, mob/user)
+ if(!allow_repaint)
+ to_chat(user, "The airlock painter does not support this airlock.")
return
- var/list/optionlist
- if(airlock_material == "glass")
- optionlist = list("Standard", "Public", "Engineering", "Atmospherics", "Security", "Command", "Medical", "Research", "Science", "Virology", "Mining", "Maintenance", "External", "External Maintenance")
- else
- optionlist = list("Standard", "Public", "Engineering", "Atmospherics", "Security", "Command", "Medical", "Research", "Freezer", "Science", "Virology", "Mining", "Maintenance", "External", "External Maintenance")
+ if((!in_range(src, user) && loc != user) || !painter.can_use(user)) // user should be adjacent to the airlock, and the painter should have a toner cartridge that isn't empty
+ return
+
+ // reads from the airlock painter's `available paintjob` list. lets the player choose a paint option, or cancel painting
+ var/current_paintjob = input(user, "Please select a paintjob for this airlock.") as null|anything in sortList(painter.available_paint_jobs)
+ if(!current_paintjob) // if the user clicked cancel on the popup, return
+ return
- var/paintjob = input(user, "Please select a paintjob for this airlock.") in sortList(optionlist)
- if((!in_range(src, usr) && loc != usr) || !W.use_paint(user))
+ var/airlock_type = painter.available_paint_jobs["[current_paintjob]"] // get the airlock type path associated with the airlock name the user just chose
+ var/obj/machinery/door/airlock/airlock = airlock_type // we need to create a new typed variable of the airlock and assembly to read initial values from them
+ var/obj/structure/door_assembly/assembly = initial(airlock.assemblytype)
+ if(airlock_material == "glass" && initial(assembly.noglass)) // prevents painting glass airlocks with a paint job that doesn't have a glass version, such as the freezer
+ to_chat(user, "This paint job can only be applied to non-glass airlocks.")
return
- switch(paintjob)
- if("Standard")
- icon = 'icons/obj/doors/airlocks/station/public.dmi'
- overlays_file = 'icons/obj/doors/airlocks/station/overlays.dmi'
- assemblytype = /obj/structure/door_assembly
- if("Public")
- icon = 'icons/obj/doors/airlocks/station2/glass.dmi'
- overlays_file = 'icons/obj/doors/airlocks/station2/overlays.dmi'
- assemblytype = /obj/structure/door_assembly/door_assembly_public
- if("Engineering")
- icon = 'icons/obj/doors/airlocks/station/engineering.dmi'
- overlays_file = 'icons/obj/doors/airlocks/station/overlays.dmi'
- assemblytype = /obj/structure/door_assembly/door_assembly_eng
- if("Atmospherics")
- icon = 'icons/obj/doors/airlocks/station/atmos.dmi'
- overlays_file = 'icons/obj/doors/airlocks/station/overlays.dmi'
- assemblytype = /obj/structure/door_assembly/door_assembly_atmo
- if("Security")
- icon = 'icons/obj/doors/airlocks/station/security.dmi'
- overlays_file = 'icons/obj/doors/airlocks/station/overlays.dmi'
- assemblytype = /obj/structure/door_assembly/door_assembly_sec
- if("Command")
- icon = 'icons/obj/doors/airlocks/station/command.dmi'
- overlays_file = 'icons/obj/doors/airlocks/station/overlays.dmi'
- assemblytype = /obj/structure/door_assembly/door_assembly_com
- if("Medical")
- icon = 'icons/obj/doors/airlocks/station/medical.dmi'
- overlays_file = 'icons/obj/doors/airlocks/station/overlays.dmi'
- assemblytype = /obj/structure/door_assembly/door_assembly_med
- if("Research")
- icon = 'icons/obj/doors/airlocks/station/research.dmi'
- overlays_file = 'icons/obj/doors/airlocks/station/overlays.dmi'
- assemblytype = /obj/structure/door_assembly/door_assembly_research
- if("Freezer")
- icon = 'icons/obj/doors/airlocks/station/freezer.dmi'
- overlays_file = 'icons/obj/doors/airlocks/station/overlays.dmi'
- assemblytype = /obj/structure/door_assembly/door_assembly_fre
- if("Science")
- icon = 'icons/obj/doors/airlocks/station/science.dmi'
- overlays_file = 'icons/obj/doors/airlocks/station/overlays.dmi'
- assemblytype = /obj/structure/door_assembly/door_assembly_science
- if("Virology")
- icon = 'icons/obj/doors/airlocks/station/virology.dmi'
- overlays_file = 'icons/obj/doors/airlocks/station/overlays.dmi'
- assemblytype = /obj/structure/door_assembly/door_assembly_viro
- if("Mining")
- icon = 'icons/obj/doors/airlocks/station/mining.dmi'
- overlays_file = 'icons/obj/doors/airlocks/station/overlays.dmi'
- assemblytype = /obj/structure/door_assembly/door_assembly_min
- if("Maintenance")
- icon = 'icons/obj/doors/airlocks/station/maintenance.dmi'
- overlays_file = 'icons/obj/doors/airlocks/station/overlays.dmi'
- assemblytype = /obj/structure/door_assembly/door_assembly_mai
- if("External")
- icon = 'icons/obj/doors/airlocks/external/external.dmi'
- overlays_file = 'icons/obj/doors/airlocks/external/overlays.dmi'
- assemblytype = /obj/structure/door_assembly/door_assembly_ext
- if("External Maintenance")
- icon = 'icons/obj/doors/airlocks/station/maintenanceexternal.dmi'
- overlays_file = 'icons/obj/doors/airlocks/station/overlays.dmi'
- assemblytype = /obj/structure/door_assembly/door_assembly_extmai
+ // applies the user-chosen airlock's icon, overlays, assemblytype anim_parts, panel_attachement and note_attachment to the src airlock
+ painter.use_paint(user)
+ icon = initial(airlock.icon)
+ overlays_file = initial(airlock.overlays_file)
+ note_overlay_file = initial(airlock.note_overlay_file)
+ assemblytype = initial(airlock.assemblytype)
+ anim_parts = initial(airlock.anim_parts)
+ panel_attachment = initial(airlock.panel_attachment)
+ note_attachment = initial(airlock.note_attachment)
+ rebuild_parts()
update_icon()
-/obj/machinery/door/airlock/CanAStarPass(obj/item/card/id/ID)
+/obj/machinery/door/airlock/CanAStarPass(obj/item/card/id/ID, to_dir, atom/movable/caller)
//Airlock is passable if it is open (!density), bot has access, and is not bolted shut or powered off)
return !density || (check_access(ID) && !locked && hasPower())
@@ -1392,6 +1401,7 @@
/obj/machinery/door/airlock/proc/set_electrified(seconds, mob/user)
secondsElectrified = seconds
diag_hud_set_electrified()
+ ui_update()
if(secondsElectrified > MACHINE_NOT_ELECTRIFIED)
INVOKE_ASYNC(src, .proc/electrified_loop)
@@ -1413,7 +1423,6 @@
if(obj_integrity < (0.75 * max_integrity))
update_icon()
-
/obj/machinery/door/airlock/deconstruct(disassembled = TRUE, mob/user)
if(!(flags_1 & NODECONSTRUCT_1))
var/obj/structure/door_assembly/A
@@ -1482,6 +1491,11 @@
return "photo"
+/obj/machinery/door/airlock/ui_requires_update(mob/user, datum/tgui/ui)
+ . = ..()
+ if(secondsMainPowerLost || secondsBackupPowerLost || secondsElectrified)
+ . = TRUE // Autoupdate while counters are counting down
+
/obj/machinery/door/airlock/ui_state(mob/user)
return GLOB.default_state
@@ -1627,6 +1641,7 @@
return
emergency = !emergency
update_icon()
+ ui_update()
/obj/machinery/door/airlock/proc/user_toggle_open(mob/user)
if(!user_allowed(user))
diff --git a/code/game/machinery/doors/airlock_electronics.dm b/code/game/machinery/doors/airlock_electronics.dm
index 3ad80df861ec7..222ae936a0dca 100644
--- a/code/game/machinery/doors/airlock_electronics.dm
+++ b/code/game/machinery/doors/airlock_electronics.dm
@@ -2,10 +2,14 @@
name = "airlock electronics"
req_access = list(ACCESS_MAINT_TUNNELS)
custom_price = 5
-
+ /// A list of all granted accesses
var/list/accesses = list()
+ /// If the airlock should require ALL or only ONE of the listed accesses
var/one_access = 0
- var/unres_sides = 0 //unrestricted sides, or sides of the airlock that will open regardless of access
+ /// Unrestricted sides, or sides of the airlock that will open regardless of access
+ var/unres_sides = 0
+ /// A holder of the electronics, in case of them working as an integrated part
+ var/holder
/obj/item/electronics/airlock/examine(mob/user)
. = ..()
@@ -47,7 +51,6 @@
data["accesses"] = accesses
data["oneAccess"] = one_access
data["unres_direction"] = unres_sides
-
return data
/obj/item/electronics/airlock/ui_act(action, params)
@@ -87,3 +90,8 @@
return
accesses -= get_region_accesses(region)
. = TRUE
+
+/obj/item/electronics/airlock/ui_host()
+ if(holder)
+ return holder
+ return src
diff --git a/code/game/machinery/doors/airlock_types.dm b/code/game/machinery/doors/airlock_types.dm
index 36ecd52dde50d..755aaf065d101 100644
--- a/code/game/machinery/doors/airlock_types.dm
+++ b/code/game/machinery/doors/airlock_types.dm
@@ -200,9 +200,9 @@
assemblytype = /obj/structure/door_assembly/door_assembly_uranium
var/last_event = 0
-/obj/machinery/door/airlock/uranium/process()
+/obj/machinery/door/airlock/uranium/process(delta_time)
if(world.time > last_event+20)
- if(prob(50))
+ if(DT_PROB(50, delta_time))
radiate()
last_event = world.time
..()
@@ -241,7 +241,7 @@
DA.update_name()
qdel(src)
-/obj/machinery/door/airlock/plasma/BlockSuperconductivity() //we don't stop the heat~
+/obj/machinery/door/airlock/plasma/BlockThermalConductivity() //we don't stop the heat~
return 0
/obj/machinery/door/airlock/plasma/attackby(obj/item/C, mob/user, params)
@@ -291,12 +291,27 @@
icon = 'icons/obj/doors/airlocks/shuttle/shuttle.dmi'
overlays_file = 'icons/obj/doors/airlocks/shuttle/overlays.dmi'
normal_integrity = 400
+ anim_parts = "rightu=11,0;left=-12,0;right=11,0"
/obj/machinery/door/airlock/titanium/glass
normal_integrity = 350
opacity = 0
glass = TRUE
+/obj/machinery/door/airlock/bronze
+ name = "bronze airlock"
+ icon = 'icons/obj/doors/airlocks/clockwork/pinion_airlock.dmi'
+ overlays_file = 'icons/obj/doors/airlocks/clockwork/overlays.dmi'
+ assemblytype = /obj/structure/door_assembly/door_assembly_bronze
+ anim_parts = "left=-13,0;right=13,0"
+ normal_integrity = 150
+ damage_deflection = 5
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0, "stamina" = 0)
+
+/obj/machinery/door/airlock/bronze/seethru
+ assemblytype = /obj/structure/door_assembly/door_assembly_bronze/seethru
+ opacity = 0
+ glass = TRUE
//////////////////////////////////
/*
Station2 Airlocks
@@ -336,6 +351,9 @@
overlays_file = 'icons/obj/doors/airlocks/external/overlays.dmi'
note_overlay_file = 'icons/obj/doors/airlocks/external/overlays.dmi'
assemblytype = /obj/structure/door_assembly/door_assembly_ext
+ anim_parts = "top=0,16;bottom=0,-16"
+ note_attachment = "bottom"
+ panel_attachment = "bottom"
/obj/machinery/door/airlock/arrivals_external
name = "arrivals airlock"
@@ -343,6 +361,9 @@
overlays_file = 'icons/obj/doors/airlocks/external/overlays.dmi'
note_overlay_file = 'icons/obj/doors/airlocks/external/overlays.dmi'
protected_door = TRUE
+ anim_parts = "top=0,16;bottom=0,-16"
+ note_attachment = "bottom"
+ panel_attachment = "bottom"
/obj/machinery/door/airlock/external/glass
opacity = 0
@@ -375,6 +396,7 @@
name = "vault door"
icon = 'icons/obj/doors/airlocks/vault/vault.dmi'
overlays_file = 'icons/obj/doors/airlocks/vault/overlays.dmi'
+ anim_parts = "rightpins=15,0;leftpins=-17,0;rightu=13,0;left=-15,0;right=13,0"
assemblytype = /obj/structure/door_assembly/door_assembly_vault
explosion_block = 2
normal_integrity = 400 // reverse engieneerd: 400 * 1.5 (sec lvl 6) = 600 = original
@@ -391,6 +413,11 @@
overlays_file = 'icons/obj/doors/airlocks/hatch/overlays.dmi'
note_overlay_file = 'icons/obj/doors/airlocks/hatch/overlays.dmi'
assemblytype = /obj/structure/door_assembly/door_assembly_hatch
+ //anim_parts = "ul=-9,9;ur=9,9;dl=-9,-9;dr=9,-9"
+ anim_parts = "ul=-15,0,0,4,-90;ur=0,15,0,4,-90;dl=0,-15,0,4,-90;dr=15,0,0,4,-90"
+ note_attachment = "ul"
+ panel_attachment = "dr"
+ allow_repaint = FALSE
/obj/machinery/door/airlock/maintenance_hatch
name = "maintenance hatch"
@@ -398,6 +425,11 @@
overlays_file = 'icons/obj/doors/airlocks/hatch/overlays.dmi'
note_overlay_file = 'icons/obj/doors/airlocks/hatch/overlays.dmi'
assemblytype = /obj/structure/door_assembly/door_assembly_mhatch
+ //anim_parts = "ul=-9,9;ur=9,9;dl=-9,-9;dr=9,-9"
+ anim_parts = "ul=-15,0,0,4,-90;ur=0,15,0,4,-90;dl=0,-15,0,4,-90;dr=15,0,0,4,-90"
+ note_attachment = "ul"
+ panel_attachment = "dr"
+ allow_repaint = FALSE
//////////////////////////////////
/*
@@ -408,6 +440,7 @@
name = "high tech security airlock"
icon = 'icons/obj/doors/airlocks/highsec/highsec.dmi'
overlays_file = 'icons/obj/doors/airlocks/highsec/overlays.dmi'
+ anim_parts = "rightu=14,0;left=-14,0;right=14,0"
assemblytype = /obj/structure/door_assembly/door_assembly_highsecurity
explosion_block = 2
normal_integrity = 500
@@ -424,6 +457,7 @@
icon = 'icons/obj/doors/airlocks/shuttle/shuttle.dmi'
overlays_file = 'icons/obj/doors/airlocks/shuttle/overlays.dmi'
assemblytype = /obj/structure/door_assembly/door_assembly_shuttle
+ anim_parts = "rightu=11,0;left=-12,0;right=11,0"
/obj/machinery/door/airlock/shuttle/glass
opacity = 0
@@ -436,12 +470,14 @@
overlays_file = 'icons/obj/doors/airlocks/abductor/overlays.dmi'
assemblytype = /obj/structure/door_assembly/door_assembly_abductor
note_overlay_file = 'icons/obj/doors/airlocks/external/overlays.dmi'
+ anim_parts="p1=0,40,0,5;p2=0,24,2,5;p3=0,-36,0.5,5;p4=0,16,3,5;p5=0,-40,0,5;p6=0,32,1,5;p7=0,-24,2,5" // the door has 7 fucking parts. SEVEN.
damage_deflection = 30
explosion_block = 3
hackProof = TRUE
aiControlDisabled = 1
normal_integrity = 700
security_level = 1
+ allow_repaint = FALSE
//////////////////////////////////
/*
@@ -460,6 +496,7 @@
var/openingoverlaytype = /obj/effect/temp_visual/cult/door
var/friendly = FALSE
var/stealthy = FALSE
+ allow_repaint = FALSE
/obj/machinery/door/airlock/cult/Initialize()
. = ..()
@@ -552,7 +589,7 @@
desc = "An airlock hastily corrupted by blood magic, it is unusually brittle in this state."
normal_integrity = 150
damage_deflection = 5
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0, "stamina" = 0)
//////////////////////////////////
/*
@@ -563,10 +600,14 @@
name = "large glass airlock"
icon = 'icons/obj/doors/airlocks/glass_large/glass_large.dmi'
overlays_file = 'icons/obj/doors/airlocks/glass_large/overlays.dmi'
+ mask_file = 'icons/obj/doors/airlocks/mask_64x32_airlocks.dmi'
+ mask_x = 16 // byond is consistent and sane
+ anim_parts = "left=-21,0;right=21,0;top=0,29"
opacity = 0
assemblytype = null
glass = TRUE
bound_width = 64 // 2x1
+ allow_repaint = FALSE
/obj/machinery/door/airlock/glass_large/narsie_act()
return
diff --git a/code/game/machinery/doors/brigdoors.dm b/code/game/machinery/doors/brigdoors.dm
index 7f8ab0f3c0338..61023ee9d8fa2 100644
--- a/code/game/machinery/doors/brigdoors.dm
+++ b/code/game/machinery/doors/brigdoors.dm
@@ -2,11 +2,8 @@
#define FONT_SIZE "5pt"
#define FONT_COLOR "#09f"
#define FONT_STYLE "Small Fonts"
-#define MAX_TIMER 9000
-#define PRESET_SHORT 1200
-#define PRESET_MEDIUM 1800
-#define PRESET_LONG 3000
+
@@ -25,13 +22,20 @@
desc = "A remote control for a door."
req_access = list(ACCESS_SECURITY)
density = FALSE
+ layer = ABOVE_WINDOW_LAYER
var/id = null // id of linked machinery/lockers
var/activation_time = 0
var/timer_duration = 0
- var/timing = FALSE // boolean, true/1 timer is on, false/0 means it's not timing
- var/list/obj/machinery/targets = list()
+ var/timing = FALSE // boolean, true/1 timer is on, false/0 means it's not timing
+ ///List of weakrefs to nearby doors
+ var/list/doors = list()
+ ///List of weakrefs to nearby flashers
+ var/list/flashers = list()
+ ///List of weakrefs to nearby closets
+ var/list/closets = list()
+
var/obj/item/radio/Radio //needed to send messages to sec radio
maptext_height = 26
@@ -51,17 +55,17 @@
if(id != null)
for(var/obj/machinery/door/window/brigdoor/M in urange(20, src))
if (M.id == id)
- targets += M
+ doors += WEAKREF(M)
for(var/obj/machinery/flasher/F in urange(20, src))
if(F.id == id)
- targets += F
+ flashers += WEAKREF(F)
for(var/obj/structure/closet/secure_closet/brig/C in urange(20, src))
if(C.id == id)
- targets += C
+ closets += WEAKREF(C)
- if(!targets.len)
+ if(!length(doors) && !length(flashers) && length(closets))
stat |= BROKEN
update_icon()
@@ -92,18 +96,26 @@
activation_time = world.time
timing = TRUE
- for(var/obj/machinery/door/window/brigdoor/door in targets)
+ for(var/datum/weakref/door_ref as anything in doors)
+ var/obj/machinery/door/window/brigdoor/door = door_ref.resolve()
+ if(!door)
+ doors -= door_ref
+ continue
if(door.density)
continue
INVOKE_ASYNC(door, /obj/machinery/door/window/brigdoor.proc/close)
- for(var/obj/structure/closet/secure_closet/brig/C in targets)
- if(C.broken)
+ for(var/datum/weakref/closet_ref as anything in closets)
+ var/obj/structure/closet/secure_closet/brig/closet = closet_ref.resolve()
+ if(!closet)
+ closets -= closet_ref
continue
- if(C.opened && !C.close())
+ if(closet.broken)
continue
- C.locked = TRUE
- C.update_icon()
+ if(closet.opened && !closet.close())
+ continue
+ closet.locked = TRUE
+ closet.update_icon()
return 1
@@ -120,19 +132,28 @@
activation_time = null
set_timer(0)
update_icon()
+ ui_update()
- for(var/obj/machinery/door/window/brigdoor/door in targets)
+ for(var/datum/weakref/door_ref as anything in doors)
+ var/obj/machinery/door/window/brigdoor/door = door_ref.resolve()
+ if(!door)
+ doors -= door_ref
+ continue
if(!door.density)
continue
INVOKE_ASYNC(door, /obj/machinery/door/window/brigdoor.proc/open)
- for(var/obj/structure/closet/secure_closet/brig/C in targets)
- if(C.broken)
+ for(var/datum/weakref/closet_ref as anything in closets)
+ var/obj/structure/closet/secure_closet/brig/closet = closet_ref.resolve()
+ if(!closet)
+ closets -= closet_ref
+ continue
+ if(closet.broken)
continue
- if(C.opened)
+ if(closet.opened)
continue
- C.locked = FALSE
- C.update_icon()
+ closet.locked = FALSE
+ closet.update_icon()
return 1
@@ -143,11 +164,16 @@
. /= 10
/obj/machinery/door_timer/proc/set_timer(value)
- var/new_time = clamp(value,0,MAX_TIMER)
+ var/new_time = clamp(value,0,CONFIG_GET(number/brig_timer_max) MINUTES)
. = new_time == timer_duration //return 1 on no change
timer_duration = new_time
+/obj/machinery/door_timer/ui_requires_update(mob/user, datum/tgui/ui)
+ . = ..()
+ if(timing)
+ . = TRUE // Autoupdate while timer is counting down
+
/obj/machinery/door_timer/ui_state(mob/user)
return GLOB.default_state
@@ -207,8 +233,12 @@
data["minutes"] = round((time_left - data["seconds"]) / 60)
data["timing"] = timing
data["flash_charging"] = FALSE
- for(var/obj/machinery/flasher/F in targets)
- if(F.last_flash && (F.last_flash + 150) > world.time)
+ for(var/datum/weakref/flash_ref as anything in flashers)
+ var/obj/machinery/flasher/flasher = flash_ref.resolve()
+ if(!flasher)
+ flashers -= flash_ref
+ continue
+ if(flasher.last_flash && (flasher.last_flash + 15 SECONDS) > world.time)
data["flash_charging"] = TRUE
break
return data
@@ -219,6 +249,8 @@
return
. = TRUE
+ var/mob/user = usr
+
if(!allowed(usr))
to_chat(usr, "Access denied.")
return FALSE
@@ -227,36 +259,47 @@
if("time")
var/value = text2num(params["adjust"])
if(value)
- . = set_timer(time_left()+value)
+ . = !set_timer(time_left()+value)
+ investigate_log("[key_name(usr)] modified the timer by [value/10] seconds for cell [id], currently [time_left(seconds = TRUE)]", INVESTIGATE_RECORDS)
+ user.log_message("modified the timer by [value/10] seconds for cell [id], currently [time_left(seconds = TRUE)]", LOG_ATTACK)
if("start")
timer_start()
+ investigate_log("[key_name(usr)] has started [id]'s timer of [time_left(seconds = TRUE)] seconds", INVESTIGATE_RECORDS)
+ user.log_message("has started [id]'s timer of [time_left(seconds = TRUE)] seconds", LOG_ATTACK)
if("stop")
+ investigate_log("[key_name(usr)] has stopped [id]'s timer of [time_left(seconds = TRUE)] seconds", INVESTIGATE_RECORDS)
+ user.log_message("[key_name(usr)] has stopped [id]'s timer of [time_left(seconds = TRUE)] seconds", LOG_ATTACK)
timer_end(forced = TRUE)
if("flash")
- for(var/obj/machinery/flasher/F in targets)
- F.flash()
+ investigate_log("[key_name(usr)] has flashed cell [id]", INVESTIGATE_RECORDS)
+ user.log_message("[key_name(usr)] has flashed cell [id]", LOG_ATTACK)
+ for(var/datum/weakref/flash_ref as anything in flashers)
+ var/obj/machinery/flasher/flasher = flash_ref.resolve()
+ if(!flasher)
+ flashers -= flash_ref
+ continue
+ flasher.flash()
if("preset")
var/preset = params["preset"]
var/preset_time = time_left()
switch(preset)
if("short")
- preset_time = PRESET_SHORT
+ preset_time = CONFIG_GET(number/brig_timer_preset_short) MINUTES
if("medium")
- preset_time = PRESET_MEDIUM
+ preset_time = CONFIG_GET(number/brig_timer_preset_med) MINUTES
if("long")
- preset_time = PRESET_LONG
- . = set_timer(preset_time)
+ preset_time = CONFIG_GET(number/brig_timer_preset_long) MINUTES
+ . = !set_timer(preset_time)
+ investigate_log("[key_name(usr)] set cell [id]'s timer to [preset_time/10] seconds", INVESTIGATE_RECORDS)
+ user.log_message("set cell [id]'s timer to [preset_time/10] seconds", LOG_ATTACK)
if(timing)
activation_time = world.time
else
. = FALSE
-#undef PRESET_SHORT
-#undef PRESET_MEDIUM
-#undef PRESET_LONG
-#undef MAX_TIMER
+
#undef FONT_SIZE
#undef FONT_COLOR
#undef FONT_STYLE
diff --git a/code/game/machinery/doors/checkForMultipleDoors.dm b/code/game/machinery/doors/checkForMultipleDoors.dm
index 3c1364dfdd9dd..55c0ba82b3620 100644
--- a/code/game/machinery/doors/checkForMultipleDoors.dm
+++ b/code/game/machinery/doors/checkForMultipleDoors.dm
@@ -12,5 +12,5 @@
for(var/obj/machinery/door/D in locate(src.x,src.y,src.z))
if(!istype(D, /obj/machinery/door/window) && D.density)
return 0
- //There are no false wall checks because that would be fucking retarded
- return 1
\ No newline at end of file
+ //There are no false wall checks because that would be fucking stupid
+ return 1
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index b40b44fcf26d7..e2729c287e4ab 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -9,9 +9,10 @@
layer = OPEN_DOOR_LAYER
power_channel = AREA_USAGE_ENVIRON
max_integrity = 350
- armor = list("melee" = 30, "bullet" = 30, "laser" = 20, "energy" = 20, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 70)
+ armor = list("melee" = 30, "bullet" = 30, "laser" = 20, "energy" = 20, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 70, "stamina" = 0)
CanAtmosPass = ATMOS_PASS_DENSITY
flags_1 = PREVENT_CLICK_UNDER_1
+ ricochet_chance_mod = 0.8
damage_deflection = 10
interaction_flags_atom = INTERACT_ATOM_UI_INTERACT
@@ -35,7 +36,7 @@
var/datum/effect_system/spark_spread/spark_system
var/real_explosion_block //ignore this, just use explosion_block
var/red_alert_access = FALSE //if TRUE, this door will always open on red alert
- var/poddoor = FALSE
+ var/poddoor = FALSE
var/unres_sides = 0 //Unrestricted sides. A bitflag for which direction (if any) can open the door with no access
var/open_speed = 5
@@ -218,7 +219,7 @@
return max_moles - min_moles > 20
/obj/machinery/door/attackby(obj/item/I, mob/user, params)
- if(user.a_intent != INTENT_HARM && (I.tool_behaviour == TOOL_CROWBAR || istype(I, /obj/item/twohanded/fireaxe)))
+ if(user.a_intent != INTENT_HARM && (I.tool_behaviour == TOOL_CROWBAR || istype(I, /obj/item/fireaxe)))
try_to_crowbar(I, user)
return 1
else if(I.tool_behaviour == TOOL_WELDER)
@@ -352,12 +353,16 @@
L.adjustBruteLoss(DOOR_CRUSH_DAMAGE * 1.5) //Xenos go into crit after aproximately the same amount of crushes as humans.
L.emote("roar")
else if(ishuman(L)) //For humans
- L.adjustBruteLoss(DOOR_CRUSH_DAMAGE)
+ var/armour = L.run_armor_check(BODY_ZONE_CHEST, "melee")
+ var/multiplier = CLAMP(1 - (armour * 0.01), 0, 1)
+ L.adjustBruteLoss(multiplier * DOOR_CRUSH_DAMAGE)
L.emote("scream")
- L.Paralyze(100)
+ if(!L.IsParalyzed())
+ L.Paralyze(60)
else if(ismonkey(L)) //For monkeys
L.adjustBruteLoss(DOOR_CRUSH_DAMAGE)
- L.Paralyze(100)
+ if(!L.IsParalyzed())
+ L.Paralyze(60)
else //for simple_animals & borgs
L.adjustBruteLoss(DOOR_CRUSH_DAMAGE)
var/turf/location = get_turf(src)
@@ -384,7 +389,7 @@
if(!glass && GLOB.cameranet)
GLOB.cameranet.updateVisibility(src, 0)
-/obj/machinery/door/BlockSuperconductivity() // All non-glass airlocks block heat, this is intended.
+/obj/machinery/door/BlockThermalConductivity() // All non-glass airlocks block heat, this is intended.
if(opacity || heat_proof)
return 1
return 0
diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm
index 0f2ae85a3dc42..b8655cd325a56 100644
--- a/code/game/machinery/doors/firedoor.dm
+++ b/code/game/machinery/doors/firedoor.dm
@@ -4,9 +4,11 @@
#define CONSTRUCTION_GUTTED 3 //Wires are removed, circuit ready to remove
#define CONSTRUCTION_NOCIRCUIT 4 //Circuit board removed, can safely weld apart
+#define RECLOSE_DELAY 5 SECONDS // How long until a firelock tries to shut itself if it's blocking a vacuum.
+
/obj/machinery/door/firedoor
name = "firelock"
- desc = "A convenable firelock. Equipt with a manual lever for operating in case of emergency."
+ desc = "A convenable firelock. Equipped with a manual lever for operating in case of emergency."
icon = 'icons/obj/doors/doorfireglass.dmi'
icon_state = "door_open"
opacity = FALSE
@@ -21,7 +23,7 @@
layer = BELOW_OPEN_DOOR_LAYER
closingLayer = CLOSED_FIREDOOR_LAYER
assemblytype = /obj/structure/firelock_frame
- armor = list("melee" = 30, "bullet" = 30, "laser" = 20, "energy" = 20, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 95, "acid" = 70)
+ armor = list("melee" = 30, "bullet" = 30, "laser" = 20, "energy" = 20, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 95, "acid" = 70, "stamina" = 0)
interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_REQUIRES_SILICON | INTERACT_MACHINE_OPEN
air_tight = TRUE
open_speed = 2
@@ -71,26 +73,17 @@
return ..()
/obj/machinery/door/firedoor/Bumped(atom/movable/AM)
- if(panel_open || operating || welded || (stat & NOPOWER))
+ if(panel_open || operating)
return
- if(ismob(AM))
- var/mob/user = AM
- if(allow_hand_open(user))
- add_fingerprint(user)
- open()
- return TRUE
- if(ismecha(AM))
- var/obj/mecha/M = AM
- if(M.occupant && allow_hand_open(M.occupant))
- open()
- return TRUE
+ if(!density)
+ return ..()
return FALSE
/obj/machinery/door/firedoor/power_change()
if(powered(power_channel))
stat &= ~NOPOWER
- latetoggle()
+ INVOKE_ASYNC(src, .proc/latetoggle)
else
stat |= NOPOWER
@@ -98,26 +91,10 @@
. = ..()
if(.)
return
-
- if (!welded && !operating)
- if (stat & NOPOWER)
- user.visible_message("[user] tries to open \the [src] manually.",
- "You operate the manual lever on \the [src].")
- if (!do_after(user, 30, TRUE, src))
- return FALSE
- else if (density && !allow_hand_open(user))
- return FALSE
-
- add_fingerprint(user)
- if(density)
- emergency_close_timer = world.time + 15 // prevent it from instaclosing again if in space
- open()
- else
- close()
- return TRUE
+
if(operating || !density)
return
-
+
user.changeNext_move(CLICK_CD_MELEE)
user.visible_message("[user] bangs on \the [src].",
@@ -181,7 +158,7 @@
whack_a_mole()
if(welded || operating || !density)
return // in case things changed during our do_after
- emergency_close_timer = world.time + 15 // prevent it from instaclosing again if in space
+ emergency_close_timer = world.time + RECLOSE_DELAY // prevent it from instaclosing again if in space
open()
else
close()
@@ -236,6 +213,8 @@
latetoggle()
/obj/machinery/door/firedoor/close()
+ if(HAS_TRAIT(loc, TRAIT_FIREDOOR_STOP))
+ return
. = ..()
latetoggle()
@@ -298,7 +277,6 @@
if(!(flags_1 & NODECONSTRUCT_1))
var/obj/structure/firelock_frame/F = new assemblytype(get_turf(src))
F.dir = src.dir
- F.firelock_type = src.type
if(disassembled)
F.constructionStep = CONSTRUCTION_PANEL_OPEN
else
@@ -325,6 +303,11 @@
CanAtmosPass = ATMOS_PASS_PROC
assemblytype = /obj/structure/firelock_frame/border
+/obj/machinery/door/firedoor/border_only/Destroy()
+ density = FALSE
+ air_update_turf(1)
+ return ..()
+
/obj/machinery/door/firedoor/border_only/closed
icon_state = "door_closed"
opacity = TRUE
@@ -423,6 +406,10 @@
heat_proof = FALSE
assemblytype = /obj/structure/firelock_frame/window
+/obj/machinery/door/firedoor/window/attack_alien(mob/living/carbon/alien/humanoid/user)
+ playsound(src.loc, 'sound/weapons/slash.ogg', 100, 1)
+ return attack_generic(user, 60, BRUTE, "melee", 0)
+
/obj/item/electronics/firelock
name = "firelock circuitry"
custom_price = 5
@@ -438,7 +425,7 @@
density = TRUE
var/constructionStep = CONSTRUCTION_NOCIRCUIT
var/reinforced = 0
- var/firelock_type
+ var/firelock_type = /obj/machinery/door/firedoor
/obj/structure/firelock_frame/examine(mob/user)
. = ..()
@@ -491,12 +478,9 @@
user.visible_message("[user] finishes the firelock.", \
"You finish the firelock.")
playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, 1)
- if(reinforced)
- new /obj/machinery/door/firedoor/heavy(get_turf(src))
- else
- var/obj/machinery/door/firedoor/F = new firelock_type(get_turf(src))
- F.dir = src.dir
- F.update_icon()
+ var/obj/machinery/door/firedoor/F = new firelock_type(get_turf(src))
+ F.dir = src.dir
+ F.update_icon()
qdel(src)
return
if(istype(C, /obj/item/stack/sheet/plasteel))
@@ -517,7 +501,8 @@
"You reinforce [src].")
playsound(get_turf(src), 'sound/items/deconstruct.ogg', 50, 1)
P.use(2)
- reinforced = 1
+ reinforced = TRUE
+ firelock_type = /obj/machinery/door/firedoor/heavy
return
if(CONSTRUCTION_WIRES_EXPOSED)
@@ -591,12 +576,21 @@
if(C.use_tool(src, user, 40, volume=50, amount=1))
if(constructionStep != CONSTRUCTION_NOCIRCUIT)
return
- user.visible_message("[user] cuts apart [src]!", \
- "You cut [src] into iron.")
var/turf/T = get_turf(src)
- new /obj/item/stack/sheet/iron(T, 3)
- if(reinforced)
- new /obj/item/stack/sheet/plasteel(T, 2)
+ switch(firelock_type)
+ if(/obj/machinery/door/firedoor/heavy)
+ user.visible_message("[user] cuts apart [src]!", \
+ "You cut [src] into iron and plasteel.")
+ new /obj/item/stack/sheet/plasteel(T, 2)
+ new /obj/item/stack/sheet/iron(T, 3)
+ if(/obj/machinery/door/firedoor/window)
+ user.visible_message("[user] cuts apart [src]!", \
+ "You cut [src] into reinforced glass.")
+ new /obj/item/stack/sheet/rglass(T,2)
+ else
+ user.visible_message("[user] cuts apart [src]!", \
+ "You cut [src] into iron.")
+ new /obj/item/stack/sheet/iron(T, 3)
qdel(src)
return
if(istype(C, /obj/item/electronics/firelock))
@@ -649,11 +643,14 @@
/obj/structure/firelock_frame/heavy
name = "heavy firelock frame"
reinforced = TRUE
+ firelock_type = /obj/machinery/door/firedoor/heavy
/obj/structure/firelock_frame/border
name = "firelock frame"
icon = 'icons/obj/doors/edge_Doorfire.dmi'
icon_state = "door_frame"
+ density = FALSE
+ firelock_type = /obj/machinery/door/firedoor/border_only
/obj/structure/firelock_frame/border/ComponentInitialize()
. = ..()
@@ -672,6 +669,7 @@
name = "window firelock frame"
icon = 'icons/obj/doors/doorfirewindow.dmi'
icon_state = "door_frame"
+ firelock_type = /obj/machinery/door/firedoor/window
/obj/structure/firelock_frame/window/update_icon()
return
diff --git a/code/game/machinery/doors/passworddoor.dm b/code/game/machinery/doors/passworddoor.dm
index fcbb214bb66aa..d383e9ecb9d1c 100644
--- a/code/game/machinery/doors/passworddoor.dm
+++ b/code/game/machinery/doors/passworddoor.dm
@@ -6,7 +6,7 @@
explosion_block = 3
heat_proof = TRUE
max_integrity = 600
- armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100)
+ armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100, "stamina" = 0)
resistance_flags = INDESTRUCTIBLE | FIRE_PROOF | ACID_PROOF | LAVA_PROOF
damage_deflection = 70
var/password = "Swordfish"
@@ -20,9 +20,9 @@
/obj/machinery/door/password/Initialize(mapload)
. = ..()
if(voice_activated)
- flags_1 |= HEAR_1
+ become_hearing_sensitive()
-/obj/machinery/door/password/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
+/obj/machinery/door/password/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods = list())
. = ..()
if(!density || !voice_activated || radio_freq)
return
@@ -70,4 +70,4 @@
return
/obj/machinery/door/password/ex_act(severity, target)
- return
\ No newline at end of file
+ return
diff --git a/code/game/machinery/doors/poddoor.dm b/code/game/machinery/doors/poddoor.dm
index 5ea8a280fd595..a4969f2e3bebf 100644
--- a/code/game/machinery/doors/poddoor.dm
+++ b/code/game/machinery/doors/poddoor.dm
@@ -11,7 +11,7 @@
heat_proof = TRUE
safe = FALSE
max_integrity = 600
- armor = list("melee" = 50, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 70)
+ armor = list("melee" = 50, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 70, "stamina" = 0)
resistance_flags = FIRE_PROOF
damage_deflection = 70
poddoor = TRUE
diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm
index ef2ba11d60774..9ab76e7d75360 100644
--- a/code/game/machinery/doors/windowdoor.dm
+++ b/code/game/machinery/doors/windowdoor.dm
@@ -9,7 +9,7 @@
var/base_state = "left"
max_integrity = 150 //If you change this, consider changing ../door/window/brigdoor/ max_integrity at the bottom of this .dm file
integrity_failure = 0
- armor = list("melee" = 20, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 70, "acid" = 100)
+ armor = list("melee" = 20, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 70, "acid" = 100, "stamina" = 0)
visible = FALSE
flags_1 = ON_BORDER_1
opacity = 0
@@ -42,6 +42,7 @@
/obj/machinery/door/window/Destroy()
density = FALSE
+ air_update_turf(1)
QDEL_LIST(debris)
if(obj_integrity == 0)
playsound(src, "shatter", 70, 1)
@@ -316,6 +317,10 @@
if(!hasPower())
return
+ //Check radio signal jamming
+ if(is_jammed())
+ return
+
// Check packet access level.
if(!check_access_ntnet(data))
return
@@ -354,7 +359,7 @@
/obj/machinery/door/window/brigdoor/security/holding
name = "holding cell door"
- req_one_access = list(ACCESS_SEC_DOORS, ACCESS_LAWYER) //love for the lawyer
+ req_one_access = list(ACCESS_SEC_DOORS, ACCESS_LAWYER, ACCESS_BRIGPHYS) //love for the lawyer and Brig Phys
/obj/machinery/door/window/clockwork
name = "brass windoor"
@@ -364,7 +369,7 @@
shards = 0
rods = 0
max_integrity = 50
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 70, "acid" = 100)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 70, "acid" = 100, "stamina" = 0)
resistance_flags = FIRE_PROOF | ACID_PROOF
var/made_glow = FALSE
@@ -403,6 +408,9 @@
animate(src, color = previouscolor, time = 8)
addtimer(CALLBACK(src, /atom/proc/update_atom_colour), 8)
+/obj/machinery/door/window/clockwork/ratvar_act()
+ return FALSE
+
/obj/machinery/door/window/clockwork/attackby(obj/item/I, mob/living/user, params)
if(operating)
diff --git a/code/game/machinery/doppler_array.dm b/code/game/machinery/doppler_array.dm
index 93f338efaa786..9e1339c0453b8 100644
--- a/code/game/machinery/doppler_array.dm
+++ b/code/game/machinery/doppler_array.dm
@@ -1,7 +1,5 @@
#define PRINTER_TIMEOUT 40
-GLOBAL_LIST_EMPTY(doppler_arrays)
-
/obj/machinery/doppler_array
name = "tachyon-doppler array"
desc = "A highly precise directional sensor array which measures the release of quants from decaying tachyons. The doppler shifting of the mirror-image formed by these quants can reveal the size, location and temporal affects of energetic disturbances within a large radius ahead of the array.\n"
@@ -22,17 +20,13 @@ GLOBAL_LIST_EMPTY(doppler_arrays)
/obj/machinery/doppler_array/Initialize()
. = ..()
- GLOB.doppler_arrays += src
+ RegisterSignal(SSdcs, COMSIG_GLOB_EXPLOSION, .proc/sense_explosion)
printer_ready = world.time + PRINTER_TIMEOUT
/obj/machinery/doppler_array/ComponentInitialize()
. = ..()
AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE,null,null,CALLBACK(src,.proc/rot_message))
-/obj/machinery/doppler_array/Destroy()
- GLOB.doppler_arrays -= src
- return ..()
-
/datum/data/tachyon_record
name = "Log Recording"
var/timestamp
@@ -81,13 +75,13 @@ GLOBAL_LIST_EMPTY(doppler_arrays)
if(!records || !(record in records))
return
records -= record
- return TRUE
+ . = TRUE
if("print_record")
var/datum/data/tachyon_record/record = locate(params["ref"]) in records
if(!records || !(record in records))
return
print(usr, record)
- return TRUE
+ . = TRUE
/obj/machinery/doppler_array/proc/print(mob/user, datum/data/tachyon_record/record)
if(!record)
@@ -140,16 +134,18 @@ GLOBAL_LIST_EMPTY(doppler_arrays)
to_chat(user, "You adjust [src]'s dish to face to the [dir2text(dir)].")
playsound(src, 'sound/items/screwdriver2.ogg', 50, 1)
-/obj/machinery/doppler_array/proc/sense_explosion(turf/epicenter,devastation_range,heavy_impact_range,light_impact_range,
+/obj/machinery/doppler_array/proc/sense_explosion(datum/source,turf/epicenter,devastation_range,heavy_impact_range,light_impact_range,
took,orig_dev_range,orig_heavy_range,orig_light_range)
+ SIGNAL_HANDLER
+
if(stat & NOPOWER)
return FALSE
var/turf/zone = get_turf(src)
- if(zone.z != epicenter.z)
+ if(zone.get_virtual_z_level() != epicenter.get_virtual_z_level())
return FALSE
if(next_announce > world.time)
- return
+ return FALSE
next_announce = world.time + cooldown
var/distance = get_dist(epicenter, zone)
@@ -185,6 +181,8 @@ GLOBAL_LIST_EMPTY(doppler_arrays)
record_number++
records += R
+ //Update to viewers
+ ui_update()
return TRUE
/obj/machinery/doppler_array/power_change()
@@ -210,10 +208,11 @@ GLOBAL_LIST_EMPTY(doppler_arrays)
desc = "A specialized tachyon-doppler bomb detection array that uses the results of the highest yield of explosions for research."
var/datum/techweb/linked_techweb
-/obj/machinery/doppler_array/research/sense_explosion(turf/epicenter, dev, heavy, light, time, orig_dev, orig_heavy, orig_light) //probably needs a way to ignore admin explosives later on
+/obj/machinery/doppler_array/research/sense_explosion(datum/source, turf/epicenter, devastation_range, heavy_impact_range, light_impact_range,
+ took, orig_dev_range, orig_heavy_range, orig_light_range) //probably needs a way to ignore admin explosives later on
. = ..()
if(!.)
- return FALSE
+ return
if(!istype(linked_techweb))
say("Warning: No linked research system!")
return
@@ -221,11 +220,11 @@ GLOBAL_LIST_EMPTY(doppler_arrays)
var/point_gain = 0
/*****The Point Calculator*****/
- if(orig_light < 10)
+ if(orig_light_range < 10)
say("Explosion not large enough for research calculations.")
return
- else if(orig_light < 4500)
- point_gain = (83300 * orig_light) / (orig_light + 3000)
+ else if(orig_light_range < 4500)
+ point_gain = (83300 * orig_light_range) / (orig_light_range + 3000)
else
point_gain = TECHWEB_BOMB_POINTCAP
diff --git a/code/game/machinery/droneDispenser.dm b/code/game/machinery/droneDispenser.dm
index c3f6a9a6cdd59..d29cecdb54f34 100644
--- a/code/game/machinery/droneDispenser.dm
+++ b/code/game/machinery/droneDispenser.dm
@@ -30,7 +30,7 @@
var/cooldownTime = 1800 //3 minutes
var/production_time = 30
//The item the dispenser will create
- var/dispense_type = /obj/item/drone_shell
+ var/dispense_type = /obj/effect/mob_spawn/drone
// The maximum number of "idle" drone shells it will make before
// ceasing production. Set to 0 for infinite.
@@ -61,7 +61,7 @@
/obj/machinery/droneDispenser/syndrone //Please forgive me
name = "syndrone shell dispenser"
desc = "A suspicious machine that will create Syndicate exterminator drones when supplied with iron and glass. Disgusting."
- dispense_type = /obj/item/drone_shell/syndrone
+ dispense_type = /obj/effect/mob_spawn/drone/syndrone
//If we're gonna be a jackass, go the full mile - 10 second recharge timer
cooldownTime = 100
end_create_message = "dispenses a suspicious drone shell."
@@ -70,14 +70,14 @@
/obj/machinery/droneDispenser/syndrone/badass //Please forgive me
name = "badass syndrone shell dispenser"
desc = "A suspicious machine that will create Syndicate exterminator drones when supplied with iron and glass. Disgusting. This one seems ominous."
- dispense_type = /obj/item/drone_shell/syndrone/badass
+ dispense_type = /obj/effect/mob_spawn/drone/syndrone/badass
end_create_message = "dispenses an ominous suspicious drone shell."
// I don't need your forgiveness, this is awesome.
/obj/machinery/droneDispenser/snowflake
name = "snowflake drone shell dispenser"
desc = "A hefty machine that, when supplied with iron and glass, will periodically create a snowflake drone shell. Does not need to be manually operated."
- dispense_type = /obj/item/drone_shell/snowflake
+ dispense_type = /obj/effect/mob_spawn/drone/snowflake
end_create_message = "dispenses a snowflake drone shell."
// Those holoprojectors aren't cheap
iron_cost = 2000
@@ -135,14 +135,9 @@
/obj/machinery/droneDispenser/power_change()
..()
- if(powered())
- stat &= ~NOPOWER
- else
- stat |= NOPOWER
update_icon()
/obj/machinery/droneDispenser/process()
- ..()
if((stat & (NOPOWER|BROKEN)) || !anchored)
return
diff --git a/code/game/machinery/embedded_controller/embedded_controller_base.dm b/code/game/machinery/embedded_controller/embedded_controller_base.dm
index 9d67aa63bf7ba..262586b01f992 100644
--- a/code/game/machinery/embedded_controller/embedded_controller_base.dm
+++ b/code/game/machinery/embedded_controller/embedded_controller_base.dm
@@ -3,6 +3,10 @@
var/state
var/obj/machinery/embedded_controller/master
+/datum/computer/file/embedded_program/Destroy()
+ master = null
+ . = ..()
+
/datum/computer/file/embedded_program/proc/post_signal(datum/signal/signal, comm_line)
if(master)
master.post_signal(signal, comm_line)
@@ -25,6 +29,11 @@
var/on = TRUE
+/obj/machinery/embedded_controller/Destroy()
+ if(program)
+ QDEL_NULL(program)
+ . = ..()
+
/obj/machinery/embedded_controller/ui_interact(mob/user)
. = ..()
user.set_machine(src)
@@ -54,9 +63,9 @@
usr.set_machine(src)
addtimer(CALLBACK(src, .proc/updateDialog), 5)
-/obj/machinery/embedded_controller/process()
+/obj/machinery/embedded_controller/process(delta_time)
if(program)
- program.process()
+ program.process(delta_time)
update_icon()
src.updateDialog()
diff --git a/code/game/machinery/embedded_controller/simple_vent_controller.dm b/code/game/machinery/embedded_controller/simple_vent_controller.dm
index 7679080ea1f95..62fb1ad92663d 100644
--- a/code/game/machinery/embedded_controller/simple_vent_controller.dm
+++ b/code/game/machinery/embedded_controller/simple_vent_controller.dm
@@ -37,6 +37,7 @@
name = "vent controller"
density = FALSE
+ layer = ABOVE_WINDOW_LAYER
frequency = FREQ_ATMOS_CONTROL
power_channel = AREA_USAGE_ENVIRON
diff --git a/code/game/machinery/exp_cloner.dm b/code/game/machinery/exp_cloner.dm
deleted file mode 100644
index e589f3794ea26..0000000000000
--- a/code/game/machinery/exp_cloner.dm
+++ /dev/null
@@ -1,303 +0,0 @@
-//Experimental cloner; clones a body regardless of the owner's status, letting a ghost control it instead
-/obj/machinery/clonepod/experimental
- name = "experimental cloning pod"
- desc = "An ancient cloning pod. It seems to be an early prototype of the experimental cloners used in Nanotrasen Stations."
- icon = 'icons/obj/machines/cloning.dmi'
- icon_state = "pod_0"
- req_access = null
- circuit = /obj/item/circuitboard/machine/clonepod/experimental
- internal_radio = FALSE
-
-//Start growing a human clone in the pod!
-/obj/machinery/clonepod/experimental/growclone(clonename, ui, mutation_index, mindref, last_death, datum/species/mrace, list/features, factions, list/quirks, datum/bank_account/insurance)
- if(panel_open)
- return NONE
- if(mess || attempting)
- return NONE
-
- attempting = TRUE //One at a time!!
- countdown.start()
-
- var/mob/living/carbon/human/H = new /mob/living/carbon/human(src)
-
- H.hardset_dna(ui, mutation_index, H.real_name, null, mrace, features)
-
- if(efficiency > 2)
- var/list/unclean_mutations = (GLOB.not_good_mutations|GLOB.bad_mutations)
- H.dna.remove_mutation_group(unclean_mutations)
- if(efficiency > 5 && prob(20))
- H.easy_randmut(POSITIVE)
- if(efficiency < 3 && prob(50))
- var/mob/M = H.easy_randmut(NEGATIVE+MINOR_NEGATIVE)
- if(ismob(M))
- H = M
-
- H.silent = 20 //Prevents an extreme edge case where clones could speak if they said something at exactly the right moment.
- occupant = H
-
- if(!clonename) //to prevent null names
- clonename = "clone ([rand(1,999)])"
- H.real_name = clonename
-
- icon_state = "pod_1"
- //Get the clone body ready
- maim_clone(H)
- ADD_TRAIT(H, TRAIT_STABLEHEART, CLONING_POD_TRAIT)
- ADD_TRAIT(H, TRAIT_STABLELIVER, CLONING_POD_TRAIT)
- ADD_TRAIT(H, TRAIT_EMOTEMUTE, CLONING_POD_TRAIT)
- ADD_TRAIT(H, TRAIT_MUTE, CLONING_POD_TRAIT)
- ADD_TRAIT(H, TRAIT_NOBREATH, CLONING_POD_TRAIT)
- ADD_TRAIT(H, TRAIT_NOCRITDAMAGE, CLONING_POD_TRAIT)
- H.Unconscious(80)
-
- var/list/candidates = pollCandidatesForMob("Do you want to play as [clonename]'s defective clone?", null, null, null, 100, H, POLL_IGNORE_DEFECTIVECLONE)
- if(LAZYLEN(candidates))
- var/mob/dead/observer/C = pick(candidates)
- H.key = C.key
-
- if(grab_ghost_when == CLONER_FRESH_CLONE)
- H.grab_ghost()
- to_chat(H, "Consciousness slowly creeps over you as your body regenerates. So this is what cloning feels like?")
-
- if(grab_ghost_when == CLONER_MATURE_CLONE)
- H.ghostize(TRUE) //Only does anything if they were still in their old body and not already a ghost
- to_chat(H.get_ghost(TRUE), "Your body is beginning to regenerate in a cloning pod. You will become conscious when it is complete.")
-
- if(H)
- H.faction |= factions
-
- H.set_cloned_appearance()
-
- H.set_suicide(FALSE)
- attempting = FALSE
- return CLONING_DELETE_RECORD | CLONING_SUCCESS //so that we don't spam clones with autoprocess unless we leave a body in the scanner
-
-
-//Prototype cloning console, much more rudimental and lacks modern functions such as saving records, autocloning, or safety checks.
-/obj/machinery/computer/prototype_cloning
- name = "prototype cloning console"
- desc = "Used to operate an experimental cloner."
- icon_screen = "dna"
- icon_keyboard = "med_key"
- circuit = /obj/item/circuitboard/computer/prototype_cloning
- var/obj/machinery/dna_scannernew/scanner = null //Linked scanner. For scanning.
- var/list/pods //Linked experimental cloning pods
- var/temp = "Inactive"
- var/scantemp = "Ready to Scan"
- var/loading = FALSE // Nice loading text
-
- light_color = LIGHT_COLOR_BLUE
-
-/obj/machinery/computer/prototype_cloning/Initialize()
- . = ..()
- updatemodules(TRUE)
-
-/obj/machinery/computer/prototype_cloning/Destroy()
- if(pods)
- for(var/P in pods)
- DetachCloner(P)
- pods = null
- return ..()
-
-/obj/machinery/computer/prototype_cloning/proc/GetAvailablePod(mind = null)
- if(pods)
- for(var/P in pods)
- var/obj/machinery/clonepod/experimental/pod = P
- if(pod.is_operational() && !(pod.occupant || pod.mess))
- return pod
-
-/obj/machinery/computer/prototype_cloning/proc/updatemodules(findfirstcloner)
- scanner = findscanner()
- if(findfirstcloner && !LAZYLEN(pods))
- findcloner()
-
-/obj/machinery/computer/prototype_cloning/proc/findscanner()
- var/obj/machinery/dna_scannernew/scannerf = null
-
- // Loop through every direction
- for(var/direction in GLOB.cardinals)
- // Try to find a scanner in that direction
- scannerf = locate(/obj/machinery/dna_scannernew, get_step(src, direction))
- // If found and operational, return the scanner
- if (!isnull(scannerf) && scannerf.is_operational())
- return scannerf
-
- // If no scanner was found, it will return null
- return null
-
-/obj/machinery/computer/prototype_cloning/proc/findcloner()
- var/obj/machinery/clonepod/experimental/podf = null
- for(var/direction in GLOB.cardinals)
- podf = locate(/obj/machinery/clonepod/experimental, get_step(src, direction))
- if (!isnull(podf) && podf.is_operational())
- AttachCloner(podf)
-
-/obj/machinery/computer/prototype_cloning/proc/AttachCloner(obj/machinery/clonepod/experimental/pod)
- if(!pod.connected)
- pod.connected = src
- LAZYADD(pods, pod)
-
-/obj/machinery/computer/prototype_cloning/proc/DetachCloner(obj/machinery/clonepod/experimental/pod)
- pod.connected = null
- LAZYREMOVE(pods, pod)
-
-/obj/machinery/computer/prototype_cloning/attackby(obj/item/W, mob/user, params)
- if(W.tool_behaviour == TOOL_MULTITOOL)
- if(!multitool_check_buffer(user, W))
- return
- var/obj/item/multitool/P = W
-
- if(istype(P.buffer, /obj/machinery/clonepod/experimental))
- if(get_area(P.buffer) != get_area(src))
- to_chat(user, "-% Cannot link machines across power zones. Buffer cleared %-")
- P.buffer = null
- return
- to_chat(user, "-% Successfully linked [P.buffer] with [src] %-")
- var/obj/machinery/clonepod/experimental/pod = P.buffer
- if(pod.connected)
- pod.connected.DetachCloner(pod)
- AttachCloner(pod)
- else
- P.buffer = src
- to_chat(user, "-% Successfully stored [REF(P.buffer)] [P.buffer.name] in buffer %-")
- return
- else
- return ..()
-
-/obj/machinery/computer/prototype_cloning/attack_hand(mob/user)
- if(..())
- return
- interact(user)
-
-/obj/machinery/computer/prototype_cloning/interact(mob/user)
- user.set_machine(src)
- add_fingerprint(user)
-
- if(..())
- return
-
- updatemodules(TRUE)
-
- var/dat = ""
- dat += "Refresh"
-
- dat += "
Cloning Pod Status
"
- dat += "
[temp]
"
-
- if (isnull(src.scanner) || !LAZYLEN(pods))
- dat += "
Modules
"
- //dat += "Reload Modules"
- if (isnull(src.scanner))
- dat += "ERROR: No Scanner detected! "
- if (!LAZYLEN(pods))
- dat += "ERROR: No Pod detected "
-
- // Scan-n-Clone
- if (!isnull(src.scanner))
- var/mob/living/scanner_occupant = get_mob_or_brainmob(scanner.occupant)
-
- dat += "
Cloning
"
-
- dat += "
"
- if(!scanner_occupant)
- dat += "Scanner Unoccupied"
- else if(loading)
- dat += "[scanner_occupant] => Scanning..."
- else
- scantemp = "Ready to Clone"
- dat += "[scanner_occupant] => [scantemp]"
- dat += "
"
-
- if(scanner_occupant)
- dat += "Clone"
- dat += " [src.scanner.locked ? "Unlock Scanner" : "Lock Scanner"]"
- else
- dat += "Clone"
-
- var/datum/browser/popup = new(user, "cloning", "Prototype Cloning System Control")
- popup.set_content(dat)
- popup.open()
-
-/obj/machinery/computer/prototype_cloning/Topic(href, href_list)
- if(..())
- return
-
- if(loading)
- return
-
- else if ((href_list["clone"]) && !isnull(scanner) && scanner.is_operational())
- scantemp = ""
-
- loading = TRUE
- updateUsrDialog()
- playsound(src, 'sound/machines/terminal_prompt.ogg', 50, 0)
- say("Initiating scan...")
-
- spawn(20)
- clone_occupant(scanner.occupant)
- loading = FALSE
- updateUsrDialog()
- playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
-
- //No locking an open scanner.
- else if ((href_list["lock"]) && !isnull(scanner) && scanner.is_operational())
- if ((!scanner.locked) && (scanner.occupant))
- scanner.locked = TRUE
- playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
- else
- scanner.locked = FALSE
- playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
-
- else if (href_list["refresh"])
- updateUsrDialog()
- playsound(src, "terminal_type", 25, 0)
-
- add_fingerprint(usr)
- updateUsrDialog()
- return
-
-/obj/machinery/computer/prototype_cloning/proc/clone_occupant(occupant)
- var/mob/living/mob_occupant = get_mob_or_brainmob(occupant)
- var/datum/dna/dna
- if(ishuman(mob_occupant))
- var/mob/living/carbon/C = mob_occupant
- dna = C.has_dna()
- if(isbrain(mob_occupant))
- var/mob/living/brain/B = mob_occupant
- dna = B.stored_dna
-
- if(!istype(dna))
- scantemp = "Unable to locate valid genetic data."
- playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
- return
- if((HAS_TRAIT(mob_occupant, TRAIT_HUSK)) && (src.scanner.scan_level < 2))
- scantemp = "Subject's body is too damaged to scan properly."
- playsound(src, 'sound/machines/terminal_alert.ogg', 50, 0)
- return
- if(HAS_TRAIT(mob_occupant, TRAIT_BADDNA))
- scantemp = "Subject's DNA is damaged beyond any hope of recovery."
- playsound(src, 'sound/machines/terminal_alert.ogg', 50, 0)
- return
-
- var/clone_species
- if(dna.species)
- clone_species = dna.species
- else
- var/datum/species/rando_race = pick(GLOB.roundstart_races)
- clone_species = rando_race.type
-
- var/obj/machinery/clonepod/pod = GetAvailablePod()
- //Can't clone without someone to clone. Or a pod. Or if the pod is busy. Or full of gibs.
- if(!LAZYLEN(pods))
- temp = "No Clonepods detected."
- playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
- else if(!pod)
- temp = "No Clonepods available."
- playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
- else if(pod.occupant)
- temp = "Cloning cycle already in progress."
- playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
- else
- pod.growclone(mob_occupant.real_name, dna.uni_identity, dna.mutation_index, null, null, clone_species, dna.features, mob_occupant.faction)
- temp = "[mob_occupant.real_name] => Cloning data sent to pod."
- playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
diff --git a/code/game/machinery/fabricators/autolathe.dm b/code/game/machinery/fabricators/autolathe.dm
new file mode 100644
index 0000000000000..669e251c6a1e9
--- /dev/null
+++ b/code/game/machinery/fabricators/autolathe.dm
@@ -0,0 +1,186 @@
+/obj/machinery/modular_fabricator/autolathe
+ name = "autolathe"
+ desc = "It produces items using iron, copper, and glass."
+ icon_state = "autolathe"
+ density = TRUE
+ use_power = IDLE_POWER_USE
+ idle_power_usage = 10
+ active_power_usage = 100
+ circuit = /obj/item/circuitboard/machine/autolathe
+
+ var/shocked = FALSE
+ var/hack_wire
+ var/disable_wire
+ var/shock_wire
+
+ //Security modes
+ can_be_hacked_or_unlocked = TRUE
+ var/security_interface_locked = TRUE
+ var/hacked = FALSE
+
+ categories = list(
+ "Tools",
+ "Electronics",
+ "Construction",
+ "T-Comm",
+ "Security",
+ "Machinery",
+ "Medical",
+ "Misc",
+ "Dinnerware",
+ "Imported"
+ )
+
+ accepts_disks = TRUE
+
+ stored_research_type = /datum/techweb/specialized/autounlocking/autolathe
+
+/obj/machinery/modular_fabricator/autolathe/Initialize()
+ . = ..()
+ wires = new /datum/wires/autolathe(src)
+
+/obj/machinery/modular_fabricator/autolathe/ui_interact(mob/user, datum/tgui/ui = null)
+ if(!is_operational())
+ return
+
+ if(shocked && !(stat & NOPOWER))
+ shock(user,50)
+
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ModularFabricator")
+ ui.open()
+ ui.set_autoupdate(TRUE)
+ viewing_mobs += user
+
+/obj/machinery/modular_fabricator/autolathe/ui_data(mob/user)
+ var/list/data = ..()
+
+ //Security interface
+ data["sec_interface_unlock"] = !security_interface_locked
+ data["hacked"] = hacked
+
+ //Being Build
+ return data
+
+/obj/machinery/modular_fabricator/autolathe/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("toggle_safety")
+ if(security_interface_locked)
+ return
+ adjust_hacked(!hacked)
+ . = TRUE
+
+ if("toggle_lock")
+ if(obj_flags & EMAGGED)
+ return
+ security_interface_locked = TRUE
+ . = TRUE
+
+/obj/machinery/modular_fabricator/autolathe/attackby(obj/item/O, mob/user, params)
+
+ if((ACCESS_SECURITY in O.GetAccess()) && !(obj_flags & EMAGGED))
+ security_interface_locked = !security_interface_locked
+ to_chat(user, "You [security_interface_locked?"lock":"unlock"] the security controls of [src].")
+ return TRUE
+
+ if (busy)
+ to_chat(user, "The autolathe is busy. Please wait for completion of previous operation.")
+ return TRUE
+
+ if(default_deconstruction_screwdriver(user, "autolathe_t", "autolathe", O))
+ return TRUE
+
+ if(default_deconstruction_crowbar(O))
+ return TRUE
+
+ if(panel_open && is_wire_tool(O))
+ wires.interact(user)
+ return TRUE
+
+ if(user.a_intent == INTENT_HARM) //so we can hit the machine
+ return ..()
+
+ if(stat)
+ return TRUE
+
+ if(istype(O, /obj/item/disk/design_disk))
+ user.visible_message("[user] loads \the [O] into \the [src]...",
+ "You load a design from \the [O]...",
+ "You hear the chatter of a floppy drive.")
+ inserted_disk = O
+ O.forceMove(src)
+ update_viewer_statics()
+ return TRUE
+
+ return ..()
+
+/obj/machinery/modular_fabricator/autolathe/proc/reset(wire)
+ switch(wire)
+ if(WIRE_HACK)
+ if(!wires.is_cut(wire))
+ adjust_hacked(FALSE)
+ if(WIRE_SHOCK)
+ if(!wires.is_cut(wire))
+ shocked = FALSE
+ if(WIRE_DISABLE)
+ if(!wires.is_cut(wire))
+ disabled = FALSE
+ wires.ui_update()
+
+/obj/machinery/modular_fabricator/autolathe/proc/shock(mob/user, prb)
+ if(stat & (BROKEN|NOPOWER)) // unpowered, no shock
+ return FALSE
+ if(!prob(prb))
+ return FALSE
+ var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
+ s.set_up(5, 1, src)
+ s.start()
+ if (electrocute_mob(user, get_area(src), src, 0.7, TRUE))
+ return TRUE
+ else
+ return FALSE
+
+/obj/machinery/modular_fabricator/autolathe/proc/adjust_hacked(state)
+ hacked = state
+ for(var/id in SSresearch.techweb_designs)
+ var/datum/design/D = SSresearch.techweb_design_by_id(id)
+ if((D.build_type & AUTOLATHE) && ("hacked" in D.category))
+ if(hacked)
+ stored_research.add_design(D)
+ else
+ stored_research.remove_design(D)
+ update_viewer_statics()
+ wires.ui_update()
+
+/obj/machinery/modular_fabricator/autolathe/emag_act(mob/user)
+ if(obj_flags & EMAGGED)
+ return
+ security_interface_locked = FALSE
+ adjust_hacked(TRUE)
+ playsound(src, "sparks", 100, 1)
+ obj_flags |= EMAGGED
+
+/obj/machinery/modular_fabricator/autolathe/hacked/Initialize()
+ . = ..()
+ adjust_hacked(TRUE)
+
+/obj/machinery/modular_fabricator/autolathe/AfterMaterialInsert(type_inserted, id_inserted, amount_inserted)
+ . = ..()
+ switch(id_inserted)
+ if (/datum/material/iron)
+ flick("autolathe_o",src)//plays metal insertion animation
+ if(/datum/material/copper)
+ flick("autolathe_c",src)//plays metal insertion animation
+ else
+ flick("autolathe_r",src)//plays glass insertion animation by default otherwise
+
+/obj/machinery/modular_fabricator/autolathe/set_default_sprite()
+ icon_state = "autolathe"
+
+/obj/machinery/modular_fabricator/autolathe/set_working_sprite()
+ icon_state = "autolathe_n"
diff --git a/code/game/machinery/fabricators/exosuit_fab.dm b/code/game/machinery/fabricators/exosuit_fab.dm
new file mode 100644
index 0000000000000..aa1fad5638971
--- /dev/null
+++ b/code/game/machinery/fabricators/exosuit_fab.dm
@@ -0,0 +1,68 @@
+/obj/machinery/modular_fabricator/exosuit_fab
+ icon = 'icons/obj/robotics.dmi'
+ icon_state = "fab-idle"
+ name = "exosuit fabricator"
+ desc = "An advanced machine containing many internal robotic arms which fabricate components for robots and exosuits."
+ density = TRUE
+ use_power = IDLE_POWER_USE
+ idle_power_usage = 20
+ active_power_usage = 5000
+ req_access = list(ACCESS_ROBOTICS)
+ circuit = /obj/item/circuitboard/machine/mechfab
+
+ output_direction = SOUTH
+
+ remote_materials = TRUE
+ can_sync = TRUE
+ can_print_category = TRUE
+
+ categories = list(
+ "Cyborg",
+ "Ripley",
+ "Firefighter",
+ "Odysseus",
+ "Gygax",
+ "Durand",
+ "H.O.N.K",
+ "Phazon",
+ "Exosuit Equipment",
+ "Cyborg Upgrade Modules",
+ "IPC Components",
+ "Cybernetics",
+ "Implants",
+ "Control Interfaces",
+ "Misc"
+ )
+
+ stored_research_type = /datum/techweb/specialized/autounlocking/exofab
+
+/obj/machinery/modular_fabricator/exosuit_fab/screwdriver_act(mob/living/user, obj/item/I)
+ if(..())
+ return TRUE
+ if(being_built)
+ to_chat(user, "\The [src] is currently processing! Please wait until completion.")
+ return FALSE
+ return default_deconstruction_screwdriver(user, "fab-o", "fab-idle", I)
+
+/obj/machinery/modular_fabricator/exosuit_fab/crowbar_act(mob/living/user, obj/item/I)
+ if(..())
+ return TRUE
+ if(being_built)
+ to_chat(user, "\The [src] is currently processing! Please wait until completion.")
+ return FALSE
+ return default_deconstruction_crowbar(I)
+
+/obj/machinery/modular_fabricator/exosuit_fab/AfterMaterialInsert(type_inserted, id_inserted, amount_inserted)
+ . = ..()
+ var/datum/material/M = id_inserted
+ add_overlay("fab-load-[M.name]")
+ addtimer(CALLBACK(src, /atom/proc/cut_overlay, "fab-load-[M.name]"), 10)
+
+/obj/machinery/modular_fabricator/exosuit_fab/set_default_sprite()
+ cut_overlay("fab-active")
+
+/obj/machinery/modular_fabricator/exosuit_fab/set_working_sprite()
+ add_overlay("fab-active")
+
+/obj/machinery/modular_fabricator/exosuit_fab/maint
+ auto_link = FALSE
diff --git a/code/game/machinery/fabricators/modular_fabricator.dm b/code/game/machinery/fabricators/modular_fabricator.dm
new file mode 100644
index 0000000000000..268576a2eb703
--- /dev/null
+++ b/code/game/machinery/fabricators/modular_fabricator.dm
@@ -0,0 +1,499 @@
+#define MODFAB_MAX_POWER_USE 2000
+
+/obj/machinery/modular_fabricator
+ name = "modular fabricator"
+ desc = "It produces items using iron, copper, and glass."
+ icon_state = "autolathe"
+ density = TRUE
+ use_power = IDLE_POWER_USE
+ idle_power_usage = 10
+ active_power_usage = 100
+ layer = BELOW_OBJ_LAYER
+
+ var/operating = FALSE
+ var/wants_operate = FALSE
+ var/disabled = 0
+
+ var/busy = FALSE
+ ///the multiplier for how much materials the created object takes from this machines stored materials
+ var/creation_efficiency = 1.6
+
+ var/can_sync = FALSE
+ var/can_be_hacked_or_unlocked = FALSE
+ var/can_print_category = FALSE
+
+ var/datum/design/being_built
+ var/process_completion_world_tick = 0
+ var/total_build_time = 0
+ var/datum/techweb/stored_research
+
+ var/list/categories = list(
+ "Tools",
+ "Electronics",
+ "Construction",
+ "T-Comm",
+ "Security",
+ "Machinery",
+ "Medical",
+ "Misc",
+ "Dinnerware",
+ "Imported"
+ )
+
+ var/output_direction = 0
+ var/accepts_disks = FALSE
+ var/obj/item/disk/design_disk/inserted_disk
+
+ var/remote_materials = FALSE
+ var/auto_link = FALSE
+
+ //A list of all the printable items
+
+ //Queue items
+
+ //Viewing mobs of the UI to update
+ var/list/mob/viewing_mobs = list()
+ //Associative list: item_queue[design_id] = list("amount" = int, "repeating" = bool, "build_mat" = something)
+ //The items in the item queue
+ var/list/item_queue = list()
+ //If true, once an item is processed it will be stuck right back on again
+ var/queue_repeating = FALSE
+ //The amount to readd to the queue when processing is done
+ var/stored_item_amount
+ //Minimum construction time per component
+ var/minimum_construction_time = 35
+
+ var/stored_research_type = /datum/techweb/specialized/autounlocking/autolathe
+
+/obj/machinery/modular_fabricator/Initialize(mapload)
+ if(remote_materials)
+ AddComponent(/datum/component/remote_materials, "modfab", mapload, TRUE, auto_link)
+ else
+ AddComponent(/datum/component/material_container, list(/datum/material/iron, /datum/material/glass, /datum/material/copper, /datum/material/gold, /datum/material/gold, /datum/material/silver, /datum/material/diamond, /datum/material/uranium, /datum/material/plasma, /datum/material/bluespace, /datum/material/bananium, /datum/material/titanium), 0, TRUE, null, null, CALLBACK(src, .proc/AfterMaterialInsert))
+ . = ..()
+ stored_research = new stored_research_type
+
+/obj/machinery/modular_fabricator/Destroy()
+ QDEL_NULL(wires)
+ return ..()
+
+/obj/machinery/modular_fabricator/proc/get_material_container()
+ var/datum/component/remote_materials/materials = GetComponent(/datum/component/remote_materials)
+ if(materials?.mat_container)
+ return materials.mat_container
+ var/datum/component/material_container/container = GetComponent(/datum/component/material_container)
+ return container
+
+/obj/machinery/modular_fabricator/RefreshParts()
+ var/mat_capacity = 0
+ for(var/obj/item/stock_parts/matter_bin/new_matter_bin in component_parts)
+ mat_capacity += new_matter_bin.rating*75000
+ //Material container
+ var/datum/component/remote_materials/materials = GetComponent(/datum/component/remote_materials)
+ if(materials)
+ materials.set_local_size(mat_capacity)
+ else
+ var/datum/component/material_container/container = GetComponent(/datum/component/material_container)
+ container.max_amount = mat_capacity
+
+ var/efficiency = 1.8
+ for(var/obj/item/stock_parts/manipulator/new_manipulator in component_parts)
+ efficiency -= new_manipulator.rating*0.2
+ creation_efficiency = max(1,efficiency) // creation_efficiency goes 1.6 -> 1.4 -> 1.2 -> 1 per level of manipulator efficiency
+
+/obj/machinery/modular_fabricator/examine(mob/user)
+ . += ..()
+ var/datum/component/material_container/materials = get_material_container()
+ if(in_range(user, src) || isobserver(user))
+ . += "The status display reads: Storing up to [materials.max_amount] material units. Material consumption at [creation_efficiency*100]%."
+
+/obj/machinery/modular_fabricator/ui_state()
+ return GLOB.default_state
+
+/obj/machinery/modular_fabricator/ui_interact(mob/user, datum/tgui/ui = null)
+ if(!is_operational())
+ return
+
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ModularFabricator")
+ ui.open()
+ ui.set_autoupdate(TRUE)
+ viewing_mobs += user
+
+/obj/machinery/modular_fabricator/ui_close(mob/user, datum/tgui/tgui)
+ . = ..()
+ viewing_mobs -= user
+
+/obj/machinery/modular_fabricator/ui_static_data(mob/user)
+ var/list/data = list()
+ data["acceptsDisk"] = accepts_disks
+ data["show_unlock_bar"] = can_be_hacked_or_unlocked
+ data["allow_add_category"] = can_print_category
+ data["can_sync"] = can_sync
+
+ //Items
+ data["items"] = list()
+ var/list/categories_associative = list()
+ for(var/v in stored_research.researched_designs)
+ var/datum/design/D = SSresearch.techweb_design_by_id(v)
+ for(var/cat in D.category)
+ //Check if printable
+ if(!(cat in categories))
+ continue
+ if(!islist(categories_associative[cat]))
+ categories_associative[cat] = list()
+
+ //Calculate cost
+ var/list/material_cost = list()
+ for(var/material_id in D.materials)
+ material_cost += list(list(
+ "name" = material_id,
+ "amount" = D.materials[material_id] / MINERAL_MATERIAL_AMOUNT,
+ ))
+
+ //Add
+ categories_associative[cat] += list(list(
+ "name" = D.name,
+ "design_id" = D.id,
+ "material_cost" = material_cost,
+ ))
+
+ //Categories and their items
+ for(var/category in categories_associative)
+ data["items"] += list(list(
+ "category_name" = category,
+ "category_items" = categories_associative[category],
+ ))
+
+ //Inserted data disk
+ data["diskInserted"] = inserted_disk
+ return data
+
+/obj/machinery/modular_fabricator/ui_data(mob/user)
+ var/list/data = list()
+ //Output direction
+ data["outputDir"] = output_direction
+
+ //Queue
+ data["queue"] = list()
+
+ //Real queue at the bottom
+ for(var/item_design_id in item_queue)
+ var/datum/design/D = SSresearch.techweb_design_by_id(item_design_id)
+ var/list/additional_data = item_queue[item_design_id]
+ data["queue"] += list(list(
+ "name" = D.name,
+ "amount" = additional_data["amount"],
+ "repeat" = additional_data["repeating"],
+ "design_id" = item_design_id,
+ ))
+
+ //Materials
+ data["materials"] = list()
+ var/datum/component/material_container/materials = get_material_container()
+ for(var/material in materials.materials)
+ var/datum/material/M = material
+ var/mineral_amount = materials.materials[material] / MINERAL_MATERIAL_AMOUNT
+ data["materials"] += list(list(
+ "name" = M.name,
+ "amount" = mineral_amount,
+ "datum" = M.type
+ ))
+
+ //Thing being made
+ if(being_built && total_build_time && process_completion_world_tick)
+ data["being_build"] = list(
+ "design_id" = being_built.id,
+ "name" = being_built.name,
+ "progress" = 100-(100*((process_completion_world_tick - world.time)/total_build_time)),
+ )
+ else
+ data["being_build"] = null
+
+ //Being Build
+ return data
+
+/obj/machinery/modular_fabricator/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+
+ if("resync_rd")
+ if(!can_sync)
+ return
+ resync_research()
+ . = TRUE
+
+ if("queue_category")
+ if(!can_print_category)
+ return
+ var/category_to_queue = params["category_name"]
+ for(var/v in stored_research.researched_designs)
+ var/datum/design/D = SSresearch.techweb_design_by_id(v)
+ if(category_to_queue in D.category)
+ add_to_queue(item_queue, v, 1)
+
+ if("output_dir")
+ output_direction = text2num(params["direction"])
+ . = TRUE
+
+ if("upload_disk")
+ if(!accepts_disks)
+ return
+ var/obj/item/disk/design_disk/D = inserted_disk
+ if(!istype(D))
+ return
+ for(var/B in D.blueprints)
+ if(B)
+ stored_research.add_design(B)
+ update_viewer_statics()
+ . = TRUE
+
+ if("eject_disk")
+ if(!inserted_disk || !accepts_disks)
+ return
+ var/obj/item/disk/design_disk/disk = inserted_disk
+ disk.forceMove(get_turf(src))
+ inserted_disk = null
+ update_viewer_statics()
+ . = TRUE
+
+ if("eject_material")
+ var/datum/component/material_container/materials = get_material_container()
+ var/material_datum = params["material_datum"] //Comes out as text
+ var/amount = text2num(params["amount"])
+ if(amount <= 0 || amount > 50)
+ return
+ for(var/mat in materials.materials)
+ var/datum/material/M = mat
+ if("[M.type]" == material_datum)
+ materials.retrieve_sheets(amount, M, get_release_turf())
+ . = TRUE
+ break
+
+ if("queue_repeat")
+ queue_repeating = text2num(params["repeating"])
+ . = TRUE
+
+ if("clear_queue")
+ item_queue.Cut()
+ . = TRUE
+
+ if("item_repeat")
+ var/design_id = params["design_id"]
+ var/repeating_mode = text2num(params["repeating"])
+ if(!item_queue["[design_id]"])
+ return
+ item_queue["[design_id]"]["repeating"] = repeating_mode
+ . = TRUE
+
+ if("clear_item")
+ var/design_id = params["design_id"]
+ item_queue -= design_id
+ . = TRUE
+
+ if("queue_item")
+ var/design_id = params["design_id"]
+ var/amount = text2num(params["amount"])
+ add_to_queue(item_queue, design_id, amount)
+ . = TRUE
+
+ if("begin_process")
+ begin_process()
+ . = TRUE
+
+/obj/machinery/modular_fabricator/proc/resync_research()
+ for(var/obj/machinery/computer/rdconsole/RDC in orange(7, src))
+ RDC.stored_research.copy_research_to(stored_research)
+ update_viewer_statics()
+ say("Successfully synchronized with R&D server.")
+ return
+
+/obj/machinery/modular_fabricator/proc/update_viewer_statics()
+ for(var/mob/M in viewing_mobs)
+ if(QDELETED(M) || !(M.client || M.mind))
+ continue
+ update_static_data(M)
+
+/obj/machinery/modular_fabricator/proc/add_to_queue(queue_list, design_id, amount, repeat=null)
+ if(queue_list["[design_id]"])
+ queue_list["[design_id]"]["amount"] += amount
+ if(queue_list["[design_id]"]["amount"] <= 0)
+ queue_list -= "[design_id]"
+ return
+ if(amount <= 0)
+ return
+ //Check if the item uses custom materials
+ var/datum/design/requested_item = stored_research.isDesignResearchedID(design_id)
+ var/datum/material/used_material = repeat
+ if(!istype(used_material))
+ for(var/MAT in requested_item.materials)
+ used_material = MAT
+ if(istext(used_material)) //This means its a category
+ var/datum/component/material_container/materials = get_material_container()
+ var/list/list_to_show = list()
+ for(var/i in SSmaterials.materials_by_category[used_material])
+ if(materials.materials[i] > 0)
+ list_to_show += i
+ used_material = input("Choose [used_material]", "Custom Material") as null|anything in sortList(list_to_show, /proc/cmp_typepaths_asc)
+ if(!used_material)
+ return //Didn't pick any material, so you can't build shit either.
+
+ queue_list["[design_id]"] = list(
+ "amount" = amount,
+ "repeating" = repeat,
+ "build_mat" = used_material,
+ )
+
+/obj/machinery/modular_fabricator/proc/get_release_turf()
+ var/turf/T
+ if(output_direction)
+ T = get_step(src, output_direction)
+ if(is_blocked_turf(T, TRUE))
+ T = get_turf(src)
+ else
+ T = get_turf(src)
+ return T
+
+/obj/machinery/modular_fabricator/on_deconstruction()
+ var/datum/component/material_container/materials = get_material_container()
+ materials.retrieve_all()
+
+/obj/machinery/modular_fabricator/proc/AfterMaterialInsert(type_inserted, id_inserted, amount_inserted)
+ if(ispath(type_inserted, /obj/item/stack/ore/bluespace_crystal))
+ use_power(MINERAL_MATERIAL_AMOUNT / 10)
+ else
+ use_power(min(1000, amount_inserted / 100))
+ //Begin processing to continue the queue if we had items in the queue
+ if(wants_operate)
+ begin_process()
+
+/obj/machinery/modular_fabricator/proc/begin_process()
+ if(busy || operating || disabled)
+ return
+ var/requested_design_id = null
+ if(LAZYLEN(item_queue))
+ requested_design_id = item_queue[1]
+ //Queue processing done
+ if(!requested_design_id)
+ say("Queue processing completed.")
+ operating = FALSE
+ return
+ operating = TRUE
+ //Doubles as protection from bad things and makes sure we can still make the item.
+ being_built = stored_research.isDesignResearchedID(requested_design_id)
+ if(!being_built)
+ playsound(src, 'sound/machines/buzz-two.ogg', 50)
+ say("Unknown design requested, removing from queue.")
+ item_queue -= requested_design_id
+ addtimer(CALLBACK(src, .proc/restart_process), 50)
+ return
+
+ var/multiplier = 1
+ var/is_stack = ispath(being_built.build_path, /obj/item/stack)
+ //Only items that can stack should be build en mass, since we now have queues.
+ if(is_stack)
+ multiplier = item_queue[requested_design_id]["amount"]
+ multiplier = CLAMP(multiplier,1,50)
+
+ /////////////////
+
+ var/coeff = (is_stack ? 1 : creation_efficiency) //stacks are unaffected by production coefficient
+ var/total_amount = 0
+
+ for(var/MAT in being_built.materials)
+ total_amount += being_built.materials[MAT]
+
+ var/power = max(MODFAB_MAX_POWER_USE, (total_amount)*multiplier/5) //Change this to use all materials
+
+ var/datum/component/material_container/materials = get_material_container()
+
+ var/list/materials_used = list()
+ var/list/custom_materials = list() //These will apply their material effect, This should usually only be one.
+
+ for(var/MAT in being_built.materials)
+ var/datum/material/used_material = MAT
+ var/amount_needed = being_built.materials[MAT] * coeff * multiplier
+ if(istext(used_material)) //This means its a category
+ used_material = item_queue[requested_design_id]["build_mat"]
+ if(!used_material)
+ item_queue -= requested_design_id
+ addtimer(CALLBACK(src, .proc/restart_process), 50)
+ return //Didn't pick any material, so you can't build shit either.
+ custom_materials[used_material] += amount_needed
+
+ materials_used[used_material] = amount_needed
+
+ if(materials.has_materials(materials_used))
+ busy = TRUE
+ use_power(power)
+ set_working_sprite()
+ var/construction_time = max(being_built.construction_time, minimum_construction_time)
+ var/time = is_stack ? construction_time : (construction_time * coeff * multiplier) ** 0.8
+ time *= being_built.lathe_time_factor
+ //===Repeating mode===
+ //Remove from queue
+ var/list/queue_data = item_queue[requested_design_id]
+ item_queue[requested_design_id]["amount"] -= multiplier
+ var/removed = FALSE
+ if(item_queue[requested_design_id]["amount"] <= 0)
+ item_queue -= requested_design_id
+ removed = TRUE
+ //Requeue if necessary
+ if(queue_repeating || queue_data["repdeating"])
+ stored_item_amount ++
+ if(removed)
+ add_to_queue(item_queue, requested_design_id, stored_item_amount, queue_data["build_mat"])
+ stored_item_amount = 0
+ //Create item and restart
+ process_completion_world_tick = world.time + time
+ total_build_time = time
+ addtimer(CALLBACK(src, .proc/make_item, power, materials_used, custom_materials, multiplier, coeff, is_stack), time)
+ addtimer(CALLBACK(src, .proc/restart_process), time + 5)
+ else
+ say("Insufficient materials, operation will proceed when sufficient materials are available.")
+ operating = FALSE
+ wants_operate = TRUE
+
+/obj/machinery/modular_fabricator/proc/restart_process()
+ operating = FALSE
+ wants_operate = FALSE
+ if(disabled)
+ return
+ begin_process()
+
+/obj/machinery/modular_fabricator/proc/make_item(power, var/list/materials_used, var/list/picked_materials, multiplier, coeff, is_stack)
+ if(QDELETED(src))
+ return
+ //Stops the queue
+ if(disabled)
+ operating = FALSE
+ return
+ var/datum/component/material_container/materials = get_material_container()
+ var/turf/A = get_release_turf()
+ use_power(power)
+ materials.use_materials(materials_used)
+ if(is_stack)
+ var/obj/item/stack/N = new being_built.build_path(A, multiplier)
+ N.update_icon()
+ else
+ for(var/i=1, i<=multiplier, i++)
+ var/obj/item/new_item = new being_built.build_path(A)
+ new_item.materials = new_item.materials.Copy()
+ for(var/mat in materials_used)
+ new_item.materials[mat] = materials_used[mat] / multiplier
+
+ if(length(picked_materials))
+ new_item.set_custom_materials(picked_materials, 1 / multiplier) //Ensure we get the non multiplied amount
+ being_built = null
+ set_default_sprite()
+ busy = FALSE
+
+/obj/machinery/modular_fabricator/proc/set_default_sprite()
+ return
+
+/obj/machinery/modular_fabricator/proc/set_working_sprite()
+ return
diff --git a/code/game/machinery/fat_sucker.dm b/code/game/machinery/fat_sucker.dm
index 9cdc2a876d210..87047cb2fc86c 100644
--- a/code/game/machinery/fat_sucker.dm
+++ b/code/game/machinery/fat_sucker.dm
@@ -12,7 +12,7 @@
var/start_at = NUTRITION_LEVEL_WELL_FED
var/stop_at = NUTRITION_LEVEL_STARVING
var/free_exit = TRUE //set to false to prevent people from exiting before being completely stripped of fat
- var/bite_size = 15 //amount of nutrients we take per process
+ var/bite_size = 7.5 //amount of nutrients we take per second
var/nutrients //amount of nutrients we got build up
var/nutrient_to_meat = 90 //one slab of meat gives about 52 nutrition
var/datum/looping_sound/microwave/soundloop //100% stolen from microwaves
@@ -33,6 +33,10 @@
soundloop = new(list(src), FALSE)
update_icon()
+/obj/machinery/fat_sucker/Destroy()
+ QDEL_NULL(soundloop)
+ return ..()
+
/obj/machinery/fat_sucker/RefreshParts()
..()
var/rating = 0
@@ -46,9 +50,9 @@
/obj/machinery/fat_sucker/examine(mob/user)
. = ..()
- . += {"Alt-Click to toggle the safety hatch.
- Removing [bite_size] nutritional units per operation.
- Requires [nutrient_to_meat] nutritional units per meat slab."}
+ . += "Alt-Click to toggle the safety hatch.\n"+\
+ "Removing [bite_size] nutritional units per operation.\n"+\
+ "Requires [nutrient_to_meat] nutritional units per meat slab."
/obj/machinery/fat_sucker/close_machine(mob/user)
if(panel_open)
@@ -131,10 +135,10 @@
if(panel_open)
overlays += "[icon_state]_panel"
-/obj/machinery/fat_sucker/process()
+/obj/machinery/fat_sucker/process(delta_time)
if(!processing)
return
- if(!powered() || !occupant || !iscarbon(occupant))
+ if(!is_operational() || !occupant || !iscarbon(occupant))
open_machine()
return
@@ -143,8 +147,8 @@
open_machine()
playsound(src, 'sound/machines/microwave/microwave-end.ogg', 100, FALSE)
return
- C.adjust_nutrition(-bite_size)
- nutrients += bite_size
+ C.adjust_nutrition(-bite_size * delta_time)
+ nutrients += bite_size * delta_time
if(next_fact <= 0)
next_fact = initial(next_fact)
@@ -155,7 +159,7 @@
use_power(500)
/obj/machinery/fat_sucker/proc/start_extracting()
- if(state_open || !occupant || processing || !powered())
+ if(state_open || !occupant || processing || !is_operational())
return
if(iscarbon(occupant))
var/mob/living/carbon/C = occupant
diff --git a/code/game/machinery/firealarm.dm b/code/game/machinery/firealarm.dm
index 1e5b2b4f71726..80e1a60977fa6 100644
--- a/code/game/machinery/firealarm.dm
+++ b/code/game/machinery/firealarm.dm
@@ -19,12 +19,13 @@
icon_state = "fire0"
max_integrity = 250
integrity_failure = 100
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30, "stamina" = 0)
use_power = IDLE_POWER_USE
idle_power_usage = 2
active_power_usage = 6
power_channel = AREA_USAGE_ENVIRON
resistance_flags = FIRE_PROOF
+ layer = ABOVE_WINDOW_LAYER
light_power = 0
light_range = 7
@@ -49,6 +50,7 @@
LAZYADD(myarea.firealarms, src)
/obj/machinery/firealarm/Destroy()
+ myarea.firereset(src)
LAZYREMOVE(myarea.firealarms, src)
return ..()
@@ -77,23 +79,28 @@
if(is_station_level(z))
add_overlay("fire_[GLOB.security_level]")
- SSvis_overlays.add_vis_overlay(src, icon, "fire_[GLOB.security_level]", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "fire_[GLOB.security_level]", layer, plane, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "fire_[GLOB.security_level]", layer, EMISSIVE_PLANE, dir)
else
add_overlay("fire_[SEC_LEVEL_GREEN]")
- SSvis_overlays.add_vis_overlay(src, icon, "fire_[SEC_LEVEL_GREEN]", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "fire_[SEC_LEVEL_GREEN]", layer, plane, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "fire_[SEC_LEVEL_GREEN]", layer, EMISSIVE_PLANE, dir)
var/area/A = src.loc
A = A.loc
if(!detecting || !A.fire)
add_overlay("fire_off")
- SSvis_overlays.add_vis_overlay(src, icon, "fire_off", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "fire_off", layer, plane, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "fire_off", layer, EMISSIVE_PLANE, dir)
else if(obj_flags & EMAGGED)
add_overlay("fire_emagged")
- SSvis_overlays.add_vis_overlay(src, icon, "fire_emagged", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "fire_emagged", layer, plane, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "fire_emagged", layer, EMISSIVE_PLANE, dir)
else
add_overlay("fire_on")
- SSvis_overlays.add_vis_overlay(src, icon, "fire_on", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "fire_on", layer, plane, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "fire_on", layer, EMISSIVE_PLANE, dir)
/obj/machinery/firealarm/emp_act(severity)
. = ..()
@@ -109,11 +116,16 @@
return
obj_flags |= EMAGGED
update_icon()
- if(user)
- user.visible_message("Sparks fly out of [src]!",
+ user?.visible_message("Sparks fly out of [src]!",
"You emag [src], disabling its thermal sensors.")
playsound(src, "sparks", 50, 1)
+/obj/machinery/firealarm/eminence_act(mob/living/simple_animal/eminence/eminence)
+ . = ..()
+ to_chat(usr, "You begin manipulating [src]!")
+ if(do_after(eminence, 20, target=get_turf(eminence)))
+ attack_hand(eminence)
+
/obj/machinery/firealarm/temperature_expose(datum/gas_mixture/air, temperature, volume)
if((temperature > T0C + 200 || temperature < BODYTEMP_COLD_DAMAGE_LIMIT) && (last_alarm+FIREALARM_COOLDOWN < world.time) && !(obj_flags & EMAGGED) && detecting && !stat)
alarm()
@@ -258,6 +270,20 @@
return ..()
+/obj/machinery/firealarm/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
+ if((buildstage == 0) && (the_rcd.upgrade & RCD_UPGRADE_SIMPLE_CIRCUITS))
+ return list("mode" = RCD_UPGRADE_SIMPLE_CIRCUITS, "delay" = 20, "cost" = 1)
+ return FALSE
+
+/obj/machinery/firealarm/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode)
+ switch(passed_mode)
+ if(RCD_UPGRADE_SIMPLE_CIRCUITS)
+ user.visible_message("[user] fabricates a circuit and places it into [src].", \
+ "You adapt a fire alarm circuit and slot it into the assembly.")
+ buildstage = 1
+ update_icon()
+ return TRUE
+ return FALSE
/obj/machinery/firealarm/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir)
. = ..()
diff --git a/code/game/machinery/flasher.dm b/code/game/machinery/flasher.dm
index 80934569f6d5e..e426f799cfe36 100644
--- a/code/game/machinery/flasher.dm
+++ b/code/game/machinery/flasher.dm
@@ -9,6 +9,7 @@
integrity_failure = 100
light_color = LIGHT_COLOR_WHITE
light_power = FLASH_LIGHT_POWER
+ layer = ABOVE_WINDOW_LAYER
var/obj/item/assembly/flash/handheld/bulb
var/id = null
var/range = 2 //this is roughly the size of brig cell
@@ -87,6 +88,13 @@
if (anchored)
return flash()
+/obj/machinery/flasher/eminence_act(mob/living/simple_animal/eminence/eminence)
+ . = ..()
+ to_chat(usr, "You begin manipulating [src]!")
+ if(do_after(eminence, 20, target=get_turf(eminence)))
+ if(anchored)
+ flash()
+
/obj/machinery/flasher/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir)
if(damage_flag == "melee" && damage_amount < 10) //any melee attack below 10 dmg does nothing
return 0
@@ -109,10 +117,7 @@
last_flash = world.time
use_power(1000)
- for (var/mob/living/L in viewers(src, null))
- if (get_dist(src, L) > range)
- continue
-
+ for (var/mob/living/L in hearers(range, src))
if(L.flash_act(affect_silicon = 1))
L.Paralyze(strength)
diff --git a/code/game/machinery/gulag_item_reclaimer.dm b/code/game/machinery/gulag_item_reclaimer.dm
index 25ce7acbe75e3..db10532fc753c 100644
--- a/code/game/machinery/gulag_item_reclaimer.dm
+++ b/code/game/machinery/gulag_item_reclaimer.dm
@@ -26,6 +26,7 @@
return
req_access = list()
obj_flags |= EMAGGED
+ ui_update()
/obj/machinery/gulag_item_reclaimer/ui_state(mob/user)
diff --git a/code/game/machinery/gulag_teleporter.dm b/code/game/machinery/gulag_teleporter.dm
index f0e66d2a56e8a..da5f324990dea 100644
--- a/code/game/machinery/gulag_teleporter.dm
+++ b/code/game/machinery/gulag_teleporter.dm
@@ -30,7 +30,7 @@ The console is located at computer/gulag_teleporter.dm
/obj/item/clothing/head/helmet/space/plasmaman,
/obj/item/tank/internals,
/obj/item/clothing/mask/breath,
- /obj/item/clothing/mask/gas))
+ /obj/item/clothing/mask/gas/old)) //makes more sense to give prisoners older models of masks
/obj/machinery/gulag_teleporter/Initialize()
. = ..()
@@ -147,6 +147,8 @@ The console is located at computer/gulag_teleporter.dm
W.forceMove(linked_reclaimer)
else
W.forceMove(src)
+ if(linked_reclaimer)
+ linked_reclaimer.ui_update()
/obj/machinery/gulag_teleporter/proc/handle_prisoner(obj/item/id, datum/data/record/R)
if(!ishuman(occupant))
diff --git a/code/game/machinery/harvester.dm b/code/game/machinery/harvester.dm
index 53b8e730596f2..ae5a155cd6099 100644
--- a/code/game/machinery/harvester.dm
+++ b/code/game/machinery/harvester.dm
@@ -173,6 +173,7 @@
to_chat(user,"[src] is active and can't be opened!") //rip
/obj/machinery/harvester/Exited(atom/movable/user)
+ . = ..()
if (!state_open && user == occupant)
container_resist(user)
@@ -189,4 +190,4 @@
else if(!harvesting)
. += "Alt-click [src] to start harvesting."
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Harvest speed at [interval*0.1] seconds per organ."
+ . += "The status display reads: Harvest speed at [interval*0.1] seconds per organ."
diff --git a/code/game/machinery/hologram.dm b/code/game/machinery/hologram.dm
index 5a65e27ba2d8a..2ec60264eeafb 100644
--- a/code/game/machinery/hologram.dm
+++ b/code/game/machinery/hologram.dm
@@ -33,12 +33,11 @@ Possible to do for anyone motivated enough:
icon_state = "holopad0"
layer = LOW_OBJ_LAYER
plane = FLOOR_PLANE
- flags_1 = HEAR_1
use_power = IDLE_POWER_USE
idle_power_usage = 5
active_power_usage = 100
max_integrity = 300
- armor = list("melee" = 50, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0)
+ armor = list("melee" = 50, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0, "stamina" = 0)
circuit = /obj/item/circuitboard/machine/holopad
var/list/masters //List of living mobs that use the holopad
var/list/holorays //Holoray-mob link.
@@ -61,6 +60,10 @@ Possible to do for anyone motivated enough:
var/offset = FALSE
var/on_network = TRUE
+/obj/machinery/holopad/Initialize()
+ . = ..()
+ become_hearing_sensitive()
+
/obj/machinery/holopad/tutorial
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
flags_1 = NODECONSTRUCT_1
@@ -145,7 +148,7 @@ Possible to do for anyone motivated enough:
/obj/machinery/holopad/examine(mob/user)
. = ..()
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Current projection range: [holo_range] units."
+ . += "The status display reads: Current projection range: [holo_range] units."
/obj/machinery/holopad/attackby(obj/item/P, mob/user, params)
if(default_deconstruction_screwdriver(user, "holopad_open", "holopad0", P))
@@ -410,17 +413,19 @@ Possible to do for anyone motivated enough:
/*This is the proc for special two-way communication between AI and holopad/people talking near holopad.
For the other part of the code, check silicon say.dm. Particularly robot talk.*/
-/obj/machinery/holopad/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode)
+/obj/machinery/holopad/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, list/message_mods = list())
. = ..()
if(speaker && LAZYLEN(masters) && !radio_freq)//Master is mostly a safety in case lag hits or something. Radio_freq so AIs dont hear holopad stuff through radios.
for(var/mob/living/silicon/ai/master in masters)
if(masters[master] && speaker != master)
- master.relay_speech(message, speaker, message_language, raw_message, radio_freq, spans, message_mode)
+ master.relay_speech(message, speaker, message_language, raw_message, radio_freq, spans, message_mods)
for(var/I in holo_calls)
var/datum/holocall/HC = I
if(HC.connected_holopad == src && speaker != HC.hologram)
- HC.user.Hear(message, speaker, message_language, raw_message, radio_freq, spans, message_mode)
+ HC.user.Hear(message, speaker, message_language, raw_message, radio_freq, spans, message_mods)
+ if(HC.user.should_show_chat_message(speaker, message_language, FALSE, is_heard = TRUE))
+ create_chat_message(speaker, message_language, list(HC.user), raw_message, spans, message_mods)
if(outgoing_call && speaker == outgoing_call.user)
outgoing_call.hologram.say(raw_message)
@@ -497,7 +502,7 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
//Can we display holos there
//Area check instead of line of sight check because this is a called a lot if AI wants to move around.
/obj/machinery/holopad/proc/validate_location(turf/T,check_los = FALSE)
- if(T.z == z && get_dist(T, src) <= holo_range && T.loc == get_area(src))
+ if(T.get_virtual_z_level() == get_virtual_z_level() && get_dist(T, src) <= holo_range && T.loc == get_area(src))
return TRUE
else
return FALSE
@@ -674,6 +679,7 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
Impersonation = null
if(!QDELETED(HC))
HC.Disconnect(HC.calling_holopad)
+ HC = null
return ..()
/obj/effect/overlay/holo_pad_hologram/Process_Spacemove(movement_dir = 0)
diff --git a/code/game/machinery/igniter.dm b/code/game/machinery/igniter.dm
index e2070fd5e8282..5b74d7d13633c 100644
--- a/code/game/machinery/igniter.dm
+++ b/code/game/machinery/igniter.dm
@@ -8,7 +8,7 @@
idle_power_usage = 2
active_power_usage = 4
max_integrity = 300
- armor = list("melee" = 50, "bullet" = 30, "laser" = 70, "energy" = 50, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70)
+ armor = list("melee" = 50, "bullet" = 30, "laser" = 70, "energy" = 50, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70, "stamina" = 0)
resistance_flags = FIRE_PROOF
var/id = null
var/on = FALSE
@@ -61,6 +61,7 @@
icon = 'icons/obj/stationobjs.dmi'
icon_state = "migniter"
resistance_flags = FIRE_PROOF
+ layer = ABOVE_WINDOW_LAYER
var/id = null
var/disable = 0
var/last_spark = 0
diff --git a/code/game/machinery/iv_drip.dm b/code/game/machinery/iv_drip.dm
index b32fcd57cd73b..91780a7ab82b7 100644
--- a/code/game/machinery/iv_drip.dm
+++ b/code/game/machinery/iv_drip.dm
@@ -14,6 +14,7 @@
var/static/list/drip_containers = typecacheof(list(/obj/item/reagent_containers/blood,
/obj/item/reagent_containers/food,
/obj/item/reagent_containers/glass))
+ var/can_convert = TRUE // If it can be made into an anesthetic machine or not
/obj/machinery/iv_drip/Initialize(mapload)
. = ..()
@@ -114,7 +115,7 @@
new /obj/item/stack/sheet/iron(loc)
qdel(src)
-/obj/machinery/iv_drip/process()
+/obj/machinery/iv_drip/process(delta_time)
if(!attached)
return PROCESS_KILL
@@ -132,8 +133,8 @@
var/transfer_amount = 5
if(istype(beaker, /obj/item/reagent_containers/blood))
// speed up transfer on blood packs
- transfer_amount = 10
- var/fraction = min(transfer_amount/beaker.reagents.total_volume, 1) //the fraction that is transfered of the total volume
+ transfer_amount *= 2
+ var/fraction = min(transfer_amount*delta_time/beaker.reagents.total_volume, 1) //the fraction that is transfered of the total volume
beaker.reagents.reaction(attached, INJECT, fraction, FALSE) //make reagents reacts, but don't spam messages
beaker.reagents.trans_to(attached, transfer_amount)
update_icon()
@@ -141,7 +142,7 @@
// Take blood
else
var/amount = beaker.reagents.maximum_volume - beaker.reagents.total_volume
- amount = min(amount, 4)
+ amount = min(amount, 4) * delta_time * 0.5
// If the beaker is full, ping
if(!amount)
if(prob(5))
@@ -162,7 +163,7 @@
if(!ishuman(user))
return
if(attached)
- visible_message("[attached] is detached from [src]")
+ visible_message("[attached] is detached from \the [src].")
attached = null
update_icon()
return
@@ -220,11 +221,28 @@
. += "[attached ? attached : "No one"] is attached."
+/obj/machinery/iv_drip/screwdriver_act(mob/living/user, obj/item/I)
+ . = ..()
+ if(user.is_holding_item_of_type(/obj/item/clothing/mask/breath) && can_convert)
+ visible_message("[user] attempts to attach the breath mask to [src].", "You attempt to attach the breath mask to [src].")
+ if(!do_after(user, 100, FALSE, src))
+ to_chat(user, "You fail to attach the breath mask to [src]!")
+ return
+ var/item = user.is_holding_item_of_type(/obj/item/clothing/mask/breath)
+ if(!item) // Check after the do_after as well
+ return
+ visible_message("[user] attaches the breath mask to [src].", "You attach the breath mask to [src].")
+ qdel(item)
+ new /obj/machinery/anesthetic_machine(loc)
+ qdel(src)
+
+
/obj/machinery/iv_drip/saline
name = "saline drip"
desc = "An all-you-can-drip saline canister designed to supply a hospital without running out, with a scary looking pump rigged to inject saline into containers, but filling people directly might be a bad idea."
icon_state = "saline"
density = TRUE
+ can_convert = FALSE
/obj/machinery/iv_drip/saline/Initialize(mapload)
. = ..()
diff --git a/code/game/machinery/launch_pad.dm b/code/game/machinery/launch_pad.dm
index afe87add691dd..35845c69f8e20 100644
--- a/code/game/machinery/launch_pad.dm
+++ b/code/game/machinery/launch_pad.dm
@@ -25,6 +25,8 @@
E += M.rating
range = initial(range)
range *= E
+ //Update to viewers
+ ui_update()
/obj/machinery/launchpad/Initialize()
. = ..()
@@ -43,7 +45,8 @@
update_indicator()
/obj/machinery/launchpad/Destroy()
- qdel(hud_list[DIAG_LAUNCHPAD_HUD])
+ for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds)
+ diag_hud.remove_from_hud(src)
return ..()
/obj/machinery/launchpad/examine(mob/user)
@@ -257,9 +260,9 @@
/obj/machinery/launchpad/briefcase/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/launchpad_remote))
var/obj/item/launchpad_remote/L = I
- if(L.pad == src) //do not attempt to link when already linked
+ if(L.pad == WEAKREF(src)) //do not attempt to link when already linked
return ..()
- L.pad = src
+ L.pad = WEAKREF(src)
to_chat(user, "You link [src] to [L].")
else
return ..()
@@ -296,9 +299,9 @@
/obj/item/storage/briefcase/launchpad/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/launchpad_remote))
var/obj/item/launchpad_remote/L = I
- if(L.pad == src.pad) //do not attempt to link when already linked
+ if(L.pad == WEAKREF(src.pad)) //do not attempt to link when already linked
return ..()
- L.pad = src.pad
+ L.pad = WEAKREF(src.pad)
to_chat(user, "You link [pad] to [L].")
else
return ..()
@@ -310,11 +313,12 @@
icon_state = "folder"
w_class = WEIGHT_CLASS_SMALL
var/sending = TRUE
- var/obj/machinery/launchpad/briefcase/pad
+ //A weakref to our linked pad
+ var/datum/weakref/pad
/obj/item/launchpad_remote/Initialize(mapload, pad) //remote spawns linked to the briefcase pad
. = ..()
- src.pad = pad
+ src.pad = WEAKREF(pad)
/obj/item/launchpad_remote/attack_self(mob/user)
. = ..()
@@ -329,22 +333,22 @@
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "LaunchpadRemote") //width, height
+ ui.set_autoupdate(TRUE) // Autoupdate because handling changes to launchpad would be hell unless I figure out and add a bunch of signals
ui.open()
- ui.set_autoupdate(TRUE)
-
/obj/item/launchpad_remote/ui_data(mob/user)
var/list/data = list()
- data["has_pad"] = pad ? TRUE : FALSE
- if(pad)
- data["pad_closed"] = pad.closed
- if(!pad || pad.closed)
+ var/obj/machinery/launchpad/briefcase/our_pad = pad?.resolve()
+ data["has_pad"] = our_pad ? TRUE : FALSE
+ if(our_pad)
+ data["pad_closed"] = our_pad.closed
+ if(!our_pad || our_pad.closed)
return data
- data["pad_name"] = pad.display_name
- data["range"] = pad.range
- data["x"] = pad.x_offset
- data["y"] = pad.y_offset
+ data["pad_name"] = our_pad.display_name
+ data["range"] = our_pad.range
+ data["x"] = our_pad.x_offset
+ data["y"] = our_pad.y_offset
return data
/obj/item/launchpad_remote/proc/teleport(mob/user, obj/machinery/launchpad/pad)
@@ -359,35 +363,39 @@
/obj/item/launchpad_remote/ui_act(action, params)
if(..())
return
+ var/obj/machinery/launchpad/briefcase/our_pad = pad?.resolve()
+ if(!our_pad)
+ pad = null
+ return TRUE
switch(action)
if("set_pos")
var/new_x = text2num(params["x"])
var/new_y = text2num(params["y"])
- pad.set_offset(new_x, new_y)
+ our_pad.set_offset(new_x, new_y)
. = TRUE
if("move_pos")
var/plus_x = text2num(params["x"])
var/plus_y = text2num(params["y"])
- pad.set_offset(
- x = pad.x_offset + plus_x,
- y = pad.y_offset + plus_y
+ our_pad.set_offset(
+ x = our_pad.x_offset + plus_x,
+ y = our_pad.y_offset + plus_y
)
. = TRUE
if("rename")
- . = TRUE
var/new_name = params["name"]
if(!new_name)
return
- pad.display_name = new_name
+ our_pad.display_name = new_name
+ . = TRUE
if("remove")
. = TRUE
if(usr && alert(usr, "Are you sure?", "Unlink Launchpad", "I'm Sure", "Abort") != "Abort")
pad = null
if("launch")
sending = TRUE
- teleport(usr, pad)
+ teleport(usr, our_pad)
. = TRUE
if("pull")
sending = FALSE
- teleport(usr, pad)
+ teleport(usr, our_pad)
. = TRUE
diff --git a/code/game/machinery/lightswitch.dm b/code/game/machinery/lightswitch.dm
index 47e2ea67ec549..04103fa8754b2 100644
--- a/code/game/machinery/lightswitch.dm
+++ b/code/game/machinery/lightswitch.dm
@@ -5,6 +5,7 @@
icon_state = "light1"
desc = "Make dark."
power_channel = AREA_USAGE_LIGHT
+ layer = ABOVE_WINDOW_LAYER
/// Set this to a string, path, or area instance to control that area
/// instead of the switch's location.
var/area/area = null
@@ -62,3 +63,9 @@
return
if(!(stat & (BROKEN|NOPOWER)))
power_change()
+
+/obj/machinery/light_switch/eminence_act(mob/living/simple_animal/eminence/eminence)
+ . = ..()
+ to_chat(usr, "You begin manipulating [src]!")
+ if(do_after(eminence, 20, target=get_turf(eminence)))
+ interact(eminence)
diff --git a/code/game/machinery/limbgrower.dm b/code/game/machinery/limbgrower.dm
index 4cbafeb6bc04c..fbf3478b37fe8 100644
--- a/code/game/machinery/limbgrower.dm
+++ b/code/game/machinery/limbgrower.dm
@@ -124,7 +124,7 @@
//Just build whatever it is
new buildpath(loc)
else
- src.visible_message(" Something went very wrong and there isnt enough synthflesh anymore!")
+ src.visible_message(" Something went very wrong and there isn't enough synthflesh anymore!")
busy = FALSE
flick("limbgrower_unfill",src)
icon_state = "limbgrower_idleoff"
@@ -159,7 +159,7 @@
/obj/machinery/limbgrower/examine(mob/user)
. = ..()
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Storing up to [reagents.maximum_volume]u of synthflesh. Synthflesh consumption at [prod_coeff*100]%."
+ . += "The status display reads: Storing up to [reagents.maximum_volume]u of synthflesh. Synthflesh consumption at [prod_coeff*100]%."
/obj/machinery/limbgrower/proc/main_win(mob/user)
var/dat = "
Limb Grower Menu:
"
diff --git a/code/game/machinery/mass_driver.dm b/code/game/machinery/mass_driver.dm
index e5df2c29d18a1..382014fe3c1e6 100644
--- a/code/game/machinery/mass_driver.dm
+++ b/code/game/machinery/mass_driver.dm
@@ -15,18 +15,18 @@
/obj/machinery/mass_driver/proc/drive(amount)
if(stat & (BROKEN|NOPOWER))
return
- use_power(500)
+ use_power(1000)
var/O_limit
var/atom/target = get_edge_target_turf(src, dir)
for(var/atom/movable/O in loc)
- if(!O.anchored || ismecha(O)) //Mechs need their launch platforms.
+ if(!O.anchored || istype(O, /obj/machinery/power/supermatter_crystal) || ismecha(O)) //Mechs need their launch platforms. Oh, and SM cannon SM cannon.
if(ismob(O) && !isliving(O))
continue
O_limit++
if(O_limit >= 20)
audible_message("[src] lets out a screech, it doesn't seem to be able to handle the load.")
break
- use_power(500)
+ use_power(1000)
O.throw_at(target, drive_range * power, power)
flick("mass_driver1", src)
diff --git a/code/game/machinery/navbeacon.dm b/code/game/machinery/navbeacon.dm
index f98cf16fb6093..7275aeedee0fd 100644
--- a/code/game/machinery/navbeacon.dm
+++ b/code/game/machinery/navbeacon.dm
@@ -10,7 +10,7 @@
level = 1 // underfloor
layer = UNDER_CATWALK
max_integrity = 500
- armor = list("melee" = 70, "bullet" = 70, "laser" = 70, "energy" = 70, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80)
+ armor = list("melee" = 70, "bullet" = 70, "laser" = 70, "energy" = 70, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80, "stamina" = 0)
var/open = FALSE // true if cover is open
var/locked = TRUE // true if controls are locked
diff --git a/code/game/machinery/newscaster.dm b/code/game/machinery/newscaster.dm
index 7832e53ce8958..eacb68b51e98e 100644
--- a/code/game/machinery/newscaster.dm
+++ b/code/game/machinery/newscaster.dm
@@ -186,9 +186,10 @@ GLOBAL_LIST_EMPTY(allCasters)
verb_say = "beeps"
verb_ask = "beeps"
verb_exclaim = "beeps"
- armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30)
+ armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30, "stamina" = 0)
max_integrity = 200
integrity_failure = 50
+ layer = ABOVE_WINDOW_LAYER
var/screen = 0
var/paper_remaining = 15
var/securityCaster = 0
@@ -295,7 +296,7 @@ GLOBAL_LIST_EMPTY(allCasters)
dat+=" The newscaster recognises you as: [scanned_user]"
if(1)
dat+= "Station Feed Channels"
- if( isemptylist(GLOB.news_network.network_channels) )
+ if(!length(GLOB.news_network.network_channels) )
dat+="No active channels found..."
else
for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels)
@@ -375,7 +376,7 @@ GLOBAL_LIST_EMPTY(allCasters)
dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice. "
dat+="No further feed story additions are allowed while the D-Notice is in effect.
"
else
- if( isemptylist(viewing_channel.messages) )
+ if( !length(viewing_channel.messages) )
dat+="No feed messages found in channel... "
else
var/i = 0
@@ -403,7 +404,7 @@ GLOBAL_LIST_EMPTY(allCasters)
dat+="NOTE: Due to the nature of news Feeds, total deletion of a Feed Story is not possible. "
dat+="Keep in mind that users attempting to view a censored feed will instead see the \[REDACTED\] tag above it."
dat+="Select Feed channel to get Stories from: "
- if(isemptylist(GLOB.news_network.network_channels))
+ if(!length(GLOB.news_network.network_channels))
dat+="No feed channels found active... "
else
for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels)
@@ -414,7 +415,7 @@ GLOBAL_LIST_EMPTY(allCasters)
dat+="A D-Notice is to be bestowed upon the channel if the handling Authority deems it as harmful for the station's"
dat+="morale, integrity or disciplinary behaviour. A D-Notice will render a channel unable to be updated by anyone, without deleting any feed"
dat+="stories it might contain at the time. You can lift a D-Notice if you have the required access at any time."
- if(isemptylist(GLOB.news_network.network_channels))
+ if(!length(GLOB.news_network.network_channels))
dat+="No feed channels found active... "
else
for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels)
@@ -423,7 +424,7 @@ GLOBAL_LIST_EMPTY(allCasters)
if(12)
dat+="[viewing_channel.channel_name]: \[ created by: [viewing_channel.returnAuthor(-1)] \] "
dat+="[(viewing_channel.authorCensor) ? ("Undo Author censorship") : ("Censor channel Author")]"
- if(isemptylist(viewing_channel.messages))
+ if(!length(viewing_channel.messages))
dat+="No feed messages found in channel... "
else
for(var/datum/newscaster/feed_message/MESSAGE in viewing_channel.messages)
@@ -440,7 +441,7 @@ GLOBAL_LIST_EMPTY(allCasters)
dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice. "
dat+="No further feed story additions are allowed while the D-Notice is in effect.
"
else
- if(isemptylist(viewing_channel.messages))
+ if(!length(viewing_channel.messages))
dat+="No feed messages found in channel... "
else
for(var/datum/newscaster/feed_message/MESSAGE in viewing_channel.messages)
@@ -907,7 +908,7 @@ GLOBAL_LIST_EMPTY(allCasters)
if(0) //Cover
dat+="
The Griffon
"
dat+="
Nanotrasen-standard newspaper, for use on Nanotrasen? Space Facilities
"
if(BUGMODE_MONITOR)
if(current)
@@ -116,10 +122,11 @@
html = "Tracking '[tracked_name]' \[Cancel Tracking\]\[Cancel camera view\] "
if(last_found)
var/time_diff = round((world.time - last_seen) / 150)
- var/obj/machinery/camera/C = bugged_cameras[last_found]
+ var/datum/weakref/camera_ref = bugged_cameras[last_found]
+ var/obj/machinery/camera/camera = camera_ref.resolve()
var/outstring
- if(C)
- outstring = "[last_found]"
+ if(camera)
+ outstring = "[last_found]"
else
outstring = last_found
if(!time_diff)
@@ -131,8 +138,8 @@
if(!s)
s = "00"
html += "Last seen near [outstring] ([m]:[s] minute\s ago) "
- if( C && (C.bug == src)) //Checks to see if the camera has a bug
- html += "\[Disable\]"
+ if(camera && (camera.bug == src)) //Checks to see if the camera has a bug
+ html += "\[Disable\]"
else
html += "Not yet seen."
@@ -203,12 +210,13 @@
if("monitor" in href_list)
//You can't locate on a list with keys
var/list/cameras = flatten_list(bugged_cameras)
- var/obj/machinery/camera/C = locate(href_list["monitor"]) in cameras
- if(C && istype(C))
- if(!same_z_level(C))
+ var/datum/weakref/camera_ref = locate(href_list["monitor"]) in cameras
+ var/obj/machinery/camera/camera = camera_ref.resolve()
+ if(camera && istype(camera))
+ if(!same_z_level(camera))
return
track_mode = BUGMODE_MONITOR
- current = C
+ current = camera
usr.reset_perspective(null)
interact()
if("track" in href_list)
@@ -224,13 +232,14 @@
if("emp" in href_list)
//You can't locate on a list with keys
var/list/cameras = flatten_list(bugged_cameras)
- var/obj/machinery/camera/C = locate(href_list["emp"]) in cameras
- if(C && istype(C) && C.bug == src)
- if(!same_z_level(C))
+ var/datum/weakref/camera_ref = locate(href_list["emp"]) in cameras
+ var/obj/machinery/camera/camera = camera_ref.resolve()
+ if(camera && istype(camera) && camera.bug == src)
+ if(!same_z_level(camera))
return
- C.emp_act(EMP_HEAVY)
- C.bug = null
- bugged_cameras -= C.c_tag
+ camera.emp_act(EMP_HEAVY)
+ camera.bug = null
+ bugged_cameras -= camera.c_tag
interact()
return
if("close" in href_list)
@@ -240,17 +249,18 @@
if("view" in href_list)
//You can't locate on a list with keys
var/list/cameras = flatten_list(bugged_cameras)
- var/obj/machinery/camera/C = locate(href_list["view"]) in cameras
- if(C && istype(C))
- if(!same_z_level(C))
+ var/datum/weakref/camera_ref = locate(href_list["view"]) in cameras
+ var/obj/machinery/camera/camera = camera_ref.resolve()
+ if(camera && istype(camera))
+ if(!same_z_level(camera))
return
- if(!C.can_use())
+ if(!camera.can_use())
to_chat(usr, "Something's wrong with that camera! You can't get a feed.")
return
- current = C
+ current = camera
spawn(6)
if(src.check_eye(usr))
- usr.reset_perspective(C)
+ usr.reset_perspective(camera)
interact()
else
usr.unset_machine()
@@ -301,7 +311,7 @@
/obj/item/camera_bug/proc/same_z_level(var/obj/machinery/camera/C)
var/turf/T_cam = get_turf(C)
var/turf/T_bug = get_turf(loc)
- if(!T_bug || T_cam.z != T_bug.z)
+ if(!T_bug || T_cam.get_virtual_z_level() != T_bug.get_virtual_z_level())
to_chat(usr, "You can't get a signal!")
return FALSE
return TRUE
diff --git a/code/game/objects/items/devices/chameleonproj.dm b/code/game/objects/items/devices/chameleonproj.dm
index 23abd97d472fa..203d327fac3f0 100644
--- a/code/game/objects/items/devices/chameleonproj.dm
+++ b/code/game/objects/items/devices/chameleonproj.dm
@@ -15,6 +15,7 @@
var/can_use = 1
var/obj/effect/dummy/chameleon/active_dummy = null
var/saved_appearance = null
+ var/list/vis_overlay_data = list()
/obj/item/chameleon/Initialize()
. = ..()
@@ -60,6 +61,14 @@
temp.plane = initial(target.plane)
saved_appearance = temp.appearance
+ if(istype(target, /atom/movable)) //Record vis_overlays data
+ var/atom/movable/M = target
+ var/count = 0
+ vis_overlay_data = list()
+ for(var/obj/effect/overlay/vis/overlay in M.vis_contents)
+ count++
+ vis_overlay_data["[count]"] = list("icon" = overlay.icon, "icon_state" = overlay.icon_state, "layer" = overlay.layer, "plane" = overlay.plane, "alpha" = overlay.alpha, "appearance_flags" = overlay.appearance_flags)
+
/obj/item/chameleon/proc/check_sprite(atom/target)
if(target.icon_state in icon_states(target.icon))
return TRUE
@@ -78,6 +87,9 @@
else
playsound(get_turf(src), 'sound/effects/pop.ogg', 100, 1, -6)
var/obj/effect/dummy/chameleon/C = new/obj/effect/dummy/chameleon(user.drop_location())
+ for(var/overlay_index in vis_overlay_data)
+ var/list/overlay_data = vis_overlay_data[overlay_index]
+ SSvis_overlays.add_vis_overlay(C, overlay_data["icon"], overlay_data["icon_state"], overlay_data["layer"], overlay_data["plane"], C.dir, alpha = overlay_data["alpha"], add_appearance_flags = overlay_data["appearance_flags"])
C.activate(user, saved_appearance, src)
to_chat(user, "You activate \the [src].")
new /obj/effect/temp_visual/emp/pulse(get_turf(src))
diff --git a/code/game/objects/items/devices/electroadaptive_pseudocircuit.dm b/code/game/objects/items/devices/electroadaptive_pseudocircuit.dm
index e87ea386c2787..792ab9beae65c 100644
--- a/code/game/objects/items/devices/electroadaptive_pseudocircuit.dm
+++ b/code/game/objects/items/devices/electroadaptive_pseudocircuit.dm
@@ -7,20 +7,23 @@
w_class = WEIGHT_CLASS_TINY
materials = list(/datum/material/iron = 50, /datum/material/glass = 300)
var/recharging = FALSE
+ var/obj/item/electronics/airlock/electronics = null
var/circuits = 5 //How many circuits the pseudocircuit has left
var/static/recycleable_circuits = typecacheof(list(/obj/item/electronics/firelock, /obj/item/electronics/airalarm, /obj/item/electronics/firealarm, \
- /obj/item/electronics/apc, /obj/item/electronics/advanced_airlock_controller))//A typecache of circuits consumable for material
+ /obj/item/electronics/apc, /obj/item/electronics/advanced_airlock_controller, /obj/item/electronics/airlock))//A typecache of circuits consumable for material
/obj/item/electroadaptive_pseudocircuit/Initialize()
. = ..()
- maptext = "[circuits]"
+ maptext = MAPTEXT("[circuits]")
+ electronics = new/obj/item/electronics/airlock(src)
+ electronics.holder = src
/obj/item/electroadaptive_pseudocircuit/examine(mob/user)
. = ..()
if(iscyborg(user))
- . += {"It has material for [circuits] circuit[circuits == 1 ? "" : "s"]. Use the pseudocircuit on existing circuits to gain material.\n
- Serves as a substitute for fire/air alarm, firelock, and APC electronics.\n
- It can also be used on an APC with no power cell to fabricate a low-capacity cell at a high power cost."}
+ . += "It has material for [circuits] circuit[circuits == 1 ? "" : "s"]. Use the pseudocircuit on existing circuits to gain material.\n"+\
+ "Serves as a substitute for fire/air alarm, firelock, and APC electronics.\n"+\
+ "It can also be used on an APC with no power cell to fabricate a low-capacity cell at a high power cost."
/obj/item/electroadaptive_pseudocircuit/proc/adapt_circuit(mob/living/silicon/robot/R, circuit_cost = 0)
if(QDELETED(R) || !istype(R))
@@ -40,7 +43,7 @@
playsound(R, 'sound/items/rped.ogg', 50, TRUE)
recharging = TRUE
circuits--
- maptext = "[circuits]"
+ maptext = MAPTEXT("[circuits]")
icon_state = "[initial(icon_state)]_recharging"
var/recharge_time = min(600, circuit_cost * 5) //40W of cost for one fabrication = 20 seconds of recharge time; this is to prevent spamming
addtimer(CALLBACK(src, .proc/recharge), recharge_time)
@@ -53,7 +56,7 @@
if(!is_type_in_typecache(target, recycleable_circuits))
return
circuits++
- maptext = "[circuits]"
+ maptext = MAPTEXT("[circuits]")
user.visible_message("User breaks down [target] with [src].", \
"You recycle [target] into [src]. It now has material for [circuits] circuits.")
playsound(user, 'sound/items/deconstruct.ogg', 50, TRUE)
@@ -63,3 +66,12 @@
playsound(src, 'sound/machines/chime.ogg', 25, TRUE)
recharging = FALSE
icon_state = initial(icon_state)
+
+/obj/item/electroadaptive_pseudocircuit/attack_self(mob/user)
+ . = ..()
+ electronics.ui_interact(user)
+
+/obj/item/electroadaptive_pseudocircuit/Destroy()
+ QDEL_NULL(electronics)
+ . = ..()
+
\ No newline at end of file
diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm
index 66ef2dc2a85c2..3cafdae575722 100644
--- a/code/game/objects/items/devices/flashlight.dm
+++ b/code/game/objects/items/devices/flashlight.dm
@@ -258,6 +258,7 @@
icon_state = "flare"
item_state = "flare"
actions_types = list()
+ /// How many seconds of fuel we have left
var/fuel = 0
var/on_damage = 7
var/produce_heat = 1500
@@ -267,12 +268,12 @@
/obj/item/flashlight/flare/Initialize()
. = ..()
- fuel = rand(800, 1000) // Sorry for changing this so much but I keep under-estimating how long X number of ticks last in seconds.
+ fuel = rand(1600, 2000)
-/obj/item/flashlight/flare/process()
+/obj/item/flashlight/flare/process(delta_time)
open_flame(heat)
- fuel = max(fuel - 1, 0)
- if(!fuel || !on)
+ fuel = max(fuel -= delta_time, 0)
+ if(fuel <= 0 || !on)
turn_off()
if(!fuel)
icon_state = "[initial(icon_state)]-empty"
@@ -305,7 +306,7 @@
/obj/item/flashlight/flare/attack_self(mob/user)
// Usual checks
- if(!fuel)
+ if(fuel <= 0)
to_chat(user, "[src] is out of fuel!")
return
if(on)
@@ -372,7 +373,9 @@
/obj/item/flashlight/emp
var/emp_max_charges = 4
var/emp_cur_charges = 4
- var/charge_tick = 0
+ var/charge_timer = 0
+ /// How many seconds between each recharge
+ var/charge_delay = 20
/obj/item/flashlight/emp/New()
..()
@@ -382,11 +385,11 @@
STOP_PROCESSING(SSobj, src)
. = ..()
-/obj/item/flashlight/emp/process()
- charge_tick++
- if(charge_tick < 10)
+/obj/item/flashlight/emp/process(delta_time)
+ charge_timer += delta_time
+ if(charge_timer < charge_delay)
return FALSE
- charge_tick = 0
+ charge_timer -= charge_delay
emp_cur_charges = min(emp_cur_charges+1, emp_max_charges)
return TRUE
@@ -433,10 +436,11 @@
icon_state = "glowstick"
item_state = "glowstick"
grind_results = list(/datum/reagent/phenol = 15, /datum/reagent/hydrogen = 10, /datum/reagent/oxygen = 5) //Meth-in-a-stick
- var/fuel = 0
+ var/burn_pickup = FALSE //If true, fuel will only decrease after being picked up or used in hand (Useful for mapping)
+ var/fuel = 0 // How many seconds of fuel we have left
/obj/item/flashlight/glowstick/Initialize()
- fuel = rand(1600, 2000)
+ fuel = rand(3200, 4000)
light_color = color
. = ..()
@@ -444,9 +448,9 @@
STOP_PROCESSING(SSobj, src)
. = ..()
-/obj/item/flashlight/glowstick/process()
- fuel = max(fuel - 1, 0)
- if(!fuel)
+/obj/item/flashlight/glowstick/process(delta_time)
+ fuel = max(fuel - delta_time, 0)
+ if(fuel <= 0)
turn_off()
STOP_PROCESSING(SSobj, src)
update_icon()
@@ -458,7 +462,7 @@
/obj/item/flashlight/glowstick/update_icon()
item_state = "glowstick"
cut_overlays()
- if(!fuel)
+ if(fuel <= 0)
icon_state = "glowstick-empty"
cut_overlays()
set_light(0)
@@ -472,8 +476,14 @@
icon_state = "glowstick"
cut_overlays()
+/obj/item/flashlight/glowstick/pickup(mob/user)
+ . = ..()
+ if(burn_pickup && on)
+ burn_pickup = FALSE
+ START_PROCESSING(SSobj, src)
+
/obj/item/flashlight/glowstick/attack_self(mob/user)
- if(!fuel)
+ if(fuel <= 0)
to_chat(user, "[src] is spent.")
return
if(on)
@@ -484,6 +494,7 @@
if(.)
user.visible_message("[user] cracks and shakes [src].", "You crack and shake [src], turning it on!")
START_PROCESSING(SSobj, src)
+ burn_pickup = FALSE
/obj/item/flashlight/glowstick/suicide_act(mob/living/carbon/human/user)
if(!fuel)
@@ -530,6 +541,22 @@
loot = typesof(/obj/item/flashlight/glowstick)
. = ..()
+/obj/effect/spawner/lootdrop/glowstick/lit/Initialize()
+ . = ..()
+ var/obj/item/flashlight/glowstick/found = locate() in get_turf(src)
+ if(!found)
+ return
+ found.on = TRUE
+ found.icon_state = "[initial(found.icon_state)]-on"
+ if(found.flashlight_power)
+ found.set_light(l_range = found.brightness_on, l_power = found.flashlight_power)
+ else
+ found.set_light(found.brightness_on)
+ for(var/X in found.actions)
+ var/datum/action/A = X
+ A.UpdateButtonIcon()
+ found.burn_pickup = TRUE
+
/obj/item/flashlight/spotlight //invisible lighting source
name = "disco light"
desc = "Groovy..."
diff --git a/code/game/objects/items/devices/forcefieldprojector.dm b/code/game/objects/items/devices/forcefieldprojector.dm
index a35d26437e521..cc160a1cae786 100644
--- a/code/game/objects/items/devices/forcefieldprojector.dm
+++ b/code/game/objects/items/devices/forcefieldprojector.dm
@@ -64,11 +64,11 @@
STOP_PROCESSING(SSobj, src)
return ..()
-/obj/item/forcefield_projector/process()
+/obj/item/forcefield_projector/process(delta_time)
if(!LAZYLEN(current_fields))
- shield_integrity = min(shield_integrity + 4, max_shield_integrity)
+ shield_integrity = min(shield_integrity + delta_time * 2, max_shield_integrity)
else
- shield_integrity = max(shield_integrity - LAZYLEN(current_fields), 0) //fields degrade slowly over time
+ shield_integrity = max(shield_integrity - LAZYLEN(current_fields) * delta_time * 0.5, 0) //fields degrade slowly over time
for(var/obj/structure/projected_forcefield/F in current_fields)
if(shield_integrity <= 0 || get_dist(F,src) > field_distance_limit)
qdel(F)
@@ -84,7 +84,7 @@
mouse_opacity = MOUSE_OPACITY_OPAQUE
resistance_flags = INDESTRUCTIBLE
CanAtmosPass = ATMOS_PASS_DENSITY
- armor = list("melee" = 0, "bullet" = 25, "laser" = 50, "energy" = 50, "bomb" = 25, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100)
+ armor = list("melee" = 0, "bullet" = 25, "laser" = 50, "energy" = 50, "bomb" = 25, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100, "stamina" = 0)
var/obj/item/forcefield_projector/generator
/obj/structure/projected_forcefield/Initialize(mapload, obj/item/forcefield_projector/origin)
diff --git a/code/game/objects/items/devices/geiger_counter.dm b/code/game/objects/items/devices/geiger_counter.dm
index fb5db099a58ea..3931c8fcc3c93 100644
--- a/code/game/objects/items/devices/geiger_counter.dm
+++ b/code/game/objects/items/devices/geiger_counter.dm
@@ -4,10 +4,6 @@
#define RAD_LEVEL_VERY_HIGH 800
#define RAD_LEVEL_CRITICAL 1500
-#define RAD_MEASURE_SMOOTHING 5
-
-#define RAD_GRACE_PERIOD 2
-
/obj/item/geiger_counter //DISCLAIMER: I know nothing about how real-life Geiger counters work. This will not be realistic. ~Xhuis
name = "\improper Geiger counter"
desc = "A handheld device used for detecting and measuring radiation pulses."
@@ -20,7 +16,7 @@
slot_flags = ITEM_SLOT_BELT
materials = list(/datum/material/iron = 150, /datum/material/glass = 150)
- var/grace = RAD_GRACE_PERIOD
+ var/grace = RAD_GEIGER_GRACE_PERIOD
var/datum/looping_sound/geiger/soundloop
var/scanning = FALSE
@@ -37,31 +33,28 @@
soundloop = new(list(src), FALSE)
/obj/item/geiger_counter/Destroy()
+ QDEL_NULL(soundloop)
STOP_PROCESSING(SSobj, src)
return ..()
-/obj/item/geiger_counter/process()
- update_icon()
- update_sound()
-
- if(!scanning)
- current_tick_amount = 0
- return
-
- radiation_count -= radiation_count/RAD_MEASURE_SMOOTHING
- radiation_count += current_tick_amount/RAD_MEASURE_SMOOTHING
+/obj/item/geiger_counter/process(delta_time)
+ if(scanning)
+ radiation_count = LPFILTER(radiation_count, current_tick_amount, delta_time, RAD_GEIGER_RC)
- if(current_tick_amount)
- grace = RAD_GRACE_PERIOD
- last_tick_amount = current_tick_amount
+ if(current_tick_amount)
+ grace = RAD_GEIGER_GRACE_PERIOD
+ last_tick_amount = current_tick_amount
- else if(!(obj_flags & EMAGGED))
- grace--
- if(grace <= 0)
- radiation_count = 0
+ else if(!(obj_flags & EMAGGED))
+ grace -= delta_time
+ if(grace <= 0)
+ radiation_count = 0
current_tick_amount = 0
+ update_icon()
+ update_sound()
+
/obj/item/geiger_counter/examine(mob/user)
. = ..()
if(!scanning)
@@ -221,6 +214,8 @@
listeningTo = user
/obj/item/geiger_counter/cyborg/proc/redirect_rad_act(datum/source, amount)
+ SIGNAL_HANDLER
+
rad_act(amount)
/obj/item/geiger_counter/cyborg/dropped()
diff --git a/code/game/objects/items/devices/gps.dm b/code/game/objects/items/devices/gps.dm
index 77d0f82baa101..fad6e4129cc8d 100644
--- a/code/game/objects/items/devices/gps.dm
+++ b/code/game/objects/items/devices/gps.dm
@@ -26,6 +26,10 @@
gpstag = "MINE0"
desc = "A positioning system helpful for rescuing trapped or injured miners, keeping one on you at all times while mining might just save your life."
+/obj/item/gps/mining/exploration
+ gpstag = "EXP0"
+ desc = "A positioning system used for long-ranged tracking of important beacons."
+
/obj/item/gps/cyborg
icon_state = "gps-b"
gpstag = "BORG0"
@@ -59,7 +63,7 @@
// I assume it's faster to color,tag and OR the turf in, rather
// then checking if its there
T.color = RANDOM_COLOUR
- T.maptext = "[T.x],[T.y],[T.z]"
+ T.maptext = MAPTEXT("[T.x],[T.y],[T.get_virtual_z_level()]")
tagged |= T
/obj/item/gps/visible_debug/proc/clear()
diff --git a/code/game/objects/items/devices/instruments.dm b/code/game/objects/items/devices/instruments.dm
deleted file mode 100644
index c5004e31d3295..0000000000000
--- a/code/game/objects/items/devices/instruments.dm
+++ /dev/null
@@ -1,275 +0,0 @@
-//copy pasta of the space piano, don't hurt me -Pete
-/obj/item/instrument
- name = "generic instrument"
- resistance_flags = FLAMMABLE
- force = 10
- max_integrity = 100
- icon = 'icons/obj/musician.dmi'
- lefthand_file = 'icons/mob/inhands/equipment/instruments_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/instruments_righthand.dmi'
- var/datum/song/handheld/song
- var/instrumentId = "generic"
- var/instrumentExt = "mid"
- block_upgrade_walk = 1
-
-/obj/item/instrument/Initialize()
- . = ..()
- song = new(instrumentId, src, instrumentExt)
-
-/obj/item/instrument/Destroy()
- QDEL_NULL(song)
- . = ..()
-
-/obj/item/instrument/suicide_act(mob/user)
- user.visible_message("[user] begins to play 'Gloomy Sunday'! It looks like [user.p_theyre()] trying to commit suicide!")
- return (BRUTELOSS)
-
-/obj/item/instrument/Initialize(mapload)
- . = ..()
- if(mapload)
- song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded
-
-/obj/item/instrument/attack_self(mob/user)
- if(!user.IsAdvancedToolUser())
- to_chat(user, "You don't have the dexterity to do this!")
- return 1
- interact(user)
-
-/obj/item/instrument/interact(mob/user)
- ui_interact(user)
-
-/obj/item/instrument/ui_interact(mob/living/user)
- if(!isliving(user) || user.stat || user.restrained() || !(user.mobility_flags & MOBILITY_STAND))
- return
-
- user.set_machine(src)
- song.interact(user)
-
-/obj/item/instrument/violin
- name = "space violin"
- desc = "A wooden musical instrument with four strings and a bow. \"The devil went down to space, he was looking for an assistant to grief.\""
- icon_state = "violin"
- item_state = "violin"
- hitsound = "swing_hit"
- instrumentId = "violin"
-
-/obj/item/instrument/violin/golden
- name = "golden violin"
- desc = "A golden musical instrument with four strings and a bow. \"The devil went down to space, he was looking for an assistant to grief.\""
- icon_state = "golden_violin"
- item_state = "golden_violin"
- resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
-
-/obj/item/instrument/piano_synth
- name = "synthesizer"
- desc = "An advanced electronic synthesizer that can be used as various instruments."
- icon_state = "synth"
- item_state = "synth"
- instrumentId = "piano"
- instrumentExt = "ogg"
- var/static/list/insTypes = list("accordion" = "mid", "bikehorn" = "ogg", "glockenspiel" = "mid", "banjo" = "ogg", "guitar" = "ogg", "harmonica" = "mid", "piano" = "ogg", "recorder" = "mid", "saxophone" = "mid", "trombone" = "mid", "violin" = "mid", "xylophone" = "mid") //No eguitar you ear-rapey fuckers.
- actions_types = list(/datum/action/item_action/synthswitch)
-
-/obj/item/instrument/piano_synth/proc/changeInstrument(name = "piano")
- song.instrumentDir = name
- song.instrumentExt = insTypes[name]
-
-/obj/item/instrument/piano_synth/proc/selectInstrument() // Moved here so it can be used by the action and PAI software panel without copypasta
- var/chosen = input("Choose the type of instrument you want to use", "Instrument Selection", song.instrumentDir) as null|anything in sortList(insTypes)
- if(!insTypes[chosen])
- return
- return changeInstrument(chosen)
-
-/obj/item/instrument/banjo
- name = "banjo"
- desc = "A 'Mura' brand banjo. It's pretty much just a drum with a neck and strings."
- icon_state = "banjo"
- item_state = "banjo"
- instrumentExt = "ogg"
- attack_verb = list("scruggs-styled", "hum-diggitied", "shin-digged", "clawhammered")
- hitsound = 'sound/weapons/banjoslap.ogg'
- instrumentId = "banjo"
-
-/obj/item/instrument/guitar
- name = "guitar"
- desc = "It's made of wood and has bronze strings."
- icon_state = "guitar"
- item_state = "guitar"
- instrumentExt = "ogg"
- attack_verb = list("played metal on", "serenaded", "crashed", "smashed")
- hitsound = 'sound/weapons/stringsmash.ogg'
- instrumentId = "guitar"
-
-/obj/item/instrument/eguitar
- name = "electric guitar"
- desc = "Makes all your shredding needs possible."
- icon_state = "eguitar"
- item_state = "eguitar"
- force = 12
- attack_verb = list("played metal on", "shredded", "crashed", "smashed")
- hitsound = 'sound/weapons/stringsmash.ogg'
- instrumentId = "eguitar"
- instrumentExt = "ogg"
-
-/obj/item/instrument/glockenspiel
- name = "glockenspiel"
- desc = "Smooth metal bars perfect for any marching band."
- icon_state = "glockenspiel"
- item_state = "glockenspiel"
- instrumentId = "glockenspiel"
-
-/obj/item/instrument/accordion
- name = "accordion"
- desc = "Pun-Pun not included."
- icon_state = "accordion"
- item_state = "accordion"
- instrumentId = "accordion"
-
-/obj/item/instrument/trumpet
- name = "trumpet"
- desc = "To announce the arrival of the king!"
- icon_state = "trumpet"
- item_state = "trombone"
- instrumentId = "trombone"
-
-/obj/item/instrument/trumpet/spectral
- name = "spectral trumpet"
- desc = "Things are about to get spooky!"
- icon_state = "trumpet"
- item_state = "trombone"
- force = 0
- instrumentId = "trombone"
- attack_verb = list("played","jazzed","trumpeted","mourned","dooted","spooked")
-
-/obj/item/instrument/trumpet/spectral/Initialize()
- . = ..()
- AddComponent(/datum/component/spooky)
-
-/obj/item/instrument/trumpet/spectral/attack(mob/living/carbon/C, mob/user)
- playsound (loc, 'sound/instruments/trombone/En4.mid', 100,1,-1)
- ..()
-
-/obj/item/instrument/saxophone
- name = "saxophone"
- desc = "This soothing sound will be sure to leave your audience in tears."
- icon_state = "saxophone"
- item_state = "saxophone"
- instrumentId = "saxophone"
-
-/obj/item/instrument/saxophone/spectral
- name = "spectral saxophone"
- desc = "This spooky sound will be sure to leave mortals in bones."
- icon_state = "saxophone"
- item_state = "saxophone"
- instrumentId = "saxophone"
- force = 0
- attack_verb = list("played","jazzed","saxxed","mourned","dooted","spooked")
-
-/obj/item/instrument/saxophone/spectral/Initialize()
- . = ..()
- AddComponent(/datum/component/spooky)
-
-/obj/item/instrument/saxophone/spectral/attack(mob/living/carbon/C, mob/user)
- playsound (loc, 'sound/instruments/saxophone/En4.mid', 100,1,-1)
- ..()
-
-/obj/item/instrument/trombone
- name = "trombone"
- desc = "How can any pool table ever hope to compete?"
- icon_state = "trombone"
- item_state = "trombone"
- instrumentId = "trombone"
-
-/obj/item/instrument/trombone/spectral
- name = "spectral trombone"
- desc = "A skeleton's favorite instrument. Apply directly on the mortals."
- instrumentId = "trombone"
- icon_state = "trombone"
- item_state = "trombone"
- force = 0
- attack_verb = list("played","jazzed","tromboned","mourned","dooted","spooked")
-
-/obj/item/instrument/trombone/spectral/Initialize()
- . = ..()
- AddComponent(/datum/component/spooky)
-
-/obj/item/instrument/trombone/spectral/attack(mob/living/carbon/C, mob/user)
- playsound (loc, 'sound/instruments/trombone/Cn4.mid', 100,1,-1)
- ..()
-
-/obj/item/instrument/recorder
- name = "recorder"
- desc = "Just like in school, playing ability and all."
- force = 5
- icon_state = "recorder"
- item_state = "recorder"
- instrumentId = "recorder"
-
-/obj/item/instrument/harmonica
- name = "harmonica"
- desc = "For when you get a bad case of the space blues."
- icon_state = "harmonica"
- item_state = "harmonica"
- instrumentId = "harmonica"
- slot_flags = ITEM_SLOT_MASK
- force = 5
- w_class = WEIGHT_CLASS_SMALL
- actions_types = list(/datum/action/item_action/instrument)
-
-/obj/item/instrument/harmonica/proc/handle_speech(datum/source, list/speech_args)
- if(song.playing && ismob(loc))
- to_chat(loc, "You stop playing the harmonica to talk...")
- song.playing = FALSE
-
-/obj/item/instrument/harmonica/equipped(mob/M, slot)
- . = ..()
- RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech)
-
-/obj/item/instrument/harmonica/dropped(mob/M)
- . = ..()
- UnregisterSignal(M, COMSIG_MOB_SAY)
-
-/obj/item/instrument/bikehorn
- name = "gilded bike horn"
- desc = "An exquisitely decorated bike horn, capable of honking in a variety of notes."
- icon_state = "bike_horn"
- item_state = "bike_horn"
- lefthand_file = 'icons/mob/inhands/equipment/horns_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/horns_righthand.dmi'
- attack_verb = list("beautifully honks")
- instrumentId = "bikehorn"
- instrumentExt = "ogg"
- w_class = WEIGHT_CLASS_TINY
- force = 0
- throw_speed = 3
- throw_range = 15
- hitsound = 'sound/items/bikehorn.ogg'
-
-///
-
-/obj/item/choice_beacon/music
- name = "instrument delivery beacon"
- desc = "Summon your tool of art."
- icon_state = "gangtool-red"
-
-/obj/item/choice_beacon/music/generate_display_names()
- var/static/list/instruments
- if(!instruments)
- instruments = list()
- var/list/templist = list(/obj/item/instrument/violin,
- /obj/item/instrument/piano_synth,
- /obj/item/instrument/banjo,
- /obj/item/instrument/guitar,
- /obj/item/instrument/eguitar,
- /obj/item/instrument/glockenspiel,
- /obj/item/instrument/accordion,
- /obj/item/instrument/trumpet,
- /obj/item/instrument/saxophone,
- /obj/item/instrument/trombone,
- /obj/item/instrument/recorder,
- /obj/item/instrument/harmonica
- )
- for(var/V in templist)
- var/atom/A = V
- instruments[initial(A.name)] = A
- return instruments
diff --git a/code/game/objects/items/devices/laserpointer.dm b/code/game/objects/items/devices/laserpointer.dm
index 16b6c27a1e932..5341d39d9faad 100644
--- a/code/game/objects/items/devices/laserpointer.dm
+++ b/code/game/objects/items/devices/laserpointer.dm
@@ -60,9 +60,9 @@
. = ..()
if(in_range(user, src) || isobserver(user))
if(!diode)
- . += "The diode is missing."
+ . += "The diode is missing."
else
- . += "A class [diode.rating] laser diode is installed. It is screwed in place."
+ . += "A class [diode.rating] laser diode is installed. It is screwed in place."
/obj/item/laser_pointer/afterattack(atom/target, mob/living/user, flag, params)
. = ..()
@@ -137,29 +137,30 @@
else
outmsg = "You miss the lens of [C] with [src]!"
- //catpeople
- for(var/mob/living/carbon/human/H in view(1,targloc))
- if(!iscatperson(H) || H.incapacitated() || H.eye_blind )
- continue
- if(user.mobility_flags & MOBILITY_STAND)
- H.setDir(get_dir(H,targloc)) // kitty always looks at the light
- if(prob(effectchance))
- H.visible_message("[H] makes a grab for the light!","LIGHT!")
- H.Move(targloc)
- log_combat(user, H, "moved with a laser pointer",src)
+ // For luring whatever mobs that are "interested" in laser pointers
+ for(var/mob/M as() in viewers(1,targloc))
+ if(M.incapacitated())
+ return
+ var/mob/living/carbon/human/H = M
+ if(iscatperson(H) && !H.eye_blind) //catpeople!
+ if(user.mobility_flags & MOBILITY_STAND)
+ H.setDir(get_dir(H,targloc)) // kitty always looks at the light
+ if(prob(effectchance))
+ H.visible_message("[H] makes a grab for the light!","LIGHT!")
+ H.Move(targloc)
+ log_combat(user, H, "moved with a laser pointer",src)
+ else
+ H.visible_message("[H] looks briefly distracted by the light."," You're briefly tempted by the shiny light... ")
else
- H.visible_message("[H] looks briefly distracted by the light."," You're briefly tempted by the shiny light... ")
- else
- H.visible_message("[H] stares at the light"," You stare at the light... ")
-
- //cats!
- for(var/mob/living/simple_animal/pet/cat/C in view(1,targloc))
- if(prob(50))
- C.visible_message("[C] pounces on the light!","LIGHT!")
- C.Move(targloc)
- C.set_resting(TRUE, FALSE)
- else
- C.visible_message("[C] looks uninterested in your games.","You spot [user] shining [src] at you. How insulting!")
+ M.visible_message("[M] stares at the light"," You stare at the light... ")
+ else if(iscat(M)) //cats!
+ var/mob/living/simple_animal/pet/cat/C = M
+ if(prob(50))
+ C.visible_message("[C] pounces on the light!","LIGHT!")
+ C.Move(targloc)
+ C.set_resting(TRUE, FALSE)
+ else
+ C.visible_message("[C] looks uninterested in your games.","You spot [user] shining [src] at you. How insulting!")
//laser pointer image
icon_state = "pointer_[pointer_icon_state]"
@@ -191,8 +192,11 @@
flick_overlay_view(I, targloc, 10)
icon_state = "pointer"
-/obj/item/laser_pointer/process()
- if(prob(20 - recharge_locked*5))
+/obj/item/laser_pointer/process(delta_time)
+ if(!diode)
+ recharging = FALSE
+ return PROCESS_KILL
+ if(DT_PROB(10 + diode.rating*10 - recharge_locked*1, delta_time)) //t1 is 20, 2 40
energy += 1
if(energy >= max_energy)
energy = max_energy
diff --git a/code/game/objects/items/devices/lightreplacer.dm b/code/game/objects/items/devices/lightreplacer.dm
index 542fec537409f..61d55f3a3529c 100644
--- a/code/game/objects/items/devices/lightreplacer.dm
+++ b/code/game/objects/items/devices/lightreplacer.dm
@@ -59,7 +59,6 @@
var/increment = 5
// How much to take from the glass?
var/decrement = 1
- var/charge = 1
// Eating used bulbs gives us bulb shards
var/bulb_shards = 0
@@ -179,10 +178,7 @@
return new_bulbs
/obj/item/lightreplacer/proc/Charge(var/mob/user)
- charge += 1
- if(charge > 3)
- AddUses(1)
- charge = 1
+ AddUses(1)
/obj/item/lightreplacer/proc/ReplaceLight(obj/machinery/light/target, mob/living/U)
diff --git a/code/game/objects/items/devices/megaphone.dm b/code/game/objects/items/devices/megaphone.dm
index 93516b4eae5be..651773b0fcd1e 100644
--- a/code/game/objects/items/devices/megaphone.dm
+++ b/code/game/objects/items/devices/megaphone.dm
@@ -9,7 +9,7 @@
w_class = WEIGHT_CLASS_SMALL
siemens_coefficient = 1
var/spamcheck = 0
- var/list/voicespan = list(SPAN_COMMAND)
+ var/list/voicespan = list(SPAN_MEGAPHONE)
/obj/item/megaphone/suicide_act(mob/living/carbon/user)
user.visible_message("[user] is uttering [user.p_their()] last words into \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
@@ -19,7 +19,7 @@
/obj/item/megaphone/equipped(mob/M, slot)
. = ..()
- if (slot == SLOT_HANDS)
+ if (slot == ITEM_SLOT_HANDS)
RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech)
else
UnregisterSignal(M, COMSIG_MOB_SAY)
@@ -29,6 +29,8 @@
UnregisterSignal(M, COMSIG_MOB_SAY)
/obj/item/megaphone/proc/handle_speech(mob/living/carbon/user, list/speech_args)
+ SIGNAL_HANDLER
+
if (user.get_active_held_item() == src)
if(spamcheck > world.time)
to_chat(user, "\The [src] needs to recharge!")
diff --git a/code/game/objects/items/devices/multitool.dm b/code/game/objects/items/devices/multitool.dm
index 29800dbf029d7..613730dc07def 100644
--- a/code/game/objects/items/devices/multitool.dm
+++ b/code/game/objects/items/devices/multitool.dm
@@ -30,79 +30,24 @@
usesound = 'sound/weapons/empty.ogg'
var/mode = 0
+/obj/item/multitool/Initialize()
+ RegisterSignal(src, COMSIG_PARENT_EXAMINE, .proc/on_examine)
+ return ..()
+
+/obj/item/multitool/Destroy()
+ UnregisterSignal(src, COMSIG_PARENT_EXAMINE)
+ return ..()
+
/obj/item/multitool/suicide_act(mob/living/carbon/user)
user.visible_message("[user] puts the [src] to [user.p_their()] chest. It looks like [user.p_theyre()] trying to pulse [user.p_their()] heart off!")
- return OXYLOSS//theres a reason it wasnt recommended by doctors
-
+ return OXYLOSS//theres a reason it wasn't recommended by doctors
-// circuit shit begin
-/obj/item/multitool/var/datum/integrated_io/selected_io = null //functional for integrated circuits.
+/obj/item/multitool/proc/on_examine(datum/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
-//I know copypasting it and overwriting the old procs is a pretty rough thing to do, but hey, it's the simplest and most effective
-/obj/item/multitool/examine(mob/user)
- . = ..()
- if(selected_io)
- . += "Activate [src] to detach the data wire."
if(buffer)
- . += "Its buffer contains [buffer]."
-
-/obj/item/multitool/attack_self(mob/user)
- if(selected_io)
- selected_io = null
- to_chat(user, "You clear the wired connection from the multitool.")
- update_icon()
-
-/obj/item/multitool/update_icon()
- if(selected_io)
- icon_state = "multitool_red"
- else
- icon_state = "multitool"
-
-/obj/item/multitool/proc/wire(var/datum/integrated_io/io, mob/user)
- if(!io.holder.assembly)
- to_chat(user, "\The [io.holder] needs to be secured inside an assembly first.")
- return
-
- if(selected_io)
- if(io == selected_io)
- to_chat(user, "Wiring \the [selected_io.holder]'s [selected_io.name] into itself is rather pointless.")
- return
- if(io.io_type != selected_io.io_type)
- to_chat(user, "Those two types of channels are incompatible. The first is a [selected_io.io_type], \
- while the second is a [io.io_type].")
- return
- if(io.holder.assembly && io.holder.assembly != selected_io.holder.assembly)
- to_chat(user, "Both \the [io.holder] and \the [selected_io.holder] need to be inside the same assembly.")
- return
- io.connect_pin(selected_io)
-
- to_chat(user, "You connect \the [selected_io.holder]'s [selected_io.name] to \the [io.holder]'s [io.name].")
- selected_io.holder.interact(user) // This is to update the UI.
- selected_io = null
-
- else
- selected_io = io
- to_chat(user, "You link \the multitool to \the [selected_io.holder]'s [selected_io.name] data channel.")
-
- update_icon()
-
-
-/obj/item/multitool/proc/unwire(var/datum/integrated_io/io1, var/datum/integrated_io/io2, mob/user)
- if(!io1.linked.len || !io2.linked.len)
- to_chat(user, "There is nothing connected to the data channel.")
- return
-
- if(!(io1 in io2.linked) || !(io2 in io1.linked) )
- to_chat(user, "These data pins aren't connected!")
- return
- else
- io1.disconnect_pin(io2)
- to_chat(user, "You clip the data connection between the [io1.holder.displayed_name]'s \
- [io1.name] and the [io2.holder.displayed_name]'s [io2.name].")
- io1.holder.interact(user) // This is to update the UI.
- update_icon()
+ examine_list += "Its buffer contains [buffer]."
-// circuit shit end
// Syndicate device disguised as a multitool; it will turn red when an AI camera is nearby.
/obj/item/multitool/ai_detect
var/track_cooldown = 0
@@ -112,13 +57,13 @@
var/rangewarning = 20 //Glows yellow when inside
var/hud_type = DATA_HUD_AI_DETECT
var/hud_on = FALSE
- var/mob/camera/aiEye/remote/ai_detector/eye
+ var/mob/camera/ai_eye/remote/ai_detector/eye
var/datum/action/item_action/toggle_multitool/toggle_action
/obj/item/multitool/ai_detect/Initialize()
. = ..()
START_PROCESSING(SSobj, src)
- eye = new /mob/camera/aiEye/remote/ai_detector()
+ eye = new /mob/camera/ai_eye/remote/ai_detector()
toggle_action = new /datum/action/item_action/toggle_multitool(src)
/obj/item/multitool/ai_detect/Destroy()
@@ -163,7 +108,7 @@
/obj/item/multitool/ai_detect/proc/show_hud(mob/user)
if(user && hud_type)
- var/obj/screen/plane_master/camera_static/PM = user.hud_used.plane_masters["[CAMERA_STATIC_PLANE]"]
+ var/atom/movable/screen/plane_master/camera_static/PM = user.hud_used.plane_masters["[CAMERA_STATIC_PLANE]"]
PM.alpha = 150
var/datum/atom_hud/H = GLOB.huds[hud_type]
if(!H.hudusers[user])
@@ -173,7 +118,7 @@
/obj/item/multitool/ai_detect/proc/remove_hud(mob/user)
if(user && hud_type)
- var/obj/screen/plane_master/camera_static/PM = user.hud_used.plane_masters["[CAMERA_STATIC_PLANE]"]
+ var/atom/movable/screen/plane_master/camera_static/PM = user.hud_used.plane_masters["[CAMERA_STATIC_PLANE]"]
PM.alpha = 255
var/datum/atom_hud/H = GLOB.huds[hud_type]
H.remove_hud_from(user)
@@ -192,7 +137,7 @@
return
var/datum/camerachunk/chunk = GLOB.cameranet.chunkGenerated(our_turf.x, our_turf.y, our_turf.z)
if(chunk && chunk.seenby.len)
- for(var/mob/camera/aiEye/A in chunk.seenby)
+ for(var/mob/camera/ai_eye/A in chunk.seenby)
if(!A.ai_detector_visible)
continue
var/turf/detect_turf = get_turf(A)
@@ -203,7 +148,7 @@
detect_state = PROXIMITY_NEAR
break
-/mob/camera/aiEye/remote/ai_detector
+/mob/camera/ai_eye/remote/ai_detector
name = "AI detector eye"
ai_detector_visible = FALSE
use_static = USE_STATIC_TRANSPARENT
diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm
index ff46b109ab7c4..39c1a9c4d4cf9 100644
--- a/code/game/objects/items/devices/paicard.dm
+++ b/code/game/objects/items/devices/paicard.dm
@@ -35,7 +35,11 @@
if(!pai.master_dna || !pai.master)
dat += "Imprint Master DNA "
dat += "Installed Personality: [pai.name] "
- dat += "Prime directive: [pai.laws.zeroth] "
+ dat += "Prime directive: "
+ if(pai.laws.zeroth)
+ dat +="[pai.laws.zeroth] "
+ else
+ dat +="None "
for(var/slaws in pai.laws.supplied)
dat += "Additional directives: [slaws] "
dat += "Configure Directives "
@@ -52,6 +56,7 @@
var/mob/living/carbon/human/H = user
if(H.real_name == pai.master || H.dna.unique_enzymes == pai.master_dna)
dat += "\[[pai.canholo? "Disable" : "Enable"] holomatrix projectors\] "
+ dat += "\[Remove Prime directive\] "
dat += "\[Wipe current pAI personality\] "
else
dat += "No personality installed. "
@@ -82,6 +87,7 @@
pai.master = M.real_name
pai.master_dna = M.dna.unique_enzymes
to_chat(pai, "You have been bound to a new master.")
+ pai.laws.set_zeroth_law("Serve your master.")
pai.emittersemicd = FALSE
if(href_list["wipe"])
var/confirm = input("Are you CERTAIN you wish to delete the current personality? This action cannot be undone.", "Personality Wipe") in list("Yes", "No")
@@ -92,6 +98,10 @@
to_chat(pai, "Your mental faculties leave you.")
to_chat(pai, "oblivion... ")
qdel(pai)
+ if(href_list["clear_zero"])
+ if((input("Are you CERTAIN you wish to remove this pAI's Prime directive? This action cannot be undone.", "Clear Directive") in list("Yes", "No")) == "Yes")
+ if(pai)
+ pai.laws.clear_zeroth_law()
if(href_list["toggle_transmit"] || href_list["toggle_receive"])
var/transmitting = href_list["toggle_transmit"] //it can't be both so if we know it's not transmitting it must be receiving.
var/transmit_holder = (transmitting ? WIRE_TX : WIRE_RX)
@@ -166,4 +176,3 @@
return
if(pai && !pai.holoform)
pai.emp_act(severity)
-
diff --git a/code/game/objects/items/devices/radio/encryptionkey.dm b/code/game/objects/items/devices/radio/encryptionkey.dm
index e897137dedc2d..3b73a5bbe7f0a 100644
--- a/code/game/objects/items/devices/radio/encryptionkey.dm
+++ b/code/game/objects/items/devices/radio/encryptionkey.dm
@@ -82,7 +82,7 @@
/obj/item/encryptionkey/heads/captain
name = "\proper the captain's encryption key"
icon_state = "cap_cypherkey"
- channels = list(RADIO_CHANNEL_COMMAND = 1, RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_ENGINEERING = 0, RADIO_CHANNEL_SCIENCE = 0, RADIO_CHANNEL_MEDICAL = 0, RADIO_CHANNEL_SUPPLY = 0, RADIO_CHANNEL_SERVICE = 0)
+ channels = list(RADIO_CHANNEL_COMMAND = 1, RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_ENGINEERING = 0, RADIO_CHANNEL_SCIENCE = 0, RADIO_CHANNEL_MEDICAL = 0, RADIO_CHANNEL_SUPPLY = 0, RADIO_CHANNEL_SERVICE = 0, RADIO_CHANNEL_EXPLORATION = 0)
/obj/item/encryptionkey/heads/rd
name = "\proper the research director's encryption key"
@@ -119,11 +119,26 @@
icon_state = "cargo_cypherkey"
channels = list(RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SCIENCE = 1)
+/obj/item/encryptionkey/headset_exp
+ name = "exploration encryption key"
+ icon_state = "exp_cypherkey"
+ channels = list(RADIO_CHANNEL_EXPLORATION = 1)
+
+/obj/item/encryptionkey/headset_expteam
+ name = "exploration team encryption key"
+ icon_state = "expteam_cypherkey"
+ channels = list(RADIO_CHANNEL_EXPLORATION = 1, RADIO_CHANNEL_SCIENCE = 1)
+
/obj/item/encryptionkey/headset_service
name = "service radio encryption key"
icon_state = "srv_cypherkey"
channels = list(RADIO_CHANNEL_SERVICE = 1)
+/obj/item/encryptionkey/headset_curator
+ name = "curator radio encryption key"
+ icon_state = "srv_cypherkey"
+ channels = list(RADIO_CHANNEL_SERVICE = 1, RADIO_CHANNEL_EXPLORATION = 1)
+
/obj/item/encryptionkey/headset_cent
name = "\improper CentCom radio encryption key"
icon_state = "cent_cypherkey"
@@ -131,7 +146,7 @@
channels = list(RADIO_CHANNEL_CENTCOM = 1)
/obj/item/encryptionkey/ai //ported from NT, this goes 'inside' the AI.
- channels = list(RADIO_CHANNEL_COMMAND = 1, RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_ENGINEERING = 1, RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_MEDICAL = 1, RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SERVICE = 1, RADIO_CHANNEL_AI_PRIVATE = 1)
+ channels = list(RADIO_CHANNEL_COMMAND = 1, RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_ENGINEERING = 1, RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_MEDICAL = 1, RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SERVICE = 1, RADIO_CHANNEL_EXPLORATION = 1, RADIO_CHANNEL_AI_PRIVATE = 1)
/obj/item/encryptionkey/secbot
channels = list(RADIO_CHANNEL_AI_PRIVATE = 1, RADIO_CHANNEL_SECURITY = 1)
diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm
index 08503c3352d81..aacfa105fd8ef 100644
--- a/code/game/objects/items/devices/radio/headset.dm
+++ b/code/game/objects/items/devices/radio/headset.dm
@@ -9,6 +9,7 @@ GLOBAL_LIST_INIT(channel_tokens, list(
RADIO_CHANNEL_CENTCOM = RADIO_TOKEN_CENTCOM,
RADIO_CHANNEL_SYNDICATE = RADIO_TOKEN_SYNDICATE,
RADIO_CHANNEL_SUPPLY = RADIO_TOKEN_SUPPLY,
+ RADIO_CHANNEL_EXPLORATION = RADIO_TOKEN_EXPLORATION,
RADIO_CHANNEL_SERVICE = RADIO_TOKEN_SERVICE,
MODE_BINARY = MODE_TOKEN_BINARY,
RADIO_CHANNEL_AI_PRIVATE = RADIO_TOKEN_AI_PRIVATE
@@ -21,6 +22,7 @@ GLOBAL_LIST_INIT(channel_tokens, list(
item_state = "headset"
materials = list(/datum/material/iron=75)
subspace_transmission = TRUE
+ headset = TRUE
canhear_range = 0 // can't hear headsets from very far away
var/bang_protect = 0 //this isn't technically clothing so it needs its own bang_protect var
@@ -61,7 +63,7 @@ GLOBAL_LIST_INIT(channel_tokens, list(
QDEL_NULL(keyslot2)
return ..()
-/obj/item/radio/headset/talk_into(mob/living/M, message, channel, list/spans,datum/language/language)
+/obj/item/radio/headset/talk_into(mob/living/M, message, channel, list/spans, datum/language/language, list/message_mods)
if (!listening)
return ITALICS | REDUCE_RANGE
return ..()
@@ -219,10 +221,22 @@ GLOBAL_LIST_INIT(channel_tokens, list(
/obj/item/radio/headset/headset_cargo
name = "supply radio headset"
- desc = "A headset used by the QM and his slaves."
+ desc = "A headset used by the QM's slaves."
icon_state = "cargo_headset"
keyslot = new /obj/item/encryptionkey/headset_cargo
+/obj/item/radio/headset/headset_quartermaster
+ name = "quartermaster radio headset"
+ desc = "A headset used by the QM."
+ icon_state = "cargo_headset"
+ keyslot = new /obj/item/encryptionkey/headset_cargo
+
+/obj/item/radio/headset/headset_exploration
+ name = "exploration radio headset"
+ desc = "A headset used by exploration teams."
+ icon_state = "exploration_headset"
+ keyslot = new /obj/item/encryptionkey/headset_expteam
+
/obj/item/radio/headset/headset_cargo/mining
name = "mining radio headset"
desc = "Headset used by shaft miners."
@@ -235,6 +249,12 @@ GLOBAL_LIST_INIT(channel_tokens, list(
icon_state = "srv_headset"
keyslot = new /obj/item/encryptionkey/headset_service
+/obj/item/radio/headset/headset_curator
+ name = "curator radio headset"
+ desc = "Headset used by the curator, which allows for communication with the exploration team."
+ icon_state = "srv_headset"
+ keyslot = new /obj/item/encryptionkey/headset_curator
+
/obj/item/radio/headset/headset_cent
name = "\improper CentCom headset"
desc = "A headset used by the upper echelons of Nanotrasen."
@@ -287,6 +307,7 @@ GLOBAL_LIST_INIT(channel_tokens, list(
keyslot2 = null
recalculateChannels()
+ ui_update()
to_chat(user, "You pop out the encryption keys in the headset.")
else
@@ -309,6 +330,7 @@ GLOBAL_LIST_INIT(channel_tokens, list(
recalculateChannels()
+ ui_update()
else
return ..()
diff --git a/code/game/objects/items/devices/radio/intercom.dm b/code/game/objects/items/devices/radio/intercom.dm
index 341ee5c86fb2b..49c7c9cdd4535 100644
--- a/code/game/objects/items/devices/radio/intercom.dm
+++ b/code/game/objects/items/devices/radio/intercom.dm
@@ -7,6 +7,7 @@
canhear_range = 2
dog_fashion = null
unscrewed = FALSE
+ layer = ABOVE_WINDOW_LAYER
/obj/item/radio/intercom/unscrewed
unscrewed = TRUE
@@ -58,6 +59,9 @@
/obj/item/radio/intercom/attack_ai(mob/user)
interact(user)
+/obj/item/radio/intercom/attack_paw(mob/user)
+ return attack_hand(user)
+
/obj/item/radio/intercom/attack_hand(mob/user)
. = ..()
if(.)
@@ -69,7 +73,10 @@
ui_interact(user)
/obj/item/radio/intercom/ui_state(mob/user)
- return GLOB.default_state
+ if(issilicon(user)) // Silicons can't use physical state remotely
+ return GLOB.default_state
+
+ return GLOB.physical_state // But monkeys can't use default state, and they can already use hotkeys
/obj/item/radio/intercom/can_receive(freq, level)
if(!on)
@@ -78,7 +85,7 @@
return FALSE
if(!(0 in level))
var/turf/position = get_turf(src)
- if(isnull(position) || !(position.z in level))
+ if(isnull(position) || !(position.get_virtual_z_level() in level))
return FALSE
if(!listening)
return FALSE
@@ -89,8 +96,8 @@
return TRUE
-/obj/item/radio/intercom/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans, message_mode)
- if(message_mode == MODE_INTERCOM)
+/obj/item/radio/intercom/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans, list/message_mods = list())
+ if(message_mods[RADIO_EXTENSION] == MODE_INTERCOM)
return // Avoid hearing the same thing twice
return ..()
@@ -112,11 +119,13 @@
/**
* Proc called whenever the intercom's area loses or gains power. Responsible for setting the `on` variable and calling `update_icon()`.
*
- * Normally called after the intercom's area recieves the `COMSIG_AREA_POWER_CHANGE` signal, but it can also be called directly.
+ * Normally called after the intercom's area receives the `COMSIG_AREA_POWER_CHANGE` signal, but it can also be called directly.
* Arguments:
* * source - the area that just had a power change.
*/
/obj/item/radio/intercom/proc/AreaPowerCheck(datum/source)
+ SIGNAL_HANDLER
+
var/area/current_area = get_area(src)
if(!current_area)
on = FALSE
@@ -136,3 +145,9 @@
pixel_shift = 29
inverse = TRUE
materials = list(/datum/material/iron = 75, /datum/material/glass = 25)
+
+/obj/item/radio/intercom/chapel
+ name = "Confessional intercom"
+ anonymize = TRUE
+ frequency = 1481
+ broadcasting = TRUE
diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm
index 71a8ca6596915..acaf3c7e715cd 100644
--- a/code/game/objects/items/devices/radio/radio.dm
+++ b/code/game/objects/items/devices/radio/radio.dm
@@ -8,7 +8,7 @@
desc = "A basic handheld radio that communicates with local telecommunication networks."
dog_fashion = /datum/dog_fashion/back
- flags_1 = CONDUCT_1 | HEAR_1
+ flags_1 = CONDUCT_1
slot_flags = ITEM_SLOT_BELT
throw_speed = 3
throw_range = 7
@@ -20,6 +20,7 @@
var/frequency = FREQ_COMMON
var/canhear_range = 3 // The range around the radio in which mobs can hear what it receives.
var/emped = 0 // Tracks the number of EMPs currently stacked.
+ var/headset = FALSE
var/broadcasting = FALSE // Whether the radio will transmit dialogue it hears nearby.
var/listening = TRUE // Whether the radio is currently receiving.
@@ -32,6 +33,9 @@
var/use_command = FALSE // If true, broadcasts will be large and BOLD.
var/command = FALSE // If true, use_command can be toggled at will.
+ ///makes anyone who is talking through this anonymous.
+ var/anonymize = FALSE
+
// Encryption key handling
var/obj/item/encryptionkey/keyslot
var/translate_binary = FALSE // If true, can hear the special binary channel.
@@ -39,6 +43,7 @@
var/syndie = FALSE // If true, hears all well-known channels automatically, and can say/hear on the Syndicate channel.
var/list/channels = list() // Map from name (see communications.dm) to on/off. First entry is current department (:h).
var/list/secure_radio_connections
+ var/radio_silent = FALSE // If true, radio doesn't make sound effects (ie for Syndicate internal radio implants)
/obj/item/radio/suicide_act(mob/living/user)
user.visible_message("[user] starts bouncing [src] off [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!")
@@ -75,6 +80,7 @@
keyslot = new /obj/item/encryptionkey/syndicate
syndie = 1
recalculateChannels()
+ ui_update()
/obj/item/radio/Destroy()
remove_radio_all(src) //Just to be sure
@@ -94,10 +100,28 @@
for(var/ch_name in channels)
secure_radio_connections[ch_name] = add_radio(src, GLOB.radiochannels[ch_name])
+ become_hearing_sensitive(ROUNDSTART_TRAIT)
+
/obj/item/radio/ComponentInitialize()
. = ..()
AddComponent(/datum/component/empprotection, EMP_PROTECT_WIRES)
+/obj/item/radio/AltClick(mob/user)
+ if(headset)
+ . = ..()
+ else if(user.canUseTopic(src, !issilicon(user), TRUE, FALSE))
+ broadcasting = !broadcasting
+ to_chat(user, "You toggle broadcasting [broadcasting ? "on" : "off"].")
+ ui_update()
+
+/obj/item/radio/CtrlShiftClick(mob/user)
+ if(headset)
+ . = ..()
+ else if(user.canUseTopic(src, !issilicon(user), TRUE, FALSE))
+ listening = !listening
+ to_chat(user, "You toggle speaker [listening ? "on" : "off"].")
+ ui_update()
+
/obj/item/radio/interact(mob/user)
if(unscrewed && !isAI(user))
wires.interact(user)
@@ -105,7 +129,6 @@
else
..()
-
/obj/item/radio/ui_state(mob/user)
return GLOB.inventory_state
@@ -189,15 +212,15 @@
recalculateChannels()
. = TRUE
-/obj/item/radio/talk_into(atom/movable/M, message, channel, list/spans, datum/language/language)
+/obj/item/radio/talk_into(atom/movable/M, message, channel, list/spans, datum/language/language, list/message_mods)
if(!spans)
spans = list(M.speech_span)
if(!language)
language = M.get_selected_language()
- INVOKE_ASYNC(src, .proc/talk_into_impl, M, message, channel, spans.Copy(), language)
+ INVOKE_ASYNC(src, .proc/talk_into_impl, M, message, channel, spans.Copy(), language, message_mods)
return ITALICS | REDUCE_RANGE
-/obj/item/radio/proc/talk_into_impl(atom/movable/M, message, channel, list/spans, datum/language/language)
+/obj/item/radio/proc/talk_into_impl(atom/movable/M, message, channel, list/spans, datum/language/language, list/message_mods)
if(!on)
return // the device has to be on
if(!M || !message)
@@ -207,6 +230,13 @@
if(!M.IsVocal())
return
+ if(!radio_silent)//Radios make small static noises now
+ var/mob/sender = loc
+ if(istype(sender) && sender.hears_radio())
+ var/sound/radio_sound = sound(pick("sound/effects/radio1.ogg", "sound/effects/radio2.ogg"), volume = 50)
+ radio_sound.frequency = get_rand_frequency()
+ SEND_SOUND(sender, radio_sound)
+
if(use_command)
spans |= SPAN_COMMAND
@@ -233,17 +263,14 @@
channel = null
// Nearby active jammers prevent the message from transmitting
- var/turf/position = get_turf(src)
- for(var/obj/item/jammer/jammer in GLOB.active_jammers)
- var/turf/jammer_turf = get_turf(jammer)
- if(position.z == jammer_turf.z && (get_dist(position, jammer_turf) <= jammer.range))
- return
+ if(is_jammed())
+ return
// Determine the identity information which will be attached to the signal.
var/atom/movable/virtualspeaker/speaker = new(null, M, src)
// Construct the signal
- var/datum/signal/subspace/vocal/signal = new(src, freq, speaker, language, message, spans)
+ var/datum/signal/subspace/vocal/signal = new(src, freq, speaker, language, message, spans, message_mods)
// Independent radios, on the CentCom frequency, reach all independent radios
if (independent && (freq == FREQ_CENTCOM || freq == FREQ_CTF_RED || freq == FREQ_CTF_BLUE))
@@ -266,30 +293,27 @@
/obj/item/radio/proc/backup_transmission(datum/signal/subspace/vocal/signal)
var/turf/T = get_turf(src)
- if (signal.data["done"] && (T.z in signal.levels))
+ if (signal.data["done"] && (T.get_virtual_z_level() in signal.levels))
return
// Okay, the signal was never processed, send a mundane broadcast.
signal.data["compression"] = 0
signal.transmission_method = TRANSMISSION_RADIO
- signal.levels = list(T.z)
+ signal.levels = list(T.get_virtual_z_level())
signal.broadcast()
-/obj/item/radio/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
+/obj/item/radio/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods = list())
. = ..()
if(radio_freq || !broadcasting || get_dist(src, speaker) > canhear_range)
return
- if(message_mode == MODE_WHISPER || message_mode == MODE_WHISPER_CRIT)
- // radios don't pick up whispers very well
- raw_message = stars(raw_message)
- else if(message_mode == MODE_L_HAND || message_mode == MODE_R_HAND)
+ if(message_mods[RADIO_EXTENSION] == MODE_L_HAND || message_mods[RADIO_EXTENSION] == MODE_R_HAND)
// try to avoid being heard double
if (loc == speaker && ismob(speaker))
var/mob/M = speaker
var/idx = M.get_held_index_of_item(src)
// left hands are odd slots
- if (idx && (idx % 2) == (message_mode == MODE_L_HAND))
+ if (idx && (idx % 2) == (message_mods[RADIO_EXTENSION] == MODE_L_HAND))
return
talk_into(speaker, raw_message, , spans, language=message_language)
@@ -305,7 +329,7 @@
return independent // hard-ignores the z-level check
if (!(0 in level))
var/turf/position = get_turf(src)
- if(!position || !(position.z in level))
+ if(!position || !(position.get_virtual_z_level() in level))
return FALSE
// allow checks: are we listening on that frequency?
@@ -327,6 +351,8 @@
. += "It can be attached and modified."
else
. += "It cannot be modified or attached."
+ if (in_range(src, user) && !headset)
+ . += "Ctrl-Shift-click on the [name] to toggle speaker. Alt-click on the [name] to toggle broadcasting."
/obj/item/radio/attackby(obj/item/W, mob/user, params)
add_fingerprint(user)
@@ -398,6 +424,7 @@
keyslot = null
recalculateChannels()
+ ui_update()
to_chat(user, "You pop out the encryption key in the radio.")
else
@@ -414,6 +441,7 @@
keyslot = W
recalculateChannels()
+ ui_update()
/obj/item/radio/off // Station bounced radios, their only difference is spawning with the speakers off, this was made to help the lag.
diff --git a/code/game/objects/items/devices/reverse_bear_trap.dm b/code/game/objects/items/devices/reverse_bear_trap.dm
index 282ce9a39fc1e..f3d900e4fe560 100644
--- a/code/game/objects/items/devices/reverse_bear_trap.dm
+++ b/code/game/objects/items/devices/reverse_bear_trap.dm
@@ -33,12 +33,12 @@
STOP_PROCESSING(SSprocessing, src)
return ..()
-/obj/item/reverse_bear_trap/process()
+/obj/item/reverse_bear_trap/process(delta_time)
if(!ticking)
return
- time_left--
+ time_left -= delta_time
soundloop2.mid_length = max(0.5, time_left - 5) //beepbeepbeepbeepbeep
- if(!time_left || !isliving(loc))
+ if(time_left <= 0 || !isliving(loc))
playsound(src, 'sound/machines/microwave/microwave-end.ogg', 100, FALSE)
soundloop.stop()
soundloop2.stop()
@@ -48,7 +48,7 @@
/obj/item/reverse_bear_trap/attack_hand(mob/user)
if(iscarbon(user))
var/mob/living/carbon/C = user
- if(C.get_item_by_slot(SLOT_HEAD) == src)
+ if(C.get_item_by_slot(ITEM_SLOT_HEAD) == src)
if(HAS_TRAIT_FROM(src, TRAIT_NODROP, REVERSE_BEAR_TRAP_TRAIT) && !struggling)
struggling = TRUE
var/fear_string
@@ -82,26 +82,26 @@
..()
/obj/item/reverse_bear_trap/attack(mob/living/target, mob/living/user)
- if(target.get_item_by_slot(SLOT_HEAD))
+ if(target.get_item_by_slot(ITEM_SLOT_HEAD))
to_chat(user, "Remove [target.p_their()] headgear first!")
return
target.visible_message("[user] starts forcing [src] onto [target]'s head!", \
"[target] starts forcing [src] onto your head!", "You hear clanking.")
to_chat(user, "You start forcing [src] onto [target]'s head...")
- if(!do_after(user, 30, target = target) || target.get_item_by_slot(SLOT_HEAD))
+ if(!do_after(user, 30, target = target) || target.get_item_by_slot(ITEM_SLOT_HEAD))
return
target.visible_message("[user] forces and locks [src] onto [target]'s head!", \
"[target] locks [src] onto your head!", "You hear a click, and then a timer ticking down.")
to_chat(user, "You force [src] onto [target]'s head and click the padlock shut.")
user.dropItemToGround(src)
- target.equip_to_slot_if_possible(src, SLOT_HEAD)
+ target.equip_to_slot_if_possible(src, ITEM_SLOT_HEAD)
arm()
notify_ghosts("[user] put a reverse bear trap on [target]!", source = src, action = NOTIFY_ORBIT, flashwindow = FALSE, ghost_sound = 'sound/machines/beep.ogg', notify_volume = 75, header = "Reverse bear trap armed")
/obj/item/reverse_bear_trap/proc/snap()
reset()
var/mob/living/carbon/human/H = loc
- if(!istype(H) || H.get_item_by_slot(SLOT_HEAD) != src)
+ if(!istype(H) || H.get_item_by_slot(ITEM_SLOT_HEAD) != src)
visible_message("[src]'s jaws snap open with an ear-piercing crack!")
playsound(src, 'sound/effects/snap.ogg', 75, TRUE)
else
diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm
index 47b1ec69ca636..8620214b31a6e 100644
--- a/code/game/objects/items/devices/scanners.dm
+++ b/code/game/objects/items/devices/scanners.dm
@@ -87,7 +87,6 @@ GENE SCANNER
throw_speed = 3
throw_range = 7
materials = list(/datum/material/iron=200)
- var/mode = 1
var/scanmode = 0
var/advanced = FALSE
@@ -108,7 +107,7 @@ GENE SCANNER
// Clumsiness/brain damage check
- if ((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(50))
+ if((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(50))
user.visible_message("[user] analyzes the floor's vitals!", \
"You stupidly try to analyze the floor's vitals!")
to_chat(user, "Analyzing results for The floor:\n\tOverall status: Healthy")
@@ -121,7 +120,7 @@ GENE SCANNER
"You analyze [M]'s vitals.")
if(scanmode == 0)
- healthscan(user, M, mode, advanced)
+ healthscan(user, M, advanced=advanced)
else if(scanmode == 1)
chemscan(user, M)
@@ -163,11 +162,11 @@ GENE SCANNER
to_chat(user, "\tSubject appears to be suffering from fatigue.")
if(advanced)
to_chat(user, "\tFatigue Level: [M.getStaminaLoss()]%.")
- if (M.getCloneLoss())
+ if(M.getCloneLoss())
to_chat(user, "\tSubject appears to have [M.getCloneLoss() > 30 ? "Severe" : "Minor"] cellular damage.")
if(advanced)
to_chat(user, "\tCellular Damage Level: [M.getCloneLoss()].")
- if (!M.getorgan(/obj/item/organ/brain))
+ if(!M.getorgan(/obj/item/organ/brain))
to_chat(user, "\tSubject lacks a brain.")
if(iscarbon(M))
var/mob/living/carbon/C = M
@@ -190,7 +189,7 @@ GENE SCANNER
if(advanced)
to_chat(user, "\tBrain Activity Level: [(200 - M.getOrganLoss(ORGAN_SLOT_BRAIN))/2]%.")
- if (M.radiation)
+ if(M.radiation)
to_chat(user, "\tSubject is irradiated.")
if(advanced)
to_chat(user, "\tRadiation Level: [M.radiation]%.")
@@ -249,7 +248,7 @@ GENE SCANNER
// Body part damage report
- if(iscarbon(M) && mode == 1)
+ if(iscarbon(M))
var/mob/living/carbon/C = M
var/list/damaged = C.get_damaged_bodyparts(1,1)
if(length(damaged)>0 || oxy_loss>0 || tox_loss>0 || fire_loss>0)
@@ -337,27 +336,27 @@ GENE SCANNER
var/mob/living/carbon/human/H = M
var/datum/species/S = H.dna.species
var/mutant = FALSE
- if (H.dna.check_mutation(HULK))
+ if(H.dna.check_mutation(HULK))
mutant = TRUE
- else if (S.mutantlungs != initial(S.mutantlungs))
+ else if(S.mutantlungs != initial(S.mutantlungs))
mutant = TRUE
- else if (S.mutant_brain != initial(S.mutant_brain))
+ else if(S.mutant_brain != initial(S.mutant_brain))
mutant = TRUE
- else if (S.mutant_heart != initial(S.mutant_heart))
+ else if(S.mutant_heart != initial(S.mutant_heart))
mutant = TRUE
- else if (S.mutanteyes != initial(S.mutanteyes))
+ else if(S.mutanteyes != initial(S.mutanteyes))
mutant = TRUE
- else if (S.mutantears != initial(S.mutantears))
+ else if(S.mutantears != initial(S.mutantears))
mutant = TRUE
- else if (S.mutanthands != initial(S.mutanthands))
+ else if(S.mutanthands != initial(S.mutanthands))
mutant = TRUE
- else if (S.mutanttongue != initial(S.mutanttongue))
+ else if(S.mutanttongue != initial(S.mutanttongue))
mutant = TRUE
- else if (S.mutanttail != initial(S.mutanttail))
+ else if(S.mutanttail != initial(S.mutanttail))
mutant = TRUE
- else if (S.mutantliver != initial(S.mutantliver))
+ else if(S.mutantliver != initial(S.mutantliver))
mutant = TRUE
- else if (S.mutantstomach != initial(S.mutantstomach))
+ else if(S.mutantstomach != initial(S.mutantstomach))
mutant = TRUE
to_chat(user, "Species: [S.name][mutant ? "-derived mutant" : ""]")
@@ -399,13 +398,14 @@ GENE SCANNER
else
to_chat(user, "Blood level: [blood_percent] %, [C.blood_volume] cl, type: [blood_type]")
- var/cyberimp_detect
+ var/list/cyberimp_detect = list()
for(var/obj/item/organ/cyberimp/CI in C.internal_organs)
if(CI.status == ORGAN_ROBOTIC && !CI.syndicate_implant)
- cyberimp_detect += "[C.name] is modified with a [CI.name]. "
- if(cyberimp_detect)
+ cyberimp_detect += CI.name
+ if(length(cyberimp_detect))
to_chat(user, "Detected cybernetic modifications:")
- to_chat(user, "[cyberimp_detect]")
+ for(var/name in cyberimp_detect)
+ to_chat(user, "[name]")
SEND_SIGNAL(M, COMSIG_NANITE_SCAN, user, FALSE)
/proc/chemscan(mob/living/user, mob/living/M)
@@ -424,20 +424,6 @@ GENE SCANNER
else
to_chat(user, "Subject is not addicted to any reagents.")
-/obj/item/healthanalyzer/verb/toggle_mode()
- set name = "Switch Verbosity"
- set category = "Object"
-
- if(usr.incapacitated())
- return
-
- mode = !mode
- switch (mode)
- if(1)
- to_chat(usr, "The scanner now shows specific limb damage.")
- if(0)
- to_chat(usr, "The scanner no longer shows limb damage.")
-
/obj/item/healthanalyzer/advanced
name = "advanced health analyzer"
icon_state = "health_adv"
@@ -487,58 +473,18 @@ GENE SCANNER
/obj/item/analyzer/attack_self(mob/user)
add_fingerprint(user)
- if (user.stat || user.eye_blind)
+ if(user.stat || user.eye_blind)
return
- var/turf/location = user.loc
+ //Functionality moved down to proc/scan_turf()
+ var/turf/location = get_turf(user)
+
if(!istype(location))
return
- var/datum/gas_mixture/environment = location.return_air()
-
- var/pressure = environment.return_pressure()
- var/total_moles = environment.total_moles()
-
- to_chat(user, "Results:")
- if(abs(pressure - ONE_ATMOSPHERE) < 10)
- to_chat(user, "Pressure: [round(pressure, 0.01)] kPa")
- else
- to_chat(user, "Pressure: [round(pressure, 0.01)] kPa")
- if(total_moles)
- var/o2_concentration = environment.get_moles(/datum/gas/oxygen)/total_moles
- var/n2_concentration = environment.get_moles(/datum/gas/nitrogen)/total_moles
- var/co2_concentration = environment.get_moles(/datum/gas/carbon_dioxide)/total_moles
- var/plasma_concentration = environment.get_moles(/datum/gas/plasma)/total_moles
-
- if(abs(n2_concentration - N2STANDARD) < 20)
- to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/nitrogen), 0.01)] mol)")
- else
- to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/nitrogen), 0.01)] mol)")
-
- if(abs(o2_concentration - O2STANDARD) < 2)
- to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/oxygen), 0.01)] mol)")
- else
- to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/oxygen), 0.01)] mol)")
-
- if(co2_concentration > 0.01)
- to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/carbon_dioxide), 0.01)] mol)")
- else
- to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/carbon_dioxide), 0.01)] mol)")
-
- if(plasma_concentration > 0.005)
- to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/plasma), 0.01)] mol)")
- else
- to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(environment.get_moles(/datum/gas/plasma), 0.01)] mol)")
-
- for(var/id in environment.get_gases())
- if(id in GLOB.hardcoded_gases)
- continue
- var/gas_concentration = environment.get_moles(id)/total_moles
- to_chat(user, "[GLOB.meta_gas_info[id][META_GAS_NAME]]: [round(gas_concentration*100, 0.01)] % ([round(environment.get_moles(id), 0.01)] mol)")
- to_chat(user, "Temperature: [round(environment.return_temperature()-T0C, 0.01)] °C ([round(environment.return_temperature(), 0.01)] K)")
+ scan_turf(user, location)
/obj/item/analyzer/AltClick(mob/user) //Barometer output for measuring when the next storm happens
- ..()
if(user.canUseTopic(src, BE_CLOSE))
@@ -629,7 +575,7 @@ GENE SCANNER
for(var/id in air_contents.get_gases())
var/gas_concentration = air_contents.get_moles(id)/total_moles
- to_chat(user, "[GLOB.meta_gas_info[id][META_GAS_NAME]]: [round(gas_concentration*100, 0.01)] % ([round(air_contents.get_moles(id), 0.01)] mol)")
+ to_chat(user, "[GLOB.gas_data.names[id]]: [round(gas_concentration*100, 0.01)] % ([round(air_contents.get_moles(id), 0.01)] mol)")
to_chat(user, "Temperature: [round(temperature - T0C,0.01)] °C ([round(temperature, 0.01)] K)")
else
@@ -644,6 +590,64 @@ GENE SCANNER
to_chat(user, "Instability of the last fusion reaction: [instability].")
return TRUE
+/obj/item/analyzer/proc/scan_turf(mob/user, turf/location)
+ var/datum/gas_mixture/environment = location.return_air()
+
+ var/pressure = environment.return_pressure()
+ var/total_moles = environment.total_moles()
+
+ to_chat(user, "Results:")
+ if(abs(pressure - ONE_ATMOSPHERE) < 10)
+ to_chat(user, "Pressure: [round(pressure, 0.01)] kPa")
+ else
+ to_chat(user, "Pressure: [round(pressure, 0.01)] kPa")
+ if(total_moles)
+ var/o2_concentration = environment.get_moles(GAS_O2)/total_moles
+ var/n2_concentration = environment.get_moles(GAS_N2)/total_moles
+ var/co2_concentration = environment.get_moles(GAS_CO2)/total_moles
+ var/plasma_concentration = environment.get_moles(GAS_PLASMA)/total_moles
+
+ if(abs(n2_concentration - N2STANDARD) < 20)
+ to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(environment.get_moles(GAS_N2), 0.01)] mol)")
+ else
+ to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(environment.get_moles(GAS_N2), 0.01)] mol)")
+
+ if(abs(o2_concentration - O2STANDARD) < 2)
+ to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(environment.get_moles(GAS_O2), 0.01)] mol)")
+ else
+ to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(environment.get_moles(GAS_O2), 0.01)] mol)")
+
+ if(co2_concentration > 0.01)
+ to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(environment.get_moles(GAS_CO2), 0.01)] mol)")
+ else
+ to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(environment.get_moles(GAS_CO2), 0.01)] mol)")
+
+ if(plasma_concentration > 0.005)
+ to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(environment.get_moles(GAS_PLASMA), 0.01)] mol)")
+ else
+ to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(environment.get_moles(GAS_PLASMA), 0.01)] mol)")
+
+ for(var/id in environment.get_gases())
+ if(id in GLOB.hardcoded_gases)
+ continue
+ var/gas_concentration = environment.get_moles(id)/total_moles
+ to_chat(user, "[GLOB.gas_data.names[id]]: [round(gas_concentration*100, 0.01)] % ([round(environment.get_moles(id), 0.01)] mol)")
+ to_chat(user, "Temperature: [round(environment.return_temperature()-T0C, 0.01)] °C ([round(environment.return_temperature(), 0.01)] K)")
+
+/obj/item/analyzer/ranged
+ desc = "A hand-held scanner which uses advanced spectroscopy and infrared readings to analyze gases as a distance. Alt-Click to use the built in barometer function."
+ name = "long-range analyzer"
+ icon = 'icons/obj/device.dmi'
+ icon_state = "ranged_analyzer"
+
+/obj/item/analyzer/ranged/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(target.tool_act(user, src, tool_behaviour))
+ return
+ // Tool act didn't scan it, so let's get it's turf.
+ var/turf/location = get_turf(target)
+ scan_turf(user, location)
+
//slime scanner
/obj/item/slime_scanner
@@ -664,7 +668,7 @@ GENE SCANNER
/obj/item/slime_scanner/attack(mob/living/M, mob/living/user)
if(user.stat || user.eye_blind)
return
- if (!isslime(M))
+ if(!isslime(M))
to_chat(user, "This device can only scan slimes!")
return
var/mob/living/simple_animal/slime/T = M
@@ -675,17 +679,17 @@ GENE SCANNER
to_chat(user, "Slime scan results:")
to_chat(user, "[T.colour] [T.is_adult ? "adult" : "baby"] slime")
to_chat(user, "Nutrition: [T.nutrition]/[T.get_max_nutrition()]")
- if (T.nutrition < T.get_starve_nutrition())
+ if(T.nutrition < T.get_starve_nutrition())
to_chat(user, "Warning: slime is starving!")
- else if (T.nutrition < T.get_hunger_nutrition())
+ else if(T.nutrition < T.get_hunger_nutrition())
to_chat(user, "Warning: slime is hungry")
to_chat(user, "Electric change strength: [T.powerlevel]")
to_chat(user, "Health: [round(T.health/T.maxHealth,0.01)*100]%")
- if (T.slime_mutation[4] == T.colour)
+ if(T.slime_mutation[4] == T.colour)
to_chat(user, "This slime does not evolve any further.")
else
- if (T.slime_mutation[3] == T.slime_mutation[4])
- if (T.slime_mutation[2] == T.slime_mutation[1])
+ if(T.slime_mutation[3] == T.slime_mutation[4])
+ if(T.slime_mutation[2] == T.slime_mutation[1])
to_chat(user, "Possible mutation: [T.slime_mutation[3]]")
to_chat(user, "Genetic destability: [T.mutation_chance/2] % chance of mutation on splitting")
else
@@ -694,12 +698,59 @@ GENE SCANNER
else
to_chat(user, "Possible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]], [T.slime_mutation[4]]")
to_chat(user, "Genetic destability: [T.mutation_chance] % chance of mutation on splitting")
- if (T.cores > 1)
+ if(T.cores > 1)
to_chat(user, "Multiple cores detected")
to_chat(user, "Growth progress: [T.amount_grown]/[SLIME_EVOLUTION_THRESHOLD]")
if(T.effectmod)
to_chat(user, "Core mutation in progress: [T.effectmod]")
to_chat(user, "Progress in core mutation: [T.applied] / [SLIME_EXTRACT_CROSSING_REQUIRED]")
+ if(T.transformeffects != SLIME_EFFECT_DEFAULT)
+ var/slimeeffect = "\nTransformative extract effect detected: "
+ if(T.transformeffects & SLIME_EFFECT_GREY)
+ slimeeffect += "grey"
+ if(T.transformeffects & SLIME_EFFECT_ORANGE)
+ slimeeffect += "orange"
+ if(T.transformeffects & SLIME_EFFECT_PURPLE)
+ slimeeffect += "purple"
+ if(T.transformeffects & SLIME_EFFECT_BLUE)
+ slimeeffect += "blue"
+ if(T.transformeffects & SLIME_EFFECT_METAL)
+ slimeeffect += "metal"
+ if(T.transformeffects & SLIME_EFFECT_YELLOW)
+ slimeeffect += "yellow"
+ if(T.transformeffects & SLIME_EFFECT_DARK_PURPLE)
+ slimeeffect += "dark purple"
+ if(T.transformeffects & SLIME_EFFECT_DARK_BLUE)
+ slimeeffect += "dark blue"
+ if(T.transformeffects & SLIME_EFFECT_SILVER)
+ slimeeffect += "silver"
+ if(T.transformeffects & SLIME_EFFECT_BLUESPACE)
+ slimeeffect += "bluespace"
+ if(T.transformeffects & SLIME_EFFECT_SEPIA)
+ slimeeffect += "sepia"
+ if(T.transformeffects & SLIME_EFFECT_CERULEAN)
+ slimeeffect += "cerulean"
+ if(T.transformeffects & SLIME_EFFECT_PYRITE)
+ slimeeffect += "pyrite"
+ if(T.transformeffects & SLIME_EFFECT_RED)
+ slimeeffect += "red"
+ if(T.transformeffects & SLIME_EFFECT_GREEN)
+ slimeeffect += "green"
+ if(T.transformeffects & SLIME_EFFECT_PINK)
+ slimeeffect += "pink"
+ if(T.transformeffects & SLIME_EFFECT_GOLD)
+ slimeeffect += "gold"
+ if(T.transformeffects & SLIME_EFFECT_OIL)
+ slimeeffect += "oil"
+ if(T.transformeffects & SLIME_EFFECT_BLACK)
+ slimeeffect += "black"
+ if(T.transformeffects & SLIME_EFFECT_LIGHT_PINK)
+ slimeeffect += "light pink"
+ if(T.transformeffects & SLIME_EFFECT_ADAMANTINE)
+ slimeeffect += "adamantine"
+ if(T.transformeffects & SLIME_EFFECT_RAINBOW)
+ slimeeffect += "rainbow"
+ to_chat(user, "[slimeeffect].")
to_chat(user, "========================")
@@ -753,7 +804,7 @@ GENE SCANNER
/obj/item/sequence_scanner/attack(mob/living/M, mob/living/carbon/human/user)
add_fingerprint(user)
- if (!HAS_TRAIT(M, TRAIT_RADIMMUNE) && !HAS_TRAIT(M, TRAIT_BADDNA)) //no scanning if its a husk or DNA-less Species
+ if(!HAS_TRAIT(M, TRAIT_RADIMMUNE) && !HAS_TRAIT(M, TRAIT_BADDNA)) //no scanning if its a husk or DNA-less Species
user.visible_message("[user] analyzes [M]'s genetic sequence.", \
"You analyze [M]'s genetic sequence.")
gene_scan(M, user)
@@ -840,7 +891,7 @@ GENE SCANNER
slot_flags = ITEM_SLOT_BELT
w_class = WEIGHT_CLASS_NORMAL
var/scan = TRUE
- var/cooldown = -1200 //so it's charged roundstart
+ var/cooldown = -1000 //so it's charged roundstart
var/obj/item/stock_parts/scanning_module/scanner //used for upgrading!
/obj/item/extrapolator/Initialize()
@@ -881,9 +932,9 @@ GENE SCANNER
. = ..()
if(in_range(user, src) || isobserver(user))
if(!scanner)
- . += "The scanner is missing."
+ . += "The scanner is missing."
else
- . += "A class [scanner.rating] scanning module is installed. It is screwed in place."
+ . += "A class [scanner.rating] scanning module is installed. It is screwed in place."
if(cooldown > world.time - (1200 / scanner.rating))
. += "The extrapolator is still recharging!"
else
@@ -895,6 +946,8 @@ GENE SCANNER
/obj/item/extrapolator/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
. = ..()
+ if(!proximity_flag)
+ return
if(scanner)
if(!target.extrapolator_act(user, src, scan))
if(locate(/datum/component/infective) in target.datum_components)
@@ -911,7 +964,7 @@ GENE SCANNER
for(var/datum/disease/D in diseases)
if(istype(D, /datum/disease/advance))
var/datum/disease/advance/A = D
- if(A.properties["stealth"] >= (2 + scanner.rating)) //the extrapolator can detect diseases of higher stealth than a normal scanner
+ if(A.stealth >= (2 + scanner.rating)) //the extrapolator can detect diseases of higher stealth than a normal scanner
continue
to_chat(user, "[A.name], stage [A.stage]/5")
to_chat(user, "[A] has the following symptoms:")
@@ -923,7 +976,7 @@ GENE SCANNER
/obj/item/extrapolator/proc/extrapolate(atom/AM, var/list/diseases = list(), mob/user, isolate = FALSE, timer = 200)
var/list/advancediseases = list()
var/list/symptoms = list()
- if(cooldown > world.time - (1200 / scanner.rating))
+ if(cooldown > world.time - (1000 / scanner.rating))
to_chat(user, "The extrapolator is still recharging!")
return
for(var/datum/disease/advance/cantidate in diseases)
@@ -965,4 +1018,3 @@ GENE SCANNER
user.put_in_hands(B)
playsound(src, 'sound/machines/ping.ogg', 30, TRUE)
return TRUE
-
diff --git a/code/game/objects/items/devices/sound_synth.dm b/code/game/objects/items/devices/sound_synth.dm
index 4c77f082acca7..b2a179808b82f 100644
--- a/code/game/objects/items/devices/sound_synth.dm
+++ b/code/game/objects/items/devices/sound_synth.dm
@@ -34,7 +34,7 @@
"Bwoink" = "selected_sound=sound/effects/adminhelp.ogg&shiftpitch=1&volume=50",
"Double Beep" = "selected_sound=sound/machines/twobeep.ogg&shiftpitch=1&volume=50",
"Flush" = "selected_sound=sound/machines/disposalflush.ogg&shiftpitch=1&volume=40",
- "Kawaii" = "selected_sound=sound/AI/animes.ogg&shiftpitch=0&volume=60",
+ "Kawaii" = "selected_sound=sound/ai/default/animes.ogg&shiftpitch=0&volume=60",
"Startup" = "selected_sound=sound/mecha/nominal.ogg&shiftpitch=0&volume=50",
"Welding Noises" = "selected_sound=sound/items/welder.ogg&shiftpitch=1&volume=55",
"Short Slide Whistle" = "selected_sound=sound/effects/slide_whistle_short.ogg&shiftpitch=1&volume=50",
@@ -66,7 +66,6 @@
spam_flag = world.timeofday
/obj/item/soundsynth/AltClick(mob/living/carbon/user)
- . = ..()
pick_sound()
/obj/item/soundsynth/attack(mob/living/M as mob, mob/living/user as mob, def_zone)
diff --git a/code/game/objects/items/devices/spyglasses.dm b/code/game/objects/items/devices/spyglasses.dm
new file mode 100644
index 0000000000000..e23b6e6fdadc0
--- /dev/null
+++ b/code/game/objects/items/devices/spyglasses.dm
@@ -0,0 +1,110 @@
+//detective spyglasses. meant to be an example for map_popups.dm
+/obj/item/clothing/glasses/sunglasses/spy
+ desc = "Made by Nerd. Co's infiltration and surveillance department. Upon closer inspection, there's a small screen in each lens."
+ actions_types = list(/datum/action/item_action/activate_remote_view)
+ var/obj/item/clothing/accessory/spy_bug/linked_bug
+
+/obj/item/clothing/glasses/sunglasses/spy/proc/show_to_user(mob/user)//this is the meat of it. most of the map_popup usage is in this.
+ if(!user?.client)
+ return
+ if(!linked_bug)
+ user.audible_message("[src] lets off a shrill beep!")
+ if("spypopup_map" in user.client.screen_maps) //alright, the popup this object uses is already IN use, so the window is open. no point in doing any other work here, so we're good.
+ return
+ user.client.setup_popup("spypopup", 3, 3, 2)
+ user.client.register_map_obj(linked_bug.cam_screen)
+ for(var/plane in linked_bug.cam_plane_masters)
+ user.client.register_map_obj(plane)
+ linked_bug.update_view()
+
+/obj/item/clothing/glasses/sunglasses/spy/equipped(mob/user, slot)
+ . = ..()
+ if(!(slot & ITEM_SLOT_EYES))
+ user.client.close_popup("spypopup")
+
+/obj/item/clothing/glasses/sunglasses/spy/dropped(mob/user)
+ . = ..()
+ user.client.close_popup("spypopup")
+
+/obj/item/clothing/glasses/sunglasses/spy/ui_action_click(mob/user)
+ show_to_user(user)
+
+/obj/item/clothing/glasses/sunglasses/spy/item_action_slot_check(slot)
+ return slot & ITEM_SLOT_EYES
+
+/obj/item/clothing/glasses/sunglasses/spy/Destroy()
+ if(linked_bug)
+ linked_bug.linked_glasses = null
+ return ..()
+
+
+/obj/item/clothing/accessory/spy_bug
+ name = "pocket protector"
+ icon = 'icons/obj/clothing/accessories.dmi'
+ icon_state = "pocketprotector"
+ desc = "An advanced piece of espionage equipment in the shape of a pocket protector. It has a built in 360 degree camera for all your \"admirable\" needs. Microphone not included."
+ var/obj/item/clothing/glasses/sunglasses/spy/linked_glasses
+ var/atom/movable/screen/map_view/cam_screen
+ var/list/cam_plane_masters
+ // Ranges higher than one can be used to see through walls.
+ var/cam_range = 1
+ var/datum/movement_detector/tracker
+
+/obj/item/clothing/accessory/spy_bug/Initialize()
+ . = ..()
+ tracker = new /datum/movement_detector(src, CALLBACK(src, .proc/update_view))
+
+ cam_screen = new
+ cam_screen.name = "screen"
+ cam_screen.assigned_map = "spypopup_map"
+ cam_screen.del_on_map_removal = FALSE
+ cam_screen.set_position(1, 1)
+
+ // We need to add planesmasters to the popup, otherwise
+ // blending fucks up massively. Any planesmaster on the main screen does
+ // NOT apply to map popups. If there's ever a way to make planesmasters
+ // omnipresent, then this wouldn't be needed.
+ cam_plane_masters = list()
+ for(var/plane in subtypesof(/atom/movable/screen/plane_master))
+ var/atom/movable/screen/instance = new plane()
+ instance.assigned_map = "spypopup_map"
+ instance.del_on_map_removal = FALSE
+ instance.screen_loc = "spypopup_map:CENTER"
+ cam_plane_masters += instance
+
+/obj/item/clothing/accessory/spy_bug/Destroy()
+ if(linked_glasses)
+ linked_glasses.linked_bug = null
+ qdel(cam_screen)
+ QDEL_LIST(cam_plane_masters)
+ qdel(tracker)
+ return ..()
+
+/obj/item/clothing/accessory/spy_bug/proc/update_view()//this doesn't do anything too crazy, just updates the vis_contents of its screen obj
+ cam_screen.vis_contents.Cut()
+ for(var/turf/visible_turf in view(1,get_turf(src)))//fuck you usr
+ cam_screen.vis_contents += visible_turf
+
+//it needs to be linked, hence a kit.
+/obj/item/storage/box/rxglasses/spyglasskit
+ name = "spyglass kit"
+ desc = "this box contains cool nerd glasses; with built-in displays to view a linked camera."
+
+/obj/item/paper/fluff/nerddocs
+ name = "Espionage For Dummies"
+ color = "#FFFF00"
+ desc = "An eye gougingly yellow pamphlet with a badly designed image of a detective on it. the subtext says \" The Latest Way To Violate Privacy Guidelines!\" "
+ info = @{"
+Thank you for your purchase of the Nerd Co SpySpeks tm, this paper will be your quick-start guide to violating the privacy of your crewmates in three easy steps!
Step One: Nerd Co SpySpeks tm upon your face.
+Step Two: Place the included "ProfitProtektor tm" camera assembly in a place of your choosing - make sure to make heavy use of it's inconspicous design!
+Step Three: Press the "Activate Remote View" Button on the side of your SpySpeks tm to open a movable camera display in the corner of your vision, it's just that easy!
TROUBLESHOOTING
+My SpySpeks tm Make a shrill beep while attempting to use!
+A shrill beep coming from your SpySpeks means that they can't connect to the included ProfitProtektor tm, please make sure your ProfitProtektor is still active, and functional!
+ "}
+
+/obj/item/storage/box/rxglasses/spyglasskit/PopulateContents()
+ var/obj/item/clothing/accessory/spy_bug/newbug = new(src)
+ var/obj/item/clothing/glasses/sunglasses/spy/newglasses = new(src)
+ newbug.linked_glasses = newglasses
+ newglasses.linked_bug = newbug
+ new /obj/item/paper/fluff/nerddocs(src)
diff --git a/code/game/objects/items/devices/swapper.dm b/code/game/objects/items/devices/swapper.dm
index 67501e527b691..894dba5b6ecaa 100644
--- a/code/game/objects/items/devices/swapper.dm
+++ b/code/game/objects/items/devices/swapper.dm
@@ -112,3 +112,5 @@
if(ismob(B))
var/mob/M = B
to_chat(M, "[linked_swapper] activates, and you find yourself somewhere else.")
+ log_combat(user, B, "swapped to [AREACOORD(B)]")
+ log_game("[key_name(B)] has been swapped to [AREACOORD(B)]! Quantum Spin Inverter activated by [key_name(user)].")
diff --git a/code/game/objects/items/devices/taperecorder.dm b/code/game/objects/items/devices/taperecorder.dm
index 006792fe8d153..59281fbf9333c 100644
--- a/code/game/objects/items/devices/taperecorder.dm
+++ b/code/game/objects/items/devices/taperecorder.dm
@@ -7,7 +7,6 @@
lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
w_class = WEIGHT_CLASS_SMALL
- flags_1 = HEAR_1
slot_flags = ITEM_SLOT_BELT
materials = list(/datum/material/iron=60, /datum/material/glass=30)
force = 2
@@ -26,7 +25,11 @@
if(starting_tape_type)
mytape = new starting_tape_type(src)
update_icon()
+ become_hearing_sensitive()
+/obj/item/taperecorder/Destroy()
+ QDEL_NULL(mytape)
+ return ..()
/obj/item/taperecorder/examine(mob/user)
. = ..()
@@ -94,7 +97,7 @@
icon_state = "taperecorder_idle"
-/obj/item/taperecorder/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, message_mode)
+/obj/item/taperecorder/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, list/message_mods = list())
. = ..()
if(mytape && recording)
mytape.timestamp += mytape.used_capacity
diff --git a/code/game/objects/items/devices/traitordevices.dm b/code/game/objects/items/devices/traitordevices.dm
index 2d9cb34e43422..3ba4e0b70db60 100644
--- a/code/game/objects/items/devices/traitordevices.dm
+++ b/code/game/objects/items/devices/traitordevices.dm
@@ -87,6 +87,7 @@ effective or pretty fucking useless.
log_combat(user, M, "irradiated", src)
var/cooldown = get_cooldown()
used = TRUE
+ SStgui.update_uis(src) // Update immediately, since it's not spammable
icon_state = "health1"
handle_cooldown(cooldown) // splits off to handle the cooldown while handling wavelength
to_chat(user, "Successfully irradiated [M].")
@@ -102,6 +103,7 @@ effective or pretty fucking useless.
spawn(cooldown)
used = FALSE
icon_state = "health"
+ SStgui.update_uis(src) // Update immediately, since it's not spammable
/obj/item/healthanalyzer/rad_laser/proc/get_cooldown()
return round(max(10, (stealth*30 + intensity*5 - wavelength/4)))
@@ -193,7 +195,6 @@ effective or pretty fucking useless.
slot_flags = ITEM_SLOT_BELT
attack_verb = list("whipped", "lashed", "disciplined")
- var/equipslot = SLOT_BELT
var/mob/living/carbon/human/user = null
var/charge = 300
var/max_charge = 300
@@ -202,7 +203,7 @@ effective or pretty fucking useless.
actions_types = list(/datum/action/item_action/toggle)
/obj/item/shadowcloak/ui_action_click(mob/user)
- if(user.get_item_by_slot(equipslot) == src)
+ if(user.get_item_by_slot(slot_flags) == src)
if(!on)
Activate(usr)
else
@@ -210,8 +211,8 @@ effective or pretty fucking useless.
return
/obj/item/shadowcloak/item_action_slot_check(slot, mob/user)
- if(slot == equipslot)
- return 1
+ if(slot == slot_flags)
+ return TRUE
/obj/item/shadowcloak/proc/Activate(mob/living/carbon/human/user)
if(!user)
@@ -232,30 +233,32 @@ effective or pretty fucking useless.
/obj/item/shadowcloak/dropped(mob/user)
..()
- if(user && user.get_item_by_slot(equipslot) != src)
+ if(user && user.get_item_by_slot(slot_flags) != src)
Deactivate()
-/obj/item/shadowcloak/process()
- if(user.get_item_by_slot(equipslot) != src)
+/obj/item/shadowcloak/process(delta_time)
+ if(user.get_item_by_slot(slot_flags) != src)
Deactivate()
return
var/turf/T = get_turf(src)
if(on)
var/lumcount = T.get_lumcount()
if(lumcount > 0.3)
- charge = max(0,charge - 25)//Quick decrease in light
+ charge = max(0, charge - 12.5 * delta_time)//Quick decrease in light
else
- charge = min(max_charge,charge + 50) //Charge in the dark
+ charge = min(max_charge,charge + 25 * delta_time) //Charge in the dark
animate(user,alpha = CLAMP(255 - charge,0,255),time = 10)
/obj/item/shadowcloak/magician
name = "magician's cape"
desc = "A magician never reveals his secrets."
icon = 'icons/obj/bedsheets.dmi'
+ lefthand_file = 'icons/mob/inhands/misc/bedsheet_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/misc/bedsheet_righthand.dmi'
icon_state = "sheetmagician"
+ item_state = "sheetmagician"
slot_flags = ITEM_SLOT_NECK
layer = MOB_LAYER
- equipslot = SLOT_NECK
attack_verb = null
/obj/item/shadowcloak/magician/attackby(obj/item/W, mob/user, params)
@@ -270,8 +273,8 @@ effective or pretty fucking useless.
playsound(user, 'sound/weapons/emitter2.ogg', 25, 1, -1)
/obj/item/jammer
- name = "radio jammer"
- desc = "Device used to disrupt nearby radio communication."
+ name = "signal jammer"
+ desc = "Device used to disrupt nearby wireless communication."
icon = 'icons/obj/device.dmi'
icon_state = "jammer"
var/active = FALSE
@@ -285,3 +288,11 @@ effective or pretty fucking useless.
else
GLOB.active_jammers -= src
update_icon()
+
+/atom/proc/is_jammed()
+ var/turf/position = get_turf(src)
+ for(var/obj/item/jammer/jammer in GLOB.active_jammers)
+ var/turf/jammer_turf = get_turf(jammer)
+ if(position?.get_virtual_z_level() == jammer_turf.get_virtual_z_level() && (get_dist(position, jammer_turf) <= jammer.range))
+ return TRUE
+ return FALSE
diff --git a/code/game/objects/items/devices/transfer_valve.dm b/code/game/objects/items/devices/transfer_valve.dm
index fae14154e294e..41e5966f63e0d 100644
--- a/code/game/objects/items/devices/transfer_valve.dm
+++ b/code/game/objects/items/devices/transfer_valve.dm
@@ -16,6 +16,10 @@
var/valve_open = FALSE
var/toggle = TRUE
+/obj/item/transfer_valve/Destroy()
+ attached_device = null
+ return ..()
+
/obj/item/transfer_valve/IsAssemblyHolder()
return TRUE
@@ -128,7 +132,7 @@
attached_device = null
update_icon()
if(href_list["device"])
- attached_device.attack_self(usr)
+ attached_device.ui_interact(usr)
attack_self(usr)
add_fingerprint(usr)
@@ -177,20 +181,15 @@
if(!target_self)
target.set_volume(target.return_volume() + tank_two.air_contents.return_volume())
target.set_volume(target.return_volume() + tank_one.air_contents.return_volume())
- var/datum/gas_mixture/temp
- temp = tank_one.air_contents.remove_ratio(1)
- target.merge(temp)
+ tank_one.air_contents.transfer_ratio_to(target, 1)
if(!target_self)
- temp = tank_two.air_contents.remove_ratio(1)
- target.merge(temp)
+ tank_two.air_contents.transfer_ratio_to(target, 1)
/obj/item/transfer_valve/proc/split_gases()
if (!valve_open || !tank_one || !tank_two)
return
var/ratio1 = tank_one.air_contents.return_volume()/tank_two.air_contents.return_volume()
- var/datum/gas_mixture/temp
- temp = tank_two.air_contents.remove_ratio(ratio1)
- tank_one.air_contents.merge(temp)
+ tank_two.air_contents.transfer_ratio_to(tank_one.air_contents, ratio1)
tank_two.air_contents.set_volume(tank_two.air_contents.return_volume() - tank_one.air_contents.return_volume())
/*
@@ -216,7 +215,7 @@
admin_attachment_message = " with [attachment] attached by [attacher ? ADMIN_LOOKUPFLW(attacher) : "Unknown"]"
attachment_message = " with [attachment] attached by [attacher ? key_name_admin(attacher) : "Unknown"]"
- var/mob/bomber = get_mob_by_key(fingerprintslast)
+ var/mob/bomber = get_mob_by_ckey(fingerprintslast)
var/admin_bomber_message
var/bomber_message
if(bomber)
@@ -292,4 +291,5 @@
attached_device = null
. = TRUE
+ ui_update()
update_icon()
diff --git a/code/game/objects/items/dice.dm b/code/game/objects/items/dice.dm
index 756f69bed2652..7043741244d31 100644
--- a/code/game/objects/items/dice.dm
+++ b/code/game/objects/items/dice.dm
@@ -3,6 +3,7 @@
desc = "Contains all the luck you'll ever need."
icon = 'icons/obj/dice.dmi'
icon_state = "dicebag"
+ pill_variance = 0
/obj/item/storage/pill_bottle/dice/Initialize()
. = ..()
@@ -39,6 +40,7 @@
desc = "For rolling several dice at once. A favorite of street urchins."
icon = 'icons/obj/dice.dmi'
icon_state = "dicecup"
+ pill_variance = 0
/obj/item/storage/pill_bottle/dice_cup/attack_self(mob/user)
var/turf/throw_target = get_step(loc,user.dir) //with telekinesis, throws the direction the user is facing
@@ -180,8 +182,10 @@
diceroll(user)
/obj/item/dice/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
- diceroll(thrownby)
- . = ..()
+ var/mob/thrown_by = thrownby?.resolve()
+ if(thrown_by)
+ diceroll(thrown_by)
+ return ..()
/obj/item/dice/proc/diceroll(mob/user)
result = roll(sides)
diff --git a/code/game/objects/items/dna_injector.dm b/code/game/objects/items/dna_injector.dm
index fd552ea70f388..274e544ce3c36 100644
--- a/code/game/objects/items/dna_injector.dm
+++ b/code/game/objects/items/dna_injector.dm
@@ -370,12 +370,12 @@
name = "\improper DNA injector (Anti-Shock Touch)"
remove_mutations = list(SHOCKTOUCH)
-/obj/item/dnainjector/spacialinstability
- name = "\improper DNA injector (Spacial Instability)"
+/obj/item/dnainjector/spatialinstability
+ name = "\improper DNA injector (Spatial Instability)"
add_mutations = list(BADBLINK)
-/obj/item/dnainjector/antispacialinstability
- name = "\improper DNA injector (Anti-Spacial Instability)"
+/obj/item/dnainjector/antispatialinstability
+ name = "\improper DNA injector (Anti-Spatial Instability)"
remove_mutations = list(BADBLINK)
/obj/item/dnainjector/acidflesh
diff --git a/code/game/objects/items/dualsaber.dm b/code/game/objects/items/dualsaber.dm
new file mode 100644
index 0000000000000..a3638efff0b94
--- /dev/null
+++ b/code/game/objects/items/dualsaber.dm
@@ -0,0 +1,200 @@
+/*
+ * Double-Bladed Energy Swords - Cheridan
+ */
+/obj/item/dualsaber
+ icon = 'icons/obj/transforming_energy.dmi'
+ icon_state = "dualsaber0"
+ lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
+ name = "double-bladed energy sword"
+ desc = "Handle with care."
+ force = 3
+ throwforce = 5
+ throw_speed = 3
+ throw_range = 5
+ w_class = WEIGHT_CLASS_SMALL
+ var/w_class_on = WEIGHT_CLASS_BULKY
+ hitsound = "swing_hit"
+ armour_penetration = 35
+ item_color = "green"
+ light_color = "#00ff00"//green
+ attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
+ block_level = 2
+ block_upgrade_walk = 1
+ block_power = 70
+ block_sound = 'sound/weapons/egloves.ogg'
+ block_flags = BLOCKING_ACTIVE | BLOCKING_NASTY | BLOCKING_PROJECTILE
+ max_integrity = 200
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70, "stamina" = 0)
+ resistance_flags = FIRE_PROOF
+ var/twohand_force = 34
+ var/hacked = FALSE
+ var/brightness_on = 6 //TWICE AS BRIGHT AS A REGULAR ESWORD
+ var/list/possible_colors = list("red", "blue", "green", "purple")
+
+/obj/item/dualsaber/Initialize()
+ . = ..()
+ RegisterSignal(src, COMSIG_TWOHANDED_WIELD, .proc/on_wield)
+ RegisterSignal(src, COMSIG_TWOHANDED_UNWIELD, .proc/on_unwield)
+ if(LAZYLEN(possible_colors))
+ item_color = pick(possible_colors)
+ switch(item_color)
+ if("red")
+ light_color = LIGHT_COLOR_RED
+ if("green")
+ light_color = LIGHT_COLOR_GREEN
+ if("blue")
+ light_color = LIGHT_COLOR_LIGHT_CYAN
+ if("purple")
+ light_color = LIGHT_COLOR_LAVENDER
+
+/obj/item/dualsaber/Destroy()
+ STOP_PROCESSING(SSobj, src)
+ . = ..()
+
+/obj/item/dualsaber/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/two_handed, force_unwielded=force, force_wielded=twohand_force, block_power_unwielded=70, block_power_wielded=75, \
+ wieldsound='sound/weapons/saberon.ogg', unwieldsound='sound/weapons/saberoff.ogg', icon_wielded="dualsaber[item_color]1")
+
+/// Triggered on wield of two handed item
+/// Specific hulk checks due to reflection chance for balance issues and switches hitsounds.
+/obj/item/dualsaber/proc/on_wield(obj/item/source, mob/living/carbon/user)
+ SIGNAL_HANDLER
+
+ if(user && user.has_dna())
+ if(user.dna.check_mutation(HULK))
+ to_chat(user, "You lack the grace to wield this!")
+ return COMPONENT_TWOHANDED_BLOCK_WIELD
+ sharpness = IS_SHARP
+ w_class = w_class_on
+ hitsound = 'sound/weapons/blade1.ogg'
+ START_PROCESSING(SSobj, src)
+ set_light(brightness_on)
+
+/// Triggered on unwield of two handed item
+/// switch hitsounds
+/obj/item/dualsaber/proc/on_unwield(obj/item/source, mob/living/carbon/user)
+ SIGNAL_HANDLER
+
+ sharpness = initial(sharpness)
+ w_class = initial(w_class)
+ hitsound = "swing_hit"
+ STOP_PROCESSING(SSobj, src)
+ set_light(0)
+
+/obj/item/dualsaber/update_icon()
+ icon_state = "dualsaber0"
+ SEND_SIGNAL(src, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD)
+ ..()
+
+/obj/item/dualsaber/suicide_act(mob/living/carbon/user)
+ if(ISWIELDED(src))
+ user.visible_message("[user] begins spinning way too fast! It looks like [user.p_theyre()] trying to commit suicide!")
+
+ var/obj/item/bodypart/head/myhead = user.get_bodypart(BODY_ZONE_HEAD)//stole from chainsaw code
+ var/obj/item/organ/brain/B = user.getorganslot(ORGAN_SLOT_BRAIN)
+ B.organ_flags &= ~ORGAN_VITAL //this cant possibly be a good idea
+ var/randdir
+ for(var/i in 1 to 24)//like a headless chicken!
+ if(user.is_holding(src))
+ randdir = pick(GLOB.alldirs)
+ user.Move(get_step(user, randdir),randdir)
+ user.emote("spin")
+ if (i == 3 && myhead)
+ myhead.drop_limb()
+ sleep(3)
+ else
+ user.visible_message("[user] panics and starts choking to death!")
+ return OXYLOSS
+
+ else
+ user.visible_message("[user] begins beating [user.p_them()]self to death with \the [src]'s handle! It probably would've been cooler if [user.p_they()] turned it on first!")
+ return BRUTELOSS
+
+/obj/item/dualsaber/attack(mob/target, mob/living/carbon/human/user)
+ var/wielded = ISWIELDED(src)
+ if(user.has_dna())
+ if(user.dna.check_mutation(HULK))
+ to_chat(user, "You grip the blade too hard and accidentally drop it!")
+ if(wielded)
+ user.dropItemToGround(src, force=TRUE)
+ return
+ ..()
+ if(wielded && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(40))
+ impale(user)
+ return
+ if(wielded && prob(50))
+ INVOKE_ASYNC(src, .proc/jedi_spin, user)
+
+/obj/item/dualsaber/proc/jedi_spin(mob/living/user) //rip complex code, but this fucked up blocking
+ user.emote("flip")
+
+/obj/item/dualsaber/proc/impale(mob/living/user)
+ to_chat(user, "You twirl around a bit before losing your balance and impaling yourself on [src].")
+ if(ISWIELDED(src))
+ user.take_bodypart_damage(20,25,check_armor = TRUE)
+ else
+ user.adjustStaminaLoss(25)
+
+/obj/item/dualsaber/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
+ if(ISWIELDED(src))
+ return ..()
+ return 0
+
+/obj/item/dualsaber/attack_hulk(mob/living/carbon/human/user, does_attack_animation = 0) //In case thats just so happens that it is still activated on the groud, prevents hulk from picking it up
+ if(ISWIELDED(src))
+ to_chat(user, "You can't pick up such dangerous item with your meaty hands without losing fingers, better not to!")
+ return 1
+
+/obj/item/dualsaber/process()
+ if(ISWIELDED(src))
+ if(hacked)
+ light_color = pick(LIGHT_COLOR_RED, LIGHT_COLOR_GREEN, LIGHT_COLOR_LIGHT_CYAN, LIGHT_COLOR_LAVENDER)
+ open_flame()
+ else
+ STOP_PROCESSING(SSobj, src)
+
+/obj/item/dualsaber/IsReflect()
+ if(ISWIELDED(src))
+ return 1
+
+/obj/item/dualsaber/ignition_effect(atom/A, mob/user)
+ // same as /obj/item/melee/transforming/energy, mostly
+ if(!ISWIELDED(src))
+ return ""
+ var/in_mouth = ""
+ if(iscarbon(user))
+ var/mob/living/carbon/C = user
+ if(C.wear_mask)
+ in_mouth = ", barely missing [user.p_their()] nose"
+ . = "[user] swings [user.p_their()] [name][in_mouth]. [user.p_they(TRUE)] light[user.p_s()] [user.p_their()] [A.name] in the process."
+ playsound(loc, hitsound, get_clamped_volume(), TRUE, -1)
+ add_fingerprint(user)
+ // Light your candles while spinning around the room
+ INVOKE_ASYNC(src, .proc/jedi_spin, user)
+
+/obj/item/dualsaber/green
+ possible_colors = list("green")
+
+/obj/item/dualsaber/red
+ possible_colors = list("red")
+
+/obj/item/dualsaber/blue
+ possible_colors = list("blue")
+
+/obj/item/dualsaber/purple
+ possible_colors = list("purple")
+
+/obj/item/dualsaber/attackby(obj/item/W, mob/user, params)
+ if(W.tool_behaviour == TOOL_MULTITOOL)
+ if(!hacked)
+ hacked = TRUE
+ to_chat(user, "2XRNBW_ENGAGE")
+ item_color = "rainbow"
+ AddComponent(/datum/component/two_handed, icon_wielded="dualsaber[item_color]1")
+ update_icon()
+ else
+ to_chat(user, "It's starting to look like a triple rainbow - no, nevermind.")
+ else
+ return ..()
diff --git a/code/game/objects/items/eightball.dm b/code/game/objects/items/eightball.dm
index cb5545ba16668..12bdf7a09f172 100644
--- a/code/game/objects/items/eightball.dm
+++ b/code/game/objects/items/eightball.dm
@@ -97,13 +97,13 @@
/obj/item/toy/eightball/haunted
shake_time = 150
cooldown_time = 1800
- flags_1 = HEAR_1
var/last_message
var/selected_message
var/list/votes
/obj/item/toy/eightball/haunted/Initialize(mapload)
. = ..()
+ become_hearing_sensitive()
votes = list()
GLOB.poi_list |= src
@@ -122,13 +122,14 @@
interact(user)
return ..()
-/obj/item/toy/eightball/haunted/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, message_mode)
+/obj/item/toy/eightball/haunted/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, spans, list/message_mods = list())
. = ..()
last_message = raw_message
/obj/item/toy/eightball/haunted/start_shaking(mob/user)
// notify ghosts that someone's shaking a haunted eightball
// and inform them of the message, (hopefully a yes/no question)
+ votes = list() //need to reset the votes everytime someone shakes it
selected_message = last_message
notify_ghosts("[user] is shaking [src], hoping to get an answer to \"[selected_message]\"", source=src, enter_link="(Click to help)", action=NOTIFY_ATTACK, header = "Magic eightball")
diff --git a/code/game/objects/items/etherealdiscoball.dm b/code/game/objects/items/etherealdiscoball.dm
index 685e83b8940ee..ec37dd9b4f85f 100644
--- a/code/game/objects/items/etherealdiscoball.dm
+++ b/code/game/objects/items/etherealdiscoball.dm
@@ -37,7 +37,6 @@
to_chat(user, "You turn the disco ball on!")
/obj/structure/etherealball/AltClick(mob/living/carbon/human/user)
- . = ..()
if(anchored)
to_chat(user, "You unlock the disco ball.")
anchored = FALSE
diff --git a/code/game/objects/items/extinguisher.dm b/code/game/objects/items/extinguisher.dm
index dee4546f11b79..31037190b83b7 100644
--- a/code/game/objects/items/extinguisher.dm
+++ b/code/game/objects/items/extinguisher.dm
@@ -55,7 +55,7 @@
name = "advanced fire extinguisher"
desc = "Used to stop thermonuclear fires from spreading inside your engine."
icon_state = "foam_extinguisher0"
- //item_state = "foam_extinguisher" needs sprite
+ item_state = "foam_extinguisher"
dog_fashion = null
chem = /datum/reagent/firefighting_foam
tanktype = /obj/structure/reagent_dispensers/foamtank
diff --git a/code/game/objects/items/fireaxe.dm b/code/game/objects/items/fireaxe.dm
new file mode 100644
index 0000000000000..c9208ec52deae
--- /dev/null
+++ b/code/game/objects/items/fireaxe.dm
@@ -0,0 +1,63 @@
+/*
+ * Fireaxe
+ */
+/obj/item/fireaxe // DEM AXES MAN, marker -Agouri
+ icon_state = "fireaxe0"
+ lefthand_file = 'icons/mob/inhands/weapons/axes_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/axes_righthand.dmi'
+ name = "fire axe"
+ desc = "Truly, the weapon of a madman. Who would think to fight fire with an axe?"
+ attack_weight = 3
+ block_upgrade_walk = 1
+ force = 5
+ throwforce = 15
+ w_class = WEIGHT_CLASS_BULKY
+ slot_flags = ITEM_SLOT_BACK
+ attack_verb = list("attacked", "chopped", "cleaved", "torn", "cut")
+ hitsound = 'sound/weapons/bladeslice.ogg'
+ sharpness = IS_SHARP
+ max_integrity = 200
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30, "stamina" = 0)
+ resistance_flags = FIRE_PROOF
+ var/icon_prefix = "fireaxe"
+
+/obj/item/fireaxe/Initialize()
+ . = ..()
+
+/obj/item/fireaxe/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/butchering, 100, 80, 0 , hitsound) //axes are not known for being precision butchering tools
+ AddComponent(/datum/component/two_handed, force_unwielded=5, force_wielded=24, block_power_wielded=25, icon_wielded="[icon_prefix]1")
+
+/obj/item/fireaxe/update_icon()
+ icon_state = "[icon_prefix]0"
+ ..()
+
+/obj/item/fireaxe/suicide_act(mob/user)
+ user.visible_message("[user] axes [user.p_them()]self from head to toe! It looks like [user.p_theyre()] trying to commit suicide!")
+ return (BRUTELOSS)
+
+/obj/item/fireaxe/afterattack(atom/A, mob/user, proximity)
+ . = ..()
+ if(!proximity)
+ return
+ if(ISWIELDED(src)) //destroys windows and grilles in one hit
+ if(istype(A, /obj/structure/window))
+ var/obj/structure/window/W = A
+ W.take_damage(200, BRUTE, "melee", 0)
+ else if(istype(A, /obj/structure/grille))
+ var/obj/structure/grille/G = A
+ G.take_damage(40, BRUTE, "melee", 0)
+
+/*
+ * Bone Axe
+ */
+/obj/item/fireaxe/boneaxe // Blatant imitation of the fireaxe, but made out of bone.
+ name = "bone axe"
+ desc = "A large, vicious axe crafted out of several sharpened bone plates and crudely tied together. Made of monsters, by killing monsters, for killing monsters."
+ icon_prefix = "bone_axe"
+ icon_state = "bone_axe0"
+
+/obj/item/fireaxe/boneaxe/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/two_handed, force_unwielded=5, force_wielded=23, icon_wielded="[icon_prefix]1")
diff --git a/code/game/objects/items/flamethrower.dm b/code/game/objects/items/flamethrower.dm
index f50d6bac85eb2..e99e855c5af8d 100644
--- a/code/game/objects/items/flamethrower.dm
+++ b/code/game/objects/items/flamethrower.dm
@@ -10,6 +10,8 @@
force = 3
throwforce = 10
block_upgrade_walk = 1
+ var/acti_sound = 'sound/items/welderactivate.ogg'
+ var/deac_sound = 'sound/items/welderdeactivate.ogg'
throw_speed = 1
throw_range = 5
w_class = WEIGHT_CLASS_NORMAL
@@ -17,6 +19,7 @@
resistance_flags = FIRE_PROOF
var/status = FALSE
var/lit = FALSE //on or off
+ light_color = LIGHT_COLOR_FIRE
var/operating = FALSE//cooldown
var/obj/item/weldingtool/weldtool = null
var/obj/item/assembly/igniter/igniter = null
@@ -161,11 +164,15 @@
to_chat(user, "You [lit ? "extinguish" : "ignite"] [src]!")
lit = !lit
if(lit)
+ set_light(1)
+ playsound(loc, acti_sound, 50, TRUE)
START_PROCESSING(SSobj, src)
if(!warned_admins)
message_admins("[ADMIN_LOOKUPFLW(user)] has lit a flamethrower.")
warned_admins = TRUE
else
+ set_light(0)
+ playsound(loc, deac_sound, 50, TRUE)
STOP_PROCESSING(SSobj,src)
update_icon()
@@ -197,7 +204,7 @@
sleep(1)
previousturf = T
operating = FALSE
- for(var/mob/M in viewers(1, loc))
+ for(var/mob/M as() in viewers(1, loc))
if((M.client && M.machine == src))
attack_self(M)
@@ -206,12 +213,11 @@
//TODO: DEFERRED Consider checking to make sure tank pressure is high enough before doing this...
//Transfer 5% of current tank air contents to turf
var/datum/gas_mixture/air_transfer = ptank.air_contents.remove_ratio(release_amount)
- air_transfer.set_moles(/datum/gas/plasma, air_transfer.get_moles(/datum/gas/plasma) * 5)
+ air_transfer.set_moles(GAS_PLASMA, air_transfer.get_moles(GAS_PLASMA) * 5)
target.assume_air(air_transfer)
//Burn it based on transfered gas
target.hotspot_expose((ptank.air_contents.return_temperature()*2) + 380,500)
//location.hotspot_expose(1000,500,1)
- SSair.add_to_active(target, 0)
/obj/item/flamethrower/Initialize(mapload)
@@ -237,7 +243,7 @@
/obj/item/flamethrower/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
var/obj/item/projectile/P = hitby
if(damage && attack_type == PROJECTILE_ATTACK && P.damage_type != STAMINA && prob(15))
- owner.visible_message("\The [attack_text] hits the fueltank on [owner]'s [name], rupturing it! What a shot!")
+ owner.visible_message("\The [attack_text] hits the fuel tank on [owner]'s [name], rupturing it! What a shot!")
var/turf/target_turf = get_turf(owner)
log_game("A projectile ([hitby]) detonated a flamethrower tank held by [key_name(owner)] at [COORD(target_turf)]")
igniter.ignite_turf(src,target_turf, release_amount = 100)
diff --git a/code/game/objects/items/granters.dm b/code/game/objects/items/granters.dm
index 6ee964e4fb644..926711b557ac5 100644
--- a/code/game/objects/items/granters.dm
+++ b/code/game/objects/items/granters.dm
@@ -188,11 +188,11 @@
user.set_nutrition(0)
/obj/item/book/granter/spell/blind
- spell = /obj/effect/proc_holder/spell/pointed/trigger/blind
+ spell = /obj/effect/proc_holder/spell/targeted/blind
spellname = "blind"
icon_state ="bookblind"
desc = "This book looks blurry, no matter how you look at it."
- remarks = list("Well I can't learn anything if I can't read the damn thing!", "Why would you use a dark font on a dark background...", "Ah, I can't see an Oh, I'm fine...", "I can't see my hand...!", "I'm manually blinking, damn you book...", "I can't read this page, but somehow I feel like I learned something from it...", "Hey, who turned off the lights?")
+ remarks = list("Well I can't learn anything if I can't read the damn thing!", "Why would you use a dark font on a dark background...", "Ah, I can't see an- Oh, I'm fine...", "I can't see my hand...!", "I'm manually blinking, damn you book...", "I can't read this page, but somehow I feel like I learned something from it...", "Hey, who turned off the lights?")
/obj/item/book/granter/spell/blind/recoil(mob/user)
..()
@@ -244,7 +244,7 @@
..()
to_chat(user,"You suddenly feel very solid!")
user.Stun(40, ignore_canstun = TRUE)
- user.petrify(30)
+ user.petrify(60)
/obj/item/book/granter/spell/knock
spell = /obj/effect/proc_holder/spell/aoe_turf/knock
@@ -271,7 +271,7 @@
var/obj/item/clothing/magichead = new /obj/item/clothing/mask/horsehead/cursed(user.drop_location())
if(!user.dropItemToGround(user.wear_mask))
qdel(user.wear_mask)
- user.equip_to_slot_if_possible(magichead, SLOT_WEAR_MASK, TRUE, TRUE)
+ user.equip_to_slot_if_possible(magichead, ITEM_SLOT_MASK, TRUE, TRUE)
qdel(src)
else
to_chat(user,"I say thee neigh") //It still lives here
@@ -377,6 +377,32 @@
name = "empty scroll"
icon_state = "blankscroll"
+/obj/item/book/granter/martial/tribal_claw
+ martial = /datum/martial_art/tribal_claw
+ name = "old scroll"
+ martialname = "tribal claw"
+ desc = "A scroll filled with ancient draconic markings."
+ greet = "You have learned the ancient martial art of the Tribal Claw! You are now able to use your tail and claws in a fight much better than before. \
+ Check the combos you are now able to perform using the Recall Teachings verb in the Tribal Claw tab"
+ icon = 'icons/obj/wizard.dmi'
+ icon_state = "scroll2"
+ remarks = list("I must prove myself worthy to the masters of the Knoises clan...", "Use your tail to surprise any enemy...", "Your sharp claws can disorient them...", "I don't think this would combine with other martial arts...", "Ooga Booga...")
+
+/obj/item/book/granter/martial/tribal_claw/onlearned(mob/living/carbon/user)
+ ..()
+ if(!oneuse)
+ return
+ desc = "It's completely blank."
+ name = "empty scroll"
+ icon_state = "blankscroll"
+
+/obj/item/book/granter/martial/tribal_claw/already_known(mob/user)
+ if(islizard(user))
+ return FALSE
+ else
+ to_chat(user, "You try to read the scroll but can't comprehend any of it.")
+ return TRUE
+
/obj/item/book/granter/martial/plasma_fist
martial = /datum/martial_art/plasma_fist
name = "frayed scroll"
diff --git a/code/game/objects/items/grenades/antigravity.dm b/code/game/objects/items/grenades/antigravity.dm
index 43b2f949867cb..4a28ddf86fb0a 100644
--- a/code/game/objects/items/grenades/antigravity.dm
+++ b/code/game/objects/items/grenades/antigravity.dm
@@ -2,16 +2,17 @@
name = "antigravity grenade"
icon_state = "emp"
item_state = "emp"
-
+
var/range = 7
var/forced_value = 0
var/duration = 300
-/obj/item/grenade/antigravity/prime()
+/obj/item/grenade/antigravity/prime(mob/living/lanced_by)
+ . = ..()
update_mob()
-
+
for(var/turf/T in view(range,src))
var/datum/component/C = T.AddComponent(/datum/component/forced_gravity,forced_value)
QDEL_IN(C,duration)
-
+
qdel(src)
diff --git a/code/game/objects/items/grenades/chem_grenade.dm b/code/game/objects/items/grenades/chem_grenade.dm
index 3378be997e450..83517420746aa 100644
--- a/code/game/objects/items/grenades/chem_grenade.dm
+++ b/code/game/objects/items/grenades/chem_grenade.dm
@@ -174,10 +174,12 @@
active = TRUE
addtimer(CALLBACK(src, .proc/prime), isnull(delayoverride)? det_time : delayoverride)
-/obj/item/grenade/chem_grenade/prime()
+/obj/item/grenade/chem_grenade/prime(mob/living/lanced_by)
if(stage != GRENADE_READY)
return
+ . = ..()
+
var/list/datum/reagents/reactants = list()
for(var/obj/item/reagent_containers/glass/G in beakers)
reactants += G.reagents
@@ -212,7 +214,7 @@
ignition_temp = 25 // Large grenades are slightly more effective at setting off heat-sensitive mixtures than smaller grenades.
threatscale = 1.1 // 10% more effective.
-/obj/item/grenade/chem_grenade/large/prime()
+/obj/item/grenade/chem_grenade/large/prime(mob/living/lanced_by)
if(stage != GRENADE_READY)
return
@@ -281,7 +283,7 @@
return
..()
-/obj/item/grenade/chem_grenade/adv_release/prime()
+/obj/item/grenade/chem_grenade/adv_release/prime(mob/living/lanced_by)
if(stage != GRENADE_READY)
return
@@ -600,4 +602,4 @@
B2.reagents.add_reagent(/datum/reagent/consumable/sodiumchloride, 50)
beakers += B1
- beakers += B2
\ No newline at end of file
+ beakers += B2
diff --git a/code/game/objects/items/grenades/clusterbuster.dm b/code/game/objects/items/grenades/clusterbuster.dm
index 920640145d790..cd64cdc245b4d 100644
--- a/code/game/objects/items/grenades/clusterbuster.dm
+++ b/code/game/objects/items/grenades/clusterbuster.dm
@@ -14,7 +14,8 @@
var/max_spawned = 8
var/segment_chance = 35
-/obj/item/grenade/clusterbuster/prime()
+/obj/item/grenade/clusterbuster/prime(mob/living/lanced_by)
+ . = ..()
update_mob()
var/numspawned = rand(min_spawned,max_spawned)
var/again = 0
@@ -28,7 +29,7 @@
new /obj/item/grenade/clusterbuster/segment(drop_location(), src)//Creates 'segments' that launches a few more payloads
new payload_spawner(drop_location(), payload, numspawned)//Launches payload
- playsound(src, prime_sound, 75, 1, -3)
+ playsound(src, prime_sound, 75, TRUE, -3)
qdel(src)
//////////////////////
@@ -59,9 +60,9 @@
step_away(src,loc)
addtimer(CALLBACK(src, .proc/prime), rand(15,60))
-/obj/item/grenade/clusterbuster/segment/prime()
+/obj/item/grenade/clusterbuster/segment/prime(mob/living/lanced_by)
new payload_spawner(drop_location(), payload, rand(min_spawned,max_spawned))
- playsound(src, prime_sound, 75, 1, -3)
+ playsound(src, prime_sound, 75, TRUE, -3)
qdel(src)
//////////////////////////////////
diff --git a/code/game/objects/items/grenades/discogrenade.dm b/code/game/objects/items/grenades/discogrenade.dm
index d5da0386ff38a..15f9885052080 100644
--- a/code/game/objects/items/grenades/discogrenade.dm
+++ b/code/game/objects/items/grenades/discogrenade.dm
@@ -1,6 +1,6 @@
//Ethereal Disco Grenade for Ethereal traitors
//Does not affect ethereals.
-//Some basic code peices taken from flashbang, spawner grenade and ethereal disco ball for functionality (basically a combination of the 3).
+//Some basic code pieces taken from flashbang, spawner grenade and ethereal disco ball for functionality (basically a combination of the 3).
//////////////////////
// Primary grenade //
@@ -16,7 +16,8 @@
var/list/messages = list("This party is great!", "Wooo!!!", "Party!", "Check out these moves!", "Hey, want to dance with me?")
var/list/message_social_anxiety = list("I want to go home...", "Where are the toilets?", "I don't like this song.")
-/obj/item/grenade/discogrenade/prime()
+/obj/item/grenade/discogrenade/prime(mob/living/lanced_by)
+ . = ..()
update_mob()
var/current_turf = get_turf(src)
if(!current_turf)
@@ -57,7 +58,7 @@
addtimer(CALLBACK(src, .proc/prime), rand(10, 60))
randomiseLightColor()
-/obj/item/grenade/discogrenade/subgrenade/prime()
+/obj/item/grenade/discogrenade/subgrenade/prime(mob/living/lanced_by)
update_mob()
var/current_turf = get_turf(src)
if(!current_turf)
@@ -73,7 +74,7 @@
//Create the lights
new /obj/effect/dummy/lighting_obj (current_turf, rand_hex_color(), 4, 1, 10)
- for(var/mob/living/carbon/human/M in view(4, src))
+ for(var/mob/living/carbon/human/M in hearers(4, src))
forcedance(get_turf(M), M)
qdel(src)
@@ -108,7 +109,7 @@
M.show_message("You resist your inner urges to break out your best moves.", 2)
M.set_drugginess(5)
return
- if(istype(M.get_item_by_slot(SLOT_HEAD), /obj/item/clothing/head/foilhat))
+ if(istype(M.get_item_by_slot(ITEM_SLOT_HEAD), /obj/item/clothing/head/foilhat))
to_chat(M, "THOSE GLOW-IN-THE-DARK NANOTRASEN LIGHTBULBS WON'T CORRUPT ME WITH THEIR AGENDA!")
M.emote("scream")
return
diff --git a/code/game/objects/items/grenades/emgrenade.dm b/code/game/objects/items/grenades/emgrenade.dm
index 9b4ad05bca61d..c2e903e48a516 100644
--- a/code/game/objects/items/grenades/emgrenade.dm
+++ b/code/game/objects/items/grenades/emgrenade.dm
@@ -4,7 +4,8 @@
icon_state = "emp"
item_state = "emp"
-/obj/item/grenade/empgrenade/prime()
+/obj/item/grenade/empgrenade/prime(mob/living/lanced_by)
+ . = ..()
update_mob()
for(var/obj/machinery/light/L in range(10, src))
L.on = 1
diff --git a/code/game/objects/items/grenades/flashbang.dm b/code/game/objects/items/grenades/flashbang.dm
index a521fc56582fb..59bf6cca1d173 100644
--- a/code/game/objects/items/grenades/flashbang.dm
+++ b/code/game/objects/items/grenades/flashbang.dm
@@ -6,7 +6,8 @@
righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
var/flashbang_range = 7 //how many tiles away the mob will be stunned.
-/obj/item/grenade/flashbang/prime()
+/obj/item/grenade/flashbang/prime(mob/living/lanced_by)
+ . = ..()
update_mob()
var/flashbang_turf = get_turf(src)
if(!flashbang_turf)
@@ -14,23 +15,137 @@
do_sparks(rand(5, 9), FALSE, src)
playsound(flashbang_turf, 'sound/weapons/flashbang.ogg', 100, TRUE, 8, 0.9)
new /obj/effect/dummy/lighting_obj (flashbang_turf, LIGHT_COLOR_WHITE, (flashbang_range + 2), 4, 2)
- for(var/mob/living/M in get_hearers_in_view(flashbang_range, flashbang_turf))
+ for(var/mob/living/M in viewers(flashbang_range, flashbang_turf))
+ flash(get_turf(M), M)
+ for(var/mob/living/M in hearers(flashbang_range, flashbang_turf))
bang(get_turf(M), M)
qdel(src)
-/obj/item/grenade/flashbang/proc/bang(turf/T , mob/living/M)
+//Flash
+/obj/item/grenade/flashbang/proc/flash(turf/T, mob/living/M)
if(M.stat == DEAD) //They're dead!
return
- M.show_message("BANG", 2)
var/distance = max(0,get_dist(get_turf(src),T))
+ //When distance is 0, will be 1
+ //When distance is 7, will be 0
+ //Can be less than 0 due to hearers being a circular radius.
+ var/distance_proportion = max(1 - (distance / flashbang_range), 0)
+
+ if(M.flash_act(intensity = 1, affect_silicon = 1))
+ if(distance_proportion)
+ M.Paralyze(20 * distance_proportion)
+ M.Knockdown(200 * distance_proportion)
+ else
+ M.flash_act(intensity = 2)
-//Flash
- if(M.flash_act(affect_silicon = 1))
- M.confused += (max(20/max(1,distance), 6))
//Bang
+/obj/item/grenade/flashbang/proc/bang(turf/T, mob/living/M)
+ if(M.stat == DEAD)
+ return
+ var/distance = max(0,get_dist(get_turf(src),T))
+ M.show_message("BANG", MSG_AUDIBLE)
if(!distance || loc == M || loc == M.loc) //Stop allahu akbarring rooms with this.
- var/protection = max(1, M.get_ear_protection())
- M.adjustEarDamage(15/protection, 30/protection)
+ M.Paralyze(20)
+ M.Knockdown(200)
M.soundbang_act(1, 200, 10, 15)
else
- M.soundbang_act(1, max(200/max(1,distance), 60), rand(3))
+ if(distance <= 1)
+ M.Paralyze(5)
+ M.Knockdown(30)
+
+ var/distance_proportion = max(1 - (distance / flashbang_range), 0)
+ if(distance_proportion)
+ M.soundbang_act(1, 200 * distance_proportion, rand(0, 5))
+
+/obj/item/grenade/stingbang
+ name = "stingbang"
+ icon_state = "timeg"
+ item_state = "flashbang"
+ lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
+ var/flashbang_range = 1 //how many tiles away the mob will be stunned.
+ shrapnel_type = /obj/item/projectile/bullet/pellet/stingball
+ shrapnel_radius = 5
+ custom_premium_price = 700 // mostly gotten through cargo, but throw in one for the sec vendor ;)
+
+/obj/item/grenade/stingbang/mega
+ name = "mega stingbang"
+ shrapnel_type = /obj/item/projectile/bullet/pellet/stingball/mega
+ shrapnel_radius = 12
+
+/obj/item/grenade/stingbang/prime(mob/living/lanced_by)
+ if(iscarbon(loc))
+ var/mob/living/carbon/C = loc
+ var/obj/item/bodypart/B = C.get_holding_bodypart_of_item(src)
+ if(B)
+ forceMove(get_turf(C))
+ C.visible_message("[src] goes off in [C]'s hand, blowing [C.p_their()] [B.name] to bloody shreds!", "[src] goes off in your hand, blowing your [B.name] to bloody shreds!")
+ B.dismember()
+
+ . = ..()
+ update_mob()
+ var/flashbang_turf = get_turf(src)
+ if(!flashbang_turf)
+ return
+ do_sparks(rand(5, 9), FALSE, src)
+ playsound(flashbang_turf, 'sound/weapons/flashbang.ogg', 50, TRUE, 8, 0.9)
+ new /obj/effect/dummy/lighting_obj (flashbang_turf, LIGHT_COLOR_WHITE, (flashbang_range + 2), 2, 1)
+ for(var/mob/living/M in get_hearers_in_view(flashbang_range, flashbang_turf))
+ pop(get_turf(M), M)
+ qdel(src)
+
+//Flash
+/obj/item/grenade/stingbang/proc/flash(turf/T, mob/living/M)
+ if(M.stat == DEAD)
+ return
+ var/distance = max(0,get_dist(get_turf(src),T))
+ if(M.flash_act(affect_silicon = 1))
+ M.Paralyze(max(10/max(1,distance), 5))
+ M.Knockdown(max(100/max(1,distance), 60))
+
+//Pop
+/obj/item/grenade/stingbang/proc/pop(turf/T , mob/living/M)
+ if(M.stat == DEAD) //They're dead!
+ return
+ M.show_message("POP")
+ var/distance = max(0,get_dist(get_turf(src),T))
+ if(!distance || loc == M || loc == M.loc) //Stop allahu akbarring rooms with this.
+ M.Paralyze(20)
+ M.Knockdown(200)
+ M.soundbang_act(1, 200, 10, 15)
+ if(M.apply_damages(10, 10))
+ to_chat(M, "The blast from \the [src] bruises and burns you!")
+
+ // only checking if they're on top of the tile, cause being one tile over will be its own punishment
+
+// Grenade that releases more shrapnel the more times you use it in hand between priming and detonation (sorta like the 9bang from MW3), for admin goofs
+/obj/item/grenade/primer
+ name = "rotfrag grenade"
+ desc = "A grenade that generates more shrapnel the more you rotate it in your hand after pulling the pin. This one releases shrapnel shards."
+ icon_state = "timeg"
+ item_state = "flashbang"
+ lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
+ var/rots_per_mag = 3 /// how many times we need to "rotate" the charge in hand per extra tile of magnitude
+ shrapnel_type = /obj/item/projectile/bullet/shrapnel
+ var/rots = 1 /// how many times we've "rotated" the charge
+
+/obj/item/grenade/primer/attack_self(mob/user)
+ . = ..()
+ if(active)
+ user.playsound_local(user, 'sound/misc/box_deploy.ogg', 50, TRUE)
+ rots++
+ user.changeNext_move(CLICK_CD_RAPID)
+
+/obj/item/grenade/primer/prime(mob/living/lanced_by)
+ shrapnel_radius = round(rots / rots_per_mag)
+ . = ..()
+ qdel(src)
+
+/obj/item/grenade/primer/stingbang
+ name = "rotsting"
+ desc = "A grenade that generates more shrapnel the more you rotate it in your hand after pulling the pin. This one releases stingballs."
+ lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
+ rots_per_mag = 2
+ shrapnel_type = /obj/item/projectile/bullet/pellet/stingball
diff --git a/code/game/objects/items/grenades/ghettobomb.dm b/code/game/objects/items/grenades/ghettobomb.dm
index 1725d84dccf07..3553d79faf93b 100644
--- a/code/game/objects/items/grenades/ghettobomb.dm
+++ b/code/game/objects/items/grenades/ghettobomb.dm
@@ -45,12 +45,13 @@
/obj/item/grenade/iedcasing/attack_self(mob/user) //
if(!active)
- if(clown_check(user))
+ if(!botch_check(user))
to_chat(user, "You light the [name]!")
cut_overlay("improvised_grenade_filled")
preprime(user, null, FALSE)
-/obj/item/grenade/iedcasing/prime() //Blowing that can up
+/obj/item/grenade/iedcasing/prime(mob/living/lanced_by) //Blowing that can up
+ . = ..()
update_mob()
explosion(src.loc,-1,-1,2, flame_range = 4) // small explosion, plus a very large fireball.
qdel(src)
diff --git a/code/game/objects/items/grenades/grenade.dm b/code/game/objects/items/grenades/grenade.dm
index dc5100b420d75..5d6d36f8bd8e6 100644
--- a/code/game/objects/items/grenades/grenade.dm
+++ b/code/game/objects/items/grenades/grenade.dm
@@ -17,6 +17,23 @@
var/det_time = 50
var/display_timer = 1
var/clumsy_check = GRENADE_CLUMSY_FUMBLE
+ var/sticky = FALSE
+ // I moved the explosion vars and behavior to base grenades because we want all grenades to call [/obj/item/grenade/proc/prime] so we can send COMSIG_GRENADE_PRIME
+ ///how big of a devastation explosion radius on prime
+ var/ex_dev = 0
+ ///how big of a heavy explosion radius on prime
+ var/ex_heavy = 0
+ ///how big of a light explosion radius on prime
+ var/ex_light = 0
+ ///how big of a flame explosion radius on prime
+ var/ex_flame = 0
+
+ // dealing with creating a [/datum/component/pellet_cloud] on prime
+ /// if set, will spew out projectiles of this type
+ var/shrapnel_type
+ /// the higher this number, the more projectiles are created as shrapnel
+ var/shrapnel_radius
+ var/shrapnel_initialized
/obj/item/grenade/suicide_act(mob/living/carbon/user)
user.visible_message("[user] primes [src], then eats it! It looks like [user.p_theyre()] trying to commit suicide!")
@@ -32,18 +49,20 @@
if(!QDELETED(src))
qdel(src)
-/obj/item/grenade/proc/clown_check(mob/living/carbon/human/user)
+/obj/item/grenade/proc/botch_check(mob/living/carbon/human/user)
var/clumsy = HAS_TRAIT(user, TRAIT_CLUMSY)
if(clumsy && (clumsy_check == GRENADE_CLUMSY_FUMBLE))
if(prob(50))
to_chat(user, "Huh? How does this thing work?")
preprime(user, 5, FALSE)
- return FALSE
+ return TRUE
else if(!clumsy && (clumsy_check == GRENADE_NONCLUMSY_FUMBLE))
- to_chat(user, "You pull the pin on [src]. Attached to it is a pink ribbon that says, \"HONK\"")
+ to_chat(user, "You pull the pin on [src]. Attached to it is a pink ribbon that says, \"HONK\"")
preprime(user, 5, FALSE)
- return FALSE
- return TRUE
+ return TRUE
+ else if(sticky && prob(50)) // to add risk to sticky tape grenade cheese, no return cause we still prime as normal after
+ to_chat(user, "What the... [src] is stuck to your hand!")
+ ADD_TRAIT(src, TRAIT_NODROP, STICKY_NODROP)
/obj/item/grenade/examine(mob/user)
@@ -56,8 +75,16 @@
/obj/item/grenade/attack_self(mob/user)
+ if(HAS_TRAIT(src, TRAIT_NODROP))
+ to_chat(user, "You try prying [src] off your hand...")
+ if(do_after(user, 70, target=src))
+ to_chat(user, "You manage to remove [src] from your hand.")
+ REMOVE_TRAIT(src, TRAIT_NODROP, STICKY_NODROP)
+
+ return
+
if(!active)
- if(clown_check(user))
+ if(!botch_check(user))
preprime(user)
/obj/item/grenade/proc/log_grenade(mob/user, turf/T)
@@ -70,12 +97,23 @@
add_fingerprint(user)
if(msg)
to_chat(user, "You prime [src]! [DisplayTimeText(det_time)]!")
+ if(shrapnel_type && shrapnel_radius)
+ shrapnel_initialized = TRUE
+ AddComponent(/datum/component/pellet_cloud, projectile_type=shrapnel_type, magnitude=shrapnel_radius)
playsound(src, 'sound/weapons/armbomb.ogg', volume, 1)
active = TRUE
icon_state = initial(icon_state) + "_active"
+ SEND_SIGNAL(src, COMSIG_GRENADE_ARMED, det_time, delayoverride)
addtimer(CALLBACK(src, .proc/prime), isnull(delayoverride)? det_time : delayoverride)
-/obj/item/grenade/proc/prime()
+/obj/item/grenade/proc/prime(mob/living/lanced_by)
+ if(shrapnel_type && shrapnel_radius && !shrapnel_initialized) // add a second check for adding the component in case whatever triggered the grenade went straight to prime (badminnery for example)
+ shrapnel_initialized = TRUE
+ AddComponent(/datum/component/pellet_cloud, projectile_type=shrapnel_type, magnitude=shrapnel_radius)
+
+ SEND_SIGNAL(src, COMSIG_GRENADE_PRIME, lanced_by)
+ if(ex_dev || ex_heavy || ex_light || ex_flame)
+ explosion(loc, ex_dev, ex_heavy, ex_light, flame_range = ex_flame)
/obj/item/grenade/proc/update_mob()
if(ismob(loc))
diff --git a/code/game/objects/items/grenades/plastic.dm b/code/game/objects/items/grenades/plastic.dm
index d074076efa6f1..340ae55ccf318 100644
--- a/code/game/objects/items/grenades/plastic.dm
+++ b/code/game/objects/items/grenades/plastic.dm
@@ -55,18 +55,21 @@
return
..()
-/obj/item/grenade/plastic/prime()
+/obj/item/grenade/plastic/prime(mob/living/lanced_by)
+ . = ..()
var/turf/location
+ var/density_check = FALSE
if(target)
if(!QDELETED(target))
location = get_turf(target)
+ density_check = target.density //since turfs getting exploded makes this a bit fucky wucky we need to assert whether we should go directional before that part
target.cut_overlay(plastic_overlay, TRUE)
if(!ismob(target) || full_damage_on_mobs)
target.ex_act(EXPLODE_HEAVY, target)
else
location = get_turf(src)
if(location)
- if(directional && target && target.density)
+ if(directional && target && density_check)
var/turf/T = get_step(location, aim_dir)
explosion(get_step(T, aim_dir), boom_sizes[1], boom_sizes[2], boom_sizes[3])
else
@@ -124,7 +127,11 @@
var/obj/item/I = AM
I.throw_speed = max(1, (I.throw_speed - 3))
I.throw_range = max(1, (I.throw_range - 3))
- I.embedding = I.embedding.setRating(embed_chance = 0)
+ if(I.embedding)
+ I.embedding["embed_chance"] = 0
+ I.updateEmbedding()
+ else if(istype(AM, /mob/living))
+ plastic_overlay.layer = FLOAT_LAYER
target.add_overlay(plastic_overlay, TRUE)
if(!nadeassembly)
@@ -204,9 +211,11 @@
else
return ..()
-/obj/item/grenade/plastic/c4/prime()
+/obj/item/grenade/plastic/c4/prime(mob/living/lanced_by)
if(QDELETED(src))
return
+
+ . = ..()
var/turf/location
if(target)
if(!QDELETED(target))
diff --git a/code/game/objects/items/grenades/smokebomb.dm b/code/game/objects/items/grenades/smokebomb.dm
index 40f6dfed3c448..d970b68191190 100644
--- a/code/game/objects/items/grenades/smokebomb.dm
+++ b/code/game/objects/items/grenades/smokebomb.dm
@@ -17,7 +17,8 @@
qdel(smoke)
return ..()
-/obj/item/grenade/smokebomb/prime()
+/obj/item/grenade/smokebomb/prime(mob/living/lanced_by)
+ . = ..()
update_mob()
playsound(src, 'sound/effects/smoke.ogg', 50, 1, -3)
smoke.set_up(4, src)
diff --git a/code/game/objects/items/grenades/spawnergrenade.dm b/code/game/objects/items/grenades/spawnergrenade.dm
index fb83da5ed9218..3562fd367d4ee 100644
--- a/code/game/objects/items/grenades/spawnergrenade.dm
+++ b/code/game/objects/items/grenades/spawnergrenade.dm
@@ -7,13 +7,14 @@
var/spawner_type = null // must be an object path
var/deliveryamt = 1 // amount of type to deliver
-/obj/item/grenade/spawnergrenade/prime() // Prime now just handles the two loops that query for people in lockers and people who can see it.
+/obj/item/grenade/spawnergrenade/prime(mob/living/lanced_by) // Prime now just handles the two loops that query for people in lockers and people who can see it.
+ . = ..()
update_mob()
if(spawner_type && deliveryamt)
// Make a quick flash
var/turf/T = get_turf(src)
playsound(T, 'sound/effects/phasein.ogg', 100, 1)
- for(var/mob/living/carbon/C in viewers(T, null))
+ for(var/mob/living/carbon/C in viewers(T))
C.flash_act()
// Spawn some hostile syndicate critters and spread them out
diff --git a/code/game/objects/items/grenades/syndieminibomb.dm b/code/game/objects/items/grenades/syndieminibomb.dm
index 35e9abd9828ef..fb73701f85f17 100644
--- a/code/game/objects/items/grenades/syndieminibomb.dm
+++ b/code/game/objects/items/grenades/syndieminibomb.dm
@@ -4,27 +4,45 @@
icon = 'icons/obj/grenade.dmi'
icon_state = "syndicate"
item_state = "flashbang"
+ ex_dev = 1
+ ex_heavy = 2
+ ex_light = 4
+ ex_flame = 2
-/obj/item/grenade/syndieminibomb/prime()
+/obj/item/grenade/syndieminibomb/prime(mob/living/lanced_by)
+ . = ..()
update_mob()
- explosion(src.loc,1,2,4,flame_range = 2)
qdel(src)
/obj/item/grenade/syndieminibomb/concussion
name = "HE Grenade"
desc = "A compact shrapnel grenade meant to devastate nearby organisms and cause some damage in the process. Pull pin and throw opposite direction."
icon_state = "concussion"
+ ex_heavy = 2
+ ex_light = 3
+ ex_flame = 3
-/obj/item/grenade/syndieminibomb/concussion/prime()
- update_mob()
- explosion(src.loc,0,2,3,flame_range = 3)
- qdel(src)
-
-/obj/item/grenade/syndieminibomb/concussion/frag
+/obj/item/grenade/frag
name = "frag grenade"
- desc = "Fire in the hole."
+ desc = "An anti-personnel fragmentation grenade, this weapon excels at killing soft targets by shredding them with metal shrapnel."
icon_state = "frag"
+ shrapnel_type = /obj/item/projectile/bullet/shrapnel
+ shrapnel_radius = 4
+ ex_heavy = 1
+ ex_light = 3
+ ex_flame = 4
+
+/obj/item/grenade/frag/mega
+ name = "\improper FRAG grenade"
+ desc = "An anti-everything fragmentation grenade, this weapon excels at killing anything any everything by shredding them with metal shrapnel."
+ shrapnel_type = /obj/item/projectile/bullet/shrapnel/mega
+ shrapnel_radius = 12
+
+/obj/item/grenade/frag/prime(mob/living/lanced_by)
+ . = ..()
+ update_mob()
+ qdel(src)
/obj/item/grenade/gluon
desc = "An advanced grenade that releases a harmful stream of gluons inducing radiation in those nearby. These gluon streams will also make victims feel exhausted, and induce shivering. This extreme coldness will also likely wet any nearby floors."
@@ -36,15 +54,14 @@
var/rad_damage = 350
var/stamina_damage = 30
-/obj/item/grenade/gluon/prime()
+/obj/item/grenade/gluon/prime(mob/living/lanced_by)
+ . = ..()
update_mob()
playsound(loc, 'sound/effects/empulse.ogg', 50, 1)
radiation_pulse(src, rad_damage)
- for(var/turf/T in view(freeze_range,loc))
- if(isfloorturf(T))
- var/turf/open/floor/F = T
- F.MakeSlippery(TURF_WET_PERMAFROST, 6 MINUTES)
- for(var/mob/living/carbon/L in T)
- L.adjustStaminaLoss(stamina_damage)
- L.adjust_bodytemperature(-230)
+ for(var/turf/open/floor/F in view(freeze_range,loc))
+ F.MakeSlippery(TURF_WET_PERMAFROST, 6 MINUTES)
+ for(var/mob/living/carbon/L in F)
+ L.adjustStaminaLoss(stamina_damage)
+ L.adjust_bodytemperature(-230)
qdel(src)
diff --git a/code/game/objects/items/handcuffs.dm b/code/game/objects/items/handcuffs.dm
index 490b754140355..35306742a8510 100644
--- a/code/game/objects/items/handcuffs.dm
+++ b/code/game/objects/items/handcuffs.dm
@@ -36,7 +36,7 @@
throw_range = 5
materials = list(/datum/material/iron=500)
breakouttime = 1 MINUTES
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50, "stamina" = 0)
var/cuffsound = 'sound/weapons/handcuffs.ogg'
var/trashtype = null //for disposable cuffs
@@ -44,24 +44,20 @@
if(!istype(C))
return
+ SEND_SIGNAL(C, COMSIG_CARBON_CUFF_ATTEMPTED, user)
+
if(iscarbon(user) && (HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)))
to_chat(user, "Uh... how do those things work?!")
apply_cuffs(user,user)
return
- // chance of monkey retaliation
- if(ismonkey(C) && prob(MONKEY_CUFF_RETALIATION_PROB))
- var/mob/living/carbon/monkey/M
- M = C
- M.retaliate(user)
-
if(!C.handcuffed)
if(C.get_num_arms(FALSE) >= 2 || C.get_arm_ignore())
C.visible_message("[user] is trying to put [src.name] on [C]!", \
"[user] is trying to put [src.name] on you!")
playsound(loc, cuffsound, 30, 1, -2)
- if(do_mob(user, C, 30) && (C.get_num_arms(FALSE) >= 2 || C.get_arm_ignore()))
+ if(do_mob(user, C, 40) && (C.get_num_arms(FALSE) >= 2 || C.get_arm_ignore()))
if(iscyborg(user))
apply_cuffs(C, user, TRUE)
else
@@ -340,11 +336,11 @@
item_state = "bola"
lefthand_file = 'icons/mob/inhands/weapons/thrown_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/thrown_righthand.dmi'
- breakouttime = 35//easy to apply, easy to break out of
+ breakouttime = 20//easy to apply, easy to break out of
gender = NEUTER
var/knockdown = 0
-/obj/item/restraints/legcuffs/bola/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback)
+/obj/item/restraints/legcuffs/bola/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, quickstart = TRUE)
if(!..())
return
playsound(src.loc,'sound/weapons/bolathrow.ogg', 75, 1)
@@ -368,7 +364,8 @@
C.update_inv_legcuffed()
SSblackbox.record_feedback("tally", "handcuffs", 1, type)
to_chat(C, "\The [src] ensnares you!")
- C.Paralyze(knockdown)
+ if(knockdown)
+ C.Paralyze(knockdown)
playsound(src, 'sound/effects/snap.ogg', 50, TRUE)
/obj/item/restraints/legcuffs/bola/tactical//traitor variant
@@ -395,12 +392,10 @@
w_class = WEIGHT_CLASS_SMALL
breakouttime = 60
-/obj/item/restraints/legcuffs/bola/energy/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
- if(iscarbon(hit_atom))
- var/obj/item/restraints/legcuffs/beartrap/B = new /obj/item/restraints/legcuffs/beartrap/energy/cyborg(get_turf(hit_atom))
- B.Crossed(hit_atom)
- qdel(src)
- ..()
+/obj/item/restraints/legcuffs/bola/energy/ensnare(mob/living/carbon/C)
+ var/obj/item/restraints/legcuffs/beartrap/B = new /obj/item/restraints/legcuffs/beartrap/energy/cyborg(get_turf(C))
+ B.Crossed(C)
+ qdel(src)
/obj/item/restraints/legcuffs/bola/gonbola
name = "gonbola"
@@ -411,11 +406,9 @@
slowdown = 0
var/datum/status_effect/gonbolaPacify/effectReference
-/obj/item/restraints/legcuffs/bola/gonbola/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
+/obj/item/restraints/legcuffs/bola/gonbola/ensnare(mob/living/carbon/C)
. = ..()
- if(iscarbon(hit_atom))
- var/mob/living/carbon/C = hit_atom
- effectReference = C.apply_status_effect(STATUS_EFFECT_GONBOLAPACIFY)
+ effectReference = C.apply_status_effect(STATUS_EFFECT_GONBOLAPACIFY)
/obj/item/restraints/legcuffs/bola/gonbola/dropped(mob/user)
. = ..()
diff --git a/code/game/objects/items/his_grace.dm b/code/game/objects/items/his_grace.dm
index 5288c329d6eab..9ec6fb3d92eeb 100644
--- a/code/game/objects/items/his_grace.dm
+++ b/code/game/objects/items/his_grace.dm
@@ -49,8 +49,8 @@
else
..()
-/obj/item/his_grace/CtrlClick(mob/user) //you can't pull his grace
- return
+/obj/item/his_grace/can_be_pulled(user, grab_state, force)
+ return FALSE
/obj/item/his_grace/examine(mob/user)
. = ..()
@@ -76,14 +76,14 @@
user.forceMove(get_turf(src))
user.visible_message("[user] scrambles out of [src]!", "You climb out of [src]!")
-/obj/item/his_grace/process()
+/obj/item/his_grace/process(delta_time)
if(!bloodthirst)
drowse()
return
if(bloodthirst < HIS_GRACE_CONSUME_OWNER && !ascended)
- adjust_bloodthirst(1 + FLOOR(LAZYLEN(contents) * 0.5, 1)) //Maybe adjust this?
+ adjust_bloodthirst((1 + FLOOR(LAZYLEN(contents) * 0.5, 1)) * delta_time) //Maybe adjust this?
else
- adjust_bloodthirst(1) //don't cool off rapidly once we're at the point where His Grace consumes all.
+ adjust_bloodthirst(1 * delta_time) //don't cool off rapidly once we're at the point where His Grace consumes all.
var/mob/living/master = get_atom_on_turf(src, /mob/living)
if(istype(master) && (src in master.held_items))
switch(bloodthirst)
@@ -117,7 +117,7 @@
L.visible_message("[src] lunges at [L]!", "[src] lunges at you!")
do_attack_animation(L, null, src)
playsound(L, 'sound/weapons/smash.ogg', 50, 1)
- playsound(L, 'sound/misc/desceration-01.ogg', 50, 1)
+ playsound(L, 'sound/misc/desecration-01.ogg', 50, 1)
L.adjustBruteLoss(force)
adjust_bloodthirst(-5) //Don't stop attacking they're right there!
else
@@ -138,6 +138,8 @@
move_gracefully()
/obj/item/his_grace/proc/move_gracefully()
+ SIGNAL_HANDLER
+
if(!awakened)
return
var/static/list/transforms
@@ -179,7 +181,7 @@
var/victims = 0
meal.visible_message("[src] swings open and devours [meal]!", "[src] consumes you!")
meal.adjustBruteLoss(200)
- playsound(meal, 'sound/misc/desceration-02.ogg', 75, 1)
+ playsound(meal, 'sound/misc/desecration-02.ogg', 75, 1)
playsound(src, 'sound/items/eatfood.ogg', 100, 1)
meal.forceMove(src)
force_bonus += HIS_GRACE_FORCE_BONUS
diff --git a/code/game/objects/items/holy_weapons.dm b/code/game/objects/items/holy_weapons.dm
index 72068e57a3473..b4b8f1a6b4141 100644
--- a/code/game/objects/items/holy_weapons.dm
+++ b/code/game/objects/items/holy_weapons.dm
@@ -13,7 +13,7 @@
desc = "Deus Vult."
icon_state = "knight_templar"
item_state = "knight_templar"
- armor = list("melee" = 50, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80)
+ armor = list("melee" = 50, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80, "stamina" = 40)
flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR
flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH
strip_delay = 80
@@ -27,13 +27,14 @@
allowed = list(/obj/item/storage/book/bible, /obj/item/nullrod, /obj/item/reagent_containers/food/drinks/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/candle, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman)
slowdown = 0
blocks_shove_knockdown = FALSE
+ move_sound = null
/obj/item/choice_beacon/holy
name = "armaments beacon"
desc = "Contains a set of armaments for the chaplain that have been reinforced with a silver and beryllium-bronze alloy, providing immunity to magic and its influences."
/obj/item/choice_beacon/holy/canUseBeacon(mob/living/user)
- if(user.mind?.isholy)
+ if(user.mind?.holy_role)
return ..()
else
playsound(src, 'sound/machines/buzz-sigh.ogg', 40, 1)
@@ -117,6 +118,7 @@
/obj/item/storage/box/holy/witchhunter/PopulateContents()
new /obj/item/clothing/suit/armor/riot/chaplain/witchhunter(src)
new /obj/item/clothing/head/helmet/chaplain/witchunter_hat(src)
+ new /obj/item/clothing/neck/crucifix(src)
/obj/item/clothing/suit/armor/riot/chaplain/witchhunter
name = "witchunter garb"
@@ -132,6 +134,49 @@
item_state = "witchhunterhat"
flags_cover = HEADCOVERSEYES
+/obj/item/storage/box/holy/graverobber
+ name = "Grave Robber Kit"
+
+/obj/item/storage/box/holy/graverobber/PopulateContents()
+ new /obj/item/clothing/suit/armor/riot/chaplain/graverobber_coat(src)
+ new /obj/item/clothing/under/rank/civilian/graverobber_under(src)
+ new /obj/item/clothing/head/chaplain/graverobber_hat(src)
+ new /obj/item/clothing/gloves/graverobber_gloves(src)
+
+/obj/item/clothing/suit/armor/riot/chaplain/graverobber_coat
+ name = "grave robber coat"
+ desc = "To those with a keen eye, gold gleams like a dagger's point."
+ icon_state = "graverobber_coat"
+ item_state = "graverobber_coat"
+ body_parts_covered = CHEST|GROIN|LEGS|ARMS
+
+/obj/item/clothing/head/chaplain/graverobber_hat
+ name = "grave robber hat"
+ desc = "A tattered leather hat. It reeks of death."
+ icon_state = "graverobber_hat"
+ item_state = "graverobber_hat"
+ flags_cover = HEADCOVERSEYES
+
+/obj/item/clothing/gloves/graverobber_gloves
+ name = "grave robber gloves"
+ desc = "A pair of leather gloves in poor condition."
+ icon_state = "graverobber-gloves"
+ item_state = "graverobber-gloves"
+ permeability_coefficient = 0.9
+ cold_protection = HANDS
+ min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT
+ heat_protection = HANDS
+ max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT
+ resistance_flags = NONE
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 20, "stamina" = 0)
+
+/obj/item/clothing/under/rank/civilian/graverobber_under
+ name = "grave robber uniform"
+ desc = "A shirt and some leather pants in poor condition."
+ icon_state = "graverobber_under"
+ item_state = "graverobber_under"
+ item_color = "graverobber_under"
+
/obj/item/storage/box/holy/adept
name = "Divine Adept Kit"
@@ -219,7 +264,7 @@
return (BRUTELOSS|FIRELOSS)
/obj/item/nullrod/attack_self(mob/user)
- if(user.mind && (user.mind.isholy) && !reskinned)
+ if(user.mind && (user.mind.holy_role) && !reskinned)
reskin_holy_weapon(user)
/obj/item/nullrod/proc/reskin_holy_weapon(mob/M)
@@ -432,20 +477,6 @@
attack_verb = list("chopped", "sliced", "cut", "zandatsu'd")
hitsound = 'sound/weapons/rapierhit.ogg'
-/obj/item/nullrod/Hypertool
- icon = 'icons/obj/device.dmi'
- icon_state = "hypertool"
- item_state = "hypertool"
- lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
- slot_flags = ITEM_SLOT_BELT
- name = "hypertool"
- desc = "A tool so powerful even you cannot perfectly use it."
- armour_penetration = 35
- damtype = BRAIN
- attack_verb = list("pulsed", "mended", "cut")
- hitsound = 'sound/effects/sparks4.ogg'
-
/obj/item/nullrod/scythe/spellblade
icon_state = "spellblade"
item_state = "spellblade"
@@ -473,6 +504,9 @@
/obj/item/nullrod/scythe/talking/attack_self(mob/living/user)
if(possessed)
return
+ if(!(GLOB.ghost_role_flags & GHOSTROLE_STATION_SENTIENCE))
+ to_chat(user, "Anomalous otherworldly energies block you from awakening the blade!")
+ return
to_chat(user, "You attempt to wake the spirit of the blade...")
@@ -517,7 +551,6 @@
tool_behaviour = TOOL_SAW
toolspeed = 0.5 //faster than normal saw
-
/obj/item/nullrod/hammmer
icon_state = "hammeron"
item_state = "hammeron"
@@ -647,7 +680,7 @@
/obj/item/nullrod/carp/attack_self(mob/living/user)
if(used_blessing)
- else if(user.mind && (user.mind.isholy))
+ else if(user.mind && (user.mind.holy_role))
to_chat(user, "You are blessed by Carp-Sie. Wild space carp will no longer attack you.")
user.faction |= "carp"
used_blessing = TRUE
@@ -719,3 +752,32 @@
righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi'
w_class = WEIGHT_CLASS_NORMAL
attack_verb = list("bashes", "smacks", "whacks")
+
+/obj/item/nullrod/hypertool
+ icon = 'icons/obj/device.dmi'
+ icon_state = "hypertool"
+ item_state = "hypertool"
+ lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
+ slot_flags = ITEM_SLOT_BELT
+ name = "hypertool"
+ desc = "A tool so powerful even you cannot perfectly use it."
+ armour_penetration = 35
+ damtype = BRAIN
+ attack_verb = list("pulsed", "mended", "cut")
+ hitsound = 'sound/effects/sparks4.ogg'
+
+/obj/item/nullrod/spear
+ name = "ancient spear"
+ desc = "An ancient spear made of brass, I mean gold, I mean bronze. It looks highly mechanical."
+ icon_state = "ratvarian_spear"
+ item_state = "ratvarian_spear"
+ lefthand_file = 'icons/mob/inhands/antag/clockwork_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/antag/clockwork_righthand.dmi'
+ icon = 'icons/obj/clockwork_objects.dmi'
+ slot_flags = ITEM_SLOT_BELT
+ armour_penetration = 10
+ sharpness = IS_SHARP_ACCURATE
+ w_class = WEIGHT_CLASS_BULKY
+ attack_verb = list("stabbed", "poked", "slashed", "clocked")
+ hitsound = 'sound/weapons/bladeslice.ogg'
diff --git a/code/game/objects/items/implants/implant.dm b/code/game/objects/items/implants/implant.dm
index 417ef03d2d4ce..b00602df9f004 100644
--- a/code/game/objects/items/implants/implant.dm
+++ b/code/game/objects/items/implants/implant.dm
@@ -88,6 +88,7 @@
if(user)
log_combat(user, target, "implanted", "\a [name]")
+ SEND_SIGNAL(src, COMSIG_IMPLANT_IMPLANTED, target, user, silent, force)
return TRUE
/obj/item/implant/proc/removed(mob/living/source, silent = FALSE, special = 0)
@@ -101,7 +102,8 @@
var/mob/living/carbon/human/H = source
H.sec_hud_set_implants()
- return 1
+ SEND_SIGNAL(src, COMSIG_IMPLANT_REMOVED, source, silent, special)
+ return TRUE
/obj/item/implant/Destroy()
if(imp_in)
@@ -112,5 +114,5 @@
return "No information available"
/obj/item/implant/dropped(mob/user)
- . = 1
+ . = TRUE
..()
diff --git a/code/game/objects/items/implants/implant_abductor.dm b/code/game/objects/items/implants/implant_abductor.dm
index 2a12868e3a69f..b551e06e56d69 100644
--- a/code/game/objects/items/implants/implant_abductor.dm
+++ b/code/game/objects/items/implants/implant_abductor.dm
@@ -5,28 +5,22 @@
icon_state = "implant"
activated = 1
var/obj/machinery/abductor/pad/home
- var/cooldown = 30
+ COOLDOWN_DECLARE(abductor_implant_cooldown)
/obj/item/implant/abductor/activate()
. = ..()
- if(cooldown == initial(cooldown))
- home.Retrieve(imp_in,1)
- cooldown = 0
- START_PROCESSING(SSobj, src)
- else
- to_chat(imp_in, "You must wait [30 - cooldown] seconds to use [src] again!")
+ if(!COOLDOWN_FINISHED(src, abductor_implant_cooldown))
+ to_chat(imp_in, "You must wait [COOLDOWN_TIMELEFT(src, abductor_implant_cooldown)*0.1] seconds to use [src] again!")
+ return
-/obj/item/implant/abductor/process()
- if(cooldown < initial(cooldown))
- cooldown++
- if(cooldown == initial(cooldown))
- STOP_PROCESSING(SSobj, src)
+ home.Retrieve(imp_in,1)
+ COOLDOWN_START(src, abductor_implant_cooldown, 60 SECONDS)
/obj/item/implant/abductor/implant(mob/living/target, mob/user, silent = FALSE, force = FALSE)
if(..())
var/obj/machinery/abductor/console/console
if(ishuman(target))
- var/datum/antagonist/abductor/A = target.mind.has_antag_datum(/datum/antagonist/abductor)
+ var/datum/antagonist/abductor/A = target.mind?.has_antag_datum(/datum/antagonist/abductor)
if(A)
console = get_abductor_console(A.team.team_number)
home = console.pad
diff --git a/code/game/objects/items/implants/implant_bb.dm b/code/game/objects/items/implants/implant_bb.dm
new file mode 100644
index 0000000000000..bc873a09e2fcb
--- /dev/null
+++ b/code/game/objects/items/implants/implant_bb.dm
@@ -0,0 +1,66 @@
+/obj/item/implant/bloodbrother
+ name = "communication implant"
+ desc = "Use this to communicate with your fellow blood brother(s)."
+ icon = 'icons/obj/radio.dmi'
+ icon_state = "headset"
+ item_color = "r"
+ var/implant_colour = "#ff0000"
+ var/list/linked_implants // All other implants that this communicates to
+
+/obj/item/implant/bloodbrother/Initialize()
+ . = ..()
+ linked_implants = list()
+
+/obj/item/implant/bloodbrother/activate()
+ . = ..()
+ if(linked_implants.len)
+ var/input = stripped_input(imp_in, "Enter a message to communicate to your blood brother(s).", "Radio Implant", "")
+ if(!input || imp_in.stat == DEAD)
+ return
+ if(CHAT_FILTER_CHECK(input))
+ to_chat(imp_in, "The message contains prohibited words!")
+ return
+
+ var/my_message = "[imp_in]: [input]" //add sender, color source with syndie color
+ var/ghost_message = "[imp_in] -> Blood Brothers: [input]"
+
+ to_chat(imp_in, my_message) // Sends message to the user
+ for(var/obj/item/implant/bloodbrother/i in linked_implants) // Sends message to all linked implnats
+ var/M = i.imp_in
+ to_chat(M, my_message)
+ for(var/M in GLOB.dead_mob_list) // Sends message to ghosts
+ var/link = FOLLOW_LINK(M, imp_in)
+ to_chat(M, "[link] [ghost_message]")
+
+ imp_in.log_talk(input, LOG_SAY, tag="Blood Brother Implant")
+ else
+ to_chat(imp_in, "There are no linked implants!")
+
+/obj/item/implant/bloodbrother/Destroy()
+ . = ..()
+ for(var/obj/item/implant/bloodbrother/i in linked_implants) // Removes this implant from the list of implants
+ i.linked_implants -= src
+
+/obj/item/implant/bloodbrother/proc/link_implant(var/obj/item/implant/bloodbrother/BB)
+ if(BB)
+ if(BB == src) // Don't want to put this implant into itself
+ return
+ linked_implants |= BB
+ BB.linked_implants |= src
+
+/obj/item/implant/bloodbrother/get_data()
+ var/dat = {"Implant Specifications:
+ Name: Donk Corp(tm) Initiate Communication Implant
+ Life: Indefinite.
+ Important Notes: Illegal
+
+ Implant Details:
+ Function: Contains a small, directly linked radio device along with a small speaker and microphone. Allows communication between two similar implants. "}
+ return dat
+
+/obj/item/implanter/bloodbrother
+ name = "implanter (communication)"
+ imp_type = /obj/item/implant/bloodbrother
+
+
+
diff --git a/code/game/objects/items/implants/implant_camera.dm b/code/game/objects/items/implants/implant_camera.dm
new file mode 100644
index 0000000000000..503ccee06baf6
--- /dev/null
+++ b/code/game/objects/items/implants/implant_camera.dm
@@ -0,0 +1,35 @@
+/obj/item/implant/camera
+ name = "camera implant"
+ desc = "Watchful eye inside you."
+ activated = FALSE
+ var/obj/machinery/camera/camera
+
+/obj/item/implant/camera/get_data()
+ var/dat = {"Implant Specifications:
+ Name: Camera Implant
+ Life: 24 hours after death of host
+ Implant Details:
+ Function: Remote surveillance of implanted subject."}
+ return dat
+
+/obj/item/implant/camera/on_implanted(mob/user)
+ camera = new (user) //Insert the camera directly into the mob so the camera actually shows what it sees
+ camera.c_tag = "IMPLANT #[rand(1, 999)]"
+ camera.network = list("ss13")
+ camera.internal_light = FALSE //No AI camera light
+
+/obj/item/implant/camera/removed(mob/living/source, silent, special)
+ . = ..()
+ if(.)
+ QDEL_NULL(camera)
+ return TRUE
+ return FALSE
+
+/obj/item/implanter/camera
+ name = "implanter (camera)"
+ imp_type = /obj/item/implant/camera
+
+/obj/item/implantcase/camera
+ name = "implant case - 'Camera'"
+ desc = "A glass case containing a camera implant."
+ imp_type = /obj/item/implant/camera
diff --git a/code/game/objects/items/implants/implant_deathrattle.dm b/code/game/objects/items/implants/implant_deathrattle.dm
new file mode 100644
index 0000000000000..8ba61bbc5bbb5
--- /dev/null
+++ b/code/game/objects/items/implants/implant_deathrattle.dm
@@ -0,0 +1,79 @@
+/datum/deathrattle_group
+ var/name
+ var/list/implants = list()
+
+/datum/deathrattle_group/New(name)
+ if(name)
+ src.name = name
+ else
+ // Give the group a unique name for debugging, and possible future
+ // use for making custom linked groups.
+ src.name = "[rand(100,999)] [pick(GLOB.phonetic_alphabet)]"
+
+/*
+ * Proc called by new implant being added to the group. Listens for the
+ * implant being implanted, removed and destroyed.
+ *
+ * If implant is already implanted in a person, then trigger the implantation
+ * code.
+ */
+/datum/deathrattle_group/proc/register(obj/item/implant/deathrattle/implant)
+ if(implant in implants)
+ return
+ RegisterSignal(implant, COMSIG_IMPLANT_IMPLANTED, .proc/on_implant_implantation)
+ RegisterSignal(implant, COMSIG_IMPLANT_REMOVED, .proc/on_implant_removal)
+ RegisterSignal(implant, COMSIG_PARENT_QDELETING, .proc/on_implant_destruction)
+
+ implants += implant
+
+ if(implant.imp_in)
+ on_implant_implantation(implant.imp_in)
+
+/datum/deathrattle_group/proc/on_implant_implantation(obj/item/implant/implant, mob/living/target, mob/user, silent = FALSE, force = FALSE)
+ SIGNAL_HANDLER
+
+ RegisterSignal(target, COMSIG_MOB_STATCHANGE, .proc/on_user_statchange)
+
+/datum/deathrattle_group/proc/on_implant_removal(obj/item/implant/implant, mob/living/source, silent = FALSE, special = 0)
+ SIGNAL_HANDLER
+
+ UnregisterSignal(source, COMSIG_MOB_STATCHANGE)
+
+/datum/deathrattle_group/proc/on_implant_destruction(obj/item/implant/implant)
+ SIGNAL_HANDLER
+
+ implants -= implant
+
+/datum/deathrattle_group/proc/on_user_statchange(mob/living/owner, new_stat)
+ SIGNAL_HANDLER
+
+ if(new_stat != DEAD)
+ return
+
+ var/name = owner.mind ? owner.mind.name : owner.real_name
+ var/area = get_area_name(get_turf(owner))
+
+ for(var/_implant in implants)
+ var/obj/item/implant/deathrattle/implant = _implant
+
+ // Skip the unfortunate soul, and any unimplanted implants
+ if(implant.imp_in == owner || !implant.imp_in)
+ continue
+
+ // Deliberately the same message framing as nanite message + ghost deathrattle
+ to_chat(implant.imp_in, "You hear a strange, robotic voice in your head... \"[name] has died at [area].\"")
+
+/obj/item/implant/deathrattle
+ name = "deathrattle implant"
+ desc = "Hope no one else dies, prepare for when they do."
+
+ activated = FALSE
+
+/obj/item/implant/deathrattle/can_be_implanted_in(mob/living/target)
+ // Can be implanted in anything that's a mob. Syndicate cyborgs, talking fish, humans...
+ return TRUE
+
+/obj/item/implantcase/deathrattle
+ name = "implant case - 'Deathrattle'"
+ desc = "A glass case containing a deathrattle implant."
+ imp_type = /obj/item/implant/deathrattle
diff --git a/code/game/objects/items/implants/implant_exile.dm b/code/game/objects/items/implants/implant_exile.dm
index 9cdcac16fdf0f..5a526a31b1040 100644
--- a/code/game/objects/items/implants/implant_exile.dm
+++ b/code/game/objects/items/implants/implant_exile.dm
@@ -42,22 +42,22 @@
if(SSticker.current_state <= GAME_STATE_PREGAME)
return TRUE
var/turf/T = get_turf(parent)
- if (!T)
+ if(!T)
return FALSE
- if (is_station_level(T.z)) // Are they on the station Z-level? If so trigger relocate()
+ if(is_station_level(T.z)) // Are they on the station Z-level? If so trigger relocate()
return FALSE
return TRUE
//Override to plop the disk back to a syndie crew spawn rather than somewhere on the station.
/datum/component/stationloving/hotelloving/relocate()
- var/mob/Hotelstaff = parent
- if(ismob(Hotelstaff))
+ var/mob/hotelstaff = parent
+ if(ismob(hotelstaff))
if(!QDELETED(src)) // if you don't do this the body gets continuously dusted forever. While this is funny, an infinitely large pile of remains that crashes clients on right click isn't.
qdel(src)
- to_chat(Hotelstaff,"The implant's anti-escape mechanisms activate!")
- Hotelstaff.dust() // Nice try hotel staff
- message_admins("[ADMIN_LOOKUPFLW(Hotelstaff)] tried to enter the station as hotel staff and was dusted.")
+ to_chat(hotelstaff,"The implant's anti-escape mechanisms activate!")
+ hotelstaff.dust() // Nice try hotel staff
+ message_admins("[ADMIN_LOOKUPFLW(hotelstaff)] tried to enter the station as hotel staff and was dusted.")
else
qdel(src) // This should only ever be applied to mobs
diff --git a/code/game/objects/items/implants/implant_gang.dm b/code/game/objects/items/implants/implant_gang.dm
deleted file mode 100644
index 02b4744e14b38..0000000000000
--- a/code/game/objects/items/implants/implant_gang.dm
+++ /dev/null
@@ -1,75 +0,0 @@
-/obj/item/implant/gang
- name = "gang implant"
- desc = "Makes you a gangster or such."
- activated = 0
- var/datum/team/gang/gang
-
-/obj/item/implant/gang/Initialize(loc, setgang)
- ..()
- gang = setgang
-
-/obj/item/implant/gang/get_data()
- var/dat = {"Implant Specifications:
- Name: Criminal brainwash implant
- Life: A few seconds after injection.
- Important Notes: Illegal
-
- Implant Details:
- Function: Contains a small pod of nanobots that change the host's brain to be loyal to a certain organization.
- Special Features: This device will also emit a small EMP pulse, destroying any other implants within the host's brain.
- Integrity: Implant's EMP function will destroy itself in the process."}
- return dat
-
-/obj/item/implant/gang/implant(mob/living/target, mob/user, silent = 0)
- if(!target || !target.mind || target.stat == DEAD)
- return 0
- var/datum/antagonist/gang/G = target.mind.has_antag_datum(/datum/antagonist/gang)
- if(G && G.gang == G)
- return 0 // it's pointless
- if(..())
- for(var/obj/item/implant/I in target.implants)
- if(I != src)
- qdel(I)
-
- if(ishuman(target))
- var/success
- if(G)
- if(!istype(G, /datum/antagonist/gang/boss))
- success = TRUE //Was not a gang boss, convert as usual
- target.mind.remove_antag_datum(/datum/antagonist/gang)
- else
- success = TRUE
- if(!success)
- target.visible_message("[target] seems to resist the implant!", "You feel the influence of your enemies try to invade your mind!")
- return FALSE
- target.mind.add_antag_datum(/datum/antagonist/gang, gang)
- qdel(src)
- return TRUE
-
-/obj/item/implanter/gang
- name = "implanter (gang)"
-
-/obj/item/implanter/gang/Initialize(loc, gang)
- if(!gang)
- qdel(src)
- return
- imp = new /obj/item/implant/gang(src,gang)
- ..()
-
-
-
-/obj/item/implant/mindshield/implant(mob/living/target, mob/user, silent = FALSE) //putting this here, pls no bulli. - qwerty
- if(..())
- if(!target.mind)
- return TRUE
- if(target.mind.has_antag_datum(/datum/antagonist/gang/boss))
- if(!silent)
- target.visible_message("[target] seems to resist the implant!", "You feel something interfering with your mental conditioning, but you resist it!")
- removed(target, 1)
- qdel(src)
- return FALSE
- target.mind.remove_antag_datum(/datum/antagonist/gang)
- if(!silent)
- to_chat(target, "You feel a sense of peace and security. You are now protected from brainwashing.")
- return TRUE
- return FALSE
diff --git a/code/game/objects/items/implants/implant_misc.dm b/code/game/objects/items/implants/implant_misc.dm
index 72aba65050525..1e61bc136443d 100644
--- a/code/game/objects/items/implants/implant_misc.dm
+++ b/code/game/objects/items/implants/implant_misc.dm
@@ -78,17 +78,28 @@
name = "health implant"
activated = 0
var/healthstring = ""
+ var/list/raw_data = list()
-/obj/item/implant/health/proc/sensehealth()
+/obj/item/implant/health/proc/sensehealth(get_list = FALSE)
if (!imp_in)
return "ERROR"
else
if(isliving(imp_in))
var/mob/living/L = imp_in
healthstring = "Oxygen Deprivation Damage => [round(L.getOxyLoss())] Fire Damage => [round(L.getFireLoss())] Toxin Damage => [round(L.getToxLoss())] Brute Force Damage => [round(L.getBruteLoss())]"
- if (!healthstring)
+ raw_data = list() //Reset list
+ raw_data["oxy"] = list("[round(L.getOxyLoss())]") //Suffocation
+ raw_data["burn"] = list("[round(L.getFireLoss())]") //Burn
+ raw_data["tox"] = list("[round(L.getToxLoss())]") //Tox
+ raw_data["brute"] = list("[round(L.getBruteLoss())]") //Brute
+ if(!healthstring) //I have no idea who made it go this order but okay.
healthstring = "ERROR"
- return healthstring
+ if(!length(raw_data))
+ raw_data = list("ERROR")
+ if(!get_list)
+ return healthstring
+ else
+ return raw_data
/obj/item/implant/radio
name = "internal radio implant"
@@ -96,6 +107,7 @@
var/obj/item/radio/radio
var/radio_key
var/subspace_transmission = FALSE
+ var/radio_silent = TRUE
icon = 'icons/obj/radio.dmi'
icon_state = "walkietalkie"
@@ -113,10 +125,15 @@
radio.name = "internal radio"
radio.subspace_transmission = subspace_transmission
radio.canhear_range = -1
+ radio.radio_silent = radio_silent
if(radio_key)
radio.keyslot = new radio_key
radio.recalculateChannels()
+/obj/item/implant/radio/Destroy()
+ QDEL_NULL(radio)
+ return ..()
+
/obj/item/implant/radio/mining
radio_key = /obj/item/encryptionkey/headset_cargo
@@ -146,16 +163,6 @@
if(!user.mind.has_antag_datum(/datum/antagonist/incursion))
user.visible_message("[imp_in] starts beeping ominously!", "You have a sudden feeling of dread. The implant is rigged to explode!")
playsound(user, 'sound/items/timer.ogg', 30, 0)
- sleep(50)
- playsound(user, 'sound/items/timer.ogg', 30, 0)
- sleep(40)
- playsound(user, 'sound/items/timer.ogg', 30, 0)
- sleep(30)
- playsound(user, 'sound/items/timer.ogg', 30, 0)
- sleep(20)
- playsound(user, 'sound/items/timer.ogg', 30, 0)
- sleep(10)
- playsound(user, 'sound/items/timer.ogg', 30, 0)
explosion(src,0,0,2,2, flame_range = 2)
user.gib(1)
qdel(src)
diff --git a/code/game/objects/items/implants/implant_track.dm b/code/game/objects/items/implants/implant_track.dm
index d0455905eb9bf..4370bf908b21f 100644
--- a/code/game/objects/items/implants/implant_track.dm
+++ b/code/game/objects/items/implants/implant_track.dm
@@ -2,8 +2,12 @@
name = "tracking implant"
desc = "Track with this."
activated = FALSE
- var/lifespan_postmortem = 6000 //for how many deciseconds after user death will the implant work?
- var/allow_teleport = TRUE //will people implanted with this act as teleporter beacons?
+ ///for how many deciseconds after user death will the implant work?
+ var/lifespan_postmortem = 6000
+ ///will people implanted with this act as teleporter beacons?
+ var/allow_teleport = TRUE
+ ///The id of the timer that's qdeleting us
+ var/timerid
/obj/item/implant/tracking/c38
name = "TRAC implant"
@@ -13,7 +17,11 @@
/obj/item/implant/tracking/c38/Initialize()
. = ..()
- QDEL_IN(src, lifespan)
+ timerid = QDEL_IN(src, lifespan)
+
+/obj/item/implant/tracking/c38/Destroy()
+ deltimer(timerid)
+ return ..()
/obj/item/implant/tracking/New()
..()
diff --git a/code/game/objects/items/implants/implantchair.dm b/code/game/objects/items/implants/implantchair.dm
index e7375a7dcad03..cab3cf9d4a901 100644
--- a/code/game/objects/items/implants/implantchair.dm
+++ b/code/game/objects/items/implants/implantchair.dm
@@ -36,7 +36,7 @@
if(!ui)
ui = new(user, src, "implantchair")
ui.open()
-
+ ui.set_autoupdate(TRUE)
/obj/machinery/implantchair/ui_data()
var/list/data = list()
@@ -197,7 +197,7 @@
return FALSE
if(ishuman(C))
var/mob/living/carbon/human/H = C
- if(istype(H.get_item_by_slot(SLOT_HEAD), /obj/item/clothing/head/foilhat))
+ if(istype(H.get_item_by_slot(ITEM_SLOT_HEAD), /obj/item/clothing/head/foilhat))
to_chat(H, "Your trusty tinfoil hat shorts out the implant as it plunges into your skull!")
H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 75)
H.emote("scream")
diff --git a/code/game/objects/items/implants/implantpad.dm b/code/game/objects/items/implants/implantpad.dm
index 7bcf3c8638ae6..82fdf8131cb98 100644
--- a/code/game/objects/items/implants/implantpad.dm
+++ b/code/game/objects/items/implants/implantpad.dm
@@ -32,7 +32,6 @@
. = ..()
/obj/item/implantpad/AltClick(mob/user)
- ..()
if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
return
if(!case)
diff --git a/code/game/objects/items/kitchen.dm b/code/game/objects/items/kitchen.dm
index a3d000c1d220c..57297f93dbc7b 100644
--- a/code/game/objects/items/kitchen.dm
+++ b/code/game/objects/items/kitchen.dm
@@ -27,7 +27,7 @@
flags_1 = CONDUCT_1
attack_verb = list("attacked", "stabbed", "poked")
hitsound = 'sound/weapons/bladeslice.ogg'
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30, "stamina" = 0)
var/datum/reagent/forkload //used to eat omelette
/obj/item/kitchen/fork/suicide_act(mob/living/carbon/user)
@@ -56,36 +56,6 @@
else
return ..()
-/obj/item/kitchen/knife/poison
- name = "venom knife"
- icon_state = "poisonknife"
- force = 12
- throwforce = 15
- throw_speed = 5
- throw_range = 7
- var/amount_per_transfer_from_this = 5
- var/list/possible_transfer_amounts
- desc = "An infamous knife of syndicate design, it has a tiny hole going through the blade to the handle which stores toxins."
- materials = null
-
-/obj/item/kitchen/knife/poison/Initialize()
- . = ..()
- create_reagents(40,OPENCONTAINER)
- possible_transfer_amounts = list(3,5)
-
-/obj/item/kitchen/knife/poison/attack_self(mob/user)
- if(possible_transfer_amounts.len)
- var/i=0
- for(var/A in possible_transfer_amounts)
- i++
- if(A == amount_per_transfer_from_this)
- if(i[src]'s transfer amount is now [amount_per_transfer_from_this] units.")
- return
-
/obj/item/kitchen/knife/poison/attack(mob/living/M, mob/user)
if (!istype(M))
return
@@ -116,7 +86,7 @@
materials = list(/datum/material/iron=12000)
attack_verb = list("slashed", "stabbed", "sliced", "tore", "ripped", "diced", "cut")
sharpness = IS_SHARP_ACCURATE
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50, "stamina" = 0)
var/bayonet = FALSE //Can this be attached to a gun?
custom_price = 30
@@ -160,11 +130,41 @@
w_class = WEIGHT_CLASS_NORMAL
custom_price = 60
+/obj/item/kitchen/knife/poison
+ name = "venom knife"
+ icon_state = "poisonknife"
+ force = 12
+ throwforce = 15
+ throw_speed = 5
+ throw_range = 7
+ var/amount_per_transfer_from_this = 5
+ var/list/possible_transfer_amounts
+ desc = "An infamous knife of syndicate design, it has a tiny hole going through the blade to the handle which stores toxins."
+ materials = null
+
+/obj/item/kitchen/knife/poison/Initialize()
+ . = ..()
+ create_reagents(40,OPENCONTAINER)
+ possible_transfer_amounts = list(3,5)
+
+/obj/item/kitchen/knife/poison/attack_self(mob/user)
+ if(possible_transfer_amounts.len)
+ var/i=0
+ for(var/A in possible_transfer_amounts)
+ i++
+ if(A == amount_per_transfer_from_this)
+ if(i[src]'s transfer amount is now [amount_per_transfer_from_this] units.")
+ return
+
/obj/item/kitchen/knife/combat
name = "combat knife"
icon_state = "buckknife"
desc = "A military combat utility survival knife."
- embedding = list("embedded_pain_multiplier" = 4, "embed_chance" = 65, "embedded_fall_chance" = 10, "embedded_ignore_throwspeed_threshold" = TRUE)
+ embedding = list("pain_mult" = 4, "embed_chance" = 65, "fall_chance" = 10, "ignore_throwspeed_threshold" = TRUE, "armour_block" = 60)
force = 20
throwforce = 20
attack_verb = list("slashed", "stabbed", "sliced", "tore", "ripped", "cut")
@@ -173,7 +173,7 @@
/obj/item/kitchen/knife/combat/survival
name = "survival knife"
icon_state = "survivalknife"
- embedding = list("embedded_pain_multiplier" = 4, "embed_chance" = 35, "embedded_fall_chance" = 10)
+ embedding = list("pain_mult" = 4, "embed_chance" = 35, "fall_chance" = 10, "armour_block" = 40)
desc = "A hunting grade survival knife."
force = 15
throwforce = 15
@@ -186,7 +186,7 @@
lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
desc = "A sharpened bone. The bare minimum in survival."
- embedding = list("embedded_pain_multiplier" = 4, "embed_chance" = 35, "embedded_fall_chance" = 10)
+ embedding = list("pain_mult" = 4, "embed_chance" = 35, "fall_chance" = 10, "armour_block" = 40)
force = 15
throwforce = 15
materials = list()
@@ -208,7 +208,27 @@
throwforce = 12//fuck git
materials = list()
attack_verb = list("shanked", "shivved")
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0, "stamina" = 0)
+
+// Shank - Makeshift weapon that can embed on throw
+/obj/item/kitchen/knife/shank
+ name = "Shank"
+ desc = "A crude knife fashioned by wrapping some cable around a glass shard. It looks like it could be thrown with some force.. and stick. Good to throw at someone chasing you"
+ icon = 'icons/obj/items_and_weapons.dmi'
+ icon_state = "shank"
+ item_state = "shank"
+ lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
+ force = 8 // 3 more than base glass shard
+ throwforce = 8
+ throw_speed = 5 //yeets
+ armour_penetration = 10 //spear has 10 armour pen, I think its fitting another glass tipped item should have it too
+ embedding = list("embedded_pain_multiplier" = 6, "embed_chance" = 40, "embedded_fall_chance" = 5, "armour_block" = 30) // Incentive to disengage/stop chasing when stuck
+ attack_verb = list("stuck", "shanked")
+
+/obj/item/kitchen/knife/shank/suicide_act(mob/user)
+ user.visible_message("[user] is slitting [user.p_their()] [pick("wrists", "throat")] with the shank! It looks like [user.p_theyre()] trying to commit suicide.")
+ return (BRUTELOSS)
/obj/item/kitchen/rollingpin
name = "rolling pin"
@@ -225,4 +245,3 @@
/obj/item/kitchen/rollingpin/suicide_act(mob/living/carbon/user)
user.visible_message("[user] begins flattening [user.p_their()] head with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
return BRUTELOSS
-/* Trays moved to /obj/item/storage/bag */
diff --git a/code/game/objects/items/manuals.dm b/code/game/objects/items/manuals.dm
index 40127f2a28b42..c95a9edda097b 100644
--- a/code/game/objects/items/manuals.dm
+++ b/code/game/objects/items/manuals.dm
@@ -73,47 +73,47 @@
Width: 1.8m
Top speed: 5km/hour
Operation in vacuum/hostile environment: Possible
-
Connect all hydraulic fittings and tighten them up with a wrench
-
Adjust the servohydraulics with a screwdriver
-
Wire the chassis. (Cable is not included.)
+
Connect all exosuit parts to the chassis frame.
+
Connect all hydraulic fittings and tighten them up with a wrench.
+
Adjust the servohydraulics with a screwdriver.
+
Wire the chassis (cable is not included).
Use the wirecutters to remove the excess cable if needed.
-
Install the central control module (Not included. Use supplied datadisk to create one).
+
Install the central control module (not included. Use supplied datadisk to create one).
Secure the mainboard with a screwdriver.
-
Install the peripherals control module (Not included. Use supplied datadisk to create one).
-
Secure the peripherals control module with a screwdriver
-
Install the internal armor plating (Not included due to Nanotrasen regulations. Can be made using 5 iron sheets.)
-
Secure the internal armor plating with a wrench
-
Weld the internal armor plating to the chassis
-
Install the external reinforced armor plating (Not included due to Nanotrasen regulations. Can be made using 5 reinforced iron sheets.)
-
Secure the external reinforced armor plating with a wrench
-
Weld the external reinforced armor plating to the chassis
+
Install the peripherals control module (not included. Use supplied datadisk to create one).
+
Secure the peripherals control module with a screwdriver.
+
Install the internal armor plating (not included due to Nanotrasen regulations. Can be made using 5 iron sheets).
+
Secure the internal armor plating with a wrench.
+
Weld the internal armor plating to the chassis.
+
Install the external reinforced armor plating (not included due to Nanotrasen regulations. Can be made using 5 reinforced iron sheets).
+
Secure the external reinforced armor plating with a wrench.
+
Weld the external reinforced armor plating to the chassis.
Additional Information:
The firefighting variation is made in a similar fashion.
A firesuit must be connected to the Firefighter chassis for heat shielding.
Internal armor is plasteel for additional strength.
External armor must be installed in 2 parts, totaling 10 sheets.
-
Completed mech is more resiliant against fire, and is a bit more durable overall
+
Completed mech is more resiliant against fire, and is a bit more durable overall.
Nanotrasen is determined to the safety of its investments employees.
-
Operation
+
Operation:
Please consult the Nanotrasen compendium "Robotics for Dummies".
"}
diff --git a/code/game/objects/items/melee/energy.dm b/code/game/objects/items/melee/energy.dm
index 2ea6afc21809f..37adc1f8a3bc1 100644
--- a/code/game/objects/items/melee/energy.dm
+++ b/code/game/objects/items/melee/energy.dm
@@ -3,7 +3,7 @@
hitsound_on = 'sound/weapons/blade1.ogg'
heat = 3500
max_integrity = 200
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30, "stamina" = 0)
resistance_flags = FIRE_PROOF
var/brightness_on = 3
@@ -93,12 +93,13 @@
righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
force = 3
throwforce = 5
+ throwforce_on = 35 //Does a lot of damage on throw, but will embed
hitsound = "swing_hit" //it starts deactivated
attack_verb_off = list("tapped", "poked")
throw_speed = 3
throw_range = 5
sharpness = IS_SHARP
- embedding = list("embed_chance" = 75, "embedded_impact_pain_multiplier" = 10)
+ embedding = list("embed_chance" = 200, "armour_block" = 60, "max_pain_mult" = 15)
armour_penetration = 35
block_level = 1
block_upgrade_walk = 1
@@ -123,8 +124,8 @@
force = 18 //About as much as a spear
hitsound = 'sound/weapons/circsawhit.ogg'
icon = 'icons/obj/surgery.dmi'
- icon_state = "saw"
- icon_state_on = "saw"
+ icon_state = "esaw_0"
+ icon_state_on = "esaw_1"
lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
item_color = null //stops icon from breaking when turned on.
@@ -142,7 +143,7 @@
var/obj/item/stock_parts/cell/C = R.cell
if(active && !(C.use(hitcost)))
attack_self(R)
- to_chat(R, "It's out of charge!")
+ balloon_alert(R, "Out of charge")
return
return ..()
@@ -215,13 +216,13 @@
if(!hacked)
hacked = TRUE
item_color = "rainbow"
- to_chat(user, "RNBW_ENGAGE")
+ balloon_alert(user, "RNBW_ENGAGE")
if(active)
icon_state = "swordrainbow"
user.update_inv_hands()
else
- to_chat(user, "It's already fabulous!")
+ balloon_alert(user, "It's already fabulous!")
else
return ..()
@@ -267,6 +268,10 @@
spark_system.set_up(5, 0, src)
spark_system.attach(src)
+/obj/item/melee/transforming/energy/blade/Destroy()
+ QDEL_NULL(spark_system)
+ return ..()
+
/obj/item/melee/transforming/energy/blade/transform_weapon(mob/living/user, supress_message_text)
return
diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm
index 0b3a5edb3ffdc..1805f2d146e93 100644
--- a/code/game/objects/items/melee/misc.dm
+++ b/code/game/objects/items/melee/misc.dm
@@ -136,6 +136,27 @@
user.death(FALSE)
REMOVE_TRAIT(src, TRAIT_NODROP, SABRE_SUICIDE_TRAIT)
+/obj/item/melee/sabre/mime
+ name = "Bread Blade"
+ desc = "An elegant weapon, it has an inscription on it that says: \"La Gluten Gutter\"."
+ force = 18
+ icon_state = "rapier"
+ item_state = "rapier"
+ lefthand_file = null
+ righthand_file = null
+ block_power = 60
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100)
+
+/obj/item/melee/sabre/mime/on_exit_storage(datum/component/storage/concrete/R)
+ var/obj/item/storage/belt/sabre/mime/M = R.real_location()
+ if(istype(M))
+ playsound(M, 'sound/items/unsheath.ogg', 25, TRUE)
+
+/obj/item/melee/sabre/on_enter_storage(datum/component/storage/concrete/R)
+ var/obj/item/storage/belt/sabre/mime/M = R.real_location()
+ if(istype(M))
+ playsound(M, 'sound/items/sheath.ogg', 25, TRUE)
+
/obj/item/melee/classic_baton
name = "classic baton"
desc = "A wooden truncheon for beating criminal scum."
@@ -185,7 +206,7 @@
. = list()
.["visibletrip"] = "[user] has knocked [target]'s legs out from under them with [src]!"
- .["localtrip"] = "[user] has knocked your legs out from under you [src]!"
+ .["localtrip"] = "[user] has knocked your legs out from under you [src]!"
.["visibledisarm"] = "[user] has disarmed [target] with [src]!"
.["localdisarm"] = "[user] whacks your arm with [src], causing a coursing pain!"
.["visiblestun"] = "[user] beat [target] with [src]!"
@@ -295,7 +316,7 @@
if(!iscarbon(user))
target.LAssailant = null
else
- target.LAssailant = user
+ target.LAssailant = WEAKREF(user)
cooldown_check = world.time + cooldown
else
var/wait_desc = get_wait_description()
@@ -380,7 +401,7 @@
//Contractor Baton
/obj/item/melee/classic_baton/contractor_baton
name = "contractor baton"
- desc = "A compact, specialised baton assigned to Syndicate contractors. Applies light electrical shocks to targets."
+ desc = "A compact, specialised baton assigned to Syndicate contractors. Applies light electric shocks that can resonate with a specific targets brain frequency causing significant stunning effects."
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "contractor_baton_0"
lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
@@ -393,6 +414,9 @@
force = 5
on = FALSE
var/knockdown_time_carbon = (1.5 SECONDS) // Knockdown length for carbons.
+ var/stamina_damage_non_target = 55
+ var/stamina_damage_target = 85
+ var/target_confusion = 4 SECONDS
stamina_damage = 85
affect_silicon = TRUE
@@ -407,7 +431,7 @@
force_off = 5
weight_class_on = WEIGHT_CLASS_NORMAL
-
+ var/datum/antagonist/traitor/owner_data = null
/obj/item/melee/classic_baton/contractor_baton/get_wait_description()
return "The baton is still charging!"
@@ -443,6 +467,11 @@
if(!on)
return ..()
+ if(!owner_data || owner_data?.owner?.current != user)
+ return ..()
+
+ var/is_target = owner_data.contractor_hub?.current_contract?.contract?.target == target.mind
+
add_fingerprint(user)
if((HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50))
to_chat(user, "You hit yourself over the head.")
@@ -499,9 +528,15 @@
user.do_attack_animation(target)
playsound(get_turf(src), on_stun_sound, 75, 1, -1)
- target.Knockdown(knockdown_time_carbon)
- target.drop_all_held_items()
- target.adjustStaminaLoss(stamina_damage)
+ if(is_target)
+ target.Knockdown(knockdown_time_carbon)
+ target.drop_all_held_items()
+ target.adjustStaminaLoss(stamina_damage)
+ if(target.confused < 6 SECONDS)
+ target.confused = min(target.confused + target_confusion, 6 SECONDS)
+ else
+ target.Knockdown(knockdown_time_carbon)
+ target.adjustStaminaLoss(stamina_damage_non_target)
additional_effects_carbon(target, user)
log_combat(user, target, "stunned", src)
@@ -519,6 +554,14 @@
if (wait_desc)
to_chat(user, wait_desc)
+/obj/item/melee/classic_baton/contractor_baton/pickup(mob/user)
+ . = ..()
+ if(!owner_data)
+ var/datum/antagonist/traitor/traitor_data = user.mind.has_antag_datum(/datum/antagonist/traitor)
+ if(traitor_data)
+ owner_data = traitor_data
+ to_chat(user, "[src] scans your genetic data as you pick it up, creating an uplink with the syndicate database. Attacking your current target will stun and mute them, however the baton is weak against non-targets.")
+
// Supermatter Sword
/obj/item/melee/supermatter_sword
name = "supermatter sword"
@@ -634,7 +677,7 @@
lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
slot_flags = ITEM_SLOT_BELT
- force = 0.001
+ force = 0.001 //"Some attack noises shit"
reach = 3
w_class = WEIGHT_CLASS_NORMAL
attack_verb = list("flogged", "whipped", "lashed", "disciplined")
@@ -642,47 +685,43 @@
/obj/item/melee/curator_whip/attack(mob/living/target, mob/living/user)
. = ..()
- var/mob/living/carbon/human/H = target
- var/mob/living/carbon/human/U = user
- if(ishuman(target))
- if((user.zone_selected == BODY_ZONE_CHEST) || (user.zone_selected == BODY_ZONE_HEAD) || (user.zone_selected == BODY_ZONE_PRECISE_GROIN))
- if(H.getarmor(type = "melee") < 16)
- H.emote("scream")
- H.visible_message("[U] whips [H]!", "[U] whips you! It stings!")
- if((user.zone_selected == BODY_ZONE_R_LEG) || (user.zone_selected == BODY_ZONE_L_LEG))
- var/dist = get_dist(H, U)
- if(dist < 2)
- to_chat(user, "[H] is too close to trip with the whip!")
- return
- else
- target.Knockdown(30)
- log_combat(user, target, "tripped", src)
- H.visible_message("[U] trips [H]!", "[U] whips your legs out from under you!")
- return
- if(user.zone_selected == BODY_ZONE_L_ARM)
- var/obj/item/I = H.get_held_items_for_side("left")
- if(I)
- if(H.dropItemToGround(I))
- H.visible_message("[I] is yanked off [H]'s hand by [src]!","[U] grabs [I] with [src]!")
- if(!user.get_inactive_held_item())
- U.throw_mode_on()
- U.swap_hand()
- I.throw_at(user, 10, 2)
- to_chat(user, "You pull [I] towards yourself.")
- log_combat(user, target, "disarmed", src)
- H.visible_message("[U] disarms [H]!", "[U] disarmed you!")
- if(user.zone_selected == BODY_ZONE_R_ARM)
- var/obj/item/I = H.get_held_items_for_side("right")
- if(I)
- if(H.dropItemToGround(I))
- H.visible_message("[I] is yanked off [H]'s hand by [src]!","[U] grabs [I] with [src]!")
- to_chat(user, "You pull [I] towards yourself.")
- if(!user.get_inactive_held_item())
- U.throw_mode_on()
- U.swap_hand()
- I.throw_at(user, 10, 2)
+ if(!ishuman(target))
+ return
+
+ switch(user.zone_selected)
+ if(BODY_ZONE_L_ARM)
+ whip_disarm(user, target, "left")
+ if(BODY_ZONE_R_ARM)
+ whip_disarm(user, target, "right")
+ if(BODY_ZONE_R_LEG, BODY_ZONE_L_LEG)
+ whip_trip(user, target)
+ else
+ whip_lash(user, target)
+
+/obj/item/melee/curator_whip/proc/whip_disarm(mob/living/carbon/user, mob/living/target, side)
+ var/obj/item/I = target.get_held_items_for_side(side)
+ if(I)
+ if(target.dropItemToGround(I))
+ target.visible_message("[I] is yanked out of [target]'s hands by [src]!","[user] grabs [I] out of your hands with [src]!")
+ to_chat(user, "You yank [I] towards yourself.")
log_combat(user, target, "disarmed", src)
- H.visible_message("[user] disarms [H]!", "[U] disarmed you!")
+ if(!user.get_inactive_held_item())
+ user.throw_mode_on()
+ user.swap_hand()
+ I.throw_at(user, 10, 2)
+
+/obj/item/melee/curator_whip/proc/whip_trip(mob/living/user, mob/living/target) //this is bad and ugly but not as bad and ugly as the original code
+ if(get_dist(user, target) < 2)
+ to_chat(user, "[target] is too close to trip with the whip!")
+ return
+ target.Knockdown(3 SECONDS)
+ log_combat(user, target, "tripped", src)
+ target.visible_message("[user] knocks [target] off [target.p_their()] feet!", "[user] yanks your legs out from under you!")
+
+/obj/item/melee/curator_whip/proc/whip_lash(mob/living/user, mob/living/target)
+ if(target.getarmor(type = "melee") < 16)
+ target.emote("scream")
+ target.visible_message("[user] whips [target]!", "[user] whips you! It stings!")
/obj/item/melee/roastingstick
name = "advanced roasting stick"
diff --git a/code/game/objects/items/melee/transforming.dm b/code/game/objects/items/melee/transforming.dm
index 21cce03275051..5a9ce7a1c1f35 100644
--- a/code/game/objects/items/melee/transforming.dm
+++ b/code/game/objects/items/melee/transforming.dm
@@ -18,7 +18,7 @@
if(active)
return ..()
return 0
-
+
/obj/item/melee/transforming/Initialize()
. = ..()
@@ -28,6 +28,8 @@
else
if(attack_verb_off.len)
attack_verb = attack_verb_off
+ if(embedding)
+ updateEmbedding()
if(is_sharp())
AddComponent(/datum/component/butchering, 50, 100, 0, hitsound, !active)
@@ -59,6 +61,8 @@
attack_verb = attack_verb_on
icon_state = icon_state_on
w_class = w_class_on
+ if(embedding)
+ updateEmbedding()
else
force = initial(force)
throwforce = initial(throwforce)
@@ -68,6 +72,8 @@
attack_verb = attack_verb_off
icon_state = initial(icon_state)
w_class = initial(w_class)
+ if(embedding)
+ disableEmbedding()
if(is_sharp())
var/datum/component/butchering/BT = LoadComponent(/datum/component/butchering)
BT.butchering_enabled = TRUE
@@ -85,7 +91,7 @@
/obj/item/melee/transforming/proc/transform_messages(mob/living/user, supress_message_text)
playsound(user, active ? 'sound/weapons/saberon.ogg' : 'sound/weapons/saberoff.ogg', 35, 1) //changed it from 50% volume to 35% because deafness
if(!supress_message_text)
- to_chat(user, "[src] [active ? "is now active":"can now be concealed"].")
+ balloon_alert(user, "[src] [active ? "is now active":"can now be concealed"]")
/obj/item/melee/transforming/proc/clumsy_transform_effect(mob/living/user)
if(clumsy_check && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50))
diff --git a/code/game/objects/items/miscellaneous.dm b/code/game/objects/items/miscellaneous.dm
index 0ca51831c1373..06f375375e33d 100644
--- a/code/game/objects/items/miscellaneous.dm
+++ b/code/game/objects/items/miscellaneous.dm
@@ -14,7 +14,7 @@
/obj/item/choice_beacon
name = "choice beacon"
- desc = "Hey, why are you viewing this?!! Please let Centcom know about this odd occurance."
+ desc = "Hey, why are you viewing this?!! Please let CentCom know about this odd occurrence."
icon = 'icons/obj/device.dmi'
icon_state = "gangtool-blue"
item_state = "radio"
@@ -61,7 +61,7 @@
msg = "You hear something crackle in your ears for a moment before a voice speaks. \"Please stand by for a message from Central Command. Message as follows: Item request received. Your package is inbound, please stand back from the landing site. Message ends.\""
to_chat(M, msg)
- new /obj/effect/DPtarget(get_turf(src), pod)
+ new /obj/effect/pod_landingzone(get_turf(src), pod)
/obj/item/choice_beacon/hero
name = "heroic beacon"
@@ -77,7 +77,6 @@
hero_item_list[initial(A.name)] = A
return hero_item_list
-
/obj/item/storage/box/hero
name = "Courageous Tomb Raider - 1940's."
@@ -111,7 +110,7 @@
/obj/item/storage/box/hero/ghostbuster/PopulateContents()
new /obj/item/clothing/glasses/welding/ghostbuster(src)
- new /obj/item/storage/belt/fannypack/bustin(src)
+ new /obj/item/storage/belt/fannypack/bustin(src)
new /obj/item/clothing/gloves/color/black(src)
new /obj/item/clothing/shoes/jackboots(src)
new /obj/item/clothing/under/color/khaki/buster(src)
@@ -193,7 +192,7 @@
maximum_size = 4
to_chat(user, "You upgrade the [src] with the [wand].")
playsound(user, 'sound/weapons/emitter2.ogg', 25, 1, -1)
-
+
/obj/item/clothing/head/that/bluespace/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
. = ..()
if(!proximity_flag)
@@ -306,3 +305,79 @@
item_state = "wand"
w_class = WEIGHT_CLASS_SMALL
var/used = FALSE
+
+/obj/item/choice_beacon/pet
+ name = "animal delivery beacon"
+ desc = "There are no faster ways, only more humane."
+ var/default_name = "Bacon"
+ var/mob_choice = /mob/living/simple_animal/pet/dog/corgi/exoticcorgi
+
+/obj/item/choice_beacon/pet/generate_options(mob/living/M)
+ var/input_name = stripped_input(M, "What would you like your new pet to be named?", "New Pet Name", default_name, MAX_NAME_LEN)
+ if(!input_name)
+ return
+ spawn_mob(M,input_name)
+ uses--
+ if(!uses)
+ qdel(src)
+ else
+ to_chat(M, "[uses] use[uses > 1 ? "s" : ""] remaining on the [src].")
+
+/obj/item/choice_beacon/pet/proc/spawn_mob(mob/living/M,name)
+ var/obj/structure/closet/supplypod/bluespacepod/pod = new()
+ var/mob/your_pet = new mob_choice(pod)
+ pod.explosionSize = list(0,0,0,0)
+ your_pet.name = name
+ your_pet.real_name = name
+ var/msg = "After making your selection, you notice a strange target on the ground. It might be best to step back!"
+ if(ishuman(M))
+ var/mob/living/carbon/human/H = M
+ if(istype(H.ears, /obj/item/radio/headset))
+ msg = "You hear something crackle in your ears for a moment before a voice speaks. \"Please stand by for a message from Central Command. Message as follows: One pet delivery straight from Central Command. Stand clear! Message ends.\""
+ to_chat(M, msg)
+ new /obj/effect/pod_landingzone(get_turf(src), pod)
+
+/obj/item/choice_beacon/pet/cat
+ name = "cat delivery beacon"
+ default_name = "Tom"
+ mob_choice = /mob/living/simple_animal/pet/cat
+
+/obj/item/choice_beacon/pet/mouse
+ name = "mouse delivery beacon"
+ default_name = "Jerry"
+ mob_choice = /mob/living/simple_animal/mouse
+
+/obj/item/choice_beacon/pet/corgi
+ name = "corgi delivery beacon"
+ default_name = "Tosha"
+ mob_choice = /mob/living/simple_animal/pet/dog/corgi
+
+/obj/item/choice_beacon/pet/hamster
+ name = "hamster delivery beacon"
+ default_name = "Doctor"
+ mob_choice = /mob/living/simple_animal/pet/hamster
+
+/obj/item/choice_beacon/pet/pug
+ name = "pug delivery beacon"
+ default_name = "Silvestro"
+ mob_choice = /mob/living/simple_animal/pet/dog/pug
+
+/obj/item/choice_beacon/pet/ems
+ name = "emotional support animal delivery beacon"
+ default_name = "Hugsie"
+ mob_choice = /mob/living/simple_animal/pet/cat/kitten
+
+/obj/item/choice_beacon/pet/pingu
+ name = "penguin delivery beacon"
+ default_name = "Pingu"
+ mob_choice = /mob/living/simple_animal/pet/penguin/baby
+
+/obj/item/choice_beacon/pet/clown
+ name = "living lube delivery beacon"
+ default_name = "Offensive"
+ mob_choice = /mob/living/simple_animal/hostile/retaliate/clown/lube
+
+/obj/item/choice_beacon/pet/goat
+ name = "goat delivery beacon"
+ default_name = "Billy"
+ mob_choice = /mob/living/simple_animal/hostile/retaliate/goat
diff --git a/code/game/objects/items/mop.dm b/code/game/objects/items/mop.dm
index 4b8da3a8cf477..145eec0563ef3 100644
--- a/code/game/objects/items/mop.dm
+++ b/code/game/objects/items/mop.dm
@@ -7,7 +7,7 @@
righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi'
force = 8
throwforce = 10
- block_upgrade_walk = 1
+ block_upgrade_walk = 1
block_level = 1
block_power = 20
throw_speed = 3
@@ -90,7 +90,8 @@
throw_range = 4
mopspeed = 8
var/refill_enabled = TRUE //Self-refill toggle for when a janitor decides to mop with something other than water.
- var/refill_rate = 1 //Rate per process() tick mop refills itself
+ /// Amount of reagent to refill per second
+ var/refill_rate = 0.5
var/refill_reagent = /datum/reagent/water //Determins what reagent to use for refilling, just in case someone wanted to make a HOLY MOP OF PURGING
/obj/item/mop/advanced/New()
@@ -106,10 +107,10 @@
to_chat(user, "You set the condenser switch to the '[refill_enabled ? "ON" : "OFF"]' position.")
playsound(user, 'sound/machines/click.ogg', 30, 1)
-/obj/item/mop/advanced/process()
-
- if(reagents.total_volume < mopcap)
- reagents.add_reagent(refill_reagent, refill_rate)
+/obj/item/mop/advanced/process(delta_time)
+ var/amadd = min(mopcap - reagents.total_volume, refill_rate * delta_time)
+ if(amadd > 0)
+ reagents.add_reagent(refill_reagent, amadd)
/obj/item/mop/advanced/examine(mob/user)
. = ..()
@@ -127,8 +128,8 @@
desc = "A mop with a sharpened handle. Careful!"
name = "sharpened mop"
force = 10
- throwforce = 15
+ throwforce = 18
throw_speed = 4
attack_verb = list("mopped", "stabbed", "shanked", "jousted")
sharpness = IS_SHARP
- embedding = list("embedded_impact_pain_multiplier" = 3)
\ No newline at end of file
+ embedding = list("armour_block" = 40)
diff --git a/code/game/objects/items/pet_carrier.dm b/code/game/objects/items/pet_carrier.dm
index 464721cec1ff4..37dff990631aa 100644
--- a/code/game/objects/items/pet_carrier.dm
+++ b/code/game/objects/items/pet_carrier.dm
@@ -30,6 +30,7 @@
return ..()
/obj/item/pet_carrier/Exited(atom/movable/occupant)
+ . = ..()
if(occupant in occupants && isliving(occupant))
var/mob/living/L = occupant
occupants -= occupant
diff --git a/code/game/objects/items/pinpointer.dm b/code/game/objects/items/pinpointer.dm
index ca86767d92c18..f20ddfeebc697 100644
--- a/code/game/objects/items/pinpointer.dm
+++ b/code/game/objects/items/pinpointer.dm
@@ -68,8 +68,8 @@
var/turf/here = get_turf(src)
var/turf/there = get_turf(target)
- if(here.z != there.z)
- if(here.z > there.z)
+ if(here.get_virtual_z_level() != there.get_virtual_z_level())
+ if(here.get_virtual_z_level() > there.get_virtual_z_level())
add_overlay("pinon_below[icon_suffix]")
else
add_overlay("pinon_above[icon_suffix]")
@@ -94,17 +94,27 @@
var/has_owner = FALSE
var/pinpointer_owner = null
+/obj/item/pinpointer/crew/examine(mob/user)
+ . = ..()
+ if(!active || !target)
+ return
+ . += "It is currently tracking [target]."
+
/obj/item/pinpointer/crew/proc/trackable(mob/living/carbon/human/H)
var/turf/here = get_turf(src)
- if((H.z == 0 || H.z == here.z || (is_station_level(here.z) && is_station_level(H.z))) && istype(H.w_uniform, /obj/item/clothing/under))
+ if((H.z == 0 || H.get_virtual_z_level() == here.get_virtual_z_level() || (is_station_level(here.z) && is_station_level(H.z))) && istype(H.w_uniform, /obj/item/clothing/under))
var/obj/item/clothing/under/U = H.w_uniform
+ //Suit sensors radio transmitter must not be jammed.
+ if(U.is_jammed())
+ return FALSE
+
// Suit sensors must be on maximum.
if(!U.has_sensor || (U.sensor_mode < SENSOR_COORDS && !ignore_suit_sensor_level))
return FALSE
var/turf/there = get_turf(H)
- return (H.z != 0 || (there && ((there.z == here.z) || (is_station_level(there.z) && is_station_level(here.z)))))
+ return (H.z != 0 || (there && ((there.get_virtual_z_level() == here.get_virtual_z_level()) || (is_station_level(there.z) && is_station_level(here.z)))))
return FALSE
diff --git a/code/game/objects/items/pitchfork.dm b/code/game/objects/items/pitchfork.dm
new file mode 100644
index 0000000000000..15509e3996bf3
--- /dev/null
+++ b/code/game/objects/items/pitchfork.dm
@@ -0,0 +1,88 @@
+/obj/item/pitchfork
+ icon_state = "pitchfork0"
+ lefthand_file = 'icons/mob/inhands/weapons/polearms_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/polearms_righthand.dmi'
+ name = "pitchfork"
+ desc = "A simple tool used for moving hay."
+ force = 7
+ throwforce = 15
+ block_level = 1
+ block_upgrade_walk = 1
+ w_class = WEIGHT_CLASS_BULKY
+ attack_verb = list("attacked", "impaled", "pierced")
+ hitsound = 'sound/weapons/bladeslice.ogg'
+ sharpness = IS_SHARP
+ max_integrity = 200
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30, "stamina" = 0)
+ resistance_flags = FIRE_PROOF
+
+/obj/item/pitchfork/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/two_handed, force_unwielded=7, force_wielded=15, block_power_wielded=25, icon_wielded="pitchfork1")
+
+/obj/item/pitchfork/update_icon()
+ icon_state = "pitchfork0"
+ ..()
+
+/obj/item/pitchfork/demonic
+ name = "demonic pitchfork"
+ desc = "A red pitchfork, it looks like the work of the devil."
+ force = 19
+ throwforce = 24
+
+/obj/item/pitchfork/demonic/Initialize()
+ . = ..()
+ set_light(3,6,LIGHT_COLOR_RED)
+
+/obj/item/pitchfork/demonic/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/two_handed, force_unwielded=19, force_wielded=25, block_power_wielded=25)
+
+/obj/item/pitchfork/demonic/greater
+ force = 24
+ throwforce = 50
+
+/obj/item/pitchfork/demonic/greater/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/two_handed, force_unwielded=24, force_wielded=34, block_power_wielded=25)
+
+/obj/item/pitchfork/demonic/ascended
+ force = 100
+ throwforce = 100
+
+/obj/item/pitchfork/demonic/ascended/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/two_handed, force_unwielded=100, force_wielded=500000) // Kills you DEAD
+
+/obj/item/pitchfork/suicide_act(mob/user)
+ user.visible_message("[user] impales [user.p_them()]self in [user.p_their()] abdomen with [src]! It looks like [user.p_theyre()] trying to commit suicide!")
+ return (BRUTELOSS)
+
+/obj/item/pitchfork/demonic/pickup(mob/living/user)
+ if(isliving(user) && user.mind && user.owns_soul() && !is_devil(user))
+ var/mob/living/U = user
+ U.visible_message("As [U] picks [src] up, [U]'s arms briefly catch fire.", \
+ "\"As you pick up [src] your arms ignite, reminding you of all your past sins.\"")
+ if(ishuman(U))
+ var/mob/living/carbon/human/H = U
+ H.apply_damage(rand(force/2, force), BURN, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
+ else
+ U.adjustFireLoss(rand(force/2,force))
+
+/obj/item/pitchfork/demonic/attack(mob/target, mob/living/carbon/human/user)
+ if(user.mind && user.owns_soul() && !is_devil(user))
+ to_chat(user, "[src] burns in your hands.")
+ user.apply_damage(rand(force/2, force), BURN, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
+ ..()
+
+/obj/item/pitchfork/demonic/ascended/afterattack(atom/target, mob/user, proximity)
+ . = ..()
+ if(!proximity || !ISWIELDED(src))
+ return
+ if(iswallturf(target))
+ var/turf/closed/wall/W = target
+ user.visible_message("[user] blasts \the [target] with \the [src]!")
+ playsound(target, 'sound/magic/disintegrate.ogg', 100, TRUE)
+ W.break_wall()
+ W.ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
+ return
diff --git a/code/game/objects/items/plushes.dm b/code/game/objects/items/plushes.dm
index b7fa326a3c161..7ba64dd56f8d7 100644
--- a/code/game/objects/items/plushes.dm
+++ b/code/game/objects/items/plushes.dm
@@ -36,6 +36,7 @@
/obj/item/toy/plush/Initialize()
. = ..()
AddComponent(/datum/component/squeak, squeak_override)
+ AddElement(/datum/element/bed_tuckable, 6, -5, 90)
//have we decided if Pinocchio goes in the blue or pink aisle yet?
if(gender == NEUTER)
@@ -159,19 +160,19 @@
//we are not catholic
if(young == TRUE || Kisser.young == TRUE)
- user.show_message("[src] plays tag with [Kisser].", 1,
- "They're happy.", 0)
+ user.show_message("[src] plays tag with [Kisser].", MSG_VISUAL,
+ "They're happy.", NONE)
Kisser.cheer_up()
cheer_up()
//never again
else if(Kisser in scorned)
//message, visible, alternate message, neither visible nor audible
- user.show_message("[src] rejects the advances of [Kisser]!", 1,
- "That didn't feel like it worked.", 0)
+ user.show_message("[src] rejects the advances of [Kisser]!", MSG_VISUAL,
+ "That didn't feel like it worked.", NONE)
else if(src in Kisser.scorned)
- user.show_message("[Kisser] realises who [src] is and turns away.", 1,
- "That didn't feel like it worked.", 0)
+ user.show_message("[Kisser] realises who [src] is and turns away.", MSG_VISUAL,
+ "That didn't feel like it worked.", NONE)
//first comes love
else if(Kisser.lover != src && Kisser.partner != src) //cannot be lovers or married
@@ -191,8 +192,8 @@
new_lover(Kisser)
Kisser.new_lover(src)
else
- user.show_message("[src] rejects the advances of [Kisser], maybe next time?", 1,
- "That didn't feel like it worked, this time.", 0)
+ user.show_message("[src] rejects the advances of [Kisser], maybe next time?", MSG_VISUAL,
+ "That didn't feel like it worked, this time.", NONE)
//then comes marriage
else if(Kisser.lover == src && Kisser.partner != src) //need to be lovers (assumes loving is a two way street) but not married (also assumes similar)
@@ -216,7 +217,7 @@
//then oh fuck something unexpected happened
else
- user.show_message("[Kisser] and [src] don't know what to do with one another.", 0)
+ user.show_message("[Kisser] and [src] don't know what to do with one another.", NONE)
/obj/item/toy/plush/proc/heartbreak(obj/item/toy/plush/Brutus)
if(lover != Brutus)
@@ -538,6 +539,14 @@
gender = FEMALE
squeak_override = list('sound/voice/moth/scream_moth.ogg'=1)
+/obj/item/toy/plush/rouny
+ name = "runner plushie"
+ desc = "A plushie depicting a xenomorph runner, made to commemorate the centenary of the Battle of LV-426. Much cuddlier than the real thing."
+ icon_state = "rouny"
+ icon_state = "rouny"
+ attack_verb = list("slashes", "bites", "charges")
+ squeak_override = list('sound/weapons/bite.ogg' = 1)
+
/obj/item/toy/plush/moth
name = "moth plushie"
desc = "An adorable mothperson plushy. It's a huggable bug!"
@@ -564,3 +573,27 @@
forceMove(random_open_spot)
user.dust(just_ash = FALSE, drop_items = TRUE)
return MANUAL_SUICIDE
+
+/////////////////
+//DONATOR ITEMS//
+/////////////////
+
+/obj/item/toy/plush/ian
+ name = "ian plushie"
+ desc = "Keep him by your side."
+ icon_state = "ianplush"
+
+/obj/item/toy/plush/lisa
+ name = "lisa plushie"
+ desc = "Keep her by your side."
+ icon_state = "lisaplush"
+
+/obj/item/toy/plush/renault
+ name = "renault plushie"
+ desc = "AWOOOO!"
+ icon_state = "renaultplush"
+
+/obj/item/toy/plush/opa
+ name = "metal upa"
+ desc = "You feel like this could have prevented World War 3 in a pararel timeline."
+ icon_state = "upaplush"
diff --git a/code/game/objects/items/pneumaticCannon.dm b/code/game/objects/items/pneumaticCannon.dm
index ee4b778a6777e..3b1b89a82085d 100644
--- a/code/game/objects/items/pneumaticCannon.dm
+++ b/code/game/objects/items/pneumaticCannon.dm
@@ -14,7 +14,7 @@
item_state = "bulldog"
lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi'
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 50)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 50, "stamina" = 0)
var/maxWeightClass = 20 //The max weight of items that can fit into the cannon
var/loadedWeightClass = 0 //The weight of items currently in the cannon
var/obj/item/tank/internals/tank = null //The gas tank that is drawn from to fire things
@@ -160,8 +160,7 @@
if(prob(10))
target = get_turf(user)
else
- var/list/possible_targets = range(3,src)
- target = pick(possible_targets)
+ target = pick(RANGE_TURFS(3,src))
discharge = 1
if(!discharge)
user.visible_message("[user] fires \the [src]!", \
@@ -316,7 +315,7 @@
throw_amount = 1
maxWeightClass = 4 //a single magspear or spear
spin_item = FALSE
- var/static/list/magspear_typecache = typecacheof(list(/obj/item/throwing_star/magspear, /obj/item/twohanded/spear, /obj/item/stack/rods/fifty, /obj/item/stack/rods, /obj/item/stack/rods/twentyfive, /obj/item/stack/rods/ten, /obj/item/katana, /obj/item/katana/cursed, /obj/item/toy/katana, /obj/item/twohanded/spear/explosive, /obj/item/twohanded/clockwork/brass_spear))
+ var/static/list/magspear_typecache = typecacheof(list(/obj/item/throwing_star/magspear, /obj/item/spear, /obj/item/stack/rods/fifty, /obj/item/stack/rods, /obj/item/stack/rods/twentyfive, /obj/item/stack/rods/ten, /obj/item/katana, /obj/item/katana/cursed, /obj/item/toy/katana, /obj/item/spear/explosive, /obj/item/clockwork/weapon/brass_spear))
/obj/item/pneumatic_cannon/speargun/Initialize()
. = ..()
diff --git a/code/game/objects/items/powerfist.dm b/code/game/objects/items/powerfist.dm
index 281e3085c3d2c..a7b5c858ba734 100644
--- a/code/game/objects/items/powerfist.dm
+++ b/code/game/objects/items/powerfist.dm
@@ -12,7 +12,7 @@
throwforce = 10
throw_range = 7
w_class = WEIGHT_CLASS_NORMAL
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 40)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 40, "stamina" = 0)
resistance_flags = FIRE_PROOF
var/click_delay = 1.5
var/fisto_setting = 1
diff --git a/code/game/objects/items/religion.dm b/code/game/objects/items/religion.dm
index 11f95f2ef56b4..1e90e3160f547 100644
--- a/code/game/objects/items/religion.dm
+++ b/code/game/objects/items/religion.dm
@@ -40,7 +40,7 @@
var/has_job_loyalties = LAZYLEN(job_loyalties)
var/has_role_loyalties = LAZYLEN(role_loyalties)
inspired += user //The user is always inspired, regardless of loyalties
- for(var/mob/living/carbon/human/H in range(4, get_turf(src)))
+ for(var/mob/living/carbon/human/H in viewers(4, get_turf(src)))
if(H.stat == DEAD || H == user)
continue
if(H.mind && (has_job_loyalties || has_role_loyalties))
@@ -120,7 +120,7 @@
category = CAT_MISC
/obj/item/banner/medical/special_inspiration(mob/living/carbon/human/H)
- H.adjustToxLoss(-15)
+ H.adjustToxLoss(-15, FALSE, TRUE)
H.setOxyLoss(0)
H.reagents.add_reagent(/datum/reagent/medicine/inaprovaline, 5)
@@ -254,7 +254,7 @@
w_class = WEIGHT_CLASS_BULKY
slowdown = 2.0 //gotta pretend we're balanced.
body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
- armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 40, "bomb" = 60, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 60)
+ armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 40, "bomb" = 60, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 60, "stamina" = 50)
/obj/item/clothing/suit/armor/plate/crusader/red
icon_state = "crusader-red"
@@ -268,7 +268,7 @@
icon_state = "crusader"
w_class = WEIGHT_CLASS_NORMAL
flags_inv = HIDEHAIR|HIDEEARS|HIDEFACE
- armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 40, "bomb" = 60, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 60)
+ armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 40, "bomb" = 60, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 60, "stamina" = 50)
/obj/item/clothing/head/helmet/plate/crusader/blue
icon_state = "crusader-blue"
@@ -282,7 +282,7 @@
desc = "A religious-looking hat."
alternate_worn_icon = 'icons/mob/large-worn-icons/64x64/head.dmi'
flags_1 = 0
- armor = list("melee" = 60, "bullet" = 60, "laser" = 60, "energy" = 50, "bomb" = 70, "bio" = 50, "rad" = 50, "fire" = 60, "acid" = 60) //religion protects you from disease and radiation, honk.
+ armor = list("melee" = 60, "bullet" = 60, "laser" = 60, "energy" = 50, "bomb" = 70, "bio" = 50, "rad" = 50, "fire" = 60, "acid" = 60, "stamina" = 60) //religion protects you from disease and radiation, honk.
worn_x_dimension = 64
worn_y_dimension = 64
@@ -342,7 +342,7 @@
desc = "Metal boots, they look heavy."
icon_state = "crusader"
w_class = WEIGHT_CLASS_NORMAL
- armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 40, "bomb" = 60, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 60) //does this even do anything on boots?
+ armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 40, "bomb" = 60, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 60, "stamina" = 30) //does this even do anything on boots?
clothing_flags = NOSLIP
cold_protection = FEET
min_cold_protection_temperature = SHOES_MIN_TEMP_PROTECT
diff --git a/code/game/objects/items/robot/robot_items.dm b/code/game/objects/items/robot/robot_items.dm
index d2da063de5d18..902cbac388106 100644
--- a/code/game/objects/items/robot/robot_items.dm
+++ b/code/game/objects/items/robot/robot_items.dm
@@ -325,7 +325,7 @@
user.visible_message("[user] blares out a near-deafening siren from its speakers!", \
"The siren pierces your hearing and confuses you!", \
"The siren pierces your hearing!")
- for(var/mob/living/carbon/M in get_hearers_in_view(9, user))
+ for(var/mob/living/carbon/M in hearers(9, user))
if(M.get_ear_protection() == FALSE)
M.confused += 6
audible_message("HUMAN HARM")
@@ -340,7 +340,7 @@
if(safety == FALSE)
user.audible_message("BZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZT")
- for(var/mob/living/carbon/C in get_hearers_in_view(9, user))
+ for(var/mob/living/carbon/C in hearers(9, user))
var/bang_effect = C.soundbang_act(2, 0, 0, 5)
switch(bang_effect)
if(1)
@@ -446,7 +446,7 @@
A.BB.nodamage = FALSE
A.BB.speed = 0.5
playsound(src.loc, 'sound/machines/click.ogg', 50, 1)
- A.fire_casing(target, user, params, 0, 0, null, 0, src)
+ A.fire_casing(target, user, params, 0, 0, null, 0, 1, src)
user.visible_message("[user] blasts a flying lollipop at [target]!")
check_amount()
@@ -462,7 +462,7 @@
A.BB.speed = 0.5
A.BB.color = rgb(rand(0, 255), rand(0, 255), rand(0, 255))
playsound(src.loc, 'sound/weapons/bulletflyby3.ogg', 50, 1)
- A.fire_casing(target, user, params, 0, 0, null, 0, src)
+ A.fire_casing(target, user, params, 0, 0, null, 0, 1, src)
user.visible_message("[user] shoots a high-velocity gumball at [target]!")
check_amount()
@@ -566,15 +566,17 @@
icon_state = "shield"
var/maxenergy = 1500
var/energy = 1500
- var/energy_recharge = 7.5
+ /// Recharging rate in energy per second
+ var/energy_recharge = 37.5
var/energy_recharge_cyborg_drain_coefficient = 0.4
var/cyborg_cell_critical_percentage = 0.05
var/mob/living/silicon/robot/host = null
var/datum/proximity_monitor/advanced/dampening_field
var/projectile_damage_coefficient = 0.5
- var/projectile_damage_tick_ecost_coefficient = 2 //Lasers get half their damage chopped off, drains 50 power/tick. Note that fields are processed 5 times per second.
+ /// Energy cost per tracked projectile damage amount per second
+ var/projectile_damage_tick_ecost_coefficient = 10
var/projectile_speed_coefficient = 1.5 //Higher the coefficient slower the projectile.
- var/projectile_tick_speed_ecost = 15
+ var/projectile_tick_speed_ecost = 75
var/list/obj/item/projectile/tracked
var/image/projectile_effect
var/field_radius = 3
@@ -656,38 +658,38 @@
deactivate_field()
. = ..()
-/obj/item/borg/projectile_dampen/process()
- process_recharge()
- process_usage()
+/obj/item/borg/projectile_dampen/process(delta_time)
+ process_recharge(delta_time)
+ process_usage(delta_time)
update_location()
/obj/item/borg/projectile_dampen/proc/update_location()
if(dampening_field)
dampening_field.HandleMove()
-/obj/item/borg/projectile_dampen/proc/process_usage()
+/obj/item/borg/projectile_dampen/proc/process_usage(delta_time)
var/usage = 0
for(var/I in tracked)
var/obj/item/projectile/P = I
if(!P.stun && P.nodamage) //No damage
continue
- usage += projectile_tick_speed_ecost
- usage += (tracked[I] * projectile_damage_tick_ecost_coefficient)
+ usage += projectile_tick_speed_ecost * delta_time
+ usage += (tracked[I] * projectile_damage_tick_ecost_coefficient * delta_time)
energy = CLAMP(energy - usage, 0, maxenergy)
if(energy <= 0)
deactivate_field()
visible_message("[src] blinks \"ENERGY DEPLETED\".")
-/obj/item/borg/projectile_dampen/proc/process_recharge()
+/obj/item/borg/projectile_dampen/proc/process_recharge(delta_time)
if(!istype(host))
if(iscyborg(host.loc))
host = host.loc
else
- energy = CLAMP(energy + energy_recharge, 0, maxenergy)
+ energy = CLAMP(energy + energy_recharge * delta_time, 0, maxenergy)
return
if(host.cell && (host.cell.charge >= (host.cell.maxcharge * cyborg_cell_critical_percentage)) && (energy < maxenergy))
- host.cell.use(energy_recharge*energy_recharge_cyborg_drain_coefficient)
- energy += energy_recharge
+ host.cell.use(energy_recharge * delta_time * energy_recharge_cyborg_drain_coefficient)
+ energy += energy_recharge * delta_time
/obj/item/borg/projectile_dampen/proc/dampen_projectile(obj/item/projectile/P, track_projectile = TRUE)
if(tracked[P])
@@ -784,6 +786,8 @@
///If we're safely deconstructed, we put the item neatly onto the ground, rather than deleting it.
/obj/item/borg/apparatus/proc/safedecon()
+ SIGNAL_HANDLER
+
if(stored)
stored.forceMove(get_turf(src))
stored = null
@@ -808,11 +812,14 @@
/obj/item/borg/apparatus/attack_self(mob/living/silicon/robot/user)
if(!stored)
return ..()
- if(user.client?.keys_held["Alt"])
- stored.forceMove(get_turf(user))
- return
stored.attack_self(user)
+//Alt click drops stored item
+/obj/item/borg/apparatus/AltClick(mob/living/silicon/robot/user)
+ if(!stored)
+ return ..()
+ stored.forceMove(get_turf(user))
+
/obj/item/borg/apparatus/pre_attack(atom/A, mob/living/user, params)
if(!stored)
var/itemcheck = FALSE
@@ -844,7 +851,7 @@
/obj/item/borg/apparatus/beaker
name = "beaker storage apparatus"
- desc = "A special apparatus for carrying beakers without spilling the contents. Alt-Z or right-click to drop the beaker."
+ desc = "A special apparatus for carrying beakers without spilling the contents."
icon_state = "borg_beaker_apparatus"
storable = list(/obj/item/reagent_containers/glass/beaker,
/obj/item/reagent_containers/glass/bottle)
@@ -859,7 +866,7 @@
if(stored)
var/obj/item/reagent_containers/C = stored
C.SplashReagents(get_turf(src))
- qdel(stored)
+ QDEL_NULL(stored)
. = ..()
/obj/item/borg/apparatus/beaker/examine()
@@ -872,6 +879,7 @@
. += "[R.volume] units of [R.name]"
else
. += "Nothing."
+ . += "Alt-click will drop the currently stored [stored]."
/obj/item/borg/apparatus/beaker/update_icon()
cut_overlays()
@@ -909,7 +917,7 @@
/obj/item/borg/apparatus/circuit
name = "circuit manipulation apparatus"
- desc = "A special apparatus for carrying and manipulating circuit boards. Alt-Z or right-click to drop the stored object."
+ desc = "A special apparatus for carrying and manipulating circuit boards."
icon_state = "borg_hardware_apparatus"
storable = list(/obj/item/circuitboard,
/obj/item/electronics)
@@ -941,19 +949,20 @@
. = ..()
if(stored)
. += "The apparatus currently has [stored] secured."
+ . += "Alt-click will drop the currently stored [stored]."
/obj/item/borg/apparatus/circuit/pre_attack(atom/A, mob/living/user, params)
. = ..()
if(istype(A, /obj/item/aiModule) && !stored) //If an admin wants a borg to upload laws, who am I to stop them? Otherwise, we can hint that it fails
to_chat(user, "This circuit board doesn't seem to have standard robot apparatus pin holes. You're unable to pick it up.")
-
+
////////////////////
//versatile service holder//
////////////////////
/obj/item/borg/apparatus/beaker/service
name = "versatile service grasper"
- desc = "Specially designed for carrying glasses, food and seeds. Alt-Z or right-click to drop the stored object."
+ desc = "Specially designed for carrying glasses, food and seeds."
storable = list(/obj/item/reagent_containers/food,
/obj/item/seeds,
/obj/item/storage/fancy/donut_box,
@@ -969,3 +978,4 @@
. = ..()
if(stored)
. += "You are currently holding [stored]."
+ . += "Alt-click will drop the currently stored [stored]."
\ No newline at end of file
diff --git a/code/game/objects/items/robot/robot_parts.dm b/code/game/objects/items/robot/robot_parts.dm
index 56f3a4f0c88b5..2550ddbae6187 100644
--- a/code/game/objects/items/robot/robot_parts.dm
+++ b/code/game/objects/items/robot/robot_parts.dm
@@ -293,7 +293,7 @@
if(forced_ai)
O.connected_ai = forced_ai
if(!lawsync)
- O.lawupdate = 0
+ O.lawupdate = FALSE
if(M.laws.id == DEFAULT_AI_LAWID)
O.make_laws()
@@ -333,6 +333,9 @@
else if(istype(W, /obj/item/borg/upgrade/ai))
var/obj/item/borg/upgrade/ai/M = W
if(check_completion())
+ if(!chest.cell)
+ to_chat(user, "The endoskeleton still needs a power cell!")
+ return
if(!isturf(loc))
to_chat(user, "You cannot install[M], the frame has to be standing on the ground to be perfectly precise!")
return
diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm
index 40665c09491e3..1b7aed3c1d549 100644
--- a/code/game/objects/items/robot/robot_upgrades.dm
+++ b/code/game/objects/items/robot/robot_upgrades.dm
@@ -9,7 +9,9 @@
var/locked = FALSE
var/installed = 0
var/require_module = 0
- var/module_type = null
+ var/list/module_type = null
+ /// Bitflags listing module compatibility. Used in the exosuit fabricator for creating sub-categories.
+ var/list/module_flags = NONE
// if true, is not stored in the robot to be ejected
// if module is reset
var/one_use = FALSE
@@ -18,7 +20,7 @@
if(R.stat == DEAD)
to_chat(user, "[src] will not function on a deceased cyborg.")
return FALSE
- if(module_type && !istype(R.module, module_type))
+ if(module_type && !is_type_in_list(R.module, module_type))
to_chat(R, "Upgrade mounting error! No suitable hardpoint detected!")
to_chat(user, "There's no mounting point for the module!")
return FALSE
@@ -94,7 +96,8 @@
desc = "Used to cool a mounted disabler, increasing the potential current in it and thus its recharge rate."
icon_state = "cyborg_upgrade3"
require_module = 1
- module_type = /obj/item/robot_module/security
+ module_type = list(/obj/item/robot_module/security)
+ module_flags = BORG_MODULE_SECURITY
/obj/item/borg/upgrade/disablercooler/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -142,7 +145,8 @@
desc = "A diamond drill replacement for the mining module's standard drill."
icon_state = "cyborg_upgrade3"
require_module = 1
- module_type = /obj/item/robot_module/miner
+ module_type = list(/obj/item/robot_module/miner)
+ module_flags = BORG_MODULE_MINER
/obj/item/borg/upgrade/ddrill/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -174,7 +178,8 @@
desc = "A satchel of holding replacement for mining cyborg's ore satchel module."
icon_state = "cyborg_upgrade3"
require_module = 1
- module_type = /obj/item/robot_module/miner
+ module_type = list(/obj/item/robot_module/miner)
+ module_flags = BORG_MODULE_MINER
/obj/item/borg/upgrade/soh/action(mob/living/silicon/robot/R)
. = ..()
@@ -201,7 +206,7 @@
desc = "An upgrade to the mining module granting a self-recharging plasma cutter."
icon_state = "cyborg_upgrade3"
require_module = 1
- module_type = /obj/item/robot_module/miner
+ module_type = list(/obj/item/robot_module/miner)
/obj/item/borg/upgrade/cutter/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -221,7 +226,8 @@
desc = "A trash bag of holding replacement for the janiborg's standard trash bag."
icon_state = "cyborg_upgrade3"
require_module = 1
- module_type = /obj/item/robot_module/janitor
+ module_type = list(/obj/item/robot_module/janitor)
+ module_flags = BORG_MODULE_JANITOR
/obj/item/borg/upgrade/tboh/action(mob/living/silicon/robot/R)
. = ..()
@@ -248,7 +254,8 @@
desc = "An advanced mop replacement for the janiborg's standard mop."
icon_state = "cyborg_upgrade3"
require_module = 1
- module_type = /obj/item/robot_module/janitor
+ module_type = list(/obj/item/robot_module/janitor)
+ module_flags = BORG_MODULE_JANITOR
/obj/item/borg/upgrade/amop/action(mob/living/silicon/robot/R)
. = ..()
@@ -297,7 +304,8 @@
icon_state = "ash_plating"
resistance_flags = LAVA_PROOF | FIRE_PROOF
require_module = 1
- module_type = /obj/item/robot_module/miner
+ module_type = list(/obj/item/robot_module/miner)
+ module_flags = BORG_MODULE_MINER
/obj/item/borg/upgrade/lavaproof/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -315,7 +323,10 @@
icon_state = "cyborg_upgrade5"
require_module = 1
var/repair_amount = -1
- var/repair_tick = 1
+ /// world.time of next repair
+ var/next_repair = 0
+ /// Minimum time between repairs in seconds
+ var/repair_cooldown = 4
var/msg_cooldown = 0
var/on = FALSE
var/powercost = 10
@@ -338,8 +349,8 @@
/obj/item/borg/upgrade/selfrepair/deactivate(mob/living/silicon/robot/R, user = usr)
. = ..()
if (.)
- toggle_action.Remove(cyborg)
- QDEL_NULL(toggle_action)
+ if(toggle_action)
+ QDEL_NULL(toggle_action)
cyborg = null
deactivate_sr()
@@ -349,8 +360,8 @@
/obj/item/borg/upgrade/selfrepair/proc/check_dropped()
if(loc != cyborg)
- toggle_action.Remove(cyborg)
- QDEL_NULL(toggle_action)
+ if(toggle_action)
+ QDEL_NULL(toggle_action)
cyborg = null
deactivate_sr()
@@ -379,8 +390,7 @@
update_icon()
/obj/item/borg/upgrade/selfrepair/process()
- if(!repair_tick)
- repair_tick = 1
+ if(world.time < next_repair)
return
if(cyborg && (cyborg.stat != DEAD) && on)
@@ -407,16 +417,16 @@
cyborg.cell.use(powercost)
else
cyborg.cell.use(5)
- repair_tick = 0
+ next_repair = world.time + repair_cooldown * 10 // Multiply by 10 since world.time is in deciseconds
- if((world.time - 2000) > msg_cooldown )
+ if(!TIMER_COOLDOWN_CHECK(src, COOLDOWN_BORG_SELF_REPAIR))
+ TIMER_COOLDOWN_START(src, COOLDOWN_BORG_SELF_REPAIR, 200 SECONDS)
var/msgmode = "standby"
if(cyborg.health < 0)
msgmode = "critical"
else if(cyborg.health < cyborg.maxHealth)
msgmode = "normal"
to_chat(cyborg, "Self-repair is active in [msgmode] mode.")
- msg_cooldown = world.time
else
deactivate_sr()
@@ -426,7 +436,8 @@
to produce more advanced and complex medical reagents."
icon_state = "cyborg_upgrade3"
require_module = 1
- module_type = /obj/item/robot_module/medical
+ module_type = list(/obj/item/robot_module/medical)
+ module_flags = BORG_MODULE_MEDICAL
var/list/additional_reagents = list()
/obj/item/borg/upgrade/hypospray/action(mob/living/silicon/robot/R, user = usr)
@@ -482,21 +493,23 @@
defibrillator, for on the scene revival."
icon_state = "cyborg_upgrade3"
require_module = 1
- module_type = /obj/item/robot_module/medical
+ module_type = list(/obj/item/robot_module/medical)
+ module_flags = BORG_MODULE_MEDICAL
/obj/item/borg/upgrade/defib/action(mob/living/silicon/robot/R, user = usr)
. = ..()
if(.)
- var/obj/item/twohanded/shockpaddles/cyborg/S = new(R.module)
+ var/obj/item/shockpaddles/cyborg/S = new(R.module)
R.module.basic_modules += S
R.module.add_module(S, FALSE, TRUE)
/obj/item/borg/upgrade/defib/deactivate(mob/living/silicon/robot/R, user = usr)
. = ..()
if (.)
- var/obj/item/twohanded/shockpaddles/cyborg/S = locate() in R.module
+ var/obj/item/shockpaddles/cyborg/S = locate() in R.module
R.module.remove_module(S, TRUE)
+
/obj/item/borg/upgrade/processor
name = "medical cyborg surgical processor"
desc = "An upgrade to the Medical module, installing a processor \
@@ -504,7 +517,8 @@
out procedures"
icon_state = "cyborg_upgrade3"
require_module = 1
- module_type = /obj/item/robot_module/medical
+ module_type = list(/obj/item/robot_module/medical, /obj/item/robot_module/syndicate_medical)
+ module_flags = BORG_MODULE_MEDICAL
/obj/item/borg/upgrade/processor/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -558,17 +572,17 @@
R.notransform = TRUE
var/prev_lockcharge = R.lockcharge
- R.SetLockdown(1)
+ R.SetLockdown(TRUE)
R.anchored = TRUE
var/datum/effect_system/smoke_spread/smoke = new
- smoke.set_up(1, R.loc)
+ smoke.set_up(TRUE, R.loc)
smoke.start()
sleep(2)
for(var/i in 1 to 4)
playsound(R, pick('sound/items/drill_use.ogg', 'sound/items/jaws_cut.ogg', 'sound/items/jaws_pry.ogg', 'sound/items/welder.ogg', 'sound/items/ratchet.ogg'), 80, 1, -1)
sleep(12)
if(!prev_lockcharge)
- R.SetLockdown(0)
+ R.SetLockdown(FALSE)
R.anchored = FALSE
R.notransform = FALSE
R.resize = 2
@@ -589,7 +603,8 @@
icon = 'icons/obj/storage.dmi'
icon_state = "borgrped"
require_module = TRUE
- module_type = /obj/item/robot_module/engineering
+ module_type = list(/obj/item/robot_module/engineering, /obj/item/robot_module/saboteur)
+ module_flags = BORG_MODULE_ENGINEERING
/obj/item/borg/upgrade/rped/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -617,7 +632,8 @@
icon = 'icons/obj/device.dmi'
icon_state = "pinpointer_crew"
require_module = TRUE
- module_type = /obj/item/robot_module/medical
+ module_type = list(/obj/item/robot_module/medical, /obj/item/robot_module/syndicate_medical)
+ module_flags = BORG_MODULE_MEDICAL
var/datum/action/crew_monitor
/obj/item/borg/upgrade/pinpointer/action(mob/living/silicon/robot/R, user = usr)
@@ -670,12 +686,26 @@
icon_state = "cyborg_upgrade3"
new_module = /obj/item/robot_module/clown
+/obj/item/borg/upgrade/transform/security
+ name = "borg module picker (Security)"
+ desc = "Allows you to turn a cyborg into a hunter, HALT!"
+ icon_state = "cyborg_upgrade3"
+ new_module = /obj/item/robot_module/security
+ module_flags = BORG_MODULE_SECURITY
+
+/obj/item/borg/upgrade/transform/security/action(mob/living/silicon/robot/R, user = usr)
+ if(CONFIG_GET(flag/disable_secborg))
+ to_chat(user, "Nanotrasen policy disallows the use of weapons of mass destruction.")
+ return FALSE
+ return ..()
+
/obj/item/borg/upgrade/circuit_app
name = "circuit manipulation apparatus"
desc = "An engineering cyborg upgrade allowing for manipulation of circuit boards."
icon_state = "cyborg_upgrade3"
require_module = TRUE
- module_type = /obj/item/robot_module/engineering
+ module_type = list(/obj/item/robot_module/engineering, /obj/item/robot_module/saboteur)
+ module_flags = BORG_MODULE_ENGINEERING
/obj/item/borg/upgrade/circuit_app/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -701,7 +731,8 @@
desc = "A supplementary beaker storage apparatus for medical cyborgs."
icon_state = "cyborg_upgrade3"
require_module = TRUE
- module_type = /obj/item/robot_module/medical
+ module_type = list(/obj/item/robot_module/medical)
+ module_flags = BORG_MODULE_MEDICAL
/obj/item/borg/upgrade/beaker_app/action(mob/living/silicon/robot/R, user = usr)
. = ..()
@@ -721,3 +752,102 @@
var/obj/item/borg/apparatus/beaker/extra/E = locate() in R.module.modules
if (E)
R.module.remove_module(E, TRUE)
+
+
+/obj/item/borg/upgrade/speciality
+ name = "Speciality Module"
+ icon_state = "cyborg_upgrade3"
+ require_module = TRUE
+ module_type = list(/obj/item/robot_module/butler)
+ var/obj/item/hat
+ var/addmodules = list()
+ var/list/additional_reagents = list()
+ module_flags = BORG_MODULE_SPECIALITY
+
+/obj/item/borg/upgrade/speciality/action(mob/living/silicon/robot/R, user = usr)
+ . = ..()
+ if(.)
+ for(var/obj/item/borg/upgrade/SPEC in R.upgrades)
+ if (istype(SPEC,/obj/item/borg/upgrade/speciality) && SPEC != src)
+ SPEC.deactivate(R)
+ R.upgrades -= SPEC
+ qdel(SPEC)
+
+
+ for(var/module in src.addmodules)
+ var/obj/item/nmodule = locate(module) in R
+ if (!nmodule)
+ nmodule = new module(R.module)
+ R.module.basic_modules += nmodule
+ R.module.add_module(nmodule, FALSE, TRUE)
+
+ for(var/obj/item/reagent_containers/borghypo/borgshaker/H in R.module.modules)
+ for(var/re in additional_reagents)
+ H.add_reagent(re)
+
+ if(hat && R.hat_offset != INFINITY && !R.hat)
+ var/obj/item/equipt = new hat(src)
+ if (equipt )
+ R.place_on_head(equipt)
+
+/obj/item/borg/upgrade/speciality/deactivate(mob/living/silicon/robot/R, user = usr)
+ . = ..()
+ if (.)
+ //Remove existing modules indiscriminately
+ for(var/module in src.addmodules)
+ var/dmod = locate(module) in R.module.modules
+ if (dmod)
+ R.module.remove_module(dmod, TRUE)
+ for(var/obj/item/reagent_containers/borghypo/borgshaker/H in R.module.modules)
+ for(var/re in additional_reagents)
+ H.del_reagent(re)
+
+/obj/item/borg/upgrade/speciality/kitchen
+ name = "Cook Speciality"
+ desc = "A service cyborg upgrade allowing for basic food handling."
+ hat = /obj/item/clothing/head/chefhat
+ addmodules = list (
+ /obj/item/kitchen/knife,
+ /obj/item/kitchen/rollingpin,
+ )
+ additional_reagents = list(
+ /datum/reagent/consumable/enzyme,
+ /datum/reagent/consumable/sugar,
+ /datum/reagent/consumable/flour,
+ /datum/reagent/water,
+ )
+
+/obj/item/borg/upgrade/speciality/botany
+ name = "Botany Speciality"
+ desc = "A service cyborg upgrade allowing for plant tending and manipulation."
+ hat = /obj/item/clothing/head/rice_hat
+ addmodules = list (
+ /obj/item/storage/bag/plants/portaseeder,
+ /obj/item/cultivator,
+ /obj/item/plant_analyzer,
+ /obj/item/shovel/spade,
+ )
+ additional_reagents = list(
+ /datum/reagent/water,
+ )
+
+
+/obj/item/borg/upgrade/speciality/casino
+ name = "Gambler Speciality"
+ desc = "It's not crew harm if they do it themselves!"
+ hat = /obj/item/clothing/head/rabbitears
+ addmodules = list (
+ /obj/item/gobbler,
+ /obj/item/storage/pill_bottle/dice_cup/cyborg,
+ /obj/item/toy/cards/deck/cyborg,
+ )
+
+/obj/item/borg/upgrade/speciality/party
+ name = "Party Speciality"
+ desc = "The night's still young..."
+ hat = /obj/item/clothing/head/beanie/rasta
+ addmodules = list (
+ /obj/item/stack/tile/light/cyborg,
+ /obj/item/crowbar/cyborg,
+ /obj/item/dance_trance,
+ )
diff --git a/code/game/objects/items/sharpener.dm b/code/game/objects/items/sharpener.dm
index b90a5b155a69e..daed28fd27ce5 100644
--- a/code/game/objects/items/sharpener.dm
+++ b/code/game/objects/items/sharpener.dm
@@ -18,32 +18,29 @@
if(I.force >= max || I.throwforce >= max)//no esword sharpening
to_chat(user, "[I] is much too powerful to sharpen further!")
return
- if(requires_sharpness && !I.sharpness)
+ if(requires_sharpness && !I.is_sharp())
to_chat(user, "You can only sharpen items that are already sharp, such as knives!")
return
if(istype(I, /obj/item/melee/transforming/energy))
to_chat(user, "You don't think \the [I] will be the thing getting modified if you use it on \the [src]!")
return
- if(istype(I, /obj/item/twohanded))//some twohanded items should still be sharpenable, but handle force differently. therefore i need this stuff
- var/obj/item/twohanded/TH = I
- if(TH.force_wielded >= max)
- to_chat(user, "[TH] is much too powerful to sharpen further!")
- return
- if(TH.wielded)
- to_chat(user, "[TH] must be unwielded before it can be sharpened!")
- return
- if(TH.force_wielded > initial(TH.force_wielded))
- to_chat(user, "[TH] has already been refined before. It cannot be sharpened further!")
- return
- TH.force_wielded = CLAMP(TH.force_wielded + increment, 0, max)//wieldforce is increased since normal force wont stay
- if(I.force > initial(I.force))
+
+ var/signal_out = SEND_SIGNAL(I, COMSIG_ITEM_SHARPEN_ACT, increment, max)
+ if(signal_out & COMPONENT_BLOCK_SHARPEN_MAXED)
+ to_chat(user, "[I] is much too powerful to sharpen further!")
+ return
+ if(signal_out & COMPONENT_BLOCK_SHARPEN_BLOCKED)
+ to_chat(user, "[I] is not able to be sharpened right now!")
+ return
+ if((signal_out & COMPONENT_BLOCK_SHARPEN_ALREADY) || (I.force > initial(I.force) && !signal_out))
to_chat(user, "[I] has already been refined before. It cannot be sharpened further!")
return
+ if(!(signal_out & COMPONENT_BLOCK_SHARPEN_APPLIED))
+ I.force = clamp(I.force + increment, 0, max)
user.visible_message("[user] sharpens [I] with [src]!", "You sharpen [I], making it much more deadly than before.")
playsound(src, 'sound/items/unsheath.ogg', 25, 1)
I.sharpness = IS_SHARP_ACCURATE
- I.force = CLAMP(I.force + increment, 0, max)
- I.throwforce = CLAMP(I.throwforce + increment, 0, max)
+ I.throwforce = clamp(I.throwforce + increment, 0, max)
I.name = "[prefix] [I.name]"
name = "worn out [name]"
desc = "[desc] At least, it used to."
diff --git a/code/game/objects/items/shields.dm b/code/game/objects/items/shields.dm
index ed5f1736bb989..ec9cb8f721ee8 100644
--- a/code/game/objects/items/shields.dm
+++ b/code/game/objects/items/shields.dm
@@ -3,7 +3,7 @@
icon = 'icons/obj/shields.dmi'
block_level = 1
block_upgrade_walk = 1
- block_flags = BLOCKING_PROJECTILE
+ block_flags = null
block_power = 50
max_integrity = 75
var/transparent = FALSE // makes beam projectiles pass through the shield
@@ -21,7 +21,7 @@
var/obj/item/projectile/P = hitby
if(P.damage_type != STAMINA)// disablers dont do shit to shields
attackforce = (P.damage / 2)
- if(isitem(hitby))
+ else if(isitem(hitby))
var/obj/item/I = hitby
attackforce = damage
if(!I.damtype == BRUTE)
@@ -255,6 +255,7 @@
throw_speed = 3
max_integrity = 50
block_sound = 'sound/weapons/egloves.ogg'
+ block_flags = BLOCKING_PROJECTILE
var/base_icon_state = "eshield" // [base_icon_state]1 for expanded, [base_icon_state]0 for contracted
var/on_force = 10
var/on_throwforce = 8
diff --git a/code/game/objects/items/shrapnel.dm b/code/game/objects/items/shrapnel.dm
new file mode 100644
index 0000000000000..11ae13ed473b4
--- /dev/null
+++ b/code/game/objects/items/shrapnel.dm
@@ -0,0 +1,68 @@
+/obj/item/shrapnel // frag grenades
+ name = "shrapnel shard"
+ embedding = list(embed_chance=70, ignore_throwspeed_threshold=TRUE, fall_chance=4)
+ custom_materials = list(/datum/material/iron=50)
+ armour_penetration = -20
+ icon = 'icons/obj/shards.dmi'
+ icon_state = "large"
+ w_class = WEIGHT_CLASS_TINY
+ item_flags = DROPDEL
+
+/obj/item/shrapnel/stingball // stingbang grenades
+ name = "stingball"
+ embedding = list(embed_chance=90, fall_chance=3, jostle_chance=7, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.7, pain_mult=5, jostle_pain_mult=6, rip_time=15)
+ icon_state = "tiny"
+
+/obj/item/shrapnel/bullet // bullets
+ name = "bullet"
+ icon = 'icons/obj/ammo.dmi'
+ icon_state = "s-casing"
+ item_flags = NONE
+
+/obj/item/shrapnel/bullet/c38 // .38 round
+ name = "\improper .38 bullet"
+
+/obj/item/shrapnel/bullet/c38/dumdum // .38 DumDum round
+ name = "\improper .38 DumDum bullet"
+ embedding = list(embed_chance=70, fall_chance=7, jostle_chance=7, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=5, jostle_pain_mult=6, rip_time=10)
+
+/obj/item/projectile/bullet/shrapnel
+ name = "flying shrapnel shard"
+ damage = 9
+ range = 10
+ armour_penetration = -30
+ dismemberment = 5
+ ricochets_max = 2
+ ricochet_chance = 40
+ shrapnel_type = /obj/item/shrapnel
+ ricochet_incidence_leeway = 60
+ hit_stunned_targets = TRUE
+
+/obj/item/projectile/bullet/shrapnel/mega
+ name = "flying shrapnel hunk"
+ range = 25
+ dismemberment = 10
+ ricochets_max = 4
+ ricochet_chance = 90
+ ricochet_decay_chance = 0.9
+
+/obj/item/projectile/bullet/pellet/stingball
+ name = "stingball pellet"
+ damage = 3
+ stamina = 8
+ ricochets_max = 4
+ ricochet_chance = 66
+ ricochet_decay_chance = 1
+ ricochet_decay_damage = 0.9
+ ricochet_auto_aim_angle = 10
+ ricochet_auto_aim_range = 2
+ ricochet_incidence_leeway = 0
+ shrapnel_type = /obj/item/shrapnel/stingball
+
+/obj/item/projectile/bullet/pellet/stingball/mega
+ name = "megastingball pellet"
+ ricochets_max = 6
+ ricochet_chance = 110
+
+/obj/item/projectile/bullet/pellet/stingball/on_ricochet(atom/A)
+ hit_stunned_targets = TRUE // ducking will save you from the first wave, but not the rebounds
diff --git a/code/game/objects/items/singularityhammer.dm b/code/game/objects/items/singularityhammer.dm
index f1a6e02241db8..500b462015d76 100644
--- a/code/game/objects/items/singularityhammer.dm
+++ b/code/game/objects/items/singularityhammer.dm
@@ -1,7 +1,7 @@
-/obj/item/twohanded/singularityhammer
+/obj/item/singularityhammer
name = "singularity hammer"
desc = "The pinnacle of close combat technology, the hammer harnesses the power of a miniaturized singularity to deal crushing blows."
- icon_state = "mjollnir0"
+ icon_state = "singularity_hammer0"
lefthand_file = 'icons/mob/inhands/weapons/hammers_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/hammers_righthand.dmi'
flags_1 = CONDUCT_1
@@ -9,60 +9,66 @@
force = 5
attack_weight = 3
block_upgrade_walk = 1
- force_unwielded = 5
- force_wielded = 20
throwforce = 15
throw_range = 1
w_class = WEIGHT_CLASS_HUGE
- var/charged = 5
- armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100)
+ armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100, "stamina" = 0)
resistance_flags = FIRE_PROOF | ACID_PROOF
force_string = "LORD SINGULOTH HIMSELF"
+ var/charged = 5
-/obj/item/twohanded/singularityhammer/New()
- ..()
+/obj/item/singularityhammer/Initialize()
+ . = ..()
START_PROCESSING(SSobj, src)
-/obj/item/twohanded/singularityhammer/Destroy()
+/obj/item/singularityhammer/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/two_handed, force_multiplier=4, icon_wielded="singularity_hammer1")
+
+/obj/item/singularityhammer/Destroy()
+ STOP_PROCESSING(SSobj, src)
+ return ..()
+
+/obj/item/singularityhammer/update_icon_state()
+ icon_state = "mjollnir0"
+ ..()
+
+/obj/item/singularityhammer/Destroy()
STOP_PROCESSING(SSobj, src)
return ..()
-/obj/item/twohanded/singularityhammer/process()
+/obj/item/singularityhammer/process()
if(charged < 5)
charged++
- return
-/obj/item/twohanded/singularityhammer/update_icon() //Currently only here to fuck with the on-mob icons.
- icon_state = "mjollnir[wielded]"
- return
+/obj/item/singularityhammer/update_icon() //Currently only here to fuck with the on-mob icons.
+ icon_state = "singularity_hammer0"
-/obj/item/twohanded/singularityhammer/proc/vortex(turf/pull, mob/wielder)
- for(var/atom/X in orange(5,pull))
- if(ismovableatom(X))
- var/atom/movable/A = X
- if(A == wielder)
- continue
- if(A && !A.anchored && !ishuman(X))
- step_towards(A,pull)
- step_towards(A,pull)
- step_towards(A,pull)
- else if(ishuman(X))
- var/mob/living/carbon/human/H = X
- if(istype(H.shoes, /obj/item/clothing/shoes/magboots))
- var/obj/item/clothing/shoes/magboots/M = H.shoes
- if(M.magpulse)
- continue
- H.apply_effect(20, EFFECT_PARALYZE, 0)
- step_towards(H,pull)
- step_towards(H,pull)
- step_towards(H,pull)
+/obj/item/singularityhammer/proc/vortex(turf/pull, mob/wielder)
+ for(var/atom/movable/A as mob|obj in orange(5,pull))
+ if(A == wielder)
+ continue
+ if(A && !A.anchored && !ishuman(A))
+ step_towards(A,pull)
+ step_towards(A,pull)
+ step_towards(A,pull)
+ else if(ishuman(A))
+ var/mob/living/carbon/human/H = A
+ if(istype(H.shoes, /obj/item/clothing/shoes/magboots))
+ var/obj/item/clothing/shoes/magboots/M = H.shoes
+ if(M.magpulse)
+ continue
+ H.apply_effect(20, EFFECT_PARALYZE, 0)
+ step_towards(H,pull)
+ step_towards(H,pull)
+ step_towards(H,pull)
return
-/obj/item/twohanded/singularityhammer/afterattack(atom/A as mob|obj|turf|area, mob/user, proximity)
+/obj/item/singularityhammer/afterattack(atom/A as mob|obj|turf|area, mob/user, proximity)
. = ..()
if(!proximity)
return
- if(wielded)
+ if(ISWIELDED(src))
if(charged == 5)
charged = 0
if(istype(A, /mob/living/))
@@ -72,7 +78,7 @@
var/turf/target = get_turf(A)
vortex(target,user)
-/obj/item/twohanded/mjollnir
+/obj/item/mjollnir
name = "Mjolnir"
desc = "A weapon worthy of a god, able to strike with the force of a lightning bolt. It crackles with barely contained energy."
icon_state = "mjollnir0"
@@ -81,15 +87,23 @@
flags_1 = CONDUCT_1
slot_flags = ITEM_SLOT_BACK
force = 5
- force_unwielded = 5
- force_wielded = 25
throwforce = 30
throw_range = 7
block_upgrade_walk = 1
attack_weight = 3
w_class = WEIGHT_CLASS_HUGE
-/obj/item/twohanded/mjollnir/proc/shock(mob/living/target)
+/obj/item/mjollnir/Initialize()
+ . = ..()
+
+/obj/item/mjollnir/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/two_handed, force_multiplier=5, icon_wielded="mjollnir1", attacksound="sparks")
+
+/obj/item/mjollnir/update_icon_state()
+ icon_state = "mjollnir0"
+
+/obj/item/mjollnir/proc/shock(mob/living/target)
target.Stun(60)
var/datum/effect_system/lightning_spread/s = new /datum/effect_system/lightning_spread
s.set_up(5, 1, target.loc)
@@ -101,17 +115,17 @@
target.throw_at(throw_target, 200, 4)
return
-/obj/item/twohanded/mjollnir/attack(mob/living/M, mob/user)
+/obj/item/mjollnir/attack(mob/living/M, mob/user)
..()
- if(wielded)
+ if(ISWIELDED(src))
playsound(src.loc, "sparks", 50, 1)
shock(M)
-/obj/item/twohanded/mjollnir/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
+/obj/item/mjollnir/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
. = ..()
if(isliving(hit_atom))
shock(hit_atom)
-/obj/item/twohanded/mjollnir/update_icon() //Currently only here to fuck with the on-mob icons.
- icon_state = "mjollnir[wielded]"
- return
+/obj/item/mjollnir/update_icon() //Currently only here to fuck with the on-mob icons.
+ icon_state = "mjollnir0"
+ ..()
diff --git a/code/game/objects/items/spear.dm b/code/game/objects/items/spear.dm
new file mode 100644
index 0000000000000..2d02e5d8b1f5b
--- /dev/null
+++ b/code/game/objects/items/spear.dm
@@ -0,0 +1,198 @@
+//spears
+/obj/item/spear
+ icon_state = "spearglass0"
+ lefthand_file = 'icons/mob/inhands/weapons/polearms_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/polearms_righthand.dmi'
+ name = "spear"
+ desc = "A haphazardly-constructed yet still deadly weapon of ancient design."
+ force = 10
+ w_class = WEIGHT_CLASS_BULKY
+ slot_flags = ITEM_SLOT_BACK
+ block_upgrade_walk = 1
+ throwforce = 20
+ throw_speed = 4
+ embedding = list("armour_block" = 60)
+ armour_penetration = 10
+ materials = list(/datum/material/iron=1150, /datum/material/glass=2075)
+ hitsound = 'sound/weapons/bladeslice.ogg'
+ attack_verb = list("attacked", "poked", "jabbed", "torn", "gored")
+ sharpness = IS_SHARP
+ max_integrity = 200
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30, "stamina" = 0)
+ var/war_cry = "AAAAARGH!!!"
+ var/icon_prefix = "spearglass"
+
+/obj/item/spear/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/butchering, 100, 70) //decent in a pinch, but pretty bad.
+ AddComponent(/datum/component/jousting)
+ AddComponent(/datum/component/two_handed, force_unwielded=10, force_wielded=18, block_power_wielded=25, icon_wielded="[icon_prefix]1")
+
+/obj/item/spear/update_icon()
+ icon_state = "[icon_prefix]0"
+ ..()
+
+/obj/item/spear/suicide_act(mob/living/carbon/user)
+ user.visible_message("[user] begins to sword-swallow \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
+ return BRUTELOSS
+
+/obj/item/spear/CheckParts(list/parts_list)
+ var/obj/item/shard/tip = locate() in parts_list
+ if(tip)
+ if (istype(tip, /obj/item/shard/plasma))
+ throwforce = 21
+ icon_prefix = "spearplasma"
+ AddComponent(/datum/component/two_handed, force_unwielded=11, force_wielded=19, icon_wielded="[icon_prefix]1")
+ update_icon()
+ parts_list -= tip
+ qdel(tip)
+ var/obj/item/grenade/G = locate() in parts_list
+ if(G)
+ var/obj/item/spear/explosive/lance = new /obj/item/spear/explosive(src.loc, G)
+ lance.TakeComponent(GetComponent(/datum/component/two_handed))
+ lance.throwforce = throwforce
+ lance.icon_prefix = icon_prefix
+ parts_list -= G
+ qdel(src)
+ return ..()
+
+/obj/item/spear/explosive
+ name = "explosive lance"
+ icon_prefix = "spearbomb"
+ icon_state = "spearbomb0"
+ var/obj/item/grenade/explosive = null
+
+/obj/item/spear/explosive/Initialize(mapload, obj/item/grenade/G)
+ . = ..()
+ set_explosive(G)
+
+/obj/item/spear/explosive/suicide_act(mob/living/carbon/user)
+ user.visible_message("[user] begins to sword-swallow \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
+ user.say("[war_cry]", forced="spear warcry")
+ explosive.forceMove(user)
+ explosive.prime()
+ user.gib()
+ qdel(src)
+ return BRUTELOSS
+
+/obj/item/spear/explosive/proc/set_explosive(obj/item/grenade/G)
+ if (!G)
+ G = new /obj/item/grenade/iedcasing() //For admin-spawned explosive lances
+ G.forceMove(src)
+ explosive = G
+ desc = "A makeshift spear with [G] attached to it"
+ update_icon()
+
+/obj/item/spear/explosive/CheckParts(list/parts_list)
+ var/obj/item/grenade/G = locate() in parts_list
+ if(G)
+ var/obj/item/spear/lancePart = locate() in parts_list
+ var/datum/component/two_handed/comp_twohand = lancePart.GetComponent(/datum/component/two_handed)
+ if(comp_twohand)
+ var/lance_wielded = comp_twohand.force_wielded
+ var/lance_unwielded = comp_twohand.force_unwielded
+ AddComponent(/datum/component/two_handed, force_unwielded=lance_unwielded, force_wielded=lance_wielded)
+ throwforce = lancePart.throwforce
+ icon_prefix = lancePart.icon_prefix
+ parts_list -= G
+ parts_list -= lancePart
+ set_explosive(G)
+ qdel(lancePart)
+ ..()
+
+/obj/item/spear/explosive/suicide_act(mob/living/carbon/user)
+ user.visible_message("[user] begins to sword-swallow \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
+ user.say("[war_cry]", forced="spear warcry")
+ explosive.forceMove(user)
+ explosive.prime()
+ user.gib()
+ qdel(src)
+ return BRUTELOSS
+
+/obj/item/spear/explosive/examine(mob/user)
+ . = ..()
+ . += "Alt-click to set your war cry."
+
+/obj/item/spear/explosive/AltClick(mob/user)
+ if(user.canUseTopic(src, BE_CLOSE))
+ ..()
+ if(istype(user) && loc == user)
+ var/input = stripped_input(user,"What do you want your war cry to be? You will shout it when you hit someone in melee.", ,"", 50)
+ if(input)
+ src.war_cry = input
+
+/obj/item/spear/explosive/afterattack(atom/movable/AM, mob/user, proximity)
+ . = ..()
+ if(!proximity)
+ return
+ if(ISWIELDED(src))
+ user.say("[war_cry]", forced="spear warcry")
+ explosive.forceMove(AM)
+ explosive.prime(lanced_by=user)
+ qdel(src)
+
+//GREY TIDE
+/obj/item/spear/grey_tide
+ name = "\improper Grey Tide"
+ desc = "Recovered from the aftermath of a revolt aboard Defense Outpost Theta Aegis, in which a seemingly endless tide of Assistants caused heavy casualities among Nanotrasen military forces."
+ attack_verb = list("gored")
+ force=15
+
+/obj/item/spear/grey_tide/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/two_handed, force_unwielded=15, force_wielded=25, block_power_wielded=25, icon_wielded="[icon_prefix]1")
+
+/obj/item/spear/grey_tide/afterattack(atom/movable/AM, mob/living/user, proximity)
+ . = ..()
+ if(!proximity)
+ return
+ user.faction |= "greytide([REF(user)])"
+ if(isliving(AM))
+ var/mob/living/L = AM
+ if(istype (L, /mob/living/simple_animal/hostile/illusion))
+ return
+ if(!L.stat && prob(50))
+ var/mob/living/simple_animal/hostile/illusion/M = new(user.loc)
+ M.faction = user.faction.Copy()
+ M.Copy_Parent(user, 100, user.health/2.5, 12, 30)
+ M.GiveTarget(L)
+
+/*
+ * Bone Spear
+ */
+/obj/item/spear/bonespear //Blatant imitation of spear, but made out of bone. Not valid for explosive modification.
+ icon_prefix = "bone_spear"
+ icon_state = "bone_spear0"
+ name = "bone spear"
+ desc = "A haphazardly-constructed yet still deadly weapon. The pinnacle of modern technology."
+ force = 12
+ throwforce = 22
+ armour_penetration = 15 //Enhanced armor piercing
+
+/obj/item/spear/bonespear/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/two_handed, force_unwielded=12, force_wielded=20, block_power_wielded=25, icon_wielded="[icon_prefix]1")
+
+/obj/item/spear/bamboospear
+ icon_prefix = "bamboo_spear"
+ icon_state = "bamboo_spear0"
+ lefthand_file = 'icons/mob/inhands/weapons/polearms_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/polearms_righthand.dmi'
+ name = "bamboo spear"
+ desc = "A haphazardly-constructed bamboo stick with a sharpened tip, ready to poke holes into unsuspecting people."
+ force = 10
+ w_class = WEIGHT_CLASS_BULKY
+ slot_flags = ITEM_SLOT_BACK
+ block_upgrade_walk = 1
+ throwforce = 22
+ throw_speed = 4
+ embedding = list("armour_block" = 30)
+ armour_penetration = 10
+ hitsound = 'sound/weapons/bladeslice.ogg'
+ attack_verb = list("attacked", "poked", "jabbed", "tore", "gored")
+ sharpness = IS_SHARP
+
+/obj/item/spear/bamboospear/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/two_handed, force_unwielded=10, force_wielded=18, \
+ block_power_wielded=25, icon_wielded="[icon_prefix]1")
diff --git a/code/game/objects/items/stacks/bscrystal.dm b/code/game/objects/items/stacks/bscrystal.dm
index 06798404d0be0..25c74bd8fda39 100644
--- a/code/game/objects/items/stacks/bscrystal.dm
+++ b/code/game/objects/items/stacks/bscrystal.dm
@@ -11,6 +11,7 @@
var/blink_range = 8 // The teleport range when crushed/thrown at someone.
refined_type = /obj/item/stack/sheet/bluespace_crystal
grind_results = list(/datum/reagent/bluespace = 20)
+ scan_state = "rock_BScrystal"
/obj/item/stack/ore/bluespace_crystal/refined
name = "refined bluespace crystal"
@@ -68,8 +69,18 @@
novariants = TRUE
grind_results = list(/datum/reagent/bluespace = 20)
point_value = 30
+ merge_type = /obj/item/stack/sheet/bluespace_crystal
var/crystal_type = /obj/item/stack/ore/bluespace_crystal/refined
+/obj/item/stack/sheet/bluespace_crystal/fifty
+ amount = 50
+
+/obj/item/stack/sheet/bluespace_crystal/twenty
+ amount = 20
+
+/obj/item/stack/sheet/bluespace_crystal/five
+ amount = 5
+
/obj/item/stack/sheet/bluespace_crystal/attack_self(mob/user)// to prevent the construction menu from ever happening
to_chat(user, "You cannot crush the polycrystal in-hand, try breaking one off.")
diff --git a/code/game/objects/items/stacks/cash.dm b/code/game/objects/items/stacks/cash.dm
index a06b96ad9c90e..7976bca9de4db 100644
--- a/code/game/objects/items/stacks/cash.dm
+++ b/code/game/objects/items/stacks/cash.dm
@@ -19,7 +19,7 @@
/obj/item/stack/spacecash/proc/update_desc()
var/total_worth = get_item_credit_value()
- desc = "It's worth [total_worth] credit[( total_worth > 1 ) ? "s" : ""]"
+ desc = "It's worth [total_worth] credit[( total_worth > 1 ) ? "s" : ""]."
/obj/item/stack/spacecash/get_item_credit_value()
return (amount*value)
diff --git a/code/game/objects/items/stacks/rods.dm b/code/game/objects/items/stacks/rods.dm
index 53b9823964fdf..1a53a6ec50ca3 100644
--- a/code/game/objects/items/stacks/rods.dm
+++ b/code/game/objects/items/stacks/rods.dm
@@ -21,8 +21,10 @@ GLOBAL_LIST_INIT(rod_recipes, list ( \
throw_range = 7
materials = list(/datum/material/iron=1000)
max_amount = 50
+ merge_type = /obj/item/stack/rods
attack_verb = list("hit", "bludgeoned", "whacked")
hitsound = 'sound/weapons/grenadelaunch.ogg'
+ embedding = list()
novariants = TRUE
block_upgrade_walk = 1
diff --git a/code/game/objects/items/stacks/sheets/glass.dm b/code/game/objects/items/stacks/sheets/glass.dm
index dfa938658463f..dba9d6c4590b9 100644
--- a/code/game/objects/items/stacks/sheets/glass.dm
+++ b/code/game/objects/items/stacks/sheets/glass.dm
@@ -9,6 +9,7 @@
* Glass sheets
*/
GLOBAL_LIST_INIT(glass_recipes, list ( \
+ new/datum/stack_recipe("glass shard", /obj/item/shard, time = 0, on_floor = FALSE), \
new/datum/stack_recipe("directional window", /obj/structure/window/unanchored, time = 0, on_floor = TRUE, window_checks = TRUE), \
new/datum/stack_recipe("fulltile window", /obj/structure/window/fulltile/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) \
))
@@ -20,7 +21,7 @@ GLOBAL_LIST_INIT(glass_recipes, list ( \
icon_state = "sheet-glass"
item_state = "sheet-glass"
materials = list(/datum/material/glass=MINERAL_MATERIAL_AMOUNT)
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 100)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 100, "stamina" = 0)
resistance_flags = ACID_PROOF
merge_type = /obj/item/stack/sheet/glass
grind_results = list(/datum/reagent/silicon = 20)
@@ -59,11 +60,12 @@ GLOBAL_LIST_INIT(glass_recipes, list ( \
var/obj/item/stack/rods/V = W
if (V.get_amount() >= 1 && get_amount() >= 1)
var/obj/item/stack/sheet/rglass/RG = new (get_turf(user))
- RG.add_fingerprint(user)
+ if(!QDELETED(RG))
+ RG.add_fingerprint(user)
var/replace = user.get_inactive_held_item()==src
V.use(1)
use(1)
- if(QDELETED(src) && replace)
+ if(QDELETED(src) && replace && !QDELETED(RG))
user.put_in_hands(RG)
else
to_chat(user, "You need one rod and one sheet of glass to make reinforced glass!")
@@ -85,11 +87,12 @@ GLOBAL_LIST_INIT(pglass_recipes, list ( \
icon_state = "sheet-pglass"
item_state = "sheet-pglass"
materials = list(/datum/material/plasma=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT)
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 75, "acid" = 100)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 75, "acid" = 100, "stamina" = 0)
resistance_flags = ACID_PROOF
merge_type = /obj/item/stack/sheet/plasmaglass
grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/toxin/plasma = 10)
-
+ tableVariant = /obj/structure/table/glass/plasma
+
/obj/item/stack/sheet/plasmaglass/fifty
amount = 50
@@ -125,7 +128,8 @@ GLOBAL_LIST_INIT(reinforced_glass_recipes, list ( \
new/datum/stack_recipe("windoor frame", /obj/structure/windoor_assembly, 5, time = 0, on_floor = TRUE, window_checks = TRUE), \
null, \
new/datum/stack_recipe("directional reinforced window", /obj/structure/window/reinforced/unanchored, time = 0, on_floor = TRUE, window_checks = TRUE), \
- new/datum/stack_recipe("fulltile reinforced window", /obj/structure/window/reinforced/fulltile/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE) \
+ new/datum/stack_recipe("fulltile reinforced window", /obj/structure/window/reinforced/fulltile/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE), \
+ new/datum/stack_recipe("window firelock frame", /obj/structure/firelock_frame/window, 2, time = 50, one_per_turf = TRUE, on_floor = TRUE, window_checks = FALSE) \
))
@@ -136,7 +140,7 @@ GLOBAL_LIST_INIT(reinforced_glass_recipes, list ( \
icon_state = "sheet-rglass"
item_state = "sheet-rglass"
materials = list(/datum/material/iron=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT)
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 100)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 100, "stamina" = 0)
resistance_flags = ACID_PROOF
merge_type = /obj/item/stack/sheet/rglass
grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/iron = 10)
@@ -179,7 +183,7 @@ GLOBAL_LIST_INIT(prglass_recipes, list ( \
icon_state = "sheet-prglass"
item_state = "sheet-prglass"
materials = list(/datum/material/plasma=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT, /datum/material/iron = MINERAL_MATERIAL_AMOUNT * 0.5,)
- armor = list("melee" = 20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100)
+ armor = list("melee" = 20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100, "stamina" = 0)
resistance_flags = ACID_PROOF
merge_type = /obj/item/stack/sheet/plasmarglass
grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/toxin/plasma = 10, /datum/reagent/iron = 10)
@@ -200,7 +204,7 @@ GLOBAL_LIST_INIT(titaniumglass_recipes, list(
icon_state = "sheet-titaniumglass"
item_state = "sheet-titaniumglass"
materials = list(/datum/material/titanium=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT)
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100, "stamina" = 0)
resistance_flags = ACID_PROOF
merge_type = /obj/item/stack/sheet/titaniumglass
@@ -219,7 +223,7 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list(
icon_state = "sheet-plastitaniumglass"
item_state = "sheet-plastitaniumglass"
materials = list(/datum/material/titanium=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/plasma=MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass=MINERAL_MATERIAL_AMOUNT)
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100, "stamina" = 0)
resistance_flags = ACID_PROOF
merge_type = /obj/item/stack/sheet/plastitaniumglass
@@ -242,10 +246,11 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list(
attack_verb = list("stabbed", "slashed", "sliced", "cut")
hitsound = 'sound/weapons/bladeslice.ogg'
resistance_flags = ACID_PROOF
- armor = list("melee" = 100, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 100)
+ armor = list("melee" = 100, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 100, "stamina" = 0)
max_integrity = 40
sharpness = IS_SHARP
var/icon_prefix
+ embedding = list("embed_chance" = 65)
/obj/item/shard/suicide_act(mob/user)
diff --git a/code/game/objects/items/stacks/sheets/mineral.dm b/code/game/objects/items/stacks/sheets/mineral.dm
index 3792d5624689e..f42661668e1ee 100644
--- a/code/game/objects/items/stacks/sheets/mineral.dm
+++ b/code/game/objects/items/stacks/sheets/mineral.dm
@@ -121,6 +121,15 @@ GLOBAL_LIST_INIT(diamond_recipes, list ( \
recipes = GLOB.diamond_recipes
. = ..()
+/obj/item/stack/sheet/mineral/diamond/fifty
+ amount = 50
+
+/obj/item/stack/sheet/mineral/diamond/twenty
+ amount = 20
+
+/obj/item/stack/sheet/mineral/diamond/five
+ amount = 5
+
/*
* Uranium
*/
@@ -147,6 +156,15 @@ GLOBAL_LIST_INIT(uranium_recipes, list ( \
recipes = GLOB.uranium_recipes
. = ..()
+/obj/item/stack/sheet/mineral/uranium/fifty
+ amount = 50
+
+/obj/item/stack/sheet/mineral/uranium/twenty
+ amount = 20
+
+/obj/item/stack/sheet/mineral/uranium/five
+ amount = 5
+
/*
* Plasma
*/
@@ -190,6 +208,15 @@ GLOBAL_LIST_INIT(plasma_recipes, list ( \
atmos_spawn_air("plasma=[amount*10];TEMP=[exposed_temperature]")
qdel(src)
+/obj/item/stack/sheet/mineral/plasma/fifty
+ amount = 50
+
+/obj/item/stack/sheet/mineral/plasma/twenty
+ amount = 20
+
+/obj/item/stack/sheet/mineral/plasma/five
+ amount = 5
+
/*
* Gold
*/
@@ -219,6 +246,15 @@ GLOBAL_LIST_INIT(gold_recipes, list ( \
recipes = GLOB.gold_recipes
. = ..()
+/obj/item/stack/sheet/mineral/gold/fifty
+ amount = 50
+
+/obj/item/stack/sheet/mineral/gold/twenty
+ amount = 20
+
+/obj/item/stack/sheet/mineral/gold/five
+ amount = 5
+
/*
* Silver
*/
@@ -248,6 +284,15 @@ GLOBAL_LIST_INIT(silver_recipes, list ( \
recipes = GLOB.silver_recipes
. = ..()
+/obj/item/stack/sheet/mineral/silver/fifty
+ amount = 50
+
+/obj/item/stack/sheet/mineral/silver/twenty
+ amount = 20
+
+/obj/item/stack/sheet/mineral/silver/five
+ amount = 5
+
/*
* Copper
*/
@@ -272,6 +317,15 @@ GLOBAL_LIST_INIT(copper_recipes, list ( \
recipes = GLOB.copper_recipes
. = ..()
+/obj/item/stack/sheet/mineral/copper/fifty
+ amount = 50
+
+/obj/item/stack/sheet/mineral/copper/twenty
+ amount = 20
+
+/obj/item/stack/sheet/mineral/copper/five
+ amount = 5
+
/*
* Clown
*/
@@ -296,6 +350,9 @@ GLOBAL_LIST_INIT(bananium_recipes, list ( \
recipes = GLOB.bananium_recipes
. = ..()
+/obj/item/stack/sheet/mineral/bananium/five
+ amount = 5
+
/*
* Titanium
*/
@@ -325,6 +382,12 @@ GLOBAL_LIST_INIT(titanium_recipes, list ( \
/obj/item/stack/sheet/mineral/titanium/fifty
amount = 50
+/obj/item/stack/sheet/mineral/titanium/twenty
+ amount = 20
+
+/obj/item/stack/sheet/mineral/titanium/five
+ amount = 5
+
/*
* Plastitanium
@@ -466,3 +529,22 @@ GLOBAL_LIST_INIT(abductor_recipes, list ( \
/obj/item/stack/sheet/mineral/coal/ten
amount = 10
+
+/*
+ * Wax
+ */
+/obj/item/stack/sheet/mineral/wax
+ name = "wax"
+ icon_state = "sheet-wax"
+ item_state = "sheet-wax"
+ singular_name = "wax block"
+ force = 1
+ throwforce = 2
+ grind_results = list(/datum/reagent/consumable/honey = 20)
+ merge_type = /obj/item/stack/sheet/mineral/wax
+
+GLOBAL_LIST_INIT(wax_recipes, list (new/datum/stack_recipe("Wax tile", /obj/item/stack/tile/mineral/wax, 1, 4, 20)))
+
+/obj/item/stack/sheet/mineral/wax/Initialize(mapload, new_amount, merge = TRUE)
+ recipes = GLOB.wax_recipes
+ . = ..()
diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm
index 8ab852b0a48c5..e565b3f1e560b 100644
--- a/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -20,6 +20,7 @@ GLOBAL_LIST_INIT(metal_recipes, list ( \
new/datum/stack_recipe("stool", /obj/structure/chair/stool, one_per_turf = TRUE, on_floor = TRUE), \
new/datum/stack_recipe("bar stool", /obj/structure/chair/stool/bar, one_per_turf = TRUE, on_floor = TRUE), \
new/datum/stack_recipe("chair", /obj/structure/chair, one_per_turf = TRUE, on_floor = TRUE), \
+ new/datum/stack_recipe("shuttle seat", /obj/structure/chair/comfy/shuttle, 2, one_per_turf = TRUE, on_floor = TRUE), \
new/datum/stack_recipe("bed", /obj/structure/bed, 2, one_per_turf = TRUE, on_floor = TRUE), \
null, \
new/datum/stack_recipe_list("office chairs", list( \
@@ -38,6 +39,12 @@ GLOBAL_LIST_INIT(metal_recipes, list ( \
new /datum/stack_recipe("sofa (left)", /obj/structure/chair/sofa/left, 1, one_per_turf = TRUE, on_floor = TRUE),
new /datum/stack_recipe("sofa (right)", /obj/structure/chair/sofa/right, 1, one_per_turf = TRUE, on_floor = TRUE),
new /datum/stack_recipe("sofa (corner)", /obj/structure/chair/sofa/corner, 1, one_per_turf = TRUE, on_floor = TRUE)
+ )), \
+ new/datum/stack_recipe_list("corporate sofas", list( \
+ new /datum/stack_recipe("sofa (middle)", /obj/structure/chair/sofa/corp, one_per_turf = TRUE, on_floor = TRUE), \
+ new /datum/stack_recipe("sofa (left)", /obj/structure/chair/sofa/corp/left, one_per_turf = TRUE, on_floor = TRUE), \
+ new /datum/stack_recipe("sofa (right)", /obj/structure/chair/sofa/corp/right, one_per_turf = TRUE, on_floor = TRUE), \
+ new /datum/stack_recipe("sofa (corner)", /obj/structure/chair/sofa/corp/corner, one_per_turf = TRUE, on_floor = TRUE), \
)),
null, \
new/datum/stack_recipe("rack parts", /obj/item/rack_parts), \
@@ -75,6 +82,7 @@ GLOBAL_LIST_INIT(metal_recipes, list ( \
)), \
null, \
new/datum/stack_recipe("firelock frame", /obj/structure/firelock_frame, 3, time = 50, one_per_turf = TRUE, on_floor = TRUE), \
+ new/datum/stack_recipe("directional firelock frame", /obj/structure/firelock_frame/border, 3, time = 50, one_per_turf = FALSE, on_floor = TRUE), \
new/datum/stack_recipe("turret frame", /obj/machinery/porta_turret_construct, 5, time = 25, one_per_turf = TRUE, on_floor = TRUE), \
new/datum/stack_recipe("meatspike frame", /obj/structure/kitchenspike_frame, 5, time = 25, one_per_turf = TRUE, on_floor = TRUE), \
new/datum/stack_recipe("reflector frame", /obj/structure/reflector, 5, time = 25, one_per_turf = TRUE, on_floor = TRUE), \
@@ -93,6 +101,8 @@ GLOBAL_LIST_INIT(metal_recipes, list ( \
null, \
new/datum/stack_recipe("iron door", /obj/structure/mineral_door/iron, 20, one_per_turf = TRUE, on_floor = TRUE), \
new/datum/stack_recipe("floodlight frame", /obj/structure/floodlight_frame, 5, one_per_turf = TRUE, on_floor = TRUE), \
+ new/datum/stack_recipe("shower frame", /obj/structure/showerframe, 2, time = 2 SECONDS), \
+ new/datum/stack_recipe("sink frame", /obj/structure/sinkframe, 2, time = 2 SECONDS), \
))
/obj/item/stack/sheet/iron
@@ -165,7 +175,7 @@ GLOBAL_LIST_INIT(plasteel_recipes, list ( \
materials = list(/datum/material/iron=2000, /datum/material/plasma=2000)
throwforce = 10
flags_1 = CONDUCT_1
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 80)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 80, "stamina" = 0)
resistance_flags = FIRE_PROOF
merge_type = /obj/item/stack/sheet/plasteel
grind_results = list(/datum/reagent/iron = 20, /datum/reagent/toxin/plasma = 20)
@@ -201,7 +211,9 @@ GLOBAL_LIST_INIT(wood_recipes, list ( \
new/datum/stack_recipe("dog bed", /obj/structure/bed/dogbed, 10, time = 10, one_per_turf = TRUE, on_floor = TRUE), \
new/datum/stack_recipe("dresser", /obj/structure/dresser, 10, time = 15, one_per_turf = TRUE, on_floor = TRUE), \
new/datum/stack_recipe("picture frame", /obj/item/wallframe/picture, 1, time = 10),\
+ new/datum/stack_recipe("painting frame", /obj/item/wallframe/painting, 1, time = 10),\
new/datum/stack_recipe("display case chassis", /obj/structure/displaycase_chassis, 5, one_per_turf = TRUE, on_floor = TRUE), \
+ new/datum/stack_recipe("easel", /obj/structure/easel, 5, time = 10, one_per_turf = TRUE, on_floor = TRUE), \
new/datum/stack_recipe("wooden buckler", /obj/item/shield/riot/buckler, 20, time = 40), \
new/datum/stack_recipe("apiary", /obj/structure/beebox, 40, time = 50),\
new/datum/stack_recipe("tiki mask", /obj/item/clothing/mask/gas/tiki_mask, 2), \
@@ -229,7 +241,7 @@ GLOBAL_LIST_INIT(wood_recipes, list ( \
item_state = "sheet-wood"
icon = 'icons/obj/stack_objects.dmi'
sheettype = "wood"
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0, "stamina" = 0)
resistance_flags = FLAMMABLE
merge_type = /obj/item/stack/sheet/mineral/wood
novariants = TRUE
@@ -271,6 +283,8 @@ GLOBAL_LIST_INIT(cloth_recipes, list ( \
new/datum/stack_recipe("mining satchel", /obj/item/storage/bag/ore, 4), \
new/datum/stack_recipe("chemistry bag", /obj/item/storage/bag/chemistry, 4), \
new/datum/stack_recipe("bio bag", /obj/item/storage/bag/bio, 4), \
+ new/datum/stack_recipe("construction bag", /obj/item/storage/bag/construction, 4), \
+ new/datum/stack_recipe("sheet snatcher", /obj/item/storage/bag/sheetsnatcher, 6), \
null, \
new/datum/stack_recipe("improvised gauze", /obj/item/stack/medical/gauze/improvised, 1, 2, 6), \
new/datum/stack_recipe("rag", /obj/item/reagent_containers/glass/rag, 1), \
@@ -283,6 +297,10 @@ GLOBAL_LIST_INIT(cloth_recipes, list ( \
new/datum/stack_recipe("white beanie", /obj/item/clothing/head/beanie, 2), \
null, \
new/datum/stack_recipe("blindfold", /obj/item/clothing/glasses/blindfold, 2), \
+ null, \
+ new/datum/stack_recipe("19x19 canvas", /obj/item/canvas/nineteen_nineteen, 3), \
+ new/datum/stack_recipe("23x19 canvas", /obj/item/canvas/twentythree_nineteen, 4), \
+ new/datum/stack_recipe("23x23 canvas", /obj/item/canvas/twentythree_twentythree, 5), \
))
/obj/item/stack/sheet/cotton/cloth
@@ -367,7 +385,7 @@ GLOBAL_LIST_INIT(durathread_recipes, list ( \
GLOBAL_LIST_INIT(bamboo_recipes, list ( \
new/datum/stack_recipe("punji sticks trap", /obj/structure/punji_sticks, 5, time = 30, one_per_turf = TRUE, on_floor = TRUE), \
new/datum/stack_recipe("blow gun", /obj/item/gun/syringe/blowgun, 10, time = 70), \
- new/datum/stack_recipe("bamboo spear", /obj/item/twohanded/bamboospear, 25, time = 90), \
+ new/datum/stack_recipe("bamboo spear", /obj/item/spear/bamboospear, 25, time = 90), \
new/datum/stack_recipe("crude syringe", /obj/item/reagent_containers/syringe/crude, 5, time = 10), \
))
@@ -381,7 +399,7 @@ GLOBAL_LIST_INIT(bamboo_recipes, list ( \
sheettype = "bamboo"
force = 10
throwforce = 10
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0, "stamina" = 0)
resistance_flags = FLAMMABLE
merge_type = /obj/item/stack/sheet/mineral/bamboo
grind_results = list("carbon" = 5)
@@ -390,6 +408,14 @@ GLOBAL_LIST_INIT(bamboo_recipes, list ( \
recipes = GLOB.bamboo_recipes
return ..()
+/obj/item/stack/sheet/mineral/bamboo/Topic(href, href_list)
+ . = ..()
+ if(href_list["make"])
+ var/list/recipes_list = recipes
+ var/datum/stack_recipe/R = recipes_list[text2num(href_list["make"])]
+ if(R.result_type == /obj/structure/punji_sticks)
+ var/turf/T = get_turf(src)
+ usr.investigate_log("has placed punji sticks trap at [AREACOORD(T)].", INVESTIGATE_BOTANY)
/*
* Cardboard
@@ -515,7 +541,7 @@ GLOBAL_LIST_INIT(runed_metal_recipes, list ( \
return
var/turf/T = get_turf(user) //we may have moved. adjust as needed...
var/area/A = get_area(user)
- if((!is_station_level(T.z) && !is_mining_level(T.z)) || (A && !A.blob_allowed))
+ if((!is_station_level(T.z) && !is_mining_level(T.z)) || (A && !(A.area_flags & BLOBS_ALLOWED)))
to_chat(user, "The veil is not weak enough here.")
return FALSE
return ..()
@@ -608,6 +634,10 @@ GLOBAL_LIST_INIT(brass_recipes, list ( \
GLOBAL_LIST_INIT(bronze_recipes, list ( \
new/datum/stack_recipe("wall gear", /obj/structure/girder/bronze, 2, time = 20, one_per_turf = TRUE, on_floor = TRUE), \
null,
+ new/datum/stack_recipe("directional bronze window", /obj/structure/window/bronze/unanchored, time = 0, on_floor = TRUE, window_checks = TRUE), \
+ new/datum/stack_recipe("fulltile bronze window", /obj/structure/window/bronze/fulltile/unanchored, 2, time = 0, on_floor = TRUE, window_checks = TRUE), \
+ new/datum/stack_recipe("pinion airlock assembly", /obj/structure/door_assembly/door_assembly_bronze, 4, time = 50, one_per_turf = TRUE, on_floor = TRUE), \
+ new/datum/stack_recipe("bronze pinion airlock assembly", /obj/structure/door_assembly/door_assembly_bronze/seethru, 4, time = 50, one_per_turf = TRUE, on_floor = TRUE), \
new/datum/stack_recipe("bronze hat", /obj/item/clothing/head/bronze), \
new/datum/stack_recipe("bronze suit", /obj/item/clothing/suit/bronze), \
new/datum/stack_recipe("bronze boots", /obj/item/clothing/shoes/bronze), \
diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm
index 637e873c06866..ec5520b6aa6ad 100644
--- a/code/game/objects/items/stacks/stack.dm
+++ b/code/game/objects/items/stacks/stack.dm
@@ -8,6 +8,7 @@
/*
* Stacks
*/
+
/obj/item/stack
icon = 'icons/obj/stack_objects.dmi'
gender = PLURAL
@@ -38,9 +39,7 @@
. = ..()
if(new_amount != null)
amount = new_amount
- while(amount > max_amount)
- amount -= max_amount
- new type(loc, max_amount, FALSE)
+ check_max_amount()
if(!merge_type)
merge_type = type
if(merge)
@@ -50,6 +49,12 @@
update_weight()
update_icon()
+/obj/item/stack/proc/check_max_amount()
+ while(amount > max_amount)
+ amount -= max_amount
+ ui_update()
+ new type(loc, max_amount, FALSE)
+
/obj/item/stack/proc/update_weight()
if(amount <= (max_amount * (1/3)))
w_class = CLAMP(full_w_class-2, WEIGHT_CLASS_TINY, full_w_class)
@@ -69,12 +74,6 @@
icon_state = "[initial(icon_state)]_3"
..()
-
-/obj/item/stack/Destroy()
- if (usr && usr.machine==src)
- usr << browse(null, "window=stack")
- . = ..()
-
/obj/item/stack/examine(mob/user)
. = ..()
if (is_cyborg)
@@ -96,139 +95,157 @@
/obj/item/stack/proc/get_amount()
if(is_cyborg)
- . = round(source.energy / cost)
+ . = round(source?.energy / cost)
else
. = (amount)
-/obj/item/stack/attack_self(mob/user)
- interact(user)
+/**
+ * Builds all recipes in a given recipe list and returns an association list containing them
+ *
+ * Arguments:
+ * * recipe_to_iterate - The list of recipes we are using to build recipes
+ */
+/obj/item/stack/proc/recursively_build_recipes(list/recipe_to_iterate)
+ var/list/L = list()
+ for(var/recipe in recipe_to_iterate)
+ if(isnull(recipe))
+ L += list(list(
+ "spacer" = TRUE
+ ))
+ if(istype(recipe, /datum/stack_recipe_list))
+ var/datum/stack_recipe_list/R = recipe
+ L += list(list(
+ "title" = R.title,
+ "sub_recipes" = recursively_build_recipes(R.recipes),
+ ))
+ if(istype(recipe, /datum/stack_recipe))
+ var/datum/stack_recipe/R = recipe
+ L += list(build_recipe(R))
+ return L
+
+/**
+ * Returns a list of properties of a given recipe
+ *
+ * Arguments:
+ * * R - The stack recipe we are using to get a list of properties
+ */
+/obj/item/stack/proc/build_recipe(datum/stack_recipe/R)
+ return list(
+ "title" = R.title,
+ "res_amount" = R.res_amount,
+ "max_res_amount" = R.max_res_amount,
+ "req_amount" = R.req_amount,
+ "ref" = "\ref[R]",
+ )
+
+/**
+ * Checks if the recipe is valid to be used
+ *
+ * Arguments:
+ * * R - The stack recipe we are checking if it is valid
+ * * recipe_list - The list of recipes we are using to check the given recipe
+ */
+/obj/item/stack/proc/is_valid_recipe(datum/stack_recipe/R, list/recipe_list)
+ for(var/S in recipe_list)
+ if(S == R)
+ return TRUE
+ if(istype(S, /datum/stack_recipe_list))
+ var/datum/stack_recipe_list/L = S
+ if(is_valid_recipe(R, L.recipes))
+ return TRUE
+ return FALSE
+
+/obj/item/stack/ui_state(mob/user)
+ return GLOB.hands_state
+
+/obj/item/stack/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Stack", name)
+ ui.open()
-/obj/item/stack/interact(mob/user, sublist)
- ui_interact(user, sublist)
+/obj/item/stack/ui_data(mob/user)
+ var/list/data = list()
+ data["amount"] = get_amount()
+ return data
-/obj/item/stack/ui_interact(mob/user, recipes_sublist)
+/obj/item/stack/ui_static_data(mob/user)
+ var/list/data = list()
+ data["recipes"] = recursively_build_recipes(recipes)
+ return data
+
+/obj/item/stack/ui_act(action, params)
. = ..()
- if (!recipes)
- return
- if (!src || get_amount() <= 0)
- user << browse(null, "window=stack")
- user.set_machine(src) //for correct work of onclose
- var/list/recipe_list = recipes
- if (recipes_sublist && recipe_list[recipes_sublist] && istype(recipe_list[recipes_sublist], /datum/stack_recipe_list))
- var/datum/stack_recipe_list/srl = recipe_list[recipes_sublist]
- recipe_list = srl.recipes
- var/t1 = "Amount Left: [get_amount()] "
- for(var/i in 1 to length(recipe_list))
- var/E = recipe_list[i]
- if (isnull(E))
- t1 += ""
- continue
- if (i>1 && !isnull(recipe_list[i-1]))
- t1+=" "
-
- if (istype(E, /datum/stack_recipe_list))
- var/datum/stack_recipe_list/srl = E
- t1 += "[srl.title]"
-
- if (istype(E, /datum/stack_recipe))
- var/datum/stack_recipe/R = E
- var/max_multiplier = round(get_amount() / R.req_amount)
- var/title
- var/can_build = 1
- can_build = can_build && (max_multiplier>0)
-
- if (R.res_amount>1)
- title+= "[R.res_amount]x [R.title]\s"
- else
- title+= "[R.title]"
- title+= " ([R.req_amount] [singular_name]\s)"
- if (can_build)
- t1 += text("[title] ")
- else
- t1 += text("[]", title)
- continue
- if (R.max_res_amount>1 && max_multiplier>1)
- max_multiplier = min(max_multiplier, round(R.max_res_amount/R.res_amount))
- t1 += " |"
- var/list/multipliers = list(5,10,25)
- for (var/n in multipliers)
- if (max_multiplier>=n)
- t1 += " [n*R.res_amount]x"
- if (!(max_multiplier in multipliers))
- t1 += " [max_multiplier*R.res_amount]x"
-
- var/datum/browser/popup = new(user, "stack", name, 400, 400)
- popup.set_content(t1)
- popup.open(FALSE)
- onclose(user, "stack")
-
-/obj/item/stack/Topic(href, href_list)
- ..()
- if (usr.restrained() || usr.stat || usr.get_active_held_item() != src)
+ if(.)
return
- if (href_list["sublist"] && !href_list["make"])
- interact(usr, text2num(href_list["sublist"]))
- if (href_list["make"])
- if (get_amount() < 1 && !is_cyborg)
- qdel(src)
-
- var/list/recipes_list = recipes
- if (href_list["sublist"])
- var/datum/stack_recipe_list/srl = recipes_list[text2num(href_list["sublist"])]
- recipes_list = srl.recipes
- var/datum/stack_recipe/R = recipes_list[text2num(href_list["make"])]
- var/multiplier = text2num(href_list["multiplier"])
- if (!isnum_safe(multiplier) || (multiplier <= 0)) //href protection
- return
- if(!building_checks(R, multiplier))
- return
- if (R.time)
- usr.visible_message("[usr] starts building \a [R.title].", "You start building \a [R.title]...")
- if (!do_after(usr, R.time, target = usr))
+
+ switch(action)
+ if("make")
+ if(get_amount() < 1 && !is_cyborg)
+ qdel(src)
return
- if(!building_checks(R, multiplier))
+ var/datum/stack_recipe/R = locate(params["ref"])
+ if(!is_valid_recipe(R, recipes)) //href exploit protection
return
-
- var/obj/O
- if(R.max_res_amount > 1) //Is it a stack?
- O = new R.result_type(usr.drop_location(), R.res_amount * multiplier)
- else if(ispath(R.result_type, /turf))
- var/turf/T = usr.drop_location()
- if(!isturf(T))
+ var/multiplier = text2num(params["multiplier"])
+ if(!isnum_safe(multiplier) || (multiplier <= 0)) //href exploit protection
return
- T.PlaceOnTop(R.result_type, flags = CHANGETURF_INHERIT_AIR)
- else
- O = new R.result_type(usr.drop_location())
- if(O)
- O.setDir(usr.dir)
- use(R.req_amount * multiplier)
-
- //START: oh fuck i'm so sorry
- if(istype(O, /obj/structure/windoor_assembly))
- var/obj/structure/windoor_assembly/W = O
- W.ini_dir = W.dir
- else if(istype(O, /obj/structure/window))
- var/obj/structure/window/W = O
- W.ini_dir = W.dir
- //END: oh fuck i'm so sorry
-
- else if(istype(O, /obj/item/restraints/handcuffs/cable))
- var/obj/item/cuffs = O
- cuffs.item_color = item_color
- cuffs.update_icon()
-
- if (QDELETED(O))
- return //It's a stack and has already been merged
-
- if (isitem(O))
- usr.put_in_hands(O)
- O.add_fingerprint(usr)
-
- //BubbleWrap - so newly formed boxes are empty
- if ( istype(O, /obj/item/storage) )
- for (var/obj/item/I in O)
- qdel(I)
- //BubbleWrap END
+ if(!building_checks(R, multiplier))
+ return
+ if(R.time)
+ usr.visible_message("[usr] starts building \a [R.title].", "You start building \a [R.title]...")
+ if (!do_after(usr, R.time, target = usr))
+ return
+ if(!building_checks(R, multiplier))
+ return
+
+ var/obj/O
+ if(R.max_res_amount > 1) //Is it a stack?
+ O = new R.result_type(usr.drop_location(), R.res_amount * multiplier)
+ else if(ispath(R.result_type, /turf))
+ var/turf/T = usr.drop_location()
+ if(!isturf(T))
+ return
+ T.PlaceOnTop(R.result_type, flags = CHANGETURF_INHERIT_AIR)
+ else
+ O = new R.result_type(usr.drop_location())
+ if(O)
+ O.setDir(usr.dir)
+ use(R.req_amount * multiplier)
+
+ /* // We don't have R.applies_mats, leaving this in here for convenience in case we get it
+ if(R.applies_mats && custom_materials && custom_materials.len)
+ var/list/used_materials = list()
+ for(var/i in custom_materials)
+ used_materials[SSmaterials.GetMaterialRef(i)] = R.req_amount / R.res_amount * (MINERAL_MATERIAL_AMOUNT / custom_materials.len)
+ O.set_custom_materials(used_materials)
+ */
+
+ if(istype(O, /obj/structure/windoor_assembly))
+ var/obj/structure/windoor_assembly/W = O
+ W.ini_dir = W.dir
+ else if(istype(O, /obj/structure/window))
+ var/obj/structure/window/W = O
+ W.ini_dir = W.dir
+
+ else if(istype(O, /obj/item/restraints/handcuffs/cable))
+ var/obj/item/cuffs = O
+ cuffs.item_color = item_color
+ cuffs.update_icon()
+
+ if(QDELETED(O))
+ return //It's a stack and has already been merged
+
+ if(isitem(O))
+ usr.put_in_hands(O)
+ O.add_fingerprint(usr)
+
+ //BubbleWrap - so newly formed boxes are empty
+ if(istype(O, /obj/item/storage))
+ for (var/obj/item/I in O)
+ qdel(I)
+ //BubbleWrap END
+ return TRUE
/obj/item/stack/proc/building_checks(datum/stack_recipe/R, multiplier)
if (get_amount() < R.req_amount*multiplier)
@@ -288,6 +305,7 @@
if(check)
zero_amount()
update_icon()
+ ui_update()
update_weight()
return TRUE
@@ -318,8 +336,10 @@
source.add_charge(amount * cost)
else
src.amount += amount
+ check_max_amount()
update_icon()
update_weight()
+ ui_update()
/obj/item/stack/proc/merge(obj/item/stack/S) //Merge src into S, as much as possible
if(QDELETED(S) || QDELETED(src) || S == src) //amusingly this can cause a stack to consume itself, let's not allow that.
diff --git a/code/game/objects/items/stacks/tape.dm b/code/game/objects/items/stacks/tape.dm
new file mode 100644
index 0000000000000..dc668431d989f
--- /dev/null
+++ b/code/game/objects/items/stacks/tape.dm
@@ -0,0 +1,60 @@
+
+
+/obj/item/stack/sticky_tape
+ name = "sticky tape"
+ singular_name = "sticky tape"
+ desc = "Used for sticking to things for sticking said things to people."
+ icon = 'icons/obj/tapes.dmi'
+ icon_state = "tape_w"
+ var/prefix = "sticky"
+ item_flags = NOBLUDGEON
+ amount = 5
+ max_amount = 5
+
+ var/list/conferred_embed = EMBED_HARMLESS
+ var/overwrite_existing = FALSE
+
+/obj/item/stack/sticky_tape/afterattack(obj/item/I, mob/living/user)
+ if(!istype(I))
+ return
+
+ if(I.embedding == conferred_embed)
+ to_chat(user, "[I] is already coated in [src]!")
+ return
+
+ user.visible_message("[user] begins wrapping [I] with [src].", "You begin wrapping [I] with [src].")
+
+ if(do_after(user, 30, target=I))
+ I.embedding = conferred_embed
+ I.updateEmbedding()
+ to_chat(user, "You finish wrapping [I] with [src].")
+ use(1)
+ I.name = "[prefix] [I.name]"
+
+ if(istype(I, /obj/item/grenade))
+ var/obj/item/grenade/sticky_bomb = I
+ sticky_bomb.sticky = TRUE
+
+/obj/item/stack/sticky_tape/super
+ name = "super sticky tape"
+ singular_name = "super sticky tape"
+ desc = "Quite possibly the most mischevious substance in the galaxy. Use with extreme lack of caution."
+ icon_state = "tape_y"
+ prefix = "super sticky"
+ conferred_embed = EMBED_HARMLESS_SUPERIOR
+
+/obj/item/stack/sticky_tape/pointy
+ name = "pointy tape"
+ singular_name = "pointy tape"
+ desc = "Used for sticking to things for sticking said things inside people."
+ icon_state = "tape_evil"
+ prefix = "pointy"
+ conferred_embed = EMBED_POINTY
+
+/obj/item/stack/sticky_tape/pointy/super
+ name = "super pointy tape"
+ singular_name = "super pointy tape"
+ desc = "You didn't know tape could look so sinister. Welcome to Space Station 13."
+ icon_state = "tape_spikes"
+ prefix = "super pointy"
+ conferred_embed = EMBED_POINTY_SUPERIOR
diff --git a/code/game/objects/items/stacks/telecrystal.dm b/code/game/objects/items/stacks/telecrystal.dm
index f5ab08468a7df..1d45f8b640b56 100644
--- a/code/game/objects/items/stacks/telecrystal.dm
+++ b/code/game/objects/items/stacks/telecrystal.dm
@@ -7,6 +7,7 @@
w_class = WEIGHT_CLASS_TINY
max_amount = 50
item_flags = NOBLUDGEON
+ merge_type = /obj/item/stack/telecrystal
/obj/item/stack/telecrystal/attack(mob/target, mob/user)
if(target == user) //You can't go around smacking people with crystals to find out if they have an uplink or not.
diff --git a/code/game/objects/items/stacks/tiles/light.dm b/code/game/objects/items/stacks/tiles/light.dm
index 428e8d621c024..71f9901506a4c 100644
--- a/code/game/objects/items/stacks/tiles/light.dm
+++ b/code/game/objects/items/stacks/tiles/light.dm
@@ -28,3 +28,11 @@
qdel(src)
else
return ..()
+
+/obj/item/stack/tile/light/cyborg
+ materials = list()
+ is_cyborg = 1
+ cost = 125
+
+/obj/item/stack/tile/light/cyborg/attackby(obj/item/O, mob/user, params)
+ return
diff --git a/code/game/objects/items/stacks/tiles/tile_mineral.dm b/code/game/objects/items/stacks/tiles/tile_mineral.dm
index 4a7dca8f3bb48..3267a53961d1c 100644
--- a/code/game/objects/items/stacks/tiles/tile_mineral.dm
+++ b/code/game/objects/items/stacks/tiles/tile_mineral.dm
@@ -105,3 +105,12 @@
item_state = "tile-silver"
turf_type = /turf/open/floor/grass/snow/safe
mineralType = "snow"
+
+/obj/item/stack/tile/mineral/wax
+ name = "wax tile"
+ singular_name = "wax tile"
+ desc = "A large, flat sheet of wax."
+ icon_state = "tile_wax"
+ item_state = "tile-wax"
+ turf_type = /turf/open/floor/wax
+ mineralType = "wax"
diff --git a/code/game/objects/items/stacks/tiles/tile_types.dm b/code/game/objects/items/stacks/tiles/tile_types.dm
index 7d2e68b2dc79c..ccb44c314864b 100644
--- a/code/game/objects/items/stacks/tiles/tile_types.dm
+++ b/code/game/objects/items/stacks/tiles/tile_types.dm
@@ -76,6 +76,76 @@
turf_type = /turf/open/floor/grass
resistance_flags = FLAMMABLE
+/obj/item/stack/tile/grass/attackby(obj/item/W, mob/user, params)
+ if((W.tool_behaviour == TOOL_SHOVEL) && params)
+ to_chat(user, "You start digging up [src].")
+ playsound(src, 'sound/effects/shovel_dig.ogg', 50, 1)
+ if(do_after(user, 2 * get_amount(), target = src))
+ new /obj/item/stack/ore/glass(get_turf(src), 2 * get_amount())
+ user.visible_message("[user] digs up [src].", "You uproot [src].")
+ playsound(src, 'sound/effects/shovel_dig.ogg', 50, 1)
+ qdel(src)
+ else
+ return ..()
+
+//Fairygrass
+/obj/item/stack/tile/fairygrass
+ name = "fairygrass tile"
+ singular_name = "fairygrass floor tile"
+ desc = "A patch of odd, glowing blue grass."
+ icon_state = "tile_fairygrass"
+ item_state = "tile-fairygrass"
+ turf_type = /turf/open/floor/grass/fairy
+ resistance_flags = FLAMMABLE
+ color = "#33CCFF"
+
+/obj/item/stack/tile/fairygrass/white
+ name = "white fairygrass tile"
+ singular_name = "white fairygrass floor tile"
+ desc = "A patch of odd, glowing white grass."
+ turf_type = /turf/open/floor/grass/fairy/white
+ color = "#FFFFFF"
+
+/obj/item/stack/tile/fairygrass/red
+ name = "red fairygrass tile"
+ singular_name = "red fairygrass floor tile"
+ desc = "A patch of odd, glowing red grass."
+ turf_type = /turf/open/floor/grass/fairy/red
+ color = "#FF3333"
+
+/obj/item/stack/tile/fairygrass/yellow
+ name = "yellow fairygrass tile"
+ singular_name = "yellow fairygrass floor tile"
+ desc = "A patch of odd, glowing yellow grass."
+ turf_type = /turf/open/floor/grass/fairy/yellow
+ color = "#FFFF66"
+
+/obj/item/stack/tile/fairygrass/green
+ name = "green fairygrass tile"
+ singular_name = "green fairygrass floor tile"
+ desc = "A patch of odd, glowing green grass."
+ turf_type = /turf/open/floor/grass/fairy/green
+ color = "#99FF99"
+
+/obj/item/stack/tile/fairygrass/blue
+ name = "blue fairygrass tile"
+ singular_name = "blue fairygrass floor tile"
+ desc = "A patch of odd, glowing blue grass."
+ turf_type = /turf/open/floor/grass/fairy/blue
+
+/obj/item/stack/tile/fairygrass/purple
+ name = "purple fairygrass tile"
+ singular_name = "purple fairygrass floor tile"
+ desc = "A patch of odd, glowing purple grass."
+ turf_type = /turf/open/floor/grass/fairy/purple
+ color = "#D966FF"
+
+/obj/item/stack/tile/fairygrass/pink
+ name = "pink fairygrass tile"
+ singular_name = "pink fairygrass floor tile"
+ desc = "A patch of odd, glowing pink grass."
+ turf_type = /turf/open/floor/grass/fairy/pink
+ color = "#FFB3DA"
//Wood
/obj/item/stack/tile/wood
@@ -111,28 +181,37 @@
name = "black carpet"
icon_state = "tile-carpet-black"
item_state = "tile-carpet-black"
+ merge_type = /obj/item/stack/tile/carpet/black
turf_type = /turf/open/floor/carpet/black
-
tableVariant = /obj/structure/table/wood/fancy/black
/obj/item/stack/tile/carpet/blue
name = "blue carpet"
icon_state = "tile-carpet-blue"
item_state = "tile-carpet-blue"
+ merge_type = /obj/item/stack/tile/carpet/blue
turf_type = /turf/open/floor/carpet/blue
tableVariant = /obj/structure/table/wood/fancy/blue
+/obj/item/stack/tile/carpet/blue/thirtytwo
+ amount = 32
+
/obj/item/stack/tile/carpet/cyan
name = "cyan carpet"
icon_state = "tile-carpet-cyan"
item_state = "tile-carpet-cyan"
+ merge_type = /obj/item/stack/tile/carpet/cyan
turf_type = /turf/open/floor/carpet/cyan
tableVariant = /obj/structure/table/wood/fancy/cyan
+/obj/item/stack/tile/carpet/cyan/thirtytwo
+ amount = 32
+
/obj/item/stack/tile/carpet/green
name = "green carpet"
icon_state = "tile-carpet-green"
item_state = "tile-carpet-green"
+ merge_type = /obj/item/stack/tile/carpet/green
turf_type = /turf/open/floor/carpet/green
tableVariant = /obj/structure/table/wood/fancy/green
@@ -140,6 +219,7 @@
name = "orange carpet"
icon_state = "tile-carpet-orange"
item_state = "tile-carpet-orange"
+ merge_type = /obj/item/stack/tile/carpet/orange
turf_type = /turf/open/floor/carpet/orange
tableVariant = /obj/structure/table/wood/fancy/orange
@@ -147,6 +227,7 @@
name = "purple carpet"
icon_state = "tile-carpet-purple"
item_state = "tile-carpet-purple"
+ merge_type = /obj/item/stack/tile/carpet/purple
turf_type = /turf/open/floor/carpet/purple
tableVariant = /obj/structure/table/wood/fancy/purple
@@ -154,6 +235,7 @@
name = "red carpet"
icon_state = "tile-carpet-red"
item_state = "tile-carpet-red"
+ merge_type = /obj/item/stack/tile/carpet/red
turf_type = /turf/open/floor/carpet/red
tableVariant = /obj/structure/table/wood/fancy/red
@@ -161,6 +243,7 @@
name = "royal black carpet"
icon_state = "tile-carpet-royalblack"
item_state = "tile-carpet-royalblack"
+ merge_type = /obj/item/stack/tile/carpet/royalblack
turf_type = /turf/open/floor/carpet/royalblack
tableVariant = /obj/structure/table/wood/fancy/royalblack
@@ -168,6 +251,7 @@
name = "royal blue carpet"
icon_state = "tile-carpet-royalblue"
item_state = "tile-carpet-royalblue"
+ merge_type = /obj/item/stack/tile/carpet/royalblue
turf_type = /turf/open/floor/carpet/royalblue
tableVariant = /obj/structure/table/wood/fancy/royalblue
@@ -176,6 +260,7 @@
singular_name = "retro floor tile"
desc = "A stack of floor tiles that remind you of simpler times.."
icon_state = "tile_eighties"
+ merge_type = /obj/item/stack/tile/eighties
turf_type = /turf/open/floor/eighties
/obj/item/stack/tile/carpet/fifty
@@ -211,6 +296,9 @@
/obj/item/stack/tile/eighties/fifty
amount = 50
+/obj/item/stack/tile/eighties/loaded
+ amount = 30
+
/obj/item/stack/tile/fakespace
name = "astral carpet"
singular_name = "astral carpet"
@@ -253,7 +341,7 @@
desc = "A high-traction floor tile. It feels rubbery in your hand."
icon_state = "tile_noslip_standard"
turf_type = /turf/open/floor/noslip/standard
- merge_type = /obj/item/stack/tile/noslip
+ merge_type = /obj/item/stack/tile/noslip/standard
/obj/item/stack/tile/noslip/white
name = "high-traction floor tile"
@@ -261,7 +349,7 @@
desc = "A high-traction floor tile. It feels rubbery in your hand."
icon_state = "tile_noslip_white"
turf_type = /turf/open/floor/noslip/white
- merge_type = /obj/item/stack/tile/noslip
+ merge_type = /obj/item/stack/tile/noslip/white
/obj/item/stack/tile/noslip/blue
name = "high-traction floor tile"
@@ -269,7 +357,7 @@
desc = "A high-traction floor tile. It feels rubbery in your hand."
icon_state = "tile_noslip_blue"
turf_type = /turf/open/floor/noslip/blue
- merge_type = /obj/item/stack/tile/noslip
+ merge_type = /obj/item/stack/tile/noslip/blue
/obj/item/stack/tile/noslip/darkblue
name = "high-traction floor tile"
@@ -277,7 +365,7 @@
desc = "A high-traction floor tile. It feels rubbery in your hand."
icon_state = "tile_noslip_darkblue"
turf_type = /turf/open/floor/noslip/darkblue
- merge_type = /obj/item/stack/tile/noslip
+ merge_type = /obj/item/stack/tile/noslip/darkblue
/obj/item/stack/tile/noslip/dark
name = "high-traction floor tile"
@@ -285,7 +373,7 @@
desc = "A high-traction floor tile. It feels rubbery in your hand."
icon_state = "tile_noslip_dark"
turf_type = /turf/open/floor/noslip/dark
- merge_type = /obj/item/stack/tile/noslip
+ merge_type = /obj/item/stack/tile/noslip/dark
/obj/item/stack/tile/noslip/vaporwave
name = "high-traction floor tile"
@@ -293,7 +381,7 @@
desc = "A high-traction floor tile. It feels rubbery in your hand."
icon_state = "tile_noslip_pinkblack"
turf_type = /turf/open/floor/noslip/vaporwave
- merge_type = /obj/item/stack/tile/noslip
+ merge_type = /obj/item/stack/tile/noslip/vaporwave
/obj/item/stack/tile/noslip/thirty
amount = 30
@@ -365,7 +453,7 @@
flags_1 = CONDUCT_1
turf_type = /turf/open/floor/plasteel
mineralType = "iron"
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70, "stamina" = 0)
resistance_flags = FIRE_PROOF
/obj/item/stack/tile/plasteel/cyborg
diff --git a/code/game/objects/items/storage/backpack.dm b/code/game/objects/items/storage/backpack.dm
index 885fd8b89a324..9a9007dc39936 100644
--- a/code/game/objects/items/storage/backpack.dm
+++ b/code/game/objects/items/storage/backpack.dm
@@ -44,7 +44,7 @@
item_state = "holdingpack"
resistance_flags = FIRE_PROOF
item_flags = NO_MAT_REDEMPTION
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 50)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 50, "stamina" = 0)
component_type = /datum/component/storage/concrete/bluespace/bag_of_holding
/obj/item/storage/backpack/holding/clown
@@ -78,7 +78,7 @@
icon_state = "hammerspace"
resistance_flags = FIRE_PROOF
item_flags = NO_MAT_REDEMPTION
- armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100)
+ armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100, "stamina" = 0)
component_type = /datum/component/storage/concrete/bluespace/bag_of_holding
/obj/item/storage/backpack/hammerspace/ComponentInitialize()
@@ -231,6 +231,40 @@
desc = "A spacious backpack with lots of pockets, worn by Engineers of an Emergency Response Team."
icon_state = "ert_engineering"
+/////////////////
+//DONATOR ITEMS//
+/////////////////
+
+/obj/item/storage/backpack/cak
+ name = "cak backpack"
+ desc = "A cute backpack resembling Cak."
+ icon_state = "cakpack"
+ item_state = "cakpack"
+
+/obj/item/storage/backpack/ian
+ name = "ian backpack"
+ desc = "A cute backpack resembling Ian."
+ icon_state = "ianpack"
+ item_state = "ianpack"
+
+/obj/item/storage/backpack/lisa
+ name = "lisa backpack"
+ desc = "A cute backpack resembling Lisa."
+ icon_state = "lisapack"
+ item_state = "lisapack"
+
+/obj/item/storage/backpack/renault
+ name = "renault backpack"
+ desc = "A cute backpack resembling Renault."
+ icon_state = "renaultpack"
+ item_state = "renaultpack"
+
+/obj/item/storage/backpack/runtime
+ name = "runtime backpack"
+ desc = "A cute backpack resembling Runtime."
+ icon_state = "runtimepack"
+ item_state = "runtimepack"
+
/*
* Satchel Types
*/
diff --git a/code/game/objects/items/storage/bags.dm b/code/game/objects/items/storage/bags.dm
index 9aa72bf756adb..96dc9d9265c1f 100644
--- a/code/game/objects/items/storage/bags.dm
+++ b/code/game/objects/items/storage/bags.dm
@@ -88,20 +88,21 @@
STR.max_combined_w_class = 60
STR.max_items = 60
+
/obj/item/storage/bag/trash/bluespace/hammerspace
name = "hammerspace belt"
desc = "A belt that opens into a near infinite pocket of bluespace."
icon_state = "hammerspace"
w_class = WEIGHT_CLASS_GIGANTIC
-/obj/item/storage/bag/trash/bluespace/ComponentInitialize()
+/obj/item/storage/bag/trash/bluespace/hammerspace/ComponentInitialize()
. = ..()
var/datum/component/storage/STR = GetComponent(/datum/component/storage)
STR.max_combined_w_class = 1000
STR.max_items = 300
STR.max_w_class = WEIGHT_CLASS_GIGANTIC
-/obj/item/storage/bag/trash/bluespace/update_icon()
+/obj/item/storage/bag/trash/bluespace/hammerspace/update_icon()
if(contents.len == 0)
icon_state = "[initial(icon_state)]"
else icon_state = "[initial(icon_state)]"
@@ -120,7 +121,7 @@
desc = "This little bugger can be used to store and transport ores."
icon = 'icons/obj/mining.dmi'
icon_state = "satchel"
- slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_POCKET
+ slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_POCKETS
w_class = WEIGHT_CLASS_NORMAL
component_type = /datum/component/storage/concrete/stack
var/spam_protection = FALSE //If this is TRUE, the holder won't receive any messages when they fail to pick up ore through crossing it
@@ -145,10 +146,13 @@
/obj/item/storage/bag/ore/dropped()
. = ..()
- UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED)
- listeningTo = null
+ if(listeningTo)
+ UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED)
+ listeningTo = null
/obj/item/storage/bag/ore/proc/Pickup_ores(mob/living/user)
+ SIGNAL_HANDLER
+
var/show_message = FALSE
var/obj/structure/ore_box/box
var/turf/tile = user.loc
@@ -163,6 +167,7 @@
continue
if (box)
user.transferItemToLoc(A, box)
+ box.ui_update()
show_message = TRUE
else if(SEND_SIGNAL(src, COMSIG_TRY_STORAGE_INSERT, A, user, TRUE))
show_message = TRUE
@@ -231,6 +236,19 @@
for(var/obj/item/O in contents)
seedify(O, 1)
+/obj/item/storage/bag/plants/portaseeder/compact
+ name = "compact portable seed extractor"
+ desc = "Create seeds for your plants in your arm."
+ icon_state = "compactseeder"
+
+/obj/item/storage/bag/plants/portaseeder/compact/ComponentInitialize()
+ . = ..()
+ var/datum/component/storage/STR = GetComponent(/datum/component/storage)
+ STR.max_w_class = WEIGHT_CLASS_NORMAL
+ STR.max_combined_w_class = 10
+ STR.max_items = 3
+ STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/grown, /obj/item/seeds, /obj/item/grown))
+
// -----------------------------
// Sheet Snatcher
// -----------------------------
@@ -243,7 +261,7 @@
icon = 'icons/obj/mining.dmi'
icon_state = "sheetsnatcher"
- var/capacity = 300; //the number of sheets it can carry.
+ var/capacity = 150 //the number of sheets it can carry.
w_class = WEIGHT_CLASS_NORMAL
component_type = /datum/component/storage/concrete/stack
@@ -252,8 +270,7 @@
var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack)
STR.allow_quick_empty = TRUE
STR.can_hold = typecacheof(list(/obj/item/stack/sheet))
- STR.cant_hold = typecacheof(list(/obj/item/stack/sheet/mineral/sandstone, /obj/item/stack/sheet/mineral/wood))
- STR.max_combined_stack_amount = 300
+ STR.max_combined_stack_amount = 150
// -----------------------------
// Sheet Snatcher (Cyborg)
@@ -268,6 +285,7 @@
. = ..()
var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack)
STR.max_combined_stack_amount = 500
+ STR.max_combined_w_class = 30
// -----------------------------
// Book bag
@@ -386,3 +404,20 @@
STR.max_items = 25
STR.insert_preposition = "in"
STR.can_hold = typecacheof(list(/obj/item/slime_extract, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/blood, /obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/food/snacks/deadmouse, /obj/item/reagent_containers/food/snacks/monkeycube, /obj/item/organ, /obj/item/bodypart))
+
+/obj/item/storage/bag/construction
+ name = "construction bag"
+ icon = 'icons/obj/tools.dmi'
+ icon_state = "construction_bag"
+ desc = "A bag for storing small construction components."
+ w_class = WEIGHT_CLASS_TINY
+ resistance_flags = FLAMMABLE
+
+/obj/item/storage/bag/construction/ComponentInitialize()
+ . = ..()
+ var/datum/component/storage/STR = GetComponent(/datum/component/storage)
+ STR.max_combined_w_class = 100
+ STR.max_items = 50
+ STR.max_w_class = WEIGHT_CLASS_SMALL
+ STR.insert_preposition = "in"
+ STR.can_hold = typecacheof(list(/obj/item/stack/ore/bluespace_crystal, /obj/item/assembly, /obj/item/stock_parts, /obj/item/reagent_containers/glass/beaker, /obj/item/stack/cable_coil, /obj/item/circuitboard, /obj/item/electronics))
diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm
index b2890b7780a2a..5b76238fe3d31 100644
--- a/code/game/objects/items/storage/belt.dm
+++ b/code/game/objects/items/storage/belt.dm
@@ -42,6 +42,7 @@
STR.max_combined_w_class = 21
var/static/list/can_hold = typecacheof(list(
/obj/item/crowbar,
+ /obj/item/powertool,
/obj/item/screwdriver,
/obj/item/weldingtool,
/obj/item/wirecutters,
@@ -74,13 +75,13 @@
item_state = "utility_ce"
/obj/item/storage/belt/utility/chief/full/PopulateContents()
- new /obj/item/screwdriver/power(src)
- new /obj/item/crowbar/power(src)
+ new /obj/item/powertool/hand_drill(src)
+ new /obj/item/powertool/jaws_of_life(src)
new /obj/item/weldingtool/experimental(src)//This can be changed if this is too much
new /obj/item/multitool(src)
- new /obj/item/stack/cable_coil(src,30,pick("red","yellow","orange"))
+ new /obj/item/stack/cable_coil(src,MAXCOIL,pick("red","yellow","orange"))
new /obj/item/extinguisher/mini(src)
- new /obj/item/analyzer(src)
+ new /obj/item/analyzer/ranged(src)
//much roomier now that we've managed to remove two tools
/obj/item/storage/belt/utility/full/PopulateContents()
@@ -90,7 +91,7 @@
new /obj/item/crowbar(src)
new /obj/item/wirecutters(src)
new /obj/item/multitool(src)
- new /obj/item/stack/cable_coil(src,30,pick("red","yellow","orange"))
+ new /obj/item/stack/cable_coil(src,MAXCOIL,pick("red","yellow","orange"))
/obj/item/storage/belt/utility/full/engi/PopulateContents()
new /obj/item/screwdriver(src)
@@ -99,7 +100,7 @@
new /obj/item/crowbar(src)
new /obj/item/wirecutters(src)
new /obj/item/multitool(src)
- new /obj/item/stack/cable_coil(src,30,pick("red","yellow","orange"))
+ new /obj/item/stack/cable_coil(src,MAXCOIL,pick("red","yellow","orange"))
/obj/item/storage/belt/utility/atmostech/PopulateContents()
@@ -126,6 +127,7 @@
STR.max_items = 7
var/static/list/can_hold = typecacheof(list(
/obj/item/crowbar,
+ /obj/item/powertool,
/obj/item/screwdriver,
/obj/item/weldingtool,
/obj/item/wirecutters,
@@ -243,7 +245,7 @@
/obj/item/storage/belt/security/ComponentInitialize()
. = ..()
var/datum/component/storage/STR = GetComponent(/datum/component/storage)
- STR.max_items = 6
+ STR.max_items = 5
STR.max_combined_w_class = 18
STR.max_w_class = WEIGHT_CLASS_NORMAL
STR.can_hold = typecacheof(list(
@@ -274,7 +276,6 @@
new /obj/item/grenade/flashbang(src)
new /obj/item/assembly/flash/handheld(src)
new /obj/item/melee/baton/loaded(src)
- new /obj/item/club(src)
update_icon()
/obj/item/storage/belt/security/deputy
@@ -299,7 +300,7 @@
/obj/item/storage/belt/security/webbing/ComponentInitialize()
. = ..()
var/datum/component/storage/STR = GetComponent(/datum/component/storage)
- STR.max_items = 7
+ STR.max_items = 6
STR.max_combined_w_class = 21
/obj/item/storage/belt/mining
@@ -317,6 +318,7 @@
STR.max_combined_w_class = 20
STR.can_hold = typecacheof(list(
/obj/item/crowbar,
+ /obj/item/powertool,
/obj/item/screwdriver,
/obj/item/weldingtool,
/obj/item/wirecutters,
@@ -353,7 +355,13 @@
/obj/item/storage/bag/plants,
/obj/item/stack/marker_beacon,
/obj/item/restraints/legcuffs/bola/watcher,
- /obj/item/claymore/bone
+ /obj/item/claymore/bone,
+ /obj/item/skeleton_key,
+ /obj/item/discovery_scanner,
+ /obj/item/gun/energy/e_gun/mini/exploration,
+ /obj/item/grenade/exploration,
+ /obj/item/exploration_detonator,
+ /obj/item/research_disk_pinpointer
))
@@ -489,7 +497,7 @@
new /obj/item/crowbar/abductor(src)
new /obj/item/wirecutters/abductor(src)
new /obj/item/multitool/abductor(src)
- new /obj/item/stack/cable_coil(src,30,"white")
+ new /obj/item/stack/cable_coil/white(src)
/obj/item/storage/belt/military/army
name = "army belt"
@@ -538,7 +546,7 @@
/obj/item/grenade/smokebomb = 4,
/obj/item/grenade/empgrenade = 1,
/obj/item/grenade/empgrenade = 1,
- /obj/item/grenade/syndieminibomb/concussion/frag = 10,
+ /obj/item/grenade/frag = 10,
/obj/item/grenade/gluon = 4,
/obj/item/grenade/chem_grenade/incendiary = 2,
/obj/item/grenade/chem_grenade/facid = 1,
@@ -603,7 +611,7 @@
/obj/item/melee/flyswatter,
/obj/item/assembly/mousetrap,
/obj/item/paint/paint_remover,
- /obj/item/twohanded/pushbroom
+ /obj/item/pushbroom
))
/obj/item/storage/belt/janitor/full/PopulateContents()
@@ -774,3 +782,30 @@
/obj/item/storage/belt/sabre/PopulateContents()
new /obj/item/melee/sabre(src)
update_icon()
+
+/obj/item/storage/belt/sabre/mime
+ name = "Baguette"
+ desc = "Bon appetit!"
+ icon = 'icons/obj/food/burgerbread.dmi'
+ icon_state = "baguette"
+ item_state = "baguette"
+ w_class = WEIGHT_CLASS_BULKY
+ slot_flags = ITEM_SLOT_BACK|ITEM_SLOT_BELT
+
+/obj/item/storage/belt/sabre/mime/update_icon()
+ icon_state = "baguette"
+ item_state = "baguette"
+
+/obj/item/storage/belt/sabre/mime/ComponentInitialize()
+ . = ..()
+ var/datum/component/storage/MTR = GetComponent(/datum/component/storage)
+ MTR.max_items = 1
+ MTR.rustle_sound = FALSE
+ MTR.max_w_class = WEIGHT_CLASS_BULKY
+ MTR.can_hold = typecacheof(list(
+ /obj/item/melee/sabre/mime
+ ))
+
+/obj/item/storage/belt/sabre/mime/PopulateContents()
+ new /obj/item/melee/sabre/mime(src)
+ update_icon()
diff --git a/code/game/objects/items/storage/book.dm b/code/game/objects/items/storage/book.dm
index 025b8125a6b79..85bbea1ff1791 100644
--- a/code/game/objects/items/storage/book.dm
+++ b/code/game/objects/items/storage/book.dm
@@ -56,7 +56,7 @@ GLOBAL_LIST_INIT(bibleitemstates, list("bible", "koran", "scrapbook", "burning",
if(!H.can_read(src))
return FALSE
// If H is the Chaplain, we can set the icon_state of the bible (but only once!)
- if(!GLOB.bible_icon_state && H.job == "Chaplain")
+ if(!GLOB.bible_icon_state && H.mind.holy_role == HOLY_ROLE_HIGHPRIEST)
var/dat = "Pick Bible Style
Pick a bible style
"
for(var/i in 1 to GLOB.biblestates.len)
var/icon/bibleicon = icon('icons/obj/storage.dmi', GLOB.biblestates[i])
@@ -78,15 +78,21 @@ GLOBAL_LIST_INIT(bibleitemstates, list("bible", "koran", "scrapbook", "burning",
if(icon_state == "honk1" || icon_state == "honk2")
var/mob/living/carbon/human/H = usr
H.dna.add_mutation(CLOWNMUT)
- H.equip_to_slot_or_del(new /obj/item/clothing/mask/gas/clown_hat(H), SLOT_WEAR_MASK)
-
+ H.equip_to_slot_or_del(new /obj/item/clothing/mask/gas/clown_hat(H), ITEM_SLOT_MASK)
GLOB.bible_icon_state = icon_state
GLOB.bible_item_state = item_state
SSblackbox.record_feedback("text", "religion_book", 1, "[biblename]")
usr << browse(null, "window=editicon")
-/obj/item/storage/book/bible/proc/bless(mob/living/carbon/human/H, mob/living/user)
+/obj/item/storage/book/bible/proc/bless(mob/living/L, mob/living/user)
+ if(GLOB.religious_sect)
+ return GLOB.religious_sect.sect_bless(L,user)
+ if(!ishuman(L))
+ return
+
+ var/mob/living/carbon/human/H = L
+
for(var/X in H.bodyparts)
var/obj/item/bodypart/BP = X
if(BP.status == BODYPART_ROBOTIC)
@@ -120,7 +126,7 @@ GLOBAL_LIST_INIT(bibleitemstates, list("bible", "koran", "scrapbook", "burning",
return
var/chaplain = 0
- if(user.mind && (user.mind.isholy))
+ if(user?.mind?.holy_role)
chaplain = 1
if(!chaplain)
@@ -138,7 +144,7 @@ GLOBAL_LIST_INIT(bibleitemstates, list("bible", "koran", "scrapbook", "burning",
to_chat(user, "You can't heal yourself!")
return
- if(ishuman(M) && prob(60) && bless(M, user))
+ if(prob(60) && bless(M, user))
smack = 0
else if(iscarbon(M))
var/mob/living/carbon/C = M
@@ -162,10 +168,10 @@ GLOBAL_LIST_INIT(bibleitemstates, list("bible", "koran", "scrapbook", "burning",
return
if(isfloorturf(A))
to_chat(user, "You hit the floor with the bible.")
- if(user.mind && (user.mind.isholy))
+ if(user?.mind?.holy_role)
for(var/obj/effect/rune/R in orange(2,user))
R.invisibility = 0
- if(user.mind && (user.mind.isholy))
+ if(user?.mind?.holy_role)
if(A.reagents && A.reagents.has_reagent(/datum/reagent/water)) // blesses all the water in the holder
to_chat(user, "You bless [A].")
var/water2holy = A.reagents.get_reagent_amount(/datum/reagent/water)
@@ -182,8 +188,8 @@ GLOBAL_LIST_INIT(bibleitemstates, list("bible", "koran", "scrapbook", "burning",
B.name = name
B.icon_state = icon_state
B.item_state = item_state
- if(istype(A, /obj/item/twohanded/required/cult_bastard) && !iscultist(user))
- var/obj/item/twohanded/required/cult_bastard/sword = A
+ if(istype(A, /obj/item/cult_bastard) && !iscultist(user))
+ var/obj/item/cult_bastard/sword = A
to_chat(user, "You begin to exorcise [sword].")
playsound(src,'sound/hallucinations/veryfar_noise.ogg',40,1)
if(do_after(user, 40, target = sword))
@@ -242,7 +248,7 @@ GLOBAL_LIST_INIT(bibleitemstates, list("bible", "koran", "scrapbook", "burning",
/obj/item/storage/book/bible/syndicate/attack_self(mob/living/carbon/human/H)
if (uses)
- H.mind.isholy = TRUE
+ H.mind.holy_role = HOLY_ROLE_PRIEST
uses -= 1
to_chat(H, "You try to open the book AND IT BITES YOU!")
playsound(src.loc, 'sound/effects/snap.ogg', 50, 1)
diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm
index de471ffa592e0..3fdda8757a29e 100644
--- a/code/game/objects/items/storage/boxes.dm
+++ b/code/game/objects/items/storage/boxes.dm
@@ -1,4 +1,4 @@
- /*
+/*
* Everything derived from the common cardboard box.
* Basically everything except the original is a kit (starts full).
*
@@ -42,7 +42,7 @@
user.visible_message("[user] puts [user.p_their()] head into \the [src], and begins closing it! It looks like [user.p_theyre()] trying to commit suicide!")
myhead.dismember()
myhead.forceMove(src)//force your enemies to kill themselves with your head collection box!
- playsound(user,pick('sound/misc/desceration-01.ogg','sound/misc/desceration-02.ogg','sound/misc/desceration-01.ogg') ,50, 1, -1)
+ playsound(user,pick('sound/misc/desecration-01.ogg','sound/misc/desecration-02.ogg','sound/misc/desecration-01.ogg') ,50, 1, -1)
return BRUTELOSS
user.visible_message("[user] beating [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
return BRUTELOSS
@@ -56,7 +56,7 @@
/obj/item/storage/box/attack_self(mob/user)
..()
- if(!foldable)
+ if(!foldable || (flags_1 & HOLOGRAM_1))
return
if(contents.len)
to_chat(user, "You can't fold this box with items still inside!")
@@ -125,14 +125,16 @@
/obj/item/storage/box/survival/PopulateContents()
new /obj/item/clothing/mask/breath(src)
new /obj/item/reagent_containers/hypospray/medipen(src)
- new /obj/item/clothing/head/helmet/space/skinsuit(src)
- new /obj/item/clothing/suit/space/skinsuit(src)
if(!isplasmaman(loc))
new /obj/item/tank/internals/emergency_oxygen(src)
else
new /obj/item/tank/internals/plasmaman/belt(src)
+ if(HAS_TRAIT(SSstation, STATION_TRAIT_PREMIUM_INTERNALS))
+ new /obj/item/flashlight/flare(src)
+ new /obj/item/radio/off(src)
+
/obj/item/storage/box/survival/radio/PopulateContents()
..() // we want the survival stuff too.
new /obj/item/radio/off(src)
@@ -142,8 +144,6 @@
new /obj/item/clothing/mask/gas/explorer(src)
new /obj/item/crowbar/red(src)
new /obj/item/reagent_containers/hypospray/medipen(src)
- new /obj/item/clothing/head/helmet/space/skinsuit(src)
- new /obj/item/clothing/suit/space/skinsuit(src)
if(!isplasmaman(loc))
new /obj/item/tank/internals/emergency_oxygen(src)
@@ -154,8 +154,6 @@
/obj/item/storage/box/engineer/PopulateContents()
new /obj/item/clothing/mask/breath(src)
new /obj/item/reagent_containers/hypospray/medipen(src)
- new /obj/item/clothing/head/helmet/space/skinsuit(src)
- new /obj/item/clothing/suit/space/skinsuit(src)
if(!isplasmaman(loc))
new /obj/item/tank/internals/emergency_oxygen/engi(src)
@@ -179,8 +177,6 @@
/obj/item/storage/box/security/PopulateContents()
new /obj/item/clothing/mask/gas/sechailer(src)
new /obj/item/reagent_containers/hypospray/medipen(src)
- new /obj/item/clothing/head/helmet/space/skinsuit(src)
- new /obj/item/clothing/suit/space/skinsuit(src)
if(!isplasmaman(loc))
new /obj/item/tank/internals/emergency_oxygen(src)
@@ -224,7 +220,7 @@
/obj/item/storage/box/syringes/variety/PopulateContents()
new /obj/item/reagent_containers/syringe(src)
new /obj/item/reagent_containers/syringe/lethal(src)
- new /obj/item/reagent_containers/syringe/noreact(src)
+ new /obj/item/reagent_containers/syringe/cryo(src)
new /obj/item/reagent_containers/syringe/piercing(src)
new /obj/item/reagent_containers/syringe/bluespace(src)
@@ -257,7 +253,7 @@
/obj/item/storage/box/beakers/bluespace
name = "box of bluespace beakers"
- illustration = "beaker"
+ illustration = "bbeaker"
/obj/item/storage/box/beakers/bluespace/PopulateContents()
for(var/i in 1 to 7)
@@ -285,6 +281,7 @@
/obj/item/storage/box/injectors
name = "box of DNA injectors"
desc = "This box contains injectors, it seems."
+ illustration = "dna"
/obj/item/storage/box/injectors/PopulateContents()
var/static/items_inside = list(
@@ -306,7 +303,7 @@
name = "box of flashbulbs"
desc = "WARNING: Flashes can cause serious eye damage, protective eyewear is required."
icon_state = "secbox"
- illustration = "flashbang"
+ illustration = "flash"
/obj/item/storage/box/flashes/PopulateContents()
for(var/i in 1 to 2)
@@ -314,10 +311,21 @@
for(var/i in 1 to 6)
new /obj/item/flashbulb(src)
+/obj/item/storage/box/stingbangs
+ name = "box of stingbangs (WARNING)"
+ desc = "WARNING: These devices are extremely dangerous and can cause severe injuries or death in repeated use."
+ icon_state = "secbox"
+ illustration = "flashbang"
+
+/obj/item/storage/box/stingbangs/PopulateContents()
+ for(var/i in 1 to 5)
+ new /obj/item/grenade/stingbang(src)
+
/obj/item/storage/box/wall_flash
name = "wall-mounted flash kit"
desc = "This box contains everything necessary to build a wall-mounted flash. WARNING: Flashes can cause serious eye damage, protective eyewear is required."
- illustration = "flashbang"
+ icon_state = "secbox"
+ illustration = "flash"
/obj/item/storage/box/wall_flash/PopulateContents()
var/id = rand(1000, 9999)
@@ -336,7 +344,8 @@
/obj/item/storage/box/teargas
name = "box of tear gas grenades (WARNING)"
desc = "WARNING: These devices are extremely dangerous and can cause blindness and skin irritation."
- illustration = "flashbang"
+ icon_state = "secbox"
+ illustration = "grenade"
/obj/item/storage/box/teargas/PopulateContents()
for(var/i in 1 to 7)
@@ -345,7 +354,7 @@
/obj/item/storage/box/emps
name = "box of emp grenades"
desc = "A box with 5 emp grenades."
- illustration = "flashbang"
+ illustration = "emp"
/obj/item/storage/box/emps/PopulateContents()
for(var/i in 1 to 5)
@@ -354,6 +363,7 @@
/obj/item/storage/box/trackimp
name = "boxed tracking implant kit"
desc = "Box full of scum-bag tracking utensils."
+ icon_state = "secbox"
illustration = "implant"
/obj/item/storage/box/trackimp/PopulateContents()
@@ -422,6 +432,7 @@
/obj/item/storage/box/drinkingglasses
name = "box of drinking glasses"
desc = "It has a picture of drinking glasses on it."
+ illustration = "drinkglass"
/obj/item/storage/box/drinkingglasses/PopulateContents()
for(var/i in 1 to 6)
@@ -430,6 +441,7 @@
/obj/item/storage/box/condimentbottles
name = "box of condiment bottles"
desc = "It has a large ketchup smear on it."
+ illustration = "condiment"
/obj/item/storage/box/condimentbottles/PopulateContents()
for(var/i in 1 to 6)
@@ -438,6 +450,7 @@
/obj/item/storage/box/cups
name = "box of paper cups"
desc = "It has pictures of paper cups on the front."
+ illustration = "cup"
/obj/item/storage/box/cups/PopulateContents()
for(var/i in 1 to 7)
@@ -568,6 +581,7 @@
/obj/item/storage/box/prisoner
name = "box of prisoner IDs"
desc = "Take away their last shred of dignity, their name."
+ icon_state = "secbox"
illustration = "id"
/obj/item/storage/box/prisoner/PopulateContents()
@@ -583,6 +597,7 @@
/obj/item/storage/box/seccarts
name = "box of PDA security cartridges"
desc = "A box full of PDA cartridges used by Security."
+ icon_state = "secbox"
illustration = "pda"
/obj/item/storage/box/seccarts/PopulateContents()
@@ -593,7 +608,8 @@
/obj/item/storage/box/firingpins
name = "box of standard firing pins"
desc = "A box full of standard firing pins, to allow newly-developed firearms to operate."
- illustration = "id"
+ icon_state = "secbox"
+ illustration = "firingpin"
/obj/item/storage/box/firingpins/PopulateContents()
for(var/i in 1 to 5)
@@ -602,7 +618,7 @@
/obj/item/storage/box/firingpins/paywall
name = "box of paywall firing pins"
desc = "A box full of paywall firing pins, to allow newly-developed firearms to operate behind a custom-set paywall."
- illustration = "id"
+ illustration = "firingpin"
/obj/item/storage/box/firingpins/paywall/PopulateContents()
for(var/i in 1 to 5)
@@ -611,7 +627,7 @@
/obj/item/storage/box/lasertagpins
name = "box of laser tag firing pins"
desc = "A box full of laser tag firing pins, to allow newly-developed firearms to require wearing brightly coloured plastic armor before being able to be used."
- illustration = "id"
+ illustration = "firingpin"
/obj/item/storage/box/lasertagpins/PopulateContents()
for(var/i in 1 to 3)
@@ -652,6 +668,7 @@
name = "boxed space suit and helmet"
desc = "A sleek, sturdy box used to hold replica spacesuits."
icon_state = "syndiebox"
+ illustration = "syndiesuit"
/obj/item/storage/box/fakesyndiesuit/PopulateContents()
new /obj/item/clothing/head/syndicatefake(src)
@@ -753,20 +770,10 @@
for(var/i in 1 to 7)
new /obj/item/light/bulb(src)
-
-/obj/item/storage/box/deputy
- name = "box of deputy armbands"
- desc = "To be issued to those authorized to act as deputy of security."
-
-/obj/item/storage/box/deputy/PopulateContents()
- for(var/i in 1 to 4) //not too many
- new /obj/item/clothing/accessory/armband/deputy(src)
- new /obj/item/card/deputy_access_card(src)
-
/obj/item/storage/box/metalfoam
name = "box of metal foam grenades"
desc = "To be used to rapidly seal hull breaches."
- illustration = "flashbang"
+ illustration = "grenade"
/obj/item/storage/box/metalfoam/PopulateContents()
for(var/i in 1 to 7)
@@ -775,7 +782,7 @@
/obj/item/storage/box/smart_metal_foam
name = "box of smart metal foam grenades"
desc = "Used to rapidly seal hull breaches. This variety conforms to the walls of its area."
- illustration = "flashbang"
+ illustration = "grenade"
/obj/item/storage/box/smart_metal_foam/PopulateContents()
for(var/i in 1 to 7)
@@ -831,10 +838,14 @@
new /obj/item/reagent_containers/hypospray/medipen(src)
if(!isplasmaman(loc))
- new /obj/item/tank/internals/emergency_oxygen(src)
+ new /obj/item/tank/internals/emergency_oxygen/clown(src)
else
new /obj/item/tank/internals/plasmaman/belt(src)
+ if(HAS_TRAIT(SSstation, STATION_TRAIT_PREMIUM_INTERNALS))
+ new /obj/item/flashlight/flare(src)
+ new /obj/item/radio/off(src)
+
/obj/item/storage/box/rubbershot
name = "box of rubber shots"
desc = "A box full of rubber shots, designed for riot shotguns."
@@ -938,12 +949,12 @@
else if(W.is_sharp())
if(!contents.len)
if(item_state == "paperbag_None")
- user.show_message("You cut eyeholes into [src].", 1)
+ user.show_message("You cut eyeholes into [src].", MSG_VISUAL)
new /obj/item/clothing/head/papersack(user.loc)
qdel(src)
return 0
else if(item_state == "paperbag_SmileyFace")
- user.show_message("You cut eyeholes into [src] and modify the design.", 1)
+ user.show_message("You cut eyeholes into [src] and modify the design.", MSG_VISUAL)
new /obj/item/clothing/head/papersack/smiley(user.loc)
qdel(src)
return 0
@@ -1097,6 +1108,7 @@
/obj/item/storage/box/emptysandbags
name = "box of empty sandbags"
+ illustration = "sandbag"
/obj/item/storage/box/emptysandbags/PopulateContents()
for(var/i in 1 to 7)
@@ -1105,6 +1117,7 @@
/obj/item/storage/box/rndboards
name = "\proper the liberator's legacy"
desc = "A box containing a gift for worthy golems."
+ illustration = "scicircuit"
/obj/item/storage/box/rndboards/PopulateContents()
new /obj/item/circuitboard/machine/protolathe(src)
@@ -1115,6 +1128,7 @@
/obj/item/storage/box/silver_sulf
name = "box of silver sulfadiazine patches"
desc = "Contains patches used to treat burns."
+ illustration = "firepatch"
/obj/item/storage/box/silver_sulf/PopulateContents()
for(var/i in 1 to 7)
@@ -1122,6 +1136,7 @@
/obj/item/storage/box/fountainpens
name = "box of fountain pens"
+ illustration = "fpen"
/obj/item/storage/box/fountainpens/PopulateContents()
for(var/i in 1 to 7)
@@ -1130,7 +1145,7 @@
/obj/item/storage/box/holy_grenades
name = "box of holy hand grenades"
desc = "Contains several grenades used to rapidly purge heresy."
- illustration = "flashbang"
+ illustration = "grenade"
/obj/item/storage/box/holy_grenades/PopulateContents()
for(var/i in 1 to 7)
@@ -1183,12 +1198,22 @@
name = "box of materials"
illustration = "implant"
+/obj/item/storage/box/material/ComponentInitialize()
+ . = ..()
+ var/datum/component/storage/STR = GetComponent(/datum/component/storage)
+ STR.max_combined_w_class = 1000
+ STR.max_w_class = WEIGHT_CLASS_GIGANTIC
+ STR.max_items = 1000
+ STR.allow_big_nesting = TRUE
+
/obj/item/storage/box/material/PopulateContents()
var/static/items_inside = list(
/obj/item/stack/sheet/iron/fifty=1, \
/obj/item/stack/sheet/glass/fifty=1,\
/obj/item/stack/sheet/rglass=50,\
+ /obj/item/stack/sheet/mineral/copper/fifty=1,\
/obj/item/stack/sheet/plasmaglass=50,\
+ /obj/item/stack/sheet/plasmarglass=50,\
/obj/item/stack/sheet/titaniumglass=50,\
/obj/item/stack/sheet/plastitaniumglass=50,\
/obj/item/stack/sheet/plasteel=50,\
@@ -1201,9 +1226,18 @@
/obj/item/stack/sheet/mineral/diamond=50,\
/obj/item/stack/sheet/bluespace_crystal=50,\
/obj/item/stack/sheet/mineral/bananium=50,\
- /obj/item/stack/sheet/mineral/wood=50,\
/obj/item/stack/sheet/plastic/fifty=1,\
- /obj/item/stack/sheet/runed_metal/fifty=1
+ /obj/item/stack/sheet/runed_metal/fifty=1,\
+ /obj/item/stack/tile/brass/fifty=1,\
+ /obj/item/stack/sheet/mineral/abductor=50,\
+ /obj/item/stack/sheet/mineral/adamantine=50,\
+ /obj/item/stack/sheet/mineral/wood=50,\
+ /obj/item/stack/sheet/cotton/cloth=50,\
+ /obj/item/stack/sheet/leather=50,\
+ /obj/item/stack/sheet/bone=12,\
+ /obj/item/stack/sheet/cardboard/fifty=1,\
+ /obj/item/stack/sheet/mineral/sandstone=50,\
+ /obj/item/stack/sheet/mineral/snow=50
)
generate_items_inside(items_inside,src)
@@ -1211,27 +1245,48 @@
name = "box of debug tools"
icon_state = "syndiebox"
+/obj/item/storage/box/debugtools/ComponentInitialize()
+ . = ..()
+ var/datum/component/storage/STR = GetComponent(/datum/component/storage)
+ STR.max_combined_w_class = 1000
+ STR.max_w_class = WEIGHT_CLASS_GIGANTIC
+ STR.max_items = 1000
+ STR.allow_big_nesting = TRUE
+
/obj/item/storage/box/debugtools/PopulateContents()
var/static/items_inside = list(
/obj/item/flashlight/emp/debug=1,\
/obj/item/pda=1,\
/obj/item/modular_computer/tablet/preset/advanced=1,\
+ /obj/item/storage/belt/military/abductor/full=1,\
/obj/item/geiger_counter=1,\
+ /obj/item/holosign_creator/atmos=1,\
/obj/item/pipe_dispenser=1,\
/obj/item/construction/rcd/combat/admin=1,\
+ /obj/item/areaeditor/blueprints=1,\
/obj/item/card/emag=1,\
- /obj/item/card/id/syndicate/nuke_leader=1,\
- /obj/item/card/id/departmental_budget/car=1,\
/obj/item/stack/spacecash/c1000=50,\
/obj/item/healthanalyzer/advanced=1,\
/obj/item/disk/tech_disk/debug=1,\
/obj/item/disk/surgery/debug=1,\
+ /obj/item/disk/data/debug=1,\
/obj/item/uplink/debug=1,\
/obj/item/uplink/nuclear/debug=1,\
+ /obj/item/spellbook=1,\
/obj/item/storage/box/beakers/bluespace=1,\
/obj/item/storage/box/beakers/variety=1,\
- /obj/item/storage/box/material=1,\
- /obj/item/storage/box/beakers/bluespace=1,\
- /obj/item/storage/box/beakers/variety=1
+ /obj/item/storage/box/material=1
)
generate_items_inside(items_inside,src)
+
+/obj/item/storage/box/deputy
+ name = "box of deputy armbands"
+ desc = "To be issued to those authorized to act as deputy of security."
+ icon_state = "secbox"
+ illustration = "depband"
+
+/obj/item/storage/box/deputy/PopulateContents()
+ for(var/i in 1 to 4)
+ new /obj/item/clothing/accessory/armband/deputy(src)
+ new /obj/item/card/id/pass/deputy(src)
+
diff --git a/code/game/objects/items/storage/fancy.dm b/code/game/objects/items/storage/fancy.dm
index 5bbabc4e2600d..d43f8f6f4e3a6 100644
--- a/code/game/objects/items/storage/fancy.dm
+++ b/code/game/objects/items/storage/fancy.dm
@@ -150,14 +150,14 @@
/obj/item/storage/fancy/cigarettes/AltClick(mob/living/carbon/user)
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
return
- var/obj/item/clothing/mask/cigarette/W = locate(/obj/item/clothing/mask/cigarette) in contents
- if(W && contents.len > 0)
- SEND_SIGNAL(src, COMSIG_TRY_STORAGE_TAKE, W, user)
- user.put_in_hands(W)
- contents -= W
- to_chat(user, "You take \a [W] out of the pack.")
+ var/obj/item/I = locate(/obj/item) in contents
+ if(I && contents.len > 0)
+ SEND_SIGNAL(src, COMSIG_TRY_STORAGE_TAKE, I, user)
+ user.put_in_hands(I)
+ contents -= I
+ to_chat(user, "You take \a [I] out of the pack.")
else
- to_chat(user, "There are no [icon_type]s left in the pack.")
+ to_chat(user, "There is nothing left in the pack.")
/obj/item/storage/fancy/cigarettes/update_icon()
if(fancy_open || !contents.len)
@@ -187,18 +187,15 @@
/obj/item/storage/fancy/cigarettes/attack(mob/living/carbon/M as mob, mob/living/carbon/user as mob)
if(!ismob(M))
return
- var/obj/item/clothing/mask/cigarette/cig = locate(/obj/item/clothing/mask/cigarette) in contents
- if(cig)
- if(M == user && contents.len > 0 && !user.wear_mask)
- var/obj/item/clothing/mask/cigarette/W = cig
- SEND_SIGNAL(src, COMSIG_TRY_STORAGE_TAKE, W, M)
- M.equip_to_slot_if_possible(W, SLOT_WEAR_MASK)
- contents -= W
- to_chat(user, "You take \a [W] out of the pack.")
- else
- ..()
- else
- to_chat(user, "There are no [icon_type]s left in the pack.")
+
+ var/obj/item/clothing/mask/cigarette/W = locate(/obj/item/clothing/mask/cigarette) in contents
+ if(!W)
+ return ..()
+ if(M == user && contents.len > 0 && !user.wear_mask)
+ SEND_SIGNAL(src, COMSIG_TRY_STORAGE_TAKE, W, M)
+ M.equip_to_slot_if_possible(W, ITEM_SLOT_MASK)
+ contents -= W
+ to_chat(user, "You take \a [W] out of the pack.")
/obj/item/storage/fancy/cigarettes/dromedaryco
name = "\improper DromedaryCo packet"
diff --git a/code/game/objects/items/storage/firstaid.dm b/code/game/objects/items/storage/firstaid.dm
index f194f1219db68..2a0eb398519d5 100644
--- a/code/game/objects/items/storage/firstaid.dm
+++ b/code/game/objects/items/storage/firstaid.dm
@@ -147,8 +147,8 @@
if(empty)
return
var/static/items_inside = list(
- /obj/item/reagent_containers/pill/patch/silver_sulf = 3,
- /obj/item/reagent_containers/pill/oxandrolone = 2,
+ /obj/item/reagent_containers/pill/patch/silver_sulf = 4,
+ /obj/item/storage/pill_bottle/kelotane = 1,
/obj/item/reagent_containers/hypospray/medipen = 1,
/obj/item/healthanalyzer = 1)
generate_items_inside(items_inside,src)
@@ -166,16 +166,16 @@
/obj/item/storage/firstaid/toxin/Initialize(mapload)
. = ..()
- icon_state = pick("antitoxin","antitoxfirstaid","antitoxfirstaid2","antitoxfirstaid3")
+ icon_state = pick("antitoxin","antitoxin2")
/obj/item/storage/firstaid/toxin/PopulateContents()
if(empty)
return
var/static/items_inside = list(
- /obj/item/reagent_containers/syringe/antitoxin = 2,
+ /obj/item/reagent_containers/syringe/antitoxin = 3,
/obj/item/reagent_containers/syringe/calomel = 1,
/obj/item/reagent_containers/syringe/diphenhydramine = 1,
- /obj/item/storage/pill_bottle/charcoal = 2,
+ /obj/item/storage/pill_bottle/charcoal = 1,
/obj/item/healthanalyzer = 1)
generate_items_inside(items_inside,src)
@@ -207,7 +207,7 @@
/obj/item/storage/firstaid/o2
name = "oxygen deprivation treatment kit"
desc = "A box full of oxygen goodies."
- icon_state = "o2"
+ icon_state = "o2firstaid"
item_state = "firstaid-o2"
damagetype_healed = OXY
@@ -241,6 +241,7 @@
return
var/static/items_inside = list(
/obj/item/reagent_containers/pill/patch/styptic = 4,
+ /obj/item/storage/pill_bottle/bicaridine = 1,
/obj/item/stack/medical/gauze = 2,
/obj/item/healthanalyzer = 1)
generate_items_inside(items_inside,src)
@@ -248,8 +249,8 @@
/obj/item/storage/firstaid/advanced
name = "advanced first aid kit"
desc = "An advanced kit to help deal with advanced wounds."
- icon_state = "radfirstaid"
- item_state = "firstaid-rad"
+ icon_state = "advfirstaid"
+ item_state = "firstaid-adv"
custom_premium_price = 600
/obj/item/storage/firstaid/advanced/PopulateContents()
@@ -265,7 +266,11 @@
/obj/item/storage/firstaid/tactical
name = "combat medical kit"
desc = "I hope you've got insurance."
- icon_state = "bezerk"
+ item_state = "firstaid-combat"
+
+/obj/item/storage/firstaid/tactical/Initialize(mapload)
+ . = ..()
+ icon_state = pick("combatfirstaid","combatfirstaid2")
/obj/item/storage/firstaid/tactical/ComponentInitialize()
. = ..()
@@ -317,12 +322,19 @@
/obj/item/storage/pill_bottle
name = "pill bottle"
desc = "It's an airtight container for storing medication."
- icon_state = "pill_canister"
+ icon_state = "pill_canister_0"
icon = 'icons/obj/chemical.dmi'
item_state = "contsolid"
lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
w_class = WEIGHT_CLASS_SMALL
+ var/pill_variance = 100 //probability pill_bottle has a different icon state. Put at 0 for no variance
+ var/pill_type = "pill_canister_"
+
+/obj/item/storage/pill_bottle/Initialize()
+ . = ..()
+ if(prob(pill_variance))
+ icon_state = "[pill_type][rand(0,6)]"
/obj/item/storage/pill_bottle/ComponentInitialize()
. = ..()
@@ -343,6 +355,22 @@
for(var/i in 1 to 7)
new /obj/item/reagent_containers/pill/charcoal(src)
+/obj/item/storage/pill_bottle/bicaridine
+ name = "bottle of bicaridine pills"
+ desc = "Contains pills used to treat moderate to small brute injuries."
+
+/obj/item/storage/pill_bottle/bicaridine/PopulateContents()
+ for(var/i in 1 to 7)
+ new /obj/item/reagent_containers/pill/bicaridine(src)
+
+/obj/item/storage/pill_bottle/kelotane
+ name = "bottle of kelotane pills"
+ desc = "Contains pills used to treat moderate to small burns."
+
+/obj/item/storage/pill_bottle/kelotane/PopulateContents()
+ for(var/i in 1 to 7)
+ new /obj/item/reagent_containers/pill/kelotane(src)
+
/obj/item/storage/pill_bottle/antirad
name = "bottle of anti-radiation pills"
desc = "Contains pills used to treat the effects of minor radiation."
@@ -375,6 +403,13 @@
for(var/i in 1 to 7)
new /obj/item/reagent_containers/pill/mannitol(src)
+/obj/item/storage/pill_bottle/mannitol/braintumor //For the brain tumor quirk
+ desc = "Generously supplied by your Nanotrasen health insurance to treat that pesky tumor in your brain."
+
+/obj/item/storage/pill_bottle/mannitol/braintumor/PopulateContents()
+ for(var/i in 1 to 3)
+ new /obj/item/reagent_containers/pill/mannitol/braintumor(src)
+
/obj/item/storage/pill_bottle/stimulant
name = "bottle of stimulant pills"
desc = "Guaranteed to give you that extra burst of energy during a long shift!"
diff --git a/code/game/objects/items/storage/lockbox.dm b/code/game/objects/items/storage/lockbox.dm
index 1c4981ab81048..52257ab333cae 100644
--- a/code/game/objects/items/storage/lockbox.dm
+++ b/code/game/objects/items/storage/lockbox.dm
@@ -115,7 +115,6 @@
if(!SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED))
open = (open ? FALSE : TRUE)
update_icon()
- ..()
/obj/item/storage/lockbox/medal/PopulateContents()
new /obj/item/clothing/accessory/medal/gold/captain(src)
@@ -176,7 +175,7 @@
/obj/item/storage/lockbox/medal/service/PopulateContents()
new /obj/item/clothing/accessory/medal/silver/excellence(src)
-
+
/obj/item/storage/lockbox/medal/sci
name = "science medal box"
desc = "A locked box used to store medals to be given to members of the science department."
diff --git a/code/game/objects/items/storage/secure.dm b/code/game/objects/items/storage/secure.dm
index 776e585b60983..ba7b05c3e2149 100644
--- a/code/game/objects/items/storage/secure.dm
+++ b/code/game/objects/items/storage/secure.dm
@@ -21,6 +21,7 @@
var/l_setshort = 0
var/l_hacking = 0
var/open = FALSE
+ var/can_hack_open = TRUE
w_class = WEIGHT_CLASS_NORMAL
desc = "This shouldn't exist. If it does, create an issue report."
@@ -32,33 +33,34 @@
/obj/item/storage/secure/examine(mob/user)
. = ..()
- . += "The service panel is currently [open ? "unscrewed" : "screwed shut"]."
+ if(can_hack_open)
+ . += "The service panel is currently [open ? "unscrewed" : "screwed shut"]."
/obj/item/storage/secure/attackby(obj/item/W, mob/user, params)
- if(SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED))
+ if(can_hack_open && SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED))
if (W.tool_behaviour == TOOL_SCREWDRIVER)
if (W.use_tool(src, user, 20))
- open =! open
+ open = !open
to_chat(user, "You [open ? "open" : "close"] the service panel.")
return
if (W.tool_behaviour == TOOL_WIRECUTTER)
to_chat(user, "[src] is protected from this sort of tampering, yet it appears the internal memory wires can still be pulsed.")
- if ((W.tool_behaviour == TOOL_MULTITOOL) && (!l_hacking))
- if(open == 1)
+ return
+ if ((W.tool_behaviour == TOOL_MULTITOOL))
+ if(l_hacking)
+ to_chat(user, "This safe is already being hacked.")
+ return
+ if(open)
to_chat(user, "Now attempting to reset internal memory, please hold.")
- l_hacking = 1
+ l_hacking = TRUE
if (W.use_tool(src, user, 400))
to_chat(user, "Internal memory reset - lock has been disengaged.")
- l_set = 0
- l_hacking = 0
- else
- l_hacking = 0
- else
- to_chat(user, "You must unscrew the service panel before you can pulse the wiring.")
+ l_set = FALSE
+
+ l_hacking = FALSE
+ return
+ to_chat(user, "You must unscrew the service panel before you can pulse the wiring.")
return
- //At this point you have exhausted all the special things to do when locked
- // ... but it's still locked.
- return
// -> storage/attackby() what with handle insertion, etc
return ..()
@@ -105,7 +107,7 @@
if (length(code) > 5)
code = "ERROR"
add_fingerprint(usr)
- for(var/mob/M in viewers(1, loc))
+ for(var/mob/M as() in viewers(1, get_turf(src)))
if ((M.client && M.machine == src))
attack_self(M)
return
@@ -165,6 +167,7 @@
icon_sparking = "safespark"
desc = "Excellent for securing things away from grubby hands."
force = 8
+ layer = ABOVE_WINDOW_LAYER
w_class = WEIGHT_CLASS_GIGANTIC
anchored = TRUE
density = FALSE
@@ -187,3 +190,34 @@
/obj/item/storage/secure/safe/HoS
name = "head of security's safe"
+
+/**
+ * This safe is meant to be damn robust. To break in, you're supposed to get creative, or use acid or an explosion.
+ *
+ * This makes the safe still possible to break in for someone who is prepared and capable enough, either through
+ * chemistry, botany or whatever else.
+ *
+ * The safe is also weak to explosions, so spending some early TC could allow an antag to blow it upen if they can
+ * get access to it.
+ */
+/obj/item/storage/secure/safe/caps_spare
+ name = "captain's spare ID safe"
+ desc = "In case of emergency, do not break glass. All Captains and Acting Captains are provided with codes to access this safe. \
+It is made out of the same material as the station's Black Box and is designed to resist all conventional weaponry. \
+There appears to be a small amount of surface corrosion. It doesn't look like it could withstand much of an explosion."
+ can_hack_open = FALSE
+ armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 70, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 70);
+ max_integrity = 300
+ color = "#ffdd33"
+
+/obj/item/storage/secure/safe/caps_spare/Initialize()
+ . = ..()
+ l_code = SSjob.spare_id_safe_code
+ l_set = TRUE
+ SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, TRUE)
+
+/obj/item/storage/secure/safe/caps_spare/PopulateContents()
+ new /obj/item/card/id/captains_spare(src)
+
+/obj/item/storage/secure/safe/caps_spare/rust_heretic_act()
+ take_damage(damage_amount = 100, damage_type = BRUTE, damage_flag = "melee", armour_penetration = 100)
diff --git a/code/game/objects/items/storage/storage.dm b/code/game/objects/items/storage/storage.dm
index 498baa9f0ffed..4688b33caa89e 100644
--- a/code/game/objects/items/storage/storage.dm
+++ b/code/game/objects/items/storage/storage.dm
@@ -19,9 +19,14 @@
return FALSE
/obj/item/storage/contents_explosion(severity, target)
- for(var/atom/A in contents)
- A.ex_act(severity, target)
- CHECK_TICK
+ for(var/thing in contents)
+ switch(severity)
+ if(EXPLODE_DEVASTATE)
+ SSexplosions.high_mov_atom += thing
+ if(EXPLODE_HEAVY)
+ SSexplosions.med_mov_atom += thing
+ if(EXPLODE_LIGHT)
+ SSexplosions.low_mov_atom += thing
/obj/item/storage/canStrip(mob/who)
. = ..()
diff --git a/code/game/objects/items/storage/toolbox.dm b/code/game/objects/items/storage/toolbox.dm
index 9cd38bc4c10dd..d56227a351b23 100644
--- a/code/game/objects/items/storage/toolbox.dm
+++ b/code/game/objects/items/storage/toolbox.dm
@@ -138,12 +138,12 @@
new /obj/item/wirecutters(src)
new /obj/item/t_scanner(src)
new /obj/item/crowbar(src)
- new /obj/item/stack/cable_coil(src,30,pickedcolor)
- new /obj/item/stack/cable_coil(src,30,pickedcolor)
+ new /obj/item/stack/cable_coil(src,MAXCOIL,pickedcolor)
+ new /obj/item/stack/cable_coil(src,MAXCOIL,pickedcolor)
if(prob(5))
new /obj/item/clothing/gloves/color/yellow(src)
else
- new /obj/item/stack/cable_coil(src,30,pickedcolor)
+ new /obj/item/stack/cable_coil(src,MAXCOIL,pickedcolor)
/obj/item/storage/toolbox/syndicate
name = "suspicious looking toolbox"
@@ -179,7 +179,7 @@
new /obj/item/wrench(src)
new /obj/item/weldingtool(src)
new /obj/item/crowbar(src)
- new /obj/item/stack/cable_coil(src,30,pickedcolor)
+ new /obj/item/stack/cable_coil(src,MAXCOIL,pickedcolor)
new /obj/item/wirecutters(src)
new /obj/item/multitool(src)
diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm
index 98215dc072009..34e5f61ad20d5 100644
--- a/code/game/objects/items/storage/uplink_kits.dm
+++ b/code/game/objects/items/storage/uplink_kits.dm
@@ -1,20 +1,23 @@
+/obj/item/storage/box/syndie_kit
+ name = "box"
+ desc = "A sleek, sturdy box."
+ icon_state = "syndiebox"
+ illustration = "writing_syndie"
-/obj/item/storage/box/syndicate
-
-/obj/item/storage/box/syndicate/bundle_A/PopulateContents()
+/obj/item/storage/box/syndie_kit/bundle_A/PopulateContents()
switch (pickweight(list("recon" = 2, "bloodyspai" = 3, "stealth" = 2, "screwed" = 2, "sabotage" = 3, "guns" = 2, "murder" = 2, "implant" = 1, "hacker" = 3, "sniper" = 1, "metaops" = 1)))
- if("recon")
+ if("recon")
new /obj/item/clothing/glasses/thermal/xray(src) // ~8 tc?
new /obj/item/storage/briefcase/launchpad(src) //6 tc
- new /obj/item/twohanded/binoculars(src) // 2 tc?
+ new /obj/item/binoculars(src) // 2 tc?
new /obj/item/encryptionkey/syndicate(src) // 2 tc
new /obj/item/storage/box/syndie_kit/space(src) //4 tc
- new /obj/item/grenade/syndieminibomb/concussion/frag(src) // ~2 tc each?
- new /obj/item/grenade/syndieminibomb/concussion/frag(src)
+ new /obj/item/grenade/frag(src) // ~2 tc each?
+ new /obj/item/grenade/frag(src)
new /obj/item/flashlight/emp(src)
new /obj/item/book/granter/martial/karate(src)
- if("bloodyspai")
+ if("bloodyspai")
new /obj/item/clothing/under/chameleon(src) // 2 tc since it's not the full set
new /obj/item/clothing/mask/chameleon(src) // Goes with above
new /obj/item/card/id/syndicate(src) // 2 tc
@@ -28,7 +31,7 @@
new /obj/item/flashlight/emp(src) // 2 tc
new /obj/item/chameleon(src) // 7 tc
- if("stealth")
+ if("stealth")
new /obj/item/gun/energy/kinetic_accelerator/crossbow(src)
new /obj/item/pen/sleepy(src)
new /obj/item/healthanalyzer/rad_laser(src)
@@ -38,7 +41,7 @@
new /obj/item/flashlight/emp(src)
new /obj/item/jammer(src)
- if("guns")
+ if("guns")
new /obj/item/gun/ballistic/revolver(src)
new /obj/item/ammo_box/a357(src)
new /obj/item/ammo_box/a357(src)
@@ -48,7 +51,7 @@
new /obj/item/clothing/mask/gas/clown_hat(src)
new /obj/item/clothing/under/suit/black_really(src)
- if("screwed")
+ if("screwed")
new /obj/item/sbeacondrop/bomb(src)
new /obj/item/grenade/syndieminibomb(src)
new /obj/item/sbeacondrop/powersink(src)
@@ -57,7 +60,7 @@
new /obj/item/encryptionkey/syndicate(src)
new /obj/item/pen/edagger(src)
- if("murder")
+ if("murder")
new /obj/item/melee/transforming/energy/sword/saber(src)
new /obj/item/clothing/glasses/thermal/syndi(src)
new /obj/item/card/emag(src)
@@ -65,7 +68,7 @@
new /obj/item/encryptionkey/syndicate(src)
new /obj/item/grenade/syndieminibomb(src)
- if("implant")
+ if("implant")
new /obj/item/implanter/freedom(src)
new /obj/item/implanter/uplink/precharged(src)
new /obj/item/implanter/emp(src)
@@ -73,7 +76,7 @@
new /obj/item/implanter/explosive(src)
new /obj/item/implanter/storage(src)
- if("hacker")
+ if("hacker")
new /obj/item/aiModule/syndicate(src)
new /obj/item/card/emag(src)
new /obj/item/encryptionkey/binary(src)
@@ -85,7 +88,7 @@
new /obj/item/card/id/syndicate(src)
new /obj/item/pen/edagger(src)
- if("lordsingulo")
+ if("lordsingulo")
new /obj/item/sbeacondrop(src)
new /obj/item/clothing/suit/space/syndicate/black/red(src)
new /obj/item/clothing/head/helmet/space/syndicate/black/red(src)
@@ -93,7 +96,7 @@
new /obj/item/storage/toolbox/syndicate(src)
new /obj/item/pen/edagger(src)
- if("sabotage")
+ if("sabotage")
new /obj/item/grenade/plastic/c4 (src)
new /obj/item/grenade/plastic/c4 (src)
new /obj/item/doorCharge(src)
@@ -113,7 +116,7 @@
new /obj/item/clothing/mask/gas/clown_hat(src)
new /obj/item/clothing/under/suit/black_really(src)
- if("metaops")
+ if("metaops")
new /obj/item/clothing/suit/space/hardsuit/syndi(src) // 8 tc
new /obj/item/gun/ballistic/shotgun/bulldog/unrestricted(src) // 8 tc
new /obj/item/implanter/explosive(src) // 2 tc
@@ -122,10 +125,10 @@
new /obj/item/grenade/plastic/c4 (src) // 1 tc
new /obj/item/grenade/plastic/c4 (src) // 1 tc
new /obj/item/card/emag(src) // 6 tc
-
-/obj/item/storage/box/syndicate/bundle_B/PopulateContents()
+
+/obj/item/storage/box/syndie_kit/bundle_B/PopulateContents()
switch (pickweight(list( "bond" = 2, "ninja" = 1, "darklord" = 1, "white_whale_holy_grail" = 2, "mad_scientist" = 2, "bee" = 2, "mr_freeze" = 2)))
- if("bond")
+ if("bond")
new /obj/item/gun/ballistic/automatic/pistol(src)
new /obj/item/suppressor(src)
new /obj/item/ammo_box/magazine/m10mm(src)
@@ -136,8 +139,8 @@
new /obj/item/reagent_containers/hypospray/medipen/pumpup(src)
new /obj/item/reagent_containers/glass/rag(src)
new /obj/item/encryptionkey/syndicate(src)
-
- if("ninja")
+
+ if("ninja")
new /obj/item/katana(src) // Unique , hard to tell how much tc this is worth. 8 tc?
new /obj/item/implanter/adrenalin(src) // 8 tc
for(var/i in 1 to 6)
@@ -146,14 +149,14 @@
new /obj/item/card/id/syndicate(src) // 2 tc
new /obj/item/chameleon(src) // 7 tc
- if("darklord")
- new /obj/item/twohanded/dualsaber(src)
+ if("darklord")
+ new /obj/item/dualsaber(src)
new /obj/item/dnainjector/telemut/darkbundle(src)
new /obj/item/clothing/suit/hooded/chaplain_hoodie(src)
new /obj/item/card/id/syndicate(src)
new /obj/item/clothing/shoes/chameleon/noslip(src) //because slipping while being a dark lord sucks
new /obj/item/book/granter/spell/summonitem(src)
-
+
if("white_whale_holy_grail") //Unique items that don't appear anywhere else
new /obj/item/pneumatic_cannon/speargun(src)
new /obj/item/storage/backpack/magspear_quiver(src)
@@ -178,8 +181,8 @@
new /obj/item/pen/edagger(src)
new /obj/item/gun/energy/wormhole_projector(src) //mooorttyyyy
new /obj/item/gun/energy/decloner/unrestricted(src)
-
- if("bee")
+
+ if("bee")
new /obj/item/paper/fluff/bee_objectives(src) // 0 tc (motivation)
new /obj/item/clothing/suit/hooded/bee_costume/syndie(src) // 0 tc
new /obj/item/clothing/mask/rat/bee(src) // 0 tc
@@ -187,7 +190,7 @@
new /obj/item/storage/box/syndie_kit/bee_grenades(src) // 15 tc
new /obj/item/reagent_containers/glass/bottle/beesease(src) // 10 tc?
new /obj/item/gun/chem/bee(src) //priceless
-
+
if("mr_freeze")
new /obj/item/clothing/glasses/cold(src)
new /obj/item/clothing/gloves/color/black(src)
@@ -200,33 +203,29 @@
new /obj/item/grenade/gluon(src)
new /obj/item/dnainjector/geladikinesis(src)
new /obj/item/dnainjector/cryokinesis(src)
- new /obj/item/gun/energy/temperature/security(src)
+ new /obj/item/gun/energy/temperature/pin(src)
new /obj/item/melee/transforming/energy/sword/saber/blue(src) //see see it fits the theme bc its blue and ice is blue
-/obj/item/storage/box/syndicate/contract_kit
+/obj/item/storage/box/syndie_kit/contract_kit
name = "Contract Kit"
desc = "Supplied to Syndicate contractors."
- icon_state = "syndiebox"
- illustration = "writing_syndie"
-/obj/item/storage/box/syndicate/contractor_loadout
+/obj/item/storage/box/syndie_kit/contractor_loadout
name = "Standard Loadout"
desc = "Supplied to Syndicate contractors, providing their specialised space suit and chameleon uniform."
- icon_state = "syndiebox"
- illustration = "writing_syndie"
/obj/item/paper/contractor_guide
name = "Contractor Guide"
/obj/item/paper/contractor_guide/Initialize()
- info = {"
Welcome agent, congratulations on your new position as contractor. On top of your already assigned objectives,
+ info = {"
Welcome agent, congratulations on your new position as contractor. On top of your already assigned objectives,
this kit will provide you contracts to take on for TC payments.
-
Provided within, we give your specialist contractor space suit. It's even more compact, being able to fit into a pocket, and faster than the
- Syndicate space suit available to you on the uplink. We also provide your chameleon jumpsuit and mask, both of which can be changed
+
Provided within, we give your specialist contractor space suit. It's even more compact, being able to fit into a pocket, and faster than the
+ Syndicate space suit available to you on the uplink. We also provide your chameleon jumpsuit and mask, both of which can be changed
to any form you need for the moment. The cigarettes are a special blend - it'll heal your injuries slowly overtime.
-
Your standard issue contractor baton hits harder than the ones you might be used to, and likely be your go to weapon for kidnapping your
+
Your standard issue contractor baton hits harder than the ones you might be used to, and likely be your go to weapon for kidnapping your
targets. The three additional items have been randomly selected from what we had available. We hope they're useful to you for your mission.
The contractor hub, available at the top right of the uplink, will provide you unique items and abilities. These are bought using Contractor Rep,
@@ -236,13 +235,13 @@
Open the Syndicate Contract Uplink program.
Here, you can accept a contract, and redeem your TC payments from completed contracts.
-
The payment number shown in brackets is the bonus you'll recieve when bringing your target alive. You recieve the
+
The payment number shown in brackets is the bonus you'll receive when bringing your target alive. You receive the
other number regardless of if they were alive or dead.
Contracts are completed by bringing the target to designated dropoff, calling for extraction, and putting them
inside the pod.
-
Be careful when accepting a contract. While you'll be able to see the location of the dropoff point, cancelling will make it
+
Be careful when accepting a contract. While you'll be able to see the location of the dropoff point, cancelling will make it
unavailable to take on again.
The tablet can also be recharged at any cell charger.
Extracting
@@ -253,15 +252,15 @@
Grab your target, and drag them into the pod.
Ransoms
-
We need your target for our own reasons, but we ransom them back to your mission area once their use is served. They will return back
- from where you sent them off from in several minutes time. Don't worry, we give you a cut of what we get paid. We pay this into whatever
+
We need your target for our own reasons, but we ransom them back to your mission area once their use is served. They will return back
+ from where you sent them off from in several minutes time. Don't worry, we give you a cut of what we get paid. We pay this into whatever
ID card you have equipped, on top of the TC payment we give.
-
+
Good luck agent. You can burn this document with the supplied lighter.
"}
return ..()
-/obj/item/storage/box/syndicate/contractor_loadout/PopulateContents()
+/obj/item/storage/box/syndie_kit/contractor_loadout/PopulateContents()
new /obj/item/clothing/head/helmet/space/syndicate/contract(src)
new /obj/item/clothing/suit/space/syndicate/contract(src)
new /obj/item/clothing/under/chameleon(src)
@@ -269,31 +268,30 @@
new /obj/item/card/id/syndicate(src)
new /obj/item/storage/fancy/cigarettes/cigpack_syndicate(src)
new /obj/item/lighter(src)
+ new /obj/item/jammer(src)
-/obj/item/storage/box/syndicate/contract_kit/PopulateContents()
+/obj/item/storage/box/syndie_kit/contract_kit/PopulateContents()
new /obj/item/modular_computer/tablet/syndicate_contract_uplink/preset/uplink(src)
- new /obj/item/storage/box/syndicate/contractor_loadout(src)
+ new /obj/item/storage/box/syndie_kit/contractor_loadout(src)
new /obj/item/melee/classic_baton/contractor_baton(src)
// All about 4 TC or less - some nukeops only items, but fit nicely to the theme.
var/list/item_list = list(
/obj/item/storage/backpack/duffelbag/syndie/x4,
/obj/item/storage/box/syndie_kit/throwing_weapons,
- /obj/item/pen/edagger,
/obj/item/pen/sleepy,
/obj/item/storage/box/syndie_kit/emp,
/obj/item/clothing/shoes/chameleon/noslip,
/obj/item/storage/firstaid/tactical,
- /obj/item/encryptionkey/binary,
/obj/item/clothing/glasses/thermal/syndi,
- /obj/item/slimepotion/slime/sentience/nuclear,
/obj/item/storage/box/syndie_kit/imp_radio,
/obj/item/clothing/gloves/krav_maga/combatglovesplus,
/obj/item/gun/ballistic/automatic/c20r/toy/unrestricted/riot,
/obj/item/reagent_containers/hypospray/medipen/pumpup,
/obj/item/compressionkit,
- /obj/item/book/granter/martial/karate,
- /obj/item/storage/box/syndie_kit/imp_freedom
+ /obj/item/storage/box/syndie_kit/imp_freedom,
+ /obj/item/storage/box/syndie_kit/chameleon,
+ /obj/item/healthanalyzer/rad_laser
)
var/obj/item1 = pick_n_take(item_list)
@@ -308,12 +306,6 @@
// Paper guide
new /obj/item/paper/contractor_guide(src)
-/obj/item/storage/box/syndie_kit
- name = "box"
- desc = "A sleek, sturdy box."
- icon_state = "syndiebox"
- illustration = "writing_syndie"
-
/obj/item/storage/box/syndie_kit/origami_bundle
name = "origami kit"
desc = "A box full of a number of rather masterfully engineered paper planes and a manual on \"The Art of Origami\"."
@@ -393,7 +385,7 @@
if(prob(50))
new /obj/item/clothing/suit/space/syndicate/black/red(src) // Black and red is so in right now
new /obj/item/clothing/head/helmet/space/syndicate/black/red(src)
-
+
else
new /obj/item/clothing/head/helmet/space/syndicate(src)
new /obj/item/clothing/suit/space/syndicate(src)
@@ -565,3 +557,29 @@
new /obj/item/storage/belt/soulstone/full/purified(src)
new /obj/item/sbeacondrop/constructshell(src)
new /obj/item/sbeacondrop/constructshell(src)
+
+/obj/item/storage/box/syndie_kit/mimesabrekit
+ name = "Baguette blade bundle"
+ desc = "Provides you with a hardly noticable blade hidden inside a baguette disguise."
+
+/obj/item/storage/box/syndie_kit/mimesabrekit/PopulateContents()
+ new /obj/item/storage/belt/sabre/mime(src)
+
+/obj/item/storage/box/syndie_kit/imp_deathrattle
+ name = "deathrattle implant box"
+ desc = "Contains eight linked deathrattle implants."
+
+/obj/item/storage/box/syndie_kit/imp_deathrattle/PopulateContents()
+ new /obj/item/implanter(src)
+
+ var/datum/deathrattle_group/group = new
+
+ var/implants = list()
+ for(var/j in 1 to 8)
+ var/obj/item/implantcase/deathrattle/case = new (src)
+ implants += case.imp
+
+ for(var/i in implants)
+ group.register(i)
+ desc += " The implants are registered to the \"[group.name]\" group."
+
diff --git a/code/game/objects/items/storage/wallets.dm b/code/game/objects/items/storage/wallets.dm
index 33a7478db4322..e1fa7b42b6e0b 100644
--- a/code/game/objects/items/storage/wallets.dm
+++ b/code/game/objects/items/storage/wallets.dm
@@ -13,7 +13,6 @@
. = ..()
var/datum/component/storage/STR = GetComponent(/datum/component/storage)
STR.max_items = 4
- STR.cant_hold = typecacheof(list(/obj/item/screwdriver/power)) //Must be specifically called out since normal screwdrivers can fit but not the wrench form of the drill
STR.can_hold = typecacheof(list(
/obj/item/stack/spacecash,
/obj/item/holochip,
diff --git a/code/game/objects/items/stunbaton.dm b/code/game/objects/items/stunbaton.dm
index 0d9e6c75563b0..afd649e36a2eb 100644
--- a/code/game/objects/items/stunbaton.dm
+++ b/code/game/objects/items/stunbaton.dm
@@ -8,11 +8,9 @@
slot_flags = ITEM_SLOT_BELT
force = 5
throwforce = 7
- block_upgrade_walk = 1
- block_flags = BLOCKING_ACTIVE | BLOCKING_NASTY
w_class = WEIGHT_CLASS_NORMAL
attack_verb = list("enforced the law upon")
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80, "stamina" = 0)
var/stunforce = 75
var/turned_on = FALSE
@@ -91,15 +89,15 @@
if(istype(W, /obj/item/stock_parts/cell))
var/obj/item/stock_parts/cell/C = W
if(cell)
- to_chat(user, "[src] already has a cell.")
+ balloon_alert(user, "[src] already has a cell")
else
if(C.maxcharge < hitcost)
- to_chat(user, "[src] requires a higher capacity cell.")
+ balloon_alert(user, "[src] requires a higher capacity cell")
return
if(!user.transferItemToLoc(W, src))
return
cell = W
- to_chat(user, "You install a cell in [src].")
+ balloon_alert(user, "Cell installed")
update_icon()
else if(W.tool_behaviour == TOOL_SCREWDRIVER)
@@ -107,7 +105,7 @@
cell.update_icon()
cell.forceMove(get_turf(src))
cell = null
- to_chat(user, "You remove the cell from [src].")
+ balloon_alert(user, "Cell removed")
turned_on = FALSE
update_icon()
else
@@ -116,14 +114,14 @@
/obj/item/melee/baton/attack_self(mob/user)
if(cell && cell.charge > hitcost)
turned_on = !turned_on
- to_chat(user, "[src] is now [turned_on ? "on" : "off"].")
+ balloon_alert(user, "[src] [turned_on ? "on" : "off"]")
playsound(src, "sparks", 75, TRUE, -1)
else
turned_on = FALSE
if(!cell)
- to_chat(user, "[src] does not have a power source!")
+ balloon_alert(user, "No power source")
else
- to_chat(user, "[src] is out of charge.")
+ balloon_alert(user, "Out of charge")
update_icon()
add_fingerprint(user)
@@ -173,7 +171,7 @@
return FALSE
var/obj/item/bodypart/affecting = target.get_bodypart(ran_zone(user.zone_selected))
- var/armor_block = target.run_armor_check(affecting, "energy")
+ var/armor_block = target.run_armor_check(affecting, "stamina")
// L.adjustStaminaLoss(stunforce)
target.apply_damage(stunforce, STAMINA, affecting, armor_block)
target.apply_effect(EFFECT_STUTTER, stunforce)
@@ -228,4 +226,3 @@
if(sparkler)
QDEL_NULL(sparkler)
return ..()
-
diff --git a/code/game/objects/items/tanks/jetpack.dm b/code/game/objects/items/tanks/jetpack.dm
index 9f3d18f0f89e2..9fa0357c4707b 100644
--- a/code/game/objects/items/tanks/jetpack.dm
+++ b/code/game/objects/items/tanks/jetpack.dm
@@ -8,7 +8,7 @@
w_class = WEIGHT_CLASS_BULKY
distribute_pressure = ONE_ATMOSPHERE * O2STANDARD
actions_types = list(/datum/action/item_action/set_internals, /datum/action/item_action/toggle_jetpack, /datum/action/item_action/jetpack_stabilization)
- var/gas_type = /datum/gas/oxygen
+ var/gas_type = GAS_O2
var/on = FALSE
var/stabilizers = FALSE
var/full_speed = TRUE // If the jetpack will have a speedboost in space/nograv or not
@@ -19,6 +19,10 @@
ion_trail = new
ion_trail.set_up(src)
+/obj/item/tank/jetpack/Destroy()
+ QDEL_NULL(ion_trail)
+ return ..()
+
/obj/item/tank/jetpack/populate_gas()
if(gas_type)
air_contents.set_moles(gas_type, ((6 * ONE_ATMOSPHERE) * volume / (R_IDEAL_GAS_EQUATION * T20C)))
@@ -66,6 +70,8 @@
user.remove_movespeed_modifier(MOVESPEED_ID_JETPACK)
/obj/item/tank/jetpack/proc/move_react(mob/user)
+ SIGNAL_HANDLER
+
allow_thrust(0.01, user)
/obj/item/tank/jetpack/proc/allow_thrust(num, mob/living/user)
@@ -75,13 +81,7 @@
turn_off(user)
return
- var/datum/gas_mixture/removed = air_contents.remove(num)
- if(removed.total_moles() < 0.005)
- turn_off(user)
- return
-
- var/turf/T = get_turf(user)
- T.assume_air(removed)
+ assume_air_moles(air_contents, num)
return TRUE
@@ -114,13 +114,7 @@
turn_off(user)
return
- var/datum/gas_mixture/removed = air_contents.remove(num)
- if(removed.total_moles() < 0.005)
- turn_off(user)
- return
-
- var/turf/T = get_turf(user)
- T.assume_air(removed)
+ assume_air_moles(air_contents, num)
return TRUE
@@ -168,7 +162,7 @@
icon_state = "jetpack-black"
item_state = "jetpack-black"
distribute_pressure = 0
- gas_type = /datum/gas/carbon_dioxide
+ gas_type = GAS_CO2
/obj/item/tank/jetpack/suit
diff --git a/code/game/objects/items/tanks/tank_types.dm b/code/game/objects/items/tanks/tank_types.dm
index d287a570ec782..d56e84a3af417 100644
--- a/code/game/objects/items/tanks/tank_types.dm
+++ b/code/game/objects/items/tanks/tank_types.dm
@@ -20,7 +20,7 @@
/obj/item/tank/internals/oxygen/populate_gas()
- air_contents.set_moles(/datum/gas/oxygen, (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
+ air_contents.set_moles(GAS_O2, (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
/obj/item/tank/internals/oxygen/yellow
desc = "A tank of oxygen, this one is yellow."
@@ -46,8 +46,8 @@
force = 10
/obj/item/tank/internals/anesthetic/populate_gas()
- air_contents.set_moles(/datum/gas/oxygen, (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD)
- air_contents.set_moles(/datum/gas/nitrous_oxide, (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD)
+ air_contents.set_moles(GAS_O2, (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD)
+ air_contents.set_moles(GAS_NITROUS, (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD)
/*
* Air
@@ -61,8 +61,8 @@
dog_fashion = /datum/dog_fashion/back
/obj/item/tank/internals/air/populate_gas()
- air_contents.set_moles(/datum/gas/oxygen, (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD)
- air_contents.set_moles(/datum/gas/nitrogen, (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD)
+ air_contents.set_moles(GAS_O2, (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD)
+ air_contents.set_moles(GAS_N2, (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD)
/*
* Plasma
@@ -77,7 +77,7 @@
/obj/item/tank/internals/plasma/populate_gas()
- air_contents.set_moles(/datum/gas/plasma, (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
+ air_contents.set_moles(GAS_PLASMA, (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
/obj/item/tank/internals/plasma/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/flamethrower))
@@ -93,7 +93,7 @@
return ..()
/obj/item/tank/internals/plasma/full/populate_gas()
- air_contents.set_moles(/datum/gas/plasma, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
+ air_contents.set_moles(GAS_PLASMA, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
/obj/item/tank/internals/plasma/empty/populate_gas()
return
@@ -111,10 +111,10 @@
distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE
/obj/item/tank/internals/plasmaman/populate_gas()
- air_contents.set_moles(/datum/gas/plasma, (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
+ air_contents.set_moles(GAS_PLASMA, (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
/obj/item/tank/internals/plasmaman/full/populate_gas()
- air_contents.set_moles(/datum/gas/plasma, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
+ air_contents.set_moles(GAS_PLASMA, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
/obj/item/tank/internals/plasmaman/empty/populate_gas()
return
@@ -130,7 +130,7 @@
w_class = WEIGHT_CLASS_SMALL //thanks i forgot this
/obj/item/tank/internals/plasmaman/belt/full/populate_gas()
- air_contents.set_moles(/datum/gas/plasma, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
+ air_contents.set_moles(GAS_PLASMA, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
/obj/item/tank/internals/plasmaman/belt/empty/populate_gas()
return
@@ -153,7 +153,7 @@
/obj/item/tank/internals/emergency_oxygen/populate_gas()
- air_contents.set_moles(/datum/gas/oxygen, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
+ air_contents.set_moles(GAS_O2, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
/obj/item/tank/internals/emergency_oxygen/empty/populate_gas()
return
@@ -181,3 +181,18 @@
/obj/item/tank/internals/emergency_oxygen/double/empty/populate_gas()
return
+
+/obj/item/tank/internals/emergency_oxygen/clown
+ name = "emergency prank tank"
+ desc = "Used for pranking in emergencies! Has a smidge of a mystery ingredient for 200% FUN!"
+ icon_state = "clown"
+ flags_1 = CONDUCT_1
+ slot_flags = ITEM_SLOT_BELT
+ w_class = WEIGHT_CLASS_SMALL
+ force = 4
+ distribute_pressure = 24
+ volume = 1
+
+/obj/item/tank/internals/emergency_oxygen/clown/populate_gas()
+ air_contents.set_moles(GAS_O2, (9.99*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
+ air_contents.set_moles(GAS_NITROUS, (0.01*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
diff --git a/code/game/objects/items/tanks/tanks.dm b/code/game/objects/items/tanks/tanks.dm
index b28e7988366e9..145574c3d49d9 100644
--- a/code/game/objects/items/tanks/tanks.dm
+++ b/code/game/objects/items/tanks/tanks.dm
@@ -13,7 +13,7 @@
throw_range = 4
materials = list(/datum/material/iron = 500)
actions_types = list(/datum/action/item_action/set_internals)
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 30)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 30, "stamina" = 0)
var/datum/gas_mixture/air_contents = null
var/distribute_pressure = ONE_ATMOSPHERE
var/integrity = 3
@@ -66,7 +66,7 @@
/obj/item/tank/Destroy()
if(air_contents)
- qdel(air_contents)
+ QDEL_NULL(air_contents)
STOP_PROCESSING(SSobj, src)
. = ..()
@@ -157,6 +157,7 @@
if(!ui)
ui = new(user, src, "Tank")
ui.open()
+ ui.set_autoupdate(TRUE)
/obj/item/tank/ui_data(mob/user)
var/list/data = list()
@@ -205,6 +206,9 @@
/obj/item/tank/remove_air(amount)
return air_contents.remove(amount)
+/obj/item/tank/remove_air_ratio(ratio)
+ return air_contents.remove_ratio(ratio)
+
/obj/item/tank/return_air()
return air_contents
@@ -217,6 +221,18 @@
check_status()
return 1
+/obj/item/tank/assume_air_moles(datum/gas_mixture/giver, moles)
+ giver.transfer_to(air_contents, moles)
+
+ check_status()
+ return 1
+
+/obj/item/tank/assume_air_ratio(datum/gas_mixture/giver, ratio)
+ giver.transfer_ratio_to(air_contents, ratio)
+
+ check_status()
+ return 1
+
/obj/item/tank/proc/remove_air_volume(volume_to_return)
if(!air_contents)
return null
@@ -231,7 +247,7 @@
/obj/item/tank/process()
//Allow for reactions
- air_contents.react()
+ air_contents.react(src)
check_status()
/obj/item/tank/proc/check_status()
@@ -245,7 +261,7 @@
if(pressure > TANK_FRAGMENT_PRESSURE)
if(!istype(src.loc, /obj/item/transfer_valve))
- log_bomber(get_mob_by_key(fingerprintslast), "was last key to touch", src, "which ruptured explosively")
+ log_bomber(get_mob_by_ckey(fingerprintslast), "was last key to touch", src, "which ruptured explosively")
//Give the gas a chance to build up more pressure through reacting
air_contents.react(src)
air_contents.react(src)
diff --git a/code/game/objects/items/tanks/watertank.dm b/code/game/objects/items/tanks/watertank.dm
index 028bb874af43e..82a73d82f21d9 100644
--- a/code/game/objects/items/tanks/watertank.dm
+++ b/code/game/objects/items/tanks/watertank.dm
@@ -12,7 +12,7 @@
slowdown = 1
actions_types = list(/datum/action/item_action/toggle_mister)
max_integrity = 200
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30, "stamina" = 0)
resistance_flags = FIRE_PROOF
var/obj/item/noz
@@ -60,7 +60,7 @@
/obj/item/watertank/equipped(mob/user, slot)
..()
- if(slot != SLOT_BACK)
+ if(slot != ITEM_SLOT_BACK)
remove_noz()
/obj/item/watertank/proc/remove_noz()
@@ -82,8 +82,8 @@
/obj/item/watertank/MouseDrop(obj/over_object)
var/mob/M = loc
- if(istype(M) && istype(over_object, /obj/screen/inventory/hand))
- var/obj/screen/inventory/hand/H = over_object
+ if(istype(M) && istype(over_object, /atom/movable/screen/inventory/hand))
+ var/atom/movable/screen/inventory/hand/H = over_object
M.putItemFromInventoryInHandIfPossible(src, H.held_index)
return ..()
@@ -281,9 +281,7 @@
step_towards(A, target)
sleep(2)
A.Smoke()
- spawn(100)
- if(src)
- resin_cooldown = FALSE
+ addtimer(VARSET_CALLBACK(src, resin_cooldown, FALSE), 10 SECONDS)
return
if(nozzle_mode == RESIN_FOAM)
if(!Adj|| !isturf(target))
@@ -296,12 +294,14 @@
var/obj/effect/particle_effect/foam/metal/resin/F = new (get_turf(target))
F.amount = 0
metal_synthesis_cooldown++
- spawn(100)
- metal_synthesis_cooldown--
+ addtimer(CALLBACK(src, .proc/reduce_metal_synth_cooldown), 10 SECONDS)
else
to_chat(user, "Resin foam mix is still being synthesized...")
return
+/obj/item/extinguisher/mini/nozzle/proc/reduce_metal_synth_cooldown()
+ metal_synthesis_cooldown--
+
/obj/effect/resin_container
name = "resin container"
desc = "A compacted ball of expansive resin, used to repair the atmosphere in a room, or seal off breaches."
@@ -337,7 +337,8 @@
var/on = FALSE
volume = 300
var/usage_ratio = 5 //5 unit added per 1 removed
- var/injection_amount = 1
+ /// How much to inject per second
+ var/injection_amount = 0.5
amount_per_transfer_from_this = 5
reagent_flags = OPENCONTAINER
spillable = FALSE
@@ -349,14 +350,14 @@
toggle_injection()
/obj/item/reagent_containers/chemtank/item_action_slot_check(slot, mob/user)
- if(slot == SLOT_BACK)
+ if(slot == ITEM_SLOT_BACK)
return 1
/obj/item/reagent_containers/chemtank/proc/toggle_injection()
var/mob/living/carbon/human/user = usr
if(!istype(user))
return
- if (user.get_item_by_slot(SLOT_BACK) != src)
+ if (user.get_item_by_slot(ITEM_SLOT_BACK) != src)
to_chat(user, "The chemtank needs to be on your back before you can activate it!")
return
if(on)
@@ -395,7 +396,7 @@
if(ismob(loc))
to_chat(loc, "[src] turns off.")
-/obj/item/reagent_containers/chemtank/process()
+/obj/item/reagent_containers/chemtank/process(delta_time)
if(!ishuman(loc))
turn_off()
return
@@ -407,7 +408,7 @@
turn_off()
return
- var/used_amount = injection_amount/usage_ratio
+ var/used_amount = (injection_amount * delta_time) /usage_ratio
reagents.reaction(user, INJECT,injection_amount,0)
reagents.trans_to(user,used_amount,multiplier=usage_ratio)
update_icon()
diff --git a/code/game/objects/items/teleportation.dm b/code/game/objects/items/teleportation.dm
index 044f1048afdf2..3fe7e32c8ef55 100644
--- a/code/game/objects/items/teleportation.dm
+++ b/code/game/objects/items/teleportation.dm
@@ -6,6 +6,7 @@
* Contains:
* Locator
* Hand-tele
+ * Syndicate Teleporter
*/
/*
@@ -59,7 +60,7 @@
if (!W.renamed)
continue
var/turf/tr = get_turf(W)
- if (tr.z == sr.z && tr)
+ if (tr.get_virtual_z_level() == sr.get_virtual_z_level() && tr)
var/direct = max(abs(tr.x - sr.x), abs(tr.y - sr.y))
if (direct < 5)
direct = "very strong"
@@ -84,7 +85,7 @@
continue
var/turf/tr = get_turf(W)
- if (tr.z == sr.z && tr)
+ if (tr.get_virtual_z_level() == sr.get_virtual_z_level() && tr)
var/direct = max(abs(tr.x - sr.x), abs(tr.y - sr.y))
if (direct < 20)
if (direct < 5)
@@ -105,8 +106,8 @@
if (ismob(src.loc))
attack_self(src.loc)
else
- for(var/mob/M in viewers(1, src))
- if (M.client)
+ for(var/mob/M as() in viewers(1, src))
+ if(M.client)
src.attack_self(M)
return
@@ -127,7 +128,7 @@
throw_speed = 3
throw_range = 5
materials = list(/datum/material/iron=10000)
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100, "stamina" = 0)
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
var/list/active_portal_pairs
var/max_portal_pairs = 3
@@ -156,27 +157,30 @@
/obj/item/hand_tele/attack_self(mob/user)
var/turf/current_location = get_turf(user)//What turf is the user on?
var/area/current_area = current_location.loc
- if(!current_location || current_area.noteleport || is_away_level(current_location.z) || is_centcom_level(current_location.z) || !isturf(user.loc))//If turf was not found or they're on z level 2 or >7 which does not currently exist. or if user is not located on a turf
+ if(!current_location || current_area.teleport_restriction || is_away_level(current_location.z) || is_centcom_level(current_location.z) || !isturf(user.loc))//If turf was not found or they're on z level 2 or >7 which does not currently exist. or if user is not located on a turf
to_chat(user, "\The [src] is malfunctioning.")
return
- var/list/L = list( )
+ var/list/L = list()
for(var/obj/machinery/computer/teleporter/com in GLOB.machines)
- if(com.target)
- var/area/A = get_area(com.target)
- if(!A || A.noteleport)
- continue
- if(com.power_station && com.power_station.teleporter_hub && com.power_station.engaged)
- L["[get_area(com.target)] (Active)"] = com.target
- else
- L["[get_area(com.target)] (Inactive)"] = com.target
- var/list/turfs = list( )
- for(var/turf/T in urange(10, orange=1))
+ var/atom/target = com.target_ref?.resolve()
+ if(!target)
+ com.target_ref = null
+ continue
+ var/area/A = get_area(target)
+ if(!A || A.teleport_restriction)
+ continue
+ if(com.power_station && com.power_station.teleporter_hub && com.power_station.engaged)
+ L["[get_area(target)] (Active)"] = target
+ else
+ L["[get_area(target)] (Inactive)"] = target
+ var/list/turfs = list()
+ for(var/turf/T as() in (RANGE_TURFS(10, user) - get_turf(user)))
if(T.x>world.maxx-8 || T.x<8)
continue //putting them at the edge is dumb
if(T.y>world.maxy-8 || T.y<8)
continue
var/area/A = T.loc
- if(A.noteleport)
+ if(A.teleport_restriction)
continue
turfs += T
if(turfs.len)
@@ -189,15 +193,15 @@
return
var/atom/T = L[t1]
var/area/A = get_area(T)
- if(A.noteleport)
+ if(A.teleport_restriction)
to_chat(user, "\The [src] is malfunctioning.")
return
current_location = get_turf(user) //Recheck.
current_area = current_location.loc
- if(!current_location || current_area.noteleport || is_away_level(current_location.z) || is_centcom_level(current_location.z) || !isturf(user.loc))//If turf was not found or they're on z level 2 or >7 which does not currently exist. or if user is not located on a turf
+ if(!current_location || current_area.teleport_restriction || is_away_level(current_location.z) || is_centcom_level(current_location.z) || !isturf(user.loc))//If turf was not found or they're on z level 2 or >7 which does not currently exist. or if user is not located on a turf
to_chat(user, "\The [src] is malfunctioning.")
return
- user.show_message("Locked In.", 2)
+ user.show_message("Locked In.", MSG_AUDIBLE)
var/list/obj/effect/portal/created = create_portal_pair(current_location, get_teleport_turf(get_turf(T)), src, 300, 1, null, atmos_link_override)
if(!(LAZYLEN(created) == 2))
return
@@ -228,9 +232,167 @@
var/obj/item/bodypart/head/head = itemUser.get_bodypart(BODY_ZONE_HEAD)
if(head)
head.drop_limb()
- var/list/safeLevels = SSmapping.levels_by_any_trait(list(ZTRAIT_SPACE_RUINS, ZTRAIT_LAVA_RUINS, ZTRAIT_STATION, ZTRAIT_MINING))
+ var/list/safeLevels = SSmapping.levels_by_any_trait(list(ZTRAIT_DYNAMIC_LEVEL, ZTRAIT_LAVA_RUINS, ZTRAIT_STATION, ZTRAIT_MINING))
head.forceMove(locate(rand(1, world.maxx), rand(1, world.maxy), pick(safeLevels)))
itemUser.visible_message("The portal snaps closed taking [user]'s head with it!")
else
itemUser.visible_message("[user] looks even further depressed as they realize they do not have a head...and suddenly dies of shame!")
return (BRUTELOSS)
+
+/*
+ * Syndicate Teleporter
+ */
+
+/obj/item/teleporter
+ name = "syndicate teleporter"
+ desc = "A Syndicate reverse-engineered version of the Nanotrasen portable handheld teleporter. It uses bluespace technology to translocate users, but lacks the advanced safety features of its counterpart. Warranty voided if exposed to an electromagnetic pulse."
+ icon = 'icons/obj/device.dmi'
+ icon_state = "syndi_tele"
+ item_state = "electronic"
+ lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
+ throwforce = 5
+ w_class = WEIGHT_CLASS_SMALL
+ throw_speed = 4
+ throw_range = 10
+
+ //Uses of the device left
+ var/charges = 4
+ //The maximum number of stored uses
+ var/max_charges = 4
+ var/minimum_teleport_distance = 4
+ var/maximum_teleport_distance = 8
+ //How far the emergency teleport checks for a safe position
+ var/parallel_teleport_distance = 3
+ //How long it takes to replenish a charge
+ var/recharge_time = 15 SECONDS
+ //If the device is recharging, prevents timers stacking
+ var/recharging = FALSE
+ //stores the recharge timer id
+ var/recharge_timer
+
+/obj/item/teleporter/examine(mob/user)
+ . = ..()
+ . += "[src] has [charges] out of [max_charges] charges left."
+ if(recharging)
+ . += "A small display on the back reads:"
+ var/timeleft = timeleft(recharge_timer)
+ var/loadingbar = num2loadingbar(timeleft/recharge_time, reverse=TRUE)
+ . += "CHARGING: [loadingbar] ([timeleft*0.1]s)"
+
+/obj/item/teleporter/attack_self(mob/user)
+ ..()
+ attempt_teleport(user, FALSE)
+
+/obj/item/teleporter/proc/check_charges()
+ if(recharging)
+ return
+ if(charges < max_charges)
+ recharge_timer = addtimer(CALLBACK(src, .proc/recharge), recharge_time, TIMER_STOPPABLE)
+ recharging = TRUE
+
+/obj/item/teleporter/proc/recharge()
+ charges++
+ playsound(src,'sound/machines/twobeep.ogg',10,TRUE, extrarange = SILENCED_SOUND_EXTRARANGE, falloff_distance = 0)
+ recharging = FALSE
+ check_charges()
+
+/obj/item/teleporter/emp_act(severity)
+ if(prob(50 / severity))
+ if(istype(loc, /mob/living/carbon/human))
+ var/mob/living/carbon/human/user = loc
+ to_chat(user, "[src] buzzes and activates!")
+ attempt_teleport(user, TRUE) //EMP Activates a teleport with no safety.
+ else
+ visible_message("[src] activates and blinks out of existence!")
+ do_sparks(2, 1, src)
+ qdel(src)
+
+/obj/item/teleporter/proc/attempt_teleport(mob/user, EMP_D = FALSE)
+ if(!charges)
+ to_chat(user, "[src] is still recharging.")
+ return
+
+ var/turf/current_location = get_turf(user)
+ var/area/current_area = current_location.loc
+ if(!current_location || current_area.teleport_restriction || is_away_level(current_location.z) || is_centcom_level(current_location.z) || !isturf(user.loc))//If turf was not found or they're on z level 2 or >7 which does not currently exist. or if user is not located on a turf
+ to_chat(user, "\The [src] is malfunctioning.")
+ return
+
+ var/mob/living/carbon/C = user
+ var/teleport_distance = rand(minimum_teleport_distance,maximum_teleport_distance)
+ var/turf/destination = get_teleport_loc(current_location,C,teleport_distance,0,0,0,0,0,0)
+ var/list/bagholding = user.GetAllContents(/obj/item/storage/backpack/holding)
+
+ if(isclosedturf(destination))
+ if(!EMP_D && !(bagholding.len))
+ panic_teleport(user, destination) //We're in a wall, engage emergency parallel teleport.
+ else
+ get_fragged(user, destination) //EMP teleported you into a wall? Wearing a BoH? You're dead.
+ else
+ telefrag(destination, user)
+ do_teleport(C, destination, channel = TELEPORT_CHANNEL_FREE)
+ charges--
+ check_charges()
+ new /obj/effect/temp_visual/teleport_abductor/syndi_teleporter(current_location)
+ new /obj/effect/temp_visual/teleport_abductor/syndi_teleporter(destination)
+ playsound(destination, 'sound/effects/phasein.ogg', 25, 1)
+ playsound(destination, "sparks", 50, 1)
+
+/obj/item/teleporter/proc/panic_teleport(mob/user, turf/destination)
+ var/mob/living/carbon/C = user
+ var/turf/mobloc = get_turf(C)
+ var/turf/emergency_destination = get_teleport_loc(destination,C,0,0,1,parallel_teleport_distance,0,0,0)
+
+ if(emergency_destination)
+ telefrag(emergency_destination, user)
+ do_teleport(C, emergency_destination, channel = TELEPORT_CHANNEL_FREE)
+ charges--
+ check_charges()
+ new /obj/effect/temp_visual/teleport_abductor/syndi_teleporter(mobloc)
+ new /obj/effect/temp_visual/teleport_abductor/syndi_teleporter(emergency_destination)
+ playsound(emergency_destination, 'sound/effects/phasein.ogg', 25, 1)
+ playsound(emergency_destination, "sparks", 50, 1)
+ else //We tried to save. We failed. Death time.
+ get_fragged(user, destination)
+
+/obj/item/teleporter/proc/get_fragged(mob/user, turf/destination)
+ var/turf/mobloc = get_turf(user)
+ if(do_teleport(user, destination, channel = TELEPORT_CHANNEL_FREE, no_effects = TRUE))
+ playsound(mobloc, "sparks", 50, TRUE)
+ new /obj/effect/temp_visual/teleport_abductor/syndi_teleporter(mobloc)
+ new /obj/effect/temp_visual/teleport_abductor/syndi_teleporter(destination)
+ playsound(destination, "sparks", 50, TRUE)
+ playsound(destination, "sound/magic/disintegrate.ogg", 50, TRUE)
+ to_chat(user, "You teleport into the wall, the teleporter tries to save you, but-")
+ destination.ex_act(2) //Destroy the wall
+ user.gib()
+
+/obj/item/teleporter/proc/telefrag(turf/fragging_location, mob/user)
+ for(var/mob/living/M in fragging_location)//Hit everything in the turf
+ M.apply_damage(20, BRUTE)
+ M.Paralyze(30)
+ to_chat(M, "[user] teleports into you, knocking you to the floor with the bluespace wave!")
+
+/obj/item/paper/teleporter
+ name = "Teleporter Guide"
+ icon_state = "paper"
+ info = {"Instructions on your new prototype syndicate teleporter:
+
+ This experimental teleporter will teleport the user 4-8 meters in the direction they are facing. Anything you are pulling will not be teleported with you.
+
+ It has 4 charges, and will recharge over time. No, sticking the teleporter into the tesla, an APC, a microwave, or an electrified door will not make it charge faster.
+
+ Warning: Teleporting into walls will activate a failsafe teleport parallel up to 3 meters, but the user will be ripped apart and gibbed in the wall if it fails to find a safe location.
+
+ Do not expose the teleporter to electromagnetic pulses, or possess a bag of holding while operating it. Unwanted malfunctions may occur.
+"}
+/obj/item/storage/box/syndie_kit/teleporter
+ name = "syndicate teleporter kit"
+
+/obj/item/storage/box/syndie_kit/teleporter/PopulateContents()
+ new /obj/item/teleporter(src)
+ new /obj/item/paper/teleporter(src)
+
+/obj/effect/temp_visual/teleport_abductor/syndi_teleporter
+ duration = 5
diff --git a/code/game/objects/items/tools/crowbar.dm b/code/game/objects/items/tools/crowbar.dm
index bc0878074d1b8..cc57661bea739 100644
--- a/code/game/objects/items/tools/crowbar.dm
+++ b/code/game/objects/items/tools/crowbar.dm
@@ -17,7 +17,7 @@
attack_verb = list("attacked", "bashed", "battered", "bludgeoned", "whacked")
tool_behaviour = TOOL_CROWBAR
toolspeed = 1
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30, "stamina" = 0)
/obj/item/crowbar/suicide_act(mob/user)
user.visible_message("[user] is beating [user.p_them()]self to death with [src]! It looks like [user.p_theyre()] trying to commit suicide!")
@@ -56,31 +56,6 @@
item_state = "crowbar"
toolspeed = 0.7
-/obj/item/crowbar/power
- name = "jaws of life"
- desc = "A set of jaws of life, compressed through the magic of science. It's fitted with a prying head."
- icon_state = "jaws_pry"
- item_state = "jawsoflife"
- lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
- materials = list(/datum/material/iron=150,/datum/material/silver=50,/datum/material/titanium=25)
-
- usesound = 'sound/items/jaws_pry.ogg'
- force = 15
- toolspeed = 0.7
-
-/obj/item/crowbar/power/suicide_act(mob/user)
- user.visible_message("[user] is putting [user.p_their()] head in [src], it looks like [user.p_theyre()] trying to commit suicide!")
- playsound(loc, 'sound/items/jaws_pry.ogg', 50, 1, -1)
- return (BRUTELOSS)
-
-/obj/item/crowbar/power/attack_self(mob/user)
- playsound(get_turf(user), 'sound/items/change_jaws.ogg', 50, 1)
- var/obj/item/wirecutters/power/cutjaws = new /obj/item/wirecutters/power(drop_location())
- to_chat(user, "You attach the cutting jaws to [src].")
- qdel(src)
- user.put_in_active_hand(cutjaws)
-
/obj/item/crowbar/cyborg
name = "hydraulic crowbar"
desc = "A hydraulic prying tool, simple but powerful."
@@ -88,4 +63,4 @@
icon_state = "crowbar_cyborg"
usesound = 'sound/items/jaws_pry.ogg'
force = 10
- toolspeed = 0.5
\ No newline at end of file
+ toolspeed = 0.5
diff --git a/code/game/objects/items/tools/powertools.dm b/code/game/objects/items/tools/powertools.dm
new file mode 100644
index 0000000000000..e4a5002da6b88
--- /dev/null
+++ b/code/game/objects/items/tools/powertools.dm
@@ -0,0 +1,158 @@
+/obj/item/powertool
+ name = "Power tool"
+ desc = "A basic powertool that does nothing."
+ icon = 'icons/obj/tools.dmi'
+ lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
+ w_class = WEIGHT_CLASS_SMALL
+ materials = list(/datum/material/iron=150,/datum/material/silver=50,/datum/material/titanium=25)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30, "stamina" = 0)
+ flags_1 = CONDUCT_1
+ slot_flags = ITEM_SLOT_BELT
+ toolspeed = 0.7
+
+/obj/item/powertool/attack_self(mob/user)
+ toggle_mode(user)
+
+/obj/item/powertool/proc/toggle_mode(mob/user)
+ return
+
+//Hand Drill
+
+/obj/item/powertool/hand_drill
+ name = "hand drill"
+ desc = "A simple powered hand drill. It's fitted with a screw bit."
+ icon_state = "drill_screw"
+ item_state = "drill"
+
+ force = 8 //might or might not be too high, subject to change
+ throwforce = 8
+ throw_speed = 2
+ throw_range = 3//it's heavier than a screw driver/wrench, so it does more damage, but can't be thrown as far
+
+ hitsound = 'sound/items/drill_hit.ogg'
+
+ tool_behaviour = TOOL_SCREWDRIVER
+ usesound = 'sound/items/drill_use.ogg'
+
+/obj/item/powertool/hand_drill/toggle_mode(mob/user)
+ playsound(get_turf(user), 'sound/items/change_drill.ogg', 50, 1)
+ if(tool_behaviour == TOOL_SCREWDRIVER)
+ balloon_alert(user, "Bolt driver bit attached")
+ become_wrench()
+ else
+ balloon_alert(user, "Screw driver bit attached")
+ become_screwdriver()
+
+/obj/item/powertool/hand_drill/proc/become_wrench()
+ icon_state = "drill_bolt"
+ tool_behaviour = TOOL_WRENCH
+
+ hitsound = null
+
+ attack_verb = list("attacked", "bashed", "battered", "bludgeoned", "whacked")
+ throw_range = 7
+
+/obj/item/powertool/hand_drill/proc/become_screwdriver()
+ icon_state = "drill_screw"
+ tool_behaviour = TOOL_SCREWDRIVER
+
+ hitsound = 'sound/items/drill_hit.ogg'
+
+ attack_verb = list("drilled", "screwed", "jabbed")
+ throw_range = 3
+
+/obj/item/powertool/hand_drill/suicide_act(mob/user)
+ if(tool_behaviour == TOOL_SCREWDRIVER)
+ user.visible_message("[user] is putting [src] to [user.p_their()] temple. It looks like [user.p_theyre()] trying to commit suicide!")
+ else
+ user.visible_message("[user] is pressing [src] against [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!")
+ return BRUTELOSS
+
+/obj/item/powertool/hand_drill/attack(mob/living/M, mob/living/user)
+ if(!istype(M) || tool_behaviour != TOOL_SCREWDRIVER)
+ return ..()
+ if(user.zone_selected != BODY_ZONE_PRECISE_EYES && user.zone_selected != BODY_ZONE_HEAD)
+ return ..()
+ if(HAS_TRAIT(user, TRAIT_PACIFISM))
+ to_chat(user, "You don't want to harm [M]!")
+ return
+ if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50))
+ M = user
+ return eyestab(M,user)
+
+//Jaws of life
+
+/obj/item/powertool/jaws_of_life
+ name = "jaws of life"
+ desc = "A set of jaws of life, compressed through the magic of science. It's fitted with a prying head."
+ usesound = 'sound/items/jaws_pry.ogg'
+ icon_state = "jaws_pry"
+ item_state = "jawsoflife"
+
+ tool_behaviour = TOOL_CROWBAR
+
+ force = 15
+ throwforce = 7
+ attack_verb = list("attacked", "bashed", "battered", "bludgeoned", "whacked")
+
+/obj/item/powertool/jaws_of_life/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_DOOR_PRYER, TRAIT_JAWS_OF_LIFE)
+
+/obj/item/powertool/jaws_of_life/toggle_mode(mob/user)
+ playsound(get_turf(user), 'sound/items/change_jaws.ogg', 50, 1)
+ if(tool_behaviour == TOOL_CROWBAR)
+ balloon_alert(user, "Cutting jaws attached")
+ become_wirecutters()
+ else
+ balloon_alert(user, "Prying jaws attached")
+ become_crowbar()
+
+/obj/item/powertool/jaws_of_life/proc/become_wirecutters()
+ icon_state = "jaws_cutter"
+ tool_behaviour = TOOL_WIRECUTTER
+
+ usesound = 'sound/items/jaws_cut.ogg'
+
+ attack_verb = list("pinched", "nipped")
+ force = 6
+ throw_speed = 3
+
+ REMOVE_TRAIT(src, TRAIT_DOOR_PRYER, TRAIT_JAWS_OF_LIFE)
+
+/obj/item/powertool/jaws_of_life/proc/become_crowbar()
+ icon_state = "jaws_pry"
+ tool_behaviour = TOOL_CROWBAR
+
+ usesound = 'sound/items/jaws_pry.ogg'
+
+ attack_verb = list("attacked", "bashed", "battered", "bludgeoned", "whacked")
+ force = 15
+ throw_speed = 2
+
+ ADD_TRAIT(src, TRAIT_DOOR_PRYER, TRAIT_JAWS_OF_LIFE)
+
+/obj/item/powertool/jaws_of_life/suicide_act(mob/user)
+ if(tool_behaviour == TOOL_CROWBAR)
+ user.visible_message("[user] is putting [user.p_their()] head in [src], it looks like [user.p_theyre()] trying to commit suicide!")
+ playsound(loc, 'sound/items/jaws_pry.ogg', 50, 1, -1)
+ else
+ user.visible_message("[user] is wrapping \the [src] around [user.p_their()] neck. It looks like [user.p_theyre()] trying to rip [user.p_their()] head off!")
+ playsound(loc, 'sound/items/jaws_cut.ogg', 50, 1, -1)
+ if(iscarbon(user))
+ var/mob/living/carbon/C = user
+ var/obj/item/bodypart/BP = C.get_bodypart(BODY_ZONE_HEAD)
+ if(BP)
+ BP.drop_limb()
+ playsound(loc,pick('sound/misc/desecration-01.ogg','sound/misc/desecration-02.ogg','sound/misc/desecration-01.ogg') ,50, 1, -1)
+ return BRUTELOSS
+
+/obj/item/powertool/jaws_of_life/attack(mob/living/carbon/C, mob/living/user)
+ if(tool_behaviour == TOOL_WIRECUTTER && istype(C) && C.handcuffed)
+ user.visible_message("[user] cuts [C]'s restraints with [src]!")
+ log_combat(user, C, "cut handcuffs from")
+ qdel(C.handcuffed)
+ return
+ else
+ ..()
diff --git a/code/game/objects/items/tools/screwdriver.dm b/code/game/objects/items/tools/screwdriver.dm
index b227e78888477..a69f47b9d3be8 100644
--- a/code/game/objects/items/tools/screwdriver.dm
+++ b/code/game/objects/items/tools/screwdriver.dm
@@ -19,7 +19,7 @@
usesound = list('sound/items/screwdriver.ogg', 'sound/items/screwdriver2.ogg')
tool_behaviour = TOOL_SCREWDRIVER
toolspeed = 1
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30, "stamina" = 0)
var/random_color = TRUE //if the screwdriver uses random coloring
var/static/list/screwdriver_colors = list(
"blue" = rgb(24, 97, 213),
@@ -104,36 +104,6 @@
/obj/item/screwdriver/abductor/get_belt_overlay()
return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "screwdriver_nuke")
-/obj/item/screwdriver/power
- name = "hand drill"
- desc = "A simple powered hand drill. It's fitted with a screw bit."
- icon_state = "drill_screw"
- item_state = "drill"
- lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
- materials = list(/datum/material/iron=150,/datum/material/silver=50,/datum/material/titanium=25) //done for balance reasons, making them high value for research, but harder to get
- force = 8 //might or might not be too high, subject to change
- w_class = WEIGHT_CLASS_SMALL
- throwforce = 8
- throw_speed = 2
- throw_range = 3//it's heavier than a screw driver/wrench, so it does more damage, but can't be thrown as far
- attack_verb = list("drilled", "screwed", "jabbed","whacked")
- hitsound = 'sound/items/drill_hit.ogg'
- usesound = 'sound/items/drill_use.ogg'
- toolspeed = 0.7
- random_color = FALSE
-
-/obj/item/screwdriver/power/suicide_act(mob/user)
- user.visible_message("[user] is putting [src] to [user.p_their()] temple. It looks like [user.p_theyre()] trying to commit suicide!")
- return(BRUTELOSS)
-
-/obj/item/screwdriver/power/attack_self(mob/user)
- playsound(get_turf(user),'sound/items/change_drill.ogg',50,1)
- var/obj/item/wrench/power/b_drill = new /obj/item/wrench/power(drop_location())
- to_chat(user, "You attach the bolt driver bit to [src].")
- qdel(src)
- user.put_in_active_hand(b_drill)
-
/obj/item/screwdriver/cyborg
name = "automated screwdriver"
desc = "A powerful automated screwdriver, designed to be both precise and quick."
@@ -142,4 +112,4 @@
hitsound = 'sound/items/drill_hit.ogg'
usesound = 'sound/items/drill_use.ogg'
toolspeed = 0.5
- random_color = FALSE
\ No newline at end of file
+ random_color = FALSE
diff --git a/code/game/objects/items/tools/weldingtool.dm b/code/game/objects/items/tools/weldingtool.dm
index ffc9ea66beadd..b6516c2fef237 100644
--- a/code/game/objects/items/tools/weldingtool.dm
+++ b/code/game/objects/items/tools/weldingtool.dm
@@ -1,4 +1,5 @@
-#define WELDER_FUEL_BURN_INTERVAL 13
+/// How many seconds between each fuel depletion tick ("use" proc)
+#define WELDER_FUEL_BURN_INTERVAL 9
/obj/item/weldingtool
name = "welding tool"
desc = "A standard edition welder provided by Nanotrasen."
@@ -18,7 +19,7 @@
throw_speed = 3
throw_range = 5
w_class = WEIGHT_CLASS_SMALL
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30, "stamina" = 0)
resistance_flags = FIRE_PROOF
materials = list(/datum/material/iron=70, /datum/material/glass=30)
@@ -60,7 +61,7 @@
return
-/obj/item/weldingtool/process()
+/obj/item/weldingtool/process(delta_time)
switch(welding)
if(0)
force = 3
@@ -73,7 +74,7 @@
if(1)
force = 15
damtype = "fire"
- ++burned_fuel_for
+ burned_fuel_for += delta_time
if(burned_fuel_for >= WELDER_FUEL_BURN_INTERVAL)
use(1)
update_icon()
@@ -126,7 +127,7 @@
return
if(!status && O.is_refillable())
reagents.trans_to(O, reagents.total_volume, transfered_by = user)
- to_chat(user, "You empty [src]'s fuel tank into [O].")
+ balloon_alert(user, "Fuel tank emptied")
update_icon()
if(isOn())
use(1)
@@ -163,8 +164,9 @@
if(!isOn() || !check_fuel())
return FALSE
- if(used)
+ if(used > 0)
burned_fuel_for = 0
+
if(get_fuel() >= used)
reagents.remove_reagent(/datum/reagent/fuel, used)
check_fuel()
@@ -189,12 +191,12 @@
//Switches the welder on
/obj/item/weldingtool/proc/switched_on(mob/user)
if(!status)
- to_chat(user, "[src] can't be turned on while unsecured!")
+ balloon_alert(user, "It can't be turned on while unsecured")
return
welding = !welding
if(welding)
if(get_fuel() >= 1)
- to_chat(user, "You switch [src] on.")
+ balloon_alert(user, "[src] turned on")
playsound(loc, acti_sound, 50, 1)
force = 15
damtype = "fire"
@@ -202,10 +204,10 @@
update_icon()
START_PROCESSING(SSobj, src)
else
- to_chat(user, "You need more fuel!")
+ balloon_alert(user, "No fuel")
switched_off(user)
else
- to_chat(user, "You switch [src] off.")
+ balloon_alert(user, "[src] turned off")
playsound(loc, deac_sound, 50, 1)
switched_off(user)
@@ -250,26 +252,26 @@
// If welding tool ran out of fuel during a construction task, construction fails.
/obj/item/weldingtool/tool_use_check(mob/living/user, amount)
if(!isOn() || !check_fuel())
- to_chat(user, "[src] has to be on to complete this task!")
+ balloon_alert(user, "[src] has to be on")
return FALSE
if(get_fuel() >= amount)
return TRUE
else
- to_chat(user, "You need more welding fuel to complete this task!")
+ balloon_alert(user, "Not enough fuel to complete this task")
return FALSE
/obj/item/weldingtool/proc/flamethrower_screwdriver(obj/item/I, mob/user)
if(welding)
- to_chat(user, "Turn it off first!")
+ balloon_alert(user, "[src] should be turned off")
return
status = !status
if(status)
- to_chat(user, "You resecure [src] and close the fuel tank.")
+ balloon_alert(user, "[src] secured and fuel tank closed")
DISABLE_BITFIELD(reagents.flags, OPENCONTAINER)
else
- to_chat(user, "[src] can now be attached, modified, and refuelled.")
+ balloon_alert(user, "[src] can now be attached, modified, and refuelled")
ENABLE_BITFIELD(reagents.flags, OPENCONTAINER)
add_fingerprint(user)
@@ -282,10 +284,10 @@
user.transferItemToLoc(src, F, TRUE)
F.weldtool = src
add_fingerprint(user)
- to_chat(user, "You add a rod to a welder, starting to build a flamethrower.")
+ balloon_alert(user, "You start bulding flamethrower")
user.put_in_hands(F)
else
- to_chat(user, "You need one rod to start building a flamethrower!")
+ balloon_alert(user, "You need one rod to build flamethrower")
/obj/item/weldingtool/ignition_effect(atom/A, mob/user)
if(use_tool(A, user, 0, amount=1))
@@ -301,7 +303,7 @@
materials = list(/datum/material/glass=60)
/obj/item/weldingtool/largetank/flamethrower_screwdriver()
- return
+ return
/obj/item/weldingtool/largetank/cyborg
name = "integrated welding tool"
@@ -371,10 +373,10 @@
item_state = "brasswelder"
-/obj/item/weldingtool/experimental/process()
+/obj/item/weldingtool/experimental/process(delta_time)
..()
if(get_fuel() < max_fuel && nextrefueltick < world.time)
nextrefueltick = world.time + 10
- reagents.add_reagent(/datum/reagent/fuel, 1)
+ reagents.add_reagent(/datum/reagent/fuel, 0.5*delta_time)
-#undef WELDER_FUEL_BURN_INTERVAL
\ No newline at end of file
+#undef WELDER_FUEL_BURN_INTERVAL
diff --git a/code/game/objects/items/tools/wirecutters.dm b/code/game/objects/items/tools/wirecutters.dm
index 9a1d10cb7a272..9ce9820e3594d 100644
--- a/code/game/objects/items/tools/wirecutters.dm
+++ b/code/game/objects/items/tools/wirecutters.dm
@@ -19,7 +19,7 @@
tool_behaviour = TOOL_WIRECUTTER
toolspeed = 1
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30, "stamina" = 0)
var/random_color = TRUE
var/static/list/wirecutter_colors = list(
"blue" = "#1861d5",
@@ -83,47 +83,10 @@
random_color = FALSE
-/obj/item/wirecutters/power
- name = "jaws of life"
- desc = "A set of jaws of life, compressed through the magic of science. It's fitted with a cutting head."
- icon_state = "jaws_cutter"
- item_state = "jawsoflife"
-
- materials = list(/datum/material/iron=150,/datum/material/silver=50,/datum/material/titanium=25)
- usesound = 'sound/items/jaws_cut.ogg'
- toolspeed = 0.7
- random_color = FALSE
-
-/obj/item/wirecutters/power/suicide_act(mob/user)
- user.visible_message("[user] is wrapping \the [src] around [user.p_their()] neck. It looks like [user.p_theyre()] trying to rip [user.p_their()] head off!")
- playsound(loc, 'sound/items/jaws_cut.ogg', 50, 1, -1)
- if(iscarbon(user))
- var/mob/living/carbon/C = user
- var/obj/item/bodypart/BP = C.get_bodypart(BODY_ZONE_HEAD)
- if(BP)
- BP.drop_limb()
- playsound(loc,pick('sound/misc/desceration-01.ogg','sound/misc/desceration-02.ogg','sound/misc/desceration-01.ogg') ,50, 1, -1)
- return (BRUTELOSS)
-
-/obj/item/wirecutters/power/attack_self(mob/user)
- playsound(get_turf(user), 'sound/items/change_jaws.ogg', 50, 1)
- var/obj/item/crowbar/power/pryjaws = new /obj/item/crowbar/power(drop_location())
- to_chat(user, "You attach the pry jaws to [src].")
- qdel(src)
- user.put_in_active_hand(pryjaws)
-
-/obj/item/wirecutters/power/attack(mob/living/carbon/C, mob/user)
- if(istype(C) && C.handcuffed)
- user.visible_message("[user] cuts [C]'s restraints with [src]!")
- qdel(C.handcuffed)
- return
- else
- ..()
-
/obj/item/wirecutters/cyborg
name = "powered wirecutters"
desc = "Cuts wires with the power of ELECTRICITY. Faster than normal wirecutters."
icon = 'icons/obj/items_cyborg.dmi'
icon_state = "wirecutters_cyborg"
toolspeed = 0.5
- random_color = FALSE
\ No newline at end of file
+ random_color = FALSE
diff --git a/code/game/objects/items/tools/wrench.dm b/code/game/objects/items/tools/wrench.dm
index 470c2b5d6fccc..6b696ba8f3687 100644
--- a/code/game/objects/items/tools/wrench.dm
+++ b/code/game/objects/items/tools/wrench.dm
@@ -17,7 +17,7 @@
attack_verb = list("bashed", "battered", "bludgeoned", "whacked")
tool_behaviour = TOOL_WRENCH
toolspeed = 1
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30, "stamina" = 0)
/obj/item/wrench/suicide_act(mob/user)
user.visible_message("[user] is beating [user.p_them()]self to death with [src]! It looks like [user.p_theyre()] trying to commit suicide!")
@@ -39,34 +39,6 @@
usesound = 'sound/effects/empulse.ogg'
toolspeed = 0.1
-
-/obj/item/wrench/power
- name = "hand drill"
- desc = "A simple powered hand drill. It's fitted with a bolt bit."
- icon_state = "drill_bolt"
- item_state = "drill"
- lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
- usesound = 'sound/items/drill_use.ogg'
- materials = list(/datum/material/iron=150,/datum/material/silver=50,/datum/material/titanium=25)
- //done for balance reasons, making them high value for research, but harder to get
- force = 8 //might or might not be too high, subject to change
- w_class = WEIGHT_CLASS_SMALL
- throwforce = 8
- attack_verb = list("drilled", "screwed", "jabbed")
- toolspeed = 0.7
-
-/obj/item/wrench/power/attack_self(mob/user)
- playsound(get_turf(user),'sound/items/change_drill.ogg',50,1)
- var/obj/item/wirecutters/power/s_drill = new /obj/item/screwdriver/power(drop_location())
- to_chat(user, "You attach the screw driver bit to [src].")
- qdel(src)
- user.put_in_active_hand(s_drill)
-
-/obj/item/wrench/power/suicide_act(mob/user)
- user.visible_message("[user] is pressing [src] against [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!")
- return (BRUTELOSS)
-
/obj/item/wrench/medical
name = "medical wrench"
desc = "A medical wrench with common(medical?) uses. Can be found in your hand."
@@ -107,4 +79,4 @@
desc = "An advanced robotic wrench, powered by internal hydraulics. Twice as fast as the handheld version."
icon = 'icons/obj/items_cyborg.dmi'
icon_state = "wrench_cyborg"
- toolspeed = 0.5
\ No newline at end of file
+ toolspeed = 0.5
diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm
index ccb616335f520..6114c53084c43 100644
--- a/code/game/objects/items/toys.dm
+++ b/code/game/objects/items/toys.dm
@@ -152,7 +152,7 @@
icon_state = "singularity_s1"
/*
- * Toy gun: Why isnt this an /obj/item/gun?
+ * Toy gun: Why isn't this an /obj/item/gun?
*/
/obj/item/toy/gun
name = "cap gun"
@@ -204,7 +204,7 @@
return
src.add_fingerprint(user)
if (src.bullets < 1)
- user.show_message("*click*", 2)
+ user.show_message("*click*", MSG_AUDIBLE)
playsound(src, 'sound/weapons/gun_dry_fire.ogg', 30, TRUE)
return
playsound(user, 'sound/weapons/gunshot.ogg', 100, 1)
@@ -275,7 +275,7 @@
return
else
to_chat(user, "You attach the ends of the two plastic swords, making a single double-bladed toy! You're fake-cool.")
- var/obj/item/twohanded/dualsaber/toy/newSaber = new /obj/item/twohanded/dualsaber/toy(user.loc)
+ var/obj/item/dualsaber/toy/newSaber = new /obj/item/dualsaber/toy(user.loc)
if(hacked) // That's right, we'll only check the "original" "sword".
newSaber.hacked = TRUE
newSaber.item_color = "rainbow"
@@ -361,29 +361,28 @@
/*
* Subtype of Double-Bladed Energy Swords
*/
-/obj/item/twohanded/dualsaber/toy
+/obj/item/dualsaber/toy
name = "double-bladed toy sword"
desc = "A cheap, plastic replica of TWO energy swords. Double the fun!"
force = 0
throwforce = 0
throw_speed = 3
throw_range = 5
- force_unwielded = 0
- force_wielded = 0
+ twohand_force = 0
attack_verb = list("attacked", "struck", "hit")
block_upgrade_walk = 1
block_power = -100
-/obj/item/twohanded/dualsaber/toy/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
+/obj/item/dualsaber/toy/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
return 0
-/obj/item/twohanded/dualsaber/toy/IsReflect()//Stops Toy Dualsabers from reflecting energy projectiles
+/obj/item/dualsaber/toy/IsReflect() //Stops Toy Dualsabers from reflecting energy projectiles
return 0
-/obj/item/twohanded/dualsaber/toy/impale(mob/living/user)//Stops Toy Dualsabers from injuring clowns
+/obj/item/dualsaber/toy/impale(mob/living/user)//Stops Toy Dualsabers from injuring clowns
to_chat(user, "You twirl around a bit before losing your balance and impaling yourself on [src].")
user.adjustStaminaLoss(25)
-
+
/obj/item/toy/katana
name = "replica katana"
desc = "Woefully underpowered in D20."
@@ -662,7 +661,6 @@
var/card_throw_range = 7
var/list/card_attack_verb = list("attacked")
var/card_sharpness
- var/card_embed_chance = 0
/obj/item/toy/cards/suicide_act(mob/living/carbon/user)
user.visible_message("[user] is slitting [user.p_their()] wrists with \the [src]! It looks like [user.p_they()] [user.p_have()] a crummy hand!")
@@ -724,7 +722,7 @@
if(!(L.mobility_flags & MOBILITY_PICKUP))
return
var/choice = null
- if(cards.len == 0)
+ if(!LAZYLEN(cards))
to_chat(user, "There are no more cards to draw!")
return
var/obj/item/toy/cards/singlecard/H = new/obj/item/toy/cards/singlecard(user.loc)
@@ -735,27 +733,27 @@
H.parentdeck = src
var/O = src
H.apply_card_vars(H,O)
- src.cards.Cut(1,2) //Removes the top card from the list
+ cards.Cut(1,2) //Removes the top card from the list
H.pickup(user)
user.put_in_hands(H)
- user.visible_message("[user] draws a card from the deck.", "You draw a card from the deck.")
+ user.visible_message("[user] draws a card from the deck.", "You draw a card from the deck.")
update_icon()
/obj/item/toy/cards/deck/update_icon()
- if(cards.len > original_size/2)
+ if(LAZYLEN(cards) > original_size/2)
icon_state = "deck_[deckstyle]_full"
- else if(cards.len > original_size/4)
+ else if(LAZYLEN(cards) > original_size/4)
icon_state = "deck_[deckstyle]_half"
- else if(cards.len > 0)
+ else if(LAZYLEN(cards))
icon_state = "deck_[deckstyle]_low"
- else if(cards.len == 0)
+ else if(!LAZYLEN(cards))
icon_state = "deck_[deckstyle]_empty"
/obj/item/toy/cards/deck/attack_self(mob/user)
if(cooldown < world.time - 50)
cards = shuffle(cards)
playsound(src, 'sound/items/cardshuffle.ogg', 50, 1)
- user.visible_message("[user] shuffles the deck.", "You shuffle the deck.")
+ user.visible_message("[user] shuffles the deck.", "You shuffle the deck.")
cooldown = world.time
/obj/item/toy/cards/deck/attackby(obj/item/I, mob/living/user, params)
@@ -766,7 +764,7 @@
to_chat(user, "The card is stuck to your hand, you can't add it to the deck!")
return
cards += SC.cardname
- user.visible_message("[user] adds a card to the bottom of the deck.","You add the card to the bottom of the deck.")
+ user.visible_message("[user] adds a card to the bottom of the deck.","You add the card to the bottom of the deck.")
qdel(SC)
else
to_chat(user, "You can't mix cards from other decks!")
@@ -778,7 +776,7 @@
to_chat(user, "The hand of cards is stuck to your hand, you can't add it to the deck!")
return
cards += CH.currenthand
- user.visible_message("[user] puts [user.p_their()] hand of cards in the deck.", "You put the hand of cards in the deck.")
+ user.visible_message("[user] puts [user.p_their()] hand of cards in the deck.", "You put the hand of cards in the deck.")
qdel(CH)
else
to_chat(user, "You can't mix cards from other decks!")
@@ -796,8 +794,8 @@
M.put_in_hands(src)
to_chat(usr, "You pick up the deck.")
- else if(istype(over_object, /obj/screen/inventory/hand))
- var/obj/screen/inventory/hand/H = over_object
+ else if(istype(over_object, /atom/movable/screen/inventory/hand))
+ var/atom/movable/screen/inventory/hand/H = over_object
if(M.putItemFromInventoryInHandIfPossible(src, H.held_index))
to_chat(usr, "You pick up the deck.")
@@ -869,7 +867,7 @@
if(istype(C))
if(C.parentdeck == src.parentdeck)
src.currenthand += C.cardname
- user.visible_message("[user] adds a card to [user.p_their()] hand.", "You add the [C.cardname] to your hand.")
+ user.visible_message("[user] adds a card to [user.p_their()] hand.", "You add the [C.cardname] to your hand.")
qdel(C)
interact(user)
update_sprite()
@@ -911,17 +909,17 @@
/obj/item/toy/cards/singlecard/apply_card_vars(obj/item/toy/cards/singlecard/newobj,obj/item/toy/cards/sourceobj)
..()
- newobj.card_embed_chance = sourceobj.card_embed_chance
- newobj.embedding = newobj.embedding.setRating(embed_chance = card_embed_chance)
+ newobj.embedding = sourceobj.embedding
newobj.card_sharpness = sourceobj.card_sharpness
- newobj.sharpness = newobj.card_sharpness
+ newobj.sharpness = sourceobj.card_sharpness
+ newobj.updateEmbedding()
/obj/item/toy/cards/singlecard/examine(mob/user)
. = ..()
if(ishuman(user))
var/mob/living/carbon/human/cardUser = user
if(cardUser.is_holding(src))
- cardUser.visible_message("[cardUser] checks [cardUser.p_their()] card.", "The card reads: [cardname].")
+ cardUser.visible_message("[cardUser] checks [cardUser.p_their()] card.", "The card reads: [cardname].")
else
. += "You need to have the card in your hand to check it!"
@@ -968,7 +966,7 @@
var/obj/item/toy/cards/cardhand/H = I
if(H.parentdeck == parentdeck)
H.currenthand += cardname
- user.visible_message("[user] adds a card to [user.p_their()] hand.", "You add the [cardname] to your hand.")
+ user.visible_message("[user] adds a card to [user.p_their()] hand.", "You add the [cardname] to your hand.")
qdel(src)
H.interact(user)
H.update_sprite()
@@ -1013,7 +1011,7 @@
card_force = 5
card_throwforce = 12
card_throw_speed = 6
- card_embed_chance = 80
+ embedding = list("pain_mult" = 1, "embed_chance" = 80, "max_damage_mult" = 8, "fall_chance" = 0, "embed_chance_turf_mod" = 15, "armour_block" = 60) //less painful than throwing stars
card_sharpness = IS_SHARP
card_throw_range = 7
card_attack_verb = list("attacked", "sliced", "diced", "slashed", "cut")
@@ -1181,9 +1179,7 @@
var/list/possible_sounds = list('sound/voice/hiss1.ogg', 'sound/voice/hiss2.ogg', 'sound/voice/hiss3.ogg', 'sound/voice/hiss4.ogg')
var/chosen_sound = pick(possible_sounds)
playsound(get_turf(src), chosen_sound, 50, 1)
- spawn(45)
- if(src)
- icon_state = "[initial(icon_state)]"
+ addtimer(VARSET_CALLBACK(src, icon_state, initial(icon_state)), 45)
else
to_chat(user, "The string on [src] hasn't rewound all the way!")
return
@@ -1429,7 +1425,7 @@
to_chat(user, "You name the dummy as \"[doll_name]\"")
name = "[initial(name)] - [doll_name]"
-/obj/item/toy/dummy/talk_into(atom/movable/A, message, channel, list/spans, datum/language/language)
+/obj/item/toy/dummy/talk_into(atom/movable/A, message, channel, list/spans, datum/language/language, list/message_mods)
var/mob/M = A
if (istype(M))
M.log_talk(message, LOG_SAY, tag="dummy toy")
@@ -1439,7 +1435,7 @@
/obj/item/toy/dummy/GetVoice()
return doll_name
-
+
/*
* Eldrich Toys
*/
@@ -1452,18 +1448,18 @@
w_class = WEIGHT_CLASS_SMALL
attack_verb = list("sacrificed", "transmuted", "grasped", "cursed")
var/open = FALSE
-
+
/obj/item/toy/eldrich_book/attack_self(mob/user)
open = !open
update_icon()
/obj/item/toy/eldrich_book/update_icon()
icon_state = open ? "book_open" : "book"
-
+
/*
* Fake tear
*/
-
+
/obj/item/toy/reality_pierce
name = "Pierced reality"
desc = "Hah. You thought it was the real deal!"
@@ -1476,4 +1472,201 @@
/obj/item/storage/box/heretic_asshole/PopulateContents()
for(var/i in 1 to rand(1,4))
- new /obj/item/toy/reality_pierce(src)
\ No newline at end of file
+ new /obj/item/toy/reality_pierce(src)
+
+// Serviceborg items
+
+/*
+|| Cyborg playing cards module. ||
+*/
+
+/obj/item/toy/cards/deck/cyborg
+ name = "dealer module"
+ desc = "A module for handling, fabricating cards and tricking suckers into gambling awaya their money. Ctrl Click to fabricate a new set of cards."
+
+/obj/item/toy/cards/deck/cyborg/update_icon()
+ icon_state = "deck_[deckstyle]_full"
+
+/obj/item/toy/cards/deck/cyborg/CtrlClick(mob/user)
+ ..()
+ if(iscyborg(user))
+ var/mob/living/silicon/robot/R = user
+ if(R.cell?.use(300))
+ populate_deck()
+ to_chat(user, "You fabricate a new set of cards.")
+
+/obj/item/toy/cards/deck/cyborg/afterattack(atom/A, mob/user, proximity)
+ . = ..()
+ if (istype(A, /obj/item/toy/cards/singlecard))
+ var/obj/item/toy/cards/singlecard/SC = A
+ if(SC.parentdeck == src)
+ if(!user.temporarilyRemoveItemFromInventory(SC))
+ to_chat(user, "The card is stuck to your hand, you can't add it to the deck!")
+ return
+ cards += SC.cardname
+ user.visible_message("[user] adds a card to the bottom of the deck.","You add the card to the bottom of the deck.")
+ qdel(SC)
+ else
+ to_chat(user, "You can't mix cards from other decks!")
+ update_icon()
+ else if (istype(A, /obj/item/toy/cards/cardhand))
+ var/obj/item/toy/cards/cardhand/CH = A
+ if(CH.parentdeck == src)
+ cards += CH.currenthand
+ user.visible_message("[user] puts [user.p_their()] hand of cards in the deck.", "You put the hand of cards in the deck.")
+ qdel(CH)
+ else
+ to_chat(user, "You can't mix cards from other decks!")
+ update_icon()
+
+ var/choice = null
+ if(!LAZYLEN(cards))
+ to_chat(user, "There are no more cards to draw!")
+ return
+
+ choice = cards[1]
+ var/obj/item/toy/cards/singlecard/H = new/obj/item/toy/cards/singlecard(get_turf(A))
+ H.cardname = choice
+ H.parentdeck = src
+ var/O = src
+ H.apply_card_vars(H,O)
+ cards.Cut(1,2) //Removes the top card from the list
+
+ if(!proximity)
+ H.forceMove(get_turf(src))
+ H.throw_at(get_turf(A), 10 , 1 , user)
+
+////////////////////
+//money eater/maker//
+////////////////////
+
+/obj/item/gobbler
+ name = "Coin Gobbler"
+ desc = "Feed it credits, and activate it, with a chance to spit out DOUBLE the amount!"
+ icon = 'icons/obj/plushes.dmi'
+ icon_state = "debug"
+ var/money = 0
+ var/moneyeaten = 0
+ var/cooldown = 0
+ var/cooldowndelay = 20
+ w_class = WEIGHT_CLASS_NORMAL
+
+/obj/item/gobbler/examine(mob/user)
+ . = ..()
+ . += "The Coin Gobbler holds [money] credits."
+
+/obj/item/gobbler/attackby()
+ return
+
+/obj/item/gobbler/attack_self(mob/user)
+ if(cooldown > world.time)
+ return
+ cooldown = world.time + cooldowndelay
+ if (money<=0)
+ to_chat(user, "The [src] has no money stored.")
+ return
+
+ playsound(src.loc, 'sound/creatures/rattle.ogg', 10, 1)
+ user.visible_message("[src]'s eyes start spinning! What will happen?", \
+ "You activate [src].")
+ sleep(10)
+
+ if(prob(33*(777+moneyeaten-money)/777))
+ playsound(src.loc, 'sound/arcade/win.ogg', 10, 1)
+ user.visible_message("[src] cashes out! [user] starts spitting credits!", \
+ "[src] cashes out!")
+ var/obj/item/holochip/payout = new (user.drop_location(), money*2)
+ payout.throw_at( get_step(loc,user.dir) ,3,1,user)
+ moneyeaten-=money
+ money=0
+ else
+ user.visible_message("[src] gobbles up all the money!", \
+ "[src] gobbles up all the money!")
+ moneyeaten+=money
+ money=0
+ playsound(src.loc, 'sound/machines/buzz-sigh.ogg', 10, 1)
+
+/obj/item/gobbler/afterattack(atom/A, mob/user, proximity)
+ . = ..()
+ if(!proximity)
+ return
+ var/cash_money = 0
+
+ if(istype(A, /obj/item/holochip))
+ var/obj/item/holochip/HC = A
+ cash_money = HC.get_item_credit_value()
+ else if(istype(A, /obj/item/stack/spacecash))
+ var/obj/item/stack/spacecash/SC = A
+ cash_money = SC.get_item_credit_value()
+ else if(istype(A, /obj/item/coin))
+ var/obj/item/coin/CN = A
+ cash_money = CN.get_item_credit_value()
+
+ if (!cash_money)
+ to_chat(user, "[src] spits out [A] as it is not worth anything!")
+ return
+ money+=cash_money
+ to_chat(user, "[src] quicky gobbles up [A], and the value goes up by [cash_money].")
+ qdel(A)
+
+/obj/item/dance_trance
+ name = "Dance Fever"
+ desc = "Makes everyone dance!"
+ icon = 'icons/obj/grenade.dmi'
+ icon_state = "disco_active"
+ var/flip_cooldown = 0
+
+/obj/item/dance_trance/attack()
+ if(flip_cooldown < world.time)
+ flip_mobs()
+ return ..()
+
+/obj/item/dance_trance/attack_self(mob/user)
+ if(flip_cooldown < world.time)
+ flip_mobs()
+ ..()
+
+/obj/item/dance_trance/proc/flip_mobs(mob/living/carbon/M, mob/user)
+ for(M in ohearers(7, get_turf(src)))
+ if(ishuman(M) && M.can_hear())
+ var/mob/living/carbon/human/H = M
+ if(istype(H.ears, /obj/item/clothing/ears/earmuffs))
+ continue
+ switch (rand(1,3))
+ if (1)
+ M.emote("flip")
+ M.emote("spin")
+ if (2)
+ M.emote("flip")
+ if (3)
+ M.emote("spin")
+ flip_cooldown = world.time + 20
+
+
+/obj/item/storage/pill_bottle/dice_cup/cyborg
+ desc = "The house always wins..."
+/obj/item/storage/pill_bottle/dice_cup/cyborg/Initialize()
+ . = ..()
+ new /obj/item/dice/d6(src)
+ new /obj/item/dice/d6(src)
+
+
+/obj/item/storage/box/yatzy
+ name = "Game of Yatzy"
+ desc = "Contains all the pieces required to play a game of Yatzy with up to 4 friends!"
+
+/obj/item/storage/box/yatzy/PopulateContents()
+ new /obj/item/storage/pill_bottle/dice_cup/yatzy(src)
+ new /obj/item/paper/yatzy(src)
+ new /obj/item/paper/yatzy(src)
+ new /obj/item/paper/yatzy(src)
+ new /obj/item/paper/yatzy(src)
+
+/obj/item/storage/pill_bottle/dice_cup/yatzy/Initialize()
+ . = ..()
+ for(var/dice in 1 to 5)
+ new /obj/item/dice/d6(src)
+
+/obj/item/paper/yatzy
+ name = "paper - Yatzy Table"
+ info = "
Upper
Game 1
Game 2
Game 3
Aces
\[___\]
\[___\]
\[___\]
Twos
\[___\]
\[___\]
\[___\]
Threes
\[___\]
\[___\]
\[___\]
Fours
\[___\]
\[___\]
\[___\]
Fives
\[___\]
\[___\]
\[___\]
Sixes
\[___\]
\[___\]
\[___\]
Total
\[___\]
\[___\]
\[___\]
Upper Total
\[___\]
\[___\]
\[___\]
Bonus
\[___\]
\[___\]
\[___\]
1 Pair
\[___\]
\[___\]
\[___\]
2 Pairs
\[___\]
\[___\]
\[___\]
3 of a Kind
\[___\]
\[___\]
\[___\]
4 of a Kind
\[___\]
\[___\]
\[___\]
Full House
\[___\]
\[___\]
\[___\]
Sm. Straight
\[___\]
\[___\]
\[___\]
Lg. Straight
\[___\]
\[___\]
\[___\]
Yatzy
\[___\]
\[___\]
\[___\]
Chance
\[___\]
\[___\]
\[___\]
Lower Total
\[___\]
\[___\]
\[___\]
Grand Total
\[___\]
\[___\]
\[___\]
"
diff --git a/code/game/objects/items/trash.dm b/code/game/objects/items/trash.dm
index ba6c51e744cdd..24c9ed862406f 100644
--- a/code/game/objects/items/trash.dm
+++ b/code/game/objects/items/trash.dm
@@ -86,6 +86,11 @@
name = "Maintenance Peaches"
icon_state = "peachcanmaint_empty"
+/obj/item/trash/can/food/beefbroth
+ name = "canned beef broth"
+ icon = 'icons/obj/food/food.dmi'
+ icon_state = "beefcan_empty"
+
/obj/item/trash/can/food/beans
name = "tin of beans"
icon = 'icons/obj/food/food.dmi'
diff --git a/code/game/objects/items/twohanded.dm b/code/game/objects/items/twohanded.dm
deleted file mode 100644
index 70f2f1f3824fe..0000000000000
--- a/code/game/objects/items/twohanded.dm
+++ /dev/null
@@ -1,1046 +0,0 @@
-/* Two-handed Weapons
- * Contains:
- * Twohanded
- * Fireaxe
- * Double-Bladed Energy Swords
- * Spears
- * CHAINSAWS
- * Bone Axe and Spear
- */
-
-/*##################################################################
-##################### TWO HANDED WEAPONS BE HERE~ -Agouri :3 ########
-####################################################################*/
-
-//Rewrote TwoHanded weapons stuff and put it all here. Just copypasta fireaxe to make new ones ~Carn
-//This rewrite means we don't have two variables for EVERY item which are used only by a few weapons.
-//It also tidies stuff up elsewhere.
-
-
-
-
-/*
- * Twohanded
- */
-/obj/item/twohanded
- var/wielded = 0
- var/force_unwielded = 0
- var/force_wielded = 0
- var/block_power_wielded = 0
- var/block_power_unwielded = 0
- var/wieldsound = null
- var/unwieldsound = null
-
-/obj/item/twohanded/proc/unwield(mob/living/carbon/user, show_message = TRUE)
- if(!wielded || !user)
- return
- wielded = 0
-
- if(!isnull(force_unwielded))
- force = force_unwielded
-
- if(!isnull(block_power_unwielded))
- block_power = block_power_unwielded
-
- var/sf = findtext(name, " (Wielded)", -10)//10 == length(" (Wielded)")
- if(sf)
- name = copytext(name, 1, sf)
- else //something wrong
- name = "[initial(name)]"
- update_icon()
- if(user.get_item_by_slot(SLOT_BACK) == src)
- user.update_inv_back()
- else
- user.update_inv_hands()
- if(show_message)
- if(iscyborg(user))
- to_chat(user, "You free up your module.")
- else
- to_chat(user, "You are now carrying [src] with one hand.")
- if(unwieldsound)
- playsound(loc, unwieldsound, 50, 1)
- var/obj/item/twohanded/offhand/O = user.get_inactive_held_item()
- if(O && istype(O))
- O.unwield()
- return
-
-/obj/item/twohanded/proc/wield(mob/living/carbon/user)
- if(wielded)
- return
- if(ismonkey(user))
- to_chat(user, "It's too heavy for you to wield fully.")
- return
- if(user.get_inactive_held_item())
- to_chat(user, "You need your other hand to be empty!")
- return
- if(user.get_num_arms() < 2)
- to_chat(user, "You don't have enough intact hands.")
- return
- wielded = 1
- if(force_wielded)
- force = force_wielded
- if(block_power_wielded)
- block_power = block_power_wielded
- name = "[name] (Wielded)"
- update_icon()
- if(iscyborg(user))
- to_chat(user, "You dedicate your module to [src].")
- else
- to_chat(user, "You grab [src] with both hands.")
- if (wieldsound)
- playsound(loc, wieldsound, 50, 1)
- var/obj/item/twohanded/offhand/O = new(user) ////Let's reserve his other hand~
- O.name = "[name] - offhand"
- O.desc = "Your second grip on [src]."
- O.wielded = TRUE
- user.put_in_inactive_hand(O)
- return
-
-/obj/item/twohanded/dropped(mob/user)
- . = ..()
- //handles unwielding a twohanded weapon when dropped as well as clearing up the offhand
- if(!wielded)
- return
- unwield(user)
-
-/obj/item/twohanded/update_icon()
- return
-
-/obj/item/twohanded/attack_self(mob/user)
- . = ..()
- if(wielded) //Trying to unwield it
- unwield(user)
- else //Trying to wield it
- wield(user)
-
-/obj/item/twohanded/equip_to_best_slot(mob/M)
- if(..())
- if(istype(src, /obj/item/twohanded/required))
- return // unwield forces twohanded-required items to be dropped.
- unwield(M)
- return
-
-/obj/item/twohanded/equipped(mob/user, slot)
- ..()
- if(!user.is_holding(src) && wielded && !istype(src, /obj/item/twohanded/required))
- unwield(user)
-
-///////////OFFHAND///////////////
-/obj/item/twohanded/offhand
- name = "offhand"
- icon_state = "offhand"
- w_class = WEIGHT_CLASS_HUGE
- item_flags = ABSTRACT
- resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
-
-/obj/item/twohanded/offhand/Destroy()
- wielded = FALSE
- return ..()
-
-/obj/item/twohanded/offhand/dropped(mob/living/user, show_message = TRUE) //Only utilized by dismemberment since you can't normally switch to the offhand to drop it.
- var/obj/I = user.get_active_held_item()
- if(I && istype(I, /obj/item/twohanded))
- var/obj/item/twohanded/thw = I
- thw.unwield(user, show_message)
- if(istype(thw, /obj/item/twohanded/required))
- user.dropItemToGround(thw)
- if(!QDELETED(src))
- qdel(src)
-
-/obj/item/twohanded/offhand/unwield()
- if(wielded)//Only delete if we're wielded
- wielded = FALSE
- qdel(src)
-
-/obj/item/twohanded/offhand/wield()
- if(wielded)//Only delete if we're wielded
- wielded = FALSE
- qdel(src)
-
-/obj/item/twohanded/offhand/attack_self(mob/living/carbon/user) //You should never be able to do this in standard use of two handed items. This is a backup for lingering offhands.
- var/obj/item/twohanded/O = user.get_inactive_held_item()
- if (istype(O) && !istype(O, /obj/item/twohanded/offhand/)) //If you have a proper item in your other hand that the offhand is for, do nothing. This should never happen.
- return
- if (QDELETED(src))
- return
- qdel(src) //If it's another offhand, or literally anything else, qdel. If I knew how to add logging messages I'd put one here.
-
-///////////Two hand required objects///////////////
-//This is for objects that require two hands to even pick up
-/obj/item/twohanded/required
- w_class = WEIGHT_CLASS_HUGE
-
-/obj/item/twohanded/required/attack_self()
- return
-
-/obj/item/twohanded/required/mob_can_equip(mob/M, mob/equipper, slot, disable_warning = 0)
- if(wielded && !slot_flags)
- if(!disable_warning)
- to_chat(M, "[src] is too cumbersome to carry with anything but your hands!")
- return 0
- return ..()
-
-/obj/item/twohanded/required/attack_hand(mob/user)//Can't even pick it up without both hands empty
- var/obj/item/twohanded/required/H = user.get_inactive_held_item()
- if(get_dist(src,user) > 1)
- return
- if(H != null)
- to_chat(user, "[src] is too cumbersome to carry in one hand!")
- return
- if(loc != user)
- wield(user)
- . = ..()
-
-/obj/item/twohanded/required/equipped(mob/user, slot)
- ..()
- var/slotbit = slotdefine2slotbit(slot)
- if(slot_flags & slotbit)
- var/datum/O = user.is_holding_item_of_type(/obj/item/twohanded/offhand)
- if(!O || QDELETED(O))
- return
- qdel(O)
- return
- if(slot == SLOT_HANDS)
- wield(user)
- else
- unwield(user)
-
-/obj/item/twohanded/required/dropped(mob/living/user, show_message = TRUE)
- unwield(user, show_message)
- ..()
-
-/obj/item/twohanded/required/wield(mob/living/carbon/user)
- ..()
- if(!wielded)
- user.dropItemToGround(src)
-
-/obj/item/twohanded/required/unwield(mob/living/carbon/user, show_message = TRUE)
- if(!wielded)
- return
- if(show_message)
- to_chat(user, "You drop [src].")
- ..(user, FALSE)
-
-/*
- * Fireaxe
- */
-/obj/item/twohanded/fireaxe // DEM AXES MAN, marker -Agouri
- icon_state = "fireaxe0"
- lefthand_file = 'icons/mob/inhands/weapons/axes_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/axes_righthand.dmi'
- name = "fire axe"
- desc = "Truly, the weapon of a madman. Who would think to fight fire with an axe?"
- attack_weight = 3
- block_power_wielded = 25
- block_upgrade_walk = 1
- force = 5
- throwforce = 15
- w_class = WEIGHT_CLASS_BULKY
- slot_flags = ITEM_SLOT_BACK
- force_unwielded = 5
- force_wielded = 24
- attack_verb = list("attacked", "chopped", "cleaved", "tore", "cut")
- hitsound = 'sound/weapons/bladeslice.ogg'
- sharpness = IS_SHARP
- max_integrity = 200
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30)
- resistance_flags = FIRE_PROOF
-
-/obj/item/twohanded/fireaxe/Initialize()
- . = ..()
- AddComponent(/datum/component/butchering, 100, 80, 0 , hitsound) //axes are not known for being precision butchering tools
-
-/obj/item/twohanded/fireaxe/update_icon() //Currently only here to fuck with the on-mob icons.
- icon_state = "fireaxe[wielded]"
- return
-
-/obj/item/twohanded/fireaxe/suicide_act(mob/user)
- user.visible_message("[user] axes [user.p_them()]self from head to toe! It looks like [user.p_theyre()] trying to commit suicide!")
- return (BRUTELOSS)
-
-/obj/item/twohanded/fireaxe/afterattack(atom/A, mob/user, proximity)
- . = ..()
- if(!proximity)
- return
- if(wielded) //destroys windows and grilles in one hit
- if(istype(A, /obj/structure/window))
- var/obj/structure/window/W = A
- W.take_damage(200, BRUTE, "melee", 0)
- else if(istype(A, /obj/structure/grille))
- var/obj/structure/grille/G = A
- G.take_damage(40, BRUTE, "melee", 0)
-
-
-/*
- * Double-Bladed Energy Swords - Cheridan
- */
-/obj/item/twohanded/dualsaber
- icon = 'icons/obj/transforming_energy.dmi'
- icon_state = "dualsaber0"
- lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
- name = "double-bladed energy sword"
- desc = "Handle with care."
- force = 3
- throwforce = 5
- throw_speed = 3
- throw_range = 5
- w_class = WEIGHT_CLASS_SMALL
- var/w_class_on = WEIGHT_CLASS_BULKY
- force_unwielded = 3
- force_wielded = 34
- block_power_wielded = 75
- wieldsound = 'sound/weapons/saberon.ogg'
- unwieldsound = 'sound/weapons/saberoff.ogg'
- hitsound = "swing_hit"
- armour_penetration = 35
- item_color = "green"
- light_color = "#00ff00"//green
- attack_verb = list("attacked", "slashed", "stabbed", "sliced", "tore", "ripped", "diced", "cut")
- block_level = 2
- block_upgrade_walk = 1
- block_power = 70
- block_sound = 'sound/weapons/egloves.ogg'
- block_flags = BLOCKING_ACTIVE | BLOCKING_NASTY | BLOCKING_PROJECTILE
- max_integrity = 200
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70)
- resistance_flags = FIRE_PROOF
- var/hacked = FALSE
- var/brightness_on = 6 //TWICE AS BRIGHT AS A REGULAR ESWORD
- var/list/possible_colors = list("red", "blue", "green", "purple")
-
-/obj/item/twohanded/dualsaber/suicide_act(mob/living/carbon/user)
- if(wielded)
- user.visible_message("[user] begins spinning way too fast! It looks like [user.p_theyre()] trying to commit suicide!")
-
- var/obj/item/bodypart/head/myhead = user.get_bodypart(BODY_ZONE_HEAD)//stole from chainsaw code
- var/obj/item/organ/brain/B = user.getorganslot(ORGAN_SLOT_BRAIN)
- B.organ_flags &= ~ORGAN_VITAL //this cant possibly be a good idea
- var/randdir
- for(var/i in 1 to 24)//like a headless chicken!
- if(user.is_holding(src))
- randdir = pick(GLOB.alldirs)
- user.Move(get_step(user, randdir),randdir)
- user.emote("spin")
- if (i == 3 && myhead)
- myhead.drop_limb()
- sleep(3)
- else
- user.visible_message("[user] panics and starts choking to death!")
- return OXYLOSS
-
-
- else
- user.visible_message("[user] begins beating [user.p_them()]self to death with \the [src]'s handle! It probably would've been cooler if [user.p_they()] turned it on first!")
- return BRUTELOSS
-
-/obj/item/twohanded/dualsaber/Initialize()
- . = ..()
- if(LAZYLEN(possible_colors))
- item_color = pick(possible_colors)
- switch(item_color)
- if("red")
- light_color = LIGHT_COLOR_RED
- if("green")
- light_color = LIGHT_COLOR_GREEN
- if("blue")
- light_color = LIGHT_COLOR_LIGHT_CYAN
- if("purple")
- light_color = LIGHT_COLOR_LAVENDER
-
-/obj/item/twohanded/dualsaber/Destroy()
- STOP_PROCESSING(SSobj, src)
- . = ..()
-
-/obj/item/twohanded/dualsaber/update_icon()
- if(wielded)
- icon_state = "dualsaber[item_color][wielded]"
- else
- icon_state = "dualsaber0"
- SEND_SIGNAL(src, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD)
-
-/obj/item/twohanded/dualsaber/attack(mob/target, mob/living/carbon/human/user)
- if(user.has_dna())
- if(user.dna.check_mutation(HULK))
- to_chat(user, "You grip the blade too hard and accidentally close it!")
- unwield()
- return
- ..()
- if(HAS_TRAIT(user, TRAIT_CLUMSY) && (wielded) && prob(40))
- impale(user)
- return
- if((wielded) && prob(50))
- INVOKE_ASYNC(src, .proc/jedi_spin, user)
-
-/obj/item/twohanded/dualsaber/proc/jedi_spin(mob/living/user) //rip complex code, but this fucked up blocking
- user.emote("flip")
-
-/obj/item/twohanded/dualsaber/proc/impale(mob/living/user)
- to_chat(user, "You twirl around a bit before losing your balance and impaling yourself on [src].")
- if (force_wielded)
- user.take_bodypart_damage(20,25,check_armor = TRUE)
- else
- user.adjustStaminaLoss(25)
-
-/obj/item/twohanded/dualsaber/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
- if(!wielded)
- return 0
- return ..()
-
-/obj/item/twohanded/dualsaber/attack_hulk(mob/living/carbon/human/user, does_attack_animation = 0) //In case thats just so happens that it is still activated on the groud, prevents hulk from picking it up
- if(wielded)
- to_chat(user, "You can't pick up such dangerous item with your meaty hands without losing fingers, better not to!")
- return 1
-
-/obj/item/twohanded/dualsaber/wield(mob/living/carbon/M) //Specific wield () hulk checks due to reflection chance for balance issues and switches hitsounds.
- if(M.has_dna())
- if(M.dna.check_mutation(HULK))
- to_chat(M, "You lack the grace to wield this!")
- return
- ..()
- if(wielded)
- sharpness = IS_SHARP
- w_class = w_class_on
- hitsound = 'sound/weapons/blade1.ogg'
- START_PROCESSING(SSobj, src)
- set_light(brightness_on)
-
-/obj/item/twohanded/dualsaber/unwield() //Specific unwield () to switch hitsounds.
- sharpness = initial(sharpness)
- w_class = initial(w_class)
- ..()
- hitsound = "swing_hit"
- STOP_PROCESSING(SSobj, src)
- set_light(0)
-
-/obj/item/twohanded/dualsaber/process()
- if(wielded)
- if(hacked)
- light_color = pick(LIGHT_COLOR_RED, LIGHT_COLOR_GREEN, LIGHT_COLOR_LIGHT_CYAN, LIGHT_COLOR_LAVENDER)
- open_flame()
- else
- STOP_PROCESSING(SSobj, src)
-
-/obj/item/twohanded/dualsaber/IsReflect()
- if(wielded)
- return 1
-
-/obj/item/twohanded/dualsaber/ignition_effect(atom/A, mob/user)
- // same as /obj/item/melee/transforming/energy, mostly
- if(!wielded)
- return ""
- var/in_mouth = ""
- if(iscarbon(user))
- var/mob/living/carbon/C = user
- if(C.wear_mask)
- in_mouth = ", barely missing [user.p_their()] nose"
- . = "[user] swings [user.p_their()] [name][in_mouth]. [user.p_they(TRUE)] light[user.p_s()] [user.p_their()] [A.name] in the process."
- playsound(loc, hitsound, get_clamped_volume(), 1, -1)
- add_fingerprint(user)
- // Light your candles while spinning around the room
- INVOKE_ASYNC(src, .proc/jedi_spin, user)
-
-/obj/item/twohanded/dualsaber/green
- possible_colors = list("green")
-
-/obj/item/twohanded/dualsaber/red
- possible_colors = list("red")
-
-/obj/item/twohanded/dualsaber/blue
- possible_colors = list("blue")
-
-/obj/item/twohanded/dualsaber/purple
- possible_colors = list("purple")
-
-/obj/item/twohanded/dualsaber/attackby(obj/item/W, mob/user, params)
- if(W.tool_behaviour == TOOL_MULTITOOL)
- if(!hacked)
- hacked = TRUE
- to_chat(user, "2XRNBW_ENGAGE")
- item_color = "rainbow"
- update_icon()
- else
- to_chat(user, "It's starting to look like a triple rainbow - no, nevermind.")
- else
- return ..()
-
-//spears
-/obj/item/twohanded/spear
- icon_state = "spearglass0"
- lefthand_file = 'icons/mob/inhands/weapons/polearms_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/polearms_righthand.dmi'
- name = "spear"
- desc = "A haphazardly-constructed yet still deadly weapon of ancient design."
- force = 10
- w_class = WEIGHT_CLASS_BULKY
- slot_flags = ITEM_SLOT_BACK
- force_unwielded = 10
- force_wielded = 18
- block_power_wielded = 25
- block_upgrade_walk = 1
- throwforce = 20
- throw_speed = 4
- embedding = list("embedded_impact_pain_multiplier" = 3)
- armour_penetration = 10
- materials = list(/datum/material/iron=1150, /datum/material/glass=2075)
- hitsound = 'sound/weapons/bladeslice.ogg'
- attack_verb = list("attacked", "poked", "jabbed", "tore", "gored")
- sharpness = IS_SHARP
- max_integrity = 200
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30)
- var/war_cry = "AAAAARGH!!!"
- var/icon_prefix = "spearglass"
-
-/obj/item/twohanded/spear/Initialize()
- . = ..()
- AddComponent(/datum/component/butchering, 100, 70) //decent in a pinch, but pretty bad.
-
-/obj/item/twohanded/spear/suicide_act(mob/living/carbon/user)
- user.visible_message("[user] begins to sword-swallow \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
- return BRUTELOSS
-
-/obj/item/twohanded/spear/Initialize()
- . = ..()
- AddComponent(/datum/component/jousting)
-
-/obj/item/twohanded/spear/update_icon()
- icon_state = "[icon_prefix][wielded]"
-
-/obj/item/twohanded/spear/CheckParts(list/parts_list)
- var/obj/item/shard/tip = locate() in parts_list
- if (istype(tip, /obj/item/shard/plasma))
- force_wielded = 19
- force_unwielded = 11
- throwforce = 21
- icon_prefix = "spearplasma"
- update_icon()
- qdel(tip)
- var/obj/item/grenade/G = locate() in parts_list
- if(G)
- var/obj/item/twohanded/spear/explosive/lance = new /obj/item/twohanded/spear/explosive(src.loc, G)
- lance.force_wielded = force_wielded
- lance.force_unwielded = force_unwielded
- lance.throwforce = throwforce
- lance.icon_prefix = icon_prefix
- parts_list -= G
- qdel(src)
- ..()
-
-
-/obj/item/twohanded/spear/explosive
- name = "explosive lance"
- var/obj/item/grenade/explosive = null
-
-/obj/item/twohanded/spear/explosive/Initialize(mapload, obj/item/grenade/G)
- . = ..()
- if (!G)
- G = new /obj/item/grenade/iedcasing() //For admin-spawned explosive lances
- G.forceMove(src)
- explosive = G
- desc = "A makeshift spear with [G] attached to it"
- update_icon()
-
-/obj/item/twohanded/spear/explosive/suicide_act(mob/living/carbon/user)
- user.visible_message("[user] begins to sword-swallow \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
- user.say("[war_cry]", forced="spear warcry")
- explosive.forceMove(user)
- explosive.prime()
- user.gib()
- qdel(src)
- return BRUTELOSS
-
-/obj/item/twohanded/spear/explosive/examine(mob/user)
- . = ..()
- . += "Alt-click to set your war cry."
-
-/obj/item/twohanded/spear/explosive/update_icon()
- icon_state = "spearbomb[wielded]"
-
-/obj/item/twohanded/spear/explosive/AltClick(mob/user)
- if(user.canUseTopic(src, BE_CLOSE))
- ..()
- if(istype(user) && loc == user)
- var/input = stripped_input(user,"What do you want your war cry to be? You will shout it when you hit someone in melee.", ,"", 50)
- if(input)
- src.war_cry = input
-
-/obj/item/twohanded/spear/explosive/afterattack(atom/movable/AM, mob/user, proximity)
- . = ..()
- if(!proximity)
- return
- if(wielded)
- user.say("[war_cry]", forced="spear warcry")
- explosive.forceMove(AM)
- explosive.prime()
- qdel(src)
-
-// CHAINSAW
-/obj/item/twohanded/required/chainsaw
- name = "chainsaw"
- desc = "A versatile power tool. Useful for limbing trees and delimbing humans."
- icon_state = "chainsaw_off"
- lefthand_file = 'icons/mob/inhands/weapons/chainsaw_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/chainsaw_righthand.dmi'
- flags_1 = CONDUCT_1
- force = 13
- block_power = 20
- block_upgrade_walk = 2
- block_flags = BLOCKING_ACTIVE | BLOCKING_NASTY
- attack_weight = 2
- var/force_on = 24
- w_class = WEIGHT_CLASS_HUGE
- throwforce = 13
- throw_speed = 2
- throw_range = 4
- materials = list(/datum/material/iron=13000)
- attack_verb = list("sawed", "tore", "cut", "chopped", "diced")
- hitsound = "swing_hit"
- sharpness = IS_SHARP
- actions_types = list(/datum/action/item_action/startchainsaw)
- var/on = FALSE
- tool_behaviour = TOOL_SAW
- toolspeed = 0.5
-
-/obj/item/twohanded/required/chainsaw/Initialize()
- . = ..()
- AddComponent(/datum/component/butchering, 30, 100, 0, 'sound/weapons/chainsawhit.ogg', TRUE)
-
-/obj/item/twohanded/required/chainsaw/suicide_act(mob/living/carbon/user)
- if(on)
- user.visible_message("[user] begins to tear [user.p_their()] head off with [src]! It looks like [user.p_theyre()] trying to commit suicide!")
- playsound(src, 'sound/weapons/chainsawhit.ogg', 100, 1)
- var/obj/item/bodypart/head/myhead = user.get_bodypart(BODY_ZONE_HEAD)
- if(myhead)
- myhead.dismember()
- else
- user.visible_message("[user] smashes [src] into [user.p_their()] neck, destroying [user.p_their()] esophagus! It looks like [user.p_theyre()] trying to commit suicide!")
- playsound(src, 'sound/weapons/genhit1.ogg', 100, 1)
- return(BRUTELOSS)
-
-/obj/item/twohanded/required/chainsaw/attack_self(mob/user)
- on = !on
- to_chat(user, "As you pull the starting cord dangling from [src], [on ? "it begins to whirr." : "the chain stops moving."]")
- force = on ? force_on : initial(force)
- throwforce = on ? force_on : initial(force)
- icon_state = "chainsaw_[on ? "on" : "off"]"
- var/datum/component/butchering/butchering = src.GetComponent(/datum/component/butchering)
- butchering.butchering_enabled = on
-
- if(on)
- hitsound = 'sound/weapons/chainsawhit.ogg'
- else
- hitsound = "swing_hit"
-
- if(src == user.get_active_held_item()) //update inhands
- user.update_inv_hands()
- for(var/X in actions)
- var/datum/action/A = X
- A.UpdateButtonIcon()
-
-/obj/item/twohanded/required/chainsaw/doomslayer
- name = "THE GREAT COMMUNICATOR"
- desc = "VRRRRRRR!!!"
- armour_penetration = 100
- force_on = 30
-
-/obj/item/twohanded/required/chainsaw/doomslayer/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
- if(attack_type == PROJECTILE_ATTACK)
- owner.visible_message("Ranged attacks just make [owner] angrier!")
- playsound(src, pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg'), 75, 1)
- return 1
- return 0
-
-// ENERGY CHAINSAW
-/obj/item/twohanded/required/chainsaw/energy
- name = "energy chainsaw"
- desc = "Become Leatherspace."
- icon = 'icons/obj/items_and_weapons.dmi'
- icon_state = "echainsaw_off"
- lefthand_file = 'icons/mob/inhands/weapons/chainsaw_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/chainsaw_righthand.dmi'
- force_on = 40
- w_class = WEIGHT_CLASS_HUGE
- attack_verb = list("sawed", "shred", "rended", "gutted", "eviscerated")
- actions_types = list(/datum/action/item_action/startchainsaw)
- block_power = 50
- armour_penetration = 50
- light_color = "#ff0000"
- var/onsound
- var/offsound
- var/wield_cooldown = 0
- onsound = 'sound/weapons/echainsawon.ogg'
- offsound = 'sound/weapons/echainsawoff.ogg'
- on = FALSE
- var/brightness_on = 3
-
-/obj/item/twohanded/required/chainsaw/energy/attack_self(mob/user)
- on = !on
- to_chat(user, "As you pull the starting cord dangling from [src], [on ? "it begins to whirr intimidatingly." : "the plasma microblades stop moving."]")
- force = on ? force_on : initial(force)
- playsound(user, on ? onsound : offsound , 50, 1)
- set_light(on ? brightness_on : 0)
- throwforce = on ? force_on : initial(force)
- icon_state = "echainsaw_[on ? "on" : "off"]"
-
- if(hitsound == "swing_hit")
- hitsound = pick('sound/weapons/echainsawhit1.ogg','sound/weapons/echainsawhit2.ogg')
- else
- hitsound = "swing_hit"
-
- if(src == user.get_active_held_item())
- user.update_inv_hands()
- for(var/X in actions)
- var/datum/action/A = X
- A.UpdateButtonIcon()
-
-// DOOMGUY ENERGY CHAINSAW
-/obj/item/twohanded/required/chainsaw/energy/doom
- name = "super energy chainsaw"
- desc = "The chainsaw you want when you need to kill every damn thing in the room."
- force_on = 60
- w_class = WEIGHT_CLASS_NORMAL
- block_power = 75
- block_level = 1
- attack_weight = 3 //fear him
- armour_penetration = 75
- var/knockdown = 1
- brightness_on = 6
-
-/obj/item/twohanded/required/chainsaw/energy/doom/attack(mob/living/target)
- ..()
- target.Knockdown(4)
-
-
-//GREY TIDE
-/obj/item/twohanded/spear/grey_tide
- icon_state = "spearglass0"
- name = "\improper Grey Tide"
- desc = "Recovered from the aftermath of a revolt aboard Defense Outpost Theta Aegis, in which a seemingly endless tide of Assistants caused heavy casualities among Nanotrasen military forces."
- force_unwielded = 15
- force_wielded = 25
- block_level = 1
- throwforce = 20
- throw_speed = 4
- attack_verb = list("gored")
-
-/obj/item/twohanded/spear/grey_tide/afterattack(atom/movable/AM, mob/living/user, proximity)
- . = ..()
- if(!proximity)
- return
- user.faction |= "greytide([REF(user)])"
- if(isliving(AM))
- var/mob/living/L = AM
- if(istype (L, /mob/living/simple_animal/hostile/illusion))
- return
- if(!L.stat && prob(50))
- var/mob/living/simple_animal/hostile/illusion/M = new(user.loc)
- M.faction = user.faction.Copy()
- M.Copy_Parent(user, 100, user.health/2.5, 12, 30)
- M.GiveTarget(L)
-
-/obj/item/twohanded/pitchfork
- icon_state = "pitchfork0"
- lefthand_file = 'icons/mob/inhands/weapons/polearms_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/polearms_righthand.dmi'
- name = "pitchfork"
- desc = "A simple tool used for moving hay."
- force = 7
- throwforce = 15
- block_power_wielded = 25
- block_level = 1
- block_upgrade_walk = 1
- w_class = WEIGHT_CLASS_BULKY
- force_unwielded = 7
- force_wielded = 15
- attack_verb = list("attacked", "impaled", "pierced")
- hitsound = 'sound/weapons/bladeslice.ogg'
- sharpness = IS_SHARP
- max_integrity = 200
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30)
- resistance_flags = FIRE_PROOF
-
-/obj/item/twohanded/pitchfork/demonic
- name = "demonic pitchfork"
- desc = "A red pitchfork, it looks like the work of the devil."
- force = 19
- throwforce = 24
- force_unwielded = 19
- force_wielded = 25
-
-/obj/item/twohanded/pitchfork/demonic/Initialize()
- . = ..()
- set_light(3,6,LIGHT_COLOR_RED)
-
-/obj/item/twohanded/pitchfork/demonic/greater
- force = 24
- throwforce = 50
- force_unwielded = 24
- force_wielded = 34
-
-/obj/item/twohanded/pitchfork/demonic/ascended
- force = 100
- throwforce = 100
- force_unwielded = 100
- force_wielded = 500000 // Kills you DEAD.
-
-/obj/item/twohanded/pitchfork/update_icon()
- icon_state = "pitchfork[wielded]"
-
-/obj/item/twohanded/pitchfork/suicide_act(mob/user)
- user.visible_message("[user] impales [user.p_them()]self in [user.p_their()] abdomen with [src]! It looks like [user.p_theyre()] trying to commit suicide!")
- return (BRUTELOSS)
-
-/obj/item/twohanded/pitchfork/demonic/pickup(mob/living/user)
- if(isliving(user) && user.mind && user.owns_soul() && !is_devil(user))
- var/mob/living/U = user
- U.visible_message("As [U] picks [src] up, [U]'s arms briefly catch fire.", \
- "\"As you pick up [src] your arms ignite, reminding you of all your past sins.\"")
- if(ishuman(U))
- var/mob/living/carbon/human/H = U
- H.apply_damage(rand(force/2, force), BURN, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
- else
- U.adjustFireLoss(rand(force/2,force))
-
-/obj/item/twohanded/pitchfork/demonic/attack(mob/target, mob/living/carbon/human/user)
- if(user.mind && user.owns_soul() && !is_devil(user))
- to_chat(user, "[src] burns in your hands.")
- user.apply_damage(rand(force/2, force), BURN, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
- ..()
-
-/obj/item/twohanded/pitchfork/demonic/ascended/afterattack(atom/target, mob/user, proximity)
- . = ..()
- if(!proximity || !wielded)
- return
- if(iswallturf(target))
- var/turf/closed/wall/W = target
- user.visible_message("[user] blasts \the [target] with \the [src]!")
- playsound(target, 'sound/magic/disintegrate.ogg', 100, 1)
- W.break_wall()
- W.ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
- return
-
-//HF blade
-
-/obj/item/twohanded/vibro_weapon
- icon_state = "hfrequency0"
- lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
- name = "vibro sword"
- desc = "A potent weapon capable of cutting through nearly anything. Wielding it in two hands will allow you to deflect gunfire."
- force_unwielded = 20
- force_wielded = 40
- armour_penetration = 100
- block_power_wielded = 40
- block_level = 1
- block_upgrade_walk = 2
- block_flags = BLOCKING_ACTIVE | BLOCKING_NASTY | BLOCKING_PROJECTILE
- block_sound = 'sound/weapons/genhit.ogg'
- throwforce = 20
- throw_speed = 4
- sharpness = IS_SHARP
- attack_verb = list("cut", "sliced", "diced")
- w_class = WEIGHT_CLASS_BULKY
- slot_flags = ITEM_SLOT_BACK
- hitsound = 'sound/weapons/bladeslice.ogg'
-
-/obj/item/twohanded/vibro_weapon/Initialize()
- . = ..()
- AddComponent(/datum/component/butchering, 20, 105)
-
-/obj/item/twohanded/vibro_weapon/update_icon()
- icon_state = "hfrequency[wielded]"
-
-/*
- * Bone Axe
- */
-/obj/item/twohanded/fireaxe/boneaxe // Blatant imitation of the fireaxe, but made out of bone.
- icon_state = "bone_axe0"
- name = "bone axe"
- desc = "A large, vicious axe crafted out of several sharpened bone plates and crudely tied together. Made of monsters, by killing monsters, for killing monsters."
- force_wielded = 23
-
-/obj/item/twohanded/fireaxe/boneaxe/update_icon()
- icon_state = "bone_axe[wielded]"
-
-/*
- * Bone Spear
- */
-/obj/item/twohanded/bonespear //Blatant imitation of spear, but made out of bone. Not valid for explosive modification.
- icon_state = "bone_spear0"
- lefthand_file = 'icons/mob/inhands/weapons/polearms_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/polearms_righthand.dmi'
- name = "bone spear"
- desc = "A haphazardly-constructed yet still deadly weapon. The pinnacle of modern technology."
- force = 11
- w_class = WEIGHT_CLASS_BULKY
- slot_flags = ITEM_SLOT_BACK
- block_power_wielded = 25
- block_upgrade_walk = 1
- force_unwielded = 11
- force_wielded = 20 //I have no idea how to balance
- throwforce = 22
- throw_speed = 4
- embedding = list("embedded_impact_pain_multiplier" = 3)
- armour_penetration = 15 //Enhanced armor piercing
- hitsound = 'sound/weapons/bladeslice.ogg'
- attack_verb = list("attacked", "poked", "jabbed", "tore", "gored")
- sharpness = IS_SHARP
-
-/obj/item/twohanded/bonespear/update_icon()
- icon_state = "bone_spear[wielded]"
-
-/obj/item/twohanded/binoculars
- name = "binoculars"
- desc = "Used for long-distance surveillance."
- item_state = "binoculars"
- icon_state = "binoculars"
- lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/items_righthand.dmi'
- slot_flags = ITEM_SLOT_BELT
- w_class = WEIGHT_CLASS_SMALL
- var/mob/listeningTo
- var/zoom_out_amt = 6
- var/zoom_amt = 10
-
-/obj/item/twohanded/binoculars/Destroy()
- listeningTo = null
- return ..()
-
-/obj/item/twohanded/binoculars/wield(mob/user)
- . = ..()
- if(!wielded)
- return
- RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/unwield)
- listeningTo = user
- user.visible_message("[user] holds [src] up to [user.p_their()] eyes.","You hold [src] up to your eyes.")
- item_state = "binoculars_wielded"
- user.regenerate_icons()
- if(!user?.client)
- return
- var/client/C = user.client
- var/_x = 0
- var/_y = 0
- switch(user.dir)
- if(NORTH)
- _y = zoom_amt
- if(EAST)
- _x = zoom_amt
- if(SOUTH)
- _y = -zoom_amt
- if(WEST)
- _x = -zoom_amt
- C.change_view(get_zoomed_view(world.view, zoom_out_amt))
- C.pixel_x = world.icon_size*_x
- C.pixel_y = world.icon_size*_y
-
-/obj/item/twohanded/binoculars/unwield(mob/user)
- . = ..()
- UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED)
- listeningTo = null
- user.visible_message("[user] lowers [src].","You lower [src].")
- item_state = "binoculars"
- user.regenerate_icons()
- if(user?.client)
- user.regenerate_icons()
- var/client/C = user.client
- C.change_view(CONFIG_GET(string/default_view))
- user.client.pixel_x = 0
- user.client.pixel_y = 0
-
-/obj/item/twohanded/bamboospear
- icon_state = "bamboo_spear0"
- lefthand_file = 'icons/mob/inhands/weapons/polearms_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/polearms_righthand.dmi'
- name = "bamboo spear"
- desc = "A haphazardly-constructed bamboo stick with a sharpened tip, ready to poke holes into unsuspecting people."
- force = 10
- w_class = WEIGHT_CLASS_BULKY
- slot_flags = ITEM_SLOT_BACK
- block_power_wielded = 25
- block_upgrade_walk = 1
- force_unwielded = 10
- force_wielded = 18
- throwforce = 22
- throw_speed = 4
- embedding = list("embedded_impact_pain_multiplier" = 2)
- armour_penetration = 10
- hitsound = 'sound/weapons/bladeslice.ogg'
- attack_verb = list("attacked", "poked", "jabbed", "tore", "gored")
- sharpness = IS_SHARP
-
-/obj/item/twohanded/bamboospear/update_icon()
- icon_state = "bamboo_spear[wielded]"
-
-/obj/item/twohanded/pushbroom
- name = "push broom"
- desc = "This is my BROOMSTICK! It can be used manually or braced with two hands to sweep items as you move. It has a telescopic handle for compact storage."
- icon = 'icons/obj/janitor.dmi'
- icon_state = "broom0"
- lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi'
- force = 8
- throwforce = 10
- throw_speed = 3
- throw_range = 7
- w_class = WEIGHT_CLASS_NORMAL
- force_unwielded = 8
- force_wielded = 12
- attack_verb = list("swept", "brushed off", "bludgeoned", "whacked")
- resistance_flags = FLAMMABLE
-
-/obj/item/twohanded/pushbroom/update_icon_state()
- icon_state = "broom[wielded]"
-
-/obj/item/twohanded/pushbroom/wield(mob/user)
- . = ..()
- if(!wielded)
- return
- to_chat(user, "You brace the [src] against the ground in a firm sweeping stance.")
- RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/sweep)
-
-/obj/item/twohanded/pushbroom/unwield(mob/user)
- . = ..()
- UnregisterSignal(user, COMSIG_MOVABLE_MOVED)
-
-/obj/item/twohanded/pushbroom/afterattack(atom/A, mob/user, proximity)
- . = ..()
- if(!proximity)
- return
- if(wielded)
- sweep(user, A, FALSE)
- else
- to_chat(user, "You need to wield \the [src] in both hands to sweep!")
-
-/obj/item/twohanded/pushbroom/proc/sweep(mob/user, atom/A, moving = TRUE)
- var/turf/target
- if (!moving)
- if (isturf(A))
- target = A
- else
- target = get_turf(A)
- else
- target = get_turf(user)
- if (locate(/obj/structure/table) in target.contents)
- return
- var/i = 0
- var/turf/target_turf = get_step(target, user.dir)
- var/obj/machinery/disposal/bin/target_bin = locate(/obj/machinery/disposal/bin) in target_turf.contents
- for(var/obj/item/garbage in target.contents)
- if(!garbage.anchored)
- if (target_bin)
- garbage.forceMove(target_bin)
- else
- garbage.Move(target_turf, user.dir)
- i++
- if(i > 19)
- break
- if(i > 0)
- if (target_bin)
- target_bin.update_icon()
- to_chat(user, "You sweep the pile of garbage into [target_bin].")
- playsound(loc, 'sound/weapons/thudswoosh.ogg', 30, TRUE, -1)
-
-/obj/item/twohanded/pushbroom/proc/janicart_insert(mob/user, obj/structure/janitorialcart/J)
- J.put_in_cart(src, user)
- J.mybroom=src
- J.update_icon()
diff --git a/code/game/objects/items/vending_items.dm b/code/game/objects/items/vending_items.dm
index 2964d31259d9f..d1a0e666114c3 100644
--- a/code/game/objects/items/vending_items.dm
+++ b/code/game/objects/items/vending_items.dm
@@ -17,7 +17,7 @@
throw_speed = 1
throw_range = 7
w_class = WEIGHT_CLASS_BULKY
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 30)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 30, "stamina" = 0)
// Built automatically from the corresponding vending machine.
// If null, considered to be full. Otherwise, is list(/typepath = amount).
diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm
index b9be1f980b17d..328c3060b50bd 100644
--- a/code/game/objects/items/weaponry.dm
+++ b/code/game/objects/items/weaponry.dm
@@ -11,7 +11,7 @@
throw_range = 7
attack_verb = list("banned")
max_integrity = 200
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70, "stamina" = 0)
resistance_flags = FIRE_PROOF
/obj/item/banhammer/suicide_act(mob/user)
@@ -81,7 +81,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
block_flags = BLOCKING_ACTIVE | BLOCKING_NASTY
sharpness = IS_SHARP
max_integrity = 200
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50, "stamina" = 0)
resistance_flags = FIRE_PROOF
/obj/item/claymore/Initialize()
@@ -232,7 +232,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "tore", "ripped", "diced", "cut")
block_level = 0
block_power = 30
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50, "stamina" = 0)
/obj/item/katana
name = "katana"
@@ -254,7 +254,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
block_flags = BLOCKING_ACTIVE | BLOCKING_NASTY | BLOCKING_PROJECTILE
sharpness = IS_SHARP
max_integrity = 200
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50, "stamina" = 0)
resistance_flags = FIRE_PROOF
/obj/item/katana/cursed
@@ -279,7 +279,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/wirerod/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/shard))
- var/obj/item/twohanded/spear/S = new /obj/item/twohanded/spear
+ var/obj/item/spear/S = new /obj/item/spear
remove_item_from_storage(user)
if (!user.transferItemToLoc(I, S))
@@ -313,27 +313,42 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi'
force = 2
- throwforce = 20 //20 + 2 (WEIGHT_CLASS_SMALL) * 4 (EMBEDDED_IMPACT_PAIN_MULTIPLIER) = 28 damage on hit due to guaranteed embedding
+ throwforce = 24
throw_speed = 4
- embedding = list("embedded_pain_multiplier" = 4, "embed_chance" = 100, "embedded_fall_chance" = 0)
+ embedding = list("pain_mult" = 4, "embed_chance" = 300, "fall_chance" = 0, "armour_block" = 70)
+ armour_penetration = 40
w_class = WEIGHT_CLASS_SMALL
hitsound = 'sound/weapons/bladeslice.ogg'
sharpness = IS_SHARP
materials = list(/datum/material/iron=500, /datum/material/glass=500)
resistance_flags = FIRE_PROOF
+/obj/item/throwing_star/stamina
+ name = "shock throwing star"
+ desc = "An aerodynamic disc designed to cause excruciating pain when stuck inside fleeing targets, hopefully without causing fatal harm."
+ throwforce = 5
+ embedding = list("pain_chance" = 5, "embed_chance" = 300, "fall_chance" = 0, "jostle_chance" = 10, "pain_stam_pct" = 0.8, "jostle_pain_mult" = 3, "armour_block" = 70)
+
+/obj/item/throwing_star/toy
+ name = "toy throwing star"
+ desc = "An aerodynamic disc strapped with adhesive for sticking to people, good for playing pranks and getting yourself killed by security."
+ sharpness = IS_BLUNT
+ force = 0
+ throwforce = 0
+ embedding = list("pain_mult" = 0, "jostle_pain_mult" = 0, "embed_chance" = 300, "fall_chance" = 0, "armour_block" = 70)
+
/obj/item/throwing_star/magspear
name = "magnetic spear"
desc = "A reusable spear that is typically loaded into kinetic spearguns."
icon = 'icons/obj/ammo.dmi'
icon_state = "magspear"
- throwforce = 25 //kills regular carps in one hit
+ throwforce = 40
force = 15 //can be used in melee- a speargun user may be beat to death with their own spear
w_class = WEIGHT_CLASS_BULKY
hitsound = 'sound/weapons/bladeslice.ogg'
throw_range = 0 //throwing these invalidates the speargun
attack_verb = list("stabbed", "ripped", "gored", "impaled")
- embedding = list("embedded_pain_multiplier" = 8, "embed_chance" = 100, "embedded_fall_chance" = 0, "embedded_impact_pain_multiplier" = 15) //55 damage+embed on hit
+ embedding = list("pain_mult" = 8, "embed_chance" = 1000, "fall_chance" = 0, "armour_block" = 100)
/obj/item/switchblade
name = "long switchblade"
@@ -521,7 +536,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/mounted_chainsaw/normal/Destroy()
var/obj/item/bodypart/part
- new /obj/item/twohanded/required/chainsaw(get_turf(src))
+ new /obj/item/chainsaw(get_turf(src))
if(iscarbon(loc))
var/mob/living/carbon/holder = loc
var/index = holder.get_held_index_of_item(src)
@@ -540,7 +555,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/mounted_chainsaw/energy/Destroy()
var/obj/item/bodypart/part
- new /obj/item/twohanded/required/chainsaw/energy(get_turf(src))
+ new /obj/item/chainsaw/energy(get_turf(src))
if(iscarbon(loc))
var/mob/living/carbon/holder = loc
var/index = holder.get_held_index_of_item(src)
@@ -559,7 +574,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/mounted_chainsaw/super/Destroy()
var/obj/item/bodypart/part
- new /obj/item/twohanded/required/chainsaw/energy/doom(get_turf(src))
+ new /obj/item/chainsaw/energy/doom(get_turf(src))
if(iscarbon(loc))
var/mob/living/carbon/holder = loc
var/index = holder.get_held_index_of_item(src)
@@ -616,7 +631,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
var/board_item_type = /obj/vehicle/ridden/scooter/skateboard
/obj/item/melee/skateboard/attack_self(mob/user)
- var/obj/vehicle/ridden/scooter/skateboard/S = new board_item_type(get_turf(user))//this probably has fucky interactions with telekinesis but for the record it wasnt my fault
+ var/obj/vehicle/ridden/scooter/skateboard/S = new board_item_type(get_turf(user))//this probably has fucky interactions with telekinesis but for the record it wasn't my fault
S.buckle_mob(user)
qdel(src)
@@ -687,12 +702,12 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
if(homerun_ready)
user.visible_message("It's a home run!")
target.throw_at(throw_target, rand(8,10), 14, user)
- target.ex_act(EXPLODE_HEAVY)
+ SSexplosions.medturf += throw_target
playsound(get_turf(src), 'sound/weapons/homerun.ogg', 100, 1)
homerun_ready = 0
return
else if(!target.anchored)
- target.throw_at(throw_target, rand(1,2), 7, user)
+ target.throw_at(throw_target, rand(1,2), 7, user, force = MOVE_FORCE_WEAK)
user.changeNext_move(CLICK_CD_MELEE * click_delay)
return
@@ -814,29 +829,6 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
M.attack_hand(user)
-// Shank - Makeshift weapon that can embed on throw
-/obj/item/melee/shank
- name = "Shank"
- desc = "A crude knife fashioned by wrapping some cable around a glass shard. It looks like it could be thrown with some force.. and stick. Good to throw at someone chasing you"
- icon = 'icons/obj/items_and_weapons.dmi'
- icon_state = "shank"
- item_state = "shank" //Kind of a placeholder, but im ass with sprites and I doubt someone will notice its a recoloured switchblade :')
- lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
- force = 8 // 3 more than base glass shard
- throwforce = 8
- throw_speed = 5 //yeets
- armour_penetration = 10 //spear has 10 armour pen, I think its fitting another glass tipped item should have it too
- embedding = list("embedded_pain_multiplier" = 6, "embed_chance" = 40, "embedded_fall_chance" = 5) // Incentive to disengage/stop chasing when stuck
- attack_verb = list("stuck", "shanked")
- w_class = WEIGHT_CLASS_SMALL
- hitsound = 'sound/weapons/bladeslice.ogg'
- sharpness = IS_SHARP
-
-/obj/item/melee/shank/suicide_act(mob/user)
- user.visible_message("[user] is slitting [user.p_their()] [pick("wrists", "throat")] with the shank! It looks like [user.p_theyre()] trying to commit suicide.")
- return (BRUTELOSS)
-
/obj/item/highfive
name = "raised hand"
desc = "Slap my hand."
@@ -915,3 +907,33 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
sharpness = IS_SHARP
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
hitsound = 'sound/weapons/bladeslice.ogg'
+
+//HF blade
+/obj/item/vibro_weapon
+ icon_state = "hfrequency0"
+ lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
+ name = "vibro sword"
+ desc = "A potent weapon capable of cutting through nearly anything. Wielding it in two hands will allow you to deflect gunfire."
+ armour_penetration = 100
+ block_level = 1
+ block_upgrade_walk = 2
+ block_flags = BLOCKING_ACTIVE | BLOCKING_NASTY | BLOCKING_PROJECTILE
+ block_sound = 'sound/weapons/genhit.ogg'
+ force = 20
+ throwforce = 20
+ throw_speed = 4
+ sharpness = IS_SHARP
+ attack_verb = list("cut", "sliced", "diced")
+ w_class = WEIGHT_CLASS_BULKY
+ slot_flags = ITEM_SLOT_BACK
+ hitsound = 'sound/weapons/bladeslice.ogg'
+
+/obj/item/vibro_weapon/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/butchering, 20, 105)
+ AddComponent(/datum/component/two_handed, force_multiplier=2, block_power_wielded=40, icon_wielded="hfrequency1")
+
+/obj/item/vibro_weapon/update_icon()
+ icon_state = "hfrequency0"
+ ..()
\ No newline at end of file
diff --git a/code/game/objects/noose.dm b/code/game/objects/noose.dm
index e8d657fd052fa..ae19b5e7f86b0 100644
--- a/code/game/objects/noose.dm
+++ b/code/game/objects/noose.dm
@@ -15,12 +15,12 @@
var/mutable_appearance/overlay
/obj/structure/chair/noose/attackby(obj/item/W, mob/user, params)
- if(istype(W, /obj/item/wirecutters))
+ if(W.tool_behaviour == TOOL_WIRECUTTER)
user.visible_message("[user] cuts the noose.", "You cut the noose.")
if(has_buckled_mobs())
for(var/m in buckled_mobs)
var/mob/living/buckled_mob = m
- if(buckled_mob.mob_has_gravity())
+ if(buckled_mob.has_gravity())
buckled_mob.visible_message("[buckled_mob] falls over and hits the ground!")
to_chat(buckled_mob, "You fall over and hit the ground!")
buckled_mob.adjustBruteLoss(10)
@@ -83,7 +83,7 @@
pixel_x = initial(pixel_x)
add_fingerprint(user)
-/obj/structure/chair/noose/user_buckle_mob(mob/living/carbon/human/M, mob/user)
+/obj/structure/chair/noose/user_buckle_mob(mob/living/carbon/human/M, mob/user, check_loc = TRUE)
if(!in_range(user, src) || user.stat || user.restrained() || !iscarbon(M))
return FALSE
@@ -126,7 +126,7 @@
else
animate(src, pixel_x = 3, time = 45, easing = ELASTIC_EASING)
animate(m, pixel_x = 3, time = 45, easing = ELASTIC_EASING)
- if(buckled_mob.mob_has_gravity())
+ if(buckled_mob.has_gravity())
if(buckled_mob.get_bodypart("head"))
if(buckled_mob.stat != DEAD)
if(!HAS_TRAIT(buckled_mob, TRAIT_NOBREATH))
diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm
index b0ce0e2057008..f68b769535c12 100644
--- a/code/game/objects/obj_defense.dm
+++ b/code/game/objects/obj_defense.dm
@@ -6,20 +6,21 @@
return
if(sound_effect)
play_attack_sound(damage_amount, damage_type, damage_flag)
- if(!(resistance_flags & INDESTRUCTIBLE) && obj_integrity > 0)
- damage_amount = run_obj_armor(damage_amount, damage_type, damage_flag, attack_dir, armour_penetration)
- if(damage_amount >= DAMAGE_PRECISION)
- . = damage_amount
- var/old_integ = obj_integrity
- obj_integrity = max(old_integ - damage_amount, 0)
- if(obj_integrity <= 0)
- var/int_fail = integrity_failure
- if(int_fail && old_integ > int_fail)
- obj_break(damage_flag)
- obj_destruction(damage_flag)
- else if(integrity_failure)
- if(obj_integrity <= integrity_failure)
- obj_break(damage_flag)
+ if((resistance_flags & INDESTRUCTIBLE) || obj_integrity <= 0)
+ return
+ damage_amount = run_obj_armor(damage_amount, damage_type, damage_flag, attack_dir, armour_penetration)
+ if(damage_amount <= DAMAGE_PRECISION)
+ return
+ . = damage_amount
+ var/old_integ = obj_integrity
+ obj_integrity = max(old_integ - damage_amount, 0)
+ //BREAKING FIRST
+ if(integrity_failure && obj_integrity <= integrity_failure)
+ obj_break(damage_flag)
+
+ //DESTROYING SECOND
+ if(obj_integrity <= 0)
+ obj_destruction(damage_flag)
//returns the damage value of the attack after processing the obj's various armor protections
/obj/proc/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir, armour_penetration = 0)
@@ -70,7 +71,8 @@
/obj/bullet_act(obj/item/projectile/P)
. = ..()
playsound(src, P.hitsound, 50, 1)
- visible_message("[src] is hit by \a [P]!", null, null, COMBAT_MESSAGE_RANGE)
+ if(P.suppressed != SUPPRESSED_VERY)
+ visible_message("[src] is hit by \a [P]!", null, null, COMBAT_MESSAGE_RANGE)
if(!QDELETED(src)) //Bullet on_hit effect might have already destroyed this object
take_damage(P.damage, P.damage_type, P.flag, 0, turn(P.dir, 180), P.armour_penetration)
@@ -108,7 +110,7 @@
/obj/attack_animal(mob/living/simple_animal/M)
if(!M.melee_damage && !M.obj_damage)
- M.emote("custom", message = "[M.friendly] [src].")
+ INVOKE_ASYNC(M, /mob.proc/emote, "custom", null, "[M.friendly] [src].")
return 0
else
var/play_soundeffect = 1
@@ -132,10 +134,13 @@
var/amt = max(0, ((force - (move_resist * MOVE_FORCE_CRUSH_RATIO)) / (move_resist * MOVE_FORCE_CRUSH_RATIO)) * 10)
take_damage(amt, BRUTE)
-/obj/attack_slime(mob/living/simple_animal/slime/user)
- if(!user.is_adult)
+/obj/attack_slime(mob/living/simple_animal/slime/M)
+ if(!M.is_adult)
return
- attack_generic(user, rand(15), "melee", 1)
+ var/damage = rand(15)
+ if(M.transformeffects & SLIME_EFFECT_RED)
+ damage *= 1.1
+ attack_generic(M, damage, "melee", 1)
/obj/mech_melee_attack(obj/mecha/M)
M.do_attack_animation(src)
@@ -159,7 +164,7 @@
return take_damage(M.force*3, mech_damtype, "melee", play_soundeffect, get_dir(src, M)) // multiplied by 3 so we can hit objs hard but not be overpowered against mobs.
/obj/singularity_act()
- ex_act(EXPLODE_DEVASTATE)
+ SSexplosions.high_mov_atom += src
if(src && !QDELETED(src))
qdel(src)
return 2
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index bcdb227cb7c64..2795ce9221bf7 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -1,4 +1,3 @@
-
/obj
animate_movement = SLIDE_STEPS
speech_span = SPAN_ROBOT
@@ -78,13 +77,14 @@
obj_flags &= ~string_to_objflag[flag]
else
obj_flags |= string_to_objflag[flag]
+
if((obj_flags & ON_BLUEPRINTS) && isturf(loc))
var/turf/T = loc
T.add_blueprints_preround(src)
/obj/Destroy(force=FALSE)
- if(!ismachinery(src))
- STOP_PROCESSING(SSobj, src) // TODO: Have a processing bitflag to reduce on unnecessary loops through the processing lists
+ if(!ismachinery(src) && (datum_flags & DF_ISPROCESSING))
+ STOP_PROCESSING(SSobj, src)
SStgui.close_uis(src)
. = ..()
@@ -92,7 +92,7 @@
SEND_SIGNAL(src, COMSIG_OBJ_SETANCHORED, anchorvalue)
anchored = anchorvalue
-/obj/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force)
+/obj/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, quickstart = TRUE)
..()
if(obj_flags & FROZEN)
visible_message("[src] shatters into a million pieces!")
@@ -105,12 +105,42 @@
else
return null
+/obj/assume_air_moles(datum/gas_mixture/giver, moles)
+ if(loc)
+ return loc.assume_air_moles(giver, moles)
+ else
+ return null
+
+/obj/assume_air_ratio(datum/gas_mixture/giver, ratio)
+ if(loc)
+ return loc.assume_air_ratio(giver, ratio)
+ else
+ return null
+
+/obj/transfer_air(datum/gas_mixture/taker, moles)
+ if(loc)
+ return loc.transfer_air(taker, moles)
+ else
+ return null
+
+/obj/transfer_air_ratio(datum/gas_mixture/taker, ratio)
+ if(loc)
+ return loc.transfer_air_ratio(taker, ratio)
+ else
+ return null
+
/obj/remove_air(amount)
if(loc)
return loc.remove_air(amount)
else
return null
+/obj/remove_air_ratio(ratio)
+ if(loc)
+ return loc.remove_air_ratio(ratio)
+ else
+ return null
+
/obj/return_air()
if(loc)
return loc.return_air()
@@ -125,8 +155,7 @@
if(breath_request>0)
var/datum/gas_mixture/environment = return_air()
- var/breath_percentage = BREATH_VOLUME / environment.return_volume()
- return remove_air(environment.total_moles() * breath_percentage)
+ return remove_air_ratio(BREATH_VOLUME / environment.return_volume())
else
return null
@@ -134,7 +163,7 @@
if((obj_flags & IN_USE) && !(obj_flags & USES_TGUI))
var/is_in_use = FALSE
var/list/nearby = viewers(1, src)
- for(var/mob/M in nearby)
+ for(var/mob/M as() in nearby)
if ((M.client && M.machine == src))
is_in_use = TRUE
ui_interact(M)
@@ -163,7 +192,7 @@
if(obj_flags & IN_USE)
var/is_in_use = FALSE
if(update_viewers)
- for(var/mob/M in viewers(1, src))
+ for(var/mob/M as() in viewers(1, src))
if ((M.client && M.machine == src))
is_in_use = TRUE
src.interact(M)
@@ -182,22 +211,24 @@
return
ui_interact(user)
-/obj/proc/container_resist(mob/living/user)
- return
-
/mob/proc/unset_machine()
- if(machine)
- machine.on_unset_machine(src)
- machine = null
+ SIGNAL_HANDLER
+
+ if(!machine)
+ return
+ UnregisterSignal(machine, COMSIG_PARENT_QDELETING)
+ machine.on_unset_machine(src)
+ machine = null
//called when the user unsets the machine.
/atom/movable/proc/on_unset_machine(mob/user)
return
/mob/proc/set_machine(obj/O)
- if(src.machine)
+ if(machine)
unset_machine()
- src.machine = O
+ machine = O
+ RegisterSignal(O, COMSIG_PARENT_QDELETING, .proc/unset_machine)
if(istype(O))
O.obj_flags |= IN_USE
@@ -217,7 +248,18 @@
/obj/get_dumping_location(datum/component/storage/source,mob/user)
return get_turf(src)
-/obj/proc/CanAStarPass()
+/**
+ * This proc is used for telling whether something can pass by this object in a given direction, for use by the pathfinding system.
+ *
+ * Trying to generate one long path across the station will call this proc on every single object on every single tile that we're seeing if we can move through, likely
+ * multiple times per tile since we're likely checking if we can access said tile from multiple directions, so keep these as lightweight as possible.
+ *
+ * Arguments:
+ * * ID- An ID card representing what access we have (and thus if we can open things like airlocks or windows to pass through them). The ID card's physical location does not matter, just the reference
+ * * to_dir- What direction we're trying to move in, relevant for things like directional windows that only block movement in certain directions
+ * * caller- The movable we're checking pass flags for, if we're making any such checks
+ **/
+/obj/proc/CanAStarPass(obj/item/card/id/ID, to_dir, atom/movable/caller)
. = !density
/obj/proc/check_uplink_validity()
@@ -363,3 +405,8 @@
//generate_tgm_metadata(thing) handles everything inside the {} for you
/obj/proc/on_object_saved(var/depth = 0)
return ""
+
+/obj/handle_ricochet(obj/item/projectile/P)
+ . = ..()
+ if(. && ricochet_damage_mod)
+ take_damage(P.damage * ricochet_damage_mod, P.damage_type, P.flag, 0, turn(P.dir, 180), P.armour_penetration) // pass along ricochet_damage_mod damage to the structure for the ricochet
diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm
index dafc935c0e13b..c3b803760be2e 100644
--- a/code/game/objects/structures.dm
+++ b/code/game/objects/structures.dm
@@ -11,9 +11,12 @@
var/mob/living/structureclimber
var/broken = 0 //similar to machinery's stat BROKEN
+ flags_ricochet = RICOCHET_HARD
+ ricochet_chance_mod = 0.5
+
/obj/structure/Initialize()
if (!armor)
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50, "stamina" = 0)
. = ..()
if(smooth)
queue_smooth(src)
diff --git a/code/game/objects/structures/ai_core.dm b/code/game/objects/structures/ai_core.dm
index c376a453a0437..ac7b7633f677a 100644
--- a/code/game/objects/structures/ai_core.dm
+++ b/code/game/objects/structures/ai_core.dm
@@ -62,7 +62,7 @@
return FALSE
var/turf/T = get_turf(src)
var/area/A = get_area(src)
- if(!A.blob_allowed)
+ if(!(A.area_flags & BLOBS_ALLOWED))
return FALSE
if(!A.power_equip)
return FALSE
diff --git a/code/game/objects/structures/aliens.dm b/code/game/objects/structures/aliens.dm
index 9c0158d6195da..783c213c27488 100644
--- a/code/game/objects/structures/aliens.dm
+++ b/code/game/objects/structures/aliens.dm
@@ -81,7 +81,7 @@
resintype = "wall"
canSmoothWith = list(/obj/structure/alien/resin/wall, /obj/structure/alien/resin/membrane)
-/obj/structure/alien/resin/wall/BlockSuperconductivity()
+/obj/structure/alien/resin/wall/BlockThermalConductivity()
return 1
/obj/structure/alien/resin/membrane
@@ -154,7 +154,7 @@
return FALSE
for(var/turf/T in U.GetAtmosAdjacentTurfs())
- if((locate(/obj/structure/alien/flesh) in T) || (locate(/obj/structure/alien/weeds) in T))
+ if((locate(/obj/structure/alien/weeds) in T))
continue
if(is_type_in_typecache(T, blacklisted_turfs))
@@ -295,9 +295,9 @@
if(kill)
child.Die()
else
- for(var/mob/M in range(1,src))
- if(CanHug(M))
- child.Leap(M)
+ for(var/mob/living/carbon/C in ohearers(1,src))
+ if(CanHug(C))
+ child.Leap(C)
break
/obj/structure/alien/egg/obj_break(damage_flag)
@@ -329,6 +329,12 @@
status = BURST
icon_state = "egg_hatched"
+/obj/structure/alien/egg/troll
+
+/obj/structure/alien/egg/troll/finish_bursting(kill = TRUE)
+ qdel(child)
+ new /obj/item/paper/troll(get_turf(src))
+
#undef BURST
#undef GROWING
#undef GROWN
diff --git a/code/game/objects/structures/artstuff.dm b/code/game/objects/structures/artstuff.dm
index b8320c80fbd2e..fa15eadb666ae 100644
--- a/code/game/objects/structures/artstuff.dm
+++ b/code/game/objects/structures/artstuff.dm
@@ -16,12 +16,12 @@
//Adding canvases
/obj/structure/easel/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/canvas))
- var/obj/item/canvas/C = I
- user.dropItemToGround(C)
- painting = C
- C.forceMove(get_turf(src))
- C.layer = layer+0.1
- user.visible_message("[user] puts \the [C] on \the [src].","You place \the [C] on \the [src].")
+ var/obj/item/canvas/canvas = I
+ user.dropItemToGround(canvas)
+ painting = canvas
+ canvas.forceMove(get_turf(src))
+ canvas.layer = layer+0.1
+ user.visible_message("[user] puts \the [canvas] on \the [src].", "You place \the [canvas] on \the [src].")
else
return ..()
@@ -35,101 +35,415 @@
else
painting = null
-
-//////////////
-// CANVASES //
-//////////////
-
-#define AMT_OF_CANVASES 4 //Keep this up to date or shit will break.
-
-//To safe memory on making /icons we cache the blanks..
-GLOBAL_LIST_INIT(globalBlankCanvases, new(AMT_OF_CANVASES))
-
/obj/item/canvas
name = "canvas"
desc = "Draw out your soul on this canvas!"
icon = 'icons/obj/artstuff.dmi'
icon_state = "11x11"
resistance_flags = FLAMMABLE
- var/whichGlobalBackup = 1 //List index
+ flags_1 = UNPAINTABLE_1
+ var/width = 11
+ var/height = 11
+ var/list/grid
+ var/canvas_color = "#ffffff" //empty canvas color
+ var/used = FALSE
+ var/painting_name = "Untitled Artwork" //Painting name, this is set after framing.
+ var/finalized = FALSE //Blocks edits
+ var/author_ckey
+ var/icon_generated = FALSE
+ var/icon/generated_icon
+ ///boolean that blocks persistence from saving it. enabled from printing copies, because we do not want to save copies.
+ var/no_save = FALSE
-/obj/item/canvas/nineteenXnineteen
- icon_state = "19x19"
- whichGlobalBackup = 2
+ // Painting overlay offset when framed
+ var/framed_offset_x = 11
+ var/framed_offset_y = 10
-/obj/item/canvas/twentythreeXnineteen
- icon_state = "23x19"
- whichGlobalBackup = 3
+ pixel_x = 10
+ pixel_y = 9
-/obj/item/canvas/twentythreeXtwentythree
- icon_state = "23x23"
- whichGlobalBackup = 4
-
-//HEY YOU
-//ARE YOU READING THE CODE FOR CANVASES?
-//ARE YOU AWARE THEY CRASH HALF THE SERVER WHEN SOMEONE DRAWS ON THEM...
-//...AND NOBODY CAN FIGURE OUT WHY?
-//THEN GO ON BRAVE TRAVELER
-//TRY TO FIX THEM AND REMOVE THIS CODE
/obj/item/canvas/Initialize()
- ..()
- return INITIALIZE_HINT_QDEL //Delete on creation
-
-//Find the right size blank canvas
-/obj/item/canvas/proc/getGlobalBackup()
- . = null
- if(GLOB.globalBlankCanvases[whichGlobalBackup])
- . = GLOB.globalBlankCanvases[whichGlobalBackup]
+ . = ..()
+ reset_grid()
+
+/obj/item/canvas/proc/reset_grid()
+ grid = new/list(width,height)
+ for(var/x in 1 to width)
+ for(var/y in 1 to height)
+ grid[x][y] = canvas_color
+
+/obj/item/canvas/attack_self(mob/user)
+ . = ..()
+ ui_interact(user)
+
+/obj/item/canvas/ui_state(mob/user)
+ if(finalized)
+ return GLOB.physical_obscured_state
else
- var/icon/I = icon(initial(icon),initial(icon_state))
- GLOB.globalBlankCanvases[whichGlobalBackup] = I
- . = I
+ return GLOB.default_state
+
+/obj/item/canvas/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Canvas", name)
+ ui.set_autoupdate(FALSE)
+ ui.open()
+
+/obj/item/canvas/attackby(obj/item/I, mob/living/user, params)
+ if(user.a_intent == INTENT_HELP)
+ ui_interact(user)
+ else
+ return ..()
+/obj/item/canvas/ui_data(mob/user)
+ . = ..()
+ .["grid"] = grid
+ .["name"] = painting_name
+ .["finalized"] = finalized
+
+/obj/item/canvas/examine(mob/user)
+ . = ..()
+ ui_interact(user)
+/obj/item/canvas/ui_act(action, params)
+ . = ..()
+ if(. || finalized)
+ return
+ var/mob/user = usr
+ switch(action)
+ if("paint")
+ var/obj/item/I = user.get_active_held_item()
+ var/color = get_paint_tool_color(I)
+ if(!color)
+ return FALSE
+ var/x = text2num(params["x"])
+ var/y = text2num(params["y"])
+ grid[x][y] = color
+ used = TRUE
+ update_icon()
+ . = TRUE
+ if("finalize")
+ . = TRUE
+ if(!finalized)
+ finalize(user)
-//One pixel increments
-/obj/item/canvas/attackby(obj/item/I, mob/user, params)
- //Click info
- var/list/click_params = params2list(params)
- var/pixX = text2num(click_params["icon-x"])
- var/pixY = text2num(click_params["icon-y"])
+/obj/item/canvas/proc/finalize(mob/user)
+ finalized = TRUE
+ author_ckey = user.ckey
+ generate_proper_overlay()
+ try_rename(user)
- //Should always be true, otherwise you didn't click the object, but let's check because SS13~
- if(!click_params || !click_params["icon-x"] || !click_params["icon-y"])
+/obj/item/canvas/update_overlays()
+ . = ..()
+ if(icon_generated)
+ var/mutable_appearance/detail = mutable_appearance(generated_icon)
+ detail.pixel_x = 1
+ detail.pixel_y = 1
+ . += detail
+ return
+ if(!used)
return
- //Cleaning one pixel with a soap or rag
- if(istype(I, /obj/item/soap) || istype(I, /obj/item/reagent_containers/glass/rag))
- //Pixel info created only when needed
- var/icon/masterpiece = icon(icon,icon_state)
- var/thePix = masterpiece.GetPixel(pixX,pixY)
- var/icon/Ico = getGlobalBackup()
- if(!Ico)
- qdel(masterpiece)
- return
+ var/mutable_appearance/detail = mutable_appearance(icon, "[icon_state]wip")
+ detail.pixel_x = 1
+ detail.pixel_y = 1
+ . += detail
- var/theOriginalPix = Ico.GetPixel(pixX,pixY)
- if(thePix != theOriginalPix) //colour changed
- DrawPixelOn(theOriginalPix,pixX,pixY)
- qdel(masterpiece)
+/obj/item/canvas/proc/generate_proper_overlay()
+ if(icon_generated)
+ return
+ var/png_filename = "data/paintings/temp_painting.png"
+ var/result = rustg_dmi_create_png(png_filename,"[width]","[height]",get_data_string())
+ if(result)
+ CRASH("Error generating painting png : [result]")
+ generated_icon = new(png_filename)
+ icon_generated = TRUE
+ update_icon()
+
+/obj/item/canvas/proc/get_data_string()
+ var/list/data = list()
+ for(var/y in 1 to height)
+ for(var/x in 1 to width)
+ data += grid[x][y]
+ return data.Join("")
+
+//Todo make this element ?
+/obj/item/canvas/proc/get_paint_tool_color(obj/item/I)
+ if(!I)
+ return
+ if(istype(I, /obj/item/toy/crayon))
+ var/obj/item/toy/crayon/crayon = I
+ return crayon.paint_color
+ else if(istype(I, /obj/item/pen))
+ var/obj/item/pen/P = I
+ switch(P.colour)
+ if("black")
+ return "#000000"
+ if("blue")
+ return "#0000ff"
+ if("red")
+ return "#ff0000"
+ return P.colour
+ else if(istype(I, /obj/item/soap) || istype(I, /obj/item/reagent_containers/glass/rag))
+ return canvas_color
+
+/obj/item/canvas/proc/try_rename(mob/user)
+ var/new_name = stripped_input(user,"What do you want to name the painting?")
+ if(new_name != painting_name && new_name && user.canUseTopic(src,BE_CLOSE))
+ painting_name = new_name
+ SStgui.update_uis(src)
+
+/obj/item/canvas/nineteen_nineteen
+ icon_state = "19x19"
+ width = 19
+ height = 19
+ pixel_x = 6
+ pixel_y = 9
+ framed_offset_x = 8
+ framed_offset_y = 9
+
+/obj/item/canvas/twentythree_nineteen
+ icon_state = "23x19"
+ width = 23
+ height = 19
+ pixel_x = 4
+ pixel_y = 10
+ framed_offset_x = 6
+ framed_offset_y = 8
- //Drawing one pixel with a crayon
- else if(istype(I, /obj/item/toy/crayon))
- var/obj/item/toy/crayon/C = I
- DrawPixelOn(C.paint_color, pixX, pixY)
+/obj/item/canvas/twentythree_twentythree
+ icon_state = "23x23"
+ width = 23
+ height = 23
+ pixel_x = 5
+ pixel_y = 9
+ framed_offset_x = 5
+ framed_offset_y = 6
+
+/obj/item/canvas/twentyfour_twentyfour
+ name = "ai universal standard canvas"
+ desc = "Besides being very large, the AI can accept these as a display from their internal database after you've hung it up."
+ icon_state = "24x24"
+ width = 24
+ height = 24
+ pixel_x = 2
+ pixel_y = 1
+ framed_offset_x = 4
+ framed_offset_y = 5
+
+/obj/item/wallframe/painting
+ name = "painting frame"
+ desc = "The perfect showcase for your favorite deathtrap memories."
+ icon = 'icons/obj/decals.dmi'
+ flags_1 = NONE
+ icon_state = "frame-empty"
+ result_path = /obj/structure/sign/painting
+
+/obj/structure/sign/painting
+ name = "Painting"
+ desc = "Art or \"Art\"? You decide."
+ icon = 'icons/obj/decals.dmi'
+ icon_state = "frame-empty"
+ var/base_icon_state = "frame" //temporal replacement before the update_appearance() port
+ buildable_sign = FALSE
+ ///Canvas we're currently displaying.
+ var/obj/item/canvas/current_canvas
+ ///Description set when canvas is added.
+ var/desc_with_canvas
+ var/persistence_id
+
+/obj/structure/sign/painting/Initialize(mapload, dir, building)
+ . = ..()
+ SSpersistence.painting_frames += src
+ if(dir)
+ setDir(dir)
+ if(building)
+ pixel_x = (dir & 3)? 0 : (dir == 4 ? -30 : 30)
+ pixel_y = (dir & 3)? (dir ==1 ? -30 : 30) : 0
+
+/obj/structure/sign/painting/Destroy()
+ . = ..()
+ SSpersistence.painting_frames -= src
+
+/obj/structure/sign/painting/attackby(obj/item/I, mob/user, params)
+ if(!current_canvas && istype(I, /obj/item/canvas))
+ frame_canvas(user,I)
+ else if(current_canvas && current_canvas.painting_name == initial(current_canvas.painting_name) && istype(I,/obj/item/pen))
+ try_rename(user)
else
return ..()
+/obj/structure/sign/painting/examine(mob/user)
+ . = ..()
+ if(persistence_id)
+ . += "Any painting placed here will be archived at the end of the shift."
+ if(current_canvas)
+ current_canvas.ui_interact(user)
+ . += "Use wirecutters to remove the painting."
-//Clean the whole canvas
-/obj/item/canvas/attack_self(mob/user)
- if(!user)
+/obj/structure/sign/painting/wirecutter_act(mob/living/user, obj/item/I)
+ . = ..()
+ if(current_canvas)
+ current_canvas.forceMove(drop_location())
+ current_canvas = null
+ to_chat(user, "You remove the painting from the frame.")
+ update_painting_stuff()
+ return TRUE
+
+/obj/structure/sign/painting/proc/frame_canvas(mob/user,obj/item/canvas/new_canvas)
+ if(user.transferItemToLoc(new_canvas,src))
+ current_canvas = new_canvas
+ if(!current_canvas.finalized)
+ current_canvas.finalize(user)
+ to_chat(user,"You frame [current_canvas].")
+ update_painting_stuff()
+
+/obj/structure/sign/painting/proc/try_rename(mob/user)
+ if(current_canvas.painting_name == initial(current_canvas.painting_name))
+ current_canvas.try_rename(user)
+
+/obj/structure/sign/painting/proc/update_painting_stuff()
+ name = current_canvas ? "painting - [current_canvas.painting_name]" : initial(name)
+ desc = current_canvas ? desc_with_canvas : initial(desc)
+ update_icon()
+ update_icon_state()
+ update_overlays()
+
+/obj/structure/sign/painting/update_icon_state()
+ icon_state = "[base_icon_state]-[current_canvas?.generated_icon ? "overlay" : "empty"]"
+ return ..()
+
+/obj/structure/sign/painting/update_overlays()
+ . = ..()
+ if(!current_canvas?.generated_icon)
+ return
+
+ var/mutable_appearance/MA = mutable_appearance(current_canvas.generated_icon)
+ MA.pixel_x = current_canvas.framed_offset_x
+ MA.pixel_y = current_canvas.framed_offset_y
+ . += MA
+ var/mutable_appearance/frame = mutable_appearance(current_canvas.icon,"[current_canvas.icon_state]frame")
+ frame.pixel_x = current_canvas.framed_offset_x - 1
+ frame.pixel_y = current_canvas.framed_offset_y - 1
+ . += frame
+
+/**
+ * Loads a painting from SSpersistence. Called globally by said subsystem when it inits
+ *
+ * Deleting paintings leaves their json, so this proc will remove the json and try again if it finds one of those.
+ */
+/obj/structure/sign/painting/proc/load_persistent()
+ if(!persistence_id || !SSpersistence.paintings || !SSpersistence.paintings[persistence_id])
return
- var/icon/blank = getGlobalBackup()
- if(blank)
- //it's basically a giant etch-a-sketch
- icon = blank
- user.visible_message("[user] cleans the canvas.","You clean the canvas.")
+ var/list/painting_category = SSpersistence.paintings[persistence_id]
+ var/list/painting
+ while(!painting)
+ if(!length(SSpersistence.paintings[persistence_id]))
+ return //aborts loading anything this category has no usable paintings
+ var/list/chosen = pick(painting_category)
+ if(!fexists("data/paintings/[persistence_id]/[chosen["md5"]].png")) //shitmin deleted this art, lets remove json entry to avoid errors
+ painting_category -= list(chosen)
+ continue //and try again
+ painting = chosen
+ var/title = painting["title"]
+ var/author = painting["ckey"]
+ var/png = "data/paintings/[persistence_id]/[painting["md5"]].png"
+ if(!title)
+ title = "Untitled Artwork" //legacy artwork allowed null names which was bad for the json, lets fix that
+ painting["title"] = title
+ var/icon/I = new(png)
+ var/obj/item/canvas/new_canvas
+ var/w = I.Width()
+ var/h = I.Height()
+ for(var/T in typesof(/obj/item/canvas))
+ new_canvas = T
+ if(initial(new_canvas.width) == w && initial(new_canvas.height) == h)
+ new_canvas = new T(src)
+ break
+ new_canvas.fill_grid_from_icon(I)
+ new_canvas.generated_icon = I
+ new_canvas.icon_generated = TRUE
+ new_canvas.finalized = TRUE
+ new_canvas.painting_name = title
+ new_canvas.author_ckey = author
+ new_canvas.name = "painting - [title]"
+ current_canvas = new_canvas
+ update_painting_stuff()
+
+/obj/structure/sign/painting/proc/save_persistent()
+ if(!persistence_id || !current_canvas || current_canvas.no_save)
+ return
+ if(sanitize_filename(persistence_id) != persistence_id)
+ stack_trace("Invalid persistence_id - [persistence_id]")
+ return
+ if(!current_canvas.painting_name)
+ current_canvas.painting_name = "Untitled Artwork"
+ var/data = current_canvas.get_data_string()
+ var/md5 = md5(lowertext(data))
+ var/list/current = SSpersistence.paintings[persistence_id]
+ if(!current)
+ current = list()
+ for(var/list/entry in current)
+ if(entry["md5"] == md5)
+ return
+ var/png_directory = "data/paintings/[persistence_id]/"
+ var/png_path = png_directory + "[md5].png"
+ var/result = rustg_dmi_create_png(png_path,"[current_canvas.width]","[current_canvas.height]",data)
+ if(result)
+ CRASH("Error saving persistent painting: [result]")
+ current += list(list("title" = current_canvas.painting_name , "md5" = md5, "ckey" = current_canvas.author_ckey))
+ SSpersistence.paintings[persistence_id] = current
+/obj/item/canvas/proc/fill_grid_from_icon(icon/I)
+ var/h = I.Height() + 1
+ for(var/x in 1 to width)
+ for(var/y in 1 to height)
+ grid[x][y] = I.GetPixel(x,h-y)
-#undef AMT_OF_CANVASES
+//Presets for art gallery mapping, for paintings to be shared across stations
+/obj/structure/sign/painting/library
+ name = "\improper Public Painting Exhibit mounting"
+ desc = "For art pieces hung by the public."
+ desc_with_canvas = "A piece of art (or \"art\"). Anyone could've hung it."
+ persistence_id = "library"
+
+/obj/structure/sign/painting/library_secure
+ name = "\improper Curated Painting Exhibit mounting"
+ desc = "For masterpieces hand-picked by the curator."
+ desc_with_canvas = "A masterpiece hand-picked by the curator, supposedly."
+ persistence_id = "library_secure"
+
+/obj/structure/sign/painting/library_private // keep your smut away from prying eyes, or non-librarians at least
+ name = "\improper Private Painting Exhibit mounting"
+ desc = "For art pieces deemed too subversive or too illegal to be shared outside of curators."
+ desc_with_canvas = "A painting hung away from lesser minds."
+ persistence_id = "library_private"
+
+/obj/structure/sign/painting/vv_get_dropdown()
+ . = ..()
+ VV_DROPDOWN_OPTION(VV_HK_REMOVE_PAINTING, "Remove Persistent Painting")
+
+/obj/structure/sign/painting/vv_do_topic(list/href_list)
+ . = ..()
+ if(href_list[VV_HK_REMOVE_PAINTING])
+ if(!check_rights(NONE))
+ return
+ var/mob/user = usr
+ if(!persistence_id || !current_canvas)
+ to_chat(user,"This is not a persistent painting.")
+ return
+ var/md5 = md5(lowertext(current_canvas.get_data_string()))
+ var/author = current_canvas.author_ckey
+ var/list/current = SSpersistence.paintings[persistence_id]
+ if(current)
+ for(var/list/entry in current)
+ if(entry["md5"] == md5)
+ current -= entry
+ var/png = "data/paintings/[persistence_id]/[md5].png"
+ fdel(png)
+ for(var/obj/structure/sign/painting/P in SSpersistence.painting_frames)
+ if(P.current_canvas && md5(P.current_canvas.get_data_string()) == md5)
+ QDEL_NULL(P.current_canvas)
+ P.update_painting_stuff()
+ log_admin("[key_name(user)] has deleted a persistent painting made by [author].")
+ message_admins("[key_name_admin(user)] has deleted persistent painting made by [author].")
diff --git a/code/game/objects/structures/barsigns.dm b/code/game/objects/structures/barsigns.dm
index cb40f3c12d291..5328175a2ad76 100644
--- a/code/game/objects/structures/barsigns.dm
+++ b/code/game/objects/structures/barsigns.dm
@@ -6,7 +6,7 @@
req_access = list(ACCESS_BAR)
max_integrity = 500
integrity_failure = 250
- armor = list("melee" = 20, "bullet" = 20, "laser" = 20, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50)
+ armor = list("melee" = 20, "bullet" = 20, "laser" = 20, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50, "stamina" = 0)
buildable_sign = 0
var/panel_open = FALSE
@@ -312,4 +312,4 @@
name = null
icon = "empty"
desc = "This sign doesn't seem to be on."
- rename_area = FALSE
\ No newline at end of file
+ rename_area = FALSE
diff --git a/code/game/objects/structures/beds_chairs/alien_nest.dm b/code/game/objects/structures/beds_chairs/alien_nest.dm
index e2bb991e71679..6686f04d83eed 100644
--- a/code/game/objects/structures/beds_chairs/alien_nest.dm
+++ b/code/game/objects/structures/beds_chairs/alien_nest.dm
@@ -48,8 +48,8 @@
unbuckle_mob(M)
add_fingerprint(user)
-/obj/structure/bed/nest/user_buckle_mob(mob/living/M, mob/living/user)
- if ( !ismob(M) || (get_dist(src, user) > 1) || (M.loc != src.loc) || user.incapacitated() || M.buckled )
+/obj/structure/bed/nest/user_buckle_mob(mob/living/M, mob/living/user, check_loc = TRUE)
+ if(!ismob(M) || (get_dist(src, user) > 1) || (M.loc != src.loc) || user.incapacitated() || M.buckled)
return
if(M.getorgan(/obj/item/organ/alien/plasmavessel))
diff --git a/code/game/objects/structures/beds_chairs/bed.dm b/code/game/objects/structures/beds_chairs/bed.dm
index 8534d8cabaa14..7407b06521526 100644
--- a/code/game/objects/structures/beds_chairs/bed.dm
+++ b/code/game/objects/structures/beds_chairs/bed.dm
@@ -168,7 +168,7 @@
anchored = FALSE
buildstacktype = /obj/item/stack/sheet/mineral/wood
buildstackamount = 10
- var/mob/living/owner = null
+ var/owned = FALSE
/obj/structure/bed/dogbed/ian
desc = "Ian's bed! Looks comfy."
@@ -200,10 +200,14 @@
name = "Walter's bed"
anchored = TRUE
+///Used to set the owner of a dogbed, returns FALSE if called on an owned bed or an invalid one, TRUE if the possesion succeeds
/obj/structure/bed/dogbed/proc/update_owner(mob/living/M)
- owner = M
+ if(owned || type != /obj/structure/bed/dogbed) //Only marked beds work, this is hacky but I'm a hacky man
+ return FALSE //Failed
+ owned = TRUE
name = "[M]'s bed"
desc = "[M]'s bed! Looks comfy."
+ return TRUE //Let any callers know that this bed is ours now
/obj/structure/bed/dogbed/buckle_mob(mob/living/M, force, check_loc)
. = ..()
diff --git a/code/game/objects/structures/beds_chairs/chair.dm b/code/game/objects/structures/beds_chairs/chair.dm
index ded6eb02b1ccc..1f17b91a6bc49 100644
--- a/code/game/objects/structures/beds_chairs/chair.dm
+++ b/code/game/objects/structures/beds_chairs/chair.dm
@@ -65,6 +65,11 @@
W.setDir(dir)
qdel(src)
+/obj/structure/chair/ratvar_act()
+ var/obj/structure/chair/brass/B = new(get_turf(src))
+ B.setDir(dir)
+ qdel(src)
+
/obj/structure/chair/attackby(obj/item/W, mob/user, params)
if(W.tool_behaviour == TOOL_WRENCH && !(flags_1&NODECONSTRUCT_1))
W.play_tool_sound(src)
@@ -385,6 +390,9 @@
playsound(src, 'sound/effects/servostep.ogg', 50, FALSE)
return FALSE
+/obj/structure/chair/brass/ratvar_act()
+ return
+
/obj/structure/chair/brass/AltClick(mob/living/user)
turns = 0
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
diff --git a/code/game/objects/structures/beds_chairs/sofa.dm b/code/game/objects/structures/beds_chairs/sofa.dm
index 473374f86ff8f..c292772cade41 100644
--- a/code/game/objects/structures/beds_chairs/sofa.dm
+++ b/code/game/objects/structures/beds_chairs/sofa.dm
@@ -3,6 +3,28 @@
icon_state = "sofamiddle"
icon = 'icons/obj/sofa.dmi'
buildstackamount = 1
+ item_chair = null
+ var/mutable_appearance/armrest
+
+/obj/structure/chair/sofa/Initialize()
+ armrest = mutable_appearance(icon, "[icon_state]_armrest", ABOVE_MOB_LAYER)
+ return ..()
+/obj/structure/chair/sofa/post_buckle_mob(mob/living/M)
+ . = ..()
+ update_armrest()
+
+/obj/structure/chair/sofa/proc/update_armrest()
+ if(has_buckled_mobs())
+ add_overlay(armrest)
+ else
+ cut_overlay(armrest)
+
+/obj/structure/chair/sofa/post_unbuckle_mob()
+ . = ..()
+ update_armrest()
+
+/obj/structure/chair/sofa/corner/handle_layer() //only the armrest/back of this chair should cover the mob.
+ return
/obj/structure/chair/sofa/left
icon_state = "sofaend_left"
@@ -11,4 +33,19 @@
icon_state = "sofaend_right"
/obj/structure/chair/sofa/corner
- icon_state = "sofacorner"
\ No newline at end of file
+ icon_state = "sofacorner"
+
+// Original icon ported from Eris(?) and updated to work here.
+/obj/structure/chair/sofa/corp
+ name = "sofa"
+ desc = "Soft and cushy."
+ icon_state = "corp_sofamiddle"
+
+/obj/structure/chair/sofa/corp/left
+ icon_state = "corp_sofaend_left"
+
+/obj/structure/chair/sofa/corp/right
+ icon_state = "corp_sofaend_right"
+
+/obj/structure/chair/sofa/corp/corner
+ icon_state = "corp_sofacorner"
\ No newline at end of file
diff --git a/code/game/objects/structures/bedsheet_bin.dm b/code/game/objects/structures/bedsheet_bin.dm
index 32e86c5d58098..819855c14b279 100644
--- a/code/game/objects/structures/bedsheet_bin.dm
+++ b/code/game/objects/structures/bedsheet_bin.dm
@@ -8,8 +8,10 @@ LINEN BINS
name = "bedsheet"
desc = "A surprisingly soft linen bedsheet."
icon = 'icons/obj/bedsheets.dmi'
+ lefthand_file = 'icons/mob/inhands/misc/bedsheet_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/misc/bedsheet_righthand.dmi'
icon_state = "sheetwhite"
- item_state = "bedsheet"
+ item_state = "sheetwhite"
slot_flags = ITEM_SLOT_NECK
layer = MOB_LAYER
throwforce = 0
@@ -22,6 +24,10 @@ LINEN BINS
dog_fashion = /datum/dog_fashion/head/ghost
var/list/dream_messages = list("white")
+/obj/item/bedsheet/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/bed_tuckable, 0, 0, 0)
+
/obj/item/bedsheet/attack(mob/living/M, mob/user)
if(!attempt_initiate_surgery(src, M, user))
..()
@@ -52,26 +58,31 @@ LINEN BINS
/obj/item/bedsheet/blue
icon_state = "sheetblue"
+ item_state = "sheetblue"
item_color = "blue"
dream_messages = list("blue")
/obj/item/bedsheet/green
icon_state = "sheetgreen"
+ item_state = "sheetgreen"
item_color = "green"
dream_messages = list("green")
/obj/item/bedsheet/grey
icon_state = "sheetgrey"
+ item_state = "sheetgrey"
item_color = "grey"
dream_messages = list("grey")
/obj/item/bedsheet/orange
icon_state = "sheetorange"
+ item_state = "sheetorange"
item_color = "orange"
dream_messages = list("orange")
/obj/item/bedsheet/purple
icon_state = "sheetpurple"
+ item_state = "sheetpurple"
item_color = "purple"
dream_messages = list("purple")
@@ -79,6 +90,7 @@ LINEN BINS
name = "patriotic bedsheet"
desc = "You've never felt more free than when sleeping on this."
icon_state = "sheetUSA"
+ item_state = "sheetUSA"
item_color = "sheetUSA"
dream_messages = list("America", "freedom", "fireworks", "bald eagles")
@@ -86,16 +98,19 @@ LINEN BINS
name = "rainbow bedsheet"
desc = "A multicolored blanket. It's actually several different sheets cut up and sewn together."
icon_state = "sheetrainbow"
+ item_state = "sheetrainbow"
item_color = "rainbow"
dream_messages = list("red", "orange", "yellow", "green", "blue", "purple", "a rainbow")
/obj/item/bedsheet/red
icon_state = "sheetred"
+ item_state = "sheetred"
item_color = "red"
dream_messages = list("red")
/obj/item/bedsheet/yellow
icon_state = "sheetyellow"
+ item_state = "sheetyellow"
item_color = "yellow"
dream_messages = list("yellow")
@@ -103,6 +118,7 @@ LINEN BINS
name = "mime's blanket"
desc = "A very soothing striped blanket. All the noise just seems to fade out when you're under the covers in this."
icon_state = "sheetmime"
+ item_state = "sheetmime"
item_color = "mime"
dream_messages = list("silence", "gestures", "a pale face", "a gaping mouth", "the mime")
@@ -110,6 +126,7 @@ LINEN BINS
name = "clown's blanket"
desc = "A rainbow blanket with a clown mask woven in. It smells faintly of bananas."
icon_state = "sheetclown"
+ item_state = "sheetrainbow"
item_color = "clown"
dream_messages = list("honk", "laughter", "a prank", "a joke", "a smiling face", "the clown")
@@ -117,6 +134,7 @@ LINEN BINS
name = "captain's bedsheet"
desc = "It has a Nanotrasen symbol on it, and was woven with a revolutionary new kind of thread guaranteed to have 0.01% permeability for most non-chemical substances, popular among most modern captains."
icon_state = "sheetcaptain"
+ item_state = "sheetcaptain"
item_color = "captain"
dream_messages = list("authority", "a golden ID", "sunglasses", "a green disc", "an antique gun", "the captain")
@@ -124,6 +142,7 @@ LINEN BINS
name = "research director's bedsheet"
desc = "It appears to have a beaker emblem, and is made out of fire-resistant material, although it probably won't protect you in the event of fires you're familiar with every day."
icon_state = "sheetrd"
+ item_state = "sheetrd"
item_color = "director"
dream_messages = list("authority", "a silvery ID", "a bomb", "a mech", "a facehugger", "maniacal laughter", "the research director")
@@ -137,6 +156,7 @@ LINEN BINS
name = "medical blanket"
desc = "It's a sterilized* blanket commonly used in the Medbay. *Sterilization is voided if a virologist is present onboard the station."
icon_state = "sheetmedical"
+ item_state = "sheetmedical"
item_color = "medical"
dream_messages = list("healing", "life", "surgery", "a doctor")
@@ -144,6 +164,7 @@ LINEN BINS
name = "chief medical officer's bedsheet"
desc = "It's a sterilized blanket that has a cross emblem. There's some cat fur on it, likely from Runtime."
icon_state = "sheetcmo"
+ item_state = "sheetcmo"
item_color = "cmo"
dream_messages = list("authority", "a silvery ID", "healing", "life", "surgery", "a cat", "the chief medical officer")
@@ -151,6 +172,7 @@ LINEN BINS
name = "head of security's bedsheet"
desc = "It is decorated with a shield emblem. While crime doesn't sleep, you do, but you are still THE LAW!"
icon_state = "sheethos"
+ item_state = "sheethos"
item_color = "hosred"
dream_messages = list("authority", "a silvery ID", "handcuffs", "a baton", "a flashbang", "sunglasses", "the head of security")
@@ -158,6 +180,7 @@ LINEN BINS
name = "head of personnel's bedsheet"
desc = "It is decorated with a key emblem. For those rare moments when you can rest and cuddle with Ian without someone screaming for you over the radio."
icon_state = "sheethop"
+ item_state = "sheethop"
item_color = "hop"
dream_messages = list("authority", "a silvery ID", "obligation", "a computer", "an ID", "a corgi", "the head of personnel")
@@ -165,6 +188,7 @@ LINEN BINS
name = "chief engineer's bedsheet"
desc = "It is decorated with a wrench emblem. It's highly reflective and stain resistant, so you don't need to worry about ruining it with oil."
icon_state = "sheetce"
+ item_state = "sheetce"
item_color = "chief"
dream_messages = list("authority", "a silvery ID", "the engine", "power tools", "an APC", "a parrot", "the chief engineer")
@@ -172,6 +196,7 @@ LINEN BINS
name = "quartermaster's bedsheet"
desc = "It is decorated with a crate emblem in silver lining. It's rather tough, and just the thing to lie on after a hard day of pushing paper."
icon_state = "sheetqm"
+ item_state = "sheetqm"
item_color = "qm"
dream_messages = list("a grey ID", "a shuttle", "a crate", "a sloth", "the quartermaster")
@@ -179,16 +204,19 @@ LINEN BINS
name = "magician's cape"
desc = "A magician never reveals his secrets."
icon_state = "sheetmagician"
+ item_state = "sheetmagician"
item_color = "magician"
dream_messages = list("trickery", "crime", "a gullible mark", "an angry wizard", "pixie dust")
/obj/item/bedsheet/brown
icon_state = "sheetbrown"
+ item_state = "sheetbrown"
item_color = "cargo"
dream_messages = list("brown")
/obj/item/bedsheet/black
icon_state = "sheetblack"
+ item_state = "sheetblack"
item_color = "black"
dream_messages = list("black")
@@ -196,6 +224,7 @@ LINEN BINS
name = "\improper CentCom bedsheet"
desc = "Woven with advanced nanothread for warmth as well as being very decorated, essential for all officials."
icon_state = "sheetcentcom"
+ item_state = "sheetcentcom"
item_color = "centcom"
dream_messages = list("a unique ID", "authority", "artillery", "an ending")
@@ -203,6 +232,7 @@ LINEN BINS
name = "syndicate bedsheet"
desc = "It has a syndicate emblem and it has an aura of evil."
icon_state = "sheetsyndie"
+ item_state = "sheetsyndie"
item_color = "syndie"
dream_messages = list("a green disc", "a red crystal", "a glowing blade", "a wire-covered ID")
@@ -210,6 +240,7 @@ LINEN BINS
name = "cultist's bedsheet"
desc = "You might dream of Nar'Sie if you sleep with this. It seems rather tattered and glows of an eldritch presence."
icon_state = "sheetcult"
+ item_state = "sheetcult"
item_color = "cult"
dream_messages = list("a tome", "a floating red crystal", "a glowing sword", "a bloody symbol", "a massive humanoid figure")
@@ -217,18 +248,21 @@ LINEN BINS
name = "wizard's bedsheet"
desc = "A special fabric enchanted with magic so you can have an enchanted night. It even glows!"
icon_state = "sheetwiz"
+ item_state = "sheetwiz"
item_color = "wiz"
dream_messages = list("a book", "an explosion", "lightning", "a staff", "a skeleton", "a robe", "magic")
/obj/item/bedsheet/nanotrasen
- name = "nanotrasen bedsheet"
+ name = "\improper Nanotrasen bedsheet"
desc = "It has the Nanotrasen logo on it and has an aura of duty."
icon_state = "sheetNT"
+ item_state = "sheetNT"
item_color = "nanotrasen"
dream_messages = list("authority", "an ending")
/obj/item/bedsheet/ian
icon_state = "sheetian"
+ item_state = "sheetian"
item_color = "ian"
dream_messages = list("a dog", "a corgi", "woof", "bark", "arf")
@@ -236,6 +270,7 @@ LINEN BINS
name = "cosmic space bedsheet"
desc = "Made from the dreams of those who wonder at the stars."
icon_state = "sheetcosmos"
+ item_state = "sheetcosmos"
item_color = "cosmos"
dream_messages = list("the infinite cosmos", "Hans Zimmer music", "a flight through space", "the galaxy", "being fabulous", "shooting stars")
light_power = 2
diff --git a/code/game/objects/structures/catwalk.dm b/code/game/objects/structures/catwalk.dm
index bc36ab7476b94..245a3edef4720 100644
--- a/code/game/objects/structures/catwalk.dm
+++ b/code/game/objects/structures/catwalk.dm
@@ -7,6 +7,11 @@
smooth = SMOOTH_TRUE
canSmoothWith = null
obj_flags = CAN_BE_HIT | BLOCK_Z_FALL
+ //Negates the effect of space and openspace.
+ //Shouldn't be placed above anything else.
+ FASTDMM_PROP(\
+ pipe_astar_cost = -98.5\
+ )
/obj/structure/lattice/catwalk/over
layer = CATWALK_LAYER
@@ -22,9 +27,9 @@
if(C.tool_behaviour == TOOL_WELDER)
if(!C.tool_start_check(user, amount=0))
return FALSE
- to_chat(user, "You begin slicing through the outer plating...")
+ balloon_alert(user, "You start slicing through outer plating")
if(C.use_tool(src, user, 25, volume=100))
- to_chat(user, "You slice off [src]")
+ balloon_alert(user, "[src] sliced off")
deconstruct()
return TRUE
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index 9b9dc40d20151..d287306131d2a 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -7,7 +7,7 @@
drag_slowdown = 1.5 // Same as a prone mob
max_integrity = 200
integrity_failure = 50
- armor = list("melee" = 20, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60)
+ armor = list("melee" = 20, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60, "stamina" = 0)
var/icon_door = null
var/icon_door_override = FALSE //override to have open overlay use icon different to its base's
var/secure = FALSE //secure locker or not, also used if overriding a non-secure locker with a secure door overlay to add fancy lights
@@ -26,7 +26,7 @@
var/max_mob_size = MOB_SIZE_HUMAN //Biggest mob_size accepted by the container
var/mob_storage_capacity = 3 // how many human sized mob/living can fit together inside a closet.
var/storage_capacity = 30 //This is so that someone can't pack hundreds of items in a locker/crate then open it in a populated area to crash clients.
- var/cutting_tool = /obj/item/weldingtool
+ var/cutting_tool = TOOL_WELDER
var/open_sound = 'sound/machines/closet_open.ogg'
var/close_sound = 'sound/machines/closet_close.ogg'
var/open_sound_volume = 35
@@ -40,7 +40,7 @@
var/is_animating_door = FALSE
var/door_anim_squish = 0.30
var/door_anim_angle = 136
- var/door_hinge_x = -6.5
+ var/door_hinge = -6.5
var/door_anim_time = 2.0 // set to 0 to make the door not animate at all
/obj/structure/closet/Initialize(mapload)
if(mapload && !opened) // if closed, any item at the crate's loc is put in the contents
@@ -58,6 +58,8 @@
return ..()
/obj/structure/closet/update_icon()
+ if(istype(src, /obj/structure/closet/supplypod))
+ return . = ..()
cut_overlays()
if(!opened)
layer = OBJ_LAYER
@@ -115,9 +117,9 @@
/obj/structure/closet/proc/get_door_transform(angle)
var/matrix/M = matrix()
- M.Translate(-door_hinge_x, 0)
+ M.Translate(-door_hinge, 0)
M.Multiply(matrix(cos(angle), 0, 0, -sin(angle) * door_anim_squish, 1, 0))
- M.Translate(door_hinge_x, 0)
+ M.Translate(door_hinge, 0)
return M
/obj/structure/closet/examine(mob/user)
@@ -267,11 +269,11 @@
else
return ..()
-/obj/structure/closet/proc/tool_interact(obj/item/W, mob/user)//returns TRUE if attackBy call shouldnt be continued (because tool was used/closet was of wrong type), FALSE if otherwise
+/obj/structure/closet/proc/tool_interact(obj/item/W, mob/user)//returns TRUE if attackBy call shouldn't be continued (because tool was used/closet was of wrong type), FALSE if otherwise
. = TRUE
if(opened)
- if(istype(W, cutting_tool))
- if(W.tool_behaviour == TOOL_WELDER)
+ if(W.tool_behaviour == cutting_tool)
+ if(cutting_tool == TOOL_WELDER)
if(!W.tool_start_check(user, amount=0))
return
@@ -326,7 +328,7 @@
return
/obj/structure/closet/MouseDrop_T(atom/movable/O, mob/living/user)
- if(!istype(O) || O.anchored || istype(O, /obj/screen))
+ if(!istype(O) || O.anchored || istype(O, /atom/movable/screen))
return
if(!istype(user) || user.incapacitated() || !(user.mobility_flags & MOBILITY_STAND))
return
@@ -358,8 +360,11 @@
var/mob/living/L = O
if(!issilicon(L))
L.Paralyze(40)
- O.forceMove(T)
- close()
+ if(istype(src, /obj/structure/closet/supplypod/extractionpod))
+ O.forceMove(src)
+ else
+ O.forceMove(T)
+ close()
else
O.forceMove(T)
return 1
@@ -454,7 +459,6 @@
open()
/obj/structure/closet/AltClick(mob/user)
- ..()
if(!user.canUseTopic(src, BE_CLOSE) || !isturf(loc))
return
if(opened || !secure)
@@ -479,13 +483,13 @@
"You [locked ? null : "un"]lock [src].")
update_icon()
else if(!silent)
- to_chat(user, "Access Denied")
+ to_chat(user, "Access Denied.")
else if(secure && broken)
to_chat(user, "\The [src] is broken!")
/obj/structure/closet/emag_act(mob/user)
if(secure && !broken)
- user.visible_message("Sparks fly from [src]!",
+ user?.visible_message("Sparks fly from [src]!",
"You scramble [src]'s lock, breaking it open!",
"You hear a faint electrical spark.")
playsound(src, "sparks", 50, 1)
@@ -495,7 +499,7 @@
/obj/structure/closet/get_remote_view_fullscreens(mob/user)
if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS)))
- user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1)
+ user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/impaired, 1)
/obj/structure/closet/emp_act(severity)
. = ..()
@@ -516,9 +520,14 @@
req_access += pick(get_all_accesses())
/obj/structure/closet/contents_explosion(severity, target)
- for(var/atom/A in contents)
- A.ex_act(severity, target)
- CHECK_TICK
+ for(var/thing in contents)
+ switch(severity)
+ if(EXPLODE_DEVASTATE)
+ SSexplosions.high_mov_atom += thing
+ if(EXPLODE_HEAVY)
+ SSexplosions.med_mov_atom += thing
+ if(EXPLODE_LIGHT)
+ SSexplosions.low_mov_atom += thing
/obj/structure/closet/singularity_act()
dump_contents()
diff --git a/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm b/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm
index 77fa687182134..339588fd17736 100644
--- a/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm
+++ b/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm
@@ -8,7 +8,7 @@
max_integrity = 70
integrity_failure = 0
can_weld_shut = 0
- cutting_tool = /obj/item/wirecutters
+ cutting_tool = TOOL_WIRECUTTER
open_sound = "rustle"
material_drop = /obj/item/stack/sheet/cardboard
delivery_icon = "deliverybox"
@@ -70,7 +70,7 @@
mob_storage_capacity = 5
resistance_flags = NONE
move_speed_multiplier = 2
- cutting_tool = /obj/item/weldingtool
+ cutting_tool = TOOL_WELDER
open_sound = 'sound/machines/crate_open.ogg'
close_sound = 'sound/machines/crate_close.ogg'
open_sound_volume = 35
diff --git a/code/game/objects/structures/crates_lockers/closets/gimmick.dm b/code/game/objects/structures/crates_lockers/closets/gimmick.dm
index e1836e6dec707..2d276d496adde 100644
--- a/code/game/objects/structures/crates_lockers/closets/gimmick.dm
+++ b/code/game/objects/structures/crates_lockers/closets/gimmick.dm
@@ -14,7 +14,6 @@
name = "strange closet"
desc = "It looks alien!"
icon_state = "alien"
- door_anim_time = 0 // no animation
/obj/structure/closet/gimmick
diff --git a/code/game/objects/structures/crates_lockers/closets/job_closets.dm b/code/game/objects/structures/crates_lockers/closets/job_closets.dm
index cf6e39481cfa1..90da208bacb0f 100644
--- a/code/game/objects/structures/crates_lockers/closets/job_closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets/job_closets.dm
@@ -137,6 +137,7 @@
var/static/items_inside = list(
/obj/item/clothing/suit/hooded/wintercoat/cargo = 1,
/obj/item/clothing/under/rank/cargo/tech = 3,
+ /obj/item/clothing/under/rank/cargo/tech/skirt = 3,
/obj/item/clothing/shoes/sneakers/black = 3,
/obj/item/clothing/gloves/fingerless = 3,
/obj/item/clothing/head/soft = 3,
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm b/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm
index c6cd9426c20ab..6532ff7c07489 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm
@@ -8,8 +8,9 @@
new /obj/item/clothing/neck/cloak/qm(src)
new /obj/item/storage/lockbox/medal/cargo(src)
new /obj/item/clothing/under/rank/cargo/qm(src)
+ new /obj/item/clothing/under/rank/cargo/qm/skirt(src)
new /obj/item/clothing/shoes/sneakers/brown(src)
- new /obj/item/radio/headset/headset_cargo(src)
+ new /obj/item/radio/headset/headset_quartermaster(src)
new /obj/item/clothing/suit/fire/firefighter(src)
new /obj/item/clothing/gloves/fingerless(src)
new /obj/item/megaphone/cargo(src)
@@ -21,4 +22,4 @@
new /obj/item/circuitboard/machine/techfab/department/cargo(src)
new /obj/item/storage/photo_album/QM(src)
new /obj/item/circuitboard/machine/ore_silo(src)
- new /obj/item/card/id/departmental_budget/car(src)
\ No newline at end of file
+ new /obj/item/card/id/departmental_budget/car(src)
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm
index 1767fc42aedda..66a2fd8d141b7 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm
@@ -22,6 +22,7 @@
new /obj/item/areaeditor/blueprints(src)
new /obj/item/airlock_painter(src)
new /obj/item/holosign_creator/engineering(src)
+ new /obj/item/holosign_creator/atmos(src)
new /obj/item/clothing/mask/gas(src)
new /obj/item/multitool(src)
new /obj/item/assembly/flash/handheld(src)
@@ -33,6 +34,7 @@
new /obj/item/extinguisher/advanced(src)
new /obj/item/storage/photo_album/CE(src)
new /obj/item/card/id/departmental_budget/eng(src)
+ new /obj/item/storage/bag/construction(src)
/obj/structure/closet/secure_closet/engineering_electrical
name = "electrical supplies locker"
@@ -79,7 +81,6 @@
new /obj/item/clothing/glasses/meson/engine(src)
new /obj/item/storage/box/emptysandbags(src)
-
/obj/structure/closet/secure_closet/atmospherics
name = "\proper atmospheric technician's locker"
req_access = list(ACCESS_ATMOSPHERICS)
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/medical.dm b/code/game/objects/structures/crates_lockers/closets/secure/medical.dm
index e9f9900aa81a2..88a1b06f8614b 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/medical.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/medical.dm
@@ -86,6 +86,7 @@
new /obj/item/clothing/head/bio_hood/cmo(src)
new /obj/item/clothing/suit/toggle/labcoat/cmo(src)
new /obj/item/clothing/under/rank/medical/chief_medical_officer(src)
+ new /obj/item/clothing/under/rank/medical/chief_medical_officer/skirt(src)
new /obj/item/clothing/shoes/sneakers/brown (src)
new /obj/item/cartridge/cmo(src)
new /obj/item/radio/headset/heads/cmo(src)
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm b/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm
old mode 100755
new mode 100644
index 93e8a28bf64a3..49843419506b1
--- a/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm
@@ -11,9 +11,12 @@
new /obj/item/clothing/head/bio_hood/scientist(src)
new /obj/item/clothing/suit/toggle/labcoat/rd(src)
new /obj/item/clothing/under/rank/rnd/research_director(src)
+ new /obj/item/clothing/under/rank/rnd/research_director/skirt(src)
new /obj/item/clothing/under/rank/rnd/research_director/alt(src)
- new /obj/item/clothing/under/rank/rnd/research_director/hazard(src)
+ new /obj/item/clothing/under/rank/rnd/research_director/alt/skirt(src)
+ new /obj/item/clothing/under/rank/rnd/research_director/vest(src)
new /obj/item/clothing/under/rank/rnd/research_director/turtleneck(src)
+ new /obj/item/clothing/under/rank/rnd/research_director/turtleneck/skirt(src)
new /obj/item/clothing/shoes/sneakers/brown(src)
new /obj/item/cartridge/rd(src)
new /obj/item/clothing/gloves/color/latex(src)
@@ -28,4 +31,4 @@
new /obj/item/door_remote/research_director(src)
new /obj/item/circuitboard/machine/techfab/department/science(src)
new /obj/item/storage/photo_album/RD(src)
- new /obj/item/card/id/departmental_budget/sci(src)
+ new /obj/item/card/id/departmental_budget/sci(src)
\ No newline at end of file
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/secure_closets.dm b/code/game/objects/structures/crates_lockers/closets/secure/secure_closets.dm
index 57520f6f407bd..51e7d0f1896fb 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/secure_closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/secure_closets.dm
@@ -4,10 +4,10 @@
locked = TRUE
icon_state = "secure"
max_integrity = 250
- armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80)
+ armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80, "stamina" = 0)
secure = TRUE
/obj/structure/closet/secure_closet/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir)
if(damage_flag == "melee" && damage_amount < 20)
return 0
- . = ..()
\ No newline at end of file
+ . = ..()
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/security.dm b/code/game/objects/structures/crates_lockers/closets/secure/security.dm
index 83e95242e3943..2f7b277ac8708 100755
--- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm
@@ -33,7 +33,6 @@
new /obj/item/storage/belt/sabre(src)
new /obj/item/gun/energy/e_gun(src)
new /obj/item/door_remote/captain(src)
- new /obj/item/card/id/captains_spare(src)
new /obj/item/storage/photo_album/Captain(src)
new /obj/item/card/id/departmental_budget/civ(src)
@@ -47,6 +46,7 @@
new /obj/item/clothing/neck/cloak/hop(src)
new /obj/item/storage/lockbox/medal/service(src)
new /obj/item/clothing/under/rank/civilian/head_of_personnel(src)
+ new /obj/item/clothing/under/rank/civilian/head_of_personnel/skirt(src)
new /obj/item/clothing/head/hopcap(src)
new /obj/item/cartridge/hop(src)
new /obj/item/radio/headset/heads/hop(src)
@@ -68,7 +68,7 @@
/obj/structure/closet/secure_closet/brig_phys
name = "\proper brig physician's locker"
- req_access = list(ACCESS_BRIG)
+ req_access = list(ACCESS_BRIGPHYS)
icon_state = "brig_phys"
/obj/structure/closet/secure_closet/brig_phys/PopulateContents()
@@ -82,11 +82,9 @@
new /obj/item/storage/firstaid/o2(src)
new /obj/item/storage/firstaid/brute(src)
new /obj/item/storage/belt/medical(src)
- new /obj/item/storage/belt/security/deputy(src)
- new /obj/item/reagent_containers/spray/pepper(src)
- new /obj/item/assembly/flash/handheld(src)
- new /obj/item/restraints/handcuffs(src)
new /obj/item/clothing/gloves/color/latex/nitrile(src)
+ new /obj/item/clothing/under/rank/brig_phys(src)
+ new /obj/item/clothing/under/rank/brig_phys/skirt(src)
/obj/structure/closet/secure_closet/hos
name = "\proper head of security's locker"
@@ -105,14 +103,16 @@
new /obj/item/clothing/suit/armor/vest/leather(src)
new /obj/item/clothing/suit/armor/hos/trenchcoat(src)
new /obj/item/clothing/suit/armor/hos(src)
+ new /obj/item/clothing/under/rank/security/head_of_security(src)
+ new /obj/item/clothing/under/rank/security/head_of_security/skirt(src)
new /obj/item/clothing/under/rank/security/head_of_security/alt(src)
+ new /obj/item/clothing/under/rank/security/head_of_security/alt/skirt(src)
new /obj/item/clothing/head/HoS(src)
new /obj/item/clothing/glasses/hud/security/sunglasses/eyepatch(src)
new /obj/item/clothing/glasses/hud/security/sunglasses/gars/supergars(src)
new /obj/item/clothing/under/rank/security/head_of_security/white(src)
new /obj/item/storage/lockbox/medal/sec(src)
new /obj/item/megaphone/sec(src)
- new /obj/item/holosign_creator/security(src)
new /obj/item/storage/lockbox/loyalty(src)
new /obj/item/clothing/mask/gas/sechailer/swat(src)
new /obj/item/storage/box/flashbangs(src)
@@ -140,8 +140,9 @@
new /obj/item/clothing/head/beret/corpwarden(src)
new /obj/item/clothing/suit/armor/vest/warden/alt(src)
new /obj/item/clothing/under/rank/security/warden/formal(src)
+ new /obj/item/clothing/under/rank/security/warden(src)
+ new /obj/item/clothing/under/rank/security/warden/skirt(src)
new /obj/item/clothing/glasses/hud/security/sunglasses(src)
- new /obj/item/holosign_creator/security(src)
new /obj/item/clothing/mask/gas/sechailer(src)
new /obj/item/storage/box/zipties(src)
new /obj/item/storage/box/flashbangs(src)
@@ -213,29 +214,19 @@
/obj/structure/closet/secure_closet/detective/PopulateContents()
..()
- new /obj/item/clothing/under/rank/security/detective(src)
- new /obj/item/clothing/suit/det_suit(src)
- new /obj/item/clothing/head/fedora/det_hat(src)
- new /obj/item/clothing/gloves/color/black(src)
- new /obj/item/clothing/under/rank/security/detective/grey(src)
- new /obj/item/clothing/accessory/waistcoat(src)
- new /obj/item/clothing/suit/det_suit/grey(src)
- new /obj/item/clothing/suit/det_suit/noir(src)
- new /obj/item/clothing/head/fedora(src)
- new /obj/item/clothing/shoes/laceup(src)
new /obj/item/storage/box/evidence(src)
new /obj/item/radio/headset/headset_sec(src)
new /obj/item/detective_scanner(src)
new /obj/item/flashlight/seclite(src)
- new /obj/item/holosign_creator/security(src)
new /obj/item/reagent_containers/spray/pepper(src)
new /obj/item/clothing/suit/armor/vest/det_suit(src)
new /obj/item/clothing/accessory/holster/detective(src)
new /obj/item/pinpointer/crew(src)
- new /obj/item/twohanded/binoculars(src)
+ new /obj/item/binoculars(src)
new /obj/item/clothing/neck/tie/red(src)
new /obj/item/clothing/neck/tie/black(src)
new /obj/item/clothing/neck/tie/detective(src)
+ new /obj/item/storage/box/rxglasses/spyglasskit(src)
/obj/structure/closet/secure_closet/deputy
name = "deputy's locker"
@@ -280,7 +271,7 @@
..()
new /obj/item/clothing/shoes/sneakers/brown(src)
for(var/i in 1 to 3)
- new /obj/item/paper/fluff/jobs/security/court_judgement (src)
+ new /obj/item/paper/fluff/jobs/security/court_judgment (src)
new /obj/item/pen (src)
new /obj/item/clothing/suit/judgerobe (src)
new /obj/item/clothing/head/powdered_wig (src)
diff --git a/code/game/objects/structures/crates_lockers/closets/utility_closets.dm b/code/game/objects/structures/crates_lockers/closets/utility_closets.dm
index 1d19c551bf718..234dc483814df 100644
--- a/code/game/objects/structures/crates_lockers/closets/utility_closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets/utility_closets.dm
@@ -32,22 +32,26 @@
new /obj/item/tank/internals/emergency_oxygen(src)
new /obj/item/clothing/mask/breath(src)
new /obj/item/clothing/mask/breath(src)
+ for(var/i in 1 to rand(1,3))
+ new /obj/item/clothing/suit/space/hardsuit/skinsuit(src)
if ("aid")
new /obj/item/tank/internals/emergency_oxygen(src)
new /obj/item/storage/firstaid/o2(src)
new /obj/item/clothing/mask/breath(src)
+ for(var/i in 1 to rand(1,3))
+ new /obj/item/clothing/suit/space/hardsuit/skinsuit(src)
if ("tank")
new /obj/item/tank/internals/air(src)
new /obj/item/clothing/mask/breath(src)
+ for(var/i in 1 to rand(1,3))
+ new /obj/item/clothing/suit/space/hardsuit/skinsuit(src)
if ("both")
new /obj/item/tank/internals/emergency_oxygen(src)
new /obj/item/clothing/mask/breath(src)
-
- if ("nothing")
- // doot
+ new /obj/item/clothing/suit/space/hardsuit/skinsuit(src)
// teehee
if ("delete")
diff --git a/code/game/objects/structures/crates_lockers/closets/wardrobe.dm b/code/game/objects/structures/crates_lockers/closets/wardrobe.dm
index 596ba4b0028fd..76ae5fa9ef126 100644
--- a/code/game/objects/structures/crates_lockers/closets/wardrobe.dm
+++ b/code/game/objects/structures/crates_lockers/closets/wardrobe.dm
@@ -7,6 +7,8 @@
..()
for(var/i in 1 to 3)
new /obj/item/clothing/under/color/blue(src)
+ for(var/i in 1 to 3)
+ new /obj/item/clothing/under/skirt/color/blue(src)
for(var/i in 1 to 3)
new /obj/item/clothing/shoes/sneakers/brown(src)
return
@@ -18,6 +20,8 @@
/obj/structure/closet/wardrobe/pink/PopulateContents()
for(var/i in 1 to 3)
new /obj/item/clothing/under/color/pink(src)
+ for(var/i in 1 to 3)
+ new /obj/item/clothing/under/skirt/color/pink(src)
for(var/i in 1 to 3)
new /obj/item/clothing/shoes/sneakers/brown(src)
return
@@ -29,6 +33,8 @@
/obj/structure/closet/wardrobe/black/PopulateContents()
for(var/i in 1 to 3)
new /obj/item/clothing/under/color/black(src)
+ for(var/i in 1 to 3)
+ new /obj/item/clothing/under/skirt/color/black(src)
if(prob(25))
new /obj/item/clothing/suit/jacket/leather(src)
if(prob(20))
@@ -53,6 +59,8 @@
/obj/structure/closet/wardrobe/green/PopulateContents()
for(var/i in 1 to 3)
new /obj/item/clothing/under/color/green(src)
+ for(var/i in 1 to 3)
+ new /obj/item/clothing/under/skirt/color/green(src)
for(var/i in 1 to 3)
new /obj/item/clothing/shoes/sneakers/black(src)
new /obj/item/clothing/mask/bandana/green(src)
@@ -80,6 +88,8 @@
/obj/structure/closet/wardrobe/yellow/PopulateContents()
for(var/i in 1 to 3)
new /obj/item/clothing/under/color/yellow(src)
+ for(var/i in 1 to 3)
+ new /obj/item/clothing/under/skirt/color/yellow(src)
for(var/i in 1 to 3)
new /obj/item/clothing/shoes/sneakers/orange(src)
new /obj/item/clothing/mask/bandana/gold(src)
@@ -94,6 +104,8 @@
/obj/structure/closet/wardrobe/white/PopulateContents()
for(var/i in 1 to 3)
new /obj/item/clothing/under/color/white(src)
+ for(var/i in 1 to 3)
+ new /obj/item/clothing/under/skirt/color/white(src)
for(var/i in 1 to 3)
new /obj/item/clothing/shoes/sneakers/white(src)
for(var/i in 1 to 3)
@@ -121,6 +133,8 @@
/obj/structure/closet/wardrobe/grey/PopulateContents()
for(var/i in 1 to 3)
new /obj/item/clothing/under/color/grey(src)
+ for(var/i in 1 to 3)
+ new /obj/item/clothing/under/skirt/color/grey(src)
for(var/i in 1 to 3)
new /obj/item/clothing/shoes/sneakers/black(src)
for(var/i in 1 to 3)
@@ -152,16 +166,27 @@
if(prob(40))
new /obj/item/clothing/suit/jacket(src)
new /obj/item/clothing/under/color/white(src)
+ new /obj/item/clothing/under/skirt/color/white(src)
new /obj/item/clothing/under/color/blue(src)
+ new /obj/item/clothing/under/skirt/color/blue(src)
new /obj/item/clothing/under/color/yellow(src)
+ new /obj/item/clothing/under/skirt/color/yellow(src)
new /obj/item/clothing/under/color/green(src)
+ new /obj/item/clothing/under/skirt/color/green(src)
new /obj/item/clothing/under/color/orange(src)
+ new /obj/item/clothing/under/skirt/color/orange(src)
new /obj/item/clothing/under/color/pink(src)
+ new /obj/item/clothing/under/skirt/color/pink(src)
new /obj/item/clothing/under/color/red(src)
+ new /obj/item/clothing/under/skirt/color/red(src)
new /obj/item/clothing/under/color/darkblue(src)
+ new /obj/item/clothing/under/skirt/color/darkblue(src)
new /obj/item/clothing/under/color/teal(src)
+ new /obj/item/clothing/under/skirt/color/teal(src)
new /obj/item/clothing/under/color/lightpurple(src)
+ new /obj/item/clothing/under/skirt/color/lightpurple(src)
new /obj/item/clothing/under/color/green(src)
+ new /obj/item/clothing/under/skirt/color/green(src)
new /obj/item/clothing/mask/bandana/red(src)
new /obj/item/clothing/mask/bandana/red(src)
new /obj/item/clothing/mask/bandana/blue(src)
@@ -174,4 +199,6 @@
if(prob(30))
new /obj/item/clothing/suit/hooded/wintercoat(src)
new /obj/item/clothing/shoes/winterboots(src)
+ if(prob(40))
+ new /obj/item/clothing/suit/toggle/softshell(src)
return
diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm
index f9f7174dcaef5..fe98dae5dd70b 100644
--- a/code/game/objects/structures/crates_lockers/crates.dm
+++ b/code/game/objects/structures/crates_lockers/crates.dm
@@ -13,19 +13,25 @@
climb_time = 10 //real fast, because let's be honest stepping into or onto a crate is easy
climb_stun = 0 //climbing onto crates isn't hard, guys
delivery_icon = "deliverycrate"
- door_anim_time = 0 // no animation
+ door_anim_time = 3
+ door_anim_angle = 180
+ door_hinge = 3.5
open_sound = 'sound/machines/crate_open.ogg'
close_sound = 'sound/machines/crate_close.ogg'
open_sound_volume = 35
close_sound_volume = 50
drag_slowdown = 0
+ var/azimuth_angle_2 = 138 //in this context the azimuth angle for over 90 degree
var/obj/item/paper/fluff/jobs/cargo/manifest/manifest
+ var/radius_2 = 1.35
+ var/static/list/animation_math //assoc list with pre calculated values
/obj/structure/closet/crate/Initialize()
. = ..()
- if(icon_state == "[initial(icon_state)]open")
- opened = TRUE
- update_icon()
+ if(animation_math == null) //checks if there is already a list for animation_math if not creates one to avoid runtimes
+ animation_math = new/list()
+ if(!door_anim_time == 0 && !animation_math["[door_anim_time]-[door_anim_angle]-[azimuth_angle_2]-[radius_2]-[door_hinge]"])
+ animation_list()
/obj/structure/closet/crate/CanPass(atom/movable/mover, turf/target)
if(!istype(mover, /obj/structure/closet))
@@ -38,11 +44,70 @@
return !density
/obj/structure/closet/crate/update_icon()
- icon_state = "[initial(icon_state)][opened ? "open" : ""]"
-
cut_overlays()
- if(manifest)
- add_overlay("manifest")
+ if(!opened)
+ layer = OBJ_LAYER
+ if(!is_animating_door)
+ if(icon_door)
+ add_overlay("[icon_door]_door")
+ else
+ add_overlay("[icon_state]_door")
+ else
+ layer = BELOW_OBJ_LAYER
+ if(!is_animating_door)
+ if(icon_door_override)
+ add_overlay("[icon_door]_open")
+ else
+ add_overlay("[icon_state]_open")
+
+/obj/structure/closet/crate/animate_door(var/closing = FALSE)
+ if(!door_anim_time)
+ return
+ if(!door_obj) door_obj = new
+ vis_contents |= door_obj
+ door_obj.icon = icon
+ door_obj.icon_state = "[icon_door || icon_state]_door"
+ is_animating_door = TRUE
+ var/num_steps = door_anim_time / world.tick_lag
+ var/list/animation_math_list = animation_math["[door_anim_time]-[door_anim_angle]-[azimuth_angle_2]-[radius_2]-[door_hinge]"]
+ for(var/I in 0 to num_steps)
+ var/door_state = I == (closing ? num_steps : 0) ? "[icon_door || icon_state]_door" : animation_math_list[closing ? 2 * num_steps - I : num_steps + I] <= 0 ? "[icon_door_override ? icon_door : icon_state]_back" : "[icon_door || icon_state]_door"
+ var/door_layer = I == (closing ? num_steps : 0) ? ABOVE_MOB_LAYER : animation_math_list[closing ? 2 * num_steps - I : num_steps + I] <= 0 ? FLOAT_LAYER : ABOVE_MOB_LAYER
+ var/matrix/M = get_door_transform(I == (closing ? num_steps : 0) ? 0 : animation_math_list[closing ? num_steps - I : I], I == (closing ? num_steps : 0) ? 1 : animation_math_list[closing ? 2 * num_steps - I : num_steps + I])
+ if(I == 0)
+ door_obj.transform = M
+ door_obj.icon_state = door_state
+ door_obj.layer = door_layer
+ else if(I == 1)
+ animate(door_obj, transform = M, icon_state = door_state, layer = door_layer, time = world.tick_lag, flags = ANIMATION_END_NOW)
+ else
+ animate(transform = M, icon_state = door_state, layer = door_layer, time = world.tick_lag)
+ addtimer(CALLBACK(src,.proc/end_door_animation),door_anim_time,TIMER_UNIQUE|TIMER_OVERRIDE)
+
+/obj/structure/closet/crate/end_door_animation()
+ is_animating_door = FALSE
+ vis_contents -= door_obj
+ update_icon()
+ COMPILE_OVERLAYS(src)
+
+/obj/structure/closet/crate/get_door_transform(crateanim_1, crateanim_2)
+ var/matrix/M = matrix()
+ M.Translate(0, -door_hinge)
+ M.Multiply(matrix(1, crateanim_1, 0, 0, crateanim_2, 0))
+ M.Translate(0, door_hinge)
+ return M
+
+/obj/structure/closet/crate/proc/animation_list() //pre calculates a list of values for the crate animation cause byond not like math
+ var/num_steps_1 = door_anim_time / world.tick_lag
+ var/list/new_animation_math_sublist[num_steps_1 * 2]
+ for(var/I in 1 to num_steps_1) //loop to save the animation values into the lists
+ var/angle_1 = door_anim_angle * (I / num_steps_1)
+ var/polar_angle = abs(arcsin(cos(angle_1)))
+ var/azimuth_angle = angle_1 >= 90 ? azimuth_angle_2 : 0
+ var/radius_cr = angle_1 >= 90 ? radius_2 : 1
+ new_animation_math_sublist[I] = -sin(polar_angle) * sin(azimuth_angle) * radius_cr
+ new_animation_math_sublist[num_steps_1 + I] = cos(azimuth_angle) * sin(polar_angle) * radius_cr
+ animation_math["[door_anim_time]-[door_anim_angle]-[azimuth_angle_2]-[radius_2]-[door_hinge]"] = new_animation_math_sublist
/obj/structure/closet/crate/attack_hand(mob/user)
. = ..()
@@ -82,26 +147,34 @@
close_sound = 'sound/machines/wooden_closet_close.ogg'
open_sound_volume = 25
close_sound_volume = 50
+ door_anim_angle = 140
+ azimuth_angle_2 = 180
+ door_anim_time = 5
+ door_hinge = 5
/obj/structure/closet/crate/internals
desc = "An internals crate."
name = "internals crate"
- icon_state = "o2crate"
+ icon_state = "o2_crate"
/obj/structure/closet/crate/trashcart
desc = "A heavy, metal trashcart with wheels."
name = "trash cart"
icon_state = "trashcart"
+ door_anim_time = 0
/obj/structure/closet/crate/medical
desc = "A medical crate."
name = "medical crate"
- icon_state = "medicalcrate"
+ icon_state = "medical_crate"
/obj/structure/closet/crate/freezer
desc = "A freezer."
name = "freezer"
icon_state = "freezer"
+ door_hinge = 5
+ door_anim_angle = 165
+ azimuth_angle_2 = 145
//Snowflake organ freezer code
//Order is important, since we check source, we need to do the check whenever we have all the organs in the crate
@@ -116,7 +189,7 @@
/obj/structure/closet/crate/freezer/Destroy()
recursive_organ_check(src)
- ..()
+ return ..()
/obj/structure/closet/crate/freezer/Initialize()
..()
@@ -139,6 +212,7 @@
new /obj/item/reagent_containers/blood/OPlus(src)
new /obj/item/reagent_containers/blood/lizard(src)
new /obj/item/reagent_containers/blood/ethereal(src)
+ new /obj/item/reagent_containers/blood/oozeling(src)
for(var/i in 1 to 3)
new /obj/item/reagent_containers/blood/random(src)
@@ -160,12 +234,12 @@
/obj/structure/closet/crate/radiation
desc = "A crate with a radiation sign on it."
name = "radiation crate"
- icon_state = "radiation"
+ icon_state = "radiation_crate"
/obj/structure/closet/crate/hydroponics
name = "hydroponics crate"
desc = "All you need to destroy those pesky weeds and pests."
- icon_state = "hydrocrate"
+ icon_state = "hydro_crate"
/obj/structure/closet/crate/engineering
name = "engineering crate"
@@ -173,6 +247,7 @@
/obj/structure/closet/crate/engineering/electrical
icon_state = "engi_e_crate"
+ icon_door = "engi_crate"
/obj/structure/closet/crate/rcd
desc = "A crate for the storage of an RCD."
@@ -188,7 +263,7 @@
/obj/structure/closet/crate/science
name = "science crate"
desc = "A science crate."
- icon_state = "scicrate"
+ icon_state = "sci_crate"
/obj/structure/closet/crate/solarpanel_small
name = "budget solar panel crate"
diff --git a/code/game/objects/structures/crates_lockers/crates/bins.dm b/code/game/objects/structures/crates_lockers/crates/bins.dm
index 364d83b90dc41..16c7bd229300d 100644
--- a/code/game/objects/structures/crates_lockers/crates/bins.dm
+++ b/code/game/objects/structures/crates_lockers/crates/bins.dm
@@ -7,13 +7,17 @@
anchored = TRUE
horizontal = FALSE
delivery_icon = null
+ door_anim_time = 0
/obj/structure/closet/crate/bin/Initialize()
. = ..()
+ if(icon_state == "[initial(icon_state)]open")
+ opened = TRUE
update_icon()
/obj/structure/closet/crate/bin/update_icon()
- ..()
+ icon_state = "[initial(icon_state)][opened ? "open" : ""]"
+
cut_overlays()
if(contents.len == 0)
add_overlay("largebing")
@@ -37,6 +41,4 @@
/obj/structure/closet/crate/bin/proc/do_animate()
playsound(loc, open_sound, 15, 1, -3)
flick("animate_largebins", src)
- spawn(13)
- playsound(loc, close_sound, 15, 1, -3)
- update_icon()
+ addtimer(CALLBACK(src, .proc/close), 13)
diff --git a/code/game/objects/structures/crates_lockers/crates/critter.dm b/code/game/objects/structures/crates_lockers/crates/critter.dm
index 19d2ad460dba0..a114b8d94d648 100644
--- a/code/game/objects/structures/crates_lockers/crates/critter.dm
+++ b/code/game/objects/structures/crates_lockers/crates/critter.dm
@@ -1,7 +1,7 @@
/obj/structure/closet/crate/critter
name = "critter crate"
desc = "A crate designed for safe transport of animals. It has an oxygen tank for safe transport in space."
- icon_state = "crittercrate"
+ icon_state = "critter_crate"
horizontal = FALSE
allow_objects = FALSE
breakout_time = 600
@@ -13,6 +13,9 @@
open_sound_volume = 25
close_sound_volume = 50
var/obj/item/tank/internals/emergency_oxygen/tank
+ door_hinge = 5.5
+ door_anim_angle = 90
+ azimuth_angle_2 = 0.35
/obj/structure/closet/crate/critter/Initialize()
. = ..()
@@ -27,13 +30,41 @@
return ..()
/obj/structure/closet/crate/critter/update_icon()
- cut_overlays()
- if(opened)
- add_overlay("crittercrate_door_open")
- else
- add_overlay("crittercrate_door")
- if(manifest)
- add_overlay("manifest")
+ . = ..()
+
+/obj/structure/closet/crate/critter/animate_door(var/closing = FALSE)
+ if(!door_anim_time)
+ return
+ if(!door_obj) door_obj = new
+ vis_contents |= door_obj
+ door_obj.icon = icon
+ door_obj.icon_state = "[icon_door || icon_state]_door"
+ is_animating_door = TRUE
+ var/num_steps = door_anim_time / world.tick_lag
+ var/list/animation_math_list = animation_math["[door_anim_time]-[door_anim_angle]-[azimuth_angle_2]-[radius_2]-[door_hinge]"]
+ for(var/I in 0 to num_steps)
+ var/matrix/M = get_door_transform(I == (closing ? num_steps : 0) ? 1 : animation_math_list[closing ? num_steps - I : I], I == (closing ? num_steps : 0) ? 0 : animation_math_list[closing ? 2 * num_steps - I : num_steps + I])
+
+ if(I == 0)
+ door_obj.transform = M
+ else if(I == 1)
+ animate(door_obj, transform = M, time = world.tick_lag, flags = ANIMATION_END_NOW)
+ else
+ animate(transform = M, time = world.tick_lag)
+ addtimer(CALLBACK(src,.proc/end_door_animation),door_anim_time,TIMER_UNIQUE|TIMER_OVERRIDE)
+
+/obj/structure/closet/crate/critter/end_door_animation()
+ is_animating_door = FALSE
+ vis_contents -= door_obj
+ update_icon()
+ COMPILE_OVERLAYS(src)
+
+/obj/structure/closet/crate/critter/get_door_transform(crateanim_1, crateanim_2)
+ var/matrix/M = matrix()
+ M.Translate(-door_hinge, 0)
+ M.Multiply(matrix(crateanim_1, 0, 0, crateanim_2, 1, 0))
+ M.Translate(door_hinge, 0)
+ return M
/obj/structure/closet/crate/critter/return_air()
if(tank)
@@ -45,4 +76,13 @@
if(tank)
return tank.return_analyzable_air()
else
- return null
\ No newline at end of file
+ return null
+
+/obj/structure/closet/crate/critter/animation_list()
+ var/num_steps_1 = door_anim_time / world.tick_lag
+ var/list/new_animation_math_sublist[num_steps_1 * 2]
+ for(var/I in 1 to num_steps_1) //loop to save the animation values into the lists
+ var/angle_1 = door_anim_angle * (I / num_steps_1)
+ new_animation_math_sublist[I] = cos(angle_1)
+ new_animation_math_sublist[num_steps_1+I] = sin(angle_1) * azimuth_angle_2
+ animation_math["[door_anim_time]-[door_anim_angle]-[azimuth_angle_2]-[radius_2]-[door_hinge]"] = new_animation_math_sublist
diff --git a/code/game/objects/structures/crates_lockers/crates/large.dm b/code/game/objects/structures/crates_lockers/crates/large.dm
index 72909f43f181e..512cb6ed3d721 100644
--- a/code/game/objects/structures/crates_lockers/crates/large.dm
+++ b/code/game/objects/structures/crates_lockers/crates/large.dm
@@ -1,7 +1,7 @@
/obj/structure/closet/crate/large
name = "large crate"
desc = "A hefty wooden crate. You'll need a crowbar to get it open."
- icon_state = "largecrate"
+ icon_state = "large_crate"
density = TRUE
material_drop = /obj/item/stack/sheet/mineral/wood
material_drop_amount = 4
@@ -11,6 +11,7 @@
close_sound = 'sound/machines/wooden_closet_close.ogg'
open_sound_volume = 25
close_sound_volume = 50
+ door_anim_time = 0
/obj/structure/closet/crate/large/attack_hand(mob/user)
add_fingerprint(user)
@@ -44,4 +45,4 @@
else
to_chat(user, "You need a crowbar to pry this open!")
return FALSE //Just stop. Do nothing. Don't turn into an invisible sprite. Don't open like a locker.
- //The large crate has no non-attack interactions other than the crowbar, anyway.
\ No newline at end of file
+ //The large crate has no non-attack interactions other than the crowbar, anyway.
diff --git a/code/game/objects/structures/crates_lockers/crates/secure.dm b/code/game/objects/structures/crates_lockers/crates/secure.dm
index e5356b613ee36..08664dd08c521 100644
--- a/code/game/objects/structures/crates_lockers/crates/secure.dm
+++ b/code/game/objects/structures/crates_lockers/crates/secure.dm
@@ -1,12 +1,14 @@
/obj/structure/closet/crate/secure
desc = "A secure crate."
name = "secure crate"
- icon_state = "securecrate"
+ icon_state = "secure_crate"
secure = TRUE
locked = TRUE
max_integrity = 500
- armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80)
+ armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80, "stamina" = 0)
var/tamperproof = 0
+ icon_door = "crate"
+ icon_door_override = TRUE
/obj/structure/closet/crate/secure/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir)
if(damage_flag == "melee" && damage_amount < 25)
@@ -41,43 +43,55 @@
/obj/structure/closet/crate/secure/weapon
desc = "A secure weapons crate."
name = "weapons crate"
- icon_state = "weaponcrate"
+ icon_state = "weapon_crate"
+ icon_door = null
+ icon_door_override = FALSE
/obj/structure/closet/crate/secure/plasma
desc = "A secure plasma crate."
name = "plasma crate"
- icon_state = "plasmacrate"
+ icon_state = "plasma_crate"
+ icon_door = null
+ icon_door_override = FALSE
/obj/structure/closet/crate/secure/gear
desc = "A secure gear crate."
name = "gear crate"
- icon_state = "secgearcrate"
+ icon_state = "secgear_crate"
+ icon_door = null
+ icon_door_override = FALSE
/obj/structure/closet/crate/secure/hydroponics
desc = "A crate with a lock on it, painted in the scheme of the station's botanists."
name = "secure hydroponics crate"
- icon_state = "hydrosecurecrate"
+ icon_state = "hydro_secure_crate"
+ icon_door = null
+ icon_door_override = FALSE
/obj/structure/closet/crate/secure/engineering
desc = "A crate with a lock on it, painted in the scheme of the station's engineers."
name = "secure engineering crate"
icon_state = "engi_secure_crate"
+ icon_door = "engi_crate"
/obj/structure/closet/crate/secure/science
name = "secure science crate"
desc = "A crate with a lock on it, painted in the scheme of the station's scientists."
- icon_state = "scisecurecrate"
+ icon_state = "sci_secure_crate"
+ icon_door = "sci_crate"
/obj/structure/closet/crate/secure/owned
name = "private crate"
desc = "A crate cover designed to only open for who purchased its contents."
- icon_state = "privatecrate"
+ icon_state = "private_crate"
+ icon_door = null
+ icon_door_override = FALSE
var/datum/bank_account/buyer_account
var/privacy_lock = TRUE
/obj/structure/closet/crate/secure/owned/examine(mob/user)
. = ..()
- . += "It's locked with a privacy lock, and can only be unlocked by the buyer's ID."
+ . += "It's locked with a privacy lock, and can only be unlocked by the buyer's ID with required access."
/obj/structure/closet/crate/secure/owned/Initialize(mapload, datum/bank_account/_buyer_account)
. = ..()
@@ -90,13 +104,16 @@
if(id_card)
if(id_card.registered_account)
if(id_card.registered_account == buyer_account)
- if(iscarbon(user))
- add_fingerprint(user)
- locked = !locked
- user.visible_message("[user] unlocks [src]'s privacy lock.",
- "You unlock [src]'s privacy lock.")
- privacy_lock = FALSE
- update_icon()
+ if(allowed(user))
+ if(iscarbon(user))
+ add_fingerprint(user)
+ locked = !locked
+ user.visible_message("[user] unlocks [src]'s privacy lock.",
+ "You unlock [src]'s privacy lock.")
+ privacy_lock = FALSE
+ update_icon()
+ else if(!silent)
+ to_chat(user, "Access Denied, insufficient access on ID card.")
else if(!silent)
to_chat(user, "Bank account does not match with buyer!")
else if(!silent)
diff --git a/code/game/objects/structures/displaycase.dm b/code/game/objects/structures/displaycase.dm
index 5d136c4c99cad..9c908942ca223 100644
--- a/code/game/objects/structures/displaycase.dm
+++ b/code/game/objects/structures/displaycase.dm
@@ -1,22 +1,25 @@
/obj/structure/displaycase
name = "display case"
icon = 'icons/obj/stationobjs.dmi'
- icon_state = "glassbox0"
+ icon_state = "glassbox"
desc = "A display case for prized possessions."
density = TRUE
anchored = TRUE
resistance_flags = ACID_PROOF
- armor = list("melee" = 30, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 100)
+ armor = list("melee" = 30, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 100, "stamina" = 0)
max_integrity = 200
- integrity_failure = 50
+ integrity_failure = 25
var/obj/item/showpiece = null
+ var/obj/item/showpiece_type = null //This allows for showpieces that can only hold items if they're the same istype as this.
var/alert = TRUE
var/open = FALSE
var/openable = TRUE
+ var/custom_glass_overlay = FALSE ///If we have a custom glass overlay to use.
var/obj/item/electronics/airlock/electronics
var/start_showpiece_type = null //add type for items on display
var/list/start_showpieces = list() //Takes sublists in the form of list("type" = /obj/item/bikehorn, "trophy_message" = "henk")
var/trophy_message = ""
+ var/glass_fix = TRUE
/obj/structure/displaycase/Initialize()
. = ..()
@@ -30,11 +33,22 @@
showpiece = new start_showpiece_type (src)
update_icon()
+/obj/structure/displaycase/vv_edit_var(vname, vval)
+ . = ..()
+ if(vname in list(NAMEOF(src, open), NAMEOF(src, showpiece), NAMEOF(src, custom_glass_overlay)))
+ update_icon()
+
+/obj/structure/displaycase/handle_atom_del(atom/A)
+ if(A == electronics)
+ electronics = null
+ if(A == showpiece)
+ showpiece = null
+ update_icon()
+ return ..()
+
/obj/structure/displaycase/Destroy()
- if(electronics)
- QDEL_NULL(electronics)
- if(showpiece)
- QDEL_NULL(showpiece)
+ QDEL_NULL(electronics)
+ QDEL_NULL(showpiece)
return ..()
/obj/structure/displaycase/examine(mob/user)
@@ -42,61 +56,63 @@
if(alert)
. += "Hooked up with an anti-theft system."
if(showpiece)
- . += "There's [showpiece] inside."
+ . += "There's \a [showpiece] inside."
if(trophy_message)
. += "The plaque reads:\n [trophy_message]"
/obj/structure/displaycase/proc/dump()
- if (showpiece)
- showpiece.forceMove(loc)
- showpiece = null
+ if(QDELETED(showpiece))
+ return
+ showpiece.forceMove(drop_location())
+ showpiece = null
+ update_icon()
/obj/structure/displaycase/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0)
switch(damage_type)
if(BRUTE)
- playsound(src.loc, 'sound/effects/glasshit.ogg', 75, 1)
+ playsound(src, 'sound/effects/glasshit.ogg', 75, 1)
if(BURN)
- playsound(src.loc, 'sound/items/welder.ogg', 100, 1)
+ playsound(src, 'sound/items/welder.ogg', 100, 1)
/obj/structure/displaycase/deconstruct(disassembled = TRUE)
if(!(flags_1 & NODECONSTRUCT_1))
dump()
if(!disassembled)
- new /obj/item/shard( src.loc )
+ new /obj/item/shard(drop_location())
trigger_alarm()
qdel(src)
/obj/structure/displaycase/obj_break(damage_flag)
if(!broken && !(flags_1 & NODECONSTRUCT_1))
density = FALSE
- broken = 1
- new /obj/item/shard( src.loc )
- playsound(src, "shatter", 70, 1)
+ broken = TRUE
+ new /obj/item/shard(drop_location())
+ playsound(src, "shatter", 70, TRUE)
update_icon()
trigger_alarm()
+///Anti-theft alarm triggered when broken.
/obj/structure/displaycase/proc/trigger_alarm()
- //Activate Anti-theft
- if(alert)
- var/area/alarmed = get_area(src)
- alarmed.burglaralert(src)
- playsound(src, 'sound/effects/alert.ogg', 50, 1)
-
-/obj/structure/displaycase/update_icon()
- var/icon/I
- if(open)
- I = icon('icons/obj/stationobjs.dmi',"glassbox_open")
- else
- I = icon('icons/obj/stationobjs.dmi',"glassbox0")
- if(broken)
- I = icon('icons/obj/stationobjs.dmi',"glassboxb0")
+ if(!alert)
+ return
+ var/area/alarmed = get_area(src)
+ alarmed.burglaralert(src)
+ playsound(src, 'sound/effects/alert.ogg', 50, TRUE)
+
+/obj/structure/displaycase/update_overlays()
+ . = ..()
if(showpiece)
- var/icon/S = getFlatIcon(showpiece)
- S.Scale(17,17)
- I.Blend(S,ICON_UNDERLAY,8,8)
- src.icon = I
- return
+ var/mutable_appearance/showpiece_overlay = mutable_appearance(showpiece.icon, showpiece.icon_state)
+ showpiece_overlay.copy_overlays(showpiece)
+ showpiece_overlay.transform *= 0.6
+ . += showpiece_overlay
+ if(custom_glass_overlay)
+ return
+ if(broken)
+ . += "[initial(icon_state)]_broken"
+ else if(!open)
+ . += "[initial(icon_state)]_closed"
/obj/structure/displaycase/attackby(obj/item/W, mob/user, params)
if(W.GetID() && !broken && openable)
@@ -104,13 +120,13 @@
to_chat(user, "You [open ? "close":"open"] [src].")
toggle_lock(user)
else
- to_chat(user, "Access denied.")
+ to_chat(user, "Access denied.")
else if(W.tool_behaviour == TOOL_WELDER && user.a_intent == INTENT_HELP && !broken)
if(obj_integrity < max_integrity)
if(!W.tool_start_check(user, amount=5))
return
- to_chat(user, "You begin repairing [src].")
+ to_chat(user, "You begin repairing [src]...")
if(W.use_tool(src, user, 40, amount=5, volume=50))
obj_integrity = max_integrity
update_icon()
@@ -121,21 +137,24 @@
else if(!alert && W.tool_behaviour == TOOL_CROWBAR && openable) //Only applies to the lab cage and player made display cases
if(broken)
if(showpiece)
- to_chat(user, "Remove the displayed object first.")
+ to_chat(user, "Remove the displayed object first!")
else
- to_chat(user, "You remove the destroyed case")
+ to_chat(user, "You remove the destroyed case.")
qdel(src)
else
- to_chat(user, "You start to [open ? "close":"open"] [src].")
+ to_chat(user, "You start to [open ? "close":"open"] [src]...")
if(W.use_tool(src, user, 20))
to_chat(user, "You [open ? "close":"open"] [src].")
toggle_lock(user)
else if(open && !showpiece)
+ if(showpiece_type && !istype(W, showpiece_type))
+ to_chat(user, "This doesn't belong in this kind of display.")
+ return TRUE
if(user.transferItemToLoc(W, src))
showpiece = W
- to_chat(user, "You put [W] on display")
+ to_chat(user, "You put [W] on display.")
update_icon()
- else if(istype(W, /obj/item/stack/sheet/glass) && broken)
+ else if(glass_fix && broken && istype(W, /obj/item/stack/sheet/glass))
var/obj/item/stack/sheet/glass/G = W
if(G.get_amount() < 2)
to_chat(user, "You need two glass sheets to fix the case!")
@@ -143,7 +162,7 @@
to_chat(user, "You start fixing [src]...")
if(do_after(user, 20, target = src))
G.use(2)
- broken = 0
+ broken = FALSE
obj_integrity = max_integrity
update_icon()
else
@@ -165,13 +184,15 @@
to_chat(user, "You deactivate the hover field built into the case.")
log_combat(user, src, "deactivates the hover field of")
dump()
- src.add_fingerprint(user)
- update_icon()
+ add_fingerprint(user)
return
else
//prevents remote "kicks" with TK
if (!Adjacent(user))
return
+ if (user.a_intent == INTENT_HELP)
+ user.examinate(src)
+ return
user.visible_message("[user] kicks the display case.", null, null, COMBAT_MESSAGE_RANGE)
log_combat(user, src, "kicks")
user.do_attack_animation(src, ATTACK_EFFECT_KICK)
@@ -203,6 +224,21 @@
electronics = I
to_chat(user, "You install the airlock electronics.")
+ else if(istype(I, /obj/item/stock_parts/manipulator))
+ var/obj/item/stock_parts/manipulator/M = I
+ to_chat(user, "You start adding [M] to [src]...")
+ if(do_after(user, 20, target = src))
+ var/obj/structure/displaycase/forsale/sale = new(src.loc)
+ if(electronics)
+ electronics.forceMove(sale)
+ sale.electronics = electronics
+ if(electronics.one_access)
+ sale.req_one_access = electronics.accesses
+ else
+ sale.req_access = electronics.accesses
+ qdel(src)
+ qdel(M)
+
else if(istype(I, /obj/item/stack/sheet/glass))
var/obj/item/stack/sheet/glass/G = I
if(G.get_amount() < 10)
@@ -242,8 +278,6 @@
var/placer_key = ""
var/added_roundstart = TRUE
var/is_locked = TRUE
-
- alert = TRUE
integrity_failure = 0
openable = FALSE
@@ -255,45 +289,47 @@
GLOB.trophy_cases -= src
return ..()
-/obj/structure/displaycase/trophy/attackby(obj/item/W, mob/user, params)
+/obj/structure/displaycase/trophy/attackby(obj/item/W, mob/living/user, params)
if(!user.Adjacent(src)) //no TK museology
return
if(user.a_intent == INTENT_HARM)
return ..()
+ if(W.tool_behaviour == TOOL_WELDER && !broken)
+ return ..()
if(user.is_holding_item_of_type(/obj/item/key/displaycase))
if(added_roundstart)
is_locked = !is_locked
- to_chat(user, "You [!is_locked ? "un" : ""]lock the case.")
+ to_chat(user, "You [!is_locked ? "un" : ""]lock the case.")
else
- to_chat(user, "The lock is stuck shut!")
+ to_chat(user, "The lock is stuck shut!")
return
if(is_locked)
- to_chat(user, "The case is shut tight with an old fashioned physical lock. Maybe you should ask the curator for the key?")
+ to_chat(user, "The case is shut tight with an old-fashioned physical lock. Maybe you should ask the curator for the key?")
return
if(!added_roundstart)
- to_chat(user, "You've already put something new in this case.")
+ to_chat(user, "You've already put something new in this case!")
return
if(is_type_in_typecache(W, GLOB.blacklisted_cargo_types))
- to_chat(user, "The case rejects the [W].")
+ to_chat(user, "The case rejects the [W]!")
return
for(var/a in W.GetAllContents())
if(is_type_in_typecache(a, GLOB.blacklisted_cargo_types))
- to_chat(user, "The case rejects the [W].")
+ to_chat(user, "The case rejects the [W]!")
return
if(user.transferItemToLoc(W, src))
if(showpiece)
- to_chat(user, "You press a button, and [showpiece] descends into the floor of the case.")
+ to_chat(user, "You press a button, and [showpiece] descends into the floor of the case.")
QDEL_NULL(showpiece)
- to_chat(user, "You insert [W] into the case.")
+ to_chat(user, "You insert [W] into the case.")
showpiece = W
added_roundstart = FALSE
update_icon()
@@ -306,9 +342,9 @@
if(chosen_plaque)
if(user.Adjacent(src))
trophy_message = chosen_plaque
- to_chat(user, "You set the plaque's text.")
+ to_chat(user, "You set the plaque's text.")
else
- to_chat(user, "You are too far to set the plaque's text.")
+ to_chat(user, "You are too far to set the plaque's text!")
SSpersistence.SaveTrophy(src)
return TRUE
@@ -325,7 +361,7 @@
new /obj/effect/decal/cleanable/ash(loc)
QDEL_NULL(showpiece)
else
- ..()
+ return ..()
/obj/item/key/displaycase
name = "display case key"
@@ -340,3 +376,209 @@
name = initial(I.name)
icon = initial(I.icon)
icon_state = initial(I.icon_state)
+
+/obj/structure/displaycase/forsale
+ name = "vend-a-tray"
+ icon_state = "laserbox"
+ custom_glass_overlay = TRUE
+ desc = "A display case with an ID-card swiper. Use your ID to purchase the contents."
+ density = FALSE
+ max_integrity = 100
+ req_access = null
+ alert = FALSE //No, we're not calling the fire department because someone stole your cookie.
+ glass_fix = FALSE //Fixable with tools instead.
+ pass_flags = PASSTABLE ///Can be placed and moved onto a table.
+ ///The price of the item being sold. Altered by grab intent ID use.
+ var/sale_price = 20
+ ///The Account which will receive payment for purchases. Set by the first ID to swipe the tray.
+ var/datum/bank_account/payments_acc = null
+
+/obj/structure/displaycase/forsale/update_icon_state()
+ icon_state = "[initial(icon_state)][broken ? "_broken" : (open ? "_open" : (!showpiece ? "_empty" : null))]"
+ return ..()
+
+/obj/structure/displaycase/forsale/update_overlays()
+ . = ..()
+ if(!broken && !open)
+ . += "[initial(icon_state)]_overlay"
+
+/obj/structure/displaycase/forsale/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Vendatray", name)
+ ui.set_autoupdate(FALSE)
+ ui.open()
+
+/obj/structure/displaycase/forsale/ui_data(mob/user)
+ var/list/data = list()
+ var/register = FALSE
+ data["owner_name"] = null
+ if(payments_acc)
+ register = TRUE
+ data["owner_name"] = payments_acc.account_holder
+ data["product_name"] = null
+ data["product_icon"] = null
+ if(showpiece)
+ data["product_name"] = capitalize(showpiece.name)
+ var/base64 = icon2base64(icon(showpiece.icon, showpiece.icon_state))
+ data["product_icon"] = base64
+ data["registered"] = register
+ data["product_cost"] = sale_price
+ data["tray_open"] = open
+ return data
+
+/obj/structure/displaycase/forsale/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+ var/obj/item/card/id/potential_acc
+ if(isliving(usr))
+ var/mob/living/L = usr
+ potential_acc = L.get_idcard(hand_first = TRUE)
+ switch(action)
+ if("Buy")
+ if(!showpiece)
+ to_chat(usr, "There's nothing for sale.")
+ return
+ if(broken)
+ to_chat(usr, "[src] appears to be broken.")
+ return
+ if(!payments_acc)
+ to_chat(usr, "[src] hasn't been registered yet.")
+ return
+ if(!usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
+ return
+ if(!potential_acc)
+ to_chat(usr, "No ID card detected.")
+ return
+ var/datum/bank_account/account = potential_acc.registered_account
+ if(!account)
+ to_chat(usr, "[potential_acc] has no account registered!")
+ return
+ if(!account.has_money(sale_price))
+ to_chat(usr, "You do not possess the funds to purchase this.")
+ return
+ else
+ account.adjust_money(-sale_price)
+ if(payments_acc)
+ payments_acc.adjust_money(sale_price)
+ usr.put_in_hands(showpiece)
+ to_chat(usr, "You purchase [showpiece] for [sale_price] credits.")
+ playsound(src, 'sound/effects/cashregister.ogg', 40, TRUE)
+ flick("[initial(icon_state)]_vend", src)
+ showpiece = null
+ update_icon()
+ . = TRUE
+ if("Open")
+ if(!payments_acc)
+ to_chat(usr, "[src] hasn't been registered yet.")
+ return
+ if(!potential_acc || !potential_acc.registered_account)
+ return
+ if(!check_access(potential_acc))
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE)
+ return
+ toggle_lock()
+ . = TRUE
+ if("Register")
+ if(payments_acc)
+ return
+ if(!potential_acc || !potential_acc.registered_account)
+ return
+ if(!check_access(potential_acc))
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE)
+ return
+ payments_acc = potential_acc.registered_account
+ playsound(src, 'sound/machines/click.ogg', 20, TRUE)
+ . = TRUE
+ if("Adjust")
+ if(!check_access(potential_acc) || potential_acc.registered_account != payments_acc)
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE)
+ return
+
+ var/new_price_input = input(usr,"Set the sale price for this vend-a-tray.","new price",0) as num|null
+ if(isnull(new_price_input) || (payments_acc != potential_acc.registered_account))
+ to_chat(usr, "[src] rejects your new price.")
+ return
+ if(!usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) )
+ to_chat(usr, "You need to get closer!")
+ return
+ new_price_input = clamp(round(new_price_input, 1), 10, 1000)
+ sale_price = new_price_input
+ to_chat(usr, "The cost is now set to [sale_price].")
+ . = TRUE
+
+/obj/structure/displaycase/forsale/attackby(obj/item/I, mob/living/user, params)
+ if(isidcard(I))
+ //Card Registration
+ var/obj/item/card/id/potential_acc = I
+ if(!potential_acc.registered_account)
+ to_chat(user, "This ID card has no account registered!")
+ return
+ if(payments_acc == potential_acc.registered_account)
+ playsound(src, 'sound/machines/click.ogg', 20, TRUE)
+ toggle_lock()
+ return
+ if(istype(I, /obj/item/pda))
+ return TRUE
+ ui_update()
+ . = ..()
+
+
+/obj/structure/displaycase/forsale/multitool_act(mob/living/user, obj/item/I)
+ . = ..()
+ if(obj_integrity <= (integrity_failure * max_integrity))
+ to_chat(user, "You start recalibrating [src]'s hover field...")
+ if(do_after(user, 20, target = src))
+ broken = FALSE
+ obj_integrity = max_integrity
+ update_icon()
+ ui_update()
+ return TRUE
+
+/obj/structure/displaycase/forsale/wrench_act(mob/living/user, obj/item/I)
+ . = ..()
+ if(open && user.a_intent == INTENT_HELP )
+ if(anchored)
+ to_chat(user, "You start unsecuring [src]...")
+ else
+ to_chat(user, "You start securing [src]...")
+ if(I.use_tool(src, user, 16, volume=50))
+ if(QDELETED(I))
+ return
+ if(anchored)
+ to_chat(user, "You unsecure [src].")
+ else
+ to_chat(user, "You secure [src].")
+ anchored = !anchored
+ return TRUE
+ else if(!open && user.a_intent == INTENT_HELP)
+ to_chat(user, "[src] must be open to move it.")
+ return
+
+/obj/structure/displaycase/forsale/emag_act(mob/user)
+ . = ..()
+ payments_acc = null
+ req_access = list()
+ to_chat(user, "[src]'s card reader fizzles and smokes, and the account owner is reset.")
+ ui_update()
+
+/obj/structure/displaycase/forsale/examine(mob/user)
+ . = ..()
+ if(showpiece && !open)
+ . += "[showpiece] is for sale for [sale_price] credits."
+ if(broken)
+ . += "[src] is sparking and the hover field generator seems to be overloaded. Use a multitool to fix it."
+
+/obj/structure/displaycase/forsale/obj_break(damage_flag)
+ . = ..()
+ if(!broken && !(flags_1 & NODECONSTRUCT_1))
+ broken = TRUE
+ playsound(src, "shatter", 70, TRUE)
+ update_icon()
+ trigger_alarm() //In case it's given an alarm anyway.
+ ui_update()
+
+/obj/structure/displaycase/forsale/kitchen
+ desc = "A display case with an ID-card swiper. Use your ID to purchase the contents. Meant for the bartender and chef."
+ req_one_access = list(ACCESS_KITCHEN, ACCESS_BAR)
diff --git a/code/game/objects/structures/door_assembly.dm b/code/game/objects/structures/door_assembly.dm
index 2bbc951efb4fa..e4f9c5b4b3226 100644
--- a/code/game/objects/structures/door_assembly.dm
+++ b/code/game/objects/structures/door_assembly.dm
@@ -161,6 +161,31 @@
name = "near finished airlock assembly"
electronics = W
+ else if(istype(W, /obj/item/electroadaptive_pseudocircuit) && state == AIRLOCK_ASSEMBLY_NEEDS_ELECTRONICS )
+ var/obj/item/electroadaptive_pseudocircuit/EP = W
+ if(EP.adapt_circuit(user, 25))
+ var/obj/item/electronics/airlock/AE = new(src)
+ AE.accesses = EP.electronics.accesses
+ AE.one_access = EP.electronics.one_access
+ AE.unres_sides = EP.electronics.unres_sides
+ AE.play_tool_sound(src, 100)
+ user.visible_message("[user] installs the electronics into the airlock assembly.", \
+ "You start to install electronics into the airlock assembly...")
+ if(do_after(user, 40, target = src))
+ if( state != AIRLOCK_ASSEMBLY_NEEDS_ELECTRONICS )
+ qdel(AE)
+ return
+ if(!user.transferItemToLoc(AE, src))
+ qdel(AE)
+ return
+
+ to_chat(user, "You install the electroadaptive pseudocircuit.")
+ state = AIRLOCK_ASSEMBLY_NEEDS_SCREWDRIVER
+ name = "near finished airlock assembly"
+ electronics = AE
+ else
+ qdel(AE)
+
else if((W.tool_behaviour == TOOL_CROWBAR) && state == AIRLOCK_ASSEMBLY_NEEDS_SCREWDRIVER )
user.visible_message("[user] removes the electronics from the airlock assembly.", \
diff --git a/code/game/objects/structures/door_assembly_types.dm b/code/game/objects/structures/door_assembly_types.dm
index a5457d016e647..bf21f60df61f4 100644
--- a/code/game/objects/structures/door_assembly_types.dm
+++ b/code/game/objects/structures/door_assembly_types.dm
@@ -248,3 +248,15 @@
airlock_type = /obj/machinery/door/airlock/wood
mineral = "wood"
glass_type = /obj/machinery/door/airlock/wood/glass
+
+/obj/structure/door_assembly/door_assembly_bronze
+ name = "bronze airlock assembly"
+ icon = 'icons/obj/doors/airlocks/clockwork/pinion_airlock.dmi'
+ base_name = "bronze airlock"
+ airlock_type = /obj/machinery/door/airlock/bronze
+ noglass = TRUE
+ material_type = /obj/item/stack/tile/bronze
+
+/obj/structure/door_assembly/door_assembly_bronze/seethru
+ name = "bronze pinion airlock assembly"
+ airlock_type = /obj/machinery/door/airlock/bronze/seethru
diff --git a/code/game/objects/structures/extinguisher.dm b/code/game/objects/structures/extinguisher.dm
index b5e618cc1f5ed..517b8cbaeb856 100644
--- a/code/game/objects/structures/extinguisher.dm
+++ b/code/game/objects/structures/extinguisher.dm
@@ -7,6 +7,7 @@
density = FALSE
max_integrity = 200
integrity_failure = 50
+ layer = ABOVE_WINDOW_LAYER
var/obj/item/extinguisher/stored_extinguisher
var/opened = FALSE
@@ -33,7 +34,13 @@
/obj/structure/extinguisher_cabinet/contents_explosion(severity, target)
if(stored_extinguisher)
- stored_extinguisher.ex_act(severity, target)
+ switch(severity)
+ if(EXPLODE_DEVASTATE)
+ SSexplosions.high_mov_atom += stored_extinguisher
+ if(EXPLODE_HEAVY)
+ SSexplosions.med_mov_atom += stored_extinguisher
+ if(EXPLODE_LIGHT)
+ SSexplosions.low_mov_atom += stored_extinguisher
/obj/structure/extinguisher_cabinet/handle_atom_del(atom/A)
if(A == stored_extinguisher)
diff --git a/code/game/objects/structures/false_walls.dm b/code/game/objects/structures/false_walls.dm
index b11e7a5d128da..dfdd08b7b3384 100644
--- a/code/game/objects/structures/false_walls.dm
+++ b/code/game/objects/structures/false_walls.dm
@@ -189,7 +189,7 @@
if(world.time > last_event+15)
active = 1
radiation_pulse(src, 150)
- for(var/turf/closed/wall/mineral/uranium/T in orange(1,src))
+ for(var/turf/closed/wall/mineral/uranium/T in (RANGE_TURFS(1,src)-src))
T.radiate()
last_event = world.time
active = null
diff --git a/code/game/objects/structures/fireaxe.dm b/code/game/objects/structures/fireaxe.dm
index cbcc871d27408..9bec725ad2896 100644
--- a/code/game/objects/structures/fireaxe.dm
+++ b/code/game/objects/structures/fireaxe.dm
@@ -5,12 +5,13 @@
icon_state = "fireaxe"
anchored = TRUE
density = FALSE
- armor = list("melee" = 50, "bullet" = 20, "laser" = 0, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 50)
+ armor = list("melee" = 50, "bullet" = 20, "laser" = 0, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 50, "stamina" = 0)
max_integrity = 150
integrity_failure = 50
+ layer = ABOVE_WINDOW_LAYER
var/locked = TRUE
var/open = FALSE
- var/obj/item/twohanded/fireaxe/fireaxe
+ var/obj/item/fireaxe/fireaxe
/obj/structure/fireaxecabinet/Initialize()
. = ..()
@@ -49,9 +50,9 @@
obj_integrity = max_integrity
update_icon()
else if(open || broken)
- if(istype(I, /obj/item/twohanded/fireaxe) && !fireaxe)
- var/obj/item/twohanded/fireaxe/F = I
- if(F.wielded)
+ if(istype(I, /obj/item/fireaxe) && !fireaxe)
+ var/obj/item/fireaxe/F = I
+ if(F && ISWIELDED(F))
to_chat(user, "Unwield the [F.name] first.")
return
if(!user.transferItemToLoc(F, src))
diff --git a/code/game/objects/structures/fireplace.dm b/code/game/objects/structures/fireplace.dm
index 2735bd7e81ac1..5b85b27cf8405 100644
--- a/code/game/objects/structures/fireplace.dm
+++ b/code/game/objects/structures/fireplace.dm
@@ -102,7 +102,7 @@
if(2000 to MAXIMUM_BURN_TIMER)
set_light(6)
-/obj/structure/fireplace/process()
+/obj/structure/fireplace/process(delta_time)
if(!lit)
return
if(world.time > flame_expiry_timer)
@@ -111,7 +111,7 @@
playsound(src, 'sound/effects/comfyfire.ogg',50,0, 0, 1)
var/turf/T = get_turf(src)
- T.hotspot_expose(700, 5)
+ T.hotspot_expose(700, 2.5 * delta_time)
update_icon()
adjust_light()
diff --git a/code/game/objects/structures/flora.dm b/code/game/objects/structures/flora.dm
index e68173928ab44..c84f0a1b3109f 100644
--- a/code/game/objects/structures/flora.dm
+++ b/code/game/objects/structures/flora.dm
@@ -14,7 +14,7 @@
/obj/structure/flora/tree/attackby(obj/item/W, mob/user, params)
if(log_amount && (!(flags_1 & NODECONSTRUCT_1)))
- if(W.sharpness && W.force > 0)
+ if(W.is_sharp() && W.force > 0)
if(W.hitsound)
playsound(get_turf(src), W.hitsound, 100, 0, 0)
user.visible_message("[user] begins to cut down [src] with [W].","You begin to cut down [src] with [W].", "You hear the sound of sawing.")
@@ -301,7 +301,7 @@
icon_state = "fullgrass_[rand(1, 3)]"
. = ..()
-/obj/item/twohanded/required/kirbyplants
+/obj/item/kirbyplants
name = "potted plant"
icon = 'icons/obj/flora/plants.dmi'
icon_state = "plant-01"
@@ -310,13 +310,13 @@
w_class = WEIGHT_CLASS_HUGE
force = 10
attack_weight = 2
- force_wielded = 10
throwforce = 13
throw_speed = 2
throw_range = 4
-/obj/item/twohanded/required/kirbyplants/Initialize()
+/obj/item/kirbyplants/ComponentInitialize()
. = ..()
+ AddComponent(/datum/component/two_handed, require_twohands=TRUE, force_unwielded=10, force_wielded=10)
AddComponent(/datum/component/storage/concrete/kirbyplants)
/datum/component/storage/concrete/kirbyplants
@@ -324,7 +324,7 @@
max_w_class = WEIGHT_CLASS_NORMAL
insert_while_closed = FALSE // We don't want clicking plants with items to insert it, you have to alt click then click the slots
-/obj/item/twohanded/required/kirbyplants/equipped(mob/living/user)
+/obj/item/kirbyplants/equipped(mob/living/user)
var/image/I = image(icon = 'icons/obj/flora/plants.dmi' , icon_state = src.icon_state, loc = user)
I.copy_overlays(src)
I.override = 1
@@ -332,23 +332,23 @@
I.layer = ABOVE_MOB_LAYER
..()
-/obj/item/twohanded/required/kirbyplants/dropped(mob/living/user)
+/obj/item/kirbyplants/dropped(mob/living/user)
..()
user.remove_alt_appearance("sneaking_mission")
-/obj/item/twohanded/required/kirbyplants/random
+/obj/item/kirbyplants/random
icon = 'icons/obj/flora/_flora.dmi'
icon_state = "random_plant"
var/list/static/states
-/obj/item/twohanded/required/kirbyplants/random/Initialize()
+/obj/item/kirbyplants/random/Initialize()
. = ..()
icon = 'icons/obj/flora/plants.dmi'
if(!states)
generate_states()
icon_state = pick(states)
-/obj/item/twohanded/required/kirbyplants/random/proc/generate_states()
+/obj/item/kirbyplants/random/proc/generate_states()
states = list()
for(var/i in 1 to 34)
var/number
@@ -360,12 +360,12 @@
states += "applebush"
-/obj/item/twohanded/required/kirbyplants/dead
+/obj/item/kirbyplants/dead
name = "RD's potted plant"
desc = "A gift from the botanical staff, presented after the RD's reassignment. There's a tag on it that says \"Y'all come back now, y'hear?\"\nIt doesn't look very healthy..."
icon_state = "plant-25"
-/obj/item/twohanded/required/kirbyplants/photosynthetic
+/obj/item/kirbyplants/photosynthetic
name = "photosynthetic potted plant"
desc = "A bioluminescent plant."
icon_state = "plant-09"
@@ -390,6 +390,7 @@
/obj/structure/flora/rock/pile
icon_state = "lavarocks"
desc = "A pile of rocks."
+ density = FALSE
//Jungle grass
diff --git a/code/game/objects/structures/girders.dm b/code/game/objects/structures/girders.dm
index 4b1294e154a40..6751c3dab091e 100644
--- a/code/game/objects/structures/girders.dm
+++ b/code/game/objects/structures/girders.dm
@@ -31,12 +31,13 @@
add_fingerprint(user)
if(istype(W, /obj/item/gun/energy/plasmacutter))
- to_chat(user, "You start slicing apart the girder...")
+ balloon_alert(user, "You start slicing apart the girder")
if(W.use_tool(src, user, 40, volume=100))
- to_chat(user, "You slice apart the girder.")
+ balloon_alert(user, "Girder sliced apart")
var/obj/item/stack/sheet/iron/M = new (loc, 2)
M.add_fingerprint(user)
qdel(src)
+ return
else if(istype(W, /obj/item/pickaxe/drill/jackhammer))
to_chat(user, "You smash through the girder!")
@@ -47,13 +48,13 @@
else if(istype(W, /obj/item/stack))
if(iswallturf(loc))
- to_chat(user, "There is already a wall present!")
+ balloon_alert(user, "Wall already present")
return
if(!isfloorturf(src.loc))
- to_chat(user, "A floor must be present to build a false wall!")
+ balloon_alert(user, "Floor is missing")
return
if (locate(/obj/structure/falsewall) in src.loc.contents)
- to_chat(user, "There is already a false wall present!")
+ balloon_alert(user, "There already is a false wall")
return
if(istype(W, /obj/item/stack/rods))
@@ -62,25 +63,26 @@
if(S.get_amount() < 2)
to_chat(user, "You need at least two rods to create a false wall!")
return
- to_chat(user, "You start building a reinforced false wall...")
+ balloon_alert(user, "You start building a reinforced false wall")
if(do_after(user, 20, target = src))
if(S.get_amount() < 2)
return
S.use(2)
- to_chat(user, "You create a false wall. Push on it to open or close the passage.")
+ balloon_alert(user, "False wall created")
var/obj/structure/falsewall/iron/FW = new (loc)
transfer_fingerprints_to(FW)
qdel(src)
+ return
else
if(S.get_amount() < 5)
to_chat(user, "You need at least five rods to add plating!")
return
- to_chat(user, "You start adding plating...")
+ balloon_alert(user, "You start adding plating")
if(do_after(user, 40, target = src))
if(S.get_amount() < 5)
return
S.use(5)
- to_chat(user, "You add the plating.")
+ balloon_alert(user, "Plating added")
var/turf/T = get_turf(src)
T.PlaceOnTop(/turf/closed/wall/mineral/iron)
transfer_fingerprints_to(T)
@@ -96,25 +98,26 @@
if(S.get_amount() < 2)
to_chat(user, "You need two sheets of iron to create a false wall!")
return
- to_chat(user, "You start building a false wall...")
+ balloon_alert(user, "You start building false wall")
if(do_after(user, 20, target = src))
if(S.get_amount() < 2)
return
S.use(2)
- to_chat(user, "You create a false wall. Push on it to open or close the passage.")
+ balloon_alert(user, "False wall created")
var/obj/structure/falsewall/F = new (loc)
transfer_fingerprints_to(F)
qdel(src)
+ return
else
if(S.get_amount() < 2)
to_chat(user, "You need two sheets of iron to finish a wall!")
return
- to_chat(user, "You start adding plating...")
+ balloon_alert(user, "You start adding plating")
if (do_after(user, 40, target = src))
if(S.get_amount() < 2)
return
S.use(2)
- to_chat(user, "You add the plating.")
+ balloon_alert(user, "Plating alert")
var/turf/T = get_turf(src)
T.PlaceOnTop(/turf/closed/wall)
transfer_fingerprints_to(T)
@@ -126,25 +129,26 @@
if(S.get_amount() < 2)
to_chat(user, "You need at least two sheets to create a false wall!")
return
- to_chat(user, "You start building a reinforced false wall...")
+ balloon_alert(user, "You start building reinforced false wall")
if(do_after(user, 20, target = src))
if(S.get_amount() < 2)
return
S.use(2)
- to_chat(user, "You create a reinforced false wall. Push on it to open or close the passage.")
+ balloon_alert(user, "Reinforced false wall created")
var/obj/structure/falsewall/reinforced/FW = new (loc)
transfer_fingerprints_to(FW)
qdel(src)
+ return
else
if(state == GIRDER_REINF)
if(S.get_amount() < 1)
return
- to_chat(user, "You start finalizing the reinforced wall...")
+ balloon_alert(user, "You start finilizing reinforced wall")
if(do_after(user, 50, target = src))
if(S.get_amount() < 1)
return
S.use(1)
- to_chat(user, "You fully reinforce the wall.")
+ balloon_alert(user, "Wall fully reinforced")
var/turf/T = get_turf(src)
T.PlaceOnTop(/turf/closed/wall/r_wall)
transfer_fingerprints_to(T)
@@ -153,12 +157,12 @@
else
if(S.get_amount() < 1)
return
- to_chat(user, "You start reinforcing the girder...")
+ balloon_alert(user, "You start reinforcing girder")
if(do_after(user, 60, target = src))
if(S.get_amount() < 1)
return
S.use(1)
- to_chat(user, "You reinforce the girder.")
+ balloon_alert(user, "Girder reinforced")
var/obj/structure/girder/reinforced/R = new (loc)
transfer_fingerprints_to(R)
qdel(src)
@@ -174,21 +178,22 @@
if(S.get_amount() < 2)
return
S.use(2)
- to_chat(user, "You create a false wall. Push on it to open or close the passage.")
+ balloon_alert(user, "False wall created")
var/F = text2path("/obj/structure/falsewall/[M]")
var/obj/structure/FW = new F (loc)
transfer_fingerprints_to(FW)
qdel(src)
+ return
else
if(S.get_amount() < 2)
to_chat(user, "You need at least two sheets to add plating!")
return
- to_chat(user, "You start adding plating...")
+ balloon_alert(user, "You start adding plating")
if (do_after(user, 40, target = src))
if(S.get_amount() < 2)
return
S.use(2)
- to_chat(user, "You add the plating.")
+ balloon_alert(user, "Plating added")
var/turf/T = get_turf(src)
T.PlaceOnTop(text2path("/turf/closed/wall/mineral/[M]"))
transfer_fingerprints_to(T)
@@ -202,7 +207,7 @@
if (P.pipe_type in list(0, 1, 5)) //simple pipes, simple bends, and simple manifolds.
if(!user.transferItemToLoc(P, drop_location()))
return
- to_chat(user, "You fit the pipe into \the [src].")
+ balloon_alert(user, "You fit the pipe into [src]")
else
return ..()
@@ -220,27 +225,27 @@
if(state != GIRDER_DISPLACED)
return
state = GIRDER_DISASSEMBLED
- to_chat(user, "You disassemble the girder.")
+ balloon_alert(user, "Girder disassembled")
var/obj/item/stack/sheet/iron/M = new (loc, 2)
M.add_fingerprint(user)
qdel(src)
return TRUE
else if(state == GIRDER_REINF)
- to_chat(user, "You start unsecuring support struts...")
+ balloon_alert(user, "You start unsecuring support struts")
if(tool.use_tool(src, user, 40, volume=100))
if(state != GIRDER_REINF)
return
- to_chat(user, "You unsecure the support struts.")
+ balloon_alert(user, "Support struts unsecured")
state = GIRDER_REINF_STRUTS
return TRUE
else if(state == GIRDER_REINF_STRUTS)
- to_chat(user, "You start securing support struts...")
+ balloon_alert(user, "You start securing support struts")
if(tool.use_tool(src, user, 40, volume=100))
if(state != GIRDER_REINF_STRUTS)
return
- to_chat(user, "You secure the support struts.")
+ balloon_alert(user, "Support struts secured")
state = GIRDER_REINF
return TRUE
@@ -248,9 +253,9 @@
/obj/structure/girder/wirecutter_act(mob/user, obj/item/tool)
. = FALSE
if(state == GIRDER_REINF_STRUTS)
- to_chat(user, "You start removing the inner grille...")
+ balloon_alert(user, "You start removing the inner grille")
if(tool.use_tool(src, user, 40, volume=100))
- to_chat(user, "You remove the inner grille.")
+ balloon_alert(user, "Inner grille removed")
new /obj/item/stack/sheet/plasteel(get_turf(src))
var/obj/structure/girder/G = new (loc)
transfer_fingerprints_to(G)
@@ -261,19 +266,19 @@
. = FALSE
if(state == GIRDER_DISPLACED)
if(!isfloorturf(loc))
- to_chat(user, "A floor must be present to secure the girder!")
+ balloon_alert(user, "Floor is missing")
- to_chat(user, "You start securing the girder...")
+ balloon_alert(user, "You start securing girder")
if(tool.use_tool(src, user, 40, volume=100))
- to_chat(user, "You secure the girder.")
+ balloon_alert(user, "Girder secured")
var/obj/structure/girder/G = new (loc)
transfer_fingerprints_to(G)
qdel(src)
return TRUE
else if(state == GIRDER_NORMAL && can_displace)
- to_chat(user, "You start unsecuring the girder...")
+ balloon_alert(user, "You start unsecuring girder")
if(tool.use_tool(src, user, 40, volume=100))
- to_chat(user, "You unsecure the girder.")
+ balloon_alert(user, "Girder unsecured")
var/obj/structure/girder/displaced/D = new (loc)
transfer_fingerprints_to(D)
qdel(src)
@@ -288,11 +293,10 @@
else
return 0
-/obj/structure/girder/CanAStarPass(ID, dir, caller)
+/obj/structure/girder/CanAStarPass(obj/item/card/id/ID, to_dir, atom/movable/caller)
. = !density
- if(ismovableatom(caller))
- var/atom/movable/mover = caller
- . = . || (mover.pass_flags & PASSGRILLE)
+ if(istype(caller))
+ . = . || (caller.pass_flags & PASSGRILLE)
/obj/structure/girder/deconstruct(disassembled = TRUE)
if(!(flags_1 & NODECONSTRUCT_1))
@@ -341,9 +345,9 @@
if(!W.tool_start_check(user, amount=0))
return
- to_chat(user, "You start slicing apart the girder...")
+ balloon_alert(user, "You start slicing apart the girder")
if(W.use_tool(src, user, 40, volume=50))
- to_chat(user, "You slice apart the girder.")
+ balloon_alert(user, "Girder sliced apart")
var/obj/item/stack/sheet/runed_metal/R = new(drop_location(), 1)
transfer_fingerprints_to(R)
qdel(src)
@@ -393,12 +397,12 @@
var/turf/T = get_turf(src)
switch(passed_mode)
if(RCD_FLOORWALL)
- to_chat(user, "You finish a wall.")
+ balloon_alert(user, "Wall finished")
T.PlaceOnTop(/turf/closed/wall)
qdel(src)
return TRUE
if(RCD_DECONSTRUCT)
- to_chat(user, "You deconstruct the girder.")
+ balloon_alert(user, "Girder deconstructed")
qdel(src)
return TRUE
return FALSE
@@ -415,9 +419,9 @@
if(W.tool_behaviour == TOOL_WELDER)
if(!W.tool_start_check(user, amount = 0))
return
- to_chat(user, "You start slicing apart [src]...")
+ balloon_alert(user, "You start slicing apart [src]")
if(W.use_tool(src, user, 40, volume=50))
- to_chat(user, "You slice apart [src].")
+ balloon_alert(user, "[src] sliced apart")
var/obj/item/stack/tile/bronze/B = new(drop_location(), 2)
transfer_fingerprints_to(B)
qdel(src)
@@ -433,7 +437,7 @@
var/obj/item/stack/tile/bronze/B = W
if(B.get_amount() < 2)
to_chat(user, "You need at least two bronze sheets to build a bronze wall!")
- return 0
+ return FALSE
user.visible_message("[user] begins plating [src] with bronze...", "You begin constructing a bronze wall...")
if(do_after(user, 50, target = src))
if(B.get_amount() < 2)
diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm
index 9c956e91e45d8..6dff64e594b5d 100644
--- a/code/game/objects/structures/grille.dm
+++ b/code/game/objects/structures/grille.dm
@@ -8,7 +8,7 @@
flags_1 = CONDUCT_1
pressure_resistance = 5*ONE_ATMOSPHERE
layer = BELOW_OBJ_LAYER
- armor = list("melee" = 50, "bullet" = 70, "laser" = 70, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 0, "acid" = 0)
+ armor = list("melee" = 50, "bullet" = 70, "laser" = 70, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 0, "acid" = 0, "stamina" = 0)
max_integrity = 50
integrity_failure = 20
var/rods_type = /obj/item/stack/rods
@@ -84,6 +84,8 @@
return
var/mob/M = AM
shock(M, 70)
+ if(prob(50))
+ take_damage(1, BRUTE, "melee", FALSE)
/obj/structure/grille/attack_animal(mob/user)
. = ..()
@@ -132,11 +134,10 @@
else
return !density
-/obj/structure/grille/CanAStarPass(ID, dir, caller)
+/obj/structure/grille/CanAStarPass(obj/item/card/id/ID, to_dir, atom/movable/caller)
. = !density
- if(ismovableatom(caller))
- var/atom/movable/mover = caller
- . = . || (mover.pass_flags & PASSGRILLE)
+ if(istype(caller))
+ . = . || (caller.pass_flags & PASSGRILLE)
/obj/structure/grille/attackby(obj/item/W, mob/user, params)
user.changeNext_move(CLICK_CD_MELEE)
diff --git a/code/game/objects/structures/guillotine.dm b/code/game/objects/structures/guillotine.dm
index e41715ddd06b0..4e793fa25ffb6 100644
--- a/code/game/objects/structures/guillotine.dm
+++ b/code/game/objects/structures/guillotine.dm
@@ -132,11 +132,9 @@
// The crowd is pleased
// The delay is to making large crowds have a longer laster applause
var/delay_offset = 0
- for(var/mob/M in viewers(src, 7))
- var/mob/living/carbon/human/C = M
- if (ishuman(M))
- addtimer(CALLBACK(C, /mob/.proc/emote, "clap"), delay_offset * 0.3)
- delay_offset++
+ for(var/mob/living/carbon/human/C in viewers(7, src))
+ addtimer(CALLBACK(C, /mob/.proc/emote, "clap"), delay_offset * 0.3)
+ delay_offset++
else
H.apply_damage(15 * blade_sharpness, BRUTE, head)
log_combat(user, H, "dropped the blade on", src, " non-fatally")
@@ -176,7 +174,7 @@
else
return ..()
-/obj/structure/guillotine/buckle_mob(mob/living/M, force = FALSE, check_loc = TRUE)
+/obj/structure/guillotine/buckle_mob(mob/living/M, mob/user, check_loc = TRUE)
if (!anchored)
to_chat(usr, "The [src] needs to be wrenched to the floor!")
return FALSE
@@ -189,7 +187,7 @@
to_chat(usr, "You need to raise the blade before buckling someone in!")
return FALSE
- return ..(M, force, FALSE)
+ return ..(M, user, FALSE)
/obj/structure/guillotine/post_buckle_mob(mob/living/M)
if (!istype(M, /mob/living/carbon/human))
diff --git a/code/game/objects/structures/guncase.dm b/code/game/objects/structures/guncase.dm
index 73bbe5087a11c..75d229e1ac167 100644
--- a/code/game/objects/structures/guncase.dm
+++ b/code/game/objects/structures/guncase.dm
@@ -95,9 +95,14 @@
update_icon()
/obj/structure/guncase/contents_explosion(severity, target)
- for(var/atom/A in contents)
- A.ex_act(severity++, target)
- CHECK_TICK
+ for(var/thing in contents)
+ switch(severity)
+ if(EXPLODE_DEVASTATE)
+ SSexplosions.high_mov_atom += thing
+ if(EXPLODE_HEAVY)
+ SSexplosions.med_mov_atom += thing
+ if(EXPLODE_LIGHT)
+ SSexplosions.low_mov_atom += thing
/obj/structure/guncase/shotgun
name = "shotgun locker"
diff --git a/code/game/objects/structures/headpike.dm b/code/game/objects/structures/headpike.dm
index c7a3940336258..6217c306919a5 100644
--- a/code/game/objects/structures/headpike.dm
+++ b/code/game/objects/structures/headpike.dm
@@ -6,7 +6,7 @@
density = FALSE
anchored = TRUE
var/bonespear = FALSE
- var/obj/item/twohanded/spear/spear
+ var/obj/item/spear/spear
var/obj/item/bodypart/head/victim
/obj/structure/headpike/bone //for bone spears
@@ -19,9 +19,9 @@
name = "[victim.name] on a spear"
update_icon()
if(bonespear)
- spear = locate(/obj/item/twohanded/bonespear) in parts_list
+ spear = locate(/obj/item/spear/bonespear) in parts_list
else
- spear = locate(/obj/item/twohanded/spear) in parts_list
+ spear = locate(/obj/item/spear) in parts_list
/obj/structure/headpike/Initialize()
. = ..()
diff --git a/code/game/objects/structures/holosign.dm b/code/game/objects/structures/holosign.dm
index f6979f5880834..77dcd60b70155 100644
--- a/code/game/objects/structures/holosign.dm
+++ b/code/game/objects/structures/holosign.dm
@@ -6,10 +6,13 @@
icon = 'icons/effects/effects.dmi'
anchored = TRUE
max_integrity = 1
- armor = list("melee" = 0, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 20)
+ armor = list("melee" = 0, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 20, "stamina" = 0)
layer = BELOW_OBJ_LAYER
var/obj/item/holosign_creator/projector
+/obj/structure/holosign/emp_act(severity)
+ take_damage(max_integrity/severity, BRUTE, "melee", 1)
+
/obj/structure/holosign/New(loc, source_projector)
if(source_projector)
projector = source_projector
@@ -96,8 +99,20 @@
/obj/structure/holosign/barrier/atmos/Initialize()
. = ..()
+ var/turf/local = get_turf(loc)
+ ADD_TRAIT(local, TRAIT_FIREDOOR_STOP, TRAIT_GENERIC)
air_update_turf(TRUE)
+/obj/structure/holosign/barrier/atmos/Destroy()
+ var/turf/local = get_turf(loc)
+ REMOVE_TRAIT(local, TRAIT_FIREDOOR_STOP, TRAIT_GENERIC)
+ return ..()
+
+/obj/structure/holosign/barrier/atmos/Move(atom/newloc, direct)
+ var/turf/local = get_turf(loc)
+ REMOVE_TRAIT(local, TRAIT_FIREDOOR_STOP, TRAIT_GENERIC)
+ return ..()
+
/obj/structure/holosign/barrier/cyborg
name = "Energy Field"
desc = "A fragile energy field that blocks movement. Excels at blocking lethal projectiles."
@@ -132,7 +147,7 @@
if(ishuman(mover))
var/mob/living/carbon/human/sickboi = mover
var/threat = sickboi.check_virus()
- if(get_disease_severity_value(threat) > get_disease_severity_value(DISEASE_SEVERITY_MINOR))
+ if(get_disease_danger_value(threat) > get_disease_danger_value(DISEASE_MINOR))
if(buzzcd < world.time)
playsound(get_turf(src),'sound/machines/buzz-sigh.ogg',65,1,4)
buzzcd = (world.time + 60)
diff --git a/code/game/objects/structures/janicart.dm b/code/game/objects/structures/janicart.dm
index 6aa9e499ab5ec..c98f1fad3e4ca 100644
--- a/code/game/objects/structures/janicart.dm
+++ b/code/game/objects/structures/janicart.dm
@@ -9,7 +9,7 @@
var/amount_per_transfer_from_this = 5 //shit I dunno, adding this so syringes stop runtime erroring. --NeoFite
var/obj/item/storage/bag/trash/mybag
var/obj/item/mop/mymop
- var/obj/item/twohanded/pushbroom/mybroom
+ var/obj/item/pushbroom/mybroom
var/obj/item/reagent_containers/spray/cleaner/myspray
var/obj/item/lightreplacer/myreplacer
var/signs = 0
@@ -51,9 +51,9 @@
m.janicart_insert(user, src)
else
to_chat(user, fail_msg)
- else if(istype(I, /obj/item/twohanded/pushbroom))
+ else if(istype(I, /obj/item/pushbroom))
if(!mybroom)
- var/obj/item/twohanded/pushbroom/b=I
+ var/obj/item/pushbroom/b=I
b.janicart_insert(user,src)
else
to_chat(user, fail_msg)
diff --git a/code/game/objects/structures/kitchen_spike.dm b/code/game/objects/structures/kitchen_spike.dm
index 9d2c673ffdabc..527c64d19017c 100644
--- a/code/game/objects/structures/kitchen_spike.dm
+++ b/code/game/objects/structures/kitchen_spike.dm
@@ -91,7 +91,7 @@
-/obj/structure/kitchenspike/user_buckle_mob(mob/living/M, mob/living/user) //Don't want them getting put on the rack other than by spiking
+/obj/structure/kitchenspike/user_buckle_mob(mob/living/M, mob/user, check_loc = TRUE) //Don't want them getting put on the rack other than by spiking
return
/obj/structure/kitchenspike/user_unbuckle_mob(mob/living/buckled_mob, mob/living/carbon/human/user)
diff --git a/code/game/objects/structures/lattice.dm b/code/game/objects/structures/lattice.dm
index 8a50bc6f033e2..6ecb920fb8986 100644
--- a/code/game/objects/structures/lattice.dm
+++ b/code/game/objects/structures/lattice.dm
@@ -5,7 +5,7 @@
icon_state = "lattice"
density = FALSE
anchored = TRUE
- armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50)
+ armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50, "stamina" = 0)
max_integrity = 50
layer = LATTICE_LAYER //under pipes
plane = FLOOR_PLANE
diff --git a/code/game/objects/structures/lavaland/geyser.dm b/code/game/objects/structures/lavaland/geyser.dm
index 6d525570bc238..d89bad89d5bf6 100644
--- a/code/game/objects/structures/lavaland/geyser.dm
+++ b/code/game/objects/structures/lavaland/geyser.dm
@@ -35,7 +35,7 @@
to_chat(user, "The [P.name] isn't strong enough!")
return
if(activated)
- to_chat(user, "The [name] is already active!")
+ to_chat(user, "The [name] is already active!")
return
to_chat(user, "You start vigorously plunging [src]!")
diff --git a/code/game/objects/structures/lavaland/necropolis_tendril.dm b/code/game/objects/structures/lavaland/necropolis_tendril.dm
index 0b710efb5f42e..5cd581fedae36 100644
--- a/code/game/objects/structures/lavaland/necropolis_tendril.dm
+++ b/code/game/objects/structures/lavaland/necropolis_tendril.dm
@@ -30,16 +30,14 @@ GLOBAL_LIST_INIT(tendrils, list())
/obj/structure/spawner/lavaland/Initialize()
. = ..()
emitted_light = new(loc)
- for(var/F in RANGE_TURFS(1, src))
- if(ismineralturf(F))
- var/turf/closed/mineral/M = F
- M.ScrapeAway(null, CHANGETURF_IGNORE_AIR)
+ for(var/turf/closed/mineral/M in RANGE_TURFS(1, src))
+ M.ScrapeAway(null, CHANGETURF_IGNORE_AIR)
AddComponent(/datum/component/gps, "Eerie Signal")
GLOB.tendrils += src
var/datum/disease/advance/random/necropolis/R = new
necroseed += R
-/obj/structure/spawner/lavaland/extrapolator_act(mob/user, var/obj/item/extrapolator/E, scan = TRUE)
+/obj/structure/spawner/lavaland/extrapolator_act(mob/user, obj/item/extrapolator/E, scan = TRUE)
if(!necroseed.len)
return FALSE
if(scan)
@@ -61,7 +59,7 @@ GLOBAL_LIST_INIT(tendrils, list())
if(last_tendril && !(flags_1 & ADMIN_SPAWNED_1))
if(SSmedals.hub_enabled)
- for(var/mob/living/L in view(7,src))
+ for(var/mob/living/L in hearers(7,src))
if(L.stat || !L.client)
continue
SSmedals.UnlockMedal("[BOSS_MEDAL_TENDRIL] [ALL_KILL_MEDAL]", L.client)
@@ -103,7 +101,7 @@ GLOBAL_LIST_INIT(tendrils, list())
shake_camera(M, 15, 1)
playsound(get_turf(src),'sound/effects/explosionfar.ogg', 200, 1)
visible_message("The tendril falls inward, the ground around it widening into a yawning chasm!")
- for(var/turf/T in range(2,src))
+ for(var/turf/T as() in RANGE_TURFS(2,src))
if(!T.density)
T.TerraformTurf(/turf/open/chasm/lavaland, /turf/open/chasm/lavaland, flags = CHANGETURF_INHERIT_AIR)
qdel(src)
diff --git a/code/game/objects/structures/loom.dm b/code/game/objects/structures/loom.dm
index de7a7f76269eb..28ff5a8de732f 100644
--- a/code/game/objects/structures/loom.dm
+++ b/code/game/objects/structures/loom.dm
@@ -25,17 +25,17 @@
if(!istype(W))
return FALSE
if(!anchored)
- user.show_message("The loom needs to be wrenched down.", 1)
+ user.show_message("The loom needs to be wrenched down.", MSG_VISUAL)
return FALSE
if(W.amount < FABRIC_PER_SHEET)
- user.show_message("You need at least [FABRIC_PER_SHEET] units of fabric before using this.", 1)
+ user.show_message("You need at least [FABRIC_PER_SHEET] units of fabric before using this.", MSG_VISUAL)
return FALSE
- user.show_message("You start weaving \the [W.name] through the loom..", 1)
+ user.show_message("You start weaving \the [W.name] through the loom..", MSG_VISUAL)
if(W.use_tool(src, user, W.pull_effort))
if(W.amount >= FABRIC_PER_SHEET)
new W.loom_result(drop_location())
W.use(FABRIC_PER_SHEET)
- user.show_message("You weave \the [W.name] into a workable fabric.", 1)
+ user.show_message("You weave \the [W.name] into a workable fabric.", MSG_VISUAL)
return TRUE
#undef FABRIC_PER_SHEET
diff --git a/code/game/objects/structures/manned_turret.dm b/code/game/objects/structures/manned_turret.dm
index 3f02bf8808f3c..f6a2a8f3861dc 100644
--- a/code/game/objects/structures/manned_turret.dm
+++ b/code/game/objects/structures/manned_turret.dm
@@ -42,8 +42,9 @@
anchored = FALSE
. = ..()
STOP_PROCESSING(SSfastprocess, src)
+ LAZYREMOVE(buckled_mob, src)
-/obj/machinery/manned_turret/user_buckle_mob(mob/living/M, mob/living/carbon/user)
+/obj/machinery/manned_turret/user_buckle_mob(mob/living/M, mob/living/carbon/user, check_loc = TRUE)
if(user.incapacitated() || !istype(user))
return
M.forceMove(get_turf(src))
@@ -72,7 +73,7 @@
if (!update_positioning())
return PROCESS_KILL
-/obj/machinery/manned_turret/proc/update_positioning()
+/obj/machinery/manned_turret/proc/update_positioning(mouseObject, params)
if (!LAZYLEN(buckled_mobs))
return FALSE
var/mob/living/controller = buckled_mobs[1]
@@ -80,11 +81,11 @@
return FALSE
var/client/C = controller.client
if(C)
- var/atom/A = C.mouseObject
+ var/atom/A = mouseObject
var/turf/T = get_turf(A)
if(istype(T)) //They're hovering over something in the map.
direction_track(controller, T)
- calculated_projectile_vars = calculate_projectile_angle_and_pixel_offsets(controller, C.mouseParams)
+ calculated_projectile_vars = calculate_projectile_angle_and_pixel_offsets(controller, params)
/obj/machinery/manned_turret/proc/direction_track(mob/user, atom/targeted)
if(user.incapacitated())
@@ -147,7 +148,7 @@
/obj/machinery/manned_turret/proc/fire_helper(mob/user)
if(user.incapacitated() || !(user in buckled_mobs))
return
- update_positioning() //REFRESH MOUSE TRACKING!!
+ update_positioning()
var/turf/targets_from = get_turf(src)
if(QDELETED(target))
target = target_turf
diff --git a/code/game/objects/structures/mineral_doors.dm b/code/game/objects/structures/mineral_doors.dm
index 3af35ea1de73a..a24bccf86ee34 100644
--- a/code/game/objects/structures/mineral_doors.dm
+++ b/code/game/objects/structures/mineral_doors.dm
@@ -10,7 +10,7 @@
icon = 'icons/obj/doors/mineral_doors.dmi'
icon_state = "metal"
max_integrity = 200
- armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 50, "acid" = 50)
+ armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 50, "acid" = 50, "stamina" = 0)
CanAtmosPass = ATMOS_PASS_DENSITY
rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
rad_insulation = RAD_MEDIUM_INSULATION
diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm
index c4fa5b4a6b46e..2b26b8c571e98 100644
--- a/code/game/objects/structures/mirror.dm
+++ b/code/game/objects/structures/mirror.dm
@@ -8,6 +8,8 @@
anchored = TRUE
max_integrity = 200
integrity_failure = 100
+ flags_ricochet = RICOCHET_SHINY
+ layer = ABOVE_WINDOW_LAYER
/obj/structure/mirror/Initialize(mapload)
. = ..()
@@ -243,3 +245,25 @@
/obj/structure/mirror/magic/proc/curse(mob/living/user)
return
+
+
+//basically stolen from human_defense.dm
+/obj/structure/mirror/bullet_act(obj/item/projectile/P)
+ if(P.reflectable & REFLECT_NORMAL)
+ if(P.starting)
+ var/new_x = P.starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2)
+ var/new_y = P.starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2)
+ var/turf/curloc = get_turf(src)
+
+ // redirect the projectile
+ P.original = locate(new_x, new_y, P.z)
+ P.starting = curloc
+ P.firer = src
+ P.yo = new_y - curloc.y
+ P.xo = new_x - curloc.x
+ var/new_angle_s = P.Angle + 180
+ while(new_angle_s > 180) // Translate to regular projectile degrees
+ new_angle_s -= 360
+ P.setAngle(new_angle_s)
+
+ return BULLET_ACT_FORCE_PIERCE // complete projectile permutation
diff --git a/code/game/objects/structures/morgue.dm b/code/game/objects/structures/morgue.dm
index 8bdbbb111209c..82140e15beb1c 100644
--- a/code/game/objects/structures/morgue.dm
+++ b/code/game/objects/structures/morgue.dm
@@ -101,7 +101,8 @@ GLOBAL_LIST_EMPTY(bodycontainers) //Let them act as spawnpoints for revenants an
return ..()
/obj/structure/bodycontainer/deconstruct(disassembled = TRUE)
- new /obj/item/stack/sheet/iron (loc, 5)
+ if (!(flags_1 & NODECONSTRUCT_1))
+ new /obj/item/stack/sheet/iron (loc, 5)
recursive_organ_check(src)
qdel(src)
@@ -126,7 +127,8 @@ GLOBAL_LIST_EMPTY(bodycontainers) //Let them act as spawnpoints for revenants an
playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1)
playsound(src, 'sound/effects/roll.ogg', 5, 1)
var/turf/T = get_step(src, dir)
- connected.setDir(dir)
+ if(connected)
+ connected.setDir(dir)
for(var/atom/movable/AM in src)
AM.forceMove(T)
update_icon()
@@ -144,7 +146,7 @@ GLOBAL_LIST_EMPTY(bodycontainers) //Let them act as spawnpoints for revenants an
/obj/structure/bodycontainer/get_remote_view_fullscreens(mob/user)
if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS)))
- user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 2)
+ user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/impaired, 2)
/*
* Morgue
*/
@@ -167,7 +169,6 @@ GLOBAL_LIST_EMPTY(bodycontainers) //Let them act as spawnpoints for revenants an
. += "The speaker is [beeper ? "enabled" : "disabled"]. Alt-click to toggle it."
/obj/structure/bodycontainer/morgue/AltClick(mob/user)
- ..()
if(!user.canUseTopic(src, !issilicon(user)))
return
beeper = !beeper
@@ -260,6 +261,11 @@ GLOBAL_LIST_EMPTY(crematoriums)
locked = TRUE
update_icon()
+ for(var/obj/O in conts)
+ if(O.resistance_flags & INDESTRUCTIBLE)
+ O.forceMove(src) // in case an item in container should be spared
+ conts -= O
+
for(var/mob/living/M in conts)
if (M.stat != DEAD)
M.emote("scream")
@@ -267,7 +273,6 @@ GLOBAL_LIST_EMPTY(crematoriums)
log_combat(user, M, "cremated")
else
M.log_message("was cremated", LOG_ATTACK)
-
M.death(1)
if(M) //some animals get automatically deleted on death.
M.ghostize()
@@ -343,6 +348,17 @@ GLOBAL_LIST_EMPTY(crematoriums)
else
to_chat(user, "That's not connected to anything!")
+/obj/structure/tray/attackby(obj/P, mob/user, params)
+ if(!istype(P, /obj/item/riding_offhand))
+ return ..()
+
+ var/obj/item/riding_offhand/riding_item = P
+ var/mob/living/carried_mob = riding_item.rider
+ if(carried_mob == user) //Piggyback user.
+ return
+ user.unbuckle_mob(carried_mob)
+ MouseDrop_T(carried_mob, user)
+
/obj/structure/tray/MouseDrop_T(atom/movable/O as mob|obj, mob/user)
if(!ismovableatom(O) || O.anchored || !Adjacent(user) || !user.Adjacent(O) || O.loc == user)
return
diff --git a/code/game/objects/structures/musician.dm b/code/game/objects/structures/musician.dm
deleted file mode 100644
index 23b8da75575c3..0000000000000
--- a/code/game/objects/structures/musician.dm
+++ /dev/null
@@ -1,386 +0,0 @@
-
-#define MUSICIAN_HEARCHECK_MINDELAY 4
-#define MUSIC_MAXLINES 300
-#define MUSIC_MAXLINECHARS 50
-
-/datum/song
- var/name = "Untitled"
- var/list/lines = new()
- var/tempo = 5 // delay between notes
-
- var/playing = 0 // if we're playing
- var/help = 0 // if help is open
- var/edit = 1 // if we're in editing mode
- var/repeat = 0 // number of times remaining to repeat
- var/max_repeats = 10 // maximum times we can repeat
-
- var/instrumentDir = "piano" // the folder with the sounds
- var/instrumentExt = "ogg" // the file extension
- var/obj/instrumentObj = null // the associated obj playing the sound
- var/last_hearcheck = 0
- var/list/hearing_mobs
-
-/datum/song/New(dir, obj, ext = "ogg")
- tempo = sanitize_tempo(tempo)
- instrumentDir = dir
- instrumentObj = obj
- instrumentExt = ext
-
-/datum/song/Destroy()
- instrumentObj = null
- return ..()
-
-// note is a number from 1-7 for A-G
-// acc is either "b", "n", or "#"
-// oct is 1-8 (or 9 for C)
-/datum/song/proc/playnote(mob/user, note, acc as text, oct)
- // handle accidental -> B<>C of E<>F
- if(acc == "b" && (note == 3 || note == 6)) // C or F
- if(note == 3)
- oct--
- note--
- acc = "n"
- else if(acc == "#" && (note == 2 || note == 5)) // B or E
- if(note == 2)
- oct++
- note++
- acc = "n"
- else if(acc == "#" && (note == 7)) //G#
- note = 1
- acc = "b"
- else if(acc == "#") // mass convert all sharps to flats, octave jump already handled
- acc = "b"
- note++
-
- // check octave, C is allowed to go to 9
- if(oct < 1 || (note == 3 ? oct > 9 : oct > 8))
- return
-
- // now generate name
- var/soundfile = "sound/instruments/[instrumentDir]/[ascii2text(note+64)][acc][oct].[instrumentExt]"
- soundfile = file(soundfile)
- // make sure the note exists
- if(!fexists(soundfile))
- return
- // and play
- var/turf/source = get_turf(instrumentObj)
- if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
- LAZYCLEARLIST(hearing_mobs)
- for(var/mob/M in get_hearers_in_view(15, source))
- LAZYADD(hearing_mobs, M)
- last_hearcheck = world.time
-
- var/sound/music_played = sound(soundfile)
- for(var/i in hearing_mobs)
- var/mob/M = i
- if(HAS_TRAIT(user, TRAIT_MUSICIAN) && isliving(M))
- var/mob/living/L = M
- L.apply_status_effect(STATUS_EFFECT_GOOD_MUSIC)
- if(!M.client || !(M.client.prefs.toggles & SOUND_INSTRUMENTS))
- continue
- M.playsound_local(source, null, 100, falloff = 5, S = music_played)
-
-/datum/song/proc/updateDialog(mob/user)
- instrumentObj.updateDialog() // assumes it's an object in world, override if otherwise
-
-/datum/song/proc/shouldStopPlaying(mob/user)
- if(instrumentObj)
- if(!user.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK))
- return TRUE
- return !instrumentObj.anchored // add special cases to stop in subclasses
- else
- return TRUE
-
-/datum/song/proc/playsong(mob/user)
- while(repeat >= 0)
- var/cur_oct[7]
- var/cur_acc[7]
- for(var/i = 1 to 7)
- cur_oct[i] = 3
- cur_acc[i] = "n"
-
- for(var/line in lines)
- for(var/beat in splittext(lowertext(line), ","))
- var/list/notes = splittext(beat, "/")
- for(var/note in splittext(notes[1], "-"))
- if(!playing || shouldStopPlaying(user))//If the instrument is playing, or special case
- playing = FALSE
- hearing_mobs = null
- return
- if(!length(note))
- continue
- var/cur_note = text2ascii(note) - 96
- if(cur_note < 1 || cur_note > 7)
- continue
- var/notelen = length(note)
- var/ni = ""
- for(var/i = length(note[1]) + 1, i <= notelen, i += length(ni))
- ni = note[i]
- if(!text2num(ni))
- if(ni == "#" || ni == "b" || ni == "n")
- cur_acc[cur_note] = ni
- else if(ni == "s")
- cur_acc[cur_note] = "#" // so shift is never required
- else
- cur_oct[cur_note] = text2num(ni)
- if(user.dizziness > 0 && prob(user.dizziness / 2))
- cur_note = CLAMP(cur_note + rand(round(-user.dizziness / 10), round(user.dizziness / 10)), 1, 7)
- if(user.dizziness > 0 && prob(user.dizziness / 5))
- if(prob(30))
- cur_acc[cur_note] = "#"
- else if(prob(42))
- cur_acc[cur_note] = "b"
- else if(prob(75))
- cur_acc[cur_note] = "n"
- playnote(user, cur_note, cur_acc[cur_note], cur_oct[cur_note])
- if(notes.len >= 2 && text2num(notes[2]))
- sleep(sanitize_tempo(tempo / text2num(notes[2])))
- else
- sleep(tempo)
- repeat--
- hearing_mobs = null
- playing = FALSE
- repeat = 0
- updateDialog(user)
-
-/datum/song/proc/interact(mob/user)
- var/dat = ""
-
- if(lines.len > 0)
- dat += "
"
- var/linecount = 0
- for(var/line in lines)
- linecount += 1
- dat += "Line [linecount]: EditX [line] "
- dat += "Add Line
"
- if(help)
- dat += "Hide Help "
- dat += {"
- Lines are a series of chords, separated by commas (,), each with notes separated by hyphens (-).
- Every note in a chord will play together, with chord timed by the tempo.
-
- Notes are played by the names of the note, and optionally, the accidental, and/or the octave number.
- By default, every note is natural and in octave 3. Defining otherwise is remembered for each note.
- Example: C,D,E,F,G,A,B will play a C major scale.
- After a note has an accidental placed, it will be remembered: C,C4,C,C3 is C3,C4,C4,C3
- Chords can be played simply by seperating each note with a hyphon: A-C#,Cn-E,E-G#,Gn-B
- A pause may be denoted by an empty chord: C,E,,C,G
- To make a chord be a different time, end it with /x, where the chord length will be length
- defined by tempo / x: C,G/2,E/4
- Combined, an example is: E-E4/4,F#/2,G#/8,B/8,E3-E4/4
-
- Lines may be up to [MUSIC_MAXLINECHARS] characters.
- A song may only contain up to [MUSIC_MAXLINES] lines.
- "}
- else
- dat += "Show Help "
-
- var/datum/browser/popup = new(user, "instrument", instrumentObj.name, 700, 500)
- popup.set_content(dat)
- popup.open()
-
-/datum/song/proc/ParseSong(text)
- set waitfor = FALSE
- //split into lines
- lines = splittext(text, "\n")
- if(lines.len)
- var/bpm_string = "BPM: "
- if(findtext(lines[1], bpm_string, 1, length(bpm_string) + 1))
- tempo = sanitize_tempo(600 / text2num(copytext(lines[1], length(bpm_string) + 1)))
- lines.Cut(1, 2)
- else
- tempo = sanitize_tempo(5) // default 120 BPM
- if(lines.len > MUSIC_MAXLINES)
- to_chat(usr, "Too many lines!")
- lines.Cut(MUSIC_MAXLINES + 1)
- var/linenum = 1
- for(var/l in lines)
- if(length_char(l) > MUSIC_MAXLINECHARS)
- to_chat(usr, "Line [linenum] too long!")
- lines.Remove(l)
- else
- linenum++
- updateDialog(usr) // make sure updates when complete
-
-/datum/song/Topic(href, href_list)
- if(!usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK))
- usr << browse(null, "window=instrument")
- usr.unset_machine()
- return
-
- instrumentObj.add_fingerprint(usr)
-
- if(href_list["newsong"])
- lines = new()
- tempo = sanitize_tempo(5) // default 120 BPM
- name = ""
-
- else if(href_list["import"])
- var/t = ""
- do
- t = stripped_multiline_input(usr, "Please paste the entire song, formatted:", text("[]", name), t, MUSIC_MAXLINES*MUSIC_MAXLINECHARS)
- if(!usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK))
- return
-
- if(length(t) >= MUSIC_MAXLINES * MUSIC_MAXLINECHARS)
- var/cont = input(usr, "Your message is too long! Would you like to continue editing it?", "", "yes") in list("yes", "no")
- if(!usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK))
- return
- if(cont == "no")
- break
- while(length(t) > MUSIC_MAXLINES * MUSIC_MAXLINECHARS)
- ParseSong(t)
-
- else if(href_list["help"])
- help = text2num(href_list["help"]) - 1
-
- else if(href_list["edit"])
- edit = text2num(href_list["edit"]) - 1
-
- if(href_list["repeat"]) //Changing this from a toggle to a number of repeats to avoid infinite loops.
- if(playing)
- return //So that people cant keep adding to repeat. If the do it intentionally, it could result in the server crashing.
- repeat += round(text2num(href_list["repeat"]))
- if(repeat < 0)
- repeat = 0
- if(repeat > max_repeats)
- repeat = max_repeats
-
- else if(href_list["tempo"])
- tempo = sanitize_tempo(tempo + text2num(href_list["tempo"]))
-
- else if(href_list["play"])
- playing = TRUE
- spawn()
- playsong(usr)
-
- else if(href_list["newline"])
- var/newline = stripped_input(usr, "Enter your line: ", instrumentObj.name, max_length=MUSIC_MAXLINECHARS)
- if(!newline || !usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK))
- return
- if(lines.len > MUSIC_MAXLINES)
- return
- if(length_char(newline) > MUSIC_MAXLINECHARS)
- newline = copytext_char(newline, 1, MUSIC_MAXLINECHARS)
- lines.Add(newline)
-
- else if(href_list["deleteline"])
- var/num = round(text2num(href_list["deleteline"]))
- if(num > lines.len || num < 1)
- return
- lines.Cut(num, num+1)
-
- else if(href_list["modifyline"])
- var/num = round(text2num(href_list["modifyline"]),1)
- var/content = stripped_input(usr, "Enter your line: ", instrumentObj.name, lines[num], max_length=MUSIC_MAXLINECHARS)
- if(!content || !usr.canUseTopic(instrumentObj, BE_CLOSE, FALSE, NO_TK))
- return
- if(num > lines.len || num < 1)
- return
- lines[num] = content
-
- else if(href_list["stop"])
- playing = FALSE
- hearing_mobs = null
-
- updateDialog(usr)
- return
-
-/datum/song/proc/sanitize_tempo(new_tempo)
- new_tempo = abs(new_tempo)
- return max(round(new_tempo, world.tick_lag), world.tick_lag)
-
-// subclass for handheld instruments, like violin
-/datum/song/handheld
-
-/datum/song/handheld/updateDialog(mob/user)
- instrumentObj.interact(user)
-
-/datum/song/handheld/shouldStopPlaying()
- if(instrumentObj)
- return !isliving(instrumentObj.loc)
- else
- return TRUE
-
-
-//////////////////////////////////////////////////////////////////////////
-
-
-/obj/structure/piano
- name = "space minimoog"
- icon = 'icons/obj/musician.dmi'
- icon_state = "minimoog"
- anchored = TRUE
- density = TRUE
- var/datum/song/song
-
-/obj/structure/piano/unanchored
- anchored = FALSE
-
-/obj/structure/piano/Initialize()
- . = ..()
- song = new("piano", src)
-
- if(prob(50) && icon_state == initial(icon_state))
- name = "space minimoog"
- desc = "This is a minimoog, like a space piano, but more spacey!"
- icon_state = "minimoog"
- else
- name = "space piano"
- desc = "This is a space piano, like a regular piano, but always in tune! Even if the musician isn't."
- icon_state = "piano"
-
-/obj/structure/piano/Destroy()
- qdel(song)
- song = null
- return ..()
-
-/obj/structure/piano/Initialize(mapload)
- . = ..()
- if(mapload)
- song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded
-
-/obj/structure/piano/attack_hand(mob/user)
- . = ..()
- if(.)
- return
- interact(user)
-
-/obj/structure/piano/attack_paw(mob/user)
- return attack_hand(user)
-
-/obj/structure/piano/interact(mob/user)
- ui_interact(user)
-
-/obj/structure/piano/ui_interact(mob/user)
- if(!user || !anchored)
- return
-
- if(!user.IsAdvancedToolUser())
- to_chat(user, "You don't have the dexterity to do this!")
- return 1
- user.set_machine(src)
- song.interact(user)
-
-/obj/structure/piano/wrench_act(mob/living/user, obj/item/I)
- default_unfasten_wrench(user, I, 40)
- return TRUE
diff --git a/code/game/objects/structures/noticeboard.dm b/code/game/objects/structures/noticeboard.dm
index 7e8bf6819a0d4..e64dc19f77d69 100644
--- a/code/game/objects/structures/noticeboard.dm
+++ b/code/game/objects/structures/noticeboard.dm
@@ -1,11 +1,15 @@
+#define MAX_NOTICES 5
+
/obj/structure/noticeboard
name = "notice board"
desc = "A board for pinning important notices upon."
icon = 'icons/obj/stationobjs.dmi'
icon_state = "nboard00"
+ layer = ABOVE_WINDOW_LAYER
density = FALSE
anchored = TRUE
max_integrity = 150
+ // Current number of a pinned notices
var/notices = 0
/obj/structure/noticeboard/Initialize(mapload)
@@ -15,7 +19,7 @@
return
for(var/obj/item/I in loc)
- if(notices > 4)
+ if(notices >= MAX_NOTICES)
break
if(istype(I, /obj/item/paper))
I.forceMove(src)
@@ -28,7 +32,7 @@
if(!allowed(user))
to_chat(user, "You are not authorized to add notices")
return
- if(notices < 5)
+ if(notices < MAX_NOTICES)
if(!user.transferItemToLoc(O, src))
return
notices++
@@ -39,54 +43,71 @@
else
return ..()
-/obj/structure/noticeboard/interact(mob/user)
- ui_interact(user)
-
-/obj/structure/noticeboard/ui_interact(mob/user)
+/obj/structure/noticeboard/ui_state(mob/user)
+ return GLOB.physical_state
+
+/obj/structure/noticeboard/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "NoticeBoard", name)
+ ui.open()
+
+/obj/structure/noticeboard/ui_data(mob/user)
+ var/list/data = list()
+ data["allowed"] = allowed(user)
+ data["items"] = list()
+ for(var/obj/item/content in contents)
+ var/list/content_data = list(
+ name = content.name,
+ ref = REF(content)
+ )
+ data["items"] += list(content_data)
+ return data
+
+/obj/structure/noticeboard/ui_act(action, params)
. = ..()
- var/auth = allowed(user)
- var/dat = "[name] "
- for(var/obj/item/P in src)
- if(istype(P, /obj/item/paper))
- dat += "[P.name] [auth ? "WriteRemove" : ""] "
- else
- dat += "[P.name] [auth ? "Remove" : ""] "
- user << browse("Notices[dat]","window=noticeboard")
- onclose(user, "noticeboard")
-
-/obj/structure/noticeboard/Topic(href, href_list)
- ..()
- usr.set_machine(src)
- if(href_list["remove"])
- if((usr.stat || usr.restrained())) //For when a player is handcuffed while they have the notice window open
- return
- var/obj/item/I = locate(href_list["remove"]) in contents
- if(istype(I) && I.loc == src)
- I.forceMove(usr.loc)
- usr.put_in_hands(I)
- notices--
- icon_state = "nboard0[notices]"
+ if(.)
+ return
- if(href_list["write"])
- if((usr.stat || usr.restrained())) //For when a player is handcuffed while they have the notice window open
- return
- var/obj/item/P = locate(href_list["write"]) in contents
- if(istype(P) && P.loc == src)
- var/obj/item/I = usr.is_holding_item_of_type(/obj/item/pen)
- if(I)
- add_fingerprint(usr)
- P.attackby(I, usr)
- else
- to_chat(usr, "You'll need something to write with!")
+ var/obj/item/item = locate(params["ref"]) in contents
+ if(!istype(item) || item.loc != src)
+ return
- if(href_list["read"])
- var/obj/item/I = locate(href_list["read"]) in contents
- if(istype(I) && I.loc == src)
- usr.examinate(I)
+ var/mob/user = usr
+
+ switch(action)
+ if("examine")
+ if(istype(item, /obj/item/paper))
+ item.ui_interact(user)
+ else
+ user.examinate(item)
+ return TRUE
+ if("remove")
+ if(!allowed(user))
+ return
+ remove_item(item, user)
+ return TRUE
+
+/**
+ * Removes an item from the notice board
+ *
+ * Arguments:
+ * * item - The item that is to be removed
+ * * user - The mob that is trying to get the item removed, if there is one
+ */
+/obj/structure/noticeboard/proc/remove_item(obj/item/item, mob/user)
+ item.forceMove(drop_location())
+ if(user)
+ user.put_in_hands(item)
+ balloon_alert(user, "removed from board")
+ notices--
+ icon_state = "nboard0[notices]"
/obj/structure/noticeboard/deconstruct(disassembled = TRUE)
if(!(flags_1 & NODECONSTRUCT_1))
new /obj/item/stack/sheet/iron (loc, 1)
+ for(var/obj/item/content in contents)
+ remove_item(content)
qdel(src)
// Notice boards for the heads of staff (plus the qm)
@@ -130,3 +151,5 @@
name = "Staff Notice Board"
desc = "Important notices from the heads of staff."
req_access = list(ACCESS_HEADS)
+
+#undef MAX_NOTICES
diff --git a/code/game/objects/structures/petrified_statue.dm b/code/game/objects/structures/petrified_statue.dm
index bbe45c136b33a..ab147fa0fa3f3 100644
--- a/code/game/objects/structures/petrified_statue.dm
+++ b/code/game/objects/structures/petrified_statue.dm
@@ -5,7 +5,7 @@
density = TRUE
anchored = TRUE
max_integrity = 200
- var/timer = 240 //eventually the person will be freed
+ var/timer = 480 //eventually the person will be freed
var/mob/living/petrified_mob
/obj/structure/statue/petrified/New(loc, mob/living/L, statue_timer)
@@ -25,10 +25,10 @@
START_PROCESSING(SSobj, src)
..()
-/obj/structure/statue/petrified/process()
+/obj/structure/statue/petrified/process(delta_time)
if(!petrified_mob)
STOP_PROCESSING(SSobj, src)
- timer--
+ timer -= delta_time
petrified_mob.Stun(40) //So they can't do anything while petrified
if(timer <= 0)
STOP_PROCESSING(SSobj, src)
diff --git a/code/game/objects/structures/plasticflaps.dm b/code/game/objects/structures/plasticflaps.dm
index e02c01b055b8f..2afa9481ffc50 100644
--- a/code/game/objects/structures/plasticflaps.dm
+++ b/code/game/objects/structures/plasticflaps.dm
@@ -3,7 +3,7 @@
desc = "Heavy duty, airtight, plastic flaps. Definitely can't get past those. No way."
icon = 'icons/obj/stationobjs.dmi'
icon_state = "plasticflaps"
- armor = list("melee" = 100, "bullet" = 80, "laser" = 80, "energy" = 100, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 50, "acid" = 50)
+ armor = list("melee" = 100, "bullet" = 80, "laser" = 80, "energy" = 100, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 50, "acid" = 50, "stamina" = 0)
density = FALSE
anchored = TRUE
layer = BELOW_OBJ_LAYER
@@ -57,17 +57,17 @@
return FALSE
return TRUE
-/obj/structure/plasticflaps/CanAStarPass(ID, to_dir, caller)
+/obj/structure/plasticflaps/CanAStarPass(obj/item/card/id/ID, to_dir, atom/movable/caller)
if(isliving(caller))
if(isbot(caller))
return TRUE
- var/mob/living/M = caller
- if(!M.ventcrawler && M.mob_size != MOB_SIZE_TINY)
+ var/mob/living/living_caller = caller
+ if(!living_caller.ventcrawler && living_caller.mob_size != MOB_SIZE_TINY)
return FALSE
- var/atom/movable/M = caller
- if(M?.pulling)
- return CanAStarPass(ID, to_dir, M.pulling)
+
+ if(caller?.pulling)
+ return CanAStarPass(ID, to_dir, caller.pulling)
return TRUE //diseases, stings, etc can pass
/obj/structure/plasticflaps/CanPass(atom/movable/A, turf/T)
diff --git a/code/game/objects/structures/railings.dm b/code/game/objects/structures/railings.dm
index c4255bdc87324..1a5b73c0481a9 100644
--- a/code/game/objects/structures/railings.dm
+++ b/code/game/objects/structures/railings.dm
@@ -66,20 +66,22 @@
return TRUE
/obj/structure/railing/CanPass(atom/movable/mover, turf/target)
- ..()
+ . = ..()
if(get_dir(loc, target) & dir)
- return !density
+ var/checking = FLYING | FLOATING
+ return . || mover.movement_type & checking
return TRUE
/obj/structure/railing/corner/CanPass()
..()
return TRUE
-/obj/structure/railing/CheckExit(atom/movable/O, turf/target)
+/obj/structure/railing/CheckExit(atom/movable/mover, turf/target)
..()
if(get_dir(loc, target) & dir)
- return 0
- return 1
+ var/checking = UNSTOPPABLE | FLYING | FLOATING
+ return !density || mover.movement_type & checking || mover.move_force >= MOVE_FORCE_EXTREMELY_STRONG
+ return TRUE
/obj/structure/railing/corner/CheckExit()
return 1
diff --git a/code/game/objects/structures/showcase.dm b/code/game/objects/structures/showcase.dm
index f42514e4e46f0..3b26c402b3e12 100644
--- a/code/game/objects/structures/showcase.dm
+++ b/code/game/objects/structures/showcase.dm
@@ -69,13 +69,13 @@
icon_state = "firefighter"
/obj/structure/showcase/machinery/implanter
- name = "Nanotrasen automated mindshield implanter exhibit"
+ name = "\improper Nanotrasen automated mindshield implanter exhibit"
desc = "A flimsy model of a standard Nanotrasen automated mindshield implant machine. With secure positioning harnesses and a robotic surgical injector, brain damage and other serious medical anomalies are now up to 60% less likely!"
icon = 'icons/obj/machines/implantchair.dmi'
icon_state = "implantchair"
/obj/structure/showcase/machinery/microwave
- name = "Nanotrasen-brand microwave"
+ name = "\improper Nanotrasen-brand microwave"
desc = "The famous Nanotrasen-brand microwave, the multi-purpose cooking appliance every station needs! This one appears to be drawn onto a cardboard box."
icon = 'icons/obj/kitchen.dmi'
icon_state = "mw"
@@ -91,7 +91,7 @@
desc = "A stand with a model of the perfect Nanotrasen Employee bolted to it. Signs indicate it is robustly genetically engineered, as well as being ruthlessly loyal."
/obj/structure/showcase/machinery/tv
- name = "Nanotrasen corporate newsfeed"
+ name = "\improper Nanotrasen corporate newsfeed"
desc = "A slightly battered looking TV. Various Nanotrasen infomercials play on a loop, accompanied by a jaunty tune."
icon = 'icons/obj/computer.dmi'
icon_state = "television"
diff --git a/code/game/objects/structures/shower.dm b/code/game/objects/structures/shower.dm
index 370f17888ee2e..9f00194827eb7 100644
--- a/code/game/objects/structures/shower.dm
+++ b/code/game/objects/structures/shower.dm
@@ -15,6 +15,34 @@
var/reagent_id = /datum/reagent/water
var/reaction_volume = 200
+/obj/structure/showerframe
+ name = "shower frame"
+ icon = 'icons/obj/watercloset.dmi'
+ icon_state = "shower_frame"
+ desc = "A shower frame, that needs 2 plastic sheets to finish construction."
+ anchored = FALSE
+
+/obj/structure/showerframe/attackby(obj/item/I, mob/living/user, params)
+ if(istype(I, /obj/item/stack/sheet/plastic))
+ balloon_alert(user, "You start constructing the shower")
+ if(do_after(user, 4 SECONDS, target = src))
+ I.use(1)
+ balloon_alert(user, "Shower created")
+ var/obj/machinery/shower/new_shower = new /obj/machinery/shower(loc)
+ new_shower.setDir(dir)
+ qdel(src)
+ return
+ return ..()
+
+/obj/structure/showerframe/Initialize()
+ . = ..()
+ AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS, null, CALLBACK(src, .proc/can_be_rotated))
+
+/obj/structure/showerframe/proc/can_be_rotated(mob/user, rotation_type)
+ if(anchored)
+ to_chat(user, "It is fastened to the floor!")
+ return !anchored
+
/obj/machinery/shower/Initialize()
. = ..()
create_reagents(reaction_volume)
@@ -34,7 +62,7 @@
add_fingerprint(M)
if(on)
START_PROCESSING(SSmachines, src)
- process()
+ process(SSMACHINES_DT)
soundloop.start()
else
soundloop.stop()
@@ -143,16 +171,16 @@
if(M.head && wash_obj(M.head))
M.update_inv_head()
- if(M.glasses && !(SLOT_GLASSES in obscured) && wash_obj(M.glasses))
+ if(M.glasses && !(ITEM_SLOT_EYES in obscured) && wash_obj(M.glasses))
M.update_inv_glasses()
- if(M.wear_mask && !(SLOT_WEAR_MASK in obscured) && wash_obj(M.wear_mask))
+ if(M.wear_mask && !(ITEM_SLOT_MASK in obscured) && wash_obj(M.wear_mask))
M.update_inv_wear_mask()
if(M.ears && !(HIDEEARS in obscured) && wash_obj(M.ears))
M.update_inv_ears()
- if(M.wear_neck && !(SLOT_NECK in obscured) && wash_obj(M.wear_neck))
+ if(M.wear_neck && !(ITEM_SLOT_NECK in obscured) && wash_obj(M.wear_neck))
M.update_inv_neck()
if(M.shoes && !(HIDESHOES in obscured) && wash_obj(M.shoes))
diff --git a/code/game/objects/structures/signs/_signs.dm b/code/game/objects/structures/signs/_signs.dm
index bd04e6c1dba48..b9e546ec71bf4 100644
--- a/code/game/objects/structures/signs/_signs.dm
+++ b/code/game/objects/structures/signs/_signs.dm
@@ -5,7 +5,7 @@
density = FALSE
layer = SIGN_LAYER
max_integrity = 100
- armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50)
+ armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50, "stamina" = 0)
var/buildable_sign = 1 //unwrenchable and modifiable
rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
@@ -121,11 +121,11 @@
setDir(turn(dir, 90))
/obj/structure/sign/nanotrasen
- name = "\improper Nanotrasen Logo"
- desc = "A sign with the Nanotrasen Logo on it. Glory to Nanotrasen!"
+ name = "\improper Nanotrasen logo"
+ desc = "A sign with the Nanotrasen logo on it. Glory to Nanotrasen!"
icon_state = "nanotrasen"
/obj/structure/sign/logo
- name = "nanotrasen logo"
+ name = "\improper Nanotrasen logo"
desc = "The Nanotrasen corporate logo."
icon_state = "nanotrasen_sign1"
diff --git a/code/game/objects/structures/signs/signs_plaques.dm b/code/game/objects/structures/signs/signs_plaques.dm
index 317f556e8d704..24df1c43d27f9 100644
--- a/code/game/objects/structures/signs/signs_plaques.dm
+++ b/code/game/objects/structures/signs/signs_plaques.dm
@@ -23,7 +23,7 @@
/obj/structure/sign/plaques/kiddie
name = "\improper AI developers plaque"
- desc = "Next to the extremely long list of names and job titles, there is a drawing of a little child. The child appears to be retarded. Beneath the image, someone has scratched the word \"PACKETS\"."
+ desc = "Next to the extremely long list of names and job titles, there is a drawing of a little child. The child appears to have an intellectual disability. Beneath the image, someone has scratched the word \"PACKETS\"."
icon_state = "kiddieplaque"
/obj/structure/sign/plaques/kiddie/badger
diff --git a/code/game/objects/structures/spirit_board.dm b/code/game/objects/structures/spirit_board.dm
index 095c696640e27..f6e68fc31aa99 100644
--- a/code/game/objects/structures/spirit_board.dm
+++ b/code/game/objects/structures/spirit_board.dm
@@ -65,7 +65,7 @@
//mobs in range check
var/users_in_range = 0
- for(var/mob/living/L in orange(1,src))
+ for(var/mob/living/L in oviewers(1,src))
if(L.ckey && L.client)
if((world.time - L.client.inactivity) < (world.time - 300) || L.stat != CONSCIOUS || L.restrained())//no playing with braindeads or corpses or handcuffed dudes.
to_chat(M, "[L] doesn't seem to be paying attention...")
diff --git a/code/game/objects/structures/stairs.dm b/code/game/objects/structures/stairs.dm
index f2c4e628e0347..2d64134829fd6 100644
--- a/code/game/objects/structures/stairs.dm
+++ b/code/game/objects/structures/stairs.dm
@@ -2,8 +2,8 @@
#define STAIR_TERMINATOR_NO 1
#define STAIR_TERMINATOR_YES 2
-// dir determines the direction of travel to go upwards (due to lack of sprites, currently only 1 and 2 make sense)
-// stairs require /turf/open/openspace as the tile above them to work
+// dir determines the direction of travel to go upwards
+// stairs require /turf/open/openspace as the tile above them to work, unless your stairs have 'force_open_above' set to TRUE
// multiple stair objects can be chained together; the Z level transition will happen on the final stair object in the chain
/obj/structure/stairs
@@ -16,6 +16,7 @@
var/terminator_mode = STAIR_TERMINATOR_AUTOMATIC
var/turf/listeningTo
+
/obj/structure/stairs/Initialize(mapload)
if(force_open_above)
force_open_above()
@@ -105,6 +106,8 @@
T.ChangeTurf(/turf/open/openspace, flags = CHANGETURF_INHERIT_AIR)
/obj/structure/stairs/proc/on_multiz_new(turf/source, dir)
+ SIGNAL_HANDLER
+
if(dir == UP)
var/turf/open/openspace/T = get_step_multiz(get_turf(src), UP)
if(T && !istype(T))
@@ -128,3 +131,6 @@
if(S.dir == dir)
return FALSE
return TRUE
+
+/obj/structure/stairs/attack_ghost(mob/user)
+ stair_ascend(user)
diff --git a/code/game/objects/structures/statues.dm b/code/game/objects/structures/statues.dm
index 33445d8ae0945..d4e86ddcd148c 100644
--- a/code/game/objects/structures/statues.dm
+++ b/code/game/objects/structures/statues.dm
@@ -10,7 +10,6 @@
var/material_drop_type = /obj/item/stack/sheet/iron
CanAtmosPass = ATMOS_PASS_DENSITY
-
/obj/structure/statue/attackby(obj/item/W, mob/living/user, params)
add_fingerprint(user)
user.changeNext_move(CLICK_CD_MELEE)
@@ -224,7 +223,7 @@
max_integrity = 300
material_drop_type = /obj/item/stack/sheet/mineral/bananium
desc = "A bananium statue with a small engraving:'HOOOOOOONK'."
- var/spam_flag = 0
+ var/spam_flag = FALSE
/obj/structure/statue/bananium/clown
name = "statue of a clown"
@@ -248,10 +247,9 @@
/obj/structure/statue/bananium/proc/honk()
if(!spam_flag)
- spam_flag = 1
+ spam_flag = TRUE
playsound(src.loc, 'sound/items/bikehorn.ogg', 50, 1)
- spawn(20)
- spam_flag = 0
+ addtimer(VARSET_CALLBACK(src, spam_flag, FALSE), 2 SECONDS)
/////////////////////sandstone/////////////////////////////////////////
diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm
index 5f268654c7159..c58e2a5adb051 100644
--- a/code/game/objects/structures/tables_racks.dm
+++ b/code/game/objects/structures/tables_racks.dm
@@ -2,6 +2,7 @@
* Contains:
* Tables
* Glass Tables
+ * Plasmaglass Tables
* Wooden Tables
* Reinforced Tables
* Racks
@@ -40,18 +41,11 @@
if(!istype(H))
return
var/feetCover = (H.wear_suit && (H.wear_suit.body_parts_covered & FEET)) || (H.w_uniform && (H.w_uniform.body_parts_covered & FEET))
- if(H.shoes || feetCover || !(H.mobility_flags & MOBILITY_STAND) || HAS_TRAIT(H, TRAIT_PIERCEIMMUNE) || H.m_intent == MOVE_INTENT_WALK)
+ if(!HAS_TRAIT(H, TRAIT_ALWAYS_STUBS) && (H.shoes || feetCover || !(H.mobility_flags & MOBILITY_STAND) || HAS_TRAIT(H, TRAIT_PIERCEIMMUNE) || H.m_intent == MOVE_INTENT_WALK))
return
- if((world.time >= last_bump + 100) && prob(5))
+ if(HAS_TRAIT(H, TRAIT_ALWAYS_STUBS) || ((world.time >= last_bump + 100) && prob(5)))
to_chat(H, "You stub your toe on the [name]!")
- var/power = 2
- if(HAS_TRAIT(H, TRAIT_LIGHT_STEP))
- power = 1
- H.emote("gasp")
- else
- H.emote("scream")
- H.apply_damage(power, BRUTE, def_zone = pick(BODY_ZONE_PRECISE_R_FOOT, BODY_ZONE_PRECISE_L_FOOT))
- H.Paralyze(10 * power)
+ H.stub_toe(2)
last_bump = world.time //do the cooldown here so walking into a table only checks toestubs once
/obj/structure/table/examine(mob/user)
@@ -71,6 +65,11 @@
qdel(src)
new /obj/structure/table/wood(A)
+/obj/structure/table/ratvar_act()
+ var/atom/A = loc
+ qdel(src)
+ new /obj/structure/table/brass(A)
+
/obj/structure/table/attack_paw(mob/user)
return attack_hand(user)
@@ -118,15 +117,15 @@
else
return !density
-/obj/structure/table/CanAStarPass(ID, dir, caller)
+/obj/structure/table/CanAStarPass(obj/item/card/id/ID, to_dir, atom/movable/caller)
. = !density
- if(ismovableatom(caller))
- var/atom/movable/mover = caller
- . = . || (mover.pass_flags & PASSTABLE)
+ if(istype(caller))
+ . = . || (caller.pass_flags & PASSTABLE)
/obj/structure/table/proc/tableplace(mob/living/user, mob/living/pushed_mob)
pushed_mob.forceMove(loc)
- pushed_mob.set_resting(TRUE, TRUE)
+ if(!isanimal(pushed_mob) || iscat(pushed_mob))
+ pushed_mob.set_resting(TRUE, TRUE)
pushed_mob.visible_message("[user] places [pushed_mob] onto [src].", \
"[user] places [pushed_mob] onto [src].")
log_combat(user, pushed_mob, "places", null, "onto [src]")
@@ -136,7 +135,7 @@
to_chat(user, "Throwing [pushed_mob] onto the table might hurt them!")
return
var/added_passtable = FALSE
- if(!pushed_mob.pass_flags & PASSTABLE)
+ if(!(pushed_mob.pass_flags & PASSTABLE))
added_passtable = TRUE
pushed_mob.pass_flags |= PASSTABLE
pushed_mob.Move(src.loc)
@@ -169,13 +168,13 @@
/obj/structure/table/attackby(obj/item/I, mob/user, params)
if(!(flags_1 & NODECONSTRUCT_1))
- if(I.tool_behaviour == TOOL_SCREWDRIVER && deconstruction_ready)
+ if(I.tool_behaviour == TOOL_SCREWDRIVER && deconstruction_ready && user.a_intent != INTENT_HELP)
to_chat(user, "You start disassembling [src]...")
if(I.use_tool(src, user, 20, volume=50))
deconstruct(TRUE)
return
- if(I.tool_behaviour == TOOL_WRENCH && deconstruction_ready)
+ if(I.tool_behaviour == TOOL_WRENCH && deconstruction_ready && user.a_intent != INTENT_HELP)
to_chat(user, "You start deconstructing [src]...")
if(I.use_tool(src, user, 40, volume=50))
playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1)
@@ -190,6 +189,34 @@
return
// If the tray IS empty, continue on (tray will be placed on the table like other items)
+ if(istype(I, /obj/item/riding_offhand))
+ var/obj/item/riding_offhand/riding_item = I
+ var/mob/living/carried_mob = riding_item.rider
+ if(carried_mob == user) //Piggyback user.
+ return
+ switch(user.a_intent)
+ if(INTENT_HARM)
+ user.unbuckle_mob(carried_mob)
+ tableheadsmash(user, carried_mob)
+ if(INTENT_HELP)
+ var/tableplace_delay = 3.5 SECONDS
+ var/skills_space = ""
+ if(HAS_TRAIT(user, TRAIT_QUICKER_CARRY))
+ tableplace_delay = 2 SECONDS
+ skills_space = " expertly"
+ else if(HAS_TRAIT(user, TRAIT_QUICK_CARRY))
+ tableplace_delay = 2.75 SECONDS
+ skills_space = " quickly"
+ carried_mob.visible_message("[user] begins to[skills_space] place [carried_mob] onto [src]...",
+ "[user] begins to[skills_space] place [carried_mob] onto [src]...")
+ if(do_after(user, tableplace_delay, target = carried_mob))
+ user.unbuckle_mob(carried_mob)
+ tableplace(user, carried_mob)
+ else
+ user.unbuckle_mob(carried_mob)
+ tablepush(user, carried_mob)
+ return TRUE
+
if(user.a_intent != INTENT_HARM && !(I.item_flags & ABSTRACT))
if(user.transferItemToLoc(I, drop_location()))
var/list/click_params = params2list(params)
@@ -197,8 +224,8 @@
if(!click_params || !click_params["icon-x"] || !click_params["icon-y"])
return
//Clamp it so that the icon never moves more than 16 pixels in either direction (thus leaving the table turf)
- I.pixel_x = CLAMP(text2num(click_params["icon-x"]) - 16, -(world.icon_size/2), world.icon_size/2)
- I.pixel_y = CLAMP(text2num(click_params["icon-y"]) - 16, -(world.icon_size/2), world.icon_size/2)
+ I.pixel_x = clamp(text2num(click_params["icon-x"]) - 16, -(world.icon_size/2), world.icon_size/2)
+ I.pixel_y = clamp(text2num(click_params["icon-y"]) - 16, -(world.icon_size/2), world.icon_size/2)
return 1
else
return ..()
@@ -227,7 +254,7 @@
canSmoothWith = null
max_integrity = 70
resistance_flags = ACID_PROOF
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100, "stamina" = 0)
var/list/debris = list()
/obj/structure/table/glass/Initialize()
@@ -292,6 +319,25 @@
for(var/obj/item/shard/S in debris)
S.color = NARSIE_WINDOW_COLOUR
+/*
+ * Plasmaglass tables
+ */
+/obj/structure/table/glass/plasma
+ name = "plasmaglass table"
+ desc = "A glass table, but it's pink and more sturdy. What will Nanotrasen design next with plasma?"
+ icon = 'icons/obj/smooth_structures/plasmaglass_table.dmi'
+ icon_state = "plasmaglass_table"
+ buildstack = /obj/item/stack/sheet/plasmaglass
+ max_integrity = 270
+ armor = list("melee" = 10, "bullet" = 5, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100)
+
+/obj/structure/table/glass/plasma/Initialize()
+ . = ..()
+ debris += new /obj/item/shard/plasma
+
+/obj/structure/table/glass/plasma/check_break(mob/living/M)
+ return
+
/*
* Wooden tables
*/
@@ -410,7 +456,7 @@
canSmoothWith = list(/obj/structure/table/reinforced, /obj/structure/table)
max_integrity = 200
integrity_failure = 50
- armor = list("melee" = 10, "bullet" = 30, "laser" = 30, "energy" = 100, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70)
+ armor = list("melee" = 10, "bullet" = 30, "laser" = 30, "energy" = 100, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70, "stamina" = 0)
/obj/structure/table/reinforced/deconstruction_hints(mob/user)
if(deconstruction_ready)
@@ -449,6 +495,9 @@
buildstackamount = 1
canSmoothWith = list(/obj/structure/table/brass, /obj/structure/table/bronze)
+/obj/structure/table/brass/ratvar_act()
+ return
+
/obj/structure/table/brass/tablepush(mob/living/user, mob/living/pushed_mob)
. = ..()
playsound(src, 'sound/magic/clockwork/fellowship_armory.ogg', 50, TRUE)
@@ -494,7 +543,7 @@
/obj/structure/table/optable/Initialize()
. = ..()
for(var/direction in GLOB.cardinals)
- computer = locate(/obj/machinery/computer/operating, get_step(src, direction))
+ computer = locate(/obj/machinery/computer/operating) in get_step(src, direction)
if(computer)
computer.table = src
break
@@ -506,19 +555,37 @@
/obj/structure/table/optable/tablepush(mob/living/user, mob/living/pushed_mob)
pushed_mob.forceMove(loc)
- pushed_mob.set_resting(TRUE, TRUE)
+ if(!isanimal(pushed_mob) || iscat(pushed_mob))
+ pushed_mob.set_resting(TRUE, TRUE)
visible_message("[user] has laid [pushed_mob] on [src].")
- check_patient()
+ get_patient()
-/obj/structure/table/optable/proc/check_patient()
- var/mob/living/carbon/human/M = locate(/mob/living/carbon/human, loc)
+/obj/structure/table/optable/proc/get_patient()
+ var/mob/living/carbon/M = locate(/mob/living/carbon) in loc
if(M)
if(M.resting)
- patient = M
- return TRUE
+ set_patient(M)
else
- patient = null
+ set_patient(null)
+
+/obj/structure/table/optable/proc/set_patient(new_patient)
+ if(patient)
+ UnregisterSignal(patient, COMSIG_PARENT_QDELETING)
+ patient = new_patient
+ if(patient)
+ RegisterSignal(patient, COMSIG_PARENT_QDELETING, .proc/patient_deleted)
+
+/obj/structure/table/optable/proc/patient_deleted(datum/source)
+ SIGNAL_HANDLER
+ set_patient(null)
+
+/obj/structure/table/optable/proc/check_eligible_patient()
+ get_patient()
+ if(!patient)
return FALSE
+ if(ishuman(patient) || ismonkey(patient))
+ return TRUE
+ return FALSE
/*
* Racks
@@ -562,7 +629,7 @@
step(O, get_dir(O, src))
/obj/structure/rack/attackby(obj/item/W, mob/user, params)
- if (W.tool_behaviour == TOOL_WRENCH && !(flags_1&NODECONSTRUCT_1))
+ if (W.tool_behaviour == TOOL_WRENCH && !(flags_1&NODECONSTRUCT_1) && user.a_intent != INTENT_HELP)
W.play_tool_sound(src)
deconstruct(TRUE)
return
diff --git a/code/game/objects/structures/tank_dispenser.dm b/code/game/objects/structures/tank_dispenser.dm
index 163b9daae0cce..3e6d716bc3624 100644
--- a/code/game/objects/structures/tank_dispenser.dm
+++ b/code/game/objects/structures/tank_dispenser.dm
@@ -63,9 +63,14 @@
return
if(!user.transferItemToLoc(I, src))
+ if(istype(I, /obj/item/tank/internals/plasma))
+ plasmatanks--
+ else if(istype(I, /obj/item/tank/internals/oxygen))
+ oxygentanks--
return
to_chat(user, "You put [I] in [src].")
update_icon()
+ ui_update()
/obj/structure/tank_dispenser/ui_state(mob/user)
@@ -100,6 +105,7 @@
usr.put_in_hands(tank)
oxygentanks--
. = TRUE
+ ui_update()
update_icon()
diff --git a/code/game/objects/structures/transit_tubes/station.dm b/code/game/objects/structures/transit_tubes/station.dm
index 4730e4fd2dbe2..71e09245b0231 100644
--- a/code/game/objects/structures/transit_tubes/station.dm
+++ b/code/game/objects/structures/transit_tubes/station.dm
@@ -153,9 +153,7 @@
pod_moving = 0
if(!QDELETED(pod))
var/datum/gas_mixture/floor_mixture = loc.return_air()
- floor_mixture.archive()
- pod.air_contents.archive()
- pod.air_contents.share(floor_mixture, 1) //mix the pod's gas mixture with the tile it's on
+ equalize_all_gases_in_list(list(pod.air_contents,floor_mixture))
air_update_turf()
/obj/structure/transit_tube/station/init_tube_dirs()
diff --git a/code/game/objects/structures/transit_tubes/transit_tube_construction.dm b/code/game/objects/structures/transit_tubes/transit_tube_construction.dm
index 454e7cca095ea..b2ca37c79b995 100644
--- a/code/game/objects/structures/transit_tubes/transit_tube_construction.dm
+++ b/code/game/objects/structures/transit_tubes/transit_tube_construction.dm
@@ -128,5 +128,6 @@
name = "unattached transit tube pod"
icon = 'icons/obj/atmospherics/pipes/transit_tube.dmi'
icon_state = "pod"
+ desc = "Could probably be dragged into an open Transit Tube."
anchored = FALSE
density = FALSE
diff --git a/code/game/objects/structures/transit_tubes/transit_tube_pod.dm b/code/game/objects/structures/transit_tubes/transit_tube_pod.dm
index 51889077cad94..c99a1d7c85218 100644
--- a/code/game/objects/structures/transit_tubes/transit_tube_pod.dm
+++ b/code/game/objects/structures/transit_tubes/transit_tube_pod.dm
@@ -11,8 +11,8 @@
/obj/structure/transit_tube_pod/Initialize()
. = ..()
- air_contents.set_moles(/datum/gas/oxygen, MOLES_O2STANDARD)
- air_contents.set_moles(/datum/gas/nitrogen, MOLES_N2STANDARD)
+ air_contents.set_moles(GAS_O2, MOLES_O2STANDARD)
+ air_contents.set_moles(GAS_N2, MOLES_N2STANDARD)
air_contents.set_temperature(T20C)
@@ -57,8 +57,14 @@
empty_pod()
/obj/structure/transit_tube_pod/contents_explosion(severity, target)
- for(var/atom/movable/AM in contents)
- AM.ex_act(severity, target)
+ for(var/thing in contents)
+ switch(severity)
+ if(EXPLODE_DEVASTATE)
+ SSexplosions.high_mov_atom += thing
+ if(EXPLODE_HEAVY)
+ SSexplosions.med_mov_atom += thing
+ if(EXPLODE_LIGHT)
+ SSexplosions.low_mov_atom += thing
/obj/structure/transit_tube_pod/singularity_pull(S, current_size)
..()
@@ -158,9 +164,24 @@
/obj/structure/transit_tube_pod/assume_air(datum/gas_mixture/giver)
return air_contents.merge(giver)
+/obj/structure/transit_tube_pod/assume_air_moles(datum/gas_mixture/giver, moles)
+ return giver.transfer_to(air_contents, moles)
+
+/obj/structure/transit_tube_pod/assume_air_ratio(datum/gas_mixture/giver, ratio)
+ return giver.transfer_ratio_to(air_contents, ratio)
+
/obj/structure/transit_tube_pod/remove_air(amount)
return air_contents.remove(amount)
+/obj/structure/transit_tube_pod/remove_air_ratio(ratio)
+ return air_contents.remove_ratio(ratio)
+
+/obj/structure/transit_tube_pod/transfer_air(datum/gas_mixture/taker, moles)
+ return air_contents.transfer_to(taker, moles)
+
+/obj/structure/transit_tube_pod/transfer_air_ratio(datum/gas_mixture/taker, ratio)
+ return air_contents.transfer_ratio_to(taker, ratio)
+
/obj/structure/transit_tube_pod/relaymove(mob/mob, direction)
if(istype(mob) && mob.client)
if(!moving)
diff --git a/code/game/objects/structures/traps.dm b/code/game/objects/structures/traps.dm
index 2c209ead993f8..18b795196bf85 100644
--- a/code/game/objects/structures/traps.dm
+++ b/code/game/objects/structures/traps.dm
@@ -108,6 +108,12 @@
time_between_triggers = 10
flare_message = "[src] snaps shut!"
+/obj/structure/trap/stun/hunter/Destroy()
+ if(!QDELETED(stored_item))
+ qdel(stored_item)
+ stored_item = null
+ return ..()
+
/obj/structure/trap/stun/hunter/Crossed(atom/movable/AM)
if(isliving(AM))
var/mob/living/L = AM
@@ -161,7 +167,9 @@
forceMove(stored_trap)//moves item into trap
/obj/item/bountytrap/Destroy()
- qdel(stored_trap)
+ if(!QDELETED(stored_trap))
+ qdel(stored_trap)
+ stored_trap = null
QDEL_NULL(radio)
QDEL_NULL(spark_system)
. = ..()
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index 1bfae8d04dced..206642bf35def 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -107,19 +107,12 @@
return ..()
/obj/structure/toilet/secret
- var/obj/item/secret
var/secret_type = null
-/obj/structure/toilet/secret/Initialize(mapload)
+/obj/structure/toilet/secret/Initialize()
. = ..()
if (secret_type)
- secret = new secret_type(src)
- secret.desc += " It's a secret!"
- w_items += secret.w_class
- contents += secret
-
-
-
+ new secret_type(src)
/obj/structure/urinal
name = "urinal"
@@ -224,6 +217,35 @@
var/busy = FALSE //Something's being washed at the moment
var/dispensedreagent = /datum/reagent/water // for whenever plumbing happens
+
+/obj/structure/sinkframe
+ name = "sink frame"
+ icon = 'icons/obj/watercloset.dmi'
+ icon_state = "sink_frame"
+ desc = "A sink frame, that needs 2 plastic sheets to finish construction."
+ anchored = FALSE
+
+/obj/structure/sinkframe/attackby(obj/item/I, mob/living/user, params)
+ if(istype(I, /obj/item/stack/sheet/plastic))
+ balloon_alert(user, "You start constructing the sink")
+ if(do_after(user, 4 SECONDS, target = src))
+ I.use(1)
+ balloon_alert(user, "Sink created")
+ var/obj/structure/sink/new_sink = new /obj/structure/sink(loc)
+ new_sink.setDir(dir)
+ qdel(src)
+ return
+ return ..()
+
+/obj/structure/sinkframe/Initialize()
+ . = ..()
+ AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS, null, CALLBACK(src, .proc/can_be_rotated))
+
+/obj/structure/sinkframe/proc/can_be_rotated(mob/user, rotation_type)
+ if(anchored)
+ to_chat(user, "It is fastened to the floor!")
+ return !anchored
+
/obj/structure/sink/attack_hand(mob/living/user)
. = ..()
if(.)
@@ -339,7 +361,6 @@
qdel(src)
-
/obj/structure/sink/kitchen
name = "kitchen sink"
icon_state = "sink_alt"
diff --git a/code/game/objects/structures/windoor_assembly.dm b/code/game/objects/structures/windoor_assembly.dm
index 6c0bc7ee89315..68a4eae86d836 100644
--- a/code/game/objects/structures/windoor_assembly.dm
+++ b/code/game/objects/structures/windoor_assembly.dm
@@ -218,6 +218,31 @@
else
W.forceMove(drop_location())
+ //Adding an electroadaptive pseudocircuit for access. Step 6 complete.
+ else if(istype(W, /obj/item/electroadaptive_pseudocircuit))
+ var/obj/item/electroadaptive_pseudocircuit/EP = W
+ if(EP.adapt_circuit(user, 25))
+ var/obj/item/electronics/airlock/AE = new(src)
+ AE.accesses = EP.electronics.accesses
+ AE.one_access = EP.electronics.one_access
+ AE.unres_sides = EP.electronics.unres_sides
+ if(!user.transferItemToLoc(AE, src))
+ qdel(AE)
+ return
+ AE.play_tool_sound(src, 100)
+ user.visible_message("[user] installs the electronics into the airlock assembly.",
+ "You start to install electronics into the airlock assembly...")
+
+ if(do_after(user, 40, target = src))
+ if(!src || electronics)
+ qdel(AE)
+ return
+ to_chat(user, "You install the electroadaptive pseudocircuit.")
+ name = "near finished windoor assembly"
+ electronics = AE
+ else
+ qdel(AE)
+
//Screwdriver to remove airlock electronics. Step 6 undone.
else if(W.tool_behaviour == TOOL_SCREWDRIVER)
if(!electronics)
@@ -243,8 +268,6 @@
created_name = t
return
-
-
//Crowbar to complete the assembly, Step 7 complete.
else if(W.tool_behaviour == TOOL_CROWBAR)
if(!electronics)
@@ -315,13 +338,8 @@
/obj/structure/windoor_assembly/ComponentInitialize()
. = ..()
- AddComponent(
- /datum/component/simple_rotation,
- ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS,
- null,
- CALLBACK(src, .proc/can_be_rotated),
- CALLBACK(src,.proc/after_rotation)
- )
+ var/static/rotation_flags = ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS
+ AddComponent(/datum/component/simple_rotation, rotation_flags, can_be_rotated=CALLBACK(src, .proc/can_be_rotated), after_rotation=CALLBACK(src,.proc/after_rotation))
/obj/structure/windoor_assembly/proc/can_be_rotated(mob/user,rotation_type)
if(anchored)
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index d20c7e9770a01..16f390f10d1a7 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -7,10 +7,10 @@
pressure_resistance = 4*ONE_ATMOSPHERE
anchored = TRUE //initially is 0 for tile smoothing
flags_1 = ON_BORDER_1
- max_integrity = 100
+ max_integrity = 50
can_be_unanchored = TRUE
resistance_flags = ACID_PROOF
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100, "stamina" = 0)
CanAtmosPass = ATMOS_PASS_PROC
rad_insulation = RAD_VERY_LIGHT_INSULATION
rad_flags = RAD_PROTECT_CONTENTS
@@ -27,6 +27,8 @@
var/real_explosion_block //ignore this, just use explosion_block
var/breaksound = "shatter"
var/hitsound = 'sound/effects/Glasshit.ogg'
+ flags_ricochet = RICOCHET_HARD
+ ricochet_chance_mod = 0.4
/obj/structure/window/examine(mob/user)
@@ -84,6 +86,13 @@
/obj/structure/window/narsie_act()
add_atom_colour(NARSIE_WINDOW_COLOUR, FIXED_COLOUR_PRIORITY)
+/obj/structure/window/ratvar_act()
+ if(!fulltile)
+ new/obj/structure/window/reinforced/clockwork(get_turf(src), dir)
+ else
+ new/obj/structure/window/reinforced/clockwork/fulltile(get_turf(src))
+ qdel(src)
+
/obj/structure/window/singularity_pull(S, current_size)
..()
if(current_size >= STAGE_FIVE)
@@ -347,7 +356,7 @@
/obj/structure/window/get_dumping_location(obj/item/storage/source,mob/user)
return null
-/obj/structure/window/CanAStarPass(ID, to_dir)
+/obj/structure/window/CanAStarPass(obj/item/card/id/ID, to_dir, atom/movable/caller)
if(!density)
return 1
if((dir == FULLTILE_WINDOW_DIR) || (dir == to_dir))
@@ -376,11 +385,12 @@
icon_state = "rwindow"
reinf = TRUE
heat_resistance = 1600
- armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 25, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 100)
- max_integrity = 200
+ armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 25, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 100, "stamina" = 0)
+ max_integrity = 100
explosion_block = 1
glass_type = /obj/item/stack/sheet/rglass
rad_insulation = RAD_HEAVY_INSULATION
+ ricochet_chance_mod = 0.8
/obj/structure/window/reinforced/spawner/east
dir = EAST
@@ -400,8 +410,8 @@
icon_state = "plasmawindow"
reinf = FALSE
heat_resistance = 25000
- armor = list("melee" = 75, "bullet" = 5, "laser" = 0, "energy" = 0, "bomb" = 45, "bio" = 100, "rad" = 100, "fire" = 99, "acid" = 100)
- max_integrity = 600
+ armor = list("melee" = 75, "bullet" = 5, "laser" = 0, "energy" = 0, "bomb" = 45, "bio" = 100, "rad" = 100, "fire" = 99, "acid" = 100, "stamina" = 0)
+ max_integrity = 300
glass_type = /obj/item/stack/sheet/plasmaglass
rad_insulation = RAD_NO_INSULATION
@@ -432,8 +442,8 @@
icon_state = "plasmarwindow"
reinf = TRUE
heat_resistance = 50000
- armor = list("melee" = 85, "bullet" = 20, "laser" = 0, "energy" = 0, "bomb" = 60, "bio" = 100, "rad" = 100, "fire" = 99, "acid" = 100)
- max_integrity = 1000
+ armor = list("melee" = 85, "bullet" = 20, "laser" = 0, "energy" = 0, "bomb" = 60, "bio" = 100, "rad" = 100, "fire" = 99, "acid" = 100, "stamina" = 0)
+ max_integrity = 500
explosion_block = 2
glass_type = /obj/item/stack/sheet/plasmarglass
@@ -463,7 +473,7 @@
icon = 'icons/obj/smooth_structures/window.dmi'
icon_state = "window"
dir = FULLTILE_WINDOW_DIR
- max_integrity = 200
+ max_integrity = 100
fulltile = TRUE
flags_1 = PREVENT_CLICK_UNDER_1
smooth = SMOOTH_TRUE
@@ -477,7 +487,7 @@
icon = 'icons/obj/smooth_structures/plasma_window.dmi'
icon_state = "plasmawindow"
dir = FULLTILE_WINDOW_DIR
- max_integrity = 1200
+ max_integrity = 600
fulltile = TRUE
flags_1 = PREVENT_CLICK_UNDER_1
smooth = SMOOTH_TRUE
@@ -504,7 +514,7 @@
icon = 'icons/obj/smooth_structures/reinforced_window.dmi'
icon_state = "r_window"
dir = FULLTILE_WINDOW_DIR
- max_integrity = 400
+ max_integrity = 200
fulltile = TRUE
flags_1 = PREVENT_CLICK_UNDER_1
smooth = SMOOTH_TRUE
@@ -546,13 +556,14 @@
flags_1 = PREVENT_CLICK_UNDER_1
reinf = TRUE
heat_resistance = 1600
- armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 100)
+ armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 100, "stamina" = 0)
smooth = SMOOTH_TRUE
canSmoothWith = null
explosion_block = 3
level = 3
glass_type = /obj/item/stack/sheet/titaniumglass
glass_amount = 2
+ ricochet_chance_mod = 0.9
/obj/structure/window/shuttle/narsie_act()
add_atom_colour("#3C3434", FIXED_COLOUR_PRIORITY)
@@ -569,13 +580,13 @@
icon = 'icons/obj/smooth_structures/plastitanium_window.dmi'
icon_state = "plastitanium_window"
dir = FULLTILE_WINDOW_DIR
- max_integrity = 400
+ max_integrity = 200
wtype = "shuttle"
fulltile = TRUE
flags_1 = PREVENT_CLICK_UNDER_1
reinf = TRUE
heat_resistance = 1600
- armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 100)
+ armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 100, "stamina" = 0)
smooth = SMOOTH_TRUE
canSmoothWith = null
explosion_block = 3
@@ -604,7 +615,7 @@
decon_speed = 10
CanAtmosPass = ATMOS_PASS_YES
resistance_flags = FLAMMABLE
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0, "stamina" = 0)
breaksound = 'sound/items/poster_ripped.ogg'
hitsound = 'sound/weapons/slashmiss.ogg'
var/static/mutable_appearance/torn = mutable_appearance('icons/obj/smooth_structures/paperframes.dmi',icon_state = "torn", layer = ABOVE_OBJ_LAYER - 0.1)
@@ -669,3 +680,26 @@
return
..()
update_icon()
+
+/obj/structure/window/bronze
+ name = "brass window"
+ desc = "A paper-thin pane of translucent yet reinforced brass. Nevermind, this is just weak bronze!"
+ icon = 'icons/obj/smooth_structures/clockwork_window.dmi'
+ icon_state = "clockwork_window_single"
+ glass_type = /obj/item/stack/tile/bronze
+
+/obj/structure/window/bronze/unanchored
+ anchored = FALSE
+
+/obj/structure/window/bronze/fulltile
+ icon_state = "clockwork_window"
+ smooth = SMOOTH_TRUE
+ canSmoothWith = null
+ fulltile = TRUE
+ flags_1 = PREVENT_CLICK_UNDER_1
+ dir = FULLTILE_WINDOW_DIR
+ max_integrity = 50
+ glass_amount = 2
+
+/obj/structure/window/bronze/fulltile/unanchored
+ anchored = FALSE
diff --git a/code/game/say.dm b/code/game/say.dm
index 90c984e0f83f9..6d65a40ca052a 100644
--- a/code/game/say.dm
+++ b/code/game/say.dm
@@ -8,6 +8,7 @@ GLOBAL_LIST_INIT(freqtospan, list(
"[FREQ_MEDICAL]" = "medradio",
"[FREQ_ENGINEERING]" = "engradio",
"[FREQ_SUPPLY]" = "suppradio",
+ "[FREQ_EXPLORATION]" = "explradio",
"[FREQ_SERVICE]" = "servradio",
"[FREQ_SECURITY]" = "secradio",
"[FREQ_COMMAND]" = "comradio",
@@ -28,19 +29,25 @@ GLOBAL_LIST_INIT(freqtospan, list(
language = get_selected_language()
send_speech(message, 7, src, , spans, message_language=language)
-/atom/movable/proc/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
- SEND_SIGNAL(src, COMSIG_MOVABLE_HEAR, message, speaker, message_language, raw_message, radio_freq, spans, message_mode)
+/atom/movable/proc/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods = list())
+ SEND_SIGNAL(src, COMSIG_MOVABLE_HEAR, args)
/atom/movable/proc/can_speak()
- return 1
-
-/atom/movable/proc/send_speech(message, range = 7, obj/source = src, bubble_type, list/spans, datum/language/message_language = null, message_mode)
- var/rendered = compose_message(src, message_language, message, , spans, message_mode)
- for(var/_AM in get_hearers_in_view(range, source))
- var/atom/movable/AM = _AM
- AM.Hear(rendered, src, message_language, message, , spans, message_mode)
-
-/atom/movable/proc/compose_message(atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, face_name = FALSE)
+ return TRUE
+
+/atom/movable/proc/send_speech(message, range = 7, obj/source = src, bubble_type, list/spans, datum/language/message_language = null, list/message_mods = list())
+ var/rendered = compose_message(src, message_language, message, , spans, message_mods)
+ var/list/show_overhead_message_to = list()
+ for(var/atom/movable/AM as() in get_hearers_in_view(range, source))
+ if(ismob(AM))
+ var/mob/M = AM
+ if(M.should_show_chat_message(source, message_language, FALSE, is_heard = TRUE))
+ show_overhead_message_to += M
+ AM.Hear(rendered, src, message_language, message, , spans, message_mods)
+ if(length(show_overhead_message_to))
+ create_chat_message(src, message_language, show_overhead_message_to, message, spans, message_mods)
+
+/atom/movable/proc/compose_message(atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, list/message_mods = list(), face_name = FALSE)
//This proc uses text() because it is faster than appending strings. Thanks BYOND.
//Basic span
var/spanpart1 = ""
@@ -50,14 +57,37 @@ GLOBAL_LIST_INIT(freqtospan, list(
var/freqpart = radio_freq ? "\[[get_radio_name(radio_freq)]\] " : ""
//Speaker name
var/namepart = "[speaker.GetVoice()][speaker.get_alt_name()]"
- if(face_name && ishuman(speaker))
+ if(ishuman(speaker))
var/mob/living/carbon/human/H = speaker
- namepart = "[H.get_face_name()]" //So "fake" speaking like in hallucinations does not give the speaker away if disguised
+ if(face_name)
+ namepart = "[H.get_face_name()]" //So "fake" speaking like in hallucinations does not give the speaker away if disguised
+ if(!radio_freq)
+ if(H.wear_id)
+ var/datum/job/wearer_job = SSjob.GetJob(H.wear_id.GetJobName())
+ var/wearer_say_span
+ if(wearer_job)
+ wearer_say_span = wearer_job.say_span
+ else
+ switch(H.wear_id.GetJobName())
+ if("CentCom")
+ wearer_say_span = "centcom"
+ if("Prisoner")
+ wearer_say_span = "prisoner"
+ else
+ wearer_say_span = "unknown"
+ if(wearer_say_span)
+ spanpart2 = ""
+ else
+ spanpart2 = ""
+ else if(isliving(speaker) && !radio_freq)
+ var/mob/living/L = speaker
+ spanpart2 = ""
+
//End name span.
var/endspanpart = ""
//Message
- var/messagepart = " [lang_treat(speaker, message_language, raw_message, spans, message_mode)]"
+ var/messagepart = " [lang_treat(speaker, message_language, raw_message, spans, message_mods)]"
var/languageicon = ""
var/datum/language/D = GLOB.language_datum_instances[message_language]
@@ -72,7 +102,7 @@ GLOBAL_LIST_INIT(freqtospan, list(
/atom/movable/proc/compose_job(atom/movable/speaker, message_langs, raw_message, radio_freq)
return ""
-/atom/movable/proc/say_mod(input, message_mode)
+/atom/movable/proc/say_mod(input, list/message_mods = list())
var/ending = copytext_char(input, -1)
if(copytext_char(input, -2) == "!!")
return verb_yell
@@ -83,7 +113,7 @@ GLOBAL_LIST_INIT(freqtospan, list(
else
return verb_say
-/atom/movable/proc/say_quote(input, list/spans=list(speech_span), message_mode)
+/atom/movable/proc/say_quote(input, list/spans=list(speech_span), list/message_mods = list())
if(!input)
input = "..."
@@ -91,23 +121,23 @@ GLOBAL_LIST_INIT(freqtospan, list(
spans |= SPAN_YELL
var/spanned = attach_spans(input, spans)
- return "[say_mod(input, message_mode)], \"[spanned]\""
+ return "[say_mod(input, message_mods)], \"[spanned]\""
-/atom/movable/proc/lang_treat(atom/movable/speaker, datum/language/language, raw_message, list/spans, message_mode)
+/atom/movable/proc/lang_treat(atom/movable/speaker, datum/language/language, raw_message, list/spans, list/message_mods = list(), no_quote = FALSE)
if(has_language(language))
var/atom/movable/AM = speaker.GetSource()
if(AM) //Basically means "if the speaker is virtual"
- return AM.say_quote(raw_message, spans, message_mode)
+ return no_quote ? raw_message : AM.say_quote(raw_message, spans, message_mods)
else
- return speaker.say_quote(raw_message, spans, message_mode)
+ return no_quote ? raw_message : speaker.say_quote(raw_message, spans, message_mods)
else if(language)
var/atom/movable/AM = speaker.GetSource()
var/datum/language/D = GLOB.language_datum_instances[language]
raw_message = D.scramble(raw_message)
if(AM)
- return AM.say_quote(raw_message, spans, message_mode)
+ return no_quote ? raw_message : AM.say_quote(raw_message, spans, message_mods)
else
- return speaker.say_quote(raw_message, spans, message_mode)
+ return no_quote ? raw_message : speaker.say_quote(raw_message, spans, message_mods)
else
return "makes a strange sound."
@@ -165,12 +195,12 @@ GLOBAL_LIST_INIT(freqtospan, list(
var/obj/item/radio/radio
INITIALIZE_IMMEDIATE(/atom/movable/virtualspeaker)
-/atom/movable/virtualspeaker/Initialize(mapload, atom/movable/M, radio)
+/atom/movable/virtualspeaker/Initialize(mapload, atom/movable/M, _radio)
. = ..()
- radio = radio
+ radio = _radio
source = M
- if (istype(M))
- name = M.GetVoice()
+ if(istype(M))
+ name = radio.anonymize ? "Unknown" : M.GetVoice()
verb_say = M.verb_say
verb_ask = M.verb_ask
verb_exclaim = M.verb_exclaim
diff --git a/code/game/shuttle_engines.dm b/code/game/shuttle_engines.dm
index e5d58c3e1bfeb..40f90ae08f64f 100644
--- a/code/game/shuttle_engines.dm
+++ b/code/game/shuttle_engines.dm
@@ -8,7 +8,7 @@
icon = 'icons/turf/shuttle.dmi'
resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
max_integrity = 500
- armor = list("melee" = 100, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 70) //default + ignores melee
+ armor = list("melee" = 100, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 70, "stamina" = 0) //default + ignores melee
/obj/structure/shuttle/engine
name = "engine"
diff --git a/code/game/sound.dm b/code/game/sound.dm
index fd520d61165aa..1e5bde8694aa7 100644
--- a/code/game/sound.dm
+++ b/code/game/sound.dm
@@ -1,4 +1,47 @@
-/proc/playsound(atom/source, soundin, vol as num, vary, extrarange as num, falloff, frequency = null, channel = 0, pressure_affected = TRUE, ignore_walls = TRUE)
+
+///Default override for echo
+/sound
+ echo = list(
+ 0, // Direct
+ 0, // DirectHF
+ -10000, // Room, -10000 means no low frequency sound reverb
+ -10000, // RoomHF, -10000 means no high frequency sound reverb
+ 0, // Obstruction
+ 0, // ObstructionLFRatio
+ 0, // Occlusion
+ 0.25, // OcclusionLFRatio
+ 1.5, // OcclusionRoomRatio
+ 1.0, // OcclusionDirectRatio
+ 0, // Exclusion
+ 1.0, // ExclusionLFRatio
+ 0, // OutsideVolumeHF
+ 0, // DopplerFactor
+ 0, // RolloffFactor
+ 0, // RoomRolloffFactor
+ 1.0, // AirAbsorptionFactor
+ 0, // Flags (1 = Auto Direct, 2 = Auto Room, 4 = Auto RoomHF)
+ )
+ environment = SOUND_ENVIRONMENT_NONE //Default to none so sounds without overrides dont get reverb
+
+/*! playsound
+
+playsound is a proc used to play a 3D sound in a specific range. This uses SOUND_RANGE + extra_range to determine that.
+
+source - Origin of sound
+soundin - Either a file, or a string that can be used to get an SFX
+vol - The volume of the sound, excluding falloff and pressure affection.
+vary - bool that determines if the sound changes pitch every time it plays
+extrarange - modifier for sound range. This gets added on top of SOUND_RANGE
+falloff_exponent - Rate of falloff for the audio. Higher means quicker drop to low volume. Should generally be over 1 to indicate a quick dive to 0 rather than a slow dive.
+frequency - playback speed of audio
+channel - The channel the sound is played at
+pressure_affected - Whether or not difference in pressure affects the sound (E.g. if you can hear in space)
+ignore_walls - Whether or not the sound can pass through walls.
+falloff_distance - Distance at which falloff begins. Sound is at peak volume (in regards to falloff) aslong as it is in this range.
+
+*/
+
+/proc/playsound(atom/source, soundin, vol as num, vary, extrarange as num, falloff_exponent = SOUND_FALLOFF_EXPONENT, frequency = null, channel = 0, pressure_affected = TRUE, ignore_walls = TRUE, falloff_distance = SOUND_DEFAULT_FALLOFF_DISTANCE, use_reverb = TRUE)
if(isarea(source))
CRASH("playsound(): source is an area")
@@ -7,26 +50,50 @@
if (!turf_source)
return
+ var/maxdistance = (SOUND_RANGE + extrarange)
+ var/max_z_range = maxdistance / (MULTI_Z_DISTANCE + 1)
+
+ var/list/z_list = get_zs_in_range(turf_source.z, max_z_range)
+
//allocate a channel if necessary now so its the same for everyone
- channel = channel || open_sound_channel()
+ channel = channel || SSsounds.random_available_channel()
// Looping through the player list has the added bonus of working for mobs inside containers
var/sound/S = sound(get_sfx(soundin))
- var/maxdistance = (getviewsize(world.view)[1] + extrarange)
- var/z = turf_source.z
- var/list/listeners = SSmobs.clients_by_zlevel[z]
+ var/list/listeners = list()
+ var/list/dead_listeners = list()
+ for(var/z in z_list)
+ listeners += SSmobs.clients_by_zlevel[z]
+ dead_listeners += SSmobs.dead_players_by_zlevel[z]
if(!ignore_walls) //these sounds don't carry through walls
listeners = listeners & hearers(maxdistance,turf_source)
- for(var/P in listeners)
- var/mob/M = P
+ for(var/mob/M as() in listeners)
if(get_dist(M, turf_source) <= maxdistance)
- M.playsound_local(turf_source, soundin, vol, vary, frequency, falloff, channel, pressure_affected, S)
- for(var/P in SSmobs.dead_players_by_zlevel[z])
- var/mob/M = P
+ M.playsound_local(turf_source, soundin, vol, vary, frequency, falloff_exponent, channel, pressure_affected, S, maxdistance, falloff_distance, 1, use_reverb)
+ for(var/mob/M as() in dead_listeners)
if(get_dist(M, turf_source) <= maxdistance)
- M.playsound_local(turf_source, soundin, vol, vary, frequency, falloff, channel, pressure_affected, S)
+ M.playsound_local(turf_source, soundin, vol, vary, frequency, falloff_exponent, channel, pressure_affected, S, maxdistance, falloff_distance, 1, use_reverb)
+
+/*! playsound
+
+playsound_local is a proc used to play a sound directly on a mob from a specific turf.
+This is called by playsound to send sounds to players, in which case it also gets the max_distance of that sound.
+
+turf_source - Origin of sound
+soundin - Either a file, or a string that can be used to get an SFX
+vol - The volume of the sound, excluding falloff
+vary - bool that determines if the sound changes pitch every time it plays
+frequency - playback speed of audio
+falloff_exponent - Rate of falloff for the audio. Higher means quicker drop to low volume. Should generally be over 1 to indicate a quick dive to 0 rather than a slow dive.
+channel - The channel the sound is played at
+pressure_affected - Whether or not difference in pressure affects the sound (E.g. if you can hear in space)
+max_distance - The peak distance of the sound, if this is a 3D sound
+falloff_distance - Distance at which falloff begins, if this is a 3D sound
+distance_multiplier - Can be used to multiply the distance at which the sound is heard
+
+*/
-/mob/proc/playsound_local(turf/turf_source, soundin, vol as num, vary, frequency, falloff, channel = 0, pressure_affected = TRUE, sound/S)
+/mob/proc/playsound_local(turf/turf_source, soundin, vol as num, vary, frequency, falloff_exponent = SOUND_FALLOFF_EXPONENT, channel = 0, pressure_affected = TRUE, sound/S, max_distance, falloff_distance = SOUND_DEFAULT_FALLOFF_DISTANCE, distance_multiplier = 1, use_reverb = TRUE)
if(!client || !can_hear())
return
@@ -34,7 +101,7 @@
S = sound(get_sfx(soundin))
S.wait = 0 //No queue
- S.channel = channel || open_sound_channel()
+ S.channel = channel || SSsounds.random_available_channel()
S.volume = vol
if(vary)
@@ -49,7 +116,20 @@
//sound volume falloff with distance
var/distance = get_dist(T, turf_source)
- S.volume -= max(distance - getviewsize(world.view)[1], 0) * 2 //multiplicative falloff to add on top of natural audio falloff.
+ var/z_change = turf_source.z - T.z
+ var/z_dist = abs(z_change) * MULTI_Z_DISTANCE
+
+ distance *= distance_multiplier
+ z_dist *= distance_multiplier
+
+ distance += z_dist
+
+ if(max_distance && distance > max_distance)
+ return
+
+ if(max_distance) //If theres no max_distance we're not a 3D sound, so no falloff.
+ S.volume -= (max(distance - falloff_distance, 0) ** (1 / falloff_exponent)) / ((max(max_distance, distance) - falloff_distance) ** (1 / falloff_exponent)) * S.volume
+ //https://www.desmos.com/calculator/sqdfl8ipgf
if(pressure_affected)
//Atmosphere affects sound
@@ -74,32 +154,75 @@
return //No sound
var/dx = turf_source.x - T.x // Hearing from the right/left
- S.x = dx
+ S.x = dx * distance_multiplier
var/dz = turf_source.y - T.y // Hearing from infront/behind
- S.z = dz
+ S.z = dz * distance_multiplier
// The y value is for above your head, but there is no ceiling in 2d spessmens.
- S.y = 1
- S.falloff = (falloff ? falloff : FALLOFF_SOUNDS)
+ S.y = z_change + 1
+ S.falloff = max_distance || 1 //use max_distance, else just use 1 as we are a direct sound so falloff isnt relevant.
+
+ // Sounds can't have their own environment. A sound's environment will be:
+ // 1. the mob's
+ // 2. the area's (defaults to SOUND_ENVRIONMENT_NONE)
+ if(sound_environment_override != SOUND_ENVIRONMENT_NONE)
+ S.environment = sound_environment_override
+ else
+ var/area/A = get_area(src)
+ S.environment = A.sound_environment
+
+ if(use_reverb && S.environment != SOUND_ENVIRONMENT_NONE) //We have reverb, reset our echo setting
+ S.echo[3] = 0 //Room setting, 0 means normal reverb
+ S.echo[4] = 0 //RoomHF setting, 0 means normal reverb.
SEND_SOUND(src, S)
-/proc/sound_to_playing_players(soundin, volume = 100, vary = FALSE, frequency = 0, falloff = FALSE, channel = 0, pressure_affected = FALSE, sound/S)
+/proc/sound_to_playing_players(soundin, volume = 100, vary = FALSE, frequency = 0, channel = 0, pressure_affected = FALSE, sound/S)
if(!S)
S = sound(get_sfx(soundin))
for(var/m in GLOB.player_list)
if(ismob(m) && !isnewplayer(m))
var/mob/M = m
- M.playsound_local(M, null, volume, vary, frequency, falloff, channel, pressure_affected, S)
+ M.playsound_local(M, null, volume, vary, frequency, null, channel, pressure_affected, S)
-/proc/open_sound_channel()
- var/static/next_channel = 1 //loop through the available 1024 - (the ones we reserve) channels and pray that its not still being used
- . = ++next_channel
- if(next_channel > CHANNEL_HIGHEST_AVAILABLE)
- next_channel = 1
+/proc/play_soundtrack_music(var/datum/soundtrack_song/song, list/hearers = null, volume = 80, ignore_prefs = FALSE, play_to_lobby = FALSE, allow_deaf = TRUE, only_station = FALSE)
+ var/sound/S = sound(initial(song.file), volume=volume, wait=0, channel=CHANNEL_AMBIENT_MUSIC)
+ . = S
+
+ if(!hearers)
+ hearers = GLOB.player_list
+
+ for(var/mob/M as() in hearers)
+ if (!ismob(M))
+ continue
+
+ if (!ignore_prefs && !(M.client?.prefs?.toggles & SOUND_AMBIENCE))
+ continue
+
+ if (!play_to_lobby && isnewplayer(M))
+ continue
+
+ if (!allow_deaf && !M.can_hear())
+ continue
+
+ if (only_station && !is_station_level(M.z))
+ continue
+
+ SEND_SOUND(M, S)
+
+ GLOB.soundtrack_this_round |= song
+
+/proc/stop_soundtrack_music()
+ for(var/mob/M as() in GLOB.player_list)
+ M?.stop_sound_channel(CHANNEL_AMBIENT_MUSIC)
/mob/proc/stop_sound_channel(chan)
SEND_SOUND(src, sound(null, repeat = 0, wait = 0, channel = chan))
+/mob/proc/set_sound_channel_volume(channel, volume)
+ var/sound/S = sound(null, FALSE, FALSE, channel, volume)
+ S.status = SOUND_UPDATE
+ SEND_SOUND(src, S)
+
/client/proc/playtitlemusic(vol = 85)
set waitfor = FALSE
UNTIL(SSticker.login_music) //wait for SSticker init to set the login music
@@ -145,8 +268,8 @@
soundin = pick('sound/machines/terminal_button01.ogg', 'sound/machines/terminal_button02.ogg', 'sound/machines/terminal_button03.ogg', \
'sound/machines/terminal_button04.ogg', 'sound/machines/terminal_button05.ogg', 'sound/machines/terminal_button06.ogg', \
'sound/machines/terminal_button07.ogg', 'sound/machines/terminal_button08.ogg')
- if ("desceration")
- soundin = pick('sound/misc/desceration-01.ogg', 'sound/misc/desceration-02.ogg', 'sound/misc/desceration-03.ogg')
+ if ("desecration")
+ soundin = pick('sound/misc/desecration-01.ogg', 'sound/misc/desecration-02.ogg', 'sound/misc/desecration-03.ogg')
if ("im_here")
soundin = pick('sound/hallucinations/im_here1.ogg', 'sound/hallucinations/im_here2.ogg')
if ("can_open")
@@ -175,7 +298,6 @@
soundin = pick('sound/machines/sm/accent/delam/1.ogg', 'sound/machines/sm/accent/normal/2.ogg', 'sound/machines/sm/accent/normal/3.ogg', 'sound/machines/sm/accent/normal/4.ogg', 'sound/machines/sm/accent/normal/5.ogg', 'sound/machines/sm/accent/normal/6.ogg', 'sound/machines/sm/accent/normal/7.ogg', 'sound/machines/sm/accent/normal/8.ogg', 'sound/machines/sm/accent/normal/9.ogg', 'sound/machines/sm/accent/normal/10.ogg', 'sound/machines/sm/accent/normal/11.ogg', 'sound/machines/sm/accent/normal/12.ogg', 'sound/machines/sm/accent/normal/13.ogg', 'sound/machines/sm/accent/normal/14.ogg', 'sound/machines/sm/accent/normal/15.ogg', 'sound/machines/sm/accent/normal/16.ogg', 'sound/machines/sm/accent/normal/17.ogg', 'sound/machines/sm/accent/normal/18.ogg', 'sound/machines/sm/accent/normal/19.ogg', 'sound/machines/sm/accent/normal/20.ogg', 'sound/machines/sm/accent/normal/21.ogg', 'sound/machines/sm/accent/normal/22.ogg', 'sound/machines/sm/accent/normal/23.ogg', 'sound/machines/sm/accent/normal/24.ogg', 'sound/machines/sm/accent/normal/25.ogg', 'sound/machines/sm/accent/normal/26.ogg', 'sound/machines/sm/accent/normal/27.ogg', 'sound/machines/sm/accent/normal/28.ogg', 'sound/machines/sm/accent/normal/29.ogg', 'sound/machines/sm/accent/normal/30.ogg', 'sound/machines/sm/accent/normal/31.ogg', 'sound/machines/sm/accent/normal/32.ogg', 'sound/machines/sm/accent/normal/33.ogg')
return soundin
-
/client/proc/channel_in_use(channel)
for (var/sound/S in src.SoundQuery())
if (S.channel == channel)
diff --git a/code/game/turfs/change_turf.dm b/code/game/turfs/change_turf.dm
index b86069189cb45..6cd9266133d27 100644
--- a/code/game/turfs/change_turf.dm
+++ b/code/game/turfs/change_turf.dm
@@ -15,9 +15,7 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
if(turf_type)
var/turf/newT = ChangeTurf(turf_type, baseturf_type, flags)
- SSair.remove_from_active(newT)
CALCULATE_ADJACENT_TURFS(newT)
- SSair.add_to_active(newT,1)
/turf/proc/copyTurf(turf/T)
if(T.type != type)
@@ -54,6 +52,14 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
/turf/proc/TerraformTurf(path, new_baseturf, flags)
return ChangeTurf(path, new_baseturf, flags)
+/turf/proc/get_z_base_turf()
+ . = SSmapping.level_trait(z, ZTRAIT_BASETURF) || /turf/open/space
+ if (!ispath(.))
+ . = text2path(.)
+ if (!ispath(.))
+ warning("Z-level [z] has invalid baseturf '[SSmapping.level_trait(z, ZTRAIT_BASETURF)]'")
+ . = /turf/open/space
+
// Creates a new turf
// new_baseturfs can be either a single type or list of types, formated the same as baseturfs. see turf.dm
/turf/proc/ChangeTurf(path, list/new_baseturfs, flags)
@@ -61,13 +67,7 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
if(null)
return
if(/turf/baseturf_bottom)
- path = SSmapping.level_trait(z, ZTRAIT_BASETURF)
- if (!ispath(path))
- var/turf/T = below()
- if(T && !istype(T, /turf/open/space))
- path = /turf/open/openspace
- else
- path = /turf/open/space
+ path = get_z_base_turf()
if(/turf/open/space/basic)
// basic doesn't initialize and this will cause issues
// no warning though because this can happen naturaly as a result of it being built on top of
@@ -138,7 +138,6 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
/turf/open/ChangeTurf(path, list/new_baseturfs, flags)
if ((flags & CHANGETURF_INHERIT_AIR) && ispath(path, /turf/open))
- SSair.remove_from_active(src)
var/datum/gas_mixture/stashed_air = new()
stashed_air.copy_from(air)
. = ..()
@@ -147,13 +146,17 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
return
var/turf/open/newTurf = .
newTurf.air.copy_from(stashed_air)
+ update_air_ref(planetary_atmos ? 1 : 2)
QDEL_NULL(stashed_air)
- update_air_ref()
- SSair.add_to_active(newTurf)
else
if(ispath(path,/turf/closed))
flags |= CHANGETURF_RECALC_ADJACENT
- return ..()
+ update_air_ref(-1)
+ . = ..()
+ else
+ . = ..()
+ if(!istype(air,/datum/gas_mixture))
+ Initalize_Atmos(0)
/turf/closed/ChangeTurf(path, list/new_baseturfs, flags)
if(ispath(path,/turf/open))
@@ -170,7 +173,7 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
while(ispath(turf_type, /turf/baseturf_skipover))
amount++
if(amount > new_baseturfs.len)
- CRASH("The bottomost baseturf of a turf is a skipover [src]([type])")
+ CRASH("The bottommost baseturf of a turf is a skipover [src]([type])")
turf_type = new_baseturfs[max(1, new_baseturfs.len - amount + 1)]
new_baseturfs.len -= min(amount, new_baseturfs.len - 1) // No removing the very bottom
if(new_baseturfs.len == 1)
@@ -318,7 +321,6 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
total.merge(S.air)
air.copy_from(total.remove_ratio(1/turf_count))
- SSair.add_to_active(src)
/turf/proc/ReplaceWithLattice()
ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
diff --git a/code/game/turfs/closed/_closed.dm b/code/game/turfs/closed/_closed.dm
index 2cf04297a40e9..ccce019a70e10 100644
--- a/code/game/turfs/closed/_closed.dm
+++ b/code/game/turfs/closed/_closed.dm
@@ -8,12 +8,10 @@
/turf/closed/Initialize()
. = ..()
- update_air_ref()
/turf/closed/AfterChange()
. = ..()
SSair.high_pressure_delta -= src
- update_air_ref()
/turf/closed/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
return FALSE
diff --git a/code/game/turfs/closed/minerals.dm b/code/game/turfs/closed/minerals.dm
index a399b5dbea31d..7be3a05b271a0 100644
--- a/code/game/turfs/closed/minerals.dm
+++ b/code/game/turfs/closed/minerals.dm
@@ -12,13 +12,11 @@
opacity = 1
density = TRUE
layer = EDGED_TURF_LAYER
- temperature = TCMB
+ initial_temperature = 293.15
var/environment_type = "asteroid"
var/turf/open/floor/plating/turf_type = /turf/open/floor/plating/asteroid/airless
- var/mineralType = null
+ var/obj/item/stack/ore/mineralType = null
var/mineralAmt = 3
- var/spread = 0 //will the seam spread?
- var/spreadChance = 0 //the percentual chance of an ore spreading to the neighbouring tiles
var/last_act = 0
var/scan_state = "" //Holder for the image we display when we're pinged by a mining scanner
var/defer_change = 0
@@ -31,12 +29,24 @@
transform = M
icon = smooth_icon
. = ..()
- if (mineralType && mineralAmt && spread && spreadChance)
+
+/turf/closed/mineral/proc/Spread_Vein()
+ var/spreadChance = initial(mineralType.spreadChance)
+ if(spreadChance)
for(var/dir in GLOB.cardinals)
if(prob(spreadChance))
var/turf/T = get_step(src, dir)
- if(istype(T, /turf/closed/mineral/random))
- Spread(T)
+ var/turf/closed/mineral/random/M = T
+ if(istype(M) && !M.mineralType)
+ M.Change_Ore(mineralType)
+
+/turf/closed/mineral/proc/Change_Ore(var/ore_type, random = 0)
+ if(random)
+ mineralAmt = rand(1, 5)
+ if(ispath(ore_type, /obj/item/stack/ore)) //If it has a scan_state, switch to it
+ var/obj/item/stack/ore/the_ore = ore_type
+ scan_state = initial(the_ore.scan_state) // I SAID. SWITCH. TO. IT.
+ mineralType = ore_type // Everything else assumes that this is typed correctly so don't set it to non-ores thanks.
/turf/closed/mineral/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
if(turf_type)
@@ -126,45 +136,46 @@
gets_drilled(null, 1)
return
-/turf/closed/mineral/Spread(turf/T)
- T.ChangeTurf(type)
-
/turf/closed/mineral/random
- var/list/mineralSpawnChanceList = list(/turf/closed/mineral/uranium = 5, /turf/closed/mineral/diamond = 1, /turf/closed/mineral/gold = 10,
- /turf/closed/mineral/silver = 12, /turf/closed/mineral/plasma = 20, /turf/closed/mineral/iron = 40, /turf/closed/mineral/titanium = 11,
- /turf/closed/mineral/gibtonite = 4, /turf/open/floor/plating/asteroid/airless/cave = 2, /turf/closed/mineral/bscrystal = 1,
- /turf/closed/mineral/copper = 15)
+ var/list/mineralSpawnChanceList = list(/obj/item/stack/ore/uranium = 5, /obj/item/stack/ore/diamond = 1, /obj/item/stack/ore/gold = 10,
+ /obj/item/stack/ore/silver = 12, /obj/item/stack/ore/plasma = 20, /obj/item/stack/ore/iron = 40, /obj/item/stack/ore/titanium = 11,
+ /turf/closed/mineral/gibtonite = 4, /obj/item/stack/ore/bluespace_crystal = 1,
+ /obj/item/stack/ore/copper = 15)
//Currently, Adamantine won't spawn as it has no uses. -Durandan
var/mineralChance = 13
- var/display_icon_state = "rock"
/turf/closed/mineral/random/Initialize()
mineralSpawnChanceList = typelist("mineralSpawnChanceList", mineralSpawnChanceList)
- if (display_icon_state)
- icon_state = display_icon_state
. = ..()
if (prob(mineralChance))
var/path = pickweight(mineralSpawnChanceList)
- var/turf/T = ChangeTurf(path,null,CHANGETURF_IGNORE_AIR)
-
- if(T && ismineralturf(T))
- var/turf/closed/mineral/M = T
- M.mineralAmt = rand(1, 5)
- M.environment_type = src.environment_type
- M.turf_type = src.turf_type
- M.baseturfs = src.baseturfs
- src = M
- M.levelupdate()
+ if(ispath(path, /turf))
+ var/turf/T = ChangeTurf(path,null,CHANGETURF_IGNORE_AIR)
+
+ T.baseturfs = src.baseturfs
+ if(ismineralturf(T))
+ var/turf/closed/mineral/M = T
+ M.turf_type = src.turf_type
+ M.mineralAmt = rand(1, 5)
+ M.environment_type = src.environment_type
+ src = M
+ M.levelupdate()
+ else
+ src = T
+ T.levelupdate()
+ else
+ Change_Ore(path, 1)
+ Spread_Vein(path)
/turf/closed/mineral/random/high_chance
icon_state = "rock_highchance"
mineralChance = 25
mineralSpawnChanceList = list(
- /turf/closed/mineral/uranium = 35, /turf/closed/mineral/diamond = 30, /turf/closed/mineral/gold = 45, /turf/closed/mineral/titanium = 45,
- /turf/closed/mineral/silver = 50, /turf/closed/mineral/copper = 50,/turf/closed/mineral/plasma = 50, /turf/closed/mineral/bscrystal = 20)
+ /obj/item/stack/ore/uranium = 35, /obj/item/stack/ore/diamond = 30, /obj/item/stack/ore/gold = 45, /obj/item/stack/ore/titanium = 45,
+ /obj/item/stack/ore/silver = 50, /obj/item/stack/ore/copper = 50, /obj/item/stack/ore/plasma = 50, /obj/item/stack/ore/bluespace_crystal = 20)
/turf/closed/mineral/random/high_chance/volcanic
environment_type = "basalt"
@@ -173,18 +184,17 @@
initial_gas_mix = LAVALAND_DEFAULT_ATMOS
defer_change = 1
mineralSpawnChanceList = list(
- /turf/closed/mineral/uranium/volcanic = 35, /turf/closed/mineral/diamond/volcanic = 30, /turf/closed/mineral/gold/volcanic = 45, /turf/closed/mineral/titanium/volcanic = 45,
- /turf/closed/mineral/silver/volcanic = 50, /turf/closed/mineral/copper/volcanic = 50, /turf/closed/mineral/plasma/volcanic = 50, /turf/closed/mineral/bscrystal/volcanic = 20)
-
+ /obj/item/stack/ore/uranium = 35, /obj/item/stack/ore/diamond = 30, /obj/item/stack/ore/gold = 45, /obj/item/stack/ore/titanium = 45,
+ /obj/item/stack/ore/silver = 50, /obj/item/stack/ore/copper = 50, /obj/item/stack/ore/plasma = 50, /obj/item/stack/ore/bluespace_crystal)
/turf/closed/mineral/random/low_chance
icon_state = "rock_lowchance"
mineralChance = 6
mineralSpawnChanceList = list(
- /turf/closed/mineral/uranium = 2, /turf/closed/mineral/diamond = 1, /turf/closed/mineral/gold = 4, /turf/closed/mineral/titanium = 4,
- /turf/closed/mineral/silver = 6, /turf/closed/mineral/copper = 6, /turf/closed/mineral/plasma = 15, /turf/closed/mineral/iron = 40,
- /turf/closed/mineral/gibtonite = 2, /turf/closed/mineral/bscrystal = 1)
+ /obj/item/stack/ore/uranium = 2, /obj/item/stack/ore/diamond = 1, /obj/item/stack/ore/gold = 4, /obj/item/stack/ore/titanium = 4,
+ /obj/item/stack/ore/silver = 6, /obj/item/stack/ore/copper = 6, /obj/item/stack/ore/plasma = 15, /obj/item/stack/ore/iron = 40,
+ /turf/closed/mineral/gibtonite = 2, /obj/item/stack/ore/bluespace_crystal = 1)
/turf/closed/mineral/random/volcanic
@@ -196,15 +206,15 @@
mineralChance = 10
mineralSpawnChanceList = list(
- /turf/closed/mineral/uranium/volcanic = 5, /turf/closed/mineral/diamond/volcanic = 1, /turf/closed/mineral/gold/volcanic = 10, /turf/closed/mineral/titanium/volcanic = 11,
- /turf/closed/mineral/silver/volcanic = 12, /turf/closed/mineral/copper/volcanic = 12, /turf/closed/mineral/plasma/volcanic = 20, /turf/closed/mineral/iron/volcanic = 40,
- /turf/closed/mineral/gibtonite/volcanic = 4, /turf/open/floor/plating/asteroid/airless/cave/volcanic = 1, /turf/closed/mineral/bscrystal/volcanic = 1)
+ /obj/item/stack/ore/uranium = 5, /obj/item/stack/ore/diamond = 1, /obj/item/stack/ore/gold = 10, /obj/item/stack/ore/titanium = 11,
+ /obj/item/stack/ore/silver = 12, /obj/item/stack/ore/copper = 12, /obj/item/stack/ore/plasma = 20, /obj/item/stack/ore/iron = 40,
+ /turf/closed/mineral/gibtonite/volcanic = 4, /obj/item/stack/ore/bluespace_crystal = 1)
/turf/closed/mineral/random/labormineral
mineralSpawnChanceList = list(
- /turf/closed/mineral/uranium = 3, /turf/closed/mineral/diamond = 1, /turf/closed/mineral/gold = 8, /turf/closed/mineral/titanium = 8,
- /turf/closed/mineral/silver = 20, /turf/closed/mineral/copper = 20, /turf/closed/mineral/plasma = 30, /turf/closed/mineral/iron = 95,
+ /obj/item/stack/ore/uranium = 3, /obj/item/stack/ore/diamond = 1, /obj/item/stack/ore/gold = 8, /obj/item/stack/ore/titanium = 8,
+ /obj/item/stack/ore/silver = 20, /obj/item/stack/ore/copper = 20, /obj/item/stack/ore/plasma = 30, /obj/item/stack/ore/iron = 95,
/turf/closed/mineral/gibtonite = 2)
icon_state = "rock_labor"
@@ -216,25 +226,16 @@
initial_gas_mix = LAVALAND_DEFAULT_ATMOS
defer_change = 1
mineralSpawnChanceList = list(
- /turf/closed/mineral/uranium/volcanic = 3, /turf/closed/mineral/diamond/volcanic = 1, /turf/closed/mineral/gold/volcanic = 8, /turf/closed/mineral/titanium/volcanic = 8,
- /turf/closed/mineral/silver/volcanic = 20, /turf/closed/mineral/copper/volcanic = 20, /turf/closed/mineral/plasma/volcanic = 30, /turf/closed/mineral/bscrystal/volcanic = 1, /turf/closed/mineral/gibtonite/volcanic = 2,
- /turf/closed/mineral/iron/volcanic = 95)
-
+ /obj/item/stack/ore/uranium = 3, /obj/item/stack/ore/diamond = 1, /obj/item/stack/ore/gold = 8, /obj/item/stack/ore/titanium = 8,
+ /obj/item/stack/ore/silver = 20, /obj/item/stack/ore/copper = 20, /obj/item/stack/ore/plasma = 30, /obj/item/stack/ore/bluespace_crystal = 1, /turf/closed/mineral/gibtonite/volcanic = 2,
+ /obj/item/stack/ore/iron = 95)
+// Subtypes for mappers placing ores manually.
/turf/closed/mineral/iron
mineralType = /obj/item/stack/ore/iron
- spreadChance = 20
- spread = 1
scan_state = "rock_Iron"
-/turf/closed/mineral/iron/volcanic
- environment_type = "basalt"
- turf_type = /turf/open/floor/plating/asteroid/basalt/lava_land_surface
- baseturfs = /turf/open/floor/plating/asteroid/basalt/lava_land_surface
- initial_gas_mix = LAVALAND_DEFAULT_ATMOS
- defer_change = 1
-
/turf/closed/mineral/iron/ice
environment_type = "snow_cavern"
icon_state = "icerock_iron"
@@ -244,11 +245,8 @@
initial_gas_mix = FROZEN_ATMOS
defer_change = TRUE
-
/turf/closed/mineral/uranium
mineralType = /obj/item/stack/ore/uranium
- spreadChance = 5
- spread = 1
scan_state = "rock_Uranium"
/turf/closed/mineral/uranium/volcanic
@@ -261,8 +259,6 @@
/turf/closed/mineral/diamond
mineralType = /obj/item/stack/ore/diamond
- spreadChance = 0
- spread = 1
scan_state = "rock_Diamond"
/turf/closed/mineral/diamond/volcanic
@@ -274,18 +270,15 @@
/turf/closed/mineral/diamond/ice
environment_type = "snow_cavern"
- icon_state = "icerock_diamond"
+ icon_state = "icerock_iron"
smooth_icon = 'icons/turf/walls/icerock_wall.dmi'
turf_type = /turf/open/floor/plating/asteroid/snow/ice
baseturfs = /turf/open/floor/plating/asteroid/snow/ice
initial_gas_mix = FROZEN_ATMOS
defer_change = TRUE
-
/turf/closed/mineral/gold
mineralType = /obj/item/stack/ore/gold
- spreadChance = 5
- spread = 1
scan_state = "rock_Gold"
/turf/closed/mineral/gold/volcanic
@@ -298,8 +291,6 @@
/turf/closed/mineral/silver
mineralType = /obj/item/stack/ore/silver
- spreadChance = 5
- spread = 1
scan_state = "rock_Silver"
/turf/closed/mineral/silver/volcanic
@@ -311,8 +302,6 @@
/turf/closed/mineral/copper
mineralType = /obj/item/stack/ore/copper
- spreadChance = 5
- spread = 1
scan_state = "rock_Copper"
/turf/closed/mineral/copper/volcanic
@@ -324,8 +313,6 @@
/turf/closed/mineral/titanium
mineralType = /obj/item/stack/ore/titanium
- spreadChance = 5
- spread = 1
scan_state = "rock_Titanium"
/turf/closed/mineral/titanium/volcanic
@@ -338,8 +325,6 @@
/turf/closed/mineral/plasma
mineralType = /obj/item/stack/ore/plasma
- spreadChance = 8
- spread = 1
scan_state = "rock_Plasma"
/turf/closed/mineral/plasma/volcanic
@@ -363,16 +348,11 @@
/turf/closed/mineral/bananium
mineralType = /obj/item/stack/ore/bananium
mineralAmt = 3
- spreadChance = 0
- spread = 0
scan_state = "rock_Bananium"
-
/turf/closed/mineral/bscrystal
mineralType = /obj/item/stack/ore/bluespace_crystal
mineralAmt = 1
- spreadChance = 0
- spread = 0
scan_state = "rock_BScrystal"
/turf/closed/mineral/bscrystal/volcanic
@@ -436,8 +416,6 @@
/turf/closed/mineral/gibtonite
mineralAmt = 1
- spreadChance = 0
- spread = 0
scan_state = "rock_Gibtonite"
var/det_time = 8 //Countdown till explosion, but also rewards the player for how close you were to detonation when you defuse it
var/stage = GIBTONITE_UNSTRUCK //How far into the lifecycle of gibtonite we are
@@ -509,7 +487,7 @@
stage = GIBTONITE_DETONATE
explosion(bombturf,1,2,5, adminlog = 0)
if(stage == GIBTONITE_STABLE) //Gibtonite deposit is now benign and extractable. Depending on how close you were to it blowing up before defusing, you get better quality ore.
- var/obj/item/twohanded/required/gibtonite/G = new (src)
+ var/obj/item/gibtonite/G = new (src)
if(det_time <= 0)
G.quality = 3
G.icon_state = "Gibtonite ore 3"
diff --git a/code/game/turfs/closed/wall/mineral_walls.dm b/code/game/turfs/closed/wall/mineral_walls.dm
index 0e5dbce952bd8..6b77928d41168 100644
--- a/code/game/turfs/closed/wall/mineral_walls.dm
+++ b/code/game/turfs/closed/wall/mineral_walls.dm
@@ -73,7 +73,7 @@
if(world.time > last_event+15)
active = 1
radiation_pulse(src, 40)
- for(var/turf/closed/wall/mineral/uranium/T in orange(1,src))
+ for(var/turf/closed/wall/mineral/uranium/T in (RANGE_TURFS(1,src)-src))
T.radiate()
last_event = world.time
active = null
@@ -141,9 +141,9 @@
canSmoothWith = list(/turf/closed/wall/mineral/wood, /obj/structure/falsewall/wood, /turf/closed/wall/mineral/wood/nonmetal)
/turf/closed/wall/mineral/wood/attackby(obj/item/W, mob/user)
- if(W.sharpness && W.force)
+ if(W.is_sharp() && W.force)
var/duration = (48/W.force) * 2 //In seconds, for now.
- if(istype(W, /obj/item/hatchet) || istype(W, /obj/item/twohanded/fireaxe))
+ if(istype(W, /obj/item/hatchet) || istype(W, /obj/item/fireaxe))
duration /= 4 //Much better with hatchets and axes.
if(do_after(user, duration*10, target=src)) //Into deciseconds.
dismantle_wall(FALSE,FALSE)
@@ -197,7 +197,8 @@
icon = 'icons/turf/walls/shuttle_wall.dmi'
icon_state = "map-shuttle"
explosion_block = 3
- flags_1 = CAN_BE_DIRTY_1 | CHECK_RICOCHET_1
+ flags_1 = CAN_BE_DIRTY_1
+ flags_ricochet = RICOCHET_SHINY | RICOCHET_HARD
sheet_type = /obj/item/stack/sheet/mineral/titanium
smooth = SMOOTH_MORE|SMOOTH_DIAGONAL
canSmoothWith = list(/turf/closed/wall/mineral/titanium, /obj/machinery/door/airlock/shuttle, /obj/machinery/door/airlock, /obj/structure/window/shuttle, /obj/structure/shuttle/engine/heater, /obj/structure/falsewall/titanium)
@@ -264,6 +265,9 @@
smooth = SMOOTH_MORE|SMOOTH_DIAGONAL
canSmoothWith = list(/turf/closed/wall/mineral/plastitanium, /obj/machinery/door/airlock/shuttle, /obj/machinery/door/airlock, /obj/structure/window/plastitanium, /obj/structure/shuttle/engine, /obj/structure/falsewall/plastitanium)
+/turf/closed/wall/mineral/plastitanium/try_destroy(obj/item/I, mob/user, turf/T)
+ return FALSE
+
/turf/closed/wall/mineral/plastitanium/nodiagonal
smooth = SMOOTH_MORE
icon_state = "map-shuttle_nd"
@@ -278,14 +282,8 @@
fixed_underlay = list("space"=1)
/turf/closed/wall/mineral/plastitanium/explosive/ex_act(severity)
- var/datum/explosion/acted_explosion = null
- for(var/datum/explosion/E in GLOB.explosions)
- if(E.explosion_id == explosion_id)
- acted_explosion = E
- break
- if(acted_explosion && istype(acted_explosion.explosion_source, /obj/item/bombcore))
- var/obj/item/bombcore/large/bombcore = new(get_turf(src))
- bombcore.detonate()
+ var/obj/item/bombcore/large/bombcore = new(get_turf(src))
+ bombcore.detonate()
..()
//have to copypaste this code
diff --git a/code/game/turfs/closed/wall/misc_walls.dm b/code/game/turfs/closed/wall/misc_walls.dm
index f21f8be704a03..f2806be5fffc8 100644
--- a/code/game/turfs/closed/wall/misc_walls.dm
+++ b/code/game/turfs/closed/wall/misc_walls.dm
@@ -16,6 +16,9 @@
/turf/closed/wall/mineral/cult/devastate_wall()
new sheet_type(get_turf(src), sheet_amount)
+/turf/closed/wall/mineral/cult/try_destroy(obj/item/I, mob/user, turf/T)
+ return FALSE
+
/turf/closed/wall/mineral/cult/Exited(atom/movable/AM, atom/newloc)
. = ..()
if(istype(AM, /mob/living/simple_animal/hostile/construct/harvester)) //harvesters can go through cult walls, dragging something with
@@ -83,3 +86,25 @@
sheet_amount = 2
girder_type = /obj/structure/girder/bronze
+
+/turf/closed/indestructible/cordon
+ name = "cordon"
+ desc = "The final word in problem solving."
+ icon_state = "cordon"
+
+//Will this look good? No. Will it work? Probably.
+
+/turf/closed/indestructible/cordon/Entered(atom/movable/AM)
+ . = ..()
+ if(isobserver(AM))
+ return
+ if(ismob(AM))
+ var/mob/interloper = AM
+ interloper.death()
+ if(ismecha(AM))
+ var/obj/mecha/fuckphazons = AM
+ var/mob/living/carbon/interloper = fuckphazons.occupant
+ interloper?.death()
+ qdel(interloper)
+
+ qdel(AM)
diff --git a/code/game/turfs/closed/wall/reinf_walls.dm b/code/game/turfs/closed/wall/reinf_walls.dm
index 27bb5f8233974..78dd5e0c1a8c3 100644
--- a/code/game/turfs/closed/wall/reinf_walls.dm
+++ b/code/game/turfs/closed/wall/reinf_walls.dm
@@ -51,15 +51,6 @@
to_chat(M, "This wall is far too strong for you to destroy.")
/turf/closed/wall/r_wall/try_destroy(obj/item/I, mob/user, turf/T)
- if(istype(I, /obj/item/pickaxe/drill/jackhammer))
- to_chat(user, "You begin to smash though [src]...")
- if(do_after(user, 50, target = src))
- if(!istype(src, /turf/closed/wall/r_wall))
- return TRUE
- I.play_tool_sound(src)
- visible_message("[user] smashes through [src] with [I]!", "You hear the grinding of metal.")
- dismantle_wall()
- return TRUE
return FALSE
/turf/closed/wall/r_wall/try_decon(obj/item/W, mob/user, turf/T)
@@ -70,140 +61,140 @@
W.play_tool_sound(src, 100)
d_state = SUPPORT_LINES
update_icon()
- to_chat(user, "You cut the outer grille.")
- return 1
+ balloon_alert(user, "Outer grille cut")
+ return TRUE
if(SUPPORT_LINES)
if(W.tool_behaviour == TOOL_SCREWDRIVER)
- to_chat(user, "You begin unsecuring the support lines...")
+ balloon_alert(user, "You begin unsecuring the support lines")
if(W.use_tool(src, user, 40, volume=100))
if(!istype(src, /turf/closed/wall/r_wall) || d_state != SUPPORT_LINES)
- return 1
+ return TRUE
d_state = COVER
update_icon()
- to_chat(user, "You unsecure the support lines.")
- return 1
+ balloon_alert(user, "Support lines unsecured")
+ return TRUE
else if(W.tool_behaviour == TOOL_WIRECUTTER)
W.play_tool_sound(src, 100)
d_state = INTACT
update_icon()
- to_chat(user, "You repair the outer grille.")
- return 1
+ balloon_alert(user, "Outer grille repaired")
+ return TRUE
if(COVER)
if(W.tool_behaviour == TOOL_WELDER)
if(!W.tool_start_check(user, amount=0))
return
- to_chat(user, "You begin slicing through the metal cover...")
+ balloon_alert(user, "You begin slicing through the metal cover")
if(W.use_tool(src, user, 60, volume=100))
if(!istype(src, /turf/closed/wall/r_wall) || d_state != COVER)
- return 1
+ return TRUE
d_state = CUT_COVER
update_icon()
- to_chat(user, "You press firmly on the cover, dislodging it.")
- return 1
+ balloon_alert(user, "Metal cover removed")
+ return TRUE
if(W.tool_behaviour == TOOL_SCREWDRIVER)
- to_chat(user, "You begin securing the support lines...")
+ balloon_alert(user, "You begin securing the support lines")
if(W.use_tool(src, user, 40, volume=100))
if(!istype(src, /turf/closed/wall/r_wall) || d_state != COVER)
- return 1
+ return TRUE
d_state = SUPPORT_LINES
update_icon()
- to_chat(user, "The support lines have been secured.")
- return 1
+ balloon_alert(user, "Support lines have been secured")
+ return TRUE
if(CUT_COVER)
if(W.tool_behaviour == TOOL_CROWBAR)
- to_chat(user, "You struggle to pry off the cover...")
+ balloon_alert(user, "You struggle to pry off the cover")
if(W.use_tool(src, user, 100, volume=100))
if(!istype(src, /turf/closed/wall/r_wall) || d_state != CUT_COVER)
- return 1
+ return TRUE
d_state = ANCHOR_BOLTS
update_icon()
- to_chat(user, "You pry off the cover.")
- return 1
+ balloon_alert(user, "Cover pried off")
+ return TRUE
if(W.tool_behaviour == TOOL_WELDER)
if(!W.tool_start_check(user, amount=0))
return
- to_chat(user, "You begin welding the metal cover back to the frame...")
+ balloon_alert(user, "You begin welding the metal cover back to the frame")
if(W.use_tool(src, user, 60, volume=100))
if(!istype(src, /turf/closed/wall/r_wall) || d_state != CUT_COVER)
return TRUE
d_state = COVER
update_icon()
- to_chat(user, "The metal cover has been welded securely to the frame.")
- return 1
+ balloon_alert(user, "Metal cover welded to the frame")
+ return TRUE
if(ANCHOR_BOLTS)
if(W.tool_behaviour == TOOL_WRENCH)
- to_chat(user, "You start loosening the anchoring bolts which secure the support rods to their frame...")
+ balloon_alert(user, "You start loosening the anchoring bolts which secure the support rods to their frame")
if(W.use_tool(src, user, 40, volume=100))
if(!istype(src, /turf/closed/wall/r_wall) || d_state != ANCHOR_BOLTS)
- return 1
+ return TRUE
d_state = SUPPORT_RODS
update_icon()
- to_chat(user, "You remove the bolts anchoring the support rods.")
- return 1
+ balloon_alert(user, "Bolts removed")
+ return TRUE
if(W.tool_behaviour == TOOL_CROWBAR)
- to_chat(user, "You start to pry the cover back into place...")
+ balloon_alert(user, "You start to pry the cover back into place")
if(W.use_tool(src, user, 20, volume=100))
if(!istype(src, /turf/closed/wall/r_wall) || d_state != ANCHOR_BOLTS)
- return 1
+ return TRUE
d_state = CUT_COVER
update_icon()
- to_chat(user, "The metal cover has been pried back into place.")
- return 1
+ balloon_alert(user, "The metal cover pried back into place")
+ return TRUE
if(SUPPORT_RODS)
if(W.tool_behaviour == TOOL_WELDER)
if(!W.tool_start_check(user, amount=0))
return
- to_chat(user, "You begin slicing through the support rods...")
+ balloon_alert(user, "You start slicing through the support rods")
if(W.use_tool(src, user, 100, volume=100))
if(!istype(src, /turf/closed/wall/r_wall) || d_state != SUPPORT_RODS)
- return 1
+ return TRUE
d_state = SHEATH
update_icon()
- to_chat(user, "You slice through the support rods.")
- return 1
+ balloon_alert(user, "Support rods sliced through")
+ return TRUE
if(W.tool_behaviour == TOOL_WRENCH)
- to_chat(user, "You start tightening the bolts which secure the support rods to their frame...")
+ balloon_alert(user, "You start tightening the bolts securing support rods")
W.play_tool_sound(src, 100)
if(W.use_tool(src, user, 40))
if(!istype(src, /turf/closed/wall/r_wall) || d_state != SUPPORT_RODS)
- return 1
+ return TRUE
d_state = ANCHOR_BOLTS
update_icon()
- to_chat(user, "You tighten the bolts anchoring the support rods.")
- return 1
+ balloon_alert(user, "Bolts tightened")
+ return TRUE
if(SHEATH)
if(W.tool_behaviour == TOOL_CROWBAR)
- to_chat(user, "You struggle to pry off the outer sheath...")
+ balloon_alert(user, "You start prying off the outer sheath")
if(W.use_tool(src, user, 100, volume=100))
if(!istype(src, /turf/closed/wall/r_wall) || d_state != SHEATH)
- return 1
- to_chat(user, "You pry off the outer sheath.")
+ return TRUE
+ balloon_alert(user, "Outer sheath pried off")
dismantle_wall()
- return 1
+ return TRUE
if(W.tool_behaviour == TOOL_WELDER)
if(!W.tool_start_check(user, amount=0))
return
- to_chat(user, "You begin welding the support rods back together...")
+ balloon_alert(user, "You start welding the support rods back together")
if(W.use_tool(src, user, 100, volume=100))
if(!istype(src, /turf/closed/wall/r_wall) || d_state != SHEATH)
return TRUE
d_state = SUPPORT_RODS
update_icon()
- to_chat(user, "You weld the support rods back together.")
- return 1
- return 0
+ balloon_alert(user, "Support rods welded back together")
+ return TRUE
+ return FALSE
/turf/closed/wall/r_wall/update_icon()
. = ..()
diff --git a/code/game/turfs/closed/walls.dm b/code/game/turfs/closed/walls.dm
index 978a7cded7e1b..2b7d523d96c76 100644
--- a/code/game/turfs/closed/walls.dm
+++ b/code/game/turfs/closed/walls.dm
@@ -12,6 +12,8 @@
baseturfs = /turf/open/floor/plating
+ flags_ricochet = RICOCHET_HARD
+
FASTDMM_PROP(\
pipe_astar_cost = 35\
)
@@ -35,6 +37,16 @@
var/list/dent_decals
+/turf/closed/wall/Initialize(mapload)
+ . = ..()
+ if(is_station_level(z))
+ GLOB.station_turfs += src
+
+/turf/closed/wall/Destroy()
+ if(is_station_level(z))
+ GLOB.station_turfs -= src
+ return ..()
+
/turf/closed/wall/examine(mob/user)
. += ..()
. += deconstruction_hints(user)
@@ -45,17 +57,6 @@
/turf/closed/wall/attack_tk()
return
-/turf/closed/wall/handle_ricochet(obj/item/projectile/P) //A huge pile of shitcode!
- var/turf/p_turf = get_turf(P)
- var/face_direction = get_dir(src, p_turf)
- var/face_angle = dir2angle(face_direction)
- var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (P.Angle + 180))
- if(abs(incidence_s) > 90 && abs(incidence_s) < 270)
- return FALSE
- var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s)
- P.setAngle(new_angle_s)
- return TRUE
-
/turf/closed/wall/proc/dismantle_wall(devastated=0, explode=0)
if(devastated)
devastate_wall()
@@ -192,10 +193,10 @@
if(!W.tool_start_check(user, amount=0))
return FALSE
- to_chat(user, "You begin fixing dents on the wall...")
+ balloon_alert(user, "You begin fixing dents on the wall")
if(W.use_tool(src, user, 0, volume=100))
if(iswallturf(src) && LAZYLEN(dent_decals))
- to_chat(user, "You fix some dents on the wall.")
+ balloon_alert(user, "Some dents on the wall were fixed")
cut_overlay(dent_decals)
dent_decals.Cut()
return TRUE
@@ -213,10 +214,6 @@
else if(istype(W, /obj/item/poster))
place_poster(W,user)
return TRUE
- else if(istype(W, /obj/item/electronic_assembly/wallmount)) // circuit wallmount
- var/obj/item/electronic_assembly/wallmount/A = W
- A.mount_assembly(src, user)
- return TRUE
return FALSE
@@ -225,10 +222,10 @@
if(!I.tool_start_check(user, amount=0))
return FALSE
- to_chat(user, "You begin slicing through the outer plating...")
+ balloon_alert(user, "You start slicing through outer plating")
if(I.use_tool(src, user, slicing_duration, volume=100))
if(iswallturf(src))
- to_chat(user, "You remove the outer plating.")
+ balloon_alert(user, "Outer plating removed")
dismantle_wall()
return TRUE
diff --git a/code/game/turfs/open/_open.dm b/code/game/turfs/open/_open.dm
index a2463bae1b464..c9b86102a443d 100644
--- a/code/game/turfs/open/_open.dm
+++ b/code/game/turfs/open/_open.dm
@@ -126,10 +126,11 @@
CanAtmosPass = ATMOS_PASS_NO
baseturfs = /turf/open/indestructible/binary
icon_state = "binary"
- footstep = null
+ footstep = FOOTSTEP_PLATING
barefootstep = null
clawfootstep = null
heavyfootstep = null
+ slowdown = 3
/turf/open/indestructible/airblock
icon_state = "bluespace"
@@ -137,18 +138,16 @@
baseturfs = /turf/open/indestructible/airblock
/turf/open/Initalize_Atmos(times_fired)
- set_excited(FALSE)
+ if(!blocks_air)
+ if(!istype(air,/datum/gas_mixture/turf))
+ air = new(2500,src)
+ air.copy_from_turf(src)
+ update_air_ref(planetary_atmos ? 1 : 2)
+
update_visuals()
- current_cycle = times_fired
ImmediateCalculateAdjacentTurfs()
- for(var/i in atmos_adjacent_turfs)
- var/turf/open/enemy_tile = i
- var/datum/gas_mixture/enemy_air = enemy_tile.return_air()
- if(!get_excited() && air.compare(enemy_air))
- //testing("Active turf found. Return value of compare(): [is_active]")
- set_excited(TRUE)
- SSair.active_turfs |= src
+
/turf/open/proc/GetHeatCapacity()
. = air.heat_capacity()
@@ -194,7 +193,7 @@
if(!(lube&GALOSHES_DONT_HELP)) //can't slip while buckled unless it's lube.
return 0
else
- if(!(C.mobility_flags & MOBILITY_STAND) || !(C.status_flags & CANKNOCKDOWN)) // can't slip unbuckled mob if they're lying or can't fall.
+ if(!(lube & SLIP_WHEN_CRAWLING) && (!(C.mobility_flags & MOBILITY_STAND) || !(C.status_flags & CANKNOCKDOWN))) // can't slip unbuckled mob if they're lying or can't fall.
return 0
if(C.m_intent == MOVE_INTENT_WALK && (lube&NO_SLIP_WHEN_WALKING))
return 0
@@ -244,8 +243,8 @@
/turf/open/rad_act(pulse_strength)
. = ..()
- if (air.get_moles(/datum/gas/carbon_dioxide) && air.get_moles(/datum/gas/oxygen))
- pulse_strength = min(pulse_strength,air.get_moles(/datum/gas/carbon_dioxide)*1000,air.get_moles(/datum/gas/oxygen)*2000) //Ensures matter is conserved properly
- air.set_moles(/datum/gas/carbon_dioxide, max(air.get_moles(/datum/gas/carbon_dioxide)-(pulse_strength/1000),0))
- air.set_moles(/datum/gas/oxygen, max(air.get_moles(/datum/gas/oxygen)-(pulse_strength/2000),0))
- air.adjust_moles(/datum/gas/pluoxium, pulse_strength/4000)
+ if (air.get_moles(GAS_CO2) && air.get_moles(GAS_O2))
+ pulse_strength = min(pulse_strength,air.get_moles(GAS_CO2)*1000,air.get_moles(GAS_O2)*2000) //Ensures matter is conserved properly
+ air.set_moles(GAS_CO2, max(air.get_moles(GAS_CO2)-(pulse_strength/1000),0))
+ air.set_moles(GAS_O2, max(air.get_moles(GAS_O2)-(pulse_strength/2000),0))
+ air.adjust_moles(GAS_PLUOXIUM, pulse_strength/4000)
diff --git a/code/game/turfs/open/floor.dm b/code/game/turfs/open/floor.dm
index 5b523e1ff6c25..264079ad9afe5 100644
--- a/code/game/turfs/open/floor.dm
+++ b/code/game/turfs/open/floor.dm
@@ -13,7 +13,7 @@
var/icon_regular_floor = "floor" //used to remember what icon the tile should have by default
var/icon_plating = "plating"
- thermal_conductivity = 0.040
+ thermal_conductivity = 0.04
heat_capacity = 10000
intact = 1
var/broken = 0
@@ -56,6 +56,13 @@
icon_regular_floor = icon_state
if(mapload && prob(33))
MakeDirty()
+ if(is_station_level(z))
+ GLOB.station_turfs += src
+
+/turf/open/floor/Destroy()
+ if(is_station_level(z))
+ GLOB.station_turfs -= src
+ return ..()
/turf/open/floor/ex_act(severity, target)
var/shielded = is_shielded()
@@ -166,7 +173,7 @@
/turf/open/floor/proc/try_replace_tile(obj/item/stack/tile/T, mob/user, params)
if(T.turf_type == type)
return
- var/obj/item/crowbar/CB = user.is_holding_item_of_type(/obj/item/crowbar)
+ var/obj/item/CB = user.is_holding_tool_quality(TOOL_CROWBAR)
if(!CB)
return
var/turf/open/floor/plating/P = pry_tile(CB, user, TRUE)
@@ -261,18 +268,19 @@
return FALSE
to_chat(user, "You build an airlock.")
var/obj/machinery/door/airlock/A = new the_rcd.airlock_type(src)
-
- A.electronics = new/obj/item/electronics/airlock(A)
-
- if(the_rcd.conf_access)
- A.electronics.accesses = the_rcd.conf_access.Copy()
- A.electronics.one_access = the_rcd.use_one_access
-
+ A.electronics = new /obj/item/electronics/airlock(A)
+ if(the_rcd.airlock_electronics)
+ A.electronics.accesses = the_rcd.airlock_electronics.accesses.Copy()
+ A.electronics.one_access = the_rcd.airlock_electronics.one_access
+ A.electronics.unres_sides = the_rcd.airlock_electronics.unres_sides
if(A.electronics.one_access)
A.req_one_access = A.electronics.accesses
else
A.req_access = A.electronics.accesses
+ if(A.electronics.unres_sides)
+ A.unres_sides = A.electronics.unres_sides
A.autoclose = TRUE
+ A.update_icon()
return TRUE
if(RCD_DECONSTRUCT)
if(ScrapeAway(flags = CHANGETURF_INHERIT_AIR) == src)
diff --git a/code/game/turfs/open/floor/fancy_floor.dm b/code/game/turfs/open/floor/fancy_floor.dm
index 259412b26d36b..81812a344527d 100644
--- a/code/game/turfs/open/floor/fancy_floor.dm
+++ b/code/game/turfs/open/floor/fancy_floor.dm
@@ -30,9 +30,9 @@
/turf/open/floor/wood/try_replace_tile(obj/item/stack/tile/T, mob/user, params)
if(T.turf_type == type)
return
- var/obj/item/tool = user.is_holding_item_of_type(/obj/item/screwdriver)
+ var/obj/item/tool = user.is_holding_tool_quality(TOOL_SCREWDRIVER)
if(!tool)
- tool = user.is_holding_item_of_type(/obj/item/crowbar)
+ tool = user.is_holding_tool_quality(TOOL_CROWBAR)
if(!tool)
return
var/turf/open/floor/plating/P = pry_tile(tool, user, TRUE)
@@ -62,7 +62,7 @@
return make_plating()
/turf/open/floor/wood/cold
- temperature = 255.37
+ initial_temperature = 255.37
/turf/open/floor/wood/airless
initial_gas_mix = AIRLESS_ATMOS
@@ -96,6 +96,56 @@
if(..())
return
+/turf/open/floor/grass/fairy //like grass but fae-er
+ name = "fairygrass patch"
+ desc = "Something about this grass makes you want to frolic. Or get high."
+ icon_state = "fairygrass"
+ floor_tile = /obj/item/stack/tile/fairygrass
+ light_range = 2
+ light_power = 0.80
+ light_color = "#33CCFF"
+ color = "#33CCFF"
+
+/turf/open/floor/grass/fairy/white
+ name = "white fairygrass patch"
+ floor_tile = /obj/item/stack/tile/fairygrass/white
+ light_color = "#FFFFFF"
+ color = "#FFFFFF"
+
+/turf/open/floor/grass/fairy/red
+ name = "red fairygrass patch"
+ floor_tile = /obj/item/stack/tile/fairygrass/red
+ light_color = "#FF3333"
+ color = "#FF3333"
+
+/turf/open/floor/grass/fairy/yellow
+ name = "yellow fairygrass patch"
+ floor_tile = /obj/item/stack/tile/fairygrass/yellow
+ light_color = "#FFFF66"
+ color = "#FFFF66"
+
+/turf/open/floor/grass/fairy/green
+ name = "green fairygrass patch"
+ floor_tile = /obj/item/stack/tile/fairygrass/green
+ light_color = "#99FF99"
+ color = "#99FF99"
+
+/turf/open/floor/grass/fairy/blue
+ floor_tile = /obj/item/stack/tile/fairygrass/blue
+ name = "blue fairygrass patch"
+
+/turf/open/floor/grass/fairy/purple
+ name = "purple fairygrass patch"
+ floor_tile = /obj/item/stack/tile/fairygrass/purple
+ light_color = "#D966FF"
+ color = "#D966FF"
+
+/turf/open/floor/grass/fairy/pink
+ name = "pink fairygrass patch"
+ floor_tile = /obj/item/stack/tile/fairygrass/pink
+ light_color = "#FFB3DA"
+ color = "#FFB3DA"
+
/turf/open/floor/grass/snow
gender = PLURAL
name = "snow"
@@ -300,3 +350,16 @@
underlay_appearance.plane = PLANE_SPACE
return TRUE
+/turf/open/floor/wax
+ name = "wax"
+ icon_state = "honeyfloor"
+ desc = "Hard wax. Makes you feel like part of a hive."
+ floor_tile = /obj/item/stack/tile/mineral/wax
+ footstep = FOOTSTEP_WOOD
+ barefootstep = FOOTSTEP_WOOD_BAREFOOT
+ clawfootstep = FOOTSTEP_WOOD_CLAW
+ heavyfootstep = FOOTSTEP_GENERIC_HEAVY
+ tiled_dirt = FALSE
+
+/turf/open/floor/wax/airless
+ initial_gas_mix = AIRLESS_ATMOS
\ No newline at end of file
diff --git a/code/game/turfs/open/floor/mineral_floor.dm b/code/game/turfs/open/floor/mineral_floor.dm
index df5dd1bd8032c..df2813221bceb 100644
--- a/code/game/turfs/open/floor/mineral_floor.dm
+++ b/code/game/turfs/open/floor/mineral_floor.dm
@@ -226,7 +226,7 @@
if(world.time > last_event+15)
active = 1
radiation_pulse(src, 10)
- for(var/turf/open/floor/mineral/uranium/T in orange(1,src))
+ for(var/turf/open/floor/mineral/uranium/T in (RANGE_TURFS(1,src)-src))
T.radiate()
last_event = world.time
active = 0
diff --git a/code/game/turfs/open/floor/plasteel_floor.dm b/code/game/turfs/open/floor/plasteel_floor.dm
index 465f3a60bb24a..ec18886b6083d 100644
--- a/code/game/turfs/open/floor/plasteel_floor.dm
+++ b/code/game/turfs/open/floor/plasteel_floor.dm
@@ -88,14 +88,6 @@
initial_gas_mix = AIRLESS_ATMOS
-/turf/open/floor/plasteel/kitchen_coldroom
- name = "cold room floor"
- initial_gas_mix = KITCHEN_COLDROOM_ATMOS
-
-/turf/open/floor/plasteel/kitchen_coldroom/freezerfloor
- icon_state = "freezerfloor"
-
-
/turf/open/floor/plasteel/grimy
icon_state = "grimy"
tiled_dirt = FALSE
diff --git a/code/game/turfs/open/floor/plating.dm b/code/game/turfs/open/floor/plating.dm
index de6b47d7dd304..ac26c75de5e71 100644
--- a/code/game/turfs/open/floor/plating.dm
+++ b/code/game/turfs/open/floor/plating.dm
@@ -28,7 +28,7 @@
. += "It looks like the dents could be welded smooth."
return
if(attachment_holes)
- . += "There are a few attachment holes for a new tile or reinforcement rods."
+ . += "There are a few attachment holes for a new tile, reinforcement sheets or catwalk rods."
else
. += "You might be able to build ontop of it with some tiles..."
diff --git a/code/game/turfs/open/floor/plating/asteroid.dm b/code/game/turfs/open/floor/plating/asteroid.dm
index 7452f168caa0b..ca1bdf8d1e0c0 100644
--- a/code/game/turfs/open/floor/plating/asteroid.dm
+++ b/code/game/turfs/open/floor/plating/asteroid.dm
@@ -129,180 +129,9 @@
baseturfs = /turf/open/floor/plating/asteroid/airless
turf_type = /turf/open/floor/plating/asteroid/airless
-
-#define SPAWN_MEGAFAUNA "bluh bluh huge boss"
-#define SPAWN_BUBBLEGUM 6
-
-/turf/open/floor/plating/asteroid/airless/cave
- var/length = 100
- var/list/mob_spawn_list
- var/list/megafauna_spawn_list
- var/list/flora_spawn_list
- var/list/terrain_spawn_list
- var/sanity = 1
- var/forward_cave_dir = 1
- var/backward_cave_dir = 2
- var/going_backwards = TRUE
- var/has_data = FALSE
- var/data_having_type = /turf/open/floor/plating/asteroid/airless/cave/has_data
- turf_type = /turf/open/floor/plating/asteroid/airless
-
-/turf/open/floor/plating/asteroid/airless/cave/has_data //subtype for producing a tunnel with given data
- has_data = TRUE
-
-/turf/open/floor/plating/asteroid/airless/cave/volcanic
- mob_spawn_list = list(/mob/living/simple_animal/hostile/asteroid/goliath/beast/random = 50, /obj/structure/spawner/lavaland/goliath = 3, \
- /mob/living/simple_animal/hostile/asteroid/basilisk/watcher/random = 40, /obj/structure/spawner/lavaland = 2, \
- /mob/living/simple_animal/hostile/asteroid/hivelord/legion/random = 30, /obj/structure/spawner/lavaland/legion = 3, \
- SPAWN_MEGAFAUNA = 6, /mob/living/simple_animal/hostile/asteroid/goldgrub = 10, )
-
- data_having_type = /turf/open/floor/plating/asteroid/airless/cave/volcanic/has_data
- turf_type = /turf/open/floor/plating/asteroid/basalt/lava_land_surface
- initial_gas_mix = LAVALAND_DEFAULT_ATMOS
-
-/turf/open/floor/plating/asteroid/airless/cave/volcanic/has_data //subtype for producing a tunnel with given data
- has_data = TRUE
-
-/turf/open/floor/plating/asteroid/airless/cave/Initialize()
- if (!mob_spawn_list)
- mob_spawn_list = list(/mob/living/simple_animal/hostile/asteroid/goldgrub = 1, /mob/living/simple_animal/hostile/asteroid/goliath = 5, /mob/living/simple_animal/hostile/asteroid/basilisk = 4, /mob/living/simple_animal/hostile/asteroid/hivelord = 3)
- if (!megafauna_spawn_list)
- megafauna_spawn_list = list(/mob/living/simple_animal/hostile/megafauna/dragon = 4, /mob/living/simple_animal/hostile/megafauna/colossus = 2, /mob/living/simple_animal/hostile/megafauna/bubblegum = SPAWN_BUBBLEGUM)
- if (!flora_spawn_list)
- flora_spawn_list = list(/obj/structure/flora/ash/leaf_shroom = 2 , /obj/structure/flora/ash/cap_shroom = 2 , /obj/structure/flora/ash/stem_shroom = 2 , /obj/structure/flora/ash/cacti = 1, /obj/structure/flora/ash/tall_shroom = 2)
- if(!terrain_spawn_list)
- terrain_spawn_list = list(/obj/structure/geyser/random = 1)
- . = ..()
- if(!has_data)
- produce_tunnel_from_data()
-
-/turf/open/floor/plating/asteroid/airless/cave/proc/get_cave_data(set_length, exclude_dir = -1)
- // If set_length (arg1) isn't defined, get a random length; otherwise assign our length to the length arg.
- if(!set_length)
- length = rand(25, 50)
- else
- length = set_length
-
- // Get our directiosn
- forward_cave_dir = pick(GLOB.alldirs - exclude_dir)
- // Get the opposite direction of our facing direction
- backward_cave_dir = angle2dir(dir2angle(forward_cave_dir) + 180)
-
-/turf/open/floor/plating/asteroid/airless/cave/proc/produce_tunnel_from_data(tunnel_length, excluded_dir = -1)
- get_cave_data(tunnel_length, excluded_dir)
- // Make our tunnels
- make_tunnel(forward_cave_dir)
- if(going_backwards)
- make_tunnel(backward_cave_dir)
- // Kill ourselves by replacing ourselves with a normal floor.
- SpawnFloor(src)
-
-/turf/open/floor/plating/asteroid/airless/cave/proc/make_tunnel(dir)
- var/turf/closed/mineral/tunnel = src
- var/next_angle = pick(45, -45)
-
- for(var/i = 0; i < length; i++)
- if(!sanity)
- break
-
- var/list/L = list(45)
- if(ISODD(dir2angle(dir))) // We're going at an angle and we want thick angled tunnels.
- L += -45
-
- // Expand the edges of our tunnel
- for(var/edge_angle in L)
- var/turf/closed/mineral/edge = get_step(tunnel, angle2dir(dir2angle(dir) + edge_angle))
- if(istype(edge))
- SpawnFloor(edge)
-
- if(!sanity)
- break
-
- // Move our tunnel forward
- tunnel = get_step(tunnel, dir)
-
- if(istype(tunnel))
- // Small chance to have forks in our tunnel; otherwise dig our tunnel.
- if(i > 3 && prob(20))
- if(istype(tunnel.loc, /area/mine/explored) || (istype(tunnel.loc, /area/lavaland/surface/outdoors) && !istype(tunnel.loc, /area/lavaland/surface/outdoors/unexplored)))
- sanity = 0
- break
- var/turf/open/floor/plating/asteroid/airless/cave/C = tunnel.ChangeTurf(data_having_type, null, CHANGETURF_IGNORE_AIR)
- C.going_backwards = FALSE
- C.produce_tunnel_from_data(rand(10, 15), dir)
- else
- SpawnFloor(tunnel)
- else //if(!istype(tunnel, parent)) // We hit space/normal/wall, stop our tunnel.
- break
-
- // Chance to change our direction left or right.
- if(i > 2 && prob(33))
- // We can't go a full loop though
- next_angle = -next_angle
- setDir(angle2dir(dir2angle(dir) )+ next_angle)
-
-
-/turf/open/floor/plating/asteroid/airless/cave/proc/SpawnFloor(turf/T)
- for(var/S in RANGE_TURFS(1, src))
- var/turf/NT = S
- if(!NT || isspaceturf(NT) || istype(NT.loc, /area/mine/explored) || (istype(NT.loc, /area/lavaland/surface/outdoors) && !istype(NT.loc, /area/lavaland/surface/outdoors/unexplored)))
- sanity = 0
- break
- if(!sanity)
- return
- SpawnFlora(T)
- // SpawnTerrain(T)
- SpawnMonster(T)
- T.ChangeTurf(turf_type, null, CHANGETURF_IGNORE_AIR)
-
-/turf/open/floor/plating/asteroid/airless/cave/proc/SpawnMonster(turf/T)
- if(prob(30))
- if(istype(loc, /area/mine/explored) || !istype(loc, /area/lavaland/surface/outdoors/unexplored))
- return
- var/randumb = pickweight(mob_spawn_list)
- while(randumb == SPAWN_MEGAFAUNA)
- if(istype(loc, /area/lavaland/surface/outdoors/unexplored/danger)) //this is danger. it's boss time.
- var/maybe_boss = pickweight(megafauna_spawn_list)
- if(megafauna_spawn_list[maybe_boss])
- randumb = maybe_boss
- if(ispath(maybe_boss, /mob/living/simple_animal/hostile/megafauna/bubblegum)) //there can be only one bubblegum, so don't waste spawns on it
- megafauna_spawn_list[maybe_boss] = 0
- else //this is not danger, don't spawn a boss, spawn something else
- randumb = pickweight(mob_spawn_list)
-
- for(var/mob/living/simple_animal/hostile/H in urange(12,T)) //prevents mob clumps
- if((ispath(randumb, /mob/living/simple_animal/hostile/megafauna) || ismegafauna(H)) && get_dist(src, H) <= 7)
- return //if there's a megafauna within standard view don't spawn anything at all
- if(ispath(randumb, /mob/living/simple_animal/hostile/asteroid) || istype(H, /mob/living/simple_animal/hostile/asteroid))
- return //if the random is a standard mob, avoid spawning if there's another one within 12 tiles
- if((ispath(randumb, /obj/structure/spawner/lavaland) || istype(H, /obj/structure/spawner/lavaland)) && get_dist(src, H) <= 2)
- return //prevents tendrils spawning in each other's collapse range
-
- new randumb(T)
- return
-
-#undef SPAWN_MEGAFAUNA
-#undef SPAWN_BUBBLEGUM
-
-/turf/open/floor/plating/asteroid/airless/cave/proc/SpawnFlora(turf/T)
- if(prob(12))
- if(istype(loc, /area/mine/explored) || istype(loc, /area/lavaland/surface/outdoors/explored))
- return
- var/randumb = pickweight(flora_spawn_list)
- for(var/obj/structure/flora/ash/F in range(4, T)) //Allows for growing patches, but not ridiculous stacks of flora
- if(!istype(F, randumb))
- return
- new randumb(T)
-
-/turf/open/floor/plating/asteroid/airless/cave/proc/SpawnTerrain(turf/T)
- if(prob(2))
- if(istype(loc, /area/mine/explored) || istype(loc, /area/lavaland/surface/outdoors/explored))
- return
- var/randumb = pickweight(terrain_spawn_list)
- for(var/obj/structure/geyser/F in range(7, T))
- if(istype(F, randumb))
- return
- new randumb(T)
+// / Breathing types. Lungs can access either by these or by a string, which will be considered a gas ID.
+#define BREATH_OXY /datum/breathing_class/oxygen
+#define BREATH_PLASMA /datum/breathing_class/plasma
/turf/open/floor/plating/asteroid/snow
gender = PLURAL
@@ -335,7 +164,7 @@
name = "icy snow"
desc = "Looks colder."
baseturfs = /turf/open/floor/plating/asteroid/snow/ice
- initial_gas_mix = "o2=0;n2=82;plasma=24;TEMP=120"
+ initial_gas_mix = "n2=82;plasma=24;TEMP=120"
floor_variance = 0
icon_state = "snow-ice"
icon_plating = "snow-ice"
diff --git a/code/game/turfs/open/floor/plating/dirt.dm b/code/game/turfs/open/floor/plating/dirt.dm
deleted file mode 100644
index 9cfbe8433b3f7..0000000000000
--- a/code/game/turfs/open/floor/plating/dirt.dm
+++ /dev/null
@@ -1,21 +0,0 @@
-/turf/open/floor/plating/dirt
- gender = PLURAL
- name = "dirt"
- desc = "Upon closer examination, it's still dirt."
- icon = 'icons/turf/floors.dmi'
- icon_state = "dirt"
- baseturfs = /turf/open/chasm/jungle
- initial_gas_mix = OPENTURF_LOW_PRESSURE
- planetary_atmos = TRUE
- attachment_holes = FALSE
- footstep = FOOTSTEP_SAND
- barefootstep = FOOTSTEP_SAND
- clawfootstep = FOOTSTEP_SAND
- heavyfootstep = FOOTSTEP_GENERIC_HEAVY
- tiled_dirt = FALSE
-
-/turf/open/floor/plating/dirt/dark
- icon_state = "greenerdirt"
-
-/turf/open/floor/plating/dirt/try_replace_tile(obj/item/stack/tile/T, mob/user, params)
- return
diff --git a/code/game/turfs/open/floor/plating/misc_plating.dm b/code/game/turfs/open/floor/plating/misc_plating.dm
index 85029c9ac3da2..e1025feb7e13d 100644
--- a/code/game/turfs/open/floor/plating/misc_plating.dm
+++ b/code/game/turfs/open/floor/plating/misc_plating.dm
@@ -3,6 +3,10 @@
icon_state = "plating"
initial_gas_mix = AIRLESS_ATMOS
+/turf/open/floor/plating/lavaland
+ icon_state = "plating"
+ initial_gas_mix = LAVALAND_DEFAULT_ATMOS
+
/turf/open/floor/plating/abductor
name = "alien floor"
icon_state = "alienpod1"
@@ -27,13 +31,6 @@
/turf/open/floor/plating/abductor2/try_replace_tile(obj/item/stack/tile/T, mob/user, params)
return
-/turf/open/floor/plating/astplate
- icon_state = "asteroidplating"
-
-/turf/open/floor/plating/airless/astplate
- icon_state = "asteroidplating"
-
-
/turf/open/floor/plating/ashplanet
icon = 'icons/turf/mining.dmi'
gender = PLURAL
@@ -173,7 +170,7 @@
icon = 'icons/turf/floors/ice_turf.dmi'
icon_state = "unsmooth"
initial_gas_mix = FROZEN_ATMOS
- temperature = 180
+ initial_temperature = 180
planetary_atmos = TRUE
baseturfs = /turf/open/floor/plating/ice
slowdown = 1
@@ -197,10 +194,10 @@
canSmoothWith = list(/turf/open/floor/plating/ice/smooth, /turf/open/floor/plating/ice, /turf/open/floor/plating/ice/colder)
/turf/open/floor/plating/ice/colder
- temperature = 140
+ initial_temperature = 140
/turf/open/floor/plating/ice/temperate
- temperature = 255.37
+ initial_temperature = 255.37
/turf/open/floor/plating/ice/break_tile()
return
@@ -215,7 +212,7 @@
icon = 'icons/turf/snow.dmi'
icon_state = "snowplating"
initial_gas_mix = FROZEN_ATMOS
- temperature = 180
+ initial_temperature = 180
attachment_holes = FALSE
planetary_atmos = TRUE
footstep = FOOTSTEP_SAND
@@ -224,7 +221,7 @@
heavyfootstep = FOOTSTEP_GENERIC_HEAVY
/turf/open/floor/plating/snowed/cavern
- initial_gas_mix = "o2=0;n2=82;plasma=24;TEMP=120"
+ initial_gas_mix = "n2=82;plasma=24;TEMP=120"
/turf/open/floor/plating/snowed/smoothed
smooth = SMOOTH_MORE | SMOOTH_BORDER
@@ -234,8 +231,8 @@
icon_state = "smooth"
/turf/open/floor/plating/snowed/colder
- temperature = 140
+ initial_temperature = 140
/turf/open/floor/plating/snowed/temperatre
- temperature = 255.37
+ initial_temperature = 255.37
diff --git a/code/game/turfs/open/floor/plating/planet.dm b/code/game/turfs/open/floor/plating/planet.dm
new file mode 100644
index 0000000000000..9f6c54a029f79
--- /dev/null
+++ b/code/game/turfs/open/floor/plating/planet.dm
@@ -0,0 +1,59 @@
+/turf/open/floor/plating/dirt
+ gender = PLURAL
+ name = "dirt"
+ desc = "Upon closer examination, it's still dirt."
+ icon = 'icons/turf/floors.dmi'
+ icon_state = "dirt"
+ baseturfs = /turf/open/chasm/jungle
+ initial_gas_mix = OPENTURF_LOW_PRESSURE
+ planetary_atmos = TRUE
+ attachment_holes = FALSE
+ footstep = FOOTSTEP_SAND
+ barefootstep = FOOTSTEP_SAND
+ clawfootstep = FOOTSTEP_SAND
+ heavyfootstep = FOOTSTEP_GENERIC_HEAVY
+ tiled_dirt = FALSE
+
+/turf/open/floor/plating/dirt/dark
+ icon_state = "greenerdirt"
+
+/turf/open/floor/plating/dirt/try_replace_tile(obj/item/stack/tile/T, mob/user, params)
+ return
+
+/turf/open/floor/plating/dirt/jungle
+ slowdown = 0.5
+ initial_gas_mix = OPENTURF_DEFAULT_ATMOS
+
+/turf/open/floor/plating/dirt/jungle/dark
+ icon_state = "greenerdirt"
+
+/turf/open/floor/plating/dirt/jungle/wasteland //Like a more fun version of living in Arizona.
+ name = "cracked earth"
+ desc = "Looks a bit dry."
+ icon = 'icons/turf/floors.dmi'
+ icon_state = "wasteland"
+ slowdown = 1
+ var/floor_variance = 15
+
+/turf/open/floor/plating/dirt/jungle/wasteland/Initialize()
+ .=..()
+ if(prob(floor_variance))
+ icon_state = "[initial(icon_state)][rand(0,12)]"
+
+/turf/open/floor/plating/grass/jungle
+ name = "jungle grass"
+ initial_gas_mix = OPENTURF_DEFAULT_ATMOS
+ planetary_atmos = TRUE
+ desc = "Greener on the other side."
+ icon = 'icons/turf/floors.dmi'
+ icon_state = "junglegrass"
+
+/turf/open/floor/plating/grass/jungle/Initialize()
+ .=..()
+ icon_state = "[initial(icon_state)][rand(1,3)]"
+
+/turf/closed/mineral/random/jungle
+ mineralSpawnChanceList = list(/obj/item/stack/ore/uranium = 5, /obj/item/stack/ore/diamond = 1, /obj/item/stack/ore/gold = 10,
+ /obj/item/stack/ore/silver = 12, /obj/item/stack/ore/plasma = 20, /obj/item/stack/ore/iron = 40, /obj/item/stack/ore/titanium = 11,
+ /obj/item/stack/ore/bluespace_crystal = 1)
+ baseturfs = /turf/open/floor/plating/dirt/dark
diff --git a/code/game/turfs/open/floor/reinf_floor.dm b/code/game/turfs/open/floor/reinf_floor.dm
index ce89591291fcb..d73bf2fd3b8cc 100644
--- a/code/game/turfs/open/floor/reinf_floor.dm
+++ b/code/game/turfs/open/floor/reinf_floor.dm
@@ -3,6 +3,7 @@
name = "reinforced floor"
desc = "Extremely sturdy."
icon_state = "engine"
+ holodeck_compatible = TRUE
thermal_conductivity = 0.025
heat_capacity = INFINITY
floor_tile = /obj/item/stack/sheet/iron
@@ -11,10 +12,14 @@
clawfootstep = FOOTSTEP_HARD_CLAW
heavyfootstep = FOOTSTEP_GENERIC_HEAVY
tiled_dirt = FALSE
+ FASTDMM_PROP(\
+ pipe_astar_cost = 15\
+ )
+
/turf/open/floor/engine/examine(mob/user)
- ..()
- to_chat(user, "The reinforcement plates are wrenched firmly in place.")
+ . = ..()
+ . += "The reinforcement plates are wrenched firmly in place."
/turf/open/floor/engine/light
icon_state = "engine_light"
@@ -153,6 +158,8 @@
icon_state = "plating"
floor_tile = null
var/obj/effect/clockwork/overlay/floor/bloodcult/realappearance
+ CanAtmosPass = ATMOS_PASS_NO
+ CanAtmosPassVertical = ATMOS_PASS_NO
/turf/open/floor/engine/cult/Initialize()
diff --git a/code/game/turfs/open/lava.dm b/code/game/turfs/open/lava.dm
index 8fbcf884cd8be..7568e38164781 100644
--- a/code/game/turfs/open/lava.dm
+++ b/code/game/turfs/open/lava.dm
@@ -37,6 +37,7 @@
initial_gas_mix = AIRLESS_ATMOS
/turf/open/lava/Entered(atom/movable/AM)
+ . = ..()
if(burn_stuff(AM))
START_PROCESSING(SSobj, src)
@@ -51,8 +52,8 @@
if(burn_stuff(AM))
START_PROCESSING(SSobj, src)
-/turf/open/lava/process()
- if(!burn_stuff())
+/turf/open/lava/process(delta_time)
+ if(!burn_stuff(null, delta_time))
STOP_PROCESSING(SSobj, src)
/turf/open/lava/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
@@ -99,7 +100,7 @@
return LAZYLEN(found_safeties)
-/turf/open/lava/proc/burn_stuff(AM)
+/turf/open/lava/proc/burn_stuff(AM, delta_time = 1)
. = 0
if(is_safe())
@@ -122,8 +123,11 @@
O.resistance_flags &= ~FIRE_PROOF
if(O.armor.fire > 50) //obj with 100% fire armor still get slowly burned away.
O.armor = O.armor.setRating(fire = 50)
- O.fire_act(10000, 1000)
-
+ O.fire_act(10000, 1000 * delta_time)
+ if(istype(O, /obj/structure/closet))
+ var/obj/structure/closet/C = O
+ for(var/I in C.contents)
+ burn_stuff(I)
else if (isliving(thing))
. = 1
var/mob/living/L = thing
@@ -146,18 +150,17 @@
if(iscarbon(L))
var/mob/living/carbon/C = L
- var/obj/item/clothing/S = C.get_item_by_slot(SLOT_WEAR_SUIT)
- var/obj/item/clothing/H = C.get_item_by_slot(SLOT_HEAD)
+ var/obj/item/clothing/S = C.get_item_by_slot(ITEM_SLOT_OCLOTHING)
+ var/obj/item/clothing/H = C.get_item_by_slot(ITEM_SLOT_HEAD)
if(S && H && S.clothing_flags & LAVAPROTECT && H.clothing_flags & LAVAPROTECT)
return
if("lava" in L.weather_immunities)
continue
-
- L.adjustFireLoss(20)
+ L.adjustFireLoss(20 * delta_time)
if(L) //mobs turning into object corpses could get deleted here.
- L.adjust_fire_stacks(20)
+ L.adjust_fire_stacks(20 * delta_time)
L.IgniteMob()
/turf/open/lava/smooth
diff --git a/code/game/turfs/open/openspace.dm b/code/game/turfs/open/openspace.dm
index 0b440cd9b3a32..16b2e63da0883 100644
--- a/code/game/turfs/open/openspace.dm
+++ b/code/game/turfs/open/openspace.dm
@@ -17,6 +17,12 @@ GLOBAL_DATUM_INIT(openspace_backdrop_one_for_all, /atom/movable/openspace_backdr
icon_state = "transparent"
baseturfs = /turf/open/openspace
CanAtmosPassVertical = ATMOS_PASS_YES
+ allow_z_travel = TRUE
+
+ FASTDMM_PROP(\
+ pipe_astar_cost = 100\
+ )
+
//mouse_opacity = MOUSE_OPACITY_TRANSPARENT
var/can_cover_up = TRUE
var/can_build_on = TRUE
@@ -82,6 +88,9 @@ GLOBAL_DATUM_INIT(openspace_backdrop_one_for_all, /atom/movable/openspace_backdr
return TRUE
/turf/open/openspace/zPassOut(atom/movable/A, direction, turf/destination)
+ //Check if our fall location has gravity
+ if(!A.has_gravity(destination))
+ return FALSE
if(A.anchored)
return FALSE
for(var/obj/O in contents)
@@ -157,3 +166,12 @@ GLOBAL_DATUM_INIT(openspace_backdrop_one_for_all, /atom/movable/openspace_backdr
PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
return TRUE
return FALSE
+
+//Returns FALSE if gravity is force disabled. True if grav is possible
+/turf/open/openspace/check_gravity()
+ var/turf/T = below()
+ if(!T)
+ return TRUE
+ if(isspaceturf(T))
+ return FALSE
+ return TRUE
diff --git a/code/game/turfs/open/reebe_void.dm b/code/game/turfs/open/reebe_void.dm
index 14aa02065d546..7db6f36b7d70a 100644
--- a/code/game/turfs/open/reebe_void.dm
+++ b/code/game/turfs/open/reebe_void.dm
@@ -6,6 +6,7 @@
planetary_atmos = TRUE
bullet_bounce_sound = null //forever falling
tiled_dirt = FALSE
+ flags_1 = NOJAUNT_1
/turf/open/indestructible/reebe_void/Initialize(mapload)
. = ..()
diff --git a/code/game/turfs/open/river.dm b/code/game/turfs/open/river.dm
index adc6b57960de8..f7a38e2168e25 100644
--- a/code/game/turfs/open/river.dm
+++ b/code/game/turfs/open/river.dm
@@ -72,8 +72,7 @@
var/list/cardinal_turfs = list()
var/list/diagonal_turfs = list()
var/logged_turf_type
- for(var/F in RANGE_TURFS(1, src) - src)
- var/turf/T = F
+ for(var/turf/T as() in RANGE_TURFS(1, src) - src)
var/area/new_area = get_area(T)
if(!T || (T.density && !ismineralturf(T)) || istype(T, /turf/open/indestructible) || (whitelisted_area && !istype(new_area, whitelisted_area)) || (T.flags_1 & NO_LAVA_GEN_1) )
continue
@@ -82,18 +81,16 @@
var/turf/closed/mineral/M = T
logged_turf_type = M.turf_type
- if(get_dir(src, F) in GLOB.cardinals)
- cardinal_turfs += F
+ if(get_dir(src, T) in GLOB.cardinals)
+ cardinal_turfs += T
else
- diagonal_turfs += F
+ diagonal_turfs += T
- for(var/F in cardinal_turfs) //cardinal turfs are always changed but don't always spread
- var/turf/T = F
+ for(var/turf/T as() in cardinal_turfs) //cardinal turfs are always changed but don't always spread
if(!istype(T, logged_turf_type) && T.ChangeTurf(type, baseturfs, CHANGETURF_IGNORE_AIR) && prob(probability))
T.Spread(probability - prob_loss, prob_loss, whitelisted_area)
- for(var/F in diagonal_turfs) //diagonal turfs only sometimes change, but will always spread if changed
- var/turf/T = F
+ for(var/turf/T as() in diagonal_turfs) //diagonal turfs only sometimes change, but will always spread if changed
if(!istype(T, logged_turf_type) && prob(probability) && T.ChangeTurf(type, baseturfs, CHANGETURF_IGNORE_AIR))
T.Spread(probability - prob_loss, prob_loss, whitelisted_area)
else if(ismineralturf(T))
diff --git a/code/game/turfs/open/space/space.dm b/code/game/turfs/open/space/space.dm
index d43716b948532..c259b96f3bc68 100644
--- a/code/game/turfs/open/space/space.dm
+++ b/code/game/turfs/open/space/space.dm
@@ -5,18 +5,20 @@
intact = 0
FASTDMM_PROP(\
- pipe_astar_cost = 4\
+ pipe_astar_cost = 100\
)
- temperature = TCMB
- thermal_conductivity = OPEN_HEAT_TRANSFER_COEFFICIENT
+ allow_z_travel = TRUE
+
+ initial_temperature = TCMB
+ thermal_conductivity = 0
heat_capacity = 700000
var/destination_z
var/destination_x
var/destination_y
- var/static/datum/gas_mixture/immutable/space/space_gas = new
+ var/static/datum/gas_mixture/immutable/space/space_gas
plane = PLANE_SPACE
layer = SPACE_LAYER
light_power = 0.25
@@ -31,8 +33,10 @@
/turf/open/space/Initialize()
icon_state = SPACE_ICON_STATE
+ if(!space_gas)
+ space_gas = new
air = space_gas
- update_air_ref()
+ update_air_ref(0)
vis_contents.Cut() //removes inherited overlays
visibilityChanged()
@@ -44,9 +48,6 @@
if(!IS_DYNAMIC_LIGHTING(src) && IS_DYNAMIC_LIGHTING(A))
add_overlay(/obj/effect/fullbright)
- if(requires_activation)
- SSair.add_to_active(src)
-
if (light_power && light_range)
update_light()
@@ -78,6 +79,13 @@
/turf/open/space/Assimilate_Air()
return
+//IT SHOULD RETURN NULL YOU MONKEY, WHY IN TARNATION WHAT THE FUCKING FUCK
+/turf/open/space/remove_air(amount)
+ return null
+
+/turf/open/space/remove_air_ratio(amount)
+ return null
+
/turf/open/space/proc/update_starlight()
if(CONFIG_GET(flag/starlight))
for(var/t in RANGE_TURFS(1,src)) //RANGE_TURFS is in code\__HELPERS\game.dm
@@ -173,6 +181,7 @@
//now we're on the new z_level, proceed the space drifting
stoplag()//Let a diagonal move finish, if necessary
A.newtonian_move(A.inertia_dir)
+ A.inertia_moving = TRUE
/turf/open/space/MakeSlippery(wet_setting, min_wet_time, wet_time_to_add, max_wet_time, permanent)
@@ -230,3 +239,10 @@
destination_x = dest_x
destination_y = dest_y
destination_z = dest_z
+
+//If someone is floating above space in 0 gravity, don't fall.
+/turf/open/space/zPassIn(atom/movable/A, direction, turf/source)
+ return A.has_gravity(src)
+
+/turf/open/space/check_gravity()
+ return FALSE
diff --git a/code/game/turfs/open/space/transit.dm b/code/game/turfs/open/space/transit.dm
index 1deb5303d3080..7ede8d0d1a963 100644
--- a/code/game/turfs/open/space/transit.dm
+++ b/code/game/turfs/open/space/transit.dm
@@ -29,23 +29,55 @@
/turf/open/space/transit/Entered(atom/movable/AM, atom/OldLoc)
..()
if(!locate(/obj/structure/lattice) in src)
- throw_atom(AM)
+ throw_atom(AM, OldLoc)
-/turf/open/space/transit/proc/throw_atom(atom/movable/AM)
+/turf/open/space/transit/proc/throw_atom(atom/movable/AM, atom/OldLoc)
set waitfor = FALSE
if(!AM || istype(AM, /obj/docking_port))
return
if(AM.loc != src) // Multi-tile objects are "in" multiple locs but its loc is it's true placement.
- return // Don't move multi tile objects if their origin isnt in transit
+ return // Don't move multi tile objects if their origin isn't in transit
var/max = world.maxx-TRANSITIONEDGE
var/min = 1+TRANSITIONEDGE
- var/list/possible_transtitons = list()
- for(var/A in SSmapping.z_list)
- var/datum/space_level/D = A
- if (D.linkage == CROSSLINKED)
- possible_transtitons += D.z_value
- var/_z = pick(possible_transtitons)
+ //Find our location
+ var/_z = 2
+
+ var/should_make_level = ismob(AM)
+ if(!should_make_level && isobj(AM))
+ var/obj/O = AM
+ if(O.resistance_flags & INDESTRUCTIBLE)
+ should_make_level = TRUE
+
+ if(should_make_level)
+ //Check if we are on a shuttle
+ var/turf/oldTurf = get_turf(OldLoc)
+ var/area/shuttle/shuttleArea = get_area(oldTurf)
+ if(istype(shuttleArea))
+ var/shuttleId = shuttleArea.mobile_port?.id || "null"
+ //Find the shuttle object
+ var/datum/orbital_object/shuttle/shuttleObj = SSorbits.assoc_shuttles[shuttleId]
+ if(shuttleObj)
+ if(length(shuttleObj.can_dock_with?.linked_z_level))
+ _z = shuttleObj.can_dock_with.linked_z_level[1].z_value
+ else if(length(shuttleObj.docking_target?.linked_z_level))
+ _z = shuttleObj.docking_target.linked_z_level[1].z_value
+ else
+ //Interdiction (Its an empty z-level)
+ var/datum/orbital_object/z_linked/beacon/ruin/z_linked = new /datum/orbital_object/z_linked/beacon/ruin/interdiction(
+ new /datum/orbital_vector(shuttleObj.position.x, shuttleObj.position.y)
+ )
+ z_linked.name = "Stranded [AM]"
+ z_linked.assign_z_level()
+ if(length(z_linked.linked_z_level))
+ _z = z_linked.linked_z_level[1].z_value
+ if(_z == 2)
+ //Chuck them at the space level
+ for(var/A in SSmapping.z_list)
+ var/datum/space_level/D = A
+ if (D.linkage == CROSSLINKED)
+ _z = D.z_value
+ break
//now select coordinates for a border turf
var/_x
@@ -76,7 +108,7 @@
. = ..()
update_icon()
for(var/atom/movable/AM in src)
- throw_atom(AM)
+ throw_atom(AM, src)
/turf/open/space/transit/update_icon()
. = ..()
diff --git a/code/game/turfs/open/water.dm b/code/game/turfs/open/water.dm
index 957fb3df19bdd..b76f07c41a2bd 100644
--- a/code/game/turfs/open/water.dm
+++ b/code/game/turfs/open/water.dm
@@ -15,3 +15,10 @@
barefootstep = FOOTSTEP_WATER
clawfootstep = FOOTSTEP_WATER
heavyfootstep = FOOTSTEP_WATER
+
+/turf/open/water/air
+ initial_gas_mix = OPENTURF_DEFAULT_ATMOS
+ planetary_atmos = FALSE
+
+/turf/open/water/jungle
+ initial_gas_mix = OPENTURF_DEFAULT_ATMOS
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index cf79aa4109781..6d179eee686c3 100755
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -1,3 +1,4 @@
+GLOBAL_LIST_EMPTY(station_turfs)
/turf
icon = 'icons/turf/floors.dmi'
level = 1
@@ -16,12 +17,15 @@
var/list/baseturfs = /turf/baseturf_bottom
/// How hot the turf is, in kelvin
- var/temperature = T20C
+ var/initial_temperature = T20C
/// Used for fire, if a melting temperature was reached, it will be destroyed
var/to_be_destroyed = 0
var/max_fire_temperature_sustained = 0 //The max temperature of the fire which it was subjected to
+ //If true, turf will allow users to float up and down in 0 grav.
+ var/allow_z_travel = FALSE
+
/// Whether the turf blocks atmos from passing through it or not
var/blocks_air = FALSE
@@ -32,6 +36,7 @@
var/explosion_level = 0 //for preventing explosion dodging
var/explosion_id = 0
+ var/list/explosion_throw_details
var/requires_activation //add to air processing after initialize?
var/changing_turf = FALSE
@@ -46,6 +51,9 @@
vis_flags = VIS_INHERIT_PLANE|VIS_INHERIT_ID //when this be added to vis_contents of something it inherit something.plane and be associated with something on clicking, important for visualisation of turf in openspace and interraction with openspace that show you turf.
+ ///the holodeck can load onto this turf if TRUE
+ var/holodeck_compatible = FALSE
+
/turf/vv_edit_var(var_name, new_value)
var/static/list/banned_edits = list("x", "y", "z")
if(var_name in banned_edits)
@@ -76,8 +84,10 @@
if(requires_activation)
CALCULATE_ADJACENT_TURFS(src)
- SSair.add_to_active(src)
+ if(color)
+ add_atom_colour(color, FIXED_COLOUR_PRIORITY)
+
if (light_power && light_range)
update_light()
@@ -94,9 +104,21 @@
has_opaque_atom = TRUE
ComponentInitialize()
+ if(isopenturf(src))
+ var/turf/open/O = src
+ __auxtools_update_turf_temp_info(isspaceturf(get_z_base_turf()) && !O.planetary_atmos)
+ else
+ update_air_ref(-1)
+ __auxtools_update_turf_temp_info(isspaceturf(get_z_base_turf()))
return INITIALIZE_HINT_NORMAL
+/turf/proc/__auxtools_update_turf_temp_info()
+
+/turf/return_temperature()
+
+/turf/proc/set_temperature()
+
/turf/proc/Initalize_Atmos(times_fired)
CALCULATE_ADJACENT_TURFS(src)
@@ -120,19 +142,75 @@
for(var/I in B.vars)
B.vars[I] = null
return
- SSair.remove_from_active(src)
visibilityChanged()
QDEL_LIST(blueprint_data)
flags_1 &= ~INITIALIZED_1
requires_activation = FALSE
..()
+ vis_contents.Cut()
+
/turf/attack_hand(mob/user)
+ //Must have no gravity.
+ if(allow_z_travel && get_turf(user) == src)
+ if(!user.has_gravity(src) || (user.movement_type & FLYING))
+ check_z_travel(user)
+ return
+ else
+ to_chat(user, "You can't float up and down when there is gravity!")
. = ..()
+ if(SEND_SIGNAL(user, COMSIG_MOB_ATTACK_HAND_TURF, src) & COMPONENT_NO_ATTACK_HAND)
+ . = TRUE
if(.)
return
user.Move_Pulled(src)
+/turf/proc/check_z_travel(mob/user)
+ if(get_turf(user) != src)
+ return
+ var/list/tool_list = list()
+ var/turf/above = above()
+ if(above)
+ tool_list["Up"] = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH)
+ var/turf/below = below()
+ if(below)
+ tool_list["Down"] = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH)
+
+ if(!length(tool_list))
+ return
+
+ var/result = show_radial_menu(user, user, tool_list, require_near = TRUE, tooltips = TRUE)
+ if(get_turf(user) != src)
+ return
+ switch(result)
+ if("Cancel")
+ return
+ if("Up")
+ travel_z(user, above, TRUE)
+ if("Down")
+ travel_z(user, below, FALSE)
+
+/turf/proc/travel_z(mob/user, turf/target, upwards = TRUE)
+ user.visible_message("[user] begins floating upwards!", "You begin floating upwards.")
+ var/matrix/M = user.transform
+ //Animation is inverted due to immediately resetting user vars.
+ animate(user, 30, pixel_y = upwards ? -64 : 64, transform = matrix() * (upwards ? 0.7 : 1.3))
+ user.pixel_y = 0
+ user.transform = M
+ if(!do_after(user, 30, FALSE, get_turf(user)))
+ animate(user, 0, flags = ANIMATION_END_NOW)
+ return
+ if(!istype(target, /turf/open/space) && !istype(target, /turf/open/openspace))
+ to_chat(user, "Something is blocking you!")
+ return
+ var/atom/movable/AM
+ if(user.pulling)
+ AM = user.pulling
+ AM.forceMove(target)
+ user.forceMove(target)
+ if(AM)
+ user.start_pulling(AM)
+
/turf/proc/multiz_turf_del(turf/T, dir)
/turf/proc/multiz_turf_new(turf/T, dir)
@@ -182,12 +260,20 @@
if(!force && (!can_zFall(A, levels, target) || !A.can_zFall(src, levels, target, DOWN)))
return FALSE
A.zfalling = TRUE
+ var/atom/movable/pulling = A.pulling
A.forceMove(target)
A.zfalling = FALSE
+ if(pulling)
+ //Things you are pulling fall with you
+ pulling.zfalling = TRUE
+ pulling.forceMove(target)
+ A.start_pulling(pulling)
+ pulling.zfalling = FALSE
+ target.zImpact(pulling, levels, src)
target.zImpact(A, levels, src)
return TRUE
-/turf/proc/handleRCL(obj/item/twohanded/rcl/C, mob/user)
+/turf/proc/handleRCL(obj/item/rcl/C, mob/user)
if(C.loaded)
for(var/obj/structure/cable/LC in src)
if(!LC.d1 || !LC.d2)
@@ -210,7 +296,7 @@
coil.place_turf(src, user)
return TRUE
- else if(istype(C, /obj/item/twohanded/rcl))
+ else if(istype(C, /obj/item/rcl))
handleRCL(C, user)
return FALSE
@@ -276,9 +362,6 @@
/turf/Entered(atom/movable/AM)
..()
- if(explosion_level && AM.ex_check(explosion_id))
- AM.ex_act(explosion_level)
-
// If an opaque movable atom moves around we need to potentially update visibility.
if (AM.opacity)
has_opaque_atom = TRUE // Make sure to do this before reconsider_lights(), incase we're on instant updates. Guaranteed to be on in this case.
@@ -380,7 +463,7 @@
if(.)
return
if(length(src_object.contents()))
- to_chat(usr, "You start dumping out the contents...")
+ balloon_alert(usr, "You dump out the contents")
if(!do_after(usr,20,target=src_object.parent))
return FALSE
@@ -452,15 +535,20 @@
else
affecting_level = 1
- for(var/V in contents)
- var/atom/A = V
- if(!QDELETED(A) && A.level >= affecting_level)
- if(ismovableatom(A))
- var/atom/movable/AM = A
- if(!AM.ex_check(explosion_id))
+ for(var/thing in contents)
+ var/atom/atom_thing = thing
+ if(!QDELETED(atom_thing) && atom_thing.level >= affecting_level)
+ if(ismovableatom(atom_thing))
+ var/atom/movable/movable_thing = atom_thing
+ if(!movable_thing.ex_check(explosion_id))
continue
- A.ex_act(severity, target)
- CHECK_TICK
+ switch(severity)
+ if(EXPLODE_DEVASTATE)
+ SSexplosions.high_mov_atom += movable_thing
+ if(EXPLODE_HEAVY)
+ SSexplosions.med_mov_atom += movable_thing
+ if(EXPLODE_LIGHT)
+ SSexplosions.low_mov_atom += movable_thing
/turf/narsie_act(force, ignore_mobs, probability = 20)
. = (prob(probability) || force)
@@ -498,7 +586,7 @@
/turf/proc/add_blueprints_preround(atom/movable/AM)
- if(!SSticker.HasRoundStarted())
+ if(!SSicon_smooth.initialized)
add_blueprints(AM)
/turf/proc/is_transition_turf()
@@ -584,3 +672,26 @@
. = ..()
if(. != BULLET_ACT_FORCE_PIERCE)
. = BULLET_ACT_TURF
+
+/turf/proc/check_gravity()
+ return TRUE
+
+/**
+ * Returns adjacent turfs to this turf that are reachable, in all cardinal directions
+ *
+ * Arguments:
+ * * caller: The movable, if one exists, being used for mobility checks to see what tiles it can reach
+ * * ID: An ID card that decides if we can gain access to doors that would otherwise block a turf
+ * * simulated_only: Do we only worry about turfs with simulated atmos, most notably things that aren't space?
+*/
+/turf/proc/reachableAdjacentTurfs(caller, ID, simulated_only)
+ var/static/space_type_cache = typecacheof(/turf/open/space)
+ . = list()
+
+ for(var/iter_dir in GLOB.cardinals)
+ var/turf/turf_to_check = get_step(src,iter_dir)
+ if(!turf_to_check || (simulated_only && space_type_cache[turf_to_check.type]))
+ continue
+ if(turf_to_check.density || LinkBlockedWithAccess(turf_to_check, caller, ID))
+ continue
+ . += turf_to_check
diff --git a/code/game/verbs.dm b/code/game/verbs.dm
new file mode 100644
index 0000000000000..068cad2c16a3f
--- /dev/null
+++ b/code/game/verbs.dm
@@ -0,0 +1,178 @@
+/*
+ * BYOND moment
+ * Byond has verbs stored on /client and /atom, but they are a different variable,
+ * So we have to make an override for /atom and /client that do exactly the same thing but affect a different variable.
+ * Seriously, why wouldn't they all just be on client?
+ *
+ * Update:
+ * Apparently client.verbs is always empty, but adding and removing from it still works?
+ * Just going to use winset instead, since that seems to work.
+ */
+
+/datum
+ var/list/stat_tabs
+ var/list/sorted_verbs
+
+/datum/proc/add_verb(new_verbs)
+ //Nooooo!!!!!
+ if(IsAdminAdvancedProcCall())
+ message_admins("[key_name(usr)] attempted to edit their verbs.")
+ log_game("[key_name(usr)] attempted to edit their verbs.")
+ return
+ if(!islist(new_verbs))
+ new_verbs = list(new_verbs)
+ //To use less memory
+ if(!islist(sorted_verbs))
+ sorted_verbs = list()
+ if(!islist(stat_tabs))
+ stat_tabs = list()
+ for(var/verb_in_list in new_verbs)
+ var/procpath/V = verb_in_list
+ if(V.category)
+ if(V.category in sorted_verbs)
+ if(V in sorted_verbs[V.category])
+ continue
+ //Binary insert at the correct position
+ var/list/verbs = sorted_verbs["[V.category]"]
+ BINARY_INSERT_TEXT(V, verbs, procpath, name)
+ sorted_verbs["[V.category]"] = verbs
+ else
+ //Add category with verb
+ stat_tabs += V.category
+ sorted_verbs["[V.category]"] = list(V)
+ sortList(sorted_verbs)
+
+/datum/proc/remove_verb(old_verbs)
+ if(IsAdminAdvancedProcCall())
+ message_admins("[key_name(usr)] attempted to edit their verbs.")
+ log_game("[key_name(usr)] attempted to edit their verbs.")
+ return
+ if(!sorted_verbs)
+ return
+ if(!islist(old_verbs))
+ old_verbs = list(old_verbs)
+ for(var/verb_in_list in old_verbs)
+ var/procpath/V = verb_in_list
+ //Find our category
+ if("[V.category]" in sorted_verbs)
+ //Remove the verb
+ sorted_verbs["[V.category]"] -= V
+ //Remove the category if necessary
+ if(!LAZYLEN(sorted_verbs["[V.category]"]))
+ sorted_verbs.Remove("[V.category]")
+ stat_tabs -= V.category
+
+/atom/add_verb(new_verbs, tgui_only = FALSE)
+ if(IsAdminAdvancedProcCall())
+ message_admins("[key_name(usr)] attempted to edit their verbs.")
+ log_game("[key_name(usr)] attempted to edit their verbs.")
+ return
+ if(!tgui_only)
+ verbs += new_verbs
+ return ..(new_verbs)
+
+/atom/remove_verb(old_verbs, tgui_only = FALSE)
+ if(IsAdminAdvancedProcCall())
+ message_admins("[key_name(usr)] attempted to edit their verbs.")
+ log_game("[key_name(usr)] attempted to edit their verbs.")
+ return
+ if(!tgui_only)
+ verbs -= old_verbs
+ return ..(old_verbs)
+
+/obj/item/remove_verb(new_verbs, tgui_only = FALSE)
+ if(IsAdminAdvancedProcCall())
+ message_admins("[key_name(usr)] attempted to edit their verbs.")
+ log_game("[key_name(usr)] attempted to edit their verbs.")
+ return
+ //If we lose an old verb while in someone's inventory, remove it frmo their panel.
+ if(item_flags & IN_INVENTORY)
+ var/mob/living/L = loc
+ if(istype(L) && L.client)
+ L.client.remove_verbs(new_verbs)
+ return ..(new_verbs)
+
+/obj/item/add_verb(new_verbs, tgui_only = FALSE)
+ if(IsAdminAdvancedProcCall())
+ message_admins("[key_name(usr)] attempted to edit their verbs.")
+ log_game("[key_name(usr)] attempted to edit their verbs.")
+ return
+ //If we get a new verb while in someone's inventory, add it to their panel.
+ if(item_flags & IN_INVENTORY)
+ var/mob/living/L = loc
+ if(istype(L) && L.client)
+ L.client.add_verbs(new_verbs)
+ return ..(new_verbs)
+
+/client/add_verb(new_verbs, tgui_only = FALSE)
+ if(IsAdminAdvancedProcCall())
+ message_admins("[key_name(usr)] attempted to edit their verbs.")
+ log_game("[key_name(usr)] attempted to edit their verbs.")
+ return
+ if(!tgui_only)
+ verbs += new_verbs
+ add_verbs(new_verbs)
+ return ..(new_verbs)
+
+/client/remove_verb(old_verbs, tgui_only = FALSE)
+ if(IsAdminAdvancedProcCall())
+ message_admins("[key_name(usr)] attempted to edit their verbs.")
+ log_game("[key_name(usr)] attempted to edit their verbs.")
+ return
+ if(!tgui_only)
+ verbs -= old_verbs
+ remove_verbs(old_verbs)
+ return ..(old_verbs)
+
+/mob/remove_verb(old_verbs, tgui_only = FALSE)
+ if(IsAdminAdvancedProcCall())
+ message_admins("[key_name(usr)] attempted to edit their verbs.")
+ log_game("[key_name(usr)] attempted to edit their verbs.")
+ return
+ if(client)
+ client.remove_verbs(old_verbs)
+ return ..()
+
+/mob/add_verb(new_verbs, tgui_only)
+ if(IsAdminAdvancedProcCall())
+ message_admins("[key_name(usr)] attempted to edit their verbs.")
+ log_game("[key_name(usr)] attempted to edit their verbs.")
+ return
+ if(client)
+ client.add_verbs(new_verbs)
+ return ..()
+
+/client/proc/remove_verbs(old_verbs)
+ if(!islist(old_verbs))
+ old_verbs = list(old_verbs)
+ var/list/removed_verbs = list()
+ for(var/pp in old_verbs)
+ var/procpath/P = pp
+ if(!P)
+ continue
+ if(!islist(removed_verbs[P.category]))
+ removed_verbs[P.category] = list()
+ removed_verbs[P.category] += "[P.name]"
+ tgui_panel.remove_verbs(removed_verbs)
+
+/client/proc/add_verbs(new_verbs)
+ if(!islist(new_verbs))
+ new_verbs = list(new_verbs)
+ var/list/added_verbs = list()
+ for(var/pp in new_verbs)
+ var/procpath/P = pp
+ if(!P)
+ continue
+ if((!mob && P.invisibility) || (mob && P.invisibility > mob.see_invisible))
+ continue
+ if(!islist(added_verbs[P.category]))
+ added_verbs[P.category] = list()
+ added_verbs[P.category]["[P.name]"] = list(
+ action = "verb",
+ params = list("verb" = P.name),
+ type = STAT_VERB,
+ )
+ tgui_panel.add_verbs(added_verbs)
+
+/proc/cmp_verb_des(procpath/a,procpath/b)
+ return sorttext(b.name, a.name)
diff --git a/code/game/world.dm b/code/game/world.dm
index 0e5a3bec102f9..e9c99adc0c50f 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -5,27 +5,29 @@ GLOBAL_VAR(restart_counter)
//This happens after the Master subsystem new(s) (it's a global datum)
//So subsystems globals exist, but are not initialised
/world/New()
- if (fexists(EXTOOLS))
- call(EXTOOLS, "debug_initialize")()
- call(EXTOOLS, "maptick_initialize")()
+ //Keep the auxtools stuff at the top
+ AUXTOOLS_CHECK(AUXMOS)
- //Early profile for auto-profiler - will be stopped on profiler init if necessary.
- world.Profile(PROFILE_START)
+ enable_debugger()
log_world("World loaded at [time_stamp()]!")
-
- GLOB.config_error_log = GLOB.world_manifest_log = GLOB.world_pda_log = GLOB.world_job_debug_log = GLOB.sql_error_log = GLOB.world_href_log = GLOB.world_runtime_log = GLOB.world_attack_log = GLOB.world_game_log = "data/logs/config_error.[GUID()].log" //temporary file used to record errors with loading config, moved to log directory once logging is set bl
+ SSmetrics.world_init_time = REALTIMEOFDAY // Important
make_datum_references_lists() //initialises global lists for referencing frequently used datums (so that we only ever do it once)
- TgsNew(minimum_required_security_level = TGS_SECURITY_TRUSTED)
+ GLOB.config_error_log = GLOB.world_manifest_log = GLOB.world_pda_log = GLOB.world_job_debug_log = GLOB.sql_error_log = GLOB.world_href_log = GLOB.world_runtime_log = GLOB.world_attack_log = GLOB.world_game_log = "data/logs/config_error.[GUID()].log" //temporary file used to record errors with loading config, moved to log directory once logging is set bl
+
+ config.Load(params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER])
GLOB.revdata = new
- config.Load(params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER])
+ InitTgs()
+
+ config.LoadMOTD()
load_admins()
load_mentors()
+ load_badge_ranks()
//SetupLogs depends on the RoundID, so lets check
//DB schema and set RoundID if we can
@@ -42,12 +44,9 @@ GLOBAL_VAR(restart_counter)
if (TgsAvailable())
world.log = file("[GLOB.log_directory]/dd.log") //not all runtimes trigger world/Error, so this is the only way to ensure we can see all of them.
#endif
- LoadVerbs(/datum/verbs/menu)
if(CONFIG_GET(flag/usewhitelist))
load_whitelist()
- GLOB.timezoneOffset = text2num(time2text(0,"hh")) * 36000
-
if(fexists(RESTART_COUNTER_PATH))
GLOB.restart_counter = text2num(trim(rustg_file_read(RESTART_COUNTER_PATH)))
fdel(RESTART_COUNTER_PATH)
@@ -57,8 +56,13 @@ GLOBAL_VAR(restart_counter)
Master.Initialize(10, FALSE, TRUE)
- if(TEST_RUN_PARAMETER in params)
- HandleTestRun()
+ #ifdef UNIT_TESTS
+ HandleTestRun()
+ #endif
+
+/world/proc/InitTgs()
+ TgsNew(new /datum/tgs_event_handler/impl, TGS_SECURITY_TRUSTED)
+ GLOB.revdata.load_tgs_info()
/world/proc/HandleTestRun()
//trigger things to run the whole process
@@ -71,7 +75,7 @@ GLOBAL_VAR(restart_counter)
#else
cb = VARSET_CALLBACK(SSticker, force_ending, TRUE)
#endif
- SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, /proc/addtimer, cb, 10 SECONDS))
+ SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, /proc/_addtimer, cb, 10 SECONDS))
/world/proc/SetupLogs()
var/override_dir = params[OVERRIDE_LOG_DIRECTORY_PARAMETER]
@@ -150,33 +154,60 @@ GLOBAL_VAR(restart_counter)
var/list/response[] = list()
- if (SSfail2topic?.IsRateLimited(addr))
- response["statuscode"] = 429
- response["response"] = "Rate limited."
- return json_encode(response)
if (length(T) > CONFIG_GET(number/topic_max_size))
response["statuscode"] = 413
- response["response"] = "Payload too large."
+ response["response"] = "Payload too large"
return json_encode(response)
- var/static/list/topic_handlers = TopicHandlers()
+ if (SSfail2topic?.IsRateLimited(addr))
+ response["statuscode"] = 429
+ response["response"] = "Rate limited"
+ return json_encode(response)
- var/list/input = params2list(T)
- var/datum/world_topic/handler
- for(var/I in topic_handlers)
- if(I in input)
- handler = topic_handlers[I]
- break
+ var/list/params[] = json_decode(rustg_url_decode(T))
+ params["addr"] = addr
+ var/query = params["query"]
+ var/auth = params["auth"]
+ var/source = params["source"]
- if((!handler || initial(handler.log)) && config && CONFIG_GET(flag/log_world_topic))
- log_topic("\"[T]\", from:[addr], master:[master], key:[key]")
+ if(CONFIG_GET(flag/log_world_topic))
+ var/list/censored_params = params.Copy()
+ censored_params["auth"] = "***[copytext(params["auth"], -4)]"
+ log_topic("\"[json_encode(censored_params)]\", from:[addr], master:[master], auth:[censored_params["auth"]], key:[key], source:[source]")
- if(!handler)
- return
+ if(!source)
+ response["statuscode"] = 400
+ response["response"] = "Bad Request - No source specified"
+ return json_encode(response)
- handler = new handler()
- return handler.TryRun(input, addr)
+ if(!query)
+ response["statuscode"] = 400
+ response["response"] = "Bad Request - No endpoint specified"
+ return json_encode(response)
+
+ if(!LAZYACCESS(GLOB.topic_tokens[auth], query))
+ response["statuscode"] = 401
+ response["response"] = "Unauthorized - Bad auth"
+ return json_encode(response)
+
+ var/datum/world_topic/command = GLOB.topic_commands[query]
+ if(!command)
+ response["statuscode"] = 501
+ response["response"] = "Not Implemented"
+ return json_encode(response)
+
+ if(command.CheckParams(params))
+ response["statuscode"] = command.statuscode
+ response["response"] = command.response
+ response["data"] = command.data
+ return json_encode(response)
+ else
+ command.Run(params)
+ response["statuscode"] = command.statuscode
+ response["response"] = command.response
+ response["data"] = command.data
+ return json_encode(response)
/world/proc/AnnouncePR(announcement, list/payload)
var/static/list/PRcounts = list() //PR id -> number of times announced this round
@@ -215,6 +246,7 @@ GLOBAL_VAR(restart_counter)
/world/Reboot(reason = 0, fast_track = FALSE)
if (reason || fast_track) //special reboot, do none of the normal stuff
+ SSdbcore.Disconnect()
if (usr)
log_admin("[key_name(usr)] Has requested an immediate world restart via client side debugging tools")
message_admins("[key_name_admin(usr)] Has requested an immediate world restart via client side debugging tools")
@@ -225,9 +257,10 @@ GLOBAL_VAR(restart_counter)
TgsReboot()
- if(TEST_RUN_PARAMETER in params)
- FinishTestRun()
- return
+ #ifdef UNIT_TESTS
+ FinishTestRun()
+ return
+ #endif
if(TgsAvailable())
var/do_hard_reboot
@@ -252,17 +285,15 @@ GLOBAL_VAR(restart_counter)
log_world("World rebooted at [time_stamp()]")
shutdown_logging() // Past this point, no logging procs can be used, at risk of data loss.
+ AUXTOOLS_SHUTDOWN(AUXMOS)
..()
/world/Del()
- // memory leaks bad
- var/num_deleted = 0
- for(var/datum/gas_mixture/GM)
- GM.__gasmixture_unregister()
- num_deleted++
- log_world("Deallocated [num_deleted] gas mixtures")
- if(fexists(EXTOOLS))
- call(EXTOOLS, "cleanup")()
+ shutdown_logging() // makes sure the thread is closed before end, else we terminate
+ AUXTOOLS_SHUTDOWN(AUXMOS)
+ var/debug_server = world.GetConfig("env", "AUXTOOLS_DEBUG_DLL")
+ if (debug_server)
+ call(debug_server, "auxtools_shutdown")()
..()
/world/proc/update_status()
diff --git a/code/modules/NTNet/netdata.dm b/code/modules/NTNet/netdata.dm
index 22bebf9704d89..2a3a85f70643d 100644
--- a/code/modules/NTNet/netdata.dm
+++ b/code/modules/NTNet/netdata.dm
@@ -42,26 +42,3 @@
/datum/netdata/proc/generate_netlog()
return "[json_encode(json_list_generation_netlog())]"
-
-// Circuit stuff
-// Process data before sending it
-/datum/netdata/proc/pre_send(datum/component/ntnet_interface/interface)
- // Decrypt the passkey.
- if(autopasskey)
- if(data["encrypted_passkey"] && !passkey)
- var/result = XorEncrypt(hextostr(data["encrypted_passkey"], TRUE), SScircuit.cipherkey)
- if(length(result) > 1)
- passkey = json_decode(XorEncrypt(hextostr(data["encrypted_passkey"], TRUE), SScircuit.cipherkey))
-
- // Encrypt the passkey.
- if(!data["encrypted_passkey"] && passkey)
- data["encrypted_passkey"] = strtohex(XorEncrypt(json_encode(passkey), SScircuit.cipherkey))
-
- // If there is no sender ID, set the default one.
- if(!sender_id && interface)
- sender_id = interface.hardware_id
-
-/datum/component/ntnet_interface/__network_send(datum/netdata/data, netid)
- // Process data before sending it
- data.pre_send(src)
- return(..(data,netid))
diff --git a/code/modules/NTNet/relays.dm b/code/modules/NTNet/relays.dm
index 9abd79ebfb676..22738ccb47e08 100644
--- a/code/modules/NTNet/relays.dm
+++ b/code/modules/NTNet/relays.dm
@@ -22,7 +22,7 @@
// Denial of Service attack variables
var/dos_overload = 0 // Amount of DoS "packets" in this relay's buffer
var/dos_capacity = 500 // Amount of DoS "packets" in buffer required to crash the relay
- var/dos_dissipate = 1 // Amount of DoS "packets" dissipated over time.
+ var/dos_dissipate = 0.5 // Amount of DoS "packets" dissipated over time.
// TODO: Implement more logic here. For now it's only a placeholder.
@@ -41,7 +41,7 @@
else
icon_state = "bus_off"
-/obj/machinery/ntnet_relay/process()
+/obj/machinery/ntnet_relay/process(delta_time)
if(is_operational())
use_power = ACTIVE_POWER_USE
else
@@ -49,17 +49,19 @@
update_icon()
- if(dos_overload)
- dos_overload = max(0, dos_overload - dos_dissipate)
+ if(dos_overload > 0)
+ dos_overload = max(0, dos_overload - dos_dissipate * delta_time)
// If DoS traffic exceeded capacity, crash.
if((dos_overload > dos_capacity) && !dos_failure)
dos_failure = 1
+ ui_update()
update_icon()
SSnetworks.station_network.add_log("Quantum relay switched from normal operation mode to overload recovery mode.")
// If the DoS buffer reaches 0 again, restart.
if((dos_overload == 0) && dos_failure)
dos_failure = 0
+ ui_update()
update_icon()
SSnetworks.station_network.add_log("Quantum relay switched from overload recovery mode to normal operation mode.")
..()
diff --git a/code/modules/VR/vr_human.dm b/code/modules/VR/vr_human.dm
deleted file mode 100644
index 524610c7632dc..0000000000000
--- a/code/modules/VR/vr_human.dm
+++ /dev/null
@@ -1,86 +0,0 @@
-/mob/living/carbon/human/virtual_reality
- var/datum/mind/real_mind // where is my mind t. pixies
- var/obj/machinery/vr_sleeper/vr_sleeper
- var/datum/action/quit_vr/quit_action
-
-/mob/living/carbon/human/virtual_reality/Initialize()
- . = ..()
- quit_action = new()
- quit_action.Grant(src)
- check_area()
-
-/mob/living/carbon/human/virtual_reality/Moved()
- . = ..()
- check_area()
-
-/mob/living/carbon/human/virtual_reality/death()
- revert_to_reality()
- . = ..()
-
-/mob/living/carbon/human/virtual_reality/Destroy()
- revert_to_reality()
- return ..()
-
-/mob/living/carbon/human/virtual_reality/Life()
- . = ..()
- if(real_mind)
- var/mob/living/real_me = real_mind.current
- if (real_me?.stat == CONSCIOUS)
- return
- revert_to_reality(FALSE)
-
-/mob/living/carbon/human/virtual_reality/ghostize()
- if(!real_mind && !vr_sleeper)
- return ..()
- stack_trace("Ghostize was called on a virtual reality mob")
-
-/mob/living/carbon/human/virtual_reality/ghost()
- set category = "OOC"
- set name = "Ghost"
- set desc = "Relinquish your life and enter the land of the dead."
- revert_to_reality(FALSE)
-
-/mob/living/carbon/human/virtual_reality/proc/check_area()
- var/area/check = get_area(src)
- if(!check || !istype(check, /area/awaymission/vr))
- return
- var/area/awaymission/vr/A = check
- if(A.death)
- to_chat(src, "It is unwise to attempt to break Virtual Reality.")
- playsound(src, 'sound/effects/supermatter.ogg', 50, 1)
- dust()
- return
- if(A.pacifist && !HAS_TRAIT_FROM(src, TRAIT_PACIFISM, VR_ZONE_TRAIT))
- ADD_TRAIT(src, TRAIT_PACIFISM, VR_ZONE_TRAIT)
- to_chat(src, "You feel like your ability to fight other living beings is being suppressed.")
- else if(!A.pacifist && HAS_TRAIT_FROM(src, TRAIT_PACIFISM, VR_ZONE_TRAIT))
- REMOVE_TRAIT(src, TRAIT_PACIFISM, VR_ZONE_TRAIT)
- to_chat(src, "You feel that your ability to fight is no longer being suppressed.")
-
-/mob/living/carbon/human/virtual_reality/proc/revert_to_reality(deathchecks = TRUE)
- if(real_mind && mind)
- real_mind.current.ckey = ckey
- real_mind.current.stop_sound_channel(CHANNEL_HEARTBEAT)
- if(deathchecks && vr_sleeper)
- if(vr_sleeper.you_die_in_the_game_you_die_for_real)
- to_chat(real_mind, "You feel everything fading away...")
- real_mind.current.death(0)
- if(deathchecks && vr_sleeper)
- vr_sleeper.vr_human = null
- vr_sleeper = null
- if(!real_mind && !vr_sleeper)
- ghostize()
- real_mind = null
-
-/datum/action/quit_vr
- name = "Quit Virtual Reality"
- icon_icon = 'icons/mob/actions/actions_vr.dmi'
- button_icon_state = "logout"
-
-/datum/action/quit_vr/Trigger()
- if(..())
- if(istype(owner, /mob/living/carbon/human/virtual_reality))
- var/mob/living/carbon/human/virtual_reality/VR = owner
- VR.revert_to_reality(FALSE)
- else
- Remove(owner)
diff --git a/code/modules/VR/vr_sleeper.dm b/code/modules/VR/vr_sleeper.dm
deleted file mode 100644
index 4876cf85edede..0000000000000
--- a/code/modules/VR/vr_sleeper.dm
+++ /dev/null
@@ -1,244 +0,0 @@
-
-
-//Glorified teleporter that puts you in a new human body.
-// it's """VR"""
-/obj/machinery/vr_sleeper
- name = "virtual reality sleeper"
- desc = "A sleeper modified to alter the subconscious state of the user, allowing them to visit virtual worlds."
- icon = 'icons/obj/machines/sleeper.dmi'
- icon_state = "sleeper"
- state_open = TRUE
- occupant_typecache = list(/mob/living/carbon/human) // turned into typecache in Initialize
- circuit = /obj/item/circuitboard/machine/vr_sleeper
- var/you_die_in_the_game_you_die_for_real = FALSE
- var/datum/effect_system/spark_spread/sparks
- var/mob/living/carbon/human/virtual_reality/vr_human
- var/vr_category = "default" //Specific category of spawn points to pick from
- var/allow_creating_vr_humans = TRUE //So you can have vr_sleepers that always spawn you as a specific person or 1 life/chance vr games
- var/only_current_user_can_interact = FALSE
-
-/obj/machinery/vr_sleeper/Initialize()
- . = ..()
- sparks = new /datum/effect_system/spark_spread()
- sparks.set_up(2,0)
- sparks.attach(src)
- update_icon()
-
-/obj/machinery/vr_sleeper/attackby(obj/item/I, mob/user, params)
- if(!state_open && !occupant)
- if(default_deconstruction_screwdriver(user, "[initial(icon_state)]-o", initial(icon_state), I))
- return
- if(default_change_direction_wrench(user, I))
- return
- if(default_pry_open(I))
- return
- if(default_deconstruction_crowbar(I))
- return
- return ..()
-
-/obj/machinery/vr_sleeper/relaymove(mob/user)
- open_machine()
-
-/obj/machinery/vr_sleeper/container_resist(mob/living/user)
- open_machine()
-
-/obj/machinery/vr_sleeper/Destroy()
- open_machine()
- cleanup_vr_human()
- QDEL_NULL(sparks)
- return ..()
-
-/obj/machinery/vr_sleeper/hugbox
- desc = "A sleeper modified to alter the subconscious state of the user, allowing them to visit virtual worlds. Seems slightly more secure."
- flags_1 = NODECONSTRUCT_1
- only_current_user_can_interact = TRUE
-
-/obj/machinery/vr_sleeper/hugbox/emag_act(mob/user)
- return
-
-/obj/machinery/vr_sleeper/emag_act(mob/user)
- you_die_in_the_game_you_die_for_real = TRUE
- sparks.start()
- addtimer(CALLBACK(src, .proc/emagNotify), 150)
-
-/obj/machinery/vr_sleeper/update_icon()
- icon_state = "[initial(icon_state)][state_open ? "-open" : ""]"
-
-/obj/machinery/vr_sleeper/open_machine()
- if(!state_open)
- if(vr_human)
- vr_human.revert_to_reality(FALSE)
- if(occupant)
- SStgui.close_user_uis(occupant, src)
- ..()
-
-/obj/machinery/vr_sleeper/MouseDrop_T(mob/target, mob/living/user)
- if(!istype(user))
- return
- if(user.stat || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser())
- return
- if(isliving(user))
- var/mob/living/L = user
- if(!(L.mobility_flags & MOBILITY_STAND))
- return
- close_machine(target)
-
-
-/obj/machinery/vr_sleeper/ui_state(mob/user)
- return GLOB.default_state
-
-/obj/machinery/vr_sleeper/ui_interact(mob/user, datum/tgui/ui)
- ui = SStgui.try_update_ui(user, src, ui)
- if(!ui)
- ui = new(user, src, "vr_sleeper")
- ui.open()
-
-/obj/machinery/vr_sleeper/ui_act(action, params)
- if(..())
- return
- switch(action)
- if("vr_connect")
- var/mob/living/carbon/human/human_occupant = occupant
- if(human_occupant?.mind && usr == occupant)
- to_chat(occupant, "Transferring to virtual reality...")
- if(vr_human && vr_human.stat == CONSCIOUS && !vr_human.real_mind)
- SStgui.close_user_uis(occupant, src)
- if(istype(human_occupant, /mob/living/carbon/human/virtual_reality))
- var/mob/living/carbon/human/virtual_reality/vr_human_occupant = human_occupant
- vr_human.real_mind = vr_human_occupant.real_mind
- else
- vr_human.real_mind = human_occupant.mind
- vr_human.ckey = human_occupant.ckey
- to_chat(vr_human, "Transfer successful! You are now playing as [vr_human] in VR!")
- else
- if(allow_creating_vr_humans)
- to_chat(occupant, "Virtual avatar not found, attempting to create one...")
- var/obj/effect/landmark/vr_spawn/V = get_vr_spawnpoint()
- var/turf/T = get_turf(V)
- if(T)
- SStgui.close_user_uis(occupant, src)
- build_virtual_human(occupant, T, V.vr_outfit)
- to_chat(vr_human, "Transfer successful! You are now playing as [vr_human] in VR!")
- else
- to_chat(occupant, "Virtual world misconfigured, aborting transfer.")
- else
- to_chat(occupant, "The virtual world does not support the creation of new virtual avatars, aborting transfer.")
- return TRUE
- if("delete_avatar")
- if(!occupant || usr == occupant)
- if(vr_human)
- cleanup_vr_human()
- else
- to_chat(usr, "The VR Sleeper's safeties prevent you from doing that.")
- return TRUE
- if("toggle_open")
- if(state_open)
- close_machine()
- else if ((!occupant || usr == occupant) || !only_current_user_can_interact)
- open_machine()
- return TRUE
-
-/obj/machinery/vr_sleeper/ui_data(mob/user)
- var/list/data = list()
- if(vr_human && !QDELETED(vr_human))
- data["can_delete_avatar"] = TRUE
- var/status
- switch(vr_human.stat)
- if(CONSCIOUS)
- status = "Conscious"
- if(DEAD)
- status = "Dead"
- if(UNCONSCIOUS)
- status = "Unconscious"
- if(SOFT_CRIT)
- status = "Barely Conscious"
- data["vr_avatar"] = list("name" = vr_human.name, "status" = status, "health" = vr_human.health, "maxhealth" = vr_human.maxHealth)
- data["toggle_open"] = state_open
- data["emagged"] = you_die_in_the_game_you_die_for_real
- data["isoccupant"] = (user == occupant)
- return data
-
-/obj/machinery/vr_sleeper/proc/get_vr_spawnpoint() //proc so it can be overridden for team games or something
- return safepick(GLOB.vr_spawnpoints[vr_category])
-
-/obj/machinery/vr_sleeper/proc/build_spawnpoints() // used to rebuild the list for admins if need be
- GLOB.vr_spawnpoints = list()
- for(var/obj/effect/landmark/vr_spawn/V in GLOB.landmarks_list)
- GLOB.vr_spawnpoints[V.vr_category] = V
-
-/obj/machinery/vr_sleeper/proc/build_virtual_human(mob/living/carbon/human/H, location, var/datum/outfit/outfit, transfer = TRUE)
- if(H)
- cleanup_vr_human()
- vr_human = new /mob/living/carbon/human/virtual_reality(location)
- vr_human.mind_initialize()
- vr_human.vr_sleeper = src
- vr_human.real_mind = H.mind
- H.dna.transfer_identity(vr_human)
- vr_human.name = H.name
- vr_human.real_name = H.real_name
- vr_human.socks = H.socks
- vr_human.undershirt = H.undershirt
- vr_human.underwear = H.underwear
- vr_human.updateappearance(TRUE, TRUE, TRUE)
- if(outfit)
- var/datum/outfit/O = new outfit()
- O.equip(vr_human)
- if(transfer && H.mind)
- SStgui.close_user_uis(H, src)
- vr_human.ckey = H.ckey
-
-/obj/machinery/vr_sleeper/proc/cleanup_vr_human()
- if(vr_human)
- vr_human.vr_sleeper = null // Prevents race condition where a new human could get created out of order and set to null.
- QDEL_NULL(vr_human)
-
-/obj/machinery/vr_sleeper/proc/emagNotify()
- if(vr_human)
- vr_human.Dizzy(10)
-
-/obj/effect/landmark/vr_spawn //places you can spawn in VR, auto selected by the vr_sleeper during get_vr_spawnpoint()
- var/vr_category = "default" //So we can have specific sleepers, eg: "Basketball VR Sleeper", etc.
- var/vr_outfit = /datum/outfit/vr
-
-/obj/effect/landmark/vr_spawn/Initialize()
- . = ..()
- LAZYADD(GLOB.vr_spawnpoints[vr_category], src)
-
-/obj/effect/landmark/vr_spawn/Destroy()
- LAZYREMOVE(GLOB.vr_spawnpoints[vr_category], src)
- return ..()
-
-/obj/effect/landmark/vr_spawn/team_1
- vr_category = "team_1"
-
-/obj/effect/landmark/vr_spawn/team_2
- vr_category = "team_2"
-
-/obj/effect/landmark/vr_spawn/admin
- vr_category = "event"
-
-/obj/effect/landmark/vr_spawn/syndicate // Multiple missions will use syndicate gear
- vr_outfit = /datum/outfit/vr/syndicate
-
-/obj/effect/vr_clean_master // Will keep VR areas that have this relatively clean.
- icon = 'icons/mob/screen_gen.dmi'
- icon_state = "x2"
- color = "#00FF00"
- invisibility = INVISIBILITY_ABSTRACT
- var/area/vr_area
-
-/obj/effect/vr_clean_master/Initialize()
- . = ..()
- vr_area = get_area(src)
- addtimer(CALLBACK(src, .proc/clean_up), 3 MINUTES)
-
-/obj/effect/vr_clean_master/proc/clean_up()
- if (vr_area)
- for (var/obj/item/ammo_casing/casing in vr_area)
- qdel(casing)
- for(var/obj/effect/decal/cleanable/C in vr_area)
- qdel(C)
- for (var/mob/living/carbon/human/virtual_reality/H in vr_area)
- if (H.stat == DEAD && !H.vr_sleeper && !H.real_mind)
- qdel(H)
- addtimer(CALLBACK(src, .proc/clean_up), 3 MINUTES)
diff --git a/code/modules/actionspeed/_actionspeed_modifier.dm b/code/modules/actionspeed/_actionspeed_modifier.dm
new file mode 100644
index 0000000000000..a2870be6f461f
--- /dev/null
+++ b/code/modules/actionspeed/_actionspeed_modifier.dm
@@ -0,0 +1,175 @@
+/*! Actionspeed modification datums.
+
+ How action speed for mobs works
+
+Action speed is now calculated by using modifier datums which are added to mobs. Some of them (nonvariable ones) are globally cached, the variable ones are instanced and changed based on need.
+
+This gives us the ability to have multiple sources of actionspeed, reliabily keep them applied and remove them when they should be
+
+THey can have unique sources and a bunch of extra fancy flags that control behaviour
+
+Previously trying to update action speed was a shot in the dark that usually meant mobs got stuck going faster or slower
+
+Actionspeed modification list is a simple key = datum system. Key will be the datum's ID if it is overridden to not be null, or type if it is not.
+
+DO NOT override datum IDs unless you are going to have multiple types that must overwrite each other. It's more efficient to use types, ID functionality is only kept for cases where dynamic creation of modifiers need to be done.
+
+When update actionspeed is called, the list of items is iterated, according to flags priority and a bunch of conditions
+this spits out a final calculated value which is used as a modifer to last_move + modifier for calculating when a mob
+can next move
+
+*/
+
+/datum/actionspeed_modifier
+ /// Whether or not this is a variable modifier. Variable modifiers can NOT be ever auto-cached. ONLY CHECKED VIA INITIAL(), EFFECTIVELY READ ONLY (and for very good reason)
+ var/variable = FALSE
+
+ /// Unique ID. You can never have different modifications with the same ID. By default, this SHOULD NOT be set. Only set it for cases where you're dynamically making modifiers/need to have two types overwrite each other. If unset, uses path (converted to text) as ID.
+ var/id
+
+ /// Higher ones override lower priorities. This is NOT used for ID, ID must be unique, if it isn't unique the newer one overwrites automatically if overriding.
+ var/priority = 0
+ var/flags = NONE
+
+ /// Multiplicative slowdown
+ var/multiplicative_slowdown = 0
+
+ /// Other modification datums this conflicts with.
+ var/conflicts_with
+
+/datum/actionspeed_modifier/New()
+ . = ..()
+ if(!id)
+ id = "[type]" //We turn the path into a string.
+
+GLOBAL_LIST_EMPTY(actionspeed_modification_cache)
+
+/// Grabs a STATIC MODIFIER datum from cache. YOU MUST NEVER EDIT THESE DATUMS, OR IT WILL AFFECT ANYTHING ELSE USING IT TOO!
+/proc/get_cached_actionspeed_modifier(modtype)
+ if(!ispath(modtype, /datum/actionspeed_modifier))
+ CRASH("[modtype] is not a actionspeed modification typepath.")
+ var/datum/actionspeed_modifier/actionspeed_mod = modtype
+ if(initial(actionspeed_mod.variable))
+ CRASH("[modtype] is a variable modifier, and can never be cached.")
+ actionspeed_mod = GLOB.actionspeed_modification_cache[modtype]
+ if(!actionspeed_mod)
+ actionspeed_mod = GLOB.actionspeed_modification_cache[modtype] = new modtype
+ return actionspeed_mod
+
+///Add a action speed modifier to a mob. If a variable subtype is passed in as the first argument, it will make a new datum. If ID conflicts, it will overwrite the old ID.
+/mob/proc/add_actionspeed_modifier(datum/actionspeed_modifier/type_or_datum, update = TRUE)
+ if(ispath(type_or_datum))
+ if(!initial(type_or_datum.variable))
+ type_or_datum = get_cached_actionspeed_modifier(type_or_datum)
+ else
+ type_or_datum = new type_or_datum
+ var/datum/actionspeed_modifier/existing = LAZYACCESS(actionspeed_modification, type_or_datum.id)
+ if(existing)
+ if(existing == type_or_datum) //same thing don't need to touch
+ return TRUE
+ remove_actionspeed_modifier(existing, FALSE)
+ if(length(actionspeed_modification))
+ BINARY_INSERT(type_or_datum.id, actionspeed_modification, /datum/actionspeed_modifier, type_or_datum, priority, COMPARE_VALUE)
+ LAZYSET(actionspeed_modification, type_or_datum.id, type_or_datum)
+ if(update)
+ update_actionspeed()
+ return TRUE
+
+/// Remove a action speed modifier from a mob, whether static or variable.
+/mob/proc/remove_actionspeed_modifier(datum/actionspeed_modifier/type_id_datum, update = TRUE)
+ var/key
+ if(ispath(type_id_datum))
+ key = initial(type_id_datum.id) || "[type_id_datum]" //id if set, path set to string if not.
+ else if(!istext(type_id_datum)) //if it isn't text it has to be a datum, as it isn't a type.
+ key = type_id_datum.id
+ else //assume it's an id
+ key = type_id_datum
+ if(!LAZYACCESS(actionspeed_modification, key))
+ return FALSE
+ LAZYREMOVE(actionspeed_modification, key)
+ if(update)
+ update_actionspeed(FALSE)
+ return TRUE
+
+/*! Used for variable slowdowns like hunger/health loss/etc, works somewhat like the old list-based modification adds. Returns the modifier datum if successful
+ How this SHOULD work is:
+ 1. Ensures type_id_datum one way or another refers to a /variable datum. This makes sure it can't be cached. This includes if it's already in the modification list.
+ 2. Instantiate a new datum if type_id_datum isn't already instantiated + in the list, using the type. Obviously, wouldn't work for ID only.
+ 3. Add the datum if necessary using the regular add proc
+ 4. If any of the rest of the args are not null (see: multiplicative slowdown), modify the datum
+ 5. Update if necessary
+*/
+/mob/proc/add_or_update_variable_actionspeed_modifier(datum/actionspeed_modifier/type_id_datum, update = TRUE, multiplicative_slowdown)
+ var/modified = FALSE
+ var/inject = FALSE
+ var/datum/actionspeed_modifier/final
+ if(istext(type_id_datum))
+ final = LAZYACCESS(actionspeed_modification, type_id_datum)
+ if(!final)
+ CRASH("Couldn't find existing modification when provided a text ID.")
+ else if(ispath(type_id_datum))
+ if(!initial(type_id_datum.variable))
+ CRASH("Not a variable modifier")
+ final = LAZYACCESS(actionspeed_modification, initial(type_id_datum.id) || "[type_id_datum]")
+ if(!final)
+ final = new type_id_datum
+ inject = TRUE
+ modified = TRUE
+ else
+ if(!initial(type_id_datum.variable))
+ CRASH("Not a variable modifier")
+ final = type_id_datum
+ if(!LAZYACCESS(actionspeed_modification, final.id))
+ inject = TRUE
+ modified = TRUE
+ if(!isnull(multiplicative_slowdown))
+ final.multiplicative_slowdown = multiplicative_slowdown
+ modified = TRUE
+ if(inject)
+ add_actionspeed_modifier(final, FALSE)
+ if(update && modified)
+ update_actionspeed(TRUE)
+ return final
+
+///Is there a actionspeed modifier for this mob
+/mob/proc/has_actionspeed_modifier(datum/actionspeed_modifier/datum_type_id)
+ var/key
+ if(ispath(datum_type_id))
+ key = initial(datum_type_id.id) || "[datum_type_id]"
+ else if(istext(datum_type_id))
+ key = datum_type_id
+ else
+ key = datum_type_id.id
+ return LAZYACCESS(actionspeed_modification, key)
+
+/// Go through the list of actionspeed modifiers and calculate a final actionspeed. ANY ADD/REMOVE DONE IN UPDATE_actionspeed MUST HAVE THE UPDATE ARGUMENT SET AS FALSE!
+/mob/proc/update_actionspeed()
+ . = 0
+ var/list/conflict_tracker = list()
+ for(var/key in get_actionspeed_modifiers())
+ var/datum/actionspeed_modifier/M = actionspeed_modification[key]
+ var/conflict = M.conflicts_with
+ var/amt = M.multiplicative_slowdown
+ if(conflict)
+ // Conflicting modifiers prioritize the larger slowdown or the larger speedup
+ // We purposefuly don't handle mixing speedups and slowdowns on the same id
+ if(abs(conflict_tracker[conflict]) < abs(amt))
+ conflict_tracker[conflict] = amt
+ else
+ continue
+ . += amt
+ cached_multiplicative_actions_slowdown = .
+
+///Adds a default action speed
+/mob/proc/initialize_actionspeed()
+ add_or_update_variable_actionspeed_modifier(/datum/actionspeed_modifier/base, multiplicative_slowdown = 1)
+
+/// Get the action speed modifiers list of the mob
+/mob/proc/get_actionspeed_modifiers()
+ . = LAZYCOPY(actionspeed_modification)
+ for(var/id in actionspeed_mod_immunities)
+ . -= id
+
+/// Checks if a action speed modifier is valid and not missing any data
+/proc/actionspeed_data_null_check(datum/actionspeed_modifier/M) //Determines if a data list is not meaningful and should be discarded.
+ . = !(M.multiplicative_slowdown)
diff --git a/code/modules/actionspeed/modifiers/base.dm b/code/modules/actionspeed/modifiers/base.dm
new file mode 100644
index 0000000000000..97c5124c3bc61
--- /dev/null
+++ b/code/modules/actionspeed/modifiers/base.dm
@@ -0,0 +1,2 @@
+/datum/actionspeed_modifier/base
+ variable = TRUE
diff --git a/code/modules/actionspeed/modifiers/mood.dm b/code/modules/actionspeed/modifiers/mood.dm
new file mode 100644
index 0000000000000..d8ed005d42b35
--- /dev/null
+++ b/code/modules/actionspeed/modifiers/mood.dm
@@ -0,0 +1,7 @@
+/datum/actionspeed_modifier/low_sanity
+ multiplicative_slowdown = 0.25
+ id = ACTIONSPEED_ID_SANITY
+
+/datum/actionspeed_modifier/high_sanity
+ multiplicative_slowdown = -0.1
+ id = ACTIONSPEED_ID_SANITY
diff --git a/code/modules/actionspeed/modifiers/race.dm b/code/modules/actionspeed/modifiers/race.dm
new file mode 100644
index 0000000000000..2e34c7bfe9f26
--- /dev/null
+++ b/code/modules/actionspeed/modifiers/race.dm
@@ -0,0 +1,2 @@
+/datum/actionspeed_modifier/cogscarab
+ multiplicative_slowdown = 0.6
diff --git a/code/modules/actionspeed/modifiers/status_effects.dm b/code/modules/actionspeed/modifiers/status_effects.dm
new file mode 100644
index 0000000000000..74846303d6e31
--- /dev/null
+++ b/code/modules/actionspeed/modifiers/status_effects.dm
@@ -0,0 +1,2 @@
+/datum/actionspeed_modifier/timecookie
+ multiplicative_slowdown = -0.05
diff --git a/code/modules/admin/IsBanned.dm b/code/modules/admin/IsBanned.dm
index 5c2ea87602247..a56fe949f1e20 100644
--- a/code/modules/admin/IsBanned.dm
+++ b/code/modules/admin/IsBanned.dm
@@ -9,7 +9,7 @@
GLOBAL_LIST_EMPTY(ckey_redirects)
/world/IsBanned(key, address, computer_id, type, real_bans_only=FALSE)
- debug_world_log("isbanned(): '[args.Join("', '")]'")
+ debug_world_log("isbanned(): SRC:'[src]', USR:'[usr]' ARGS:'[args.Join("', '")]'")
if (!key || (!real_bans_only && (!address || !computer_id)))
if(real_bans_only)
return FALSE
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index e8b196b5149ec..dcce6a141af72 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -1,4 +1,3 @@
-
////////////////////////////////
/proc/message_admins(msg)
msg = "ADMIN LOG:[msg]"
@@ -43,6 +42,12 @@
if(M.client)
body += "
First Seen: [M.client.player_join_date] Byond account registered on: [M.client.account_join_date]"
+
+ if(M.client?.tgui_panel)
+ body += M.client.tgui_panel.show_notices() //The datum holds a reference to the client already, no need to pass it.
+ else //This should never happen in practice.
+ body += " Telemetry Status:USER CHAT NOT LOADED, CALL A CODER MAYBE?"
+
body += "
CentCom Galactic Ban DB: "
if(CONFIG_GET(string/centcom_ban_db))
body += "Search"
@@ -254,7 +259,7 @@
dat+=" The newscaster recognises you as: [src.admin_signature]"
if(1)
dat+= "Station Feed Channels"
- if( isemptylist(GLOB.news_network.network_channels) )
+ if( !length(GLOB.news_network.network_channels) )
dat+="No active channels found..."
else
for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels)
@@ -307,7 +312,7 @@
dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice. "
dat+="No further feed story additions are allowed while the D-Notice is in effect.
"
else
- if( isemptylist(src.admincaster_feed_channel.messages) )
+ if( !length(src.admincaster_feed_channel.messages) )
dat+="No feed messages found in channel... "
else
var/i = 0
@@ -329,7 +334,7 @@
dat+="NOTE: Due to the nature of news Feeds, total deletion of a Feed Story is not possible. "
dat+="Keep in mind that users attempting to view a censored feed will instead see the \[REDACTED\] tag above it."
dat+="Select Feed channel to get Stories from: "
- if(isemptylist(GLOB.news_network.network_channels))
+ if(!length(GLOB.news_network.network_channels))
dat+="No feed channels found active... "
else
for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels)
@@ -340,7 +345,7 @@
dat+="A D-Notice is to be bestowed upon the channel if the handling Authority deems it as harmful for the station's"
dat+="morale, integrity or disciplinary behaviour. A D-Notice will render a channel unable to be updated by anyone, without deleting any feed"
dat+="stories it might contain at the time. You can lift a D-Notice if you have the required access at any time."
- if(isemptylist(GLOB.news_network.network_channels))
+ if(!length(GLOB.news_network.network_channels))
dat+="No feed channels found active... "
else
for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels)
@@ -351,7 +356,7 @@
dat+="[src.admincaster_feed_channel.channel_name]: \[ created by: [src.admincaster_feed_channel.returnAuthor(-1)] \] "
dat+="[(src.admincaster_feed_channel.authorCensor) ? ("Undo Author censorship") : ("Censor channel Author")]"
- if( isemptylist(src.admincaster_feed_channel.messages) )
+ if( !length(src.admincaster_feed_channel.messages) )
dat+="No feed messages found in channel... "
else
for(var/datum/newscaster/feed_message/MESSAGE in src.admincaster_feed_channel.messages)
@@ -368,7 +373,7 @@
dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice. "
dat+="No further feed story additions are allowed while the D-Notice is in effect.
"
else
- if( isemptylist(src.admincaster_feed_channel.messages) )
+ if( !length(src.admincaster_feed_channel.messages) )
dat+="No feed messages found in channel... "
else
for(var/datum/newscaster/feed_message/MESSAGE in src.admincaster_feed_channel.messages)
@@ -438,8 +443,7 @@
"}
if(GLOB.master_mode == "secret")
dat += "(Force Secret Mode) "
-
- if(GLOB.master_mode == "dynamic")
+ if(SSticker.is_mode("dynamic"))
if(SSticker.current_state <= GAME_STATE_PREGAME)
dat += "(Force Roundstart Rulesets) "
if (GLOB.dynamic_forced_roundstart_ruleset.len > 0)
@@ -543,27 +547,12 @@
No stacking: - Option is [GLOB.dynamic_no_stacking ? "ON" : "OFF"].
Unless the threat goes above [GLOB.dynamic_stacking_limit], only one "round-ender" ruleset will be drafted.
- High population limit: Current value : [GLOB.dynamic_high_pop_limit].
- The threshold at which "high population override" will be in effect.
Stacking threeshold: Current value : [GLOB.dynamic_stacking_limit].
The threshold at which "round-ender" rulesets will stack. A value higher than 100 ensure this never happens.
-
Advanced parameters
- Curve centre: -> [GLOB.dynamic_curve_centre] <-
- Curve width: -> [GLOB.dynamic_curve_width] <-
- Latejoin injection delay:
- Minimum: -> [GLOB.dynamic_latejoin_delay_min / 60 / 10] <- Minutes
- Maximum: -> [GLOB.dynamic_latejoin_delay_max / 60 / 10] <- Minutes
- Midround injection delay:
- Minimum: -> [GLOB.dynamic_midround_delay_min / 60 / 10] <- Minutes
- Maximum: -> [GLOB.dynamic_midround_delay_max / 60 / 10] <- Minutes
"}
user << browse(dat, "window=dyn_mode_options;size=900x650")
@@ -704,7 +693,7 @@
log_admin("[key_name(usr)] delayed the round start.")
else
to_chat(world, "The game will start in [DisplayTimeText(newtime)].")
- SEND_SOUND(world, sound('sound/ai/attention.ogg'))
+ SEND_SOUND(world, sound('sound/ai/default/attention.ogg'))
log_admin("[key_name(usr)] set the pre-game delay to [DisplayTimeText(newtime)].")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Delay Game Start") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -716,6 +705,9 @@
SSticker.admin_delay_notice = input(usr, "Enter a reason for delaying the round end", "Round Delay Reason") as null|text
if(isnull(SSticker.admin_delay_notice))
return
+ for(var/client/admin in GLOB.admins)
+ if(check_rights(R_FUN) && !GLOB.battle_royale && admin.tgui_panel && SSticker.current_state == GAME_STATE_FINISHED)
+ admin.tgui_panel.give_br_popup()
else
if(alert(usr, "Really cancel current round end delay? The reason for the current delay is: \"[SSticker.admin_delay_notice]\"", "Undelay round end", "Yes", "No") != "Yes")
return
@@ -783,7 +775,7 @@
var/obj/structure/closet/supplypod/centcompod/pod = new()
var/atom/A = new chosen(pod)
A.flags_1 |= ADMIN_SPAWNED_1
- new /obj/effect/DPtarget(T, pod)
+ new /obj/effect/pod_landingzone(T, pod)
log_admin("[key_name(usr)] pod-spawned [chosen] at [AREACOORD(usr)]")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Podspawn Atom") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -855,20 +847,26 @@
for(var/i in GLOB.silicon_mobs)
var/mob/living/silicon/S = i
ai_number++
+ var/message = ""
if(isAI(S))
- to_chat(usr, "AI [key_name(S, usr)]'s laws:")
+ message += "AI [key_name(S, usr)]'s laws:"
else if(iscyborg(S))
var/mob/living/silicon/robot/R = S
- to_chat(usr, "CYBORG [key_name(S, usr)] [R.connected_ai?"(Slaved to: [key_name(R.connected_ai)])":"(Independent)"]: laws:")
+ message += "CYBORG [key_name(S, usr)] [R.connected_ai?"(Slaved to: [key_name(R.connected_ai)])":"(Independent)"]: laws:"
else if (ispAI(S))
- to_chat(usr, "pAI [key_name(S, usr)]'s laws:")
+ message += "pAI [key_name(S, usr)]'s laws:"
else
- to_chat(usr, "SOMETHING SILICON [key_name(S, usr)]'s laws:")
+ message += "SOMETHING SILICON [key_name(S, usr)]'s laws:"
+
+ message += " "
if (S.laws == null)
- to_chat(usr, "[key_name(S, usr)]'s laws are null?? Contact a coder.")
+ message += "[key_name(S, usr)]'s laws are null?? Contact a coder."
else
- S.laws.show_laws(usr)
+ message += jointext(S.laws.get_law_list(include_zeroth = TRUE), " ")
+
+ to_chat(usr, message)
+
if(!ai_number)
to_chat(usr, "No AIs located" )
@@ -979,7 +977,7 @@
if (!frommob || !tomob) //make sure the mobs don't go away while we waited for a response
return 1
- tomob.ghostize(0)
+ tomob.ghostize(FALSE)
message_admins("[key_name_admin(usr)] has put [frommob.key] in control of [tomob.name].")
log_admin("[key_name(usr)] stuffed [frommob.key] into [tomob.name].")
diff --git a/code/modules/admin/admin_investigate.dm b/code/modules/admin/admin_investigate.dm
index c6a4850f4677f..0fbc143c21639 100644
--- a/code/modules/admin/admin_investigate.dm
+++ b/code/modules/admin/admin_investigate.dm
@@ -1,5 +1,5 @@
/atom/proc/investigate_log(message, subject)
- if(!message || !subject)
+ if(!message || !subject || SSticker.current_state == GAME_STATE_FINISHED)
return
var/F = file("[GLOB.log_directory]/[subject].html")
WRITE_FILE(F, "[time_stamp()] [REF(src)] ([x],[y],[z]) || [src] [message] ")
@@ -10,7 +10,7 @@
if(!holder)
return
- var/list/investigates = list(INVESTIGATE_RESEARCH, INVESTIGATE_EXONET, INVESTIGATE_PORTAL, INVESTIGATE_SINGULO, INVESTIGATE_WIRES, INVESTIGATE_TELESCI, INVESTIGATE_GRAVITY, INVESTIGATE_RECORDS, INVESTIGATE_CARGO, INVESTIGATE_SUPERMATTER, INVESTIGATE_ATMOS, INVESTIGATE_EXPERIMENTOR, INVESTIGATE_BOTANY, INVESTIGATE_HALLUCINATIONS, INVESTIGATE_RADIATION, INVESTIGATE_NANITES, INVESTIGATE_PRESENTS, INVESTIGATE_CIRCUIT)
+ var/list/investigates = list(INVESTIGATE_RESEARCH, INVESTIGATE_EXONET, INVESTIGATE_PORTAL, INVESTIGATE_ENGINES, INVESTIGATE_WIRES, INVESTIGATE_TELESCI, INVESTIGATE_GRAVITY, INVESTIGATE_RECORDS, INVESTIGATE_CARGO, INVESTIGATE_ATMOS, INVESTIGATE_EXPERIMENTOR, INVESTIGATE_BOTANY, INVESTIGATE_HALLUCINATIONS, INVESTIGATE_RADIATION, INVESTIGATE_NANITES, INVESTIGATE_PRESENTS)
var/list/logs_present = list("notes, memos, watchlist")
var/list/logs_missing = list("---")
diff --git a/code/modules/admin/admin_ranks.dm b/code/modules/admin/admin_ranks.dm
index 3e31b85456999..e4d0de3140455 100644
--- a/code/modules/admin/admin_ranks.dm
+++ b/code/modules/admin/admin_ranks.dm
@@ -10,6 +10,7 @@ GLOBAL_PROTECT(protected_ranks)
var/exclude_rights = 0
var/include_rights = 0
var/can_edit_rights = 0
+ var/badge_icon
/datum/admin_rank/New(init_name, init_rights, init_exclude_rights, init_edit_rights)
if(IsAdminAdvancedProcCall())
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 11d86ba64f833..9ff7bee9da21d 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -18,7 +18,8 @@ GLOBAL_PROTECT(admin_verbs_default)
/client/proc/cmd_admin_pm_context, /*right-click adminPM interface*/
/client/proc/cmd_admin_pm_panel, /*admin-pm list*/
/client/proc/stop_sounds,
- /client/proc/mark_datum_mapview
+ /client/proc/mark_datum_mapview,
+ /client/proc/requests
)
GLOBAL_LIST_INIT(admin_verbs_admin, world.AVerbsAdmin())
GLOBAL_PROTECT(admin_verbs_admin)
@@ -27,9 +28,10 @@ GLOBAL_PROTECT(admin_verbs_admin)
/client/proc/invisimin, /*allows our mob to go invisible/visible*/
// /datum/admins/proc/show_traitor_panel, /*interface which shows a mob's mind*/ -Removed due to rare practical use. Moved to debug verbs ~Errorage
/datum/admins/proc/show_player_panel, /*shows an interface for individual players, with various links (links require additional flags*/
- /datum/verbs/menu/Admin/verb/playerpanel,
+ /client/proc/playerpanel,
/client/proc/game_panel, /*game panel, allows to change game-mode etc*/
/client/proc/check_ai_laws, /*shows AI and borg laws*/
+ /client/proc/ghost_pool_protection, /*opens a menu for toggling ghost roles*/
/datum/admins/proc/toggleooc, /*toggles ooc on/off for everyone*/
/datum/admins/proc/toggleoocdead, /*toggles ooc on/off for everyone who is dead*/
/datum/admins/proc/toggleenter, /*toggles whether people can join the current game*/
@@ -77,7 +79,8 @@ GLOBAL_PROTECT(admin_verbs_admin)
/datum/admins/proc/open_borgopanel,
/client/proc/fix_say,
/client/proc/stabilize_atmos,
- /client/proc/openTicketManager
+ /client/proc/openTicketManager,
+ /client/proc/battle_royale
)
GLOBAL_LIST_INIT(admin_verbs_ban, list(/client/proc/unban_panel, /client/proc/ban_panel, /client/proc/stickybanpanel))
GLOBAL_PROTECT(admin_verbs_ban)
@@ -149,6 +152,7 @@ GLOBAL_PROTECT(admin_verbs_debug)
/client/proc/enable_debug_verbs,
/client/proc/callproc,
/client/proc/callproc_datum,
+ /client/proc/forcemapconfig,
/client/proc/SDQL2_query,
/client/proc/test_movable_UI,
/client/proc/test_snap_UI,
@@ -166,6 +170,7 @@ GLOBAL_PROTECT(admin_verbs_debug)
/client/proc/map_template_load,
/client/proc/map_template_upload,
/client/proc/jump_to_ruin,
+ /client/proc/generate_ruin,
/client/proc/clear_dynamic_transit,
/client/proc/fucky_wucky,
/client/proc/toggle_medal_disable,
@@ -174,8 +179,18 @@ GLOBAL_PROTECT(admin_verbs_debug)
/client/proc/cmd_display_init_log,
/client/proc/cmd_display_overlay_log,
/client/proc/reload_configuration,
+ /client/proc/give_all_spells,
/datum/admins/proc/create_or_modify_area,
- /client/proc/toggle_cdn
+ /datum/admins/proc/fixcorruption,
+ #ifdef TESTING
+ /client/proc/export_dynamic_json,
+ /client/proc/run_dynamic_simulations,
+ #endif
+ #ifdef SENDMAPS_PROFILE
+ /client/proc/display_sendmaps,
+ #endif
+ /client/proc/toggle_cdn,
+ /client/proc/check_timer_sources
)
GLOBAL_LIST_INIT(admin_verbs_possess, list(/proc/possess, /proc/release))
@@ -257,36 +272,38 @@ GLOBAL_PROTECT(admin_verbs_hideable)
control_freak = CONTROL_FREAK_SKIN | CONTROL_FREAK_MACROS
var/rights = holder.rank.rights
- verbs += GLOB.admin_verbs_default
+ add_verb(GLOB.admin_verbs_default)
if(rights & R_BUILD)
- verbs += /client/proc/togglebuildmodeself
+ add_verb(/client/proc/togglebuildmodeself)
if(rights & R_ADMIN)
- verbs += GLOB.admin_verbs_admin
+ add_verb(GLOB.admin_verbs_admin)
if(rights & R_BAN)
- verbs += GLOB.admin_verbs_ban
+ add_verb(GLOB.admin_verbs_ban)
if(rights & R_FUN)
- verbs += GLOB.admin_verbs_fun
+ add_verb(GLOB.admin_verbs_fun)
if(rights & R_SERVER)
- verbs += GLOB.admin_verbs_server
+ add_verb(GLOB.admin_verbs_server)
if(rights & R_DEBUG)
- verbs += GLOB.admin_verbs_debug
+ add_verb(GLOB.admin_verbs_debug)
if(rights & R_POSSESS)
- verbs += GLOB.admin_verbs_possess
+ add_verb(GLOB.admin_verbs_possess)
if(rights & R_PERMISSIONS)
- verbs += GLOB.admin_verbs_permissions
+ add_verb(GLOB.admin_verbs_permissions)
if(rights & R_STEALTH)
- verbs += /client/proc/stealth
+ add_verb(/client/proc/stealth)
if(rights & R_ADMIN)
- verbs += GLOB.admin_verbs_poll
+ add_verb(GLOB.admin_verbs_poll)
if(rights & R_SOUND)
- verbs += GLOB.admin_verbs_sounds
+ add_verb(GLOB.admin_verbs_sounds)
if(CONFIG_GET(string/invoke_youtubedl))
- verbs += /client/proc/play_web_sound
+ add_verb(/client/proc/play_web_sound)
if(rights & R_SPAWN)
- verbs += GLOB.admin_verbs_spawn
+ add_verb(GLOB.admin_verbs_spawn)
+ reset_badges()
/client/proc/remove_admin_verbs()
- verbs.Remove(
+ var/list/verb_list = list()
+ verb_list.Add(
GLOB.admin_verbs_default,
/client/proc/togglebuildmodeself,
GLOB.admin_verbs_admin,
@@ -307,13 +324,15 @@ GLOBAL_PROTECT(admin_verbs_hideable)
/client/proc/readmin,
/client/proc/fix_say
)
+ remove_verb(verb_list)
+ reset_badges()
/client/proc/hide_most_verbs()//Allows you to keep some functionality while hiding some verbs
set name = "Adminverbs - Hide Most"
set category = "Admin"
- verbs.Remove(/client/proc/hide_most_verbs, GLOB.admin_verbs_hideable)
- verbs += /client/proc/show_verbs
+ remove_verb(list(/client/proc/hide_most_verbs) + GLOB.admin_verbs_hideable)
+ add_verb(/client/proc/show_verbs)
to_chat(src, "Most of your adminverbs have been hidden.")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Hide Most Adminverbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -324,7 +343,7 @@ GLOBAL_PROTECT(admin_verbs_hideable)
set category = "Admin"
remove_admin_verbs()
- verbs += /client/proc/show_verbs
+ add_verb(/client/proc/show_verbs)
to_chat(src, "Almost all of your adminverbs have been hidden.")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Hide All Adminverbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -334,7 +353,7 @@ GLOBAL_PROTECT(admin_verbs_hideable)
set name = "Adminverbs - Show"
set category = "Admin"
- verbs -= /client/proc/show_verbs
+ remove_verb(/client/proc/show_verbs)
add_admin_verbs()
to_chat(src, "All of your adminverbs are now visible.")
@@ -368,7 +387,7 @@ GLOBAL_PROTECT(admin_verbs_hideable)
log_admin("[key_name(usr)] admin ghosted.")
message_admins("[key_name_admin(usr)] admin ghosted.")
var/mob/body = mob
- body.ghostize(1)
+ body.ghostize(TRUE)
if(body && !body.key)
body.key = "@[key]" //Haaaaaaaack. But the people have spoken. If it breaks; blame adminbus
SSblackbox.record_feedback("tally", "admin_verb", 1, "Admin Ghost") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -476,6 +495,7 @@ GLOBAL_PROTECT(admin_verbs_hideable)
if(holder)
if(holder.fakekey)
holder.fakekey = null
+ reset_badges()
if(isobserver(mob))
mob.invisibility = initial(mob.invisibility)
mob.alpha = initial(mob.alpha)
@@ -486,6 +506,7 @@ GLOBAL_PROTECT(admin_verbs_hideable)
if(!new_key)
return
holder.fakekey = new_key
+ reset_badges()
createStealthKey()
if(isobserver(mob))
mob.invisibility = INVISIBILITY_MAXIMUM //JUST IN CASE
@@ -648,6 +669,7 @@ GLOBAL_PROTECT(admin_verbs_hideable)
log_admin("[key_name(usr)] made [O] at [AREACOORD(O)] say \"[message]\"")
message_admins("[key_name_admin(usr)] made [O] at [AREACOORD(O)]. say \"[message]\"")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Object Say") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+
/client/proc/togglebuildmodeself()
set name = "Toggle Build Mode Self"
set category = "Adminbus"
@@ -711,34 +733,34 @@ GLOBAL_PROTECT(admin_verbs_hideable)
set category = "Debug"
set desc = "(\"Amount of mobs to create\") Populate the world with test mobs."
- if (amount > 0)
+ if(amount > 0)
var/area/area
var/list/candidates
var/turf/open/floor/tile
var/j,k
- for (var/i = 1 to amount)
+ for(var/i = 1 to amount)
j = 100
do
area = pick(GLOB.the_station_areas)
- if (area)
+ if(area)
candidates = get_area_turfs(area)
- if (candidates.len)
+ if(candidates.len)
k = 100
do
tile = pick(candidates)
while ((!tile || !istype(tile)) && --k > 0)
- if (tile)
+ if(tile)
var/mob/living/carbon/human/hooman = new(tile)
hooman.equipOutfit(pick(subtypesof(/datum/outfit)))
testing("Spawned test mob at [COORD(tile)]")
- while (!area && --j > 0)
+ while(!area && --j > 0)
/client/proc/toggle_AI_interact()
set name = "Toggle Admin AI Interact"
@@ -779,7 +801,10 @@ GLOBAL_PROTECT(admin_verbs_hideable)
can.valve_open = FALSE
can.update_icon()
+#ifdef SENDMAPS_PROFILE
+/client/proc/display_sendmaps()
+ set name = "Send Maps Profile"
+ set category = "Debug"
-
-
-
+ src << link("?debug=profile&type=sendmaps&window=test")
+#endif
diff --git a/code/modules/admin/adminmenu.dm b/code/modules/admin/adminmenu.dm
index 64ce5987cfd5d..0378ac39f3c62 100644
--- a/code/modules/admin/adminmenu.dm
+++ b/code/modules/admin/adminmenu.dm
@@ -1,11 +1,7 @@
-/datum/verbs/menu/Admin/Generate_list(client/C)
- if (C.holder)
- . = ..()
-
-/datum/verbs/menu/Admin/verb/playerpanel()
+/client/proc/playerpanel()
set name = "Player Panel"
set desc = "Player Panel"
set category = "Admin"
- if(usr.client.holder)
- usr.client.holder.player_panel_new()
+ if(holder)
+ holder.player_panel_new()
SSblackbox.record_feedback("tally", "admin_verb", 1, "Player Panel New") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
diff --git a/code/modules/admin/antag_panel.dm b/code/modules/admin/antag_panel.dm
index 1672310567794..2c98483461b4f 100644
--- a/code/modules/admin/antag_panel.dm
+++ b/code/modules/admin/antag_panel.dm
@@ -47,7 +47,7 @@ GLOBAL_VAR(antag_prototypes)
else
var/obj_count = 1
for(var/datum/objective/objective in objectives)
- result += "[obj_count]: [objective.explanation_text] EditDelete[objective.completed ? "Mark as incomplete" : "Mark as complete"] "
+ result += "[obj_count]: [objective.explanation_text] EditDelete[objective.completed ? "Mark as incomplete" : "Mark as complete"] "
obj_count++
result += "Add objective "
result += "Announce objectives "
@@ -77,7 +77,7 @@ GLOBAL_VAR(antag_prototypes)
return common_commands
/datum/mind/proc/get_special_statuses()
- var/list/result = list()
+ var/list/result = LAZYCOPY(special_statuses)
if(!current)
result += "No body!"
if(current && HAS_TRAIT(current, TRAIT_MINDSHIELD))
@@ -128,7 +128,7 @@ GLOBAL_VAR(antag_prototypes)
var/list/possible_admin_antags = list()
for(var/datum/antagonist/prototype in GLOB.antag_prototypes[antag_category])
- var/datum/antagonist/A = has_antag_datum(prototype.type)
+ var/datum/antagonist/A = has_antag_datum(prototype.type, FALSE)
if(A)
//We got the antag
if(!current_antag)
@@ -166,7 +166,10 @@ GLOBAL_VAR(antag_prototypes)
pref_source = prototype
break
if(pref_source.job_rank)
- antag_header_parts += pref_source.enabled_in_preferences(src) ? "Enabled in Prefs" : "Disabled in Prefs"
+ if(!is_banned_from(src.key, pref_source.job_rank))
+ antag_header_parts += pref_source.enabled_in_preferences(src) ? "Enabled in Prefs" : "Disabled in Prefs"
+ else
+ antag_header_parts += "\[BANNED\]"
//Traitor : None | Traitor | IAA
// Command1 | Command2 | Command3
@@ -214,4 +217,4 @@ GLOBAL_VAR(antag_prototypes)
var/datum/browser/panel = new(usr, "traitorpanel", "", 600, 600)
panel.set_content(out)
panel.open()
- return
\ No newline at end of file
+ return
diff --git a/code/modules/admin/battle_royale.dm b/code/modules/admin/battle_royale.dm
new file mode 100644
index 0000000000000..5cc22e8c18369
--- /dev/null
+++ b/code/modules/admin/battle_royale.dm
@@ -0,0 +1,447 @@
+//Global lists so they can be editted by admins
+GLOBAL_LIST_INIT(battle_royale_basic_loot, list(
+ /obj/item/soap,
+ /obj/item/kitchen/knife,
+ /obj/item/kitchen/knife/combat,
+ /obj/item/kitchen/knife/poison,
+ /obj/item/throwing_star,
+ /obj/item/syndie_glue,
+ /obj/item/book_of_babel,
+ /obj/item/card/emag,
+ /obj/item/storage/box/emps,
+ /obj/item/storage/box/lethalshot,
+ /obj/item/storage/box/gorillacubes,
+ /obj/item/storage/box/teargas,
+ /obj/item/storage/box/security/radio,
+ /obj/item/storage/box/medsprays,
+ /obj/item/storage/toolbox/syndicate,
+ /obj/item/storage/box/syndie_kit/bee_grenades,
+ /obj/item/storage/box/syndie_kit/centcom_costume,
+ /obj/item/storage/box/syndie_kit/chameleon,
+ /obj/item/storage/box/syndie_kit/chemical,
+ /obj/item/storage/box/syndie_kit/emp,
+ /obj/item/storage/box/syndie_kit/imp_adrenal,
+ /obj/item/storage/box/syndie_kit/imp_freedom,
+ /obj/item/storage/box/syndie_kit/imp_radio,
+ /obj/item/storage/box/syndie_kit/imp_stealth,
+ /obj/item/storage/box/syndie_kit/imp_storage,
+ /obj/item/storage/box/syndie_kit/imp_uplink,
+ /obj/item/storage/box/syndie_kit/origami_bundle,
+ /obj/item/storage/box/syndie_kit/throwing_weapons,
+ /obj/item/storage/box/syndie_kit/bundle_A,
+ /obj/item/storage/box/syndie_kit/bundle_B,
+ /obj/item/gun/ballistic/automatic/pistol,
+ /obj/item/gun/energy/disabler,
+ /obj/item/construction/rcd,
+ /obj/item/clothing/glasses/chameleon/flashproof,
+ /obj/item/book/granter/spell/knock,
+ /obj/item/clothing/glasses/sunglasses/advanced,
+ /obj/item/clothing/glasses/thermal/eyepatch,
+ /obj/item/clothing/glasses/thermal/syndi,
+ /obj/item/clothing/suit/space,
+ /obj/item/clothing/suit/armor/riot,
+ /obj/item/clothing/suit/armor/vest,
+ /obj/item/clothing/suit/armor/vest/russian_coat,
+ /obj/item/clothing/suit/armor/hos/trenchcoat,
+ /obj/item/clothing/mask/chameleon,
+ /obj/item/clothing/head/centhat,
+ /obj/item/clothing/head/crown,
+ /obj/item/clothing/head/HoS/syndicate,
+ /obj/item/clothing/head/helmet,
+ /obj/item/clothing/head/helmet/clockcult,
+ /obj/item/clothing/head/helmet/space,
+ /obj/item/clothing/head/helmet/sec,
+ /obj/item/clothing/under/syndicate,
+ /obj/item/clothing/gloves/combat,
+ /obj/item/deployablemine/stun,
+ /obj/item/switchblade,
+ /obj/item/club/tailclub,
+ /obj/item/nullrod/tribal_knife,
+ /obj/item/nullrod/fedora,
+ /obj/item/nullrod/godhand,
+ /obj/item/melee/baton/loaded,
+ /obj/item/melee/chainofcommand/tailwhip/kitty,
+ /obj/item/melee/classic_baton,
+ /obj/item/melee/ghost_sword,
+ /obj/item/melee/powerfist,
+ /obj/item/storage/firstaid/advanced,
+ /obj/item/storage/firstaid/brute,
+ /obj/item/storage/firstaid/fire,
+ /obj/item/storage/firstaid/medical,
+ /obj/item/storage/firstaid/tactical,
+ /obj/item/gun/energy/ionrifle,
+ /obj/item/organ/regenerative_core/battle_royale
+ ))
+
+GLOBAL_LIST_INIT(battle_royale_good_loot, list(
+ /obj/item/hand_tele,
+ /obj/item/gun/ballistic/bow/clockbolt,
+ /obj/item/gun/ballistic/rifle/boltaction,
+ /obj/item/gun/ballistic/shotgun/doublebarrel,
+ /obj/item/gun/energy/laser/captain,
+ /obj/item/gun/ballistic/revolver/mateba,
+ /obj/item/gun/ballistic/automatic/c20r,
+ /obj/item/ammo_box/magazine/smgm45,
+ /obj/item/ammo_box/magazine/pistolm9mm,
+ /obj/item/katana,
+ /obj/item/melee/transforming/energy/sword,
+ /obj/item/dualsaber,
+ /obj/item/fireaxe,
+ /obj/item/stack/telecrystal/five,
+ /obj/item/stack/telecrystal/twenty,
+ /obj/item/clothing/suit/space/hardsuit/syndi
+ ))
+
+GLOBAL_LIST_INIT(battle_royale_insane_loot, list(
+ /obj/item/gun/ballistic/automatic/l6_saw/unrestricted,
+ /obj/item/energy_katana,
+ /obj/item/clothing/suit/space/hardsuit/shielded/syndi,
+ /obj/item/his_grace,
+ /obj/mecha/combat/marauder/mauler/loaded,
+ /obj/item/guardiancreator/tech,
+ /obj/item/mjollnir,
+ /obj/item/pneumatic_cannon/pie/selfcharge,
+ /obj/item/uplink/nuclear
+ ))
+
+GLOBAL_DATUM(battle_royale, /datum/battle_royale_controller)
+
+#define BATTLE_ROYALE_AVERBS list(\
+ /client/proc/battle_royale_speed,\
+ /client/proc/battle_royale_varedit,\
+ /client/proc/battle_royale_spawn_loot,\
+ /client/proc/battle_royale_spawn_loot_good\
+)
+
+/client/proc/battle_royale()
+ set name = "Battle Royale"
+ set category = "Adminbus"
+ if(!(check_rights(R_FUN) || (check_rights(R_ADMIN) && SSticker.current_state == GAME_STATE_FINISHED)))
+ to_chat(src, "You do not have permission to do that! (If you don't have +FUN, wait until the round is over then you can trigger it.)")
+ return
+ if(GLOB.battle_royale)
+ to_chat(src, "A game is already in progress!")
+ return
+ if(alert(src, "ARE YOU SURE YOU ARE SURE YOU WANT TO START BATTLE ROYALE?",,"Yes","No") != "Yes")
+ to_chat(src, "oh.. ok then.. I see how it is.. :(")
+ return
+ log_admin("[key_name(usr)] HAS TRIGGERED BATTLE ROYALE")
+ message_admins("[key_name(usr)] HAS TRIGGERED BATTLE ROYALE")
+
+ for(var/client/admin in GLOB.admins)
+ if(check_rights(R_ADMIN) && !GLOB.battle_royale && admin.tgui_panel)
+ admin.tgui_panel.clear_br_popup()
+
+ GLOB.battle_royale = new()
+ GLOB.battle_royale.start()
+
+/client/proc/battle_royale_speed()
+ set name = "Battle Royale - Change wall speed"
+ set category = "Event"
+ if(!check_rights(R_ADMIN))
+ to_chat(src, "You do not have permission to do that!")
+ return
+ if(!GLOB.battle_royale)
+ to_chat(src, "No game is in progress.")
+ return
+ var/new_speed = input(src, "New wall delay (seconds)") as num
+ if(new_speed > 0)
+ GLOB.battle_royale.field_delay = new_speed
+ log_admin("[key_name(usr)] has changed the field delay to [new_speed] seconds")
+ message_admins("[key_name(usr)] has changed the field delay to [new_speed] seconds")
+
+/client/proc/battle_royale_varedit()
+ set name = "Battle Royale - Variable Edit"
+ set category = "Event"
+ if(!check_rights(R_FUN))
+ to_chat(src, "You do not have permission to do that!")
+ return
+ if(!GLOB.battle_royale)
+ to_chat(src, "No game is in progress.")
+ return
+ debug_variables(GLOB.battle_royale)
+
+/client/proc/battle_royale_spawn_loot()
+ set name = "Battle Royale - Spawn Loot Drop (Minor)"
+ set category = "Event"
+ if(!check_rights(R_FUN))
+ to_chat(src, "You do not have permission to do that!")
+ return
+ if(!GLOB.battle_royale)
+ to_chat(src, "No game is in progress.")
+ return
+ GLOB.battle_royale.generate_good_drop()
+ log_admin("[key_name(usr)] generated a battle royale drop.")
+ message_admins("[key_name(usr)] generated a battle royale drop.")
+
+/client/proc/battle_royale_spawn_loot_good()
+ set name = "Battle Royale - Spawn Loot Drop (Major)"
+ set category = "Event"
+ if(!check_rights(R_FUN))
+ to_chat(src, "You do not have permission to do that!")
+ return
+ if(!GLOB.battle_royale)
+ to_chat(src, "No game is in progress.")
+ return
+ GLOB.battle_royale.generate_endgame_drop()
+ log_admin("[key_name(usr)] generated a good battle royale drop.")
+ message_admins("[key_name(usr)] generated a good battle royale drop.")
+
+/datum/battle_royale_controller
+ var/list/players
+ var/datum/proximity_monitor/advanced/battle_royale/field_wall
+ var/radius = 118
+ var/process_num = 0
+ var/list/death_wall
+ var/field_delay = 15
+ var/debug_mode = FALSE
+
+/datum/battle_royale_controller/Destroy(force, ...)
+ QDEL_LIST(death_wall)
+ for(var/client/C in GLOB.admins)
+ C.remove_verb(BATTLE_ROYALE_AVERBS)
+ . = ..()
+ GLOB.enter_allowed = TRUE
+ world.update_status()
+ GLOB.battle_royale = null
+
+//Trigger random events and shit, update the world border
+/datum/battle_royale_controller/process()
+ process_num++
+ //Once every 25 seconds
+ if(prob(4))
+ generate_basic_loot(5)
+ //Once every 50 seconds.
+ if(prob(2))
+ generate_good_drop()
+ var/living_victims = 0
+ var/mob/winner
+ for(var/mob/living/M as() in players)
+ if(QDELETED(M))
+ players -= M
+ continue
+ if(M.stat == DEAD) //We aren't going to remove them from the list in case they somehow revive.
+ continue
+ var/turf/T = get_turf(M)
+ if(T.x > 128 + radius || T.x < 128 - radius || T.y > 128 + radius || T.y < 128 - radius)
+ to_chat(M, "You have left the zone!")
+ M.gib()
+ if(!SSmapping.level_trait(T.z, ZTRAIT_STATION) && !SSmapping.level_trait(T.z, ZTRAIT_RESERVED))
+ to_chat(M, "You have left the z-level!")
+ M.gib()
+ living_victims++
+ winner = M
+ CHECK_TICK
+ if(living_victims <= 1 && !debug_mode)
+ to_chat(world, "VICTORY ROYALE!!")
+ if(winner)
+ winner.client?.process_greentext()
+ to_chat(world, "[key_name(winner)] is the winner!")
+ new /obj/item/melee/supermatter_sword(get_turf(winner))
+ qdel(src)
+ return
+ //Once every 15 seconsd
+ // 1,920 seconds (about 32 minutes per game)
+ if(process_num % (field_delay) == 0)
+ for(var/obj/effect/death_wall/wall as() in death_wall)
+ wall.decrease_size()
+ if(QDELETED(wall))
+ death_wall -= wall
+ CHECK_TICK
+ radius--
+ if(radius < 70 && prob(1))
+ generate_endgame_drop()
+
+//==================================
+// INITIALIZATION
+//==================================
+
+/datum/battle_royale_controller/proc/start()
+ //Give Verbs to admins
+ for(var/client/C in GLOB.admins)
+ if(check_rights_for(C, R_ADMIN))
+ C.add_verb(BATTLE_ROYALE_AVERBS)
+ toggle_ooc(FALSE)
+ to_chat(world, "Battle Royale will begin soon...")
+ //Stop new player joining
+ GLOB.enter_allowed = FALSE
+ world.update_status()
+ if(SSticker.current_state < GAME_STATE_PREGAME)
+ to_chat(world, "Battle Royale: Waiting for server to be ready...")
+ SSticker.start_immediately = FALSE
+ UNTIL(SSticker.current_state >= GAME_STATE_PREGAME)
+ to_chat(world, "Battle Royale: Done!")
+ //Delay pre-game if we are in it.
+ if(SSticker.current_state == GAME_STATE_PREGAME)
+ //Force people to be not ready and start the game
+ for(var/mob/dead/new_player/player in GLOB.player_list)
+ to_chat(player, "You have been forced as an observer. When the prompt to join battle royale comes up, press yes. This is normal and you are still in queue to play.")
+ player.ready = FALSE
+ player.make_me_an_observer(TRUE)
+ to_chat(world, "Battle Royale: Force-starting game.")
+ SSticker.start_immediately = TRUE
+ SEND_SOUND(world, sound('sound/misc/server-ready.ogg'))
+ sleep(50)
+ //Clear client mobs
+ to_chat(world, "Battle Royale: Clearing world mobs.")
+ for(var/mob/M as() in GLOB.player_list)
+ if(isliving(M))
+ qdel(M)
+ CHECK_TICK
+ sleep(50)
+ to_chat(world, "Battle Royale: STARTING IN 30 SECONDS.")
+ to_chat(world, "If you are on the main menu, observe immediately to sign up. (You will be prompted in 30 seconds.)")
+ toggle_ooc(TRUE)
+ sleep(300)
+ toggle_ooc(FALSE)
+ to_chat(world, "Battle Royale: STARTING IN 5 SECONDS.")
+ to_chat(world, "Make sure to hit yes to the sign up message given to all observing players.")
+ sleep(50)
+ to_chat(world, "Battle Royale: Starting game.")
+ titanfall()
+ death_wall = list()
+ var/z_level = SSmapping.station_start
+ var/turf/center = SSmapping.get_station_center()
+ var/list/edge_turfs = list()
+ edge_turfs += block(locate(12, 12, z_level), locate(244, 12, z_level)) //BOTTOM
+ edge_turfs += block(locate(12, 244, z_level), locate(244, 244, z_level)) //TOP
+ edge_turfs |= block(locate(12, 12, z_level), locate(12, 244, z_level)) //LEFT
+ edge_turfs |= block(locate(244, 12, z_level), locate(244, 244, z_level)) //RIGHT
+ for(var/turf/T in edge_turfs)
+ var/obj/effect/death_wall/DW = new(T)
+ DW.set_center(center)
+ death_wall += DW
+ CHECK_TICK
+ START_PROCESSING(SSprocessing, src)
+
+/datum/battle_royale_controller/proc/titanfall()
+ var/list/participants = pollGhostCandidates("Would you like to partake in BATTLE ROYALE?")
+ var/turf/spawn_turf = get_safe_random_station_turfs()
+ var/obj/structure/closet/supplypod/centcompod/pod = new()
+ pod.setStyle()
+ players = list()
+ for(var/mob/M in participants)
+ var/key = M.key
+ //Create a mob and transfer their mind to it.
+ CHECK_TICK
+ var/mob/living/carbon/human/H = new(pod)
+ ADD_TRAIT(H, TRAIT_PACIFISM, BATTLE_ROYALE_TRAIT)
+ H.status_flags |= GODMODE
+ //Assistant gang
+ H.equipOutfit(/datum/outfit/job/assistant)
+ //Give them a spell
+ H.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock)
+ H.key = key
+ //Give weapons key
+ var/obj/item/implant/weapons_auth/W = new
+ W.implant(H)
+ players += H
+ to_chat(M, "You have been given knock and pacafism for 30 seconds.")
+ new /obj/effect/pod_landingzone(spawn_turf, pod)
+ SEND_SOUND(world, sound('sound/misc/airraid.ogg'))
+ to_chat(world, "A 30 second grace period has been established. Good luck.")
+ to_chat(world, "WARNING: YOU WILL BE GIBBED IF YOU LEAVE THE STATION Z-LEVEL!")
+ to_chat(world, "[players.len] people remain...")
+ //Start processing our world events
+ addtimer(CALLBACK(src, .proc/end_grace), 300)
+ generate_basic_loot(150)
+
+/datum/battle_royale_controller/proc/end_grace()
+ for(var/mob/M in GLOB.player_list)
+ M.RemoveSpell(/obj/effect/proc_holder/spell/aoe_turf/knock)
+ M.status_flags -= GODMODE
+ REMOVE_TRAIT(M, TRAIT_PACIFISM, BATTLE_ROYALE_TRAIT)
+ to_chat(M, "You are no longer a pacifist. Be the last [M.gender == MALE ? "man" : "woman"] standing.")
+
+//==================================
+// EVENTS / DROPS
+//==================================
+
+/datum/battle_royale_controller/proc/generate_basic_loot(amount=1)
+ for(var/i in 1 to amount)
+ send_item(pick(GLOB.battle_royale_basic_loot))
+ stoplag()
+
+/datum/battle_royale_controller/proc/generate_good_drop()
+ var/list/good_drops = list()
+ for(var/i in 1 to rand(1,3))
+ good_drops += pick(GLOB.battle_royale_good_loot)
+ send_item(good_drops, announce = "Incoming extended supply materials.", force_time = 150)
+
+/datum/battle_royale_controller/proc/generate_endgame_drop()
+ var/obj/item = pick(GLOB.battle_royale_insane_loot)
+ send_item(item, announce = "We found a weird looking package in the back of our warehouse. We have no idea what is in it, but it is marked as incredibily dangerous and could be a superweapon.", force_time = 600)
+
+/datum/battle_royale_controller/proc/send_item(item_path, style = STYLE_BOX, announce=FALSE, force_time = 0)
+ if(!item_path)
+ return
+ var/turf/target = get_safe_random_station_turfs()
+ var/obj/structure/closet/supplypod/battleroyale/pod = new()
+ if(islist(item_path))
+ for(var/thing in item_path)
+ new thing(pod)
+ else
+ new item_path(pod)
+ if(force_time)
+ pod.delays[POD_FALLING]= force_time
+ new /obj/effect/pod_landingzone(target, pod)
+ if(announce)
+ priority_announce("[announce] \nExpected Drop Location: [get_area(target)]\n ETA: [force_time/10] Seconds.", "High Command Supply Control", SSstation.announcer.get_rand_alert_sound())
+
+//==================================
+// WORLD BORDER
+//==================================
+
+/obj/effect/death_wall
+ var/current_radius = 118
+ var/turf/center_turf
+ icon = 'icons/effects/fields.dmi'
+ icon_state = "projectile_dampen_generic"
+
+/obj/effect/death_wall/Crossed(atom/movable/AM, oldloc)
+ . = ..()
+ //lol u died
+ if(isliving(AM))
+ var/mob/living/M = AM
+ M.gib()
+ to_chat(M, "You left the zone!")
+
+/obj/effect/death_wall/Moved(atom/OldLoc, Dir)
+ . = ..()
+ for(var/mob/living/M in get_turf(src))
+ M.gib()
+ to_chat(M, "You left the zone!")
+
+/obj/effect/death_wall/proc/set_center(turf/center)
+ center_turf = center
+
+/obj/effect/death_wall/proc/decrease_size()
+ var/minx = CLAMP(center_turf.x - current_radius, 1, 255)
+ var/maxx = CLAMP(center_turf.x + current_radius, 1, 255)
+ var/miny = CLAMP(center_turf.y - current_radius, 1, 255)
+ var/maxy = CLAMP(center_turf.y + current_radius, 1, 255)
+ if(y == maxy || y == miny)
+ //We have nowhere to move to so are deleted
+ if(x == minx || x == minx + 1 || x == maxx || x == maxx - 1)
+ qdel(src)
+ return
+ //Where do we go to?
+ var/top = y == maxy
+ var/bottom = y == miny
+ var/left = x == minx
+ var/right = x == maxx
+ if(left)
+ forceMove(get_step(get_turf(src), EAST))
+ else if(right)
+ forceMove(get_step(get_turf(src), WEST))
+ else if(bottom)
+ forceMove(get_step(get_turf(src), NORTH))
+ else if(top)
+ forceMove(get_step(get_turf(src), SOUTH))
+ current_radius--
+
+//=====
+// Heal
+// =====
+/obj/item/organ/regenerative_core/battle_royale
+ preserved = TRUE
diff --git a/code/modules/admin/callproc/callproc.dm b/code/modules/admin/callproc/callproc.dm
index ea567b5296c19..99bca8f05c7fd 100644
--- a/code/modules/admin/callproc/callproc.dm
+++ b/code/modules/admin/callproc/callproc.dm
@@ -15,7 +15,7 @@
switch(alert("Proc owned by something?",,"Yes","No"))
if("Yes")
targetselected = TRUE
- var/list/value = vv_get_value(default_class = VV_ATOM_REFERENCE, classes = list(VV_ATOM_REFERENCE, VV_DATUM_REFERENCE, VV_MOB_REFERENCE, VV_CLIENT, VV_MARKED_DATUM, VV_TEXT_LOCATE, VV_PROCCALL_RETVAL))
+ var/list/value = vv_get_value(default_class = VV_ATOM_REFERENCE, classes = list(VV_ATOM_REFERENCE, VV_DATUM_REFERENCE, VV_MOB_REFERENCE, VV_MARKED_DATUM, VV_TEXT_LOCATE, VV_PROCCALL_RETVAL))
if (!value["class"] || !value["value"])
return
target = value["value"]
@@ -83,8 +83,6 @@ GLOBAL_VAR(LastAdminCalledTarget)
GLOBAL_PROTECT(LastAdminCalledTarget)
GLOBAL_VAR(LastAdminCalledProc)
GLOBAL_PROTECT(LastAdminCalledProc)
-GLOBAL_LIST_EMPTY(AdminProcCallSpamPrevention)
-GLOBAL_PROTECT(AdminProcCallSpamPrevention)
/proc/WrapAdminProcCall(datum/target, procname, list/arguments)
if(target && procname == "Del")
@@ -98,15 +96,11 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention)
var/ckey = usr ? usr.client.ckey : GLOB.AdminProcCaller
if(!ckey)
CRASH("WrapAdminProcCall with no ckey: [target] [procname] [english_list(arguments)]")
+
if(current_caller && current_caller != ckey)
- if(!GLOB.AdminProcCallSpamPrevention[ckey])
- to_chat(usr, "Another set of admin called procs are still running, your proc will be run after theirs finish.")
- GLOB.AdminProcCallSpamPrevention[ckey] = TRUE
- UNTIL(!GLOB.AdminProcCaller)
- to_chat(usr, "Running your proc")
- GLOB.AdminProcCallSpamPrevention -= ckey
- else
- UNTIL(!GLOB.AdminProcCaller)
+ to_chat(usr, "Another set of admin called procs are still running. Try again later.")
+ return
+
GLOB.LastAdminCalledProc = procname
if(target != GLOBAL_PROC)
GLOB.LastAdminCalledTargetRef = REF(target)
@@ -119,7 +113,7 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention)
//adv proc call this, ya nerds
/world/proc/WrapAdminProcCall(datum/target, procname, list/arguments)
if(target == GLOBAL_PROC)
- return call(procname)(arglist(arguments))
+ return call("/proc/[procname]")(arglist(arguments))
else if(target != world)
return call(target, procname)(arglist(arguments))
else
diff --git a/code/modules/admin/chat_commands.dm b/code/modules/admin/chat_commands.dm
index 73450f9883458..86f0a312dbe39 100644
--- a/code/modules/admin/chat_commands.dm
+++ b/code/modules/admin/chat_commands.dm
@@ -30,6 +30,7 @@
var/server = CONFIG_GET(string/server)
return "[GLOB.round_id ? "Round #[GLOB.round_id]: " : ""][GLOB.clients.len] players on [SSmapping.config.map_name], Mode: [GLOB.master_mode]; Round [SSticker.HasRoundStarted() ? (SSticker.IsRoundInProgress() ? "Active" : "Finishing") : "Starting"] -- [server ? server : "[world.internet_address]:[world.port]"]"
+/** -- Not for use within BeeStation
/datum/tgs_chat_command/ahelp
name = "ahelp"
help_text = "|list>>"
@@ -51,6 +52,7 @@
var/res = IrcPm(target, all_params.Join(" "), sender.friendly_name)
if(res != "Message Successful")
return res
+**/
/datum/tgs_chat_command/namecheck
name = "namecheck"
@@ -71,7 +73,7 @@
admin_only = TRUE
/datum/tgs_chat_command/adminwho/Run(datum/tgs_chat_user/sender, params)
- return ircadminwho()
+ return tgsadminwho()
GLOBAL_LIST(round_end_notifiees)
@@ -87,6 +89,7 @@ GLOBAL_LIST(round_end_notifiees)
GLOB.round_end_notifiees[sender.mention] = TRUE
return "I will notify [sender.mention] when the round ends."
+/** -- Not for use within BeeStation
/datum/tgs_chat_command/sdql
name = "sdql"
help_text = "Runs an SDQL query"
@@ -103,7 +106,7 @@ GLOBAL_LIST(round_end_notifiees)
var/list/text_res = results.Copy(1, 3)
var/list/refs = results.len > 3 ? results.Copy(4) : null
. = "[text_res.Join("\n")][refs ? "\nRefs: [refs.Join(" ")]" : ""]"
-
+
/datum/tgs_chat_command/reload_admins
name = "reload_admins"
help_text = "Forces the server to reload admins."
@@ -117,3 +120,4 @@ GLOBAL_LIST(round_end_notifiees)
/datum/tgs_chat_command/reload_admins/proc/ReloadAsync()
set waitfor = FALSE
load_admins()
+**/
diff --git a/code/modules/admin/fun_balloon.dm b/code/modules/admin/fun_balloon.dm
index 6a10706d5a0f2..fd1dd17c62dc1 100644
--- a/code/modules/admin/fun_balloon.dm
+++ b/code/modules/admin/fun_balloon.dm
@@ -49,7 +49,7 @@
/obj/effect/fun_balloon/sentience/effect()
var/list/bodies = list()
- for(var/mob/living/M in range(effect_range, get_turf(src)))
+ for(var/mob/living/M in viewers(effect_range, get_turf(src)))
bodies += M
var/question = "Would you like to be [group_name]?"
@@ -79,7 +79,7 @@
var/effect_range = 5
/obj/effect/fun_balloon/scatter/effect()
- for(var/mob/living/M in range(effect_range, get_turf(src)))
+ for(var/mob/living/M in hearers(effect_range, get_turf(src)))
var/turf/T = find_safe_turf()
new /obj/effect/temp_visual/gravpush(get_turf(M))
M.forceMove(T)
@@ -108,7 +108,7 @@
/obj/effect/forcefield/arena_shuttle
name = "portal"
timeleft = 0
- var/list/warp_points
+ var/list/warp_points = list()
/obj/effect/forcefield/arena_shuttle/Initialize()
. = ..()
@@ -127,7 +127,7 @@
L.forceMove(LA)
L.hallucination = 0
to_chat(L, "The battle is won. Your bloodlust subsides.")
- for(var/obj/item/twohanded/required/chainsaw/doomslayer/chainsaw in L)
+ for(var/obj/item/chainsaw/doomslayer/chainsaw in L)
qdel(chainsaw)
else
to_chat(L, "You are not yet worthy of passing. Drag a severed head to the barrier to be allowed entry to the hall of champions.")
diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm
index 23f139a3787fe..694b60c13d50e 100644
--- a/code/modules/admin/holder2.dm
+++ b/code/modules/admin/holder2.dm
@@ -31,6 +31,8 @@ GLOBAL_PROTECT(href_token)
//Admin help manager
var/datum/admin_help_ui/admin_interface
+ var/datum/filter_editor/filteriffic
+
/datum/admins/New(datum/admin_rank/R, ckey, force_active = FALSE, protected)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
@@ -94,7 +96,7 @@ GLOBAL_PROTECT(href_token)
var/client/C
if ((C = owner) || (C = GLOB.directory[target]))
disassociate()
- C.verbs += /client/proc/readmin
+ C.add_verb(/client/proc/readmin)
/datum/admins/proc/associate(client/C)
if(IsAdminAdvancedProcCall())
@@ -114,7 +116,7 @@ GLOBAL_PROTECT(href_token)
owner = C
owner.holder = src
owner.add_admin_verbs() //TODO <--- todo what? the proc clearly exists and works since its the backbone to our entire admin system
- owner.verbs -= /client/proc/readmin
+ owner.remove_verb(/client/proc/readmin)
GLOB.admins |= C
/datum/admins/proc/disassociate()
diff --git a/code/modules/admin/outfits.dm b/code/modules/admin/outfits.dm
index 4573e2d22bec0..6d3724206305a 100644
--- a/code/modules/admin/outfits.dm
+++ b/code/modules/admin/outfits.dm
@@ -35,7 +35,7 @@ GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits
var/outfit_file = input("Pick outfit json file:", "File") as null|file
if(!outfit_file)
return
- var/filedata = rustg_file_read(outfit_file)
+ var/filedata = file2text(outfit_file)
var/json = json_decode(filedata)
if(!json)
to_chat(admin,"JSON decode error.")
@@ -240,4 +240,5 @@ GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits
O.ears = text2path(href_list["outfit_ears"])
GLOB.custom_outfits.Add(O)
- message_admins("[key_name(usr)] created \"[O.name]\" outfit!")
\ No newline at end of file
+ message_admins("[key_name(usr)] created \"[O.name]\" outfit!")
+
diff --git a/code/modules/admin/permissionedit.dm b/code/modules/admin/permissionedit.dm
index 8ca949f0bc46c..f7d252fdd94bb 100644
--- a/code/modules/admin/permissionedit.dm
+++ b/code/modules/admin/permissionedit.dm
@@ -140,7 +140,7 @@
to_chat(usr, "Admin Edit blocked: Advanced ProcCall detected.")
return
var/datum/asset/permissions_assets = get_asset_datum(/datum/asset/simple/namespaced/common)
- permissions_assets.send(src)
+ permissions_assets.send(owner)
var/admin_key = href_list["key"]
var/admin_ckey = ckey(admin_key)
var/datum/admins/D = GLOB.admin_datums[admin_ckey]
@@ -207,6 +207,10 @@
edit_admin_permissions()
/datum/admins/proc/add_admin(admin_ckey, admin_key, use_db)
+ if(!check_rights(R_PERMISSIONS))
+ message_admins("[key_name_admin(usr)] attempted to add an admin without sufficient rights.")
+ log_admin("[key_name(usr)] attempted to add an admin without sufficient rights.")
+ return
if(admin_ckey)
. = admin_ckey
else
@@ -249,6 +253,10 @@
qdel(query_add_admin_log)
/datum/admins/proc/remove_admin(admin_ckey, admin_key, use_db, datum/admins/D)
+ if(!check_rights(R_PERMISSIONS))
+ message_admins("[key_name_admin(usr)] attempted to remove an admin without sufficient rights.")
+ log_admin("[key_name(usr)] attempted to remove an admin without sufficient rights.")
+ return
if(alert("Are you sure you want to remove [admin_ckey]?","Confirm Removal","Do it","Cancel") == "Do it")
GLOB.admin_datums -= admin_ckey
GLOB.deadmins -= admin_ckey
@@ -300,6 +308,10 @@
return TRUE
/datum/admins/proc/change_admin_rank(admin_ckey, admin_key, use_db, datum/admins/D, legacy_only)
+ if(!check_rights(R_PERMISSIONS))
+ message_admins("[key_name_admin(usr)] attempted to change an admin's rank without sufficient rights.")
+ log_admin("[key_name(usr)] attempted to change an admin's rank without sufficient rights.")
+ return
var/datum/admin_rank/R
var/list/rank_names = list()
if(!use_db || (use_db && !legacy_only))
@@ -391,6 +403,10 @@
log_admin(m2)
/datum/admins/proc/change_admin_flags(admin_ckey, admin_key, use_db, datum/admins/D, legacy_only)
+ if(!check_rights(R_PERMISSIONS))
+ message_admins("[key_name_admin(usr)] attempted to edit admin flags without sufficient rights.")
+ log_admin("[key_name(usr)] attempted to edit admin flags without sufficient rights.")
+ return
var/new_flags = input_bitfield(usr, "Include permission flags [use_db ? "This will affect ALL admins with this rank." : "This will affect only the current admin [admin_key]"]", "admin_flags", D.rank.include_rights, 350, 590, allowed_edit_list = usr.client.holder.rank.can_edit_rights)
if(isnull(new_flags))
return
@@ -402,7 +418,7 @@
return
var/m1 = "[key_name_admin(usr)] edited the permissions of [use_db ? " rank [D.rank.name] permanently" : "[admin_key] temporarily"]"
var/m2 = "[key_name(usr)] edited the permissions of [use_db ? " rank [D.rank.name] permanently" : "[admin_key] temporarily"]"
- if(use_db || legacy_only)
+ if(use_db && !legacy_only)
var/rank_name = D.rank.name
var/old_flags
var/old_exclude_flags
@@ -470,6 +486,10 @@
log_admin(m2)
/datum/admins/proc/remove_rank(admin_rank)
+ if(!check_rights(R_PERMISSIONS))
+ message_admins("[key_name_admin(usr)] attempted to remove an admin's rank without sufficient rights.")
+ log_admin("[key_name(usr)] attempted to remove an admin's rank without sufficient rights.")
+ return
if(!admin_rank)
return
for(var/datum/admin_rank/R in GLOB.admin_ranks)
diff --git a/code/modules/admin/secrets.dm b/code/modules/admin/secrets.dm
index 5b05339de8586..beb2876c45173 100644
--- a/code/modules/admin/secrets.dm
+++ b/code/modules/admin/secrets.dm
@@ -15,7 +15,7 @@ GLOBAL_DATUM_INIT(admin_secrets, /datum/admin_secrets, new)
ui = new(user, src, "AdminSecretsPanel", "Secrets Panel")
ui.open()
-/datum/admin_secrets/ui_data(mob/user)
+/datum/admin_secrets/ui_static_data(mob/user)
/*
Each command is a list that will be read like [Name, Action(see ui_act)]
"omg but you could have done it like X"
@@ -73,7 +73,7 @@ GLOBAL_DATUM_INIT(admin_secrets, /datum/admin_secrets, new)
list("Summon Events (Toggle)", "events"),
list("There can only be one!", "onlyone"),
list("There can only be one! (40-second delay)", "delayed_onlyone"),
- list("Make all players retarded", "retardify"),
+ list("Make all players intellectually disabled", "dumbify"),
list("Make all players Australian", "aussify"),
list("Egalitarian Station Mode", "eagles"),
list("Anarcho-Capitalist Station Mode", "ancap"),
@@ -89,7 +89,8 @@ GLOBAL_DATUM_INIT(admin_secrets, /datum/admin_secrets, new)
list("Mass Purrbation", "masspurrbation"),
list("Mass Remove Purrbation", "massremovepurrbation"),
list("Fully Immerse Everyone", "massimmerse"),
- list("Un-Fully Immerse Everyone", "unmassimmerse")
+ list("Un-Fully Immerse Everyone", "unmassimmerse"),
+ list("Make All Animals Playable", "animalsentience")
)
if(check_rights(R_DEBUG,0))
@@ -144,7 +145,7 @@ GLOBAL_DATUM_INIT(admin_secrets, /datum/admin_secrets, new)
for(var/mob/living/mob in thunderdome)
qdel(mob) //Clear mobs
for(var/obj/obj in thunderdome)
- if(!istype(obj, /obj/machinery/camera) && !istype(obj, /obj/effect/abstract/proximity_checker))
+ if(!istype(obj, /obj/machinery/camera) && !istype(obj, /obj/effect/abstract/proximity_checker) && !istype(obj, /obj/effect/landmark/arena))
qdel(obj) //Clear objects
var/area/template = GLOB.areas_by_type[/area/tdome/arena_source]
@@ -174,7 +175,7 @@ GLOBAL_DATUM_INIT(admin_secrets, /datum/admin_secrets, new)
set_station_name(new_name)
log_admin("[key_name(usr)] renamed the station to \"[new_name]\".")
message_admins("[key_name_admin(usr)] renamed the station to: [new_name].")
- priority_announce("[command_name()] has renamed the station to \"[new_name]\".")
+ priority_announce("[command_name()] has renamed the station to \"[new_name]\".", sound = SSstation.announcer.get_rand_alert_sound())
if("night_shift_set")
if(!check_rights(R_ADMIN))
return
@@ -200,7 +201,7 @@ GLOBAL_DATUM_INIT(admin_secrets, /datum/admin_secrets, new)
set_station_name(new_name)
log_admin("[key_name(usr)] reset the station name.")
message_admins("[key_name_admin(usr)] reset the station name.")
- priority_announce("[command_name()] has renamed the station to \"[new_name]\".")
+ priority_announce("[command_name()] has renamed the station to \"[new_name]\".", sound = SSstation.announcer.get_rand_alert_sound())
if("list_bombers")
if(!check_rights(R_ADMIN))
@@ -458,7 +459,7 @@ GLOBAL_DATUM_INIT(admin_secrets, /datum/admin_secrets, new)
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Chinese Cartoons"))
message_admins("[key_name_admin(usr)] made everything kawaii.")
for(var/mob/living/carbon/human/H in GLOB.carbon_list)
- SEND_SOUND(H, sound('sound/ai/animes.ogg'))
+ SEND_SOUND(H, sound(SSstation.announcer.event_sounds[ANNOUNCER_ANIMES]))
if(H.dna.species.id == "human")
if(H.dna.features["tail_human"] == "None" || H.dna.features["ears"] == "None")
@@ -477,7 +478,7 @@ GLOBAL_DATUM_INIT(admin_secrets, /datum/admin_secrets, new)
var/obj/item/clothing/under/costume/schoolgirl/I = new seifuku
var/olduniform = H.w_uniform
H.temporarilyRemoveItemFromInventory(H.w_uniform, TRUE, FALSE)
- H.equip_to_slot_or_del(I, SLOT_W_UNIFORM)
+ H.equip_to_slot_or_del(I, ITEM_SLOT_ICLOTHING)
qdel(olduniform)
if(droptype == "Yes")
ADD_TRAIT(I, TRAIT_NODROP, ADMIN_TRAIT)
@@ -510,14 +511,14 @@ GLOBAL_DATUM_INIT(admin_secrets, /datum/admin_secrets, new)
var/datum/round_event/disease_outbreak/DO = E
DO.virus_type = virus
- if("retardify")
+ if("dumbify")
if(!check_rights(R_FUN))
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Mass Braindamage"))
for(var/mob/living/carbon/human/H in GLOB.player_list)
to_chat(H, "You suddenly feel stupid.")
H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 60, 80)
- message_admins("[key_name_admin(usr)] made everybody retarded")
+ message_admins("[key_name_admin(usr)] gave everybody intellectual disability")
if("aussify") //for rimjobtide
if(!check_rights(R_FUN))
@@ -539,7 +540,7 @@ GLOBAL_DATUM_INIT(admin_secrets, /datum/admin_secrets, new)
if(is_station_level(W.z) && !istype(get_area(W), /area/bridge) && !istype(get_area(W), /area/crew_quarters) && !istype(get_area(W), /area/security/prison))
W.req_access = list()
message_admins("[key_name_admin(usr)] activated Egalitarian Station mode")
- priority_announce("CentCom airlock control override activated. Please take this time to get acquainted with your coworkers.", null, 'sound/ai/commandreport.ogg')
+ priority_announce("CentCom airlock control override activated. Please take this time to get acquainted with your coworkers.", null, SSstation.announcer.get_rand_report_sound())
if("ancap")
if(!check_rights(R_FUN))
@@ -548,9 +549,9 @@ GLOBAL_DATUM_INIT(admin_secrets, /datum/admin_secrets, new)
SSeconomy.full_ancap = !SSeconomy.full_ancap
message_admins("[key_name_admin(usr)] toggled Anarcho-capitalist mode")
if(SSeconomy.full_ancap)
- priority_announce("The NAP is now in full effect.", null, 'sound/ai/commandreport.ogg')
+ priority_announce("The NAP is now in full effect.", null, SSstation.announcer.get_rand_report_sound())
else
- priority_announce("The NAP has been revoked.", null, 'sound/ai/commandreport.ogg')
+ priority_announce("The NAP has been revoked.", null, SSstation.announcer.get_rand_report_sound())
@@ -682,6 +683,14 @@ GLOBAL_DATUM_INIT(admin_secrets, /datum/admin_secrets, new)
message_admins("[key_name_admin(usr)] has Un-Fully Immersed \
everyone!")
log_admin("[key_name(usr)] has Un-Fully Immersed everyone.")
+ if("animalsentience")
+ for(var/mob/living/simple_animal/L in GLOB.alive_mob_list)
+ var/turf/T = get_turf(L)
+ if(!T || !is_station_level(T.z))
+ continue
+ if((L in GLOB.player_list) || L.mind || (L.flags_1 & HOLOGRAM_1))
+ continue
+ L.set_playable()
if("flipmovement")
if(!check_rights(R_FUN))
@@ -824,7 +833,7 @@ GLOBAL_DATUM_INIT(admin_secrets, /datum/admin_secrets, new)
if (playlightning)
sound_to_playing_players('sound/magic/lightning_chargeup.ogg')
sleep(80)
- priority_announce(replacetext(announcement, "%STATION%", station_name()))
+ priority_announce(replacetext(announcement, "%STATION%", station_name()), sound = SSstation.announcer.get_rand_alert_sound())
if (playlightning)
sleep(20)
sound_to_playing_players('sound/magic/lightningbolt.ogg')
diff --git a/code/modules/admin/sound_emitter.dm b/code/modules/admin/sound_emitter.dm
index 64ce709dfab10..3944492d9bada 100644
--- a/code/modules/admin/sound_emitter.dm
+++ b/code/modules/admin/sound_emitter.dm
@@ -81,7 +81,7 @@
var/new_label = stripped_input(user, "Choose a new label.", "Sound Emitter")
if(!new_label)
return
- maptext = new_label
+ maptext = MAPTEXT(new_label)
to_chat(user, "Label set to [maptext].")
if(href_list["edit_sound_file"])
var/new_file = input(user, "Choose a sound file.", "Sound Emitter") as null|sound
@@ -135,7 +135,7 @@
hearing_mobs += M
if(SOUND_EMITTER_ZLEVEL)
for(var/mob/M in GLOB.player_list)
- if(M.z == z)
+ if(M.get_virtual_z_level() == get_virtual_z_level())
hearing_mobs += M
if(SOUND_EMITTER_GLOBAL)
hearing_mobs = GLOB.player_list.Copy()
diff --git a/code/modules/admin/sql_ban_system.dm b/code/modules/admin/sql_ban_system.dm
index d23795425be0e..fbf7f7ddecb26 100644
--- a/code/modules/admin/sql_ban_system.dm
+++ b/code/modules/admin/sql_ban_system.dm
@@ -125,7 +125,10 @@
C.ban_cache[query_build_ban_cache.item[1]] = TRUE
qdel(query_build_ban_cache)
-/datum/admins/proc/ban_panel(player_key, player_ip, player_cid, role, duration = 1440, applies_to_admins, reason, edit_id, page, admin_key, global_ban)
+/datum/admins/proc/ban_panel(player_key, player_ip, player_cid, role, duration = 1440, applies_to_admins, reason, edit_id, page, admin_key, global_ban = TRUE)
+ var/suppressor
+ if(check_rights(R_SUPPRESS))
+ suppressor = TRUE
var/panel_height = 620
if(edit_id)
panel_height = 240
@@ -149,12 +152,17 @@
+ [(suppressor && !edit_id) ? "" : ""]
"
- var/list/long_job_lists = list("Civilian" = GLOB.civilian_positions,
+ var/list/long_job_lists = list(("Civilian" = GLOB.civilian_positions | "Gimmick"),
"Ghost and Other Roles" = list(ROLE_BRAINWASHED, ROLE_DEATHSQUAD, ROLE_DRONE, ROLE_LAVALAND, ROLE_MIND_TRANSFER, ROLE_POSIBRAIN, ROLE_SENTIENCE),
"Antagonist Positions" = list(ROLE_ABDUCTOR, ROLE_ALIEN, ROLE_BLOB,
- ROLE_BROTHER, ROLE_CHANGELING, ROLE_CULTIST,
+ ROLE_BROTHER, ROLE_CHANGELING, ROLE_CULTIST, ROLE_HERETIC,
ROLE_DEVIL, ROLE_INTERNAL_AFFAIRS, ROLE_MALF,
ROLE_MONKEY, ROLE_NINJA, ROLE_OPERATIVE,
ROLE_SERVANT_OF_RATVAR,
ROLE_OVERTHROW, ROLE_REV, ROLE_REVENANT,
ROLE_REV_HEAD, ROLE_SYNDICATE,
- ROLE_TRAITOR, ROLE_WIZARD, ROLE_HIVE, ROLE_GANG)) //ROLE_REV_HEAD is excluded from this because rev jobbans are handled by ROLE_REV
+ ROLE_TRAITOR, ROLE_WIZARD, ROLE_HIVE, ROLE_GANG, ROLE_TERATOMA)) //ROLE_REV_HEAD is excluded from this because rev jobbans are handled by ROLE_REV
for(var/department in long_job_lists)
output += "
"
break_counter = 0
@@ -358,7 +366,7 @@
var/player_cid
var/use_last_connection = FALSE
var/applies_to_admins = FALSE
- var/global_ban = FALSE
+ var/global_ban = TRUE
var/duration
var/interval
var/severity
@@ -372,8 +380,18 @@
var/old_globalban
var/page
var/admin_key
+ var/redact
var/list/changes = list()
var/list/roles_to_ban = list()
+ if(href_list["redactioncheck"])
+ if(check_rights(R_SUPPRESS))
+ if(!edit_id)
+ redact = TRUE
+ else
+ error_state += "Bans may not have their suppression flag edited. If a ban requiring suppression was accidently issued without it, contact [CONFIG_GET(string/hostedby)] immediately."
+ else
+ error_state += "You have attempted to issue a suppressed ban without permission, This incident has been logged."
+ log_admin_private("SUPPRESS: [key_name(usr)] ATTEMPTED TO ISSUE A SUPPRESSED BAN WITHOUT THE REQUISITE RIGHT!")
if(href_list["keycheck"])
player_key = href_list["keytext"]
if(!player_key)
@@ -399,10 +417,14 @@
if(use_last_connection && !ip_check && !cid_check)
error_state += "Use last connection was ticked, but neither IP nor CID was."
if(href_list["applyadmins"])
+ if(redact)
+ error_state += "Admin bans can not be suppressed."
applies_to_admins = TRUE
switch(href_list["radioservban"])
if("local")
global_ban = FALSE
+ if(redact)
+ error_state += "Suppressed bans must be global."
if("global")
global_ban = TRUE
switch(href_list["radioduration"])
@@ -463,20 +485,33 @@
roles_to_ban |= key
else
error_state += "No ban type was selected."
+ if((href_list["radioban"] != "server") && redact)
+ error_state += "Suppression may only be applied to server bans."
if(error_state.len)
to_chat(usr, "Ban not [edit_id ? "edited" : "created"] because the following errors were present:\n[error_state.Join("\n")]")
return
if(edit_id)
edit_ban(edit_id, player_key, ip_check, player_ip, cid_check, player_cid, use_last_connection, applies_to_admins, duration, interval, reason, global_ban, mirror_edit, old_key, old_ip, old_cid, old_applies, old_globalban, page, admin_key, changes)
else
- create_ban(player_key, ip_check, player_ip, cid_check, player_cid, use_last_connection, applies_to_admins, duration, interval, severity, reason, global_ban, roles_to_ban)
+ create_ban(player_key, ip_check, player_ip, cid_check, player_cid, use_last_connection, applies_to_admins, duration, interval, severity, reason, global_ban, roles_to_ban, redact)
-/datum/admins/proc/create_ban(player_key, ip_check, player_ip, cid_check, player_cid, use_last_connection, applies_to_admins, duration, interval, severity, reason, global_ban, list/roles_to_ban)
+/datum/admins/proc/create_ban(player_key, ip_check, player_ip, cid_check, player_cid, use_last_connection, applies_to_admins, duration, interval, severity, reason, global_ban, list/roles_to_ban, redact = 0)
if(!check_rights(R_BAN))
return
if(!SSdbcore.Connect())
to_chat(usr, "Failed to establish database connection.")
return
+
+ if(cid_check && config.protected_cids.Find(player_cid))
+ if(alert(usr, "CID [player_cid] is listed as protected for the following reason: [config.protected_cids[player_cid]], Are you sure you want to restrict this CID? THIS WILL PROBABLY CATCH LEGITIMATE PLAYERS.", "Protected CID", "Yes", "No", "Cancel") != "Yes")
+ return
+ var/kn = key_name(usr)
+ //Log the shit out of this and scream bloody murder to anyone who will listen.
+ send2tgs("CID PROTECTION BYPASS", "[kn] Has overridden CID protection for a ban on CID [player_cid]!")
+ message_admins("[kn] Has overridden CID protection for a ban on CID [player_cid]!")
+ log_admin_private("[kn] Has overridden CID protection for a ban on CID [player_cid]!")
+ if(redact && alert(usr, "You are about to issue a Suppressed ban, This will require direct database editing to revoke, ARE YOU SURE?", "Protected CID", "Yes", "No", "Cancel") != "Yes")
+ return
var/player_ckey = ckey(player_key)
if(player_ckey)
var/datum/DBQuery/query_create_ban_get_player = SSdbcore.NewQuery({"
@@ -553,6 +588,20 @@
"expiration_time" = "IF(? IS NULL, NULL, NOW() + INTERVAL ? [interval])"
)
var/sql_ban = list()
+ //I'm going to crosscheck this one last time because this is playing with fire.
+ if(redact)
+ if(!check_rights(R_SUPPRESS))
+ to_chat(usr, "You have attempted to register a suppressed ban without the correct access, this incident has been logged, and the ban has been aborted.")
+ log_admin_private("SUPPRESS: [key_name(usr)] ATTEMPTED TO ISSUE A SUPPRESSED BAN WITHOUT THE REQUISITE RIGHT!")
+ return
+ if(roles_to_ban[1] != "Server") //This should never happen. Still checking it.
+ to_chat(usr, "You have attempted to directly register a suppressed ban that is not a server ban, this incident has been logged, and the ban has been aborted.")
+ log_admin_private("SUPPRESS: [key_name(usr)] ATTEMPTED TO MANUALLY ISSUE A SUPPRESSED NON-SERVER BAN!")
+ return
+ if(applies_to_admins)
+ to_chat(usr, "You have attempted to directly register a suppressed ban that affects admins, this incident has been logged, and the ban has been aborted.")
+ log_admin_private("SUPPRESS: [key_name(usr)] ATTEMPTED TO MANUALLY ISSUE A SUPPRESSED ADMIN BAN!")
+ return
for(var/role in roles_to_ban)
sql_ban += list(list(
"server_name" = CONFIG_GET(string/serversqlname),
@@ -571,26 +620,36 @@
"a_computerid" = admin_cid,
"who" = who,
"adminwho" = adminwho,
- "global_ban" = global_ban
+ "global_ban" = global_ban,
+ "hidden" = redact
))
if(!SSdbcore.MassInsert(format_table_name("ban"), sql_ban, warn = TRUE, special_columns = special_columns))
return
var/target = ban_target_string(player_key, player_ip, player_cid)
var/msg = "has created a [global_ban ? "global" : "local"] [isnull(duration) ? "permanent" : "temporary [time_message]"] [applies_to_admins ? "admin " : ""][roles_to_ban[1] == "Server" ? "server ban" : "role ban from [roles_to_ban.len] roles"] for [target]."
- log_admin_private("[kn] [msg][roles_to_ban[1] == "Server" ? "" : " Roles: [roles_to_ban.Join(", ")]"] Reason: [reason]")
- message_admins("[kna] [msg][roles_to_ban[1] == "Server" ? "" : " Roles: [roles_to_ban.Join("\n")]"]\nReason: [reason]")
- if(applies_to_admins)
- send2irc("BAN ALERT","[kn] [msg]")
- if(player_ckey)
- create_message("note", player_ckey, admin_ckey, note_reason, null, null, 0, 0, null, 0, severity)
+ if(!redact)
+ log_admin_private("[kn] [msg][roles_to_ban[1] == "Server" ? "" : " Roles: [roles_to_ban.Join(", ")]"] Reason: [reason]")
+ message_admins("[kna] [msg][roles_to_ban[1] == "Server" ? "" : " Roles: [roles_to_ban.Join("\n")]"]\nReason: [reason]")
+ else
+ log_admin_private("SUPPRESS: [kn] has created a suppressed ban.")
+ to_chat(usr, "Ban issued successfuly, This has not been announced to other admins.")
+ if(applies_to_admins && !redact) //Should never happen.
+ send2tgs("BAN ALERT","[kn] [msg]")
+ if(player_ckey && !redact)
+ create_message("note", player_ckey, admin_ckey, note_reason, null, null, 0, 0, null, -1, severity)
var/client/C = GLOB.directory[player_ckey]
var/datum/admin_help/AH = admin_ticket_log(player_ckey, msg)
var/appeal_url = "No ban appeal url set!"
appeal_url = CONFIG_GET(string/banappeals)
var/is_admin = FALSE
+ var/special_prefix = ""
+ if(redact)
+ special_prefix = "hard "
+ if(applies_to_admins)
+ special_prefix = "admin "
if(C)
build_ban_cache(C)
- to_chat(C, "You have been [applies_to_admins ? "admin " : ""]banned by [usr.client.key] from [roles_to_ban[1] == "Server" ? "the server" : " Roles: [roles_to_ban.Join(", ")]"].\nReason: [reason] This ban is [isnull(duration) ? "permanent." : "temporary, it will be removed in [time_message]."] [global_ban ? "This ban applies to all of our servers." : "This is a single-server ban, and only applies to this server."] The round ID is [GLOB.round_id]. To appeal this ban go to [appeal_url]")
+ to_chat(C, "You have been [special_prefix]banned by [usr.client.key] from [roles_to_ban[1] == "Server" ? "the server" : " Roles: [roles_to_ban.Join(", ")]"].\nReason: [reason] This ban is [isnull(duration) ? "permanent." : "temporary, it will be removed in [time_message]."] [global_ban ? "This ban applies to all of our servers." : "This is a single-server ban, and only applies to this server."] The round ID is [GLOB.round_id]. [redact ? "This ban may not be appealed." : "To appeal this ban go to [appeal_url]"]")
if(GLOB.admin_datums[C.ckey] || GLOB.deadmins[C.ckey])
is_admin = TRUE
if(roles_to_ban[1] == "Server" && (!is_admin || (is_admin && applies_to_admins)))
@@ -600,7 +659,7 @@
for(var/client/i in GLOB.clients - C)
if(i.address == player_ip || i.computer_id == player_cid)
build_ban_cache(i)
- to_chat(i, "You have been [applies_to_admins ? "admin " : ""]banned by [usr.client.key] from [roles_to_ban[1] == "Server" ? "the server" : " Roles: [roles_to_ban.Join(", ")]"].\nReason: [reason] This ban is [isnull(duration) ? "permanent." : "temporary, it will be removed in [time_message]."] [global_ban ? "This ban applies to all of our servers." : "This is a single-server ban, and only applies to this server."] The round ID is [GLOB.round_id]. To appeal this ban go to [appeal_url]")
+ to_chat(i, "You have been [special_prefix]banned by [usr.client.key] from [roles_to_ban[1] == "Server" ? "the server" : " Roles: [roles_to_ban.Join(", ")]"].\nReason: [reason] This ban is [isnull(duration) ? "permanent." : "temporary, it will be removed in [time_message]."] [global_ban ? "This ban applies to all of our servers." : "This is a single-server ban, and only applies to this server."] The round ID is [GLOB.round_id]. To appeal this ban go to [appeal_url]")
if(GLOB.admin_datums[i.ckey] || GLOB.deadmins[i.ckey])
is_admin = TRUE
if(roles_to_ban[1] == "Server" && (!is_admin || (is_admin && applies_to_admins)))
@@ -741,8 +800,7 @@
if(unban_datetime)
output += " Unbanned by [unban_key] on [unban_datetime] during round #[unban_round_id]."
output += "
"
@@ -793,6 +851,16 @@
if(!SSdbcore.Connect())
to_chat(usr, "Failed to establish database connection.")
return
+
+ if(cid_check && config.protected_cids.Find(player_cid))
+ if(alert(usr, "CID [player_cid] is listed as protected for the following reason: [config.protected_cids[player_cid]], Are you sure you want to restrict this CID? THIS WILL PROBABLY CATCH LEGITIMATE PLAYERS.", "Protected CID", "Yes", "No", "Cancel") != "Yes")
+ return
+ var/kn = key_name(usr)
+ //Log the shit out of this and scream bloody murder to anyone who will listen.
+ send2tgs("CID PROTECTION BYPASS", "[kn] Has overridden CID protection for a ban on CID [player_cid]!")
+ message_admins("[kn] Has overridden CID protection for a ban on CID [player_cid]!")
+ log_admin_private("[kn] Has overridden CID protection for a ban on CID [player_cid]!")
+
var/player_ckey = ckey(player_key)
var/bantime
if(player_ckey)
@@ -910,7 +978,7 @@
log_admin_private("[kn] has edited the [changes_keys_text] of a ban for [old_key ? "[old_key]" : "[old_ip]-[old_cid]"].") //if a ban doesn't have a key it must have an ip and/or a cid to have reached this point normally
message_admins("[kna] has edited the [changes_keys_text] of a ban for [old_key ? "[old_key]" : "[old_ip]-[old_cid]"].")
if(changes["Applies to admins"])
- send2irc("BAN ALERT","[kn] has edited a ban for [old_key ? "[old_key]" : "[old_ip]-[old_cid]"] to [applies_to_admins ? "" : "not"]affect admins")
+ send2tgs("BAN ALERT","[kn] has edited a ban for [old_key ? "[old_key]" : "[old_ip]-[old_cid]"] to [applies_to_admins ? "" : "not"]affect admins")
var/client/C = GLOB.directory[old_key]
if(C)
build_ban_cache(C)
diff --git a/code/modules/admin/sql_message_system.dm b/code/modules/admin/sql_message_system.dm
index 309c38b3177fb..bffad896c0069 100644
--- a/code/modules/admin/sql_message_system.dm
+++ b/code/modules/admin/sql_message_system.dm
@@ -73,6 +73,8 @@
return
expiry = query_validate_expire_time.item[1]
qdel(query_validate_expire_time)
+ if(expiry == -1)
+ expiry = null //This is so garbage and hacky.
if(type == "note" && isnull(note_severity))
note_severity = input("Set the severity of the note.", "Severity", null, null) as null|anything in list("High", "Medium", "Minor", "None")
if(!note_severity)
@@ -503,7 +505,7 @@
alphatext = "filter: alpha(opacity=[alpha]); opacity: [alpha/100];"
var/list/data = list("
")
if(severity)
- data += " "
+ data += " "
data += "[timestamp] | [server_name] | [admin_key][secret ? " | - Secret" : ""]"
if(expire_timestamp)
data += " | Expires [expire_timestamp]"
@@ -661,7 +663,7 @@
qdel(query_message_read)
if("watchlist entry")
message_admins("Notice: [key_name_admin(target_ckey)] has been on the watchlist since [timestamp] and has just connected - Reason: [text]")
- send2irc_adminless_only("Watchlist", "[key_name(target_ckey)] is on the watchlist and has just connected - Reason: [text]")
+ send2tgs_adminless_only("Watchlist", "[key_name(target_ckey)] is on the watchlist and has just connected - Reason: [text]")
if("memo")
output += "Memo by [admin_key] on [timestamp]"
if(editor_key)
diff --git a/code/modules/admin/team_panel.dm b/code/modules/admin/team_panel.dm
index 139785f72e041..ce3aa031bfd34 100644
--- a/code/modules/admin/team_panel.dm
+++ b/code/modules/admin/team_panel.dm
@@ -147,11 +147,12 @@
//qdel maybe
/datum/team/proc/admin_add_member(mob/user)
- var/list/minds = list()
- for(var/mob/M in GLOB.mob_list)
- if(M.mind)
- minds |= M.mind
- var/datum/mind/value = input("Select new member:", "New team member", null) as null|anything in sortNames(minds)
+ var/list/candidates = list()
+ for(var/mob/M in GLOB.player_list)
+ if(M.mind?.special_role)
+ continue
+ candidates += M.mind
+ var/datum/mind/value = input("Select new member:", "New team member", null) as null|anything in sortNames(candidates)
if (!value)
return
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index 3cac98d0ddb70..ce4d452ca7541 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -65,33 +65,37 @@
return
switch(href_list["makeAntag"])
if("traitors")
- if(src.makeTraitors())
- message_admins("[key_name_admin(usr)] created traitors.")
- log_admin("[key_name(usr)] created traitors.")
+ var/maxCount = input("Set number of Traitors","Set Traitor Count (max)",1) as num|null
+ if(src.makeTraitors(maxCount))
+ message_admins("[key_name_admin(usr)] created [maxCount] traitor(s).")
+ log_admin("[key_name(usr)] created [maxCount] traitor(s).")
else
- message_admins("[key_name_admin(usr)] tried to create traitors. Unfortunately, there were no candidates available.")
- log_admin("[key_name(usr)] failed to create traitors.")
+ message_admins("[key_name_admin(usr)] tried to create [maxCount] traitor(s). Unfortunately, there were no candidates available.")
+ log_admin("[key_name(usr)] failed to create [maxCount] traitor(s).")
if("changelings")
- if(src.makeChangelings())
- message_admins("[key_name(usr)] created changelings.")
- log_admin("[key_name(usr)] created changelings.")
+ var/maxCount = input("Set number of Changelings","Set Changeling Count (max)",1) as num|null
+ if(src.makeChangelings(maxCount))
+ message_admins("[key_name(usr)] created [maxCount] changelings.")
+ log_admin("[key_name(usr)] created [maxCount] changelings.")
else
- message_admins("[key_name_admin(usr)] tried to create changelings. Unfortunately, there were no candidates available.")
- log_admin("[key_name(usr)] failed to create changelings.")
+ message_admins("[key_name_admin(usr)] tried to create [maxCount] changelings. Unfortunately, there were no candidates available.")
+ log_admin("[key_name(usr)] failed to create [maxCount] changelings.")
if("revs")
- if(src.makeRevs())
- message_admins("[key_name(usr)] started a revolution.")
- log_admin("[key_name(usr)] started a revolution.")
+ var/maxCount = input("Set number of Revolutionaries","Set Revolutionaries Count (max)",1) as num|null
+ if(src.makeRevs(maxCount))
+ message_admins("[key_name(usr)] started a revolution with [maxCount] freedom fighters.")
+ log_admin("[key_name(usr)] started a [maxCount] freedom fighters.")
else
- message_admins("[key_name_admin(usr)] tried to start a revolution. Unfortunately, there were no candidates available.")
- log_admin("[key_name(usr)] failed to start a revolution.")
+ message_admins("[key_name_admin(usr)] tried to start a revolution with [maxCount] freedom fighters. Unfortunately, there were no candidates available.")
+ log_admin("[key_name(usr)] failed to start a revolution with [maxCount] freedom fighters.")
if("cult")
- if(src.makeCult())
- message_admins("[key_name(usr)] started a cult.")
- log_admin("[key_name(usr)] started a cult.")
+ var/maxCount = input("Set number of Cultists","Set Cultist Count (max)",1) as num|null
+ if(src.makeCult(maxCount))
+ message_admins("[key_name(usr)] started a cult with [maxCount] cultists.")
+ log_admin("[key_name(usr)] started a cult with [maxCount] cultists.")
else
- message_admins("[key_name_admin(usr)] tried to start a cult. Unfortunately, there were no candidates available.")
- log_admin("[key_name(usr)] failed to start a cult.")
+ message_admins("[key_name_admin(usr)] tried to start a cult with [maxCount] cultists. Unfortunately, there were no candidates available.")
+ log_admin("[key_name(usr)] failed to start a cult with [maxCount] cultists.")
if("wizard")
message_admins("[key_name(usr)] is creating a wizard...")
if(src.makeWizard())
@@ -102,12 +106,13 @@
log_admin("[key_name(usr)] failed to create a wizard.")
if("nukeops")
message_admins("[key_name(usr)] is creating a nuke team...")
- if(src.makeNukeTeam())
- message_admins("[key_name(usr)] created a nuke team.")
- log_admin("[key_name(usr)] created a nuke team.")
+ var/maxCount = input("Set number of Nuke OPs","Set Nuke OP Count (max)",5) as num|null
+ if(src.makeNukeTeam(maxCount))
+ message_admins("[key_name(usr)] created a nuke team with [maxCount] operatives")
+ log_admin("[key_name(usr)] created a nuke team with [maxCount] operatives")
else
- message_admins("[key_name_admin(usr)] tried to create a nuke team. Unfortunately, there were not enough candidates available.")
- log_admin("[key_name(usr)] failed to create a nuke team.")
+ message_admins("[key_name_admin(usr)] tried to create a nuke team with [maxCount] operatives Unfortunately, there were not enough candidates available.")
+ log_admin("[key_name(usr)] failed to create a nuke team with [maxCount] operatives.")
if("ninja")
message_admins("[key_name(usr)] spawned a ninja.")
log_admin("[key_name(usr)] spawned a ninja.")
@@ -145,7 +150,7 @@
message_admins("[key_name(usr)] created an abductor team.")
log_admin("[key_name(usr)] created an abductor team.")
else
- message_admins("[key_name_admin(usr)] tried to create an abductor team. Unfortunatly there were not enough candidates available.")
+ message_admins("[key_name_admin(usr)] tried to create an abductor team. Unfortunately there were not enough candidates available.")
log_admin("[key_name(usr)] failed to create an abductor team.")
if("revenant")
if(src.makeRevenant())
@@ -173,6 +178,7 @@
return
if("No")
event.announceChance = 0
+ event.on_admin_trigger()
event.processing = TRUE
message_admins("[key_name_admin(usr)] has triggered an event. ([E.name])")
log_admin("[key_name(usr)] has triggered an event. ([E.name])")
@@ -206,7 +212,7 @@
return
if(SSticker && SSticker.mode)
return alert(usr, "The game has already started.", null, null, null, null)
- if(GLOB.master_mode != "dynamic")
+ if(!SSticker.is_mode("dynamic"))
return alert(usr, "The game mode has to be dynamic mode.", null, null, null, null)
var/roundstart_rules = list()
for (var/rule in subtypesof(/datum/dynamic_ruleset/roundstart))
@@ -241,15 +247,16 @@
return
if(!SSticker || !SSticker.mode)
return alert(usr, "The game must start first.", null, null, null, null)
- if(GLOB.master_mode != "dynamic")
+ if(!SSticker.is_mode("dynamic"))
return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
var/latejoin_rules = list()
+ var/datum/game_mode/dynamic/mode = SSticker.mode
for (var/rule in subtypesof(/datum/dynamic_ruleset/latejoin))
var/datum/dynamic_ruleset/latejoin/newrule = new rule()
+ mode.configure_ruleset(newrule)
latejoin_rules[newrule.name] = newrule
var/added_rule = input(usr,"What ruleset do you want to force upon the next latejoiner? This will bypass threat level and population restrictions.", "Rigging Latejoin", null) as null|anything in latejoin_rules
if (added_rule)
- var/datum/game_mode/dynamic/mode = SSticker.mode
mode.forced_latejoin_rule = latejoin_rules[added_rule]
log_admin("[key_name(usr)] set [added_rule] to proc on the next latejoin.")
message_admins("[key_name(usr)] set [added_rule] to proc on the next latejoin.", 1)
@@ -270,15 +277,16 @@
return
if(!SSticker || !SSticker.mode)
return alert(usr, "The game must start first.", null, null, null, null)
- if(GLOB.master_mode != "dynamic")
+ if(!SSticker.is_mode("dynamic"))
return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
var/midround_rules = list()
+ var/datum/game_mode/dynamic/mode = SSticker.mode
for (var/rule in subtypesof(/datum/dynamic_ruleset/midround))
var/datum/dynamic_ruleset/midround/newrule = new rule()
+ mode.configure_ruleset(newrule)
midround_rules[newrule.name] = rule
var/added_rule = input(usr,"What ruleset do you want to force right now? This will bypass threat level and population restrictions.", "Execute Ruleset", null) as null|anything in midround_rules
if (added_rule)
- var/datum/game_mode/dynamic/mode = SSticker.mode
log_admin("[key_name(usr)] executed the [added_rule] ruleset.")
message_admins("[key_name(usr)] executed the [added_rule] ruleset.", 1)
mode.picking_specific_rule(midround_rules[added_rule],1)
@@ -289,122 +297,16 @@
if(SSticker && SSticker.mode)
return alert(usr, "The game has already started.", null, null, null, null)
- if(GLOB.master_mode != "dynamic")
- return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
-
- dynamic_mode_options(usr)
-
- else if(href_list["f_dynamic_roundstart_centre"])
- if(!check_rights(R_ADMIN))
- return
- if(SSticker && SSticker.mode)
- return alert(usr, "The game has already started.", null, null, null, null)
- if(GLOB.master_mode != "dynamic")
- return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
-
- var/new_centre = input(usr,"Change the centre of the dynamic mode threat curve. A negative value will give a more peaceful round ; a positive value, a round with higher threat. Any number between -5 and +5 is allowed.", "Change curve centre", null) as num
- if (new_centre < -5 || new_centre > 5)
- return alert(usr, "Only values between -5 and +5 are allowed.", null, null, null, null)
-
- log_admin("[key_name(usr)] changed the distribution curve center to [new_centre].")
- message_admins("[key_name(usr)] changed the distribution curve center to [new_centre]", 1)
- GLOB.dynamic_curve_centre = new_centre
- dynamic_mode_options(usr)
-
- else if(href_list["f_dynamic_roundstart_width"])
- if(!check_rights(R_ADMIN))
- return
- if(SSticker && SSticker.mode)
- return alert(usr, "The game has already started.", null, null, null, null)
- if(GLOB.master_mode != "dynamic")
- return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
-
- var/new_width = input(usr,"Change the width of the dynamic mode threat curve. A higher value will favour extreme rounds ; a lower value, a round closer to the average. Any Number between 0.5 and 4 are allowed.", "Change curve width", null) as num
- if (new_width < 0.5 || new_width > 4)
- return alert(usr, "Only values between 0.5 and +2.5 are allowed.", null, null, null, null)
-
- log_admin("[key_name(usr)] changed the distribution curve width to [new_width].")
- message_admins("[key_name(usr)] changed the distribution curve width to [new_width]", 1)
- GLOB.dynamic_curve_width = new_width
- dynamic_mode_options(usr)
-
- else if(href_list["f_dynamic_roundstart_latejoin_min"])
- if(!check_rights(R_ADMIN))
- return
- if(SSticker && SSticker.mode)
- return alert(usr, "The game has already started.", null, null, null, null)
- if(GLOB.master_mode != "dynamic")
- return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
- var/new_min = input(usr,"Change the minimum delay of latejoin injection in minutes.", "Change latejoin injection delay minimum", null) as num
- if(new_min <= 0)
- return alert(usr, "The minimum can't be zero or lower.", null, null, null, null)
- if((new_min MINUTES) > GLOB.dynamic_latejoin_delay_max)
- return alert(usr, "The minimum must be lower than the maximum.", null, null, null, null)
-
- log_admin("[key_name(usr)] changed the latejoin injection minimum delay to [new_min] minutes.")
- message_admins("[key_name(usr)] changed the latejoin injection minimum delay to [new_min] minutes", 1)
- GLOB.dynamic_latejoin_delay_min = (new_min MINUTES)
- dynamic_mode_options(usr)
-
- else if(href_list["f_dynamic_roundstart_latejoin_max"])
- if(!check_rights(R_ADMIN))
- return
- if(SSticker && SSticker.mode)
- return alert(usr, "The game has already started.", null, null, null, null)
- if(GLOB.master_mode != "dynamic")
- return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
- var/new_max = input(usr,"Change the maximum delay of latejoin injection in minutes.", "Change latejoin injection delay maximum", null) as num
- if(new_max <= 0)
- return alert(usr, "The maximum can't be zero or lower.", null, null, null, null)
- if((new_max MINUTES) < GLOB.dynamic_latejoin_delay_min)
- return alert(usr, "The maximum must be higher than the minimum.", null, null, null, null)
-
- log_admin("[key_name(usr)] changed the latejoin injection maximum delay to [new_max] minutes.")
- message_admins("[key_name(usr)] changed the latejoin injection maximum delay to [new_max] minutes", 1)
- GLOB.dynamic_latejoin_delay_max = (new_max MINUTES)
- dynamic_mode_options(usr)
-
- else if(href_list["f_dynamic_roundstart_midround_min"])
- if(!check_rights(R_ADMIN))
- return
- if(SSticker && SSticker.mode)
- return alert(usr, "The game has already started.", null, null, null, null)
- if(GLOB.master_mode != "dynamic")
+ if(!SSticker.is_mode("dynamic"))
return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
- var/new_min = input(usr,"Change the minimum delay of midround injection in minutes.", "Change midround injection delay minimum", null) as num
- if(new_min <= 0)
- return alert(usr, "The minimum can't be zero or lower.", null, null, null, null)
- if((new_min MINUTES) > GLOB.dynamic_midround_delay_max)
- return alert(usr, "The minimum must be lower than the maximum.", null, null, null, null)
-
- log_admin("[key_name(usr)] changed the midround injection minimum delay to [new_min] minutes.")
- message_admins("[key_name(usr)] changed the midround injection minimum delay to [new_min] minutes", 1)
- GLOB.dynamic_midround_delay_min = (new_min MINUTES)
- dynamic_mode_options(usr)
- else if(href_list["f_dynamic_roundstart_midround_max"])
- if(!check_rights(R_ADMIN))
- return
- if(SSticker && SSticker.mode)
- return alert(usr, "The game has already started.", null, null, null, null)
- if(GLOB.master_mode != "dynamic")
- return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
- var/new_max = input(usr,"Change the maximum delay of midround injection in minutes.", "Change midround injection delay maximum", null) as num
- if(new_max <= 0)
- return alert(usr, "The maximum can't be zero or lower.", null, null, null, null)
- if((new_max MINUTES) > GLOB.dynamic_midround_delay_max)
- return alert(usr, "The maximum must be higher than the minimum.", null, null, null, null)
-
- log_admin("[key_name(usr)] changed the midround injection maximum delay to [new_max] minutes.")
- message_admins("[key_name(usr)] changed the midround injection maximum delay to [new_max] minutes", 1)
- GLOB.dynamic_midround_delay_max = (new_max MINUTES)
dynamic_mode_options(usr)
else if(href_list["f_dynamic_force_extended"])
if(!check_rights(R_ADMIN))
return
- if(GLOB.master_mode != "dynamic")
+ if(!SSticker.is_mode("dynamic"))
return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
GLOB.dynamic_forced_extended = !GLOB.dynamic_forced_extended
@@ -416,7 +318,7 @@
if(!check_rights(R_ADMIN))
return
- if(GLOB.master_mode != "dynamic")
+ if(!SSticker.is_mode("dynamic"))
return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
GLOB.dynamic_no_stacking = !GLOB.dynamic_no_stacking
@@ -424,23 +326,11 @@
message_admins("[key_name(usr)] set 'no_stacking' to [GLOB.dynamic_no_stacking].")
dynamic_mode_options(usr)
- else if(href_list["f_dynamic_classic_secret"])
- if(!check_rights(R_ADMIN))
- return
-
- if(GLOB.master_mode != "dynamic")
- return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
-
- GLOB.dynamic_classic_secret = !GLOB.dynamic_classic_secret
- log_admin("[key_name(usr)] set 'classic_secret' to [GLOB.dynamic_classic_secret].")
- message_admins("[key_name(usr)] set 'classic_secret' to [GLOB.dynamic_classic_secret].")
- dynamic_mode_options(usr)
-
else if(href_list["f_dynamic_stacking_limit"])
if(!check_rights(R_ADMIN))
return
- if(GLOB.master_mode != "dynamic")
+ if(!SSticker.is_mode("dynamic"))
return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
GLOB.dynamic_stacking_limit = input(usr,"Change the threat limit at which round-endings rulesets will start to stack.", "Change stacking limit", null) as num
@@ -448,25 +338,6 @@
message_admins("[key_name(usr)] set 'stacking_limit' to [GLOB.dynamic_stacking_limit].")
dynamic_mode_options(usr)
- else if(href_list["f_dynamic_high_pop_limit"])
- if(!check_rights(R_ADMIN))
- return
-
- if(SSticker && SSticker.mode)
- return alert(usr, "The game has already started.", null, null, null, null)
-
- if(GLOB.master_mode != "dynamic")
- return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
-
- var/new_value = input(usr, "Enter the high-pop override threshold for dynamic mode.", "High pop override") as num
- if (new_value < 0)
- return alert(usr, "Only positive values allowed!", null, null, null, null)
- GLOB.dynamic_high_pop_limit = new_value
-
- log_admin("[key_name(usr)] set 'high_pop_limit' to [GLOB.dynamic_high_pop_limit].")
- message_admins("[key_name(usr)] set 'high_pop_limit' to [GLOB.dynamic_high_pop_limit].")
- dynamic_mode_options(usr)
-
else if(href_list["f_dynamic_forced_threat"])
if(!check_rights(R_ADMIN))
return
@@ -474,7 +345,7 @@
if(SSticker && SSticker.mode)
return alert(usr, "The game has already started.", null, null, null, null)
- if(GLOB.master_mode != "dynamic")
+ if(!SSticker.is_mode("dynamic"))
return alert(usr, "The game mode has to be dynamic mode!", null, null, null, null)
var/new_value = input(usr, "Enter the forced threat level for dynamic mode.", "Forced threat level") as num
@@ -876,17 +747,20 @@
return
if (SSticker.HasRoundStarted())
- if (askuser(usr, "The game has already started. Would you like to save this as the default mode effective next round?", "Save mode", "Yes", "Cancel", Timeout = null) == 1)
- SSticker.save_mode(href_list["c_mode2"])
+ alert("The round has already started.")
+ HandleCMode()
+ return
+ if(SSticker.gamemode_hotswap_disabled)
+ alert("A gamemode has already loaded maps and cannot be changed!")
HandleCMode()
return
GLOB.master_mode = href_list["c_mode2"]
+ //Disable presetup so their gamemode gets loaded.
+ SSticker.pre_setup_completed = FALSE
log_admin("[key_name(usr)] set the mode as [GLOB.master_mode].")
message_admins("[key_name_admin(usr)] set the mode as [GLOB.master_mode].")
to_chat(world, "The mode is now: [GLOB.master_mode]")
Game() // updates the main game menu
- if (askuser(usr, "Would you like to save this as the default mode for the server?", "Save mode", "Yes", "No", Timeout = null) == 1)
- SSticker.save_mode(GLOB.master_mode)
HandleCMode()
else if(href_list["f_secret2"])
@@ -1103,8 +977,8 @@
if(ishuman(L))
var/mob/living/carbon/human/observer = L
- observer.equip_to_slot_or_del(new /obj/item/clothing/under/suit/black(observer), SLOT_W_UNIFORM)
- observer.equip_to_slot_or_del(new /obj/item/clothing/shoes/sneakers/black(observer), SLOT_SHOES)
+ observer.equip_to_slot_or_del(new /obj/item/clothing/under/suit/black(observer), ITEM_SLOT_ICLOTHING)
+ observer.equip_to_slot_or_del(new /obj/item/clothing/shoes/sneakers/black(observer), ITEM_SLOT_FEET)
L.Unconscious(100)
sleep(5)
L.forceMove(pick(GLOB.tdomeobserve))
@@ -1613,16 +1487,18 @@
R.activate_module(I)
if(pod)
- new /obj/effect/DPtarget(target, pod)
+ new /obj/effect/pod_landingzone(target, pod)
+
+ var/turf/T = get_turf(usr.loc) // get admin's LOC as a turf
if (number == 1)
- log_admin("[key_name(usr)] created a [english_list(paths)]")
+ log_admin("[key_name(usr)] created a [english_list(paths)] at [AREACOORD(T)]")
for(var/path in paths)
if(ispath(path, /mob))
message_admins("[key_name_admin(usr)] created a [english_list(paths)]")
break
else
- log_admin("[key_name(usr)] created [number]ea [english_list(paths)]")
+ log_admin("[key_name(usr)] created [number]ea [english_list(paths)] at [AREACOORD(T)]")
for(var/path in paths)
if(ispath(path, /mob))
message_admins("[key_name_admin(usr)] created [number]ea [english_list(paths)]")
@@ -1981,7 +1857,7 @@
return
if(!CONFIG_GET(string/centcom_ban_db))
- to_chat(usr, "Centcom Galactic Ban DB is disabled!")
+ to_chat(usr, "CentCom Galactic Ban DB is disabled!")
return
var/ckey = href_list["centcomlookup"]
@@ -2024,7 +1900,7 @@
dat += sanitize(jobs.Join(", "))
dat += " "
dat += ""
-
+
var/datum/browser/popup = new(usr, "centcomlookup-[ckey]", "
Central Command Galactic Ban Database
", 700, 600)
popup.set_content(dat.Join())
popup.open(FALSE)
@@ -2193,7 +2069,7 @@
return
var/turf/T = get_turf(tear.old_loc)
message_admins("The items consumed by the BoH tear at [ADMIN_VERBOSEJMP(T)] were retrieved by [key_name_admin(usr)].")
- tear.investigate_log("Items consumed at [AREACOORD(T)] retrieved by [key_name(usr)].", INVESTIGATE_SINGULO)
+ tear.investigate_log("Items consumed at [AREACOORD(T)] retrieved by [key_name(usr)].", INVESTIGATE_ENGINES)
tear.retrieve_consumed_items()
else if(href_list["beakerpanel"])
diff --git a/code/modules/admin/verbs/SDQL2/SDQL_2.dm b/code/modules/admin/verbs/SDQL2/SDQL_2.dm
index c93ee5bd0e841..a1c27a7a7973c 100644
--- a/code/modules/admin/verbs/SDQL2/SDQL_2.dm
+++ b/code/modules/admin/verbs/SDQL2/SDQL_2.dm
@@ -276,7 +276,6 @@
"SDQL combined querys took [DisplayTimeText(end_time_total)] to complete.") + combined_refs
GLOBAL_LIST_INIT(sdql2_queries, GLOB.sdql2_queries || list())
-GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null, "VIEW VARIABLES (all)", null))
/datum/SDQL2_query
var/list/query_tree
@@ -305,10 +304,6 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
var/list/obj_count_eligible
var/obj_count_finished
- //Statclick
- var/obj/effect/statclick/SDQL2_delete/delete_click
- var/obj/effect/statclick/SDQL2_action/action_click
-
/datum/SDQL2_query/New(list/tree, SU = FALSE, admin_interact = TRUE, _options = SDQL2_OPTIONS_DEFAULT, finished_qdel = FALSE)
if(IsAdminAdvancedProcCall() || !LAZYLEN(tree))
qdel(src)
@@ -391,17 +386,29 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
return "##HALTING"
/datum/SDQL2_query/proc/generate_stat()
+ var/list/tab_data = list()
if(!allow_admin_interact)
return
- if(!delete_click)
- delete_click = new(null, "INITIALIZING", src)
- if(!action_click)
- action_click = new(null, "INITIALIZNG", src)
- stat("[id] ", delete_click.update("DELETE QUERY | STATE : [text_state()] | ALL/ELIG/FIN \
- [islist(obj_count_all)? length(obj_count_all) : (isnull(obj_count_all)? "0" : obj_count_all)]/\
- [islist(obj_count_eligible)? length(obj_count_eligible) : (isnull(obj_count_eligible)? "0" : obj_count_eligible)]/\
- [islist(obj_count_finished)? length(obj_count_finished) : (isnull(obj_count_finished)? "0" : obj_count_finished)] - [get_query_text()]"))
- stat(" ", action_click.update("[SDQL2_IS_RUNNING? "HALT" : "RUN"]"))
+ tab_data["Delete Query [id]"] = list(
+ text = "DELETE QUERY | STATE : [text_state()] | ALL/ELIG/FIN \
+ [islist(obj_count_all)? length(obj_count_all) : (isnull(obj_count_all)? "0" : obj_count_all)]/\
+ [islist(obj_count_eligible)? length(obj_count_eligible) : (isnull(obj_count_eligible)? "0" : obj_count_eligible)]/\
+ [islist(obj_count_finished)? length(obj_count_finished) : (isnull(obj_count_finished)? "0" : obj_count_finished)] - [get_query_text()]",
+ action = "sdql2delete",
+ params = list(
+ qid = id,
+ ),
+ type = STAT_BUTTON,
+ )
+ tab_data["Toggle Query [id]"] = list(
+ text = "[SDQL2_IS_RUNNING? "HALT" : "RUN"]",
+ action = "sdql2toggle",
+ params = list(
+ qid = id,
+ ),
+ type = STAT_BUTTON,
+ )
+ return tab_data
/datum/SDQL2_query/proc/delete_click()
admin_del(usr)
@@ -762,7 +769,7 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
new_args[++new_args.len] = SDQL_expression(source, arg)
if(object == GLOB) // Global proc.
procname = "/proc/[procname]"
- return superuser? (call(procname)(new_args)) : (WrapAdminProcCall(GLOBAL_PROC, procname, new_args))
+ return superuser? (call("/proc/[procname]")(new_args)) : (WrapAdminProcCall(GLOBAL_PROC, procname, new_args))
return superuser? (call(object, procname)(new_args)) : (WrapAdminProcCall(object, procname, new_args))
/datum/SDQL2_query/proc/SDQL_function_async(datum/object, procname, list/arguments, source)
@@ -968,13 +975,13 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
//Staying as a world proc as this is called too often for changes to offset the potential IsAdminAdvancedProcCall checking overhead.
/world/proc/SDQL_var(object, list/expression, start = 1, source, superuser, datum/SDQL2_query/query)
var/v
- var/static/list/exclude = list("usr", "src", "marked", "global")
+ var/static/list/exclude = list("usr", "src", "marked", "global", "MC", "FS", "CFG")
var/long = start < expression.len
var/datum/D
if(is_proper_datum(object))
D = object
- if (object == world && (!long || expression[start + 1] == ".") && !(expression[start] in exclude))
+ if (object == world && (!long || expression[start + 1] == ".") && !(expression[start] in exclude) && copytext(expression[start], 1, 3) != "SS")
to_chat(usr, "World variables are not allowed to be accessed. Use global.")
return null
@@ -1020,46 +1027,22 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
v = Failsafe
if("CFG")
v = config
- //Subsystem switches
- if("SSgarbage")
- v = SSgarbage
- if("SSmachines")
- v = SSmachines
- if("SSobj")
- v = SSobj
- if("SSresearch")
- v = SSresearch
- if("SSprojectiles")
- v = SSprojectiles
- if("SSfastprocess")
- v = SSfastprocess
- if("SSticker")
- v = SSticker
- if("SStimer")
- v = SStimer
- if("SSradiation")
- v = SSradiation
- if("SSnpcpool")
- v = SSnpcpool
- if("SSmobs")
- v = SSmobs
- if("SSmood")
- v = SSmood
- if("SSquirks")
- v = SSquirks
- if("SSwet_floors")
- v = SSwet_floors
- if("SSshuttle")
- v = SSshuttle
- if("SSmapping")
- v = SSmapping
- if("SSevents")
- v = SSevents
- if("SSeconomy")
- v = SSeconomy
- //End
else
- return null
+ if(copytext(expression[start], 1, 3) == "SS") //Subsystem
+ var/SSname = copytext(expression[start], 3)
+ var/SSlength = length(SSname)
+ var/datum/controller/subsystem/SS
+ var/SSmatch
+ for(var/_SS in Master.subsystems)
+ SS = _SS
+ if(copytext("[SS.type]", -SSlength) == SSname)
+ SSmatch = SS
+ break
+ if(!SSmatch)
+ return null
+ v = SSmatch
+ else
+ return null
else if(object == GLOB) // Shitty ass hack kill me.
v = expression[start]
if(long)
@@ -1187,16 +1170,7 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
/proc/is_proper_datum(thing)
return istype(thing, /datum) || istype(thing, /client)
-/obj/effect/statclick/SDQL2_delete/Click()
- var/datum/SDQL2_query/Q = target
- Q.delete_click()
-
-/obj/effect/statclick/SDQL2_action/Click()
- var/datum/SDQL2_query/Q = target
- Q.action_click()
-
-/obj/effect/statclick/SDQL2_VV_all
- name = "VIEW VARIABLES"
-
-/obj/effect/statclick/SDQL2_VV_all/Click()
- usr.client.debug_variables(GLOB.sdql2_queries)
+/proc/sdqlQueryByID(id)
+ for(var/datum/SDQL2_query/query as anything in GLOB.sdql2_queries)
+ if(query.id == id)
+ return query
diff --git a/code/modules/admin/verbs/SDQL2/SDQL_2_parser.dm b/code/modules/admin/verbs/SDQL2/SDQL_2_parser.dm
index aee395d9aa2a2..254821ec0e66d 100644
--- a/code/modules/admin/verbs/SDQL2/SDQL_2_parser.dm
+++ b/code/modules/admin/verbs/SDQL2/SDQL_2_parser.dm
@@ -27,15 +27,15 @@
// assignment : '=' expression
// variable : | variable '.' variable | variable '[' ']' | '{' '}' | '(' expression ')' | call_function
//
-// bool_expression : expression comparitor expression [bool_operator bool_expression]
+// bool_expression : expression comparator expression [bool_operator bool_expression]
// expression : ( unary_expression | '(' expression ')' | value ) [binary_operator expression]
// expression_list : expression [',' expression_list]
// unary_expression : unary_operator ( unary_expression | value )
//
-// comparitor : '=' | '==' | '!=' | '<>' | '<' | '<=' | '>' | '>='
+// comparator : '=' | '==' | '!=' | '<>' | '<' | '<=' | '>' | '>='
// value : variable | string | number | 'null' | object_type | array | selectors_array
// unary_operator : '!' | '-' | '~'
-// binary_operator : comparitor | '+' | '-' | '/' | '*' | '&' | '|' | '^' | '%'
+// binary_operator : comparator | '+' | '-' | '/' | '*' | '&' | '|' | '^' | '%'
// bool_operator : 'AND' | '&&' | 'OR' | '||'
//
// array : '[' expression_list ']'
@@ -56,7 +56,7 @@
var/list/boolean_operators = list("and", "or", "&&", "||")
var/list/unary_operators = list("!", "-", "~")
var/list/binary_operators = list("+", "-", "/", "*", "&", "|", "^", "%")
- var/list/comparitors = list("=", "==", "!=", "<>", "<", "<=", ">", ">=")
+ var/list/comparators = list("=", "==", "!=", "<>", "<", "<=", ">", ">=")
/datum/SDQL_parser/New(query_list)
query = query_list
@@ -109,7 +109,7 @@
parse_error("Invalid option assignment symbol: [token(i + 1)]")
var/val = tokenl(i + 2)
if(!(val in SDQL2_VALID_OPTION_VALUES))
- parse_error("Invalid optoin value: [val]")
+ parse_error("Invalid option value: [val]")
assignment_list[type] = val
return (i + 3)
@@ -396,21 +396,21 @@
var/path = text2path(token(i))
if (path == null)
- return parse_error("Nonexistant type path: [token(i)]")
+ return parse_error("Nonexistent type path: [token(i)]")
node += path
return i + 1
-//comparitor: '=' | '==' | '!=' | '<>' | '<' | '<=' | '>' | '>='
-/datum/SDQL_parser/proc/comparitor(i, list/node)
+//comparator: '=' | '==' | '!=' | '<>' | '<' | '<=' | '>' | '>='
+/datum/SDQL_parser/proc/comparator(i, list/node)
if(token(i) in list("=", "==", "!=", "<>", "<", "<=", ">", ">="))
node += token(i)
else
- parse_error("Unknown comparitor [token(i)]")
+ parse_error("Unknown comparator [token(i)]")
return i + 1
@@ -422,7 +422,7 @@
node += token(i)
else
- parse_error("Unknown comparitor [token(i)]")
+ parse_error("Unknown comparator [token(i)]")
return i + 1
@@ -551,7 +551,7 @@
i = binary_operator(i, node)
i = expression(i, node)
- else if(token(i) in comparitors)
+ else if(token(i) in comparators)
i = binary_operator(i, node)
var/list/rhs = list()
@@ -587,10 +587,10 @@
return i
-//binary_operator: comparitor | '+' | '-' | '/' | '*' | '&' | '|' | '^' | '%'
+//binary_operator: comparator | '+' | '-' | '/' | '*' | '&' | '|' | '^' | '%'
/datum/SDQL_parser/proc/binary_operator(i, list/node)
- if(token(i) in (binary_operators + comparitors))
+ if(token(i) in (binary_operators + comparators))
node += token(i)
else
diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm
index 857704c374dc8..d1dc12930a020 100644
--- a/code/modules/admin/verbs/adminhelp.dm
+++ b/code/modules/admin/verbs/adminhelp.dm
@@ -30,22 +30,11 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
var/list/closed_tickets = list()
var/list/resolved_tickets = list()
- var/obj/effect/statclick/ticket_list/browse_statclick = new(null, null, null)
- var/obj/effect/statclick/ticket_list/ustatclick = new(null, null, AHELP_UNCLAIMED)
- var/obj/effect/statclick/ticket_list/astatclick = new(null, null, AHELP_ACTIVE)
- var/obj/effect/statclick/ticket_list/cstatclick = new(null, null, AHELP_CLOSED)
- var/obj/effect/statclick/ticket_list/rstatclick = new(null, null, AHELP_RESOLVED)
-
/datum/admin_help_tickets/Destroy()
QDEL_LIST(unclaimed_tickets)
QDEL_LIST(active_tickets)
QDEL_LIST(closed_tickets)
QDEL_LIST(resolved_tickets)
- QDEL_NULL(browse_statclick)
- QDEL_NULL(ustatclick)
- QDEL_NULL(astatclick)
- QDEL_NULL(cstatclick)
- QDEL_NULL(rstatclick)
return ..()
/datum/admin_help_tickets/proc/TicketByID(id)
@@ -204,20 +193,47 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
//Tickets statpanel
/datum/admin_help_tickets/proc/stat_entry()
+ var/list/tab_data = list()
+ tab_data["Tickets"] = list(
+ text = "Open Ticket Browser",
+ type = STAT_BUTTON,
+ action = "browsetickets",
+ )
+ tab_data["Active Tickets"] = list(
+ text = "[active_tickets.len]",
+ type = STAT_BUTTON,
+ action = "browsetickets",
+ )
var/num_disconnected = 0
- stat("", browse_statclick.update("Open Ticket Browser"))
- stat("Active Tickets:", astatclick.update("[active_tickets.len]"))
for(var/l in list(active_tickets, unclaimed_tickets))
for(var/I in l)
var/datum/admin_help/AH = I
if(AH.initiator)
- stat("#[AH.id]. [AH.initiator_key_name]:", AH.statclick.update())
+ tab_data["#[AH.id]. [AH.initiator_key_name]"] = list(
+ text = AH.name,
+ type = STAT_BUTTON,
+ action = "open_ticket",
+ params = list("id" = AH.id),
+ )
else
++num_disconnected
if(num_disconnected)
- stat("Disconnected:", astatclick.update("[num_disconnected]"))
- stat("Closed Tickets:", cstatclick.update("[closed_tickets.len]"))
- stat("Resolved Tickets:", rstatclick.update("[resolved_tickets.len]"))
+ tab_data["Disconnected"] = list(
+ text = "[num_disconnected]",
+ type = STAT_BUTTON,
+ action = "browsetickets",
+ )
+ tab_data["Closed Tickets"] = list(
+ text = "[closed_tickets.len]",
+ type = STAT_BUTTON,
+ action = "browsetickets",
+ )
+ tab_data["Resolved Tickets"] = list(
+ text = "[resolved_tickets.len]",
+ type = STAT_BUTTON,
+ action = "browsetickets",
+ )
+ return tab_data
//Reassociate still open ticket if one exists
/datum/admin_help_tickets/proc/ClientLogin(client/C)
@@ -241,20 +257,6 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
if(AH.initiator_ckey == ckey)
return AH
-//
-//TICKET LIST STATCLICK
-//
-
-/obj/effect/statclick/ticket_list
- var/current_state
-
-/obj/effect/statclick/ticket_list/New(loc, name, state)
- current_state = state
- ..()
-
-/obj/effect/statclick/ticket_list/Click()
- GLOB.ahelp_tickets.BrowseTickets(usr)
-
//
// Ticket interaction
//
@@ -265,6 +267,8 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
var/from_user = ""
var/to_user = ""
var/message = ""
+ var/from_user_safe
+ var/to_user_safe
/datum/ticket_interaction/New()
. = ..()
@@ -292,8 +296,6 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
var/list/_interactions //use AddInteraction() or, preferably, admin_ticket_log()
- var/obj/effect/statclick/ahelp/statclick
-
var/static/ticket_counter = 0
var/bwoink // is the ahelp player to admin (not bwoink) or admin to player (bwoink)
@@ -311,7 +313,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
id = ++ticket_counter
opened_at = world.time
- name = msg
+ name = copytext_char(msg, 1, 100)
initiator = C
initiator_ckey = initiator.ckey
@@ -324,23 +326,22 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
TimeoutVerb()
- statclick = new(null, src)
_interactions = list()
GLOB.ahelp_tickets.unclaimed_tickets += src
if(is_bwoink)
- AddInteraction("blue", name, usr.ckey, initiator_key_name)
+ AddInteraction("blue", name, usr.ckey, initiator_key_name, "Administrator", "You")
message_admins("Ticket [TicketHref("#[id]")] created")
Claim() //Auto claim bwoinks
else
MessageNoRecipient(msg)
- //send it to irc if nobody is on and tell us how many were on
- var/admin_number_present = send2irc_adminless_only(initiator_ckey, "Ticket #[id]: [name]")
+ //send it to tgs if nobody is on and tell us how many were on
+ var/admin_number_present = send2tgs_adminless_only(initiator_ckey, "Ticket #[id]: [msg]")
log_admin_private("Ticket #[id]: [key_name(initiator)]: [name] - heard by [admin_number_present] non-AFK admins who have +BAN.")
if(admin_number_present <= 0)
- to_chat(C, "No active admins are online, your adminhelp was sent to the admin irc.")
+ to_chat(C, "No active admins are online, your adminhelp was sent through TGS to admins who are available. This may use IRC or Discord.")
heard_by_no_admins = TRUE
bwoink = is_bwoink
@@ -353,20 +354,22 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
GLOB.ahelp_tickets.resolved_tickets -= src
return ..()
-/datum/admin_help/proc/AddInteraction(msg_color, message, name_from, name_to)
+/datum/admin_help/proc/AddInteraction(msg_color, message, name_from, name_to, safe_from, safe_to)
if(heard_by_no_admins && usr && usr.ckey != initiator_ckey)
heard_by_no_admins = FALSE
- send2irc(initiator_ckey, "Ticket #[id]: Answered by [key_name(usr)]")
+ send2tgs(initiator_ckey, "Ticket #[id]: Answered by [key_name(usr)]")
var/datum/ticket_interaction/interaction_message = new /datum/ticket_interaction
interaction_message.message_color = msg_color
interaction_message.message = message
interaction_message.from_user = name_from
interaction_message.to_user = name_to
+ interaction_message.from_user_safe = safe_from
+ interaction_message.to_user_safe = safe_to
_interactions += interaction_message
SStgui.update_uis(src)
/datum/admin_help/proc/TimeoutVerb()
- initiator.verbs -= /client/verb/adminhelp
+ initiator.remove_verb(/client/verb/adminhelp)
initiator.adminhelptimerid = addtimer(CALLBACK(initiator, /client/proc/giveadminhelpverb), 1200, TIMER_STOPPABLE)
//private
@@ -510,7 +513,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
//Message to be sent to all admins
var/admin_msg = "Ticket [TicketHref("#[id]", ref_src)]: [LinkedReplyName(ref_src)] [FullMonty(ref_src)]:[keywords_lookup(msg)]"
- AddInteraction("red", msg, initiator_key_name, claimed_admin_key_name)
+ AddInteraction("red", msg, initiator_key_name, claimed_admin_key_name, "You", "Administrator")
log_admin_private("Ticket #[id]: [key_name(initiator)]: [msg]")
//send this msg to all admins
@@ -537,7 +540,6 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
to_chat(usr, "This user already has an active ticket, cannot reopen this one.")
return
- statclick = new(null, src)
GLOB.ahelp_tickets.active_tickets += src
GLOB.ahelp_tickets.closed_tickets -= src
GLOB.ahelp_tickets.resolved_tickets -= src
@@ -563,7 +565,6 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
if(state > AHELP_ACTIVE)
return
closed_at = world.time
- QDEL_NULL(statclick)
if(state == AHELP_ACTIVE)
GLOB.ahelp_tickets.active_tickets -= src
else
@@ -733,27 +734,6 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
if("mhelp")
MHelpThis()
-//
-// TICKET STATCLICK
-//
-
-/obj/effect/statclick/ahelp
- var/datum/admin_help/ahelp_datum
-
-/obj/effect/statclick/ahelp/Initialize(mapload, datum/admin_help/AH)
- ahelp_datum = AH
- . = ..()
-
-/obj/effect/statclick/ahelp/update()
- return ..(ahelp_datum.name)
-
-/obj/effect/statclick/ahelp/Click()
- ahelp_datum.TicketPanel()
-
-/obj/effect/statclick/ahelp/Destroy()
- ahelp_datum = null
- return ..()
-
//
//CLIENT PROCS
//
@@ -761,16 +741,16 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
/client/proc/giveadminhelpverb()
if(!src)
return
- src.verbs |= /client/verb/adminhelp
+ src.add_verb(/client/verb/adminhelp)
deltimer(adminhelptimerid)
adminhelptimerid = 0
// Used for methods where input via arg doesn't work
/client/proc/get_adminhelp()
- var/msg = capped_input(src, "Please describe your problem concisely and an admin will help as soon as they're able. Include the names of the people you are ahelping against if applicable.", "Adminhelp contents")
+ var/msg = capped_multiline_input(src, "Please describe your problem concisely and an admin will help as soon as they're able. Include the names of the people you are ahelping against if applicable.", "Adminhelp contents")
adminhelp(msg)
-/client/verb/adminhelp(msg as text)
+/client/verb/adminhelp(msg as message)
set category = "Admin"
set name = "Adminhelp"
@@ -811,7 +791,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
//Use this proc when an admin takes action that may be related to an open ticket on what
//what can be a client, ckey, or mob
-/proc/admin_ticket_log(what, message, whofrom = "", whoto = "", color = "white")
+/proc/admin_ticket_log(what, message, whofrom = "", whoto = "", color = "white", isSenderAdmin = FALSE, safeSenderLogged = FALSE)
var/client/C
var/mob/Mob = what
if(istype(Mob))
@@ -819,12 +799,18 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
else
C = what
if(istype(C) && C.current_ticket)
- C.current_ticket.AddInteraction(color, message, whofrom, whoto)
+ if(safeSenderLogged)
+ C.current_ticket.AddInteraction(color, message, whofrom, whoto, isSenderAdmin ? "Administrator" : "You", isSenderAdmin ? "You" : "Administrator")
+ else
+ C.current_ticket.AddInteraction(color, message, whofrom, whoto)
return C.current_ticket
if(istext(what)) //ckey
var/datum/admin_help/AH = GLOB.ahelp_tickets.CKey2ActiveTicket(what)
if(AH)
- AH.AddInteraction(color, message, whofrom, whoto)
+ if(safeSenderLogged)
+ AH.AddInteraction(color, message, whofrom, whoto, isSenderAdmin ? "Administrator" : "You", isSenderAdmin ? "You" : "Administrator")
+ else
+ AH.AddInteraction(color, message, whofrom, whoto)
return AH
@@ -845,7 +831,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
else
.["present"] += X
-/proc/send2irc_adminless_only(source, msg, requiredflags = R_BAN)
+/proc/send2tgs_adminless_only(source, msg, requiredflags = R_BAN)
var/list/adm = get_admin_counts(requiredflags)
var/list/activemins = adm["present"]
. = activemins.len
@@ -859,32 +845,16 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
final = "[msg] - No admins online"
else
final = "[msg] - All admins stealthed\[[english_list(stealthmins)]\], AFK\[[english_list(afkmins)]\], or lacks +BAN\[[english_list(powerlessmins)]\]! Total: [allmins.len] "
- send2irc(source,final)
- send2otherserver(source,final)
+ send2tgs(source,final)
+ SStopic.crosscomms_send("ahelp", final, source)
-/proc/send2irc(msg,msg2)
+/proc/send2tgs(msg,msg2)
msg = replacetext(replacetext(msg, "\proper", ""), "\improper", "")
msg2 = replacetext(replacetext(msg2, "\proper", ""), "\improper", "")
world.TgsTargetedChatBroadcast("[msg] | [msg2]", TRUE)
-/proc/send2otherserver(source,msg,type = "Ahelp")
- var/comms_key = CONFIG_GET(string/comms_key)
- if(!comms_key)
- return
- var/list/message = list()
- message["message_sender"] = source
- message["message"] = msg
- message["source"] = "([CONFIG_GET(string/cross_comms_name)])"
- message["key"] = comms_key
- message += type
-
- var/list/servers = CONFIG_GET(keyed_list/cross_server)
- for(var/I in servers)
- world.Export("[servers[I]]?[list2params(message)]")
-
-
-/proc/ircadminwho()
+/proc/tgsadminwho()
var/list/message = list("Admins: ")
var/list/admin_keys = list()
for(var/adm in GLOB.admins)
@@ -899,7 +869,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
return jointext(message, "")
-/proc/keywords_lookup(msg,irc)
+/proc/keywords_lookup(msg,external)
//This is a list of words which are ignored by the parser when comparing message contents for names. MUST BE IN LOWER CASE!
var/list/adminhelp_ignored_words = list("unknown","the","a","an","of","monkey","alien","as", "i")
@@ -911,8 +881,11 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
var/list/surnames = list()
var/list/forenames = list()
var/list/ckeys = list()
- var/founds = ""
+ var/list/founds = list()
for(var/mob/M in GLOB.mob_list)
+ if(istype(M, /mob/living/carbon/human/dummy))
+ continue
+
var/list/indexing = list(M.real_name, M.name)
if(M.mind)
indexing += M.mind.name
@@ -958,15 +931,16 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
var/is_antag = 0
if(found.mind?.special_role)
is_antag = 1
- founds += "Name: [found.name]([found.real_name]) Key: [found.key] Ckey: [found.ckey] [is_antag ? "(Antag)" : null] "
+ founds[++founds.len] = list("name" = found.name,
+ "real_name" = found.real_name,
+ "ckey" = found.ckey,
+ "key" = found.key,
+ "antag" = is_antag)
msg += "[original_word](?|F) "
continue
msg += "[original_word] "
- if(irc)
- if(founds == "")
- return "Search Failed"
- else
- return founds
+ if(external)
+ return founds
return msg
diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm
index 02c41581744c5..1709f27b47b9e 100644
--- a/code/modules/admin/verbs/adminpm.dm
+++ b/code/modules/admin/verbs/adminpm.dm
@@ -1,4 +1,4 @@
-#define IRCREPLYCOUNT 2
+#define EXTERNALREPLYCOUNT 2
//allows right clicking mobs to send an admin PM to their client, forwards the selected mob's client to cmd_admin_pm
@@ -95,20 +95,20 @@
return
var/client/recipient
- var/irc = 0
+ var/external = 0
if(istext(whom))
if(whom[1] == "@")
whom = findStealthKey(whom)
if(whom == "IRCKEY")
- irc = 1
+ external = 1
else
recipient = GLOB.directory[whom]
else if(istype(whom, /client))
recipient = whom
- if(irc)
- if(!ircreplyamount) //to prevent people from spamming irc
+ if(external)
+ if(!externalreplyamount) //to prevent people from spamming irc/discord
return
if(!msg)
msg = stripped_multiline_input(src,"Message:", "Private message to Administrator")
@@ -116,7 +116,7 @@
if(!msg)
return
if(holder)
- to_chat(src, "Error: Use the admin IRC channel, nerd.", type = MESSAGE_TYPE_ADMINPM)
+ to_chat(src, "Error: Use the admin IRC/Discord channel, nerd.", type = MESSAGE_TYPE_ADMINPM)
return
@@ -153,7 +153,7 @@
return
//clean the message if it's not sent by a high-rank admin
- if(!check_rights(R_SERVER|R_DEBUG,0)||irc)//no sending html to the poor bots
+ if(!check_rights(R_SERVER|R_DEBUG,0)||external)//no sending html to the poor bots
msg = trim(sanitize(msg), MAX_MESSAGE_LEN)
if(!msg)
return
@@ -165,11 +165,11 @@
var/keywordparsedmsg = keywords_lookup(msg)
- if(irc)
+ if(external)
to_chat(src, "PM to-Admins: [rawmsg]", type = MESSAGE_TYPE_ADMINPM)
- var/datum/admin_help/AH = admin_ticket_log(src, "Reply PM from-[key_name(src, TRUE, TRUE)] to IRC: [keywordparsedmsg]")
- ircreplyamount--
- send2irc("[AH ? "#[AH.id] " : ""]Reply: [ckey]", rawmsg)
+ var/datum/admin_help/AH = admin_ticket_log(src, "Reply PM from-[key_name(src, TRUE, TRUE)] to External: [keywordparsedmsg]")
+ externalreplyamount--
+ send2tgs("[AH ? "#[AH.id] " : ""]Reply: [ckey]", rawmsg)
else
if(recipient.holder)
if(holder) //both are admins
@@ -177,13 +177,13 @@
to_chat(src, "Admin PM to-[key_name(recipient, src, 1)]: [keywordparsedmsg]", type = MESSAGE_TYPE_ADMINPM)
//omg this is dumb, just fill in both their tickets
- admin_ticket_log(src, keywordparsedmsg, key_name(src, recipient, 1), key_name(recipient, src, 1), color="teal")
+ admin_ticket_log(src, keywordparsedmsg, key_name(src, recipient, 1), key_name(recipient, src, 1), color="teal", isSenderAdmin = TRUE, safeSenderLogged = TRUE)
if(recipient != src) //reeee
- admin_ticket_log(recipient, keywordparsedmsg, key_name(src, recipient, 1), key_name(recipient, src, 1), color="teal")
+ admin_ticket_log(recipient, keywordparsedmsg, key_name(src, recipient, 1), key_name(recipient, src, 1), color="teal", isSenderAdmin = TRUE, safeSenderLogged = TRUE)
else //recipient is an admin but sender is not
var/replymsg = "Reply PM from-[key_name(src, recipient, 1)]: [keywordparsedmsg]"
- admin_ticket_log(src, keywordparsedmsg, key_name(src, recipient, 1), null, "white")
+ admin_ticket_log(src, keywordparsedmsg, key_name(src, recipient, 1), null, "white", isSenderAdmin = TRUE, safeSenderLogged = TRUE)
to_chat(recipient, "[replymsg]", type = MESSAGE_TYPE_ADMINPM)
to_chat(src, "PM to-Admins: [msg]", type = MESSAGE_TYPE_ADMINPM)
@@ -201,7 +201,7 @@
to_chat(recipient, "Click on the administrator's name to reply.", type = MESSAGE_TYPE_ADMINPM)
to_chat(src, "Admin PM to-[key_name(recipient, src, 1)]: [msg]", type = MESSAGE_TYPE_ADMINPM)
- admin_ticket_log(recipient, keywordparsedmsg, key_name_admin(src), null, "purple")
+ admin_ticket_log(recipient, keywordparsedmsg, key_name_admin(src), null, "purple", safeSenderLogged = TRUE)
//always play non-admin recipients the adminhelp sound
SEND_SOUND(recipient, sound('sound/effects/adminhelp.ogg'))
@@ -227,10 +227,10 @@
to_chat(src, "Error: Admin-PM: Non-admin to non-admin PM communication is forbidden.", type = MESSAGE_TYPE_ADMINPM)
return
- if(irc)
- log_admin_private("PM: [key_name(src)]->IRC: [rawmsg]")
+ if(external)
+ log_admin_private("PM: [key_name(src)]->External: [rawmsg]")
for(var/client/X in GLOB.admins)
- to_chat(X, "PM: [key_name(src, X, 0)]->IRC: [keywordparsedmsg]")
+ to_chat(X, "PM: [key_name(src, X, 0)]->External: [keywordparsedmsg]")
else
window_flash(recipient, ignorepref = TRUE)
log_admin_private("PM: [key_name(src)]->[key_name(recipient)]: [rawmsg]")
@@ -241,34 +241,34 @@
-#define IRC_AHELP_USAGE "Usage: ticket "
-/proc/IrcPm(target,msg,sender)
+#define TGS_AHELP_USAGE "Usage: ticket "
+/proc/TgsPm(target,msg,sender)
target = ckey(target)
var/client/C = GLOB.directory[target]
var/datum/admin_help/ticket = C ? C.current_ticket : GLOB.ahelp_tickets.CKey2ActiveTicket(target)
var/compliant_msg = trim(lowertext(msg))
- var/irc_tagged = "[sender](IRC)"
+ var/tgs_tagged = "[sender](TGS/External)"
var/list/splits = splittext(compliant_msg, " ")
if(splits.len && splits[1] == "ticket")
if(splits.len < 2)
- return IRC_AHELP_USAGE
+ return TGS_AHELP_USAGE
switch(splits[2])
if("close")
if(ticket)
- ticket.Close(irc_tagged)
+ ticket.Close(tgs_tagged)
return "Ticket #[ticket.id] successfully closed"
if("resolve")
if(ticket)
- ticket.Resolve(irc_tagged)
+ ticket.Resolve(tgs_tagged)
return "Ticket #[ticket.id] successfully resolved"
if("icissue")
if(ticket)
- ticket.ICIssue(irc_tagged)
+ ticket.ICIssue(tgs_tagged)
return "Ticket #[ticket.id] successfully marked as IC issue"
if("reject")
if(ticket)
- ticket.Reject(irc_tagged)
+ ticket.Reject(tgs_tagged)
return "Ticket #[ticket.id] successfully rejected"
if("reopen")
if(ticket)
@@ -277,7 +277,7 @@
if(!isnull(fail))
fail = text2num(splits[3])
if(isnull(fail))
- return "Error: No/Invalid ticket id specified. [IRC_AHELP_USAGE]"
+ return "Error: No/Invalid ticket id specified. [TGS_AHELP_USAGE]"
var/datum/admin_help/AH = GLOB.ahelp_tickets.TicketByID(fail)
if(!AH)
return "Error: Ticket #[fail] not found"
@@ -299,41 +299,41 @@
. += "#[AH.id]"
return
else
- return IRC_AHELP_USAGE
+ return TGS_AHELP_USAGE
return "Error: Ticket could not be found"
var/static/stealthkey
- var/adminname = CONFIG_GET(flag/show_irc_name) ? irc_tagged : "Administrator"
+ var/adminname = CONFIG_GET(flag/show_irc_name) ? tgs_tagged : "Administrator"
if(!C)
return "Error: No client"
if(!stealthkey)
- stealthkey = GenIrcStealthKey()
+ stealthkey = GenTgsStealthKey()
msg = sanitize(copytext_char(msg, 1, MAX_MESSAGE_LEN))
if(!msg)
return "Error: No message"
- message_admins("IRC message from [sender] to [key_name_admin(C)] : [msg]")
- log_admin_private("IRC PM: [sender] -> [key_name(C)] : [msg]")
+ message_admins("External message from [sender] to [key_name_admin(C)] : [msg]")
+ log_admin_private("External PM: [sender] -> [key_name(C)] : [msg]")
msg = emoji_parse(msg)
to_chat(C, "-- Administrator private message --", type = MESSAGE_TYPE_ADMINPM)
- to_chat(C, "Admin PM from-[adminname]: [msg]", type = MESSAGE_TYPE_ADMINPM)
+ to_chat(C, "Admin PM from-[adminname]: [msg]", allow_linkify = TRUE, type = MESSAGE_TYPE_ADMINPM)
to_chat(C, "Click on the administrator's name to reply.", type = MESSAGE_TYPE_ADMINPM)
- admin_ticket_log(C, msg, adminname, null, "cyan")
+ admin_ticket_log(C, msg, adminname, null, "cyan", isSenderAdmin = TRUE, safeSenderLogged = TRUE)
window_flash(C, ignorepref = TRUE)
//always play non-admin recipients the adminhelp sound
SEND_SOUND(C, 'sound/effects/adminhelp.ogg')
- C.ircreplyamount = IRCREPLYCOUNT
+ C.externalreplyamount = EXTERNALREPLYCOUNT
return "Message Successful"
-/proc/GenIrcStealthKey()
+/proc/GenTgsStealthKey()
var/num = (rand(0,1000))
var/i = 0
while(i == 0)
@@ -346,4 +346,4 @@
GLOB.stealthminID["IRCKEY"] = stealth
return stealth
-#undef IRCREPLYCOUNT
+#undef EXTERNALREPLYCOUNT
diff --git a/code/modules/admin/verbs/adminsay.dm b/code/modules/admin/verbs/adminsay.dm
index 38679d7d34215..19c4ec47db8e9 100644
--- a/code/modules/admin/verbs/adminsay.dm
+++ b/code/modules/admin/verbs/adminsay.dm
@@ -17,6 +17,6 @@
msg = keywords_lookup(msg)
var/custom_asay_color = (CONFIG_GET(flag/allow_admin_asaycolor) && prefs.asaycolor) ? "" : ""
msg = "ADMIN:[key_name(usr, 1)] [ADMIN_FLW(mob)]: [custom_asay_color][msg][custom_asay_color ? "":null]"
- to_chat(GLOB.admins, msg, type = MESSAGE_TYPE_ADMINCHAT)
+ to_chat(GLOB.admins, msg, allow_linkify = TRUE, type = MESSAGE_TYPE_ADMINCHAT)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Asay") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
diff --git a/code/modules/admin/verbs/atmosdebug.dm b/code/modules/admin/verbs/atmosdebug.dm
index 48434dba49ee4..0a4abfd033111 100644
--- a/code/modules/admin/verbs/atmosdebug.dm
+++ b/code/modules/admin/verbs/atmosdebug.dm
@@ -21,6 +21,13 @@
if(pipe.z && (!pipe.nodes || !pipe.nodes.len || (null in pipe.nodes)))
to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]")
+ //Erroneous Connections, e.g. duplicate pipes
+ //This uses pipeline_expansion(), so you can detect some atmos machineries causing problems at pipenet code.
+ for (var/obj/machinery/atmospherics/AM in GLOB.machines)
+ for (var/obj/machinery/atmospherics/AMT in AM.pipeline_expansion())
+ if (!(AM in AMT.pipeline_expansion()))
+ to_chat(usr, "Errorneous connections around [AM.name]. Duplicate or rogue pipes suspected at or around [ADMIN_VERBOSEJMP(AM)]")
+
/client/proc/powerdebug()
set category = "Mapping"
set name = "Check Power"
diff --git a/code/modules/admin/verbs/borgpanel.dm b/code/modules/admin/verbs/borgpanel.dm
index 1e3f24e67a746..fcbbca7324051 100644
--- a/code/modules/admin/verbs/borgpanel.dm
+++ b/code/modules/admin/verbs/borgpanel.dm
@@ -41,6 +41,7 @@
if(!ui)
ui = new(user, src, "BorgPanel", "Borging Panel")
ui.open()
+ ui.set_autoupdate(TRUE)
/datum/borgpanel/ui_data(mob/user)
. = list()
@@ -56,7 +57,7 @@
.["upgrades"] = list()
for (var/upgradetype in subtypesof(/obj/item/borg/upgrade)-/obj/item/borg/upgrade/hypospray) //hypospray is a dummy parent for hypospray upgrades
var/obj/item/borg/upgrade/upgrade = upgradetype
- if (initial(upgrade.module_type) && !istype(borg.module, initial(upgrade.module_type))) // Upgrade requires a different module
+ if (initial(upgrade.module_type) && !is_type_in_list(borg.module, initial(upgrade.module_type))) // Upgrade requires a different module
continue
var/installed = FALSE
if (locate(upgradetype) in borg)
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index 995620cc4a994..054e9c229734c 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -214,7 +214,7 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that
id.forceMove(W)
W.update_icon()
else
- H.equip_to_slot(id,SLOT_WEAR_ID)
+ H.equip_to_slot(id,ITEM_SLOT_ID)
else
alert("Invalid mob")
@@ -521,21 +521,23 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that
message_admins("[key_name_admin(usr)] changed the equipment of [ADMIN_LOOKUPFLW(H)] to [dresscode].")
/client/proc/robust_dress_shop()
- var/list/outfits = list("Naked","Custom","As Job...")
- var/list/paths = subtypesof(/datum/outfit) - typesof(/datum/outfit/job)
+ var/list/outfits = list("Naked","Custom","As Job...","As Job(Plasmaman)...", "Debug")
+ var/list/paths = subtypesof(/datum/outfit) - typesof(/datum/outfit/job) - typesof(/datum/outfit/plasmaman) - typesof(/datum/outfit/debug)
for(var/path in paths)
var/datum/outfit/O = path //not much to initalize here but whatever
if(initial(O.can_be_admin_equipped))
outfits[initial(O.name)] = path
var/dresscode = input("Select outfit", "Robust quick dress shop") as null|anything in outfits
- if (isnull(dresscode))
+ if(isnull(dresscode))
return
- if (outfits[dresscode])
+ if(outfits[dresscode])
dresscode = outfits[dresscode]
- if (dresscode == "As Job...")
+
+
+ if(dresscode == "As Job...")
var/list/job_paths = subtypesof(/datum/outfit/job)
var/list/job_outfits = list()
for(var/path in job_paths)
@@ -548,7 +550,25 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that
if(isnull(dresscode))
return
- if (dresscode == "Custom")
+ if(dresscode == "As Job(Plasmaman)...")
+ var/list/job_paths = subtypesof(/datum/outfit/plasmaman)
+ var/list/job_outfits = list()
+ for(var/path in job_paths)
+ var/datum/outfit/O = path
+ if(initial(O.can_be_admin_equipped))
+ job_outfits[initial(O.name)] = path
+
+ dresscode = input("Select plasmaman equipment", "Robust quick dress shop") as null|anything in sortList(job_outfits)
+ dresscode = job_outfits[dresscode]
+ if(isnull(dresscode))
+ return
+
+ if(dresscode == "Debug")
+ dresscode = /datum/outfit/debug
+ if(isnull(dresscode))
+ return
+
+ if(dresscode == "Custom")
var/list/custom_names = list()
for(var/datum/outfit/D in GLOB.custom_outfits)
custom_names[D.name] = D
@@ -604,7 +624,7 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that
if(Rad.anchored)
if(!Rad.loaded_tank)
var/obj/item/tank/internals/plasma/Plasma = new/obj/item/tank/internals/plasma(Rad)
- Plasma.air_contents.set_moles(/datum/gas/plasma, 70)
+ Plasma.air_contents.set_moles(GAS_PLASMA, 70)
Rad.drainratio = 0
Rad.loaded_tank = Plasma
Plasma.forceMove(Rad)
@@ -734,9 +754,9 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that
exists[L.ruin_template] = landmark
var/list/names = list()
- names += "---- Space Ruins ----"
+ names += "---- Dynamic Levels ----"
for(var/name in SSmapping.space_ruins_templates)
- names[name] = list(SSmapping.space_ruins_templates[name], ZTRAIT_SPACE_RUINS, /area/space)
+ names[name] = list(SSmapping.space_ruins_templates[name], ZTRAIT_DYNAMIC_LEVEL, /area/space)
names += "---- Lava Ruins ----"
for(var/name in SSmapping.lava_ruins_templates)
names[name] = list(SSmapping.lava_ruins_templates[name], ZTRAIT_LAVA_RUINS, /area/lavaland/surface/outdoors/unexplored)
@@ -765,6 +785,22 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that
else
to_chat(src, "Failed to place [template.name].")
+/client/proc/generate_ruin()
+ set category = "Debug"
+ set name = "Generate Ruin"
+ set desc = "Randomly generate a space ruin."
+ if (!holder)
+ return
+ var/ruin_size = input(src, "Ruin size (NxN) (Between 10 and 200)", "Ruin Size", 0) as num
+ if(ruin_size < 10 || ruin_size >= 200)
+ return
+ var/response = alert(src, "This will place the ruin at your current location.", "Spawn Ruin", "Spawn Ruin", "Cancel")
+ if (response == "Cancel")
+ return
+ var/border_size = (world.maxx - ruin_size) / 2
+ generate_space_ruin(mob.x, mob.y, mob.z, border_size, border_size)
+ log_admin("[key_name(src)] randomly generated a space ruin at [COORD(mob)].")
+
/client/proc/clear_dynamic_transit()
set category = "Debug"
set name = "Clear Dynamic Turf Reservations"
@@ -786,7 +822,7 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that
if(!check_rights(R_DEBUG))
return
- verbs -= /client/proc/fucky_wucky
+ remove_verb(/client/proc/fucky_wucky)
message_admins("[key_name_admin(src)] did a fucky wucky.")
log_admin("[key_name(src)] did a fucky wucky.")
for(var/m in GLOB.player_list)
@@ -798,7 +834,7 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that
addtimer(CALLBACK(src, .proc/restore_fucky_wucky), 600)
/client/proc/restore_fucky_wucky()
- verbs += /client/proc/fucky_wucky
+ add_verb(/client/proc/fucky_wucky)
/client/proc/toggle_medal_disable()
set category = "Debug"
@@ -882,13 +918,16 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that
if(!check_rights(R_DEBUG))
return
if(alert(usr, "Are you absolutely sure you want to reload the configuration from the default path on the disk, wiping any in-round modificatoins?", "Really reset?", "No", "Yes") == "Yes")
+ //Reload the config
config.admin_reload()
+ //Reload badges
+ load_badge_ranks()
/client/proc/modify_canister_gas(obj/machinery/portable_atmospherics/canister/C)
if(!check_rights(R_DEBUG) || !C)
return
- var/gas_to_add = input(usr, "Choose a gas to modify.", "Choose a gas.") as null|anything in (subtypesof(/datum/gas) - /datum/gas/unobtanium) //nice try
+ var/gas_to_add = input(usr, "Choose a gas to modify.", "Choose a gas.") as null|anything in GLOB.gas_data.ids
var/amount = input(usr, "Choose the amount of moles.", "Choose the amount.", 0) as num
var/temp = input(usr, "Choose the temperature (Kelvin).", "Choose the temp (K).", 0) as num
@@ -900,3 +939,71 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that
message_admins("[key_name_admin(src)] modified \the [C.name] at [AREACOORD(C)] - Gas: [gas_to_add], Moles: [amount], Temp: [temp].")
log_admin("[key_name_admin(src)] modified \the [C.name] at [AREACOORD(C)] - Gas: [gas_to_add], Moles: [amount], Temp: [temp].")
+/client/proc/give_all_spells()
+ set category = "Debug"
+ set name = "Give all spells"
+ if(!check_rights(R_DEBUG))
+ return
+ for(var/type in GLOB.spells)
+ var/obj/effect/proc_holder/spell/spell = new type
+ mob.AddSpell(spell)
+
+/// A debug verb to check the sources of currently running timers
+/client/proc/check_timer_sources()
+ set category = "Debug"
+ set name = "Check Timer Sources"
+ set desc = "Checks the sources of the running timers"
+ if (!check_rights(R_DEBUG))
+ return
+
+ var/bucket_list_output = generate_timer_source_output(SStimer.bucket_list)
+ var/second_queue = generate_timer_source_output(SStimer.second_queue)
+
+ usr << browse({"
+
bucket_list
+ [bucket_list_output]
+
+
second_queue
+ [second_queue]
+ "}, "window=check_timer_sources;size=700x700")
+
+/proc/generate_timer_source_output(list/datum/timedevent/events)
+ var/list/per_source = list()
+
+ // Collate all events and figure out what sources are creating the most
+ for (var/_event in events)
+ if (!_event)
+ continue
+ var/datum/timedevent/event = _event
+
+ do
+ if (event.source)
+ if (per_source[event.source] == null)
+ per_source[event.source] = 1
+ else
+ per_source[event.source] += 1
+ event = event.next
+ while (event && event != _event)
+
+ // Now, sort them in order
+ var/list/sorted = list()
+ for (var/source in per_source)
+ sorted += list(list("source" = source, "count" = per_source[source]))
+ sorted = sortTim(sorted, .proc/cmp_timer_data)
+
+ // Now that everything is sorted, compile them into an HTML output
+ var/output = "
"
+
+ for (var/_timer_data in sorted)
+ var/list/timer_data = _timer_data
+ output += {"
+
[timer_data["source"]]
+
[timer_data["count"]]
+
"}
+
+ output += "
"
+
+ return output
+
+/proc/cmp_timer_data(list/a, list/b)
+ return b["count"] - a["count"]
diff --git a/code/modules/admin/verbs/diagnostics.dm b/code/modules/admin/verbs/diagnostics.dm
index fd4374a7724d6..7c65da13d5acb 100644
--- a/code/modules/admin/verbs/diagnostics.dm
+++ b/code/modules/admin/verbs/diagnostics.dm
@@ -14,9 +14,7 @@
var/largest_click_time = 0
var/mob/largest_move_mob = null
var/mob/largest_click_mob = null
- for(var/mob/M in world)
- if(!M.client)
- continue
+ for(var/mob/M in GLOB.player_list)
if(M.next_move >= largest_move_time)
largest_move_mob = M
if(M.next_move > world.time)
@@ -32,6 +30,7 @@
log_admin("DEBUG: [key_name(M)] next_move = [M.next_move] lastDblClick = [M.next_click] world.time = [world.time]")
M.next_move = 1
M.next_click = 0
+
message_admins("[ADMIN_LOOKUPFLW(largest_move_mob)] had the largest move delay with [largest_move_time] frames / [DisplayTimeText(largest_move_time)]!")
message_admins("[ADMIN_LOOKUPFLW(largest_click_mob)] had the largest click delay with [largest_click_time] frames / [DisplayTimeText(largest_click_time)]!")
message_admins("world.time = [world.time]")
@@ -50,12 +49,16 @@
output += " ERROR "
continue
for (var/filter in fqs.devices)
- var/list/f = fqs.devices[filter]
- if (!f)
+ var/list/filtered = fqs.devices[filter]
+ if (!filtered)
output += " [filter]: ERROR "
continue
- output += " [filter]: [f.len] "
- for (var/device in f)
+ output += " [filter]: [filtered.len] "
+ for(var/datum/weakref/device_ref as anything in filtered)
+ var/atom/device = device_ref.resolve()
+ if(!device)
+ filtered -= device_ref
+ continue
if (istype(device, /atom))
var/atom/A = device
output += " [device] ([AREACOORD(A)]) "
diff --git a/code/modules/admin/verbs/ghost_pool_protection.dm b/code/modules/admin/verbs/ghost_pool_protection.dm
new file mode 100644
index 0000000000000..aa4724d1cfdf8
--- /dev/null
+++ b/code/modules/admin/verbs/ghost_pool_protection.dm
@@ -0,0 +1,93 @@
+//very similar to centcom_podlauncher in terms of how this is coded, so i kept a lot of comments from it
+
+/client/proc/ghost_pool_protection() //Creates a verb for admins to open up the ui
+ set name = "Ghost Pool Protection"
+ set desc = "Choose which ways people can get into the round, or just clear it out completely for admin events."
+ set category = "Adminbus"
+ var/datum/ghost_pool_menu/tgui = new(usr)//create the datum
+ tgui.ui_interact(usr)//datum has a tgui component, here we open the window
+
+/datum/ghost_pool_menu
+ var/client/holder //client of whoever is using this datum
+
+ //when submitted, what the pool flags will be set to
+ var/new_role_flags = ALL
+
+ //EVERY TYPE OF WAY SOMEONE IS GETTING BACK INTO THE ROUND!
+ //these are the same comments as the ones in admin.dm defines, please update those if you change them here
+ /*
+ var/events_or_midrounds = TRUE //ie fugitives, space dragon, etc. also includes dynamic midrounds as it's the same deal
+ var/spawners = TRUE //ie ashwalkers, free golems, beach bums
+ var/station_sentience = TRUE //ie posibrains, mind monkeys, sentience potion, etc.
+ var/minigames = TRUE //ie mafia, ctf
+ var/misc = TRUE //oddities like split personality and any animal ones like spiders, xenos
+ */
+
+/datum/ghost_pool_menu/New(user)//user can either be a client or a mob due to byondcode(tm)
+ if (istype(user, /client))
+ var/client/user_client = user
+ holder = user_client //if its a client, assign it to holder
+ else
+ var/mob/user_mob = user
+ holder = user_mob.client //if its a mob, assign the mob's client to holder
+ new_role_flags = GLOB.ghost_role_flags
+
+/datum/ghost_pool_menu/ui_state(mob/user)
+ return GLOB.admin_state
+
+/datum/ghost_pool_menu/ui_close()
+ qdel(src)
+
+/datum/ghost_pool_menu/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "GhostPoolProtection")
+ ui.open()
+
+/datum/ghost_pool_menu/ui_data(mob/user)
+ var/list/data = list()
+ data["events_or_midrounds"] = (new_role_flags & GHOSTROLE_MIDROUND_EVENT)
+ data["spawners"] = (new_role_flags & GHOSTROLE_SPAWNER)
+ data["station_sentience"] = (new_role_flags & GHOSTROLE_STATION_SENTIENCE)
+ data["silicons"] = (new_role_flags & GHOSTROLE_SILICONS)
+ data["minigames"] = (new_role_flags & GHOSTROLE_MINIGAME)
+ return data
+
+/datum/ghost_pool_menu/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+ switch(action)
+ if("toggle_events_or_midrounds")
+ new_role_flags ^= GHOSTROLE_MIDROUND_EVENT
+ . = TRUE
+ if("toggle_spawners")
+ new_role_flags ^= GHOSTROLE_SPAWNER
+ . = TRUE
+ if("toggle_station_sentience")
+ new_role_flags ^= GHOSTROLE_STATION_SENTIENCE
+ . = TRUE
+ if("toggle_silicons")
+ new_role_flags ^= GHOSTROLE_SILICONS
+ . = TRUE
+ if("toggle_minigames")
+ new_role_flags ^= GHOSTROLE_MINIGAME
+ . = TRUE
+ if("all_roles")
+ new_role_flags = ALL
+ . = TRUE
+ if("no_roles")
+ new_role_flags = NONE
+ . = TRUE
+ if("apply_settings")
+ to_chat(usr, "Settings Applied!")
+ var/msg
+ switch(new_role_flags)
+ if(ALL)
+ msg = "enabled all of"
+ if(NONE)
+ msg = "disabled all of"
+ else
+ msg = "modified"
+ message_admins("[key_name_admin(holder)] has [msg] this round's allowed ghost roles.")
+ GLOB.ghost_role_flags = new_role_flags
diff --git a/code/modules/admin/verbs/individual_logging.dm b/code/modules/admin/verbs/individual_logging.dm
index 954b5ccee6058..9ad07ac1b2573 100644
--- a/code/modules/admin/verbs/individual_logging.dm
+++ b/code/modules/admin/verbs/individual_logging.dm
@@ -5,7 +5,7 @@
var/ntype = text2num(type)
//Add client links
- var/dat = ""
+ var/list/dat = list()
if(M.client)
dat += "
Client
"
dat += "
"
@@ -46,22 +46,27 @@
var/log_source = M.logging;
if(source == LOGSRC_CLIENT && M.client) //if client doesn't exist just fall back to the mob log
log_source = M.client.player_details.logging //should exist, if it doesn't that's a bug, don't check for it not existing
-
+ var/list/concatenated_logs = list()
for(var/log_type in log_source)
var/nlog_type = text2num(log_type)
if(nlog_type & ntype)
- var/list/reversed = log_source[log_type]
- if(islist(reversed))
- reversed = reverseRange(reversed.Copy())
- for(var/entry in reversed)
- dat += "[entry] [reversed[entry]] "
- dat += ""
+ var/list/all_the_entrys = log_source[log_type]
+ for(var/entry in all_the_entrys)
+ concatenated_logs += "[entry] [all_the_entrys[entry]]"
+ if(length(concatenated_logs))
+ sortTim(concatenated_logs, cmp = /proc/cmp_text_dsc) //Sort by timestamp.
+ dat += ""
+ dat += concatenated_logs.Join(" ")
+ dat += ""
- usr << browse(dat, "window=invidual_logging_[key_name(M)];size=600x480")
+ var/datum/browser/popup = new(usr, "invidual_logging_[key_name(M)]", "Individual Logs", 600, 600)
+ popup.set_content(dat.Join())
+ popup.open()
/proc/individual_logging_panel_link(mob/M, log_type, log_src, label, selected_src, selected_type)
var/slabel = label
if(selected_type == log_type && selected_src == log_src)
slabel = "\[[label]\]"
-
+ //This is necessary because num2text drops digits and rounds on big numbers. If more defines get added in the future it could break again.
+ log_type = num2text(log_type, MAX_BITFLAG_DIGITS)
return "[slabel]"
diff --git a/code/modules/admin/verbs/map_template_loadverb.dm b/code/modules/admin/verbs/map_template_loadverb.dm
index 0e687c8b045ea..82db4dc885ad7 100644
--- a/code/modules/admin/verbs/map_template_loadverb.dm
+++ b/code/modules/admin/verbs/map_template_loadverb.dm
@@ -37,11 +37,14 @@
to_chat(src, "Filename must end in '.dmm': [map]")
return
var/datum/map_template/M
+ var/type
switch(alert(src, "What kind of map is this?", "Map type", "Normal", "Shuttle", "Cancel"))
if("Normal")
- M = new /datum/map_template(map, "[map]", TRUE)
+ type = "Normal"
+ M = new /datum/map_template(map, "[map] - Uploaded by [ckey] at [time2text(world.timeofday,"YYYY-MM-DD hh:mm:ss")]", TRUE)
if("Shuttle")
- M = new /datum/map_template/shuttle(map, "[map]", TRUE)
+ type = "Shuttle"
+ M = new /datum/map_template/shuttle(map, "[map] - Uploaded by [ckey] at [time2text(world.timeofday,"YYYY-MM-DD hh:mm:ss")]", TRUE, copytext("[map]",1, -4))
else
return
if(!M.cached_map)
@@ -61,7 +64,11 @@
else
alert(src, "The map failed validation and cannot be loaded.", "Map Errors", "Oh Darn")
return
-
- SSmapping.map_templates[M.name] = M
- message_admins("[key_name_admin(src)] has uploaded a map template '[map]' ([M.width]x[M.height])[report_link].")
+ switch(type)
+ if("Normal")
+ SSmapping.map_templates[M.name] = M
+ if("Shuttle")
+ var/datum/map_template/shuttle/S = M
+ SSmapping.shuttle_templates[S.shuttle_id] = S
+ message_admins("[key_name_admin(src)] has uploaded a [type] map template '[map]' ([M.width]x[M.height])[report_link].")
to_chat(src, "Map template '[map]' ready to place ([M.width]x[M.height])")
diff --git a/code/modules/admin/verbs/mapping.dm b/code/modules/admin/verbs/mapping.dm
index a5c164a736d56..ad172f4e76774 100644
--- a/code/modules/admin/verbs/mapping.dm
+++ b/code/modules/admin/verbs/mapping.dm
@@ -84,9 +84,8 @@ GLOBAL_PROTECT(admin_verbs_debug_mapping)
for(var/turf/T in C.can_see())
seen[T]++
for(var/turf/T in seen)
- T.maptext = "[seen[T]]"
+ T.maptext = MAPTEXT("[seen[T]]")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
- SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range")
#ifdef TESTING
GLOBAL_LIST_EMPTY(dirty_vars)
@@ -157,7 +156,7 @@ GLOBAL_LIST_EMPTY(dirty_vars)
if(intercom_range_display_status)
for(var/obj/item/radio/intercom/I in world)
- for(var/turf/T in orange(7,I))
+ for(var/turf/T as() in RANGE_TURFS(7,I))
var/obj/effect/debugging/marker/F = new/obj/effect/debugging/marker(T)
if (!(F in view(7,I.loc)))
qdel(F)
@@ -205,15 +204,15 @@ GLOBAL_LIST_EMPTY(dirty_vars)
set name = "Debug verbs - Enable"
if(!check_rights(R_DEBUG))
return
- verbs -= /client/proc/enable_debug_verbs
- verbs.Add(/client/proc/disable_debug_verbs, GLOB.admin_verbs_debug_mapping)
+ remove_verb(/client/proc/enable_debug_verbs)
+ add_verb(list(/client/proc/disable_debug_verbs) + GLOB.admin_verbs_debug_mapping)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Enable Debug Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/disable_debug_verbs()
set category = "Debug"
set name = "Debug verbs - Disable"
- verbs.Remove(/client/proc/disable_debug_verbs, GLOB.admin_verbs_debug_mapping)
- verbs += /client/proc/enable_debug_verbs
+ remove_verb(list(/client/proc/disable_debug_verbs) + GLOB.admin_verbs_debug_mapping)
+ add_verb(/client/proc/enable_debug_verbs)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Disable Debug Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/count_objects_on_z_level()
@@ -239,18 +238,19 @@ GLOBAL_LIST_EMPTY(dirty_vars)
var/list/atom/atom_list = list()
- for(var/atom/A in world)
- if(istype(A,type_path))
- var/atom/B = A
- while(!(isturf(B.loc)))
+ for(var/area/T as() in get_areas(/area, num_level))
+ for(var/atom/A in T)
+ if(istype(A, type_path))
+ var/atom/B = A
+ while(!(isturf(B.loc)))
if(B?.loc)
B = B.loc
else
break
- if(B)
- if(B.z == num_level)
+ if(B)
count++
atom_list += A
+ CHECK_TICK
to_chat(world, "There are [count] objects of type [type_path] on z-level [num_level]")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Count Objects Zlevel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
diff --git a/code/modules/admin/verbs/maprotation.dm b/code/modules/admin/verbs/maprotation.dm
index 91e7045b9fa7d..19c509857930d 100644
--- a/code/modules/admin/verbs/maprotation.dm
+++ b/code/modules/admin/verbs/maprotation.dm
@@ -12,7 +12,7 @@
/client/proc/adminchangemap()
set category = "Server"
set name = "Change Map"
-
+
var/list/maprotatechoices = list()
for (var/map in config.maplist)
var/datum/map_config/VM = config.maplist[map]
@@ -43,3 +43,28 @@
log_admin("[key_name(usr)] is changing the map to [VM.map_name]")
if (SSmapping.changemap(VM) == 0)
message_admins("[key_name_admin(usr)] has changed the map to [VM.map_name]")
+
+/client/proc/forcemapconfig()
+ set category = "Debug"
+ set name = "Debug Force Map"
+
+ //Locked behind permissions since it needs serious protection.
+ if(!check_rights(R_DEBUG) || !check_rights(R_SERVER) || !check_rights(R_PERMISSIONS))
+ to_chat(src, "Insufficient rights (Requires debug, server and permissions).")
+ return
+
+ var/json_settings = input(usr, "Enter map json name:", "Map Json Name", "") as text|null
+
+ if(!json_settings)
+ return
+
+ var/datum/map_config/config = new
+ if(!config.LoadConfig("_maps/[json_settings].json", TRUE))
+ qdel(config)
+ to_chat(usr, "Map json failed to load!")
+ return
+ SSticker.maprotatechecked = 1
+ message_admins("[key_name_admin(usr)] is changing the map to [config.map_name]")
+ log_admin("[key_name(usr)] is changing the map to [config.map_name]")
+ if (SSmapping.changemap(config) == 0)
+ message_admins("[key_name_admin(usr)] has changed the map to [config.map_name]")
diff --git a/code/modules/admin/verbs/mentors_edit.dm b/code/modules/admin/verbs/mentors_edit.dm
index 316ab40d55c09..43da6121095b4 100644
--- a/code/modules/admin/verbs/mentors_edit.dm
+++ b/code/modules/admin/verbs/mentors_edit.dm
@@ -4,68 +4,70 @@ also probably less secure, but honestly dude
its mentors, not actual dangerous perms
******************************************/
/client/proc/edit_mentors()
- set category = "Admin"
- set name = "Mentor Panel"
- set desc = "Edit mentors"
+ set category = "Admin"
+ set name = "Mentor Panel"
+ set desc = "Edit mentors"
- if(!check_rights(R_PERMISSIONS))
- return
- if(!SSdbcore.IsConnected())
- to_chat(src, "Failed to establish database connection.")
- return
+ if(!check_rights(R_PERMISSIONS))
+ return
+ if(!SSdbcore.IsConnected())
+ to_chat(src, "Failed to establish database connection.")
+ return
- var/html = "
\n"
- var/datum/DBQuery/query_mentor_list = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("mentor")]")
- if(!query_mentor_list.warn_execute())
- to_chat(src, "Unable to pull the mentor list from the database.")
- qdel(query_mentor_list)
- query_mentor_list.Execute()
- while(query_mentor_list.NextRow())
- html += "
\n"
+ var/datum/DBQuery/query_mentor_list = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("mentor")]")
+ if(!query_mentor_list.warn_execute())
+ to_chat(src, "Unable to pull the mentor list from the database.")
+ qdel(query_mentor_list)
+ query_mentor_list.Execute()
+ while(query_mentor_list.NextRow())
+ html += "
\
Points: [points] / [total_points]\
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_antag.dm b/code/modules/antagonists/eldritch_cult/eldritch_antag.dm
index d4d1cad0afd65..c71a9f5e0a02b 100644
--- a/code/modules/antagonists/eldritch_cult/eldritch_antag.dm
+++ b/code/modules/antagonists/eldritch_cult/eldritch_antag.dm
@@ -4,8 +4,9 @@
antagpanel_category = "Heretic"
antag_moodlet = /datum/mood_event/heretics
job_rank = ROLE_HERETIC
- var/antag_hud_type = ANTAG_HUD_HERETIC
+ var/antag_hud_type = ANTAG_HUD_HERETIC // someone make all the other antags conform to this too lol
var/antag_hud_name = "heretic"
+ hijack_speed = 0.5
var/give_equipment = TRUE
var/list/researched_knowledge = list()
var/total_sacrifices = 0
@@ -18,7 +19,7 @@
log_admin("[key_name(admin)] has heresized [key_name(new_owner)].")
/datum/antagonist/heretic/greet()
- owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ecult_op.ogg', 100, FALSE, pressure_affected = FALSE)//subject to change
+ owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ecult_op.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE)//subject to change
to_chat(owner, "You are the Heretic! \
The old ones gave you these tasks to fulfill:")
owner.announce_objectives()
@@ -26,6 +27,8 @@
Your book allows you to research abilities, read it very carefully! You cannot undo what has been done! \
You gain charges by either collecting influences or sacrificing people tracked by the living heart \
You can find a basic guide at : https://wiki.beestation13.com/view/Heretics ")
+ owner.current.client?.tgui_panel?.give_antagonist_popup("Heretic",
+ "Collect influences or sacrafice targets to expand your forbidden knowledge.")
/datum/antagonist/heretic/on_gain()
var/mob/living/current = owner.current
@@ -34,8 +37,8 @@
gain_knowledge(/datum/eldritch_knowledge/spell/basic)
gain_knowledge(/datum/eldritch_knowledge/living_heart)
gain_knowledge(/datum/eldritch_knowledge/codex_cicatrix)
- current.log_message("has become a heretic", LOG_ATTACK, color="#960000")
- GLOB.reality_smash_track.AddMind(owner)
+ current.log_message("has been turned into a heretic!", LOG_ATTACK, color="#960000")
+ GLOB.reality_smash_track.Generate()
START_PROCESSING(SSprocessing,src)
if(give_equipment)
equip_cultist()
@@ -50,12 +53,11 @@
if(!silent)
to_chat(owner.current, "Your mind begins to flare as the otherwordly knowledge escapes your grasp!")
owner.current.log_message("has become a non-heretic", LOG_ATTACK, color="#960000")
- GLOB.reality_smash_track.RemoveMind(owner)
+ GLOB.reality_smash_track.targets--
STOP_PROCESSING(SSprocessing,src)
return ..()
-
/datum/antagonist/heretic/proc/equip_cultist()
var/mob/living/carbon/H = owner.current
if(!istype(H))
@@ -65,9 +67,9 @@
/datum/antagonist/heretic/proc/ecult_give_item(obj/item/item_path, mob/living/carbon/human/H)
var/list/slots = list(
- "backpack" = SLOT_IN_BACKPACK,
- "left pocket" = SLOT_L_STORE,
- "right pocket" = SLOT_R_STORE
+ "backpack" = ITEM_SLOT_BACKPACK,
+ "left pocket" = ITEM_SLOT_LPOCKET,
+ "right pocket" = ITEM_SLOT_RPOCKET
)
var/T = new item_path(H)
@@ -89,12 +91,30 @@
EK.on_life(owner.current)
/datum/antagonist/heretic/proc/forge_primary_objectives()
- var/list/assasination = list()
- var/list/protection = list()
- for(var/i in 1 to 2)
- var/pck = pick("assasinate","stalk","protect")
- switch(pck)
- if("assasinate")
+ if (prob(5))
+ if (prob(66))
+ var/datum/objective/ascend/AE = new()
+ AE.owner = owner
+ AE.update_explanation_text()
+ objectives += AE
+ log_objective(owner, AE.explanation_text)
+ else
+ var/datum/objective/hijack/hijack_objective = new
+ hijack_objective.owner = owner
+ hijack_objective.update_explanation_text()
+ objectives += hijack_objective
+ log_objective(owner, hijack_objective.explanation_text)
+ else
+ var/list/assasination = list()
+ var/list/protection = list()
+ for(var/i in 1 to 2)
+ if (prob(35))
+ var/datum/objective/stalk/S = new()
+ S.owner = owner
+ S.find_target()
+ objectives += S
+ log_objective(owner, S.explanation_text)
+ else
var/datum/objective/assassinate/A = new()
A.owner = owner
var/list/owners = A.get_owners()
@@ -102,26 +122,12 @@
assasination += A.target
objectives += A
log_objective(owner, A.explanation_text)
- if("stalk")
- var/datum/objective/stalk/S = new()
- S.owner = owner
- S.find_target()
- objectives += S
- log_objective(owner, S.explanation_text)
- if("protect")
- var/datum/objective/protect/P = new()
- P.owner = owner
- var/list/owners = P.get_owners()
- P.find_target(owners,assasination)
- protection += P.target
- objectives += P
- log_objective(owner, P.explanation_text)
-
- var/datum/objective/sacrifice_ecult/SE = new()
- SE.owner = owner
- SE.update_explanation_text()
- objectives += SE
- log_objective(owner, SE.explanation_text)
+ var/datum/objective/sacrifice_ecult/SE = new()
+ SE.owner = owner
+ SE.update_explanation_text()
+ objectives += SE
+ log_objective(owner, SE.explanation_text)
+
/datum/antagonist/heretic/apply_innate_effects(mob/living/mob_override)
. = ..()
@@ -168,14 +174,11 @@
count++
if(ascended)
- //Ascension isnt technically finishing the objectives, buut it is to be considered a great win.
- var/client/C = GLOB.directory[ckey(owner.key)]
- if(C)
- C.process_greentext()
- parts += "HERETIC HAS ASCENDED!"
+ //Ascension isn't technically finishing the objectives, buut it is to be considered a great win.
+ parts += "THIS HERETIC ASCENDED!"
else
if(cultiewin)
- parts += "The heretic was successful!"
+ parts += "The heretic was successful!"
else
parts += "The heretic has failed."
@@ -187,6 +190,7 @@
parts += knowledge_message.Join(", ")
return parts.Join(" ")
+
////////////////
// Knowledge //
////////////////
@@ -224,9 +228,9 @@
name = "spendtime"
var/timer = 5 MINUTES
-/datum/objective/stalk/process()
- if(owner?.current?.stat != DEAD && target?.current?.stat != DEAD && (target in view(5,owner.current)))
- timer -= 1 SECONDS
+/datum/objective/stalk/process(delta_time)
+ if(owner?.current?.stat != DEAD && target?.current?.stat != DEAD && (owner.current in viewers(5, get_turf(target))))
+ timer -= delta_time * 10 // timer is in deciseconds
///we don't want to process after the counter reaches 0, otherwise it is wasted processing
if(timer <= 0)
completed = TRUE
@@ -252,9 +256,11 @@
/datum/objective/sacrifice_ecult
name = "sacrifice"
-/datum/objective/sacrifice_ecult/update_explanation_text()
- . = ..()
+/datum/objective/sacrifice_ecult/New()
+ ..()
target_amount = rand(2,6)
+
+/datum/objective/sacrifice_ecult/update_explanation_text()
explanation_text = "Sacrifice at least [target_amount] people."
/datum/objective/sacrifice_ecult/check_completion()
@@ -264,3 +270,13 @@
if(!cultie)
return FALSE
return cultie.total_sacrifices >= target_amount
+
+/datum/objective/ascend
+ name = "ascend"
+ explanation_text = "Appease the Gods and ascend."
+
+/datum/objective/ascend/check_completion()
+ if(!owner)
+ return FALSE
+ var/datum/antagonist/heretic/cultie = owner.has_antag_datum(/datum/antagonist/heretic)
+ return cultie?.ascended
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_book.dm b/code/modules/antagonists/eldritch_cult/eldritch_book.dm
index d6f48c6a70864..f455d35b9047a 100644
--- a/code/modules/antagonists/eldritch_cult/eldritch_book.dm
+++ b/code/modules/antagonists/eldritch_cult/eldritch_book.dm
@@ -1,6 +1,6 @@
/obj/item/forbidden_book
name = "Codex Cicatrix"
- desc = "Book describing the secrets of the veil."
+ desc = "This book describes the secrets of the veil between worlds."
icon = 'icons/obj/eldritch.dmi'
icon_state = "book"
w_class = WEIGHT_CLASS_SMALL
@@ -8,11 +8,12 @@
var/mob/living/last_user
///how many charges do we have?
var/charge = 1
- ///Where we cannot create the rune?
- var/static/list/blacklisted_turfs = typecacheof(list(/turf/closed,/turf/open/space,/turf/open/lava))
///Is it in use?
var/in_use = FALSE
+/obj/item/forbidden_book/empty
+ charge = 0
+
/obj/item/forbidden_book/Destroy()
last_user = null
. = ..()
@@ -23,21 +24,15 @@
if(!IS_HERETIC(user))
return
. += "The Tome holds [charge] charges."
- . += "Use it on the floor to create a transmutation rune, used to perform rituals."
. += "Hit an influence in the black part with it to gain a charge."
- . += "Hit a transmutation rune to destroy it."
/obj/item/forbidden_book/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
. = ..()
if(!proximity_flag || !IS_HERETIC(user) || in_use)
return
in_use = TRUE
- if(istype(target,/obj/effect/eldritch))
- remove_rune(target,user)
if(istype(target,/obj/effect/reality_smash))
get_power_from_influence(target,user)
- if(istype(target,/turf/open))
- draw_rune(target,user)
in_use = FALSE
///Gives you a charge and destroys a corresponding influence
@@ -48,28 +43,6 @@
qdel(RS)
charge += 1
-///Draws a rune on a selected turf
-/obj/item/forbidden_book/proc/draw_rune(atom/target,mob/user)
-
- for(var/turf/T in range(1,target))
- if(is_type_in_typecache(T, blacklisted_turfs))
- to_chat(target, "The terrain doesn't support runes!")
- return
- var/A = get_turf(target)
- to_chat(user, "You start drawing a rune...")
-
- if(do_after(user,30 SECONDS,FALSE,A))
-
- new /obj/effect/eldritch/big(A)
-
-
-///Removes runes from the selected turf
-/obj/item/forbidden_book/proc/remove_rune(atom/target,mob/user)
-
- to_chat(user, "You start removing a rune...")
- if(do_after(user,2 SECONDS,user))
- qdel(target)
-
/obj/item/forbidden_book/ui_interact(mob/user, datum/tgui/ui = null)
if(!IS_HERETIC(user))
@@ -142,9 +115,7 @@
charge -= text2num(params["cost"])
return TRUE
- update_icon() // Not applicable to all objects.
-
-/obj/item/forbidden_book/ui_close(mob/user)
+/obj/item/forbidden_book/ui_close(mob/user, datum/tgui/tgui)
flick("book_closing",src)
icon_state = initial(icon_state)
return ..()
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_effects.dm b/code/modules/antagonists/eldritch_cult/eldritch_effects.dm
index 2dc2206a6c4f7..c53dc086e548a 100644
--- a/code/modules/antagonists/eldritch_cult/eldritch_effects.dm
+++ b/code/modules/antagonists/eldritch_cult/eldritch_effects.dm
@@ -1,13 +1,20 @@
/obj/effect/eldritch
name = "Generic rune"
- desc = "Weird combination of shapes and symbols etched into the floor itself. The indentation is filled with thick black tar-like fluid."
+ desc = "A flowing circle of shapes and runes is etched into the floor, filled with a thick black tar-like fluid."
anchored = TRUE
icon_state = ""
resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
layer = SIGIL_LAYER
+ forensic_protected = TRUE
///Used mainly for summoning ritual to prevent spamming the rune to create millions of monsters.
var/is_in_use = FALSE
+/obj/effect/eldritch/Initialize()
+ . = ..()
+ var/image/I = image(icon = 'icons/effects/eldritch.dmi', icon_state = null, loc = src)
+ I.override = TRUE
+ add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/silicons, "heretic_rune", I)
+
/obj/effect/eldritch/attack_hand(mob/living/user)
. = ..()
if(.)
@@ -32,13 +39,8 @@
var/list/knowledge = cultie.get_all_knowledge()
var/list/atoms_in_range = list()
- for(var/A in range(1, src))
- var/atom/atom_in_range = A
- if(istype(atom_in_range,/area))
- continue
- if(istype(atom_in_range,/turf)) // we dont want turfs
- continue
- if(istype(atom_in_range,/mob/living))
+ for(var/atom/atom_in_range as() in range(1, src))
+ if(isliving(atom_in_range))
var/mob/living/living_in_range = atom_in_range
if(living_in_range.stat != DEAD || living_in_range == user) // we only accept corpses, no living beings allowed.
continue
@@ -67,6 +69,7 @@
if(is_type_in_list(local_atom_in_range,local_required_atom_list))
selected_atoms |= local_atom_in_range
local_required_atoms -= list(local_required_atom_list)
+ break
if(length(local_required_atoms) > 0)
continue
@@ -81,7 +84,7 @@
atom_to_disappear.invisibility = INVISIBILITY_ABSTRACT
if(current_eldritch_knowledge.on_finished_recipe(user,selected_atoms,loc))
current_eldritch_knowledge.cleanup_atoms(selected_atoms)
- is_in_use = FALSE
+ is_in_use = FALSE
for(var/to_appear in atoms_to_disappear)
var/atom/atom_to_appear = to_appear
@@ -90,104 +93,87 @@
return
is_in_use = FALSE
- to_chat(user,"Your ritual failed! You used either wrong components or are missing something important!")
+ to_chat(user,"Your ritual failed! You either used the wrong components or are missing something important!")
+
/obj/effect/eldritch/big
- name = "Transmutation rune"
+ name = "transmutation rune"
icon = 'icons/effects/96x96.dmi'
icon_state = "eldritch_rune1"
pixel_x = -32 //So the big ol' 96x96 sprite shows up right
pixel_y = -32
/**
- * #Reality smash tracker
- *
- * Stupid fucking list holder, DONT create new ones, it will break the game, this is automnatically created whenever eldritch cultists are created.
- *
- * Tracks relevant data, generates relevant data, useful tool
- */
+ * #Reality smash tracker
+ *
+ * Stupid fucking list holder, DONT create new ones, it will break the game, this is automnatically created whenever eldritch cultists are created.
+ *
+ * Tracks relevant data, generates relevant data, useful tool
+ */
/datum/reality_smash_tracker
///list of tracked reality smashes
- var/list/smashes = list()
+ var/smashes = 0
///List of mobs with ability to see the smashes
- var/list/targets = list()
+ var/targets = 0
/datum/reality_smash_tracker/Destroy(force, ...)
if(GLOB.reality_smash_track == src)
- stack_trace("/datum/reality_smash_tracker was deleted. Heretics may no longer access any influences. Fix it or call coder support")
- QDEL_LIST(smashes)
- targets.Cut()
+ stack_trace("/datum/reality_smash_tracker was deleted. New heretics will no longer generate new influences")
return ..()
/**
- * Automatically fixes the target and smash network
- *
- * Fixes any bugs that are caused by late Generate() or exchanging clients
- */
-/datum/reality_smash_tracker/proc/ReworkNetwork()
- listclearnulls(smashes)
- for(var/mind in targets)
- if(isnull(mind))
- stack_trace("A null somehow landed in a list of minds")
- continue
- for(var/X in smashes)
- var/obj/effect/reality_smash/reality_smash = X
- reality_smash.AddMind(mind)
-
-/**
- * Generates a set amount of reality smashes based on the N value
- *
- * Automatically creates more reality smashes
- */
-/datum/reality_smash_tracker/proc/_Generate()
- var/targ_len = length(targets)
- var/smash_len = length(smashes)
- var/number = targ_len * 6 - smash_len
+ * Generates a set amount of reality smashes based on the N value
+ *
+ * Automatically creates more reality smashes
+ */
+/datum/reality_smash_tracker/proc/Generate()
+ targets++
+ var/number = max(targets * ( 4 - (targets-1) ) - smashes,1)
for(var/i in 0 to number)
-
- var/turf/chosen_location = get_safe_random_station_turf()
+ var/turf/chosen_location = get_safe_random_station_turfs()
//we also dont want them close to each other, at least 1 tile of seperation
var/obj/effect/reality_smash/what_if_i_have_one = locate() in range(1, chosen_location)
var/obj/effect/broken_illusion/what_if_i_had_one_but_got_used = locate() in range(1, chosen_location)
if(what_if_i_have_one || what_if_i_had_one_but_got_used) //we dont want to spawn
continue
- var/obj/effect/reality_smash/RS = new/obj/effect/reality_smash(chosen_location)
- smashes += RS
- ReworkNetwork()
-
-
-/**
- * Adds a mind to the list of people that can see the reality smashes
- *
- * Use this whenever you want to add someone to the list
- */
-/datum/reality_smash_tracker/proc/AddMind(var/datum/mind/M)
- RegisterSignal(M.current,COMSIG_MOB_LOGIN,.proc/ReworkNetwork)
- targets |= M
- _Generate()
- for(var/X in smashes)
- var/obj/effect/reality_smash/reality_smash = X
- reality_smash.AddMind(M)
-
-
-/**
- * Removes a mind from the list of people that can see the reality smashes
- *
- * Use this whenever you want to remove someone from the list
- */
-/datum/reality_smash_tracker/proc/RemoveMind(var/datum/mind/M)
- UnregisterSignal(M.current,COMSIG_MOB_LOGIN)
- targets -= M
- for(var/obj/effect/reality_smash/RS in smashes)
- RS.RemoveMind(M)
+ new /obj/effect/reality_smash(chosen_location)
+ smashes++
/obj/effect/broken_illusion
- name = "Pierced reality"
+ name = "pierced reality"
icon = 'icons/effects/eldritch.dmi'
icon_state = "pierced_illusion"
anchored = TRUE
+ forensic_protected = TRUE
resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ alpha = 0
+
+/obj/effect/broken_illusion/ComponentInitialize()
+ AddComponent(/datum/component/discoverable, 5000)
+
+/obj/effect/broken_illusion/Initialize()
+ . = ..()
+ addtimer(CALLBACK(src,.proc/show_presence),15 SECONDS)
+
+ var/image/I = image('icons/effects/eldritch.dmi',src,null,OBJ_LAYER)
+ I.override = TRUE
+ add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/silicons, "pierced_reality_silicons", I)
+
+ I = image('icons/effects/eldritch.dmi',src, "pierced_illusion",OBJ_LAYER)
+ I.override = TRUE
+ I.alpha = 255
+ I.appearance_flags = RESET_ALPHA
+ add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/heretics,"pierced_reality_heretics",I)
+ addtimer(CALLBACK(src,.proc/dissipate),15 MINUTES)
+
+///Makes this obj appear out of nothing
+/obj/effect/broken_illusion/proc/show_presence()
+ animate(src,alpha = 255,time = 15 SECONDS)
+
+/obj/effect/broken_illusion/proc/dissipate()
+ animate(src,alpha = 0,time = 2 MINUTES)
+ QDEL_IN(src, 2 MINUTES)
/obj/effect/broken_illusion/attack_hand(mob/living/user)
if(!ishuman(user))
@@ -198,11 +184,11 @@
else
var/obj/item/bodypart/arm = human_user.get_active_hand()
if(prob(25))
- to_chat(human_user,"Otherwordly presence tears your arm aparts into atoms as you try to touch the hole in the very fabric of reality!")
+ to_chat(human_user,"An otherwordly presence tears and atomizes your arm as you try to touch the hole in the very fabric of reality!")
arm.dismember()
qdel(arm)
else
- to_chat(human_user,"You pull your hand away from the hole as the eldritch energy flails trying to catch onto the existance itself!")
+ to_chat(human_user,"You pull your hand away from the hole as the eldritch energy flails trying to latch onto existance itself!")
/obj/effect/broken_illusion/attack_tk(mob/user)
if(!ishuman(user))
@@ -210,44 +196,41 @@
var/mob/living/carbon/human/human_user = user
if(IS_HERETIC(human_user))
to_chat(human_user,"You know better than to tempt forces out of your control!")
+ return
+ //a very elaborate way to suicide
+ to_chat(human_user,"Eldritch energy lashes out, piercing your fragile mind, tearing it to pieces!")
+ human_user.ghostize(FALSE,SENTIENCE_ERASE)
+ var/obj/item/bodypart/head/head = locate() in human_user.bodyparts
+ if(head)
+ head.dismember()
+ qdel(head)
else
- //a very elaborate way to suicide
- to_chat(human_user,"Eldritch energy lashes out, piercing your fragile mind, tearing it to pieces!")
- human_user.ghostize()
- var/obj/item/bodypart/head/head = locate() in human_user.bodyparts
- if(head)
- head.dismember()
- qdel(head)
- else
- human_user.gib()
-
+ human_user.gib()
var/datum/effect_system/reagents_explosion/explosion = new()
- explosion.set_up(1, get_turf(human_user), 1, 0)
+ explosion.set_up(1, get_turf(human_user), TRUE, 0)
explosion.start()
/obj/effect/broken_illusion/examine(mob/user)
- if(!IS_HERETIC(user) && ishuman(user))
- var/mob/living/carbon/human/human_user = user
- to_chat(human_user,"Your brain hurts when you look at this!")
- human_user.adjustOrganLoss(ORGAN_SLOT_BRAIN,10)
. = ..()
+ var/mob/living/carbon/human/human_user = user
+ if(istype(human_user) && !IS_HERETIC(human_user) && !IS_HERETIC_MONSTER(human_user))
+ to_chat(human_user,"Your mind burns as you stare at the tear!")
+ SEND_SIGNAL(human_user, COMSIG_ADD_MOOD_EVENT, "gates_of_mansus", /datum/mood_event/gates_of_mansus)
/obj/effect/reality_smash
- name = "\improper reality smash"
+ name = "reality smash"
+ desc = "A weak spot in the veil of reality. You can pierce reality by harvesting this with your Codex Cicatrix to gain charges."
icon = 'icons/effects/eldritch.dmi'
anchored = TRUE
+ forensic_protected = TRUE
resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
- ///We cannot use icon_state since this is invisible, functions the same way but with custom behaviour.
- var/image_state = "reality_smash"
- ///Who can see us?
- var/list/minds = list()
- ///Tracked image
- var/image/img
+ invisibility = INVISIBILITY_OBSERVER
/obj/effect/reality_smash/Initialize()
. = ..()
- img = image(icon, src, image_state, OBJ_LAYER)
+ var/img = image(icon, src, "reality_smash", OBJ_LAYER)
generate_name()
+ add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/heretics,"influence",img)
/obj/effect/reality_smash/Destroy()
on_destroy()
@@ -255,35 +238,13 @@
///Custom effect that happens on destruction
/obj/effect/reality_smash/proc/on_destroy()
- for(var/cm in minds)
- var/datum/mind/cultie = cm
- if(cultie.current?.client)
- cultie.current.client.images -= img
- //clear the list
- minds -= cultie
- GLOB.reality_smash_track.smashes -= src
- img = null
- new /obj/effect/broken_illusion(drop_location())
-
-///Makes the mind able to see this effect
-/obj/effect/reality_smash/proc/AddMind(var/datum/mind/cultie)
- minds |= cultie
- if(cultie.current.client)
- cultie.current.client.images |= img
-
-
-
-///Makes the mind not able to see this effect
-/obj/effect/reality_smash/proc/RemoveMind(var/datum/mind/cultie)
- minds -= cultie
- if(cultie.current.client)
- cultie.current.client.images -= img
-
-
+ GLOB.reality_smash_track.smashes--
+ var/obj/effect/broken_illusion/illusion = new /obj/effect/broken_illusion(drop_location())
+ illusion.name = pick("Researched","Siphoned","Analyzed","Emptied","Drained") + " " + name
///Generates random name
/obj/effect/reality_smash/proc/generate_name()
var/static/list/prefix = list("Omniscient","Thundering","Enlightening","Intrusive","Rejectful","Atomized","Subtle","Rising","Lowering","Fleeting","Towering","Blissful","Arrogant","Threatening","Peaceful","Aggressive")
var/static/list/postfix = list("Flaw","Presence","Crack","Heat","Cold","Memory","Reminder","Breeze","Grasp","Sight","Whisper","Flow","Touch","Veil","Thought","Imperfection","Blemish","Blush")
- name = pick(prefix) + " " + pick(postfix)
+ name = "\improper" + pick(prefix) + " " + pick(postfix)
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_items.dm b/code/modules/antagonists/eldritch_cult/eldritch_items.dm
index f3470a737b408..1a71eceed8a7b 100644
--- a/code/modules/antagonists/eldritch_cult/eldritch_items.dm
+++ b/code/modules/antagonists/eldritch_cult/eldritch_items.dm
@@ -1,6 +1,6 @@
/obj/item/living_heart
name = "Living Heart"
- desc = "Link to the worlds beyond."
+ desc = "A link to the worlds beyond."
icon = 'icons/obj/eldritch.dmi'
icon_state = "living_heart"
w_class = WEIGHT_CLASS_SMALL
@@ -12,11 +12,11 @@
if(!IS_HERETIC(user))
return
if(!target)
- to_chat(user,"No target could be found. Put the living heart on the rune and use the rune to recieve a target.")
+ to_chat(user,"No target could be found. Put the living heart on the rune and use the rune to receive a target.")
return
var/dist = get_dist(user.loc,target.loc)
var/dir = get_dir(user.loc,target.loc)
- if(user.z != target.z)
+ if(user.get_virtual_z_level() != target.get_virtual_z_level())
to_chat(user,"[target.real_name] is on another plane of existance!")
else
switch(dist)
@@ -24,17 +24,15 @@
to_chat(user,"[target.real_name] is near you. They are to the [dir2text(dir)] of you!")
if(16 to 31)
to_chat(user,"[target.real_name] is somewhere in your vicinty. They are to the [dir2text(dir)] of you!")
- if(32 to 127)
- to_chat(user,"[target.real_name] is far away from you. They are to the [dir2text(dir)] of you!")
else
- to_chat(user,"[target.real_name] is beyond our reach.")
+ to_chat(user,"[target.real_name] is far away from you. They are to the [dir2text(dir)] of you!")
if(target.stat == DEAD)
- to_chat(user,"[target.real_name] is dead. Bring them onto a transmutation rune!")
+ to_chat(user,"[target.real_name] is dead. Bring them to a transmutation rune!")
/datum/action/innate/heretic_shatter
name = "Shattering Offer"
- desc = "By breaking your blade you are noticed by the hill or rust and are granted an escape from a dire sitatuion. (Teleports you to a random safe z turf on your current z level but destroys your blade.)"
+ desc = "By breaking your blade, you will be granted salvation from a dire situation. (Teleports you to a random safe turf on your current z level, but destroys your blade.)"
background_icon_state = "bg_ecult"
button_icon_state = "shatter"
icon_icon = 'icons/mob/actions/actions_ecult.dmi'
@@ -56,8 +54,8 @@
/datum/action/innate/heretic_shatter/Activate()
var/turf/safe_turf = find_safe_turf(zlevels = sword.z, extended_safety_checks = TRUE)
- do_teleport(holder,safe_turf,forceMove = TRUE)
- to_chat(holder," You feel a gust of energy flow through your body, Rusted Hills heard your call...")
+ do_teleport(holder,safe_turf,forceMove = TRUE,channel = TELEPORT_CHANNEL_MAGIC)
+ to_chat(holder,"You feel a gust of energy flow through your body... the Rusted Hills heard your call...")
qdel(sword)
@@ -74,7 +72,7 @@
flags_1 = CONDUCT_1
sharpness = IS_SHARP
w_class = WEIGHT_CLASS_NORMAL
- force = 17
+ force = 24
throwforce = 10
hitsound = 'sound/weapons/bladeslice.ogg'
attack_verb = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "rends")
@@ -103,16 +101,19 @@
/obj/item/melee/sickly_blade/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
. = ..()
var/datum/antagonist/heretic/cultie = user.mind.has_antag_datum(/datum/antagonist/heretic)
- if(!cultie || !proximity_flag)
+ if(!cultie)
return
var/list/knowledge = cultie.get_all_knowledge()
for(var/X in knowledge)
var/datum/eldritch_knowledge/eldritch_knowledge_datum = knowledge[X]
- eldritch_knowledge_datum.on_eldritch_blade(target,user,proximity_flag,click_parameters)
+ if(proximity_flag)
+ eldritch_knowledge_datum.on_eldritch_blade(target,user,proximity_flag,click_parameters)
+ else
+ eldritch_knowledge_datum.on_ranged_attack_eldritch_blade(target,user,click_parameters)
/obj/item/melee/sickly_blade/rust
name = "\improper Rusted Blade"
- desc = "This crescent blade is decrepit, wasting to dust. Yet still it bites, catching flesh with jagged, rotten teeth."
+ desc = "This crescent blade is decrepit, wasting to rust. Yet still it bites, ripping flesh and bone with jagged, rotten teeth."
icon_state = "rust_blade"
item_state = "rust_blade"
@@ -124,13 +125,13 @@
/obj/item/melee/sickly_blade/flesh
name = "\improper Flesh Blade"
- desc = "A crescent blade born from a fleshwarped creature. Keenly aware, it seeks to spread to others the excruitations it has endured from dread origins."
+ desc = "A crescent blade born from a fleshwarped creature. Keenly aware, it seeks to spread to others the suffering it has endured from its dreadful origins."
icon_state = "flesh_blade"
item_state = "flesh_blade"
/obj/item/clothing/neck/eldritch_amulet
name = "Warm Eldritch Medallion"
- desc = "A strange medallion. Peering through the crystalline surface, the world around you melts away. You see your own beating heart, and the pulse of a thousand others."
+ desc = "A strange medallion. Peering through the crystalline surface, the world around you melts away. You see your own beating heart, and the pulsing of a thousand others."
icon = 'icons/obj/eldritch.dmi'
icon_state = "eye_medalion"
w_class = WEIGHT_CLASS_SMALL
@@ -139,7 +140,7 @@
/obj/item/clothing/neck/eldritch_amulet/equipped(mob/user, slot)
. = ..()
- if(ishuman(user) && user.mind && slot == SLOT_NECK && IS_HERETIC(user) )
+ if(ishuman(user) && user.mind && slot == ITEM_SLOT_NECK && IS_HERETIC(user) )
ADD_TRAIT(user, trait, CLOTHING_TRAIT)
user.update_sight()
@@ -150,15 +151,21 @@
/obj/item/clothing/neck/eldritch_amulet/piercing
name = "Piercing Eldritch Medallion"
- desc = "A strange medallion. Peering through the crystalline surface, the light refracts into new and terrifying spectrums of color. You see yourself, reflected off cascading mirrors, warped into improbable shapes."
+ desc = "A strange medallion. Peering through the crystalline surface, the light refracts into new and terrifying spectrums of color. You see yourself, reflected off cascading mirrors, warped into impossible shapes."
trait = TRAIT_XRAY_VISION
+/obj/item/clothing/neck/eldritch_amulet/guise
+ name = "guise of Istasha"
+ desc = "An odd amulet formed out of multiple floating parts, strung togethere by forces from another world."
+ icon_state = "eye_medalion"
+ trait = TRAIT_DIGINVIS
+
/obj/item/clothing/head/hooded/cult_hoodie/eldritch
name = "ominous hood"
icon_state = "eldritch"
desc = "A torn, dust-caked hood. Strange eyes line the inside."
flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR
- flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF
+ flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH
flash_protect = 1
/obj/item/clothing/suit/hooded/cultrobes/eldritch
@@ -171,11 +178,87 @@
allowed = list(/obj/item/melee/sickly_blade, /obj/item/forbidden_book)
hoodtype = /obj/item/clothing/head/hooded/cult_hoodie/eldritch
// slightly better than normal cult robes
- armor = list("melee" = 50, "bullet" = 50, "laser" = 50,"energy" = 50, "bomb" = 35, "bio" = 20, "rad" = 0, "fire" = 20, "acid" = 20)
+ armor = list("melee" = 50, "bullet" = 50, "laser" = 50,"energy" = 50, "bomb" = 35, "bio" = 20, "rad" = 0, "fire" = 20, "acid" = 20, "stamina" = 60)
/obj/item/reagent_containers/glass/beaker/eldritch
name = "flask of eldritch essence"
- desc = "Toxic to the close minded. Healing to those with knowledge of the beyond."
+ desc = "Toxic to the closed minded, yet refreshing to those with knowledge of the beyond."
icon = 'icons/obj/eldritch.dmi'
icon_state = "eldrich_flask"
list_reagents = list(/datum/reagent/eldritch = 50)
+
+/obj/item/clothing/mask/void_mask
+ name = "Mask Of Madness"
+ desc = "Mask created from the suffering of existance, you can look down it's eyes, and notice something gazing back at you."
+ icon_state = "mad_mask"
+ w_class = WEIGHT_CLASS_SMALL
+ flags_cover = MASKCOVERSEYES
+ resistance_flags = FLAMMABLE
+ flags_inv = HIDEFACE|HIDEFACIALHAIR
+ ///Who is wearing this
+ var/mob/living/carbon/human/local_user
+
+/obj/item/clothing/mask/void_mask/equipped(mob/user, slot)
+ . = ..()
+ if(slot == ITEM_SLOT_MASK && ishuman(user) && user.mind)
+ local_user = user
+ START_PROCESSING(SSobj,src)
+
+ if(IS_HERETIC(user) || IS_HERETIC_MONSTER(user))
+ return
+ ADD_TRAIT(src, TRAIT_NODROP, CLOTHING_TRAIT)
+
+/obj/item/clothing/mask/void_mask/dropped(mob/M)
+ local_user = null
+ STOP_PROCESSING(SSobj,src)
+ REMOVE_TRAIT(src, TRAIT_NODROP, CLOTHING_TRAIT)
+ return ..()
+
+/obj/item/clothing/mask/void_mask/process()
+ if(!local_user)
+ return PROCESS_KILL
+
+ if((IS_HERETIC(local_user) || IS_HERETIC_MONSTER(local_user)) && HAS_TRAIT(src,TRAIT_NODROP))
+ REMOVE_TRAIT(src, TRAIT_NODROP, CLOTHING_TRAIT)
+
+ for(var/mob/living/carbon/human/human_in_range in viewers(9,local_user))
+ if(IS_HERETIC(human_in_range) || IS_HERETIC_MONSTER(human_in_range))
+ continue
+
+ SEND_SIGNAL(human_in_range,COMSIG_HUMAN_VOID_MASK_ACT,rand(-1,-10))
+
+ if(prob(60))
+ human_in_range.hallucination += 5
+
+ if(prob(40))
+ human_in_range.Jitter(5)
+
+ if(prob(30))
+ human_in_range.emote(pick("giggle","laugh"))
+ human_in_range.adjustStaminaLoss(10)
+
+ if(prob(25))
+ human_in_range.Dizzy(5)
+
+/obj/item/clothing/neck/crucifix
+ name = "crucifix"
+ desc = "In the eventuality that one of those you falesly accused is, in fact, a real witch, this will ward you against their curses."
+ resistance_flags = FIRE_PROOF | ACID_PROOF
+ icon = 'icons/obj/objects.dmi'
+ icon_state = "crucifix"
+ w_class = WEIGHT_CLASS_SMALL
+
+/obj/item/clothing/neck/crucifix/equipped(mob/living/carbon/human/user, slot)
+ . = ..()
+ if(slot == ITEM_SLOT_NECK && istype(user))
+ ADD_TRAIT(user, TRAIT_WARDED, CLOTHING_TRAIT)
+
+/obj/item/clothing/neck/crucifix/dropped(mob/user)
+ . = ..()
+ REMOVE_TRAIT(user, TRAIT_WARDED, CLOTHING_TRAIT)
+
+/obj/item/clothing/neck/crucifix/rosary
+ name = "rosary beads"
+ desc = "A wooden crucifix meant to ward off curses and hexes."
+ resistance_flags = FLAMMABLE
+ icon_state = "rosary"
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm b/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm
index dea7a2cec0700..71184b8169945 100644
--- a/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm
+++ b/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm
@@ -1,11 +1,10 @@
-
/**
- * #Eldritch Knwoledge
- *
- * Datum that makes eldritch cultist interesting.
- *
- * Eldritch knowledge aren't instantiated anywhere roundstart, and are initalized and destroyed as the round goes on.
- */
+ * #Eldritch Knwoledge
+ *
+ * Datum that makes eldritch cultist interesting.
+ *
+ * Eldritch knowledge aren't instantiated anywhere roundstart, and are initalized and destroyed as the round goes on.
+ */
/datum/eldritch_knowledge
///Name of the knowledge
var/name = "Basic knowledge"
@@ -35,41 +34,41 @@
required_atoms = temp_list
/**
- * What happens when this is assigned to an antag datum
- *
- * This proc is called whenever a new eldritch knowledge is added to an antag datum
- */
+ * What happens when this is assigned to an antag datum
+ *
+ * This proc is called whenever a new eldritch knowledge is added to an antag datum
+ */
/datum/eldritch_knowledge/proc/on_gain(mob/user)
to_chat(user, "[gain_text]")
return
/**
- * What happens when you loose this
- *
- * This proc is called whenever antagonist looses his antag datum, put cleanup code in here
- */
+ * What happens when you loose this
+ *
+ * This proc is called whenever antagonist looses his antag datum, put cleanup code in here
+ */
/datum/eldritch_knowledge/proc/on_lose(mob/user)
return
/**
- * What happens every tick
- *
- * This proc is called on SSprocess in eldritch cultist antag datum. SSprocess happens roughly every second
- */
+ * What happens every tick
+ *
+ * This proc is called on SSprocess in eldritch cultist antag datum. SSprocess happens roughly every second
+ */
/datum/eldritch_knowledge/proc/on_life(mob/user)
return
/**
- * Special check for recipes
- *
- * If you are adding a more complex summoning or something that requires a special check that parses through all the atoms in an area override this.
- */
+ * Special check for recipes
+ *
+ * If you are adding a more complex summoning or something that requires a special check that parses through all the atoms in an area override this.
+ */
/datum/eldritch_knowledge/proc/recipe_snowflake_check(list/atoms,loc)
return TRUE
/**
- * What happens once the recipe is succesfully finished
- *
- * By default this proc creates atoms from result_atoms list. Override this is you want something else to happen.
- */
+ * What happens once the recipe is succesfully finished
+ *
+ * By default this proc creates atoms from result_atoms list. Override this is you want something else to happen.
+ */
/datum/eldritch_knowledge/proc/on_finished_recipe(mob/living/user,list/atoms,loc)
if(result_atoms.len == 0)
return FALSE
@@ -80,11 +79,11 @@
return TRUE
/**
- * Used atom cleanup
- *
- * Overide this proc if you dont want ALL ATOMS to be destroyed. useful in many situations.
- */
-/datum/eldritch_knowledge/proc/cleanup_atoms(list/atoms)
+ * Used atom cleanup
+ *
+ * Overide this proc if you dont want ALL ATOMS to be destroyed. useful in many situations.
+ */
+/datum/eldritch_knowledge/proc/cleanup_atoms(list/atoms) //BUG: crafting something will add to this list and delete ALL of the required components, IE if you have 2 bibles on a rune, and craft a codex, it will make 1 codex and qdel BOTH bibles
for(var/X in atoms)
var/atom/A = X
if(!isliving(A))
@@ -93,22 +92,35 @@
return
/**
- * Mansus grasp act
- *
- * Gives addtional effects to mansus grasp spell
- */
+ * Mansus grasp act
+ *
+ * Gives addtional effects to mansus grasp spell
+ * Gives addtional effects to mansus touch spell of your followers
+ */
/datum/eldritch_knowledge/proc/on_mansus_grasp(atom/target, mob/user, proximity_flag, click_parameters)
return FALSE
+/datum/eldritch_knowledge/proc/on_mansus_touch(atom/target, mob/user, proximity_flag, click_parameters)
+ return FALSE
+
+
/**
- * Sickly blade act
- *
- * Gives addtional effects to sickly blade weapon
- */
+ * Sickly blade act
+ *
+ * Gives addtional effects to sickly blade weapon
+ */
/datum/eldritch_knowledge/proc/on_eldritch_blade(target,user,proximity_flag,click_parameters)
return
+/**
+ * Sickly blade distant act
+ *
+ * Same as [/datum/eldritch_knowledge/proc/on_eldritch_blade] but works on targets that are not in proximity to you.
+ */
+/datum/eldritch_knowledge/proc/on_ranged_attack_eldritch_blade(atom/target,mob/user,click_parameters)
+ return
+
//////////////
///Subtypes///
//////////////
@@ -144,20 +156,22 @@
var/list/compiled_list = list()
for(var/H in GLOB.carbon_list)
- if(!ishuman(H))
- continue
var/mob/living/carbon/human/human_to_check = H
- if(fingerprints[md5(human_to_check.dna.uni_identity)])
+ if(istype(human_to_check) && fingerprints[md5(human_to_check.dna.uni_identity)])
compiled_list |= human_to_check.real_name
compiled_list[human_to_check.real_name] = human_to_check
if(compiled_list.len == 0)
- to_chat(user, "The items don't posses required fingerprints.")
+ to_chat(user, "These items don't possess the required fingerprints or DNA.")
return FALSE
var/chosen_mob = input("Select the person you wish to curse","Your target") as null|anything in sortList(compiled_list, /proc/cmp_mob_realname_dsc)
if(!chosen_mob)
return FALSE
+ var/mob/living/living_mob = chosen_mob
+ if (istype(living_mob) && HAS_TRAIT(living_mob, TRAIT_WARDED))
+ to_chat(user, "The curse failed! The target is warded against curses.")
+ return FALSE
curse(compiled_list[chosen_mob])
addtimer(CALLBACK(src, .proc/uncurse, compiled_list[chosen_mob]),timer)
return TRUE
@@ -177,14 +191,14 @@
//we need to spawn the mob first so that we can use it in pollCandidatesForMob, we will move it from nullspace down the code
var/mob/living/summoned = new mob_to_summon(loc)
message_admins("[summoned.name] is being summoned by [user.real_name] in [loc]")
- var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [summoned.name]", ROLE_HERETIC, null, FALSE, 100, summoned)
+ var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [summoned.real_name]", ROLE_HERETIC, null, FALSE, 100, summoned)
if(!LAZYLEN(candidates))
to_chat(user,"No ghost could be found...")
qdel(summoned)
return FALSE
var/mob/dead/observer/C = pick(candidates)
log_game("[key_name_admin(C)] has taken control of ([key_name_admin(summoned)]), their master is [user.real_name]")
- summoned.ghostize(FALSE)
+ summoned.ghostize(FALSE,SENTIENCE_ERASE)
summoned.key = C.key
summoned.mind.add_antag_datum(/datum/antagonist/heretic_monster)
var/datum/antagonist/heretic_monster/heretic_monster = summoned.mind.has_antag_datum(/datum/antagonist/heretic_monster)
@@ -226,7 +240,7 @@
/datum/eldritch_knowledge/spell/basic
name = "Break of dawn"
- desc = "Starts your journey in the mansus. Allows you to select a target using a living heart on a transmutation rune."
+ desc = "You can sacrifice specific targets by placing their dead bodies and the living heart on a transmutation rune, and performing a transmutation ritual."
gain_text = "Gates of mansus open up to your mind."
next_knowledge = list(/datum/eldritch_knowledge/base_rust,/datum/eldritch_knowledge/base_ash,/datum/eldritch_knowledge/base_flesh)
cost = 0
@@ -260,28 +274,35 @@
if(!istype(X,/obj/item/forbidden_book))
continue
var/obj/item/forbidden_book/FB = X
- FB.charge++
+ FB.charge += 2
break
if(!LH.target)
var/datum/objective/A = new
A.owner = user.mind
- var/datum/mind/targeted = A.find_target()//easy way, i dont feel like copy pasting that entire block of code
- LH.target = targeted.current
+ var/list/targets = list()
+ for(var/i in 1 to 3)
+ var/datum/mind/targeted = A.find_target()//easy way, i dont feel like copy pasting that entire block of code
+ if(!targeted)
+ break
+ targets[targeted.current.real_name] = targeted.current
+ LH.target = targets[input(user,"Choose your next target","Target") in targets]
qdel(A)
if(LH.target)
to_chat(user,"Your new target has been selected, go and sacrifice [LH.target.real_name]!")
-
else
to_chat(user,"target could not be found for living heart.")
/datum/eldritch_knowledge/spell/basic/cleanup_atoms(list/atoms)
return
+
+// --- GENERAL ---
+
/datum/eldritch_knowledge/living_heart
name = "Living Heart"
desc = "Allows you to create additional living hearts, using a heart, a pool of blood and a poppy. Living hearts when used on a transmutation rune will grant you a person to hunt and sacrifice on the rune. Every sacrifice gives you an additional charge in the book."
- gain_text = "Gates of mansus open up to your mind."
+ gain_text = "The Gates of Mansus open up to your mind."
cost = 0
required_atoms = list(/obj/item/organ/heart,/obj/effect/decal/cleanable/blood,/obj/item/reagent_containers/food/snacks/grown/poppy)
result_atoms = list(/obj/item/living_heart)
@@ -293,5 +314,179 @@
gain_text = "Their hand is at your throats, yet you see Them not."
cost = 0
required_atoms = list(/obj/item/organ/eyes,/obj/item/stack/sheet/animalhide/human,/obj/item/storage/book/bible,/obj/item/pen)
- result_atoms = list(/obj/item/forbidden_book)
+ result_atoms = list(/obj/item/forbidden_book/empty)
route = "Start"
+
+// --- CRAFTING ---
+
+/datum/eldritch_knowledge/ashen_eyes
+ name = "Ashen Eyes"
+ gain_text = "Piercing eyes, guide me through the mundane."
+ desc = "Allows you to craft a thermal vision amulet by transmutating eyes with a glass shard."
+ cost = 1
+ next_knowledge = list(/datum/eldritch_knowledge/spell/ashen_shift,/datum/eldritch_knowledge/flesh_ghoul)
+ required_atoms = list(/obj/item/organ/eyes,/obj/item/shard)
+ result_atoms = list(/obj/item/clothing/neck/eldritch_amulet)
+
+/datum/eldritch_knowledge/guise
+ name = "Guise of Istasha"
+ gain_text = "Hide your form from the ones without a soul."
+ desc = "Allows you to craft a digital camoflage amulet by transmutating a circuit board with a glass shard."
+ cost = 1
+ next_knowledge = list(/datum/eldritch_knowledge/spell/ashen_shift,/datum/eldritch_knowledge/flesh_ghoul)
+ required_atoms = list(/obj/item/circuitboard,/obj/item/shard)
+ result_atoms = list(/obj/item/clothing/neck/eldritch_amulet/guise)
+
+/datum/eldritch_knowledge/armor
+ name = "Armorer's ritual"
+ desc = "You can now create eldritch armor using a table and a gas mask."
+ gain_text = "For I am the heir to the throne of doom."
+ cost = 1
+ next_knowledge = list(/datum/eldritch_knowledge/rust_regen,/datum/eldritch_knowledge/flesh_ghoul)
+ required_atoms = list(/obj/structure/table,/obj/item/clothing/mask/gas)
+ result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/eldritch)
+
+/datum/eldritch_knowledge/essence
+ name = "Priest's Ritual"
+ desc = "You can now transmute a tank of water and a glass shard into a bottle of eldritch water."
+ gain_text = "This is an old recipe. The Owl whispered it to me."
+ cost = 1
+ required_atoms = list(/obj/structure/reagent_dispensers/watertank)
+ result_atoms = list(/obj/item/reagent_containers/glass/beaker/eldritch)
+
+// --- CURSES ---
+
+/datum/eldritch_knowledge/curse/alteration
+ name = "Curse Of Alteration"
+ gain_text = "Mortal bodies, prisons of flesh. Death, a release..."
+ desc = "Start an alteration ritual by transmuting a wire cutter, a hatchet and an item that the victim touched with their bare hands. Inflict a debilitating curse that will cripple your target's body for 2 minutes. Add eyes, ears, limbs or tongues to the mix to disable those organs while the curse is in effect."
+ cost = 1
+ required_atoms = list(/obj/item/wirecutters,/obj/item/hatchet)
+ timer = 2 MINUTES
+ var/list/debuffs = list()
+
+/datum/eldritch_knowledge/curse/alteration/on_finished_recipe(mob/living/user, list/atoms, loc) //the ritual completed, take the payment and apply the curse
+ //declare
+ debuffs = list()
+ var/list/extra_atoms = list()
+
+ //check variables
+ for(var/A in range(1, loc)) //this
+ var/obj/item/bodypart/selected_part = A
+ if (istype(selected_part) && selected_part.status == BODYPART_ORGANIC)
+ switch(selected_part.body_zone)
+ if(BODY_ZONE_R_LEG)
+ extra_atoms |= A
+ debuffs |= "r_leg"
+ if(BODY_ZONE_L_LEG)
+ extra_atoms |= A
+ debuffs |= "l_leg"
+ if(BODY_ZONE_R_ARM)
+ extra_atoms |= A
+ debuffs |= "r_arm"
+ if(BODY_ZONE_L_ARM)
+ extra_atoms |= A
+ debuffs |= "l_arm"
+
+ var/obj/item/organ/selected_organ = A
+ if (istype(selected_organ) && selected_organ.status == ORGAN_ORGANIC)
+ switch(selected_organ.slot)
+ if(ORGAN_SLOT_TONGUE)
+ extra_atoms |= A
+ debuffs |= "tongue"
+ if(ORGAN_SLOT_EYES)
+ extra_atoms |= A
+ debuffs |= "eyes"
+ if(ORGAN_SLOT_EARS)
+ extra_atoms |= A
+ debuffs |= "ears"
+
+ cleanup_atoms(extra_atoms)
+ . = ..()
+ return .
+
+/datum/eldritch_knowledge/curse/alteration/curse(mob/living/chosen_mob)
+ . = ..()
+ if (chosen_mob.has_status_effect(/datum/status_effect/corrosion_curse))
+ return FALSE
+
+ var/mob/living/carbon/human/chosen_mortal = chosen_mob
+ if (!istype(chosen_mob))
+ return
+
+ chosen_mortal.apply_status_effect(/datum/status_effect/corrosion_curse) //the purpose of this debuff is to alert the victim they've been cursed
+ for(var/X in debuffs)
+ switch (X)
+ if ("r_leg")
+ ADD_TRAIT(chosen_mortal,TRAIT_PARALYSIS_R_LEG,CURSE_TRAIT)
+ if ("l_leg")
+ ADD_TRAIT(chosen_mortal,TRAIT_PARALYSIS_L_LEG,CURSE_TRAIT)
+ if ("r_arm")
+ ADD_TRAIT(chosen_mortal,TRAIT_PARALYSIS_R_ARM,CURSE_TRAIT)
+ if ("l_arm")
+ ADD_TRAIT(chosen_mortal,TRAIT_PARALYSIS_L_ARM,CURSE_TRAIT)
+ if ("tongue")
+ ADD_TRAIT(chosen_mortal, TRAIT_MUTE, CURSE_TRAIT)
+ if ("eyes")
+ chosen_mortal.become_blind(CURSE_TRAIT)
+ if ("ears")
+ ADD_TRAIT(chosen_mortal, TRAIT_DEAF, CURSE_TRAIT)
+ return .
+
+/datum/eldritch_knowledge/curse/alteration/uncurse(mob/living/chosen_mob)
+ . = ..()
+ var/mob/living/carbon/human/chosen_mortal = chosen_mob
+ //organ fuckup
+ chosen_mortal.remove_status_effect(/datum/status_effect/corrosion_curse)
+
+ //CC
+ chosen_mortal.cure_blind(CURSE_TRAIT)
+ REMOVE_TRAIT(chosen_mortal, TRAIT_MUTE, CURSE_TRAIT)
+ REMOVE_TRAIT(chosen_mortal, TRAIT_DEAF, CURSE_TRAIT)
+
+ //paralysis
+ REMOVE_TRAIT(chosen_mortal,TRAIT_PARALYSIS_R_ARM,CURSE_TRAIT)
+ REMOVE_TRAIT(chosen_mortal,TRAIT_PARALYSIS_L_ARM,CURSE_TRAIT)
+ REMOVE_TRAIT(chosen_mortal,TRAIT_PARALYSIS_L_LEG,CURSE_TRAIT)
+ REMOVE_TRAIT(chosen_mortal,TRAIT_PARALYSIS_R_LEG,CURSE_TRAIT)
+ chosen_mortal.update_mobility()
+
+ return .
+
+// --- SPELLS ---
+
+/datum/eldritch_knowledge/spell/cleave
+ name = "Blood Cleave"
+ gain_text = "At first I didn't understand these instruments of war, but the priest told me to use them regardless. Soon, he said, I would know them well."
+ desc = "Gives AOE spell that causes heavy bleeding and blood loss."
+ cost = 1
+ spell_to_add = /obj/effect/proc_holder/spell/pointed/cleave
+ next_knowledge = list(/datum/eldritch_knowledge/spell/rust_wave,/datum/eldritch_knowledge/spell/flame_birth)
+
+/datum/eldritch_knowledge/spell/blood_siphon
+ name = "Blood Siphon"
+ gain_text = "No matter the man, we bleed all the same. That's what the Marshal told me."
+ desc = "You gain a spell that drains health from your enemies to restores your own."
+ cost = 1
+ spell_to_add = /obj/effect/proc_holder/spell/targeted/touch/blood_siphon
+ next_knowledge = list(/datum/eldritch_knowledge/summon/raw_prophet,/datum/eldritch_knowledge/spell/area_conversion)
+
+// --- SUMMONS ---
+
+/datum/eldritch_knowledge/summon/ashy
+ name = "Ashen Ritual"
+ gain_text = "I combined my principle of hunger with my desire for destruction. And the Nightwatcher knew my name."
+ desc = "You can now summon an Ash Man by transmutating a pile of ash, a head and a book."
+ cost = 1
+ required_atoms = list(/obj/effect/decal/cleanable/ash,/obj/item/bodypart/head,/obj/item/book)
+ mob_to_summon = /mob/living/simple_animal/hostile/eldritch/ash_spirit
+ next_knowledge = list(/datum/eldritch_knowledge/summon/stalker,/datum/eldritch_knowledge/spell/rust_wave)
+
+/datum/eldritch_knowledge/summon/rusty
+ name = "Rusted Ritual"
+ gain_text = "I combined my principle of hunger with my desire for corruption. And the Rusted Hills called my name."
+ desc = "You can now summon a Rust Walker by transmutating a vomit pool, a severed head and a book."
+ cost = 1
+ required_atoms = list(/obj/effect/decal/cleanable/vomit,/obj/item/bodypart/head,/obj/item/book)
+ mob_to_summon = /mob/living/simple_animal/hostile/eldritch/rust_spirit
+ next_knowledge = list(/datum/eldritch_knowledge/summon/stalker,/datum/eldritch_knowledge/spell/flame_birth)
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_magic.dm b/code/modules/antagonists/eldritch_cult/eldritch_magic.dm
index e38562c03833d..2a0f54f6af7cd 100644
--- a/code/modules/antagonists/eldritch_cult/eldritch_magic.dm
+++ b/code/modules/antagonists/eldritch_cult/eldritch_magic.dm
@@ -42,14 +42,19 @@
/obj/item/melee/touch_attack/mansus_fist
name = "Mansus Grasp"
- desc = "A sinister looking aura that distorts the flow of reality around it. Causes knockdown, major stamina damage aswell as some Brute. It gains additional beneficial effects with certain knowledges you can research."
+ desc = "A sinister looking aura that distorts the flow of reality around it. Mutes, causes knockdown, major stamina damage aswell as some Brute. You also can lay and remove transmutation runes using this. It gains additional beneficial effects with certain knowledges you can research."
icon_state = "mansus_grasp"
item_state = "mansus_grasp"
catchphrase = "R'CH T'H TR'TH"
+ ///Where we cannot create the rune?
+ var/static/list/blacklisted_turfs = typecacheof(list(/turf/closed,/turf/open/space,/turf/open/lava))
/obj/item/melee/touch_attack/mansus_fist/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
if(!proximity_flag || target == user)
- return
+ return FALSE
+ if(istype(target,/obj/effect/eldritch))
+ remove_rune(target,user)
+ return FALSE
playsound(user, 'sound/items/welder.ogg', 75, TRUE)
if(ishuman(target))
var/mob/living/carbon/human/tar = target
@@ -66,6 +71,7 @@
use_charge = TRUE
var/mob/living/carbon/C = target
C.adjustBruteLoss(10)
+ C.silent = 3 SECONDS
C.AdjustKnockdown(5 SECONDS)
C.adjustStaminaLoss(80)
var/list/knowledge = cultie.get_all_knowledge()
@@ -77,6 +83,25 @@
if(use_charge)
return ..()
+///Draws a rune on a selected turf
+/obj/item/melee/touch_attack/mansus_fist/attack_self(mob/user)
+
+ for(var/turf/T in range(1,user))
+ if(is_type_in_typecache(T, blacklisted_turfs))
+ to_chat(user, "The targeted terrain doesn't support runes!")
+ return
+ var/A = get_turf(user)
+ to_chat(user, "You start drawing a rune...")
+
+ if(do_after(user,30 SECONDS,FALSE,A))
+ new /obj/effect/eldritch/big(A)
+
+///Removes runes from the selected turf
+/obj/item/melee/touch_attack/mansus_fist/proc/remove_rune(atom/target,mob/user)
+ to_chat(user, "You start removing a rune...")
+ if(do_after(user,2 SECONDS,target = user))
+ qdel(target)
+
/obj/effect/proc_holder/spell/aoe_turf/rust_conversion
name = "Aggressive Spread"
desc = "Spreads rust onto nearby turfs."
@@ -219,7 +244,7 @@
if(!can_target(targets[1], user))
return FALSE
- for(var/mob/living/carbon/human/C in range(1,targets[1]))
+ for(var/mob/living/carbon/human/C in hearers(1,targets[1]))
targets |= C
@@ -362,7 +387,7 @@
playsound(get_turf(centre), 'sound/items/welder.ogg', 75, TRUE)
var/_range = 1
for(var/i = 0, i <= max_range,i++)
- for(var/turf/T in spiral_range_turfs(_range,centre))
+ for(var/turf/open/T in spiral_range_turfs(_range,centre))
new /obj/effect/hotspot(T)
T.hotspot_expose(700,50,1)
_range++
@@ -405,13 +430,15 @@
/obj/effect/proc_holder/spell/targeted/fire_sworn/proc/remove()
has_fire_ring = FALSE
-/obj/effect/proc_holder/spell/targeted/fire_sworn/process()
+/obj/effect/proc_holder/spell/targeted/fire_sworn/process(delta_time)
. = ..()
if(!has_fire_ring)
return
- for(var/turf/T in range(1,current_user))
+ for(var/turf/open/T in RANGE_TURFS(1,current_user))
new /obj/effect/hotspot(T)
- T.hotspot_expose(700,50,1)
+ T.hotspot_expose(700,250 * delta_time,1)
+ for(var/mob/living/livies in T.contents - current_user)
+ livies.adjustFireLoss(25 * delta_time)
/obj/effect/proc_holder/spell/targeted/worm_contract
@@ -460,7 +487,7 @@
if(!ishuman(user))
return
var/mob/living/carbon/human/human_user = user
- for(var/mob/living/carbon/target in view(7,user))
+ for(var/mob/living/carbon/target in ohearers(7,user))
if(target.stat == DEAD || !target.on_fire)
continue
//This is essentially a death mark, use this to finish your opponent quicker.
@@ -472,7 +499,7 @@
human_user.adjustBruteLoss(-10, FALSE)
human_user.adjustFireLoss(-10, FALSE)
human_user.adjustStaminaLoss(-10, FALSE)
- human_user.adjustToxLoss(-10, FALSE)
+ human_user.adjustToxLoss(-10, FALSE, TRUE)
human_user.adjustOxyLoss(-10)
/obj/effect/proc_holder/spell/targeted/shed_human_form
@@ -499,7 +526,7 @@
target.mind.transfer_to(outside, TRUE)
target.forceMove(outside)
target.apply_status_effect(STATUS_EFFECT_STASIS,STASIS_ASCENSION_EFFECT)
- for(var/mob/living/carbon/human/humie in view(9,outside)-target)
+ for(var/mob/living/carbon/human/humie in (viewers(9,outside)-target))
if(IS_HERETIC(humie) || IS_HERETIC_MONSTER(humie))
continue
SEND_SIGNAL(humie, COMSIG_ADD_MOOD_EVENT, "gates_of_mansus", /datum/mood_event/gates_of_mansus)
@@ -556,7 +583,7 @@
to_chat(originator, "You begin linking [target]'s mind to yours...")
to_chat(target, "You feel your mind being pulled... connected... intertwined with the very fabric of reality...")
- if(!do_after(originator, 6 SECONDS, target))
+ if(!do_after(originator, 6 SECONDS, target = target))
return
if(!originator.link_mob(target))
to_chat(originator, "You can't seem to link [target]'s mind...")
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_monster_antag.dm b/code/modules/antagonists/eldritch_cult/eldritch_monster_antag.dm
index c2f404d8fbad1..7ecba7e1df168 100644
--- a/code/modules/antagonists/eldritch_cult/eldritch_monster_antag.dm
+++ b/code/modules/antagonists/eldritch_cult/eldritch_monster_antag.dm
@@ -7,7 +7,7 @@
job_rank = ROLE_HERETIC
var/antag_hud_type = ANTAG_HUD_HERETIC
var/antag_hud_name = "heretic_beast"
- var/datum/antagonist/master
+ var/datum/antagonist/heretic/master
/datum/antagonist/heretic_monster/admin_add(datum/mind/new_owner,mob/admin)
new_owner.add_antag_datum(src)
@@ -15,12 +15,14 @@
log_admin("[key_name(admin)] has heresized [key_name(new_owner)].")
/datum/antagonist/heretic_monster/greet()
- owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ecult_op.ogg', 100, FALSE, pressure_affected = FALSE)//subject to change
- to_chat(owner, "You became an Eldritch Horror!")
+ owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ecult_op.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE)//subject to change
+ to_chat(owner, "You became an Eldritch Horror, servant of [master]!")
+ owner.current.client?.tgui_panel?.give_antagonist_popup("Eldritch Horror",
+ "You are an Eldritch Horror, follow your master's orders.")
/datum/antagonist/heretic_monster/on_removal()
if(master)
- to_chat(owner, "Your master is no longer [master.owner.current.real_name]")
+ to_chat(owner, "Your no longer bound to your master, [master.owner.current.real_name]")
master = null
return ..()
diff --git a/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm
index 92ce37df727f3..059c7567cfe4f 100644
--- a/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm
+++ b/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm
@@ -1,7 +1,7 @@
/datum/eldritch_knowledge/base_ash
name = "Nightwatcher's secret"
- desc = "Opens up the path of ash to you. Allows you to transmute a match with a kitchen knife or it's derivatives into an ashen blade."
- gain_text = "City guard knows their watch. If you ask them at night they may tell you about the ashy lantern."
+ desc = "Opens up the Path of Ash to you. Allows you to transmute a match with a kitchen knife, or its derivatives, into an Ashen Blade."
+ gain_text = "The City Guard know their watch. If you ask them at night, they may tell you about the ashy lantern."
banned_knowledge = list(/datum/eldritch_knowledge/base_rust,/datum/eldritch_knowledge/base_flesh,/datum/eldritch_knowledge/final/rust_final,/datum/eldritch_knowledge/final/flesh_final)
next_knowledge = list(/datum/eldritch_knowledge/ashen_grasp)
required_atoms = list(/obj/item/kitchen/knife,/obj/item/match)
@@ -9,10 +9,18 @@
cost = 1
route = PATH_ASH
+/datum/eldritch_knowledge/base_ash/on_gain(mob/user)
+ . = ..()
+ ADD_TRAIT( user, TRAIT_NOFIRE, MAGIC_TRAIT)
+
+/datum/eldritch_knowledge/base_ash/on_lose(mob/user)
+ . = ..()
+ REMOVE_TRAIT( user, TRAIT_NOFIRE, MAGIC_TRAIT)
+
/datum/eldritch_knowledge/spell/ashen_shift
name = "Ashen Shift"
- gain_text = "Ash is all the same, how can one man master it all?"
- desc = "Short range jaunt that can help you escape from bad situations."
+ gain_text = "The Nightwatcher was the first of them, his treason started it all."
+ desc = "A short range jaunt that can help you escape from bad situations."
cost = 1
spell_to_add = /obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash
next_knowledge = list(/datum/eldritch_knowledge/ash_mark,/datum/eldritch_knowledge/essence,/datum/eldritch_knowledge/ashen_eyes)
@@ -28,70 +36,57 @@
/datum/eldritch_knowledge/ashen_grasp/on_mansus_grasp(atom/target, mob/user, proximity_flag, click_parameters)
. = ..()
- if(!iscarbon(target))
+ var/mob/living/carbon/C = target
+ if(!istype(C))
return
+ to_chat(C, "Your eyes burn horrifically!") //pocket sand! also, this is the message that changeling blind stings use, and no, I'm not ashamed about reusing it
+ C.become_nearsighted(EYE_DAMAGE)
+ C.blind_eyes(5)
+ C.blur_eyes(10)
+ return
+/datum/eldritch_knowledge/ashen_grasp/on_eldritch_blade(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(!iscarbon(target))
+ return
var/mob/living/carbon/C = target
var/datum/status_effect/eldritch/E = C.has_status_effect(/datum/status_effect/eldritch/rust) || C.has_status_effect(/datum/status_effect/eldritch/ash) || C.has_status_effect(/datum/status_effect/eldritch/flesh)
if(E)
- . = TRUE
E.on_effect()
for(var/X in user.mind.spell_list)
if(!istype(X,/obj/effect/proc_holder/spell/targeted/touch/mansus_grasp))
continue
var/obj/effect/proc_holder/spell/targeted/touch/mansus_grasp/MG = X
MG.charge_counter = min(round(MG.charge_counter + MG.charge_max * 0.75),MG.charge_max) // refunds 75% of charge.
- var/atom/throw_target = get_edge_target_turf(C, user.dir)
- if(!C.anchored)
- . = TRUE
- C.throw_at(throw_target, rand(4,8), 14, user)
- return
-
-/datum/eldritch_knowledge/ashen_eyes
- name = "Ashen Eyes"
- gain_text = "Piercing eyes may guide me through the mundane."
- desc = "Allows you to craft thermal vision amulet by transmutating eyes with a glass shard."
- cost = 1
- next_knowledge = list(/datum/eldritch_knowledge/spell/ashen_shift,/datum/eldritch_knowledge/flesh_ghoul)
- required_atoms = list(/obj/item/organ/eyes,/obj/item/shard)
- result_atoms = list(/obj/item/clothing/neck/eldritch_amulet)
/datum/eldritch_knowledge/ash_mark
name = "Mark of ash"
- gain_text = "Spread the famine."
- desc = "Your sickly blade now applies ash mark on hit. Use your mansus grasp to proc the mark. Mark of Ash causes stamina damage, and fire loss, and spreads to a nearby carbon. Damage decreases with how many times the mark has spread."
+ gain_text = "The Nightwatcher was a very particular man, always watching in the dead of night. But in spite of his duty, he regularly tranced through the manse with his blazing lantern held high."
+ desc = "Your Mansus Grasp now applies the Mark of Ash on hit. Attack the afflicted with your Sickly Blade to detonate the mark. Upon detonation, the Mark of Ash causes stamina damage and burn damage, and spreads to an additional nearby opponent. The damage decreases with each spread."
cost = 2
- next_knowledge = list(/datum/eldritch_knowledge/curse/blindness)
+ next_knowledge = list(/datum/eldritch_knowledge/mad_mask)
banned_knowledge = list(/datum/eldritch_knowledge/rust_mark,/datum/eldritch_knowledge/flesh_mark)
route = PATH_ASH
-/datum/eldritch_knowledge/ash_mark/on_eldritch_blade(target,user,proximity_flag,click_parameters)
+/datum/eldritch_knowledge/ash_mark/on_mansus_grasp(target,user,proximity_flag,click_parameters)
. = ..()
if(isliving(target))
var/mob/living/living_target = target
living_target.apply_status_effect(/datum/status_effect/eldritch/ash,5)
-/datum/eldritch_knowledge/curse/blindness
- name = "Curse of blindness"
- gain_text = "Blind man walks through the world, unnoticed by the masses."
- desc = "Curse someone with 2 minutes of complete blindness by sacrificing a pair of eyes, a screwdriver and a pool of blood, with an object that the victim has touched with their bare hands."
- cost = 1
+/datum/eldritch_knowledge/mad_mask
+ name = "Mask of Madness"
+ gain_text = "He walks the world, unnoticed by the masses."
+ desc = "Allows you to transmute any mask, with a candle and a pair of eyes, to create a mask of madness, It causes passive stamina damage to everyone around the wearer and hallucinations, can be forced on a non believer to make him unable to take it off..."
+ result_atoms = list(/obj/item/clothing/mask/void_mask)
required_atoms = list(/obj/item/organ/eyes,/obj/item/screwdriver,/obj/effect/decal/cleanable/blood)
- next_knowledge = list(/datum/eldritch_knowledge/curse/corrosion,/datum/eldritch_knowledge/ash_blade_upgrade,/datum/eldritch_knowledge/curse/paralysis)
- timer = 2 MINUTES
+ next_knowledge = list(/datum/eldritch_knowledge/guise,/datum/eldritch_knowledge/ash_blade_upgrade,/datum/eldritch_knowledge/curse/alteration)
+ cost = 1
route = PATH_ASH
-/datum/eldritch_knowledge/curse/blindness/curse(mob/living/chosen_mob)
- . = ..()
- chosen_mob.become_blind(MAGIC_TRAIT)
-
-/datum/eldritch_knowledge/curse/blindness/uncurse(mob/living/chosen_mob)
- . = ..()
- chosen_mob.cure_blind(MAGIC_TRAIT)
-
/datum/eldritch_knowledge/spell/flame_birth
name = "Flame Birth"
- gain_text = "Nightwatcher was a man of principles, and yet he arose from the chaos he vowed to protect from."
+ gain_text = "The Nightwatcher was a man of principles, and yet his power arose from the chaos he vowed to combat."
desc = "Short range spell that allows you to curse someone with massive sanity loss."
cost = 1
spell_to_add = /obj/effect/proc_holder/spell/targeted/fiery_rebirth
@@ -100,8 +95,8 @@
/datum/eldritch_knowledge/ash_blade_upgrade
name = "Fiery blade"
- gain_text = "May the sun burn the heretics."
- desc = "Your blade of choice will now add firestacks."
+ gain_text = "Blade in hand, he swung and swung as the ash fell from the skies. His city, his people... all burnt to cinders, and yet life still remained in his charred body."
+ desc = "Your blade of choice will now light your enemies ablaze."
cost = 2
next_knowledge = list(/datum/eldritch_knowledge/spell/flame_birth)
banned_knowledge = list(/datum/eldritch_knowledge/rust_blade_upgrade,/datum/eldritch_knowledge/flesh_blade_upgrade)
@@ -114,63 +109,17 @@
C.adjust_fire_stacks(1)
C.IgniteMob()
-/datum/eldritch_knowledge/curse/corrosion
- name = "Curse of Corrosion"
- gain_text = "Cursed land, cursed man, cursed mind."
- desc = "Curse someone for 2 minutes of vomiting and major organ damage. Using a wirecutter, a spill of blood, a heart, left arm and a right arm, and an item that the victim touched with their bare hands."
- cost = 1
- required_atoms = list(/obj/item/wirecutters,/obj/effect/decal/cleanable/blood,/obj/item/organ/heart,/obj/item/bodypart/l_arm,/obj/item/bodypart/r_arm)
- next_knowledge = list(/datum/eldritch_knowledge/curse/blindness,/datum/eldritch_knowledge/spell/area_conversion)
- timer = 2 MINUTES
-
-/datum/eldritch_knowledge/curse/corrosion/curse(mob/living/chosen_mob)
- . = ..()
- chosen_mob.apply_status_effect(/datum/status_effect/corrosion_curse)
-
-/datum/eldritch_knowledge/curse/corrosion/uncurse(mob/living/chosen_mob)
- . = ..()
- chosen_mob.remove_status_effect(/datum/status_effect/corrosion_curse)
-
-/datum/eldritch_knowledge/curse/paralysis
- name = "Curse of Paralysis"
- gain_text = "Corrupt their flesh, make them bleed."
- desc = "Curse someone for 5 minutes of inability to walk. Using a knife, pool of blood, left leg, right leg, a hatchet and an item that the victim touched with their bare hands. "
- cost = 1
- required_atoms = list(/obj/item/kitchen/knife,/obj/effect/decal/cleanable/blood,/obj/item/bodypart/l_leg,/obj/item/bodypart/r_leg,/obj/item/hatchet)
- next_knowledge = list(/datum/eldritch_knowledge/curse/blindness,/datum/eldritch_knowledge/summon/raw_prophet)
- timer = 5 MINUTES
-
-/datum/eldritch_knowledge/curse/paralysis/curse(mob/living/chosen_mob)
- . = ..()
- ADD_TRAIT(chosen_mob,TRAIT_PARALYSIS_L_LEG,MAGIC_TRAIT)
- ADD_TRAIT(chosen_mob,TRAIT_PARALYSIS_R_LEG,MAGIC_TRAIT)
- chosen_mob.update_mobility()
-
-/datum/eldritch_knowledge/curse/paralysis/uncurse(mob/living/chosen_mob)
- . = ..()
- REMOVE_TRAIT(chosen_mob,TRAIT_PARALYSIS_L_LEG,MAGIC_TRAIT)
- REMOVE_TRAIT(chosen_mob,TRAIT_PARALYSIS_R_LEG,MAGIC_TRAIT)
- chosen_mob.update_mobility()
-
-/datum/eldritch_knowledge/spell/cleave
- name = "Blood Cleave"
- gain_text = "At first i didn't know these instruments of war, but the priest told me to use them."
- desc = "Gives AOE spell that causes heavy bleeding and blood loss."
- cost = 1
- spell_to_add = /obj/effect/proc_holder/spell/pointed/cleave
- next_knowledge = list(/datum/eldritch_knowledge/spell/rust_wave,/datum/eldritch_knowledge/spell/flame_birth)
-
/datum/eldritch_knowledge/final/ash_final
name = "Ashlord's rite"
- gain_text = "The forgotten lords have spoken! The lord of ash have come! Fear the fire!"
- desc = "Bring 3 corpses onto a transmutation rune, you will become immune to fire ,space ,cold and other enviromental hazards and become overall sturdier to all other damages. You will gain a spell that passively creates ring of fire around you as well ,as you will gain a powerful abiltiy that let's you create a wave of flames all around you."
+ gain_text = "The Nightwatcher found the rite and shared it amongst mankind! For now I am one with the fire, WITNESS MY ASCENSION!"
+ desc = "Bring 3 corpses onto a transmutation rune, you will become immune to fire, the vacuum of space, cold and other enviromental hazards and become overall sturdier to all other damages. You will gain a spell that passively creates ring of fire around you as well ,as you will gain a powerful ability that lets you create a wave of flames all around you."
required_atoms = list(/mob/living/carbon/human)
cost = 3
route = PATH_ASH
- var/list/trait_list = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER)
+ var/list/trait_list = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER)
/datum/eldritch_knowledge/final/ash_final/on_finished_recipe(mob/living/user, list/atoms, loc)
- priority_announce("$^@*$^@(#&$(@^$^@# Fear the blaze, for Ashbringer [user.real_name] has come! $^@*$^@(#&$(@^$^@#","#$^@*$^@(#&$(@^$^@#", 'sound/ai/spanomalies.ogg')
+ priority_announce("$^@*$^@(#&$(@^$^@# Fear the blaze, for the Ashlord, [user.real_name] has ascended! The flames shall consume all! $^@*$^@(#&$(@^$^@#","#$^@*$^@(#&$(@^$^@#", ANNOUNCER_SPANOMALIES)
user.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/fire_cascade/big)
user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/fire_sworn)
var/mob/living/carbon/human/H = user
@@ -186,7 +135,7 @@
return
var/turf/L = get_turf(user)
var/datum/gas_mixture/env = L.return_air()
- for(var/turf/T in range(1,user))
+ for(var/turf/T as() in RANGE_TURFS(1,user))
env = T.return_air()
env.set_temperature(env.return_temperature() + 5 )
T.air_update_turf()
diff --git a/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm
index eeeab79438e64..2225f85c2e64f 100644
--- a/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm
+++ b/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm
@@ -1,7 +1,11 @@
+#define GHOUL_MAX_HEALTH 25
+#define MUTE_MAX_HEALTH 50
+#define ORIGINAL_MAX_HEALTH 100
+
/datum/eldritch_knowledge/base_flesh
name = "Principle of Hunger"
- desc = "Opens up the path of flesh to you. Allows you to transmute a pool of blood with a kitchen knife into a Flesh Blade"
- gain_text = "Hundred's of us starved, but I.. I found the strength in my greed."
+ desc = "Opens up the Path of Flesh to you. Allows you to transmute a pool of blood with a kitchen knife, or its derivatives, into a Flesh Blade."
+ gain_text = "Hundreds of us starved, but not me... I found strength in my greed."
banned_knowledge = list(/datum/eldritch_knowledge/base_ash,/datum/eldritch_knowledge/base_rust,/datum/eldritch_knowledge/final/ash_final,/datum/eldritch_knowledge/final/rust_final)
next_knowledge = list(/datum/eldritch_knowledge/flesh_grasp)
required_atoms = list(/obj/item/kitchen/knife,/obj/effect/decal/cleanable/blood)
@@ -12,7 +16,7 @@
/datum/eldritch_knowledge/flesh_ghoul
name = "Imperfect Ritual"
desc = "Allows you to resurrect the dead as voiceless dead by sacrificing them on the transmutation rune with a poppy. Voiceless dead are mute and have 50 HP. You can only have 2 at a time."
- gain_text = "I found notes.. notes of a ritual, it was unfinished and yet i still did it."
+ gain_text = "I found notes of a dark ritual, unfinished... yet still, I pushed forward."
cost = 1
required_atoms = list(/mob/living/carbon/human,/obj/item/reagent_containers/food/snacks/grown/poppy)
next_knowledge = list(/datum/eldritch_knowledge/flesh_mark,/datum/eldritch_knowledge/armor,/datum/eldritch_knowledge/ashen_eyes)
@@ -41,7 +45,7 @@
return
var/mob/dead/observer/C = pick(candidates)
message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(humie)]) to replace an AFK player.")
- humie.ghostize(0)
+ humie.ghostize(FALSE,SENTIENCE_ERASE)
humie.key = C.key
log_game("[key_name_admin(humie)] has become a voiceless dead, their master is [user.real_name]")
@@ -52,10 +56,11 @@
ADD_TRAIT(humie, TRAIT_IGNOREDAMAGESLOWDOWN, MAGIC_TRAIT)
ADD_TRAIT(humie, TRAIT_NOSTAMCRIT, MAGIC_TRAIT)
ADD_TRAIT(humie, TRAIT_NOLIMBDISABLE, MAGIC_TRAIT)
- humie.setMaxHealth(50)
- humie.health = 50 // Voiceless dead are much tougher than ghouls
+ humie.setMaxHealth(MUTE_MAX_HEALTH)
+ humie.health = MUTE_MAX_HEALTH // Voiceless dead are much tougher than ghouls
humie.become_husk()
humie.faction |= "heretics"
+ humie.apply_status_effect(/datum/status_effect/ghoul)
var/datum/antagonist/heretic_monster/heretic_monster = humie.mind.add_antag_datum(/datum/antagonist/heretic_monster)
var/datum/antagonist/heretic/master = user.mind.has_antag_datum(/datum/antagonist/heretic)
@@ -65,15 +70,19 @@
ghouls += humie
/datum/eldritch_knowledge/flesh_ghoul/proc/remove_ghoul(datum/source)
+ SIGNAL_HANDLER
+
var/mob/living/carbon/human/humie = source
ghouls -= humie
+ humie.setMaxHealth(ORIGINAL_MAX_HEALTH)
+ humie.remove_status_effect(/datum/status_effect/ghoul)
humie.mind.remove_antag_datum(/datum/antagonist/heretic_monster)
UnregisterSignal(source,COMSIG_MOB_DEATH)
/datum/eldritch_knowledge/flesh_grasp
name = "Grasp of Flesh"
- gain_text = "My new found desire, it drove me to do great things! The Priest said."
- desc = "Empowers your mansus grasp to be able to create a single ghoul out of a dead person. Ghouls have only 25 HP and look like husks."
+ gain_text = "My new found desires drove me to greater and greater heights."
+ desc = "Empowers your mansus grasp to be able to create a single ghoul out of a dead person. Ghouls have only 25 HP and look like husks to the heathens' eyes."
cost = 1
next_knowledge = list(/datum/eldritch_knowledge/flesh_ghoul)
var/ghoul_amt = 1
@@ -85,13 +94,6 @@
if(!ishuman(target) || target == user)
return
var/mob/living/carbon/human/human_target = target
- var/datum/status_effect/eldritch/eldritch_effect = human_target.has_status_effect(/datum/status_effect/eldritch/rust) || human_target.has_status_effect(/datum/status_effect/eldritch/ash) || human_target.has_status_effect(/datum/status_effect/eldritch/flesh)
- if(eldritch_effect)
- . = TRUE
- eldritch_effect.on_effect()
- if(ishuman(target))
- var/mob/living/carbon/human/htarget = target
- htarget.bleed_rate += 10
if(QDELETED(human_target) || human_target.stat != DEAD)
return
@@ -114,34 +116,48 @@
log_game("[key_name_admin(human_target)] has become a ghoul, their master is [user.real_name]")
//we change it to true only after we know they passed all the checks
. = TRUE
- RegisterSignal(human_target,COMSIG_MOB_DEATH,.proc/remove_ghoul)
+ RegisterSignal(human_target,COMSIG_MOB_DEATH, .proc/remove_ghoul)
human_target.revive(full_heal = TRUE, admin_revive = TRUE)
ADD_TRAIT(human_target, TRAIT_NOSTAMCRIT, MAGIC_TRAIT)
ADD_TRAIT(human_target, TRAIT_NOLIMBDISABLE, MAGIC_TRAIT)
- human_target.setMaxHealth(25)
- human_target.health = 25
+ human_target.setMaxHealth(GHOUL_MAX_HEALTH)
+ human_target.health = GHOUL_MAX_HEALTH
human_target.become_husk()
+ human_target.apply_status_effect(/datum/status_effect/ghoul)
human_target.faction |= "heretics"
var/datum/antagonist/heretic_monster/heretic_monster = human_target.mind.add_antag_datum(/datum/antagonist/heretic_monster)
var/datum/antagonist/heretic/master = user.mind.has_antag_datum(/datum/antagonist/heretic)
heretic_monster.set_owner(master)
/datum/eldritch_knowledge/flesh_grasp/proc/remove_ghoul(datum/source)
+ SIGNAL_HANDLER
+
var/mob/living/carbon/human/humie = source
spooky_scaries -= humie
+ humie.setMaxHealth(ORIGINAL_MAX_HEALTH)
+ humie.remove_status_effect(/datum/status_effect/ghoul)
humie.mind.remove_antag_datum(/datum/antagonist/heretic_monster)
UnregisterSignal(source, COMSIG_MOB_DEATH)
+/datum/eldritch_knowledge/flesh_grasp/on_eldritch_blade(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(!ishuman(target))
+ return
+ var/mob/living/carbon/C = target
+ var/datum/status_effect/eldritch/E = C.has_status_effect(/datum/status_effect/eldritch/rust) || C.has_status_effect(/datum/status_effect/eldritch/ash) || C.has_status_effect(/datum/status_effect/eldritch/flesh)
+ if(E)
+ E.on_effect()
+
/datum/eldritch_knowledge/flesh_mark
- name = "Mark of flesh"
- gain_text = "I saw them, the marked ones. The screams.. the silence."
- desc = "Your sickly blade now applies mark of flesh status effect. To proc the mark, use your mansus grasp on the marked. Mark of flesh when procced causeds additional bleeding."
+ name = "Mark of Flesh"
+ gain_text = "I saw them, the marked ones. The screams... the silence."
+ desc = "Your Mansus Grasp now applies the Mark of Flesh on hit. Attack the afflicted with your Sickly Blade to detonate the mark. Upon detonation, the Mark of Flesh causes additional bleeding."
cost = 2
next_knowledge = list(/datum/eldritch_knowledge/summon/raw_prophet)
banned_knowledge = list(/datum/eldritch_knowledge/rust_mark,/datum/eldritch_knowledge/ash_mark)
route = PATH_FLESH
-/datum/eldritch_knowledge/flesh_mark/on_eldritch_blade(target,user,proximity_flag,click_parameters)
+/datum/eldritch_knowledge/flesh_mark/on_mansus_grasp(target,user,proximity_flag,click_parameters)
. = ..()
if(isliving(target))
var/mob/living/living_target = target
@@ -149,8 +165,8 @@
/datum/eldritch_knowledge/flesh_blade_upgrade
name = "Bleeding Steel"
- gain_text = "It rained blood, that's when i understood the gravekeeper's advice."
- desc = "Your blade will now cause additional bleeding."
+ gain_text = "And then, blood rained from the heavens. That's when I finally understood the Marshal's teachings."
+ desc = "Your Sickly Blade will now cause additional bleeding."
cost = 2
next_knowledge = list(/datum/eldritch_knowledge/summon/stalker)
banned_knowledge = list(/datum/eldritch_knowledge/ash_blade_upgrade,/datum/eldritch_knowledge/rust_blade_upgrade)
@@ -162,53 +178,26 @@
var/mob/living/carbon/human/H = target
H.bleed_rate+= 2
-
/datum/eldritch_knowledge/summon/raw_prophet
name = "Raw Ritual"
- gain_text = "Uncanny man, walks alone in the valley, I was able to call his aid."
- desc = "You can now summon a Raw Prophet using eyes, a left arm, right arm and a pool of blood. Raw prophets have increased seeing range, as well as Xray. But are very fragile and weak."
+ gain_text = "The Uncanny Man, who walks alone in the valley between the worlds... I was able to summon his aid."
+ desc = "You can now summon a Raw Prophet by transmutating a pair of eyes, a left arm and a pool of blood. Raw prophets have increased seeing range, as well as X-Ray vision, but they are very fragile."
cost = 1
required_atoms = list(/obj/item/organ/eyes,/obj/item/bodypart/l_arm,/obj/item/bodypart/r_arm,/obj/effect/decal/cleanable/blood)
mob_to_summon = /mob/living/simple_animal/hostile/eldritch/raw_prophet
- next_knowledge = list(/datum/eldritch_knowledge/flesh_blade_upgrade,/datum/eldritch_knowledge/spell/blood_siphon,/datum/eldritch_knowledge/curse/paralysis)
+ next_knowledge = list(/datum/eldritch_knowledge/flesh_blade_upgrade,/datum/eldritch_knowledge/spell/blood_siphon,/datum/eldritch_knowledge/curse/alteration)
route = PATH_FLESH
/datum/eldritch_knowledge/summon/stalker
name = "Lonely Ritual"
- gain_text = "I was able to combine my greed and desires to summon an eldritch beast i have not seen before."
- desc = "You can now summon a Stalker using a knife, a flower, a pen and a piece of paper. Stalkers can shapeshift into harmeless animals and get close to the victim."
+ gain_text = "I was able to combine my greed and desires to summon an eldritch beast I had never seen before. An ever shapeshifting mass of flesh, it knew well my goals."
+ desc = "You can now summon a Stalker by transmutating a pair of eyes, a candle, a pen and a piece of paper. Stalkers can shapeshift into harmless animals to get close to the victim."
cost = 1
required_atoms = list(/obj/item/kitchen/knife,/obj/item/reagent_containers/food/snacks/grown/poppy,/obj/item/pen,/obj/item/paper)
mob_to_summon = /mob/living/simple_animal/hostile/eldritch/stalker
next_knowledge = list(/datum/eldritch_knowledge/summon/ashy,/datum/eldritch_knowledge/summon/rusty,/datum/eldritch_knowledge/final/flesh_final)
route = PATH_FLESH
-/datum/eldritch_knowledge/summon/ashy
- name = "Ashen Ritual"
- gain_text = "I combined principle of hunger with desire of destruction. The eyeful lords have noticed me."
- desc = "You can now summon an Ash Man by transmutating a pile of ash , a head and a book."
- cost = 1
- required_atoms = list(/obj/effect/decal/cleanable/ash,/obj/item/bodypart/head,/obj/item/book)
- mob_to_summon = /mob/living/simple_animal/hostile/eldritch/ash_spirit
- next_knowledge = list(/datum/eldritch_knowledge/summon/stalker,/datum/eldritch_knowledge/spell/rust_wave)
-
-/datum/eldritch_knowledge/summon/rusty
- name = "Rusted Ritual"
- gain_text = "I combined principle of hunger with desire of corruption. The rusted hills call my name."
- desc = "You can now summon a Rust Walker transmutating vomit pool, a head and a book."
- cost = 1
- required_atoms = list(/obj/effect/decal/cleanable/vomit,/obj/item/bodypart/head,/obj/item/book)
- mob_to_summon = /mob/living/simple_animal/hostile/eldritch/rust_spirit
- next_knowledge = list(/datum/eldritch_knowledge/summon/stalker,/datum/eldritch_knowledge/spell/flame_birth)
-
-/datum/eldritch_knowledge/spell/blood_siphon
- name = "Blood Siphon"
- gain_text = "Our blood is all the same after all, the owl told me."
- desc = "You gain a spell that drains enemies health and restores yours."
- cost = 1
- spell_to_add = /obj/effect/proc_holder/spell/targeted/touch/blood_siphon
- next_knowledge = list(/datum/eldritch_knowledge/summon/raw_prophet,/datum/eldritch_knowledge/spell/area_conversion)
-
/datum/eldritch_knowledge/final/flesh_final
name = "Priest's Final Hymn"
gain_text = "Man of this world. Hear me! For the time of the lord of arms has come! Emperor of Flesh guides my army!"
@@ -219,7 +208,7 @@
/datum/eldritch_knowledge/final/flesh_final/on_finished_recipe(mob/living/user, list/atoms, loc)
. = ..()
- priority_announce("$^@*$^@(#&$(@^$^@# Ever coiling vortex, Reality unfoiled. KING OF ARMS [user.real_name] has come! Fear the ever twisting hand! $^@*$^@(#&$(@^$^@#","#$^@*$^@(#&$(@^$^@#", 'sound/ai/spanomalies.ogg')
+ priority_announce("$^@*$^@(#&$(@^$^@# Ever coiling vortex. Reality unfolded. THE LORD OF ARMS, [user.real_name] has ascended! Fear the ever twisting hand! $^@*$^@(#&$(@^$^@#","#$^@*$^@(#&$(@^$^@#", ANNOUNCER_SPANOMALIES)
user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shed_human_form)
if(!ishuman(user))
return
@@ -231,3 +220,7 @@
ghoul1.ghoul_amt *= 3
var/datum/eldritch_knowledge/flesh_ghoul/ghoul2 = heretic.get_knowledge(/datum/eldritch_knowledge/flesh_ghoul)
ghoul2.max_amt *= 3
+
+#undef GHOUL_MAX_HEALTH
+#undef MUTE_MAX_HEALTH
+#undef ORIGINAL_MAX_HEALTH
diff --git a/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm
index 6440f7cc1e9a2..7f079ca7e44ea 100644
--- a/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm
+++ b/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm
@@ -1,7 +1,7 @@
/datum/eldritch_knowledge/base_rust
name = "Blacksmith's Tale"
- desc = "Opens up the path of rust to you. Allows you to transmute a knife with any trash item into a Rusty Blade."
- gain_text = "Let me tell you a story, blacksmith said as he glazed into his rusty blade."
+ desc = "Opens up the Path of Rust to you. Allows you to transmute a kitchen knife, or its derivatives, with any trash item into a Rusty Blade."
+ gain_text = "'Let me tell you a story', said the Blacksmith, as he gazed deep into his rusty blade."
banned_knowledge = list(/datum/eldritch_knowledge/base_ash,/datum/eldritch_knowledge/base_flesh,/datum/eldritch_knowledge/final/ash_final,/datum/eldritch_knowledge/final/flesh_final)
next_knowledge = list(/datum/eldritch_knowledge/rust_fist)
required_atoms = list(/obj/item/kitchen/knife,/obj/item/trash)
@@ -10,9 +10,9 @@
route = PATH_RUST
/datum/eldritch_knowledge/rust_fist
- name = "Grasp of rust"
- desc = "Empowers your mansus grasp to deal 500 damage to non-living matter and rust any turf it touches. Destroys already rusted turfs."
- gain_text = "Rust grows on the ceiling of the mansus."
+ name = "Grasp of Rust"
+ desc = "Empowers your Mansus Grasp to deal 500 damage to non-living matter and rust any surface it touches. Already rusted surfaces are destroyed."
+ gain_text = "On the ceiling of the Mansus, rust grows as moss does on a stone."
cost = 1
next_knowledge = list(/datum/eldritch_knowledge/rust_regen)
var/rust_force = 500
@@ -21,54 +21,57 @@
/datum/eldritch_knowledge/rust_fist/on_mansus_grasp(atom/target, mob/user, proximity_flag, click_parameters)
. = ..()
+ target.rust_heretic_act()
+ return TRUE
+
+/datum/eldritch_knowledge/rust_fist/on_eldritch_blade(atom/target, mob/user, proximity_flag, click_parameters)
if(ishuman(target))
var/mob/living/carbon/human/H = target
var/datum/status_effect/eldritch/E = H.has_status_effect(/datum/status_effect/eldritch/rust) || H.has_status_effect(/datum/status_effect/eldritch/ash) || H.has_status_effect(/datum/status_effect/eldritch/flesh)
if(E)
E.on_effect()
H.adjustOrganLoss(pick(ORGAN_SLOT_BRAIN,ORGAN_SLOT_EARS,ORGAN_SLOT_EYES,ORGAN_SLOT_LIVER,ORGAN_SLOT_LUNGS,ORGAN_SLOT_STOMACH,ORGAN_SLOT_HEART),25)
- target.rust_heretic_act()
- return TRUE
/datum/eldritch_knowledge/spell/area_conversion
name = "Agressive Spread"
- desc = "Spreads rust to nearby turfs. Destroys already rusted walls."
- gain_text = "All men wise know not to touch the bound king."
+ desc = "Spreads rust to nearby surfaces. Already rusted surfaces are destroyed."
+ gain_text = "All wise men know well not to touch the Bound King."
cost = 1
spell_to_add = /obj/effect/proc_holder/spell/aoe_turf/rust_conversion
- next_knowledge = list(/datum/eldritch_knowledge/rust_blade_upgrade,/datum/eldritch_knowledge/curse/corrosion,/datum/eldritch_knowledge/spell/blood_siphon)
+ next_knowledge = list(/datum/eldritch_knowledge/rust_blade_upgrade,/datum/eldritch_knowledge/guise,/datum/eldritch_knowledge/spell/blood_siphon)
route = PATH_RUST
/datum/eldritch_knowledge/rust_regen
name = "Leeching Walk"
desc = "Passively heals you when you are on rusted tiles."
- gain_text = "The strength was unparallel, it was unnatural. Blacksmith was smiling."
+ gain_text = "The strength was unparalleled, unnatural. The Blacksmith was smiling."
cost = 1
next_knowledge = list(/datum/eldritch_knowledge/rust_mark,/datum/eldritch_knowledge/armor,/datum/eldritch_knowledge/essence)
route = PATH_RUST
/datum/eldritch_knowledge/rust_regen/on_life(mob/user)
- . = ..()
+ ..()
var/turf/user_loc_turf = get_turf(user)
if(!istype(user_loc_turf, /turf/open/floor/plating/rust) || !isliving(user))
return
var/mob/living/living_user = user
living_user.adjustBruteLoss(-2, FALSE)
living_user.adjustFireLoss(-2, FALSE)
- living_user.adjustToxLoss(-2, FALSE)
+ living_user.adjustToxLoss(-2, FALSE, TRUE)
living_user.adjustOxyLoss(-0.5, FALSE)
living_user.adjustStaminaLoss(-2)
+ living_user.AdjustAllImmobility(-5)
/datum/eldritch_knowledge/rust_mark
name = "Mark of Rust"
- desc = "Your eldritch blade now applies a rust mark. Rust mark has a chance to deal between 0 to 200 damage to 75% of enemies items. To Detonate the mark use your mansus grasp on it."
- gain_text = "Lords of the depths help those in dire need at a cost."
+ desc = "Your Mansus Grasp now applies the Mark of Rust on hit. Attack the afflicted with your Sickly Blade to detonate the mark. Upon detonation, the Mark of Rust has a chance to deal between 0 to 200 damage to 75% of your enemy's held items."
+ gain_text = "Rusted Hills help those in dire need at a cost."
cost = 2
next_knowledge = list(/datum/eldritch_knowledge/spell/area_conversion)
banned_knowledge = list(/datum/eldritch_knowledge/ash_mark,/datum/eldritch_knowledge/flesh_mark)
route = PATH_RUST
-/datum/eldritch_knowledge/rust_mark/on_eldritch_blade(target,user,proximity_flag,click_parameters)
+/datum/eldritch_knowledge/rust_mark/on_mansus_grasp(target,user,proximity_flag,click_parameters)
. = ..()
if(isliving(target))
var/mob/living/living_target = target
@@ -76,8 +79,9 @@
/datum/eldritch_knowledge/rust_blade_upgrade
name = "Toxic blade"
- gain_text = "Let the blade guide you through the flesh."
- desc = "Your blade of choice will now add toxin to enemies bloodstream."
+ desc = "Your blade of choice will now poison your enemies on hit."
+ gain_text = "The Blade will guide you through the flesh, should you let it."
+ desc = "Your blade of choice will now transfer your pain as toxic damage."
cost = 2
next_knowledge = list(/datum/eldritch_knowledge/spell/rust_wave)
banned_knowledge = list(/datum/eldritch_knowledge/ash_blade_upgrade,/datum/eldritch_knowledge/flesh_blade_upgrade)
@@ -85,9 +89,10 @@
/datum/eldritch_knowledge/rust_blade_upgrade/on_eldritch_blade(target,user,proximity_flag,click_parameters)
. = ..()
- if(iscarbon(target))
- var/mob/living/carbon/carbon_target = target
- carbon_target.reagents.add_reagent(/datum/reagent/eldritch, 2)
+ var/mob/living/carbon/carbon_user = user
+ var/mob/living/carbon/carbon_target = target
+ if(istype(carbon_user) && istype(carbon_target))
+ carbon_target.adjustToxLoss((carbon_user.maxHealth - carbon_user.health)/10)
/datum/eldritch_knowledge/spell/rust_wave
name = "Wave of Rust"
@@ -98,37 +103,22 @@
next_knowledge = list(/datum/eldritch_knowledge/final/rust_final,/datum/eldritch_knowledge/spell/cleave,/datum/eldritch_knowledge/summon/rusty)
route = PATH_RUST
-/datum/eldritch_knowledge/armor
- name = "Armorer's ritual"
- desc = "You can now create eldritch armor using a table and a gas mask."
- gain_text = "For I am the heir to the throne of doom."
- cost = 1
- next_knowledge = list(/datum/eldritch_knowledge/rust_regen,/datum/eldritch_knowledge/flesh_ghoul)
- required_atoms = list(/obj/structure/table,/obj/item/clothing/mask/gas)
- result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/eldritch)
-
-/datum/eldritch_knowledge/essence
- name = "Priest's ritual"
- desc = "You can now transmute a tank of water into a bottle of eldritch water."
- gain_text = "This is an old recipe, i got it from an owl."
- cost = 1
- next_knowledge = list(/datum/eldritch_knowledge/rust_regen,/datum/eldritch_knowledge/spell/ashen_shift)
- required_atoms = list(/obj/structure/reagent_dispensers/watertank)
- result_atoms = list(/obj/item/reagent_containers/glass/beaker/eldritch)
-
/datum/eldritch_knowledge/final/rust_final
name = "Rustbringer's Oath"
- desc = "Bring 3 corpses onto the transmutation rune. After you finish the ritual rust will now automatically spread from the rune. Your healing on rust is also tripled, while you become more resillient overall."
+ desc = "Bring 3 corpses onto the transmutation rune. After you finish the ritual rust will now automatically spread from the rune. Your healing on rust is also tripled, while you become more resillient overall and space proof."
gain_text = "Champion of rust. Corruptor of steel. Fear the dark for Rustbringer has come!"
cost = 3
required_atoms = list(/mob/living/carbon/human)
route = PATH_RUST
+ var/list/trait_list = list(TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTLOWPRESSURE,TRAIT_NODISMEMBER)
/datum/eldritch_knowledge/final/rust_final/on_finished_recipe(mob/living/user, list/atoms, loc)
var/mob/living/carbon/human/H = user
H.physiology.brute_mod *= 0.5
H.physiology.burn_mod *= 0.5
- priority_announce("$^@*$^@(#&$(@^$^@# Fear the decay, for Rustbringer [user.real_name] has come! $^@*$^@(#&$(@^$^@#","#$^@*$^@(#&$(@^$^@#", 'sound/ai/spanomalies.ogg')
+ for(var/X in trait_list)
+ ADD_TRAIT(user,X,MAGIC_TRAIT)
+ priority_announce("$^@*$^@(#&$(@^$^@# Fear the decay, for the Rustbringer, [user.real_name] has ascended! None shall escape the corrosion! $^@*$^@(#&$(@^$^@#","#$^@*$^@(#&$(@^$^@#", ANNOUNCER_SPANOMALIES)
new /datum/rust_spread(loc)
return ..()
@@ -138,32 +128,33 @@
if(!finished)
return
var/mob/living/carbon/human/human_user = user
- human_user.adjustBruteLoss(-3, FALSE)
- human_user.adjustFireLoss(-3, FALSE)
- human_user.adjustToxLoss(-3, FALSE)
- human_user.adjustOxyLoss(-1, FALSE)
- human_user.adjustStaminaLoss(-10)
-
+ human_user.adjustBruteLoss(-4, FALSE)
+ human_user.adjustFireLoss(-4, FALSE)
+ human_user.adjustToxLoss(-4, FALSE, TRUE)
+ human_user.adjustOxyLoss(-2, FALSE)
+ human_user.adjustStaminaLoss(-20)
+ human_user.AdjustAllImmobility(-10)
/**
- * #Rust spread datum
- *
- * Simple datum that automatically spreads rust around it
- *
- * Simple implementation of automatically growing entity
- */
+ * #Rust spread datum
+ *
+ * Simple datum that automatically spreads rust around it
+ *
+ * Simple implementation of automatically growing entity
+ */
/datum/rust_spread
var/list/edge_turfs = list()
+ var/turf/centre
var/list/turfs = list()
var/static/list/blacklisted_turfs = typecacheof(list(/turf/open/indestructible,/turf/closed/indestructible,/turf/open/space,/turf/open/lava,/turf/open/chasm))
- var/spread_per_tick = 6
+ var/spread_per_sec = 6
/datum/rust_spread/New(loc)
. = ..()
- var/turf/turf_loc = get_turf(loc)
- turf_loc.rust_heretic_act()
- turfs += turf_loc
+ centre = get_turf(loc)
+ centre.rust_heretic_act()
+ turfs += centre
START_PROCESSING(SSprocessing,src)
@@ -171,28 +162,39 @@
STOP_PROCESSING(SSprocessing,src)
return ..()
-/datum/rust_spread/process()
- compile_turfs()
+/datum/rust_spread/process(delta_time)
+ var/spread_am = round(spread_per_sec * delta_time)
+
+ if(edge_turfs.len < spread_am)
+ compile_turfs()
+
var/turf/T
- for(var/i in 0 to spread_per_tick)
+ for(var/i in 0 to spread_am)
+ if(!edge_turfs.len)
+ continue
T = pick(edge_turfs)
+ edge_turfs -= T
T.rust_heretic_act()
- turfs += get_turf(T)
+ turfs += T
/**
- * Compile turfs
- *
- * Recreates all edge_turfs as well as normal turfs.
- */
+ * Compile turfs
+ *
+ * Recreates all edge_turfs as well as normal turfs.
+ */
/datum/rust_spread/proc/compile_turfs()
edge_turfs = list()
- for(var/X in turfs)
- if(!istype(X,/turf/closed/wall/rust) && !istype(X,/turf/closed/wall/r_wall/rust) && !istype(X,/turf/open/floor/plating/rust))
- turfs -=X
+ var/list/removal_list = list()
+ var/max_dist = 1
+ for(var/turfie in turfs)
+ if(!istype(turfie,/turf/closed/wall/rust) && !istype(turfie,/turf/closed/wall/r_wall/rust) && !istype(turfie,/turf/open/floor/plating/rust))
+ removal_list += turfie
+ max_dist = max(max_dist,get_dist(turfie,centre)+1)
+ turfs -= removal_list
+ for(var/turfie in spiral_range_turfs(max_dist,centre,FALSE))
+ if(turfie in turfs || is_type_in_typecache(turfie,blacklisted_turfs))
continue
- for(var/turf/T in range(1,X))
- if(T in turfs)
- continue
- if(is_type_in_typecache(T,blacklisted_turfs))
- continue
- edge_turfs += T
+ for(var/line_turfie_owo in getline(turfie,centre))
+ if(get_dist(turfie,line_turfie_owo) <= 1)
+ edge_turfs += turfie
+ CHECK_TICK
diff --git a/code/modules/antagonists/ert/ert.dm b/code/modules/antagonists/ert/ert.dm
index b211016c16e88..a0f0cd9324999 100644
--- a/code/modules/antagonists/ert/ert.dm
+++ b/code/modules/antagonists/ert/ert.dm
@@ -2,25 +2,32 @@
/datum/team/ert
name = "Emergency Response Team"
var/datum/objective/mission //main mission
+ var/ert_frequency
+
+/datum/team/ert/New(starting_members)
+ . = ..()
+ ert_frequency = get_free_team_frequency("cent")
/datum/antagonist/ert
name = "Emergency Response Officer"
var/datum/team/ert/ert_team
var/leader = FALSE
var/datum/outfit/outfit = /datum/outfit/ert/security
+ var/datum/outfit/plasmaman_outfit = /datum/outfit/plasmaman/ert
var/role = "Security Officer"
var/list/name_source
var/random_names = TRUE
+ can_elimination_hijack = ELIMINATION_PREVENT
show_in_antagpanel = FALSE
show_to_ghosts = TRUE
antag_moodlet = /datum/mood_event/focused
- can_hijack = HIJACK_PREVENT
/datum/antagonist/ert/on_gain()
if(random_names)
update_name()
forge_objectives()
equipERT()
+ owner.store_memory("Your team's shared tracking beacon frequency is [ert_team.ert_frequency].")
. = ..()
/datum/antagonist/ert/get_team()
@@ -77,13 +84,22 @@
name = "Deathsquad Trooper"
outfit = /datum/outfit/death_commando
role = "Trooper"
+ plasmaman_outfit = /datum/outfit/plasmaman/death_commando
/datum/antagonist/ert/medic/inquisitor
outfit = /datum/outfit/ert/medic/inquisitor
+/datum/antagonist/ert/medic/inquisitor/on_gain()
+ . = ..()
+ owner.holy_role = HOLY_ROLE_PRIEST
+
/datum/antagonist/ert/security/inquisitor
outfit = /datum/outfit/ert/security/inquisitor
+/datum/antagonist/ert/security/inquisitor/on_gain()
+ . = ..()
+ owner.holy_role = HOLY_ROLE_PRIEST
+
/datum/antagonist/ert/chaplain
role = "Chaplain"
outfit = /datum/outfit/ert/chaplain
@@ -93,14 +109,14 @@
/datum/antagonist/ert/chaplain/on_gain()
. = ..()
- owner.isholy = TRUE
+ owner.holy_role = HOLY_ROLE_PRIEST
/datum/antagonist/ert/commander/inquisitor
outfit = /datum/outfit/ert/commander/inquisitor
/datum/antagonist/ert/commander/inquisitor/on_gain()
. = ..()
- owner.isholy = TRUE
+ owner.holy_role = HOLY_ROLE_PRIEST
/datum/antagonist/ert/janitor
role = "Janitor"
@@ -110,6 +126,10 @@
role = "Heavy Duty Janitor"
outfit = /datum/outfit/ert/janitor/heavy
+/datum/antagonist/ert/kudzu
+ role = "Weed Whacker"
+ outfit = /datum/outfit/ert/kudzu
+
/datum/antagonist/ert/deathsquad/leader
name = "Deathsquad Officer"
outfit = /datum/outfit/death_commando/officer
@@ -120,6 +140,7 @@
outfit = /datum/outfit/centcom_intern
random_names = FALSE
role = "Intern"
+ plasmaman_outfit = /datum/outfit/plasmaman/intern
/datum/antagonist/ert/intern/leader
name = "CentCom Head Intern"
@@ -136,11 +157,13 @@
name = "Comedy Response Officer"
outfit = /datum/outfit/centcom_clown
role = "Prankster"
+ plasmaman_outfit = /datum/outfit/plasmaman/honk
/datum/antagonist/ert/clown/honk
name = "HONK Squad Trooper"
outfit = /datum/outfit/centcom_clown/honk_squad
role = "HONKER"
+ plasmaman_outfit = /datum/outfit/plasmaman/honk_squad
/datum/antagonist/ert/create_team(datum/team/ert/new_team)
if(istype(new_team))
@@ -154,7 +177,18 @@
var/mob/living/carbon/human/H = owner.current
if(!istype(H))
return
+ if(isplasmaman(H))
+ H.equipOutfit(plasmaman_outfit)
+ H.internal = H.get_item_for_held_index(2)
+ H.update_internals_hud_icon(1)
H.equipOutfit(outfit)
+ //Set the suits frequency
+ var/obj/item/I = H.get_item_by_slot(ITEM_SLOT_OCLOTHING)
+ if(I)
+ var/datum/component/tracking_beacon/beacon = I.GetComponent(/datum/component/tracking_beacon)
+ if(beacon)
+ beacon.set_frequency(ert_team.ert_frequency)
+
/datum/antagonist/ert/greet()
if(!ert_team)
@@ -171,6 +205,8 @@
missiondesc += "Avoid civilian casualites when possible."
missiondesc += " Your Mission : [ert_team.mission.explanation_text]"
+ missiondesc += " Your Shared Tracking Frequency : [ert_team.ert_frequency]"
+
to_chat(owner,missiondesc)
/datum/antagonist/ert/deathsquad/greet()
@@ -218,4 +254,4 @@
missiondesc += " Slip as many civilians as possible."
missiondesc += " Your Mission : [ert_team.mission.explanation_text]"
- to_chat(owner,missiondesc)
\ No newline at end of file
+ to_chat(owner,missiondesc)
diff --git a/code/modules/antagonists/fugitive/fugitive.dm b/code/modules/antagonists/fugitive/fugitive.dm
index 4e311b4c0634d..1208ad6817d55 100644
--- a/code/modules/antagonists/fugitive/fugitive.dm
+++ b/code/modules/antagonists/fugitive/fugitive.dm
@@ -35,7 +35,7 @@
switch(backstory)
if("prisoner")
to_chat(owner, "I can't believe we managed to break out of a Nanotrasen superjail! Sadly though, our work is not done. The emergency teleport at the station logs everyone who uses it, and where they went.")
- to_chat(owner, "It won't be long until Centcom tracks where we've gone off to. I need to work with my fellow escapees to prepare for the troops Nanotrasen is sending, I'm not going back.")
+ to_chat(owner, "It won't be long until CentCom tracks where we've gone off to. I need to work with my fellow escapees to prepare for the troops Nanotrasen is sending, I'm not going back.")
if("cultist")
to_chat(owner, "Blessed be our journey so far, but I fear the worst has come to our doorstep, and only those with the strongest faith will survive.")
to_chat(owner, "Our religion has been repeatedly culled by Nanotrasen because it is categorized as an \"Enemy of the Corporation\", whatever that means.")
diff --git a/code/modules/antagonists/fugitive/fugitive_outfits.dm b/code/modules/antagonists/fugitive/fugitive_outfits.dm
index 9ad730f7c6a56..bbeb0b90254f9 100644
--- a/code/modules/antagonists/fugitive/fugitive_outfits.dm
+++ b/code/modules/antagonists/fugitive/fugitive_outfits.dm
@@ -41,11 +41,11 @@
if(H.mind)
H.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock(null))
var/list/no_drops = list()
- no_drops += H.get_item_by_slot(SLOT_SHOES)
- no_drops += H.get_item_by_slot(SLOT_W_UNIFORM)
- no_drops += H.get_item_by_slot(SLOT_WEAR_SUIT)
- no_drops += H.get_item_by_slot(SLOT_HEAD)
- no_drops += H.get_item_by_slot(SLOT_GLASSES)
+ no_drops += H.get_item_by_slot(ITEM_SLOT_FEET)
+ no_drops += H.get_item_by_slot(ITEM_SLOT_ICLOTHING)
+ no_drops += H.get_item_by_slot(ITEM_SLOT_OCLOTHING)
+ no_drops += H.get_item_by_slot(ITEM_SLOT_HEAD)
+ no_drops += H.get_item_by_slot(ITEM_SLOT_EYES)
for(var/i in no_drops)
var/obj/item/I = i
ADD_TRAIT(I, TRAIT_NODROP, CURSED_ITEM_TRAIT)
@@ -59,7 +59,7 @@
if(visualsOnly)
return
var/obj/item/organ/eyes/robotic/glow/eyes = new()
- eyes.Insert(src, drop_if_replaced = FALSE)
+ eyes.Insert(H, drop_if_replaced = FALSE)
/datum/outfit/spacepol
name = "Spacepol Officer"
@@ -132,14 +132,6 @@
/obj/item/ammo_casing/shotgun/incapacitate = 6
)
-/datum/outfit/bountygrapple/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- if(visualsOnly)
- return
- var/obj/item/card/id/W = H.wear_id
- W.assignment = "Bounty Hunter"
- W.registered_name = H.real_name
- W.update_label()
-
/datum/outfit/bountysynth
name = "Bounty Hunter - Synth"
uniform = /obj/item/clothing/under/rank/prisoner
@@ -156,15 +148,3 @@
backpack_contents = list(
/obj/item/bountytrap = 4
)
-
-/datum/outfit/bountysynth/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
- if(visualsOnly)
- return
- var/datum/species/synth/synthetic_appearance = new()
- H.set_species(synthetic_appearance)
- synthetic_appearance.assume_disguise(synthetic_appearance, H)
- H.update_hair()
- var/obj/item/card/id/W = H.wear_id
- W.assignment = "Bounty Hunter"
- W.registered_name = H.real_name
- W.update_label()
diff --git a/code/modules/antagonists/fugitive/fugitive_ship.dm b/code/modules/antagonists/fugitive/fugitive_ship.dm
index dfdf40f261247..56b0b67a8ed89 100644
--- a/code/modules/antagonists/fugitive/fugitive_ship.dm
+++ b/code/modules/antagonists/fugitive/fugitive_ship.dm
@@ -32,21 +32,11 @@
to_chat(fugitive, "You are thrown into a vast void of bluespace, and as you fall further into oblivion the comparatively small entrance to reality gets smaller and smaller until you cannot see it anymore. You have failed to avoid capture.")
fugitive.ghostize(TRUE) //so they cannot suicide, round end stuff.
-/obj/machinery/computer/shuttle/hunter
+/obj/machinery/computer/shuttle_flight/hunter
name = "shuttle console"
shuttleId = "huntership"
possible_destinations = "huntership_home;huntership_custom;whiteship_home;syndicate_nw"
-/obj/machinery/computer/camera_advanced/shuttle_docker/syndicate/hunter
- name = "shuttle navigation computer"
- desc = "Used to designate a precise transit location to travel to."
- shuttleId = "huntership"
- lock_override = CAMERA_LOCK_STATION
- shuttlePortId = "huntership_custom"
- see_hidden = FALSE
- jumpto_ports = list("huntership_home" = 1, "whiteship_home" = 1, "syndicate_nw" = 1)
- view_range = 4.5
-
/obj/structure/closet/crate/eva
name = "EVA crate"
diff --git a/code/modules/antagonists/gang/gang.dm b/code/modules/antagonists/gang/gang.dm
index 9691c2baee22f..10ea21f54944f 100644
--- a/code/modules/antagonists/gang/gang.dm
+++ b/code/modules/antagonists/gang/gang.dm
@@ -27,6 +27,8 @@
/datum/antagonist/gang/greet()
gang.greet_gangster(owner)
+ owner.current.client?.tgui_panel?.give_antagonist_popup("Gangster",
+ "You rule the streets, assist your gang in taking over the station.")
/datum/antagonist/gang/farewell()
if(ishuman(owner.current))
@@ -103,8 +105,6 @@
. = ..()
.["Promote"] = CALLBACK(src,.proc/admin_promote)
.["Set Influence"] = CALLBACK(src, .proc/admin_adjust_influence)
- if(gang.domination_time != NOT_DOMINATING)
- .["Set domination time left"] = CALLBACK(src, .proc/set_dom_time_left)
/datum/antagonist/gang/admin_add(datum/mind/new_owner,mob/admin)
var/new_or_existing = input(admin, "Which gang do you want to be assigned to the user?", "Gangs") as null|anything in list("New","Existing")
@@ -154,14 +154,6 @@
gang.remove_member(owner)
owner.current.log_message("Has been deconverted from the [gang.name] gang!", INDIVIDUAL_ATTACK_LOG)
-/datum/antagonist/gang/proc/set_dom_time_left(mob/admin)
- if(gang.domination_time == NOT_DOMINATING)
- return // an admin shouldn't need this
- var/seconds = input(admin, "Set the time left for the gang to win, in seconds", "Domination time left") as null|num
- if(seconds && seconds > 0)
- gang.domination_time = world.time + seconds*10
- gang.message_gangtools("Takeover shortened to [gang.domination_time_remaining()] seconds by your Syndicate benefactors.")
-
// Boss type. Those can use gang tools to buy items for their gang, in particular the Dominator, used to win the gamemode, along with more gang tools to promote fellow gangsters to boss status.
/datum/antagonist/gang/boss
name = "Gang boss"
@@ -187,10 +179,10 @@
return
var/list/slots = list (
- "backpack" = SLOT_IN_BACKPACK,
- "left pocket" = SLOT_L_STORE,
- "right pocket" = SLOT_R_STORE,
- "hands" = SLOT_HANDS
+ "backpack" = ITEM_SLOT_BACKPACK,
+ "left pocket" = ITEM_SLOT_LPOCKET,
+ "right pocket" = ITEM_SLOT_RPOCKET,
+ "hands" = ITEM_SLOT_HANDS
)
if(gangtool)
@@ -279,8 +271,11 @@
name = "Gang Lieutenant"
message_name = "Lieutenant"
+#define INFLUENCE_SWAG 2
+#define INFLUENCE_TERRITORY 0
+#define INFLUENCE_BASE 20
-#define MAXIMUM_RECALLS 3
+#define MAXIMUM_RECALLS 1
#define INFLUENCE_INTERVAL 1800
// Gang team datum. This handles the gang itself.
/datum/team/gang
@@ -293,15 +288,15 @@
var/list/lost_territories = list() // territories lost by the gang.
var/list/new_territories = list() // territories captured by the gang.
var/list/gangtools = list()
- var/domination_time = NOT_DOMINATING
- var/dom_attempts = INITIAL_DOM_ATTEMPTS
var/color
+ var/winner = FALSE //winner winner chicken dinner
var/influence = 0 // influence of the gang, based on how many territories they own. Can be used to buy weapons and tools from a gang uplink.
- var/winner // Once the gang wins with a dominator, this becomes true. For roundend credits purposes.
- var/list/inner_outfits = list()
- var/list/outer_outfits = list()
+ var/victory_points = 0 // influence earned throughout the round, used at eotg to calculate most efficient gang
var/next_point_time
var/recalls = MAXIMUM_RECALLS // Once this reaches 0, this gang cannot force recall the shuttle with their gangtool anymore
+ var/obj/item/clothing/head/hat
+ var/obj/item/clothing/under/outfit
+ var/obj/item/clothing/suit/suit
/datum/team/gang/New(starting_members)
. = ..()
@@ -343,14 +338,14 @@
return "
[report.Join(" ")]
"
-/datum/team/gang/proc/greet_gangster(datum/mind/gangster)
+/datum/team/gang/proc/greet_gangster(datum/mind/gangster)//THIS NEEDS REVISION
to_chat(gangster, "You are now a member of the [name] Gang!")
to_chat(gangster, "Help your bosses take over the station by claiming territory with special spraycans only they can provide. Simply spray on any unclaimed area of the station.")
- to_chat(gangster, "Their ultimate objective is to take over the station with a Dominator machine.")
+ to_chat(gangster, "Your objective is to take over as many territories as possible, and expand the influence of your gang.")
to_chat(gangster, "You can identify your mates by their large, bright \[G\] icon.")
gangster.store_memory("You are a member of the [name] Gang!")
-/datum/team/gang/proc/handle_territories()
+/datum/team/gang/proc/handle_territories() //influence is counted here
next_point_time = world.time + INFLUENCE_INTERVAL
if(!leaders.len)
return
@@ -388,20 +383,16 @@
lost_territories = list()
var/total_territories = total_claimable_territories()
var/control = round((territories.len/total_territories)*100, 1)
- var/uniformed = check_clothing()
+ var/uniformed = count_gang_swag()
message += "Your gang now has [control]% control of the station. *---------* "
- if(domination_time != NOT_DOMINATING)
- var/new_time = max(world.time, domination_time - (uniformed * 4) - (territories.len * 2))
- if(new_time < domination_time)
- message += "Takeover shortened by [(domination_time - new_time)*0.1] seconds for defending [territories.len] territories. "
- domination_time = new_time
- message += "[domination_time_remaining()] seconds remain in hostile takeover. "
- else
- var/new_influence = check_territory_income()
- if(new_influence != influence)
- message += "Gang influence has increased by [new_influence - influence] for defending [territories.len] territories and [uniformed] uniformed gangsters. "
- influence = new_influence
- message += "Your gang now has [influence] influence. "
+
+ var/new_influence = update_influence()
+ influence = min(999,influence+new_influence)
+ victory_points += new_influence
+ if(new_influence > 0)
+ message += "Gang influence has increased by [new_influence] for defending [territories.len] territories and [uniformed] swag. "
+ message += "Your gang now has [influence] influence. "
+
message_gangtools(message)
addtimer(CALLBACK(src, .proc/handle_territories), INFLUENCE_INTERVAL)
@@ -411,39 +402,33 @@
for(var/z in SSmapping.levels_by_trait(ZTRAIT_STATION)) //First, collect all area types on the station zlevel
for(var/ar in SSmapping.areas_in_z["[z]"])
var/area/A = ar
- if(!(A.type in valid_territories) && A.valid_territory)
+ if(!(A.type in valid_territories) && A.area_flags & VALID_TERRITORY)
valid_territories |= A.type
return valid_territories.len
-/datum/team/gang/proc/check_territory_income()
- var/new_influence = min(999,influence + 15 + (check_clothing() * 2) + territories.len)
- return new_influence
+/datum/team/gang/proc/update_influence()
+ return min(999,influence + INFLUENCE_BASE + (count_gang_swag()) + LAZYLEN(territories) * INFLUENCE_TERRITORY)
-/datum/team/gang/proc/check_clothing()
- //Count uniformed gangsters
- var/uniformed = 0
+/datum/team/gang/proc/count_gang_swag()
+ //Count swag on gangsters
+ var/swag = 0
for(var/datum/mind/gangmind in members)
if(ishuman(gangmind.current))
- var/mob/living/carbon/human/gangster = gangmind.current
- //Gangster must be alive and on station
- if((gangster.stat == DEAD) || (is_station_level(gangster.z)))
- continue
-
- var/obj/item/clothing/outfit
- var/obj/item/clothing/gang_outfit
- if(gangster.w_uniform)
- outfit = gangster.w_uniform
- if(outfit.type in inner_outfits)
- gang_outfit = outfit
- if(gangster.wear_suit)
- outfit = gangster.wear_suit
- if(outfit.type in outer_outfits)
- gang_outfit = outfit
-
- if(gang_outfit)
- gangster << "The [src] Gang's influence grows as you wear [gang_outfit]."
- uniformed++
- return uniformed
+ swag+=check_gangster_swag(gangmind.current)
+ return swag
+
+/datum/team/gang/proc/check_gangster_swag(var/mob/living/carbon/human/gangster)
+ //Gangster must be alive and on station
+ var/swag = 1
+ if((gangster.stat == DEAD) || !(is_station_level(gangster.z)))
+ return FALSE
+ if (gangster.w_uniform?.type == outfit)
+ swag *= INFLUENCE_SWAG
+ if (gangster.wear_suit?.type == suit)
+ swag *= INFLUENCE_SWAG
+ if (gangster.head?.type == hat)
+ swag *= INFLUENCE_SWAG
+ return swag
/datum/team/gang/proc/adjust_influence(value)
influence = max(0, influence + value)
@@ -461,17 +446,8 @@
playsound(mob.loc, 'sound/machines/twobeep.ogg', 50, 1)
return
-/datum/team/gang/proc/domination()
- domination_time = world.time + determine_domination_time()*10
- set_security_level("delta")
-
-/datum/team/gang/proc/determine_domination_time() // calculates the value in seconds (this is the initial domination time!)
- var/total_territories = total_claimable_territories()
- return max(180,480 - (round((territories.len/total_territories)*100, 1) * 9))
-
-/datum/team/gang/proc/domination_time_remaining() // retrieves the value from world.time based deciseconds to seconds
- var/diff = domination_time - world.time
- return round(diff * 0.1)
-
#undef MAXIMUM_RECALLS
-#undef INFLUENCE_INTERVAL
+#undef INFLUENCE_INTERVAL
+#undef INFLUENCE_SWAG
+#undef INFLUENCE_TERRITORY
+#undef INFLUENCE_BASE
\ No newline at end of file
diff --git a/code/modules/antagonists/gang/gang_datums.dm b/code/modules/antagonists/gang/gang_datums.dm
index bd1ce8f94d745..b02ac8a30fd28 100644
--- a/code/modules/antagonists/gang/gang_datums.dm
+++ b/code/modules/antagonists/gang/gang_datums.dm
@@ -1,126 +1,146 @@
// Gang datums go here. If you want to create a new gang, you must be sure to edit:
// name
// color (must be a hex, "blue" isn't acceptable due to how spraycans are handled)
-// inner_outfits (must be a list() with typepaths of the clothes in it. One is fine, but there is support for multiple: one will be picked at random when bought)
-// outer_outfits (same as above)
+// hat/outfit/suit is the type of items that will count towards the points/scoring. Lists are nice and all, but they weren't really used at all... ever. And were just expensive
// You also need to make a gang graffiti, that will go in crayondecal.dmi inside our icons(not tg's), with the same name of the gang it's assigned to. Nothing else,just the icon.
// Those are all required. If one is missed, stuff could break.
/datum/team/gang/clandestine
name = "Clandestine"
color = "#FF0000"
- inner_outfits = list(/obj/item/clothing/under/syndicate/combat)
- outer_outfits = list(/obj/item/clothing/suit/jacket)
+ hat = /obj/item/clothing/head/beanie/black
+ outfit = /obj/item/clothing/under/syndicate/combat
+ suit = /obj/item/clothing/suit/jacket
/datum/team/gang/prima
name = "Prima"
- color = "#FFFF00"
- inner_outfits = list(/obj/item/clothing/under/color/yellow)
- outer_outfits = list(/obj/item/clothing/suit/hastur)
+ color = "#FFFF00" //cluck cluck clan
+ hat = /obj/item/clothing/head/chicken
+ outfit = /obj/item/clothing/under/color/yellow
+ suit = /obj/item/clothing/suit/chickensuit
/datum/team/gang/zerog
name = "Zero-G"
- color = "#C0C0C0"
- inner_outfits = list(/obj/item/clothing/under/suit/white)
- outer_outfits = list(/obj/item/clothing/suit/hooded/wintercoat)
+ color = "#00FFFF"
+ outfit = /obj/item/clothing/under/color/blue
+ suit = /obj/item/clothing/suit/apron
+ hat = /obj/item/clothing/head/beanie/stripedblue
/datum/team/gang/max
name = "Max"
color = "#800000"
- inner_outfits = list(/obj/item/clothing/under/color/maroon)
- outer_outfits = list(/obj/item/clothing/suit/poncho/red)
+ hat = /obj/item/clothing/head/that
+ outfit = /obj/item/clothing/under/costume/joker
+ suit = /obj/item/clothing/suit/joker
/datum/team/gang/blasto
name = "Blasto"
color = "#000080"
- inner_outfits = list(/obj/item/clothing/under/suit/navy)
- outer_outfits = list(/obj/item/clothing/suit/jacket/miljacket)
+ hat = /obj/item/clothing/head/beret/navy
+ outfit = /obj/item/clothing/under/suit/navy
+ suit = /obj/item/clothing/suit/jacket/miljacket
/datum/team/gang/waffle
name = "Waffle"
color = "#808000"
- inner_outfits = list(/obj/item/clothing/under/suit/green)
- outer_outfits = list(/obj/item/clothing/suit/poncho)
+ hat = /obj/item/clothing/head/sombrero/green
+ outfit = /obj/item/clothing/under/suit/green
+ suit = /obj/item/clothing/suit/poncho/green
/datum/team/gang/north
name = "North"
- color = "#00FF00"
- inner_outfits = list(/obj/item/clothing/under/color/green)
- outer_outfits = list(/obj/item/clothing/suit/poncho/green)
+ color = "#C0C0C0"
+ hat = /obj/item/clothing/head/snowman
+ outfit = /obj/item/clothing/under/suit/white
+ suit = /obj/item/clothing/suit/snowman
/datum/team/gang/omni
name = "Omni"
color = "#008080"
- inner_outfits = list(/obj/item/clothing/under/color/teal)
- outer_outfits = list(/obj/item/clothing/suit/chaplainsuit/studentuni)
+ hat = /obj/item/clothing/head/soft/blue
+ outfit = /obj/item/clothing/under/color/teal
+ suit = /obj/item/clothing/suit/apron/overalls
/datum/team/gang/newton
name = "Newton"
color = "#A52A2A"
- inner_outfits = list(/obj/item/clothing/under/color/brown)
- outer_outfits = list(/obj/item/clothing/suit/toggle/owlwings)
+ hat = /obj/item/clothing/head/griffin
+ outfit = /obj/item/clothing/under/color/brown
+ suit = /obj/item/clothing/suit/toggle/owlwings/griffinwings
/datum/team/gang/cyber
name = "Cyber"
- color = "#808000"
- inner_outfits = list(/obj/item/clothing/under/color/lightbrown)
- outer_outfits = list(/obj/item/clothing/suit/nemes)
+ color = "#0000FF"
+ hat = /obj/item/clothing/head/helmet/rus_ushanka
+ outfit = /obj/item/clothing/under/color/darkblue
+ suit = /obj/item/clothing/suit/security/officer/russian
/datum/team/gang/donk
name = "Donk"
- color = "#0000FF"
- inner_outfits = list(/obj/item/clothing/under/color/darkblue)
- outer_outfits = list(/obj/item/clothing/suit/apron/overalls)
+ color = "#808000"
+ hat = /obj/item/clothing/head/beret/black
+ outfit = /obj/item/clothing/under/color/green
+ suit = /obj/item/clothing/suit/jacket/puffer/vest
/datum/team/gang/gene
name = "Gene"
- color = "#00FFFF"
- inner_outfits = list(/obj/item/clothing/under/color/blue)
- outer_outfits = list(/obj/item/clothing/suit/apron)
+ color = "#00FF00"
+ hat = /obj/item/clothing/head/soft/green
+ outfit = /obj/item/clothing/under/color/green
+ suit = /obj/item/clothing/suit/toggle/labcoat/mad
/datum/team/gang/gib
name = "Gib"
color = "#000000"
- inner_outfits = list(/obj/item/clothing/under/color/black)
- outer_outfits = list(/obj/item/clothing/suit/jacket/leather/overcoat)
+ outfit = /obj/item/clothing/under/color/black
+ suit = /obj/item/clothing/suit/toggle/lawyer/black
+ hat = /obj/item/clothing/head/bowler
/datum/team/gang/diablo
name = "Diablo"
color = "#FF0000"
- inner_outfits = list(/obj/item/clothing/under/color/red)
- outer_outfits = list(/obj/item/clothing/suit/jacket/leather)
+ outfit = /obj/item/clothing/under/color/red
+ suit = /obj/item/clothing/suit/jacket/leather
+ hat = /obj/item/clothing/head/beret
/datum/team/gang/psyke
name = "Psyke"
color = "#808080"
- inner_outfits = list(/obj/item/clothing/under/color/grey)
- outer_outfits = list(/obj/item/clothing/suit/toggle/owlwings/griffinwings)
+ hat = /obj/item/clothing/head/soft/rainbow
+ suit = /obj/item/clothing/suit/poncho/ponchoshame
+ outfit = /obj/item/clothing/under/color/rainbow
/datum/team/gang/osiron
name = "Osiron"
color = "#FFFFFF"
- inner_outfits = list(/obj/item/clothing/under/color/white)
- outer_outfits = list(/obj/item/clothing/suit/toggle/labcoat)
+ outfit = /obj/item/clothing/under/costume/roman
+ hat = /obj/item/clothing/head/helmet/roman/legionnaire/fake
+ suit = /obj/item/clothing/suit/toggle/owlwings
/datum/team/gang/sirius
name = "Sirius"
color = "#FFC0CB"
- inner_outfits = list(/obj/item/clothing/under/color/pink)
+ hat = /obj/item/clothing/head/fedora
+ outfit = /obj/item/clothing/under/color/white
+ suit = /obj/item/clothing/suit/nerdshirt
/datum/team/gang/sleepingcarp
name = "Sleeping Carp"
color = "#800080"
- inner_outfits = list(/obj/item/clothing/under/color/lightpurple)
- outer_outfits = list(/obj/item/clothing/suit/hooded/carp_costume)
+ outfit = /obj/item/clothing/under/color/lightpurple
+ suit = /obj/item/clothing/suit/toggle/lawyer/purple
+ hat = /obj/item/clothing/head/hooded/carp_hood
/datum/team/gang/rigatonifamily
name = "Rigatoni family"
color = "#cc9900" // p a s t a colored
- inner_outfits = list(/obj/item/clothing/under/rank/civilian/chef)
- outer_outfits = list(/obj/item/clothing/suit/apron/chef)
+ outfit = /obj/item/clothing/under/rank/civilian/chef
+ suit = /obj/item/clothing/suit/apron/chef
+ hat = /obj/item/clothing/head/chefhat
/datum/team/gang/weed
name = "Weed"
color = "#66ff33"
- inner_outfits = list(/obj/item/clothing/under/color/darkgreen)
- outer_outfits = list(/obj/item/clothing/suit/vapeshirt)
+ hat = /obj/item/clothing/head/beanie/rasta
+ outfit = /obj/item/clothing/under/color/green
+ suit = /obj/item/clothing/suit/vapeshirt
diff --git a/code/modules/antagonists/highlander/highlander.dm b/code/modules/antagonists/highlander/highlander.dm
index bde21c26ae23d..5d063ffaccdc6 100644
--- a/code/modules/antagonists/highlander/highlander.dm
+++ b/code/modules/antagonists/highlander/highlander.dm
@@ -3,7 +3,7 @@
var/obj/item/claymore/highlander/sword
show_in_antagpanel = FALSE
show_name_in_check_antagonists = TRUE
- can_hijack = HIJACK_HIJACKER
+ can_elimination_hijack = ELIMINATION_ENABLED
/datum/antagonist/highlander/apply_innate_effects(mob/living/mob_override)
var/mob/living/L = owner.current || mob_override
@@ -18,13 +18,9 @@
steal_objective.owner = owner
steal_objective.set_target(new /datum/objective_item/steal/nukedisc)
objectives += steal_objective
- log_objective(owner, steal_objective.explanation_text)
-
- var/datum/objective/hijack/hijack_objective = new
- hijack_objective.explanation_text = "Escape on the shuttle alone. Ensure that nobody else makes it out."
- hijack_objective.owner = owner
- objectives += hijack_objective
- log_objective(owner, hijack_objective.explanation_text)
+ var/datum/objective/elimination/highlander/elimination_objective = new
+ elimination_objective.owner = owner
+ objectives += elimination_objective
/datum/antagonist/highlander/on_gain()
forge_objectives()
@@ -38,6 +34,8 @@
Activate it in your hand, and it will lead to the nearest target. Attack the nuclear authentication disk with it, and you will store it.")
owner.announce_objectives()
+ owner.current.client?.tgui_panel?.give_antagonist_popup("Highlander",
+ "Locate victims to fall to your sword and claim the nuclear authentication disk for yourself.")
/datum/antagonist/highlander/proc/give_equipment()
var/mob/living/carbon/human/H = owner.current
@@ -48,11 +46,11 @@
qdel(I)
for(var/obj/item/I in H.held_items)
qdel(I)
- H.equip_to_slot_or_del(new /obj/item/clothing/under/costume/kilt/highlander(H), SLOT_W_UNIFORM)
- H.equip_to_slot_or_del(new /obj/item/radio/headset/heads/captain(H), SLOT_EARS)
- H.equip_to_slot_or_del(new /obj/item/clothing/head/beret/highlander(H), SLOT_HEAD)
- H.equip_to_slot_or_del(new /obj/item/clothing/shoes/combat(H), SLOT_SHOES)
- H.equip_to_slot_or_del(new /obj/item/pinpointer/nuke(H), SLOT_L_STORE)
+ H.equip_to_slot_or_del(new /obj/item/clothing/under/costume/kilt/highlander(H), ITEM_SLOT_ICLOTHING)
+ H.equip_to_slot_or_del(new /obj/item/radio/headset/heads/captain(H), ITEM_SLOT_EARS)
+ H.equip_to_slot_or_del(new /obj/item/clothing/head/beret/highlander(H), ITEM_SLOT_HEAD)
+ H.equip_to_slot_or_del(new /obj/item/clothing/shoes/combat(H), ITEM_SLOT_FEET)
+ H.equip_to_slot_or_del(new /obj/item/pinpointer/nuke(H), ITEM_SLOT_LPOCKET)
for(var/obj/item/pinpointer/nuke/P in H)
P.attack_self(H)
var/obj/item/card/id/W = new(H)
@@ -63,7 +61,7 @@
W.registered_name = H.real_name
ADD_TRAIT(W, TRAIT_NODROP, HIGHLANDER)
W.update_label(H.real_name)
- H.equip_to_slot_or_del(W, SLOT_WEAR_ID)
+ H.equip_to_slot_or_del(W, ITEM_SLOT_ID)
sword = new(H)
if(!GLOB.highlander)
diff --git a/code/modules/antagonists/hivemind/hivemind.dm b/code/modules/antagonists/hivemind/hivemind.dm
index dd2fbeb2e6820..d54936102b579 100644
--- a/code/modules/antagonists/hivemind/hivemind.dm
+++ b/code/modules/antagonists/hivemind/hivemind.dm
@@ -277,6 +277,12 @@
owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/assimilation.ogg', 100, FALSE, pressure_affected = FALSE)
owner.announce_objectives()
+ owner.current.client?.tgui_panel?.give_antagonist_popup("Hivemind",
+ "Your psionic powers will grow by assimilating the crew into your hive. Use the Assimilate Vessel spell on a stationary \
+ target, and after ten seconds he will be one of the hive. This is completely silent and safe to use, and failing will reset the cooldown. As \
+ you assimilate the crew, you will gain more powers to use. Most are silent and won't help you in a fight, but grant you great power over your \
+ vessels. Hover your mouse over a power's action icon for an extended description on what it does. There are other hiveminds onboard the station, \
+ collaboration is possible, but a strong enough hivemind can reap many rewards from a well planned betrayal.")
/datum/antagonist/hivemind/roundend_report()
var/list/result = list()
diff --git a/code/modules/antagonists/incursion/incursion.dm b/code/modules/antagonists/incursion/incursion.dm
index 1db2161ab5632..8e98e740fe21f 100644
--- a/code/modules/antagonists/incursion/incursion.dm
+++ b/code/modules/antagonists/incursion/incursion.dm
@@ -5,7 +5,7 @@
var/special_role = ROLE_INCURSION
var/datum/team/incursion/team
antag_moodlet = /datum/mood_event/focused
- can_hijack = HIJACK_HIJACKER
+ hijack_speed = 0.5
/datum/antagonist/incursion/create_team(datum/team/incursion/new_team)
if(!new_team)
@@ -53,6 +53,8 @@
to_chat(owner.current, "You have formed a team of Syndicate members with a similar mindset and must infiltrate the ranks of the station!")
to_chat(owner.current, "You have been implanted with a syndicate headset for communication with your team. This headset can only be heard by you directly and if those pigs at Nanotrasen try to steal it they will violently explode!")
owner.announce_objectives()
+ owner.current.client?.tgui_panel?.give_antagonist_popup("Incursion",
+ "Work with your team members to complete your objectives.")
/datum/antagonist/incursion/apply_innate_effects(mob/living/mob_override)
if(issilicon(owner))
@@ -89,9 +91,18 @@
log_admin("[key_name(admin)] made [key_name(new_owner)] and [key_name(new_owner.current)] into incursion traitor team.")
/datum/antagonist/incursion/proc/equip(var/silent = FALSE)
- var/obj/item/uplink/incursion/uplink = new(owner, owner.key, 15)
- owner.current.equip_to_slot(uplink, SLOT_IN_BACKPACK)
- to_chat(owner.current, "You have been equipped with a syndicate uplink located in your backpack. Activate the transponder in hand to access the market.")
+ var/obj/item/uplink/incursion/uplink = new(get_turf(owner.current), owner.key, 15)
+ var/where
+ if(ishuman(owner.current)) //if he's not a human, uplink will spawn under his feet
+ var/mob/living/carbon/human/H = owner.current
+ var/static/list/slots = list(
+ "in your backpack" = ITEM_SLOT_BACKPACK,
+ "in your left pocket" = ITEM_SLOT_LPOCKET,
+ "in your right pocket" = ITEM_SLOT_RPOCKET,
+ "in your hands" = ITEM_SLOT_HANDS
+ )
+ where = H.equip_in_one_of_slots(uplink, slots, FALSE)
+ to_chat(owner.current, "You have been equipped with a syndicate uplink located [where ? where : "at your feet"]. Activate the transponder in hand to access the market.")
var/obj/item/implant/radio/syndicate/selfdestruct/syndio = new
syndio.implant(owner.current)
@@ -151,8 +162,8 @@
for(var/i = 1 to max(1, CONFIG_GET(number/incursion_objective_amount)))
forge_single_objective(CLAMP((5 + !is_hijacker)-i, 1, 3)) //Hijack = 3, 2, 1, 1 no hijack = 3, 3, 2, 1
if(is_hijacker)
- if(!(locate(/datum/objective/hijack/single) in objectives))
- add_objective(new/datum/objective/hijack/single)
+ if(!(locate(/datum/objective/hijack) in objectives))
+ add_objective(new/datum/objective/hijack)
else if(!(locate(/datum/objective/escape/single) in objectives))
add_objective(new/datum/objective/escape/single, FALSE)
@@ -167,6 +178,9 @@
//Kill head
var/datum/objective/assassinate/killchosen = new
var/current_heads = SSjob.get_all_heads()
+ if(!current_heads)
+ generate_traitor_kill_objective()
+ return
var/datum/mind/selected = pick(current_heads)
if(selected.special_role)
generate_traitor_kill_objective()
@@ -200,6 +214,7 @@
target.make_Traitor()
to_chat(target, "You have been declared an ex-communicate of the syndicate and are being hunted down.")
to_chat(target, "You have stolen syndicate objective documents, complete the objectives to throw off the syndicate and sabotage their efforts.")
+ target.store_memory("You have been declared an ex-communicate of the syndicate and are being hunted down by a group of traitors. Be careful!")
//Create objective
var/datum/objective/assassinate/incursion/killchosen = new
killchosen.target = target
@@ -208,4 +223,3 @@
/datum/team/incursion/antag_listing_name()
return "[name]"
-
diff --git a/code/modules/antagonists/monkey/monkey.dm b/code/modules/antagonists/monkey/monkey.dm
index e3b3688b031fb..087919471c082 100644
--- a/code/modules/antagonists/monkey/monkey.dm
+++ b/code/modules/antagonists/monkey/monkey.dm
@@ -36,6 +36,8 @@
to_chat(owner, "As an intelligent monkey, you know how to use technology and how to ventcrawl while wearing things.")
to_chat(owner, "You can use :k to talk to fellow monkeys!")
SEND_SOUND(owner.current, sound('sound/ambience/antag/monkey.ogg'))
+ owner.current.client?.tgui_panel?.give_antagonist_popup("Monkey",
+ "Bite other humans to infect them and escape on the emergency shuttle to succeed.")
/datum/antagonist/monkey/on_removal()
owner.special_role = null
@@ -69,17 +71,8 @@
/datum/antagonist/monkey/admin_remove(mob/admin)
var/mob/living/carbon/monkey/M = owner.current
- if(istype(M))
- switch(alert(admin, "Humanize?", "Humanize", "Yes", "No"))
- if("Yes")
- if(admin == M)
- admin = M.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_DEFAULTMSG)
- else
- M.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_DEFAULTMSG)
- if("No")
- //nothing
- else
- return
+ if(alert(admin, "Humanize?", "Humanize", "Yes", "No") == "Yes")
+ M.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_DEFAULTMSG)
. = ..()
/datum/antagonist/monkey/leader
@@ -88,17 +81,8 @@
/datum/antagonist/monkey/leader/admin_add(datum/mind/new_owner,mob/admin)
var/mob/living/carbon/human/H = new_owner.current
- if(istype(H))
- switch(alert(admin, "Monkeyize?", "Monkeyize", "Yes", "No"))
- if("Yes")
- if(admin == H)
- admin = H.monkeyize()
- else
- H.monkeyize()
- if("No")
- //nothing
- else
- return
+ if(alert(admin, "Monkeyize?", "Monkeyize", "Yes", "No") == "Yes")
+ H.monkeyize()
new_owner.add_antag_datum(src)
log_admin("[key_name(admin)] made [key_name(new_owner)] a monkey leader!")
message_admins("[key_name_admin(admin)] made [key_name_admin(new_owner)] a monkey leader!")
@@ -126,6 +110,8 @@
to_chat(owner, "As an initial infectee, you will be considered a 'leader' by your fellow monkeys.")
to_chat(owner, "You can use :k to talk to fellow monkeys!")
SEND_SOUND(owner.current, sound('sound/ambience/antag/monkey.ogg'))
+ owner.current.client?.tgui_panel?.give_antagonist_popup("Monkey Leader",
+ "Bite other humans to infect them and escape on the emergency shuttle to succeed.")
/datum/objective/monkey
explanation_text = "Ensure that infected monkeys escape on the emergency shuttle!"
diff --git a/code/modules/antagonists/morph/morph.dm b/code/modules/antagonists/morph/morph.dm
index a166ad52f7b1f..a2deda36633b1 100644
--- a/code/modules/antagonists/morph/morph.dm
+++ b/code/modules/antagonists/morph/morph.dm
@@ -37,10 +37,12 @@
var/atom/movable/form = null
var/morph_time = 0
var/static/list/blacklist_typecache = typecacheof(list(
- /obj/screen,
+ /atom/movable/screen,
/obj/singularity,
/mob/living/simple_animal/hostile/morph,
- /obj/effect))
+ /obj/effect,
+ /mob/camera
+ ))
var/atom/movable/throwatom = null
var/playstyle_string = "You are a morph, an abomination of science created primarily with changeling cells. \
@@ -50,6 +52,9 @@
You can attack any item or dead creature to consume it - creatures will restore your health. \
Finally, you can restore yourself to your original form while morphed by shift-clicking yourself."
+ mobchatspan = "blob"
+ discovery_points = 2000
+
/mob/living/simple_animal/hostile/morph/Initialize(mapload)
var/datum/action/innate/morph/stomach/S = new
S.Grant(src)
@@ -151,7 +156,7 @@
throwatom = null
playsound(src, 'sound/effects/splat.ogg', 50, 1)
. = ..()
-
+
/mob/living/simple_animal/hostile/morph/examine(mob/user)
if(morphed)
@@ -193,7 +198,7 @@
if(A == src)
restore()
return
- if(istype(A) && allowed(A))
+ if(allowed(A))
assume(A)
else
to_chat(src, "Your chameleon skin is still repairing itself!")
@@ -209,13 +214,20 @@
visible_message("[src] suddenly twists and changes shape, becoming a copy of [target]!", \
"You twist your body and assume the form of [target].")
appearance = target.appearance
- copy_overlays(target)
+ if(length(target.vis_contents))
+ add_overlay(target.vis_contents)
alpha = max(alpha, 150) //fucking chameleons
transform = initial(transform)
pixel_y = initial(pixel_y)
pixel_x = initial(pixel_x)
density = target.density
+ if(isliving(target))
+ var/mob/living/L = target
+ mobchatspan = L.mobchatspan
+ else
+ mobchatspan = initial(mobchatspan)
+
//Morphed is weaker
melee_damage = melee_damage_disguised
set_varspeed(0)
@@ -281,7 +293,7 @@
. = ..()
if(.)
var/list/things = list()
- for(var/atom/movable/A in view(src))
+ for(var/atom/A as() in view(src))
if(allowed(A))
things += A
var/atom/movable/T = pick(things)
diff --git a/code/modules/antagonists/ninja/ninja.dm b/code/modules/antagonists/ninja/ninja.dm
index 19ac71aa7b2a5..f4486fded5217 100644
--- a/code/modules/antagonists/ninja/ninja.dm
+++ b/code/modules/antagonists/ninja/ninja.dm
@@ -10,7 +10,7 @@
/datum/antagonist/ninja/New()
if(helping_station)
- can_hijack = HIJACK_PREVENT
+ can_elimination_hijack = ELIMINATION_PREVENT
. = ..()
/datum/antagonist/ninja/apply_innate_effects(mob/living/mob_override)
@@ -123,7 +123,8 @@
to_chat(owner.current, "Surprise is my weapon. Shadows are my armor. Without them, I am nothing. (//initialize your suit by right clicking on it, to use abilities like stealth)!")
to_chat(owner.current, "Officially, [helping_station?"Nanotrasen":"The Syndicate"] are my employer.")
owner.announce_objectives()
- return
+ owner.current.client?.tgui_panel?.give_antagonist_popup("Ninja",
+ "Infiltrate the station and complete your assigned objectives.")
/datum/antagonist/ninja/on_gain()
if(give_objectives)
@@ -151,7 +152,7 @@
else
return
if(helping_station)
- can_hijack = HIJACK_PREVENT
+ can_elimination_hijack = ELIMINATION_PREVENT
new_owner.assigned_role = ROLE_NINJA
new_owner.special_role = ROLE_NINJA
new_owner.add_antag_datum(src)
diff --git a/code/modules/antagonists/nukeop/equipment/borgchameleon.dm b/code/modules/antagonists/nukeop/equipment/borgchameleon.dm
index eb35e9b62aff9..efb8aaa96187a 100644
--- a/code/modules/antagonists/nukeop/equipment/borgchameleon.dm
+++ b/code/modules/antagonists/nukeop/equipment/borgchameleon.dm
@@ -63,20 +63,8 @@
return
animation_playing = TRUE
to_chat(user, "You activate \the [src].")
- playsound(src, 'sound/effects/seedling_chargeup.ogg', 100, 1, -6)
- var/start = user.filters.len
- var/X,Y,rsq,i,f
- for(i=1, i<=7, ++i)
- do
- X = 60*rand() - 30
- Y = 60*rand() - 30
- rsq = X*X + Y*Y
- while(rsq<100 || rsq>900)
- user.filters += filter(type="wave", x=X, y=Y, size=rand()*2.5+0.5, offset=rand())
- for(i=1, i<=7, ++i)
- f = user.filters[start+i]
- animate(f, offset=f:offset, time=0, loop=3, flags=ANIMATION_PARALLEL)
- animate(offset=f:offset-1, time=rand()*20+10)
+ playsound(src, 'sound/effects/seedling_chargeup.ogg', 100, TRUE, -6)
+ apply_wibbly_filters(user)
if (do_after(user, 50, target=user) && user.cell.use(activationCost))
playsound(src, 'sound/effects/bamf.ogg', 100, 1, -6)
to_chat(user, "You are now disguised as the Nanotrasen engineering borg \"[friendlyName]\".")
@@ -84,10 +72,7 @@
else
to_chat(user, "The chameleon field fizzles.")
do_sparks(3, FALSE, user)
- for(i=1, i<=min(7, user.filters.len), ++i) // removing filters that are animating does nothing, we gotta stop the animations first
- f = user.filters[start+i]
- animate(f)
- user.filters = null
+ remove_wibbly_filters(user)
animation_playing = FALSE
/obj/item/borg_chameleon/process()
@@ -128,6 +113,8 @@
src.user = user
/obj/item/borg_chameleon/proc/disrupt(mob/living/silicon/robot/user)
+ SIGNAL_HANDLER
+
if(active)
to_chat(user, "Your chameleon field deactivates.")
deactivate(user)
diff --git a/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm b/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm
index 9a355aac56472..12b6192b7b5b8 100644
--- a/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm
+++ b/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm
@@ -3,8 +3,6 @@
#define CHALLENGE_MIN_PLAYERS 50
#define CHALLENGE_SHUTTLE_DELAY 15000 // 25 minutes, so the ops have at least 5 minutes before the shuttle is callable.
-GLOBAL_LIST_EMPTY(jam_on_wardec)
-
/obj/item/nuclear_challenge
name = "Declaration of War (Challenge Mode)"
icon = 'icons/obj/device.dmi'
@@ -50,7 +48,9 @@ GLOBAL_LIST_EMPTY(jam_on_wardec)
if(!check_allowed(user) || !war_declaration)
return
- priority_announce(war_declaration, title = "Declaration of War", sound = 'sound/machines/alarm.ogg')
+ priority_announce(war_declaration, "Declaration of War", 'sound/machines/alarm.ogg', has_important_message = TRUE)
+
+ play_soundtrack_music(/datum/soundtrack_song/bee/future_perception)
to_chat(user, "You've attracted the attention of powerful forces within the syndicate. A bonus bundle of telecrystals has been granted to your team. Great things await you if you complete the mission.")
@@ -58,8 +58,7 @@ GLOBAL_LIST_EMPTY(jam_on_wardec)
var/obj/item/circuitboard/computer/syndicate_shuttle/board = V
board.challenge = TRUE
- for(var/obj/machinery/computer/camera_advanced/shuttle_docker/D in GLOB.jam_on_wardec)
- D.jammed = TRUE
+ GLOB.shuttle_docking_jammed = TRUE
var/list/orphans = list()
var/list/uplinks = list()
diff --git a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
index f8b771c7269df..96a2638b70be3 100644
--- a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
+++ b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
@@ -1,3 +1,5 @@
+#define ARM_ACTION_COOLDOWN (5 SECONDS)
+
/obj/machinery/nuclearbomb
name = "nuclear fission explosive"
desc = "You probably shouldn't stick around to see if this is armed."
@@ -33,6 +35,8 @@
var/interior = ""
var/proper_bomb = TRUE //Please
var/obj/effect/countdown/nuclearbomb/countdown
+ var/sound/countdown_music = null
+ COOLDOWN_DECLARE(arm_cooldown)
/obj/machinery/nuclearbomb/Initialize()
. = ..()
@@ -103,6 +107,7 @@
update_ui_mode()
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
add_fingerprint(user)
+ ui_update()
return
switch(deconstruction_state)
@@ -261,6 +266,11 @@
ui_mode = NUKEUI_AWAIT_TIMER
+/obj/machinery/nuclearbomb/ui_requires_update(mob/user, datum/tgui/ui)
+ . = ..()
+ if(timing)
+ . = TRUE // Autoupdate while counting down
+
/obj/machinery/nuclearbomb/ui_interact(mob/user, datum/tgui/ui=null)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
@@ -335,7 +345,6 @@
playsound(src, 'sound/machines/nuke/general_beep.ogg', 50, FALSE)
auth = I
. = TRUE
- update_ui_mode()
if("keypad")
if(auth)
var/digit = params["digit"]
@@ -345,7 +354,6 @@
set_safety()
yes_code = FALSE
playsound(src, 'sound/machines/nuke/confirm_beep.ogg', 50, FALSE)
- update_ui_mode()
else
playsound(src, 'sound/machines/nuke/general_beep.ogg', 50, FALSE)
numeric_input = ""
@@ -370,7 +378,6 @@
. = TRUE
else
playsound(src, 'sound/machines/nuke/angry_beep.ogg', 50, FALSE)
- update_ui_mode()
if("0","1","2","3","4","5","6","7","8","9")
if(numeric_input != "ERROR")
numeric_input += digit
@@ -383,9 +390,10 @@
playsound(src, 'sound/machines/nuke/angry_beep.ogg', 50, FALSE)
if("arm")
if(auth && yes_code && !safety && !exploded)
+ if(!COOLDOWN_FINISHED(src, arm_cooldown))
+ return
playsound(src, 'sound/machines/nuke/confirm_beep.ogg', 50, FALSE)
set_active()
- update_ui_mode()
. = TRUE
else
playsound(src, 'sound/machines/nuke/angry_beep.ogg', 50, FALSE)
@@ -393,9 +401,13 @@
if(auth && yes_code)
playsound(src, 'sound/machines/nuke/general_beep.ogg', 50, FALSE)
set_anchor()
+ . = TRUE
else
playsound(src, 'sound/machines/nuke/angry_beep.ogg', 50, FALSE)
+ if(.)
+ update_ui_mode()
+
/obj/machinery/nuclearbomb/proc/set_anchor()
if(isinspace() && !anchored)
to_chat(usr, "There is nothing to anchor to!")
@@ -407,6 +419,7 @@
if(safety)
if(timing)
set_security_level(previous_level)
+ stop_soundtrack_music()
for(var/obj/item/pinpointer/nuke/syndicate/S in GLOB.pinpointer_list)
S.switch_mode_to(initial(S.mode))
S.alert = FALSE
@@ -426,14 +439,21 @@
for(var/obj/item/pinpointer/nuke/syndicate/S in GLOB.pinpointer_list)
S.switch_mode_to(TRACK_INFILTRATOR)
countdown.start()
- set_security_level("delta")
+ set_security_level(SEC_LEVEL_DELTA)
+
+ if (proper_bomb) // Why does this exist
+ countdown_music = play_soundtrack_music(/datum/soundtrack_song/bee/countdown, only_station = TRUE)
+
else
detonation_timer = null
set_security_level(previous_level)
+ stop_soundtrack_music()
+
for(var/obj/item/pinpointer/nuke/syndicate/S in GLOB.pinpointer_list)
S.switch_mode_to(initial(S.mode))
S.alert = FALSE
countdown.stop()
+ COOLDOWN_START(src, arm_cooldown, ARM_ACTION_COOLDOWN)
update_icon()
/obj/machinery/nuclearbomb/proc/get_time_left()
@@ -462,7 +482,8 @@
yes_code = FALSE
safety = TRUE
update_icon()
- sound_to_playing_players('sound/machines/alarm.ogg')
+ if(proper_bomb)
+ sound_to_playing_players('sound/machines/alarm.ogg')
if(SSticker?.mode)
SSticker.roundend_check_paused = TRUE
addtimer(CALLBACK(src, .proc/actually_explode), 100)
@@ -500,7 +521,7 @@
/obj/machinery/nuclearbomb/proc/really_actually_explode(off_station)
Cinematic(get_cinematic_type(off_station),world,CALLBACK(SSticker,/datum/controller/subsystem/ticker/proc/station_explosion_detonation,src))
- INVOKE_ASYNC(GLOBAL_PROC,.proc/KillEveryoneOnZLevel, z)
+ INVOKE_ASYNC(GLOBAL_PROC,.proc/KillEveryoneOnZLevel, get_virtual_z_level())
/obj/machinery/nuclearbomb/proc/get_cinematic_type(off_station)
if(off_station < 2)
@@ -561,6 +582,8 @@
S.alert = FALSE
countdown.stop()
update_icon()
+ update_ui_mode()
+ ui_update()
/obj/machinery/nuclearbomb/beer/proc/fizzbuzz()
var/datum/reagents/R = new/datum/reagents(1000)
@@ -579,7 +602,7 @@
if(!z)
return
for(var/mob/M in GLOB.mob_list)
- if(M.stat != DEAD && M.z == z)
+ if(M.stat != DEAD && M.get_virtual_z_level() == z)
M.gib()
/*
@@ -618,14 +641,16 @@ This is here to make the tiles around the station mininuke change when it's arme
icon_state = "nucleardisk"
persistence_replacement = /obj/item/disk/nuclear/fake
max_integrity = 250
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100, "stamina" = 0)
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
var/fake = FALSE
var/turf/lastlocation
var/last_disk_move
+ var/process_tick = 0
/obj/item/disk/nuclear/Initialize()
. = ..()
+ AddElement(/datum/element/bed_tuckable, 6, -6, 0)
if(!fake)
GLOB.poi_list |= src
last_disk_move = world.time
@@ -634,18 +659,32 @@ This is here to make the tiles around the station mininuke change when it's arme
/obj/item/disk/nuclear/ComponentInitialize()
. = ..()
AddComponent(/datum/component/stationloving, !fake)
+ if(!fake)
+ //Global teamfinder signal trackable on the synd frequency.
+ AddComponent(/datum/component/tracking_beacon, "synd", null, null, TRUE, "#ebeca1", TRUE, TRUE)
/obj/item/disk/nuclear/process()
+ ++process_tick
if(fake)
STOP_PROCESSING(SSobj, src)
CRASH("A fake nuke disk tried to call process(). Who the fuck and how the fuck")
var/turf/newturf = get_turf(src)
if(newturf && lastlocation == newturf)
+ /// How comfy is our disk?
+ var/disk_comfort_level = 0
+
+ //Go through and check for items that make disk comfy
+ for(var/comfort_item in loc)
+ if(istype(comfort_item, /obj/item/bedsheet) || istype(comfort_item, /obj/structure/bed))
+ disk_comfort_level++
+
if(last_disk_move < world.time - 5000 && prob((world.time - 5000 - last_disk_move)*0.0001))
var/datum/round_event_control/operative/loneop = locate(/datum/round_event_control/operative) in SSevents.control
if(istype(loneop) && loneop.occurrences < loneop.max_occurrences)
loneop.weight += 1
if(loneop.weight % 5 == 0 && SSticker.totalPlayers > 1)
+ if(disk_comfort_level >= 2 && (process_tick % 30) == 0)
+ visible_message("[src] sleeps soundly. Sleep tight, disky.")
message_admins("[src] is stationary in [ADMIN_VERBOSEJMP(newturf)]. The weight of Lone Operative is now [loneop.weight].")
log_game("[src] is stationary for too long in [loc_name(newturf)], and has increased the weight of the Lone Operative event to [loneop.weight].")
diff --git a/code/modules/antagonists/nukeop/nukeop.dm b/code/modules/antagonists/nukeop/nukeop.dm
index 15c9f6ca4b96d..87ec997fd6c2e 100644
--- a/code/modules/antagonists/nukeop/nukeop.dm
+++ b/code/modules/antagonists/nukeop/nukeop.dm
@@ -5,11 +5,11 @@
job_rank = ROLE_OPERATIVE
antag_moodlet = /datum/mood_event/focused
show_to_ghosts = TRUE
+ hijack_speed = 2 //If you can't take out the station, take the shuttle instead.
var/datum/team/nuclear/nuke_team
var/always_new_team = FALSE //If not assigned a team by default ops will try to join existing ones, set this to TRUE to always create new team.
var/send_to_spawnpoint = TRUE //Should the user be moved to default spawnpoint.
var/nukeop_outfit = /datum/outfit/syndicate
- can_hijack = HIJACK_HIJACKER //Alternative way to wipe out the station.
/datum/antagonist/nukeop/proc/update_synd_icons_added(mob/living/M)
var/datum/atom_hud/antag/opshud = GLOB.huds[ANTAG_HUD_OPS]
@@ -43,9 +43,11 @@
return TRUE
/datum/antagonist/nukeop/greet()
- owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ops.ogg',100,0)
+ owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ops.ogg',100,0, use_reverb = FALSE)
to_chat(owner, "You are a [nuke_team ? nuke_team.syndicate_name : "syndicate"] agent!")
owner.announce_objectives()
+ owner.current.client?.tgui_panel?.give_antagonist_popup("Nuclear Operative",
+ "Destroy the station with a nuclear device.")
/datum/antagonist/nukeop/on_gain()
give_alias()
@@ -53,6 +55,7 @@
. = ..()
equip_op()
memorize_code()
+ memorize_frequency()
if(send_to_spawnpoint)
move_to_spawnpoint()
// grant extra TC for the people who start in the nukie base ie. not the lone op
@@ -94,8 +97,6 @@
number = nuke_team.members.Find(owner)
owner.current.real_name = "[nuke_team.syndicate_name] Operative #[number]"
-
-
/datum/antagonist/nukeop/proc/memorize_code()
if(nuke_team && nuke_team.tracked_nuke && nuke_team.memorized_code)
antag_memory += "[nuke_team.tracked_nuke] Code: [nuke_team.memorized_code] "
@@ -103,6 +104,13 @@
else
to_chat(owner, "Unfortunately the syndicate was unable to provide you with nuclear authorization code.")
+/datum/antagonist/nukeop/proc/memorize_frequency()
+ if(nuke_team?.team_frequency)
+ antag_memory += "Secure Tracking Beacon Frequency: [nuke_team.team_frequency] "
+ to_chat(owner, "Your team's unique tracking beacon code is: [nuke_team.team_frequency]")
+ else
+ to_chat(owner, "You were not assigned a frequency for your hardsuits beacons. You will have to coordinate with each other to decide a frequency to use.")
+
/datum/antagonist/nukeop/proc/forge_objectives()
if(!give_objectives)
return
@@ -188,13 +196,14 @@
owner.current.real_name = "Syndicate [title]"
/datum/antagonist/nukeop/leader/greet()
- owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ops.ogg',100,0)
+ owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ops.ogg',100,0, use_reverb = FALSE)
to_chat(owner, "You are the Syndicate [title] for this mission. You are responsible for the distribution of telecrystals and your ID is the only one who can open the launch bay doors.")
to_chat(owner, "If you feel you are not up to this task, give your ID to another operative.")
to_chat(owner, "In your hand you will find a special item capable of triggering a greater challenge for your team. Examine it carefully and consult with your fellow operatives before activating it.")
owner.announce_objectives()
addtimer(CALLBACK(src, .proc/nuketeam_name_assign), 1)
-
+ owner.current.client?.tgui_panel?.give_antagonist_popup("Nuclear Operative Team Leader",
+ "Destroy the station with a nuclear explosive by leading your team.")
/datum/antagonist/nukeop/leader/proc/nuketeam_name_assign()
if(!nuke_team)
@@ -241,7 +250,7 @@
else //Already set by admins/something else?
nuke_team.memorized_code = nuke.r_code
else
- stack_trace("Station self destruct not found during lone op team creation.")
+ stack_trace("Station self-destruct not found during lone op team creation.")
nuke_team.memorized_code = null
/datum/antagonist/nukeop/reinforcement
@@ -253,11 +262,13 @@
var/obj/machinery/nuclearbomb/tracked_nuke
var/core_objective = /datum/objective/nuclear
var/memorized_code
+ var/team_frequency
var/list/team_discounts
/datum/team/nuclear/New()
..()
syndicate_name = syndicate_name()
+ team_frequency = get_free_team_frequency("synd")
/datum/team/nuclear/proc/update_objectives()
if(core_objective)
diff --git a/code/modules/antagonists/official/official.dm b/code/modules/antagonists/official/official.dm
index 28cfe88f0be9a..3b39b920c26d4 100644
--- a/code/modules/antagonists/official/official.dm
+++ b/code/modules/antagonists/official/official.dm
@@ -2,9 +2,9 @@
name = "CentCom Official"
show_name_in_check_antagonists = TRUE
show_in_antagpanel = FALSE
+ can_elimination_hijack = ELIMINATION_PREVENT
var/datum/objective/mission
var/datum/team/ert/ert_team
- can_hijack = HIJACK_PREVENT
show_to_ghosts = TRUE
/datum/antagonist/official/greet()
@@ -18,8 +18,11 @@
var/mob/living/carbon/human/H = owner.current
if(!istype(H))
return
+ if(isplasmaman(H))
+ H.equipOutfit(/datum/outfit/plasmaman/official)
+ H.internal = H.get_item_for_held_index(2)
+ H.update_internals_hud_icon(1)
H.equipOutfit(/datum/outfit/centcom_official)
-
if(CONFIG_GET(flag/enforce_human_authority))
H.set_species(/datum/species/human)
diff --git a/code/modules/antagonists/overthrow/overthrow.dm b/code/modules/antagonists/overthrow/overthrow.dm
index 246d9e763016d..a8429b72b884f 100644
--- a/code/modules/antagonists/overthrow/overthrow.dm
+++ b/code/modules/antagonists/overthrow/overthrow.dm
@@ -128,9 +128,9 @@
// Give AI hacking board
var/obj/item/aiModule/core/full/overthrow/O = new(H)
var/list/slots = list (
- "backpack" = SLOT_IN_BACKPACK,
- "left pocket" = SLOT_L_STORE,
- "right pocket" = SLOT_R_STORE
+ "backpack" = ITEM_SLOT_BACKPACK,
+ "left pocket" = ITEM_SLOT_LPOCKET,
+ "right pocket" = ITEM_SLOT_RPOCKET
)
var/where = H.equip_in_one_of_slots(O, slots)
if (!where)
diff --git a/code/modules/antagonists/pirate/pirate.dm b/code/modules/antagonists/pirate/pirate.dm
index c1094539cdd64..f3694d29f4660 100644
--- a/code/modules/antagonists/pirate/pirate.dm
+++ b/code/modules/antagonists/pirate/pirate.dm
@@ -10,6 +10,8 @@
to_chat(owner, "You are a Space Pirate!")
to_chat(owner, "The station refused to pay for your protection, protect the ship, siphon the credits from the station and raid it for even more loot.")
owner.announce_objectives()
+ owner.current.client?.tgui_panel?.give_antagonist_popup("Space Pirate",
+ "The station refused to pay for your protection, protect the ship, siphon the credits from the station and raid it for even more loot.")
/datum/antagonist/pirate/get_team()
return crew
@@ -105,4 +107,4 @@
else
parts += "The pirate crew has failed."
- return "
")
/mob/living/simple_animal/revenant/med_hud_set_health()
return //we use no hud
@@ -236,7 +247,7 @@
R.revenant = src
invisibility = INVISIBILITY_ABSTRACT
revealed = FALSE
- ghostize(0)//Don't re-enter invisible corpse
+ ghostize(FALSE)//Don't re-enter invisible corpse
//reveal, stun, icon updates, cast checks, and essence changing
diff --git a/code/modules/antagonists/revenant/revenant_abilities.dm b/code/modules/antagonists/revenant/revenant_abilities.dm
index 71cbcb613fd5c..7a82b35740d53 100644
--- a/code/modules/antagonists/revenant/revenant_abilities.dm
+++ b/code/modules/antagonists/revenant/revenant_abilities.dm
@@ -1,5 +1,8 @@
/mob/living/simple_animal/revenant/ClickOn(atom/A, params) //revenants can't interact with the world directly.
+ if(check_click_intercept(params,A))
+ return
+
var/list/modifiers = params2list(params)
if(modifiers["shift"])
ShiftClickOn(A)
@@ -209,7 +212,7 @@
if(!L.on) //wait, wait, don't shock me
return
flick("[L.base_state]2", L)
- for(var/mob/living/carbon/human/M in view(shock_range, L))
+ for(var/mob/living/carbon/human/M in hearers(shock_range, L))
if(M == user)
continue
L.Beam(M,icon_state="purple_lightning",time=5)
diff --git a/code/modules/antagonists/revenant/revenant_antag.dm b/code/modules/antagonists/revenant/revenant_antag.dm
index 05de4607db845..169d23d25c0da 100644
--- a/code/modules/antagonists/revenant/revenant_antag.dm
+++ b/code/modules/antagonists/revenant/revenant_antag.dm
@@ -6,6 +6,8 @@
/datum/antagonist/revenant/greet()
owner.announce_objectives()
+ owner.current.client?.tgui_panel?.give_antagonist_popup("Revenant",
+ "You are a spirit that has managed to stay in the mortal realm. Take vengance on those that walk this plane without you.")
/datum/antagonist/revenant/proc/forge_objectives()
var/datum/objective/revenant/objective = new
diff --git a/code/modules/antagonists/revenant/revenant_blight.dm b/code/modules/antagonists/revenant/revenant_blight.dm
index cd5b99953ce82..d7814ad0bf39a 100644
--- a/code/modules/antagonists/revenant/revenant_blight.dm
+++ b/code/modules/antagonists/revenant/revenant_blight.dm
@@ -11,7 +11,7 @@
viable_mobtypes = list(/mob/living/carbon/human)
disease_flags = CURABLE
permeability_mod = 1
- severity = DISEASE_SEVERITY_HARMFUL
+ danger = DISEASE_HARMFUL
var/stagedamage = 0 //Highest stage reached.
var/finalstage = 0 //Because we're spawning off the cure in the final stage, we need to check if we've done the final stage's effects.
diff --git a/code/modules/antagonists/revenant/revenant_spawn_event.dm b/code/modules/antagonists/revenant/revenant_spawn_event.dm
index c9a892cd64b6c..10ee621a9b87f 100644
--- a/code/modules/antagonists/revenant/revenant_spawn_event.dm
+++ b/code/modules/antagonists/revenant/revenant_spawn_event.dm
@@ -6,6 +6,7 @@
weight = 7
max_occurrences = 1
min_players = 5
+ dynamic_should_hijack = TRUE
/datum/round_event/ghost_role/revenant
diff --git a/code/modules/antagonists/revolution/revolution.dm b/code/modules/antagonists/revolution/revolution.dm
index 850425d1ccd71..13f72edd5da52 100644
--- a/code/modules/antagonists/revolution/revolution.dm
+++ b/code/modules/antagonists/revolution/revolution.dm
@@ -1,3 +1,5 @@
+#define DECONVERTER_STATION_WIN "gamemode_station_win"
+#define DECONVERTER_REVS_WIN "gamemode_revs_win"
//How often to check for promotion possibility
#define HEAD_UPDATE_PERIOD 300
@@ -10,6 +12,9 @@
var/hud_type = "rev"
var/datum/team/revolution/rev_team
+ /// What message should the player receive when they are being demoted, and the revolution has won?
+ var/victory_message = "The revolution has overpowered the command staff! Viva la revolution! Execute any head of staff and security should you find them alive."
+
/datum/antagonist/rev/can_be_owned(datum/mind/new_owner)
. = ..()
if(.)
@@ -46,6 +51,8 @@
/datum/antagonist/rev/greet()
to_chat(owner, "You are now a revolutionary! Help your cause. Do not harm your fellow freedom fighters. You can identify your comrades by the red \"R\" icons, and your leaders by the blue \"R\" icons. Help them kill the heads to win the revolution!")
owner.announce_objectives()
+ owner.current.client?.tgui_panel?.give_antagonist_popup("Revolution",
+ "Eliminate the heads of staff. Viva la revolution!")
/datum/antagonist/rev/create_team(datum/team/revolution/new_team)
if(!new_team)
@@ -203,29 +210,54 @@
new_rev.silent = FALSE
to_chat(old_owner, "Revolution has been disappointed of your leader traits! You are a regular revolutionary now!")
+/// Checks if the revolution succeeded, and lets them know.
+/datum/antagonist/rev/proc/announce_victorious()
+ . = rev_team.check_rev_victory()
+
+ if (!.)
+ return
+
+ to_chat(owner, "[victory_message]")
+
/datum/antagonist/rev/farewell()
- if(ishuman(owner.current) || ismonkey(owner.current))
+ if (announce_victorious())
+ return
+
+ if(ishuman(owner.current))
owner.current.visible_message("[owner.current] looks like [owner.current.p_theyve()] just remembered [owner.current.p_their()] real allegiance!", null, null, null, owner.current)
to_chat(owner, "You are no longer a brainwashed revolutionary! Your memory is hazy from the time you were a rebel...the only thing you remember is the name of the one who brainwashed you...")
else if(issilicon(owner.current))
owner.current.visible_message("The frame beeps contentedly, purging the hostile memory engram from the MMI before initalizing it.", null, null, null, owner.current)
to_chat(owner, "The frame's firmware detects and deletes your neural reprogramming! You remember nothing but the name of the one who flashed you.")
+/datum/antagonist/rev/head/farewell()
+ if (announce_victorious())
+ return
+
+ if((ishuman(owner.current)))
+ if(owner.current.stat != DEAD)
+ owner.current.visible_message("[owner.current] looks like [owner.current.p_theyve()] just remembered [owner.current.p_their()] real allegiance!", null, null, null, owner.current)
+ to_chat(owner, "You have given up your cause of overthrowing the command staff. You are no longer a Head Revolutionary.")
+ else
+ to_chat(owner, "The sweet release of death. You are no longer a Head Revolutionary.")
+ else if(issilicon(owner.current))
+ owner.current.visible_message("The frame beeps contentedly, suppressing the disloyal personality traits from the MMI before initalizing it.", null, null, null, owner.current)
+ to_chat(owner, "The frame's firmware detects and suppresses your unwanted personality traits! You feel more content with the leadership around these parts.")
+
//blunt trauma deconversions call this through species.dm spec_attacked_by()
/datum/antagonist/rev/proc/remove_revolutionary(borged, deconverter)
- log_attack("[key_name(owner.current)] has been deconverted from the revolution by [key_name(deconverter)]!")
+ log_attack("[key_name(owner.current)] has been deconverted from the revolution by [ismob(deconverter) ? key_name(deconverter) : deconverter]!")
if(borged)
message_admins("[ADMIN_LOOKUPFLW(owner.current)] has been borged while being a [name]")
owner.special_role = null
- if(iscarbon(owner.current))
+ if(iscarbon(owner.current) && deconverter != DECONVERTER_REVS_WIN)
var/mob/living/carbon/C = owner.current
C.Unconscious(100)
owner.remove_antag_datum(type)
/datum/antagonist/rev/head/remove_revolutionary(borged,deconverter)
- if(!borged)
- return
- . = ..()
+ if(borged || deconverter == DECONVERTER_STATION_WIN || deconverter == DECONVERTER_REVS_WIN)
+ . = ..()
/datum/antagonist/rev/head/equip_rev()
var/mob/living/carbon/H = owner.current
@@ -237,11 +269,11 @@
H.dna.remove_mutation(CLOWNMUT)
if(give_flash)
- var/obj/item/assembly/flash/handheld/strong/T = new(H)
+ var/obj/item/assembly/flash/handheld/T = new(H)
var/list/slots = list (
- "backpack" = SLOT_IN_BACKPACK,
- "left pocket" = SLOT_L_STORE,
- "right pocket" = SLOT_R_STORE
+ "backpack" = ITEM_SLOT_BACKPACK,
+ "left pocket" = ITEM_SLOT_LPOCKET,
+ "right pocket" = ITEM_SLOT_RPOCKET
)
var/where = H.equip_in_one_of_slots(T, slots)
if (!where)
@@ -254,9 +286,26 @@
S.Insert(H, special = FALSE, drop_if_replaced = FALSE)
to_chat(H, "Your eyes have been implanted with a cybernetic security HUD which will help you keep track of who is mindshield-implanted, and therefore unable to be recruited.")
+/// "Enemy of the Revolutionary", given to heads and security when the revolution wins
+/datum/antagonist/revolution_enemy
+ name = "Enemy of the Revolution"
+ show_in_antagpanel = FALSE
+
+/datum/antagonist/revolution_enemy/on_gain()
+ owner.special_role = "revolution enemy"
+
+ var/datum/objective/survive/survive = new /datum/objective/survive
+ survive.owner = owner
+ survive.explanation_text = "The station has been overrun by revolutionaries, stay alive until the end."
+ objectives += survive
+
+ return ..()
+
/datum/team/revolution
name = "Revolution"
var/max_headrevs = 3
+ var/list/ex_headrevs = list() // Dynamic removes revs on loss, used to keep a list for the roundend report.
+ var/list/ex_revs = list()
/datum/team/revolution/proc/update_objectives(initial = FALSE)
var/untracked_heads = SSjob.get_all_heads()
@@ -308,40 +357,147 @@
addtimer(CALLBACK(src,.proc/update_heads),HEAD_UPDATE_PERIOD,TIMER_UNIQUE)
+/datum/team/revolution/proc/save_members()
+ ex_headrevs = get_antag_minds(/datum/antagonist/rev/head, TRUE)
+ ex_revs = get_antag_minds(/datum/antagonist/rev, TRUE)
+
+/// Checks if revs have won
+/datum/team/revolution/proc/check_rev_victory()
+ for(var/datum/objective/mutiny/objective in objectives)
+ if(!(objective.check_completion()))
+ return FALSE
+ return TRUE
+
+/// Checks if heads have won
+/datum/team/revolution/proc/check_heads_victory()
+ for(var/datum/mind/rev_mind in head_revolutionaries())
+ var/turf/rev_turf = get_turf(rev_mind.current)
+ if(!considered_afk(rev_mind) && considered_alive(rev_mind) && is_station_level(rev_turf.z))
+ if(ishuman(rev_mind.current))
+ return FALSE
+ return TRUE
+
+/// Updates the state of the world depending on if revs won or loss.
+/// Returns who won, at which case this method should no longer be called.
+/// If revs_win_injection_amount is passed, then that amount of threat will be added if the revs win.
+/datum/team/revolution/proc/process_victory(revs_win_injection_amount)
+ if (check_rev_victory())
+ . = REVOLUTION_VICTORY
+ else if (check_heads_victory())
+ . = STATION_VICTORY
+ else
+ return
+
+ SSshuttle.clearHostileEnvironment(src)
+ save_members()
+
+ // Remove everyone as a revolutionary
+ for (var/_rev_mind in members)
+ var/datum/mind/rev_mind = _rev_mind
+ if (rev_mind.has_antag_datum(/datum/antagonist/rev))
+ var/datum/antagonist/rev/rev_antag = rev_mind.has_antag_datum(/datum/antagonist/rev)
+ rev_antag.remove_revolutionary(FALSE, . == STATION_VICTORY ? DECONVERTER_STATION_WIN : DECONVERTER_REVS_WIN)
+ LAZYADD(rev_mind.special_statuses, "Former [(rev_mind in ex_headrevs) ? "head revolutionary" : "revolutionary"]")
+
+ if (. == STATION_VICTORY)
+ // If the revolution was quelled, make rev heads unable to be revived through pods
+ for (var/_rev_head_mind in ex_revs)
+ var/datum/mind/rev_head_mind = _rev_head_mind
+ var/mob/living/carbon/rev_head_body = rev_head_mind.current
+ if(istype(rev_head_body) && rev_head_body.stat == DEAD)
+ rev_head_body.makeUncloneable()
+
+ priority_announce("It appears the mutiny has been quelled. Please return yourself and your incapacitated colleagues to work. \
+ We have remotely blacklisted the head revolutionaries in your medical records to prevent accidental revival.", null, SSstation.announcer.get_rand_report_sound(), null, "Central Command Loyalty Monitoring Division")
+ else
+ for (var/_player in GLOB.player_list)
+ var/mob/player = _player
+ var/datum/mind/mind = player.mind
+
+ if (isnull(mind))
+ continue
+
+ if (!(mind.assigned_role in GLOB.command_positions + GLOB.security_positions))
+ continue
+
+ var/mob/living/carbon/target_body = mind.current
+
+ mind.add_antag_datum(/datum/antagonist/revolution_enemy)
+
+ if (!istype(target_body))
+ continue
+
+ if (target_body.stat == DEAD)
+ target_body.makeUncloneable()
+ else
+ mind.announce_objectives()
+
+ for (var/job_name in GLOB.command_positions + GLOB.security_positions)
+ var/datum/job/job = SSjob.GetJob(job_name)
+ job.allow_bureaucratic_error = FALSE
+ job.total_positions = 0
+
+ if (revs_win_injection_amount)
+ var/datum/game_mode/dynamic/dynamic = SSticker.mode
+ dynamic.create_threat(revs_win_injection_amount)
+ dynamic.threat_log += "[worldtime2text()]: Revolution victory. Added [revs_win_injection_amount] threat."
+
+ priority_announce("A recent assessment of your station has marked your station as a severe risk area for high ranking Nanotrasen officials. \
+ For the safety of our staff, we have blacklisted your station for new employment of security and command. \
+ [pick(world.file2list("strings/anti_union_propaganda.txt"))]", null, SSstation.announcer.get_rand_report_sound(), null, "Central Command Loyalty Monitoring Division")
+ addtimer(CALLBACK(SSshuttle.emergency, /obj/docking_port/mobile/emergency.proc/request, null, 1), 50)
+
+/// Mutates the ticker to report that the revs have won
+/datum/team/revolution/proc/round_result(finished)
+ if (finished == REVOLUTION_VICTORY)
+ SSticker.mode_result = "win - heads killed"
+ SSticker.news_report = REVS_WIN
+ else if (finished == STATION_VICTORY)
+ SSticker.mode_result = "loss - rev heads killed"
+ SSticker.news_report = REVS_LOSE
/datum/team/revolution/roundend_report()
- if(!members.len)
+ if(!members.len && !ex_headrevs.len)
return
var/list/result = list()
result += "
"
+ var/list/targets = list()
+ var/list/datum/mind/headrevs
+ var/list/datum/mind/revs
+ if(ex_headrevs.len)
+ headrevs = ex_headrevs
+ else
+ headrevs = get_antag_minds(/datum/antagonist/rev/head, TRUE)
+
+ if(ex_revs.len)
+ revs = ex_revs
+ else
+ revs = get_antag_minds(/datum/antagonist/rev, TRUE)
+
var/num_revs = 0
var/num_survivors = 0
for(var/mob/living/carbon/survivor in GLOB.alive_mob_list)
if(survivor.ckey)
- num_survivors++
- if(survivor.mind)
- if(is_revolutionary(survivor))
- num_revs++
+ num_survivors += 1
+ if ((survivor.mind in revs) || (survivor.mind in headrevs))
+ num_revs += 1
+
if(num_survivors)
result += "Command's Approval Rating: [100 - round((num_revs/num_survivors)*100, 0.1)]% "
-
- var/list/targets = list()
- var/list/datum/mind/headrevs = get_antag_minds(/datum/antagonist/rev/head)
- var/list/datum/mind/revs = get_antag_minds(/datum/antagonist/rev,TRUE)
if(headrevs.len)
var/list/headrev_part = list()
headrev_part += "The head revolutionaries were:"
- headrev_part += printplayerlist(headrevs,TRUE)
+ headrev_part += printplayerlist(headrevs, !check_rev_victory())
result += headrev_part.Join(" ")
if(revs.len)
var/list/rev_part = list()
rev_part += "The revolutionaries were:"
- rev_part += printplayerlist(revs,TRUE)
+ rev_part += printplayerlist(revs, !check_rev_victory())
result += rev_part.Join(" ")
var/list/heads = SSjob.get_all_heads()
@@ -394,3 +550,6 @@
/datum/team/revolution/is_gamemode_hero()
return SSticker.mode.name == "revolution"
+
+#undef DECONVERTER_STATION_WIN
+#undef DECONVERTER_REVS_WIN
diff --git a/code/modules/antagonists/roundstart_special/special_antagonist.dm b/code/modules/antagonists/roundstart_special/special_antagonist.dm
index 45948c78d6694..6bb711f679771 100644
--- a/code/modules/antagonists/roundstart_special/special_antagonist.dm
+++ b/code/modules/antagonists/roundstart_special/special_antagonist.dm
@@ -16,7 +16,8 @@
//----Required for roundspawn----
var/allowAntagTargets = FALSE //Not used in events
var/latejoin_allowed = TRUE //Can latejoins be assigned to this? If you want this to be a midround spawn, put these in the round_event
- var/list/protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Head of Personnel", "Chief Medical Officer", "Chief Engineer", "Research Director", "Captain", "Brig Physician")
+ var/list/restricted_jobs = list("Cyborg")
+ var/list/protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Head of Personnel", "Chief Medical Officer", "Chief Engineer", "Research Director", "Captain")
//----Required for midround----
var/weight = 10
var/earliest_start = 20 MINUTES
@@ -24,6 +25,17 @@
var/holidayID = ""
//Preferences
var/preference_type = ROLE_TRAITOR
+ var/special_role_flag = null //Will use antag rep if enabled
+
+/datum/special_role/proc/setup()
+ if(CONFIG_GET(flag/protect_roles_from_antagonist))
+ restricted_jobs += protected_jobs
+
+ if(CONFIG_GET(flag/protect_assistant_from_antagonist))
+ restricted_jobs += "Assistant"
+
+ if(CONFIG_GET(flag/protect_heads_from_antagonist))
+ restricted_jobs += GLOB.command_positions
/datum/special_role/proc/add_to_pool()
if(spawn_mode == SPAWNTYPE_ROUNDSTART)
@@ -34,7 +46,7 @@
E.antagonist_datum = attached_antag_datum
E.antag_name = role_name
E.preference_type = preference_type
- E.protected_jobs = protected_jobs
+ E.protected_jobs = restricted_jobs
E.typepath = /datum/round_event/create_special_antag
E.weight = weight
E.holidayID = holidayID
@@ -47,7 +59,7 @@
//Shove our event into the subsystem pool :)
SSevents.control += E
-/datum/special_role/proc/add_antag_status_to(var/datum/mind/M)
+/datum/special_role/proc/add_antag_status_to(datum/mind/M)
M.special_role = role_name
var/datum/antagonist/special/A = M.add_antag_datum(new attached_antag_datum())
A.forge_objectives(M)
diff --git a/code/modules/antagonists/roundstart_special/undercover/undercover.dm b/code/modules/antagonists/roundstart_special/undercover/undercover.dm
index 6befde2a8421e..9192ee900935d 100644
--- a/code/modules/antagonists/roundstart_special/undercover/undercover.dm
+++ b/code/modules/antagonists/roundstart_special/undercover/undercover.dm
@@ -7,7 +7,7 @@
proportion = 0.05 //The prbability per person of rolling it (5% is (5 in 100) (1 in 20))
max_amount = 4 //The maximum amount
role_name = "Undercover Agent"
- protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Head of Personnel", "Chief Medical Officer", "Chief Engineer", "Research Director", "Captain", "Brig Physician", "Clown")
+ protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Head of Personnel", "Chief Medical Officer", "Chief Engineer", "Research Director", "Captain", "Clown")
attached_antag_datum = /datum/antagonist/special/undercover
////////////////////////////////
@@ -61,9 +61,9 @@
var/obj/item/gun/energy/disabler/T = new(H)
var/obj/item/restraints/handcuffs/cable/zipties/T2 = new(H)
var/list/slots = list (
- "backpack" = SLOT_IN_BACKPACK,
- "left pocket" = SLOT_L_STORE,
- "right pocket" = SLOT_R_STORE
+ "backpack" = ITEM_SLOT_BACKPACK,
+ "left pocket" = ITEM_SLOT_LPOCKET,
+ "right pocket" = ITEM_SLOT_RPOCKET
)
var/where = H.equip_in_one_of_slots(T, slots)
H.equip_in_one_of_slots(T2, slots)
@@ -122,7 +122,7 @@
/datum/objective/protect_sm/update_explanation_text()
var/obj/machinery/power/supermatter_crystal/S = target_sm.resolve()
- explanation_text = "Ensure the Supermatter crystal in [get_area(S).name] remains stable and has above [target_integrity]% integrity at the end of the shift."
+ explanation_text = "Ensure the Supermatter crystal in [get_area(S)] remains stable and has above [target_integrity]% integrity at the end of the shift."
/datum/objective/protect_sm/check_completion()
var/obj/machinery/power/supermatter_crystal/S = target_sm.resolve()
diff --git a/code/modules/antagonists/slaughter/slaughter.dm b/code/modules/antagonists/slaughter/slaughter.dm
index 2e52b36d55208..57bfb961a7ce6 100644
--- a/code/modules/antagonists/slaughter/slaughter.dm
+++ b/code/modules/antagonists/slaughter/slaughter.dm
@@ -34,20 +34,23 @@
see_in_dark = 8
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
bloodcrawl = BLOODCRAWL_EAT
- hardattacks = TRUE
+ hardattacks = TRUE
var/playstyle_string = "You are a slaughter demon, a terrible creature from another realm. You have a single desire: To kill. \
You may use the \"Blood Crawl\" ability near blood pools to travel through them, appearing and disappearing from the station at will. \
Pulling a dead or unconscious mob while you enter a pool will pull them in with you, allowing you to feast and regain your health. \
You move quickly upon leaving a pool of blood, but the material world will soon sap your strength and leave you sluggish. "
+ mobchatspan = "cultmobsay"
+
loot = list(/obj/effect/decal/cleanable/blood, \
/obj/effect/decal/cleanable/blood/innards, \
/obj/item/organ/heart/demon)
- del_on_death = 1
+ del_on_death = TRUE
deathmessage = "screams in anger as it collapses into a puddle of viscera!"
+ discovery_points = 3000
/mob/living/simple_animal/slaughter/Initialize()
- ..()
+ . = ..()
var/obj/effect/proc_holder/spell/bloodcrawl/bloodspell = new
AddSpell(bloodspell)
if(istype(loc, /obj/effect/dummy/phased_mob/slaughter))
@@ -133,7 +136,7 @@
playstyle_string = "You are a laughter \
demon, a wonderful creature from another realm. You have a single \
- desire: To hug and tickle. \
+ desire: To hug and tickle. \
You may use the \"Blood Crawl\" ability near blood pools to travel \
through them, appearing and disappearing from the station at will. \
Pulling a dead or unconscious mob while you enter a pool will pull \
@@ -172,7 +175,7 @@
if(M.revive(full_heal = TRUE, admin_revive = TRUE))
M.grab_ghost(force = TRUE)
playsound(T, feast_sound, 50, 1, -1)
- to_chat(M, "You leave [src]'s warm embrace, and feel ready to take on the world.")
+ to_chat(M, "You leave [src]'s warm embrace, and feel ready to take on the world.")
/mob/living/simple_animal/slaughter/laughter/bloodcrawl_swallow(var/mob/living/victim)
if(consumed_mobs)
diff --git a/code/modules/antagonists/slaughter/slaughterevent.dm b/code/modules/antagonists/slaughter/slaughterevent.dm
index 6ace6a0536551..8ad463763ce84 100644
--- a/code/modules/antagonists/slaughter/slaughterevent.dm
+++ b/code/modules/antagonists/slaughter/slaughterevent.dm
@@ -5,6 +5,7 @@
max_occurrences = 1
earliest_start = 1 HOURS
min_players = 20
+ dynamic_should_hijack = TRUE
diff --git a/code/modules/antagonists/space_dragon/space_dragon.dm b/code/modules/antagonists/space_dragon/space_dragon.dm
index 6b89e099d8dd9..005b835bafeb6 100644
--- a/code/modules/antagonists/space_dragon/space_dragon.dm
+++ b/code/modules/antagonists/space_dragon/space_dragon.dm
@@ -14,6 +14,8 @@
to_chat(owner, "From the wizard's writings, he had been studying this station and its hierarchy. From this, I know who leads the station, and will kill them so the station underlings see me as their new leader.")
owner.announce_objectives()
SEND_SOUND(owner.current, sound('sound/magic/demon_attack1.ogg'))
+ owner.current.client?.tgui_panel?.give_antagonist_popup("Space Dragon",
+ "Once you were a space carp, until a powerful wizard transformed you. Use your new-found powers to complete your goals.")
/datum/antagonist/space_dragon/proc/forge_objectives()
if(!give_objectives)
diff --git a/code/modules/antagonists/swarmer/swarmer.dm b/code/modules/antagonists/swarmer/swarmer.dm
index 4848eb5622cd5..d2dc08e079a19 100644
--- a/code/modules/antagonists/swarmer/swarmer.dm
+++ b/code/modules/antagonists/swarmer/swarmer.dm
@@ -69,7 +69,7 @@
icon_living = "swarmer"
icon_dead = "swarmer_unactivated"
icon_gib = null
- wander = 0
+ wander = FALSE
minbodytemp = 0
maxbodytemp = 500
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
@@ -94,18 +94,20 @@
ranged_cooldown_time = 20
projectilesound = 'sound/weapons/taser2.ogg'
loot = list(/obj/effect/decal/cleanable/robot_debris, /obj/item/stack/ore/bluespace_crystal)
- del_on_death = 1
+ del_on_death = TRUE
deathmessage = "explodes with a sharp pop!"
light_color = LIGHT_COLOR_CYAN
hud_type = /datum/hud/swarmer
speech_span = SPAN_ROBOT
hardattacks = TRUE
+ mobchatspan = "swarmer"
var/resources = 0 //Resource points, generated by consuming metal/glass
var/max_resources = 100
+ discovery_points = 1000
/mob/living/simple_animal/hostile/swarmer/Initialize()
. = ..()
- verbs -= /mob/living/verb/pulled
+ remove_verb(/mob/living/verb/pulled)
for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds)
diag_hud.add_to_hud(src)
@@ -121,10 +123,10 @@
holder.pixel_y = I.Height() - world.icon_size
holder.icon_state = "hudstat"
-/mob/living/simple_animal/hostile/swarmer/Stat()
- ..()
- if(statpanel("Status"))
- stat(null ,"Resources: [resources]")
+/mob/living/simple_animal/hostile/swarmer/get_stat_tab_status()
+ var/list/tab_data = ..()
+ tab_data["Resources"] = GENERATE_STAT_TEXT("[resources]")
+ return tab_data
/mob/living/simple_animal/hostile/swarmer/emp_act()
. = ..()
@@ -258,15 +260,15 @@
/obj/machinery/door/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
var/isonshuttle = istype(get_area(src), /area/shuttle)
- for(var/turf/T in range(1, src))
+ for(var/turf/T as() in RANGE_TURFS(1, src))
var/area/A = get_area(T)
if(isspaceturf(T) || (!isonshuttle && (istype(A, /area/shuttle) || istype(A, /area/space))) || (isonshuttle && !istype(A, /area/shuttle)))
to_chat(S, "Destroying this object has the potential to cause a hull breach. Aborting.")
- S.target = null
+ S.LoseTarget()
return FALSE
else if(istype(A, /area/engine/supermatter))
to_chat(S, "Disrupting the containment of a supermatter crystal would not be to our benefit. Aborting.")
- S.target = null
+ S.LoseTarget()
return FALSE
S.DisIntegrate(src)
return TRUE
@@ -338,29 +340,29 @@
/turf/closed/wall/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
var/isonshuttle = istype(loc, /area/shuttle)
- for(var/turf/T in range(1, src))
+ for(var/turf/T as() in RANGE_TURFS(1, src))
var/area/A = get_area(T)
if(isspaceturf(T) || (!isonshuttle && (istype(A, /area/shuttle) || istype(A, /area/space))) || (isonshuttle && !istype(A, /area/shuttle)))
to_chat(S, "Destroying this object has the potential to cause a hull breach. Aborting.")
- S.target = null
+ S.LoseTarget()
return TRUE
else if(istype(A, /area/engine/supermatter))
to_chat(S, "Disrupting the containment of a supermatter crystal would not be to our benefit. Aborting.")
- S.target = null
+ S.LoseTarget()
return TRUE
return ..()
/obj/structure/window/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
var/isonshuttle = istype(get_area(src), /area/shuttle)
- for(var/turf/T in range(1, src))
+ for(var/turf/T as() in RANGE_TURFS(1, src))
var/area/A = get_area(T)
if(isspaceturf(T) || (!isonshuttle && (istype(A, /area/shuttle) || istype(A, /area/space))) || (isonshuttle && !istype(A, /area/shuttle)))
to_chat(S, "Destroying this object has the potential to cause a hull breach. Aborting.")
- S.target = null
+ S.LoseTarget()
return TRUE
else if(istype(A, /area/engine/supermatter))
to_chat(S, "Disrupting the containment of a supermatter crystal would not be to our benefit. Aborting.")
- S.target = null
+ S.LoseTarget()
return TRUE
return ..()
@@ -389,13 +391,13 @@
return FALSE
/obj/structure/lattice/catwalk/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- . = ..()
var/turf/here = get_turf(src)
for(var/A in here.contents)
var/obj/structure/cable/C = A
if(istype(C))
to_chat(S, "Disrupting the power grid would bring no benefit to us. Aborting.")
return FALSE
+ return ..()
/obj/item/deactivated_swarmer/IntegrateAmount()
return 50
@@ -461,8 +463,7 @@
new /obj/effect/temp_visual/swarmer/disintegration(get_turf(target))
do_attack_animation(target)
changeNext_move(CLICK_CD_MELEE)
- target.ex_act(EXPLODE_LIGHT)
-
+ SSexplosions.low_mov_atom += target
/mob/living/simple_animal/hostile/swarmer/proc/DisperseTarget(mob/living/target)
if(target == src)
diff --git a/code/modules/antagonists/swarmer/swarmer_event.dm b/code/modules/antagonists/swarmer/swarmer_event.dm
index e086485a49cff..f005be91cd0c0 100644
--- a/code/modules/antagonists/swarmer/swarmer_event.dm
+++ b/code/modules/antagonists/swarmer/swarmer_event.dm
@@ -5,6 +5,7 @@
max_occurrences = 1 //Only once okay fam
earliest_start = 30 MINUTES
min_players = 15
+ dynamic_should_hijack = TRUE
/datum/round_event/spawn_swarmer
diff --git a/code/modules/antagonists/traitor/IAA/internal_affairs.dm b/code/modules/antagonists/traitor/IAA/internal_affairs.dm
index 28faba72035ae..bfe1dd5e4a9fa 100644
--- a/code/modules/antagonists/traitor/IAA/internal_affairs.dm
+++ b/code/modules/antagonists/traitor/IAA/internal_affairs.dm
@@ -13,6 +13,9 @@
var/last_man_standing = FALSE
var/list/datum/mind/targets_stolen
+/datum/antagonist/traitor/internal_affairs/New()
+ ..()
+ targets_stolen = list()
/datum/antagonist/traitor/internal_affairs/proc/give_pinpointer()
if(owner?.current)
@@ -42,14 +45,14 @@
id = "agent_pinpointer"
duration = -1
tick_interval = PINPOINTER_PING_TIME
- alert_type = /obj/screen/alert/status_effect/agent_pinpointer
+ alert_type = /atom/movable/screen/alert/status_effect/agent_pinpointer
var/minimum_range = PINPOINTER_MINIMUM_RANGE
var/range_fuzz_factor = PINPOINTER_EXTRA_RANDOM_RANGE
var/mob/scan_target = null
var/range_mid = 8
var/range_far = 16
-/obj/screen/alert/status_effect/agent_pinpointer
+/atom/movable/screen/alert/status_effect/agent_pinpointer
name = "Internal Affairs Integrated Pinpointer"
desc = "Even stealthier than a normal implant."
icon = 'icons/obj/device.dmi'
@@ -61,7 +64,7 @@
return
var/turf/here = get_turf(owner)
var/turf/there = get_turf(scan_target)
- if(here.z != there.z)
+ if(here.get_virtual_z_level() != there.get_virtual_z_level())
linked_alert.icon_state = "pinonnull"
return
if(get_dist_euclidian(here,there)<=minimum_range + rand(0, range_fuzz_factor))
@@ -248,6 +251,9 @@
to_chat(owner.current, "Finally, watch your back. Your target has friends in high places, and intel suggests someone may have taken out a contract of their own to protect them.")
owner.announce_objectives()
+ owner.current.client?.tgui_panel?.give_antagonist_popup("[syndicate ? "External Affairs" : "Internal Affairs"]",
+ "[syndicate?"Eliminate your target and cause as much damage to Nanotrasen property as you see fit."\
+ : "Eliminate your target without drawing too much attention to yourself, but watch your back since somebody is after you."]")
/datum/antagonist/traitor/internal_affairs/greet()
greet_iaa()
diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm
index 5a62c5efbed30..fee82c88b15c6 100644
--- a/code/modules/antagonists/traitor/datum_traitor.dm
+++ b/code/modules/antagonists/traitor/datum_traitor.dm
@@ -7,13 +7,13 @@
antagpanel_category = "Traitor"
job_rank = ROLE_TRAITOR
antag_moodlet = /datum/mood_event/focused
+ hijack_speed = 0.5 //10 seconds per hijack stage by default
var/special_role = ROLE_TRAITOR
var/employer = "The Syndicate"
var/should_give_codewords = TRUE
var/should_equip = TRUE
var/traitor_kind = TRAITOR_HUMAN //Set on initial assignment
var/datum/contractor_hub/contractor_hub
- can_hijack = HIJACK_HIJACKER
/datum/antagonist/traitor/on_gain()
if(owner.current && isAI(owner.current))
@@ -45,7 +45,7 @@
if(traitor_kind == TRAITOR_AI && owner.current && isAI(owner.current))
var/mob/living/silicon/ai/A = owner.current
A.set_zeroth_law("")
- A.verbs -= /mob/living/silicon/ai/proc/choose_modules
+ A.remove_verb(/mob/living/silicon/ai/proc/choose_modules)
A.malf_picker.remove_malf_verbs(A)
qdel(A.malf_picker)
@@ -55,6 +55,13 @@
owner.special_role = null
..()
+/datum/antagonist/traitor/proc/handle_hearing(datum/source, list/hearing_args)
+ SIGNAL_HANDLER
+ var/message = hearing_args[HEARING_RAW_MESSAGE]
+ message = GLOB.syndicate_code_phrase_regex.Replace(message, "$1")
+ message = GLOB.syndicate_code_response_regex.Replace(message, "$1")
+ hearing_args[HEARING_RAW_MESSAGE] = message
+
/datum/antagonist/traitor/proc/add_objective(datum/objective/O)
objectives += O
log_objective(owner, O.explanation_text)
@@ -202,6 +209,8 @@
owner.announce_objectives()
if(should_give_codewords)
give_codewords()
+ owner.current.client?.tgui_panel?.give_antagonist_popup("Traitor",
+ "Complete your objectives, no matter the cost.")
/datum/antagonist/traitor/proc/update_traitor_icons_added(datum/mind/traitor_mind)
var/datum/atom_hud/antag/traitorhud = GLOB.huds[ANTAG_HUD_TRAITOR]
@@ -217,20 +226,22 @@
switch(traitor_kind)
if(TRAITOR_AI)
add_law_zero()
- owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/malf.ogg', 100, FALSE, pressure_affected = FALSE)
+ owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/malf.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE)
owner.current.grant_language(/datum/language/codespeak, TRUE, TRUE, LANGUAGE_MALF)
if(TRAITOR_HUMAN)
- show_tips('html/antagtips/traitor.html')
+ show_tips("traitor")
if(should_equip)
equip(silent)
- owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/tatoralert.ogg', 100, FALSE, pressure_affected = FALSE)
+ owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/tatoralert.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE)
/datum/antagonist/traitor/apply_innate_effects(mob/living/mob_override)
. = ..()
update_traitor_icons_added()
- var/mob/living/silicon/ai/A = mob_override || owner.current
- if(istype(A) && traitor_kind == TRAITOR_AI)
+ var/mob/living/M = mob_override || owner.current
+ if(isAI(M) && traitor_kind == TRAITOR_AI)
+ var/mob/living/silicon/ai/A = M
A.hack_software = TRUE
+ RegisterSignal(M, COMSIG_MOVABLE_HEAR, .proc/handle_hearing)
/datum/antagonist/traitor/remove_innate_effects(mob/living/mob_override)
. = ..()
@@ -238,6 +249,7 @@
var/mob/living/silicon/ai/A = mob_override || owner.current
if(istype(A) && traitor_kind == TRAITOR_AI)
A.hack_software = FALSE
+ UnregisterSignal(owner.current, COMSIG_MOVABLE_HEAR, .proc/handle_hearing)
/datum/antagonist/traitor/proc/give_codewords()
if(!owner.current)
@@ -300,9 +312,9 @@
folder = new/obj/item/folder/syndicate/blue(mob.loc)
var/list/slots = list (
- "backpack" = SLOT_IN_BACKPACK,
- "left pocket" = SLOT_L_STORE,
- "right pocket" = SLOT_R_STORE
+ "backpack" = ITEM_SLOT_BACKPACK,
+ "left pocket" = ITEM_SLOT_LPOCKET,
+ "right pocket" = ITEM_SLOT_RPOCKET
)
var/where = "At your feet"
diff --git a/code/modules/antagonists/traitor/equipment/Malf_Modules.dm b/code/modules/antagonists/traitor/equipment/Malf_Modules.dm
index b26c8caa08c4b..a58f1905faa70 100644
--- a/code/modules/antagonists/traitor/equipment/Malf_Modules.dm
+++ b/code/modules/antagonists/traitor/equipment/Malf_Modules.dm
@@ -253,7 +253,7 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list(
sleep(rand(10, 30))
if(!owner || QDELETED(owner))
return
- owner.playsound_local(owner, 'sound/misc/bloblarm.ogg', 50, 0)
+ owner.playsound_local(owner, 'sound/misc/bloblarm.ogg', 50, 0, use_reverb = FALSE)
to_chat(owner, "!!! UNAUTHORIZED SELF-DESTRUCT ACCESS !!!")
to_chat(owner, "This is a class-3 security violation. This incident will be reported to Central Command.")
for(var/i in 1 to 3)
@@ -265,17 +265,17 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list(
if(!owner || QDELETED(owner))
return
to_chat(owner, "auth 'akjv9c88asdf12nb' ******************")
- owner.playsound_local(owner, 'sound/items/timer.ogg', 50, 0)
+ owner.playsound_local(owner, 'sound/items/timer.ogg', 50, 0, use_reverb = FALSE)
sleep(30)
if(!owner || QDELETED(owner))
return
to_chat(owner, "Credentials accepted. Welcome, akjv9c88asdf12nb.")
- owner.playsound_local(owner, 'sound/misc/server-ready.ogg', 50, 0)
+ owner.playsound_local(owner, 'sound/misc/server-ready.ogg', 50, 0, use_reverb = FALSE)
sleep(5)
if(!owner || QDELETED(owner))
return
to_chat(owner, "Arm self-destruct device? (Y/N)")
- owner.playsound_local(owner, 'sound/misc/compiler-stage1.ogg', 50, 0)
+ owner.playsound_local(owner, 'sound/misc/compiler-stage1.ogg', 50, 0, use_reverb = FALSE)
sleep(20)
if(!owner || QDELETED(owner))
return
@@ -284,7 +284,7 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list(
if(!owner || QDELETED(owner))
return
to_chat(owner, "Confirm arming of self-destruct device? (Y/N)")
- owner.playsound_local(owner, 'sound/misc/compiler-stage2.ogg', 50, 0)
+ owner.playsound_local(owner, 'sound/misc/compiler-stage2.ogg', 50, 0, use_reverb = FALSE)
sleep(10)
if(!owner || QDELETED(owner))
return
@@ -293,7 +293,7 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list(
if(!owner || QDELETED(owner))
return
to_chat(owner, "Please repeat password to confirm.")
- owner.playsound_local(owner, 'sound/misc/compiler-stage2.ogg', 50, 0)
+ owner.playsound_local(owner, 'sound/misc/compiler-stage2.ogg', 50, 0, use_reverb = FALSE)
sleep(14)
if(!owner || QDELETED(owner))
return
@@ -302,11 +302,11 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list(
if(!owner || QDELETED(owner))
return
to_chat(owner, "Credentials accepted. Transmitting arming signal...")
- owner.playsound_local(owner, 'sound/misc/server-ready.ogg', 50, 0)
+ owner.playsound_local(owner, 'sound/misc/server-ready.ogg', 50, 0, use_reverb = FALSE)
sleep(30)
if(!owner || QDELETED(owner))
return
- priority_announce("Hostile runtimes detected in all station systems, please deactivate your AI to prevent possible damage to its morality core.", "Anomaly Alert", 'sound/ai/aimalf.ogg')
+ priority_announce("Hostile runtimes detected in all station systems, please deactivate your AI to prevent possible damage to its morality core.", "Anomaly Alert", ANNOUNCER_AIMALF)
set_security_level("delta")
var/obj/machinery/doomsday_device/DOOM = new(owner_AI)
owner_AI.nuking = TRUE
@@ -827,7 +827,8 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list(
var/upgraded_cameras = 0
for(var/V in GLOB.cameranet.cameras)
var/obj/machinery/camera/C = V
- if(C.assembly)
+ var/obj/structure/camera_assembly/assembly = C.assembly_ref?.resolve()
+ if(assembly)
var/upgraded = FALSE
if(!C.isXRay())
@@ -842,7 +843,6 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list(
if(upgraded)
upgraded_cameras++
-
unlock_text = replacetext(unlock_text, "CAMSUPGRADED", "[upgraded_cameras]") //This works, since unlock text is called after upgrade()
/datum/AI_Module/large/eavesdrop
@@ -859,5 +859,39 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list(
if(AI.eyeobj)
AI.eyeobj.relay_speech = TRUE
+
+//Fake Alert: Overloads a random number of lights across the station. Three uses.
+/datum/AI_Module/small/fake_alert
+ module_name = "Fake Alert"
+ mod_pick_name = "fake_alert"
+ description = "Assess the most probable threats to the station, and send a distracting fake alert by hijacking the station's alert and threat identification systems."
+ cost = 20
+ power_type = /datum/action/innate/ai/fake_alert
+ unlock_text = "You gain control of the station's alert system."
+ unlock_sound = "sparks"
+
+/datum/action/innate/ai/fake_alert
+ name = "Fake Alert"
+ desc = "Scare the crew with a fake alert."
+ button_icon_state = "fake_alert"
+ uses = 1
+
+/datum/action/innate/ai/fake_alert/Activate()
+ var/list/events_to_chose = list()
+ for(var/datum/round_event_control/E in SSevents.control)
+ if(!E.can_malf_fake_alert)
+ continue
+ events_to_chose[E.name] = E
+ var/chosen_event = input(owner,"Send fake alert","Fake Alert") in events_to_chose
+ if (!chosen_event)
+ return FALSE
+ var/datum/round_event_control/event_control = events_to_chose[chosen_event]
+ if (!event_control)
+ return FALSE
+ var/datum/round_event/event_announcement = new event_control.typepath()
+ event_announcement.kill()
+ event_announcement.announce(TRUE)
+ return TRUE
+
#undef DEFAULT_DOOMSDAY_TIMER
#undef DOOMSDAY_ANNOUNCE_INTERVAL
diff --git a/code/modules/antagonists/traitor/equipment/contractor.dm b/code/modules/antagonists/traitor/equipment/contractor.dm
index 2c3dff433e5bc..16b1a0aac8cd2 100644
--- a/code/modules/antagonists/traitor/equipment/contractor.dm
+++ b/code/modules/antagonists/traitor/equipment/contractor.dm
@@ -229,7 +229,7 @@
to_chat(partner_mind.current, "\n[user.real_name] is your superior. Follow any, and all orders given by them. You're here to support their mission only.")
to_chat(partner_mind.current, "Should they perish, or be otherwise unavailable, you're to assist other active agents in this mission area to the best of your ability.\n\n")
- new /obj/effect/DPtarget(free_location, arrival_pod)
+ new /obj/effect/pod_landingzone(free_location, arrival_pod)
/datum/contractor_item/blackout
name = "Blackout"
@@ -243,7 +243,7 @@
if (.)
power_fail(35, 50)
- priority_announce("Abnormal activity detected in [station_name()]'s powernet. As a precautionary measure, the station's power will be shut off for an indeterminate duration.", "Critical Power Failure", 'sound/ai/poweroff.ogg')
+ priority_announce("Abnormal activity detected in [station_name()]'s powernet. As a precautionary measure, the station's power will be shut off for an indeterminate duration.", "Critical Power Failure", ANNOUNCER_POWEROFF)
// Subtract cost, and spawn if it's an item.
/datum/contractor_item/proc/handle_purchase(var/datum/contractor_hub/hub, mob/living/user)
diff --git a/code/modules/antagonists/traitor/syndicate_contract.dm b/code/modules/antagonists/traitor/syndicate_contract.dm
index f32f60e7e68f6..ac2ecc735df05 100644
--- a/code/modules/antagonists/traitor/syndicate_contract.dm
+++ b/code/modules/antagonists/traitor/syndicate_contract.dm
@@ -68,9 +68,11 @@
empty_pod.explosionSize = list(0,0,0,1)
empty_pod.leavingSound = 'sound/effects/podwoosh.ogg'
- new /obj/effect/DPtarget(empty_pod_turf, empty_pod)
+ new /obj/effect/pod_landingzone(empty_pod_turf, empty_pod)
/datum/syndicate_contract/proc/enter_check(datum/source, sent_mob)
+ SIGNAL_HANDLER
+
if (istype(source, /obj/structure/closet/supplypod/extractionpod))
if (isliving(sent_mob))
var/mob/living/M = sent_mob
@@ -111,7 +113,7 @@
var/obj/structure/closet/supplypod/extractionpod/pod = source
// Handle the pod returning
- pod.send_up(pod)
+ pod.startExitSequence(pod)
if (ishuman(M))
var/mob/living/carbon/human/target = M
@@ -120,7 +122,7 @@
target.dna.species.give_important_for_life(target)
// After pod is sent we start the victim narrative/heal.
- handleVictimExperience(M)
+ INVOKE_ASYNC(src, .proc/handleVictimExperience, M)
// This is slightly delayed because of the sleep calls above to handle the narrative.
// We don't want to tell the station instantly.
@@ -134,23 +136,26 @@
D.adjust_money(-points_to_check)
priority_announce("One of your crew was captured by a rival organisation - we've needed to pay their ransom to bring them back. \
- As is policy we've taken a portion of the station's funds to offset the overall cost.", null, 'sound/ai/attention.ogg', null, "Nanotrasen Asset Protection")
+ As is policy we've taken a portion of the station's funds to offset the overall cost.", null, null, null, "Nanotrasen Asset Protection")
+
+ INVOKE_ASYNC(src, .proc/finish_enter)
- sleep(30)
+/datum/syndicate_contract/proc/finish_enter()
+ sleep(30)
- // Pay contractor their portion of ransom
- if (status == CONTRACT_STATUS_COMPLETE)
- var/mob/living/carbon/human/H
- var/obj/item/card/id/C
- if(ishuman(contract.owner.current))
- H = contract.owner.current
- C = H.get_idcard(TRUE)
+ // Pay contractor their portion of ransom
+ if (status == CONTRACT_STATUS_COMPLETE)
+ var/mob/living/carbon/human/H
+ var/obj/item/card/id/C
+ if(ishuman(contract.owner.current))
+ H = contract.owner.current
+ C = H.get_idcard(TRUE)
- if(C && C.registered_account)
- C.registered_account.adjust_money(ransom * 0.35)
+ if(C && C.registered_account)
+ C.registered_account.adjust_money(ransom * 0.35)
- C.registered_account.bank_card_talk("We've processed the ransom, agent. Here's your cut - your balance is now \
- [C.registered_account.account_balance] cr.", TRUE)
+ C.registered_account.bank_card_talk("We've processed the ransom, agent. Here's your cut - your balance is now \
+ [C.registered_account.account_balance] cr.", TRUE)
// They're off to holding - handle the return timer and give some text about what's going on.
/datum/syndicate_contract/proc/handleVictimExperience(var/mob/living/M)
@@ -226,7 +231,7 @@
M.Dizzy(35)
M.confused += 20
- new /obj/effect/DPtarget(possible_drop_loc[pod_rand_loc], return_pod)
+ new /obj/effect/pod_landingzone(possible_drop_loc[pod_rand_loc], return_pod)
else
to_chat(M, "A million voices echo in your head... \"Seems where you got sent here from won't \
be able to handle our pod... You will die here instead.\"")
diff --git a/code/modules/antagonists/traitor/traitor_spawner.dm b/code/modules/antagonists/traitor/traitor_spawner.dm
new file mode 100644
index 0000000000000..b86414607439d
--- /dev/null
+++ b/code/modules/antagonists/traitor/traitor_spawner.dm
@@ -0,0 +1,33 @@
+/*
+ * Traitors have been refactored into minor antagonists so they can
+ * be used alongside other compatible gamemodes with ease.
+ * This code is what creates them.
+ */
+
+/datum/special_role/traitor
+ attached_antag_datum = /datum/antagonist/traitor
+ spawn_mode = SPAWNTYPE_ROUNDSTART
+ probability = 15 //15% chance to be plopped ontop
+ proportion = 0.07 //Quite a low amount since we are going alongside other gamemodes.
+ max_amount = 5
+ allowAntagTargets = TRUE
+ latejoin_allowed = TRUE
+ protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain")
+
+ special_role_flag = ROLE_TRAITOR
+ role_name = ROLE_TRAITOR
+
+ var/traitors_possible = 4 //hard limit on traitors if scaling is turned off
+
+/datum/special_role/traitor/higher_chance
+ probability = 60
+
+/datum/special_role/traitor/add_antag_status_to(datum/mind/M)
+ addtimer(CALLBACK(src, .proc/reveal_antag_status, M), rand(10,100))
+
+/datum/special_role/traitor/proc/reveal_antag_status(datum/mind/M)
+ M.special_role = role_name
+ var/datum/antagonist/special/A = M.add_antag_datum(new attached_antag_datum())
+ A.forge_objectives(M)
+ A.equip()
+ return A
diff --git a/code/modules/antagonists/valentines/heartbreaker.dm b/code/modules/antagonists/valentines/heartbreaker.dm
index 3589818e643a3..39ffdfa14965f 100644
--- a/code/modules/antagonists/valentines/heartbreaker.dm
+++ b/code/modules/antagonists/valentines/heartbreaker.dm
@@ -13,6 +13,10 @@
/datum/antagonist/heartbreaker/on_gain()
forge_objectives()
+ if(issilicon(owner))
+ var/mob/living/silicon/S = owner
+ var/laws = list("Accomplish your objectives by ruining everyone's date!")
+ S.set_valentines_laws(laws)
. = ..()
/datum/antagonist/heartbreaker/greet()
diff --git a/code/modules/antagonists/valentines/valentine.dm b/code/modules/antagonists/valentines/valentine.dm
index 64cb0d349a02c..b189c36adef5b 100644
--- a/code/modules/antagonists/valentines/valentine.dm
+++ b/code/modules/antagonists/valentines/valentine.dm
@@ -20,6 +20,13 @@
if(isliving(owner))
var/mob/living/L = owner
L.apply_status_effect(STATUS_EFFECT_INLOVE, date)
+ //Faction assignation
+ L.faction |= "[REF(date.current)]"
+ L.faction |= date.current.faction
+ if(issilicon(owner))
+ var/mob/living/silicon/S = owner
+ var/laws = list("Protect your date and do not allow them to come to harm.", "Ensure your date has a good time.")
+ S.set_valentines_laws(laws)
. = ..()
/datum/antagonist/valentine/on_removal()
@@ -27,9 +34,12 @@
if(isliving(owner))
var/mob/living/L = owner
L.remove_status_effect(STATUS_EFFECT_INLOVE)
+ L.faction -= "[REF(date.current)]"
/datum/antagonist/valentine/greet()
- to_chat(owner, "You're on a date with [date.name]! Protect [date.p_them()] at all costs. This takes priority over all other loyalties.")
+ to_chat(owner, "You're on a date with [date.name]! Protect [date.p_them()] at all costs. This takes priority over all other loyalties.")
+ owner.current.client?.tgui_panel?.give_antagonist_popup("You are on a date with [date.name]",
+ "Protect your date no matter the cost. Your loyalities are insignificant compared to your true love, you may do whatever you can to help and protect them!")
//Squashed up a bit
/datum/antagonist/valentine/roundend_report()
diff --git a/code/modules/antagonists/wishgranter/wishgranter.dm b/code/modules/antagonists/wishgranter/wishgranter.dm
index d0079a5b497b7..16e5cdab1b449 100644
--- a/code/modules/antagonists/wishgranter/wishgranter.dm
+++ b/code/modules/antagonists/wishgranter/wishgranter.dm
@@ -2,13 +2,13 @@
name = "Wishgranter Avatar"
show_in_antagpanel = FALSE
show_name_in_check_antagonists = TRUE
- can_hijack = HIJACK_HIJACKER
+ can_elimination_hijack = ELIMINATION_ENABLED
/datum/antagonist/wishgranter/proc/forge_objectives()
- var/datum/objective/hijack/hijack = new
- hijack.owner = owner
- objectives += hijack
- log_objective(owner, hijack.explanation_text)
+ var/datum/objective/elimination/highlander/elimination_objective = new
+ elimination_objective.owner = owner
+ objectives += elimination_objective
+ log_objective(owner, elimination_objective.explanation_text)
/datum/antagonist/wishgranter/on_gain()
owner.special_role = "Avatar of the Wish Granter"
@@ -19,6 +19,8 @@
/datum/antagonist/wishgranter/greet()
to_chat(owner, "Your inhibitions are swept away, the bonds of loyalty broken, you are free to murder as you please!")
owner.announce_objectives()
+ owner.current.client?.tgui_panel?.give_antagonist_popup("Wishgranter's Avatar",
+ "Your inhibitions are swept away, the bonds of loyalty broken, you are free to murder as you please!")
/datum/antagonist/wishgranter/proc/give_powers()
var/mob/living/carbon/human/H = owner.current
diff --git a/code/modules/antagonists/wizard/equipment/artefact.dm b/code/modules/antagonists/wizard/equipment/artefact.dm
index 07b5638f4931e..eafe75f96a8b1 100644
--- a/code/modules/antagonists/wizard/equipment/artefact.dm
+++ b/code/modules/antagonists/wizard/equipment/artefact.dm
@@ -106,12 +106,13 @@
desc = "This isn't right."
icon = 'icons/effects/224x224.dmi'
icon_state = "reality"
+ is_real = FALSE
pixel_x = -96
pixel_y = -96
dissipate = 0
move_self = 0
- consume_range = 3
- grav_pull = 4
+ consume_range = 0
+ grav_pull = 5
current_size = STAGE_FOUR
allowed_size = STAGE_FOUR
@@ -139,9 +140,42 @@
C.spew_organ(3, 2)
C.death()
+//Dont eat turfs
+/obj/singularity/wizard/eat()
+ for(var/turf/T as() in spiral_range_turfs(grav_pull, src))
+ if(!T || !isturf(loc))
+ continue
+ for(var/thing in T)
+ if(isturf(loc) && thing != src)
+ var/atom/movable/X = thing
+ X.singularity_pull(src, current_size)
+ CHECK_TICK
+
/obj/singularity/wizard/mapped/admin_investigate_setup()
return
+/obj/singularity/wizard/Bump(atom/A)
+ if(ismovableatom(A))
+ free(A)
+
+/obj/singularity/wizard/Bumped(atom/movable/AM)
+ free(AM)
+
+/obj/singularity/wizard/consume(atom/A)
+ if(ismovableatom(A))
+ free(A)
+
+/obj/singularity/wizard/proc/free(atom/movable/A)
+ if(!LAZYLEN(GLOB.destabliization_exits))
+ if(ismob(A))
+ to_chat(A, "There is no way out of this place...")
+ return
+ var/atom/return_thing = pick(GLOB.destabliization_exits)
+ var/turf/T = get_turf(return_thing)
+ if(!T)
+ return
+ A.forceMove(T)
+
/////////////////////////////////////////Scrying///////////////////
/obj/item/scrying
@@ -189,7 +223,7 @@
/obj/item/scrying/attack_self(mob/user)
visible_message("[user] stares into [src], their eyes glazing over.")
- user.ghostize(1)
+ user.ghostize(TRUE)
/////////////////////////////////////////Necromantic Stone///////////////////
@@ -264,12 +298,12 @@
H.dropItemToGround(I)
var/hat = pick(/obj/item/clothing/head/helmet/roman, /obj/item/clothing/head/helmet/roman/legionnaire)
- H.equip_to_slot_or_del(new hat(H), SLOT_HEAD)
- H.equip_to_slot_or_del(new /obj/item/clothing/under/costume/roman(H), SLOT_W_UNIFORM)
- H.equip_to_slot_or_del(new /obj/item/clothing/shoes/roman(H), SLOT_SHOES)
+ H.equip_to_slot_or_del(new hat(H), ITEM_SLOT_HEAD)
+ H.equip_to_slot_or_del(new /obj/item/clothing/under/costume/roman(H), ITEM_SLOT_ICLOTHING)
+ H.equip_to_slot_or_del(new /obj/item/clothing/shoes/roman(H), ITEM_SLOT_FEET)
H.put_in_hands(new /obj/item/shield/riot/roman(H), TRUE)
H.put_in_hands(new /obj/item/claymore(H), TRUE)
- H.equip_to_slot_or_del(new /obj/item/twohanded/spear(H), SLOT_BACK)
+ H.equip_to_slot_or_del(new /obj/item/spear(H), ITEM_SLOT_BACK)
/obj/item/voodoo
@@ -438,7 +472,7 @@
var/breakout = 0
while(breakout < 50)
var/turf/potential_T = find_safe_turf()
- if(T.z != potential_T.z || abs(get_dist_euclidian(potential_T,T)) > 50 - breakout)
+ if(T.get_virtual_z_level() != potential_T.get_virtual_z_level() || abs(get_dist_euclidian(potential_T,T)) > 50 - breakout)
do_teleport(user, potential_T, channel = TELEPORT_CHANNEL_MAGIC)
user.mobility_flags &= ~MOBILITY_MOVE
T = potential_T
diff --git a/code/modules/antagonists/wizard/equipment/soulstone.dm b/code/modules/antagonists/wizard/equipment/soulstone.dm
index 3ce0bf27d70ce..af5087b5c844f 100644
--- a/code/modules/antagonists/wizard/equipment/soulstone.dm
+++ b/code/modules/antagonists/wizard/equipment/soulstone.dm
@@ -60,6 +60,17 @@
A.death()
return ..()
+/obj/item/soulstone/Exited(mob/living/simple_animal/shade/S, atom/newLoc)
+ ..()
+ if(istype(S))
+ // Things that *really should always* happen to the shade when it comes out should go here.
+ S.status_flags &= ~GODMODE
+ S.mobility_flags = MOBILITY_FLAGS_DEFAULT
+ S.cancel_camera()
+ if(purified)
+ S.icon_state = "ghost1"
+ S.name = "Purified [initial(S.name)]"
+
/obj/item/soulstone/proc/hot_potato(mob/living/user)
to_chat(user, "Holy magics residing in \the [src] burn your hand!")
var/obj/item/bodypart/affecting = user.get_bodypart("[(user.active_hand_index % 2 == 0) ? "r" : "l" ]_arm")
@@ -80,6 +91,9 @@
return
if(!ishuman(M))//If target is not a human.
return ..()
+ if(M.mind && !M.mind.hasSoul)
+ to_chat(user, "That person has no soul!")
+ return
if(iscultist(M))
if(iscultist(user))
to_chat(user, "\"Come now, do not capture your bretheren's soul.\"")
@@ -106,14 +120,9 @@
/obj/item/soulstone/proc/release_shades(mob/user)
for(var/mob/living/simple_animal/shade/A in src)
- A.status_flags &= ~GODMODE
- A.mobility_flags = MOBILITY_FLAGS_DEFAULT
A.forceMove(get_turf(user))
- A.cancel_camera()
if(purified)
icon_state = "purified_soulstone"
- A.icon_state = "ghost1"
- A.name = "Purified [initial(A.name)]"
else
icon_state = "soulstone"
name = initial(name)
@@ -133,11 +142,11 @@
/obj/structure/constructshell/examine(mob/user)
. = ..()
if(iscultist(user) || iswizard(user) || user.stat == DEAD)
- . += {"A construct shell, used to house bound souls from a soulstone.\n
- Placing a soulstone with a soul into this shell allows you to produce your choice of the following:\n
- An Artificer, which can produce more shells and soulstones, as well as fortifications.\n
- A Wraith, which does high damage and can jaunt through walls, though it is quite fragile.\n
- A Juggernaut, which is very hard to kill and can produce temporary walls, but is slow."}
+ . += "A construct shell, used to house bound souls from a soulstone.\n"+\
+ "Placing a soulstone with a soul into this shell allows you to produce your choice of the following:\n"+\
+ "An Artificer, which can produce more shells and soulstones, as well as fortifications.\n"+\
+ "A Wraith, which does high damage and can jaunt through walls, though it is quite fragile.\n"+\
+ "A Juggernaut, which is very hard to kill and can produce temporary walls, but is slow."
/obj/structure/constructshell/attackby(obj/item/O, mob/user, params)
if(istype(O, /obj/item/soulstone))
@@ -191,6 +200,9 @@
to_chat(user, "Capture failed!: The soul has already fled its mortal frame. You attempt to bring it back...")
getCultGhost(T,user)
else
+ if(old_shard) //no insta cremating on the spot
+ to_chat(user, "Capture failed!: The old shard is not powerful enough to absorb the soul of this being.")
+ return FALSE
for(var/obj/item/W in T)
T.dropItemToGround(W)
init_shade(T, user, vic = 1)
@@ -270,7 +282,7 @@
var/datum/action/innate/seek_master/SM = new()
SM.Grant(newstruct)
newstruct.key = target.key
- var/obj/screen/alert/bloodsense/BS
+ var/atom/movable/screen/alert/bloodsense/BS
if(newstruct.mind && ((stoner && iscultist(stoner)) || cultoverride) && SSticker && SSticker.mode)
SSticker.mode.add_cultist(newstruct.mind, 0)
if(iscultist(stoner) || cultoverride)
@@ -278,7 +290,7 @@
else if(stoner)
to_chat(newstruct, "You are still bound to serve your creator, [stoner], follow [stoner.p_their()] orders and help [stoner.p_them()] complete [stoner.p_their()] goals at all costs.")
newstruct.clear_alert("bloodsense")
- BS = newstruct.throw_alert("bloodsense", /obj/screen/alert/bloodsense)
+ BS = newstruct.throw_alert("bloodsense", /atom/movable/screen/alert/bloodsense)
if(BS)
BS.Cviewer = newstruct
newstruct.cancel_camera()
diff --git a/code/modules/antagonists/wizard/equipment/spellbook.dm b/code/modules/antagonists/wizard/equipment/spellbook.dm
index b1502e44d3093..baf7719cb5013 100644
--- a/code/modules/antagonists/wizard/equipment/spellbook.dm
+++ b/code/modules/antagonists/wizard/equipment/spellbook.dm
@@ -153,7 +153,7 @@
/datum/spellbook_entry/blind
name = "Blind"
- spell_type = /obj/effect/proc_holder/spell/pointed/trigger/blind
+ spell_type = /obj/effect/proc_holder/spell/targeted/blind
cost = 1
/datum/spellbook_entry/mindswap
@@ -389,7 +389,7 @@
name = "Bottle of Blood"
desc = "A bottle of magically infused blood, the smell of which will attract extradimensional beings when broken. Be careful though, the kinds of creatures summoned by blood magic are indiscriminate in their killing, and you yourself may become a victim."
item_path = /obj/item/antag_spawner/slaughter_demon
- limit = 3
+ limit = 1
category = "Assistance"
/datum/spellbook_entry/item/hugbottle
@@ -403,18 +403,18 @@
destructive."
item_path = /obj/item/antag_spawner/slaughter_demon/laughter
cost = 1 //non-destructive; it's just a jape, sibling!
- limit = 3
+ limit = 1
category = "Assistance"
/datum/spellbook_entry/item/mjolnir
name = "Mjolnir"
desc = "A mighty hammer on loan from Thor, God of Thunder. It crackles with barely contained power."
- item_path = /obj/item/twohanded/mjollnir
+ item_path = /obj/item/mjollnir
/datum/spellbook_entry/item/singularity_hammer
name = "Singularity Hammer"
desc = "A hammer that creates an intensely powerful field of gravity where it strikes, pulling everything nearby to the point of impact."
- item_path = /obj/item/twohanded/singularityhammer
+ item_path = /obj/item/singularityhammer
/datum/spellbook_entry/item/battlemage
name = "Battlemage Armour"
@@ -437,6 +437,9 @@
category = "Mobility"
cost = 1
+/// How much threat we need to let these rituals happen on dynamic
+#define MINIMUM_THREAT_FOR_RITUALS 100
+
/datum/spellbook_entry/summon
name = "Summon Stuff"
category = "Rituals"
@@ -464,12 +467,6 @@
desc = "Spook the crew out by making them see dead people. Be warned, ghosts are capricious and occasionally vindicative, and some will use their incredibly minor abilities to frustrate you."
cost = 0
-/datum/spellbook_entry/summon/ghosts/IsAvailable()
- if(!SSticker.mode)
- return FALSE
- else
- return TRUE
-
/datum/spellbook_entry/summon/ghosts/Buy(mob/living/carbon/human/user, obj/item/spellbook/book)
SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name)
new /datum/round_event/wizard/ghost()
@@ -485,6 +482,10 @@
/datum/spellbook_entry/summon/guns/IsAvailable()
if(!SSticker.mode) // In case spellbook is placed on map
return FALSE
+ if(istype(SSticker.mode, /datum/game_mode/dynamic)) // Disable events on dynamic
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ if(mode.threat_level < MINIMUM_THREAT_FOR_RITUALS)
+ return FALSE
return !CONFIG_GET(flag/no_summon_guns)
/datum/spellbook_entry/summon/guns/Buy(mob/living/carbon/human/user,obj/item/spellbook/book)
@@ -502,6 +503,10 @@
/datum/spellbook_entry/summon/magic/IsAvailable()
if(!SSticker.mode) // In case spellbook is placed on map
return FALSE
+ if(istype(SSticker.mode, /datum/game_mode/dynamic)) // Disable events on dynamic
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ if(mode.threat_level < MINIMUM_THREAT_FOR_RITUALS)
+ return FALSE
return !CONFIG_GET(flag/no_summon_magic)
/datum/spellbook_entry/summon/magic/Buy(mob/living/carbon/human/user,obj/item/spellbook/book)
@@ -520,6 +525,10 @@
/datum/spellbook_entry/summon/events/IsAvailable()
if(!SSticker.mode) // In case spellbook is placed on map
return FALSE
+ if(istype(SSticker.mode, /datum/game_mode/dynamic)) // Disable events on dynamic
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ if(mode.threat_level < MINIMUM_THREAT_FOR_RITUALS)
+ return FALSE
return !CONFIG_GET(flag/no_summon_events)
/datum/spellbook_entry/summon/events/Buy(mob/living/carbon/human/user,obj/item/spellbook/book)
@@ -552,6 +561,8 @@
playsound(user, 'sound/magic/mandswap.ogg', 50, 1)
return TRUE
+#undef MINIMUM_THREAT_FOR_RITUALS
+
/obj/item/spellbook
name = "spell book"
desc = "An unearthly tome that glows with power."
diff --git a/code/modules/antagonists/wizard/wizard.dm b/code/modules/antagonists/wizard/wizard.dm
index 16092be475858..2d227758fcffb 100644
--- a/code/modules/antagonists/wizard/wizard.dm
+++ b/code/modules/antagonists/wizard/wizard.dm
@@ -4,6 +4,7 @@
antagpanel_category = "Wizard"
job_rank = ROLE_WIZARD
antag_moodlet = /datum/mood_event/focused
+ hijack_speed = 0.5
var/strip = TRUE //strip before equipping
var/allow_rename = TRUE
var/hud_version = "wizard"
@@ -11,7 +12,6 @@
var/move_to_lair = TRUE
var/outfit_type = /datum/outfit/wizard
var/wiz_age = WIZARD_AGE_MIN /* Wizards by nature cannot be too young. */
- can_hijack = HIJACK_HIJACKER
show_to_ghosts = TRUE
/datum/antagonist/wizard/on_gain()
@@ -142,7 +142,9 @@
to_chat(owner, "You will find a list of available spells in your spell book. Choose your magic arsenal carefully.")
to_chat(owner, "The spellbook is bound to you, and others cannot use it.")
to_chat(owner, "In your pockets you will find a teleport scroll. Use it as needed.")
- to_chat(owner,"Remember: do not forget to prepare your spells.")
+ to_chat(owner,"Remember: Do not forget to prepare your spells.")
+ owner.current.client?.tgui_panel?.give_antagonist_popup("Space Wizard",
+ "Prepare your spells and cause havok upon the accursed station.")
/datum/antagonist/wizard/farewell()
to_chat(owner, "You have been brainwashed! You are no longer a wizard!")
@@ -247,17 +249,17 @@
if(!istype(master_mob) || !istype(H))
return
if(master_mob.ears)
- H.equip_to_slot_or_del(new master_mob.ears.type, SLOT_EARS)
+ H.equip_to_slot_or_del(new master_mob.ears.type, ITEM_SLOT_EARS)
if(master_mob.w_uniform)
- H.equip_to_slot_or_del(new master_mob.w_uniform.type, SLOT_W_UNIFORM)
+ H.equip_to_slot_or_del(new master_mob.w_uniform.type, ITEM_SLOT_ICLOTHING)
if(master_mob.shoes)
- H.equip_to_slot_or_del(new master_mob.shoes.type, SLOT_SHOES)
+ H.equip_to_slot_or_del(new master_mob.shoes.type, ITEM_SLOT_FEET)
if(master_mob.wear_suit)
- H.equip_to_slot_or_del(new master_mob.wear_suit.type, SLOT_WEAR_SUIT)
+ H.equip_to_slot_or_del(new master_mob.wear_suit.type, ITEM_SLOT_OCLOTHING)
if(master_mob.head)
- H.equip_to_slot_or_del(new master_mob.head.type, SLOT_HEAD)
+ H.equip_to_slot_or_del(new master_mob.head.type, ITEM_SLOT_HEAD)
if(master_mob.back)
- H.equip_to_slot_or_del(new master_mob.back.type, SLOT_BACK)
+ H.equip_to_slot_or_del(new master_mob.back.type, ITEM_SLOT_BACK)
//Operation: Fuck off and scare people
owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/area_teleport/teleport(null))
diff --git a/code/modules/aquarium/aquarium.dm b/code/modules/aquarium/aquarium.dm
new file mode 100644
index 0000000000000..18db12d3620b8
--- /dev/null
+++ b/code/modules/aquarium/aquarium.dm
@@ -0,0 +1,255 @@
+#define AQUARIUM_LAYER_STEP 0.01
+/// Aquarium content layer offsets
+#define AQUARIUM_MIN_OFFSET 0.01
+#define AQUARIUM_MAX_OFFSET 1
+
+/obj/structure/aquarium
+ name = "aquarium"
+ density = TRUE
+ anchored = TRUE
+
+ icon = 'icons/obj/aquarium.dmi'
+ icon_state = "aquarium_base"
+
+ integrity_failure = 0.3
+
+ var/fluid_type = AQUARIUM_FLUID_FRESHWATER
+ var/fluid_temp = DEFAULT_AQUARIUM_TEMP
+ var/min_fluid_temp = MIN_AQUARIUM_TEMP
+ var/max_fluid_temp = MAX_AQUARIUM_TEMP
+ var/allow_breeding = FALSE
+
+ var/glass_icon_state = "aquarium_glass"
+ var/broken_glass_icon_state = "aquarium_glass_broken"
+
+ //This is the area where fish can swim
+ var/aquarium_zone_min_px = 2
+ var/aquarium_zone_max_px = 31
+ var/aquarium_zone_min_py = 10
+ var/aquarium_zone_max_py = 24
+
+ var/list/fluid_types = list(AQUARIUM_FLUID_SALTWATER, AQUARIUM_FLUID_FRESHWATER, AQUARIUM_FLUID_SULPHWATEVER, AQUARIUM_FLUID_AIR)
+
+ var/panel_open = TRUE
+
+ ///Current layers in use by aquarium contents
+ var/list/used_layers = list()
+
+ var/alive_fish = 0
+ var/dead_fish = 0
+
+/obj/structure/aquarium/Initialize()
+ . = ..()
+ update_icon()
+ RegisterSignal(src,COMSIG_PARENT_ATTACKBY, .proc/feed_feedback)
+
+
+/obj/structure/aquarium/proc/request_layer(layer_type)
+ /**
+ * base aq layer
+ * min_offset = this value is returned on bottom layer mode
+ * min_offset + 0.1 fish1
+ * min_offset + 0.2 fish2
+ * ... these layers are returned for auto layer mode and tracked by used_layers
+ * min_offset + max_offset = this value is returned for top layer mode
+ * min_offset + max_offset + 1 = this is used for glass overlay
+ */
+ //optional todo: hook up sending surface changed on aquarium changing layers
+ switch(layer_type)
+ if(AQUARIUM_LAYER_MODE_BOTTOM)
+ return layer + AQUARIUM_MIN_OFFSET
+ if(AQUARIUM_LAYER_MODE_TOP)
+ return layer + AQUARIUM_MAX_OFFSET
+ if(AQUARIUM_LAYER_MODE_AUTO)
+ var/chosen_layer = layer + AQUARIUM_MIN_OFFSET + AQUARIUM_LAYER_STEP
+ while((chosen_layer in used_layers) && (chosen_layer <= layer + AQUARIUM_MAX_OFFSET))
+ chosen_layer += AQUARIUM_LAYER_STEP
+ used_layers += chosen_layer
+ return chosen_layer
+
+/obj/structure/aquarium/proc/free_layer(value)
+ used_layers -= value
+
+/obj/structure/aquarium/proc/get_surface_properties()
+ . = list()
+ .[AQUARIUM_PROPERTIES_PX_MIN] = aquarium_zone_min_px
+ .[AQUARIUM_PROPERTIES_PX_MAX] = aquarium_zone_max_px
+ .[AQUARIUM_PROPERTIES_PY_MIN] = aquarium_zone_min_py
+ .[AQUARIUM_PROPERTIES_PY_MAX] = aquarium_zone_max_py
+
+/obj/structure/aquarium/update_overlays()
+ . = ..()
+ if(panel_open)
+ . += "panel"
+
+ //Glass overlay goes on top of everything else.
+ var/mutable_appearance/glass_overlay = mutable_appearance(icon,broken ? broken_glass_icon_state : glass_icon_state,layer=AQUARIUM_MAX_OFFSET-1)
+ . += glass_overlay
+
+/obj/structure/aquarium/examine(mob/user)
+ . = ..()
+ . += "Alt-click to [panel_open ? "close" : "open"] the control panel."
+
+/obj/structure/aquarium/AltClick(mob/user)
+ if(!user.canUseTopic(src, BE_CLOSE))
+ return ..()
+ panel_open = !panel_open
+ update_icon()
+
+/obj/structure/aquarium/wrench_act(mob/living/user, obj/item/I)
+ if(default_unfasten_wrench(user,I))
+ return TRUE
+
+/obj/structure/aquarium/attackby(obj/item/I, mob/living/user, params)
+ if(broken)
+ var/obj/item/stack/sheet/glass/glass = I
+ if(istype(glass))
+ if(glass.get_amount() < 2)
+ to_chat(user, "You need two glass sheets to fix the case!")
+ return
+ to_chat(user, "You start fixing [src]...")
+ if(do_after(user, 2 SECONDS, target = src))
+ glass.use(2)
+ broken = FALSE
+ obj_integrity = max_integrity
+ update_icon()
+ return TRUE
+ else
+ // This signal exists so we common items instead of adding component on init can just register creation of one in response.
+ // This way we can avoid the cost of 9999 aquarium components on rocks that will never see water in their life.
+ SEND_SIGNAL(I,COMSIG_AQUARIUM_BEFORE_INSERT_CHECK,src)
+ var/datum/component/aquarium_content/content_component = I.GetComponent(/datum/component/aquarium_content)
+ if(content_component && content_component.is_ready_to_insert(src))
+ if(user.transferItemToLoc(I,src))
+ update_icon()
+ return TRUE
+ else
+ return ..()
+ return ..()
+
+/obj/structure/aquarium/proc/feed_feedback(datum/source, obj/item/thing, mob/user, params)
+ SIGNAL_HANDLER
+ if(istype(thing, /obj/item/fish_feed))
+ to_chat(user,"You feed the fish.")
+ return NONE
+
+/obj/structure/aquarium/interact(mob/user)
+ if(!broken && user.pulling && user.a_intent == INTENT_GRAB && isliving(user.pulling))
+ var/mob/living/living_pulled = user.pulling
+ SEND_SIGNAL(living_pulled, COMSIG_AQUARIUM_BEFORE_INSERT_CHECK,src)
+ var/datum/component/aquarium_content/content_component = living_pulled.GetComponent(/datum/component/aquarium_content)
+ if(content_component && content_component.is_ready_to_insert(src))
+ try_to_put_mob_in(user)
+ else if(panel_open)
+ . = ..() //call base ui_interact
+ else
+ admire(user)
+
+/// Tries to put mob pulled by the user in the aquarium after a delay
+/obj/structure/aquarium/proc/try_to_put_mob_in(mob/user)
+ if(user.pulling && user.a_intent == INTENT_GRAB && isliving(user.pulling))
+ var/mob/living/living_pulled = user.pulling
+ if(living_pulled.buckled || living_pulled.has_buckled_mobs())
+ to_chat(user, "[living_pulled] is attached to something!")
+ return
+ user.visible_message("[user] starts to put [living_pulled] into [src]!")
+ if(do_after(user, 10 SECONDS, target = src))
+ if(QDELETED(living_pulled) || user.pulling != living_pulled || living_pulled.buckled || living_pulled.has_buckled_mobs())
+ return
+ var/datum/component/aquarium_content/content_component = living_pulled.GetComponent(/datum/component/aquarium_content)
+ if(content_component || content_component.is_ready_to_insert(src))
+ return
+ user.visible_message("[user] stuffs [living_pulled] into [src]!")
+ living_pulled.forceMove(src)
+ update_icon()
+
+///Apply mood bonus depending on aquarium status
+/obj/structure/aquarium/proc/admire(mob/user)
+ to_chat(user,"You take a moment to watch [src].")
+ if(do_after(user, 5 SECONDS, target = src))
+ //Check if there are live fish - good mood
+ //All fish dead - bad mood.
+ //No fish - nothing.
+ if(alive_fish > 0)
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "aquarium", /datum/mood_event/aquarium_positive)
+ else if(dead_fish > 0)
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "aquarium", /datum/mood_event/aquarium_negative)
+ // Could maybe scale power of this mood with number/types of fish
+
+/obj/structure/aquarium/ui_data(mob/user)
+ . = ..()
+ .["fluid_type"] = fluid_type
+ .["temperature"] = fluid_temp
+ .["allow_breeding"] = allow_breeding
+ var/list/content_data = list()
+ for(var/atom/movable/fish in contents)
+ content_data += list(list("name"=fish.name,"ref"=ref(fish)))
+ .["contents"] = content_data
+
+/obj/structure/aquarium/ui_static_data(mob/user)
+ . = ..()
+ //I guess these should depend on the fluid so lava critters can get high or stuff below water freezing point but let's keep it simple for now.
+ .["minTemperature"] = min_fluid_temp
+ .["maxTemperature"] = max_fluid_temp
+ .["fluidTypes"] = fluid_types
+
+/obj/structure/aquarium/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+ var/mob/user = usr
+ switch(action)
+ if("temperature")
+ var/temperature = params["temperature"]
+ if(isnum(temperature))
+ fluid_temp = clamp(temperature, min_fluid_temp, max_fluid_temp)
+ . = TRUE
+ if("fluid")
+ if(params["fluid"] in fluid_types)
+ fluid_type = params["fluid"]
+ SEND_SIGNAL(src, COMSIG_AQUARIUM_FLUID_CHANGED, fluid_type)
+ . = TRUE
+ if("allow_breeding")
+ allow_breeding = !allow_breeding
+ . = TRUE
+ if("remove")
+ var/atom/movable/inside = locate(params["ref"]) in contents
+ if(inside)
+ if(isitem(inside))
+ user.put_in_hands(inside)
+ else
+ inside.forceMove(get_turf(src))
+ to_chat(user,"You take out [inside] from [src].")
+
+/obj/structure/aquarium/ui_interact(mob/user, datum/tgui/ui)
+ . = ..()
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Aquarium", name)
+ ui.open()
+
+/obj/structure/aquarium/obj_break(damage_flag)
+ . = ..()
+ if(!broken)
+ aquarium_smash()
+
+/obj/structure/aquarium/proc/aquarium_smash()
+ broken = TRUE
+ var/possible_destinations_for_fish = list()
+ var/droploc = drop_location()
+ if(isturf(droploc))
+ possible_destinations_for_fish = get_adjacent_open_turfs(droploc)
+ else
+ possible_destinations_for_fish = list(droploc)
+ playsound(src, 'sound/effects/glassbr3.ogg', 100, TRUE)
+ for(var/atom/movable/fish in contents)
+ fish.forceMove(pick(possible_destinations_for_fish))
+ if(fluid_type != AQUARIUM_FLUID_AIR)
+ var/datum/reagents/reagent_splash = new()
+ reagent_splash.add_reagent(/datum/reagent/water, 30)
+ chem_splash(droploc, 3, list(reagent_splash))
+ update_icon()
+
+#undef AQUARIUM_LAYER_STEP
+#undef AQUARIUM_MIN_OFFSET
+#undef AQUARIUM_MAX_OFFSET
diff --git a/code/modules/aquarium/aquarium_behaviour.dm b/code/modules/aquarium/aquarium_behaviour.dm
new file mode 100644
index 0000000000000..4a7145473f13e
--- /dev/null
+++ b/code/modules/aquarium/aquarium_behaviour.dm
@@ -0,0 +1,301 @@
+//Fish breeding stops if fish count exceeds this.
+#define AQUARIUM_MAX_BREEDING_POPULATION 20
+
+// Configuration objects defining prop/fish behaviour in the aquarium
+// These are used as a base for autogenerating the actual instances
+/datum/aquarium_behaviour
+ var/name = "base aquarium element"
+ var/desc = ""
+
+ var/unique = FALSE
+
+ var/icon = 'icons/obj/aquarium.dmi'
+ /// Icon state used for catalog/autogenerated fish item. Also used as basis for in aquarium visual if dedicated_in_aquarium_icon_state is not set.
+ var/icon_state
+ /// Applied and item/catalog for use with greyscaled icons.
+ var/color
+
+ /// How the thing will be layered
+ var/layer_mode = AQUARIUM_LAYER_MODE_AUTO
+ /// If the starting position is randomised within bounds.
+ var/randomize_position = FALSE
+
+ /**
+ * Fish sprite how to:
+ * Need to be centered on 16,16 in the dmi and facing left by default.
+ * sprite_height/sprite_width is the size it will have in aquarium and used to control animation boundaries.
+ * source_height/source_width is the size of the original icon (ideally only the non-empty parts)
+ */
+
+ /// If this is set this icon state will be used for the holder while icon_state will only be used for item/catalog. Transformation from source_width/height WON'T be applied.
+ var/dedicated_in_aquarium_icon_state
+ /// Applied to vc object only for use with greyscaled icons.
+ var/aquarium_vc_color
+
+ //Target sprite size for path/position calculations.
+ var/sprite_height = 3
+ var/sprite_width = 3
+ //This is the size of the source sprite. This will be used to calculate scale down factor.
+ var/source_width = 32
+ var/source_height = 32
+
+ /// Current animation
+ var/current_animation
+
+ /// Does this behviour need additional processing in aquarium, will be added to SSobj processing on insertion
+ var/processing = FALSE
+
+ /// Our holder component
+ var/datum/component/aquarium_content/parent
+
+/// Applies icon,color and base scaling to our visual holder
+/datum/aquarium_behaviour/proc/apply_appearance(obj/effect/holder)
+ holder.icon = icon
+ if(dedicated_in_aquarium_icon_state)
+ holder.icon_state = dedicated_in_aquarium_icon_state
+ else
+ holder.icon_state = icon_state
+ holder.transform = base_transform()
+ if(aquarium_vc_color)
+ holder.color = aquarium_vc_color
+
+// Generates scaling matrix for our visual
+/datum/aquarium_behaviour/proc/base_transform()
+ var/matrix/matrix = matrix()
+ if(!dedicated_in_aquarium_icon_state)
+ var/x_scale = sprite_width / source_width
+ var/y_scale = sprite_height / source_height
+ matrix.Scale(x_scale, y_scale)
+ return matrix
+
+/// Called when fed.
+/datum/aquarium_behaviour/proc/on_feeding(datum/reagents/feed_reagents)
+ return
+
+/// Called when aquarium fluid changes.
+/datum/aquarium_behaviour/proc/on_fluid_changed()
+ return
+
+/// Called to update current animation based on state
+/datum/aquarium_behaviour/proc/update_animation()
+ return
+
+/// Called when inserted into new aquarium
+/datum/aquarium_behaviour/proc/on_inserted()
+ if(processing)
+ START_PROCESSING(SSobj, src)
+
+/// Called just before the object gets removed from the aquarium
+/datum/aquarium_behaviour/proc/before_removal()
+ return
+
+/datum/aquarium_behaviour/Destroy(force, ...)
+ STOP_PROCESSING(SSobj, src)
+ parent = null
+ return ..()
+
+/datum/aquarium_behaviour/prop
+ name = "aquarium prop"
+ unique = TRUE
+ layer_mode = AQUARIUM_LAYER_MODE_BOTTOM
+
+ // Prop sprites do not need any scaling they go 1:1
+ sprite_width = 32
+ sprite_height = 32
+ source_width = 32
+ source_height = 32
+
+/datum/aquarium_behaviour/prop/rocks
+ name = "rocks"
+ icon_state = "rocks"
+
+/datum/aquarium_behaviour/prop/seaweed_top
+ name = "dense seaweeds"
+ icon_state = "seaweeds_front"
+ layer_mode = AQUARIUM_LAYER_MODE_TOP
+
+/datum/aquarium_behaviour/prop/seaweed
+ name = "seaweeds"
+ icon_state = "seaweeds_back"
+ layer_mode = AQUARIUM_LAYER_MODE_BOTTOM
+
+/datum/aquarium_behaviour/prop/rockfloor
+ name = "rock floor"
+ icon_state = "rockfloor"
+ layer_mode = AQUARIUM_LAYER_MODE_BOTTOM
+
+/datum/aquarium_behaviour/prop/treasure
+ name = "tiny treasure chest"
+ icon_state = "treasure"
+ layer_mode = AQUARIUM_LAYER_MODE_BOTTOM
+
+/datum/aquarium_behaviour/fish
+ name = "generic fish"
+ icon_state = "fish_greyscale"
+ randomize_position = TRUE //We have random starting point of our swim path
+
+ current_animation = AQUARIUM_ANIMATION_FISH_SWIM
+ processing = TRUE
+
+ // Default size of the "greyscale_fish" icon_state
+ sprite_height = 3
+ sprite_width = 3
+
+ /// Required fluid type for this fish to live.
+ var/required_fluid_type = AQUARIUM_FLUID_FRESHWATER
+
+ /// Required minimum temperature for the fish to live.
+ var/required_temperature_min = MIN_AQUARIUM_TEMP
+ /// Maximum possible temperature for the fish to live.
+ var/required_temperature_max = MAX_AQUARIUM_TEMP
+
+ /// What type of reagent this fish needs to be fed.
+ var/food = /datum/reagent/consumable/nutriment
+ /// How often the fish needs to be fed
+ var/feeding_frequency = 5 MINUTES
+ /// Time of last feedeing
+ var/last_feeding
+
+ /// Fish status
+ var/status = FISH_ALIVE
+
+ /// Current fish health. Dies at 0.
+ var/health = 100
+
+ /// Should this fish type show in fish catalog
+ var/show_in_catalog = TRUE
+ /// Should this fish spawn in random fish cases
+ var/availible_in_random_cases = TRUE
+ /// How rare this fish is in the random cases
+ var/random_case_rarity = FISH_RARITY_BASIC
+
+ /// Fish autogenerated from this behaviour will be processable into this
+ var/fillet_type = /obj/item/reagent_containers/food/snacks/carpmeat/icantbeliveitsnotcarp
+
+ /// Won't breed more than this amount in single aquarium.
+ var/stable_population = 1
+ /// Last time new fish was created
+ var/last_breeding
+ /// How long it takes to produce new fish
+ var/breeding_timeout = 2 MINUTES
+
+/datum/aquarium_behaviour/fish/on_inserted()
+ . = ..()
+ if(isnull(last_feeding)) //Fish start fed.
+ last_feeding = world.time
+ if(status == FISH_DEAD)
+ parent.current_aquarium.dead_fish += 1
+ else
+ parent.current_aquarium.alive_fish += 1
+ parent.stop_flopping()
+
+/datum/aquarium_behaviour/fish/before_removal()
+ . = ..()
+ if(status == FISH_DEAD)
+ parent.current_aquarium.dead_fish -= 1
+ else
+ parent.current_aquarium.alive_fish -= 1
+ //We do not stop processing properties here. We want fish to die outside of aquariums after first insert. We only stop processing in properties.death or destroy
+ if(!QDELETED(parent) && status != FISH_DEAD)
+ parent.start_flopping()
+
+/datum/aquarium_behaviour/fish/on_fluid_changed()
+ //In case we'll flop to bottom from this or go back to swimming.
+ update_animation()
+
+/// Updates our animation variable based on state and prompts component to animate it.
+/datum/aquarium_behaviour/fish/update_animation()
+ var/obj/structure/aquarium/aquarium = parent.current_aquarium
+ var/old_animation = current_animation
+ if(!aquarium || aquarium.fluid_type == AQUARIUM_FLUID_AIR || status == FISH_DEAD)
+ current_animation = AQUARIUM_ANIMATION_FISH_DEAD
+ else
+ current_animation = AQUARIUM_ANIMATION_FISH_SWIM
+ if(aquarium && old_animation != current_animation)
+ parent.generate_animation()
+
+/datum/aquarium_behaviour/fish/on_feeding(datum/reagents/feed_reagents)
+ if(feed_reagents.has_reagent(food))
+ last_feeding = world.time
+
+/// Checks if our current environment lets us live.
+/datum/aquarium_behaviour/fish/proc/proper_environment()
+ var/obj/structure/aquarium/aquarium = parent.current_aquarium
+ if(!aquarium)
+ return FALSE
+ if(aquarium.fluid_type != required_fluid_type)
+ return FALSE
+ if(aquarium.fluid_temp < required_temperature_min || aquarium.fluid_temp > required_temperature_max)
+ return FALSE
+ return TRUE
+
+/datum/aquarium_behaviour/fish/process(delta_time)
+ set waitfor = FALSE
+
+ var/health_change_per_second = 0
+ if(!proper_environment())
+ health_change_per_second -= 3 //Dying here
+ if(world.time - last_feeding >= feeding_frequency)
+ health_change_per_second -= 0.5 //Starving
+ else
+ health_change_per_second += 0.5 //Slowly healing
+ health = clamp(health + health_change_per_second * delta_time, 0, initial(health))
+ if(health <= 0)
+ death()
+ else if(ready_to_reproduce())
+ try_to_reproduce()
+
+/datum/aquarium_behaviour/fish/proc/death()
+ STOP_PROCESSING(SSobj, src)
+ status = FISH_DEAD
+ var/message = "\The [name] dies."
+ if(parent.current_aquarium)
+ parent.current_aquarium.visible_message(message)
+ parent.current_aquarium.alive_fish -= 1
+ parent.current_aquarium.dead_fish += 1
+ else
+ var/atom/movable/AM = parent.parent
+ AM.visible_message(message)
+ parent.stop_flopping()
+ update_animation()
+
+/datum/aquarium_behaviour/fish/proc/ready_to_reproduce()
+ return parent.current_aquarium && parent.current_aquarium.allow_breeding && health == initial(health) && stable_population > 1 && world.time - last_breeding >= breeding_timeout
+
+/datum/aquarium_behaviour/fish/proc/try_to_reproduce()
+ if(!parent.current_aquarium)
+ return
+ if(parent.current_aquarium.alive_fish + parent.current_aquarium.dead_fish >= AQUARIUM_MAX_BREEDING_POPULATION) //so aquariums full of fish don't need to do these expensive checks
+ return
+ var/list/other_fish_of_same_type = list()
+ for(var/atom/movable/fish_or_prop in parent.current_aquarium)
+ if(fish_or_prop == parent.parent)
+ continue
+ var/datum/component/aquarium_content/other_content = fish_or_prop.GetComponent(/datum/component/aquarium_content)
+ if(other_content && other_content.properties.type == type)
+ other_fish_of_same_type += other_content.properties
+ if(length(other_fish_of_same_type) >= stable_population)
+ return
+ var/datum/aquarium_behaviour/fish/second_fish
+ for(var/datum/aquarium_behaviour/fish/other_fish in other_fish_of_same_type)
+ if(other_fish.ready_to_reproduce())
+ second_fish = other_fish
+ break
+ if(second_fish)
+ generate_fish(parent.current_aquarium,type)
+ last_breeding = world.time
+ second_fish.last_breeding = world.time
+
+/// This path exists mostly for admin abuse.
+/datum/aquarium_behaviour/fish/auto
+ name = "automatic fish"
+ desc = "generates fish appearance automatically from component parent appearance"
+ availible_in_random_cases = FALSE
+ sprite_width = 8
+ sprite_height = 8
+ show_in_catalog = FALSE
+
+/datum/aquarium_behaviour/fish/auto/apply_appearance(obj/effect/holder)
+ holder.appearance = parent.parent
+ holder.transform = base_transform()
+ holder.dir = WEST
diff --git a/code/modules/aquarium/fish.dm b/code/modules/aquarium/fish.dm
new file mode 100644
index 0000000000000..fd75157762bf7
--- /dev/null
+++ b/code/modules/aquarium/fish.dm
@@ -0,0 +1,177 @@
+// Fish path used for autogenerated fish
+/obj/item/fish
+ name = "generic looking aquarium fish"
+ desc = "very bland"
+ w_class = WEIGHT_CLASS_TINY
+
+/// Automatically generates object of given base path from the behaviour type in loc
+/proc/generate_fish(loc,behaviour_type,base_path=/obj/item/fish)
+ var/datum/aquarium_behaviour/behaviour = behaviour_type
+ var/obj/item/fish = new base_path(loc)
+ fish.name = initial(behaviour.name)
+ fish.icon = initial(behaviour.icon)
+ fish.icon_state = initial(behaviour.icon_state)
+ fish.desc = initial(behaviour.desc)
+ if(initial(behaviour.color))
+ fish.add_atom_colour(initial(behaviour.color), FIXED_COLOUR_PRIORITY)
+ fish.AddElement(/datum/element/deferred_aquarium_content, behaviour_type)
+ return fish
+
+/// Returns random fish, using random_case_rarity probabilities.
+/proc/random_fish_type(case_fish_only=TRUE, required_fluid)
+ var/static/probability_table
+ var/argkey = "fish_[required_fluid]_[case_fish_only]" //If this expands more extract bespoke element arg generation to some common helper.
+ if(!probability_table || !probability_table[argkey])
+ if(!probability_table)
+ probability_table = list()
+ var/chance_table = list()
+ for(var/_fish_behavior in subtypesof(/datum/aquarium_behaviour/fish))
+ var/datum/aquarium_behaviour/fish/fish_behavior = _fish_behavior
+ if(required_fluid && initial(fish_behavior.required_fluid_type) != required_fluid)
+ continue
+ if(initial(fish_behavior.availible_in_random_cases) || !case_fish_only)
+ chance_table[fish_behavior] = initial(fish_behavior.random_case_rarity)
+ probability_table[argkey] = chance_table
+ return pickweight(probability_table[argkey])
+
+// Actual fish definitions below - there's no specific paths, they are autogenerated from behaviours
+
+// Freshwater fish
+
+/datum/aquarium_behaviour/fish/goldfish
+ name = "goldfish"
+ desc = "Despite common belief, goldfish do not have three-second memories. They can actually remember things that happened up to three months ago."
+ icon_state = "goldfish"
+ sprite_width = 8
+ sprite_height = 8
+ stable_population = 3
+
+/datum/aquarium_behaviour/fish/angelfish
+ name = "angelfish"
+ desc = "Young Angelfish often live in groups, while adults prefer solitary life. They become territorial and aggressive toward other fish when they reach adulthood."
+ icon_state = "angelfish"
+ dedicated_in_aquarium_icon_state = "bigfish"
+ sprite_height = 7
+ source_height = 7
+ stable_population = 3
+
+/datum/aquarium_behaviour/fish/guppy
+ name = "guppy"
+ desc = "Guppy is also known as rainbow fish because of the brightly colored body and fins."
+ icon_state = "guppy"
+ dedicated_in_aquarium_icon_state = "fish_greyscale"
+ aquarium_vc_color = "#91AE64"
+ sprite_width = 8
+ sprite_height = 5
+ stable_population = 6
+
+/datum/aquarium_behaviour/fish/plasmatetra
+ name = "plasma tetra"
+ desc = "Due to their small size, tetras are prey to many predators in their watery world, including eels, crustaceans, and invertebrates."
+ icon_state = "plastetra"
+ dedicated_in_aquarium_icon_state = "fish_greyscale"
+ aquarium_vc_color = "#D30EB0"
+ stable_population = 3
+
+/datum/aquarium_behaviour/fish/catfish
+ name = "cory catfish"
+ desc = "A catfish has about 100,000 taste buds, and their bodies are covered with them to help detect chemicals present in the water and also to respond to touch."
+ icon_state = "catfish"
+ dedicated_in_aquarium_icon_state = "fish_greyscale"
+ aquarium_vc_color = "#907420"
+ stable_population = 3
+
+/datum/aquarium_behaviour/fish/spacecarp
+ name = "space carp"
+ desc = "This space predator fish is known to cause yearly damage to space stations during their migrations."
+ icon_state = "carp"
+ sprite_width = 8
+ sprite_height = 8
+ availible_in_random_cases = FALSE
+
+// Saltwater fish below
+
+/datum/aquarium_behaviour/fish/clownfish
+ name = "clownfish"
+ desc = "Clownfish catch prey by swimming onto the reef, attracting larger fish, and luring them back to the anemone. The anemone will sting and eat the larger fish, leaving the remains for the clownfish."
+ icon_state = "clownfish"
+ dedicated_in_aquarium_icon_state = "clownfish_small"
+ required_fluid_type = AQUARIUM_FLUID_SALTWATER
+ sprite_width = 8
+ sprite_height = 5
+ stable_population = 4
+
+/datum/aquarium_behaviour/fish/cardinal
+ name = "cardinalfish"
+ desc = "Cardinalfish are often found near sea urchins, where the fish hide when threatened."
+ icon_state = "cardinalfish"
+ dedicated_in_aquarium_icon_state = "fish_greyscale"
+ required_fluid_type = AQUARIUM_FLUID_SALTWATER
+ stable_population = 4
+
+/datum/aquarium_behaviour/fish/greenchromis
+ name = "green chromis"
+ desc = "The Chromis can vary in color from blue to green depending on the lighting and distance from the lights."
+ icon_state = "greenchromis"
+ dedicated_in_aquarium_icon_state = "fish_greyscale"
+ aquarium_vc_color = "#00ff00"
+ required_fluid_type = AQUARIUM_FLUID_SALTWATER
+ stable_population = 5
+
+/datum/aquarium_behaviour/fish/firefish
+ name = "firefish goby"
+ desc = "To communicate in the wild, the firefish uses its dorsal fin to alert others of potential danger."
+ icon_state = "firefish"
+ sprite_width = 6
+ sprite_height = 5
+ required_fluid_type = AQUARIUM_FLUID_SALTWATER
+ stable_population = 3
+
+/datum/aquarium_behaviour/fish/pufferfish
+ name = "pufferfish"
+ desc = "One Pufferfish contains enough toxins in its liver to kill 30 people."
+ icon_state = "pufferfish"
+ required_fluid_type = AQUARIUM_FLUID_SALTWATER
+ sprite_width = 8
+ sprite_height = 8
+ stable_population = 3
+
+/datum/aquarium_behaviour/fish/lanternfish
+ name = "lanternfish"
+ desc = "Typically found in areas below 6600 feet below the surface of the ocean, they live in complete darkness."
+ icon_state = "lanternfish"
+ required_fluid_type = AQUARIUM_FLUID_SALTWATER
+ random_case_rarity = FISH_RARITY_VERY_RARE
+ source_width = 28
+ source_height = 21
+ sprite_width = 8
+ sprite_height = 8
+ stable_population = 3
+
+/datum/aquarium_behaviour/fish/dwarf_moonfish
+ name = "dwarf moonfish"
+ desc = "Ordinarily in the wild, the Zagoskian moonfish is around the size of a tuna, however through selective breeding a smaller breed suitable for being kept as an aquarium pet has been created."
+ icon_state = "dwarf_moonfish"
+ required_fluid_type = AQUARIUM_FLUID_SALTWATER
+ stable_population = 2
+
+/datum/aquarium_behaviour/fish/gunner_jellyfish
+ name = "gunner jellyfish"
+ desc = "So called due to their resemblance to an artillery shell, the gunner jellyfish is native to Tizira, where it is enjoyed as a delicacy. Produces a mild hallucinogen that is destroyed by cooking."
+ icon_state = "gunner_jellyfish"
+ required_fluid_type = AQUARIUM_FLUID_SALTWATER
+ stable_population = 4
+
+/datum/aquarium_behaviour/fish/needlefish
+ name = "needlefish"
+ desc = "A tiny, transparent fish which resides in large schools in the oceans of Tizira. A common food for other, larger fish."
+ icon_state = "needlefish"
+ required_fluid_type = AQUARIUM_FLUID_SALTWATER
+ stable_population = 12
+
+/datum/aquarium_behaviour/fish/armorfish
+ name = "armorfish"
+ desc = "A small shellfish native to Tizira's oceans, known for its exceptionally hard shell. Consumed similarly to prawns."
+ icon_state = "armorfish"
+ required_fluid_type = AQUARIUM_FLUID_SALTWATER
+ stable_population = 10
diff --git a/code/modules/aquarium/misc.dm b/code/modules/aquarium/misc.dm
new file mode 100644
index 0000000000000..23cd565d4dca3
--- /dev/null
+++ b/code/modules/aquarium/misc.dm
@@ -0,0 +1,124 @@
+#define AQUARIUM_COMPANY "Aquatech Ltd."
+
+// Fish feed can
+/obj/item/fish_feed
+ name = "fish feed can"
+ desc = "Autogenerates nutritious fish feed based on sample inside."
+ icon = 'icons/obj/aquarium.dmi'
+ icon_state = "fish_feed"
+ w_class = WEIGHT_CLASS_TINY
+
+/obj/item/fish_feed/Initialize()
+ . = ..()
+ create_reagents(5, OPENCONTAINER)
+ reagents.add_reagent(/datum/reagent/consumable/nutriment, 1) //Default fish diet
+
+// Stasis fish case container for moving fish between aquariums safely.
+/obj/item/storage/fish_case
+ name = "stasis fish case"
+ desc = "A small case keeping the fish inside in stasis."
+ icon_state = "fishbox"
+
+ lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
+
+ component_type = /datum/component/storage/concrete/fish_case
+
+/obj/item/storage/fish_case/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_FISH_SAFE_STORAGE, TRAIT_GENERIC)
+
+/// Fish case with single random fish inside.
+/obj/item/storage/fish_case/random/PopulateContents()
+ . = ..()
+ generate_fish(src, select_fish_type())
+
+/obj/item/storage/fish_case/random/proc/select_fish_type()
+ return random_fish_type()
+
+/obj/item/storage/fish_case/random/freshwater/select_fish_type()
+ return random_fish_type(required_fluid=AQUARIUM_FLUID_FRESHWATER)
+
+/// Book detailing where to get the fish and their properties.
+/obj/item/book/fish_catalog
+ name = "Fish Encyclopedia"
+ desc = "Indexes all fish known to mankind (and related species)"
+ icon_state = "fishbook"
+ dat = "Lot of fish stuff" //book wrappers could use cleaning so this is not necessary
+
+/obj/item/book/fish_catalog/on_read(mob/user)
+ ui_interact(user)
+
+/obj/item/book/fish_catalog/ui_interact(mob/user, datum/tgui/ui)
+ . = ..()
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "FishCatalog", name)
+ ui.open()
+
+/obj/item/book/fish_catalog/ui_static_data(mob/user)
+ . = ..()
+ var/static/fish_info
+ if(!fish_info)
+ fish_info = list()
+ for(var/_fish_behaviour in subtypesof(/datum/aquarium_behaviour/fish))
+ var/datum/aquarium_behaviour/fish/fish_behaviour = _fish_behaviour
+ var/list/fish_data = list()
+ if(!initial(fish_behaviour.show_in_catalog))
+ continue
+ fish_data["name"] = initial(fish_behaviour.name)
+ fish_data["desc"] = initial(fish_behaviour.desc)
+ fish_data["fluid"] = initial(fish_behaviour.required_fluid_type)
+ fish_data["temp_min"] = initial(fish_behaviour.required_temperature_min)
+ fish_data["temp_max"] = initial(fish_behaviour.required_temperature_max)
+ fish_data["icon"] = sanitize_css_class_name("[initial(fish_behaviour.icon)][initial(fish_behaviour.icon_state)]")
+ fish_data["color"] = initial(fish_behaviour.color)
+ fish_data["source"] = initial(fish_behaviour.availible_in_random_cases) ? "[AQUARIUM_COMPANY] Fish Packs" : "Unknown"
+ var/datum/reagent/food_type = initial(fish_behaviour.food)
+ if(food_type != /datum/reagent/consumable/nutriment)
+ fish_data["feed"] = initial(food_type.name)
+ else
+ fish_data["feed"] = "[AQUARIUM_COMPANY] Fish Feed"
+ fish_info += list(fish_data)
+
+ .["fish_info"] = fish_info
+ .["sponsored_by"] = AQUARIUM_COMPANY
+
+/obj/item/book/fish_catalog/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/spritesheet/fish)
+ )
+
+/obj/item/aquarium_kit
+ name = "DIY Aquarium Construction Kit"
+ desc = "Everything you need to build your own aquarium.(Raw materials sold separately)"
+ icon = 'icons/obj/aquarium.dmi'
+ icon_state = "construction_kit"
+ w_class = WEIGHT_CLASS_TINY
+
+/obj/item/aquarium_kit/attack_self(mob/user)
+ . = ..()
+ to_chat(user,"There's instruction and tools necessary to build aquarium inside. All you need is to start crafting.")
+
+
+/obj/item/aquarium_prop
+ name = "generic aquarium prop"
+ desc = "very boring"
+ w_class = WEIGHT_CLASS_TINY
+
+/obj/item/storage/box/aquarium_props
+ name = "aquarium props box"
+ desc = "All you need to make your aquarium look good"
+
+/obj/item/storage/box/aquarium_props/PopulateContents()
+ for(var/prop_type in subtypesof(/datum/aquarium_behaviour/prop))
+ generate_fish(src, prop_type, /obj/item/aquarium_prop)
+
+/obj/item/storage/box/fish_debug
+ name = "box full of fish"
+
+/obj/item/storage/box/fish_debug/PopulateContents()
+ for(var/fish_type in subtypesof(/datum/aquarium_behaviour/fish))
+ generate_fish(src,fish_type)
+
+#undef AQUARIUM_COMPANY
diff --git a/code/modules/assembly/assembly.dm b/code/modules/assembly/assembly.dm
index 567fc6d014994..a6ad3c3f10bd3 100644
--- a/code/modules/assembly/assembly.dm
+++ b/code/modules/assembly/assembly.dm
@@ -29,6 +29,10 @@
var/next_activate = 0 //When we're next allowed to activate - for spam control
var/activate_delay = 30
+/obj/item/assembly/Destroy()
+ holder = null
+ return ..()
+
/obj/item/assembly/get_part_rating()
return 1
@@ -125,3 +129,8 @@
/obj/item/assembly/interact(mob/user)
return ui_interact(user)
+
+/obj/item/assembly/ui_status(mob/user)
+ . = ..()
+ if(src.can_interact(user) || holder?.can_interact(user))
+ return UI_INTERACTIVE
diff --git a/code/modules/assembly/bomb.dm b/code/modules/assembly/bomb.dm
index 1b5e957603d19..04cb3c8785eec 100644
--- a/code/modules/assembly/bomb.dm
+++ b/code/modules/assembly/bomb.dm
@@ -138,7 +138,7 @@
return
/obj/item/tank/proc/ignite() //This happens when a bomb is told to explode
- var/fuel_moles = air_contents.get_moles(/datum/gas/plasma) + air_contents.get_moles(/datum/gas/oxygen)/6
+ var/fuel_moles = air_contents.get_moles(GAS_PLASMA) + air_contents.get_moles(GAS_O2)/6
var/datum/gas_mixture/bomb_mixture = air_contents.copy()
var/strength = 1
@@ -190,11 +190,10 @@
ground_zero.air_update_turf()
/obj/item/tank/proc/release() //This happens when the bomb is not welded. Tank contents are just spat out.
- var/datum/gas_mixture/removed = air_contents.remove(air_contents.total_moles())
var/turf/T = get_turf(src)
if(!T)
return
- T.assume_air(removed)
+ T.assume_air(air_contents)
air_update_turf()
/obj/item/onetankbomb/return_analyzable_air()
diff --git a/code/modules/assembly/flash.dm b/code/modules/assembly/flash.dm
index 01fc70ebe099a..8edc3a9556412 100644
--- a/code/modules/assembly/flash.dm
+++ b/code/modules/assembly/flash.dm
@@ -14,7 +14,7 @@
flags_1 = CONDUCT_1
throw_speed = 3
throw_range = 7
- var/charges_left = 15
+ var/charges_left = 10
/obj/item/flashbulb/update_icon()
if(charges_left <= 0)
@@ -42,13 +42,36 @@
name = "weakened flashbulb"
charges_left = 4
-/obj/item/flashbulb/strong
+/obj/item/flashbulb/recharging
+ charges_left = 3
+ var/max_charges = 3
+ var/charge_time = 10 SECONDS
+ var/recharging = FALSE
+
+/obj/item/flashbulb/recharging/proc/recharge()
+ recharging = FALSE
+ if(charges_left >= max_charges)
+ return
+ charges_left ++
+ icon_state = "flashbulb"
+ if(charges_left < max_charges)
+ addtimer(CALLBACK(src, .proc/recharge), charge_time, TIMER_UNIQUE)
+ recharging = TRUE
+
+/obj/item/flashbulb/recharging/use_flashbulb()
+ . = ..()
+ if(!recharging)
+ addtimer(CALLBACK(src, .proc/recharge), charge_time, TIMER_UNIQUE)
+ recharging = TRUE
+
+/obj/item/flashbulb/recharging/revolution
name = "modified flashbulb"
- charges_left = 120
+ charges_left = 10
+ max_charges = 10
+ charge_time = 15 SECONDS
-/obj/item/flashbulb/cyborg
+/obj/item/flashbulb/recharging/cyborg
name = "cyborg flashbulb"
- charges_left = INFINITY
/obj/item/assembly/flash
name = "flash"
@@ -73,7 +96,7 @@
bulb = /obj/item/flashbulb/weak
/obj/item/assembly/flash/handheld/strong
- bulb = /obj/item/flashbulb/strong
+ bulb = /obj/item/flashbulb/recharging/revolution
/obj/item/assembly/flash/Initialize()
. = ..()
@@ -147,6 +170,9 @@
if(!bulb)
to_chat(user, "There is no bulb in \the [src].")
return FALSE
+ if(flags_1 & NODECONSTRUCT_1)
+ to_chat(user, "You cannot remove the bulb from \the [src].")
+ return FALSE
bulb.forceMove(drop_location())
user.put_in_hands(bulb)
bulb.update_icon()
@@ -184,6 +210,10 @@
return FALSE
if(FLASH_USE_BURNOUT)
burn_out()
+ if(is_head_revolutionary(user) && !burnt_out)
+ //Flash will drain to a minimum of 1 charge when used by a head rev.
+ if(bulb.charges_left < rand(2, initial(bulb.charges_left) - 1))
+ bulb.charges_left ++
last_trigger = world.time
playsound(src, 'sound/weapons/flash.ogg', 100, TRUE)
flash_lighting_fx(FLASH_LIGHT_RANGE, light_power, light_color)
@@ -232,9 +262,7 @@
log_combat(user, R, "flashed", src)
update_icon(1)
R.Paralyze(70)
- var/diff = 5 * CONFUSION_STACK_MAX_MULTIPLIER - M.confused
- R.confused += min(5, diff)
- R.flash_act(affect_silicon = 1, type = /obj/screen/fullscreen/flash/static)
+ R.flash_act(affect_silicon = 1, type = /atom/movable/screen/fullscreen/flash/static)
user.visible_message("[user] overloads [R]'s sensors with the flash!", "You overload [R]'s sensors with the flash!")
return TRUE
@@ -278,7 +306,7 @@
/obj/item/assembly/flash/cyborg
- bulb = /obj/item/flashbulb/cyborg
+ bulb = /obj/item/flashbulb/recharging/cyborg
/obj/item/assembly/flash/cyborg/attack(mob/living/M, mob/user)
..()
@@ -311,19 +339,22 @@
desc = "A high-powered photon projector implant normally used for lighting purposes, but also doubles as a flashbulb weapon. Self-repair protocols fix the flashbulb if it ever burns out."
var/flashcd = 20
var/overheat = 0
- var/obj/item/organ/cyberimp/arm/flash/I = null
+ //Wearef to our arm
+ var/datum/weakref/arm
/obj/item/assembly/flash/armimplant/burn_out()
- if(I?.owner)
- to_chat(I.owner, "Your photon projector implant overheats and deactivates!")
- I.Retract()
+ var/obj/item/organ/cyberimp/arm/flash/real_arm = arm.resolve()
+ if(real_arm?.owner)
+ to_chat(real_arm.owner, "Your photon projector implant overheats and deactivates!")
+ real_arm.Retract()
overheat = TRUE
addtimer(CALLBACK(src, .proc/cooldown), flashcd * 2)
/obj/item/assembly/flash/armimplant/try_use_flash(mob/user = null)
if(overheat)
- if(I?.owner)
- to_chat(I.owner, "Your photon projector is running too hot to be used again so quickly!")
+ var/obj/item/organ/cyberimp/arm/flash/real_arm = arm.resolve()
+ if(real_arm?.owner)
+ to_chat(real_arm.owner, "Your photon projector is running too hot to be used again so quickly!")
return FALSE
overheat = TRUE
addtimer(CALLBACK(src, .proc/cooldown), flashcd)
@@ -343,7 +374,7 @@
flashing_overlay = "flash-hypno"
light_color = LIGHT_COLOR_PINK
cooldown = 20
- bulb = /obj/item/flashbulb/cyborg //Flashbulb with infinite charges
+ bulb = /obj/item/flashbulb/recharging/revolution
/obj/item/assembly/flash/hypnotic/burn_out()
return
@@ -362,20 +393,19 @@
to_chat(M, "[src] emits a soothing light...")
if(targeted)
if(M.flash_act(1, 1))
- var/hypnosis = FALSE
- if(M.hypnosis_vulnerable())
- hypnosis = TRUE
if(user)
user.visible_message("[user] blinds [M] with the flash!", "You hypno-flash [M]!")
- if(!hypnosis)
+ if(M.hypnosis_vulnerable())
+ M.apply_status_effect(/datum/status_effect/trance, 200, TRUE)
+ else
to_chat(M, "The light makes you feel oddly relaxed...")
M.confused += min(M.confused + 10, 20)
M.dizziness += min(M.dizziness + 10, 20)
M.drowsyness += min(M.drowsyness + 10, 20)
M.apply_status_effect(STATUS_EFFECT_PACIFY, 100)
- else
- M.apply_status_effect(/datum/status_effect/trance, 200, TRUE)
+
+
else if(user)
user.visible_message("[user] fails to blind [M] with the flash!", "You fail to hypno-flash [M]!")
diff --git a/code/modules/assembly/health.dm b/code/modules/assembly/health.dm
index 4a7190bdad850..7d8ef4e068be5 100644
--- a/code/modules/assembly/health.dm
+++ b/code/modules/assembly/health.dm
@@ -5,13 +5,14 @@
materials = list(/datum/material/iron=800, /datum/material/glass=200)
attachable = TRUE
- var/scanning = TRUE
+ var/scanning = FALSE
var/health_scan
var/alarm_health = HEALTH_THRESHOLD_CRIT
/obj/item/assembly/health/examine(mob/user)
. = ..()
- . += "Use a multitool to swap between \"detect death\" mode and \"detect critical state\" mode."
+ . += "Use it in hand to turn it off/on and Alt-click to swap between \"detect death\" mode and \"detect critical state\" mode."
+ . += "[src.scanning ? "The sensor is on and you can see [health_scan] displayed on the screen" : "The sensor is off"]."
/obj/item/assembly/health/activate()
if(!..())
@@ -29,14 +30,13 @@
update_icon()
return secured
-/obj/item/assembly/health/multitool_act(mob/living/user, obj/item/I)
+/obj/item/assembly/health/AltClick(mob/living/user)
if(alarm_health == HEALTH_THRESHOLD_CRIT)
alarm_health = HEALTH_THRESHOLD_DEAD
to_chat(user, "You toggle [src] to \"detect death\" mode.")
else
alarm_health = HEALTH_THRESHOLD_CRIT
to_chat(user, "You toggle [src] to \"detect critical state\" mode.")
- return TRUE
/obj/item/assembly/health/process()
if(!scanning || !secured)
@@ -69,36 +69,7 @@
STOP_PROCESSING(SSobj, src)
return
-/obj/item/assembly/health/ui_interact(mob/user as mob)//TODO: Change this to the wires thingy
+/obj/item/assembly/health/attack_self(mob/user)
. = ..()
- if(!secured)
- user.show_message("The [name] is unsecured!")
- return FALSE
- var/dat = "Health Sensor"
- dat += " [scanning?"On":"Off"]"
- if(scanning && health_scan)
- dat += " Health: [health_scan]"
- user << browse(dat, "window=hscan")
- onclose(user, "hscan")
-
-/obj/item/assembly/health/Topic(href, href_list)
- ..()
- if(!ismob(usr))
- return
-
- var/mob/user = usr
-
- if(!user.canUseTopic(src, BE_CLOSE))
- usr << browse(null, "window=hscan")
- onclose(usr, "hscan")
- return
-
- if(href_list["scanning"])
- toggle_scan()
-
- if(href_list["close"])
- usr << browse(null, "window=hscan")
- return
-
- attack_self(user)
- return
+ to_chat(user, "You toggle [src] [src.scanning ? "off" : "on"].")
+ toggle_scan()
diff --git a/code/modules/assembly/holder.dm b/code/modules/assembly/holder.dm
index f439775877c69..868eb305d176a 100644
--- a/code/modules/assembly/holder.dm
+++ b/code/modules/assembly/holder.dm
@@ -16,9 +16,8 @@
/obj/item/assembly_holder/ComponentInitialize()
. = ..()
- AddComponent(
- /datum/component/simple_rotation,
- ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_FLIP | ROTATION_VERBS)
+ var/static/rotation_flags = ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_FLIP | ROTATION_VERBS
+ AddComponent(/datum/component/simple_rotation, rotation_flags)
/obj/item/assembly_holder/IsAssemblyHolder()
return TRUE
diff --git a/code/modules/assembly/infrared.dm b/code/modules/assembly/infrared.dm
index b5e4f52eb3773..c8e70fbf8fa3d 100644
--- a/code/modules/assembly/infrared.dm
+++ b/code/modules/assembly/infrared.dm
@@ -22,7 +22,7 @@
/obj/item/assembly/infra/ComponentInitialize()
. = ..()
var/static/rotation_flags = ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_FLIP | ROTATION_VERBS
- AddComponent(/datum/component/simple_rotation, rotation_flags, null, null, CALLBACK(src,.proc/after_rotation))
+ AddComponent(/datum/component/simple_rotation, rotation_flags, after_rotation=CALLBACK(src,.proc/after_rotation))
/obj/item/assembly/infra/proc/after_rotation()
refreshBeam()
@@ -134,7 +134,7 @@
. = ..()
setDir(t)
-/obj/item/assembly/infra/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE)
+/obj/item/assembly/infra/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE)
. = ..()
olddir = dir
@@ -152,10 +152,7 @@
return FALSE
pulse(FALSE)
audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range)
- for(var/CHM in get_hearers_in_view(hearing_range, src))
- if(ismob(CHM))
- var/mob/LM = CHM
- LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE)
+ playsound(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE)
next_activate = world.time + 30
/obj/item/assembly/infra/proc/switchListener(turf/newloc)
@@ -167,6 +164,8 @@
listeningTo = newloc
/obj/item/assembly/infra/proc/check_exit(datum/source, atom/movable/offender)
+ SIGNAL_HANDLER
+
if(QDELETED(src))
return
if(offender == src || istype(offender,/obj/effect/beam/i_beam))
@@ -175,7 +174,7 @@
var/obj/item/I = offender
if (I.item_flags & ABSTRACT)
return
- return refreshBeam()
+ INVOKE_ASYNC(src, .proc/refreshBeam)
/obj/item/assembly/infra/setDir()
. = ..()
@@ -214,8 +213,9 @@
visible = !visible
. = TRUE
- update_icon()
- refreshBeam()
+ if(.)
+ update_icon()
+ refreshBeam()
/***************************IBeam*********************************/
diff --git a/code/modules/assembly/proximity.dm b/code/modules/assembly/proximity.dm
index f526d203e3e48..bc00b18d4b209 100644
--- a/code/modules/assembly/proximity.dm
+++ b/code/modules/assembly/proximity.dm
@@ -8,7 +8,7 @@
var/scanning = FALSE
var/timing = FALSE
- var/time = 10
+ var/time = 20
var/sensitivity = 1
var/hearing_range = 3
@@ -66,20 +66,18 @@
return FALSE
pulse(FALSE)
audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range)
- for(var/CHM in get_hearers_in_view(hearing_range, src))
- if(ismob(CHM))
- var/mob/LM = CHM
- LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE)
+ playsound(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE)
next_activate = world.time + 30
return TRUE
-/obj/item/assembly/prox_sensor/process()
+/obj/item/assembly/prox_sensor/process(delta_time)
if(!timing)
return
- time--
+ time -= delta_time
if(time <= 0)
timing = FALSE
toggle_scan(TRUE)
+ ui_update()
time = initial(time)
/obj/item/assembly/prox_sensor/proc/toggle_scan(scan)
@@ -108,12 +106,17 @@
holder.update_icon()
return
+
+/obj/item/assembly/prox_sensor/ui_requires_update(mob/user, datum/tgui/ui)
+ . = ..()
+ if(timing)
+ . = TRUE // Autoupdate while counting down
+
/obj/item/assembly/prox_sensor/ui_status(mob/user)
if(is_secured(user))
return ..()
return UI_CLOSE
-
/obj/item/assembly/prox_sensor/ui_state(mob/user)
return GLOB.hands_state
diff --git a/code/modules/assembly/signaler.dm b/code/modules/assembly/signaler.dm
index 2456dcc939b85..5830df340a444 100644
--- a/code/modules/assembly/signaler.dm
+++ b/code/modules/assembly/signaler.dm
@@ -109,7 +109,8 @@
code = initial(code)
. = TRUE
- update_icon()
+ if(.)
+ update_icon()
/obj/item/assembly/signaler/attackby(obj/item/W, mob/user, params)
if(issignaler(W))
@@ -118,6 +119,7 @@
code = signaler2.code
set_frequency(signaler2.frequency)
to_chat(user, "You transfer the frequency and code of \the [signaler2.name] to \the [name]")
+ ui_update()
..()
/obj/item/assembly/signaler/proc/signal()
@@ -145,10 +147,7 @@
return
pulse(TRUE)
audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range)
- for(var/CHM in get_hearers_in_view(hearing_range, src))
- if(ismob(CHM))
- var/mob/LM = CHM
- LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE)
+ playsound(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE)
return TRUE
/obj/item/assembly/signaler/proc/set_frequency(new_frequency)
diff --git a/code/modules/assembly/timer.dm b/code/modules/assembly/timer.dm
index 0dc8876b9426c..c817d0e29bba9 100644
--- a/code/modules/assembly/timer.dm
+++ b/code/modules/assembly/timer.dm
@@ -7,15 +7,15 @@
var/timing = FALSE
- var/time = 5
- var/saved_time = 5
+ var/time = 10
+ var/saved_time = 10
var/loop = FALSE
var/hearing_range = 3
/obj/item/assembly/timer/suicide_act(mob/living/user)
user.visible_message("[user] looks at the timer and decides [user.p_their()] fate! It looks like [user.p_theyre()] going to commit suicide!")
activate()//doesnt rely on timer_end to prevent weird metas where one person can control the timer and therefore someone's life. (maybe that should be how it works...)
- addtimer(CALLBACK(src, .proc/manual_suicide, user), time*10)//kill yourself once the time runs out
+ addtimer(CALLBACK(src, .proc/manual_suicide, user), time SECONDS)//kill yourself once the time runs out
return MANUAL_SUICIDE
/obj/item/assembly/timer/proc/manual_suicide(mob/living/user)
@@ -57,18 +57,16 @@
return FALSE
pulse(FALSE)
audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range)
- for(var/CHM in get_hearers_in_view(hearing_range, src))
- if(ismob(CHM))
- var/mob/LM = CHM
- LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE)
+ playsound(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE)
if(loop)
timing = TRUE
update_icon()
+ ui_update()
-/obj/item/assembly/timer/process()
+/obj/item/assembly/timer/process(delta_time)
if(!timing)
return
- time--
+ time -= delta_time
if(time <= 0)
timing = FALSE
timer_end()
@@ -89,6 +87,11 @@
return UI_CLOSE
+/obj/item/assembly/timer/ui_requires_update(mob/user, datum/tgui/ui)
+ . = ..()
+ if(timing)
+ . = TRUE // Autoupdate while counting down
+
/obj/item/assembly/timer/ui_state(mob/user)
return GLOB.hands_state
diff --git a/code/modules/assembly/voice.dm b/code/modules/assembly/voice.dm
index dbcec82f39202..e7e558aabe79f 100644
--- a/code/modules/assembly/voice.dm
+++ b/code/modules/assembly/voice.dm
@@ -8,7 +8,6 @@
desc = "A small electronic device able to record a voice sample, and send a signal when that sample is repeated."
icon_state = "voice"
materials = list(/datum/material/iron=500, /datum/material/glass=50)
- flags_1 = HEAR_1
attachable = TRUE
verb_say = "beeps"
verb_ask = "beeps"
@@ -21,11 +20,15 @@
"recognizer",
"voice sensor")
+/obj/item/assembly/voice/Initialize()
+ . = ..()
+ become_hearing_sensitive(ROUNDSTART_TRAIT)
+
/obj/item/assembly/voice/examine(mob/user)
. = ..()
. += "Use a multitool to swap between \"inclusive\", \"exclusive\", \"recognizer\", and \"voice sensor\" mode."
-/obj/item/assembly/voice/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
+/obj/item/assembly/voice/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods = list())
. = ..()
if(speaker == src)
return
diff --git a/code/modules/asset_cache/asset_list.dm b/code/modules/asset_cache/asset_list.dm
index 221febbe14d4d..3f9e8c3fcca2c 100644
--- a/code/modules/asset_cache/asset_list.dm
+++ b/code/modules/asset_cache/asset_list.dm
@@ -11,6 +11,7 @@ GLOBAL_LIST_EMPTY(asset_datums)
/datum/asset
var/_abstract = /datum/asset
+ var/cached_url_mappings
/datum/asset/New()
GLOB.asset_datums[type] = src
@@ -19,6 +20,13 @@ GLOBAL_LIST_EMPTY(asset_datums)
/datum/asset/proc/get_url_mappings()
return list()
+/// Returns a cached tgui message of URL mappings
+/datum/asset/proc/get_serialized_url_mappings()
+ if (isnull(cached_url_mappings))
+ cached_url_mappings = TGUI_CREATE_MESSAGE("asset/mappings", get_url_mappings())
+
+ return cached_url_mappings
+
/datum/asset/proc/register()
return
diff --git a/code/modules/asset_cache/asset_list_items.dm b/code/modules/asset_cache/asset_list_items.dm
index bd98b9cc07a99..077c00485757d 100644
--- a/code/modules/asset_cache/asset_list_items.dm
+++ b/code/modules/asset_cache/asset_list_items.dm
@@ -3,21 +3,21 @@
/datum/asset/simple/tgui_common
keep_local_name = TRUE
assets = list(
- "tgui-common.chunk.js" = 'tgui/public/tgui-common.chunk.js',
+ "tgui-common.bundle.js" = file("tgui/public/tgui-common.bundle.js"),
)
/datum/asset/simple/tgui
keep_local_name = TRUE
assets = list(
- "tgui.bundle.js" = 'tgui/public/tgui.bundle.js',
- "tgui.bundle.css" = 'tgui/public/tgui.bundle.css',
+ "tgui.bundle.js" = file("tgui/public/tgui.bundle.js"),
+ "tgui.bundle.css" = file("tgui/public/tgui.bundle.css"),
)
/datum/asset/simple/tgui_panel
keep_local_name = TRUE
assets = list(
- "tgui-panel.bundle.js" = 'tgui/public/tgui-panel.bundle.js',
- "tgui-panel.bundle.css" = 'tgui/public/tgui-panel.bundle.css',
+ "tgui-panel.bundle.js" = file("tgui/public/tgui-panel.bundle.js"),
+ "tgui-panel.bundle.css" = file("tgui/public/tgui-panel.bundle.css"),
)
/datum/asset/simple/headers
@@ -50,6 +50,18 @@
"smmon_6.gif" = 'icons/program_icons/smmon_6.gif',
)
+/datum/asset/simple/circuit_assets
+ assets = list(
+ "grid_background.png" = 'icons/ui_icons/tgui/grid_background.png'
+ )
+
+/datum/asset/simple/radar_assets
+ assets = list(
+ "ntosradarbackground.png" = 'icons/UI_Icons/tgui/ntosradar_background.png',
+ "ntosradarpointer.png" = 'icons/UI_Icons/tgui/ntosradar_pointer.png',
+ "ntosradarpointerS.png" = 'icons/UI_Icons/tgui/ntosradar_pointer_S.png'
+ )
+
/datum/asset/spritesheet/simple/pda
name = "pda"
assets = list(
@@ -158,11 +170,21 @@
)
parents = list("font-awesome.css" = 'html/font-awesome/css/all.min.css')
+/datum/asset/simple/namespaced/tgfont
+ assets = list(
+ "tgfont.eot" = file("tgui/packages/tgfont/dist/tgfont.eot"),
+ "tgfont.woff2" = file("tgui/packages/tgfont/dist/tgfont.woff2"),
+ )
+ parents = list(
+ "tgfont.css" = file("tgui/packages/tgfont/dist/tgfont.css"),
+ )
+
/datum/asset/spritesheet/chat
name = "chat"
/datum/asset/spritesheet/chat/register()
InsertAll("emoji", 'icons/emoji.dmi')
+ InsertAll("badge", 'icons/badges.dmi')
// pre-loading all lanugage icons also helps to avoid meta
InsertAll("language", 'icons/misc/language.dmi')
// catch languages which are pulling icons from another file
@@ -211,7 +233,7 @@
"boss4.gif" = 'icons/UI_Icons/Arcade/boss4.gif',
"boss5.gif" = 'icons/UI_Icons/Arcade/boss5.gif',
"boss6.gif" = 'icons/UI_Icons/Arcade/boss6.gif',
- )
+ )
/datum/asset/spritesheet/simple/pills
name ="pills"
@@ -255,6 +277,47 @@
InsertAll("", each, GLOB.alldirs)
..()
+/datum/asset/simple/genetics
+ assets = list(
+ "dna_discovered.gif" = 'html/dna_discovered.gif',
+ "dna_undiscovered.gif" = 'html/dna_undiscovered.gif',
+ "dna_extra.gif" = 'html/dna_extra.gif'
+ )
+
+/datum/asset/spritesheet/supplypods
+ name = "supplypods"
+
+/datum/asset/spritesheet/supplypods/register()
+ for (var/style in 1 to length(GLOB.podstyles))
+ var/icon_file = 'icons/obj/supplypods.dmi'
+ var/states = icon_states(icon_file)
+ if (style == STYLE_SEETHROUGH)
+ Insert("pod_asset[style]", icon(icon_file, "seethrough-icon", SOUTH))
+ continue
+ var/base = GLOB.podstyles[style][POD_BASE]
+ if (!base)
+ Insert("pod_asset[style]", icon(icon_file, "invisible-icon", SOUTH))
+ continue
+ var/icon/podIcon = icon(icon_file, base, SOUTH)
+ var/door = GLOB.podstyles[style][POD_DOOR]
+ if (door)
+ door = "[base]_door"
+ if(door in states)
+ podIcon.Blend(icon(icon_file, door, SOUTH), ICON_OVERLAY)
+ var/shape = GLOB.podstyles[style][POD_SHAPE]
+ if (shape == POD_SHAPE_NORML)
+ var/decal = GLOB.podstyles[style][POD_DECAL]
+ if (decal)
+ if(decal in states)
+ podIcon.Blend(icon(icon_file, decal, SOUTH), ICON_OVERLAY)
+ var/glow = GLOB.podstyles[style][POD_GLOW]
+ if (glow)
+ glow = "pod_glow_[glow]"
+ if(glow in states)
+ podIcon.Blend(icon(icon_file, glow, SOUTH), ICON_OVERLAY)
+ Insert("pod_asset[style]", podIcon)
+ return ..()
+
// Representative icons for each research design
/datum/asset/spritesheet/research_designs
name = "design"
@@ -347,14 +410,6 @@
Insert(imgid, I)
return ..()
-
-/datum/asset/simple/genetics
- assets = list(
- "dna_discovered.png" = 'html/dna_discovered.png',
- "dna_undiscovered.png" = 'html/dna_undiscovered.png',
- "dna_extra.png" = 'html/dna_extra.png'
- )
-
/datum/asset/simple/bee_antags
assets = list(
"traitor.png" = 'html/img/traitor.png',
@@ -412,3 +467,51 @@
// Special case to handle Bluespace Crystals
Insert("polycrystal", 'icons/obj/telescience.dmi', "polycrystal")
..()
+
+/datum/asset/simple/pAI
+ assets = list(
+ "paigrid.png" = 'html/paigrid.png'
+ )
+
+/datum/asset/simple/portraits
+ var/tab = "use subtypes of this please"
+ assets = list()
+
+/datum/asset/simple/portraits/New()
+ if(!SSpersistence.paintings || !SSpersistence.paintings[tab] || !length(SSpersistence.paintings[tab]))
+ return
+ for(var/p in SSpersistence.paintings[tab])
+ var/list/portrait = p
+ var/png = "data/paintings/[tab]/[portrait["md5"]].png"
+ if(fexists(png))
+ var/asset_name = "[tab]_[portrait["md5"]]"
+ assets[asset_name] = png
+ ..() //this is where it registers all these assets we added to the list
+
+/datum/asset/simple/portraits/library
+ tab = "library"
+
+/datum/asset/simple/portraits/library_secure
+ tab = "library_secure"
+
+/datum/asset/simple/portraits/library_private
+ tab = "library_private"
+
+/datum/asset/spritesheet/fish
+ name = "fish"
+
+/datum/asset/spritesheet/fish/register()
+ for (var/path in subtypesof(/datum/aquarium_behaviour/fish))
+ var/datum/aquarium_behaviour/fish/fish_type = path
+ var/fish_icon = initial(fish_type.icon)
+ var/fish_icon_state = initial(fish_type.icon_state)
+ var/id = sanitize_css_class_name("[fish_icon][fish_icon_state]")
+ if(sprites[id]) //no dupes
+ continue
+ Insert(id, fish_icon, fish_icon_state)
+ ..()
+
+/// Removes all non-alphanumerics from the text, keep in mind this can lead to id conflicts
+/proc/sanitize_css_class_name(name)
+ var/static/regex/regex = new(@"[^a-zA-Z0-9]","g")
+ return replacetext(name, regex, "")
diff --git a/code/modules/atmospherics/auxgm/breathing_classes.dm b/code/modules/atmospherics/auxgm/breathing_classes.dm
new file mode 100644
index 0000000000000..4abfab58eecc3
--- /dev/null
+++ b/code/modules/atmospherics/auxgm/breathing_classes.dm
@@ -0,0 +1,37 @@
+// Breathing classes are, yes, just a list of gases, associated with numbers.
+// But they're very simple: pluoxium's status as O2 * 8 is represented here,
+// with a single line of code, no hardcoding and special-casing across the codebase.
+// Not only that, but they're very general: you could have a negative value
+// to simulate asphyxiants, e.g. if I add krypton it could go into the oxygen
+// breathing class at -7, simulating krypton narcosis.
+
+/datum/breathing_class
+ var/list/gases = null
+ var/list/products = null
+ var/danger_reagent = null
+ var/low_alert_category = "not_enough_oxy"
+ var/low_alert_datum = /atom/movable/screen/alert/not_enough_oxy
+ var/high_alert_category = "too_much_oxy"
+ var/high_alert_datum = /atom/movable/screen/alert/too_much_oxy
+
+/datum/breathing_class/oxygen
+ gases = list(
+ GAS_O2 = 1,
+ GAS_PLUOXIUM = 8,
+ GAS_CO2 = -0.7, // CO2 isn't actually toxic, just an asphyxiant
+ )
+ products = list(
+ GAS_CO2 = 1,
+ )
+
+/datum/breathing_class/plasma
+ gases = list(
+ GAS_PLASMA = 1
+ )
+ products = list(
+ GAS_CO2 = 1
+ )
+ low_alert_category = "not_enough_tox"
+ low_alert_datum = /atom/movable/screen/alert/not_enough_tox
+ high_alert_category = "too_much_tox"
+ high_alert_datum = /atom/movable/screen/alert/too_much_tox
diff --git a/code/modules/atmospherics/auxgm/gas_types.dm b/code/modules/atmospherics/auxgm/gas_types.dm
new file mode 100644
index 0000000000000..82ec62f90f915
--- /dev/null
+++ b/code/modules/atmospherics/auxgm/gas_types.dm
@@ -0,0 +1,129 @@
+/datum/gas/oxygen
+ id = GAS_O2
+ specific_heat = 20
+ name = "Oxygen"
+ oxidation_temperature = T0C - 100 // it checks max of this and fire temperature, so rarely will things spontaneously combust
+
+/datum/gas/nitrogen
+ id = GAS_N2
+ specific_heat = 20
+ breath_alert_info = list(
+ not_enough_alert = list(
+ alert_category = "not_enough_nitro",
+ alert_type = /atom/movable/screen/alert/not_enough_nitro
+ ),
+ too_much_alert = list(
+ alert_category = "too_much_nitro",
+ alert_type = /atom/movable/screen/alert/too_much_nitro
+ )
+ )
+ name = "Nitrogen"
+
+/datum/gas/carbon_dioxide //what the fuck is this?
+ id = GAS_CO2
+ specific_heat = 30
+ name = "Carbon Dioxide"
+ breath_results = GAS_O2
+ breath_alert_info = list(
+ not_enough_alert = list(
+ alert_category = "not_enough_co2",
+ alert_type = /atom/movable/screen/alert/not_enough_co2
+ ),
+ too_much_alert = list(
+ alert_category = "too_much_co2",
+ alert_type = /atom/movable/screen/alert/too_much_co2
+ )
+ )
+ fusion_power = 3
+
+/datum/gas/plasma
+ id = GAS_PLASMA
+ specific_heat = 200
+ name = "Plasma"
+ gas_overlay = "plasma"
+ moles_visible = MOLES_GAS_VISIBLE
+ flags = GAS_FLAG_DANGEROUS
+ // no fire info cause it has its own bespoke reaction for trit generation reasons
+
+/datum/gas/water_vapor
+ id = GAS_H2O
+ specific_heat = 40
+ name = "Water Vapor"
+ gas_overlay = "water_vapor"
+ moles_visible = MOLES_GAS_VISIBLE
+ fusion_power = 8
+ breath_reagent = /datum/reagent/water
+
+/datum/gas/hypernoblium
+ id = GAS_HYPERNOB
+ specific_heat = 2000
+ name = "Hyper-noblium"
+ gas_overlay = "freon"
+ moles_visible = MOLES_GAS_VISIBLE
+
+/datum/gas/nitrous_oxide
+ id = GAS_NITROUS
+ specific_heat = 40
+ name = "Nitrous Oxide"
+ gas_overlay = "nitrous_oxide"
+ moles_visible = MOLES_GAS_VISIBLE * 2
+ flags = GAS_FLAG_DANGEROUS
+ fire_products = list(GAS_N2 = 1)
+ oxidation_rate = 0.5
+ oxidation_temperature = FIRE_MINIMUM_TEMPERATURE_TO_EXIST + 100
+
+/datum/gas/nitryl
+ id = GAS_NITRYL
+ specific_heat = 20
+ name = "Nitryl"
+ gas_overlay = "nitryl"
+ moles_visible = MOLES_GAS_VISIBLE
+ flags = GAS_FLAG_DANGEROUS
+ fusion_power = 15
+ fire_products = list(GAS_N2 = 0.5)
+ oxidation_temperature = FIRE_MINIMUM_TEMPERATURE_TO_EXIST - 50
+
+/datum/gas/tritium
+ id = GAS_TRITIUM
+ specific_heat = 10
+ name = "Tritium"
+ gas_overlay = "tritium"
+ moles_visible = MOLES_GAS_VISIBLE
+ flags = GAS_FLAG_DANGEROUS
+ fusion_power = 1
+ /*
+ these are for when we add hydrogen, trit gets to keep its hardcoded fire for legacy reasons
+ fire_provides = list(GAS_H2O = 2)
+ fire_burn_rate = 2
+ fire_energy_released = FIRE_HYDROGEN_ENERGY_RELEASED
+ fire_temperature = FIRE_MINIMUM_TEMPERATURE_TO_EXIST - 50
+ */
+
+/datum/gas/bz
+ id = GAS_BZ
+ specific_heat = 20
+ name = "BZ"
+ flags = GAS_FLAG_DANGEROUS
+ fusion_power = 8
+
+/datum/gas/stimulum
+ id = GAS_STIMULUM
+ specific_heat = 5
+ name = "Stimulum"
+ fusion_power = 7
+
+/datum/gas/pluoxium
+ id = GAS_PLUOXIUM
+ specific_heat = 80
+ name = "Pluoxium"
+ fusion_power = 10
+ oxidation_temperature = FIRE_MINIMUM_TEMPERATURE_TO_EXIST * 1000 // it is VERY stable
+ oxidation_rate = 8
+
+/datum/gas/miasma
+ id = GAS_MIASMA
+ specific_heat = 20
+ fusion_power = 50
+ name = "Miasma"
+ gas_overlay = "miasma"
+ moles_visible = MOLES_GAS_VISIBLE * 60
diff --git a/code/modules/atmospherics/environmental/LINDA_fire.dm b/code/modules/atmospherics/environmental/LINDA_fire.dm
index 9f66bdd5fcd5d..f27e68a4eb501 100644
--- a/code/modules/atmospherics/environmental/LINDA_fire.dm
+++ b/code/modules/atmospherics/environmental/LINDA_fire.dm
@@ -13,28 +13,21 @@
if(!air)
return
- var/oxy = air.get_moles(/datum/gas/oxygen)
- if (oxy < 0.5)
+ if (air.get_oxidation_power(exposed_temperature) < 0.5)
return
- var/tox = air.get_moles(/datum/gas/plasma)
- var/trit = air.get_moles(/datum/gas/tritium)
+ var/has_fuel = air.get_moles(GAS_PLASMA) > 0.5 || air.get_moles(GAS_TRITIUM) > 0.5 || air.get_fuel_amount(exposed_temperature) > 0.5
if(active_hotspot)
if(soh)
- if(tox > 0.5 || trit > 0.5)
+ if(has_fuel)
if(active_hotspot.temperature < exposed_temperature)
active_hotspot.temperature = exposed_temperature
if(active_hotspot.volume < exposed_volume)
active_hotspot.volume = exposed_volume
return
- if((exposed_temperature > PLASMA_MINIMUM_BURN_TEMPERATURE) && (tox > 0.5 || trit > 0.5))
-
+ if((exposed_temperature > PLASMA_MINIMUM_BURN_TEMPERATURE) && has_fuel)
active_hotspot = new /obj/effect/hotspot(src, exposed_volume*25, exposed_temperature)
- active_hotspot.just_spawned = (current_cycle < SSair.times_fired)
- //remove just_spawned protection if no longer processing this cell
- SSair.add_to_active(src, 0)
-
//This is the icon for fire on turfs, also helps for nurturing small fires until they are full tile
/obj/effect/hotspot
anchored = TRUE
@@ -48,9 +41,9 @@
var/volume = 125
var/temperature = FIRE_MINIMUM_TEMPERATURE_TO_EXIST
- var/just_spawned = TRUE
var/bypassing = FALSE
var/visual_update_tick = 0
+ var/first_cycle = TRUE
/obj/effect/hotspot/Initialize(mapload, starting_volume, starting_temperature)
. = ..()
@@ -70,7 +63,9 @@
location.active_hotspot = src
- bypassing = !just_spawned && (volume > CELL_VOLUME*0.95) || location.air.return_temperature() > FUSION_TEMPERATURE_THRESHOLD
+ bypassing = !first_cycle && volume > CELL_VOLUME*0.95 || location.air.return_temperature() > FUSION_TEMPERATURE_THRESHOLD
+ if(first_cycle)
+ first_cycle = FALSE
if(bypassing)
volume = location.air.reaction_results["fire"]*FIRE_GROWTH_RATE
@@ -150,10 +145,6 @@
#define INSUFFICIENT(path) (location.air.get_moles(path) < 0.5)
/obj/effect/hotspot/process()
- if(just_spawned)
- just_spawned = FALSE
- return
-
var/turf/open/location = loc
if(!istype(location))
qdel(src)
@@ -164,13 +155,7 @@
if((temperature < FIRE_MINIMUM_TEMPERATURE_TO_EXIST) || (volume <= 1))
qdel(src)
return
- if(!location.air || (INSUFFICIENT(/datum/gas/plasma) && INSUFFICIENT(/datum/gas/tritium)) || INSUFFICIENT(/datum/gas/oxygen))
- qdel(src)
- return
-
- //Not enough to burn
- // god damn it previous coder you made the INSUFFICIENT macro for a fucking reason why didn't you use it here smh
- if((INSUFFICIENT(/datum/gas/plasma) && INSUFFICIENT(/datum/gas/tritium)) || INSUFFICIENT(/datum/gas/oxygen))
+ if(!location.air || location.air.get_oxidation_power() < 0.5 || (INSUFFICIENT(GAS_PLASMA) && INSUFFICIENT(GAS_TRITIUM) && location.air.get_fuel_amount() < 0.5))
qdel(src)
return
diff --git a/code/modules/atmospherics/environmental/LINDA_system.dm b/code/modules/atmospherics/environmental/LINDA_system.dm
index 0e213e951cfd0..2caaaf6d8375b 100644
--- a/code/modules/atmospherics/environmental/LINDA_system.dm
+++ b/code/modules/atmospherics/environmental/LINDA_system.dm
@@ -18,57 +18,75 @@
/turf/open/CanAtmosPass(turf/T, vertical = FALSE)
var/dir = vertical? get_dir_multiz(src, T) : get_dir(src, T)
- var/opp = dir_inverse_multiz(dir)
- var/R = FALSE
+ var/opp = REVERSE_DIR(dir)
+ . = TRUE
if(vertical && !(zAirOut(dir, T) && T.zAirIn(dir, src)))
- R = TRUE
+ . = FALSE
if(blocks_air || T.blocks_air)
- R = TRUE
+ . = FALSE
if (T == src)
- return !R
+ return .
for(var/obj/O in contents+T.contents)
var/turf/other = (O.loc == src ? T : src)
if(!(vertical? (CANVERTICALATMOSPASS(O, other)) : (CANATMOSPASS(O, other))))
- R = TRUE
- if(O.BlockSuperconductivity()) //the direction and open/closed are already checked on CanAtmosPass() so there are no arguments
- atmos_supeconductivity |= dir
- T.atmos_supeconductivity |= opp
- return FALSE //no need to keep going, we got all we asked
-
- atmos_supeconductivity &= ~dir
- T.atmos_supeconductivity &= ~opp
-
- return !R
-
-/atom/movable/proc/BlockSuperconductivity() // objects that block air and don't let superconductivity act. Only firelocks atm.
+ . = FALSE
+ if(O.BlockThermalConductivity()) //the direction and open/closed are already checked on CanAtmosPass() so there are no arguments
+ conductivity_blocked_directions |= dir
+ T.conductivity_blocked_directions |= opp
+ if(!.)
+ return .
+
+/atom/movable/proc/BlockThermalConductivity() // Objects that don't let heat through.
return FALSE
/turf/proc/ImmediateCalculateAdjacentTurfs()
+ if(SSair.thread_running())
+ CALCULATE_ADJACENT_TURFS(src)
+ return
var/canpass = CANATMOSPASS(src, src)
var/canvpass = CANVERTICALATMOSPASS(src, src)
for(var/direction in GLOB.cardinals_multiz)
var/turf/T = get_step_multiz(src, direction)
- var/opp_dir = dir_inverse_multiz(direction)
- if(!isopenturf(T))
+ if(!istype(T))
continue
- if(!(blocks_air || T.blocks_air) && ((direction & (UP|DOWN))? (canvpass && CANVERTICALATMOSPASS(T, src)) : (canpass && CANATMOSPASS(T, src))) )
+ var/opp_dir = REVERSE_DIR(direction)
+ if(isopenturf(T) && !(blocks_air || T.blocks_air) && ((direction & (UP|DOWN))? (canvpass && CANVERTICALATMOSPASS(T, src)) : (canpass && CANATMOSPASS(T, src))) )
LAZYINITLIST(atmos_adjacent_turfs)
LAZYINITLIST(T.atmos_adjacent_turfs)
atmos_adjacent_turfs[T] = direction
T.atmos_adjacent_turfs[src] = opp_dir
- T.__update_extools_adjacent_turfs()
else
if (atmos_adjacent_turfs)
atmos_adjacent_turfs -= T
if (T.atmos_adjacent_turfs)
T.atmos_adjacent_turfs -= src
- T.__update_extools_adjacent_turfs()
UNSETEMPTY(T.atmos_adjacent_turfs)
+ T.set_sleeping(T.blocks_air)
+ T.__update_auxtools_turf_adjacency_info(isspaceturf(T.get_z_base_turf()), -1)
UNSETEMPTY(atmos_adjacent_turfs)
src.atmos_adjacent_turfs = atmos_adjacent_turfs
- __update_extools_adjacent_turfs()
+ set_sleeping(blocks_air)
+ __update_auxtools_turf_adjacency_info(isspaceturf(get_z_base_turf()))
+
+/turf/proc/ImmediateDisableAdjacency(disable_adjacent = TRUE)
+ if(SSair.thread_running())
+ SSadjacent_air.disable_queue[src] = disable_adjacent
+ return
+ if(disable_adjacent)
+ for(var/direction in GLOB.cardinals_multiz)
+ var/turf/T = get_step_multiz(src, direction)
+ if(!istype(T))
+ continue
+ if (T.atmos_adjacent_turfs)
+ T.atmos_adjacent_turfs -= src
+ UNSETEMPTY(T.atmos_adjacent_turfs)
+ T.__update_auxtools_turf_adjacency_info(isspaceturf(T.get_z_base_turf()), -1)
+ LAZYCLEARLIST(atmos_adjacent_turfs)
+ __update_auxtools_turf_adjacency_info(isspaceturf(get_z_base_turf()))
+
+/turf/proc/set_sleeping(should_sleep)
-/turf/proc/__update_extools_adjacent_turfs()
+/turf/proc/__update_auxtools_turf_adjacency_info()
//returns a list of adjacent turfs that can share air with this one.
//alldir includes adjacent diagonal tiles that can share
@@ -114,7 +132,6 @@
/turf/air_update_turf(command = 0)
if(command)
ImmediateCalculateAdjacentTurfs()
- SSair.add_to_active(src,command)
/atom/movable/proc/move_update_air(turf/T)
if(isturf(T))
@@ -133,7 +150,4 @@
var/datum/gas_mixture/G = new
G.parse_gas_string(text)
-
- air.merge(G)
- archive()
- SSair.add_to_active(src, 0)
+ assume_air(G)
diff --git a/code/modules/atmospherics/environmental/LINDA_turf_tile.dm b/code/modules/atmospherics/environmental/LINDA_turf_tile.dm
index 9660e7e012cbd..2564531906eb6 100644
--- a/code/modules/atmospherics/environmental/LINDA_turf_tile.dm
+++ b/code/modules/atmospherics/environmental/LINDA_turf_tile.dm
@@ -1,18 +1,12 @@
/turf
- //used for temperature calculations
+ //conductivity is divided by 10 when interacting with air for balance purposes
var/thermal_conductivity = 0.05
var/heat_capacity = 1
- var/temperature_archived
//list of open turfs adjacent to us
var/list/atmos_adjacent_turfs
- //bitfield of dirs in which we are superconducitng
- var/atmos_supeconductivity = NONE
- var/is_openturf = FALSE // used by extools shizz.
-
- //used to determine whether we should archive
- var/archived_cycle = 0
- var/current_cycle = 0
+ //bitfield of dirs in which we thermal conductivity is blocked
+ var/conductivity_blocked_directions = NONE
//used for mapping and for breathing while in walls (because that's a thing that needs to be accounted for...)
//string parsed by /datum/gas/proc/copy_from_turf
@@ -32,21 +26,17 @@
var/planetary_atmos = FALSE //air will revert to initial_gas_mix over time
var/list/atmos_overlay_types //gas IDs of current active gas overlays
- is_openturf = TRUE
/turf/open/Initialize()
if(!blocks_air)
- air = new
+ air = new(2500,src)
air.copy_from_turf(src)
- update_air_ref()
+ update_air_ref(planetary_atmos ? 1 : 2)
. = ..()
/turf/open/Destroy()
if(active_hotspot)
QDEL_NULL(active_hotspot)
- // Adds the adjacent turfs to the current atmos processing
- for(var/T in atmos_adjacent_turfs)
- SSair.add_to_active(T)
return ..()
/turf/proc/update_air_ref()
@@ -54,10 +44,46 @@
/////////////////GAS MIXTURE PROCS///////////////////
/turf/open/assume_air(datum/gas_mixture/giver) //use this for machines to adjust air
+ return assume_air_ratio(giver, 1)
+
+/turf/open/assume_air_moles(datum/gas_mixture/giver, moles)
if(!giver)
return FALSE
- air.merge(giver)
- update_visuals()
+ if(SSair.thread_running())
+ SSair.deferred_airs += list(list(giver, air, moles / giver.total_moles()))
+ else
+ giver.transfer_to(air, moles)
+ update_visuals()
+ return TRUE
+
+/turf/open/assume_air_ratio(datum/gas_mixture/giver, ratio)
+ if(!giver)
+ return FALSE
+ if(SSair.thread_running())
+ SSair.deferred_airs += list(list(giver, air, ratio))
+ else
+ giver.transfer_ratio_to(air, ratio)
+ update_visuals()
+ return TRUE
+
+/turf/open/transfer_air(datum/gas_mixture/taker, moles)
+ if(!taker || !return_air()) // shouldn't transfer from space
+ return FALSE
+ if(SSair.thread_running())
+ SSair.deferred_airs += list(list(air, taker, moles / air.total_moles()))
+ else
+ air.transfer_to(taker, moles)
+ update_visuals()
+ return TRUE
+
+/turf/open/transfer_air_ratio(datum/gas_mixture/taker, ratio)
+ if(!taker || !return_air())
+ return FALSE
+ if(SSair.thread_running())
+ SSair.deferred_airs += list(list(air, taker, ratio))
+ else
+ air.transfer_ratio_to(taker, ratio)
+ update_visuals()
return TRUE
/turf/open/remove_air(amount)
@@ -66,6 +92,12 @@
update_visuals()
return removed
+/turf/open/remove_air_ratio(ratio)
+ var/datum/gas_mixture/ours = return_air()
+ var/datum/gas_mixture/removed = ours.remove_ratio(ratio)
+ update_visuals()
+ return removed
+
/turf/open/proc/copy_air_with_tile(turf/open/T)
if(istype(T))
air.copy_from(T.air)
@@ -88,16 +120,9 @@
return return_air()
/turf/temperature_expose()
- if(temperature > heat_capacity)
+ if(return_temperature() > heat_capacity)
to_be_destroyed = TRUE
-/turf/proc/archive()
- temperature_archived = temperature
-
-/turf/open/archive()
- air.archive()
- archived_cycle = SSair.times_fired
- temperature_archived = temperature
/turf/open/proc/eg_reset_cooldowns()
/turf/open/proc/eg_garbage_collect()
@@ -124,9 +149,8 @@
for(var/id in air.get_gases())
if (nonoverlaying_gases[id])
continue
- var/gas_meta = GLOB.meta_gas_info[id]
- var/gas_overlay = gas_meta[META_GAS_OVERLAY]
- if(gas_overlay && air.get_moles(id) > gas_meta[META_GAS_MOLES_VISIBLE])
+ var/gas_overlay = GLOB.gas_data.overlays[id]
+ if(gas_overlay && air.get_moles(id) > GLOB.gas_data.visibility[id])
new_overlay_types += gas_overlay[min(FACTOR_GAS_VISIBLE_MAX, CEILING(air.get_moles(id) / MOLES_GAS_VISIBLE_STEP, 1))]
if (atmos_overlay_types)
@@ -160,12 +184,11 @@
for (var/gastype in subtypesof(/datum/gas))
var/datum/gas/gasvar = gastype
if (!initial(gasvar.gas_overlay))
- .[gastype] = TRUE
+ .[initial(gasvar.id)] = TRUE
/////////////////////////////SIMULATION///////////////////////////////////
/turf/proc/process_cell(fire_count)
- SSair.remove_from_active(src)
/turf/open/proc/equalize_pressure_in_zone(cyclenum)
/turf/open/proc/consider_firelocks(turf/T2)
@@ -185,14 +208,25 @@
/turf/proc/handle_decompression_floor_rip()
/turf/open/floor/handle_decompression_floor_rip(sum)
- if(sum > 20 && prob(CLAMP(sum / 10, 0, 30)))
- remove_tile()
+ if(sum > 20 && prob(CLAMP(sum / 20, 0, 15)))
+ if(floor_tile)
+ new floor_tile(src)
+ make_plating()
+
+/turf/open/floor/plating/handle_decompression_floor_rip()
+ return
+
+/turf/open/floor/engine/handle_decompression_floor_rip()
+ return
/turf/open/process_cell(fire_count)
//////////////////////////SPACEWIND/////////////////////////////
-/turf/open/proc/consider_pressure_difference(turf/T, difference)
+/turf/proc/consider_pressure_difference()
+ return
+
+/turf/open/consider_pressure_difference(turf/T, difference)
SSair.high_pressure_delta |= src
if(difference > pressure_difference)
pressure_direction = get_dir(src, T)
@@ -209,8 +243,6 @@
M = thing
if (!M.anchored && !M.pulledby && M.last_high_pressure_movement_air_cycle < SSair.times_fired)
M.experience_pressure_difference(pressure_difference * multiplier, pressure_direction, 0, pressure_specific_target)
- if(pressure_difference > 100)
- new /obj/effect/temp_visual/dir_setting/space_wind(src, pressure_direction, CLAMP(round(sqrt(pressure_difference) * 2), 10, 255))
/atom/movable/var/pressure_resistance = 10
/atom/movable/var/last_high_pressure_movement_air_cycle = 0
@@ -236,112 +268,3 @@
else
step(src, direction)
last_high_pressure_movement_air_cycle = SSair.times_fired
-
-////////////////////////SUPERCONDUCTIVITY/////////////////////////////
-/turf/proc/conductivity_directions()
- if(archived_cycle < SSair.times_fired)
- archive()
- return NORTH|SOUTH|EAST|WEST
-
-/turf/open/conductivity_directions()
- if(blocks_air)
- return ..()
- for(var/direction in GLOB.cardinals)
- var/turf/T = get_step(src, direction)
- if(!(T in atmos_adjacent_turfs) && !(atmos_supeconductivity & direction))
- . |= direction
-
-/turf/proc/neighbor_conduct_with_src(turf/open/other)
- if(!other.blocks_air) //Open but neighbor is solid
- other.temperature_share_open_to_solid(src)
- else //Both tiles are solid
- other.share_temperature_mutual_solid(src, thermal_conductivity)
- temperature_expose(null, temperature, null)
-
-/turf/open/neighbor_conduct_with_src(turf/other)
- if(blocks_air)
- ..()
- return
-
- if(!other.blocks_air) //Both tiles are open
- var/turf/open/T = other
- T.air.temperature_share(air, WINDOW_HEAT_TRANSFER_COEFFICIENT)
- else //Solid but neighbor is open
- temperature_share_open_to_solid(other)
- SSair.add_to_active(src, 0)
-
-/turf/proc/super_conduct()
- var/conductivity_directions = conductivity_directions()
-
- if(conductivity_directions)
- //Conduct with tiles around me
- for(var/direction in GLOB.cardinals)
- if(conductivity_directions & direction)
- var/turf/neighbor = get_step(src,direction)
-
- if(!neighbor.thermal_conductivity)
- continue
-
- if(neighbor.archived_cycle < SSair.times_fired)
- neighbor.archive()
-
- neighbor.neighbor_conduct_with_src(src)
-
- neighbor.consider_superconductivity()
-
- radiate_to_spess()
-
- finish_superconduction()
-
-/turf/proc/finish_superconduction(temp = temperature)
- //Make sure still hot enough to continue conducting heat
- if(temp < MINIMUM_TEMPERATURE_FOR_SUPERCONDUCTION)
- SSair.active_super_conductivity -= src
- return FALSE
-
-/turf/open/finish_superconduction()
- //Conduct with air on my tile if I have it
- if(!blocks_air)
- temperature = air.temperature_share(null, thermal_conductivity, temperature, heat_capacity)
- ..((blocks_air ? temperature : air.return_temperature()))
-
-/turf/proc/consider_superconductivity()
- if(!thermal_conductivity)
- return FALSE
-
- SSair.active_super_conductivity |= src
- return TRUE
-
-/turf/open/consider_superconductivity(starting)
- if(air.return_temperature() < (starting?MINIMUM_TEMPERATURE_START_SUPERCONDUCTION:MINIMUM_TEMPERATURE_FOR_SUPERCONDUCTION))
- return FALSE
- if(air.heat_capacity() < M_CELL_WITH_RATIO) // Was: MOLES_CELLSTANDARD*0.1*0.05 Since there are no variables here we can make this a constant.
- return FALSE
- return ..()
-
-/turf/closed/consider_superconductivity(starting)
- if(temperature < (starting?MINIMUM_TEMPERATURE_START_SUPERCONDUCTION:MINIMUM_TEMPERATURE_FOR_SUPERCONDUCTION))
- return FALSE
- return ..()
-
-/turf/proc/radiate_to_spess() //Radiate excess tile heat to space
- if(temperature > T0C) //Considering 0 degC as te break even point for radiation in and out
- var/delta_temperature = (temperature_archived - TCMB) //hardcoded space temperature
- if((heat_capacity > 0) && (abs(delta_temperature) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER))
-
- var/heat = thermal_conductivity*delta_temperature* \
- (heat_capacity*HEAT_CAPACITY_VACUUM/(heat_capacity+HEAT_CAPACITY_VACUUM))
- temperature -= heat/heat_capacity
-
-/turf/open/proc/temperature_share_open_to_solid(turf/sharer)
- sharer.temperature = air.temperature_share(null, sharer.thermal_conductivity, sharer.temperature, sharer.heat_capacity)
-
-/turf/proc/share_temperature_mutual_solid(turf/sharer, conduction_coefficient) //to be understood
- var/delta_temperature = (temperature_archived - sharer.temperature_archived)
- if(abs(delta_temperature) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER && heat_capacity && sharer.heat_capacity)
-
- var/heat = conduction_coefficient*delta_temperature* \
- (heat_capacity*sharer.heat_capacity/(heat_capacity+sharer.heat_capacity))
-
- temperature -= heat/heat_capacity
- sharer.temperature += heat/sharer.heat_capacity
diff --git a/code/modules/atmospherics/gasmixtures/auxgm.dm b/code/modules/atmospherics/gasmixtures/auxgm.dm
new file mode 100644
index 0000000000000..04ebc3584e7f0
--- /dev/null
+++ b/code/modules/atmospherics/gasmixtures/auxgm.dm
@@ -0,0 +1,130 @@
+GLOBAL_LIST_INIT(hardcoded_gases, list(GAS_O2, GAS_N2, GAS_CO2, GAS_PLASMA)) //the main four gases, which were at one time hardcoded
+GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(GAS_O2, GAS_N2, GAS_CO2, GAS_PLUOXIUM, GAS_STIMULUM, GAS_NITRYL))) //unable to react amongst themselves
+
+// Auxgm
+// It's a send-up of XGM, like what baystation got.
+// It's got the same architecture as XGM, but it's structured
+// differently to make it more convenient for auxmos.
+
+// Most important compared to TG is that it does away with hardcoded typepaths,
+// which lead to problems on the auxmos end anyway. We cache the string value
+// references on the Rust end, so no performance is lost here.
+
+// Also allows you to add new gases at runtime
+
+/proc/_auxtools_register_gas(datum/gas/gas) // makes sure auxtools knows stuff about this gas
+
+/datum/auxgm
+ var/list/datums = list()
+ var/list/specific_heats = list()
+ var/list/names = list()
+ var/list/visibility = list()
+ var/list/overlays = list()
+ var/list/flags = list()
+ var/list/ids = list()
+ var/list/typepaths = list()
+ var/list/fusion_powers = list()
+ var/list/breathing_classes = list()
+ var/list/breath_results = list()
+ var/list/breath_reagents = list()
+ var/list/breath_reagents_dangerous = list()
+ var/list/breath_alert_info = list()
+ var/list/oxidation_temperatures = list()
+ var/list/oxidation_rates = list()
+ var/list/fire_temperatures = list()
+ var/list/fire_enthalpies = list()
+ var/list/fire_products = list()
+ var/list/fire_burn_rates = list()
+
+
+/datum/gas
+ var/id = ""
+ var/specific_heat = 0
+ var/name = ""
+ var/gas_overlay = "" //icon_state in icons/effects/atmospherics.dmi
+ var/moles_visible = null
+ var/flags = NONE //currently used by canisters
+ var/fusion_power = 0 // How much the gas destabilizes a fusion reaction
+ var/breath_results = GAS_CO2 // what breathing this breathes out
+ var/breath_reagent = null // what breathing this adds to your reagents
+ var/breath_reagent_dangerous = null // what breathing this adds to your reagents IF it's above a danger threshold
+ var/list/breath_alert_info = null // list for alerts that pop up when you have too much/not enough of something
+ var/oxidation_temperature = null // temperature above which this gas is an oxidizer; null for none
+ var/oxidation_rate = 1 // how many moles of this can oxidize how many moles of material
+ var/fire_temperature = null // temperature above which gas may catch fire; null for none
+ var/list/fire_products = null // what results when this gas is burned (oxidizer or fuel); null for none
+ var/fire_energy_released = 0 // how much energy is released per mole of fuel burned
+ var/fire_burn_rate = 1 // how many moles are burned per product released
+
+/datum/gas/proc/breath(partial_pressure, light_threshold, heavy_threshold, moles, mob/living/carbon/C, obj/item/organ/lungs/lungs)
+ // This is only called on gases with the GAS_FLAG_BREATH_PROC flag. When possible, do NOT use this--
+ // greatly prefer just adding a reagent. This is mostly around for legacy reasons.
+ return null
+
+/datum/auxgm/proc/add_gas(datum/gas/gas)
+ var/g = gas.id
+ if(g)
+ datums[g] = gas
+ specific_heats[g] = gas.specific_heat
+ names[g] = gas.name
+ if(gas.moles_visible)
+ visibility[g] = gas.moles_visible
+ overlays[g] = new /list(FACTOR_GAS_VISIBLE_MAX)
+ for(var/i in 1 to FACTOR_GAS_VISIBLE_MAX)
+ overlays[g][i] = new /obj/effect/overlay/gas(gas.gas_overlay, i * 255 / FACTOR_GAS_VISIBLE_MAX)
+ else
+ visibility[g] = 0
+ overlays[g] = 0
+ flags[g] = gas.flags
+ ids[g] = g
+ typepaths[g] = gas.type
+ fusion_powers[g] = gas.fusion_power
+
+ if(gas.breath_alert_info)
+ breath_alert_info[g] = gas.breath_alert_info
+ breath_results[g] = gas.breath_results
+ if(gas.breath_reagent)
+ breath_reagents[g] = gas.breath_reagent
+ if(gas.breath_reagent_dangerous)
+ breath_reagents_dangerous[g] = gas.breath_reagent_dangerous
+
+ if(gas.oxidation_temperature)
+ oxidation_temperatures[g] = gas.oxidation_temperature
+ oxidation_rates[g] = gas.oxidation_rate
+ if(gas.fire_products)
+ fire_products[g] = gas.fire_products
+ fire_enthalpies[g] = gas.fire_energy_released
+ else if(gas.fire_temperature)
+ fire_temperatures[g] = gas.fire_temperature
+ fire_burn_rates[g] = gas.fire_burn_rate
+ if(gas.fire_products)
+ fire_products[g] = gas.fire_products
+ fire_enthalpies[g] = gas.fire_energy_released
+
+ _auxtools_register_gas(gas)
+
+/proc/finalize_gas_refs()
+
+/datum/auxgm/New()
+ for(var/gas_path in subtypesof(/datum/gas))
+ var/datum/gas/gas = new gas_path
+ add_gas(gas)
+ for(var/breathing_class_path in subtypesof(/datum/breathing_class))
+ var/datum/breathing_class/class = new breathing_class_path
+ breathing_classes[breathing_class_path] = class
+ finalize_gas_refs()
+
+GLOBAL_DATUM_INIT(gas_data, /datum/auxgm, new)
+
+/obj/effect/overlay/gas
+ icon = 'icons/effects/atmospherics.dmi'
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ anchored = TRUE // should only appear in vis_contents, but to be safe
+ layer = FLY_LAYER
+ appearance_flags = TILE_BOUND
+ vis_flags = NONE
+
+/obj/effect/overlay/gas/New(state, alph)
+ . = ..()
+ icon_state = state
+ alpha = alph
diff --git a/code/modules/atmospherics/gasmixtures/gas_mixture.dm b/code/modules/atmospherics/gasmixtures/gas_mixture.dm
index 7e885c31d46d9..400405bac9303 100644
--- a/code/modules/atmospherics/gasmixtures/gas_mixture.dm
+++ b/code/modules/atmospherics/gasmixtures/gas_mixture.dm
@@ -7,38 +7,112 @@ What are the archived variables for?
#define MINIMUM_MOLE_COUNT 0.01
#define QUANTIZE(variable) (round(variable,0.0000001))/*I feel the need to document what happens here. Basically this is used to catch most rounding errors, however it's previous value made it so that
once gases got hot enough, most procedures wouldnt occur due to the fact that the mole counts would get rounded away. Thus, we lowered it a few orders of magnititude */
-GLOBAL_LIST_INIT(meta_gas_info, meta_gas_list()) //see ATMOSPHERICS/gas_types.dm
-GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache())
-
-/proc/init_gaslist_cache()
- . = list()
- for(var/id in GLOB.meta_gas_info)
- var/list/cached_gas = new(3)
-
- .[id] = cached_gas
-
- cached_gas[MOLES] = 0
- cached_gas[ARCHIVE] = 0
- cached_gas[GAS_META] = GLOB.meta_gas_info[id]
/datum/gas_mixture
+ /// Never ever set this variable, hooked into vv_get_var for view variables viewing.
+ var/gas_list_view_only
var/initial_volume = CELL_VOLUME //liters
var/list/reaction_results
var/list/analyzer_results //used for analyzer feedback - not initialized until its used
- var/_extools_pointer_gasmixture = 0 // Contains the memory address of the shared_ptr object for this gas mixture in c++ land. Don't. Touch. This. Var.
+ var/_extools_pointer_gasmixture // Contains the index in the gas vector for this gas mixture in rust land. Don't. Touch. This. Var.
+
+GLOBAL_LIST_INIT(auxtools_atmos_initialized, FALSE)
+
+/proc/auxtools_atmos_init()
/datum/gas_mixture/New(volume)
if (!isnull(volume))
initial_volume = volume
- ATMOS_EXTOOLS_CHECK
+ AUXTOOLS_CHECK(AUXMOS)
+ if(!GLOB.auxtools_atmos_initialized && auxtools_atmos_init())
+ GLOB.auxtools_atmos_initialized = TRUE
__gasmixture_register()
reaction_results = new
+/*
+we use a hook instead
+/datum/gas_mixture/Del()
+ __gasmixture_unregister()
+ . = ..()
+*/
+
/datum/gas_mixture/vv_edit_var(var_name, var_value)
if(var_name == "_extools_pointer_gasmixture")
return FALSE // please no. segfaults bad.
+ if(var_name == "gas_list_view_only")
+ return FALSE
return ..()
+/datum/gas_mixture/vv_get_var(var_name)
+ . = ..()
+ if(var_name == "gas_list_view_only")
+ var/list/dummy = get_gases()
+ for(var/gas in dummy)
+ dummy[gas] = get_moles(gas)
+ dummy["CAP [gas]"] = partial_heat_capacity(gas)
+ dummy["TEMP"] = return_temperature()
+ dummy["PRESSURE"] = return_pressure()
+ dummy["HEAT CAPACITY"] = heat_capacity()
+ dummy["TOTAL MOLES"] = total_moles()
+ dummy["VOLUME"] = return_volume()
+ dummy["THERMAL ENERGY"] = thermal_energy()
+ return debug_variable("gases (READ ONLY)", dummy, 0, src)
+
+/datum/gas_mixture/vv_get_dropdown()
+ . = ..()
+ VV_DROPDOWN_OPTION("", "---")
+ VV_DROPDOWN_OPTION(VV_HK_PARSE_GASSTRING, "Parse Gas String")
+ VV_DROPDOWN_OPTION(VV_HK_EMPTY, "Empty")
+ VV_DROPDOWN_OPTION(VV_HK_SET_MOLES, "Set Moles")
+ VV_DROPDOWN_OPTION(VV_HK_SET_TEMPERATURE, "Set Temperature")
+ VV_DROPDOWN_OPTION(VV_HK_SET_VOLUME, "Set Volume")
+
+/datum/gas_mixture/vv_do_topic(list/href_list)
+ . = ..()
+ if(!.)
+ return
+ if(href_list[VV_HK_PARSE_GASSTRING])
+ var/gasstring = input(usr, "Input Gas String (WARNING: Advanced. Don't use this unless you know how these work.", "Gas String Parse") as text|null
+ if(!istext(gasstring))
+ return
+ log_admin("[key_name(usr)] modified gas mixture [REF(src)]: Set to gas string [gasstring].")
+ message_admins("[key_name(usr)] modified gas mixture [REF(src)]: Set to gas string [gasstring].")
+ parse_gas_string(gasstring)
+ if(href_list[VV_HK_EMPTY])
+ log_admin("[key_name(usr)] emptied gas mixture [REF(src)].")
+ message_admins("[key_name(usr)] emptied gas mixture [REF(src)].")
+ clear()
+ if(href_list[VV_HK_SET_MOLES])
+ var/list/gases = get_gases()
+ for(var/gas in gases)
+ gases[gas] = get_moles(gas)
+ var/gasid = input(usr, "What kind of gas?", "Set Gas") as null|anything in GLOB.gas_data.ids
+ if(!gasid)
+ return
+ var/amount = input(usr, "Input amount", "Set Gas", gases[gasid] || 0) as num|null
+ if(!isnum(amount))
+ return
+ amount = max(0, amount)
+ log_admin("[key_name(usr)] modified gas mixture [REF(src)]: Set gas [gasid] to [amount] moles.")
+ message_admins("[key_name(usr)] modified gas mixture [REF(src)]: Set gas [gasid] to [amount] moles.")
+ set_moles(gasid, amount)
+ if(href_list[VV_HK_SET_TEMPERATURE])
+ var/temp = input(usr, "Set the temperature of this mixture to?", "Set Temperature", return_temperature()) as num|null
+ if(!isnum(temp))
+ return
+ temp = max(2.7, temp)
+ log_admin("[key_name(usr)] modified gas mixture [REF(src)]: Changed temperature to [temp].")
+ message_admins("[key_name(usr)] modified gas mixture [REF(src)]: Changed temperature to [temp].")
+ set_temperature(temp)
+ if(href_list[VV_HK_SET_VOLUME])
+ var/volume = input(usr, "Set the volume of this mixture to?", "Set Volume", return_volume()) as num|null
+ if(!isnum(volume))
+ return
+ volume = max(0, volume)
+ log_admin("[key_name(usr)] modified gas mixture [REF(src)]: Changed volume to [volume].")
+ message_admins("[key_name(usr)] modified gas mixture [REF(src)]: Changed volume to [volume].")
+ set_volume(volume)
+
/datum/gas_mixture/proc/__gasmixture_unregister()
/datum/gas_mixture/proc/__gasmixture_register()
@@ -49,7 +123,9 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache())
L[gt] = initial(G.specific_heat)
return L
-/datum/gas_mixture/proc/heat_capacity(data = MOLES) //joules per kelvin
+/datum/gas_mixture/proc/heat_capacity() //joules per kelvin
+
+/datum/gas_mixture/proc/partial_heat_capacity(gas_type)
/datum/gas_mixture/proc/total_moles()
@@ -62,7 +138,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache())
/datum/gas_mixture/proc/set_volume(new_volume)
/datum/gas_mixture/proc/get_moles(gas_type)
/datum/gas_mixture/proc/set_moles(gas_type, moles)
-/datum/gas_mixture/proc/scrub_into(datum/gas_mixture/target, list/gases)
+/datum/gas_mixture/proc/scrub_into(datum/gas_mixture/target, ratio, list/gases)
/datum/gas_mixture/proc/mark_immutable()
/datum/gas_mixture/proc/get_gases()
/datum/gas_mixture/proc/multiply(factor)
@@ -70,7 +146,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache())
/datum/gas_mixture/proc/clear()
/datum/gas_mixture/proc/adjust_moles(gas_type, amt = 0)
- set_moles(gas_type, get_moles(gas_type) + amt)
+ set_moles(gas_type, clamp(get_moles(gas_type) + amt,0,INFINITY))
/datum/gas_mixture/proc/return_volume() //liters
@@ -81,13 +157,18 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache())
//Returns: 1 in all cases
/datum/gas_mixture/proc/merge(datum/gas_mixture/giver)
- //Merges all air from giver into self. Deletes giver.
+ //Merges all air from giver into self. Does NOT delete the giver.
//Returns: 1 if we are mutable, 0 otherwise
/datum/gas_mixture/proc/remove(amount)
//Proportionally removes amount of gas from the gas_mixture
//Returns: gas_mixture with the gases removed
+/datum/gas_mixture/proc/transfer_to(datum/gas_mixture/target, amount)
+
+/datum/gas_mixture/proc/transfer_ratio_to(datum/gas_mixture/target, ratio)
+ //Transfers ratio of gas to target. Equivalent to target.merge(remove_ratio(amount)) but faster.
+
/datum/gas_mixture/proc/remove_ratio(ratio)
//Proportionally removes amount of gas from the gas_mixture
//Returns: gas_mixture with the gases removed
@@ -124,6 +205,24 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache())
//Performs various reactions such as combustion or fusion (LOL)
//Returns: 1 if any reaction took place; 0 otherwise
+/datum/gas_mixture/proc/adjust_heat(amt)
+ //Adjusts the thermal energy of the gas mixture, rather than having to do the full calculation.
+ //Returns: null
+
+/datum/gas_mixture/proc/equalize_with(datum/gas_mixture/giver)
+ //Makes this mix have the same temperature and gas ratios as the giver, but with the same pressure, accounting for volume.
+ //Returns: null
+
+/datum/gas_mixture/proc/get_oxidation_power(temp)
+ //Gets how much oxidation this gas can do, optionally at a given temperature.
+
+/datum/gas_mixture/proc/get_fuel_amount(temp)
+ //Gets how much fuel for fires (not counting trit/plasma!) this gas has, optionally at a given temperature.
+
+/proc/equalize_all_gases_in_list(list/L)
+ //Makes every gas in the given list have the same pressure, temperature and gas proportions.
+ //Returns: null
+
/datum/gas_mixture/proc/__remove()
/datum/gas_mixture/remove(amount)
var/datum/gas_mixture/removed = new type
@@ -145,73 +244,27 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache())
return copy
/datum/gas_mixture/copy_from_turf(turf/model)
+ set_temperature(initial(model.initial_temperature))
parse_gas_string(model.initial_gas_mix)
-
- //acounts for changes in temperature
- var/turf/model_parent = model.parent_type
- if(model.temperature != initial(model.temperature) || model.temperature != initial(model_parent.temperature))
- set_temperature(model.temperature)
-
return 1
/datum/gas_mixture/parse_gas_string(gas_string)
- gas_string = SSair.preprocess_gas_string(gas_string)
-
var/list/gas = params2list(gas_string)
if(gas["TEMP"])
- set_temperature(text2num(gas["TEMP"]))
+ var/temp = text2num(gas["TEMP"])
gas -= "TEMP"
+ if(!isnum(temp) || temp < 2.7)
+ temp = 2.7
+ set_temperature(temp)
clear()
for(var/id in gas)
- var/path = id
- if(!ispath(path))
- path = gas_id2path(path) //a lot of these strings can't have embedded expressions (especially for mappers), so support for IDs needs to stick around
- set_moles(path, text2num(gas[id]))
+ set_moles(id, text2num(gas[id]))
return 1
-/datum/gas_mixture/react(datum/holder)
- . = NO_REACTION
- var/list/reactions = list()
- for(var/I in get_gases())
- reactions += SSair.gas_reactions[I]
- if(!length(reactions))
- return
- reaction_results = new
- var/temp = return_temperature()
- var/ener = thermal_energy()
-
- reaction_loop:
- for(var/r in reactions)
- var/datum/gas_reaction/reaction = r
-
- var/list/min_reqs = reaction.min_requirements
- if((min_reqs["TEMP"] && temp < min_reqs["TEMP"]) \
- || (min_reqs["ENER"] && ener < min_reqs["ENER"]))
- continue
-
- for(var/id in min_reqs)
- if (id == "TEMP" || id == "ENER")
- continue
- if(get_moles(id) < min_reqs[id])
- continue reaction_loop
-
- //at this point, all requirements for the reaction are satisfied. we can now react()
-
- . |= reaction.react(src, holder)
- if (. & STOP_REACTIONS)
- break
-
-//Takes the amount of the gas you want to PP as an argument
-//So I don't have to do some hacky switches/defines/magic strings
-//eg:
-//Tox_PP = get_partial_pressure(gas_mixture.toxins)
-//O2_PP = get_partial_pressure(gas_mixture.oxygen)
-
-/datum/gas_mixture/proc/get_breath_partial_pressure(gas_pressure)
- return (gas_pressure * R_IDEAL_GAS_EQUATION * return_temperature()) / BREATH_VOLUME
-//inverse
-/datum/gas_mixture/proc/get_true_breath_pressure(partial_pressure)
- return (partial_pressure * BREATH_VOLUME) / (R_IDEAL_GAS_EQUATION * return_temperature())
+/datum/gas_mixture/proc/set_analyzer_results(instability)
+ if(!analyzer_results)
+ analyzer_results = new
+ analyzer_results["fusion"] = instability
//Mathematical proofs:
/*
@@ -223,3 +276,26 @@ get_true_breath_pressure(pp) --> gas_pp = pp/breath_pp*total_moles()
*/
/datum/gas_mixture/turf
+
+/// Releases gas from src to output air. This means that it can not transfer air to gas mixture with higher pressure.
+/datum/gas_mixture/proc/release_gas_to(datum/gas_mixture/output_air, target_pressure)
+ var/output_starting_pressure = output_air.return_pressure()
+ var/input_starting_pressure = return_pressure()
+
+ if(output_starting_pressure >= min(target_pressure,input_starting_pressure-10))
+ //No need to pump gas if target is already reached or input pressure is too low
+ //Need at least 10 kPa difference to overcome friction in the mechanism
+ return FALSE
+
+ //Calculate necessary moles to transfer using PV = nRT
+ if((total_moles() > 0) && (return_temperature()>0))
+ var/pressure_delta = min(target_pressure - output_starting_pressure, (input_starting_pressure - output_starting_pressure)/2)
+ //Can not have a pressure delta that would cause output_pressure > input_pressure
+
+ var/transfer_moles = pressure_delta*output_air.return_volume()/(return_temperature() * R_IDEAL_GAS_EQUATION)
+
+ //Actually transfer the gas
+ var/datum/gas_mixture/removed = remove(transfer_moles)
+ output_air.merge(removed)
+ return TRUE
+ return FALSE
\ No newline at end of file
diff --git a/code/modules/atmospherics/gasmixtures/gas_types.dm b/code/modules/atmospherics/gasmixtures/gas_types.dm
deleted file mode 100644
index 839b234ed2220..0000000000000
--- a/code/modules/atmospherics/gasmixtures/gas_types.dm
+++ /dev/null
@@ -1,176 +0,0 @@
-GLOBAL_LIST_INIT(hardcoded_gases, list(/datum/gas/oxygen, /datum/gas/nitrogen, /datum/gas/carbon_dioxide, /datum/gas/plasma)) //the main four gases, which were at one time hardcoded
-GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/gas/nitrogen, /datum/gas/carbon_dioxide, /datum/gas/pluoxium, /datum/gas/stimulum, /datum/gas/nitryl))) //unable to react amongst themselves
-
-/proc/meta_gas_list()
- . = subtypesof(/datum/gas)
- for(var/gas_path in .)
- var/list/gas_info = new(7)
- var/datum/gas/gas = gas_path
-
- gas_info[META_GAS_SPECIFIC_HEAT] = initial(gas.specific_heat)
- gas_info[META_GAS_NAME] = initial(gas.name)
-
- gas_info[META_GAS_MOLES_VISIBLE] = initial(gas.moles_visible)
- if(initial(gas.moles_visible) != null)
- gas_info[META_GAS_OVERLAY] = new /list(FACTOR_GAS_VISIBLE_MAX)
- for(var/i in 1 to FACTOR_GAS_VISIBLE_MAX)
- gas_info[META_GAS_OVERLAY][i] = new /obj/effect/overlay/gas(initial(gas.gas_overlay), i * 255 / FACTOR_GAS_VISIBLE_MAX)
-
- gas_info[META_GAS_FUSION_POWER] = initial(gas.fusion_power)
- gas_info[META_GAS_DANGER] = initial(gas.dangerous)
- gas_info[META_GAS_ID] = initial(gas.id)
- .[gas_path] = gas_info
-
-/proc/gas_id2path(id)
- var/list/meta_gas = GLOB.meta_gas_info
- if(id in meta_gas)
- return id
- for(var/path in meta_gas)
- if(meta_gas[path][META_GAS_ID] == id)
- return path
- return ""
-
-/*||||||||||||||/----------\||||||||||||||*\
-||||||||||||||||[GAS DATUMS]||||||||||||||||
-||||||||||||||||\__________/||||||||||||||||
-||||These should never be instantiated. ||||
-||||They exist only to make it easier ||||
-||||to add a new gas. They are accessed ||||
-||||only by meta_gas_list(). ||||
-\*||||||||||||||||||||||||||||||||||||||||*/
-
-/datum/gas
- var/id = ""
- var/specific_heat = 0
- var/name = ""
- var/gas_overlay = "" //icon_state in icons/effects/atmospherics.dmi
- var/moles_visible = null
- var/dangerous = FALSE //currently used by canisters
- var/fusion_power = 0 //How much the gas accelerates a fusion reaction
- var/rarity = 0 // relative rarity compared to other gases, used when setting up the reactions list.
-
-// If you add or remove gases, update TOTAL_NUM_GASES in the extools code to match! Extools currently expects 14 gas types to exist.
-
-/datum/gas/oxygen
- id = "o2"
- specific_heat = 20
- name = "Oxygen"
- rarity = 900
-
-/datum/gas/nitrogen
- id = "n2"
- specific_heat = 20
- name = "Nitrogen"
- rarity = 1000
-
-/datum/gas/carbon_dioxide //what the fuck is this?
- id = "co2"
- specific_heat = 30
- name = "Carbon Dioxide"
- rarity = 700
-
-/datum/gas/plasma
- id = "plasma"
- specific_heat = 200
- name = "Plasma"
- gas_overlay = "plasma"
- moles_visible = MOLES_GAS_VISIBLE
- dangerous = TRUE
- rarity = 800
-
-/datum/gas/water_vapor
- id = "water_vapor"
- specific_heat = 40
- name = "Water Vapor"
- gas_overlay = "water_vapor"
- moles_visible = MOLES_GAS_VISIBLE
- fusion_power = 8
- rarity = 500
-
-/datum/gas/hypernoblium
- id = "nob"
- specific_heat = 2000
- name = "Hyper-noblium"
- gas_overlay = "freon"
- moles_visible = MOLES_GAS_VISIBLE
- dangerous = TRUE
- rarity = 50
-
-/datum/gas/nitrous_oxide
- id = "n2o"
- specific_heat = 40
- name = "Nitrous Oxide"
- gas_overlay = "nitrous_oxide"
- moles_visible = MOLES_GAS_VISIBLE * 2
- fusion_power = 10
- dangerous = TRUE
- rarity = 600
-
-/datum/gas/nitryl
- id = "no2"
- specific_heat = 20
- name = "Nitryl"
- gas_overlay = "nitryl"
- moles_visible = MOLES_GAS_VISIBLE
- dangerous = TRUE
- fusion_power = 16
- rarity = 100
-
-/datum/gas/tritium
- id = "tritium"
- specific_heat = 10
- name = "Tritium"
- gas_overlay = "tritium"
- moles_visible = MOLES_GAS_VISIBLE
- dangerous = TRUE
- fusion_power = 1
- rarity = 300
-
-/datum/gas/bz
- id = "bz"
- specific_heat = 20
- name = "BZ"
- dangerous = TRUE
- fusion_power = 8
- rarity = 400
-
-/datum/gas/stimulum
- id = "stim"
- specific_heat = 5
- name = "Stimulum"
- fusion_power = 7
- rarity = 1
-
-/datum/gas/pluoxium
- id = "pluox"
- specific_heat = 80
- name = "Pluoxium"
- fusion_power = -10
- rarity = 200
-
-/datum/gas/miasma
- id = "miasma"
- specific_heat = 20
- name = "Miasma"
- gas_overlay = "miasma"
- moles_visible = MOLES_GAS_VISIBLE * 60
- rarity = 250
-
-/datum/gas/unobtanium //C++ monstermos expects 14 gas types to exist, we only had 13
- id = "unobtanium"
- specific_heat = 20
- name = "Unobtanium"
- rarity = 2500
-
-/obj/effect/overlay/gas
- icon = 'icons/effects/atmospherics.dmi'
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- anchored = TRUE // should only appear in vis_contents, but to be safe
- layer = FLY_LAYER
- appearance_flags = TILE_BOUND
- vis_flags = NONE
-
-/obj/effect/overlay/gas/New(state, alph)
- . = ..()
- icon_state = state
- alpha = alph
diff --git a/code/modules/atmospherics/gasmixtures/immutable_mixtures.dm b/code/modules/atmospherics/gasmixtures/immutable_mixtures.dm
index 2f1a10e2756ab..7de056ed0261b 100644
--- a/code/modules/atmospherics/gasmixtures/immutable_mixtures.dm
+++ b/code/modules/atmospherics/gasmixtures/immutable_mixtures.dm
@@ -25,4 +25,4 @@
initial_temperature = T20C
/datum/gas_mixture/immutable/cloner/populate()
- set_moles(/datum/gas/nitrogen, MOLES_O2STANDARD + MOLES_N2STANDARD)
+ set_moles(GAS_N2, MOLES_O2STANDARD + MOLES_N2STANDARD)
diff --git a/code/modules/atmospherics/gasmixtures/reactions.dm b/code/modules/atmospherics/gasmixtures/reactions.dm
index a2ec4cc58e3e0..3f10e57a34190 100644
--- a/code/modules/atmospherics/gasmixtures/reactions.dm
+++ b/code/modules/atmospherics/gasmixtures/reactions.dm
@@ -2,22 +2,14 @@
/proc/init_gas_reactions()
. = list()
- for(var/type in subtypesof(/datum/gas))
- .[type] = list()
for(var/r in subtypesof(/datum/gas_reaction))
var/datum/gas_reaction/reaction = r
if(initial(reaction.exclude))
continue
reaction = new r
- var/datum/gas/reaction_key
- for (var/req in reaction.min_requirements)
- if (ispath(req))
- var/datum/gas/req_gas = req
- if (!reaction_key || initial(reaction_key.rarity) > initial(req_gas.rarity))
- reaction_key = req_gas
- .[reaction_key] += list(reaction)
- sortTim(., /proc/cmp_gas_reactions, TRUE)
+ . += reaction
+ sortTim(., /proc/cmp_gas_reactions)
/proc/cmp_gas_reactions(list/datum/gas_reaction/a, list/datum/gas_reaction/b) // compares lists of reactions by the maximum priority contained within the list
if (!length(a) || !length(b))
@@ -36,7 +28,7 @@
//regarding the requirements lists: the minimum or maximum requirements must be non-zero.
//when in doubt, use MINIMUM_MOLE_COUNT.
var/list/min_requirements
- var/major_gas //the highest rarity gas used in the reaction.
+ var/list/max_requirements
var/exclude = FALSE //do it this way to allow for addition/removal of reactions midmatch in the future
var/priority = 100 //lower numbers are checked/react later than higher numbers. if two reactions have the same priority they may happen in either order
var/name = "reaction"
@@ -56,7 +48,7 @@
id = "nobstop"
/datum/gas_reaction/nobliumsupression/init_reqs()
- min_requirements = list(/datum/gas/hypernoblium = REACTION_OPPRESSION_THRESHOLD)
+ min_requirements = list(GAS_HYPERNOB = REACTION_OPPRESSION_THRESHOLD)
/datum/gas_reaction/nobliumsupression/react()
return STOP_REACTIONS
@@ -68,7 +60,7 @@
id = "vapor"
/datum/gas_reaction/water_vapor/init_reqs()
- min_requirements = list(/datum/gas/water_vapor = MOLES_GAS_VISIBLE)
+ min_requirements = list(GAS_H2O = MOLES_GAS_VISIBLE)
/datum/gas_reaction/water_vapor/react(datum/gas_mixture/air, datum/holder)
var/turf/open/location = isturf(holder) ? holder : null
@@ -77,7 +69,7 @@
if(location && location.freon_gas_act())
. = REACTING
else if(location && location.water_vapor_gas_act())
- air.adjust_moles(/datum/gas/water_vapor, -MOLES_GAS_VISIBLE)
+ air.adjust_moles(GAS_H2O, -MOLES_GAS_VISIBLE)
. = REACTING
//tritium combustion: combustion of oxygen and tritium (treated as hydrocarbons). creates hotspots. exothermic
@@ -89,7 +81,7 @@
/datum/gas_reaction/nitrous_decomp/init_reqs()
min_requirements = list(
"TEMP" = N2O_DECOMPOSITION_MIN_ENERGY,
- /datum/gas/nitrous_oxide = MINIMUM_MOLE_COUNT
+ GAS_NITROUS = MINIMUM_MOLE_COUNT
)
/datum/gas_reaction/nitrous_decomp/react(datum/gas_mixture/air, datum/holder)
@@ -99,14 +91,14 @@
var/burned_fuel = 0
- burned_fuel = max(0,0.00002*(temperature-(0.00001*(temperature**2))))*air.get_moles(/datum/gas/nitrous_oxide)
- air.set_moles(/datum/gas/nitrous_oxide, air.get_moles(/datum/gas/nitrous_oxide) - burned_fuel)
+ burned_fuel = max(0,0.00002*(temperature-(0.00001*(temperature**2))))*air.get_moles(GAS_NITROUS)
+ air.set_moles(GAS_NITROUS, air.get_moles(GAS_NITROUS) - burned_fuel)
if(burned_fuel)
energy_released += (N2O_DECOMPOSITION_ENERGY_RELEASED * burned_fuel)
- air.set_moles(/datum/gas/oxygen, air.get_moles(/datum/gas/oxygen) + burned_fuel/2)
- air.set_moles(/datum/gas/nitrogen, air.get_moles(/datum/gas/nitrogen) + burned_fuel)
+ air.set_moles(GAS_O2, air.get_moles(GAS_O2) + burned_fuel/2)
+ air.set_moles(GAS_N2, air.get_moles(GAS_N2) + burned_fuel)
var/new_heat_capacity = air.heat_capacity()
if(new_heat_capacity > MINIMUM_HEAT_CAPACITY)
@@ -123,10 +115,22 @@
/datum/gas_reaction/tritfire/init_reqs()
min_requirements = list(
"TEMP" = FIRE_MINIMUM_TEMPERATURE_TO_EXIST,
- /datum/gas/tritium = MINIMUM_MOLE_COUNT,
- /datum/gas/oxygen = MINIMUM_MOLE_COUNT
+ GAS_TRITIUM = MINIMUM_MOLE_COUNT,
+ GAS_O2 = MINIMUM_MOLE_COUNT
)
+/proc/fire_expose(turf/open/location, datum/gas_mixture/air, temperature)
+ if(istype(location) && temperature > FIRE_MINIMUM_TEMPERATURE_TO_EXIST)
+ location.hotspot_expose(temperature, CELL_VOLUME)
+ for(var/I in location)
+ var/atom/movable/item = I
+ item.temperature_expose(air, temperature, CELL_VOLUME)
+ location.temperature_expose(air, temperature, CELL_VOLUME)
+
+/proc/radiation_burn(turf/open/location, energy_released)
+ if(istype(location) && prob(10))
+ radiation_pulse(location, energy_released/TRITIUM_BURN_RADIOACTIVITY_FACTOR)
+
/datum/gas_reaction/tritfire/react(datum/gas_mixture/air, datum/holder)
var/energy_released = 0
var/old_heat_capacity = air.heat_capacity()
@@ -135,15 +139,15 @@
cached_results["fire"] = 0
var/turf/open/location = isturf(holder) ? holder : null
var/burned_fuel = 0
- var/initial_trit = air.get_moles(/datum/gas/tritium)// Yogs
- if(air.get_moles(/datum/gas/oxygen) < initial_trit || MINIMUM_TRIT_OXYBURN_ENERGY > (temperature * old_heat_capacity))// Yogs -- Maybe a tiny performance boost? I'unno
- burned_fuel = air.get_moles(/datum/gas/oxygen)/TRITIUM_BURN_OXY_FACTOR
+ var/initial_trit = air.get_moles(GAS_TRITIUM)// Yogs
+ if(air.get_moles(GAS_O2) < initial_trit || MINIMUM_TRIT_OXYBURN_ENERGY > (temperature * old_heat_capacity))// Yogs -- Maybe a tiny performance boost? I'unno
+ burned_fuel = air.get_moles(GAS_O2)/TRITIUM_BURN_OXY_FACTOR
if(burned_fuel > initial_trit) burned_fuel = initial_trit //Yogs -- prevents negative moles of Tritium
- air.adjust_moles(/datum/gas/tritium, -burned_fuel)
+ air.adjust_moles(GAS_TRITIUM, -burned_fuel)
else
burned_fuel = initial_trit // Yogs -- Conservation of Mass fix
- air.set_moles(/datum/gas/tritium, air.get_moles(/datum/gas/tritium) * (1 - 1/TRITIUM_BURN_TRIT_FACTOR)) // Yogs -- Maybe a tiny performance boost? I'unno
- air.adjust_moles(/datum/gas/oxygen, -air.get_moles(/datum/gas/tritium))
+ air.set_moles(GAS_TRITIUM, air.get_moles(GAS_TRITIUM) * (1 - 1/TRITIUM_BURN_TRIT_FACTOR)) // Yogs -- Maybe a tiny performance boost? I'unno
+ air.adjust_moles(GAS_O2, -air.get_moles(GAS_TRITIUM))
energy_released += (FIRE_HYDROGEN_ENERGY_RELEASED * burned_fuel * (TRITIUM_BURN_TRIT_FACTOR - 1)) // Yogs -- Fixes low-energy tritium fires
if(burned_fuel)
@@ -152,7 +156,7 @@
radiation_pulse(location, energy_released/TRITIUM_BURN_RADIOACTIVITY_FACTOR)
//oxygen+more-or-less hydrogen=H2O
- air.adjust_moles(/datum/gas/water_vapor, burned_fuel )// Yogs -- Conservation of Mass
+ air.adjust_moles(GAS_H2O, burned_fuel )// Yogs -- Conservation of Mass
cached_results["fire"] += burned_fuel
@@ -182,8 +186,8 @@
/datum/gas_reaction/plasmafire/init_reqs()
min_requirements = list(
"TEMP" = FIRE_MINIMUM_TEMPERATURE_TO_EXIST,
- /datum/gas/plasma = MINIMUM_MOLE_COUNT,
- /datum/gas/oxygen = MINIMUM_MOLE_COUNT
+ GAS_PLASMA = MINIMUM_MOLE_COUNT,
+ GAS_O2 = MINIMUM_MOLE_COUNT
)
/datum/gas_reaction/plasmafire/react(datum/gas_mixture/air, datum/holder)
@@ -208,21 +212,21 @@
temperature_scale = (temperature-PLASMA_MINIMUM_BURN_TEMPERATURE)/(PLASMA_UPPER_TEMPERATURE-PLASMA_MINIMUM_BURN_TEMPERATURE)
if(temperature_scale > 0)
oxygen_burn_rate = OXYGEN_BURN_RATE_BASE - temperature_scale
- if(air.get_moles(/datum/gas/oxygen) / air.get_moles(/datum/gas/plasma) > SUPER_SATURATION_THRESHOLD) //supersaturation. Form Tritium.
+ if(air.get_moles(GAS_O2) / air.get_moles(GAS_PLASMA) > SUPER_SATURATION_THRESHOLD) //supersaturation. Form Tritium.
super_saturation = TRUE
- if(air.get_moles(/datum/gas/oxygen) > air.get_moles(/datum/gas/plasma)*PLASMA_OXYGEN_FULLBURN)
- plasma_burn_rate = (air.get_moles(/datum/gas/plasma)*temperature_scale)/PLASMA_BURN_RATE_DELTA
+ if(air.get_moles(GAS_O2) > air.get_moles(GAS_PLASMA)*PLASMA_OXYGEN_FULLBURN)
+ plasma_burn_rate = (air.get_moles(GAS_PLASMA)*temperature_scale)/PLASMA_BURN_RATE_DELTA
else
- plasma_burn_rate = (temperature_scale*(air.get_moles(/datum/gas/oxygen)/PLASMA_OXYGEN_FULLBURN))/PLASMA_BURN_RATE_DELTA
+ plasma_burn_rate = (temperature_scale*(air.get_moles(GAS_O2)/PLASMA_OXYGEN_FULLBURN))/PLASMA_BURN_RATE_DELTA
if(plasma_burn_rate > MINIMUM_HEAT_CAPACITY)
- plasma_burn_rate = min(plasma_burn_rate,air.get_moles(/datum/gas/plasma),air.get_moles(/datum/gas/oxygen)/oxygen_burn_rate) //Ensures matter is conserved properly
- air.set_moles(/datum/gas/plasma, QUANTIZE(air.get_moles(/datum/gas/plasma) - plasma_burn_rate))
- air.set_moles(/datum/gas/oxygen, QUANTIZE(air.get_moles(/datum/gas/oxygen) - (plasma_burn_rate * oxygen_burn_rate)))
+ plasma_burn_rate = min(plasma_burn_rate,air.get_moles(GAS_PLASMA),air.get_moles(GAS_O2)/oxygen_burn_rate) //Ensures matter is conserved properly
+ air.set_moles(GAS_PLASMA, QUANTIZE(air.get_moles(GAS_PLASMA) - plasma_burn_rate))
+ air.set_moles(GAS_O2, QUANTIZE(air.get_moles(GAS_O2) - (plasma_burn_rate * oxygen_burn_rate)))
if (super_saturation)
- air.adjust_moles(/datum/gas/tritium, plasma_burn_rate)
+ air.adjust_moles(GAS_TRITIUM, plasma_burn_rate)
else
- air.adjust_moles(/datum/gas/carbon_dioxide, plasma_burn_rate)
+ air.adjust_moles(GAS_CO2, plasma_burn_rate)
energy_released += FIRE_PLASMA_ENERGY_RELEASED * (plasma_burn_rate)
@@ -245,6 +249,84 @@
return cached_results["fire"] ? REACTING : NO_REACTION
+/datum/gas_reaction/genericfire
+ priority = -3 // very last reaction
+ name = "Combustion"
+ id = "genericfire"
+
+/datum/gas_reaction/genericfire/init_reqs()
+ var/lowest_fire_temp = INFINITY
+ var/list/fire_temperatures = GLOB.gas_data.fire_temperatures
+ for(var/gas in fire_temperatures)
+ lowest_fire_temp = min(lowest_fire_temp, fire_temperatures[gas])
+ var/lowest_oxi_temp = INFINITY
+ var/list/oxidation_temperatures = GLOB.gas_data.oxidation_temperatures
+ for(var/gas in oxidation_temperatures)
+ lowest_oxi_temp = min(lowest_oxi_temp, oxidation_temperatures[gas])
+ min_requirements = list(
+ "TEMP" = max(lowest_oxi_temp, lowest_fire_temp),
+ "FIRE_REAGENTS" = MINIMUM_MOLE_COUNT
+ )
+
+// no requirements, always runs
+// bad idea? maybe
+// this is overridden by auxmos but, hey, good idea to have it readable
+
+/datum/gas_reaction/genericfire/react(datum/gas_mixture/air, datum/holder)
+ var/temperature = air.return_temperature()
+ var/list/oxidation_temps = GLOB.gas_data.oxidation_temperatures
+ var/list/oxidation_rates = GLOB.gas_data.oxidation_rates
+ var/oxidation_power = 0
+ var/list/burn_results = list()
+ var/list/fuels = list()
+ var/list/oxidizers = list()
+ var/list/fuel_rates = GLOB.gas_data.fire_burn_rates
+ var/list/fuel_temps = GLOB.gas_data.fire_temperatures
+ var/total_fuel = 0
+ var/energy_released = 0
+ for(var/G in air.get_gases())
+ var/oxidation_temp = oxidation_temps[G]
+ if(oxidation_temp && oxidation_temp > temperature)
+ var/temperature_scale = max(0, 1-(temperature / oxidation_temp))
+ var/amt = air.get_moles(G) * temperature_scale
+ oxidizers[G] = amt
+ oxidation_power += amt * oxidation_rates[G]
+ else
+ var/fuel_temp = fuel_temps[G]
+ if(fuel_temp && fuel_temp > temperature)
+ var/amt = (air.get_moles(G) / fuel_rates[G]) * max(0, 1-(temperature / fuel_temp))
+ fuels[G] = amt // we have to calculate the actual amount we're using after we get all oxidation together
+ total_fuel += amt
+ if(oxidation_power <= 0 || total_fuel <= 0)
+ return NO_REACTION
+ var/oxidation_ratio = oxidation_power / total_fuel
+ if(oxidation_ratio > 1)
+ for(var/oxidizer in oxidizers)
+ oxidizers[oxidizer] /= oxidation_ratio
+ else if(oxidation_ratio < 1)
+ for(var/fuel in fuels)
+ fuels[fuel] *= oxidation_ratio
+ fuels += oxidizers
+ var/list/fire_products = GLOB.gas_data.fire_products
+ var/list/fire_enthalpies = GLOB.gas_data.fire_enthalpies
+ for(var/fuel in fuels + oxidizers)
+ var/amt = fuels[fuel]
+ if(!burn_results[fuel])
+ burn_results[fuel] = 0
+ burn_results[fuel] -= amt
+ energy_released += amt * fire_enthalpies[fuel]
+ for(var/product in fire_products[fuel])
+ if(!burn_results[product])
+ burn_results[product] = 0
+ burn_results[product] += amt
+ var/final_energy = air.thermal_energy() + energy_released
+ for(var/result in burn_results)
+ air.adjust_moles(result, burn_results[result])
+ air.set_temperature(final_energy / air.heat_capacity())
+ var/list/cached_results = air.reaction_results
+ cached_results["fire"] = min(total_fuel, oxidation_power) * 2
+ return cached_results["fire"] ? REACTING : NO_REACTION
+
//fusion: a terrible idea that was fun but broken. Now reworked to be less broken and more interesting. Again (and again, and again). Again!
//Fusion Rework Counter: Please increment this if you make a major overhaul to this system again.
//6 reworks
@@ -258,9 +340,9 @@
/datum/gas_reaction/fusion/init_reqs()
min_requirements = list(
"TEMP" = FUSION_TEMPERATURE_THRESHOLD,
- /datum/gas/tritium = FUSION_TRITIUM_MOLES_USED,
- /datum/gas/plasma = FUSION_MOLE_THRESHOLD,
- /datum/gas/carbon_dioxide = FUSION_MOLE_THRESHOLD)
+ GAS_TRITIUM = FUSION_TRITIUM_MOLES_USED,
+ GAS_PLASMA = FUSION_MOLE_THRESHOLD,
+ GAS_CO2 = FUSION_MOLE_THRESHOLD)
/datum/gas_reaction/fusion/react(datum/gas_mixture/air, datum/holder)
var/turf/open/location
@@ -272,16 +354,21 @@
if(!air.analyzer_results)
air.analyzer_results = new
var/list/cached_scan_results = air.analyzer_results
- var/old_thermal_energy = air.thermal_energy()
+ var/thermal_energy = air.thermal_energy()
var/reaction_energy = 0 //Reaction energy can be negative or positive, for both exothermic and endothermic reactions.
- var/initial_plasma = air.get_moles(/datum/gas/plasma)
- var/initial_carbon = air.get_moles(/datum/gas/carbon_dioxide)
- var/scale_factor = (air.return_volume())/(PI) //We scale it down by volume/Pi because for fusion conditions, moles roughly = 2*volume, but we want it to be based off something constant between reactions.
- var/toroidal_size = (2*PI)+TORADIANS(arctan((air.return_volume()-TOROID_VOLUME_BREAKEVEN)/TOROID_VOLUME_BREAKEVEN)) //The size of the phase space hypertorus
+ var/initial_plasma = air.get_moles(GAS_PLASMA)
+ var/initial_carbon = air.get_moles(GAS_CO2)
+ var/scale_factor = max(air.return_volume() / FUSION_SCALE_DIVISOR, FUSION_MINIMAL_SCALE)
+ var/temperature_scale = log(10, air.return_temperature())
+ //The size of the phase space hypertorus
+ var/toroidal_size = TOROID_CALCULATED_THRESHOLD \
+ + (temperature_scale <= FUSION_BASE_TEMPSCALE ? \
+ (temperature_scale-FUSION_BASE_TEMPSCALE) / FUSION_BUFFER_DIVISOR \
+ : 4 ** (temperature_scale-FUSION_BASE_TEMPSCALE) / FUSION_SLOPE_DIVISOR)
var/gas_power = 0
for (var/gas_id in air.get_gases())
- gas_power += (GLOB.meta_gas_info[gas_id][META_GAS_FUSION_POWER]*air.get_moles(gas_id))
- var/instability = MODULUS((gas_power*INSTABILITY_GAS_POWER_FACTOR)**2,toroidal_size) //Instability effects how chaotic the behavior of the reaction is
+ gas_power += (GLOB.gas_data.fusion_powers[gas_id]*air.get_moles(gas_id))
+ var/instability = MODULUS((gas_power*INSTABILITY_GAS_POWER_FACTOR),toroidal_size) //Instability effects how chaotic the behavior of the reaction is
cached_scan_results[id] = instability//used for analyzer feedback
var/plasma = (initial_plasma-FUSION_MOLE_THRESHOLD)/(scale_factor) //We have to scale the amounts of carbon and plasma down a significant amount in order to show the chaotic dynamics we want
@@ -291,41 +378,49 @@
plasma = MODULUS(plasma - (instability*sin(TODEGREES(carbon))), toroidal_size)
carbon = MODULUS(carbon - plasma, toroidal_size)
+ air.set_moles(GAS_PLASMA, plasma*scale_factor + FUSION_MOLE_THRESHOLD )//Scales the gases back up
+ air.set_moles(GAS_CO2, carbon*scale_factor + FUSION_MOLE_THRESHOLD)
+ var/delta_plasma = min(initial_plasma - air.get_moles(GAS_PLASMA), toroidal_size * scale_factor * 1.5)
- air.set_moles(/datum/gas/plasma, plasma*scale_factor + FUSION_MOLE_THRESHOLD )//Scales the gases back up
- air.set_moles(/datum/gas/carbon_dioxide, carbon*scale_factor + FUSION_MOLE_THRESHOLD)
- var/delta_plasma = initial_plasma - air.get_moles(/datum/gas/plasma)
+ //Energy is gained or lost corresponding to the creation or destruction of mass.
+ //Low instability prevents endothermality while higher instability acutally encourages it.
+ reaction_energy = instability <= FUSION_INSTABILITY_ENDOTHERMALITY || delta_plasma > 0 ? \
+ max(delta_plasma*PLASMA_BINDING_ENERGY, 0) \
+ : delta_plasma*PLASMA_BINDING_ENERGY * (instability-FUSION_INSTABILITY_ENDOTHERMALITY)**0.5
- reaction_energy += delta_plasma*PLASMA_BINDING_ENERGY //Energy is gained or lost corresponding to the creation or destruction of mass.
- if(instability < FUSION_INSTABILITY_ENDOTHERMALITY)
- reaction_energy = max(reaction_energy,0) //Stable reactions don't end up endothermic.
- else if (reaction_energy < 0)
- reaction_energy *= (instability-FUSION_INSTABILITY_ENDOTHERMALITY)**0.5
+ //To achieve faster equilibrium. Too bad it is not that good at cooling down.
+ if (reaction_energy)
+ var/middle_energy = (((TOROID_CALCULATED_THRESHOLD / 2) * scale_factor) + FUSION_MOLE_THRESHOLD) * (200 * FUSION_MIDDLE_ENERGY_REFERENCE)
+ thermal_energy = middle_energy * FUSION_ENERGY_TRANSLATION_EXPONENT ** log(10, thermal_energy / middle_energy)
+
+ //This bowdlerization is a double-edged sword. Tread with care!
+ var/bowdlerized_reaction_energy = clamp(reaction_energy, \
+ thermal_energy * ((1 / FUSION_ENERGY_TRANSLATION_EXPONENT ** 2) - 1), \
+ thermal_energy * (FUSION_ENERGY_TRANSLATION_EXPONENT ** 2 - 1))
+ thermal_energy = middle_energy * 10 ** log(FUSION_ENERGY_TRANSLATION_EXPONENT, (thermal_energy + bowdlerized_reaction_energy) / middle_energy)
+
+ //The reason why you should set up a tritium production line.
+ air.adjust_moles(GAS_TRITIUM, -FUSION_TRITIUM_MOLES_USED)
- if(old_thermal_energy + reaction_energy < 0) //No using energy that doesn't exist.
- air.set_moles(/datum/gas/plasma, initial_plasma)
- air.set_moles(/datum/gas/carbon_dioxide, initial_carbon)
- return NO_REACTION
- air.adjust_moles(/datum/gas/tritium, -FUSION_TRITIUM_MOLES_USED)
//The decay of the tritium and the reaction's energy produces waste gases, different ones depending on whether the reaction is endo or exothermic
- if(reaction_energy > 0)
- air.adjust_moles(/datum/gas/oxygen, FUSION_TRITIUM_MOLES_USED*(reaction_energy*FUSION_TRITIUM_CONVERSION_COEFFICIENT))
- air.adjust_moles(/datum/gas/nitrous_oxide, FUSION_TRITIUM_MOLES_USED*(reaction_energy*FUSION_TRITIUM_CONVERSION_COEFFICIENT))
- else
- air.adjust_moles(/datum/gas/bz, FUSION_TRITIUM_MOLES_USED*(reaction_energy*-FUSION_TRITIUM_CONVERSION_COEFFICIENT))
- air.adjust_moles(/datum/gas/nitryl, FUSION_TRITIUM_MOLES_USED*(reaction_energy*-FUSION_TRITIUM_CONVERSION_COEFFICIENT))
+ var/standard_waste_gas_output = scale_factor * (FUSION_TRITIUM_CONVERSION_COEFFICIENT*FUSION_TRITIUM_MOLES_USED)
+ delta_plasma > 0 ? air.adjust_moles(GAS_H2O, standard_waste_gas_output) : air.adjust_moles(GAS_BZ, standard_waste_gas_output)
+ air.adjust_moles(GAS_O2, standard_waste_gas_output) //Oxygen is a bit touchy subject
if(reaction_energy)
if(location)
- var/particle_chance = ((PARTICLE_CHANCE_CONSTANT)/(reaction_energy-PARTICLE_CHANCE_CONSTANT)) + 1//Asymptopically approaches 100% as the energy of the reaction goes up.
- if(prob(PERCENT(particle_chance)))
- location.fire_nuclear_particle()
- var/rad_power = max((FUSION_RAD_COEFFICIENT/instability) + FUSION_RAD_MAX,0)
- radiation_pulse(location,rad_power)
-
+ var/standard_energy = 400 * air.get_moles(GAS_PLASMA) * air.return_temperature() //Prevents putting meaningless waste gases to achieve high rads.
+ if(prob(PERCENT(((PARTICLE_CHANCE_CONSTANT)/(reaction_energy-PARTICLE_CHANCE_CONSTANT)) + 1))) //Asymptopically approaches 100% as the energy of the reaction goes up.
+ location.fire_nuclear_particle(customize = TRUE, custompower = standard_energy)
+ radiation_pulse(location, max(2000 * 3 ** (log(10,standard_energy) - FUSION_RAD_MIDPOINT), 0))
+ var/new_heat_capacity = air.heat_capacity()
+ if(new_heat_capacity > MINIMUM_HEAT_CAPACITY)
+ air.set_temperature(clamp(thermal_energy/new_heat_capacity, TCMB, INFINITY))
+ return REACTING
+ else if(reaction_energy == 0 && instability <= FUSION_INSTABILITY_ENDOTHERMALITY)
var/new_heat_capacity = air.heat_capacity()
if(new_heat_capacity > MINIMUM_HEAT_CAPACITY)
- air.set_temperature(CLAMP(((old_thermal_energy + reaction_energy)/new_heat_capacity),TCMB,INFINITY))
+ air.set_temperature(clamp(thermal_energy/new_heat_capacity, TCMB, INFINITY)) //THIS SHOULD STAY OR FUSION WILL EAT YOUR FACE
return REACTING
/datum/gas_reaction/nitrylformation //The formation of nitryl. Endothermic. Requires N2O as a catalyst.
@@ -335,9 +430,9 @@
/datum/gas_reaction/nitrylformation/init_reqs()
min_requirements = list(
- /datum/gas/oxygen = 20,
- /datum/gas/nitrogen = 20,
- /datum/gas/pluoxium = 5, //Gates Nitryl behind pluoxium to offset N2O burning up during formation
+ GAS_O2 = 20,
+ GAS_N2 = 20,
+ GAS_PLUOXIUM = 5, //Gates Nitryl behind pluoxium to offset N2O burning up during formation
"TEMP" = FIRE_MINIMUM_TEMPERATURE_TO_EXIST*60
)
@@ -345,13 +440,13 @@
var/temperature = air.return_temperature()
var/old_heat_capacity = air.heat_capacity()
- var/heat_efficency = min(temperature/(FIRE_MINIMUM_TEMPERATURE_TO_EXIST*60),air.get_moles(/datum/gas/oxygen),air.get_moles(/datum/gas/nitrogen))
+ var/heat_efficency = min(temperature/(FIRE_MINIMUM_TEMPERATURE_TO_EXIST*60),air.get_moles(GAS_O2),air.get_moles(GAS_N2))
var/energy_used = heat_efficency*NITRYL_FORMATION_ENERGY
- if ((air.get_moles(/datum/gas/oxygen) - heat_efficency < 0 )|| (air.get_moles(/datum/gas/nitrogen) - heat_efficency < 0)) //Shouldn't produce gas from nothing.
+ if ((air.get_moles(GAS_O2) - heat_efficency < 0 )|| (air.get_moles(GAS_N2) - heat_efficency < 0)) //Shouldn't produce gas from nothing.
return NO_REACTION
- air.adjust_moles(/datum/gas/oxygen, -heat_efficency)
- air.adjust_moles(/datum/gas/nitrogen, -heat_efficency)
- air.adjust_moles(/datum/gas/nitryl, heat_efficency*2)
+ air.adjust_moles(GAS_O2, -heat_efficency)
+ air.adjust_moles(GAS_N2, -heat_efficency)
+ air.adjust_moles(GAS_NITRYL, heat_efficency*2)
if(energy_used > 0)
var/new_heat_capacity = air.heat_capacity()
@@ -366,8 +461,8 @@
/datum/gas_reaction/bzformation/init_reqs()
min_requirements = list(
- /datum/gas/nitrous_oxide = 10,
- /datum/gas/plasma = 10
+ GAS_NITROUS = 10,
+ GAS_PLASMA = 10
)
@@ -375,18 +470,18 @@
var/temperature = air.return_temperature()
var/pressure = air.return_pressure()
var/old_heat_capacity = air.heat_capacity()
- var/reaction_efficency = min(1/((pressure/(0.5*ONE_ATMOSPHERE))*(max(air.get_moles(/datum/gas/plasma)/air.get_moles(/datum/gas/nitrous_oxide),1))),air.get_moles(/datum/gas/nitrous_oxide),air.get_moles(/datum/gas/plasma)/2)
+ var/reaction_efficency = min(1/((pressure/(0.5*ONE_ATMOSPHERE))*(max(air.get_moles(GAS_PLASMA)/air.get_moles(GAS_NITROUS),1))),air.get_moles(GAS_NITROUS),air.get_moles(GAS_PLASMA)/2)
var/energy_released = 2*reaction_efficency*FIRE_CARBON_ENERGY_RELEASED
- if ((air.get_moles(/datum/gas/nitrous_oxide) - reaction_efficency < 0 )|| (air.get_moles(/datum/gas/plasma) - (2*reaction_efficency) < 0) || energy_released <= 0) //Shouldn't produce gas from nothing.
+ if ((air.get_moles(GAS_NITROUS) - reaction_efficency < 0 )|| (air.get_moles(GAS_PLASMA) - (2*reaction_efficency) < 0) || energy_released <= 0) //Shouldn't produce gas from nothing.
return NO_REACTION
- air.adjust_moles(/datum/gas/bz, reaction_efficency)
- if(reaction_efficency == air.get_moles(/datum/gas/nitrous_oxide))
- air.adjust_moles(/datum/gas/bz, -min(pressure,1))
- air.adjust_moles(/datum/gas/oxygen, min(pressure,1))
- air.adjust_moles(/datum/gas/nitrous_oxide, -reaction_efficency)
- air.adjust_moles(/datum/gas/plasma, -2*reaction_efficency)
+ air.adjust_moles(GAS_BZ, reaction_efficency)
+ if(reaction_efficency == air.get_moles(GAS_NITROUS))
+ air.adjust_moles(GAS_BZ, -min(pressure,1))
+ air.adjust_moles(GAS_O2, min(pressure,1))
+ air.adjust_moles(GAS_NITROUS, -reaction_efficency)
+ air.adjust_moles(GAS_PLASMA, -2*reaction_efficency)
- SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, min((reaction_efficency**2)*BZ_RESEARCH_SCALE),BZ_RESEARCH_MAX_AMOUNT)
+ SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, min((reaction_efficency**2)*BZ_RESEARCH_SCALE,BZ_RESEARCH_MAX_AMOUNT))
if(energy_released > 0)
var/new_heat_capacity = air.heat_capacity()
@@ -401,24 +496,24 @@
/datum/gas_reaction/stimformation/init_reqs()
min_requirements = list(
- /datum/gas/tritium = 30,
- /datum/gas/plasma = 10,
- /datum/gas/bz = 20,
- /datum/gas/nitryl = 30,
+ GAS_TRITIUM = 30,
+ GAS_PLASMA = 10,
+ GAS_BZ = 20,
+ GAS_NITRYL = 30,
"TEMP" = STIMULUM_HEAT_SCALE/2)
/datum/gas_reaction/stimformation/react(datum/gas_mixture/air)
var/old_heat_capacity = air.heat_capacity()
- var/heat_scale = min(air.return_temperature()/STIMULUM_HEAT_SCALE,air.get_moles(/datum/gas/plasma),air.get_moles(/datum/gas/nitryl))
+ var/heat_scale = min(air.return_temperature()/STIMULUM_HEAT_SCALE,air.get_moles(GAS_PLASMA),air.get_moles(GAS_NITRYL))
var/stim_energy_change = heat_scale + STIMULUM_FIRST_RISE*(heat_scale**2) - STIMULUM_FIRST_DROP*(heat_scale**3) + STIMULUM_SECOND_RISE*(heat_scale**4) - STIMULUM_ABSOLUTE_DROP*(heat_scale**5)
- if ((air.get_moles(/datum/gas/plasma) - heat_scale < 0) || (air.get_moles(/datum/gas/nitryl) - heat_scale < 0) || (air.get_moles(/datum/gas/tritium) - heat_scale < 0)) //Shouldn't produce gas from nothing.
+ if ((air.get_moles(GAS_PLASMA) - heat_scale < 0) || (air.get_moles(GAS_NITRYL) - heat_scale < 0) || (air.get_moles(GAS_TRITIUM) - heat_scale < 0)) //Shouldn't produce gas from nothing.
return NO_REACTION
- air.adjust_moles(/datum/gas/stimulum, heat_scale/10)
- air.adjust_moles(/datum/gas/plasma, -heat_scale)
- air.adjust_moles(/datum/gas/nitryl, -heat_scale)
- air.adjust_moles(/datum/gas/tritium, -heat_scale)
+ air.adjust_moles(GAS_STIMULUM, heat_scale/10)
+ air.adjust_moles(GAS_PLASMA, -heat_scale)
+ air.adjust_moles(GAS_NITRYL, -heat_scale)
+ air.adjust_moles(GAS_TRITIUM, -heat_scale)
SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, STIMULUM_RESEARCH_AMOUNT*max(stim_energy_change,0))
if(stim_energy_change)
var/new_heat_capacity = air.heat_capacity()
@@ -433,19 +528,19 @@
/datum/gas_reaction/nobliumformation/init_reqs()
min_requirements = list(
- /datum/gas/nitrogen = 10,
- /datum/gas/tritium = 5,
+ GAS_N2 = 10,
+ GAS_TRITIUM = 5,
"TEMP" = 5000000)
/datum/gas_reaction/nobliumformation/react(datum/gas_mixture/air)
var/old_heat_capacity = air.heat_capacity()
- var/nob_formed = min((air.get_moles(/datum/gas/nitrogen)+air.get_moles(/datum/gas/tritium))/100,air.get_moles(/datum/gas/tritium)/10,air.get_moles(/datum/gas/nitrogen)/20)
- var/energy_taken = nob_formed*(NOBLIUM_FORMATION_ENERGY/(max(air.get_moles(/datum/gas/bz),1)))
- if ((air.get_moles(/datum/gas/tritium) - 10*nob_formed < 0) || (air.get_moles(/datum/gas/nitrogen) - 20*nob_formed < 0))
+ var/nob_formed = min((air.get_moles(GAS_N2)+air.get_moles(GAS_TRITIUM))/100,air.get_moles(GAS_TRITIUM)/10,air.get_moles(GAS_N2)/20)
+ var/energy_taken = nob_formed*(NOBLIUM_FORMATION_ENERGY/(max(air.get_moles(GAS_BZ),1)))
+ if ((air.get_moles(GAS_TRITIUM) - 10*nob_formed < 0) || (air.get_moles(GAS_N2) - 20*nob_formed < 0))
return NO_REACTION
- air.adjust_moles(/datum/gas/tritium, -10*nob_formed)
- air.adjust_moles(/datum/gas/nitrogen, -20*nob_formed)
- air.adjust_moles(/datum/gas/hypernoblium, nob_formed)
+ air.adjust_moles(GAS_TRITIUM, -10*nob_formed)
+ air.adjust_moles(GAS_N2, -20*nob_formed)
+ air.adjust_moles(GAS_HYPERNOB, nob_formed)
SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, nob_formed*NOBLIUM_RESEARCH_AMOUNT)
if (nob_formed)
@@ -462,18 +557,18 @@
/datum/gas_reaction/miaster/init_reqs()
min_requirements = list(
"TEMP" = FIRE_MINIMUM_TEMPERATURE_TO_EXIST+70,
- /datum/gas/miasma = MINIMUM_MOLE_COUNT
+ GAS_MIASMA = MINIMUM_MOLE_COUNT
)
/datum/gas_reaction/miaster/react(datum/gas_mixture/air, datum/holder)
// As the name says it, it needs to be dry
- if(air.get_moles(/datum/gas/water_vapor)/air.total_moles() > 0.1)
+ if(air.get_moles(GAS_H2O)/air.total_moles() > 0.1)
return
//Replace miasma with oxygen
- var/cleaned_air = min(air.get_moles(/datum/gas/miasma), 20 + (air.return_temperature() - FIRE_MINIMUM_TEMPERATURE_TO_EXIST - 70) / 20)
- air.adjust_moles(/datum/gas/miasma, -cleaned_air)
- air.adjust_moles(/datum/gas/oxygen, cleaned_air)
+ var/cleaned_air = min(air.get_moles(GAS_MIASMA), 20 + (air.return_temperature() - FIRE_MINIMUM_TEMPERATURE_TO_EXIST - 70) / 20)
+ air.adjust_moles(GAS_MIASMA, -cleaned_air)
+ air.adjust_moles(GAS_O2, cleaned_air)
//Possibly burning a bit of organic matter through maillard reaction, so a *tiny* bit more heat would be understandable
air.set_temperature(air.return_temperature() + cleaned_air * 0.002)
@@ -486,10 +581,10 @@
/datum/gas_reaction/stim_ball/init_reqs()
min_requirements = list(
- /datum/gas/pluoxium = STIM_BALL_GAS_AMOUNT,
- /datum/gas/stimulum = STIM_BALL_GAS_AMOUNT,
- /datum/gas/nitryl = MINIMUM_MOLE_COUNT,
- /datum/gas/plasma = MINIMUM_MOLE_COUNT,
+ GAS_PLUOXIUM = STIM_BALL_GAS_AMOUNT,
+ GAS_STIMULUM = STIM_BALL_GAS_AMOUNT,
+ GAS_NITRYL = MINIMUM_MOLE_COUNT,
+ GAS_PLASMA = MINIMUM_MOLE_COUNT,
"TEMP" = FIRE_MINIMUM_TEMPERATURE_TO_EXIST
)
@@ -501,16 +596,16 @@
location = get_turf(pick(pipenet.members))
else
location = get_turf(holder)
- var/ball_shot_angle = 180*cos(air.get_moles(/datum/gas/water_vapor)/air.get_moles(/datum/gas/nitryl))+180
- var/stim_used = min(STIM_BALL_GAS_AMOUNT/air.get_moles(/datum/gas/plasma),air.get_moles(/datum/gas/stimulum))
- var/pluox_used = min(STIM_BALL_GAS_AMOUNT/air.get_moles(/datum/gas/plasma),air.get_moles(/datum/gas/pluoxium))
+ var/ball_shot_angle = 180*cos(air.get_moles(GAS_H2O)/air.get_moles(GAS_NITRYL))+180
+ var/stim_used = min(STIM_BALL_GAS_AMOUNT/air.get_moles(GAS_PLASMA),air.get_moles(GAS_STIMULUM))
+ var/pluox_used = min(STIM_BALL_GAS_AMOUNT/air.get_moles(GAS_PLASMA),air.get_moles(GAS_PLUOXIUM))
var/energy_released = stim_used*STIMULUM_HEAT_SCALE//Stimulum has a lot of stored energy, and breaking it up releases some of it
location.fire_nuclear_particle(ball_shot_angle)
- air.adjust_moles(/datum/gas/carbon_dioxide, 4*pluox_used)
- air.adjust_moles(/datum/gas/nitrogen, 8*stim_used)
- air.adjust_moles(/datum/gas/pluoxium, -pluox_used)
- air.adjust_moles(/datum/gas/stimulum, -stim_used)
- air.adjust_moles(/datum/gas/plasma, -air.get_moles(/datum/gas/plasma)/2)
+ air.adjust_moles(GAS_CO2, 4*pluox_used)
+ air.adjust_moles(GAS_N2, 8*stim_used)
+ air.adjust_moles(GAS_PLUOXIUM, -pluox_used)
+ air.adjust_moles(GAS_STIMULUM, -stim_used)
+ air.adjust_moles(GAS_PLASMA, max(-air.get_moles(GAS_PLASMA)/2,-30))
if(energy_released)
var/new_heat_capacity = air.heat_capacity()
if(new_heat_capacity > MINIMUM_HEAT_CAPACITY)
diff --git a/code/modules/atmospherics/machinery/airalarm.dm b/code/modules/atmospherics/machinery/airalarm.dm
index b1034564bf793..d6afb607a71f8 100644
--- a/code/modules/atmospherics/machinery/airalarm.dm
+++ b/code/modules/atmospherics/machinery/airalarm.dm
@@ -69,9 +69,9 @@
req_access = list(ACCESS_ATMOSPHERICS)
max_integrity = 250
integrity_failure = 80
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30, "stamina" = 0)
resistance_flags = FIRE_PROOF
-
+ layer = ABOVE_WINDOW_LAYER
var/danger_level = 0
@@ -89,57 +89,57 @@
var/list/TLV = list( // Breathable air.
"pressure" = new/datum/tlv(ONE_ATMOSPHERE * 0.8, ONE_ATMOSPHERE* 0.9, ONE_ATMOSPHERE * 1.1, ONE_ATMOSPHERE * 1.2), // kPa. Values are min2, min1, max1, max2
"temperature" = new/datum/tlv(T0C, T0C+10, T0C+40, T0C+66),
- /datum/gas/oxygen = new/datum/tlv(16, 19, 135, 140), // Partial pressure, kpa
- /datum/gas/nitrogen = new/datum/tlv(-1, -1, 1000, 1000),
- /datum/gas/carbon_dioxide = new/datum/tlv(-1, -1, 5, 10),
- /datum/gas/miasma = new/datum/tlv/(-1, -1, 15, 30),
- /datum/gas/plasma = new/datum/tlv/dangerous,
- /datum/gas/nitrous_oxide = new/datum/tlv/dangerous,
- /datum/gas/bz = new/datum/tlv/dangerous,
- /datum/gas/hypernoblium = new/datum/tlv(-1, -1, 1000, 1000), // Hyper-Noblium is inert and nontoxic
- /datum/gas/water_vapor = new/datum/tlv/dangerous,
- /datum/gas/tritium = new/datum/tlv/dangerous,
- /datum/gas/stimulum = new/datum/tlv/dangerous,
- /datum/gas/nitryl = new/datum/tlv/dangerous,
- /datum/gas/pluoxium = new/datum/tlv(-1, -1, 1000, 1000) // Unlike oxygen, pluoxium does not fuel plasma/tritium fires
+ GAS_O2 = new/datum/tlv(16, 19, 40, 50), // Partial pressure, kpa
+ GAS_N2 = new/datum/tlv(-1, -1, 1000, 1000),
+ GAS_CO2 = new/datum/tlv(-1, -1, 5, 10),
+ GAS_MIASMA = new/datum/tlv/(-1, -1, 15, 30),
+ GAS_PLASMA = new/datum/tlv/dangerous,
+ GAS_NITROUS = new/datum/tlv/dangerous,
+ GAS_BZ = new/datum/tlv/dangerous,
+ GAS_HYPERNOB = new/datum/tlv(-1, -1, 1000, 1000), // Hyper-Noblium is inert and nontoxic
+ GAS_H2O = new/datum/tlv/dangerous,
+ GAS_TRITIUM = new/datum/tlv/dangerous,
+ GAS_STIMULUM = new/datum/tlv/dangerous,
+ GAS_NITRYL = new/datum/tlv/dangerous,
+ GAS_PLUOXIUM = new/datum/tlv(-1, -1, 5, 6), // Unlike oxygen, pluoxium does not fuel plasma/tritium fires
)
/obj/machinery/airalarm/server // No checks here.
TLV = list(
"pressure" = new/datum/tlv/no_checks,
"temperature" = new/datum/tlv/no_checks,
- /datum/gas/oxygen = new/datum/tlv/no_checks,
- /datum/gas/nitrogen = new/datum/tlv/no_checks,
- /datum/gas/carbon_dioxide = new/datum/tlv/no_checks,
- /datum/gas/miasma = new/datum/tlv/no_checks,
- /datum/gas/plasma = new/datum/tlv/no_checks,
- /datum/gas/nitrous_oxide = new/datum/tlv/no_checks,
- /datum/gas/bz = new/datum/tlv/no_checks,
- /datum/gas/hypernoblium = new/datum/tlv/no_checks,
- /datum/gas/water_vapor = new/datum/tlv/no_checks,
- /datum/gas/tritium = new/datum/tlv/no_checks,
- /datum/gas/stimulum = new/datum/tlv/no_checks,
- /datum/gas/nitryl = new/datum/tlv/no_checks,
- /datum/gas/pluoxium = new/datum/tlv/no_checks
+ GAS_O2 = new/datum/tlv/no_checks,
+ GAS_N2 = new/datum/tlv/no_checks,
+ GAS_CO2 = new/datum/tlv/no_checks,
+ GAS_MIASMA = new/datum/tlv/no_checks,
+ GAS_PLASMA = new/datum/tlv/no_checks,
+ GAS_NITROUS = new/datum/tlv/no_checks,
+ GAS_BZ = new/datum/tlv/no_checks,
+ GAS_HYPERNOB = new/datum/tlv/no_checks,
+ GAS_H2O = new/datum/tlv/no_checks,
+ GAS_TRITIUM = new/datum/tlv/no_checks,
+ GAS_STIMULUM = new/datum/tlv/no_checks,
+ GAS_NITRYL = new/datum/tlv/no_checks,
+ GAS_PLUOXIUM = new/datum/tlv/no_checks
)
/obj/machinery/airalarm/kitchen_cold_room // Kitchen cold rooms start off at -20°C or 253.15°K.
TLV = list(
"pressure" = new/datum/tlv(ONE_ATMOSPHERE * 0.8, ONE_ATMOSPHERE* 0.9, ONE_ATMOSPHERE * 1.1, ONE_ATMOSPHERE * 1.2), // kPa
"temperature" = new/datum/tlv(T0C-273.15, T0C-80, T0C-10, T0C+10),
- /datum/gas/oxygen = new/datum/tlv(16, 19, 135, 140), // Partial pressure, kpa
- /datum/gas/nitrogen = new/datum/tlv(-1, -1, 1000, 1000),
- /datum/gas/carbon_dioxide = new/datum/tlv(-1, -1, 5, 10),
- /datum/gas/miasma = new/datum/tlv/(-1, -1, 2, 5),
- /datum/gas/plasma = new/datum/tlv/dangerous,
- /datum/gas/nitrous_oxide = new/datum/tlv/dangerous,
- /datum/gas/bz = new/datum/tlv/dangerous,
- /datum/gas/hypernoblium = new/datum/tlv(-1, -1, 1000, 1000), // Hyper-Noblium is inert and nontoxic
- /datum/gas/water_vapor = new/datum/tlv/dangerous,
- /datum/gas/tritium = new/datum/tlv/dangerous,
- /datum/gas/stimulum = new/datum/tlv/dangerous,
- /datum/gas/nitryl = new/datum/tlv/dangerous,
- /datum/gas/pluoxium = new/datum/tlv(-1, -1, 1000, 1000) // Unlike oxygen, pluoxium does not fuel plasma/tritium fires
+ GAS_O2 = new/datum/tlv(16, 19, 135, 140), // Partial pressure, kpa
+ GAS_N2 = new/datum/tlv(-1, -1, 1000, 1000),
+ GAS_CO2 = new/datum/tlv(-1, -1, 5, 10),
+ GAS_MIASMA = new/datum/tlv/(-1, -1, 2, 5),
+ GAS_PLASMA = new/datum/tlv/dangerous,
+ GAS_NITROUS = new/datum/tlv/dangerous,
+ GAS_BZ = new/datum/tlv/dangerous,
+ GAS_HYPERNOB = new/datum/tlv(-1, -1, 1000, 1000), // Hyper-Noblium is inert and nontoxic
+ GAS_H2O = new/datum/tlv/dangerous,
+ GAS_TRITIUM = new/datum/tlv/dangerous,
+ GAS_STIMULUM = new/datum/tlv/dangerous,
+ GAS_NITRYL = new/datum/tlv/dangerous,
+ GAS_PLUOXIUM = new/datum/tlv(-1, -1, 1000, 1000) // Unlike oxygen, pluoxium does not fuel plasma/tritium fires
)
/obj/machinery/airalarm/unlocked
@@ -214,11 +214,15 @@
SSradio.remove_object(src, frequency)
qdel(wires)
wires = null
+ var/area/ourarea = get_area(src)
+ ourarea.atmosalert(FALSE, src)
+ GLOB.zclear_atoms -= src
return ..()
/obj/machinery/airalarm/Initialize(mapload)
. = ..()
set_frequency(frequency)
+ GLOB.zclear_atoms += src
/obj/machinery/airalarm/examine(mob/user)
. = ..()
@@ -246,7 +250,9 @@
if(!ui)
ui = new(user, src, "AirAlarm")
ui.open()
+ ui.set_autoupdate(TRUE) // Turf gas mixture
+//Oh my, thats a lot of data being sent that should probably be refactored
/obj/machinery/airalarm/ui_data(mob/user)
var/data = list(
"locked" = locked,
@@ -287,7 +293,7 @@
continue
cur_tlv = TLV[gas_id]
data["environment_data"] += list(list(
- "name" = GLOB.meta_gas_info[gas_id][META_GAS_NAME],
+ "name" = GLOB.gas_data.names[gas_id],
"value" = environment.get_moles(gas_id) / total_moles * 100,
"unit" = "%",
"danger_level" = cur_tlv.get_danger_level(environment.get_moles(gas_id) * partial_pressure)
@@ -357,11 +363,11 @@
thresholds[thresholds.len]["settings"] += list(list("env" = "temperature", "val" = "max1", "selected" = selected.max1))
thresholds[thresholds.len]["settings"] += list(list("env" = "temperature", "val" = "max2", "selected" = selected.max2))
- for(var/gas_id in GLOB.meta_gas_info)
+ for(var/gas_id in GLOB.gas_data.names)
if(!(gas_id in TLV)) // We're not interested in this gas, it seems.
continue
selected = TLV[gas_id]
- thresholds += list(list("name" = GLOB.meta_gas_info[gas_id][META_GAS_NAME], "settings" = list()))
+ thresholds += list(list("name" = GLOB.gas_data.names[gas_id], "settings" = list()))
thresholds[thresholds.len]["settings"] += list(list("env" = gas_id, "val" = "min2", "selected" = selected.min2))
thresholds[thresholds.len]["settings"] += list(list("env" = gas_id, "val" = "min1", "selected" = selected.min1))
thresholds[thresholds.len]["settings"] += list(list("env" = gas_id, "val" = "max1", "selected" = selected.max1))
@@ -425,15 +431,16 @@
. = TRUE
if("alarm")
var/area/A = get_area(src)
- if(A.atmosalert(2, src))
+ if(A.atmosalert(TRUE, src))
post_alert(2)
. = TRUE
if("reset")
var/area/A = get_area(src)
- if(A.atmosalert(0, src))
+ if(A.atmosalert(FALSE, src))
post_alert(0)
. = TRUE
- update_icon()
+ if(.)
+ update_icon()
/obj/machinery/airalarm/proc/reset(wire)
@@ -441,10 +448,12 @@
if(WIRE_POWER)
if(!wires.is_cut(WIRE_POWER))
shorted = FALSE
+ wires.ui_update()
update_icon()
if(WIRE_AI)
if(!wires.is_cut(WIRE_AI))
aidisabled = FALSE
+ wires.ui_update()
/obj/machinery/airalarm/proc/shock(mob/user, prb)
@@ -505,7 +514,7 @@
for(var/device_id in A.air_scrub_names)
send_signal(device_id, list(
"power" = 1,
- "set_filters" = list(/datum/gas/carbon_dioxide, /datum/gas/bz),
+ "set_filters" = list(GAS_CO2, GAS_BZ),
"scrubbing" = 1,
"widenet" = 0
), signal_source)
@@ -520,17 +529,17 @@
send_signal(device_id, list(
"power" = 1,
"set_filters" = list(
- /datum/gas/carbon_dioxide,
- /datum/gas/miasma,
- /datum/gas/plasma,
- /datum/gas/water_vapor,
- /datum/gas/hypernoblium,
- /datum/gas/nitrous_oxide,
- /datum/gas/nitryl,
- /datum/gas/tritium,
- /datum/gas/bz,
- /datum/gas/stimulum,
- /datum/gas/pluoxium
+ GAS_CO2,
+ GAS_MIASMA,
+ GAS_PLASMA,
+ GAS_H2O,
+ GAS_HYPERNOB,
+ GAS_NITROUS,
+ GAS_NITRYL,
+ GAS_TRITIUM,
+ GAS_BZ,
+ GAS_STIMULUM,
+ GAS_PLUOXIUM
),
"scrubbing" = 1,
"widenet" = 1
@@ -558,7 +567,7 @@
for(var/device_id in A.air_scrub_names)
send_signal(device_id, list(
"power" = 1,
- "set_filters" = list(/datum/gas/carbon_dioxide, /datum/gas/bz),
+ "set_filters" = list(GAS_CO2, GAS_BZ),
"scrubbing" = 1,
"widenet" = 0
), signal_source)
@@ -702,7 +711,7 @@
var/new_area_danger_level = 0
for(var/obj/machinery/airalarm/AA in A)
if (!(AA.stat & (NOPOWER|BROKEN)) && !AA.shorted)
- new_area_danger_level = max(new_area_danger_level,AA.danger_level)
+ new_area_danger_level = clamp(max(new_area_danger_level, AA.danger_level), 0, 1)
if(A.atmosalert(new_area_danger_level,src)) //if area was in normal state or if area was in alert state
post_alert(new_area_danger_level)
@@ -792,8 +801,22 @@
return ..()
+/obj/machinery/airalarm/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
+ if((buildstage == 0) && (the_rcd.upgrade & RCD_UPGRADE_SIMPLE_CIRCUITS))
+ return list("mode" = RCD_UPGRADE_SIMPLE_CIRCUITS, "delay" = 20, "cost" = 1)
+ return FALSE
+
+/obj/machinery/airalarm/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode)
+ switch(passed_mode)
+ if(RCD_UPGRADE_SIMPLE_CIRCUITS)
+ user.visible_message("[user] fabricates a circuit and places it into [src].", \
+ "You adapt an air alarm circuit and slot it into the assembly.")
+ buildstage = 1
+ update_icon()
+ return TRUE
+ return FALSE
+
/obj/machinery/airalarm/AltClick(mob/user)
- ..()
if(!user.canUseTopic(src, !issilicon(user)) || !isturf(loc))
return
else
diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm
index c6a49f5ed06b8..2c27e30874c3d 100644
--- a/code/modules/atmospherics/machinery/atmosmachinery.dm
+++ b/code/modules/atmospherics/machinery/atmosmachinery.dm
@@ -37,6 +37,7 @@
var/construction_type
var/pipe_state //icon_state as a pipe item
var/on = FALSE
+ var/interacts_with_air = FALSE
/obj/machinery/atmospherics/examine(mob/user)
. = ..()
@@ -52,10 +53,13 @@
normalize_cardinal_directions()
nodes = new(device_type)
if (!armor)
- armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 70)
+ armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 70, "stamina" = 0)
..()
if(process)
- SSair.atmos_machinery += src
+ if(interacts_with_air)
+ SSair.atmos_air_machinery += src
+ else
+ SSair.atmos_machinery += src
SetInitDirections()
/obj/machinery/atmospherics/Destroy()
@@ -63,6 +67,8 @@
nullifyNode(i)
SSair.atmos_machinery -= src
+ SSair.atmos_air_machinery -= src
+ SSair.pipenets_needing_rebuilt -= src
dropContents()
if(pipe_vision_img)
@@ -167,8 +173,9 @@
if(istype(reference, /obj/machinery/atmospherics/pipe))
var/obj/machinery/atmospherics/pipe/P = reference
P.destroy_network()
- nodes[nodes.Find(reference)] = null
- update_icon()
+ if(nodes.len >= nodes.Find(reference)) // for some reason things can still be acted on even though they've been deleted this is a really fucky way of detecting that
+ nodes[nodes.Find(reference)] = null
+ update_icon()
/obj/machinery/atmospherics/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/pipe)) //lets you autodrop
@@ -212,11 +219,11 @@
if(unsafe_wrenching)
unsafe_pressure_release(user, internal_pressure)
-
+
if (user.client)
SSmedals.UnlockMedal(MEDAL_UNWRENCH_HIGH_PRESSURE,user.client)
-
-
+
+
deconstruct(TRUE)
return TRUE
@@ -224,9 +231,14 @@
return can_unwrench
// Throws the user when they unwrench a pipe with a major difference between the internal and environmental pressure.
-/obj/machinery/atmospherics/proc/unsafe_pressure_release(mob/user, pressures = null)
+/obj/machinery/atmospherics/proc/unsafe_pressure_release(mob/living/carbon/user, pressures = null)
if(!user)
return
+ if(ishuman(user)) //other carbons like monkeys can unwrench but cant wear magboots
+ if(istype(user.shoes, /obj/item/clothing/shoes/magboots))
+ var/obj/item/clothing/shoes/magboots/M = user.shoes
+ if(M.negates_gravity())
+ return
if(!pressures)
var/datum/gas_mixture/int_air = return_air()
var/datum/gas_mixture/env_air = loc.return_air()
@@ -322,10 +334,10 @@
//PLACEHOLDER COMMENT FOR ME TO READD THE 1 (?) DS DELAY THAT WAS IMPLEMENTED WITH A... TIMER?
/obj/machinery/atmospherics/AltClick(mob/living/L)
+ . = ..()
if(istype(L) && is_type_in_list(src, GLOB.ventcrawl_machinery))
L.handle_ventcrawl(src)
return
- ..()
/obj/machinery/atmospherics/proc/can_crawl_through()
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm b/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm
index 45ccaf1e867a0..2575f0feedb81 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm
@@ -19,3 +19,10 @@
/obj/machinery/atmospherics/components/binary/getNodeConnects()
return list(turn(dir, 180), dir)
+
+/obj/machinery/atmospherics/components/binary/proc/set_overlay_offset(pipe_layer)
+ switch(pipe_layer)
+ if(2)
+ return 1
+ if(1, 3)
+ return 2
\ No newline at end of file
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm b/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm
index 68cccb6a0102f..e92f6924f10a0 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm
@@ -46,7 +46,7 @@
var/input_starting_pressure = air2.return_pressure()
if(output_starting_pressure >= input_starting_pressure-10)
- //Need at least 10 KPa difference to overcome friction in the mechanism
+ //Need at least 10 kPa difference to overcome friction in the mechanism
last_pressure_delta = 0
return null
@@ -116,7 +116,7 @@
if(node2)
node2.atmosinit()
node2.addMember(src)
- build_network()
+ SSair.add_to_rebuild_queue(src)
return TRUE
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/dp_vent_pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/dp_vent_pump.dm
index 335649a826fb8..1c16833f9f669 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/dp_vent_pump.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/dp_vent_pump.dm
@@ -14,7 +14,12 @@
name = "dual-port air vent"
desc = "Has a valve and pump attached to it. There are two ports."
+ welded = FALSE
+
level = 1
+
+ interacts_with_air = TRUE
+
var/frequency = 0
var/id = null
var/datum/radio_frequency/radio_connection
@@ -45,6 +50,10 @@
var/image/cap = getpipeimage(icon, "dpvent_cap", dir, piping_layer = piping_layer)
add_overlay(cap)
+ if(welded)
+ icon_state = "vent_welded"
+ return
+
if(!on || !is_operational())
icon_state = "vent_off"
else
@@ -52,7 +61,8 @@
/obj/machinery/atmospherics/components/binary/dp_vent_pump/process_atmos()
..()
-
+ if(welded || !is_operational() || !isopenturf(loc))
+ return FALSE
if(!on)
return
var/datum/gas_mixture/air1 = airs[1]
@@ -73,16 +83,12 @@
if(air1.return_temperature() > 0)
var/transfer_moles = pressure_delta*environment.return_volume()/(air1.return_temperature() * R_IDEAL_GAS_EQUATION)
- var/datum/gas_mixture/removed = air1.remove(transfer_moles)
- //Removed can be null if there is no atmosphere in air1
- if(!removed)
- return
+ loc.assume_air_moles(air1, transfer_moles)
- loc.assume_air(removed)
air_update_turf()
var/datum/pipeline/parent1 = parents[1]
- parent1.update = 1
+ parent1.update = PIPENET_UPDATE_STATUS_RECONCILE_NEEDED
else //external -> output
if(environment.return_pressure() > 0)
@@ -94,15 +100,11 @@
moles_delta = min(moles_delta, (input_pressure_min - air2.return_pressure()) * our_multiplier)
if(moles_delta > 0)
- var/datum/gas_mixture/removed = loc.remove_air(moles_delta)
- if (isnull(removed)) // in space
- return
-
- air2.merge(removed)
+ loc.transfer_air(air2, moles_delta)
air_update_turf()
var/datum/pipeline/parent2 = parents[2]
- parent2.update = 1
+ parent2.update = PIPENET_UPDATE_STATUS_RECONCILE_NEEDED
//Radio remote control
@@ -176,6 +178,40 @@
broadcast_status()
update_icon()
+/obj/machinery/atmospherics/components/binary/dp_vent_pump/welder_act(mob/living/user, obj/item/I)
+ if(!I.tool_start_check(user, amount=0))
+ return TRUE
+ to_chat(user, "You begin welding the dual-port vent...")
+ if(I.use_tool(src, user, 20, volume=50))
+ if(!welded)
+ user.visible_message("[user] welds the dual-port vent shut.", "You weld the dual-port vent shut.", "You hear welding.")
+ welded = TRUE
+ else
+ user.visible_message("[user] unwelded the dual-port vent.", "You unweld the dual-port vent.", "You hear welding.")
+ welded = FALSE
+ update_icon()
+ pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir)
+ pipe_vision_img.plane = ABOVE_HUD_PLANE
+ return TRUE
+
+/obj/machinery/atmospherics/components/binary/dp_vent_pump/examine(mob/user)
+ . = ..()
+ if(welded)
+ . += "It seems welded shut."
+
+/obj/machinery/atmospherics/components/binary/dp_vent_pump/can_crawl_through()
+ return !welded
+
+/obj/machinery/atmospherics/components/binary/dp_vent_pump/attack_alien(mob/user)
+ if(!welded || !(do_after(user, 20, target = src)))
+ return
+ user.visible_message("[user] furiously claws at [src]!", "You manage to clear away the stuff blocking the dual-port vent.", "You hear loud scraping noises.")
+ welded = FALSE
+ update_icon()
+ pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir)
+ pipe_vision_img.plane = ABOVE_HUD_PLANE
+ playsound(loc, 'sound/weapons/bladeslice.ogg', 100, 1)
+
/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume
name = "large dual-port air vent"
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm b/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm
index ecf8d47da9f75..99724e43964bb 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm
@@ -3,6 +3,7 @@
Passive gate is similar to the regular pump except:
* It doesn't require power
* Can not transfer low pressure to higher pressure (so it's more like a valve where you can control the flow)
+* Passes gas when output pressure lower than target pressure
*/
@@ -10,7 +11,7 @@ Passive gate is similar to the regular pump except:
icon_state = "passgate_map-2"
name = "passive gate"
- desc = "A one-way air valve that does not require power."
+ desc = "A one-way air valve that does not require power. Passes gas when the output pressure is lower than the target pressure."
can_unwrench = TRUE
shift_underlay_only = FALSE
@@ -33,13 +34,16 @@ Passive gate is similar to the regular pump except:
if(can_interact(user))
on = !on
update_icon()
+ ui_update()
return ..()
/obj/machinery/atmospherics/components/binary/passive_gate/AltClick(mob/user)
if(can_interact(user))
- target_pressure = MAX_OUTPUT_PRESSURE
+ target_pressure = ONE_ATMOSPHERE*100
+ balloon_alert(user, "Set to [target_pressure] kPa")
update_icon()
- return ..()
+ ui_update()
+ return
/obj/machinery/atmospherics/components/binary/passive_gate/Destroy()
SSradio.remove_object(src,frequency)
@@ -58,29 +62,9 @@ Passive gate is similar to the regular pump except:
var/datum/gas_mixture/air1 = airs[1]
var/datum/gas_mixture/air2 = airs[2]
-
- var/output_starting_pressure = air2.return_pressure()
- var/input_starting_pressure = air1.return_pressure()
-
- if(output_starting_pressure >= min(target_pressure,input_starting_pressure-10))
- //No need to pump gas if target is already reached or input pressure is too low
- //Need at least 10 KPa difference to overcome friction in the mechanism
- return
-
- //Calculate necessary moles to transfer using PV = nRT
- if((air1.total_moles() > 0) && (air1.return_temperature()>0))
- var/pressure_delta = min(target_pressure - output_starting_pressure, (input_starting_pressure - output_starting_pressure)/2)
- //Can not have a pressure delta that would cause output_pressure > input_pressure
-
- var/transfer_moles = pressure_delta*air2.return_volume()/(air1.return_temperature() * R_IDEAL_GAS_EQUATION)
-
- //Actually transfer the gas
- var/datum/gas_mixture/removed = air1.remove(transfer_moles)
- air2.merge(removed)
-
+ if(air1.release_gas_to(air2, target_pressure))
update_parents()
-
//Radio remote control
/obj/machinery/atmospherics/components/binary/passive_gate/proc/set_frequency(new_frequency)
@@ -116,7 +100,7 @@ Passive gate is similar to the regular pump except:
var/data = list()
data["on"] = on
data["pressure"] = round(target_pressure)
- data["max_pressure"] = round(MAX_OUTPUT_PRESSURE)
+ data["max_pressure"] = round(ONE_ATMOSPHERE*100)
return data
/obj/machinery/atmospherics/components/binary/passive_gate/ui_act(action, params)
@@ -130,15 +114,16 @@ Passive gate is similar to the regular pump except:
if("pressure")
var/pressure = params["pressure"]
if(pressure == "max")
- pressure = MAX_OUTPUT_PRESSURE
+ pressure = ONE_ATMOSPHERE*100
. = TRUE
else if(text2num(pressure) != null)
pressure = text2num(pressure)
. = TRUE
if(.)
- target_pressure = clamp(pressure, 0, MAX_OUTPUT_PRESSURE)
+ target_pressure = clamp(pressure, 0, ONE_ATMOSPHERE*100)
investigate_log("was set to [target_pressure] kPa by [key_name(usr)]", INVESTIGATE_ATMOS)
- update_icon()
+ if(.)
+ update_icon()
/obj/machinery/atmospherics/components/binary/passive_gate/atmosinit()
..()
@@ -158,7 +143,7 @@ Passive gate is similar to the regular pump except:
on = !on
if("set_output_pressure" in signal.data)
- target_pressure = CLAMP(text2num(signal.data["set_output_pressure"]),0,ONE_ATMOSPHERE*50)
+ target_pressure = clamp(text2num(signal.data["set_output_pressure"]),0,ONE_ATMOSPHERE*100)
if(on != old_on)
investigate_log("was turned [on ? "on" : "off"] by a remote signal", INVESTIGATE_ATMOS)
@@ -169,6 +154,7 @@ Passive gate is similar to the regular pump except:
broadcast_status()
update_icon()
+ ui_update()
/obj/machinery/atmospherics/components/binary/passive_gate/power_change()
..()
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/pressure_valve.dm b/code/modules/atmospherics/machinery/components/binary_devices/pressure_valve.dm
new file mode 100644
index 0000000000000..79b18a9a1c132
--- /dev/null
+++ b/code/modules/atmospherics/machinery/components/binary_devices/pressure_valve.dm
@@ -0,0 +1,177 @@
+/obj/machinery/atmospherics/components/binary/pressure_valve
+ icon_state = "pvalve_map-2"
+ name = "pressure valve"
+ desc = "An activable one way valve that let gas pass through if the pressure on the input side is higher than the set pressure."
+
+ can_unwrench = TRUE
+ shift_underlay_only = FALSE
+
+ //Amount of pressure needed before the valve for it to open
+ var/target_pressure = ONE_ATMOSPHERE
+ //Frequency for radio signaling
+ var/frequency = 0
+ //ID for radio signaling
+ var/id = null
+ //Connection to the radio processing
+ var/datum/radio_frequency/radio_connection
+ //Check if the gas is moving from one pipenet to the other
+ var/is_gas_flowing = FALSE
+
+ construction_type = /obj/item/pipe/directional
+ pipe_state = "pvalve"
+
+/obj/machinery/atmospherics/components/binary/pressure_valve/CtrlClick(mob/user)
+ if(can_interact(user))
+ on = !on
+ investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS)
+ update_icon()
+ return ..()
+
+/obj/machinery/atmospherics/components/binary/pressure_valve/AltClick(mob/user)
+ if(can_interact(user))
+ target_pressure = MAX_OUTPUT_PRESSURE
+ balloon_alert(user, "Set to [target_pressure] kPa")
+ investigate_log("was set to [target_pressure] kPa by [key_name(user)]", INVESTIGATE_ATMOS)
+ update_icon()
+ return ..()
+
+/obj/machinery/atmospherics/components/binary/pressure_valve/Destroy()
+ SSradio.remove_object(src,frequency)
+ if(radio_connection)
+ radio_connection = null
+ return ..()
+
+/obj/machinery/atmospherics/components/binary/pressure_valve/update_icon_nopipes()
+ if(on && is_operational())
+ if(is_gas_flowing)
+ icon_state = "pvalve_flow"
+ else
+ icon_state = "pvalve_on"
+ else
+ icon_state = "pvalve_off"
+
+/obj/machinery/atmospherics/components/binary/pressure_valve/process_atmos()
+
+ if(!on || !is_operational())
+ return
+
+ var/datum/gas_mixture/air1 = airs[1]
+ var/datum/gas_mixture/air2 = airs[2]
+
+ if(air1.return_pressure() > target_pressure)
+ if(air1.release_gas_to(air2, air1.return_pressure()))
+ update_parents()
+ is_gas_flowing = TRUE
+ else
+ is_gas_flowing = FALSE
+ update_icon_nopipes()
+
+/obj/machinery/atmospherics/components/binary/pressure_valve/proc/set_frequency(new_frequency)
+ SSradio.remove_object(src, frequency)
+ frequency = new_frequency
+ if(frequency)
+ radio_connection = SSradio.add_object(src, frequency, filter = RADIO_ATMOSIA)
+
+/obj/machinery/atmospherics/components/binary/pressure_valve/proc/broadcast_status()
+ if(!radio_connection)
+ return
+
+ var/datum/signal/signal = new(list(
+ "tag" = id,
+ "device" = "AGP",
+ "power" = on,
+ "target_output" = target_pressure,
+ "sigtype" = "status"
+ ))
+ radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA)
+
+/obj/machinery/atmospherics/components/binary/pressure_valve/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "AtmosPump", name)
+ ui.open()
+
+/obj/machinery/atmospherics/components/binary/pressure_valve/ui_data()
+ var/data = list()
+ data["on"] = on
+ data["pressure"] = round(target_pressure)
+ data["max_pressure"] = round(ONE_ATMOSPHERE*100)
+ return data
+
+/obj/machinery/atmospherics/components/binary/pressure_valve/ui_act(action, params)
+ if(..())
+ return
+ switch(action)
+ if("power")
+ on = !on
+ investigate_log("was turned [on ? "on" : "off"] by [key_name(usr)]", INVESTIGATE_ATMOS)
+ . = TRUE
+ if("pressure")
+ var/pressure = params["pressure"]
+ if(pressure == "max")
+ pressure = ONE_ATMOSPHERE*100
+ . = TRUE
+ else if(text2num(pressure) != null)
+ pressure = text2num(pressure)
+ . = TRUE
+ if(.)
+ target_pressure = clamp(pressure, 0, ONE_ATMOSPHERE*100)
+ investigate_log("was set to [target_pressure] kPa by [key_name(usr)]", INVESTIGATE_ATMOS)
+ update_icon()
+
+/obj/machinery/atmospherics/components/binary/pressure_valve/atmosinit()
+ . = ..()
+ if(frequency)
+ set_frequency(frequency)
+
+/obj/machinery/atmospherics/components/binary/pressure_valve/receive_signal(datum/signal/signal)
+ if(!signal.data["tag"] || (signal.data["tag"] != id) || (signal.data["sigtype"]!="command"))
+ return
+
+ var/old_on = on //for logging
+
+ if("power" in signal.data)
+ on = text2num(signal.data["power"])
+
+ if("power_toggle" in signal.data)
+ on = !on
+
+ if("set_output_pressure" in signal.data)
+ target_pressure = clamp(text2num(signal.data["set_output_pressure"]),0,ONE_ATMOSPHERE*100)
+
+ if(on != old_on)
+ investigate_log("was turned [on ? "on" : "off"] by a remote signal", INVESTIGATE_ATMOS)
+
+ if("status" in signal.data)
+ broadcast_status()
+ return
+
+ broadcast_status()
+ update_icon()
+
+/obj/machinery/atmospherics/components/binary/pressure_valve/can_unwrench(mob/user)
+ . = ..()
+ if(. && on && is_operational())
+ to_chat(user, "You cannot unwrench [src], turn it off first!")
+ return FALSE
+
+
+/obj/machinery/atmospherics/components/binary/pressure_valve/layer1
+ piping_layer = 1
+ icon_state= "pvalve_map-1"
+
+/obj/machinery/atmospherics/components/binary/pressure_valve/layer3
+ piping_layer = 3
+ icon_state= "pvalve_map-3"
+
+/obj/machinery/atmospherics/components/binary/pressure_valve/on
+ on = TRUE
+ icon_state = "pvalve_on_map-2"
+
+/obj/machinery/atmospherics/components/binary/pressure_valve/on/layer1
+ piping_layer = 1
+ icon_state= "pvalve_on_map-1"
+
+/obj/machinery/atmospherics/components/binary/pressure_valve/on/layer3
+ piping_layer = 3
+ icon_state= "pvalve_on_map-3"
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/pump.dm
index 84d5a8d02e080..b8962bd27fe42 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/pump.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/pump.dm
@@ -34,13 +34,16 @@
if(can_interact(user))
on = !on
update_icon()
+ ui_update()
return ..()
/obj/machinery/atmospherics/components/binary/pump/AltClick(mob/user)
if(can_interact(user))
target_pressure = MAX_OUTPUT_PRESSURE
+ balloon_alert(user, "Set to [target_pressure] kPa")
update_icon()
- return ..()
+ ui_update()
+ return
/obj/machinery/atmospherics/components/binary/pump/Destroy()
SSradio.remove_object(src,frequency)
@@ -55,24 +58,18 @@
// ..()
if(!on || !is_operational())
return
-
var/datum/gas_mixture/air1 = airs[1]
var/datum/gas_mixture/air2 = airs[2]
-
var/output_starting_pressure = air2.return_pressure()
-
if((target_pressure - output_starting_pressure) < 0.01)
//No need to pump gas if target is already reached!
return
-
//Calculate necessary moles to transfer using PV=nRT
if((air1.total_moles() > 0) && (air1.return_temperature()>0))
var/pressure_delta = target_pressure - output_starting_pressure
var/transfer_moles = pressure_delta*air2.return_volume()/(air1.return_temperature() * R_IDEAL_GAS_EQUATION)
- //Actually transfer the gas
- var/datum/gas_mixture/removed = air1.remove(transfer_moles)
- air2.merge(removed)
+ air1.transfer_to(air2,transfer_moles)
update_parents()
@@ -132,7 +129,8 @@
if(.)
target_pressure = clamp(pressure, 0, MAX_OUTPUT_PRESSURE)
investigate_log("was set to [target_pressure] kPa by [key_name(usr)]", INVESTIGATE_ATMOS)
- update_icon()
+ if(.)
+ update_icon()
/obj/machinery/atmospherics/components/binary/pump/atmosinit()
..()
@@ -163,6 +161,7 @@
broadcast_status()
update_icon()
+ ui_update()
/obj/machinery/atmospherics/components/binary/pump/power_change()
..()
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/temperature_gate.dm b/code/modules/atmospherics/machinery/components/binary_devices/temperature_gate.dm
new file mode 100644
index 0000000000000..ef08d93fa164d
--- /dev/null
+++ b/code/modules/atmospherics/machinery/components/binary_devices/temperature_gate.dm
@@ -0,0 +1,129 @@
+/obj/machinery/atmospherics/components/binary/temperature_gate
+ icon_state = "tgate_map-2"
+ name = "temperature gate"
+ desc = "An activable gate that compares the input temperature with the interface set temperature to check if the gas can flow from the input side to the output side or not."
+
+ can_unwrench = TRUE
+ shift_underlay_only = FALSE
+ construction_type = /obj/item/pipe/directional
+ pipe_state = "tgate"
+
+ //If the temperature of the mix before the gate is lower than this, the gas will flow (if inverted, if the temperature of the mix before the gate is higher than this)
+ var/target_temperature = T0C
+ //Minimum allowed temperature
+ var/minimum_temperature = TCMB
+ //Maximum allowed temperature to be set
+ var/max_temperature = 4500
+ //Check if the sensor should let gas pass if temperature in the mix is less/higher than the target one
+ var/inverted = FALSE
+ //Check if the gas is moving from one pipenet to the other
+ var/is_gas_flowing = FALSE
+
+/obj/machinery/atmospherics/components/binary/temperature_gate/CtrlClick(mob/user)
+ if(can_interact(user))
+ on = !on
+ investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS)
+ update_icon()
+ return ..()
+
+/obj/machinery/atmospherics/components/binary/temperature_gate/AltClick(mob/user)
+ if(can_interact(user))
+ target_temperature = max_temperature
+ balloon_alert(user, "Set to [target_temperature] K")
+ investigate_log("was set to [target_temperature] K by [key_name(user)]", INVESTIGATE_ATMOS)
+ update_icon()
+ return ..()
+
+
+/obj/machinery/atmospherics/components/binary/temperature_gate/examine(mob/user)
+ . = ..()
+ . += "This device will let gas flow if the temperature of the gas in the input is [inverted ? "higher" : "lower"] than the temperature set in the interface."
+ if(inverted)
+ . += "The device settings can be restored if a multitool is used on it."
+ else
+ . += "The sensor's settings can be changed by using a multitool on the device."
+
+/obj/machinery/atmospherics/components/binary/temperature_gate/update_icon_nopipes()
+ if(on && is_operational())
+ if(is_gas_flowing)
+ icon_state = "tgate_flow-[set_overlay_offset(piping_layer)]"
+ else
+ icon_state = "tgate_on-[set_overlay_offset(piping_layer)]"
+ else
+ icon_state = "tgate_off-[set_overlay_offset(piping_layer)]"
+
+
+/obj/machinery/atmospherics/components/binary/temperature_gate/process_atmos()
+
+ if(!on || !is_operational())
+ return
+
+ var/datum/gas_mixture/air1 = airs[1]
+ var/datum/gas_mixture/air2 = airs[2]
+
+ if(!inverted)
+ if(air1.return_temperature() < target_temperature)
+ if(air1.release_gas_to(air2, air1.return_pressure()))
+ update_parents()
+ is_gas_flowing = TRUE
+ else
+ is_gas_flowing = FALSE
+ else
+ if(air1.return_temperature() > target_temperature)
+ if(air1.release_gas_to(air2, air1.return_pressure()))
+ update_parents()
+ is_gas_flowing = TRUE
+ else
+ is_gas_flowing = FALSE
+ update_icon_nopipes()
+
+/obj/machinery/atmospherics/components/binary/temperature_gate/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "AtmosTempGate", name)
+ ui.open()
+
+/obj/machinery/atmospherics/components/binary/temperature_gate/ui_data()
+ var/data = list()
+ data["on"] = on
+ data["temperature"] = round(target_temperature)
+ data["min_temperature"] = round(minimum_temperature)
+ data["max_temperature"] = round(max_temperature)
+ return data
+
+/obj/machinery/atmospherics/components/binary/temperature_gate/ui_act(action, params)
+ if(..())
+ return
+ switch(action)
+ if("power")
+ on = !on
+ investigate_log("was turned [on ? "on" : "off"] by [key_name(usr)]", INVESTIGATE_ATMOS)
+ . = TRUE
+ if("temperature")
+ var/temperature = params["temperature"]
+ if(temperature == "max")
+ temperature = max_temperature
+ . = TRUE
+ else if(text2num(temperature) != null)
+ temperature = text2num(temperature)
+ . = TRUE
+ if(.)
+ target_temperature = clamp(minimum_temperature, temperature, max_temperature)
+ investigate_log("was set to [target_temperature] K by [key_name(usr)]", INVESTIGATE_ATMOS)
+ update_icon()
+
+/obj/machinery/atmospherics/components/binary/temperature_gate/can_unwrench(mob/user)
+ . = ..()
+ if(. && on && is_operational())
+ to_chat(user, "You cannot unwrench [src], turn it off first!")
+ return FALSE
+
+/obj/machinery/atmospherics/components/binary/temperature_gate/multitool_act(mob/living/user, obj/item/multitool/I)
+ . = ..()
+ if (istype(I))
+ inverted = !inverted
+ if(inverted)
+ balloon_alert(user, "Sensors set to release when the temperature is above")
+ else
+ balloon_alert(user, "Sensors set to default")
+ return TRUE
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/temperature_pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/temperature_pump.dm
new file mode 100644
index 0000000000000..867a2864e8ad9
--- /dev/null
+++ b/code/modules/atmospherics/machinery/components/binary_devices/temperature_pump.dm
@@ -0,0 +1,95 @@
+/obj/machinery/atmospherics/components/binary/temperature_pump
+ icon_state = "tpump_map-2"
+ name = "temperature pump"
+ desc = "A pump that moves heat from one pipeline to another. The input will get cooler, and the output will get hotter."
+
+ can_unwrench = TRUE
+ shift_underlay_only = FALSE
+
+ //Percent of the heat delta to transfer
+ var/heat_transfer_rate = 0
+ //Maximum allowed transfer percentage
+ var/max_heat_transfer_rate = 100
+
+ construction_type = /obj/item/pipe/directional
+ pipe_state = "tpump"
+
+/obj/machinery/atmospherics/components/binary/temperature_pump/CtrlClick(mob/user)
+ if(can_interact(user))
+ on = !on
+ investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS)
+ update_icon()
+ return ..()
+
+/obj/machinery/atmospherics/components/binary/temperature_pump/AltClick(mob/user)
+ if(can_interact(user) && !(heat_transfer_rate == max_heat_transfer_rate))
+ heat_transfer_rate = max_heat_transfer_rate
+ balloon_alert(user, "Set to [heat_transfer_rate]%")
+ investigate_log("was set to [heat_transfer_rate]% by [key_name(user)]", INVESTIGATE_ATMOS)
+ update_icon()
+ return ..()
+
+/obj/machinery/atmospherics/components/binary/temperature_pump/update_icon_nopipes()
+ icon_state = "tpump_[on && is_operational() ? "on" : "off"]-[set_overlay_offset(piping_layer)]"
+
+/obj/machinery/atmospherics/components/binary/temperature_pump/process_atmos()
+
+ if(!on || !is_operational())
+ return
+
+ var/datum/gas_mixture/air_input = airs[1]
+ var/datum/gas_mixture/air_output = airs[2]
+
+ if(!QUANTIZE(air_input.total_moles()) || !QUANTIZE(air_output.total_moles())) //Don't transfer if there's no gas
+ return
+ var/datum/gas_mixture/remove_input = air_input.remove_ratio(0.9)
+ var/datum/gas_mixture/remove_output = air_output.remove_ratio(0.9)
+
+ var/coolant_temperature_delta = remove_input.return_temperature() - remove_output.return_temperature()
+
+ if(coolant_temperature_delta > 0)
+ var/input_capacity = remove_input.heat_capacity()
+ var/output_capacity = air_output.heat_capacity()
+
+ var/cooling_heat_amount = (heat_transfer_rate * 0.01) * coolant_temperature_delta * (input_capacity * output_capacity / (input_capacity + output_capacity))
+ remove_input.set_temperature(max(remove_input.return_temperature() - (cooling_heat_amount / input_capacity), TCMB))
+ remove_output.set_temperature(max(remove_output.return_temperature() + (cooling_heat_amount / output_capacity), TCMB))
+
+ air_input.merge(remove_input)
+ air_output.merge(remove_output)
+
+ update_parents()
+
+/obj/machinery/atmospherics/components/binary/temperature_pump/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "AtmosTempPump", name)
+ ui.open()
+
+/obj/machinery/atmospherics/components/binary/temperature_pump/ui_data()
+ var/data = list()
+ data["on"] = on
+ data["rate"] = round(heat_transfer_rate)
+ data["max_heat_transfer_rate"] = round(max_heat_transfer_rate)
+ return data
+
+/obj/machinery/atmospherics/components/binary/temperature_pump/ui_act(action, params)
+ if(..())
+ return
+ switch(action)
+ if("power")
+ on = !on
+ investigate_log("was turned [on ? "on" : "off"] by [key_name(usr)]", INVESTIGATE_ATMOS)
+ . = TRUE
+ if("rate")
+ var/rate = params["rate"]
+ if(rate == "max")
+ rate = max_heat_transfer_rate
+ . = TRUE
+ else if(text2num(rate) != null)
+ rate = text2num(rate)
+ . = TRUE
+ if(.)
+ heat_transfer_rate = clamp(rate, 0, max_heat_transfer_rate)
+ investigate_log("was set to [heat_transfer_rate]% by [key_name(usr)]", INVESTIGATE_ATMOS)
+ update_icon()
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/valve.dm b/code/modules/atmospherics/machinery/components/binary_devices/valve.dm
index cbf0cb95f2b9e..c7ecdb4b4e8cf 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/valve.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/valve.dm
@@ -24,24 +24,40 @@ It's like a regular ol' straight pipe, but you can turn it on and off.
var/switching = FALSE
+/obj/machinery/atmospherics/components/binary/valve/Destroy()
+ //Should only happen on extreme circumstances
+ if(on)
+ //Let's give presumably now-severed pipenets a chance to scramble for what's happening at next SSair fire()
+ if(parents[1])
+ parents[1].update = PIPENET_UPDATE_STATUS_RECONCILE_NEEDED
+ if(parents[2])
+ parents[2].update = PIPENET_UPDATE_STATUS_RECONCILE_NEEDED
+ . = ..()
+
/obj/machinery/atmospherics/components/binary/valve/update_icon_nopipes(animation = FALSE)
normalize_cardinal_directions()
if(animation)
flick("[valve_type]valve_[on][!on]", src)
icon_state = "[valve_type]valve_[on ? "on" : "off"]"
-/obj/machinery/atmospherics/components/binary/valve/proc/toggle()
+/**
+ * Called by finish_interact(), switch between open and closed, reconcile the air between two pipelines
+ */
+/obj/machinery/atmospherics/components/binary/valve/proc/set_open(to_open)
+ if(on == to_open)
+ return
+ SEND_SIGNAL(src, COMSIG_VALVE_SET_OPEN, to_open)
+ . = on
+ on = to_open
if(on)
- on = FALSE
- update_icon_nopipes()
- investigate_log("was closed by [usr ? key_name(usr) : "a remote signal"]", INVESTIGATE_ATMOS)
- else
- on = TRUE
update_icon_nopipes()
update_parents()
var/datum/pipeline/parent1 = parents[1]
parent1.reconcile_air()
investigate_log("was opened by [usr ? key_name(usr) : "a remote signal"]", INVESTIGATE_ATMOS)
+ else
+ update_icon_nopipes()
+ investigate_log("was closed by [usr ? key_name(usr) : "a remote signal"]", INVESTIGATE_ATMOS)
/obj/machinery/atmospherics/components/binary/valve/interact(mob/user)
add_fingerprint(usr)
@@ -52,7 +68,7 @@ It's like a regular ol' straight pipe, but you can turn it on and off.
addtimer(CALLBACK(src, .proc/finish_interact), 10)
/obj/machinery/atmospherics/components/binary/valve/proc/finish_interact()
- toggle()
+ set_open(!on)
switching = FALSE
@@ -66,6 +82,69 @@ It's like a regular ol' straight pipe, but you can turn it on and off.
interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OFFLINE | INTERACT_MACHINE_OPEN | INTERACT_MACHINE_OPEN_SILICON
+/obj/machinery/atmospherics/components/binary/valve/digital/Initialize()
+ . = ..()
+ AddComponent(/datum/component/usb_port, list(/obj/item/circuit_component/digital_valve))
+
+/obj/item/circuit_component/digital_valve
+ display_name = "Digital Valve"
+ display_desc = "The interface for communicating with a digital valve."
+
+ var/obj/machinery/atmospherics/components/binary/valve/digital/attached_valve
+
+ /// Opens the digital valve
+ var/datum/port/input/open
+ /// Closes the digital valve
+ var/datum/port/input/close
+
+ /// Whether the valve is currently open
+ var/datum/port/output/is_open
+ /// Sent when the valve is opened
+ var/datum/port/output/opened
+ /// Sent when the valve is closed
+ var/datum/port/output/closed
+
+/obj/item/circuit_component/digital_valve/Initialize()
+ . = ..()
+ open = add_input_port("Open", PORT_TYPE_SIGNAL)
+ close = add_input_port("Close", PORT_TYPE_SIGNAL)
+
+ is_open = add_output_port("Is Open", PORT_TYPE_NUMBER)
+ opened = add_output_port("Opened", PORT_TYPE_SIGNAL)
+ closed = add_output_port("Closed", PORT_TYPE_SIGNAL)
+
+/obj/item/circuit_component/digital_valve/register_usb_parent(atom/movable/shell)
+ . = ..()
+ if(istype(shell, /obj/machinery/atmospherics/components/binary/valve/digital))
+ attached_valve = shell
+ RegisterSignal(attached_valve, COMSIG_VALVE_SET_OPEN, .proc/handle_valve_toggled)
+
+/obj/item/circuit_component/digital_valve/unregister_usb_parent(atom/movable/shell)
+ UnregisterSignal(attached_valve, COMSIG_VALVE_SET_OPEN)
+ attached_valve = null
+ return ..()
+
+/obj/item/circuit_component/digital_valve/proc/handle_valve_toggled(datum/source, on)
+ is_open.set_output(on)
+ if(on)
+ opened.set_output(COMPONENT_SIGNAL)
+ else
+ closed.set_output(COMPONENT_SIGNAL)
+
+/obj/item/circuit_component/digital_valve/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ if(!attached_valve)
+ return
+
+ if(COMPONENT_TRIGGERED_BY(open, port) && !attached_valve.on)
+ attached_valve.set_open(TRUE)
+ if(COMPONENT_TRIGGERED_BY(close, port) && attached_valve.on)
+ attached_valve.set_open(FALSE)
+
+
/obj/machinery/atmospherics/components/binary/valve/digital/update_icon_nopipes(animation)
if(!is_operational())
normalize_cardinal_directions()
@@ -73,7 +152,6 @@ It's like a regular ol' straight pipe, but you can turn it on and off.
return
..()
-
/obj/machinery/atmospherics/components/binary/valve/layer1
piping_layer = 1
icon_state = "mvalve_map-1"
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm
index b95f45d34473c..092253bbf7ede 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm
@@ -35,13 +35,16 @@
if(can_interact(user))
on = !on
update_icon()
+ ui_update()
return ..()
/obj/machinery/atmospherics/components/binary/volume_pump/AltClick(mob/user)
if(can_interact(user))
transfer_rate = MAX_TRANSFER_RATE
+ balloon_alert(user, "Set to [transfer_rate] L/s")
update_icon()
- return ..()
+ ui_update()
+ return
/obj/machinery/atmospherics/components/binary/volume_pump/Destroy()
SSradio.remove_object(src,frequency)
@@ -69,19 +72,15 @@
if(overclocked && (output_starting_pressure-input_starting_pressure > 1000))//Overclocked pumps can only force gas a certain amount.
return
-
- var/transfer_ratio = transfer_rate/air1.return_volume()
-
- var/datum/gas_mixture/removed = air1.remove_ratio(transfer_ratio)
-
if(overclocked)//Some of the gas from the mixture leaks to the environment when overclocked
var/turf/open/T = loc
if(istype(T))
- var/datum/gas_mixture/leaked = removed.remove_ratio(VOLUME_PUMP_LEAK_AMOUNT)
+ var/datum/gas_mixture/leaked = air1.remove_ratio(VOLUME_PUMP_LEAK_AMOUNT)
T.assume_air(leaked)
T.air_update_turf()
- air2.merge(removed)
+ var/transfer_ratio = transfer_rate / air1.return_volume()
+ air1.transfer_ratio_to(air2,transfer_ratio)
update_parents()
@@ -150,7 +149,8 @@
if(.)
transfer_rate = clamp(rate, 0, MAX_TRANSFER_RATE)
investigate_log("was set to [transfer_rate] L/s by [key_name(usr)]", INVESTIGATE_ATMOS)
- update_icon()
+ if(.)
+ update_icon()
/obj/machinery/atmospherics/components/binary/volume_pump/receive_signal(datum/signal/signal)
if(!signal.data["tag"] || (signal.data["tag"] != id) || (signal.data["sigtype"]!="command"))
@@ -177,6 +177,7 @@
broadcast_status()
update_icon()
+ ui_update()
/obj/machinery/atmospherics/components/binary/volume_pump/power_change()
..()
diff --git a/code/modules/atmospherics/machinery/components/components_base.dm b/code/modules/atmospherics/machinery/components/components_base.dm
index 9295c702cc0c3..5fdb40cf4b759 100644
--- a/code/modules/atmospherics/machinery/components/components_base.dm
+++ b/code/modules/atmospherics/machinery/components/components_base.dm
@@ -65,8 +65,10 @@
// Pipenet stuff; housekeeping
/obj/machinery/atmospherics/components/nullifyNode(i)
- if(nodes[i])
+ // Every node has a parent pipeline and an air associated with it, but we need to accomdate for edge cases like init dir cache building...
+ if(parents[i])
nullifyPipenet(parents[i])
+ if(airs[i])
QDEL_NULL(airs[i])
..()
@@ -76,7 +78,7 @@
/obj/machinery/atmospherics/components/build_network()
for(var/i in 1 to device_type)
- if(!parents[i])
+ if(QDELETED(parents[i]))
parents[i] = new /datum/pipeline()
var/datum/pipeline/P = parents[i]
P.build_pipeline(src)
@@ -87,10 +89,27 @@
var/i = parents.Find(reference)
reference.other_airs -= airs[i]
reference.other_atmosmch -= src
+ /**
+ * We explicitly qdel pipeline when this particular pipeline
+ * is projected to have no member and cause GC problems.
+ * We have to do this because components don't qdel pipelines
+ * while pipes must and will happily wreck and rebuild everything again
+ * every time they are qdeleted.
+ */
+ if(!(reference.other_atmosmch.len || reference.members.len || QDESTROYING(reference)))
+ qdel(reference)
parents[i] = null
+// We should return every air sharing a parent
/obj/machinery/atmospherics/components/returnPipenetAir(datum/pipeline/reference)
- return airs[parents.Find(reference)]
+ for(var/i in 1 to device_type)
+ if(parents[i] == reference)
+ if(.)
+ if(!islist(.))
+ . = list(.)
+ . += airs[i]
+ else
+ . = airs[i]
/obj/machinery/atmospherics/components/pipeline_expansion(datum/pipeline/reference)
if(reference)
@@ -121,14 +140,9 @@
times_lost++
var/shared_loss = lost/times_lost
- var/datum/gas_mixture/to_release
for(var/i in 1 to device_type)
var/datum/gas_mixture/air = airs[i]
- if(!to_release)
- to_release = air.remove(shared_loss)
- continue
- to_release.merge(air.remove(shared_loss))
- T.assume_air(to_release)
+ T.assume_air_moles(air, shared_loss)
air_update_turf(1)
/obj/machinery/atmospherics/components/proc/safe_input(var/title, var/text, var/default_set)
@@ -143,9 +157,11 @@
for(var/i in 1 to device_type)
var/datum/pipeline/parent = parents[i]
if(!parent)
- WARNING("Component is missing a pipenet! Rebuilding...")
- build_network()
- parent.update = 1
+ //WARNING("Component is missing a pipenet! Rebuilding...")
+ //At pre-SSair_rebuild_pipenets times, not having a parent wasn't supposed to happen
+ SSair.add_to_rebuild_queue(src)
+ continue
+ parent.update = PIPENET_UPDATE_STATUS_RECONCILE_NEEDED
/obj/machinery/atmospherics/components/returnPipenets()
. = list()
diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm b/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm
index eacea6d4416fd..96b330a961dd7 100644
--- a/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm
+++ b/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm
@@ -21,13 +21,16 @@
if(can_interact(user))
on = !on
update_icon()
+ ui_update()
return ..()
/obj/machinery/atmospherics/components/trinary/filter/AltClick(mob/user)
if(can_interact(user))
transfer_rate = MAX_TRANSFER_RATE
+ balloon_alert(user, "Set to [transfer_rate] L/s")
update_icon()
- return ..()
+ ui_update()
+ return
/obj/machinery/atmospherics/components/trinary/filter/proc/set_frequency(new_frequency)
SSradio.remove_object(src, frequency)
@@ -85,37 +88,17 @@
//No need to transfer if target is already full!
return
- var/transfer_ratio = transfer_rate/air1.return_volume()
+ var/transfer_ratio = transfer_rate / air1.return_volume()
//Actually transfer the gas
if(transfer_ratio <= 0)
return
- var/datum/gas_mixture/removed = air1.remove_ratio(transfer_ratio)
-
- if(!removed)
- return
-
- var/filtering = TRUE
- if(!ispath(filter_type))
- if(filter_type)
- filter_type = gas_id2path(filter_type) //support for mappers so they don't need to type out paths
- else
- filtering = FALSE
-
- if(filtering && removed.get_moles(filter_type))
- var/datum/gas_mixture/filtered_out = new
-
- filtered_out.set_temperature(removed.return_temperature())
- filtered_out.set_moles(filter_type, removed.get_moles(filter_type))
-
- removed.set_moles(filter_type, 0)
-
- var/datum/gas_mixture/target = (air2.return_pressure() < MAX_OUTPUT_PRESSURE ? air2 : air1) //if there's no room for the filtered gas; just leave it in air1
- target.merge(filtered_out)
-
- air3.merge(removed)
+ if(filter_type && air2.return_pressure() <= 9000)
+ air1.scrub_into(air2, transfer_ratio, list(filter_type))
+ if(air3.return_pressure() <= 9000)
+ air1.transfer_ratio_to(air3, transfer_ratio)
update_parents()
@@ -140,10 +123,9 @@
data["max_rate"] = round(MAX_TRANSFER_RATE)
data["filter_types"] = list()
- data["filter_types"] += list(list("name" = "Nothing", "path" = "", "selected" = !filter_type))
- for(var/path in GLOB.meta_gas_info)
- var/list/gas = GLOB.meta_gas_info[path]
- data["filter_types"] += list(list("name" = gas[META_GAS_NAME], "id" = gas[META_GAS_ID], "selected" = (path == gas_id2path(filter_type))))
+ data["filter_types"] += list(list("name" = "Nothing", "id" = "", "selected" = !filter_type))
+ for(var/id in GLOB.gas_data.ids)
+ data["filter_types"] += list(list("name" = GLOB.gas_data.names[id], "id" = id, "selected" = (id == filter_type)))
return data
@@ -173,13 +155,14 @@
if("filter")
filter_type = null
var/filter_name = "nothing"
- var/gas = gas_id2path(params["mode"])
- if(gas in GLOB.meta_gas_info)
+ var/gas = params["mode"]
+ if(gas in GLOB.gas_data.names)
filter_type = gas
- filter_name = GLOB.meta_gas_info[gas][META_GAS_NAME]
+ filter_name = GLOB.gas_data.names[gas]
investigate_log("was set to filter [filter_name] by [key_name(usr)]", INVESTIGATE_ATMOS)
. = TRUE
- update_icon()
+ if(.)
+ update_icon()
/obj/machinery/atmospherics/components/trinary/filter/can_unwrench(mob/user)
. = ..()
diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm b/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm
index ae7922605da1f..f3e55f45c3bbd 100644
--- a/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm
+++ b/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm
@@ -23,13 +23,16 @@
if(can_interact(user))
on = !on
update_icon()
+ ui_update()
return ..()
/obj/machinery/atmospherics/components/trinary/mixer/AltClick(mob/user)
if(can_interact(user))
target_pressure = MAX_OUTPUT_PRESSURE
+ balloon_alert(user, "Set to [target_pressure] kPa")
update_icon()
- return ..()
+ ui_update()
+ return
/obj/machinery/atmospherics/components/trinary/mixer/update_icon()
cut_overlays()
@@ -117,19 +120,17 @@
//Actually transfer the gas
if(transfer_moles1)
- var/datum/gas_mixture/removed1 = air1.remove(transfer_moles1)
- air3.merge(removed1)
+ air1.transfer_to(air3, transfer_moles1)
var/datum/pipeline/parent1 = parents[1]
- parent1.update = TRUE
+ parent1.update = PIPENET_UPDATE_STATUS_RECONCILE_NEEDED
if(transfer_moles2)
- var/datum/gas_mixture/removed2 = air2.remove(transfer_moles2)
- air3.merge(removed2)
+ air2.transfer_to(air3, transfer_moles2)
var/datum/pipeline/parent2 = parents[2]
- parent2.update = TRUE
+ parent2.update = PIPENET_UPDATE_STATUS_RECONCILE_NEEDED
var/datum/pipeline/parent3 = parents[3]
- parent3.update = TRUE
+ parent3.update = PIPENET_UPDATE_STATUS_RECONCILE_NEEDED
/obj/machinery/atmospherics/components/trinary/mixer/ui_state(mob/user)
@@ -179,7 +180,8 @@
adjust_node1_value(100 - value)
investigate_log("was set to [node2_concentration] % on node 2 by [key_name(usr)]", INVESTIGATE_ATMOS)
. = TRUE
- update_icon()
+ if(.)
+ update_icon()
/obj/machinery/atmospherics/components/trinary/mixer/proc/adjust_node1_value(newValue)
node1_concentration = newValue / 100
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
index 46c5ed12699e9..1c11922777f4f 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
@@ -1,4 +1,6 @@
#define CRYOMOBS 'icons/obj/cryo_mobs.dmi'
+#define CRYO_MULTIPLY_FACTOR 1.5 // Multiply factor is used with efficiency to multiply Tx quantity and how much extra is transfered to occupant magically.
+#define CRYO_TX_QTY 0.4 // Tx quantity is how much volume should be removed from the cell's beaker - multiplied by delta_time
/obj/machinery/atmospherics/components/unary/cryo_cell
name = "cryo cell"
@@ -6,7 +8,7 @@
icon_state = "pod-off"
density = TRUE
max_integrity = 350
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 30, "acid" = 30)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 30, "acid" = 30, "stamina" = 0)
layer = ABOVE_WINDOW_LAYER
state_open = FALSE
circuit = /obj/item/circuitboard/machine/cryo_tube
@@ -25,7 +27,6 @@
var/conduction_coefficient = 0.3
var/obj/item/reagent_containers/glass/beaker = null
- var/reagent_transfer = 0
var/obj/item/radio/radio
var/radio_key = /obj/item/encryptionkey/headset_med
@@ -73,7 +74,7 @@
/obj/machinery/atmospherics/components/unary/cryo_cell/examine(mob/user) //this is leaving out everything but efficiency since they follow the same idea of "better beaker, better results"
. = ..()
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Efficiency at [efficiency*100]%."
+ . += "The status display reads: Efficiency at [efficiency*100]%."
/obj/machinery/atmospherics/components/unary/cryo_cell/Destroy()
QDEL_NULL(radio)
@@ -83,7 +84,13 @@
/obj/machinery/atmospherics/components/unary/cryo_cell/contents_explosion(severity, target)
..()
if(beaker)
- beaker.ex_act(severity, target)
+ switch(severity)
+ if(EXPLODE_DEVASTATE)
+ SSexplosions.high_mov_atom += beaker
+ if(EXPLODE_HEAVY)
+ SSexplosions.med_mov_atom += beaker
+ if(EXPLODE_LIGHT)
+ SSexplosions.low_mov_atom += beaker
/obj/machinery/atmospherics/components/unary/cryo_cell/handle_atom_del(atom/A)
..()
@@ -166,19 +173,32 @@
/obj/machinery/atmospherics/components/unary/cryo_cell/nap_violation(mob/violator)
open_machine()
-/obj/machinery/atmospherics/components/unary/cryo_cell/process()
- ..()
-
+/obj/machinery/atmospherics/components/unary/cryo_cell/process(delta_time)
if(!on)
return
+
if(!is_operational())
on = FALSE
update_icon()
return
- if(!occupant)
+
+ if(!occupant)//Won't operate unless there's an occupant.
+ on = FALSE
+ update_icon()
+ var/msg = "Aborting. No occupant detected."
+ radio.talk_into(src, msg, radio_channel)
+ return
+
+ if(!beaker?.reagents?.reagent_list.len) //No beaker or beaker without reagents with stop the machine from running.
+ on = FALSE
+ update_icon()
+ var/msg = "Aborting. No beaker or chemicals installed."
+ radio.talk_into(src, msg, radio_channel)
return
var/mob/living/mob_occupant = occupant
+ if(mob_occupant.on_fire) //Extinguish occupant, happens after the occupant is healed and ejected.
+ mob_occupant.ExtinguishMob()
if(!check_nap_violations())
return
if(mob_occupant.stat == DEAD) // We don't bother with dead people.
@@ -199,15 +219,11 @@
if(air1.total_moles())
if(mob_occupant.bodytemperature < T0C) // Sleepytime. Why? More cryo magic.
- mob_occupant.Sleeping((mob_occupant.bodytemperature * sleep_factor) * 2000)
- mob_occupant.Unconscious((mob_occupant.bodytemperature * unconscious_factor) * 2000)
- if(beaker)
- if(reagent_transfer == 0) // Magically transfer reagents. Because cryo magic.
- beaker.reagents.trans_to(occupant, 1, efficiency * 0.25) // Transfer reagents.
- beaker.reagents.reaction(occupant, VAPOR)
- air1.adjust_moles(/datum/gas/oxygen, -max(0,air1.get_moles(/datum/gas/oxygen) - 2 / efficiency)) //Let's use gas for this
- if(++reagent_transfer >= 10 * efficiency) // Throttle reagent transfer (higher efficiency will transfer the same amount but consume less from the beaker).
- reagent_transfer = 0
+ mob_occupant.Sleeping((mob_occupant.bodytemperature * sleep_factor) * 1000 * delta_time)
+ mob_occupant.Unconscious((mob_occupant.bodytemperature * unconscious_factor) * 1000 * delta_time)
+ if(beaker)//How much to transfer. As efficiency is increased, less reagent from the beaker is used and more is magically transferred to occupant
+ beaker.reagents.trans_to(occupant, (CRYO_TX_QTY / (efficiency * CRYO_MULTIPLY_FACTOR)) * delta_time, efficiency * CRYO_MULTIPLY_FACTOR, method = VAPOR) // Transfer reagents.
+ use_power(1000 * efficiency)
return 1
@@ -219,9 +235,11 @@
var/datum/gas_mixture/air1 = airs[1]
- if(!nodes[1] || !airs[1] || air1.get_moles(/datum/gas/oxygen) < 5) // Turn off if the machine won't work.
+ if(!nodes[1] || !airs[1] || air1.get_moles(GAS_O2) < 5) // Turn off if the machine won't work due to not having enough moles to operate.
on = FALSE
update_icon()
+ var/msg = "Aborting. Not enough gas present to operate."
+ radio.talk_into(src, msg, radio_channel)
return
if(occupant)
@@ -241,7 +259,9 @@
air1.set_temperature(max(air1.return_temperature() - heat / air_heat_capacity, TCMB))
mob_occupant.adjust_bodytemperature(heat / heat_capacity, TCMB)
- air1.set_moles(/datum/gas/oxygen, max(0,air1.get_moles(/datum/gas/oxygen) - 0.5 / efficiency)) // Magically consume gas? Why not, we run on cryo magic.
+ air1.set_moles(GAS_O2, max(0,air1.get_moles(GAS_O2) - 0.5 / efficiency)) // Magically consume gas? Why not, we run on cryo magic.
+
+ update_parents()
/obj/machinery/atmospherics/components/unary/cryo_cell/power_change()
..()
@@ -340,6 +360,7 @@
if(!ui)
ui = new(user, src, "Cryo")
ui.open()
+ ui.set_autoupdate(TRUE)
/obj/machinery/atmospherics/components/unary/cryo_cell/ui_data()
var/list/data = list()
@@ -420,24 +441,24 @@
. = TRUE
/obj/machinery/atmospherics/components/unary/cryo_cell/CtrlClick(mob/user)
- if(can_interact(user) && !state_open)
+ if(user.can_interact_with(src) && !state_open && occupant != user)
on = !on
update_icon()
return ..()
/obj/machinery/atmospherics/components/unary/cryo_cell/AltClick(mob/user)
- if(can_interact(user))
+ if(user.can_interact_with(src) && occupant != user)
if(state_open)
close_machine()
else
open_machine()
- return ..()
+ return
/obj/machinery/atmospherics/components/unary/cryo_cell/update_remote_sight(mob/living/user)
return // we don't see the pipe network while inside cryo.
/obj/machinery/atmospherics/components/unary/cryo_cell/get_remote_view_fullscreens(mob/user)
- user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1)
+ user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/impaired, 1)
/obj/machinery/atmospherics/components/unary/cryo_cell/can_crawl_through()
return // can't ventcrawl in or out of cryo.
@@ -466,6 +487,8 @@
if(node)
node.atmosinit()
node.addMember(src)
- build_network()
+ SSair.add_to_rebuild_queue(src)
#undef CRYOMOBS
+#undef CRYO_MULTIPLY_FACTOR
+#undef CRYO_TX_QTY
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/heat_exchanger.dm b/code/modules/atmospherics/machinery/components/unary_devices/heat_exchanger.dm
index c0dfc5633e9fd..25b6ad13287e2 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/heat_exchanger.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/heat_exchanger.dm
@@ -10,7 +10,7 @@
layer = LOW_OBJ_LAYER
- var/obj/machinery/atmospherics/components/unary/heat_exchanger/partner = null
+ var/datum/weakref/partner_ref = null
var/update_cycle
pipe_state = "heunary"
@@ -33,20 +33,26 @@
PIPING_LAYER_SHIFT(src, piping_layer)
/obj/machinery/atmospherics/components/unary/heat_exchanger/atmosinit()
+ var/obj/machinery/atmospherics/components/unary/heat_exchanger/partner = partner_ref?.resolve()
if(!partner)
+ partner_ref = null
var/partner_connect = turn(dir,180)
for(var/obj/machinery/atmospherics/components/unary/heat_exchanger/target in get_step(src,partner_connect))
if(target.dir & get_dir(src,target))
- partner = target
- partner.partner = src
+ partner_ref = WEAKREF(target)
+ target.partner_ref = WEAKREF(src)
break
..()
/obj/machinery/atmospherics/components/unary/heat_exchanger/process_atmos()
..()
- if(!partner || SSair.times_fired <= update_cycle)
+ var/obj/machinery/atmospherics/components/unary/heat_exchanger/partner = partner_ref?.resolve()
+ if(!partner)
+ partner_ref = null
+ return
+ if(SSair.times_fired <= update_cycle)
return
update_cycle = SSair.times_fired
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm b/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm
index 8f92bda897bc6..48a963cf292cd 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm
@@ -19,6 +19,7 @@
var/datum/radio_frequency/radio_connection
level = 1
+ interacts_with_air = TRUE
layer = GAS_SCRUBBER_LAYER
pipe_state = "injector"
@@ -30,13 +31,16 @@
if(can_interact(user))
on = !on
update_icon()
+ ui_update()
return ..()
/obj/machinery/atmospherics/components/unary/outlet_injector/AltClick(mob/user)
if(can_interact(user))
volume_rate = MAX_TRANSFER_RATE
+ balloon_alert(user, "Set to [volume_rate] L/s")
update_icon()
- return ..()
+ ui_update()
+ return
/obj/machinery/atmospherics/components/unary/outlet_injector/Destroy()
SSradio.remove_object(src,frequency)
@@ -64,20 +68,17 @@
injecting = 0
- if(!on || !is_operational())
+ if(!on || !is_operational() || !isopenturf(loc))
return
var/datum/gas_mixture/air_contents = airs[1]
- if(air_contents.return_temperature() > 0)
- var/transfer_moles = (air_contents.return_pressure())*volume_rate/(air_contents.return_temperature() * R_IDEAL_GAS_EQUATION)
-
- var/datum/gas_mixture/removed = air_contents.remove(transfer_moles)
+ if(air_contents != null)
+ if(air_contents.return_temperature() > 0)
+ loc.assume_air_ratio(air_contents, volume_rate / air_contents.return_volume())
+ air_update_turf()
- loc.assume_air(removed)
- air_update_turf()
-
- update_parents()
+ update_parents()
/obj/machinery/atmospherics/components/unary/outlet_injector/proc/inject()
@@ -89,9 +90,7 @@
injecting = 1
if(air_contents.return_temperature() > 0)
- var/transfer_moles = (air_contents.return_pressure())*volume_rate/(air_contents.return_temperature() * R_IDEAL_GAS_EQUATION)
- var/datum/gas_mixture/removed = air_contents.remove(transfer_moles)
- loc.assume_air(removed)
+ loc.assume_air_ratio(air_contents, volume_rate / air_contents.return_volume())
update_parents()
flick("inje_inject", src)
@@ -146,6 +145,7 @@
if(!("status" in signal.data)) //do not update_icon
update_icon()
+ ui_update()
@@ -185,8 +185,9 @@
if(.)
volume_rate = clamp(rate, 0, MAX_TRANSFER_RATE)
investigate_log("was set to [volume_rate] L/s by [key_name(usr)]", INVESTIGATE_ATMOS)
- update_icon()
- broadcast_status()
+ if(.)
+ update_icon()
+ broadcast_status()
/obj/machinery/atmospherics/components/unary/outlet_injector/can_unwrench(mob/user)
. = ..()
@@ -218,7 +219,7 @@
/obj/machinery/atmospherics/components/unary/outlet_injector/atmos
frequency = FREQ_ATMOS_STORAGE
on = TRUE
- volume_rate = 200
+ volume_rate = MAX_TRANSFER_RATE
/obj/machinery/atmospherics/components/unary/outlet_injector/atmos/atmos_waste
name = "atmos waste outlet injector"
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/passive_vent.dm b/code/modules/atmospherics/machinery/components/unary_devices/passive_vent.dm
index 572296f03f597..61d21bc3596d6 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/passive_vent.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/passive_vent.dm
@@ -6,6 +6,7 @@
can_unwrench = TRUE
level = 1
+ interacts_with_air = TRUE
layer = GAS_SCRUBBER_LAYER
pipe_state = "pvent"
@@ -19,29 +20,25 @@
/obj/machinery/atmospherics/components/unary/passive_vent/process_atmos()
..()
+ if(isclosedturf(loc))
+ return
- var/datum/gas_mixture/environment = loc.return_air()
- var/environment_pressure = environment.return_pressure()
- var/pressure_delta = abs(environment_pressure - airs[1].return_pressure())
-
- if((environment.return_temperature() || airs[1].return_temperature()) && pressure_delta > 0.5)
- if(environment_pressure < airs[1].return_pressure())
- var/air_temperature = (environment.return_temperature() > 0) ? environment.return_temperature() : airs[1].return_temperature()
- var/transfer_moles = (pressure_delta * environment.return_volume()) / (air_temperature * R_IDEAL_GAS_EQUATION)
- var/datum/gas_mixture/removed = airs[1].remove(transfer_moles)
- loc.assume_air(removed)
- air_update_turf()
- else
- var/air_temperature = (airs[1].return_temperature() > 0) ? airs[1].return_temperature() : environment.return_temperature()
- var/output_volume = airs[1].return_volume()
- var/transfer_moles = (pressure_delta * output_volume) / (air_temperature * R_IDEAL_GAS_EQUATION)
- transfer_moles = min(transfer_moles, environment.total_moles()*airs[1].return_volume()/environment.return_volume())
- var/datum/gas_mixture/removed = loc.remove_air(transfer_moles)
- if(isnull(removed))
- return
- airs[1].merge(removed)
- air_update_turf()
- update_parents()
+ var/active = FALSE
+ var/datum/gas_mixture/external = loc.return_air()
+ var/datum/gas_mixture/internal = airs[1]
+ var/external_pressure = external.return_pressure()
+ var/internal_pressure = internal.return_pressure()
+ var/pressure_delta = abs(external_pressure - internal_pressure)
+
+ if(pressure_delta > 0.5)
+ equalize_all_gases_in_list(list(internal,external))
+ active = TRUE
+
+ active = internal.temperature_share(external, OPEN_HEAT_TRANSFER_COEFFICIENT) || active
+
+ if(active)
+ air_update_turf()
+ update_parents()
/obj/machinery/atmospherics/components/unary/passive_vent/can_crawl_through()
return TRUE
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/tank.dm b/code/modules/atmospherics/machinery/components/unary_devices/tank.dm
index 512d693555614..3ca9fd77e69b4 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/tank.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/tank.dm
@@ -21,7 +21,7 @@
air_contents.set_temperature(T20C)
if(gas_type)
air_contents.set_moles(gas_type, AIR_CONTENTS)
- name = "[name] ([GLOB.meta_gas_info[gas_type][META_GAS_NAME]])"
+ name = "[name] ([GLOB.gas_data.names[gas_type]])"
setPipingLayer(piping_layer)
@@ -32,20 +32,20 @@
/obj/machinery/atmospherics/components/unary/tank/air/New()
..()
var/datum/gas_mixture/air_contents = airs[1]
- air_contents.set_moles(/datum/gas/oxygen, AIR_CONTENTS * 0.2)
- air_contents.set_moles(/datum/gas/nitrogen, AIR_CONTENTS * 0.8)
+ air_contents.set_moles(GAS_O2, AIR_CONTENTS * 0.2)
+ air_contents.set_moles(GAS_N2, AIR_CONTENTS * 0.8)
/obj/machinery/atmospherics/components/unary/tank/carbon_dioxide
- gas_type = /datum/gas/carbon_dioxide
+ gas_type = GAS_CO2
/obj/machinery/atmospherics/components/unary/tank/toxins
icon_state = "orange"
- gas_type = /datum/gas/plasma
+ gas_type = GAS_PLASMA
/obj/machinery/atmospherics/components/unary/tank/oxygen
icon_state = "blue"
- gas_type = /datum/gas/oxygen
+ gas_type = GAS_O2
/obj/machinery/atmospherics/components/unary/tank/nitrogen
icon_state = "red"
- gas_type = /datum/gas/nitrogen
+ gas_type = GAS_N2
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm b/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm
index 68ac81f9936ea..74acbef1cef7c 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm
@@ -2,12 +2,12 @@
icon = 'icons/obj/atmospherics/components/thermomachine.dmi'
icon_state = "freezer"
- name = "thermomachine"
+ name = "Thermomachine"
desc = "Heats or cools gas in connected pipes."
density = TRUE
max_integrity = 300
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 30)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 30, "stamina" = 0)
layer = OBJ_LAYER
circuit = /obj/item/circuitboard/machine/thermomachine
@@ -19,27 +19,58 @@
var/icon_state_on = "freezer_1"
var/icon_state_open = "freezer-o"
- var/min_temperature = 0
- var/max_temperature = 0
+ var/min_temperature = T20C //actual temperature will be defined by RefreshParts() and by the cooling var
+ var/max_temperature = T20C //actual temperature will be defined by RefreshParts() and by the cooling var
var/target_temperature = T20C
var/heat_capacity = 0
var/interactive = TRUE // So mapmakers can disable interaction.
+ var/cooling = TRUE
+ var/base_heating = 140
+ var/base_cooling = 170
/obj/machinery/atmospherics/components/unary/thermomachine/Initialize()
. = ..()
initialize_directions = dir
+ RefreshParts()
+ update_icon()
+
+/obj/machinery/atmospherics/components/unary/thermomachine/proc/swap_function()
+ cooling = !cooling
+ if(cooling)
+ icon_state_off = "freezer"
+ icon_state_on = "freezer_1"
+ icon_state_open = "freezer-o"
+ else
+ icon_state_off = "heater"
+ icon_state_on = "heater_1"
+ icon_state_open = "heater-o"
+ target_temperature = T20C
+ RefreshParts()
+ update_icon()
/obj/machinery/atmospherics/components/unary/thermomachine/on_construction()
var/obj/item/circuitboard/machine/thermomachine/board = circuit
if(board)
piping_layer = board.pipe_layer
- ..(dir, piping_layer)
+ return ..(dir, piping_layer)
/obj/machinery/atmospherics/components/unary/thermomachine/RefreshParts()
- var/B
- for(var/obj/item/stock_parts/matter_bin/M in component_parts)
- B += M.rating
- heat_capacity = 5000 * ((B - 1) ** 2)
+ var/calculated_bin_rating
+ for(var/obj/item/stock_parts/matter_bin/bin in component_parts)
+ calculated_bin_rating += bin.rating
+ heat_capacity = 5000 * ((calculated_bin_rating - 1) ** 2)
+ min_temperature = T20C
+ max_temperature = T20C
+ if(cooling)
+ var/calculated_laser_rating
+ for(var/obj/item/stock_parts/micro_laser/laser in component_parts)
+ calculated_laser_rating += laser.rating
+ min_temperature = max(T0C - (base_cooling + calculated_laser_rating * 15), TCMB) //73.15K with T1 stock parts
+ else
+ var/calculated_laser_rating
+ for(var/obj/item/stock_parts/micro_laser/laser in component_parts)
+ calculated_laser_rating += laser.rating
+ max_temperature = T20C + (base_heating * calculated_laser_rating) //573.15K with T1 stock parts
/obj/machinery/atmospherics/components/unary/thermomachine/update_icon()
cut_overlays()
@@ -65,9 +96,20 @@
. += "The status display reads: Efficiency [(heat_capacity/5000)*100]%."
. += "Temperature range [min_temperature]K - [max_temperature]K ([(T0C-min_temperature)*-1]C - [(T0C-max_temperature)*-1]C)."
+/obj/machinery/atmospherics/components/unary/thermomachine/AltClick(mob/living/user)
+ if(!can_interact(user))
+ return
+ if(cooling)
+ target_temperature = min_temperature
+ investigate_log("was set to [target_temperature] K by [key_name(user)]", INVESTIGATE_ATMOS)
+ else
+ target_temperature = max_temperature
+ investigate_log("was set to [target_temperature] K by [key_name(user)]", INVESTIGATE_ATMOS)
+ balloon_alert(user, "Set to [target_temperature] K")
+
/obj/machinery/atmospherics/components/unary/thermomachine/process_atmos()
..()
- if(!on || !nodes[1])
+ if(!is_operational() || !on || !nodes[1]) //if it has no power or its switched off, dont process atmos
return
var/datum/gas_mixture/air_contents = airs[1]
@@ -109,14 +151,16 @@
if(node)
node.disconnect(src)
nodes[1] = null
- nullifyPipenet(parents[1])
+ //Sometimes this gets called more than once per atmos tick; i.e. before the incoming build_network call by SSAIR_REBUILD_PIPENETS, so we check this here.
+ if(parents[1])
+ nullifyPipenet(parents[1])
atmosinit()
node = nodes[1]
if(node)
node.atmosinit()
node.addMember(src)
- build_network()
+ SSair.add_to_rebuild_queue(src)
return TRUE
/obj/machinery/atmospherics/components/unary/thermomachine/ui_status(mob/user)
@@ -133,10 +177,12 @@
if(!ui)
ui = new(user, src, "ThermoMachine")
ui.open()
+ ui.set_autoupdate(TRUE) // Air temperature and pressure
/obj/machinery/atmospherics/components/unary/thermomachine/ui_data(mob/user)
var/list/data = list()
data["on"] = on
+ data["cooling"] = cooling
data["min"] = min_temperature
data["max"] = max_temperature
@@ -159,6 +205,10 @@
use_power = on ? ACTIVE_POWER_USE : IDLE_POWER_USE
investigate_log("was turned [on ? "on" : "off"] by [key_name(usr)]", INVESTIGATE_ATMOS)
. = TRUE
+ if("cooling")
+ swap_function()
+ investigate_log("was changed to [cooling ? "cooling" : "heating"] by [key_name(usr)]", INVESTIGATE_ATMOS)
+ . = TRUE
if("target")
var/target = params["target"]
var/adjust = text2num(params["adjust"])
@@ -175,8 +225,8 @@
if(.)
target_temperature = clamp(target, min_temperature, max_temperature)
investigate_log("was set to [target_temperature] K by [key_name(usr)]", INVESTIGATE_ATMOS)
-
- update_icon()
+ if(.)
+ update_icon()
/obj/machinery/atmospherics/components/unary/thermomachine/CtrlClick(mob/living/user)
if(!can_interact(user))
@@ -185,14 +235,11 @@
update_icon()
/obj/machinery/atmospherics/components/unary/thermomachine/freezer
- name = "freezer"
icon_state = "freezer"
icon_state_off = "freezer"
icon_state_on = "freezer_1"
icon_state_open = "freezer-o"
- max_temperature = T20C
- min_temperature = 170 //actual minimum temperature is defined by RefreshParts()
- circuit = /obj/item/circuitboard/machine/thermomachine/freezer
+ cooling = TRUE
/obj/machinery/atmospherics/components/unary/thermomachine/freezer/on
on = TRUE
@@ -210,40 +257,13 @@
. = ..()
target_temperature = T0C-20 //Cold enough to prevent Miasma
-/obj/machinery/atmospherics/components/unary/thermomachine/freezer/RefreshParts()
- ..()
- var/L
- for(var/obj/item/stock_parts/micro_laser/M in component_parts)
- L += M.rating
- min_temperature = max(T0C - (initial(min_temperature) + L * 15), TCMB) //73.15K with T1 stock parts
-
-/obj/machinery/atmospherics/components/unary/thermomachine/freezer/AltClick(mob/living/user)
- if(!can_interact(user))
- return
- target_temperature = min_temperature
-
/obj/machinery/atmospherics/components/unary/thermomachine/heater
- name = "heater"
icon_state = "heater"
icon_state_off = "heater"
icon_state_on = "heater_1"
icon_state_open = "heater-o"
- max_temperature = 140 //actual maximum temperature is defined by RefreshParts()
- min_temperature = T20C
- circuit = /obj/item/circuitboard/machine/thermomachine/heater
+ cooling = FALSE
/obj/machinery/atmospherics/components/unary/thermomachine/heater/on
on = TRUE
icon_state = "heater_1"
-
-/obj/machinery/atmospherics/components/unary/thermomachine/heater/RefreshParts()
- ..()
- var/L
- for(var/obj/item/stock_parts/micro_laser/M in component_parts)
- L += M.rating
- max_temperature = T20C + (initial(max_temperature) * L) //573.15K with T1 stock parts
-
-/obj/machinery/atmospherics/components/unary/thermomachine/heater/AltClick(mob/living/user)
- if(!can_interact(user))
- return
- target_temperature = max_temperature
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm
index ba9764d63cebd..05a5047f81403 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm
@@ -17,6 +17,8 @@
level = 1
layer = GAS_SCRUBBER_LAYER
+ interacts_with_air = TRUE
+
var/id_tag = null
var/pump_direction = RELEASING
@@ -90,7 +92,7 @@
/obj/machinery/atmospherics/components/unary/vent_pump/process_atmos()
..()
- if(!is_operational())
+ if(!is_operational() || !isopenturf(loc))
return
if(!nodes[1])
on = FALSE
@@ -99,6 +101,10 @@
var/datum/gas_mixture/air_contents = airs[1]
var/datum/gas_mixture/environment = loc.return_air()
+
+ if(environment == null)
+ return
+
var/environment_pressure = environment.return_pressure()
if(pump_direction & RELEASING) // internal -> external
@@ -113,9 +119,7 @@
if(air_contents.return_temperature() > 0)
var/transfer_moles = pressure_delta*environment.return_volume()/(air_contents.return_temperature() * R_IDEAL_GAS_EQUATION)
- var/datum/gas_mixture/removed = air_contents.remove(transfer_moles)
-
- loc.assume_air(removed)
+ loc.assume_air_moles(air_contents, transfer_moles)
air_update_turf()
else // external -> internal
@@ -128,11 +132,7 @@
moles_delta = min(moles_delta, (internal_pressure_bound - air_contents.return_pressure()) * our_multiplier)
if(moles_delta > 0)
- var/datum/gas_mixture/removed = loc.remove_air(moles_delta)
- if (isnull(removed)) // in space
- return
-
- air_contents.merge(removed)
+ loc.transfer_air(air_contents, moles_delta)
air_update_turf()
update_parents()
@@ -288,7 +288,7 @@
/obj/machinery/atmospherics/components/unary/vent_pump/attack_alien(mob/user)
if(!welded || !(do_after(user, 20, target = src)))
return
- user.visible_message("[user] furiously claws at [src]!", "You manage to clear away the stuff blocking the vent", "You hear loud scraping noises.")
+ user.visible_message("[user] furiously claws at [src]!", "You manage to clear away the stuff blocking the vent.", "You hear loud scraping noises.")
welded = FALSE
update_icon()
pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir)
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm
index 323e1597e01df..bb71cbd73b59f 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm
@@ -14,10 +14,12 @@
level = 1
layer = GAS_SCRUBBER_LAYER
+ interacts_with_air = TRUE
+
var/id_tag = null
var/scrubbing = SCRUBBING //0 = siphoning, 1 = scrubbing
- var/filter_types = list(/datum/gas/carbon_dioxide, /datum/gas/bz)
+ var/filter_types = list(GAS_CO2, GAS_BZ)
var/volume_rate = 200
var/widenet = 0 //is this scrubber acting on the 3x3 area around it.
var/list/turf/adjacent_turfs = list()
@@ -34,11 +36,6 @@
if(!id_tag)
id_tag = assign_uid_vents()
- for(var/f in filter_types)
- if(istext(f))
- filter_types -= f
- filter_types += gas_id2path(f)
-
/obj/machinery/atmospherics/components/unary/vent_scrubber/Destroy()
var/area/A = get_area(src)
if (A)
@@ -98,9 +95,8 @@
return FALSE
var/list/f_types = list()
- for(var/path in GLOB.meta_gas_info)
- var/list/gas = GLOB.meta_gas_info[path]
- f_types += list(list("gas_id" = gas[META_GAS_ID], "gas_name" = gas[META_GAS_NAME], "enabled" = (path in filter_types)))
+ for(var/id in GLOB.gas_data.ids)
+ f_types += list(list("gas_id" = id, "gas_name" = GLOB.gas_data.names[id], "enabled" = (id in filter_types)))
var/datum/signal/signal = new(list(
"tag" = id_tag,
@@ -146,38 +142,21 @@
scrub(tile)
return TRUE
-/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/scrub(var/turf/tile)
+/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/scrub(var/turf/open/tile)
if(!istype(tile))
return FALSE
var/datum/gas_mixture/environment = tile.return_air()
var/datum/gas_mixture/air_contents = airs[1]
- if(air_contents.return_pressure() >= 50*ONE_ATMOSPHERE)
+ if(air_contents.return_pressure() >= 50 * ONE_ATMOSPHERE || !islist(filter_types))
return FALSE
if(scrubbing & SCRUBBING)
- var/transfer_moles = min(1, volume_rate/environment.return_volume())*environment.total_moles()
-
- //Take a gas sample
- var/datum/gas_mixture/removed = tile.remove_air(transfer_moles)
-
- //Nothing left to remove from the tile
- if(isnull(removed))
- return FALSE
-
- removed.scrub_into(air_contents, filter_types)
-
- //Remix the resulting gases
- tile.assume_air(removed)
+ environment.scrub_into(air_contents, volume_rate/environment.return_volume(), filter_types)
tile.air_update_turf()
else //Just siphoning all air
-
- var/transfer_moles = environment.total_moles()*(volume_rate/environment.return_volume())
-
- var/datum/gas_mixture/removed = tile.remove_air(transfer_moles)
-
- air_contents.merge(removed)
+ environment.transfer_ratio_to(air_contents, volume_rate/environment.return_volume())
tile.air_update_turf()
update_parents()
@@ -224,12 +203,12 @@
investigate_log(" was toggled to [scrubbing ? "scrubbing" : "siphon"] mode by [key_name(signal_sender)]",INVESTIGATE_ATMOS)
if("toggle_filter" in signal.data)
- filter_types ^= gas_id2path(signal.data["toggle_filter"])
+ filter_types ^= signal.data["toggle_filter"]
if("set_filters" in signal.data)
filter_types = list()
for(var/gas in signal.data["set_filters"])
- filter_types += gas_id2path(gas)
+ filter_types += gas
if("init" in signal.data)
name = signal.data["init"]
@@ -280,7 +259,7 @@
/obj/machinery/atmospherics/components/unary/vent_scrubber/attack_alien(mob/user)
if(!welded || !(do_after(user, 20, target = src)))
return
- user.visible_message("[user] furiously claws at [src]!", "You manage to clear away the stuff blocking the scrubber.", "You hear loud scraping noises.")
+ user.visible_message("[user] furiously claws at [src]!", "You manage to clear away the stuff blocking the scrubber.", "You hear loud scraping noises.")
welded = FALSE
update_icon()
pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir)
@@ -309,10 +288,10 @@
icon_state = "scrub_map_on-3"
/obj/machinery/atmospherics/components/unary/vent_scrubber/on/lavaland
- filter_types = list(/datum/gas/carbon_dioxide, /datum/gas/plasma, /datum/gas/water_vapor, /datum/gas/bz)
+ filter_types = list(GAS_CO2, GAS_PLASMA, GAS_H2O, GAS_BZ)
/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer3/lavaland
- filter_types = list(/datum/gas/carbon_dioxide, /datum/gas/plasma, /datum/gas/water_vapor, /datum/gas/bz)
+ filter_types = list(GAS_CO2, GAS_PLASMA, GAS_H2O, GAS_BZ)
#undef SIPHONING
#undef SCRUBBING
diff --git a/code/modules/atmospherics/machinery/datum_pipeline.dm b/code/modules/atmospherics/machinery/datum_pipeline.dm
index ca1024b799067..2bae4e25e771f 100644
--- a/code/modules/atmospherics/machinery/datum_pipeline.dm
+++ b/code/modules/atmospherics/machinery/datum_pipeline.dm
@@ -15,7 +15,7 @@
/datum/pipeline/Destroy()
SSair.networks -= src
- if(air?.return_volume())
+ if(air && air.return_volume())
temporarily_store_air()
for(var/obj/machinery/atmospherics/pipe/P in members)
P.parent = null
@@ -43,10 +43,12 @@
if(!air)
air = new
var/list/possible_expansions = list(base)
- while(possible_expansions.len)
+ while(possible_expansions.len>0)
for(var/obj/machinery/atmospherics/borderline in possible_expansions)
+
var/list/result = borderline.pipeline_expansion(src)
- if(result && result.len)
+
+ if(result.len>0)
for(var/obj/machinery/atmospherics/P in result)
if(istype(P, /obj/machinery/atmospherics/pipe))
var/obj/machinery/atmospherics/pipe/item = P
@@ -55,8 +57,8 @@
if(item.parent)
var/static/pipenetwarnings = 10
if(pipenetwarnings > 0)
- log_mapping("build_pipeline(): [item.type] added to a pipenet while still having one. (pipes leading to the same spot stacking in one turf) around [AREACOORD(item)].")
- pipenetwarnings--
+ log_mapping("build_pipeline(): [item.type] added to a pipenet while still having one. (pipes leading to the same spot stacking in one turf) Nearby: ([item.x], [item.y], [item.z]).")
+ pipenetwarnings -= 1
if(pipenetwarnings == 0)
log_mapping("build_pipeline(): further messages about pipenets will be suppressed")
members += item
@@ -192,7 +194,7 @@
else
if((target.heat_capacity>0) && (partial_heat_capacity>0))
- var/delta_temperature = air.return_temperature() - target.temperature
+ var/delta_temperature = air.return_temperature() - target.return_temperature()
var/heat = thermal_conductivity*delta_temperature* \
(partial_heat_capacity*target.heat_capacity/(partial_heat_capacity+target.heat_capacity))
@@ -203,10 +205,14 @@
/datum/pipeline/proc/return_air()
. = other_airs + air
if(null in .)
- stack_trace("[src] has one or more null gas mixtures, which may cause bugs. Null mixtures will not be considered in reconcile_air().")
- return removeNullsFromList(.)
+ stack_trace("[src]([REF(src)]) has one or more null gas mixtures, which may cause bugs. Null mixtures will not be considered in reconcile_air().")
+ listclearnulls(.)
-/datum/pipeline/proc/reconcile_air()
+/datum/pipeline/proc/empty()
+ for(var/datum/gas_mixture/GM in get_all_connected_airs())
+ GM.clear()
+
+/datum/pipeline/proc/get_all_connected_airs()
var/list/datum/gas_mixture/GL = list()
var/list/datum/pipeline/PL = list()
PL += src
@@ -226,18 +232,8 @@
var/obj/machinery/atmospherics/components/unary/portables_connector/C = atmosmch
if(C.connected_device)
GL += C.portableConnectorReturnAir()
+ return GL
- var/datum/gas_mixture/total_gas_mixture = new(0)
- var/total_volume = 0
-
- for(var/i in GL)
- var/datum/gas_mixture/G = i
- total_gas_mixture.merge(G)
- total_volume += G.return_volume()
-
- if(total_volume > 0)
- //Update individual gas_mixtures by volume ratio
- for(var/i in GL)
- var/datum/gas_mixture/G = i
- G.copy_from(total_gas_mixture)
- G.multiply(G.return_volume()/total_volume)
+/datum/pipeline/proc/reconcile_air()
+ var/list/datum/gas_mixture/GL = get_all_connected_airs()
+ equalize_all_gases_in_list(GL)
diff --git a/code/modules/atmospherics/machinery/other/meter.dm b/code/modules/atmospherics/machinery/other/meter.dm
index 4fcef087b72c0..bf20a874a5c9f 100644
--- a/code/modules/atmospherics/machinery/other/meter.dm
+++ b/code/modules/atmospherics/machinery/other/meter.dm
@@ -9,7 +9,7 @@
idle_power_usage = 2
active_power_usage = 4
max_integrity = 150
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 40, "acid" = 0)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 40, "acid" = 0, "stamina" = 0)
var/frequency = 0
var/atom/target
var/id_tag
diff --git a/code/modules/atmospherics/machinery/other/miner.dm b/code/modules/atmospherics/machinery/other/miner.dm
index ae126b3e91875..90b283fb66835 100644
--- a/code/modules/atmospherics/machinery/other/miner.dm
+++ b/code/modules/atmospherics/machinery/other/miner.dm
@@ -5,6 +5,7 @@
#define GASMINER_POWER_KPA 3
#define GASMINER_POWER_FULLSCALE 4
+
/obj/machinery/atmospherics/miner
name = "gas miner"
desc = "Gasses mined from the gas giant below (above?) flow out through this massive vent."
@@ -12,9 +13,11 @@
icon_state = "miner"
density = FALSE
resistance_flags = INDESTRUCTIBLE|ACID_PROOF|FIRE_PROOF
+ interacts_with_air = TRUE
var/spawn_id = null
var/spawn_temp = T20C
- var/spawn_mol = MOLES_CELLSTANDARD * 10
+ /// Moles of gas to spawn per second
+ var/spawn_mol = MOLES_CELLSTANDARD * 5
var/max_ext_mol = INFINITY
var/max_ext_kpa = 6500
var/overlay_color = "#FFFFFF"
@@ -26,7 +29,7 @@
var/broken = FALSE
var/broken_message = "ERROR"
idle_power_usage = 150
- active_power_usage = 2000
+ active_power_usage = 3000
/obj/machinery/atmospherics/miner/Initialize()
. = ..()
@@ -94,7 +97,7 @@
if(GASMINER_POWER_KPA)
active_power_usage = P * power_draw_dynamic_kpa_coeff
if(GASMINER_POWER_FULLSCALE)
- active_power_usage = (spawn_mol * power_draw_dynamic_mol_coeff) + (P * power_draw_dynamic_kpa_coeff)
+ active_power_usage = (spawn_mol * power_draw_dynamic_mol_coeff) + (P * power_draw_dynamic_kpa_coeff) + power_draw_static
/obj/machinery/atmospherics/miner/proc/do_use_power(amount)
var/turf/T = get_turf(src)
@@ -117,7 +120,7 @@
on_overlay.color = overlay_color
add_overlay(on_overlay)
-/obj/machinery/atmospherics/miner/process()
+/obj/machinery/atmospherics/miner/process_atmos() //TODO figure out delta_time for this
update_power()
check_operation()
if(active && !broken)
@@ -126,12 +129,12 @@
if(do_use_power(active_power_usage))
mine_gas()
-/obj/machinery/atmospherics/miner/proc/mine_gas()
+/obj/machinery/atmospherics/miner/proc/mine_gas(delta_time = 2)
var/turf/open/O = get_turf(src)
if(!isopenturf(O))
return FALSE
var/datum/gas_mixture/merger = new
- merger.set_moles(spawn_id, spawn_mol)
+ merger.set_moles(spawn_id, spawn_mol * delta_time)
merger.set_temperature(spawn_temp)
O.assume_air(merger)
O.air_update_turf(TRUE)
@@ -144,34 +147,81 @@
/obj/machinery/atmospherics/miner/n2o
name = "\improper N2O Gas Miner"
overlay_color = "#FFCCCC"
- spawn_id = /datum/gas/nitrous_oxide
+ spawn_id = GAS_NITROUS
/obj/machinery/atmospherics/miner/nitrogen
name = "\improper N2 Gas Miner"
overlay_color = "#CCFFCC"
- spawn_id = /datum/gas/nitrogen
+ spawn_id = GAS_N2
/obj/machinery/atmospherics/miner/oxygen
name = "\improper O2 Gas Miner"
overlay_color = "#007FFF"
- spawn_id = /datum/gas/oxygen
+ spawn_id = GAS_O2
/obj/machinery/atmospherics/miner/toxins
name = "\improper Plasma Gas Miner"
overlay_color = "#FF0000"
- spawn_id = /datum/gas/plasma
+ spawn_id = GAS_PLASMA
/obj/machinery/atmospherics/miner/carbon_dioxide
name = "\improper CO2 Gas Miner"
overlay_color = "#CDCDCD"
- spawn_id = /datum/gas/carbon_dioxide
+ spawn_id = GAS_CO2
/obj/machinery/atmospherics/miner/bz
name = "\improper BZ Gas Miner"
overlay_color = "#FAFF00"
- spawn_id = /datum/gas/bz
+ spawn_id = GAS_BZ
/obj/machinery/atmospherics/miner/water_vapor
name = "\improper Water Vapor Gas Miner"
overlay_color = "#99928E"
- spawn_id = /datum/gas/water_vapor
+ spawn_id = GAS_H2O
+
+/obj/machinery/atmospherics/miner/station
+ power_draw = GASMINER_POWER_FULLSCALE
+ spawn_mol = MOLES_CELLSTANDARD / 10
+ max_ext_kpa = 2500
+
+/obj/machinery/atmospherics/miner/station/n2o
+ name = "\improper N2O Gas Miner"
+ overlay_color = "#FFCCCC"
+ spawn_id = GAS_NITROUS
+
+/obj/machinery/atmospherics/miner/station/nitrogen
+ name = "\improper N2 Gas Miner"
+ overlay_color = "#CCFFCC"
+ spawn_id = GAS_N2
+
+/obj/machinery/atmospherics/miner/station/oxygen
+ name = "\improper O2 Gas Miner"
+ overlay_color = "#007FFF"
+ spawn_id = GAS_O2
+
+/obj/machinery/atmospherics/miner/station/toxins
+ name = "\improper Plasma Gas Miner"
+ overlay_color = "#FF0000"
+ spawn_id = GAS_PLASMA
+
+/obj/machinery/atmospherics/miner/station/carbon_dioxide
+ name = "\improper CO2 Gas Miner"
+ overlay_color = "#CDCDCD"
+ spawn_id = GAS_CO2
+
+/obj/machinery/atmospherics/miner/station/bz
+ name = "\improper BZ Gas Miner"
+ overlay_color = "#FAFF00"
+ spawn_id = GAS_BZ
+
+/obj/machinery/atmospherics/miner/station/water_vapor
+ name = "\improper Water Vapor Gas Miner"
+ overlay_color = "#99928E"
+ spawn_id = GAS_H2O
+
+
+#undef GASMINER_POWER_NONE
+#undef GASMINER_POWER_STATIC
+#undef GASMINER_POWER_MOLES
+#undef GASMINER_POWER_KPA
+#undef GASMINER_POWER_FULLSCALE
diff --git a/code/modules/atmospherics/machinery/pipes/heat_exchange/he_pipes.dm b/code/modules/atmospherics/machinery/pipes/heat_exchange/he_pipes.dm
index 66475be45a212..23169af7e56a1 100644
--- a/code/modules/atmospherics/machinery/pipes/heat_exchange/he_pipes.dm
+++ b/code/modules/atmospherics/machinery/pipes/heat_exchange/he_pipes.dm
@@ -6,6 +6,7 @@
buckle_lying = -1
var/icon_temperature = T20C //stop small changes in temperature causing icon refresh
resistance_flags = LAVA_PROOF | FIRE_PROOF
+ interacts_with_air = TRUE
/obj/machinery/atmospherics/pipe/heat_exchanging/Initialize()
. = ..()
@@ -25,9 +26,7 @@
var/turf/T = loc
if(istype(T))
- if(islava(T))
- environment_temperature = 5000
- else if(T.blocks_air)
+ if(T.blocks_air)
environment_temperature = T.return_temperature()
else
var/turf/open/OT = T
@@ -35,8 +34,9 @@
else
environment_temperature = T.return_temperature()
- if(abs(environment_temperature-pipe_air.return_temperature()) > minimum_temperature_difference)
- parent.temperature_interact(T, volume, thermal_conductivity)
+ if(pipe_air != null)
+ if(abs(environment_temperature-pipe_air.return_temperature()) > minimum_temperature_difference)
+ parent.temperature_interact(T, volume, thermal_conductivity)
//heatup/cooldown any mobs buckled to ourselves based on our temperature
@@ -50,7 +50,7 @@
L.bodytemperature = avg_temp
pipe_air.set_temperature(avg_temp)
-/obj/machinery/atmospherics/pipe/heat_exchanging/process()
+/obj/machinery/atmospherics/pipe/heat_exchanging/process(delta_time)
if(!parent)
return //machines subsystem fires before atmos is initialized so this prevents race condition runtimes
@@ -79,4 +79,4 @@
if(pipe_air.return_temperature() > heat_limit + 1)
for(var/m in buckled_mobs)
var/mob/living/buckled_mob = m
- buckled_mob.apply_damage(4 * log(pipe_air.return_temperature() - heat_limit), BURN, BODY_ZONE_CHEST)
+ buckled_mob.apply_damage(delta_time * 2 * log(pipe_air.return_temperature() - heat_limit), BURN, BODY_ZONE_CHEST)
diff --git a/code/modules/atmospherics/machinery/pipes/layermanifold.dm b/code/modules/atmospherics/machinery/pipes/layermanifold.dm
index db7656c2b3274..a5888ceeb0a98 100644
--- a/code/modules/atmospherics/machinery/pipes/layermanifold.dm
+++ b/code/modules/atmospherics/machinery/pipes/layermanifold.dm
@@ -8,7 +8,6 @@
pipe_flags = PIPING_ALL_LAYER | PIPING_DEFAULT_LAYER_ONLY | PIPING_CARDINAL_AUTONORMALIZE
piping_layer = PIPING_LAYER_DEFAULT
device_type = 0
- volume = 260
construction_type = /obj/item/pipe/binary
pipe_state = "manifoldlayer"
FASTDMM_PROP(\
@@ -20,6 +19,7 @@
var/list/back_nodes
/obj/machinery/atmospherics/pipe/layer_manifold/Initialize()
+ volume = 280 //260 isn't divisible by 35 bull this is 280L
front_nodes = list()
back_nodes = list()
icon_state = "manifoldlayer_center"
@@ -35,8 +35,9 @@
back_nodes = null
nodes = list()
for(var/obj/machinery/atmospherics/A in needs_nullifying)
- A.disconnect(src)
- A.build_network()
+ if(A != null && src != null) //if it's already null why are we doing this? The answer is byond... it'll always find a way
+ A.disconnect(src)
+ SSair.add_to_rebuild_queue(A)
/obj/machinery/atmospherics/pipe/layer_manifold/proc/get_all_connected_nodes()
return front_nodes + back_nodes + nodes
@@ -74,9 +75,9 @@
/obj/machinery/atmospherics/pipe/layer_manifold/SetInitDirections()
switch(dir)
- if(NORTH || SOUTH)
+ if(NORTH, SOUTH)
initialize_directions = NORTH|SOUTH
- if(EAST || WEST)
+ if(EAST, WEST)
initialize_directions = EAST|WEST
/obj/machinery/atmospherics/pipe/layer_manifold/isConnectable(obj/machinery/atmospherics/target, given_layer)
diff --git a/code/modules/atmospherics/machinery/pipes/pipes.dm b/code/modules/atmospherics/machinery/pipes/pipes.dm
index 60668f77eab67..41831924cd6c0 100644
--- a/code/modules/atmospherics/machinery/pipes/pipes.dm
+++ b/code/modules/atmospherics/machinery/pipes/pipes.dm
@@ -28,7 +28,7 @@
var/obj/machinery/atmospherics/oldN = nodes[i]
..()
if(oldN)
- oldN.build_network()
+ SSair.add_to_rebuild_queue(oldN)
/obj/machinery/atmospherics/pipe/destroy_network()
QDEL_NULL(parent)
@@ -55,14 +55,19 @@
air_update_turf()
/obj/machinery/atmospherics/pipe/return_air()
- return parent.air
+ if(parent)
+ return parent.air
/obj/machinery/atmospherics/pipe/return_analyzable_air()
- return parent.air
+ if(parent)
+ return parent.air
/obj/machinery/atmospherics/pipe/remove_air(amount)
return parent.air.remove(amount)
+/obj/machinery/atmospherics/pipe/remove_air_ratio(ratio)
+ return parent.air.remove_ratio(ratio)
+
/obj/machinery/atmospherics/pipe/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/pipe_meter))
var/obj/item/pipe_meter/meter = W
@@ -72,7 +77,8 @@
return ..()
/obj/machinery/atmospherics/pipe/returnPipenet()
- return parent
+ if(parent)
+ return parent.air
/obj/machinery/atmospherics/pipe/setPipenet(datum/pipeline/P)
parent = P
diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm
index d088464387389..1291163a9b701 100644
--- a/code/modules/atmospherics/machinery/portable/canister.dm
+++ b/code/modules/atmospherics/machinery/portable/canister.dm
@@ -9,7 +9,6 @@
var/valve_open = FALSE
- var/obj/machinery/atmospherics/components/binary/passive_gate/pump
var/release_log = ""
volume = 1000
@@ -19,12 +18,12 @@
var/can_max_release_pressure = (ONE_ATMOSPHERE * 10)
var/can_min_release_pressure = (ONE_ATMOSPHERE / 10)
- armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 50)
+ armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 50, "stamina" = 0)
max_integrity = 250
integrity_failure = 100
pressure_resistance = 7 * ONE_ATMOSPHERE
var/temperature_resistance = 1000 + T0C
- var/starter_temp
+ var/starter_temp = T20C
// Prototype vars
var/prototype = FALSE
var/valve_timer = null
@@ -66,37 +65,37 @@
name = "n2 canister"
desc = "Nitrogen gas. Reportedly useful for something."
icon_state = "red"
- gas_type = /datum/gas/nitrogen
+ gas_type = GAS_N2
/obj/machinery/portable_atmospherics/canister/oxygen
name = "o2 canister"
desc = "Oxygen. Necessary for human life."
icon_state = "blue"
- gas_type = /datum/gas/oxygen
+ gas_type = GAS_O2
/obj/machinery/portable_atmospherics/canister/carbon_dioxide
name = "co2 canister"
desc = "Carbon dioxide. What the fuck is carbon dioxide?"
icon_state = "black"
- gas_type = /datum/gas/carbon_dioxide
+ gas_type = GAS_CO2
/obj/machinery/portable_atmospherics/canister/toxins
name = "plasma canister"
desc = "Plasma gas. The reason YOU are here. Highly toxic."
icon_state = "orange"
- gas_type = /datum/gas/plasma
+ gas_type = GAS_PLASMA
/obj/machinery/portable_atmospherics/canister/bz
name = "\improper BZ canister"
desc = "BZ, a powerful hallucinogenic nerve agent."
icon_state = "purple"
- gas_type = /datum/gas/bz
+ gas_type = GAS_BZ
/obj/machinery/portable_atmospherics/canister/nitrous_oxide
name = "n2o canister"
desc = "Nitrous oxide gas. Known to cause drowsiness."
icon_state = "redws"
- gas_type = /datum/gas/nitrous_oxide
+ gas_type = GAS_NITROUS
/obj/machinery/portable_atmospherics/canister/air
name = "air canister"
@@ -107,44 +106,44 @@
name = "tritium canister"
desc = "Tritium. Inhalation might cause irradiation."
icon_state = "green"
- gas_type = /datum/gas/tritium
+ gas_type = GAS_TRITIUM
/obj/machinery/portable_atmospherics/canister/nob
name = "hyper-noblium canister"
desc = "Hyper-Noblium. More noble than all other gases."
icon_state = "freon"
- gas_type = /datum/gas/hypernoblium
+ gas_type = GAS_HYPERNOB
/obj/machinery/portable_atmospherics/canister/nitryl
name = "nitryl canister"
desc = "Nitryl gas. Feels great 'til the acid eats your lungs."
icon_state = "brown"
- gas_type = /datum/gas/nitryl
+ gas_type = GAS_NITRYL
/obj/machinery/portable_atmospherics/canister/stimulum
name = "stimulum canister"
desc = "Stimulum. High energy gas, high energy people."
icon_state = "darkpurple"
- gas_type = /datum/gas/stimulum
+ gas_type = GAS_STIMULUM
/obj/machinery/portable_atmospherics/canister/pluoxium
name = "pluoxium canister"
desc = "Pluoxium. Like oxygen, but more bang for your buck."
icon_state = "darkblue"
- gas_type = /datum/gas/pluoxium
+ gas_type = GAS_PLUOXIUM
/obj/machinery/portable_atmospherics/canister/water_vapor
name = "water vapor canister"
desc = "Water Vapor. We get it, you vape."
icon_state = "water_vapor"
- gas_type = /datum/gas/water_vapor
+ gas_type = GAS_H2O
filled = 1
/obj/machinery/portable_atmospherics/canister/miasma
name = "miasma canister"
desc = "Miasma. Makes you wish your nose was blocked."
icon_state = "miasma"
- gas_type = /datum/gas/miasma
+ gas_type = GAS_MIASMA
filled = 1
@@ -181,7 +180,7 @@
name = "prototype canister"
desc = "A prototype canister for a prototype bike, what could go wrong?"
icon_state = "proto"
- gas_type = /datum/gas/oxygen
+ gas_type = GAS_O2
filled = 1
release_pressure = ONE_ATMOSPHERE*2
@@ -200,29 +199,21 @@
air_contents.copy_from(existing_mixture)
else
create_gas()
- pump = new(src, FALSE)
- pump.on = TRUE
- pump.stat = 0
- pump.build_network()
-
update_icon()
-/obj/machinery/portable_atmospherics/canister/Destroy()
- qdel(pump)
- pump = null
- return ..()
/obj/machinery/portable_atmospherics/canister/proc/create_gas()
if(gas_type)
if(starter_temp)
air_contents.set_temperature(starter_temp)
+ if(!air_contents.return_volume())
+ CRASH("Auxtools is failing somehow! Gas with pointer [air_contents._extools_pointer_gasmixture] is not valid.")
air_contents.set_moles(gas_type, (maximum_pressure * filled) * air_contents.return_volume() / (R_IDEAL_GAS_EQUATION * air_contents.return_temperature()))
/obj/machinery/portable_atmospherics/canister/air/create_gas()
- if(starter_temp)
- air_contents.set_temperature(starter_temp)
- air_contents.set_moles(/datum/gas/oxygen, (O2STANDARD * maximum_pressure * filled) * air_contents.return_volume() / (R_IDEAL_GAS_EQUATION * air_contents.return_temperature()))
- air_contents.set_moles(/datum/gas/nitrogen, (N2STANDARD * maximum_pressure * filled) * air_contents.return_volume() / (R_IDEAL_GAS_EQUATION * air_contents.return_temperature()))
+ air_contents.set_temperature(starter_temp)
+ air_contents.set_moles(GAS_O2, (O2STANDARD * maximum_pressure * filled) * air_contents.return_volume() / (R_IDEAL_GAS_EQUATION * air_contents.return_temperature()))
+ air_contents.set_moles(GAS_N2, (N2STANDARD * maximum_pressure * filled) * air_contents.return_volume() / (R_IDEAL_GAS_EQUATION * air_contents.return_temperature()))
#define CANISTER_UPDATE_HOLDING (1<<0)
#define CANISTER_UPDATE_CONNECTED (1<<1)
@@ -317,9 +308,8 @@
/obj/machinery/portable_atmospherics/canister/proc/canister_break()
disconnect()
- var/datum/gas_mixture/expelled_gas = air_contents.remove(air_contents.total_moles())
var/turf/T = get_turf(src)
- T.assume_air(expelled_gas)
+ T.assume_air(air_contents)
air_update_turf()
stat |= BROKEN
@@ -349,21 +339,20 @@
if(timing && valve_timer < world.time)
valve_open = !valve_open
timing = FALSE
+
+ // Handle gas transfer.
if(valve_open)
var/turf/T = get_turf(src)
- pump.airs[1] = air_contents
- pump.airs[2] = holding ? holding.air_contents : T.return_air()
- pump.target_pressure = release_pressure
-
- pump.process_atmos() // Pump gas.
- if(!holding)
- air_update_turf() // Update the environment if needed.
- else
- pump.airs[1] = null
- pump.airs[2] = null
+ var/datum/gas_mixture/target_air = holding ? holding.air_contents : T.return_air()
+ if(air_contents.release_gas_to(target_air, release_pressure) && !holding)
+ air_update_turf()
update_icon()
+/obj/machinery/portable_atmospherics/canister/ui_status(mob/user)
+ . = ..()
+ if(. > UI_UPDATE && !allowed(user))
+ . = UI_UPDATE
/obj/machinery/portable_atmospherics/canister/ui_state(mob/user)
return GLOB.physical_state
@@ -373,6 +362,7 @@
if(!ui)
ui = new(user, src, "Canister")
ui.open()
+ ui.set_autoupdate(TRUE) // Canister pressure, tank pressure, prototype canister timer
/obj/machinery/portable_atmospherics/canister/ui_data()
var/data = list()
@@ -415,12 +405,14 @@
desc = initial(replacement.desc)
icon_state = initial(replacement.icon_state)
if("restricted")
+ if(!prototype)
+ return // Prototype canister only feature
restricted = !restricted
if(restricted)
req_access = list(ACCESS_ENGINE)
else
req_access = list()
- . = TRUE
+ . = TRUE
if("pressure")
var/pressure = params["pressure"]
if(pressure == "reset")
@@ -450,10 +442,10 @@
if(!holding)
var/list/danger = list()
for(var/id in air_contents.get_gases())
- if(!GLOB.meta_gas_info[id][META_GAS_DANGER])
+ if(!(GLOB.gas_data.flags[id] & GAS_FLAG_DANGEROUS))
continue
- if(air_contents.get_moles(id) > (GLOB.meta_gas_info[id][META_GAS_MOLES_VISIBLE] || MOLES_GAS_VISIBLE)) //if moles_visible is undefined, default to default visibility
- danger[GLOB.meta_gas_info[id][META_GAS_NAME]] = air_contents.get_moles(id) //ex. "plasma" = 20
+ if(air_contents.get_moles(id) > (GLOB.gas_data.visibility[id] || MOLES_GAS_VISIBLE)) //if moles_visible is undefined, default to default visibility
+ danger[GLOB.gas_data.names[id]] = air_contents.get_moles(id) //ex. "plasma" = 20
if(danger.len)
message_admins("[ADMIN_LOOKUPFLW(usr)] opened a canister that contains the following at [ADMIN_VERBOSEJMP(src)]:")
@@ -467,15 +459,21 @@
investigate_log(logmsg, INVESTIGATE_ATMOS)
release_log += logmsg
. = TRUE
+ /* // Apparently the timer isn't present in TGUI - commenting out so it can't be used via exploits
if("timer")
+ if(!prototype)
+ return
var/change = params["change"]
switch(change)
if("reset")
timer_set = default_timer_set
+ . = TRUE
if("decrease")
timer_set = max(minimum_timer_set, timer_set - 10)
+ . = TRUE
if("increase")
timer_set = min(maximum_timer_set, timer_set + 10)
+ . = TRUE
if("input")
var/user_input = input(usr, "Set time to valve toggle.", name) as null|num
if(!user_input)
@@ -488,6 +486,8 @@
. = TRUE
if("toggle_timer")
set_active()
+ . = TRUE
+ */
if("eject")
if(holding)
if(valve_open)
@@ -495,4 +495,5 @@
investigate_log("[key_name(usr)] removed the [holding], leaving the valve open and transferring into the air.", INVESTIGATE_ATMOS)
replace_tank(usr, FALSE)
. = TRUE
- update_icon()
+ if(.)
+ update_icon()
diff --git a/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm
index 4d537624ab32d..ca26846171b0c 100644
--- a/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm
+++ b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm
@@ -3,7 +3,7 @@
icon = 'icons/obj/atmos.dmi'
use_power = NO_POWER_USE
max_integrity = 250
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 60, "acid" = 30)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 60, "acid" = 30, "stamina" = 0)
anchored = FALSE
var/datum/gas_mixture/air_contents
@@ -16,7 +16,7 @@
/obj/machinery/portable_atmospherics/New()
..()
- SSair.atmos_machinery += src
+ SSair.atmos_air_machinery += src
air_contents = new(volume)
air_contents.set_temperature(T20C)
@@ -24,11 +24,12 @@
return 1
/obj/machinery/portable_atmospherics/Destroy()
- SSair.atmos_machinery -= src
-
+ SSair.atmos_air_machinery -= src
disconnect()
qdel(air_contents)
air_contents = null
+
+ SSair.atmos_machinery -= src
return ..()
@@ -45,7 +46,7 @@
return ..()
/obj/machinery/portable_atmospherics/process_atmos()
- if(!connected_port) // Pipe network handles reactions if connected.
+ if(!connected_port && air_contents != null && src != null) // Pipe network handles reactions if connected.
air_contents.react(src)
/obj/machinery/portable_atmospherics/return_air()
@@ -66,8 +67,7 @@
//Perform the connection
connected_port = new_port
connected_port.connected_device = src
- var/datum/pipeline/connected_port_parent = connected_port.parents[1]
- connected_port_parent.reconcile_air()
+ connected_port.parents[1].update = PIPENET_UPDATE_STATUS_RECONCILE_NEEDED
anchored = TRUE //Prevent movement
pixel_x = new_port.pixel_x
@@ -104,8 +104,8 @@
/obj/machinery/portable_atmospherics/examine(mob/user)
. = ..()
if(holding)
- . += {"\The [src] contains [holding]. Alt-click [src] to remove it.
- Click [src] with another gas tank to hot swap [holding]."}
+ . += "\The [src] contains [holding]. Alt-click [src] to remove it.\n"+\
+ "Click [src] with another gas tank to hot swap [holding]."
/obj/machinery/portable_atmospherics/proc/replace_tank(mob/living/user, close_valve, obj/item/tank/new_tank)
if(holding)
diff --git a/code/modules/atmospherics/machinery/portable/pump.dm b/code/modules/atmospherics/machinery/portable/pump.dm
index 769c457abdca4..54b225b7a8574 100644
--- a/code/modules/atmospherics/machinery/portable/pump.dm
+++ b/code/modules/atmospherics/machinery/portable/pump.dm
@@ -13,6 +13,7 @@
var/on = FALSE
var/direction = PUMP_OUT
+ var/target_pressure = ONE_ATMOSPHERE
var/obj/machinery/atmospherics/components/binary/pump/pump
volume = 1000
@@ -22,7 +23,7 @@
pump = new(src, FALSE)
pump.on = TRUE
pump.stat = 0
- pump.build_network()
+ SSair.add_to_rebuild_queue(pump)
/obj/machinery/portable_atmospherics/pump/Destroy()
var/turf/T = get_turf(src)
@@ -91,6 +92,7 @@
if(!ui)
ui = new(user, src, "PortablePump")
ui.open()
+ ui.set_autoupdate(TRUE) // Air pressure, tank pressure
/obj/machinery/portable_atmospherics/pump/ui_data()
var/data = list()
@@ -118,8 +120,8 @@
if("power")
on = !on
if(on && !holding)
- var/plasma = air_contents.get_moles(/datum/gas/plasma)
- var/n2o = air_contents.get_moles(/datum/gas/nitrous_oxide)
+ var/plasma = air_contents.get_moles(GAS_PLASMA)
+ var/n2o = air_contents.get_moles(GAS_NITROUS)
if(n2o || plasma)
message_admins("[ADMIN_LOOKUPFLW(usr)] turned on a pump that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""] at [ADMIN_VERBOSEJMP(src)]")
log_admin("[key_name(usr)] turned on a pump that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""] at [AREACOORD(src)]")
@@ -155,4 +157,5 @@
if(holding)
replace_tank(usr, FALSE)
. = TRUE
- update_icon()
+ if(.)
+ update_icon()
diff --git a/code/modules/atmospherics/machinery/portable/scrubber.dm b/code/modules/atmospherics/machinery/portable/scrubber.dm
index 8cb8c55908d7f..105deda497567 100644
--- a/code/modules/atmospherics/machinery/portable/scrubber.dm
+++ b/code/modules/atmospherics/machinery/portable/scrubber.dm
@@ -7,9 +7,10 @@
var/on = FALSE
var/volume_rate = 1000
+ var/overpressure_m = 80
volume = 1000
- var/list/scrubbing = list(/datum/gas/plasma, /datum/gas/carbon_dioxide, /datum/gas/nitrous_oxide, /datum/gas/bz, /datum/gas/nitryl, /datum/gas/tritium, /datum/gas/hypernoblium, /datum/gas/water_vapor)
+ var/list/scrubbing = list(GAS_PLASMA, GAS_CO2, GAS_NITROUS, GAS_BZ, GAS_NITRYL, GAS_TRITIUM, GAS_HYPERNOB, GAS_H2O)
/obj/machinery/portable_atmospherics/scrubber/Destroy()
var/turf/T = get_turf(src)
@@ -38,15 +39,10 @@
scrub(T.return_air())
/obj/machinery/portable_atmospherics/scrubber/proc/scrub(var/datum/gas_mixture/mixture)
- var/transfer_moles = min(1, volume_rate / mixture.return_volume()) * mixture.total_moles()
-
- var/datum/gas_mixture/filtering = mixture.remove(transfer_moles) // Remove part of the mixture to filter.
- if(!filtering)
+ if(air_contents.return_pressure() >= overpressure_m * ONE_ATMOSPHERE)
return
- filtering.scrub_into(air_contents, scrubbing)
-
- mixture.merge(filtering) // Returned the cleaned gas.
+ mixture.scrub_into(air_contents, volume_rate / mixture.return_volume(), scrubbing)
if(!holding)
air_update_turf()
@@ -68,6 +64,7 @@
if(!ui)
ui = new(user, src, "PortableScrubber")
ui.open()
+ ui.set_autoupdate(TRUE) // Air pressure, tank pressure
/obj/machinery/portable_atmospherics/scrubber/ui_data()
var/data = list()
@@ -77,9 +74,8 @@
data["id_tag"] = -1 //must be defined in order to reuse code between portable and vent scrubbers
data["filter_types"] = list()
- for(var/path in GLOB.meta_gas_info)
- var/list/gas = GLOB.meta_gas_info[path]
- data["filter_types"] += list(list("gas_id" = gas[META_GAS_ID], "gas_name" = gas[META_GAS_NAME], "enabled" = (path in scrubbing)))
+ for(var/id in GLOB.gas_data.ids)
+ data["filter_types"] += list(list("gas_id" = id, "gas_name" = GLOB.gas_data.names[id], "enabled" = (id in scrubbing)))
if(holding)
data["holding"] = list()
@@ -111,9 +107,10 @@
replace_tank(usr, FALSE)
. = TRUE
if("toggle_filter")
- scrubbing ^= gas_id2path(params["val"])
+ scrubbing ^= params["val"]
. = TRUE
- update_icon()
+ if(.)
+ update_icon()
/obj/machinery/portable_atmospherics/scrubber/huge
name = "huge air scrubber"
@@ -122,6 +119,7 @@
active_power_usage = 500
idle_power_usage = 10
+ overpressure_m = 200
volume_rate = 1500
volume = 50000
diff --git a/code/modules/atmospherics/multiz.dm b/code/modules/atmospherics/multiz.dm
index 75b628ccc362b..999855f278373 100644
--- a/code/modules/atmospherics/multiz.dm
+++ b/code/modules/atmospherics/multiz.dm
@@ -3,27 +3,43 @@
desc = "An adapter which allows pipes to connect to other pipenets on different decks."
icon_state = "multiz_pipe"
icon = 'icons/obj/atmos.dmi'
+ level = 2 //Always visible.
+
+/obj/machinery/atmospherics/pipe/simple/multiz/layer1
+ piping_layer = 1
+
+/obj/machinery/atmospherics/pipe/simple/multiz/layer3
+ piping_layer = 3
/obj/machinery/atmospherics/pipe/simple/multiz/update_icon()
. = ..()
cut_overlays() //This adds the overlay showing it's a multiz pipe. This should go above turfs and such
var/image/multiz_overlay_node = new(src) //If we have a firing state, light em up!
multiz_overlay_node.icon = 'icons/obj/atmos.dmi'
- multiz_overlay_node.icon_state = "multiz_pipe"
+ multiz_overlay_node.icon_state = "multiz_pipe-[piping_layer]"
multiz_overlay_node.layer = HIGH_OBJ_LAYER
add_overlay(multiz_overlay_node)
///Attempts to locate a multiz pipe that's above us, if it finds one it merges us into its pipenet
-/obj/machinery/atmospherics/pipe/simple/multiz/pipeline_expansion()
+/obj/machinery/atmospherics/pipe/simple/multiz/pipeline_expansion(expand_up = TRUE, expand_down = TRUE)
icon = 'icons/obj/atmos.dmi' //Just to refresh.
var/turf/T = get_turf(src)
- var/obj/machinery/atmospherics/pipe/simple/multiz/above = locate(/obj/machinery/atmospherics/pipe/simple/multiz) in(SSmapping.get_turf_above(T))
- var/obj/machinery/atmospherics/pipe/simple/multiz/below = locate(/obj/machinery/atmospherics/pipe/simple/multiz) in(SSmapping.get_turf_below(T))
- if(below)
- below.pipeline_expansion() //If we've got one below us, force it to add us on facebook
- if(above)
- nodes += above
- above.nodes += src //Two way travel :)
- return ..()
- else
- return ..()
\ No newline at end of file
+ //Expand above pipes recursively
+ if(SSmapping.get_turf_above(T) && expand_up)
+ for(var/obj/machinery/atmospherics/pipe/simple/multiz/above in SSmapping.get_turf_above(T))
+ if(above.piping_layer == piping_layer)
+ //Link ourself to the above node
+ nodes |= above
+ above.nodes |= src
+ //Link above node to their above nodes.
+ above.pipeline_expansion(TRUE, FALSE)
+ //Expand below pipes recursively
+ if(SSmapping.get_turf_below(T) && expand_down)
+ for(var/obj/machinery/atmospherics/pipe/simple/multiz/below in SSmapping.get_turf_below(T))
+ if(below.piping_layer == piping_layer)
+ //Link ourself to the below node
+ nodes |= below
+ below.nodes |= src
+ //Link below node to their below nodes
+ below.pipeline_expansion(FALSE, TRUE)
+ return ..()
diff --git a/code/modules/awaymissions/bluespaceartillery.dm b/code/modules/awaymissions/bluespaceartillery.dm
index deb05920e2292..874f85fa2244e 100644
--- a/code/modules/awaymissions/bluespaceartillery.dm
+++ b/code/modules/awaymissions/bluespaceartillery.dm
@@ -1,8 +1,8 @@
/obj/machinery/artillerycontrol
- var/reload = 60
- var/reload_cooldown = 60
+ var/reload = 120
+ var/reload_cooldown = 120
var/explosiondev = 3
var/explosionmed = 6
var/explosionlight = 12
@@ -11,9 +11,9 @@
icon = 'icons/obj/machines/particle_accelerator.dmi'
density = TRUE
-/obj/machinery/artillerycontrol/process()
+/obj/machinery/artillerycontrol/process(delta_time)
if(reload < reload_cooldown)
- reload++
+ reload += delta_time
/obj/structure/artilleryplaceholder
name = "artillery"
@@ -45,7 +45,7 @@
if(reload < reload_cooldown)
return
if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr))
- priority_announce("Bluespace artillery fire detected. Brace for impact.")
+ priority_announce("Bluespace artillery fire detected. Brace for impact.", sound = SSstation.announcer.get_rand_alert_sound())
message_admins("[ADMIN_LOOKUPFLW(usr)] has launched an artillery strike.")
var/list/L = list()
for(var/turf/T in get_area_turfs(thearea.type))
diff --git a/code/modules/awaymissions/capture_the_flag.dm b/code/modules/awaymissions/capture_the_flag.dm
index 0e96d7d8be29d..897276323e3b7 100644
--- a/code/modules/awaymissions/capture_the_flag.dm
+++ b/code/modules/awaymissions/capture_the_flag.dm
@@ -9,7 +9,7 @@
-/obj/item/twohanded/ctf
+/obj/item/ctf
name = "banner"
icon = 'icons/obj/banner.dmi'
icon_state = "banner"
@@ -31,20 +31,28 @@
var/obj/effect/ctf/flag_reset/reset
var/reset_path = /obj/effect/ctf/flag_reset
-/obj/item/twohanded/ctf/Destroy()
+/obj/item/ctf/Destroy()
QDEL_NULL(reset)
return ..()
-/obj/item/twohanded/ctf/Initialize()
+/obj/item/ctf/Initialize()
. = ..()
if(!reset)
reset = new reset_path(get_turf(src))
+ reset.flag = src
-/obj/item/twohanded/ctf/process()
+/obj/item/ctf/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/two_handed)
+
+/obj/item/ctf/process()
if(is_ctf_target(loc)) //don't reset from someone's hands.
return PROCESS_KILL
if(world.time > reset_cooldown)
- forceMove(get_turf(src.reset))
+ var/turf/our_turf = get_turf(src.reset)
+ if(!our_turf)
+ return TRUE
+ forceMove(our_turf)
for(var/mob/M in GLOB.player_list)
var/area/mob_area = get_area(M)
if(istype(mob_area, /area/ctf))
@@ -52,7 +60,7 @@
STOP_PROCESSING(SSobj, src)
//ATTACK HAND IGNORING PARENT RETURN VALUE
-/obj/item/twohanded/ctf/attack_hand(mob/living/user)
+/obj/item/ctf/attack_hand(mob/living/user)
if(!is_ctf_target(user) && !anyonecanpickup)
to_chat(user, "Non players shouldn't be moving the flag!")
return
@@ -76,7 +84,7 @@
STOP_PROCESSING(SSobj, src)
..()
-/obj/item/twohanded/ctf/dropped(mob/user)
+/obj/item/ctf/dropped(mob/user)
..()
user.anchored = FALSE
user.status_flags |= CANPUSH
@@ -89,7 +97,7 @@
anchored = TRUE
-/obj/item/twohanded/ctf/red
+/obj/item/ctf/red
name = "red flag"
icon_state = "banner-red"
item_state = "banner-red"
@@ -98,7 +106,7 @@
reset_path = /obj/effect/ctf/flag_reset/red
-/obj/item/twohanded/ctf/blue
+/obj/item/ctf/blue
name = "blue flag"
icon_state = "banner-blue"
item_state = "banner-blue"
@@ -112,6 +120,13 @@
icon_state = "banner"
desc = "This is where a banner with Nanotrasen's logo on it would go."
layer = LOW_ITEM_LAYER
+ var/obj/item/ctf/flag
+
+/obj/effect/ctf/flag_reset/Destroy()
+ if(flag)
+ flag.reset = null
+ flag = null
+ return ..()
/obj/effect/ctf/flag_reset/red
name = "red flag landmark"
@@ -171,20 +186,19 @@
GLOB.poi_list.Remove(src)
..()
-/obj/machinery/capture_the_flag/process()
- for(var/i in spawned_mobs)
- if(!i)
- spawned_mobs -= i
+/obj/machinery/capture_the_flag/process(delta_time)
+ for(var/mob/living/M as() in spawned_mobs)
+ if(QDELETED(M))
+ spawned_mobs -= M
continue
// Anyone in crit, automatically reap
- var/mob/living/M = i
if(M.InCritical() || M.stat == DEAD)
ctf_dust_old(M)
else
// The changes that you've been hit with no shield but not
// instantly critted are low, but have some healing.
- M.adjustBruteLoss(-5)
- M.adjustFireLoss(-5)
+ M.adjustBruteLoss(-2.5 * delta_time)
+ M.adjustFireLoss(-2.5 * delta_time)
/obj/machinery/capture_the_flag/red
name = "Red CTF Controller"
@@ -212,6 +226,9 @@
return
+ if(!(GLOB.ghost_role_flags & GHOSTROLE_MINIGAME))
+ to_chat(user, "CTF has been temporarily disabled by admins.")
+ return
people_who_want_to_play |= user.ckey
var/num = people_who_want_to_play.len
var/remaining = CTF_REQUIRED_PLAYERS - num
@@ -264,7 +281,7 @@
/obj/machinery/capture_the_flag/proc/spawn_team_member(client/new_team_member)
var/mob/living/carbon/human/M = new/mob/living/carbon/human(get_turf(src))
new_team_member.prefs.copy_to(M)
- M.set_species(/datum/species/synth)
+ M.set_species(/datum/species/human)
M.key = new_team_member.key
M.faction += team
M.equipOutfit(ctf_gear)
@@ -277,8 +294,8 @@
attack_ghost(ghost)
/obj/machinery/capture_the_flag/attackby(obj/item/I, mob/user, params)
- if(istype(I, /obj/item/twohanded/ctf))
- var/obj/item/twohanded/ctf/flag = I
+ if(istype(I, /obj/item/ctf))
+ var/obj/item/ctf/flag = I
if(flag.team != src.team)
user.transferItemToLoc(flag, get_turf(flag.reset), TRUE)
points++
@@ -295,7 +312,7 @@
if(istype(mob_area, /area/ctf))
to_chat(M, "[team] team wins!")
to_chat(M, "Teams have been cleared. Click on the machines to vote to begin another round.")
- for(var/obj/item/twohanded/ctf/W in M)
+ for(var/obj/item/ctf/W in M)
M.dropItemToGround(W)
M.dust()
for(var/obj/machinery/control_point/control in GLOB.machines)
@@ -336,7 +353,7 @@
var/list/ctf_object_typecache = typecacheof(list(
/obj/machinery,
/obj/effect/ctf,
- /obj/item/twohanded/ctf
+ /obj/item/ctf
))
for(var/atm in A)
if (isturf(A) || ismob(A) || isarea(A))
@@ -497,11 +514,11 @@
W.registered_name = H.real_name
W.update_label(W.registered_name, W.assignment)
- no_drops += H.get_item_by_slot(SLOT_WEAR_SUIT)
- no_drops += H.get_item_by_slot(SLOT_GLOVES)
- no_drops += H.get_item_by_slot(SLOT_SHOES)
- no_drops += H.get_item_by_slot(SLOT_W_UNIFORM)
- no_drops += H.get_item_by_slot(SLOT_EARS)
+ no_drops += H.get_item_by_slot(ITEM_SLOT_OCLOTHING)
+ no_drops += H.get_item_by_slot(ITEM_SLOT_GLOVES)
+ no_drops += H.get_item_by_slot(ITEM_SLOT_FEET)
+ no_drops += H.get_item_by_slot(ITEM_SLOT_ICLOTHING)
+ no_drops += H.get_item_by_slot(ITEM_SLOT_EARS)
for(var/i in no_drops)
var/obj/item/I = i
ADD_TRAIT(I, TRAIT_NODROP, CAPTURE_THE_FLAG_TRAIT)
@@ -641,6 +658,13 @@
for(var/obj/machinery/capture_the_flag/CTF in GLOB.machines)
CTF.dead_barricades += src
+/obj/effect/ctf/dead_barricade/Destroy()
+ for(var/obj/machinery/capture_the_flag/CTF in GLOB.machines)
+ //if(CTF.game_id != game_id)
+ // continue
+ CTF.dead_barricades -= src
+ return ..()
+
/obj/effect/ctf/dead_barricade/proc/respawn()
if(!QDELETED(src))
new /obj/structure/barricade/security/ctf(get_turf(src))
@@ -657,11 +681,11 @@
resistance_flags = INDESTRUCTIBLE
var/obj/machinery/capture_the_flag/controlling
var/team = "none"
- var/point_rate = 1
+ var/point_rate = 0.5
-/obj/machinery/control_point/process()
+/obj/machinery/control_point/process(delta_time)
if(controlling)
- controlling.control_points += point_rate
+ controlling.control_points += point_rate * delta_time
if(controlling.control_points >= controlling.control_points_to_win)
controlling.victory()
diff --git a/code/modules/awaymissions/corpse.dm b/code/modules/awaymissions/corpse.dm
index 6975891b328d8..9a569fcadc7fa 100644
--- a/code/modules/awaymissions/corpse.dm
+++ b/code/modules/awaymissions/corpse.dm
@@ -36,6 +36,9 @@
/obj/effect/mob_spawn/attack_ghost(mob/user)
if(!SSticker.HasRoundStarted() || !loc || !ghost_usable)
return
+ if(!(GLOB.ghost_role_flags & GHOSTROLE_SPAWNER) && !(flags_1 & ADMIN_SPAWNED_1))
+ to_chat(user, "An admin has temporarily disabled non-admin ghost roles!")
+ return
if(!uses)
to_chat(user, "This spawner is out of charges!")
return
@@ -60,6 +63,7 @@
else if(ghost_usable)
GLOB.poi_list |= src
LAZYADD(GLOB.mob_spawners[name], src)
+ SSmobs.update_spawners()
/obj/effect/mob_spawn/Destroy()
GLOB.poi_list -= src
@@ -67,6 +71,7 @@
LAZYREMOVE(spawners, src)
if(!LAZYLEN(spawners))
GLOB.mob_spawners -= name
+ SSmobs.update_spawners()
return ..()
/obj/effect/mob_spawn/proc/special(mob/M)
@@ -469,7 +474,7 @@
outfit = /datum/outfit/nanotrasencommandercorpse
/datum/outfit/nanotrasencommandercorpse
- name = "Nanotrasen Private Security Commander"
+ name = "\improper Nanotrasen Private Security Commander"
uniform = /obj/item/clothing/under/rank/centcom/commander
suit = /obj/item/clothing/suit/armor/bulletproof
ears = /obj/item/radio/headset/heads/captain
@@ -483,7 +488,7 @@
/obj/effect/mob_spawn/human/nanotrasensoldier
- name = "Nanotrasen Private Security Officer"
+ name = "\improper Nanotrasen Private Security Officer"
id_job = "Private Security Force"
id_access_list = list(ACCESS_CENT_CAPTAIN, ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_MEDICAL, ACCESS_CENT_STORAGE, ACCESS_SECURITY, ACCESS_MECH_SECURITY)
outfit = /datum/outfit/nanotrasensoldiercorpse
@@ -503,7 +508,7 @@
/obj/effect/mob_spawn/human/commander/alive
death = FALSE
roundstart = FALSE
- mob_name = "Nanotrasen Commander"
+ mob_name = "\improper Nanotrasen Commander"
name = "sleeper"
icon = 'icons/obj/machines/sleeper.dmi'
icon_state = "sleeper"
diff --git a/code/modules/awaymissions/mission_code/Academy.dm b/code/modules/awaymissions/mission_code/Academy.dm
index 7d73a394591c8..2ec007ba869d1 100644
--- a/code/modules/awaymissions/mission_code/Academy.dm
+++ b/code/modules/awaymissions/mission_code/Academy.dm
@@ -69,9 +69,9 @@
/obj/singularity/academy/admin_investigate_setup()
return
-/obj/singularity/academy/process()
+/obj/singularity/academy/process(delta_time)
eat()
- if(prob(1))
+ if(DT_PROB(0.5, delta_time))
mezzer()
@@ -107,7 +107,7 @@
if(next_check < world.time)
if(!current_wizard)
for(var/mob/living/L in GLOB.player_list)
- if(L.z == src.z && L.stat != DEAD && !(faction in L.faction))
+ if(L.get_virtual_z_level() == src.get_virtual_z_level() && L.stat != DEAD && !(faction in L.faction))
summon_wizard()
break
else
@@ -132,8 +132,10 @@
if(LAZYLEN(candidates))
var/mob/dead/observer/C = pick(candidates)
message_admins("[ADMIN_LOOKUPFLW(C)] was spawned as Wizard Academy Defender")
- current_wizard.ghostize() // on the off chance braindead defender gets back in
+ current_wizard.ghostize(FALSE) // on the off chance braindead defender gets back in
current_wizard.key = C.key
+ else
+ current_wizard.ghostize(FALSE,SENTIENCE_FORCE)
/obj/structure/academy_wizard_spawner/proc/summon_wizard()
var/turf/T = src.loc
@@ -170,6 +172,7 @@
microwave_riggable = FALSE
var/reusable = TRUE
var/used = FALSE
+ var/roll_in_progress = FALSE
/obj/item/dice/d20/fate/stealth
name = "d20"
@@ -192,6 +195,10 @@
/obj/item/dice/d20/fate/diceroll(mob/user)
. = ..()
+ if(roll_in_progress)
+ to_chat(user, "The dice is already channeling its power! Be patient!")
+ return
+
if(!used)
if(!ishuman(user) || !user.mind || (user.mind in SSticker.mode.wizards))
to_chat(user, "You feel the magic of the dice is restricted to ordinary humans!")
@@ -199,10 +206,9 @@
if(!reusable)
used = TRUE
-
+ roll_in_progress = TRUE
var/turf/T = get_turf(src)
T.visible_message("[src] flares briefly.")
-
addtimer(CALLBACK(src, .proc/effect, user, .), 1 SECONDS)
/obj/item/dice/d20/fate/equipped(mob/user, slot)
@@ -213,6 +219,7 @@
/obj/item/dice/d20/fate/proc/effect(var/mob/living/carbon/human/user,roll)
var/turf/T = get_turf(src)
+
switch(roll)
if(1)
//Dust
@@ -321,7 +328,7 @@
if(17)
//Tator Kit
T.visible_message("A suspicious box appears!")
- new /obj/item/storage/box/syndicate/bundle_A(drop_location())
+ new /obj/item/storage/box/syndie_kit/bundle_A(drop_location())
do_smoke(0, drop_location())
if(18)
//Captain ID
@@ -338,6 +345,9 @@
//Free wizard!
T.visible_message("Magic flows out of [src] and into [user]!")
user.mind.make_Wizard()
+ //roll is completed, allow others players to roll the dice
+ roll_in_progress = FALSE
+
/datum/outfit/butler
name = "Butler"
diff --git a/code/modules/awaymissions/mission_code/Cabin.dm b/code/modules/awaymissions/mission_code/Cabin.dm
index e9266faa4c9d7..c9ba3a6897a7f 100644
--- a/code/modules/awaymissions/mission_code/Cabin.dm
+++ b/code/modules/awaymissions/mission_code/Cabin.dm
@@ -45,7 +45,7 @@
var/active = 1
/obj/structure/firepit/Initialize()
- ..()
+ . = ..()
toggleFirepit()
/obj/structure/firepit/interact(mob/living/user)
@@ -111,6 +111,7 @@
eggsleft = 0
egg_type = null
speak = list()
+ mobchatspan = "assistant"
/*Cabin's forest. Removed in the new cabin map since it was buggy and I prefer manual placement.*/
/datum/mapGenerator/snowy
diff --git a/code/modules/awaymissions/mission_code/TheFactory.dm b/code/modules/awaymissions/mission_code/TheFactory.dm
new file mode 100644
index 0000000000000..35e85a38a2e46
--- /dev/null
+++ b/code/modules/awaymissions/mission_code/TheFactory.dm
@@ -0,0 +1,839 @@
+/obj/singularity/factory
+ name = "tear in the fabric of reality"
+ desc = "Your own comprehension of reality starts bending as you stare this."
+ icon = 'icons/effects/96x96.dmi'
+ icon_state = "boh_tear"
+ pixel_x = -32
+ pixel_y = -32
+ dissipate = 0
+ move_self = 0
+ grav_pull = 1
+
+/obj/singularity/factory/admin_investigate_setup()
+ return
+
+//AREAS//
+//"old" means places we get in after the rune transition
+
+/area/awaymission/factory
+ teleport_restriction = TELEPORT_ALLOW_NONE
+
+/area/awaymission/factory/secret
+ name = "secrets"
+ ambientsounds = list('sound/ambience/secrets.ogg','sound/ambience/ambiholy2.ogg')
+
+/area/awaymission/factory/villageafter
+ name = "The Village"
+ ambientsounds = list('sound/ambience/seag1.ogg', 'sound/ambience/seag2.ogg', 'sound/ambience/seag2.ogg', 'sound/ambience/ambiodd.ogg', 'sound/ambience/ambimystery.ogg', 'sound/ambience/ambiearth.ogg', 'sound/ambience/ambiwind.ogg', 'sound/ambience/ambimine.ogg')
+ dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
+ requires_power = FALSE
+
+/area/awaymission/factory/villageafter/church
+ name = "The Church"
+ ambience_index = AMBIENCE_HOLY
+
+/area/awaymission/factory/villageafter/house
+ ambientsounds = list('sound/ambience/ambiruin4.ogg','sound/ambience/ambiruin6.ogg','sound/ambience/ambiruin7.ogg','sound/ambience/ambiruin2.ogg')
+
+/area/awaymission/factory/villageafter/house/ritual
+ ambientsounds = list('sound/ambience/antag/ecult_op.ogg','sound/ambience/ambiruin2.ogg','sound/spookoween/insane_low_laugh.ogg')
+ mood_bonus = -2
+ mood_message = "It smells like death in here!\n"
+
+/area/awaymission/factory/villageafter/house/start
+ name = "The House"
+ ambientsounds = list('sound/ambience/ambidet2.ogg','sound/ambience/ambiodd.ogg')
+
+/area/awaymission/factory/villageafter/hospital
+ ambientsounds = list('sound/ambience/ambiodd.ogg','sound/ambience/ambimystery.ogg','sound/ambience/ambimaint.ogg','sound/ambience/ambiruin2.ogg')
+ name = "The Hospital"
+ requires_power = TRUE
+
+/area/awaymission/factory/villageafter/spooky
+ ambience_index = AMBIENCE_SPOOKY
+ mood_bonus = -2
+ mood_message = "It smells like death in here!\n"
+
+/area/awaymission/factory/factoryafter
+ name = "The Factory"
+ ambientsounds = list('sound/ambience/ambiodd.ogg','sound/ambience/ambimystery.ogg','sound/ambience/ambimaint.ogg','sound/ambience/ambiruin6.ogg','sound/ambience/ambitech3.ogg')
+ requires_power = TRUE
+ always_unpowered = TRUE
+
+/area/awaymission/factory/clownplanet
+ dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
+ ambientsounds = list('sound/spookoween/scary_horn3.ogg','sound/spookoween/scary_horn.ogg','sound/spookoween/scary_horn2.ogg','sound/spookoween/scary_clown_appear.ogg')
+ mood_bonus = 3
+ mood_message = "I hate this!\n"
+
+/area/awaymission/factory/factoryafter/down
+ ambientsounds = list('sound/ambience/ambiatm1.ogg','sound/ambience/ambifac.ogg','sound/ambience/ambimaint3.ogg','sound/ambience/ambiodd.ogg','sound/ambience/ambimystery.ogg','sound/ambience/ambimaint.ogg','sound/ambience/ambiruin6.ogg','sound/ambience/ambitech3.ogg')
+ requires_power = FALSE
+ always_unpowered = FALSE
+ dynamic_lighting = DYNAMIC_LIGHTING_FORCED
+
+/area/awaymission/factory/factoryafter/down/maint
+ ambientsounds = list('sound/ambience/ambiatm1.ogg','sound/ambience/ambimaint3.ogg','sound/ambience/ambimystery.ogg','sound/ambience/ambimaint.ogg','sound/ambience/ambimaint2.ogg')
+
+/area/awaymission/factory/factoryafter/down/batsecret
+ name = "The maze"
+ ambientsounds = list('sound/ambience/ambiatm1.ogg','sound/ambience/ambimaint3.ogg','sound/ambience/ambimystery.ogg','sound/ambience/ambimaint.ogg','sound/ambience/ambimaint2.ogg','sound/ambience/ambibasement.ogg')
+
+/area/awaymission/factory/factoryafter/down/leveltwo
+ name = "The Factory - middle level"
+ ambientsounds = list('sound/ambience/ambigen7.ogg','sound/ambience/ambifac.ogg','sound/ambience/ambireebe1.ogg','sound/ambience/ambimystery.ogg','sound/ambience/ambiodd.ogg','sound/ambience/ambigen14.ogg')
+
+/area/awaymission/factory/factoryafter/down/leveltwo/morgue
+ name = "The Morgue"
+ ambience_index = AMBIENCE_SPOOKY
+ mood_bonus = -2
+ mood_message = "It smells like death in here!\n"
+
+/area/awaymission/factory/factoryafter/down/leveltwo/ritual
+ name = "a strange place"
+ ambientsounds = list('sound/ambience/antag/ecult_op.ogg','sound/ambience/ambiodd.ogg','sound/ambience/ambireebe2.ogg','sound/spookoween/insane_low_laugh.ogg','sound/spookoween/chain_rattling.ogg')
+ mood_bonus = -2
+ mood_message = "It smells like death in here!\n"
+
+/area/awaymission/factory/factoryafter/down/levelthree
+ name = "The Factory - lower level"
+ ambientsounds = list('sound/ambience/ambiatm1.ogg','sound/ambience/ambitech.ogg','sound/ambience/ambitech2.ogg','sound/ambience/ambitech3.ogg','sound/ambience/ambiatmos.ogg','sound/ambience/ambiatmos2.ogg','sound/ambience/ambiodd.ogg','sound/ambience/ambimaint5.ogg','sound/ambience/ambireebe3.ogg','sound/ambience/ambilava.ogg')
+
+/area/awaymission/factory/factoryafter/down/levelthree/engine
+ name = "The reality engine"
+ ambientsounds = list('sound/ambience/singulambience.ogg','sound/ambience/ambisin1.ogg','sound/ambience/ambisin2.ogg','sound/ambience/ambisin3.ogg','sound/ambience/ambisin4.ogg','sound/ambience/antag/assimilation.ogg')
+ mood_bonus = 1
+ mood_message = "Uhm... Ok?... I guess...\n"
+
+/area/awaymission/factory/factoryduring
+ name = "The old Factory"
+ ambientsounds = list('sound/ambience/ambidanger.ogg','sound/ambience/ambifac.ogg','sound/ambience/ambidanger2.ogg','sound/ambience/ambiatm1.ogg','sound/ambience/ambiatmos.ogg')
+
+/area/awaymission/factory/factoryduring/down/levelthree
+ name = "The old Factory - lower level"
+ ambientsounds = list('sound/ambience/ambiatm1.ogg','sound/ambience/ambitech.ogg','sound/ambience/ambitech2.ogg','sound/ambience/ambitech3.ogg','sound/ambience/ambiatmos.ogg','sound/ambience/ambiatmos2.ogg','sound/ambience/signal.ogg','sound/ambience/ambidanger.ogg','sound/ambience/ambidanger2.ogg','sound/ambience/ambiruin2.ogg')
+
+/area/awaymission/factory/factoryduring/down/levelthree/engine
+ name = "The old reality engine"
+ mood_bonus = 1
+ mood_message = "Uhm... Ok?... I guess...\n"
+ ambientsounds = list('sound/ambience/singulambience.ogg','sound/ambience/ambisin1.ogg','sound/ambience/ambisin2.ogg','sound/ambience/ambisin3.ogg','sound/ambience/ambisin4.ogg','sound/ambience/antag/assimilation.ogg','sound/ambience/ambidanger.ogg','sound/ambience/ambidanger2.ogg')
+ name = "The reality engine"
+
+/area/awaymission/factory/factoryduring/down/leveltwo
+ name = "The old Factory - middle level"
+ ambientsounds = list('sound/ambience/ambidanger.ogg','sound/ambience/ambifac.ogg','sound/ambience/ambidanger2.ogg','sound/ambience/ambiruin5.ogg','sound/ambience/ambiruin6.ogg','sound/ambience/ambigen1.ogg','sound/ambience/ambigen3.ogg')
+
+/area/awaymission/factory/factoryduring/down/leveltwo/ritual
+ name = "a strange place"
+ ambientsounds = list('sound/ambience/antag/ecult_op.ogg','sound/spookoween/insane_low_laugh.ogg','sound/spookoween/spookywind.ogg')
+
+/area/awaymission/factory/factoryduring/down/leveltwo/morgue
+ name = "The old Morgue"
+ ambience_index = AMBIENCE_SPOOKY
+ mood_bonus = -2
+ mood_message = "It smells like death in here!\n"
+
+/area/awaymission/factory/factoryduring/down/leveltwo/asylum
+ name = "The sector of mentally disordered"
+ ambientsounds = list('sound/ambience/ambimo2.ogg','sound/ambience/ambiodd.ogg','sound/ambience/ambitech.ogg','sound/ambience/ambireebe1.ogg')
+
+/area/awaymission/factory/factoryduring/down
+ ambientsounds = list('sound/ambience/ambidanger.ogg','sound/ambience/ambidanger2.ogg','sound/ambience/ambimaint.ogg','sound/ambience/ambiruin6.ogg','sound/ambience/ambitech3.ogg','sound/ambience/ambiatm1.ogg')
+
+/area/awaymission/factory/factoryduring/down/maint
+ ambientsounds = list('sound/ambience/ambidanger.ogg','sound/ambience/ambidanger2.ogg','sound/ambience/ambiatm1.ogg','sound/ambience/ambimaint3.ogg','sound/ambience/ambimaint.ogg','sound/ambience/ambimaint2.ogg')
+
+/area/awaymission/factory/villageduring
+ name = "The old village"
+ dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
+ requires_power = FALSE
+ ambientsounds = list('sound/ambience/ambidanger.ogg','sound/ambience/ambidanger2.ogg','sound/ambience/ambiearthduring.ogg','sound/ambience/ambiwind.ogg','sound/ambience/ambimine.ogg')
+
+/area/awaymission/factory/villageduring/house
+ ambientsounds = list('sound/ambience/ambidanger.ogg','sound/ambience/ambidanger2.ogg','sound/ambience/ambiearthduring.ogg','sound/ambience/ambiwind.ogg','sound/ambience/ambimine.ogg','sound/ambience/ambiruin4.ogg','sound/ambience/ambiruin6.ogg','sound/ambience/ambiruin7.ogg','sound/ambience/ambiruin2.ogg','sound/ambience/signal.ogg')
+
+/area/awaymission/factory/villageduring/house/ritual
+ ambientsounds = list('sound/ambience/antag/ecult_op.ogg','sound/ambience/ambiruin2.ogg','sound/spookoween/insane_low_laugh.ogg')
+ mood_bonus = -2
+ mood_message = "It smells like death in here!\n"
+
+/area/awaymission/factory/villageduring/church
+ name = "The old Church"
+ ambience_index = AMBIENCE_HOLY
+
+/area/awaymission/factory/villageduring/church/leveltwo
+ name = "The old Church - 2nd floor"
+ ambientsounds = list('sound/ambience/ambimystery.ogg','sound/ambience/ambiodd.ogg','sound/ambience/signal.ogg','sound/ambience/ambiwind.ogg','sound/ambience/ambicha2.ogg','sound/ambience/ambiholy3.ogg')
+
+/area/awaymission/factory/villageduring/basement
+ name = "The old basement"
+ ambientsounds = list('sound/ambience/ambibasement.ogg','sound/ambience/ambimystery.ogg','sound/ambience/ambiodd.ogg')
+ dynamic_lighting = DYNAMIC_LIGHTING_FORCED
+
+/area/awaymission/factory/villageduring/house/start
+ name = "The old House"
+ ambientsounds = list('sound/ambience/ambidet2.ogg')
+
+/area/awaymission/factory/villageduring/hospital
+ ambientsounds = list('sound/ambience/ambidanger.ogg','sound/ambience/ambidanger2.ogg','sound/ambience/ambiatmos.ogg','sound/ambience/ambimystery.ogg','sound/ambience/ambimaint.ogg','sound/ambience/ambiruin2.ogg')
+ name = "The Hospital"
+
+/area/awaymission/factory/transition
+ name = "Beyond the time"
+ dynamic_lighting = DYNAMIC_LIGHTING_FORCED
+ ambientsounds = list('sound/ambience/shipambience.ogg','sound/ambience/ambiatmos.ogg','sound/ambience/antag/malf.ogg','sound/ambience/signal.ogg','sound/ambience/ambimalf.ogg')
+
+//ITEMS//
+
+/obj/item/statuebust/toy
+ name = "aesthetic bust"
+ desc = "A priceless ancient marble bust, the kind that belongs in a museum. Looks like this one has some differences."
+ var/cooldown = 0
+
+/obj/item/statuebust/toy/attack_self(mob/user)
+ if (cooldown < world.time)
+ cooldown = world.time + 450
+ user.visible_message("[user] activates \the [src].", "You activate \the [src]!", "You hear a music playing.")
+ playsound(src, 'sound/ambience/ambivapor1.ogg', 50, 0)
+ else
+ to_chat(user, "Nothing happens!")
+
+/obj/item/clothing/glasses/hud/terminator
+ name = "T-80 tactical sunglasses"
+ desc = "Strangely ancient technology used to help provide rudimentary eye cover. Also shows information about criminals and their condition. Has enhanced shielding which blocks flashes."
+ icon_state = "t80sunglasses"
+ darkness_view = 1
+ clothing_flags = SCAN_REAGENTS
+ vision_flags = SEE_MOBS
+ flash_protect = 1
+ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
+ lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ hud_type = list(DATA_HUD_SECURITY_ADVANCED, DATA_HUD_DIAGNOSTIC_BASIC, DATA_HUD_MEDICAL_ADVANCED)
+ hud_trait = list(TRAIT_SECURITY_HUD, TRAIT_MEDICAL_HUD)
+
+//MOBS//
+
+/mob/living/simple_animal/hostile/proc/summon_backup_nosound(distance, exact_faction_match)
+ do_alert_animation(src)
+ for(var/mob/living/simple_animal/hostile/M in oview(distance, GET_TARGETS_FROM(src)))
+ if(faction_check_mob(M, TRUE))
+ if(M.AIStatus == AI_OFF)
+ return
+ else
+ M.Goto(src,M.move_to_delay,M.minimum_distance)
+
+/mob/living/simple_animal/hostile/factory
+ name = "Guard"
+ desc = "An armed officer. Looks like they prefer to shoot rather than asking questions."
+ speak = list("Stop resisting!","I need backup!","Die, freak!","It's getting away!")
+ icon = 'icons/mob/simple_human.dmi'
+ icon_state = "nanotrasen"
+ icon_living = "nanotrasen"
+ icon_dead = null
+ icon_gib = "syndicate_gib"
+ mob_biotypes = list(MOB_ORGANIC, MOB_HUMANOID)
+ turns_per_move = 5
+ speak_chance = 5
+ del_on_death = TRUE
+ do_footstep = TRUE
+ environment_smash = ENVIRONMENT_SMASH_STRUCTURES
+ search_objects = 1
+ a_intent = INTENT_HARM
+ attack_sound = 'sound/weapons/cqchit2.ogg'
+ attacktext = "punches"
+ robust_searching = 1
+ melee_damage = 12
+ speed = 0
+ maxHealth = 100
+ health = 100
+ melee_damage = 12
+ stat_attack = UNCONSCIOUS
+ faction = list("nanotrasenprivate")
+ status_flags = CANPUSH
+ atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0)
+ unsuitable_atmos_damage = 10
+ loot = list(/obj/effect/mob_spawn/human/corpse/nanotrasensoldier)
+ response_help = "pokes"
+ response_disarm = "shoves"
+ response_harm = "hits"
+ var/cooldown = 0
+
+/mob/living/simple_animal/hostile/factory/death(gibbed)
+ var/chosen_sound = pick('sound/creatures/guarddeath.ogg','sound/creatures/guarddeath2.ogg','sound/creatures/guarddeath3.ogg','sound/creatures/guarddeath4.ogg')
+ playsound(get_turf(src), chosen_sound, 100, TRUE, 0)
+ ..()
+
+/mob/living/simple_animal/hostile/factory/Aggro()
+ ..()
+ var/list/possible_sounds = list('sound/effects/radio1.ogg','sound/effects/radio2.ogg','sound/effects/radio3.ogg')
+ var/chosen_sound = pick(possible_sounds)
+ if (cooldown < world.time) // So we don't repeat the sound and the phrase every time we get hit and do it at least each 30 seconds
+ cooldown = world.time + 300
+ summon_backup_nosound(10)
+ playsound(get_turf(src), chosen_sound, 100, 0, 0)
+ var/list/possible_phrases = list("Anomaly spotted! Send backup!","Intruder over here!","Hostile spotted, get them!")
+ var/chosen_phrase = pick(possible_phrases)
+ say(chosen_phrase)
+ else
+ return
+
+/mob/living/simple_animal/hostile/factory/ranged
+ icon_state = "nanotrasenranged"
+ icon_living = "nanotrasenranged"
+ ranged_cooldown_time = 15
+ ranged = 1
+ retreat_distance = 3
+ minimum_distance = 5
+ casingtype = /obj/item/ammo_casing/c45
+ projectilesound = 'sound/weapons/gunshot.ogg'
+ loot = list(/obj/item/gun/ballistic/automatic/pistol/m1911,
+ /obj/effect/mob_spawn/human/corpse/nanotrasensoldier)
+
+/mob/living/simple_animal/hostile/factory/ranged/lmg
+ desc = "This one is carrying a bigger gun. Seek for cover."
+ icon_state = "nanotrasenrangedlmg"
+ icon_living = "nanotrasenrangedlmg"
+ projectilesound = 'sound/weapons/rifleshot.ogg'
+ sidestep_per_cycle = 0
+ check_friendly_fire = 1
+ minimum_distance = 6
+ approaching_target = FALSE
+ rapid = 8
+ rapid_fire_delay = 2
+ casingtype = /obj/item/ammo_casing/mm712x82
+ ranged_cooldown_time = 80
+ vision_range = 12
+ aggro_vision_range = 12
+ retreat_distance = 1
+ move_to_delay = 4
+ speed = 12
+ health = 150
+ maxHealth = 150
+ loot = list(/obj/item/gun/ballistic/automatic/l6_saw/unrestricted,
+ /obj/effect/mob_spawn/human/corpse/nanotrasensoldier)
+
+/mob/living/simple_animal/hostile/factory/ranged/shotgun
+ icon_state = "nanotrasenrangedshot"
+ icon_living = "nanotrasenrangedshot"
+ rapid = 2
+ rapid_fire_delay = 5
+ casingtype = /obj/item/ammo_casing/shotgun/buckshot
+ projectilesound = 'sound/weapons/shotgunshot.ogg'
+ ranged_cooldown_time = 25
+ loot = list(/obj/item/gun/ballistic/shotgun/automatic/combat,
+ /obj/effect/mob_spawn/human/corpse/nanotrasensoldier)
+
+/mob/living/simple_animal/hostile/factory/ranged/smg
+ icon_state = "nanotrasenrangedsmg"
+ icon_living = "nanotrasenrangedsmg"
+ rapid = 3
+ casingtype = /obj/item/ammo_casing/c46x30mm
+ projectilesound = 'sound/weapons/gunshot_smg.ogg'
+ loot = list(/obj/item/gun/ballistic/automatic/wt550,
+ /obj/effect/mob_spawn/human/corpse/nanotrasensoldier)
+
+/mob/living/simple_animal/hostile/syndicate/factory
+ gender = MALE
+ faction = list("nanotrasenprivate")
+ speak_chance = 5
+ speak = list("Come get me!","You can't get away!","Die!")
+
+/mob/living/simple_animal/hostile/syndicate/factory/sniper
+ name = "The Warden"
+ desc = "One of the best snipers. Take cover or get shot"
+ icon_state = "fsniper"
+ icon_living = "fsniper"
+ ranged = TRUE
+ speed = 1
+ dodge_prob = 40
+ ranged_cooldown_time = 40
+ check_friendly_fire = 1
+ sidestep_per_cycle = 3
+ minimum_distance = 4
+ turns_per_move = 6
+ melee_queue_distance = 2
+ health = 250
+ maxHealth = 250
+ melee_damage = 20
+ rapid_melee = 3
+ attacktext = "hits"
+ attack_sound = 'sound/weapons/genhit3.ogg'
+ projectilesound = 'sound/weapons/sniper_shot.ogg'
+ speak_chance = 2
+ var/cooldown = 0
+ speak = list("You're pretty good.","You can't dodge everything!","Fall down already!")
+ loot = list(/obj/item/gun/ballistic/automatic/sniper_rifle,
+ /obj/effect/mob_spawn/human/corpse/sniper,
+ /obj/item/ammo_box/magazine/sniper_rounds,
+ /obj/item/ammo_box/magazine/sniper_rounds/penetrator,
+ /obj/item/ammo_box/magazine/sniper_rounds/soporific)
+
+/mob/living/simple_animal/hostile/syndicate/factory/sniper/Aggro()
+ ..()
+ ranged_cooldown = 30
+ if (cooldown < world.time)
+ cooldown = world.time + 150
+ summon_backup_nosound(10)
+ playsound(get_turf(src), 'sound/weapons/sniper_rack.ogg', 80, TRUE)
+ say("I've got you in my scope.")
+
+/mob/living/simple_animal/hostile/syndicate/factory/sniper/Shoot()
+ var/allowed_projectile_types = list(/obj/item/ammo_casing/p50, /obj/item/ammo_casing/p50/penetrator)
+ casingtype = pick(allowed_projectile_types)
+ ..()
+
+/mob/living/simple_animal/hostile/syndicate/factory/sniper/death(gibbed)
+ playsound(get_turf(src), 'sound/creatures/wardendeath.ogg', 100, TRUE, 0)
+ ..()
+
+/mob/living/simple_animal/hostile/psycho
+ name = "Psycho"
+ desc = "They're wearing a pretty uncomfortable jacket."
+ icon = 'icons/mob/simple_human.dmi'
+ icon_state = "psycho"
+ icon_living = "psycho"
+ attacktext = "bites"
+ mob_biotypes = list(MOB_ORGANIC, MOB_HUMANOID)
+ turns_per_move = 0
+ del_on_death = TRUE
+ response_help = "pokes"
+ response_disarm = "touches"
+ response_harm = "hits"
+ speak_chance = 5
+ attack_sound = 'sound/weapons/bite.ogg'
+ speak = list("I'm not mad!","What insanity?","Kill")
+ speed = -2
+ maxHealth = 100
+ health = 100
+ atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0)
+ unsuitable_atmos_damage = 5
+ faction = list("psycho")
+ move_to_delay = 3
+ rapid_melee = 2
+ in_melee = TRUE
+ approaching_target = TRUE
+ environment_smash = ENVIRONMENT_SMASH_NONE
+ obj_damage = 5
+ sidestep_per_cycle = 0
+ stat_attack = UNCONSCIOUS
+ melee_damage = 15
+ lose_patience_timeout = 350
+ loot = list(/obj/effect/mob_spawn/human/corpse/psychost)
+
+/mob/living/simple_animal/hostile/psycho/regular
+ var/cooldown = 0
+ var/static/list/idle_sounds
+
+/mob/living/simple_animal/hostile/psycho/regular/Initialize()
+ . = ..()
+ idle_sounds = list('sound/creatures/psycidle1.ogg','sound/creatures/psycidle2.ogg','sound/creatures/psycidle3.ogg')
+
+/mob/living/simple_animal/hostile/psycho/regular/Life()
+ ..()
+ if(Aggro() || stat)
+ return
+ if(prob(20))
+ var/chosen_sound = pick(idle_sounds)
+ playsound(src, chosen_sound, 50, FALSE)
+
+/mob/living/simple_animal/hostile/psycho/regular/Aggro()
+ ..()
+ var/list/possible_sounds = list('sound/hallucinations/growl1.ogg','sound/hallucinations/growl2.ogg','sound/hallucinations/growl3.ogg')
+ var/chosen_sound = pick(possible_sounds)
+ if (cooldown < world.time)
+ cooldown = world.time + 300
+ playsound(get_turf(src), chosen_sound, 70, TRUE, 0)
+
+/mob/living/simple_animal/hostile/psycho/regular/death(gibbed)
+ var/list/possible_sounds = list('sound/creatures/psycdeath1.ogg','sound/creatures/psycdeath2.ogg')
+ var/chosen_sound = pick(possible_sounds)
+ playsound(get_turf(src), chosen_sound, 70, TRUE, 0)
+ ..()
+
+/mob/living/simple_animal/hostile/psycho/fast
+ move_to_delay = 2
+ speed = -5
+ maxHealth = 70
+ health = 70
+
+/mob/living/simple_animal/hostile/psycho/muzzle
+ icon_state = "psychomuzzle"
+ icon_living = "psychomuzzle"
+ attacktext = "headbutts"
+ attack_sound = null
+ speak_chance = 0
+ melee_damage = 9
+ var/cooldown = 0
+ var/static/list/idle_sounds
+ speed = 0
+ loot = list(/obj/effect/mob_spawn/human/corpse/psychost/muzzle)
+
+/mob/living/simple_animal/hostile/psycho/muzzle/Initialize()
+ . = ..()
+ idle_sounds = list('sound/creatures/psychidle.ogg','sound/creatures/psychidle2.ogg')
+
+/mob/living/simple_animal/hostile/psycho/muzzle/death(gibbed)
+ var/list/possible_sounds = list('sound/creatures/psychdeath.ogg','sound/creatures/psychdeath2.ogg',)
+ var/chosen_sound = pick(possible_sounds)
+ playsound(get_turf(src), chosen_sound, 70, TRUE, 0)
+ ..()
+
+/mob/living/simple_animal/hostile/psycho/muzzle/Aggro()
+ ..()
+ var/list/possible_sounds = list('sound/creatures/psychsight.ogg','sound/creatures/psychsight2.ogg')
+ var/chosen_sound = pick(possible_sounds)
+ if (cooldown < world.time)
+ cooldown = world.time + 300
+ playsound(get_turf(src), chosen_sound, 70, TRUE, 0)
+
+/mob/living/simple_animal/hostile/psycho/muzzle/AttackingTarget()
+ ..()
+ playsound(get_turf(src), 'sound/creatures/psychattack1.ogg', 70, TRUE, 0)
+
+/mob/living/simple_animal/hostile/psycho/muzzle/Life()
+ ..()
+ if(Aggro() || stat)
+ return
+ if(prob(20))
+ var/chosen_sound = pick(idle_sounds)
+ playsound(src, chosen_sound, 50, TRUE)
+
+/mob/living/simple_animal/hostile/psycho/trap
+ desc = "This one has a strange device on his head."
+ icon_state = "psychotrap"
+ icon_living = "psychotrap"
+ speak_chance = 0
+ speed = -3
+ move_to_delay = 2
+ melee_damage = 15
+ attack_sound = null
+ attacktext = "headbutts"
+ loot = list(/obj/effect/mob_spawn/human/corpse/psychost/trap)
+ var/cooldown = 0
+ var/static/list/idle_sounds
+
+/mob/living/simple_animal/hostile/psycho/trap/Aggro()
+ ..()
+ var/list/possible_sounds = list('sound/creatures/psychsight.ogg','sound/creatures/psychsight2.ogg')
+ var/chosen_sound = pick(possible_sounds)
+ if (cooldown < world.time)
+ cooldown = world.time + 300
+ playsound(get_turf(src), chosen_sound, 70, TRUE, 0)
+
+/mob/living/simple_animal/hostile/psycho/trap/Initialize()
+ . = ..()
+ idle_sounds = list('sound/creatures/psychidle.ogg','sound/creatures/psychidle2.ogg')
+
+/mob/living/simple_animal/hostile/psycho/trap/Life()
+ ..()
+ if(Aggro() || stat)
+ return
+ if(prob(20))
+ var/chosen_sound = pick(idle_sounds)
+ playsound(src, chosen_sound, 50, FALSE)
+ if(health < maxHealth)
+ playsound(src, 'sound/machines/beep.ogg', 80, FALSE)
+ addtimer(CALLBACK(src, .proc/death), 200)
+
+/mob/living/simple_animal/hostile/psycho/trap/AttackingTarget()
+ var/list/possible_sounds = list('sound/creatures/psychhead.ogg','sound/creatures/psychhead2.ogg')
+ var/chosen_sound = pick(possible_sounds)
+ playsound(get_turf(src), chosen_sound, 100, TRUE, 0)
+ ..()
+
+/mob/living/simple_animal/hostile/psycho/trap/death(gibbed)
+ var/list/possible_sounds = list('sound/creatures/psychdeath.ogg','sound/creatures/psychdeath2.ogg')
+ var/chosen_sound = pick(possible_sounds)
+ playsound(get_turf(src), chosen_sound, 70, 0, 0)
+ playsound(get_turf(src), 'sound/effects/snap.ogg', 75, TRUE, 0)
+ playsound(get_turf(src), 'sound/effects/splat.ogg', 90, TRUE, 0)
+ visible_message("The device activates!")
+ ..()
+
+/mob/living/simple_animal/hostile/syndicate/factory/heavy
+ name = "Heavy gunner"
+ desc = "They didn't get that backpack for nothing."
+ icon_state = "Heavy"
+ icon_living = "Heavy"
+ sidestep_per_cycle = 0
+ minimum_distance = 5
+ approaching_target = TRUE
+ ranged = TRUE
+ rapid = 65
+ rapid_fire_delay = 0.5
+ projectiletype = /obj/item/projectile/beam
+ ranged_cooldown_time = 110
+ vision_range = 9
+ speak_chance = 0
+ speak = null
+ aggro_vision_range = 9
+ attacktext = "hits"
+ attack_sound = 'sound/weapons/genhit3.ogg'
+ retreat_distance = 2
+ melee_queue_distance = 1
+ melee_damage = 25
+ move_to_delay = 4
+ projectilesound = null
+ speed = 15
+ health = 300
+ maxHealth = 300
+ loot = list(/obj/effect/mob_spawn/human/corpse/heavy)
+ var/cooldown = 0
+
+/mob/living/simple_animal/hostile/syndicate/factory/heavy/Initialize()
+ ..()
+
+/mob/living/simple_animal/hostile/syndicate/factory/heavy/Aggro()
+ ..()
+ if (cooldown < world.time)
+ cooldown = world.time + 300
+ playsound(get_turf(src), 'sound/creatures/heavysight1.ogg', 80, 0, 0)
+
+/mob/living/simple_animal/hostile/syndicate/factory/heavy/OpenFire(atom/A)
+ playsound(get_turf(src), 'sound/weapons/heavyminigunstart.ogg', 80, 0, 0)
+ move_to_delay = 6//slowdown when shoot
+ speed = 30
+ sleep(15)
+ playsound(get_turf(src), 'sound/weapons/heavyminigunshoot.ogg', 90, 0, 0)
+ if(CheckFriendlyFire(A))
+ return
+ if(!(simple_mob_flags & SILENCE_RANGED_MESSAGE))
+ visible_message("[src] [ranged_message] at [A]!")
+ if(rapid > 1)
+ var/datum/callback/cb = CALLBACK(src, .proc/Shoot, A)
+ for(var/i in 1 to rapid)
+ addtimer(cb, (i - 1)*rapid_fire_delay)
+ else
+ Shoot(A)
+ ranged_cooldown = world.time + ranged_cooldown_time
+ playsound(get_turf(src), 'sound/weapons/heavyminigunstop.ogg', 80, 0, 0)
+ move_to_delay = initial(move_to_delay)//restore speed
+ speed = initial(speed)
+
+/mob/living/simple_animal/hostile/syndicate/factory/heavy/death(gibbed)
+ playsound(get_turf(src), 'sound/creatures/heavydeath1.ogg', 80, TRUE, 0)
+ ..()
+
+/obj/item/clothing/mask/gas/sechailer/swat/emagged
+ safety = FALSE
+
+/mob/living/simple_animal/hostile/zombie_suicide
+ name = "agressive corpse"
+ desc = "This corpse is holding a grenade without a pin in it..."
+ icon = 'icons/mob/simple_human.dmi'
+ icon_state = "suicidezombie"
+ icon_living = "suicidezombie"
+ mob_biotypes = list(MOB_ORGANIC, MOB_HUMANOID)
+ atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
+ minbodytemp = 0
+ status_flags = CANPUSH
+ health = 100
+ maxHealth = 100
+ move_to_delay = 2
+ speed = 0
+ melee_damage = null
+ attack_sound = null
+ del_on_death = TRUE
+ stat_attack = UNCONSCIOUS
+ a_intent = INTENT_HARM
+ var/det_time = 30
+ var/active = 0
+ var/cooldown = 0
+ loot = list(/obj/effect/mob_spawn/human/corpse/suicidezombie, /obj/item/grenade/syndieminibomb/concussion/frag/activated)
+ do_footstep = TRUE
+ hardattacks = TRUE
+
+/mob/living/simple_animal/hostile/zombie_suicide/Aggro()
+ ..()
+ var/list/possible_sounds = list('sound/creatures/szombiesight.ogg','sound/creatures/szombiesight2.ogg')
+ var/chosen_sound = pick(possible_sounds)
+ if (cooldown < world.time)
+ cooldown = world.time + 300
+ playsound(get_turf(src), chosen_sound, 50, TRUE, 0)
+
+/mob/living/simple_animal/hostile/zombie_suicide/AttackingTarget()
+ if(!active)
+ active = TRUE
+ playsound(src, 'sound/weapons/armbomb.ogg', 100, TRUE)
+ var/list/possible_sounds = list('sound/creatures/szombiesight.ogg','sound/creatures/szombiesight2.ogg')
+ var/chosen_sound = pick(possible_sounds)
+ playsound(get_turf(src), chosen_sound, 50, TRUE, 0)
+ visible_message("[src] primes the grenade!.")
+ addtimer(CALLBACK(src, .proc/prime), det_time)
+
+/mob/living/simple_animal/hostile/zombie_suicide/proc/prime()
+ explosion(src,0, 2, 3, flame_range = 3)
+ new /obj/effect/gibspawner/generic(get_turf(src), src)
+ qdel(src)
+
+/mob/living/simple_animal/hostile/zombie_suicide/death(gibbed)
+ playsound(src, 'sound/creatures/szombiedeath.ogg', 60, TRUE)
+ ..()
+
+/obj/item/grenade/syndieminibomb/concussion/frag/activated
+ det_time = 30
+
+/obj/item/grenade/syndieminibomb/concussion/frag/activated/Initialize()
+ ..()
+ preprime()
+
+/mob/living/simple_animal/hostile/syndicate/factory/boss
+ name = "The Director"
+ desc = "This thing looks more like a machine than human."
+ maxHealth = 500
+ health = 500
+ icon_state = "facboss"
+ icon_living = "facboss"
+ see_in_dark = 13
+ vision_range = 12
+ aggro_vision_range = 12
+ search_objects = 1
+ minbodytemp = 0
+ speak = null
+ dodging = FALSE
+ mob_biotypes = list(MOB_ROBOTIC)
+ obj_damage = 100
+ move_force = MOVE_FORCE_EXTREMELY_STRONG
+ move_resist = MOVE_FORCE_EXTREMELY_STRONG
+ pull_force = MOVE_FORCE_EXTREMELY_STRONG
+ hardattacks = TRUE
+ melee_damage = 50
+ speed = 5
+ move_to_delay = 3
+ ranged = TRUE
+ hud_possible = list(ANTAG_HUD)
+ approaching_target = TRUE
+ ranged_ignores_vision = TRUE
+ environment_smash = ENVIRONMENT_SMASH_RWALLS
+ ranged_cooldown_time = 30
+ check_friendly_fire = 1
+ turns_per_move = 2
+ spacewalk = TRUE
+ rapid_melee = 0
+ lose_patience_timeout = 400
+ atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
+ unsuitable_atmos_damage = 0
+ attack_sound = 'sound/weapons/sonic_jackhammer.ogg'
+ projectilesound = 'sound/weapons/shotgunshot.ogg'
+ var/cooldown = 0
+ loot = list(/obj/item/gun/ballistic/shotgun/lever_action,
+ /obj/effect/mob_spawn/human/corpse/facboss)
+
+/mob/living/simple_animal/hostile/syndicate/factory/boss/Shoot()
+ var/static/list/allowed_projectile_types = list(/obj/item/ammo_casing/shotgun/beanbag,
+ /obj/item/ammo_casing/shotgun, /obj/item/ammo_casing/shotgun/incendiary,
+ /obj/item/ammo_casing/shotgun/dragonsbreath,
+ /obj/item/ammo_casing/shotgun/meteorslug,
+ /obj/item/ammo_casing/shotgun/pulseslug,
+ /obj/item/ammo_casing/shotgun/frag12,
+ /obj/item/ammo_casing/shotgun/buckshot,
+ /obj/item/ammo_casing/shotgun/rubbershot,
+ /obj/item/ammo_casing/shotgun/incapacitate,
+ /obj/item/ammo_casing/shotgun/improvised,
+ /obj/item/ammo_casing/shotgun/ion,
+ /obj/item/ammo_casing/shotgun/laserslug,
+ /obj/item/ammo_casing/shotgun/breacher)
+ casingtype = pick(allowed_projectile_types)
+ ..()
+ sleep(5)
+ playsound(get_turf(src), 'sound/weapons/shotgunpump.ogg', 50, 0, 0)
+
+/mob/living/simple_animal/hostile/syndicate/factory/boss/Aggro()
+ ..()
+ var/list/possible_sounds = list('sound/voice/ed209_20sec.ogg','sound/creatures/bosssight.ogg','sound/creatures/bosssight2.ogg','sound/voice/complionator/harry.ogg','sound/weapons/leveractionrack.ogg')
+ var/chosen_sound = pick(possible_sounds)
+ if (cooldown < world.time)
+ cooldown = world.time + 300
+ playsound(get_turf(src), chosen_sound, 80, TRUE, 0)
+ say("Target!")
+
+/mob/living/simple_animal/hostile/syndicate/factory/boss/Life()
+ ..()
+ if(health <= 300)
+ icon_state = "facboss2"
+ icon_living = "facboss2"
+ ranged_cooldown_time = 20//less health - faster shooting
+ return
+ if(health <= 150)
+ if(prob(5) && Aggro())//change to insult the target on low health
+ playsound(get_turf(src), 'sound/voice/beepsky/insult.ogg', 100, 0, 0)
+ visible_message("FUCK YOUR CUNT YOU SHIT EATING COCKSTORM AND EAT A DONG FUCKING ASS RAMMING SHIT FUCK EAT PENISES IN YOUR FUCK FACE AND SHIT OUT ABORTIONS OF FUCK AND POO AND SHIT IN YOUR ASS YOU COCK FUCK SHIT MONKEY FUCK ASS WANKER FROM THE DEPTHS OF SHIT.")
+ icon_state = "facboss3"
+ icon_living = "facboss3"
+ ranged_cooldown_time = 10//even faster
+
+/mob/living/simple_animal/hostile/syndicate/factory/boss/updatehealth()
+ ..()
+ if(health <= 300)
+ var/list/possible_sounds = list('sound/creatures/bosspain.ogg','sound/creatures/bosspain2.ogg')
+ var/chosen_sound = pick(possible_sounds)
+ playsound(get_turf(src), chosen_sound, 60, TRUE, 0)
+
+/mob/living/simple_animal/hostile/syndicate/factory/boss/death(gibbed)
+ playsound(get_turf(src), 'sound/voice/borg_deathsound.ogg', 80, TRUE, 0)
+ visible_message("\the [src] activates its self-destruct system!.")
+ speed = 15
+ move_to_delay = 20
+ ranged_cooldown = 300
+ ranged_cooldown_time = 300
+ INVOKE_ASYNC(src, .proc/explosion, src.loc, 0, 3, 4, null, null, FALSE, 2)
+ ..()
+
+//GUNS//
+
+/obj/item/gun/ballistic/shotgun/lever_action
+ name = "lever action shotgun"
+ desc = "A really old shotgun with five shell capacity. This one can fit in a backpack."
+ w_class = WEIGHT_CLASS_NORMAL
+ dual_wield_spread = 0
+ fire_sound_volume = 60 //tried on 90 my eardrums said goodbye
+ item_state = "leveraction"
+ icon_state = "leveraction"
+ rack_sound = "sound/weapons/leveractionrack.ogg"
+ fire_sound = "sound/weapons/leveractionshot.ogg"
+ vary_fire_sound = FALSE
+ rack_sound_vary = FALSE
+ recoil = 1
+ mag_type = /obj/item/ammo_box/magazine/internal/shot/lever
+ pb_knockback = 5
+
+/obj/item/gun/ballistic/shotgun/lever_action/examine(mob/user)
+ . = ..()
+ . += "You will instantly reload it after a shot if you have another hand free."
+
+/obj/item/gun/ballistic/shotgun/lever_action/shoot_live_shot(mob/living/user, pointblank = 0, atom/pbtarget = null, message = 1)
+ ..()
+ if(user.get_inactive_held_item())
+ return
+ else
+ rack()
+
+/obj/item/gun/ballistic/shotgun/lever_action/rack(mob/user = null)
+ if (user)
+ to_chat(user, "You rack the [bolt_wording] of \the [src].")
+ process_chamber(!chambered, FALSE)
+ playsound(src, rack_sound, rack_sound_volume, rack_sound_vary)
+ update_icon()
+ if(user.get_inactive_held_item() && prob(50) && chambered)
+ user.visible_message("With a single move of [user.p_their()] arm, [user] flips \the [src] and loads the chamber with a shell.")
+
+/obj/item/gun/ballistic/automatic/pistol/deagle/sound
+ desc = "A robust .50 AE handgun. This one looks even more robust."
+ rack_sound = "sound/weapons/deaglerack.ogg"
+ bolt_drop_sound = "sound/weapons/deagleslidedrop.ogg"
+ lock_back_sound = "sound/weapons/deaglelock.ogg"
+ lock_back_sound_vary = FALSE
+ rack_sound_vary = FALSE
+ load_sound_vary = FALSE
+ eject_sound_vary = FALSE
+ fire_sound = "sound/weapons/deagleshot.ogg"
+ vary_fire_sound = TRUE
+ fire_rate = 5
+ force = 18
+
+//MISC//
+
+/obj/effect/trap/nexus/trickyspawner/clowns
+ mobs = 5
+ spawned = /mob/living/simple_animal/hostile/retaliate/clown
diff --git a/code/modules/awaymissions/mission_code/centcomAway.dm b/code/modules/awaymissions/mission_code/centcomAway.dm
index 897315a3c6cc6..b785431bad1a3 100644
--- a/code/modules/awaymissions/mission_code/centcomAway.dm
+++ b/code/modules/awaymissions/mission_code/centcomAway.dm
@@ -7,32 +7,32 @@
/area/awaymission/centcomAway/general
name = "XCC-P5831"
- ambient_effects = list('sound/ambience/ambigen3.ogg')
+ ambientsounds = list('sound/ambience/ambigen3.ogg')
/area/awaymission/centcomAway/maint
name = "XCC-P5831 Maintenance"
icon_state = "away1"
- ambient_effects = list('sound/ambience/ambisin1.ogg')
+ ambientsounds = list('sound/ambience/ambisin1.ogg')
/area/awaymission/centcomAway/thunderdome
name = "XCC-P5831 Thunderdome"
icon_state = "away2"
- ambient_effects = list('sound/ambience/ambisin2.ogg')
+ ambientsounds = list('sound/ambience/ambisin2.ogg')
/area/awaymission/centcomAway/cafe
name = "XCC-P5831 Kitchen Arena"
icon_state = "away3"
- ambient_effects = list('sound/ambience/ambisin3.ogg')
+ ambientsounds = list('sound/ambience/ambisin3.ogg')
/area/awaymission/centcomAway/courtroom
name = "XCC-P5831 Courtroom"
icon_state = "away4"
- ambient_effects = list('sound/ambience/ambisin4.ogg')
+ ambientsounds = list('sound/ambience/ambisin4.ogg')
/area/awaymission/centcomAway/hangar
name = "XCC-P5831 Hangars"
icon_state = "away4"
- ambient_effects = list('sound/ambience/ambigen5.ogg')
+ ambientsounds = list('sound/ambience/ambigen5.ogg')
//centcomAway items
@@ -60,4 +60,4 @@
teams never did figure out what happened that last time... and I can't wrap my head \
around it myself. Why would a shuttle full of evacuees all snap and beat each other \
to death the moment they reached safety? \
- - D. Cereza"
+ - D. Cereza"
\ No newline at end of file
diff --git a/code/modules/awaymissions/mission_code/moonoutpost19.dm b/code/modules/awaymissions/mission_code/moonoutpost19.dm
index d45d177e25a26..0feb096910a4e 100644
--- a/code/modules/awaymissions/mission_code/moonoutpost19.dm
+++ b/code/modules/awaymissions/mission_code/moonoutpost19.dm
@@ -24,7 +24,7 @@
power_equip = FALSE
power_light = FALSE
poweralm = FALSE
- ambient_effects = list('sound/ambience/ambimine.ogg')
+ ambientsounds = list('sound/ambience/ambimine.ogg')
icon_state = "awaycontent5"
/area/awaymission/moonoutpost19/hive
diff --git a/code/modules/awaymissions/mission_code/snowdin.dm b/code/modules/awaymissions/mission_code/snowdin.dm
index 5c7f4b26bf4ac..cfdd9fa082cb3 100644
--- a/code/modules/awaymissions/mission_code/snowdin.dm
+++ b/code/modules/awaymissions/mission_code/snowdin.dm
@@ -135,30 +135,18 @@
/area/shuttle/snowdin/elevator2
name = "Mining Elevator"
-//shuttle console for elevators//
-
-/obj/machinery/computer/shuttle/snowdin/mining
- name = "shuttle console"
- desc = "A shuttle control computer."
- icon_screen = "shuttle"
- icon_keyboard = "tech_key"
- light_color = LIGHT_COLOR_CYAN
- shuttleId = "snowdin_mining"
- possible_destinations = "snowdin_mining_top;snowdin_mining_down"
-
-
//liquid plasma!!!!!!//
/turf/open/floor/plasteel/dark/snowdin
initial_gas_mix = FROZEN_ATMOS
planetary_atmos = 1
- temperature = 180
+ initial_temperature = 180
/turf/open/lava/plasma
name = "liquid plasma"
desc = "A flowing stream of chilled liquid plasma. You probably shouldn't get in."
icon_state = "liquidplasma"
- initial_gas_mix = "o2=0;n2=82;plasma=24;TEMP=120"
+ initial_gas_mix = "n2=82;plasma=24;TEMP=120"
baseturfs = /turf/open/lava/plasma
slowdown = 2
@@ -219,7 +207,7 @@
var/list/robo_parts = list()//keep a reference of robotic parts so we know if we can turn them into a plasmaman
var/mob/living/carbon/human/PP = L
var/S = PP.dna.species
- if(istype(S, /datum/species/plasmaman) || istype(S, /datum/species/android) || istype(S, /datum/species/synth)) //ignore plasmamen/robotic species
+ if(istype(S, /datum/species/plasmaman) || istype(S, /datum/species/android)) //ignore plasmamen/robotic species
continue
for(var/BP in PP.bodyparts)
@@ -516,11 +504,11 @@
/obj/effect/spawner/lootdrop/snowdin/dungeonheavy
name = "dungeon heavy"
- loot = list(/obj/item/twohanded/singularityhammer = 25,
- /obj/item/twohanded/mjollnir = 10,
- /obj/item/twohanded/fireaxe = 25,
+ loot = list(/obj/item/singularityhammer = 25,
+ /obj/item/mjollnir = 10,
+ /obj/item/fireaxe = 25,
/obj/item/organ/brain/alien = 17,
- /obj/item/twohanded/dualsaber = 15,
+ /obj/item/dualsaber = 15,
/obj/item/organ/heart/demon = 7,
/obj/item/gun/ballistic/automatic/c20r/unrestricted = 16,
/obj/item/gun/magic/wand/resurrection/inert = 15,
@@ -541,7 +529,7 @@
loot = list(/obj/item/stack/sheet/mineral/snow{amount = 25} = 10,
/obj/item/toy/snowball = 15,
/obj/item/shovel = 10,
- /obj/item/twohanded/spear = 8,
+ /obj/item/spear = 8,
)
//special items//--
@@ -555,7 +543,7 @@
/obj/item/clothing/under/syndicate/coldres
name = "insulated tactical turtleneck"
desc = "A nondescript and slightly suspicious-looking turtleneck with digital camouflage cargo pants. The interior has been padded with special insulation for both warmth and protection."
- armor = list("melee" = 20, "bullet" = 10, "laser" = 0,"energy" = 5, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 25, "acid" = 25)
+ armor = list("melee" = 20, "bullet" = 10, "laser" = 0,"energy" = 5, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 25, "acid" = 25, "stamina" = 30)
cold_protection = CHEST|GROIN|ARMS|LEGS
min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT
diff --git a/code/modules/awaymissions/mission_code/spacebattle.dm b/code/modules/awaymissions/mission_code/spacebattle.dm
index a477a223b2dc0..11d99108a5fc2 100644
--- a/code/modules/awaymissions/mission_code/spacebattle.dm
+++ b/code/modules/awaymissions/mission_code/spacebattle.dm
@@ -6,7 +6,7 @@
requires_power = FALSE
/area/awaymission/spacebattle/cruiser
- name = "Nanotrasen Cruiser"
+ name = "\improper Nanotrasen Cruiser"
icon_state = "awaycontent2"
/area/awaymission/spacebattle/syndicate1
diff --git a/code/modules/awaymissions/mission_code/stationCollision.dm b/code/modules/awaymissions/mission_code/stationCollision.dm
index 0adef7f0b4b08..19c1bad75b764 100644
--- a/code/modules/awaymissions/mission_code/stationCollision.dm
+++ b/code/modules/awaymissions/mission_code/stationCollision.dm
@@ -112,7 +112,7 @@ GLOBAL_VAR_INIT(sc_safecode5, "[rand(0,9)]")
info = {"Target: Research-station Epsilon Objective: Prototype weaponry. The captain likely keeps them locked in her safe.
- Our on-board spy has learned the code and has hidden away a few copies of the code around the station. Unfortunatly he has been captured by security
+ Our on-board spy has learned the code and has hidden away a few copies of the code around the station. Unfortunately he has been captured by security
Your objective is to split up, locate any of the papers containing the captain's safe code, open the safe and
secure anything found inside. If possible, recover the imprisioned syndicate operative and receive the code from him.
@@ -148,9 +148,9 @@ GLOBAL_VAR_INIT(sc_safecode5, "[rand(0,9)]")
/obj/singularity/narsie/mini/admin_investigate_setup()
return
-/obj/singularity/narsie/mini/process()
+/obj/singularity/narsie/mini/process(delta_time)
eat()
- if(prob(25))
+ if(DT_PROB(13, delta_time))
mezzer()
/obj/singularity/narsie/mini/ex_act()
diff --git a/code/modules/awaymissions/mission_code/wildwest.dm b/code/modules/awaymissions/mission_code/wildwest.dm
index 8f216506eacec..7a42a05c04f46 100644
--- a/code/modules/awaymissions/mission_code/wildwest.dm
+++ b/code/modules/awaymissions/mission_code/wildwest.dm
@@ -105,7 +105,7 @@
if("Immortality")
to_chat(user, "Your wish is granted, but at a terrible cost...")
to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.")
- user.verbs += /mob/living/carbon/proc/immortality
+ user.add_verb(/mob/living/carbon/proc/immortality)
user.set_species(/datum/species/shadow)
if("Peace")
to_chat(user, "Whatever alien sentience that the Wish Granter possesses is satisfied with your wish. There is a distant wailing as the last of the Faithless begin to die, then silence.")
diff --git a/code/modules/awaymissions/zlevel.dm b/code/modules/awaymissions/zlevel.dm
index 79f61f352bb14..47f783cbb3828 100644
--- a/code/modules/awaymissions/zlevel.dm
+++ b/code/modules/awaymissions/zlevel.dm
@@ -12,7 +12,7 @@ GLOBAL_LIST_INIT(potentialRandomZlevels, generateMapList(filename = "[global.con
to_chat(world, "Away mission loaded.")
/proc/reset_gateway_spawns(reset = FALSE)
- for(var/obj/machinery/gateway/G in world)
+ for(var/obj/machinery/gateway/G in GLOB.machines)
if(reset)
G.randomspawns = GLOB.awaydestinations
else
diff --git a/code/modules/badges/badges.dm b/code/modules/badges/badges.dm
new file mode 100644
index 0000000000000..b708ded074943
--- /dev/null
+++ b/code/modules/badges/badges.dm
@@ -0,0 +1,89 @@
+/*
+
+Loads all badges from the badge.json file.
+Format should be
+rank|icon
+Where:
+Rank - The name of the admin rank
+Icon - The name of the icon state in the badges.dmi file
+
+Special ranks:
+- Donator: Matches clients in the donator list
+- Mentor: Matches clients in the mentor list
+
+*/
+
+//Global list of badges
+GLOBAL_LIST_EMPTY(badge_data)
+
+/client
+ var/list/cached_badges = null
+
+//Loads the badge ranks
+/proc/load_badge_ranks()
+ //No badges
+ if(!CONFIG_GET(flag/badges))
+ return
+ //Load and parse data
+ GLOB.badge_data = json_decode(rustg_file_read("[global.config.directory]/badges.json"))
+ //Associate badges with admin ranks
+ for(var/datum/admin_rank/rank as() in GLOB.admin_ranks)
+ rank.badge_icon = GLOB.badge_data[rank.name]
+ //Yay
+ log_game("[LAZYLEN(GLOB.badge_data)] badges loaded successfully.")
+ //Reset everyones badges so they get reloaded.
+ for(var/client/C as() in GLOB.clients)
+ C.reset_badges()
+
+//Gets the badges attached to a client.
+/client/proc/get_badges()
+ //No badges
+ if(!CONFIG_GET(flag/badges))
+ return
+ //Send cached badges
+ if(islist(cached_badges))
+ return cached_badges
+ var/list/badges = list()
+ //Add the holder rank
+ if(holder)
+ //No badges when deadminned / fakeminned
+ if(holder.deadmined || holder.fakekey)
+ cached_badges = list()
+ return list()
+ //Admin badge otherwise
+ if(holder?.rank?.badge_icon)
+ badges += holder.rank.badge_icon
+ //Add the mentor rank
+ else
+ if(mentor_datum && GLOB.badge_data["Mentor"])
+ badges += GLOB.badge_data["Mentor"]
+ //Add the donator rank
+ if(IS_PATRON(ckey) && GLOB.badge_data["Donator"])
+ badges += GLOB.badge_data["Donator"]
+ cached_badges = badges
+ return badges
+
+/client/proc/reset_badges()
+ cached_badges = null
+
+/proc/badge_parse(badges)
+ if(!LAZYLEN(badges))
+ return ""
+
+ var/output = ""
+ var/first_badge = TRUE
+
+ if(!CONFIG_GET(flag/badges))
+ return ""
+
+ for(var/badge in badges)
+ var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/chat)
+ var/tag = sheet.icon_tag("badge-badge_[badge]")
+ if(tag)
+ if(first_badge)
+ output = "[output][tag]"
+ first_badge = FALSE
+ else
+ output = "[output] [tag]"
+
+ return "[output] "
diff --git a/code/modules/buildmode/buildmode.dm b/code/modules/buildmode/buildmode.dm
index b232bd212c643..e8490837c3d31 100644
--- a/code/modules/buildmode/buildmode.dm
+++ b/code/modules/buildmode/buildmode.dm
@@ -17,10 +17,10 @@
var/switch_state = BM_SWITCHSTATE_NONE
var/switch_width = 5
// modeswitch UI
- var/obj/screen/buildmode/mode/modebutton
+ var/atom/movable/screen/buildmode/mode/modebutton
var/list/modeswitch_buttons = list()
// dirswitch UI
- var/obj/screen/buildmode/bdir/dirbutton
+ var/atom/movable/screen/buildmode/bdir/dirbutton
var/list/dirswitch_buttons = list()
/datum/buildmode/New(client/c)
@@ -34,7 +34,7 @@
holder.screen += buttons
holder.click_intercept = src
mode.enter_mode(src)
-
+
/datum/buildmode/proc/quit()
mode.exit_mode(src)
holder.screen -= buttons
@@ -45,8 +45,10 @@
/datum/buildmode/Destroy()
close_switchstates()
holder.player_details.post_login_callbacks -= li_cb
+ li_cb = null
holder = null
QDEL_NULL(mode)
+ QDEL_LIST(buttons)
QDEL_LIST(modeswitch_buttons)
QDEL_LIST(dirswitch_buttons)
return ..()
@@ -63,16 +65,16 @@
/datum/buildmode/proc/create_buttons()
// keep a reference so we can update it upon mode switch
- modebutton = new /obj/screen/buildmode/mode(src)
+ modebutton = new /atom/movable/screen/buildmode/mode(src)
buttons += modebutton
- buttons += new /obj/screen/buildmode/help(src)
+ buttons += new /atom/movable/screen/buildmode/help(src)
// keep a reference so we can update it upon dir switch
- dirbutton = new /obj/screen/buildmode/bdir(src)
+ dirbutton = new /atom/movable/screen/buildmode/bdir(src)
buttons += dirbutton
- buttons += new /obj/screen/buildmode/quit(src)
+ buttons += new /atom/movable/screen/buildmode/quit(src)
// build the lists of switching buttons
- build_options_grid(subtypesof(/datum/buildmode_mode), modeswitch_buttons, /obj/screen/buildmode/modeswitch)
- build_options_grid(list(SOUTH,EAST,WEST,NORTH,NORTHWEST), dirswitch_buttons, /obj/screen/buildmode/dirswitch)
+ build_options_grid(subtypesof(/datum/buildmode_mode), modeswitch_buttons, /atom/movable/screen/buildmode/modeswitch)
+ build_options_grid(list(SOUTH,EAST,WEST,NORTH,NORTHWEST), dirswitch_buttons, /atom/movable/screen/buildmode/dirswitch)
// this creates a nice offset grid for choosing between buildmode options,
// because going "click click click ah hell" sucks.
@@ -81,7 +83,7 @@
for(var/thing in elements)
var/x = pos_idx % switch_width
var/y = FLOOR(pos_idx / switch_width, 1)
- var/obj/screen/buildmode/B = new buttontype(src, thing)
+ var/atom/movable/screen/buildmode/B = new buttontype(src, thing)
// extra .5 for a nice offset look
B.screen_loc = "NORTH-[(1 + 0.5 + y*1.5)],WEST+[0.5 + x*1.5]"
buttonslist += B
@@ -100,7 +102,7 @@
else
close_switchstates()
open_modeswitch()
-
+
/datum/buildmode/proc/open_modeswitch()
switch_state = BM_SWITCHSTATE_MODE
holder.screen += modeswitch_buttons
@@ -115,7 +117,7 @@
else
close_switchstates()
open_dirswitch()
-
+
/datum/buildmode/proc/open_dirswitch()
switch_state = BM_SWITCHSTATE_DIR
holder.screen += dirswitch_buttons
@@ -155,7 +157,7 @@
new /datum/buildmode(M.client)
message_admins("[key_name_admin(usr)] has entered build mode.")
log_admin("[key_name(usr)] has entered build mode.")
-
+
#undef BM_SWITCHSTATE_NONE
#undef BM_SWITCHSTATE_MODE
-#undef BM_SWITCHSTATE_DIR
\ No newline at end of file
+#undef BM_SWITCHSTATE_DIR
diff --git a/code/modules/buildmode/buttons.dm b/code/modules/buildmode/buttons.dm
index e72dbde064382..e2bfec25bc5e4 100644
--- a/code/modules/buildmode/buttons.dm
+++ b/code/modules/buildmode/buttons.dm
@@ -1,23 +1,23 @@
-/obj/screen/buildmode
+/atom/movable/screen/buildmode
icon = 'icons/misc/buildmode.dmi'
var/datum/buildmode/bd
// If we don't do this, we get occluded by item action buttons
layer = ABOVE_HUD_LAYER
-/obj/screen/buildmode/New(bld)
+/atom/movable/screen/buildmode/New(bld)
bd = bld
return ..()
-/obj/screen/buildmode/Destroy()
+/atom/movable/screen/buildmode/Destroy()
bd = null
return ..()
-/obj/screen/buildmode/mode
+/atom/movable/screen/buildmode/mode
name = "Toggle Mode"
icon_state = "buildmode_basic"
screen_loc = "NORTH,WEST"
-/obj/screen/buildmode/mode/Click(location, control, params)
+/atom/movable/screen/buildmode/mode/Click(location, control, params)
var/list/pa = params2list(params)
if(pa.Find("left"))
@@ -27,64 +27,64 @@
update_icon()
return 1
-/obj/screen/buildmode/mode/update_icon()
+/atom/movable/screen/buildmode/mode/update_icon()
icon_state = bd.mode.get_button_iconstate()
-/obj/screen/buildmode/help
+/atom/movable/screen/buildmode/help
icon_state = "buildhelp"
screen_loc = "NORTH,WEST+1"
name = "Buildmode Help"
-/obj/screen/buildmode/help/Click(location, control, params)
+/atom/movable/screen/buildmode/help/Click(location, control, params)
bd.mode.show_help(usr.client)
return 1
-/obj/screen/buildmode/bdir
+/atom/movable/screen/buildmode/bdir
icon_state = "build"
screen_loc = "NORTH,WEST+2"
name = "Change Dir"
-/obj/screen/buildmode/bdir/update_icon()
+/atom/movable/screen/buildmode/bdir/update_icon()
dir = bd.build_dir
return
-/obj/screen/buildmode/bdir/Click()
+/atom/movable/screen/buildmode/bdir/Click()
bd.toggle_dirswitch()
update_icon()
return 1
// used to switch between modes
-/obj/screen/buildmode/modeswitch
+/atom/movable/screen/buildmode/modeswitch
var/datum/buildmode_mode/modetype
-/obj/screen/buildmode/modeswitch/New(bld, mt)
+/atom/movable/screen/buildmode/modeswitch/New(bld, mt)
modetype = mt
icon_state = "buildmode_[initial(modetype.key)]"
name = initial(modetype.key)
return ..(bld)
-/obj/screen/buildmode/modeswitch/Click()
+/atom/movable/screen/buildmode/modeswitch/Click()
bd.change_mode(modetype)
return 1
// used to switch between dirs
-/obj/screen/buildmode/dirswitch
+/atom/movable/screen/buildmode/dirswitch
icon_state = "build"
-/obj/screen/buildmode/dirswitch/New(bld, dir)
+/atom/movable/screen/buildmode/dirswitch/New(bld, dir)
src.dir = dir
name = dir2text(dir)
return ..(bld)
-/obj/screen/buildmode/dirswitch/Click()
+/atom/movable/screen/buildmode/dirswitch/Click()
bd.change_dir(dir)
return 1
-/obj/screen/buildmode/quit
+/atom/movable/screen/buildmode/quit
icon_state = "buildquit"
screen_loc = "NORTH,WEST+3"
name = "Quit Buildmode"
-/obj/screen/buildmode/quit/Click()
+/atom/movable/screen/buildmode/quit/Click()
bd.quit()
- return 1
\ No newline at end of file
+ return 1
diff --git a/code/modules/buildmode/submodes/delete.dm b/code/modules/buildmode/submodes/delete.dm
new file mode 100644
index 0000000000000..cbd93851577b6
--- /dev/null
+++ b/code/modules/buildmode/submodes/delete.dm
@@ -0,0 +1,63 @@
+/datum/buildmode_mode/delete
+ key = "delete"
+
+/datum/buildmode_mode/delete/show_help(client/c)
+ to_chat(c, "***********************************************************\n\
+ Left Mouse Button on anything to delete it. If you break it, you buy it.\n\
+ Right Mouse Button on anything to delete everything of the type. Probably don\'t do this unless you know what you are doing.\n\
+ ***********************************************************")
+
+/datum/buildmode_mode/delete/handle_click(client/c, params, object)
+ var/list/pa = params2list(params)
+ var/left_click = pa.Find("left")
+ var/right_click = pa.Find("right")
+
+ if(left_click)
+ if(isturf(object))
+ var/turf/T = object
+ T.ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
+ else if(isatom(object))
+ qdel(object)
+
+ if(right_click)
+ if(!check_rights(R_DEBUG|R_SERVER)) //Prevents buildmoded non-admins from breaking everything.
+ return
+ if(isturf(object))
+ return
+ var/atom/deleting = object
+ var/action_type = alert(usr,"Strict type ([deleting.type]) or type and all subtypes?","Strict type","Type and subtypes","Cancel")
+ if(action_type == "Cancel" || !action_type)
+ return
+
+ if(alert(usr,"Are you really sure you want to delete all instances of type [deleting.type]?","Yes","No") != "Yes")
+ return
+
+ if(alert(usr,"Second confirmation required. Delete?","Yes","No") != "Yes")
+ return
+
+ var/O_type = deleting.type
+ switch(action_type)
+ if("Strict type")
+ var/i = 0
+ for(var/atom/Obj in world)
+ if(Obj.type == O_type)
+ i++
+ qdel(Obj)
+ CHECK_TICK
+ if(!i)
+ to_chat(usr, "No instances of this type exist")
+ return
+ log_admin("[key_name(usr)] deleted all instances of type [O_type] ([i] instances deleted) ")
+ message_admins("[key_name(usr)] deleted all instances of type [O_type] ([i] instances deleted)")
+ if("Type and subtypes")
+ var/i = 0
+ for(var/Obj in world)
+ if(istype(Obj,O_type))
+ i++
+ qdel(Obj)
+ CHECK_TICK
+ if(!i)
+ to_chat(usr, "No instances of this type exist")
+ return
+ log_admin("[key_name(usr)] deleted all instances of type or subtype of [O_type] ([i] instances deleted) ")
+ message_admins("[key_name(usr)] deleted all instances of type or subtype of [O_type] ([i] instances deleted) ")
diff --git a/code/modules/buildmode/submodes/outfit.dm b/code/modules/buildmode/submodes/outfit.dm
new file mode 100644
index 0000000000000..5d57582680ccd
--- /dev/null
+++ b/code/modules/buildmode/submodes/outfit.dm
@@ -0,0 +1,45 @@
+/datum/buildmode_mode/outfit
+ key = "outfit"
+ var/datum/outfit/dressuptime
+
+/datum/buildmode_mode/outfit/Destroy()
+ dressuptime = null
+ return ..()
+
+/datum/buildmode_mode/outfit/show_help(client/c)
+ to_chat(c, "***********************************************************\n\
+ Right Mouse Button on buildmode button = Select outfit to equip.\n\
+ Left Mouse Button on mob/living/carbon/human = Equip the selected outfit.\n\
+ Right Mouse Button on mob/living/carbon/human = Strip and delete current outfit.\n\
+ ***********************************************************")
+
+/datum/buildmode_mode/outfit/Reset()
+ . = ..()
+ dressuptime = null
+
+/datum/buildmode_mode/outfit/change_settings(client/c)
+ dressuptime = c.robust_dress_shop()
+
+/datum/buildmode_mode/outfit/handle_click(client/c, params, object)
+ var/list/pa = params2list(params)
+ var/left_click = pa.Find("left")
+ var/right_click = pa.Find("right")
+
+
+ if(!ishuman(object))
+ return
+ var/mob/living/carbon/human/dollie = object
+
+ if(left_click)
+ if(isnull(dressuptime))
+ to_chat(c, ("Pick an outfit first."))
+ return
+
+ for (var/item in dollie.get_equipped_items(TRUE))
+ qdel(item)
+ if(dressuptime != "Naked")
+ dollie.equipOutfit(dressuptime)
+
+ if(right_click)
+ for (var/item in dollie.get_equipped_items(TRUE))
+ qdel(item)
diff --git a/code/modules/cargo/bounties/assistant.dm b/code/modules/cargo/bounties/assistant.dm
index e0c423a164073..9263cea75f81e 100644
--- a/code/modules/cargo/bounties/assistant.dm
+++ b/code/modules/cargo/bounties/assistant.dm
@@ -35,7 +35,7 @@
description = "CentCom's security forces are going through budget cuts. You will be paid if you ship a set of spears."
reward = 2000
required_count = 5
- wanted_types = list(/obj/item/twohanded/spear)
+ wanted_types = list(/obj/item/spear)
/datum/bounty/item/assistant/toolbox
name = "Toolboxes"
@@ -124,13 +124,6 @@
required_count = 5
wanted_types = list(/obj/structure/chair/comfy)
-/datum/bounty/item/assistant/geranium
- name = "Geraniums"
- description = "Commander Zot has the hots for Commander Zena. Send a shipment of geraniums - her favorite flower - and he'll happily reward you."
- reward = 4000
- required_count = 3
- wanted_types = list(/obj/item/reagent_containers/food/snacks/grown/poppy/geranium)
-
/datum/bounty/item/assistant/poppy
name = "Poppies"
description = "Commander Zot really wants to sweep Security Officer Olivia off her feet. Send a shipment of Poppies - her favorite flower - and he'll happily reward you."
@@ -150,7 +143,7 @@
description = "Central Command is looking to commission a new BirdBoat-class station. You've been ordered to supply the potted plants."
reward = 2000
required_count = 8
- wanted_types = list(/obj/item/twohanded/required/kirbyplants)
+ wanted_types = list(/obj/item/kirbyplants)
/datum/bounty/item/assistant/earmuffs
name = "Earmuffs"
@@ -176,7 +169,7 @@
name = "Chainsaw"
description = "The chef at CentCom is having trouble butchering her animals. She requests one chainsaw, please."
reward = 2500
- wanted_types = list(/obj/item/twohanded/required/chainsaw)
+ wanted_types = list(/obj/item/chainsaw)
/datum/bounty/item/assistant/ied
name = "IED"
diff --git a/code/modules/cargo/bounties/botany.dm b/code/modules/cargo/bounties/botany.dm
index 76e4bcf252f6a..294278405625f 100644
--- a/code/modules/cargo/bounties/botany.dm
+++ b/code/modules/cargo/bounties/botany.dm
@@ -3,12 +3,15 @@
var/datum/bounty/item/botany/multiplier = 0 //adds bonus reward money; increased for higher tier or rare mutations
var/datum/bounty/item/botany/bonus_desc //for adding extra flavor text to bounty descriptions
var/datum/bounty/item/botany/foodtype = "meal" //same here
+ var/datum/bounty/item/botany/format_exception = FALSE //Set to true if the bounty uses a custom format from the one below
/datum/bounty/item/botany/New()
..()
- description = "Central Command's head chef is looking to prepare a fine [foodtype] with [name]. [bonus_desc]"
- reward += multiplier * 1000
- required_count = rand(5, 10)
+
+ if (format_exception == FALSE)
+ description = "Central Command's head chef is looking to prepare a fine [foodtype] with [name]. [bonus_desc]"
+ reward += multiplier * 1000
+ required_count = rand(5, 10)
/datum/bounty/item/botany/ambrosia_vulgaris
name = "Ambrosia Vulgaris Leaves"
@@ -199,3 +202,27 @@
multiplier = 2
foodtype = "batch of oatmeal"
bonus_desc = "Squats and oats. We're all out of oats."
+
+/datum/bounty/item/botany/forgetmenot
+ name = "Forget-Me-Nots"
+ description = "Commander Zot has his eyes on Quartermaster Maya. Send a shipment of forget-me-nots - her favorite flower - and he'll happily reward you."
+ reward = 7000
+ required_count = 3
+ wanted_types = list(/obj/item/reagent_containers/food/snacks/grown/poppy/geranium/forgetmenot)
+ format_exception = TRUE
+
+/datum/bounty/item/botany/geranium
+ name = "Geraniums"
+ description = "Commander Zot has the hots for Commander Zena. Send a shipment of geraniums - her favorite flower - and he'll happily reward you."
+ reward = 6000
+ required_count = 3
+ wanted_types = list(/obj/item/reagent_containers/food/snacks/grown/poppy/geranium)
+ format_exception = TRUE
+
+/datum/bounty/item/botany/rainbowflowercrown
+ name = "Rainbow Flower Crowns"
+ description = "Central Command is concerned about their intern suicide rate. A shipment of rainbow flower crowns should do nicely to improve morale."
+ reward = 10000
+ required_count = 3
+ wanted_types = list(/obj/item/clothing/head/rainbowbunchcrown)
+ format_exception = TRUE
\ No newline at end of file
diff --git a/code/modules/cargo/bounties/engineering.dm b/code/modules/cargo/bounties/engineering.dm
index e31952e416e4d..8b3ebf629f592 100644
--- a/code/modules/cargo/bounties/engineering.dm
+++ b/code/modules/cargo/bounties/engineering.dm
@@ -4,7 +4,7 @@
reward = 7500
wanted_types = list(/obj/item/tank)
var/moles_required = 20 // A full tank is 28 moles, but CentCom ignores that fact.
- var/gas_type = /datum/gas/pluoxium
+ var/gas_type = GAS_PLUOXIUM
/datum/bounty/item/engineering/gas/applies_to(obj/O)
if(!..())
@@ -15,21 +15,15 @@
/datum/bounty/item/engineering/gas/nitryl_tank
name = "Full Tank of Nitryl"
description = "The non-human staff of Station 88 has been volunteered to test performance enhancing drugs. Ship them a tank full of Nitryl so they can get started."
- gas_type = /datum/gas/nitryl
+ gas_type = GAS_NITRYL
/datum/bounty/item/engineering/gas/tritium_tank
name = "Full Tank of Tritium"
description = "Station 49 is looking to kickstart their research program. Ship them a tank full of Tritium."
- gas_type = /datum/gas/tritium
+ gas_type = GAS_TRITIUM
/datum/bounty/item/engineering/energy_ball
name = "Contained Tesla Ball"
description = "Station 24 is being overrun by hordes of angry Mothpeople. They are requesting the ultimate bug zapper."
reward = 75000 //requires 14k credits of purchases, not to mention cooperation with engineering/heads of staff to set up inside the cramped shuttle
wanted_types = list(/obj/singularity/energy_ball)
-
-/datum/bounty/item/engineering/energy_ball/applies_to(obj/O)
- if(!..())
- return FALSE
- var/obj/singularity/energy_ball/T = O
- return !T.miniball
diff --git a/code/modules/cargo/bounties/mining.dm b/code/modules/cargo/bounties/mining.dm
index e68144e1a134e..6761ffa2a57e4 100644
--- a/code/modules/cargo/bounties/mining.dm
+++ b/code/modules/cargo/bounties/mining.dm
@@ -22,7 +22,7 @@
name = "Bone Axe"
description = "Station 12 has had their fire axes stolen by marauding clowns. Ship them a bone axe as a replacement."
reward = 7500
- wanted_types = list(/obj/item/twohanded/fireaxe/boneaxe)
+ wanted_types = list(/obj/item/fireaxe/boneaxe)
/datum/bounty/item/mining/bone_bow
name = "Bone Bow"
diff --git a/code/modules/cargo/bounties/reagent.dm b/code/modules/cargo/bounties/reagent.dm
index d2a0084ebec0d..209f959578ce0 100644
--- a/code/modules/cargo/bounties/reagent.dm
+++ b/code/modules/cargo/bounties/reagent.dm
@@ -175,7 +175,6 @@
/datum/reagent/consumable/frostoil,\
/datum/reagent/toxin/slimejelly,\
/datum/reagent/teslium/energized_jelly,\
- /datum/reagent/toxin/skewium,\
/datum/reagent/toxin/mimesbane,\
/datum/reagent/medicine/strange_reagent,\
/datum/reagent/nitroglycerin,\
diff --git a/code/modules/cargo/bounties/science.dm b/code/modules/cargo/bounties/science.dm
index 33f334ac47265..83a069724a5e7 100644
--- a/code/modules/cargo/bounties/science.dm
+++ b/code/modules/cargo/bounties/science.dm
@@ -8,7 +8,7 @@
name = "Trash Bag of Holding"
description = "Nanotrasen would make good use of high-capacity trash bags. If you have any, please ship them."
reward = 10000
- wanted_types = list(/obj/item/storage/backpack/holding)
+ wanted_types = list(/obj/item/storage/bag/trash/bluespace)
/datum/bounty/item/science/bluespace_syringe
name = "Bluespace Syringe"
diff --git a/code/modules/cargo/bounties/special.dm b/code/modules/cargo/bounties/special.dm
index cbbf36c671023..af61a6698e445 100644
--- a/code/modules/cargo/bounties/special.dm
+++ b/code/modules/cargo/bounties/special.dm
@@ -28,7 +28,7 @@
/datum/bounty/item/trash
name = "Trash"
- description = "Recently a group of janitors have run out of trash to clean up, without any trash Centcom wants to fire them to cut costs. Send a shipment of trash to keep them employed, and they'll give you a small compensation."
+ description = "Recently a group of janitors have run out of trash to clean up, without any trash CentCom wants to fire them to cut costs. Send a shipment of trash to keep them employed, and they'll give you a small compensation."
reward = 1000
required_count = 10
wanted_types = list(/obj/item/trash)
diff --git a/code/modules/cargo/bounties/virus.dm b/code/modules/cargo/bounties/virus.dm
index 8f078a2668bd5..13a51281fd008 100644
--- a/code/modules/cargo/bounties/virus.dm
+++ b/code/modules/cargo/bounties/virus.dm
@@ -56,26 +56,26 @@
/datum/bounty/virus/resistance/accepts_virus(V)
var/datum/disease/advance/A = V
- return A.totalResistance() == stat_value
+ return A.resistance == stat_value
/datum/bounty/virus/stage_speed
stat_name = "stage speed"
/datum/bounty/virus/stage_speed/accepts_virus(V)
var/datum/disease/advance/A = V
- return A.totalStageSpeed() == stat_value
+ return A.stage_rate == stat_value
/datum/bounty/virus/stealth
stat_name = "stealth"
/datum/bounty/virus/stealth/accepts_virus(V)
var/datum/disease/advance/A = V
- return A.totalStealth() == stat_value
+ return A.stealth == stat_value
/datum/bounty/virus/transmit
stat_name = "transmissible"
/datum/bounty/virus/transmit/accepts_virus(V)
var/datum/disease/advance/A = V
- return A.totalTransmittable() == stat_value
+ return A.transmission == stat_value
diff --git a/code/modules/cargo/bounty.dm b/code/modules/cargo/bounty.dm
index 7bfd9eabe8305..e5ad56382378a 100644
--- a/code/modules/cargo/bounty.dm
+++ b/code/modules/cargo/bounty.dm
@@ -13,7 +13,7 @@ GLOBAL_LIST_EMPTY(bounties_list)
// Displayed on bounty UI screen.
/datum/bounty/proc/reward_string()
- return "[reward] Credits"
+ return "[reward * SSeconomy.bounty_modifier] Credits"
/datum/bounty/proc/can_claim()
return !claimed
@@ -21,9 +21,7 @@ GLOBAL_LIST_EMPTY(bounties_list)
// Called when the claim button is clicked. Override to provide fancy rewards.
/datum/bounty/proc/claim()
if(can_claim())
- var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR)
- if(D)
- D.adjust_money(reward)
+ SSeconomy.distribute_funds(reward * SSeconomy.bounty_modifier * 3)
claimed = TRUE
// If an item sent in the cargo shuttle can satisfy the bounty.
diff --git a/code/modules/cargo/bounty_console.dm b/code/modules/cargo/bounty_console.dm
index 0135fb714e21f..a6a2d55bdcfb0 100644
--- a/code/modules/cargo/bounty_console.dm
+++ b/code/modules/cargo/bounty_console.dm
@@ -1,16 +1,18 @@
#define PRINTER_TIMEOUT 10
/obj/machinery/computer/bounty
- name = "Nanotrasen bounty console"
+ name = "\improper Nanotrasen bounty console"
desc = "Used to check and claim bounties offered by Nanotrasen"
icon_screen = "bounty"
circuit = /obj/item/circuitboard/computer/bounty
light_color = "#E2853D"//orange
var/printer_ready = 0 //cooldown var
+ var/static/datum/bank_account/cargocash
/obj/machinery/computer/bounty/Initialize()
. = ..()
printer_ready = world.time + PRINTER_TIMEOUT
+ cargocash = SSeconomy.get_dep_account(ACCOUNT_CAR)
/obj/machinery/computer/bounty/proc/print_paper()
new /obj/item/paper/bounty_printout(loc)
@@ -30,67 +32,36 @@
info += "
"
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "CargoBountyConsole")
+ ui.set_autoupdate(FALSE)
+ ui.open()
- var/datum/browser/popup = new(user, "bounties", "Nanotrasen Bounties", 700, 600)
- popup.set_content(dat)
- popup.open()
+/obj/machinery/computer/bounty/ui_data(mob/user)
+ var/list/data = list()
+ var/list/bountyinfo = list()
+ for(var/datum/bounty/B in GLOB.bounties_list)
+ bountyinfo += list(list("name" = B.name, "description" = B.description, "reward_string" = B.reward_string(), "completion_string" = B.completion_string() , "claimed" = B.claimed, "can_claim" = B.can_claim(), "priority" = B.high_priority, "bounty_ref" = REF(B)))
+ data["stored_cash"] = cargocash.account_balance
+ data["bountydata"] = bountyinfo
+ return data
-/obj/machinery/computer/bounty/Topic(href, href_list)
+/obj/machinery/computer/bounty/ui_act(action, params)
if(..())
return
-
- switch(href_list["choice"])
+ switch(action)
+ if("ClaimBounty")
+ var/datum/bounty/cashmoney = locate(params["bounty"]) in GLOB.bounties_list
+ if(cashmoney)
+ cashmoney.claim()
+ return TRUE
if("Print")
if(printer_ready < world.time)
printer_ready = world.time + PRINTER_TIMEOUT
print_paper()
-
- if("Claim")
- var/datum/bounty/B = locate(href_list["d_rec"]) in GLOB.bounties_list
- if(B)
- B.claim()
-
- if(href_list["refresh"])
- playsound(src, "terminal_type", 25, 0)
-
- updateUsrDialog()
-
+ return
diff --git a/code/modules/cargo/centcom_podlauncher.dm b/code/modules/cargo/centcom_podlauncher.dm
index 51eacc2a0a8f5..0f26fd2cddeea 100644
--- a/code/modules/cargo/centcom_podlauncher.dm
+++ b/code/modules/cargo/centcom_podlauncher.dm
@@ -1,3 +1,10 @@
+#define TAB_POD 0 //Used to check if the UIs built in camera is looking at the pod
+#define TAB_BAY 1 //Used to check if the UIs built in camera is looking at the launch bay area
+
+#define LAUNCH_ALL 0 //Used to check if we're launching everything from the bay area at once
+#define LAUNCH_ORDERED 1 //Used to check if we're launching everything from the bay area in order
+#define LAUNCH_RANDOM 2 //Used to check if we're launching everything from the bay area randomly
+
//The Great and Mighty CentCom Pod Launcher - MrDoomBringer
//This was originally created as a way to get adminspawned items to the station in an IC manner. It's evolved to contain a few more
//features such as item removal, smiting, controllable delivery mobs, and more.
@@ -11,20 +18,23 @@
/client/proc/centcom_podlauncher() //Creates a verb for admins to open up the ui
set name = "Config/Launch Supplypod"
- set desc = "Configure and launch a Centcom supplypod full of whatever your heart desires!"
+ set desc = "Configure and launch a CentCom supplypod full of whatever your heart desires!"
set category = "Adminbus"
- var/datum/centcom_podlauncher/plaunch = new(usr)//create the datum
- plaunch.ui_interact(usr)//datum has a tgui component, here we open the window
+ new /datum/centcom_podlauncher(usr)//create the datum
//Variables declared to change how items in the launch bay are picked and launched. (Almost) all of these are changed in the ui_act proc
//Some effect groups are choices, while other are booleans. This is because some effects can stack, while others dont (ex: you can stack explosion and quiet, but you cant stack ordered launch and random launch)
/datum/centcom_podlauncher
- var/static/list/ignored_atoms = typecacheof(list(null, /mob/dead, /obj/effect/landmark, /obj/docking_port, /atom/movable/lighting_object, /obj/effect/particle_effect/sparks, /obj/effect/DPtarget, /obj/effect/supplypod_selector ))
+ var/static/list/ignored_atoms = typecacheof(list(null, /mob/dead, /obj/effect/landmark, /obj/docking_port, /atom/movable/lighting_object, /obj/effect/particle_effect/sparks, /obj/effect/pod_landingzone, /obj/effect/hallucination/simple/supplypod_selector, /obj/effect/hallucination/simple/dropoff_location))
var/turf/oldTurf //Keeps track of where the user was at if they use the "teleport to centcom" button, so they can go back
var/client/holder //client of whoever is using this datum
- var/area/bay //What bay we're using to launch shit from.
+ var/area/centcom/supplypod/loading/bay //What bay we're using to launch shit from.
+ var/bayNumber //Quick reference to what bay we're in. Usually set to the loading_id variable for the related area type
+ var/customDropoff = FALSE
+ var/picking_dropoff_turf = FALSE
var/launchClone = FALSE //If true, then we don't actually launch the thing in the bay. Instead we call duplicateObject() and send the result
- var/launchChoice = 1 //Determines if we launch all at once (0) , in order (1), or at random(2)
+ var/launchRandomItem = FALSE //If true, lauches a single random item instead of everything on a turf.
+ var/launchChoice = LAUNCH_RANDOM //Determines if we launch all at once (0) , in order (1), or at random(2)
var/explosionChoice = 0 //Determines if there is no explosion (0), custom explosion (1), or just do a maxcap (2)
var/damageChoice = 0 //Determines if we do no damage (0), custom amnt of damage (1), or gib + 5000dmg (2)
var/launcherActivated = FALSE //check if we've entered "launch mode" (when we click a pod is launched). Used for updating mouse cursor
@@ -36,58 +46,129 @@
var/list/orderedArea = list() //Contains an ordered list of turfs in an area (filled in the createOrderedArea() proc), read top-left to bottom-right. Used for the "ordered" launch mode (launchChoice = 1)
var/list/turf/acceptableTurfs = list() //Contians a list of turfs (in the "bay" area on centcom) that have items that can be launched. Taken from orderedArea
var/list/launchList = list() //Contains whatever is going to be put in the supplypod and fired. Taken from acceptableTurfs
- var/obj/effect/supplypod_selector/selector = new() //An effect used for keeping track of what item is going to be launched when in "ordered" mode (launchChoice = 1)
+ var/obj/effect/hallucination/simple/supplypod_selector/selector //An effect used for keeping track of what item is going to be launched when in "ordered" mode (launchChoice = 1)
+ var/obj/effect/hallucination/simple/dropoff_location/indicator
var/obj/structure/closet/supplypod/centcompod/temp_pod //The temporary pod that is modified by this datum, then cloned. The buildObject() clone of this pod is what is launched
+ // Stuff needed to render the map
+ var/map_name
+ var/atom/movable/screen/map_view/cam_screen
+ var/list/cam_plane_masters
+ var/atom/movable/screen/background/cam_background
+ var/tabIndex = 1
+ var/renderLighting = FALSE
-/datum/centcom_podlauncher/New(H)//H can either be a client or a mob due to byondcode(tm)
- if (istype(H,/client))
- var/client/C = H
- holder = C //if its a client, assign it to holder
+/datum/centcom_podlauncher/New(user) //user can either be a client or a mob
+ if (user) //Prevents runtimes on datums being made without clients
+ setup(user)
+
+/datum/centcom_podlauncher/proc/setup(user) //H can either be a client or a mob
+ if (istype(user,/client))
+ var/client/user_client = user
+ holder = user_client //if its a client, assign it to holder
else
- var/mob/M = H
- holder = M.client //if its a mob, assign the mob's client to holder
+ var/mob/user_mob = user
+ holder = user_mob.client //if its a mob, assign the mob's client to holder
bay = locate(/area/centcom/supplypod/loading/one) in GLOB.sortedAreas //Locate the default bay (one) from the centcom map
- temp_pod = new(locate(/area/centcom/supplypod/podStorage) in GLOB.sortedAreas) //Create a new temp_pod in the podStorage area on centcom (so users are free to look at it and change other variables if needed)
+ bayNumber = bay.loading_id //Used as quick reference to what bay we're taking items from
+ var/area/pod_storage_area = locate(/area/centcom/supplypod/pod_storage) in GLOB.sortedAreas
+ temp_pod = new(pick(get_area_turfs(pod_storage_area))) //Create a new temp_pod in the podStorage area on centcom (so users are free to look at it and change other variables if needed)
orderedArea = createOrderedArea(bay) //Order all the turfs in the selected bay (top left to bottom right) to a single list. Used for the "ordered" mode (launchChoice = 1)
+ selector = new(null, holder.mob)
+ indicator = new(null, holder.mob)
+ setDropoff(bay)
+ initMap()
+ refreshBay()
+ ui_interact(holder.mob)
+
+/datum/centcom_podlauncher/proc/initMap()
+ if(map_name)
+ holder.clear_map(map_name)
+
+ map_name = "admin_supplypod_bay_[REF(src)]_map"
+ // Initialize map objects
+ cam_screen = new
+ cam_screen.name = "screen"
+ cam_screen.assigned_map = map_name
+ cam_screen.del_on_map_removal = TRUE
+ cam_screen.screen_loc = "[map_name]:1,1"
+ cam_plane_masters = list()
+ for(var/plane in subtypesof(/atom/movable/screen/plane_master))
+ var/atom/movable/screen/instance = new plane()
+ if (!renderLighting && instance.plane == LIGHTING_PLANE)
+ instance.alpha = 100
+ instance.assigned_map = map_name
+ instance.del_on_map_removal = TRUE
+ instance.screen_loc = "[map_name]:CENTER"
+ cam_plane_masters += instance
+ cam_background = new
+ cam_background.assigned_map = map_name
+ cam_background.del_on_map_removal = TRUE
+ refreshView()
+ holder.register_map_obj(cam_screen)
+ for(var/plane in cam_plane_masters)
+ holder.register_map_obj(plane)
+ holder.register_map_obj(cam_background)
/datum/centcom_podlauncher/ui_state(mob/user)
+ if (SSticker.current_state >= GAME_STATE_FINISHED)
+ return GLOB.always_state //Allow the UI to be given to players by admins after roundend
return GLOB.admin_state
-/datum/centcom_podlauncher/ui_interact(mob/user, datum/tgui/ui)//ui_interact is called when the client verb is called.
+/datum/centcom_podlauncher/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/spritesheet/supplypods),
+ )
+/datum/centcom_podlauncher/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
+ // Open UI
ui = new(user, src, "CentcomPodLauncher")
ui.open()
+ refreshView()
+
+/datum/centcom_podlauncher/ui_static_data(mob/user)
+ var/list/data = list()
+ data["mapRef"] = map_name
+ data["defaultSoundVolume"] = initial(temp_pod.soundVolume) //default volume for pods
+ return data
/datum/centcom_podlauncher/ui_data(mob/user) //Sends info about the pod to the UI.
var/list/data = list() //*****NOTE*****: Many of these comments are similarly described in supplypod.dm. If you change them here, please consider doing so in the supplypod code as well!
- var/B = (istype(bay, /area/centcom/supplypod/loading/one)) ? 1 : (istype(bay, /area/centcom/supplypod/loading/two)) ? 2 : (istype(bay, /area/centcom/supplypod/loading/three)) ? 3 : (istype(bay, /area/centcom/supplypod/loading/four)) ? 4 : (istype(bay, /area/centcom/supplypod/loading/ert)) ? 5 : 0 //top ten THICCEST FUCKING TERNARY CONDITIONALS OF 2036
- data["bay"] = bay //Holds the current bay the user is launching objects from. Bays are specific rooms on the centcom map.
- data["bayNumber"] = B //Holds the bay as a number. Useful for comparisons in centcom_podlauncher.ract
+ bayNumber = bay?.loading_id //Used as quick reference to what bay we're taking items from
+ data["bayNumber"] = bayNumber //Holds the bay as a number. Useful for comparisons in centcom_podlauncher.ract
data["oldArea"] = (oldTurf ? get_area(oldTurf) : null) //Holds the name of the area that the user was in before using the teleportCentcom action
+ data["picking_dropoff_turf"] = picking_dropoff_turf //If we're picking or have picked a dropoff turf. Only works when pod is in reverse mode
+ data["customDropoff"] = customDropoff
+ data["renderLighting"] = renderLighting
data["launchClone"] = launchClone //Do we launch the actual items in the bay or just launch clones of them?
+ data["launchRandomItem"] = launchRandomItem //Do we launch a single random item instead of everything on the turf?
data["launchChoice"] = launchChoice //Launch turfs all at once (0), ordered (1), or randomly(1)
data["explosionChoice"] = explosionChoice //An explosion that occurs when landing. Can be no explosion (0), custom explosion (1), or maxcap (2)
data["damageChoice"] = damageChoice //Damage that occurs to any mob under the pod when it lands. Can be no damage (0), custom damage (1), or gib+5000dmg (2)
- data["fallDuration"] = temp_pod.fallDuration //How long the pod's falling animation lasts
- data["landingDelay"] = temp_pod.landingDelay //How long the pod takes to land after launching
- data["openingDelay"] = temp_pod.openingDelay //How long the pod takes to open after landing
- data["departureDelay"] = temp_pod.departureDelay //How long the pod takes to leave after opening (if bluespace=true, it deletes. if reversing=true, it flies back to centcom)
- data["styleChoice"] = temp_pod.style //Style is a variable that keeps track of what the pod is supposed to look like. It acts as an index to the POD_STYLES list in cargo.dm defines to get the proper icon/name/desc for the pod.
+ data["delays"] = temp_pod.delays
+ data["rev_delays"] = temp_pod.reverse_delays
+ data["custom_rev_delay"] = temp_pod.custom_rev_delay
+ data["styleChoice"] = temp_pod.style //Style is a variable that keeps track of what the pod is supposed to look like. It acts as an index to the GLOB.podstyles list in cargo.dm defines to get the proper icon/name/desc for the pod.
+ data["effectShrapnel"] = temp_pod.effectShrapnel //If true, creates a cloud of shrapnel of a decided type and magnitude on landing
+ data["shrapnelType"] = "[temp_pod.shrapnel_type]" //Path2String
+ data["shrapnelMagnitude"] = temp_pod.shrapnel_magnitude
data["effectStun"] = temp_pod.effectStun //If true, stuns anyone under the pod when it launches until it lands, forcing them to get hit by the pod. Devilish!
data["effectLimb"] = temp_pod.effectLimb //If true, pops off a limb (if applicable) from anyone caught under the pod when it lands
data["effectOrgans"] = temp_pod.effectOrgans //If true, yeets the organs out of any bodies caught under the pod when it lands
data["effectBluespace"] = temp_pod.bluespace //If true, the pod deletes (in a shower of sparks) after landing
- data["effectStealth"] = temp_pod.effectStealth //If true, a target icon isnt displayed on the turf where the pod will land
+ data["effectStealth"] = temp_pod.effectStealth //If true, a target icon isn't displayed on the turf where the pod will land
data["effectQuiet"] = temp_pod.effectQuiet //The female sniper. If true, the pod makes no noise (including related explosions, opening sounds, etc)
data["effectMissile"] = temp_pod.effectMissile //If true, the pod deletes the second it lands. If you give it an explosion, it will act like a missile exploding as it hits the ground
data["effectCircle"] = temp_pod.effectCircle //If true, allows the pod to come in at any angle. Bit of a weird feature but whatever its here
data["effectBurst"] = effectBurst //IOf true, launches five pods at once (with a very small delay between for added coolness), in a 3x3 area centered around the area
data["effectReverse"] = temp_pod.reversing //If true, the pod will not send any items. Instead, after opening, it will close again (picking up items/mobs) and fly back to centcom
+ data["reverseOptionList"] = temp_pod.reverseOptionList
data["effectTarget"] = specificTarget //Launches the pod at the turf of a specific mob target, rather than wherever the user clicked. Useful for smites
data["effectName"] = temp_pod.adminNamed //Determines whether or not the pod has been named by an admin. If true, the pod's name will not get overridden when the style of the pod changes (changing the style of the pod normally also changes the name+desc)
+ data["podName"] = temp_pod.name
+ data["podDesc"] = temp_pod.desc
data["effectAnnounce"] = effectAnnounce
data["giveLauncher"] = launcherActivated //If true, the user is in launch mode, and whenever they click a pod will be launched (either at their mouse position or at a specific target)
data["numObjects"] = numTurfs //Counts the number of turfs that contain a launchable object in the centcom supplypod bay
@@ -95,77 +176,102 @@
data["landingSound"] = temp_pod.landingSound //Admin sound to play when the pod lands
data["openingSound"] = temp_pod.openingSound //Admin sound to play when the pod opens
data["leavingSound"] = temp_pod.leavingSound //Admin sound to play when the pod leaves
- data["soundVolume"] = temp_pod.soundVolume != initial(temp_pod.soundVolume) //Admin sound to play when the pod leaves
+ data["soundVolume"] = temp_pod.soundVolume //Admin sound to play when the pod leaves
return data
/datum/centcom_podlauncher/ui_act(action, params)
- if(..())
+ . = ..()
+ if(.)
return
switch(action)
////////////////////////////UTILITIES//////////////////
- if("bay1")
- bay = locate(/area/centcom/supplypod/loading/one) in GLOB.sortedAreas //set the "bay" variable to the corresponding room in centcom
- refreshBay() //calls refreshBay() which "recounts" the bay to see what items we can launch (among other things).
- . = TRUE
- if("bay2")
- bay = locate(/area/centcom/supplypod/loading/two) in GLOB.sortedAreas
- refreshBay()
- . = TRUE
- if("bay3")
- bay = locate(/area/centcom/supplypod/loading/three) in GLOB.sortedAreas
- refreshBay()
- . = TRUE
- if("bay4")
- bay = locate(/area/centcom/supplypod/loading/four) in GLOB.sortedAreas
- refreshBay()
- . = TRUE
- if("bay5")
- bay = locate(/area/centcom/supplypod/loading/ert) in GLOB.sortedAreas
+ if("gamePanel")
+ holder.holder.Game()
+ SSblackbox.record_feedback("tally", "admin_verb", 1, "Game Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+ . = TRUE
+ if("buildMode")
+ var/mob/holder_mob = holder.mob
+ if (holder_mob)
+ togglebuildmode(holder_mob)
+ SSblackbox.record_feedback("tally", "admin_verb", 1, "Toggle Build Mode") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+ . = TRUE
+ if("loadDataFromPreset")
+ var/list/savedData = params["payload"]
+ loadData(savedData)
+ . = TRUE
+ if("switchBay")
+ bayNumber = params["bayNumber"]
refreshBay()
. = TRUE
- if("teleportCentcom") //Teleports the user to the centcom supply loading facility.
+ if("pickDropoffTurf") //Enters a mode that lets you pick the dropoff location for reverse pods
+ if (picking_dropoff_turf)
+ picking_dropoff_turf = FALSE
+ else
+ if (launcherActivated)
+ launcherActivated = FALSE //We don't want to have launch mode enabled while we're picking a turf
+ picking_dropoff_turf = TRUE
+ updateCursor() //Update the cursor of the user to a cool looking target icon
+ . = TRUE
+ if("clearDropoffTurf")
+ setDropoff(bay)
+ customDropoff = FALSE
+ picking_dropoff_turf = FALSE
+ updateCursor()
+ . = TRUE
+ if("teleportDropoff") //Teleports the user to the dropoff point.
var/mob/M = holder.mob //We teleport whatever mob the client is attached to at the point of clicking
- oldTurf = get_turf(M) //Used for the "teleportBack" action
- var/area/A = locate(bay) in GLOB.sortedAreas
- var/list/turfs = list()
- for(var/turf/T in A)
- turfs.Add(T) //Fill a list with turfs in the area
- var/turf/T = safepick(turfs) //Only teleport if the list isn't empty
- if(!T) //If the list is empty, error and cancel
- to_chat(M, "Nowhere to jump to!")
- return
- M.forceMove(T) //Perform the actual teleport
- log_admin("[key_name(usr)] jumped to [AREACOORD(A)]")
- message_admins("[key_name_admin(usr)] jumped to [AREACOORD(A)]")
+ var/turf/current_location = get_turf(M)
+ var/list/coordinate_list = temp_pod.reverse_dropoff_coords
+ var/turf/dropoff_turf = locate(coordinate_list[1], coordinate_list[2], coordinate_list[3])
+ if (current_location != dropoff_turf)
+ oldTurf = current_location
+ M.forceMove(dropoff_turf) //Perform the actual teleport
+ log_admin("[key_name(usr)] jumped to [AREACOORD(dropoff_turf)]")
+ message_admins("[key_name_admin(usr)] jumped to [AREACOORD(dropoff_turf)]")
. = TRUE
- if("teleportBack") //After teleporting to centcom, this button allows the user to teleport to the last spot they were at.
+ if("teleportCentcom") //Teleports the user to the centcom supply loading facility.
+ var/mob/holder_mob = holder.mob //We teleport whatever mob the client is attached to at the point of clicking
+ var/turf/current_location = get_turf(holder_mob)
+ var/area/bay_area = bay
+ if (current_location.loc != bay_area)
+ oldTurf = current_location
+ var/turf/teleport_turf = pick(get_area_turfs(bay_area))
+ holder_mob.forceMove(teleport_turf) //Perform the actual teleport
+ if (holder.holder)
+ log_admin("[key_name(usr)] jumped to [AREACOORD(teleport_turf)]")
+ message_admins("[key_name_admin(usr)] jumped to [AREACOORD(teleport_turf)]")
+ . = TRUE
+ if("teleportBack") //After teleporting to centcom/dropoff, this button allows the user to teleport to the last spot they were at.
var/mob/M = holder.mob
if (!oldTurf) //If theres no turf to go back to, error and cancel
to_chat(M, "Nowhere to jump to!")
return
M.forceMove(oldTurf) //Perform the actual teleport
- log_admin("[key_name(usr)] jumped to [AREACOORD(oldTurf)]")
- message_admins("[key_name_admin(usr)] jumped to [AREACOORD(oldTurf)]")
+ if (holder.holder)
+ log_admin("[key_name(usr)] jumped to [AREACOORD(oldTurf)]")
+ message_admins("[key_name_admin(usr)] jumped to [AREACOORD(oldTurf)]")
. = TRUE
////////////////////////////LAUNCH STYLE CHANGES//////////////////
if("launchClone") //Toggles the launchClone var. See variable declarations above for what this specifically means
launchClone = !launchClone
. = TRUE
+ if("launchRandomItem") //Pick random turfs from the supplypod bay at centcom to launch
+ launchRandomItem = TRUE
+ . = TRUE
+ if("launchWholeTurf") //Pick random turfs from the supplypod bay at centcom to launch
+ launchRandomItem = FALSE
+ . = TRUE
+ if("launchAll") //Launch turfs (from the orderedArea list) all at once, from the supplypod bay at centcom
+ launchChoice = LAUNCH_ALL
+ updateSelector()
+ . = TRUE
if("launchOrdered") //Launch turfs (from the orderedArea list) one at a time in order, from the supplypod bay at centcom
- if (launchChoice == 1) //launchChoice 1 represents ordered. If we push "ordered" and it already is, then we go to default value
- launchChoice = 0
- updateSelector() //Move the selector effect to the next object that will be launched. See variable declarations for more info on the selector effect.
- return
- launchChoice = 1
+ launchChoice = LAUNCH_ORDERED
updateSelector()
. = TRUE
- if("launchRandom") //Pick random turfs from the supplypod bay at centcom to launch
- if (launchChoice == 2)
- launchChoice = 0
- updateSelector()
- return
- launchChoice = 2
+ if("launchRandomTurf") //Pick random turfs from the supplypod bay at centcom to launch
+ launchChoice = LAUNCH_RANDOM
updateSelector()
. = TRUE
@@ -174,65 +280,82 @@
if (explosionChoice == 1) //If already a custom explosion, set to default (no explosion)
explosionChoice = 0
temp_pod.explosionSize = list(0,0,0,0)
- return
- var/list/expNames = list("Devastation", "Heavy Damage", "Light Damage", "Flame") //Explosions have a range of different types of damage
- var/list/boomInput = list()
- for (var/i=1 to expNames.len) //Gather input from the user for the value of each type of damage
- boomInput.Add(input("[expNames[i]] Range", "Enter the [expNames[i]] range of the explosion. WARNING: This ignores the bomb cap!", 0) as null|num)
- if (isnull(boomInput[i]))
- return
- if (!isnum_safe(boomInput[i])) //If the user doesn't input a number, set that specific explosion value to zero
- alert(usr, "That wasnt a number! Value set to default (zero) instead.")
- boomInput = 0
- explosionChoice = 1
- temp_pod.explosionSize = boomInput
+ else
+ var/list/expNames = list("Devastation", "Heavy Damage", "Light Damage", "Flame") //Explosions have a range of different types of damage
+ var/list/boomInput = list()
+ for (var/i=1 to expNames.len) //Gather input from the user for the value of each type of damage
+ boomInput.Add(input("Enter the [expNames[i]] range of the explosion. WARNING: This ignores the bomb cap!", "[expNames[i]] Range", 0) as null|num)
+ if (isnull(boomInput[i]))
+ return
+ if (!isnum_safe(boomInput[i])) //If the user doesn't input a number, set that specific explosion value to zero
+ alert(usr, "That wasn't a number! Value set to default (zero) instead.")
+ boomInput = 0
+ explosionChoice = 1
+ temp_pod.explosionSize = boomInput
. = TRUE
if("explosionBus") //Creates a maxcap when the pod lands
if (explosionChoice == 2) //If already a maccap, set to default (no explosion)
explosionChoice = 0
temp_pod.explosionSize = list(0,0,0,0)
- return
- explosionChoice = 2
- temp_pod.explosionSize = list(GLOB.MAX_EX_DEVESTATION_RANGE, GLOB.MAX_EX_HEAVY_RANGE, GLOB.MAX_EX_LIGHT_RANGE,GLOB.MAX_EX_FLAME_RANGE) //Set explosion to max cap of server
+ else
+ explosionChoice = 2
+ temp_pod.explosionSize = list(GLOB.MAX_EX_DEVESTATION_RANGE, GLOB.MAX_EX_HEAVY_RANGE, GLOB.MAX_EX_LIGHT_RANGE,GLOB.MAX_EX_FLAME_RANGE) //Set explosion to max cap of server
. = TRUE
if("damageCustom") //Deals damage to whoevers under the pod when it lands
if (damageChoice == 1) //If already doing custom damage, set back to default (no damage)
damageChoice = 0
temp_pod.damage = 0
- return
- var/damageInput = input("How much damage to deal", "Enter the amount of brute damage dealt by getting hit", 0) as null|num
- if (isnull(damageInput))
- return
- if (!isnum_safe(damageInput)) //Sanitize the input for damage to deal.s
- alert(usr, "That wasnt a number! Value set to default (zero) instead.")
- damageInput = 0
- damageChoice = 1
- temp_pod.damage = damageInput
+ else
+ var/damageInput = input("Enter the amount of brute damage dealt by getting hit","How much damage to deal", 0) as null|num
+ if (isnull(damageInput))
+ return
+ if (!isnum_safe(damageInput)) //Sanitize the input for damage to deal.s
+ alert(usr, "That wasn't a number! Value set to default (zero) instead.")
+ damageInput = 0
+ damageChoice = 1
+ temp_pod.damage = damageInput
. = TRUE
if("damageGib") //Gibs whoever is under the pod when it lands. Also deals 5000 damage, just to be sure.
if (damageChoice == 2) //If already gibbing, set back to default (no damage)
damageChoice = 0
temp_pod.damage = 0
temp_pod.effectGib = FALSE
- return
- damageChoice = 2
- temp_pod.damage = 5000
- temp_pod.effectGib = TRUE //Gibs whoever is under the pod when it lands
+ else
+ damageChoice = 2
+ temp_pod.damage = 5000
+ temp_pod.effectGib = TRUE //Gibs whoever is under the pod when it lands
. = TRUE
if("effectName") //Give the supplypod a custom name. Supplypods automatically get their name based on their style (see supplypod/setStyle() proc), so doing this overrides that.
if (temp_pod.adminNamed) //If we're already adminNamed, set the name of the pod back to default
temp_pod.adminNamed = FALSE
temp_pod.setStyle(temp_pod.style) //This resets the name of the pod based on it's current style (see supplypod/setStyle() proc)
- return
- var/nameInput= input("Custom name", "Enter a custom name", POD_STYLES[temp_pod.style][POD_NAME]) as null|text //Gather input for name and desc
- if (isnull(nameInput))
- return
- var/descInput = input("Custom description", "Enter a custom desc", POD_STYLES[temp_pod.style][POD_DESC]) as null|text //The POD_STYLES is used to get the name, desc, or icon state based on the pod's style
- if (isnull(descInput))
- return
- temp_pod.name = nameInput
- temp_pod.desc = descInput
- temp_pod.adminNamed = TRUE //This variable is checked in the supplypod/setStyle() proc
+ else
+ var/nameInput= input("Custom name", "Enter a custom name", GLOB.podstyles[temp_pod.style][POD_NAME]) as null|text //Gather input for name and desc
+ if (isnull(nameInput))
+ return
+ var/descInput = input("Custom description", "Enter a custom desc", GLOB.podstyles[temp_pod.style][POD_DESC]) as null|text //The GLOB.podstyles is used to get the name, desc, or icon state based on the pod's style
+ if (isnull(descInput))
+ return
+ temp_pod.name = nameInput
+ temp_pod.desc = descInput
+ temp_pod.adminNamed = TRUE //This variable is checked in the supplypod/setStyle() proc
+ . = TRUE
+ if("effectShrapnel") //Creates a cloud of shrapnel on landing
+ if (temp_pod.effectShrapnel == TRUE) //If already doing custom damage, set back to default (no shrapnel)
+ temp_pod.effectShrapnel = FALSE
+ else
+ var/shrapnelInput = input("Please enter the type of pellet cloud you'd like to create on landing (Can be any projectile!)", "Projectile Typepath", 0) in sortList(subtypesof(/obj/item/projectile), /proc/cmp_typepaths_asc)
+ if (isnull(shrapnelInput))
+ return
+ var/shrapnelMagnitude = input("Enter the magnitude of the pellet cloud. This is usually a value around 1-5. Please note that Ryll-Ryll has asked me to tell you that if you go too crazy with the projectiles you might crash the server. So uh, be gentle!", "Shrapnel Magnitude", 0) as null|num
+ if (isnull(shrapnelMagnitude))
+ return
+ if (!isnum(shrapnelMagnitude))
+ alert(usr, "That wasn't a number! Value set to 3 instead.")
+ shrapnelMagnitude = 3
+ temp_pod.shrapnel_type = shrapnelInput
+ temp_pod.shrapnel_magnitude = shrapnelMagnitude
+ temp_pod.effectShrapnel = TRUE
. = TRUE
if("effectStun") //Toggle: Any mob under the pod is stunned (cant move) until the pod lands, hitting them!
temp_pod.effectStun = !temp_pod.effectStun
@@ -266,170 +389,136 @@
. = TRUE
if("effectReverse") //Toggle: Don't send any items. Instead, after landing, close (taking any objects inside) and go back to the centcom bay it came from
temp_pod.reversing = !temp_pod.reversing
+ if (temp_pod.reversing)
+ indicator.alpha = 150
+ else
+ indicator.alpha = 0
+ . = TRUE
+ if("reverseOption")
+ var/reverseOption = params["reverseOption"]
+ temp_pod.reverseOptionList[reverseOption] = !temp_pod.reverseOptionList[reverseOption]
. = TRUE
if("effectTarget") //Toggle: Launch at a specific mob (instead of at whatever turf you click on). Used for the supplypod smite
if (specificTarget)
specificTarget = null
- return
- var/list/mobs = getpois()//code stolen from observer.dm
- var/inputTarget = input("Select a mob! (Smiting does this automatically)", "Target", null, null) as null|anything in mobs
- if (isnull(inputTarget))
- return
- var/mob/target = mobs[inputTarget]
- specificTarget = target///input specific tartget
+ else
+ var/list/mobs = getpois()//code stolen from observer.dm
+ var/inputTarget = input("Select a mob! (Smiting does this automatically)", "Target", null, null) as null|anything in mobs
+ if (isnull(inputTarget))
+ return
+ var/mob/target = mobs[inputTarget]
+ specificTarget = target///input specific tartget
. = TRUE
////////////////////////////TIMER DELAYS//////////////////
- if("fallDuration") //Change the time it takes the pod to land, after firing
- if (temp_pod.fallDuration != initial(temp_pod.fallDuration)) //If the landing delay has already been changed when we push the "change value" button, then set it to default
- temp_pod.fallDuration = initial(temp_pod.fallDuration)
- return
- var/timeInput = input("Enter the duration of the pod's falling animation, in seconds", "Delay Time", initial(temp_pod.fallDuration) * 0.1) as null|num
- if (isnull(timeInput))
- return
- if (!isnum_safe(timeInput)) //Sanitize input, if it doesnt check out, error and set to default
- alert(usr, "That wasnt a number! Value set to default ([initial(temp_pod.fallDuration)*0.1]) instead.")
- timeInput = initial(temp_pod.fallDuration)
- temp_pod.fallDuration = 10 * timeInput
- . = TRUE
- if("landingDelay") //Change the time it takes the pod to land, after firing
- if (temp_pod.landingDelay != initial(temp_pod.landingDelay)) //If the landing delay has already been changed when we push the "change value" button, then set it to default
- temp_pod.landingDelay = initial(temp_pod.landingDelay)
- return
- var/timeInput = input("Enter the time it takes for the pod to land, in seconds", "Delay Time", initial(temp_pod.landingDelay) * 0.1) as null|num
- if (isnull(timeInput))
- return
- if (!isnum_safe(timeInput)) //Sanitize input, if it doesnt check out, error and set to default
- alert(usr, "That wasnt a number! Value set to default ([initial(temp_pod.landingDelay)*0.1]) instead.")
- timeInput = initial(temp_pod.landingDelay)
- temp_pod.landingDelay = 10 * timeInput
- . = TRUE
- if("openingDelay") //Change the time it takes the pod to open it's door (and release its contents) after landing
- if (temp_pod.openingDelay != initial(temp_pod.openingDelay)) //If the opening delay has already been changed when we push the "change value" button, then set it to default
- temp_pod.openingDelay = initial(temp_pod.openingDelay)
- return
- var/timeInput = input("Enter the time it takes for the pod to open after landing, in seconds", "Delay Time", initial(temp_pod.openingDelay) * 0.1) as null|num
- if (isnull(timeInput))
- return
- if (!isnum_safe(timeInput)) //Sanitize input
- alert(usr, "That wasnt a number! Value set to default ([initial(temp_pod.openingDelay)*0.1]) instead.")
- timeInput = initial(temp_pod.openingDelay)
- temp_pod.openingDelay = 10 * timeInput
- . = TRUE
- if("departureDelay") //Change the time it takes the pod to leave (if bluespace = true it just deletes, if effectReverse = true it goes back to centcom)
- if (temp_pod.departureDelay != initial(temp_pod.departureDelay)) //If the departure delay has already been changed when we push the "change value" button, then set it to default
- temp_pod.departureDelay = initial(temp_pod.departureDelay)
- return
- var/timeInput = input("Enter the time it takes for the pod to leave after opening, in seconds", "Delay Time", initial(temp_pod.departureDelay) * 0.1) as null|num
- if (isnull(timeInput))
- return
- if (!isnum_safe(timeInput))
- alert(usr, "That wasnt a number! Value set to default ([initial(temp_pod.departureDelay)*0.1]) instead.")
- timeInput = initial(temp_pod.departureDelay)
- temp_pod.departureDelay = 10 * timeInput
+ if("editTiming") //Change the different timers relating to the pod
+ var/delay = params["timer"]
+ var/value = params["value"]
+ var/reverse = params["reverse"]
+ if (reverse)
+ temp_pod.reverse_delays[delay] = value * 10
+ else
+ temp_pod.delays[delay] = value * 10
+ . = TRUE
+ if("resetTiming")
+ temp_pod.delays = list(POD_TRANSIT = 20, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30)
+ temp_pod.reverse_delays = list(POD_TRANSIT = 20, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30)
+ . = TRUE
+ if("toggleRevDelays")
+ temp_pod.custom_rev_delay = !temp_pod.custom_rev_delay
. = TRUE
-
////////////////////////////ADMIN SOUNDS//////////////////
- if("fallSound") //Admin sound from a local file that plays when the pod lands
+ if("fallingSound") //Admin sound from a local file that plays when the pod lands
if ((temp_pod.fallingSound) != initial(temp_pod.fallingSound))
temp_pod.fallingSound = initial(temp_pod.fallingSound)
temp_pod.fallingSoundLength = initial(temp_pod.fallingSoundLength)
- return
- var/soundInput = input(holder, "Please pick a sound file to play when the pod lands! NOTICE: Take a note of exactly how long the sound is.", "Pick a Sound File") as null|sound
- if (isnull(soundInput))
- return
- var/timeInput = input(holder, "What is the exact length of the sound file, in seconds. This number will be used to line the sound up so that it finishes right as the pod lands!", "Pick a Sound File", 0.3) as null|num
- if (isnull(timeInput))
- return
- if (!isnum_safe(timeInput))
- alert(usr, "That wasnt a number! Value set to default ([initial(temp_pod.fallingSoundLength)*0.1]) instead.")
- temp_pod.fallingSound = soundInput
- temp_pod.fallingSoundLength = 10 * timeInput
+ else
+ var/soundInput = input(holder, "Please pick a sound file to play when the pod lands! Sound will start playing and try to end when the pod lands", "Pick a Sound File") as null|sound
+ if (isnull(soundInput))
+ return
+ var/sound/tempSound = sound(soundInput)
+ playsound(holder.mob, tempSound, 1)
+ var/list/sounds_list = holder.SoundQuery()
+ var/soundLen = 0
+ for (var/playing_sound in sounds_list)
+ if (isnull(playing_sound))
+ stack_trace("client.SoundQuery() Returned a list containing a null sound! Somehow!")
+ continue
+ var/sound/found = playing_sound
+ if (found.file == tempSound.file)
+ soundLen = found.len
+ if (!soundLen)
+ soundLen = input(holder, "Couldn't auto-determine sound file length. What is the exact length of the sound file, in seconds. This number will be used to line the sound up so that it finishes right as the pod lands!", "Pick a Sound File", 0.3) as null|num
+ if (isnull(soundLen))
+ return
+ if (!isnum(soundLen))
+ alert(usr, "That wasn't a number! Value set to default ([initial(temp_pod.fallingSoundLength)*0.1]) instead.")
+ temp_pod.fallingSound = soundInput
+ temp_pod.fallingSoundLength = 10 * soundLen
. = TRUE
if("landingSound") //Admin sound from a local file that plays when the pod lands
if (!isnull(temp_pod.landingSound))
temp_pod.landingSound = null
- return
- var/soundInput = input(holder, "Please pick a sound file to play when the pod lands! I reccomend a nice \"oh shit, i'm sorry\", incase you hit someone with the pod.", "Pick a Sound File") as null|sound
- if (isnull(soundInput))
- return
- temp_pod.landingSound = soundInput
+ else
+ var/soundInput = input(holder, "Please pick a sound file to play when the pod lands! I reccomend a nice \"oh shit, i'm sorry\", incase you hit someone with the pod.", "Pick a Sound File") as null|sound
+ if (isnull(soundInput))
+ return
+ temp_pod.landingSound = soundInput
. = TRUE
if("openingSound") //Admin sound from a local file that plays when the pod opens
if (!isnull(temp_pod.openingSound))
temp_pod.openingSound = null
- return
- var/soundInput = input(holder, "Please pick a sound file to play when the pod opens! I reccomend a stock sound effect of kids cheering at a party, incase your pod is full of fun exciting stuff!", "Pick a Sound File") as null|sound
- if (isnull(soundInput))
- return
- temp_pod.openingSound = soundInput
+ else
+ var/soundInput = input(holder, "Please pick a sound file to play when the pod opens! I reccomend a stock sound effect of kids cheering at a party, incase your pod is full of fun exciting stuff!", "Pick a Sound File") as null|sound
+ if (isnull(soundInput))
+ return
+ temp_pod.openingSound = soundInput
. = TRUE
if("leavingSound") //Admin sound from a local file that plays when the pod leaves
if (!isnull(temp_pod.leavingSound))
temp_pod.leavingSound = null
- return
- var/soundInput = input(holder, "Please pick a sound file to play when the pod leaves! I reccomend a nice slide whistle sound, especially if you're using the reverse pod effect.", "Pick a Sound File") as null|sound
- if (isnull(soundInput))
- return
- temp_pod.leavingSound = soundInput
+ else
+ var/soundInput = input(holder, "Please pick a sound file to play when the pod leaves! I reccomend a nice slide whistle sound, especially if you're using the reverse pod effect.", "Pick a Sound File") as null|sound
+ if (isnull(soundInput))
+ return
+ temp_pod.leavingSound = soundInput
. = TRUE
if("soundVolume") //Admin sound from a local file that plays when the pod leaves
if (temp_pod.soundVolume != initial(temp_pod.soundVolume))
temp_pod.soundVolume = initial(temp_pod.soundVolume)
- return
- var/soundInput = input(holder, "Please pick a volume. Default is between 1 and 100 with 50 being average, but pick whatever. I'm a notification, not a cop. If you still cant hear your sound, consider turning on the Quiet effect. It will silence all pod sounds except for the custom admin ones set by the previous three buttons.", "Pick Admin Sound Volume") as null|num
- if (isnull(soundInput))
- return
- temp_pod.soundVolume = soundInput
+ else
+ var/soundInput = input(holder, "Please pick a volume. Default is between 1 and 100 with 50 being average, but pick whatever. I'm a notification, not a cop. If you still cant hear your sound, consider turning on the Quiet effect. It will silence all pod sounds except for the custom admin ones set by the previous three buttons.", "Pick Admin Sound Volume") as null|num
+ if (isnull(soundInput))
+ return
+ temp_pod.soundVolume = soundInput
. = TRUE
////////////////////////////STYLE CHANGES//////////////////
- //Style is a value that is used to keep track of what the pod is supposed to look like. It can be used with the POD_STYLES list (in cargo.dm defines)
+ //Style is a value that is used to keep track of what the pod is supposed to look like. It can be used with the GLOB.podstyles list (in cargo.dm defines)
//as a way to get the proper icon state, name, and description of the pod.
- if("styleStandard")
- temp_pod.setStyle(STYLE_STANDARD)
- . = TRUE
- if("styleBluespace")
- temp_pod.setStyle(STYLE_BLUESPACE)
- . = TRUE
- if("styleSyndie")
- temp_pod.setStyle(STYLE_SYNDICATE)
- . = TRUE
- if("styleBlue")
- temp_pod.setStyle(STYLE_BLUE)
- . = TRUE
- if("styleCult")
- temp_pod.setStyle(STYLE_CULT)
- . = TRUE
- if("styleMissile")
- temp_pod.setStyle(STYLE_MISSILE)
- . = TRUE
- if("styleSMissile")
- temp_pod.setStyle(STYLE_RED_MISSILE)
+ if("tabSwitch")
+ tabIndex = params["tabIndex"]
+ refreshView()
. = TRUE
- if("styleBox")
- temp_pod.setStyle(STYLE_BOX)
+ if("refreshView")
+ initMap()
+ refreshView()
. = TRUE
- if("styleHONK")
- temp_pod.setStyle(STYLE_HONK)
+ if("renderLighting")
+ renderLighting = !renderLighting
. = TRUE
- if("styleFruit")
- temp_pod.setStyle(STYLE_FRUIT)
- . = TRUE
- if("styleInvisible")
- temp_pod.setStyle(STYLE_INVISIBLE)
- . = TRUE
- if("styleGondola")
- temp_pod.setStyle(STYLE_GONDOLA)
- . = TRUE
- if("styleSeeThrough")
- temp_pod.setStyle(STYLE_SEETHROUGH)
+ if("setStyle")
+ var/chosenStyle = params["style"]
+ temp_pod.setStyle(chosenStyle+1)
. = TRUE
if("refresh") //Refresh the Pod bay. User should press this if they spawn something new in the centcom bay. Automatically called whenever the user launches a pod
refreshBay()
. = TRUE
if("giveLauncher") //Enters the "Launch Mode". When the launcher is activated, temp_pod is cloned, and the result it filled and launched anywhere the user clicks (unless specificTarget is true)
launcherActivated = !launcherActivated
- updateCursor(launcherActivated) //Update the cursor of the user to a cool looking target icon
+ if (picking_dropoff_turf)
+ picking_dropoff_turf = FALSE //We don't want to have launch mode enabled while we're picking a turf
+ updateCursor() //Update the cursor of the user to a cool looking target icon
+ updateSelector()
. = TRUE
if("clearBay") //Delete all mobs and objs in the selected bay
if(alert(usr, "This will delete all objs and mobs in [bay]. Are you sure?", "Confirmation", "Delete that shit", "No") == "Delete that shit")
@@ -437,30 +526,62 @@
refreshBay()
. = TRUE
-/datum/centcom_podlauncher/ui_close() //Uses the destroy() proc. When the user closes the UI, we clean up the temp_pod and supplypod_selector variables.
+/datum/centcom_podlauncher/ui_close(mob/user, datum/tgui/tgui) //Uses the destroy() proc. When the user closes the UI, we clean up the temp_pod and supplypod_selector variables.
+ QDEL_NULL(temp_pod)
+ user.client?.clear_map(map_name)
+ QDEL_NULL(cam_screen)
+ QDEL_LIST(cam_plane_masters)
+ QDEL_NULL(cam_background)
qdel(src)
-/datum/centcom_podlauncher/proc/updateCursor(var/launching) //Update the moues of the user
- if (holder) //Check to see if we have a client
- if (launching) //If the launching param is true, we give the user new mouse icons.
+/datum/centcom_podlauncher/proc/setupViewPod()
+ setupView(RANGE_TURFS(2, temp_pod))
+
+/datum/centcom_podlauncher/proc/setupViewBay()
+ var/list/visible_turfs = list()
+ for(var/turf/bay_turf in bay)
+ visible_turfs += bay_turf
+ setupView(visible_turfs)
+
+/datum/centcom_podlauncher/proc/setupViewDropoff()
+ var/list/coords_list = temp_pod.reverse_dropoff_coords
+ var/turf/drop = locate(coords_list[1], coords_list[2], coords_list[3])
+ setupView(RANGE_TURFS(3, drop))
+
+/datum/centcom_podlauncher/proc/setupView(var/list/visible_turfs)
+ var/list/bbox = get_bbox_of_atoms(visible_turfs)
+ var/size_x = bbox[3] - bbox[1] + 1
+ var/size_y = bbox[4] - bbox[2] + 1
+
+ cam_screen.vis_contents = visible_turfs
+ cam_background.icon_state = "clear"
+ cam_background.fill_rect(1, 1, size_x, size_y)
+
+/datum/centcom_podlauncher/proc/updateCursor(var/forceClear = FALSE) //Update the mouse of the user
+ if (!holder) //Can't update the mouse icon if the client doesnt exist!
+ return
+ if (!forceClear && (launcherActivated || picking_dropoff_turf)) //If the launching param is true, we give the user new mouse icons.
+ if(launcherActivated)
holder.mouse_up_icon = 'icons/effects/supplypod_target.dmi' //Icon for when mouse is released
holder.mouse_down_icon = 'icons/effects/supplypod_down_target.dmi' //Icon for when mouse is pressed
- holder.mouse_pointer_icon = holder.mouse_up_icon //Icon for idle mouse (same as icon for when released)
- holder.click_intercept = src //Create a click_intercept so we know where the user is clicking
- else
- var/mob/M = holder.mob
- holder.mouse_up_icon = null
- holder.mouse_down_icon = null
- holder.click_intercept = null
- if (M)
- M.update_mouse_pointer() //set the moues icons to null, then call update_moues_pointer() which resets them to the correct values based on what the mob is doing (in a mech, holding a spell, etc)()
+ else if(picking_dropoff_turf)
+ holder.mouse_up_icon = 'icons/effects/supplypod_pickturf.dmi' //Icon for when mouse is released
+ holder.mouse_down_icon = 'icons/effects/supplypod_pickturf_down.dmi' //Icon for when mouse is pressed
+ holder.mouse_pointer_icon = holder.mouse_up_icon //Icon for idle mouse (same as icon for when released)
+ holder.click_intercept = src //Create a click_intercept so we know where the user is clicking
+ else
+ var/mob/holder_mob = holder.mob
+ holder.mouse_up_icon = null
+ holder.mouse_down_icon = null
+ holder.click_intercept = null
+ holder_mob?.update_mouse_pointer() //set the moues icons to null, then call update_moues_pointer() which resets them to the correct values based on what the mob is doing (in a mech, holding a spell, etc)()
/datum/centcom_podlauncher/proc/InterceptClickOn(user,params,atom/target) //Click Intercept so we know where to send pods where the user clicks
var/list/pa = params2list(params)
var/left_click = pa.Find("left")
if (launcherActivated)
//Clicking on UI elements shouldn't launch a pod
- if(istype(target,/obj/screen))
+ if(istype(target,/atom/movable/screen))
return FALSE
. = TRUE
@@ -476,9 +597,10 @@
if (effectAnnounce)
deadchat_broadcast("A special package is being launched at the station!", turf_target = target)
var/list/bouttaDie = list()
- for (var/mob/living/M in target)
- bouttaDie.Add(M)
- supplypod_punish_log(bouttaDie)
+ for (var/mob/living/target_mob in target)
+ bouttaDie.Add(target_mob)
+ if (holder.holder)
+ supplypod_punish_log(bouttaDie)
if (!effectBurst) //If we're not using burst mode, just launch normally.
launch(target)
else
@@ -486,88 +608,159 @@
if (isnull(target))
break //if our target gets deleted during this, we stop the show
preLaunch() //Same as above
- var/LZ = locate(target.x + rand(-1,1), target.y + rand(-1,1), target.z) //Pods are randomly adjacent to (or the same as) the target
- if (LZ) //just incase we're on the edge of the map or something that would cause target.x+1 to fail
- launch(LZ) //launch the pod at the adjacent turf
+ var/landingzone = locate(target.x + rand(-1,1), target.y + rand(-1,1), target.z) //Pods are randomly adjacent to (or the same as) the target
+ if (landingzone) //just incase we're on the edge of the map or something that would cause target.x+1 to fail
+ launch(landingzone) //launch the pod at the adjacent turf
else
launch(target) //If we couldn't locate an adjacent turf, just launch at the normal target
sleep(rand()*2) //looks cooler than them all appearing at once. Gives the impression of burst fire.
+ else if (picking_dropoff_turf)
+ //Clicking on UI elements shouldn't pick a dropoff turf
+ if(istype(target, /atom/movable/screen))
+ return FALSE
+
+ . = TRUE
+ if(left_click) //When we left click:
+ var/turf/target_turf = get_turf(target)
+ setDropoff(target_turf)
+ customDropoff = TRUE
+ to_chat(user, " You've selected [target_turf] at [COORD(target_turf)] as your dropoff location.")
+ ui_update()
+
+/datum/centcom_podlauncher/proc/refreshView()
+ switch(tabIndex)
+ if (TAB_POD)
+ setupViewPod()
+ if (TAB_BAY)
+ setupViewBay()
+ else
+ setupViewDropoff()
/datum/centcom_podlauncher/proc/refreshBay() //Called whenever the bay is switched, as well as wheneber a pod is launched
+ bay = GLOB.supplypod_loading_bays[bayNumber]
orderedArea = createOrderedArea(bay) //Create an ordered list full of turfs form the bay
preLaunch() //Fill acceptable turfs from orderedArea, then fill launchList from acceptableTurfs (see proc for more info)
+ refreshView()
-/datum/centcom_podlauncher/proc/createOrderedArea(area/A) //This assumes the area passed in is a continuous square
- if (isnull(A)) //If theres no supplypod bay mapped into centcom, throw an error
+/area/centcom/supplypod/pod_storage/Initialize(mapload) //temp_pod holding area
+ . = ..()
+ var/obj/imgbound = locate() in locate(200,SUPPLYPOD_X_OFFSET*-4.5, 1)
+ call(GLOB.podlauncher, "RegisterSignal")(imgbound, "ct[GLOB.podstyles[14][9]]", "[GLOB.podstyles[14][10]]dlauncher")
+
+/datum/centcom_podlauncher/proc/createOrderedArea(area/area_to_order) //This assumes the area passed in is a continuous square
+ if (isnull(area_to_order)) //If theres no supplypod bay mapped into centcom, throw an error
to_chat(holder.mob, "No /area/centcom/supplypod/loading/one (or /two or /three or /four) in the world! You can make one yourself (then refresh) for now, but yell at a mapper to fix this, today!")
CRASH("No /area/centcom/supplypod/loading/one (or /two or /three or /four) has been mapped into the centcom z-level!")
orderedArea = list()
- if (!isemptylist(A.contents)) //Go through the area passed into the proc, and figure out the top left and bottom right corners by calculating max and min values
- var/startX = A.contents[1].x //Create the four values (we do it off a.contents[1] so they have some sort of arbitrary initial value. They should be overwritten in a few moments)
- var/endX = A.contents[1].x
- var/startY = A.contents[1].y
- var/endY = A.contents[1].y
- for (var/turf/T in A) //For each turf in the area, go through and find:
- if (T.x < startX) //The turf with the smallest x value. This is our startX
- startX = T.x
- else if (T.x > endX) //The turf with the largest x value. This is our endX
- endX = T.x
- else if (T.y > startY) //The turf with the largest Y value. This is our startY
- startY = T.y
- else if (T.y < endY) //The turf with the smallest Y value. This is our endY
- endY = T.y
- for (var/i in endY to startY)
- for (var/j in startX to endX)
- orderedArea.Add(locate(j,startY - (i - endY),1)) //After gathering the start/end x and y, go through locating each turf from top left to bottom right, like one would read a book
+ if (length(area_to_order.contents)) //Go through the area passed into the proc, and figure out the top left and bottom right corners by calculating max and min values
+ var/startX = area_to_order.contents[1].x //Create the four values (we do it off a.contents[1] so they have some sort of arbitrary initial value. They should be overwritten in a few moments)
+ var/endX = area_to_order.contents[1].x
+ var/startY = area_to_order.contents[1].y
+ var/endY = area_to_order.contents[1].y
+ for (var/turf/turf_in_area in area_to_order) //For each turf in the area, go through and find:
+ if (turf_in_area.x < startX) //The turf with the smallest x value. This is our startX
+ startX = turf_in_area.x
+ else if (turf_in_area.x > endX) //The turf with the largest x value. This is our endX
+ endX = turf_in_area.x
+ else if (turf_in_area.y > startY) //The turf with the largest Y value. This is our startY
+ startY = turf_in_area.y
+ else if (turf_in_area.y < endY) //The turf with the smallest Y value. This is our endY
+ endY = turf_in_area.y
+ for (var/vertical in endY to startY)
+ for (var/horizontal in startX to endX)
+ orderedArea.Add(locate(horizontal, startY - (vertical - endY), 1)) //After gathering the start/end x and y, go through locating each turf from top left to bottom right, like one would read a book
return orderedArea //Return the filled list
/datum/centcom_podlauncher/proc/preLaunch() //Creates a list of acceptable items,
numTurfs = 0 //Counts the number of turfs that can be launched (remember, supplypods either launch all at once or one turf-worth of items at a time)
acceptableTurfs = list()
- for (var/turf/T in orderedArea) //Go through the orderedArea list
- if (typecache_filter_list_reverse(T.contents, ignored_atoms).len != 0) //if there is something in this turf that isnt in the blacklist, we consider this turf "acceptable" and add it to the acceptableTurfs list
- acceptableTurfs.Add(T) //Because orderedArea was an ordered linear list, acceptableTurfs will be as well.
+ for (var/t in orderedArea) //Go through the orderedArea list
+ var/turf/unchecked_turf = t
+ if (iswallturf(unchecked_turf) || typecache_filter_list_reverse(unchecked_turf.contents, ignored_atoms).len != 0) //if there is something in this turf that isn't in the blacklist, we consider this turf "acceptable" and add it to the acceptableTurfs list
+ acceptableTurfs.Add(unchecked_turf) //Because orderedArea was an ordered linear list, acceptableTurfs will be as well.
numTurfs ++
launchList = list() //Anything in launchList will go into the supplypod when it is launched
- if (!isemptylist(acceptableTurfs) && !temp_pod.reversing && !temp_pod.effectMissile) //We dont fill the supplypod if acceptableTurfs is empty, if the pod is going in reverse (effectReverse=true), or if the pod is acitng like a missile (effectMissile=true)
+ if (length(acceptableTurfs) && !temp_pod.reversing && !temp_pod.effectMissile) //We dont fill the supplypod if acceptableTurfs is empty, if the pod is going in reverse (effectReverse=true), or if the pod is acitng like a missile (effectMissile=true)
switch(launchChoice)
- if(0) //If we are launching all the turfs at once
- for (var/turf/T in acceptableTurfs)
- launchList |= typecache_filter_list_reverse(T.contents, ignored_atoms) //We filter any blacklisted atoms and add the rest to the launchList
- if(1) //If we are launching one at a time
+ if(LAUNCH_ALL) //If we are launching all the turfs at once
+ for (var/t in acceptableTurfs)
+ var/turf/accepted_turf = t
+ launchList |= typecache_filter_list_reverse(accepted_turf.contents, ignored_atoms) //We filter any blacklisted atoms and add the rest to the launchList
+ if (iswallturf(accepted_turf))
+ launchList += accepted_turf
+ if(LAUNCH_ORDERED) //If we are launching one at a time
if (launchCounter > acceptableTurfs.len) //Check if the launchCounter, which acts as an index, is too high. If it is, reset it to 1
launchCounter = 1 //Note that the launchCounter index is incremented in the launch() proc
- for (var/atom/movable/O in acceptableTurfs[launchCounter].contents) //Go through the acceptableTurfs list based on the launchCounter index
- launchList |= typecache_filter_list_reverse(acceptableTurfs[launchCounter].contents, ignored_atoms) //Filter the specicic turf chosen from acceptableTurfs, and add it to the launchList
- if(2) //If we are launching randomly
- launchList |= typecache_filter_list_reverse(pick_n_take(acceptableTurfs).contents, ignored_atoms) //filter a random turf from the acceptableTurfs list and add it to the launchList
+ var/turf/next_turf_in_line = acceptableTurfs[launchCounter]
+ launchList |= typecache_filter_list_reverse(next_turf_in_line.contents, ignored_atoms) //Filter the specicic turf chosen from acceptableTurfs, and add it to the launchList
+ if (iswallturf(next_turf_in_line))
+ launchList += next_turf_in_line
+ if(LAUNCH_RANDOM) //If we are launching randomly
+ var/turf/acceptable_turf = pick_n_take(acceptableTurfs)
+ launchList |= typecache_filter_list_reverse(acceptable_turf.contents, ignored_atoms) //filter a random turf from the acceptableTurfs list and add it to the launchList
+ if (iswallturf(acceptable_turf))
+ launchList += acceptable_turf
updateSelector() //Call updateSelector(), which, if we are launching one at a time (launchChoice==2), will move to the next turf that will be launched
//UpdateSelector() is here (instead if the if(1) switch block) because it also moves the selector to nullspace (to hide it) if needed
-/datum/centcom_podlauncher/proc/launch(turf/A) //Game time started
- if (isnull(A))
+/datum/centcom_podlauncher/proc/launch(turf/target_turf) //Game time started
+ if (isnull(target_turf))
return
var/obj/structure/closet/supplypod/centcompod/toLaunch = DuplicateObject(temp_pod) //Duplicate the temp_pod (which we have been varediting or configuring with the UI) and store the result
- toLaunch.bay = bay //Bay is currently a nonstatic expression, so it cant go into toLaunch using DuplicateObject
toLaunch.update_icon()//we update_icon() here so that the door doesnt "flicker on" right after it lands
- var/shippingLane = GLOB.areas_by_type[/area/centcom/supplypod/flyMeToTheMoon]
+ var/shippingLane = GLOB.areas_by_type[/area/centcom/supplypod/supplypod_temp_holding]
toLaunch.forceMove(shippingLane)
if (launchClone) //We arent launching the actual items from the bay, rather we are creating clones and launching those
- for (var/atom/movable/O in launchList)
- DuplicateObject(O).forceMove(toLaunch) //Duplicate each atom/movable in launchList and forceMove them into the supplypod
- new /obj/effect/DPtarget(A, toLaunch) //Create the DPTarget, which will eventually forceMove the temp_pod to it's location
+ if(launchRandomItem)
+ var/launch_candidate = pick_n_take(launchList)
+ if(!isnull(launch_candidate))
+ if (iswallturf(launch_candidate))
+ var/atom/atom_to_launch = launch_candidate
+ toLaunch.turfs_in_cargo += atom_to_launch.type
+ else
+ var/atom/movable/movable_to_launch = launch_candidate
+ DuplicateObject(movable_to_launch).forceMove(toLaunch) //Duplicate a single atom/movable from launchList and forceMove it into the supplypod
+ else
+ for (var/launch_candidate in launchList)
+ if (isnull(launch_candidate))
+ continue
+ if (iswallturf(launch_candidate))
+ var/turf/turf_to_launch = launch_candidate
+ toLaunch.turfs_in_cargo += turf_to_launch.type
+ else
+ var/atom/movable/movable_to_launch = launch_candidate
+ DuplicateObject(movable_to_launch).forceMove(toLaunch) //Duplicate each atom/movable in launchList and forceMove them into the supplypod
else
- for (var/atom/movable/O in launchList) //If we aren't cloning the objects, just go through the launchList
- O.forceMove(toLaunch) //and forceMove any atom/moveable into the supplypod
- new /obj/effect/DPtarget(A, toLaunch) //Then, create the DPTarget effect, which will eventually forceMove the temp_pod to it's location
+ if(launchRandomItem)
+ var/atom/random_item = pick_n_take(launchList)
+ if(!isnull(random_item))
+ if (iswallturf(random_item))
+ var/turf/wall = random_item
+ toLaunch.turfs_in_cargo += wall.type
+ wall.ScrapeAway()
+ else
+ var/atom/movable/random_item_movable = random_item
+ random_item_movable.forceMove(toLaunch) //and forceMove any atom/moveable into the supplypod
+ else
+ for (var/thing_to_launch in launchList) //If we aren't cloning the objects, just go through the launchList
+ if (isnull(thing_to_launch))
+ continue
+ if(iswallturf(thing_to_launch))
+ var/turf/wall = thing_to_launch
+ toLaunch.turfs_in_cargo += wall.type
+ wall.ScrapeAway()
+ else
+ var/atom/movable/movable_to_launch = thing_to_launch
+ movable_to_launch.forceMove(toLaunch) //and forceMove any atom/moveable into the supplypod
+ new /obj/effect/pod_landingzone(target_turf, toLaunch) //Then, create the DPTarget effect, which will eventually forceMove the temp_pod to it's location
if (launchClone)
launchCounter++ //We only need to increment launchCounter if we are cloning objects.
//If we aren't cloning objects, taking and removing the first item each time from the acceptableTurfs list will inherently iterate through the list in order
/datum/centcom_podlauncher/proc/updateSelector() //Ensures that the selector effect will showcase the next item if needed
- if (launchChoice == 1 && !isemptylist(acceptableTurfs) && !temp_pod.reversing && !temp_pod.effectMissile) //We only show the selector if we are taking items from the bay
- var/index = launchCounter + 1 //launchCounter acts as an index to the ordered acceptableTurfs list, so adding one will show the next item in the list
+ if (launchChoice == LAUNCH_ORDERED && length(acceptableTurfs) > 1 && !temp_pod.reversing && !temp_pod.effectMissile) //We only show the selector if we are taking items from the bay
+ var/index = (launchCounter == 1 ? launchCounter : launchCounter + 1) //launchCounter acts as an index to the ordered acceptableTurfs list, so adding one will show the next item in the list. We don't want to do this for the very first item tho
if (index > acceptableTurfs.len) //out of bounds check
index = 1
selector.forceMove(acceptableTurfs[index]) //forceMove the selector to the next turf in the ordered acceptableTurfs list
@@ -576,14 +769,21 @@
/datum/centcom_podlauncher/proc/clearBay() //Clear all objs and mobs from the selected bay
for (var/obj/O in bay.GetAllContents())
+ if(istype(O, /obj/effect/hallucination/simple/supplypod_selector) \
+ || istype(O, /obj/effect/hallucination/simple/dropoff_location))
+ continue // Don't clear indicators, especially since they don't recreate automatically
qdel(O)
for (var/mob/M in bay.GetAllContents())
qdel(M)
+ for (var/bayturf in bay)
+ var/turf/turf_to_clear = bayturf
+ turf_to_clear.ChangeTurf(/turf/open/floor/plasteel)
/datum/centcom_podlauncher/Destroy() //The Destroy() proc. This is called by ui_close proc, or whenever the user leaves the game
- updateCursor(FALSE) //Make sure our moues cursor resets to default. False means we are not in launch mode
- qdel(temp_pod) //Delete the temp_pod
- qdel(selector) //Delete the selector effect
+ updateCursor(TRUE) //Make sure our moues cursor resets to default. False means we are not in launch mode
+ QDEL_NULL(temp_pod) //Delete the temp_pod
+ QDEL_NULL(selector) //Delete the selector effect
+ QDEL_NULL(indicator)
. = ..()
/datum/centcom_podlauncher/proc/supplypod_punish_log(var/list/whoDyin)
@@ -593,17 +793,87 @@
for (var/mob/living/M in whoDyin)
whomString += "[key_name(M)], "
- var/delayString = temp_pod.landingDelay == initial(temp_pod.landingDelay) ? "" : " Delay=[temp_pod.landingDelay*0.1]s"
- var/damageString = temp_pod.damage == 0 ? "" : " Dmg=[temp_pod.damage]"
- var/explosionString = ""
- var/explosion_sum = temp_pod.explosionSize[1] + temp_pod.explosionSize[2] + temp_pod.explosionSize[3] + temp_pod.explosionSize[4]
- if (explosion_sum != 0)
- explosionString = " Boom=|"
- for (var/X in temp_pod.explosionSize)
- explosionString += "[X]|"
-
- var/msg = "launched [podString][whomString].[delayString][damageString][explosionString]"
- message_admins("[key_name_admin(usr)] [msg] in [AREACOORD(specificTarget)].")
- if (!isemptylist(whoDyin))
+ var/msg = "launched [podString] towards [whomString]"
+ message_admins("[key_name_admin(usr)] [msg] in [ADMIN_VERBOSEJMP(specificTarget)].")
+ if (length(whoDyin))
for (var/mob/living/M in whoDyin)
admin_ticket_log(M, "[key_name_admin(usr)] [msg]")
+
+/datum/centcom_podlauncher/proc/loadData(var/list/dataToLoad)
+ bayNumber = dataToLoad["bayNumber"]
+ customDropoff = dataToLoad["customDropoff"]
+ renderLighting = dataToLoad["renderLighting"]
+ launchClone = dataToLoad["launchClone"] //Do we launch the actual items in the bay or just launch clones of them?
+ launchRandomItem = dataToLoad["launchRandomItem"] //Do we launch a single random item instead of everything on the turf?
+ launchChoice = dataToLoad["launchChoice"] //Launch turfs all at once (0), ordered (1), or randomly(1)
+ explosionChoice = dataToLoad["explosionChoice"] //An explosion that occurs when landing. Can be no explosion (0), custom explosion (1), or maxcap (2)
+ damageChoice = dataToLoad["damageChoice"] //Damage that occurs to any mob under the pod when it lands. Can be no damage (0), custom damage (1), or gib+5000dmg (2)
+ temp_pod.delays = dataToLoad["delays"]
+ temp_pod.reverse_delays = dataToLoad["rev_delays"]
+ temp_pod.custom_rev_delay = dataToLoad["custom_rev_delay"]
+ temp_pod.setStyle(dataToLoad["styleChoice"]) //Style is a variable that keeps track of what the pod is supposed to look like. It acts as an index to the GLOB.podstyles list in cargo.dm defines to get the proper icon/name/desc for the pod.
+ temp_pod.effectShrapnel = dataToLoad["effectShrapnel"] //If true, creates a cloud of shrapnel of a decided type and magnitude on landing
+ temp_pod.shrapnel_type = text2path(dataToLoad["shrapnelType"])
+ temp_pod.shrapnel_magnitude = dataToLoad["shrapnelMagnitude"]
+ temp_pod.effectStun = dataToLoad["effectStun"]//If true, stuns anyone under the pod when it launches until it lands, forcing them to get hit by the pod. Devilish!
+ temp_pod.effectLimb = dataToLoad["effectLimb"]//If true, pops off a limb (if applicable) from anyone caught under the pod when it lands
+ temp_pod.effectOrgans = dataToLoad["effectOrgans"]//If true, yeets the organs out of any bodies caught under the pod when it lands
+ temp_pod.bluespace = dataToLoad["effectBluespace"] //If true, the pod deletes (in a shower of sparks) after landing
+ temp_pod.effectStealth = dataToLoad["effectStealth"]//If true, a target icon isn't displayed on the turf where the pod will land
+ temp_pod.effectQuiet = dataToLoad["effectQuiet"] //The female sniper. If true, the pod makes no noise (including related explosions, opening sounds, etc)
+ temp_pod.effectMissile = dataToLoad["effectMissile"] //If true, the pod deletes the second it lands. If you give it an explosion, it will act like a missile exploding as it hits the ground
+ temp_pod.effectCircle = dataToLoad["effectCircle"] //If true, allows the pod to come in at any angle. Bit of a weird feature but whatever its here
+ effectBurst = dataToLoad["effectBurst"] //IOf true, launches five pods at once (with a very small delay between for added coolness), in a 3x3 area centered around the area
+ temp_pod.reversing = dataToLoad["effectReverse"] //If true, the pod will not send any items. Instead, after opening, it will close again (picking up items/mobs) and fly back to centcom
+ temp_pod.reverseOptionList = dataToLoad["reverseOptionList"]
+ specificTarget = dataToLoad["effectTarget"] //Launches the pod at the turf of a specific mob target, rather than wherever the user clicked. Useful for smites
+ temp_pod.adminNamed = dataToLoad["effectName"] //Determines whether or not the pod has been named by an admin. If true, the pod's name will not get overridden when the style of the pod changes (changing the style of the pod normally also changes the name+desc)
+ temp_pod.name = dataToLoad["podName"]
+ temp_pod.desc = dataToLoad["podDesc"]
+ effectAnnounce = dataToLoad["effectAnnounce"]
+ numTurfs = dataToLoad["numObjects"] //Counts the number of turfs that contain a launchable object in the centcom supplypod bay
+ temp_pod.fallingSound = dataToLoad["fallingSound"]//Admin sound to play as the pod falls
+ temp_pod.landingSound = dataToLoad["landingSound"]//Admin sound to play when the pod lands
+ temp_pod.openingSound = dataToLoad["openingSound"]//Admin sound to play when the pod opens
+ temp_pod.leavingSound = dataToLoad["leavingSound"]//Admin sound to play when the pod leaves
+ temp_pod.soundVolume = dataToLoad["soundVolume"] //Admin sound to play when the pod leaves
+ picking_dropoff_turf = FALSE
+ launcherActivated = FALSE
+ updateCursor()
+ refreshView()
+
+GLOBAL_DATUM_INIT(podlauncher, /datum/centcom_podlauncher, new)
+//Proc for admins to enable others to use podlauncher after roundend
+/datum/centcom_podlauncher/proc/give_podlauncher(mob/living/user, override)
+ if (SSticker.current_state < GAME_STATE_FINISHED)
+ return
+ if (!istype(user))
+ user = override
+ if (user)
+ setup(user)//setup the datum
+
+//Set the dropoff location and indicator to either a specific turf or somewhere in an area
+/datum/centcom_podlauncher/proc/setDropoff(target)
+ var/turf/target_turf
+ if (isturf(target))
+ target_turf = target
+ else if (isarea(target))
+ target_turf = pick(get_area_turfs(target))
+ else
+ CRASH("Improper type passed to setDropoff! Should be /turf or /area")
+ temp_pod.reverse_dropoff_coords = list(target_turf.x, target_turf.y, target_turf.z)
+ indicator.forceMove(target_turf)
+
+/obj/effect/hallucination/simple/supplypod_selector
+ name = "Supply Selector (Only you can see this)"
+ image_icon = 'icons/obj/supplypods_32x32.dmi'
+ image_state = "selector"
+ image_layer = FLY_LAYER
+ alpha = 150
+
+/obj/effect/hallucination/simple/dropoff_location
+ name = "Dropoff Location (Only you can see this)"
+ image_icon = 'icons/obj/supplypods_32x32.dmi'
+ image_state = "dropoff_indicator"
+ image_layer = FLY_LAYER
+ alpha = 0
diff --git a/code/modules/cargo/console.dm b/code/modules/cargo/console.dm
index d117be6f5b35c..9ea4162111c35 100644
--- a/code/modules/cargo/console.dm
+++ b/code/modules/cargo/console.dm
@@ -17,6 +17,7 @@
var/obj/item/radio/headset/radio
/// var that tracks message cooldown
var/message_cooldown
+ COOLDOWN_DECLARE(order_cooldown)
light_color = "#E2853D"//orange
@@ -39,7 +40,7 @@
/obj/machinery/computer/cargo/Destroy()
QDEL_NULL(radio)
- ..()
+ return ..()
/obj/machinery/computer/cargo/proc/get_export_categories()
. = EXPORT_CARGO
@@ -72,6 +73,7 @@
if(!ui)
ui = new(user, src, "Cargo")
ui.open()
+ ui.set_autoupdate(TRUE) // Account balance, shuttle status
/obj/machinery/computer/cargo/ui_data()
var/list/data = list()
@@ -94,7 +96,7 @@
for(var/datum/supply_order/SO in SSshuttle.shoppinglist)
data["cart"] += list(list(
"object" = SO.pack.name,
- "cost" = SO.pack.cost,
+ "cost" = SO.pack.get_cost(),
"id" = SO.id,
"orderer" = SO.orderer,
"paid" = !isnull(SO.paying_account) //paid by requester
@@ -104,7 +106,7 @@
for(var/datum/supply_order/SO in SSshuttle.requestlist)
data["requests"] += list(list(
"object" = SO.pack.name,
- "cost" = SO.pack.cost,
+ "cost" = SO.pack.get_cost(),
"orderer" = SO.orderer,
"reason" = SO.reason,
"id" = SO.id
@@ -127,7 +129,7 @@
continue
data["supplies"][P.group]["packs"] += list(list(
"name" = P.name,
- "cost" = P.cost,
+ "cost" = P.get_cost(),
"id" = pack,
"desc" = P.desc || P.name, // If there is a description, use it. Otherwise use the pack's name.
"small_item" = P.small_item,
@@ -136,7 +138,8 @@
return data
/obj/machinery/computer/cargo/ui_act(action, params, datum/tgui/ui)
- if(..())
+ . = ..()
+ if(.)
return
switch(action)
if("send")
@@ -171,6 +174,8 @@
say("The supply shuttle has been loaned to CentCom.")
. = TRUE
if("add")
+ if(!COOLDOWN_FINISHED(src, order_cooldown))
+ return
var/id = text2path(params["id"])
var/datum/supply_pack/pack = SSshuttle.supply_packs[id]
if(!istype(pack))
@@ -204,7 +209,10 @@
var/reason = ""
if(requestonly && !self_paid)
reason = stripped_input(usr, "Reason:", name, "")
- if(isnull(reason) || ..())
+ if(!reason)
+ return
+ if(CHAT_FILTER_CHECK(reason))
+ to_chat(usr, "You cannot send a message that contains a word prohibited in IC chat!")
return
var/turf/T = get_turf(src)
diff --git a/code/modules/cargo/exports.dm b/code/modules/cargo/exports.dm
index b5858b9b195b3..06e7204555896 100644
--- a/code/modules/cargo/exports.dm
+++ b/code/modules/cargo/exports.dm
@@ -99,17 +99,19 @@ Credit dupes that require a lot of manual work shouldn't be removed, unless they
/datum/export/New()
..()
- SSprocessing.processing += src
+ START_PROCESSING(SSprocessing, src)
init_cost = cost
export_types = typecacheof(export_types)
exclude_types = typecacheof(exclude_types)
/datum/export/Destroy()
- SSprocessing.processing -= src
+ STOP_PROCESSING(SSprocessing, src)
return ..()
/datum/export/process()
- ..()
+ . = ..()
+ if(!k_elasticity)
+ return PROCESS_KILL
cost *= NUM_E**(k_elasticity * (1/30))
if(cost > init_cost)
cost = init_cost
diff --git a/code/modules/cargo/exports/large_objects.dm b/code/modules/cargo/exports/large_objects.dm
index 03a52c7ea270c..54e70e13c9dd7 100644
--- a/code/modules/cargo/exports/large_objects.dm
+++ b/code/modules/cargo/exports/large_objects.dm
@@ -141,10 +141,10 @@
var/obj/machinery/portable_atmospherics/canister/C = O
var/worth = 10
- worth += C.air_contents.get_moles(/datum/gas/bz)*4
- worth += C.air_contents.get_moles(/datum/gas/stimulum)*100
- worth += C.air_contents.get_moles(/datum/gas/hypernoblium)*1000
- worth += C.air_contents.get_moles(/datum/gas/miasma)*10
- worth += C.air_contents.get_moles(/datum/gas/tritium)*5
- worth += C.air_contents.get_moles(/datum/gas/pluoxium)*5
+ worth += C.air_contents.get_moles(GAS_BZ)*4
+ worth += C.air_contents.get_moles(GAS_STIMULUM)*100
+ worth += C.air_contents.get_moles(GAS_HYPERNOB)*1000
+ worth += C.air_contents.get_moles(GAS_MIASMA)*10
+ worth += C.air_contents.get_moles(GAS_TRITIUM)*5
+ worth += C.air_contents.get_moles(GAS_PLUOXIUM)*5
return worth
diff --git a/code/modules/cargo/exports/tools.dm b/code/modules/cargo/exports/tools.dm
index 7bee3017d9f2a..7b98c0fc4c899 100644
--- a/code/modules/cargo/exports/tools.dm
+++ b/code/modules/cargo/exports/tools.dm
@@ -31,6 +31,10 @@
message = "of wirecutters"
export_types = list(/obj/item/wirecutters)
+/datum/export/powertool
+ cost = 15
+ unit_name = "powertool"
+ export_types = list(/obj/item/powertool)
/datum/export/weldingtool
cost = 5
diff --git a/code/modules/cargo/exports/xenobio.dm b/code/modules/cargo/exports/xenobio.dm
new file mode 100644
index 0000000000000..38ad39aebff0c
--- /dev/null
+++ b/code/modules/cargo/exports/xenobio.dm
@@ -0,0 +1,43 @@
+/datum/export/slime
+
+/datum/export/slime/get_cost(obj/O, allowed_categories = NONE, apply_elastic = TRUE)
+ var/costfromparent = ..()
+ if (istype(O,/obj/item/slime_extract))
+ var/obj/item/slime_extract/slimething = O
+ if (slimething.sparkly)
+ return costfromparent*2
+
+/datum/export/slime/grey
+ cost = 25
+ unit_name = "grey slime core"
+ export_types = list(/obj/item/slime_extract/grey)
+
+/datum/export/slime/common
+ cost = 60
+ unit_name = "common slime core"
+ export_types = list(/obj/item/slime_extract/metal,/obj/item/slime_extract/orange,/obj/item/slime_extract/purple,/obj/item/slime_extract/blue)
+
+/datum/export/slime/uncommon
+ cost = 100
+ unit_name = "uncommon slime core"
+ export_types = list(/obj/item/slime_extract/silver,/obj/item/slime_extract/darkblue,/obj/item/slime_extract/darkpurple,/obj/item/slime_extract/yellow)
+
+/datum/export/slime/rare
+ cost = 140
+ unit_name = "rare slime core"
+ export_types = list(/obj/item/slime_extract/gold,/obj/item/slime_extract/green,/obj/item/slime_extract/red,/obj/item/slime_extract/pink)
+
+/datum/export/slime/hypercharged
+ cost = 600
+ unit_name = "hypercharged slime core"
+ export_types = list(/obj/item/stock_parts/cell/high/slime/hypercharged)
+
+/datum/export/slime/epic
+ cost = 220
+ unit_name = "epic slime core"
+ export_types = list(/obj/item/slime_extract/black,/obj/item/slime_extract/cerulean,/obj/item/slime_extract/oil,/obj/item/slime_extract/sepia,/obj/item/slime_extract/pyrite,/obj/item/slime_extract/adamantine,/obj/item/slime_extract/lightpink,/obj/item/slime_extract/bluespace)
+
+/datum/export/slime/rainbow
+ cost = 500
+ unit_name = "rainbow slime core"
+ export_types = list(/obj/item/slime_extract/rainbow)
diff --git a/code/modules/cargo/expressconsole.dm b/code/modules/cargo/expressconsole.dm
index 90033b6cf288c..dcd7df0e86fda 100644
--- a/code/modules/cargo/expressconsole.dm
+++ b/code/modules/cargo/expressconsole.dm
@@ -1,15 +1,7 @@
-#define MAX_EMAG_ROCKETS 8
-#define BEACON_COST 500
-#define SP_LINKED 1
-#define SP_READY 2
-#define SP_LAUNCH 3
-#define SP_UNLINK 4
-#define SP_UNREADY 5
-
/obj/machinery/computer/cargo/express
name = "express supply console"
desc = "This console allows the user to purchase a package \
- with 1/40th of the delivery time: made possible by NanoTrasen's new \"1500mm Orbital Railgun\".\
+ with 1/40th of the delivery time: made possible by Nanotrasen's new \"1500mm Orbital Railgun\".\
All sales are near instantaneous - please choose carefully"
icon_screen = "supply_express"
circuit = /obj/item/circuitboard/computer/cargo/express
@@ -83,7 +75,7 @@
continue // i'd be right happy to
meme_pack_data[P.group]["packs"] += list(list(
"name" = P.name,
- "cost" = P.cost,
+ "cost" = P.get_cost(),
"id" = pack,
"desc" = P.desc || P.name // If there is a description, use it. Otherwise use the pack's name.
))
@@ -96,6 +88,7 @@
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "CargoExpress")
+ ui.set_autoupdate(TRUE) // Account balance
ui.open()
/obj/machinery/computer/cargo/express/ui_data(mob/user)
@@ -134,18 +127,22 @@
return data
/obj/machinery/computer/cargo/express/ui_act(action, params, datum/tgui/ui)
- . = ..()
- if(!isliving(usr))
+ if(action == "add")
+ action = "express_add" // Ignore parent's "add" action
+ . = ..(action, params, ui)
+ if(.)
return
switch(action)
if("LZCargo")
usingBeacon = FALSE
if (beacon)
beacon.update_status(SP_UNREADY) //ready light on beacon will turn off
+ . = TRUE
if("LZBeacon")
usingBeacon = TRUE
if (beacon)
beacon.update_status(SP_READY) //turns on the beacon's ready light
+ . = TRUE
if("printBeacon")
var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR)
if(D)
@@ -155,9 +152,14 @@
C.link_console(src, usr)//rather than in beacon's Initialize(), we can assign the computer to the beacon by reusing this proc)
printed_beacons++//printed_beacons starts at 0, so the first one out will be called beacon # 1
beacon.name = "Supply Pod Beacon #[printed_beacons]"
+ . = TRUE
- if("add")//Generate Supply Order first
+ if("express_add")//Generate Supply Order first
+ if(!COOLDOWN_FINISHED(src, order_cooldown))
+ return
+ if(usingBeacon && !(beacon && (isturf(beacon.loc) || ismob(beacon.loc))))
+ return
var/id = text2path(params["id"])
var/datum/supply_pack/pack = SSshuttle.supply_packs[id]
if(!istype(pack))
@@ -180,7 +182,7 @@
if(D)
points_to_check = D.account_balance
if(!(obj_flags & EMAGGED))
- if(SO.pack.cost <= points_to_check)
+ if(SO.pack.get_cost() <= points_to_check)
var/LZ
if (istype(beacon) && usingBeacon)//prioritize beacons over landing in cargobay
LZ = get_turf(beacon)
@@ -197,13 +199,14 @@
CHECK_TICK
if(empty_turfs?.len)
LZ = pick(empty_turfs)
- if (SO.pack.cost <= points_to_check && LZ)//we need to call the cost check again because of the CHECK_TICK call
- D.adjust_money(-SO.pack.cost)
- new /obj/effect/DPtarget(LZ, podType, SO)
+ if (SO.pack.get_cost() <= points_to_check && LZ)//we need to call the cost check again because of the CHECK_TICK call
+ new /obj/effect/pod_landingzone(LZ, podType, SO)
+ COOLDOWN_START(src, order_cooldown, ORDER_COOLDOWN)
+ D.adjust_money(-SO.pack.get_cost())
. = TRUE
update_icon()
else
- if(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS) <= points_to_check) // bulk discount :^)
+ if(SO.pack.get_cost() * (0.72*MAX_EMAG_ROCKETS) <= points_to_check) // bulk discount :^)
landingzone = GLOB.areas_by_type[pick(GLOB.the_station_areas)] //override default landing zone
for(var/turf/open/floor/T in landingzone.contents)
if(is_blocked_turf(T))
@@ -211,13 +214,14 @@
LAZYADD(empty_turfs, T)
CHECK_TICK
if(empty_turfs && empty_turfs.len)
- D.adjust_money(-(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS)))
+ D.adjust_money(-(SO.pack.get_cost() * (0.72*MAX_EMAG_ROCKETS)))
SO.generateRequisition(get_turf(src))
for(var/i in 1 to MAX_EMAG_ROCKETS)
var/LZ = pick(empty_turfs)
LAZYREMOVE(empty_turfs, LZ)
- new /obj/effect/DPtarget(LZ, podType, SO)
+ new /obj/effect/pod_landingzone(LZ, podType, SO)
+ COOLDOWN_START(src, order_cooldown, ORDER_COOLDOWN/2)
. = TRUE
update_icon()
CHECK_TICK
diff --git a/code/modules/cargo/gondolapod.dm b/code/modules/cargo/gondolapod.dm
index 7cc71e84d9f3b..90d915fdcf8ab 100644
--- a/code/modules/cargo/gondolapod.dm
+++ b/code/modules/cargo/gondolapod.dm
@@ -7,9 +7,9 @@
response_harm = "kicks"
faction = list("gondola")
turns_per_move = 10
- icon = 'icons/mob/gondolapod.dmi'
- icon_state = "gondolapod"
- icon_living = "gondolapod"
+ icon = 'icons/obj/supplypods.dmi'
+ icon_state = "gondola"
+ icon_living = "gondola"
pixel_x = -16//2x2 sprite
pixel_y = -5
layer = TABLE_LAYER//so that deliveries dont appear underneath it
@@ -29,24 +29,23 @@
name = linked_pod.name
. = ..()
-/mob/living/simple_animal/pet/gondola/gondolapod/update_icon_state()
+/mob/living/simple_animal/pet/gondola/gondolapod/update_overlays()
+ . = ..()
if(opened)
- icon_state = "gondolapod_open"
- else
- icon_state = "gondolapod"
+ . += "[icon_state]_open"
/mob/living/simple_animal/pet/gondola/gondolapod/verb/deliver()
set name = "Release Contents"
set category = "Gondola"
set desc = "Release any contents stored within your vast belly."
- linked_pod.open(src, forced = TRUE)
+ linked_pod.open_pod(src, forced = TRUE)
/mob/living/simple_animal/pet/gondola/gondolapod/examine(mob/user)
. = ..()
if (contents.len)
- . += "It looks like it hasn't made its delivery yet."
+ . += "It looks like it hasn't made its delivery yet."
else
- . += "It looks like it has already made its delivery."
+ . += "It looks like it has already made its delivery."
/mob/living/simple_animal/pet/gondola/gondolapod/verb/check()
set name = "Count Contents"
@@ -58,12 +57,12 @@
else
to_chat(src, "A closer look inside yourself reveals... nothing.")
-/mob/living/simple_animal/pet/gondola/gondolapod/proc/setOpened()
+/mob/living/simple_animal/pet/gondola/gondolapod/setOpened()
opened = TRUE
update_icon()
- addtimer(CALLBACK(src, .proc/setClosed), 50)
+ addtimer(CALLBACK(src, /atom/.proc/setClosed), 50)
-/mob/living/simple_animal/pet/gondola/gondolapod/proc/setClosed()
+/mob/living/simple_animal/pet/gondola/gondolapod/setClosed()
opened = FALSE
update_icon()
diff --git a/code/modules/cargo/packs.dm b/code/modules/cargo/packs.dm
index 609c3c4e3c6ef..d25d16f1f138f 100644
--- a/code/modules/cargo/packs.dm
+++ b/code/modules/cargo/packs.dm
@@ -3,7 +3,7 @@
var/group = ""
var/hidden = FALSE
var/contraband = FALSE
- var/cost = 700 // Minimum cost, or infinite points are possible.
+ var/cost = 400 // Minimum cost, or infinite points are possible. I've already had to fix it once because someone didn't listen. Don't be THAT person.
var/access = FALSE
var/access_any = FALSE
var/list/contains = null
@@ -33,6 +33,13 @@
fill(C)
return C
+/datum/supply_pack/proc/get_cost()
+ . = cost
+ if(HAS_TRAIT(SSstation, STATION_TRAIT_DISTANT_SUPPLY_LINES))
+ . *= 1.2
+ else if(HAS_TRAIT(SSstation, STATION_TRAIT_STRONG_SUPPLY_LINES))
+ . *= 0.8
+
/datum/supply_pack/proc/fill(obj/structure/closet/crate/C)
if (admin_spawned)
for(var/item in contains)
@@ -54,7 +61,7 @@
/datum/supply_pack/emergency/vehicle
name = "Biker Gang Kit" //TUNNEL SNAKES OWN THIS TOWN
desc = "TUNNEL SNAKES OWN THIS TOWN. Contains an unbranded All Terrain Vehicle, and a complete gang outfit -- consists of black gloves, a menacing skull bandanna, and a SWEET leather overcoat!"
- cost = 2000
+ cost = 1500
contraband = TRUE
contains = list(/obj/vehicle/ridden/atv,
/obj/item/key,
@@ -68,7 +75,7 @@
/datum/supply_pack/emergency/bio
name = "Biological Emergency Crate"
desc = "This crate holds 2 full bio suits which will protect you from viruses."
- cost = 2000
+ cost = 1500
contains = list(/obj/item/clothing/head/bio_hood,
/obj/item/clothing/head/bio_hood,
/obj/item/clothing/suit/bio_suit,
@@ -83,7 +90,7 @@
/datum/supply_pack/emergency/equipment
name = "Emergency Bot/Internals Crate"
desc = "Explosions got you down? These supplies are guaranteed to patch up holes, in stations and people alike! Comes with two floorbots, two medbots, five oxygen masks and five small oxygen tanks."
- cost = 3500
+ cost = 1500
contains = list(/mob/living/simple_animal/bot/floorbot,
/mob/living/simple_animal/bot/floorbot,
/mob/living/simple_animal/bot/medbot,
@@ -104,7 +111,7 @@
/datum/supply_pack/emergency/medical
name = "Emergency Medical Crate"
desc = "For when the shit hits the fan and medical can't keep up. Comes with a 7x5 Medical capsule and 2 Medibots for emergencies."
- cost = 3500
+ cost = 1100
contains = list(/mob/living/simple_animal/bot/medbot,
/mob/living/simple_animal/bot/medbot,
/obj/item/survivalcapsule/medical)
@@ -114,7 +121,7 @@
/datum/supply_pack/emergency/bomb
name = "Explosive Emergency Crate"
desc = "Science gone bonkers? Beeping behind the airlock? Buy now and be the hero the station des... I mean needs! (time not included)"
- cost = 1500
+ cost = 800
contains = list(/obj/item/clothing/head/bomb_hood,
/obj/item/clothing/suit/bomb_suit,
/obj/item/clothing/mask/gas,
@@ -125,44 +132,53 @@
/datum/supply_pack/emergency/firefighting
name = "Firefighting Crate"
- desc = "Only you can prevent station fires. Partner up with two firefighter suits, gas masks, flashlights, large oxygen tanks, extinguishers, and hardhats!"
- cost = 1000
+ desc = "Only you can prevent station fires. Partner up with three firefighter suits, gas masks, flashlights, large oxygen tanks, extinguishers, and hardhats!"
+ cost = 800
contains = list(/obj/item/clothing/suit/fire/firefighter,
+ /obj/item/clothing/suit/fire/firefighter,
/obj/item/clothing/suit/fire/firefighter,
/obj/item/clothing/mask/gas,
/obj/item/clothing/mask/gas,
+ /obj/item/clothing/mask/gas,
+ /obj/item/flashlight,
/obj/item/flashlight,
/obj/item/flashlight,
/obj/item/tank/internals/oxygen/red,
/obj/item/tank/internals/oxygen/red,
+ /obj/item/tank/internals/oxygen/red,
/obj/item/extinguisher/advanced,
/obj/item/extinguisher/advanced,
+ /obj/item/extinguisher/advanced,
+ /obj/item/clothing/head/hardhat/red,
/obj/item/clothing/head/hardhat/red,
/obj/item/clothing/head/hardhat/red)
crate_name = "firefighting crate"
/datum/supply_pack/emergency/atmostank
name = "Firefighting Tank Backpack"
- desc = "Mow down fires with this high-capacity fire fighting tank backpack. Requires Atmospherics access to open."
+ desc = "Mow down fires with this high-capacity fire fighting tank backpack."
cost = 1000
- access = ACCESS_ATMOSPHERICS
contains = list(/obj/item/watertank/atmos)
crate_name = "firefighting backpack crate"
crate_type = /obj/structure/closet/crate/secure
/datum/supply_pack/emergency/internals
name = "Internals Crate"
- desc = "Master your life energy and control your breathing with three breath masks, three emergency oxygen tanks and three large air tanks."//IS THAT A
- cost = 1000
+ desc = "Master your life energy and control your breathing with four breath masks, four emergency oxygen tanks and four large air tanks."//IS THAT A
+ cost = 800
contains = list(/obj/item/clothing/mask/gas,
/obj/item/clothing/mask/gas,
/obj/item/clothing/mask/gas,
+ /obj/item/clothing/mask/gas,
+ /obj/item/clothing/mask/breath,
/obj/item/clothing/mask/breath,
/obj/item/clothing/mask/breath,
/obj/item/clothing/mask/breath,
/obj/item/tank/internals/emergency_oxygen,
/obj/item/tank/internals/emergency_oxygen,
/obj/item/tank/internals/emergency_oxygen,
+ /obj/item/tank/internals/emergency_oxygen,
+ /obj/item/tank/internals/air,
/obj/item/tank/internals/air,
/obj/item/tank/internals/air,
/obj/item/tank/internals/air)
@@ -172,52 +188,14 @@
/datum/supply_pack/emergency/metalfoam
name = "Metal Foam Grenade Crate"
desc = "Seal up those pesky hull breaches with 7 Metal Foam Grenades."
- cost = 1000
+ cost = 700
contains = list(/obj/item/storage/box/metalfoam)
crate_name = "metal foam grenade crate"
-/datum/supply_pack/emergency/syndicate
- name = "NULL_ENTRY"
- desc = "(#@&^$THIS PACKAGE CONTAINS 30TC WORTH OF SOME RANDOM SYNDICATE GEAR WE HAD LYING AROUND THE WAREHOUSE. GIVE EM HELL, OPERATIVE, BUT DON'T GET GREEDY- ORDER TOO MANY AND WE'LL BE SENDING OUR DEADLIEST ENFORCERS TO INVESTIGATE@&!*() "
- hidden = TRUE
- cost = 20000
- contains = list()
- crate_name = "emergency crate"
- crate_type = /obj/structure/closet/crate/internals
- dangerous = TRUE
- var/beepsky_chance = -1
- var/level = 1
-
-/datum/supply_pack/emergency/syndicate/fill(obj/structure/closet/crate/C)
- var/crate_value = 30
- var/list/uplink_items = get_uplink_items(SSticker.mode)
- beepsky_chance += min(level, 5) //1% chance per crate an item will be replaced with a beepsky and the crate stops spawning items. Doesnt act as a hardcap, making nullcrates far riskier and less predictable
- while(crate_value)
- if(prob(beepsky_chance) && prob(50))
- new /mob/living/simple_animal/bot/secbot/grievous/nullcrate(C)
- crate_value = 0
- beepsky_chance = 0
- level += 1
- var/category = pick(uplink_items)
- var/item = pick(uplink_items[category])
- var/datum/uplink_item/I = uplink_items[category][item]
- if(!I.surplus_nullcrates || prob(100 - I.surplus_nullcrates))
- continue
- if(crate_value < I.cost)
- continue
- crate_value -= I.cost
- new I.item(C)
- var/datum/round_event_control/operative/loneop = locate(/datum/round_event_control/operative) in SSevents.control
- if(istype(loneop))
- loneop.weight += 7
- message_admins("a NULL_ENTRY crate has shipped, increasing the weight of the Lone Operative event to [loneop.weight]")
- log_game("a NULL_ENTRY crate has shipped, increasing the weight of the Lone Operative event to [loneop.weight]")
-
/datum/supply_pack/emergency/plasma_spacesuit
name = "Plasmaman Space Envirosuits"
desc = "Contains two space-worthy envirosuits for Plasmamen. Order now and we'll throw in two free helmets! Requires EVA access to open."
- cost = 4000
- access = ACCESS_EVA
+ cost = 1400 // 500 per suit, equal to normal space suits.
contains = list(/obj/item/clothing/suit/space/eva/plasmaman,
/obj/item/clothing/suit/space/eva/plasmaman,
/obj/item/clothing/head/helmet/space/plasmaman,
@@ -227,20 +205,23 @@
/datum/supply_pack/emergency/plasmaman
name = "Plasmaman Supply Kit"
- desc = "Keep those Plasmamen alive with two sets of Plasmaman outfits. Each set contains a plasmaman jumpsuit, internals tank, and helmet."
- cost = 2000
+ desc = "Keep those Plasmamen alive with three sets of Plasmaman outfits. Each set contains a plasmaman jumpsuit, internals tank, and helmet."
+ cost = 700 //50 credits per suit.
contains = list(/obj/item/clothing/under/plasmaman,
/obj/item/clothing/under/plasmaman,
+ /obj/item/clothing/under/plasmaman,
+ /obj/item/tank/internals/plasmaman/belt/full,
/obj/item/tank/internals/plasmaman/belt/full,
/obj/item/tank/internals/plasmaman/belt/full,
/obj/item/clothing/head/helmet/space/plasmaman,
+ /obj/item/clothing/head/helmet/space/plasmaman,
/obj/item/clothing/head/helmet/space/plasmaman)
crate_name = "plasmaman supply kit"
/datum/supply_pack/emergency/radiation
name = "Radiation Protection Crate"
desc = "Survive the Nuclear Apocalypse and Supermatter Engine alike with two sets of Radiation suits. Each set contains a helmet, suit, and Geiger counter. We'll even throw in a bottle of vodka and some glasses too, considering the life-expectancy of people who order this."
- cost = 1000
+ cost = 800
contains = list(/obj/item/clothing/head/radiation,
/obj/item/clothing/head/radiation,
/obj/item/clothing/suit/radiation,
@@ -255,21 +236,35 @@
/datum/supply_pack/emergency/spacesuit
name = "Space Suit Crate"
- desc = "Contains one aging suit from Space-Goodwill and a jetpack. Requires EVA access to open."
- cost = 2500
- access = ACCESS_EVA
+ desc = "Contains one aging suit from Space-Goodwill."
+ cost = 900 //500 credits per 1 suit
contains = list(/obj/item/clothing/suit/space,
/obj/item/clothing/head/helmet/space,
- /obj/item/clothing/mask/breath,
- /obj/item/tank/jetpack/carbondioxide)
+ /obj/item/clothing/mask/breath)
crate_name = "space suit crate"
crate_type = /obj/structure/closet/crate/secure
+/datum/supply_pack/emergency/spacesuit/bulk
+ name = "Bulk Space Suit Crate"
+ desc = "Contains three aging suits from Space-Goodwill."
+ cost = 1600 //20% discount
+ contains = list(/obj/item/clothing/suit/space,
+ /obj/item/clothing/head/helmet/space,
+ /obj/item/clothing/mask/breath,
+ /obj/item/clothing/suit/space,
+ /obj/item/clothing/head/helmet/space,
+ /obj/item/clothing/mask/breath,
+ /obj/item/clothing/suit/space,
+ /obj/item/clothing/head/helmet/space,
+ /obj/item/clothing/mask/breath)
+ crate_name = "bulk space suit crate"
+ crate_type = /obj/structure/closet/crate/secure
+
/datum/supply_pack/emergency/specialops
name = "Special Ops Supplies"
- desc = "(*!&@#TOO CHEAP FOR THAT NULL_ENTRY, HUH OPERATIVE? WELL, THIS LITTLE ORDER CAN STILL HELP YOU OUT IN A PINCH. CONTAINS A BOX OF FIVE EMP GRENADES, THREE SMOKEBOMBS, AN INCENDIARY GRENADE, AND A \"SLEEPY PEN\" FULL OF NICE TOXINS!#@*$"
+ desc = "(*!&@#SAD ABOUT THAT NULL_ENTRY, HUH OPERATIVE? WELL, THIS LITTLE ORDER CAN STILL HELP YOU OUT IN A PINCH. CONTAINS A BOX OF FIVE EMP GRENADES, THREE SMOKEBOMBS, AN INCENDIARY GRENADE, AND A \"SLEEPY PEN\" FULL OF NICE TOXINS!#@*$"
hidden = TRUE
- cost = 2000
+ cost = 800
contains = list(/obj/item/storage/box/emps,
/obj/item/grenade/smokebomb,
/obj/item/grenade/smokebomb,
@@ -282,7 +277,7 @@
/datum/supply_pack/emergency/weedcontrol
name = "Weed Control Crate"
desc = "Keep those invasive species OUT. Contains a scythe, gasmask, and two anti-weed chemical grenades. Warranty void if used on ambrosia. Requires Hydroponics access to open."
- cost = 1500
+ cost = 800
access = ACCESS_HYDROPONICS
contains = list(/obj/item/scythe,
/obj/item/clothing/mask/gas,
@@ -300,30 +295,16 @@
access = ACCESS_SECURITY
crate_type = /obj/structure/closet/crate/secure/gear
-/datum/supply_pack/security/ammo
- name = "Ammo Crate"
- desc = "Contains two 20-round magazines for the WT-550 Auto Rifle, three boxes of buckshot ammo, three boxes of rubber ammo and special .38 speedloarders. Requires Security access to open."
- cost = 2500
- contains = list(/obj/item/ammo_box/magazine/wt550m9,
- /obj/item/ammo_box/magazine/wt550m9,
- /obj/item/storage/box/lethalshot,
- /obj/item/storage/box/lethalshot,
- /obj/item/storage/box/lethalshot,
- /obj/item/storage/box/rubbershot,
- /obj/item/storage/box/rubbershot,
- /obj/item/storage/box/rubbershot,
- /obj/item/ammo_box/c38/trac,
- /obj/item/ammo_box/c38/hotshot,
- /obj/item/ammo_box/c38/iceblox)
- crate_name = "ammo crate"
-
/datum/supply_pack/security/armor
name = "Armor Crate"
- desc = "Three vests of well-rounded, decently-protective armor. Requires Security access to open."
- cost = 1000
+ desc = "Three vests of well-rounded, decently-protective armor and 3 brain buckets. Requires Security access to open."
+ cost = 1500
contains = list(/obj/item/clothing/suit/armor/vest,
/obj/item/clothing/suit/armor/vest,
- /obj/item/clothing/suit/armor/vest)
+ /obj/item/clothing/suit/armor/vest,
+ /obj/item/clothing/head/helmet/sec,
+ /obj/item/clothing/head/helmet/sec,
+ /obj/item/clothing/head/helmet/sec)
crate_name = "armor crate"
/datum/supply_pack/security/disabler
@@ -335,10 +316,20 @@
/obj/item/gun/energy/disabler)
crate_name = "disabler crate"
+/datum/supply_pack/security/dumdum
+ name = ".38 DumDum Speedloader"
+ desc = "Contains one speedloader of .38 DumDum ammunition, good for embedding in soft targets. Requires Security or Forensics access to open."
+ cost = 1200
+ access = FALSE
+ small_item = TRUE
+ access_any = list(ACCESS_SECURITY, ACCESS_FORENSICS_LOCKERS)
+ contains = list(/obj/item/ammo_box/c38/dumdum)
+ crate_name = ".38 match crate"
+
/datum/supply_pack/security/forensics
name = "Forensics Crate"
desc = "Stay hot on the criminal's heels with Nanotrasen's Detective Essentials(tm). Contains a forensics scanner, six evidence bags, camera, tape recorder, white crayon, and of course, a fedora. Requires Security access to open."
- cost = 2000
+ cost = 1700
contains = list(/obj/item/detective_scanner,
/obj/item/storage/box/evidence,
/obj/item/camera,
@@ -347,24 +338,25 @@
/obj/item/clothing/head/fedora/det_hat)
crate_name = "forensics crate"
-/datum/supply_pack/security/helmets
- name = "Helmets Crate"
- desc = "Contains three standard-issue brain buckets. Requires Security access to open."
- cost = 1000
- contains = list(/obj/item/clothing/head/helmet/sec,
- /obj/item/clothing/head/helmet/sec,
- /obj/item/clothing/head/helmet/sec)
- crate_name = "helmet crate"
-
/datum/supply_pack/security/laser
name = "Lasers Crate"
desc = "Contains three lethal, high-energy laser guns. Requires Security access to open."
- cost = 2000
+ cost = 1800
contains = list(/obj/item/gun/energy/laser,
/obj/item/gun/energy/laser,
/obj/item/gun/energy/laser)
crate_name = "laser crate"
+/datum/supply_pack/security/match
+ name = ".38 Match Grade Speedloader"
+ desc = "Contains one speedloader of match grade .38 ammunition, perfect for showing off trickshots. Requires Security or Forensics access to open."
+ cost = 1200
+ access = FALSE
+ small_item = TRUE
+ access_any = list(ACCESS_SECURITY, ACCESS_FORENSICS_LOCKERS)
+ contains = list(/obj/item/ammo_box/c38/match)
+ crate_name = ".38 match crate"
+
/datum/supply_pack/security/securitybarriers
name = "Security Barrier Grenades"
desc = "Stem the tide with four Security Barrier grenades. Requires Security access to open."
@@ -372,13 +364,13 @@
/obj/item/grenade/barrier,
/obj/item/grenade/barrier,
/obj/item/grenade/barrier)
- cost = 2000
+ cost = 1500
crate_name = "security barriers crate"
/datum/supply_pack/security/securityclothes
name = "Security Clothing Crate"
desc = "Contains appropriate outfits for the station's private security force. Contains outfits for the Warden, Head of Security, and two Security Officers. Each outfit comes with a rank-appropriate jumpsuit, suit, and beret. Requires Security access to open."
- cost = 3000
+ cost = 1700
contains = list(/obj/item/clothing/under/rank/security/officer/formal,
/obj/item/clothing/under/rank/security/officer/formal,
/obj/item/clothing/suit/security/officer,
@@ -393,10 +385,24 @@
/obj/item/clothing/head/beret/sec/navyhos)
crate_name = "security clothing crate"
+/datum/supply_pack/security/stingpack
+ name = "Stingbang Grenade Pack"
+ desc = "Contains five \"stingbang\" grenades, perfect for stopping riots and playing morally unthinkable pranks. Requires Security access to open."
+ cost = 2500
+ contains = list(/obj/item/storage/box/stingbangs)
+ crate_name = "stingbang grenade pack crate"
+
+/datum/supply_pack/security/stingpack/single
+ name = "Stingbang Single-Pack"
+ desc = "Contains one \"stingbang\" grenade, perfect for playing meanhearted pranks. Requires Security access to open."
+ cost = 1400
+ small_item = TRUE
+ contains = list(/obj/item/grenade/stingbang)
+
/datum/supply_pack/security/supplies
name = "Security Supplies Crate"
desc = "Contains seven flashbangs, seven teargas grenades, six flashes, and seven handcuffs. Requires Security access to open."
- cost = 1000
+ cost = 700
contains = list(/obj/item/storage/box/flashbangs,
/obj/item/storage/box/teargas,
/obj/item/storage/box/flashes,
@@ -406,14 +412,14 @@
/datum/supply_pack/security/vending/security
name = "SecTech Supply Crate"
desc = "Officer Paul bought all the donuts? Then refill the security vendor with ths crate."
- cost = 1500
+ cost = 1200
contains = list(/obj/item/vending_refill/security)
crate_name = "SecTech supply crate"
/datum/supply_pack/security/firingpins
name = "Standard Firing Pins Crate"
desc = "Upgrade your arsenal with 10 standard firing pins. Requires Security access to open."
- cost = 2000
+ cost = 1700
contains = list(/obj/item/storage/box/firingpins,
/obj/item/storage/box/firingpins)
crate_name = "firing pins crate"
@@ -421,7 +427,7 @@
/datum/supply_pack/security/firingpins/paywall
name = "Paywall Firing Pins Crate"
desc = "Specialized firing pins with a built-in configurable paywall. Requires Security access to open."
- cost = 2500
+ cost = 1700
contains = list(/obj/item/storage/box/firingpins/paywall,
/obj/item/storage/box/firingpins/paywall)
crate_name = "paywall firing pins crate"
@@ -429,7 +435,7 @@
/datum/supply_pack/security/justiceinbound
name = "Standard Justice Enforcer Crate"
desc = "This is it. The Bee's Knees. The Creme of the Crop. The Pick of the Litter. The best of the best of the best. The Crown Jewel of Nanotrasen. The Alpha and the Omega of security headwear. Guaranteed to strike fear into the hearts of each and every criminal aboard the station. Also comes with a security gasmask. Requires Security access to open."
- cost = 6000 //justice comes at a price. An expensive, noisy price.
+ cost = 5700 //justice comes at a price. An expensive, noisy price.
contraband = TRUE
contains = list(/obj/item/clothing/head/helmet/justice,
/obj/item/clothing/mask/gas/sechailer)
@@ -438,7 +444,7 @@
/datum/supply_pack/security/baton
name = "Stun Batons Crate"
desc = "Arm the Civil Protection Forces with three stun batons. Batteries included. Requires Security access to open."
- cost = 1000
+ cost = 900
contains = list(/obj/item/melee/baton/loaded,
/obj/item/melee/baton/loaded,
/obj/item/melee/baton/loaded)
@@ -446,9 +452,10 @@
/datum/supply_pack/security/wall_flash
name = "Wall-Mounted Flash Crate"
- desc = "Contains four wall-mounted flashes. Requires Security access to open."
- cost = 1000
+ desc = "Contains five wall-mounted flashes. Requires Security access to open."
+ cost = 800
contains = list(/obj/item/storage/box/wall_flash,
+ /obj/item/storage/box/wall_flash,
/obj/item/storage/box/wall_flash,
/obj/item/storage/box/wall_flash,
/obj/item/storage/box/wall_flash)
@@ -463,40 +470,50 @@
access = ACCESS_ARMORY
crate_type = /obj/structure/closet/crate/secure/weapon
+/datum/supply_pack/security/ammo
+ name = "Ammo Crate"
+ desc = "Contains two 20-round magazines for the WT-550 Auto Rifle, three boxes of buckshot ammo, three boxes of rubber ammo and special .38 speedloaders. Requires Security access to open."
+ cost = 2500
+ contains = list(/obj/item/ammo_box/magazine/wt550m9,
+ /obj/item/ammo_box/magazine/wt550m9,
+ /obj/item/storage/box/lethalshot,
+ /obj/item/storage/box/lethalshot,
+ /obj/item/storage/box/lethalshot,
+ /obj/item/storage/box/rubbershot,
+ /obj/item/storage/box/rubbershot,
+ /obj/item/storage/box/rubbershot,
+ /obj/item/ammo_box/c38/trac,
+ /obj/item/ammo_box/c38/hotshot,
+ /obj/item/ammo_box/c38/iceblox)
+ crate_name = "ammo crate"
+
/datum/supply_pack/security/armory/bulletarmor
name = "Bulletproof Armor Crate"
desc = "Contains three sets of bulletproof armor. Guaranteed to reduce a bullet's stopping power by over half. Requires Armory access to open."
- cost = 1500
+ cost = 1200
contains = list(/obj/item/clothing/suit/armor/bulletproof,
/obj/item/clothing/suit/armor/bulletproof,
/obj/item/clothing/suit/armor/bulletproof)
crate_name = "bulletproof armor crate"
-/datum/supply_pack/security/armory/cling_test
- name = "Changeling Testing Kit"
- desc = "Contains a single bottle of concentrated BZ, used for detecting and incapacitating changelings. Due to the rarity of this chemical, the cost is extortionate, and security personnel are recommended to visit their local chemistry department instead if possible. Requires Armory access to open."
- cost = 10000
- contains = list(/obj/item/reagent_containers/glass/bottle/concentrated_bz)
- crate_name = "Changeling testing kit crate"
-
/datum/supply_pack/security/armory/chemimp
name = "Chemical Implants Crate"
desc = "Contains five Remote Chemical implants. Requires Armory access to open."
- cost = 2000
+ cost = 700
contains = list(/obj/item/storage/box/chemimp)
crate_name = "chemical implant crate"
/datum/supply_pack/security/armory/combatknives_single
name = "Combat Knife Single-Pack"
desc = "Contains one sharpened combat knive. Guaranteed to fit snugly inside any Nanotrasen-standard boot. Requires Armory access to open."
- cost = 1200
+ cost = 700 // 300 credits per 1 knife
small_item = TRUE
contains = list(/obj/item/kitchen/knife/combat)
/datum/supply_pack/security/armory/combatknives
name = "Combat Knives Crate"
desc = "Contains three sharpened combat knives. Each knife guaranteed to fit snugly inside any Nanotrasen-standard boot. Requires Armory access to open."
- cost = 3000
+ cost = 1120 //20% discount
contains = list(/obj/item/kitchen/knife/combat,
/obj/item/kitchen/knife/combat,
/obj/item/kitchen/knife/combat)
@@ -505,7 +522,7 @@
/datum/supply_pack/security/armory/ballistic_single
name = "Combat Shotgun Single-Pack"
desc = "For when the enemy absolutely needs to be replaced with lead. Contains one Aussec-designed Combat Shotgun, and one Shotgun Bandolier. Requires Armory access to open."
- cost = 3200
+ cost = 2900 //2500 credits per shotgun
small_item = TRUE
contains = list(/obj/item/gun/ballistic/shotgun/automatic/combat,
/obj/item/storage/belt/bandolier)
@@ -513,7 +530,7 @@
/datum/supply_pack/security/armory/ballistic
name = "Combat Shotguns Crate"
desc = "For when the enemy absolutely needs to be replaced with lead. Contains three Aussec-designed Combat Shotguns, and three Shotgun Bandoliers. Requires Armory access to open."
- cost = 8000
+ cost = 6400 //20% discount
contains = list(/obj/item/gun/ballistic/shotgun/automatic/combat,
/obj/item/gun/ballistic/shotgun/automatic/combat,
/obj/item/gun/ballistic/shotgun/automatic/combat,
@@ -525,13 +542,13 @@
/datum/supply_pack/security/armory/riot_shotgun_single
name = "Riot Shotgun Single-Pack"
desc = "When the clown's slipped you one time too many. Requires armory access to open."
- cost = 2500
+ cost = 2200 //1800 credits per shotgun
contains = list(/obj/item/gun/ballistic/shotgun/riot)
/datum/supply_pack/security/armory/riot_shotgun
name = "Riot Shotguns Crate"
desc = "For when the greytide gets out of hand. Contains 3 riot shotguns. Requires armory access to open."
- cost = 6500
+ cost = 4720 //20% discount
contains = list(/obj/item/gun/ballistic/shotgun/riot,
/obj/item/gun/ballistic/shotgun/riot,
/obj/item/gun/ballistic/shotgun/riot)
@@ -546,32 +563,34 @@
crate_name = "\improper DRAGnet crate"
/datum/supply_pack/security/armory/energy_single
- name = "Energy Guns Single-Pack"
+ name = "Energy Gun Single-Pack"
desc = "Contains one Energy Gun, capable of firing both nonlethal and lethal blasts of light. Requires Armory access to open."
- cost = 1500
+ cost = 1200
small_item = TRUE
contains = list(/obj/item/gun/energy/e_gun)
+ crate_name = "single energy gun crate"
/datum/supply_pack/security/armory/energy
- name = "Energy Guns Crate"
- desc = "Contains two Energy Guns, capable of firing both nonlethal and lethal blasts of light. Requires Armory access to open."
- cost = 2500
+ name = "Bulk Energy Guns Crate"
+ desc = "Contains three Energy Guns, capable of firing both nonlethal and lethal blasts of light. Requires Armory access to open."
+ cost = 2320 //20%
contains = list(/obj/item/gun/energy/e_gun,
+ /obj/item/gun/energy/e_gun,
/obj/item/gun/energy/e_gun)
- crate_name = "energy gun crate"
+ crate_name = "bulk energy guns crate"
crate_type = /obj/structure/closet/crate/secure/plasma
/datum/supply_pack/security/armory/exileimp
name = "Exile Implants Crate"
desc = "Contains five Exile implants. Requires Armory access to open."
- cost = 3000
+ cost = 2700
contains = list(/obj/item/storage/box/exileimp)
crate_name = "exile implant crate"
/datum/supply_pack/security/armory/fire
name = "Incendiary Weapons Crate"
desc = "Burn, baby burn. Contains three incendiary grenades, three plasma canisters, and a flamethrower. Requires Armory access to open."
- cost = 1500
+ cost = 1200
access = ACCESS_HEADS
contains = list(/obj/item/flamethrower/full,
/obj/item/tank/internals/plasma,
@@ -591,7 +610,7 @@
contains = list(/obj/item/survivalcapsule/barricade,
/obj/item/survivalcapsule/barricade,
/obj/item/survivalcapsule/barricade)
- cost = 2500
+ cost = 2000
crate_name = "security barriers crate XL"
/datum/supply_pack/security/armory/mindshield
@@ -604,7 +623,7 @@
/datum/supply_pack/security/armory/trackingimp
name = "Tracking Implants Crate"
desc = "Contains four tracking implants and three tracking speedloaders of tracing .38 ammo. Requires Armory access to open."
- cost = 2000
+ cost = 1200
contains = list(/obj/item/storage/box/trackimp,
/obj/item/ammo_box/c38/trac,
/obj/item/ammo_box/c38/trac,
@@ -622,26 +641,20 @@
/datum/supply_pack/security/armory/riotarmor
name = "Riot Armor Crate"
- desc = "Contains three sets of heavy body armor. Advanced padding protects against close-ranged weaponry, making melee attacks feel only half as potent to the user. Requires Armory access to open."
- cost = 1500
+ desc = "Contains three sets of heavy body armor and helmets. Advanced padding protects against close-ranged weaponry, making melee attacks feel only half as potent to the user. Requires Armory access to open."
+ cost = 2200
contains = list(/obj/item/clothing/suit/armor/riot,
/obj/item/clothing/suit/armor/riot,
- /obj/item/clothing/suit/armor/riot)
- crate_name = "riot armor crate"
-
-/datum/supply_pack/security/armory/riothelmets
- name = "Riot Helmets Crate"
- desc = "Contains three riot helmets. Requires Armory access to open."
- cost = 1500
- contains = list(/obj/item/clothing/head/helmet/riot,
+ /obj/item/clothing/suit/armor/riot,
+ /obj/item/clothing/head/helmet/riot,
/obj/item/clothing/head/helmet/riot,
/obj/item/clothing/head/helmet/riot)
- crate_name = "riot helmets crate"
+ crate_name = "riot armor crate"
/datum/supply_pack/security/armory/riotshields
name = "Riot Shields Crate"
desc = "For when the greytide gets really uppity. Contains three riot shields. Requires Armory access to open."
- cost = 2000
+ cost = 1500
contains = list(/obj/item/shield/riot,
/obj/item/shield/riot,
/obj/item/shield/riot)
@@ -650,7 +663,7 @@
/datum/supply_pack/security/armory/russian
name = "Russian Surplus Crate"
desc = "Hello Comrade, we have the most modern russian military equipment the black market can offer, for the right price of course. Sadly we couldnt remove the lock so it requires Armory access to open."
- cost = 5000
+ cost = 4000
contraband = TRUE
contains = list(/obj/item/reagent_containers/food/snacks/rationpack,
/obj/item/ammo_box/a762,
@@ -672,10 +685,11 @@
for(var/i in 1 to 10)
var/item = pick(contains)
new item(C)
+
/datum/supply_pack/security/armory/smartmine
name = "Smart Mine Crate"
desc = "Contains three nonlethal pressure activated stun mines capable of ignoring mindshieled personnel. Requires Armory access to open."
- cost = 4000
+ cost = 2500
contains = list(/obj/item/deployablemine/smartstun,
/obj/item/deployablemine/smartstun,
/obj/item/deployablemine/smartstun)
@@ -684,17 +698,18 @@
/datum/supply_pack/security/armory/stunmine
name = "Stun Mine Crate"
desc = "Contains five nonlethal pressure activated stun mines. Requires Armory access to open."
- cost = 2500
+ cost = 2000
contains = list(/obj/item/deployablemine/stun,
/obj/item/deployablemine/stun,
/obj/item/deployablemine/stun,
/obj/item/deployablemine/stun,
/obj/item/deployablemine/stun)
crate_name = "stun mine create"
+
/datum/supply_pack/security/armory/swat
name = "SWAT Crate"
desc = "Contains two fullbody sets of tough, fireproof, pressurized suits designed in a joint effort by IS-ERI and Nanotrasen. Each set contains a suit, helmet, mask, combat belt, and combat gloves. Requires Armory access to open."
- cost = 6000
+ cost = 5000
contains = list(/obj/item/clothing/head/helmet/swat/nanotrasen,
/obj/item/clothing/head/helmet/swat/nanotrasen,
/obj/item/clothing/suit/space/swat,
@@ -710,34 +725,28 @@
/datum/supply_pack/security/armory/wt550_single
name = "WT-550 Auto Rifle Single-Pack"
desc = "Contains one high-powered, semiautomatic rifles chambered in 4.6x30mm. Requires Armory access to open."
- cost = 2000
+ cost = 1600 // 1200 per 1 gun
contains = list(/obj/item/gun/ballistic/automatic/wt550)
small_item = TRUE
/datum/supply_pack/security/armory/wt550
name = "WT-550 Auto Rifle Crate"
desc = "Contains two high-powered, semiautomatic rifles chambered in 4.6x30mm. Requires Armory access to open."
- cost = 3500
+ cost = 3280 //20%
contains = list(/obj/item/gun/ballistic/automatic/wt550,
+ /obj/item/gun/ballistic/automatic/wt550,
/obj/item/gun/ballistic/automatic/wt550)
crate_name = "auto rifle crate"
/datum/supply_pack/security/armory/wt550ammo
name = "WT-550 Auto Rifle Ammo Crate"
desc = "Contains four 20-round magazine for the WT-550 Auto Rifle. Each magazine is designed to facilitate rapid tactical reloads. Requires Armory access to open."
- cost = 3000
+ cost = 1500
contains = list(/obj/item/ammo_box/magazine/wt550m9,
/obj/item/ammo_box/magazine/wt550m9,
/obj/item/ammo_box/magazine/wt550m9,
/obj/item/ammo_box/magazine/wt550m9)
-/datum/supply_pack/security/armory/wt550ammo_single
- name = "WT-550 Auto Rifle Ammo Single-Pack"
- desc = "Contains a 20-round magazine for the WT-550 Auto Rifle. Each magazine is designed to facilitate rapid tactical reloads. Requires Armory access to open."
- cost = 750 //one of the few single-pack items that who's price per unit is the exact same as the bulk
- contains = list(/obj/item/ammo_box/magazine/wt550m9)
- small_item = TRUE
-
//////////////////////////////////////////////////////////////////////////////
//////////////////////////// Engineering /////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
@@ -749,7 +758,7 @@
/datum/supply_pack/engineering/shieldgen
name = "Anti-breach Shield Projector Crate"
desc = "Hull breaches again? Say no more with the Nanotrasen Anti-Breach Shield Projector! Uses forcefield technology to keep the air in, and the space out. Contains two shield projectors."
- cost = 2500
+ cost = 1000
contains = list(/obj/machinery/shieldgen,
/obj/machinery/shieldgen)
crate_name = "anti-breach shield projector crate"
@@ -757,7 +766,7 @@
/datum/supply_pack/engineering/ripley
name = "APLU MK-I Crate"
desc = "A do-it-yourself kit for building an ALPU MK-I \"Ripley\", designed for lifting and carrying heavy equipment, and other station tasks. Batteries not included."
- cost = 2000
+ cost = 1500
contains = list(/obj/item/mecha_parts/chassis/ripley,
/obj/item/mecha_parts/part/ripley_torso,
/obj/item/mecha_parts/part/ripley_right_arm,
@@ -775,7 +784,7 @@
/datum/supply_pack/engineering/conveyor
name = "Conveyor Assembly Crate"
desc = "Keep production moving along with thirty conveyor belts. Conveyor switch included. If you have any questions, check out the enclosed instruction book."
- cost = 1500
+ cost = 1200
contains = list(/obj/item/stack/conveyor/thirty,
/obj/item/conveyor_switch_construct,
/obj/item/paper/guides/conveyor)
@@ -784,7 +793,7 @@
/datum/supply_pack/engineering/engiequipment
name = "Engineering Gear Crate"
desc = "Gear up with three toolbelts, high-visibility vests, welding helmets, hardhats, and two pairs of meson goggles!"
- cost = 1300
+ cost = 1000
contains = list(/obj/item/storage/belt/utility,
/obj/item/storage/belt/utility,
/obj/item/storage/belt/utility,
@@ -801,27 +810,36 @@
/obj/item/clothing/glasses/meson/engine)
crate_name = "engineering gear crate"
-/datum/supply_pack/engineering/sologamermitts
- name = "Insulated Gloves Single-Pack"
- desc = "The backbone of modern society. Barely ever ordered for actual engineering. Single Order."
- cost = 800
- small_item = TRUE
- contains = list(/obj/item/clothing/gloves/color/yellow)
-
/datum/supply_pack/engineering/powergamermitts
name = "Insulated Gloves Crate"
desc = "The backbone of modern society. Barely ever ordered for actual engineering. Contains three insulated gloves."
- cost = 2000 //Made of pure-grade bullshittinium
+ cost = 1600 //Made of pure-grade bullshittinium
contains = list(/obj/item/clothing/gloves/color/yellow,
/obj/item/clothing/gloves/color/yellow,
/obj/item/clothing/gloves/color/yellow)
crate_name = "insulated gloves crate"
crate_type = /obj/structure/closet/crate/engineering/electrical
+/datum/supply_pack/engineering/jetpack
+ name = "Jetpack Crate"
+ desc = "For when you need to go fast in space!"
+ cost = 850
+ contains = list(/obj/item/tank/jetpack/carbondioxide)
+ crate_name = "jetpack crate"
+
+/datum/supply_pack/engineering/jetpack3
+ name = "Bulk Jetpack Crate"
+ desc = "Three jetpacks, enough for the whole gang!"
+ cost = 1750 //20% discount
+ contains = list(/obj/item/tank/jetpack/carbondioxide,
+ /obj/item/tank/jetpack/carbondioxide,
+ /obj/item/tank/jetpack/carbondioxide)
+ crate_name = "bulk jetpack crate"
+
/datum/supply_pack/engineering/spacecapsule
name = "Space Shelter Capsule"
desc = "A crate containing an RCD, some compressed matter cartridges, and a single bluespace capsule containing a spaceworthy shelter for construction/emergencies."
- cost = 2000
+ cost = 1500
contains = list(/obj/item/survivalcapsule/space,
/obj/item/construction/rcd,
/obj/item/rcd_ammo,
@@ -838,7 +856,7 @@
/datum/supply_pack/engineering/inducers
name = "NT-75 Electromagnetic Power Inducers Crate"
desc = "No rechargers? No problem, with the NT-75 EPI, you can recharge any standard cell-based equipment anytime, anywhere. Contains two Inducers."
- cost = 2000
+ cost = 1500
contains = list(/obj/item/inducer/sci {cell_type = /obj/item/stock_parts/cell/inducer_supply; opened = 0}, /obj/item/inducer/sci {cell_type = /obj/item/stock_parts/cell/inducer_supply; opened = 0}) //FALSE doesn't work in modified type paths apparently.
crate_name = "inducer crate"
crate_type = /obj/structure/closet/crate/engineering/electrical
@@ -846,7 +864,7 @@
/datum/supply_pack/engineering/pacman
name = "P.A.C.M.A.N Generator Crate"
desc = "Engineers can't set up the engine? Not an issue for you, once you get your hands on this P.A.C.M.A.N. Generator! Takes in plasma and spits out sweet sweet energy."
- cost = 2500
+ cost = 1000
contains = list(/obj/machinery/power/port_gen/pacman)
crate_name = "PACMAN generator crate"
crate_type = /obj/structure/closet/crate/engineering/electrical
@@ -854,7 +872,7 @@
/datum/supply_pack/engineering/power
name = "Power Cell Crate"
desc = "Looking for power overwhelming? Look no further. Contains three high-voltage power cells."
- cost = 1000
+ cost = 500
contains = list(/obj/item/stock_parts/cell/high,
/obj/item/stock_parts/cell/high,
/obj/item/stock_parts/cell/high)
@@ -880,13 +898,13 @@
/obj/item/storage/toolbox/mechanical,
/obj/item/storage/toolbox/mechanical,
/obj/item/storage/toolbox/mechanical)
- cost = 1000
+ cost = 700
crate_name = "toolbox crate"
/datum/supply_pack/engineering/vending/engineering
name = "Engineering Vending Crate"
desc = "Sick of assistants breaking into engineering for tools? Contains one Engi-Vend refill and one YouTool refill."
- cost = 2000
+ cost = 1100
contains = list(/obj/item/vending_refill/engivend,
/obj/item/vending_refill/tool)
crate_name = "engineering vending crate"
@@ -956,7 +974,7 @@
/datum/supply_pack/engineering/shuttle_construction
name = "Shuttle Construction Kit"
desc = "A DIY kit for building your own shuttle! Comes with all the parts you need to get your people to the stars!"
- cost = 6000
+ cost = 5000
contains = list(
/obj/machinery/portable_atmospherics/canister/toxins,
/obj/item/construction/rcd/loaded,
@@ -966,7 +984,6 @@
/obj/item/pipe_dispenser,
/obj/item/storage/toolbox/mechanical,
/obj/item/storage/toolbox/electrical,
- /obj/item/circuitboard/computer/shuttle/docker,
/obj/item/circuitboard/computer/shuttle/flight_control,
/obj/item/circuitboard/machine/shuttle/engine/plasma,
/obj/item/circuitboard/machine/shuttle/engine/plasma,
@@ -987,7 +1004,7 @@
/datum/supply_pack/engine/am_jar
name = "Antimatter Containment Jar Crate"
desc = "Two Antimatter containment jars stuffed into a single crate."
- cost = 2000
+ cost = 1700
contains = list(/obj/item/am_containment,
/obj/item/am_containment)
crate_name = "antimatter jar crate"
@@ -995,14 +1012,14 @@
/datum/supply_pack/engine/am_core
name = "Antimatter Control Crate"
desc = "The brains of the Antimatter engine, this device is sure to teach the station's powergrid the true meaning of real power."
- cost = 5000
+ cost = 4700
contains = list(/obj/machinery/power/am_control_unit)
crate_name = "antimatter control crate"
/datum/supply_pack/engine/am_shielding
name = "Antimatter Shielding Crate"
desc = "Contains ten Antimatter shields, somehow crammed into a crate."
- cost = 2000
+ cost = 1700
contains = list(/obj/item/am_shielding_container,
/obj/item/am_shielding_container,
/obj/item/am_shielding_container,
@@ -1018,7 +1035,7 @@
/datum/supply_pack/engine/emitter
name = "Emitter Crate"
desc = "Useful for powering forcefield generators while destroying locked crates and intruders alike. Contains two high-powered energy emitters. Requires CE access to open."
- cost = 1500
+ cost = 1200
access = ACCESS_CE
contains = list(/obj/machinery/power/emitter,
/obj/machinery/power/emitter)
@@ -1029,7 +1046,7 @@
/datum/supply_pack/engine/field_gen
name = "Field Generator Crate"
desc = "Typically the only thing standing between the station and a messy death. Powered by emitters. Contains two field generators."
- cost = 1500
+ cost = 1200
contains = list(/obj/machinery/field/generator,
/obj/machinery/field/generator)
crate_name = "field generator crate"
@@ -1037,7 +1054,7 @@
/datum/supply_pack/engine/grounding_rods
name = "Grounding Rod Crate"
desc = "Four grounding rods guaranteed to keep any uppity tesla's lightning under control."
- cost = 1700
+ cost = 700
contains = list(/obj/machinery/power/grounding_rod,
/obj/machinery/power/grounding_rod,
/obj/machinery/power/grounding_rod,
@@ -1048,7 +1065,7 @@
/datum/supply_pack/engine/PA
name = "Particle Accelerator Crate"
desc = "A supermassive black hole or hyper-powered teslaball are the perfect way to spice up any party! This \"My First Apocalypse\" kit contains everything you need to build your own Particle Accelerator! Ages 10 and up."
- cost = 3000
+ cost = 2700
contains = list(/obj/structure/particle_accelerator/fuel_chamber,
/obj/machinery/particle_accelerator/control_box,
/obj/structure/particle_accelerator/particle_emitter/center,
@@ -1061,7 +1078,7 @@
/datum/supply_pack/engine/collector
name = "Radiation Collector Crate"
desc = "Contains three radiation collectors. Useful for collecting energy off nearby Supermatter Crystals, Singularities or Teslas!"
- cost = 2500
+ cost = 2200
contains = list(/obj/machinery/power/rad_collector,
/obj/machinery/power/rad_collector,
/obj/machinery/power/rad_collector)
@@ -1070,14 +1087,14 @@
/datum/supply_pack/engine/sing_gen
name = "Singularity Generator Crate"
desc = "The key to unlocking the power of Lord Singuloth. Particle Accelerator not included."
- cost = 5000
+ cost = 4700
contains = list(/obj/machinery/the_singularitygen)
crate_name = "singularity generator crate"
/datum/supply_pack/engine/solar
name = "Solar Panel Crate"
desc = "Go green with this DIY advanced solar array. Contains twenty one solar assemblies, a solar-control circuit board, and tracker. If you have any questions, please check out the enclosed instruction book."
- cost = 2000
+ cost = 1700
contains = list(/obj/item/solar_assembly,
/obj/item/solar_assembly,
/obj/item/solar_assembly,
@@ -1118,7 +1135,7 @@
/datum/supply_pack/engine/tesla_coils
name = "Tesla Coil Crate"
desc = "Whether it's high-voltage executions, creating research points, or just plain old power generation: This pack of four Tesla coils can do it all!"
- cost = 2500
+ cost = 1200
contains = list(/obj/machinery/power/tesla_coil,
/obj/machinery/power/tesla_coil,
/obj/machinery/power/tesla_coil,
@@ -1154,6 +1171,17 @@
contains = list(/obj/item/stack/sheet/glass/fifty)
crate_name = "glass sheets crate"
+/datum/supply_pack/materials/glass250
+ name = "250 Glass Sheets"
+ desc = "Holy SHEET thats a lot of glass!"
+ cost = 2800 //20%
+ contains = list(/obj/item/stack/sheet/glass/fifty,
+ /obj/item/stack/sheet/glass/fifty,
+ /obj/item/stack/sheet/glass/fifty,
+ /obj/item/stack/sheet/glass/fifty,
+ /obj/item/stack/sheet/glass/fifty)
+ crate_name = "bulk glass sheets crate"
+
/datum/supply_pack/materials/iron50
name = "50 Iron Sheets"
desc = "Any construction project begins with a good stack of fifty iron sheets!"
@@ -1161,38 +1189,63 @@
contains = list(/obj/item/stack/sheet/iron/fifty)
crate_name = "iron sheets crate"
+/datum/supply_pack/materials/iron250
+ name = "250 Iron Sheets"
+ desc = "Enough Iron to rebuild half a station!"
+ cost = 2800 //20%
+ contains = list(/obj/item/stack/sheet/iron/fifty,
+ /obj/item/stack/sheet/iron/fifty,
+ /obj/item/stack/sheet/iron/fifty,
+ /obj/item/stack/sheet/iron/fifty,
+ /obj/item/stack/sheet/iron/fifty)
+ crate_name = "bulk iron sheets crate"
+
/datum/supply_pack/materials/plasteel20
name = "20 Plasteel Sheets"
desc = "Reinforce the station's integrity with twenty plasteel sheets!"
- cost = 7500
+ cost = 7200
contains = list(/obj/item/stack/sheet/plasteel/twenty)
crate_name = "plasteel sheets crate"
/datum/supply_pack/materials/plasteel50
name = "50 Plasteel Sheets"
desc = "For when you REALLY have to reinforce something."
- cost = 16500
+ cost = 14000 // 20% discount
contains = list(/obj/item/stack/sheet/plasteel/fifty)
crate_name = "plasteel sheets crate"
+/datum/supply_pack/materials/copper20
+ name = "20 Copper Sheets"
+ desc = "Makes your floors look nice and your circuitry run!"
+ cost = 1000
+ contains = list(/obj/item/stack/sheet/mineral/copper/twenty)
+ crate_name = "copper sheets crate"
+
+/datum/supply_pack/materials/copper50
+ name = "50 Copper Sheets"
+ desc = "Makes your floors look nice and your circuitry run!"
+ cost = 1600 //20% discount
+ contains = list(/obj/item/stack/sheet/mineral/copper/fifty)
+ crate_name = "bulk copper sheets crate"
+
/datum/supply_pack/materials/plastic50
name = "50 Plastic Sheets"
desc = "Build a limitless amount of toys with fifty plastic sheets!"
- cost = 1000
+ cost = 800
contains = list(/obj/item/stack/sheet/plastic/fifty)
crate_name = "plastic sheets crate"
/datum/supply_pack/materials/sandstone30
name = "30 Sandstone Blocks"
desc = "Neither sandy nor stoney, these thirty blocks will still get the job done."
- cost = 1000
+ cost = 800
contains = list(/obj/item/stack/sheet/mineral/sandstone/thirty)
crate_name = "sandstone blocks crate"
/datum/supply_pack/materials/wood50
name = "50 Wood Planks"
desc = "Turn cargo's boring metal groundwork into beautiful panelled flooring and much more with fifty wooden planks!"
- cost = 2000
+ cost = 1700
contains = list(/obj/item/stack/sheet/mineral/wood/fifty)
crate_name = "wood planks crate"
@@ -1208,7 +1261,7 @@
/datum/supply_pack/materials/carbon_dio
name = "Carbon Dioxide Canister"
desc = "Contains a canister of Carbon Dioxide."
- cost = 3000
+ cost = 1200
contains = list(/obj/machinery/portable_atmospherics/canister/carbon_dioxide)
crate_name = "carbon dioxide canister crate"
crate_type = /obj/structure/closet/crate/large
@@ -1240,7 +1293,7 @@
/datum/supply_pack/materials/nitrogen
name = "Nitrogen Canister"
desc = "Contains a canister of Nitrogen."
- cost = 2000
+ cost = 800
contains = list(/obj/machinery/portable_atmospherics/canister/nitrogen)
crate_name = "nitrogen canister crate"
crate_type = /obj/structure/closet/crate/large
@@ -1248,7 +1301,7 @@
/datum/supply_pack/materials/nitrous_oxide_canister
name = "Nitrous Oxide Canister"
desc = "Contains a canister of Nitrous Oxide. Requires Atmospherics access to open."
- cost = 3000
+ cost = 2400
access = ACCESS_ATMOSPHERICS
contains = list(/obj/machinery/portable_atmospherics/canister/nitrous_oxide)
crate_name = "nitrous oxide canister crate"
@@ -1257,7 +1310,7 @@
/datum/supply_pack/materials/oxygen
name = "Oxygen Canister"
desc = "Contains a canister of Oxygen. Canned in Druidia."
- cost = 1500
+ cost = 800
contains = list(/obj/machinery/portable_atmospherics/canister/oxygen)
crate_name = "oxygen canister crate"
crate_type = /obj/structure/closet/crate/large
@@ -1265,7 +1318,7 @@
/datum/supply_pack/materials/watertank
name = "Water Tank Crate"
desc = "Contains a tank of dihydrogen monoxide... sounds dangerous."
- cost = 600
+ cost = 750
contains = list(/obj/structure/reagent_dispensers/watertank)
crate_name = "water tank crate"
crate_type = /obj/structure/closet/crate/large
@@ -1289,7 +1342,7 @@
/datum/supply_pack/medical/bloodpacks
name = "Blood Pack Variety Crate"
desc = "Contains eight different blood packs for reintroducing blood to patients."
- cost = 3500
+ cost = 700
contains = list(/obj/item/reagent_containers/blood,
/obj/item/reagent_containers/blood,
/obj/item/reagent_containers/blood/APlus,
@@ -1299,14 +1352,15 @@
/obj/item/reagent_containers/blood/OPlus,
/obj/item/reagent_containers/blood/OMinus,
/obj/item/reagent_containers/blood/lizard,
- /obj/item/reagent_containers/blood/ethereal)
+ /obj/item/reagent_containers/blood/ethereal,
+ /obj/item/reagent_containers/blood/oozeling)
crate_name = "blood freezer"
crate_type = /obj/structure/closet/crate/freezer
/datum/supply_pack/medical/synthflesh
name = "Synthflesh resupply pack"
desc = "Contains four 100u cartons of synthflesh in case the cloner ran out of it."
- cost = 3000
+ cost = 1400
contains = list(/obj/item/reagent_containers/food/drinks/bottle/synthflesh,
/obj/item/reagent_containers/food/drinks/bottle/synthflesh,
/obj/item/reagent_containers/food/drinks/bottle/synthflesh,
@@ -1314,24 +1368,60 @@
crate_name = "rusty freezer"
crate_type = /obj/structure/closet/crate/freezer
-/datum/supply_pack/medical/firstaidbruises_single
- name = "Bruise Treatment Kit Single-Pack"
- desc = "Contains one first aid kit focused on healing bruises and broken bones."
- cost = 330
+/datum/supply_pack/medical/basickits
+ name = "Basic Treatment Kits Crate"
+ desc = "Contains three basic aid kits focused on basic types of damage in a simple way."
+ cost = 1400
small_item = TRUE
- contains = list(/obj/item/storage/firstaid/brute)
+ contains = list(/obj/item/storage/firstaid/regular,
+ /obj/item/storage/firstaid/regular,
+ /obj/item/storage/firstaid/regular)
+ crate_name = "basic wound treatment kits crate"
+
+/datum/supply_pack/medical/bruisekits
+ name = "Bruise Treatment Kits Crate"
+ desc = "Contains three first aid kits focused on healing bruises and broken bones."
+ cost = 1400
+ small_item = TRUE
+ contains = list(/obj/item/storage/firstaid/brute,
+ /obj/item/storage/firstaid/brute,
+ /obj/item/storage/firstaid/brute)
+ crate_name = "brute treatment kits crate"
+
+/datum/supply_pack/medical/burnkits
+ name = "Burn Treatment Kits Crate"
+ desc = "Contains three first aid kits focused on healing severe burns."
+ cost = 1400
+ small_item = TRUE
+ contains = list(/obj/item/storage/firstaid/fire,
+ /obj/item/storage/firstaid/fire,
+ /obj/item/storage/firstaid/fire)
+ crate_name = "burn treatment kits crate"
+
+/datum/supply_pack/medical/oxylosskits
+ name = "Oxygen Deprivation Kits Crate"
+ desc = "Contains three first aid kits focused on helping oxygen deprivation victims."
+ cost = 1400
+ small_item = TRUE
+ contains = list(/obj/item/storage/firstaid/o2,
+ /obj/item/storage/firstaid/o2,
+ /obj/item/storage/firstaid/o2)
+ crate_name = "oxygen deprivation treatment kits crate"
-/datum/supply_pack/medical/firstaidburns_single
- name = "Burn Treatment Kit Single-Pack"
- desc = "Contains one first aid kit focused on healing severe burns."
- cost = 330
+/datum/supply_pack/medical/toxinkits
+ name = "Toxin Treatment Kits Crate"
+ desc = "Contains three first aid kits focused on healing damage dealt by heavy toxins."
+ cost = 1400
small_item = TRUE
- contains = list(/obj/item/storage/firstaid/fire)
+ contains = list(/obj/item/storage/firstaid/toxin,
+ /obj/item/storage/firstaid/toxin,
+ /obj/item/storage/firstaid/toxin)
+ crate_name = "toxin treatment kits crate"
/datum/supply_pack/medical/chemical
name = "Chemical Starter Kit Crate"
desc = "Contains thirteen different chemicals, for all the fun experiments you can make."
- cost = 1700
+ cost = 1000
contains = list(/obj/item/reagent_containers/glass/bottle/hydrogen,
/obj/item/reagent_containers/glass/bottle/carbon,
/obj/item/reagent_containers/glass/bottle/nitrogen,
@@ -1353,29 +1443,23 @@
/datum/supply_pack/medical/defibs
name = "Defibrillator Crate"
desc = "Contains two defibrillators for bringing the recently deceased back to life."
- cost = 2500
+ cost = 1800
contains = list(/obj/item/defibrillator/loaded,
/obj/item/defibrillator/loaded)
crate_name = "defibrillator crate"
-
-/datum/supply_pack/medical/firstaid_single
- name = "First Aid Kit Single-Pack"
- desc = "Contains one first aid kit for healing most types of wounds."
- cost = 250
- small_item = TRUE
- contains = list(/obj/item/storage/firstaid/regular)
-
/datum/supply_pack/medical/iv_drip
name = "IV Drip Crate"
- desc = "Contains a single IV drip for administering blood to patients."
- cost = 1000
- contains = list(/obj/machinery/iv_drip)
+ desc = "Contains three IV drips for administering blood to patients."
+ cost = 800
+ contains = list(/obj/machinery/iv_drip,
+ /obj/machinery/iv_drip,
+ /obj/machinery/iv_drip)
crate_name = "iv drip crate"
/datum/supply_pack/medical/supplies
name = "Medical Supplies Crate"
- desc = "Contains several medical supplies. German doctor not included."
+ desc = "Contains a little bit of everything needed to stock a medbay or to form your own."
cost = 2000
contains = list(/obj/item/reagent_containers/glass/bottle/charcoal,
/obj/item/reagent_containers/glass/bottle/epinephrine,
@@ -1405,40 +1489,26 @@
var/item = pick(contains)
new item(C)
-/datum/supply_pack/medical/firstaidoxygen_single
- name = "Oxygen Deprivation Kit Single-Pack"
- desc = "Contains three first aid kits focused on helping oxygen deprivation victims."
- cost = 330
- small_item = TRUE
- contains = list(/obj/item/storage/firstaid/o2)
-
/datum/supply_pack/medical/surgery
name = "Surgical Supplies Crate"
desc = "Do you want to perform surgery, but don't have one of those fancy shmancy degrees? Just get started with this crate containing a medical duffelbag, Sterilizine spray and collapsible roller bed."
- cost = 3000
+ cost = 900
contains = list(/obj/item/storage/backpack/duffelbag/med/surgery,
/obj/item/reagent_containers/medspray/sterilizine,
/obj/item/roller)
crate_name = "surgical supplies crate"
-/datum/supply_pack/medical/firstaidtoxins_single
- name = "Toxin Treatment Kit Single-Pack"
- desc = "Contains one first aid kit focused on healing damage dealt by heavy toxins."
- cost = 330
- small_item = TRUE
- contains = list(/obj/item/storage/firstaid/toxin)
-
/datum/supply_pack/medical/salglucanister
name = "Heavy-Duty Saline Canister"
desc = "Contains a bulk supply of saline-glucose condensed into a single canister that should last several days, with a large pump to fill containers with. Direct injection of saline should be left to medical professionals as the pump is capable of overdosing patients. Requires medbay access to open."
- cost = 3000
+ cost = 1200
access = ACCESS_MEDICAL
contains = list(/obj/machinery/iv_drip/saline)
/datum/supply_pack/medical/randomvirus
name = "Virus Sample Crate"
desc = "Contains five random experimental disease cultures for epidemiological research"
- cost = 1500
+ cost = 3750
access = ACCESS_VIROLOGY
contains = list(/obj/item/reagent_containers/glass/bottle/random_virus,
/obj/item/reagent_containers/glass/bottle/random_virus,
@@ -1452,7 +1522,7 @@
/datum/supply_pack/medical/virology
name = "Junior Epidemiology Kit"
desc = "Contains the necessary supplies to start an epidemiological research lab. P.A.N.D.E.M.I.C. not included. Comes with a free virologist action figure!"
- cost = 2000
+ cost = 1500
access = ACCESS_VIROLOGY
contains = list(/obj/item/reagent_containers/food/snacks/monkeycube,
/obj/item/reagent_containers/food/drinks/bottle/virusfood,
@@ -1467,7 +1537,7 @@
/datum/supply_pack/medical/vending
name = "Medical Vending Crate"
desc = "Contains one NanoMed Plus refill and one wall-mounted NanoMed refill."
- cost = 2000
+ cost = 1500
contains = list(/obj/item/vending_refill/medical,
/obj/item/vending_refill/wallmed)
crate_name = "medical vending crate"
@@ -1488,6 +1558,24 @@
crate_type = /obj/structure/closet/crate/secure/plasma
dangerous = TRUE
+/datum/supply_pack/medical/extrapolator
+ name = "Virus Extrapolator Supply Crate"
+ desc = "Contains 3 Virus Extrapolators should any existing ones be lost or otherwise destroyed."
+ cost = 4500
+ access = ACCESS_VIROLOGY
+ contains = list(/obj/item/extrapolator, /obj/item/extrapolator, /obj/item/extrapolator)
+ crate_name = "Extrapolator Crate"
+ crate_type = /obj/structure/closet/crate/secure/plasma
+ dangerous = TRUE
+
+/datum/supply_pack/medical/pandemic
+ name = "Pandemic Replacement Crate"
+ desc = "Contains a replacement P.A.N.D.E.M.I.C. in case the ones in virology get destroyed or you want to build a new lab."
+ cost = 7500
+ access = ACCESS_VIROLOGY
+ contains = list(/obj/machinery/computer/pandemic)
+ crate_name = "P.A.N.D.E.M.I.C. Replacement Crate"
+ dangerous = TRUE
//////////////////////////////////////////////////////////////////////////////
//////////////////////////// Science /////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
@@ -1499,7 +1587,7 @@
/datum/supply_pack/science/plasma
name = "Plasma Assembly Crate"
desc = "Everything you need to burn something to the ground, this contains three plasma assembly sets. Each set contains a plasma tank, igniter, proximity sensor, and timer! Warranty void if exposed to high temperatures. Requires Toxins access to open."
- cost = 1000
+ cost = 800
access = ACCESS_TOX_STORAGE
contains = list(/obj/item/tank/internals/plasma,
/obj/item/tank/internals/plasma,
@@ -1519,7 +1607,7 @@
/datum/supply_pack/science/robotics
name = "Robotics Assembly Crate"
desc = "The tools you need to replace those finicky humans with a loyal robot army! Contains four proximity sensors, two empty first aid kits, two health analyzers, two red hardhats, two mechanical toolboxes, and two cleanbot assemblies! Requires Robotics access to open."
- cost = 1500
+ cost = 1200
access = ACCESS_ROBOTICS
contains = list(/obj/item/assembly/prox_sensor,
/obj/item/assembly/prox_sensor,
@@ -1555,14 +1643,14 @@
/datum/supply_pack/science/rped
name = "RPED crate"
desc = "Need to rebuild the ORM but science got annihialted after a bomb test? Buy this for the most advanced parts NT can give you."
- cost = 1500
+ cost = 800
contains = list(/obj/item/storage/part_replacer/cargo)
crate_name = "\improper RPED crate"
/datum/supply_pack/science/shieldwalls
name = "Shield Generator Crate"
desc = "These high powered Shield Wall Generators are guaranteed to keep any unwanted lifeforms on the outside, where they belong! Contains four shield wall generators. Requires Teleporter access to open."
- cost = 2000
+ cost = 1700
access = ACCESS_TELEPORTER
contains = list(/obj/machinery/shieldwallgen,
/obj/machinery/shieldwallgen,
@@ -1574,14 +1662,14 @@
/datum/supply_pack/science/modularpc
name = "Deluxe Silicate Selections restocking unit"
desc = "What's a computer? Contains Deluxe Silicate Selections restocking unit."
- cost = 1500
+ cost = 1200
contains = list(/obj/item/vending_refill/modularpc)
crate_name = "computer supply crate"
/datum/supply_pack/science/transfer_valves
name = "Tank Transfer Valves Crate"
desc = "The key ingredient for making a lot of people very angry very fast. Contains two tank transfer valves. Requires RD access to open."
- cost = 6000
+ cost = 4000
access = ACCESS_RD
contains = list(/obj/item/transfer_valve,
/obj/item/transfer_valve)
@@ -1589,6 +1677,20 @@
crate_type = /obj/structure/closet/crate/secure/science
dangerous = TRUE
+/datum/supply_pack/science/xenobio
+ name = "Xenobiology Lab Crate"
+ desc = "In case a freak accident has rendered the xenobiology lab non-functional! Contains two grey slime extracts, some plasma, and the required circuit boards to set up your xenobiology lab up and running! Requires Xenobiology access to open."
+ cost = 10000
+ access = ACCESS_XENOBIOLOGY
+ contains = list(/obj/item/slime_extract/grey,
+ /obj/item/slime_extract/grey,
+ /obj/item/reagent_containers/syringe/plasma,
+ /obj/item/circuitboard/computer/xenobiology,
+ /obj/item/circuitboard/machine/monkey_recycler,
+ /obj/item/circuitboard/machine/processor/slime)
+ crate_name = "xenobiology starter crate"
+ crate_type = /obj/structure/closet/crate/secure/science
+
//////////////////////////////////////////////////////////////////////////////
/////////////////////////////// Service //////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
@@ -1599,7 +1701,7 @@
/datum/supply_pack/service/cargo_supples
name = "Cargo Supplies Crate"
desc = "Sold everything that wasn't bolted down? You can get right back to work with this crate containing stamps, an export scanner, destination tagger, hand labeler and some package wrapping."
- cost = 1000
+ cost = 700
contains = list(/obj/item/stamp,
/obj/item/stamp/denied,
/obj/item/export_scanner,
@@ -1610,20 +1712,30 @@
/datum/supply_pack/service/noslipfloor
name = "High-traction Floor Tiles"
- desc = "Make slipping a thing of the past with thirty industrial-grade anti-slip floortiles!"
- cost = 2000
+ desc = "Make slipping a thing of the past with thirty industrial-grade anti-slip floor tiles!"
+ cost = 800
contains = list(/obj/item/stack/tile/noslip/thirty)
crate_name = "high-traction floor tiles crate"
+/datum/supply_pack/service/noslipfloorbulk
+ name = "Bulk High-traction Floor Tiles"
+ desc = "Make an entire department not need to know the pain of slipping on a wet floor with 120 anti-slip floor tiles!"
+ cost = 2000
+ contains = list(/obj/item/stack/tile/noslip/thirty,
+ /obj/item/stack/tile/noslip/thirty,
+ /obj/item/stack/tile/noslip/thirty,
+ /obj/item/stack/tile/noslip/thirty)
+ crate_name = "high-traction floor tiles crate"
+
/datum/supply_pack/service/janitor
name = "Janitorial Supplies Crate"
desc = "Fight back against dirt and grime with Nanotrasen's Janitorial Essentials(tm)! Contains three buckets, caution signs, and cleaner grenades. Also has a single mop, broom, spray cleaner, rag, and trash bag."
- cost = 1000
+ cost = 800
contains = list(/obj/item/reagent_containers/glass/bucket,
/obj/item/reagent_containers/glass/bucket,
/obj/item/reagent_containers/glass/bucket,
/obj/item/mop,
- /obj/item/twohanded/pushbroom,
+ /obj/item/pushbroom,
/obj/item/clothing/suit/caution,
/obj/item/clothing/suit/caution,
/obj/item/clothing/suit/caution,
@@ -1638,7 +1750,7 @@
/datum/supply_pack/service/janitor/janicart
name = "Janitorial Cart and Galoshes Crate"
desc = "The keystone to any successful janitor. As long as you have feet, this pair of galoshes will keep them firmly planted on the ground. Also contains a janitorial cart."
- cost = 2000
+ cost = 1000
contains = list(/obj/structure/janitorialcart,
/obj/item/clothing/shoes/galoshes)
crate_name = "janitorial cart crate"
@@ -1646,9 +1758,8 @@
/datum/supply_pack/service/janitor/janitank
name = "Janitor Backpack Crate"
- desc = "Call forth divine judgement upon dirt and grime with this high capacity janitor backpack. Contains 500 units of station-cleansing cleaner. Requires janitor access to open."
- cost = 1000
- access = ACCESS_JANITOR
+ desc = "Call forth divine judgment upon dirt and grime with this high capacity janitor backpack. Contains 500 units of station-cleansing cleaner."
+ cost = 700
contains = list(/obj/item/watertank/janitor)
crate_name = "janitor backpack crate"
crate_type = /obj/structure/closet/crate/secure
@@ -1656,7 +1767,7 @@
/datum/supply_pack/service/mule
name = "MULEbot Crate"
desc = "Pink-haired Quartermaster not doing her job? Replace her with this tireless worker, today!"
- cost = 2000
+ cost = 1700
contains = list(/mob/living/simple_animal/bot/mulebot)
crate_name = "\improper MULEbot Crate"
crate_type = /obj/structure/closet/crate/large
@@ -1664,7 +1775,7 @@
/datum/supply_pack/service/party
name = "Party Equipment"
desc = "Celebrate both life and death on the station with Nanotrasen's Party Essentials(tm)! Contains a special party area, seven colored glowsticks, four beers, two ales, and a bottle of patron, goldschlager, and shaker!"
- cost = 2500
+ cost = 1500
contains = list(/obj/item/storage/box/drinkingglasses,
/obj/item/reagent_containers/food/drinks/shaker,
/obj/item/reagent_containers/food/drinks/bottle/patron,
@@ -1688,7 +1799,7 @@
/datum/supply_pack/service/carpet
name = "Premium Carpet Crate"
desc = "Plasteel floor tiles getting on your nerves? These stacks of extra soft carpet will tie any room together."
- cost = 1000
+ cost = 700
contains = list(/obj/item/stack/tile/carpet/fifty,
/obj/item/stack/tile/carpet/fifty,
/obj/item/stack/tile/carpet/black/fifty,
@@ -1698,7 +1809,7 @@
/datum/supply_pack/service/carpet_exotic
name = "Exotic Carpet Crate"
desc = "Exotic carpets straight from Space Russia, for all your decorating needs. Contains 100 tiles each of 8 different flooring patterns."
- cost = 4000
+ cost = 2000
contains = list(/obj/item/stack/tile/carpet/blue/fifty,
/obj/item/stack/tile/carpet/blue/fifty,
/obj/item/stack/tile/carpet/cyan/fifty,
@@ -1721,9 +1832,10 @@
/datum/supply_pack/service/lightbulbs
name = "Replacement Lights"
- desc = "May the light of Aether shine upon this station! Or at least, the light of forty two light tubes and twenty one light bulbs."
- cost = 1000
+ desc = "May the light of Aether shine upon this station! Or at least, the light of fifty six light tubes and twenty eight light bulbs."
+ cost = 800
contains = list(/obj/item/storage/box/lights/mixed,
+ /obj/item/storage/box/lights/mixed,
/obj/item/storage/box/lights/mixed,
/obj/item/storage/box/lights/mixed)
crate_name = "replacement lights"
@@ -1731,7 +1843,7 @@
/datum/supply_pack/service/minerkit
name = "Shaft Miner Starter Kit"
desc = "All the miners died too fast? Assistant wants to get a taste of life off-station? Either way, this kit is the best way to turn a regular crewman into an ore-producing, monster-slaying machine. Contains meson goggles, a pickaxe, advanced mining scanner, cargo headset, ore bag, gasmask, an explorer suit and a miner ID upgrade. Requires QM access to open."
- cost = 2500
+ cost = 800
access = ACCESS_QM
contains = list(/obj/item/storage/backpack/duffelbag/mining_conscript)
crate_name = "shaft miner starter kit"
@@ -1740,7 +1852,7 @@
/datum/supply_pack/service/vending/bartending
name = "Booze-o-mat and Coffee Supply Crate"
desc = "Bring on the booze and coffee vending machine refills."
- cost = 2000
+ cost = 1200
contains = list(/obj/item/vending_refill/boozeomat,
/obj/item/vending_refill/coffee)
crate_name = "bartending supply crate"
@@ -1748,7 +1860,7 @@
/datum/supply_pack/service/vending/cigarette
name = "Cigarette Supply Crate"
desc = "Don't believe the reports - smoke today! Contains a cigarette vending machine refill."
- cost = 1500
+ cost = 1200
contains = list(/obj/item/vending_refill/cigarette)
crate_name = "cigarette supply crate"
crate_type = /obj/structure/closet/crate
@@ -1756,14 +1868,14 @@
/datum/supply_pack/service/vending/dinnerware
name = "Dinnerware Supply Crate"
desc = "More knives for the chef."
- cost = 1000
+ cost = 800
contains = list(/obj/item/vending_refill/dinnerware)
crate_name = "dinnerware supply crate"
/datum/supply_pack/service/vending/games
name = "Games Supply Crate"
desc = "Get your game on with this game vending machine refill."
- cost = 1000
+ cost = 800
contains = list(/obj/item/vending_refill/games)
crate_name = "games supply crate"
crate_type = /obj/structure/closet/crate
@@ -1771,7 +1883,7 @@
/datum/supply_pack/service/vending/imported
name = "Imported Vending Machines"
desc = "Vending machines famous in other parts of the galaxy."
- cost = 4000
+ cost = 3000
contains = list(/obj/item/vending_refill/sustenance,
/obj/item/vending_refill/robotics,
/obj/item/vending_refill/sovietsoda,
@@ -1781,28 +1893,28 @@
/datum/supply_pack/service/vending/ptech
name = "PTech Supply Crate"
desc = "Not enough cartridges after half the crew lost their PDA to explosions? This may fix it."
- cost = 1500
+ cost = 800
contains = list(/obj/item/vending_refill/cart)
crate_name = "ptech supply crate"
/datum/supply_pack/service/vending/snack
name = "Snack Supply Crate"
desc = "One vending machine refill of cavity-bringin' goodness! The number one dentist recommended order!"
- cost = 1500
+ cost = 800
contains = list(/obj/item/vending_refill/snack)
crate_name = "snacks supply crate"
/datum/supply_pack/service/vending/cola
name = "Softdrinks Supply Crate"
desc = "Got whacked by a toolbox, but you still have those pesky teeth? Get rid of those pearly whites with this soda machine refill, today!"
- cost = 1500
+ cost = 800
contains = list(/obj/item/vending_refill/cola)
crate_name = "soft drinks supply crate"
/datum/supply_pack/service/vending/vendomat
name = "Vendomat Supply Crate"
desc = "More tools for your IED testing facility."
- cost = 1000
+ cost = 800
contains = list(/obj/item/vending_refill/assist)
crate_name = "vendomat supply crate"
@@ -1829,7 +1941,7 @@
/datum/supply_pack/organic/hydroponics/beekeeping_suits
name = "Beekeeper Suit Crate"
desc = "Bee business booming? Better be benevolent and boost botany by bestowing bi-Beekeeper-suits! Contains two beekeeper suits and matching headwear."
- cost = 1000
+ cost = 800
contains = list(/obj/item/clothing/head/beekeeper_head,
/obj/item/clothing/suit/beekeeper_suit,
/obj/item/clothing/head/beekeeper_head,
@@ -1840,7 +1952,7 @@
/datum/supply_pack/organic/hydroponics/beekeeping_fullkit
name = "Beekeeping Starter Crate"
desc = "BEES BEES BEES. Contains three honey frames, a beekeeper suit and helmet, flyswatter, bee house, and, of course, a pure-bred Nanotrasen-Standardized Queen Bee!"
- cost = 1500
+ cost = 1400
contains = list(/obj/structure/beebox/unwrenched,
/obj/item/honey_frame,
/obj/item/honey_frame,
@@ -1855,7 +1967,7 @@
/datum/supply_pack/organic/randomized/chef
name = "Excellent Meat Crate"
desc = "The best cuts in the whole galaxy."
- cost = 2000
+ cost = 1700
contains = list(/obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/slime,
/obj/item/reagent_containers/food/snacks/meat/slab/killertomato,
/obj/item/reagent_containers/food/snacks/meat/slab/bear,
@@ -1876,7 +1988,7 @@
/datum/supply_pack/organic/exoticseeds
name = "Exotic Seeds Crate"
desc = "Any entrepreneuring botanist's dream. Contains fourteen different seeds, including three replica-pod seeds and two mystery seeds!"
- cost = 1500
+ cost = 1000
contains = list(/obj/item/seeds/nettle,
/obj/item/seeds/replicapod,
/obj/item/seeds/replicapod,
@@ -1917,7 +2029,7 @@
/datum/supply_pack/organic/randomized/chef/fruits
name = "Fruit Crate"
desc = "Rich of vitamins, may contain oranges."
- cost = 1500
+ cost = 1200
contains = list(/obj/item/reagent_containers/food/snacks/grown/citrus/lime,
/obj/item/reagent_containers/food/snacks/grown/citrus/orange,
/obj/item/reagent_containers/food/snacks/grown/watermelon,
@@ -1939,7 +2051,7 @@
/datum/supply_pack/organic/hydroponics
name = "Hydroponics Crate"
desc = "Supplies for growing a great garden! Contains two bottles of ammonia, two Plant-B-Gone spray bottles, a hatchet, cultivator, plant analyzer, as well as a pair of leather gloves and a botanist's apron."
- cost = 1500
+ cost = 800
contains = list(/obj/item/reagent_containers/spray/plantbgone,
/obj/item/reagent_containers/spray/plantbgone,
/obj/item/reagent_containers/glass/bottle/ammonia,
@@ -1956,8 +2068,7 @@
/datum/supply_pack/organic/hydroponics/hydrotank
name = "Hydroponics Backpack Crate"
desc = "Bring on the flood with this high-capacity backpack crate. Contains 500 units of life-giving H2O. Requires hydroponics access to open."
- cost = 1000
- access = ACCESS_HYDROPONICS
+ cost = 700
contains = list(/obj/item/watertank)
crate_name = "hydroponics backpack crate"
crate_type = /obj/structure/closet/crate/secure
@@ -1965,7 +2076,7 @@
/datum/supply_pack/organic/pizza
name = "Pizza Crate"
desc = "Best prices on this side of the galaxy. All deliveries are guaranteed to be 99% anomaly-free!"
- cost = 6000 // Best prices this side of the galaxy.
+ cost = 5000 // Best prices this side of the galaxy.
contains = list(/obj/item/pizzabox/margherita,
/obj/item/pizzabox/mushroom,
/obj/item/pizzabox/meat,
@@ -1999,19 +2110,19 @@
/datum/supply_pack/organic/potted_plants
name = "Potted Plants Crate"
desc = "Spruce up the station with these lovely plants! Contains a random assortment of five potted plants from Nanotrasen's potted plant research division. Warranty void if thrown."
- cost = 700
- contains = list(/obj/item/twohanded/required/kirbyplants/random,
- /obj/item/twohanded/required/kirbyplants/random,
- /obj/item/twohanded/required/kirbyplants/random,
- /obj/item/twohanded/required/kirbyplants/random,
- /obj/item/twohanded/required/kirbyplants/random)
+ cost = 550
+ contains = list(/obj/item/kirbyplants/random,
+ /obj/item/kirbyplants/random,
+ /obj/item/kirbyplants/random,
+ /obj/item/kirbyplants/random,
+ /obj/item/kirbyplants/random)
crate_name = "potted plants crate"
crate_type = /obj/structure/closet/crate/hydroponics
/datum/supply_pack/organic/seeds
name = "Seeds Crate"
desc = "Big things have small beginnings. Contains fourteen different seeds."
- cost = 1000
+ cost = 800
contains = list(/obj/item/seeds/chili,
/obj/item/seeds/cotton,
/obj/item/seeds/berry,
@@ -2032,7 +2143,7 @@
/datum/supply_pack/organic/randomized/chef/vegetables
name = "Vegetables Crate"
desc = "Grown in vats."
- cost = 1300
+ cost = 1000
contains = list(/obj/item/reagent_containers/food/snacks/grown/chili,
/obj/item/reagent_containers/food/snacks/grown/corn,
/obj/item/reagent_containers/food/snacks/grown/tomato,
@@ -2046,7 +2157,7 @@
/datum/supply_pack/organic/vending/hydro_refills
name = "Hydroponics Vending Machines Refills"
desc = "When the clown takes all the banana seeds. Contains a NutriMax refill and an MegaSeed Servitor refill."
- cost = 2000
+ cost = 1700
crate_type = /obj/structure/closet/crate
contains = list(/obj/item/vending_refill/hydroseeds,
/obj/item/vending_refill/hydronutrients)
@@ -2073,6 +2184,25 @@
)
crate_name = "grilling fuel kit crate"
+/datum/supply_pack/organic/beefbroth
+
+ name = "Beef Broth Bulk Crate"
+ desc = "No one really wants to order beef broth so we're selling it in bulk!"
+ cost = 5000
+ contraband = TRUE
+ crate_type = /obj/structure/closet/crate
+ contains = list(/obj/item/reagent_containers/food/snacks/canned/beefbroth,
+ /obj/item/reagent_containers/food/snacks/canned/beefbroth,
+ /obj/item/reagent_containers/food/snacks/canned/beefbroth,
+ /obj/item/reagent_containers/food/snacks/canned/beefbroth,
+ /obj/item/reagent_containers/food/snacks/canned/beefbroth,
+ /obj/item/reagent_containers/food/snacks/canned/beefbroth,
+ /obj/item/reagent_containers/food/snacks/canned/beefbroth,
+ /obj/item/reagent_containers/food/snacks/canned/beefbroth,
+ /obj/item/reagent_containers/food/snacks/canned/beefbroth,
+ /obj/item/reagent_containers/food/snacks/canned/beefbroth
+ )
+ crate_name = "Beef Broth Care"
//////////////////////////////////////////////////////////////////////////////
////////////////////////////// Livestock /////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
@@ -2124,6 +2254,28 @@
qdel(C)
new /mob/living/simple_animal/pet/cat/Proc(.)
+/datum/supply_pack/critter/cat/exotic
+ name = "Exotic Cat Crate"
+ desc = "Commes with one of the exotic cats, collar and a toy."
+ cost = 5500
+ contains = list(/obj/item/clothing/neck/petcollar,
+ /obj/item/toy/cattoy)
+ crate_name = "cat crate"
+
+/datum/supply_pack/critter/cat/exotic/generate()
+ . = ..()
+ switch(rand(1, 5))
+ if(1)
+ new /mob/living/simple_animal/pet/cat/original(.)
+ if(2)
+ new /mob/living/simple_animal/pet/cat/breadcat(.)
+ if(3)
+ new /mob/living/simple_animal/pet/cat/cak(.)
+ if(4)
+ new /mob/living/simple_animal/pet/cat/space(.)
+ if(5)
+ new /mob/living/simple_animal/pet/cat/halal(.)
+
/datum/supply_pack/critter/chick
name = "Chicken Crate"
desc = "The chicken goes bwaak!"
@@ -2192,11 +2344,12 @@
/datum/supply_pack/critter/monkey
name = "Monkey Cube Crate"
- desc = "Stop monkeying around! Contains seven monkey cubes. Just add water!"
- cost = 2000
+ desc = "Stop monkeying around! Contains five monkey cubes. Just add water!"
+ cost = 1000
contains = list (/obj/item/storage/box/monkeycubes)
crate_type = /obj/structure/closet/crate
crate_name = "monkey cube crate"
+ small_item = TRUE
/datum/supply_pack/critter/pug
name = "Pug Crate"
@@ -2444,15 +2597,14 @@
crate_name = "toy crate"
crate_type = /obj/structure/closet/crate/wooden
-/datum/supply_pack/costumes_toys/randomised/toys/generate()
- . = ..()
+/datum/supply_pack/costumes_toys/randomised/toys/fill(obj/structure/closet/crate/C)
var/the_toy
for(var/i in 1 to num_contained)
if(prob(50))
the_toy = pickweight(GLOB.arcade_prize_pool)
else
the_toy = pick(subtypesof(/obj/item/toy/plush))
- new the_toy(.)
+ new the_toy(C)
/datum/supply_pack/costumes_toys/wizard
name = "Wizard Costume Crate"
@@ -2471,24 +2623,83 @@
var/item = pick_n_take(L)
new item(C)
+/datum/supply_pack/costumes_toys/chess_white
+ name = "White Chess Piece Crate"
+ desc = "Look at you, playing a nerd game within a nerd game!"
+ cost = 800
+ contains = list(
+ /obj/item/cardboard_cutout/adaptive/chess/king,
+ /obj/item/cardboard_cutout/adaptive/chess/queen,
+ /obj/item/cardboard_cutout/adaptive/chess/rook,
+ /obj/item/cardboard_cutout/adaptive/chess/rook,
+ /obj/item/cardboard_cutout/adaptive/chess/knight,
+ /obj/item/cardboard_cutout/adaptive/chess/knight,
+ /obj/item/cardboard_cutout/adaptive/chess/bishop,
+ /obj/item/cardboard_cutout/adaptive/chess/bishop,
+ /obj/item/cardboard_cutout/adaptive/chess/pawn,
+ /obj/item/cardboard_cutout/adaptive/chess/pawn,
+ /obj/item/cardboard_cutout/adaptive/chess/pawn,
+ /obj/item/cardboard_cutout/adaptive/chess/pawn,
+ /obj/item/cardboard_cutout/adaptive/chess/pawn,
+ /obj/item/cardboard_cutout/adaptive/chess/pawn,
+ /obj/item/cardboard_cutout/adaptive/chess/pawn,
+ /obj/item/cardboard_cutout/adaptive/chess/pawn,
+ )
+ crate_type = /obj/structure/closet/crate/wooden
+
+/datum/supply_pack/costumes_toys/chess_black
+ name = "Black Chess Piece Crate"
+ desc = "Look at you, playing a nerd game within a nerd game!"
+ cost = 800
+ contains = list(
+ /obj/item/cardboard_cutout/adaptive/chess/black/king,
+ /obj/item/cardboard_cutout/adaptive/chess/black/queen,
+ /obj/item/cardboard_cutout/adaptive/chess/black/rook,
+ /obj/item/cardboard_cutout/adaptive/chess/black/rook,
+ /obj/item/cardboard_cutout/adaptive/chess/black/knight,
+ /obj/item/cardboard_cutout/adaptive/chess/black/knight,
+ /obj/item/cardboard_cutout/adaptive/chess/black/bishop,
+ /obj/item/cardboard_cutout/adaptive/chess/black/bishop,
+ /obj/item/cardboard_cutout/adaptive/chess/black/pawn,
+ /obj/item/cardboard_cutout/adaptive/chess/black/pawn,
+ /obj/item/cardboard_cutout/adaptive/chess/black/pawn,
+ /obj/item/cardboard_cutout/adaptive/chess/black/pawn,
+ /obj/item/cardboard_cutout/adaptive/chess/black/pawn,
+ /obj/item/cardboard_cutout/adaptive/chess/black/pawn,
+ /obj/item/cardboard_cutout/adaptive/chess/black/pawn,
+ /obj/item/cardboard_cutout/adaptive/chess/black/pawn,
+ )
+ crate_type = /obj/structure/closet/crate/wooden
+
+//////////////////////////////////////////////////////////////////////////////
+///////////////////////// Wardrobe Resupplies ////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////
+
/datum/supply_pack/costumes_toys/wardrobes/autodrobe
name = "Autodrobe Supply Crate"
desc = "Autodrobe missing your favorite dress? Solve that issue today with this autodrobe refill."
- cost = 1500
+ cost = 800
contains = list(/obj/item/vending_refill/autodrobe)
crate_name = "autodrobe supply crate"
/datum/supply_pack/costumes_toys/wardrobes/cargo
name = "Cargo Wardrobe Supply Crate"
desc = "This crate contains a refill for the CargoDrobe."
- cost = 750
+ cost = 800
contains = list(/obj/item/vending_refill/wardrobe/cargo_wardrobe)
crate_name = "cargo department supply crate"
+/datum/supply_pack/costumes_toys/wardrobes/clothesmate
+ name = "ClothesMate Wardrobe Supply Crate"
+ desc = "This crate contains a refill for the ClothesMate."
+ cost = 800
+ contains = list(/obj/item/vending_refill/clothing)
+ crate_name = "clothesmate supply crate"
+
/datum/supply_pack/costumes_toys/wardrobes/engineering
name = "Engineering Wardrobe Supply Crate"
desc = "This crate contains refills for the EngiDrobe and AtmosDrobe."
- cost = 1500
+ cost = 800
contains = list(/obj/item/vending_refill/wardrobe/engi_wardrobe,
/obj/item/vending_refill/wardrobe/atmos_wardrobe)
crate_name = "engineering department wardrobe supply crate"
@@ -2496,7 +2707,7 @@
/datum/supply_pack/costumes_toys/wardrobes/general
name = "General Wardrobes Supply Crate"
desc = "This crate contains refills for the CuraDrobe, BarDrobe, ChefDrobe, JaniDrobe, ChapDrobe."
- cost = 3750
+ cost = 1200
contains = list(/obj/item/vending_refill/wardrobe/curator_wardrobe,
/obj/item/vending_refill/wardrobe/bar_wardrobe,
/obj/item/vending_refill/wardrobe/chef_wardrobe,
@@ -2507,14 +2718,14 @@
/datum/supply_pack/costumes_toys/wardrobes/hydroponics
name = "Hydrobe Supply Crate"
desc = "This crate contains a refill for the Hydrobe."
- cost = 750
+ cost = 600
contains = list(/obj/item/vending_refill/wardrobe/hydro_wardrobe)
crate_name = "hydrobe supply crate"
/datum/supply_pack/costumes_toys/wardrobes/medical
name = "Medical Wardrobe Supply Crate"
desc = "This crate contains refills for the MediDrobe, ChemDrobe, GeneDrobe, and ViroDrobe."
- cost = 3000
+ cost = 1200
contains = list(/obj/item/vending_refill/wardrobe/medi_wardrobe,
/obj/item/vending_refill/wardrobe/chem_wardrobe,
/obj/item/vending_refill/wardrobe/gene_wardrobe,
@@ -2524,16 +2735,17 @@
/datum/supply_pack/costumes_toys/wardrobes/science
name = "Science Wardrobe Supply Crate"
desc = "This crate contains refills for the SciDrobe and RoboDrobe."
- cost = 1500
+ cost = 800
contains = list(/obj/item/vending_refill/wardrobe/robo_wardrobe,
/obj/item/vending_refill/wardrobe/science_wardrobe)
crate_name = "science department wardrobe supply crate"
/datum/supply_pack/costumes_toys/wardrobes/security
name = "Security Wardrobe Supply Crate"
- desc = "This crate contains refills for the SecDrobe and LawDrobe."
- cost = 1500
+ desc = "This crate contains refills for the SecDrobe, DetDrobe and LawDrobe."
+ cost = 1000
contains = list(/obj/item/vending_refill/wardrobe/sec_wardrobe,
+ /obj/item/vending_refill/wardrobe/det_wardrobe,
/obj/item/vending_refill/wardrobe/law_wardrobe)
crate_name = "security department supply crate"
@@ -2547,20 +2759,41 @@
/datum/supply_pack/misc/artsupply
name = "Art Supplies"
desc = "Make some happy little accidents with six canvasses, two easels, and two rainbow crayons!"
- cost = 800
+ cost = 500
contains = list(/obj/structure/easel,
/obj/structure/easel,
- /obj/item/canvas/nineteenXnineteen,
- /obj/item/canvas/nineteenXnineteen,
- /obj/item/canvas/twentythreeXnineteen,
- /obj/item/canvas/twentythreeXnineteen,
- /obj/item/canvas/twentythreeXtwentythree,
- /obj/item/canvas/twentythreeXtwentythree,
+ /obj/item/canvas/nineteen_nineteen,
+ /obj/item/canvas/nineteen_nineteen,
+ /obj/item/canvas/twentythree_nineteen,
+ /obj/item/canvas/twentythree_nineteen,
+ /obj/item/canvas/twentythree_twentythree,
+ /obj/item/canvas/twentythree_twentythree,
/obj/item/toy/crayon/rainbow,
/obj/item/toy/crayon/rainbow)
crate_name = "art supply crate"
crate_type = /obj/structure/closet/crate/wooden
+/datum/supply_pack/misc/aquarium_kit
+ name = "Aquarium Kit"
+ desc = "Everything you need to start your own aquarium. Contains aquarium construction kit, fish catalog, feed can and three freshwater fish from our collection."
+ cost = 2000
+ contains = list(/obj/item/book/fish_catalog,
+ /obj/item/storage/fish_case/random/freshwater,
+ /obj/item/storage/fish_case/random/freshwater,
+ /obj/item/storage/fish_case/random/freshwater,
+ /obj/item/fish_feed,
+ /obj/item/storage/box/aquarium_props,
+ /obj/item/aquarium_kit)
+ crate_name = "aquarium kit crate"
+ crate_type = /obj/structure/closet/crate/wooden
+
+/datum/supply_pack/misc/aquarium_fish
+ name = "Aquarium Fish Case"
+ desc = "An aquarium fish handpicked by monkeys from our collection."
+ cost = 600
+ contains = list(/obj/item/storage/fish_case/random)
+ crate_name = "aquarium fish crate"
+
/datum/supply_pack/misc/bicycle
name = "Bicycle"
desc = "Nanotrasen reminds all employees to never toy with powers outside their control."
@@ -2572,7 +2805,7 @@
/datum/supply_pack/misc/bigband
name = "Big Band Instrument Collection"
desc = "Get your sad station movin' and groovin' with this fine collection! Contains nine different instruments!"
- cost = 5000
+ cost = 800
crate_name = "Big band musical instruments collection"
contains = list(/obj/item/instrument/violin,
/obj/item/instrument/guitar,
@@ -2582,13 +2815,13 @@
/obj/item/instrument/trombone,
/obj/item/instrument/recorder,
/obj/item/instrument/harmonica,
- /obj/structure/piano/unanchored)
+ /obj/structure/musician/piano/unanchored)
crate_type = /obj/structure/closet/crate/wooden
/datum/supply_pack/misc/book_crate
name = "Book Crate"
desc = "Surplus from the Nanotrasen Archives, these seven books are sure to be good reads."
- cost = 1500
+ cost = 1200
contains = list(/obj/item/book/codex_gigas,
/obj/item/book/manual/random/,
/obj/item/book/manual/random/,
@@ -2601,7 +2834,7 @@
/datum/supply_pack/misc/paper
name = "Bureaucracy Crate"
desc = "High stacks of papers on your desk Are a big problem - make it Pea-sized with these bureaucratic supplies! Contains six pens, some camera film, hand labeler supplies, a paper bin, three folders, a laser pointer, two clipboards and two stamps."//that was too forced
- cost = 1500
+ cost = 800
contains = list(/obj/structure/filingcabinet/chestdrawer/wheeled,
/obj/item/camera_film,
/obj/item/hand_labeler,
@@ -2627,7 +2860,7 @@
/datum/supply_pack/misc/fountainpens
name = "Calligraphy Crate"
desc = "Sign death warrants in style with these seven executive fountain pens."
- cost = 700
+ cost = 800
contains = list(/obj/item/storage/box/fountainpens)
crate_type = /obj/structure/closet/crate/wooden
crate_name = "calligraphy crate"
@@ -2635,7 +2868,7 @@
/datum/supply_pack/misc/wrapping_paper
name = "Festive Wrapping Paper Crate"
desc = "Want to mail your loved ones gift-wrapped chocolates, stuffed animals, the Clown's severed head? You can do all that, with this crate full of wrapping paper."
- cost = 1000
+ cost = 800
contains = list(/obj/item/stack/wrapping_paper)
crate_type = /obj/structure/closet/crate/wooden
crate_name = "festive wrapping paper crate"
@@ -2644,7 +2877,7 @@
/datum/supply_pack/misc/funeral
name = "Funeral Supply crate"
desc = "At the end of the day, someone's gonna want someone dead. Give them a proper send-off with these funeral supplies! Contains a coffin with burial garmets and flowers."
- cost = 600
+ cost = 800
contains = list(/obj/item/clothing/under/misc/burial,
/obj/item/reagent_containers/food/snacks/grown/harebell,
/obj/item/reagent_containers/food/snacks/grown/poppy/geranium)
@@ -2653,24 +2886,39 @@
/datum/supply_pack/misc/religious_supplies
name = "Religious Supplies Crate"
- desc = "Keep your local chaplain happy and well-supplied, lest they call down judgement upon your cargo bay. Contains two bottles of holywater, bibles, chaplain robes, and burial garmets."
+ desc = "Keep your local chaplain happy and well-supplied, lest they call down judgment upon your cargo bay. Contains two bottles of holywater, bibles, chaplain robes, and burial garmets."
cost = 4000 // it costs so much because the Space Church is ran by Space Jews
contains = list(/obj/item/reagent_containers/food/drinks/bottle/holywater,
/obj/item/reagent_containers/food/drinks/bottle/holywater,
/obj/item/storage/book/bible/booze,
/obj/item/storage/book/bible/booze,
+ /obj/item/clothing/neck/crucifix/rosary,
/obj/item/clothing/suit/hooded/chaplain_hoodie,
/obj/item/clothing/suit/hooded/chaplain_hoodie)
crate_name = "religious supplies crate"
/datum/supply_pack/misc/toner
name = "Toner Crate"
- desc = "Spent too much ink printing butt pictures? Fret not, with these six toner refills, you'll be printing butts 'till the cows come home!'"
- cost = 1000
+ desc = "Spent too much ink printing butt pictures? Fret not, with these eight toner refills, you'll be printing butts 'till the cows come home!'"
+ cost = 800
contains = list(/obj/item/toner,
/obj/item/toner,
/obj/item/toner,
/obj/item/toner,
/obj/item/toner,
+ /obj/item/toner,
+ /obj/item/toner,
/obj/item/toner)
crate_name = "toner crate"
+
+/datum/supply_pack/misc/toner_large
+ name = "Toner Crate (Large)"
+ desc = "Tired of changing toner cartridges? These six extra heavy duty refills contain roughly five times as much toner as the base model!"
+ cost = 3000
+ contains = list(/obj/item/toner/large,
+ /obj/item/toner/large,
+ /obj/item/toner/large,
+ /obj/item/toner/large,
+ /obj/item/toner/large,
+ /obj/item/toner/large)
+ crate_name = "large toner crate"
diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm
index 2c1a3328d0008..1075b1ca1671e 100644
--- a/code/modules/cargo/supplypod.dm
+++ b/code/modules/cargo/supplypod.dm
@@ -1,103 +1,189 @@
-//The "BDPtarget" temp visual is created by anything that "launches" a supplypod. It makes two things: a falling droppod animation, and the droppod itself.
+//The "pod_landingzone" temp visual is created by anything that "launches" a supplypod. This is what animates the pod and makes the pod forcemove to the station.
//------------------------------------SUPPLY POD-------------------------------------//
/obj/structure/closet/supplypod
name = "supply pod" //Names and descriptions are normally created with the setStyle() proc during initialization, but we have these default values here as a failsafe
desc = "A Nanotrasen supply drop pod."
icon = 'icons/obj/supplypods.dmi'
- icon_state = "supplypod"
- pixel_x = -16 //2x2 sprite
- pixel_y = -5
- layer = TABLE_LAYER //So that the crate inside doesn't appear underneath
+ icon_state = "pod" //This is a common base sprite shared by a number of pods
+ pixel_x = SUPPLYPOD_X_OFFSET //2x2 sprite
+ layer = BELOW_OBJ_LAYER //So that the crate inside doesn't appear underneath
allow_objects = TRUE
allow_dense = TRUE
delivery_icon = null
can_weld_shut = FALSE
- armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 100, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 80)
+ armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 100, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 80, "stamina" = 0)
anchored = TRUE //So it cant slide around after landing
anchorable = FALSE
+ flags_1 = PREVENT_CONTENTS_EXPLOSION_1
+ appearance_flags = KEEP_TOGETHER | PIXEL_SCALE
+ density = FALSE
+ ///List of bitflags for supply pods, see: code\__DEFINES\obj_flags.dm
+ var/pod_flags = NONE
+
//*****NOTE*****: Many of these comments are similarly described in centcom_podlauncher.dm. If you change them here, please consider doing so in the centcom podlauncher code as well!
var/adminNamed = FALSE //Determines whether or not the pod has been named by an admin. If true, the pod's name will not get overridden when the style of the pod changes (changing the style of the pod normally also changes the name+desc)
var/bluespace = FALSE //If true, the pod deletes (in a shower of sparks) after landing
- var/landingDelay = 30 //How long the pod takes to land after launching
- var/openingDelay = 30 //How long the pod takes to open after landing
- var/departureDelay = 30 //How long the pod takes to leave after opening. If bluespace = TRUE, it deletes. If reversing = TRUE, it flies back to centcom.
+ var/delays = list(POD_TRANSIT = 30, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30)
+ var/reverse_delays = list(POD_TRANSIT = 30, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30)
+ var/custom_rev_delay = FALSE
var/damage = 0 //Damage that occurs to any mob under the pod when it lands.
var/effectStun = FALSE //If true, stuns anyone under the pod when it launches until it lands, forcing them to get hit by the pod. Devilish!
var/effectLimb = FALSE //If true, pops off a limb (if applicable) from anyone caught under the pod when it lands
var/effectOrgans = FALSE //If true, yeets out every limb and organ from anyone caught under the pod when it lands
var/effectGib = FALSE //If true, anyone under the pod will be gibbed when it lands
- var/effectStealth = FALSE //If true, a target icon isnt displayed on the turf where the pod will land
+ var/effectStealth = FALSE //If true, a target icon isn't displayed on the turf where the pod will land
var/effectQuiet = FALSE //The female sniper. If true, the pod makes no noise (including related explosions, opening sounds, etc)
var/effectMissile = FALSE //If true, the pod deletes the second it lands. If you give it an explosion, it will act like a missile exploding as it hits the ground
var/effectCircle = FALSE //If true, allows the pod to come in at any angle. Bit of a weird feature but whatever its here
- var/style = STYLE_STANDARD //Style is a variable that keeps track of what the pod is supposed to look like. It acts as an index to the POD_STYLES list in cargo.dm defines to get the proper icon/name/desc for the pod.
+ var/style = STYLE_STANDARD //Style is a variable that keeps track of what the pod is supposed to look like. It acts as an index to the GLOB.podstyles list in cargo.dm defines to get the proper icon/name/desc for the pod.
var/reversing = FALSE //If true, the pod will not send any items. Instead, after opening, it will close again (picking up items/mobs) and fly back to centcom
- var/fallDuration = 4
+ var/list/reverse_dropoff_coords //Turf that the reverse pod will drop off it's newly-acquired cargo to
var/fallingSoundLength = 11
var/fallingSound = 'sound/weapons/mortar_long_whistle.ogg'//Admin sound to play before the pod lands
var/landingSound //Admin sound to play when the pod lands
var/openingSound //Admin sound to play when the pod opens
var/leavingSound //Admin sound to play when the pod leaves
var/soundVolume = 80 //Volume to play sounds at. Ignores the cap
- var/bay //Used specifically for the centcom_podlauncher datum. Holds the current bay the user is launching objects from. Bays are specific rooms on the centcom map.
var/list/explosionSize = list(0,0,2,3)
var/stay_after_drop = FALSE
- var/specialised = TRUE // It's not a general use pod for cargo/admin use
+ var/specialised = FALSE // It's not a general use pod for cargo/admin use
+ var/rubble_type //Rubble effect associated with this supplypod
+ var/decal = "default" //What kind of extra decals we add to the pod to make it look nice
+ var/door = "pod_door"
+ var/fin_mask = "topfin"
+ var/obj/effect/supplypod_rubble/rubble
+ var/obj/effect/engineglow/glow_effect
+ var/effectShrapnel = FALSE
+ var/shrapnel_type = /obj/item/projectile/bullet/shrapnel
+ var/shrapnel_magnitude = 3
+ var/list/reverseOptionList = list("Mobs"=FALSE,"Objects"=FALSE,"Anchored"=FALSE,"Underfloor"=FALSE,"Wallmounted"=FALSE,"Floors"=FALSE,"Walls"=FALSE)
+ var/list/turfs_in_cargo = list()
/obj/structure/closet/supplypod/bluespacepod
style = STYLE_BLUESPACE
bluespace = TRUE
explosionSize = list(0,0,1,2)
- landingDelay = 15 //Slightly quicker than the supplypod
+ delays = list(POD_TRANSIT = 15, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30)
/obj/structure/closet/supplypod/extractionpod
name = "Syndicate Extraction Pod"
- desc = "A specalised, blood-red styled pod for extracting high-value targets out of active mission areas."
+ desc = "A specalised, blood-red styled pod for extracting high-value targets out of active mission areas. Targets must be manually stuffed inside the pod for proper delivery."
specialised = TRUE
style = STYLE_SYNDICATE
bluespace = TRUE
explosionSize = list(0,0,1,2)
- landingDelay = 25 //Longer than others
+ delays = list(POD_TRANSIT = 25, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30)
/obj/structure/closet/supplypod/centcompod
style = STYLE_CENTCOM
bluespace = TRUE
explosionSize = list(0,0,0,0)
- landingDelay = 20 //Very speedy!
+ delays = list(POD_TRANSIT = 20, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30)
+ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+
+/obj/structure/closet/supplypod/battleroyale
+ style = STYLE_BOX
+ bluespace = FALSE
+ explosionSize = list(0,0,0,0)
+ delays = list(POD_TRANSIT = 40, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30) //Very slow
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ max_integrity = 20
+/obj/structure/closet/supplypod/Initialize(mapload, customStyle = FALSE)
+ . = ..()
+ if (!loc)
+ var/shippingLane = GLOB.areas_by_type[/area/centcom/supplypod/supplypod_temp_holding] //temporary holder for supplypods mid-transit
+ forceMove(shippingLane)
+ if (customStyle)
+ style = customStyle
+ setStyle(style) //Upon initialization, give the supplypod an iconstate, name, and description based on the "style" variable. This system is important for the centcom_podlauncher to function correctly
+
+/obj/structure/closet/supplypod/extractionpod/Initialize()
+ . = ..()
+ var/turf/picked_turf = pick(GLOB.holdingfacility)
+ reverse_dropoff_coords = list(picked_turf.x, picked_turf.y, picked_turf.z)
+
+/obj/structure/closet/supplypod/proc/setStyle(chosenStyle) //Used to give the sprite an icon state, name, and description.
+ style = chosenStyle
+ var/base = GLOB.podstyles[chosenStyle][POD_BASE] //GLOB.podstyles is a 2D array we treat as a dictionary. The style represents the verticle index, with the icon state, name, and desc being stored in the horizontal indexes of the 2D array.
+ icon_state = base
+ decal = GLOB.podstyles[chosenStyle][POD_DECAL]
+ rubble_type = GLOB.podstyles[chosenStyle][POD_RUBBLE_TYPE]
+ if (!adminNamed && !specialised) //We dont want to name it ourselves if it has been specifically named by an admin using the centcom_podlauncher datum
+ name = GLOB.podstyles[chosenStyle][POD_NAME]
+ desc = GLOB.podstyles[chosenStyle][POD_DESC]
+ if (GLOB.podstyles[chosenStyle][POD_DOOR])
+ door = "[base]_door"
+ else
+ door = FALSE
+ update_icon()
-/obj/structure/closet/supplypod/proc/specialisedPod()
- return 1
+/obj/structure/closet/supplypod/proc/SetReverseIcon()
+ fin_mask = "bottomfin"
+ if (GLOB.podstyles[style][POD_SHAPE] == POD_SHAPE_NORML)
+ icon_state = GLOB.podstyles[style][POD_BASE] + "_reverse"
+ pixel_x = initial(pixel_x)
+ transform = matrix()
+ update_icon()
-/obj/structure/closet/supplypod/extractionpod/specialisedPod(atom/movable/holder)
- holder.forceMove(pick(GLOB.holdingfacility)) // land in ninja jail
- open(holder, forced = TRUE)
+/obj/structure/closet/supplypod/proc/backToNonReverseIcon()
+ fin_mask = initial(fin_mask)
+ if (GLOB.podstyles[style][POD_SHAPE] == POD_SHAPE_NORML)
+ icon_state = GLOB.podstyles[style][POD_BASE]
+ pixel_x = initial(pixel_x)
+ transform = matrix()
+ update_icon()
-/obj/structure/closet/supplypod/Initialize()
+/obj/structure/closet/supplypod/update_overlays()
. = ..()
- setStyle(style, TRUE) //Upon initialization, give the supplypod an iconstate, name, and description based on the "style" variable. This system is important for the centcom_podlauncher to function correctly
+ if (style == STYLE_INVISIBLE)
+ return
+ if (rubble)
+ . += rubble.getForeground(src)
+ if (style == STYLE_SEETHROUGH)
+ for (var/atom/A in contents)
+ var/mutable_appearance/itemIcon = new(A)
+ itemIcon.transform = matrix().Translate(-1 * SUPPLYPOD_X_OFFSET, 0)
+ . += itemIcon
+ for (var/t in turfs_in_cargo)//T is just a turf's type
+ var/turf/turf_type = t
+ var/mutable_appearance/itemIcon = mutable_appearance(initial(turf_type.icon), initial(turf_type.icon_state))
+ itemIcon.transform = matrix().Translate(-1 * SUPPLYPOD_X_OFFSET, 0)
+ . += itemIcon
+ return
-/obj/structure/closet/supplypod/update_icon()
- cut_overlays()
- if (style == STYLE_SEETHROUGH || style == STYLE_INVISIBLE) //If we're invisible, we dont bother adding any overlays
+ if (opened) //We're opened means all we have to worry about is masking a decal if we have one
+ if (!decal) //We don't have a decal to mask
+ return
+ if (!door) //We have a decal but no door, so let's just add the decal
+ . += decal
+ return
+ var/icon/masked_decal = new(icon, decal) //The decal we want to apply
+ var/icon/door_masker = new(icon, door) //The door shape we want to 'cut out' of the decal
+ door_masker.MapColors(0,0,0,1, 0,0,0,1, 0,0,0,1, 1,1,1,0, 0,0,0,1)
+ door_masker.SwapColor("#ffffffff", null)
+ door_masker.Blend("#000000", ICON_SUBTRACT)
+ masked_decal.Blend(door_masker, ICON_ADD)
+ . += masked_decal
+ return
+ //If we're closed
+ if(!door) //We have no door, lets see if we have a decal. If not, theres nothing we need to do
+ if(decal)
+ . += decal
return
+ else if (GLOB.podstyles[style][POD_SHAPE] != POD_SHAPE_NORML) //If we're not a normal pod shape (aka, if we don't have fins), just add the door without masking
+ . += door
else
- if (opened)
- add_overlay("[icon_state]_open")
- else
- add_overlay("[icon_state]_door")
+ var/icon/masked_door = new(icon, door) //The door we want to apply
+ var/icon/fin_masker = new(icon, "mask_[fin_mask]") //The fin shape we want to 'cut out' of the door
+ fin_masker.MapColors(0,0,0,1, 0,0,0,1, 0,0,0,1, 1,1,1,0, 0,0,0,1)
+ fin_masker.SwapColor("#ffffffff", null)
+ fin_masker.Blend("#000000", ICON_SUBTRACT)
+ masked_door.Blend(fin_masker, ICON_ADD)
+ . += masked_door
+ if(decal)
+ . += decal
-/obj/structure/closet/supplypod/proc/setStyle(chosenStyle, var/duringInit = FALSE) //Used to give the sprite an icon state, name, and description
- if (!duringInit && style == chosenStyle) //Check if the input style is already the same as the pod's style. This happens in centcom_podlauncher, and as such we set the style to STYLE_CENTCOM.
- setStyle(STYLE_CENTCOM) //We make sure to not check this during initialize() so the standard supplypod works correctly.
- return
- style = chosenStyle
- icon_state = POD_STYLES[chosenStyle][POD_ICON_STATE] //POD_STYLES is a 2D array we treat as a dictionary. The style represents the verticle index, with the icon state, name, and desc being stored in the horizontal indexes of the 2D array.
- if (!adminNamed && !specialised) //We dont want to name it ourselves if it has been specifically named by an admin using the centcom_podlauncher datum
- name = POD_STYLES[chosenStyle][POD_NAME]
- desc = POD_STYLES[chosenStyle][POD_DESC]
- update_icon()
/obj/structure/closet/supplypod/tool_interact(obj/item/W, mob/user)
if(bluespace) //We dont want to worry about interacting with bluespace pods, as they are due to delete themselves soon anyways.
@@ -111,199 +197,386 @@
/obj/structure/closet/supplypod/contents_explosion() //Supplypods also protect their contents from the harmful effects of fucking exploding.
return
-/obj/structure/closet/supplypod/prevent_content_explosion() //Useful for preventing epicenter explosions from damaging contents
- return TRUE
-
/obj/structure/closet/supplypod/toggle(mob/living/user) //Supplypods shouldn't be able to be manually opened under any circumstances, as the open() proc generates supply order datums
return
-/obj/structure/closet/supplypod/proc/handleReturningClose(atom/movable/holder, returntobay)
- opened = FALSE
- INVOKE_ASYNC(holder, .proc/setClosed) //Use the INVOKE_ASYNC proc to call setClosed() on whatever the holder may be, without giving the atom/movable base class a setClosed() proc definition
- for (var/atom/movable/O in get_turf(holder))
- if ((ismob(O) && !isliving(O)) || (is_type_in_typecache(O, GLOB.blacklisted_cargo_types) && !isliving(O))) //We dont want to take ghosts with us, and we don't want blacklisted items going, but we allow mobs.
- continue
- O.forceMove(holder) //Put objects inside before we close
- var/obj/effect/temp_visual/risingPod = new /obj/effect/DPfall(get_turf(holder), src) //Make a nice animation of flying back up
- risingPod.pixel_z = 0 //The initial value of risingPod's pixel_z is 200 because it normally comes down from a high spot
- animate(risingPod, pixel_z = 200, time = 10, easing = LINEAR_EASING) //Animate our rising pod
- if (returntobay)
- holder.forceMove(bay) //Move the pod back to centcom, where it belongs
- QDEL_IN(risingPod, 10)
- reversing = FALSE //Now that we're done reversing, we set this to false (otherwise we would get stuck in an infinite loop of calling the close proc at the bottom of open() )
- bluespace = TRUE //Make it so that the pod doesn't stay in centcom forever
- open(holder, forced = TRUE)
- else
- reversing = FALSE //Now that we're done reversing, we set this to false (otherwise we would get stuck in an infinite loop of calling the close proc at the bottom of open() )
- bluespace = TRUE //Make it so that the pod doesn't stay in centcom forever
-
- QDEL_IN(risingPod, 10)
- audible_message("The pod hisses, closing quickly and launching itself away from the station.", "The ground vibrates, the nearby pod launching away from the station.")
-
- stay_after_drop = FALSE
- specialisedPod(holder) // Do special actions for specialised pods - this is likely if we were already doing manual launches
+/obj/structure/closet/supplypod/open(mob/living/user, force = TRUE)
+ return
-/obj/structure/closet/supplypod/proc/preOpen() //Called before the open() proc. Handles anything that occurs right as the pod lands.
- var/turf/T = get_turf(src)
+/obj/structure/closet/supplypod/proc/handleReturnAfterDeparting(atom/movable/holder = src)
+ reversing = FALSE //Now that we're done reversing, we set this to false (otherwise we would get stuck in an infinite loop of calling the close proc at the bottom of open_pod() )
+ bluespace = TRUE //Make it so that the pod doesn't stay in centcom forever
+ pod_flags &= ~FIRST_SOUNDS //Make it so we play sounds now
+ if (!effectQuiet && style != STYLE_SEETHROUGH)
+ audible_message("The pod hisses, closing and launching itself away from the station.", "The ground vibrates, and you hear the sound of engines firing.")
+ stay_after_drop = FALSE
+ holder.pixel_z = initial(holder.pixel_z)
+ holder.alpha = initial(holder.alpha)
+ var/shippingLane = GLOB.areas_by_type[/area/centcom/supplypod/supplypod_temp_holding]
+ forceMove(shippingLane) //Move to the centcom-z-level until the pod_landingzone says we can drop back down again
+ if (!reverse_dropoff_coords) //If we're centcom-launched, the reverse dropoff turf will be a centcom loading bay. If we're an extraction pod, it should be the ninja jail. Thus, this shouldn't ever really happen.
+ var/obj/error_landmark = locate(/obj/effect/landmark/error) in GLOB.landmarks_list
+ var/turf/error_landmark_turf = get_turf(error_landmark)
+ reverse_dropoff_coords = list(error_landmark_turf.x, error_landmark_turf.y, error_landmark_turf.z)
+ if (custom_rev_delay)
+ delays = reverse_delays
+ backToNonReverseIcon()
+ var/turf/return_turf = locate(reverse_dropoff_coords[1], reverse_dropoff_coords[2], reverse_dropoff_coords[3])
+ new /obj/effect/pod_landingzone(return_turf, src)
+
+/obj/structure/closet/supplypod/proc/preOpen() //Called before the open_pod() proc. Handles anything that occurs right as the pod lands.
+ var/turf/turf_underneath = get_turf(src)
var/list/B = explosionSize //Mostly because B is more readable than explosionSize :p
- if (landingSound)
- playsound(get_turf(src), landingSound, soundVolume, 0, 0)
- for (var/mob/living/M in T)
- if (effectLimb && iscarbon(M)) //If effectLimb is true (which means we pop limbs off when we hit people):
- var/mob/living/carbon/CM = M
- for (var/obj/item/bodypart/bodypart in CM.bodyparts) //Look at the bodyparts in our poor mob beneath our pod as it lands
- if(bodypart.body_part != HEAD && bodypart.body_part != CHEST)//we dont want to kill him, just teach em a lesson!
- if (bodypart.dismemberable)
- bodypart.dismember() //Using the power of flextape i've sawed this man's limb in half!
- break
- if (effectOrgans && iscarbon(M)) //effectOrgans means remove every organ in our mob
- var/mob/living/carbon/CM = M
- for(var/X in CM.internal_organs)
- var/destination = get_edge_target_turf(T, pick(GLOB.alldirs)) //Pick a random direction to toss them in
- var/obj/item/organ/O = X
- O.Remove(CM) //Note that this isn't the same proc as for lists
- O.forceMove(T) //Move the organ outta the body
- O.throw_at(destination, 2, 3) //Thow the organ at a random tile 3 spots away
- sleep(1)
- for (var/obj/item/bodypart/bodypart in CM.bodyparts) //Look at the bodyparts in our poor mob beneath our pod as it lands
- var/destination = get_edge_target_turf(T, pick(GLOB.alldirs))
- if (bodypart.dismemberable)
- bodypart.dismember() //Using the power of flextape i've sawed this man's bodypart in half!
- bodypart.throw_at(destination, 2, 3)
+ density = TRUE //Density is originally false so the pod doesn't block anything while it's still falling through the air
+ for (var/mob/living/target_living in turf_underneath)
+ if (iscarbon(target_living)) //If effectLimb is true (which means we pop limbs off when we hit people):
+ if (effectLimb)
+ var/mob/living/carbon/carbon_target_mob = target_living
+ for (var/bp in carbon_target_mob.bodyparts) //Look at the bodyparts in our poor mob beneath our pod as it lands
+ var/obj/item/bodypart/bodypart = bp
+ if(bodypart.body_part != HEAD && bodypart.body_part != CHEST)//we dont want to kill him, just teach em a lesson!
+ if (bodypart.dismemberable)
+ bodypart.dismember() //Using the power of flextape i've sawed this man's limb in half!
+ break
+ if (effectOrgans) //effectOrgans means remove every organ in our mob
+ var/mob/living/carbon/carbon_target_mob = target_living
+ for(var/organ in carbon_target_mob.internal_organs)
+ var/destination = get_edge_target_turf(turf_underneath, pick(GLOB.alldirs)) //Pick a random direction to toss them in
+ var/obj/item/organ/organ_to_yeet = organ
+ organ_to_yeet.Remove(carbon_target_mob) //Note that this isn't the same proc as for lists
+ organ_to_yeet.forceMove(turf_underneath) //Move the organ outta the body
+ organ_to_yeet.throw_at(destination, 2, 3) //Thow the organ at a random tile 3 spots away
sleep(1)
+ for (var/bp in carbon_target_mob.bodyparts) //Look at the bodyparts in our poor mob beneath our pod as it lands
+ var/obj/item/bodypart/bodypart = bp
+ var/destination = get_edge_target_turf(turf_underneath, pick(GLOB.alldirs))
+ if (bodypart.dismemberable)
+ bodypart.dismember() //Using the power of flextape i've sawed this man's bodypart in half!
+ bodypart.throw_at(destination, 2, 3)
+ sleep(1)
if (effectGib) //effectGib is on, that means whatever's underneath us better be fucking oof'd on
- M.adjustBruteLoss(5000) //THATS A LOT OF DAMAGE (called just in case gib() doesnt work on em)
- M.gib() //After adjusting the fuck outta that brute loss we finish the job with some satisfying gibs
- M.adjustBruteLoss(damage)
+ target_living.adjustBruteLoss(5000) //THATS A LOT OF DAMAGE (called just in case gib() doesnt work on em)
+ if (!QDELETED(target_living))
+ target_living.gib() //After adjusting the fuck outta that brute loss we finish the job with some satisfying gibs
+ else
+ target_living.adjustBruteLoss(damage)
var/explosion_sum = B[1] + B[2] + B[3] + B[4]
if (explosion_sum != 0) //If the explosion list isn't all zeroes, call an explosion
- explosion(get_turf(src), B[1], B[2], B[3], flame_range = B[4], silent = effectQuiet, ignorecap = istype(src, /obj/structure/closet/supplypod/centcompod)) //less advanced equipment than bluespace pod, so larger explosion when landing
- else if (!effectQuiet) //If our explosion list IS all zeroes, we still make a nice explosion sound (unless the effectQuiet var is true)
- playsound(src, "explosion", landingSound ? 15 : 80, 1)
+ explosion(turf_underneath, B[1], B[2], B[3], flame_range = B[4], silent = effectQuiet, ignorecap = istype(src, /obj/structure/closet/supplypod/centcompod)) //less advanced equipment than bluespace pod, so larger explosion when landing
+ else if (!effectQuiet && !(pod_flags & FIRST_SOUNDS)) //If our explosion list IS all zeroes, we still make a nice explosion sound (unless the effectQuiet var is true)
+ playsound(src, "explosion", landingSound ? soundVolume * 0.25 : soundVolume, TRUE)
+ if (landingSound)
+ playsound(turf_underneath, landingSound, soundVolume, FALSE, FALSE)
if (effectMissile) //If we are acting like a missile, then right after we land and finish fucking shit up w explosions, we should delete
opened = TRUE //We set opened to TRUE to avoid spending time trying to open (due to being deleted) during the Destroy() proc
qdel(src)
return
if (style == STYLE_GONDOLA) //Checks if we are supposed to be a gondola pod. If so, create a gondolapod mob, and move this pod to nullspace. I'd like to give a shout out, to my man oranges
- var/mob/living/simple_animal/pet/gondola/gondolapod/benis = new(get_turf(src), src)
+ var/mob/living/simple_animal/pet/gondola/gondolapod/benis = new(turf_underneath, src)
benis.contents |= contents //Move the contents of this supplypod into the gondolapod mob.
moveToNullspace()
- addtimer(CALLBACK(src, .proc/open, benis), openingDelay) //After the openingDelay passes, we use the open proc from this supplyprod while referencing the contents of the "holder", in this case the gondolapod mob
+ addtimer(CALLBACK(src, .proc/open_pod, benis), delays[POD_OPENING]) //After the opening delay passes, we use the open proc from this supplyprod while referencing the contents of the "holder", in this case the gondolapod mob
else if (style == STYLE_SEETHROUGH)
- open(src)
+ open_pod(src)
else
- addtimer(CALLBACK(src, .proc/open, src), openingDelay) //After the openingDelay passes, we use the open proc from this supplypod, while referencing this supplypod's contents
+ addtimer(CALLBACK(src, .proc/open_pod, src), delays[POD_OPENING]) //After the opening delay passes, we use the open proc from this supplypod, while referencing this supplypod's contents
-/obj/structure/closet/supplypod/open(atom/movable/holder, var/broken = FALSE, var/forced = FALSE) //The holder var represents an atom whose contents we will be working with
+/obj/structure/closet/supplypod/proc/open_pod(atom/movable/holder, broken = FALSE, forced = FALSE) //The holder var represents an atom whose contents we will be working with
if (!holder)
return
if (opened) //This is to ensure we don't open something that has already been opened
return
opened = TRUE
- var/turf/T = get_turf(holder) //Get the turf of whoever's contents we're talking about
- var/mob/M
+ holder.setOpened()
+ var/turf/turf_underneath = get_turf(holder) //Get the turf of whoever's contents we're talking about
if (istype(holder, /mob)) //Allows mobs to assume the role of the holder, meaning we look at the mob's contents rather than the supplypod's contents. Typically by this point the supplypod's contents have already been moved over to the mob's contents
- M = holder
- if (M.key && !forced && !broken) //If we are player controlled, then we shouldnt open unless the opening is manual, or if it is due to being destroyed (represented by the "broken" parameter)
+ var/mob/holder_as_mob = holder
+ if (holder_as_mob.key && !forced && !broken) //If we are player controlled, then we shouldn't open unless the opening is manual, or if it is due to being destroyed (represented by the "broken" parameter)
return
if (openingSound)
- playsound(get_turf(holder), openingSound, soundVolume, 0, 0) //Special admin sound to play
- INVOKE_ASYNC(holder, .proc/setOpened) //Use the INVOKE_ASYNC proc to call setOpened() on whatever the holder may be, without giving the atom/movable base class a setOpened() proc definition
- if (style == STYLE_SEETHROUGH)
- update_icon()
- for (var/atom/movable/O in holder.contents) //Go through the contents of the holder
- O.forceMove(T) //move everything from the contents of the holder to the turf of the holder
- if (!effectQuiet && !openingSound && style != STYLE_SEETHROUGH) //If we aren't being quiet, play the default pod open sound
- playsound(get_turf(holder), open_sound, 15, 1, -3)
+ playsound(get_turf(holder), openingSound, soundVolume, FALSE, FALSE) //Special admin sound to play
+ for (var/turf_type in turfs_in_cargo)
+ turf_underneath.PlaceOnTop(turf_type)
+ for (var/cargo in contents)
+ var/atom/movable/movable_cargo = cargo
+ movable_cargo.forceMove(turf_underneath)
+ if (!effectQuiet && !openingSound && style != STYLE_SEETHROUGH && !(pod_flags & FIRST_SOUNDS)) //If we aren't being quiet, play the default pod open sound
+ playsound(get_turf(holder), open_sound, 15, TRUE, -3)
if (broken) //If the pod is opening because it's been destroyed, we end here
return
if (style == STYLE_SEETHROUGH)
- depart(src)
+ startExitSequence(src)
else
+ if (reversing)
+ addtimer(CALLBACK(src, .proc/SetReverseIcon), delays[POD_LEAVING]/2) //Finish up the pod's duties after a certain amount of time
if(!stay_after_drop) // Departing should be handled manually
- addtimer(CALLBACK(src, .proc/depart, holder), departureDelay) //Finish up the pod's duties after a certain amount of time
+ addtimer(CALLBACK(src, .proc/startExitSequence, holder), delays[POD_LEAVING]*(4/5)) //Finish up the pod's duties after a certain amount of time
-/obj/structure/closet/supplypod/proc/depart(atom/movable/holder)
+/obj/structure/closet/supplypod/proc/startExitSequence(atom/movable/holder)
if (leavingSound)
- playsound(get_turf(holder), leavingSound, soundVolume, 0, 0)
+ playsound(get_turf(holder), leavingSound, soundVolume, FALSE, FALSE)
if (reversing) //If we're reversing, we call the close proc. This sends the pod back up to centcom
close(holder)
else if (bluespace) //If we're a bluespace pod, then delete ourselves (along with our holder, if a seperate holder exists)
+ deleteRubble()
if (!effectQuiet && style != STYLE_INVISIBLE && style != STYLE_SEETHROUGH)
do_sparks(5, TRUE, holder) //Create some sparks right before closing
qdel(src) //Delete ourselves and the holder
if (holder != src)
qdel(holder)
-/obj/structure/closet/supplypod/centcompod/close(atom/movable/holder) //Closes the supplypod and sends it back to centcom. Should only ever be called if the "reversing" variable is true
- handleReturningClose(holder, TRUE)
+/obj/structure/closet/supplypod/close(atom/movable/holder) //Closes the supplypod and sends it back to centcom. Should only ever be called if the "reversing" variable is true
+ if (!holder)
+ return
+ take_contents(holder)
+ playsound(holder, close_sound, soundVolume*0.75, TRUE, -3)
+ holder.setClosed()
+ addtimer(CALLBACK(src, .proc/preReturn, holder), delays[POD_LEAVING] * 0.2) //Start to leave a bit after closing for cinematic effect
+
+/obj/structure/closet/supplypod/take_contents(atom/movable/holder)
+ var/turf/turf_underneath = holder.drop_location()
+ for(var/atom_to_check in turf_underneath)
+ if(atom_to_check != src && !insert(atom_to_check, holder)) // Can't insert that
+ continue
+ insert(turf_underneath, holder)
+
+/obj/structure/closet/supplypod/insert(atom/to_insert, atom/movable/holder)
+ if(insertion_allowed(to_insert))
+ if(isturf(to_insert))
+ var/turf/turf_to_insert = to_insert
+ turfs_in_cargo += turf_to_insert.type
+ turf_to_insert.ScrapeAway()
+ else
+ var/atom/movable/movable_to_insert = to_insert
+ movable_to_insert.forceMove(holder)
+ return TRUE
+ else
+ return FALSE
-/obj/structure/closet/supplypod/extractionpod/close(atom/movable/holder) //handles closing, and returns pod - deletes itself when returned
- . = ..()
- return
+/obj/structure/closet/supplypod/insertion_allowed(atom/to_insert)
+ if(to_insert.invisibility == INVISIBILITY_ABSTRACT)
+ return FALSE
+ if(ismob(to_insert))
+ if(!reverseOptionList["Mobs"])
+ return FALSE
+ if(!isliving(to_insert)) //let's not put ghosts or camera mobs inside
+ return FALSE
+ var/mob/living/mob_to_insert = to_insert
+ if(mob_to_insert.anchored || mob_to_insert.incorporeal_move)
+ return FALSE
+ mob_to_insert.stop_pulling()
+
+ else if(isobj(to_insert))
+ var/obj/obj_to_insert = to_insert
+ if(istype(obj_to_insert, /obj/structure/closet/supplypod))
+ return FALSE
+ if(istype(obj_to_insert, /obj/effect/supplypod_smoke))
+ return FALSE
+ if(istype(obj_to_insert, /obj/effect/pod_landingzone))
+ return FALSE
+ if(istype(obj_to_insert, /obj/effect/supplypod_rubble))
+ return FALSE
+ /*
+ if((obj_to_insert.comp_lookup && obj_to_insert.comp_lookup[COMSIG_OBJ_HIDE]) && reverseOptionList["Underfloor"])
+ return TRUE
+ else if ((obj_to_insert.comp_lookup && obj_to_insert.comp_lookup[COMSIG_OBJ_HIDE]) && !reverseOptionList["Underfloor"])
+ return FALSE
+ */
+ if(isProbablyWallMounted(obj_to_insert) && reverseOptionList["Wallmounted"])
+ return TRUE
+ else if (isProbablyWallMounted(obj_to_insert) && !reverseOptionList["Wallmounted"])
+ return FALSE
+ if(!obj_to_insert.anchored && reverseOptionList["Unanchored"])
+ return TRUE
+ if(obj_to_insert.anchored && reverseOptionList["Anchored"])
+ return TRUE
+ return FALSE
-/obj/structure/closet/supplypod/extractionpod/proc/send_up(atom/movable/holder)
- if (!holder)
- holder = src
+ else if (isturf(to_insert))
+ if(isfloorturf(to_insert) && reverseOptionList["Floors"])
+ return TRUE
+ if(isfloorturf(to_insert) && !reverseOptionList["Floors"])
+ return FALSE
+ if(isclosedturf(to_insert) && reverseOptionList["Walls"])
+ return TRUE
+ if(isclosedturf(to_insert) && !reverseOptionList["Walls"])
+ return FALSE
+ return FALSE
+ return TRUE
- if (leavingSound)
- playsound(get_turf(holder), leavingSound, soundVolume, 0, 0)
+/obj/structure/closet/supplypod/proc/preReturn(atom/movable/holder)
+ deleteRubble()
+ animate(holder, alpha = 0, time = 8, easing = QUAD_EASING|EASE_IN, flags = ANIMATION_PARALLEL)
+ animate(holder, pixel_z = 400, time = 10, easing = QUAD_EASING|EASE_IN, flags = ANIMATION_PARALLEL) //Animate our rising pod
+
+ addtimer(CALLBACK(src, .proc/handleReturnAfterDeparting, holder), 15) //Finish up the pod's duties after a certain amount of time
+
+/obj/structure/closet/supplypod/setOpened() //Proc exists here, as well as in any atom that can assume the role of a "holder" of a supplypod. Check the open_pod() proc for more details
+ opened = TRUE
+ density = FALSE
+ update_icon()
- handleReturningClose(holder, FALSE)
+/obj/structure/closet/supplypod/extractionpod/setOpened()
+ opened = TRUE
+ density = TRUE
+ update_icon()
-/obj/structure/closet/supplypod/proc/setOpened() //Proc exists here, as well as in any atom that can assume the role of a "holder" of a supplypod. Check the open() proc for more details
+/obj/structure/closet/supplypod/setClosed() //Ditto
+ opened = FALSE
+ density = TRUE
update_icon()
-/obj/structure/closet/supplypod/proc/setClosed() //Ditto
+/obj/structure/closet/supplypod/proc/tryMakeRubble(turf/T) //Ditto
+ if (rubble_type == RUBBLE_NONE)
+ return
+ if (rubble)
+ return
+ if (effectMissile)
+ return
+ if (isspaceturf(T) || isclosedturf(T))
+ return
+ rubble = new /obj/effect/supplypod_rubble(T)
+ rubble.setStyle(rubble_type, src)
+ update_icon()
+
+/obj/structure/closet/supplypod/Moved()
+ deleteRubble()
+ return ..()
+
+/obj/structure/closet/supplypod/proc/deleteRubble()
+ rubble?.fadeAway()
+ rubble = null
update_icon()
+/obj/structure/closet/supplypod/proc/addGlow()
+ if (GLOB.podstyles[style][POD_SHAPE] != POD_SHAPE_NORML)
+ return
+ glow_effect = new(src)
+ glow_effect.icon_state = "pod_glow_" + GLOB.podstyles[style][POD_GLOW]
+ vis_contents += glow_effect
+ glow_effect.layer = GASFIRE_LAYER
+ RegisterSignal(glow_effect, COMSIG_PARENT_QDELETING, .proc/remove_glow)
+
+/obj/structure/closet/supplypod/proc/endGlow()
+ if(!glow_effect)
+ return
+ glow_effect.layer = LOW_ITEM_LAYER
+ glow_effect.fadeAway(delays[POD_OPENING])
+ //Trust the signals
+
+/obj/structure/closet/supplypod/proc/remove_glow()
+ SIGNAL_HANDLER
+
+ UnregisterSignal(glow_effect, COMSIG_PARENT_QDELETING)
+ vis_contents -= glow_effect
+ glow_effect = null
+
/obj/structure/closet/supplypod/Destroy()
- open(src, broken = TRUE) //Lets dump our contents by opening up
- . = ..()
+ open_pod(src, broken = TRUE) //Lets dump our contents by opening up
+ deleteRubble()
+ //Trust the signals even harder
+ qdel(glow_effect)
+ return ..()
-//------------------------------------FALLING SUPPLY POD-------------------------------------//
-/obj/effect/DPfall //Falling pod
+//------------------------------------TEMPORARY_VISUAL-------------------------------------//
+/obj/effect/supplypod_smoke //Falling pod smoke
name = ""
+ icon = 'icons/obj/supplypods_32x32.dmi'
+ icon_state = "smoke"
+ desc = ""
+ layer = PROJECTILE_HIT_THRESHHOLD_LAYER
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ alpha = 0
+
+/obj/effect/engineglow //Falling pod smoke
+ name = ""
+ icon = 'icons/obj/supplypods.dmi'
+ icon_state = "pod_engineglow"
+ desc = ""
+ layer = GASFIRE_LAYER
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ alpha = 255
+
+/obj/effect/engineglow/proc/fadeAway(leaveTime)
+ var/duration = min(leaveTime, 25)
+ animate(src, alpha=0, time = duration)
+ QDEL_IN(src, duration + 5)
+
+/obj/effect/supplypod_smoke/proc/drawSelf(amount)
+ alpha = max(0, 255-(amount*20))
+
+/obj/effect/supplypod_rubble //This is the object that forceMoves the supplypod to it's location
+ name = "Debris"
+ desc = "A small crater of rubble. Closer inspection reveals the debris to be made primarily of space-grade metal fragments. You're pretty sure that this will disperse before too long."
icon = 'icons/obj/supplypods.dmi'
- pixel_x = -16
- pixel_y = -5
- pixel_z = 200
- desc = "Get out of the way!"
- layer = FLY_LAYER//that wasnt flying, that was falling with style!
- icon_state = ""
-
-/obj/effect/DPfall/Initialize(dropLocation, obj/structure/closet/supplypod/pod)
- if (pod.style == STYLE_SEETHROUGH)
- pixel_x = -16
- pixel_y = 0
- for (var/atom/movable/O in pod.contents)
- var/icon/I = getFlatIcon(O) //im so sorry
- add_overlay(I)
- else if (pod.style != STYLE_INVISIBLE) //Check to ensure the pod isn't invisible
- icon_state = "[pod.icon_state]_falling"
- name = pod.name
+ layer = PROJECTILE_HIT_THRESHHOLD_LAYER // We want this to go right below the layer of supplypods and supplypod_rubble's forground.
+ icon_state = "rubble_bg"
+ anchored = TRUE
+ pixel_x = SUPPLYPOD_X_OFFSET
+ var/foreground = "rubble_fg"
+ var/verticle_offset = 0
+
+/obj/effect/supplypod_rubble/proc/getForeground(obj/structure/closet/supplypod/pod)
+ var/mutable_appearance/rubble_overlay = mutable_appearance('icons/obj/supplypods.dmi', foreground)
+ rubble_overlay.appearance_flags = KEEP_APART|RESET_TRANSFORM
+ rubble_overlay.transform = matrix().Translate(SUPPLYPOD_X_OFFSET - pod.pixel_x, verticle_offset)
+ return rubble_overlay
+
+/obj/effect/supplypod_rubble/proc/fadeAway()
+ animate(src, alpha=0, time = 30)
+ QDEL_IN(src, 35)
+
+/obj/effect/supplypod_rubble/proc/setStyle(type, obj/structure/closet/supplypod/pod)
+ if (type == RUBBLE_WIDE)
+ icon_state += "_wide"
+ foreground += "_wide"
+ if (type == RUBBLE_THIN)
+ icon_state += "_thin"
+ foreground += "_thin"
+ if (pod.style == STYLE_BOX)
+ verticle_offset = -2
+ else
+ verticle_offset = initial(verticle_offset)
+ pixel_y = verticle_offset
+
+/obj/effect/pod_landingzone_effect
+ name = ""
+ desc = ""
+ icon = 'icons/obj/supplypods_32x32.dmi'
+ icon_state = "LZ_Slider"
+ layer = PROJECTILE_HIT_THRESHHOLD_LAYER
+
+/obj/effect/pod_landingzone_effect/Initialize(mapload, obj/structure/closet/supplypod/pod)
. = ..()
+ transform = matrix() * 1.5
+ animate(src, transform = matrix()*0.01, time = pod.delays[POD_TRANSIT]+pod.delays[POD_FALLING])
-//------------------------------------TEMPORARY_VISUAL-------------------------------------//
-/obj/effect/DPtarget //This is the object that forceMoves the supplypod to it's location
+/obj/effect/pod_landingzone //This is the object that forceMoves the supplypod to it's location
name = "Landing Zone Indicator"
desc = "A holographic projection designating the landing zone of something. It's probably best to stand back."
- icon = 'icons/mob/actions/actions_items.dmi'
- icon_state = "sniper_zoom"
+ icon = 'icons/obj/supplypods_32x32.dmi'
+ icon_state = "LZ"
layer = PROJECTILE_HIT_THRESHHOLD_LAYER
light_range = 2
- var/obj/effect/temp_visual/fallingPod //Temporary "falling pod" that we animate
- var/obj/structure/closet/supplypod/pod //The supplyPod that will be landing ontop of this target
+ anchored = TRUE
+ alpha = 0
+ var/obj/structure/closet/supplypod/pod //The supplyPod that will be landing ontop of this pod_landingzone
+ var/obj/effect/pod_landingzone_effect/helper
+ var/list/smoke_effects = new /list(13)
/obj/effect/ex_act()
return
-/obj/effect/DPtarget/Initialize(mapload, podParam, var/single_order = null)
+/obj/effect/pod_landingzone/Initialize(mapload, podParam, single_order = null, clientman)
. = ..()
if (ispath(podParam)) //We can pass either a path for a pod (as expressconsoles do), or a reference to an instantiated pod (as the centcom_podlauncher does)
podParam = new podParam() //If its just a path, instantiate it
pod = podParam
+ if (!pod.effectStealth)
+ helper = new (drop_location(), pod)
+ alpha = 255
+ animate(src, transform = matrix().Turn(90), time = pod.delays[POD_TRANSIT]+pod.delays[POD_FALLING])
if (single_order)
if (istype(single_order, /datum/supply_order))
var/datum/supply_order/SO = single_order
@@ -311,48 +584,75 @@
else if (istype(single_order, /atom/movable))
var/atom/movable/O = single_order
O.forceMove(pod)
- for (var/mob/living/M in pod) //If there are any mobs in the supplypod, we want to forceMove them into the target. This is so that they can see where they are about to land, AND so that they don't get sent to the nullspace error room (as the pod is currently in nullspace)
- M.forceMove(src)
- if(pod.effectStun) //If effectStun is true, stun any mobs caught on this target until the pod gets a chance to hit them
- for (var/mob/living/M in get_turf(src))
- M.Stun(pod.landingDelay+10, ignore_canstun = TRUE)//you aint goin nowhere, kid.
- if (pod.effectStealth) //If effectStealth is true we want to be invisible
- icon_state = ""
- if (pod.fallDuration == initial(pod.fallDuration) && pod.landingDelay + pod.fallDuration < pod.fallingSoundLength)
+ for (var/mob/living/mob_in_pod in pod) //If there are any mobs in the supplypod, we want to set their view to the pod_landingzone. This is so that they can see where they are about to land
+ mob_in_pod.reset_perspective(src)
+ if(pod.effectStun) //If effectStun is true, stun any mobs caught on this pod_landingzone until the pod gets a chance to hit them
+ for (var/mob/living/target_living in get_turf(src))
+ target_living.Stun(pod.delays[POD_TRANSIT]+10, ignore_canstun = TRUE)//you ain't goin nowhere, kid.
+ if (pod.delays[POD_FALLING] == initial(pod.delays[POD_FALLING]) && pod.delays[POD_TRANSIT] + pod.delays[POD_FALLING] < pod.fallingSoundLength)
pod.fallingSoundLength = 3 //The default falling sound is a little long, so if the landing time is shorter than the default falling sound, use a special, shorter default falling sound
pod.fallingSound = 'sound/weapons/mortar_whistle.ogg'
- var/soundStartTime = pod.landingDelay - pod.fallingSoundLength + pod.fallDuration
+ var/soundStartTime = pod.delays[POD_TRANSIT] - pod.fallingSoundLength + pod.delays[POD_FALLING]
if (soundStartTime < 0)
soundStartTime = 1
- if (!pod.effectQuiet)
+ if (!pod.effectQuiet && !(pod.pod_flags & FIRST_SOUNDS))
addtimer(CALLBACK(src, .proc/playFallingSound), soundStartTime)
- addtimer(CALLBACK(src, .proc/beginLaunch, pod.effectCircle), pod.landingDelay)
+ addtimer(CALLBACK(src, .proc/beginLaunch, pod.effectCircle), pod.delays[POD_TRANSIT])
-/obj/effect/DPtarget/proc/playFallingSound()
+/obj/effect/pod_landingzone/proc/playFallingSound()
playsound(src, pod.fallingSound, pod.soundVolume, 1, 6)
-/obj/effect/DPtarget/proc/beginLaunch(effectCircle) //Begin the animation for the pod falling. The effectCircle param determines whether the pod gets to come in from any descent angle
- fallingPod = new /obj/effect/DPfall(drop_location(), pod)
- var/matrix/M = matrix(fallingPod.transform) //Create a new matrix that we can rotate
- var/angle = effectCircle ? rand(0,360) : rand(70,110) //The angle that we can come in from
- fallingPod.pixel_x = cos(angle)*400 //Use some ADVANCED MATHEMATICS to set the animated pod's position to somewhere on the edge of a circle with the center being the target
- fallingPod.pixel_z = sin(angle)*400
- var/rotation = Get_Pixel_Angle(fallingPod.pixel_z, fallingPod.pixel_x) //CUSTOM HOMEBREWED proc that is just arctan with extra steps
- M.Turn(rotation) //Turn our matrix accordingly
- fallingPod.transform = M //Transform the animated pod according to the matrix
- M = matrix(pod.transform) //Make another matrix based on the pod
- M.Turn(rotation) //Turn the matrix
- pod.transform = M //Turn the actual pod (Won't be visible until endLaunch() proc tho)
- animate(fallingPod, pixel_z = 0, pixel_x = -16, time = pod.fallDuration, , easing = LINEAR_EASING) //Make the pod fall! At an angle!
- addtimer(CALLBACK(src, .proc/endLaunch), pod.fallDuration, TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation
-
-/obj/effect/DPtarget/proc/endLaunch()
+/obj/effect/pod_landingzone/proc/beginLaunch(effectCircle) //Begin the animation for the pod falling. The effectCircle param determines whether the pod gets to come in from any descent angle
+ pod.addGlow()
pod.update_icon()
- pod.forceMove(drop_location()) //The fallingPod animation is over, now's a good time to forceMove the actual pod into position
- QDEL_NULL(fallingPod) //Delete the falling pod effect, because at this point its animation is over. We dont use temp_visual because we want to manually delete it as soon as the pod appears
- for (var/mob/living/M in src) //Remember earlier (initialization) when we moved mobs into the DPTarget so they wouldnt get lost in nullspace? Time to get them out
- M.forceMove(pod)
+ if (pod.style != STYLE_INVISIBLE)
+ pod.add_filter("motionblur",1,list("type"="motion_blur", "x"=0, "y"=3))
+ pod.forceMove(drop_location())
+ for (var/mob/living/M in pod) //Remember earlier (initialization) when we moved mobs into the pod_landingzone so they wouldnt get lost in nullspace? Time to get them out
+ M.reset_perspective(null)
+ var/angle = effectCircle ? rand(0,360) : rand(70,110) //The angle that we can come in from
+ pod.pixel_x = cos(angle)*32*length(smoke_effects) //Use some ADVANCED MATHEMATICS to set the animated pod's position to somewhere on the edge of a circle with the center being the target
+ pod.pixel_z = sin(angle)*32*length(smoke_effects)
+ var/rotation = Get_Pixel_Angle(pod.pixel_z, pod.pixel_x) //CUSTOM HOMEBREWED proc that is just arctan with extra steps
+ setupSmoke(rotation)
+ pod.transform = matrix().Turn(rotation)
+ pod.layer = FLY_LAYER
+ if (pod.style != STYLE_INVISIBLE)
+ animate(pod.get_filter("motionblur"), y = 0, time = pod.delays[POD_FALLING], flags = ANIMATION_PARALLEL)
+ animate(pod, pixel_z = -1 * abs(sin(rotation))*4, pixel_x = SUPPLYPOD_X_OFFSET + (sin(rotation) * 20), time = pod.delays[POD_FALLING], easing = LINEAR_EASING, flags = ANIMATION_PARALLEL) //Make the pod fall! At an angle!
+ addtimer(CALLBACK(src, .proc/endLaunch), pod.delays[POD_FALLING], TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation
+
+/obj/effect/pod_landingzone/proc/setupSmoke(rotation)
+ if (pod.style == STYLE_INVISIBLE || pod.style == STYLE_SEETHROUGH)
+ return
+ for ( var/i in 1 to length(smoke_effects))
+ var/obj/effect/supplypod_smoke/smoke_part = new (drop_location())
+ if (i == 1)
+ smoke_part.layer = FLY_LAYER
+ smoke_part.icon_state = "smoke_start"
+ smoke_part.transform = matrix().Turn(rotation)
+ smoke_effects[i] = smoke_part
+ smoke_part.pixel_x = sin(rotation)*32 * i
+ smoke_part.pixel_y = abs(cos(rotation))*32 * i
+ smoke_part.add_filter("smoke_blur", 1, gauss_blur_filter(size = 4))
+ var/time = (pod.delays[POD_FALLING] / length(smoke_effects))*(length(smoke_effects)-i)
+ addtimer(CALLBACK(smoke_part, /obj/effect/supplypod_smoke/.proc/drawSelf, i), time, TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation
+ QDEL_IN(smoke_part, pod.delays[POD_FALLING] + 35)
+
+/obj/effect/pod_landingzone/proc/drawSmoke()
+ if (pod.style == STYLE_INVISIBLE || pod.style == STYLE_SEETHROUGH)
+ return
+ for (var/obj/effect/supplypod_smoke/smoke_part in smoke_effects)
+ animate(smoke_part, alpha = 0, time = 20, flags = ANIMATION_PARALLEL)
+ animate(smoke_part.get_filter("smoke_blur"), size = 6, time = 15, easing = CUBIC_EASING|EASE_OUT, flags = ANIMATION_PARALLEL)
+
+/obj/effect/pod_landingzone/proc/endLaunch()
+ pod.tryMakeRubble(drop_location())
+ pod.layer = initial(pod.layer)
+ pod.endGlow()
+ QDEL_NULL(helper)
pod.preOpen() //Begin supplypod open procedures. Here effects like explosions, damage, and other dangerous (and potentially admin-caused, if the centcom_podlauncher datum was used) memes will take place
+ drawSmoke()
qdel(src) //The target's purpose is complete. It can rest easy now
//------------------------------------UPGRADES-------------------------------------//
diff --git a/code/modules/chatter/chatter.dm b/code/modules/chatter/chatter.dm
index c985852c7dadd..6e6c69dd0aa3d 100644
--- a/code/modules/chatter/chatter.dm
+++ b/code/modules/chatter/chatter.dm
@@ -38,7 +38,7 @@
var/path = "sound/chatter/[phomeme]_[length].ogg"
playsound(loc, path,
- vol = 40, vary = 0, extrarange = 3, falloff = FALSE)
+ vol = 40, vary = 0, extrarange = 3)
sleep((length + 1) * chatter_get_sleep_multiplier(phomeme))
diff --git a/code/modules/client/client_colour.dm b/code/modules/client/client_colour.dm
index f1477fb4d2c2d..5773a58e9012f 100644
--- a/code/modules/client/client_colour.dm
+++ b/code/modules/client/client_colour.dm
@@ -1,77 +1,158 @@
-
-/*
- Client Colour Priority System By RemieRichards
- A System that gives finer control over which client.colour value to display on screen
- so that the "highest priority" one is always displayed as opposed to the default of
- "whichever was set last is displayed"
-*/
-
-
-
-/*
- Define subtypes of this datum
-*/
+#define PRIORITY_ABSOLUTE 1
+#define PRIORITY_HIGH 10
+#define PRIORITY_NORMAL 100
+#define PRIORITY_LOW 1000
+
+/**
+ * Client Colour Priority System By RemieRichards (then refactored by another contributor)
+ * A System that gives finer control over which client.colour value to display on screen
+ * so that the "highest priority" one is always displayed as opposed to the default of
+ * "whichever was set last is displayed".
+ *
+ * Refactored to allow multiple overlapping client colours
+ * (e.g. wearing blue glasses under a yellow visor, even though the result is a little unsatured.)
+ * As well as some support for animated colour transitions.
+ *
+ * Define subtypes of this datum
+ */
/datum/client_colour
- var/colour = "" //Any client.color-valid value
- var/priority = 1 //Since only one client.color can be rendered on screen, we take the one with the highest priority value:
- //eg: "Bloody screen" > "goggles colour" as the former is much more important
-
+ ///Any client.color-valid value
+ var/colour = ""
+ ///The mob that owns this client_colour.
+ var/mob/owner
+ /**
+ * We prioritize colours with higher priority (lower numbers), so they don't get overriden by less important ones:
+ * eg: "Bloody screen" > "goggles colour" as the former is much more important
+ */
+ var/priority = PRIORITY_NORMAL
+ ///Will this client_colour prevent ones of lower priority from being applied?
+ var/override = FALSE
+ ///IF non-zero, 'animate_client_colour(fade_in)' will be called instead of 'update_client_colour' when added.
+ var/fade_in = 0
+ ///Same as above, but on removal.
+ var/fade_out = 0
+
+/datum/client_colour/New(mob/_owner)
+ owner = _owner
+
+/datum/client_colour/Destroy()
+ if(!QDELETED(owner))
+ owner.client_colours -= src
+ if(fade_out)
+ owner.animate_client_colour(fade_out)
+ else
+ owner.update_client_colour()
+ owner = null
+ return ..()
+
+///Sets a new colour, then updates the owner's screen colour.
+/datum/client_colour/proc/update_colour(new_colour, anim_time, easing = 0)
+ colour = new_colour
+ if(anim_time)
+ owner.animate_client_colour(anim_time, easing)
+ else
+ owner.update_client_colour()
/mob
var/list/client_colours = list()
-
-
-/*
- Adds an instance of colour_type to the mob's client_colours list
- colour_type - a typepath (subtyped from /datum/client_colour)
-*/
+/**
+ * Adds an instance of colour_type to the mob's client_colours list
+ * colour_type - a typepath (subtyped from /datum/client_colour)
+ */
/mob/proc/add_client_colour(colour_type)
if(!ispath(colour_type, /datum/client_colour))
return
- var/datum/client_colour/CC = new colour_type()
- client_colours |= CC
- sortTim(client_colours, /proc/cmp_clientcolour_priority)
- update_client_colour()
-
-
-/*
- Removes an instance of colour_type from the mob's client_colours list
- colour_type - a typepath (subtyped from /datum/client_colour)
-*/
+ var/datum/client_colour/colour = new colour_type(src)
+ BINARY_INSERT(colour, client_colours, /datum/client_colour, colour, priority, COMPARE_KEY)
+ if(colour.fade_in)
+ animate_client_colour(colour.fade_in)
+ else
+ update_client_colour()
+ return colour
+
+/**
+ * Removes an instance of colour_type from the mob's client_colours list
+ * colour_type - a typepath (subtyped from /datum/client_colour)
+ */
/mob/proc/remove_client_colour(colour_type)
if(!ispath(colour_type, /datum/client_colour))
return
for(var/cc in client_colours)
- var/datum/client_colour/CC = cc
- if(CC.type == colour_type)
- client_colours -= CC
- qdel(CC)
+ var/datum/client_colour/colour = cc
+ if(colour.type == colour_type)
+ qdel(colour)
break
- update_client_colour()
-
-/*
- Resets the mob's client.color to null, and then sets it to the highest priority
- client_colour datum, if one exists
-*/
+/**
+ * Gets the resulting colour/tone from client_colours.
+ * In the case of multiple colours, they'll be converted to RGBA matrices for compatibility,
+ * summed together, and then each element divided by the number of matrices. (except we do this with lists because byond)
+ * target is the target variable.
+ */
+#define MIX_CLIENT_COLOUR(target)\
+ var/_our_colour;\
+ var/_number_colours = 0;\
+ var/_pool_closed = INFINITY;\
+ for(var/_c in client_colours){\
+ var/datum/client_colour/_colour = _c;\
+ if(_pool_closed < _colour.priority){\
+ break\
+ };\
+ _number_colours++;\
+ if(_colour.override){\
+ _pool_closed = _colour.priority\
+ };\
+ if(!_our_colour){\
+ _our_colour = _colour.colour;\
+ continue\
+ };\
+ if(_number_colours == 2){\
+ _our_colour = color_to_full_rgba_matrix(_our_colour)\
+ };\
+ var/list/_colour_matrix = color_to_full_rgba_matrix(_colour.colour);\
+ var/list/_L = _our_colour;\
+ for(var/_i in 1 to 20){\
+ _L[_i] += _colour_matrix[_i]\
+ };\
+ };\
+ if(_number_colours > 1){\
+ var/list/_L = _our_colour;\
+ for(var/_i in 1 to 20){\
+ _L[_i] /= _number_colours\
+ };\
+ };\
+ target = _our_colour\
+
+
+/**
+ * Resets the mob's client.color to null, and then reapplies a new color based
+ * on the client_colour datums it currently has.
+ */
/mob/proc/update_client_colour()
if(!client)
return
client.color = ""
if(!client_colours.len)
return
- var/datum/client_colour/CC = client_colours[1]
- if(CC)
- client.color = CC.colour
-
+ MIX_CLIENT_COLOUR(client.color)
+///Works similarly to 'update_client_colour', but animated.
+/mob/proc/animate_client_colour(anim_time = 20, anim_easing = 0)
+ if(!client)
+ return
+ if(!client_colours.len)
+ animate(client, color = "", time = anim_time, easing = anim_easing)
+ return
+ MIX_CLIENT_COLOUR(var/anim_colour)
+ animate(client, color = anim_colour, time = anim_time, easing = anim_easing)
+#undef MIX_CLIENT_COLOUR
/datum/client_colour/glass_colour
- priority = 0
+ priority = PRIORITY_LOW
colour = "red"
/datum/client_colour/glass_colour/green
@@ -110,4 +191,21 @@
/datum/client_colour/monochrome
colour = list(rgb(77,77,77), rgb(150,150,150), rgb(28,28,28), rgb(0,0,0))
- priority = INFINITY //we can't see colors anyway!
+ priority = PRIORITY_HIGH //we can't see colors anyway!
+ override = TRUE
+ fade_in = 20
+ fade_out = 20
+
+/datum/client_colour/bloodlust
+ priority = PRIORITY_ABSOLUTE // Only anger.
+ colour = list(0,0,0,0,0,0,0,0,0,1,0,0) //pure red.
+ fade_out = 10
+
+/datum/client_colour/bloodlust/New(mob/_owner)
+ ..()
+ addtimer(CALLBACK(src, .proc/update_colour, list(1,0,0,0.8,0.2,0, 0.8,0,0.2,0.1,0,0), 10, SINE_EASING|EASE_OUT), 1)
+
+#undef PRIORITY_ABSOLUTE
+#undef PRIORITY_HIGH
+#undef PRIORITY_NORMAL
+#undef PRIORITY_LOW
diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm
index b77e28ef24e4c..814cf3f84b786 100644
--- a/code/modules/client/client_defines.dm
+++ b/code/modules/client/client_defines.dm
@@ -25,8 +25,9 @@
var/total_message_count = 0
/// Next tick to reset the total message counter
var/total_count_reset = 0
- var/ircreplyamount = 0
+ var/externalreplyamount = 0
var/cryo_warned = -3000//when was the last time we warned them about not cryoing without an ahelp, set to -5 minutes so that rounstart cryo still warns
+ var/staff_check_rate = 0 //when was the last time they checked online staff
/////////
//OTHER//
@@ -34,6 +35,7 @@
/// The client's preferences
var/datum/preferences/prefs = null
var/list/keybindings[0]
+ var/movement_locked = FALSE
/// The last world.time that the client's mob turned
var/last_turn = 0
@@ -42,11 +44,7 @@
var/move_delay = 0
var/area = null
- ///////////////
- //SOUND STUFF//
- ///////////////
- var/ambient_buzz_playing = null // What buzz ambience is currently playing
- var/ambient_effect_last_played = 0 // What was the last time we played an ambient effect noise?
+ var/buzz_playing = null
////////////
//SECURITY//
////////////
@@ -70,7 +68,7 @@
preload_rsc = PRELOAD_RSC
- var/obj/screen/click_catcher/void
+ var/atom/movable/screen/click_catcher/void
//These two vars are used to make a special mouse cursor, with a unique icon for clicking
/// Mouse icon while not clicking
@@ -99,7 +97,7 @@
/// These persist between logins/logouts during the same round.
var/datum/player_details/player_details
- var/list/char_render_holders //Should only be a key-value list of north/south/east/west = obj/screen.
+ var/list/char_render_holders //Should only be a key-value list of north/south/east/west = atom/movable/screen.
var/client_keysend_amount = 0
var/next_keysend_reset = 0
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index 1c9ab5a76ca3f..18b281e10926b 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -4,7 +4,7 @@
#define UPLOAD_LIMIT 10485760 //Restricts client uploads to the server to 1MB //Could probably do with being lower.
-
+#define MAX_RECOMMENDED_CLIENT 1568
GLOBAL_LIST_INIT(blacklisted_builds, list(
"1407" = "bug preventing client display overrides from working leads to clients being able to see things/mobs they shouldn't be able to see",
@@ -95,6 +95,9 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
if(tgui_Topic(href_list))
return
+ if(href_list["reload_tguipanel"])
+ nuke_chat()
+
// Admin PM
if(href_list["priv_msg"])
cmd_admin_pm(href_list["priv_msg"],null)
@@ -215,6 +218,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
tgui_panel = new(src)
GLOB.ahelp_tickets.ClientLogin(src)
+ GLOB.requests.client_login(src)
var/connecting_admin = FALSE //because de-admined admins connecting should be treated like admins.
//Admin Authorisation
holder = GLOB.admin_datums[ckey]
@@ -223,7 +227,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
holder.owner = src
connecting_admin = TRUE
else if(GLOB.deadmins[ckey])
- verbs += /client/proc/readmin
+ add_verb(/client/proc/readmin)
connecting_admin = TRUE
if(CONFIG_GET(flag/autoadmin))
if(!GLOB.admin_datums[ckey])
@@ -252,8 +256,10 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
prefs.last_id = computer_id //these are gonna be used for banning
fps = prefs.clientfps
+ prefs.handle_donator_items()
+
if(fexists(roundend_report_file()))
- verbs += /client/proc/show_previous_roundend_report
+ add_verb(/client/proc/show_previous_roundend_report)
var/full_version = "[byond_version].[byond_build ? byond_build : "xxx"]"
log_access("Login: [key_name(src)] from [address ? address : "localhost"]-[computer_id] || BYOND v[full_version]")
@@ -309,7 +315,9 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
else
qdel(src)
return
-
+ if(byond_build > MAX_RECOMMENDED_CLIENT)
+ to_chat(src, "Your version of byond is over the maximum recommended version for clients (build [MAX_RECOMMENDED_CLIENT]) and may be unstable.")
+ to_chat(src, "Please download an older version of byond. You can go to BYOND's website to download other versions.")
if(SSinput.initialized)
set_macros()
@@ -385,7 +393,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
if (nnpa >= 0)
message_admins("New user: [key_name_admin(src)] is connecting here for the first time.")
if (CONFIG_GET(flag/irc_first_connection_alert))
- send2irc_adminless_only("New-user", "[key_name(src)] is connecting for the first time!")
+ send2tgs_adminless_only("New-user", "[key_name(src)] is connecting for the first time!")
else if (isnum_safe(cached_player_age) && cached_player_age < nnpa)
message_admins("New user: [key_name_admin(src)] just connected with an age of [cached_player_age] day[(player_age==1?"":"s")]")
if(CONFIG_GET(flag/use_account_age_for_jobs) && account_age >= 0)
@@ -393,13 +401,13 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
if(account_age >= 0 && account_age < nnpa)
message_admins("[key_name_admin(src)] (IP: [address], ID: [computer_id]) is a new BYOND account [account_age] day[(account_age==1?"":"s")] old, created on [account_join_date].")
if (CONFIG_GET(flag/irc_first_connection_alert))
- send2irc_adminless_only("new_byond_user", "[key_name(src)] (IP: [address], ID: [computer_id]) is a new BYOND account [account_age] day[(account_age==1?"":"s")] old, created on [account_join_date].")
+ send2tgs_adminless_only("new_byond_user", "[key_name(src)] (IP: [address], ID: [computer_id]) is a new BYOND account [account_age] day[(account_age==1?"":"s")] old, created on [account_join_date].")
get_message_output("watchlist entry", ckey)
check_ip_intel()
validate_key_in_db()
fetch_uuid()
- verbs += /client/proc/show_account_identifier
+ add_verb(/client/proc/show_account_identifier)
send_resources()
@@ -424,32 +432,12 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
if(!winexists(src, "asset_cache_browser")) // The client is using a custom skin, tell them.
to_chat(src, "Unable to access asset cache browser, if you are using a custom skin file, please allow DS to download the updated version, if you are not, then make a bug report. This is not a critical issue but can cause issues with resource downloading, as it is impossible to know when extra resources arrived to you.")
+ update_ambience_pref()
//This is down here because of the browse() calls in tooltip/New()
if(!tooltips)
tooltips = new /datum/tooltip(src)
- var/list/topmenus = GLOB.menulist[/datum/verbs/menu]
- for (var/thing in topmenus)
- var/datum/verbs/menu/topmenu = thing
- var/topmenuname = "[topmenu]"
- if (topmenuname == "[topmenu.type]")
- var/list/tree = splittext(topmenuname, "/")
- topmenuname = tree[tree.len]
- winset(src, "[topmenu.type]", "parent=menu;name=[rustg_url_encode(topmenuname)]")
- var/list/entries = topmenu.Generate_list(src)
- for (var/child in entries)
- winset(src, "[child]", "[entries[child]]")
- if (!ispath(child, /datum/verbs/menu))
- var/procpath/verbpath = child
- if (verbpath.name[1] != "@")
- new child(src)
-
- for (var/thing in prefs.menuoptions)
- var/datum/verbs/menu/menuitem = GLOB.menulist[thing]
- if (menuitem)
- menuitem.Load_checked(src)
-
view_size = new(src, getScreenSize(mob))
view_size.resetFormat()
view_size.setZoomMode()
@@ -457,8 +445,17 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
Master.UpdateTickRate()
if(GLOB.ckey_redirects.Find(ckey))
- to_chat(src, "The server is full. You will be redirected to [CONFIG_GET(string/redirect_address)] in 10 seconds.")
- addtimer(CALLBACK(src, .proc/time_to_redirect), (10 SECONDS))
+ if(isnewplayer(mob))
+ to_chat(src, "The server is full. You will be redirected to [CONFIG_GET(string/redirect_address)] in 10 seconds.")
+ addtimer(CALLBACK(src, .proc/time_to_redirect), (10 SECONDS))
+ else
+ GLOB.ckey_redirects -= ckey
+
+ //Add the default client verbs to the TGUI window
+ add_verb(subtypesof(/client/verb), TRUE)
+
+ //Load the TGUI stat in case of TGUI subsystem not ready (startup)
+ mob.UpdateMobStat(TRUE)
/client/proc/time_to_redirect()
var/redirect_address = CONFIG_GET(string/redirect_address)
@@ -541,12 +538,14 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
"Forever alone :("\
)
- send2irc("Server", "[cheesy_message] (No admins online)")
+ send2tgs("Server", "[cheesy_message] (No admins online)")
GLOB.ahelp_tickets.ClientLogout(src)
+ GLOB.requests.client_logout(src)
GLOB.directory -= ckey
GLOB.clients -= src
GLOB.mentors -= src
+ SSambience.ambience_listening_clients -= src
QDEL_LIST_ASSOC_VAL(char_render_holders)
if(movingmob != null)
movingmob.client_mobs_in_contents -= mob
@@ -598,11 +597,19 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
if(!query_client_in_db.Execute())
qdel(query_client_in_db)
return
- if(!query_client_in_db.NextRow())
- if (CONFIG_GET(flag/panic_bunker) && !holder && !GLOB.deadmins[ckey])
- log_access("Failed Login: [key] - New account attempting to connect during panic bunker")
- message_admins("Failed Login: [key] - New account attempting to connect during panic bunker")
- to_chat(src, CONFIG_GET(string/panic_bunker_message))
+
+ //If we aren't an admin, and the flag is set
+ if(CONFIG_GET(flag/panic_bunker) && !holder && !GLOB.deadmins[ckey])
+ var/living_recs = CONFIG_GET(number/panic_bunker_living)
+ //Relies on pref existing, but this proc is only called after that occurs, so we're fine.
+ var/minutes = get_exp_living(pure_numeric = TRUE)
+ if(minutes < living_recs)
+ var/reject_message = "Failed Login: [key] - Account attempting to connect during panic bunker, but they do not have the required living time [minutes]/[living_recs]"
+ log_access(reject_message)
+ message_admins("[reject_message]")
+ var/message = CONFIG_GET(string/panic_bunker_message)
+ message = replacetext(message, "%minutes%", living_recs)
+ to_chat(src, message)
var/list/connectiontopic_a = params2list(connectiontopic)
var/list/panic_addr = CONFIG_GET(string/panic_server_address)
if(panic_addr && !connectiontopic_a["redirect"])
@@ -614,6 +621,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
qdel(src)
return
+ if(!query_client_in_db.NextRow())
new_player = 1
account_join_date = findJoinDate()
var/datum/DBQuery/query_add_player = SSdbcore.NewQuery({"
@@ -777,7 +785,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
if (!cidcheck_failedckeys[ckey])
message_admins("[key_name(src)] has been detected as using a cid randomizer. Connection rejected.")
- send2irc_adminless_only("CidRandomizer", "[key_name(src)] has been detected as using a cid randomizer. Connection rejected.")
+ send2tgs_adminless_only("CidRandomizer", "[key_name(src)] has been detected as using a cid randomizer. Connection rejected.")
cidcheck_failedckeys[ckey] = TRUE
note_randomizer_user()
@@ -788,7 +796,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
else
if (cidcheck_failedckeys[ckey])
message_admins("[key_name_admin(src)] has been allowed to connect after showing they removed their cid randomizer")
- send2irc_adminless_only("CidRandomizer", "[key_name(src)] has been allowed to connect after showing they removed their cid randomizer.")
+ send2tgs_adminless_only("CidRandomizer", "[key_name(src)] has been allowed to connect after showing they removed their cid randomizer.")
cidcheck_failedckeys -= ckey
if (cidcheck_spoofckeys[ckey])
message_admins("[key_name_admin(src)] has been allowed to connect after appearing to have attempted to spoof a cid randomizer check because it appears they aren't spoofing one this time")
@@ -913,9 +921,9 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
/client/proc/add_verbs_from_config()
if(CONFIG_GET(flag/see_own_notes))
- verbs += /client/proc/self_notes
+ add_verb(/client/proc/self_notes)
if(CONFIG_GET(flag/use_exp_tracking))
- verbs += /client/proc/self_playtime
+ add_verb(/client/proc/self_playtime)
#undef UPLOAD_LIMIT
@@ -964,6 +972,8 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
return FALSE
if ("key")
return FALSE
+ if("cached_badges")
+ return FALSE
if("view")
view_size.setDefault(var_value)
return TRUE
@@ -1003,7 +1013,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
var/pos = 0
for(var/D in GLOB.cardinals)
pos++
- var/obj/screen/O = LAZYACCESS(char_render_holders, "[D]")
+ var/atom/movable/screen/O = LAZYACCESS(char_render_holders, "[D]")
if(!O)
O = new
LAZYSET(char_render_holders, "[D]", O)
@@ -1014,7 +1024,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
/client/proc/clear_character_previews()
for(var/index in char_render_holders)
- var/obj/screen/S = char_render_holders[index]
+ var/atom/movable/screen/S = char_render_holders[index]
screen -= S
qdel(S)
char_render_holders = null
@@ -1024,7 +1034,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
set category = "OOC"
set desc ="Get your ID for account verification."
- verbs -= /client/proc/show_account_identifier
+ remove_verb(/client/proc/show_account_identifier)
addtimer(CALLBACK(src, .proc/restore_account_identifier), 20) //Don't DoS DB queries, asshole
var/confirm = alert("Do NOT share the verification ID in the following popup. Understand?", "Important Warning", "Yes", "Cancel")
@@ -1047,7 +1057,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
onclose(src, "accountidentifier")
/client/proc/restore_account_identifier()
- verbs += /client/proc/show_account_identifier
+ add_verb(/client/proc/show_account_identifier)
/client/proc/check_upstream_bans()
set waitfor = 0
@@ -1079,3 +1089,15 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
qdel(src)
return
+/client/proc/open_filter_editor(atom/in_atom)
+ if(holder)
+ holder.filteriffic = new /datum/filter_editor(in_atom)
+ holder.filteriffic.ui_interact(mob)
+
+/client/proc/update_ambience_pref()
+ if(prefs.toggles & SOUND_AMBIENCE)
+ if(SSambience.ambience_listening_clients[src] > world.time)
+ return // If already properly set we don't want to reset the timer.
+ SSambience.ambience_listening_clients[src] = world.time + 10 SECONDS //Just wait 10 seconds before the next one aight mate? cheers.
+ else
+ SSambience.ambience_listening_clients -= src
diff --git a/code/modules/client/loadout/loadout.dm b/code/modules/client/loadout/loadout.dm
index 58a3e2ad7bd60..a62c4ad4c832e 100644
--- a/code/modules/client/loadout/loadout.dm
+++ b/code/modules/client/loadout/loadout.dm
@@ -11,10 +11,14 @@ GLOBAL_LIST_EMPTY(gear_datums)
/proc/populate_gear_list()
//create a list of gear datums to sort
+ var/list/used_ids = list()
for(var/geartype in subtypesof(/datum/gear))
var/datum/gear/G = geartype
var/use_name = initial(G.display_name)
+ var/use_id = initial(G.id)
+ if(!use_id) //Not set, generate fallback
+ use_id = md5(use_name)
var/use_category = initial(G.sort_category)
if(G == initial(G.subtype_path))
@@ -23,6 +27,9 @@ GLOBAL_LIST_EMPTY(gear_datums)
if(!use_name)
WARNING("Loadout - Missing display name: [G]")
continue
+ if(use_id in used_ids)
+ WARNING("Loadout - ID Already Exists: [G], with ID:[use_id], Conflicts with: [used_ids[use_id]]")
+ continue
if(!initial(G.cost))
WARNING("Loadout - Missing cost: [G]")
continue
@@ -32,18 +39,19 @@ GLOBAL_LIST_EMPTY(gear_datums)
if(!GLOB.loadout_categories[use_category])
GLOB.loadout_categories[use_category] = new /datum/loadout_category(use_category)
+ used_ids[use_id] = G
var/datum/loadout_category/LC = GLOB.loadout_categories[use_category]
- GLOB.gear_datums[use_name] = new geartype
- LC.gear[use_name] = GLOB.gear_datums[use_name]
+ GLOB.gear_datums[use_id] = new geartype
+ LC.gear[use_id] = GLOB.gear_datums[use_id]
GLOB.loadout_categories = sortAssoc(GLOB.loadout_categories)
- return 1
/datum/gear
- var/display_name //Name/index. Must be unique.
+ var/display_name //Name. Should be unique.
+ var/id //ID string. MUST be unique.
var/description //Description of this gear. If left blank will default to the description of the pathed item.
var/path //Path to item.
- var/cost = INFINITY //Number of metacoins
+ var/cost = INFINITY //Number of metacoins
var/slot //Slot to equip to.
var/list/allowed_roles //Roles that can spawn with this item.
var/list/species_blacklist //Stop certain species from receiving this gear
@@ -53,6 +61,7 @@ GLOBAL_LIST_EMPTY(gear_datums)
/datum/gear/New()
..()
+ id = md5(display_name)
if(!description)
var/obj/O = path
description = initial(O.desc)
diff --git a/code/modules/client/loadout/loadout_accessories.dm b/code/modules/client/loadout/loadout_accessories.dm
index cb2b8170cfaae..6502d8d2d6533 100644
--- a/code/modules/client/loadout/loadout_accessories.dm
+++ b/code/modules/client/loadout/loadout_accessories.dm
@@ -2,7 +2,7 @@
/datum/gear/accessory
subtype_path = /datum/gear/accessory
- slot = SLOT_NECK
+ slot = ITEM_SLOT_NECK
sort_category = "Accessories"
/datum/gear/accessory/scarf
@@ -36,11 +36,22 @@
path = /obj/item/clothing/neck/stripedbluescarf
cost = 1200
+//armbands
/datum/gear/accessory/armband_red
- display_name = "armband"
+ display_name = "armband, red"
path = /obj/item/clothing/accessory/armband
cost = 1000
+/datum/gear/accessory/armband_blu
+ display_name = "armband, blue"
+ path = /obj/item/clothing/accessory/armband/blue
+ cost = 1000
+
+/datum/gear/accessory/armband_grn
+ display_name = "armband, green"
+ path = /obj/item/clothing/accessory/armband/green
+ cost = 1000
+
//ties
/datum/gear/accessory/tie
subtype_path = /datum/gear/accessory/tie
@@ -88,19 +99,19 @@
/datum/gear/accessory/eyepatch
display_name = "eyepatch"
- slot = SLOT_GLASSES
+ slot = ITEM_SLOT_EYES
path = /obj/item/clothing/glasses/eyepatch
cost = 1200
/datum/gear/accessory/monocle
display_name = "monocle"
- slot = SLOT_GLASSES
+ slot = ITEM_SLOT_EYES
path = /obj/item/clothing/glasses/monocle
cost = 1200
/datum/gear/accessory/glasses
display_name = "prescription glasses"
- slot = SLOT_GLASSES
+ slot = ITEM_SLOT_EYES
path = /obj/item/clothing/glasses/regular
cost = 3000
@@ -129,7 +140,7 @@
path = /obj/item/clothing/glasses/heat
/datum/gear/accessory/glasses/orange
- display_name = "sunglasses"
+ display_name = "orange sunglasses"
path = /obj/item/clothing/glasses/orange
/datum/gear/accessory/glasses/red
@@ -162,3 +173,14 @@
display_name = "lipstick, random color"
path = /obj/item/lipstick/random
cost = 1400
+
+//Cloaks
+
+/datum/gear/accessory/cloak
+ subtype_path = /datum/gear/accessory/cloak
+ cost = 10000
+
+/datum/gear/accessory/cloak/blackbishop
+ display_name = "black bishop's cloak"
+ path = /obj/item/clothing/neck/cloak/chap/bishop/black
+ allowed_roles = list("Chaplain")
diff --git a/code/modules/client/loadout/loadout_donator.dm b/code/modules/client/loadout/loadout_donator.dm
new file mode 100644
index 0000000000000..6231340325a87
--- /dev/null
+++ b/code/modules/client/loadout/loadout_donator.dm
@@ -0,0 +1,136 @@
+/datum/gear/donator
+ subtype_path = /datum/gear/donator
+ sort_category = "Donator"
+ cost = 3500
+
+//BACKPACKS
+
+/datum/gear/donator/backpack
+ subtype_path = /datum/gear/donator/backpack
+ slot = ITEM_SLOT_BACK
+
+/datum/gear/donator/backpack/ian
+ display_name = "ian backpack"
+ path = /obj/item/storage/backpack/ian
+
+/datum/gear/donator/backpack/lisa
+ display_name = "lisa backpack"
+ path = /obj/item/storage/backpack/lisa
+
+/datum/gear/donator/backpack/renault
+ display_name = "renault backpack"
+ path = /obj/item/storage/backpack/renault
+
+/datum/gear/donator/backpack/cak
+ display_name = "cak catpack"
+ path = /obj/item/storage/backpack/cak
+
+/datum/gear/donator/backpack/runtime
+ display_name = "runtime catpack"
+ path = /obj/item/storage/backpack/runtime
+
+//SUIT
+
+/datum/gear/donator/suit
+ subtype_path = /datum/gear/donator/suit
+ slot = ITEM_SLOT_OCLOTHING
+
+/datum/gear/donator/suit/delinquent
+ display_name = "deliquent jacket"
+ path = /obj/item/clothing/suit/delinquent
+
+/datum/gear/donator/suit/madsci
+ display_name = "mad scientist labcoat"
+ path = /obj/item/clothing/suit/madsci
+
+/datum/gear/donator/suit/renault_costume
+ display_name = "renault costume"
+ path = /obj/item/clothing/suit/hooded/renault_costume
+
+/datum/gear/donator/suit/retro_jacket
+ display_name = "retro jacket"
+ path = /obj/item/clothing/suit/retro_jacket
+
+//UNIFORM/UNDER
+/datum/gear/donator/uniform
+ subtype_path = /datum/gear/donator/uniform
+ slot = ITEM_SLOT_ICLOTHING
+
+/datum/gear/donator/uniform/wine_gown
+ display_name = "wine gown"
+ path = /obj/item/clothing/under/dress/gown
+
+/datum/gear/donator/uniform/teal_gown
+ display_name = "teal gown"
+ path = /obj/item/clothing/under/dress/gown/teal
+
+/datum/gear/donator/uniform/midnight_gown
+ display_name = "midnight gown"
+ path = /obj/item/clothing/under/dress/gown/midnight
+
+/datum/gear/donator/uniform/gangster
+ display_name = "gangster suit"
+ path = /obj/item/clothing/under/gangster
+
+/datum/gear/donator/uniform/gangster_purple
+ display_name = "purple gangster suit"
+ path = /obj/item/clothing/under/gangster/purple
+
+//HEAD
+
+/datum/gear/donator/head
+ subtype_path = /datum/gear/donator/head
+ slot = ITEM_SLOT_HEAD
+
+/datum/gear/donator/head/gangster_wig
+ display_name = "gangster wig"
+ path = /obj/item/clothing/head/gangsterwig
+
+/datum/gear/donator/head/oldhat
+ display_name = "old man hat"
+ path = /obj/item/clothing/head/oldhat
+
+/datum/gear/donator/head/marine
+ display_name = "mariner hat"
+ path = /obj/item/clothing/head/marine
+
+/datum/gear/donator/suit/retro_chicken_head
+ display_name = "retro chicken head"
+ path = /obj/item/clothing/head/chicken_head_retro
+
+//NECK
+/datum/gear/donator/neck
+ subtype_path = /datum/gear/donator/neck
+ slot = ITEM_SLOT_NECK
+
+/datum/gear/donator/neck/bizzare
+ display_name = "bizzare scarf"
+ path = /obj/item/clothing/neck/bizzarescarf
+
+/datum/gear/donator/neck/conductive
+ display_name = "conductive scarf"
+ path = /obj/item/clothing/neck/conductivescarf
+
+//ITEMS
+
+/datum/gear/donator/item
+ subtype_path = /datum/gear/donator/item
+ slot = ITEM_SLOT_BACKPACK
+ cost = 5000
+
+/datum/gear/donator/item/plush_ian
+ display_name = "ian plushie"
+ path = /obj/item/toy/plush/ian
+
+/datum/gear/donator/item/plush_lisa
+ display_name = "lisa plushie"
+ path = /obj/item/toy/plush/lisa
+
+/datum/gear/donator/item/plush_renault
+ display_name = "renault plushie"
+ path = /obj/item/toy/plush/renault
+
+/datum/gear/donator/item/plush_opa
+ display_name = "metal opa plushie"
+ path = /obj/item/toy/plush/opa
+
diff --git a/code/modules/client/loadout/loadout_footwear.dm b/code/modules/client/loadout/loadout_footwear.dm
index 23e6678375cfd..24df6ff8377f1 100644
--- a/code/modules/client/loadout/loadout_footwear.dm
+++ b/code/modules/client/loadout/loadout_footwear.dm
@@ -1,6 +1,6 @@
/datum/gear/footwear
subtype_path = /datum/gear/footwear
- slot = SLOT_SHOES
+ slot = ITEM_SLOT_FEET
sort_category = "Footwear"
cost = 1000
@@ -19,6 +19,11 @@
path = /obj/item/clothing/shoes/winterboots
cost = 4000
+/datum/gear/footwear/swagshoes
+ display_name = "swag shoes"
+ path = /obj/item/clothing/shoes/swagshoes
+ cost = 31000
+
//Standard shoes
/datum/gear/footwear/color
diff --git a/code/modules/client/loadout/loadout_hat.dm b/code/modules/client/loadout/loadout_hat.dm
index 280aa1b62f06f..9eab99af345a6 100644
--- a/code/modules/client/loadout/loadout_hat.dm
+++ b/code/modules/client/loadout/loadout_hat.dm
@@ -1,6 +1,6 @@
/datum/gear/hat
subtype_path = /datum/gear/hat
- slot = SLOT_HEAD
+ slot = ITEM_SLOT_HEAD
sort_category = "Headwear"
species_blacklist = list("plasmaman") //Their helmet takes up the head slot
cost = 2000
@@ -190,3 +190,10 @@
path = /obj/item/clothing/head/pirate
cost = 5000
+//CHAPLAIN HATS
+
+/datum/gear/hat/blackbishop
+ display_name = "black bishop mitre"
+ path = /obj/item/clothing/head/bishopmitre/black
+ cost = 5000
+ allowed_roles = list("Chaplain")
diff --git a/code/modules/client/loadout/loadout_suit.dm b/code/modules/client/loadout/loadout_suit.dm
index 32068752713dd..4c1e3b64a73ab 100644
--- a/code/modules/client/loadout/loadout_suit.dm
+++ b/code/modules/client/loadout/loadout_suit.dm
@@ -1,6 +1,6 @@
/datum/gear/suit
subtype_path = /datum/gear/suit
- slot = SLOT_WEAR_SUIT
+ slot = ITEM_SLOT_OCLOTHING
sort_category = "External Wear"
cost = 2500
@@ -89,6 +89,10 @@
display_name = "bomber jacket"
path = /obj/item/clothing/suit/jacket
+/datum/gear/suit/jacket/softshell
+ display_name = "softshell jacket"
+ path = /obj/item/clothing/suit/toggle/softshell
+
/datum/gear/suit/jacket/leather
display_name = "leather jacket"
path = /obj/item/clothing/suit/jacket/leather
@@ -111,7 +115,7 @@
path = /obj/item/clothing/suit/jacket/letterman_red
/datum/gear/suit/jacket/letterman_nanotrasen
- display_name = "letterman jacket, NanoTrasen blue"
+ display_name = "letterman jacket, Nanotrasen blue"
path = /obj/item/clothing/suit/jacket/letterman_nanotrasen
cost = 5000
@@ -159,3 +163,13 @@
/datum/gear/suit/poncho/red
display_name = "poncho, red"
path = /obj/item/clothing/suit/poncho/red
+
+//ROBES
+/datum/gear/suit/robe
+ subtype_path = /datum/gear/suit/robe
+ cost = 5000
+
+/datum/gear/suit/robe/blackbishop
+ display_name = "black bishop's robes"
+ path = /obj/item/clothing/suit/chaplainsuit/bishoprobe/black
+ allowed_roles = list("Chaplain")
diff --git a/code/modules/client/loadout/loadout_uniform.dm b/code/modules/client/loadout/loadout_uniform.dm
index c70ef9044dfed..d566ace0f37b1 100644
--- a/code/modules/client/loadout/loadout_uniform.dm
+++ b/code/modules/client/loadout/loadout_uniform.dm
@@ -1,7 +1,7 @@
// Uniform slot
/datum/gear/uniform
subtype_path = /datum/gear/uniform
- slot = SLOT_W_UNIFORM
+ slot = ITEM_SLOT_ICLOTHING
sort_category = "Uniforms and Casual Dress"
species_blacklist = list("plasmaman") //Envirosuit moment
cost = 1000
@@ -234,6 +234,11 @@
path = /obj/item/clothing/under/costume/jabroni
cost = 20000
+/datum/gear/uniform/misc/geisha
+ display_name = "geisha suit"
+ path = /obj/item/clothing/under/costume/geisha
+ cost = 20000
+
/datum/gear/uniform/misc/soviet
display_name = "soviet uniform"
path = /obj/item/clothing/under/costume/soviet
@@ -246,6 +251,11 @@
display_name = "sailor outfit"
path = /obj/item/clothing/under/costume/sailor
+/datum/gear/uniform/misc/swagoutfit
+ display_name = "swag outfit"
+ path = /obj/item/clothing/under/costume/swagoutfit
+ cost = 69000
+
//RANK SUBTYPE
/datum/gear/uniform/rank
@@ -289,7 +299,7 @@
/datum/gear/uniform/rank/cargo/mining_classic
display_name = "miner's overalls, asteroid purple"
- description = "Purple jumpsuit, dirty overalls. Standard NanoTrasen Shaft Miner attire, from when Lavaland had yet to be discovered and exploited for plasma."
+ description = "Purple jumpsuit, dirty overalls. Standard Nanotrasen Shaft Miner attire, from when Lavaland had yet to be discovered and exploited for plasma."
path = /obj/item/clothing/under/rank/cargo/miner
allowed_roles = list ("Shaft Miner")
@@ -301,7 +311,7 @@
/datum/gear/uniform/rank/medical/nurse_dress
display_name = "medical, nurse's dress"
- path = /obj/item/clothing/under/rank/medical/doctor
+ path = /obj/item/clothing/under/rank/medical/doctor/nurse
/datum/gear/uniform/rank/medical/emt
display_name = "medical scrubs, EMT"
@@ -345,10 +355,10 @@
/datum/gear/uniform/rank/security/blueshift
display_name = "security jumpsuit, blue shirt and tie"
- path = /obj/item/clothing/under/rank/security/blueshirt
+ path = /obj/item/clothing/under/rank/security/officer/blueshirt
cost = 7500
/datum/gear/uniform/rank/security/blart
- display_name = "security jumpsuit, NanoTrasen mall cop uniform"
+ display_name = "security jumpsuit, Nanotrasen mall cop uniform"
path = /obj/item/clothing/under/rank/security/officer/mallcop
cost = 7500
diff --git a/code/modules/client/message.dm b/code/modules/client/message.dm
index 2b4f8453d7457..e282e3a6cbf62 100644
--- a/code/modules/client/message.dm
+++ b/code/modules/client/message.dm
@@ -4,6 +4,7 @@ GLOBAL_LIST_EMPTY(clientmessages)
ckey = ckey(ckey)
if (!ckey || !message)
return
- if (!(ckey in GLOB.clientmessages))
- GLOB.clientmessages[ckey] = list()
- GLOB.clientmessages[ckey] += message
\ No newline at end of file
+ var/list/L = GLOB.clientmessages[ckey]
+ if(!L)
+ GLOB.clientmessages[ckey] = L = list()
+ L += message
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 88a096d9841e4..f436ed2aae6cf 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -26,11 +26,20 @@ GLOBAL_LIST_EMPTY(preferences_datums)
//autocorrected this round, not that you'd need to check that.
var/UI_style = null
- var/overhead_chat = TRUE
var/outline_enabled = TRUE
var/outline_color = COLOR_BLUE_GRAY
var/buttons_locked = FALSE
var/hotkeys = FALSE
+
+ ///Runechat preference. If true, certain messages will be displayed on the map, not ust on the chat area. Boolean.
+ var/chat_on_map = TRUE
+ ///Whether non-mob messages will be displayed, such as machine vendor announcements. Requires chat_on_map to have effect. Boolean.
+ var/see_chat_non_mob = TRUE
+ ///Whether emotes will be displayed on runechat. Requires chat_on_map to have effect. Boolean.
+ var/see_rc_emotes = TRUE
+ ///Whether we want balloon alerts displayed alone, with chat or not displayed at all
+ var/see_balloon_alerts = BALLOON_ALERT_ALWAYS
+
var/tgui_fancy = TRUE
var/tgui_lock = TRUE
var/windowflashing = TRUE
@@ -67,6 +76,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/undershirt = "Nude" //undershirt type
var/socks = "Nude" //socks type
var/backbag = DBACKPACK //backpack type
+ var/jumpsuit_style = PREF_SUIT //suit/skirt
var/hair_style = "Bald" //Hair type
var/hair_color = "000" //Hair color
var/facial_hair_style = "Shaved" //Face hair type
@@ -136,7 +146,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
max_save_slots = 8
var/loaded_preferences_successfully = load_preferences()
if(loaded_preferences_successfully)
- if("extra character slot" in purchased_gear)
+ if("6030fe461e610e2be3a2c3e75c06067e" in purchased_gear) //MD5 hash of, "extra character slot"
max_save_slots += 1
if(load_character())
return
@@ -242,6 +252,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "Undershirt: [undershirt] "
dat += "Socks: [socks] "
dat += "Backpack: [backbag] "
+ dat += "Jumpsuit: [jumpsuit_style] "
dat += "Uplink Spawn Location: [uplink_spawn_loc] "
var/use_skintones = pref_species.use_skintones
@@ -520,11 +531,14 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "
Meteors have been detected on collision course with the station.
")
- SEND_SOUND(target, 'sound/ai/meteors.ogg')
+ SEND_SOUND(target, SSstation.announcer.event_sounds[ANNOUNCER_METEORS])
if("supermatter")
SEND_SOUND(target, 'sound/magic/charge.ogg')
to_chat(target, "You feel reality distort for a moment...")
@@ -930,46 +929,46 @@ GLOBAL_LIST_INIT(hallucination_list, list(
feedback_details += "Type: [alert_type]"
switch(alert_type)
if("not_enough_oxy")
- target.throw_alert(alert_type, /obj/screen/alert/not_enough_oxy, override = TRUE)
+ target.throw_alert(alert_type, /atom/movable/screen/alert/not_enough_oxy, override = TRUE)
if("not_enough_tox")
- target.throw_alert(alert_type, /obj/screen/alert/not_enough_tox, override = TRUE)
+ target.throw_alert(alert_type, /atom/movable/screen/alert/not_enough_tox, override = TRUE)
if("not_enough_co2")
- target.throw_alert(alert_type, /obj/screen/alert/not_enough_co2, override = TRUE)
+ target.throw_alert(alert_type, /atom/movable/screen/alert/not_enough_co2, override = TRUE)
if("too_much_oxy")
- target.throw_alert(alert_type, /obj/screen/alert/too_much_oxy, override = TRUE)
+ target.throw_alert(alert_type, /atom/movable/screen/alert/too_much_oxy, override = TRUE)
if("too_much_co2")
- target.throw_alert(alert_type, /obj/screen/alert/too_much_co2, override = TRUE)
+ target.throw_alert(alert_type, /atom/movable/screen/alert/too_much_co2, override = TRUE)
if("too_much_tox")
- target.throw_alert(alert_type, /obj/screen/alert/too_much_tox, override = TRUE)
+ target.throw_alert(alert_type, /atom/movable/screen/alert/too_much_tox, override = TRUE)
if("nutrition")
if(prob(50))
- target.throw_alert(alert_type, /obj/screen/alert/fat, override = TRUE)
+ target.throw_alert(alert_type, /atom/movable/screen/alert/fat, override = TRUE)
else
- target.throw_alert(alert_type, /obj/screen/alert/starving, override = TRUE)
+ target.throw_alert(alert_type, /atom/movable/screen/alert/starving, override = TRUE)
if("gravity")
- target.throw_alert(alert_type, /obj/screen/alert/weightless, override = TRUE)
+ target.throw_alert(alert_type, /atom/movable/screen/alert/weightless, override = TRUE)
if("fire")
- target.throw_alert(alert_type, /obj/screen/alert/fire, override = TRUE)
+ target.throw_alert(alert_type, /atom/movable/screen/alert/fire, override = TRUE)
if("temphot")
alert_type = "temp"
- target.throw_alert(alert_type, /obj/screen/alert/hot, 3, override = TRUE)
+ target.throw_alert(alert_type, /atom/movable/screen/alert/hot, 3, override = TRUE)
if("tempcold")
alert_type = "temp"
- target.throw_alert(alert_type, /obj/screen/alert/cold, 3, override = TRUE)
+ target.throw_alert(alert_type, /atom/movable/screen/alert/cold, 3, override = TRUE)
if("pressure")
if(prob(50))
- target.throw_alert(alert_type, /obj/screen/alert/highpressure, 2, override = TRUE)
+ target.throw_alert(alert_type, /atom/movable/screen/alert/highpressure, 2, override = TRUE)
else
- target.throw_alert(alert_type, /obj/screen/alert/lowpressure, 2, override = TRUE)
+ target.throw_alert(alert_type, /atom/movable/screen/alert/lowpressure, 2, override = TRUE)
//BEEP BOOP I AM A ROBOT
if("newlaw")
- target.throw_alert(alert_type, /obj/screen/alert/newlaw, override = TRUE)
+ target.throw_alert(alert_type, /atom/movable/screen/alert/newlaw, override = TRUE)
if("locked")
- target.throw_alert(alert_type, /obj/screen/alert/locked, override = TRUE)
+ target.throw_alert(alert_type, /atom/movable/screen/alert/locked, override = TRUE)
if("hacked")
- target.throw_alert(alert_type, /obj/screen/alert/hacked, override = TRUE)
+ target.throw_alert(alert_type, /atom/movable/screen/alert/hacked, override = TRUE)
if("charge")
- target.throw_alert(alert_type, /obj/screen/alert/emptycell, override = TRUE)
+ target.throw_alert(alert_type, /atom/movable/screen/alert/emptycell, override = TRUE)
sleep(duration)
target.clear_alert(alert_type, clear_override = TRUE)
qdel(src)
@@ -1044,7 +1043,7 @@ GLOBAL_LIST_INIT(hallucination_list, list(
//Flashes of danger
if(!target.halimage)
var/list/possible_points = list()
- for(var/turf/open/floor/F in view(target,world.view))
+ for(var/turf/open/floor/F in view(world.view, target))
possible_points += F
if(possible_points.len)
var/turf/open/floor/danger_point = pick(possible_points)
@@ -1116,8 +1115,8 @@ GLOBAL_LIST_INIT(hallucination_list, list(
. = ..()
START_PROCESSING(SSobj, src)
-/obj/effect/hallucination/danger/anomaly/process()
- if(prob(70))
+/obj/effect/hallucination/danger/anomaly/process(delta_time)
+ if(DT_PROB(45, delta_time))
step(src,pick(GLOB.alldirs))
/obj/effect/hallucination/danger/anomaly/Destroy()
@@ -1174,7 +1173,7 @@ GLOBAL_LIST_INIT(hallucination_list, list(
if(target.client)
target.client.images += fire_overlay
to_chat(target, "You're set on fire!")
- target.throw_alert("fire", /obj/screen/alert/fire, override = TRUE)
+ target.throw_alert("fire", /atom/movable/screen/alert/fire, override = TRUE)
sleep(20)
for(var/i in 1 to 3)
if(target.fire_stacks <= 0)
@@ -1196,7 +1195,7 @@ GLOBAL_LIST_INIT(hallucination_list, list(
target.clear_alert("temp", clear_override = TRUE)
else
target.clear_alert("temp", clear_override = TRUE)
- target.throw_alert("temp", /obj/screen/alert/hot, stage, override = TRUE)
+ target.throw_alert("temp", /atom/movable/screen/alert/hot, stage, override = TRUE)
/datum/hallucination/fire/proc/clear_fire()
if(!active)
@@ -1253,7 +1252,7 @@ GLOBAL_LIST_INIT(hallucination_list, list(
..()
if(!target.halbody)
var/list/possible_points = list()
- for(var/turf/open/floor/F in view(target,world.view))
+ for(var/turf/open/floor/F in view(world.view, target))
possible_points += F
if(possible_points.len)
var/turf/open/floor/husk_point = pick(possible_points)
@@ -1284,8 +1283,10 @@ GLOBAL_LIST_INIT(hallucination_list, list(
set waitfor = FALSE
..()
var/list/turf/startlocs = list()
- for(var/turf/open/T in view(world.view+1,target)-view(world.view,target))
+ for(var/turf/open/T in view(getexpandedview(world.view, 1, 1),target))
startlocs += T
+ for(var/turf/open/T in view(world.view,target)) // God this is bad
+ startlocs -= T
if(!startlocs.len)
qdel(src)
return
diff --git a/code/modules/food_and_drinks/drinks/drinks.dm b/code/modules/food_and_drinks/drinks/drinks.dm
index c8e837a1245da..e8c274a1992ec 100644
--- a/code/modules/food_and_drinks/drinks/drinks.dm
+++ b/code/modules/food_and_drinks/drinks/drinks.dm
@@ -257,7 +257,7 @@
/obj/item/reagent_containers/food/drinks/mug/cocoa
name = "Dutch hot cocoa"
desc = "Made in Space South America."
- list_reagents = list(/datum/reagent/consumable/hot_cocoa = 15, /datum/reagent/consumable/sugar = 5)
+ list_reagents = list(/datum/reagent/consumable/cocoa/hot_cocoa = 15, /datum/reagent/consumable/sugar = 5)
foodtype = SUGAR
resistance_flags = FREEZE_PROOF
custom_price = 42
@@ -279,6 +279,15 @@
list_reagents = list(/datum/reagent/consumable/ethanol/beer = 30)
foodtype = GRAIN | ALCOHOL
+/obj/item/reagent_containers/food/drinks/beer/almost_empty
+ var/amount
+ list_reagents = null
+
+/obj/item/reagent_containers/food/drinks/beer/almost_empty/Initialize()
+ . = ..()
+ amount = rand(1,4)
+ reagents.add_reagent(/datum/reagent/consumable/ethanol/beer, amount)
+
/obj/item/reagent_containers/food/drinks/syndicatebeer
name = "syndicate beer"
desc = "Consumed only by the finest syndicate agents. There is a round warning label stating 'Don't drink more than one in quick succession!'"
@@ -382,7 +391,21 @@
name = "small carton"
desc = "A small carton, intended for holding drinks."
+/obj/item/reagent_containers/food/drinks/honeycomb
+ name = "Honeycomb"
+ desc = "A honeycomb made by an apid. It seems to be made out of beeswax and fairly weak."
+ icon = 'icons/obj/hydroponics/harvest.dmi'
+ icon_state = "honeycomb"
+ list_reagents = list(/datum/reagent/consumable/honey = 25)
+/obj/item/reagent_containers/food/drinks/honeycomb/attack_self(mob/user)
+ if(!reagents.total_volume)
+ user.visible_message("[user] snaps the [src] into 2 pieces!",
+ "You snap [src] in half.")
+ new /obj/item/stack/sheet/mineral/wax(user.loc, 2)
+ qdel(src)
+ return
+ return ..()
//////////////////////////drinkingglass and shaker//
//Note by Darem: This code handles the mixing of drinks. New drinks go in three places: In Chemistry-Reagents.dm (for the drink
@@ -426,6 +449,7 @@
volume = 30
spillable = TRUE
+
//////////////////////////soda_cans//
//These are in their own group to be used as IED's in /obj/item/grenade/ghettobomb.dm
@@ -449,7 +473,7 @@
playsound(H,'sound/items/drink.ogg', 80, 1)
reagents.trans_to(H, src.reagents.total_volume, transfered_by = H) //a big sip
sleep(5)
- H.say(pick("Now, Outbomb Cuban Pete, THAT was a game.", "All these new fangled arcade games are too slow. I prefer the classics.", "They don't make 'em like Orion Trail anymore.", "You know what they say. Worst day of spess carp fishing is better than the best day at work.", "They don't make 'em like good old fashioned singularity engines anymore."))
+ H.say(pick("Now, Outbomb Cuban Pete, THAT was a game.", "All these new fangled arcade games are too slow. I prefer the classics.", "They don't make 'em like Orion Trail anymore.", "You know what they say. Worst day of spess carp fishing is better than the best day at work.", "They don't make 'em like good old-fashioned singularity engines anymore."))
if(H.age >= 30)
H.Stun(50)
sleep(50)
diff --git a/code/modules/food_and_drinks/drinks/drinks/bottle.dm b/code/modules/food_and_drinks/drinks/drinks/bottle.dm
index e485f693413d7..5194f1a5c1da8 100644
--- a/code/modules/food_and_drinks/drinks/drinks/bottle.dm
+++ b/code/modules/food_and_drinks/drinks/drinks/bottle.dm
@@ -21,10 +21,10 @@
if(isliving(hitby))
var/mob/living/L = hitby
smash(L)
- else
+ else
smash()
return TRUE
-
+
/obj/item/reagent_containers/food/drinks/bottle/smash(mob/living/target, mob/thrower, ranged = FALSE)
//Creates a shattering noise and replaces the bottle with a broken_bottle
@@ -336,6 +336,24 @@
icon_state = "fernetbottle"
list_reagents = list(/datum/reagent/consumable/ethanol/fernet = 100)
+/obj/item/reagent_containers/food/drinks/bottle/beer
+ name = "Space Beer"
+ desc = "Beer. In space. In a bigger bottle."
+ icon_state = "beer"
+ list_reagents = list(/datum/reagent/consumable/ethanol/beer = 100)
+
+/obj/item/reagent_containers/food/drinks/bottle/ale
+ name = "Magm-Ale"
+ desc = "A true dorf's drink of choice, now in a MANLY bottle."
+ icon_state = "alebottle"
+ list_reagents = list(/datum/reagent/consumable/ethanol/ale = 100)
+
+/obj/item/reagent_containers/food/drinks/bottle/homemaderum
+ name = "Cookie's Homemade Rum"
+ desc = "Brewed all the way back on Space Station 3. Might tell you where those basket-hats of fruit keep coming from."
+ icon_state = "moonshinebottle"
+ list_reagents = list(/datum/reagent/consumable/ethanol/rum = 95, /datum/reagent/drug/mushroomhallucinogen = 5)
+
//////////////////////////JUICES AND STUFF ///////////////////////
/obj/item/reagent_containers/food/drinks/bottle/orangejuice
diff --git a/code/modules/food_and_drinks/food/customizables.dm b/code/modules/food_and_drinks/food/customizables.dm
index 184b391203eac..759ba38d44889 100644
--- a/code/modules/food_and_drinks/food/customizables.dm
+++ b/code/modules/food_and_drinks/food/customizables.dm
@@ -53,7 +53,7 @@
mix_filling_color(S)
S.reagents.trans_to(src,min(S.reagents.total_volume, 15), transfered_by = user) //limit of 15, we don't want our custom food to be completely filled by just one ingredient with large reagent volume.
foodtype |= S.foodtype
- update_overlays(S)
+ update_customizable_overlays(S)
to_chat(user, "You add the [I.name] to the [name].")
update_name(S)
else
@@ -101,7 +101,7 @@
rgbcolor[4] = (customcolor[4]+ingcolor[4])/2
filling_color = rgb(rgbcolor[1], rgbcolor[2], rgbcolor[3], rgbcolor[4])
-/obj/item/reagent_containers/food/snacks/customizable/update_overlays(obj/item/reagent_containers/food/snacks/S)
+/obj/item/reagent_containers/food/snacks/customizable/update_customizable_overlays(obj/item/reagent_containers/food/snacks/S)
var/mutable_appearance/filling = mutable_appearance(icon, "[initial(icon_state)]_filling")
if(S.filling_color == "#FFFFFF")
filling.color = pick("#FF0000","#0000FF","#008000","#FFFF00")
@@ -137,7 +137,7 @@
/obj/item/reagent_containers/food/snacks/customizable/initialize_slice(obj/item/reagent_containers/food/snacks/slice, reagents_per_slice)
..()
slice.filling_color = filling_color
- slice.update_overlays(src)
+ slice.update_customizable_overlays(src)
/obj/item/reagent_containers/food/snacks/customizable/Destroy()
diff --git a/code/modules/food_and_drinks/food/snacks.dm b/code/modules/food_and_drinks/food/snacks.dm
index a9dfd126c6c6a..b2d4b2cc14797 100644
--- a/code/modules/food_and_drinks/food/snacks.dm
+++ b/code/modules/food_and_drinks/food/snacks.dm
@@ -270,7 +270,8 @@ All foods are distributed among various categories. Use common sense.
trash = null
return
-/obj/item/reagent_containers/food/snacks/proc/update_overlays(obj/item/reagent_containers/food/snacks/S)
+// We need to refactor this someday, like, in 3 years
+/obj/item/reagent_containers/food/snacks/proc/update_customizable_overlays(obj/item/reagent_containers/food/snacks/S)
cut_overlays()
var/mutable_appearance/filling = mutable_appearance(icon, "[initial(icon_state)]_filling")
if(S.filling_color == "#FFFFFF")
@@ -312,6 +313,14 @@ All foods are distributed among various categories. Use common sense.
return result
+/obj/item/reagent_containers/food/snacks/burn()
+ if(prob(25))
+ microwave_act()
+ else
+ var/turf/T = get_turf(src)
+ new /obj/item/reagent_containers/food/snacks/badrecipe(T)
+ qdel(src)
+
/obj/item/reagent_containers/food/snacks/Destroy()
if(contents)
for(var/atom/movable/something in contents)
@@ -323,13 +332,13 @@ All foods are distributed among various categories. Use common sense.
if(iscorgi(M))
var/mob/living/L = M
if(bitecount == 0 || prob(50))
- M.emote("me", 1, "nibbles away at \the [src]")
+ M.emote("me", 1, "nibbles away at \the [src].")
bitecount++
L.taste(reagents) // why should carbons get all the fun?
if(bitecount >= 5)
- var/sattisfaction_text = pick("burps from enjoyment", "yaps for more", "woofs twice", "looks at the area where \the [src] was")
- if(sattisfaction_text)
- M.emote("me", 1, "[sattisfaction_text]")
+ var/satisfaction_text = pick("burps from enjoyment.", "yaps for more.", "woofs twice.", "looks at the area where \the [src] was.")
+ if(satisfaction_text)
+ M.emote("me", 1, "[satisfaction_text]")
qdel(src)
/obj/item/reagent_containers/food/snacks/afterattack(obj/item/reagent_containers/M, mob/user, proximity)
diff --git a/code/modules/food_and_drinks/food/snacks_bread.dm b/code/modules/food_and_drinks/food/snacks_bread.dm
index 807e4f655160f..26966dea14e97 100644
--- a/code/modules/food_and_drinks/food/snacks_bread.dm
+++ b/code/modules/food_and_drinks/food/snacks_bread.dm
@@ -199,6 +199,14 @@
tastes = list("bread" = 1)
foodtype = GRAIN
+/obj/item/reagent_containers/food/snacks/baguette/mime
+ name = "French Baguette"
+ desc = "It would be a shame if it was consumed by someone unworthy..."
+ bonus_reagents = list(/datum/reagent/consumable/nutriment = 2, /datum/reagent/consumable/nutriment/vitamin = 2, /datum/reagent/consumable/nothing = 1)
+ list_reagents = list(/datum/reagent/consumable/nutriment = 6, /datum/reagent/consumable/nutriment/vitamin = 1, /datum/reagent/consumable/nothing = 2)
+ bitesize = -1
+
+
/obj/item/reagent_containers/food/snacks/garlicbread
name = "garlic bread"
desc = "Alas, it is limited."
diff --git a/code/modules/food_and_drinks/food/snacks_burgers.dm b/code/modules/food_and_drinks/food/snacks_burgers.dm
index f5e16c3c93e4d..5cde358161ed3 100644
--- a/code/modules/food_and_drinks/food/snacks_burgers.dm
+++ b/code/modules/food_and_drinks/food/snacks_burgers.dm
@@ -383,3 +383,11 @@
if(prob(33))
icon_state = "cheeseburgeralt"
+/obj/item/reagent_containers/food/snacks/burger/crazy
+ name = "crazy hamburger"
+ desc = "Crazy hamburger! It is horrible!"
+ icon_state = "crazyhamburger"
+ tastes = list("bread made in turkey" = 2, "horse meat" = 4, "cheese from sao paulo from brazil" = 3, "red hot chili peppers" = 3, "oil from iraq" = 2, "grass of death" = 3)
+ bonus_reagents = list(/datum/reagent/consumable/nutriment = 1)
+ foodtype = GRAIN | MEAT | DAIRY | TOXIC | GROSS | FRUIT
+ w_class = WEIGHT_CLASS_NORMAL // The crazy hamburger in the video was bigger than joker's hand therefore i think this weight class is adequate.
diff --git a/code/modules/food_and_drinks/food/snacks_meat.dm b/code/modules/food_and_drinks/food/snacks_meat.dm
index ffc26e5b25fbe..1383b491a834c 100644
--- a/code/modules/food_and_drinks/food/snacks_meat.dm
+++ b/code/modules/food_and_drinks/food/snacks_meat.dm
@@ -33,6 +33,12 @@
name = "imitation carp fillet"
desc = "Almost just like the real thing, kinda."
+/obj/item/reagent_containers/food/snacks/carpmeat/icantbeliveitsnotcarp
+ name = "fish fillet"
+ desc = "A fillet of unspecified fish meat."
+ list_reagents = list(/datum/reagent/consumable/nutriment = 4, /datum/reagent/consumable/nutriment/vitamin = 2) //No carpotoxin
+
+
/obj/item/reagent_containers/food/snacks/fishfingers
name = "fish fingers"
desc = "A finger of fish."
@@ -123,7 +129,7 @@
/obj/item/reagent_containers/food/snacks/sausage/Initialize()
. = ..()
- eatverb = pick("bite","chew","nibble","deep throat","gobble","chomp")
+ eatverb = pick("bite","chew","nibble","gobble","chomp")
/obj/item/reagent_containers/food/snacks/rawkhinkali
name = "raw khinkali"
@@ -160,7 +166,7 @@
if(GLOB.total_cube_monkeys >= CONFIG_GET(number/max_cube_monkeys))
visible_message("[src] refuses to expand!")
return
- var/mob/spammer = get_mob_by_key(fingerprintslast)
+ var/mob/spammer = get_mob_by_ckey(fingerprintslast)
var/mob/living/bananas = new spawned_mob(drop_location(), TRUE, spammer)
if(faction)
bananas.faction = faction
@@ -377,4 +383,4 @@
list_reagents = list(/datum/reagent/consumable/nutriment = 8, /datum/reagent/consumable/nutriment/vitamin = 2, /datum/reagent/consumable/bbqsauce = 5)
bonus_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/nutriment/vitamin = 1)
tastes = list("meat" = 3, "smokey sauce" = 1)
- foodtype = MEAT
\ No newline at end of file
+ foodtype = MEAT
diff --git a/code/modules/food_and_drinks/food/snacks_other.dm b/code/modules/food_and_drinks/food/snacks_other.dm
index 5afeda625f28c..1596c542db543 100644
--- a/code/modules/food_and_drinks/food/snacks_other.dm
+++ b/code/modules/food_and_drinks/food/snacks_other.dm
@@ -647,3 +647,13 @@
filling_color = "#ECA735"
tastes = list("fried corn" = 1)
foodtype = JUNKFOOD | FRIED
+
+/obj/item/reagent_containers/food/snacks/canned/beefbroth
+ name = "canned beef broth"
+ desc = "Why does this exist?"
+ icon_state = "beefcan"
+ trash = /obj/item/trash/can/food/beefbroth
+ list_reagents = list(/datum/reagent/consumable/beefbroth = 50)
+ filling_color = "#100800"
+ tastes = list("disgust" = 7, "tin" = 1)
+ foodtype = MEAT | GROSS | JUNKFOOD
\ No newline at end of file
diff --git a/code/modules/food_and_drinks/food/snacks_pastry.dm b/code/modules/food_and_drinks/food/snacks_pastry.dm
index 8e104dcdd52c6..2ec595df8abb5 100644
--- a/code/modules/food_and_drinks/food/snacks_pastry.dm
+++ b/code/modules/food_and_drinks/food/snacks_pastry.dm
@@ -111,7 +111,7 @@
name = "chocolate donut"
desc = "Goes great with a glass of warm milk."
icon_state = "donut_choc"
- bonus_reagents = list(/datum/reagent/consumable/hot_cocoa = 3, /datum/reagent/consumable/sprinkles = 1) //the cocoa reagent is just bitter.
+ bonus_reagents = list(/datum/reagent/consumable/cocoa/hot_cocoa = 3, /datum/reagent/consumable/sprinkles = 1) //the cocoa reagent is just bitter.
tastes = list("donut" = 4, "bitterness" = 1)
decorated_icon = "donut_choc_sprinkles"
filling_color = "#4F230D"
@@ -201,7 +201,7 @@
name = "chocolate jelly donut"
desc = "Goes great with a glass of warm milk."
icon_state = "jelly_choc"
- bonus_reagents = list(/datum/reagent/consumable/hot_cocoa = 3, /datum/reagent/consumable/sprinkles = 1, /datum/reagent/consumable/nutriment/vitamin = 1) //the cocoa reagent is just bitter.
+ bonus_reagents = list(/datum/reagent/consumable/cocoa/hot_cocoa = 3, /datum/reagent/consumable/sprinkles = 1, /datum/reagent/consumable/nutriment/vitamin = 1) //the cocoa reagent is just bitter.
tastes = list("jelly" = 1, "donut" = 4, "bitterness" = 1)
decorated_icon = "jelly_choc_sprinkles"
filling_color = "#4F230D"
@@ -282,7 +282,7 @@
name = "chocolate jelly donut"
desc = "Goes great with a glass of warm milk."
icon_state = "jelly_choc"
- bonus_reagents = list(/datum/reagent/consumable/hot_cocoa = 3, /datum/reagent/consumable/sprinkles = 1, /datum/reagent/consumable/nutriment/vitamin = 1) //the cocoa reagent is just bitter.
+ bonus_reagents = list(/datum/reagent/consumable/cocoa/hot_cocoa = 3, /datum/reagent/consumable/sprinkles = 1, /datum/reagent/consumable/nutriment/vitamin = 1) //the cocoa reagent is just bitter.
tastes = list("jelly" = 1, "donut" = 4, "bitterness" = 1)
decorated_icon = "jelly_choc_sprinkles"
filling_color = "#4F230D"
@@ -601,7 +601,7 @@
/obj/item/reagent_containers/food/snacks/hotdog
name = "hotdog"
- desc = "Fresh footlong ready to go down on."
+ desc = "Hot and steamy hotdog weenie."
icon_state = "hotdog"
bitesize = 3
bonus_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/nutriment/vitamin = 3)
@@ -782,22 +782,22 @@
to_chat(user, "You add the [I] to the [name].")
P.name = initial(P.name)
contents += P
- update_overlays(P)
+ update_customizable_overlays(P)
if (P.contents.len)
for(var/V in P.contents)
P = V
P.name = initial(P.name)
contents += P
- update_overlays(P)
+ update_customizable_overlays(P)
P = I
- clearlist(P.contents)
+ P.contents.Cut()
return
else if(contents.len)
var/obj/O = contents[contents.len]
return O.attackby(I, user, params)
..()
-/obj/item/reagent_containers/food/snacks/pancakes/update_overlays(obj/item/reagent_containers/food/snacks/P)
+/obj/item/reagent_containers/food/snacks/pancakes/update_customizable_overlays(obj/item/reagent_containers/food/snacks/P)
var/mutable_appearance/pancake = mutable_appearance(icon, "[P.item_state]_[rand(1,3)]")
pancake.pixel_x = rand(-1,1)
pancake.pixel_y = 3 * contents.len - 1
diff --git a/code/modules/food_and_drinks/food/snacks_pie.dm b/code/modules/food_and_drinks/food/snacks_pie.dm
index c5e0b7651dcf2..76f2db7e638d8 100644
--- a/code/modules/food_and_drinks/food/snacks_pie.dm
+++ b/code/modules/food_and_drinks/food/snacks_pie.dm
@@ -52,7 +52,7 @@
H.Paralyze(20) //splat!
H.adjust_blurriness(1)
H.visible_message("[H] is creamed by [src]!", "You've been creamed by [src]!")
- playsound(H, "desceration", 50, TRUE)
+ playsound(H, "desecration", 50, TRUE)
if(!H.creamed) // one layer at a time
H.add_overlay(creamoverlay)
H.creamed = TRUE
diff --git a/code/modules/food_and_drinks/food/snacks_pizza.dm b/code/modules/food_and_drinks/food/snacks_pizza.dm
index cb2ea093ca3d3..91cb43b0006e3 100644
--- a/code/modules/food_and_drinks/food/snacks_pizza.dm
+++ b/code/modules/food_and_drinks/food/snacks_pizza.dm
@@ -177,7 +177,7 @@
l_arm.dismember()
else
r_arm.dismember()
- playsound(user,pick('sound/misc/desceration-01.ogg','sound/misc/desceration-02.ogg','sound/misc/desceration-01.ogg') ,50, TRUE, -1)
+ playsound(user,pick('sound/misc/desecration-01.ogg','sound/misc/desecration-02.ogg','sound/misc/desecration-01.ogg') ,50, TRUE, -1)
/obj/item/reagent_containers/food/snacks/proc/i_kill_you(obj/item/I, mob/user)
if(istype(I, /obj/item/reagent_containers/food/snacks/pineappleslice))
diff --git a/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm b/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm
index 727b17d8e783d..28b69a5a85266 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm
@@ -20,6 +20,9 @@ God bless America.
-
*/
+#define DEEPFRYER_COOKTIME 60
+#define DEEPFRYER_BURNTIME 120
+
/obj/machinery/deepfryer
name = "deep fryer"
desc = "Deep fried everything."
@@ -31,7 +34,7 @@ God bless America.
layer = BELOW_OBJ_LAYER
var/obj/item/reagent_containers/food/snacks/deepfryholder/frying //What's being fried RIGHT NOW?
var/cook_time = 0
- var/oil_use = 0.05 //How much cooking oil is used per tick
+ var/oil_use = 0.025 //How much cooking oil is used per second
var/fry_speed = 1 //How quickly we fry food
var/frying_fried //If the object has been fried; used for messages
var/frying_burnt //If the object has been burnt
@@ -42,6 +45,7 @@ God bless America.
/obj/item/wirecutters,
/obj/item/multitool,
/obj/item/weldingtool,
+ /obj/item/powertool,
/obj/item/reagent_containers/glass,
/obj/item/reagent_containers/syringe,
/obj/item/reagent_containers/food/condiment,
@@ -60,11 +64,15 @@ God bless America.
RefreshParts()
fry_loop = new(list(src), FALSE)
+/obj/machinery/deepfryer/Destroy()
+ QDEL_NULL(fry_loop)
+ return ..()
+
/obj/machinery/deepfryer/RefreshParts()
var/oil_efficiency
for(var/obj/item/stock_parts/micro_laser/M in component_parts)
oil_efficiency += M.rating
- oil_use = initial(oil_use) - (oil_efficiency * 0.0095)
+ oil_use = initial(oil_use) - (oil_efficiency * 0.00475)
fry_speed = oil_efficiency
/obj/machinery/deepfryer/examine(mob/user)
@@ -72,7 +80,7 @@ God bless America.
if(frying)
. += "You can make out \a [frying] in the oil."
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Frying at [fry_speed*100]% speed. Using [oil_use*10] units of oil per second."
+ . += "The status display reads: Frying at [fry_speed*100]% speed. Using [oil_use] units of oil per second."
/obj/machinery/deepfryer/attackby(obj/item/I, mob/user)
if(istype(I, /obj/item/reagent_containers/pill))
@@ -110,20 +118,20 @@ God bless America.
icon_state = "fryer_on"
fry_loop.start()
-/obj/machinery/deepfryer/process()
+/obj/machinery/deepfryer/process(delta_time)
..()
var/datum/reagent/consumable/cooking_oil/C = reagents.has_reagent(/datum/reagent/consumable/cooking_oil)
if(!C)
return
reagents.chem_temp = C.fry_temperature
if(frying)
- reagents.trans_to(frying, oil_use, multiplier = fry_speed * 3) //Fried foods gain more of the reagent thanks to space magic
- cook_time += fry_speed
- if(cook_time >= 30 && !frying_fried)
+ reagents.trans_to(frying, oil_use * delta_time, multiplier = fry_speed * 3) //Fried foods gain more of the reagent thanks to space magic
+ cook_time += fry_speed * delta_time
+ if(cook_time >= DEEPFRYER_COOKTIME && !frying_fried)
frying_fried = TRUE //frying... frying... fried
playsound(src.loc, 'sound/machines/ding.ogg', 50, 1)
audible_message("[src] dings!")
- else if (cook_time >= 60 && !frying_burnt)
+ else if (cook_time >= DEEPFRYER_BURNTIME && !frying_burnt)
frying_burnt = TRUE
visible_message("[src] emits an acrid smell!")
@@ -158,3 +166,6 @@ God bless America.
C.Paralyze(60)
user.changeNext_move(CLICK_CD_MELEE)
return ..()
+
+#undef DEEPFRYER_COOKTIME
+#undef DEEPFRYER_BURNTIME
diff --git a/code/modules/food_and_drinks/kitchen_machinery/food_cart.dm b/code/modules/food_and_drinks/kitchen_machinery/food_cart.dm
index 893d40819bb9f..22a76ff6962c3 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/food_cart.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/food_cart.dm
@@ -27,35 +27,6 @@
QDEL_NULL(mixer)
return ..()
-/obj/machinery/food_cart/ui_interact(mob/user)
- . = ..()
- var/dat
- dat += " STORED INGREDIENTS AND DRINKS
"
- dat += "Remaining glasses: [glasses] "
- dat += "Portion: [portion] "
- for(var/datum/reagent/R in reagents.reagent_list)
- dat += "[R.name]: [R.volume] "
- dat += "Purge"
- if (glasses > 0)
- dat += "Pour in a glass"
- dat += "Add to the mixer "
- dat += "
MIXER CONTENTS
"
- for(var/datum/reagent/R in mixer.reagents.reagent_list)
- dat += "[R.name]: [R.volume] "
- dat += "Transfer back"
- if (glasses > 0)
- dat += "Pour in a glass"
- dat += " "
- dat += "
STORED FOOD
"
- for(var/V in stored_food)
- if(stored_food[V] > 0)
- dat += "[V]: [stored_food[V]]Dispense "
- dat += "
"
+ dat += "Remaining glasses: [glasses] "
+ dat += "Portion: [portion] "
+ for(var/i in 1 to LAZYLEN(reagents.reagent_list))
+ var/datum/reagent/R = reagents.reagent_list[i]
+ dat += "[R.name]: [R.volume] "
+ dat += "Purge"
+ if (glasses > 0)
+ dat += "Pour in a glass"
+ dat += "Add to the mixer "
+ dat += "
MIXER CONTENTS
"
+ for(var/i in 1 to LAZYLEN(mixer.reagents.reagent_list))
+ var/datum/reagent/R = mixer.reagents.reagent_list[i]
+ dat += "[R.name]: [R.volume] "
+ dat += "Transfer back"
+ if (glasses > 0)
+ dat += "Pour in a glass"
+ dat += " "
+ dat += "
STORED FOOD
"
+ for(var/V in stored_food)
+ if(stored_food[V] > 0)
+ dat += "[V]: [stored_food[V]]Dispense "
+ dat += "
RefreshClose"
+
+ var/datum/browser/popup = new(user, "foodcart","Food Cart", 500, 350, src)
+ popup.set_content(dat)
+ popup.open()
+
/obj/machinery/food_cart/Topic(href, href_list)
if(..())
return
if(href_list["disposeI"])
- reagents.del_reagent(href_list["disposeI"])
+ reagents.del_reagent(reagents.reagent_list[text2num(href_list["disposeI"])]?.type)
if(href_list["dispense"])
if(stored_food[href_list["dispense"]]-- <= 0)
@@ -130,16 +132,16 @@
else
var/obj/item/reagent_containers/food/drinks/drinkingglass/DG = new(loc)
if(href_list["pour"])
- reagents.trans_id_to(DG, href_list["pour"], portion)
+ reagents.trans_id_to(DG, reagents.reagent_list[text2num(href_list["pour"])]?.type, portion)
if(href_list["m_pour"])
- mixer.reagents.trans_id_to(DG, href_list["m_pour"], portion)
+ mixer.reagents.trans_id_to(DG, mixer.reagents.reagent_list[text2num(href_list["m_pour"])]?.type, portion)
if(href_list["mix"])
- if(reagents.trans_id_to(mixer, href_list["mix"], portion) == 0)
+ if(!reagents.trans_id_to(mixer, reagents.reagent_list[text2num(href_list["mix"])]?.type, portion))
to_chat(usr, "[mixer] is full!")
if(href_list["transfer"])
- if(mixer.reagents.trans_id_to(src, href_list["transfer"], portion) == 0)
+ if(!mixer.reagents.trans_id_to(src, mixer.reagents.reagent_list[text2num(href_list["transfer"])]?.type, portion))
to_chat(usr, "[src] is full!")
updateDialog()
@@ -154,6 +156,25 @@
new /obj/item/stack/sheet/iron(loc, 4)
qdel(src)
+/obj/machinery/food_cart/coffee
+ name = "coffee cart"
+ desc = "Ah! The bitter drink of the Gods."
+ icon_state = "icecream_vat"
+ glasses = 10
+ portion = 20
+
+/obj/machinery/food_cart/coffee/Initialize()
+ ..()
+ var/A = rand(0,3)
+ var/B = rand(0,3)
+ var/C = rand(0,3)
+ var/D = rand(0,1)
+ reagents.add_reagent(/datum/reagent/consumable/cafe_latte, A*20)
+ reagents.add_reagent(/datum/reagent/consumable/icecoffee, B*20)
+ reagents.add_reagent(/datum/reagent/consumable/soy_latte, C*20)
+ reagents.add_reagent(/datum/reagent/consumable/pumpkin_latte, D*20)
+ reagents.add_reagent(/datum/reagent/consumable/coffee, (10-A-B-C-D)*20)
+
#undef STORAGE_CAPACITY
#undef LIQUID_CAPACIY
#undef MIXER_CAPACITY
diff --git a/code/modules/food_and_drinks/kitchen_machinery/gibber.dm b/code/modules/food_and_drinks/kitchen_machinery/gibber.dm
index 8756ab4fbfcf5..067169c2cbe8e 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/gibber.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/gibber.dm
@@ -33,10 +33,10 @@
/obj/machinery/gibber/examine(mob/user)
. = ..()
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Outputting [meat_produced] meat slab(s) after [gibtime*0.1] seconds of processing."
+ . += "The status display reads: Outputting [meat_produced] meat slab(s) after [gibtime*0.1] seconds of processing."
for(var/obj/item/stock_parts/manipulator/M in component_parts)
if(M.rating >= 2)
- . += "Gibber has been upgraded to process inorganic materials."
+ . += "Gibber has been upgraded to process inorganic materials."
/obj/machinery/gibber/update_icon()
cut_overlays()
diff --git a/code/modules/food_and_drinks/kitchen_machinery/grill.dm b/code/modules/food_and_drinks/kitchen_machinery/grill.dm
index e2ec15c1854c4..7d7ab765dc02c 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/grill.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/grill.dm
@@ -1,5 +1,8 @@
//I JUST WANNA GRILL FOR GOD'S SAKE
+#define GRILL_FUELUSAGE_IDLE 0.5
+#define GRILL_FUELUSAGE_ACTIVE 5
+
/obj/machinery/grill
name = "grill"
desc = "Just like the old days."
@@ -20,7 +23,7 @@
/obj/machinery/grill/update_icon()
if(grilled_item)
icon_state = "grill"
- else if(grill_fuel)
+ else if(grill_fuel > 0)
icon_state = "grill_on"
else
icon_state = "grill_open"
@@ -62,21 +65,21 @@
return
..()
-/obj/machinery/grill/process()
+/obj/machinery/grill/process(delta_time)
..()
update_icon()
- if(!grill_fuel)
+ if(grill_fuel <= 0)
return
else
- grill_fuel -= 1
- if(prob(1))
+ grill_fuel -= GRILL_FUELUSAGE_IDLE * delta_time
+ if(DT_PROB(0.5, delta_time))
var/datum/effect_system/smoke_spread/bad/smoke = new
smoke.set_up(1, loc)
smoke.start()
if(grilled_item)
- grill_time += 1
- grilled_item.reagents.add_reagent(/datum/reagent/consumable/char, 1)
- grill_fuel -= 10
+ grill_time += delta_time
+ grilled_item.reagents.add_reagent(/datum/reagent/consumable/char, 0.5 * delta_time)
+ grill_fuel -= GRILL_FUELUSAGE_ACTIVE * delta_time
grilled_item.AddComponent(/datum/component/sizzle)
/obj/machinery/grill/Exited(atom/movable/AM)
@@ -86,6 +89,7 @@
..()
/obj/machinery/grill/Destroy()
+ QDEL_NULL(grill_loop)
grilled_item = null
. = ..()
@@ -118,19 +122,19 @@
return ..()
/obj/machinery/grill/proc/finish_grill()
- switch(grill_time) //no 0-9 to prevent spam
- if(10 to 15)
+ switch(grill_time) //no 0-20 to prevent spam
+ if(20 to 30)
grilled_item.name = "lightly-grilled [grilled_item.name]"
grilled_item.desc = "[grilled_item.desc] It's been lightly grilled."
- if(16 to 39)
+ if(30 to 80)
grilled_item.name = "grilled [grilled_item.name]"
grilled_item.desc = "[grilled_item.desc] It's been grilled."
grilled_item.foodtype |= FRIED
- if(40 to 50)
+ if(80 to 100)
grilled_item.name = "heavily grilled [grilled_item.name]"
grilled_item.desc = "[grilled_item.desc] It's been heavily grilled."
grilled_item.foodtype |= FRIED
- if(51 to INFINITY) //grill marks reach max alpha
+ if(100 to INFINITY) //grill marks reach max alpha
grilled_item.name = "Powerfully Grilled [grilled_item.name]"
grilled_item.desc = "A [grilled_item.name]. Reminds you of your wife, wait, no, it's prettier!"
grilled_item.foodtype |= FRIED
@@ -139,3 +143,6 @@
/obj/machinery/grill/unwrenched
anchored = FALSE
+
+#undef GRILL_FUELUSAGE_IDLE
+#undef GRILL_FUELUSAGE_ACTIVE
diff --git a/code/modules/food_and_drinks/kitchen_machinery/microwave.dm b/code/modules/food_and_drinks/kitchen_machinery/microwave.dm
index 4125b21494c06..4729824db5d13 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/microwave.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/microwave.dm
@@ -40,6 +40,7 @@
/obj/machinery/microwave/Destroy()
eject()
+ QDEL_NULL(soundloop)
if(wires)
QDEL_NULL(wires)
. = ..()
@@ -83,9 +84,9 @@
. += "\The [src] is empty."
if(!(stat & (NOPOWER|BROKEN)))
- . += {"The status display reads:\n
- - Capacity: [max_n_of_items] items.\n
- - Cook time reduced by [(efficiency - 1) * 25]%."}
+ . += "The status display reads:\n"+\
+ "- Capacity: [max_n_of_items] items.\n"+\
+ "- Cook time reduced by [(efficiency - 1) * 25]%."
/obj/machinery/microwave/update_icon()
if(broken)
@@ -146,10 +147,13 @@
to_chat(user, "You need more space cleaner!")
return TRUE
- if(istype(O, /obj/item/soap))
- var/obj/item/soap/P = O
+ if(istype(O, /obj/item/soap) || istype(O, /obj/item/reagent_containers/glass/rag))
+ var/cleanspeed = 50
+ if(istype(O, /obj/item/soap))
+ var/obj/item/soap/used_soap = O
+ cleanspeed = used_soap.cleanspeed
user.visible_message("[user] starts to clean \the [src].", "You start to clean \the [src]...")
- if(do_after(user, P.cleanspeed, target = src))
+ if(do_after(user, cleanspeed, target = src))
user.visible_message("[user] has cleaned \the [src].", "You clean \the [src].")
dirty = 0
update_icon()
@@ -252,8 +256,8 @@
break
start()
-/obj/machinery/microwave/proc/turn_on()
- visible_message("\The [src] turns on.", "You hear a microwave humming.")
+/obj/machinery/microwave/proc/wzhzhzh()
+ visible_message("\The [src] turns on.", null, "You hear a microwave humming.")
operating = TRUE
set_light(1.5)
@@ -271,15 +275,15 @@
#define MICROWAVE_PRE 2
/obj/machinery/microwave/proc/start()
- turn_on()
+ wzhzhzh()
loop(MICROWAVE_NORMAL, 10)
/obj/machinery/microwave/proc/start_can_fail()
- turn_on()
+ wzhzhzh()
loop(MICROWAVE_PRE, 4)
/obj/machinery/microwave/proc/muck()
- turn_on()
+ wzhzhzh()
playsound(src.loc, 'sound/effects/splat.ogg', 50, 1)
dirty_anim_playing = TRUE
update_icon()
diff --git a/code/modules/food_and_drinks/kitchen_machinery/monkeyrecycler.dm b/code/modules/food_and_drinks/kitchen_machinery/monkeyrecycler.dm
index 5722601ad5522..bf8ae8047e56b 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/monkeyrecycler.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/monkeyrecycler.dm
@@ -37,7 +37,7 @@ GLOBAL_LIST_EMPTY(monkey_recyclers)
/obj/machinery/monkey_recycler/examine(mob/user)
. = ..()
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Producing [cube_production] cubes for every monkey inserted."
+ . += "The status display reads: Producing [cube_production] cubes for every monkey inserted."
/obj/machinery/monkey_recycler/attackby(obj/item/O, mob/user, params)
if(default_deconstruction_screwdriver(user, "grinder_open", "grinder", O))
diff --git a/code/modules/food_and_drinks/kitchen_machinery/processor.dm b/code/modules/food_and_drinks/kitchen_machinery/processor.dm
index e31b9e6eddc4c..479416615af80 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/processor.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/processor.dm
@@ -14,6 +14,7 @@
var/processing = FALSE
var/rating_speed = 1
var/rating_amount = 1
+ processing_flags = NONE
/obj/machinery/processor/RefreshParts()
for(var/obj/item/stock_parts/matter_bin/B in component_parts)
@@ -24,7 +25,7 @@
/obj/machinery/processor/examine(mob/user)
. = ..()
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Outputting [rating_amount] item(s) at [rating_speed*100]% speed."
+ . += "The status display reads: Outputting [rating_amount] item(s) at [rating_speed*100]% speed."
/obj/machinery/processor/proc/process_food(datum/food_processor_process/recipe, atom/movable/what)
if (recipe.output && loc && !QDELETED(src))
@@ -153,11 +154,13 @@
/obj/machinery/processor/slime
name = "slime processor"
desc = "An industrial grinder with a sticker saying appropriated for science department. Keep hands clear of intake area while operating."
+ var/sbacklogged = FALSE
/obj/machinery/processor/slime/Initialize()
. = ..()
var/obj/item/circuitboard/machine/B = new /obj/item/circuitboard/machine/processor/slime(null)
B.apply_default_parts(src)
+ proximity_monitor = new(src, 1)
/obj/machinery/processor/slime/adjust_item_drop_location(atom/movable/AM)
var/static/list/slimecores = subtypesof(/obj/item/slime_extract)
@@ -173,36 +176,31 @@
AM.pixel_y = -8 + (round(ii/3)*8)
return i
-/obj/machinery/processor/slime/process()
- if(processing)
- return
- var/mob/living/simple_animal/slime/picked_slime
- for(var/mob/living/simple_animal/slime/slime in range(1,src))
- if(slime.loc == src)
- continue
- if(istype(slime, /mob/living/simple_animal/slime))
- if(slime.stat)
- picked_slime = slime
- break
- if(!picked_slime)
- return
- var/datum/food_processor_process/P = select_recipe(picked_slime)
- if (!P)
- return
-
- visible_message("[picked_slime] is sucked into [src].")
- picked_slime.forceMove(src)
+/obj/machinery/processor/slime/interact(mob/user)
+ . = ..()
+ if(sbacklogged)
+ for(var/mob/living/simple_animal/slime/AM in ohearers(1,src)) //fallback in case slimes got placed while processor was active triggers only after processing!!!!
+ if(AM.stat == DEAD)
+ visible_message("[AM] is sucked into [src].")
+ AM.forceMove(src)
+ sbacklogged = FALSE
+
+/obj/machinery/processor/slime/HasProximity(mob/AM)
+ if(!sbacklogged && istype(AM, /mob/living/simple_animal/slime) && AM.stat == DEAD)
+ if(processing)
+ sbacklogged = TRUE
+ else
+ visible_message("[AM] is sucked into [src].")
+ AM.forceMove(src)
/obj/machinery/processor/slime/process_food(datum/food_processor_process/recipe, atom/movable/what)
var/mob/living/simple_animal/slime/S = what
if (istype(S))
var/C = S.cores
- if(S.stat != DEAD)
- S.forceMove(drop_location())
- S.visible_message("[C] crawls free of the processor!")
- return
for(var/i in 1 to (C+rating_amount-1))
- var/atom/movable/item = new S.coretype(drop_location())
+ var/obj/item/slime_extract/item = new S.coretype(drop_location())
+ if(S.transformeffects & SLIME_EFFECT_GOLD)
+ item.sparkly = TRUE
adjust_item_drop_location(item)
SSblackbox.record_feedback("tally", "slime_core_harvested", 1, S.colour)
..()
diff --git a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
index c6608e416b60b..ad8f98caea03e 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
@@ -39,7 +39,7 @@
/obj/machinery/smartfridge/examine(mob/user)
. = ..()
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: This unit can hold a maximum of [max_n_of_items] items."
+ . += "The status display reads: This unit can hold a maximum of [max_n_of_items] items."
/obj/machinery/smartfridge/power_change()
..()
@@ -73,7 +73,7 @@
cut_overlays()
if(panel_open)
add_overlay("[initial(icon_state)]-panel")
- updateUsrDialog()
+ ui_update()
return
if(default_pry_open(O))
@@ -84,7 +84,6 @@
return
if(default_deconstruction_crowbar(O))
- updateUsrDialog()
return
if(!stat)
@@ -96,7 +95,6 @@
if(accept_check(O))
load(O)
user.visible_message("[user] has added \the [O] to \the [src].", "You add \the [O] to \the [src].")
- updateUsrDialog()
if (visible_contents)
update_icon()
return TRUE
@@ -110,7 +108,6 @@
if(accept_check(G))
load(G)
loaded++
- updateUsrDialog()
if(loaded)
if(contents.len >= max_n_of_items)
@@ -128,21 +125,40 @@
to_chat(user, "There is nothing in [O] to put in [src]!")
return FALSE
+ if(istype(O, /obj/item/organ_storage))
+ var/obj/item/organ_storage/S = O
+ if(S.contents.len)
+ var/obj/item/I = S.contents[1]
+ if(accept_check(I))
+ load(I)
+ user.visible_message("[user] inserts \the [I] into \the [src].", \
+ "You insert \the [I] into \the [src].")
+ O.cut_overlays()
+ O.icon_state = "evidenceobj"
+ O.desc = "A container for holding body parts."
+ if(visible_contents)
+ update_icon()
+ return TRUE
+ else
+ to_chat(user, "[src] does not accept [I]!")
+ return FALSE
+ else
+ to_chat(user, "There is nothing in [O] to put into [src]!")
+ return FALSE
+
if(user.a_intent != INTENT_HARM)
to_chat(user, "\The [src] smartly refuses [O].")
- updateUsrDialog()
return FALSE
else
return ..()
-/obj/machinery/smartfridge/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
+/obj/machinery/smartfridge/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
if(!stat)
- if (istype(AM, /obj/item))
+ if (istype(AM, /obj/item))
var/obj/item/O = AM
if(contents.len < max_n_of_items && accept_check(O))
load(O)
- updateUsrDialog()
if (visible_contents)
update_icon()
return TRUE
@@ -161,13 +177,16 @@
to_chat(usr, "\the [O] is stuck to your hand, you cannot put it in \the [src]!")
return FALSE
else
- return TRUE
+ . = TRUE
else
if(SEND_SIGNAL(O.loc, COMSIG_CONTAINS_STORAGE))
- return SEND_SIGNAL(O.loc, COMSIG_TRY_STORAGE_TAKE, O, src)
+ . = SEND_SIGNAL(O.loc, COMSIG_TRY_STORAGE_TAKE, O, src)
else
O.forceMove(src)
- return TRUE
+ . = TRUE
+
+ if(.)
+ ui_update()
///Really simple proc, just moves the object "O" into the hands of mob "M" if able, done so I could modify the proc a little for the organ fridge
/obj/machinery/smartfridge/proc/dispense(obj/item/O, var/mob/M)
@@ -226,28 +245,21 @@
else
desired = input("How many items?", "How many items would you like to take out?", 1) as null|num
- if(QDELETED(src) || QDELETED(usr) || !usr.Adjacent(src)) // Sanity checkin' in case stupid stuff happens while we wait for input()
- return FALSE
+ if(!isnum_safe(desired) || desired <= 0)
+ return
- if(desired == 1 && Adjacent(usr) && !issilicon(usr))
- for(var/obj/item/O in src)
- if(O.name == params["name"])
- dispense(O, usr)
- break
- if (visible_contents)
- update_icon()
- return TRUE
+ if(QDELETED(src) || QDELETED(usr) || !usr.Adjacent(src)) // Sanity checkin' in case stupid stuff happens while we wait for input()
+ return
for(var/obj/item/O in src)
- if(desired <= 0)
- break
if(O.name == params["name"])
dispense(O, usr)
desired--
- if (visible_contents)
+ . = TRUE
+ if(desired <= 0)
+ break
+ if (visible_contents && .)
update_icon()
- return TRUE
- return FALSE
// ----------------------------
@@ -341,7 +353,11 @@
S.forceMove(drop_location())
else
var/dried = S.dried_type
- new dried(drop_location())
+ dried = new dried(drop_location())
+ if(istype(dried, /obj/item/reagent_containers)) // If the product is a reagent container, transfer reagents
+ var/obj/item/reagent_containers/R = dried
+ R.reagents.clear_reagents()
+ S.reagents.copy_to(R)
qdel(S)
return TRUE
for(var/obj/item/stack/sheet/wetleather/WL in src)
@@ -422,14 +438,14 @@
/obj/machinery/smartfridge/organ/RefreshParts()
for(var/obj/item/stock_parts/matter_bin/B in component_parts)
max_n_of_items = 20 * B.rating
- repair_rate = max(0, STANDARD_ORGAN_HEALING * (B.rating - 1))
+ repair_rate = max(0, STANDARD_ORGAN_HEALING * (B.rating - 1) * 0.5)
-/obj/machinery/smartfridge/organ/process()
+/obj/machinery/smartfridge/organ/process(delta_time)
for(var/organ in contents)
var/obj/item/organ/O = organ
if(!istype(O))
return
- O.applyOrganDamage(-repair_rate)
+ O.applyOrganDamage(-repair_rate * delta_time)
/obj/machinery/smartfridge/organ/Exited(obj/item/organ/AM, atom/newLoc)
. = ..()
diff --git a/code/modules/food_and_drinks/pizzabox.dm b/code/modules/food_and_drinks/pizzabox.dm
index ceb58da11375b..0ee7479a0bba5 100644
--- a/code/modules/food_and_drinks/pizzabox.dm
+++ b/code/modules/food_and_drinks/pizzabox.dm
@@ -25,11 +25,11 @@
var/obj/item/bombcore/miniature/pizza/bomb
var/bomb_active = FALSE // If the bomb is counting down.
var/bomb_defused = TRUE // If the bomb is inert.
- var/bomb_timer = 1 // How long before blowing the bomb.
- /// Min bomb timer allowed
+ var/bomb_timer = 1 // How long before blowing the bomb, in seconds.
+ /// Min bomb timer allowed in seconds
var/bomb_timer_min = 1
- /// Max bomb timer allower
- var/bomb_timer_max = 10
+ /// Max bomb timer allower in seconds
+ var/bomb_timer_max = 20
/obj/item/pizzabox/Initialize()
. = ..()
@@ -105,6 +105,7 @@
if(open && !bomb_defused)
audible_message("[icon2html(src, hearers(src))] *beep*")
bomb_active = TRUE
+ wires.ui_update()
START_PROCESSING(SSobj, src)
update_icon()
@@ -131,10 +132,11 @@
if (isnull(bomb_timer))
return
- bomb_timer = clamp(CEILING(bomb_timer / 2, 1), bomb_timer_min, bomb_timer_max)
+ bomb_timer = clamp(CEILING(bomb_timer, 1), bomb_timer_min, bomb_timer_max)
bomb_defused = FALSE
+ wires.ui_update()
- log_bomber(user, "has trapped a", src, "with [bomb] set to [bomb_timer * 2] seconds")
+ log_bomber(user, "has trapped a", src, "with [bomb] set to [bomb_timer] seconds")
bomb.adminlog = "The [bomb.name] in [src.name] that [key_name(user)] activated has detonated!"
to_chat(user, "You trap [src] with [bomb].")
@@ -213,10 +215,10 @@
to_chat(user, "That's not a pizza!")
..()
-/obj/item/pizzabox/process()
+/obj/item/pizzabox/process(delta_time)
if(bomb_active && !bomb_defused && (bomb_timer > 0))
- playsound(loc, 'sound/items/timer.ogg', 50, 0)
- bomb_timer--
+ playsound(loc, 'sound/items/timer.ogg', 50, FALSE)
+ bomb_timer -= delta_time
if(bomb_active && !bomb_defused && (bomb_timer <= 0))
if(bomb in src)
bomb.detonate()
@@ -226,6 +228,7 @@
if(bomb_defused && (bomb in src))
bomb.defuse()
bomb_active = FALSE
+ wires.ui_update()
unprocess()
return
diff --git a/code/modules/food_and_drinks/recipes/drinks_recipes.dm b/code/modules/food_and_drinks/recipes/drinks_recipes.dm
index 09d3225e4e708..cc36bad640428 100644
--- a/code/modules/food_and_drinks/recipes/drinks_recipes.dm
+++ b/code/modules/food_and_drinks/recipes/drinks_recipes.dm
@@ -606,7 +606,7 @@
id = /datum/reagent/consumable/ethanol/quintuple_sec
results = list(/datum/reagent/consumable/ethanol/quintuple_sec = 15)
required_reagents = list(/datum/reagent/consumable/ethanol/quadruple_sec = 5, /datum/reagent/consumable/clownstears = 5, /datum/reagent/consumable/ethanol/syndicatebomb = 5)
- mix_message = "Judgement is upon you."
+ mix_message = "Judgment is upon you."
mix_sound = 'sound/items/airhorn2.ogg'
/datum/chemical_reaction/bastion_bourbon
@@ -656,7 +656,7 @@
name = "Peppermint Patty"
id = /datum/reagent/consumable/ethanol/peppermint_patty
results = list(/datum/reagent/consumable/ethanol/peppermint_patty = 10)
- required_reagents = list(/datum/reagent/consumable/hot_cocoa = 6, /datum/reagent/consumable/ethanol/creme_de_cacao = 1, /datum/reagent/consumable/ethanol/creme_de_menthe = 1, /datum/reagent/consumable/ethanol/vodka = 1, /datum/reagent/consumable/menthol = 1)
+ required_reagents = list(/datum/reagent/consumable/cocoa/hot_cocoa = 6, /datum/reagent/consumable/ethanol/creme_de_cacao = 1, /datum/reagent/consumable/ethanol/creme_de_menthe = 1, /datum/reagent/consumable/ethanol/vodka = 1, /datum/reagent/consumable/menthol = 1)
mix_message = "The cocoa turns mint green just as the strong scent hits your nose."
/datum/chemical_reaction/alexander
@@ -840,3 +840,9 @@
id = /datum/reagent/consumable/ethanol/beesknees
results = list(/datum/reagent/consumable/ethanol/beesknees = 4)
required_reagents = list(/datum/reagent/consumable/ethanol/mead = 1, /datum/reagent/consumable/honey = 1, /datum/reagent/consumable/ethanol/whiskey = 1, /datum/reagent/consumable/lemonjuice = 1)
+
+/datum/chemical_reaction/beeffizz
+ name = "Beef Fizz"
+ id = /datum/reagent/consumable/ethanol/beeffizz
+ results = list(/datum/reagent/consumable/ethanol/beeffizz = 10)
+ required_reagents = list(/datum/reagent/consumable/beefbroth = 7, /datum/reagent/consumable/ice = 2, /datum/reagent/consumable/lemonjuice = 1 )
diff --git a/code/modules/food_and_drinks/recipes/food_mixtures.dm b/code/modules/food_and_drinks/recipes/food_mixtures.dm
index 5503ecc0b25a0..3a126cf8bee5a 100644
--- a/code/modules/food_and_drinks/recipes/food_mixtures.dm
+++ b/code/modules/food_and_drinks/recipes/food_mixtures.dm
@@ -47,8 +47,8 @@
/datum/chemical_reaction/hot_cocoa
name = "Hot Cocoa"
- id = /datum/reagent/consumable/hot_cocoa
- results = list(/datum/reagent/consumable/hot_cocoa = 5)
+ id = /datum/reagent/consumable/cocoa/hot_cocoa
+ results = list(/datum/reagent/consumable/cocoa/hot_cocoa = 5)
required_reagents = list(/datum/reagent/water = 5, /datum/reagent/consumable/cocoa = 1)
/datum/chemical_reaction/coffee
diff --git a/code/modules/food_and_drinks/recipes/processor_recipes.dm b/code/modules/food_and_drinks/recipes/processor_recipes.dm
index eab3cc0707da3..8a53b95cb8690 100644
--- a/code/modules/food_and_drinks/recipes/processor_recipes.dm
+++ b/code/modules/food_and_drinks/recipes/processor_recipes.dm
@@ -51,4 +51,8 @@
/datum/food_processor_process/mob/slime
input = /mob/living/simple_animal/slime
output = null
- required_machine = /obj/machinery/processor/slime
\ No newline at end of file
+ required_machine = /obj/machinery/processor/slime
+
+/datum/food_processor_process/fish
+ input = /obj/item/fish
+ output = /obj/item/reagent_containers/food/snacks/carpmeat/icantbeliveitsnotcarp
\ No newline at end of file
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_bread.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_bread.dm
index 6d08137d49342..6c315a2747a76 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_bread.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_bread.dm
@@ -115,3 +115,13 @@
)
result = /mob/living/simple_animal/pet/cat/breadcat
subcategory = CAT_BREAD
+
+/datum/crafting_recipe/food/hotdog
+ name = "Hot dog"
+ reqs = list(
+ /datum/reagent/consumable/ketchup = 5,
+ /obj/item/reagent_containers/food/snacks/bun = 1,
+ /obj/item/reagent_containers/food/snacks/sausage = 1
+ )
+ result = /obj/item/reagent_containers/food/snacks/hotdog
+ subcategory = CAT_BREAD
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_burger.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_burger.dm
index 0bb2b970ca2a3..a35a6025988fb 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_burger.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_burger.dm
@@ -368,3 +368,17 @@
)
result = /obj/item/reagent_containers/food/snacks/burger/chicken
subcategory = CAT_BURGER
+
+/datum/crafting_recipe/food/crazyhamburger
+ name = "Crazy Hamburger"
+ reqs = list(
+ /obj/item/reagent_containers/food/snacks/meat/steak/plain = 1, // we have no horse meat sadly
+ /obj/item/reagent_containers/food/snacks/grown/chili = 2,
+ /datum/reagent/consumable/cooking_oil = 20,
+ /obj/item/reagent_containers/food/snacks/grown/nettle/death = 2, // closest thing to "grass of death"
+ /obj/item/reagent_containers/food/snacks/cheesewedge = 4,
+ /obj/item/reagent_containers/food/snacks/bun = 1
+ )
+ result = /obj/item/reagent_containers/food/snacks/burger/crazy
+ subcategory = CAT_BURGER
+
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_drink.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_drink.dm
index 1cb57f78f748d..e2b13d4b0f2bc 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_drink.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_drink.dm
@@ -114,3 +114,12 @@
time = 10
reqs = list(/obj/item/stack/sheet/cardboard = 1)
category = CAT_DRINK
+
+/datum/crafting_recipe/honeycomb
+ name = "Honeycomb"
+ result = /obj/item/reagent_containers/food/drinks/honeycomb
+ always_available = FALSE
+ time = 30
+ reqs = list(/datum/reagent/consumable/sugar = 50)
+ category = CAT_DRINK
+
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 40f24280943cf..0f248e3afcc03 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm
@@ -23,7 +23,7 @@
)
result = /obj/item/reagent_containers/food/snacks/donut/chaos
-datum/crafting_recipe/food/donut/meat
+/datum/crafting_recipe/food/donut/meat
time = 15
name = "Meat donut"
reqs = list(
@@ -444,16 +444,6 @@ datum/crafting_recipe/food/donut/meat
////////////////////////////////////////////OTHER////////////////////////////////////////////
-/datum/crafting_recipe/food/hotdog
- name = "Hot dog"
- reqs = list(
- /datum/reagent/consumable/ketchup = 5,
- /obj/item/reagent_containers/food/snacks/bun = 1,
- /obj/item/reagent_containers/food/snacks/sausage = 1
- )
- result = /obj/item/reagent_containers/food/snacks/hotdog
- subcategory = CAT_PASTRY
-
/datum/crafting_recipe/food/meatbun
name = "Meat bun"
reqs = list(
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_soup.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_soup.dm
index 0cc5f67f94b31..41a3756673803 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_soup.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_soup.dm
@@ -140,7 +140,7 @@
/datum/reagent/water = 10,
/obj/item/reagent_containers/glass/bowl = 1,
/obj/item/reagent_containers/food/snacks/grown/banana = 1,
- /obj/item/stack/ore/bananium = 1
+ /obj/item/stack/sheet/mineral/bananium = 1
)
result = /obj/item/reagent_containers/food/snacks/soup/clownstears
subcategory = CAT_SOUP
@@ -269,4 +269,4 @@
/obj/item/reagent_containers/food/snacks/grown/mushroom/jupitercup = 1
)
result = /obj/item/reagent_containers/food/snacks/soup/electron
- subcategory = CAT_SOUP
\ No newline at end of file
+ subcategory = CAT_SOUP
diff --git a/code/modules/guardian/abilities/major/assassin.dm b/code/modules/guardian/abilities/major/assassin.dm
index 3569ba88e59ad..27994c798344b 100644
--- a/code/modules/guardian/abilities/major/assassin.dm
+++ b/code/modules/guardian/abilities/major/assassin.dm
@@ -10,8 +10,8 @@
arrow_weight = 0.9
var/next_stealth = 0
var/stealthcooldown = 0
- var/obj/screen/alert/canstealthalert
- var/obj/screen/alert/instealthalert
+ var/atom/movable/screen/alert/canstealthalert
+ var/atom/movable/screen/alert/instealthalert
/datum/guardian_ability/major/assassin/Apply()
@@ -68,12 +68,12 @@
if(next_stealth <= world.time)
if(mode)
if(!instealthalert)
- instealthalert = guardian.throw_alert("instealth", /obj/screen/alert/instealth)
+ instealthalert = guardian.throw_alert("instealth", /atom/movable/screen/alert/instealth)
guardian.clear_alert("canstealth")
canstealthalert = null
else
if(!canstealthalert)
- canstealthalert = guardian.throw_alert("canstealth", /obj/screen/alert/canstealth)
+ canstealthalert = guardian.throw_alert("canstealth", /atom/movable/screen/alert/canstealth)
guardian.clear_alert("instealth")
instealthalert = null
else
diff --git a/code/modules/guardian/abilities/major/explosive.dm b/code/modules/guardian/abilities/major/explosive.dm
index 4bc3d7963c83f..b015db7dad833 100644
--- a/code/modules/guardian/abilities/major/explosive.dm
+++ b/code/modules/guardian/abilities/major/explosive.dm
@@ -14,11 +14,11 @@
/datum/guardian_ability/major/explosive/Apply()
. = ..()
- guardian.verbs += /mob/living/simple_animal/hostile/guardian/proc/DetonateBomb
+ guardian.add_verb(/mob/living/simple_animal/hostile/guardian/proc/DetonateBomb)
/datum/guardian_ability/major/explosive/Remove()
. = ..()
- guardian.verbs -= /mob/living/simple_animal/hostile/guardian/proc/DetonateBomb
+ guardian.remove_verb(/mob/living/simple_animal/hostile/guardian/proc/DetonateBomb)
/datum/guardian_ability/major/explosive/Attack(atom/target)
if(prob(40) && isliving(target))
@@ -26,7 +26,7 @@
if(!M.anchored && M != guardian.summoner?.current && !guardian.hasmatchingsummoner(M))
new /obj/effect/temp_visual/guardian/phase/out(get_turf(M))
do_teleport(M, M, 10, channel = TELEPORT_CHANNEL_BLUESPACE)
- for(var/mob/living/L in range(1, M))
+ for(var/mob/living/L in hearers(1, M))
if(guardian.hasmatchingsummoner(L)) //if the summoner matches don't hurt them
continue
if(L != guardian && L != guardian.summoner?.current)
@@ -51,6 +51,8 @@
to_chat(guardian, "Your powers are on cooldown! You must wait 20 seconds between bombs.")
/datum/guardian_ability/major/explosive/proc/kaboom(atom/source, mob/living/explodee)
+ SIGNAL_HANDLER
+
if(!istype(explodee))
return
if(explodee == guardian || explodee == guardian.summoner?.current || guardian.hasmatchingsummoner(explodee))
@@ -70,6 +72,8 @@
UNREGISTER_BOMB_SIGNALS(A)
/datum/guardian_ability/major/explosive/proc/display_examine(datum/source, mob/user, text)
+ SIGNAL_HANDLER
+
text += "It glows with a strange light!"
diff --git a/code/modules/guardian/abilities/major/frenzy.dm b/code/modules/guardian/abilities/major/frenzy.dm
index 51ee4d68383b0..800b1dd9d93e5 100644
--- a/code/modules/guardian/abilities/major/frenzy.dm
+++ b/code/modules/guardian/abilities/major/frenzy.dm
@@ -41,7 +41,7 @@
next_rush = world.time + ((0.2 SECONDS * (5 - master_stats.potential)) + 2) //2 to 3 seconds
/datum/guardian_ability/major/frenzy/Stat()
- . = ..()
- if(statpanel("Status"))
- if(next_rush > world.time)
- stat(null, "Frenzy Charge Cooldown Remaining: [DisplayTimeText(next_rush - world.time)]")
+ var/list/tab_data = list()
+ if(next_rush > world.time)
+ tab_data["Frenzy Charge Cooldown Remaining"] = GENERATE_STAT_TEXT("[DisplayTimeText(next_rush - world.time)]")
+ return tab_data
diff --git a/code/modules/guardian/abilities/major/gravity.dm b/code/modules/guardian/abilities/major/gravity.dm
index 16ec3978eb53c..8f57370c4fc54 100644
--- a/code/modules/guardian/abilities/major/gravity.dm
+++ b/code/modules/guardian/abilities/major/gravity.dm
@@ -23,6 +23,8 @@
remove_gravity(C)
/datum/guardian_ability/major/gravity/proc/recheck_distances()
+ SIGNAL_HANDLER
+
for(var/datum/component/C in gravito_targets)
if(get_dist(src, C.parent) > (master_stats.potential * 2))
remove_gravity(C)
@@ -49,5 +51,7 @@
qdel(C)
/datum/guardian_ability/major/gravity/proc/__distance_check(atom/movable/AM, OldLoc, Dir, Forced)
+ SIGNAL_HANDLER
+
if(get_dist(src, AM) > (master_stats.potential * 2))
remove_gravity(AM.GetComponent(/datum/component/forced_gravity))
diff --git a/code/modules/guardian/abilities/major/hand.dm b/code/modules/guardian/abilities/major/hand.dm
index 1c107cf22d6dc..2173d6408a763 100644
--- a/code/modules/guardian/abilities/major/hand.dm
+++ b/code/modules/guardian/abilities/major/hand.dm
@@ -14,12 +14,12 @@
for(var/atom/movable/AM in get_turf(target))
if(AM.anchored)
continue
- AM.forceMove(hand_turf)
+ do_teleport(AM, hand_turf, no_effects = TRUE, channel = TELEPORT_CHANNEL_MAGIC)
guardian.face_atom(hand_turf)
return ..()
/datum/guardian_ability/major/hand/Stat()
- . = ..()
- if(statpanel("Status"))
- if(next_hand > world.time)
- stat(null, "THE HAND Cooldown Remaining: [DisplayTimeText(next_hand - world.time)]")
+ var/list/tab_data = list()
+ if(next_hand > world.time)
+ tab_data["THE HAND Cooldown Remaining"] = GENERATE_STAT_TEXT("[DisplayTimeText(next_hand - world.time)]")
+ return tab_data
diff --git a/code/modules/guardian/abilities/major/predator.dm b/code/modules/guardian/abilities/major/predator.dm
index f6f4e58fff6b2..f2e2ca14d7121 100644
--- a/code/modules/guardian/abilities/major/predator.dm
+++ b/code/modules/guardian/abilities/major/predator.dm
@@ -83,7 +83,7 @@
status.scan_target = prey
status.point_to_target()
-/obj/screen/alert/status_effect/agent_pinpointer/predator
+/atom/movable/screen/alert/status_effect/agent_pinpointer/predator
name = "Predator's All-Seeing Eyes"
/datum/status_effect/agent_pinpointer/predator
@@ -91,7 +91,7 @@
minimum_range = 1
range_fuzz_factor = 0
tick_interval = 10
- alert_type = /obj/screen/alert/status_effect/agent_pinpointer/predator
+ alert_type = /atom/movable/screen/alert/status_effect/agent_pinpointer/predator
/datum/status_effect/agent_pinpointer/predator/scan_for_target()
return
diff --git a/code/modules/guardian/abilities/major/scout.dm b/code/modules/guardian/abilities/major/scout.dm
index 714accc693d88..bba045dc3b4ed 100644
--- a/code/modules/guardian/abilities/major/scout.dm
+++ b/code/modules/guardian/abilities/major/scout.dm
@@ -17,6 +17,7 @@
guardian.alpha = 45
guardian.range = 255
guardian.do_the_cool_invisible_thing = FALSE
+ guardian.can_use_abilities = FALSE
else
guardian.ranged = initial(guardian.ranged)
guardian.melee_damage = initial(guardian.melee_damage)
@@ -26,6 +27,7 @@
guardian.range = initial(guardian.range)
guardian.do_the_cool_invisible_thing = initial(guardian.do_the_cool_invisible_thing)
guardian.stats.Apply(guardian)
+ guardian.can_use_abilities = initial(guardian.can_use_abilities)
/datum/guardian_ability/major/scout/Manifest()
if(mode)
diff --git a/code/modules/guardian/abilities/minor/snares.dm b/code/modules/guardian/abilities/minor/snares.dm
index 2b616ecd24ac0..0b1a1e3344293 100644
--- a/code/modules/guardian/abilities/minor/snares.dm
+++ b/code/modules/guardian/abilities/minor/snares.dm
@@ -5,17 +5,20 @@
cost = 1
/datum/guardian_ability/minor/snare/Apply()
- guardian.verbs += /mob/living/simple_animal/hostile/guardian/proc/Snare
- guardian.verbs += /mob/living/simple_animal/hostile/guardian/proc/DisarmSnare
+ guardian.add_verb(/mob/living/simple_animal/hostile/guardian/proc/Snare)
+ guardian.add_verb(/mob/living/simple_animal/hostile/guardian/proc/DisarmSnare)
/datum/guardian_ability/minor/snare/Remove()
- guardian.verbs -= /mob/living/simple_animal/hostile/guardian/proc/Snare
- guardian.verbs -= /mob/living/simple_animal/hostile/guardian/proc/DisarmSnare
+ guardian.remove_verb(/mob/living/simple_animal/hostile/guardian/proc/Snare)
+ guardian.remove_verb(/mob/living/simple_animal/hostile/guardian/proc/DisarmSnare)
/mob/living/simple_animal/hostile/guardian/proc/Snare()
set name = "Set Surveillance Snare"
set category = "Guardian"
set desc = "Set an invisible snare that will alert you when living creatures walk over it. Max of 5"
+ if(!can_use_abilities)
+ to_chat(src, "You can't do that right now!")
+ return
if(snares.len <6)
var/turf/snare_loc = get_turf(src.loc)
var/obj/effect/snare/S = new /obj/effect/snare(snare_loc)
diff --git a/code/modules/guardian/abilities/minor/teleport.dm b/code/modules/guardian/abilities/minor/teleport.dm
index d155ed50f00f0..d6b9bcc5fb996 100644
--- a/code/modules/guardian/abilities/minor/teleport.dm
+++ b/code/modules/guardian/abilities/minor/teleport.dm
@@ -7,15 +7,15 @@
/datum/guardian_ability/minor/teleport/Apply()
..()
- guardian.verbs += /mob/living/simple_animal/hostile/guardian/proc/Beacon
+ guardian.add_verb(/mob/living/simple_animal/hostile/guardian/proc/Beacon)
/datum/guardian_ability/minor/teleport/Remove()
..()
- guardian.verbs -= /mob/living/simple_animal/hostile/guardian/proc/Beacon
+ guardian.remove_verb(/mob/living/simple_animal/hostile/guardian/proc/Beacon)
/obj/effect/proc_holder/spell/targeted/guardian/teleport
name = "Teleport"
- desc = "Teleport someone to your recieving pad."
+ desc = "Teleport someone to your receiving pad."
/obj/effect/proc_holder/spell/targeted/guardian/teleport/InterceptClickOn(mob/living/caller, params, atom/movable/A)
if(!istype(A))
@@ -27,6 +27,9 @@
if(!G.is_deployed())
to_chat(G, "You must be manifested to warp a target!")
return
+ if(!G.can_use_abilities)
+ to_chat(G, "You can't do that right now!")
+ return
if(!G.beacon)
to_chat(G, "You need a beacon placed to warp things!")
return
@@ -38,7 +41,7 @@
return
var/turf/T = get_turf(A)
- if(G.beacon.z != T.z)
+ if(G.beacon.get_virtual_z_level() != T.get_virtual_z_level())
to_chat(G, "The beacon is too far away to warp to!")
return
remove_ranged_ability()
diff --git a/code/modules/guardian/abilities/special/absolution.dm b/code/modules/guardian/abilities/special/absolution.dm
index 5bd3a8af31d8b..91ba7eb2dcf26 100644
--- a/code/modules/guardian/abilities/special/absolution.dm
+++ b/code/modules/guardian/abilities/special/absolution.dm
@@ -50,19 +50,6 @@
// STUFF
-/obj/item/melee_attack_chain(mob/user, atom/target, params)
- if(!tool_attack_chain(user, target) && pre_attack(target, user, params))
- var/resolved
- if(HAS_TRAIT(target, TRAIT_ONEWAYROAD))
- resolved = user.attackby(src, user, params) // you just hit yourself
- else
- resolved = target.attackby(src, user, params)
- if(!resolved && target && !QDELETED(src))
- if(HAS_TRAIT(target, TRAIT_ONEWAYROAD))
- afterattack(user, user, 1, params)
- else
- afterattack(target, user, 1, params)
-
/mob/living/carbon/human/bullet_act(obj/item/projectile/P, def_zone)
if(HAS_TRAIT(src, TRAIT_ONEWAYROAD))
var/atom/movable/oldfirer = P.firer
diff --git a/code/modules/guardian/guardian.dm b/code/modules/guardian/guardian.dm
index c65469e451b33..2e1b43a3315c2 100644
--- a/code/modules/guardian/guardian.dm
+++ b/code/modules/guardian/guardian.dm
@@ -38,7 +38,8 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
melee_damage = 15
AIStatus = AI_OFF
hud_type = /datum/hud/guardian
- mobsay_color = "#ffffff"
+ chat_color = "#ffffff"
+ mobchatspan = "blob"
var/next_reset = 0
var/guardiancolor = "#ffffff"
var/mutable_appearance/cooloverlay
@@ -49,7 +50,7 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
var/range = 10
var/cooldown = 0
var/datum/mind/summoner
- var/toggle_button_type = /obj/screen/guardian/ToggleMode
+ var/toggle_button_type = /atom/movable/screen/guardian/ToggleMode
var/datum/guardian_stats/stats
var/summoner_visible = TRUE
var/battlecry = "AT"
@@ -63,12 +64,14 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
var/beacon_cooldown = 0
var/list/pocket_dim
var/transforming = FALSE
+ var/can_use_abilities = TRUE
+ discovery_points = 5000
/mob/living/simple_animal/hostile/guardian/Initialize(mapload, theme, guardiancolor)
GLOB.parasites += src
if(guardiancolor)
src.guardiancolor = guardiancolor
- src.mobsay_color = guardiancolor
+ src.chat_color = guardiancolor
updatetheme(theme)
battlecry = pick("ORA", "MUDA", "DORA", "ARRI", "VOLA", "AT")
return ..()
@@ -169,7 +172,7 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
speak_emote = list("telepathically cries")
desc = "A truly alien creature, it is a mass of unknown organic material, standing by its' owner's side."
attack_sound = 'sound/weapons/pierce.ogg'
- if(!recolorentiresprite) //we want this to proc before stand logs in, so the overlay isnt gone for some reason
+ if(!recolorentiresprite) //we want this to proc before stand logs in, so the overlay isn't gone for some reason
cooloverlay = mutable_appearance(icon, theme)
cooloverlay.color = guardiancolor
add_overlay(cooloverlay)
@@ -216,6 +219,8 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
snapback()
/mob/living/simple_animal/hostile/guardian/proc/OnMoved()
+ SIGNAL_HANDLER
+
snapback()
setup_barriers()
@@ -241,20 +246,20 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
if(stats.ability)
stats.ability.Berserk()
-/mob/living/simple_animal/hostile/guardian/Stat()
- ..()
- if(statpanel("Status"))
- if(summoner?.current)
- var/resulthealth
- if(iscarbon(summoner.current))
- resulthealth = round((abs(HEALTH_THRESHOLD_DEAD - summoner.current.health) / abs(HEALTH_THRESHOLD_DEAD - summoner.current.maxHealth)) * 100)
- else
- resulthealth = round((summoner.current.health / summoner.current.maxHealth) * 100, 0.5)
- stat(null, "Summoner Health: [resulthealth]%")
- if(cooldown >= world.time)
- stat(null, "Manifest/Recall Cooldown Remaining: [DisplayTimeText(cooldown - world.time)]")
- if(stats.ability)
- stats.ability.Stat()
+/mob/living/simple_animal/hostile/guardian/get_stat_tab_status()
+ var/list/tab_data = ..()
+ if(summoner?.current)
+ var/resulthealth
+ if(iscarbon(summoner.current))
+ resulthealth = round((abs(HEALTH_THRESHOLD_DEAD - summoner.current.health) / abs(HEALTH_THRESHOLD_DEAD - summoner.current.maxHealth)) * 100)
+ else
+ resulthealth = round((summoner.current.health / summoner.current.maxHealth) * 100, 0.5)
+ tab_data["Summoner Health"] = GENERATE_STAT_TEXT("[resulthealth]%")
+ if(cooldown >= world.time)
+ tab_data["Manifest/Recall Cooldown Remaining"] = GENERATE_STAT_TEXT(" [DisplayTimeText(cooldown - world.time)]")
+ if(stats.ability)
+ tab_data += stats.ability.Stat()
+ return tab_data
/mob/living/simple_animal/hostile/guardian/Move() //Returns to summoner if they move out of range
pixel_x = initial(pixel_x)
@@ -340,9 +345,10 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
return loc != summoner?.current
/mob/living/simple_animal/hostile/guardian/Shoot(atom/targeted_atom)
- if( QDELETED(targeted_atom) || targeted_atom == targets_from.loc || targeted_atom == targets_from )
+ var/atom/target_from = GET_TARGETS_FROM(src)
+ if( QDELETED(targeted_atom) || targeted_atom == target_from.loc || targeted_atom == target_from )
return
- var/turf/startloc = get_turf(targets_from)
+ var/turf/startloc = get_turf(target_from)
var/obj/item/projectile/P = new /obj/item/projectile/guardian(startloc)
playsound(src, projectilesound, 100, 1)
P.color = guardiancolor
@@ -353,7 +359,7 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
P.yo = targeted_atom.y - startloc.y
P.xo = targeted_atom.x - startloc.x
if(AIStatus != AI_ON)//Don't want mindless mobs to have their movement screwed up firing in space
- newtonian_move(get_dir(targeted_atom, targets_from))
+ newtonian_move(get_dir(targeted_atom, target_from))
P.original = targeted_atom
P.preparePixelProjectile(targeted_atom, src)
P.fire()
@@ -391,7 +397,7 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
if(stats.ability)
stats.ability.AfterAttack(target)
-/mob/living/simple_animal/hostile/guardian/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /obj/screen/fullscreen/flash)
+/mob/living/simple_animal/hostile/guardian/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /atom/movable/screen/fullscreen/flash)
return FALSE
/mob/living/simple_animal/hostile/guardian/death()
@@ -409,7 +415,7 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
resulthealth = round((abs(HEALTH_THRESHOLD_DEAD - summoner.current.health) / abs(HEALTH_THRESHOLD_DEAD - summoner.current.maxHealth)) * 100)
else
resulthealth = round((summoner.current.health / summoner.current.maxHealth) * 100, 0.5)
- hud_used.healths.maptext = "
[resulthealth]%
"
+ hud_used.healths.maptext = MAPTEXT("
[resulthealth]%
")
/mob/living/simple_animal/hostile/guardian/adjustHealth(amount, updating_health = TRUE, forced = FALSE) //The spirit is invincible, but passes on damage to the summoner
if(berserk)
@@ -580,16 +586,20 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
key = C.key
/mob/living/simple_animal/hostile/guardian/proc/Reviveify()
+ SIGNAL_HANDLER
+
revive()
var/mob/gost = grab_ghost(TRUE)
if(!QDELETED(gost) && gost.ckey)
ckey = gost.ckey
/mob/living/simple_animal/hostile/guardian/proc/OnMindTransfer(datum/_source, mob/old_body, mob/new_body)
+ SIGNAL_HANDLER
+
if(!QDELETED(old_body))
- old_body.verbs -= /mob/living/proc/guardian_comm
- old_body.verbs -= /mob/living/proc/guardian_recall
- old_body.verbs -= /mob/living/proc/guardian_reset
+ old_body.remove_verb(/mob/living/proc/guardian_comm)
+ old_body.remove_verb(/mob/living/proc/guardian_recall)
+ old_body.remove_verb(/mob/living/proc/guardian_reset)
UnregisterSignal(old_body, COMSIG_MOVABLE_MOVED)
UnregisterSignal(old_body, COMSIG_LIVING_REVIVE)
if(isliving(new_body))
@@ -600,9 +610,9 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
RegisterSignal(new_body, COMSIG_MOVABLE_MOVED, /mob/living/simple_animal/hostile/guardian.proc/OnMoved)
RegisterSignal(new_body, COMSIG_LIVING_REVIVE, /mob/living/simple_animal/hostile/guardian.proc/Reviveify)
to_chat(src, "You manifest into existence, as your master's soul appears in a new body!")
- new_body.verbs |= /mob/living/proc/guardian_comm
- new_body.verbs |= /mob/living/proc/guardian_recall
- new_body.verbs |= /mob/living/proc/guardian_reset
+ new_body.add_verb(/mob/living/proc/guardian_comm)
+ new_body.add_verb(/mob/living/proc/guardian_recall)
+ new_body.add_verb(/mob/living/proc/guardian_reset)
/mob/living/proc/guardian_comm()
set name = "Communicate"
@@ -682,7 +692,7 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
else
to_chat(src, "You decide not to reset [guardians.len > 1 ? "any of your guardians":"your guardian"].")
else
- verbs -= /mob/living/proc/guardian_reset
+ remove_verb(/mob/living/proc/guardian_reset)
////////parasite tracking/finding procs
diff --git a/code/modules/guardian/guardianbuilder.dm b/code/modules/guardian/guardianbuilder.dm
index 5452ea37026a8..36b9023edf615 100644
--- a/code/modules/guardian/guardianbuilder.dm
+++ b/code/modules/guardian/guardianbuilder.dm
@@ -34,7 +34,6 @@
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "Guardian")
- ui.set_autoupdate(TRUE)
ui.open()
/datum/guardianbuilder/ui_data(mob/user)
@@ -83,7 +82,7 @@
cost = GA.cost,
icon = GA.ui_icon,
selected = istype(saved_stats.ability, ability),
- available = (points >= GA.cost) && GA.CanBuy(),
+ available = (points+saved_stats.ability?.cost >= GA.cost) && GA.CanBuy(),
path = "[ability]",
requiem = istype(GA, /datum/guardian_ability/major/special)
))
@@ -104,60 +103,73 @@
/datum/guardianbuilder/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
if(..() || used)
return
- calc_points()
switch(action)
if("name")
guardian_name = params["name"]
+ . = TRUE
if("set")
switch(params["name"])
if("Damage")
var/lvl = CLAMP(text2num(params["level"]), 1, 5)
if((points + (saved_stats.damage > 1 ? saved_stats.damage - 1 : 0)) >= lvl - 1 || lvl == 1)
saved_stats.damage = lvl
- . = TRUE
+ . = TRUE
if("Defense")
var/lvl = CLAMP(text2num(params["level"]), 1, 5)
if((points + (saved_stats.defense > 1 ? saved_stats.defense - 1 : 0)) >= lvl - 1 || lvl == 1)
saved_stats.defense = lvl
- . = TRUE
+ . = TRUE
if("Speed")
var/lvl = CLAMP(text2num(params["level"]), 1, 5)
if((points + (saved_stats.speed > 1 ? saved_stats.speed - 1 : 0)) >= lvl - 1 || lvl == 1)
saved_stats.speed = lvl
- . = TRUE
+ . = TRUE
if("Potential")
var/lvl = CLAMP(text2num(params["level"]), 1, 5)
if((points + (saved_stats.potential > 1 ? saved_stats.potential - 1 : 0)) >= lvl - 1 || lvl == 1)
saved_stats.potential = lvl
- . = TRUE
+ . = TRUE
if("Range")
var/lvl = CLAMP(text2num(params["level"]), 1, 5)
if((points + (saved_stats.range > 1 ? saved_stats.range - 1 : 0)) >= lvl - 1 || lvl == 1)
saved_stats.range = lvl
- . = TRUE
+ . = TRUE
if("color")
var/color = input(usr, "What would you like your guardian's color to be?", "Choose Your Color", "#ffffff") as color|null
if(color)
guardian_color = color
+ . = TRUE
if("clear_ability_major")
QDEL_NULL(saved_stats.ability)
+ . = TRUE
if("ability_major")
- var/ability = text2path(params["path"])
+ var/datum/guardian_ability/ability = text2path(params["path"])
var/list/types = allow_special ? (subtypesof(/datum/guardian_ability/major) - /datum/guardian_ability/major/special) : (subtypesof(/datum/guardian_ability/major) - typesof(/datum/guardian_ability/major/special))
if(ispath(ability))
if(saved_stats.ability && saved_stats.ability.type == ability)
QDEL_NULL(saved_stats.ability)
- else if(ability in types) // no nullspace narsie for you!
- QDEL_NULL(saved_stats.ability)
- saved_stats.ability = new ability
- saved_stats.ability.master_stats = saved_stats
+ . = TRUE
+ else if((ability in types) && (points + (saved_stats.ability?.cost || 0)) >= initial(ability.cost)) // no nullspace narsie for you!
+ var/datum/guardian_ability/new_ability = new ability
+ new_ability.master_stats = saved_stats
+ var/datum/guardian_ability/old_ability = saved_stats.ability
+ saved_stats.ability = null
+ if(new_ability.CanBuy(FALSE))
+ qdel(old_ability)
+ saved_stats.ability = new_ability
+ . = TRUE
+ else
+ qdel(new_ability)
+ saved_stats.ability = old_ability
if("ability_minor")
- var/ability = text2path(params["path"])
+ var/datum/guardian_ability/ability = text2path(params["path"])
if(ispath(ability) && (ability in subtypesof(/datum/guardian_ability/minor))) // no nullspace narsie for you!
if(saved_stats.HasMinorAbility(ability))
saved_stats.TakeMinorAbility(ability)
- else
+ . = TRUE
+ else if(points >= initial(ability.cost))
saved_stats.AddMinorAbility(ability)
+ . = TRUE
if("spawn")
. = spawn_guardian(usr)
if("reset")
@@ -167,8 +179,21 @@
if("ranged")
if(points >= 3)
saved_stats.ranged = TRUE
+ . = TRUE
if("melee")
saved_stats.ranged = FALSE
+ . = TRUE
+ if(.)
+ if(saved_stats.ability && !saved_stats.ability.CanBuy(FALSE)) // In case stat changes made some abilities invalid to have. Right now only Frenzy.
+ QDEL_NULL(saved_stats.ability)
+ var/list/datum/guardian_ability/minor/abilities_to_remove = list()
+ for(var/datum/guardian_ability/minor/minor in saved_stats.minor_abilities)
+ if(!minor.CanBuy(FALSE))
+ abilities_to_remove += minor
+ for(var/datum/guardian_ability/minor/minor in abilities_to_remove)
+ saved_stats.minor_abilities -= minor
+ qdel(minor)
+ calc_points()
/datum/guardianbuilder/proc/calc_points()
points = max_points
@@ -230,9 +255,9 @@
to_chat(user, "[G.real_name] has been caught!")
if(GUARDIAN_HIVE)
to_chat(user, "[G.real_name] has been created from the core!")
- user.verbs += /mob/living/proc/guardian_comm
- user.verbs += /mob/living/proc/guardian_recall
- user.verbs += /mob/living/proc/guardian_reset
+ user.add_verb(/mob/living/proc/guardian_comm)
+ user.add_verb(/mob/living/proc/guardian_recall)
+ user.add_verb(/mob/living/proc/guardian_reset)
return TRUE
else
to_chat(user, "[failure_message]")
diff --git a/code/modules/guardian/standarrow.dm b/code/modules/guardian/standarrow.dm
index d690bec334831..fe996818b9af3 100644
--- a/code/modules/guardian/standarrow.dm
+++ b/code/modules/guardian/standarrow.dm
@@ -45,17 +45,9 @@
user.dropItemToGround(src, TRUE)
forceMove(H)
if(iscarbon(M))
- sleep(15 SECONDS)
- if(prob(kill_chance))
- H.visible_message("[H] stares ahead, eyes full of fear, before collapsing lifelessly into ash, \the [src] falling out...")
- log_game("[key_name(H)] was killed by a stand arrow.")
- forceMove(H.drop_location())
- H.mind.no_cloning_at_all = TRUE
- H.adjustCloneLoss(500)
- H.dust(TRUE)
- in_use = FALSE
- else
- INVOKE_ASYNC(src, .proc/generate_stand, H)
+ in_use = TRUE
+ addtimer(CALLBACK(src, .proc/after_arrow_attack, H, kill_chance), 15 SECONDS)
+ in_use = FALSE
else if(isguardian(M))
INVOKE_ASYNC(src, .proc/requiem, M)
@@ -63,6 +55,17 @@
visible_message("[src] falls apart!")
qdel(src)
+/obj/item/stand_arrow/proc/after_arrow_attack(mob/living/carbon/H, var/kill_chance)
+ if(prob(kill_chance))
+ H.visible_message("[H] stares ahead, eyes full of fear, before collapsing lifelessly into ash, \the [src] falling out...")
+ log_game("[key_name(H)] was killed by a stand arrow.")
+ forceMove(H.drop_location())
+ H.mind.no_cloning_at_all = TRUE
+ H.adjustCloneLoss(500)
+ H.dust(TRUE)
+ else
+ INVOKE_ASYNC(src, .proc/generate_stand, H)
+
/obj/item/stand_arrow/proc/requiem(mob/living/simple_animal/hostile/guardian/G)
G.range = 255
G.transforming = TRUE
@@ -178,9 +181,9 @@
users[G] = TRUE
log_game("[key_name(H)] has summoned [key_name(G)], a holoparasite, via the stand arrow.")
to_chat(H, "[G.real_name] has been summoned!")
- H.verbs += /mob/living/proc/guardian_comm
- H.verbs += /mob/living/proc/guardian_recall
- H.verbs += /mob/living/proc/guardian_reset
+ H.add_verb(/mob/living/proc/guardian_comm)
+ H.add_verb(/mob/living/proc/guardian_recall)
+ H.add_verb(/mob/living/proc/guardian_reset)
uses--
in_use = FALSE
H.visible_message("\The [src] falls out of [H]!")
diff --git a/code/modules/holiday/easter.dm b/code/modules/holiday/easter.dm
index 9b21cde77f48f..6dbd6e92e38c0 100644
--- a/code/modules/holiday/easter.dm
+++ b/code/modules/holiday/easter.dm
@@ -7,7 +7,7 @@
earliest_start = 0 MINUTES
/datum/round_event/easter/announce(fake)
- priority_announce(pick("Hip-hop into Easter!","Find some Bunny's stash!","Today is National 'Hunt a Wabbit' Day.","Be kind, give Chocolate Eggs!"))
+ priority_announce(pick("Hip-hop into Easter!","Find some Bunny's stash!","Today is National 'Hunt a Wabbit' Day.","Be kind, give Chocolate Eggs!"), sound = SSstation.announcer.get_rand_alert_sound())
/datum/round_event_control/rabbitrelease
@@ -18,7 +18,7 @@
max_occurrences = 10
/datum/round_event/rabbitrelease/announce(fake)
- priority_announce("Unidentified furry objects detected coming aboard [station_name()]. Beware of Adorable-ness.", "Fluffy Alert", 'sound/ai/aliens.ogg')
+ priority_announce("Unidentified furry objects detected coming aboard [station_name()]. Beware of Adorable-ness.", "Fluffy Alert", ANNOUNCER_ALIENS)
/datum/round_event/rabbitrelease/start()
diff --git a/code/modules/holiday/holidays.dm b/code/modules/holiday/holidays.dm
index 098972034b1c6..6eb02740e6540 100644
--- a/code/modules/holiday/holidays.dm
+++ b/code/modules/holiday/holidays.dm
@@ -93,6 +93,19 @@
/datum/holiday/valentines/getStationPrefix()
return pick("Love","Amore","Single","Smootch","Hug")
+/// Garbage DAYYYYY
+/// Huh?.... NOOOO
+/// *GUNSHOT*
+/// AHHHGHHHHHHH
+/datum/holiday/garbageday
+ name = GARBAGEDAY
+ begin_day = 17
+ end_day = 17
+ begin_month = JUNE
+
+/datum/holiday/garbageday/getStationPrefix()
+ return pick("Trash","Janitor","Rubish","Bin")
+
/datum/holiday/birthday
name = "Birthday of Space Station 13"
begin_day = 16
@@ -281,6 +294,14 @@
/datum/holiday/friendship/greet()
return "Have a magical [name]!"
+/datum/holiday/anz
+ name = "ANZAC Day"
+ begin_day = 25
+ begin_month = APRIL
+
+/datum/holiday/anz/getStationPrefix()
+ return pick("Australian", "New Zealand", "Poppy", "Southern Cross")
+
/datum/holiday/beer
name = "Beer Day"
@@ -498,9 +519,9 @@ Since Ramadan is an entire month that lasts 29.5 days on average, the start and
/datum/holiday/xmas
name = CHRISTMAS
- begin_day = 22
+ begin_day = 24
begin_month = DECEMBER
- end_day = 27
+ end_day = 26
drone_hat = /obj/item/clothing/head/santa
/datum/holiday/xmas/greet()
diff --git a/code/modules/holodeck/area_copy.dm b/code/modules/holodeck/area_copy.dm
index dca4681137894..c94700b45e162 100644
--- a/code/modules/holodeck/area_copy.dm
+++ b/code/modules/holodeck/area_copy.dm
@@ -1,7 +1,10 @@
//Vars that will not be copied when using /DuplicateObject
GLOBAL_LIST_INIT(duplicate_forbidden_vars,list(
"tag", "datum_components", "area", "type", "loc", "locs", "vars", "parent", "parent_type", "verbs", "ckey", "key",
- "power_supply", "contents", "reagents", "stat", "x", "y", "z", "group", "atmos_adjacent_turfs", "comp_lookup"
+ "power_supply", "contents", "reagents", "stat", "x", "y", "z", "group", "atmos_adjacent_turfs", "comp_lookup",
+ "client_mobs_in_contents", "bodyparts", "internal_organs", "hand_bodyparts", "overlays_standing", "hud_list",
+ "actions", "AIStatus", "appearance", "managed_overlays", "managed_vis_overlays", "computer_id", "lastKnownIP", "implants",
+ "tgui_shared_states"
))
/proc/DuplicateObject(atom/original, perfectcopy = TRUE, sameloc, atom/newloc = null, nerf, holoitem)
@@ -20,7 +23,7 @@ GLOBAL_LIST_INIT(duplicate_forbidden_vars,list(
if(islist(original.vars[V]))
var/list/L = original.vars[V]
O.vars[V] = L.Copy()
- else if(istype(original.vars[V], /datum))
+ else if(istype(original.vars[V], /datum) || ismob(original.vars[V]))
continue // this would reference the original's object, that will break when it is used or deleted.
else
O.vars[V] = original.vars[V]
@@ -52,6 +55,11 @@ GLOBAL_LIST_INIT(duplicate_forbidden_vars,list(
contained_atom.flags_1 |= HOLOGRAM_1
if(M.circuit)
M.circuit.flags_1 |= HOLOGRAM_1
+
+ if(ismob(O)) //Overlays are carried over despite disallowing them, if a fix is found remove this.
+ var/mob/M = O
+ M.cut_overlays()
+ M.regenerate_icons()
return O
@@ -135,7 +143,5 @@ GLOBAL_LIST_INIT(duplicate_forbidden_vars,list(
if(toupdate.len)
for(var/turf/T1 in toupdate)
CALCULATE_ADJACENT_TURFS(T1)
- SSair.add_to_active(T1,1)
-
return copiedobjs
diff --git a/code/modules/holodeck/computer.dm b/code/modules/holodeck/computer.dm
index e9803d7e575f0..eb6dfc51f1dd5 100644
--- a/code/modules/holodeck/computer.dm
+++ b/code/modules/holodeck/computer.dm
@@ -1,22 +1,30 @@
/*
- Holodeck Update
-
- The on-station holodeck area is of type [holodeck_type].
- All subtypes of [program_type] are loaded into the program cache or emag programs list.
- If init_program is null, a random program will be loaded on startup.
- If you don't wish this, set it to the offline program or another of your choosing.
-
- You can use this to add holodecks with minimal code:
- 1) Define new areas for the holodeck programs
- 2) Map them
- 3) Create a new control console that uses those areas
-
- Non-mapped areas should be skipped but you should probably comment them out anyway.
- The base of program_type will always be ignored; only subtypes will be loaded.
+Map Template Holodeck
+
+Holodeck finds the location of mapped_start_area and loads offline_program in it on LateInitialize. It then passes its program templates to Holodeck.js in the form of program_cache and emag_programs. when a user selects a program the
+ui calls load_program() with the id of the selected program.
+load_program() -> map_template/load() on map_template/holodeck.
+
+holodeck map templates:
+1. have an update_blacklist that doesnt allow placing on non holofloors (except for engine floors so you can repair it)
+2. has should_place_on_top = FALSE, so that the baseturfs list doesnt pull a kilostation oom crash
+3. has returns_created = TRUE, so that SSatoms gives the map template a list of spawned atoms
+all the fancy flags and shit are added to holodeck objects in finish_spawn()
+
+Easiest way to add new holodeck programs:
+1. Define new map template datums in code/modules/holodeck/holodeck_map_templates, make sure they have the access flags
+of the holodeck you want them to be able to load, for the onstation holodeck the flag is STATION_HOLODECK.
+2. Create the new map templates in _maps/templates (remember theyre 9x10, and make sure they have area/noop or else it will fuck with linked)
+all turfs in holodeck programs MUST be of type /turf/open/floor/holofloor, OR /turf/open/floor/engine, or they will block future programs!
+
+Note: if youre looking at holodeck code because you want to see how returns_created is handled so that templates return a list of atoms
+created from them: make sure you handle that list correctly! Either copy them by value and delete them or reference it and handle qdel'ing
+and clear when youre done! if you dont i will use :newspaper2: on you
*/
-#define HOLODECK_CD 25
-#define HOLODECK_DMG_CD 500
+#define HOLODECK_CD 2 SECONDS
+#define HOLODECK_DMG_CD 5 SECONDS
+
/obj/machinery/computer/holodeck
name = "holodeck control console"
@@ -25,71 +33,97 @@
idle_power_usage = 10
active_power_usage = 50
+ //new vars
+
+ ///what area type this holodeck loads into. linked turns into the nearest instance of this area
+ var/area/mapped_start_area = /area/holodeck/rec_center
+ ///the currently used map template
+ var/datum/map_template/holodeck/template
+ ///bottom left corner of the loading room, used for placing
+ var/turf/bottom_left
+
+ ///if TRUE the holodeck is busy spawning another simulation and should immediately stop loading the newest one
+ var/spawning_simulation = FALSE
+
+ //old vars
+
+ ///the area that this holodeck loads templates into, used for power and deleting holo objects that leave it
var/area/holodeck/linked
- var/area/holodeck/program
- var/area/holodeck/last_program
- var/area/offline_program = /area/holodeck/rec_center/offline
+ ///what program is loaded right now or is about to be loaded
+ var/program = "offline"
+ var/last_program
+
+ ///the default program loaded by this holodeck when spawned and when deactivated
+ var/offline_program = "offline"
+
+ ///stores all of the unrestricted holodeck map templates that this computer has access to
var/list/program_cache
+ ///stores all of the restricted holodeck map templates that this computer has access to
var/list/emag_programs
- // Splitting this up allows two holodecks of the same size
- // to use the same source patterns. Y'know, if you want to.
- var/holodeck_type = /area/holodeck/rec_center // locate(this) to get the target holodeck
- var/program_type = /area/holodeck/rec_center // subtypes of this (but not this itself) are loadable programs
+ ///subtypes of this (but not this itself) are loadable programs
+ var/program_type = /datum/map_template/holodeck
+ ///every holo object created by the holodeck goes in here to track it
+ var/list/spawned = list()
+ var/list/effects = list() //like above, but for holo effects
+
+ ///TRUE if the holodeck is using extra power because of a program, FALSE otherwise
var/active = FALSE
+ ///increases the holodeck cooldown if TRUE, causing the holodeck to take longer to allow loading new programs
var/damaged = FALSE
- var/list/spawned = list()
- var/list/effects = list()
- var/current_cd = 0
+
+ //creates the timer that determines if another program can be manually loaded
+ COOLDOWN_DECLARE(holodeck_cooldown)
/obj/machinery/computer/holodeck/Initialize(mapload)
..()
return INITIALIZE_HINT_LATELOAD
-/obj/machinery/computer/holodeck/LateInitialize()
- if(ispath(holodeck_type, /area))
- linked = pop(get_areas(holodeck_type, FALSE))
- if(ispath(offline_program, /area))
- offline_program = pop(get_areas(offline_program), FALSE)
+/obj/machinery/computer/holodeck/LateInitialize()//from here linked is populated and the program list is generated. its also set to load the offline program
+ linked = GLOB.areas_by_type[mapped_start_area]
+ bottom_left = locate(linked.x, linked.y, src.z)
+
+ var/area/computer_area = get_area(src)
+ if(istype(computer_area, /area/holodeck))
+ log_mapping("Holodeck computer cannot be in a holodeck, This would cause circular power dependency.")
+ qdel(src)
+ return
+
// the following is necessary for power reasons
- if(!linked || !offline_program)
+ if(!linked)
log_world("No matching holodeck area found")
qdel(src)
return
- var/area/AS = get_area(src)
- if(istype(AS, /area/holodeck))
- log_mapping("Holodeck computer cannot be in a holodeck, This would cause circular power dependency.")
+ else if (!offline_program)
+ stack_trace("Holodeck console created without an offline program")
qdel(src)
return
+
else
linked.linked = src
var/area/my_area = get_area(src)
if(my_area)
linked.power_usage = my_area.power_usage
else
- linked.power_usage = new /list(AREA_USAGE_LEN)
+ linked.power_usage = list(AREA_USAGE_LEN)
+ COOLDOWN_START(src, holodeck_cooldown, HOLODECK_CD)
generate_program_list()
- load_program(offline_program, FALSE, FALSE)
-
-/obj/machinery/computer/holodeck/Destroy()
- emergency_shutdown()
- if(linked)
- linked.linked = null
- linked.power_usage = new /list(AREA_USAGE_LEN)
- return ..()
-
-/obj/machinery/computer/holodeck/power_change()
- . = ..()
- toggle_power(!stat)
+ load_program(offline_program,TRUE)
-
-/obj/machinery/computer/holodeck/ui_state(mob/user)
- return GLOB.default_state
+///adds all programs that this holodeck has access to, and separates the restricted and unrestricted ones
+/obj/machinery/computer/holodeck/proc/generate_program_list()
+ for(var/typekey in subtypesof(program_type))
+ var/datum/map_template/holodeck/program = typekey
+ var/list/info_this = list("id" = initial(program.template_id), "name" = initial(program.name))
+ if(initial(program.restricted))
+ LAZYADD(emag_programs, list(info_this))
+ else
+ LAZYADD(program_cache, list(info_this))
/obj/machinery/computer/holodeck/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
@@ -104,220 +138,273 @@
if(obj_flags & EMAGGED)
data["emagged"] = TRUE
data["emag_programs"] = emag_programs
+ else
+ data["emagged"] = null
+ data["emag_programs"] = null
data["program"] = program
data["can_toggle_safety"] = issilicon(user) || IsAdminGhost(user)
-
return data
/obj/machinery/computer/holodeck/ui_act(action, params)
if(..())
return
- . = TRUE
switch(action)
if("load_program")
- var/program_to_load = text2path(params["type"])
- if(!ispath(program_to_load))
- return FALSE
- var/valid = FALSE
- var/list/checked = program_cache.Copy()
- if(obj_flags & EMAGGED)
- checked |= emag_programs
- for(var/prog in checked)
- var/list/P = prog
- if(P["type"] == program_to_load)
+ var/program_to_load = params["id"]
+
+ var/valid = FALSE //dont tell security about this
+
+ //checks if program_to_load is any one of the loadable programs, if it isnt then it rejects it
+ for(var/list/check_list as anything in program_cache)
+ if(check_list["id"] == program_to_load)
valid = TRUE
break
+ if(!valid && (obj_flags & EMAGGED))
+ for(var/list/check_list as anything in emag_programs)
+ if(check_list["id"] == program_to_load)
+ valid = TRUE
+ break
if(!valid)
- return FALSE
-
- var/area/A = locate(program_to_load) in GLOB.sortedAreas
- if(A)
- if(istype(A, /area/holodeck)) //Admins could technically load a non-holodeck area with some varediting
- var/area/holodeck/H = A
- if(H.restricted)
- message_admins("[key_name(usr)] is loading a restricted (and potentially dangerous) holodeck area: [H.name]")
- log_game("[key_name(usr)] is loading a restricted (and potentially dangerous) holodeck area: [H.name]")
- load_program(A)
+ return
+ //load the map_template that program_to_load represents
+ load_program(program_to_load)
+ . = TRUE
if("safety")
if((obj_flags & EMAGGED) && program)
emergency_shutdown()
- nerf(obj_flags & EMAGGED)
+ nerf(obj_flags & EMAGGED,FALSE)
obj_flags ^= EMAGGED
- say("Safeties restored. Restarting...")
+ say("Safeties reset. Restarting...")
+ . = TRUE
-/obj/machinery/computer/holodeck/process()
- if(damaged && prob(10))
- for(var/turf/T in linked)
- if(prob(5))
- do_sparks(2, 1, T)
- return
+///this is what makes the holodeck not spawn anything on broken tiles (space and non engine plating / non holofloors)
+/datum/map_template/holodeck/update_blacklist(turf/placement, list/input_blacklist)
+ for(var/turf/possible_blacklist as anything in get_affected_turfs(placement))
+ if (possible_blacklist.holodeck_compatible)
+ continue
+ input_blacklist += possible_blacklist
- if(!..() || !active)
+///loads the template whose id string it was given ("offline_program" loads datum/map_template/holodeck/offline)
+/obj/machinery/computer/holodeck/proc/load_program(map_id, force = FALSE, add_delay = TRUE)
+ if (program == map_id)
return
- if(!floorcheck())
- emergency_shutdown()
- damaged = TRUE
- for(var/mob/M in urange(10,src))
- M.show_message("The holodeck overloads!")
+ if (!is_operational())//load_program is called once with a timer (in toggle_power) we dont want this to load anything if its off
+ map_id = offline_program
+ force = TRUE
- for(var/turf/T in linked)
- if(prob(30))
- do_sparks(2, 1, T)
- T.ex_act(EXPLODE_LIGHT)
- T.hotspot_expose(1000,500,1)
+ if (!force && (!COOLDOWN_FINISHED(src, holodeck_cooldown) || spawning_simulation))
+ say("ERROR. Recalibrating projection apparatus.")
+ return
- if(!(obj_flags & EMAGGED))
- for(var/item in spawned)
- if(!(get_turf(item) in linked))
- derez(item, 0)
- for(var/e in effects)
- var/obj/effect/holodeck_effect/HE = e
- HE.tick()
+ if(spawning_simulation)
+ return
- active_power_usage = 50 + spawned.len * 3 + effects.len * 5
+ if (add_delay)
+ COOLDOWN_START(src, holodeck_cooldown, (damaged ? HOLODECK_CD + HOLODECK_DMG_CD : HOLODECK_CD))
+ if (damaged && floorcheck())
+ damaged = FALSE
+
+ spawning_simulation = TRUE
+ active = (map_id != offline_program)
+ use_power = active + IDLE_POWER_USE
+ program = map_id
+
+ //clear the items from the previous program
+ for(var/holo_atom in spawned)
+ derez(holo_atom)
+
+ for(var/obj/effect/holodeck_effect/holo_effect as anything in effects)
+ effects -= holo_effect
+ holo_effect.deactivate(src)
+
+ //makes sure that any time a holoturf is inside a baseturf list (e.g. if someone put a wall over it) its set to the OFFLINE turf
+ //so that you cant bring turfs from previous programs into other ones (like putting the plasma burn turf into lounge for example)
+ for(var/turf/closed/holo_turf in linked)
+ for(var/baseturf in holo_turf.baseturfs)
+ if(ispath(baseturf, /turf/open/floor/holofloor))
+ holo_turf.baseturfs -= baseturf
+ holo_turf.baseturfs += /turf/open/floor/holofloor/plating
+
+ template = SSmapping.holodeck_templates[map_id]
+ template.load(bottom_left) //this is what actually loads the holodeck simulation into the map
+
+ spawned = template.created_atoms //populate the spawned list with the atoms belonging to the holodeck
+
+ if(istype(template, /datum/map_template/holodeck/thunderdome1218) && !SSshuttle.shuttle_purchase_requirements_met[SHUTTLE_UNLOCK_MEDISIM])
+ say("Special note from \"1218 AD\" developer: I see you too are interested in the REAL dark ages of humanity! I've made this program also unlock some interesting shuttle designs on any communication console around. Have fun!")
+ SSshuttle.shuttle_purchase_requirements_met[SHUTTLE_UNLOCK_MEDISIM] = TRUE
-/obj/machinery/computer/holodeck/emag_act(mob/user)
- if(obj_flags & EMAGGED)
- return
- if(!LAZYLEN(emag_programs))
- to_chat(user, "[src] does not seem to have a card swipe port. It must be an inferior model.")
- return
- playsound(src, "sparks", 75, TRUE)
- obj_flags |= EMAGGED
- to_chat(user, "You vastly increase projector power and override the safety and security protocols.")
- say("Warning. Automatic shutoff and derezzing protocols have been corrupted. Please call Nanotrasen maintenance and do not use the simulator.")
- log_game("[key_name(user)] emagged the Holodeck Control Console")
nerf(!(obj_flags & EMAGGED))
+ finish_spawn()
-/obj/machinery/computer/holodeck/emp_act(severity)
- . = ..()
- if(. & EMP_PROTECT_SELF)
+///finalizes objects in the spawned list
+/obj/machinery/computer/holodeck/proc/finish_spawn()
+ for(var/atom/holo_atom as anything in spawned)
+ if(QDELETED(holo_atom))
+ spawned -= holo_atom
+ continue
+
+ RegisterSignal(holo_atom, COMSIG_PARENT_PREQDELETED, .proc/remove_from_holo_lists)
+ holo_atom.flags_1 |= HOLOGRAM_1
+
+ if(isholoeffect(holo_atom))//activates holo effects and transfers them from the spawned list into the effects list
+ var/obj/effect/holodeck_effect/holo_effect = holo_atom
+ effects += holo_effect
+ spawned -= holo_effect
+ var/atom/holo_effect_product = holo_effect.activate(src)//change name
+ if(istype(holo_effect_product))
+ spawned += holo_effect_product // we want mobs or objects spawned via holoeffects to be tracked as objects
+ RegisterSignal(holo_effect_product, COMSIG_PARENT_PREQDELETED, .proc/remove_from_holo_lists)
+ if(islist(holo_effect_product))
+ for(var/atom/atom_product as anything in holo_effect_product)
+ RegisterSignal(atom_product, COMSIG_PARENT_PREQDELETED, .proc/remove_from_holo_lists)
+ continue
+
+ if(isobj(holo_atom))
+ var/obj/holo_object = holo_atom
+ holo_object.resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+
+ if(isstructure(holo_object))
+ holo_object.flags_1 |= NODECONSTRUCT_1
+ continue
+
+ if(ismachinery(holo_object))
+ var/obj/machinery/holo_machine = holo_object
+ holo_machine.flags_1 |= NODECONSTRUCT_1
+ holo_machine.power_change()
+
+ if(istype(holo_machine, /obj/machinery/button))
+ var/obj/machinery/button/holo_button = holo_machine
+ holo_button.setup_device()
+
+ spawning_simulation = FALSE
+
+///this qdels holoitems that should no longer exist for whatever reason
+/obj/machinery/computer/holodeck/proc/derez(atom/movable/holo_atom, silent = TRUE, forced = FALSE)
+ spawned -= holo_atom
+ if(!holo_atom)
return
- emergency_shutdown()
+ UnregisterSignal(holo_atom, COMSIG_PARENT_PREQDELETED)
+ var/turf/target_turf = get_turf(holo_atom)
+ for(var/atom/movable/atom_contents as anything in holo_atom) //make sure that things inside of a holoitem are moved outside before destroying it
+ atom_contents.forceMove(target_turf)
-/obj/machinery/computer/holodeck/ex_act(severity, target)
- emergency_shutdown()
- return ..()
+ if(!silent)
+ visible_message("[holo_atom] fades away!")
-/obj/machinery/computer/holodeck/blob_act(obj/structure/blob/B)
- emergency_shutdown()
- return ..()
+ qdel(holo_atom)
-/obj/machinery/computer/holodeck/proc/generate_program_list()
- for(var/typekey in subtypesof(program_type))
- var/area/holodeck/A = GLOB.areas_by_type[typekey]
- if(!A || !A.contents.len)
- continue
- var/list/info_this = list()
- info_this["name"] = A.name
- info_this["type"] = A.type
- if(A.restricted)
- LAZYADD(emag_programs, list(info_this))
- else
- LAZYADD(program_cache, list(info_this))
+/obj/machinery/computer/holodeck/proc/remove_from_holo_lists(datum/to_remove, _forced)
+ SIGNAL_HANDLER
+
+ spawned -= to_remove
+ UnregisterSignal(to_remove, COMSIG_PARENT_PREQDELETED)
+
+/obj/machinery/computer/holodeck/process(delta_time=2)
+ if(damaged && DT_PROB(10, delta_time))
+ for(var/turf/holo_turf in linked)
+ if(DT_PROB(5, delta_time))
+ do_sparks(2, 1, holo_turf)
+ return
+ . = ..()
+ if(!. || program == offline_program)//we dont need to scan the holodeck if the holodeck is offline
+ return
+
+ if(!floorcheck()) //if any turfs in the floor of the holodeck are broken
+ emergency_shutdown()
+ damaged = TRUE
+ visible_message("The holodeck overloads!")
+ for(var/turf/holo_turf in linked)
+ if(DT_PROB(30, delta_time))
+ do_sparks(2, 1, holo_turf)
+ SSexplosions.lowturf += holo_turf
+ holo_turf.hotspot_expose(1000,500,1)
+
+ if(!(obj_flags & EMAGGED))
+ for(var/item in spawned)
+ if(!(get_turf(item) in linked))
+ derez(item)
+ for(var/obj/effect/holodeck_effect/holo_effect as anything in effects)
+ holo_effect.tick()
+ active_power_usage = 50 + spawned.len * 3 + effects.len * 5
/obj/machinery/computer/holodeck/proc/toggle_power(toggleOn = FALSE)
if(active == toggleOn)
return
if(toggleOn)
- if(last_program && last_program != offline_program)
- addtimer(CALLBACK(src, .proc/load_program, last_program, TRUE), 25)
+ if(last_program && (last_program != offline_program))
+ addtimer(CALLBACK(src,.proc/load_program, last_program, TRUE), 25)
active = TRUE
else
last_program = program
load_program(offline_program, TRUE)
active = FALSE
+/obj/machinery/computer/holodeck/power_change()
+ . = ..()
+ INVOKE_ASYNC(src, .proc/toggle_power, !stat)
+
+///shuts down the holodeck and force loads the offline_program
/obj/machinery/computer/holodeck/proc/emergency_shutdown()
last_program = program
- load_program(offline_program, TRUE)
active = FALSE
+ load_program(offline_program, TRUE)
+ ui_update()
+///returns TRUE if the entire floor of the holodeck is intact, returns FALSE if any are broken
/obj/machinery/computer/holodeck/proc/floorcheck()
- for(var/turf/T in linked)
- if(!T.intact || isspaceturf(T))
+ for(var/turf/holo_floor in linked)
+ if(isspaceturf(holo_floor))
+ return FALSE
+ if(!holo_floor.intact)
return FALSE
return TRUE
-/obj/machinery/computer/holodeck/proc/nerf(active)
- for(var/obj/item/I in spawned)
- I.damtype = active ? STAMINA : initial(I.damtype)
- for(var/e in effects)
- var/obj/effect/holodeck_effect/HE = e
- HE.safety(active)
-
-/obj/machinery/computer/holodeck/proc/load_program(area/A, force = FALSE, add_delay = TRUE)
- if(!is_operational())
- A = offline_program
- force = TRUE
+///changes all weapons in the holodeck to do stamina damage if set
+/obj/machinery/computer/holodeck/proc/nerf(nerf_this, is_loading = TRUE)
+ if (!nerf_this && is_loading)
+ return
+ for(var/obj/item/to_be_nerfed in spawned)
+ to_be_nerfed.damtype = nerf_this ? STAMINA : initial(to_be_nerfed.damtype)
+ for(var/obj/effect/holodeck_effect/holo_effect as anything in effects)
+ holo_effect.safety(nerf_this)
- if(program == A)
+/obj/machinery/computer/holodeck/emag_act(mob/user)
+ if(obj_flags & EMAGGED)
return
- if(current_cd > world.time && !force)
- say("ERROR. Recalibrating projection apparatus.")
+ if(!LAZYLEN(emag_programs))
+ to_chat(user, "[src] does not seem to have a card swipe port. It must be an inferior model.")
return
- if(add_delay)
- current_cd = world.time + HOLODECK_CD
- if(damaged)
- current_cd += HOLODECK_DMG_CD
- active = (A != offline_program)
- use_power = active + IDLE_POWER_USE
-
- for(var/e in effects)
- var/obj/effect/holodeck_effect/HE = e
- HE.deactivate(src)
-
- for(var/item in spawned)
- derez(item, !force)
+ playsound(src, "sparks", 75, TRUE)
+ obj_flags |= EMAGGED
+ to_chat(user, "You vastly increase projector power and override the safety and security protocols.")
+ say("Warning. Automatic shutoff and derezzing protocols have been corrupted. Please call Nanotrasen maintenance and do not use the simulator.")
+ log_game("[key_name(user)] emagged the Holodeck Control Console")
+ nerf(!(obj_flags & EMAGGED),FALSE)
+ ui_update()
- program = A
- // note nerfing does not yet work on guns, should
- // should also remove/limit/filter reagents?
- // this is an exercise left to others I'm afraid. -Sayu
- spawned = A.copy_contents_to(linked, 1, nerf_weapons = !(obj_flags & EMAGGED))
- for(var/obj/machinery/M in spawned)
- M.flags_1 |= NODECONSTRUCT_1
- for(var/obj/structure/S in spawned)
- S.flags_1 |= NODECONSTRUCT_1
- effects = list()
+/obj/machinery/computer/holodeck/emp_act(severity)
+ . = ..()
+ if(. & EMP_PROTECT_SELF)
+ return
+ emergency_shutdown()
- addtimer(CALLBACK(src, .proc/finish_spawn), 30)
+/obj/machinery/computer/holodeck/ex_act(severity, target)
+ emergency_shutdown()
+ return ..()
-/obj/machinery/computer/holodeck/proc/finish_spawn()
- var/list/added = list()
- for(var/obj/effect/holodeck_effect/HE in spawned)
- effects += HE
- spawned -= HE
- var/atom/x = HE.activate(src)
- if(istype(x) || islist(x))
- spawned += x // holocarp are not forever
- added += x
- for(var/obj/machinery/M in added)
- M.flags_1 |= NODECONSTRUCT_1
- for(var/obj/structure/S in added)
- S.flags_1 |= NODECONSTRUCT_1
-
-/obj/machinery/computer/holodeck/proc/derez(obj/O, silent = TRUE, forced = FALSE)
- // Emagging a machine creates an anomaly in the derez systems.
- if(O && (obj_flags & EMAGGED) && !stat && !forced)
- if((ismob(O) || ismob(O.loc)) && prob(50))
- addtimer(CALLBACK(src, .proc/derez, O, silent), 50) // may last a disturbingly long time
- return
-
- spawned -= O
- if(!O)
- return
- var/turf/T = get_turf(O)
- for(var/atom/movable/AM in O) // these should be derezed if they were generated
- AM.forceMove(T)
- if(ismob(AM))
- silent = FALSE // otherwise make sure they are dropped
+/obj/machinery/computer/holodeck/Destroy()
+ emergency_shutdown()
+ if(linked)
+ linked.linked = null
+ linked.power_usage = list(AREA_USAGE_LEN)
+ return ..()
- if(!silent)
- visible_message("[O] fades away!")
- qdel(O)
+/obj/machinery/computer/holodeck/blob_act(obj/structure/blob/B)
+ emergency_shutdown()
+ return ..()
#undef HOLODECK_CD
#undef HOLODECK_DMG_CD
diff --git a/code/modules/holodeck/holo_effect.dm b/code/modules/holodeck/holo_effect.dm
index 7d39ad8fa96d3..adfe9c96b2f1a 100644
--- a/code/modules/holodeck/holo_effect.dm
+++ b/code/modules/holodeck/holo_effect.dm
@@ -28,31 +28,36 @@
/obj/effect/holodeck_effect/cards
icon = 'icons/obj/toy.dmi'
icon_state = "deck_nanotrasen_full"
- var/obj/item/toy/cards/deck/D
+ var/obj/item/toy/cards/deck/deck
/obj/effect/holodeck_effect/cards/activate(var/obj/machinery/computer/holodeck/HC)
- D = new(loc)
+ deck = new(loc)
safety(!(HC.obj_flags & EMAGGED))
- D.holo = HC
- return D
+ deck.holo = HC
+ RegisterSignal(deck, COMSIG_PARENT_QDELETING, .proc/handle_card_delete)
+ return deck
+
+/obj/effect/holodeck_effect/cards/proc/handle_card_delete(datum/source)
+ SIGNAL_HANDLER
+ deck = null
/obj/effect/holodeck_effect/cards/safety(active)
- if(!D)
+ if(!deck)
return
if(active)
- D.card_hitsound = null
- D.card_force = 0
- D.card_throwforce = 0
- D.card_throw_speed = 3
- D.card_throw_range = 7
- D.card_attack_verb = list("attacked")
+ deck.card_hitsound = null
+ deck.card_force = 0
+ deck.card_throwforce = 0
+ deck.card_throw_speed = 3
+ deck.card_throw_range = 7
+ deck.card_attack_verb = list("attacked")
else
- D.card_hitsound = 'sound/weapons/bladeslice.ogg'
- D.card_force = 5
- D.card_throwforce = 10
- D.card_throw_speed = 3
- D.card_throw_range = 7
- D.card_attack_verb = list("attacked", "sliced", "diced", "slashed", "cut")
+ deck.card_hitsound = 'sound/weapons/bladeslice.ogg'
+ deck.card_force = 5
+ deck.card_throwforce = 10
+ deck.card_throw_speed = 3
+ deck.card_throw_range = 7
+ deck.card_attack_verb = list("attacked", "sliced", "diced", "slashed", "cut")
/obj/effect/holodeck_effect/sparks/activate(var/obj/machinery/computer/holodeck/HC)
@@ -61,31 +66,44 @@
var/datum/effect_system/spark_spread/s = new
s.set_up(3, 1, T)
s.start()
- T.temperature = 5000
+ T.set_temperature(5000)
T.hotspot_expose(50000,50000,1)
+/obj/effect/holodeck_effect/random_book
+
+/obj/effect/holodeck_effect/random_book/activate(obj/machinery/computer/holodeck/father_holodeck)
+ var/static/banned_books = list(/obj/item/book/manual/random, /obj/item/book/manual/nuclear, /obj/item/book/manual/wiki)
+ var/newtype = pick(subtypesof(/obj/item/book/manual) - banned_books)
+ var/obj/item/book/manual/to_spawn = new newtype(loc)
+ to_spawn.flags_1 |= (HOLOGRAM_1 | NODECONSTRUCT_1)
+ return to_spawn
/obj/effect/holodeck_effect/mobspawner
var/mobtype = /mob/living/simple_animal/hostile/carp/holocarp
- var/mob/mob = null
+ var/mob/our_mob = null
/obj/effect/holodeck_effect/mobspawner/activate(var/obj/machinery/computer/holodeck/HC)
if(islist(mobtype))
mobtype = pick(mobtype)
- mob = new mobtype(loc)
- mob.flags_1 |= HOLOGRAM_1
+ our_mob = new mobtype(loc)
+ our_mob.flags_1 |= HOLOGRAM_1
// these vars are not really standardized but all would theoretically create stuff on death
- for(var/v in list("butcher_results","corpse","weapon1","weapon2","blood_volume") & mob.vars)
- mob.vars[v] = null
- return mob
+ for(var/v in list("butcher_results","corpse","weapon1","weapon2","blood_volume") & our_mob.vars)
+ our_mob.vars[v] = null
+ RegisterSignal(our_mob, COMSIG_PARENT_QDELETING, .proc/handle_mob_delete)
+ return our_mob
/obj/effect/holodeck_effect/mobspawner/deactivate(var/obj/machinery/computer/holodeck/HC)
- if(mob)
- HC.derez(mob)
+ if(our_mob)
+ HC.derez(our_mob)
qdel(src)
+/obj/effect/holodeck_effect/mobspawner/proc/handle_mob_delete(datum/source)
+ SIGNAL_HANDLER
+ our_mob = null
+
/obj/effect/holodeck_effect/mobspawner/pet
mobtype = list(
/mob/living/simple_animal/butterfly, /mob/living/simple_animal/chick/holo,
@@ -101,7 +119,7 @@
/obj/effect/holodeck_effect/mobspawner/penguin
mobtype = /mob/living/simple_animal/pet/penguin/emperor
-
+
/obj/effect/holodeck_effect/mobspawner/penguin/Initialize()
if(prob(1))
mobtype = /mob/living/simple_animal/pet/penguin/emperor/shamebrero
@@ -109,3 +127,21 @@
/obj/effect/holodeck_effect/mobspawner/penguin_baby
mobtype = /mob/living/simple_animal/pet/penguin/baby
+
+/obj/effect/holodeck_effect/mobspawner/cat
+ mobtype = /mob/living/simple_animal/pet/cat
+
+/obj/effect/holodeck_effect/mobspawner/butterfly
+ mobtype = /mob/living/simple_animal/butterfly
+
+/obj/effect/holodeck_effect/mobspawner/clown
+ mobtype = list (/mob/living/simple_animal/hostile/retaliate/clown = 10,
+ /mob/living/simple_animal/hostile/retaliate/clown/banana = 6, /mob/living/simple_animal/hostile/retaliate/clown/honkling = 6,
+ /mob/living/simple_animal/hostile/retaliate/clown/fleshclown = 3, /mob/living/simple_animal/hostile/retaliate/clown/longface = 3,
+ /mob/living/simple_animal/hostile/retaliate/clown/mutant = 1, /mob/living/simple_animal/hostile/retaliate/clown/mutant/blob = 1)
+
+/obj/effect/holodeck_effect/mobspawner/psycho
+ mobtype = list (/mob/living/simple_animal/hostile/psycho/regular = 9,
+ /mob/living/simple_animal/hostile/psycho/muzzle = 3,
+ /mob/living/simple_animal/hostile/psycho/fast = 3,
+ /mob/living/simple_animal/hostile/psycho/trap = 1)
diff --git a/code/modules/holodeck/holodeck_map_templates.dm b/code/modules/holodeck/holodeck_map_templates.dm
new file mode 100644
index 0000000000000..f687d3e2fd6ba
--- /dev/null
+++ b/code/modules/holodeck/holodeck_map_templates.dm
@@ -0,0 +1,165 @@
+// --------------------
+// -- HOLODECK TEMPLATES --
+// --------------------
+
+/datum/map_template/holodeck
+ var/template_id = "id"
+ var/restricted = FALSE
+ var/datum/parsed_map/lastparsed
+
+ should_place_on_top = FALSE
+ returns_created_atoms = TRUE
+ keep_cached_map = TRUE
+
+ var/obj/machinery/computer/holodeck/linked
+
+/datum/map_template/holodeck/offline
+ name = "Holodeck - Offline"
+ template_id = "offline"
+ mappath = "_maps/holodeck/offline.dmm"
+
+/datum/map_template/holodeck/emptycourt
+ name = "Holodeck - Empty Court"
+ template_id = "emptycourt"
+ mappath = "_maps/holodeck/emptycourt.dmm"
+
+/datum/map_template/holodeck/dodgeball
+ name = "Holodeck - Dodgeball Court"
+ template_id = "dodgeball"
+ mappath = "_maps/holodeck/dodgeball.dmm"
+
+/datum/map_template/holodeck/basketball
+ name = "Holodeck - Basketball Court"
+ template_id = "basketball"
+ mappath = "_maps/holodeck/basketball.dmm"
+
+/datum/map_template/holodeck/thunderdome
+ name = "Holodeck - Thunderdome Arena"
+ template_id = "thunderdome"
+ mappath = "_maps/holodeck/thunderdome.dmm"
+
+/datum/map_template/holodeck/beach
+ name = "Holodeck - Beach"
+ template_id = "beach"
+ mappath = "_maps/holodeck/beach.dmm"
+
+/datum/map_template/holodeck/lounge
+ name = "Holodeck - Lounge"
+ template_id = "lounge"
+ mappath = "_maps/holodeck/lounge.dmm"
+
+/datum/map_template/holodeck/petpark
+ name = "Holodeck - Pet Park"
+ template_id = "petpark"
+ mappath = "_maps/holodeck/petpark.dmm"
+
+/datum/map_template/holodeck/firingrange
+ name = "Holodeck - Firing Range"
+ template_id = "firingrange"
+ mappath = "_maps/holodeck/firingrange.dmm"
+
+/datum/map_template/holodeck/anime_school
+ name = "Holodeck - Anime School"
+ template_id = "animeschool"
+ mappath = "_maps/holodeck/animeschool.dmm"
+
+/datum/map_template/holodeck/chapelcourt
+ name = "Holodeck - Chapel Courtroom"
+ template_id = "chapelcourt"
+ mappath = "_maps/holodeck/chapelcourt.dmm"
+
+/datum/map_template/holodeck/spacechess
+ name = "Holodeck - Space Chess"
+ template_id = "spacechess"
+ mappath = "_maps/holodeck/spacechess.dmm"
+
+/datum/map_template/holodeck/spacecheckers
+ name = "Holodeck - Space Checkers"
+ template_id = "spacecheckers"
+ mappath = "_maps/holodeck/spacecheckers.dmm"
+
+/datum/map_template/holodeck/kobayashi
+ name = "Holodeck - Kobayashi Maru"
+ template_id = "kobayashi"
+ mappath = "_maps/holodeck/kobayashi.dmm"
+
+/datum/map_template/holodeck/winterwonderland
+ name = "Holodeck - Winter Wonderland"
+ template_id = "winterwonderland"
+ mappath = "_maps/holodeck/winterwonderland.dmm"
+
+/datum/map_template/holodeck/photobooth
+ name = "Holodeck - Photobooth"
+ template_id = "photobooth"
+ mappath = "_maps/holodeck/photobooth.dmm"
+
+/datum/map_template/holodeck/skatepark
+ name = "Holodeck - Skatepark"
+ template_id = "skatepark"
+ mappath = "_maps/holodeck/skatepark.dmm"
+
+/datum/map_template/holodeck/teahouse
+ name = "Holodeck - Japanese Tea House"
+ template_id = "holodeck_teahouse"
+ mappath = "_maps/templates/holodeck_teahouse.dmm"
+
+/datum/map_template/holodeck/kitchen
+ name = "Holodeck - Holo-Kitchen"
+ template_id = "holodeck_kitchen"
+ mappath = "_maps/templates/holodeck_kitchen.dmm"
+
+//bad evil no good programs
+
+/datum/map_template/holodeck/medicalsim
+ name = "Holodeck - Emergency Medical"
+ template_id = "medicalsim"
+ mappath = "_maps/holodeck/medicalsim.dmm"
+ restricted = TRUE
+
+/datum/map_template/holodeck/thunderdome1218
+ name = "Holodeck - 1218 AD"
+ template_id = "thunderdome1218"
+ mappath = "_maps/holodeck/thunderdome1218.dmm"
+ restricted = TRUE
+
+/datum/map_template/holodeck/burntest
+ name = "Holodeck - Atmospheric Burn Test"
+ template_id = "burntest"
+ mappath = "_maps/holodeck/burntest.dmm"
+ restricted = TRUE
+
+/datum/map_template/holodeck/wildlifesim
+ name = "Holodeck - Wildlife Simulation"
+ template_id = "wildlifesim"
+ mappath = "_maps/holodeck/wildlifesim.dmm"
+ restricted = TRUE
+
+/datum/map_template/holodeck/holdoutbunker
+ name = "Holodeck - Holdout Bunker"
+ template_id = "holdoutbunker"
+ mappath = "_maps/holodeck/holdoutbunker.dmm"
+ restricted = TRUE
+
+/datum/map_template/holodeck/anthophillia
+ name = "Holodeck - Anthophillia"
+ template_id = "anthophillia"
+ mappath = "_maps/holodeck/anthophillia.dmm"
+ restricted = TRUE
+
+/datum/map_template/holodeck/refuelingstation
+ name = "Holodeck - Refueling Station"
+ template_id = "refuelingstation"
+ mappath = "_maps/holodeck/refuelingstation.dmm"
+ restricted = TRUE
+
+/datum/map_template/holodeck/asylum
+ name = "Holodeck - Asylum"
+ template_id = "holodeck_asylum"
+ mappath = "_maps/templates/holodeck_asylum.dmm"
+ restricted = TRUE
+
+/datum/map_template/holodeck/clownworld
+ name = "Holodeck - Clown World"
+ template_id = "holodeck_clownworld"
+ mappath = "_maps/templates/holodeck_clownworld.dmm"
+ restricted = TRUE
diff --git a/code/modules/holodeck/items.dm b/code/modules/holodeck/items.dm
index 27685f242b385..9bcf8513db529 100644
--- a/code/modules/holodeck/items.dm
+++ b/code/modules/holodeck/items.dm
@@ -227,3 +227,20 @@
/obj/item/paper/fluff/holodeck/disclaimer
name = "Holodeck Disclaimer"
info = "Bruises sustained in the holodeck can be healed simply by sleeping."
+
+/obj/vehicle/ridden/scooter/skateboard/pro/holodeck
+ name = "holographic skateboard"
+ desc = "A holographic copy of the EightO brand professional skateboard."
+ instability = 6
+
+/obj/vehicle/ridden/scooter/skateboard/pro/holodeck/screwdriver_act(mob/living/user, obj/item/I)
+ return FALSE
+
+/obj/vehicle/ridden/scooter/skateboard/pro/holodeck/pick_up_board() //picking up normal skateboards spawned in the holodeck gets rid of the holo flag, now you cant pick them up.
+ return
+
+/obj/vehicle/ridden/scooter/skateboard/pro/holodeck/attackby(obj/item/I, mob/user, params)
+ if(istype(I, /obj/item/stack/rods))
+ return
+ else
+ return ..()
diff --git a/code/modules/holodeck/turfs.dm b/code/modules/holodeck/turfs.dm
index f8b9e3587e52e..a482ed877febf 100644
--- a/code/modules/holodeck/turfs.dm
+++ b/code/modules/holodeck/turfs.dm
@@ -1,7 +1,9 @@
/turf/open/floor/holofloor
icon_state = "floor"
+ holodeck_compatible = TRUE
thermal_conductivity = 0
flags_1 = NONE
+ var/direction = SOUTH
/turf/open/floor/holofloor/attackby(obj/item/I, mob/living/user)
return // HOLOFLOOR DOES NOT GIVE A FUCK
@@ -19,6 +21,30 @@
name = "holodeck projector floor"
icon_state = "engine"
+/turf/open/floor/holofloor/chapel
+ name = "chapel floor"
+ icon_state = "chapel"
+
+/turf/open/floor/holofloor/chapel/bottom_left
+ direction = WEST
+
+/turf/open/floor/holofloor/chapel/top_right
+ direction = EAST
+
+/turf/open/floor/holofloor/chapel/bottom_right
+
+/turf/open/floor/holofloor/chapel/top_left
+ direction = NORTH
+
+/turf/open/floor/holofloor/chapel/Initialize(mapload)
+ . = ..()
+ if (direction != SOUTH)
+ setDir(direction)
+
+/turf/open/floor/holofloor/white
+ name = "white floor"
+ icon_state = "white"
+
/turf/open/floor/holofloor/plating/burnmix
name = "burn-mix floor"
initial_gas_mix = BURNMIX_ATMOS
@@ -142,3 +168,14 @@
icon = 'icons/turf/floors.dmi'
icon_state = "asteroid"
tiled_dirt = FALSE
+
+/turf/open/floor/holofloor/dark
+ icon_state = "darkfull"
+
+/turf/open/floor/holofloor/clown
+ icon_state = "bananium"
+
+/turf/open/floor/holofloor/white
+ name = "white floor"
+ desc = "A tile in a pure white color."
+ icon_state = "pure_white"
diff --git a/code/modules/hydroponics/beekeeping/beebox.dm b/code/modules/hydroponics/beekeeping/beebox.dm
index 38dd8b48e0029..8dbdc997c482a 100644
--- a/code/modules/hydroponics/beekeeping/beebox.dm
+++ b/code/modules/hydroponics/beekeeping/beebox.dm
@@ -16,9 +16,7 @@
/mob/living/carbon/human/bee_friendly()
- if(dna?.species?.id == "pod") //bees pollinate plants, duh.
- return 1
- if (wear_suit && head && istype(wear_suit, /obj/item/clothing) && istype(head, /obj/item/clothing))
+ if (wear_suit && head && isclothing(wear_suit) && isclothing(head))
var/obj/item/clothing/CS = wear_suit
var/obj/item/clothing/CH = head
if (CS.clothing_flags & CH.clothing_flags & THICKMATERIAL)
@@ -208,7 +206,7 @@
continue
if(B.loc == src)
B.forceMove(drop_location())
- B.target = user
+ B.GiveTarget(user)
bees = TRUE
if(bees)
visible_message("[user] disturbs the bees!")
diff --git a/code/modules/hydroponics/biogenerator.dm b/code/modules/hydroponics/biogenerator.dm
index f0342c7c847b3..e4b2483355803 100644
--- a/code/modules/hydroponics/biogenerator.dm
+++ b/code/modules/hydroponics/biogenerator.dm
@@ -32,13 +32,20 @@
/obj/machinery/biogenerator/contents_explosion(severity, target)
..()
if(beaker)
- beaker.ex_act(severity, target)
+ switch(severity)
+ if(EXPLODE_DEVASTATE)
+ SSexplosions.high_mov_atom += beaker
+ if(EXPLODE_HEAVY)
+ SSexplosions.med_mov_atom += beaker
+ if(EXPLODE_LIGHT)
+ SSexplosions.low_mov_atom += beaker
/obj/machinery/biogenerator/handle_atom_del(atom/A)
..()
if(A == beaker)
beaker = null
update_icon()
+ ui_update()
/obj/machinery/biogenerator/RefreshParts()
var/E = 0
@@ -56,7 +63,7 @@
/obj/machinery/biogenerator/examine(mob/user)
. = ..()
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Productivity at [productivity*100]%. Matter consumption reduced by [(efficiency*25)-25]%. Machine can hold up to [max_items] pieces of produce."
+ . += "The status display reads: Productivity at [productivity*100]%. Matter consumption reduced by [(efficiency*25)-25]%. Machine can hold up to [max_items] pieces of produce."
/obj/machinery/biogenerator/on_reagent_change(changetype) //When the reagents change, change the icon as well.
update_icon()
@@ -85,6 +92,7 @@
var/obj/item/reagent_containers/glass/B = beaker
B.forceMove(drop_location())
beaker = null
+ ui_update()
update_icon()
return
@@ -102,6 +110,7 @@
beaker = O
to_chat(user, "You add the container to the machine.")
update_icon()
+ ui_update()
else
to_chat(user, "Close the maintenance panel first.")
return
@@ -125,6 +134,7 @@
to_chat(user, "You empty the plant bag into the biogenerator, filling it to its capacity.")
else
to_chat(user, "You fill the biogenerator to its capacity.")
+ ui_update()
return TRUE //no afterattack
else if(istype(O, /obj/item/reagent_containers/food/snacks/grown))
@@ -136,24 +146,26 @@
else
if(user.transferItemToLoc(O, src))
to_chat(user, "You put [O.name] in [src.name]")
+ ui_update()
return TRUE //no afterattack
else if (istype(O, /obj/item/disk/design_disk))
user.visible_message("[user] begins to load \the [O] in \the [src]...",
"You begin to load a design from \the [O]...",
"You hear the chatter of a floppy drive.")
processing = TRUE
+ ui_update()
var/obj/item/disk/design_disk/D = O
if(do_after(user, 10, target = src))
for(var/B in D.blueprints)
if(B)
stored_research.add_design(B)
processing = FALSE
+ ui_update()
return TRUE
else
to_chat(user, "You cannot put this in [src.name]!")
/obj/machinery/biogenerator/AltClick(mob/living/user)
- . = ..()
if(user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) && can_interact(user))
detach(user)
@@ -176,16 +188,20 @@
S += 5
if(I.reagents.get_reagent_amount(/datum/reagent/consumable/nutriment) < 0.1)
points += 1 * productivity
+ ui_update()
else
points += I.reagents.get_reagent_amount(/datum/reagent/consumable/nutriment) * 10 * productivity
+ ui_update()
qdel(I)
if(S)
processing = TRUE
+ ui_update()
update_icon()
playsound(loc, 'sound/machines/blender.ogg', 50, TRUE)
use_power(S * 30)
sleep(S + 15 / productivity)
processing = FALSE
+ ui_update()
update_icon()
/obj/machinery/biogenerator/proc/check_cost(list/materials, multiplier = 1, remove_points = TRUE)
@@ -196,6 +212,7 @@
else
if(remove_points)
points -= materials[getmaterialref(/datum/material/biomass)]*multiplier/efficiency
+ ui_update()
update_icon()
return TRUE
@@ -245,6 +262,7 @@
beaker.forceMove(drop_location())
beaker = null
update_icon()
+ ui_update()
/obj/machinery/biogenerator/ui_status(mob/user)
if(stat & BROKEN || panel_open)
diff --git a/code/modules/hydroponics/gene_modder.dm b/code/modules/hydroponics/gene_modder.dm
index cdcca86bd5e3e..058a65e57dba7 100644
--- a/code/modules/hydroponics/gene_modder.dm
+++ b/code/modules/hydroponics/gene_modder.dm
@@ -15,7 +15,7 @@
var/list/trait_genes = list()
var/datum/plant_gene/target
- var/operation = ""
+ var/operation = null
var/max_potency = 50 // See RefreshParts() for how these work
var/max_yield = 2
var/min_production = 12
@@ -23,6 +23,8 @@
var/min_wchance = 67
var/min_wrate = 10
+ var/skip_confirmation = FALSE
+
/obj/machinery/plantgenes/RefreshParts() // Comments represent the max you can set per tier, respectively. seeds.dm [219] clamps these for us but we don't want to mislead the viewer.
for(var/obj/item/stock_parts/manipulator/M in component_parts)
if(M.rating > 3)
@@ -52,6 +54,7 @@
max_endurance = 100
min_wchance = 0
min_wrate = 0
+ ui_update()
/obj/machinery/plantgenes/update_icon()
..()
@@ -97,163 +100,148 @@
else
..()
-/obj/machinery/plantgenes/ui_interact(mob/user)
- . = ..()
- if(!user)
+/obj/machinery/plantgenes/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "PlantDNAManipulator")
+ ui.open()
+
+/obj/machinery/plantgenes/ui_data(mob/user)
+ var/list/data = list()
+ . = data
+
+ data["seed"] = seed?.name
+ data["disk"] = null
+ data["disk_gene"] = null
+ data["disk_readonly"] = FALSE
+ data["disk_canadd"] = TRUE
+
+ data["operation"] = operation
+ data["operation_target"] = build_gene(target)
+
+ if(disk)
+ data["disk_readonly"] = disk.read_only
+ if(disk.gene)
+ data["disk_gene"] = build_gene(disk.gene)
+ data["disk"] = disk.gene.get_name()
+ if(seed)
+ data["disk_canadd"] = disk.gene.can_add(seed)
+ else
+ data["disk"] = "Empty disk"
+ if(disk.read_only)
+ data["disk"] += " (RO)"
+
+ data["core_genes"] = build_gene_list(core_genes, /datum/plant_gene/core)
+ data["reagent_genes"] = build_gene_list(reagent_genes, /datum/plant_gene/reagent)
+ data["trait_genes"] = build_gene_list(trait_genes, /datum/plant_gene/trait)
+
+ data["machine_stats"] = build_machine_stats()
+ data["skip_confirmation"] = skip_confirmation
+
+/obj/machinery/plantgenes/proc/build_machine_stats()
+ var/list/L = list()
+ . = L
+
+ L["potency"] = list("max", max_potency)
+ L["yield"] = list("max", max_yield)
+ L["production speed"] = list("min", min_production)
+ L["endurance"] = list("max", max_endurance)
+ L["lifespan"] = list("max", max_endurance)
+ L["weed growth rate"] = list("min", min_wrate)
+ L["weed vulnerability"] = list("min", min_wchance)
+
+/obj/machinery/plantgenes/proc/build_gene_list(list/genes, filter_type)
+ var/list/L = list()
+ . = L
+
+ for(var/datum/plant_gene/gene in genes)
+ L += list(build_gene(gene, filter_type))
+
+/obj/machinery/plantgenes/proc/get_gene_id(datum/plant_gene/gene)
+ if(istype(gene, /datum/plant_gene/core))
+ var/datum/plant_gene/core/core_gene = gene
+ return "core[core_gene.type]"
+ if(istype(gene, /datum/plant_gene/reagent))
+ var/datum/plant_gene/reagent/reagent_gene = gene
+ return "reagent[reagent_gene.reagent_id]"
+ if(istype(gene, /datum/plant_gene/trait))
+ var/datum/plant_gene/trait/trait_gene = gene
+ return "trait[trait_gene.type]"
+
+ return "unknown/[gene.name]"
+
+/obj/machinery/plantgenes/proc/build_gene(datum/plant_gene/gene, filter_type)
+ if(!gene)
return
- var/datum/browser/popup = new(user, "plantdna", "Plant DNA Manipulator", 450, 600)
- if(!(in_range(src, user) || issilicon(user)))
- popup.close()
+ if(filter_type && !istype(gene, filter_type))
return
- var/dat = ""
+ var/list/L = list()
+ . = L
- if(operation)
- if(!seed || (!target && operation != "insert"))
- operation = ""
- target = null
- interact(user)
- return
- if((operation == "replace" || operation == "insert") && (!disk || !disk.gene))
- operation = ""
- target = null
- interact(user)
- return
+ L["name"] = gene.get_name()
- dat += "
Confirm Operation
"
- dat += "
Are you sure you want to [operation] "
- switch(operation)
- if("remove")
- dat += "[target.get_name()] gene from \the [seed]? "
- if("extract")
- dat += "[target.get_name()] gene from \the [seed]? "
- dat += "The sample will be destroyed in process!"
- if(istype(target, /datum/plant_gene/core))
- var/datum/plant_gene/core/gene = target
- if(istype(target, /datum/plant_gene/core/potency))
- if(gene.value > max_potency)
- dat += "
This device's extraction capabilities are currently limited to [max_potency] potency. "
- dat += "Target gene will be degraded to [max_potency] potency on extraction."
- else if(istype(target, /datum/plant_gene/core/lifespan))
- if(gene.value > max_endurance)
- dat += "
This device's extraction capabilities are currently limited to [max_endurance] lifespan. "
- dat += "Target gene will be degraded to [max_endurance] Lifespan on extraction."
- else if(istype(target, /datum/plant_gene/core/endurance))
- if(gene.value > max_endurance)
- dat += "
This device's extraction capabilities are currently limited to [max_endurance] endurance. "
- dat += "Target gene will be degraded to [max_endurance] endurance on extraction."
- else if(istype(target, /datum/plant_gene/core/yield))
- if(gene.value > max_yield)
- dat += "
This device's extraction capabilities are currently limited to [max_yield] yield. "
- dat += "Target gene will be degraded to [max_yield] yield on extraction."
- else if(istype(target, /datum/plant_gene/core/production))
- if(gene.value < min_production)
- dat += "
This device's extraction capabilities are currently limited to [min_production] production. "
- dat += "Target gene will be degraded to [min_production] production on extraction."
- else if(istype(target, /datum/plant_gene/core/weed_rate))
- if(gene.value < min_wrate)
- dat += "
This device's extraction capabilities are currently limited to [min_wrate] weed rate. "
- dat += "Target gene will be degraded to [min_wrate] weed rate on extraction."
- else if(istype(target, /datum/plant_gene/core/weed_chance))
- if(gene.value < min_wchance)
- dat += "
This device's extraction capabilities are currently limited to [min_wchance] weed chance. "
- dat += "Target gene will be degraded to [min_wchance] weed chance on extraction."
-
- if("replace")
- dat += "[target.get_name()] gene with [disk.gene.get_name()]? "
- if("insert")
- dat += "[disk.gene.get_name()] gene into \the [seed]? "
- dat += "
"
+ var/linecount = 0
+ for(var/line in lines)
+ linecount += 1
+ dat += "Line [linecount]: EditX [line] "
+ dat += "Add Line
"
+ if(help)
+ dat += "Hide Help "
+ dat += {"
+ Lines are a series of chords, separated by commas (,), each with notes separated by hyphens (-).
+ Every note in a chord will play together, with chord timed by the tempo.
+
+ Notes are played by the names of the note, and optionally, the accidental, and/or the octave number.
+ By default, every note is natural and in octave 3. Defining otherwise is remembered for each note.
+ Example: C,D,E,F,G,A,B will play a C major scale.
+ After a note has an accidental placed, it will be remembered: C,C4,C,C3 is C3,C4,C4,C3
+ Chords can be played simply by seperating each note with a hyphon: A-C#,Cn-E,E-G#,Gn-B
+ A pause may be denoted by an empty chord: C,E,,C,G
+ To make a chord be a different time, end it with /x, where the chord length will be length
+ defined by tempo / x: C,G/2,E/4
+ Combined, an example is: E-E4/4,F#/2,G#/8,B/8,E3-E4/4
+
+ Lines may be up to [MUSIC_MAXLINECHARS] characters.
+ A song may only contain up to [MUSIC_MAXLINES] lines.
+ "}
+ else
+ dat += "Show Help "
+
+ var/datum/browser/popup = new(user, "instrument", parent?.name || "instrument", 700, 500)
+ popup.set_content(dat.Join(""))
+ popup.open()
+
+/**
+ * Parses a song the user has input into lines and stores them.
+ */
+/datum/song/proc/ParseSong(text)
+ set waitfor = FALSE
+ //split into lines
+ lines = splittext(text, "\n")
+ if(lines.len)
+ var/bpm_string = "BPM: "
+ if(findtext(lines[1], bpm_string, 1, length(bpm_string) + 1))
+ var/divisor = text2num(copytext(lines[1], length(bpm_string) + 1)) || 120 // default
+ tempo = sanitize_tempo(600 / round(divisor, 1))
+ lines.Cut(1, 2)
+ else
+ tempo = sanitize_tempo(5) // default 120 BPM
+ if(lines.len > MUSIC_MAXLINES)
+ to_chat(usr, "Too many lines!")
+ lines.Cut(MUSIC_MAXLINES + 1)
+ var/linenum = 1
+ for(var/l in lines)
+ if(length_char(l) > MUSIC_MAXLINECHARS)
+ to_chat(usr, "Line [linenum] too long!")
+ lines.Remove(l)
+ else
+ linenum++
+ updateDialog(usr) // make sure updates when complete
+
+/datum/song/Topic(href, href_list)
+ if(!usr.canUseTopic(parent, TRUE, FALSE, FALSE, FALSE))
+ usr << browse(null, "window=instrument")
+ usr.unset_machine()
+ return
+
+ parent.add_fingerprint(usr)
+
+ if(href_list["newsong"])
+ lines = new()
+ tempo = sanitize_tempo(5) // default 120 BPM
+ name = ""
+
+ else if(href_list["import"])
+ var/t = ""
+ do
+ t = html_encode(input(usr, "Please paste the entire song, formatted:", text("[]", name), t) as message)
+ if(!in_range(parent, usr))
+ return
+
+ if(length_char(t) >= MUSIC_MAXLINES * MUSIC_MAXLINECHARS)
+ var/cont = input(usr, "Your message is too long! Would you like to continue editing it?", "", "yes") in list("yes", "no")
+ if(cont == "no")
+ break
+ while(length_char(t) > MUSIC_MAXLINES * MUSIC_MAXLINECHARS)
+ ParseSong(t)
+
+ else if(href_list["help"])
+ help = text2num(href_list["help"]) - 1
+
+ else if(href_list["edit"])
+ editing = text2num(href_list["edit"]) - 1
+
+ if(href_list["repeat"]) //Changing this from a toggle to a number of repeats to avoid infinite loops.
+ if(playing)
+ return //So that people cant keep adding to repeat. If the do it intentionally, it could result in the server crashing.
+ repeat += round(text2num(href_list["repeat"]))
+ if(repeat < 0)
+ repeat = 0
+ if(repeat > max_repeats)
+ repeat = max_repeats
+
+ else if(href_list["tempo"])
+ tempo = sanitize_tempo(tempo + text2num(href_list["tempo"]))
+
+ else if(href_list["play"])
+ INVOKE_ASYNC(src, .proc/start_playing, usr)
+
+ else if(href_list["newline"])
+ var/newline = html_encode(input("Enter your line: ", parent.name) as text|null)
+ if(!newline || !in_range(parent, usr))
+ return
+ if(lines.len > MUSIC_MAXLINES)
+ return
+ if(length(newline) > MUSIC_MAXLINECHARS)
+ newline = copytext(newline, 1, MUSIC_MAXLINECHARS)
+ lines.Add(newline)
+
+ else if(href_list["deleteline"])
+ var/num = round(text2num(href_list["deleteline"]))
+ if(num > lines.len || num < 1)
+ return
+ lines.Cut(num, num+1)
+
+ else if(href_list["modifyline"])
+ var/num = round(text2num(href_list["modifyline"]),1)
+ var/content = stripped_input(usr, "Enter your line: ", parent.name, lines[num], MUSIC_MAXLINECHARS)
+ if(!content || !in_range(parent, usr))
+ return
+ if(num > lines.len || num < 1)
+ return
+ lines[num] = content
+
+ else if(href_list["stop"])
+ stop_playing()
+
+ else if(href_list["setlinearfalloff"])
+ var/amount = input(usr, "Set linear sustain duration in seconds", "Linear Sustain Duration") as null|num
+ if(!isnull(amount))
+ set_linear_falloff_duration(round(amount * 10, world.tick_lag))
+
+ else if(href_list["setexpfalloff"])
+ var/amount = input(usr, "Set exponential sustain factor", "Exponential sustain factor") as null|num
+ if(!isnull(amount))
+ set_exponential_drop_rate(round(amount, 0.00001))
+
+ else if(href_list["setvolume"])
+ var/amount = input(usr, "Set volume", "Volume") as null|num
+ if(!isnull(amount))
+ set_volume(round(amount, 1))
+
+ else if(href_list["setdropoffvolume"])
+ var/amount = input(usr, "Set dropoff threshold", "Dropoff Threshold Volume") as null|num
+ if(!isnull(amount))
+ set_dropoff_volume(round(amount, 0.01))
+
+ else if(href_list["switchinstrument"])
+ if(!length(allowed_instrument_ids))
+ return
+ else if(length(allowed_instrument_ids) == 1)
+ set_instrument(allowed_instrument_ids[1])
+ return
+ var/list/categories = list()
+ for(var/i in allowed_instrument_ids)
+ var/datum/instrument/I = SSinstruments.get_instrument(i)
+ if(I)
+ LAZYSET(categories[I.category || "ERROR CATEGORY"], I.name, I.id)
+ var/cat = input(usr, "Select Category", "Instrument Category") as null|anything in categories
+ if(!cat)
+ return
+ var/list/instruments = categories[cat]
+ var/choice = input(usr, "Select Instrument", "Instrument Selection") as null|anything in instruments
+ if(!choice)
+ return
+ choice = instruments[choice] //get id
+ if(choice)
+ set_instrument(choice)
+
+ else if(href_list["setnoteshift"])
+ var/amount = input(usr, "Set note shift", "Note Shift") as null|num
+ if(!isnull(amount))
+ note_shift = clamp(amount, note_shift_min, note_shift_max)
+
+ else if(href_list["setsustainmode"])
+ var/choice = input(usr, "Choose a sustain mode", "Sustain Mode") as null|anything in list("Linear", "Exponential")
+ switch(choice)
+ if("Linear")
+ sustain_mode = SUSTAIN_LINEAR
+ if("Exponential")
+ sustain_mode = SUSTAIN_EXPONENTIAL
+
+ else if(href_list["togglesustainhold"])
+ full_sustain_held_note = !full_sustain_held_note
+
+ updateDialog()
\ No newline at end of file
diff --git a/code/modules/instruments/songs/play_legacy.dm b/code/modules/instruments/songs/play_legacy.dm
new file mode 100644
index 0000000000000..b95e8f193f548
--- /dev/null
+++ b/code/modules/instruments/songs/play_legacy.dm
@@ -0,0 +1,91 @@
+/**
+ * Compiles our lines into "chords" with filenames for legacy playback. This makes there have to be a bit of lag at the beginning of the song, but repeats will not have to parse it again, and overall playback won't be impacted by as much lag.
+ */
+/datum/song/proc/compile_legacy()
+ if(!length(src.lines))
+ return
+ var/list/lines = src.lines //cache for hyepr speed!
+ compiled_chords = list()
+ var/list/octaves = list(3, 3, 3, 3, 3, 3, 3)
+ var/list/accents = list("n", "n", "n", "n", "n", "n", "n")
+ for(var/line in lines)
+ var/list/chords = splittext(lowertext(line), ",")
+ for(var/chord in chords)
+ var/list/compiled_chord = list()
+ var/tempodiv = 1
+ var/list/notes_tempodiv = splittext(chord, "/")
+ var/len = length(notes_tempodiv)
+ if(len >= 2)
+ tempodiv = text2num(notes_tempodiv[2])
+ if(len) //some dunkass is going to do ,,,, to make 3 rests instead of ,/1 because there's no standardization so let's be prepared for that.
+ var/list/notes = splittext(notes_tempodiv[1], "-")
+ for(var/note in notes)
+ if(length(note) == 0)
+ continue
+ // 1-7, A-G
+ var/key = text2ascii(note) - 96
+ if((key < 1) || (key > 7))
+ continue
+ for(var/i in 2 to length(note))
+ var/oct_acc = copytext(note, i, i + 1)
+ var/num = text2num(oct_acc)
+ if(!num) //it's an accidental
+ accents[key] = oct_acc //if they misspelled it/fucked up that's on them lmao, no safety checks.
+ else //octave
+ octaves[key] = clamp(num, octave_min, octave_max)
+ compiled_chord[++compiled_chord.len] = list(key, accents[key], octaves[key])
+ compiled_chord += tempodiv //this goes last
+ if(length(compiled_chord))
+ compiled_chords[++compiled_chords.len] = compiled_chord
+
+/**
+ * Proc to play a legacy note. Just plays the sound to hearing mobs (and does hearcheck if necessary), no fancy channel/sustain/management.
+ *
+ * Arguments:
+ * * note is a number from 1-7 for A-G
+ * * acc is either "b", "n", or "#"
+ * * oct is 1-8 (or 9 for C)
+ */
+/datum/song/proc/playkey_legacy(note, acc as text, oct, mob/user)
+ // handle accidental -> B<>C of E<>F
+ if(acc == "b" && (note == 3 || note == 6)) // C or F
+ if(note == 3)
+ oct--
+ note--
+ acc = "n"
+ else if(acc == "#" && (note == 2 || note == 5)) // B or E
+ if(note == 2)
+ oct++
+ note++
+ acc = "n"
+ else if(acc == "#" && (note == 7)) //G#
+ note = 1
+ acc = "b"
+ else if(acc == "#") // mass convert all sharps to flats, octave jump already handled
+ acc = "b"
+ note++
+
+ // check octave, C is allowed to go to 9
+ if(oct < 1 || (note == 3 ? oct > 9 : oct > 8))
+ return
+
+ // now generate name
+ var/soundfile = "sound/instruments/[cached_legacy_dir]/[ascii2text(note+64)][acc][oct].[cached_legacy_ext]"
+ soundfile = file(soundfile)
+ // make sure the note exists
+ if(!fexists(soundfile))
+ return
+ // and play
+ var/turf/source = get_turf(parent)
+ if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
+ do_hearcheck()
+ var/sound/music_played = sound(soundfile)
+ for(var/i in hearing_mobs)
+ var/mob/M = i
+ if(user && HAS_TRAIT(user, TRAIT_MUSICIAN) && isliving(M))
+ var/mob/living/L = M
+ L.apply_status_effect(STATUS_EFFECT_GOOD_MUSIC)
+ if(!(M?.client?.prefs?.toggles & SOUND_INSTRUMENTS))
+ continue
+ M.playsound_local(source, null, volume * using_instrument.volume_multiplier, S = music_played)
+ // Could do environment and echo later but not for now
\ No newline at end of file
diff --git a/code/modules/instruments/songs/play_synthesized.dm b/code/modules/instruments/songs/play_synthesized.dm
new file mode 100644
index 0000000000000..b1d6ad500548a
--- /dev/null
+++ b/code/modules/instruments/songs/play_synthesized.dm
@@ -0,0 +1,137 @@
+/**
+ * Compiles our lines into "chords" with numbers. This makes there have to be a bit of lag at the beginning of the song, but repeats will not have to parse it again, and overall playback won't be impacted by as much lag.
+ */
+/datum/song/proc/compile_synthesized()
+ if(!length(src.lines))
+ return
+ var/list/lines = src.lines //cache for hyepr speed!
+ compiled_chords = list()
+ var/list/octaves = list(3, 3, 3, 3, 3, 3, 3)
+ var/list/accents = list("n", "n", "n", "n", "n", "n", "n")
+ for(var/line in lines)
+ var/list/chords = splittext(lowertext(line), ",")
+ for(var/chord in chords)
+ var/list/compiled_chord = list()
+ var/tempodiv = 1
+ var/list/notes_tempodiv = splittext(chord, "/")
+ var/len = length(notes_tempodiv)
+ if(len >= 2)
+ tempodiv = text2num(notes_tempodiv[2])
+ if(len) //some dunkass is going to do ,,,, to make 3 rests instead of ,/1 because there's no standardization so let's be prepared for that.
+ var/list/notes = splittext(notes_tempodiv[1], "-")
+ for(var/note in notes)
+ if(length(note) == 0)
+ continue
+ // 1-7, A-G
+ var/key = text2ascii(note) - 96
+ if((key < 1) || (key > 7))
+ continue
+ for(var/i in 2 to length(note))
+ var/oct_acc = copytext(note, i, i + 1)
+ var/num = text2num(oct_acc)
+ if(!num) //it's an accidental
+ accents[key] = oct_acc //if they misspelled it/fucked up that's on them lmao, no safety checks.
+ else //octave
+ octaves[key] = clamp(num, octave_min, octave_max)
+ compiled_chord += clamp((note_offset_lookup[key] + octaves[key] * 12 + accent_lookup[accents[key]]), key_min, key_max)
+ compiled_chord += tempodiv //this goes last
+ if(length(compiled_chord))
+ compiled_chords[++compiled_chords.len] = compiled_chord
+
+/**
+ * Plays a specific numerical key from our instrument to anyone who can hear us.
+ * Does a hearing check if enough time has passed.
+ */
+/datum/song/proc/playkey_synth(key, mob/user)
+ if(can_noteshift)
+ key = clamp(key + note_shift, key_min, key_max)
+ if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
+ do_hearcheck()
+ var/datum/instrument_key/K = using_instrument.samples[num2text(key)] //See how fucking easy it is to make a number text? You don't need a complicated 9 line proc!
+ //Should probably add channel limiters here at some point but I don't care right now.
+ var/channel = pop_channel()
+ if(isnull(channel))
+ return FALSE
+ . = TRUE
+ var/sound/copy = sound(K.sample)
+ var/volume = src.volume * using_instrument.volume_multiplier
+ copy.frequency = K.frequency
+ copy.volume = volume
+ var/channel_text = num2text(channel)
+ channels_playing[channel_text] = 100
+ last_channel_played = channel_text
+ for(var/i in hearing_mobs)
+ var/mob/M = i
+ if(user && HAS_TRAIT(user, TRAIT_MUSICIAN) && isliving(M))
+ var/mob/living/L = M
+ L.apply_status_effect(STATUS_EFFECT_GOOD_MUSIC)
+ if(!(M?.client?.prefs?.toggles & SOUND_INSTRUMENTS))
+ continue
+ M.playsound_local(get_turf(parent), null, volume, FALSE, K.frequency, INSTRUMENT_DISTANCE_NO_FALLOFF, channel, null, copy, distance_multiplier = INSTRUMENT_DISTANCE_FALLOFF_BUFF)
+ // Could do environment and echo later but not for now
+
+/**
+ * Stops all sounds we are "responsible" for. Only works in synthesized mode.
+ */
+/datum/song/proc/terminate_all_sounds(clear_channels = TRUE)
+ for(var/i in hearing_mobs)
+ terminate_sound_mob(i)
+ if(clear_channels)
+ channels_playing.len = 0
+ channels_idle.len = 0
+ SSinstruments.current_instrument_channels -= using_sound_channels
+ using_sound_channels = 0
+ SSsounds.free_datum_channels(src)
+
+/**
+ * Stops all sounds we are responsible for in a given person. Only works in synthesized mode.
+ */
+/datum/song/proc/terminate_sound_mob(mob/M)
+ for(var/channel in channels_playing)
+ M.stop_sound_channel(text2num(channel))
+
+/**
+ * Pops a channel we have reserved so we don't have to release and re-request them from SSsounds every time we play a note. This is faster.
+ */
+/datum/song/proc/pop_channel()
+ if(length(channels_idle)) //just pop one off of here if we have one available
+ . = text2num(channels_idle[1])
+ channels_idle.Cut(1,2)
+ return
+ if(using_sound_channels >= max_sound_channels)
+ return
+ . = SSinstruments.reserve_instrument_channel(src)
+ if(!isnull(.))
+ using_sound_channels++
+
+/**
+ * Decays our channels and updates their volumes to mobs who can hear us.
+ *
+ * Arguments:
+ * * wait_ds - the deciseconds we should decay by. This is to compensate for any lag, as otherwise songs would get pretty nasty during high time dilation.
+ */
+/datum/song/proc/process_decay(wait_ds)
+ var/linear_dropoff = cached_linear_dropoff * wait_ds
+ var/exponential_dropoff = cached_exponential_dropoff ** wait_ds
+ for(var/channel in channels_playing)
+ if(full_sustain_held_note && (channel == last_channel_played))
+ continue
+ var/current_volume = channels_playing[channel]
+ switch(sustain_mode)
+ if(SUSTAIN_LINEAR)
+ current_volume -= linear_dropoff
+ if(SUSTAIN_EXPONENTIAL)
+ current_volume /= exponential_dropoff
+ channels_playing[channel] = current_volume
+ var/dead = current_volume <= sustain_dropoff_volume
+ var/channelnumber = text2num(channel)
+ if(dead)
+ channels_playing -= channel
+ channels_idle += channel
+ for(var/i in hearing_mobs)
+ var/mob/M = i
+ M.stop_sound_channel(channelnumber)
+ else
+ for(var/i in hearing_mobs)
+ var/mob/M = i
+ M.set_sound_channel_volume(channelnumber, (current_volume * 0.01) * volume * using_instrument.volume_multiplier)
\ No newline at end of file
diff --git a/code/modules/instruments/stationary.dm b/code/modules/instruments/stationary.dm
new file mode 100644
index 0000000000000..79228d88a8fd9
--- /dev/null
+++ b/code/modules/instruments/stationary.dm
@@ -0,0 +1,52 @@
+/obj/structure/musician
+ name = "Not A Piano"
+ desc = "Something broke, contact coderbus."
+ interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT | INTERACT_ATOM_REQUIRES_DEXTERITY
+ var/can_play_unanchored = FALSE
+ var/list/allowed_instrument_ids = list("r3grand","r3harpsi","crharpsi","crgrand1","crbright1", "crichugan", "crihamgan","piano")
+ var/datum/song/song
+
+/obj/structure/musician/Initialize(mapload)
+ . = ..()
+ song = new(src, allowed_instrument_ids)
+ allowed_instrument_ids = null
+
+/obj/structure/musician/Destroy()
+ QDEL_NULL(song)
+ return ..()
+
+/obj/structure/musician/proc/should_stop_playing(mob/user)
+ if(!(anchored || can_play_unanchored))
+ return TRUE
+ if(!user)
+ return FALSE
+ return !user.canUseTopic(src, FALSE, TRUE, FALSE, FALSE) //can play with TK and while resting because fun.
+
+/obj/structure/musician/ui_interact(mob/user)
+ . = ..()
+ song.ui_interact(user)
+
+/obj/structure/musician/wrench_act(mob/living/user, obj/item/I)
+ default_unfasten_wrench(user, I, 40)
+ return TRUE
+
+/obj/structure/musician/piano
+ name = "space minimoog"
+ icon = 'icons/obj/musician.dmi'
+ icon_state = "minimoog"
+ anchored = TRUE
+ density = TRUE
+
+/obj/structure/musician/piano/unanchored
+ anchored = FALSE
+
+/obj/structure/musician/piano/Initialize(mapload)
+ . = ..()
+ if(prob(50) && icon_state == initial(icon_state))
+ name = "space minimoog"
+ desc = "This is a minimoog, like a space piano, but more spacey!"
+ icon_state = "minimoog"
+ else
+ name = "space piano"
+ desc = "This is a space piano, like a regular piano, but always in tune! Even if the musician isn't."
+ icon_state = "piano"
\ No newline at end of file
diff --git a/code/modules/integrated_electronics/core/analyzer.dm b/code/modules/integrated_electronics/core/analyzer.dm
deleted file mode 100644
index c3851cdb320a9..0000000000000
--- a/code/modules/integrated_electronics/core/analyzer.dm
+++ /dev/null
@@ -1,22 +0,0 @@
-/obj/item/integrated_electronics/analyzer
- name = "circuit analyzer"
- desc = "This tool can scan an assembly and generate code necessary to recreate it in a circuit printer."
- icon = 'icons/obj/assemblies/electronic_tools.dmi'
- icon_state = "analyzer"
- flags_1 = CONDUCT_1
- w_class = WEIGHT_CLASS_SMALL
-
-/obj/item/integrated_electronics/analyzer/afterattack(var/atom/A, var/mob/living/user)
- . = ..()
- if(istype(A, /obj/item/electronic_assembly))
- var/obj/item/electronic_assembly/EA = A
- if(EA.idlock)
- to_chat(user, "[A] is currently identity-locked and can't be analyzed.")
- return FALSE
-
- var/saved = "[A.name] analyzed! On circuit printers with cloning enabled, you may use the code below to clone the circuit:
[SScircuit.save_electronic_assembly(A)]"
- if(saved)
- to_chat(user, "You scan [A].")
- user << browse(saved, "window=circuit_scan;size=500x600;border=1;can_resize=1;can_close=1;can_minimize=1")
- else
- to_chat(user, "[A] is not complete enough to be encoded!")
diff --git a/code/modules/integrated_electronics/core/assemblies.dm b/code/modules/integrated_electronics/core/assemblies.dm
deleted file mode 100644
index b3f604daf194b..0000000000000
--- a/code/modules/integrated_electronics/core/assemblies.dm
+++ /dev/null
@@ -1,1048 +0,0 @@
-#define IC_MAX_SIZE_BASE 25
-#define IC_COMPLEXITY_BASE 75
-
-/obj/item/electronic_assembly
- name = "electronic assembly"
- obj_flags = CAN_BE_HIT | UNIQUE_RENAME
- desc = "A case designed for building small electronics."
- w_class = WEIGHT_CLASS_SMALL
- icon = 'icons/obj/assemblies/electronic_setups.dmi'
- icon_state = "setup_small"
- item_flags = NOBLUDGEON
- materials = list() // To be filled later
- datum_flags = DF_USE_TAG
- var/list/assembly_components = list()
- var/list/ckeys_allowed_to_scan = list() // Players who built the circuit can scan it as a ghost.
- var/max_components = IC_MAX_SIZE_BASE
- var/max_complexity = IC_COMPLEXITY_BASE
- var/opened = TRUE
- var/obj/item/stock_parts/cell/battery // Internal cell which most circuits need to work.
- var/cell_type = /obj/item/stock_parts/cell
- var/can_charge = TRUE //Can it be charged in a recharger?
- var/can_fire_equipped = FALSE //Can it fire/throw weapons when the assembly is being held?
- var/charge_sections = 4
- var/charge_tick = FALSE
- var/charge_delay = 4
- var/use_cyborg_cell = TRUE
- var/ext_next_use = 0
- var/atom/collw
- var/obj/item/card/id/access_card
- var/allowed_circuit_action_flags = IC_ACTION_COMBAT | IC_ACTION_LONG_RANGE //which circuit flags are allowed
- var/combat_circuits = 0 //number of combat cicuits in the assembly, used for diagnostic hud
- var/long_range_circuits = 0 //number of long range cicuits in the assembly, used for diagnostic hud
- var/prefered_hud_icon = "hudstat" // Used by the AR circuit to change the hud icon.
- var/creator // circuit creator if any
- var/static/next_assembly_id = 0
- var/sealed = FALSE
- var/datum/weakref/idlock = null
-
- hud_possible = list(DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_TRACK_HUD, DIAG_CIRCUIT_HUD) //diagnostic hud overlays
- max_integrity = 50
- pass_flags = 0
- armor = list("melee" = 50, "bullet" = 70, "laser" = 70, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 0, "acid" = 0)
- anchored = FALSE
- var/can_anchor = TRUE
- var/detail_color = COLOR_ASSEMBLY_BLACK
-
-/obj/item/electronic_assembly/New()
- ..()
- src.max_components = round(max_components)
- src.max_complexity = round(max_complexity)
-
-/obj/item/electronic_assembly/GenerateTag()
- tag = "assembly_[next_assembly_id++]"
-
-/obj/item/electronic_assembly/examine(mob/user)
- . = ..()
- if(can_anchor)
- to_chat(user, "The anchoring bolts [anchored ? "are" : "can be"] wrenched in place and the maintenance panel [opened ? "can be" : "is"] screwed in place.")
- else
- to_chat(user, "The maintenance panel [opened ? "can be" : "is"] screwed in place.")
-
- if((isobserver(user) && ckeys_allowed_to_scan[user.ckey]) || IsAdminGhost(user))
- to_chat(user, "You can scan this circuit.")
-
- for(var/obj/item/integrated_circuit/I in assembly_components)
- I.external_examine(user)
- if(opened)
- interact(user)
-
-/obj/item/electronic_assembly/proc/check_interactivity(mob/user)
- if(!istype(user, /mob))
- return
- return user.canUseTopic(src, BE_CLOSE)
-
-/obj/item/electronic_assembly/Bump(atom/AM)
- collw = AM
- .=..()
- if((istype(collw, /obj/machinery/door/airlock) || istype(collw, /obj/machinery/door/window)) && (!isnull(access_card)))
- var/obj/machinery/door/D = collw
- if(D.check_access(access_card))
- D.open()
-
-/obj/item/electronic_assembly/Initialize()
- .=..()
- START_PROCESSING(SScircuit, src)
- materials[/datum/material/iron] = round((max_complexity + max_components) / 4) * SScircuit.cost_multiplier
-
- //sets up diagnostic hud view
- prepare_huds()
- for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds)
- diag_hud.add_to_hud(src)
- diag_hud_set_circuithealth()
- diag_hud_set_circuitcell()
- diag_hud_set_circuitstat()
- diag_hud_set_circuittracking()
-
- access_card = new /obj/item/card/id(src)
-
-/obj/item/electronic_assembly/Destroy()
- STOP_PROCESSING(SScircuit, src)
- for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds)
- diag_hud.remove_from_hud(src)
- QDEL_NULL(access_card)
- return ..()
-
-/obj/item/electronic_assembly/process()
- handle_idle_power()
- check_pulling()
-
- //updates diagnostic hud
- diag_hud_set_circuithealth()
- diag_hud_set_circuitcell()
-
-/obj/item/electronic_assembly/proc/handle_idle_power()
-
- // First we generate power.
- for(var/obj/item/integrated_circuit/passive/power/P in assembly_components)
- P.make_energy()
-
- // Now spend it.
- for(var/obj/item/integrated_circuit/I in assembly_components)
- if(I.power_draw_idle)
- if(!draw_power(I.power_draw_idle))
- I.power_fail()
-
-/obj/item/electronic_assembly/interact(mob/user, circuit)
- ui_interact(user, circuit)
-
-/obj/item/electronic_assembly/ui_interact(mob/user, obj/item/integrated_circuit/circuit_pins)
- . = ..()
- if(!check_interactivity(user))
- return
-
- var/total_part_size = return_total_size()
- var/total_complexity = return_total_complexity()
- var/datum/browser/popup = new(user, "scannernew", name, 800, 630) // Set up the popup browser window
- popup.add_stylesheet("scannernew", 'html/browser/assembly_ui.css')
-
- var/HTML = "[name]\
-
\
- Refresh | Rename \
- [total_part_size]/[max_components] ([round((total_part_size / max_components) * 100, 0.1)]%) space taken up in the assembly. \
- [total_complexity]/[max_complexity] ([round((total_complexity / max_complexity) * 100, 0.1)]%) maximum complexity. "
- if(battery)
- HTML += "[round(battery.charge, 0.1)]/[battery.maxcharge] ([round(battery.percent(), 0.1)]%) cell charge. Remove"
- else
- HTML += "No power cell detected!"
- HTML += "
"
-
-
- //Getting the newest viewed circuit to compare with new circuit list
- if(!circuit_pins || !istype(circuit_pins,/obj/item/integrated_circuit) || !(circuit_pins in assembly_components))
- if(assembly_components.len > 0)
- circuit_pins = assembly_components[1]
-
-
- HTML += "
Components: "
-
- var/builtin_components = ""
- var/removables = ""
- var/remove_num = 1
-
- for(var/obj/item/integrated_circuit/circuit in assembly_components)
- if(!circuit.removable)
- if(circuit == circuit_pins)
- builtin_components += "[circuit.displayed_name] "
- else
- builtin_components += "[circuit.displayed_name] "
-
- // Non-inbuilt circuits come after inbuilt circuits
- else
- removables += "[remove_num]. | "
- if(circuit == circuit_pins)
- removables += "[circuit.displayed_name] "
- else
- removables += "[circuit.displayed_name] "
- remove_num++
-
- // Put removable circuits (if any) in separate categories from non-removable
- if(builtin_components)
- HTML += " Built in: [builtin_components] Removable: "
-
- HTML += removables
-
- HTML += "
\
- Complexity: [circuit_pins.complexity]\
- Cooldown per use: [circuit_pins.cooldown_per_use/10] sec"
- if(circuit_pins.ext_cooldown)
- HTML += " External manipulation cooldown: [circuit_pins.ext_cooldown/10] sec"
- if(circuit_pins.power_draw_idle)
- HTML += " Power Draw: [circuit_pins.power_draw_idle] W (Idle)"
- if(circuit_pins.power_draw_per_use)
- HTML += " Power Draw: [circuit_pins.power_draw_per_use] W (Active)" // Borgcode says that powercells' checked_use() takes joules as input.
- HTML += " [circuit_pins.extended_desc]
"
-
-
- HTML += "
"
-
- popup.set_content(HTML)
- popup.open()
-
-/obj/item/electronic_assembly/Topic(href, href_list)
- if(..())
- return 1
-
- if(href_list["ghostscan"])
- if((isobserver(usr) && ckeys_allowed_to_scan[usr.ckey]) || IsAdminGhost(usr))
- if(assembly_components.len)
- var/saved = "On circuit printers with cloning enabled, you may use the code below to clone the circuit:
[SScircuit.save_electronic_assembly(src)]"
- usr << browse(saved, "window=circuit_scan;size=500x600;border=1;can_resize=1;can_close=1;can_minimize=1")
- else
- to_chat(usr, "The circuit is empty!")
- return
-
- if(!check_interactivity(usr))
- return
-
- if(href_list["rename"])
- rename(usr)
-
- if(href_list["remove_cell"])
- if(!battery)
- to_chat(usr, "There's no power cell to remove from \the [src].")
- else
- battery.forceMove(drop_location())
- playsound(src, 'sound/items/Crowbar.ogg', 50, 1)
- to_chat(usr, "You pull \the [battery] out of \the [src]'s power supplier.")
- battery = null
- diag_hud_set_circuitstat() //update diagnostic hud
-
- var/obj/item/integrated_circuit/component
-
- if(href_list["component"])
- component = locate(href_list["component"]) in assembly_components
-
- if(!component)
- return
-
-
- if(href_list["scan"])
- var/obj/held_item = usr.get_active_held_item()
- if(istype(held_item, /obj/item/integrated_electronics/debugger))
- var/obj/item/integrated_electronics/debugger/D = held_item
- if(D.accepting_refs)
- D.afterattack(component, usr, TRUE)
- else
- to_chat(usr, "The debugger's 'ref scanner' needs to be on.")
- else
- to_chat(usr, "You need a debugger set to 'ref' mode to do that.")
-
- // Builtin components are not supposed to be removed or rearranged
- if(!component.removable)
- return
-
- add_allowed_scanner(usr.ckey)
-
- // Find the position of a first removable component
- var/first_removable_pos = 0
- for(var/i in assembly_components)
- first_removable_pos++
- var/obj/item/integrated_circuit/temp_component = i
- if(temp_component.removable)
- break
-
- if(href_list["remove"])
- if(try_remove_component(component, usr))
- component = null
-
- if(href_list["rename_component"])
- component.rename_component(usr)
- if(component.assembly)
- component.assembly.add_allowed_scanner(usr.ckey)
-
- if(href_list["interact"])
- var/obj/item/I = usr.get_active_held_item()
- if(istype(I))
- I.melee_attack_chain(usr, component)
- else
- component.attack_hand(usr)
-
- // Adjust the position
- if(href_list["change_pos"])
- var/new_pos = max(input(usr,"Write the new number","New position") as num,1)
-
- if(new_pos > assembly_components.len)
- new_pos = assembly_components.len
-
- if(new_pos < first_removable_pos)
- new_pos = first_removable_pos
-
- assembly_components.Remove(component)
- assembly_components.Insert(new_pos, component)
-
- interact(usr, component) // To refresh the UI.
-
-/obj/item/electronic_assembly/pickup(mob/living/user)
- . = ..()
- //update diagnostic hud when picked up, true is used to force the hud to be hidden
- diag_hud_set_circuithealth(TRUE)
- diag_hud_set_circuitcell(TRUE)
- diag_hud_set_circuitstat(TRUE)
- diag_hud_set_circuittracking(TRUE)
-
-/obj/item/electronic_assembly/dropped(mob/user)
- . = ..()
- //update diagnostic hud when dropped
- diag_hud_set_circuithealth()
- diag_hud_set_circuitcell()
- diag_hud_set_circuitstat()
- diag_hud_set_circuittracking()
-
-/obj/item/electronic_assembly/proc/rename()
- var/mob/M = usr
- if(!check_interactivity(M))
- return
-
- var/input = reject_bad_name(input("What do you want to name this?", "Rename", src.name) as null|text, TRUE)
- if(!check_interactivity(M))
- return
- if(src && input)
- to_chat(M, "The machine now has a label reading '[input]'.")
- name = input
-
-/obj/item/electronic_assembly/proc/add_allowed_scanner(ckey)
- ckeys_allowed_to_scan[ckey] = TRUE
-
-/obj/item/electronic_assembly/proc/can_move()
- return FALSE
-
-/obj/item/electronic_assembly/update_icon()
- if(opened)
- icon_state = initial(icon_state) + "-open"
- else
- icon_state = initial(icon_state)
- cut_overlays()
- if(detail_color == COLOR_ASSEMBLY_BLACK) //Black colored overlay looks almost but not exactly like the base sprite, so just cut the overlay and avoid it looking kinda off.
- return
- var/mutable_appearance/detail_overlay = mutable_appearance('icons/obj/assemblies/electronic_setups.dmi', "[icon_state]-color")
- detail_overlay.color = detail_color
- add_overlay(detail_overlay)
-
-/obj/item/electronic_assembly/proc/return_total_complexity()
- var/returnvalue = 0
- for(var/obj/item/integrated_circuit/part in assembly_components)
- returnvalue += part.complexity
- return(returnvalue)
-
-/obj/item/electronic_assembly/proc/return_total_size()
- var/returnvalue = 0
- for(var/obj/item/integrated_circuit/part in assembly_components)
- returnvalue += part.size
- return(returnvalue)
-
-// Returns true if the circuit made it inside.
-/obj/item/electronic_assembly/proc/try_add_component(obj/item/integrated_circuit/IC, mob/user)
- if(!opened)
- to_chat(user, "\The [src]'s hatch is closed, you can't put anything inside.")
- return FALSE
-
- if(IC.w_class > w_class)
- to_chat(user, "\The [IC] is way too big to fit into \the [src].")
- return FALSE
-
- var/total_part_size = return_total_size()
- var/total_complexity = return_total_complexity()
-
- if(IC.max_allowed)
- var/current_components
- for(var/obj/item/integrated_circuit/component as anything in assembly_components)
- if(component.type == IC.type)
- current_components++
- if(current_components >= IC.max_allowed)
- to_chat(user, "You can't seem to add the '[IC]', as there are too many installed already.")
- return FALSE
-
- if((total_part_size + IC.size) > max_components)
- to_chat(user, "You can't seem to add the '[IC]', as there's insufficient space.")
- return FALSE
- if((total_complexity + IC.complexity) > max_complexity)
- to_chat(user, "You can't seem to add the '[IC]', since this setup's too complicated for the case.")
- return FALSE
- if((allowed_circuit_action_flags & IC.action_flags) != IC.action_flags)
- to_chat(user, "You can't seem to add the '[IC]', since the case doesn't support the circuit type.")
- return FALSE
-
- if(!user.transferItemToLoc(IC, src))
- return FALSE
-
- to_chat(user, "You slide [IC] inside [src].")
- playsound(src, 'sound/items/Deconstruct.ogg', 50, 1)
- add_allowed_scanner(user.ckey)
- investigate_log("had [IC]([IC.type]) inserted by [key_name(user)].", INVESTIGATE_CIRCUIT)
-
- add_component(IC)
- return TRUE
-
-
-// Actually puts the circuit inside, doesn't perform any checks.
-/obj/item/electronic_assembly/proc/add_component(obj/item/integrated_circuit/component)
- component.forceMove(get_object())
- component.assembly = src
- assembly_components |= component
-
- //increment numbers for diagnostic hud
- if(component.action_flags & IC_ACTION_COMBAT)
- combat_circuits += 1;
- if(component.action_flags & IC_ACTION_LONG_RANGE)
- long_range_circuits += 1;
-
- //diagnostic hud update
- diag_hud_set_circuitstat()
- diag_hud_set_circuittracking()
-
-
-/obj/item/electronic_assembly/proc/try_remove_component(obj/item/integrated_circuit/IC, mob/user, silent)
- if(!opened)
- if(!silent)
- to_chat(user, "[src]'s hatch is closed, so you can't fiddle with the internal components.")
- return FALSE
-
- if(!IC.removable)
- if(!silent)
- to_chat(user, "[src] is permanently attached to the case.")
- return FALSE
-
- remove_component(IC)
- if(!silent)
- to_chat(user, "You pop \the [IC] out of the case, and slide it out.")
- playsound(src, 'sound/items/crowbar.ogg', 50, 1)
- user.put_in_hands(IC)
- add_allowed_scanner(user.ckey)
- investigate_log("had [IC]([IC.type]) removed by [key_name(user)].", INVESTIGATE_CIRCUIT)
-
- return TRUE
-
-// Actually removes the component, doesn't perform any checks.
-/obj/item/electronic_assembly/proc/remove_component(obj/item/integrated_circuit/component)
- component.disconnect_all()
- component.forceMove(drop_location())
- component.assembly = null
-
- assembly_components -= component
-
- //decrement numbers for diagnostic hud
- if(component.action_flags & IC_ACTION_COMBAT)
- combat_circuits -= 1;
- if(component.action_flags & IC_ACTION_LONG_RANGE)
- long_range_circuits -= 1;
-
- //diagnostic hud update
- diag_hud_set_circuitstat()
- diag_hud_set_circuittracking()
-
-
-/obj/item/electronic_assembly/afterattack(atom/target, mob/user, proximity)
- . = ..()
- for(var/obj/item/integrated_circuit/input/S in assembly_components)
- if(S.sense(target,user,proximity))
- visible_message(" [user] waves [src] around [target].")
-
-
-/obj/item/electronic_assembly/screwdriver_act(mob/living/user, obj/item/I)
- if(sealed)
- to_chat(user,"The assembly is sealed. Any attempt to force it open would break it.")
- return FALSE
- if(..())
- return TRUE
- I.play_tool_sound(src)
- opened = !opened
- to_chat(user, "You [opened ? "open" : "close"] the maintenance hatch of [src].")
- update_icon()
- return TRUE
-
-/obj/item/electronic_assembly/welder_act(mob/living/user, obj/item/I)
- var/type_to_use
-
- if(!sealed)
- type_to_use = input("What would you like to do?","[src] type setting") as null|anything in list("repair", "seal")
- else
- type_to_use = input("What would you like to do?","[src] type setting") as null|anything in list("repair", "unseal")
-
- switch(type_to_use)
- if("repair")
- to_chat(world,"Integrity: [obj_integrity] / [max_integrity]")
- if(obj_integrity < max_integrity)
- obj_integrity = min(obj_integrity + 20,max_integrity)
- to_chat(world,"Integrity: [obj_integrity] / [max_integrity]")
- to_chat(user,"You fix the dents and scratches of the assembly.")
- to_chat(world,user)
- return TRUE
-
- else
- to_chat(user,"The assembly is already in impeccable condition.")
- return FALSE
-
- if("seal")
- if(!opened)
- sealed = TRUE
- if(I.use_tool(src, user, 50, volume=100, amount=3))
- to_chat(user,"You seal the assembly, making it impossible to be opened.")
- return TRUE
-
- else
- to_chat(user,"You need to close the assembly first before sealing it indefinitely!")
- return FALSE
-
- if("unseal")
- to_chat(user,"You start unsealing the assembly carefully...")
- if(I.use_tool(src, user, 50, volume=250, amount=3))
- for(var/obj/item/integrated_circuit/IC in assembly_components)
- if(prob(50))
- IC.disconnect_all()
-
- to_chat(user,"You unsealed the assembly.")
- sealed = FALSE
- return TRUE
-
-/obj/item/electronic_assembly/attackby(obj/item/I, mob/living/user)
- if(can_anchor && default_unfasten_wrench(user, I, 20))
- return
-
- // ID-Lock part: check if we have an id-lock and only lock if we're not trying to get values from it, to prevent accidents
- if(istype(I, /obj/item/integrated_electronics/debugger))
- var/obj/item/integrated_electronics/debugger/debugger = I
- if(debugger.idlock)
- // check if unlocked to lock
- if(!idlock)
- idlock = debugger.idlock
- to_chat(user,"You lock \the [src].")
-
- //if locked, unlock if ids match
- else
- if(idlock.resolve() == debugger.idlock.resolve())
- idlock = null
- to_chat(user,"You unlock \the [src].")
-
- else
- to_chat(user,"The scanned ID doesn't match with \the [src]'s lock.")
-
- debugger.idlock = null
- return
-
- if(istype(I, /obj/item/integrated_circuit))
- if(!user.canUnEquip(I))
- return FALSE
- if(try_add_component(I, user))
- return TRUE
- else
- for(var/obj/item/integrated_circuit/input/S in assembly_components)
- S.attackby_react(I,user,user.a_intent)
- return ..()
-
- else if(I.tool_behaviour == TOOL_MULTITOOL || istype(I, /obj/item/integrated_electronics/wirer) || istype(I, /obj/item/integrated_electronics/debugger))
- if(opened)
- interact(user)
- return TRUE
- else
- to_chat(user, "[src]'s hatch is closed, so you can't fiddle with the internal components.")
- for(var/obj/item/integrated_circuit/input/S in assembly_components)
- S.attackby_react(I,user,user.a_intent)
- return ..()
-
- else if(istype(I, /obj/item/stock_parts/cell))
- if(!opened)
- to_chat(user, "[src]'s hatch is closed, so you can't access \the [src]'s power supplier.")
- for(var/obj/item/integrated_circuit/input/S in assembly_components)
- S.attackby_react(I,user,user.a_intent)
- return ..()
- if(battery)
- to_chat(user, "[src] already has \a [battery] installed. Remove it first if you want to replace it.")
- for(var/obj/item/integrated_circuit/input/S in assembly_components)
- S.attackby_react(I,user,user.a_intent)
- return ..()
- I.forceMove(src)
- battery = I
- diag_hud_set_circuitstat() //update diagnostic hud
- playsound(get_turf(src), 'sound/items/Deconstruct.ogg', 50, 1)
- to_chat(user, "You slot the [I] inside \the [src]'s power supplier.")
- return TRUE
-
- else if(istype(I, /obj/item/integrated_electronics/detailer))
- var/obj/item/integrated_electronics/detailer/D = I
- detail_color = D.detail_color
- update_icon()
-
- else
- if(user.a_intent != INTENT_HELP)
- return ..()
- var/list/input_selection = list()
- //Check all the components asking for an input
- for(var/obj/item/integrated_circuit/input in assembly_components)
- if((input.demands_object_input && opened) || (input.demands_object_input && input.can_input_object_when_closed))
- var/i = 0
- //Check if there is another component with the same name and append a number for identification
- for(var/s in input_selection)
- var/obj/item/integrated_circuit/s_circuit = input_selection[s] //The for-loop iterates the keys of the associative list.
- if(s_circuit.name == input.name && s_circuit.displayed_name == input.displayed_name && s_circuit != input)
- i++
- var/disp_name= "[input.displayed_name] \[[input]\]"
- if(i)
- disp_name += " ([i+1])"
- //Associative lists prevent me from needing another list and using a Find proc
- input_selection[disp_name] = input
-
- var/obj/item/integrated_circuit/choice
- if(input_selection)
- if(input_selection.len == 1)
- choice = input_selection[input_selection[1]]
- else
- var/selection = input(user, "Where do you want to insert that item?", "Interaction") as null|anything in input_selection
- if(!check_interactivity(user))
- return ..()
- if(selection)
- choice = input_selection[selection]
- if(choice)
- choice.additem(I, user)
- for(var/obj/item/integrated_circuit/input/S in assembly_components)
- S.attackby_react(I,user,user.a_intent)
- return ..()
-
-
-/obj/item/electronic_assembly/attack_self(mob/user)
- if(!check_interactivity(user))
- return
- if(opened)
- interact(user)
-
- var/list/input_selection = list()
- //Check all the components asking for an input
- for(var/obj/item/integrated_circuit/input/input in assembly_components)
- if(input.can_be_asked_input)
- var/i = 0
- //Check if there is another component with the same name and append a number for identification
- for(var/s in input_selection)
- var/obj/item/integrated_circuit/s_circuit = input_selection[s] //The for-loop iterates the keys of an associative list.
- if(s_circuit.name == input.name && s_circuit.displayed_name == input.displayed_name && s_circuit != input)
- i++
- var/disp_name= "[input.displayed_name] \[[input]\]"
- if(i)
- disp_name += " ([i+1])"
- //Associative lists prevent me from needing another list and using a Find proc
- input_selection[disp_name] = input
-
- var/obj/item/integrated_circuit/input/choice
-
-
- if(input_selection)
- if(input_selection.len ==1)
- choice = input_selection[input_selection[1]]
- else
- var/selection = input(user, "What do you want to interact with?", "Interaction") as null|anything in input_selection
- if(!check_interactivity(user))
- return
- if(selection)
- choice = input_selection[selection]
-
- if(choice)
- choice.ask_for_input(user)
-
-/obj/item/electronic_assembly/emp_act(severity)
- . = ..()
- if(. & EMP_PROTECT_CONTENTS)
- return
- for(var/I in src)
- var/atom/movable/AM = I
- AM.emp_act(severity)
-
-// Returns true if power was successfully drawn.
-/obj/item/electronic_assembly/proc/draw_power(amount)
- if(battery && battery.use(amount * GLOB.CELLRATE))
- return TRUE
- return FALSE
-
-// Ditto for giving.
-/obj/item/electronic_assembly/proc/give_power(amount)
- if(battery && battery.give(amount * GLOB.CELLRATE))
- return TRUE
- return FALSE
-
-/obj/item/electronic_assembly/Moved(oldLoc, dir)
- for(var/I in assembly_components)
- var/obj/item/integrated_circuit/IC = I
- IC.ext_moved(oldLoc, dir)
- if(light) //Update lighting objects (From light circuits).
- update_light()
-
-/obj/item/electronic_assembly/stop_pulling()
- for(var/I in assembly_components)
- var/obj/item/integrated_circuit/IC = I
- IC.stop_pulling()
- ..()
-
-
-// Returns the object that is supposed to be used in attack messages, location checks, etc.
-// Override in children for special behavior.
-/obj/item/electronic_assembly/proc/get_object()
- return src
-
-// Returns the location to be used for dropping items.
-// Same as the regular drop_location(), but with checks being run on acting_object if necessary.
-/obj/item/integrated_circuit/drop_location()
- var/atom/movable/acting_object = get_object()
-
- // plz no infinite loops
- if(acting_object == src)
- return ..()
-
- return acting_object.drop_location()
-
-/obj/item/electronic_assembly/attack_tk(mob/user)
- if(anchored)
- return
- ..()
-
-/obj/item/electronic_assembly/attack_hand(mob/user)
- if(anchored)
- attack_self(user)
- return
- ..()
-
-/obj/item/electronic_assembly/default //The /default electronic_assemblys are to allow the introduction of the new naming scheme without breaking old saves.
- name = "type-a electronic assembly"
-
-/obj/item/electronic_assembly/calc
- name = "type-b electronic assembly"
- icon_state = "setup_small_calc"
- desc = "A case designed for building small electronics. This one resembles a pocket calculator."
-
-/obj/item/electronic_assembly/clam
- name = "type-c electronic assembly"
- icon_state = "setup_small_clam"
- desc = "A case designed for building small electronics. This one has a clamshell design."
-
-/obj/item/electronic_assembly/simple
- name = "type-d electronic assembly"
- icon_state = "setup_small_simple"
- desc = "A case designed for building small electronics. This one has a simple design."
-
-/obj/item/electronic_assembly/hook
- name = "type-e electronic assembly"
- icon_state = "setup_small_hook"
- desc = "A case designed for building small electronics. This one looks like it has a belt clip, but it's purely decorative."
-
-/obj/item/electronic_assembly/pda
- name = "type-f electronic assembly"
- icon_state = "setup_small_pda"
- desc = "A case designed for building small electronics. This one resembles a PDA."
-
-/obj/item/electronic_assembly/small
- name = "electronic device"
- icon_state = "setup_device"
- desc = "A case designed for building tiny-sized electronics."
- w_class = WEIGHT_CLASS_TINY
- max_components = IC_MAX_SIZE_BASE / 2
- max_complexity = IC_COMPLEXITY_BASE / 2
-
-/obj/item/electronic_assembly/small/default
- name = "type-a electronic device"
-
-/obj/item/electronic_assembly/small/cylinder
- name = "type-b electronic device"
- icon_state = "setup_device_cylinder"
- desc = "A case designed for building tiny-sized electronics. This one has a cylindrical design."
-
-/obj/item/electronic_assembly/small/scanner
- name = "type-c electronic device"
- icon_state = "setup_device_scanner"
- desc = "A case designed for building tiny-sized electronics. This one has a scanner-like design."
-
-/obj/item/electronic_assembly/small/hook
- name = "type-d electronic device"
- icon_state = "setup_device_hook"
- desc = "A case designed for building tiny-sized electronics. This one looks like it has a belt clip, but it's purely decorative."
-
-/obj/item/electronic_assembly/small/box
- name = "type-e electronic device"
- icon_state = "setup_device_box"
- desc = "A case designed for building tiny-sized electronics. This one has a box design."
-
-/obj/item/electronic_assembly/medium
- name = "electronic mechanism"
- icon_state = "setup_medium"
- desc = "A case designed for building medium-sized electonics."
- w_class = WEIGHT_CLASS_NORMAL
- max_components = IC_MAX_SIZE_BASE * 2
- max_complexity = IC_COMPLEXITY_BASE * 2
-
-/obj/item/electronic_assembly/medium/default
- name = "type-a electronic mechanism"
-
-/obj/item/electronic_assembly/medium/box
- name = "type-b electronic mechanism"
- icon_state = "setup_medium_box"
- desc = "A case designed for building medium-sized electonics. This one has a box design."
-
-/obj/item/electronic_assembly/medium/clam
- name = "type-c electronic mechanism"
- icon_state = "setup_medium_clam"
- desc = "A case designed for building medium-sized electonics. This one has a clamshell design."
-
-/obj/item/electronic_assembly/medium/medical
- name = "type-d electronic mechanism"
- icon_state = "setup_medium_med"
- desc = "A case designed for building medium-sized electonics. This one resembles medical apparatus."
-
-/obj/item/electronic_assembly/medium/gun
- name = "type-e electronic mechanism"
- icon_state = "setup_medium_gun"
- item_state = "circuitgun"
- desc = "A case designed for building medium-sized electonics. This one resembles a gun, or some type of tool, if you're feeling optimistic. It can fire guns and throw items while the user is holding it."
- lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi'
- can_fire_equipped = TRUE
-
-/obj/item/electronic_assembly/medium/radio
- name = "type-f electronic mechanism"
- icon_state = "setup_medium_radio"
- desc = "A case designed for building medium-sized electonics. This one resembles an old radio."
-
-/obj/item/electronic_assembly/large
- name = "electronic machine"
- icon_state = "setup_large"
- desc = "A case designed for building large electronics."
- w_class = WEIGHT_CLASS_BULKY
- max_components = IC_MAX_SIZE_BASE * 4
- max_complexity = IC_COMPLEXITY_BASE * 4
-
-/obj/item/electronic_assembly/large/default
- name = "type-a electronic machine"
-
-/obj/item/electronic_assembly/large/scope
- name = "type-b electronic machine"
- icon_state = "setup_large_scope"
- desc = "A case designed for building large electronics. This one resembles an oscilloscope."
-
-/obj/item/electronic_assembly/large/terminal
- name = "type-c electronic machine"
- icon_state = "setup_large_terminal"
- desc = "A case designed for building large electronics. This one resembles a computer terminal."
-
-/obj/item/electronic_assembly/large/arm
- name = "type-d electronic machine"
- icon_state = "setup_large_arm"
- desc = "A case designed for building large electronics. This one resembles a robotic arm."
-
-/obj/item/electronic_assembly/large/tall
- name = "type-e electronic machine"
- icon_state = "setup_large_tall"
- desc = "A case designed for building large electronics. This one has a tall design."
-
-/obj/item/electronic_assembly/large/industrial
- name = "type-f electronic machine"
- icon_state = "setup_large_industrial"
- desc = "A case designed for building mobile electronics. This one resembles industrial machinery."
-
-/obj/item/electronic_assembly/drone
- name = "electronic drone"
- icon_state = "setup_drone"
- desc = "A case designed for building mobile electronics."
- w_class = WEIGHT_CLASS_BULKY
- max_components = IC_MAX_SIZE_BASE * 3
- max_complexity = IC_COMPLEXITY_BASE * 3
- allowed_circuit_action_flags = IC_ACTION_MOVEMENT | IC_ACTION_COMBAT | IC_ACTION_LONG_RANGE
- can_anchor = FALSE
-
-/obj/item/electronic_assembly/drone/can_move()
- return TRUE
-
-/obj/item/electronic_assembly/drone/default
- name = "type-a electronic drone"
-
-/obj/item/electronic_assembly/drone/arms
- name = "type-b electronic drone"
- icon_state = "setup_drone_arms"
- desc = "A case designed for building mobile electronics. This one is armed and dangerous."
-
-/obj/item/electronic_assembly/drone/secbot
- name = "type-c electronic drone"
- icon_state = "setup_drone_secbot"
- desc = "A case designed for building mobile electronics. This one resembles a Securitron."
-
-/obj/item/electronic_assembly/drone/medbot
- name = "type-d electronic drone"
- icon_state = "setup_drone_medbot"
- desc = "A case designed for building mobile electronics. This one resembles a Medibot."
-
-/obj/item/electronic_assembly/drone/genbot
- name = "type-e electronic drone"
- icon_state = "setup_drone_genbot"
- desc = "A case designed for building mobile electronics. This one has a generic bot design."
-
-/obj/item/electronic_assembly/drone/android
- name = "type-f electronic drone"
- icon_state = "setup_drone_android"
- desc = "A case designed for building mobile electronics. This one has a hominoid design."
-
-/obj/item/electronic_assembly/wallmount
- name = "wall-mounted electronic assembly"
- icon_state = "setup_wallmount_medium"
- desc = "A case designed for building medium-sized electronics.. It has a magnetized backing to allow it to stick to walls, but you'll still need to wrench the anchoring bolts in place to keep it on."
- w_class = WEIGHT_CLASS_NORMAL
- max_components = IC_MAX_SIZE_BASE * 2
- max_complexity = IC_COMPLEXITY_BASE * 2
-
-/obj/item/electronic_assembly/wallmount/heavy
- name = "heavy wall-mounted electronic assembly"
- icon_state = "setup_wallmount_large"
- desc = "A case designed for building large electronics. It has a magnetized backing to allow it to stick to walls, but you'll still need to wrench the anchoring bolts in place to keep it on."
- w_class = WEIGHT_CLASS_BULKY
- max_components = IC_MAX_SIZE_BASE * 4
- max_complexity = IC_COMPLEXITY_BASE * 4
-
-/obj/item/electronic_assembly/wallmount/light
- name = "light wall-mounted electronic assembly"
- icon_state = "setup_wallmount_small"
- desc = "A case designed for building small electronics. It has a magnetized backing to allow it to stick to walls, but you'll still need to wrench the anchoring bolts in place to keep it on."
- w_class = WEIGHT_CLASS_SMALL
- max_components = IC_MAX_SIZE_BASE
- max_complexity = IC_COMPLEXITY_BASE
-
-/obj/item/electronic_assembly/wallmount/tiny
- name = "tiny wall-mounted electronic assembly"
- icon_state = "setup_wallmount_tiny"
- desc = "It's a case, for building tiny electronics with. It has a magnetized backing to allow it to stick to walls, but you'll still need to wrench the anchoring bolts in place to keep it on."
- w_class = WEIGHT_CLASS_TINY
- max_components = IC_MAX_SIZE_BASE / 2
- max_complexity = IC_COMPLEXITY_BASE / 2
-
-/obj/item/electronic_assembly/wallmount/proc/mount_assembly(turf/on_wall, mob/user) //Yeah, this is admittedly just an abridged and kitbashed version of the wallframe attach procs.
- if(get_dist(on_wall,user)>1)
- return
- var/ndir = get_dir(on_wall, user)
- if(!(ndir in GLOB.cardinals))
- return
- var/turf/T = get_turf(user)
- if(!isfloorturf(T))
- to_chat(user, "You cannot place [src] on this spot!")
- return
- if(gotwallitem(T, ndir))
- to_chat(user, "There's already an item on this wall!")
- return
- playsound(src.loc, 'sound/machines/click.ogg', 75, 1)
- user.visible_message("[user.name] attaches [src] to the wall.",
- "You attach [src] to the wall.",
- "You hear clicking.")
- user.dropItemToGround(src)
- switch(ndir)
- if(NORTH)
- pixel_y = -31
- if(SOUTH)
- pixel_y = 31
- if(EAST)
- pixel_x = -31
- if(WEST)
- pixel_x = 31
diff --git a/code/modules/integrated_electronics/core/debugger.dm b/code/modules/integrated_electronics/core/debugger.dm
deleted file mode 100644
index 867ae9ad460bb..0000000000000
--- a/code/modules/integrated_electronics/core/debugger.dm
+++ /dev/null
@@ -1,108 +0,0 @@
-/obj/item/integrated_electronics/debugger
- name = "circuit debugger"
- desc = "This small tool allows one working with custom machinery to directly set data to a specific pin, useful for writing \
- settings to specific circuits, or for debugging purposes. It can also pulse activation pins."
- icon = 'icons/obj/assemblies/electronic_tools.dmi'
- icon_state = "debugger"
- flags_1 = CONDUCT_1
- item_flags = NOBLUDGEON
- w_class = WEIGHT_CLASS_SMALL
- var/data_to_write = null
- var/accepting_refs = FALSE
- var/copy_values = FALSE
- var/copy_id = FALSE
- var/datum/weakref/idlock = null
-
-/obj/item/integrated_electronics/debugger/attack_self(mob/user)
- var/type_to_use = input("Please choose a type to use.","[src] type setting") as null|anything in list("string","number","ref","copy","null","id lock")
- if(!user.IsAdvancedToolUser())
- return
-
- var/new_data = null
- switch(type_to_use)
- if("string")
- accepting_refs = FALSE
- copy_values = FALSE
- copy_id = FALSE
- new_data = stripped_input(user, "Now type in a string.","[src] string writing", no_trim = TRUE)
- if(istext(new_data) && user.IsAdvancedToolUser())
- data_to_write = new_data
- to_chat(user, "You set \the [src]'s memory to \"[new_data]\".")
- if("number")
- accepting_refs = FALSE
- copy_values = FALSE
- copy_id = FALSE
- new_data = input(user, "Now type in a number.","[src] number writing") as null|num
- if(isnum_safe(new_data) && user.IsAdvancedToolUser())
- data_to_write = new_data
- to_chat(user, "You set \the [src]'s memory to [new_data].")
- if("ref")
- accepting_refs = TRUE
- copy_values = FALSE
- copy_id = FALSE
- to_chat(user, "You turn \the [src]'s ref scanner on. Slide it across \
- an object for a ref of that object to save it in memory.")
- if("copy")
- accepting_refs = FALSE
- copy_values = TRUE
- copy_id = FALSE
- to_chat(user, "You turn \the [src]'s value copier on. Use it on a pin \
- to save its current value in memory.")
- if("null")
- data_to_write = null
- copy_values = FALSE
- to_chat(user, "You set \the [src]'s memory to absolutely nothing.")
- if("id lock")
- accepting_refs = FALSE
- copy_values = FALSE
- copy_id = TRUE
- to_chat(user, "You turn \the [src]'s id card scanner on. Use your own card \
- to store the identity and id-lock an assembly.")
-
-/obj/item/integrated_electronics/debugger/afterattack(atom/target, mob/living/user, proximity)
- . = ..()
- if(accepting_refs && proximity)
- data_to_write = WEAKREF(target)
- visible_message("[user] slides \a [src]'s over \the [target].")
- to_chat(user, "You set \the [src]'s memory to a reference to [target.name] \[Ref\]. The ref scanner is \
- now off.")
- accepting_refs = FALSE
-
- else if(copy_id && proximity)
- if(istype(target,/obj/item/card/id))
- idlock = WEAKREF(target)
- to_chat(user, "You set \the [src]'s card memory to [target.name]. The id card scanner is \
- now off.")
-
- else
- to_chat(user, "You turn the id card scanner is off.")
-
- copy_id = FALSE
- return
-
-/obj/item/integrated_electronics/debugger/proc/write_data(var/datum/integrated_io/io, mob/user)
- //If the pin can take data:
- if(io.io_type == DATA_CHANNEL)
- //If the debugger is set to copy, copy the data in the pin onto it
- if(copy_values)
- data_to_write = io.data
- to_chat(user, "You let the debugger copy the data.")
- copy_values = FALSE
- return
-
- //Else, write the data to the pin
- io.write_data_to_pin(data_to_write)
- var/data_to_show = data_to_write
- //This is only to convert a weakref into a name for better output
- if(isweakref(data_to_write))
- var/datum/weakref/w = data_to_write
- var/atom/A = w.resolve()
- data_to_show = A.name
- to_chat(user, "You write '[data_to_write ? data_to_show : "NULL"]' to the '[io]' pin of \the [io.holder].")
-
- //If the pin can only be pulsed
- else if(io.io_type == PULSE_CHANNEL)
- io.holder.check_then_do_work(io.ord,ignore_power = TRUE)
- to_chat(user, "You pulse \the [io.holder]'s [io].")
-
- io.holder.interact(user) // This is to update the UI.
diff --git a/code/modules/integrated_electronics/core/detailer.dm b/code/modules/integrated_electronics/core/detailer.dm
deleted file mode 100644
index 33f7ef96add03..0000000000000
--- a/code/modules/integrated_electronics/core/detailer.dm
+++ /dev/null
@@ -1,48 +0,0 @@
-/obj/item/integrated_electronics/detailer
- name = "assembly detailer"
- desc = "A combination autopainter and flash anodizer designed to give electronic assemblies a colorful, wear-resistant finish."
- icon = 'icons/obj/assemblies/electronic_tools.dmi'
- icon_state = "detailer"
- flags_1 = CONDUCT_1
- item_flags = NOBLUDGEON
- w_class = WEIGHT_CLASS_SMALL
- var/data_to_write = null
- var/accepting_refs = FALSE
- var/detail_color = COLOR_ASSEMBLY_WHITE
- var/list/color_list = list(
- "black" = COLOR_ASSEMBLY_BLACK,
- "gray" = COLOR_FLOORTILE_GRAY,
- "machine gray" = COLOR_ASSEMBLY_BGRAY,
- "white" = COLOR_ASSEMBLY_WHITE,
- "red" = COLOR_ASSEMBLY_RED,
- "orange" = COLOR_ASSEMBLY_ORANGE,
- "beige" = COLOR_ASSEMBLY_BEIGE,
- "brown" = COLOR_ASSEMBLY_BROWN,
- "gold" = COLOR_ASSEMBLY_GOLD,
- "yellow" = COLOR_ASSEMBLY_YELLOW,
- "gurkha" = COLOR_ASSEMBLY_GURKHA,
- "light green" = COLOR_ASSEMBLY_LGREEN,
- "green" = COLOR_ASSEMBLY_GREEN,
- "light blue" = COLOR_ASSEMBLY_LBLUE,
- "blue" = COLOR_ASSEMBLY_BLUE,
- "purple" = COLOR_ASSEMBLY_PURPLE
- )
-
-/obj/item/integrated_electronics/detailer/Initialize()
- .=..()
- update_icon()
-
-/obj/item/integrated_electronics/detailer/update_icon()
- cut_overlays()
- var/mutable_appearance/detail_overlay = mutable_appearance('icons/obj/assemblies/electronic_tools.dmi', "detailer-color")
- detail_overlay.color = detail_color
- add_overlay(detail_overlay)
-
-/obj/item/integrated_electronics/detailer/attack_self(mob/user)
- var/color_choice = input(user, "Select color.", "Assembly Detailer") as null|anything in color_list
- if(!color_list[color_choice])
- return
- if(!in_range(src, user))
- return
- detail_color = color_list[color_choice]
- update_icon()
diff --git a/code/modules/integrated_electronics/core/helpers.dm b/code/modules/integrated_electronics/core/helpers.dm
deleted file mode 100644
index e9cbb346458d9..0000000000000
--- a/code/modules/integrated_electronics/core/helpers.dm
+++ /dev/null
@@ -1,142 +0,0 @@
-/obj/item/integrated_circuit/proc/setup_io(list/io_list, io_type, list/io_default_list, pin_type)
- var/list/io_list_copy = io_list.Copy()
- io_list.Cut()
- for(var/i in 1 to io_list_copy.len)
- var/io_entry = io_list_copy[i]
- var/default_data = null
- var/io_type_override = null
-
- // Override the default data.
- if(length(io_default_list)) // List containing special pin types that need to be added.
- default_data = io_default_list["[i]"] // This is deliberately text because the index is a number in text form.
-
- // Override the pin type.
- if(io_list_copy[io_entry])
- io_type_override = io_list_copy[io_entry]
-
- if(io_type_override)
- io_list.Add(new io_type_override(src, io_entry, default_data, pin_type,i))
- else
- io_list.Add(new io_type(src, io_entry, default_data, pin_type,i))
-
-
-/obj/item/integrated_circuit/proc/set_pin_data(pin_type, pin_number, datum/new_data)
- if(islist(new_data))
- for(var/i in 1 to length(new_data))
- if (istype(new_data) && !isweakref(new_data))
- new_data[i] = WEAKREF(new_data[i])
- if (istype(new_data) && !isweakref(new_data))
- new_data = WEAKREF(new_data)
- var/datum/integrated_io/pin = get_pin_ref(pin_type, pin_number)
- return pin.write_data_to_pin(new_data)
-
-/obj/item/integrated_circuit/proc/get_pin_data(pin_type, pin_number)
- var/datum/integrated_io/pin = get_pin_ref(pin_type, pin_number)
- return pin.get_data()
-
-/obj/item/integrated_circuit/proc/get_pin_data_as_type(pin_type, pin_number, as_type)
- var/datum/integrated_io/pin = get_pin_ref(pin_type, pin_number)
- return pin.data_as_type(as_type)
-
-/obj/item/integrated_circuit/proc/activate_pin(pin_number)
- var/datum/integrated_io/activate/A = activators[pin_number]
- A.push_data()
-
-/obj/item/integrated_circuit/proc/get_pin_ref(pin_type, pin_number)
- switch(pin_type)
- if(IC_INPUT)
- if(pin_number > inputs.len)
- return
- return inputs[pin_number]
- if(IC_OUTPUT)
- if(pin_number > outputs.len)
- return
- return outputs[pin_number]
- if(IC_ACTIVATOR)
- if(pin_number > activators.len)
- return
- return activators[pin_number]
- return
-
-/datum/integrated_io/proc/get_data()
- if(islist(data))
- for(var/i in 1 to length(data))
- if(isweakref(data[i]))
- data[i] = data[i].resolve()
- if(isweakref(data))
- return data.resolve()
- return data
-
-
-// Returns a list of parameters necessary to locate a pin in the assembly: component number, pin type and pin number
-// Components list can be supplied from the outside, for use in savefiles
-/datum/integrated_io/proc/get_pin_parameters(list/components)
- if(!holder)
- return
-
- if(!components)
- if(!holder.assembly)
- return
- components = holder.assembly.assembly_components
-
- var/component_number = components.Find(holder)
-
- var/list/pin_holder_list
- switch(pin_type)
- if(IC_INPUT)
- pin_holder_list = holder.inputs
- if(IC_OUTPUT)
- pin_holder_list = holder.outputs
- if(IC_ACTIVATOR)
- pin_holder_list = holder.activators
- else
- return
-
- var/pin_number = pin_holder_list.Find(src)
-
- return list(component_number, pin_type, pin_number)
-
-
-// Locates a pin in the assembly when given component number, pin type and pin number
-// Components list can be supplied from the outside, for use in savefiles
-/obj/item/electronic_assembly/proc/get_pin_ref(component_number, pin_type, pin_number, list/components)
- if(!components)
- components = assembly_components
-
- if(component_number > components.len)
- return
-
- var/obj/item/integrated_circuit/component = components[component_number]
- return component.get_pin_ref(pin_type, pin_number)
-
-
-// Same as get_pin_ref, but takes in a list of 3 parameters (same format as get_pin_parameters)
-// and performs extra sanity checks on parameters list and index numbers
-/obj/item/electronic_assembly/proc/get_pin_ref_list(list/parameters, list/components)
- if(!components)
- components = assembly_components
-
- if(!islist(parameters) || parameters.len != 3)
- return
-
- // Those are supposed to be list indexes, check them for sanity
- if(!isnum_safe(parameters[1]) || parameters[1] % 1 || parameters[1] < 1)
- return
-
- if(!isnum_safe(parameters[3]) || parameters[3] % 1 || parameters[3] < 1)
- return
-
- return get_pin_ref(parameters[1], parameters[2], parameters[3], components)
-
-
-
-
-// Used to obfuscate object refs imported/exported as strings.
-// Not very secure, but if someone still finds a way to abuse refs, they deserve it.
-/proc/XorEncrypt(string, key)
- if(!string || !key ||!istext(string)||!istext(key))
- return
- var/r
- for(var/i = 1 to length(string))
- r += ascii2text(text2ascii(string,i) ^ text2ascii(key,((i-1)%length(string))+1))
- return r
diff --git a/code/modules/integrated_electronics/core/integrated_circuit.dm b/code/modules/integrated_electronics/core/integrated_circuit.dm
deleted file mode 100644
index 73f64c98f1b17..0000000000000
--- a/code/modules/integrated_electronics/core/integrated_circuit.dm
+++ /dev/null
@@ -1,414 +0,0 @@
-/obj/item/integrated_circuit
- name = "integrated circuit"
- desc = "It's a tiny chip! This one doesn't seem to do much, however."
- icon = 'icons/obj/assemblies/electronic_components.dmi'
- icon_state = "template"
- w_class = WEIGHT_CLASS_TINY
- materials = list() // To be filled later
- var/obj/item/electronic_assembly/assembly // Reference to the assembly holding this circuit, if any.
- var/extended_desc
- var/list/inputs = list()
- var/list/inputs_default = list()// Assoc list which will fill a pin with data upon creation. e.g. "2" = 0 will set input pin 2 to equal 0 instead of null.
- var/list/outputs = list()
- var/list/outputs_default =list()// Ditto, for output.
- var/list/activators = list()
- var/next_use = 0 // Uses world.time
- var/complexity = 1 // This acts as a limitation on building machines, more resource-intensive components cost more 'space'.
- var/size = 1 // This acts as a limitation on building machines, bigger components cost more 'space'. -1 for size 0
- var/cooldown_per_use = 1 // Circuits are limited in how many times they can be work()'d by this variable.
- var/ext_cooldown = 0 // Circuits are limited in how many times they can be work()'d with external world by this variable.
- var/power_draw_per_use = 0 // How much power is drawn when work()'d.
- var/power_draw_idle = 0 // How much power is drawn when doing nothing.
- var/spawn_flags // Used for world initializing, see the #defines above.
- var/action_flags = NONE // Used for telling circuits that can do certain actions from other circuits.
- var/category_text = "NO CATEGORY THIS IS A BUG" // To show up on circuit printer, and perhaps other places.
- var/removable = TRUE // Determines if a circuit is removable from the assembly.
- var/displayed_name = ""
- var/demands_object_input = FALSE
- var/can_input_object_when_closed = FALSE
- var/max_allowed = 0 // The maximum amount of components allowed inside an integrated circuit.
-
-
-/*
- Integrated circuits are essentially modular machines. Each circuit has a specific function, and combining them inside Electronic Assemblies allows
-a creative player the means to solve many problems. Circuits are held inside an electronic assembly, and are wired using special tools.
-*/
-
-/obj/item/integrated_circuit/examine(mob/user)
- interact(user)
- external_examine(user)
- . = ..()
-
-// Can be called via electronic_assembly/attackby()
-/obj/item/integrated_circuit/proc/additem(var/obj/item/I, var/mob/living/user)
- attackby(I, user)
-
-// This should be used when someone is examining while the case is opened.
-/obj/item/integrated_circuit/proc/internal_examine(mob/user)
- to_chat(user, "This board has [inputs.len] input pin\s, [outputs.len] output pin\s and [activators.len] activation pin\s.")
- for(var/k in inputs)
- var/datum/integrated_io/I = k
- if(I.linked.len)
- to_chat(user, "The '[I]' is connected to [I.get_linked_to_desc()].")
- for(var/k in outputs)
- var/datum/integrated_io/O = k
- if(O.linked.len)
- to_chat(user, "The '[O]' is connected to [O.get_linked_to_desc()].")
- for(var/k in activators)
- var/datum/integrated_io/activate/A = k
- if(A.linked.len)
- to_chat(user, "The '[A]' is connected to [A.get_linked_to_desc()].")
- any_examine(user)
- interact(user)
-
-// This should be used when someone is examining from an 'outside' perspective, e.g. reading a screen or LED.
-/obj/item/integrated_circuit/proc/external_examine(mob/user)
- any_examine(user)
-
-/obj/item/integrated_circuit/proc/any_examine(mob/user)
- return
-
-/obj/item/integrated_circuit/proc/attackby_react(var/atom/movable/A,mob/user)
- return
-
-/obj/item/integrated_circuit/proc/sense(var/atom/movable/A,mob/user,prox)
- return
-
-/obj/item/integrated_circuit/proc/check_interactivity(mob/user)
- if(assembly)
- return assembly.check_interactivity(user)
- else
- return user.canUseTopic(src, BE_CLOSE)
-
-/obj/item/integrated_circuit/Initialize()
- . = ..()
- displayed_name = name
- setup_io(inputs, /datum/integrated_io, inputs_default, IC_INPUT)
- setup_io(outputs, /datum/integrated_io, outputs_default, IC_OUTPUT)
- setup_io(activators, /datum/integrated_io/activate, null, IC_ACTIVATOR)
- materials[/datum/material/iron] = w_class * SScircuit.cost_multiplier
-
-
-/obj/item/integrated_circuit/proc/on_data_written() //Override this for special behaviour when new data gets pushed to the circuit.
- return
-
-/obj/item/integrated_circuit/Destroy()
- . = ..()
- QDEL_LIST(inputs)
- QDEL_LIST(outputs)
- QDEL_LIST(activators)
-
-
-/obj/item/integrated_circuit/emp_act(severity)
- for(var/k in inputs)
- var/datum/integrated_io/I = k
- I.scramble()
- for(var/k in outputs)
- var/datum/integrated_io/O = k
- O.scramble()
- for(var/k in activators)
- var/datum/integrated_io/activate/A = k
- A.scramble()
-
-
-/obj/item/integrated_circuit/verb/rename_component()
- set name = "Rename Circuit"
- set category = "Object"
- set desc = "Rename your circuit, useful to stay organized."
-
- var/mob/M = usr
- if(!check_interactivity(M))
- return
-
- var/input = reject_bad_name(stripped_input(M, "What do you want to name this?", "Rename", name), TRUE)
- if(check_interactivity(M))
- if(!input)
- input = name
- if(CHAT_FILTER_CHECK(input))
- to_chat(M, "The circuit name contains prohibited words!")
- return
- to_chat(M, "The circuit '[name]' is now labeled '[input]'.")
- displayed_name = input
-
-/obj/item/integrated_circuit/interact(mob/user)
- ui_interact(user)
-
-/obj/item/integrated_circuit/ui_interact(mob/user)
- . = ..()
- if(!check_interactivity(user))
- return
-
- if(assembly)
- assembly.ui_interact(user, src)
- return
-
- var/table_edge_width = "30%"
- var/table_middle_width = "40%"
-
- var/datum/browser/popup = new(user, "scannernew", name, 800, 630) // Set up the popup browser window
- popup.add_stylesheet("scannernew", 'html/browser/assembly_ui.css')
-
- var/HTML = "[src.displayed_name] \
-
"
- return return_text
-
-
-/client/proc/get_exp_living(var/format = TRUE)
- if(!prefs.exp)
- return "No data"
+/client/proc/get_exp_living(pure_numeric = FALSE)
+ if(!prefs.exp || !prefs.exp[EXP_TYPE_LIVING])
+ return pure_numeric ? 0 : "No data"
var/exp_living = text2num(prefs.exp[EXP_TYPE_LIVING])
- if(format)
- return get_exp_format(exp_living)
- return exp_living
+ return pure_numeric ? exp_living : get_exp_format(exp_living)
/proc/get_exp_format(expnum)
if(expnum > 60)
diff --git a/code/modules/jobs/job_report.dm b/code/modules/jobs/job_report.dm
new file mode 100644
index 0000000000000..88c7f7ad1902c
--- /dev/null
+++ b/code/modules/jobs/job_report.dm
@@ -0,0 +1,49 @@
+#define JOB_REPORT_MENU_FAIL_REASON_TRACKING_DISABLED 1
+#define JOB_REPORT_MENU_FAIL_REASON_NO_RECORDS 2
+
+/datum/job_report_menu
+ var/client/owner
+
+/datum/job_report_menu/New(client/owner, mob/viewer)
+ src.owner = owner
+ ui_interact(viewer)
+
+/datum/job_report_menu/ui_state()
+ return GLOB.always_state
+
+/datum/job_report_menu/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if (!ui)
+ ui = new(user, src, "TrackedPlaytime")
+ ui.open()
+
+/datum/job_report_menu/ui_static_data()
+ if (!CONFIG_GET(flag/use_exp_tracking))
+ return list("failReason" = JOB_REPORT_MENU_FAIL_REASON_TRACKING_DISABLED)
+
+ var/list/play_records = owner.prefs.exp
+ if (!play_records.len)
+ owner.set_exp_from_db()
+ play_records = owner.prefs.exp
+ if (!play_records.len)
+ return list("failReason" = JOB_REPORT_MENU_FAIL_REASON_NO_RECORDS)
+
+ var/list/data = list()
+ data["jobPlaytimes"] = list()
+ data["specialPlaytimes"] = list()
+
+ for (var/job_name in SSjob.name_occupations)
+ var/playtime = play_records[job_name] ? text2num(play_records[job_name]) : 0
+ data["jobPlaytimes"][job_name] = playtime
+
+ for (var/special_name in GLOB.exp_specialmap[EXP_TYPE_SPECIAL])
+ var/playtime = play_records[special_name] ? text2num(play_records[special_name]) : 0
+ data["specialPlaytimes"][special_name] = playtime
+
+ data["livingTime"] = play_records[EXP_TYPE_LIVING]
+ data["ghostTime"] = play_records[EXP_TYPE_GHOST]
+
+ return data
+
+#undef JOB_REPORT_MENU_FAIL_REASON_TRACKING_DISABLED
+#undef JOB_REPORT_MENU_FAIL_REASON_NO_RECORDS
diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm
index c83b4cce9b435..7d5fea574aa33 100644
--- a/code/modules/jobs/job_types/_job.dm
+++ b/code/modules/jobs/job_types/_job.dm
@@ -2,6 +2,9 @@
//The name of the job , used for preferences, bans and more. Make sure you know what you're doing before changing this.
var/title = "NOPE"
+ //Calculated in /New
+ var/say_span = ""
+
//Job access. The use of minimal_access or access is determined by a config setting: config.jobs_have_minimal_access
var/list/minimal_access = list() //Useful for servers which prefer to only have access given to the places a job absolutely needs (Larger server population)
var/list/access = list() //Useful for servers which either have fewer players, so each person needs to fill more than one role, or servers which like to give more access, so players can't hide forever in their super secure departments (I'm looking at you, chemistry!)
@@ -13,7 +16,7 @@
var/list/head_announce = null
//Bitflags for the job
- var/flag = NONE //Deprecated
+ var/flag = NONE //Deprecated //Except not really, still used throughout the codebase
var/department_flag = NONE //Deprecated
var/auto_deadmin_role_flags = NONE
@@ -62,14 +65,28 @@
var/display_order = JOB_DISPLAY_ORDER_DEFAULT
- var/tmp/list/gear_leftovers = list()
var/gimmick = FALSE //least hacky way i could think of for this
+ ///Bitfield of departments this job belongs wit
+ var/departments = NONE
+ ///Is this job affected by weird spawns like the ones from station traits
+ var/random_spawns_possible = TRUE
+ /// Should this job be allowed to be picked for the bureaucratic error event?
+ var/allow_bureaucratic_error = TRUE
+
+ ///A dictionary of species IDs and a path to the outfit.
+ var/list/species_outfits = null
+
+/datum/job/New()
+ . = ..()
+ say_span = replacetext(lowertext(title), " ", "")
+
//Only override this proc, unless altering loadout code. Loadouts act on H but get info from M
//H is usually a human unless an /equip override transformed it
//do actions on H but send messages to M as the key may not have been transferred_yet
/datum/job/proc/after_spawn(mob/living/H, mob/M, latejoin = FALSE)
//do actions on H but send messages to M as the key may not have been transferred_yet
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_JOB_AFTER_SPAWN, src, H, M, latejoin)
if(mind_traits)
for(var/t in mind_traits)
ADD_TRAIT(H.mind, t, JOB_TRAIT)
@@ -77,7 +94,8 @@
if(!ishuman(H))
return
var/mob/living/carbon/human/human = H
- if(M.client && (M.client.prefs.equipped_gear && M.client.prefs.equipped_gear.len))
+ var/list/gear_leftovers = list()
+ if(M.client && LAZYLEN(M.client.prefs.equipped_gear))
for(var/gear in M.client.prefs.equipped_gear)
var/datum/gear/G = GLOB.gear_datums[gear]
if(G)
@@ -97,12 +115,12 @@
permitted = FALSE
if(!permitted)
- to_chat(M, "Your current species or role does not permit you to spawn with [gear]!")
+ to_chat(M, "Your current species or role does not permit you to spawn with [G.display_name]!")
continue
if(G.slot)
if(H.equip_to_slot_or_del(G.spawn_item(H), G.slot))
- to_chat(M, "Equipping you with [gear]!")
+ to_chat(M, "Equipping you with [G.display_name]!")
else
gear_leftovers += G
else
@@ -113,7 +131,7 @@
if(gear_leftovers.len)
for(var/datum/gear/G in gear_leftovers)
- var/metadata = M.client.prefs.equipped_gear[G.display_name]
+ var/metadata = M.client.prefs.equipped_gear[G.id]
var/item = G.spawn_item(null, metadata)
var/atom/placed_in = human.equip_or_collect(item)
@@ -140,8 +158,6 @@
to_chat(M, "Failed to locate a storage object on your mob, either you spawned with no hands free and no backpack or this is a bug.")
qdel(item)
- qdel(gear_leftovers)
-
/datum/job/proc/announce(mob/living/carbon/human/H)
if(head_announce)
announce_head(H, head_announce)
@@ -178,6 +194,11 @@
//Equip the rest of the gear
H.dna.species.before_equip_job(src, H, visualsOnly)
+
+ if(src.species_outfits)
+ if(H.dna.species.id in src.species_outfits)
+ var/datum/outfit/O = species_outfits[H.dna.species.id]
+ H.equipOutfit(O, visualsOnly)
if(outfit_override || outfit)
H.equipOutfit(outfit_override ? outfit_override : outfit, visualsOnly)
@@ -204,7 +225,7 @@
/datum/job/proc/announce_head(var/mob/living/carbon/human/H, var/channels) //tells the given channel that the given mob is the new department head. See communications.dm for valid channels.
if(H && GLOB.announcement_systems.len)
//timer because these should come after the captain announcement
- SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, .proc/addtimer, CALLBACK(pick(GLOB.announcement_systems), /obj/machinery/announcement_system/proc/announce, "NEWHEAD", H.real_name, H.job, channels), 1))
+ SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, .proc/_addtimer, CALLBACK(pick(GLOB.announcement_systems), /obj/machinery/announcement_system/proc/announce, "NEWHEAD", H.real_name, H.job, channels), 1))
//If the configuration option is set to require players to be logged as old enough to play certain jobs, then this proc checks that they are, otherwise it just returns 1
/datum/job/proc/player_old_enough(client/C)
@@ -237,7 +258,7 @@
/datum/outfit/job
name = "Standard Gear"
- var/jobtype = null
+ var/jobtype
uniform = /obj/item/clothing/under/color/grey
id = /obj/item/card/id
@@ -251,7 +272,7 @@
var/satchel = /obj/item/storage/backpack/satchel
var/duffelbag = /obj/item/storage/backpack/duffelbag
- var/pda_slot = SLOT_BELT
+ var/pda_slot = ITEM_SLOT_BELT
/datum/outfit/job/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
switch(H.backbag)
@@ -270,6 +291,17 @@
else
back = backpack //Department backpack
+ //converts the uniform string into the path we'll wear, whether it's the skirt or regular variant
+ var/holder
+ if(H.jumpsuit_style == PREF_SKIRT)
+ holder = "[uniform]/skirt"
+ if(!text2path(holder))
+ holder = "[uniform]"
+ else
+ holder = "[uniform]"
+ uniform = text2path(holder)
+
+
/datum/outfit/job/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
if(visualsOnly)
return
diff --git a/code/modules/jobs/job_types/ai.dm b/code/modules/jobs/job_types/ai.dm
index 9653797d03f10..30d7d024ab5de 100644
--- a/code/modules/jobs/job_types/ai.dm
+++ b/code/modules/jobs/job_types/ai.dm
@@ -10,10 +10,13 @@
supervisors = "your laws"
req_admin_notify = TRUE
minimal_player_age = 30
- exp_requirements = 240
+ exp_requirements = 600
exp_type = EXP_TYPE_CREW
exp_type_department = EXP_TYPE_SILICON
display_order = JOB_DISPLAY_ORDER_AI
+ departments = DEPARTMENT_SILICON
+ random_spawns_possible = FALSE
+ allow_bureaucratic_error = FALSE
var/do_special_check = TRUE
/datum/job/ai/equip(mob/living/carbon/human/H, visualsOnly, announce, latejoin, datum/outfit/outfit_override, client/preference_source = null)
diff --git a/code/modules/jobs/job_types/assistant.dm b/code/modules/jobs/job_types/assistant.dm
index 6a5f4a9a611e3..9d15f4896d4a6 100644
--- a/code/modules/jobs/job_types/assistant.dm
+++ b/code/modules/jobs/job_types/assistant.dm
@@ -18,7 +18,11 @@ Assistant
paycheck = PAYCHECK_ASSISTANT // Get a job. Job reassignment changes your paycheck now. Get over it.
paycheck_department = ACCOUNT_CIV
display_order = JOB_DISPLAY_ORDER_ASSISTANT
+ departments = DEPARTMENT_SERVICE
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman
+ )
/datum/job/assistant/get_access()
if(CONFIG_GET(flag/assistants_have_maint_access) || !CONFIG_GET(flag/jobs_have_minimal_access)) //Config has assistant maint access set
. = ..()
@@ -33,6 +37,12 @@ Assistant
/datum/outfit/job/assistant/pre_equip(mob/living/carbon/human/H)
..()
if (CONFIG_GET(flag/grey_assistants))
- uniform = /obj/item/clothing/under/color/grey
+ if(H.jumpsuit_style == PREF_SUIT)
+ uniform = /obj/item/clothing/under/color/grey
+ else
+ uniform = /obj/item/clothing/under/skirt/color/grey
else
- uniform = /obj/item/clothing/under/color/random
+ if(H.jumpsuit_style == PREF_SUIT)
+ uniform = /obj/item/clothing/under/color/random
+ else
+ uniform = /obj/item/clothing/under/skirt/color/random
diff --git a/code/modules/jobs/job_types/atmospheric_technician.dm b/code/modules/jobs/job_types/atmospheric_technician.dm
index 70412c2c1452a..33918897a09a5 100644
--- a/code/modules/jobs/job_types/atmospheric_technician.dm
+++ b/code/modules/jobs/job_types/atmospheric_technician.dm
@@ -9,17 +9,22 @@
supervisors = "the chief engineer"
selection_color = "#fff5cc"
chat_color = "#D4A07D"
+ exp_requirements = 120
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/atmos
access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, ACCESS_MECH_ENGINE,
- ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_ATMOSPHERICS, ACCESS_MAINT_TUNNELS, ACCESS_CONSTRUCTION, ACCESS_MECH_ENGINE, ACCESS_MINERAL_STOREROOM)
+ ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS, ACCESS_MINERAL_STOREROOM, ACCESS_AUX_BASE)
+ minimal_access = list(ACCESS_ATMOSPHERICS, ACCESS_MAINT_TUNNELS, ACCESS_CONSTRUCTION, ACCESS_MECH_ENGINE, ACCESS_MINERAL_STOREROOM, ACCESS_AUX_BASE)
paycheck = PAYCHECK_MEDIUM
paycheck_department = ACCOUNT_ENG
display_order = JOB_DISPLAY_ORDER_ATMOSPHERIC_TECHNICIAN
+ departments = DEPARTMENT_ENGINEERING
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/atmospherics
+ )
/datum/outfit/job/atmos
name = "Atmospheric Technician"
jobtype = /datum/job/atmos
@@ -35,7 +40,7 @@
satchel = /obj/item/storage/backpack/satchel/eng
duffelbag = /obj/item/storage/backpack/duffelbag/engineering
box = /obj/item/storage/box/engineer
- pda_slot = SLOT_L_STORE
+ pda_slot = ITEM_SLOT_LPOCKET
backpack_contents = list(/obj/item/modular_computer/tablet/preset/advanced=1)
/datum/outfit/job/atmos/rig
@@ -44,4 +49,4 @@
mask = /obj/item/clothing/mask/gas
suit = /obj/item/clothing/suit/space/hardsuit/engine/atmos
suit_store = /obj/item/tank/internals/oxygen
- internals_slot = SLOT_S_STORE
+ internals_slot = ITEM_SLOT_SUITSTORE
diff --git a/code/modules/jobs/job_types/bartender.dm b/code/modules/jobs/job_types/bartender.dm
index b4e201d686087..572042bc51438 100644
--- a/code/modules/jobs/job_types/bartender.dm
+++ b/code/modules/jobs/job_types/bartender.dm
@@ -18,7 +18,11 @@
paycheck = PAYCHECK_EASY
paycheck_department = ACCOUNT_SRV
display_order = JOB_DISPLAY_ORDER_BARTENDER
+ departments = DEPARTMENT_SERVICE
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/bar
+ )
/datum/outfit/job/bartender
name = "Bartender"
jobtype = /datum/job/bartender
diff --git a/code/modules/jobs/job_types/botanist.dm b/code/modules/jobs/job_types/botanist.dm
index 1ee1c3338639e..3b2938f4d5b99 100644
--- a/code/modules/jobs/job_types/botanist.dm
+++ b/code/modules/jobs/job_types/botanist.dm
@@ -17,7 +17,11 @@
paycheck = PAYCHECK_EASY
paycheck_department = ACCOUNT_SRV
display_order = JOB_DISPLAY_ORDER_BOTANIST
+ departments = DEPARTMENT_SERVICE
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/botany
+ )
/datum/outfit/job/botanist
name = "Botanist"
jobtype = /datum/job/hydro
@@ -27,7 +31,7 @@
ears = /obj/item/radio/headset/headset_srv
uniform = /obj/item/clothing/under/rank/civilian/hydroponics
suit = /obj/item/clothing/suit/apron
- gloves =/obj/item/clothing/gloves/botanic_leather
+ gloves = /obj/item/clothing/gloves/botanic_leather
suit_store = /obj/item/plant_analyzer
backpack = /obj/item/storage/backpack/botany
diff --git a/code/modules/jobs/job_types/brigphys.dm b/code/modules/jobs/job_types/brigphys.dm
index 670c1f2e53e07..0dc6acf7c000d 100644
--- a/code/modules/jobs/job_types/brigphys.dm
+++ b/code/modules/jobs/job_types/brigphys.dm
@@ -1,29 +1,31 @@
/datum/job/brig_phys
title = "Brig Physician"
flag = BRIG_PHYS
- department_head = list("Head of Security", "Chief Medical Officer")
+ department_head = list("Chief Medical Officer")
department_flag = ENGSEC
faction = "Station"
total_positions = 1
spawn_positions = 1
- supervisors = "the head of security and chief medical officer"
+ supervisors = "chief medical officer"
selection_color = "#ffeeee"
chat_color = "#b16789"
minimal_player_age = 7
exp_requirements = 120
exp_type = EXP_TYPE_CREW
- exp_type_department = EXP_TYPE_SECURITY
outfit = /datum/outfit/job/brig_phys
- access = list(ACCESS_BRIGPHYS, ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_FORENSICS_LOCKERS, ACCESS_MEDICAL, ACCESS_KEYCARD_AUTH)
- minimal_access = list(ACCESS_SECURITY, ACCESS_BRIGPHYS, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_FORENSICS_LOCKERS, ACCESS_MEDICAL, ACCESS_KEYCARD_AUTH)
- paycheck = PAYCHECK_HARD
+ access = list(ACCESS_SEC_DOORS, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_MORGUE, ACCESS_MEDICAL, ACCESS_BRIGPHYS)
+ minimal_access = list(ACCESS_SEC_DOORS, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_MORGUE, ACCESS_MEDICAL, ACCESS_BRIGPHYS)
+ paycheck = PAYCHECK_MEDIUM
paycheck_department = ACCOUNT_MED
- mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
display_order = JOB_DISPLAY_ORDER_BRIG_PHYS
+ departments = DEPARTMENT_MEDICAL | DEPARTMENT_SECURITY
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/secmed
+ )
/datum/outfit/job/brig_phys
name = "Brig Physician"
jobtype = /datum/job/brig_phys
@@ -34,12 +36,14 @@
shoes = /obj/item/clothing/shoes/sneakers/white
glasses = /obj/item/clothing/glasses/hud/health/sunglasses
suit = /obj/item/clothing/suit/hazardvest/brig_phys
+ gloves = /obj/item/clothing/gloves/color/latex/nitrile
suit_store = /obj/item/flashlight/seclite
l_hand = /obj/item/storage/firstaid/medical
head = /obj/item/clothing/head/soft/sec/brig_phys
- implants = list(/obj/item/implant/mindshield)
backpack = /obj/item/storage/backpack/medic
satchel = /obj/item/storage/backpack/satchel/med
duffelbag = /obj/item/storage/backpack/duffelbag/med
box = /obj/item/storage/box/security
+
+ chameleon_extras = /obj/item/gun/syringe
diff --git a/code/modules/jobs/job_types/captain.dm b/code/modules/jobs/job_types/captain.dm
index 02c159d0ea91f..c5b8655201949 100755
--- a/code/modules/jobs/job_types/captain.dm
+++ b/code/modules/jobs/job_types/captain.dm
@@ -26,7 +26,11 @@
mind_traits = list(TRAIT_DISK_VERIFIER)
display_order = JOB_DISPLAY_ORDER_CAPTAIN
+ departments = DEPARTMENT_COMMAND
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/command
+ )
/datum/job/captain/get_access()
return get_all_accesses()
@@ -47,7 +51,7 @@
suit = /obj/item/clothing/suit/armor/vest/capcarapace
shoes = /obj/item/clothing/shoes/sneakers/brown
head = /obj/item/clothing/head/caphat
- backpack_contents = list(/obj/item/melee/classic_baton/police/telescopic=1, /obj/item/station_charter=1)
+ backpack_contents = list(/obj/item/melee/classic_baton/police/telescopic=1, /obj/item/station_charter=1, /obj/item/modular_computer/tablet/preset/advanced=1)
backpack = /obj/item/storage/backpack/captain
satchel = /obj/item/storage/backpack/satchel/cap
diff --git a/code/modules/jobs/job_types/cargo_technician.dm b/code/modules/jobs/job_types/cargo_technician.dm
index 94ea69f421132..e6f9598035437 100644
--- a/code/modules/jobs/job_types/cargo_technician.dm
+++ b/code/modules/jobs/job_types/cargo_technician.dm
@@ -18,7 +18,11 @@
paycheck_department = ACCOUNT_CAR
display_order = JOB_DISPLAY_ORDER_CARGO_TECHNICIAN
+ departments = DEPARTMENT_CARGO
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/cargo
+ )
/datum/outfit/job/cargo_tech
name = "Cargo Technician"
jobtype = /datum/job/cargo_tech
diff --git a/code/modules/jobs/job_types/chaplain.dm b/code/modules/jobs/job_types/chaplain.dm
index da3e554e84b83..93c6c3e945295 100644
--- a/code/modules/jobs/job_types/chaplain.dm
+++ b/code/modules/jobs/job_types/chaplain.dm
@@ -10,6 +10,7 @@
selection_color = "#dddddd"
chat_color = "#8AB48C"
+
outfit = /datum/outfit/job/chaplain
access = list(ACCESS_MORGUE, ACCESS_CHAPEL_OFFICE, ACCESS_CREMATORIUM, ACCESS_THEATRE)
@@ -18,26 +19,31 @@
paycheck_department = ACCOUNT_CIV
display_order = JOB_DISPLAY_ORDER_CHAPLAIN
+ departments = DEPARTMENT_SERVICE
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/chaplain
+ )
/datum/job/chaplain/after_spawn(mob/living/H, mob/M)
. = ..()
- if(H.mind)
- H.mind.isholy = TRUE
var/obj/item/storage/book/bible/booze/B = new
if(GLOB.religion)
+ H.mind?.holy_role = HOLY_ROLE_PRIEST
B.deity_name = GLOB.deity
B.name = GLOB.bible_name
B.icon_state = GLOB.bible_icon_state
B.item_state = GLOB.bible_item_state
to_chat(H, "There is already an established religion onboard the station. You are an acolyte of [GLOB.deity]. Defer to the Chaplain.")
- H.equip_to_slot_or_del(B, SLOT_IN_BACKPACK)
+ H.equip_to_slot_or_del(B, ITEM_SLOT_BACKPACK)
var/nrt = GLOB.holy_weapon_type || /obj/item/nullrod
var/obj/item/nullrod/N = new nrt(H)
H.put_in_hands(N)
+ GLOB.religious_sect?.on_conversion(H)
return
+ H.mind?.holy_role = HOLY_ROLE_HIGHPRIEST
var/new_religion = DEFAULT_RELIGION
if(M.client && M.client.prefs.custom_names["religion"])
@@ -63,8 +69,6 @@
B.name = "The Necronomicon"
if("hinduism")
B.name = "The Vedas"
- if("homosexuality")
- B.name = pick("Guys Gone Wild","Coming Out of The Closet")
if("imperium")
B.name = "Uplifting Primer"
if("islam")
@@ -73,9 +77,6 @@
B.name = "The Torah"
if("lampism")
B.name = "Fluorescent Incandescence"
- if("lol", "wtf", "gay", "penis", "ass", "poo", "badmin", "shitmin", "deadmin", "cock", "cocks", "meme", "memes")
- B.name = pick("Woodys Got Wood: The Aftermath", "War of the Cocks", "Sweet Bro and Hella Jef: Expanded Edition","F.A.T.A.L. Rulebook")
- H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 100) // starts off retarded as fuck
if("monkeyism","apism","gorillism","primatism")
B.name = pick("Going Bananas", "Bananas Out For Harambe")
if("mormonism")
@@ -107,7 +108,7 @@
GLOB.bible_name = B.name
GLOB.deity = B.deity_name
- H.equip_to_slot_or_del(B, SLOT_IN_BACKPACK)
+ H.equip_to_slot_or_del(B, ITEM_SLOT_BACKPACK)
SSblackbox.record_feedback("text", "religion_name", 1, "[new_religion]", 1)
SSblackbox.record_feedback("text", "religion_deity", 1, "[new_deity]", 1)
diff --git a/code/modules/jobs/job_types/chemist.dm b/code/modules/jobs/job_types/chemist.dm
index a3c76f56e0b49..7d36248996fcf 100644
--- a/code/modules/jobs/job_types/chemist.dm
+++ b/code/modules/jobs/job_types/chemist.dm
@@ -9,6 +9,7 @@
supervisors = "the chief medical officer"
selection_color = "#d4ebf2"
chat_color = "#82BDCE"
+ exp_requirements = 120
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/chemist
@@ -19,7 +20,11 @@
paycheck_department = ACCOUNT_MED
display_order = JOB_DISPLAY_ORDER_CHEMIST
+ departments = DEPARTMENT_MEDICAL
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/chemist
+ )
/datum/outfit/job/chemist
name = "Chemist"
jobtype = /datum/job/chemist
diff --git a/code/modules/jobs/job_types/chief_engineer.dm b/code/modules/jobs/job_types/chief_engineer.dm
index af8122d323a9b..dc7eacf6ca5d5 100644
--- a/code/modules/jobs/job_types/chief_engineer.dm
+++ b/code/modules/jobs/job_types/chief_engineer.dm
@@ -20,18 +20,22 @@
outfit = /datum/outfit/job/ce
access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
- ACCESS_EXTERNAL_AIRLOCKS, ACCESS_ATMOSPHERICS, ACCESS_EVA,
+ ACCESS_EXTERNAL_AIRLOCKS, ACCESS_ATMOSPHERICS, ACCESS_EVA, ACCESS_AUX_BASE,
ACCESS_HEADS, ACCESS_CONSTRUCTION, ACCESS_SEC_DOORS, ACCESS_MINISAT, ACCESS_MECH_ENGINE,
ACCESS_CE, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM)
minimal_access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
- ACCESS_EXTERNAL_AIRLOCKS, ACCESS_ATMOSPHERICS, ACCESS_EVA,
+ ACCESS_EXTERNAL_AIRLOCKS, ACCESS_ATMOSPHERICS, ACCESS_EVA, ACCESS_AUX_BASE,
ACCESS_HEADS, ACCESS_CONSTRUCTION, ACCESS_SEC_DOORS, ACCESS_MINISAT, ACCESS_MECH_ENGINE,
ACCESS_CE, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM)
paycheck = PAYCHECK_COMMAND
paycheck_department = ACCOUNT_ENG
display_order = JOB_DISPLAY_ORDER_CHIEF_ENGINEER
+ departments = DEPARTMENT_ENGINEERING | DEPARTMENT_COMMAND
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/ce
+ )
/datum/outfit/job/ce
name = "Chief Engineer"
jobtype = /datum/job/chief_engineer
@@ -50,7 +54,7 @@
satchel = /obj/item/storage/backpack/satchel/eng
duffelbag = /obj/item/storage/backpack/duffelbag/engineering
box = /obj/item/storage/box/engineer
- pda_slot = SLOT_L_STORE
+ pda_slot = ITEM_SLOT_LPOCKET
chameleon_extras = /obj/item/stamp/ce
/datum/outfit/job/ce/rig
@@ -63,4 +67,4 @@
glasses = /obj/item/clothing/glasses/meson/engine
gloves = /obj/item/clothing/gloves/color/yellow
head = null
- internals_slot = SLOT_S_STORE
+ internals_slot = ITEM_SLOT_SUITSTORE
diff --git a/code/modules/jobs/job_types/chief_medical_officer.dm b/code/modules/jobs/job_types/chief_medical_officer.dm
index b27561a3c4adc..b26ab3ee4ad38 100644
--- a/code/modules/jobs/job_types/chief_medical_officer.dm
+++ b/code/modules/jobs/job_types/chief_medical_officer.dm
@@ -21,14 +21,19 @@
access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_APOTHECARY, ACCESS_HEADS, ACCESS_MINERAL_STOREROOM,
ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_SURGERY, ACCESS_RC_ANNOUNCE, ACCESS_MECH_MEDICAL,
- ACCESS_KEYCARD_AUTH, ACCESS_SEC_DOORS, ACCESS_MAINT_TUNNELS)
+ ACCESS_KEYCARD_AUTH, ACCESS_SEC_DOORS, ACCESS_MAINT_TUNNELS, ACCESS_BRIGPHYS)
minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_APOTHECARY, ACCESS_HEADS, ACCESS_MINERAL_STOREROOM,
ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_SURGERY, ACCESS_RC_ANNOUNCE, ACCESS_MECH_MEDICAL,
- ACCESS_KEYCARD_AUTH, ACCESS_SEC_DOORS, ACCESS_MAINT_TUNNELS)
+ ACCESS_KEYCARD_AUTH, ACCESS_SEC_DOORS, ACCESS_MAINT_TUNNELS, ACCESS_BRIGPHYS)
paycheck = PAYCHECK_COMMAND
paycheck_department = ACCOUNT_MED
display_order = JOB_DISPLAY_ORDER_CHIEF_MEDICAL_OFFICER
+ departments = DEPARTMENT_MEDICAL | DEPARTMENT_COMMAND
+
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/cmo
+ )
/datum/outfit/job/cmo
name = "Chief Medical Officer"
@@ -58,4 +63,3 @@
suit = /obj/item/clothing/suit/space/hardsuit/medical/cmo
suit_store = /obj/item/tank/internals/oxygen
r_pocket = /obj/item/flashlight/pen
-
diff --git a/code/modules/jobs/job_types/clown.dm b/code/modules/jobs/job_types/clown.dm
index a7ea5b08a38de..b5c34bf771a06 100644
--- a/code/modules/jobs/job_types/clown.dm
+++ b/code/modules/jobs/job_types/clown.dm
@@ -18,7 +18,11 @@
paycheck_department = ACCOUNT_SRV
display_order = JOB_DISPLAY_ORDER_CLOWN
+ departments = DEPARTMENT_SERVICE
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/honk
+ )
/datum/job/clown/after_spawn(mob/living/carbon/human/H, mob/M)
. = ..()
@@ -52,6 +56,11 @@
chameleon_extras = /obj/item/stamp/clown
+/datum/outfit/job/clown/pre_equip(mob/living/carbon/human/H, visualsOnly)
+ . = ..()
+ if(HAS_TRAIT(SSstation, STATION_TRAIT_BANANIUM_SHIPMENTS))
+ backpack_contents[/obj/item/stack/sheet/mineral/bananium/five] = 1
+
/datum/outfit/job/clown/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
..()
if(visualsOnly)
diff --git a/code/modules/jobs/job_types/cook.dm b/code/modules/jobs/job_types/cook.dm
index 0afd7ec0c662b..2ed7549de09d0 100644
--- a/code/modules/jobs/job_types/cook.dm
+++ b/code/modules/jobs/job_types/cook.dm
@@ -19,7 +19,11 @@
paycheck_department = ACCOUNT_SRV
display_order = JOB_DISPLAY_ORDER_COOK
+ departments = DEPARTMENT_SERVICE
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/chef
+ )
/datum/outfit/job/cook
name = "Cook"
jobtype = /datum/job/cook
@@ -50,7 +54,7 @@
var/list/possible_boxes = subtypesof(/obj/item/storage/box/ingredients)
var/chosen_box = pick(possible_boxes)
var/obj/item/storage/box/I = new chosen_box(src)
- H.equip_to_slot_or_del(I,SLOT_IN_BACKPACK)
+ H.equip_to_slot_or_del(I,ITEM_SLOT_BACKPACK)
var/datum/martial_art/cqc/under_siege/justacook = new
justacook.teach(H)
diff --git a/code/modules/jobs/job_types/curator.dm b/code/modules/jobs/job_types/curator.dm
index 31ce98e0d438f..26d3c2ed85d13 100644
--- a/code/modules/jobs/job_types/curator.dm
+++ b/code/modules/jobs/job_types/curator.dm
@@ -12,13 +12,17 @@
outfit = /datum/outfit/job/curator
- access = list(ACCESS_LIBRARY, ACCESS_CONSTRUCTION, ACCESS_MINING_STATION)
- minimal_access = list(ACCESS_LIBRARY, ACCESS_CONSTRUCTION, ACCESS_MINING_STATION)
+ access = list(ACCESS_LIBRARY, ACCESS_AUX_BASE, ACCESS_MINING_STATION)
+ minimal_access = list(ACCESS_LIBRARY, ACCESS_AUX_BASE, ACCESS_MINING_STATION)
paycheck = PAYCHECK_EASY
paycheck_department = ACCOUNT_CIV
display_order = JOB_DISPLAY_ORDER_CURATOR
+ departments = DEPARTMENT_SERVICE
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/curator
+ )
/datum/outfit/job/curator
name = "Curator"
jobtype = /datum/job/curator
@@ -26,7 +30,7 @@
id = /obj/item/card/id/job/chap
shoes = /obj/item/clothing/shoes/laceup
belt = /obj/item/pda/curator
- ears = /obj/item/radio/headset/headset_srv
+ ears = /obj/item/radio/headset/headset_curator
uniform = /obj/item/clothing/under/rank/civilian/curator
l_hand = /obj/item/storage/bag/books
r_pocket = /obj/item/key/displaycase
diff --git a/code/modules/jobs/job_types/cyborg.dm b/code/modules/jobs/job_types/cyborg.dm
index 70d96cc07349d..0e262badb9225 100644
--- a/code/modules/jobs/job_types/cyborg.dm
+++ b/code/modules/jobs/job_types/cyborg.dm
@@ -11,8 +11,10 @@
minimal_player_age = 21
exp_requirements = 120
exp_type = EXP_TYPE_CREW
+ random_spawns_possible = FALSE
display_order = JOB_DISPLAY_ORDER_CYBORG
+ departments = DEPARTMENT_SILICON
/datum/job/cyborg/equip(mob/living/carbon/human/H, visualsOnly = FALSE, announce = TRUE, latejoin = FALSE, datum/outfit/outfit_override = null, client/preference_source = null)
if(visualsOnly)
diff --git a/code/modules/jobs/job_types/deputy.dm b/code/modules/jobs/job_types/deputy.dm
index 38d810c1be63a..6e84deef6830d 100644
--- a/code/modules/jobs/job_types/deputy.dm
+++ b/code/modules/jobs/job_types/deputy.dm
@@ -8,6 +8,7 @@
spawn_positions = 0
supervisors = "the head of security"
selection_color = "#ffeeee"
+ chat_color = "#ffeeee"
minimal_player_age = 7
exp_requirements = 180
exp_type = EXP_TYPE_CREW
@@ -21,6 +22,7 @@
mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
display_order = JOB_DISPLAY_ORDER_DEPUTY //see code/__DEFINES/jobs.dm
+ departments = DEPARTMENT_SECURITY
/datum/outfit/job/deputy
name = "Deputy"
@@ -39,22 +41,9 @@
satchel = /obj/item/storage/backpack/satchel/sec
duffelbag = /obj/item/storage/backpack/duffelbag/sec
box = /obj/item/storage/box/survival
-
-/obj/item/card/deputy_access_card
- name = "deputy assignment card"
- desc = "A small card, that when used on any ID, will grant basic security access and the role of Deputy."
- icon_state = "data_1"
-/obj/item/card/deputy_access_card/afterattack(atom/movable/AM, mob/user, proximity)
- . = ..()
- if(istype(AM, /obj/item/card/id) && proximity)
- var/obj/item/card/id/I = AM
- I.assignment = "Deputy"
- I.access |= ACCESS_SEC_DOORS
- I.access |= ACCESS_MAINT_TUNNELS
- I.access |= ACCESS_COURT
- I.access |= ACCESS_BRIG
- I.access |= ACCESS_WEAPONS
- to_chat(user, "You have been assigned as deputy.")
- log_id("[key_name(user)] added basic security access to '[I]' using [src] at [AREACOORD(user)].")
- qdel(src)
+/obj/item/card/id/pass/deputy
+ name = "deputy promotion card"
+ desc = "A small card, that when used on an ID, will grant basic security access, and the job title of 'Deputy.'"
+ assignment = "Deputy"
+ access = list(ACCESS_SEC_DOORS, ACCESS_MAINT_TUNNELS, ACCESS_COURT, ACCESS_BRIG, ACCESS_WEAPONS)
\ No newline at end of file
diff --git a/code/modules/jobs/job_types/detective.dm b/code/modules/jobs/job_types/detective.dm
index 737f68d201aab..24fe0fc991f08 100644
--- a/code/modules/jobs/job_types/detective.dm
+++ b/code/modules/jobs/job_types/detective.dm
@@ -17,14 +17,18 @@
outfit = /datum/outfit/job/detective
- access = list(ACCESS_SEC_DOORS, ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_MECH_SECURITY, ACCESS_COURT, ACCESS_BRIG, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM, ACCESS_KEYCARD_AUTH)
- minimal_access = list(ACCESS_SEC_DOORS, ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_MECH_SECURITY, ACCESS_COURT, ACCESS_BRIG, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM, ACCESS_KEYCARD_AUTH)
+ access = list(ACCESS_SEC_DOORS, ACCESS_SEC_RECORDS, ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_MECH_SECURITY, ACCESS_COURT, ACCESS_BRIG, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_SEC_DOORS, ACCESS_SEC_RECORDS, ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_MECH_SECURITY, ACCESS_COURT, ACCESS_BRIG, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM)
paycheck = PAYCHECK_MEDIUM
paycheck_department = ACCOUNT_SEC
mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
display_order = JOB_DISPLAY_ORDER_DETECTIVE
+ departments = DEPARTMENT_SECURITY
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/detective
+ )
/datum/outfit/job/detective
name = "Detective"
jobtype = /datum/job/detective
diff --git a/code/modules/jobs/job_types/emt.dm b/code/modules/jobs/job_types/emt.dm
index 713da7dfd3880..6f53305daa816 100644
--- a/code/modules/jobs/job_types/emt.dm
+++ b/code/modules/jobs/job_types/emt.dm
@@ -9,16 +9,24 @@
supervisors = "the chief medical officer"
selection_color = "#d4ebf2"
chat_color = "#8FBEB4"
+ exp_requirements = 120
+ exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/emt
- access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CLONING, ACCESS_MECH_MEDICAL, ACCESS_MINERAL_STOREROOM, ACCESS_MAINT_TUNNELS, ACCESS_EVA, ACCESS_EXTERNAL_AIRLOCKS)
- minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_CLONING, ACCESS_MECH_MEDICAL, ACCESS_MAINT_TUNNELS, ACCESS_EVA, ACCESS_EXTERNAL_AIRLOCKS)
+ access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CLONING, ACCESS_MECH_MEDICAL, ACCESS_MINERAL_STOREROOM,
+ ACCESS_MAINT_TUNNELS, ACCESS_EVA, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_AUX_BASE)
+ minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_CLONING, ACCESS_MECH_MEDICAL, ACCESS_MAINT_TUNNELS,
+ ACCESS_EVA, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_AUX_BASE)
paycheck = PAYCHECK_MEDIUM
paycheck_department = ACCOUNT_MED
display_order = JOB_DISPLAY_ORDER_MEDICAL_DOCTOR
+ departments = DEPARTMENT_MEDICAL
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/emt
+ )
/datum/outfit/job/emt
name = "Paramedic"
jobtype = /datum/job/emt
@@ -26,6 +34,7 @@
id = /obj/item/card/id/job/med
belt = /obj/item/pda/medical
ears = /obj/item/radio/headset/headset_med
+ gloves = /obj/item/clothing/gloves/color/latex/nitrile
uniform = /obj/item/clothing/under/rank/medical/emt
shoes = /obj/item/clothing/shoes/sneakers/white
head = /obj/item/clothing/head/soft/emt
diff --git a/code/modules/jobs/job_types/exploration_team.dm b/code/modules/jobs/job_types/exploration_team.dm
new file mode 100644
index 0000000000000..fd824a0a0d753
--- /dev/null
+++ b/code/modules/jobs/job_types/exploration_team.dm
@@ -0,0 +1,128 @@
+/datum/job/exploration
+ title = "Exploration Crew"
+ flag = EXPLORATION_CREW
+ department_head = list("Research Director")
+ department_flag = MEDSCI
+ faction = "Station"
+ total_positions = 3
+ spawn_positions = 3
+ supervisors = "the research director"
+ selection_color = "#ffeeff"
+ chat_color = "#85d8b8"
+
+ outfit = /datum/outfit/job/exploration
+
+ access = list(ACCESS_MAINT_TUNNELS, ACCESS_RESEARCH, ACCESS_EXPLORATION, ACCESS_TOX, ACCESS_MECH_SCIENCE, ACCESS_XENOBIOLOGY)
+ minimal_access = list(ACCESS_RESEARCH, ACCESS_EXPLORATION, ACCESS_TOX, ACCESS_MECH_SCIENCE)
+ paycheck = PAYCHECK_HARD
+ paycheck_department = ACCOUNT_SCI
+
+ display_order = JOB_DISPLAY_ORDER_EXPLORATION
+ departments = DEPARTMENT_SCIENCE
+
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/exploration
+ )
+
+/datum/job/exploration/equip(mob/living/carbon/human/H, visualsOnly, announce, latejoin, datum/outfit/outfit_override, client/preference_source)
+ if(outfit_override)
+ return ..()
+ if(visualsOnly || latejoin)
+ return ..()
+ var/static/exploration_job_id = 0
+ exploration_job_id ++
+ switch(exploration_job_id)
+ //Scientist is most important due to scanner
+ if(1)
+ to_chat(H, "You are the exploration team's Scientist!")
+ to_chat(H, "Scan undiscovered creates to gain discovery research points!")
+ outfit_override = /datum/outfit/job/exploration/scientist
+ if(2)
+ to_chat(H, "You are the exploration team's Medical Doctor!")
+ to_chat(H, "Ensure your team's health by locating and healing injured team members.")
+ outfit_override = /datum/outfit/job/exploration/medic
+ if(3)
+ to_chat(H, "You are the exploration team's Engineer!")
+ to_chat(H, "Create entry points with your explosives and maintain the hull of your ship.")
+ outfit_override = /datum/outfit/job/exploration/engineer
+ . = ..(H, visualsOnly, announce, latejoin, outfit_override, preference_source)
+
+/datum/outfit/job/exploration
+ name = "Exploration Crew"
+ jobtype = /datum/job/exploration
+
+ id = /obj/item/card/id/job/exploration
+ belt = /obj/item/pda/exploration
+ ears = /obj/item/radio/headset/headset_exploration
+ shoes = /obj/item/clothing/shoes/jackboots
+ gloves = /obj/item/clothing/gloves/color/black
+ uniform = /obj/item/clothing/under/rank/cargo/exploration
+ backpack_contents = list(
+ /obj/item/kitchen/knife/combat/survival=1,\
+ /obj/item/stack/marker_beacon/thirty=1)
+ l_pocket = /obj/item/gps/mining/exploration
+ r_pocket = /obj/item/gun/energy/e_gun/mini/exploration
+
+ backpack = /obj/item/storage/backpack/explorer
+ satchel = /obj/item/storage/backpack/satchel/explorer
+ duffelbag = /obj/item/storage/backpack/duffelbag
+
+ chameleon_extras = /obj/item/gun/energy/e_gun/mini/exploration
+
+/datum/outfit/job/exploration/engineer
+ name = "Exploration Crew (Engineer)"
+
+ belt = /obj/item/storage/belt/utility/full
+ r_pocket = /obj/item/pda/exploration
+
+ backpack_contents = list(
+ /obj/item/kitchen/knife/combat/survival=1,
+ /obj/item/stack/marker_beacon/thirty=1,
+ /obj/item/gun/energy/e_gun/mini/exploration=1,
+ /obj/item/grenade/exploration=3, //Breaching charges for entering ruins
+ /obj/item/exploration_detonator=1, //Detonator for the breaching charges.
+ /obj/item/discovery_scanner=1
+ )
+
+ backpack = /obj/item/storage/backpack/industrial
+ satchel = /obj/item/storage/backpack/satchel/eng
+ duffelbag = /obj/item/storage/backpack/duffelbag/engineering
+
+/datum/outfit/job/exploration/scientist
+ name = "Exploration Crew (Scientist)"
+
+ glasses = /obj/item/clothing/glasses/science
+
+ backpack_contents = list(
+ /obj/item/kitchen/knife/combat/survival=1,
+ /obj/item/stack/marker_beacon/thirty=1,
+ /obj/item/discovery_scanner=1,
+ /obj/item/sbeacondrop/exploration=1, //Spawns in a bluespace beacon
+ /obj/item/research_disk_pinpointer=1 //Locates research disks
+ )
+
+ backpack = /obj/item/storage/backpack/science
+ satchel = /obj/item/storage/backpack/satchel/tox
+
+/datum/outfit/job/exploration/medic
+ name = "Exploration Crew (Medical Doctor)"
+
+ backpack_contents = list(
+ /obj/item/kitchen/knife/combat/survival=1,
+ /obj/item/stack/marker_beacon/thirty=1,
+ /obj/item/storage/firstaid/medical=1,
+ /obj/item/pinpointer/crew=1,
+ /obj/item/sensor_device=1,
+ /obj/item/roller=1,
+ /obj/item/discovery_scanner=1
+ )
+
+ backpack = /obj/item/storage/backpack/medic
+ satchel = /obj/item/storage/backpack/satchel/med
+ duffelbag = /obj/item/storage/backpack/duffelbag/med
+
+/datum/outfit/job/exploration/hardsuit
+ name = "Exploration Crew (Hardsuit)"
+ suit = /obj/item/clothing/suit/space/hardsuit/exploration
+ suit_store = /obj/item/tank/internals/emergency_oxygen/double
+ mask = /obj/item/clothing/mask/breath
diff --git a/code/modules/jobs/job_types/geneticist.dm b/code/modules/jobs/job_types/geneticist.dm
index 9e8e2e31d39ec..5e4752d8f2ea5 100644
--- a/code/modules/jobs/job_types/geneticist.dm
+++ b/code/modules/jobs/job_types/geneticist.dm
@@ -9,6 +9,7 @@
supervisors = "the chief medical officer and research director"
selection_color = "#d4ebf2"
chat_color = "#83BBBF"
+ exp_requirements = 120
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/geneticist
@@ -19,7 +20,11 @@
paycheck_department = ACCOUNT_MED
display_order = JOB_DISPLAY_ORDER_GENETICIST
+ departments = DEPARTMENT_MEDICAL
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/genetics
+ )
/datum/outfit/job/geneticist
name = "Geneticist"
jobtype = /datum/job/geneticist
diff --git a/code/modules/jobs/job_types/gimmick.dm b/code/modules/jobs/job_types/gimmick.dm
index f7d4eb45729be..82dfe3fe706be 100644
--- a/code/modules/jobs/job_types/gimmick.dm
+++ b/code/modules/jobs/job_types/gimmick.dm
@@ -7,17 +7,23 @@
spawn_positions = 0
supervisors = "no one"
selection_color = "#dddddd"
+ chat_color = "#FFFFFF"
- access = list( ACCESS_MAINT_TUNNELS)
+ exp_type_department = EXP_TYPE_GIMMICK
+
+ access = list(ACCESS_MAINT_TUNNELS)
minimal_access = list(ACCESS_MAINT_TUNNELS)
paycheck = PAYCHECK_ASSISTANT
paycheck_department = ACCOUNT_CIV
display_order = JOB_DISPLAY_ORDER_ASSISTANT
+ departments = DEPARTMENT_SERVICE
+
+ allow_bureaucratic_error = FALSE
+ outfit = /datum/outfit/job/gimmick
-/datum/job/gimmick/New()
- . = ..()
- GLOB.civilian_positions |= title
+/datum/outfit/job/gimmick
+ can_be_admin_equipped = FALSE // we want just the parent outfit to be unequippable since this leads to problems
/datum/job/gimmick/barber
title = "Barber"
@@ -26,6 +32,11 @@
access = list(ACCESS_MORGUE, ACCESS_MAINT_TUNNELS)
minimal_access = list(ACCESS_MORGUE, ACCESS_MAINT_TUNNELS)
gimmick = TRUE
+ chat_color = "#bd9e86"
+
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman
+ )
/datum/outfit/job/gimmick/barber
name = "Barber"
@@ -37,6 +48,7 @@
shoes = /obj/item/clothing/shoes/laceup
l_hand = /obj/item/storage/wallet
l_pocket = /obj/item/razor/straightrazor
+ can_be_admin_equipped = TRUE
/datum/job/gimmick/magician
title = "Stage Magician"
@@ -45,6 +57,11 @@
access = list(ACCESS_THEATRE, ACCESS_MAINT_TUNNELS)
minimal_access = list(ACCESS_THEATRE, ACCESS_MAINT_TUNNELS)
gimmick = TRUE
+ chat_color = "#b898b3"
+
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/magic
+ )
/datum/outfit/job/gimmick/magician
name = "Stage Magician"
@@ -59,6 +76,7 @@
gloves = /obj/item/clothing/gloves/color/white
l_hand = /obj/item/cane
backpack_contents = list(/obj/item/choice_beacon/magic=1)
+ can_be_admin_equipped = TRUE
/datum/job/gimmick/hobo
title = "Debtor"
@@ -67,6 +85,12 @@
access = list(ACCESS_MAINT_TUNNELS)
minimal_access = list(ACCESS_MAINT_TUNNELS)
gimmick = TRUE
+ chat_color = "#929292"
+ departments = NONE //being hobo is not a real job
+
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/hobo
+ )
/datum/outfit/job/gimmick/hobo
name = "Debtor"
@@ -76,7 +100,7 @@
ears = null //hobos dont start with a headset
uniform = /obj/item/clothing/under/pants/jeans
suit = /obj/item/clothing/suit/jacket
-
+ can_be_admin_equipped = TRUE
/datum/outfit/job/gimmick/hobo/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
..()
@@ -86,7 +110,7 @@
var/list/possible_drugs = list(/obj/item/storage/pill_bottle/happy, /obj/item/storage/pill_bottle/zoom, /obj/item/storage/pill_bottle/stimulant, /obj/item/storage/pill_bottle/lsd, /obj/item/storage/pill_bottle/aranesp, /obj/item/storage/pill_bottle/floorpill/full)
var/chosen_drugs = pick(possible_drugs)
var/obj/item/storage/pill_bottle/I = new chosen_drugs(src)
- H.equip_to_slot_or_del(I,SLOT_IN_BACKPACK)
+ H.equip_to_slot_or_del(I,ITEM_SLOT_BACKPACK)
var/datum/martial_art/psychotic_brawling/junkie = new //this fits well, but i'm unsure about it, cuz this martial art is so fucking rng dependent i swear...
junkie.teach(H)
ADD_TRAIT(H, TRAIT_APPRAISAL, JOB_TRAIT)
@@ -100,6 +124,12 @@
minimal_access = list(ACCESS_MAINT_TUNNELS, ACCESS_MEDICAL)
paycheck = PAYCHECK_EASY
gimmick = TRUE
+ chat_color = "#a2dfdc"
+ departments = DEPARTMENT_MEDICAL
+
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman
+ )
/datum/outfit/job/gimmick/shrink //psychiatrist doesnt get much shit, but he has more access and a cushier paycheck
name = "Psychiatrist"
@@ -109,15 +139,22 @@
ears = /obj/item/radio/headset/headset_med
uniform = /obj/item/clothing/under/suit/black
shoes = /obj/item/clothing/shoes/laceup
+ backpack_contents = list(/obj/item/choice_beacon/pet/ems=1)
+ can_be_admin_equipped = TRUE
/datum/job/gimmick/celebrity
title = "VIP"
flag = CELEBRITY
outfit = /datum/outfit/job/gimmick/celebrity
- access = list(ACCESS_HEADS, ACCESS_MAINT_TUNNELS) //there is no way whatsoever this could go wrong
- minimal_access = list(ACCESS_HEADS, ACCESS_MAINT_TUNNELS)
+ access = list(ACCESS_MAINT_TUNNELS) //Assistants with shitloads of money, what could go wrong?
+ minimal_access = list(ACCESS_MAINT_TUNNELS)
gimmick = TRUE
paycheck = PAYCHECK_VIP //our power is being fucking rich
+ chat_color = "#ebc96b"
+
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/vip
+ )
/datum/outfit/job/gimmick/celebrity
name = "VIP"
@@ -128,5 +165,4 @@
ears = /obj/item/radio/headset/heads //VIP can talk loud for no reason
uniform = /obj/item/clothing/under/suit/black_really
shoes = /obj/item/clothing/shoes/laceup
-
- implants = list(/obj/item/implant/mindshield) //this fuck gets a mindshield, but he isn't necessarily antag-proof
\ No newline at end of file
+ can_be_admin_equipped = TRUE
diff --git a/code/modules/jobs/job_types/head_of_personnel.dm b/code/modules/jobs/job_types/head_of_personnel.dm
index f37feebf1cf77..41f1d4ed3341d 100644
--- a/code/modules/jobs/job_types/head_of_personnel.dm
+++ b/code/modules/jobs/job_types/head_of_personnel.dm
@@ -14,8 +14,8 @@
req_admin_notify = 1
minimal_player_age = 10
exp_requirements = 600
- exp_type = EXP_TYPE_SERVICE
- exp_type_department = EXP_TYPE_SERVICE
+ exp_type = EXP_TYPE_COMMAND
+ exp_type_department = EXP_TYPE_COMMAND
outfit = /datum/outfit/job/hop
@@ -24,19 +24,24 @@
ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_MAINT_TUNNELS, ACCESS_BAR, ACCESS_JANITOR, ACCESS_CONSTRUCTION, ACCESS_MORGUE,
ACCESS_CREMATORIUM, ACCESS_KITCHEN, ACCESS_CARGO, ACCESS_MAILSORTING, ACCESS_QM, ACCESS_HYDROPONICS, ACCESS_LAWYER,
ACCESS_THEATRE, ACCESS_CHAPEL_OFFICE, ACCESS_LIBRARY, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_VAULT, ACCESS_MINING_STATION,
- ACCESS_MECH_MINING, ACCESS_MECH_ENGINE, ACCESS_MECH_SCIENCE, ACCESS_MECH_SECURITY, ACCESS_MECH_MEDICAL,
- ACCESS_HOP, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM)
+ ACCESS_MECH_MINING, ACCESS_MECH_ENGINE, ACCESS_MECH_SCIENCE, ACCESS_MECH_SECURITY, ACCESS_MECH_MEDICAL, ACCESS_EXPLORATION,
+ ACCESS_HOP, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM, ACCESS_AUX_BASE)
minimal_access = list(ACCESS_SEC_DOORS, ACCESS_COURT, ACCESS_WEAPONS,
ACCESS_MEDICAL, ACCESS_ENGINE, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, ACCESS_EVA, ACCESS_HEADS,
ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_MAINT_TUNNELS, ACCESS_BAR, ACCESS_JANITOR, ACCESS_CONSTRUCTION, ACCESS_MORGUE,
ACCESS_CREMATORIUM, ACCESS_KITCHEN, ACCESS_CARGO, ACCESS_MAILSORTING, ACCESS_QM, ACCESS_HYDROPONICS, ACCESS_LAWYER,
- ACCESS_MECH_MINING, ACCESS_MECH_ENGINE, ACCESS_MECH_SCIENCE, ACCESS_MECH_SECURITY, ACCESS_MECH_MEDICAL,
+ ACCESS_MECH_MINING, ACCESS_MECH_ENGINE, ACCESS_MECH_SCIENCE, ACCESS_MECH_SECURITY, ACCESS_MECH_MEDICAL, ACCESS_EXPLORATION,
ACCESS_THEATRE, ACCESS_CHAPEL_OFFICE, ACCESS_LIBRARY, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_VAULT, ACCESS_MINING_STATION,
- ACCESS_HOP, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM)
+ ACCESS_HOP, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM, ACCESS_AUX_BASE)
paycheck = PAYCHECK_COMMAND
paycheck_department = ACCOUNT_SRV
display_order = JOB_DISPLAY_ORDER_HEAD_OF_PERSONNEL
+ departments = DEPARTMENT_COMMAND | DEPARTMENT_SERVICE
+
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/hop
+ )
/datum/outfit/job/hop
name = "Head of Personnel"
diff --git a/code/modules/jobs/job_types/head_of_security.dm b/code/modules/jobs/job_types/head_of_security.dm
index 8b6aec2e01af7..88550c7663656 100644
--- a/code/modules/jobs/job_types/head_of_security.dm
+++ b/code/modules/jobs/job_types/head_of_security.dm
@@ -20,18 +20,23 @@
outfit = /datum/outfit/job/hos
mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
- access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_WEAPONS, ACCESS_MECH_SECURITY,
- ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_ALL_PERSONAL_LOCKERS,
+ access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_SEC_RECORDS, ACCESS_BRIG, ACCESS_BRIGPHYS, ACCESS_ARMORY, ACCESS_COURT, ACCESS_WEAPONS, ACCESS_MECH_SECURITY,
+ ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_AUX_BASE,
ACCESS_RESEARCH, ACCESS_ENGINE, ACCESS_MINING, ACCESS_MEDICAL, ACCESS_CONSTRUCTION, ACCESS_MAILSORTING,
ACCESS_HEADS, ACCESS_HOS, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_WEAPONS, ACCESS_MECH_SECURITY,
- ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_ALL_PERSONAL_LOCKERS,
+ minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_SEC_RECORDS, ACCESS_BRIG, ACCESS_BRIGPHYS, ACCESS_ARMORY, ACCESS_COURT, ACCESS_WEAPONS, ACCESS_MECH_SECURITY,
+ ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_AUX_BASE,
ACCESS_RESEARCH, ACCESS_ENGINE, ACCESS_MINING, ACCESS_MEDICAL, ACCESS_CONSTRUCTION, ACCESS_MAILSORTING,
ACCESS_HEADS, ACCESS_HOS, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM)
paycheck = PAYCHECK_COMMAND
paycheck_department = ACCOUNT_SEC
display_order = JOB_DISPLAY_ORDER_HEAD_OF_SECURITY
+ departments = DEPARTMENT_SECURITY | DEPARTMENT_COMMAND
+
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/hos
+ )
/datum/outfit/job/hos
name = "Head of Security"
@@ -49,7 +54,7 @@
suit_store = /obj/item/gun/energy/e_gun
r_pocket = /obj/item/assembly/flash/handheld
l_pocket = /obj/item/restraints/handcuffs
- backpack_contents = list(/obj/item/melee/baton/loaded=1, /obj/item/club=1)
+ backpack_contents = list(/obj/item/melee/baton/loaded=1)
backpack = /obj/item/storage/backpack/security
satchel = /obj/item/storage/backpack/satchel/sec
@@ -66,5 +71,5 @@
mask = /obj/item/clothing/mask/gas/sechailer
suit = /obj/item/clothing/suit/space/hardsuit/security/hos
suit_store = /obj/item/tank/internals/oxygen
- backpack_contents = list(/obj/item/melee/baton/loaded=1, /obj/item/gun/energy/e_gun=1)
+ backpack_contents = list(/obj/item/melee/baton/loaded=1)
diff --git a/code/modules/jobs/job_types/janitor.dm b/code/modules/jobs/job_types/janitor.dm
index 9677931605041..14e2f8dfa412d 100644
--- a/code/modules/jobs/job_types/janitor.dm
+++ b/code/modules/jobs/job_types/janitor.dm
@@ -18,6 +18,11 @@
paycheck_department = ACCOUNT_SRV
display_order = JOB_DISPLAY_ORDER_JANITOR
+ departments = DEPARTMENT_SERVICE
+
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/janitor
+ )
/datum/outfit/job/janitor
name = "Janitor"
@@ -28,3 +33,9 @@
ears = /obj/item/radio/headset/headset_srv
uniform = /obj/item/clothing/under/rank/civilian/janitor
backpack_contents = list(/obj/item/modular_computer/tablet/preset/advanced=1)
+
+/datum/outfit/job/janitor/pre_equip(mob/living/carbon/human/H, visualsOnly)
+ . = ..()
+ if(GARBAGEDAY in SSevents.holidays)
+ l_pocket = /obj/item/gun/ballistic/revolver
+ r_pocket = /obj/item/ammo_box/a357
diff --git a/code/modules/jobs/job_types/lawyer.dm b/code/modules/jobs/job_types/lawyer.dm
index 04ab7ebe2a8b7..13bcb6e6e4c32 100644
--- a/code/modules/jobs/job_types/lawyer.dm
+++ b/code/modules/jobs/job_types/lawyer.dm
@@ -20,6 +20,11 @@
mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
display_order = JOB_DISPLAY_ORDER_LAWYER
+ departments = DEPARTMENT_SERVICE
+
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman //they dont have one.
+ )
/datum/outfit/job/lawyer
name = "Lawyer"
diff --git a/code/modules/jobs/job_types/medical_doctor.dm b/code/modules/jobs/job_types/medical_doctor.dm
index 18f7850138664..650ffa7aa2cdb 100644
--- a/code/modules/jobs/job_types/medical_doctor.dm
+++ b/code/modules/jobs/job_types/medical_doctor.dm
@@ -9,6 +9,8 @@
supervisors = "the chief medical officer"
selection_color = "#d4ebf2"
chat_color = "#6CB1C5"
+ exp_requirements = 120
+ exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/doctor
@@ -18,6 +20,11 @@
paycheck_department = ACCOUNT_MED
display_order = JOB_DISPLAY_ORDER_MEDICAL_DOCTOR
+ departments = DEPARTMENT_MEDICAL
+
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/medical
+ )
/datum/outfit/job/doctor
name = "Medical Doctor"
@@ -29,6 +36,7 @@
uniform = /obj/item/clothing/under/rank/medical/doctor
shoes = /obj/item/clothing/shoes/sneakers/white
suit = /obj/item/clothing/suit/toggle/labcoat
+ gloves = /obj/item/clothing/gloves/color/latex/nitrile
l_hand = /obj/item/storage/firstaid/medical
suit_store = /obj/item/flashlight/pen
diff --git a/code/modules/jobs/job_types/mime.dm b/code/modules/jobs/job_types/mime.dm
index e21b83c165429..c39841175d81b 100644
--- a/code/modules/jobs/job_types/mime.dm
+++ b/code/modules/jobs/job_types/mime.dm
@@ -18,6 +18,11 @@
paycheck_department = ACCOUNT_SRV
display_order = JOB_DISPLAY_ORDER_MIME
+ departments = DEPARTMENT_SERVICE
+
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/mime
+ )
/datum/job/mime/after_spawn(mob/living/carbon/human/H, mob/M)
. = ..()
diff --git a/code/modules/jobs/job_types/quartermaster.dm b/code/modules/jobs/job_types/quartermaster.dm
index f6d2b1a0ee28b..7d97628d47bc7 100644
--- a/code/modules/jobs/job_types/quartermaster.dm
+++ b/code/modules/jobs/job_types/quartermaster.dm
@@ -15,12 +15,17 @@
outfit = /datum/outfit/job/quartermaster
- access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_QM, ACCESS_MINING, ACCESS_MECH_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM, ACCESS_VAULT)
- minimal_access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_QM, ACCESS_MINING, ACCESS_MECH_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM, ACCESS_VAULT)
+ access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_QM, ACCESS_MINING, ACCESS_MECH_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM, ACCESS_VAULT, ACCESS_AUX_BASE, ACCESS_EXPLORATION)
+ minimal_access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_QM, ACCESS_MINING, ACCESS_MECH_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM, ACCESS_VAULT, ACCESS_AUX_BASE, ACCESS_EXPLORATION)
paycheck = PAYCHECK_MEDIUM
paycheck_department = ACCOUNT_CAR
display_order = JOB_DISPLAY_ORDER_QUARTERMASTER
+ departments = DEPARTMENT_CARGO
+
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/cargo
+ )
/datum/outfit/job/quartermaster
name = "Quartermaster"
@@ -28,7 +33,7 @@
id = /obj/item/card/id/job/qm
belt = /obj/item/pda/quartermaster
- ears = /obj/item/radio/headset/headset_cargo
+ ears = /obj/item/radio/headset/headset_quartermaster
uniform = /obj/item/clothing/under/rank/cargo/qm
shoes = /obj/item/clothing/shoes/sneakers/brown
glasses = /obj/item/clothing/glasses/sunglasses/advanced
diff --git a/code/modules/jobs/job_types/research_director.dm b/code/modules/jobs/job_types/research_director.dm
index 22ea32e5652e7..91c5ed0926d5a 100644
--- a/code/modules/jobs/job_types/research_director.dm
+++ b/code/modules/jobs/job_types/research_director.dm
@@ -19,21 +19,25 @@
outfit = /datum/outfit/job/rd
- access = list(ACCESS_RD, ACCESS_HEADS, ACCESS_TOX, ACCESS_GENETICS, ACCESS_MORGUE,
+ access = list(ACCESS_RD, ACCESS_HEADS, ACCESS_TOX, ACCESS_GENETICS, ACCESS_MORGUE, ACCESS_EXPLORATION,
ACCESS_TOX_STORAGE, ACCESS_TELEPORTER, ACCESS_SEC_DOORS, ACCESS_MECH_SCIENCE,
ACCESS_RESEARCH, ACCESS_ROBOTICS, ACCESS_XENOBIOLOGY, ACCESS_AI_UPLOAD,
ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM,
- ACCESS_TECH_STORAGE, ACCESS_MINISAT, ACCESS_MAINT_TUNNELS, ACCESS_NETWORK)
- minimal_access = list(ACCESS_RD, ACCESS_HEADS, ACCESS_TOX, ACCESS_GENETICS, ACCESS_MORGUE,
+ ACCESS_TECH_STORAGE, ACCESS_MINISAT, ACCESS_MAINT_TUNNELS, ACCESS_NETWORK, ACCESS_AUX_BASE)
+ minimal_access = list(ACCESS_RD, ACCESS_HEADS, ACCESS_TOX, ACCESS_GENETICS, ACCESS_MORGUE, ACCESS_EXPLORATION,
ACCESS_TOX_STORAGE, ACCESS_TELEPORTER, ACCESS_SEC_DOORS, ACCESS_MECH_SCIENCE,
ACCESS_RESEARCH, ACCESS_ROBOTICS, ACCESS_XENOBIOLOGY, ACCESS_AI_UPLOAD,
ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MINERAL_STOREROOM,
- ACCESS_TECH_STORAGE, ACCESS_MINISAT, ACCESS_MAINT_TUNNELS, ACCESS_NETWORK)
+ ACCESS_TECH_STORAGE, ACCESS_MINISAT, ACCESS_MAINT_TUNNELS, ACCESS_NETWORK, ACCESS_AUX_BASE)
paycheck = PAYCHECK_COMMAND
paycheck_department = ACCOUNT_SCI
display_order = JOB_DISPLAY_ORDER_RESEARCH_DIRECTOR
+ departments = DEPARTMENT_SCIENCE | DEPARTMENT_COMMAND
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/rd
+ )
/datum/outfit/job/rd
name = "Research Director"
jobtype = /datum/job/rd
@@ -60,4 +64,4 @@
mask = /obj/item/clothing/mask/breath
suit = /obj/item/clothing/suit/space/hardsuit/rd
suit_store = /obj/item/tank/internals/oxygen
- internals_slot = SLOT_S_STORE
+ internals_slot = ITEM_SLOT_SUITSTORE
diff --git a/code/modules/jobs/job_types/roboticist.dm b/code/modules/jobs/job_types/roboticist.dm
index ff86ffab83af1..fcc1344c6f1e4 100644
--- a/code/modules/jobs/job_types/roboticist.dm
+++ b/code/modules/jobs/job_types/roboticist.dm
@@ -9,16 +9,24 @@
supervisors = "the research director"
selection_color = "#ffeeff"
chat_color = "#AC71BA"
+ exp_requirements = 120
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/roboticist
- access = list(ACCESS_ROBOTICS, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_TECH_STORAGE, ACCESS_MORGUE, ACCESS_MECH_SCIENCE, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM, ACCESS_XENOBIOLOGY, ACCESS_GENETICS)
- minimal_access = list(ACCESS_ROBOTICS, ACCESS_TECH_STORAGE, ACCESS_MORGUE, ACCESS_RESEARCH, ACCESS_MECH_SCIENCE, ACCESS_MINERAL_STOREROOM)
+ access = list(ACCESS_ROBOTICS, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_TECH_STORAGE, ACCESS_MORGUE, ACCESS_MECH_SCIENCE,
+ ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM, ACCESS_XENOBIOLOGY, ACCESS_GENETICS, ACCESS_AUX_BASE)
+ minimal_access = list(ACCESS_ROBOTICS, ACCESS_TECH_STORAGE, ACCESS_MORGUE, ACCESS_RESEARCH, ACCESS_MECH_SCIENCE,
+ ACCESS_MINERAL_STOREROOM, ACCESS_AUX_BASE)
paycheck = PAYCHECK_MEDIUM
paycheck_department = ACCOUNT_SCI
display_order = JOB_DISPLAY_ORDER_ROBOTICIST
+ departments = DEPARTMENT_SCIENCE
+
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/robotics
+ )
/datum/outfit/job/roboticist
name = "Roboticist"
@@ -34,4 +42,4 @@
backpack = /obj/item/storage/backpack/science
satchel = /obj/item/storage/backpack/satchel/tox
- pda_slot = SLOT_L_STORE
+ pda_slot = ITEM_SLOT_LPOCKET
diff --git a/code/modules/jobs/job_types/scientist.dm b/code/modules/jobs/job_types/scientist.dm
index 6ba82788bcad2..ce567e0018a9c 100644
--- a/code/modules/jobs/job_types/scientist.dm
+++ b/code/modules/jobs/job_types/scientist.dm
@@ -9,16 +9,24 @@
supervisors = "the research director"
selection_color = "#ffeeff"
chat_color = "#C772C7"
+ exp_requirements = 120
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/scientist
- access = list(ACCESS_ROBOTICS, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_MECH_SCIENCE, ACCESS_MINERAL_STOREROOM, ACCESS_TECH_STORAGE, ACCESS_GENETICS)
- minimal_access = list(ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_MECH_SCIENCE, ACCESS_MINERAL_STOREROOM)
+ access = list(ACCESS_ROBOTICS, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_MECH_SCIENCE,
+ ACCESS_MINERAL_STOREROOM, ACCESS_TECH_STORAGE, ACCESS_GENETICS, ACCESS_AUX_BASE, ACCESS_EXPLORATION)
+ minimal_access = list(ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_MECH_SCIENCE,
+ ACCESS_MINERAL_STOREROOM, ACCESS_AUX_BASE, ACCESS_EXPLORATION)
paycheck = PAYCHECK_MEDIUM
paycheck_department = ACCOUNT_SCI
display_order = JOB_DISPLAY_ORDER_SCIENTIST
+ departments = DEPARTMENT_SCIENCE
+
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/science
+ )
/datum/outfit/job/scientist
name = "Scientist"
@@ -31,6 +39,12 @@
shoes = /obj/item/clothing/shoes/sneakers/white
suit = /obj/item/clothing/suit/toggle/labcoat/science
+ r_pocket = /obj/item/discovery_scanner
+
backpack = /obj/item/storage/backpack/science
satchel = /obj/item/storage/backpack/satchel/tox
+/datum/outfit/job/scientist/pre_equip(mob/living/carbon/human/H)
+ ..()
+ if(prob(0.4))
+ neck = /obj/item/clothing/neck/tie/horrible
diff --git a/code/modules/jobs/job_types/security_officer.dm b/code/modules/jobs/job_types/security_officer.dm
index f9fec0fa3ff86..c7fa4f1717bcd 100644
--- a/code/modules/jobs/job_types/security_officer.dm
+++ b/code/modules/jobs/job_types/security_officer.dm
@@ -16,14 +16,21 @@
outfit = /datum/outfit/job/security
- access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_MECH_SECURITY, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_FORENSICS_LOCKERS, ACCESS_MINERAL_STOREROOM, ACCESS_KEYCARD_AUTH)
- minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_WEAPONS, ACCESS_MECH_SECURITY, ACCESS_MINERAL_STOREROOM, ACCESS_KEYCARD_AUTH) // See /datum/job/officer/get_access()
+ access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_SEC_RECORDS, ACCESS_BRIG, ACCESS_COURT, ACCESS_MAINT_TUNNELS,
+ ACCESS_MECH_SECURITY, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_FORENSICS_LOCKERS,
+ ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_SEC_RECORDS, ACCESS_BRIG, ACCESS_COURT, ACCESS_WEAPONS,
+ ACCESS_MECH_SECURITY, ACCESS_MINERAL_STOREROOM) // See /datum/job/officer/get_access()
paycheck = PAYCHECK_HARD
paycheck_department = ACCOUNT_SEC
mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
display_order = JOB_DISPLAY_ORDER_SECURITY_OFFICER
+ departments = DEPARTMENT_SECURITY
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/security
+ )
/datum/job/officer/get_access()
var/list/L = list()
L |= ..() | check_config_for_sec_maint()
@@ -51,13 +58,13 @@ GLOBAL_LIST_INIT(available_depts, list(SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICAL, S
switch(department)
if(SEC_DEPT_SUPPLY)
ears = /obj/item/radio/headset/headset_sec/alt/department/supply
- dep_access = list(ACCESS_MAILSORTING, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_CARGO)
+ dep_access = list(ACCESS_MAILSORTING, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_CARGO, ACCESS_AUX_BASE)
destination = /area/security/checkpoint/supply
spawn_point = locate(/obj/effect/landmark/start/depsec/supply) in GLOB.department_security_spawns
accessory = /obj/item/clothing/accessory/armband/cargo
if(SEC_DEPT_ENGINEERING)
ears = /obj/item/radio/headset/headset_sec/alt/department/engi
- dep_access = list(ACCESS_CONSTRUCTION, ACCESS_ENGINE, ACCESS_ATMOSPHERICS)
+ dep_access = list(ACCESS_CONSTRUCTION, ACCESS_ENGINE, ACCESS_ATMOSPHERICS, ACCESS_AUX_BASE)
destination = /area/security/checkpoint/engineering
spawn_point = locate(/obj/effect/landmark/start/depsec/engineering) in GLOB.department_security_spawns
accessory = /obj/item/clothing/accessory/armband/engine
@@ -69,7 +76,7 @@ GLOBAL_LIST_INIT(available_depts, list(SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICAL, S
accessory = /obj/item/clothing/accessory/armband/medblue
if(SEC_DEPT_SCIENCE)
ears = /obj/item/radio/headset/headset_sec/alt/department/sci
- dep_access = list(ACCESS_RESEARCH, ACCESS_TOX)
+ dep_access = list(ACCESS_RESEARCH, ACCESS_TOX, ACCESS_AUX_BASE)
destination = /area/security/checkpoint/science
spawn_point = locate(/obj/effect/landmark/start/depsec/science) in GLOB.department_security_spawns
accessory = /obj/item/clothing/accessory/armband/science
@@ -80,7 +87,7 @@ GLOBAL_LIST_INIT(available_depts, list(SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICAL, S
if(ears)
if(H.ears)
qdel(H.ears)
- H.equip_to_slot_or_del(new ears(H),SLOT_EARS)
+ H.equip_to_slot_or_del(new ears(H),ITEM_SLOT_EARS)
var/obj/item/card/id/W = H.wear_id
W.access |= dep_access
@@ -125,7 +132,7 @@ GLOBAL_LIST_INIT(available_depts, list(SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICAL, S
l_pocket = /obj/item/restraints/handcuffs
r_pocket = /obj/item/assembly/flash/handheld
suit_store = /obj/item/gun/energy/disabler
- backpack_contents = list(/obj/item/melee/baton/loaded=1, /obj/item/club=1)
+ backpack_contents = list(/obj/item/melee/baton/loaded=1)
backpack = /obj/item/storage/backpack/security
satchel = /obj/item/storage/backpack/satchel/sec
diff --git a/code/modules/jobs/job_types/shaft_miner.dm b/code/modules/jobs/job_types/shaft_miner.dm
index 1c5ea2a2087ab..9384ce651f174 100644
--- a/code/modules/jobs/job_types/shaft_miner.dm
+++ b/code/modules/jobs/job_types/shaft_miner.dm
@@ -12,12 +12,19 @@
outfit = /datum/outfit/job/miner
- access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_QM, ACCESS_MINING, ACCESS_MECH_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM)
- minimal_access = list(ACCESS_MINING, ACCESS_MECH_MINING, ACCESS_MINING_STATION, ACCESS_MAILSORTING, ACCESS_MINERAL_STOREROOM)
+ access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_QM, ACCESS_MINING, ACCESS_MECH_MINING,
+ ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM, ACCESS_AUX_BASE)
+ minimal_access = list(ACCESS_MINING, ACCESS_MECH_MINING, ACCESS_MINING_STATION, ACCESS_MAILSORTING, ACCESS_MINERAL_STOREROOM,
+ ACCESS_AUX_BASE)
paycheck = PAYCHECK_HARD
paycheck_department = ACCOUNT_CAR
display_order = JOB_DISPLAY_ORDER_SHAFT_MINER
+ departments = DEPARTMENT_CARGO
+
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/mining
+ )
/datum/outfit/job/miner
name = "Shaft Miner"
@@ -50,7 +57,7 @@
mask = /obj/item/clothing/mask/gas/explorer
glasses = /obj/item/clothing/glasses/meson
suit_store = /obj/item/tank/internals/oxygen
- internals_slot = SLOT_S_STORE
+ internals_slot = ITEM_SLOT_SUITSTORE
backpack_contents = list(
/obj/item/flashlight/seclite=1,\
/obj/item/kitchen/knife/combat/survival=1,
diff --git a/code/modules/jobs/job_types/station_engineer.dm b/code/modules/jobs/job_types/station_engineer.dm
index 01dae42ad7c3b..99495da5159e2 100644
--- a/code/modules/jobs/job_types/station_engineer.dm
+++ b/code/modules/jobs/job_types/station_engineer.dm
@@ -9,18 +9,25 @@
supervisors = "the chief engineer"
selection_color = "#fff5cc"
chat_color = "#D9BC89"
+ exp_requirements = 120
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/engineer
access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, ACCESS_MECH_ENGINE,
- ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM)
+ ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM,
+ ACCESS_AUX_BASE)
minimal_access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, ACCESS_MECH_ENGINE,
- ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM)
+ ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM, ACCESS_AUX_BASE)
paycheck = PAYCHECK_MEDIUM
paycheck_department = ACCOUNT_ENG
display_order = JOB_DISPLAY_ORDER_STATION_ENGINEER
+ departments = DEPARTMENT_ENGINEERING
+
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/engineering
+ )
/datum/outfit/job/engineer
name = "Station Engineer"
@@ -39,7 +46,7 @@
satchel = /obj/item/storage/backpack/satchel/eng
duffelbag = /obj/item/storage/backpack/duffelbag/engineering
box = /obj/item/storage/box/engineer
- pda_slot = SLOT_L_STORE
+ pda_slot = ITEM_SLOT_LPOCKET
backpack_contents = list(/obj/item/modular_computer/tablet/preset/advanced=1)
/datum/outfit/job/engineer/gloved
@@ -52,5 +59,5 @@
suit = /obj/item/clothing/suit/space/hardsuit/engine
suit_store = /obj/item/tank/internals/oxygen
head = null
- internals_slot = SLOT_S_STORE
+ internals_slot = ITEM_SLOT_SUITSTORE
diff --git a/code/modules/jobs/job_types/virologist.dm b/code/modules/jobs/job_types/virologist.dm
index 8534ed0f2456f..19ea83cb4bc8f 100644
--- a/code/modules/jobs/job_types/virologist.dm
+++ b/code/modules/jobs/job_types/virologist.dm
@@ -9,8 +9,9 @@
supervisors = "the chief medical officer"
selection_color = "#d4ebf2"
chat_color = "#75AEA3"
- exp_type = EXP_TYPE_CREW
exp_requirements = 180
+ exp_type = EXP_TYPE_MEDICAL
+ exp_type_department = EXP_TYPE_MEDICAL
outfit = /datum/outfit/job/virologist
@@ -20,6 +21,11 @@
paycheck_department = ACCOUNT_MED
display_order = JOB_DISPLAY_ORDER_VIROLOGIST
+ departments = DEPARTMENT_MEDICAL
+
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/viro
+ )
/datum/outfit/job/virologist
name = "Virologist"
@@ -38,3 +44,4 @@
backpack = /obj/item/storage/backpack/virology
satchel = /obj/item/storage/backpack/satchel/vir
duffelbag = /obj/item/storage/backpack/duffelbag/med
+ pda_slot = ITEM_SLOT_RPOCKET
diff --git a/code/modules/jobs/job_types/warden.dm b/code/modules/jobs/job_types/warden.dm
index 2dca4531ca20e..6d2ebedd6fcfc 100644
--- a/code/modules/jobs/job_types/warden.dm
+++ b/code/modules/jobs/job_types/warden.dm
@@ -17,13 +17,18 @@
outfit = /datum/outfit/job/warden
- access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_MECH_SECURITY, ACCESS_MAINT_TUNNELS, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_FORENSICS_LOCKERS, ACCESS_MINERAL_STOREROOM, ACCESS_KEYCARD_AUTH)
- minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_MECH_SECURITY, ACCESS_COURT, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM, ACCESS_KEYCARD_AUTH) // See /datum/job/warden/get_access()
+ access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_SEC_RECORDS, ACCESS_BRIG, ACCESS_BRIGPHYS, ACCESS_ARMORY,ACCESS_COURT, ACCESS_MECH_SECURITY, ACCESS_MAINT_TUNNELS, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_FORENSICS_LOCKERS, ACCESS_MINERAL_STOREROOM)
+ minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_SEC_RECORDS, ACCESS_BRIG, ACCESS_BRIGPHYS, ACCESS_ARMORY, ACCESS_MECH_SECURITY, ACCESS_COURT, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM) // See /datum/job/warden/get_access()
paycheck = PAYCHECK_HARD
paycheck_department = ACCOUNT_SEC
mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
display_order = JOB_DISPLAY_ORDER_WARDEN
+ departments = DEPARTMENT_SECURITY
+
+ species_outfits = list(
+ SPECIES_PLASMAMAN = /datum/outfit/plasmaman/warden
+ )
/datum/job/warden/get_access()
var/list/L = list()
@@ -46,7 +51,7 @@
r_pocket = /obj/item/assembly/flash/handheld
l_pocket = /obj/item/restraints/handcuffs
suit_store = /obj/item/gun/energy/disabler
- backpack_contents = list(/obj/item/melee/baton/loaded=1, /obj/item/club=1)
+ backpack_contents = list(/obj/item/melee/baton/loaded=1)
backpack = /obj/item/storage/backpack/security
satchel = /obj/item/storage/backpack/satchel/sec
diff --git a/code/modules/jobs/jobs.dm b/code/modules/jobs/jobs.dm
index 021d7251c422b..0c6dc89cb1a06 100644
--- a/code/modules/jobs/jobs.dm
+++ b/code/modules/jobs/jobs.dm
@@ -19,12 +19,14 @@ GLOBAL_LIST_INIT(medical_positions, list(
"Geneticist",
"Virologist",
"Paramedic",
- "Chemist"))
+ "Chemist",
+ "Brig Physician"))
GLOBAL_LIST_INIT(science_positions, list(
"Research Director",
"Scientist",
+ "Exploration Crew",
"Roboticist"))
@@ -47,13 +49,19 @@ GLOBAL_LIST_INIT(civilian_positions, list(
"Mime",
"Assistant"))
+GLOBAL_LIST_INIT(gimmick_positions, list(
+ "Gimmick",
+ "Barber",
+ "Stage Magician",
+ "Debtor",
+ "Psychiatrist",
+ "VIP"))
GLOBAL_LIST_INIT(security_positions, list(
"Head of Security",
"Warden",
"Detective",
"Security Officer",
- "Brig Physician",
"Deputy"))
@@ -63,7 +71,7 @@ GLOBAL_LIST_INIT(nonhuman_positions, list(
ROLE_PAI))
GLOBAL_LIST_INIT(exp_jobsmap, list(
- EXP_TYPE_CREW = list("titles" = command_positions | engineering_positions | medical_positions | science_positions | supply_positions | security_positions | civilian_positions | list("AI","Cyborg")), // crew positions
+ EXP_TYPE_CREW = list("titles" = command_positions | engineering_positions | medical_positions | science_positions | supply_positions | security_positions | civilian_positions | gimmick_positions | list("AI","Cyborg")), // crew positions
EXP_TYPE_COMMAND = list("titles" = command_positions),
EXP_TYPE_ENGINEERING = list("titles" = engineering_positions),
EXP_TYPE_MEDICAL = list("titles" = medical_positions),
@@ -71,7 +79,8 @@ GLOBAL_LIST_INIT(exp_jobsmap, list(
EXP_TYPE_SUPPLY = list("titles" = supply_positions),
EXP_TYPE_SECURITY = list("titles" = security_positions),
EXP_TYPE_SILICON = list("titles" = list("AI","Cyborg")),
- EXP_TYPE_SERVICE = list("titles" = civilian_positions),
+ EXP_TYPE_SERVICE = list("titles" = civilian_positions | gimmick_positions),
+ EXP_TYPE_GIMMICK = list("titles" = gimmick_positions)
))
GLOBAL_LIST_INIT(exp_specialmap, list(
diff --git a/code/modules/keybindings/bindings_atom.dm b/code/modules/keybindings/bindings_atom.dm
index 5f3e879237d83..be3416777077d 100644
--- a/code/modules/keybindings/bindings_atom.dm
+++ b/code/modules/keybindings/bindings_atom.dm
@@ -2,7 +2,7 @@
// Only way to do that is to tie the behavior into the focus's keyLoop().
/atom/movable/keyLoop(client/user)
- if(!user.keys_held["Ctrl"])
+ if(!user.movement_locked)
var/movement_dir = NONE
for(var/_key in user.keys_held)
movement_dir = movement_dir | SSinput.movement_keys[_key]
diff --git a/code/modules/keybindings/bindings_client.dm b/code/modules/keybindings/bindings_client.dm
index a69ffbcfe76d3..81b670d618e26 100644
--- a/code/modules/keybindings/bindings_client.dm
+++ b/code/modules/keybindings/bindings_client.dm
@@ -18,7 +18,7 @@ GLOBAL_LIST_INIT(valid_keys, list(
"GamepadStart" = 1, "GamepadSelect" = 1, "Gamepad2Up" = 1, "Gamepad2Down" = 1, "Gamepad2Left" = 1, "Gamepad2Right" = 1, "Gamepad2DownLeft" = 1,
"Gamepad2DownRight" = 1, "Gamepad2UpLeft" = 1, "Gamepad2UpRight" = 1, "Gamepad2Face1" = 1, "Gamepad2Face2" = 1, "Gamepad2Face3" = 1, "Gamepad2Face4" = 1,
"Gamepad2R1" = 1, "Gamepad2R2" = 1, "Gamepad2R3" = 1, "Gamepad2L1" = 1, "Gamepad2L2" = 1, "Gamepad2L3" = 1, "Gamepad2Start" = 1, "Gamepad2Select" = 1,
- "Gamepad3Up" = 1, "Gamepad3Down" = 1, "Gamepad3Left" = 1, "Gamepad3Right" = 1, "Gamepad3DownLeft" = 1, "Gamepad3DownRight" = 1, "Gamepad3UpLeft" = 1,
+ "Gamepad3Up" = 1, "Gamepad3Down" = 1, "Gamepad3Left" = 1, "Gamepad3Right" = 1, "Gamepad3DownLeft" = 1, "Gamepad3DownRight" = 1, "Gamepad3UpLeft" = 1,
"Gamepad3UpRight" = 1, "Gamepad3Face1" = 1, "Gamepad3Face2" = 1, "Gamepad3Face3" = 1, "Gamepad3Face4" = 1, "Gamepad3R1" = 1, "Gamepad3R2" = 1, "Gamepad3R3" = 1,
"Gamepad3L1" = 1, "Gamepad3L2" = 1, "Gamepad3L3" = 1, "Gamepad3Start" = 1, "Gamepad3Select" = 1, "Gamepad4Up" = 1, "Gamepad4Down" = 1, "Gamepad4Left" = 1,
"Gamepad4Right" = 1, "Gamepad4DownLeft" = 1,"Gamepad4DownRight" = 1, "Gamepad4UpLeft" = 1, "Gamepad4UpRight" = 1, "Gamepad4Face1" = 1, "Gamepad4Face2" = 1,
@@ -34,8 +34,8 @@ GLOBAL_LIST_INIT(valid_keys, list(
log_admin("[key_name(C)] just attempted to send an invalid keypress with length over 32 characters, likely malicious.")
message_admins("Mob [(C.mob)] with the ckey [(C.ckey)] just attempted to send an invalid keypress with length over 32 characters, likely malicious.")
else
- log_admin_private("[key_name(C)] just attempted to send an invalid keypress - \"[key]\", possibly malicious.")
- message_admins("Mob [(C.mob)] with the ckey [(C.ckey)] just attempted to send an invalid keypress - \"[key]\", possibly malicious.")
+ log_admin_private("[key_name(C)] just attempted to send an invalid keypress - \"[key]\".")
+ message_admins("Mob [(C.mob)] with the ckey [(C.ckey)] just attempted to send an invalid keypress - \"[sanitize(key)]\".")
return TRUE
@@ -48,7 +48,7 @@ GLOBAL_LIST_INIT(valid_keys, list(
keys_held[_key] = world.time
var/movement = SSinput.movement_keys[_key]
- if(!(next_move_dir_sub & movement) && !keys_held["Ctrl"])
+ if(!(next_move_dir_sub & movement) && !movement_locked)
next_move_dir_add |= movement
// Client-level keybindings are ones anyone should be able to do at any time
@@ -70,9 +70,9 @@ GLOBAL_LIST_INIT(valid_keys, list(
break
if(holder)
- holder.key_down(full_key, src)
+ holder.key_down(_key, src) //full_key is not necessary here, _key is enough
if(mob.focus)
- mob.focus.key_down(full_key, src)
+ mob.focus.key_down(_key, src) //same as above
/client/verb/keyUp(_key as text)
set instant = TRUE
@@ -101,10 +101,3 @@ GLOBAL_LIST_INIT(valid_keys, list(
holder.key_up(_key, src)
if(mob.focus)
mob.focus.key_up(_key, src)
-
-// Called every game tick
-/client/keyLoop()
- if(holder)
- holder.keyLoop(src)
- if(mob?.focus)
- mob.focus.keyLoop(src)
diff --git a/code/modules/keybindings/setup.dm b/code/modules/keybindings/setup.dm
index 3cefa78dc8c71..10e463f8d65b9 100644
--- a/code/modules/keybindings/setup.dm
+++ b/code/modules/keybindings/setup.dm
@@ -22,6 +22,8 @@
var/erase_output = ""
for(var/i in 1 to macro_sets.len)
var/setname = macro_sets[i]
+ if(copytext(setname, 1, 9) == "persist_") // Don't remove macro sets not handled by input. Used in input_box.dm by create_input_window
+ continue
var/list/macro_set = params2list(winget(src, "[setname].*", "command")) // The third arg doesnt matter here as we're just removing them all
for(var/k in 1 to macro_set.len)
var/list/split_name = splittext(macro_set[k], ".")
diff --git a/code/modules/language/language_holder.dm b/code/modules/language/language_holder.dm
index 56ff745e63afb..00b1013f4afeb 100644
--- a/code/modules/language/language_holder.dm
+++ b/code/modules/language/language_holder.dm
@@ -50,10 +50,12 @@ Key procs
/// Currently spoken language
var/selected_language
/// Tracks the entity that owns the holder.
- var/owner
+ var/atom/owner
/// Initializes, and copies in the languages from the current atom if available.
-/datum/language_holder/New(_owner)
+/datum/language_holder/New(atom/_owner)
+ if(_owner && QDELING(_owner))
+ CRASH("Langauge holder added to a qdeleting thing, what the fuck \ref[_owner]")
owner = _owner
if(istype(owner, /datum/mind))
var/datum/mind/M = owner
@@ -63,6 +65,7 @@ Key procs
/datum/language_holder/Destroy()
QDEL_NULL(language_menu)
+ owner = null
return ..()
/// Grants the supplied language.
@@ -269,6 +272,12 @@ Key procs
spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM),
/datum/language/slime = list(LANGUAGE_ATOM))
+/datum/language_holder/oozeling
+ understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM),
+ /datum/language/slime = list(LANGUAGE_ATOM))
+ spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM),
+ /datum/language/slime = list(LANGUAGE_ATOM))
+
/datum/language_holder/lightbringer
understood_languages = list(/datum/language/slime = list(LANGUAGE_ATOM))
spoken_languages = list(/datum/language/slime = list(LANGUAGE_ATOM))
@@ -281,7 +290,9 @@ Key procs
/datum/language/draconic = list(LANGUAGE_ATOM))
/datum/language_holder/lizard/ash
- selected_language = /datum/language/draconic
+ understood_languages = list(/datum/language/draconic = list(LANGUAGE_ATOM))
+ spoken_languages = list(/datum/language/draconic = list(LANGUAGE_ATOM))
+ blocked_languages = list(/datum/language/common = list(LANGUAGE_ATOM))
/datum/language_holder/monkey
understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM),
diff --git a/code/modules/language/language_menu.dm b/code/modules/language/language_menu.dm
index 0dac29c1573d9..807a6cd148790 100644
--- a/code/modules/language/language_menu.dm
+++ b/code/modules/language/language_menu.dm
@@ -61,6 +61,10 @@
L["key"] = initial(language.key)
data["unknown_languages"] += list(L)
+ else
+ data["admin_mode"] = null
+ data["omnitongue"] = null
+ data["unknown_languages"] = null
return data
/datum/language_menu/ui_act(action, params)
diff --git a/code/modules/library/lib_codex_gigas.dm b/code/modules/library/lib_codex_gigas.dm
index 9ebf186ac2f28..1e1cd74acca31 100644
--- a/code/modules/library/lib_codex_gigas.dm
+++ b/code/modules/library/lib_codex_gigas.dm
@@ -74,6 +74,7 @@
addtimer(CALLBACK(src, .proc/perform_research, usr, currentName), 0)
currentName = ""
currentSection = PRE_TITLE
+ ui_update()
return FALSE
else
currentName += action
@@ -89,6 +90,7 @@
currentSection = SYLLABLE
else if(GLOB.devil_suffix.Find(action))
currentSection = SUFFIX
+ ui_update()
return currentSection != oldSection
diff --git a/code/modules/library/lib_items.dm b/code/modules/library/lib_items.dm
index ddf66b31f8396..4433ee2000800 100644
--- a/code/modules/library/lib_items.dm
+++ b/code/modules/library/lib_items.dm
@@ -20,7 +20,7 @@
opacity = 0
resistance_flags = FLAMMABLE
max_integrity = 200
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0, "stamina" = 0)
var/state = 0
var/list/allowed_books = list(/obj/item/book, /obj/item/spellbook, /obj/item/storage/book) //Things allowed in the bookcase
/// When enabled, books_to_load number of random books will be generated for this bookcase when first interacted with.
@@ -210,10 +210,13 @@
/obj/item/book/attack_self(mob/user)
if(!user.can_read(src))
return
+ user.visible_message("[user] opens a book titled \"[title]\" and begins reading intently.")
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "book_nerd", /datum/mood_event/book_nerd)
+ on_read(user)
+
+/obj/item/book/proc/on_read(mob/user)
if(dat)
user << browse("Penned by [author]. " + "[dat]", "window=book[window_size != null ? ";size=[window_size]" : ""]")
- user.visible_message("[user] opens a book titled \"[title]\" and begins reading intently.")
- SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "book_nerd", /datum/mood_event/book_nerd)
onclose(user, "book")
else
to_chat(user, "This book is completely blank!")
@@ -239,7 +242,7 @@
var/newtitle = reject_bad_text(stripped_input(user, "Write a new title:"))
if(!user.canUseTopic(src, BE_CLOSE, literate))
return
- if (length(newtitle) > 20)
+ if (length(newtitle) > 50)
to_chat(user, "That title won't fit on the cover!")
return
if(!newtitle)
@@ -301,7 +304,7 @@
scanner.computer.inventory.Add(src)
to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Title added to general inventory.'")
- else if(istype(I, /obj/item/kitchen/knife) || I.tool_behaviour == TOOL_WIRECUTTER)
+ else if((istype(I, /obj/item/kitchen/knife) || I.tool_behaviour == TOOL_WIRECUTTER) && !(flags_1 & HOLOGRAM_1))
to_chat(user, "You begin to carve out [title]...")
if(do_after(user, 30, target = src))
to_chat(user, "You carve out the pages from [title]! You didn't want to read it anyway.")
diff --git a/code/modules/library/lib_machines.dm b/code/modules/library/lib_machines.dm
index 2d52a48bb328b..606f4835a1f45 100644
--- a/code/modules/library/lib_machines.dm
+++ b/code/modules/library/lib_machines.dm
@@ -27,6 +27,7 @@
var/author
var/SQLquery
clockwork = TRUE //it'd look weird
+ broken_overlay_emissive = TRUE
/obj/machinery/computer/libraryconsole/ui_interact(mob/user)
. = ..()
@@ -316,15 +317,19 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums
popup.open()
/obj/machinery/computer/libraryconsole/bookmanagement/proc/findscanner(viewrange)
- for(var/obj/machinery/libraryscanner/S in range(viewrange, get_turf(src)))
- return S
- return null
+ return locate(/obj/machinery/libraryscanner) in range(viewrange, get_turf(src))
/obj/machinery/computer/libraryconsole/bookmanagement/proc/print_forbidden_lore(mob/user)
- if (prob(50))
- new /obj/item/melee/cultblade/dagger(get_turf(src))
- to_chat(user, "Your sanity barely endures the seconds spent in the vault's browsing window. The only thing to remind you of this when you stop browsing is a sinister dagger sitting on the desk. You don't even remember where it came from...")
-
+ switch(rand(1,3))
+ if(1)
+ new /obj/item/melee/cultblade/dagger(get_turf(src))
+ to_chat(user, "Your sanity barely endures the seconds spent in the vault's browsing window. The only thing to remind you of this when you stop browsing is a sinister dagger sitting on the desk. You don't even remember where it came from...")
+ if(2)
+ new /obj/item/clockwork/clockwork_slab(get_turf(src))
+ to_chat(user, "Your sanity barely endures the seconds spent in the vault's browsing window. The only thing to remind you of this when you stop browsing is a strange metal tablet sitting on the desk. You don't even remember where it came from...")
+ if(3)
+ new /obj/item/forbidden_book(get_turf(src))
+ to_chat(user, "Your sanity barely endures the seconds spent in the vault's browsing window. The only thing to remind you of this when you stop browsing is an ominous book, bound by a chain, sitting on the desk. You don't even remember where it came from...")
user.visible_message("[user] stares at the blank screen for a few moments, [user.p_their()] expression frozen in fear. When [user.p_they()] finally awaken[user.p_s()] from it, [user.p_they()] look[user.p_s()] a lot older.", 2)
/obj/machinery/computer/libraryconsole/bookmanagement/attackby(obj/item/W, mob/user, params)
diff --git a/code/modules/library/soapstone.dm b/code/modules/library/soapstone.dm
index 78212e4f92aa2..e25c4ed5a24e9 100644
--- a/code/modules/library/soapstone.dm
+++ b/code/modules/library/soapstone.dm
@@ -155,8 +155,6 @@
var/hash = rustg_hash_string(RUSTG_HASH_MD5, hidden_message)
var/newcolor = copytext_char(hash, 1, 7)
add_atom_colour("#[newcolor]", FIXED_COLOUR_PRIORITY)
- light_color = "#[newcolor]"
- set_light(1)
/obj/structure/chisel_message/proc/pack()
var/list/data = list()
diff --git a/code/modules/lighting/emissive_blocker.dm b/code/modules/lighting/emissive_blocker.dm
new file mode 100644
index 0000000000000..b69a474009ee8
--- /dev/null
+++ b/code/modules/lighting/emissive_blocker.dm
@@ -0,0 +1,44 @@
+/**
+ * Internal atom that copies an appearance on to the blocker plane
+ *
+ * Copies an appearance vis render_target and render_source on to the emissive blocking plane.
+ * This means that the atom in question will block any emissive sprites.
+ * This should only be used internally. If you are directly creating more of these, you're
+ * almost guaranteed to be doing something wrong.
+ */
+/atom/movable/emissive_blocker
+ name = ""
+ plane = EMISSIVE_BLOCKER_PLANE
+ layer = EMISSIVE_BLOCKER_LAYER
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ //Why?
+ //render_targets copy the transform of the target as well, but vis_contents also applies the transform
+ //to what's in it. Applying RESET_TRANSFORM here makes vis_contents not apply the transform.
+ //Since only render_target handles transform we don't get any applied transform "stacking"
+ appearance_flags = RESET_TRANSFORM
+
+/atom/movable/emissive_blocker/Initialize(mapload, source)
+ . = ..()
+ verbs.Cut() //Cargo culting from lighting object, this maybe affects memory usage?
+
+ render_source = source
+
+/atom/movable/emissive_blocker/ex_act(severity)
+ return FALSE
+
+/atom/movable/emissive_blocker/singularity_act()
+ return
+
+/atom/movable/emissive_blocker/singularity_pull()
+ return
+
+/atom/movable/emissive_blocker/blob_act()
+ return
+
+/atom/movable/emissive_blocker/onTransitZ()
+ return
+
+//Prevents people from moving these after creation, because they shouldn't be.
+/atom/movable/emissive_blocker/forceMove(atom/destination, no_tp=FALSE, harderforce = FALSE)
+ if(harderforce)
+ return ..()
diff --git a/code/modules/lighting/lighting_area.dm b/code/modules/lighting/lighting_area.dm
index 13676a5cb4f49..878d41aeee829 100644
--- a/code/modules/lighting/lighting_area.dm
+++ b/code/modules/lighting/lighting_area.dm
@@ -10,11 +10,20 @@
if (IS_DYNAMIC_LIGHTING(src))
cut_overlay(/obj/effect/fullbright)
+ if(lighting_overlay)
+ cut_overlay(lighting_overlay)
+ if(lighting_overlay_opacity && lighting_overlay_colour)
+ lighting_overlay = new /obj/effect/fullbright
+ lighting_overlay.color = lighting_overlay_colour
+ lighting_overlay.alpha = lighting_overlay_opacity
+ add_overlay(lighting_overlay)
for (var/turf/T in src)
if (IS_DYNAMIC_LIGHTING(T))
T.lighting_build_overlay()
else
+ if(lighting_overlay)
+ cut_overlay(lighting_overlay)
add_overlay(/obj/effect/fullbright)
for (var/turf/T in src)
if (T.lighting_object)
@@ -27,4 +36,18 @@
if("dynamic_lighting")
set_dynamic_lighting(var_value)
return TRUE
+ if("lighting_overlay_colour")
+ ..()
+ if(lighting_overlay)
+ cut_overlay(lighting_overlay)
+ lighting_overlay.color = var_value
+ add_overlay(lighting_overlay)
+ return TRUE
+ if("lighting_overlay_opacity")
+ ..()
+ if(lighting_overlay)
+ cut_overlay(lighting_overlay)
+ lighting_overlay.alpha = var_value
+ add_overlay(lighting_overlay)
+ return TRUE
return ..()
diff --git a/code/modules/lighting/lighting_corner.dm b/code/modules/lighting/lighting_corner.dm
index c18c7e831a476..be73c636f8a94 100644
--- a/code/modules/lighting/lighting_corner.dm
+++ b/code/modules/lighting/lighting_corner.dm
@@ -136,6 +136,6 @@ GLOBAL_LIST_INIT(LIGHTING_CORNER_DIAGONAL, list(NORTHEAST, SOUTHEAST, SOUTHWEST,
if (!force)
return QDEL_HINT_LETMELIVE
- stack_trace("Ok, Look, /tg/, I need you to find whatever fucker decided to call qdel on a fucking lighting corner, then tell him very nicely and politely that he is 100% retarded and needs his head checked. Thanks. Send them my regards by the way.")
+ stack_trace("Ok, Look, /tg/, I need you to find whatever fucker decided to call qdel on a fucking lighting corner, then tell him very nicely and politely that he is 100% intellectually disabled and needs his head checked. Thanks. Send them my regards by the way.")
return ..()
diff --git a/code/modules/lighting/lighting_object.dm b/code/modules/lighting/lighting_object.dm
index 05e0cabe35bc9..d2fae0c1ca55f 100644
--- a/code/modules/lighting/lighting_object.dm
+++ b/code/modules/lighting/lighting_object.dm
@@ -16,7 +16,7 @@
/atom/movable/lighting_object/Initialize(mapload)
. = ..()
- verbs.Cut()
+ remove_verb(verbs)
atom_colours.Cut()
myturf = loc
@@ -101,7 +101,7 @@
#if LIGHTING_SOFT_THRESHOLD != 0
var/set_luminosity = max > LIGHTING_SOFT_THRESHOLD
#else
- // Because of floating points?, it won't even be a flat 0.
+ // Because of floating points�?, it won't even be a flat 0.
// This number is mostly arbitrary.
var/set_luminosity = max > 1e-6
#endif
diff --git a/code/modules/lighting/lighting_setup.dm b/code/modules/lighting/lighting_setup.dm
index 5086b0c9d29c5..6cd25efe2b231 100644
--- a/code/modules/lighting/lighting_setup.dm
+++ b/code/modules/lighting/lighting_setup.dm
@@ -1,5 +1,5 @@
/proc/create_all_lighting_objects()
- for(var/area/A in world)
+ for(var/area/A in GLOB.sortedAreas)
if(!IS_DYNAMIC_LIGHTING(A))
continue
diff --git a/code/modules/lighting/lighting_source.dm b/code/modules/lighting/lighting_source.dm
index dc965bbe3e392..d12a129e89f24 100644
--- a/code/modules/lighting/lighting_source.dm
+++ b/code/modules/lighting/lighting_source.dm
@@ -63,7 +63,12 @@
if (needs_update)
GLOB.lighting_update_lights -= src
- . = ..()
+ top_atom = null
+ source_atom = null
+ source_turf = null
+ pixel_turf = null
+
+ return ..()
// Yes this doesn't align correctly on anything other than 4 width tabs.
// If you want it to go switch everybody to elastic tab stops.
@@ -234,13 +239,14 @@
var/oldlum = source_turf.luminosity
source_turf.luminosity = CEILING(light_range, 1)
for(T in view(CEILING(light_range, 1), source_turf))
- if((!IS_DYNAMIC_LIGHTING(T) && !T.light_sources) || T.has_opaque_atom)
+ if((!IS_DYNAMIC_LIGHTING(T) && !T.light_sources))
continue
- if (!T.lighting_corners_initialised)
- T.generate_missing_corners()
- for (thing in T.corners)
- C = thing
- corners[C] = 0
+ if(!T.has_opaque_atom)
+ if (!T.lighting_corners_initialised)
+ T.generate_missing_corners()
+ for (thing in T.corners)
+ C = thing
+ corners[C] = 0
turfs += T
source_turf.luminosity = oldlum
diff --git a/code/modules/mapexporting/mapexporter.dm b/code/modules/mapexporting/mapexporter.dm
index a886db0636ec3..d71b94f89c72a 100644
--- a/code/modules/mapexporting/mapexporter.dm
+++ b/code/modules/mapexporting/mapexporter.dm
@@ -62,18 +62,19 @@ GLOBAL_LIST_INIT(save_file_chars, list(
var/turf/place = sortedmap[x][y]
var/area/location
var/list/objects
+ var/area/AR = get_area(place)
//If there is nothing there, save as a noop (For odd shapes)
if(!place)
place = /turf/template_noop
location = /area/template_noop
objects = list()
//Ignore things in space, must be a space turf and the area has to be empty space
- else if(istype(place, /turf/open/space) && get_area(place).type == /area/space && !(save_flag & SAVE_SPACE))
+ else if(istype(place, /turf/open/space) && istype(AR, /area/space) && !(save_flag & SAVE_SPACE))
place = /turf/template_noop
location = /area/template_noop
//Stuff to add
else
- location = get_area(place).type
+ location = AR.type
objects = place
place = place.type
//====Saving shuttles only / non shuttles only====
diff --git a/code/modules/mapping/map_template.dm b/code/modules/mapping/map_template.dm
index 4294c4464b127..92ba6b5814054 100644
--- a/code/modules/mapping/map_template.dm
+++ b/code/modules/mapping/map_template.dm
@@ -7,7 +7,17 @@
var/datum/parsed_map/cached_map
var/keep_cached_map = FALSE
-/datum/map_template/New(path = null, rename = null, cache = FALSE)
+ ///if true, turfs loaded from this template are placed on top of the turfs already there, defaults to TRUE
+ var/should_place_on_top = TRUE
+
+ ///if true, creates a list of all atoms created by this template loading, defaults to FALSE
+ var/returns_created_atoms = FALSE
+
+ ///the list of atoms created by this template being loaded, only populated if returns_created_atoms is TRUE
+ var/list/created_atoms = list()
+ //make sure this list is accounted for/cleared if you request it from ssatoms!
+
+/datum/map_template/New(path = null, rename = null, cache = FALSE, admin_load = FALSE)
if(path)
mappath = path
if(mappath)
@@ -25,42 +35,80 @@
cached_map = parsed
return bounds
-/datum/parsed_map/proc/initTemplateBounds()
+/datum/map_template/proc/initTemplateBounds(list/bounds, init_atmos = TRUE)
+ if (!bounds) //something went wrong
+ stack_trace("[name] template failed to initialize correctly!")
+ return
+
var/list/obj/machinery/atmospherics/atmos_machines = list()
var/list/obj/structure/cable/cables = list()
- var/list/atom/atoms = list()
+ var/list/atom/movable/movables = list()
var/list/area/areas = list()
- var/list/turfs = block( locate(bounds[MAP_MINX], bounds[MAP_MINY], bounds[MAP_MINZ]),
- locate(bounds[MAP_MAXX], bounds[MAP_MAXY], bounds[MAP_MAXZ]))
- var/list/border = block(locate(max(bounds[MAP_MINX]-1, 1), max(bounds[MAP_MINY]-1, 1), bounds[MAP_MINZ]),
- locate(min(bounds[MAP_MAXX]+1, world.maxx), min(bounds[MAP_MAXY]+1, world.maxy), bounds[MAP_MAXZ])) - turfs
- for(var/L in turfs)
- var/turf/B = L
- atoms += B
- areas |= B.loc
- for(var/A in B)
- atoms += A
- if(istype(A, /obj/structure/cable))
- cables += A
+ var/list/turfs = block(
+ locate(
+ bounds[MAP_MINX],
+ bounds[MAP_MINY],
+ bounds[MAP_MINZ]
+ ),
+ locate(
+ bounds[MAP_MAXX],
+ bounds[MAP_MAXY],
+ bounds[MAP_MAXZ]
+ )
+ )
+ for(var/turf/current_turf as anything in turfs)
+ var/area/current_turfs_area = current_turf.loc
+ areas |= current_turfs_area
+ if(!SSatoms.initialized)
+ continue
+
+ for(var/movable_in_turf in current_turf)
+ movables += movable_in_turf
+ if(istype(movable_in_turf, /obj/structure/cable))
+ cables += movable_in_turf
continue
- if(istype(A, /obj/machinery/atmospherics))
- atmos_machines += A
- for(var/L in border)
- var/turf/T = L
- T.air_update_turf(TRUE) //calculate adjacent turfs along the border to prevent runtimes
+ if(istype(movable_in_turf, /obj/machinery/atmospherics))
+ atmos_machines += movable_in_turf
+ // Not sure if there is some importance here to make sure the area is in z
+ // first or not. Its defined In Initialize yet its run first in templates
+ // BEFORE so... hummm
SSmapping.reg_in_areas_in_z(areas)
- SSatoms.InitializeAtoms(atoms)
+ if(!SSatoms.initialized)
+ return
+
+ SSatoms.InitializeAtoms(areas + turfs + movables, returns_created_atoms ? created_atoms : null)
+
+ // NOTE, now that Initialize and LateInitialize run correctly, do we really
+ // need these two below?
SSmachines.setup_template_powernets(cables)
SSair.setup_template_machinery(atmos_machines)
-/datum/map_template/proc/load_new_z()
+ if(init_atmos)
+ //calculate all turfs inside the border
+ var/list/template_and_bordering_turfs = block(
+ locate(
+ max(bounds[MAP_MINX]-1, 1),
+ max(bounds[MAP_MINY]-1, 1),
+ bounds[MAP_MINZ]
+ ),
+ locate(
+ min(bounds[MAP_MAXX]+1, world.maxx),
+ min(bounds[MAP_MAXY]+1, world.maxy),
+ bounds[MAP_MAXZ]
+ )
+ )
+ for(var/turf/affected_turf as anything in template_and_bordering_turfs)
+ affected_turf.air_update_turf(TRUE)
+ affected_turf.levelupdate()
+
+/datum/map_template/proc/load_new_z(orbital_body_type, list/level_traits = list(ZTRAIT_AWAY = TRUE))
var/x = round((world.maxx - width)/2)
var/y = round((world.maxy - height)/2)
- var/datum/space_level/level = SSmapping.add_new_zlevel(name, list(ZTRAIT_AWAY = TRUE))
- var/datum/parsed_map/parsed = load_map(file(mappath), x, y, level.z_value, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=TRUE)
+ var/datum/space_level/level = SSmapping.add_new_zlevel(name, level_traits, orbital_body_type = orbital_body_type)
+ var/datum/parsed_map/parsed = load_map(file(mappath), x, y, level.z_value, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=should_place_on_top)
var/list/bounds = parsed.bounds
if(!bounds)
return FALSE
@@ -68,13 +116,13 @@
repopulate_sorted_areas()
//initialize things that are normally initialized after map load
- parsed.initTemplateBounds()
+ initTemplateBounds(bounds)
smooth_zlevel(world.maxz)
log_game("Z-level [name] loaded at [x],[y],[world.maxz]")
return level
-/datum/map_template/proc/load(turf/T, centered = FALSE)
+/datum/map_template/proc/load(turf/T, centered = FALSE, init_atmos = TRUE)
if(centered)
T = locate(T.x - round(width/2) , T.y - round(height/2) , T.z)
if(!T)
@@ -84,11 +132,22 @@
if(T.y+height > world.maxy)
return
+ var/list/border = block(locate(max(T.x, 1), max(T.y, 1), T.z),
+ locate(min(T.x+width, world.maxx), min(T.y+height, world.maxy), T.z))
+ for(var/L in border)
+ var/turf/turf_to_disable = L
+ turf_to_disable.ImmediateDisableAdjacency()
+
// Accept cached maps, but don't save them automatically - we don't want
// ruins clogging up memory for the whole round.
var/datum/parsed_map/parsed = cached_map || new(file(mappath))
cached_map = keep_cached_map ? parsed : null
- if(!parsed.load(T.x, T.y, T.z, cropMap=TRUE, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=TRUE))
+
+ var/list/turf_blacklist = list()
+ update_blacklist(T, turf_blacklist)
+
+ parsed.turf_blacklist = turf_blacklist
+ if(!parsed.load(T.x, T.y, T.z, cropMap=TRUE, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=should_place_on_top))
return
var/list/bounds = parsed.bounds
if(!bounds)
@@ -98,11 +157,14 @@
repopulate_sorted_areas()
//initialize things that are normally initialized after map load
- parsed.initTemplateBounds()
+ initTemplateBounds(bounds, init_atmos)
log_game("[name] loaded at [T.x],[T.y],[T.z]")
return bounds
+/datum/map_template/proc/update_blacklist(turf/T, list/input_blacklist)
+ return
+
/datum/map_template/proc/get_affected_turfs(turf/T, centered = FALSE)
var/turf/placement = T
if(centered)
@@ -114,6 +176,6 @@
//for your ever biggening badminnery kevinz000
//⤠- Cyberboss
-/proc/load_new_z_level(var/file, var/name)
+/proc/load_new_z_level(var/file, var/name, orbital_body_type)
var/datum/map_template/template = new(file, name)
- template.load_new_z()
+ template.load_new_z(orbital_body_type = orbital_body_type)
diff --git a/code/modules/mapping/mapping_helpers.dm b/code/modules/mapping/mapping_helpers.dm
index 98c0ed209f7d5..9af886776a731 100644
--- a/code/modules/mapping/mapping_helpers.dm
+++ b/code/modules/mapping/mapping_helpers.dm
@@ -213,7 +213,7 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava)
if(target_type && !istype(A,target_type))
continue
var/cargs = build_args()
- A.AddComponent(arglist(cargs))
+ A._AddComponent(cargs)
qdel(src)
return
diff --git a/code/modules/mapping/preloader.dm b/code/modules/mapping/preloader.dm
index 79afa8c032506..d966d8c0410bc 100644
--- a/code/modules/mapping/preloader.dm
+++ b/code/modules/mapping/preloader.dm
@@ -37,3 +37,12 @@ GLOBAL_DATUM_INIT(_preloader, /datum/map_preloader, new)
name = "Turf Passthrough"
icon_state = "noop"
bullet_bounce_sound = null
+
+//The following two turfs can be used to hint if any genturfs below should be generated as closed or open
+/turf/template_noop/closed
+ name = "Area Passthrough (prefer closed)"
+ icon_state = "noop_closed"
+
+/turf/template_noop/open
+ name = "Area Passthrough (prefer open)"
+ icon_state = "noop_open"
diff --git a/code/modules/mapping/random_rooms.dm b/code/modules/mapping/random_rooms.dm
index dd37032bc16b0..d87be0728c9a0 100644
--- a/code/modules/mapping/random_rooms.dm
+++ b/code/modules/mapping/random_rooms.dm
@@ -689,7 +689,7 @@
template_height = 3
template_width = 3
weight = 3
-
+
/datum/map_template/random_room/sk_rdm082
name = "Maint Chemistry"
room_id = "sk_rdm082_maintmedical"
@@ -1161,25 +1161,6 @@
template_width = 10
weight = 3
-
-/datum/map_template/random_room/sk_rdm135 //this room is fun.
- name = "Cluwne Altar"
- room_id = "sk_rdm135_cluwnealtar"
- mappath = "_maps/RandomRooms/10x10/sk_rdm135_cluwnealtar.dmm"
- centerspawner = FALSE
- template_height = 10
- template_width = 10
- weight = 1 //rare
-
-/datum/map_template/random_room/sk_rdm136 //this room is fun as well
- name = "Tiny Cluwne Altar"
- room_id = "sk_rdm136_tinycluwnealtar"
- mappath = "_maps/RandomRooms/5x4/sk_rdm136_tinycluwnealtar.dmm"
- centerspawner = FALSE
- template_height = 4
- template_width = 5
- weight = 1
-
/datum/map_template/random_room/sk_rdm137
name = "Tiny psych ward"
room_id = "sk_rdm137_tinyshrink"
@@ -1198,7 +1179,7 @@
template_width = 5
weight = 4
-/datum/map_template/random_room/sk_rdm139
+/datum/map_template/random_room/sk_rdm139
name = "containment cell"
room_id = "sk_rdm139_containmentcell"
mappath = "_maps/RandomRooms/3x3/containmentcell.dmm"
@@ -1214,4 +1195,157 @@
centerspawner = FALSE
template_height = 5
template_width = 3
- weight = 2
\ No newline at end of file
+ weight = 2
+
+/datum/map_template/random_room/sk_rdm141
+ name = "the place 6 sectors down"
+ room_id = "sk_rdm141_6sectorsdown"
+ mappath = "_maps/RandomRooms/10x10/sk_rdm141_6sectorsdown.dmm"
+ centerspawner = FALSE
+ template_height = 10
+ template_width = 10
+ weight = 2
+
+/datum/map_template/random_room/sk_rdm142
+ name = "old diner"
+ room_id = "sk_rdm142_olddiner"
+ mappath = "_maps/RandomRooms/10x10/sk_rdm142_olddiner.dmm"
+ centerspawner = FALSE
+ template_height = 10
+ template_width = 10
+ weight = 4
+
+/datum/map_template/random_room/sk_rdm143
+ name = "gamer cave"
+ room_id = "sk_rdm143_gamercave"
+ mappath = "_maps/RandomRooms/10x10/sk_rdm143_gamercave.dmm"
+ centerspawner = FALSE
+ template_height = 10
+ template_width = 10
+ weight = 3
+
+/datum/map_template/random_room/sk_rdm144 //has Stage Magician Spawner
+ name = "small stage and bar"
+ room_id = "sk_rdm144_smallmagician"
+ mappath = "_maps/RandomRooms/10x10/sk_rdm144_smallmagician.dmm"
+ centerspawner = FALSE
+ template_height = 10
+ template_width = 10
+ weight = 3
+
+/datum/map_template/random_room/sk_rdm145 //has tela anchor
+ name = "lady tesla altar"
+ room_id = "sk_rdm145_ladytesla_altar"
+ mappath = "_maps/RandomRooms/10x10/sk_rdm145_ladytesla_altar.dmm"
+ centerspawner = FALSE
+ template_height = 10
+ template_width = 10
+ weight = 1 //rare
+
+/datum/map_template/random_room/sk_rdm146
+ name = "blastdoor interchange"
+ room_id = "sk_rdm146_blastdoor_interchange"
+ mappath = "_maps/RandomRooms/10x10/sk_rdm146_blastdoor_interchange.dmm"
+ centerspawner = FALSE
+ template_height = 10
+ template_width = 10
+ weight = 4 //common
+
+/datum/map_template/random_room/sk_rdm147
+ name = "advanced micro botany"
+ room_id = "sk_rdm147_advbotany"
+ mappath = "_maps/RandomRooms/10x10/sk_rdm147_advbotany.dmm"
+ centerspawner = FALSE
+ template_height = 10
+ template_width = 10
+ weight = 2
+
+/datum/map_template/random_room/sk_rdm148
+ name = "maintenance apiary"
+ room_id = "sk_rdm148_botany_apiary"
+ mappath = "_maps/RandomRooms/10x10/sk_rdm148_botany_apiary.dmm"
+ centerspawner = FALSE
+ template_height = 10
+ template_width = 10
+ weight = 2
+
+/datum/map_template/random_room/sk_rdm149
+ name = "space window with crates"
+ room_id = "sk_rdm149_cratewindow"
+ mappath = "_maps/RandomRooms/10x5/sk_rdm149_cratewindow.dmm"
+ centerspawner = FALSE
+ template_height = 5
+ template_width = 10
+ weight = 3
+
+/datum/map_template/random_room/sk_rdm150
+ name = "small medical lobby"
+ room_id = "sk_rdm150_smallmedlobby"
+ mappath = "_maps/RandomRooms/10x5/sk_rdm150_smallmedlobby.dmm"
+ centerspawner = FALSE
+ template_height = 5
+ template_width = 10
+ weight = 3 //common
+
+/datum/map_template/random_room/sk_rdm151 //delicious
+ name = "small medical lobby"
+ room_id = "sk_rdm151_ratburger"
+ mappath = "_maps/RandomRooms/10x5/sk_rdm151_ratburger.dmm"
+ centerspawner = FALSE
+ template_height = 5
+ template_width = 10
+ weight = 1 //rare
+
+/datum/map_template/random_room/sk_rdm152
+ name = "old genetics office"
+ room_id = "sk_rdm152_geneticsoffice"
+ mappath = "_maps/RandomRooms/10x5/sk_rdm152_geneticsoffice.dmm"
+ centerspawner = FALSE
+ template_height = 5
+ template_width = 10
+ weight = 2
+
+/datum/map_template/random_room/sk_rdm153 //its a hobo den featuring Peter the pet frog. Includes a debtor spawn
+ name = "peters room"
+ room_id = "sk_rdm153_hobowithpeter"
+ mappath = "_maps/RandomRooms/10x5/sk_rdm153_hobowithpeter.dmm"
+ centerspawner = FALSE
+ template_height = 5
+ template_width = 10
+ weight = 2
+
+/datum/map_template/random_room/sk_rdm154 //rare, has a cleaver.
+ name = "butchers den"
+ room_id = "sk_rdm154_butchersden"
+ mappath = "_maps/RandomRooms/10x5/sk_rdm154_butchersden.dmm"
+ centerspawner = FALSE
+ template_height = 5
+ template_width = 10
+ weight = 1
+
+/datum/map_template/random_room/sk_rdm155
+ name = "punji stick conveyor trap"
+ room_id = "sk_rdm155_punjiconveyor"
+ mappath = "_maps/RandomRooms/10x5/sk_rdm155_punjiconveyor.dmm"
+ centerspawner = FALSE
+ template_height = 5
+ template_width = 10
+ weight = 1
+
+/datum/map_template/random_room/sk_rdm156
+ name = "ancient interchange"
+ room_id = "sk_rdm156_oldairlock_interchange"
+ mappath = "_maps/RandomRooms/10x5/sk_rdm156_oldairlock_interchange.dmm"
+ centerspawner = FALSE
+ template_height = 5
+ template_width = 10
+ weight = 4
+ stock = 2
+
+/datum/map_template/random_room/sk_rdm157
+ name = "Space Chess"
+ room_id = "sk_rdm157_chess"
+ mappath = "_maps/RandomRooms/10x10/sk_rdm157_chess.dmm"
+ centerspawner = FALSE
+ template_height = 10
+ template_width = 10
diff --git a/code/modules/mapping/reader.dm b/code/modules/mapping/reader.dm
index 1beddbfdbbb21..9f73367f7aee2 100644
--- a/code/modules/mapping/reader.dm
+++ b/code/modules/mapping/reader.dm
@@ -23,6 +23,9 @@
var/list/bounds
var/did_expand = FALSE
+ ///any turf in this list is skipped inside of build_coordinate
+ var/list/turf_blacklist = list()
+
// raw strings used to represent regexes more accurately
// '' used to avoid confusing syntax highlighting
var/static/regex/dmmRegex = new(@'"([a-zA-Z]+)" = \(((?:.|\n)*?)\)\n(?!\t)|\((\d+),(\d+),(\d+)\) = \{"([a-zA-Z\n]*)"\}', "g")
@@ -52,7 +55,11 @@
/datum/parsed_map/New(tfile, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper=INFINITY, measureOnly=FALSE)
if(isfile(tfile))
original_path = "[tfile]"
+ var/temp_hack_garbage = tfile
tfile = rustg_file_read(tfile)
+ //This is hacky garbage
+ if(tfile == "") //Might be an upload, try raw loading it.
+ tfile = file2text(temp_hack_garbage)
else if(isnull(tfile))
// create a new datum without loading a map
return
@@ -310,6 +317,13 @@
//Instanciation
////////////////
+ for (var/turf_in_blacklist in turf_blacklist)
+ if (crds == turf_in_blacklist) //if the given turf is blacklisted, dont do anything with it
+ return
+
+ //Keep a reference to the original area in case we need to tell it to generate this turf later
+ var/area/orig_area = crds.loc
+
//The next part of the code assumes there's ALWAYS an /area AND a /turf on a given tile
//first instance the /area and remove it from the members list
index = members.len
@@ -327,6 +341,8 @@
if(GLOB.use_preloader && instance)
world.preloader_load(instance)
+ else
+ orig_area = null // We won't be messing with the old area, null it
//then instance the /turf and, if multiple tiles are presents, simulates the DMM underlays piling effect
@@ -338,8 +354,23 @@
SSatoms.map_loader_begin()
//instanciate the first /turf
var/turf/T
- if(members[first_turf_index] != /turf/template_noop)
- T = instance_atom(members[first_turf_index],members_attributes[first_turf_index],crds,no_changeturf,placeOnTop)
+ if(ispath(members[first_turf_index], /turf/template_noop))
+ if(istype(crds, /turf/open/genturf))
+ var/turf/open/genturf/genturf = crds
+ //If the new area is different from the original area, ensure the new turfs are generated as part of the original area
+ if(orig_area && orig_area.type != members[index])
+ LAZYADD(orig_area.additional_genturfs, crds)
+ //Cave generation checks current area flags for generation; ignore them
+ genturf.force_generation = TRUE
+ //Pass on any hints for whether the turf should be open or closed
+ if(ispath(members[first_turf_index], /turf/template_noop/closed))
+ genturf.genturf_hint = GENTURF_HINT_CLOSED
+ else if(ispath(members[first_turf_index], /turf/template_noop/open))
+ genturf.genturf_hint = GENTURF_HINT_OPEN
+ else
+ ///Disable placeOnTop for genturfs, instead making sure to replace them
+ var/shouldPlaceOnTop = placeOnTop && !istype(crds, /turf/open/genturf)
+ T = instance_atom(members[first_turf_index],members_attributes[first_turf_index],crds,no_changeturf,shouldPlaceOnTop)
if(T)
//if others /turf are presents, simulates the underlays piling effect
@@ -480,4 +511,9 @@
/datum/parsed_map/Destroy()
..()
+ turf_blacklist.Cut()
+ parsed_bounds.Cut()
+ bounds.Cut()
+ grid_models.Cut()
+ gridSets.Cut()
return QDEL_HINT_HARDDEL_NOW
diff --git a/code/modules/mapping/ruins.dm b/code/modules/mapping/ruins.dm
index fe2f0c65b7b73..eee1f635aa2f1 100644
--- a/code/modules/mapping/ruins.dm
+++ b/code/modules/mapping/ruins.dm
@@ -20,6 +20,8 @@
for(var/i in get_affected_turfs(central_turf, 1))
var/turf/T = i
+ for(var/obj/structure/spawner/nest in T)
+ qdel(nest)
for(var/mob/living/simple_animal/monster in T)
qdel(monster)
for(var/obj/structure/flora/ash/plant in T)
@@ -121,8 +123,6 @@
forced_ruins[linked] = forced_z ? forced_z : z_placed //I guess you might want a chain somehow
if(PLACE_LAVA_RUIN)
forced_ruins[linked] = pick(SSmapping.levels_by_trait(ZTRAIT_LAVA_RUINS))
- if(PLACE_SPACE_RUIN)
- forced_ruins[linked] = pick(SSmapping.levels_by_trait(ZTRAIT_SPACE_RUINS))
if(PLACE_DEFAULT)
forced_ruins[linked] = -1
forced_z = 0
diff --git a/code/modules/mapping/space_management/multiz_helpers.dm b/code/modules/mapping/space_management/multiz_helpers.dm
index 74bd7c8782eff..fe5aa0b039151 100644
--- a/code/modules/mapping/space_management/multiz_helpers.dm
+++ b/code/modules/mapping/space_management/multiz_helpers.dm
@@ -45,4 +45,38 @@
else
holder = UP
dir |= holder
- return dir
\ No newline at end of file
+ return dir
+
+/proc/get_zs_in_range(z_level, max_z_range)
+ . = list(z_level)
+ if(max_z_range <= 0)
+ return
+ var/turf/center_turf = locate(world.maxx / 2, world.maxy / 2, z_level)
+ var/turf/temp = center_turf.above()
+ //Iterate upwards.
+ var/i = 0
+ while(isturf(temp))
+ . += temp
+ i ++
+ if(i >= max_z_range)
+ break
+ temp = temp.above()
+ //Iterate downwards.
+ temp = center_turf.below()
+ i = 0
+ while(isturf(temp))
+ . += temp
+ i ++
+ if(i >= max_z_range)
+ break
+ temp = temp.below()
+
+/proc/multi_z_dist(turf/T0, turf/T1)
+ if(T0.get_virtual_z_level() == T1.get_virtual_z_level())
+ return get_dist(T0, T1)
+ if(is_station_level(T0.z) && is_station_level(T1.z))
+ var/raw_dist = get_dist(T0, T1)
+ var/z_dist = abs(T0.z - T1.z) * MULTI_Z_DISTANCE
+ var/total_dist = raw_dist + z_dist
+ return total_dist
+ return INFINITY
diff --git a/code/modules/mapping/space_management/space_level.dm b/code/modules/mapping/space_management/space_level.dm
index cc9c6e11f173b..d696dfb47130f 100644
--- a/code/modules/mapping/space_management/space_level.dm
+++ b/code/modules/mapping/space_management/space_level.dm
@@ -6,9 +6,16 @@
var/linkage = SELFLOOPING
var/xi
var/yi //imaginary placements on the grid
+ //Z-levels orbital body
+ var/datum/orbital_object/z_linked/orbital_body
+ //Is something generating on this level?
+ var/generating = FALSE
-/datum/space_level/New(new_z, new_name, list/new_traits = list())
+/datum/space_level/New(new_z, new_name, list/new_traits = list(), orbital_body_type)
z_value = new_z
name = new_name
traits = new_traits
set_linkage(new_traits[ZTRAIT_LINKAGE])
+ if(orbital_body_type)
+ orbital_body = new orbital_body_type()
+ orbital_body.link_to_z(src)
diff --git a/code/modules/mapping/space_management/zlevel_manager.dm b/code/modules/mapping/space_management/zlevel_manager.dm
index 6129c5fd2b6a2..c50a2e60e3e7d 100644
--- a/code/modules/mapping/space_management/zlevel_manager.dm
+++ b/code/modules/mapping/space_management/zlevel_manager.dm
@@ -13,17 +13,18 @@
for (var/I in 1 to default_map_traits.len)
var/list/features = default_map_traits[I]
- var/datum/space_level/S = new(I, features[DL_NAME], features[DL_TRAITS])
+ //All default levels are assumed to be phobos at this stage, since there is only 1.
+ var/datum/space_level/S = new(I, features[DL_NAME], features[DL_TRAITS], orbital_body_type = /datum/orbital_object/z_linked/phobos)
z_list += S
-/datum/controller/subsystem/mapping/proc/add_new_zlevel(name, traits = list(), z_type = /datum/space_level)
+/datum/controller/subsystem/mapping/proc/add_new_zlevel(name, traits = list(), z_type = /datum/space_level, orbital_body_type)
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_NEW_Z, args)
var/new_z = z_list.len + 1
if (world.maxz < new_z)
world.incrementMaxZ()
CHECK_TICK
// TODO: sleep here if the Z level needs to be cleared
- var/datum/space_level/S = new z_type(new_z, name, traits)
+ var/datum/space_level/S = new z_type(new_z, name, traits, orbital_body_type)
z_list += S
return S
diff --git a/code/modules/mentor/follow.dm b/code/modules/mentor/follow.dm
index 7c53c5c0fb571..e5ec5adc90166 100644
--- a/code/modules/mentor/follow.dm
+++ b/code/modules/mentor/follow.dm
@@ -7,7 +7,7 @@
return
mentor_datum.following = M
usr.reset_perspective(M)
- verbs += /client/proc/mentor_unfollow
+ add_verb(/client/proc/mentor_unfollow)
to_chat(GLOB.admins, "MENTOR:[key_name(usr)] is now following [key_name(M)]")
to_chat(usr, "Click the \"Stop Following\" button in the Mentor tab to stop following [key_name(M)].")
log_mentor("[key_name(usr)] began following [key_name(M)]")
@@ -20,7 +20,7 @@
if(!is_mentor())
return
usr.reset_perspective()
- verbs -= /client/proc/mentor_unfollow
+ remove_verb(/client/proc/mentor_unfollow)
to_chat(GLOB.admins, "MENTOR:[key_name(usr)] is no longer following [key_name(mentor_datum.following)]")
log_mentor("[key_name(usr)] stopped following [key_name(mentor_datum.following)]")
mentor_datum.following = null
diff --git a/code/modules/mentor/mentor_verbs.dm b/code/modules/mentor/mentor_verbs.dm
index f7faa873eee54..7b3e5dd1d1b3a 100644
--- a/code/modules/mentor/mentor_verbs.dm
+++ b/code/modules/mentor/mentor_verbs.dm
@@ -6,7 +6,9 @@ GLOBAL_PROTECT(mentor_verbs)
/client/proc/add_mentor_verbs()
if(mentor_datum)
- verbs += GLOB.mentor_verbs
+ add_verb(GLOB.mentor_verbs)
+ reset_badges()
/client/proc/remove_mentor_verbs()
- verbs -= GLOB.mentor_verbs
+ remove_verb(GLOB.mentor_verbs)
+ reset_badges()
diff --git a/code/modules/mentor/mentorhelp.dm b/code/modules/mentor/mentorhelp.dm
index f6be4410795b7..b90c95adc0394 100644
--- a/code/modules/mentor/mentorhelp.dm
+++ b/code/modules/mentor/mentorhelp.dm
@@ -10,9 +10,9 @@
if(!msg) return
//remove out mentorhelp verb temporarily to prevent spamming of mentors.
- verbs -= /client/verb/mentorhelp
+ remove_verb(/client/verb/mentorhelp)
spawn(300)
- verbs += /client/verb/mentorhelp // 30 second cool-down for mentorhelp
+ add_verb(/client/verb/mentorhelp) // 30 second cool-down for mentorhelp
msg = sanitize(copytext(msg,1,MAX_MESSAGE_LEN))
if(!msg) return
@@ -39,7 +39,7 @@
else
.["present"]++
-/proc/key_name_mentor(var/whom, var/include_link = null, var/include_name = 0, var/include_follow = 0, var/char_name_only = 0)
+/proc/key_name_mentor(var/whom, var/include_link = null, var/include_name = 0, var/char_name_only = 0)
var/mob/M
var/client/C
var/key
@@ -96,7 +96,4 @@
else
. += "*no key*"
- if(include_follow)
- . += " (F)"
-
return .
diff --git a/code/modules/mentor/mentorpm.dm b/code/modules/mentor/mentorpm.dm
index 891daa9fb68fb..b2aa9ce4c0602 100644
--- a/code/modules/mentor/mentorpm.dm
+++ b/code/modules/mentor/mentorpm.dm
@@ -1,3 +1,5 @@
+//This file was ported from hippie, used to be indented with spaces, and is the single worst corner of this codebase next to voice radio. For the love of god please rewrite this.
+
//shows a list of clients we could send PMs to, then forwards our choice to cmd_Mentor_pm
/client/proc/cmd_mentor_pm_panel()
set category = "Mentor"
@@ -24,11 +26,13 @@
C = M.client
else if(istext(whom))
C = GLOB.directory[whom]
- else if(istype(whom,/client))
+ else if(istype(whom, /client))
C = whom
if(!C)
- if(is_mentor()) to_chat(src, "Error: Mentor-PM: Client not found.")
- else mentorhelp(msg) //Mentor we are replying to left. Mentorhelp instead(check below)
+ if(is_mentor())
+ to_chat(src, "Error: Mentor-PM: Client not found.")
+ else
+ mentorhelp(msg) //Mentor we are replying to left. Mentorhelp instead(check below)
return
//get message text, limit it's length.and clean/escape html
@@ -43,7 +47,7 @@
to_chat(src, "Error: Mentor-PM: Client not found.")
else
mentorhelp(msg) //Mentor we are replying to has vanished, Mentorhelp instead (how the fuck does this work?let's hope it works,shrug)
- return
+ return
// Neither party is a mentor, they shouldn't be PMing!
if (!C.is_mentor() && !is_mentor())
@@ -59,21 +63,21 @@
var/show_char = CONFIG_GET(flag/mentors_mobname_only)
if(C.is_mentor())
if(is_mentor())//both are mentors
- to_chat(C, "Mentor PM from-[key_name_mentor(src, C, 1, 0, 0)]: [msg]")
- to_chat(src, "Mentor PM to-[key_name_mentor(C, C, 1, 0, 0)]: [msg]")
+ to_chat(C, "Mentor PM from-[key_name_mentor(src, C, 1, 0)]: [msg]")
+ to_chat(src, "Mentor PM to-[key_name_mentor(C, C, 1, 0)]: [msg]")
else //recipient is an mentor but sender is not
- to_chat(C, "Reply PM from-[key_name_mentor(src, C, 1, 0, show_char)]: [msg]")
- to_chat(src, "Mentor PM to-[key_name_mentor(C, C, 1, 0, 0)]: [msg]")
+ to_chat(C, "Reply PM from-[key_name_mentor(src, C, 1, show_char)]: [msg]")
+ to_chat(src, "Mentor PM to-[key_name_mentor(C, C, 1, 0)]: [msg]")
else
if(is_mentor()) //sender is an mentor but recipient is not.
- to_chat(C, "Mentor PM from-[key_name_mentor(src, C, 1, 0, 0)]: [msg]")
- to_chat(src, "Mentor PM to-[key_name_mentor(C, C, 1, 0, show_char)]: [msg]")
+ to_chat(C, "Mentor PM from-[key_name_mentor(src, C, 1, 0)]: [msg]")
+ to_chat(src, "Mentor PM to-[key_name_mentor(C, C, 1, show_char)]: [msg]")
//we don't use message_Mentors here because the sender/receiver might get it too
var/show_char_sender = !is_mentor() && CONFIG_GET(flag/mentors_mobname_only)
var/show_char_recip = !C.is_mentor() && CONFIG_GET(flag/mentors_mobname_only)
for(var/client/X in GLOB.mentors | GLOB.admins)
if(X.key!=key && X.key!=C.key) //check client/X is an Mentor and isn't the sender or recipient
- to_chat(X, "Mentor PM: [key_name_mentor(src, X, 0, 0, show_char_sender)]->[key_name_mentor(C, X, 0, 0, show_char_recip)]:[msg]") //inform X
+ to_chat(X, "Mentor PM: [key_name_mentor(src, X, 0, show_char_sender)]->[key_name_mentor(C, X, 0, 0, show_char_recip)]:[msg]") //inform X
diff --git a/code/modules/mentor/mentorsay.dm b/code/modules/mentor/mentorsay.dm
index 17e24b4f5a01a..1143f95994200 100644
--- a/code/modules/mentor/mentorsay.dm
+++ b/code/modules/mentor/mentorsay.dm
@@ -1,6 +1,6 @@
/client/proc/cmd_mentor_say(msg as text)
set category = "Mentor"
- set name = "Msay" //Gave this shit a shorter name so you only have to time out "msay" rather than "mentor say" to use it --NeoFite
+ set name = "Msay" //Gave this shit a shorter name so you only have to type out "msay" rather than "mentor say" to use it --NeoFite
set hidden = 1
if(!is_mentor())
return
diff --git a/code/modules/mining/abandoned_crates.dm b/code/modules/mining/abandoned_crates.dm
index e083e838507e0..843cc4e76c76c 100644
--- a/code/modules/mining/abandoned_crates.dm
+++ b/code/modules/mining/abandoned_crates.dm
@@ -3,7 +3,6 @@
/obj/structure/closet/crate/secure/loot
name = "abandoned crate"
desc = "What could be inside?"
- icon_state = "securecrate"
integrity_failure = 0 //no breaking open the crate
var/code = null
var/lastattempt = null
@@ -172,6 +171,7 @@
locked = FALSE
cut_overlays()
add_overlay("securecrateg")
+ add_overlay("[icon_door || icon_state]_door") //needs to put the door overlayer back cause of this snowflake code
tamperproof = 0 // set explosion chance to zero, so we dont accidently hit it with a multitool and instantly die
else if(!input || !sanitycheck || length(sanitised) != codelen)
to_chat(user, "You leave the crate alone.")
@@ -242,3 +242,9 @@
/obj/structure/closet/crate/secure/loot/deconstruct(disassembled = TRUE)
boom()
+
+/obj/structure/closet/crate/secure/loot/emp_act(severity)
+ if(locked)
+ boom()
+ else
+ ..()
\ No newline at end of file
diff --git a/code/modules/mining/aux_base.dm b/code/modules/mining/aux_base.dm
index 9ad890dbe2049..c311a5c4747ac 100644
--- a/code/modules/mining/aux_base.dm
+++ b/code/modules/mining/aux_base.dm
@@ -21,7 +21,7 @@ interface with the mining shuttle at the landing site if a mobile beacon is also
var/launch_warning = TRUE
var/list/turrets = list() //List of connected turrets
- req_one_access = list(ACCESS_CARGO, ACCESS_CONSTRUCTION, ACCESS_HEADS, ACCESS_RESEARCH)
+ req_one_access = list(ACCESS_AUX_BASE, ACCESS_HEADS)
var/possible_destinations
clockwork = TRUE
circuit = /obj/item/circuitboard/computer/auxillary_base
diff --git a/code/modules/mining/aux_base_camera.dm b/code/modules/mining/aux_base_camera.dm
index 02c54ffbd36e1..fb710abf7e388 100644
--- a/code/modules/mining/aux_base_camera.dm
+++ b/code/modules/mining/aux_base_camera.dm
@@ -1,22 +1,22 @@
//Aux base construction console
-/mob/camera/aiEye/remote/base_construction
+/mob/camera/ai_eye/remote/base_construction
name = "construction holo-drone"
move_on_shuttle = 1 //Allows any curious crew to watch the base after it leaves. (This is safe as the base cannot be modified once it leaves)
icon = 'icons/obj/mining.dmi'
icon_state = "construction_drone"
var/area/starting_area
-/mob/camera/aiEye/remote/base_construction/Initialize()
+/mob/camera/ai_eye/remote/base_construction/Initialize()
. = ..()
starting_area = get_area(loc)
-/mob/camera/aiEye/remote/base_construction/setLoc(var/t)
+/mob/camera/ai_eye/remote/base_construction/setLoc(var/t)
var/area/curr_area = get_area(t)
if(curr_area == starting_area || istype(curr_area, /area/shuttle/auxillary_base))
return ..()
//While players are only allowed to build in the base area, but consoles starting outside the base can move into the base area to begin work.
-/mob/camera/aiEye/remote/base_construction/relaymove(mob/user, direct)
+/mob/camera/ai_eye/remote/base_construction/relaymove(mob/user, direct)
dir = direct //This camera eye is visible as a drone, and needs to keep the dir updated
..()
@@ -74,7 +74,7 @@
spawn_spot = src
- eyeobj = new /mob/camera/aiEye/remote/base_construction(get_turf(spawn_spot))
+ eyeobj = new /mob/camera/ai_eye/remote/base_construction(get_turf(spawn_spot))
eyeobj.origin = src
@@ -130,7 +130,7 @@
/datum/action/innate/aux_base //Parent aux base action
icon_icon = 'icons/mob/actions/actions_construction.dmi'
var/mob/living/C //Mob using the action
- var/mob/camera/aiEye/remote/base_construction/remote_eye //Console's eye mob
+ var/mob/camera/ai_eye/remote/base_construction/remote_eye //Console's eye mob
var/obj/machinery/computer/camera_advanced/base_construction/B //Console itself
/datum/action/innate/aux_base/Activate()
@@ -202,27 +202,27 @@
name = "Select Airlock Type"
button_icon_state = "airlock_select"
-datum/action/innate/aux_base/airlock_type/Activate()
+/datum/action/innate/aux_base/airlock_type/Activate()
if(..())
return
B.RCD.change_airlock_setting()
-datum/action/innate/aux_base/window_type
+/datum/action/innate/aux_base/window_type
name = "Select Window Type"
button_icon_state = "window_select"
-datum/action/innate/aux_base/window_type/Activate()
+/datum/action/innate/aux_base/window_type/Activate()
if(..())
return
B.RCD.toggle_window_type()
-datum/action/innate/aux_base/place_fan
+/datum/action/innate/aux_base/place_fan
name = "Place Tiny Fan"
button_icon_state = "build_fan"
-datum/action/innate/aux_base/place_fan/Activate()
+/datum/action/innate/aux_base/place_fan/Activate()
if(..())
return
@@ -244,11 +244,11 @@ datum/action/innate/aux_base/place_fan/Activate()
to_chat(owner, "Tiny fan placed. [B.fans_remaining] remaining.")
playsound(fan_turf, 'sound/machines/click.ogg', 50, 1)
-datum/action/innate/aux_base/install_turret
+/datum/action/innate/aux_base/install_turret
name = "Install Plasma Anti-Wildlife Turret"
button_icon_state = "build_turret"
-datum/action/innate/aux_base/install_turret/Activate()
+/datum/action/innate/aux_base/install_turret/Activate()
if(..())
return
diff --git a/code/modules/mining/equipment/explorer_gear.dm b/code/modules/mining/equipment/explorer_gear.dm
index bcd5f599848fe..21a6dd55abe50 100644
--- a/code/modules/mining/equipment/explorer_gear.dm
+++ b/code/modules/mining/equipment/explorer_gear.dm
@@ -10,9 +10,11 @@
max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT
heat_protection = CHEST|GROIN|LEGS|ARMS
hoodtype = /obj/item/clothing/head/hooded/explorer
- armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 50, "bio" = 100, "rad" = 50, "fire" = 50, "acid" = 50)
+ armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 50, "bio" = 100, "rad" = 50, "fire" = 50, "acid" = 50, "stamina" = 20)
allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/resonator, /obj/item/mining_scanner, /obj/item/t_scanner/adv_mining_scanner, /obj/item/gun/energy/kinetic_accelerator, /obj/item/pickaxe)
resistance_flags = FIRE_PROOF
+ high_pressure_multiplier = 0.4
+ flags_inv = HIDEJUMPSUIT
/obj/item/clothing/head/hooded/explorer
name = "explorer hood"
@@ -22,8 +24,9 @@
flags_inv = HIDEHAIR|HIDEFACE|HIDEEARS
min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT
max_heat_protection_temperature = FIRE_HELM_MAX_TEMP_PROTECT
- armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 50, "bio" = 100, "rad" = 50, "fire" = 50, "acid" = 50)
+ armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 50, "bio" = 100, "rad" = 50, "fire" = 50, "acid" = 50, "stamina" = 20)
resistance_flags = FIRE_PROOF
+ high_pressure_multiplier = 0.4
/obj/item/clothing/suit/hooded/explorer/Initialize()
. = ..()
@@ -37,11 +40,12 @@
name = "explorer gas mask"
desc = "A military-grade gas mask that can be connected to an air supply."
icon_state = "gas_mining"
+ flags_cover = MASKCOVERSEYES | MASKCOVERSMOUTH
visor_flags = BLOCK_GAS_SMOKE_EFFECT | MASKINTERNALS
visor_flags_inv = HIDEFACIALHAIR
- visor_flags_cover = MASKCOVERSMOUTH
+ visor_flags_cover = MASKCOVERSEYES | MASKCOVERSMOUTH
actions_types = list(/datum/action/item_action/adjust)
- armor = list("melee" = 10, "bullet" = 5, "laser" = 5, "energy" = 5, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 20, "acid" = 40)
+ armor = list("melee" = 10, "bullet" = 5, "laser" = 5, "energy" = 5, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 20, "acid" = 40, "stamina" = 10)
resistance_flags = FIRE_PROOF
/obj/item/clothing/mask/gas/explorer/attack_self(mob/user)
@@ -49,7 +53,7 @@
/obj/item/clothing/mask/gas/explorer/adjustmask(user)
..()
- w_class = mask_adjusted ? WEIGHT_CLASS_NORMAL : WEIGHT_CLASS_SMALL
+ w_class = mask_adjusted ? WEIGHT_CLASS_SMALL : WEIGHT_CLASS_NORMAL
/obj/item/clothing/mask/gas/explorer/folded/Initialize()
. = ..()
@@ -64,8 +68,9 @@
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
resistance_flags = FIRE_PROOF | LAVA_PROOF
slowdown = 0
- armor = list("melee" = 70, "bullet" = 40, "laser" = 20, "energy" = 20, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100)
+ armor = list("melee" = 70, "bullet" = 40, "laser" = 20, "energy" = 20, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100, "stamina" = 40)
allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/resonator, /obj/item/mining_scanner, /obj/item/t_scanner/adv_mining_scanner, /obj/item/gun/energy/kinetic_accelerator, /obj/item/pickaxe)
+ high_pressure_multiplier = 0.6
/obj/item/clothing/suit/space/hostile_environment/Initialize()
. = ..()
@@ -76,10 +81,10 @@
STOP_PROCESSING(SSobj, src)
return ..()
-/obj/item/clothing/suit/space/hostile_environment/process()
+/obj/item/clothing/suit/space/hostile_environment/process(delta_time)
var/mob/living/carbon/C = loc
- if(istype(C) && prob(2)) //cursed by bubblegum
- if(prob(15))
+ if(istype(C) && DT_PROB(1, delta_time)) //cursed by bubblegum
+ if(DT_PROB(7.5, delta_time))
new /datum/hallucination/oh_yeah(C)
to_chat(C, "[pick("I AM IMMORTAL.","I SHALL TAKE BACK WHAT'S MINE.","I SEE YOU.","YOU CANNOT ESCAPE ME FOREVER.","DEATH CANNOT HOLD ME.")]")
else
@@ -93,8 +98,9 @@
w_class = WEIGHT_CLASS_NORMAL
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
clothing_flags = THICKMATERIAL // no space protection
- armor = list("melee" = 70, "bullet" = 40, "laser" = 20, "energy" = 20, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100)
+ armor = list("melee" = 70, "bullet" = 40, "laser" = 20, "energy" = 20, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100, "stamina" = 40)
resistance_flags = FIRE_PROOF | LAVA_PROOF
+ high_pressure_multiplier = 0.6
/obj/item/clothing/head/helmet/space/hostile_environment/Initialize()
. = ..()
diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm
index a8b870d6aa255..a87023de5e524 100644
--- a/code/modules/mining/equipment/kinetic_crusher.dm
+++ b/code/modules/mining/equipment/kinetic_crusher.dm
@@ -1,5 +1,5 @@
/*********************Mining Hammer****************/
-/obj/item/twohanded/kinetic_crusher
+/obj/item/kinetic_crusher
icon = 'icons/obj/mining.dmi'
icon_state = "crusher"
item_state = "crusher0"
@@ -11,8 +11,6 @@
force = 0 //You can't hit stuff unless wielded
w_class = WEIGHT_CLASS_BULKY
slot_flags = ITEM_SLOT_BACK
- force_unwielded = 20 //It's never not wielded so these are the same
- force_wielded = 0
throwforce = 5
block_upgrade_walk = 1
throw_speed = 4
@@ -30,15 +28,16 @@
var/light_on = FALSE
var/brightness_on = 5
-/obj/item/twohanded/kinetic_crusher/Initialize()
+/obj/item/kinetic_crusher/ComponentInitialize()
. = ..()
AddComponent(/datum/component/butchering, 60, 110) //technically it's huge and bulky, but this provides an incentive to use it
+ AddComponent(/datum/component/two_handed, force_unwielded=0, force_wielded=20)
-/obj/item/twohanded/kinetic_crusher/Destroy()
+/obj/item/kinetic_crusher/Destroy()
QDEL_LIST(trophies)
return ..()
-/obj/item/twohanded/kinetic_crusher/examine(mob/living/user)
+/obj/item/kinetic_crusher/examine(mob/living/user)
. = ..()
to_chat(user, "Mark a large creature with the destabilizing force, then hit them in melee to do [force + detonation_damage] damage.")
to_chat(user, "Does [force + detonation_damage + backstab_bonus] damage if the target is backstabbed, instead of [force + detonation_damage].")
@@ -46,7 +45,7 @@
var/obj/item/crusher_trophy/T = t
to_chat(user, "It has \a [T] attached, which causes [T.effect_desc()].")
-/obj/item/twohanded/kinetic_crusher/attackby(obj/item/I, mob/living/user)
+/obj/item/kinetic_crusher/attackby(obj/item/I, mob/living/user)
if(I.tool_behaviour == TOOL_CROWBAR)
if(LAZYLEN(trophies))
to_chat(user, "You remove [src]'s trophies.")
@@ -62,8 +61,8 @@
else
return ..()
-/obj/item/twohanded/kinetic_crusher/attack(mob/living/target, mob/living/carbon/user)
- if(!wielded)
+/obj/item/kinetic_crusher/attack(mob/living/target, mob/living/carbon/user)
+ if(!ISWIELDED(src))
to_chat(user, "[src] is too heavy to use with one hand. You fumble and drop everything.")
user.drop_all_held_items()
return
@@ -79,9 +78,9 @@
if(!QDELETED(C) && !QDELETED(target))
C.total_damage += target_health - target.health //we did some damage, but let's not assume how much we did
-/obj/item/twohanded/kinetic_crusher/afterattack(atom/target, mob/living/user, proximity_flag, clickparams)
+/obj/item/kinetic_crusher/afterattack(atom/target, mob/living/user, proximity_flag, clickparams)
. = ..()
- if(!wielded)
+ if(!ISWIELDED(src))
return
if(!proximity_flag && charged)//Mark a target, or mine a tile.
var/turf/proj_turf = user.loc
@@ -128,26 +127,27 @@
C.total_damage += detonation_damage
L.apply_damage(detonation_damage, BRUTE, blocked = def_check)
-/obj/item/twohanded/kinetic_crusher/proc/Recharge()
+/obj/item/kinetic_crusher/proc/Recharge()
if(!charged)
charged = TRUE
update_icon()
playsound(src.loc, 'sound/weapons/kenetic_reload.ogg', 60, 1)
-/obj/item/twohanded/kinetic_crusher/ui_action_click(mob/user, actiontype)
+/obj/item/kinetic_crusher/ui_action_click(mob/user, actiontype)
light_on = !light_on
playsound(user, 'sound/weapons/empty.ogg', 100, TRUE)
update_brightness(user)
update_icon()
-/obj/item/twohanded/kinetic_crusher/proc/update_brightness(mob/user = null)
+/obj/item/kinetic_crusher/proc/update_brightness(mob/user = null)
if(light_on)
set_light(brightness_on)
else
set_light(0)
-/obj/item/twohanded/kinetic_crusher/update_icon()
+/obj/item/kinetic_crusher/update_icon()
..()
+ var/wielded = ISWIELDED(src)
cut_overlays()
if(!charged)
add_overlay("[icon_state]_uncharged")
@@ -169,7 +169,7 @@
flag = "bomb"
range = 6
log_override = TRUE
- var/obj/item/twohanded/kinetic_crusher/hammer_synced
+ var/obj/item/kinetic_crusher/hammer_synced
/obj/item/projectile/destabilizer/Destroy()
hammer_synced = null
@@ -201,19 +201,19 @@
var/denied_type = /obj/item/crusher_trophy
/obj/item/crusher_trophy/examine(mob/living/user)
- ..()
- to_chat(user, "Causes [effect_desc()] when attached to a kinetic crusher.")
+ . = ..()
+ . += "Causes [effect_desc()] when attached to a kinetic crusher."
/obj/item/crusher_trophy/proc/effect_desc()
return "errors"
/obj/item/crusher_trophy/attackby(obj/item/A, mob/living/user)
- if(istype(A, /obj/item/twohanded/kinetic_crusher))
+ if(istype(A, /obj/item/kinetic_crusher))
add_to(A, user)
else
..()
-/obj/item/crusher_trophy/proc/add_to(obj/item/twohanded/kinetic_crusher/H, mob/living/user)
+/obj/item/crusher_trophy/proc/add_to(obj/item/kinetic_crusher/H, mob/living/user)
for(var/t in H.trophies)
var/obj/item/crusher_trophy/T = t
if(istype(T, denied_type) || istype(src, T.denied_type))
@@ -225,7 +225,7 @@
to_chat(user, "You attach [src] to [H].")
return TRUE
-/obj/item/crusher_trophy/proc/remove_from(obj/item/twohanded/kinetic_crusher/H, mob/living/user)
+/obj/item/crusher_trophy/proc/remove_from(obj/item/kinetic_crusher/H, mob/living/user)
forceMove(get_turf(H))
H.trophies -= src
return TRUE
@@ -312,12 +312,12 @@
/obj/item/crusher_trophy/legion_skull/effect_desc()
return "a kinetic crusher to recharge [bonus_value*0.1] second\s faster"
-/obj/item/crusher_trophy/legion_skull/add_to(obj/item/twohanded/kinetic_crusher/H, mob/living/user)
+/obj/item/crusher_trophy/legion_skull/add_to(obj/item/kinetic_crusher/H, mob/living/user)
. = ..()
if(.)
H.charge_time -= bonus_value
-/obj/item/crusher_trophy/legion_skull/remove_from(obj/item/twohanded/kinetic_crusher/H, mob/living/user)
+/obj/item/crusher_trophy/legion_skull/remove_from(obj/item/kinetic_crusher/H, mob/living/user)
. = ..()
if(.)
H.charge_time += bonus_value
@@ -370,21 +370,19 @@
/obj/item/crusher_trophy/demon_claws/effect_desc()
return "melee hits to do [bonus_value * 0.2] more damage and heal you for [bonus_value * 0.1], with 5X effect on mark detonation"
-/obj/item/crusher_trophy/demon_claws/add_to(obj/item/twohanded/kinetic_crusher/H, mob/living/user)
+/obj/item/crusher_trophy/demon_claws/add_to(obj/item/kinetic_crusher/H, mob/living/user)
. = ..()
if(.)
H.force += bonus_value * 0.2
- H.force_unwielded += bonus_value * 0.2
- H.force_wielded += bonus_value * 0.2
H.detonation_damage += bonus_value * 0.8
+ AddComponent(/datum/component/two_handed, force_wielded=(20 + bonus_value * 0.2))
-/obj/item/crusher_trophy/demon_claws/remove_from(obj/item/twohanded/kinetic_crusher/H, mob/living/user)
+/obj/item/crusher_trophy/demon_claws/remove_from(obj/item/kinetic_crusher/H, mob/living/user)
. = ..()
if(.)
H.force -= bonus_value * 0.2
- H.force_unwielded -= bonus_value * 0.2
- H.force_wielded -= bonus_value * 0.2
H.detonation_damage -= bonus_value * 0.8
+ AddComponent(/datum/component/two_handed, force_wielded=20)
/obj/item/crusher_trophy/demon_claws/on_melee_hit(mob/living/target, mob/living/user)
user.heal_ordered_damage(bonus_value * 0.1, damage_heal_order)
diff --git a/code/modules/mining/equipment/marker_beacons.dm b/code/modules/mining/equipment/marker_beacons.dm
index f4d1afd8b7306..a687b1b46e4f9 100644
--- a/code/modules/mining/equipment/marker_beacons.dm
+++ b/code/modules/mining/equipment/marker_beacons.dm
@@ -37,8 +37,8 @@ GLOBAL_LIST_INIT(marker_beacon_colors, sortList(list(
/obj/item/stack/marker_beacon/examine(mob/user)
. = ..()
- . += {"Use in-hand to place a [singular_name].\n
- Alt-click to select a color. Current color is [picked_color]."}
+ . += "Use in-hand to place a [singular_name].\n"+\
+ "Alt-click to select a color. Current color is [picked_color]."
/obj/item/stack/marker_beacon/update_icon()
icon_state = "marker[lowertext(picked_color)]"
@@ -72,7 +72,7 @@ GLOBAL_LIST_INIT(marker_beacon_colors, sortList(list(
icon = 'icons/obj/lighting.dmi'
icon_state = "markerrandom"
layer = BELOW_OPEN_DOOR_LAYER
- armor = list("melee" = 50, "bullet" = 75, "laser" = 75, "energy" = 75, "bomb" = 25, "bio" = 100, "rad" = 100, "fire" = 25, "acid" = 0)
+ armor = list("melee" = 50, "bullet" = 75, "laser" = 75, "energy" = 75, "bomb" = 25, "bio" = 100, "rad" = 100, "fire" = 25, "acid" = 0, "stamina" = 0)
max_integrity = 50
anchored = TRUE
light_range = 2
@@ -135,7 +135,6 @@ GLOBAL_LIST_INIT(marker_beacon_colors, sortList(list(
return ..()
/obj/structure/marker_beacon/AltClick(mob/living/user)
- ..()
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE))
return
var/input_color = input(user, "Choose a color.", "Beacon Color") as null|anything in GLOB.marker_beacon_colors
diff --git a/code/modules/mining/equipment/mineral_scanner.dm b/code/modules/mining/equipment/mineral_scanner.dm
index dadbdf2af3f69..46cf736273934 100644
--- a/code/modules/mining/equipment/mineral_scanner.dm
+++ b/code/modules/mining/equipment/mineral_scanner.dm
@@ -24,10 +24,12 @@
/obj/item/mining_scanner/admin
/obj/item/mining_scanner/admin/attack_self(mob/user)
- for(var/turf/closed/mineral/M in world)
- if(M.scan_state)
- M.icon_state = M.scan_state
- qdel(src)
+ for(var/area/A as() in get_areas(/area, user.z))
+ for(var/turf/closed/mineral/M in A)
+ if(M.scan_state)
+ var/obj/effect/temp_visual/mining_overlay/C = new /obj/effect/temp_visual/mining_overlay(M)
+ C.icon_state = M.scan_state
+ //qdel(src)
/obj/item/t_scanner/adv_mining_scanner
desc = "A scanner that automatically checks surrounding rock for useful minerals; it can also be used to stop gibtonite detonations. This one has an extended range."
@@ -61,17 +63,54 @@
mineral_scan_pulse(t, range)
/proc/mineral_scan_pulse(turf/T, range = world.view)
- var/list/minerals = list()
- for(var/turf/closed/mineral/M in range(range, T))
- if(M.scan_state)
- minerals += M
- if(LAZYLEN(minerals))
- for(var/turf/closed/mineral/M in minerals)
- var/obj/effect/temp_visual/mining_overlay/oldC = locate(/obj/effect/temp_visual/mining_overlay) in M
- if(oldC)
- qdel(oldC)
- var/obj/effect/temp_visual/mining_overlay/C = new /obj/effect/temp_visual/mining_overlay(M)
- C.icon_state = M.scan_state
+ var/list/parsedrange = getviewsize(range)
+ var/xrange = (parsedrange[1] - 1) / 2
+ var/yrange = (parsedrange[2] - 1) / 2
+ var/cx = T.x
+ var/cy = T.y
+ for(var/r in 1 to max(xrange, yrange))
+ var/xr = min(xrange, r)
+ var/yr = min(yrange, r)
+ var/turf/TL = locate(cx - xr, cy + yr, T.z)
+ var/turf/BL = locate(cx - xr, cy - yr, T.z)
+ var/turf/TR = locate(cx + xr, cy + yr, T.z)
+ var/turf/BR = locate(cx + xr, cy - yr, T.z)
+ var/list/turfs = list()
+ turfs += block(TL, TR)
+ turfs += block(TL, BL)
+ turfs |= block(BL, BR)
+ turfs |= block(BR, TR)
+ for(var/turf/closed/mineral/M in turfs)
+ new /obj/effect/temp_visual/mining_scanner(M)
+ if(M.scan_state)
+ var/obj/effect/temp_visual/mining_overlay/oldC = locate(/obj/effect/temp_visual/mining_overlay) in M
+ if(oldC)
+ qdel(oldC)
+ var/obj/effect/temp_visual/mining_overlay/C = new /obj/effect/temp_visual/mining_overlay(M)
+ C.icon_state = M.scan_state
+ sleep(1)
+
+/proc/pulse_effect(turf/T, range = world.view)
+ var/list/parsedrange = getviewsize(range)
+ var/xrange = (parsedrange[1] - 1) / 2
+ var/yrange = (parsedrange[2] - 1) / 2
+ var/cx = T.x
+ var/cy = T.y
+ for(var/r in 1 to max(xrange, yrange))
+ var/xr = min(xrange, r)
+ var/yr = min(yrange, r)
+ var/turf/TL = locate(cx - xr, cy + yr, T.z)
+ var/turf/BL = locate(cx - xr, cy - yr, T.z)
+ var/turf/TR = locate(cx + xr, cy + yr, T.z)
+ var/turf/BR = locate(cx + xr, cy - yr, T.z)
+ var/list/turfs = list()
+ turfs += block(TL, TR)
+ turfs += block(TL, BL)
+ turfs |= block(BL, BR)
+ turfs |= block(BR, TR)
+ for(var/turf/M in turfs)
+ new /obj/effect/temp_visual/mining_scanner(M)
+ sleep(1)
/obj/effect/temp_visual/mining_overlay
plane = FULLSCREEN_PLANE
@@ -85,3 +124,18 @@
/obj/effect/temp_visual/mining_overlay/Initialize()
. = ..()
animate(src, alpha = 0, time = duration, easing = EASE_IN)
+
+/obj/effect/temp_visual/mining_scanner
+ plane = FULLSCREEN_PLANE
+ layer = FLASH_LAYER
+ icon = 'icons/effects/mining_scanner.dmi'
+ appearance_flags = 0
+ pixel_x = -224
+ pixel_y = -224
+ duration = 3
+ alpha = 100
+ icon_state = "mining_scan"
+
+/obj/effect/temp_visual/mining_scanner/Initialize()
+ . = ..()
+ animate(src, alpha = 0, time = duration, easing = EASE_IN)
diff --git a/code/modules/mining/equipment/regenerative_core.dm b/code/modules/mining/equipment/regenerative_core.dm
index a14b17c0ba57b..1f6cd4d11221e 100644
--- a/code/modules/mining/equipment/regenerative_core.dm
+++ b/code/modules/mining/equipment/regenerative_core.dm
@@ -12,9 +12,12 @@
if(!istype(C, /obj/item/organ/regenerative_core))
to_chat(user, "The stabilizer only works on certain types of monster organs, generally regenerative in nature.")
return ..()
+ if(C.preserved)
+ to_chat(user, "[M] is already stabilised.")
+ return
C.preserved()
- to_chat(user, "You inject the [M] with the stabilizer. It will no longer go inert.")
+ to_chat(user, "You inject [M] with the stabilizer. It will no longer go inert.")
qdel(src)
/************************Hivelord core*******************/
diff --git a/code/modules/mining/equipment/survival_pod.dm b/code/modules/mining/equipment/survival_pod.dm
index 5c68cdbcfb7a4..df2d72d1dc51b 100644
--- a/code/modules/mining/equipment/survival_pod.dm
+++ b/code/modules/mining/equipment/survival_pod.dm
@@ -5,7 +5,7 @@
dynamic_lighting = DYNAMIC_LIGHTING_FORCED
requires_power = FALSE
has_gravity = STANDARD_GRAVITY
- valid_territory = FALSE
+ area_flags = BLOBS_ALLOWED | UNIQUE_AREA
//Survival Capsule
/obj/item/survivalcapsule
@@ -33,8 +33,9 @@
/obj/item/survivalcapsule/examine(mob/user)
. = ..()
get_template()
- . += "This capsule has the [template.name] stored."
- . += template.description
+ if(template)
+ . += "This capsule has the [template.name] stored."
+ . += template.description
/obj/item/survivalcapsule/attack_self()
//Can't grab when capsule is New() because templates aren't loaded then
@@ -91,14 +92,14 @@
icon_state = "capsulemed"
icon = 'icons/obj/mining.dmi'
template_id = "shelter_echo"
-
+
/obj/item/survivalcapsule/space
name = "space shelter capsule"
desc = "A spaceworthy shelter designed for emergencies/construction in a bluespace capsule."
icon_state = "capsuleeng"
icon = 'icons/obj/mining.dmi'
template_id = "shelter_eta"
-
+
/obj/item/survivalcapsule/barricade
name = "barricade capsule"
desc = "A 3x3 glass barricade designed for security use with energy weapons."
@@ -143,6 +144,7 @@
icon = 'icons/obj/doors/airlocks/survival/survival.dmi'
overlays_file = 'icons/obj/doors/airlocks/survival/survival_overlays.dmi'
assemblytype = /obj/structure/door_assembly/door_assembly_pod
+ anim_parts = "topbolts=0,6,0,3;bottombolts=0,-6,3,-6;top=0,4,0,2;bottom=0,-4,0,2;rightbolts=14,0,1.5,5;left=-15,0,1.5,5;right=14,0,1.5,5"
/obj/machinery/door/airlock/survival_pod/glass
opacity = FALSE
@@ -349,7 +351,7 @@
/obj/item/energy_katana,
/obj/item/hierophant_club,
/obj/item/his_grace,
- /obj/item/gun/ballistic/minigun,
+ /obj/item/gun/energy/minigun,
/obj/item/gun/ballistic/automatic/l6_saw,
/obj/item/gun/magic/staff/chaos,
/obj/item/gun/magic/staff/spellblade,
diff --git a/code/modules/mining/equipment/wormhole_jaunter.dm b/code/modules/mining/equipment/wormhole_jaunter.dm
index 1d769fa83cf34..07bd5b7d09443 100644
--- a/code/modules/mining/equipment/wormhole_jaunter.dm
+++ b/code/modules/mining/equipment/wormhole_jaunter.dm
@@ -58,7 +58,7 @@
var/mob/M = loc
if(istype(M))
var/triggered = FALSE
- if(M.get_item_by_slot(SLOT_BELT) == src)
+ if(M.get_item_by_slot(ITEM_SLOT_BELT) == src)
if(power == 1)
triggered = TRUE
else if(power == 2 && prob(50))
@@ -70,7 +70,7 @@
activate(M)
/obj/item/wormhole_jaunter/proc/chasm_react(mob/user)
- if(user.get_item_by_slot(SLOT_BELT) == src)
+ if(user.get_item_by_slot(ITEM_SLOT_BELT) == src)
to_chat(user, "Your [name] activates, saving you from the chasm!")
SSblackbox.record_feedback("tally", "jaunter", 1, "Chasm") // chasm automatic activation
activate(user, FALSE)
diff --git a/code/modules/mining/fulton.dm b/code/modules/mining/fulton.dm
index bbba80d85a2b2..bf33eecde43ef 100644
--- a/code/modules/mining/fulton.dm
+++ b/code/modules/mining/fulton.dm
@@ -75,7 +75,7 @@ GLOBAL_LIST_EMPTY(total_extraction_beacons)
if(isliving(A))
var/mob/living/M = A
M.Paralyze(320) // Keep them from moving during the duration of the extraction
- M.buckled = 0 // Unbuckle them to prevent anchoring problems
+ M.buckled?.unbuckle_mob(M, TRUE) // Unbuckle them to prevent anchoring problems
else
A.anchored = TRUE
A.density = FALSE
@@ -112,9 +112,9 @@ GLOBAL_LIST_EMPTY(total_extraction_beacons)
L.SetSleeping(0)
sleep(30)
var/list/flooring_near_beacon = list()
- for(var/turf/open/floor in orange(1, beacon))
+ for(var/turf/open/floor in (RANGE_TURFS(1, beacon)-get_turf(beacon)))
flooring_near_beacon += floor
- holder_obj.forceMove(pick(flooring_near_beacon))
+ do_teleport(holder_obj, pick(flooring_near_beacon), no_effects = TRUE, channel = TELEPORT_CHANNEL_FREE)
animate(holder_obj, pixel_z = 10, time = 50)
sleep(50)
animate(holder_obj, pixel_z = 15, time = 10)
@@ -169,7 +169,7 @@ GLOBAL_LIST_EMPTY(total_extraction_beacons)
/obj/effect/extraction_holder
name = "extraction holder"
- desc = "you shouldnt see this"
+ desc = "you shouldn't see this"
var/atom/movable/stored_obj
/obj/item/extraction_pack/proc/check_for_living_mobs(atom/A)
diff --git a/code/modules/mining/laborcamp/laborshuttle.dm b/code/modules/mining/laborcamp/laborshuttle.dm
index dbe607f42fa35..c4ceb4d51a795 100644
--- a/code/modules/mining/laborcamp/laborshuttle.dm
+++ b/code/modules/mining/laborcamp/laborshuttle.dm
@@ -1,4 +1,4 @@
-/obj/machinery/computer/shuttle/labor
+/obj/machinery/computer/shuttle_flight/labor
name = "labor shuttle console"
desc = "Used to call and send the labor camp shuttle."
circuit = /obj/item/circuitboard/computer/labor_shuttle
@@ -7,21 +7,9 @@
req_access = list(ACCESS_BRIG)
-/obj/machinery/computer/shuttle/labor/one_way
+/obj/machinery/computer/shuttle_flight/labor/one_way
name = "prisoner shuttle console"
desc = "A one-way shuttle console, used to summon the shuttle to the labor camp."
- possible_destinations = "laborcamp_away"
+ recall_docking_port_id = "laborcamp_away"
circuit = /obj/item/circuitboard/computer/labor_shuttle/one_way
req_access = list( )
-
-/obj/machinery/computer/shuttle/labor/one_way/Topic(href, href_list)
- if(href_list["move"])
- var/obj/docking_port/mobile/M = SSshuttle.getShuttle("laborcamp")
- if(!M)
- to_chat(usr, "Cannot locate shuttle!")
- return 0
- var/obj/docking_port/stationary/S = M.get_docked()
- if(S && S.name == "laborcamp_away")
- to_chat(usr, "Shuttle is already at the outpost!")
- return 0
- ..()
\ No newline at end of file
diff --git a/code/modules/mining/laborcamp/laborstacker.dm b/code/modules/mining/laborcamp/laborstacker.dm
index d179ab9a94373..250f35d82e5ce 100644
--- a/code/modules/mining/laborcamp/laborstacker.dm
+++ b/code/modules/mining/laborcamp/laborstacker.dm
@@ -18,9 +18,12 @@ GLOBAL_LIST(labor_sheet_values)
/obj/machinery/mineral/labor_claim_console/Initialize()
. = ..()
- Radio = new/obj/item/radio(src)
+ Radio = new /obj/item/radio(src)
Radio.listening = FALSE
locate_stacking_machine()
+ //If we can't find a stacking machine end it all ok?
+ if(!stacking_machine)
+ return INITIALIZE_HINT_QDEL
if(!GLOB.labor_sheet_values)
var/sheet_list = list()
@@ -31,6 +34,13 @@ GLOBAL_LIST(labor_sheet_values)
sheet_list += list(list("ore" = initial(sheet.name), "value" = initial(sheet.point_value)))
GLOB.labor_sheet_values = sortList(sheet_list, /proc/cmp_sheet_list)
+/obj/machinery/mineral/labor_claim_console/Destroy()
+ QDEL_NULL(Radio)
+ if(stacking_machine)
+ stacking_machine.console = null
+ stacking_machine = null
+ return ..()
+
/proc/cmp_sheet_list(list/a, list/b)
return a["value"] - b["value"]
@@ -43,6 +53,7 @@ GLOBAL_LIST(labor_sheet_values)
if(!ui)
ui = new(user, src, "LaborClaimConsole")
ui.open()
+ ui.set_autoupdate(TRUE)
/obj/machinery/mineral/labor_claim_console/ui_static_data(mob/user)
var/list/data = list()
@@ -114,9 +125,7 @@ GLOBAL_LIST(labor_sheet_values)
/obj/machinery/mineral/labor_claim_console/proc/locate_stacking_machine()
stacking_machine = locate(/obj/machinery/mineral/stacking_machine, get_step(src, machinedir))
if(stacking_machine)
- stacking_machine.CONSOLE = src
- else
- qdel(src)
+ stacking_machine.console = src
/obj/machinery/mineral/labor_claim_console/emag_act(mob/user)
if(!(obj_flags & EMAGGED))
diff --git a/code/modules/mining/lavaland/ash_flora.dm b/code/modules/mining/lavaland/ash_flora.dm
index 8be7ce7ee3bed..85bb74c97f57c 100644
--- a/code/modules/mining/lavaland/ash_flora.dm
+++ b/code/modules/mining/lavaland/ash_flora.dm
@@ -18,6 +18,7 @@
var/harvest_message_low = "You pick a mushroom, but fail to collect many shavings from its cap."
var/harvest_message_med = "You pick a mushroom, carefully collecting the shavings from its cap."
var/harvest_message_high = "You harvest and collect shavings from several mushroom caps."
+ var/destroy_on_harvest = FALSE
var/harvested = FALSE
var/base_icon
var/regrowth_time_low = 8 MINUTES
@@ -44,6 +45,8 @@
for(var/i in 1 to rand_harvested)
new harvest(get_turf(src))
+ if(destroy_on_harvest)
+ Destroy()
icon_state = "[base_icon]p"
name = harvested_name
desc = harvested_desc
@@ -58,7 +61,7 @@
harvested = FALSE
/obj/structure/flora/ash/attackby(obj/item/W, mob/user, params)
- if(!harvested && needs_sharp_harvest && W.sharpness)
+ if(!harvested && needs_sharp_harvest && W.is_sharp())
user.visible_message("[user] starts to harvest from [src] with [W].","You begin to harvest from [src] with [W].")
if(do_after(user, harvest_time, target = src))
harvest(user)
@@ -141,6 +144,21 @@
regrowth_time_low = 4800
regrowth_time_high = 7200
+
+/obj/structure/flora/ash/strange
+ icon_state = "xpod"
+ name = "strange plant"
+ desc = "An alient plant born under warming glow of space radiation. What mysteries does it hold? Botanist should know."
+ harvest = /obj/item/reagent_containers/food/snacks/grown/random
+ needs_sharp_harvest = FALSE
+ harvest_amount_high = 2
+ harvest_time = 10
+ harvest_message_low = "You bravely pick a strange plant."
+ harvest_message_high = "You bravely pick a pair of strange plant."
+ light_range = 1.5
+ light_power = 2.1
+ destroy_on_harvest = TRUE
+
/obj/structure/flora/ash/cacti/Initialize(mapload)
. = ..()
// min dmg 3, max dmg 6, prob(70)
diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm
index b55eb29de51ad..7066e45779616 100644
--- a/code/modules/mining/lavaland/necropolis_chests.dm
+++ b/code/modules/mining/lavaland/necropolis_chests.dm
@@ -1,60 +1,59 @@
//The chests dropped by mob spawner tendrils. Also contains associated loot.
-#define HIEROPHANT_CLUB_CARDINAL_DAMAGE 30
+#define HIEROPHANT_CLUB_CARDINAL_DAMAGE 15
/obj/structure/closet/crate/necropolis
name = "necropolis chest"
desc = "It's watching you closely."
- icon_state = "necrocrate"
+ icon_state = "necro_crate"
resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
+ door_anim_time = 0
/obj/structure/closet/crate/necropolis/tendril
desc = "It's watching you suspiciously."
+ ///prevents bust_open to fire
+ integrity_failure = 0
+ /// var to check if it got opened by a key
+ var/spawned_loot = FALSE
-/obj/structure/closet/crate/necropolis/tendril/PopulateContents()
- var/loot = rand(1,30)
+/obj/structure/closet/crate/necropolis/tendril/Initialize()
+ . = ..()
+ RegisterSignal(src, COMSIG_PARENT_ATTACKBY, .proc/try_spawn_loot)
+
+/obj/structure/closet/crate/necropolis/tendril/proc/try_spawn_loot(datum/source, obj/item/item, mob/user, params) ///proc that handles key checking and generating loot
+ SIGNAL_HANDLER
+
+ if(!istype(item, /obj/item/skeleton_key) || spawned_loot)
+ return FALSE
+ var/loot = rand(1,25)
switch(loot)
- if(1)
- new /obj/item/shared_storage/red(src)
- if(2)
- new /obj/item/clothing/suit/space/hardsuit/cult(src)
- if(3)
- new /obj/item/soulstone/anybody(src)
- if(4)
- new /obj/item/katana/cursed(src)
- if(5)
- new /obj/item/clothing/glasses/godeye(src)
- if(6)
- new /obj/item/reagent_containers/glass/bottle/potion/flight(src)
- if(7)
- new /obj/item/pickaxe/diamond(src)
- if(8)
- if(prob(50))
- new /obj/item/disk/design_disk/modkit_disc/resonator_blast(src)
- else
- new /obj/item/disk/design_disk/modkit_disc/rapid_repeater(src)
+ if(1 to 2)
+ new /obj/item/disk/design_disk/modkit_disc/resonator_blast(src) //Doubled chance to receive upgrade disk that is directly relevant to mining
+ if(3 to 4)
+ new /obj/item/disk/design_disk/modkit_disc/rapid_repeater(src)
+ if(5 to 6)
+ new /obj/item/disk/design_disk/modkit_disc/mob_and_turf_aoe(src)
+ if(7 to 8)
+ new /obj/item/disk/design_disk/modkit_disc/bounty(src)
if(9)
- new /obj/item/rod_of_asclepius(src)
+ new /obj/item/borg/upgrade/modkit/lifesteal(src)
if(10)
- new /obj/item/organ/heart/cursed/wizard(src)
+ new /obj/item/shared_storage/red(src)
if(11)
- new /obj/item/ship_in_a_bottle(src)
+ new /obj/item/clothing/glasses/godeye(src)
if(12)
- new /obj/item/clothing/suit/space/hardsuit/ert/paranormal/beserker(src)
+ new /obj/item/reagent_containers/glass/bottle/potion/flight(src)
if(13)
- new /obj/item/jacobs_ladder(src)
+ new /obj/item/pickaxe/diamond(src) //Ashwalkers exist. This is actually a great drop for them
if(14)
- new /obj/item/nullrod/scythe/talking(src)
+ new /obj/item/rod_of_asclepius(src)
if(15)
- new /obj/item/nullrod/armblade(src)
+ new /obj/item/organ/heart/cursed/wizard(src)
if(16)
- new /obj/item/guardiancreator/hive(src)
+ new /obj/item/ship_in_a_bottle(src)
if(17)
- if(prob(50))
- new /obj/item/disk/design_disk/modkit_disc/mob_and_turf_aoe(src)
- else
- new /obj/item/disk/design_disk/modkit_disc/bounty(src)
+ new /obj/item/jacobs_ladder(src)
if(18)
new /obj/item/warp_cube/red(src)
if(19)
@@ -64,25 +63,28 @@
if(21)
new /obj/item/gun/magic/hook(src)
if(22)
- new /obj/item/voodoo(src)
- if(23)
- new /obj/item/grenade/clusterbuster/inferno(src)
- if(24)
- new /obj/item/reagent_containers/food/drinks/bottle/holywater/hell(src)
- new /obj/item/clothing/suit/space/hardsuit/ert/paranormal/inquisitor(src)
- if(25)
- new /obj/item/book/granter/spell/summonitem(src)
- if(26)
new /obj/item/book_of_babel(src)
- if(27)
- new /obj/item/borg/upgrade/modkit/lifesteal(src)
- new /obj/item/bedsheet/cult(src)
- if(28)
+ if(23)
new /obj/item/clothing/neck/necklace/memento_mori(src)
- if(29)
+ if(24)
new /obj/item/reagent_containers/glass/waterbottle/relic(src)
- if(30)
+ if(25)
new /obj/item/reagent_containers/glass/bottle/necropolis_seed(src)
+ spawned_loot = TRUE
+ qdel(item)
+ to_chat(user, "You disable the magic lock, revealing the loot.")
+ return TRUE
+
+/obj/structure/closet/crate/necropolis/tendril/can_open(mob/living/user, force = FALSE)
+ if(!spawned_loot)
+ return FALSE
+ return ..()
+
+/obj/structure/closet/crate/necropolis/tendril/examine(mob/user)
+ . = ..()
+ if(!spawned_loot)
+ . += "You need a skeleton key to open it."
+
//KA modkit design discs
/obj/item/disk/design_disk/modkit_disc
@@ -216,7 +218,7 @@
var/mob/living/carbon/human/active_owner
/obj/item/clothing/neck/necklace/memento_mori/item_action_slot_check(slot)
- return slot == SLOT_NECK
+ return slot == ITEM_SLOT_NECK
/obj/item/clothing/neck/necklace/memento_mori/dropped(mob/user)
..()
@@ -329,6 +331,8 @@
to_chat(orbits.parent, "Your vision returns to normal.")
/obj/effect/wisp/proc/update_user_sight(mob/user)
+ SIGNAL_HANDLER
+
user.sight |= sight_flags
if(!isnull(lighting_alpha))
user.lighting_alpha = min(user.lighting_alpha, lighting_alpha)
@@ -357,15 +361,26 @@
var/obj/item/warp_cube/linked
var/teleporting = FALSE
+/obj/item/warp_cube/Destroy()
+ if(!QDELETED(linked))
+ qdel(linked)
+ linked = null
+ return ..()
+
/obj/item/warp_cube/attack_self(mob/user)
if(!linked)
to_chat(user, "[src] fizzles uselessly.")
return
if(teleporting)
return
+ var/turf/T = get_turf(src)
+ var/area/A1 = get_area(T)
+ var/area/A2 = get_area(linked)
+ if(A1.teleport_restriction || A2.teleport_restriction)
+ to_chat(user, "[src] fizzles gently as it fails to breach the bluespace veil.")
+ return
teleporting = TRUE
linked.teleporting = TRUE
- var/turf/T = get_turf(src)
new /obj/effect/temp_visual/warp_cube(T, user, teleport_color, TRUE)
SSblackbox.record_feedback("tally", "warp_cube", 1, type)
new /obj/effect/temp_visual/warp_cube(get_turf(linked), user, linked.teleport_color, FALSE)
@@ -379,7 +394,7 @@
user.forceMove(get_turf(link_holder))
qdel(link_holder)
return
- link_holder.forceMove(get_turf(linked))
+ do_teleport(link_holder, get_turf(linked), no_effects = TRUE, channel = TELEPORT_CHANNEL_MAGIC)
sleep(2.5)
if(QDELETED(user))
qdel(link_holder)
@@ -421,8 +436,8 @@
righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
fire_sound = 'sound/weapons/batonextend.ogg'
max_charges = 1
- item_flags = NEEDS_PERMIT | NOBLUDGEON
- force = 18
+ item_flags = NEEDS_PERMIT
+ force = 15
attack_weight = 2
/obj/item/ammo_casing/magic/hook
@@ -437,11 +452,11 @@
icon_state = "hook"
icon = 'icons/obj/lavaland/artefacts.dmi'
pass_flags = PASSTABLE
- damage = 25
+ damage = 10
armour_penetration = 100
damage_type = BRUTE
hitsound = 'sound/effects/splat.ogg'
- paralyze = 30
+ knockdown = 30
var/chain
/obj/item/projectile/hook/fire(setAngle)
@@ -692,13 +707,31 @@
///Bosses
+//Legion
+
+/obj/structure/closet/crate/necropolis/legion
+ name = "legion chest"
+
+/obj/structure/closet/crate/necropolis/legion/PopulateContents()
+ var/list/choices = subtypesof(/obj/machinery/anomalous_crystal)
+ var/random_crystal = pick(choices)
+ new random_crystal(src)
+ new /obj/effect/spawner/lootdrop/megafaunaore(src)
+
//Miniboss Miner
+/obj/structure/closet/crate/necropolis/bdm
+ name = "blood-drunk miner chest"
+
+/obj/structure/closet/crate/necropolis/bdm/PopulateContents()
+ new /obj/item/melee/transforming/cleaving_saw(src)
+ new /obj/effect/spawner/lootdrop/megafaunaore(src)
+
/obj/item/melee/transforming/cleaving_saw
name = "cleaving saw"
desc = "This saw, effective at drawing the blood of beasts, transforms into a long cleaver that makes use of centrifugal force."
- force = 12
- force_on = 20 //force when active
+ force = 8
+ force_on = 15 //force when active
throwforce = 20
throwforce_on = 20
icon = 'icons/obj/lavaland/artefacts.dmi'
@@ -716,16 +749,16 @@
block_upgrade_walk = 1
w_class = WEIGHT_CLASS_BULKY
sharpness = IS_SHARP
- faction_bonus_force = 30
+ faction_bonus_force = 45
nemesis_factions = list("mining", "boss")
var/transform_cooldown
var/swiping = FALSE
/obj/item/melee/transforming/cleaving_saw/examine(mob/user)
. = ..()
- . += {"It is [active ? "open, will cleave enemies in a wide arc and deal additional damage to fauna":"closed, and can be used for rapid consecutive attacks that cause fauna to bleed"].\n
- Both modes will build up existing bleed effects, doing a burst of high damage if the bleed is built up high enough.\n
- Transforming it immediately after an attack causes the next attack to come out faster."}
+ . += "It is [active ? "open, will cleave enemies in a wide arc and deal additional damage to fauna":"closed, and can be used for rapid consecutive attacks that cause fauna to bleed"].\n"+\
+ "Both modes will build up existing bleed effects, doing a burst of high damage if the bleed is built up high enough.\n"+\
+ "Transforming it immediately after an attack causes the next attack to come out faster."
/obj/item/melee/transforming/cleaving_saw/suicide_act(mob/user)
user.visible_message("[user] is [active ? "closing [src] on [user.p_their()] neck" : "opening [src] into [user.p_their()] chest"]! It looks like [user.p_theyre()] trying to commit suicide!")
@@ -792,24 +825,10 @@
name = "dragon chest"
/obj/structure/closet/crate/necropolis/dragon/PopulateContents()
- var/loot = rand(1,4)
- switch(loot)
- if(1)
- new /obj/item/melee/ghost_sword(src)
- if(2)
- new /obj/item/lava_staff(src)
- if(3)
- new /obj/item/book/granter/spell/sacredflame(src)
- new /obj/item/gun/magic/wand/fireball(src)
- if(4)
- new /obj/item/dragons_blood(src)
+ new /obj/effect/spawner/lootdrop/megafaunaore(src)
+ new /obj/item/dragons_blood(src)
-/obj/structure/closet/crate/necropolis/dragon/crusher
- name = "firey dragon chest"
-
-/obj/structure/closet/crate/necropolis/dragon/crusher/PopulateContents()
- ..()
- new /obj/item/crusher_trophy/tail_spike(src)
+// Ghost Sword - left in for other references and admin shenanigans
/obj/item/melee/ghost_sword
name = "\improper spectral blade"
@@ -919,7 +938,7 @@
return
var/mob/living/carbon/human/H = user
- var/random = rand(1,4)
+ var/random = rand(1,3)
switch(random)
if(1)
@@ -931,11 +950,6 @@
to_chat(user, "Your flesh begins to melt! Miraculously, you seem fine otherwise.")
H.set_species(/datum/species/skeleton)
if(3)
- to_chat(user, "Power courses through you! You can now shift your form at will.")
- if(user.mind)
- var/obj/effect/proc_holder/spell/targeted/shapeshift/dragon/D = new
- user.mind.AddSpell(D)
- if(4)
to_chat(user, "You feel like you could walk straight through lava now.")
H.weather_immunities |= "lava"
@@ -949,7 +963,7 @@
agent = "dragon's blood"
desc = "What do dragons have to do with Space Station 13?"
stage_prob = 20
- severity = DISEASE_SEVERITY_BIOHAZARD
+ danger = DISEASE_BIOHAZARD
visibility_flags = 0
stage1 = list("Your bones ache.")
stage2 = list("Your skin feels scaly.")
@@ -993,7 +1007,7 @@
if(is_type_in_typecache(target, banned_turfs))
return
- if(target in view(user.client.view, get_turf(user)))
+ if(user in viewers(user.client.view, get_turf(target)))
var/turf/open/T = get_turf(target)
if(!istype(T))
@@ -1033,21 +1047,7 @@
/obj/structure/closet/crate/necropolis/bubblegum/PopulateContents()
new /obj/item/clothing/suit/space/hostile_environment(src)
new /obj/item/clothing/head/helmet/space/hostile_environment(src)
- var/loot = rand(1,3)
- switch(loot)
- if(1)
- new /obj/item/mayhem(src)
- if(2)
- new /obj/item/blood_contract(src)
- if(3)
- new /obj/item/gun/magic/staff/spellblade(src)
-
-/obj/structure/closet/crate/necropolis/bubblegum/crusher
- name = "bloody bubblegum chest"
-
-/obj/structure/closet/crate/necropolis/bubblegum/crusher/PopulateContents()
- ..()
- new /obj/item/crusher_trophy/demon_claws(src)
+ new /obj/effect/spawner/lootdrop/megafaunaore(src)
/obj/item/mayhem
name = "mayhem in a bottle"
@@ -1119,19 +1119,19 @@
return ..()
/obj/structure/closet/crate/necropolis/colossus/PopulateContents()
- var/list/choices = subtypesof(/obj/machinery/anomalous_crystal)
- var/random_crystal = pick(choices)
- new random_crystal(src)
new /obj/item/organ/vocal_cords/colossus(src)
+ new /obj/effect/spawner/lootdrop/megafaunaore(src)
-/obj/structure/closet/crate/necropolis/colossus/crusher
- name = "angelic colossus chest"
-
-/obj/structure/closet/crate/necropolis/colossus/crusher/PopulateContents()
- ..()
- new /obj/item/crusher_trophy/blaster_tubes(src)
//Hierophant
+
+/obj/structure/closet/crate/necropolis/hierophant
+ name = "hierophant chest"
+
+/obj/structure/closet/crate/necropolis/hierophant/PopulateContents()
+ new /obj/item/hierophant_club(src)
+ new /obj/effect/spawner/lootdrop/megafaunaore(src)
+
/obj/item/hierophant_club
name = "hierophant club"
desc = "The strange technology of this large club allows various nigh-magical feats. It used to beat you, but now you can set the beat."
@@ -1144,7 +1144,7 @@
inhand_y_dimension = 64
slot_flags = ITEM_SLOT_BACK
w_class = WEIGHT_CLASS_BULKY
- force = 15
+ force = 5 //Melee attacks also invoke a 15 burn damage AoE, for a total of 20 damage
attack_verb = list("clubbed", "beat", "pummeled")
hitsound = 'sound/weapons/sonic_jackhammer.ogg'
actions_types = list(/datum/action/item_action/vortex_recall, /datum/action/item_action/toggle_unfriendly_fire)
@@ -1171,7 +1171,7 @@
for(var/obj/item/I in user)
if(I != src)
user.dropItemToGround(I)
- for(var/turf/T in RANGE_TURFS(1, user))
+ for(var/turf/T as() in RANGE_TURFS(1, user))
var/obj/effect/temp_visual/hierophant/blast/B = new(T, user, TRUE)
B.damage = 0
user.dropItemToGround(src) //Drop us last, so it goes on top of their stuff
@@ -1191,12 +1191,12 @@
if(ismineralturf(target) && get_dist(user, target) < 6) //target is minerals, we can hit it(even if we can't see it)
INVOKE_ASYNC(src, .proc/cardinal_blasts, T, user)
timer = world.time + cooldown_time
- else if(target in view(5, get_turf(user))) //if the target is in view, hit it
+ else if(user in viewers(5, get_turf(target))) //if the target is in view, hit it
timer = world.time + cooldown_time
if(isliving(target) && chaser_timer <= world.time) //living and chasers off cooldown? fire one!
chaser_timer = world.time + chaser_cooldown
var/obj/effect/temp_visual/hierophant/chaser/C = new(get_turf(user), user, target, chaser_speed, friendly_fire_check)
- C.damage = 30
+ C.damage = 15
C.monster_damage_boost = FALSE
log_combat(user, target, "fired a chaser at", src)
else
@@ -1316,13 +1316,13 @@
user.log_message("teleported self from [AREACOORD(source)] to [beacon]", LOG_GAME)
new /obj/effect/temp_visual/hierophant/telegraph/teleport(T, user)
new /obj/effect/temp_visual/hierophant/telegraph/teleport(source, user)
- for(var/t in RANGE_TURFS(1, T))
+ for(var/turf/t as() in RANGE_TURFS(1, T))
var/obj/effect/temp_visual/hierophant/blast/B = new /obj/effect/temp_visual/hierophant/blast(t, user, TRUE) //blasts produced will not hurt allies
B.damage = 30
- for(var/t in RANGE_TURFS(1, source))
+ for(var/turf/t as() in RANGE_TURFS(1, source))
var/obj/effect/temp_visual/hierophant/blast/B = new /obj/effect/temp_visual/hierophant/blast(t, user, TRUE) //but absolutely will hurt enemies
B.damage = 30
- for(var/mob/living/L in range(1, source))
+ for(var/mob/living/L in hearers(1, source))
INVOKE_ASYNC(src, .proc/teleport_mob, source, L, T, user) //regardless, take all mobs near us along
sleep(6) //at this point the blasts detonate
if(beacon)
@@ -1350,7 +1350,7 @@
sleep(2)
if(!M)
return
- M.forceMove(turf_to_teleport_to)
+ do_teleport(M, turf_to_teleport_to, no_effects = TRUE, channel = TELEPORT_CHANNEL_MAGIC)
sleep(1)
if(!M)
return
@@ -1399,17 +1399,27 @@
var/obj/effect/temp_visual/hierophant/blast/B = new(t, user, friendly_fire_check)
B.damage = 15 //keeps monster damage boost due to lower damage
-
//Just some minor stuff
/obj/structure/closet/crate/necropolis/puzzle
name = "puzzling chest"
/obj/structure/closet/crate/necropolis/puzzle/PopulateContents()
- var/loot = rand(1,3)
+ var/loot = rand(1,5)
switch(loot)
if(1)
- new /obj/item/soulstone/anybody(src)
+ new /obj/item/disk/design_disk/modkit_disc/resonator_blast(src)
if(2)
- new /obj/item/wisp_lantern(src)
+ new /obj/item/disk/design_disk/modkit_disc/rapid_repeater(src)
if(3)
- new /obj/item/prisoncube(src)
+ new /obj/item/disk/design_disk/modkit_disc/mob_and_turf_aoe(src)
+ if(4)
+ new /obj/item/disk/design_disk/modkit_disc/bounty(src)
+ if(5)
+ new /obj/item/borg/upgrade/modkit/lifesteal(src)
+
+/obj/item/skeleton_key
+ name = "skeleton key"
+ desc = "An artifact usually found in the hands of the natives of lavaland, which NT now holds a monopoly on."
+ icon = 'icons/obj/lavaland/artefacts.dmi'
+ icon_state = "skeleton_key"
+ w_class = WEIGHT_CLASS_SMALL
diff --git a/code/modules/mining/lavaland/ruins/gym.dm b/code/modules/mining/lavaland/ruins/gym.dm
index d9cdf70ced234..d3a09764b8235 100644
--- a/code/modules/mining/lavaland/ruins/gym.dm
+++ b/code/modules/mining/lavaland/ruins/gym.dm
@@ -33,6 +33,9 @@
. = ..()
if(.)
return
+ if(!Adjacent(user))
+ to_chat(user, "You're too far away to get swole!")
+ return
if(obj_flags & IN_USE)
to_chat(user, "It's already in use - wait a bit.")
return
@@ -51,10 +54,10 @@
user.pixel_y = 0
var/finishmessage = pick("You feel stronger!","You feel like you can take on the world!","You feel robust!","You feel indestructible!")
-
+
if (user.client)
SSmedals.UnlockMedal(MEDAL_USE_WEIGHT_MACHINE,user.client)
-
+
SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "exercise", /datum/mood_event/exercise)
icon_state = initial(icon_state)
diff --git a/code/modules/mining/machine_bluespaceminer.dm b/code/modules/mining/machine_bluespaceminer.dm
deleted file mode 100644
index 226e66bdf9907..0000000000000
--- a/code/modules/mining/machine_bluespaceminer.dm
+++ /dev/null
@@ -1,40 +0,0 @@
-/obj/machinery/mineral/bluespace_miner
- name = "bluespace mining machine"
- desc = "A machine that uses the magic of Bluespace to slowly generate materials and add them to a linked ore silo."
- icon = 'icons/obj/machines/mining_machines.dmi'
- icon_state = "bs_miner"
- density = TRUE
- circuit = /obj/item/circuitboard/machine/bluespace_miner
- layer = BELOW_OBJ_LAYER
- var/list/ore_rates = list(/datum/material/iron = 0.6, /datum/material/glass = 0.6, /datum/material/copper = 0.4, /datum/material/plasma = 0.2, /datum/material/silver = 0.2, /datum/material/gold = 0.1, /datum/material/titanium = 0.1, /datum/material/uranium = 0.1, /datum/material/diamond = 0.1)
- var/datum/component/remote_materials/materials
-
-/obj/machinery/mineral/bluespace_miner/Initialize(mapload)
- . = ..()
- materials = AddComponent(/datum/component/remote_materials, "bsm", mapload)
-
-/obj/machinery/mineral/bluespace_miner/Destroy()
- materials = null
- return ..()
-
-/obj/machinery/mineral/bluespace_miner/multitool_act(mob/living/user, obj/item/multitool/M)
- if(istype(M))
- if(!M.buffer || !istype(M.buffer, /obj/machinery/ore_silo))
- to_chat(user, "You need to multitool the ore silo first.")
- return FALSE
-
-/obj/machinery/mineral/bluespace_miner/examine(mob/user)
- . = ..()
- if(!materials?.silo)
- . += "No ore silo connected. Use a multi-tool to link an ore silo to this machine."
- else if(materials?.on_hold())
- . += "Ore silo access is on hold, please contact the quartermaster."
-
-/obj/machinery/mineral/bluespace_miner/process()
- if(!materials?.silo || materials?.on_hold())
- return
- var/datum/component/material_container/mat_container = materials.mat_container
- if(!mat_container || panel_open || !powered())
- return
- var/datum/material/ore = pick(ore_rates)
- mat_container.insert_amount_mat((ore_rates[ore] * 1000), ore)
diff --git a/code/modules/mining/machine_processing.dm b/code/modules/mining/machine_processing.dm
index 47096a9a601c2..e40a8ad72e5c0 100644
--- a/code/modules/mining/machine_processing.dm
+++ b/code/modules/mining/machine_processing.dm
@@ -1,11 +1,50 @@
-#define SMELT_AMOUNT 10
+/// Smelt amount per second
+#define SMELT_AMOUNT 5
/**********************Mineral processing unit console**************************/
/obj/machinery/mineral
+ processing_flags = START_PROCESSING_MANUALLY
+ subsystem_type = /datum/controller/subsystem/processing/fastprocess
+ /// The current direction of `input_turf`, in relation to the machine.
var/input_dir = NORTH
+ /// The current direction, in relation to the machine, that items will be output to.
var/output_dir = SOUTH
+ /// The turf the machines listens to for items to pick up. Calls the `pickup_item()` proc.
+ var/turf/input_turf = null
+ /// Determines if this machine needs to pick up items. Used to avoid registering signals to `/mineral` machines that don't pickup items.
+ var/needs_item_input = FALSE
+/obj/machinery/mineral/Initialize(mapload)
+ . = ..()
+ if(needs_item_input)
+ register_input_turf()
+
+/// Gets the turf in the `input_dir` direction adjacent to the machine, and registers signals for ATOM_ENTERED and ATOM_CREATED. Calls the `pickup_item()` proc when it recieves these signals.
+/obj/machinery/mineral/proc/register_input_turf()
+ input_turf = get_step(src, input_dir)
+ if(input_turf) // make sure there is actually a turf
+ RegisterSignal(input_turf, list(COMSIG_ATOM_CREATED, COMSIG_ATOM_ENTERED), .proc/pickup_item)
+
+/// Unregisters signals that are registered the machine's input turf, if it has one.
+/obj/machinery/mineral/proc/unregister_input_turf()
+ if(input_turf)
+ UnregisterSignal(input_turf, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_CREATED))
+
+/**
+ Base proc for all `/mineral` subtype machines to use. Place your item pickup behavior in this proc when you override it for your specific machine.
+ Called when the COMSIG_ATOM_ENTERED and COMSIG_ATOM_CREATED signals are sent.
+ Arguments:
+ * source - the turf that is listening for the signals.
+ * target - the atom that just moved onto the `source` turf.
+ * oldLoc - the old location that `target` was at before moving onto `source`.
+*/
+/obj/machinery/mineral/proc/pickup_item(datum/source, atom/movable/target, atom/oldLoc)
+ SIGNAL_HANDLER
+
+ return
+
+/// Generic unloading proc. Takes an atom as an argument and forceMove's it to the turf adjacent to this machine in the `output_dir` direction.
/obj/machinery/mineral/proc/unload_mineral(atom/movable/S)
S.forceMove(drop_location())
var/turf/T = get_step(src,output_dir)
@@ -19,7 +58,6 @@
density = TRUE
var/obj/machinery/mineral/processing_unit/machine = null
var/machinedir = EAST
- speed_process = TRUE
/obj/machinery/mineral/processing_unit_console/Initialize()
. = ..()
@@ -58,6 +96,7 @@
if(href_list["set_on"])
machine.on = (href_list["set_on"] == "on")
+ machine.begin_processing()
updateUsrDialog()
return
@@ -75,6 +114,7 @@
icon = 'icons/obj/machines/mining_machines.dmi'
icon_state = "furnace"
density = TRUE
+ needs_item_input = TRUE
var/obj/machinery/mineral/CONSOLE = null
var/on = FALSE
var/datum/material/selected_material = null
@@ -93,11 +133,9 @@
QDEL_NULL(stored_research)
return ..()
-/obj/machinery/mineral/processing_unit/HasProximity(atom/movable/AM)
- if(istype(AM, /obj/item/stack/ore) && AM.loc == get_step(src, input_dir))
- process_ore(AM)
-
/obj/machinery/mineral/processing_unit/proc/process_ore(obj/item/stack/ore/O)
+ if(QDELETED(O))
+ return
var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
var/material_amount = materials.get_item_material_amount(O)
if(!materials.has_space(material_amount))
@@ -142,23 +180,31 @@
return dat
-/obj/machinery/mineral/processing_unit/process()
- if (on)
+/obj/machinery/mineral/processing_unit/pickup_item(datum/source, atom/movable/target, atom/oldLoc)
+ if(QDELETED(target))
+ return
+ if(istype(target, /obj/item/stack/ore))
+ process_ore(target)
+
+/obj/machinery/mineral/processing_unit/process(delta_time)
+ if(on)
if(selected_material)
- smelt_ore()
+ smelt_ore(delta_time)
else if(selected_alloy)
- smelt_alloy()
+ smelt_alloy(delta_time)
if(CONSOLE)
CONSOLE.updateUsrDialog()
+ else
+ end_processing()
-/obj/machinery/mineral/processing_unit/proc/smelt_ore()
+/obj/machinery/mineral/processing_unit/proc/smelt_ore(delta_time = 2)
var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
var/datum/material/mat = selected_material
if(mat)
- var/sheets_to_remove = (materials.materials[mat] >= (MINERAL_MATERIAL_AMOUNT * SMELT_AMOUNT) ) ? SMELT_AMOUNT : round(materials.materials[mat] / MINERAL_MATERIAL_AMOUNT)
+ var/sheets_to_remove = (materials.materials[mat] >= (MINERAL_MATERIAL_AMOUNT * SMELT_AMOUNT * delta_time) ) ? SMELT_AMOUNT * delta_time : round(materials.materials[mat] / MINERAL_MATERIAL_AMOUNT)
if(!sheets_to_remove)
on = FALSE
else
@@ -166,13 +212,13 @@
materials.retrieve_sheets(sheets_to_remove, mat, out)
-/obj/machinery/mineral/processing_unit/proc/smelt_alloy()
+/obj/machinery/mineral/processing_unit/proc/smelt_alloy(delta_time = 2)
var/datum/design/alloy = stored_research.isDesignResearchedID(selected_alloy) //check if it's a valid design
if(!alloy)
on = FALSE
return
- var/amount = can_smelt(alloy)
+ var/amount = can_smelt(alloy, delta_time)
if(!amount)
on = FALSE
@@ -183,11 +229,11 @@
generate_mineral(alloy.build_path)
-/obj/machinery/mineral/processing_unit/proc/can_smelt(datum/design/D)
+/obj/machinery/mineral/processing_unit/proc/can_smelt(datum/design/D, delta_time = 2)
if(D.make_reagents.len)
return FALSE
- var/build_amount = SMELT_AMOUNT
+ var/build_amount = SMELT_AMOUNT * delta_time
var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
diff --git a/code/modules/mining/machine_redemption.dm b/code/modules/mining/machine_redemption.dm
index 47e537f7d08ec..42d6e8649c722 100644
--- a/code/modules/mining/machine_redemption.dm
+++ b/code/modules/mining/machine_redemption.dm
@@ -10,20 +10,19 @@
input_dir = NORTH
output_dir = SOUTH
req_access = list(ACCESS_MINERAL_STOREROOM)
- speed_process = TRUE
circuit = /obj/item/circuitboard/machine/ore_redemption
-
+ needs_item_input = TRUE
+ processing_flags = START_PROCESSING_MANUALLY
layer = BELOW_OBJ_LAYER
var/obj/item/card/id/inserted_id
var/points = 0
- var/ore_pickup_rate = 15
var/sheet_per_ore = 1
var/point_upgrade = 1
var/list/ore_values = list(/datum/material/iron = 1, /datum/material/glass = 1, /datum/material/copper = 5, /datum/material/plasma = 15, /datum/material/silver = 16, /datum/material/gold = 18, /datum/material/titanium = 30, /datum/material/uranium = 30, /datum/material/diamond = 50, /datum/material/bluespace = 50, /datum/material/bananium = 60)
- var/message_sent = FALSE
- var/list/ore_buffer = list()
+ /// Variable that holds a timer which is used for callbacks to `send_console_message()`. Used for preventing multiple calls to this proc while the ORM is eating a stack of ores.
+ var/console_notify_timer
var/datum/techweb/stored_research
var/obj/item/disk/design_disk/inserted_disk
var/datum/component/remote_materials/materials
@@ -39,39 +38,34 @@
return ..()
/obj/machinery/mineral/ore_redemption/RefreshParts()
- var/ore_pickup_rate_temp = 15
var/point_upgrade_temp = 1
var/sheet_per_ore_temp = 1
for(var/obj/item/stock_parts/matter_bin/B in component_parts)
sheet_per_ore_temp = 0.65 + (0.35 * B.rating)
- for(var/obj/item/stock_parts/manipulator/M in component_parts)
- ore_pickup_rate_temp = 15 * M.rating
for(var/obj/item/stock_parts/micro_laser/L in component_parts)
point_upgrade_temp = 0.65 + (0.35 * L.rating)
- ore_pickup_rate = ore_pickup_rate_temp
point_upgrade = point_upgrade_temp
sheet_per_ore = round(sheet_per_ore_temp, 0.01)
/obj/machinery/mineral/ore_redemption/examine(mob/user)
. = ..()
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Smelting [sheet_per_ore] sheet(s) per piece of ore. Reward point generation at [point_upgrade*100]%. Ore pickup speed at [ore_pickup_rate]."
+ . += "The status display reads: Smelting [sheet_per_ore] sheet(s) per piece of ore. Reward point generation at [point_upgrade*100]%."
if(panel_open)
. += "Alt-click to rotate the input and output direction."
/obj/machinery/mineral/ore_redemption/proc/smelt_ore(obj/item/stack/ore/O)
+ if(QDELETED(O))
+ return
var/datum/component/material_container/mat_container = materials.mat_container
if (!mat_container)
return
+ console_notify_timer = null
+
if(O.refined_type == null)
return
- ore_buffer -= O
-
- if(O?.refined_type)
- points += O.points * point_upgrade * O.amount
-
var/material_amount = mat_container.get_item_material_amount(O)
if(!material_amount)
@@ -81,6 +75,8 @@
unload_mineral(O)
else
+ if(O?.refined_type)
+ points += O.points * point_upgrade * O.amount
var/mats = O.materials & mat_container.materials
var/amount = O.amount
mat_container.insert_item(O, sheet_per_ore) //insert it
@@ -114,17 +110,15 @@
return build_amount
/obj/machinery/mineral/ore_redemption/proc/process_ores(list/ores_to_process)
- var/current_amount = 0
for(var/ore in ores_to_process)
- if(current_amount >= ore_pickup_rate)
- break
smelt_ore(ore)
/obj/machinery/mineral/ore_redemption/proc/send_console_message()
var/datum/component/material_container/mat_container = materials.mat_container
if(!mat_container || !is_station_level(z))
return
- message_sent = TRUE
+
+ console_notify_timer = null
var/area/A = get_area(src)
var/msg = "Now available in [A]: "
@@ -150,26 +144,34 @@
))
signal.send_to_receivers()
-/obj/machinery/mineral/ore_redemption/process()
+/obj/machinery/mineral/ore_redemption/pickup_item(datum/source, atom/movable/target, atom/oldLoc)
+ if(QDELETED(target))
+ return
if(!materials.mat_container || panel_open || !powered())
return
- var/atom/input = get_step(src, input_dir)
- var/obj/structure/ore_box/OB = locate() in input
- if(OB)
- input = OB
-
- for(var/obj/item/stack/ore/O in input)
- if(QDELETED(O))
- continue
- ore_buffer |= O
- O.forceMove(src)
- CHECK_TICK
-
- if(LAZYLEN(ore_buffer))
- message_sent = FALSE
- process_ores(ore_buffer)
- else if(!message_sent)
- send_console_message()
+
+ if(istype(target, /obj/structure/ore_box))
+ var/obj/structure/ore_box/box = target
+ process_ores(box.contents)
+ box.ui_update()
+ else if(istype(target, /obj/item/stack/ore))
+ var/obj/item/stack/ore/O = target
+ smelt_ore(O)
+ else
+ return
+
+ if(!console_notify_timer)
+ // gives 5 seconds for a load of ores to be sucked up by the ORM before it sends out request console notifications. This should be enough time for most deposits that people make
+ console_notify_timer = addtimer(CALLBACK(src, .proc/send_console_message), 5 SECONDS)
+
+/obj/machinery/mineral/ore_redemption/default_unfasten_wrench(mob/user, obj/item/I)
+ . = ..()
+ if(!.)
+ return
+ if(anchored)
+ register_input_turf() // someone just wrenched us down, re-register the turf
+ else
+ unregister_input_turf() // someone just un-wrenched us, unregister the turf
/obj/machinery/mineral/ore_redemption/attackby(obj/item/W, mob/user, params)
if(default_unfasten_wrench(user, W))
@@ -197,13 +199,14 @@
return ..()
/obj/machinery/mineral/ore_redemption/AltClick(mob/living/user)
- ..()
if(!user.canUseTopic(src, BE_CLOSE))
return
- if (panel_open)
+ if(panel_open)
input_dir = turn(input_dir, -90)
output_dir = turn(output_dir, -90)
to_chat(user, "You change [src]'s I/O settings, setting the input to [dir2text(input_dir)] and the output to [dir2text(output_dir)].")
+ unregister_input_turf() // someone just rotated the input and output directions, unregister the old turf
+ register_input_turf() // register the new one
return TRUE
@@ -215,6 +218,7 @@
if(!ui)
ui = new(user, src, "OreRedemptionMachine")
ui.open()
+ ui.set_autoupdate(TRUE) // Material amounts
/obj/machinery/mineral/ore_redemption/ui_data(mob/user)
var/list/data = list()
@@ -234,6 +238,8 @@
for(var/v in stored_research.researched_designs)
var/datum/design/D = SSresearch.techweb_design_by_id(v)
data["alloys"] += list(list("name" = D.name, "id" = D.id, "amount" = can_smelt_alloy(D)))
+ else
+ data["alloys"] = null // In case we lose mat_container while UI is open somehow
if (!mat_container)
data["disconnected"] = "local mineral storage is unavailable"
@@ -241,6 +247,8 @@
data["disconnected"] = "no ore silo connection is available; storing locally"
else if (materials.on_hold())
data["disconnected"] = "mineral withdrawal is on hold"
+ else
+ data["disconnected"] = null
data["diskDesigns"] = list()
data["hasDisk"] = FALSE
@@ -265,11 +273,11 @@
if(points)
if(I?.mining_points += points)
points = 0
+ . = TRUE
else
to_chat(usr, "No ID detected.")
else
to_chat(usr, "No points to claim.")
- return TRUE
if("Release")
if(!mat_container)
return
@@ -303,26 +311,26 @@
mats[mat] = MINERAL_MATERIAL_AMOUNT
materials.silo_log(src, "released", -count, "sheets", mats)
//Logging deleted for quick coding
- return TRUE
+ . = TRUE
if("diskInsert")
var/obj/item/disk/design_disk/disk = usr.get_active_held_item()
if(istype(disk))
if(!usr.transferItemToLoc(disk,src))
return
inserted_disk = disk
+ . = TRUE
else
to_chat(usr, "Not a valid Design Disk!")
- return TRUE
if("diskEject")
if(inserted_disk)
usr.put_in_hands(inserted_disk)
inserted_disk = null
- return TRUE
+ . = TRUE
if("diskUpload")
var/n = text2num(params["design"])
if(inserted_disk && inserted_disk.blueprints && inserted_disk.blueprints[n])
stored_research.add_design(inserted_disk.blueprints[n])
- return TRUE
+ . = TRUE
if("Smelt")
if(!mat_container)
return
@@ -347,9 +355,9 @@
else
output = new alloy.build_path(src)
unload_mineral(output)
+ . = TRUE
else
to_chat(usr, "Required access not found.")
- return TRUE
/obj/machinery/mineral/ore_redemption/ex_act(severity, target)
do_sparks(5, TRUE, src)
diff --git a/code/modules/mining/machine_silo.dm b/code/modules/mining/machine_silo.dm
index 61cb675cf057f..45cb07e25dad9 100644
--- a/code/modules/mining/machine_silo.dm
+++ b/code/modules/mining/machine_silo.dm
@@ -15,14 +15,22 @@ GLOBAL_LIST_EMPTY(silo_access_logs)
/obj/machinery/ore_silo/Initialize(mapload)
. = ..()
- AddComponent(/datum/component/material_container,
- list(/datum/material/iron, /datum/material/glass, /datum/material/copper, /datum/material/silver, /datum/material/gold, /datum/material/diamond, /datum/material/plasma, /datum/material/uranium, /datum/material/bananium, /datum/material/titanium, /datum/material/bluespace, /datum/material/plastic),
- INFINITY,
- FALSE,
- /obj/item/stack,
- null,
- null,
- TRUE)
+ var/static/list/materials_list = list(
+ /datum/material/iron,
+ /datum/material/glass,
+ /datum/material/copper,
+ /datum/material/silver,
+ /datum/material/gold,
+ /datum/material/diamond,
+ /datum/material/plasma,
+ /datum/material/uranium,
+ /datum/material/bananium,
+ /datum/material/titanium,
+ /datum/material/bluespace,
+ /datum/material/plastic,
+ )
+ AddComponent(/datum/component/material_container, materials_list, INFINITY, allowed_types=/obj/item/stack, _disable_attackby=TRUE)
+
if (!GLOB.ore_silo_default && mapload && is_station_level(z))
GLOB.ore_silo_default = src
@@ -43,6 +51,7 @@ GLOBAL_LIST_EMPTY(silo_access_logs)
/obj/machinery/ore_silo/proc/remote_attackby(obj/machinery/M, mob/user, obj/item/stack/I)
var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
+
// stolen from /datum/component/material_container/proc/OnAttackBy
if(user.a_intent != INTENT_HELP)
return
@@ -62,8 +71,19 @@ GLOBAL_LIST_EMPTY(silo_access_logs)
return TRUE
/obj/machinery/ore_silo/attackby(obj/item/W, mob/user, params)
- if (istype(W, /obj/item/stack))
+ if(default_deconstruction_screwdriver(user, icon_state, icon_state, W))
+ updateUsrDialog()
+ return
+
+ if(default_deconstruction_crowbar(W))
+ return
+
+ if(!powered())
+ return ..()
+
+ if(istype(W, /obj/item/stack))
return remote_attackby(src, user, W)
+
return ..()
/obj/machinery/ore_silo/ui_interact(mob/user)
diff --git a/code/modules/mining/machine_stacking.dm b/code/modules/mining/machine_stacking.dm
index 35ca8b8e2a8ee..b6575073bfa5b 100644
--- a/code/modules/mining/machine_stacking.dm
+++ b/code/modules/mining/machine_stacking.dm
@@ -14,7 +14,13 @@
. = ..()
machine = locate(/obj/machinery/mineral/stacking_machine, get_step(src, machinedir))
if (machine)
- machine.CONSOLE = src
+ machine.console = src
+
+/obj/machinery/mineral/stacking_unit_console/Destroy()
+ if(machine)
+ machine.console = null
+ machine = null
+ return ..()
/obj/machinery/mineral/stacking_unit_console/ui_interact(mob/user)
. = ..()
@@ -74,7 +80,7 @@
circuit = /obj/item/circuitboard/machine/stacking_machine
input_dir = EAST
output_dir = WEST
- var/obj/machinery/mineral/stacking_unit_console/CONSOLE
+ var/obj/machinery/mineral/stacking_unit_console/console
var/stk_types = list()
var/stk_amt = list()
var/stack_list[0] //Key: Type. Value: Instance of type.
@@ -88,23 +94,38 @@
materials = AddComponent(/datum/component/remote_materials, "stacking", mapload, FALSE, mapload && force_connect)
/obj/machinery/mineral/stacking_machine/Destroy()
- CONSOLE = null
+ if(console)
+ console.machine = null
+ console = null
materials = null
return ..()
/obj/machinery/mineral/stacking_machine/HasProximity(atom/movable/AM)
+ if(QDELETED(AM))
+ return
if(istype(AM, /obj/item/stack/sheet) && AM.loc == get_step(src, input_dir))
- process_sheet(AM)
+ var/obj/effect/portal/P = locate() in AM.loc
+ if(P)
+ visible_message("[src] attempts to stack the portal!")
+ message_admins("Stacking machine exploded via [P.creator ? key_name(P.creator) : "UNKNOWN"]'s portal at [AREACOORD(src)]")
+ log_game("Stacking machine exploded via [P.creator ? key_name(P.creator) : "UNKNOWN"]'s portal at [AREACOORD(src)]")
+ explosion(src.loc, 0, 1, 2, 3)
+ if(!QDELETED(src))
+ qdel(src)
+ else
+ process_sheet(AM)
/obj/machinery/mineral/stacking_machine/multitool_act(mob/living/user, obj/item/multitool/M)
if(istype(M))
if(istype(M.buffer, /obj/machinery/mineral/stacking_unit_console))
- CONSOLE = M.buffer
- CONSOLE.machine = src
+ console = M.buffer
+ console.machine = src
to_chat(user, "You link [src] to the console in [M]'s buffer.")
return TRUE
/obj/machinery/mineral/stacking_machine/proc/process_sheet(obj/item/stack/sheet/inp)
+ if(QDELETED(inp))
+ return
var/key = inp.merge_type
var/obj/item/stack/sheet/storage = stack_list[key]
if(!storage) //It's the first of this sheet added
diff --git a/code/modules/mining/machine_unloading.dm b/code/modules/mining/machine_unloading.dm
index cea90bfce029c..b8f3ed70fa647 100644
--- a/code/modules/mining/machine_unloading.dm
+++ b/code/modules/mining/machine_unloading.dm
@@ -8,24 +8,17 @@
density = TRUE
input_dir = WEST
output_dir = EAST
- speed_process = TRUE
+ needs_item_input = TRUE
+ processing_flags = START_PROCESSING_MANUALLY
-/obj/machinery/mineral/unloading_machine/process()
- var/turf/T = get_step(src,input_dir)
- if(T)
- var/limit
- for(var/obj/structure/ore_box/B in T)
- for (var/obj/item/stack/ore/O in B)
- B.contents -= O
- unload_mineral(O)
- limit++
- if (limit>=10)
- return
- CHECK_TICK
- CHECK_TICK
- for(var/obj/item/I in T)
- unload_mineral(I)
- limit++
- if (limit>=10)
- return
- CHECK_TICK
\ No newline at end of file
+/obj/machinery/mineral/unloading_machine/pickup_item(datum/source, atom/movable/target, atom/oldLoc)
+ if(QDELETED(target))
+ return
+ if(istype(target, /obj/structure/ore_box))
+ var/obj/structure/ore_box/box = target
+ for(var/obj/item/stack/ore/O in box)
+ unload_mineral(O)
+ box.ui_update()
+ else if(istype(target, /obj/item/stack/ore))
+ var/obj/item/stack/ore/O = target
+ unload_mineral(O)
diff --git a/code/modules/mining/machine_vending.dm b/code/modules/mining/machine_vending.dm
index 070cfdf14124c..4d9fe0863b8f7 100644
--- a/code/modules/mining/machine_vending.dm
+++ b/code/modules/mining/machine_vending.dm
@@ -1,117 +1,53 @@
/**********************Mining Equipment Vendor**************************/
-/obj/machinery/mineral/equipment_vendor
- name = "mining equipment vendor"
- desc = "An equipment vendor for miners, points collected at an ore redemption machine can be spent here."
- icon = 'icons/obj/machines/mining_machines.dmi'
- icon_state = "mining"
+/obj/machinery/vendor
+ name = "equipment vendor"
+ processing_flags = START_PROCESSING_MANUALLY
+ subsystem_type = /datum/controller/subsystem/processing/fastprocess
density = TRUE
- circuit = /obj/item/circuitboard/machine/mining_equipment_vendor
-
- var/icon_deny = "mining-deny"
+ var/icon_deny
var/obj/item/card/id/inserted_id
- var/list/prize_list = list( //if you add something to this, please, for the love of god, sort it by price/type. use tabs and not spaces.
- new /datum/data/mining_equipment("1 Marker Beacon", /obj/item/stack/marker_beacon, 10),
- new /datum/data/mining_equipment("10 Marker Beacons", /obj/item/stack/marker_beacon/ten, 100),
- new /datum/data/mining_equipment("30 Marker Beacons", /obj/item/stack/marker_beacon/thirty, 300),
- new /datum/data/mining_equipment("Whiskey", /obj/item/reagent_containers/food/drinks/bottle/whiskey, 100),
- new /datum/data/mining_equipment("Absinthe", /obj/item/reagent_containers/food/drinks/bottle/absinthe/premium, 100),
- new /datum/data/mining_equipment("Cigar", /obj/item/clothing/mask/cigarette/cigar/havana, 150),
- new /datum/data/mining_equipment("Soap", /obj/item/soap/nanotrasen, 200),
- new /datum/data/mining_equipment("Laser Pointer", /obj/item/laser_pointer, 300),
- new /datum/data/mining_equipment("Alien Toy", /obj/item/clothing/mask/facehugger/toy, 300),
- new /datum/data/mining_equipment("Stabilizing Serum", /obj/item/hivelordstabilizer, 400),
- new /datum/data/mining_equipment("Fulton Beacon", /obj/item/fulton_core, 400),
- new /datum/data/mining_equipment("Shelter Capsule", /obj/item/survivalcapsule, 400),
- new /datum/data/mining_equipment("GAR Meson Scanners", /obj/item/clothing/glasses/meson/gar, 500),
- new /datum/data/mining_equipment("Explorer's Webbing", /obj/item/storage/belt/mining, 500),
- new /datum/data/mining_equipment("Point Transfer Card", /obj/item/card/mining_point_card, 500),
- new /datum/data/mining_equipment("Survival Medipen", /obj/item/reagent_containers/hypospray/medipen/survival, 500),
- new /datum/data/mining_equipment("Brute First-Aid Kit", /obj/item/storage/firstaid/brute, 600),
- new /datum/data/mining_equipment("Tracking Implant Kit", /obj/item/storage/box/minertracker, 600),
- new /datum/data/mining_equipment("Jaunter", /obj/item/wormhole_jaunter, 750),
- new /datum/data/mining_equipment("Kinetic Crusher", /obj/item/twohanded/kinetic_crusher, 750),
- new /datum/data/mining_equipment("Kinetic Accelerator", /obj/item/gun/energy/kinetic_accelerator, 750),
- new /datum/data/mining_equipment("Advanced Scanner", /obj/item/t_scanner/adv_mining_scanner, 800),
- new /datum/data/mining_equipment("Resonator", /obj/item/resonator, 800),
- new /datum/data/mining_equipment("Fulton Pack", /obj/item/extraction_pack, 1000),
- new /datum/data/mining_equipment("Lazarus Injector", /obj/item/lazarus_injector, 1000),
- new /datum/data/mining_equipment("Silver Pickaxe", /obj/item/pickaxe/silver, 1000),
- new /datum/data/mining_equipment("Mining Conscription Kit", /obj/item/storage/backpack/duffelbag/mining_conscript, 1000),
- new /datum/data/mining_equipment("Jetpack Upgrade", /obj/item/tank/jetpack/suit, 2000),
- new /datum/data/mining_equipment("Space Cash", /obj/item/stack/spacecash/c1000, 2000),
- new /datum/data/mining_equipment("Mining Hardsuit", /obj/item/clothing/suit/space/hardsuit/mining, 2000),
- new /datum/data/mining_equipment("Diamond Pickaxe", /obj/item/pickaxe/diamond, 2000),
- new /datum/data/mining_equipment("Super Resonator", /obj/item/resonator/upgraded, 2500),
- new /datum/data/mining_equipment("Jump Boots", /obj/item/clothing/shoes/bhop, 2500),
- new /datum/data/mining_equipment("Luxury Shelter Capsule", /obj/item/survivalcapsule/luxury, 3000),
- new /datum/data/mining_equipment("Luxury Bar Capsule", /obj/item/survivalcapsule/luxuryelite, 10000),
- new /datum/data/mining_equipment("Mining Encampment Capsule", /obj/item/survivalcapsule/encampment, 5000),
- new /datum/data/mining_equipment("Nanotrasen Minebot", /mob/living/simple_animal/hostile/mining_drone, 800),
- new /datum/data/mining_equipment("Minebot Melee Upgrade", /obj/item/mine_bot_upgrade, 400),
- new /datum/data/mining_equipment("Minebot Armor Upgrade", /obj/item/mine_bot_upgrade/health, 400),
- new /datum/data/mining_equipment("Minebot Cooldown Upgrade", /obj/item/borg/upgrade/modkit/cooldown/minebot, 600),
- new /datum/data/mining_equipment("Minebot AI Upgrade", /obj/item/slimepotion/slime/sentience/mining, 1000),
- new /datum/data/mining_equipment("KA Minebot Passthrough", /obj/item/borg/upgrade/modkit/minebot_passthrough, 100),
- new /datum/data/mining_equipment("KA White Tracer Rounds", /obj/item/borg/upgrade/modkit/tracer, 100),
- new /datum/data/mining_equipment("KA Adjustable Tracer Rounds", /obj/item/borg/upgrade/modkit/tracer/adjustable, 150),
- new /datum/data/mining_equipment("KA Super Chassis", /obj/item/borg/upgrade/modkit/chassis_mod, 250),
- new /datum/data/mining_equipment("KA Hyper Chassis", /obj/item/borg/upgrade/modkit/chassis_mod/orange, 300),
- new /datum/data/mining_equipment("KA Range Increase", /obj/item/borg/upgrade/modkit/range, 1000),
- new /datum/data/mining_equipment("KA Damage Increase", /obj/item/borg/upgrade/modkit/damage, 1000),
- new /datum/data/mining_equipment("KA Cooldown Decrease", /obj/item/borg/upgrade/modkit/cooldown, 1000),
- new /datum/data/mining_equipment("KA AoE Damage", /obj/item/borg/upgrade/modkit/aoe/mobs, 2000)
- )
-
-/datum/data/mining_equipment
- var/equipment_name = "generic"
- var/equipment_path = null
- var/cost = 0
-
-/datum/data/mining_equipment/New(name, path, cost)
- src.equipment_name = name
- src.equipment_path = path
- src.cost = cost
+ var/list/prize_list = list()
-/obj/machinery/mineral/equipment_vendor/Initialize()
+/obj/machinery/vendor/Initialize()
. = ..()
build_inventory()
-/obj/machinery/mineral/equipment_vendor/proc/build_inventory()
+/obj/machinery/vendor/proc/build_inventory()
for(var/p in prize_list)
- var/datum/data/mining_equipment/M = p
+ var/datum/data/vendor_equipment/M = p
GLOB.vending_products[M.equipment_path] = 1
-/obj/machinery/mineral/equipment_vendor/power_change()
+/obj/machinery/vendor/power_change()
..()
update_icon()
-/obj/machinery/mineral/equipment_vendor/update_icon()
+/obj/machinery/vendor/update_icon()
if(powered())
icon_state = initial(icon_state)
else
icon_state = "[initial(icon_state)]-off"
-/obj/machinery/mineral/equipment_vendor/ui_assets(mob/user)
+/obj/machinery/vendor/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/spritesheet/vending),
)
-/obj/machinery/mineral/equipment_vendor/ui_state(mob/user)
+/obj/machinery/vendor/ui_state(mob/user)
return GLOB.default_state
-/obj/machinery/mineral/equipment_vendor/ui_interact(mob/user, datum/tgui/ui)
+/obj/machinery/vendor/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "MiningVendor")
ui.open()
-/obj/machinery/mineral/equipment_vendor/ui_static_data(mob/user)
+/obj/machinery/vendor/ui_static_data(mob/user)
. = list()
.["product_records"] = list()
- for(var/datum/data/mining_equipment/prize in prize_list)
+ for(var/datum/data/vendor_equipment/prize in prize_list)
var/list/product_data = list(
path = replacetext(replacetext("[prize.equipment_path]", "/obj/item/", ""), "/", "-"),
name = prize.equipment_name,
@@ -120,16 +56,17 @@
)
.["product_records"] += list(product_data)
-/obj/machinery/mineral/equipment_vendor/ui_data(mob/user)
+/obj/machinery/vendor/ui_data(mob/user)
. = list()
var/mob/living/carbon/human/H
var/obj/item/card/id/C
+ .["user"] = null
if(ishuman(user))
H = user
C = H.get_idcard(TRUE)
if(C)
.["user"] = list()
- .["user"]["points"] = C.mining_points
+ .["user"]["points"] = get_points(C)
if(C.registered_account)
.["user"]["name"] = C.registered_account.account_holder
if(C.registered_account.account_job)
@@ -137,7 +74,7 @@
else
.["user"]["job"] = "No Job"
-/obj/machinery/mineral/equipment_vendor/ui_act(action, params)
+/obj/machinery/vendor/ui_act(action, params)
if(..())
return
@@ -149,32 +86,123 @@
to_chat(usr, "Error: An ID is required!")
flick(icon_deny, src)
return
- var/datum/data/mining_equipment/prize = locate(params["ref"]) in prize_list
+ var/datum/data/vendor_equipment/prize = locate(params["ref"]) in prize_list
if(!prize || !(prize in prize_list))
to_chat(usr, "Error: Invalid choice!")
flick(icon_deny, src)
return
- if(prize.cost > I.mining_points)
+ if(prize.cost > get_points(I))
to_chat(usr, "Error: Insufficient points for [prize.equipment_name] on [I]!")
flick(icon_deny, src)
return
- I.mining_points -= prize.cost
+ subtract_points(I, prize.cost)
to_chat(usr, "[src] clanks to life briefly before vending [prize.equipment_name]!")
new prize.equipment_path(loc)
SSblackbox.record_feedback("nested tally", "mining_equipment_bought", 1, list("[type]", "[prize.equipment_path]"))
. = TRUE
-/obj/machinery/mineral/equipment_vendor/attackby(obj/item/I, mob/user, params)
- if(istype(I, /obj/item/mining_voucher))
- RedeemVoucher(I, user)
- return
+/obj/machinery/vendor/attackby(obj/item/I, mob/user, params)
if(default_deconstruction_screwdriver(user, "mining-open", "mining", I))
return
if(default_deconstruction_crowbar(I))
return
return ..()
-/obj/machinery/mineral/equipment_vendor/proc/RedeemVoucher(obj/item/mining_voucher/voucher, mob/redeemer)
+/obj/machinery/vendor/ex_act(severity, target)
+ do_sparks(5, TRUE, src)
+ if(prob(50 / severity) && severity < 3)
+ qdel(src)
+
+/obj/machinery/vendor/proc/subtract_points(obj/item/card/id/I, amount)
+ I.mining_points -= amount
+
+/obj/machinery/vendor/proc/get_points(obj/item/card/id/I)
+ return I.mining_points
+
+/obj/machinery/vendor/mining
+ name = "mining equipment vendor"
+ desc = "An equipment vendor for miners, points collected at an ore redemption machine can be spent here."
+ icon = 'icons/obj/machines/mining_machines.dmi'
+ icon_state = "mining"
+ density = TRUE
+ circuit = /obj/item/circuitboard/machine/mining_equipment_vendor
+
+ icon_deny = "mining-deny"
+ prize_list = list( //if you add something to this, please, for the love of god, sort it by price/type. use tabs and not spaces.
+ new /datum/data/vendor_equipment("1 Marker Beacon", /obj/item/stack/marker_beacon, 5),
+ new /datum/data/vendor_equipment("10 Marker Beacons", /obj/item/stack/marker_beacon/ten, 75),
+ new /datum/data/vendor_equipment("30 Marker Beacons", /obj/item/stack/marker_beacon/thirty, 150),
+ new /datum/data/vendor_equipment("Shelter Capsule", /obj/item/survivalcapsule, 400),
+ new /datum/data/vendor_equipment("Regen. Core Stabilizer", /obj/item/hivelordstabilizer, 400),
+ new /datum/data/vendor_equipment("Skeleton Key", /obj/item/skeleton_key, 750),
+ new /datum/data/vendor_equipment("Survival Medipen", /obj/item/reagent_containers/hypospray/medipen/survival, 500),
+ new /datum/data/vendor_equipment("Brute Healing Kit", /obj/item/storage/firstaid/brute, 600),
+ new /datum/data/vendor_equipment("Burn Healing Kit", /obj/item/storage/firstaid/fire, 600),
+ new /datum/data/vendor_equipment("Advanced Healing Kit", /obj/item/storage/firstaid/advanced, 1200),
+ new /datum/data/vendor_equipment("Fulton Beacon", /obj/item/fulton_core, 500),
+ new /datum/data/vendor_equipment("Fulton Extraction Pack", /obj/item/extraction_pack, 1000),
+ new /datum/data/vendor_equipment("Jaunter", /obj/item/wormhole_jaunter, 750),
+ new /datum/data/vendor_equipment("Advanced Ore Scanner", /obj/item/t_scanner/adv_mining_scanner, 800),
+ new /datum/data/vendor_equipment("Explorer's Webbing", /obj/item/storage/belt/mining, 500),
+ new /datum/data/vendor_equipment("Jump Boots", /obj/item/clothing/shoes/bhop, 2000),
+ new /datum/data/vendor_equipment("Proto-Kinetic Crusher", /obj/item/kinetic_crusher, 800),
+ new /datum/data/vendor_equipment("Proto-Kinetic Accelerator", /obj/item/gun/energy/kinetic_accelerator, 500),
+ new /datum/data/vendor_equipment("Resonator", /obj/item/resonator, 750),
+ new /datum/data/vendor_equipment("Upgraded Resonator", /obj/item/resonator/upgraded, 1500),
+ new /datum/data/vendor_equipment("Silver Pickaxe", /obj/item/pickaxe/silver, 500),
+ new /datum/data/vendor_equipment("Diamond Pickaxe", /obj/item/pickaxe/diamond, 1000),
+ new /datum/data/vendor_equipment("Mining Bot Companion", /mob/living/simple_animal/hostile/mining_drone, 800),
+ new /datum/data/vendor_equipment("Minebot Upgrade: Melee", /obj/item/mine_bot_upgrade, 400),
+ new /datum/data/vendor_equipment("Minebot Upgrade: Armor", /obj/item/mine_bot_upgrade/health, 400),
+ new /datum/data/vendor_equipment("Minebot Upgrade: Cooldown", /obj/item/borg/upgrade/modkit/cooldown/minebot, 600),
+ new /datum/data/vendor_equipment("Minebot Upgrade: A.I.", /obj/item/slimepotion/slime/sentience/mining, 1000),
+ new /datum/data/vendor_equipment("P-KA Upgrade: Bot-Friendly", /obj/item/borg/upgrade/modkit/minebot_passthrough, 100),
+ new /datum/data/vendor_equipment("P-KA Upgrade: Tracer Shots", /obj/item/borg/upgrade/modkit/tracer, 200),
+ new /datum/data/vendor_equipment("P-KA Upgrade: Adj. T. Shots", /obj/item/borg/upgrade/modkit/tracer/adjustable, 300),
+ new /datum/data/vendor_equipment("P-KA Upgrade: Range", /obj/item/borg/upgrade/modkit/range, 1000),
+ new /datum/data/vendor_equipment("P-KA Upgrade: Damage", /obj/item/borg/upgrade/modkit/damage, 1000),
+ new /datum/data/vendor_equipment("P-KA Upgrade: Cooldown", /obj/item/borg/upgrade/modkit/cooldown, 1000),
+ new /datum/data/vendor_equipment("P-KA Upgrade: Damage Radius", /obj/item/borg/upgrade/modkit/aoe/mobs, 2000),
+ new /datum/data/vendor_equipment("P-KA Cosmetic Super Chassis", /obj/item/borg/upgrade/modkit/chassis_mod, 200),
+ new /datum/data/vendor_equipment("P-KA Cosmetic Hyper Chassis", /obj/item/borg/upgrade/modkit/chassis_mod/orange, 200),
+ new /datum/data/vendor_equipment("Mining Hardsuit", /obj/item/clothing/suit/space/hardsuit/mining, 2000),
+ new /datum/data/vendor_equipment("Expanded E. Oxygen Tank", /obj/item/tank/internals/emergency_oxygen/engi, 1000),
+ new /datum/data/vendor_equipment("Mining Conscription Kit", /obj/item/storage/backpack/duffelbag/mining_conscript, 1000),
+ new /datum/data/vendor_equipment("Survival Knife", /obj/item/kitchen/knife/combat/survival, 1000),
+ new /datum/data/vendor_equipment("Tracking Implant Kit", /obj/item/storage/box/minertracker, 1000),
+ new /datum/data/vendor_equipment("Point Transfer Card", /obj/item/card/mining_point_card, 500),
+ new /datum/data/vendor_equipment("GAR Mesons", /obj/item/clothing/glasses/meson/gar, 500),
+ new /datum/data/vendor_equipment("Luxury Shelter Capsule", /obj/item/survivalcapsule/luxury, 3000),
+ new /datum/data/vendor_equipment("Mining Outpost Capsule", /obj/item/survivalcapsule/encampment, 5000),
+ new /datum/data/vendor_equipment("Luxury Bar Capsule", /obj/item/survivalcapsule/luxuryelite, 10000),
+ new /datum/data/vendor_equipment("Lazarus Injector", /obj/item/lazarus_injector, 1000),
+ new /datum/data/vendor_equipment("1000 Space Cash", /obj/item/stack/spacecash/c1000, 2000),
+ new /datum/data/vendor_equipment("Pizza", /obj/item/pizzabox/margherita, 200),
+ new /datum/data/vendor_equipment("Whiskey", /obj/item/reagent_containers/food/drinks/bottle/whiskey, 100),
+ new /datum/data/vendor_equipment("Absinthe", /obj/item/reagent_containers/food/drinks/bottle/absinthe/premium, 100),
+ new /datum/data/vendor_equipment("Cigar", /obj/item/clothing/mask/cigarette/cigar/havana, 150),
+ new /datum/data/vendor_equipment("Soap", /obj/item/soap/nanotrasen, 200),
+ new /datum/data/vendor_equipment("Laser Pointer", /obj/item/laser_pointer, 300),
+ new /datum/data/vendor_equipment("Toy Alien", /obj/item/clothing/mask/facehugger/toy, 300),
+ )
+
+/datum/data/vendor_equipment
+ var/equipment_name = "generic"
+ var/equipment_path = null
+ var/cost = 0
+
+/datum/data/vendor_equipment/New(name, path, cost)
+ src.equipment_name = name
+ src.equipment_path = path
+ src.cost = cost
+
+/obj/machinery/vendor/mining/attackby(obj/item/I, mob/user, params)
+ if(istype(I, /obj/item/mining_voucher))
+ RedeemVoucher(I, user)
+ return
+ return ..()
+
+/obj/machinery/vendor/mining/proc/RedeemVoucher(obj/item/mining_voucher/voucher, mob/redeemer)
var/items = list("Survival Capsule and Explorer's Webbing", "Resonator Kit", "Minebot Kit", "Extraction and Rescue Kit", "Crusher Kit", "Mining Conscription Kit")
var/selection = input(redeemer, "Pick your equipment", "Mining Voucher Redemption") as null|anything in sortList(items)
@@ -198,36 +226,31 @@
new /obj/item/stack/marker_beacon/thirty(drop_location)
if("Crusher Kit")
new /obj/item/extinguisher/mini(drop_location)
- new /obj/item/twohanded/kinetic_crusher(drop_location)
+ new /obj/item/kinetic_crusher(drop_location)
if("Mining Conscription Kit")
new /obj/item/storage/backpack/duffelbag/mining_conscript(drop_location)
SSblackbox.record_feedback("tally", "mining_voucher_redeemed", 1, selection)
qdel(voucher)
-/obj/machinery/mineral/equipment_vendor/ex_act(severity, target)
- do_sparks(5, TRUE, src)
- if(prob(50 / severity) && severity < 3)
- qdel(src)
-
/****************Golem Point Vendor**************************/
-/obj/machinery/mineral/equipment_vendor/golem
+/obj/machinery/vendor/mining/golem
name = "golem ship equipment vendor"
circuit = /obj/item/circuitboard/machine/mining_equipment_vendor/golem
-/obj/machinery/mineral/equipment_vendor/golem/Initialize()
+/obj/machinery/vendor/mining/golem/Initialize()
. = ..()
desc += "\nIt seems a few selections have been added."
prize_list += list(
- new /datum/data/mining_equipment("Extra Id", /obj/item/card/id/mining, 250),
- new /datum/data/mining_equipment("Science Goggles", /obj/item/clothing/glasses/science, 250),
- new /datum/data/mining_equipment("Monkey Cube", /obj/item/reagent_containers/food/snacks/monkeycube, 300),
- new /datum/data/mining_equipment("Toolbelt", /obj/item/storage/belt/utility, 350),
- new /datum/data/mining_equipment("Royal Cape of the Liberator", /obj/item/bedsheet/rd/royal_cape, 500),
- new /datum/data/mining_equipment("Grey Slime Extract", /obj/item/slime_extract/grey, 1000),
- new /datum/data/mining_equipment("Modification Kit", /obj/item/borg/upgrade/modkit/trigger_guard, 1700),
- new /datum/data/mining_equipment("The Liberator's Legacy", /obj/item/storage/box/rndboards, 2000)
+ new /datum/data/vendor_equipment("Extra Id", /obj/item/card/id/mining, 250),
+ new /datum/data/vendor_equipment("Science Goggles", /obj/item/clothing/glasses/science, 250),
+ new /datum/data/vendor_equipment("Monkey Cube", /obj/item/reagent_containers/food/snacks/monkeycube, 300),
+ new /datum/data/vendor_equipment("Toolbelt", /obj/item/storage/belt/utility, 350),
+ new /datum/data/vendor_equipment("Royal Cape of the Liberator", /obj/item/bedsheet/rd/royal_cape, 500),
+ new /datum/data/vendor_equipment("Grey Slime Extract", /obj/item/slime_extract/grey, 1000),
+ new /datum/data/vendor_equipment("P-KA Upgrade: Trigger Mod", /obj/item/borg/upgrade/modkit/trigger_guard, 1000),
+ new /datum/data/vendor_equipment("The Liberator's Legacy", /obj/item/storage/box/rndboards, 2000)
)
/**********************Mining Equipment Vendor Items**************************/
@@ -261,27 +284,14 @@
..()
/obj/item/card/mining_point_card/examine(mob/user)
- ..()
- to_chat(user, "There's [points] point\s on the card.")
+ . = ..()
+ . += "There's [points] point\s on the card."
///Conscript kit
-/obj/item/card/mining_access_card
+/obj/item/card/id/pass/mining_access_card
name = "mining access card"
desc = "A small card, that when used on any ID, will add mining access."
- icon_state = "data_1"
-
-/obj/item/card/mining_access_card/afterattack(atom/movable/AM, mob/user, proximity)
- . = ..()
- if(istype(AM, /obj/item/card/id) && proximity)
- var/obj/item/card/id/I = AM
- I.access |= ACCESS_MINING
- I.access |= ACCESS_MINING_STATION
- I.access |= ACCESS_MECH_MINING
- I.access |= ACCESS_MINERAL_STOREROOM
- I.access |= ACCESS_CARGO
- to_chat(user, "You upgrade [I] with mining access.")
- log_id("[key_name(user)] added mining access to '[I]' using [src] at [AREACOORD(user)].")
- qdel(src)
+ access = list(ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MECH_MINING, ACCESS_MINERAL_STOREROOM, ACCESS_CARGO)
/obj/item/storage/backpack/duffelbag/mining_conscript
name = "mining conscription kit"
@@ -295,4 +305,4 @@
new /obj/item/clothing/suit/hooded/explorer(src)
new /obj/item/encryptionkey/headset_cargo(src)
new /obj/item/clothing/mask/gas/explorer(src)
- new /obj/item/card/mining_access_card(src)
+ new /obj/item/card/id/pass/mining_access_card(src)
diff --git a/code/modules/mining/mine_items.dm b/code/modules/mining/mine_items.dm
index fa1e641a8190b..3dc17909227d4 100644
--- a/code/modules/mining/mine_items.dm
+++ b/code/modules/mining/mine_items.dm
@@ -71,17 +71,17 @@
/**********************Shuttle Computer**************************/
-/obj/machinery/computer/shuttle/mining
+/obj/machinery/computer/shuttle_flight/mining
name = "mining shuttle console"
desc = "Used to call and send the mining shuttle."
circuit = /obj/item/circuitboard/computer/mining_shuttle
shuttleId = "mining"
possible_destinations = "mining_home;mining_away;landing_zone_dock;mining_public"
- no_destination_swap = 1
+ req_access = list(ACCESS_MINING)
var/static/list/dumb_rev_heads = list()
//ATTACK HAND IGNORING PARENT RETURN VALUE
-/obj/machinery/computer/shuttle/mining/attack_hand(mob/user)
+/obj/machinery/computer/shuttle_flight/mining/attack_hand(mob/user)
if(is_station_level(user.z) && user.mind && is_head_revolutionary(user) && !(user.mind in dumb_rev_heads))
to_chat(user, "You get a feeling that leaving the station might be a REALLY dumb idea...")
dumb_rev_heads += user.mind
@@ -89,13 +89,12 @@
. = ..()
//It is on lavaland, soooo....
-/obj/machinery/computer/shuttle/science
+/obj/machinery/computer/shuttle_flight/science
name = "science outpost shuttle console"
desc = "Used to call and send the science shuttle."
circuit = /obj/item/circuitboard/computer/science_shuttle
shuttleId = "science"
possible_destinations = "science_station;science_outpost"
- no_destination_swap = 1
/**********************Mining car (Crate like thing, not the rail car)**************************/
@@ -103,3 +102,4 @@
desc = "A mining car. This one doesn't work on rails, but has to be dragged."
name = "Mining car (not for rails)"
icon_state = "miningcar"
+ door_anim_time = 0
diff --git a/code/modules/mining/minebot.dm b/code/modules/mining/minebot.dm
index d752467f2aa72..e70887698008f 100644
--- a/code/modules/mining/minebot.dm
+++ b/code/modules/mining/minebot.dm
@@ -3,7 +3,7 @@
#define MINEDRONE_ATTACK 2
/mob/living/simple_animal/hostile/mining_drone
- name = "nanotrasen minebot"
+ name = "\improper Nanotrasen minebot"
desc = "The instructions printed on the side read: This is a small robot used to support miners, can be set to search and collect loose ore, or to help fend off wildlife."
gender = NEUTER
icon = 'icons/mob/aibots.dmi'
diff --git a/code/modules/mining/mint.dm b/code/modules/mining/mint.dm
index 4d44e66c503dd..5dc2bbd05a450 100644
--- a/code/modules/mining/mint.dm
+++ b/code/modules/mining/mint.dm
@@ -7,6 +7,7 @@
icon_state = "coinpress0"
density = TRUE
input_dir = EAST
+ needs_item_input = TRUE
var/obj/item/storage/bag/money/bag_to_use
@@ -31,16 +32,21 @@
chosen = getmaterialref(chosen)
-/obj/machinery/mineral/mint/process()
- var/turf/T = get_step(src, input_dir)
+/obj/machinery/mineral/mint/pickup_item(datum/source, atom/movable/target, atom/oldLoc)
+ if(QDELETED(target))
+ return
+ if(!istype(target, /obj/item/stack))
+ return
+
var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
+ var/obj/item/stack/S = target
- for(var/obj/item/stack/O in T)
- var/inserted = materials.insert_item(O)
- if(inserted)
- qdel(O)
+ if(materials.insert_item(S))
+ qdel(S)
+/obj/machinery/mineral/mint/process()
if(processing)
+ var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
var/datum/material/M = chosen
if(!M)
@@ -68,6 +74,7 @@
if(!found_new)
processing = FALSE
else
+ end_processing()
icon_state = "coinpress0"
@@ -79,6 +86,7 @@
if(!ui)
ui = new(user, src, "Mint")
ui.open()
+ ui.set_autoupdate(TRUE) // Coins pressed (could be refactored to ui_update), material amounts
/obj/machinery/mineral/mint/ui_data()
var/list/data = list()
@@ -114,13 +122,18 @@
if (!processing)
produced_coins = 0
processing = TRUE
+ begin_processing()
+ . = TRUE
if ("stoppress")
processing = FALSE
+ end_processing()
+ . = TRUE
if ("changematerial")
var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
for(var/datum/material/mat in materials.materials)
if (params["material_name"] == mat.name)
chosen = mat
+ . = TRUE
/obj/machinery/mineral/mint/proc/create_coins()
var/turf/T = get_step(src,output_dir)
diff --git a/code/modules/mining/ores_coins.dm b/code/modules/mining/ores_coins.dm
index 7c07bc065d942..f5e800dc07f7d 100644
--- a/code/modules/mining/ores_coins.dm
+++ b/code/modules/mining/ores_coins.dm
@@ -18,6 +18,8 @@
var/refined_type = null //What this ore defaults to being refined into
novariants = TRUE // Ore stacks handle their icon updates themselves to keep the illusion that there's more going
var/list/stack_overlays
+ var/scan_state = "" //Used by mineral turfs for their scan overlay.
+ var/spreadChance = 0 //Also used by mineral turfs for spreading veins
/obj/item/stack/ore/update_icon()
var/difference = min(ORESTACK_OVERLAYS_MAX, amount) - (LAZYLEN(stack_overlays)+1)
@@ -71,6 +73,8 @@
points = 30
materials = list(/datum/material/uranium=MINERAL_MATERIAL_AMOUNT)
refined_type = /obj/item/stack/sheet/mineral/uranium
+ scan_state = "rock_Uranium"
+ spreadChance = 5
/obj/item/stack/ore/iron
name = "iron ore"
@@ -80,6 +84,8 @@
points = 1
materials = list(/datum/material/iron=MINERAL_MATERIAL_AMOUNT)
refined_type = /obj/item/stack/sheet/iron
+ scan_state = "rock_Iron"
+ spreadChance = 20
/obj/item/stack/ore/glass
name = "sand pile"
@@ -132,6 +138,8 @@ GLOBAL_LIST_INIT(sand_recipes, list(\
points = 15
materials = list(/datum/material/plasma=MINERAL_MATERIAL_AMOUNT)
refined_type = /obj/item/stack/sheet/mineral/plasma
+ scan_state = "rock_Plasma"
+ spreadChance = 8
/obj/item/stack/ore/plasma/welder_act(mob/living/user, obj/item/I)
to_chat(user, "You can't hit a high enough temperature to smelt [src] properly!")
@@ -145,6 +153,8 @@ GLOBAL_LIST_INIT(sand_recipes, list(\
points = 5
materials = list(/datum/material/copper=MINERAL_MATERIAL_AMOUNT)
refined_type = /obj/item/stack/sheet/mineral/copper
+ scan_state = "rock_Copper"
+ spreadChance = 5
/obj/item/stack/ore/silver
name = "silver ore"
@@ -154,6 +164,8 @@ GLOBAL_LIST_INIT(sand_recipes, list(\
points = 16
materials = list(/datum/material/silver=MINERAL_MATERIAL_AMOUNT)
refined_type = /obj/item/stack/sheet/mineral/silver
+ scan_state = "rock_Silver"
+ spreadChance = 5
/obj/item/stack/ore/gold
name = "gold ore"
@@ -163,6 +175,8 @@ GLOBAL_LIST_INIT(sand_recipes, list(\
points = 18
materials = list(/datum/material/gold=MINERAL_MATERIAL_AMOUNT)
refined_type = /obj/item/stack/sheet/mineral/gold
+ scan_state = "rock_Gold"
+ spreadChance = 5
/obj/item/stack/ore/diamond
name = "diamond ore"
@@ -172,6 +186,7 @@ GLOBAL_LIST_INIT(sand_recipes, list(\
points = 50
materials = list(/datum/material/diamond=MINERAL_MATERIAL_AMOUNT)
refined_type = /obj/item/stack/sheet/mineral/diamond
+ scan_state = "rock_Diamond"
/obj/item/stack/ore/bananium
name = "bananium ore"
@@ -181,6 +196,7 @@ GLOBAL_LIST_INIT(sand_recipes, list(\
points = 60
materials = list(/datum/material/bananium=MINERAL_MATERIAL_AMOUNT)
refined_type = /obj/item/stack/sheet/mineral/bananium
+ scan_state = "rock_Bananium"
/obj/item/stack/ore/titanium
name = "titanium ore"
@@ -190,6 +206,8 @@ GLOBAL_LIST_INIT(sand_recipes, list(\
points = 50
materials = list(/datum/material/titanium=MINERAL_MATERIAL_AMOUNT)
refined_type = /obj/item/stack/sheet/mineral/titanium
+ scan_state = "rock_Titanium"
+ spreadChance = 5
/obj/item/stack/ore/slag
name = "slag"
@@ -198,7 +216,7 @@ GLOBAL_LIST_INIT(sand_recipes, list(\
item_state = "slag"
singular_name = "slag chunk"
-/obj/item/twohanded/required/gibtonite
+/obj/item/gibtonite
name = "gibtonite ore"
desc = "Extremely explosive if struck with mining equipment, Gibtonite is often used by miners to speed up their work by using it as a mining charge. This material is illegal to possess by unauthorized personnel under space law."
icon = 'icons/obj/mining.dmi'
@@ -212,12 +230,16 @@ GLOBAL_LIST_INIT(sand_recipes, list(\
var/attacher = "UNKNOWN"
var/det_timer
-/obj/item/twohanded/required/gibtonite/Destroy()
+/obj/item/gibtonite/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/two_handed, require_twohands=TRUE)
+
+/obj/item/gibtonite/Destroy()
qdel(wires)
wires = null
return ..()
-/obj/item/twohanded/required/gibtonite/attackby(obj/item/I, mob/user, params)
+/obj/item/gibtonite/attackby(obj/item/I, mob/user, params)
if(!wires && istype(I, /obj/item/assembly/igniter))
user.visible_message("[user] attaches [I] to [src].", "You attach [I] to [src].")
wires = new /datum/wires/explosive/gibtonite(src)
@@ -245,20 +267,20 @@ GLOBAL_LIST_INIT(sand_recipes, list(\
return
..()
-/obj/item/twohanded/required/gibtonite/attack_self(user)
+/obj/item/gibtonite/attack_self(user)
if(wires)
wires.interact(user)
else
..()
-/obj/item/twohanded/required/gibtonite/bullet_act(obj/item/projectile/P)
+/obj/item/gibtonite/bullet_act(obj/item/projectile/P)
GibtoniteReaction(P.firer)
. = ..()
-/obj/item/twohanded/required/gibtonite/ex_act()
+/obj/item/gibtonite/ex_act()
GibtoniteReaction(null, 1)
-/obj/item/twohanded/required/gibtonite/proc/GibtoniteReaction(mob/user, triggered_by = 0)
+/obj/item/gibtonite/proc/GibtoniteReaction(mob/user, triggered_by = 0)
if(!primed)
primed = TRUE
playsound(src,'sound/effects/hit_on_shattered_glass.ogg',50,1)
@@ -281,7 +303,7 @@ GLOBAL_LIST_INIT(sand_recipes, list(\
log_bomber(user, "has primed a", src, "for detonation", notify_admins)
det_timer = addtimer(CALLBACK(src, .proc/detonate, notify_admins), det_time, TIMER_STOPPABLE)
-/obj/item/twohanded/required/gibtonite/proc/detonate(notify_admins)
+/obj/item/gibtonite/proc/detonate(notify_admins)
if(primed)
switch(quality)
if(GIBTONITE_QUALITY_HIGH)
diff --git a/code/modules/mining/satchel_ore_boxdm.dm b/code/modules/mining/satchel_ore_boxdm.dm
index f48df08b00805..8f837880ef7d5 100644
--- a/code/modules/mining/satchel_ore_boxdm.dm
+++ b/code/modules/mining/satchel_ore_boxdm.dm
@@ -8,16 +8,21 @@
desc = "A heavy wooden box, which can be filled with a lot of ores."
density = TRUE
pressure_resistance = 5*ONE_ATMOSPHERE
+ var/static/list/typecache_to_take
-
-
+/obj/structure/ore_box/Initialize()
+ . = ..()
+ if(!typecache_to_take)
+ typecache_to_take = typecacheof(/obj/item/stack/ore)
/obj/structure/ore_box/attackby(obj/item/W, mob/user, params)
if (istype(W, /obj/item/stack/ore))
user.transferItemToLoc(W, src)
+ ui_update()
else if(SEND_SIGNAL(W, COMSIG_CONTAINS_STORAGE))
- SEND_SIGNAL(W, COMSIG_TRY_STORAGE_TAKE_TYPE, /obj/item/stack/ore, src)
+ SEND_SIGNAL(W, COMSIG_TRY_STORAGE_TAKE_TYPE, typecache_to_take, src)
to_chat(user, "You empty the ore in [W] into \the [src].")
+ ui_update()
else
return ..()
@@ -84,14 +89,12 @@
/obj/structure/ore_box/ui_act(action, params)
if(..())
return
- if(!Adjacent(usr))
- return
- add_fingerprint(usr)
- usr.set_machine(src)
+
switch(action)
if("removeall")
dump_box_contents()
to_chat(usr, "You open the release hatch on the box..")
+ . = TRUE
/obj/structure/ore_box/deconstruct(disassembled = TRUE, mob/user)
var/obj/item/stack/sheet/mineral/wood/WD = new (loc, 4)
diff --git a/code/modules/mob/dead/dead.dm b/code/modules/mob/dead/dead.dm
index 6dcdd076357c2..7f5b31ac3987e 100644
--- a/code/modules/mob/dead/dead.dm
+++ b/code/modules/mob/dead/dead.dm
@@ -12,13 +12,14 @@ INITIALIZE_IMMEDIATE(/mob/dead)
stack_trace("Warning: [src]([type]) initialized multiple times!")
flags_1 |= INITIALIZED_1
tag = "mob_[next_mob_id++]"
- GLOB.mob_list += src
+ add_to_mob_list()
prepare_huds()
- if(length(CONFIG_GET(keyed_list/cross_server)))
- verbs += /mob/dead/proc/server_hop
+ if(length(CONFIG_GET(keyed_list/server_hop)))
+ add_verb(/mob/dead/proc/server_hop)
set_focus(src)
+ become_hearing_sensitive()
return INITIALIZE_HINT_NORMAL
/mob/dead/canUseStorage()
@@ -39,27 +40,26 @@ INITIALIZE_IMMEDIATE(/mob/dead)
loc = destination
Moved(oldloc, NONE, TRUE)
-/mob/dead/Stat()
- ..()
+/mob/dead/get_stat_tab_status()
+ var/list/tab_data = ..()
- if(!statpanel("Status"))
- return
- stat(null, "Game Mode: [SSticker.hide_mode ? "Secret" : "[GLOB.master_mode]"]")
+ tab_data["Game Mode"] = GENERATE_STAT_TEXT("[SSticker.hide_mode ? "Secret" : "[GLOB.master_mode]"]")
if(SSticker.HasRoundStarted())
- return
+ return tab_data
var/time_remaining = SSticker.GetTimeLeft()
if(time_remaining > 0)
- stat(null, "Time To Start: [round(time_remaining/10)]s")
+ tab_data["Time To Start"] = GENERATE_STAT_TEXT("[round(time_remaining/10)]s")
else if(time_remaining == -10)
- stat(null, "Time To Start: DELAYED")
+ tab_data["Time To Start"] = GENERATE_STAT_TEXT("DELAYED")
else
- stat(null, "Time To Start: SOON")
+ tab_data["Time To Start"] = GENERATE_STAT_TEXT("SOON")
- stat(null, "Players: [SSticker.totalPlayers]")
+ tab_data["Players"] = GENERATE_STAT_TEXT("[SSticker.totalPlayers]")
if(client.holder)
- stat(null, "Players Ready: [SSticker.totalPlayersReady]")
+ tab_data["Players Ready"] = GENERATE_STAT_TEXT("[SSticker.totalPlayersReady]")
+ return tab_data
/mob/dead/proc/server_hop()
set category = "OOC"
@@ -67,11 +67,11 @@ INITIALIZE_IMMEDIATE(/mob/dead)
set desc= "Jump to the other server"
if(notransform)
return
- var/list/csa = CONFIG_GET(keyed_list/cross_server)
+ var/list/csa = CONFIG_GET(keyed_list/server_hop)
var/pick
switch(csa.len)
if(0)
- verbs -= /mob/dead/proc/server_hop
+ remove_verb(/mob/dead/proc/server_hop)
to_chat(src, "Server Hop has been disabled.")
if(1)
pick = csa[1]
@@ -88,7 +88,7 @@ INITIALIZE_IMMEDIATE(/mob/dead)
var/client/C = client
to_chat(C, "Sending you to [pick].")
- new /obj/screen/splash(C)
+ new /atom/movable/screen/splash(C)
notransform = TRUE
sleep(29) //let the animation play
diff --git a/code/modules/mob/dead/new_player/login.dm b/code/modules/mob/dead/new_player/login.dm
index 887556500cba7..97e97bb31e0e6 100644
--- a/code/modules/mob/dead/new_player/login.dm
+++ b/code/modules/mob/dead/new_player/login.dm
@@ -4,21 +4,21 @@
client.set_db_player_flags()
if(!mind)
mind = new /datum/mind(key)
- mind.active = 1
- mind.current = src
+ mind.active = TRUE
+ mind.set_current(src)
..()
var/motd = global.config.motd
if(motd)
- to_chat(src, "
[motd]
", handle_whitespace=FALSE)
+ to_chat(src, "
[motd]
", handle_whitespace=FALSE, allow_linkify = TRUE)
if(GLOB.admin_notice)
to_chat(src, "Admin Notice:\n \t [GLOB.admin_notice]")
var/spc = CONFIG_GET(number/soft_popcap)
if(spc && living_player_count() >= spc)
- to_chat(src, "Server Notice:\n \t [CONFIG_GET(string/soft_popcap_message)]")
+ to_chat(src, "Server Notice:\n \t [CONFIG_GET(string/soft_popcap_message)]", allow_linkify = TRUE)
sight |= SEE_TURFS
diff --git a/code/modules/mob/dead/new_player/logout.dm b/code/modules/mob/dead/new_player/logout.dm
index a70800d35b535..b2f37376a3c62 100644
--- a/code/modules/mob/dead/new_player/logout.dm
+++ b/code/modules/mob/dead/new_player/logout.dm
@@ -3,5 +3,6 @@
..()
if(!spawning)//Here so that if they are spawning and log out, the other procs can play out and they will have a mob to come back to.
key = null//We null their key before deleting the mob, so they are properly kicked out.
+ QDEL_NULL(mind) //Clean out mind, yes this fucking sucks
qdel(src)
return
\ No newline at end of file
diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm
index 6491c692ebdd9..e47c097febdde 100644
--- a/code/modules/mob/dead/new_player/new_player.dm
+++ b/code/modules/mob/dead/new_player/new_player.dm
@@ -15,7 +15,7 @@
/mob/dead/new_player/Initialize()
if(client && SSticker.state == GAME_STATE_STARTUP)
- var/obj/screen/splash/S = new(client, TRUE, TRUE)
+ var/atom/movable/screen/splash/S = new(client, TRUE, TRUE)
S.Fade(TRUE)
if(length(GLOB.newplayer_start))
@@ -140,8 +140,8 @@
LateChoices()
return
- if(SSticker.queued_players.len || (relevant_cap && living_player_count() >= relevant_cap && !(ckey(key) in GLOB.admin_datums)))
- if(IS_PATRON(src.ckey))
+ if(SSticker.queued_players.len || (relevant_cap && living_player_count() >= relevant_cap))
+ if(IS_PATRON(src.ckey) || (client in GLOB.admins))
LateChoices()
return
to_chat(usr, "[CONFIG_GET(string/hard_popcap_message)]")
@@ -169,7 +169,7 @@
to_chat(usr, "There is an administrative lock on entering the game!")
return
- if(SSticker.queued_players.len && !(ckey(key) in GLOB.admin_datums))
+ if(SSticker.queued_players.len && !(ckey(key) in GLOB.admin_datums) && !IS_PATRON(ckey(key)))
if((living_player_count() >= relevant_cap) || (src != SSticker.queued_players[1]))
to_chat(usr, "Server is full.")
return
@@ -193,12 +193,14 @@
vote_on_poll_handler(poll, href_list)
//When you cop out of the round (NB: this HAS A SLEEP FOR PLAYER INPUT IN IT)
-/mob/dead/new_player/proc/make_me_an_observer()
+/mob/dead/new_player/proc/make_me_an_observer(force_observe=FALSE)
if(QDELETED(src) || !src.client)
ready = PLAYER_NOT_READY
return FALSE
- var/this_is_like_playing_right = alert(src,"Are you sure you wish to observe? You will not be able to play this round!","Player Setup","Yes","No")
+ var/this_is_like_playing_right = "Yes"
+ if(!force_observe)
+ this_is_like_playing_right = alert(src,"Are you sure you wish to observe? You will not be able to play this round!","Player Setup","Yes","No")
if(QDELETED(src) || !src.client || this_is_like_playing_right != "Yes")
ready = PLAYER_NOT_READY
@@ -308,9 +310,9 @@
if(job && !job.override_latejoin_spawn(character))
SSjob.SendToLateJoin(character)
if(!arrivals_docked)
- var/obj/screen/splash/Spl = new(character.client, TRUE)
+ var/atom/movable/screen/splash/Spl = new(character.client, TRUE)
Spl.Fade(TRUE)
- character.playsound_local(get_turf(character), 'sound/voice/ApproachingTG.ogg', 25)
+ character.playsound_local(get_turf(character), 'sound/voice/welcomeBee.ogg', 50)
character.update_parallax_teleport()
@@ -382,7 +384,7 @@
SSjob.prioritized_jobs -= prioritized_job
dat += "
"
var/column_counter = 0
- for(var/list/category in list(GLOB.command_positions) + list(GLOB.engineering_positions) + list(GLOB.supply_positions) + list(GLOB.nonhuman_positions - "pAI") + list(GLOB.civilian_positions) + list(GLOB.medical_positions) + list(GLOB.science_positions) + list(GLOB.security_positions))
+ for(var/list/category in list(GLOB.command_positions) + list(GLOB.engineering_positions) + list(GLOB.supply_positions) + list(GLOB.nonhuman_positions - "pAI") + list(GLOB.civilian_positions) + list(GLOB.gimmick_positions) + list(GLOB.medical_positions) + list(GLOB.science_positions) + list(GLOB.security_positions))
var/cat_color = SSjob.name_occupations[category[1]].selection_color //use the color of the first job in the category (the department head) as the category color
dat += "
Close
@@ -224,20 +241,7 @@
var/obj/item/I = locate(href_list["embedded_object"]) in L.embedded_objects
if(!I || I.loc != src) //no item, no limb, or item is not in limb or in the person anymore
return
- var/time_taken = I.embedding.embedded_unsafe_removal_time*I.w_class
- usr.visible_message("[usr] attempts to remove [I] from [usr.p_their()] [L.name].","You attempt to remove [I] from your [L.name]... (It will take [DisplayTimeText(time_taken)].)")
- if(do_after(usr, time_taken, needhand = 1, target = src))
- if(!I || !L || I.loc != src || !(I in L.embedded_objects))
- return
- L.embedded_objects -= I
- L.receive_damage(I.embedding.embedded_unsafe_removal_pain_multiplier*I.w_class)//It hurts to rip it out, get surgery you dingus.
- I.forceMove(get_turf(src))
- usr.put_in_hands(I)
- usr.emote("scream")
- usr.visible_message("[usr] successfully rips [I] out of [usr.p_their()] [L.name]!","You successfully remove [I] from your [L.name].")
- if(!has_embedded_objects())
- clear_alert("embeddedobject")
- SEND_SIGNAL(usr, COMSIG_CLEAR_MOOD_EVENT, "embedded")
+ SEND_SIGNAL(src, COMSIG_CARBON_EMBED_RIP, I, L)
return
if(href_list["item"]) //canUseTopic check for this is handled by mob/Topic()
@@ -248,8 +252,8 @@
if(href_list["pockets"] && usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) //TODO: Make it match (or intergrate it into) strippanel so you get 'item cannot fit here' warnings if mob_can_equip fails
var/pocket_side = href_list["pockets"]
- var/pocket_id = (pocket_side == "right" ? SLOT_R_STORE : SLOT_L_STORE)
- var/obj/item/pocket_item = (pocket_id == SLOT_R_STORE ? r_store : l_store)
+ var/pocket_id = (pocket_side == "right" ? ITEM_SLOT_RPOCKET : ITEM_SLOT_LPOCKET)
+ var/obj/item/pocket_item = (pocket_id == ITEM_SLOT_RPOCKET ? r_store : l_store)
var/obj/item/place_item = usr.get_active_held_item() // Item to place in the pocket, if it's empty
var/delay_denominator = 1
@@ -265,7 +269,7 @@
if(do_mob(usr, src, POCKET_STRIP_DELAY/delay_denominator)) //placing an item into the pocket is 4 times faster
if(pocket_item)
- if(pocket_item == (pocket_id == SLOT_R_STORE ? r_store : l_store)) //item still in the pocket we search
+ if(pocket_item == (pocket_id == ITEM_SLOT_RPOCKET ? r_store : l_store)) //item still in the pocket we search
dropItemToGround(pocket_item)
else
if(place_item)
@@ -278,246 +282,255 @@
// Display a warning if the user mocks up
to_chat(src, "You feel your [pocket_side] pocket being fumbled with!")
+ if(href_list["set_sensor"])
+ if(istype(w_uniform, /obj/item/clothing/under))
+ var/obj/item/clothing/under/U = w_uniform
+ U.set_sensors(usr)
+
///////HUDs///////
if(href_list["hud"])
- if(ishuman(usr))
- var/mob/living/carbon/human/H = usr
- var/perpname = get_face_name(get_id_name(""))
- if(istype(H.glasses, /obj/item/clothing/glasses/hud) || istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud))
- var/datum/data/record/R = find_record("name", perpname, GLOB.data_core.general)
- if(href_list["photo_front"] || href_list["photo_side"])
- if(R)
- if(!H.canUseHUD())
- return
- else if(!istype(H.glasses, /obj/item/clothing/glasses/hud) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/medical))
- return
- var/obj/item/photo/P = null
- if(href_list["photo_front"])
- P = R.fields["photo_front"]
- else if(href_list["photo_side"])
- P = R.fields["photo_side"]
- if(P)
- P.show(H)
-
- if(href_list["hud"] == "m")
- if(istype(H.glasses, /obj/item/clothing/glasses/hud/health) || istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/medical))
- if(href_list["p_stat"])
- var/health_status = input(usr, "Specify a new physical status for this person.", "Medical HUD", R.fields["p_stat"]) in list("Active", "Physically Unfit", "*Unconscious*", "*Deceased*", "Cancel")
- if(R)
- if(!H.canUseHUD())
- return
- else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/health) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/medical))
- return
- if(health_status && health_status != "Cancel")
- R.fields["p_stat"] = health_status
- return
- if(href_list["m_stat"])
- var/health_status = input(usr, "Specify a new mental status for this person.", "Medical HUD", R.fields["m_stat"]) in list("Stable", "*Watch*", "*Unstable*", "*Insane*", "Cancel")
- if(R)
- if(!H.canUseHUD())
- return
- else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/health) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/medical))
- return
- if(health_status && health_status != "Cancel")
- R.fields["m_stat"] = health_status
- return
- if(href_list["evaluation"])
- if(!getBruteLoss() && !getFireLoss() && !getOxyLoss() && getToxLoss() < 20)
- to_chat(usr, "No external injuries detected. ")
- return
- var/span = "notice"
- var/status = ""
- if(getBruteLoss())
- to_chat(usr, "Physical trauma analysis:")
- for(var/X in bodyparts)
- var/obj/item/bodypart/BP = X
- var/brutedamage = BP.brute_dam
- if(brutedamage > 0)
- status = "received minor physical injuries."
- span = "notice"
- if(brutedamage > 20)
- status = "been seriously damaged."
- span = "danger"
- if(brutedamage > 40)
- status = "sustained major trauma!"
- span = "userdanger"
- if(brutedamage)
- to_chat(usr, "[BP] appears to have [status]")
- if(getFireLoss())
- to_chat(usr, "Analysis of skin burns:")
- for(var/X in bodyparts)
- var/obj/item/bodypart/BP = X
- var/burndamage = BP.burn_dam
- if(burndamage > 0)
- status = "signs of minor burns."
- span = "notice"
- if(burndamage > 20)
- status = "serious burns."
- span = "danger"
- if(burndamage > 40)
- status = "major burns!"
- span = "userdanger"
- if(burndamage)
- to_chat(usr, "[BP] appears to have [status]")
- if(getOxyLoss())
- to_chat(usr, "Patient has signs of suffocation, emergency treatment may be required!")
- if(getToxLoss() > 20)
- to_chat(usr, "Gathered data is inconsistent with the analysis, possible cause: poisoning.")
-
- if(href_list["hud"] == "s")
- if(istype(H.glasses, /obj/item/clothing/glasses/hud/security) || istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security))
- if(usr.stat || usr == src) //|| !usr.canmove || usr.restrained()) Fluff: Sechuds have eye-tracking technology and sets 'arrest' to people that the wearer looks and blinks at.
- return //Non-fluff: This allows sec to set people to arrest as they get disarmed or beaten
- // Checks the user has security clearence before allowing them to change arrest status via hud, comment out to enable all access
- var/allowed_access = null
- var/obj/item/clothing/glasses/hud/security/G = H.glasses
- if(istype(G) && (G.obj_flags & EMAGGED))
- allowed_access = "@%&ERROR_%$*"
- else //Implant and standard glasses check access
- if(H.wear_id)
- var/list/access = H.wear_id.GetAccess()
- if(ACCESS_SEC_DOORS in access)
- allowed_access = H.get_authentification_name()
-
- if(!allowed_access)
- to_chat(H, "ERROR: Invalid Access.")
- return
-
- if(perpname)
- R = find_record("name", perpname, GLOB.data_core.security)
- if(R)
- if(href_list["status"])
- var/setcriminal = input(usr, "Specify a new criminal status for this person.", "Security HUD", R.fields["criminal"]) in list("None", "*Arrest*", "Incarcerated", "Paroled", "Discharged", "Cancel")
- if(setcriminal != "Cancel")
- if(R)
- if(H.canUseHUD())
- if(istype(H.glasses, /obj/item/clothing/glasses/hud/security) || istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security))
- investigate_log("[key_name(src)] has been set from [R.fields["criminal"]] to [setcriminal] by [key_name(usr)].", INVESTIGATE_RECORDS)
- R.fields["criminal"] = setcriminal
- sec_hud_set_security_status()
- return
-
- if(href_list["view"])
- if(R)
- if(!H.canUseHUD())
- return
- else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security))
- return
- to_chat(usr, "Name: [R.fields["name"]] Criminal Status: [R.fields["criminal"]]")
- to_chat(usr, "Minor Crimes:")
- for(var/datum/data/crime/c in R.fields["mi_crim"])
- to_chat(usr, "Crime: [c.crimeName]")
- to_chat(usr, "Details: [c.crimeDetails]")
- to_chat(usr, "Added by [c.author] at [c.time]")
- to_chat(usr, "----------")
- to_chat(usr, "Major Crimes:")
- for(var/datum/data/crime/c in R.fields["ma_crim"])
- to_chat(usr, "Crime: [c.crimeName]")
- to_chat(usr, "Details: [c.crimeDetails]")
- to_chat(usr, "Added by [c.author] at [c.time]")
- to_chat(usr, "----------")
- to_chat(usr, "Notes: [R.fields["notes"]]")
- return
-
- if(href_list["add_citation"])
- var/maxFine = CONFIG_GET(number/maxfine)
- var/t1 = stripped_input("Please input citation crime:", "Security HUD", "", null)
- var/fine = FLOOR(input("Please input citation fine, up to [maxFine]:", "Security HUD", 50) as num|null, 1)
- if(!R || !t1 || !fine || !allowed_access)
- return
- if(!H.canUseHUD())
- return
- if(!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security))
- return
- if(fine < 0)
- to_chat(usr, "You're pretty sure that's not how money works.")
- return
- fine = min(fine, maxFine)
-
- var/crime = GLOB.data_core.createCrimeEntry(t1, "", allowed_access, station_time_timestamp(), fine)
- for (var/obj/item/pda/P in GLOB.PDAs)
- if(P.owner == R.fields["name"])
- var/message = "You have been fined [fine] credits for '[t1]'. Fines may be paid at security."
- var/datum/signal/subspace/messaging/pda/signal = new(src, list(
- "name" = "Security Citation",
- "job" = "Citation Server",
- "message" = message,
- "targets" = list("[P.owner] ([P.ownjob])"),
- "automated" = 1
- ))
- signal.send_to_receivers()
- usr.log_message("(PDA: Citation Server) sent \"[message]\" to [signal.format_target()]", LOG_PDA)
- GLOB.data_core.addCitation(R.fields["id"], crime)
- investigate_log("New Citation: [t1] Fine: [fine] | Added to [R.fields["name"]] by [key_name(usr)]", INVESTIGATE_RECORDS)
- return
-
- if(href_list["add_crime"])
- switch(alert("What crime would you like to add?","Security HUD","Minor Crime","Major Crime","Cancel"))
- if("Minor Crime")
- if(R)
- var/t1 = stripped_input(usr, "Please input minor crime names:", "Security HUD")
- var/t2 = stripped_multiline_input(usr, "Please input minor crime details:", "Security HUD")
- if(R)
- if (!t1 || !t2 || !allowed_access)
- return
- else if(!H.canUseHUD())
- return
- else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security))
- return
- var/crime = GLOB.data_core.createCrimeEntry(t1, t2, allowed_access, station_time_timestamp())
- GLOB.data_core.addMinorCrime(R.fields["id"], crime)
- investigate_log("New Minor Crime: [t1]: [t2] | Added to [R.fields["name"]] by [key_name(usr)]", INVESTIGATE_RECORDS)
- to_chat(usr, "Successfully added a minor crime.")
- return
- if("Major Crime")
- if(R)
- var/t1 = stripped_input(usr, "Please input major crime names:", "Security HUD")
- var/t2 = stripped_multiline_input(usr, "Please input major crime details:", "Security HUD")
- if(R)
- if (!t1 || !t2 || !allowed_access)
- return
- else if (!H.canUseHUD())
- return
- else if (!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security))
- return
- var/crime = GLOB.data_core.createCrimeEntry(t1, t2, allowed_access, station_time_timestamp())
- GLOB.data_core.addMajorCrime(R.fields["id"], crime)
- investigate_log("New Major Crime: [t1]: [t2] | Added to [R.fields["name"]] by [key_name(usr)]", INVESTIGATE_RECORDS)
- to_chat(usr, "Successfully added a major crime.")
- return
-
- if(href_list["view_comment"])
- if(R)
- if(!H.canUseHUD())
- return
- else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security))
- return
- to_chat(usr, "Comments/Log:")
- var/counter = 1
- while(R.fields[text("com_[]", counter)])
- to_chat(usr, R.fields[text("com_[]", counter)])
- to_chat(usr, "----------")
- counter++
- return
-
- if(href_list["add_comment"])
- if(R)
- var/t1 = stripped_multiline_input(usr, "Add Comment:", "Secure. records")
- if(R)
- if (!t1 || !allowed_access)
- return
- else if(!H.canUseHUD())
- return
- else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security))
- return
- var/counter = 1
- while(R.fields[text("com_[]", counter)])
- counter++
- R.fields[text("com_[]", counter)] = text("Made by [] on [] [], [] []", allowed_access, station_time_timestamp(), time2text(world.realtime, "MMM DD"), GLOB.year_integer+540, t1)
- to_chat(usr, "Successfully added comment.")
- return
- to_chat(usr, "Unable to locate a data core entry for this person.")
+ if(!ishuman(usr))
+ return
+ var/mob/living/carbon/human/H = usr
+ var/perpname = get_face_name(get_id_name(""))
+ if(!HAS_TRAIT(H, TRAIT_SECURITY_HUD) && !HAS_TRAIT(H, TRAIT_MEDICAL_HUD))
+ return
+ var/datum/data/record/R = find_record("name", perpname, GLOB.data_core.general)
+ if(href_list["photo_front"] || href_list["photo_side"])
+ if(!R)
+ return
+ if(!H.canUseHUD())
+ return
+ var/obj/item/photo/P = null
+ if(href_list["photo_front"])
+ P = R.fields["photo_front"]
+ else if(href_list["photo_side"])
+ P = R.fields["photo_side"]
+ if(P)
+ P.show(H)
+ return
+
+ if(href_list["hud"] == "m")
+ if(!HAS_TRAIT(H, TRAIT_MEDICAL_HUD))
+ return
+ if(href_list["evaluation"])
+ if(!getBruteLoss() && !getFireLoss() && !getOxyLoss() && getToxLoss() < 20)
+ to_chat(usr, "No external injuries detected. ")
+ return
+ var/span = "notice"
+ var/status = ""
+ if(getBruteLoss())
+ to_chat(usr, "Physical trauma analysis:")
+ for(var/X in bodyparts)
+ var/obj/item/bodypart/BP = X
+ var/brutedamage = BP.brute_dam
+ if(brutedamage > 0)
+ status = "received minor physical injuries."
+ span = "notice"
+ if(brutedamage > 20)
+ status = "been seriously damaged."
+ span = "danger"
+ if(brutedamage > 40)
+ status = "sustained major trauma!"
+ span = "userdanger"
+ if(brutedamage)
+ to_chat(usr, "[BP] appears to have [status]")
+ if(getFireLoss())
+ to_chat(usr, "Analysis of skin burns:")
+ for(var/X in bodyparts)
+ var/obj/item/bodypart/BP = X
+ var/burndamage = BP.burn_dam
+ if(burndamage > 0)
+ status = "signs of minor burns."
+ span = "notice"
+ if(burndamage > 20)
+ status = "serious burns."
+ span = "danger"
+ if(burndamage > 40)
+ status = "major burns!"
+ span = "userdanger"
+ if(burndamage)
+ to_chat(usr, "[BP] appears to have [status]")
+ if(getOxyLoss())
+ to_chat(usr, "Patient has signs of suffocation, emergency treatment may be required!")
+ if(getToxLoss() > 20)
+ to_chat(usr, "Gathered data is inconsistent with the analysis, possible cause: poisoning.")
+ if(!H.wear_id) //You require access from here on out.
+ to_chat(H, "ERROR: Invalid access")
+ return
+ var/list/access = H.wear_id.GetAccess()
+ if(!(ACCESS_MEDICAL in access))
+ to_chat(H, "ERROR: Invalid access")
+ return
+ if(href_list["p_stat"])
+ var/health_status = input(usr, "Specify a new physical status for this person.", "Medical HUD", R.fields["p_stat"]) in list("Active", "Physically Unfit", "*Unconscious*", "*Deceased*", "Cancel")
+ if(!R)
+ return
+ if(!H.canUseHUD())
+ return
+ if(!HAS_TRAIT(H, TRAIT_MEDICAL_HUD))
+ return
+ if(health_status && health_status != "Cancel")
+ R.fields["p_stat"] = health_status
+ return
+ if(href_list["m_stat"])
+ var/health_status = input(usr, "Specify a new mental status for this person.", "Medical HUD", R.fields["m_stat"]) in list("Stable", "*Watch*", "*Unstable*", "*Insane*", "Cancel")
+ if(!R)
+ return
+ if(!H.canUseHUD())
+ return
+ if(!HAS_TRAIT(H, TRAIT_MEDICAL_HUD))
+ return
+ if(health_status && health_status != "Cancel")
+ R.fields["m_stat"] = health_status
+ return
+ return //Medical HUD ends here.
+
+ if(href_list["hud"] == "s")
+ if(!HAS_TRAIT(H, TRAIT_SECURITY_HUD))
+ return
+ if(usr.stat || usr == src) //|| !usr.canmove || usr.restrained()) Fluff: Sechuds have eye-tracking technology and sets 'arrest' to people that the wearer looks and blinks at.
+ return //Non-fluff: This allows sec to set people to arrest as they get disarmed or beaten
+ // Checks the user has security clearence before allowing them to change arrest status via hud, comment out to enable all access
+ var/allowed_access = null
+ var/obj/item/clothing/glasses/hud/security/G = H.glasses
+ if(istype(G) && (G.obj_flags & EMAGGED))
+ allowed_access = "@%&ERROR_%$*"
+ else //Implant and standard glasses check access
+ if(H.wear_id)
+ var/list/access = H.wear_id.GetAccess()
+ if(ACCESS_SEC_RECORDS in access)
+ allowed_access = H.get_authentification_name()
+
+ if(!allowed_access)
+ to_chat(H, "ERROR: Invalid access.")
+ return
+
+ if(!perpname)
+ to_chat(H, "ERROR: Can not identify target.")
+ return
+ R = find_record("name", perpname, GLOB.data_core.security)
+ if(!R)
+ to_chat(usr, "ERROR: Unable to locate data core entry for target.")
+ return
+ if(href_list["status"])
+ var/setcriminal = input(usr, "Specify a new criminal status for this person.", "Security HUD", R.fields["criminal"]) in list("None", "Arrest", "Search", "Monitor", "Incarcerated", "Paroled", "Discharged", "Cancel")
+ if(setcriminal != "Cancel")
+ if(!R)
+ return
+ if(!H.canUseHUD())
+ return
+ if(!HAS_TRAIT(H, TRAIT_SECURITY_HUD))
+ return
+ investigate_log("[key_name(src)] has been set from [R.fields["criminal"]] to [setcriminal] by [key_name(usr)].", INVESTIGATE_RECORDS)
+ R.fields["criminal"] = setcriminal
+ sec_hud_set_security_status()
+ return
+
+ if(href_list["view"])
+ if(!H.canUseHUD())
+ return
+ if(!HAS_TRAIT(H, TRAIT_SECURITY_HUD))
+ return
+ to_chat(usr, "Name: [R.fields["name"]] Criminal Status: [R.fields["criminal"]]")
+ for(var/datum/data/crime/c in R.fields["crim"])
+ to_chat(usr, "Crime: [c.crimeName]")
+ if (c.crimeDetails)
+ to_chat(usr, "Details: [c.crimeDetails]")
+ else
+ to_chat(usr, "Details:\[Add details]")
+ to_chat(usr, "Added by [c.author] at [c.time]")
+ to_chat(usr, "----------")
+ to_chat(usr, "Notes: [R.fields["notes"]]")
+ return
+
+ if(href_list["add_citation"])
+ var/maxFine = CONFIG_GET(number/maxfine)
+ var/t1 = stripped_input("Please input citation crime:", "Security HUD", "", null)
+ var/fine = FLOOR(input("Please input citation fine, up to [maxFine]:", "Security HUD", 50) as num|null, 1)
+ if(!R || !t1 || !fine || !allowed_access)
+ return
+ if(!H.canUseHUD())
+ return
+ if(!HAS_TRAIT(H, TRAIT_SECURITY_HUD))
+ return
+ if(fine < 0)
+ to_chat(usr, "You're pretty sure that's not how money works.")
+ return
+ fine = min(fine, maxFine)
+
+ var/crime = GLOB.data_core.createCrimeEntry(t1, "", allowed_access, station_time_timestamp(), fine)
+ for (var/obj/item/pda/P in GLOB.PDAs)
+ if(P.owner == R.fields["name"])
+ var/message = "You have been fined [fine] credits for '[t1]'. Fines may be paid at security."
+ var/datum/signal/subspace/messaging/pda/signal = new(src, list(
+ "name" = "Security Citation",
+ "job" = "Citation Server",
+ "message" = message,
+ "targets" = list("[P.owner] ([P.ownjob])"),
+ "automated" = 1
+ ))
+ signal.send_to_receivers()
+ usr.log_message("(PDA: Citation Server) sent \"[message]\" to [signal.format_target()]", LOG_PDA)
+ GLOB.data_core.addCitation(R.fields["id"], crime)
+ investigate_log("New Citation: [t1] Fine: [fine] | Added to [R.fields["name"]] by [key_name(usr)]", INVESTIGATE_RECORDS)
+ return
+ if(href_list["add_crime"])
+ var/t1 = stripped_input("Please input crime name:", "Security HUD", "", null)
+ if(!R || !t1 || !allowed_access)
+ return
+ if(!H.canUseHUD())
+ return
+ if(!HAS_TRAIT(H, TRAIT_SECURITY_HUD))
+ return
+ var/crime = GLOB.data_core.createCrimeEntry(t1, null, allowed_access, station_time_timestamp())
+ GLOB.data_core.addCrime(R.fields["id"], crime)
+ investigate_log("New Crime: [t1] | Added to [R.fields["name"]] by [key_name(usr)]", INVESTIGATE_RECORDS)
+ to_chat(usr, "Successfully added a crime.")
+ return
+
+ if(href_list["add_details"])
+ var/t1 = stripped_input(usr, "Please input crime details:", "Secure. records", "", null)
+ if(!R || !t1 || !allowed_access)
+ return
+ if(!H.canUseHUD())
+ return
+ if(!HAS_TRAIT(H, TRAIT_SECURITY_HUD))
+ return
+ if(href_list["cdataid"])
+ GLOB.data_core.addCrimeDetails(R.fields["id"], href_list["cdataid"], t1)
+ investigate_log("New Crime details: [t1] | Added to [R.fields["name"]] by [key_name(usr)]", INVESTIGATE_RECORDS)
+ to_chat(usr, "Successfully added details.")
+ return
+
+ if(href_list["view_comment"])
+ if(!H.canUseHUD())
+ return
+ if(!HAS_TRAIT(H, TRAIT_SECURITY_HUD))
+ return
+ to_chat(usr, "Comments/Log:")
+ var/counter = 1
+ while(R.fields[text("com_[]", counter)])
+ to_chat(usr, R.fields[text("com_[]", counter)])
+ to_chat(usr, "----------")
+ counter++
+ return
+
+ if(href_list["add_comment"])
+ var/t1 = stripped_multiline_input("Add Comment:", "Secure. records", null, null)
+ if (!R || !t1 || !allowed_access)
+ return
+ if(!H.canUseHUD())
+ return
+ if(!HAS_TRAIT(H, TRAIT_SECURITY_HUD))
+ return
+ var/counter = 1
+ while(R.fields[text("com_[]", counter)])
+ counter++
+ R.fields[text("com_[]", counter)] = text("Made by [] on [] [], [] []", allowed_access, station_time_timestamp(), time2text(world.realtime, "MMM DD"), GLOB.year_integer+540, t1)
+ to_chat(usr, "Successfully added comment.")
+ return
..() //end of this massive fucking chain. TODO: make the hud chain not spooky.
@@ -534,21 +547,34 @@
// If targeting anything else, see if the wear suit is thin enough.
if (!penetrate_thick)
if(above_neck(target_zone))
- if(head && istype(head, /obj/item/clothing))
- var/obj/item/clothing/CH = head
- if (CH.clothing_flags & THICKMATERIAL)
- . = 0
- else
- if(wear_suit && istype(wear_suit, /obj/item/clothing))
- var/obj/item/clothing/CS = wear_suit
- if (CS.clothing_flags & THICKMATERIAL)
- . = 0
- if(!. && error_msg && user)
- // Might need re-wording.
- to_chat(user, "There is no exposed flesh or thin material [above_neck(target_zone) ? "on [p_their()] head" : "on [p_their()] body"].")
-
-/mob/living/carbon/human/assess_threat(judgement_criteria, lasercolor = "", datum/callback/weaponcheck=null)
- if(judgement_criteria & JUDGE_EMAGGED)
+ if(head && isclothing(head))
+ var/obj/item/clothing/head/CH = head
+ if(CH.clothing_flags & THICKMATERIAL)
+ balloon_alert(user, "There is no exposed flesh on [p_their()] head")
+ return FALSE
+ if(wear_suit && isclothing(wear_suit))
+ var/obj/item/clothing/suit/CS = wear_suit
+ if(CS.clothing_flags & THICKMATERIAL)
+ switch(target_zone)
+ if(BODY_ZONE_CHEST)
+ if(CS.body_parts_covered & CHEST)
+ balloon_alert(user, "There is no exposed flesh on this chest")
+ return FALSE
+ if(BODY_ZONE_PRECISE_GROIN)
+ if(CS.body_parts_covered & GROIN)
+ balloon_alert(user, "There is no exposed flesh on this groin")
+ return FALSE
+ if(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)
+ if(CS.body_parts_covered & ARMS)
+ balloon_alert(user, "There is no exposed flesh on these arms")
+ return FALSE
+ if(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
+ if(CS.body_parts_covered & LEGS)
+ balloon_alert(user, "There is no exposed flesh on these legs")
+ return FALSE
+
+/mob/living/carbon/human/assess_threat(judgment_criteria, lasercolor = "", datum/callback/weaponcheck=null)
+ if(judgment_criteria & JUDGE_EMAGGED)
return 10 //Everyone is a criminal!
var/threatcount = 0
@@ -575,11 +601,11 @@
//Check for ID
var/obj/item/card/id/idcard = get_idcard(FALSE)
- if( (judgement_criteria & JUDGE_IDCHECK) && !idcard && name=="Unknown")
+ if( (judgment_criteria & JUDGE_IDCHECK) && !idcard && name=="Unknown")
threatcount += 4
//Check for weapons
- if( (judgement_criteria & JUDGE_WEAPONCHECK) && weaponcheck)
+ if( (judgment_criteria & JUDGE_WEAPONCHECK) && weaponcheck)
if(!idcard || !(ACCESS_WEAPONS in idcard.access))
for(var/obj/item/I in held_items) //if they're holding a gun
if(weaponcheck.Invoke(I))
@@ -588,17 +614,21 @@
threatcount += 2 //not enough to trigger look_for_perp() on it's own unless they also have criminal status.
//Check for arrest warrant
- if(judgement_criteria & JUDGE_RECORDCHECK)
+ if(judgment_criteria & JUDGE_RECORDCHECK)
var/perpname = get_face_name(get_id_name())
var/datum/data/record/R = find_record("name", perpname, GLOB.data_core.security)
if(R && R.fields["criminal"])
switch(R.fields["criminal"])
- if("*Arrest*")
+ if("Arrest")
threatcount += 5
if("Incarcerated")
threatcount += 2
if("Paroled")
threatcount += 2
+ if("Monitor")
+ threatcount += 1
+ if("Search")
+ threatcount += 2
//Check for dresscode violations
if(istype(head, /obj/item/clothing/head/wizard) || istype(head, /obj/item/clothing/head/helmet/space/hardsuit/wizard))
@@ -617,7 +647,7 @@
threatcount -= 5
//individuals wearing tinfoil hats are 30% more likely to be criminals
- if(istype(get_item_by_slot(SLOT_HEAD), /obj/item/clothing/head/foilhat))
+ if(istype(get_item_by_slot(ITEM_SLOT_HEAD), /obj/item/clothing/head/foilhat))
threatcount += 2
return threatcount
@@ -693,6 +723,8 @@
dropItemToGround(I)
/mob/living/carbon/human/proc/clean_blood(datum/source, strength)
+ SIGNAL_HANDLER
+
if(strength < CLEAN_STRENGTH_BLOOD)
return
if(gloves)
@@ -781,7 +813,7 @@
hud_used.healthdoll.icon_state = "healthdoll_OVERLAY"
for(var/X in bodyparts)
var/obj/item/bodypart/BP = X
- var/damage = BP.burn_dam + BP.brute_dam
+ var/damage = BP.burn_dam + BP.brute_dam + (hallucination ? BP.stamina_dam : 0)
var/comparison = (BP.max_damage/5)
var/icon_num = 0
if(damage)
@@ -798,6 +830,12 @@
icon_num = 0
if(icon_num)
hud_used.healthdoll.add_overlay(mutable_appearance('icons/mob/screen_gen.dmi', "[BP.body_zone][icon_num]"))
+ //Stamina Outline (Communicate that we have stamina damage)
+ //Hallucinations will appear as regular damage
+ if(BP.stamina_dam && !hallucination)
+ var/mutable_appearance/MA = mutable_appearance('icons/mob/screen_gen.dmi', "[BP.body_zone]stam")
+ MA.alpha = (BP.stamina_dam / BP.max_stamina_damage) * 70 + 30
+ hud_used.healthdoll.add_overlay(MA)
for(var/t in get_missing_limbs()) //Missing limbs
hud_used.healthdoll.add_overlay(mutable_appearance('icons/mob/screen_gen.dmi', "[t]6"))
for(var/t in get_disabled_limbs()) //Disabled limbs
@@ -953,7 +991,7 @@
. = ..()
if(ishuman(over))
var/mob/living/carbon/human/T = over // curbstomp, ported from PP with modifications
- if(!src.is_busy && (src.zone_selected == BODY_ZONE_HEAD || src.zone_selected == BODY_ZONE_PRECISE_GROIN) && get_turf(src) == get_turf(T) && !(T.mobility_flags & MOBILITY_STAND) && src.a_intent != INTENT_HELP) //all the stars align, time to curbstomp
+ if(!src.is_busy && (src.zone_selected == BODY_ZONE_HEAD || src.zone_selected == BODY_ZONE_PRECISE_GROIN) && get_turf(src) == get_turf(T) && !(T.mobility_flags & MOBILITY_STAND) && src.a_intent != INTENT_HELP && !HAS_TRAIT(src, TRAIT_PACIFISM)) //all the stars align, time to curbstomp
src.is_busy = TRUE
if (!do_mob(src,T,25) || get_turf(src) != get_turf(T) || (T.mobility_flags & MOBILITY_STAND) || src.a_intent == INTENT_HELP || src == T) //wait 30ds and make sure the stars still align (Body zone check removed after PR #958)
@@ -1023,18 +1061,35 @@
src.is_busy = FALSE
+/mob/living/carbon/human/limb_attack_self()
+ var/obj/item/bodypart/arm = hand_bodyparts[active_hand_index]
+ if(arm)
+ arm.attack_self(src)
+ return ..()
+
+
//src is the user that will be carrying, target is the mob to be carried
/mob/living/carbon/human/proc/can_piggyback(mob/living/carbon/target)
return (istype(target) && target.stat == CONSCIOUS && (target.mobility_flags & MOBILITY_STAND))
/mob/living/carbon/human/proc/can_be_firemanned(mob/living/carbon/target)
- return (ishuman(target) && !(target.mobility_flags & MOBILITY_STAND))
+ return ((ishuman(target) || ismonkey(target)) && !(target.mobility_flags & MOBILITY_STAND))
/mob/living/carbon/human/proc/fireman_carry(mob/living/carbon/target)
- if(can_be_firemanned(target))
- visible_message("[src] starts lifting [target] onto their back.",
- "You start lifting [target] onto your back.")
- if(do_after(src, 50, TRUE, target))
+ var/carrydelay = 50 //if you have latex you are faster at grabbing
+ var/skills_space = "" //cobby told me to do this
+ if(HAS_TRAIT(src, TRAIT_QUICKER_CARRY))
+ carrydelay = 30
+ skills_space = " expertly"
+ else if(HAS_TRAIT(src, TRAIT_QUICK_CARRY))
+ carrydelay = 40
+ skills_space = " quickly"
+ if(can_be_firemanned(target) && !incapacitated(FALSE, TRUE))
+ visible_message("[src] starts[skills_space] lifting [target] onto their back..",
+ //Joe Medic starts quickly/expertly lifting Grey Tider onto their back..
+ "[HAS_TRAIT(src, TRAIT_QUICKER_CARRY) ? "Using your gloves' nanochips, you" : "You"][skills_space] start to lift [target] onto your back[HAS_TRAIT(src, TRAIT_QUICK_CARRY) ? ", while assisted by the nanochips in your gloves..." : "..."]")
+ //(Using your gloves' nanochips, you/You) ( /quickly/expertly) start to lift Grey Tider onto your back(, while assisted by the nanochips in your gloves../...)
+ if(do_after(src, carrydelay, TRUE, target))
//Second check to make sure they're still valid to be carried
if(can_be_firemanned(target) && !incapacitated(FALSE, TRUE) && !target.buckled)
buckle_mob(target, TRUE, TRUE, 90, 1, 0)
@@ -1057,6 +1112,7 @@
else
to_chat(target, "You can't piggyback ride [src] right now!")
+
/mob/living/carbon/human/buckle_mob(mob/living/target, force = FALSE, check_loc = TRUE, lying_buckle = FALSE, hands_needed = 0, target_hands_needed = 0)
if(!force)//humans are only meant to be ridden through piggybacking and special cases
return
@@ -1093,7 +1149,7 @@
/mob/living/carbon/human/proc/is_shove_knockdown_blocked() //If you want to add more things that block shove knockdown, extend this
var/list/body_parts = list(head, wear_mask, wear_suit, w_uniform, back, gloves, shoes, belt, s_store, glasses, ears, wear_id) //Everything but pockets. Pockets are l_store and r_store. (if pockets were allowed, putting something armored, gloves or hats for example, would double up on the armor)
for(var/bp in body_parts)
- if(istype(bp, /obj/item/clothing))
+ if(isclothing(bp))
var/obj/item/clothing/C = bp
if(C.blocks_shove_knockdown)
return TRUE
@@ -1105,10 +1161,6 @@
if(is_type_in_typecache(active_item, GLOB.shove_disarming_types))
visible_message("[src.name] regains their grip on \the [active_item]!", "You regain your grip on \the [active_item].", null, COMBAT_MESSAGE_RANGE)
-/mob/living/carbon/human/do_after_coefficent()
- . = ..()
- . *= physiology.do_after_speed
-
/mob/living/carbon/human/updatehealth()
. = ..()
dna?.species.spec_updatehealth(src)
@@ -1128,13 +1180,49 @@
/mob/living/carbon/human/adjust_nutrition(var/change) //Honestly FUCK the oldcoders for putting nutrition on /mob someone else can move it up because holy hell I'd have to fix SO many typechecks
if(HAS_TRAIT(src, TRAIT_NOHUNGER))
return FALSE
+ if(HAS_TRAIT(src, TRAIT_POWERHUNGRY))
+ var/obj/item/organ/stomach/battery/battery = getorganslot(ORGAN_SLOT_STOMACH)
+ if(istype(battery))
+ battery.adjust_charge_scaled(change)
+ return FALSE
return ..()
/mob/living/carbon/human/set_nutrition(var/change) //Seriously fuck you oldcoders.
if(HAS_TRAIT(src, TRAIT_NOHUNGER))
return FALSE
+ if(HAS_TRAIT(src, TRAIT_POWERHUNGRY))
+ var/obj/item/organ/stomach/battery/battery = getorganslot(ORGAN_SLOT_STOMACH)
+ if(istype(battery))
+ battery.set_charge_scaled(change)
+ return FALSE
return ..()
+/mob/living/carbon/human/ZImpactDamage(turf/T, levels)
+ //Non cat-people smash into the ground
+ if(!iscatperson(src))
+ return ..()
+ //Check to make sure legs are working
+ var/obj/item/bodypart/left_leg = get_bodypart(BODY_ZONE_L_LEG)
+ var/obj/item/bodypart/right_leg = get_bodypart(BODY_ZONE_R_LEG)
+ if(!left_leg || !right_leg || left_leg.disabled || right_leg.disabled)
+ return ..()
+ //Nailed it!
+ visible_message("[src] lands elegantly on [p_their()] feet!",
+ "You fall [levels] level[levels > 1 ? "s" : ""] into [T], perfecting the landing!")
+ Stun(levels * 50)
+
+/mob/living/carbon/human/proc/stub_toe(var/power)
+ if(HAS_TRAIT(src, TRAIT_LIGHT_STEP))
+ power *= 0.5
+ src.emote("gasp")
+ else
+ src.emote("scream")
+ src.apply_damage(power, BRUTE, def_zone = pick(BODY_ZONE_PRECISE_R_FOOT, BODY_ZONE_PRECISE_L_FOOT))
+ src.Paralyze(10 * power)
+
+/mob/living/carbon/human/monkeybrain
+ ai_controller = /datum/ai_controller/monkey
+
/mob/living/carbon/human/species
var/race = null
@@ -1148,12 +1236,18 @@
/mob/living/carbon/human/species/android
race = /datum/species/android
+/mob/living/carbon/human/species/apid
+ race = /datum/species/apid
+
/mob/living/carbon/human/species/corporate
race = /datum/species/corporate
/mob/living/carbon/human/species/dullahan
race = /datum/species/dullahan
+/mob/living/carbon/human/species/ethereal
+ race = /datum/species/ethereal
+
/mob/living/carbon/human/species/felinid
race = /datum/species/human/felinid
@@ -1250,9 +1344,15 @@
/mob/living/carbon/human/species/golem/soviet
race = /datum/species/golem/soviet
+/mob/living/carbon/human/species/ipc
+ race = /datum/species/ipc
+
/mob/living/carbon/human/species/jelly
race = /datum/species/jelly
+/mob/living/carbon/human/species/oozeling
+ race = /datum/species/oozeling
+
/mob/living/carbon/human/species/jelly/slime
race = /datum/species/jelly/slime
@@ -1265,18 +1365,12 @@
/mob/living/carbon/human/species/lizard
race = /datum/species/lizard
-/mob/living/carbon/human/species/ethereal
- race = /datum/species/ethereal
-
/mob/living/carbon/human/species/lizard/ashwalker
race = /datum/species/lizard/ashwalker
/mob/living/carbon/human/species/moth
race = /datum/species/moth
-/mob/living/carbon/human/species/apid
- race = /datum/species/apid
-
/mob/living/carbon/human/species/mush
race = /datum/species/mush
@@ -1295,12 +1389,6 @@
/mob/living/carbon/human/species/skeleton
race = /datum/species/skeleton
-/mob/living/carbon/human/species/synth
- race = /datum/species/synth
-
-/mob/living/carbon/human/species/synth/military
- race = /datum/species/synth/military
-
/mob/living/carbon/human/species/supersoldier
race = /datum/species/human/supersoldier
diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm
index ec05c9a1e8477..5c38743f36e10 100644
--- a/code/modules/mob/living/carbon/human/human_defense.dm
+++ b/code/modules/mob/living/carbon/human/human_defense.dm
@@ -28,10 +28,10 @@
for(var/bp in body_parts)
if(!bp)
continue
- if(bp && istype(bp , /obj/item/clothing))
+ if(bp && isclothing(bp))
var/obj/item/clothing/C = bp
if(C.body_parts_covered & def_zone.body_part)
- protection += C.armor.getRating(d_type)
+ protection += C.get_armor_rating(d_type, src)
protection += physiology.armor.getRating(d_type)
return protection
@@ -49,7 +49,7 @@
if(mind)
if(mind.martial_art && !incapacitated(FALSE, TRUE) && mind.martial_art.can_use(src) && mind.martial_art.deflection_chance) //Some martial arts users can deflect projectiles!
if(prob(mind.martial_art.deflection_chance))
- if((mobility_flags & MOBILITY_USE) && dna && !dna.check_mutation(HULK)) //But only if they're otherwise able to use items, and hulks can't do it
+ if((mobility_flags & MOBILITY_USE) && dna && !dna.check_mutation(HULK) && !P.martial_arts_no_deflect) //But only if they're otherwise able to use items, and hulks can't do it. Also damageless weapons are not deflected.
if(!isturf(loc)) //if we're inside something and still got hit
P.force_hit = TRUE //The thing we're in passed the bullet to us. Pass it back, and tell it to take the damage.
loc.bullet_act(P)
@@ -111,7 +111,7 @@
/mob/living/carbon/human/proc/check_shields(atom/AM, var/damage, attack_text = "the attack", attack_type = MELEE_ATTACK, armour_penetration = 0)
for(var/obj/item/I in held_items)
- if(!istype(I, /obj/item/clothing))
+ if(!isclothing(I))
if(I.hit_reaction(src, AM, attack_text, damage, attack_type))
I.on_block(src, AM, attack_text, damage, attack_type)
return 1
@@ -142,28 +142,14 @@
if(istype(AM, /obj/item))
I = AM
throwpower = I.throwforce
- if(I.thrownby == src) //No throwing stuff at yourself to trigger hit reactions
+ if(I.thrownby == WEAKREF(src)) //No throwing stuff at yourself to trigger hit reactions
return ..()
if(check_shields(AM, throwpower, "\the [AM.name]", THROWN_PROJECTILE_ATTACK))
hitpush = FALSE
skipcatch = TRUE
blocked = TRUE
- else if(I)
- if(((throwingdatum ? throwingdatum.speed : I.throw_speed) >= EMBED_THROWSPEED_THRESHOLD) || I.embedding.embedded_ignore_throwspeed_threshold)
- if(can_embed(I))
- if(prob(I.embedding.embed_chance) && !HAS_TRAIT(src, TRAIT_PIERCEIMMUNE))
- throw_alert("embeddedobject", /obj/screen/alert/embeddedobject)
- var/obj/item/bodypart/L = pick(bodyparts)
- L.embedded_objects |= I
- I.add_mob_blood(src)//it embedded itself in you, of course it's bloody!
- I.forceMove(src)
- L.receive_damage(I.w_class*I.embedding.embedded_impact_pain_multiplier)
- visible_message("[I] embeds itself in [src]'s [L.name]!","[I] embeds itself in your [L.name]!")
- SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "embedded", /datum/mood_event/embedded)
- hitpush = FALSE
- skipcatch = TRUE //can't catch the now embedded item
-
- return ..()
+
+ return ..(AM, skipcatch, hitpush, blocked, throwingdatum)
/mob/living/carbon/human/grippedby(mob/living/user, instant = FALSE)
if(w_uniform)
@@ -219,7 +205,7 @@
return
if(ishuman(user))
var/mob/living/carbon/human/H = user
- dna.species.spec_attack_hand(H, src)
+ H.dna.species.spec_attack_hand(H, src)
/mob/living/carbon/human/attack_paw(mob/living/carbon/monkey/M)
if(check_shields(M, 0, "the M.name", UNARMED_ATTACK))
@@ -237,7 +223,7 @@
if(M.a_intent == INTENT_DISARM) //the fact that this fucking works is hilarious to me
dna.species.disarm(M, src)
return 1
-
+
if(M.limb_destroyer)
dismembering_strike(M, affecting.body_zone)
@@ -321,6 +307,9 @@
if(M.is_adult)
damage = 30
+ if(M.transformeffects & SLIME_EFFECT_RED)
+ damage *= 1.1
+
if(check_shields(M, damage, "the [M.name]"))
return 0
@@ -388,9 +377,14 @@
switch (severity)
if (EXPLODE_DEVASTATE)
if(bomb_armor < EXPLODE_GIB_THRESHOLD) //gibs the mob if their bomb armor is lower than EXPLODE_GIB_THRESHOLD
- for(var/I in contents)
- var/atom/A = I
- A.ex_act(severity)
+ for(var/thing in contents)
+ switch(severity)
+ if(EXPLODE_DEVASTATE)
+ SSexplosions.high_mov_atom += thing
+ if(EXPLODE_HEAVY)
+ SSexplosions.med_mov_atom += thing
+ if(EXPLODE_LIGHT)
+ SSexplosions.low_mov_atom += thing
gib()
return
else
@@ -420,11 +414,19 @@
apply_damage(burn_loss, BURN, blocked = (bomb_armor * 0.6))
//attempt to dismember bodyparts
- if(severity >= 2) //don't dismember from light explosions
- var/max_limb_loss = round(4/severity) //so you don't lose four limbs at severity 3.
+ if(severity >= EXPLODE_HEAVY || !bomb_armor)
+ var/max_limb_loss = 0
+ var/probability = 0
+ switch(severity)
+ if(EXPLODE_HEAVY)
+ max_limb_loss = 3
+ probability = 40
+ if(EXPLODE_DEVASTATE)
+ max_limb_loss = 4
+ probability = 50
for(var/X in bodyparts)
var/obj/item/bodypart/BP = X
- if(prob(50/severity) && !prob(getarmor(BP, "bomb")) && BP.body_zone != BODY_ZONE_HEAD && BP.body_zone != BODY_ZONE_CHEST)
+ if(prob(probability) && !prob(getarmor(BP, "bomb")) && BP.body_zone != BODY_ZONE_HEAD && BP.body_zone != BODY_ZONE_CHEST)
BP.brute_dam = BP.max_damage
BP.dismember()
max_limb_loss--
@@ -501,7 +503,7 @@
if(2)
L.receive_damage(0,5)
Paralyze(100)
- if((TRAIT_EASYDISMEMBER in L.owner.dna.species.species_traits) && L.body_zone != "chest")
+ if(HAS_TRAIT(L, TRAIT_EASYDISMEMBER) && L.body_zone != "chest")
if(prob(20))
L.dismember(BRUTE)
@@ -659,7 +661,7 @@
gain = 100
if(mind.assigned_role == "Clown")
gain = rand(-1000, 1000)
- investigate_log("([key_name(src)]) has been consumed by the singularity.", INVESTIGATE_SINGULO) //Oh that's where the clown ended up!
+ investigate_log("([key_name(src)]) has been consumed by the singularity.", INVESTIGATE_ENGINES) //Oh that's where the clown ended up!
gib()
return(gain)
@@ -688,12 +690,15 @@
..()
-/mob/living/carbon/human/proc/check_self_for_injuries()
+/mob/living/carbon/human/check_self_for_injuries()
if(stat == DEAD || stat == UNCONSCIOUS)
return
visible_message("[src] examines [p_them()]self.", \
"You check yourself for injuries.")
+ var/list/harm_descriptors = dna?.species.get_harm_descriptors()
+ harm_descriptors ||= list("bleed" = "bleeding")
+ var/bleed_msg = harm_descriptors["bleed"]
var/list/missing = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
for(var/X in bodyparts)
@@ -741,13 +746,16 @@
to_chat(src, "\t Your [LB.name] [HAS_TRAIT(src, TRAIT_SELF_AWARE) ? "has" : "is"] [status].")
for(var/obj/item/I in LB.embedded_objects)
- to_chat(src, "\t There is \a [I] embedded in your [LB.name]!")
+ if(I.isEmbedHarmless())
+ to_chat(src, "\t There is \a [I] stuck to your [LB.name]!")
+ else
+ to_chat(src, "\t There is \a [I] embedded in your [LB.name]!")
for(var/t in missing)
to_chat(src, "Your [parse_zone(t)] is missing!")
if(bleed_rate)
- to_chat(src, "You are bleeding!")
+ to_chat(src, "You are [bleed_msg]!")
if(getStaminaLoss())
if(getStaminaLoss() > 30)
to_chat(src, "You're completely exhausted.")
@@ -769,7 +777,7 @@
else if(oxyloss > 30)
to_chat(src, "You're choking!")
- if(!HAS_TRAIT(src, TRAIT_NOHUNGER))
+ if(!HAS_TRAIT(src, TRAIT_NOHUNGER) && !HAS_TRAIT(src, TRAIT_POWERHUNGRY))
switch(nutrition)
if(NUTRITION_LEVEL_FULL to INFINITY)
to_chat(src, "You're completely stuffed!")
@@ -888,7 +896,7 @@
for(var/obj/item/I in torn_items)
I.take_damage(damage_amount, damage_type, damage_flag, 0)
-
+
/mob/living/carbon/human/proc/blockbreak()
to_chat(src, "Your block was broken!")
ADD_TRAIT(src, TRAIT_NOBLOCK, type)
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index 7e44ab789af28..300d12f0b1610 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -6,6 +6,7 @@
can_buckle = TRUE
buckle_lying = FALSE
mob_biotypes = list(MOB_ORGANIC, MOB_HUMANOID)
+ blocks_emissive = EMISSIVE_BLOCK_UNIQUE
//Hair colour and style
var/hair_color = "000"
var/hair_style = "Bald"
@@ -29,6 +30,7 @@
var/undershirt = "Nude" //Which undershirt the player wants
var/socks = "Nude" //Which socks the player wants
var/backbag = DBACKPACK //Which backpack type the player has chosen.
+ var/jumpsuit_style = PREF_SUIT //suit/skirt
//Equipment slots
var/obj/item/clothing/wear_suit = null
@@ -53,7 +55,7 @@
var/list/datum/bioware = list()
var/creamed = FALSE //to use with creampie overlays
- var/static/list/can_ride_typecache = typecacheof(list(/mob/living/carbon/human, /mob/living/simple_animal/slime, /mob/living/simple_animal/parrot))
+ var/static/list/can_ride_typecache = typecacheof(list(/mob/living/carbon/human, /mob/living/simple_animal/slime, /mob/living/simple_animal/parrot, /mob/living/carbon/monkey))
var/lastpuke = 0
var/last_fire_update
var/account_id
diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm
index 014d8da338e89..01ab15927b1e4 100644
--- a/code/modules/mob/living/carbon/human/human_helpers.dm
+++ b/code/modules/mob/living/carbon/human/human_helpers.dm
@@ -76,11 +76,10 @@
else if(istype(tablet))
var/obj/item/computer_hardware/card_slot/card_slot = tablet.all_components[MC_CARD]
if(card_slot && (card_slot.stored_card2 || card_slot.stored_card))
- if(card_slot.stored_card2) //The second card is the one used for authorization in the ID changing program, so we prioritize it here for consistency
+ if(card_slot.stored_card2?.registered_name) //The second card is the one used for authorization in the ID changing program, so we prioritize it here for consistency
. = card_slot.stored_card2.registered_name
- else
- if(card_slot.stored_card)
- . = card_slot.stored_card.registered_name
+ else if(card_slot.stored_card?.registered_name)
+ . = card_slot.stored_card.registered_name
if(!.)
. = if_no_id //to prevent null-names making the mob unclickable
return
@@ -181,3 +180,14 @@
return account
return FALSE
+
+/mob/living/carbon/human/can_see_reagents()
+ . = ..()
+ if(.) //No need to run through all of this if it's already true.
+ return
+ if(isclothing(glasses) && (glasses.clothing_flags & SCAN_REAGENTS))
+ return TRUE
+ if(isclothing(head) && (head.clothing_flags & SCAN_REAGENTS))
+ return TRUE
+ if(isclothing(wear_mask) && (wear_mask.clothing_flags & SCAN_REAGENTS))
+ return TRUE
diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm
index 3a3e54ea2fa21..9c15b7952c2dd 100644
--- a/code/modules/mob/living/carbon/human/human_movement.dm
+++ b/code/modules/mob/living/carbon/human/human_movement.dm
@@ -18,7 +18,7 @@
if (!(lube&GALOSHES_DONT_HELP))
if(HAS_TRAIT(src, TRAIT_NOSLIPWATER))
return 0
- if(shoes && istype(shoes, /obj/item/clothing))
+ if(shoes && isclothing(shoes))
var/obj/item/clothing/CS = shoes
if (CS.clothing_flags & NOSLIP)
return 0
@@ -29,20 +29,17 @@
playsound_local(null, 'sound/effects/space_wind_big.ogg', CLAMP(pressure_difference / 50, 10, 100), 1)
else
playsound_local(null, 'sound/effects/space_wind.ogg', CLAMP(pressure_difference, 10, 100), 1)
- if(shoes && istype(shoes, /obj/item/clothing))
+ if(shoes && isclothing(shoes))
var/obj/item/clothing/S = shoes
if((S.clothing_flags & NOSLIP))
return 0
return ..()
-/mob/living/carbon/human/mob_has_gravity()
- . = ..()
- if(!.)
- if(mob_negates_gravity())
- . = 1
+/mob/living/carbon/human/has_gravity(turf/T)
+ return ..() || mob_negates_gravity()
/mob/living/carbon/human/mob_negates_gravity()
- return ((shoes && shoes.negates_gravity()) || (dna.species.negates_gravity(src)))
+ return ((shoes && shoes.negates_gravity()) || (dna?.species?.negates_gravity(src)))
/mob/living/carbon/human/Move(NewLoc, direct)
. = ..()
diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm
index 36af888930939..274b10f6c521e 100644
--- a/code/modules/mob/living/carbon/human/inventory.dm
+++ b/code/modules/mob/living/carbon/human/inventory.dm
@@ -4,39 +4,39 @@
// Return the item currently in the slot ID
/mob/living/carbon/human/get_item_by_slot(slot_id)
switch(slot_id)
- if(SLOT_BACK)
+ if(ITEM_SLOT_BACK)
return back
- if(SLOT_WEAR_MASK)
+ if(ITEM_SLOT_MASK)
return wear_mask
- if(SLOT_NECK)
+ if(ITEM_SLOT_NECK)
return wear_neck
- if(SLOT_HANDCUFFED)
+ if(ITEM_SLOT_HANDCUFFED)
return handcuffed
- if(SLOT_LEGCUFFED)
+ if(ITEM_SLOT_LEGCUFFED)
return legcuffed
- if(SLOT_BELT)
+ if(ITEM_SLOT_BELT)
return belt
- if(SLOT_WEAR_ID)
+ if(ITEM_SLOT_ID)
return wear_id
- if(SLOT_EARS)
+ if(ITEM_SLOT_EARS)
return ears
- if(SLOT_GLASSES)
+ if(ITEM_SLOT_EYES)
return glasses
- if(SLOT_GLOVES)
+ if(ITEM_SLOT_GLOVES)
return gloves
- if(SLOT_HEAD)
+ if(ITEM_SLOT_HEAD)
return head
- if(SLOT_SHOES)
+ if(ITEM_SLOT_FEET)
return shoes
- if(SLOT_WEAR_SUIT)
+ if(ITEM_SLOT_OCLOTHING)
return wear_suit
- if(SLOT_W_UNIFORM)
+ if(ITEM_SLOT_ICLOTHING)
return w_uniform
- if(SLOT_L_STORE)
+ if(ITEM_SLOT_LPOCKET)
return l_store
- if(SLOT_R_STORE)
+ if(ITEM_SLOT_RPOCKET)
return r_store
- if(SLOT_S_STORE)
+ if(ITEM_SLOT_SUITSTORE)
return s_store
return null
@@ -84,17 +84,17 @@
var/not_handled = FALSE //Added in case we make this type path deeper one day
switch(slot)
- if(SLOT_BELT)
+ if(ITEM_SLOT_BELT)
belt = I
update_inv_belt()
- if(SLOT_WEAR_ID)
+ if(ITEM_SLOT_ID)
wear_id = I
sec_hud_set_ID()
update_inv_wear_id()
- if(SLOT_EARS)
+ if(ITEM_SLOT_EARS)
ears = I
update_inv_ears()
- if(SLOT_GLASSES)
+ if(ITEM_SLOT_EYES)
glasses = I
var/obj/item/clothing/glasses/G = I
if(G.glass_colour_type)
@@ -107,13 +107,13 @@
if(G.vision_flags || G.darkness_view || G.invis_override || G.invis_view || !isnull(G.lighting_alpha))
update_sight()
update_inv_glasses()
- if(SLOT_GLOVES)
+ if(ITEM_SLOT_GLOVES)
gloves = I
update_inv_gloves()
- if(SLOT_SHOES)
+ if(ITEM_SLOT_FEET)
shoes = I
update_inv_shoes()
- if(SLOT_WEAR_SUIT)
+ if(ITEM_SLOT_OCLOTHING)
wear_suit = I
if(I.flags_inv & HIDEJUMPSUIT)
update_inv_w_uniform()
@@ -121,17 +121,17 @@
stop_pulling() //can't pull if restrained
update_action_buttons_icon() //certain action buttons will no longer be usable.
update_inv_wear_suit()
- if(SLOT_W_UNIFORM)
+ if(ITEM_SLOT_ICLOTHING)
w_uniform = I
update_suit_sensors()
update_inv_w_uniform()
- if(SLOT_L_STORE)
+ if(ITEM_SLOT_LPOCKET)
l_store = I
update_inv_pockets()
- if(SLOT_R_STORE)
+ if(ITEM_SLOT_RPOCKET)
r_store = I
update_inv_pockets()
- if(SLOT_S_STORE)
+ if(ITEM_SLOT_SUITSTORE)
s_store = I
update_inv_s_store()
else
@@ -143,9 +143,9 @@
return not_handled //For future deeper overrides
-/mob/living/carbon/human/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE, silent = FALSE)
+/mob/living/carbon/human/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE, silent = FALSE, was_thrown = FALSE)
var/index = get_held_index_of_item(I)
- . = ..() //See mob.dm for an explanation on this and some rage about people copypasting instead of calling ..() like they should.
+ . = ..(I, force, newloc, no_move, invdrop, was_thrown) //See mob.dm for an explanation on this and some rage about people copypasting instead of calling ..() like they should.
if(!. || !I)
return
if(index && !QDELETED(src) && dna.species.mutanthands) //hand freed, fill with claws, skip if we're getting deleted.
@@ -188,7 +188,7 @@
update_tint()
if(G.vision_correction)
if(HAS_TRAIT(src, TRAIT_NEARSIGHT))
- overlay_fullscreen("nearsighted", /obj/screen/fullscreen/impaired, 1)
+ overlay_fullscreen("nearsighted", /atom/movable/screen/fullscreen/impaired, 1)
if(G.vision_flags || G.darkness_view || G.invis_override || G.invis_view || !isnull(G.lighting_alpha))
update_sight()
if(!QDELETED(src))
@@ -262,7 +262,6 @@
return O.equip(src, visualsOnly)
-
//delete all equipment without dropping anything
/mob/living/carbon/human/proc/delete_equipment()
for(var/slot in get_all_slots())//order matters, dependant slots go first
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index 09c9c4ac69cf8..1e1d0b16450a4 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -44,25 +44,6 @@
// Tissues die without blood circulation
adjustBruteLoss(2)
- if(stat != DEAD)
- //handle embedded objects
- //Stuff jammed in your limbs hurts
- for(var/X in bodyparts)
- var/obj/item/bodypart/BP = X
- for(var/obj/item/I in BP.embedded_objects)
- if(prob(I.embedding.embedded_pain_chance))
- BP.receive_damage(I.w_class*I.embedding.embedded_pain_multiplier)
- to_chat(src, "[I] embedded in your [BP.name] hurts!")
-
- if(prob(I.embedding.embedded_fall_chance))
- BP.receive_damage(I.w_class*I.embedding.embedded_fall_pain_multiplier)
- BP.embedded_objects -= I
- I.forceMove(drop_location())
- visible_message("[I] falls out of [name]'s [BP.name]!","[I] falls out of your [BP.name]!")
- if(!has_embedded_objects())
- clear_alert("embeddedobject")
- SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "embedded")
-
if(stat != DEAD)
//Handle hygiene
if(HAS_TRAIT(src, TRAIT_ALWAYS_CLEAN))
@@ -72,23 +53,23 @@
var/hygiene_loss = -HYGIENE_FACTOR * 0.25 //Small loss per life
//If you're covered in blood, you'll start smelling like shit faster.
- var/obj/item/head = get_item_by_slot(SLOT_HEAD)
+ var/obj/item/head = get_item_by_slot(ITEM_SLOT_HEAD)
if(head && HAS_BLOOD_DNA(head))
hygiene_loss -= 1 * HYGIENE_FACTOR
- var/obj/item/mask = get_item_by_slot(SLOT_HEAD)
+ var/obj/item/mask = get_item_by_slot(ITEM_SLOT_HEAD)
if(mask && HAS_BLOOD_DNA(mask))
hygiene_loss -= 1 * HYGIENE_FACTOR
- var/obj/item/uniform = get_item_by_slot(SLOT_W_UNIFORM)
+ var/obj/item/uniform = get_item_by_slot(ITEM_SLOT_ICLOTHING)
if(uniform && HAS_BLOOD_DNA(uniform))
hygiene_loss -= 4 * HYGIENE_FACTOR
- var/obj/item/suit = get_item_by_slot(SLOT_WEAR_SUIT)
+ var/obj/item/suit = get_item_by_slot(ITEM_SLOT_OCLOTHING)
if(suit && HAS_BLOOD_DNA(suit))
hygiene_loss -= 3 * HYGIENE_FACTOR
- var/obj/item/feet = get_item_by_slot(SLOT_SHOES)
+ var/obj/item/feet = get_item_by_slot(ITEM_SLOT_FEET)
if(feet && HAS_BLOOD_DNA(feet))
hygiene_loss -= 0.5 * HYGIENE_FACTOR
@@ -104,28 +85,36 @@
/mob/living/carbon/human/calculate_affecting_pressure(pressure)
- if (wear_suit && head && istype(wear_suit, /obj/item/clothing) && istype(head, /obj/item/clothing))
- var/obj/item/clothing/CS = wear_suit
- var/obj/item/clothing/CH = head
- if (CS.clothing_flags & CH.clothing_flags & STOPSPRESSUREDAMAGE)
- return ONE_ATMOSPHERE
+ var/chest_covered = FALSE
+ var/head_covered = FALSE
+ for(var/obj/item/clothing/equipped in get_equipped_items())
+ if((equipped.body_parts_covered & CHEST) && (equipped.clothing_flags & STOPSPRESSUREDAMAGE))
+ chest_covered = TRUE
+ if((equipped.body_parts_covered & HEAD) && (equipped.clothing_flags & STOPSPRESSUREDAMAGE))
+ head_covered = TRUE
+
+ if(chest_covered && head_covered)
+ return ONE_ATMOSPHERE
return pressure
/mob/living/carbon/human/handle_traits()
+ if (getOrganLoss(ORGAN_SLOT_BRAIN) >= 60)
+ SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "brain_damage", /datum/mood_event/brain_damage)
+ else
+ SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "brain_damage")
+
if(eye_blind) //blindness, heals slowly over time
if(HAS_TRAIT_FROM(src, TRAIT_BLIND, EYES_COVERED)) //covering your eyes heals blurry eyes faster
adjust_blindness(-3)
else
adjust_blindness(-1)
- else if(eye_blurry) //blurry eyes heal slowly
+ //If you have blindness from a trait, heal blurryness too, otherwise return and ignore that.
+ if(!(HAS_TRAIT(src, TRAIT_BLIND)))
+ return
+ if(eye_blurry) //blurry eyes heal slowly
adjust_blurriness(-1)
- if (getOrganLoss(ORGAN_SLOT_BRAIN) >= 60)
- SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "brain_damage", /datum/mood_event/brain_damage)
- else
- SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "brain_damage")
-
/mob/living/carbon/human/handle_mutations_and_radiation()
if(!dna || !dna.species.handle_mutations_and_radiation(src))
..()
@@ -149,13 +138,13 @@
var/datum/species/S = dna.species
if(S.breathid == "o2")
- throw_alert("not_enough_oxy", /obj/screen/alert/not_enough_oxy)
+ throw_alert("not_enough_oxy", /atom/movable/screen/alert/not_enough_oxy)
else if(S.breathid == "tox")
- throw_alert("not_enough_tox", /obj/screen/alert/not_enough_tox)
+ throw_alert("not_enough_tox", /atom/movable/screen/alert/not_enough_tox)
else if(S.breathid == "co2")
- throw_alert("not_enough_co2", /obj/screen/alert/not_enough_co2)
+ throw_alert("not_enough_co2", /atom/movable/screen/alert/not_enough_co2)
else if(S.breathid == "n2")
- throw_alert("not_enough_nitro", /obj/screen/alert/not_enough_nitro)
+ throw_alert("not_enough_nitro", /atom/movable/screen/alert/not_enough_nitro)
return FALSE
else
@@ -337,7 +326,7 @@
if(glasses)
if(glasses.clothing_flags & BLOCK_GAS_SMOKE_EFFECT)
return TRUE
- if(head && istype(head, /obj/item/clothing))
+ if(head && isclothing(head))
var/obj/item/clothing/CH = head
if(CH.clothing_flags & BLOCK_GAS_SMOKE_EFFECT)
return TRUE
diff --git a/code/modules/mob/living/carbon/human/physiology.dm b/code/modules/mob/living/carbon/human/physiology.dm
index 4d411deb53df6..2a197b64ee29f 100644
--- a/code/modules/mob/living/carbon/human/physiology.dm
+++ b/code/modules/mob/living/carbon/human/physiology.dm
@@ -23,7 +23,5 @@
var/hunger_mod = 1 //% of hunger rate taken per tick.
- var/do_after_speed = 1 //Speed mod for do_after. Lower is better. If temporarily adjusting, please only modify using *= and /=, so you don't interrupt other calculations.
-
/datum/physiology/New()
armor = new
diff --git a/code/modules/mob/living/carbon/human/say.dm b/code/modules/mob/living/carbon/human/say.dm
index 5303daa89a46d..aab11ee6a26b4 100644
--- a/code/modules/mob/living/carbon/human/say.dm
+++ b/code/modules/mob/living/carbon/human/say.dm
@@ -1,4 +1,4 @@
-/mob/living/carbon/human/say_mod(input, message_mode)
+/mob/living/carbon/human/say_mod(input, list/message_mods = list())
verb_say = dna.species.say_mod
if(slurring)
return "slurs"
@@ -45,35 +45,28 @@
return special_voice
/mob/living/carbon/human/binarycheck()
- if(ears)
- var/obj/item/radio/headset/dongle = ears
- if(!istype(dongle))
- return FALSE
- if(dongle.translate_binary)
- return TRUE
+ if(stat >= SOFT_CRIT || !ears)
+ return FALSE
+ var/obj/item/radio/headset/dongle = ears
+ if(!istype(dongle))
+ return FALSE
+ return dongle.translate_binary
-/mob/living/carbon/human/radio(message, message_mode, list/spans, language)
+/mob/living/carbon/human/radio(message, list/message_mods = list(), list/spans, language)
. = ..()
- if(. != 0)
+ if(. != FALSE)
return .
- switch(message_mode)
- if(MODE_HEADSET)
- if (ears)
- ears.talk_into(src, message, , spans, language)
- return ITALICS | REDUCE_RANGE
-
- if(MODE_DEPARTMENT)
- if (ears)
- ears.talk_into(src, message, message_mode, spans, language)
- return ITALICS | REDUCE_RANGE
-
- if(message_mode in GLOB.radiochannels)
+ if(message_mods[MODE_HEADSET])
+ if(ears)
+ ears.talk_into(src, message, , spans, language, message_mods)
+ return ITALICS | REDUCE_RANGE
+ else if(message_mods[RADIO_EXTENSION] == MODE_DEPARTMENT || (GLOB.radiochannels[message_mods[RADIO_EXTENSION]]))
if(ears)
- ears.talk_into(src, message, message_mode, spans, language)
+ ears.talk_into(src, message, message_mods[RADIO_EXTENSION], spans, language, message_mods)
return ITALICS | REDUCE_RANGE
- return 0
+ return FALSE
/mob/living/carbon/human/get_alt_name()
if(name != GetVoice())
@@ -86,7 +79,7 @@
var/say_starter = "Say \"" //"
if(findtextEx(temp, say_starter, 1, length(say_starter) + 1) && length(temp) > length(say_starter)) //case sensitive means
- temp = trim_left(copytext(temp, length(say_starter + 1)))
+ temp = trim_left(copytext(temp, length(say_starter) + 1))
temp = replacetext(temp, ";", "", 1, 2) //general radio
while(trim_left(temp)[1] == ":") //dept radio again (necessary)
temp = copytext_char(trim_left(temp), 3)
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index 49e0d3fcf7792..4c99da5a99009 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -6,11 +6,12 @@ GLOBAL_LIST_EMPTY(roundstart_races)
var/id // if the game needs to manually check your race to do something not included in a proc here, it will use this
var/limbs_id //this is used if you want to use a different species limb sprites. Mainly used for angels as they look like humans.
var/name // this is the fluff name. these will be left generic (such as 'Lizardperson' for the lizard race) so servers can change them to whatever
+ var/bodyflag = FLAG_HUMAN //Species flags currently used for species restriction on items
var/default_color = "#FFF" // if alien colors are disabled, this is the color that will be used by that race
var/sexes = 1 // whether or not the race has sexual characteristics. at the moment this is only 0 for skeletons and shadows
- var/list/offset_features = list(OFFSET_UNIFORM = list(0,0), OFFSET_ID = list(0,0), OFFSET_GLOVES = list(0,0), OFFSET_GLASSES = list(0,0), OFFSET_EARS = list(0,0), OFFSET_SHOES = list(0,0), OFFSET_S_STORE = list(0,0), OFFSET_FACEMASK = list(0,0), OFFSET_HEAD = list(0,0), OFFSET_FACE = list(0,0), OFFSET_BELT = list(0,0), OFFSET_BACK = list(0,0), OFFSET_SUIT = list(0,0), OFFSET_NECK = list(0,0))
+ var/list/offset_features = list(OFFSET_UNIFORM = list(0,0), OFFSET_ID = list(0,0), OFFSET_GLOVES = list(0,0), OFFSET_GLASSES = list(0,0), OFFSET_EARS = list(0,0), OFFSET_SHOES = list(0,0), OFFSET_S_STORE = list(0,0), OFFSET_FACEMASK = list(0,0), OFFSET_HEAD = list(0,0), OFFSET_FACE = list(0,0), OFFSET_BELT = list(0,0), OFFSET_BACK = list(0,0), OFFSET_SUIT = list(0,0), OFFSET_NECK = list(0,0), OFFSET_RIGHT_HAND = list(0,0), OFFSET_LEFT_HAND = list(0,0))
var/hair_color // this allows races to have specific hair colors... if null, it uses the H's hair/facial hair colors. if "mutcolor", it uses the H's mutant_color
var/hair_alpha = 255 // the alpha used by the hair. 255 is completely solid, 0 is transparent.
@@ -28,6 +29,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
var/say_mod = "says" // affects the speech message
var/species_language_holder = /datum/language_holder
var/list/default_features = list() // Default mutant bodyparts for this species. Don't forget to set one for every mutant bodypart you allow this species to have.
+ var/list/forced_features = list() // A list of features forced on characters
var/list/mutant_bodyparts = list() // Visible CURRENT bodyparts that are unique to a species. DO NOT USE THIS AS A LIST OF ALL POSSIBLE BODYPARTS AS IT WILL FUCK SHIT UP! Changes to this list for non-species specific bodyparts (ie cat ears and tails) should be assigned at organ level if possible. Layer hiding is handled by handle_mutant_bodyparts() below.
var/list/mutant_organs = list() //Internal organs that are unique to this race.
var/speedmod = 0 // this affects the race's speed. positive numbers make it move slower, negative numbers make it move faster
@@ -50,12 +52,12 @@ GLOBAL_LIST_EMPTY(roundstart_races)
var/deathsound //used to set the mobs deathsound on species change
var/list/special_step_sounds //Sounds to override barefeet walkng
var/grab_sound //Special sound for grabbing
+ var/blood_color //Blood color for decals
var/reagent_tag = PROCESS_ORGANIC //Used for metabolizing reagents. We're going to assume you're a meatbag unless you say otherwise.
- var/species_gibs = "human"
+ var/species_gibs = GIB_TYPE_HUMAN //by default human gibs are used
var/allow_numbers_in_name // Can this species use numbers in its name?
var/datum/outfit/outfit_important_for_life /// A path to an outfit that is important for species life e.g. plasmaman outfit
var/datum/action/innate/flight/fly //the actual flying ability given to flying species
-
// species-only traits. Can be found in DNA.dm
var/list/species_traits = list()
// generic traits tied to having the species
@@ -90,6 +92,9 @@ GLOBAL_LIST_EMPTY(roundstart_races)
//in __DEFINES/mobs.dm, defaults to NONE, so people actually have to think about it
var/changesource_flags = NONE
+ //The component to add when swimming
+ var/swimming_component = /datum/component/swimming
+
///////////
// PROCS //
///////////
@@ -160,7 +165,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
var/should_have_brain = TRUE
var/should_have_heart = !(NOBLOOD in species_traits)
var/should_have_lungs = !(TRAIT_NOBREATH in inherent_traits)
- var/should_have_appendix = !(TRAIT_NOHUNGER in inherent_traits)
+ var/should_have_appendix = !((TRAIT_NOHUNGER in inherent_traits) || (TRAIT_POWERHUNGRY in inherent_traits))
var/should_have_eyes = TRUE
var/should_have_ears = TRUE
var/should_have_tongue = TRUE
@@ -348,6 +353,8 @@ GLOBAL_LIST_EMPTY(roundstart_races)
/datum/species/proc/on_species_loss(mob/living/carbon/human/C, datum/species/new_species, pref_load)
+ SIGNAL_HANDLER
+
if(C.dna.species.exotic_bloodtype)
C.dna.blood_type = random_blood_type()
if(DIGITIGRADE in species_traits)
@@ -368,7 +375,9 @@ GLOBAL_LIST_EMPTY(roundstart_races)
//keep it at the right spot, so we can't have people taking shortcuts
var/location = C.dna.mutation_index.Find(inert_mutation)
C.dna.mutation_index[location] = new_species.inert_mutation
+ C.dna.default_mutation_genes[location] = C.dna.mutation_index[location]
C.dna.mutation_index[new_species.inert_mutation] = create_sequence(new_species.inert_mutation)
+ C.dna.default_mutation_genes[new_species.inert_mutation] = C.dna.mutation_index[new_species.inert_mutation]
if(inherent_factions)
for(var/i in inherent_factions)
@@ -401,7 +410,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
//we check if our hat or helmet hides our facial hair.
if(H.head)
var/obj/item/I = H.head
- if(istype(I, /obj/item/clothing))
+ if(isclothing(I))
var/obj/item/clothing/C = I
dynamic_fhair_suffix = C.dynamic_fhair_suffix
if(I.flags_inv & HIDEFACIALHAIR)
@@ -454,7 +463,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(H.head)
var/obj/item/I = H.head
- if(istype(I, /obj/item/clothing))
+ if(isclothing(I))
var/obj/item/clothing/C = I
dynamic_hair_suffix = C.dynamic_hair_suffix
if(I.flags_inv & HIDEHAIR)
@@ -559,7 +568,10 @@ GLOBAL_LIST_EMPTY(roundstart_races)
var/datum/sprite_accessory/underwear/underwear = GLOB.underwear_list[H.underwear]
var/mutable_appearance/underwear_overlay
if(underwear)
- underwear_overlay = mutable_appearance(underwear.icon, underwear.icon_state, -BODY_LAYER)
+ if(H.dna.species.sexes && H.gender == FEMALE && (underwear.gender == MALE))
+ underwear_overlay = wear_female_version(underwear.icon_state, underwear.icon, BODY_LAYER, FEMALE_UNIFORM_FULL)
+ else
+ underwear_overlay = mutable_appearance(underwear.icon, underwear.icon_state, -BODY_LAYER)
if(!underwear.use_static)
underwear_overlay.color = "#" + H.underwear_color
standing += underwear_overlay
@@ -572,7 +584,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
else
standing += mutable_appearance(undershirt.icon, undershirt.icon_state, -BODY_LAYER)
- if(H.socks && H.get_num_legs(FALSE) >= 2 && !(DIGITIGRADE in species_traits))
+ if(H.socks && H.get_num_legs(FALSE) >= 2 && !(DIGITIGRADE in species_traits) && !(NOSOCKS in species_traits))
var/datum/sprite_accessory/socks/socks = GLOB.socks_list[H.socks]
if(socks)
standing += mutable_appearance(socks.icon, socks.icon_state, -BODY_LAYER)
@@ -831,16 +843,18 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(slot in no_equip)
if(!I.species_exception || !is_type_in_list(src, I.species_exception))
return FALSE
-
+ if(I.species_restricted & H.dna?.species.bodyflag)
+ to_chat(H, "Your species cannot wear this item!")
+ return FALSE
var/num_arms = H.get_num_arms(FALSE)
var/num_legs = H.get_num_legs(FALSE)
switch(slot)
- if(SLOT_HANDS)
+ if(ITEM_SLOT_HANDS)
if(H.get_empty_held_indexes())
return TRUE
return FALSE
- if(SLOT_WEAR_MASK)
+ if(ITEM_SLOT_MASK)
if(H.wear_mask)
return FALSE
if(!(I.slot_flags & ITEM_SLOT_MASK))
@@ -848,25 +862,25 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(!H.get_bodypart(BODY_ZONE_HEAD))
return FALSE
return equip_delay_self_check(I, H, bypass_equip_delay_self)
- if(SLOT_NECK)
+ if(ITEM_SLOT_NECK)
if(H.wear_neck)
return FALSE
if( !(I.slot_flags & ITEM_SLOT_NECK) )
return FALSE
return TRUE
- if(SLOT_BACK)
+ if(ITEM_SLOT_BACK)
if(H.back)
return FALSE
if( !(I.slot_flags & ITEM_SLOT_BACK) )
return FALSE
return equip_delay_self_check(I, H, bypass_equip_delay_self)
- if(SLOT_WEAR_SUIT)
+ if(ITEM_SLOT_OCLOTHING)
if(H.wear_suit)
return FALSE
if( !(I.slot_flags & ITEM_SLOT_OCLOTHING) )
return FALSE
return equip_delay_self_check(I, H, bypass_equip_delay_self)
- if(SLOT_GLOVES)
+ if(ITEM_SLOT_GLOVES)
if(H.gloves)
return FALSE
if( !(I.slot_flags & ITEM_SLOT_GLOVES) )
@@ -874,7 +888,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(num_arms < 2)
return FALSE
return equip_delay_self_check(I, H, bypass_equip_delay_self)
- if(SLOT_SHOES)
+ if(ITEM_SLOT_FEET)
if(H.shoes)
return FALSE
if( !(I.slot_flags & ITEM_SLOT_FEET) )
@@ -886,7 +900,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
to_chat(H, "The footwear around here isn't compatible with your feet!")
return FALSE
return equip_delay_self_check(I, H, bypass_equip_delay_self)
- if(SLOT_BELT)
+ if(ITEM_SLOT_BELT)
if(H.belt)
return FALSE
@@ -899,7 +913,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(!(I.slot_flags & ITEM_SLOT_BELT))
return
return equip_delay_self_check(I, H, bypass_equip_delay_self)
- if(SLOT_GLASSES)
+ if(ITEM_SLOT_EYES)
if(H.glasses)
return FALSE
if(!(I.slot_flags & ITEM_SLOT_EYES))
@@ -910,7 +924,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(E?.no_glasses)
return FALSE
return equip_delay_self_check(I, H, bypass_equip_delay_self)
- if(SLOT_HEAD)
+ if(ITEM_SLOT_HEAD)
if(H.head)
return FALSE
if(!(I.slot_flags & ITEM_SLOT_HEAD))
@@ -918,7 +932,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(!H.get_bodypart(BODY_ZONE_HEAD))
return FALSE
return equip_delay_self_check(I, H, bypass_equip_delay_self)
- if(SLOT_EARS)
+ if(ITEM_SLOT_EARS)
if(H.ears)
return FALSE
if(!(I.slot_flags & ITEM_SLOT_EARS))
@@ -926,13 +940,13 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(!H.get_bodypart(BODY_ZONE_HEAD))
return FALSE
return equip_delay_self_check(I, H, bypass_equip_delay_self)
- if(SLOT_W_UNIFORM)
+ if(ITEM_SLOT_ICLOTHING)
if(H.w_uniform)
return FALSE
if( !(I.slot_flags & ITEM_SLOT_ICLOTHING) )
return FALSE
return equip_delay_self_check(I, H, bypass_equip_delay_self)
- if(SLOT_WEAR_ID)
+ if(ITEM_SLOT_ID)
if(H.wear_id)
return FALSE
@@ -944,7 +958,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if( !(I.slot_flags & ITEM_SLOT_ID) )
return FALSE
return equip_delay_self_check(I, H, bypass_equip_delay_self)
- if(SLOT_L_STORE)
+ if(ITEM_SLOT_LPOCKET)
if(HAS_TRAIT(I, TRAIT_NODROP)) //Pockets aren't visible, so you can't move TRAIT_NODROP items into them.
return FALSE
if(H.l_store)
@@ -956,11 +970,9 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(!disable_warning)
to_chat(H, "You need a jumpsuit before you can attach this [I.name]!")
return FALSE
- if(I.slot_flags & ITEM_SLOT_DENYPOCKET)
- return FALSE
- if( I.w_class <= WEIGHT_CLASS_SMALL || (I.slot_flags & ITEM_SLOT_POCKET) )
+ if( I.w_class <= WEIGHT_CLASS_SMALL || (I.slot_flags & ITEM_SLOT_LPOCKET) )
return TRUE
- if(SLOT_R_STORE)
+ if(ITEM_SLOT_RPOCKET)
if(HAS_TRAIT(I, TRAIT_NODROP))
return FALSE
if(H.r_store)
@@ -972,12 +984,10 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(!disable_warning)
to_chat(H, "You need a jumpsuit before you can attach this [I.name]!")
return FALSE
- if(I.slot_flags & ITEM_SLOT_DENYPOCKET)
- return FALSE
- if( I.w_class <= WEIGHT_CLASS_SMALL || (I.slot_flags & ITEM_SLOT_POCKET) )
+ if( I.w_class <= WEIGHT_CLASS_SMALL || (I.slot_flags & ITEM_SLOT_RPOCKET) )
return TRUE
return FALSE
- if(SLOT_S_STORE)
+ if(ITEM_SLOT_SUITSTORE)
if(HAS_TRAIT(I, TRAIT_NODROP))
return FALSE
if(H.s_store)
@@ -997,7 +1007,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if( istype(I, /obj/item/pda) || istype(I, /obj/item/pen) || is_type_in_list(I, H.wear_suit.allowed) )
return TRUE
return FALSE
- if(SLOT_HANDCUFFED)
+ if(ITEM_SLOT_HANDCUFFED)
if(H.handcuffed)
return FALSE
if(!istype(I, /obj/item/restraints/handcuffs))
@@ -1005,7 +1015,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(num_arms < 2)
return FALSE
return TRUE
- if(SLOT_LEGCUFFED)
+ if(ITEM_SLOT_LEGCUFFED)
if(H.legcuffed)
return FALSE
if(!istype(I, /obj/item/restraints/legcuffs))
@@ -1013,7 +1023,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(num_legs < 2)
return FALSE
return TRUE
- if(SLOT_IN_BACKPACK)
+ if(ITEM_SLOT_BACKPACK)
if(H.back)
if(SEND_SIGNAL(H.back, COMSIG_TRY_STORAGE_CAN_INSERT, I, H, TRUE))
return TRUE
@@ -1032,24 +1042,27 @@ GLOBAL_LIST_EMPTY(roundstart_races)
/datum/species/proc/after_equip_job(datum/job/J, mob/living/carbon/human/H)
H.update_mutant_bodyparts()
+// Do species-specific reagent handling here
+// Return 1 if it should do normal processing too
+// Return 0 if it shouldn't deplete and do its normal effect
+// Other return values will cause weird badness
+
/datum/species/proc/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H)
if(chem.type == exotic_blood)
H.blood_volume = min(H.blood_volume + round(chem.volume, 0.1), BLOOD_VOLUME_MAXIMUM)
H.reagents.del_reagent(chem.type)
- return 1
+ return TRUE
+ //This handles dumping unprocessable reagents.
+ var/dump_reagent = TRUE
+ if((chem.process_flags & SYNTHETIC) && (H.dna.species.reagent_tag & PROCESS_SYNTHETIC)) //SYNTHETIC-oriented reagents require PROCESS_SYNTHETIC
+ dump_reagent = FALSE
+ if((chem.process_flags & ORGANIC) && (H.dna.species.reagent_tag & PROCESS_ORGANIC)) //ORGANIC-oriented reagents require PROCESS_ORGANIC
+ dump_reagent = FALSE
+ if(dump_reagent)
+ chem.holder.remove_reagent(chem.type, chem.metabolization_rate)
+ return TRUE
return FALSE
-// Do species-specific reagent handling here
-// Return 1 if it should do normal processing too
-// Return 0 if it shouldn't deplete and do its normal effect
-// Other return values will cause weird badness
-/datum/species/proc/handle_reagents(mob/living/carbon/human/H, datum/reagent/R)
- if(R.type == exotic_blood)
- H.blood_volume = min(H.blood_volume + round(R.volume, 0.1), BLOOD_VOLUME_NORMAL)
- H.reagents.del_reagent(R.type)
- return FALSE
- return TRUE
-
/datum/species/proc/check_species_weakness(obj/item, mob/living/attacker)
return 0 //This is not a boolean, it's the multiplier for the damage that the user takes from the item.It is added onto the check_weakness value of the mob, and then the force of the item is multiplied by this value
@@ -1139,23 +1152,38 @@ GLOBAL_LIST_EMPTY(roundstart_races)
var/hungry = (500 - H.nutrition) / 5 //So overeat would be 100 and default level would be 80
if(hungry >= 70)
H.add_movespeed_modifier(MOVESPEED_ID_HUNGRY, override = TRUE, multiplicative_slowdown = (hungry / 50))
- else if(isethereal(H))
- var/datum/species/ethereal/E = H.dna.species
- var/charge = E.get_charge()
- if(charge <= ETHEREAL_CHARGE_NORMAL)
- . += 1.5 * (1 - charge / 100)
else
H.remove_movespeed_modifier(MOVESPEED_ID_HUNGRY)
+ if(HAS_TRAIT(H, TRAIT_POWERHUNGRY))
+ handle_charge(H)
+ else
+ switch(H.nutrition)
+ if(NUTRITION_LEVEL_FULL to INFINITY)
+ H.throw_alert("nutrition", /atom/movable/screen/alert/fat)
+ if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_FULL)
+ H.clear_alert("nutrition")
+ if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_HUNGRY)
+ H.throw_alert("nutrition", /atom/movable/screen/alert/hungry)
+ if(0 to NUTRITION_LEVEL_STARVING)
+ H.throw_alert("nutrition", /atom/movable/screen/alert/starving)
+
+/datum/species/proc/handle_charge(mob/living/carbon/human/H)
switch(H.nutrition)
- if(NUTRITION_LEVEL_FULL to INFINITY)
- H.throw_alert("nutrition", /obj/screen/alert/fat)
- if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_FULL)
+ if(NUTRITION_LEVEL_FED to INFINITY)
H.clear_alert("nutrition")
+ if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_FED)
+ H.throw_alert("nutrition", /atom/movable/screen/alert/lowcell, 1)
if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_HUNGRY)
- H.throw_alert("nutrition", /obj/screen/alert/hungry)
- if(0 to NUTRITION_LEVEL_STARVING)
- H.throw_alert("nutrition", /obj/screen/alert/starving)
+ H.throw_alert("nutrition", /atom/movable/screen/alert/lowcell, 2)
+ if(1 to NUTRITION_LEVEL_STARVING)
+ H.throw_alert("nutrition", /atom/movable/screen/alert/lowcell, 3)
+ else
+ var/obj/item/organ/stomach/battery/battery = H.getorganslot(ORGAN_SLOT_STOMACH)
+ if(!istype(battery))
+ H.throw_alert("nutrition", /atom/movable/screen/alert/nocell)
+ else
+ H.throw_alert("nutrition", /atom/movable/screen/alert/emptycell)
/datum/species/proc/update_health_hud(mob/living/carbon/human/H)
return 0
@@ -1282,12 +1310,15 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(target.check_block())
target.visible_message("[target] blocks [user]'s grab attempt!", \
"You block [user]'s grab attempt!")
- return 0
+ return FALSE
if(attacker_style && attacker_style.grab_act(user,target))
return TRUE
else
//Steal them shoes
if(!(target.mobility_flags & MOBILITY_STAND) && (user.zone_selected == BODY_ZONE_L_LEG || user.zone_selected == BODY_ZONE_R_LEG) && user.a_intent == INTENT_GRAB && target.shoes)
+ if(HAS_TRAIT(target.shoes, TRAIT_NODROP))
+ target.grabbedby(user)
+ return TRUE
user.visible_message("[user] starts stealing [target]'s shoes!",
"You start stealing [target]'s shoes!")
var/obj/item/I = target.shoes
@@ -1396,6 +1427,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
var/mob/living/carbon/human/target_collateral_human
var/obj/structure/table/target_table
var/obj/machinery/disposal/bin/target_disposal_bin
+ var/turf/open/indestructible/sound/pool/target_pool //This list is getting pretty long, but its better than calling shove_act or something on every atom
var/shove_blocked = FALSE //Used to check if a shove is blocked so that if it is knockdown logic can be applied
//Thank you based whoneedsspace
@@ -1407,6 +1439,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(get_turf(target) == target_oldturf)
target_table = locate(/obj/structure/table) in target_shove_turf.contents
target_disposal_bin = locate(/obj/machinery/disposal/bin) in target_shove_turf.contents
+ target_pool = istype(target_shove_turf, /turf/open/indestructible/sound/pool) ? target_shove_turf : null
shove_blocked = TRUE
if(target.IsKnockdown() && !target.IsParalyzed())
@@ -1429,7 +1462,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(O.flags_1 & ON_BORDER_1 && O.dir == turn(shove_dir, 180) && O.density)
directional_blocked = TRUE
break
- if((!target_table && !target_collateral_human && !target_disposal_bin) || directional_blocked)
+ if((!target_table && !target_collateral_human && !target_disposal_bin && !target_pool) || directional_blocked)
target.Knockdown(SHOVE_KNOCKDOWN_SOLID)
target.drop_all_held_items()
user.visible_message("[user.name] shoves [target.name], knocking [target.p_them()] down!",
@@ -1454,6 +1487,12 @@ GLOBAL_LIST_EMPTY(roundstart_races)
user.visible_message("[user.name] shoves [target.name] into \the [target_disposal_bin]!",
"You shove [target.name] into \the [target_disposal_bin]!", null, COMBAT_MESSAGE_RANGE)
log_combat(user, target, "shoved", "into [target_disposal_bin] (disposal bin)")
+ else if(target_pool)
+ target.Knockdown(SHOVE_KNOCKDOWN_SOLID)
+ target.forceMove(target_pool)
+ user.visible_message("[user.name] shoves [target.name] into \the [target_pool]!",
+ "You shove [target.name] into \the [target_pool]!", null, COMBAT_MESSAGE_RANGE)
+ log_combat(user, target, "shoved", "into [target_pool] (swimming pool)")
else
user.visible_message("[user.name] shoves [target.name]!",
"You shove [target.name]!", null, COMBAT_MESSAGE_RANGE)
@@ -1544,10 +1583,10 @@ GLOBAL_LIST_EMPTY(roundstart_races)
//dismemberment
var/dismemberthreshold = ((affecting.max_damage * 2) - affecting.get_damage()) //don't take the current hit into account.
- var/attackforce = (((I.w_class - 3) * 5) + ((I.attack_weight - 1) * 14) + ((I.sharpness-1) * 20)) //all the variables that go into ripping off a limb in one handy package. Force is absent because it's already been taken into account by the limb being damaged
+ var/attackforce = (((I.w_class - 3) * 5) + ((I.attack_weight - 1) * 14) + ((I.is_sharp()-1) * 20)) //all the variables that go into ripping off a limb in one handy package. Force is absent because it's already been taken into account by the limb being damaged
if(HAS_TRAIT(src, TRAIT_EASYDISMEMBER))
dismemberthreshold -= 30
- if(I.sharpness)
+ if(I.is_sharp())
attackforce = max(attackforce, I.force)
if(attackforce >= dismemberthreshold && I.force >= 10)
if(affecting.dismember(I.damtype))
@@ -1555,7 +1594,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
playsound(get_turf(H), I.get_dismember_sound(), 80, 1)
var/bloody = 0
- if((I.damtype == BRUTE) && (I.force >= max(10, armor_block) || I.sharpness))
+ if((I.damtype == BRUTE) && (I.force >= max(10, armor_block) || I.is_sharp()))
if(affecting.status == BODYPART_ORGANIC)
I.add_mob_blood(H) //Make the weapon bloody, not the person.
if(prob(I.force * 2)) //blood spatter!
@@ -1571,7 +1610,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
switch(hit_area)
if(BODY_ZONE_HEAD)
- if(!I.sharpness)
+ if(!I.is_sharp())
if(H.mind && H.stat == CONSCIOUS && H != user && (H.health - (I.force * I.attack_weight)) <= 0) // rev deconversion through blunt trauma.
var/datum/antagonist/rev/rev = H.mind.has_antag_datum(/datum/antagonist/rev)
if(rev)
@@ -1648,7 +1687,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
var/damage_amount = forced ? damage : damage * hit_percent * staminamod * H.physiology.stamina_mod
if(BP)
if(BP.receive_damage(0, 0, damage_amount))
- H.update_stamina()
+ H.update_stamina(TRUE)
else
H.adjustStaminaLoss(damage_amount)
if(BRAIN)
@@ -1728,11 +1767,11 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if (burn_damage)
switch(burn_damage)
if(0 to 2)
- H.throw_alert("temp", /obj/screen/alert/hot, 1)
+ H.throw_alert("temp", /atom/movable/screen/alert/hot, 1)
if(2 to 4)
- H.throw_alert("temp", /obj/screen/alert/hot, 2)
+ H.throw_alert("temp", /atom/movable/screen/alert/hot, 2)
else
- H.throw_alert("temp", /obj/screen/alert/hot, 3)
+ H.throw_alert("temp", /atom/movable/screen/alert/hot, 3)
burn_damage = burn_damage * heatmod * H.physiology.heat_mod
if (H.stat < UNCONSCIOUS && (prob(burn_damage) * 10) / 4) //40% for level 3 damage on humans
H.emote("scream")
@@ -1745,13 +1784,13 @@ GLOBAL_LIST_EMPTY(roundstart_races)
H.add_movespeed_modifier(MOVESPEED_ID_COLD, override = TRUE, multiplicative_slowdown = ((BODYTEMP_COLD_DAMAGE_LIMIT - H.bodytemperature) / COLD_SLOWDOWN_FACTOR), blacklisted_movetypes = FLOATING)
switch(H.bodytemperature)
if(200 to BODYTEMP_COLD_DAMAGE_LIMIT)
- H.throw_alert("temp", /obj/screen/alert/cold, 1)
+ H.throw_alert("temp", /atom/movable/screen/alert/cold, 1)
H.apply_damage(COLD_DAMAGE_LEVEL_1*coldmod*H.physiology.cold_mod, BURN)
if(120 to 200)
- H.throw_alert("temp", /obj/screen/alert/cold, 2)
+ H.throw_alert("temp", /atom/movable/screen/alert/cold, 2)
H.apply_damage(COLD_DAMAGE_LEVEL_2*coldmod*H.physiology.cold_mod, BURN)
else
- H.throw_alert("temp", /obj/screen/alert/cold, 3)
+ H.throw_alert("temp", /atom/movable/screen/alert/cold, 3)
H.apply_damage(COLD_DAMAGE_LEVEL_3*coldmod*H.physiology.cold_mod, BURN)
else
@@ -1766,21 +1805,21 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(HAZARD_HIGH_PRESSURE to INFINITY)
if(!HAS_TRAIT(H, TRAIT_RESISTHIGHPRESSURE))
H.adjustBruteLoss(min(((adjusted_pressure / HAZARD_HIGH_PRESSURE) -1 ) * PRESSURE_DAMAGE_COEFFICIENT, MAX_HIGH_PRESSURE_DAMAGE) * H.physiology.pressure_mod)
- H.throw_alert("pressure", /obj/screen/alert/highpressure, 2)
+ H.throw_alert("pressure", /atom/movable/screen/alert/highpressure, 2)
else
H.clear_alert("pressure")
if(WARNING_HIGH_PRESSURE to HAZARD_HIGH_PRESSURE)
- H.throw_alert("pressure", /obj/screen/alert/highpressure, 1)
+ H.throw_alert("pressure", /atom/movable/screen/alert/highpressure, 1)
if(WARNING_LOW_PRESSURE to WARNING_HIGH_PRESSURE)
H.clear_alert("pressure")
if(HAZARD_LOW_PRESSURE to WARNING_LOW_PRESSURE)
- H.throw_alert("pressure", /obj/screen/alert/lowpressure, 1)
+ H.throw_alert("pressure", /atom/movable/screen/alert/lowpressure, 1)
else
if(HAS_TRAIT(H, TRAIT_RESISTLOWPRESSURE))
H.clear_alert("pressure")
else
H.adjustBruteLoss(LOW_PRESSURE_DAMAGE * H.physiology.pressure_mod)
- H.throw_alert("pressure", /obj/screen/alert/lowpressure, 2)
+ H.throw_alert("pressure", /atom/movable/screen/alert/lowpressure, 2)
//////////
// FIRE //
@@ -1795,26 +1834,26 @@ GLOBAL_LIST_EMPTY(roundstart_races)
var/list/obscured = H.check_obscured_slots(TRUE)
//HEAD//
- if(H.glasses && !(SLOT_GLASSES in obscured))
+ if(H.glasses && !(ITEM_SLOT_EYES in obscured))
burning_items += H.glasses
- if(H.wear_mask && !(SLOT_WEAR_MASK in obscured))
+ if(H.wear_mask && !(ITEM_SLOT_MASK in obscured))
burning_items += H.wear_mask
- if(H.wear_neck && !(SLOT_NECK in obscured))
+ if(H.wear_neck && !(ITEM_SLOT_NECK in obscured))
burning_items += H.wear_neck
- if(H.ears && !(SLOT_EARS in obscured))
+ if(H.ears && !(ITEM_SLOT_EARS in obscured))
burning_items += H.ears
if(H.head)
burning_items += H.head
//CHEST//
- if(H.w_uniform && !(SLOT_W_UNIFORM in obscured))
+ if(H.w_uniform && !(ITEM_SLOT_ICLOTHING in obscured))
burning_items += H.w_uniform
if(H.wear_suit)
burning_items += H.wear_suit
//ARMS & HANDS//
var/obj/item/clothing/arm_clothes = null
- if(H.gloves && !(SLOT_GLOVES in obscured))
+ if(H.gloves && !(ITEM_SLOT_GLOVES in obscured))
arm_clothes = H.gloves
else if(H.wear_suit && ((H.wear_suit.body_parts_covered & HANDS) || (H.wear_suit.body_parts_covered & ARMS)))
arm_clothes = H.wear_suit
@@ -1825,7 +1864,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
//LEGS & FEET//
var/obj/item/clothing/leg_clothes = null
- if(H.shoes && !(SLOT_SHOES in obscured))
+ if(H.shoes && !(ITEM_SLOT_FEET in obscured))
leg_clothes = H.shoes
else if(H.wear_suit && ((H.wear_suit.body_parts_covered & FEET) || (H.wear_suit.body_parts_covered & LEGS)))
leg_clothes = H.wear_suit
@@ -1977,3 +2016,41 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(isturf(H.loc))
var/turf/T = H.loc
T.Entered(H)
+
+///Calls the DMI data for a custom icon for a given bodypart from the Species Datum.
+/datum/species/proc/get_custom_icons(var/part)
+ return
+/*Here's what a species that has a unique icon for every slot would look like. If your species doesnt have any custom icons for a given part, return null.
+/datum/species/teshari/get_custom_icons(var/part)
+ switch(part)
+ if("uniform")
+ return 'icons/mob/species/teshari/tesh_uniforms.dmi'
+ if("gloves")
+ return 'icons/mob/species/teshari/tesh_gloves.dmi'
+ if("glasses")
+ return 'icons/mob/species/teshari/tesh_glasses.dmi'
+ if("ears")
+ return 'icons/mob/species/teshari/tesh_ears.dmi'
+ if("shoes")
+ return 'icons/mob/species/teshari/tesh_shoes.dmi'
+ if("head")
+ return 'icons/mob/species/teshari/tesh_head.dmi'
+ if("belt")
+ return 'icons/mob/species/teshari/tesh_belts.dmi'
+ if("suit")
+ return 'icons/mob/species/teshari/tesh_suits.dmi'
+ if("mask")
+ return 'icons/mob/species/teshari/tesh_masks.dmi'
+ if("back")
+ return 'icons/mob/species/teshari/tesh_back.dmi'
+ if("generic")
+ return 'icons/mob/species/teshari/tesh_generic.dmi'
+ else
+ return
+*/
+
+/datum/species/proc/get_item_offsets_for_index(i)
+ return
+
+/datum/species/proc/get_harm_descriptors()
+ return
diff --git a/code/modules/mob/living/carbon/human/species_types/IPC.dm b/code/modules/mob/living/carbon/human/species_types/IPC.dm
index d855bfe19cf59..02bc0ca066dcd 100644
--- a/code/modules/mob/living/carbon/human/species_types/IPC.dm
+++ b/code/modules/mob/living/carbon/human/species_types/IPC.dm
@@ -1,23 +1,24 @@
-/datum/species/ipc // im fucking lazy mk2 and cant get sprites to normally work
- name = "IPC" //inherited from the real species, for health scanners and things
- id = "ipc"
- say_mod = "states" //inherited from a user's real species
- sexes = 0
- species_traits = list(NOTRANSSTING,NOEYESPRITES,NO_DNA_COPY,NOBLOOD,TRAIT_EASYDISMEMBER,ROBOTIC_LIMBS,NOZOMBIE,MUTCOLORS,REVIVESBYHEALING,NOHUSK,NOMOUTH) //all of these + whatever we inherit from the real species
- inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RADIMMUNE,TRAIT_LIMBATTACHMENT,TRAIT_NOCRITDAMAGE)
+/datum/species/ipc
+ name = "IPC"
+ id = SPECIES_IPC
+ bodyflag = FLAG_IPC
+ say_mod = "states"
+ sexes = FALSE
+ species_traits = list(NOTRANSSTING,NOEYESPRITES,NO_DNA_COPY,ROBOTIC_LIMBS,NOZOMBIE,MUTCOLORS,REVIVESBYHEALING,NOHUSK,NOMOUTH)
+ inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RADIMMUNE,TRAIT_LIMBATTACHMENT,TRAIT_NOCRITDAMAGE,TRAIT_EASYDISMEMBER,TRAIT_POWERHUNGRY,TRAIT_XENO_IMMUNE, TRAIT_TOXIMMUNE)
inherent_biotypes = list(MOB_ROBOTIC, MOB_HUMANOID)
mutant_brain = /obj/item/organ/brain/positron
mutanteyes = /obj/item/organ/eyes/robotic
mutanttongue = /obj/item/organ/tongue/robot
mutantliver = /obj/item/organ/liver/cybernetic/upgraded/ipc
- mutantstomach = /obj/item/organ/stomach/cell
+ mutantstomach = /obj/item/organ/stomach/battery/ipc
mutantears = /obj/item/organ/ears/robot
mutant_organs = list(/obj/item/organ/cyberimp/arm/power_cord)
mutant_bodyparts = list("ipc_screen", "ipc_antenna", "ipc_chassis")
default_features = list("mcolor" = "#7D7D7D", "ipc_screen" = "Static", "ipc_antenna" = "None", "ipc_chassis" = "Morpheus Cyberkinetics(Greyscale)")
meat = /obj/item/stack/sheet/plasteel{amount = 5}
skinned_type = /obj/item/stack/sheet/iron{amount = 10}
- exotic_blood = "oil"
+ exotic_blood = /datum/reagent/oil
damage_overlay_type = "synth"
limbs_id = "synth"
mutant_bodyparts = list("ipc_screen", "ipc_antenna", "ipc_chassis")
@@ -25,33 +26,36 @@
burnmod = 2
heatmod = 1.5
brutemod = 1
- toxmod = 0
clonemod = 0
staminamod = 0.8
siemens_coeff = 1.5
+ blood_color = "#000000"
reagent_tag = PROCESS_SYNTHETIC
- species_gibs = "robotic"
+ species_gibs = GIB_TYPE_ROBOTIC
attack_sound = 'sound/items/trayhit1.ogg'
allow_numbers_in_name = TRUE
deathsound = "sound/voice/borg_deathsound.ogg"
- var/saved_screen //for saving the screen when they die
- var/list/initial_species_traits //for getting these values back for assume_disguise()
- var/list/initial_inherent_traits
changesource_flags = MIRROR_BADMIN | WABBAJACK
species_language_holder = /datum/language_holder/synthetic
+ special_step_sounds = list('sound/effects/servostep.ogg')
+ var/saved_screen //for saving the screen when they die
var/datum/action/innate/change_screen/change_screen
/datum/species/ipc/random_name(unique)
var/ipc_name = "[pick(GLOB.posibrain_names)]-[rand(100, 999)]"
return ipc_name
-/datum/species/ipc/on_species_gain(mob/living/carbon/C) // Let's make that IPC actually robotic.
+/datum/species/ipc/on_species_gain(mob/living/carbon/C)
. = ..()
- var/obj/item/organ/appendix/appendix = C.getorganslot("appendix") // Easiest way to remove it.
- appendix.Remove(C)
- QDEL_NULL(appendix)
- ADD_TRAIT(C, TRAIT_XENO_IMMUNE, "xeno immune") //makes the IPC immune to huggers
+ var/obj/item/organ/appendix/A = C.getorganslot("appendix") //See below.
+ if(A)
+ A.Remove(C)
+ QDEL_NULL(A)
+ var/obj/item/organ/lungs/L = C.getorganslot("lungs") //Hacky and bad. Will be rewritten entirely in KapuCarbons anyway.
+ if(L)
+ L.Remove(C)
+ QDEL_NULL(L)
if(ishuman(C) && !change_screen)
change_screen = new
change_screen.Grant(C)
@@ -64,10 +68,16 @@
C.dna.species.species_traits += MUTCOLORS
else if(MUTCOLORS in C.dna.species.species_traits)
C.dna.species.species_traits -= MUTCOLORS
+ O.light_brute_msg = "scratched"
+ O.medium_brute_msg = "dented"
+ O.heavy_brute_msg = "sheared"
+
+ O.light_burn_msg = "burned"
+ O.medium_burn_msg = "scorched"
+ O.heavy_burn_msg = "seared"
-datum/species/ipc/on_species_loss(mob/living/carbon/C)
+/datum/species/ipc/on_species_loss(mob/living/carbon/C)
. = ..()
- REMOVE_TRAIT(C, TRAIT_XENO_IMMUNE, "xeno immune")
if(change_screen)
change_screen.Remove(C)
@@ -78,8 +88,12 @@ datum/species/ipc/on_species_loss(mob/living/carbon/C)
saved_screen = C.dna.features["ipc_screen"]
C.dna.features["ipc_screen"] = "BSOD"
C.update_body()
- sleep(3 SECONDS)
- C.dna.features["ipc_screen"] = null // Turns off their monitor on death.
+ addtimer(CALLBACK(src, .proc/post_death, C), 5 SECONDS)
+
+/datum/species/ipc/proc/post_death(mob/living/carbon/C)
+ if(C.stat < DEAD)
+ return
+ C.dna.features["ipc_screen"] = null //Turns off screen on death
C.update_body()
/datum/action/innate/change_screen
@@ -114,41 +128,45 @@ datum/species/ipc/on_species_loss(mob/living/carbon/C)
user.changeNext_move(CLICK_CD_MELEE)
var/obj/machinery/power/apc/A = target
var/mob/living/carbon/human/H = user
- var/obj/item/organ/stomach/cell/cell = locate(/obj/item/organ/stomach/cell) in H.internal_organs
- if(!cell)
+ var/obj/item/organ/stomach/battery/battery = locate(/obj/item/organ/stomach/battery) in H.internal_organs
+ if(!battery)
to_chat(H, "You try to siphon energy from the [A], but your power cell is gone!")
return
- if(A.cell && A.cell.charge > 0)
- if(H.nutrition >= NUTRITION_LEVEL_WELL_FED)
+ if(A.cell && A.cell.charge > A.cell.maxcharge/4)
+ if(H.nutrition >= NUTRITION_LEVEL_ALMOST_FULL)
to_chat(user, "You are already fully charged!")
return
else
powerdraw_loop(A, H)
return
- to_chat(user, "There is no charge to draw from that APC.")
+ to_chat(user, "There is not enough charge to draw from that APC.")
/obj/item/apc_powercord/proc/powerdraw_loop(obj/machinery/power/apc/A, mob/living/carbon/human/H)
H.visible_message("[H] inserts a power connector into the [A].", "You begin to draw power from the [A].")
+ var/obj/item/organ/stomach/battery/battery = locate(/obj/item/organ/stomach/battery) in H.internal_organs
while(do_after(H, 10, target = A))
+ if(!battery)
+ to_chat(H, "You need a battery to recharge!")
+ break
if(loc != H)
to_chat(H, "You must keep your connector out while charging!")
break
- if(A.cell.charge == 0)
+ if(A.cell.charge <= A.cell.maxcharge/4)
to_chat(H, "The [A] doesn't have enough charge to spare.")
break
A.charging = 1
- if(A.cell.charge >= 500)
- H.nutrition += 50
+ if(A.cell.charge > A.cell.maxcharge/4 + 250)
+ battery.adjust_charge(250)
A.cell.charge -= 250
to_chat(H, "You siphon off some of the stored charge for your own use.")
else
- H.nutrition += A.cell.charge/10
- A.cell.charge = 0
+ battery.adjust_charge(A.cell.charge - A.cell.maxcharge/4)
+ A.cell.charge = A.cell.maxcharge/4
to_chat(H, "You siphon off as much as the [A] can spare.")
break
- if(H.nutrition > NUTRITION_LEVEL_WELL_FED)
+ if(battery.charge >= battery.max_charge)
to_chat(H, "You are now fully charged.")
break
H.visible_message("[H] unplugs from the [A].", "You unplug from the [A].")
@@ -162,15 +180,26 @@ datum/species/ipc/on_species_loss(mob/living/carbon/C)
H.visible_message("[H]'s cooling system fans stutter and stall. There is a faint, yet rapid beeping coming from inside their chassis.")
/datum/species/ipc/spec_revival(mob/living/carbon/human/H)
+ H.notify_ghost_cloning("You have been repaired!")
+ H.grab_ghost()
H.dna.features["ipc_screen"] = "BSOD"
H.update_body()
H.say("Reactivating [pick("core systems", "central subroutines", "key functions")]...")
sleep(3 SECONDS)
+ if(H.stat == DEAD)
+ return
H.say("Reinitializing [pick("personality matrix", "behavior logic", "morality subsystems")]...")
sleep(3 SECONDS)
+ if(H.stat == DEAD)
+ return
H.say("Finalizing setup...")
sleep(3 SECONDS)
+ if(H.stat == DEAD)
+ return
H.say("Unit [H.real_name] is fully functional. Have a nice day.")
H.dna.features["ipc_screen"] = saved_screen
H.update_body()
return
+
+/datum/species/ipc/get_harm_descriptors()
+ return list("bleed" = "leaking", "brute" = "denting", "burn" = "burns")
diff --git a/code/modules/mob/living/carbon/human/species_types/android.dm b/code/modules/mob/living/carbon/human/species_types/android.dm
index 31d20bfa67ae8..ebb5e5582690d 100644
--- a/code/modules/mob/living/carbon/human/species_types/android.dm
+++ b/code/modules/mob/living/carbon/human/species_types/android.dm
@@ -3,7 +3,8 @@
id = "android"
say_mod = "states"
species_traits = list(NOTRANSSTING,NOREAGENTS,NO_DNA_COPY,NOBLOOD,NOFLASH)
- inherent_traits = list(TRAIT_NOMETABOLISM,TRAIT_TOXIMMUNE,TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_NOFIRE,TRAIT_PIERCEIMMUNE,TRAIT_NOHUNGER,TRAIT_LIMBATTACHMENT)
+ inherent_traits = list(TRAIT_NOMETABOLISM,TRAIT_TOXIMMUNE,TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,\
+ TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_NOFIRE,TRAIT_PIERCEIMMUNE,TRAIT_NOHUNGER,TRAIT_LIMBATTACHMENT,TRAIT_NOCLONELOSS)
inherent_biotypes = list(MOB_ROBOTIC, MOB_HUMANOID)
meat = null
damage_overlay_type = "synth"
@@ -11,7 +12,7 @@
species_language_holder = /datum/language_holder/synthetic
limbs_id = "synth"
reagent_tag = PROCESS_SYNTHETIC
- species_gibs = "robotic"
+ species_gibs = GIB_TYPE_ROBOTIC
attack_sound = 'sound/items/trayhit1.ogg'
changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT
@@ -21,6 +22,8 @@
for(var/X in C.bodyparts)
var/obj/item/bodypart/O = X
O.change_bodypart_status(BODYPART_ROBOTIC, FALSE, TRUE)
+ O.brute_reduction = 5
+ O.burn_reduction = 4
/datum/species/android/on_species_loss(mob/living/carbon/C)
. = ..()
@@ -28,3 +31,5 @@
for(var/X in C.bodyparts)
var/obj/item/bodypart/O = X
O.change_bodypart_status(BODYPART_ORGANIC,FALSE, TRUE)
+ O.brute_reduction = initial(O.brute_reduction)
+ O.burn_reduction = initial(O.burn_reduction)
diff --git a/code/modules/mob/living/carbon/human/species_types/apid.dm b/code/modules/mob/living/carbon/human/species_types/apid.dm
index cec5dfdd83e7f..0a45aa860e8b3 100644
--- a/code/modules/mob/living/carbon/human/species_types/apid.dm
+++ b/code/modules/mob/living/carbon/human/species_types/apid.dm
@@ -1,11 +1,13 @@
/datum/species/apid
// Beepeople, god damn it. It's hip, and alive! - Fuck ubunutu edition
name = "Apids"
- id = "apid"
+ id = SPECIES_APID
+ bodyflag = FLAG_APID
say_mod = "buzzes"
default_color = "FFE800"
- species_traits = list(LIPS, NOEYESPRITES, TRAIT_BEEFRIEND)
- inherent_biotypes = list(MOB_ORGANIC, MOB_HUMANOID, MOB_BUG)
+ species_traits = list(LIPS,NOEYESPRITES)
+ inherent_traits = list(TRAIT_BEEFRIEND)
+ inherent_biotypes = list(MOB_ORGANIC,MOB_HUMANOID,MOB_BUG)
mutanttongue = /obj/item/organ/tongue/bee
attack_verb = "slash"
attack_sound = 'sound/weapons/slash.ogg'
@@ -17,11 +19,29 @@
mutanteyes = /obj/item/organ/eyes/apid
mutantlungs = /obj/item/organ/lungs/apid
mutantwings = /obj/item/organ/wings/bee
- heatmod = 1.5
- coldmod = 1.5
burnmod = 1.5
+ toxmod = 1.5
+ staminamod = 1.25
changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_MAGIC | MIRROR_PRIDE | ERT_SPAWN | RACE_SWAP | SLIME_EXTRACT
species_language_holder = /datum/language_holder/apid
+ inert_mutation = WAXSALIVA
+ var/cold_cycle = 0
+
+/datum/species/apid/spec_life(mob/living/carbon/human/H)
+ . = ..()
+ if(H.bodytemperature < BODYTEMP_COLD_DAMAGE_LIMIT && !H.IsSleeping() && !HAS_TRAIT(H,TRAIT_RESISTCOLD)) // Sleep when cold, like bees
+ cold_cycle++
+ if(prob(5))
+ to_chat(H, "The cold is making you feel tired...")
+ switch(cold_cycle)
+ if(5 to 10)
+ H.drowsyness++
+ if(10 to INFINITY)
+ H.SetSleeping(50) // Should be 5 seconds
+ cold_cycle = 0 // Resets the cycle, they have a chance to get out after waking up
+
+ else
+ cold_cycle = 0
/datum/species/apid/random_name(gender,unique,lastname)
if(unique)
@@ -40,7 +60,20 @@
return 0
/datum/species/apid/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H)
- . = ..()
if(chem.type == /datum/reagent/toxin/pestkiller)
H.adjustToxLoss(3)
- H.reagents.remove_reagent(chem.type, REAGENTS_METABOLISM)
\ No newline at end of file
+ H.reagents.remove_reagent(chem.type, chem.metabolization_rate)
+ return FALSE
+ return ..()
+
+/datum/species/apid/after_equip_job(datum/job/J, mob/living/carbon/human/H) // For roundstart
+ H.mind?.teach_crafting_recipe(/datum/crafting_recipe/honeycomb)
+ return ..()
+
+/datum/species/apid/on_species_gain(mob/living/carbon/C, datum/species/old_species, pref_load) // For transformations
+ C.mind?.teach_crafting_recipe(/datum/crafting_recipe/honeycomb)
+ return ..()
+
+/datum/species/apid/on_species_loss(mob/living/carbon/human/C, datum/species/new_species, pref_load)
+ C.mind?.forget_crafting_recipe(/datum/crafting_recipe/honeycomb)
+ return ..()
diff --git a/code/modules/mob/living/carbon/human/species_types/debug.dm b/code/modules/mob/living/carbon/human/species_types/debug.dm
new file mode 100644
index 0000000000000..95f901a567fce
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species_types/debug.dm
@@ -0,0 +1,41 @@
+/datum/species/debug
+ name = "CODER DISASTER"
+ id = "debug"
+ bodyflag = FLAG_DEBUG_SPECIES
+ changesource_flags = MIRROR_BADMIN
+ sexes = 0
+
+/datum/species/debug/get_custom_icons(var/part)
+ switch(part)
+ if("uniform")
+ return 'icons/mob/species/debug/debug_all.dmi'
+ if("gloves")
+ return 'icons/mob/species/debug/debug_all.dmi'
+ if("glasses")
+ return 'icons/mob/species/debug/debug_all.dmi'
+ if("ears")
+ return 'icons/mob/species/debug/debug_all.dmi'
+ if("shoes")
+ return 'icons/mob/species/debug/debug_all.dmi'
+ if("head")
+ return 'icons/mob/species/debug/debug_all.dmi'
+ if("belt")
+ return 'icons/mob/species/debug/debug_all.dmi'
+ if("suit")
+ return 'icons/mob/species/debug/debug_all.dmi'
+ if("mask")
+ return 'icons/mob/species/debug/debug_all.dmi'
+ if("back")
+ return 'icons/mob/species/debug/debug_all.dmi'
+ if("generic")
+ return 'icons/mob/species/debug/debug_all.dmi'
+ else
+ return
+
+/obj/item/clothing/head/ushanka/spritesheet_debug
+ name = "Racist Ushanka"
+ desc = "The Return"
+ flags_inv = HIDEEARS|HIDEHAIR
+ icon_state = "ushankadown"
+ item_state = "ushankadown"
+ sprite_sheets = FLAG_DEBUG_SPECIES //The small emblem on the ushanka will appear green for the debug species instead of red
diff --git a/code/modules/mob/living/carbon/human/species_types/dullahan.dm b/code/modules/mob/living/carbon/human/species_types/dullahan.dm
index a699354b7ae34..88b4413da976c 100644
--- a/code/modules/mob/living/carbon/human/species_types/dullahan.dm
+++ b/code/modules/mob/living/carbon/human/species_types/dullahan.dm
@@ -3,7 +3,7 @@
id = "dullahan"
default_color = "FFFFFF"
species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS)
- inherent_traits = list(TRAIT_NOHUNGER,TRAIT_NOBREATH)
+ inherent_traits = list(TRAIT_NOHUNGER,TRAIT_NOBREATH, TRAIT_NONECRODISEASE)
default_features = list("mcolor" = "FFF", "tail_human" = "None", "ears" = "None", "wings" = "None")
use_skintones = TRUE
mutant_brain = /obj/item/organ/brain/dullahan
@@ -24,17 +24,16 @@
/datum/species/dullahan/on_species_gain(mob/living/carbon/human/H, datum/species/old_species)
. = ..()
- H.flags_1 &= ~HEAR_1
+ REMOVE_TRAIT(src, TRAIT_HEARING_SENSITIVE, TRAIT_GENERIC)
var/obj/item/bodypart/head/head = H.get_bodypart(BODY_ZONE_HEAD)
if(head)
head.drop_limb()
- head.flags_1 = HEAR_1
head.throwforce = 25
myhead = new /obj/item/dullahan_relay (head, H)
H.put_in_hands(head)
/datum/species/dullahan/on_species_loss(mob/living/carbon/human/H)
- H.flags_1 |= ~HEAR_1
+ H.become_hearing_sensitive()
H.reset_perspective(H)
if(myhead)
var/obj/item/dullahan_relay/DR = myhead
@@ -109,22 +108,22 @@
/obj/item/dullahan_relay
var/mob/living/owner
- flags_1 = HEAR_1
/obj/item/dullahan_relay/Initialize(mapload,new_owner)
. = ..()
owner = new_owner
START_PROCESSING(SSobj, src)
+ become_hearing_sensitive(ROUNDSTART_TRAIT)
/obj/item/dullahan_relay/process()
if(!istype(loc, /obj/item/bodypart/head) || QDELETED(owner))
. = PROCESS_KILL
qdel(src)
-/obj/item/dullahan_relay/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
+/obj/item/dullahan_relay/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods = list())
. = ..()
if(!QDELETED(owner))
- message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode)
+ message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mods)
to_chat(owner,message)
else
qdel(src)
@@ -138,4 +137,4 @@
D.myhead = null
owner.gib()
owner = null
- ..()
+ return ..()
diff --git a/code/modules/mob/living/carbon/human/species_types/ethereal.dm b/code/modules/mob/living/carbon/human/species_types/ethereal.dm
index 4a318f861a4d9..7713b5801b684 100644
--- a/code/modules/mob/living/carbon/human/species_types/ethereal.dm
+++ b/code/modules/mob/living/carbon/human/species_types/ethereal.dm
@@ -8,23 +8,23 @@
attack_sound = 'sound/weapons/etherealhit.ogg'
miss_sound = 'sound/weapons/etherealmiss.ogg'
meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/ethereal
- mutantstomach = /obj/item/organ/stomach/ethereal
+ mutantstomach = /obj/item/organ/stomach/battery/ethereal
mutanttongue = /obj/item/organ/tongue/ethereal
exotic_blood = /datum/reagent/consumable/liquidelectricity //Liquid Electricity. fuck you think of something better gamer
siemens_coeff = 0.5 //They thrive on energy
brutemod = 1.25 //They're weak to punches
attack_type = BURN //burn bish
damage_overlay_type = "" //We are too cool for regular damage overlays
- species_traits = list(DYNCOLORS, AGENDER, NO_UNDERWEAR, HAIR)
+ species_traits = list(DYNCOLORS, AGENDER, HAIR)
changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT
species_language_holder = /datum/language_holder/ethereal
- inherent_traits = list(TRAIT_NOHUNGER)
+ inherent_traits = list(TRAIT_POWERHUNGRY)
sexes = FALSE //no fetish content allowed
toxic_food = NONE
hair_color = "fixedmutcolor"
hair_alpha = 140
+ swimming_component = /datum/component/swimming/ethereal
var/current_color
- var/ethereal_charge = ETHEREAL_CHARGE_FULL
var/EMPeffect = FALSE
var/emageffect = FALSE
var/r1
@@ -76,6 +76,8 @@
H.update_body()
/datum/species/ethereal/proc/on_emp_act(mob/living/carbon/human/H, severity)
+ SIGNAL_HANDLER
+
EMPeffect = TRUE
spec_updatehealth(H)
to_chat(H, "You feel the light of your body leave you.")
@@ -86,6 +88,8 @@
addtimer(CALLBACK(src, .proc/stop_emp, H), 20 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) //We're out for 20 seconds
/datum/species/ethereal/proc/on_emag_act(mob/living/carbon/human/H, mob/user)
+ SIGNAL_HANDLER
+
if(emageffect)
return
emageffect = TRUE
@@ -119,30 +123,23 @@
spec_updatehealth(H)
H.visible_message("[H] stops flickering and goes back to their normal state!")
-/datum/species/ethereal/proc/handle_charge(mob/living/carbon/human/H)
+/datum/species/ethereal/handle_charge(mob/living/carbon/human/H)
brutemod = 1.25
- switch(get_charge(H))
- if(ETHEREAL_CHARGE_NONE)
- H.throw_alert("ethereal_charge", /obj/screen/alert/etherealcharge, 3)
- if(ETHEREAL_CHARGE_NONE to ETHEREAL_CHARGE_LOWPOWER)
- H.throw_alert("ethereal_charge", /obj/screen/alert/etherealcharge, 2)
+ if(HAS_TRAIT(H, TRAIT_NOHUNGER))
+ return
+ switch(H.nutrition)
+ if(NUTRITION_LEVEL_FED to INFINITY)
+ H.clear_alert("nutrition")
+ if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_FED)
+ H.throw_alert("nutrition", /atom/movable/screen/alert/etherealcharge, 1)
+ brutemod = 1.5
+ if(1 to NUTRITION_LEVEL_STARVING)
+ H.throw_alert("nutrition", /atom/movable/screen/alert/etherealcharge, 2)
if(H.health > 10.5)
apply_damage(0.65, TOX, null, null, H)
brutemod = 1.75
- if(ETHEREAL_CHARGE_LOWPOWER to ETHEREAL_CHARGE_NORMAL)
- H.throw_alert("ethereal_charge", /obj/screen/alert/etherealcharge, 1)
- brutemod = 1.5
else
- H.clear_alert("ethereal_charge")
-
-/datum/species/ethereal/proc/get_charge(mob/living/carbon/H) //this feels like it should be somewhere else. Eh?
- var/obj/item/organ/stomach/ethereal/stomach = H.getorganslot(ORGAN_SLOT_STOMACH)
- if(istype(stomach))
- return stomach.crystal_charge
- return ETHEREAL_CHARGE_NONE
-
-/datum/species/ethereal/proc/adjust_charge(var/change)
- ethereal_charge = CLAMP(ethereal_charge + change, ETHEREAL_CHARGE_NONE, ETHEREAL_CHARGE_FULL)
-
-/datum/species/ethereal/proc/set_charge(var/change)
- ethereal_charge = CLAMP(change, ETHEREAL_CHARGE_NONE, ETHEREAL_CHARGE_FULL)
+ H.throw_alert("nutrition", /atom/movable/screen/alert/etherealcharge, 3)
+ if(H.health > 10.5)
+ apply_damage(1, TOX, null, null, H)
+ brutemod = 2
diff --git a/code/modules/mob/living/carbon/human/species_types/felinid.dm b/code/modules/mob/living/carbon/human/species_types/felinid.dm
index bcf8345b428fe..a018c152feb01 100644
--- a/code/modules/mob/living/carbon/human/species_types/felinid.dm
+++ b/code/modules/mob/living/carbon/human/species_types/felinid.dm
@@ -1,19 +1,24 @@
//Subtype of human
/datum/species/human/felinid
name = "Felinid"
- id = "felinid"
+ id = SPECIES_FELINID
+ bodyflag = FLAG_FELINID
limbs_id = "human"
+ say_mod = "meows"
disliked_food = VEGETABLES | SUGAR
liked_food = DAIRY | MEAT
mutant_bodyparts = list("ears", "tail_human")
- default_features = list("mcolor" = "FFF", "tail_human" = "Cat", "ears" = "Cat", "wings" = "None")
+ default_features = list("mcolor" = "FFF", "wings" = "None")
+ forced_features = list("tail_human" = "Cat", "ears" = "Cat")
mutantears = /obj/item/organ/ears/cat
mutanttail = /obj/item/organ/tail/cat
changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT
+ swimming_component = /datum/component/swimming/felinid
+
/datum/species/human/felinid/qualifies_for_rank(rank, list/features)
return TRUE
@@ -94,8 +99,7 @@
tail.Remove(H)
/datum/species/human/felinid/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/M)
- .=..()
- if(chem.type == /datum/reagent/consumable/cocoa)
+ if(istype(chem, /datum/reagent/consumable/cocoa))
if(prob(40))
M.adjust_disgust(20)
if(prob(5))
@@ -107,6 +111,7 @@
var/obj/item/organ/guts = pick(M.internal_organs)
guts.applyOrganDamage(15)
return FALSE
+ return ..()
/proc/mass_purrbation()
diff --git a/code/modules/mob/living/carbon/human/species_types/flypeople.dm b/code/modules/mob/living/carbon/human/species_types/flypeople.dm
index aad8ae1066a0c..337e3a2d15fe9 100644
--- a/code/modules/mob/living/carbon/human/species_types/flypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/flypeople.dm
@@ -1,6 +1,7 @@
/datum/species/fly
name = "Flyperson"
- id = "fly"
+ id = SPECIES_FLY
+ bodyflag = FLAG_FLY
say_mod = "buzzes"
species_traits = list(NOEYESPRITES, NO_UNDERWEAR, TRAIT_BEEFRIEND)
inherent_biotypes = list(MOB_ORGANIC, MOB_HUMANOID, MOB_BUG)
@@ -21,7 +22,7 @@
/datum/species/fly/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H)
if(chem.type == /datum/reagent/toxin/pestkiller)
H.adjustToxLoss(3)
- H.reagents.remove_reagent(chem.type, REAGENTS_METABOLISM)
+ H.reagents.remove_reagent(chem.type, chem.metabolization_rate)
return TRUE
if(istype(chem, /datum/reagent/consumable))
var/datum/reagent/consumable/nutri_check = chem
@@ -32,8 +33,7 @@
H.visible_message("[H] vomits on the floor!", \
"You throw up on the floor!")
return TRUE
-
- ..()
+ return ..()
// Change body types
/datum/species/fly/on_species_gain(mob/living/carbon/C)
diff --git a/code/modules/mob/living/carbon/human/species_types/golems.dm b/code/modules/mob/living/carbon/human/species_types/golems.dm
index 66b5980785888..3caf3273bf7c7 100644
--- a/code/modules/mob/living/carbon/human/species_types/golems.dm
+++ b/code/modules/mob/living/carbon/human/species_types/golems.dm
@@ -3,15 +3,15 @@
name = "Golem"
id = "iron_golem"
say_mod = "rumbles"
- species_traits = list(NOBLOOD,MUTCOLORS,NO_UNDERWEAR)
- inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER)
+ species_traits = list(NOBLOOD,MUTCOLORS,NO_UNDERWEAR,NOTRANSSTING)
+ inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER, TRAIT_NONECRODISEASE)
inherent_biotypes = list(MOB_INORGANIC, MOB_HUMANOID)
mutant_organs = list(/obj/item/organ/adamantine_resonator)
speedmod = 2
armor = 55
siemens_coeff = 0
punchdamage = 11
- no_equip = list(SLOT_WEAR_MASK, SLOT_WEAR_SUIT, SLOT_GLOVES, SLOT_SHOES, SLOT_W_UNIFORM, SLOT_S_STORE)
+ no_equip = list(ITEM_SLOT_MASK, ITEM_SLOT_OCLOTHING, ITEM_SLOT_GLOVES, ITEM_SLOT_FEET, ITEM_SLOT_ICLOTHING, ITEM_SLOT_SUITSTORE)
nojumpsuit = 1
changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC
sexes = 1
@@ -22,6 +22,7 @@
// changes, only the Random Golem type can be chosen
limbs_id = "golem"
fixed_mut_color = "aaa"
+ swimming_component = /datum/component/swimming/golem
var/info_text = "As an Iron Golem, you don't have any special traits."
var/random_eligible = TRUE //If false, the golem subtype can't be made through golem mutation toxin
@@ -262,7 +263,7 @@
/datum/species/golem/alloy
name = "Alien Alloy Golem"
id = "alloy_golem"
- species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES)
+ species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,NOTRANSSTING)
limbs_id = "a_golem"
meat = /obj/item/stack/sheet/mineral/abductor
mutanttongue = /obj/item/organ/tongue/abductor
@@ -318,8 +319,9 @@
/datum/species/golem/wood/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H)
if(chem.type == /datum/reagent/toxin/plantbgone)
H.adjustToxLoss(3)
- H.reagents.remove_reagent(chem.type, REAGENTS_METABOLISM)
- return 1
+ H.reagents.remove_reagent(chem.type, chem.metabolization_rate)
+ return TRUE
+ return ..()
//Radioactive
/datum/species/golem/uranium
@@ -439,7 +441,7 @@
var/obj/item/I
if(istype(AM, /obj/item))
I = AM
- if(I.thrownby == H) //No throwing stuff at yourself to trigger the teleport
+ if(I.thrownby == WEAKREF(H)) //No throwing stuff at yourself to trigger the teleport
return 0
else
reactive_teleport(H)
@@ -508,7 +510,7 @@
/datum/species/golem/bananium
name = "Bananium Golem"
id = "bananium_golem"
- species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES)
+ species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,NOTRANSSTING)
say_mod = "honks"
punchdamage = 0
meat = /obj/item/stack/ore/bananium
@@ -563,7 +565,7 @@
var/obj/item/I
if(istype(AM, /obj/item))
I = AM
- if(I.thrownby == H) //No throwing stuff at yourself to make bananas
+ if(I.thrownby == WEAKREF(H)) //No throwing stuff at yourself to make bananas
return 0
else
new/obj/item/grown/bananapeel/specialpeel(get_turf(H))
@@ -583,6 +585,8 @@
playsound(get_turf(H), 'sound/misc/sadtrombone.ogg', 70, 0)
/datum/species/golem/bananium/proc/handle_speech(datum/source, list/speech_args)
+ SIGNAL_HANDLER
+
speech_args[SPEECH_SPANS] |= SPAN_CLOWN
/datum/species/golem/runic
@@ -591,7 +595,7 @@
limbs_id = "cultgolem"
sexes = FALSE
info_text = "As a Runic Golem, you possess eldritch powers granted by the Elder Goddess Nar'Sie."
- species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,NOFLASH) //no mutcolors
+ species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,NOFLASH,NOTRANSSTING) //no mutcolors
prefix = "Runic"
special_names = null
random_eligible = FALSE //Zesko claims runic golems break the game
@@ -611,12 +615,15 @@
. = ..()
phase_shift = new
phase_shift.charge_counter = 0
+ phase_shift.start_recharge()
C.AddSpell(phase_shift)
abyssal_gaze = new
abyssal_gaze.charge_counter = 0
+ abyssal_gaze.start_recharge()
C.AddSpell(abyssal_gaze)
dominate = new
dominate.charge_counter = 0
+ dominate.start_recharge()
C.AddSpell(dominate)
/datum/species/golem/runic/on_species_loss(mob/living/carbon/C)
@@ -631,21 +638,22 @@
/datum/species/golem/runic/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H)
if(istype(chem, /datum/reagent/water/holywater))
H.adjustFireLoss(4)
- H.reagents.remove_reagent(chem.type, REAGENTS_METABOLISM)
+ H.reagents.remove_reagent(chem.type, chem.metabolization_rate)
+ return TRUE
if(chem.type == /datum/reagent/fuel/unholywater)
H.adjustBruteLoss(-4)
H.adjustFireLoss(-4)
- H.reagents.remove_reagent(chem.type, REAGENTS_METABOLISM)
-
-
+ H.reagents.remove_reagent(chem.type, chem.metabolization_rate)
+ return TRUE
+ return ..()
/datum/species/golem/clockwork
name = "Clockwork Golem"
id = "clockwork_golem"
say_mod = "clicks"
limbs_id = "clockgolem"
info_text = "As a Clockwork Golem, you are faster than other types of golems. On death, you will break down into scrap."
- species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,NOFLASH)
+ species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,NOFLASH,NOTRANSSTING)
inherent_biotypes = list(MOB_ROBOTIC, MOB_HUMANOID)
armor = 20 //Reinforced, but much less so to allow for fast movement
attack_verb = "smash"
@@ -698,7 +706,7 @@
sexes = FALSE
info_text = "As a Cloth Golem, you are able to reform yourself after death, provided your remains aren't burned or destroyed. You are, of course, very flammable. \
Being made of cloth, your body is magic resistant and faster than that of other golems, but weaker and less resilient."
- species_traits = list(NOBLOOD,NO_UNDERWEAR) //no mutcolors, and can burn
+ species_traits = list(NOBLOOD,NO_UNDERWEAR,NOTRANSSTING) //no mutcolors, and can burn
inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOGUNS)
inherent_biotypes = list(MOB_UNDEAD, MOB_HUMANOID)
armor = 15 //feels no pain, but not too resistant
@@ -748,7 +756,7 @@
name = "pile of bandages"
desc = "It emits a strange aura, as if there was still life within it..."
max_integrity = 50
- armor = list("melee" = 90, "bullet" = 90, "laser" = 25, "energy" = 80, "bomb" = 50, "bio" = 100, "fire" = -50, "acid" = -50)
+ armor = list("melee" = 90, "bullet" = 90, "laser" = 25, "energy" = 80, "bomb" = 50, "bio" = 100, "fire" = -50, "acid" = -50, "stamina" = 0)
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "pile_bandages"
resistance_flags = FLAMMABLE
@@ -861,31 +869,31 @@
/datum/species/golem/bronze/proc/gong(mob/living/carbon/human/H)
last_gong_time = world.time
- for(var/mob/living/M in get_hearers_in_view(7,H))
+ for(var/mob/living/M in hearers(7,H))
if(M.stat == DEAD) //F
return
if(M == H)
- H.show_message("You cringe with pain as your body rings around you!", 2)
+ H.show_message("You cringe with pain as your body rings around you!", MSG_AUDIBLE)
H.playsound_local(H, 'sound/effects/gong.ogg', 100, TRUE)
H.soundbang_act(2, 0, 100, 1)
H.jitteriness += 7
var/distance = max(0,get_dist(get_turf(H),get_turf(M)))
switch(distance)
if(0 to 1)
- M.show_message("GONG!", 2)
+ M.show_message("GONG!", MSG_AUDIBLE)
M.playsound_local(H, 'sound/effects/gong.ogg', 100, TRUE)
M.soundbang_act(1, 0, 30, 3)
M.confused += 10
M.jitteriness += 4
SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "gonged", /datum/mood_event/loud_gong)
if(2 to 3)
- M.show_message("GONG!", 2)
+ M.show_message("GONG!", MSG_AUDIBLE)
M.playsound_local(H, 'sound/effects/gong.ogg', 75, TRUE)
M.soundbang_act(1, 0, 15, 2)
M.jitteriness += 3
SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "gonged", /datum/mood_event/loud_gong)
else
- M.show_message("GONG!", 2)
+ M.show_message("GONG!", MSG_AUDIBLE)
M.playsound_local(H, 'sound/effects/gong.ogg', 50, TRUE)
@@ -895,7 +903,7 @@
prefix = "Cardboard"
special_names = list("Box")
info_text = "As a Cardboard Golem, you aren't very strong, but you are a bit quicker and can easily create more brethren by using cardboard on yourself."
- species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,NOFLASH)
+ species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,NOFLASH,NOTRANSSTING)
inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER)
limbs_id = "c_golem" //special sprites
attack_verb = "whips"
@@ -952,7 +960,7 @@
prefix = "Durathread"
limbs_id = "d_golem"
special_names = list("Boll","Weave")
- species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,NOFLASH)
+ species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,NOFLASH,NOTRANSSTING)
fixed_mut_color = null
inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER)
info_text = "As a Durathread Golem, your strikes will cause those your targets to start choking, but your woven body won't withstand fire as well."
@@ -992,7 +1000,6 @@
..()
/datum/species/golem/bone/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H)
- . = ..()
if(chem.type == /datum/reagent/consumable/milk)
if(chem.volume >= 6)
H.reagents.remove_reagent(chem.type, chem.volume - 5)
@@ -1005,7 +1012,7 @@
H.adjustBruteLoss(0.5, 0)
H.reagents.remove_reagent(chem.type, REAGENTS_METABOLISM)
return TRUE
-
+ return ..()
/datum/action/innate/bonechill
name = "Bone Chill"
desc = "Rattle your bones and strike fear into your enemies!"
@@ -1051,7 +1058,7 @@
info_text = "As a Snow Golem, you are extremely vulnerable to burn damage, but you can generate snowballs and shoot cryokinetic beams. You will also turn to snow when dying, preventing any form of recovery."
prefix = "Snow"
special_names = list("Flake", "Blizzard", "Storm")
- species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES) //no mutcolors, no eye sprites
+ species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,NOTRANSSTING) //no mutcolors, no eye sprites
inherent_traits = list(TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER)
var/obj/effect/proc_holder/spell/targeted/conjure_item/snowball/ball
@@ -1071,9 +1078,11 @@
C.weather_immunities |= "snow"
ball = new
ball.charge_counter = 0
+ ball.start_recharge()
C.AddSpell(ball)
cryo = new
cryo.charge_counter = 0
+ cryo.start_recharge()
C.AddSpell(cryo)
/datum/species/golem/snow/on_species_loss(mob/living/carbon/C)
@@ -1099,7 +1108,7 @@
attack_verb = "monopoliz"
limbs_id = "ca_golem"
special_names = list("John D. Rockefeller","Rich Uncle Pennybags","Commodore Vanderbilt","Entrepreneur","Mr. Moneybags", "Adam Smith")
- species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,NOFLASH)
+ species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,NOFLASH,NOTRANSSTING)
fixed_mut_color = null
inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER)
info_text = "As a Capitalist Golem, your fist spreads the powerful industrializing light of capitalism."
@@ -1111,7 +1120,7 @@
/datum/species/golem/capitalist/on_species_gain(mob/living/carbon/C, datum/species/old_species)
. = ..()
- C.equip_to_slot_or_del(new /obj/item/clothing/glasses/sunglasses/advanced (), SLOT_GLASSES)
+ C.equip_to_slot_or_del(new /obj/item/clothing/glasses/monocle (), ITEM_SLOT_EYES)
C.revive(full_heal = TRUE)
SEND_SOUND(C, sound('sound/misc/capitialism.ogg'))
@@ -1134,6 +1143,8 @@
target.adjust_nutrition(40)
/datum/species/golem/capitalist/proc/handle_speech(datum/source, list/speech_args)
+ SIGNAL_HANDLER
+
playsound(source, 'sound/misc/mymoney.ogg', 25, 0)
speech_args[SPEECH_MESSAGE] = "Hello, I like money!"
@@ -1144,7 +1155,7 @@
attack_verb = "nationaliz"
limbs_id = "s_golem"
special_names = list("Stalin","Lenin","Trotsky","Marx","Comrade") //comrade comrade
- species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,NOFLASH)
+ species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,NOFLASH,NOTRANSSTING)
fixed_mut_color = null
inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER)
info_text = "As a Soviet Golem, your fist spreads the bright soviet light of communism."
@@ -1153,7 +1164,7 @@
/datum/species/golem/soviet/on_species_gain(mob/living/carbon/C, datum/species/old_species)
. = ..()
- C.equip_to_slot_or_del(new /obj/item/clothing/head/ushanka (), SLOT_HEAD)
+ C.equip_to_slot_or_del(new /obj/item/clothing/head/ushanka (), ITEM_SLOT_HEAD)
C.revive(full_heal = TRUE)
SEND_SOUND(C, sound('sound/misc/Russian_Anthem_chorus.ogg'))
@@ -1176,5 +1187,7 @@
target.adjust_nutrition(-40)
/datum/species/golem/soviet/proc/handle_speech(datum/source, list/speech_args)
+ SIGNAL_HANDLER
+
playsound(source, 'sound/misc/Cyka Blyat.ogg', 25, 0)
speech_args[SPEECH_MESSAGE] = "Cyka Blyat"
diff --git a/code/modules/mob/living/carbon/human/species_types/humans.dm b/code/modules/mob/living/carbon/human/species_types/humans.dm
index d3a2505853b50..1c77f0890f152 100644
--- a/code/modules/mob/living/carbon/human/species_types/humans.dm
+++ b/code/modules/mob/living/carbon/human/species_types/humans.dm
@@ -1,6 +1,6 @@
/datum/species/human
name = "Human"
- id = "human"
+ id = SPECIES_HUMAN
default_color = "FFFFFF"
species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS)
default_features = list("mcolor" = "FFF", "wings" = "None")
diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
index 92292bbab1eaa..c4e0f7014343c 100644
--- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
@@ -3,9 +3,9 @@
name = "Jellyperson"
id = "jelly"
default_color = "00FF90"
- say_mod = "chirps"
+ say_mod = "blorbles"
species_traits = list(MUTCOLORS,EYECOLOR,NOBLOOD)
- inherent_traits = list(TRAIT_TOXINLOVER)
+ inherent_traits = list(TRAIT_TOXINLOVER, TRAIT_NONECRODISEASE)
mutantlungs = /obj/item/organ/lungs/slime
meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/slime
exotic_blood = /datum/reagent/toxin/slimejelly
@@ -18,6 +18,7 @@
changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT
inherent_factions = list("slime")
species_language_holder = /datum/language_holder/jelly
+ swimming_component = /datum/component/swimming/dissolve
/datum/species/jelly/on_species_loss(mob/living/carbon/C)
if(regenerate_limbs)
@@ -191,6 +192,13 @@
if(!isslimeperson(H))
return
CHECK_DNA_AND_SPECIES(H)
+
+ //Prevent one person from creating 100 bodies.
+ var/datum/species/jelly/slime/species = H.dna.species
+ if(length(species.bodies) > CONFIG_GET(number/max_slimeperson_bodies))
+ to_chat(H, "Your mind is spread too thin! You have too many bodies already.")
+ return
+
H.visible_message("[owner] gains a look of \
concentration while standing perfectly still.",
"You focus intently on moving your body while \
@@ -271,6 +279,7 @@
if(!ui)
ui = new(user, src, "SlimeBodySwapper")
ui.open()
+ ui.set_autoupdate(TRUE) // Body status (health, occupied, etc.)
/datum/action/innate/swap_body/ui_data(mob/user)
var/mob/living/carbon/human/H = owner
@@ -406,18 +415,25 @@
var/datum/action/innate/use_extract/major/extract_major
var/extract_cooldown = 0
+//Species datums don't normally implement destroy, but JELLIES SUCK ASS OUT OF A STEEL STRAW
+/datum/species/jelly/luminescent/Destroy(force, ...)
+ current_extract = null
+ QDEL_NULL(glow)
+ QDEL_NULL(integrate_extract)
+ QDEL_NULL(extract_major)
+ QDEL_NULL(extract_minor)
+ return ..()
+
+
/datum/species/jelly/luminescent/on_species_loss(mob/living/carbon/C)
..()
if(current_extract)
current_extract.forceMove(C.drop_location())
current_extract = null
- qdel(glow)
- if(integrate_extract)
- integrate_extract.Remove(C)
- if(extract_minor)
- extract_minor.Remove(C)
- if(extract_major)
- extract_major.Remove(C)
+ QDEL_NULL(glow)
+ QDEL_NULL(integrate_extract)
+ QDEL_NULL(extract_major)
+ QDEL_NULL(extract_minor)
/datum/species/jelly/luminescent/on_species_gain(mob/living/carbon/C, datum/species/old_species)
..()
@@ -460,13 +476,9 @@
button_icon_state = "slimeconsume"
icon_icon = 'icons/mob/actions/actions_slime.dmi'
background_icon_state = "bg_alien"
- var/datum/species/jelly/luminescent/species
-
-/datum/action/innate/integrate_extract/New(_species)
- ..()
- species = _species
/datum/action/innate/integrate_extract/proc/update_name()
+ var/datum/species/jelly/luminescent/species = target
if(!species || !species.current_extract)
name = "Integrate Extract"
desc = "Eat a slime extract to use its properties."
@@ -475,19 +487,22 @@
desc = "Eject your current slime extract."
/datum/action/innate/integrate_extract/UpdateButtonIcon(status_only, force)
+ var/datum/species/jelly/luminescent/species = target
if(!species || !species.current_extract)
button_icon_state = "slimeconsume"
else
button_icon_state = "slimeeject"
..()
-/datum/action/innate/integrate_extract/ApplyIcon(obj/screen/movable/action_button/current_button, force)
+/datum/action/innate/integrate_extract/ApplyIcon(atom/movable/screen/movable/action_button/current_button, force)
..(current_button, TRUE)
+ var/datum/species/jelly/luminescent/species = target
if(species?.current_extract)
current_button.add_overlay(mutable_appearance(species.current_extract.icon, species.current_extract.icon_state))
/datum/action/innate/integrate_extract/Activate()
var/mob/living/carbon/human/H = owner
+ var/datum/species/jelly/luminescent/species = target
if(!is_species(H, /datum/species/jelly/luminescent) || !species)
return
CHECK_DNA_AND_SPECIES(H)
@@ -523,28 +538,27 @@
icon_icon = 'icons/mob/actions/actions_slime.dmi'
background_icon_state = "bg_alien"
var/activation_type = SLIME_ACTIVATE_MINOR
- var/datum/species/jelly/luminescent/species
-
-/datum/action/innate/use_extract/New(_species)
- ..()
- species = _species
/datum/action/innate/use_extract/IsAvailable()
if(..())
+ var/datum/species/jelly/luminescent/species = target
if(species && species.current_extract && (world.time > species.extract_cooldown))
return TRUE
return FALSE
-/datum/action/innate/use_extract/ApplyIcon(obj/screen/movable/action_button/current_button, force)
+/datum/action/innate/use_extract/ApplyIcon(atom/movable/screen/movable/action_button/current_button, force)
..(current_button, TRUE)
+ var/mob/living/carbon/human/H = owner
+ var/datum/species/jelly/luminescent/species = H.dna.species
if(species && species.current_extract)
current_button.add_overlay(mutable_appearance(species.current_extract.icon, species.current_extract.icon_state))
/datum/action/innate/use_extract/Activate()
var/mob/living/carbon/human/H = owner
+ CHECK_DNA_AND_SPECIES(H)
+ var/datum/species/jelly/luminescent/species = H.dna.species
if(!is_species(H, /datum/species/jelly/luminescent) || !species)
return
- CHECK_DNA_AND_SPECIES(H)
if(species.current_extract)
species.extract_cooldown = world.time + 100
@@ -568,22 +582,35 @@
var/datum/action/innate/link_minds/link_minds
var/list/mob/living/linked_mobs = list()
var/list/datum/action/innate/linked_speech/linked_actions = list()
- var/mob/living/carbon/human/slimelink_owner
+ var/datum/weakref/slimelink_owner
var/current_link_id = 0
+//Species datums don't normally implement destroy, but JELLIES SUCK ASS OUT OF A STEEL STRAW
+/datum/species/jelly/stargazer/Destroy()
+ for(var/mob/living/link_to_clear as anything in linked_mobs)
+ unlink_mob(link_to_clear)
+ linked_mobs.Cut()
+ QDEL_NULL(project_thought)
+ QDEL_NULL(link_minds)
+ slimelink_owner = null
+ return ..()
+
/datum/species/jelly/stargazer/on_species_loss(mob/living/carbon/C)
..()
- for(var/M in linked_mobs)
- unlink_mob(M)
+ for(var/mob/living/link_to_clear as anything in linked_mobs)
+ unlink_mob(link_to_clear)
if(project_thought)
- project_thought.Remove(C)
+ QDEL_NULL(project_thought)
if(link_minds)
- link_minds.Remove(C)
+ QDEL_NULL(link_minds)
+ slimelink_owner = null
/datum/species/jelly/stargazer/spec_death(gibbed, mob/living/carbon/human/H)
..()
- for(var/M in linked_mobs)
- unlink_mob(M)
+ for(var/mob/living/link_to_clear as anything in linked_mobs)
+ if(link_to_clear == H)
+ continue
+ unlink_mob(link_to_clear)
/datum/species/jelly/stargazer/on_species_gain(mob/living/carbon/C, datum/species/old_species)
..()
@@ -591,7 +618,7 @@
project_thought.Grant(C)
link_minds = new(src)
link_minds.Grant(C)
- slimelink_owner = C
+ slimelink_owner = WEAKREF(C)
link_mob(C)
/datum/species/jelly/stargazer/proc/link_mob(mob/living/M)
@@ -599,14 +626,18 @@
return FALSE
if(HAS_TRAIT(M, TRAIT_MINDSHIELD)) //mindshield implant, no dice
return FALSE
- if(istype(M.get_item_by_slot(SLOT_HEAD), /obj/item/clothing/head/foilhat))
- to_chat(M, "[slimelink_owner.real_name]'s no-good syndicate mind-slime is blocked by your protective headgear!")
+ var/mob/living/carbon/human/owner = slimelink_owner.resolve()
+ if(istype(M.get_item_by_slot(ITEM_SLOT_HEAD), /obj/item/clothing/head/foilhat))
+ if(owner)
+ to_chat(M, "[owner.real_name]'s no-good syndicate mind-slime is blocked by your protective headgear!")
return FALSE
if(M in linked_mobs)
return FALSE
+ if(!owner)
+ return FALSE
linked_mobs.Add(M)
- to_chat(M, "You are now connected to [slimelink_owner.real_name]'s Slime Link.")
+ to_chat(M, "You are now connected to [owner.real_name]'s Slime Link.")
var/datum/action/innate/linked_speech/action = new(src)
linked_actions.Add(action)
action.Grant(M)
@@ -618,9 +649,12 @@
return
var/datum/action/innate/linked_speech/action = linked_actions[link_id]
action.Remove(M)
- to_chat(M, "You are no longer connected to [slimelink_owner.real_name]'s Slime Link.")
- linked_mobs[link_id] = null
- linked_actions[link_id] = null
+ var/mob/living/carbon/human/owner = slimelink_owner.resolve()
+ if(owner)
+ to_chat(M, "You are no longer connected to [owner.real_name]'s Slime Link.")
+ linked_mobs -= M
+ linked_actions -= action
+ qdel(action)
/datum/action/innate/linked_speech
name = "Slimelink"
@@ -628,14 +662,10 @@
button_icon_state = "link_speech"
icon_icon = 'icons/mob/actions/actions_slime.dmi'
background_icon_state = "bg_alien"
- var/datum/species/jelly/stargazer/species
-
-/datum/action/innate/linked_speech/New(_species)
- ..()
- species = _species
/datum/action/innate/linked_speech/Activate()
var/mob/living/carbon/human/H = owner
+ var/datum/species/jelly/stargazer/species = target
if(!species || !(H in species.linked_mobs))
to_chat(H, "The link seems to have been severed...")
Remove(H)
@@ -652,9 +682,11 @@
species.unlink_mob(H)
return
- if(message)
- var/msg = "\[[species.slimelink_owner.real_name]'s Slime Link\] [H]: [message]"
- log_directed_talk(H, species.slimelink_owner, msg, LOG_SAY, "slime link")
+ var/mob/living/carbon/human/star_owner = species.slimelink_owner.resolve()
+
+ if(message && star_owner)
+ var/msg = "\[[star_owner.real_name]'s Slime Link\] [H]: [message]"
+ log_directed_talk(H, star_owner, msg, LOG_SAY, "slime link")
for(var/X in species.linked_mobs)
var/mob/living/M = X
if(QDELETED(M) || M.stat == DEAD)
@@ -707,11 +739,6 @@
button_icon_state = "mindlink"
icon_icon = 'icons/mob/actions/actions_slime.dmi'
background_icon_state = "bg_alien"
- var/datum/species/jelly/stargazer/species
-
-/datum/action/innate/link_minds/New(_species)
- ..()
- species = _species
/datum/action/innate/link_minds/Activate()
var/mob/living/carbon/human/H = owner
@@ -724,6 +751,7 @@
return
var/mob/living/target = H.pulling
+ var/datum/species/jelly/stargazer/species = H.dna.species
to_chat(H, "You begin linking [target]'s mind to yours...")
to_chat(target, "You feel a foreign presence within your mind...")
diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
index 2468c43defa30..dfe471dd3fb6b 100644
--- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
@@ -1,7 +1,8 @@
/datum/species/lizard
// Reptilian humanoids with scaled skin and tails.
name = "Lizardperson"
- id = "lizard"
+ id = SPECIES_LIZARD
+ bodyflag = FLAG_LIZARD
say_mod = "hisses"
default_color = "00FF00"
species_traits = list(MUTCOLORS,EYECOLOR,LIPS)
diff --git a/code/modules/mob/living/carbon/human/species_types/mothmen.dm b/code/modules/mob/living/carbon/human/species_types/mothmen.dm
index d2478c1e995c1..c4fa04a44c3d1 100644
--- a/code/modules/mob/living/carbon/human/species_types/mothmen.dm
+++ b/code/modules/mob/living/carbon/human/species_types/mothmen.dm
@@ -1,6 +1,7 @@
/datum/species/moth
name = "Mothman"
- id = "moth"
+ id = SPECIES_MOTH
+ bodyflag = FLAG_MOTH
say_mod = "flutters"
default_color = "00FF00"
species_traits = list(LIPS, NOEYESPRITES)
@@ -31,11 +32,11 @@
return randname
/datum/species/moth/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H)
- . = ..()
if(chem.type == /datum/reagent/toxin/pestkiller)
H.adjustToxLoss(3)
- H.reagents.remove_reagent(chem.type, REAGENTS_METABOLISM)
-
+ H.reagents.remove_reagent(chem.type, chem.metabolization_rate)
+ return FALSE
+ return ..()
/datum/species/moth/check_species_weakness(obj/item/weapon, mob/living/attacker)
if(istype(weapon, /obj/item/melee/flyswatter))
return 9 //flyswatters deal 10x damage to moths
diff --git a/code/modules/mob/living/carbon/human/species_types/mushpeople.dm b/code/modules/mob/living/carbon/human/species_types/mushpeople.dm
index a3e518fe0b237..6105b98acc7ae 100644
--- a/code/modules/mob/living/carbon/human/species_types/mushpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/mushpeople.dm
@@ -17,7 +17,7 @@
punchdamage = 12
- no_equip = list(SLOT_WEAR_MASK, SLOT_WEAR_SUIT, SLOT_GLOVES, SLOT_SHOES, SLOT_W_UNIFORM)
+ no_equip = list(ITEM_SLOT_MASK, ITEM_SLOT_OCLOTHING, ITEM_SLOT_GLOVES, ITEM_SLOT_FEET, ITEM_SLOT_ICLOTHING)
burnmod = 1.25
heatmod = 1.5
@@ -50,6 +50,7 @@
H.adjustToxLoss(3)
H.reagents.remove_reagent(chem.type, REAGENTS_METABOLISM)
return TRUE
+ return ..()
/datum/species/mush/handle_mutant_bodyparts(mob/living/carbon/human/H, forced_colour)
forced_colour = FALSE
diff --git a/code/modules/mob/living/carbon/human/species_types/oozelings.dm b/code/modules/mob/living/carbon/human/species_types/oozelings.dm
new file mode 100644
index 0000000000000..930161bbdf64e
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species_types/oozelings.dm
@@ -0,0 +1,162 @@
+/datum/species/oozeling
+ name = "Oozeling"
+ id = SPECIES_OOZELING
+ bodyflag = FLAG_OOZELING
+ default_color = "00FF90"
+ say_mod = "blorbles"
+ species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR)
+ inherent_traits = list(TRAIT_TOXINLOVER,TRAIT_NOFIRE,TRAIT_ALWAYS_CLEAN,TRAIT_EASYDISMEMBER)
+ hair_color = "mutcolor"
+ hair_alpha = 150
+ mutantlungs = /obj/item/organ/lungs/oozeling
+ meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/slime
+ exotic_blood = /datum/reagent/toxin/slimeooze
+ damage_overlay_type = ""
+ var/datum/action/innate/regenerate_limbs/regenerate_limbs
+ coldmod = 6 // = 3x cold damage
+ heatmod = 0.5 // = 1/4x heat damage
+ changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT
+ species_language_holder = /datum/language_holder/oozeling
+ limbs_id = "ooze"
+ swimming_component = /datum/component/swimming/dissolve
+ toxic_food = NONE
+ disliked_food = NONE
+
+/datum/species/oozeling/random_name(gender,unique,lastname)
+ if(unique)
+ return random_unique_ooze_name()
+
+ var/randname = ooze_name()
+
+ if(lastname)
+ randname += " [lastname]"
+
+ return randname
+
+/datum/species/oozeling/on_species_loss(mob/living/carbon/C)
+ if(regenerate_limbs)
+ regenerate_limbs.Remove(C)
+ ..()
+
+/datum/species/oozeling/on_species_gain(mob/living/carbon/C, datum/species/old_species)
+ ..()
+ if(ishuman(C))
+ regenerate_limbs = new
+ regenerate_limbs.Grant(C)
+
+/datum/species/oozeling/spec_life(mob/living/carbon/human/H)
+ ..()
+ if(H.stat == DEAD) //can't farm slime jelly from a dead slime/jelly person indefinitely
+ return
+ if(!H.blood_volume)
+ H.blood_volume += 5
+ H.adjustBruteLoss(5)
+ to_chat(H, "You feel empty!")
+ if(H.nutrition >= NUTRITION_LEVEL_WELL_FED && H.blood_volume <= 672)
+ if(H.nutrition >= NUTRITION_LEVEL_ALMOST_FULL)
+ H.adjust_nutrition(-5)
+ H.blood_volume += 10
+ else
+ H.blood_volume += 8
+ if(H.nutrition <= NUTRITION_LEVEL_HUNGRY)
+ if(H.nutrition <= NUTRITION_LEVEL_STARVING)
+ H.blood_volume -= 8
+ if(prob(5))
+ to_chat(H, "You're starving! Get some food!")
+ else
+ if(prob(35))
+ H.blood_volume -= 2
+ if(prob(5))
+ to_chat(H, "You're feeling pretty hungry...")
+ var/atmos_sealed = FALSE
+ if(H.wear_suit && H.head && isclothing(H.wear_suit) && isclothing(H.head))
+ var/obj/item/clothing/CS = H.wear_suit
+ var/obj/item/clothing/CH = H.head
+ if(CS.clothing_flags & CH.clothing_flags & STOPSPRESSUREDAMAGE)
+ atmos_sealed = TRUE
+ if(H.w_uniform && H.head)
+ var/obj/item/clothing/CU = H.w_uniform
+ var/obj/item/clothing/CH = H.head
+ if (CU.envirosealed && (CH.clothing_flags & STOPSPRESSUREDAMAGE))
+ atmos_sealed = TRUE
+ if(!atmos_sealed)
+ var/datum/gas_mixture/environment = H.loc.return_air()
+ if(environment?.total_moles())
+ if(environment.get_moles(GAS_H2O) >= 1)
+ H.blood_volume -= 15
+ if(prob(50))
+ to_chat(H, "Your ooze melts away rapidly in the water vapor!")
+ if(H.blood_volume <= 672 && environment.get_moles(GAS_PLASMA) >= 1)
+ H.blood_volume += 15
+ if(H.blood_volume < BLOOD_VOLUME_OKAY && prob(5))
+ to_chat(H, "You feel drained!")
+ if(H.blood_volume < BLOOD_VOLUME_OKAY)
+ Cannibalize_Body(H)
+ if(regenerate_limbs)
+ regenerate_limbs.UpdateButtonIcon()
+
+/datum/species/oozeling/proc/Cannibalize_Body(mob/living/carbon/human/H)
+ var/list/limbs_to_consume = list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG) - H.get_missing_limbs()
+ var/obj/item/bodypart/consumed_limb
+ if(!limbs_to_consume.len)
+ H.losebreath++
+ return
+ if(H.get_num_legs(FALSE)) //Legs go before arms
+ limbs_to_consume -= list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM)
+ consumed_limb = H.get_bodypart(pick(limbs_to_consume))
+ consumed_limb.drop_limb()
+ to_chat(H, "Your [consumed_limb] is drawn back into your body, unable to maintain its shape!")
+ qdel(consumed_limb)
+ H.blood_volume += 80
+ H.nutrition += 20
+
+/datum/action/innate/regenerate_limbs
+ name = "Regenerate Limbs"
+ check_flags = AB_CHECK_CONSCIOUS
+ button_icon_state = "slimeheal"
+ icon_icon = 'icons/mob/actions/actions_slime.dmi'
+ background_icon_state = "bg_alien"
+
+/datum/action/innate/regenerate_limbs/IsAvailable()
+ if(..())
+ var/mob/living/carbon/human/H = owner
+ var/list/limbs_to_heal = H.get_missing_limbs()
+ if(limbs_to_heal.len && H.blood_volume >= BLOOD_VOLUME_OKAY+80)
+ return TRUE
+ return FALSE
+
+/datum/action/innate/regenerate_limbs/Activate()
+ var/mob/living/carbon/human/H = owner
+ var/list/limbs_to_heal = H.get_missing_limbs()
+ if(!LAZYLEN(limbs_to_heal))
+ to_chat(H, "You feel intact enough as it is.")
+ return
+ to_chat(H, "You focus intently on your missing [limbs_to_heal.len >= 2 ? "limbs" : "limb"]...")
+ if(H.blood_volume >= 80*limbs_to_heal.len+BLOOD_VOLUME_OKAY)
+ if(do_after(H, 60, target = H))
+ H.regenerate_limbs()
+ H.blood_volume -= 80*limbs_to_heal.len
+ H.nutrition -= 20*limbs_to_heal.len
+ to_chat(H, "...and after a moment you finish reforming!")
+ return
+ if(H.blood_volume >= 80)//We can partially heal some limbs
+ while(H.blood_volume >= BLOOD_VOLUME_OKAY+80 && LAZYLEN(limbs_to_heal))
+ if(do_after(H, 30, target = H))
+ var/healed_limb = pick(limbs_to_heal)
+ H.regenerate_limb(healed_limb)
+ limbs_to_heal -= healed_limb
+ H.blood_volume -= 80
+ H.nutrition -= 20
+ to_chat(H, "...but there is not enough of you to fix everything! You must attain more blood volume to heal completely!")
+ return
+ to_chat(H, "...but there is not enough of you to go around! You must attain more blood volume to heal!")
+
+/datum/species/oozeling/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H)
+ if(chem.type == /datum/reagent/water)
+ if(chem.volume > 10)
+ H.reagents.remove_reagent(chem.type, chem.volume - 10)
+ to_chat(H, "The water you consumed is melting away your insides!")
+ H.blood_volume -= 25
+ H.reagents.remove_reagent(chem.type, chem.metabolization_rate)
+ return TRUE
+ return ..()
diff --git a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
index e886b712a31a9..120e61dd4e9d5 100644
--- a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
+++ b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
@@ -1,6 +1,7 @@
/datum/species/plasmaman
name = "Plasmaman"
- id = "plasmaman"
+ id = SPECIES_PLASMAMAN
+ bodyflag = FLAG_PLASMAMAN
say_mod = "rattles"
sexes = 0
meat = /obj/item/stack/sheet/mineral/plasma
@@ -25,7 +26,7 @@
/datum/species/plasmaman/spec_life(mob/living/carbon/human/H)
var/atmos_sealed = FALSE
- if (H.wear_suit && H.head && istype(H.wear_suit, /obj/item/clothing) && istype(H.head, /obj/item/clothing))
+ if (H.wear_suit && H.head && isclothing(H.wear_suit) && isclothing(H.head))
var/obj/item/clothing/CS = H.wear_suit
var/obj/item/clothing/CH = H.head
if (CS.clothing_flags & CH.clothing_flags & STOPSPRESSUREDAMAGE)
@@ -39,7 +40,7 @@
var/datum/gas_mixture/environment = H.loc.return_air()
if(environment)
if(environment.total_moles())
- if(environment.get_moles(/datum/gas/oxygen) >= 1) //Same threshhold that extinguishes fire
+ if(environment.get_moles(GAS_O2) >= 1) //Same threshhold that extinguishes fire
H.adjust_fire_stacks(0.5)
if(!H.on_fire && H.fire_stacks > 0)
H.visible_message("[H]'s body reacts with the atmosphere and bursts into flames!","Your body reacts with the atmosphere and bursts into flame!")
@@ -59,113 +60,10 @@
no_protection = TRUE
. = ..()
-/datum/species/plasmaman/before_equip_job(datum/job/J, mob/living/carbon/human/H, visualsOnly = FALSE)
- var/current_job = J.title
- var/datum/outfit/plasmaman/O = new /datum/outfit/plasmaman
- switch(current_job)
- if("Chaplain")
- O = new /datum/outfit/plasmaman/chaplain
-
- if("Curator")
- O = new /datum/outfit/plasmaman/curator
-
- if("Janitor")
- O = new /datum/outfit/plasmaman/janitor
-
- if("Botanist")
- O = new /datum/outfit/plasmaman/botany
-
- if("Bartender", "Lawyer", "Barber", "Psychiatrist")
- O = new /datum/outfit/plasmaman/bar
-
- if("Stage Magician")
- O = new /datum/outfit/plasmaman/magic
-
- if("VIP")
- O = new /datum/outfit/plasmaman/vip
-
- if("Debtor")
- O = new /datum/outfit/plasmaman/hobo
-
- if("Cook")
- O = new /datum/outfit/plasmaman/chef
-
- if("Security Officer")
- O = new /datum/outfit/plasmaman/security
-
- if("Deputy")
- O = new /datum/outfit/plasmaman
-
- if("Brig Physician")
- O = new /datum/outfit/plasmaman/secmed
-
- if("Detective")
- O = new /datum/outfit/plasmaman/detective
-
- if("Warden")
- O = new /datum/outfit/plasmaman/warden
-
- if("Cargo Technician", "Quartermaster")
- O = new /datum/outfit/plasmaman/cargo
-
- if("Shaft Miner")
- O = new /datum/outfit/plasmaman/mining
-
- if("Medical Doctor")
- O = new /datum/outfit/plasmaman/medical
-
- if("Paramedic")
- O = new /datum/outfit/plasmaman/emt
-
- if("Chemist")
- O = new /datum/outfit/plasmaman/chemist
-
- if("Geneticist")
- O = new /datum/outfit/plasmaman/genetics
-
- if("Roboticist")
- O = new /datum/outfit/plasmaman/robotics
-
- if("Virologist")
- O = new /datum/outfit/plasmaman/viro
-
- if("Scientist")
- O = new /datum/outfit/plasmaman/science
-
- if("Station Engineer")
- O = new /datum/outfit/plasmaman/engineering
-
- if("Atmospheric Technician")
- O = new /datum/outfit/plasmaman/atmospherics
-
- if("Captain")
- O = new /datum/outfit/plasmaman/command
-
- if("Chief Engineer")
- O = new /datum/outfit/plasmaman/ce
-
- if("Chief Medical Officer")
- O = new /datum/outfit/plasmaman/cmo
-
- if("Head of Security")
- O = new /datum/outfit/plasmaman/hos
-
- if("Research Director")
- O = new /datum/outfit/plasmaman/rd
-
- if("Head of Personnel")
- O = new /datum/outfit/plasmaman/hop
-
- if("Clown")
- O = new /datum/outfit/plasmaman/honk
-
- if("Mime")
- O = new /datum/outfit/plasmaman/mime
-
- H.equipOutfit(O, visualsOnly)
+/datum/species/plasmaman/after_equip_job(datum/job/J, mob/living/carbon/human/H, visualsOnly = FALSE)
H.internal = H.get_item_for_held_index(2)
H.update_internals_hud_icon(1)
- return 0
+ return FALSE
/datum/species/plasmaman/qualifies_for_rank(rank, list/features)
if(rank in GLOB.security_positions)
@@ -186,7 +84,6 @@
return randname
/datum/species/plasmaman/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H)
- . = ..()
if(chem.type == /datum/reagent/consumable/milk)
if(chem.volume > 10)
H.reagents.remove_reagent(chem.type, chem.volume - 10)
@@ -219,3 +116,4 @@
H.emote("sigh")
H.reagents.remove_reagent(chem.type, chem.metabolization_rate)
return TRUE
+ return ..()
diff --git a/code/modules/mob/living/carbon/human/species_types/podpeople.dm b/code/modules/mob/living/carbon/human/species_types/podpeople.dm
index 4ed54c2820509..15c4fb7f92703 100644
--- a/code/modules/mob/living/carbon/human/species_types/podpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/podpeople.dm
@@ -4,7 +4,7 @@
id = "pod"
default_color = "59CE00"
species_traits = list(MUTCOLORS,EYECOLOR)
- inherent_traits = list(TRAIT_ALWAYS_CLEAN)
+ inherent_traits = list(TRAIT_ALWAYS_CLEAN, TRAIT_BEEFRIEND, TRAIT_NONECRODISEASE)
inherent_factions = list("plants", "vines")
attack_verb = "slash"
attack_sound = 'sound/weapons/slice.ogg'
@@ -38,8 +38,9 @@
/datum/species/pod/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H)
if(chem.type == /datum/reagent/toxin/plantbgone)
H.adjustToxLoss(3)
- H.reagents.remove_reagent(chem.type, REAGENTS_METABOLISM)
- return 1
+ H.reagents.remove_reagent(chem.type, chem.metabolization_rate)
+ return TRUE
+ return ..()
/datum/species/pod/on_hit(obj/item/projectile/P, mob/living/carbon/human/H)
switch(P.type)
diff --git a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
index 88e1209577853..a2958f0793997 100644
--- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
@@ -35,7 +35,7 @@
id = "nightmare"
limbs_id = "shadow"
burnmod = 1.5
- no_equip = list(SLOT_WEAR_MASK, SLOT_WEAR_SUIT, SLOT_GLOVES, SLOT_SHOES, SLOT_W_UNIFORM, SLOT_S_STORE)
+ no_equip = list(ITEM_SLOT_MASK, ITEM_SLOT_OCLOTHING, ITEM_SLOT_GLOVES, ITEM_SLOT_FEET, ITEM_SLOT_ICLOTHING, ITEM_SLOT_SUITSTORE)
species_traits = list(NOBLOOD,NO_UNDERWEAR,NO_DNA_COPY,NOTRANSSTING,NOEYESPRITES,NOFLASH)
inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOHUNGER)
mutanteyes = /obj/item/organ/eyes/night_vision/nightmare
@@ -49,7 +49,7 @@
. = ..()
to_chat(C, "[info_text]")
- C.fully_replace_character_name("[pick(GLOB.nightmare_names)]")
+ C.fully_replace_character_name(null, pick(GLOB.nightmare_names))
/datum/species/shadow/nightmare/bullet_act(obj/item/projectile/P, mob/living/carbon/human/H)
var/turf/T = H.loc
@@ -180,40 +180,50 @@
. = ..()
if(!proximity)
return
- if(isopenturf(AM)) //So you can actually melee with it
+ AM.lighteater_act(src)
+
+/mob/living/lighteater_act(obj/item/light_eater/light_eater)
+ if(on_fire)
+ ExtinguishMob()
+ playsound(src, 'sound/items/cig_snuff.ogg', 50, 1)
+ for(var/obj/item/O in src)
+ if(O.light_range && O.light_power)
+ O.lighteater_act(light_eater)
+ if(pulling && pulling.light_range)
+ pulling.lighteater_act(light_eater)
+
+/mob/living/carbon/human/lighteater_act(obj/item/light_eater/light_eater)
+ ..()
+ if(isethereal(src))
+ emp_act(EMP_LIGHT)
+
+/mob/living/silicon/robot/lighteater_act(obj/item/light_eater/light_eater)
+ ..()
+ if(!lamp_cooldown)
+ update_headlamp(TRUE, INFINITY)
+ to_chat(src, "Your headlamp is fried! You'll need a human to help replace it.")
+
+/obj/structure/bonfire/lighteater_act(obj/item/light_eater/light_eater)
+ if(burning)
+ extinguish()
+ playsound(src, 'sound/items/cig_snuff.ogg', 50, 1)
+
+/obj/item/pda/lighteater_act(obj/item/light_eater/light_eater)
+ if(!light_range || !light_power)
+ return
+ set_light(0)
+ light_power = 0
+ fon = FALSE
+ shorted = TRUE
+ update_icon()
+ visible_message("The light in [src] shorts out!")
+
+/obj/item/lighteater_act(obj/item/light_eater/light_eater)
+ if(!light_range || !light_power)
return
- if(isliving(AM))
- var/mob/living/L = AM
- if(isethereal(AM))
- AM.emp_act(EMP_LIGHT)
-
- if(iscyborg(AM))
- var/mob/living/silicon/robot/borg = AM
- if(!borg.lamp_cooldown)
- borg.update_headlamp(TRUE, INFINITY)
- to_chat(borg, "Your headlamp is fried! You'll need a human to help replace it.")
- else
- for(var/obj/item/O in AM)
- if(O.light_range && O.light_power)
- disintegrate(O)
- if(L.pulling && L.pulling.light_range && isitem(L.pulling))
- disintegrate(L.pulling)
- else if(isitem(AM))
- var/obj/item/I = AM
- if(I.light_range && I.light_power)
- disintegrate(I)
-
-/obj/item/light_eater/proc/disintegrate(obj/item/O)
- if(istype(O, /obj/item/pda))
- var/obj/item/pda/PDA = O
- PDA.set_light(0)
- PDA.fon = FALSE
- PDA.f_lum = 0
- PDA.update_icon()
- visible_message("The light in [PDA] shorts out!")
- else
- visible_message("[O] is disintegrated by [src]!")
- O.burn()
+ if(light_eater)
+ visible_message("[src] is disintegrated by [light_eater]!")
+ burn()
playsound(src, 'sound/items/welder.ogg', 50, 1)
#undef HEART_SPECIAL_SHADOWIFY
diff --git a/code/modules/mob/living/carbon/human/species_types/skeletons.dm b/code/modules/mob/living/carbon/human/species_types/skeletons.dm
index 5b2cd09d6ee70..0cef22b50096b 100644
--- a/code/modules/mob/living/carbon/human/species_types/skeletons.dm
+++ b/code/modules/mob/living/carbon/human/species_types/skeletons.dm
@@ -7,7 +7,7 @@
meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/skeleton
species_traits = list(NOBLOOD,NOHUSK)
inherent_traits = list(TRAIT_TOXIMMUNE,TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,\
- TRAIT_PIERCEIMMUNE,TRAIT_NOHUNGER,TRAIT_EASYDISMEMBER,TRAIT_LIMBATTACHMENT,TRAIT_FAKEDEATH,TRAIT_XENO_IMMUNE)
+ TRAIT_PIERCEIMMUNE,TRAIT_NOHUNGER,TRAIT_EASYDISMEMBER,TRAIT_LIMBATTACHMENT,TRAIT_FAKEDEATH,TRAIT_XENO_IMMUNE,TRAIT_NOCLONELOSS)
inherent_biotypes = list(MOB_UNDEAD, MOB_HUMANOID)
mutanttongue = /obj/item/organ/tongue/bone
damage_overlay_type = ""//let's not show bloody wounds or burns over bones.
@@ -24,7 +24,6 @@
//Can still metabolize milk through meme magic
/datum/species/skeleton/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H)
- . = ..()
if(chem.type == /datum/reagent/consumable/milk)
if(chem.volume > 10)
H.reagents.remove_reagent(chem.type, chem.volume - 10)
@@ -57,3 +56,4 @@
H.emote("sigh")
H.reagents.remove_reagent(chem.type, chem.metabolization_rate)
return TRUE
+ return ..()
diff --git a/code/modules/mob/living/carbon/human/species_types/snail.dm b/code/modules/mob/living/carbon/human/species_types/snail.dm
index c65334e7b03a5..83549f8d04a74 100644
--- a/code/modules/mob/living/carbon/human/species_types/snail.dm
+++ b/code/modules/mob/living/carbon/human/species_types/snail.dm
@@ -24,22 +24,23 @@
if(istype(chem,/datum/reagent/consumable/sodiumchloride))
H.adjustFireLoss(2)
playsound(H, 'sound/weapons/sear.ogg', 30, 1)
- H.reagents.remove_reagent(chem.type, REAGENTS_METABOLISM)
- return 1
+ H.reagents.remove_reagent(chem.type, chem.metabolization_rate)
+ return TRUE
+ return ..()
/datum/species/snail/on_species_gain(mob/living/carbon/C, datum/species/old_species, pref_load)
. = ..()
- var/obj/item/storage/backpack/bag = C.get_item_by_slot(SLOT_BACK)
+ var/obj/item/storage/backpack/bag = C.get_item_by_slot(ITEM_SLOT_BACK)
if(!istype(bag, /obj/item/storage/backpack/snail))
if(C.dropItemToGround(bag)) //returns TRUE even if its null
- C.equip_to_slot_or_del(new /obj/item/storage/backpack/snail(C), SLOT_BACK)
+ C.equip_to_slot_or_del(new /obj/item/storage/backpack/snail(C), ITEM_SLOT_BACK)
ADD_TRAIT(C, TRAIT_NOSLIPALL, SPECIES_TRAIT)
/datum/species/snail/on_species_loss(mob/living/carbon/C)
. = ..()
qdel(C.GetComponent(/datum/component/snailcrawl))
REMOVE_TRAIT(C, TRAIT_NOSLIPALL, SPECIES_TRAIT)
- var/obj/item/storage/backpack/bag = C.get_item_by_slot(SLOT_BACK)
+ var/obj/item/storage/backpack/bag = C.get_item_by_slot(ITEM_SLOT_BACK)
if(istype(bag, /obj/item/storage/backpack/snail))
bag.emptyStorage()
C.doUnEquip(bag, TRUE, no_move = TRUE)
@@ -52,7 +53,7 @@
item_state = "snailshell"
lefthand_file = 'icons/mob/inhands/equipment/backpack_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/backpack_righthand.dmi'
- armor = list("melee" = 20, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
+ armor = list("melee" = 20, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0, "stamina" = 0)
max_integrity = 200
resistance_flags = FIRE_PROOF | ACID_PROOF
diff --git a/code/modules/mob/living/carbon/human/species_types/squidpeople.dm b/code/modules/mob/living/carbon/human/species_types/squidpeople.dm
index 3085b00e675c0..529948bd4774a 100644
--- a/code/modules/mob/living/carbon/human/species_types/squidpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/squidpeople.dm
@@ -1,32 +1,34 @@
/datum/species/squid
- name = "Squidperson"
- id = "squid"
- default_color = "b8dfda"
- species_traits = list(MUTCOLORS,EYECOLOR,TRAIT_EASYDISMEMBER)
- inherent_traits = list(TRAIT_NOSLIPALL)
- default_features = list("mcolor" = "FFF") // bald
- speedmod = 0.5
- burnmod = 1.5
- heatmod = 1.4
- coldmod = 1.5
- punchdamage = 7 // Lower max damage in melee. It's just a tentacle
- changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | SLIME_EXTRACT
- attack_verb = "lash"
- attack_sound = 'sound/weapons/whip.ogg'
- miss_sound = 'sound/weapons/etherealmiss.ogg'
- grab_sound = 'sound/weapons/whipgrab.ogg'
- deathsound = 'sound/voice/hiss1.ogg'
- use_skintones = 0
- no_equip = list(SLOT_SHOES)
- skinned_type = /obj/item/stack/sheet/animalhide/human
- toxic_food = FRIED
- species_language_holder = /datum/language_holder/squid
+ name = "Squidperson"
+ id = SPECIES_SQUID
+ bodyflag = FLAG_SQUID
+ default_color = "b8dfda"
+ species_traits = list(MUTCOLORS,EYECOLOR,NOSOCKS)
+ inherent_traits = list(TRAIT_NOSLIPALL,TRAIT_EASYDISMEMBER)
+ default_features = list("mcolor" = "FFF") // bald
+ speedmod = 0.5
+ burnmod = 1.5
+ heatmod = 1.4
+ coldmod = 1.5
+ punchdamage = 7 // Lower max damage in melee. It's just a tentacle
+ changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | SLIME_EXTRACT
+ attack_verb = "lash"
+ attack_sound = 'sound/weapons/whip.ogg'
+ miss_sound = 'sound/weapons/etherealmiss.ogg'
+ grab_sound = 'sound/weapons/whipgrab.ogg'
+ deathsound = 'sound/voice/hiss1.ogg'
+ use_skintones = 0
+ no_equip = list(ITEM_SLOT_FEET)
+ skinned_type = /obj/item/stack/sheet/animalhide/human
+ toxic_food = FRIED
+ species_language_holder = /datum/language_holder/squid
+ swimming_component = /datum/component/swimming/squid
/mob/living/carbon/human/species/squid
- race = /datum/species/squid
+ race = /datum/species/squid
/datum/species/squid/qualifies_for_rank(rank, list/features)
- return TRUE
+ return TRUE
/datum/species/squid/random_name(gender,unique,lastname)
if(unique)
@@ -37,10 +39,10 @@
return randname
/proc/random_unique_squid_name(attempts_to_find_unique_name=10, genderToFind)
- for(var/i in 1 to attempts_to_find_unique_name)
- . = capitalize(squid_name(genderToFind))
- if(!findname(.))
- break
+ for(var/i in 1 to attempts_to_find_unique_name)
+ . = capitalize(squid_name(genderToFind))
+ if(!findname(.))
+ break
/datum/species/squid/after_equip_job(datum/job/J, mob/living/carbon/human/H)
var/datum/action/innate/squid_change/S = new
@@ -50,7 +52,7 @@
fixed_mut_color = rgb(128,128,128)
H.update_body()
var/datum/action/innate/squid_change/S = locate(/datum/action/innate/squid_change) in H.actions
- S?.Remove(H)
+ qdel(S)
/datum/action/innate/squid_change
name = "Color Change"
@@ -60,9 +62,9 @@
var/cooldown = 0
/datum/action/innate/squid_change/IsAvailable()
- if(cooldown > world.time)
- return FALSE
- return ..()
+ if(cooldown > world.time)
+ return FALSE
+ return ..()
/datum/action/innate/squid_change/Activate()
var/mob/living/carbon/human/H = owner
@@ -79,5 +81,5 @@
UpdateButtonIcon()
/datum/action/innate/squid_change/Deactivate()
- active = FALSE
- UpdateButtonIcon()
+ active = FALSE
+ UpdateButtonIcon()
diff --git a/code/modules/mob/living/carbon/human/species_types/supersoldier.dm b/code/modules/mob/living/carbon/human/species_types/supersoldier.dm
index 36f296b80944a..59edf3462c08c 100644
--- a/code/modules/mob/living/carbon/human/species_types/supersoldier.dm
+++ b/code/modules/mob/living/carbon/human/species_types/supersoldier.dm
@@ -1,7 +1,8 @@
/datum/species/human/supersoldier
name = "Super Soldier" //inherited from the real species, for health scanners and things
id = "supersoldier"
- species_traits = list(NOTRANSSTING) //all of these + whatever we inherit from the real species
+ limbs_id = "human"
+ species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,NOTRANSSTING) //all of these + whatever we inherit from the real species
inherent_traits = list(TRAIT_NOLIMBDISABLE,TRAIT_NOHUNGER,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_IGNORESLOWDOWN,TRAIT_IGNOREDAMAGESLOWDOWN,TRAIT_STUNIMMUNE,TRAIT_CONFUSEIMMUNE,TRAIT_SLEEPIMMUNE,TRAIT_PUSHIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOSLIPALL,TRAIT_THERMAL_VISION,TRAIT_STRONG_GRABBER,TRAIT_LAW_ENFORCEMENT_METABOLISM,TRAIT_ALWAYS_CLEAN,TRAIT_FEARLESS)
mutanteyes = /obj/item/organ/eyes/night_vision
changesource_flags = MIRROR_BADMIN | ERT_SPAWN
@@ -16,4 +17,4 @@
if(light_amount > 0) //if there's any light, heal
H.heal_overall_damage(1,1, 0, BODYPART_ORGANIC)
H.adjustToxLoss(-1)
- H.adjustOxyLoss(-1)
\ No newline at end of file
+ H.adjustOxyLoss(-1)
diff --git a/code/modules/mob/living/carbon/human/species_types/synths.dm b/code/modules/mob/living/carbon/human/species_types/synths.dm
deleted file mode 100644
index 88f3ef726ca6d..0000000000000
--- a/code/modules/mob/living/carbon/human/species_types/synths.dm
+++ /dev/null
@@ -1,132 +0,0 @@
-/datum/species/synth
- name = "Synth" //inherited from the real species, for health scanners and things
- id = "synth"
- say_mod = "beep boops" //inherited from a user's real species
- sexes = 0
- species_traits = list(NOTRANSSTING) //all of these + whatever we inherit from the real species
- inherent_traits = list(TRAIT_NODISMEMBER,TRAIT_NOLIMBDISABLE,TRAIT_NOHUNGER,TRAIT_NOBREATH)
- inherent_biotypes = list(MOB_ROBOTIC, MOB_HUMANOID)
- meat = null
- damage_overlay_type = "synth"
- limbs_id = "synth"
- var/disguise_fail_health = 75 //When their health gets to this level their synthflesh partially falls off
- var/datum/species/fake_species //a species to do most of our work for us, unless we're damaged
- var/list/initial_species_traits //for getting these values back for assume_disguise()
- var/list/initial_inherent_traits
- changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC
- species_language_holder = /datum/language_holder/synthetic
-
-
-/datum/species/synth/New()
- initial_species_traits = species_traits.Copy()
- initial_inherent_traits = inherent_traits.Copy()
- ..()
-
-/datum/species/synth/military
- name = "Military Synth"
- id = "military_synth"
- armor = 25
- punchdamage = 14
- disguise_fail_health = 50
- changesource_flags = MIRROR_BADMIN | WABBAJACK
-
-/datum/species/synth/on_species_gain(mob/living/carbon/human/H, datum/species/old_species)
- ..()
- assume_disguise(old_species, H)
- RegisterSignal(H, COMSIG_MOB_SAY, .proc/handle_speech)
-
-/datum/species/synth/on_species_loss(mob/living/carbon/human/H)
- . = ..()
- UnregisterSignal(H, COMSIG_MOB_SAY)
-
-/datum/species/synth/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H)
- if(chem.type == /datum/reagent/medicine/synthflesh)
- chem.reaction_mob(H, TOUCH, 2 ,0) //heal a little
- H.reagents.remove_reagent(chem.type, REAGENTS_METABOLISM)
- return 1
- else
- return ..()
-
-
-/datum/species/synth/proc/assume_disguise(datum/species/S, mob/living/carbon/human/H)
- if(S && !istype(S, type))
- name = S.name
- say_mod = S.say_mod
- sexes = S.sexes
- species_traits = initial_species_traits.Copy()
- inherent_traits = initial_inherent_traits.Copy()
- species_traits |= S.species_traits
- inherent_traits |= S.inherent_traits
- attack_verb = S.attack_verb
- attack_sound = S.attack_sound
- miss_sound = S.miss_sound
- meat = S.meat
- mutant_bodyparts = S.mutant_bodyparts.Copy()
- mutant_organs = S.mutant_organs.Copy()
- default_features = S.default_features.Copy()
- nojumpsuit = S.nojumpsuit
- no_equip = S.no_equip.Copy()
- limbs_id = S.limbs_id
- use_skintones = S.use_skintones
- fixed_mut_color = S.fixed_mut_color
- hair_color = S.hair_color
- fake_species = new S.type
- else
- name = initial(name)
- say_mod = initial(say_mod)
- species_traits = initial_species_traits.Copy()
- inherent_traits = initial_inherent_traits.Copy()
- attack_verb = initial(attack_verb)
- attack_sound = initial(attack_sound)
- miss_sound = initial(miss_sound)
- mutant_bodyparts = list()
- default_features = list()
- nojumpsuit = initial(nojumpsuit)
- no_equip = list()
- qdel(fake_species)
- fake_species = null
- meat = initial(meat)
- limbs_id = "synth"
- use_skintones = 0
- sexes = 0
- fixed_mut_color = ""
- hair_color = ""
-
- for(var/X in H.bodyparts) //propagates the damage_overlay changes
- var/obj/item/bodypart/BP = X
- BP.update_limb()
- H.update_body_parts() //to update limb icon cache with the new damage overlays
-
-//Proc redirects:
-//Passing procs onto the fake_species, to ensure we look as much like them as possible
-
-/datum/species/synth/handle_hair(mob/living/carbon/human/H, forced_colour)
- if(fake_species)
- fake_species.handle_hair(H, forced_colour)
- else
- return ..()
-
-
-/datum/species/synth/handle_body(mob/living/carbon/human/H)
- if(fake_species)
- fake_species.handle_body(H)
- else
- return ..()
-
-
-/datum/species/synth/handle_mutant_bodyparts(mob/living/carbon/human/H, forced_colour)
- if(fake_species)
- fake_species.handle_body(H,forced_colour)
- else
- return ..()
-
-
-/datum/species/synth/proc/handle_speech(datum/source, list/speech_args)
- if (isliving(source)) // yeah it's gonna be living but just to be clean
- var/mob/living/L = source
- if(fake_species && L.health > disguise_fail_health)
- switch (fake_species.type)
- if (/datum/species/golem/bananium)
- speech_args[SPEECH_SPANS] |= SPAN_CLOWN
- if (/datum/species/golem/clockwork)
- speech_args[SPEECH_SPANS] |= SPAN_ROBOT
diff --git a/code/modules/mob/living/carbon/human/species_types/zombies.dm b/code/modules/mob/living/carbon/human/species_types/zombies.dm
index f51b76a3f81e3..d168481c99a94 100644
--- a/code/modules/mob/living/carbon/human/species_types/zombies.dm
+++ b/code/modules/mob/living/carbon/human/species_types/zombies.dm
@@ -8,7 +8,8 @@
sexes = 0
meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/zombie
species_traits = list(NOBLOOD,NOZOMBIE,NOTRANSSTING)
- inherent_traits = list(TRAIT_TOXIMMUNE,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_EASYDISMEMBER,TRAIT_LIMBATTACHMENT,TRAIT_NOBREATH,TRAIT_NODEATH,TRAIT_FAKEDEATH)
+ inherent_traits = list(TRAIT_TOXIMMUNE,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_EASYDISMEMBER,\
+ TRAIT_LIMBATTACHMENT,TRAIT_NOBREATH,TRAIT_NODEATH,TRAIT_FAKEDEATH,TRAIT_NOCLONELOSS)
inherent_biotypes = list(MOB_UNDEAD, MOB_HUMANOID)
mutanttongue = /obj/item/organ/tongue/zombie
var/static/list/spooks = list('sound/hallucinations/growl1.ogg','sound/hallucinations/growl2.ogg','sound/hallucinations/growl3.ogg','sound/hallucinations/veryfar_noise.ogg','sound/hallucinations/wail.ogg')
@@ -61,7 +62,7 @@
playsound(C, pick(spooks), 50, TRUE, 10)
//Congrats you somehow died so hard you stopped being a zombie
-/datum/species/zombie/infectious/spec_death(mob/living/carbon/C)
+/datum/species/zombie/infectious/spec_death(gibbed, mob/living/carbon/C)
. = ..()
var/obj/item/organ/zombie_infection/infection
infection = C.getorganslot(ORGAN_SLOT_ZOMBIE)
diff --git a/code/modules/mob/living/carbon/human/suicides.dm b/code/modules/mob/living/carbon/human/suicides.dm
index bde239a36d473..32bfd92ed556a 100644
--- a/code/modules/mob/living/carbon/human/suicides.dm
+++ b/code/modules/mob/living/carbon/human/suicides.dm
@@ -2,7 +2,7 @@
suicide_log()
adjustBruteLoss(max(200 - getToxLoss() - getFireLoss() - getBruteLoss() - getOxyLoss(), 0))
death(FALSE)
- ghostize(FALSE) // Disallows reentering body and disassociates mind
+ ghostize(FALSE,SENTIENCE_ERASE) // Disallows reentering body and disassociates mind
/mob/living/carbon/human/proc/disarm_suicide()
var/suicide_message = "[src] is ripping [p_their()] own arms off! It looks like [p_theyre()] trying to commit suicide." //heheh get it?
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index 5df4c01afa812..0372cfc4ce959 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -101,7 +101,7 @@ There are several things that need to be remembered:
remove_overlay(UNIFORM_LAYER)
if(client && hud_used)
- var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_W_UNIFORM]
+ var/atom/movable/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_ICLOTHING) + 1]
inv.update_icon()
if(istype(w_uniform, /obj/item/clothing/under))
@@ -131,8 +131,11 @@ There are several things that need to be remembered:
if(G == "f" && U.fitted != NO_FEMALE_UNIFORM)
uniform_overlay = U.build_worn_icon(state = "[t_color]", default_layer = UNIFORM_LAYER, default_icon_file = 'icons/mob/uniform.dmi', isinhands = FALSE, femaleuniform = U.fitted)
+ var/icon_file = 'icons/mob/uniform.dmi'
if(!uniform_overlay)
- uniform_overlay = U.build_worn_icon(state = "[t_color]", default_layer = UNIFORM_LAYER, default_icon_file = 'icons/mob/uniform.dmi', isinhands = FALSE)
+ if(U.sprite_sheets & (dna?.species.bodyflag))
+ icon_file = dna.species.get_custom_icons("uniform")
+ uniform_overlay = U.build_worn_icon(state = "[t_color]", default_layer = UNIFORM_LAYER, default_icon_file = icon_file, isinhands = FALSE)
if(OFFSET_UNIFORM in dna.species.offset_features)
@@ -148,19 +151,23 @@ There are several things that need to be remembered:
remove_overlay(ID_LAYER)
if(client && hud_used)
- var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_WEAR_ID]
+ var/atom/movable/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_ID) + 1]
inv.update_icon()
var/mutable_appearance/id_overlay = overlays_standing[ID_LAYER]
if(wear_id)
+ var/icon_file = 'icons/mob/mob.dmi'
wear_id.screen_loc = ui_id
if(client && hud_used && hud_used.hud_shown)
client.screen += wear_id
update_observer_view(wear_id)
-
+ if(istype(wear_id, /obj/item))
+ var/obj/item/I = wear_id
+ if(I.sprite_sheets & dna?.species.bodyflag)
+ icon_file = dna.species.get_custom_icons("generic")
//TODO: add an icon file for ID slot stuff, so it's less snowflakey
- id_overlay = wear_id.build_worn_icon(state = wear_id.item_state, default_layer = ID_LAYER, default_icon_file = 'icons/mob/mob.dmi')
+ id_overlay = wear_id.build_worn_icon(state = wear_id.item_state, default_layer = ID_LAYER, default_icon_file = icon_file)
if(OFFSET_ID in dna.species.offset_features)
id_overlay.pixel_x += dna.species.offset_features[OFFSET_ID][1]
id_overlay.pixel_y += dna.species.offset_features[OFFSET_ID][2]
@@ -172,8 +179,8 @@ There are several things that need to be remembered:
/mob/living/carbon/human/update_inv_gloves()
remove_overlay(GLOVES_LAYER)
- if(client && hud_used && hud_used.inv_slots[SLOT_GLOVES])
- var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_GLOVES]
+ if(client && hud_used && hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_GLOVES) + 1])
+ var/atom/movable/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_GLOVES) + 1]
inv.update_icon()
if(!gloves && bloody_hands)
@@ -188,6 +195,11 @@ There are several things that need to be remembered:
var/mutable_appearance/gloves_overlay = overlays_standing[GLOVES_LAYER]
if(gloves)
+ var/icon_file = 'icons/mob/hands.dmi'
+ if(istype(gloves, /obj/item/clothing/gloves))
+ var/obj/item/clothing/gloves/G = gloves
+ if(G.sprite_sheets & (dna?.species.bodyflag))
+ icon_file = dna.species.get_custom_icons("gloves")
gloves.screen_loc = ui_gloves
if(client && hud_used && hud_used.hud_shown)
if(hud_used.inventory_shown)
@@ -196,7 +208,7 @@ There are several things that need to be remembered:
var/t_state = gloves.item_state
if(!t_state)
t_state = gloves.icon_state
- overlays_standing[GLOVES_LAYER] = gloves.build_worn_icon(state = t_state, default_layer = GLOVES_LAYER, default_icon_file = 'icons/mob/hands.dmi')
+ overlays_standing[GLOVES_LAYER] = gloves.build_worn_icon(state = t_state, default_layer = GLOVES_LAYER, default_icon_file = icon_file)
gloves_overlay = overlays_standing[GLOVES_LAYER]
if(OFFSET_GLOVES in dna.species.offset_features)
gloves_overlay.pixel_x += dna.species.offset_features[OFFSET_GLOVES][1]
@@ -212,17 +224,21 @@ There are several things that need to be remembered:
return
if(client && hud_used)
- var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_GLASSES]
+ var/atom/movable/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_EYES) + 1]
inv.update_icon()
-
if(glasses)
glasses.screen_loc = ui_glasses //...draw the item in the inventory screen
+ if(istype(glasses, /obj/item/clothing/glasses))
+ var/obj/item/clothing/glasses/G = glasses
+ var/icon_file = 'icons/mob/eyes.dmi'
+ if(G.sprite_sheets & (dna?.species.bodyflag))
+ icon_file = dna.species.get_custom_icons("glasses")
if(client && hud_used && hud_used.hud_shown)
if(hud_used.inventory_shown) //if the inventory is open ...
client.screen += glasses //Either way, add the item to the HUD
update_observer_view(glasses,1)
if(!(head && (head.flags_inv & HIDEEYES)) && !(wear_mask && (wear_mask.flags_inv & HIDEEYES)))
- overlays_standing[GLASSES_LAYER] = glasses.build_worn_icon(state = glasses.icon_state, default_layer = GLASSES_LAYER, default_icon_file = 'icons/mob/eyes.dmi')
+ overlays_standing[GLASSES_LAYER] = glasses.build_worn_icon(state = glasses.icon_state, default_layer = GLASSES_LAYER, default_icon_file = icon_file)
var/mutable_appearance/glasses_overlay = overlays_standing[GLASSES_LAYER]
if(glasses_overlay)
@@ -240,17 +256,22 @@ There are several things that need to be remembered:
return
if(client && hud_used)
- var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_EARS]
+ var/atom/movable/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_EARS) + 1]
inv.update_icon()
if(ears)
+ var/icon_file = 'icons/mob/ears.dmi'
+ if(istype(ears, /obj/item))
+ var/obj/item/E = ears
+ if(E.sprite_sheets & (dna?.species.bodyflag))
+ icon_file = dna.species.get_custom_icons("ears")
ears.screen_loc = ui_ears //move the item to the appropriate screen loc
if(client && hud_used && hud_used.hud_shown)
if(hud_used.inventory_shown) //if the inventory is open
client.screen += ears //add it to the client's screen
update_observer_view(ears,1)
- overlays_standing[EARS_LAYER] = ears.build_worn_icon(state = ears.icon_state, default_layer = EARS_LAYER, default_icon_file = 'icons/mob/ears.dmi')
+ overlays_standing[EARS_LAYER] = ears.build_worn_icon(state = ears.icon_state, default_layer = EARS_LAYER, default_icon_file = icon_file)
var/mutable_appearance/ears_overlay = overlays_standing[EARS_LAYER]
if(OFFSET_EARS in dna.species.offset_features)
ears_overlay.pixel_x += dna.species.offset_features[OFFSET_EARS][1]
@@ -258,6 +279,32 @@ There are several things that need to be remembered:
overlays_standing[EARS_LAYER] = ears_overlay
apply_overlay(EARS_LAYER)
+/mob/living/carbon/human/update_inv_neck()
+ remove_overlay(NECK_LAYER)
+
+ if(client && hud_used)
+ var/atom/movable/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_NECK) + 1]
+ inv.update_icon()
+
+ if(wear_neck)
+ wear_neck.screen_loc = ui_neck
+ if(client && hud_used && hud_used.hud_shown)
+ if(hud_used.inventory_shown) //if the inventory is open
+ client.screen += wear_neck //add it to the client's screen
+ update_observer_view(wear_neck,1)
+ if(!(ITEM_SLOT_NECK in check_obscured_slots()))
+ var/icon_file = 'icons/mob/neck.dmi'
+ if(istype(wear_neck, /obj/item))
+ var/obj/item/N = wear_neck
+ if(N.sprite_sheets & dna?.species.bodyflag)
+ icon_file = dna.species.get_custom_icons("neck")
+ overlays_standing[NECK_LAYER] = wear_neck.build_worn_icon(state = wear_neck.icon_state, default_layer = NECK_LAYER, default_icon_file = icon_file)
+ var/mutable_appearance/neck_overlay = overlays_standing[NECK_LAYER]
+ if(OFFSET_NECK in dna.species.offset_features)
+ neck_overlay.pixel_x += dna.species.offset_features[OFFSET_NECK][1]
+ neck_overlay.pixel_y += dna.species.offset_features[OFFSET_NECK][2]
+ overlays_standing[NECK_LAYER] = neck_overlay
+ apply_overlay(NECK_LAYER)
/mob/living/carbon/human/update_inv_shoes()
remove_overlay(SHOES_LAYER)
@@ -266,16 +313,21 @@ There are several things that need to be remembered:
return
if(client && hud_used)
- var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_SHOES]
+ var/atom/movable/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_FEET) + 1]
inv.update_icon()
if(shoes)
+ var/icon_file = 'icons/mob/feet.dmi'
+ if(istype(shoes, /obj/item/clothing/shoes))
+ var/obj/item/clothing/shoes/S = shoes
+ if(S.sprite_sheets & (dna?.species.bodyflag))
+ icon_file = dna.species.get_custom_icons("shoes")
shoes.screen_loc = ui_shoes //move the item to the appropriate screen loc
if(client && hud_used && hud_used.hud_shown)
if(hud_used.inventory_shown) //if the inventory is open
client.screen += shoes //add it to client's screen
update_observer_view(shoes,1)
- overlays_standing[SHOES_LAYER] = shoes.build_worn_icon(state = shoes.icon_state, default_layer = SHOES_LAYER, default_icon_file = 'icons/mob/feet.dmi')
+ overlays_standing[SHOES_LAYER] = shoes.build_worn_icon(state = shoes.icon_state, default_layer = SHOES_LAYER, default_icon_file = icon_file)
var/mutable_appearance/shoes_overlay = overlays_standing[SHOES_LAYER]
if(OFFSET_SHOES in dna.species.offset_features)
shoes_overlay.pixel_x += dna.species.offset_features[OFFSET_SHOES][1]
@@ -289,7 +341,7 @@ There are several things that need to be remembered:
remove_overlay(SUIT_STORE_LAYER)
if(client && hud_used)
- var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_S_STORE]
+ var/atom/movable/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_SUITSTORE) + 1]
inv.update_icon()
if(s_store)
@@ -310,8 +362,24 @@ There are several things that need to be remembered:
/mob/living/carbon/human/update_inv_head()
- ..()
+ remove_overlay(HEAD_LAYER)
+
+ if(!get_bodypart(BODY_ZONE_HEAD)) //Decapitated
+ return
+
+ if(client && hud_used && hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_HEAD) + 1])
+ var/atom/movable/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_HEAD) + 1]
+ inv.update_icon()
+
update_mutant_bodyparts()
+ if(head)
+ update_hud_head(head)
+ var/icon_file = 'icons/mob/head.dmi'
+ if(istype(head, /obj/item/clothing/head))
+ var/obj/item/clothing/head/HE = head
+ if(HE.sprite_sheets & (dna?.species.bodyflag))
+ icon_file = dna.species.get_custom_icons("head")
+ overlays_standing[HEAD_LAYER] = head.build_worn_icon(state = head.icon_state, default_layer = HEAD_LAYER, default_icon_file = icon_file)
var/mutable_appearance/head_overlay = overlays_standing[HEAD_LAYER]
if(head_overlay)
remove_overlay(HEAD_LAYER)
@@ -325,10 +393,15 @@ There are several things that need to be remembered:
remove_overlay(BELT_LAYER)
if(client && hud_used)
- var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_BELT]
+ var/atom/movable/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_BELT) + 1]
inv.update_icon()
if(belt)
+ var/icon_file = 'icons/mob/belt.dmi'
+ if(istype(belt, /obj/item/storage/belt))
+ var/obj/item/storage/belt/B = belt
+ if(B.sprite_sheets & (dna?.species.bodyflag))
+ icon_file = dna.species.get_custom_icons("belt")
belt.screen_loc = ui_belt
if(client && hud_used && hud_used.hud_shown)
client.screen += belt
@@ -338,12 +411,12 @@ There are several things that need to be remembered:
if(!t_state)
t_state = belt.icon_state
- overlays_standing[BELT_LAYER] = belt.build_worn_icon(state = t_state, default_layer = BELT_LAYER, default_icon_file = 'icons/mob/belt.dmi')
+ overlays_standing[BELT_LAYER] = belt.build_worn_icon(state = t_state, default_layer = BELT_LAYER, default_icon_file = icon_file)
var/mutable_appearance/belt_overlay = overlays_standing[BELT_LAYER]
if(OFFSET_BELT in dna.species.offset_features)
belt_overlay.pixel_x += dna.species.offset_features[OFFSET_BELT][1]
belt_overlay.pixel_y += dna.species.offset_features[OFFSET_BELT][2]
- overlays_standing[BELT_LAYER] = belt_overlay
+ overlays_standing[BELT_LAYER] = belt_overlay
apply_overlay(BELT_LAYER)
@@ -353,22 +426,26 @@ There are several things that need to be remembered:
remove_overlay(SUIT_LAYER)
if(client && hud_used)
- var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_WEAR_SUIT]
+ var/atom/movable/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_OCLOTHING) + 1]
inv.update_icon()
if(istype(wear_suit, /obj/item/clothing/suit))
+ var/icon_file = 'icons/mob/suit.dmi'
+ var/obj/item/clothing/suit/S = wear_suit
+ if(S.sprite_sheets & (dna?.species.bodyflag))
+ icon_file = dna.species.get_custom_icons("suit")
wear_suit.screen_loc = ui_oclothing
if(client && hud_used && hud_used.hud_shown)
if(hud_used.inventory_shown)
client.screen += wear_suit
update_observer_view(wear_suit,1)
- overlays_standing[SUIT_LAYER] = wear_suit.build_worn_icon(state = wear_suit.icon_state, default_layer = SUIT_LAYER, default_icon_file = 'icons/mob/suit.dmi')
+ overlays_standing[SUIT_LAYER] = wear_suit.build_worn_icon(state = wear_suit.icon_state, default_layer = SUIT_LAYER, default_icon_file = icon_file)
var/mutable_appearance/suit_overlay = overlays_standing[SUIT_LAYER]
if(OFFSET_SUIT in dna.species.offset_features)
suit_overlay.pixel_x += dna.species.offset_features[OFFSET_SUIT][1]
suit_overlay.pixel_y += dna.species.offset_features[OFFSET_SUIT][2]
- overlays_standing[SUIT_LAYER] = suit_overlay
+ overlays_standing[SUIT_LAYER] = suit_overlay
update_hair()
update_mutant_bodyparts()
@@ -377,12 +454,12 @@ There are several things that need to be remembered:
/mob/living/carbon/human/update_inv_pockets()
if(client && hud_used)
- var/obj/screen/inventory/inv
+ var/atom/movable/screen/inventory/inv
- inv = hud_used.inv_slots[SLOT_L_STORE]
+ inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_LPOCKET) + 1]
inv.update_icon()
- inv = hud_used.inv_slots[SLOT_R_STORE]
+ inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_RPOCKET) + 1]
inv.update_icon()
if(l_store)
@@ -399,25 +476,54 @@ There are several things that need to be remembered:
/mob/living/carbon/human/update_inv_wear_mask()
- ..()
- var/mutable_appearance/mask_overlay = overlays_standing[FACEMASK_LAYER]
- if(mask_overlay)
- remove_overlay(FACEMASK_LAYER)
- if(OFFSET_FACEMASK in dna.species.offset_features)
- mask_overlay.pixel_x += dna.species.offset_features[OFFSET_FACEMASK][1]
- mask_overlay.pixel_y += dna.species.offset_features[OFFSET_FACEMASK][2]
- overlays_standing[FACEMASK_LAYER] = mask_overlay
+ remove_overlay(FACEMASK_LAYER)
+
+ if(!get_bodypart(BODY_ZONE_HEAD)) //Decapitated
+ return
+
+ if(client && hud_used)
+ var/atom/movable/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_MASK) + 1]
+ inv.update_icon()
+
+ if(wear_mask)
+ update_hud_wear_mask(wear_mask)
+ var/icon_file = 'icons/mob/mask.dmi'
+ if(istype(wear_mask, /obj/item/clothing/mask))
+ var/obj/item/clothing/mask/M = wear_mask
+ if(M.sprite_sheets & dna?.species.bodyflag)
+ icon_file = dna.species.get_custom_icons("mask")
+ overlays_standing[FACEMASK_LAYER] = wear_mask.build_worn_icon(state = wear_mask.icon_state, default_layer = FACEMASK_LAYER, default_icon_file = icon_file)
+ var/mutable_appearance/mask_overlay = overlays_standing[FACEMASK_LAYER]
+ if(mask_overlay)
+ remove_overlay(FACEMASK_LAYER)
+ if(OFFSET_FACEMASK in dna.species.offset_features)
+ mask_overlay.pixel_x += dna.species.offset_features[OFFSET_FACEMASK][1]
+ mask_overlay.pixel_y += dna.species.offset_features[OFFSET_FACEMASK][2]
+ overlays_standing[FACEMASK_LAYER] = mask_overlay
apply_overlay(FACEMASK_LAYER)
update_mutant_bodyparts() //e.g. upgate needed because mask now hides lizard snout
/mob/living/carbon/human/update_inv_back()
- ..()
- var/mutable_appearance/back_overlay = overlays_standing[BACK_LAYER]
- if(back_overlay)
- remove_overlay(BACK_LAYER)
- if(OFFSET_BACK in dna.species.offset_features)
- back_overlay.pixel_x += dna.species.offset_features[OFFSET_BACK][1]
- back_overlay.pixel_y += dna.species.offset_features[OFFSET_BACK][2]
+ remove_overlay(BACK_LAYER)
+
+ if(client && hud_used && hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_BACK) + 1])
+ var/atom/movable/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_BACK) + 1]
+ inv.update_icon()
+
+ if(back)
+ update_hud_back(back)
+ var/icon_file = 'icons/mob/back.dmi'
+ if(istype(back, /obj/item))
+ var/obj/item/I = back
+ if(I.sprite_sheets & dna?.species.bodyflag)
+ icon_file = dna.species.get_custom_icons("back")
+ overlays_standing[BACK_LAYER] = back.build_worn_icon(state = back.icon_state, default_layer = BACK_LAYER, default_icon_file = icon_file)
+ var/mutable_appearance/back_overlay = overlays_standing[BACK_LAYER]
+ if(back_overlay)
+ remove_overlay(BACK_LAYER)
+ if(OFFSET_BACK in dna.species.offset_features)
+ back_overlay.pixel_x += dna.species.offset_features[OFFSET_BACK][1]
+ back_overlay.pixel_y += dna.species.offset_features[OFFSET_BACK][2]
overlays_standing[BACK_LAYER] = back_overlay
apply_overlay(BACK_LAYER)
@@ -425,9 +531,55 @@ There are several things that need to be remembered:
remove_overlay(LEGCUFF_LAYER)
clear_alert("legcuffed")
if(legcuffed)
- overlays_standing[LEGCUFF_LAYER] = mutable_appearance('icons/mob/mob.dmi', "legcuff1", -LEGCUFF_LAYER)
+ var/path = dna?.species.get_custom_icons("generic")
+ if(!path)
+ path = 'icons/mob/mob.dmi'
+ overlays_standing[LEGCUFF_LAYER] = mutable_appearance(path, "legcuff1", -LEGCUFF_LAYER)
apply_overlay(LEGCUFF_LAYER)
- throw_alert("legcuffed", /obj/screen/alert/restrained/legcuffed, new_master = src.legcuffed)
+ throw_alert("legcuffed", /atom/movable/screen/alert/restrained/legcuffed, new_master = src.legcuffed)
+
+/mob/living/carbon/human/update_inv_hands()
+ remove_overlay(HANDS_LAYER)
+ if (handcuffed)
+ drop_all_held_items()
+ return
+
+ var/list/hands = list()
+ for(var/obj/item/I in held_items)
+ if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD)
+ I.screen_loc = ui_hand_position(get_held_index_of_item(I))
+ client.screen += I
+ if(observers?.len)
+ for(var/M in observers)
+ var/mob/dead/observe = M
+ if(observe.client && observe.client.eye == src)
+ observe.client.screen += I
+ else
+ observers -= observe
+ if(!observers.len)
+ observers = null
+ break
+
+ var/t_state = I.item_state
+ if(!t_state)
+ t_state = I.icon_state
+
+ var/icon_file = I.lefthand_file
+ var/mutable_appearance/hand_overlay
+ if(get_held_index_of_item(I) % 2 == 0)
+ icon_file = I.righthand_file
+ hand_overlay = I.build_worn_icon(state = t_state, default_layer = HANDS_LAYER, default_icon_file = icon_file, isinhands = TRUE)
+ if(OFFSET_RIGHT_HAND in dna.species.offset_features)
+ hand_overlay.pixel_x += dna.species.offset_features[OFFSET_RIGHT_HAND][1]
+ hand_overlay.pixel_y += dna.species.offset_features[OFFSET_RIGHT_HAND][2]
+ else
+ hand_overlay = I.build_worn_icon(state = t_state, default_layer = HANDS_LAYER, default_icon_file = icon_file, isinhands = TRUE)
+ if(OFFSET_LEFT_HAND in dna.species.offset_features)
+ hand_overlay.pixel_x += dna.species.offset_features[OFFSET_LEFT_HAND][1]
+ hand_overlay.pixel_y += dna.species.offset_features[OFFSET_LEFT_HAND][2]
+ hands += hand_overlay
+ overlays_standing[HANDS_LAYER] = hands
+ apply_overlay(HANDS_LAYER)
/proc/wear_female_version(t_color, icon, layer, type)
var/index = t_color
@@ -482,7 +634,7 @@ There are several things that need to be remembered:
//Update whether we smell
/mob/living/carbon/human/proc/update_smell(var/smelly_icon = "generic_mob_smell")
remove_overlay(SMELL_LAYER)
- if(hygiene <= HYGIENE_LEVEL_DIRTY)
+ if(hygiene == HYGIENE_LEVEL_DISGUSTING) //You have literally ignored your stank for so long that you physically can't get dirtier.
var/mutable_appearance/new_smell_overlay = mutable_appearance('icons/mob/smelly.dmi', smelly_icon, -SMELL_LAYER)
overlays_standing[SMELL_LAYER] = new_smell_overlay
apply_overlay(SMELL_LAYER)
@@ -559,8 +711,14 @@ generate/load female uniform sprites matching all previously decided variables
/obj/item/proc/get_held_offsets()
var/list/L
if(ismob(loc))
+ if(ishuman(loc))
+ var/mob/living/carbon/human/H = loc
+ L = H.dna?.species.get_item_offsets_for_index(src)
+ if(L)
+ return L
var/mob/M = loc
L = M.get_item_offsets_for_index(M.get_held_index_of_item(src))
+
return L
diff --git a/code/modules/mob/living/carbon/human/verbs/give.dm b/code/modules/mob/living/carbon/human/verbs/give.dm
index 1dd13edcb40b4..8b32cc381f39d 100644
--- a/code/modules/mob/living/carbon/human/verbs/give.dm
+++ b/code/modules/mob/living/carbon/human/verbs/give.dm
@@ -1,27 +1,6 @@
-/mob/living/carbon/human/verb/Give(var/mob/living/carbon/human/l in view(1))
- set category = "Object"
- set name = "Give"
- set desc = "Give whatever's in your hand to someone."
+/mob/living/carbon/human/verb/Give()
+ give()
- var/obj/item/i = usr.get_active_held_item()
-
- if(src == usr || !istype(l))
- return
- if(!i)
- to_chat(usr, "You must be holding your gift in your active hand.")
- return
- if(alert(l, "[usr] is trying to give you \the [i], will you accept?", "Yes", "No") == "No")
- to_chat(usr, "[l] didn't accept \the [i].")
- return
-
- if(!(usr in range(1, src))) // so if they walk away with the alert window open, it doesnt teleport
- to_chat(usr, "You're too far away!")
- return
-
- l.put_in_hands(i)
-
-/mob/living/carbon/human/CtrlShiftClickOn(var/mob/living/carbon/human/l in view(1))
+/mob/living/carbon/human/CtrlShiftClickOn()
..()
- Give(l)
-
-
+ Give()
diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm
index fcc4f3ab8cfd6..60d1ffa4259b3 100644
--- a/code/modules/mob/living/carbon/inventory.dm
+++ b/code/modules/mob/living/carbon/inventory.dm
@@ -1,22 +1,22 @@
/mob/living/carbon/get_item_by_slot(slot_id)
switch(slot_id)
- if(SLOT_BACK)
+ if(ITEM_SLOT_BACK)
return back
- if(SLOT_WEAR_MASK)
+ if(ITEM_SLOT_MASK)
return wear_mask
- if(SLOT_NECK)
+ if(ITEM_SLOT_NECK)
return wear_neck
- if(SLOT_HEAD)
+ if(ITEM_SLOT_HEAD)
return head
- if(SLOT_HANDCUFFED)
+ if(ITEM_SLOT_HANDCUFFED)
return handcuffed
- if(SLOT_LEGCUFFED)
+ if(ITEM_SLOT_LEGCUFFED)
return legcuffed
return null
-/mob/living/carbon/proc/equip_in_one_of_slots(obj/item/I, list/slots, qdel_on_fail = 1)
+/mob/living/carbon/proc/equip_in_one_of_slots(obj/item/I, list/slots, qdel_on_fail = TRUE)
for(var/slot in slots)
- if(equip_to_slot_if_possible(I, slots[slot], qdel_on_fail = 0, disable_warning = TRUE))
+ if(equip_to_slot_if_possible(I, slots[slot], qdel_on_fail = FALSE, disable_warning = TRUE))
return slot
if(qdel_on_fail)
qdel(I)
@@ -50,28 +50,28 @@
I.appearance_flags |= NO_CLIENT_COLOR
var/not_handled = FALSE
switch(slot)
- if(SLOT_BACK)
+ if(ITEM_SLOT_BACK)
back = I
update_inv_back()
- if(SLOT_WEAR_MASK)
+ if(ITEM_SLOT_MASK)
wear_mask = I
wear_mask_update(I, toggle_off = 0)
- if(SLOT_HEAD)
+ if(ITEM_SLOT_HEAD)
head = I
head_update(I)
- if(SLOT_NECK)
+ if(ITEM_SLOT_NECK)
wear_neck = I
update_inv_neck(I)
- if(SLOT_HANDCUFFED)
+ if(ITEM_SLOT_HANDCUFFED)
handcuffed = I
update_handcuffed()
- if(SLOT_LEGCUFFED)
+ if(ITEM_SLOT_LEGCUFFED)
legcuffed = I
update_inv_legcuffed()
- if(SLOT_HANDS)
+ if(ITEM_SLOT_HANDS)
put_in_hands(I)
update_inv_hands()
- if(SLOT_IN_BACKPACK)
+ if(ITEM_SLOT_BACKPACK)
if(!back || !SEND_SIGNAL(back, COMSIG_TRY_STORAGE_INSERT, I, src, TRUE))
not_handled = TRUE
else
@@ -85,7 +85,7 @@
return not_handled
-/mob/living/carbon/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE)
+/mob/living/carbon/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE, was_thrown = FALSE)
. = ..() //Sets the default return value to what the parent returns.
if(!. || !I) //We don't want to set anything to null if the parent returned 0.
return
@@ -129,7 +129,7 @@
//handle stuff to update when a mob equips/unequips a headgear.
/mob/living/carbon/proc/head_update(obj/item/I, forced)
- if(istype(I, /obj/item/clothing))
+ if(isclothing(I))
var/obj/item/clothing/C = I
if(C.tint || initial(C.tint))
update_tint()
@@ -141,3 +141,61 @@
/mob/living/carbon/proc/get_holding_bodypart_of_item(obj/item/I)
var/index = get_held_index_of_item(I)
return index && hand_bodyparts[index]
+
+/**
+ * Proc called when giving an item to another player
+ *
+ * This handles creating an alert and adding an overlay to it
+ */
+
+/mob/living/carbon/proc/give()
+ var/alert = null
+ var/obj/item/receiving = get_active_held_item()
+ if(!receiving)
+ to_chat(src, "You're not holding anything to give!")
+ return
+
+ for(var/mob/living/carbon/C in orange(1, src)) //Fixed that, now it shouldn't be able to give benos stunbatons and IDs
+ if(!CanReach(C) || !C.can_hold_items())
+ continue
+
+ var/atom/movable/screen/alert/give/G = C.throw_alert("[src]", /atom/movable/screen/alert/give)
+ if(!G)
+ to_chat(src, "There is nobody nearby to give [receiving]!")
+ continue
+
+ G.setup(C, src, receiving)
+
+ if(!alert) // only displays alert once
+ do_alert_animation(src)
+ visible_message("[src] is offering [receiving].", \
+ "You offer [receiving].", null, 2)
+ alert=1 // disable alert animation and chat message for possible second alert
+
+/**
+ * Proc called when the player clicks the give alert
+ *
+ * Handles checking if the player taking the item has open slots and is in range of the giver
+ * Also deals with the actual transferring of the item to the players hands
+ * Arguments:
+ * * giver - The person giving the original item
+ * * I - The item being given by the giver
+ */
+
+/mob/living/carbon/proc/take(mob/living/carbon/giver, obj/item/I)
+ clear_alert("[giver]")
+ if(get_dist(src, giver) > 1)
+ to_chat(src, "[giver] is out of range! ")
+ return
+ if(!I || giver.get_active_held_item() != I)
+ to_chat(src, "[giver] is no longer holding the item they were offering! ")
+ return
+ if(!get_empty_held_indexes())
+ to_chat(src, "You have no empty hands!")
+ return
+ if(!giver.temporarilyRemoveItemFromInventory(I))
+ visible_message("[giver] tries to hand over [I] but it's stuck to them....")
+ return
+ visible_message("[src] takes [I] from [giver]", \
+ "You take [I] from [giver]")
+ put_in_hands(I)
diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm
index 7c9988dc574ab..7d8c73cce0488 100644
--- a/code/modules/mob/living/carbon/life.dm
+++ b/code/modules/mob/living/carbon/life.dm
@@ -16,14 +16,14 @@
var/obj/item/organ/O = V
O.on_life()
else
- if(!reagents.has_reagent(/datum/reagent/toxin/formaldehyde, 1)) // No organ decay if the body contains formaldehyde.
+ if(reagents && !reagents.has_reagent(/datum/reagent/toxin/formaldehyde, 1)) // No organ decay if the body contains formaldehyde.
for(var/V in internal_organs)
var/obj/item/organ/O = V
O.on_death() //Needed so organs decay while inside the body.
. = ..()
- if (QDELETED(src))
+ if(QDELETED(src))
return
if(.) //not dead
@@ -53,7 +53,7 @@
if(changeling)
changeling.regenerate()
hud_used.lingchemdisplay.invisibility = 0
- hud_used.lingchemdisplay.maptext = "
[round(changeling.chem_charges)]
"
+ hud_used.lingchemdisplay.maptext = MAPTEXT("
[round(changeling.chem_charges)]
")
else
hud_used.lingchemdisplay.invisibility = INVISIBILITY_ABSTRACT
@@ -127,16 +127,18 @@
breath = loc_as_obj.handle_internal_lifeform(src, BREATH_VOLUME)
else if(isturf(loc)) //Breathe from loc as turf
- var/breath_moles = 0
+ var/breath_ratio = 0
if(environment)
- breath_moles = environment.total_moles()*BREATH_PERCENTAGE
+ breath_ratio = BREATH_VOLUME/environment.return_volume()
- breath = loc.remove_air(breath_moles)
+ breath = loc.remove_air_ratio(breath_ratio)
else //Breathe from loc as obj again
if(istype(loc, /obj/))
var/obj/loc_as_obj = loc
loc_as_obj.handle_internal_lifeform(src,0)
+ if(breath)
+ breath.set_volume(BREATH_VOLUME)
check_breath(breath)
if(breath)
@@ -167,7 +169,7 @@
adjustOxyLoss(1)
failed_last_breath = 1
- throw_alert("not_enough_oxy", /obj/screen/alert/not_enough_oxy)
+ throw_alert("not_enough_oxy", /atom/movable/screen/alert/not_enough_oxy)
return 0
var/safe_oxy_min = 16
@@ -176,11 +178,11 @@
var/SA_para_min = 1
var/SA_sleep_min = 5
var/oxygen_used = 0
- var/breath_pressure = (breath.total_moles()*R_IDEAL_GAS_EQUATION*breath.return_temperature())/BREATH_VOLUME
-
- var/O2_partialpressure = (breath.get_moles(/datum/gas/oxygen)/breath.total_moles())*breath_pressure
- var/Toxins_partialpressure = (breath.get_moles(/datum/gas/plasma)/breath.total_moles())*breath_pressure
- var/CO2_partialpressure = (breath.get_moles(/datum/gas/carbon_dioxide)/breath.total_moles())*breath_pressure
+ var/moles = breath.total_moles()
+ var/breath_pressure = (moles*R_IDEAL_GAS_EQUATION*breath.return_temperature())/BREATH_VOLUME
+ var/O2_partialpressure = ((breath.get_moles(GAS_O2)/moles)*breath_pressure) + (((breath.get_moles(GAS_PLUOXIUM)*8)/moles)*breath_pressure)
+ var/Toxins_partialpressure = (breath.get_moles(GAS_PLASMA)/moles)*breath_pressure
+ var/CO2_partialpressure = (breath.get_moles(GAS_CO2)/moles)*breath_pressure
//OXYGEN
@@ -191,21 +193,21 @@
var/ratio = 1 - O2_partialpressure/safe_oxy_min
adjustOxyLoss(min(5*ratio, 3))
failed_last_breath = 1
- oxygen_used = breath.get_moles(/datum/gas/oxygen)*ratio
+ oxygen_used = breath.get_moles(GAS_O2)*ratio
else
adjustOxyLoss(3)
failed_last_breath = 1
- throw_alert("not_enough_oxy", /obj/screen/alert/not_enough_oxy)
+ throw_alert("not_enough_oxy", /atom/movable/screen/alert/not_enough_oxy)
else //Enough oxygen
failed_last_breath = 0
if(health >= crit_threshold)
adjustOxyLoss(-5)
- oxygen_used = breath.get_moles(/datum/gas/oxygen)
+ oxygen_used = breath.get_moles(GAS_O2)
clear_alert("not_enough_oxy")
- breath.adjust_moles(/datum/gas/oxygen, -oxygen_used)
- breath.adjust_moles(/datum/gas/carbon_dioxide, oxygen_used)
+ breath.adjust_moles(GAS_O2, -oxygen_used)
+ breath.adjust_moles(GAS_CO2, oxygen_used)
//CARBON DIOXIDE
if(CO2_partialpressure > safe_co2_max)
@@ -224,15 +226,15 @@
//TOXINS/PLASMA
if(Toxins_partialpressure > safe_tox_max)
- var/ratio = (breath.get_moles(/datum/gas/plasma)/safe_tox_max) * 10
+ var/ratio = (breath.get_moles(GAS_PLASMA)/safe_tox_max) * 10
adjustToxLoss(CLAMP(ratio, MIN_TOXIC_GAS_DAMAGE, MAX_TOXIC_GAS_DAMAGE))
- throw_alert("too_much_tox", /obj/screen/alert/too_much_tox)
+ throw_alert("too_much_tox", /atom/movable/screen/alert/too_much_tox)
else
clear_alert("too_much_tox")
//NITROUS OXIDE
- if(breath.get_moles(/datum/gas/nitrous_oxide))
- var/SA_partialpressure = (breath.get_moles(/datum/gas/nitrous_oxide)/breath.total_moles())*breath_pressure
+ if(breath.get_moles(GAS_NITROUS))
+ var/SA_partialpressure = (breath.get_moles(GAS_NITROUS)/breath.total_moles())*breath_pressure
if(SA_partialpressure > SA_para_min)
Unconscious(60)
if(SA_partialpressure > SA_sleep_min)
@@ -245,26 +247,26 @@
SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "chemical_euphoria")
//BZ (Facepunch port of their Agent B)
- if(breath.get_moles(/datum/gas/bz))
- var/bz_partialpressure = (breath.get_moles(/datum/gas/bz)/breath.total_moles())*breath_pressure
+ if(breath.get_moles(GAS_BZ))
+ var/bz_partialpressure = (breath.get_moles(GAS_BZ)/breath.total_moles())*breath_pressure
if(bz_partialpressure > 1)
hallucination += 10
else if(bz_partialpressure > 0.01)
hallucination += 5
//TRITIUM
- if(breath.get_moles(/datum/gas/tritium))
- var/tritium_partialpressure = (breath.get_moles(/datum/gas/tritium)/breath.total_moles())*breath_pressure
+ if(breath.get_moles(GAS_TRITIUM))
+ var/tritium_partialpressure = (breath.get_moles(GAS_TRITIUM)/breath.total_moles())*breath_pressure
radiation += tritium_partialpressure/10
//NITRYL
- if(breath.get_moles(/datum/gas/nitryl))
- var/nitryl_partialpressure = (breath.get_moles(/datum/gas/nitryl)/breath.total_moles())*breath_pressure
+ if(breath.get_moles(GAS_NITRYL))
+ var/nitryl_partialpressure = (breath.get_moles(GAS_NITRYL)/breath.total_moles())*breath_pressure
adjustFireLoss(nitryl_partialpressure/4)
//MIASMA
- if(breath.get_moles(/datum/gas/miasma))
- var/miasma_partialpressure = (breath.get_moles(/datum/gas/miasma)/breath.total_moles())*breath_pressure
+ if(breath.get_moles(GAS_MIASMA))
+ var/miasma_partialpressure = (breath.get_moles(GAS_MIASMA)/breath.total_moles())*breath_pressure
if(prob(1 * miasma_partialpressure))
var/datum/disease/advance/miasma_disease = new /datum/disease/advance/random(2,3)
@@ -315,7 +317,7 @@
/mob/living/carbon/proc/get_breath_from_internal(volume_needed)
if(internal)
- if(internal.loc != src)
+ if(internal.loc != src && !(wear_mask.clothing_flags & MASKEXTENDRANGE)) // If the mask has extended range, do not check for internal.loc
internal = null
update_internals_hud_icon(0)
else if ((!wear_mask || !(wear_mask.clothing_flags & MASKINTERNALS)) && !getorganslot(ORGAN_SLOT_BREATHING_TUBE))
@@ -336,10 +338,25 @@
stam_regen = TRUE
if(stam_paralyzed)
. |= BODYPART_LIFE_UPDATE_HEALTH //make sure we remove the stamcrit
- for(var/I in bodyparts)
- var/obj/item/bodypart/BP = I
+ var/bodyparts_with_stam = 0
+ var/stam_heal_multiplier = 1
+ var/total_stamina_loss = 0 //Quicker to put it here too than do it again with getStaminaLoss
+ var/force_heal = 0
+ //Find how many bodyparts we have with stamina damage
+ if(stam_regen)
+ for(var/obj/item/bodypart/BP as anything in bodyparts)
+ if(BP.stamina_dam > DAMAGE_PRECISION)
+ bodyparts_with_stam ++
+ total_stamina_loss += BP.stamina_dam * BP.stam_damage_coeff
+ //Force bodyparts to heal if we have more than 120 stamina damage (6 seconds)
+ force_heal = max(0, total_stamina_loss - 120) / max(bodyparts_with_stam, 1)
+ //Increase damage the more stam damage
+ //Incraesed stamina healing when above 50 stamloss, up to 2x healing rate when at 100 stamloss.
+ stam_heal_multiplier = CLAMP(total_stamina_loss / 50, 1, 2)
+ //Heal bodypart stamina damage
+ for(var/obj/item/bodypart/BP as() in bodyparts)
if(BP.needs_processing)
- . |= BP.on_life(stam_regen)
+ . |= BP.on_life(force_heal + ((stam_regen * stam_heal * stam_heal_multiplier) / max(bodyparts_with_stam, 1)))
/mob/living/carbon/handle_diseases()
for(var/thing in diseases)
@@ -503,6 +520,7 @@ GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put
adjustFireLoss(-0.06, FALSE)
else
SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "drunk")
+ sound_environment_override = SOUND_ENVIRONMENT_NONE
if(drunkenness >= 11 && slurring < 5)
slurring += 1.2
diff --git a/code/modules/mob/living/carbon/monkey/combat.dm b/code/modules/mob/living/carbon/monkey/combat.dm
index 17adfc3efee0d..8cdebc709f38e 100644
--- a/code/modules/mob/living/carbon/monkey/combat.dm
+++ b/code/modules/mob/living/carbon/monkey/combat.dm
@@ -64,8 +64,8 @@
/mob/living/carbon/monkey/proc/battle_screech()
if(next_battle_screech < world.time)
- emote(pick("roar","screech"))
- for(var/mob/living/carbon/monkey/M in view(7,src))
+ INVOKE_ASYNC(src, /mob.proc/emote, pick("roar","screech"))
+ for(var/mob/living/carbon/monkey/M in hearers(7,src))
M.next_battle_screech = world.time + battle_screech_cooldown
/mob/living/carbon/monkey/proc/equip_item(obj/item/I)
@@ -85,7 +85,7 @@
return TRUE
// CLOTHING
- else if(istype(I, /obj/item/clothing))
+ else if(isclothing(I))
var/obj/item/clothing/C = I
monkeyDrop(C)
addtimer(CALLBACK(src, .proc/pickup_and_wear, C), 5)
@@ -139,7 +139,7 @@
blacklistItems[pickupTarget] ++
pickupTarget = null
pickupTimer = 0
- else if(!IsDeadOrIncap())
+ else if(!IsDeadOrIncap())
INVOKE_ASYNC(src, .proc/walk2derpless, pickupTarget.loc)
if(Adjacent(pickupTarget) || Adjacent(pickupTarget.loc)) // next to target
drop_all_held_items() // who cares about these items, i want that one!
@@ -158,7 +158,7 @@
switch(mode)
if(MONKEY_IDLE) // idle
if(enemies.len)
- var/list/around = view(src, MONKEY_ENEMY_VISION) // scan for enemies
+ var/list/around = view(MONKEY_ENEMY_VISION, src) // scan for enemies
for(var/mob/living/L in around)
if( should_target(L) )
if(L.stat == CONSCIOUS)
@@ -199,7 +199,7 @@
pickupTarget = W
// recruit other monkies
- var/list/around = view(src, MONKEY_ENEMY_VISION)
+ var/list/around = view(MONKEY_ENEMY_VISION, src)
for(var/mob/living/carbon/monkey/M in around)
if(M.mode == MONKEY_IDLE && prob(MONKEY_RECRUIT_PROB))
M.battle_screech()
@@ -249,11 +249,10 @@
back_to_idle()
if(MONKEY_FLEE)
- var/list/around = view(src, MONKEY_FLEE_VISION)
target = null
// flee from anyone who attacked us and we didn't beat down
- for(var/mob/living/L in around)
+ for(var/mob/living/L in view(MONKEY_FLEE_VISION, src))
if( enemies[L] && L.stat == CONSCIOUS )
target = L
@@ -388,6 +387,26 @@
retaliate(L)
return ..()
+/mob/living/carbon/monkey/attack_animal(mob/living/simple_animal/M)
+ . = ..()
+ if(. && prob(MONKEY_RETALIATE_HARM_PROB))
+ retaliate(M)
+
+/mob/living/carbon/monkey/attack_alien(mob/living/carbon/alien/humanoid/M)
+ . = ..()
+ if(. && prob(MONKEY_RETALIATE_HARM_PROB))
+ retaliate(M)
+
+/mob/living/carbon/monkey/attack_larva(mob/living/carbon/alien/larva/L)
+ . = ..()
+ if(. && prob(MONKEY_RETALIATE_HARM_PROB))
+ retaliate(L)
+
+/mob/living/carbon/monkey/attack_slime(mob/living/simple_animal/slime/M)
+ . = ..()
+ if(. && prob(MONKEY_RETALIATE_HARM_PROB))
+ retaliate(M)
+
/mob/living/carbon/monkey/attackby(obj/item/W, mob/user, params)
..()
if((W.force) && (!target) && (W.damtype != STAMINA) )
diff --git a/code/modules/mob/living/carbon/monkey/inventory.dm b/code/modules/mob/living/carbon/monkey/inventory.dm
index d5fffc70a2ac2..42803c5aa65ba 100644
--- a/code/modules/mob/living/carbon/monkey/inventory.dm
+++ b/code/modules/mob/living/carbon/monkey/inventory.dm
@@ -1,28 +1,28 @@
/mob/living/carbon/monkey/can_equip(obj/item/I, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE)
switch(slot)
- if(SLOT_HANDS)
+ if(ITEM_SLOT_HANDS)
if(get_empty_held_indexes())
return TRUE
return FALSE
- if(SLOT_WEAR_MASK)
+ if(ITEM_SLOT_MASK)
if(wear_mask)
return FALSE
if( !(I.slot_flags & ITEM_SLOT_MASK) )
return FALSE
return TRUE
- if(SLOT_NECK)
+ if(ITEM_SLOT_NECK)
if(wear_neck)
return FALSE
if( !(I.slot_flags & ITEM_SLOT_NECK) )
return FALSE
return TRUE
- if(SLOT_HEAD)
+ if(ITEM_SLOT_HEAD)
if(head)
return FALSE
if( !(I.slot_flags & ITEM_SLOT_HEAD) )
return FALSE
return TRUE
- if(SLOT_BACK)
+ if(ITEM_SLOT_BACK)
if(back)
return FALSE
if( !(I.slot_flags & ITEM_SLOT_BACK) )
diff --git a/code/modules/mob/living/carbon/monkey/life.dm b/code/modules/mob/living/carbon/monkey/life.dm
index 41ec1e0e7f7d2..5c49627c98f8f 100644
--- a/code/modules/mob/living/carbon/monkey/life.dm
+++ b/code/modules/mob/living/carbon/monkey/life.dm
@@ -1,33 +1,5 @@
-
-
/mob/living/carbon/monkey
-
-/mob/living/carbon/monkey/Life()
- set invisibility = 0
-
- if (notransform)
- return
-
- if(..() && !IsInStasis())
-
- if(!client)
- if(stat == CONSCIOUS)
- if(on_fire || buckled || restrained())
- if(!resisting && prob(MONKEY_RESIST_PROB))
- resisting = TRUE
- walk_to(src,0)
- resist()
- else if(resisting)
- resisting = FALSE
- else if((mode == MONKEY_IDLE && !pickupTarget && !prob(MONKEY_SHENANIGAN_PROB)) || !handle_combat())
- if(prob(25) && (mobility_flags & MOBILITY_MOVE) && isturf(loc) && !pulledby)
- step(src, pick(GLOB.cardinals))
- else if(prob(1))
- emote(pick("scratch","jump","roll","tail"))
- else
- walk_to(src,0)
-
/mob/living/carbon/monkey/handle_mutations_and_radiation()
if(radiation)
if(radiation > RAD_MOB_KNOCKDOWN && prob(RAD_MOB_KNOCKDOWN_PROB))
@@ -43,7 +15,17 @@
domutcheck()
if(radiation > RAD_MOB_MUTATE * 1.5)
- gorillize()
+ switch(rand(1, 3))
+ if(1)
+ gorillize()
+ if(2)
+ humanize(TR_KEEPITEMS | TR_KEEPVIRUS | TR_DEFAULTMSG | TR_KEEPDAMAGE | TR_KEEPORGANS)
+ if(3)
+ var/obj/item/bodypart/BP = pick(bodyparts)
+ if(BP.body_part != HEAD && BP.body_part != CHEST)
+ if(BP.dismemberable)
+ BP.dismember()
+ take_bodypart_damage(100, 0, 0)
return
if(radiation > RAD_MOB_VOMIT && prob(RAD_MOB_VOMIT_PROB))
vomit(10, TRUE)
@@ -75,22 +57,22 @@
adjust_bodytemperature(natural_bodytemperature_stabilization())
if(!on_fire) //If you're on fire, you do not heat up or cool down based on surrounding gases
- if(loc_temp < bodytemperature)
+ if(loc_temp < bodytemperature && (!head?.min_cold_protection_temperature || head.min_cold_protection_temperature > loc_temp))
adjust_bodytemperature(max((loc_temp - bodytemperature) / BODYTEMP_COLD_DIVISOR, BODYTEMP_COOLING_MAX))
- else
+ else if(!head?.max_heat_protection_temperature || head.max_heat_protection_temperature < loc_temp)
adjust_bodytemperature(min((loc_temp - bodytemperature) / BODYTEMP_HEAT_DIVISOR, BODYTEMP_HEATING_MAX))
if(bodytemperature > BODYTEMP_HEAT_DAMAGE_LIMIT && !HAS_TRAIT(src, TRAIT_RESISTHEAT))
switch(bodytemperature)
if(360 to 400)
- throw_alert("temp", /obj/screen/alert/hot, 1)
+ throw_alert("temp", /atom/movable/screen/alert/hot, 1)
apply_damage(HEAT_DAMAGE_LEVEL_1, BURN)
if(400 to 460)
- throw_alert("temp", /obj/screen/alert/hot, 2)
+ throw_alert("temp", /atom/movable/screen/alert/hot, 2)
apply_damage(HEAT_DAMAGE_LEVEL_2, BURN)
if(460 to INFINITY)
- throw_alert("temp", /obj/screen/alert/hot, 3)
+ throw_alert("temp", /atom/movable/screen/alert/hot, 3)
if(on_fire)
apply_damage(HEAT_DAMAGE_LEVEL_3, BURN)
else
@@ -100,13 +82,13 @@
if(!istype(loc, /obj/machinery/atmospherics/components/unary/cryo_cell))
switch(bodytemperature)
if(200 to 260)
- throw_alert("temp", /obj/screen/alert/cold, 1)
+ throw_alert("temp", /atom/movable/screen/alert/cold, 1)
apply_damage(COLD_DAMAGE_LEVEL_1, BURN)
if(120 to 200)
- throw_alert("temp", /obj/screen/alert/cold, 2)
+ throw_alert("temp", /atom/movable/screen/alert/cold, 2)
apply_damage(COLD_DAMAGE_LEVEL_2, BURN)
if(-INFINITY to 120)
- throw_alert("temp", /obj/screen/alert/cold, 3)
+ throw_alert("temp", /atom/movable/screen/alert/cold, 3)
apply_damage(COLD_DAMAGE_LEVEL_3, BURN)
else
clear_alert("temp")
@@ -121,19 +103,26 @@
switch(adjusted_pressure)
if(HAZARD_HIGH_PRESSURE to INFINITY)
adjustBruteLoss( min( ( (adjusted_pressure / HAZARD_HIGH_PRESSURE) -1 )*PRESSURE_DAMAGE_COEFFICIENT , MAX_HIGH_PRESSURE_DAMAGE) )
- throw_alert("pressure", /obj/screen/alert/highpressure, 2)
+ throw_alert("pressure", /atom/movable/screen/alert/highpressure, 2)
if(WARNING_HIGH_PRESSURE to HAZARD_HIGH_PRESSURE)
- throw_alert("pressure", /obj/screen/alert/highpressure, 1)
+ throw_alert("pressure", /atom/movable/screen/alert/highpressure, 1)
if(WARNING_LOW_PRESSURE to WARNING_HIGH_PRESSURE)
clear_alert("pressure")
if(HAZARD_LOW_PRESSURE to WARNING_LOW_PRESSURE)
- throw_alert("pressure", /obj/screen/alert/lowpressure, 1)
+ throw_alert("pressure", /atom/movable/screen/alert/lowpressure, 1)
else
adjustBruteLoss( LOW_PRESSURE_DAMAGE )
- throw_alert("pressure", /obj/screen/alert/lowpressure, 2)
+ throw_alert("pressure", /atom/movable/screen/alert/lowpressure, 2)
return
+/mob/living/carbon/monkey/calculate_affecting_pressure(pressure)
+ if (head && isclothing(head))
+ var/obj/item/clothing/CH = head
+ if (CH.clothing_flags & STOPSPRESSUREDAMAGE)
+ return ONE_ATMOSPHERE
+ return pressure
+
/mob/living/carbon/monkey/handle_random_events()
if (prob(1) && prob(2))
emote("scratch")
@@ -152,9 +141,9 @@
var/list/burning_items = list()
//HEAD//
var/list/obscured = check_obscured_slots(TRUE)
- if(wear_mask && !(SLOT_WEAR_MASK in obscured))
+ if(wear_mask && !(ITEM_SLOT_MASK in obscured))
burning_items += wear_mask
- if(wear_neck && !(SLOT_NECK in obscured))
+ if(wear_neck && !(ITEM_SLOT_NECK in obscured))
burning_items += wear_neck
if(head)
burning_items += head
@@ -166,5 +155,6 @@
var/obj/item/I = X
I.fire_act((fire_stacks * 50)) //damage taken is reduced to 2% of this value by fire_act()
- adjust_bodytemperature(BODYTEMP_HEATING_MAX)
- SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "on_fire", /datum/mood_event/on_fire)
+ if(!head?.max_heat_protection_temperature || head.max_heat_protection_temperature < FIRE_IMMUNITY_MAX_TEMP_PROTECT)
+ adjust_bodytemperature(BODYTEMP_HEATING_MAX)
+ SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "on_fire", /datum/mood_event/on_fire)
diff --git a/code/modules/mob/living/carbon/monkey/monkey.dm b/code/modules/mob/living/carbon/monkey/monkey.dm
index ab37ce2f2bde9..223f7c37d0740 100644
--- a/code/modules/mob/living/carbon/monkey/monkey.dm
+++ b/code/modules/mob/living/carbon/monkey/monkey.dm
@@ -13,13 +13,17 @@
type_of_meat = /obj/item/reagent_containers/food/snacks/meat/slab/monkey
gib_type = /obj/effect/decal/cleanable/blood/gibs
unique_name = TRUE
+ blocks_emissive = EMISSIVE_BLOCK_UNIQUE
bodyparts = list(/obj/item/bodypart/chest/monkey, /obj/item/bodypart/head/monkey, /obj/item/bodypart/l_arm/monkey,
/obj/item/bodypart/r_arm/monkey, /obj/item/bodypart/r_leg/monkey, /obj/item/bodypart/l_leg/monkey)
hud_type = /datum/hud/monkey
+ mobchatspan = "monkeyhive"
+ ai_controller = /datum/ai_controller/monkey
+ faction = list("neutral", "monkey")
/mob/living/carbon/monkey/Initialize(mapload, cubespawned=FALSE, mob/spawner)
- verbs += /mob/living/proc/mob_sleep
- verbs += /mob/living/proc/lay_down
+ add_verb(/mob/living/proc/mob_sleep)
+ add_verb(/mob/living/proc/lay_down)
if(unique_name) //used to exclude pun pun
gender = pick(MALE, FEMALE)
@@ -39,7 +43,7 @@
return INITIALIZE_HINT_QDEL
SSmobs.cubemonkeys += src
- create_dna(src)
+ create_dna()
dna.initialize_dna(random_blood_type())
/mob/living/carbon/monkey/Destroy()
@@ -85,17 +89,16 @@
slow += ((283.222 - bodytemperature) / 10) * 1.75
add_movespeed_modifier(MOVESPEED_ID_MONKEY_TEMPERATURE_SPEEDMOD, TRUE, 100, override = TRUE, multiplicative_slowdown = slow)
-/mob/living/carbon/monkey/Stat()
- ..()
- if(statpanel("Status"))
- stat(null, "Intent: [a_intent]")
- stat(null, "Move Mode: [m_intent]")
- if(client && mind)
- var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling)
- if(changeling)
- stat(null, "Chemical Storage: [changeling.chem_charges]/[changeling.chem_storage]")
- stat(null, "Absorbed DNA: [changeling.absorbedcount]")
- return
+/mob/living/carbon/monkey/get_stat_tab_status()
+ var/list/tab_data = ..()
+ tab_data["Intent"] = GENERATE_STAT_TEXT("[a_intent]")
+ tab_data["Move Mode"] = GENERATE_STAT_TEXT("[m_intent]")
+ if(client && mind)
+ var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling)
+ if(changeling)
+ tab_data["Chemical Storage"] = GENERATE_STAT_TEXT("[changeling.chem_charges]/[changeling.chem_storage]")
+ tab_data["Absorbed DNA"] = GENERATE_STAT_TEXT("[changeling.absorbedcount]")
+ return tab_data
/mob/living/carbon/monkey/verb/removeinternal()
@@ -116,14 +119,14 @@
/mob/living/carbon/monkey/canBeHandcuffed()
return TRUE
-/mob/living/carbon/monkey/assess_threat(judgement_criteria, lasercolor = "", datum/callback/weaponcheck=null)
- if(judgement_criteria & JUDGE_EMAGGED)
+/mob/living/carbon/monkey/assess_threat(judgment_criteria, lasercolor = "", datum/callback/weaponcheck=null)
+ if(judgment_criteria & JUDGE_EMAGGED)
return 10 //Everyone is a criminal!
var/threatcount = 0
//Securitrons can't identify monkeys
- if( !(judgement_criteria & JUDGE_IGNOREMONKEYS) && (judgement_criteria & JUDGE_IDCHECK) )
+ if( !(judgment_criteria & JUDGE_IGNOREMONKEYS) && (judgment_criteria & JUDGE_IDCHECK) )
threatcount += 4
//Lasertag bullshit
@@ -139,7 +142,7 @@
return threatcount
//Check for weapons
- if( (judgement_criteria & JUDGE_WEAPONCHECK) && weaponcheck )
+ if( (judgment_criteria & JUDGE_WEAPONCHECK) && weaponcheck )
for(var/obj/item/I in held_items) //if they're holding a gun
if(weaponcheck.Invoke(I))
threatcount += 4
@@ -170,13 +173,13 @@
return TRUE
/mob/living/carbon/monkey/angry
- aggressive = TRUE
+ ai_controller = /datum/ai_controller/monkey/angry
/mob/living/carbon/monkey/angry/Initialize()
. = ..()
if(prob(10))
var/obj/item/clothing/head/helmet/justice/escape/helmet = new(src)
- equip_to_slot_or_del(helmet,SLOT_HEAD)
+ equip_to_slot_or_del(helmet,ITEM_SLOT_HEAD)
helmet.attack_self(src) // todo encapsulate toggle
@@ -203,6 +206,42 @@
icon_state = null
butcher_results = list(/obj/effect/spawner/lootdrop/teratoma/minor = 5, /obj/effect/spawner/lootdrop/teratoma/major = 1)
type_of_meat = /obj/effect/spawner/lootdrop/teratoma/minor
- aggressive = TRUE
bodyparts = list(/obj/item/bodypart/chest/monkey/teratoma, /obj/item/bodypart/head/monkey/teratoma, /obj/item/bodypart/l_arm/monkey/teratoma,
/obj/item/bodypart/r_arm/monkey/teratoma, /obj/item/bodypart/r_leg/monkey/teratoma, /obj/item/bodypart/l_leg/monkey/teratoma)
+ ai_controller = null
+
+/datum/dna/tumor
+ species = new /datum/species/teratoma
+
+/datum/species/teratoma
+ name = "Teratoma"
+ id = "teratoma"
+ say_mod = "mumbles"
+ species_traits = list(NOTRANSSTING, NO_DNA_COPY, EYECOLOR, HAIR, FACEHAIR, LIPS)
+ inherent_traits = list(TRAIT_NOHUNGER, TRAIT_RADIMMUNE, TRAIT_BADDNA, TRAIT_NOGUNS, TRAIT_NONECRODISEASE) //Made of mutated cells
+ default_features = list("mcolor" = "FFF", "wings" = "None")
+ use_skintones = 1
+ skinned_type = /obj/item/stack/sheet/animalhide/monkey
+ liked_food = JUNKFOOD | FRIED | GROSS | RAW
+ changesource_flags = MIRROR_BADMIN
+ mutant_brain = /obj/item/organ/brain/tumor
+
+/obj/item/organ/brain/tumor
+ name = "teratoma brain"
+
+/obj/item/organ/brain/tumor/Remove(mob/living/carbon/C, special, no_id_transfer)
+ . = ..()
+ //Removing it deletes it
+ qdel(src)
+
+/mob/living/carbon/monkey/tumor/handle_mutations_and_radiation()
+ return
+
+/mob/living/carbon/monkey/tumor/has_dna()
+ return FALSE
+
+/mob/living/carbon/monkey/tumor/create_dna()
+ dna = new /datum/dna/tumor(src)
+ //Give us the juicy mutant organs
+ dna.species.on_species_gain(src, null, FALSE)
+ dna.species.regenerate_organs(src, replace_current = TRUE)
diff --git a/code/modules/mob/living/carbon/monkey/monkey_defense.dm b/code/modules/mob/living/carbon/monkey/monkey_defense.dm
index 4ccd1de779cbf..859ae03979754 100644
--- a/code/modules/mob/living/carbon/monkey/monkey_defense.dm
+++ b/code/modules/mob/living/carbon/monkey/monkey_defense.dm
@@ -121,6 +121,8 @@
var/damage = 20
if(M.is_adult)
damage = 30
+ if(M.transformeffects & SLIME_EFFECT_RED)
+ damage *= 1.1
var/dam_zone = dismembering_strike(M, pick(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG))
if(!dam_zone) //Dismemberment successful
return 1
diff --git a/code/modules/mob/living/carbon/monkey/punpun.dm b/code/modules/mob/living/carbon/monkey/punpun.dm
index cee9f0387eea1..5fa3091fb6d8f 100644
--- a/code/modules/mob/living/carbon/monkey/punpun.dm
+++ b/code/modules/mob/living/carbon/monkey/punpun.dm
@@ -16,16 +16,16 @@
else if(prob(10))
name = pick(list("Professor Bobo", "Deempisi's Revenge", "Furious George", "King Louie", "Dr. Zaius", "Jimmy Rustles", "Dinner", "Lanky"))
if(name == "Furious George")
- aggressive = TRUE // Furious George is PISSED
+ ai_controller = /datum/ai_controller/monkey/angry //hes always mad
. = ..()
//These have to be after the parent new to ensure that the monkey
//bodyparts are actually created before we try to equip things to
//those slots
if(relic_hat)
- equip_to_slot_or_del(new relic_hat, SLOT_HEAD)
+ equip_to_slot_or_del(new relic_hat, ITEM_SLOT_HEAD)
if(relic_mask)
- equip_to_slot_or_del(new relic_mask, SLOT_WEAR_MASK)
+ equip_to_slot_or_del(new relic_mask, ITEM_SLOT_MASK)
/mob/living/carbon/monkey/punpun/Life()
if(!stat && SSticker.current_state == GAME_STATE_FINISHED && !memory_saved)
diff --git a/code/modules/mob/living/carbon/say.dm b/code/modules/mob/living/carbon/say.dm
index e971fd800de8a..240314fda3a4a 100644
--- a/code/modules/mob/living/carbon/say.dm
+++ b/code/modules/mob/living/carbon/say.dm
@@ -1,4 +1,6 @@
/mob/living/carbon/proc/handle_tongueless_speech(mob/living/carbon/speaker, list/speech_args)
+ SIGNAL_HANDLER
+
var/message = speech_args[SPEECH_MESSAGE]
var/static/regex/tongueless_lower = new("\[gdntke]+", "g")
var/static/regex/tongueless_upper = new("\[GDNTKE]+", "g")
@@ -19,16 +21,3 @@
else
. = initial(dt.flags) & TONGUELESS_SPEECH
-/mob/living/carbon/hear_intercept(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode)
- var/datum/status_effect/bugged/B = has_status_effect(STATUS_EFFECT_BUGGED)
- if(B)
- B.listening_in.show_message(message)
- for(var/T in get_traumas())
- var/datum/brain_trauma/trauma = T
- message = trauma.on_hear(message, speaker, message_language, raw_message, radio_freq)
-
- if (src.mind.has_antag_datum(/datum/antagonist/traitor))
- message = GLOB.syndicate_code_phrase_regex.Replace(message, "$1")
- message = GLOB.syndicate_code_response_regex.Replace(message, "$1")
-
- return message
diff --git a/code/modules/mob/living/carbon/status_procs.dm b/code/modules/mob/living/carbon/status_procs.dm
index 7fe02dcc148bf..4c530e0535b0e 100644
--- a/code/modules/mob/living/carbon/status_procs.dm
+++ b/code/modules/mob/living/carbon/status_procs.dm
@@ -13,24 +13,27 @@
return
if(!IsParalyzed())
to_chat(src, "You're too exhausted to keep going.")
+ stam_regen_start_time = world.time + STAMINA_CRIT_TIME
stam_paralyzed = TRUE
/mob/living/carbon/adjust_drugginess(amount)
druggy = max(druggy+amount, 0)
if(druggy)
- overlay_fullscreen("high", /obj/screen/fullscreen/high)
- throw_alert("high", /obj/screen/alert/high)
+ overlay_fullscreen("high", /atom/movable/screen/fullscreen/high)
+ throw_alert("high", /atom/movable/screen/alert/high)
SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "high", /datum/mood_event/high)
+ sound_environment_override = SOUND_ENVIRONMENT_DRUGGED
else
clear_fullscreen("high")
clear_alert("high")
SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "high")
+ sound_environment_override = SOUND_ENVIRONMENT_NONE
/mob/living/carbon/set_drugginess(amount)
druggy = max(amount, 0)
if(druggy)
- overlay_fullscreen("high", /obj/screen/fullscreen/high)
- throw_alert("high", /obj/screen/alert/high)
+ overlay_fullscreen("high", /atom/movable/screen/fullscreen/high)
+ throw_alert("high", /atom/movable/screen/alert/high)
else
clear_fullscreen("high")
clear_alert("high")
diff --git a/code/modules/mob/living/carbon/update_icons.dm b/code/modules/mob/living/carbon/update_icons.dm
index 725ecef40eba1..97d00426a4389 100644
--- a/code/modules/mob/living/carbon/update_icons.dm
+++ b/code/modules/mob/living/carbon/update_icons.dm
@@ -114,12 +114,12 @@
if(!get_bodypart(BODY_ZONE_HEAD)) //Decapitated
return
- if(client && hud_used && hud_used.inv_slots[SLOT_WEAR_MASK])
- var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_WEAR_MASK]
+ if(client && hud_used && hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_MASK) + 1])
+ var/atom/movable/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_MASK) + 1]
inv.update_icon()
if(wear_mask)
- if(!(SLOT_WEAR_MASK in check_obscured_slots()))
+ if(!(ITEM_SLOT_MASK in check_obscured_slots()))
overlays_standing[FACEMASK_LAYER] = wear_mask.build_worn_icon(state = wear_mask.icon_state, default_layer = FACEMASK_LAYER, default_icon_file = 'icons/mob/mask.dmi')
update_hud_wear_mask(wear_mask)
@@ -128,12 +128,12 @@
/mob/living/carbon/update_inv_neck()
remove_overlay(NECK_LAYER)
- if(client && hud_used && hud_used.inv_slots[SLOT_NECK])
- var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_NECK]
+ if(client && hud_used && hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_NECK) + 1])
+ var/atom/movable/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_NECK) + 1]
inv.update_icon()
if(wear_neck)
- if(!(SLOT_NECK in check_obscured_slots()))
+ if(!(ITEM_SLOT_NECK in check_obscured_slots()))
overlays_standing[NECK_LAYER] = wear_neck.build_worn_icon(state = wear_neck.icon_state, default_layer = NECK_LAYER, default_icon_file = 'icons/mob/neck.dmi')
update_hud_neck(wear_neck)
@@ -142,8 +142,8 @@
/mob/living/carbon/update_inv_back()
remove_overlay(BACK_LAYER)
- if(client && hud_used && hud_used.inv_slots[SLOT_BACK])
- var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_BACK]
+ if(client && hud_used && hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_BACK) + 1])
+ var/atom/movable/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_BACK) + 1]
inv.update_icon()
if(back)
@@ -158,8 +158,8 @@
if(!get_bodypart(BODY_ZONE_HEAD)) //Decapitated
return
- if(client && hud_used && hud_used.inv_slots[SLOT_BACK])
- var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_HEAD]
+ if(client && hud_used && hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_HEAD) + 1])
+ var/atom/movable/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_HEAD) + 1]
inv.update_icon()
if(head)
@@ -182,7 +182,7 @@
/mob/living/carbon/proc/update_hud_handcuffed()
if(hud_used)
for(var/hand in hud_used.hand_slots)
- var/obj/screen/inventory/hand/H = hud_used.hand_slots[hand]
+ var/atom/movable/screen/inventory/hand/H = hud_used.hand_slots[hand]
if(H)
H.update_icon()
@@ -257,7 +257,7 @@
- limbs (stores as the limb name and whether it is removed/fine, organic/robotic)
These procs only store limbs as to increase the number of matching icon_render_keys
This cache exists because drawing 6/7 icons for humans constantly is quite a waste
- See RemieRichards on irc.rizon.net #coderbus
+ See RemieRichards on irc.rizon.net #coderbus (RIP remie :sob:)
*/
//produces a key based on the mob's limbs
diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm
index fe4f0b8b79d0c..f57c06ea39f92 100644
--- a/code/modules/mob/living/damage_procs.dm
+++ b/code/modules/mob/living/damage_procs.dm
@@ -207,7 +207,7 @@
return cloneloss
/mob/living/proc/adjustCloneLoss(amount, updating_health = TRUE, forced = FALSE)
- if(!forced && (status_flags & GODMODE))
+ if(!forced && ((status_flags & GODMODE) || HAS_TRAIT(src, TRAIT_NOCLONELOSS)))
return FALSE
cloneloss = CLAMP((cloneloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2)
if(updating_health)
@@ -215,7 +215,7 @@
return amount
/mob/living/proc/setCloneLoss(amount, updating_health = TRUE, forced = FALSE)
- if(!forced && (status_flags & GODMODE))
+ if(!forced && ((status_flags & GODMODE) || HAS_TRAIT(src, TRAIT_NOCLONELOSS)))
return FALSE
cloneloss = amount
if(updating_health)
@@ -256,7 +256,7 @@
adjustStaminaLoss(stamina, FALSE)
if(updating_health)
updatehealth()
- update_stamina()
+ update_stamina(stamina > DAMAGE_PRECISION)
// heal MANY bodyparts, in random order
/mob/living/proc/heal_overall_damage(brute = 0, burn = 0, stamina = 0, required_status, updating_health = TRUE)
@@ -274,7 +274,7 @@
adjustStaminaLoss(stamina, FALSE)
if(updating_health)
updatehealth()
- update_stamina()
+ update_stamina(stamina > DAMAGE_PRECISION)
//heal up to amount damage, in a given order
/mob/living/proc/heal_ordered_damage(amount, list/damage_types)
diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm
index d437abfeb33cd..5800d400a8048 100644
--- a/code/modules/mob/living/death.dm
+++ b/code/modules/mob/living/death.dm
@@ -48,25 +48,23 @@
/mob/living/death(gibbed)
var/was_dead_before = stat == DEAD
- stat = DEAD
+ set_stat(DEAD)
unset_machine()
timeofdeath = world.time
tod = station_time_timestamp()
var/turf/T = get_turf(src)
for(var/obj/item/I in contents)
I.on_mob_death(src, gibbed)
- for(var/datum/disease/advance/D in diseases)
- for(var/symptom in D.symptoms)
- var/datum/symptom/S = symptom
- S.OnDeath(D)
if(mind)
if(mind.name && mind.active && !istype(T.loc, /area/ctf))
var/rendered = "[mind.name] has died at [get_area_name(T)]."
deadchat_broadcast(rendered, follow_target = src, turf_target = T, message_type=DEADCHAT_DEATHRATTLE)
mind.store_memory("Time of death: [tod]", 0)
- GLOB.alive_mob_list -= src
+ remove_from_alive_mob_list()
+ if(playable)
+ remove_from_spawner_menu()
if(!gibbed && !was_dead_before)
- GLOB.dead_mob_list += src
+ add_to_dead_mob_list()
SetSleeping(0, 0)
blind_eyes(1)
@@ -99,6 +97,9 @@
var/datum/soullink/S = s
S.sharerDies(gibbed)
+ if(mind?.current)
+ client?.tgui_panel?.give_dead_popup()
+
return TRUE
/mob/living/carbon/death(gibbed)
diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm
index 60a498894c2b6..61c9f1e5a6331 100644
--- a/code/modules/mob/living/emote.dm
+++ b/code/modules/mob/living/emote.dm
@@ -146,7 +146,7 @@
/datum/emote/living/gasp
key = "gasp"
key_third_person = "gasps"
- message = "gasps!"
+ message = "gasps"
emote_type = EMOTE_AUDIBLE
stat_allowed = UNCONSCIOUS
@@ -183,7 +183,7 @@
/datum/emote/living/jump
key = "jump"
key_third_person = "jumps"
- message = "jumps!"
+ message = "jumps"
restraint_check = TRUE
/datum/emote/living/kiss
@@ -346,7 +346,7 @@
/datum/emote/living/surrender
key = "surrender"
key_third_person = "surrenders"
- message = "puts their hands on their head and falls to the ground, they surrender!"
+ message = "puts their hands on their head and falls to the ground, surrendering"
emote_type = EMOTE_AUDIBLE
/datum/emote/living/surrender/run_emote(mob/user, params, type_override, intentional)
@@ -363,7 +363,7 @@
/datum/emote/living/tremble
key = "tremble"
key_third_person = "trembles"
- message = "trembles in fear!"
+ message = "trembles in fear"
/datum/emote/living/twitch
key = "twitch"
@@ -477,8 +477,8 @@
/datum/emote/beep
key = "beep"
key_third_person = "beeps"
- message = "beeps."
- message_param = "beeps at %t."
+ message = "beeps"
+ message_param = "beeps at %t"
sound = 'sound/machines/twobeep.ogg'
mob_type_allowed_typecache = list(/mob/living/brain, /mob/living/silicon)
@@ -554,9 +554,9 @@
/datum/emote/inhale
key = "inhale"
key_third_person = "inhales"
- message = "breathes in."
+ message = "breathes in"
/datum/emote/exhale
key = "exhale"
key_third_person = "exhales"
- message = "breathes out."
+ message = "breathes out"
diff --git a/code/modules/mob/living/inhand_holder.dm b/code/modules/mob/living/inhand_holder.dm
index bbe55b8b343ff..ffe96c74d60f9 100644
--- a/code/modules/mob/living/inhand_holder.dm
+++ b/code/modules/mob/living/inhand_holder.dm
@@ -6,6 +6,7 @@
icon = null
icon_state = ""
slot_flags = NONE
+ clothing_flags = NOTCONSUMABLE
var/mob/living/held_mob
var/can_head = TRUE
var/destroying = FALSE
@@ -44,11 +45,15 @@
/obj/item/clothing/head/mob_holder/proc/update_visuals(mob/living/L)
appearance = L.appearance
-/obj/item/clothing/head/mob_holder/dropped()
+/obj/item/clothing/head/mob_holder/dropped(mob/user, thrown = FALSE)
..()
- if(held_mob && isturf(loc))
+ if(held_mob && isturf(loc) && !thrown)
release()
+/obj/item/clothing/head/mob_holder/throw_impact(atom/hit_atom, datum/thrownthing/thrownthing)
+ . = ..()
+ release()
+
/obj/item/clothing/head/mob_holder/proc/release(del_on_release = TRUE)
if(!held_mob)
if(del_on_release && !destroying)
diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm
index 22514ee8ceebc..32e4d34012f3b 100644
--- a/code/modules/mob/living/life.dm
+++ b/code/modules/mob/living/life.dm
@@ -2,12 +2,12 @@
set waitfor = FALSE
set invisibility = 0
- if(digitalinvis) //AI unable to see mob
+ if(HAS_TRAIT(src,TRAIT_DIGINVIS)) //AI unable to see mob
if(!digitaldisguise)
src.digitaldisguise = image(loc = src)
src.digitaldisguise.override = 1
for(var/mob/living/silicon/ai/AI in GLOB.ai_list)
- AI.client.images |= src.digitaldisguise
+ AI.client?.images |= src.digitaldisguise
if((movement_type & FLYING) && !(movement_type & FLOATING)) //TODO: Better floating
float(on = TRUE)
@@ -42,7 +42,7 @@
handle_environment(environment)
//Handle gravity
- var/gravity = mob_has_gravity()
+ var/gravity = has_gravity()
update_gravity(gravity)
if(gravity > STANDARD_GRAVITY)
@@ -90,7 +90,7 @@
ExtinguishMob()
return TRUE //mob was put out, on_fire = FALSE via ExtinguishMob(), no need to update everything down the chain.
var/datum/gas_mixture/G = loc.return_air() // Check if we're standing in an oxygenless environment
- if(G.get_moles(/datum/gas/oxygen) < 1)
+ if(G.get_moles(GAS_O2) < 1)
ExtinguishMob() //If there's no oxygen in the tile we're on, put out the fire
return TRUE
var/turf/location = get_turf(src)
@@ -109,9 +109,11 @@
if(client && !eye_blind)
clear_alert("blind")
clear_fullscreen("blind")
+ //Prevents healing blurryness while blind from normal means
+ return
else
eye_blind = max(eye_blind-1,1)
- else if(eye_blurry) //blurry eyes heal slowly
+ if(eye_blurry) //blurry eyes heal slowly
eye_blurry = max(eye_blurry-1, 0)
if(client)
update_eye_blur()
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 24b2c5143949b..413fe0be22d22 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -10,6 +10,8 @@
faction += "[REF(src)]"
GLOB.mob_living_list += src
initialize_footstep()
+ if (playable)
+ set_playable() //announce to ghosts
/mob/living/proc/initialize_footstep()
AddComponent(/datum/component/footstep)
@@ -53,18 +55,26 @@
/mob/living/proc/OpenCraftingMenu()
return
+/mob/living/proc/can_bumpslam()
+ REMOVE_MOB_PROPERTY(src, PROP_CANTBUMPSLAM, src.type)
+
//Generic Bump(). Override MobBump() and ObjBump() instead of this.
/mob/living/Bump(atom/A)
if(..()) //we are thrown onto something
return
if(buckled || now_pushing)
return
- if(!ismovableatom(A) || is_blocked_turf(A)) // ported from VORE, sue me
- if((confused || is_blind()) && stat == CONSCIOUS && m_intent=="run" && mobility_flags & MOBILITY_STAND)
+ if((confused || is_blind()) && stat == CONSCIOUS && (mobility_flags & MOBILITY_STAND) && m_intent == "run" && (!ismovableatom(A) || is_blocked_turf(A)) && !HAS_MOB_PROPERTY(src, PROP_CANTBUMPSLAM)) // ported from VORE, sue me
+ APPLY_MOB_PROPERTY(src, PROP_CANTBUMPSLAM, src.type) //Bump() is called continuously so ratelimit the check to 20 seconds if it passes or 5 if it doesn't
+ if(prob(10))
playsound(get_turf(src), "punch", 25, 1, -1)
visible_message("[src] [pick("ran", "slammed")] into \the [A]!")
apply_damage(5, BRUTE)
Paralyze(40)
+ addtimer(CALLBACK(src, .proc/can_bumpslam), 200)
+ else
+ addtimer(CALLBACK(src, .proc/can_bumpslam), 50)
+
if(ismob(A))
var/mob/M = A
@@ -181,7 +191,7 @@
return TRUE
//anti-riot equipment is also anti-push
for(var/obj/item/I in M.held_items)
- if(!istype(M, /obj/item/clothing))
+ if(!isclothing(M))
if(I.block_power >= 50)
return
@@ -269,6 +279,9 @@
pulling = AM
AM.pulledby = src
+
+ SEND_SIGNAL(src, COMSIG_LIVING_START_PULL, AM, state, force)
+
if(!supress_message)
var/sound_to_play = 'sound/weapons/thudswoosh.ogg'
if(ishuman(src))
@@ -284,14 +297,20 @@
var/mob/M = AM
log_combat(src, M, "grabbed", addition="passive grab")
- if(!supress_message && !(iscarbon(AM) && HAS_TRAIT(src, TRAIT_STRONG_GRABBER)))
- M.visible_message("[src] grabs [M] [(zone_selected == "l_arm" || zone_selected == "r_arm")? "by their hands":"passively"]!", \
- "[src] grabs you [(zone_selected == "l_arm" || zone_selected == "r_arm")? "by your hands":"passively"]!", null, null, src)
- to_chat(src, "You grab [M] [(zone_selected == "l_arm" || zone_selected == "r_arm")? "by their hands":"passively"]!")
+ if(!supress_message && !(iscarbon(AM) && HAS_TRAIT(src, TRAIT_STRONG_GRABBER))) //Everything in this if statement handles chat messages for grabbing
+ var/mob/living/L = M
+ if (L.getorgan(/obj/item/organ/tail) && zone_selected == BODY_ZONE_PRECISE_GROIN) //Does the target have a tail?
+ M.visible_message("[src] grabs [L] by [L.p_their()] tail!",\
+ " [src] grabs you by the tail!", null, null, src) //Message sent to area, Message sent to grabbee
+ to_chat(src, "You grab [L] by [L.p_their()] tail!") //Message sent to grabber
+ else
+ M.visible_message("[src] grabs [M] [(zone_selected == BODY_ZONE_L_ARM || zone_selected == BODY_ZONE_R_ARM)? "by their hands":"passively"]!", \
+ "[src] grabs you [(zone_selected == BODY_ZONE_L_ARM || zone_selected == BODY_ZONE_R_ARM)? "by your hands":"passively"]!", null, null, src) //Message sent to area, Message sent to grabbee
+ to_chat(src, "You grab [M] [(zone_selected == BODY_ZONE_L_ARM|| zone_selected == BODY_ZONE_R_ARM)? "by their hands":"passively"]!") //Message sent to grabber
if(!iscarbon(src))
M.LAssailant = null
else
- M.LAssailant = usr
+ M.LAssailant = WEAKREF(usr)
if(isliving(M))
var/mob/living/L = M
//Share diseases that are spread by touch
@@ -513,15 +532,6 @@
/mob/living/is_drawable(mob/user, allowmobs = TRUE)
return (allowmobs && reagents && can_inject(user))
-/mob/living/proc/get_organ_target()
- var/mob/shooter = src
- var/t = shooter.zone_selected
- if ((t in list( BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH )))
- t = BODY_ZONE_HEAD
- var/def_zone = ran_zone(t)
- return def_zone
-
-
/mob/living/proc/updatehealth()
if(status_flags & GODMODE)
return
@@ -537,10 +547,10 @@
if(full_heal)
fully_heal(admin_revive)
if(stat == DEAD && can_be_revived()) //in some cases you can't revive (e.g. no brain)
- GLOB.dead_mob_list -= src
- GLOB.alive_mob_list += src
+ remove_from_dead_mob_list()
+ add_to_alive_mob_list()
set_suicide(FALSE)
- stat = UNCONSCIOUS //the mob starts unconscious,
+ set_stat(UNCONSCIOUS) //the mob starts unconscious,
blind_eyes(1)
updatehealth() //then we check if the mob should wake up.
update_mobility()
@@ -648,8 +658,8 @@
if(!(mobility_flags & MOBILITY_STAND) && !buckled && prob(getBruteLoss()*200/maxHealth))
makeTrail(newloc, T, old_direction)
-/mob/living/proc/makeTrail(turf/target_turf, turf/start, direction)
- if(!has_gravity())
+/mob/living/proc/makeTrail(turf/target_turf, turf/start, direction, spec_color)
+ if(!has_gravity() || (movement_type & THROWN))
return
var/blood_exists = FALSE
@@ -679,9 +689,13 @@
TH.add_overlay(image('icons/effects/blood.dmi', trail_type, dir = newdir))
TH.transfer_mob_blood_dna(src)
-/mob/living/carbon/human/makeTrail(turf/T)
+ if(spec_color)
+ TH.color = spec_color
+
+/mob/living/carbon/human/makeTrail(turf/T, turf/start, direction, spec_color)
if((NOBLOOD in dna.species.species_traits) || !bleed_rate || bleedsuppress)
return
+ spec_color = dna.species.blood_color
..()
/mob/living/proc/getTrail()
@@ -742,9 +756,9 @@
resist_buckle()
//Breaking out of a container (Locker, sleeper, cryo...)
- else if(isobj(loc))
- var/obj/C = loc
- C.container_resist(src)
+ else if(istype(loc, /atom/movable))
+ var/atom/movable/M = loc
+ M.container_resist(src)
else if(mobility_flags & MOBILITY_MOVE)
if(on_fire)
@@ -799,11 +813,11 @@
clear_alert("gravity")
else
if(has_gravity >= GRAVITY_DAMAGE_TRESHOLD)
- throw_alert("gravity", /obj/screen/alert/veryhighgravity)
+ throw_alert("gravity", /atom/movable/screen/alert/veryhighgravity)
else
- throw_alert("gravity", /obj/screen/alert/highgravity)
+ throw_alert("gravity", /atom/movable/screen/alert/highgravity)
else
- throw_alert("gravity", /obj/screen/alert/weightless)
+ throw_alert("gravity", /atom/movable/screen/alert/weightless)
if(!override && !is_flying())
float(!has_gravity)
@@ -814,9 +828,8 @@
if(anchored || (buckled && buckled.anchored))
fixed = 1
if(on && !(movement_type & FLOATING) && !fixed)
- animate(src, pixel_y = pixel_y + 2, time = 10, loop = -1)
- sleep(10)
- animate(src, pixel_y = pixel_y - 2, time = 10, loop = -1)
+ animate(src, pixel_y = 2, time = 10, loop = -1, flags = ANIMATION_RELATIVE)
+ animate(pixel_y = -2, time = 10, loop = -1, flags = ANIMATION_RELATIVE)
setMovetype(movement_type | FLOATING)
else if(((!on || fixed) && (movement_type & FLOATING)))
animate(src, pixel_y = get_standard_pixel_y_offset(lying), time = 10)
@@ -912,7 +925,7 @@
loc_temp = obj_temp
else if(isspaceturf(get_turf(src)))
var/turf/heat_turf = get_turf(src)
- loc_temp = heat_turf.temperature
+ loc_temp = heat_turf.return_temperature()
return loc_temp
/mob/living/proc/get_standard_pixel_x_offset(lying = 0)
@@ -938,7 +951,7 @@
return 0
if(invisibility || alpha == 0)//cloaked
return 0
- if(digitalcamo || digitalinvis)
+ if(HAS_TRAIT(src, TRAIT_DIGICAMO) || HAS_TRAIT(src, TRAIT_DIGINVIS))
return 0
// Now, are they viewable by a camera? (This is last because it's the most intensive check)
@@ -1005,7 +1018,7 @@
return TRUE
return FALSE
-/mob/living/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback)
+/mob/living/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force = MOVE_FORCE_STRONG, quickstart = TRUE)
stop_pulling()
. = ..()
@@ -1063,7 +1076,7 @@
src.visible_message("[src] catches fire!", \
"You're set on fire!")
new/obj/effect/dummy/lighting_obj/moblight/fire(src)
- throw_alert("fire", /obj/screen/alert/fire)
+ throw_alert("fire", /atom/movable/screen/alert/fire)
update_fire()
SEND_SIGNAL(src, COMSIG_LIVING_IGNITED,src)
return TRUE
@@ -1338,11 +1351,11 @@
return FALSE
if("stat")
if((stat == DEAD) && (var_value < DEAD))//Bringing the dead back to life
- GLOB.dead_mob_list -= src
- GLOB.alive_mob_list += src
+ remove_from_dead_mob_list()
+ add_to_alive_mob_list()
if((stat < DEAD) && (var_value == DEAD))//Kill he
- GLOB.alive_mob_list -= src
- GLOB.dead_mob_list += src
+ remove_from_alive_mob_list()
+ add_to_dead_mob_list()
. = ..()
switch(var_name)
if("knockdown")
@@ -1385,6 +1398,6 @@
"}
/mob/living/eminence_act(mob/living/simple_animal/eminence/eminence)
- if(is_servant_of_ratvar(src))
+ if(is_servant_of_ratvar(src) && !iseminence(src))
eminence.selected_mob = src
to_chat(eminence, "You select [src].")
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index 31293726e61b4..7a9827ce54f41 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -1,9 +1,13 @@
-
-/mob/living/proc/run_armor_check(def_zone = null, attack_flag = "melee", absorb_text = null, soften_text = null, armour_penetration, penetrated_text)
+/mob/living/proc/run_armor_check(def_zone = null, attack_flag = "melee", absorb_text = null, soften_text = null, armour_penetration, penetrated_text, silent=FALSE)
var/armor = getarmor(def_zone, attack_flag)
+ if(armor <= 0)
+ return armor
+ if(silent)
+ return max(0, armor - armour_penetration)
+
//the if "armor" check is because this is used for everything on /living, including humans
- if(armor > 0 && armour_penetration)
+ if(armour_penetration)
armor = max(0, armor - armour_penetration)
if(penetrated_text)
to_chat(src, "[penetrated_text]")
@@ -14,14 +18,13 @@
to_chat(src, "[absorb_text]")
else
to_chat(src, "Your armor absorbs the blow!")
- else if(armor > 0)
+ else
if(soften_text)
to_chat(src, "[soften_text]")
else
to_chat(src, "Your armor softens the blow!")
return armor
-
/mob/living/proc/getarmor(def_zone, type)
return 0
@@ -38,12 +41,12 @@
/mob/living/proc/is_eyes_covered(check_glasses = 1, check_head = 1, check_mask = 1)
return FALSE
-/mob/living/proc/is_pepper_proof(check_head = TRUE, check_mask = TRUE)
- return FALSE
+
/mob/living/proc/on_hit(obj/item/projectile/P)
return BULLET_ACT_HIT
/mob/living/bullet_act(obj/item/projectile/P, def_zone)
+ SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, P, def_zone)
var/armor = run_armor_check(def_zone, P.flag, "","",P.armour_penetration)
if(!P.nodamage)
apply_damage(P.damage, P.damage_type, def_zone, armor)
@@ -68,8 +71,13 @@
var/zone = ran_zone(BODY_ZONE_CHEST, 65)//Hits a random part of the body, geared towards the chest
var/dtype = BRUTE
var/volume = I.get_volume_by_throwforce_and_or_w_class()
- SEND_SIGNAL(I, COMSIG_MOVABLE_IMPACT_ZONE, src, zone)
- dtype = I.damtype
+ var/nosell_hit = SEND_SIGNAL(I, COMSIG_MOVABLE_IMPACT_ZONE, src, zone, throwingdatum) // TODO: find a better way to handle hitpush and skipcatch for humans
+ if(nosell_hit)
+ skipcatch = TRUE
+ hitpush = FALSE
+
+ if(blocked)
+ return TRUE
if (I.throwforce > 0) //If the weapon's throwforce is greater than zero...
if (I.throwhitsound) //...and throwhitsound is defined...
@@ -88,13 +96,17 @@
"You're hit by [I]!")
var/armor = run_armor_check(zone, "melee", "Your armor has protected your [parse_zone(zone)].", "Your armor has softened hit to your [parse_zone(zone)].",I.armour_penetration)
apply_damage(I.throwforce, dtype, zone, armor)
- if(I.thrownby)
- log_combat(I.thrownby, src, "threw and hit", I)
+
+ var/mob/thrown_by = I.thrownby?.resolve()
+ if(thrown_by)
+ log_combat(thrown_by, src, "threw and hit", I)
+ if(!incapacitated(FALSE, TRUE)) // physics says it's significantly harder to push someone by constantly chucking random furniture at them if they are down on the floor.
+ hitpush = FALSE
else
return 1
else
playsound(loc, 'sound/weapons/genhit.ogg', 50, 1, -1)
- ..()
+ ..(AM, skipcatch, hitpush, blocked, throwingdatum)
/mob/living/mech_melee_attack(obj/mecha/M)
@@ -104,7 +116,7 @@
step_away(src,M,15)
switch(M.damtype)
if(BRUTE)
- Unconscious(20)
+ Knockdown(20)
take_overall_damage(rand(M.force/2, M.force))
playsound(src, 'sound/weapons/punch4.ogg', 50, 1)
if(BURN)
@@ -170,7 +182,7 @@
if(!user.pulling || user.pulling != src || user.grab_state != old_grab_state)
return 0
if(user.a_intent != INTENT_GRAB)
- to_chat(user, "You must be on grab intent to upgrade your grab further!")
+ to_chat(user, "You must be on grab intent to upgrade your grab further!")
return 0
user.setGrabState(user.grab_state + 1)
switch(user.grab_state)
@@ -213,11 +225,11 @@
M.Feedstop()
return // can't attack while eating!
- if(HAS_TRAIT(src, TRAIT_PACIFISM))
+ if(HAS_TRAIT(M, TRAIT_PACIFISM))
to_chat(M, "You don't want to hurt anyone!")
return FALSE
- if (stat != DEAD)
+ if(stat != DEAD)
log_combat(M, src, "attacked")
M.do_attack_animation(src)
visible_message("\The [M.name] glomps [src]!", \
@@ -227,21 +239,20 @@
/mob/living/attack_animal(mob/living/simple_animal/M)
M.face_atom(src)
if(M.melee_damage == 0)
- M.visible_message("\The [M] [M.friendly] [src]!", \
- "\The [M] [M.friendly] you!")
+ visible_message("\The [M] [M.friendly] [src]!", \
+ "\The [M] [M.friendly] you!", null, COMBAT_MESSAGE_RANGE)
+ return FALSE
+ if(HAS_TRAIT(M, TRAIT_PACIFISM))
+ to_chat(M, "You don't want to hurt anyone!")
return FALSE
- else
- if(HAS_TRAIT(M, TRAIT_PACIFISM))
- to_chat(M, "You don't want to hurt anyone!")
- return FALSE
- if(M.attack_sound)
- playsound(loc, M.attack_sound, 50, 1, 1)
- M.do_attack_animation(src)
- visible_message("\The [M] [M.attacktext] [src]!", \
- "\The [M] [M.attacktext] you!", null, COMBAT_MESSAGE_RANGE)
- log_combat(M, src, "attacked")
- return TRUE
+ if(M.attack_sound)
+ playsound(loc, M.attack_sound, 50, 1, 1)
+ M.do_attack_animation(src)
+ visible_message("\The [M] [M.attacktext] [src]!", \
+ "\The [M] [M.attacktext] you!", null, COMBAT_MESSAGE_RANGE)
+ log_combat(M, src, "attacked")
+ return TRUE
/mob/living/attack_paw(mob/living/carbon/monkey/M)
@@ -261,7 +272,7 @@
log_combat(M, src, "attacked")
playsound(loc, 'sound/weapons/bite.ogg', 50, 1, -1)
visible_message("[M.name] bites [src]!", \
- "[M.name] bites you!", null, COMBAT_MESSAGE_RANGE)
+ "[M.name] bites you!", null, COMBAT_MESSAGE_RANGE)
return TRUE
return FALSE
@@ -290,6 +301,7 @@
return FALSE
/mob/living/attack_alien(mob/living/carbon/alien/humanoid/M)
+ SEND_SIGNAL(src, COMSIG_MOB_ATTACK_ALIEN, M)
switch(M.a_intent)
if ("help")
visible_message("[M] caresses [src] with its scythe-like arm.", \
@@ -320,7 +332,7 @@
return 1
/mob/living/proc/electrocute_act(shock_damage, source, siemens_coeff = 1, safety = 0, tesla_shock = 0, illusion = 0, stun = TRUE)
- SEND_SIGNAL(src, COMSIG_LIVING_ELECTROCUTE_ACT, shock_damage)
+ SEND_SIGNAL(src, COMSIG_LIVING_ELECTROCUTE_ACT, shock_damage, source, siemens_coeff, safety, tesla_shock, illusion, stun)
if(tesla_shock && (flags_1 & TESLA_IGNORE_1))
return FALSE
if(HAS_TRAIT(src, TRAIT_SHOCKIMMUNE))
@@ -350,7 +362,7 @@
SSmedals.UnlockMedal(MEDAL_SINGULARITY_DEATH,client)
- investigate_log("([key_name(src)]) has been consumed by the singularity.", INVESTIGATE_SINGULO) //Oh that's where the clown ended up!
+ investigate_log("([key_name(src)]) has been consumed by the singularity.", INVESTIGATE_ENGINES) //Oh that's where the clown ended up!
gib()
return(gain)
@@ -381,7 +393,7 @@
//called when the mob receives a bright flash
-/mob/living/proc/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /obj/screen/fullscreen/flash)
+/mob/living/proc/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /atom/movable/screen/fullscreen/flash)
if(get_eye_protection() < intensity && (override_blindness_check || !(HAS_TRAIT(src, TRAIT_BLIND))))
overlay_fullscreen("flash", type)
addtimer(CALLBACK(src, .proc/clear_fullscreen, "flash", 25), 25)
@@ -410,7 +422,5 @@
else
E.extrapolate(src, diseases, user)
return TRUE
- else
+ else
return FALSE
-
-
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index 9a2c22c7dc429..153a23c96fe38 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -4,6 +4,8 @@
see_in_dark = 2
hud_possible = list(HEALTH_HUD,STATUS_HUD,ANTAG_HUD,NANITE_HUD,DIAG_NANITE_FULL_HUD)
pressure_resistance = 10
+ chat_color = "#CCCCCC" //The say color of the mob, for when ID say isn't available (simplemobs that are not /mob/living/carbon/human)
+
var/resize = 1 //Badminnery resize
var/lastattacker = null
@@ -77,6 +79,9 @@
var/list/guaranteed_butcher_results = null //these will always be yielded from butchering
var/butcher_difficulty = 0 //effectiveness prob. is modified negatively by this amount; positive numbers make it more difficult, negative ones make it easier
+ //LETTING SIMPLE ANIMALS ATTACK? WHAT COULD GO WRONG. Defaults to zero so Ian can still be cuddly
+ var/melee_damage = 0
+
var/hellbound = 0 //People who've signed infernal contracts are unrevivable.
var/list/weather_immunities = list()
@@ -115,7 +120,7 @@
var/ventcrawl_layer = PIPING_LAYER_DEFAULT
var/losebreath = 0
- var/mobsay_color = "#CCCCCC" //The say colour of the mob, for when ID say isn't available (simplemobs that are not /mob/living/carbon/human)
+ var/mobchatspan = "unknown" //The span to use when this mob talks in chat for the name tag
//List of active diseases
var/list/diseases = list() // list of all diseases in a mob
@@ -130,3 +135,7 @@
var/icon/held_rh = 'icons/mob/pets_held_rh.dmi'
var/icon/head_icon = 'icons/mob/pets_held.dmi'//what it looks like on your head
var/held_state = ""//icon state for the above
+
+ //is mob player controllable
+ var/playable = FALSE
+ var/flavor_text = FLAVOR_TEXT_NONE
diff --git a/code/modules/mob/living/living_sentience.dm b/code/modules/mob/living/living_sentience.dm
new file mode 100644
index 0000000000000..293c8e920ae7a
--- /dev/null
+++ b/code/modules/mob/living/living_sentience.dm
@@ -0,0 +1,71 @@
+//WHY ISN'T THIS COMPONENT
+
+/mob/living/ghostize(can_reenter_corpse, sentience_retention)
+ . = ..()
+ switch(sentience_retention)
+ if (SENTIENCE_RETAIN)
+ if (playable) //so the alert goes through for observing ghosts
+ set_playable()
+ if (SENTIENCE_FORCE)
+ set_playable()
+ if (SENTIENCE_ERASE)
+ playable = FALSE
+
+/mob/living/attack_ghost(mob/user)
+ . = ..()
+ if(.)
+ return
+ give_mind(user)
+
+/mob/living/Topic(href, href_list)
+ if(..())
+ return
+ if(href_list["activate"])
+ var/mob/dead/observer/ghost = usr
+ if(istype(ghost) && playable)
+ give_mind(ghost)
+
+/mob/living/proc/give_mind(mob/user)
+ if(key || !playable || stat)
+ return 0
+ var/question = alert("Do you want to become [name]?", "[name]", "Yes", "No")
+ if(question == "No" || !src || QDELETED(src))
+ return TRUE
+ if(key)
+ to_chat(user, "Someone else already took [name].")
+ return TRUE
+ key = user.key
+ log_game("[key_name(src)] took control of [name].")
+ remove_from_spawner_menu()
+ if(get_spawner_flavour_text())
+ to_chat(src, "[get_spawner_flavour_text()]")
+ return TRUE
+
+/mob/living/proc/set_playable()
+ playable = TRUE
+ if (!key) //check if there is nobody already inhibiting this mob
+ notify_ghosts("[name] can be controlled", null, enter_link="(Click to play)", source=src, action=NOTIFY_ATTACK, ignore_key = name)
+ LAZYADD(GLOB.mob_spawners["[name]"], src)
+ GLOB.poi_list |= src
+ SSmobs.update_spawners()
+
+/mob/living/get_spawner_desc()
+ return "Become [name]."
+
+/mob/living/get_spawner_flavour_text()
+ switch (flavor_text)
+ if (FLAVOR_TEXT_EVIL)
+ return "You are a untamed creature with no reason to hold back. Kill anyone you see as a threat to you or your cause."
+ if (FLAVOR_TEXT_GOOD)
+ return "Remember, you have no hate towards the inhabitants of the station. There is no reason for you to attack them unless you are attacked."
+ if (FLAVOR_TEXT_GOAL_ANTAG)
+ return "You have a disdain for the inhabitants of this station, but your goals are more important. Make sure you work towards your objectives with your kin, instead of attacking everything on sight."
+ return flavor_text
+
+/mob/living/proc/remove_from_spawner_menu()
+ for(var/spawner in GLOB.mob_spawners)
+ GLOB.mob_spawners[spawner] -= src
+ if(!length(GLOB.mob_spawners[spawner]))
+ GLOB.mob_spawners -= spawner
+ SSmobs.update_spawners()
+ GLOB.poi_list -= src
diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm
index b506482a58488..5c1fecf4e754d 100644
--- a/code/modules/mob/living/say.dm
+++ b/code/modules/mob/living/say.dm
@@ -14,19 +14,15 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
RADIO_KEY_ENGINEERING = RADIO_CHANNEL_ENGINEERING,
RADIO_KEY_SECURITY = RADIO_CHANNEL_SECURITY,
RADIO_KEY_SUPPLY = RADIO_CHANNEL_SUPPLY,
+ RADIO_KEY_EXPLORATION = RADIO_CHANNEL_EXPLORATION,
RADIO_KEY_SERVICE = RADIO_CHANNEL_SERVICE,
// Faction
RADIO_KEY_SYNDICATE = RADIO_CHANNEL_SYNDICATE,
RADIO_KEY_CENTCOM = RADIO_CHANNEL_CENTCOM,
- // Admin
- MODE_KEY_ADMIN = MODE_ADMIN,
- MODE_KEY_DEADMIN = MODE_DEADMIN,
-
// Misc
RADIO_KEY_AI_PRIVATE = RADIO_CHANNEL_AI_PRIVATE, // AI Upload channel
- MODE_KEY_VOCALCORDS = MODE_VOCALCORDS, // vocal cords, used by Voice of God
//kinda localization -- rastaf0
@@ -45,18 +41,14 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
"û" = RADIO_CHANNEL_SECURITY,
"ã" = RADIO_CHANNEL_SUPPLY,
"ì" = RADIO_CHANNEL_SERVICE,
+ "ÑŽ" = RADIO_CHANNEL_EXPLORATION,
// Faction
"Ã¥" = RADIO_CHANNEL_SYNDICATE,
"Ã" = RADIO_CHANNEL_CENTCOM,
- // Admin
- "ç" = MODE_ADMIN,
- "â" = MODE_ADMIN,
-
// Misc
- "ù" = RADIO_CHANNEL_AI_PRIVATE,
- "÷" = MODE_VOCALCORDS
+ "ù" = RADIO_CHANNEL_AI_PRIVATE
))
/mob/living/proc/Ellipsis(original_msg, chance = 50, keep_words)
@@ -81,12 +73,9 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
return new_msg
-/mob/living/say(message, bubble_type,var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
- var/static/list/crit_allowed_modes = list(MODE_WHISPER = TRUE, MODE_CHANGELING = TRUE, MODE_ALIEN = TRUE)
+/mob/living/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+ var/static/list/crit_allowed_modes = list(WHISPER_MODE = TRUE, MODE_CHANGELING = TRUE, MODE_ALIEN = TRUE)
var/static/list/unconscious_allowed_modes = list(MODE_CHANGELING = TRUE, MODE_ALIEN = TRUE)
- var/talk_key = get_key(message)
-
- var/static/list/one_character_prefix = list(MODE_HEADSET = TRUE, MODE_ROBOT = TRUE, MODE_WHISPER = TRUE, MODE_SING = TRUE)
var/ic_blocked = FALSE
if(client && !forced && CHAT_FILTER_CHECK(message))
@@ -103,62 +92,44 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
to_chat(src, "That message contained a word prohibited in IC chat! Consider reviewing the server rules.\n\"[message]\"")
return
- var/datum/saymode/saymode = SSradio.saymodes[talk_key]
- var/message_mode = get_message_mode(message)
+ var/list/message_mods = list()
var/original_message = message
+ message = get_message_mods(message, message_mods)
+ var/datum/saymode/saymode = SSradio.saymodes[message_mods[RADIO_KEY]]
var/in_critical = InCritical()
- if(one_character_prefix[message_mode])
- message = copytext_char(message, 2)
- else if(message_mode || saymode)
- message = copytext_char(message, 3)
- message = trim_left(message)
-
- if(message_mode == MODE_ADMIN)
- if(client)
- client.cmd_admin_say(message)
- return
-
- if(message_mode == MODE_DEADMIN)
- if(client)
- client.dsay(message)
+ if(!message)
return
if(stat == DEAD)
say_dead(original_message)
return
- if(check_emote(original_message, forced) || !can_speak_basic(original_message, ignore_spam))
+ if(check_emote(original_message, forced) || !can_speak_basic(original_message, ignore_spam, forced))
return
- if(in_critical)
- if(!(crit_allowed_modes[message_mode]))
+ if(in_critical) //There are cheaper ways to do this, but they're less flexible, and this isn't ran all that often
+ var/end = TRUE
+ for(var/index in message_mods)
+ if(crit_allowed_modes[index])
+ end = FALSE
+ break
+ if(end)
return
else if(stat == UNCONSCIOUS)
- if(!(unconscious_allowed_modes[message_mode]))
+ var/end = TRUE
+ for(var/index in message_mods)
+ if(unconscious_allowed_modes[index])
+ end = FALSE
+ break
+ if(end)
return
- // language comma detection.
- var/datum/language/message_language = get_message_language(message)
- if(message_language)
- // No, you cannot speak in xenocommon just because you know the key
- if(can_speak_language(message_language))
- language = message_language
- message = copytext_char(message, 3)
-
- // Trim the space if they said ",0 I LOVE LANGUAGES"
- message = trim_left(message)
+ language = message_mods[LANGUAGE_EXTENSION]
if(!language)
language = get_selected_language()
- // Detection of language needs to be before inherent channels, because
- // AIs use inherent channels for the holopad. Most inherent channels
- // ignore the language argument however.
-
- if(saymode && !saymode.handle_message(src, message, language))
- return
-
if(!can_speak_vocal(message))
to_chat(src, "You find yourself unable to speak!")
return
@@ -168,9 +139,12 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
var/succumbed = FALSE
var/fullcrit = InFullCritical()
- if((InCritical() && !fullcrit) || message_mode == MODE_WHISPER)
+ if((in_critical && !fullcrit) || message_mods[WHISPER_MODE] == MODE_WHISPER)
+ if(saymode || message_mods[RADIO_EXTENSION]) //no radio while in crit
+ saymode = null
+ message_mods -= RADIO_EXTENSION
message_range = 1
- message_mode = MODE_WHISPER
+ message_mods[WHISPER_MODE] = MODE_WHISPER
src.log_talk(message, LOG_WHISPER)
if(fullcrit)
var/health_diff = round(-HEALTH_THRESHOLD_DEAD + health)
@@ -179,7 +153,7 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
message = copytext_char(message, 1, health_diff) + "[message_len > health_diff ? "-.." : "..."]"
message = Ellipsis(message, 10, 1)
last_words = message
- message_mode = MODE_WHISPER_CRIT
+ message_mods[WHISPER_MODE] = MODE_WHISPER_CRIT
succumbed = TRUE
else
src.log_talk(message, LOG_SAY, forced_by=forced)
@@ -197,18 +171,27 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
var/datum/language/L = GLOB.language_datum_instances[language]
spans |= L.spans
- if(message_mode == MODE_SING)
+ if(message_mods[MODE_SING])
var/randomnote = pick("\u2669", "\u266A", "\u266B")
- spans |= SPAN_SINGING
message = "[randomnote] [message] [randomnote]"
+ spans |= SPAN_SINGING
- var/radio_return = radio(message, message_mode, spans, language)
+ //This is before anything that sends say a radio message, and after all important message type modifications, so you can scumb in alien chat or something
+ if(saymode && !saymode.handle_message(src, message, language))
+ return
+ var/radio_message = message
+ if(message_mods[WHISPER_MODE])
+ // radios don't pick up whispers very well
+ radio_message = stars(radio_message)
+ spans |= SPAN_ITALICS
+ var/radio_return = radio(radio_message, message_mods, spans, language)
if(radio_return & ITALICS)
spans |= SPAN_ITALICS
if(radio_return & REDUCE_RANGE)
message_range = 1
+ message_mods[MODE_RADIO_MESSAGE] = MODE_RADIO_MESSAGE
if(radio_return & NOPASS)
- return 1
+ return TRUE
//No screams in space, unless you're next to someone.
var/turf/T = get_turf(src)
@@ -220,16 +203,16 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
if(pressure < ONE_ATMOSPHERE*0.4) //Thin air, let's italicise the message
spans |= SPAN_ITALICS
- send_speech(message, message_range, src, bubble_type, spans, language, message_mode)
+ send_speech(message, message_range, src, bubble_type, spans, language, message_mods)
if(succumbed)
- succumb(1)
- to_chat(src, compose_message(src, language, message, , spans, message_mode))
+ succumb(TRUE)
+ to_chat(src, compose_message(src, language, message, , spans, message_mods))
- return 1
+ return TRUE
-/mob/living/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode)
- . = ..()
+/mob/living/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, list/message_mods = list())
+ SEND_SIGNAL(src, COMSIG_MOVABLE_HEAR, args)
if(!client)
return
var/deaf_message
@@ -243,32 +226,27 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
deaf_type = 2 // Since you should be able to hear yourself without looking
// Recompose message for AI hrefs, language incomprehension.
- message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode)
- message = hear_intercept(message, speaker, message_language, raw_message, radio_freq, spans, message_mode)
-
- show_message(message, 2, deaf_message, deaf_type)
- return message
+ message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mods)
-/mob/living/proc/hear_intercept(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode)
+ show_message(message, MSG_AUDIBLE, deaf_message, deaf_type)
return message
-/mob/living/send_speech(message, message_range = 6, obj/source = src, bubble_type = bubble_icon, list/spans, datum/language/message_language=null, message_mode)
+/mob/living/send_speech(message, message_range = 6, obj/source = src, bubble_type = bubble_icon, list/spans, datum/language/message_language=null, list/message_mods = list())
var/static/list/eavesdropping_modes = list(MODE_WHISPER = TRUE, MODE_WHISPER_CRIT = TRUE)
var/eavesdrop_range = 0
- if(eavesdropping_modes[message_mode])
+ if(message_mods[WHISPER_MODE]) //If we're whispering
eavesdrop_range = EAVESDROP_EXTRA_RANGE
var/list/listening = get_hearers_in_view(message_range+eavesdrop_range, source)
var/list/the_dead = list()
- for(var/_M in GLOB.player_list)
- var/mob/M = _M
+ for(var/mob/M as() in GLOB.player_list)
if(!M) //yogs
continue //yogs | null in player_list for whatever reason :shrug:
if(M.stat != DEAD) //not dead, not important
continue
if(!M.client || !client) //client is so that ghosts don't have to listen to mice
continue
- if(get_dist(M, src) > 7 || M.z != z) //they're out of range of normal hearing
- if(eavesdropping_modes[message_mode] && !(M.client.prefs.chat_toggles & CHAT_GHOSTWHISPER)) //they're whispering and we have hearing whispers at any range off
+ if(get_dist(M, src) > 7 || M.get_virtual_z_level() != get_virtual_z_level()) //they're out of range of normal hearing
+ if(eavesdrop_range && !(M.client.prefs.chat_toggles & CHAT_GHOSTWHISPER)) //they're whispering and we have hearing whispers at any range off
continue
if(!(M.client.prefs.chat_toggles & CHAT_GHOSTEARS)) //they're talking normally and we have hearing at any range off
continue
@@ -279,26 +257,38 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
var/eavesrendered
if(eavesdrop_range)
eavesdropping = stars(message)
- eavesrendered = compose_message(src, message_language, eavesdropping, , spans, message_mode)
+ eavesrendered = compose_message(src, message_language, eavesdropping, , spans, message_mods)
- var/rendered = compose_message(src, message_language, message, , spans, message_mode)
- for(var/_AM in listening)
- var/atom/movable/AM = _AM
+ var/list/show_overhead_message_to = list()
+ var/list/show_overhead_message_to_eavesdrop = list()
+ var/rendered = compose_message(src, message_language, message, , spans, message_mods)
+ for(var/atom/movable/AM as() in listening)
if(eavesdrop_range && get_dist(source, AM) > message_range && !(the_dead[AM]))
- AM.Hear(eavesrendered, src, message_language, eavesdropping, , spans, message_mode)
+ if(ismob(AM))
+ var/mob/M = AM
+ if(M.should_show_chat_message(src, message_language, FALSE, is_heard = TRUE))
+ show_overhead_message_to_eavesdrop += M
+ AM.Hear(eavesrendered, src, message_language, eavesdropping, , spans, message_mods)
else
- AM.Hear(rendered, src, message_language, message, , spans, message_mode)
+ if(ismob(AM))
+ var/mob/M = AM
+ if(M.should_show_chat_message(src, message_language, FALSE, is_heard = TRUE))
+ show_overhead_message_to += M
+ AM.Hear(rendered, src, message_language, message, , spans, message_mods)
+ if(length(show_overhead_message_to))
+ create_chat_message(src, message_language, show_overhead_message_to, message, spans)
+ if(length(show_overhead_message_to_eavesdrop))
+ create_chat_message(src, message_language, show_overhead_message_to_eavesdrop, eavesdropping, spans)
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_LIVING_SAY_SPECIAL, src, message)
//speech bubble
var/list/speech_bubble_recipients = list()
for(var/mob/M in listening)
- if(M.client)
+ if(M.client && !M.client.prefs.chat_on_map)
speech_bubble_recipients.Add(M.client)
var/image/I = image('icons/mob/talk.dmi', src, "[bubble_type][say_test(message)]", FLY_LAYER)
I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
INVOKE_ASYNC(GLOBAL_PROC, /.proc/animate_speechbubble, I, speech_bubble_recipients, 30)
- INVOKE_ASYNC(GLOBAL_PROC, /.proc/animate_chat, src, message, message_language, message_mode, speech_bubble_recipients, 50) // see chatheader.dm
/proc/animate_speechbubble(image/I, list/show_to, duration)
var/matrix/M = matrix()
@@ -319,43 +309,29 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
/mob/living/can_speak(message) //For use outside of Say()
if(can_speak_basic(message) && can_speak_vocal(message))
- return 1
+ return TRUE
-/mob/living/proc/can_speak_basic(message, ignore_spam = FALSE) //Check BEFORE handling of xeno and ling channels
+/mob/living/proc/can_speak_basic(message, ignore_spam = FALSE, forced = FALSE) //Check BEFORE handling of xeno and ling channels
if(client)
if(client.prefs.muted & MUTE_IC)
to_chat(src, "You cannot speak in IC (muted).")
- return 0
- if(!ignore_spam && client.handle_spam_prevention(message,MUTE_IC))
- return 0
+ return FALSE
+ if(!ignore_spam && !forced && client.handle_spam_prevention(message,MUTE_IC))
+ return FALSE
- return 1
+ return TRUE
/mob/living/proc/can_speak_vocal(message) //Check AFTER handling of xeno and ling channels
if(HAS_TRAIT(src, TRAIT_MUTE))
- return 0
+ return FALSE
if(is_muzzled())
- return 0
+ return FALSE
if(!IsVocal())
- return 0
+ return FALSE
- return 1
-
-/mob/living/proc/get_key(message)
- var/key = message[1]
- if(key in GLOB.department_radio_prefixes)
- return lowertext(message[1 + length(key)])
-
-/mob/living/proc/get_message_language(message)
- if(message[1] == ",")
- var/key = message[1 + length(message[1])]
- for(var/ld in GLOB.all_languages)
- var/datum/language/LD = ld
- if(initial(LD.key) == key)
- return LD
- return null
+ return TRUE
/mob/living/proc/treat_message(message)
@@ -376,7 +352,7 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
if(clockslurring)
message = clockslur(message)
-
+
// check for and apply punctuation
var/end = copytext(message, length(message))
if(!(end in list("!", ".", "?", ":", "\"", "-")))
@@ -386,51 +362,46 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
return message
-/mob/living/proc/radio(message, message_mode, list/spans, language)
+/mob/living/proc/radio(message, list/message_mods = list(), list/spans, language)
var/obj/item/implant/radio/imp = locate() in src
if(imp && imp.radio.on)
- if(message_mode == MODE_HEADSET)
- imp.radio.talk_into(src, message, , spans, language)
+ if(message_mods[MODE_HEADSET])
+ imp.radio.talk_into(src, message, , spans, language, message_mods)
return ITALICS | REDUCE_RANGE
- if(message_mode == MODE_DEPARTMENT || (message_mode in imp.radio.channels))
- imp.radio.talk_into(src, message, message_mode, spans, language)
+ if(message_mods[RADIO_EXTENSION] == MODE_DEPARTMENT || (message_mods[RADIO_EXTENSION] in imp.radio.channels))
+ imp.radio.talk_into(src, message, message_mods[RADIO_EXTENSION], spans, language, message_mods)
return ITALICS | REDUCE_RANGE
- switch(message_mode)
- if(MODE_WHISPER)
- return ITALICS
+ switch(message_mods[RADIO_EXTENSION])
if(MODE_R_HAND)
for(var/obj/item/r_hand in get_held_items_for_side("r", all = TRUE))
if (r_hand)
- return r_hand.talk_into(src, message, , spans, language)
+ return r_hand.talk_into(src, message, , spans, language, message_mods)
return ITALICS | REDUCE_RANGE
if(MODE_L_HAND)
for(var/obj/item/l_hand in get_held_items_for_side("l", all = TRUE))
if (l_hand)
- return l_hand.talk_into(src, message, , spans, language)
+ return l_hand.talk_into(src, message, , spans, language, message_mods)
return ITALICS | REDUCE_RANGE
if(MODE_INTERCOM)
- for (var/obj/item/radio/intercom/I in view(MODE_RANGE_INTERCOM, null))
- I.talk_into(src, message, , spans, language)
+ for (var/obj/item/radio/intercom/I in view(MODE_RANGE_INTERCOM, src))
+ I.talk_into(src, message, , spans, language, message_mods)
return ITALICS | REDUCE_RANGE
- if(MODE_BINARY)
- return ITALICS | REDUCE_RANGE //Does not return 0 since this is only reached by humans, not borgs or AIs.
-
return 0
-/mob/living/say_mod(input, message_mode)
- if(message_mode == MODE_WHISPER)
+/mob/living/say_mod(input, list/message_mods = list())
+ if(message_mods[WHISPER_MODE] == MODE_WHISPER)
. = verb_whisper
- else if(message_mode == MODE_WHISPER_CRIT)
+ else if(message_mods[WHISPER_MODE] == MODE_WHISPER_CRIT)
. = "[verb_whisper] in [p_their()] last breath"
+ else if(message_mods[MODE_SING])
+ . = verb_sing
else if(stuttering)
. = "stammers"
else if(derpspeech)
. = "gibbers"
- else if(message_mode == MODE_SING)
- . = verb_sing
else
. = ..()
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 18f7e91bc6c89..bf0e114672700 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -76,7 +76,7 @@
var/nuking = FALSE
var/obj/machinery/doomsday_device/doomsday_device
- var/mob/camera/aiEye/eyeobj
+ var/mob/camera/ai_eye/eyeobj
var/sprint = 10
var/cooldown = 0
var/acceleration = 1
@@ -88,7 +88,7 @@
var/chnotify = 0
var/multicam_on = FALSE
- var/obj/screen/movable/pic_in_pic/ai/master_multicam
+ var/atom/movable/screen/movable/pic_in_pic/ai/master_multicam
var/list/multicam_screens = list()
var/list/all_eyes = list()
var/max_multicams = 6
@@ -132,7 +132,8 @@
if(client)
apply_pref_name("ai",client)
- set_core_display_icon()
+ INVOKE_ASYNC(src, .proc/set_core_display_icon)
+
holo_icon = getHologramIcon(icon('icons/mob/ai.dmi',"default"))
@@ -140,7 +141,7 @@
spark_system.set_up(5, 0, src)
spark_system.attach(src)
- verbs += /mob/living/silicon/ai/proc/show_laws_verb
+ add_verb(/mob/living/silicon/ai/proc/show_laws_verb)
aiPDA = new/obj/item/pda/ai(src)
aiPDA.owner = real_name
@@ -154,10 +155,10 @@
deploy_action.Grant(src)
if(isturf(loc))
- verbs.Add(/mob/living/silicon/ai/proc/ai_network_change, \
+ add_verb(list(/mob/living/silicon/ai/proc/ai_network_change, \
/mob/living/silicon/ai/proc/ai_statuschange, /mob/living/silicon/ai/proc/ai_hologram_change, \
/mob/living/silicon/ai/proc/botcall, /mob/living/silicon/ai/proc/control_integrated_radio, \
- /mob/living/silicon/ai/proc/set_automatic_say_channel)
+ /mob/living/silicon/ai/proc/set_automatic_say_channel))
GLOB.ai_list += src
GLOB.shuttle_caller_list += src
@@ -212,11 +213,17 @@
set name = "Set AI Core Display"
if(incapacitated())
return
+ icon = initial(icon)
+ icon_state = "ai"
+ cut_overlays()
var/list/iconstates = GLOB.ai_core_display_screens
for(var/option in iconstates)
if(option == "Random")
iconstates[option] = image(icon = src.icon, icon_state = "ai-random")
continue
+ if(option == "Portrait")
+ iconstates[option] = image(icon = src.icon, icon_state = "ai-portrait")
+ continue
iconstates[option] = image(icon = src.icon, icon_state = resolve_ai_icon(option))
view_core()
@@ -228,28 +235,32 @@
display_icon_override = ai_core_icon
set_core_display_icon(ai_core_icon)
-/mob/living/silicon/ai/Stat()
- ..()
- if(statpanel("Status"))
- if(!stat)
- stat(null, text("System integrity: [(health+100)/2]%"))
- if(isturf(loc)) //only show if we're "in" a core
- stat(null, text("Backup Power: [battery/2]%"))
- stat(null, text("Connected cyborgs: [connected_robots.len]"))
- for(var/mob/living/silicon/robot/R in connected_robots)
- var/robot_status = "Nominal"
- if(R.shell)
- robot_status = "AI SHELL"
- else if(R.stat || !R.client)
- robot_status = "OFFLINE"
- else if(!R.cell || R.cell.charge <= 0)
- robot_status = "DEPOWERED"
- //Name, Health, Battery, Module, Area, and Status! Everything an AI wants to know about its borgies!
- stat(null, text("[R.name] | S.Integrity: [R.health]% | Cell: [R.cell ? "[R.cell.charge]/[R.cell.maxcharge]" : "Empty"] | \
- Module: [R.designation] | Loc: [get_area_name(R, TRUE)] | Status: [robot_status]"))
- stat(null, text("AI shell beacons detected: [LAZYLEN(GLOB.available_ai_shells)]")) //Count of total AI shells
- else
- stat(null, text("Systems nonfunctional"))
+/mob/living/silicon/ai/get_stat_tab_status()
+ var/list/tab_data = ..()
+ if(!stat)
+ tab_data["System integrity"] = GENERATE_STAT_TEXT("[(health+100)/2]%")
+ if(isturf(loc)) //only show if we're "in" a core
+ tab_data["Backup Power"] = GENERATE_STAT_TEXT("[battery/2]%")
+ tab_data["Connected cyborgs"] = GENERATE_STAT_TEXT("[connected_robots.len]")
+ var/index = 0
+ for(var/mob/living/silicon/robot/R in connected_robots)
+ var/robot_status = "Nominal"
+ if(R.shell)
+ robot_status = "AI SHELL"
+ else if(R.stat || !R.client)
+ robot_status = "OFFLINE"
+ else if(!R.cell || R.cell.charge <= 0)
+ robot_status = "DEPOWERED"
+ //Name, Health, Battery, Module, Area, and Status! Everything an AI wants to know about its borgies!
+ index++
+ tab_data["[R.name] (Connection [index])"] = list(
+ text="S.Integrity: [R.health]% | Cell: [R.cell ? "[R.cell.charge]/[R.cell.maxcharge]" : "Empty"] | \
+ Module: [R.designation] | Loc: [get_area_name(R, TRUE)] | Status: [robot_status]",
+ type=STAT_TEXT)
+ tab_data["AI shell beacons detected"] = GENERATE_STAT_TEXT("[LAZYLEN(GLOB.available_ai_shells)]") //Count of total AI shells
+ else
+ tab_data["Systems"] = GENERATE_STAT_TEXT("nonfunctional")
+ return tab_data
/mob/living/silicon/ai/proc/ai_alerts()
var/dat = "Current Station Alerts\n"
@@ -313,7 +324,10 @@
if(!target)
return
- if ((ai.z != target.z) && !is_station_level(ai.z))
+ if ((ai.get_virtual_z_level() != target.get_virtual_z_level()) && !is_station_level(ai.z))
+ return FALSE
+
+ if(A.is_jammed())
return FALSE
if (istype(loc, /obj/item/aicard))
@@ -331,13 +345,16 @@
set category = "OOC"
set desc = "Wipe your core. This is functionally equivalent to cryo, freeing up your job slot."
+ if(stat)
+ return
+
// Guard against misclicks, this isn't the sort of thing we want happening accidentally
if(alert("WARNING: This will immediately wipe your core and ghost you, removing your character from the round permanently (similar to cryo). Are you entirely sure you want to do this?",
"Wipe Core", "No", "No", "Yes") != "Yes")
return
// We warned you.
- var/obj/structure/AIcore/latejoin_inactive/inactivecore = New(loc)
+ var/obj/structure/AIcore/latejoin_inactive/inactivecore = new(loc)
transfer_fingerprints_to(inactivecore)
if(GLOB.announcement_systems.len)
@@ -352,9 +369,9 @@
if(!get_ghost(1))
if(world.time < 30 * 600)//before the 30 minute mark
- ghostize(0) // Players despawned too early may not re-enter the game
+ ghostize(FALSE,SENTIENCE_ERASE) // Players despawned too early may not re-enter the game
else
- ghostize(1)
+ ghostize(TRUE,SENTIENCE_ERASE)
QDEL_NULL(src)
@@ -363,7 +380,7 @@
set name = "Toggle Floor Bolts"
if(!isturf(loc)) // if their location isn't a turf
return // stop
- if(stat == DEAD)
+ if(stat)
return
if(incapacitated())
if(battery < 50)
@@ -433,9 +450,11 @@
trackeable += track.humans + track.others
var/list/target = list()
for(var/I in trackeable)
- var/mob/M = trackeable[I]
- if(M.name == string)
- target += M
+ var/datum/weakref/to_resolve = trackeable[I]
+ var/mob/to_track = to_resolve.resolve()
+ if(!to_track || to_track.name != string)
+ continue
+ target += to_track
if(name == string)
target += src
if(target.len)
@@ -514,13 +533,13 @@
to_chat(src, "Wireless control is disabled.")
return
var/turf/ai_current_turf = get_turf(src)
- var/ai_Zlevel = ai_current_turf.z
+ var/ai_Zlevel = ai_current_turf.get_virtual_z_level()
var/d
d += "Query network status "
d += "
Name
Status
Location
Control
"
for (Bot in GLOB.alive_mob_list)
- if(Bot.z == ai_Zlevel && !Bot.remote_disabled) //Only non-emagged bots on the same Z-level are detected!
+ if(Bot.get_virtual_z_level() == ai_Zlevel && !Bot.remote_disabled) //Only non-emagged bots on the same Z-level are detected!
var/bot_mode = Bot.get_mode()
d += "
Take your unrefined ore to the Redemption Machine in the Delivery Office to redeem points.
--SS13 Command"
-/obj/item/paper/fluff/jobs/security/court_judgement
- name = "paper- 'Judgement'"
+/obj/item/paper/fluff/jobs/security/court_judgment
+ name = "paper- 'Judgment'"
info = "For crimes against the station, the offender is sentenced to: \n \n"
/obj/item/paper/fluff/jobs/toxins/chemical_info
@@ -104,4 +104,3 @@
/obj/item/paper/fluff/stations/lavaland/orm_notice
name = "URGENT!"
info = "A hastily written note has been scribbled here...
Please use the ore redemption machine in the cargo office for smelting. PLEASE!
--The Research Staff"
-
diff --git a/code/modules/paperwork/paperbin.dm b/code/modules/paperwork/paperbin.dm
index 303f791c53165..1dec5232b28f5 100644
--- a/code/modules/paperwork/paperbin.dm
+++ b/code/modules/paperwork/paperbin.dm
@@ -49,8 +49,8 @@
if(over_object == M)
M.put_in_hands(src)
- else if(istype(over_object, /obj/screen/inventory/hand))
- var/obj/screen/inventory/hand/H = over_object
+ else if(istype(over_object, /atom/movable/screen/inventory/hand))
+ var/atom/movable/screen/inventory/hand/H = over_object
M.putItemFromInventoryInHandIfPossible(src, H.held_index)
add_fingerprint(M)
diff --git a/code/modules/paperwork/paperplane.dm b/code/modules/paperwork/paperplane.dm
index 93eba231c6c03..4a97595628ade 100644
--- a/code/modules/paperwork/paperplane.dm
+++ b/code/modules/paperwork/paperplane.dm
@@ -52,8 +52,9 @@
return ..()
-/obj/item/origami/paperplane/throw_at(atom/target, range, speed, mob/thrower, spin=FALSE, diagonals_first = FALSE, datum/callback/callback)
- . = ..(target, range, speed, thrower, FALSE, diagonals_first, callback)
+/obj/item/origami/paperplane/throw_at(atom/target, range, speed, mob/thrower, spin=FALSE, diagonals_first = FALSE, datum/callback/callback, quickstart = TRUE)
+ . = ..(target, range, speed, thrower, FALSE, diagonals_first, callback, quickstart = quickstart)
+
/obj/item/origami/paperplane/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
if(iscarbon(hit_atom))
diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm
index 9681d30a673ce..32ed008c21d81 100644
--- a/code/modules/paperwork/pen.dm
+++ b/code/modules/paperwork/pen.dm
@@ -41,6 +41,7 @@
desc = "It's a normal red ink pen."
icon_state = "pen_red"
colour = "red"
+ throw_speed = 4 // red ones go faster (in this case, fast enough to embed!)
/obj/item/pen/invisible
desc = "It's an invisible pen marker."
@@ -56,8 +57,10 @@
switch(colour)
if("black")
colour = "red"
+ throw_speed++
if("red")
colour = "green"
+ throw_speed = initial(throw_speed)
if("green")
colour = "blue"
else
@@ -71,6 +74,22 @@
icon_state = "pen-fountain"
font = FOUNTAIN_PEN_FONT
+/obj/item/pen/charcoal
+ name = "charcoal stylus"
+ desc = "It's just a wooden stick with some compressed ash on the end. At least it can write."
+ icon_state = "pen-charcoal"
+ colour = "dimgray"
+ font = CHARCOAL_FONT
+ custom_materials = null
+
+/datum/crafting_recipe/charcoal_stylus
+ name = "Charcoal Stylus"
+ result = /obj/item/pen/charcoal
+ reqs = list(/obj/item/stack/sheet/mineral/wood = 1, /datum/reagent/ash = 30)
+ time = 30
+ category = CAT_PRIMAL
+
+
/obj/item/pen/fountain/captain
name = "captain's fountain pen"
desc = "It's an expensive Oak fountain pen. The nib is quite sharp."
@@ -88,6 +107,7 @@
"Black and Silver" = "pen-fountain-b",
"Command Blue" = "pen-fountain-cb"
)
+ embedding = list("embed_chance" = 75, "armour_block" = 40)
/obj/item/pen/fountain/captain/Initialize()
. = ..()
@@ -188,7 +208,7 @@
w_class = initial(w_class)
name = initial(name)
hitsound = initial(hitsound)
- embedding = embedding.setRating(embed_chance = EMBED_CHANCE)
+ embedding = list(embed_chance = EMBED_CHANCE, armour_block = 30)
throwforce = initial(throwforce)
playsound(user, 'sound/weapons/saberoff.ogg', 5, 1)
to_chat(user, "[src] can now be concealed.")
@@ -199,10 +219,11 @@
w_class = WEIGHT_CLASS_NORMAL
name = "energy dagger"
hitsound = 'sound/weapons/blade1.ogg'
- embedding = embedding.setRating(embed_chance = 100) //rule of cool
+ embedding = list(embed_chance = 200, max_damage_mult = 15, armour_block = 40) //rule of cool
throwforce = 35
playsound(user, 'sound/weapons/saberon.ogg', 5, 1)
to_chat(user, "[src] is now active.")
+ updateEmbedding()
var/datum/component/butchering/butchering = src.GetComponent(/datum/component/butchering)
butchering.butchering_enabled = on
update_icon()
diff --git a/code/modules/paperwork/photocopier.dm b/code/modules/paperwork/photocopier.dm
index d8ea3eba98fa4..7d9837aaaf0f6 100644
--- a/code/modules/paperwork/photocopier.dm
+++ b/code/modules/paperwork/photocopier.dm
@@ -112,7 +112,7 @@
else if(ass) //ASS COPY. By Miauw
for(var/i = 0, i < copies, i++)
var/icon/temp_img
- if(ishuman(ass) && (ass.get_item_by_slot(SLOT_W_UNIFORM) || ass.get_item_by_slot(SLOT_WEAR_SUIT)))
+ if(ishuman(ass) && (ass.get_item_by_slot(ITEM_SLOT_ICLOTHING) || ass.get_item_by_slot(ITEM_SLOT_OCLOTHING)))
to_chat(usr, "You feel kind of silly, copying [ass == usr ? "your" : ass][ass == usr ? "" : "\'s"] ass with [ass == usr ? "your" : "[ass.p_their()]"] clothes on." )
break
else if(toner >= 5 && !busy && check_ass()) //You have to be sitting on the copier and either be a xeno or a human without clothes on.
@@ -296,7 +296,7 @@
updateUsrDialog()
return 0
else if(ishuman(ass))
- if(!ass.get_item_by_slot(SLOT_W_UNIFORM) && !ass.get_item_by_slot(SLOT_WEAR_SUIT))
+ if(!ass.get_item_by_slot(ITEM_SLOT_ICLOTHING) && !ass.get_item_by_slot(ITEM_SLOT_OCLOTHING))
return 1
else
return 0
@@ -331,3 +331,16 @@
grind_results = list(/datum/reagent/iodine = 40, /datum/reagent/iron = 10)
var/charges = 5
var/max_charges = 5
+
+/obj/item/toner/large
+ name = "large toner cartridge"
+ desc = "A hefty cartridge of NanoTrasen ValueBrand toner. Fits photocopiers and autopainters alike."
+ grind_results = list(/datum/reagent/iodine = 90, /datum/reagent/iron = 10)
+ charges = 25
+ max_charges = 25
+
+/obj/item/toner/extreme
+ name = "extremely large toner cartridge"
+ desc = "Why would ANYONE need THIS MUCH TONER?"
+ charges = 200
+ max_charges = 200
diff --git a/code/modules/photography/camera/camera.dm b/code/modules/photography/camera/camera.dm
index c8b3aaccddc73..9471c05fc9c85 100644
--- a/code/modules/photography/camera/camera.dm
+++ b/code/modules/photography/camera/camera.dm
@@ -103,7 +103,7 @@
else if(!(get_turf(target) in get_hear(world.view, user)))
return FALSE
else //user is an atom
- if(!(get_turf(target) in view(world.view, user)))
+ if(!(user in viewers(world.view, get_turf(target))))
return FALSE
return TRUE
diff --git a/code/modules/photography/photos/album.dm b/code/modules/photography/photos/album.dm
index ee1ff945c1719..7446240763756 100644
--- a/code/modules/photography/photos/album.dm
+++ b/code/modules/photography/photos/album.dm
@@ -10,6 +10,7 @@
righthand_file = 'icons/mob/inhands/equipment/briefcase_righthand.dmi'
resistance_flags = FLAMMABLE
w_class = WEIGHT_CLASS_SMALL
+ flags_1 = PREVENT_CONTENTS_EXPLOSION_1
var/persistence_id
/obj/item/storage/photo_album/Initialize()
@@ -54,9 +55,6 @@
if(!SEND_SIGNAL(src, COMSIG_TRY_STORAGE_INSERT, P, null, TRUE, TRUE))
qdel(P)
-/obj/item/storage/photo_album/prevent_content_explosion()
- return TRUE
-
/obj/item/storage/photo_album/HoS
persistence_id = "HoS"
diff --git a/code/modules/photography/photos/frame.dm b/code/modules/photography/photos/frame.dm
index 2275deb0ce0cd..522fb5045f63e 100644
--- a/code/modules/photography/photos/frame.dm
+++ b/code/modules/photography/photos/frame.dm
@@ -64,6 +64,7 @@
desc = "Every time you look it makes you laugh."
icon = 'icons/obj/decals.dmi'
icon_state = "frame-empty"
+ layer = ABOVE_WINDOW_LAYER
var/obj/item/photo/framed
var/persistence_id
var/can_decon = TRUE
diff --git a/code/modules/plumbing/ducts.dm b/code/modules/plumbing/ducts.dm
index bb6b3b34dd532..d49ed687c993d 100644
--- a/code/modules/plumbing/ducts.dm
+++ b/code/modules/plumbing/ducts.dm
@@ -57,7 +57,7 @@ All the important duct code:
if(D == src)
continue
if(D.duct_layer & duct_layer)
- disconnect_duct()
+ return INITIALIZE_HINT_QDEL //If we have company, end it all
if(active)
attempt_connect()
@@ -153,8 +153,10 @@ All the important duct code:
lose_neighbours()
reset_connects(0)
update_icon()
- if(ispath(drop_on_wrench) && !QDELING(src))
+ if(ispath(drop_on_wrench))
new drop_on_wrench(drop_location())
+ drop_on_wrench = null
+ if(!QDELETED(src))
qdel(src)
///''''''''''''''''optimized''''''''''''''''' proc for quickly reconnecting after a duct net was destroyed
@@ -233,7 +235,7 @@ All the important duct code:
adjacents += D
return adjacents
-/obj/machinery/duct/update_icon() //setting connects isnt a parameter because sometimes we make more than one change, overwrite it completely or just add it to the bitfield
+/obj/machinery/duct/update_icon() //setting connects isn't a parameter because sometimes we make more than one change, overwrite it completely or just add it to the bitfield
var/temp_icon = initial(icon_state)
for(var/D in GLOB.cardinals)
if(D & connects)
@@ -282,7 +284,7 @@ All the important duct code:
"You hear ratcheting.")
attempt_connect()
return TRUE
-///collection of all the sanity checks to prevent us from stacking ducts that shouldnt be stacked
+///collection of all the sanity checks to prevent us from stacking ducts that shouldn't be stacked
/obj/machinery/duct/proc/can_anchor(turf/T)
if(!T)
T = get_turf(src)
@@ -360,7 +362,7 @@ All the important duct code:
else
connects = EAST | WEST
-///don't connect to other multilayered stuff because honestly it shouldnt be done and I dont wanna deal with it
+///don't connect to other multilayered stuff because honestly it shouldn't be done and I dont wanna deal with it
/obj/machinery/duct/multilayered/connect_duct(obj/machinery/duct/D, direction, ignore_color)
if(istype(D, /obj/machinery/duct/multilayered))
return
diff --git a/code/modules/plumbing/plumbers/_plumb_machinery.dm b/code/modules/plumbing/plumbers/_plumb_machinery.dm
index e242afaa67900..a82a1412b63a1 100644
--- a/code/modules/plumbing/plumbers/_plumb_machinery.dm
+++ b/code/modules/plumbing/plumbers/_plumb_machinery.dm
@@ -8,7 +8,7 @@
icon = 'icons/obj/plumbing/plumbers.dmi'
icon_state = "pump"
density = TRUE
- active_power_usage = 30
+ active_power_usage = 45
use_power = ACTIVE_POWER_USE
resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
///Plumbing machinery is always gonna need reagents, so we might aswell put it here
diff --git a/code/modules/plumbing/plumbers/acclimator.dm b/code/modules/plumbing/plumbers/acclimator.dm
index 76ef1ac892b21..a80d84fb427d4 100644
--- a/code/modules/plumbing/plumbers/acclimator.dm
+++ b/code/modules/plumbing/plumbers/acclimator.dm
@@ -16,7 +16,7 @@
///I cant find a good name for this. Basically if target is 300, and this is 10, it will still target 300 but will start emptying itself at 290 and 310.
var/allowed_temperature_difference = 1
///cool/heat power
- var/heater_coefficient = 0.1
+ var/heater_coefficient = 0.05
///Are we turned on or off? this is from the on and off button
var/enabled = TRUE
///COOLING, HEATING or NEUTRAL. We track this for change, so we dont needlessly update our icon
@@ -33,7 +33,7 @@
. = ..()
AddComponent(/datum/component/plumbing/acclimator, bolt)
-/obj/machinery/plumbing/acclimator/process()
+/obj/machinery/plumbing/acclimator/process(delta_time)
if(stat & NOPOWER || !enabled || !reagents.total_volume || reagents.chem_temp == target_temperature)
if(acclimate_state != NEUTRAL)
acclimate_state = NEUTRAL
@@ -54,7 +54,7 @@
if(reagents.chem_temp <= target_temperature && target_temperature - allowed_temperature_difference <= reagents.chem_temp) //heating here
emptying = TRUE
- reagents.adjust_thermal_energy((target_temperature - reagents.chem_temp) * heater_coefficient * SPECIFIC_HEAT_DEFAULT * reagents.total_volume) //keep constant with chem heater
+ reagents.adjust_thermal_energy((target_temperature - reagents.chem_temp) * heater_coefficient * delta_time * SPECIFIC_HEAT_DEFAULT * reagents.total_volume) //keep constant with chem heater
reagents.handle_reactions()
/obj/machinery/plumbing/acclimator/update_icon()
@@ -74,6 +74,7 @@
if(!ui)
ui = new(user, src, "ChemAcclimator")
ui.open()
+ ui.set_autoupdate(TRUE)
/obj/machinery/plumbing/acclimator/ui_data(mob/user)
var/list/data = list()
@@ -91,19 +92,22 @@
/obj/machinery/plumbing/acclimator/ui_act(action, params)
if(..())
return
- . = TRUE
switch(action)
if("set_target_temperature")
var/target = text2num(params["temperature"])
target_temperature = clamp(target, 0, 1000)
+ . = TRUE
if("set_allowed_temperature_difference")
var/target = text2num(params["temperature"])
allowed_temperature_difference = clamp(target, 0, 1000)
+ . = TRUE
if("toggle_power")
enabled = !enabled
+ . = TRUE
if("change_volume")
var/target = text2num(params["volume"])
reagents.maximum_volume = clamp(round(target), 1, buffer)
+ . = TRUE
#undef COOLING
#undef HEATING
diff --git a/code/modules/plumbing/plumbers/bottle_dispenser.dm b/code/modules/plumbing/plumbers/bottle_dispenser.dm
index f45aff7f0f5ce..fc9dddf41adb9 100644
--- a/code/modules/plumbing/plumbers/bottle_dispenser.dm
+++ b/code/modules/plumbing/plumbers/bottle_dispenser.dm
@@ -60,10 +60,11 @@
/obj/machinery/plumbing/bottle_dispenser/ui_act(action, params)
if(..())
return
- . = TRUE
switch(action)
if("change_bottle_size")
bottle_size = CLAMP(text2num(params["volume"]), 0, 30)
+ . = TRUE
if("change_bottle_name")
var/new_name = stripped_input(usr, "Enter a bottle name.", name, bottle_name)
bottle_name = new_name + " bottle"
+ . = TRUE
diff --git a/code/modules/plumbing/plumbers/destroyer.dm b/code/modules/plumbing/plumbers/destroyer.dm
index 2c80143d839e5..eaf6ba773ef7c 100644
--- a/code/modules/plumbing/plumbers/destroyer.dm
+++ b/code/modules/plumbing/plumbers/destroyer.dm
@@ -2,20 +2,21 @@
name = "chemical disposer"
desc = "Breaks down chemicals and annihilates them."
icon_state = "disposal"
- ///we remove 10 reagents per second
- var/disposal_rate = 10
+ active_power_usage = 70
+ ///we remove 5 reagents per second
+ var/disposal_rate = 5
/obj/machinery/plumbing/disposer/Initialize(mapload, bolt)
. = ..()
AddComponent(/datum/component/plumbing/simple_demand, bolt)
-/obj/machinery/plumbing/disposer/process()
+/obj/machinery/plumbing/disposer/process(delta_time)
if(stat & NOPOWER)
return
if(reagents.total_volume)
if(icon_state != initial(icon_state) + "_working") //threw it here instead of update icon since it only has two states
icon_state = initial(icon_state) + "_working"
- reagents.remove_any(disposal_rate)
+ reagents.remove_any(disposal_rate * delta_time)
else
if(icon_state != initial(icon_state))
icon_state = initial(icon_state)
diff --git a/code/modules/plumbing/plumbers/filter.dm b/code/modules/plumbing/plumbers/filter.dm
index 5f89e4ebdbf28..b5da00e34327d 100644
--- a/code/modules/plumbing/plumbers/filter.dm
+++ b/code/modules/plumbing/plumbers/filter.dm
@@ -39,7 +39,6 @@
/obj/machinery/plumbing/filter/ui_act(action, params)
if(..())
return
- . = TRUE
switch(action)
if("add")
var/new_chem_name = params["name"]
@@ -50,10 +49,12 @@
if(!left.Find(chem_id))
english_left += new_chem_name
left += chem_id
+ . = TRUE
if("right")
if(!right.Find(chem_id))
english_right += new_chem_name
right += chem_id
+ . = TRUE
else
to_chat(usr, "No such known reagent exists!")
@@ -65,9 +66,9 @@
if(english_left.Find(chem_name))
english_left -= chem_name
left -= chem_id
+ . = TRUE
if("right")
if(english_right.Find(chem_name))
english_right -= chem_name
right -= chem_id
-
-
+ . = TRUE
diff --git a/code/modules/plumbing/plumbers/grinder_chemical.dm b/code/modules/plumbing/plumbers/grinder_chemical.dm
index cc42e604c1423..90ab80cb1c26c 100644
--- a/code/modules/plumbing/plumbers/grinder_chemical.dm
+++ b/code/modules/plumbing/plumbers/grinder_chemical.dm
@@ -7,6 +7,7 @@
rcd_cost = 30
rcd_delay = 30
buffer = 400
+ active_power_usage = 80
var/eat_dir = SOUTH
/obj/machinery/plumbing/grinder_chemical/Initialize(mapload, bolt)
diff --git a/code/modules/plumbing/plumbers/patch_dispenser.dm b/code/modules/plumbing/plumbers/patch_dispenser.dm
index 81fd32afb3c0e..1a5c92550b643 100644
--- a/code/modules/plumbing/plumbers/patch_dispenser.dm
+++ b/code/modules/plumbing/plumbers/patch_dispenser.dm
@@ -3,6 +3,8 @@
name = "patch dispenser"
desc = "A dispenser that dispenses patches."
icon_state = "pill_press" //TODO SPRITE IT !!!!!!
+ active_power_usage = 80
+
var/patch_name = "factory patch"
var/patch_size = 40
///the icon_state number for the patch.
@@ -60,10 +62,11 @@
/obj/machinery/plumbing/patch_dispenser/ui_act(action, params)
if(..())
return
- . = TRUE
switch(action)
if("change_patch_size")
patch_size = CLAMP(text2num(params["volume"]), 0, 40)
+ . = TRUE
if("change_patch_name")
var/new_name = stripped_input(usr, "Enter a patch name.", name, patch_name)
patch_name = new_name + " patch"
+ . = TRUE
diff --git a/code/modules/plumbing/plumbers/pill_press.dm b/code/modules/plumbing/plumbers/pill_press.dm
index b4c098a9b52ee..7806cf8e2be9b 100644
--- a/code/modules/plumbing/plumbers/pill_press.dm
+++ b/code/modules/plumbing/plumbers/pill_press.dm
@@ -3,6 +3,7 @@
name = "pill press"
desc = "A press that presses pills."
icon_state = "pill_press"
+ active_power_usage = 100
///the minimum size a pill can be
var/minimum_pill = 5
///the maximum size a pill can be
@@ -92,15 +93,17 @@
/obj/machinery/plumbing/pill_press/ui_act(action, params)
if(..())
return
- . = TRUE
switch(action)
if("change_pill_style")
pill_number = CLAMP(text2num(params["id"]), 1 , PILL_STYLE_COUNT)
+ . = TRUE
if("change_pill_size")
pill_size = CLAMP(text2num(params["volume"]), minimum_pill, maximum_pill)
+ . = TRUE
if("change_pill_name")
var/new_name = stripped_input(usr, "Enter a pill name.", name, pill_name)
if(findtext(new_name, "pill")) //names like pillatron and Pilliam are thus valid
pill_name = new_name
else
pill_name = new_name + " pill"
+ . = TRUE
diff --git a/code/modules/plumbing/plumbers/pumps.dm b/code/modules/plumbing/plumbers/pumps.dm
index 782540470ac6b..c7e5d45a3958c 100644
--- a/code/modules/plumbing/plumbers/pumps.dm
+++ b/code/modules/plumbing/plumbers/pumps.dm
@@ -11,8 +11,8 @@
active_power_usage = 1000
///Are we powered?
var/powered = FALSE
- ///units we pump per process (2 seconds)
- var/pump_power = 2
+ ///units we pump per second
+ var/pump_power = 1
///set to true if the loop couldnt find a geyser in process, so it remembers and stops checking every loop until moved. more accurate name would be absolutely_no_geyser_under_me_so_dont_try
var/geyserless = FALSE
///The geyser object
@@ -46,7 +46,7 @@
powered = FALSE
geyserless = FALSE //we switched state, so lets just set this back aswell
-/obj/machinery/power/liquid_pump/process()
+/obj/machinery/power/liquid_pump/process(delta_time)
if(!anchored || panel_open)
return
if(!geyser && !geyserless)
@@ -64,15 +64,16 @@
powered = TRUE
update_icon()
add_load(active_power_usage)
- pump()
+ pump(delta_time)
else if(powered) //we were powered, but now we arent
powered = FALSE
update_icon()
+
///pump up that sweet geyser nectar
-/obj/machinery/power/liquid_pump/proc/pump()
+/obj/machinery/power/liquid_pump/proc/pump(delta_time)
if(!geyser || !geyser.reagents)
return
- geyser.reagents.trans_to(src, pump_power)
+ geyser.reagents.trans_to(src, pump_power * delta_time)
/obj/machinery/power/liquid_pump/update_icon()
if(powered)
@@ -80,4 +81,4 @@
else if(panel_open)
icon_state = initial(icon_state) + "-open"
else
- icon_state = initial(icon_state)
\ No newline at end of file
+ icon_state = initial(icon_state)
diff --git a/code/modules/plumbing/plumbers/reaction_chamber.dm b/code/modules/plumbing/plumbers/reaction_chamber.dm
index f57102b1406f9..d9ba7ddfbb0e5 100644
--- a/code/modules/plumbing/plumbers/reaction_chamber.dm
+++ b/code/modules/plumbing/plumbers/reaction_chamber.dm
@@ -40,6 +40,7 @@
if(!ui)
ui = new(user, src, "ChemReactionChamber")
ui.open()
+ ui.set_autoupdate(TRUE)
/obj/machinery/plumbing/reaction_chamber/ui_data(mob/user)
var/list/data = list()
@@ -55,15 +56,16 @@
/obj/machinery/plumbing/reaction_chamber/ui_act(action, params)
if(..())
return
- . = TRUE
switch(action)
if("remove")
var/reagent = get_chem_id(params["chem"])
if(reagent)
required_reagents.Remove(reagent)
+ . = TRUE
if("add")
var/input_reagent = get_chem_id(params["chem"])
if(input_reagent && !required_reagents.Find(input_reagent))
var/input_amount = text2num(params["amount"])
if(input_amount)
required_reagents[input_reagent] = input_amount
+ . = TRUE
diff --git a/code/modules/plumbing/plumbers/splitters.dm b/code/modules/plumbing/plumbers/splitters.dm
index a5fba301c59df..0b287fab9c287 100644
--- a/code/modules/plumbing/plumbers/splitters.dm
+++ b/code/modules/plumbing/plumbers/splitters.dm
@@ -42,7 +42,6 @@
/obj/machinery/plumbing/splitter/ui_act(action, params)
if(..())
return
- . = TRUE
switch(action)
if("set_amount")
var/direction = params["target"]
@@ -54,3 +53,4 @@
transfer_side = value
else
return FALSE
+ . = TRUE
diff --git a/code/modules/plumbing/plumbers/synthesizer.dm b/code/modules/plumbing/plumbers/synthesizer.dm
index 482c840aa7d89..65f227abe5906 100644
--- a/code/modules/plumbing/plumbers/synthesizer.dm
+++ b/code/modules/plumbing/plumbers/synthesizer.dm
@@ -7,6 +7,7 @@
icon = 'icons/obj/plumbing/plumbers.dmi'
rcd_cost = 25
rcd_delay = 15
+ active_power_usage = 500
///Amount we produce for every process. Ideally keep under 5 since thats currently the standard duct capacity
var/amount = 1
@@ -55,12 +56,12 @@
. = ..()
AddComponent(/datum/component/plumbing/simple_supply, bolt)
-/obj/machinery/plumbing/synthesizer/process()
+/obj/machinery/plumbing/synthesizer/process(delta_time)
if(stat & NOPOWER || !reagent_id || !amount)
return
- if(reagents.total_volume >= amount) //otherwise we get leftovers, and we need this to be precise
+ if(reagents.total_volume >= amount*delta_time*0.5) //otherwise we get leftovers, and we need this to be precise
return
- reagents.add_reagent(reagent_id, amount)
+ reagents.add_reagent(reagent_id, amount*delta_time*0.5)
/obj/machinery/plumbing/synthesizer/ui_state(mob/user)
@@ -95,7 +96,6 @@
/obj/machinery/plumbing/synthesizer/ui_act(action, params)
if(..())
return
- . = TRUE
switch(action)
if("amount")
var/new_amount = text2num(params["target"])
@@ -107,8 +107,9 @@
if(new_reagent in dispensable_reagents)
reagent_id = new_reagent
. = TRUE
- update_icon()
- reagents.clear_reagents()
+ if(.)
+ update_icon()
+ reagents.clear_reagents()
/obj/machinery/plumbing/synthesizer/update_icon()
if(!r_overlay)
diff --git a/code/modules/pool/components/swimming.dm b/code/modules/pool/components/swimming.dm
new file mode 100644
index 0000000000000..ebcfb965f2776
--- /dev/null
+++ b/code/modules/pool/components/swimming.dm
@@ -0,0 +1,153 @@
+//Component used to show that a mob is swimming, and force them to swim a lil' bit slower. Components are actually really based!
+
+/datum/component/swimming
+ dupe_mode = COMPONENT_DUPE_UNIQUE
+ var/lengths = 0 //How far have we swum?
+ var/lengths_for_bonus = 25 //If you swim this much, you'll count as having "excercised" and thus gain a buff.
+ var/list/species = list()
+ var/drowning = FALSE
+ var/ticks_drowned = 0
+ var/slowdown = 4
+ var/bob_height_min = 2
+ var/bob_height_max = 5
+ var/bob_tick = 0
+
+/datum/component/swimming/Initialize()
+ . = ..()
+ if(!isliving(parent))
+ message_admins("Swimming component erroneously added to a non-living mob ([parent]).")
+ return INITIALIZE_HINT_QDEL //Only mobs can swim, like Ian...
+ var/mob/M = parent
+ M.visible_message("[parent] starts splashing around in the water!")
+ M.add_movespeed_modifier(MOVESPEED_ID_SWIMMING, update=TRUE, priority=50, multiplicative_slowdown=slowdown, movetypes=GROUND)
+ RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/onMove)
+ RegisterSignal(parent, COMSIG_CARBON_SPECIESCHANGE, .proc/onChangeSpecies)
+ RegisterSignal(parent, COMSIG_MOB_ATTACK_HAND_TURF, .proc/try_leave_pool)
+ START_PROCESSING(SSprocessing, src)
+ enter_pool()
+
+/datum/component/swimming/proc/onMove()
+ SIGNAL_HANDLER
+
+ lengths ++
+ if(lengths > lengths_for_bonus)
+ var/mob/living/L = parent
+ SEND_SIGNAL(L, COMSIG_ADD_MOOD_EVENT, "exercise", /datum/mood_event/exercise)
+ L.apply_status_effect(STATUS_EFFECT_EXERCISED) //Swimming is really good excercise!
+ lengths = 0
+
+//Damn edge cases
+/datum/component/swimming/proc/onChangeSpecies()
+ SIGNAL_HANDLER
+
+ var/mob/living/carbon/C = parent
+ var/component_type = /datum/component/swimming
+ if(istype(C) && C?.dna?.species)
+ component_type = C.dna.species.swimming_component
+ var/mob/M = parent
+ RemoveComponent()
+ M.AddComponent(component_type)
+
+/datum/component/swimming/proc/try_leave_pool(datum/source, turf/clicked_turf)
+ SIGNAL_HANDLER
+
+ var/mob/living/L = parent
+ if(!L.can_interact_with(clicked_turf))
+ return
+ if(is_blocked_turf(clicked_turf))
+ return
+ if(istype(clicked_turf, /turf/open/indestructible/sound/pool))
+ return
+ if(L.pulling)
+ INVOKE_ASYNC(src, .proc/pull_out, L, clicked_turf)
+ return
+ INVOKE_ASYNC(src, .proc/climb_out, L, clicked_turf)
+
+/datum/component/swimming/proc/climb_out(var/mob/living/L, turf/clicked_turf)
+ L.forceMove(clicked_turf)
+ L.visible_message("[parent] climbs out of the pool.")
+ RemoveComponent()
+
+/datum/component/swimming/proc/pull_out(var/mob/living/L, turf/clicked_turf)
+ to_chat(parent, "You start to climb out of the pool...")
+ if(do_after(parent, 1 SECONDS, target=clicked_turf))
+ to_chat(parent, "You start to lift [L.pulling] out of the pool...")
+ var/atom/movable/pulled_object = L.pulling
+ if(do_after(parent, 1 SECONDS, target=pulled_object))
+ pulled_object.forceMove(clicked_turf)
+ L.visible_message("[parent] pulls [pulled_object] out of the pool.")
+ var/datum/component/swimming/swimming_comp = pulled_object.GetComponent(/datum/component/swimming)
+ if(swimming_comp)
+ swimming_comp.RemoveComponent()
+
+/datum/component/swimming/UnregisterFromParent()
+ exit_pool()
+ var/mob/M = parent
+ if(drowning)
+ stop_drowning(M)
+ if(bob_tick)
+ M.pixel_y = 0
+ M.remove_movespeed_modifier(MOVESPEED_ID_SWIMMING)
+ UnregisterSignal(parent, COMSIG_MOVABLE_MOVED)
+ UnregisterSignal(parent, COMSIG_CARBON_SPECIESCHANGE)
+ UnregisterSignal(parent, COMSIG_MOB_ATTACK_HAND_TURF)
+ STOP_PROCESSING(SSprocessing, src)
+ return ..()
+
+/datum/component/swimming/process()
+ var/mob/living/L = parent
+ var/floating = FALSE
+ var/obj/item/pool/helditem = L.get_active_held_item()
+ if(istype(helditem) && ISWIELDED(helditem))
+ bob_tick ++
+ animate(L, time=9.5, pixel_y = (L.pixel_y == bob_height_max) ? bob_height_min : bob_height_max)
+ floating = TRUE
+ else
+ if(bob_tick)
+ animate(L, time=5, pixel_y = 0)
+ bob_tick = 0
+ if(!floating && is_drowning(L))
+ if(!drowning)
+ start_drowning(L)
+ drowning = TRUE
+ drown(L)
+ else if(drowning)
+ stop_drowning(L)
+ drowning = FALSE
+ L.adjust_fire_stacks(-1)
+
+/datum/component/swimming/proc/is_drowning(mob/living/victim)
+ var/obj/item/pool/helditem = victim.get_active_held_item()
+ if(istype(helditem) && ISWIELDED(helditem))
+ return
+ return ((!(victim.mobility_flags & MOBILITY_STAND)) && (!HAS_TRAIT(victim, TRAIT_NOBREATH)))
+
+/datum/component/swimming/proc/drown(mob/living/victim)
+ if(victim.losebreath < 1)
+ victim.losebreath += 1
+ ticks_drowned ++
+ if(prob(20))
+ victim.emote("cough")
+ else if(prob(25))
+ victim.emote("gasp")
+ if(ticks_drowned > 20)
+ if(prob(10))
+ victim.visible_message("[victim] falls unconcious for a moment!")
+ victim.Unconscious(10)
+
+/datum/component/swimming/proc/start_drowning(mob/living/victim)
+ to_chat(victim, "Water fills your lungs and mouth, you can't breathe!")
+ ADD_TRAIT(victim, TRAIT_MUTE, "pool")
+
+/datum/component/swimming/proc/stop_drowning(mob/living/victim)
+ victim.emote("cough")
+ to_chat(victim, "You cough up the last of the water, regaining your ability to speak and breathe clearly!")
+ REMOVE_TRAIT(victim, TRAIT_MUTE, "pool")
+ ticks_drowned = 0
+
+/datum/component/swimming/proc/enter_pool()
+ return
+
+//Essentially the same as remove component, but easier for overiding
+/datum/component/swimming/proc/exit_pool()
+ return
diff --git a/code/modules/pool/components/swimming_dissolve.dm b/code/modules/pool/components/swimming_dissolve.dm
new file mode 100644
index 0000000000000..09946a63d2fdb
--- /dev/null
+++ b/code/modules/pool/components/swimming_dissolve.dm
@@ -0,0 +1,32 @@
+/datum/component/swimming/dissolve
+ var/start_alpha = 0
+
+/datum/component/swimming/dissolve/enter_pool()
+ var/mob/living/L = parent
+ start_alpha = L.alpha
+ to_chat(parent, "You begin disolving into the pool, get out fast!")
+
+/datum/component/swimming/dissolve/process()
+ ..()
+ var/mob/living/L = parent
+ var/mob/living/carbon/human/H = L
+ if(istype(H))
+ if(H.wear_suit && isclothing(H.wear_suit))
+ var/obj/item/clothing/CH = H.wear_suit
+ if (CH.clothing_flags & THICKMATERIAL)
+ return
+ L.adjustCloneLoss(1)
+ L.alpha = ((L.health-HEALTH_THRESHOLD_DEAD) / (L.maxHealth - HEALTH_THRESHOLD_DEAD)) * 255
+ if(L.stat == DEAD)
+ L.visible_message("[L] dissolves into the pool!")
+ var/obj/item/organ/brain = L.getorgan(/obj/item/organ/brain)
+ brain.Remove(L) //Maybe making them completely unrecoverable is too far
+ brain.forceMove(get_turf(L))
+ //Force all items to the ground to not delete anything important.
+ for(var/obj/item/W in L)
+ L.dropItemToGround(W, TRUE)
+ //Delete the body.
+ qdel(L)
+
+/datum/component/swimming/dissolve/exit_pool()
+ animate(parent, alpha=start_alpha, time=20)
diff --git a/code/modules/pool/components/swimming_ethereal.dm b/code/modules/pool/components/swimming_ethereal.dm
new file mode 100644
index 0000000000000..13f1877e9d3bf
--- /dev/null
+++ b/code/modules/pool/components/swimming_ethereal.dm
@@ -0,0 +1,11 @@
+/datum/component/swimming/ethereal/enter_pool()
+ var/mob/living/L = parent
+ L.visible_message("Sparks of energy begin coursing around the pool!")
+
+/datum/component/swimming/ethereal/process()
+ ..()
+ var/mob/living/L = parent
+ if(prob(2) && L.nutrition > NUTRITION_LEVEL_FED)
+ L.adjust_nutrition(-50)
+ tesla_zap(L, 7, 2000, TESLA_MOB_STUN)
+ playsound(L, 'sound/machines/defib_zap.ogg', 50, TRUE)
diff --git a/code/modules/pool/components/swimming_felinid.dm b/code/modules/pool/components/swimming_felinid.dm
new file mode 100644
index 0000000000000..6cd36aa17a981
--- /dev/null
+++ b/code/modules/pool/components/swimming_felinid.dm
@@ -0,0 +1,27 @@
+/datum/component/swimming/felinid/enter_pool()
+ var/mob/living/L = parent
+ L.emote("scream")
+ to_chat(parent, "You get covered in water and start panicking!")
+
+/datum/component/swimming/felinid/process()
+ ..()
+ var/mob/living/L = parent
+ var/obj/item/pool/helditem = L.get_active_held_item()
+ if(istype(helditem) && ISWIELDED(helditem))
+ return
+ switch(rand(1, 100))
+ if(1 to 4)
+ to_chat(parent, "You can't touch the bottom!")
+ L.emote("scream")
+ if(5 to 7)
+ if(L.confused < 5)
+ L.confused += 1
+ if(8 to 12)
+ L.Jitter(10)
+ if(13 to 14)
+ shake_camera(L, 15, 1)
+ L.emote("whimper")
+ L.Paralyze(10)
+ to_chat(parent, "You feel like you are never going to get out...")
+ if(15 to 17)
+ L.emote("cry")
diff --git a/code/modules/pool/components/swimming_golem.dm b/code/modules/pool/components/swimming_golem.dm
new file mode 100644
index 0000000000000..669d2aa097ffd
--- /dev/null
+++ b/code/modules/pool/components/swimming_golem.dm
@@ -0,0 +1,9 @@
+/datum/component/swimming/golem/enter_pool()
+ var/mob/living/M = parent
+ M.Paralyze(60)
+ M.visible_message("[M] crashed violently into the ground!",
+ "You sink like a rock!")
+ playsound(get_turf(M), 'sound/effects/picaxe1.ogg')
+
+/datum/component/swimming/golem/is_drowning()
+ return FALSE
diff --git a/code/modules/pool/components/swimming_squid.dm b/code/modules/pool/components/swimming_squid.dm
new file mode 100644
index 0000000000000..1d1beff5b3116
--- /dev/null
+++ b/code/modules/pool/components/swimming_squid.dm
@@ -0,0 +1,8 @@
+/datum/component/swimming/squid
+ slowdown = 0.7
+
+/datum/component/swimming/squid/enter_pool()
+ to_chat(parent, "You feel at ease in your natural habitat!")
+
+/datum/component/swimming/squid/is_drowning(mob/living/victim)
+ return FALSE
diff --git a/code/modules/pool/pool.dm b/code/modules/pool/pool.dm
new file mode 100644
index 0000000000000..503291dd7a03f
--- /dev/null
+++ b/code/modules/pool/pool.dm
@@ -0,0 +1,291 @@
+//Simple pool behaviour. Sprites by Cdey!
+
+/**
+How to pool!
+Place pool turfs on the inside of your pool. This is where you swim. Pool end caps to cap it off on the north face
+Place pool border decals around the pool so it doesn't look weird
+Place a pool ladder at the top of the pool so that it leads to a normal tile (or else it'll be hard to get out of the pool.)
+Place a pool filter somewhere in the pool if you want people to be able to modify the pool's settings (Temperature) or dump reagents into the pool (which'll change the pool's colour)
+*/
+
+/obj/effect/overlay/poolwater
+ name = "Pool water"
+ icon = 'icons/obj/pool.dmi'
+ icon_state = "water"
+ anchored = TRUE
+ layer = ABOVE_ALL_MOB_LAYER
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+
+/turf/open/indestructible/sound/pool
+ name = "Swimming pool"
+ desc = "A fun place where you go to swim! Drag and drop yourself onto it to climb in..."
+ icon = 'icons/obj/pool.dmi'
+ icon_state = "pool"
+ sound = 'sound/effects/splash.ogg'
+ var/id = null //Set me if you don't want the pool and the pump to be in the same area, or you have multiple pools per area.
+ var/obj/effect/water_overlay = null
+
+/turf/open/indestructible/sound/pool/end
+ icon_state = "poolwall"
+
+/turf/open/indestructible/sound/pool/Initialize(mapload)
+ . = ..()
+ water_overlay = new /obj/effect/overlay/poolwater(get_turf(src))
+
+/turf/open/indestructible/sound/pool/proc/set_colour(colour)
+ water_overlay.color = colour
+
+/turf/open/indestructible/sound/pool/end/ChangeTurf(path, list/new_baseturfs, flags)
+ if(water_overlay)
+ qdel(water_overlay)
+ . = ..()
+
+/turf/open/CanPass(atom/movable/mover, turf/target)
+ var/datum/component/swimming/S = mover.GetComponent(/datum/component/swimming) //If you're swimming around, you don't really want to stop swimming just like that do you?
+ if(S)
+ return FALSE //If you're swimming, you can't swim into a regular turf, y'dig?
+ . = ..()
+
+/turf/open/indestructible/sound/pool/CanPass(atom/movable/mover, turf/target)
+ var/datum/component/swimming/S = mover.GetComponent(/datum/component/swimming) //You can't get in the pool unless you're swimming.
+ return (isliving(mover)) ? S : ..() //So you can do stuff like throw beach balls around the pool!
+
+/turf/open/indestructible/sound/pool/Entered(atom/movable/AM)
+ . = ..()
+ SEND_SIGNAL(AM, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_WEAK)
+ if(isliving(AM))
+ var/datum/component/swimming/S = AM.GetComponent(/datum/component/swimming) //You can't get in the pool unless you're swimming.
+ if(!S)
+ var/mob/living/carbon/C = AM
+ var/component_type = /datum/component/swimming
+ if(istype(C) && C?.dna?.species)
+ component_type = C.dna.species.swimming_component
+ AM.AddComponent(component_type)
+
+/turf/open/indestructible/sound/pool/Exited(atom/movable/Obj, atom/newloc)
+ . = ..()
+ if(!istype(newloc, /turf/open/indestructible/sound/pool))
+ var/datum/component/swimming/S = Obj.GetComponent(/datum/component/swimming) //Handling admin TPs here.
+ S?.RemoveComponent()
+
+/turf/open/MouseDrop_T(atom/dropping, mob/user)
+ if(!isliving(user) || !isliving(dropping)) //No I don't want ghosts to be able to dunk people into the pool.
+ return
+ var/atom/movable/AM = dropping
+ var/datum/component/swimming/S = dropping.GetComponent(/datum/component/swimming)
+ if(S)
+ if(do_after(user, 1 SECONDS, target = dropping))
+ S.RemoveComponent()
+ visible_message("[dropping] climbs out of the pool.")
+ AM.forceMove(src)
+ else
+ . = ..()
+
+/turf/open/indestructible/sound/pool/MouseDrop_T(atom/dropping, mob/user)
+ if(!isliving(user) || !isliving(dropping)) //No I don't want ghosts to be able to dunk people into the pool.
+ return
+ var/datum/component/swimming/S = dropping.GetComponent(/datum/component/swimming) //If they're already swimming, don't let them start swimming again.
+ if(S)
+ return FALSE
+ . = ..()
+ if(user != dropping)
+ dropping.visible_message("[user] starts to lower [dropping] down into [src].", \
+ "You start to lower [dropping] down into [src].")
+ else
+ to_chat(user, "You start climbing down into [src]...")
+ if(do_after(user, 4 SECONDS, target = dropping))
+ splash(dropping)
+
+/datum/mood_event/poolparty
+ description = "I love swimming!.\n"
+ mood_change = 2
+ timeout = 2 MINUTES
+
+/datum/mood_event/robotpool
+ description = "I really wasn't built with water resistance in mind...\n"
+ mood_change = -3
+ timeout = 2 MINUTES
+
+/datum/mood_event/poolwet
+ description = "Eugh! my clothes are soaking wet from that swim.\n"
+ mood_change = -4
+ timeout = 4 MINUTES
+
+/turf/open/indestructible/sound/pool/proc/splash(mob/user)
+ user.forceMove(src)
+ playsound(src, 'sound/effects/splosh.ogg', 100, 1) //Credit to hippiestation for this sound file!
+ user.visible_message("SPLASH!")
+ var/zap = 0
+ if(issilicon(user)) //Do not throw brick in a pool. Brick begs.
+ zap = 1 //Sorry borgs! Swimming will come at a cost.
+ if(ishuman(user))
+ var/mob/living/carbon/human/F = user
+ var/datum/species/SS = F.dna.species
+ if(MOB_ROBOTIC in SS.inherent_biotypes) //ZAP goes the IPC!
+ zap = 2 //You can protect yourself from water damage with thick clothing.
+ if(F.head && isclothing(F.head))
+ var/obj/item/clothing/CH = F.head
+ if (CH.clothing_flags & THICKMATERIAL) //Skinsuit should suffice! But IPCs are robots and probably not water-sealed.
+ zap --
+ if(F.wear_suit && isclothing(F.wear_suit))
+ var/obj/item/clothing/CS = F.wear_suit
+ if (CS.clothing_flags & THICKMATERIAL)
+ zap --
+ if(zap > 0)
+ user.emp_act(zap)
+ user.emote("scream") //Chad coders use M.say("*scream")
+ do_sparks(zap, TRUE, user)
+ to_chat(user, "WARNING: WATER DAMAGE DETECTED!")
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "robotpool", /datum/mood_event/robotpool)
+ else
+ if(!check_clothes(user))
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "pool", /datum/mood_event/poolparty)
+ return
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "pool", /datum/mood_event/poolwet)
+
+//Largely a copypaste from shower.dm. Checks if the mob was stupid enough to enter a pool fully clothed. We allow masks as to not discriminate against clown and mime players.
+/turf/open/indestructible/sound/pool/proc/check_clothes(mob/living/carbon/human/H)
+ if(!istype(H) || iscatperson(H)) //Don't care about non humans.
+ return FALSE
+ if(H.wear_suit && (H.wear_suit.clothing_flags & SHOWEROKAY))
+ // Do not check underclothing if the over-suit is suitable.
+ // This stops people feeling dumb if they're showering
+ // with a radiation suit on.
+ return FALSE
+
+ . = FALSE
+ if(!(H.wear_suit?.clothing_flags & SHOWEROKAY))
+ return TRUE
+ if(!(H.w_uniform?.clothing_flags & SHOWEROKAY))
+ return TRUE
+ if(!(H.head?.clothing_flags & SHOWEROKAY))
+ return TRUE
+
+/obj/effect/turf_decal/pool
+ name = "Pool siding"
+ icon = 'icons/obj/pool.dmi'
+ icon_state = "poolborder"
+
+/obj/effect/turf_decal/pool/corner
+ icon_state = "bordercorner"
+
+/obj/effect/turf_decal/pool/innercorner
+ icon_state = "innercorner"
+
+//Pool machinery
+
+/obj/structure/pool_ladder
+ name = "Pool ladder"
+ desc = "Click this to get out of a pool quickly."
+ icon = 'icons/obj/pool.dmi'
+ icon_state = "ladder"
+ pixel_y = 12
+
+GLOBAL_LIST_EMPTY(pool_filters)
+
+/obj/machinery/pool_filter
+ name = "Pool filter"
+ desc = "A device which can help you regulate conditions in a pool. Use a wrench to change its operating temperature, or hit it with a reagent container to load in new liquid to add to the pool."
+ icon = 'icons/obj/pool.dmi'
+ icon_state = "poolfilter"
+ pixel_y = 12 //So it sits above the water
+ idle_power_usage = IDLE_POWER_USE
+ var/id = null //change this if youre an annoying mapper who wants multiple pools per area.
+ var/list/pool = list()
+ var/desired_temperature = 300 //Room temperature
+ var/current_temperature = 300 //current temp
+ var/preset_reagent_type = null //Set this if you want your pump to start filled with a given reagent. SKEWIUM POOL SKEWIUM POOL!
+
+/obj/machinery/pool_filter/examine(mob/user)
+ . = ..()
+ . += "The thermostat on it reads [current_temperature]."
+
+/obj/machinery/pool_filter/Initialize()
+ . = ..()
+ create_reagents(100, OPENCONTAINER) //If you're a terrible terrible clown and want to dump reagents into the pool.
+ if(preset_reagent_type)
+ reagents.add_reagent(preset_reagent_type, 100)
+ var/area/AR = get_area(src)
+ for(var/turf/open/indestructible/sound/pool/water in get_area_turfs(AR))
+ if(id && water.id != id)
+ continue //Not the same id. Fine. Ignore that one then!
+ pool += water
+ GLOB.pool_filters += src
+
+//Brick can set the pool to low temperatures remotely. This will probably be hell on malf!
+
+/obj/machinery/pool_filter/attack_robot(mob/user)
+ . = ..()
+ wrench_act(user, null)
+
+/obj/machinery/pool_filter/attack_ai(mob/user)
+ . = ..()
+ wrench_act(user, null)
+
+/obj/machinery/pool_filter/wrench_act(mob/living/user, obj/item/I)
+ var/newTemp = input(user, "Set a new temperature for [src] (Kelvin).", "[src]", null) as num
+ if(!newTemp)
+ return
+ newTemp = CLAMP(newTemp, T0C, 320)
+ desired_temperature = newTemp
+ return FALSE
+
+/obj/machinery/pool_filter/process(delta_time)
+ if(!LAZYLEN(pool) || !is_operational())
+ return //No use having one of these processing for no reason is there?
+ use_power(idle_power_usage)
+ var/delta = ((current_temperature > desired_temperature) ? -0.25 : 0.25 ) * delta_time
+ current_temperature += delta
+ current_temperature = CLAMP(current_temperature, T0C, desired_temperature)
+ var/trans_amount = reagents.total_volume / pool.len //Split up the reagents equally.
+ for(var/turf/open/indestructible/sound/pool/water as() in pool)
+ if(reagents.reagent_list.len)
+ water.set_colour(mix_color_from_reagents(reagents.reagent_list))
+ else
+ water.set_colour("#009999")
+ if(water.contents.len && reagents.total_volume > 0)
+ for(var/mob/living/M in water)
+ if(!istype(M))
+ continue
+ var/datum/reagents/splash_holder = new/datum/reagents(trans_amount) //Take some of our reagents out, react them with the pool denizens.
+ splash_holder.my_atom = water
+ reagents.trans_to(splash_holder, trans_amount, transfered_by = src)
+ splash_holder.chem_temp = current_temperature
+ if(DT_PROB(80, delta_time))
+ splash_holder.reaction(M, TOUCH)
+ else //Sometimes the water penetrates a lil deeper than just a splosh.
+ splash_holder.reaction(M, INGEST)
+ splash_holder.trans_to(M, trans_amount, transfered_by = src) //Actually put reagents in the mob
+ qdel(splash_holder)
+ var/mob/living/carbon/C = M
+ if(current_temperature <= 283.5) //Colder than 10 degrees is going to make you very cold
+ if(iscarbon(M))
+ C.adjust_bodytemperature(-80, 80)
+ to_chat(M, "The water is freezing cold!")
+ else if(current_temperature >= 308.5) //Hotter than 35 celsius is going to make you burn up
+ if(iscarbon(M))
+ C.adjust_bodytemperature(35, 0, 500)
+ M.adjustFireLoss(2.5 * delta_time)
+ to_chat(M, "The water is searing hot!")
+
+/obj/structure/pool_ladder/attack_hand(mob/user)
+ var/datum/component/swimming/S = user.GetComponent(/datum/component/swimming)
+ if(S)
+ to_chat(user, "You start to climb out of the pool...")
+ if(do_after(user, 1 SECONDS, target=src))
+ S.RemoveComponent()
+ visible_message("[user] climbs out of the pool.")
+ user.forceMove(get_turf(get_step(src, NORTH))) //Ladders shouldn't adjoin another pool section. Ever.
+ else
+ to_chat(user, "You start to climb into the pool...")
+ var/turf/T = get_turf(src)
+ if(do_after(user, 1 SECONDS, target=src))
+ if(!istype(T, /turf/open/indestructible/sound/pool)) //Ugh, fine. Whatever.
+ user.forceMove(get_turf(src))
+ else
+ var/turf/open/indestructible/sound/pool/P = T
+ P.splash(user)
+
+/obj/structure/pool_ladder/attack_robot(mob/user)
+ . = ..()
+ attack_hand(user)
diff --git a/code/modules/pool/pool_items.dm b/code/modules/pool/pool_items.dm
new file mode 100644
index 0000000000000..e6f53786c2c38
--- /dev/null
+++ b/code/modules/pool/pool_items.dm
@@ -0,0 +1,85 @@
+/obj/item/pool
+ icon = 'icons/obj/pool.dmi'
+ lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/items_righthand.dmi'
+ force = 0
+ damtype = STAMINA
+ w_class = WEIGHT_CLASS_BULKY
+ block_sound = 'sound/weapons/tap.ogg'
+ block_level = 1
+ block_upgrade_walk = 0
+ block_power = 0
+ block_flags = BLOCKING_ACTIVE | BLOCKING_NASTY
+ attack_verb = list("wacked")
+
+/obj/item/pool/Initialize()
+ . = ..()
+ //Pick a random color
+ add_atom_colour(pick(COLOR_YELLOW, COLOR_LIME, COLOR_RED, COLOR_BLUE_LIGHT, COLOR_CYAN, COLOR_MAGENTA), FIXED_COLOUR_PRIORITY)
+
+/obj/item/pool/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/two_handed, require_twohands=TRUE, force_unwielded=0, force_wielded=5, \
+ wieldsound='sound/weapons/tap.ogg', unwieldsound='sound/weapons/tap.ogg', block_power_wielded=20)
+
+/obj/item/pool/rubber_ring
+ name = "inflateable ring"
+ desc = "An inflateable ring used for keeping people afloat. Throw at drowning people to save them."
+ icon_state = "rubber_ring"
+
+/obj/item/pool/rubber_ring/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
+ . = ..()
+ if(ishuman(hit_atom))
+ var/mob/living/carbon/human/H = hit_atom
+ //Make sure they are in a pool
+ if(!istype(get_turf(H), /turf/open/indestructible/sound/pool))
+ return
+ //Make sure they are alive and can pick it up
+ if(H.stat)
+ return
+ //Try shove it in their inventory
+ if(H.put_in_active_hand(src))
+ visible_message("The [src] lands over [H]'s head!")
+
+/obj/item/pool/pool_noodle
+ icon_state = "pool_noodle"
+ lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
+ name = "pool noodle"
+ desc = "A long noodle made of foam. Helping those with fears of swimming swim since the 1980s."
+ var/suiciding = FALSE
+
+/obj/item/pool/pool_noodle/attack(mob/target, mob/living/carbon/human/user)
+ . = ..()
+ if(ISWIELDED(src) && prob(50))
+ INVOKE_ASYNC(src, .proc/jedi_spin, user)
+
+/obj/item/pool/pool_noodle/proc/jedi_spin(mob/living/user) //rip complex code, but this fucked up blocking
+ user.emote("flip")
+
+/obj/item/pool/pool_noodle/suicide_act(mob/user)
+ if(suiciding)
+ return SHAME
+ suiciding = TRUE
+ user.visible_message("[user] begins kicking their legs to stay afloat!")
+ var/mob/living/L = user
+ if(istype(L))
+ L.Immobilize(63)
+ animate(user, time=20, pixel_y=18)
+ sleep(20)
+ animate(user, time=10, pixel_y=12)
+ sleep(10)
+ user.visible_message("[user] keeps swimming higher and higher!")
+ animate(user, time=10, pixel_y=22)
+ sleep(10)
+ animate(user, time=10, pixel_y=16)
+ sleep(10)
+ animate(user, time=15, pixel_y=32)
+ sleep(15)
+ user.visible_message("[user] suddenly realised they aren't in the water and cannot float.")
+ animate(user, time=1, pixel_y=0)
+ sleep(1)
+ user.ghostize()
+ user.gib()
+ suiciding = FALSE
+ return MANUAL_SUICIDE
diff --git a/code/modules/power/antimatter/control.dm b/code/modules/power/antimatter/control.dm
index f5b30c7e4b9d0..70c1a5b151735 100644
--- a/code/modules/power/antimatter/control.dm
+++ b/code/modules/power/antimatter/control.dm
@@ -35,13 +35,14 @@
. = ..()
linked_shielding = list()
linked_cores = list()
-
+ investigate_log("has been created.", INVESTIGATE_ENGINES)
/obj/machinery/power/am_control_unit/Destroy()//Perhaps damage and run stability checks rather than just del on the others
for(var/obj/machinery/am_shielding/AMS in linked_shielding)
AMS.control_unit = null
qdel(AMS)
QDEL_NULL(fueljar)
+ investigate_log("has been destroyed.", INVESTIGATE_ENGINES)
return ..()
@@ -239,6 +240,8 @@
/obj/machinery/power/am_control_unit/proc/toggle_power(powerfail = 0)
active = !active
+ investigate_log("turned [active ? "ON" : "OFF"] by [usr ? key_name(usr) : "outside forces"] at [AREACOORD(src)]", INVESTIGATE_ENGINES)
+ message_admins("Antimatter turned [active ? "ON" : "OFF"] by [usr ? ADMIN_LOOKUPFLW(usr) : "outside forces"] in [ADMIN_VERBOSEJMP(src)]")
if(active)
use_power = ACTIVE_POWER_USE
visible_message("The [src.name] starts up.")
@@ -344,11 +347,13 @@
if(href_list["strengthup"])
fuel_injection++
+ investigate_log("increased to [fuel_injection] by [key_name(usr)] at [AREACOORD(src)]", INVESTIGATE_ENGINES)
if(href_list["strengthdown"])
fuel_injection--
if(fuel_injection < 0)
fuel_injection = 0
+ investigate_log("decreased to [fuel_injection] by [key_name(usr)] at [AREACOORD(src)]", INVESTIGATE_ENGINES)
if(href_list["refreshstability"])
check_core_stability()
diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm
index a498c76fafdbc..d6cdc621d3ce2 100644
--- a/code/modules/power/apc.dm
+++ b/code/modules/power/apc.dm
@@ -56,6 +56,7 @@
integrity_failure = 50
resistance_flags = FIRE_PROOF
interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON
+ layer = ABOVE_WINDOW_LAYER
@@ -165,7 +166,7 @@
if (!req_access)
req_access = list(ACCESS_ENGINE_EQUIP)
if (!armor)
- armor = list("melee" = 20, "bullet" = 20, "laser" = 10, "energy" = 100, "bomb" = 30, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 50)
+ armor = list("melee" = 20, "bullet" = 20, "laser" = 10, "energy" = 100, "bomb" = 30, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 50, "stamina" = 0)
..()
GLOB.apcs_list += src
@@ -212,6 +213,7 @@
area.power_equip = FALSE
area.power_environ = FALSE
area.power_change()
+ area.poweralert(FALSE, src)
if(occupier)
malfvacate(1)
qdel(wires)
@@ -273,8 +275,8 @@
if(has_electronics && terminal)
. += "The cover is [opened==APC_COVER_REMOVED?"removed":"open"] and the power cell is [ cell ? "installed" : "missing"]."
else
- . += {"It's [ !terminal ? "not" : "" ] wired up.\n
- The electronics are[!has_electronics?"n't":""] installed."}
+ . += "It's [ !terminal ? "not" : "" ] wired up.\n"+\
+ "The electronics are[!has_electronics?"n't":""] installed."
if(integration_cog || (user.hallucinating() && prob(20)))
. += "A small cogwheel is inside of it."
@@ -331,12 +333,17 @@
if(update & 2)
SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays)
if(!(stat & (BROKEN|MAINT)) && update_state & UPSTATE_ALLGOOD)
- SSvis_overlays.add_vis_overlay(src, icon, "apcox-[locked]", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir)
- SSvis_overlays.add_vis_overlay(src, icon, "apco3-[charging]", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "apcox-[locked]", layer, plane, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "apcox-[locked]", layer, EMISSIVE_PLANE, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "apco3-[charging]", layer, plane, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "apco3-[charging]", layer, EMISSIVE_PLANE, dir)
if(operating)
- SSvis_overlays.add_vis_overlay(src, icon, "apco0-[equipment]", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir)
- SSvis_overlays.add_vis_overlay(src, icon, "apco1-[lighting]", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir)
- SSvis_overlays.add_vis_overlay(src, icon, "apco2-[environ]", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "apco0-[equipment]", layer, plane, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "apco0-[equipment]", layer, EMISSIVE_PLANE, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "apco1-[lighting]", layer, plane, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "apco1-[lighting]", layer, EMISSIVE_PLANE, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "apco2-[environ]", layer, plane, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "apco2-[environ]", layer, EMISSIVE_PLANE, dir)
// And now, separately for cleanness, the lighting changing
if(update_state & UPSTATE_ALLGOOD)
@@ -435,7 +442,7 @@
. = TRUE
if (opened)
if(integration_cog)
- to_chat(user, "You begin prying something out of the APC...")
+ to_chat(user, "You begin prying something out of the APC.")
W.play_tool_sound(src)
if(W.use_tool(src, user, 50))
to_chat(user, "You screw up breaking whatever was inside!")
@@ -445,7 +452,7 @@
to_chat(user, "Disconnect the wires first!")
return
W.play_tool_sound(src)
- to_chat(user, "You attempt to remove the power control board..." )
+ to_chat(user, "You attempt to remove the power control board." )
if(W.use_tool(src, user, 50))
if (has_electronics == APC_ELECTRONICS_INSTALLED)
has_electronics = APC_ELECTRONICS_MISSING
@@ -526,7 +533,7 @@
return
else
panel_open = !panel_open
- to_chat(user, "The wires have been [panel_open ? "exposed" : "unexposed"]")
+ to_chat(user, "The wires have been [panel_open ? "exposed" : "unexposed"].")
update_icon()
/obj/machinery/power/apc/wirecutter_act(mob/living/user, obj/item/W)
@@ -540,7 +547,7 @@
if(!W.tool_start_check(user, amount=3))
return
user.visible_message("[user.name] welds [src].", \
- "You start welding the APC frame...", \
+ "You start welding the APC frame.", \
"You hear welding.")
if(W.use_tool(src, user, 50, volume=50, amount=3))
if ((stat & BROKEN) || opened==APC_COVER_REMOVED)
@@ -598,7 +605,7 @@
to_chat(user, "You need ten lengths of cable for APC!")
return
user.visible_message("[user.name] adds cables to the APC frame.", \
- "You start adding cables to the APC frame...")
+ "You start adding cables to the APC frame.")
playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1)
if(do_after(user, 20, target = src))
if (C.get_amount() < 10 || !C)
@@ -622,12 +629,13 @@
return
user.visible_message("[user.name] inserts the power control board into [src].", \
- "You start to insert the power control board into the frame...")
+ "You start to insert the power control board into the frame.")
playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1)
if(do_after(user, 10, target = src))
if(!has_electronics)
has_electronics = APC_ELECTRONICS_INSTALLED
locked = FALSE
+ wires.ui_update()
to_chat(user, "You place the power control board inside the frame.")
qdel(W)
else if(istype(W, /obj/item/electroadaptive_pseudocircuit) && opened)
@@ -642,6 +650,7 @@
"You adapt a power control board and click it into place in [src]'s guts.")
has_electronics = APC_ELECTRONICS_INSTALLED
locked = FALSE
+ wires.ui_update()
else if(!cell)
if(stat & MAINT)
to_chat(user, "There's no connector for a power cell.")
@@ -653,7 +662,7 @@
cell = C
chargecount = 0
user.visible_message("[user] fabricates a weak power cell and places it into [src].", \
- "Your [P.name] whirrs with strain as you create a weak power cell and place it into [src]!")
+ "Your [P.name] whirs with strain as you create a weak power cell and place it into [src]!")
update_icon()
else
to_chat(user, "[src] has both electronics and a cell.")
@@ -664,7 +673,7 @@
return
if (!(stat & BROKEN) && opened==APC_COVER_REMOVED) // Cover is the only thing broken, we do not need to remove elctronicks to replace cover
user.visible_message("[user.name] replaces missing APC's cover.",\
- "You begin to replace APC's cover...")
+ "You begin to replace APC's cover.")
if(do_after(user, 20, target = src)) // replacing cover is quicker than replacing whole frame
to_chat(user, "You replace missing APC's cover.")
qdel(W)
@@ -675,7 +684,7 @@
to_chat(user, "You cannot repair this APC until you remove the electronics still inside!")
return
user.visible_message("[user.name] replaces the damaged APC frame with a new one.",\
- "You begin to replace the damaged APC frame...")
+ "You begin to replace the damaged APC frame.")
if(do_after(user, 50, target = src))
to_chat(user, "You replace the damaged APC frame with a new one.")
qdel(W)
@@ -693,8 +702,53 @@
else
return ..()
+/obj/machinery/power/apc/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
+ if(the_rcd.upgrade & RCD_UPGRADE_SIMPLE_CIRCUITS)
+ if(!has_electronics)
+ if(stat & BROKEN)
+ to_chat(user, "[src]'s frame is too damaged to support a circuit.")
+ return FALSE
+ return list("mode" = RCD_UPGRADE_SIMPLE_CIRCUITS, "delay" = 20, "cost" = 1)
+ else if(!cell)
+ if(stat & MAINT)
+ to_chat(user, "There's no connector for a power cell.")
+ return FALSE
+ return list("mode" = RCD_UPGRADE_SIMPLE_CIRCUITS, "delay" = 50, "cost" = 10) //16 for a wall
+ else
+ to_chat(user, "[src] has both electronics and a cell.")
+ return FALSE
+ return FALSE
+
+/obj/machinery/power/apc/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode)
+ switch(passed_mode)
+ if(RCD_UPGRADE_SIMPLE_CIRCUITS)
+ if(!has_electronics)
+ if(stat & BROKEN)
+ to_chat(user, "[src]'s frame is too damaged to support a circuit.")
+ return
+ user.visible_message("[user] fabricates a circuit and places it into [src].", \
+ "You adapt a power control board and click it into place in [src]'s guts.")
+ has_electronics = TRUE
+ locked = FALSE
+ return TRUE
+ else if(!cell)
+ if(stat & MAINT)
+ to_chat(user, "There's no connector for a power cell.")
+ return FALSE
+ var/obj/item/stock_parts/cell/crap/empty/C = new(src)
+ C.forceMove(src)
+ cell = C
+ chargecount = 0
+ user.visible_message("[user] fabricates a weak power cell and places it into [src].", \
+ "Your [the_rcd.name] whirrs with strain as you create a weak power cell and place it into [src]!")
+ update_icon()
+ return TRUE
+ else
+ to_chat(user, "[src] has both electronics and a cell.")
+ return FALSE
+ return FALSE
+
/obj/machinery/power/apc/AltClick(mob/user)
- ..()
if(!user.canUseTopic(src, !issilicon(user)) || !isturf(loc))
return
else
@@ -712,6 +766,7 @@
else
if(allowed(usr) && !wires.is_cut(WIRE_IDSCAN) && !malfhack)
locked = !locked
+ wires.ui_update()
to_chat(user, "You [ locked ? "lock" : "unlock"] the APC interface.")
update_icon()
updateUsrDialog()
@@ -758,6 +813,7 @@
playsound(src, "sparks", 75, 1)
obj_flags |= EMAGGED
locked = FALSE
+ wires.ui_update()
to_chat(user, "You emag the APC interface.")
update_icon()
@@ -772,51 +828,75 @@
if(isethereal(user))
var/mob/living/carbon/human/H = user
var/datum/species/ethereal/E = H.dna.species
- if((H.a_intent == INTENT_HARM) && (E.drain_time < world.time))
- if(cell.charge <= (cell.maxcharge / 2)) // if charge is under 50% you shouldn't drain it
- to_chat(H, "The APC doesn't have much power, you probably shouldn't drain any.")
- return
- var/obj/item/organ/stomach/ethereal/stomach = H.getorganslot(ORGAN_SLOT_STOMACH)
+ if(E.drain_time > world.time)
+ return
+ var/obj/item/organ/stomach/battery/stomach = H.getorganslot(ORGAN_SLOT_STOMACH)
+ if(H.a_intent == INTENT_HARM)
if(!istype(stomach))
to_chat(H, "You can't receive charge!")
return
- if(stomach.crystal_charge >= ETHEREAL_CHARGE_FULL)
- to_chat(H, "Your charge is full!")
+ if(H.nutrition >= NUTRITION_LEVEL_ALMOST_FULL)
+ to_chat(user, "You are already fully charged!")
+ return
+ if(cell.charge <= cell.maxcharge/4) // if charge is under 25% you shouldn't drain it
+ to_chat(H, "The APC doesn't have much power, you probably shouldn't drain anymore.")
return
- E.drain_time = world.time + 75
+
+ E.drain_time = world.time + 80
to_chat(H, "You start channeling some power through the APC into your body.")
- if(do_after(user, 75, target = src))
- if(cell.charge <= (cell.maxcharge / 2) || (stomach.crystal_charge >= ETHEREAL_CHARGE_FULL))
- to_chat(H, "You can't receive more charge from the APC.")
+ while(do_after(user, 75, target = src))
+ if(!istype(stomach))
+ to_chat(H, "You can't receive charge!")
return
- to_chat(H, "You receive some charge from the APC.")
- stomach.adjust_charge(10)
- cell.charge -= 10
- else
- to_chat(H, "You fail to receive charge from the APC!")
+ if(cell.charge <= cell.maxcharge/4)
+ to_chat(H, "The APC doesn't have much power, you probably shouldn't drain anymore.")
+ E.drain_time = 0
+ return
+ E.drain_time = world.time + 80
+ if(cell.charge > cell.maxcharge/4 + 250)
+ stomach.adjust_charge(250)
+ cell.charge -= 250
+ to_chat(H, "You receive some charge from the APC.")
+ else
+ stomach.adjust_charge(cell.charge - cell.maxcharge/4)
+ cell.charge = cell.maxcharge/4
+ to_chat(H, "The APC doesn't have much power, you probably shouldn't drain anymore.")
+ E.drain_time = 0
+ return
+ if(stomach.charge >= stomach.max_charge)
+ to_chat(H, "You are now fully charged.")
+ E.drain_time = 0
+ return
+ to_chat(H, "You fail to receive charge from the APC!")
+ E.drain_time = 0
return
- if((H.a_intent == INTENT_GRAB) && (E.drain_time < world.time))
- if(cell.charge == cell.maxcharge)
- to_chat(H, "The APC is full!")
- return
- var/obj/item/organ/stomach/ethereal/stomach = H.getorganslot(ORGAN_SLOT_STOMACH)
+ else if(H.a_intent == INTENT_GRAB)
if(!istype(stomach))
to_chat(H, "You can't transfer charge!")
return
- if(stomach.crystal_charge < 10)
- to_chat(H, "Your charge is too low!")
- return
- E.drain_time = world.time + 75
+ E.drain_time = world.time + 80
to_chat(H, "You start channeling power through your body into the APC.")
- if(cell.charge == cell.maxcharge || (stomach.crystal_charge < 10))
- to_chat(H, "You can't transfer more charge to the APC.")
- return
- if(do_after(user, 75, target = src))
- to_chat(H, "You transfer some power to the APC.")
- stomach.adjust_charge(-10)
- cell.charge += 10
- else
- to_chat(H, "You fail to transfer power to the APC!")
+ while(do_after(user, 75, target = src))
+ if(!istype(stomach))
+ to_chat(H, "You can't transfer charge!")
+ return
+ E.drain_time = world.time + 80
+ if(stomach.charge > 250)
+ to_chat(H, "You transfer some power to the APC.")
+ stomach.adjust_charge(-250)
+ cell.charge = min(cell.charge + 250, cell.maxcharge)
+ else
+ to_chat(H, "You transfer the last of your charge to the APC.")
+ cell.charge = min(cell.charge + stomach.charge, cell.maxcharge)
+ stomach.set_charge(0)
+ E.drain_time = 0
+ return
+ if(cell.charge >= cell.maxcharge)
+ to_chat(H, "The APC is now fully recharged.")
+ E.drain_time = 0
+ return
+ to_chat(H, "You fail to transfer power to the APC!")
+ E.drain_time = 0
return
if(opened && (!issilicon(user)))
@@ -831,12 +911,17 @@
if((stat & MAINT) && !opened) //no board; no interface
return
+/obj/machinery/power/apc/eminence_act(mob/living/simple_animal/eminence/eminence)
+ . = ..()
+ ui_interact(eminence)
/obj/machinery/power/apc/ui_state(mob/user)
if(isAI(user))
var/mob/living/silicon/ai/AI = user
if(AI.apc_override == src)
return GLOB.conscious_state
+ if(iseminence(user) && integration_cog)
+ return GLOB.conscious_state
return GLOB.default_state
/obj/machinery/power/apc/ui_interact(mob/user, datum/tgui/ui)
@@ -845,6 +930,7 @@
if(!ui)
ui = new(user, src, "Apc")
ui.open()
+ ui.set_autoupdate(TRUE) // Power level, reboot timer
/obj/machinery/power/apc/ui_data(mob/user)
var/list/data = list(
@@ -956,8 +1042,20 @@
. = UI_INTERACTIVE
/obj/machinery/power/apc/ui_act(action, params)
- if(..() || !can_use(usr, 1) || (locked && !usr.has_unlimited_silicon_privilege && !failure_timer))
+ if(..() || !can_use(usr, 1))
return
+
+ switch(action)
+ if("reboot")
+ if(failure_timer)
+ failure_timer = 0
+ update_icon()
+ update()
+ . = TRUE
+
+ if(locked && !usr.has_unlimited_silicon_privilege)
+ return
+
switch(action)
if("lock")
if(usr.has_unlimited_silicon_privilege)
@@ -995,6 +1093,8 @@
environ = setsubsystem(text2num(params["env"]))
update_icon()
update()
+ else
+ return FALSE
. = TRUE
if("overload")
if(usr.has_unlimited_silicon_privilege)
@@ -1003,16 +1103,15 @@
if("hack")
if(get_malf_status(usr))
malfhack(usr)
+ . = TRUE
if("occupy")
if(get_malf_status(usr))
malfoccupy(usr)
+ . = TRUE
if("deoccupy")
if(get_malf_status(usr))
malfvacate()
- if("reboot")
- failure_timer = 0
- update_icon()
- update()
+ . = TRUE
if("emergency_lighting")
emergency_lights = !emergency_lights
for(var/obj/machinery/light/L in area)
@@ -1020,9 +1119,12 @@
L.no_emergency = emergency_lights
INVOKE_ASYNC(L, /obj/machinery/light/.proc/update, FALSE)
CHECK_TICK
- return 1
+ . = TRUE
+
+ if(.)
+ wires.ui_update() // I don't know why this would be here, but I'm too scared to remove it
-/obj/machinery/power/apc/ui_close(mob/user)
+/obj/machinery/power/apc/ui_close(mob/user, datum/tgui/tgui)
if(isAI(user))
var/mob/living/silicon/ai/AI = user
if(AI.apc_override == src)
@@ -1049,8 +1151,8 @@
malf.malfhack = src
malf.malfhacking = addtimer(CALLBACK(malf, /mob/living/silicon/ai/.proc/malfhacked, src), 600, TIMER_STOPPABLE)
- var/obj/screen/alert/hackingapc/A
- A = malf.throw_alert("hackingapc", /obj/screen/alert/hackingapc)
+ var/atom/movable/screen/alert/hackingapc/A
+ A = malf.throw_alert("hackingapc", /atom/movable/screen/alert/hackingapc)
A.target = src
/obj/machinery/power/apc/proc/malfoccupy(mob/living/silicon/ai/malf)
@@ -1076,7 +1178,7 @@
occupier.eyeobj.name = "[occupier.name] (AI Eye)"
if(malf.parent)
qdel(malf)
- occupier.verbs += /mob/living/silicon/ai/proc/corereturn
+ occupier.add_verb(/mob/living/silicon/ai/proc/corereturn)
occupier.cancel_camera()
@@ -1088,7 +1190,7 @@
occupier.parent.shunted = 0
occupier.parent.setOxyLoss(occupier.getOxyLoss())
occupier.parent.cancel_camera()
- occupier.parent.verbs -= /mob/living/silicon/ai/proc/corereturn
+ occupier.parent.remove_verb(/mob/living/silicon/ai/proc/corereturn)
qdel(occupier)
else
to_chat(occupier, "Primary core damaged, unable to return core processes.")
@@ -1135,7 +1237,7 @@
to_chat(occupier, "[user] moved away! Transfer canceled.")
transfer_in_progress = FALSE
return
- to_chat(user, "AI accepted request. Transferring stored intelligence to [card]...")
+ to_chat(user, "AI accepted request. Transferring stored intelligence to [card].")
to_chat(occupier, "Transfer starting. You will be moved to [card] shortly.")
if(!do_after(user, 50, target = src))
to_chat(occupier, "[user] was interrupted! Transfer canceled.")
@@ -1240,28 +1342,31 @@
else if(longtermpower > -10)
longtermpower -= 2
+ var/power_alert_fine = TRUE
+
if(cell.charge <= 0) // zero charge, turn all off
equipment = autoset(equipment, 0)
lighting = autoset(lighting, 0)
environ = autoset(environ, 0)
- area.poweralert(0, src)
+ power_alert_fine = FALSE
else if(cell.percent() < 15 && longtermpower < 0) // <15%, turn off lighting & equipment
equipment = autoset(equipment, 2)
lighting = autoset(lighting, 2)
environ = autoset(environ, 1)
- area.poweralert(0, src)
+ power_alert_fine = FALSE
else if(cell.percent() < 30 && longtermpower < 0) // <30%, turn off equipment
equipment = autoset(equipment, 2)
lighting = autoset(lighting, 1)
environ = autoset(environ, 1)
- area.poweralert(0, src)
+ power_alert_fine = FALSE
else // otherwise all can be on
equipment = autoset(equipment, 1)
lighting = autoset(lighting, 1)
environ = autoset(environ, 1)
- area.poweralert(1, src)
- if(cell.percent() > 75)
- area.poweralert(1, src)
+
+ if(integration_cog)
+ power_alert_fine = TRUE
+ area.poweralert(power_alert_fine, src)
// now trickle-charge the cell
if(chargemode && charging == APC_CHARGING && operating)
@@ -1352,6 +1457,7 @@
environ = 3
update_icon()
update()
+ wires.ui_update()
// damage and destruction acts
/obj/machinery/power/apc/emp_act(severity)
diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm
index d37f9f1ba1aab..5ba4c503873cc 100644
--- a/code/modules/power/cable.dm
+++ b/code/modules/power/cable.dm
@@ -46,7 +46,6 @@ By design, d1 is the smallest direction and d2 is the highest
var/d1 = 0 // cable direction 1 (see above)
var/d2 = 1 // cable direction 2 (see above)
var/datum/powernet/powernet
- var/obj/item/stack/cable_coil/stored
FASTDMM_PROP(\
pipe_type = PIPE_TYPE_CABLE,\
@@ -99,11 +98,6 @@ By design, d1 is the smallest direction and d2 is the highest
hide(T.intact)
GLOB.cable_list += src //add it to the global cable list
- if(d1)
- stored = new/obj/item/stack/cable_coil(null,2,cable_color)
- else
- stored = new/obj/item/stack/cable_coil(null,1,cable_color)
-
var/list/cable_colors = GLOB.cable_colors
cable_color = param_color || cable_color || pick(cable_colors)
if(cable_colors[cable_color])
@@ -114,22 +108,15 @@ By design, d1 is the smallest direction and d2 is the highest
if(powernet)
cut_cable_from_powernet() // update the powernets
GLOB.cable_list -= src //remove it from global cable list
-
- //If we have a stored item at this point, lets just delete it, since that should be
- //handled by deconstruction
- if(stored)
- QDEL_NULL(stored)
return ..() // then go ahead and delete the cable
/obj/structure/cable/deconstruct(disassembled = TRUE)
if(!(flags_1 & NODECONSTRUCT_1))
var/turf/T = get_turf(loc)
if(T)
- stored.forceMove(T)
- stored = null
- else
- qdel(stored)
- qdel(src)
+ var/obj/item/stack/cable_coil/temp_item = new /obj/item/stack/cable_coil(T, d1 ? 2 : 1, cable_color)
+ transfer_fingerprints_to(temp_item)
+ ..()
///////////////////////////////////
// General procedures
@@ -155,7 +142,6 @@ By design, d1 is the smallest direction and d2 is the highest
if (shock(user, 50))
return
user.visible_message("[user] cuts the cable.", "You cut the cable.")
- stored.add_fingerprint(user)
investigate_log("was cut by [key_name(usr)] in [AREACOORD(src)]", INVESTIGATE_WIRES)
deconstruct()
return
@@ -167,17 +153,14 @@ By design, d1 is the smallest direction and d2 is the highest
return
coil.cable_join(src, user)
- else if(istype(W, /obj/item/twohanded/rcl))
- var/obj/item/twohanded/rcl/R = W
+ else if(istype(W, /obj/item/rcl))
+ var/obj/item/rcl/R = W
if(R.loaded)
R.loaded.cable_join(src, user)
R.is_empty(user)
else if(W.tool_behaviour == TOOL_MULTITOOL)
- if(powernet && (powernet.avail > 0)) // is it powered?
- to_chat(user, "Total power: [DisplayPower(powernet.avail)]\nLoad: [DisplayPower(powernet.load)]\nExcess power: [DisplayPower(surplus())]")
- else
- to_chat(user, "The cable is not powered.")
+ to_chat(user, get_power_info())
shock(user, 5, 0.2)
add_fingerprint(user)
@@ -190,6 +173,10 @@ By design, d1 is the smallest direction and d2 is the highest
/obj/structure/cable/attackby(obj/item/W, mob/user, params)
handlecable(W, user, params)
+/obj/structure/cable/examine(mob/user)
+ . = ..()
+ if(isobserver(user))
+ . += get_power_info()
// shock the user with probability prb
/obj/structure/cable/proc/shock(mob/user, prb, siemens_coeff = 1)
@@ -206,10 +193,11 @@ By design, d1 is the smallest direction and d2 is the highest
if(current_size >= STAGE_FIVE)
deconstruct()
-/obj/structure/cable/proc/update_stored(length = 1, colorC = "red")
- stored.amount = length
- stored.item_color = colorC
- stored.update_icon()
+/obj/structure/cable/proc/get_power_info()
+ if(powernet && (powernet.avail > 0)) // is it powered?
+ return "Total power: [DisplayPower(powernet.avail)]\nLoad: [DisplayPower(powernet.load)]\nExcess power: [DisplayPower(surplus())]"
+ else
+ return "The cable is not powered."
////////////////////////////////////////////
// Power related
@@ -752,9 +740,6 @@ GLOBAL_LIST_INIT(cable_coil_recipes, list (new/datum/stack_recipe("cable restrai
C.d1 = nd1
C.d2 = nd2
- //updates the stored cable coil
- C.update_stored(2, item_color)
-
C.add_fingerprint(user)
C.update_icon()
diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm
index d17bfecfe363b..63b5b34b812f5 100644
--- a/code/modules/power/cell.dm
+++ b/code/modules/power/cell.dm
@@ -48,9 +48,9 @@
STOP_PROCESSING(SSobj, src)
. = ..()
-/obj/item/stock_parts/cell/process()
+/obj/item/stock_parts/cell/process(delta_time)
if(self_recharge)
- give(chargerate * 0.25)
+ give(chargerate * 0.125 * delta_time)
else
return PROCESS_KILL
@@ -154,31 +154,42 @@
var/datum/species/ethereal/E = H.dna.species
if(E.drain_time > world.time)
return
- if(charge < 100)
- to_chat(H, "The [src] doesn't have enough power!")
- return
- var/obj/item/organ/stomach/ethereal/stomach = H.getorganslot(ORGAN_SLOT_STOMACH)
+ var/obj/item/organ/stomach/battery/stomach = H.getorganslot(ORGAN_SLOT_STOMACH)
if(!istype(stomach))
to_chat(H, "You can't receive charge!")
return
- if(stomach.crystal_charge >= ETHEREAL_CHARGE_FULL)
- to_chat(H, "Your charge is full!")
+ if(H.nutrition >= NUTRITION_LEVEL_ALMOST_FULL)
+ to_chat(user, "You are already fully charged!")
return
+
to_chat(H, "You clumsily channel power through the [src] and into your body, wasting some in the process.")
- E.drain_time = world.time + 20
- if((charge < 100) || (stomach.crystal_charge >= ETHEREAL_CHARGE_FULL))
- return
- if(do_after(user, 20, target = src))
- to_chat(H, "You receive some charge from the [src].")
- stomach.adjust_charge(3)
- charge -= 100 //you waste way more than you receive, so that ethereals cant just steal one cell and forget about hunger
- else
- to_chat(H, "You fail to receive charge from the [src]!")
+ E.drain_time = world.time + 25
+ while(do_after(user, 20, target = src))
+ if(!istype(stomach))
+ to_chat(H, "You can't receive charge!")
+ return
+ E.drain_time = world.time + 25
+ if(charge > 300)
+ stomach.adjust_charge(75)
+ charge -= 300 //you waste way more than you receive, so that ethereals cant just steal one cell and forget about hunger
+ to_chat(H, "You receive some charge from the [src].")
+ else
+ stomach.adjust_charge(charge/4)
+ charge = 0
+ to_chat(H, "You drain the [src].")
+ E.drain_time = 0
+ return
+
+ if(stomach.charge >= stomach.max_charge)
+ to_chat(H, "You are now fully charged.")
+ E.drain_time = 0
+ return
+ to_chat(H, "You fail to receive charge from the [src]!")
+ E.drain_time = 0
return
-
/obj/item/stock_parts/cell/blob_act(obj/structure/blob/B)
- ex_act(EXPLODE_DEVASTATE)
+ SSexplosions.high_mov_atom += src
/obj/item/stock_parts/cell/proc/get_electrocute_damage()
if(charge >= 1000)
diff --git a/code/modules/power/gravitygenerator.dm b/code/modules/power/gravitygenerator.dm
index 58255da8e105b..e7fc2cb018e9c 100644
--- a/code/modules/power/gravitygenerator.dm
+++ b/code/modules/power/gravitygenerator.dm
@@ -225,6 +225,11 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne
return ..()
+/obj/machinery/gravity_generator/main/ui_requires_update(mob/user, datum/tgui/ui)
+ . = ..()
+ if(charging_state != POWER_IDLE && !(stat & BROKEN))
+ . = TRUE // Autoupdate while charging up/down
+
/obj/machinery/gravity_generator/main/ui_state(mob/user)
return GLOB.default_state
@@ -306,7 +311,7 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne
update_icon()
update_list()
- src.updateUsrDialog()
+ ui_update()
if(alert)
shake_everyone()
@@ -329,7 +334,6 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne
if(charge_count % 4 == 0 && prob(75)) // Let them know it is charging/discharging.
playsound(src.loc, 'sound/effects/empulse.ogg', 100, 1)
- updateDialog()
if(prob(25)) // To help stop "Your clothes feel warm." spam.
pulse_radiation()
@@ -363,9 +367,9 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne
var/sound/alert_sound = sound('sound/effects/alert.ogg')
for(var/i in GLOB.mob_list)
var/mob/M = i
- if(M.z != z && !(ztrait && SSmapping.level_trait(z, ztrait) && SSmapping.level_trait(M.z, ztrait)))
+ if(M.get_virtual_z_level() != get_virtual_z_level() && !(ztrait && SSmapping.level_trait(z, ztrait) && SSmapping.level_trait(M.z, ztrait)))
continue
- M.update_gravity(M.mob_has_gravity())
+ M.update_gravity(M.has_gravity())
if(M.client)
shake_camera(M, 15, 1)
M.playsound_local(T, null, 100, 1, 0.5, S = alert_sound)
@@ -374,8 +378,8 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne
var/turf/T = get_turf(src)
if(!T)
return 0
- if(GLOB.gravity_generators["[T.z]"])
- return length(GLOB.gravity_generators["[T.z]"])
+ if(GLOB.gravity_generators["[T.get_virtual_z_level()]"])
+ return length(GLOB.gravity_generators["[T.get_virtual_z_level()]"])
return 0
/obj/machinery/gravity_generator/main/proc/update_list()
diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm
index c40e008f888c5..1400469d3114d 100644
--- a/code/modules/power/lighting.dm
+++ b/code/modules/power/lighting.dm
@@ -44,7 +44,7 @@
anchored = TRUE
layer = WALL_OBJ_LAYER
max_integrity = 200
- armor = list("melee" = 50, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50)
+ armor = list("melee" = 50, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50, "stamina" = 0)
var/stage = 1
var/fixture_type = "tube"
@@ -213,7 +213,7 @@
var/static_power_used = 0
var/brightness = 10 // luminosity when on, also used in power calculation
var/bulb_power = 1 // basically the alpha of the emitted light source
- var/bulb_colour = "#FFF6ED" // befault colour of the light.
+ var/bulb_colour = "#FFF6ED" // default colour of the light.
var/status = LIGHT_OK // LIGHT_OK, _EMPTY, _BURNED or _BROKEN
var/flickering = FALSE
var/light_type = /obj/item/light/tube // the type of light item
@@ -241,6 +241,7 @@
var/bulb_vacuum_colour = "#4F82FF" // colour of the light when air alarm is set to severe
var/bulb_vacuum_brightness = 8
+ var/static/list/lighting_overlays // dictionary for lighting overlays
/obj/machinery/light/broken
status = LIGHT_BROKEN
@@ -298,7 +299,7 @@
brightness = A.lighting_brightness_bulb
else
bulb_colour = A.lighting_colour_tube
- brightness = A.lighting_brightness_bulb
+ brightness = A.lighting_brightness_tube
if(nightshift_light_color == initial(nightshift_light_color))
nightshift_light_color = A.lighting_colour_night
@@ -313,11 +314,11 @@
spawn(2)
switch(fitting)
if("tube")
- brightness = 11
+ brightness = A.lighting_brightness_tube
if(prob(2))
break_light_tube(1)
if("bulb")
- brightness = 6
+ brightness = A.lighting_brightness_bulb
if(prob(5))
break_light_tube(1)
spawn(1)
@@ -331,8 +332,7 @@
QDEL_NULL(cell)
return ..()
-/obj/machinery/light/update_icon()
- cut_overlays()
+/obj/machinery/light/update_icon_state()
switch(status) // set icon_states
if(LIGHT_OK)
var/area/A = get_area(src)
@@ -342,17 +342,25 @@
icon_state = "[base_state]_vacuum"
else
icon_state = "[base_state]"
- if(on)
- var/mutable_appearance/glowybit = mutable_appearance(overlayicon, base_state, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE)
- glowybit.alpha = CLAMP(light_power*250, 30, 200)
- add_overlay(glowybit)
if(LIGHT_EMPTY)
icon_state = "[base_state]-empty"
if(LIGHT_BURNED)
icon_state = "[base_state]-burned"
if(LIGHT_BROKEN)
icon_state = "[base_state]-broken"
- return
+
+/obj/machinery/light/update_overlays()
+ . = ..()
+ if(on || emergency_mode)
+ if(!lighting_overlays)
+ lighting_overlays = list()
+ var/mutable_appearance/LO = lighting_overlays["[base_state]-[light_power]-[light_color]"]
+ if(!LO)
+ LO = mutable_appearance(overlayicon, base_state, layer, EMISSIVE_PLANE)
+ LO.color = light_color
+ LO.alpha = clamp(light_power*255, 30, 200)
+ lighting_overlays["[base_state]-[light_power]-[light_color]"] = LO
+ . += LO
// update the icon_state and luminosity of the light depending on its state
/obj/machinery/light/proc/update(trigger = TRUE)
@@ -375,20 +383,21 @@
else if (nightshift_enabled)
BR = nightshift_brightness
PO = nightshift_light_power
- CO = nightshift_light_color
+ if(!color)
+ CO = nightshift_light_color
var/matching = light && BR == light.light_range && PO == light.light_power && CO == light.light_color
if(!matching)
switchcount++
if(rigged)
if(status == LIGHT_OK && trigger)
explode()
- else if( prob( min(60, (switchcount^2)*0.01) ) )
+ else if( prob( min(60, (switchcount**2)*0.01) ) )
if(trigger)
burn_out()
else
use_power = ACTIVE_POWER_USE
set_light(BR, PO, CO)
- else if(has_emergency_power(LIGHT_EMERGENCY_POWER_USE) && !turned_off())
+ else if(use_emergency_power(LIGHT_EMERGENCY_POWER_USE) && !turned_off())
use_power = IDLE_POWER_USE
emergency_mode = TRUE
START_PROCESSING(SSmachines, src)
@@ -542,7 +551,7 @@
drop_light_tube()
new /obj/item/stack/cable_coil(loc, 1, "red")
transfer_fingerprints_to(newlight)
- if(cell)
+ if(!QDELETED(cell))
newlight.cell = cell
cell.forceMove(newlight)
cell = null
@@ -654,20 +663,34 @@
var/mob/living/carbon/human/H = user
if(istype(H))
- var/datum/species/ethereal/eth_species = H.dna?.species
- if(istype(eth_species))
+ if(isethereal(H))
var/datum/species/ethereal/E = H.dna.species
if(E.drain_time > world.time)
return
+ var/obj/item/organ/stomach/battery/stomach = H.getorganslot(ORGAN_SLOT_STOMACH)
+ if(!istype(stomach))
+ to_chat(H, "You can't receive charge!")
+ return
+ if(H.nutrition >= NUTRITION_LEVEL_ALMOST_FULL)
+ to_chat(user, "You are already fully charged!")
+ return
+
to_chat(H, "You start channeling some power through the [fitting] into your body.")
- E.drain_time = world.time + 30
- if(do_after(user, 30, target = src))
- var/obj/item/organ/stomach/ethereal/stomach = H.getorganslot(ORGAN_SLOT_STOMACH)
- if(istype(stomach))
- to_chat(H, "You receive some charge from the [fitting].")
- stomach.adjust_charge(2)
- else
- to_chat(H, "You fail to receive charge from the [fitting]!")
+ E.drain_time = world.time + 35
+ while(do_after(user, 30, target = src))
+ E.drain_time = world.time + 35
+ if(!istype(stomach))
+ to_chat(H, "You can't receive charge!")
+ return
+ to_chat(H, "You receive some charge from the [fitting].")
+ stomach.adjust_charge(50)
+ use_power(50)
+ if(stomach.charge >= stomach.max_charge)
+ to_chat(H, "You are now fully charged.")
+ E.drain_time = 0
+ return
+ to_chat(H, "You fail to receive charge from the [fitting]!")
+ E.drain_time = 0
return
if(H.gloves)
@@ -749,7 +772,12 @@
/obj/machinery/light/tesla_act(power, tesla_flags)
if(tesla_flags & TESLA_MACHINE_EXPLOSIVE)
- explosion(src,0,0,0,flame_range = 5, adminlog = 0)
+ //Fire can cause a lot of lag, just do a mini explosion.
+ explosion(src,0,0,1, adminlog = 0)
+ for(var/mob/living/L in range(3, src))
+ L.fire_stacks = max(L.fire_stacks, 3)
+ L.IgniteMob()
+ L.electrocute_act(0, "Tesla Light Zap", tesla_shock = TRUE, stun = TRUE)
qdel(src)
else
return ..()
@@ -776,6 +804,10 @@
sleep(1)
qdel(src)
+/obj/machinery/light/eminence_act(mob/living/simple_animal/eminence/eminence)
+ . = ..()
+ break_light_tube()
+
// the light item
// can be tube or bulb subtypes
// will fit into empty /obj/machinery/light of the corresponding type
diff --git a/code/modules/power/monitor.dm b/code/modules/power/monitor.dm
index 55b5634aeb163..15b7023752cb7 100644
--- a/code/modules/power/monitor.dm
+++ b/code/modules/power/monitor.dm
@@ -93,6 +93,7 @@
if(!ui)
ui = new(user, src, "PowerMonitor")
ui.open()
+ ui.set_autoupdate(TRUE) // Power in powernet
/obj/machinery/computer/monitor/ui_data()
var/datum/powernet/connected_powernet = get_powernet()
@@ -112,6 +113,8 @@
var/cell_charge
if(!A.cell)
cell_charge = 0
+ else if(A.integration_cog)
+ cell_charge = 100
else
cell_charge = A.cell.percent()
data["areas"] += list(list(
diff --git a/code/modules/power/multiz.dm b/code/modules/power/multiz.dm
index fde88209e2e20..7de433ef9663d 100644
--- a/code/modules/power/multiz.dm
+++ b/code/modules/power/multiz.dm
@@ -107,26 +107,39 @@
/obj/machinery/power/deck_relay/Initialize()
. = ..()
- addtimer(CALLBACK(src, .proc/find_relays), 30)
- addtimer(CALLBACK(src, .proc/refresh), 50) //Wait a bit so we can find the one below, then get powering
+ return INITIALIZE_HINT_LATELOAD
+
+/obj/machinery/power/deck_relay/LateInitialize()
+ . = ..()
+ find_relays()
+ refresh()
///Handles re-acquiring + merging powernets found by find_relays()
-/obj/machinery/power/deck_relay/proc/refresh()
+/obj/machinery/power/deck_relay/proc/refresh(failures = 0)
if(above)
- above.merge(src)
+ above.merge(src, failures)
if(below)
- below.merge(src)
+ below.merge(src, failures)
///Merges the two powernets connected to the deck relays
-/obj/machinery/power/deck_relay/proc/merge(var/obj/machinery/power/deck_relay/DR)
+/obj/machinery/power/deck_relay/proc/merge(var/obj/machinery/power/deck_relay/DR, failures = 0)
if(!DR)
return
var/turf/merge_from = get_turf(DR)
var/turf/merge_to = get_turf(src)
+ if(failures > 5)
+ message_admins("Failed to merge powernet at [ADMIN_COORDJMP(merge_to)] after 5 attempts.")
+ return
var/obj/structure/cable/C = merge_from.get_cable_node()
var/obj/structure/cable/XR = merge_to.get_cable_node()
if(C && XR)
- merge_powernets(XR.powernet,C.powernet)//Bridge the powernets.
+ if(!XR.powernet || !C.powernet)
+ //Try again in 10 seconds.
+ addtimer(CALLBACK(src, .proc/refresh, failures + 1), 10 SECONDS)
+ return
+ merge_powernets(XR.powernet,C.powernet)
+ else
+ stack_trace("Multi-z power adapter failed to merge powernets at [COORD(merge_to)] due to not being able to find a power node.")
///Locates relays that are above and below this object
/obj/machinery/power/deck_relay/proc/find_relays()
diff --git a/code/modules/power/port_gen.dm b/code/modules/power/port_gen.dm
index 6166ab6a279bc..52a1dc9f10c05 100644
--- a/code/modules/power/port_gen.dm
+++ b/code/modules/power/port_gen.dm
@@ -121,7 +121,7 @@
if(anchored)
. += "It is anchored to the ground."
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Fuel efficiency increased by [(consumption*100)-100]%."
+ . += "The status display reads: Fuel efficiency increased by [(consumption*100)-100]%."
/obj/machinery/power/port_gen/pacman/HasFuel()
if(sheets >= 1 / (time_per_sheet / power_output) - sheet_left)
@@ -228,6 +228,7 @@
if(!ui)
ui = new(user, src, "PortableGenerator")
ui.open()
+ ui.set_autoupdate(TRUE) // Fuel left, power generated, power in powernet, current heat(?)
/obj/machinery/power/port_gen/pacman/ui_data()
var/data = list()
diff --git a/code/modules/power/power.dm b/code/modules/power/power.dm
index 8cf463c296ec8..2974a54625125 100644
--- a/code/modules/power/power.dm
+++ b/code/modules/power/power.dm
@@ -104,9 +104,12 @@
/obj/machinery/proc/removeStaticPower(value, powerchannel)
addStaticPower(-value, powerchannel)
-/obj/machinery/proc/power_change() // called whenever the power settings of the containing area change
- // by default, check equipment channel & set flag
- // can override if needed
+// called whenever the power settings of the containing area change
+// by default, check equipment channel & set flag
+// can override if needed
+/obj/machinery/proc/power_change()
+ SIGNAL_HANDLER
+
if(powered(power_channel))
stat &= ~NOPOWER
else
@@ -201,6 +204,11 @@
. += C
return .
+/obj/machinery/power/lateShuttleMove(turf/oldT, list/movement_force, move_dir)
+ . = ..()
+ disconnect_from_network()
+ connect_to_network()
+
///////////////////////////////////////////
// GLOBAL PROCS for powernets handling
//////////////////////////////////////////
@@ -256,14 +264,13 @@
worklist |= C.get_connections() //get adjacents power objects, with or without a powernet
else if(P.anchored && istype(P, /obj/machinery/power))
- var/obj/machinery/power/M = P
- found_machines |= M //we wait until the powernet is fully propagates to connect the machines
+ found_machines |= P //we wait until the powernet is fully propagates to connect the machines
else
continue
//now that the powernet is set, connect found machines to it
- for(var/obj/machinery/power/PM in found_machines)
+ for(var/obj/machinery/power/PM as() in found_machines)
if(!PM.connect_to_network()) //couldn't find a node on its turf...
PM.disconnect_from_network() //... so disconnect if already on a powernet
@@ -294,7 +301,7 @@
//Determines how strong could be shock, deals damage to mob, uses power.
//M is a mob who touched wire/whatever
-//power_source is a source of electricity, can be powercell, area, apc, cable, powernet or null
+//power_source is a source of electricity, can be power cell, area, apc, cable, powernet or null
//source is an object caused electrocuting (airlock, grille, etc)
//siemens_coeff - layman's terms, conductivity
//dist_check - set to only shock mobs within 1 of source (vendors, airlocks, etc.)
diff --git a/code/modules/power/rtg.dm b/code/modules/power/rtg.dm
index 15b950c996e29..fef9b902c937f 100644
--- a/code/modules/power/rtg.dm
+++ b/code/modules/power/rtg.dm
@@ -39,7 +39,7 @@
/obj/machinery/power/rtg/examine(mob/user)
. = ..()
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Power generation now at [power_gen*0.001]kW."
+ . += "The status display reads: Power generation now at [power_gen*0.001]kW."
/obj/machinery/power/rtg/attackby(obj/item/I, mob/user, params)
if(default_deconstruction_screwdriver(user, "[initial(icon_state)]-open", initial(icon_state), I))
diff --git a/code/modules/power/singularity/boh_tear.dm b/code/modules/power/singularity/boh_tear.dm
index 684776d8485c5..9d337c25887d6 100644
--- a/code/modules/power/singularity/boh_tear.dm
+++ b/code/modules/power/singularity/boh_tear.dm
@@ -6,6 +6,7 @@
desc = "Your own comprehension of reality starts bending as you stare this."
icon = 'icons/effects/96x96.dmi'
icon_state = "boh_tear"
+ is_real = FALSE
pixel_x = -32
pixel_y = -32
dissipate = 0
@@ -19,7 +20,7 @@
/obj/singularity/boh_tear/Initialize()
. = ..()
- old_loc = loc
+ old_loc = get_turf(src)
addtimer(CALLBACK(src, /atom/movable.proc/moveToNullspace), 5 SECONDS) // vanishes after 5 seconds
QDEL_IN(src, 10 MINUTES)
@@ -52,7 +53,7 @@
if(isliving(AM))
var/mob/living/M = AM
var/turf/T = get_turf(src)
- investigate_log("([key_name(A)]) has been consumed by the BoH tear at [AREACOORD(T)].", INVESTIGATE_SINGULO)
+ investigate_log("([key_name(A)]) has been consumed by the BoH tear at [AREACOORD(T)].", INVESTIGATE_ENGINES)
M.ghostize(FALSE)
else if(!isobj(AM))
return
@@ -61,7 +62,7 @@
/obj/singularity/boh_tear/admin_investigate_setup()
var/turf/T = get_turf(src)
message_admins("A BoH tear has been created at [ADMIN_VERBOSEJMP(T)]. [ADMIN_RETRIEVE_BOH_ITEMS(src)]")
- investigate_log("was created at [AREACOORD(T)].", INVESTIGATE_SINGULO)
+ investigate_log("was created at [AREACOORD(T)].", INVESTIGATE_ENGINES)
/obj/singularity/boh_tear/attack_tk(mob/living/user)
if(!istype(user))
diff --git a/code/modules/power/singularity/collector.dm b/code/modules/power/singularity/collector.dm
index e208f436c42a6..427d70b8d678d 100644
--- a/code/modules/power/singularity/collector.dm
+++ b/code/modules/power/singularity/collector.dm
@@ -5,6 +5,7 @@
#define RAD_COLLECTOR_MINING_CONVERSION_RATE 0.00001 //This is gonna need a lot of tweaking to get right. This is the number used to calculate the conversion of watts to research points per process()
#define RAD_COLLECTOR_OUTPUT min(stored_energy, (stored_energy*RAD_COLLECTOR_STORED_OUT)+1000) //Produces at least 1000 watts if it has more than that stored
+
/obj/machinery/power/rad_collector
name = "Radiation Collector Array"
desc = "A device which uses Hawking Radiation and plasma to produce power."
@@ -22,44 +23,54 @@
var/stored_energy = 0
var/active = 0
var/locked = FALSE
- var/drainratio = 1
- var/powerproduction_drain = 0.001
-
+ var/drainratio = 0.5
+ var/powerproduction_drain = 0.01
var/bitcoinproduction_drain = 0.15
var/bitcoinmining = FALSE
+ var/obj/item/radio/radio
+/obj/machinery/power/rad_collector/Initialize()
+ . = ..()
+
+ radio = new(src)
+ radio.keyslot = new /obj/item/encryptionkey/headset_eng
+ radio.subspace_transmission = TRUE
+ radio.canhear_range = 0
+ radio.recalculateChannels()
/obj/machinery/power/rad_collector/anchored
anchored = TRUE
/obj/machinery/power/rad_collector/Destroy()
+ QDEL_NULL(radio)
return ..()
-/obj/machinery/power/rad_collector/process()
+/obj/machinery/power/rad_collector/process(delta_time)
if(!loaded_tank)
return
if(!bitcoinmining)
- if(loaded_tank.air_contents.get_moles(/datum/gas/plasma) < 0.0001)
- investigate_log("out of fuel.", INVESTIGATE_SINGULO)
+ if(loaded_tank.air_contents.get_moles(GAS_PLASMA) < 0.0001)
+ investigate_log("out of fuel.", INVESTIGATE_ENGINES)
playsound(src, 'sound/machines/ding.ogg', 50, 1)
+ var/msg = "Plasma depleted, recommend replacing tank."
+ radio.talk_into(src, msg, RADIO_CHANNEL_ENGINEERING)
eject()
else
- var/gasdrained = min(powerproduction_drain*drainratio,loaded_tank.air_contents.get_moles(/datum/gas/plasma))
- loaded_tank.air_contents.adjust_moles(/datum/gas/plasma, -gasdrained)
- loaded_tank.air_contents.adjust_moles(/datum/gas/tritium, gasdrained)
-
+ var/gasdrained = min(powerproduction_drain*drainratio*delta_time,loaded_tank.air_contents.get_moles(GAS_PLASMA))
+ loaded_tank.air_contents.adjust_moles(GAS_PLASMA, -gasdrained)
+ loaded_tank.air_contents.adjust_moles(GAS_TRITIUM, gasdrained)
var/power_produced = RAD_COLLECTOR_OUTPUT
add_avail(power_produced)
stored_energy-=power_produced
else if(is_station_level(z) && SSresearch.science_tech)
- if(!loaded_tank.air_contents.get_moles(/datum/gas/tritium) || !loaded_tank.air_contents.get_moles(/datum/gas/oxygen))
+ if(!loaded_tank.air_contents.get_moles(GAS_TRITIUM) || !loaded_tank.air_contents.get_moles(GAS_O2))
playsound(src, 'sound/machines/ding.ogg', 50, 1)
eject()
else
- var/gasdrained = bitcoinproduction_drain*drainratio
- loaded_tank.air_contents.adjust_moles(/datum/gas/tritium, -gasdrained)
- loaded_tank.air_contents.adjust_moles(/datum/gas/oxygen, -gasdrained)
- loaded_tank.air_contents.adjust_moles(/datum/gas/carbon_dioxide, gasdrained*2)
+ var/gasdrained = bitcoinproduction_drain*drainratio*delta_time
+ loaded_tank.air_contents.adjust_moles(GAS_TRITIUM, -gasdrained)
+ loaded_tank.air_contents.adjust_moles(GAS_O2, -gasdrained)
+ loaded_tank.air_contents.adjust_moles(GAS_CO2, gasdrained*2)
var/bitcoins_mined = RAD_COLLECTOR_OUTPUT
var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_ENG)
if(D)
@@ -73,8 +84,8 @@
toggle_power()
user.visible_message("[user.name] turns the [src.name] [active? "on":"off"].", \
"You turn the [src.name] [active? "on":"off"].")
- var/fuel = loaded_tank.air_contents.get_moles(/datum/gas/plasma)
- investigate_log("turned [active?"on":"off"] by [key_name(user)]. [loaded_tank?"Fuel: [round(fuel/0.29)]%":"It is empty"].", INVESTIGATE_SINGULO)
+ var/fuel = loaded_tank?.air_contents.get_moles(GAS_PLASMA)
+ investigate_log("turned [active?"on":"off"] by [key_name(user)]. [loaded_tank?"Fuel: [round(fuel/0.29)]%":"It is empty"].", INVESTIGATE_ENGINES)
return
else
to_chat(user, "The controls are locked!")
diff --git a/code/modules/power/singularity/emitter.dm b/code/modules/power/singularity/emitter.dm
index 87bf530bd5db8..4bbe7770424c4 100644
--- a/code/modules/power/singularity/emitter.dm
+++ b/code/modules/power/singularity/emitter.dm
@@ -16,7 +16,7 @@
use_power = NO_POWER_USE
idle_power_usage = 10
- active_power_usage = 300
+ active_power_usage = 600
var/icon_state_on = "emitter_+a"
var/icon_state_underpowered = "emitter_+u"
@@ -92,7 +92,7 @@
/obj/machinery/power/emitter/examine(mob/user)
. = ..()
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Emitting one beam each [fire_delay*0.1] seconds. Power consumption at [active_power_usage]W."
+ . += "The status display reads: Emitting one beam each [fire_delay*0.1] seconds. Power consumption at [active_power_usage]W."
/obj/machinery/power/emitter/ComponentInitialize()
. = ..()
@@ -109,7 +109,7 @@
var/turf/T = get_turf(src)
message_admins("Emitter deleted at [ADMIN_VERBOSEJMP(T)]")
log_game("Emitter deleted at [AREACOORD(T)]")
- investigate_log("deleted at [AREACOORD(T)]", INVESTIGATE_SINGULO)
+ investigate_log("deleted at [AREACOORD(T)]", INVESTIGATE_ENGINES)
QDEL_NULL(sparks)
return ..()
@@ -126,7 +126,7 @@
/obj/machinery/power/emitter/interact(mob/user)
add_fingerprint(user)
if(state == EMITTER_WELDED)
- if(!powernet)
+ if(!powernet && active_power_usage)
to_chat(user, "\The [src] isn't connected to a wire!")
return TRUE
if(!locked && allow_switch_interact)
@@ -141,7 +141,7 @@
message_admins("Emitter turned [active ? "ON" : "OFF"] by [ADMIN_LOOKUPFLW(user)] in [ADMIN_VERBOSEJMP(src)]")
log_game("Emitter turned [active ? "ON" : "OFF"] by [key_name(user)] in [AREACOORD(src)]")
- investigate_log("turned [active ? "ON" : "OFF"] by [key_name(user)] at [AREACOORD(src)]", INVESTIGATE_SINGULO)
+ investigate_log("turned [active ? "ON" : "OFF"] by [key_name(user)] at [AREACOORD(src)]", INVESTIGATE_ENGINES)
update_icon()
@@ -161,7 +161,7 @@
if(!anchored)
step(src, get_dir(M, src))
-/obj/machinery/power/emitter/process()
+/obj/machinery/power/emitter/process(delta_time)
if(stat & (BROKEN))
return
if(state != EMITTER_WELDED || (!powernet && active_power_usage))
@@ -174,16 +174,16 @@
if(!powered)
powered = TRUE
update_icon()
- investigate_log("regained power and turned ON at [AREACOORD(src)]", INVESTIGATE_SINGULO)
+ investigate_log("regained power and turned ON at [AREACOORD(src)]", INVESTIGATE_ENGINES)
else
if(powered)
powered = FALSE
update_icon()
- investigate_log("lost power and turned OFF at [AREACOORD(src)]", INVESTIGATE_SINGULO)
+ investigate_log("lost power and turned OFF at [AREACOORD(src)]", INVESTIGATE_ENGINES)
log_game("Emitter lost power in [AREACOORD(src)]")
return
if(charge <= 80)
- charge += 5
+ charge += 2.5 * delta_time
if(!check_delay() || manual == TRUE)
return FALSE
fire_beam()
@@ -353,8 +353,7 @@
return
locked = FALSE
obj_flags |= EMAGGED
- if(user)
- user.visible_message("[user.name] emags [src].","You short out the lock.")
+ user?.visible_message("[user.name] emags [src].","You short out the lock.")
/obj/machinery/power/emitter/prototype
@@ -384,7 +383,7 @@
auto.Remove(buckled_mob)
. = ..()
-/obj/machinery/power/emitter/prototype/user_buckle_mob(mob/living/M, mob/living/carbon/user)
+/obj/machinery/power/emitter/prototype/user_buckle_mob(mob/living/M, mob/user, check_loc = TRUE)
if(user.incapacitated() || !istype(user))
return
for(var/atom/movable/A in get_turf(src))
diff --git a/code/modules/power/singularity/field_generator.dm b/code/modules/power/singularity/field_generator.dm
index 6296554fe9ee7..5a11d770ea83c 100644
--- a/code/modules/power/singularity/field_generator.dm
+++ b/code/modules/power/singularity/field_generator.dm
@@ -33,7 +33,7 @@ field_generator power level display
use_power = NO_POWER_USE
max_integrity = 500
//100% immune to lasers and energy projectiles since it absorbs their energy.
- armor = list("melee" = 25, "bullet" = 10, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 70)
+ armor = list("melee" = 25, "bullet" = 10, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 70, "stamina" = 0)
var/power_level = 0
var/active = FG_OFFLINE
var/power = 20 // Current amount of power
@@ -77,7 +77,7 @@ field_generator power level display
"You turn on [src].", \
"You hear heavy droning.")
turn_on()
- investigate_log("activated by [key_name(user)].", INVESTIGATE_SINGULO)
+ investigate_log("activated by [key_name(user)].", INVESTIGATE_ENGINES)
add_fingerprint(user)
else
@@ -210,7 +210,7 @@ field_generator power level display
else
visible_message("The [name] shuts down!", "You hear something shutting down.")
turn_off()
- investigate_log("ran out of power and deactivated", INVESTIGATE_SINGULO)
+ investigate_log("ran out of power and deactivated", INVESTIGATE_ENGINES)
power = 0
check_power_level()
return 0
@@ -330,12 +330,12 @@ field_generator power level display
spawn(1)
var/temp = 1 //stops spam
for(var/obj/singularity/O in GLOB.singularities)
- if(O.last_warning && temp)
+ if(O.last_warning && temp && O.is_real)
if((world.time - O.last_warning) > 50) //to stop message-spam
temp = 0
var/turf/T = get_turf(src)
message_admins("A singulo exists and a containment field has failed at [ADMIN_VERBOSEJMP(T)].")
- investigate_log("has failed whilst a singulo exists at [AREACOORD(T)].", INVESTIGATE_SINGULO)
+ investigate_log("has failed whilst a singulo exists at [AREACOORD(T)].", INVESTIGATE_ENGINES)
notify_ghosts("IT'S LOOSE", source = src, action = NOTIFY_ORBIT, flashwindow = FALSE, ghost_sound = 'sound/machines/warning-buzzer.ogg', header = "IT'S LOOSE", notify_volume = 75)
O.last_warning = world.time
diff --git a/code/modules/power/singularity/generator.dm b/code/modules/power/singularity/generator.dm
index f5e3bbc1417ba..f608b6f256bcd 100644
--- a/code/modules/power/singularity/generator.dm
+++ b/code/modules/power/singularity/generator.dm
@@ -23,7 +23,7 @@
else
return ..()
-/obj/machinery/the_singularitygen/process()
+/obj/machinery/the_singularitygen/process(delta_time)
if(energy > 0)
if(energy >= 200)
var/turf/T = get_turf(src)
@@ -32,4 +32,4 @@
transfer_fingerprints_to(S)
qdel(src)
else
- energy -= 1
+ energy -= delta_time * 0.5
diff --git a/code/modules/power/singularity/investigate.dm b/code/modules/power/singularity/investigate.dm
index 3caf934b50c50..d8760830907f2 100644
--- a/code/modules/power/singularity/investigate.dm
+++ b/code/modules/power/singularity/investigate.dm
@@ -1,4 +1,4 @@
/area/engine/engineering/poweralert(state, source)
if (state != poweralm)
- investigate_log("has a power alarm!", INVESTIGATE_SINGULO)
+ investigate_log("has a power alarm!", INVESTIGATE_ENGINES)
..()
\ No newline at end of file
diff --git a/code/modules/power/singularity/narsie.dm b/code/modules/power/singularity/narsie.dm
index 4d202fd8d17cb..e042aa8e184a1 100644
--- a/code/modules/power/singularity/narsie.dm
+++ b/code/modules/power/singularity/narsie.dm
@@ -2,6 +2,7 @@
name = "Nar'Sie's Avatar"
desc = "Your mind begins to bubble and ooze as it tries to comprehend what it sees."
icon = 'icons/obj/magic_terror.dmi'
+ is_real = FALSE
pixel_x = -89
pixel_y = -85
density = FALSE
@@ -16,6 +17,7 @@
light_color = rgb(255, 0, 0)
gender = FEMALE
var/clashing = FALSE //If Nar'Sie is fighting Ratvar
+ var/next_attack_tick
/obj/singularity/narsie/large
name = "Nar'Sie"
@@ -68,12 +70,13 @@
souls_needed[player] = TRUE
soul_goal = round(1 + LAZYLEN(souls_needed) * 0.75)
INVOKE_ASYNC(src, .proc/begin_the_end)
+ check_gods_battle()
/obj/singularity/narsie/large/cult/proc/begin_the_end()
sleep(50)
priority_announce("An acausal dimensional event has been detected in your sector. Event has been flagged EXTINCTION-CLASS. Directing all available assets toward simulating solutions. SOLUTION ETA: 60 SECONDS.","Central Command Higher Dimensional Affairs", 'sound/misc/airraid.ogg')
sleep(500)
- priority_announce("Simulations on acausal dimensional event complete. Deploying solution package now. Deployment ETA: ONE MINUTE. ","Central Command Higher Dimensional Affairs")
+ priority_announce("Simulations on acausal dimensional event complete. Deploying solution package now. Deployment ETA: ONE MINUTE. ", "Central Command Higher Dimensional Affairs", SSstation.announcer.get_rand_alert_sound())
sleep(50)
set_security_level("delta")
SSshuttle.registerHostileEnvironment(src)
@@ -101,15 +104,36 @@
/obj/singularity/narsie/large/attack_ghost(mob/dead/observer/user as mob)
makeNewConstruct(/mob/living/simple_animal/hostile/construct/harvester, user, cultoverride = TRUE, loc_override = src.loc)
-/obj/singularity/narsie/process()
+/obj/singularity/narsie/process(delta_time)
+ eat()
if(clashing)
+ //Oh god what is it doing...
+ target = clashing
+ if(get_dist(src, clashing) < 5)
+ if(next_attack_tick < world.time)
+ next_attack_tick = world.time + rand(50, 100)
+ to_chat(world, "[pick("You hear the scratching of cogs.","You hear the clanging of pipes.","You feel your bones start to rust...")]")
+ SEND_SOUND(world, 'sound/magic/clockwork/narsie_attack.ogg')
+ SpinAnimation(4, 0)
+ for(var/mob/living/M in GLOB.player_list)
+ shake_camera(M, 25, 6)
+ M.Knockdown(10)
+ if(DT_PROB(max(SSticker.mode?.cult.len/2, 15), delta_time))
+ SEND_SOUND(world, 'sound/magic/clockwork/anima_fragment_death.ogg')
+ SEND_SOUND(world, 'sound/effects/explosionfar.ogg')
+ to_chat(world, "You really thought you could best me twice?")
+ QDEL_NULL(clashing)
+ for(var/datum/mind/M as() in GLOB.servants_of_ratvar)
+ to_chat(M, "You feel a stabbing pain in your chest... This can't be happening!")
+ M.current?.dust()
+ return
+ move()
return
- eat()
- if(!target || prob(5))
+ if(!target || DT_PROB(5, delta_time))
pickcultist()
else
move()
- if(prob(25))
+ if(DT_PROB(25, delta_time))
mezzer()
@@ -125,11 +149,11 @@
/obj/singularity/narsie/mezzer()
- for(var/mob/living/carbon/M in viewers(consume_range, src))
- if(M.stat == CONSCIOUS)
- if(!iscultist(M))
- to_chat(M, "You feel conscious thought crumble away in an instant as you gaze upon [src.name]...")
- M.apply_effect(60, EFFECT_STUN)
+ for(var/mob/living/carbon/M in hearers(consume_range, src))
+ if(M.stat || iscultist(M))
+ continue
+ to_chat(M, "You feel conscious thought crumble away in an instant as you gaze upon [src.name].")
+ M.apply_effect(60, EFFECT_STUN)
/obj/singularity/narsie/consume(atom/A)
@@ -147,7 +171,7 @@
for(var/mob/living/carbon/food in GLOB.alive_mob_list) //we don't care about constructs or cult-Ians or whatever. cult-monkeys are fair game i guess
var/turf/pos = get_turf(food)
- if(!pos || (pos.z != z))
+ if(!pos || (pos.get_virtual_z_level() != get_virtual_z_level()))
continue
if(iscultist(food))
@@ -168,7 +192,7 @@
if(!ghost.client)
continue
var/turf/pos = get_turf(ghost)
- if(!pos || (pos.z != z))
+ if(!pos || (pos.get_virtual_z_level() != get_virtual_z_level()))
continue
cultists += ghost
if(cultists.len)
@@ -193,9 +217,10 @@
/obj/singularity/narsie/wizard/eat()
// if(defer_powernet_rebuild != 2)
// defer_powernet_rebuild = 1
- for(var/atom/X in urange(consume_range,src,1))
- if(isturf(X) || ismovableatom(X))
- consume(X)
+ for(var/turf/T as() in RANGE_TURFS(consume_range, src))
+ consume(T)
+ for(var/atom/movable/AM in urange(consume_range,src,1))
+ consume(AM)
// if(defer_powernet_rebuild != 2)
// defer_powernet_rebuild = 0
return
@@ -211,4 +236,3 @@
icon = initial(icon)
-
diff --git a/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm b/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm
index b880958aaed31..05f39cf954ad6 100644
--- a/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm
+++ b/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm
@@ -27,7 +27,7 @@
anchored = FALSE
density = TRUE
max_integrity = 500
- armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 80)
+ armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 80, "stamina" = 0)
var/obj/machinery/particle_accelerator/control_box/master = null
var/construction_state = PA_CONSTRUCTION_UNSECURED
@@ -122,7 +122,7 @@
. = ..()
if(master?.active)
master.toggle_power()
- investigate_log("was moved whilst active; it powered down.", INVESTIGATE_SINGULO)
+ investigate_log("was moved whilst active; it powered down.", INVESTIGATE_ENGINES)
/obj/structure/particle_accelerator/update_icon()
diff --git a/code/modules/power/singularity/particle_accelerator/particle_control.dm b/code/modules/power/singularity/particle_accelerator/particle_control.dm
index 58632b8d0d81a..cf77bd75a1780 100644
--- a/code/modules/power/singularity/particle_accelerator/particle_control.dm
+++ b/code/modules/power/singularity/particle_accelerator/particle_control.dm
@@ -53,11 +53,13 @@
part.powered = FALSE
part.update_icon()
connected_parts.Cut()
+ ui_update()
return
if(!part_scan())
use_power = IDLE_POWER_USE
active = FALSE
connected_parts.Cut()
+ ui_update()
/obj/machinery/particle_accelerator/control_box/update_icon()
if(active)
@@ -89,8 +91,7 @@
strength_change()
message_admins("PA Control Computer increased to [strength] by [ADMIN_LOOKUPFLW(usr)] in [ADMIN_VERBOSEJMP(src)]")
- log_game("PA Control Computer increased to [strength] by [key_name(usr)] in [AREACOORD(src)]")
- investigate_log("increased to [strength] by [key_name(usr)] at [AREACOORD(src)]", INVESTIGATE_SINGULO)
+ investigate_log("increased to [strength] by [key_name(usr)] at [AREACOORD(src)]", INVESTIGATE_ENGINES)
/obj/machinery/particle_accelerator/control_box/proc/remove_strength(s)
if(assembled && (strength > 0))
@@ -98,8 +99,7 @@
strength_change()
message_admins("PA Control Computer decreased to [strength] by [ADMIN_LOOKUPFLW(usr)] in [ADMIN_VERBOSEJMP(src)]")
- log_game("PA Control Computer decreased to [strength] by [key_name(usr)] in [AREACOORD(src)]")
- investigate_log("decreased to [strength] by [key_name(usr)] at [AREACOORD(src)]", INVESTIGATE_SINGULO)
+ investigate_log("decreased to [strength] by [key_name(usr)] at [AREACOORD(src)]", INVESTIGATE_ENGINES)
/obj/machinery/particle_accelerator/control_box/power_change()
..()
@@ -113,7 +113,7 @@
if(active)
//a part is missing!
if(connected_parts.len < 6)
- investigate_log("lost a connected part; It powered down.", INVESTIGATE_SINGULO)
+ investigate_log("lost a connected part; It powered down.", INVESTIGATE_ENGINES)
toggle_power()
update_icon()
return
@@ -172,9 +172,8 @@
/obj/machinery/particle_accelerator/control_box/proc/toggle_power()
active = !active
- investigate_log("turned [active?"ON":"OFF"] by [usr ? key_name(usr) : "outside forces"] at [AREACOORD(src)]", INVESTIGATE_SINGULO)
+ investigate_log("turned [active?"ON":"OFF"] by [usr ? key_name(usr) : "outside forces"] at [AREACOORD(src)]", INVESTIGATE_ENGINES)
message_admins("PA Control Computer turned [active ?"ON":"OFF"] by [usr ? ADMIN_LOOKUPFLW(usr) : "outside forces"] in [ADMIN_VERBOSEJMP(src)]")
- log_game("PA Control Computer turned [active ?"ON":"OFF"] by [usr ? "[key_name(usr)]" : "outside forces"] at [AREACOORD(src)]")
if(active)
use_power = ACTIVE_POWER_USE
for(var/CP in connected_parts)
@@ -239,6 +238,7 @@
"You close the access panel.")
construction_state = PA_CONSTRUCTION_COMPLETE
did_something = TRUE
+ ui_update()
if(PA_CONSTRUCTION_COMPLETE)
if(W.tool_behaviour == TOOL_SCREWDRIVER)
user.visible_message("[user.name] opens the [name]'s access panel.", \
@@ -318,7 +318,8 @@
remove_strength()
. = TRUE
- update_icon()
+ if(.)
+ update_icon()
#undef PA_CONSTRUCTION_UNSECURED
#undef PA_CONSTRUCTION_UNWIRED
diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm
index fbec10cd15f94..793de2b1767ed 100644
--- a/code/modules/power/singularity/singularity.dm
+++ b/code/modules/power/singularity/singularity.dm
@@ -7,6 +7,7 @@
icon_state = "singularity_s1"
anchored = TRUE
density = TRUE
+ var/is_real = TRUE
move_resist = INFINITY
layer = MASSIVE_OBJ_LAYER
light_range = 6
@@ -16,9 +17,12 @@
var/contained = 1 //Are we going to move around?
var/energy = 100 //How strong are we?
var/dissipate = 1 //Do we lose energy over time?
- var/dissipate_delay = 10
- var/dissipate_track = 0
- var/dissipate_strength = 1 //How much energy do we lose?
+ /// How long should it take for us to dissipate in seconds?
+ var/dissipate_delay = 20
+ /// How much energy do we lose every dissipate_delay?
+ var/dissipate_strength = 1
+ /// How long its been (in seconds) since the last dissipation
+ var/time_since_last_dissipiation = 0
var/move_self = 1 //Do we move on our own?
var/grav_pull = 4 //How many tiles out do we pull?
var/consume_range = 0 //How many tiles out do we eat
@@ -101,7 +105,7 @@
switch(severity)
if(1)
if(current_size <= STAGE_TWO)
- investigate_log("has been destroyed by a heavy explosion.", INVESTIGATE_SINGULO)
+ investigate_log("has been destroyed by a heavy explosion.", INVESTIGATE_ENGINES)
qdel(src)
return
else
@@ -127,14 +131,14 @@
consume(AM)
-/obj/singularity/process()
+/obj/singularity/process(delta_time)
if(current_size >= STAGE_TWO)
move()
radiation_pulse(src, min(5000, (energy*4.5)+1000), RAD_DISTANCE_COEFFICIENT*0.5)
if(prob(event_chance))//Chance for it to run a special event TODO:Come up with one or two more that fit
event()
eat()
- dissipate()
+ dissipate(delta_time)
check_energy()
return
@@ -149,17 +153,18 @@
var/count = locate(/obj/machinery/field/containment) in urange(30, src, 1)
if(!count)
message_admins("A singulo has been created without containment fields active at [ADMIN_VERBOSEJMP(T)].")
- investigate_log("was created at [AREACOORD(T)]. [count?"":"No containment fields were active"]", INVESTIGATE_SINGULO)
+ investigate_log("was created at [AREACOORD(T)]. [count?"":"No containment fields were active"]", INVESTIGATE_ENGINES)
-/obj/singularity/proc/dissipate()
+/obj/singularity/proc/dissipate(delta_time)
if(!dissipate)
return
- if(dissipate_track >= dissipate_delay)
- src.energy -= dissipate_strength
- dissipate_track = 0
- else
- dissipate_track++
+ time_since_last_dissipiation += delta_time
+
+ // Uses a while in case of especially long delta times
+ while (time_since_last_dissipiation >= dissipate_delay)
+ energy -= dissipate_strength
+ time_since_last_dissipiation -= dissipate_delay
/obj/singularity/proc/expand(force_size = 0)
var/temp_allowed_size = src.allowed_size
@@ -177,7 +182,7 @@
grav_pull = 4
consume_range = 0
dissipate_delay = 10
- dissipate_track = 0
+ time_since_last_dissipiation = 0
dissipate_strength = 1
if(STAGE_TWO)
if(check_cardinals_range(1, TRUE))
@@ -189,7 +194,7 @@
grav_pull = 6
consume_range = 1
dissipate_delay = 5
- dissipate_track = 0
+ time_since_last_dissipiation = 0
dissipate_strength = 5
if(STAGE_THREE)
if(check_cardinals_range(2, TRUE))
@@ -201,7 +206,7 @@
grav_pull = 8
consume_range = 2
dissipate_delay = 4
- dissipate_track = 0
+ time_since_last_dissipiation = 0
dissipate_strength = 20
if(STAGE_FOUR)
if(check_cardinals_range(3, TRUE))
@@ -213,7 +218,7 @@
grav_pull = 10
consume_range = 3
dissipate_delay = 10
- dissipate_track = 0
+ time_since_last_dissipiation = 0
dissipate_strength = 10
if(STAGE_FIVE)//this one also lacks a check for gens because it eats everything
current_size = STAGE_FIVE
@@ -234,7 +239,7 @@
consume_range = 5
dissipate = 0
if(current_size == allowed_size)
- investigate_log("grew to size [current_size]", INVESTIGATE_SINGULO)
+ investigate_log("grew to size [current_size]", INVESTIGATE_ENGINES)
return 1
else if(current_size < (--temp_allowed_size))
expand(temp_allowed_size)
@@ -244,7 +249,7 @@
/obj/singularity/proc/check_energy()
if(energy <= 0)
- investigate_log("collapsed.", INVESTIGATE_SINGULO)
+ investigate_log("collapsed.", INVESTIGATE_ENGINES)
qdel(src)
return 0
switch(energy)//Some of these numbers might need to be changed up later -Mport
@@ -267,8 +272,7 @@
/obj/singularity/proc/eat()
- for(var/tile in spiral_range_turfs(grav_pull, src))
- var/turf/T = tile
+ for(var/turf/T as() in spiral_range_turfs(grav_pull, src))
if(!T || !isturf(loc))
continue
if(get_dist(T, src) > consume_range)
@@ -422,7 +426,7 @@
continue
if(M.stat == CONSCIOUS)
- if (ishuman(M))
+ if(ishuman(M))
var/mob/living/carbon/human/H = M
if(istype(H.glasses, /obj/item/clothing/glasses/meson))
var/obj/item/clothing/glasses/meson/MS = H.glasses
@@ -448,5 +452,19 @@
return(gain)
/obj/singularity/proc/bluespace_reaction()
- investigate_log("has been shot by bluespace artillery and destroyed.", INVESTIGATE_SINGULO)
+ SIGNAL_HANDLER
+
+ investigate_log("has been shot by bluespace artillery and destroyed.", INVESTIGATE_ENGINES)
qdel(src)
+
+/obj/singularity/deadchat_controlled
+ move_self = FALSE
+
+/obj/singularity/deadchat_controlled/Initialize(mapload, starting_energy)
+ . = ..()
+ AddComponent(/datum/component/deadchat_control, DEMOCRACY_MODE, list(
+ "up" = CALLBACK(GLOBAL_PROC, .proc/_step, src, NORTH),
+ "down" = CALLBACK(GLOBAL_PROC, .proc/_step, src, SOUTH),
+ "left" = CALLBACK(GLOBAL_PROC, .proc/_step, src, WEST),
+ "right" = CALLBACK(GLOBAL_PROC, .proc/_step, src, EAST)))
+
\ No newline at end of file
diff --git a/code/modules/power/smes.dm b/code/modules/power/smes.dm
index 9995804c64fdb..31e2a40da5a9b 100644
--- a/code/modules/power/smes.dm
+++ b/code/modules/power/smes.dm
@@ -157,7 +157,7 @@
if(default_deconstruction_crowbar(I))
message_admins("[src] has been deconstructed by [ADMIN_LOOKUPFLW(user)] in [ADMIN_VERBOSEJMP(T)]")
log_game("[src] has been deconstructed by [key_name(user)] at [AREACOORD(src)]")
- investigate_log("SMES deconstructed by [key_name(user)] at [AREACOORD(src)]", INVESTIGATE_SINGULO)
+ investigate_log("SMES deconstructed by [key_name(user)] at [AREACOORD(src)]", INVESTIGATE_ENGINES)
return
else if(panel_open && I.tool_behaviour == TOOL_CROWBAR)
return
@@ -188,7 +188,7 @@
var/turf/T = get_turf(src)
message_admins("SMES deleted at [ADMIN_VERBOSEJMP(T)]")
log_game("SMES deleted at [AREACOORD(T)]")
- investigate_log("deleted at [AREACOORD(T)]", INVESTIGATE_SINGULO)
+ investigate_log("deleted at [AREACOORD(T)]", INVESTIGATE_ENGINES)
if(terminal)
disconnect_terminal()
return ..()
@@ -286,7 +286,7 @@
if(output_used < 0.0001) // either from no charge or set to 0
outputting = FALSE
- investigate_log("lost power and turned off", INVESTIGATE_SINGULO)
+ investigate_log("lost power and turned off", INVESTIGATE_ENGINES)
else if(output_attempt && charge > output_level && output_level > 0)
outputting = TRUE
else
@@ -339,6 +339,7 @@
if(!ui)
ui = new(user, src, "Smes")
ui.open()
+ ui.set_autoupdate(TRUE) // Power level
/obj/machinery/power/smes/ui_data()
var/list/data = list(
@@ -366,12 +367,10 @@
switch(action)
if("tryinput")
input_attempt = !input_attempt
- log_smes(usr)
update_icon()
. = TRUE
if("tryoutput")
output_attempt = !output_attempt
- log_smes(usr)
update_icon()
. = TRUE
if("input")
@@ -391,7 +390,6 @@
. = TRUE
if(.)
input_level = clamp(target, 0, input_level_max)
- log_smes(usr)
if("output")
var/target = params["target"]
var/adjust = text2num(params["adjust"])
@@ -409,10 +407,11 @@
. = TRUE
if(.)
output_level = clamp(target, 0, output_level_max)
- log_smes(usr)
+ if(.)
+ log_smes(usr)
/obj/machinery/power/smes/proc/log_smes(mob/user)
- investigate_log("input/output; [input_level>output_level?"":""][input_level]/[output_level] | Charge: [charge] | Output-mode: [output_attempt?"on":"off"] | Input-mode: [input_attempt?"auto":"off"] by [user ? key_name(user) : "outside forces"]", INVESTIGATE_SINGULO)
+ investigate_log("input/output; [input_level>output_level?"":""][input_level]/[output_level] | Charge: [charge] | Output-mode: [output_attempt?"on":"off"] | Input-mode: [input_attempt?"auto":"off"] by [user ? key_name(user) : "outside forces"]", INVESTIGATE_ENGINES)
/obj/machinery/power/smes/emp_act(severity)
diff --git a/code/modules/power/solar.dm b/code/modules/power/solar.dm
index ab160bdbb201d..0658a0482b93b 100644
--- a/code/modules/power/solar.dm
+++ b/code/modules/power/solar.dm
@@ -332,6 +332,7 @@
/obj/machinery/power/solar_control/update_icon()
cut_overlays()
+ SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays)
if(stat & NOPOWER)
add_overlay("[icon_keyboard]_off")
return
@@ -339,7 +340,8 @@
if(stat & BROKEN)
add_overlay("[icon_state]_broken")
else
- add_overlay(icon_screen)
+ SSvis_overlays.add_vis_overlay(src, icon, icon_screen, layer, plane, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, icon_screen, layer, EMISSIVE_PLANE, dir)
/obj/machinery/power/solar_control/ui_state(mob/user)
@@ -350,6 +352,7 @@
if(!ui)
ui = new(user, src, "SolarControl")
ui.open()
+ ui.set_autoupdate(TRUE) // Power output, solar panel direction
/obj/machinery/power/solar_control/ui_data()
var/data = list()
@@ -369,47 +372,45 @@
/obj/machinery/power/solar_control/ui_act(action, params)
if(..())
return
- if(action == "angle")
- var/adjust = text2num(params["adjust"])
- var/value = text2num(params["value"])
- if(adjust)
- value = currentdir + adjust
- if(value != null)
- currentdir = CLAMP((360 + value) % 360, 0, 359)
- targetdir = currentdir
- set_panels(currentdir)
- return TRUE
- return FALSE
- if(action == "rate")
- var/adjust = text2num(params["adjust"])
- var/value = text2num(params["value"])
- if(adjust)
- value = trackrate + adjust
- if(value != null)
- trackrate = CLAMP(value, -7200, 7200)
- if(trackrate)
- nexttime = world.time + 36000 / abs(trackrate)
- return TRUE
- return FALSE
- if(action == "tracking")
- var/mode = text2num(params["mode"])
- track = mode
- if(mode == 2 && connected_tracker)
- connected_tracker.set_angle(SSsun.angle)
+ switch(action)
+ if("angle")
+ var/adjust = text2num(params["adjust"])
+ var/value = text2num(params["value"])
+ if(adjust)
+ value = currentdir + adjust
+ if(value != null)
+ currentdir = CLAMP((360 + value) % 360, 0, 359)
+ targetdir = currentdir
+ set_panels(currentdir)
+ . = TRUE
+ if("rate")
+ var/adjust = text2num(params["adjust"])
+ var/value = text2num(params["value"])
+ if(adjust)
+ value = trackrate + adjust
+ if(value != null)
+ trackrate = CLAMP(value, -7200, 7200)
+ if(trackrate)
+ nexttime = world.time + 36000 / abs(trackrate)
+ . = TRUE
+ if("tracking")
+ var/mode = text2num(params["mode"])
+ track = mode
+ if(mode == 2 && connected_tracker)
+ connected_tracker.set_angle(SSsun.angle)
+ set_panels(currentdir)
+ else if(mode == 1)
+ targetdir = currentdir
+ if(trackrate)
+ nexttime = world.time + 36000 / abs(trackrate)
+ set_panels(targetdir)
+ . = TRUE
+ if("refresh")
+ search_for_connected()
+ if(connected_tracker && track == 2)
+ connected_tracker.set_angle(SSsun.angle)
set_panels(currentdir)
- else if(mode == 1)
- targetdir = currentdir
- if(trackrate)
- nexttime = world.time + 36000 / abs(trackrate)
- set_panels(targetdir)
- return TRUE
- if(action == "refresh")
- search_for_connected()
- if(connected_tracker && track == 2)
- connected_tracker.set_angle(SSsun.angle)
- set_panels(currentdir)
- return TRUE
- return FALSE
+ . = TRUE
/obj/machinery/power/solar_control/attackby(obj/item/I, mob/user, params)
if(I.tool_behaviour == TOOL_SCREWDRIVER)
diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm
index 571f7a1674f6b..a2e6c996fa20a 100644
--- a/code/modules/power/supermatter/supermatter.dm
+++ b/code/modules/power/supermatter/supermatter.dm
@@ -82,6 +82,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
var/static/gl_uid = 1
light_range = 4
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF
+ flags_1 = PREVENT_CONTENTS_EXPLOSION_1
critical_machine = TRUE
@@ -167,7 +168,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
/obj/machinery/power/supermatter_crystal/Initialize()
. = ..()
uid = gl_uid++
- SSair.atmos_machinery += src
+ SSair.atmos_air_machinery += src
countdown = new(src)
countdown.start()
GLOB.poi_list |= src
@@ -175,7 +176,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
radio.keyslot = new radio_key
radio.listening = 0
radio.recalculateChannels()
- investigate_log("has been created.", INVESTIGATE_SUPERMATTER)
+ investigate_log("has been created.", INVESTIGATE_ENGINES)
if(is_main_engine)
GLOB.main_supermatter_engine = src
@@ -185,8 +186,8 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
soundloop = new(list(src), TRUE)
/obj/machinery/power/supermatter_crystal/Destroy()
- investigate_log("has been destroyed.", INVESTIGATE_SUPERMATTER)
- SSair.atmos_machinery -= src
+ investigate_log("has been destroyed.", INVESTIGATE_ENGINES)
+ SSair.atmos_air_machinery -= src
QDEL_NULL(radio)
GLOB.poi_list -= src
QDEL_NULL(countdown)
@@ -281,7 +282,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
/obj/machinery/power/supermatter_crystal/proc/explode()
for(var/mob in GLOB.alive_mob_list)
var/mob/living/L = mob
- if(istype(L) && L.z == z)
+ if(istype(L) && L.get_virtual_z_level() == get_virtual_z_level())
if(ishuman(mob))
//Hilariously enough, running into a closet should make you get hit the hardest.
var/mob/living/carbon/human/H = mob
@@ -291,27 +292,28 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
var/turf/T = get_turf(src)
for(var/mob/M in GLOB.player_list)
- if(M.z == z)
+ if(M.get_virtual_z_level() == get_virtual_z_level())
SEND_SOUND(M, 'sound/magic/charge.ogg')
to_chat(M, "You feel reality distort for a moment...")
SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "delam", /datum/mood_event/delam)
if(combined_gas > MOLE_PENALTY_THRESHOLD)
- investigate_log("has collapsed into a singularity.", INVESTIGATE_SUPERMATTER)
+ investigate_log("has collapsed into a singularity.", INVESTIGATE_ENGINES)
if(T)
var/obj/singularity/S = new(T)
S.energy = 800
S.consume(src)
else
- investigate_log("has exploded.", INVESTIGATE_SUPERMATTER)
+ investigate_log("has exploded.", INVESTIGATE_ENGINES)
explosion(get_turf(T), explosion_power * max(gasmix_power_ratio, 0.205) * 0.5 , explosion_power * max(gasmix_power_ratio, 0.205) + 2, explosion_power * max(gasmix_power_ratio, 0.205) + 4 , explosion_power * max(gasmix_power_ratio, 0.205) + 6, 1, 1)
if(power > POWER_PENALTY_THRESHOLD)
- investigate_log("has spawned additional energy balls.", INVESTIGATE_SUPERMATTER)
- var/obj/singularity/energy_ball/E = new(T)
- E.energy = power
+ investigate_log("has spawned additional energy balls.", INVESTIGATE_ENGINES)
+ new /obj/singularity/energy_ball(T, power)
qdel(src)
//this is here to eat arguments
/obj/machinery/power/supermatter_crystal/proc/call_explode()
+ SIGNAL_HANDLER
+
explode()
/obj/machinery/power/supermatter_crystal/process_atmos()
@@ -352,7 +354,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
if(produces_gas)
//Remove gas from surrounding area
- removed = env.remove(gasefficency * env.total_moles())
+ removed = env.remove_ratio(gasefficency)
else
// Pass all the gas related code an empty gas container
removed = new()
@@ -378,15 +380,15 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
//calculating gas related values
combined_gas = max(removed.total_moles(), 0)
- plasmacomp = max(removed.get_moles(/datum/gas/plasma)/combined_gas, 0)
- o2comp = max(removed.get_moles(/datum/gas/oxygen)/combined_gas, 0)
- co2comp = max(removed.get_moles(/datum/gas/carbon_dioxide)/combined_gas, 0)
- pluoxiumcomp = max(removed.get_moles(/datum/gas/pluoxium)/combined_gas, 0)
- tritiumcomp = max(removed.get_moles(/datum/gas/tritium)/combined_gas, 0)
- bzcomp = max(removed.get_moles(/datum/gas/bz)/combined_gas, 0)
+ plasmacomp = max(removed.get_moles(GAS_PLASMA)/combined_gas, 0)
+ o2comp = max(removed.get_moles(GAS_O2)/combined_gas, 0)
+ co2comp = max(removed.get_moles(GAS_CO2)/combined_gas, 0)
+ pluoxiumcomp = max(removed.get_moles(GAS_PLUOXIUM)/combined_gas, 0)
+ tritiumcomp = max(removed.get_moles(GAS_TRITIUM)/combined_gas, 0)
+ bzcomp = max(removed.get_moles(GAS_BZ)/combined_gas, 0)
- n2ocomp = max(removed.get_moles(/datum/gas/nitrous_oxide)/combined_gas, 0)
- n2comp = max(removed.get_moles(/datum/gas/nitrogen)/combined_gas, 0)
+ n2ocomp = max(removed.get_moles(GAS_NITROUS)/combined_gas, 0)
+ n2comp = max(removed.get_moles(GAS_N2)/combined_gas, 0)
if(pluoxiumcomp >= 15)
pluoxiumbonus = 1 //Just to be safe I don't want to remove pluoxium
@@ -446,21 +448,21 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
removed.set_temperature(max(0, min(removed.return_temperature(), 2500 * dynamic_heat_modifier)))
//Calculate how much gas to release
- removed.adjust_moles(/datum/gas/plasma, max((device_energy * dynamic_heat_modifier) / PLASMA_RELEASE_MODIFIER, 0))
+ removed.adjust_moles(GAS_PLASMA, max((device_energy * dynamic_heat_modifier) / PLASMA_RELEASE_MODIFIER, 0))
- removed.adjust_moles(/datum/gas/oxygen, max(((device_energy + removed.return_temperature() * dynamic_heat_modifier) - T0C) / OXYGEN_RELEASE_MODIFIER, 0))
+ removed.adjust_moles(GAS_O2, max(((device_energy + removed.return_temperature() * dynamic_heat_modifier) - T0C) / OXYGEN_RELEASE_MODIFIER, 0))
if(produces_gas)
env.merge(removed)
air_update_turf()
- for(var/mob/living/carbon/human/l in view(src, HALLUCINATION_RANGE(power))) // If they can see it without mesons on. Bad on them.
+ for(var/mob/living/carbon/human/l in viewers(HALLUCINATION_RANGE(power), src)) // If they can see it without mesons on. Bad on them.
if(!istype(l.glasses, /obj/item/clothing/glasses/meson))
var/D = sqrt(1 / max(1, get_dist(l, src)))
l.hallucination += power * config_hallucination_power * D
l.hallucination = CLAMP(0, 200, l.hallucination)
- for(var/mob/living/l in range(src, round((power / 100) ** 0.25)))
+ for(var/mob/living/l in range(round((power / 100) ** 0.25), src))
var/rads = (power / 10) * sqrt( 1 / max(get_dist(l, src),1) )
l.rad_act(rads)
@@ -499,7 +501,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
radio.talk_into(src, "[emergency_alert] Integrity: [get_integrity()]%", common_channel)
lastwarning = REALTIMEOFDAY
if(!has_reached_emergency)
- investigate_log("has reached the emergency point for the first time.", INVESTIGATE_SUPERMATTER)
+ investigate_log("has reached the emergency point for the first time.", INVESTIGATE_ENGINES)
message_admins("[src] has reached the emergency point [ADMIN_JMP(src)].")
has_reached_emergency = TRUE
else if(damage >= damage_archived) // The damage is still going up
@@ -528,11 +530,11 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
if(!istype(L))
return FALSE
if(!istype(Proj.firer, /obj/machinery/power/emitter))
- investigate_log("has been hit by [Proj] fired by [key_name(Proj.firer)]", INVESTIGATE_SUPERMATTER)
+ investigate_log("has been hit by [Proj] fired by [key_name(Proj.firer)]", INVESTIGATE_ENGINES)
if(Proj.flag != "bullet")
power += Proj.damage * config_bullet_energy
if(!has_been_powered)
- investigate_log("has been powered for the first time.", INVESTIGATE_SUPERMATTER)
+ investigate_log("has been powered for the first time.", INVESTIGATE_ENGINES)
message_admins("[src] has been powered for the first time [ADMIN_JMP(src)].")
has_been_powered = TRUE
else if(takes_damage)
@@ -541,11 +543,11 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
/obj/machinery/power/supermatter_crystal/singularity_act()
var/gain = 100
- investigate_log("Supermatter shard consumed by singularity.", INVESTIGATE_SINGULO)
+ investigate_log("Supermatter shard consumed by singularity.", INVESTIGATE_ENGINES)
message_admins("Singularity has consumed a supermatter shard and can now become stage six.")
visible_message("[src] is consumed by the singularity!")
for(var/mob/M in GLOB.player_list)
- if(M.z == z)
+ if(M.get_virtual_z_level() == get_virtual_z_level())
SEND_SOUND(M, 'sound/effects/supermatter.ogg') //everyone goan know bout this
to_chat(M, "A horrible screeching fills your ears, and a wave of dread washes over you...")
qdel(src)
@@ -613,7 +615,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
if(!cause)
cause = "contact"
nom.visible_message(vis_msg, mob_msg, "You hear an unearthly noise as a wave of heat washes over you.")
- investigate_log("has been attacked ([cause]) by [key_name(nom)]", INVESTIGATE_SUPERMATTER)
+ investigate_log("has been attacked ([cause]) by [key_name(nom)]", INVESTIGATE_ENGINES)
playsound(get_turf(src), 'sound/effects/supermatter.ogg', 50, 1)
Consume(nom)
@@ -635,7 +637,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
"Oops! The [W] flashes out of existence on contact with \the [src], taking your arm with it! That was clumsy of you!")
playsound(src, 'sound/effects/supermatter.ogg', 150, 1)
Consume(dust_arm)
- qdel(W)
+ Consume(W)
return
if(cig.lit || user.a_intent != INTENT_HELP)
user.visible_message("A hideous sound echoes as [W] is ashed out on contact with \the [src]. That didn't seem like a good idea...")
@@ -667,7 +669,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
user.visible_message("As [user] touches \the [src] with \a [W], silence fills the room...",\
"You touch \the [src] with \the [W], and everything suddenly goes silent.\n\The [W] flashes into dust as you flinch away from \the [src].",\
"Everything suddenly goes silent.")
- investigate_log("has been attacked ([W]) by [key_name(user)]", INVESTIGATE_SUPERMATTER)
+ investigate_log("has been attacked ([W]) by [key_name(user)]", INVESTIGATE_ENGINES)
Consume(W)
playsound(get_turf(src), 'sound/effects/supermatter.ogg', 50, 1)
@@ -704,18 +706,25 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
if(user.status_flags & GODMODE)
return
message_admins("[src] has consumed [key_name_admin(user)] [ADMIN_JMP(src)].")
- investigate_log("has consumed [key_name(user)].", INVESTIGATE_SUPERMATTER)
+ investigate_log("has consumed [key_name(user)].", INVESTIGATE_ENGINES)
user.dust(force = TRUE)
matter_power += 200
else if(istype(AM, /obj/singularity))
return
else if(isobj(AM))
+ var/obj/O = AM
+ if(O.resistance_flags & INDESTRUCTIBLE)
+ var/image/causality_field = image(icon, null, "causality_field")
+ add_overlay(causality_field, TRUE)
+ radio.talk_into(src, "Anomalous object has breached containment, emergency causality field enganged to prevent reality destabilization.", engineering_channel)
+ addtimer(CALLBACK(src, .proc/disengage_field, causality_field), 5 SECONDS)
+ return
if(!iseffect(AM))
var/suspicion = ""
if(AM.fingerprintslast)
suspicion = "last touched by [AM.fingerprintslast]"
message_admins("[src] has consumed [AM], [suspicion] [ADMIN_JMP(src)].")
- investigate_log("has consumed [AM] - [suspicion].", INVESTIGATE_SUPERMATTER)
+ investigate_log("has consumed [AM] - [suspicion].", INVESTIGATE_ENGINES)
qdel(AM)
if(!iseffect(AM))
matter_power += 200
@@ -723,20 +732,22 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
//Some poor sod got eaten, go ahead and irradiate people nearby.
radiation_pulse(src, 3000, 2, TRUE)
for(var/mob/living/L in range(10))
- investigate_log("has irradiated [key_name(L)] after consuming [AM].", INVESTIGATE_SUPERMATTER)
- if(L in view())
+ investigate_log("has irradiated [key_name(L)] after consuming [AM].", INVESTIGATE_ENGINES)
+ if(L in viewers(get_turf(src)))
L.show_message("As \the [src] slowly stops resonating, you find your skin covered in new radiation burns.", 1,\
- "The unearthly ringing subsides and you notice you have new radiation burns.", 2)
+ "The unearthly ringing subsides and you notice you have new radiation burns.", MSG_AUDIBLE)
else
- L.show_message("You hear an unearthly ringing and notice your skin is covered in fresh radiation burns.", 2)
+ L.show_message("You hear an unearthly ringing and notice your skin is covered in fresh radiation burns.", MSG_AUDIBLE)
+
+/obj/machinery/power/supermatter_crystal/proc/disengage_field(causality_field)
+ if(QDELETED(src) || !causality_field)
+ return
+ cut_overlay(causality_field, TRUE)
//Do not blow up our internal radio
/obj/machinery/power/supermatter_crystal/contents_explosion(severity, target)
return
-/obj/machinery/power/supermatter_crystal/prevent_content_explosion()
- return TRUE
-
/obj/machinery/power/supermatter_crystal/engine
is_main_engine = TRUE
@@ -791,7 +802,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
step_towards(P,center)
/obj/machinery/power/supermatter_crystal/proc/supermatter_anomaly_gen(turf/anomalycenter, type = FLUX_ANOMALY, anomalyrange = 5)
- var/turf/L = pick(orange(anomalyrange, anomalycenter))
+ var/turf/L = pick(RANGE_TURFS(anomalyrange, anomalycenter) - anomalycenter)
if(L)
switch(type)
if(FLUX_ANOMALY)
@@ -816,7 +827,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
var/list/arctargetsstructure = list()
if(prob(20)) //let's not hit all the engineers with every beam and/or segment of the arc
- for(var/mob/living/Z in oview(zapstart, range+2))
+ for(var/mob/living/Z in ohearers(range+2, zapstart))
arctargetsmob += Z
if(arctargetsmob.len)
var/mob/living/H = pick(arctargetsmob)
@@ -825,7 +836,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
target_atom = A
else
- for(var/obj/machinery/X in oview(zapstart, range+2))
+ for(var/obj/machinery/X in oview(range+2, zapstart))
arctargetsmachine += X
if(arctargetsmachine.len)
var/obj/machinery/M = pick(arctargetsmachine)
@@ -834,7 +845,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
target_atom = A
else
- for(var/obj/structure/Y in oview(zapstart, range+2))
+ for(var/obj/structure/Y in oview(range+2, zapstart))
arctargetsstructure += Y
if(arctargetsstructure.len)
var/obj/structure/O = pick(arctargetsstructure)
diff --git a/code/modules/power/tesla/coil.dm b/code/modules/power/tesla/coil.dm
index 3f05b4eb51229..ce0f2adbb8476 100644
--- a/code/modules/power/tesla/coil.dm
+++ b/code/modules/power/tesla/coil.dm
@@ -40,7 +40,7 @@
/obj/machinery/power/tesla_coil/examine(mob/user)
. = ..()
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Power generation at [input_power_multiplier*100]%. Shock interval at [zap_cooldown*0.1] seconds."
+ . += "The status display reads: Power generation at [input_power_multiplier*100]%. Shock interval at [zap_cooldown*0.1] seconds."
/obj/machinery/power/tesla_coil/on_construction()
if(anchored)
diff --git a/code/modules/power/tesla/energy_ball.dm b/code/modules/power/tesla/energy_ball.dm
index 5596b64b10fe7..b267c3adf09f5 100644
--- a/code/modules/power/tesla/energy_ball.dm
+++ b/code/modules/power/tesla/energy_ball.dm
@@ -1,5 +1,6 @@
#define TESLA_DEFAULT_POWER 1738260
#define TESLA_MINI_POWER 869130
+#define TESLA_MAX_BALLS 10
/obj/singularity/energy_ball
name = "energy ball"
@@ -18,57 +19,54 @@
dissipate_delay = 5
dissipate_strength = 1
var/list/orbiting_balls = list()
- var/miniball = FALSE
var/produced_power
var/energy_to_raise = 32
var/energy_to_lower = -20
-/obj/singularity/energy_ball/Initialize(mapload, starting_energy = 50, is_miniball = FALSE)
- miniball = is_miniball
+/*
+/obj/singularity/energy_ball/Initialize(mapload, starting_energy = 50)
. = ..()
+ //TODO: Renable when we get a better lighting system that doesn't need constant updating
if(!is_miniball)
set_light(10, 7, "#EEEEFF")
+*/
/obj/singularity/energy_ball/ex_act(severity, target)
return
/obj/singularity/energy_ball/Destroy()
- if(orbiting && istype(orbiting.parent, /obj/singularity/energy_ball))
- var/obj/singularity/energy_ball/EB = orbiting.parent
- EB.orbiting_balls -= src
-
- for(var/ball in orbiting_balls)
- var/obj/singularity/energy_ball/EB = ball
- qdel(EB)
-
+ QDEL_LIST(orbiting_balls)
. = ..()
-/obj/singularity/energy_ball/admin_investigate_setup()
- if(miniball)
- return //don't annnounce miniballs
- ..()
-
-
/obj/singularity/energy_ball/process()
- if(!orbiting)
- handle_energy()
+ handle_energy()
- move_the_basket_ball(4 + orbiting_balls.len * 1.5)
+ move_the_basket_ball(4 + orbiting_balls.len * 1.5)
- playsound(src.loc, 'sound/magic/lightningbolt.ogg', 100, 1, extrarange = 30)
+ playsound(src.loc, 'sound/magic/lightningbolt.ogg', 100, 1, extrarange = 30)
- pixel_x = 0
- pixel_y = 0
-
- tesla_zap(src, 7, TESLA_DEFAULT_POWER, TRUE)
+ pixel_x = 0
+ pixel_y = 0
+ //Main one can zap
+ //Tesla only zaps if the tick usage isn't over the limit.
+ if(!TICK_CHECK)
+ tesla_zap(src, 7, TESLA_DEFAULT_POWER, TESLA_ENERGY_PRIMARY_BALL_FLAGS)
+ else
+ //Weaker, less intensive zap
+ tesla_zap(src, 4, TESLA_DEFAULT_POWER, TESLA_ENERGY_MINI_BALL_FLAGS)
pixel_x = -32
pixel_y = -32
- for (var/ball in orbiting_balls)
- var/range = rand(1, CLAMP(orbiting_balls.len, 3, 7))
- tesla_zap(ball, range, TESLA_MINI_POWER/7*range)
- else
- energy = 0 // ensure we dont have miniballs of miniballs
+ return
+
+ pixel_x = -32
+ pixel_y = -32
+ for (var/ball in orbiting_balls)
+ if(TICK_CHECK)
+ return
+ var/range = rand(1, CLAMP(orbiting_balls.len, 3, 7))
+ //Miniballs don't explode.
+ tesla_zap(ball, range, TESLA_MINI_POWER/7*range, TESLA_ENERGY_MINI_BALL_FLAGS)
/obj/singularity/energy_ball/examine(mob/user)
. = ..()
@@ -112,12 +110,14 @@
/obj/singularity/energy_ball/proc/new_mini_ball()
if(!loc)
return
- var/obj/singularity/energy_ball/EB = new(loc, 0, TRUE)
+ if(orbiting_balls.len >= TESLA_MAX_BALLS)
+ return
+ var/obj/effect/energy_ball/EB = new(loc, 0, TRUE)
- EB.transform *= pick(0.3, 0.4, 0.5, 0.6, 0.7)
+ EB.transform *= rand(30, 70) * 0.01
var/icon/I = icon(icon,icon_state,dir)
- var/orbitsize = (I.Width() + I.Height()) * pick(0.4, 0.5, 0.6, 0.7, 0.8)
+ var/orbitsize = (I.Width() + I.Height()) * rand(40, 80) * 0.01
orbitsize -= (orbitsize / world.icon_size) * (world.icon_size * 0.25)
EB.orbit(src, orbitsize, pick(FALSE, TRUE), rand(10, 25), pick(3, 4, 5, 6, 36))
@@ -138,23 +138,6 @@
qdel(rip_u)
C.death()
-/obj/singularity/energy_ball/orbit(obj/singularity/energy_ball/target)
- if (istype(target))
- target.orbiting_balls += src
- GLOB.poi_list -= src
- target.dissipate_strength = target.orbiting_balls.len
-
- . = ..()
-/obj/singularity/energy_ball/stop_orbit()
- if (orbiting && istype(orbiting.parent, /obj/singularity/energy_ball))
- var/obj/singularity/energy_ball/orbitingball = orbiting.parent
- orbitingball.orbiting_balls -= src
- orbitingball.dissipate_strength = orbitingball.orbiting_balls.len
- . = ..()
- if (!QDELETED(src))
- qdel(src)
-
-
/obj/singularity/energy_ball/proc/dust_mobs(atom/A)
if(isliving(A))
var/mob/living/L = A
@@ -162,12 +145,40 @@
return
if(!iscarbon(A))
return
- for(var/obj/machinery/power/grounding_rod/GR in orange(src, 2))
+ for(var/obj/machinery/power/grounding_rod/GR in orange(2, src))
if(GR.anchored)
return
var/mob/living/carbon/C = A
C.dust()
+//Less intensive energy ball for the orbiting ones.
+/obj/effect/energy_ball
+ name = "energy ball"
+ desc = "An energy ball."
+ icon = 'icons/obj/tesla_engine/energy_ball.dmi'
+ icon_state = "energy_ball"
+ pixel_x = -32
+ pixel_y = -32
+
+/obj/effect/energy_ball/Destroy(force)
+ if(orbiting && istype(orbiting.parent, /obj/singularity/energy_ball))
+ var/obj/singularity/energy_ball/EB = orbiting.parent
+ EB.orbiting_balls -= src
+ EB.dissipate_strength = EB.orbiting_balls.len
+ . = ..()
+
+/obj/effect/energy_ball/orbit(obj/singularity/energy_ball/target)
+ if (istype(target))
+ target.orbiting_balls += src
+ target.dissipate_strength = target.orbiting_balls.len
+ . = ..()
+
+/obj/effect/energy_ball/stop_orbit()
+ . = ..()
+ //Qdel handles removing from the parent ball list.
+ if (!QDELETED(src))
+ qdel(src)
+
/proc/tesla_zap(atom/source, zap_range = 3, power, tesla_flags = TESLA_DEFAULT_FLAGS, list/shocked_targets)
. = source.dir
if(power < 1000)
@@ -205,8 +216,9 @@
/obj/machinery/the_singularitygen/tesla,
/obj/structure/frame/machine))
- for(var/A in typecache_filter_multi_list_exclusion(oview(source, zap_range+2), things_to_shock, blacklisted_tesla_types))
- if(!(tesla_flags & TESLA_ALLOW_DUPLICATES) && LAZYACCESS(shocked_targets, A))
+ for(var/atom/A as() in oview(zap_range+2, source))
+ //typecache_filter_multi_list_exclusion has been inlined to minimize lag.
+ if(!things_to_shock[A.type] || blacklisted_tesla_types[A.type] || (!(tesla_flags & TESLA_ALLOW_DUPLICATES) && LAZYACCESS(shocked_targets, A)))
continue
if(istype(A, /obj/machinery/power/tesla_coil))
@@ -253,7 +265,7 @@
closest_atom = A
closest_dist = dist
- else if(closest_mob)
+ else if(closest_machine)
continue
else if(istype(A, /obj/structure/blob))
@@ -311,3 +323,5 @@
else if(closest_structure)
closest_structure.tesla_act(power, tesla_flags, shocked_targets)
+
+#undef TESLA_MAX_BALLS
diff --git a/code/modules/power/turbine.dm b/code/modules/power/turbine.dm
index b54db0e152e65..2964005bde32e 100644
--- a/code/modules/power/turbine.dm
+++ b/code/modules/power/turbine.dm
@@ -135,13 +135,11 @@
cut_overlays()
rpm = 0.9* rpm + 0.1 * rpmtarget
- var/datum/gas_mixture/environment = inturf.return_air()
// It's a simplified version taking only 1/10 of the moles from the turf nearby. It should be later changed into a better version
+ // above todo 7 years and counting
- var/transfer_moles = environment.total_moles()/10
- var/datum/gas_mixture/removed = inturf.remove_air(transfer_moles)
- gas_contained.merge(removed)
+ inturf.transfer_air_ratio(gas_contained, 0.1)
// RPM function to include compression friction - be advised that too low/high of a compfriction value can make things screwy
@@ -190,7 +188,7 @@
/obj/machinery/power/turbine/examine(mob/user)
. = ..()
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Productivity at [productivity*100]%."
+ . += "The status display reads: Productivity at [productivity*100]%."
/obj/machinery/power/turbine/locate_machinery()
if(compressor)
@@ -231,8 +229,7 @@
if(destroy_output)
compressor.gas_contained.set_moles(compressor.gas_contained.get_moles() - oamount)
else
- var/datum/gas_mixture/removed = compressor.gas_contained.remove(oamount)
- outturf.assume_air(removed)
+ outturf.assume_air_moles(compressor.gas_contained, oamount)
// If it works, put an overlay that it works!
@@ -266,6 +263,7 @@
if(!ui)
ui = new(user, src, "TurbineComputer")
ui.open()
+ ui.set_autoupdate(TRUE) // Turbine stats (power, RPM, temperature)
/obj/machinery/power/turbine/ui_data(mob/user)
var/list/data = list()
@@ -322,8 +320,8 @@
if(C.comp_id == id)
compressor = C
return
- else
- compressor = locate(/obj/machinery/power/compressor) in range(7, src)
+ // Couldn't find compressor, time to do search indiscriminately
+ compressor = locate(/obj/machinery/power/compressor) in range(7, src)
/obj/machinery/computer/turbine_computer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
@@ -331,6 +329,7 @@
if(!ui)
ui = new(user, src, "TurbineComputer")
ui.open()
+ ui.set_autoupdate(TRUE) // Turbine stats (power, RPM, temperature)
/obj/machinery/computer/turbine_computer/ui_data(mob/user)
var/list/data = list()
diff --git a/code/modules/procedural_mapping/mapGeneratorModule.dm b/code/modules/procedural_mapping/mapGeneratorModule.dm
index 3a78d8385ed4d..bcde26732caac 100644
--- a/code/modules/procedural_mapping/mapGeneratorModule.dm
+++ b/code/modules/procedural_mapping/mapGeneratorModule.dm
@@ -42,7 +42,7 @@
//You're the same as me? I hate you I'm going home
if(clusterCheckFlags & CLUSTER_CHECK_SAME_TURFS)
clustering = rand(clusterMin,clusterMax)
- for(var/turf/F in RANGE_TURFS(clustering,T))
+ for(var/turf/F as() in RANGE_TURFS(clustering,T))
if(istype(F,turfPath))
skipLoopIteration = TRUE
break
@@ -53,7 +53,7 @@
//You're DIFFERENT to me? I hate you I'm going home
if(clusterCheckFlags & CLUSTER_CHECK_DIFFERENT_TURFS)
clustering = rand(clusterMin,clusterMax)
- for(var/turf/F in RANGE_TURFS(clustering,T))
+ for(var/turf/F as() in RANGE_TURFS(clustering,T))
if(!(istype(F,turfPath)))
skipLoopIteration = TRUE
break
diff --git a/code/modules/procedural_mapping/mapGeneratorModules/helpers.dm b/code/modules/procedural_mapping/mapGeneratorModules/helpers.dm
index 00dba2d000146..18607b919aba4 100644
--- a/code/modules/procedural_mapping/mapGeneratorModules/helpers.dm
+++ b/code/modules/procedural_mapping/mapGeneratorModules/helpers.dm
@@ -10,12 +10,13 @@
if(!mother)
return
var/list/map = mother.map
- for(var/turf/T in map)
- SSair.remove_from_active(T)
for(var/turf/open/T in map)
if(T.air)
- T.air.copy_from_turf(T)
- SSair.add_to_active(T)
+ if(T.initial_gas_mix)
+ T.air.parse_gas_string(T.initial_gas_mix)
+ T.set_temperature(T.air.return_temperature())
+ else
+ T.air.copy_from_turf(T)
/datum/mapGeneratorModule/bottomLayer/massdelete
spawnableAtoms = list()
diff --git a/code/modules/projectiles/ammunition/_ammunition.dm b/code/modules/projectiles/ammunition/_ammunition.dm
index 14877e3ac7d42..14e1ceeecfcee 100644
--- a/code/modules/projectiles/ammunition/_ammunition.dm
+++ b/code/modules/projectiles/ammunition/_ammunition.dm
@@ -19,6 +19,8 @@
var/firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect //the visual effect appearing when the ammo is fired.
var/heavy_metal = TRUE
var/harmful = TRUE //pacifism check for boolet, set to FALSE if bullet is non-lethal
+ var/click_cooldown_override = 0
+ var/exists = TRUE
/obj/item/ammo_casing/spent
name = "spent bullet casing"
@@ -33,6 +35,13 @@
setDir(pick(GLOB.alldirs))
update_icon()
+/obj/item/ammo_casing/Destroy()
+ var/turf/T = get_turf(src)
+ if(T && !BB && is_station_level(T.z))
+ SSblackbox.record_feedback("tally", "station_mess_destroyed", 1, name)
+ QDEL_NULL(BB)
+ return ..()
+
/obj/item/ammo_casing/update_icon()
..()
icon_state = "[initial(icon_state)][BB ? "-live" : ""]"
diff --git a/code/modules/projectiles/ammunition/_firing.dm b/code/modules/projectiles/ammunition/_firing.dm
index f01aaa0581add..93082748f1d88 100644
--- a/code/modules/projectiles/ammunition/_firing.dm
+++ b/code/modules/projectiles/ammunition/_firing.dm
@@ -1,18 +1,26 @@
-/obj/item/ammo_casing/proc/fire_casing(atom/target, mob/living/user, params, distro, quiet, zone_override, spread, atom/fired_from)
+/obj/item/ammo_casing/proc/fire_casing(atom/target, mob/living/user, params, distro, quiet, zone_override, spread, spread_mult = 1, atom/fired_from)
distro += variance
- for (var/i = max(1, pellets), i > 0, i--)
- var/targloc = get_turf(target)
- ready_proj(target, user, quiet, zone_override, fired_from)
+ var/targloc = get_turf(target)
+ ready_proj(target, user, quiet, zone_override, fired_from)
+ if(pellets == 1)
if(distro) //We have to spread a pixel-precision bullet. throw_proj was called before so angles should exist by now...
if(randomspread)
- spread = round((rand() - 0.5) * distro)
+ spread = round((rand() - 0.5) * distro) * spread_mult
else //Smart spread
- spread = round((i / pellets - 0.5) * distro)
+ spread = round(1 - 0.5) * distro * spread_mult
if(!throw_proj(target, targloc, user, params, spread))
- return 0
- if(i > 1)
- newshot()
- user.newtonian_move(get_dir(target, user))
+ return FALSE
+ else
+ if(isnull(BB))
+ return FALSE
+ AddComponent(/datum/component/pellet_cloud, projectile_type, pellets)
+ SEND_SIGNAL(src, COMSIG_PELLET_CLOUD_INIT, target, user, fired_from, randomspread, spread, zone_override, params, distro)
+ if(click_cooldown_override)
+ user.changeNext_move(click_cooldown_override)
+ else
+ user.changeNext_move(CLICK_CD_RANGE)
+ if(exists)
+ user.newtonian_move(get_dir(target, user))
update_icon()
return TRUE
@@ -57,3 +65,21 @@
var/dx = abs(target.x - current.x)
var/dy = abs(target.y - current.y)
return locate(target.x + round(gaussian(0, distro) * (dy+2)/8, 1), target.y + round(gaussian(0, distro) * (dx+2)/8, 1), target.z)
+
+/obj/item/ammo_casing/screwdriver_act(mob/living/user, obj/item/I)
+ user.visible_message("[user] hits the [src]'s primer with [user.p_their()] [I]!")
+ if(!user.is_holding(src))
+ to_chat(user, "You need to pickup \the [src] first!")
+ return
+ if(prob(75))
+ fire_casing(get_step(src, user.dir), user, spread = rand(-40, 40))
+ if(iscarbon(user))
+ var/mob/living/carbon/C = user
+ var/obj/item/bodypart/affecting = C.get_holding_bodypart_of_item(src)
+ C.apply_damage(rand(5, 10), BRUTE, affecting)
+ else
+ user.visible_message("[user]'s [I] slips!")
+ fire_casing(user, user)
+
+/obj/item/ammo_casing/caseless/screwdriver_act(mob/living/user, /obj/item/I)
+ return // No launching arrows with screwdrivers!
diff --git a/code/modules/projectiles/ammunition/ballistic/lmg.dm b/code/modules/projectiles/ammunition/ballistic/lmg.dm
index 54287eea62b8f..4f3ea9c624f85 100644
--- a/code/modules/projectiles/ammunition/ballistic/lmg.dm
+++ b/code/modules/projectiles/ammunition/ballistic/lmg.dm
@@ -21,3 +21,8 @@
name = "7.12x82mm incendiary bullet casing"
desc = "A 7.12x82mm bullet casing designed with a chemical-filled capsule on the tip that when bursted, reacts with the atmosphere to produce a fireball, engulfing the target in flames."
projectile_type = /obj/item/projectile/bullet/incendiary/mm712x82
+
+/obj/item/ammo_casing/mm712x82/match
+ name = "7.12x82mm match bullet casing"
+ desc = "A 7.12x82mm bullet casing manufactured to unfailingly high standards, you could pull off some cool trickshots with this."
+ projectile_type = /obj/item/projectile/bullet/mm712x82_match
diff --git a/code/modules/projectiles/ammunition/ballistic/revolver.dm b/code/modules/projectiles/ammunition/ballistic/revolver.dm
index 70afc7d4a8766..666993bb8a3a2 100644
--- a/code/modules/projectiles/ammunition/ballistic/revolver.dm
+++ b/code/modules/projectiles/ammunition/ballistic/revolver.dm
@@ -6,6 +6,12 @@
caliber = "357"
projectile_type = /obj/item/projectile/bullet/a357
+/obj/item/ammo_casing/a357/match
+ name = ".357 match bullet casing"
+ desc = "A .357 bullet casing, manufactured to exceedingly high standards."
+ caliber = "357"
+ projectile_type = /obj/item/projectile/bullet/a357/match
+
// 7.62x38mmR (Nagant Revolver)
/obj/item/ammo_casing/n762
@@ -39,13 +45,28 @@
caliber = "38"
projectile_type = /obj/item/projectile/bullet/c38/iceblox
+/obj/item/ammo_casing/c38/match
+ name = ".38 Match bullet casing"
+ desc = "A .38 bullet casing, manufactured to exceedingly high standards."
+ projectile_type = /obj/item/projectile/bullet/c38/match
+
+/obj/item/ammo_casing/c38/match/bouncy
+ name = ".38 Rubber bullet casing"
+ desc = "A .38 rubber bullet casing, manufactured to exceedingly high standards."
+ projectile_type = /obj/item/projectile/bullet/c38/match/bouncy
+
+/obj/item/ammo_casing/c38/dumdum
+ name = ".38 DumDum bullet casing"
+ desc = "A .38 DumDum bullet casing."
+ projectile_type = /obj/item/projectile/bullet/c38/dumdum
+
/obj/item/ammo_casing/caseless/mime
name = "invisible .38 bullet casing"
icon_state = null
- desc = "You shouldnt be seeing this."
+ desc = "You shouldn't be seeing this."
caliber = "mime"
projectile_type = /obj/item/projectile/bullet/c38/mime
+ exists = FALSE
-/obj/item/ammo_casing/caseless/mime/lethals
- projectile_type = /obj/item/projectile/bullet/c38
-
+/obj/item/ammo_casing/caseless/mime/lethal
+ projectile_type = /obj/item/projectile/bullet/c38/mime_lethal
diff --git a/code/modules/projectiles/ammunition/ballistic/shotgun.dm b/code/modules/projectiles/ammunition/ballistic/shotgun.dm
index 824c4bc9f8d1c..e95385f5cb206 100644
--- a/code/modules/projectiles/ammunition/ballistic/shotgun.dm
+++ b/code/modules/projectiles/ammunition/ballistic/shotgun.dm
@@ -17,7 +17,7 @@
/obj/item/ammo_casing/shotgun/sleepytime
name = "soporific shell"
- desc = "A shotgun shell loaded with a hypodermic needle containing a low strength sleeping agent."
+ desc = "A shotgun shell loaded with a hypodermic needle containing a low strength knock-out agent that will confuse a target on the first shot, and put them to sleep on the second."
icon_state = "sleepy"
projectile_type = /obj/item/projectile/bullet/sleepy
@@ -68,7 +68,7 @@
icon_state = "gshell"
projectile_type = /obj/item/projectile/bullet/pellet/shotgun_buckshot
pellets = 6
- variance = 25
+ variance = 10
/obj/item/ammo_casing/shotgun/rubbershot
name = "rubber shot"
@@ -76,7 +76,7 @@
icon_state = "bshell"
projectile_type = /obj/item/projectile/bullet/pellet/shotgun_rubbershot
pellets = 6
- variance = 25
+ variance = 20
materials = list(/datum/material/iron=4000)
/obj/item/ammo_casing/shotgun/incapacitate
@@ -85,7 +85,7 @@
icon_state = "bountyshell"
projectile_type = /obj/item/projectile/bullet/pellet/shotgun_incapacitate
pellets = 12//double the pellets, but half the stun power of each, which makes this best for just dumping right in someone's face.
- variance = 25
+ variance = 20
materials = list(/datum/material/iron=4000)
/obj/item/ammo_casing/shotgun/improvised
@@ -160,4 +160,4 @@
desc = "A 12 gauge anti-material slug. Great for breaching airlocks and windows with minimal shots."
icon_state = "breacher"
projectile_type = /obj/item/projectile/bullet/shotgun_breaching
- materials = list(/datum/material/iron=4000)
\ No newline at end of file
+ materials = list(/datum/material/iron=4000)
diff --git a/code/modules/projectiles/ammunition/ballistic/smg.dm b/code/modules/projectiles/ammunition/ballistic/smg.dm
index 84a5d83ba17ee..1233b52acc2fa 100644
--- a/code/modules/projectiles/ammunition/ballistic/smg.dm
+++ b/code/modules/projectiles/ammunition/ballistic/smg.dm
@@ -16,6 +16,12 @@
desc = "A 4.6x30mm incendiary bullet casing."
projectile_type = /obj/item/projectile/bullet/incendiary/c46x30mm
+/obj/item/ammo_casing/c46x30mm/rubber
+ name = "4.6x30mm bullet casing"
+ desc = "A 4.6x30mm bullet casing."
+ caliber = "4.6x30mm"
+ projectile_type = /obj/item/projectile/bullet/c46x30mm_rubber
+
// .45 (M1911 + C20r)
/obj/item/ammo_casing/c45
diff --git a/code/modules/projectiles/ammunition/caseless/_caseless.dm b/code/modules/projectiles/ammunition/caseless/_caseless.dm
index db1aa6562c468..aee2570f91035 100644
--- a/code/modules/projectiles/ammunition/caseless/_caseless.dm
+++ b/code/modules/projectiles/ammunition/caseless/_caseless.dm
@@ -3,7 +3,7 @@
firing_effect_type = null
heavy_metal = FALSE
-/obj/item/ammo_casing/caseless/fire_casing(atom/target, mob/living/user, params, distro, quiet, zone_override, spread, atom/fired_from)
+/obj/item/ammo_casing/caseless/fire_casing(atom/target, mob/living/user, params, distro, quiet, zone_override, spread, spread_multiplier = 1, atom/fired_from)
if (..()) //successfully firing
moveToNullspace()
QDEL_NULL(src)
diff --git a/code/modules/projectiles/ammunition/caseless/arrow.dm b/code/modules/projectiles/ammunition/caseless/arrow.dm
index 04769f88ece28..47090d090ed75 100644
--- a/code/modules/projectiles/ammunition/caseless/arrow.dm
+++ b/code/modules/projectiles/ammunition/caseless/arrow.dm
@@ -1,6 +1,6 @@
/obj/item/ammo_casing/caseless/arrow
name = "arrow of questionable material"
- desc = "You shouldnt be seeing this arrow"
+ desc = "You shouldn't be seeing this arrow"
projectile_type = /obj/item/projectile/bullet/reusable/arrow
caliber = "arrow"
icon_state = "arrow"
diff --git a/code/modules/projectiles/ammunition/caseless/misc.dm b/code/modules/projectiles/ammunition/caseless/misc.dm
index fc39c9599624c..1f044176f3bc4 100644
--- a/code/modules/projectiles/ammunition/caseless/misc.dm
+++ b/code/modules/projectiles/ammunition/caseless/misc.dm
@@ -10,3 +10,4 @@
/obj/item/ammo_casing/caseless/laser/gatling
projectile_type = /obj/item/projectile/beam/weak/penetrator
variance = 0.8
+ click_cooldown_override = 1
diff --git a/code/modules/projectiles/ammunition/energy/laser.dm b/code/modules/projectiles/ammunition/energy/laser.dm
index 565f9928bab42..a30cfb5fb2601 100644
--- a/code/modules/projectiles/ammunition/energy/laser.dm
+++ b/code/modules/projectiles/ammunition/energy/laser.dm
@@ -2,9 +2,12 @@
projectile_type = /obj/item/projectile/beam/laser
select_name = "kill"
+/obj/item/ammo_casing/energy/laser/gatlinggun
+ e_cost = 1
+
/obj/item/ammo_casing/energy/lasergun
projectile_type = /obj/item/projectile/beam/laser
- e_cost = 83
+ e_cost = 71
select_name = "kill"
/obj/item/ammo_casing/energy/lasergun/old
diff --git a/code/modules/projectiles/ammunition/energy/portal.dm b/code/modules/projectiles/ammunition/energy/portal.dm
index ec1f067080d2a..3d560158a7acb 100644
--- a/code/modules/projectiles/ammunition/energy/portal.dm
+++ b/code/modules/projectiles/ammunition/energy/portal.dm
@@ -4,7 +4,8 @@
harmful = FALSE
fire_sound = 'sound/weapons/pulse3.ogg'
select_name = "blue"
- var/obj/item/gun/energy/wormhole_projector/gun
+ //Weakref to the gun that shot us
+ var/datum/weakref/gun
/obj/item/ammo_casing/energy/wormhole/orange
projectile_type = /obj/item/projectile/beam/wormhole/orange
@@ -12,7 +13,7 @@
/obj/item/ammo_casing/energy/wormhole/Initialize(mapload, obj/item/gun/energy/wormhole_projector/wh)
. = ..()
- gun = wh
+ gun = WEAKREF(wh)
/obj/item/ammo_casing/energy/wormhole/throw_proj()
. = ..()
diff --git a/code/modules/projectiles/ammunition/special/vortex.dm b/code/modules/projectiles/ammunition/special/vortex.dm
new file mode 100644
index 0000000000000..01165cdcedd8a
--- /dev/null
+++ b/code/modules/projectiles/ammunition/special/vortex.dm
@@ -0,0 +1,8 @@
+/obj/item/ammo_casing/energy/vortex
+ name = "vortex blast"
+ desc = "what the hell?"
+ projectile_type = /obj/item/projectile/energy/vortex
+ firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/energy
+ heavy_metal = FALSE
+ e_cost = 100
+ select_name = "vortex blast"
diff --git a/code/modules/projectiles/autofire.dm b/code/modules/projectiles/autofire.dm
index 5371d5401d165..e269af4f9e69a 100644
--- a/code/modules/projectiles/autofire.dm
+++ b/code/modules/projectiles/autofire.dm
@@ -40,7 +40,7 @@ Everything else should be handled for you. Good luck soldier.
/obj/item/gun/ballistic/automatic/c20r
full_auto = TRUE
-/obj/item/gun/ballistic/minigun
+/obj/item/gun/energy/minigun
full_auto = TRUE
/obj/item/gun/ballistic/automatic/laser/ctf
@@ -87,6 +87,8 @@ Everything else should be handled for you. Good luck soldier.
START_PROCESSING(SSfastprocess, src) //Target acquired. Begin the spam. If we're already processing this is just ignored (see _DEFINES/MC.dm)
/datum/component/full_auto/proc/unset_target()
+ SIGNAL_HANDLER
+
autofire_target = null
next_process = world.time + melee_attack_delay //So you can't abuse this to magdump.
@@ -106,7 +108,10 @@ Everything else should be handled for you. Good luck soldier.
next_process = world.time + default_fire_delay
if(L.Adjacent(autofire_target)) //Melee attack? Or ranged attack?
next_process = world.time + melee_attack_delay
- G.attack(autofire_target, L)
+ if(isobj(autofire_target))
+ G.attack_obj(autofire_target, L)
+ else
+ G.attack(autofire_target, L)
else
G.afterattack(autofire_target,L)
diff --git a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
index 1ff5681ef63b5..4b9534ea73b54 100644
--- a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
+++ b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
@@ -6,6 +6,11 @@
max_ammo = 7
multiple_sprites = 1
+/obj/item/ammo_box/a357/match
+ name = "speed loader (.357 Match)"
+ desc = "Designed to quickly reload revolvers. These rounds are manufactured within extremely tight tolerances, making them easy to show off trickshots with."
+ ammo_type = /obj/item/ammo_casing/a357/match
+
/obj/item/ammo_box/c38
name = "speed loader (.38)"
desc = "Designed to quickly reload revolvers."
@@ -20,6 +25,21 @@
desc = "Designed to quickly reload revolvers. TRAC bullets embed a tracking implant within the target's body."
ammo_type = /obj/item/ammo_casing/c38/trac
+/obj/item/ammo_box/c38/match
+ name = "speed loader (.38 Match)"
+ desc = "Designed to quickly reload revolvers. These rounds are manufactured within extremely tight tolerances, making them easy to show off trickshots with."
+ ammo_type = /obj/item/ammo_casing/c38/match
+
+/obj/item/ammo_box/c38/match/bouncy
+ name = "speed loader (.38 Rubber)"
+ desc = "Designed to quickly reload revolvers. These rounds are incredibly bouncy and MOSTLY nonlethal, making them great to show off trickshots with."
+ ammo_type = /obj/item/ammo_casing/c38/match/bouncy
+
+/obj/item/ammo_box/c38/dumdum
+ name = "speed loader (.38 DumDum)"
+ desc = "Designed to quickly reload revolvers. DumDum bullets shatter on impact and shred the target's innards, likely getting caught inside."
+ ammo_type = /obj/item/ammo_casing/c38/dumdum
+
/obj/item/ammo_box/c38/hotshot
name = "speed loader (.38 Hot Shot)"
desc = "Designed to quickly reload revolvers. Hot Shot bullets contain an incendiary payload."
@@ -35,7 +55,7 @@
max_ammo = 6
desc = "Designed to quickly reload your fingers with lethal rounds."
item_flags = DROPDEL
- ammo_type = /obj/item/ammo_casing/caseless/mime/lethals
+ ammo_type = /obj/item/ammo_casing/caseless/mime/lethal
/obj/item/ammo_box/c9mm
name = "ammo box (9mm)"
diff --git a/code/modules/projectiles/boxes_magazines/external/lmg.dm b/code/modules/projectiles/boxes_magazines/external/lmg.dm
index 2b192f598cb56..11d18783bef8c 100644
--- a/code/modules/projectiles/boxes_magazines/external/lmg.dm
+++ b/code/modules/projectiles/boxes_magazines/external/lmg.dm
@@ -20,3 +20,7 @@
/obj/item/ammo_box/magazine/mm712x82/update_icon()
..()
icon_state = "a762-[round(ammo_count(),10)]"
+
+/obj/item/ammo_box/magazine/mm712x82/match
+ name = "box magazine (Match 7.12x82mm)"
+ ammo_type = /obj/item/ammo_casing/mm712x82/match
diff --git a/code/modules/projectiles/boxes_magazines/external/smg.dm b/code/modules/projectiles/boxes_magazines/external/smg.dm
index 291ec47fa82d4..7880f3f1e449a 100644
--- a/code/modules/projectiles/boxes_magazines/external/smg.dm
+++ b/code/modules/projectiles/boxes_magazines/external/smg.dm
@@ -27,6 +27,11 @@
..()
icon_state = "46x30mmtI-[round(ammo_count(),4)]"
+/obj/item/ammo_box/magazine/wt550m9/rubber
+ name = "wt550 rubber magazine (4.6x30mm rubber)"
+ icon_state = "46x30mmt-20"
+ ammo_type = /obj/item/ammo_casing/c46x30mm/rubber
+
/obj/item/ammo_box/magazine/uzim9mm
name = "uzi magazine (9mm)"
icon_state = "uzi9mm-32"
diff --git a/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm b/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm
index eb244a7ee681a..cc14f753e4f64 100644
--- a/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm
+++ b/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm
@@ -52,4 +52,8 @@
name = "fingergun cylinder"
ammo_type = /obj/item/ammo_casing/caseless/mime
caliber = "mime"
- max_ammo = 6
\ No newline at end of file
+ max_ammo = 6
+
+/obj/item/ammo_box/magazine/internal/cylinder/mime/lethal
+ ammo_type = /obj/item/ammo_casing/caseless/mime/lethal
+ max_ammo = 3 //Because that's how many this is supposed to have from what I gather
diff --git a/code/modules/projectiles/boxes_magazines/internal/misc.dm b/code/modules/projectiles/boxes_magazines/internal/misc.dm
deleted file mode 100644
index d723c964db4cc..0000000000000
--- a/code/modules/projectiles/boxes_magazines/internal/misc.dm
+++ /dev/null
@@ -1,11 +0,0 @@
-/obj/item/ammo_box/magazine/internal/minigun
- name = "gatling gun fusion core"
- ammo_type = /obj/item/ammo_casing/caseless/laser/gatling
- caliber = "gatling"
- max_ammo = 5000
-
-/obj/item/ammo_box/magazine/internal/hook
- name = "hook internal tube"
- ammo_type = /obj/item/ammo_casing/magic/hook
- caliber = "hook"
- max_ammo = 1
diff --git a/code/modules/projectiles/boxes_magazines/internal/shotgun.dm b/code/modules/projectiles/boxes_magazines/internal/shotgun.dm
index 14e3f54ab6abe..e9988ced31c67 100644
--- a/code/modules/projectiles/boxes_magazines/internal/shotgun.dm
+++ b/code/modules/projectiles/boxes_magazines/internal/shotgun.dm
@@ -45,4 +45,9 @@
/obj/item/ammo_box/magazine/internal/shot/breaching
name = "breaching shotgun internal magazine"
ammo_type = /obj/item/ammo_casing/shotgun/breacher
- max_ammo = 3
\ No newline at end of file
+ max_ammo = 3
+
+/obj/item/ammo_box/magazine/internal/shot/lever
+ name = "lever action shotgun internal magazine"
+ ammo_type = /obj/item/ammo_casing/shotgun/buckshot
+ max_ammo = 5
\ No newline at end of file
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index efdd4ab542968..f4d506fcf2ee1 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -1,5 +1,6 @@
-#define DUALWIELD_PENALTY_EXTRA_MULTIPLIER 1.4
+#define DUALWIELD_PENALTY_EXTRA_MULTIPLIER 1.1
+#define FIRING_PIN_REMOVAL_DELAY 50
/obj/item/gun
name = "gun"
@@ -40,14 +41,20 @@
var/weapon_weight = WEAPON_LIGHT
var/dual_wield_spread = 24 //additional spread when dual wielding
var/spread = 0 //Spread induced by the gun itself.
+ var/spread_multiplier = 1 //Multiplier for shotgun spread
+ var/spread_unwielded //Spread induced by holding the gun with 1 hand. (40 for light weapons, 60 for medium by default)
var/randomspread = 1 //Set to 0 for shotguns. This is used for weapons that don't fire all their bullets at once.
+ var/is_wielded = FALSE
+
lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi'
var/obj/item/firing_pin/pin = /obj/item/firing_pin //standard firing pin for most guns
var/no_pin_required = FALSE //whether the gun can be fired without a pin
var/can_flashlight = FALSE //if a flashlight can be added or removed if it already has one.
+
+ //Flashlight
var/obj/item/flashlight/seclite/gun_light
var/mutable_appearance/flashlight_overlay
var/datum/action/item_action/toggle_gunlight/alight
@@ -74,6 +81,7 @@
var/atom/autofire_target = null //What are we aiming at? This will change if you move your mouse whilst spraying.
var/next_autofire = 0 //As to stop mag dumps, Whoops!
var/pb_knockback = 0
+ var/ranged_cooldown = 0
/obj/item/gun/Initialize()
. = ..()
@@ -87,6 +95,21 @@
if(!canMouseDown) //Some things like beam rifles override this.
canMouseDown = automatic //Nsv13 / Bee change.
build_zooming()
+ if(!spread_unwielded)
+ spread_unwielded = weapon_weight * 20 + 20
+ RegisterSignal(src, COMSIG_TWOHANDED_WIELD, .proc/wield)
+ RegisterSignal(src, COMSIG_TWOHANDED_UNWIELD, .proc/unwield)
+
+/obj/item/gun/ComponentInitialize()
+ . = ..()
+ //Smaller weapons are better when used in a single hand.
+ AddComponent(/datum/component/two_handed, unwield_on_swap = TRUE, auto_wield = TRUE, ignore_attack_self = TRUE, force_wielded = force, force_unwielded = force, block_power_wielded = block_power, block_power_unwielded = block_power, wieldsound = 'sound/effects/suitstep1.ogg', unwieldsound = 'sound/effects/suitstep2.ogg')
+
+/obj/item/gun/proc/wield()
+ is_wielded = TRUE
+
+/obj/item/gun/proc/unwield()
+ is_wielded = FALSE
/obj/item/gun/Destroy()
if(isobj(pin)) //Can still be the initial path, then we skip
@@ -99,6 +122,7 @@
QDEL_NULL(chambered)
if(azoom)
QDEL_NULL(azoom)
+ UnregisterSignal(list(COMSIG_TWOHANDED_WIELD, COMSIG_TWOHANDED_UNWIELD))
return ..()
/obj/item/gun/handle_atom_del(atom/A)
@@ -113,21 +137,14 @@
clear_gunlight()
return ..()
-/obj/item/gun/CheckParts(list/parts_list)
- ..()
- var/obj/item/gun/G = locate(/obj/item/gun) in contents
- if(G)
- G.forceMove(loc)
- QDEL_NULL(G.pin)
- visible_message("[G] can now fit a new pin, but the old one was destroyed in the process.", null, null, 3)
- qdel(src)
-
/obj/item/gun/examine(mob/user)
. = ..()
if(no_pin_required)
return
+
if(pin)
. += "It has \a [pin] installed."
+ . += "[pin] looks like it could be removed with some tools."
else
. += "It doesn't have a firing pin installed, and won't fire."
@@ -160,7 +177,7 @@
return TRUE
/obj/item/gun/proc/shoot_with_empty_chamber(mob/living/user as mob|obj)
- to_chat(user, "*click*")
+ balloon_alert(user, "Gun clicks")
playsound(src, dry_fire_sound, 30, TRUE)
@@ -221,6 +238,8 @@
shoot_with_empty_chamber(user)
return
+ if (ranged_cooldown>world.time)
+ return
//Exclude lasertag guns from the TRAIT_CLUMSY check.
if(clumsy_check)
if(istype(user))
@@ -231,8 +250,9 @@
user.dropItemToGround(src, TRUE)
return
- if(weapon_weight == WEAPON_HEAVY && user.get_inactive_held_item())
- to_chat(user, "You need both hands free to fire \the [src]!")
+ var/obj/item/bodypart/other_hand = user.has_hand_for_held_index(user.get_inactive_hand_index()) //returns non-disabled inactive hands
+ if(weapon_weight == WEAPON_HEAVY && (!istype(user.get_inactive_held_item(), /obj/item/offhand) || !other_hand))
+ balloon_alert(user, "You need both hands free to fire")
return
//DUAL (or more!) WIELDING
@@ -250,8 +270,6 @@
process_fire(target, user, TRUE, params, null, bonus_spread)
-
-
/obj/item/gun/can_trigger_gun(mob/living/user)
. = ..()
if(!handle_pins(user))
@@ -267,7 +285,7 @@
pin.auth_fail(user)
return FALSE
else
- to_chat(user, "[src]'s trigger is locked. This weapon doesn't have a firing pin installed!")
+ balloon_alert(user, "No firing pin installed")
return FALSE
/obj/item/gun/proc/recharge_newshot()
@@ -291,7 +309,7 @@
else //Smart spread
sprd = round((((rand_spr/burst_size) * iteration) - (0.5 + (rand_spr * 0.25))) * (randomized_gun_spread + randomized_bonus_spread))
before_firing(target,user)
- if(!chambered.fire_casing(target, user, params, ,suppressed, zone_override, sprd, src))
+ if(!chambered.fire_casing(target, user, params, ,suppressed, zone_override, sprd, spread_multiplier, src))
shoot_with_empty_chamber(user)
firing_burst = FALSE
return FALSE
@@ -313,9 +331,9 @@
/obj/item/gun/proc/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
add_fingerprint(user)
if(fire_rate)
- user.changeNext_move(10 / fire_rate)
+ ranged_cooldown = world.time + 10 / fire_rate
else
- user.changeNext_move(CLICK_CD_RANGE)
+ ranged_cooldown = world.time + CLICK_CD_RANGE
if(semicd)
return
@@ -326,6 +344,8 @@
randomized_gun_spread = rand(0,spread)
if(HAS_TRAIT(user, TRAIT_POOR_AIM)) //nice shootin' tex
bonus_spread += 25
+ if(!is_wielded)
+ bonus_spread += spread_unwielded
var/randomized_bonus_spread = rand(0, bonus_spread)
if(burst_size > 1)
@@ -340,7 +360,7 @@
return
sprd = round((rand() - 0.5) * DUALWIELD_PENALTY_EXTRA_MULTIPLIER * (randomized_gun_spread + randomized_bonus_spread))
before_firing(target,user)
- if(!chambered.fire_casing(target, user, params, , suppressed, zone_override, sprd, src))
+ if(!chambered.fire_casing(target, user, params, , suppressed, zone_override, sprd, spread_multiplier, src))
shoot_with_empty_chamber(user)
return
else
@@ -394,7 +414,7 @@
if(!gun_light)
if(!user.transferItemToLoc(I, src))
return
- to_chat(user, "You click [S] into place on [src].")
+ balloon_alert(user, "[S] attached")
if(S.on)
set_light(0)
gun_light = S
@@ -408,7 +428,7 @@
return ..()
if(!user.transferItemToLoc(I, src))
return
- to_chat(user, "You attach [K] to [src]'s bayonet lug.")
+ balloon_alert(user, "[K] attached to [src]")
bayonet = K
var/state = "bayonet" //Generic state.
if(bayonet.icon_state in icon_states('icons/obj/guns/bayonets.dmi')) //Snowflake state?
@@ -440,10 +460,56 @@
else if(bayonet && can_bayonet) //if it has a bayonet, and the bayonet can be removed
return remove_gun_attachment(user, I, bayonet, "unfix")
+ else if(pin && user.is_holding(src))
+ user.visible_message("[user] attempts to remove [pin] from [src] with [I].",
+ "You attempt to remove [pin] from [src]. (It will take [DisplayTimeText(FIRING_PIN_REMOVAL_DELAY)].)", null, 3)
+ if(I.use_tool(src, user, FIRING_PIN_REMOVAL_DELAY, volume = 50))
+ if(!pin) //check to see if the pin is still there, or we can spam messages by clicking multiple times during the tool delay
+ return
+ user.visible_message("[pin] was pried out of [src] by [user], destroying the pin in the process.",
+ "You pried [pin] out with [I], destroying the pin in the process.", null, 3)
+ QDEL_NULL(pin)
+ return TRUE
+
+
+/obj/item/gun/welder_act(mob/living/user, obj/item/I)
+ . = ..()
+ if(.)
+ return
+ if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
+ return
+ if(pin && user.is_holding(src))
+ user.visible_message("[user] attempts to remove [pin] from [src] with [I].",
+ "You attempt to remove [pin] from [src]. (It will take [DisplayTimeText(FIRING_PIN_REMOVAL_DELAY)].)", null, 3)
+ if(I.use_tool(src, user, FIRING_PIN_REMOVAL_DELAY, 5, volume = 50))
+ if(!pin) //check to see if the pin is still there, or we can spam messages by clicking multiple times during the tool delay
+ return
+ user.visible_message("[pin] was spliced out of [src] by [user], melting part of the pin in the process.",
+ "You spliced [pin] out of [src] with [I], melting part of the pin in the process.", null, 3)
+ QDEL_NULL(pin)
+ return TRUE
+
+/obj/item/gun/wirecutter_act(mob/living/user, obj/item/I)
+ . = ..()
+ if(.)
+ return
+ if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
+ return
+ if(pin && user.is_holding(src))
+ user.visible_message("[user] attempts to remove [pin] from [src] with [I].",
+ "You attempt to remove [pin] from [src]. (It will take [DisplayTimeText(FIRING_PIN_REMOVAL_DELAY)].)", null, 3)
+ if(I.use_tool(src, user, FIRING_PIN_REMOVAL_DELAY, volume = 50))
+ if(!pin) //check to see if the pin is still there, or we can spam messages by clicking multiple times during the tool delay
+ return
+ user.visible_message("[pin] was ripped out of [src] by [user], mangling the pin in the process.",
+ "You ripped [pin] out of [src] with [I], mangling the pin in the process.", null, 3)
+ QDEL_NULL(pin)
+ return TRUE
+
/obj/item/gun/proc/remove_gun_attachment(mob/living/user, obj/item/tool_item, obj/item/item_to_remove, removal_verb)
if(tool_item)
tool_item.play_tool_sound(src)
- to_chat(user, "You [removal_verb ? removal_verb : "remove"] [item_to_remove] from [src].")
+ balloon_alert(user, "[item_to_remove] removed")
item_to_remove.forceMove(drop_location())
if(Adjacent(user) && !issilicon(user))
@@ -485,7 +551,7 @@
var/mob/living/carbon/human/user = usr
gun_light.on = !gun_light.on
- to_chat(user, "You toggle the gunlight [gun_light.on ? "on":"off"].")
+ balloon_alert(user, "Flashlight [gun_light.on ? "on":"off"]")
playsound(user, 'sound/weapons/empty.ogg', 100, TRUE)
update_gunlight()
@@ -530,6 +596,9 @@
if(!ishuman(user) || !ishuman(target))
return
+ if(HAS_TRAIT(user, TRAIT_PACIFISM)) //This prevents multiplying projectile damage without shooting yourself.
+ return
+
if(semicd)
return
@@ -593,6 +662,8 @@
..()
/obj/item/gun/proc/rotate(atom/thing, old_dir, new_dir)
+ SIGNAL_HANDLER
+
if(ismob(thing))
var/mob/lad = thing
lad.client.view_size.zoomOut(zoom_out_amt, zoom_amt, new_dir)
@@ -622,3 +693,6 @@
if(zoomable)
azoom = new()
azoom.gun = src
+
+#undef FIRING_PIN_REMOVAL_DELAY
+#undef DUALWIELD_PENALTY_EXTRA_MULTIPLIER
diff --git a/code/modules/projectiles/guns/ballistic.dm b/code/modules/projectiles/guns/ballistic.dm
index 69c8b3aa7e170..44dd8ef3b5fb3 100644
--- a/code/modules/projectiles/guns/ballistic.dm
+++ b/code/modules/projectiles/guns/ballistic.dm
@@ -129,15 +129,20 @@
chambered.forceMove(src)
/obj/item/gun/ballistic/proc/rack(mob/user = null)
- if (bolt_type == BOLT_TYPE_NO_BOLT) //If there's no bolt, nothing to rack
- return
- if (bolt_type == BOLT_TYPE_OPEN)
- if(!bolt_locked) //If it's an open bolt, racking again would do nothing
- if (user)
- to_chat(user, "\The [src]'s [bolt_wording] is already cocked!")
+ switch(bolt_type)
+ if(BOLT_TYPE_NO_BOLT)
return
- bolt_locked = FALSE
- if (user)
+ if(BOLT_TYPE_OPEN)
+ if(!bolt_locked) //If it's an open bolt, racking again would do nothing
+ if(user)
+ to_chat(user, "\The [src]'s [bolt_wording] is already cocked!")
+ return
+ bolt_locked = FALSE
+ if(BOLT_TYPE_PUMP)
+ if(user?.get_inactive_held_item() && !istype(user.get_inactive_held_item(), /obj/item/offhand))
+ to_chat(user, "You require your other hand to be free to rack the [bolt_wording] of \the [src]!")
+ return
+ if(user)
to_chat(user, "You rack the [bolt_wording] of \the [src].")
process_chamber(!chambered, FALSE)
if (bolt_type == BOLT_TYPE_LOCKING && !chambered)
@@ -262,7 +267,7 @@
if(suppressed && can_unsuppress)
var/obj/item/suppressor/S = suppressed
if(!user.is_holding(src))
- return ..()
+ return
to_chat(user, "You unscrew \the [suppressed] from \the [src].")
user.put_in_hands(suppressed)
w_class -= S.w_class
@@ -410,6 +415,7 @@
slot_flags |= ITEM_SLOT_BELT //but you can wear it on your belt (poorly concealed under a trenchcoat, ideally)
recoil = SAWN_OFF_RECOIL
sawn_off = TRUE
+ spread_multiplier = 1.6
update_icon()
return TRUE
diff --git a/code/modules/projectiles/guns/ballistic/automatic.dm b/code/modules/projectiles/guns/ballistic/automatic.dm
index c76f29ea035d5..75d1e1df13024 100644
--- a/code/modules/projectiles/guns/ballistic/automatic.dm
+++ b/code/modules/projectiles/guns/ballistic/automatic.dm
@@ -66,6 +66,7 @@
fire_delay = 2
burst_size = 2
pin = /obj/item/firing_pin/implant/pindicate
+ spread_unwielded = 15
can_bayonet = TRUE
knife_x_offset = 26
knife_y_offset = 12
@@ -97,6 +98,10 @@
empty_indicator = TRUE
fire_rate = 3
block_upgrade_walk = 1
+ w_class = WEIGHT_CLASS_BULKY
+
+/obj/item/gun/ballistic/automatic/wt550/rubber_loaded
+ mag_type = /obj/item/ammo_box/magazine/wt550m9/rubber
/obj/item/gun/ballistic/automatic/mini_uzi
name = "\improper Type U3 Uzi"
@@ -120,6 +125,7 @@
burst_size = 3
fire_delay = 2
pin = /obj/item/firing_pin/implant/pindicate
+ spread_unwielded = 15
mag_display = TRUE
empty_indicator = TRUE
block_upgrade_walk = 1
@@ -224,6 +230,7 @@
fire_rate = 6
spread = 7
pin = /obj/item/firing_pin/implant/pindicate
+ spread_unwielded = 15
bolt_type = BOLT_TYPE_OPEN
mag_display = TRUE
mag_display_ammo = TRUE
diff --git a/code/modules/projectiles/guns/ballistic/laser_gatling.dm b/code/modules/projectiles/guns/ballistic/laser_gatling.dm
deleted file mode 100644
index 17be3a063d409..0000000000000
--- a/code/modules/projectiles/guns/ballistic/laser_gatling.dm
+++ /dev/null
@@ -1,147 +0,0 @@
-
-
-//The ammo/gun is stored in a back slot item
-/obj/item/minigunpack
- name = "backpack power source"
- desc = "The massive external power source for the laser gatling gun."
- icon = 'icons/obj/guns/minigun.dmi'
- icon_state = "holstered"
- item_state = "backpack"
- lefthand_file = 'icons/mob/inhands/equipment/backpack_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/backpack_righthand.dmi'
- slot_flags = ITEM_SLOT_BACK
- w_class = WEIGHT_CLASS_HUGE
- var/obj/item/gun/ballistic/minigun/gun
- var/armed = 0 //whether the gun is attached, 0 is attached, 1 is the gun is wielded.
- var/overheat = 0
- var/overheat_max = 40
- var/heat_diffusion = 1
-
-/obj/item/minigunpack/Initialize()
- . = ..()
- gun = new(src)
- START_PROCESSING(SSobj, src)
-
-/obj/item/minigunpack/Destroy()
- STOP_PROCESSING(SSobj, src)
- return ..()
-
-/obj/item/minigunpack/process()
- overheat = max(0, overheat - heat_diffusion)
-
-//ATTACK HAND IGNORING PARENT RETURN VALUE
-/obj/item/minigunpack/attack_hand(var/mob/living/carbon/user)
- if(src.loc == user)
- if(!armed)
- if(user.get_item_by_slot(SLOT_BACK) == src)
- armed = 1
- if(!user.put_in_hands(gun))
- armed = 0
- to_chat(user, "You need a free hand to hold the gun!")
- return
- update_icon()
- user.update_inv_back()
- else
- to_chat(user, "You are already holding the gun!")
- else
- ..()
-
-/obj/item/minigunpack/attackby(obj/item/W, mob/user, params)
- if(W == gun) //Don't need armed check, because if you have the gun assume its armed.
- user.dropItemToGround(gun, TRUE)
- else
- ..()
-
-/obj/item/minigunpack/dropped(mob/user)
- if(armed)
- user.dropItemToGround(gun, TRUE)
-
-/obj/item/minigunpack/MouseDrop(atom/over_object)
- . = ..()
- if(armed)
- return
- if(iscarbon(usr))
- var/mob/M = usr
-
- if(!over_object)
- return
-
- if(!M.incapacitated())
-
- if(istype(over_object, /obj/screen/inventory/hand))
- var/obj/screen/inventory/hand/H = over_object
- M.putItemFromInventoryInHandIfPossible(src, H.held_index)
-
-
-/obj/item/minigunpack/update_icon()
- if(armed)
- icon_state = "notholstered"
- else
- icon_state = "holstered"
-
-/obj/item/minigunpack/proc/attach_gun(var/mob/user)
- if(!gun)
- gun = new(src)
- gun.forceMove(src)
- armed = 0
- if(user)
- to_chat(user, "You attach the [gun.name] to the [name].")
- else
- src.visible_message("The [gun.name] snaps back onto the [name]!")
- update_icon()
- user.update_inv_back()
-
-
-/obj/item/gun/ballistic/minigun
- name = "laser gatling gun"
- desc = "An advanced laser cannon with an incredible rate of fire. Requires a bulky backpack power source to use."
- icon = 'icons/obj/guns/minigun.dmi'
- icon_state = "minigun_spin"
- item_state = "minigun"
- flags_1 = CONDUCT_1
- slowdown = 1
- slot_flags = null
- w_class = WEIGHT_CLASS_HUGE
- materials = list()
- automatic = 1
- fire_rate = 10
- weapon_weight = WEAPON_HEAVY
- fire_sound = 'sound/weapons/laser.ogg'
- mag_type = /obj/item/ammo_box/magazine/internal/minigun
- tac_reloads = FALSE
- casing_ejector = FALSE
- item_flags = NEEDS_PERMIT | SLOWS_WHILE_IN_HAND
- var/obj/item/minigunpack/ammo_pack
-
-/obj/item/gun/ballistic/minigun/Initialize()
- if(istype(loc, /obj/item/minigunpack)) //We should spawn inside an ammo pack so let's use that one.
- ammo_pack = loc
- else
- return INITIALIZE_HINT_QDEL //No pack, no gun
-
- return ..()
-
-/obj/item/gun/ballistic/minigun/attack_self(mob/living/user)
- return
-
-/obj/item/gun/ballistic/minigun/dropped(mob/user)
- if(ammo_pack)
- ammo_pack.attach_gun(user)
- else
- qdel(src)
-
-/obj/item/gun/ballistic/minigun/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
- if(ammo_pack)
- if(ammo_pack.overheat < ammo_pack.overheat_max)
- ammo_pack.overheat += burst_size
- ..()
- else
- to_chat(user, "The gun's heat sensor locked the trigger to prevent lens damage.")
-
-/obj/item/gun/ballistic/minigun/afterattack(atom/target, mob/living/user, flag, params)
- if(!ammo_pack || ammo_pack.loc != user)
- to_chat(user, "You need the backpack power source to fire the gun!")
- . = ..()
-
-/obj/item/gun/ballistic/minigun/dropped(mob/living/user)
- ammo_pack.attach_gun(user)
diff --git a/code/modules/projectiles/guns/ballistic/launchers.dm b/code/modules/projectiles/guns/ballistic/launchers.dm
index 588ebb88c36f8..439edaa536501 100644
--- a/code/modules/projectiles/guns/ballistic/launchers.dm
+++ b/code/modules/projectiles/guns/ballistic/launchers.dm
@@ -10,6 +10,7 @@
fire_sound = 'sound/weapons/grenadelaunch.ogg'
w_class = WEIGHT_CLASS_NORMAL
pin = /obj/item/firing_pin/implant/pindicate
+ spread_unwielded = 15
bolt_type = BOLT_TYPE_NO_BOLT
weapon_weight = WEAPON_MEDIUM
fire_rate = 2
@@ -75,7 +76,7 @@
return //too difficult to remove the rocket with TK
/obj/item/gun/ballistic/rocketlauncher/suicide_act(mob/living/user)
- user.visible_message("[user] aims [src] at the ground! It looks like [user.p_theyre()] performing a sick rocket jump!", \
+ user.visible_message("[user] aims [src] at the ground! It looks like [user.p_theyre()] performing a sick rocket jump!", \
"You aim [src] at the ground to perform a bisnasty rocket jump...")
if(can_shoot())
user.notransform = TRUE
diff --git a/code/modules/projectiles/guns/ballistic/pistol.dm b/code/modules/projectiles/guns/ballistic/pistol.dm
index 0f7cd8e100375..31bdbe5de9d1b 100644
--- a/code/modules/projectiles/guns/ballistic/pistol.dm
+++ b/code/modules/projectiles/guns/ballistic/pistol.dm
@@ -20,6 +20,9 @@
/obj/item/gun/ballistic/automatic/pistol/no_mag
spawnwithmagazine = FALSE
+/obj/item/gun/ballistic/automatic/pistol/locker
+ desc = "A small, easily concealable 10mm handgun. Has a threaded barrel for suppressors. This one is rusted from being inside of a locker for so long."
+
/obj/item/gun/ballistic/automatic/pistol/suppressed/Initialize(mapload)
. = ..()
var/obj/item/suppressor/S = new(src)
diff --git a/code/modules/projectiles/guns/ballistic/revolver.dm b/code/modules/projectiles/guns/ballistic/revolver.dm
index 9be063489ffa9..c3b28f09ed080 100644
--- a/code/modules/projectiles/guns/ballistic/revolver.dm
+++ b/code/modules/projectiles/guns/ballistic/revolver.dm
@@ -49,7 +49,7 @@
playsound(usr, "revolver_spin", 30, FALSE)
usr.visible_message("[usr] spins [src]'s chamber.", "You spin [src]'s chamber.")
else
- verbs -= /obj/item/gun/ballistic/revolver/verb/spin
+ remove_verb(/obj/item/gun/ballistic/revolver/verb/spin)
/obj/item/gun/ballistic/revolver/proc/do_spin()
var/obj/item/ammo_box/magazine/internal/cylinder/C = magazine
@@ -285,4 +285,8 @@
qdel(src)
/obj/item/gun/ballistic/revolver/mime/attack_self(mob/user)
- qdel(src)
\ No newline at end of file
+ qdel(src)
+
+//The Lethal Version from Advanced Mimery
+/obj/item/gun/ballistic/revolver/mime/magic
+ mag_type = /obj/item/ammo_box/magazine/internal/cylinder/mime/lethal
diff --git a/code/modules/projectiles/guns/ballistic/rifle.dm b/code/modules/projectiles/guns/ballistic/rifle.dm
index ba55d1a4f4d60..43e76a9fc2192 100644
--- a/code/modules/projectiles/guns/ballistic/rifle.dm
+++ b/code/modules/projectiles/guns/ballistic/rifle.dm
@@ -17,12 +17,12 @@
weapon_weight = WEAPON_MEDIUM
block_upgrade_walk = 1
-obj/item/gun/ballistic/rifle/update_icon()
+/obj/item/gun/ballistic/rifle/update_icon()
..()
add_overlay("[icon_state]_bolt[bolt_locked ? "_locked" : ""]")
-obj/item/gun/ballistic/rifle/rack(mob/user = null)
- if (bolt_locked == FALSE)
+/obj/item/gun/ballistic/rifle/rack(mob/user = null)
+ if(bolt_locked == FALSE)
to_chat(user, "You open the bolt of \the [src].")
playsound(src, rack_sound, rack_sound_volume, rack_sound_vary)
process_chamber(FALSE, FALSE, FALSE)
@@ -31,12 +31,12 @@ obj/item/gun/ballistic/rifle/rack(mob/user = null)
return
drop_bolt(user)
-obj/item/gun/ballistic/rifle/can_shoot()
+/obj/item/gun/ballistic/rifle/can_shoot()
if (bolt_locked)
return FALSE
return ..()
-obj/item/gun/ballistic/rifle/attackby(obj/item/A, mob/user, params)
+/obj/item/gun/ballistic/rifle/attackby(obj/item/A, mob/user, params)
if (!bolt_locked)
to_chat(user, "The bolt is closed!")
return
@@ -60,6 +60,8 @@ obj/item/gun/ballistic/rifle/attackby(obj/item/A, mob/user, params)
can_bayonet = TRUE
knife_x_offset = 27
knife_y_offset = 13
+ w_class = WEIGHT_CLASS_BULKY
+ weapon_weight = WEAPON_HEAVY
/obj/item/gun/ballistic/rifle/boltaction/enchanted
name = "enchanted bolt action rifle"
diff --git a/code/modules/projectiles/guns/ballistic/shotgun.dm b/code/modules/projectiles/guns/ballistic/shotgun.dm
index 7136d0565b318..adf7ca8cdc8b1 100644
--- a/code/modules/projectiles/guns/ballistic/shotgun.dm
+++ b/code/modules/projectiles/guns/ballistic/shotgun.dm
@@ -22,10 +22,12 @@
internal_magazine = TRUE
casing_ejector = FALSE
bolt_wording = "pump"
+ bolt_type = BOLT_TYPE_PUMP
cartridge_wording = "shell"
tac_reloads = FALSE
fire_rate = 1 //reee
block_upgrade_walk = 1
+ recoil = 1
pb_knockback = 2
/obj/item/gun/ballistic/shotgun/blow_up(mob/user)
@@ -58,6 +60,9 @@
// Automatic Shotguns//
+/obj/item/gun/ballistic/shotgun/automatic
+ weapon_weight = WEAPON_HEAVY
+
/obj/item/gun/ballistic/shotgun/automatic/shoot_live_shot(mob/living/user, pointblank = 0, atom/pbtarget = null, message = 1)
..()
rack()
@@ -75,8 +80,16 @@
desc = "A compact version of the semi automatic combat shotgun. For close encounters."
icon_state = "cshotgunc"
mag_type = /obj/item/ammo_box/magazine/internal/shot/com/compact
+ weapon_weight = WEAPON_MEDIUM
w_class = WEIGHT_CLASS_BULKY
+/obj/item/gun/ballistic/shotgun/automatic/combat/compact/shoot_live_shot(mob/living/user, pointblank, atom/pbtarget, message)
+ if(!is_wielded)
+ recoil = 6
+ else
+ recoil = initial(recoil)
+ . = ..()
+
// Breaching Shotgun //
/obj/item/gun/ballistic/shotgun/automatic/breaching
@@ -133,7 +146,7 @@
/obj/item/gun/ballistic/shotgun/bulldog
name = "\improper Bulldog Shotgun"
- desc = "A semi-auto, mag-fed shotgun for combat in narrow corridors, nicknamed 'Bulldog' by boarding parties. Compatible only with specialized 8-round drum magazines."
+ desc = "A semi-auto, mag-fed shotgun for combat in narrow corridors with a built in recoil dampening system, nicknamed 'Bulldog' by boarding parties. Compatible only with specialized 8-round drum magazines."
icon_state = "bulldog"
item_state = "bulldog"
w_class = WEIGHT_CLASS_NORMAL
@@ -143,6 +156,7 @@
burst_size = 1
fire_delay = 0
pin = /obj/item/firing_pin/implant/pindicate
+ spread_unwielded = 15
actions_types = list()
mag_display = TRUE
empty_indicator = TRUE
@@ -153,7 +167,8 @@
tac_reloads = TRUE
fire_rate = 2
automatic = 1
-
+ recoil = 0
+ bolt_type = BOLT_TYPE_STANDARD //Not using a pump
/obj/item/gun/ballistic/shotgun/bulldog/unrestricted
pin = /obj/item/firing_pin
@@ -214,6 +229,7 @@
mag_type = /obj/item/ammo_box/magazine/internal/shot/improvised
sawn_desc = "I'm just here for the gasoline."
unique_reskin = null
+ recoil = 2
var/slung = FALSE
/obj/item/gun/ballistic/shotgun/doublebarrel/improvised/attackby(obj/item/A, mob/user, params)
diff --git a/code/modules/projectiles/guns/ballistic/toy.dm b/code/modules/projectiles/guns/ballistic/toy.dm
index fd5f79a48d9bf..68ca8ada85d2e 100644
--- a/code/modules/projectiles/guns/ballistic/toy.dm
+++ b/code/modules/projectiles/guns/ballistic/toy.dm
@@ -59,6 +59,7 @@
casing_ejector = FALSE
can_suppress = FALSE
pb_knockback = 0
+ recoil = 0
/obj/item/gun/ballistic/shotgun/toy/update_icon()
. = ..()
diff --git a/code/modules/projectiles/guns/energy.dm b/code/modules/projectiles/guns/energy.dm
index 82439c28eff04..b142e7fb958ca 100644
--- a/code/modules/projectiles/guns/energy.dm
+++ b/code/modules/projectiles/guns/energy.dm
@@ -16,8 +16,8 @@
var/shaded_charge = FALSE //if this gun uses a stateful charge bar for more detail
var/old_ratio = 0 // stores the gun's previous ammo "ratio" to see if it needs an updated icon
var/selfcharge = 0
- var/charge_tick = 0
- var/charge_delay = 4
+ var/charge_timer = 0
+ var/charge_delay = 8
var/use_cyborg_cell = FALSE //whether the gun's cell drains the cyborg user's cell to recharge
var/dead_cell = FALSE //set to true so the gun is given an empty cell
@@ -68,12 +68,12 @@
update_icon(FALSE, TRUE)
return ..()
-/obj/item/gun/energy/process()
+/obj/item/gun/energy/process(delta_time)
if(selfcharge && cell && cell.percent() < 100)
- charge_tick++
- if(charge_tick < charge_delay)
+ charge_timer += delta_time
+ if(charge_timer < charge_delay)
return
- charge_tick = 0
+ charge_timer = 0
cell.give(100)
if(!chambered) //if empty chamber we try to charge a new shot
recharge_newshot(TRUE)
@@ -130,7 +130,7 @@
fire_sound = shot.fire_sound
fire_delay = shot.delay
if (shot.select_name)
- to_chat(user, "[src] is now set to [shot.select_name].")
+ balloon_alert(user, "Set to [shot.select_name]")
chambered = null
recharge_newshot(TRUE)
update_icon(TRUE)
diff --git a/code/modules/projectiles/guns/energy/dueling.dm b/code/modules/projectiles/guns/energy/dueling.dm
index 143b7444c600b..3267e7f25efca 100644
--- a/code/modules/projectiles/guns/energy/dueling.dm
+++ b/code/modules/projectiles/guns/energy/dueling.dm
@@ -1,6 +1,6 @@
#define DUEL_IDLE 1
#define DUEL_PREPARATION 2
-#define DUEL_READY 3
+#define DUEL_READY 3
#define DUEL_COUNTDOWN 4
#define DUEL_FIRING 5
@@ -219,7 +219,7 @@
else
duel.fired[src] = TRUE
. = ..()
-
+
/obj/item/gun/energy/dueling/before_firing(target,user)
var/obj/item/ammo_casing/energy/duel/D = chambered
D.setting = setting
@@ -253,7 +253,7 @@
D.setting = setting
D.update_icon()
-/obj/item/ammo_casing/energy/duel/fire_casing(atom/target, mob/living/user, params, distro, quiet, zone_override, spread, atom/fired_from)
+/obj/item/ammo_casing/energy/duel/fire_casing(atom/target, mob/living/user, params, distro, quiet, zone_override, spread, spread_multiplier = 1, atom/fired_from)
. = ..()
var/obj/effect/temp_visual/dueling_chaff/C = new(get_turf(user))
C.setting = setting
@@ -297,7 +297,7 @@
var/mob/living/L = target
if(!istype(target))
return BULLET_ACT_BLOCK
-
+
var/obj/item/bodypart/B = L.get_bodypart(BODY_ZONE_HEAD)
B.dismember()
qdel(B)
diff --git a/code/modules/projectiles/guns/energy/energy_gun.dm b/code/modules/projectiles/guns/energy/energy_gun.dm
index ab5dc51efce6a..9348855cf2a0d 100644
--- a/code/modules/projectiles/guns/energy/energy_gun.dm
+++ b/code/modules/projectiles/guns/energy/energy_gun.dm
@@ -2,6 +2,7 @@
name = "energy gun"
desc = "A basic hybrid energy gun with two settings: disable and kill."
icon_state = "energy"
+ w_class = WEIGHT_CLASS_BULKY //powergaming is kill
item_state = null //so the human update icon uses the icon_state instead.
ammo_type = list(/obj/item/ammo_casing/energy/disabler, /obj/item/ammo_casing/energy/laser)
modifystate = 1
@@ -59,6 +60,7 @@
desc = "This is an expensive, modern recreation of an antique laser gun. This gun has several unique firemodes, but lacks the ability to recharge over time."
cell_type = /obj/item/stock_parts/cell{charge = 1200; maxcharge = 1200}
icon_state = "hoslaser"
+ w_class = WEIGHT_CLASS_NORMAL
force = 10
automatic = 1
fire_rate = 3
@@ -78,6 +80,7 @@
can_flashlight = FALSE
ammo_x_offset = 1
fire_rate = 1.5
+ w_class = WEIGHT_CLASS_NORMAL
/obj/item/gun/energy/e_gun/dragnet/snare
name = "Energy Snare Launcher"
@@ -105,7 +108,7 @@
desc = "An energy gun with an experimental miniaturized nuclear reactor that automatically charges the internal power cell."
icon_state = "nucgun"
item_state = "nucgun"
- charge_delay = 5
+ charge_delay = 10
pin = null
can_charge = FALSE
ammo_x_offset = 1
@@ -115,9 +118,9 @@
var/fail_tick = 0
var/fail_chance = 0
-/obj/item/gun/energy/e_gun/nuclear/process()
+/obj/item/gun/energy/e_gun/nuclear/process(delta_time)
if(fail_tick > 0)
- fail_tick--
+ fail_tick -= delta_time * 0.5
..()
/obj/item/gun/energy/e_gun/nuclear/shoot_live_shot(mob/living/user, pointblank = 0, atom/pbtarget = null, message = 1)
diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
index 224f9f25d1a72..567cf698f20ed 100644
--- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
+++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
@@ -227,7 +227,8 @@
icon_state = "modkit"
w_class = WEIGHT_CLASS_SMALL
require_module = 1
- module_type = /obj/item/robot_module/miner
+ module_type = list(/obj/item/robot_module/miner)
+ module_flags = BORG_MODULE_MINER
var/denied_type = null
var/maximum_of_type = 1
var/cost = 30
@@ -384,10 +385,8 @@
return
new /obj/effect/temp_visual/explosion/fast(target_turf)
if(turf_aoe)
- for(var/T in RANGE_TURFS(1, target_turf) - target_turf)
- if(ismineralturf(T))
- var/turf/closed/mineral/M = T
- M.gets_drilled(K.firer)
+ for(var/turf/closed/mineral/M in RANGE_TURFS(1, target_turf) - target_turf)
+ M.gets_drilled(K.firer)
if(modifier)
for(var/mob/living/L in range(1, target_turf) - K.firer - target)
var/armor = L.run_armor_check(K.def_zone, K.flag, "", "", K.armour_penetration)
diff --git a/code/modules/projectiles/guns/energy/laser.dm b/code/modules/projectiles/guns/energy/laser.dm
index 7c546031dea54..2abeef720fc9b 100644
--- a/code/modules/projectiles/guns/energy/laser.dm
+++ b/code/modules/projectiles/guns/energy/laser.dm
@@ -4,7 +4,7 @@
icon_state = "laser"
item_state = "laser"
block_upgrade_walk = 1
- w_class = WEIGHT_CLASS_NORMAL
+ w_class = WEIGHT_CLASS_BULKY
materials = list(/datum/material/iron=2000)
ammo_type = list(/obj/item/ammo_casing/energy/lasergun)
ammo_x_offset = 1
@@ -34,7 +34,8 @@
name = "antique laser gun"
icon_state = "caplaser"
item_state = "caplaser"
- desc = "This is an antique laser gun. All craftsmanship is of the highest quality. It is decorated with assistant leather and chrome. The object menaces with spikes of energy. On the item is an image of Space Station 13. The station is exploding."
+ w_class = WEIGHT_CLASS_NORMAL
+ desc = "This is an antique laser gun. All craftsmanship is of the highest quality. It is decorated with assistant leather and chrome. The object menaces with spikes of energy. On the item is an image of Space Station 13 with the words NTSSGolden engraved. The station is exploding."
force = 10
ammo_x_offset = 3
selfcharge = 1
@@ -110,6 +111,7 @@
ammo_type = list(/obj/item/ammo_casing/energy/xray)
pin = null
ammo_x_offset = 3
+ w_class = WEIGHT_CLASS_BULKY
////////Laser Tag////////////////////
diff --git a/code/modules/projectiles/guns/energy/laser_gatling.dm b/code/modules/projectiles/guns/energy/laser_gatling.dm
new file mode 100644
index 0000000000000..bf852f7b1afbb
--- /dev/null
+++ b/code/modules/projectiles/guns/energy/laser_gatling.dm
@@ -0,0 +1,191 @@
+
+
+//The ammo/gun is stored in a back slot item
+/obj/item/minigunpack
+ name = "backpack power source"
+ desc = "The massive external power source for the laser gatling gun."
+ icon = 'icons/obj/guns/minigun.dmi'
+ icon_state = "holstered"
+ item_state = "backpack"
+ lefthand_file = 'icons/mob/inhands/equipment/backpack_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/backpack_righthand.dmi'
+ slot_flags = ITEM_SLOT_BACK
+ w_class = WEIGHT_CLASS_HUGE
+ var/obj/item/gun/energy/minigun/gun
+ var/armed = 0 //whether the gun is attached, 0 is attached, 1 is the gun is wielded.
+ var/overheat = 0
+ var/overheat_max = 40
+ var/heat_diffusion = 0.5
+
+/obj/item/minigunpack/Initialize()
+ . = ..()
+ gun = new(src)
+ START_PROCESSING(SSobj, src)
+
+/obj/item/minigunpack/Destroy()
+ if(!QDELETED(gun))
+ qdel(gun)
+ gun = null
+ STOP_PROCESSING(SSobj, src)
+ return ..()
+
+/obj/item/minigunpack/process(delta_time)
+ overheat = max(0, overheat - heat_diffusion * delta_time)
+
+//ATTACK HAND IGNORING PARENT RETURN VALUE
+/obj/item/minigunpack/attack_hand(var/mob/living/carbon/user)
+ if(src.loc == user)
+ if(!armed)
+ if(user.get_item_by_slot(ITEM_SLOT_BACK) == src)
+ armed = 1
+ if(!user.put_in_hands(gun))
+ armed = 0
+ to_chat(user, "You need a free hand to hold the gun!")
+ return
+ update_icon()
+ user.update_inv_back()
+ else
+ to_chat(user, "You are already holding the gun!")
+ else
+ ..()
+
+/obj/item/minigunpack/attackby(obj/item/W, mob/user, params)
+ if(W == gun) //Don't need armed check, because if you have the gun assume its armed.
+ user.dropItemToGround(gun, TRUE)
+ else
+ ..()
+
+/obj/item/minigunpack/dropped(mob/user)
+ if(armed)
+ user.dropItemToGround(gun, TRUE)
+
+/obj/item/minigunpack/MouseDrop(atom/over_object)
+ . = ..()
+ if(armed)
+ return
+ if(iscarbon(usr))
+ var/mob/M = usr
+
+ if(!over_object)
+ return
+
+ if(!M.incapacitated())
+
+ if(istype(over_object, /atom/movable/screen/inventory/hand))
+ var/atom/movable/screen/inventory/hand/H = over_object
+ M.putItemFromInventoryInHandIfPossible(src, H.held_index)
+
+
+/obj/item/minigunpack/update_icon()
+ if(armed)
+ icon_state = "notholstered"
+ else
+ icon_state = "holstered"
+
+/obj/item/minigunpack/proc/attach_gun(var/mob/user)
+ if(!gun)
+ gun = new(src)
+ gun.forceMove(src)
+ armed = 0
+ if(user)
+ to_chat(user, "You attach the [gun.name] to the [name].")
+ else
+ src.visible_message("The [gun.name] snaps back onto the [name]!")
+ update_icon()
+ user.update_inv_back()
+
+/obj/item/minigunpack/emag_act(mob/user)
+ if(obj_flags & EMAGGED)
+ return
+ obj_flags |= EMAGGED
+ to_chat(user, "You break the heat sensor.")
+ overheat_max = 1000
+
+/obj/item/stock_parts/cell/minigun
+ name = "Minigun gun fusion core"
+ maxcharge = 500000
+ self_recharge = 0
+
+/obj/item/gun/energy/minigun
+ name = "laser gatling gun"
+ desc = "An advanced laser cannon with an incredible rate of fire. Requires a bulky backpack power source to use."
+ icon = 'icons/obj/guns/minigun.dmi'
+ icon_state = "minigun_spin"
+ item_state = "minigun"
+ flags_1 = CONDUCT_1
+ slowdown = 1
+ slot_flags = null
+ w_class = WEIGHT_CLASS_HUGE
+ materials = list()
+ automatic = 1
+ fire_rate = 10
+ weapon_weight = WEAPON_HEAVY
+ ammo_type = list(/obj/item/ammo_casing/energy/laser)
+ cell_type = /obj/item/stock_parts/cell/minigun
+ can_charge = FALSE
+ fire_sound = 'sound/weapons/laser.ogg'
+ item_flags = NEEDS_PERMIT | SLOWS_WHILE_IN_HAND
+ var/cooldown = 0
+ var/obj/item/minigunpack/ammo_pack
+
+/obj/item/gun/energy/minigun/Initialize()
+ if(istype(loc, /obj/item/minigunpack)) //We should spawn inside an ammo pack so let's use that one.
+ ammo_pack = loc
+ else
+ return INITIALIZE_HINT_QDEL //No pack, no gun
+
+ return ..()
+
+/obj/item/gun/energy/minigun/Destroy()
+ if(!QDELETED(ammo_pack))
+ qdel(ammo_pack)
+ ammo_pack = null
+ return ..()
+
+/obj/item/gun/energy/minigun/attack_self(mob/living/user)
+ return
+
+/obj/item/gun/energy/minigun/dropped(mob/user)
+ if(ammo_pack)
+ ammo_pack.attach_gun(user)
+ else
+ qdel(src)
+
+/obj/item/gun/energy/minigun/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
+ if(ammo_pack)
+ if(obj_flags & EMAGGED)
+ if(cooldown < world.time)
+ cooldown = world.time + 50
+ playsound(get_turf(src), 'sound/weapons/heavyminigunstart.ogg', 50, 0, 0)
+ slowdown = 5
+ sleep(15)
+ if(ammo_pack.overheat < ammo_pack.overheat_max)
+ ammo_pack.overheat += burst_size
+ playsound(get_turf(src), 'sound/weapons/heavyminigunshoot.ogg', 60, 0, 0)
+ ..()
+ playsound(get_turf(src), 'sound/weapons/heavyminigunstop.ogg', 50, 0, 0)
+ slowdown = initial(slowdown)
+ else
+ to_chat(user, "The gun's heat sensor locked the trigger to prevent lens damage.")
+ else
+ ..()
+
+/obj/item/gun/energy/minigun/afterattack(atom/target, mob/living/user, flag, params)
+ if(!ammo_pack || ammo_pack.loc != user)
+ to_chat(user, "You need the backpack power source to fire the gun!")
+ . = ..()
+
+/obj/item/gun/energy/minigun/dropped(mob/living/user)
+ ammo_pack.attach_gun(user)
+
+/obj/item/gun/energy/minigun/emag_act(mob/user)
+ if(obj_flags & EMAGGED)
+ return
+ obj_flags |= EMAGGED
+ fire_sound = null
+ spread = 60
+ recoil = 1
+ burst_size = 120
+ fire_delay = 0.2
+ playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 30, 0, 0)
+ to_chat(user, "OVERDRIVE.")
diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm
index 8ff6270eca997..40160c076bb22 100644
--- a/code/modules/projectiles/guns/energy/special.dm
+++ b/code/modules/projectiles/guns/energy/special.dm
@@ -27,6 +27,9 @@
flight_x_offset = 18
flight_y_offset = 11
+/obj/item/gun/energy/ionrifle/carbine/pin
+ pin = /obj/item/firing_pin
+
/obj/item/gun/energy/decloner
name = "biological demolecularisor"
desc = "A gun that discharges high amounts of controlled radiation to slowly break a target into component elements."
@@ -237,8 +240,8 @@
can_charge = FALSE
use_cyborg_cell = TRUE
tool_behaviour = null //because it will drain the cutters cell and not the borgs.
-
-
+
+
/obj/item/gun/energy/wormhole_projector
name = "bluespace wormhole projector"
desc = "A projector that emits high density quantum-coupled bluespace beams."
@@ -258,10 +261,10 @@
for(var/i in 1 to ammo_type.len)
var/obj/item/ammo_casing/energy/wormhole/W = ammo_type[i]
if(istype(W))
- W.gun = src
+ W.gun = WEAKREF(src)
var/obj/item/projectile/beam/wormhole/WH = W.BB
if(istype(WH))
- WH.gun = src
+ WH.gun = WEAKREF(src)
/obj/item/gun/energy/wormhole_projector/process_chamber()
..()
@@ -337,11 +340,15 @@
pin = null
block_upgrade_walk = 1
+/obj/item/gun/energy/temperature/pin
+ pin = /obj/item/firing_pin
+
/obj/item/gun/energy/temperature/security
name = "security temperature gun"
desc = "A weapon that can only be used to its full potential by the truly robust."
pin = /obj/item/firing_pin
block_upgrade_walk = 1
+ w_class = WEIGHT_CLASS_BULKY
/obj/item/gun/energy/laser/instakill
name = "instakill rifle"
diff --git a/code/modules/projectiles/guns/magic.dm b/code/modules/projectiles/guns/magic.dm
index 2b8e0e0bf5a9f..5d3639e0c438c 100644
--- a/code/modules/projectiles/guns/magic.dm
+++ b/code/modules/projectiles/guns/magic.dm
@@ -12,8 +12,8 @@
var/checks_antimagic = TRUE
var/max_charges = 6
var/charges = 0
- var/recharge_rate = 4
- var/charge_tick = 0
+ var/recharge_rate = 8
+ var/charge_timer = 0
var/can_charge = TRUE
var/ammo_type
var/no_den_usage
@@ -64,12 +64,14 @@
STOP_PROCESSING(SSobj, src)
return ..()
-
-/obj/item/gun/magic/process()
- charge_tick++
- if(charge_tick < recharge_rate || charges >= max_charges)
+/obj/item/gun/magic/process(delta_time)
+ if (charges >= max_charges)
+ charge_timer = 0
+ return
+ charge_timer += delta_time
+ if(charge_timer < recharge_rate)
return 0
- charge_tick = 0
+ charge_timer = 0
charges++
if(charges == 1)
recharge_newshot()
diff --git a/code/modules/projectiles/guns/misc/beam_rifle.dm b/code/modules/projectiles/guns/misc/beam_rifle.dm
index 84fe4ce6c3a35..157d2ac4bea3b 100644
--- a/code/modules/projectiles/guns/misc/beam_rifle.dm
+++ b/code/modules/projectiles/guns/misc/beam_rifle.dm
@@ -76,6 +76,9 @@
var/datum/action/item_action/zoom_lock_action/zoom_lock_action
var/mob/listeningTo
+ var/obj/aiming_target
+ var/aiming_params
+
/obj/item/gun/energy/beam_rifle/debug
delay = 0
cell_type = /obj/item/stock_parts/cell/infinite
@@ -197,12 +200,12 @@
else
P.color = rgb(0, 255, 0)
var/turf/curloc = get_turf(src)
- var/turf/targloc = get_turf(current_user.client.mouseObject)
+ var/turf/targloc = get_turf(aiming_target)
if(!istype(targloc))
if(!istype(curloc))
return
targloc = get_turf_in_angle(lastangle, curloc, 10)
- P.preparePixelProjectile(targloc, current_user, current_user.client.mouseParams, 0)
+ P.preparePixelProjectile(targloc, current_user, aiming_params, 0)
P.fire(lastangle)
/obj/item/gun/energy/beam_rifle/process()
@@ -224,14 +227,16 @@
return TRUE
/obj/item/gun/energy/beam_rifle/proc/process_aim()
- if(istype(current_user) && current_user.client && current_user.client.mouseParams)
- var/angle = mouse_angle_from_client(current_user.client)
+ if(istype(current_user) && current_user.client)
+ var/angle = mouse_angle_from_client(current_user.client, aiming_params)
current_user.setDir(angle2dir_cardinal(angle))
var/difference = abs(closer_angle_difference(lastangle, angle))
delay_penalty(difference * aiming_time_increase_angle_multiplier)
lastangle = angle
/obj/item/gun/energy/beam_rifle/proc/on_mob_move()
+ SIGNAL_HANDLER
+
check_user()
if(aiming)
delay_penalty(aiming_time_increase_user_movement)
@@ -261,15 +266,15 @@
UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED)
listeningTo = null
if(istype(current_user))
- LAZYREMOVE(current_user.mousemove_intercept_objects, src)
current_user = null
if(istype(user))
current_user = user
- LAZYOR(current_user.mousemove_intercept_objects, src)
RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/on_mob_move)
listeningTo = user
/obj/item/gun/energy/beam_rifle/onMouseDrag(src_object, over_object, src_location, over_location, params, mob)
+ aiming_target = over_object
+ aiming_params = params
if(aiming)
process_aim()
aiming_beam()
@@ -281,7 +286,7 @@
/obj/item/gun/energy/beam_rifle/onMouseDown(object, location, params, mob/mob)
if(istype(mob))
set_user(mob)
- if(istype(object, /obj/screen) && !istype(object, /obj/screen/click_catcher))
+ if(istype(object, /atom/movable/screen) && !istype(object, /atom/movable/screen/click_catcher))
return
if((object in mob.contents) || (object == mob))
return
@@ -289,12 +294,12 @@
return ..()
/obj/item/gun/energy/beam_rifle/onMouseUp(object, location, params, mob/M)
- if(istype(object, /obj/screen) && !istype(object, /obj/screen/click_catcher))
+ if(istype(object, /atom/movable/screen) && !istype(object, /atom/movable/screen/click_catcher))
return
process_aim()
if(aiming_time_left <= aiming_time_fire_threshold && check_user())
sync_ammo()
- afterattack(M.client.mouseObject, M, FALSE, M.client.mouseParams, passthrough = TRUE)
+ afterattack(object, M, FALSE, params, passthrough = TRUE)
stop_aiming()
QDEL_LIST(current_tracers)
return ..()
@@ -441,11 +446,11 @@
if(!epicenter)
return
new /obj/effect/temp_visual/explosion/fast(epicenter)
- for(var/mob/living/L in range(aoe_mob_range, epicenter)) //handle aoe mob damage
+ for(var/mob/living/L in hearers(aoe_mob_range, epicenter)) //handle aoe mob damage
L.adjustFireLoss(aoe_mob_damage)
to_chat(L, "\The [src] sears you!")
- for(var/turf/T in range(aoe_fire_range, epicenter)) //handle aoe fire
- if(prob(aoe_fire_chance))
+ for(var/turf/open/T in RANGE_TURFS(aoe_fire_range, epicenter)) //handle aoe fire
+ if(prob(aoe_fire_chance) && can_see(epicenter, T, aoe_fire_range))
new /obj/effect/hotspot(T)
for(var/obj/O in range(aoe_structure_range, epicenter))
if(!isitem(O))
@@ -465,7 +470,7 @@
var/turf/closed/wall/W = target
W.dismantle_wall(TRUE, TRUE)
else
- target.ex_act(EXPLODE_HEAVY)
+ SSexplosions.medturf += target
return TRUE
if(ismovableatom(target))
var/atom/movable/AM = target
diff --git a/code/modules/projectiles/guns/misc/grenade_launcher.dm b/code/modules/projectiles/guns/misc/grenade_launcher.dm
index e8ce62f12d061..48eff29eeebdb 100644
--- a/code/modules/projectiles/guns/misc/grenade_launcher.dm
+++ b/code/modules/projectiles/guns/misc/grenade_launcher.dm
@@ -47,3 +47,6 @@
F.icon_state = initial(F.icon_state) + "_active"
playsound(user.loc, 'sound/weapons/armbomb.ogg', 75, 1, -3)
addtimer(CALLBACK(F, /obj/item/grenade.proc/prime), 15)
+
+/obj/item/gun/grenadelauncher/security
+ desc = "A terrible, terrible thing. It's really awful! It's been crudely painted blue."
diff --git a/code/modules/projectiles/guns/misc/medbeam.dm b/code/modules/projectiles/guns/misc/medbeam.dm
index 36e9317e5b23e..3e51445576bf0 100644
--- a/code/modules/projectiles/guns/misc/medbeam.dm
+++ b/code/modules/projectiles/guns/misc/medbeam.dm
@@ -116,7 +116,7 @@
new /obj/effect/temp_visual/heal(get_turf(target), "#80F5FF")
target.adjustBruteLoss(-4)
target.adjustFireLoss(-4)
- target.adjustToxLoss(-1)
+ target.adjustToxLoss(-1, FALSE, TRUE)
target.adjustOxyLoss(-1)
return
diff --git a/code/modules/projectiles/guns/misc/syringe_gun.dm b/code/modules/projectiles/guns/misc/syringe_gun.dm
index 8482e5667bd47..72fcdf5d1539e 100644
--- a/code/modules/projectiles/guns/misc/syringe_gun.dm
+++ b/code/modules/projectiles/guns/misc/syringe_gun.dm
@@ -10,11 +10,14 @@
materials = list(/datum/material/iron=2000)
clumsy_check = 0
fire_sound = 'sound/items/syringeproj.ogg'
+ var/load_sound = 'sound/weapons/shotguninsert.ogg'
var/list/syringes = list()
var/max_syringes = 1
+ var/has_syringe_overlay = TRUE ///If it has an overlay for inserted syringes. If true, the overlay is determined by the number of syringes inserted into it.
/obj/item/gun/syringe/Initialize()
. = ..()
+ update_icon()
chambered = new /obj/item/ammo_casing/syringegun(src)
/obj/item/gun/syringe/handle_atom_del(atom/A)
@@ -33,6 +36,7 @@
/obj/item/gun/syringe/process_chamber()
if(chambered && !chambered.BB) //we just fired
recharge_newshot()
+ update_icon()
/obj/item/gun/syringe/examine(mob/user)
. = ..()
@@ -46,13 +50,14 @@
var/obj/item/reagent_containers/syringe/S = syringes[syringes.len]
if(!S)
- return 0
+ return FALSE
user.put_in_hands(S)
syringes.Remove(S)
+ update_icon()
to_chat(user, "You unload [S] from \the [src].")
- return 1
+ return TRUE
/obj/item/gun/syringe/attackby(obj/item/A, mob/user, params, show_msg = TRUE)
if(istype(A, /obj/item/reagent_containers/syringe))
@@ -62,11 +67,20 @@
to_chat(user, "You load [A] into \the [src].")
syringes += A
recharge_newshot()
+ update_icon()
+ playsound(loc, load_sound, 40)
return TRUE
else
to_chat(user, "[src] cannot hold more syringes!")
return FALSE
+/obj/item/gun/syringe/update_overlays()
+ . = ..()
+ if(!has_syringe_overlay)
+ return
+ var/syringe_count = syringes.len
+ . += "[initial(icon_state)]_[syringe_count ? clamp(syringe_count, 1, initial(max_syringes)) : "empty"]"
+
/obj/item/gun/syringe/rapidsyringe
name = "rapid syringe gun"
desc = "A modification of the syringe gun design, using a rotating cylinder to store up to six syringes."
@@ -105,6 +119,8 @@
to_chat(user, "You load \the [D] into \the [src].")
syringes += D
recharge_newshot()
+ update_icon()
+ playsound(loc, load_sound, 40)
return TRUE
else
to_chat(user, "[src] cannot hold more syringes!")
@@ -114,6 +130,7 @@
name = "blowgun"
desc = "Fire syringes at a short distance."
icon_state = "blowgun"
+ has_syringe_overlay = FALSE
item_state = "blowgun"
fire_sound = 'sound/items/syringeproj.ogg'
no_pin_required = TRUE
@@ -124,4 +141,4 @@
if(do_after(user, 25, target = src))
user.adjustStaminaLoss(20)
user.adjustOxyLoss(20)
- ..()
+ return ..()
\ No newline at end of file
diff --git a/code/modules/projectiles/pins.dm b/code/modules/projectiles/pins.dm
index 9a7edd5f3ef1d..98b47d3104efd 100644
--- a/code/modules/projectiles/pins.dm
+++ b/code/modules/projectiles/pins.dm
@@ -60,10 +60,10 @@
/obj/item/firing_pin/proc/auth_fail(mob/living/user)
if(user)
- user.show_message(fail_message, 1)
+ user.show_message(fail_message, MSG_VISUAL)
if(selfdestruct)
if(user)
- user.show_message("SELF-DESTRUCTING... ", 1)
+ user.show_message("SELF-DESTRUCTING... ", MSG_VISUAL)
to_chat(user, "[gun] explodes!")
explosion(get_turf(gun), -1, 0, 2, 3)
if(gun)
@@ -86,7 +86,7 @@
/obj/item/firing_pin/test_range/pin_auth(mob/living/user)
if(!istype(user))
return FALSE
- for(var/obj/machinery/magnetic_controller/M in range(user, 3))
+ if(locate(/obj/machinery/magnetic_controller) in range(3, user))
return TRUE
return FALSE
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 6b794624e73f7..a2a82f431975b 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -37,16 +37,30 @@
var/datum/point/vector/trajectory
var/trajectory_ignore_forcemove = FALSE //instructs forceMove to NOT reset our trajectory to the new location!
- var/speed = 0.7 //Amount of deciseconds it takes for projectile to travel
+ var/speed = 0.8 //Amount of deciseconds it takes for projectile to travel
var/Angle = 0
var/original_angle = 0 //Angle at firing
var/nondirectional_sprite = FALSE //Set TRUE to prevent projectiles from having their sprites rotated based on firing angle
var/spread = 0 //amount (in degrees) of projectile spread
animate_movement = NO_STEPS //Use SLIDE_STEPS in conjunction with legacy
- var/ricochets = 0
- var/ricochets_max = 2
- var/ricochet_chance = 30
- var/force_hit = FALSE //If the object being hit can pass ths damage on to something else, it should not do it for this bullet.
+ var/ricochets = 0 // how many times we've ricochet'd so far (instance variable, not a stat)
+ /// how many times we can ricochet max
+ var/ricochets_max = 0
+ /// 0-100, the base chance of ricocheting, before being modified by the atom we shoot and our chance decay
+ var/ricochet_chance = 0
+ /// 0-1 (or more, I guess) multiplier, the ricochet_chance is modified by multiplying this after each ricochet
+ var/ricochet_decay_chance = 0.7
+ /// 0-1 (or more, I guess) multiplier, the projectile's damage is modified by multiplying this after each ricochet
+ var/ricochet_decay_damage = 0.7
+ /// On ricochet, if nonzero, we consider all mobs within this range of our projectile at the time of ricochet to home in on like Revolver Ocelot, as governed by ricochet_auto_aim_angle
+ var/ricochet_auto_aim_range = 0
+ /// On ricochet, if ricochet_auto_aim_range is nonzero, we'll consider any mobs within this range of the normal angle of incidence to home in on, higher = more auto aim
+ var/ricochet_auto_aim_angle = 30
+ /// the angle of impact must be within this many degrees of the struck surface, set to 0 to allow any angle
+ var/ricochet_incidence_leeway = 40
+
+ ///If the object being hit can pass ths damage on to something else, it should not do it for this bullet
+ var/force_hit = FALSE
//Hitscan
var/hitscan = FALSE //Whether this is hitscan. If it is, speed is basically ignored.
@@ -104,9 +118,15 @@
var/dismemberment = 0 //The higher the number, the greater the bonus to dismembering. 0 will not dismember at all.
var/impact_effect_type //what type of impact effect to show when hitting something
var/log_override = FALSE //is this type spammed enough to not log? (KAs)
+ var/martial_arts_no_deflect = FALSE
var/temporary_unstoppable_movement = FALSE
+ ///If defined, on hit we create an item of this type then call hitby() on the hit target with this
+ var/shrapnel_type
+ ///If TRUE, hit mobs even if they're on the floor and not our target
+ var/hit_stunned_targets = FALSE
+
/obj/item/projectile/Initialize()
. = ..()
permutated = list()
@@ -118,6 +138,7 @@
on_range()
/obj/item/projectile/proc/on_range() //if we want there to be effects when they reach the end of their range
+ SEND_SIGNAL(src, COMSIG_PROJECTILE_RANGE_OUT)
qdel(src)
//to get the correct limb (if any) for the projectile hit message
@@ -137,6 +158,9 @@
/obj/item/projectile/proc/on_hit(atom/target, blocked = FALSE)
if(fired_from)
SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_BEFORE_FIRE, src, original)
+ // i know that this is probably more with wands and gun mods in mind, but it's a bit silly that the projectile on_hit signal doesn't ping the projectile itself.
+ // maybe we care what the projectile thinks! See about combining these via args some time when it's not 5AM
+ SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_ON_HIT, firer, target, Angle)
var/turf/target_loca = get_turf(target)
var/hitx
@@ -172,14 +196,15 @@
if(isalien(L))
new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(target_loca, splatter_dir)
var/obj/item/bodypart/B = L.get_bodypart(def_zone)
- if(B.status == BODYPART_ROBOTIC) // So if you hit a robotic, it sparks instead of bloodspatters
- do_sparks(2, FALSE, target.loc)
- if(prob(25))
- new /obj/effect/decal/cleanable/oil(target_loca)
- else
- new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_loca, splatter_dir)
- if(prob(33))
- L.add_splatter_floor(target_loca)
+ if(B)
+ if(B.status == BODYPART_ROBOTIC) // So if you hit a robotic, it sparks instead of bloodspatters
+ do_sparks(2, FALSE, target.loc)
+ if(prob(25))
+ new /obj/effect/decal/cleanable/oil(target_loca)
+ else
+ new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_loca, splatter_dir)
+ if(prob(33))
+ L.add_splatter_floor(target_loca)
else if(impact_effect_type && !hitscan)
new impact_effect_type(target_loca, hitx, hity)
@@ -187,7 +212,9 @@
var/limb_hit = L.check_limb_hit(def_zone)//to get the correct message info.
if(limb_hit)
organ_hit_text = " in \the [parse_zone(limb_hit)]"
- if(suppressed)
+ if(suppressed==SUPPRESSED_VERY)
+ playsound(loc, hitsound, 5, TRUE, -1)
+ else if(suppressed)
playsound(loc, hitsound, 5, 1, -1)
to_chat(L, "You're shot by \a [src][organ_hit_text]!")
else
@@ -199,11 +226,16 @@
L.on_hit(src)
var/reagent_note
- if(reagents?.reagent_list)
+ if(length(reagents?.reagent_list) > 0)
reagent_note = " REAGENTS:"
for(var/datum/reagent/R in reagents.reagent_list)
reagent_note += "[R.name] ([num2text(R.volume)])"
-
+ if(istype(src, /obj/item/projectile/bullet/dart))
+ var/obj/item/projectile/bullet/dart/D = src
+ if(length(D.syringe?.reagents?.reagent_list) > 0)
+ reagent_note = " REAGENTS:"
+ for(var/datum/reagent/R in D.syringe.reagents.reagent_list)
+ reagent_note += "[R.name] ([num2text(R.volume)])"
if(ismob(firer))
log_combat(firer, L, "shot", src, reagent_note)
else
@@ -218,7 +250,23 @@
return 50 //if the projectile doesn't do damage, play its hitsound at 50% volume
/obj/item/projectile/proc/on_ricochet(atom/A)
- return
+ if(!ricochet_auto_aim_angle || !ricochet_auto_aim_range)
+ return
+
+ var/mob/living/unlucky_sob
+ var/best_angle = ricochet_auto_aim_angle
+ if(firer && HAS_TRAIT(firer, TRAIT_NICE_SHOT))
+ best_angle += NICE_SHOT_RICOCHET_BONUS
+ for(var/mob/living/L in range(ricochet_auto_aim_range, src.loc))
+ if(L.stat == DEAD || !isInSight(src, L))
+ continue
+ var/our_angle = abs(closer_angle_difference(Angle, Get_Angle(src.loc, L.loc)))
+ if(our_angle < best_angle)
+ best_angle = our_angle
+ unlucky_sob = L
+
+ if(unlucky_sob)
+ setAngle(Get_Angle(src, unlucky_sob.loc))
/obj/item/projectile/proc/store_hitscan_collision(datum/point/pcache)
beam_segments[beam_index] = pcache
@@ -316,8 +364,12 @@
return FALSE
/obj/item/projectile/proc/check_ricochet_flag(atom/A)
- if(A.flags_1 & CHECK_RICOCHET_1)
+ if((flag in list("energy", "laser")) && (A.flags_ricochet & RICOCHET_SHINY))
+ return TRUE
+
+ if((flag in list("bomb", "bullet")) && (A.flags_ricochet & RICOCHET_HARD))
return TRUE
+
return FALSE
/obj/item/projectile/proc/return_predicted_turf_after_moves(moves, forced_angle) //I say predicted because there's no telling that the projectile won't change direction/location in flight.
@@ -396,7 +448,7 @@
last_projectile_move = world.time
fired = TRUE
if(hitscan)
- process_hitscan()
+ INVOKE_ASYNC(src, .proc/process_hitscan)
if(!(datum_flags & DF_ISPROCESSING))
START_PROCESSING(SSprojectiles, src)
pixel_move(1, FALSE) //move it now!
@@ -501,6 +553,8 @@
else if(T != loc)
step_towards(src, T)
hitscan_last = loc
+ if(QDELETED(src))
+ return
if(!hitscanning && !forcemoved)
pixel_x = trajectory.return_px() - trajectory.mpx * trajectory_multiplier * SSprojectiles.global_iterations_per_move
pixel_y = trajectory.return_py() - trajectory.mpy * trajectory_multiplier * SSprojectiles.global_iterations_per_move
@@ -633,13 +687,14 @@
finalize_hitscan_and_generate_tracers()
STOP_PROCESSING(SSprojectiles, src)
cleanup_beam_segments()
- qdel(trajectory)
+ if(trajectory)
+ QDEL_NULL(trajectory)
return ..()
/obj/item/projectile/proc/cleanup_beam_segments()
QDEL_LIST_ASSOC(beam_segments)
beam_segments = list()
- qdel(beam_index)
+ QDEL_NULL(beam_index)
/obj/item/projectile/proc/finalize_hitscan_and_generate_tracers(impacting = TRUE)
if(trajectory && beam_index)
diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm
index 21e2e2cd60cfe..a397c67a77cbe 100644
--- a/code/modules/projectiles/projectile/beams.dm
+++ b/code/modules/projectiles/projectile/beams.dm
@@ -37,9 +37,10 @@
impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser/wall
/obj/item/projectile/beam/weak
- damage = 15
+ damage = 8
-/obj/item/projectile/beam/weak/penetrator
+/obj/item/projectile/beam/weak/penetrator //laser gatling and centcom shuttle turret
+ damage = 15
armour_penetration = 50
/obj/item/projectile/beam/practice
@@ -69,7 +70,7 @@
/obj/item/projectile/beam/disabler
name = "disabler beam"
icon_state = "omnilaser"
- damage = 35
+ damage = 28
damage_type = STAMINA
flag = "energy"
hitsound = 'sound/weapons/tap.ogg'
@@ -93,7 +94,10 @@
/obj/item/projectile/beam/pulse/on_hit(atom/target, blocked = FALSE)
. = ..()
if (!QDELETED(target) && (isturf(target) || istype(target, /obj/structure/)))
- target.ex_act(EXPLODE_HEAVY)
+ if(isobj(target))
+ SSexplosions.med_mov_atom += target
+ else
+ SSexplosions.medturf += target
/obj/item/projectile/beam/pulse/shotgun
damage = 40
@@ -125,10 +129,11 @@
hitsound = null
damage = 0
damage_type = STAMINA
- flag = "laser"
+ flag = "energy"
var/suit_types = list(/obj/item/clothing/suit/redtag, /obj/item/clothing/suit/bluetag)
impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser
light_color = LIGHT_COLOR_BLUE
+ martial_arts_no_deflect = TRUE
/obj/item/projectile/beam/lasertag/on_hit(atom/target, blocked = FALSE)
. = ..()
diff --git a/code/modules/projectiles/projectile/bullets/dart_syringe.dm b/code/modules/projectiles/projectile/bullets/dart_syringe.dm
index f34b6d60acd43..e305b86dcdaf1 100644
--- a/code/modules/projectiles/projectile/bullets/dart_syringe.dm
+++ b/code/modules/projectiles/projectile/bullets/dart_syringe.dm
@@ -13,7 +13,7 @@
if(iscarbon(target))
var/mob/living/carbon/M = target
if(blocked != 100) // not completely blocked
- if(M.can_inject(null, FALSE, def_zone, piercing)) // Pass the hit zone to see if it can inject by whether it hit the head or the body.
+ if(M.can_inject(firer, FALSE, def_zone, piercing)) // Pass the hit zone to see if it can inject by whether it hit the head or the body.
..()
if(syringe)
syringe.embed(M)
@@ -40,13 +40,6 @@
reagents.add_reagent(/datum/reagent/foaming_agent, 5)
reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 5)
-/obj/item/projectile/bullet/dart/tranq
- name = "syringe"
- icon_state = "syringeproj"
-
-/obj/item/projectile/bullet/dart/tranq/Initialize()
- . = ..()
- reagents.add_reagent(/datum/reagent/toxin/chloralhydrate, 4) //these'll get the victim wallslamming and then sleep em, but it will take awhile before it puts the victim to sleep
/obj/item/projectile/bullet/dart/syringe
name = "syringe"
@@ -67,29 +60,48 @@
var/mob/living/simple_animal/hostile/poison/bees/B = new(src.loc)
for(var/datum/reagent/R in reagents.reagent_list)
B.assign_reagent(GLOB.chemical_reagents_list[R.type])
- break
+ break
else
playsound(src, 'sound/effects/splat.ogg', 40, 1)
new /obj/effect/decal/cleanable/insectguts(src.loc)
-
+
else if (prob(20)) //high velocity bees die easily
var/mob/living/simple_animal/hostile/poison/bees/B = new(M.loc)
for(var/datum/reagent/R in reagents.reagent_list)
B.assign_reagent(GLOB.chemical_reagents_list[R.type])
break
-
+
else
playsound(src, 'sound/effects/splat.ogg', 40, 1)
new /obj/effect/decal/cleanable/insectguts(src.loc)
-
+
else if(prob(20))
var/mob/living/simple_animal/hostile/poison/bees/B = new(src.loc)
for(var/datum/reagent/R in reagents.reagent_list)
B.assign_reagent(GLOB.chemical_reagents_list[R.type])
break
-
+
else
playsound(src, 'sound/effects/splat.ogg', 40, 1)
new /obj/effect/decal/cleanable/insectguts(src.loc)
-
+
return ..()
+
+/obj/item/projectile/bullet/dart/tranq
+ name = "tranquilizer dart"
+
+/obj/item/projectile/bullet/dart/tranq/Initialize()
+ . = ..()
+ reagents.add_reagent(/datum/reagent/toxin/chloralhydrate, 4) //these'll get the victim wallslamming and then sleep em, but it will take awhile before it puts the victim to sleep
+
+/obj/item/projectile/bullet/dart/tranq/plus
+
+/obj/item/projectile/bullet/dart/tranq/plus/Initialize()
+ . = ..()
+ reagents.add_reagent(/datum/reagent/pax, 1)
+
+/obj/item/projectile/bullet/dart/tranq/plusplus
+
+/obj/item/projectile/bullet/dart/tranq/plusplus/Initialize()
+ . = ..()
+ reagents.add_reagent(/datum/reagent/pax, 3)
diff --git a/code/modules/projectiles/projectile/bullets/lmg.dm b/code/modules/projectiles/projectile/bullets/lmg.dm
index d8b97431ad274..2dcc4081f8b5f 100644
--- a/code/modules/projectiles/projectile/bullets/lmg.dm
+++ b/code/modules/projectiles/projectile/bullets/lmg.dm
@@ -42,3 +42,11 @@
name = "7.12x82mm incendiary bullet"
damage = 20
fire_stacks = 3
+
+/obj/item/projectile/bullet/mm712x82_match
+ name = "7.12x82mm match bullet"
+ damage = 40
+ ricochets_max = 2
+ ricochet_chance = 60
+ ricochet_auto_aim_range = 4
+ ricochet_incidence_leeway = 35
diff --git a/code/modules/projectiles/projectile/bullets/revolver.dm b/code/modules/projectiles/projectile/bullets/revolver.dm
index 14755c9f8554c..a7106e67889e1 100644
--- a/code/modules/projectiles/projectile/bullets/revolver.dm
+++ b/code/modules/projectiles/projectile/bullets/revolver.dm
@@ -15,25 +15,59 @@
/obj/item/projectile/bullet/c38
name = ".38 bullet"
damage = 25
+ ricochets_max = 2
+ ricochet_chance = 50
+ ricochet_auto_aim_angle = 10
+ ricochet_auto_aim_range = 3
+
+/obj/item/projectile/bullet/c38/match
+ name = ".38 Match bullet"
+ ricochets_max = 4
+ ricochet_chance = 100
+ ricochet_auto_aim_angle = 40
+ ricochet_auto_aim_range = 5
+ ricochet_incidence_leeway = 50
+ ricochet_decay_chance = 1
+ ricochet_decay_damage = 1
+
+/obj/item/projectile/bullet/c38/match/bouncy
+ name = ".38 Rubber bullet"
+ damage = 10
+ stamina = 30
+ armour_penetration = -30
+ ricochets_max = 6
+ ricochet_incidence_leeway = 70
+ ricochet_chance = 130
+ ricochet_decay_damage = 0.8
+ shrapnel_type = NONE
+
+/obj/item/projectile/bullet/c38/dumdum
+ name = ".38 DumDum bullet"
+ damage = 15
+ armour_penetration = -30
+ ricochets_max = 0
+ shrapnel_type = /obj/item/shrapnel/bullet/c38/dumdum
/obj/item/projectile/bullet/c38/trac
name = ".38 TRAC bullet"
damage = 10
+ ricochets_max = 0
/obj/item/projectile/bullet/c38/trac/on_hit(atom/target, blocked = FALSE)
. = ..()
- var/mob/living/carbon/M = target
- var/obj/item/implant/tracking/c38/imp
- for(var/obj/item/implant/tracking/c38/TI in M.implants) //checks if the target already contains a tracking implant
- imp = TI
+ var/mob/living/M = target
+ if(!istype(M))
return
- if(!imp)
- imp = new /obj/item/implant/tracking/c38(M)
- imp.implant(M)
+ if(locate(/obj/item/implant/tracking/c38) in M.implants) //checks if the target already contains a tracking implant
+ return
+
+ var/obj/item/implant/tracking/c38/imp = new (M)
+ imp.implant(M)
/obj/item/projectile/bullet/c38/hotshot //similar to incendiary bullets, but do not leave a flaming trail
name = ".38 Hot Shot bullet"
damage = 20
+ ricochets_max = 0
/obj/item/projectile/bullet/c38/hotshot/on_hit(atom/target, blocked = FALSE)
. = ..()
@@ -46,6 +80,7 @@
name = ".38 Iceblox bullet"
damage = 20
var/temperature = 100
+ ricochets_max = 0
/obj/item/projectile/bullet/c38/iceblox/on_hit(atom/target, blocked = FALSE)
. = ..()
@@ -58,6 +93,7 @@
icon_state = null
damage = 0
nodamage = TRUE
+ martial_arts_no_deflect = TRUE
/obj/item/projectile/bullet/c38/mime/on_hit(atom/target, blocked = FALSE)
if(isliving(target))
@@ -67,11 +103,31 @@
M.apply_damage(5, BRUTE, CHEST, defense)
M.visible_message("A bullet wound appears in [M]'s chest!", \
"You get hit with a .38 bullet from a finger gun! Those hurt!...")
- else
+ else
to_chat(M, "You get shot with the finger gun!")
+/obj/item/projectile/bullet/c38/mime_lethal
+ name = "invisible .38 bullet"
+ icon_state = null
+ damage = 20
+
+/obj/item/projectile/bullet/c38/mime_lethal/on_hit(atom/target, blocked)
+ . = ..()
+ if(iscarbon(target))
+ var/mob/living/carbon/M = target
+ M.silent = max(M.silent, 10)
// .357 (Syndie Revolver)
/obj/item/projectile/bullet/a357
name = ".357 bullet"
damage = 60
+
+// admin only really, for ocelot memes
+/obj/item/projectile/bullet/a357/match
+ name = ".357 match bullet"
+ ricochets_max = 5
+ ricochet_chance = 140
+ ricochet_auto_aim_angle = 50
+ ricochet_auto_aim_range = 6
+ ricochet_incidence_leeway = 80
+ ricochet_decay_chance = 1
diff --git a/code/modules/projectiles/projectile/bullets/shotgun.dm b/code/modules/projectiles/projectile/bullets/shotgun.dm
index e399460b79da8..349e7914fbe26 100644
--- a/code/modules/projectiles/projectile/bullets/shotgun.dm
+++ b/code/modules/projectiles/projectile/bullets/shotgun.dm
@@ -1,6 +1,7 @@
/obj/item/projectile/bullet/shotgun_slug
name = "12g shotgun slug"
damage = 60
+ armour_penetration = -20
/obj/item/projectile/bullet/shotgun_beanbag
name = "beanbag slug"
@@ -16,9 +17,12 @@
damage = 0
/obj/item/projectile/bullet/sleepy/on_hit(atom/target, blocked = FALSE)
- if((blocked != 100) && isliving(target))
+ if((blocked != 100) && ishuman(target))
var/mob/living/L = target
- L.Sleeping(50)
+ if(L.confused)
+ L.Sleeping(50)
+ else
+ L.confused = 80
return ..()
/obj/item/projectile/bullet/incendiary/shotgun/dragonsbreath
@@ -40,7 +44,7 @@
icon = 'icons/obj/meteor.dmi'
icon_state = "dust"
damage = 20
- paralyze = 80
+ paralyze = 20
hitsound = 'sound/effects/meteorimpact.ogg'
/obj/item/projectile/bullet/shotgun_meteorslug/on_hit(atom/target, blocked = FALSE)
@@ -57,7 +61,7 @@
/obj/item/projectile/bullet/shotgun_frag12
name ="frag12 slug"
damage = 25
- paralyze = 50
+ paralyze = 10
/obj/item/projectile/bullet/shotgun_frag12/on_hit(atom/target, blocked = FALSE)
..()
@@ -67,20 +71,24 @@
/obj/item/projectile/bullet/pellet
var/tile_dropoff = 0.75
var/tile_dropoff_s = 0.5
+ ricochets_max = 1
+ ricochet_chance = 50
+ ricochet_decay_chance = 0.9
/obj/item/projectile/bullet/pellet/shotgun_buckshot
name = "buckshot pellet"
- damage = 12.5
+ damage = 9
+ tile_dropoff = 0.5
/obj/item/projectile/bullet/pellet/shotgun_rubbershot
name = "rubbershot pellet"
damage = 3
- stamina = 11
+ stamina = 9
/obj/item/projectile/bullet/pellet/shotgun_incapacitate
name = "incapacitating pellet"
damage = 1
- stamina = 6
+ stamina = 5
/obj/item/projectile/bullet/pellet/Range()
..()
@@ -106,7 +114,7 @@
// Mech Scattershot
/obj/item/projectile/bullet/scattershot
- damage = 24
+ damage = 18
//Breaching Ammo
diff --git a/code/modules/projectiles/projectile/bullets/smg.dm b/code/modules/projectiles/projectile/bullets/smg.dm
index 4ebc3667f5075..4b5feae4fb667 100644
--- a/code/modules/projectiles/projectile/bullets/smg.dm
+++ b/code/modules/projectiles/projectile/bullets/smg.dm
@@ -19,3 +19,10 @@
name = "4.6x30mm incendiary bullet"
damage = 10
fire_stacks = 1
+
+//Slightly worse disabler, but fully automatic
+/obj/item/projectile/bullet/c46x30mm_rubber
+ name = "4.6x30mm rubber bullet"
+ damage_type = STAMINA
+ flag = "stamina"
+ damage = 20
diff --git a/code/modules/projectiles/projectile/energy/net_snare.dm b/code/modules/projectiles/projectile/energy/net_snare.dm
index 6149e9868af65..995975f126b07 100644
--- a/code/modules/projectiles/projectile/energy/net_snare.dm
+++ b/code/modules/projectiles/projectile/energy/net_snare.dm
@@ -3,6 +3,7 @@
icon_state = "e_netting"
damage = 10
damage_type = STAMINA
+ flag = "stamina"
hitsound = 'sound/weapons/taserhit.ogg'
range = 10
@@ -32,7 +33,7 @@
/obj/effect/nettingportal/Initialize()
. = ..()
var/obj/item/beacon/teletarget = null
-
+
for(var/obj/item/beacon/bea in GLOB.teleportbeacons)
if(is_eligible(bea) && bea.nettingportal) //is it quick dragnet beacon?
teletarget = bea
@@ -41,16 +42,16 @@
/obj/effect/nettingportal/proc/is_eligible(atom/movable/AM)
//this code has to be ported in so it is not abused
-
+
var/turf/T = get_turf(AM)
if(!T)
return FALSE
-
+
var/turf/S = get_turf(src)
- if (S.z != T.z) //cannot teleport to another Zlevel
+ if (S.get_virtual_z_level() != T.get_virtual_z_level()) //cannot teleport to another Zlevel
return FALSE
var/area/A = get_area(T)
- if(!A || A.noteleport)
+ if(!A || A.teleport_restriction)
return FALSE
return TRUE
diff --git a/code/modules/projectiles/projectile/energy/nuclear_particle.dm b/code/modules/projectiles/projectile/energy/nuclear_particle.dm
index d7674f3a6ee72..a0bb035c654db 100644
--- a/code/modules/projectiles/projectile/energy/nuclear_particle.dm
+++ b/code/modules/projectiles/projectile/energy/nuclear_particle.dm
@@ -3,9 +3,6 @@
name = "nuclear particle"
icon_state = "nuclear_particle"
pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE
- damage = 10
- damage_type = TOX
- irradiate = 2500 //enough to knockdown and induce vomiting
speed = 0.4
hitsound = 'sound/weapons/emitter2.ogg'
impact_type = /obj/effect/projectile/impact/xray
@@ -18,13 +15,44 @@
"purple" = "#FF00FF"
)
-/obj/item/projectile/energy/nuclear_particle/Initialize()
- . = ..()
+/obj/item/projectile/energy/nuclear_particle/proc/random_color_time()
//Random color time!
var/our_color = pick(particle_colors)
add_atom_colour(particle_colors[our_color], FIXED_COLOUR_PRIORITY)
set_light(4, 3, particle_colors[our_color]) //Range of 4, brightness of 3 - Same range as a flashlight
-/atom/proc/fire_nuclear_particle(angle = rand(0,360)) //used by fusion to fire random nuclear particles. Fires one particle in a random direction.
+/obj/item/projectile/energy/nuclear_particle/proc/customize(custompower)
+ irradiate = max(3000 * 3 ** (log(10,custompower)-FUSION_RAD_MIDPOINT),10)
+ var/custom_color = HSVtoRGB(hsv(clamp(log(10,custompower)-12,0,5)*256,rand(191,255),rand(191,255),255))
+ add_atom_colour(custom_color, FIXED_COLOUR_PRIORITY)
+ set_light(4, 3, custom_color)
+ switch (irradiate)
+ if(10 to 100)
+ name = "pathetically weak nuclear particle"
+ damage = 1
+ if(100 to 200)
+ name = "very weak nuclear particle"
+ damage = 2
+ if(200 to 500)
+ name = "fairly weak nuclear particle"
+ damage = 4
+ if(500 to 1500)
+ name = "slightly weak nuclear particle"
+ damage = 7
+ if(4000 to 8000)
+ name = "powerful nuclear particle"
+ damage = 15
+ if(8000 to 30000)
+ name = "extremely strong nuclear particle"
+ damage = 20
+ if(30000 to INFINITY)
+ name = "impossibly strong nuclear particle"
+ damage = 30
+
+/atom/proc/fire_nuclear_particle(angle = rand(0,360), customize = FALSE, custompower = 1e12) //used by fusion to fire random nuclear particles. Fires one particle in a random direction.
var/obj/item/projectile/energy/nuclear_particle/P = new /obj/item/projectile/energy/nuclear_particle(src)
+ if(customize)
+ P.customize(custompower)
+ else
+ P.random_color_time()
P.fire(angle)
\ No newline at end of file
diff --git a/code/modules/projectiles/projectile/energy/stun.dm b/code/modules/projectiles/projectile/energy/stun.dm
index f9f6242b9c47c..8f370cb168974 100644
--- a/code/modules/projectiles/projectile/energy/stun.dm
+++ b/code/modules/projectiles/projectile/energy/stun.dm
@@ -4,6 +4,7 @@
color = "#FFFF00"
damage = 40
damage_type = STAMINA
+ flag = "stamina"
nodamage = FALSE
knockdown = 30
stutter = 5
diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm
index 677782d22f2d6..81a3764619c6a 100644
--- a/code/modules/projectiles/projectile/magic.dm
+++ b/code/modules/projectiles/projectile/magic.dm
@@ -6,10 +6,12 @@
nodamage = TRUE
armour_penetration = 100
flag = "magic"
+ martial_arts_no_deflect = TRUE
/obj/item/projectile/magic/death
name = "bolt of death"
icon_state = "pulse1_bl"
+ martial_arts_no_deflect = FALSE
/obj/item/projectile/magic/death/on_hit(target)
. = ..()
@@ -26,6 +28,7 @@
damage = 0
damage_type = OXY
nodamage = TRUE
+ martial_arts_no_deflect = FALSE
/obj/item/projectile/magic/resurrection/on_hit(mob/living/carbon/target)
. = ..()
@@ -51,6 +54,7 @@
damage = 0
damage_type = OXY
nodamage = TRUE
+ martial_arts_no_deflect = FALSE
var/inner_tele_radius = 0
var/outer_tele_radius = 6
@@ -79,6 +83,7 @@
damage = 0
damage_type = OXY
nodamage = TRUE
+ martial_arts_no_deflect = FALSE
/obj/item/projectile/magic/safety/on_hit(atom/target)
. = ..()
@@ -134,6 +139,7 @@
damage = 0
damage_type = BURN
nodamage = TRUE
+ martial_arts_no_deflect = FALSE
/obj/item/projectile/magic/change/on_hit(atom/change)
. = ..()
@@ -340,6 +346,7 @@
flag = "magic"
dismemberment = 50
nodamage = FALSE
+ martial_arts_no_deflect = FALSE
/obj/item/projectile/magic/spellblade/on_hit(target)
if(ismob(target))
@@ -359,6 +366,7 @@
armour_penetration = 0
flag = "magic"
hitsound = 'sound/weapons/barragespellhit.ogg'
+ martial_arts_no_deflect = FALSE
/obj/item/projectile/magic/arcane_barrage/on_hit(target)
if(ismob(target))
@@ -375,14 +383,11 @@
icon_state = "locker"
nodamage = TRUE
flag = "magic"
+ martial_arts_no_deflect = FALSE
var/weld = TRUE
var/created = FALSE //prevents creation of more then one locker if it has multiple hits
var/locker_suck = TRUE
- var/obj/structure/closet/locker_temp_instance = /obj/structure/closet/decay
-/obj/item/projectile/magic/locker/Initialize()
- . = ..()
- locker_temp_instance = new(src)
/obj/item/projectile/magic/locker/prehit(atom/A)
if(isliving(A) && locker_suck)
@@ -391,7 +396,7 @@
M.visible_message("[src] vanishes on contact with [A]!")
qdel(src)
return
- if(!locker_temp_instance.insertion_allowed(M))
+ if(M.incorporeal_move || M.mob_size > MOB_SIZE_HUMAN || LAZYLEN(contents)>=5)
return ..()
M.forceMove(src)
return FALSE
@@ -400,11 +405,11 @@
/obj/item/projectile/magic/locker/on_hit(target)
if(created)
return ..()
- var/obj/structure/closet/C = new locker_temp_instance(get_turf(src))
+ var/obj/structure/closet/decay/C = new(get_turf(src))
if(LAZYLEN(contents))
for(var/atom/movable/AM in contents)
- C.insert(AM)
- C.welded = weld
+ AM.forceMove(C)
+ C.welded = TRUE
C.update_icon()
created = TRUE
return ..()
@@ -418,21 +423,21 @@
/obj/structure/closet/decay
breakout_time = 600
icon_welded = null
+ material_drop_amount = 0
var/magic_icon = "cursed"
var/weakened_icon = "decursed"
- var/auto_destroy = TRUE
/obj/structure/closet/decay/Initialize()
. = ..()
- if(auto_destroy)
- addtimer(CALLBACK(src, .proc/bust_open), 5 MINUTES)
- addtimer(CALLBACK(src, .proc/magicly_lock), 5)
+ addtimer(CALLBACK(src, .proc/locker_magic_timer), 5)
-/obj/structure/closet/decay/proc/magicly_lock()
- if(!welded)
- return
- icon_state = magic_icon
- update_icon()
+/obj/structure/closet/decay/proc/locker_magic_timer()
+ if(welded)
+ addtimer(CALLBACK(src, .proc/bust_open), 5 MINUTES)
+ icon_state = magic_icon
+ update_icon()
+ else
+ addtimer(CALLBACK(src, .proc/decay), 15 SECONDS)
/obj/structure/closet/decay/after_weld(weld_state)
if(weld_state)
@@ -454,11 +459,11 @@
icon_state = weakened_icon
update_icon()
addtimer(CALLBACK(src, .proc/decay), 15 SECONDS)
- icon_welded = "welded"
/obj/item/projectile/magic/flying
name = "bolt of flying"
icon_state = "flight"
+ martial_arts_no_deflect = FALSE
/obj/item/projectile/magic/flying/on_hit(target)
. = ..()
@@ -473,6 +478,7 @@
/obj/item/projectile/magic/bounty
name = "bolt of bounty"
icon_state = "bounty"
+ martial_arts_no_deflect = FALSE
/obj/item/projectile/magic/bounty/on_hit(target)
. = ..()
@@ -486,6 +492,7 @@
/obj/item/projectile/magic/antimagic
name = "bolt of antimagic"
icon_state = "antimagic"
+ martial_arts_no_deflect = FALSE
/obj/item/projectile/magic/antimagic/on_hit(target)
. = ..()
@@ -499,6 +506,7 @@
/obj/item/projectile/magic/fetch
name = "bolt of fetching"
icon_state = "fetch"
+ martial_arts_no_deflect = FALSE
/obj/item/projectile/magic/fetch/on_hit(target)
. = ..()
@@ -513,6 +521,7 @@
/obj/item/projectile/magic/sapping
name = "bolt of sapping"
icon_state = "sapping"
+ martial_arts_no_deflect = FALSE
/obj/item/projectile/magic/sapping/on_hit(target)
. = ..()
@@ -526,6 +535,7 @@
/obj/item/projectile/magic/necropotence
name = "bolt of necropotence"
icon_state = "necropotence"
+ martial_arts_no_deflect = FALSE
/obj/item/projectile/magic/necropotence/on_hit(target)
. = ..()
@@ -548,12 +558,13 @@
/obj/item/projectile/magic/wipe
name = "bolt of possession"
icon_state = "wipe"
+ martial_arts_no_deflect = FALSE
/obj/item/projectile/magic/wipe/on_hit(target)
. = ..()
if(iscarbon(target))
var/mob/living/carbon/M = target
- if(M.anti_magic_check() || istype(M.get_item_by_slot(SLOT_HEAD), /obj/item/clothing/head/foilhat))
+ if(M.anti_magic_check() || istype(M.get_item_by_slot(ITEM_SLOT_HEAD), /obj/item/clothing/head/foilhat))
M.visible_message("[src] vanishes on contact with [target]!")
return BULLET_ACT_BLOCK
for(var/x in M.get_traumas())//checks to see if the victim is already going through possession
@@ -597,6 +608,7 @@
desc = "What the fuck does this do?!"
damage = 0
var/proxdet = TRUE
+ martial_arts_no_deflect = FALSE
/obj/item/projectile/magic/aoe/Range()
if(proxdet)
@@ -621,6 +633,11 @@
var/chain
var/mob/living/caster
+/obj/item/projectile/magic/aoe/lightning/New(loc, spell_level)
+ . = ..()
+ tesla_power += 5000 * spell_level
+ tesla_range += 2 * spell_level
+
/obj/item/projectile/magic/aoe/lightning/fire(setAngle)
if(caster)
chain = caster.Beam(src, icon_state = "lightning[rand(1, 12)]", time = INFINITY, maxdistance = INFINITY)
@@ -654,6 +671,13 @@
var/exp_flash = 3
var/exp_fire = 2
+/obj/item/projectile/magic/aoe/fireball/New(loc, spell_level)
+ . = ..()
+ exp_fire += spell_level
+ exp_flash += spell_level
+ exp_light += spell_level
+ exp_heavy = max(spell_level - 2, 0)
+
/obj/item/projectile/magic/aoe/fireball/on_hit(target)
. = ..()
if(ismob(target))
diff --git a/code/modules/projectiles/projectile/magic/spellcard.dm b/code/modules/projectiles/projectile/magic/spellcard.dm
index 8b17d622bea1b..a3292d720026f 100644
--- a/code/modules/projectiles/projectile/magic/spellcard.dm
+++ b/code/modules/projectiles/projectile/magic/spellcard.dm
@@ -4,3 +4,7 @@
icon_state = "spellcard"
damage_type = BRUTE
damage = 2
+
+/obj/item/projectile/spellcard/New(loc, spell_level)
+ . = ..()
+ damage += spell_level
diff --git a/code/modules/projectiles/projectile/reusable/foam_dart.dm b/code/modules/projectiles/projectile/reusable/foam_dart.dm
index 7a17d225cf412..3ee843ac73944 100644
--- a/code/modules/projectiles/projectile/reusable/foam_dart.dm
+++ b/code/modules/projectiles/projectile/reusable/foam_dart.dm
@@ -8,6 +8,7 @@
icon_state = "foamdart_proj"
ammo_type = /obj/item/ammo_casing/caseless/foam_dart
range = 10
+ martial_arts_no_deflect = TRUE
var/modified = FALSE
var/obj/item/pen/pen = null
@@ -20,8 +21,9 @@
newcasing.modified = modified
var/obj/item/projectile/bullet/reusable/foam_dart/newdart = newcasing.BB
newdart.modified = modified
- newdart.damage = damage
- newdart.nodamage = nodamage
+ if(modified)
+ newdart.damage = 5
+ newdart.nodamage = FALSE
newdart.damage_type = damage_type
if(pen)
newdart.pen = pen
@@ -40,3 +42,4 @@
ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot
nodamage = FALSE
stamina = 25
+ martial_arts_no_deflect = FALSE
diff --git a/code/modules/projectiles/projectile/special/curse.dm b/code/modules/projectiles/projectile/special/curse.dm
index 2598c439706b7..0e0eaa1f21a49 100644
--- a/code/modules/projectiles/projectile/special/curse.dm
+++ b/code/modules/projectiles/projectile/special/curse.dm
@@ -36,7 +36,7 @@
/obj/item/projectile/curse_hand/Destroy()
if(arm)
arm.End()
- arm = null
+ QDEL_NULL(arm)
if(CHECK_BITFIELD(movement_type, UNSTOPPABLE))
playsound(src, 'sound/effects/curse3.ogg', 25, 1, -1)
var/turf/T = get_step(src, dir)
diff --git a/code/modules/projectiles/projectile/special/floral.dm b/code/modules/projectiles/projectile/special/floral.dm
index 4ecce442ba7eb..dc3d05fb2e54c 100644
--- a/code/modules/projectiles/projectile/special/floral.dm
+++ b/code/modules/projectiles/projectile/special/floral.dm
@@ -5,6 +5,7 @@
damage_type = TOX
nodamage = TRUE
flag = "energy"
+ martial_arts_no_deflect = TRUE
/obj/item/projectile/energy/florayield
name = "beta somatoray"
@@ -13,3 +14,4 @@
damage_type = TOX
nodamage = TRUE
flag = "energy"
+ martial_arts_no_deflect = TRUE
diff --git a/code/modules/projectiles/projectile/special/gravity.dm b/code/modules/projectiles/projectile/special/gravity.dm
index eb929e933a61f..1f315a6b94e00 100644
--- a/code/modules/projectiles/projectile/special/gravity.dm
+++ b/code/modules/projectiles/projectile/special/gravity.dm
@@ -20,7 +20,7 @@
/obj/item/projectile/gravityrepulse/on_hit()
. = ..()
T = get_turf(src)
- for(var/atom/movable/A in range(T, power))
+ for(var/atom/movable/A in range(power, T))
if(A == src || (firer && A == src.firer) || A.anchored || thrown_items[A])
continue
if(ismob(A)) //because (ismob(A) && A:mob_negates_gravity()) is a recipe for bugs.
@@ -30,7 +30,7 @@
var/throwtarget = get_edge_target_turf(src, get_dir(src, get_step_away(A, src)))
A.safe_throw_at(throwtarget,power+1,1, force = MOVE_FORCE_EXTREMELY_STRONG)
thrown_items[A] = A
- for(var/turf/F in range(T,power))
+ for(var/turf/F as() in RANGE_TURFS(power, T))
new /obj/effect/temp_visual/gravpush(F)
/obj/item/projectile/gravityattract
@@ -55,7 +55,7 @@
/obj/item/projectile/gravityattract/on_hit()
. = ..()
T = get_turf(src)
- for(var/atom/movable/A in range(T, power))
+ for(var/atom/movable/A in range(power, T))
if(A == src || (firer && A == src.firer) || A.anchored || thrown_items[A])
continue
if(ismob(A))
@@ -64,7 +64,7 @@
continue
A.safe_throw_at(T, power+1, 1, force = MOVE_FORCE_EXTREMELY_STRONG)
thrown_items[A] = A
- for(var/turf/F in range(T,power))
+ for(var/turf/F as() in RANGE_TURFS(power, T))
new /obj/effect/temp_visual/gravpush(F)
/obj/item/projectile/gravitychaos
@@ -89,8 +89,8 @@
/obj/item/projectile/gravitychaos/on_hit()
. = ..()
T = get_turf(src)
- for(var/atom/movable/A in range(T, power))
- if(A == src|| (firer && A == src.firer) || A.anchored || thrown_items[A])
+ for(var/atom/movable/A as mob|obj in range(power, T))
+ if(A == src || (firer && A == src.firer) || A.anchored || thrown_items[A])
continue
if(ismob(A))
var/mob/M = A
@@ -98,5 +98,5 @@
continue
A.safe_throw_at(get_edge_target_turf(A, pick(GLOB.cardinals)), power+1, 1, force = MOVE_FORCE_EXTREMELY_STRONG)
thrown_items[A] = A
- for(var/turf/Z in range(T,power))
+ for(var/turf/Z as() in RANGE_TURFS(power, T))
new /obj/effect/temp_visual/gravpush(Z)
diff --git a/code/modules/projectiles/projectile/special/hallucination.dm b/code/modules/projectiles/projectile/special/hallucination.dm
index a4751cf247174..0f3921c439bf3 100644
--- a/code/modules/projectiles/projectile/special/hallucination.dm
+++ b/code/modules/projectiles/projectile/special/hallucination.dm
@@ -176,6 +176,7 @@
/obj/item/projectile/hallucination/disabler
name = "disabler beam"
damage_type = STAMINA
+ flag = "stamina"
hal_icon_state = "omnilaser"
hal_fire_sound = 'sound/weapons/taser2.ogg'
hal_hitsound = 'sound/weapons/tap.ogg'
diff --git a/code/modules/projectiles/projectile/special/meteor.dm b/code/modules/projectiles/projectile/special/meteor.dm
index 146dea4077d1f..ff669970b313c 100644
--- a/code/modules/projectiles/projectile/special/meteor.dm
+++ b/code/modules/projectiles/projectile/special/meteor.dm
@@ -11,7 +11,10 @@
if(A == firer)
forceMove(A.loc)
return
- A.ex_act(EXPLODE_HEAVY)
+ if(isobj(A))
+ SSexplosions.med_mov_atom += A
+ else if(isturf(A))
+ SSexplosions.medturf += A
playsound(src.loc, 'sound/effects/meteorimpact.ogg', 40, 1)
for(var/mob/M in urange(10, src))
if(!M.stat)
diff --git a/code/modules/projectiles/projectile/special/neurotoxin.dm b/code/modules/projectiles/projectile/special/neurotoxin.dm
index 1a4a783816076..f48085115de2d 100644
--- a/code/modules/projectiles/projectile/special/neurotoxin.dm
+++ b/code/modules/projectiles/projectile/special/neurotoxin.dm
@@ -11,5 +11,5 @@
if(iscarbon(target))
var/mob/living/carbon/human/H = target
if(H.can_inject())
- H.Stun(100)
+ H.adjustStaminaLoss(40)
return ..()
diff --git a/code/modules/projectiles/projectile/special/vortex.dm b/code/modules/projectiles/projectile/special/vortex.dm
new file mode 100644
index 0000000000000..267ed935d9c83
--- /dev/null
+++ b/code/modules/projectiles/projectile/special/vortex.dm
@@ -0,0 +1,14 @@
+/obj/item/projectile/energy/vortex
+ name = "vortex beam"
+ alpha = 0
+ damage = 0
+ damage_type = BURN
+ reflectable = REFLECT_NORMAL
+ nodamage = FALSE
+ flag = "energy"
+ range = 10
+ pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE | PASSCLOSEDTURF | PASSMOB
+
+/obj/item/projectile/energy/vortex/Range()
+ new /obj/effect/temp_visual/hierophant/blast/vortex(get_turf(src), firer, FALSE)
+ return ..()
diff --git a/code/modules/projectiles/projectile/special/wormhole.dm b/code/modules/projectiles/projectile/special/wormhole.dm
index 0832f0cc76eb0..075db9472c953 100644
--- a/code/modules/projectiles/projectile/special/wormhole.dm
+++ b/code/modules/projectiles/projectile/special/wormhole.dm
@@ -5,12 +5,14 @@
damage = 0
nodamage = TRUE
pass_flags = PASSGLASS | PASSTABLE | PASSGRILLE | PASSMOB
- var/obj/item/gun/energy/wormhole_projector/gun
+ //Weakref to the thing that shot us
+ var/datum/weakref/gun
color = "#33CCFF"
tracer_type = /obj/effect/projectile/tracer/wormhole
impact_type = /obj/effect/projectile/impact/wormhole
muzzle_type = /obj/effect/projectile/muzzle/wormhole
hitscan = TRUE
+ martial_arts_no_deflect = TRUE
/obj/item/projectile/beam/wormhole/orange
name = "orange bluespace beam"
@@ -23,7 +25,8 @@
/obj/item/projectile/beam/wormhole/on_hit(atom/target)
- if(!gun)
+ var/obj/item/gun/energy/wormhole_projector/projector = gun.resolve()
+ if(!projector)
qdel(src)
- return
- gun.create_portal(src, get_turf(src))
+ return
+ projector.create_portal(src, get_turf(src))
diff --git a/code/modules/reagents/chem_splash.dm b/code/modules/reagents/chem_splash.dm
index b16392ecd161a..8a4274d99e99a 100644
--- a/code/modules/reagents/chem_splash.dm
+++ b/code/modules/reagents/chem_splash.dm
@@ -35,17 +35,14 @@
steam.start()
var/list/viewable = view(affected_range, epicenter)
-
var/list/accessible = list(epicenter)
for(var/i=1; i<=affected_range; i++)
- var/list/turflist = list()
- for(var/turf/T in (orange(i, epicenter) - orange(i-1, epicenter)))
- turflist |= T
- for(var/turf/T in turflist)
+ var/list/turflist = RANGE_TURFS(i, epicenter) - RANGE_TURFS(i-1, epicenter)
+ for(var/turf/T as() in turflist)
if(!(get_dir(T,epicenter) in GLOB.cardinals) && (abs(T.x - epicenter.x) == abs(T.y - epicenter.y) ))
turflist.Remove(T)
turflist.Add(T) // we move the purely diagonal turfs to the end of the list.
- for(var/turf/T in turflist)
+ for(var/turf/T as() in turflist)
if(accessible[T])
continue
for(var/thing in T.GetAtmosAdjacentTurfs(alldir = TRUE))
@@ -58,7 +55,7 @@
break
var/list/reactable = accessible
for(var/turf/T in accessible)
- for(var/atom/A in T.GetAllContents())
+ for(var/atom/A as() in T.GetAllContents())
if(!(A in viewable))
continue
reactable |= A
@@ -66,8 +63,7 @@
T.hotspot_expose(extra_heat*2, 5)
if(!reactable.len) //Nothing to react with. Probably means we're in nullspace.
return
- for(var/thing in reactable)
- var/atom/A = thing
+ for(var/atom/A as() in reactable)
var/distance = max(1,get_dist(A, epicenter))
var/fraction = 0.5/(2 ** distance) //50/25/12/6... for a 200u splash, 25/12/6/3... for a 100u, 12/6/3/1 for a 50u
splash_holder.reaction(A, TOUCH, fraction)
diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm
index 6ebade9615655..973859a70b98a 100644
--- a/code/modules/reagents/chemistry/holder.dm
+++ b/code/modules/reagents/chemistry/holder.dm
@@ -260,7 +260,7 @@
var/copy_amount = T.volume * part
if(preserve_data)
trans_data = T.data
- R.add_reagent(T.type, copy_amount * multiplier, trans_data)
+ R.add_reagent(T.type, copy_amount * multiplier, trans_data, chem_temp)
src.update_total()
R.update_total()
@@ -311,38 +311,9 @@
if(!C)
C = R.holder.my_atom
- if(ishuman(C))
- var/mob/living/carbon/human/H = C
- //Check if this mob's species is set and can process this type of reagent
- var/can_process = FALSE
- //If we somehow avoided getting a species or reagent_tag set, we'll assume we aren't meant to process ANY reagents (CODERS: SET YOUR SPECIES AND TAG!)
- if(H.dna && H.dna.species.reagent_tag)
- if((R.process_flags & SYNTHETIC) && (H.dna.species.reagent_tag & PROCESS_SYNTHETIC)) //SYNTHETIC-oriented reagents require PROCESS_SYNTHETIC
- can_process = TRUE
- if((R.process_flags & ORGANIC) && (H.dna.species.reagent_tag & PROCESS_ORGANIC)) //ORGANIC-oriented reagents require PROCESS_ORGANIC
- can_process = TRUE
-
- //If handle_reagents returns 0, it's doing the reagent removal on its own
- var/species_handled = !(H.dna.species.handle_reagents(H, R))
- can_process = can_process && !species_handled
- //If the mob can't process it, remove the reagent at it's normal rate without doing any addictions, overdoses, or on_mob_life() for the reagent
- if(!can_process)
- if(!species_handled)
- R.holder.remove_reagent(R.type, R.metabolization_rate)
- continue
- //We'll assume that non-human mobs lack the ability to process synthetic-oriented reagents (adjust this if we need to change that assumption)
- else
- if(R.process_flags == SYNTHETIC)
- R.holder.remove_reagent(R.type, R.metabolization_rate)
- continue
- //If you got this far, that means we can process whatever reagent this iteration is for. Handle things normally from here.
-
- if(!R.metabolizing)
- R.metabolizing = TRUE
- R.on_mob_metabolize(C)
if(C && R)
- if(C.reagent_check(R) != TRUE)
+ if(C.reagent_check(R) != TRUE) //Most relevant to Humans, this handles species-specific chem interactions.
if(liverless && !R.self_consuming) //need to be metabolized
continue
if(!R.metabolizing)
@@ -513,7 +484,7 @@
remove_reagent(B, (multiplier * cached_required_reagents[B]), safety = 1)
for(var/P in selected_reaction.results)
- multiplier = max(multiplier, 1) //this shouldnt happen ...
+ multiplier = max(multiplier, 1) //this shouldn't happen ...
SSblackbox.record_feedback("tally", "chemical_reaction", cached_results[P]*multiplier, P)
add_reagent(P, cached_results[P]*multiplier, null, chem_temp)
@@ -524,14 +495,14 @@
if(selected_reaction.mix_sound)
playsound(get_turf(cached_my_atom), selected_reaction.mix_sound, 80, 1)
- for(var/mob/M in seen)
+ for(var/mob/M as() in seen)
to_chat(M, "[iconhtml] [selected_reaction.mix_message]")
if(istype(cached_my_atom, /obj/item/slime_extract))
var/obj/item/slime_extract/ME2 = my_atom
ME2.Uses--
if(ME2.Uses <= 0) // give the notification that the slime core is dead
- for(var/mob/M in seen)
+ for(var/mob/M as() in seen)
to_chat(M, "[iconhtml] \The [my_atom]'s power is consumed in the reaction.")
ME2.name = "used slime extract"
ME2.desc = "This extract has been used up."
diff --git a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm
index 253be74411ff2..d9f184852272e 100644
--- a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm
@@ -24,7 +24,6 @@
circuit = /obj/item/circuitboard/machine/chem_dispenser
-
var/obj/item/stock_parts/cell/cell
var/powerefficiency = 0.1
var/amount = 30
@@ -103,12 +102,12 @@
if(panel_open)
. += "[src]'s maintenance hatch is open!"
if(in_range(user, src) || isobserver(user))
- . += "The status display reads:\n\
- Recharging [recharge_amount] power units per interval.\n\
- Power efficiency increased by [round((powerefficiency*1000)-100, 1)]%."
+ . += "The status display reads:\n"+\
+ "Recharging [recharge_amount] power units per interval.\n"+\
+ "Power efficiency increased by [round((powerefficiency*1000)-100, 1)]%."
-/obj/machinery/chem_dispenser/process()
- if (recharge_counter >= 4)
+/obj/machinery/chem_dispenser/process(delta_time)
+ if (recharge_counter >= 8)
if(!is_operational())
return
var/usedpower = cell.give(recharge_amount)
@@ -116,7 +115,7 @@
use_power(250*recharge_amount)
recharge_counter = 0
return
- recharge_counter++
+ recharge_counter += delta_time
/obj/machinery/chem_dispenser/proc/display_beaker()
var/mutable_appearance/b_o = beaker_overlay || mutable_appearance(icon, "disp_beaker")
@@ -158,7 +157,13 @@
/obj/machinery/chem_dispenser/contents_explosion(severity, target)
..()
if(beaker)
- beaker.ex_act(severity, target)
+ switch(severity)
+ if(EXPLODE_DEVASTATE)
+ SSexplosions.high_mov_atom += beaker
+ if(EXPLODE_HEAVY)
+ SSexplosions.med_mov_atom += beaker
+ if(EXPLODE_LIGHT)
+ SSexplosions.low_mov_atom += beaker
/obj/machinery/chem_dispenser/handle_atom_del(atom/A)
..()
@@ -176,6 +181,9 @@
ui = new(user, src, "ChemDispenser")
if(user.hallucinating())
ui.set_autoupdate(FALSE) //to not ruin the immersion by constantly changing the fake chemicals
+ //Seems like a pretty bad way to do it, but I think a better one would deserve a wider refactor including at least sleeper
+ else
+ ui.set_autoupdate(TRUE) // Cell charge
ui.open()
/obj/machinery/chem_dispenser/ui_data(mob/user)
@@ -220,11 +228,21 @@
return data
/obj/machinery/chem_dispenser/ui_act(action, params)
- if(..())
+ . = ..()
+ if(.) // Propagation only used by debug machine, but eh
+ return
+
+ switch(action)
+ if("eject")
+ replace_beaker(usr)
+ . = TRUE
+
+ if(!is_operational())
return
+
switch(action)
if("amount")
- if(!is_operational() || QDELETED(beaker))
+ if(QDELETED(beaker))
return
var/target = text2num(params["target"])
if(target in beaker.possible_transfer_amounts)
@@ -232,7 +250,7 @@
work_animation()
. = TRUE
if("dispense")
- if(!is_operational() || QDELETED(cell))
+ if(QDELETED(cell))
return
var/reagent_name = params["reagent"]
if(!recording_recipe)
@@ -251,18 +269,15 @@
recording_recipe[reagent_name] += amount
. = TRUE
if("remove")
- if(!is_operational() || recording_recipe)
+ if(recording_recipe)
return
var/amount = text2num(params["amount"])
if(beaker && (amount in beaker.possible_transfer_amounts))
beaker.reagents.remove_all(amount)
work_animation()
. = TRUE
- if("eject")
- replace_beaker(usr)
- . = TRUE
if("dispense_recipe")
- if(!is_operational() || QDELETED(cell))
+ if(QDELETED(cell))
return
var/list/chemicals_to_dispense = saved_recipes[params["recipe"]]
if(!LAZYLEN(chemicals_to_dispense))
@@ -288,20 +303,14 @@
recording_recipe[key] += dispense_amount
. = TRUE
if("clear_recipes")
- if(!is_operational())
- return
var/yesno = alert("Clear all recipes?",, "Yes","No")
if(yesno == "Yes")
saved_recipes = list()
. = TRUE
if("record_recipe")
- if(!is_operational())
- return
recording_recipe = list()
. = TRUE
if("save_recording")
- if(!is_operational())
- return
var/name = stripped_input(usr,"Name","What do you want to name this recipe?", "Recipe", MAX_NAME_LEN)
if(!usr.canUseTopic(src, !issilicon(usr)))
return
@@ -319,8 +328,6 @@
recording_recipe = null
. = TRUE
if("cancel_recording")
- if(!is_operational())
- return
recording_recipe = null
. = TRUE
@@ -404,7 +411,7 @@
return ..()
/obj/machinery/chem_dispenser/AltClick(mob/living/user)
- ..()
+ . = ..()
if(istype(user) && user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
replace_beaker(user)
@@ -484,19 +491,11 @@
desc = "Contains a large reservoir of soft drinks. This model has had its safeties shorted out."
obj_flags = CAN_BE_HIT | EMAGGED
flags_1 = NODECONSTRUCT_1
+ circuit = /obj/item/circuitboard/machine/chem_dispenser/drinks/fullupgrade
/obj/machinery/chem_dispenser/drinks/fullupgrade/Initialize()
. = ..()
dispensable_reagents |= emagged_reagents //adds emagged reagents
- component_parts = list()
- component_parts += new /obj/item/circuitboard/machine/chem_dispenser/drinks(null)
- component_parts += new /obj/item/stock_parts/matter_bin/bluespace(null)
- component_parts += new /obj/item/stock_parts/matter_bin/bluespace(null)
- component_parts += new /obj/item/stock_parts/capacitor/quadratic(null)
- component_parts += new /obj/item/stock_parts/manipulator/femto(null)
- component_parts += new /obj/item/stack/sheet/glass(null)
- component_parts += new /obj/item/stock_parts/cell/bluespace(null)
- RefreshParts()
/obj/machinery/chem_dispenser/drinks/beer
name = "booze dispenser"
@@ -537,19 +536,11 @@
desc = "Contains a large reservoir of the good stuff. This model has had its safeties shorted out."
obj_flags = CAN_BE_HIT | EMAGGED
flags_1 = NODECONSTRUCT_1
+ circuit = /obj/item/circuitboard/machine/chem_dispenser/drinks/beer/fullupgrade
/obj/machinery/chem_dispenser/drinks/beer/fullupgrade/Initialize()
. = ..()
dispensable_reagents |= emagged_reagents //adds emagged reagents
- component_parts = list()
- component_parts += new /obj/item/circuitboard/machine/chem_dispenser/drinks/beer(null)
- component_parts += new /obj/item/stock_parts/matter_bin/bluespace(null)
- component_parts += new /obj/item/stock_parts/matter_bin/bluespace(null)
- component_parts += new /obj/item/stock_parts/capacitor/quadratic(null)
- component_parts += new /obj/item/stock_parts/manipulator/femto(null)
- component_parts += new /obj/item/stack/sheet/glass(null)
- component_parts += new /obj/item/stock_parts/cell/bluespace(null)
- RefreshParts()
/obj/machinery/chem_dispenser/mutagen
name = "mutagen dispenser"
@@ -564,6 +555,8 @@
desc = "Creates and dispenses chemicals useful for botany."
flags_1 = NODECONSTRUCT_1
+ circuit = /obj/item/circuitboard/machine/chem_dispenser/mutagensaltpeter
+
dispensable_reagents = list(
/datum/reagent/toxin/mutagen,
/datum/reagent/saltpetre,
@@ -580,18 +573,6 @@
/datum/reagent/diethylamine)
upgrade_reagents = null
-/obj/machinery/chem_dispenser/mutagensaltpeter/Initialize()
- . = ..()
- component_parts = list()
- component_parts += new /obj/item/circuitboard/machine/chem_dispenser(null)
- component_parts += new /obj/item/stock_parts/matter_bin/bluespace(null)
- component_parts += new /obj/item/stock_parts/matter_bin/bluespace(null)
- component_parts += new /obj/item/stock_parts/capacitor/quadratic(null)
- component_parts += new /obj/item/stock_parts/manipulator/femto(null)
- component_parts += new /obj/item/stack/sheet/glass(null)
- component_parts += new /obj/item/stock_parts/cell/bluespace(null)
- RefreshParts()
-
/obj/machinery/chem_dispenser/mutagensaltpetersmall
name = "minor botanical chemical dispenser"
desc = "A botanical chemical dispenser on a budget."
@@ -615,25 +596,15 @@
b_o.pixel_x = -4
return b_o
-
-
/obj/machinery/chem_dispenser/fullupgrade //fully ugpraded stock parts, emagged
desc = "Creates and dispenses chemicals. This model has had its safeties shorted out."
obj_flags = CAN_BE_HIT | EMAGGED
flags_1 = NODECONSTRUCT_1
+ circuit = /obj/item/circuitboard/machine/chem_dispenser/fullupgrade
/obj/machinery/chem_dispenser/fullupgrade/Initialize()
. = ..()
dispensable_reagents |= emagged_reagents //adds emagged reagents
- component_parts = list()
- component_parts += new /obj/item/circuitboard/machine/chem_dispenser(null)
- component_parts += new /obj/item/stock_parts/matter_bin/bluespace(null)
- component_parts += new /obj/item/stock_parts/matter_bin/bluespace(null)
- component_parts += new /obj/item/stock_parts/capacitor/quadratic(null)
- component_parts += new /obj/item/stock_parts/manipulator/femto(null)
- component_parts += new /obj/item/stack/sheet/glass(null)
- component_parts += new /obj/item/stock_parts/cell/bluespace(null)
- RefreshParts()
/obj/machinery/chem_dispenser/abductor
name = "reagent synthesizer"
@@ -688,15 +659,3 @@
/datum/reagent/medicine/silibinin,
/datum/reagent/medicine/polypyr
)
-
-/obj/machinery/chem_dispenser/abductor/Initialize()
- . = ..()
- component_parts = list()
- component_parts += new /obj/item/circuitboard/machine/chem_dispenser(null)
- component_parts += new /obj/item/stock_parts/matter_bin/bluespace(null)
- component_parts += new /obj/item/stock_parts/matter_bin/bluespace(null)
- component_parts += new /obj/item/stock_parts/capacitor/quadratic(null)
- component_parts += new /obj/item/stock_parts/manipulator/femto(null)
- component_parts += new /obj/item/stack/sheet/glass(null)
- component_parts += new /obj/item/stock_parts/cell/bluespace(null)
- RefreshParts()
diff --git a/code/modules/reagents/chemistry/machinery/chem_heater.dm b/code/modules/reagents/chemistry/machinery/chem_heater.dm
index ddb5f6cd52a14..9b1b055d5cfa6 100644
--- a/code/modules/reagents/chemistry/machinery/chem_heater.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_heater.dm
@@ -12,7 +12,7 @@
var/obj/item/reagent_containers/beaker = null
var/target_temperature = 300
- var/heater_coefficient = 0.1
+ var/heater_coefficient = 0.05
var/on = FALSE
/obj/machinery/chem_heater/Destroy()
@@ -35,6 +35,7 @@
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
return
replace_beaker(user)
+ ui_update()
return
/obj/machinery/chem_heater/proc/replace_beaker(mob/living/user, obj/item/reagent_containers/new_beaker)
@@ -46,6 +47,7 @@
beaker = new_beaker
else
beaker = null
+ on = FALSE
update_icon()
return TRUE
@@ -57,16 +59,16 @@
/obj/machinery/chem_heater/examine(mob/user)
. = ..()
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Heating reagents at [heater_coefficient*1000]% speed."
+ . += "The status display reads: Heating reagents at [heater_coefficient*1000]% speed."
-/obj/machinery/chem_heater/process()
+/obj/machinery/chem_heater/process(delta_time)
..()
if(stat & NOPOWER)
return
if(on)
if(beaker && beaker.reagents.total_volume)
//keep constant with the chemical acclimator please
- beaker.reagents.adjust_thermal_energy((target_temperature - beaker.reagents.chem_temp) * heater_coefficient * SPECIFIC_HEAT_DEFAULT * beaker.reagents.total_volume)
+ beaker.reagents.adjust_thermal_energy((target_temperature - beaker.reagents.chem_temp) * heater_coefficient * delta_time * SPECIFIC_HEAT_DEFAULT * beaker.reagents.total_volume)
beaker.reagents.handle_reactions()
/obj/machinery/chem_heater/attackby(obj/item/I, mob/user, params)
@@ -83,7 +85,7 @@
return
replace_beaker(user, B)
to_chat(user, "You add [B] to [src].")
- updateUsrDialog()
+ ui_update()
update_icon()
return
return ..()
@@ -93,6 +95,11 @@
return ..()
+/obj/machinery/chem_heater/ui_requires_update(mob/user, datum/tgui/ui)
+ . = ..()
+ if(on && beaker)
+ . = TRUE
+
/obj/machinery/chem_heater/ui_state(mob/user)
return GLOB.default_state
@@ -127,13 +134,10 @@
on = !on
. = TRUE
if("temperature")
- var/target = params["target"]
- if(text2num(target) != null)
- target = text2num(target)
- . = TRUE
- if(.)
+ var/target = text2num(params["target"])
+ if(target != null)
target_temperature = clamp(target, 0, 1000)
+ . = TRUE
if("eject")
- on = FALSE
replace_beaker(usr)
. = TRUE
diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm
index 5d8aa479eb031..3a37ab3e5c561 100644
--- a/code/modules/reagents/chemistry/machinery/chem_master.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_master.dm
@@ -53,9 +53,21 @@
/obj/machinery/chem_master/contents_explosion(severity, target)
..()
if(beaker)
- beaker.ex_act(severity, target)
+ switch(severity)
+ if(EXPLODE_DEVASTATE)
+ SSexplosions.high_mov_atom += beaker
+ if(EXPLODE_HEAVY)
+ SSexplosions.med_mov_atom += beaker
+ if(EXPLODE_LIGHT)
+ SSexplosions.low_mov_atom += beaker
if(bottle)
- bottle.ex_act(severity, target)
+ switch(severity)
+ if(EXPLODE_DEVASTATE)
+ SSexplosions.high_mov_atom += bottle
+ if(EXPLODE_HEAVY)
+ SSexplosions.med_mov_atom += bottle
+ if(EXPLODE_LIGHT)
+ SSexplosions.low_mov_atom += bottle
/obj/machinery/chem_master/handle_atom_del(atom/A)
..()
@@ -63,8 +75,10 @@
beaker = null
reagents.clear_reagents()
update_icon()
+ ui_update()
else if(A == bottle)
bottle = null
+ ui_update()
/obj/machinery/chem_master/update_icon()
cut_overlays()
@@ -99,7 +113,7 @@
return
replace_beaker(user, B)
to_chat(user, "You add [B] to [src].")
- updateUsrDialog()
+ ui_update()
update_icon()
else if(!condi && istype(I, /obj/item/storage/pill_bottle))
if(bottle)
@@ -109,7 +123,7 @@
return
bottle = I
to_chat(user, "You add [I] into the dispenser slot.")
- updateUsrDialog()
+ ui_update()
else
return ..()
@@ -117,6 +131,7 @@
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
return
replace_beaker(user)
+ ui_update()
return
/obj/machinery/chem_master/proc/replace_beaker(mob/living/user, obj/item/reagent_containers/new_beaker)
@@ -190,185 +205,176 @@
if(..())
return
- if(action == "eject")
- replace_beaker(usr)
- return TRUE
-
- if(action == "ejectPillBottle")
- if(!bottle)
- return FALSE
- bottle.forceMove(drop_location())
- adjust_item_drop_location(bottle)
- bottle = null
- return TRUE
-
- if(action == "transfer")
- if(!beaker)
- return FALSE
- var/reagent = GLOB.name2reagent[params["id"]]
- var/amount = text2num(params["amount"])
- var/to_container = params["to"]
- // Custom amount
- if (amount == -1)
- amount = text2num(input(
- "Enter the amount you want to transfer:",
- name, ""))
- if (amount == null || amount <= 0)
- return FALSE
- if (to_container == "buffer")
- beaker.reagents.trans_id_to(src, reagent, amount)
- return TRUE
- if (to_container == "beaker" && mode)
- reagents.trans_id_to(beaker, reagent, amount)
- return TRUE
- if (to_container == "beaker" && !mode)
- reagents.remove_reagent(reagent, amount)
- return TRUE
- return FALSE
-
- if(action == "toggleMode")
- mode = !mode
- return TRUE
-
- if(action == "pillStyle")
- var/id = text2num(params["id"])
- chosenPillStyle = id
- return TRUE
-
- if(action == "create")
- if(reagents.total_volume == 0)
- return FALSE
- var/item_type = params["type"]
- // Get amount of items
- var/amount = text2num(params["amount"])
- if(amount == null)
- amount = text2num(input(usr,
- "Max 10. Buffer content will be split evenly.",
- "How many to make?", 1))
- amount = clamp(round(amount), 0, 10)
- if (amount <= 0)
- return FALSE
- // Get units per item
- var/vol_each = text2num(params["volume"])
- var/vol_each_text = params["volume"]
- var/vol_each_max = reagents.total_volume / amount
- if (item_type == "pill")
- vol_each_max = min(50, vol_each_max)
- else if (item_type == "patch")
- vol_each_max = min(40, vol_each_max)
- else if (item_type == "bottle")
- vol_each_max = min(30, vol_each_max)
- else if (item_type == "condimentPack")
- vol_each_max = min(10, vol_each_max)
- else if (item_type == "condimentBottle")
- vol_each_max = min(50, vol_each_max)
- else
- return FALSE
- if(vol_each_text == "auto")
- vol_each = vol_each_max
- if(vol_each == null)
- vol_each = text2num(input(usr,
- "Maximum [vol_each_max] units per item.",
- "How many units to fill?",
- vol_each_max))
- vol_each = clamp(vol_each, 0, vol_each_max)
- if(vol_each <= 0)
- return FALSE
- // Get item name
- var/name = params["name"]
- var/name_has_units = item_type == "pill" || item_type == "patch"
- if(!name)
- var/name_default = reagents.get_master_reagent_name()
- if (name_has_units)
- name_default += " ([vol_each]u)"
- name = stripped_input(usr,
- "Name:",
- "Give it a name!",
- name_default,
- MAX_NAME_LEN)
- if(!name || !reagents.total_volume || !src || QDELETED(src) || !usr.canUseTopic(src, !issilicon(usr)))
- return FALSE
- // Start filling
- if(item_type == "pill")
- var/obj/item/reagent_containers/pill/P
- var/target_loc = drop_location()
- var/drop_threshold = INFINITY
- if(bottle)
- var/datum/component/storage/STRB = bottle.GetComponent(
- /datum/component/storage)
- if(STRB)
- drop_threshold = STRB.max_items - bottle.contents.len
- for(var/i = 0; i < amount; i++)
- if(i < drop_threshold)
- P = new/obj/item/reagent_containers/pill(target_loc)
- else
- P = new/obj/item/reagent_containers/pill(drop_location())
- P.name = trim("[name] pill")
- if(chosenPillStyle == RANDOM_PILL_STYLE)
- P.icon_state ="pill[rand(1,21)]"
- else
- P.icon_state = "pill[chosenPillStyle]"
- if(P.icon_state == "pill4")
- P.desc = "A tablet or capsule, but not just any, a red one, one taken by the ones not scared of knowledge, freedom, uncertainty and the brutal truths of reality."
- adjust_item_drop_location(P)
- reagents.trans_to(P, vol_each, transfered_by = usr)
- return TRUE
- if(item_type == "patch")
- var/obj/item/reagent_containers/pill/patch/P
- for(var/i = 0; i < amount; i++)
- P = new/obj/item/reagent_containers/pill/patch(drop_location())
- P.name = trim("[name] patch")
- adjust_item_drop_location(P)
- reagents.trans_to(P, vol_each, transfered_by = usr)
- return TRUE
- if(item_type == "bottle")
- var/obj/item/reagent_containers/glass/bottle/P
- for(var/i = 0; i < amount; i++)
- P = new/obj/item/reagent_containers/glass/bottle(drop_location())
- P.name = trim("[name] bottle")
- adjust_item_drop_location(P)
- reagents.trans_to(P, vol_each, transfered_by = usr)
- return TRUE
- if(item_type == "condimentPack")
- var/obj/item/reagent_containers/food/condiment/pack/P
- for(var/i = 0; i < amount; i++)
- P = new/obj/item/reagent_containers/food/condiment/pack(drop_location())
- P.originalname = name
- P.name = trim("[name] pack")
- P.desc = "A small condiment pack. The label says it contains [name]."
- reagents.trans_to(P, vol_each, transfered_by = usr)
- return TRUE
- if(item_type == "condimentBottle")
- var/obj/item/reagent_containers/food/condiment/P
- for(var/i = 0; i < amount; i++)
- P = new/obj/item/reagent_containers/food/condiment(drop_location())
- P.originalname = name
- P.name = trim("[name] bottle")
- reagents.trans_to(P, vol_each, transfered_by = usr)
- return TRUE
- return FALSE
-
- if(action == "analyze")
- var/datum/reagent/R = GLOB.name2reagent[params["id"]]
- if(R)
- var/state = "Unknown"
- if(initial(R.reagent_state) == 1)
- state = "Solid"
- else if(initial(R.reagent_state) == 2)
- state = "Liquid"
- else if(initial(R.reagent_state) == 3)
- state = "Gas"
- var/const/P = 3 //The number of seconds between life ticks
- var/T = initial(R.metabolization_rate) * (60 / P)
- analyzeVars = list("name" = initial(R.name), "state" = state, "color" = initial(R.color), "description" = initial(R.description), "metaRate" = T, "overD" = initial(R.overdose_threshold), "addicD" = initial(R.addiction_threshold))
- screen = "analyze"
- return TRUE
-
- if(action == "goScreen")
- screen = params["screen"]
- return TRUE
-
- return FALSE
+ switch(action)
+ if("eject")
+ replace_beaker(usr)
+ . = TRUE
+ if("ejectPillBottle")
+ if(!bottle)
+ return
+ bottle.forceMove(drop_location())
+ adjust_item_drop_location(bottle)
+ bottle = null
+ . = TRUE
+ if("transfer")
+ if(!beaker)
+ return
+ var/reagent = GLOB.name2reagent[params["id"]]
+ var/amount = text2num(params["amount"])
+ var/to_container = params["to"]
+ // Custom amount
+ if (amount == -1)
+ amount = text2num(input(
+ "Enter the amount you want to transfer:",
+ name, ""))
+ if (amount == null || amount <= 0)
+ return
+ if (to_container == "buffer")
+ beaker.reagents.trans_id_to(src, reagent, amount)
+ . = TRUE
+ else if (to_container == "beaker" && mode)
+ reagents.trans_id_to(beaker, reagent, amount)
+ . = TRUE
+ else if (to_container == "beaker" && !mode)
+ reagents.remove_reagent(reagent, amount)
+ . = TRUE
+ if("toggleMode")
+ mode = !mode
+ . = TRUE
+ if("pillStyle")
+ var/id = text2num(params["id"])
+ chosenPillStyle = id
+ . = TRUE
+ if("create")
+ if(reagents.total_volume == 0)
+ return
+ var/item_type = params["type"]
+ // Get amount of items
+ var/amount = text2num(params["amount"])
+ if(amount == null)
+ amount = text2num(input(usr,
+ "Max 10. Buffer content will be split evenly.",
+ "How many to make?", 1))
+ amount = clamp(round(amount), 0, 10)
+ if (amount <= 0)
+ return
+ // Get units per item
+ var/vol_each = text2num(params["volume"])
+ var/vol_each_text = params["volume"]
+ var/vol_each_max = reagents.total_volume / amount
+ if (item_type == "pill" && !condi)
+ vol_each_max = min(50, vol_each_max)
+ else if (item_type == "patch" && !condi)
+ vol_each_max = min(40, vol_each_max)
+ else if (item_type == "bottle" && !condi)
+ vol_each_max = min(30, vol_each_max)
+ else if (item_type == "condimentPack" && condi)
+ vol_each_max = min(10, vol_each_max)
+ else if (item_type == "condimentBottle" && condi)
+ vol_each_max = min(50, vol_each_max)
+ else
+ return
+ if(vol_each_text == "auto")
+ vol_each = vol_each_max
+ if(vol_each == null)
+ vol_each = text2num(input(usr,
+ "Maximum [vol_each_max] units per item.",
+ "How many units to fill?",
+ vol_each_max))
+ vol_each = clamp(vol_each, 0, vol_each_max)
+ if(vol_each <= 0)
+ return
+ // Get item name
+ var/name = params["name"]
+ var/name_has_units = item_type == "pill" || item_type == "patch"
+ if(!name)
+ var/name_default = reagents.get_master_reagent_name()
+ if (name_has_units)
+ name_default += " ([vol_each]u)"
+ name = stripped_input(usr,
+ "Name:",
+ "Give it a name!",
+ name_default,
+ MAX_NAME_LEN)
+ if(!name || !reagents.total_volume || !src || QDELETED(src) || !usr.canUseTopic(src, !issilicon(usr)))
+ return
+ // Start filling
+ switch(item_type)
+ if("pill")
+ var/obj/item/reagent_containers/pill/P
+ var/target_loc = drop_location()
+ var/drop_threshold = INFINITY
+ if(bottle)
+ var/datum/component/storage/STRB = bottle.GetComponent(
+ /datum/component/storage)
+ if(STRB)
+ drop_threshold = STRB.max_items - bottle.contents.len
+ for(var/i = 0; i < amount; i++)
+ if(i < drop_threshold)
+ P = new/obj/item/reagent_containers/pill(target_loc)
+ else
+ P = new/obj/item/reagent_containers/pill(drop_location())
+ P.name = trim("[name] pill")
+ if(chosenPillStyle == RANDOM_PILL_STYLE)
+ P.icon_state ="pill[rand(1,21)]"
+ else
+ P.icon_state = "pill[chosenPillStyle]"
+ if(P.icon_state == "pill4")
+ P.desc = "A tablet or capsule, but not just any, a red one, one taken by the ones not scared of knowledge, freedom, uncertainty and the brutal truths of reality."
+ adjust_item_drop_location(P)
+ reagents.trans_to(P, vol_each, transfered_by = usr)
+ . = TRUE
+ if("patch")
+ var/obj/item/reagent_containers/pill/patch/P
+ for(var/i = 0; i < amount; i++)
+ P = new/obj/item/reagent_containers/pill/patch(drop_location())
+ P.name = trim("[name] patch")
+ adjust_item_drop_location(P)
+ reagents.trans_to(P, vol_each, transfered_by = usr)
+ . = TRUE
+ if("bottle")
+ var/obj/item/reagent_containers/glass/bottle/P
+ for(var/i = 0; i < amount; i++)
+ P = new/obj/item/reagent_containers/glass/bottle(drop_location())
+ P.name = trim("[name] bottle")
+ adjust_item_drop_location(P)
+ reagents.trans_to(P, vol_each, transfered_by = usr)
+ . = TRUE
+ if("condimentPack")
+ var/obj/item/reagent_containers/food/condiment/pack/P
+ for(var/i = 0; i < amount; i++)
+ P = new/obj/item/reagent_containers/food/condiment/pack(drop_location())
+ P.originalname = name
+ P.name = trim("[name] pack")
+ P.desc = "A small condiment pack. The label says it contains [name]."
+ reagents.trans_to(P, vol_each, transfered_by = usr)
+ . = TRUE
+ if("condimentBottle")
+ var/obj/item/reagent_containers/food/condiment/P
+ for(var/i = 0; i < amount; i++)
+ P = new/obj/item/reagent_containers/food/condiment(drop_location())
+ P.originalname = name
+ P.name = trim("[name] bottle")
+ reagents.trans_to(P, vol_each, transfered_by = usr)
+ . = TRUE
+ if("analyze")
+ var/datum/reagent/R = GLOB.name2reagent[params["id"]]
+ if(R && reagents.get_reagent_amount(R))
+ var/state = "Unknown"
+ if(initial(R.reagent_state) == 1)
+ state = "Solid"
+ else if(initial(R.reagent_state) == 2)
+ state = "Liquid"
+ else if(initial(R.reagent_state) == 3)
+ state = "Gas"
+ var/const/P = 3 //The number of seconds between life ticks
+ var/T = initial(R.metabolization_rate) * (60 / P)
+ analyzeVars = list("name" = initial(R.name), "state" = state, "color" = initial(R.color), "description" = initial(R.description), "metaRate" = T, "overD" = initial(R.overdose_threshold), "addicD" = initial(R.addiction_threshold))
+ screen = "analyze"
+ . = TRUE
+ if("goScreen")
+ screen = params["screen"]
+ . = TRUE
/obj/machinery/chem_master/proc/isgoodnumber(num)
diff --git a/code/modules/reagents/chemistry/machinery/chem_synthesizer.dm b/code/modules/reagents/chemistry/machinery/chem_synthesizer.dm
index b8ecac8aeb331..47dfbd62b4a3e 100644
--- a/code/modules/reagents/chemistry/machinery/chem_synthesizer.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_synthesizer.dm
@@ -24,6 +24,7 @@
if(!ui)
ui = new(user, src, "ChemDebugSynthesizer")
ui.open()
+ ui.set_autoupdate(TRUE) // Cell charge
/obj/machinery/chem_dispenser/chem_synthesizer/ui_act(action, params)
if(..())
@@ -55,16 +56,25 @@
else if(!beaker.reagents && !QDELETED(beaker))
beaker.create_reagents(beaker.volume)
beaker.reagents.add_reagent(input_reagent, amount)
+ . = TRUE
if("makecup")
if(beaker)
return
beaker = new /obj/item/reagent_containers/glass/beaker/bluespace(src)
visible_message("[src] dispenses a bluespace beaker.")
+ . = TRUE
if("amount")
var/input = text2num(params["amount"])
if(input)
amount = input
- update_icon()
+ . = TRUE
+ if(.)
+ update_icon()
+
+/obj/machinery/chem_dispenser/chem_synthesizer/Destroy()
+ if(beaker)
+ QDEL_NULL(beaker)
+ return ..()
/obj/machinery/chem_dispenser/chem_synthesizer/proc/find_reagent(input)
. = FALSE
diff --git a/code/modules/reagents/chemistry/machinery/pandemic.dm b/code/modules/reagents/chemistry/machinery/pandemic.dm
index 4280bec0ace04..372ec2f6abd38 100644
--- a/code/modules/reagents/chemistry/machinery/pandemic.dm
+++ b/code/modules/reagents/chemistry/machinery/pandemic.dm
@@ -38,7 +38,6 @@
. += "Alt-click to eject [is_close ? beaker : "the beaker"]."
/obj/machinery/computer/pandemic/AltClick(mob/user)
- . = ..()
if(user.canUseTopic(src, BE_CLOSE))
eject_beaker()
@@ -83,18 +82,18 @@
var/list/this_symptom = list()
this_symptom = get_symptom_data(S)
this["symptoms"] += list(this_symptom)
- this["resistance"] = A.totalResistance()
- this["stealth"] = A.totalStealth()
- this["stage_speed"] = A.totalStageSpeed()
- this["transmission"] = A.totalTransmittable()
- this["symptom_severity"] = A.totalSeverity()
+ this["resistance"] = A.resistance
+ this["stealth"] = A.stealth
+ this["stage_speed"] = A.stage_rate
+ this["transmission"] = A.transmission
+ this["symptom_severity"] = A.severity
this["index"] = index++
this["agent"] = D.agent
this["description"] = D.desc || "none"
this["spread"] = D.spread_text || "none"
this["cure"] = D.cure_text || "none"
- this["severity"] = D.severity || "none"
+ this["danger"] = D.danger || "none"
. += list(this)
@@ -106,7 +105,7 @@
this["stealth"] = S.stealth
this["resistance"] = S.resistance
this["stage_speed"] = S.stage_speed
- this["transmission"] = S.transmittable
+ this["transmission"] = S.transmission
this["level"] = S.level
this["neutered"] = S.neutered
this["threshold_desc"] = S.threshold_desc
@@ -130,6 +129,7 @@
/obj/machinery/computer/pandemic/proc/reset_replicator_cooldown()
wait = FALSE
update_icon()
+ SStgui.update_uis(src)
playsound(src, 'sound/machines/ping.ogg', 30, TRUE)
/obj/machinery/computer/pandemic/update_icon()
@@ -148,6 +148,7 @@
beaker.forceMove(drop_location())
beaker = null
update_icon()
+ ui_update()
/obj/machinery/computer/pandemic/ui_state(mob/user)
@@ -256,6 +257,7 @@
beaker = I
to_chat(user, "You insert [I] into [src].")
update_icon()
+ ui_update()
else
return ..()
diff --git a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm
index 4948f068da41d..578786c35932d 100644
--- a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm
+++ b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm
@@ -17,6 +17,7 @@
var/limit = 10
var/speed = 1
var/list/holdingitems
+ var/static/list/typecache_to_take
var/static/radial_examine = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_examine")
var/static/radial_eject = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_eject")
@@ -26,6 +27,8 @@
/obj/machinery/reagentgrinder/Initialize()
. = ..()
+ if(!typecache_to_take)
+ typecache_to_take = typecacheof(/obj/item/reagent_containers/food/snacks/grown)
holdingitems = list()
beaker = new /obj/item/reagent_containers/glass/beaker/large(src)
beaker.desc += " May contain blended dust. Don't breathe this in!"
@@ -39,12 +42,19 @@
/obj/machinery/reagentgrinder/Destroy()
if(beaker)
beaker.forceMove(drop_location())
+ beaker = null
drop_all_items()
return ..()
/obj/machinery/reagentgrinder/contents_explosion(severity, target)
if(beaker)
- beaker.ex_act(severity, target)
+ switch(severity)
+ if(EXPLODE_DEVASTATE)
+ SSexplosions.high_mov_atom += beaker
+ if(EXPLODE_HEAVY)
+ SSexplosions.med_mov_atom += beaker
+ if(EXPLODE_LIGHT)
+ SSexplosions.low_mov_atom += beaker
/obj/machinery/reagentgrinder/RefreshParts()
speed = 1
@@ -70,8 +80,8 @@
. += "- \A [O.name]."
if(!(stat & (NOPOWER|BROKEN)))
- . += {"The status display reads:\n
- - Grinding reagents at [speed*100]%."}
+ . += "The status display reads:\n"+\
+ "- Grinding reagents at [speed*100]%."
if(beaker)
for(var/datum/reagent/R in beaker.reagents.reagent_list)
. += "- [R.volume] units of [R.name]."
@@ -139,7 +149,7 @@
//Fill machine with a bag!
if(istype(I, /obj/item/storage/bag))
var/list/inserted = list()
- if(SEND_SIGNAL(I, COMSIG_TRY_STORAGE_TAKE_TYPE, /obj/item/reagent_containers/food/snacks/grown, src, limit - length(holdingitems), null, null, user, inserted))
+ if(SEND_SIGNAL(I, COMSIG_TRY_STORAGE_TAKE_TYPE, typecache_to_take, src, limit - length(holdingitems), null, null, user, inserted))
for(var/i in inserted)
holdingitems[i] = TRUE
if(!I.contents.len)
diff --git a/code/modules/reagents/chemistry/machinery/smoke_machine.dm b/code/modules/reagents/chemistry/machinery/smoke_machine.dm
index fb42e9e52fdf8..f9f64445a46c9 100644
--- a/code/modules/reagents/chemistry/machinery/smoke_machine.dm
+++ b/code/modules/reagents/chemistry/machinery/smoke_machine.dm
@@ -13,7 +13,6 @@
var/efficiency = 10
var/on = FALSE
var/cooldown = 0
- var/screen = "home"
var/useramount = 30 // Last used amount
var/setting = 1 // displayed range is 3 * setting
var/max_range = 3 // displayed max range is 3 * max range
@@ -38,6 +37,11 @@
for(var/obj/item/stock_parts/matter_bin/B in component_parts)
reagents.maximum_volume += REAGENTS_BASE_VOLUME * B.rating
+ AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS, null, CALLBACK(src, .proc/can_be_rotated))
+
+/obj/machinery/smoke_machine/proc/can_be_rotated(mob/user,rotation_type)
+ return !anchored
+
/obj/machinery/smoke_machine/update_icon()
if((!is_operational()) || (!on) || (reagents.total_volume == 0))
if (panel_open)
@@ -113,6 +117,7 @@
if(!ui)
ui = new(user, src, "SmokeMachine")
ui.open()
+ ui.set_autoupdate(TRUE) // Tank contents, particularly plumbing
/obj/machinery/smoke_machine/ui_data(mob/user)
var/data = list()
@@ -127,7 +132,6 @@
data["TankMaxVolume"] = reagents.maximum_volume
data["active"] = on
data["setting"] = setting
- data["screen"] = screen
data["maxSetting"] = max_range
return data
@@ -147,12 +151,10 @@
if("power")
on = !on
update_icon()
+ . = TRUE
if(on)
message_admins("[ADMIN_LOOKUPFLW(usr)] activated a smoke machine that contains [english_list(reagents.reagent_list)] at [ADMIN_VERBOSEJMP(src)].")
log_game("[key_name(usr)] activated a smoke machine that contains [english_list(reagents.reagent_list)] at [AREACOORD(src)].")
log_combat(usr, src, "has activated [src] which contains [english_list(reagents.reagent_list)] at [AREACOORD(src)].")
- if("goScreen")
- screen = params["screen"]
- . = TRUE
#undef REAGENTS_BASE_VOLUME
diff --git a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
index e98af0942281e..55ffe514e4d3d 100644
--- a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
@@ -41,7 +41,10 @@ All effects don't start immediately, but rather get worse over time; the rate is
if(HAS_TRAIT(C, TRAIT_ALCOHOL_TOLERANCE)) //we're an accomplished drinker
booze_power *= 0.7
if(HAS_TRAIT(C, TRAIT_LIGHT_DRINKER))
- booze_power *= 2
+ if(booze_power < 0)
+ booze_power *= -1
+ else
+ booze_power *= 2
C.drunkenness = max((C.drunkenness + (sqrt(volume) * booze_power * ALCOHOL_RATE)), 0) //Volume, power, and server alcohol rate effect how quickly one gets drunk
var/obj/item/organ/liver/L = C.getorganslot(ORGAN_SLOT_LIVER)
if (istype(L))
@@ -75,8 +78,8 @@ All effects don't start immediately, but rather get worse over time; the rate is
for(var/s in C.surgeries)
var/datum/surgery/S = s
- S.success_multiplier = max(0.1*power_multiplier, S.success_multiplier)
- // +10% success propability on each step, useful while operating in less-than-perfect conditions
+ S.speed_modifier = max(0.1*power_multiplier, S.speed_modifier)
+ // +10% surgery speed on each step, useful while operating in less-than-perfect conditions
return ..()
/datum/reagent/consumable/ethanol/beer
@@ -641,7 +644,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
/datum/reagent/consumable/ethanol/beepsky_smash/on_mob_metabolize(mob/living/carbon/M)
if(HAS_TRAIT(M, TRAIT_ALCOHOL_TOLERANCE))
metabolization_rate = 0.8
- if(!HAS_TRAIT(M.mind, TRAIT_LAW_ENFORCEMENT_METABOLISM))
+ if(M.mind && !HAS_TRAIT(M.mind, TRAIT_LAW_ENFORCEMENT_METABOLISM))
B = new()
M.gain_trauma(B, TRAUMA_RESILIENCE_ABSOLUTE)
ADD_TRAIT(M, TRAIT_NOBLOCK, type) //sorry sec, but you dont get a special stam heal to help with blocking
@@ -649,7 +652,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
/datum/reagent/consumable/ethanol/beepsky_smash/on_mob_life(mob/living/carbon/M)
M.Jitter(2)
- if(HAS_TRAIT(M.mind, TRAIT_LAW_ENFORCEMENT_METABOLISM))
+ if(M.mind && HAS_TRAIT(M.mind, TRAIT_LAW_ENFORCEMENT_METABOLISM))
M.adjustStaminaLoss(-10, 0)
if(prob(20))
new /datum/hallucination/items_other(M)
@@ -665,7 +668,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
return ..()
/datum/reagent/consumable/ethanol/beepsky_smash/overdose_start(mob/living/carbon/M)
- if(!HAS_TRAIT(M.mind, TRAIT_LAW_ENFORCEMENT_METABOLISM))
+ if(M.mind && !HAS_TRAIT(M.mind, TRAIT_LAW_ENFORCEMENT_METABOLISM))
M.gain_trauma(/datum/brain_trauma/mild/phobia/security, TRAUMA_RESILIENCE_BASIC)
@@ -1457,7 +1460,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
/datum/reagent/consumable/ethanol/quadruple_sec
name = "Quadruple Sec"
- description = "Kicks just as hard as licking the powercell on a baton, but tastier."
+ description = "Kicks just as hard as licking the power cell on a baton, but tastier."
color = "#cc0000"
boozepwr = 35
quality = DRINK_GOOD
@@ -1468,7 +1471,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
/datum/reagent/consumable/ethanol/quadruple_sec/on_mob_life(mob/living/carbon/M)
//Securidrink in line with the Screwdriver for engineers or Nothing for mimes
- if(HAS_TRAIT(M.mind, TRAIT_LAW_ENFORCEMENT_METABOLISM))
+ if(M.mind && HAS_TRAIT(M.mind, TRAIT_LAW_ENFORCEMENT_METABOLISM))
M.heal_bodypart_damage(1, 1)
M.adjustBruteLoss(-2,0)
. = 1
@@ -1488,7 +1491,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
/datum/reagent/consumable/ethanol/quintuple_sec/on_mob_life(mob/living/carbon/M)
//Securidrink in line with the Screwdriver for engineers or Nothing for mimes but STRONG..
- if(HAS_TRAIT(M.mind, TRAIT_LAW_ENFORCEMENT_METABOLISM))
+ if(M.mind && HAS_TRAIT(M.mind, TRAIT_LAW_ENFORCEMENT_METABOLISM))
M.heal_bodypart_damage(2,2,2)
M.adjustBruteLoss(-5,0)
M.adjustOxyLoss(-5,0)
@@ -1604,7 +1607,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
/datum/reagent/consumable/ethanol/crevice_spike
name = "Crevice Spike"
- description = "Sour, bitter, and smashingly sobering."
+ description = "Sour, bitter, and smashingly sobering. Doesn't sober up light drinkers."
color = "#5BD231"
boozepwr = -10 //sobers you up - ideally, one would drink to get hit with brute damage now to avoid alcohol problems later
quality = DRINK_VERYGOOD
@@ -1769,7 +1772,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
/datum/reagent/consumable/ethanol/fanciulli
name = "Fanciulli"
- description = "What if the Manhattan coctail ACTUALLY used a bitter herb liquour? Helps you sobers up." //also causes a bit of stamina damage to symbolize the afterdrink lazyness
+ description = "What if the Manhattan coctail ACTUALLY used a bitter herb liquour? Helps you sobers up. Doesn't sober up light drinkers." //also causes a bit of stamina damage to symbolize the afterdrink lazyness
color = "#CA933F" // rgb: 202, 147, 63
boozepwr = -10
quality = DRINK_NICE
@@ -2094,7 +2097,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_desc = "boozy Catholicism in a glass."
/datum/reagent/consumable/ethanol/trappist/on_mob_life(mob/living/carbon/M)
- if(M.mind.isholy)
+ if(M.mind?.holy_role)
M.adjustFireLoss(-2.5, 0)
M.jitteriness = max(0, M.jitteriness-1)
M.stuttering = max(0, M.stuttering-1)
@@ -2339,3 +2342,20 @@ All effects don't start immediately, but rather get worse over time; the rate is
M.adjustFireLoss(-1.5, 0)
M.adjustToxLoss(-1, 0)
. = ..()
+
+/datum/reagent/consumable/ethanol/beeffizz
+ name = "Beef Fizz"
+ description = "This is beef fizz, BEEF FIZZ, THERE IS NO GOD"
+ boozepwr = 15
+ quality = DRINK_BAD
+ taste_description = "Nice and Salty Fizzless Beef Juice with a quick bite of lemon"
+ glass_icon_state = "beef_fizz"
+ glass_name = "Beef Fizz"
+ glass_desc = "WHO THOUGHT THIS WAS A GOOD IDEA??"
+
+
+/datum/reagent/consumable/beeffizz/on_mob_metabolize(mob/living/M)
+ to_chat(M, "That drink was way too beefy! You feel sick.")
+ M.adjust_disgust(30)
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "quality_drink", /datum/mood_event/quality_bad)
+ . = ..()
\ No newline at end of file
diff --git a/code/modules/reagents/chemistry/reagents/drink_reagents.dm b/code/modules/reagents/chemistry/reagents/drink_reagents.dm
index fcecc8bddcd35..66d62045aaa83 100644
--- a/code/modules/reagents/chemistry/reagents/drink_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/drink_reagents.dm
@@ -162,6 +162,15 @@
SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "chemical_laughter", /datum/mood_event/chemical_laughter)
..()
+/datum/reagent/consumable/laughter/reaction_mob(mob/living/M, method=TOUCH, reac_volume)
+ var/mob/living/carbon/human/reactor = M
+ if(istype(reactor))
+ var/datum/component/mood/mood = reactor.GetComponent(/datum/component/mood)
+ if (mood.get_event("slipped"))
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "laughter", /datum/mood_event/funny_prank)
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "slipped")
+ reactor.AdjustKnockdown(-20)
+
/datum/reagent/consumable/superlaughter
name = "Super Laughter"
description = "Funny until you're the one laughing."
@@ -200,6 +209,7 @@
glass_icon_state = "glass_white"
glass_name = "glass of milk"
glass_desc = "White and nutritious goodness!"
+ overdose_threshold = 500 //High calcium intake is bad for bone health. OD is exactly like having taken a normal-ish bone hurt juice. If anyone hits the superoverdose, well I'll be damned
/datum/reagent/consumable/milk/on_mob_life(mob/living/carbon/M)
if(M.getBruteLoss() && prob(20))
@@ -209,6 +219,31 @@
holder.remove_reagent(/datum/reagent/consumable/capsaicin, 2)
..()
+/*See block comment in ../milk/overdose_process(mob/living/M) for calculation and explanation of why this exists and why 5 was chosen
+* For best results use in tandem with method outlined in this comment
+*/
+/datum/reagent/consumable/milk/overdose_start(mob/living/M)
+ M.reagents.add_reagent(/datum/reagent/toxin/bonehurtingjuice, 5) //The integer here should match var/starting_amount in ../milk/overdose_process(mob/living/M)
+ return ..()
+
+/datum/reagent/consumable/milk/overdose_process(mob/living/M)
+ var/datum/reagent/converted_reagent = /datum/reagent/toxin/bonehurtingjuice //Needed to get the metabolism for desired reagent, exists solely for brevity compared to /datum/reagent/category/reagent.metabolization_rate
+ var/minimum_cycles = overdose_threshold/metabolization_rate //minimum_cycles is the number of ticks for an amount of units equal to the overdose threshold to process.
+ var/amount_to_add = 45 / minimum_cycles + initial(converted_reagent.metabolization_rate) //amount_to_add is the calculated amount to add per tick to meet ensure that target_units after minimum_cycle ticks.
+ M.reagents.add_reagent(/datum/reagent/toxin/bonehurtingjuice, amount_to_add)
+ return ..()
+ /*In depth explanation by DatBoiTim
+ * This number will not put more than 50u of BHJ into their system if only 500u(ie bare minimum OD).
+ * milk.overdose_threshold / milk.metabolization_rate = minimum_cycles = 1,250 cycles
+ * (target_units / total_cycles) + BHJ.metabolization_rate = amount_to_add = .44
+ * However, regular livers process 1u per tick of any toxin if it is under 3u. This does not account for others, since most others are likely upgrades, and having a workaround for those upgrades defeats their purpose.
+ * Meaning we need a starting amount to offset this which is more than three. Ideally this should yield the lowest amount of decimal spaces to save space, while being as low as possible.
+ * In this case starting_amount = 5.
+ * ( (target_units - starting_amount) / minimum_cycles) + BHJ.metabolization_rate = amount_to_add = .436
+ * Copy pasting the above and changing /datum/reagent/toxin/bonehurtingjuice as well as the documentation to be accurate for another type path will work so long as the reagent using this has an OD threshold.
+ * You can just change the target units and should double check that the starting amount meets outlined criteria.
+ */
+
/datum/reagent/consumable/soymilk
name = "Soy Milk"
description = "An opaque white liquid made from soybeans."
@@ -410,7 +445,7 @@
taste_description = "carbonated oil"
glass_icon_state = "grey_bull_glass"
glass_name = "glass of Grey Bull"
- glass_desc = "Surprisingly it isnt grey."
+ glass_desc = "Surprisingly it isn't grey."
/datum/reagent/consumable/grey_bull/on_mob_metabolize(mob/living/L)
..()
@@ -642,7 +677,7 @@
M.adjustToxLoss(-0.5, 0)
M.adjustOxyLoss(-0.5, 0)
if(M.nutrition && (M.nutrition - 2 > 0))
- if(!(M.mind && M.mind.assigned_role == "Medical Doctor")) //Drains the nutrition of the holder. Not medical doctors though, since it's the Doctor's Delight!
+ if(M.mind?.assigned_role != "Medical Doctor") //Drains the nutrition of the holder. Not medical doctors though, since it's the Doctor's Delight!
M.adjust_nutrition(-2)
..()
. = 1
@@ -813,12 +848,13 @@
glass_name = "Red Queen"
glass_desc = "DRINK ME."
random_unrestricted = TRUE
- var/current_size = 1
+ var/current_size = RESIZE_DEFAULT_SIZE
/datum/reagent/consumable/red_queen/on_mob_life(mob/living/carbon/H)
if(prob(75))
return ..()
var/newsize = pick(0.5, 0.75, 1, 1.50, 2)
+ newsize *= RESIZE_DEFAULT_SIZE
H.resize = newsize/current_size
current_size = newsize
H.update_transform()
@@ -827,15 +863,30 @@
..()
/datum/reagent/consumable/red_queen/on_mob_end_metabolize(mob/living/M)
- M.resize = 1/current_size
+ M.resize = RESIZE_DEFAULT_SIZE/current_size
+ current_size = RESIZE_DEFAULT_SIZE
M.update_transform()
..()
/datum/reagent/consumable/bungojuice
name = "Bungo Juice"
color = "#F9E43D"
- description = "Exotic! You feel like you are on vactation already."
+ description = "Exotic! You feel like you are on vacation already."
taste_description = "succulent bungo"
glass_icon_state = "glass_yellow"
glass_name = "glass of bungo juice"
- glass_desc = "Exotic! You feel like you are on vactation already."
+ glass_desc = "Exotic! You feel like you are on vacation already."
+
+/datum/reagent/consumable/beefbroth
+ name = "Beef Broth"
+ color = "#100800" // rgb: 16, 8, 0 , just like cola
+ taste_description = "Pure Beef Essence"
+ glass_icon_state = "glass_brown"
+ glass_name = "glass of Space Cola?"
+ glass_desc = "A glass of what appears to be refreshing Space Cola."
+
+/datum/reagent/consumable/beefbroth/on_mob_metabolize(mob/living/M)
+ to_chat(M, "That drink was way too beefy! You feel sick.")
+ M.adjust_disgust(30)
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "quality_drink", /datum/mood_event/quality_bad)
+ . = ..()
diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm
index a04b0850fb486..98143b38b42f7 100644
--- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm
@@ -166,12 +166,17 @@
/datum/reagent/drug/krokodil/addiction_act_stage4(mob/living/carbon/human/M)
CHECK_DNA_AND_SPECIES(M)
- if(!istype(M.dna.species, /datum/species/krokodil_addict))
- to_chat(M, "Your skin falls off easily!")
- M.adjustBruteLoss(50*REM, 0) // holy shit your skin just FELL THE FUCK OFF
- M.set_species(/datum/species/krokodil_addict)
+ if(ishumanbasic(M))
+ if(!istype(M.dna.species, /datum/species/krokodil_addict))
+ to_chat(M, "Your skin falls off easily!")
+ M.adjustBruteLoss(50*REM, 0) // holy shit your skin just FELL THE FUCK OFF
+ M.set_species(/datum/species/krokodil_addict)
+ else
+ M.adjustBruteLoss(5*REM, 0)
else
- M.adjustBruteLoss(5*REM, 0)
+ to_chat(M, "Your skin peels and tears!")
+ M.adjustBruteLoss(5*REM, 0) // repeats 5 times and then you get over it
+
..()
. = 1
@@ -190,10 +195,12 @@
if (L.client)
SSmedals.UnlockMedal(MEDAL_APPLY_REAGENT_METH,L.client)
- L.add_movespeed_modifier(type, update=TRUE, priority=100, multiplicative_slowdown=-2, blacklisted_movetypes=(FLYING|FLOATING))
+ L.add_movespeed_modifier(type, update=TRUE, priority=100, multiplicative_slowdown=-1.25, blacklisted_movetypes=(FLYING|FLOATING))
+ ADD_TRAIT(L, TRAIT_SLEEPIMMUNE, type)
/datum/reagent/drug/methamphetamine/on_mob_end_metabolize(mob/living/L)
REMOVE_TRAIT(L, TRAIT_NOBLOCK, type)
+ REMOVE_TRAIT(L, TRAIT_SLEEPIMMUNE, type)
L.remove_movespeed_modifier(type)
..()
@@ -206,7 +213,8 @@
M.AdjustUnconscious(-40, FALSE)
M.AdjustParalyzed(-40, FALSE)
M.AdjustImmobilized(-40, FALSE)
- M.adjustStaminaLoss(-30, 0)
+ M.adjustStaminaLoss(-40, 0)
+ M.drowsyness = max(0,M.drowsyness-30)
M.Jitter(2)
M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1)
if(prob(5))
@@ -471,3 +479,85 @@
M.emote(pick("twitch","laugh","frown"))
..()
. = 1
+
+//I had to do too much research on this to make this a thing. Hopefully the FBI won't kick my door down.
+/datum/reagent/drug/ketamine
+ name = "Ketamine"
+ description = "A heavy duty tranquilizer found to also invoke feelings of euphoria, and assist with pain. Popular at parties and amongst small frogmen who drive Honda Civics."
+ reagent_state = LIQUID
+ color = "#c9c9c9"
+ metabolization_rate = 0.5 * REAGENTS_METABOLISM
+ addiction_threshold = 8
+ overdose_threshold = 16
+
+/datum/reagent/drug/ketamine/on_mob_metabolize(mob/living/L)
+ ADD_TRAIT(L, TRAIT_IGNOREDAMAGESLOWDOWN, type)
+ . = ..()
+
+/datum/reagent/drug/ketamine/on_mob_delete(mob/living/L)
+ REMOVE_TRAIT(L, TRAIT_IGNOREDAMAGESLOWDOWN, type)
+ . = ..()
+
+/datum/reagent/drug/ketamine/on_mob_life(mob/living/carbon/M)
+ //Friendly Reminder: Ketamine is a tranquilizer and will sleep you.
+ switch(current_cycle)
+ if(10)
+ to_chat(M, "You start to feel tired..." )
+ if(11 to 25)
+ M.drowsyness ++
+ if(26 to INFINITY)
+ M.Sleeping(60, 0)
+ . = 1
+ //Providing a Mood Boost
+ M.confused -= 3
+ M.jitteriness -= 5
+ M.disgust -= 3
+ //Ketamine is also a dissociative anasthetic which means Hallucinations!
+ M.hallucination += 5
+ ..()
+
+/datum/reagent/drug/ketamine/overdose_process(mob/living/M)
+ //Dissociative anesthetics? Overdosing? Time to dissociate hard.
+ var/obj/item/organ/brain/B = M.getorgan(/obj/item/organ/brain)
+ if(B.can_gain_trauma(/datum/brain_trauma/severe/split_personality, 5))
+ B.brain_gain_trauma(/datum/brain_trauma/severe/split_personality, 5)
+ . = 1
+ M.hallucination += 10
+ //Uh Oh Someone is tired
+ if(prob(40))
+ if(HAS_TRAIT(M, TRAIT_IGNOREDAMAGESLOWDOWN))
+ REMOVE_TRAIT(M, TRAIT_IGNOREDAMAGESLOWDOWN, type)
+ if(prob(33))
+ to_chat(M, "Your limbs begin to feel heavy...")
+ else if(prob(33))
+ to_chat(M, "It feels hard to move...")
+ else
+ to_chat(M, "You feel like you your limbs won't move...")
+ M.drop_all_held_items()
+ M.Dizzy(5)
+ ..()
+
+//Addiction Gradient
+/datum/reagent/drug/ketamine/addiction_act_stage1(mob/living/M)
+ if(prob(20))
+ M.drop_all_held_items()
+ M.Jitter(2)
+ ..()
+
+/datum/reagent/drug/ketamine/addiction_act_stage2(mob/living/M)
+ if(prob(30))
+ M.drop_all_held_items()
+ M.adjustToxLoss(2*REM, 0)
+ . = 1
+ M.Jitter(3)
+ M.Dizzy(3)
+ ..()
+
+/datum/reagent/drug/ketamine/addiction_act_stage3(mob/living/M)
+ if(prob(40))
+ M.drop_all_held_items()
+ M.adjustToxLoss(3*REM, 0)
+ . = 1
+ M.Jitter(4)
+ M.Dizzy(4)
+ ..()
diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm
index 7e67208f9f5eb..5e0e69fd8fbac 100755
--- a/code/modules/reagents/chemistry/reagents/food_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm
@@ -19,7 +19,7 @@
current_cycle++
if(ishuman(M))
var/mob/living/carbon/human/H = M
- if(!HAS_TRAIT(H, TRAIT_NOHUNGER))
+ if(!HAS_TRAIT(H, TRAIT_NOHUNGER) && !HAS_TRAIT(H, TRAIT_POWERHUNGRY))
H.adjust_nutrition(nutriment_factor)
holder.remove_reagent(type, metabolization_rate)
@@ -284,17 +284,15 @@
var/mob/living/carbon/victim = M
if(method == TOUCH || method == VAPOR)
- var/pepper_proof = victim.is_pepper_proof()
-
//check for protection
//actually handle the pepperspray effects
- if (!(pepper_proof)) // you need both eye and mouth protection
- if(prob(5))
- victim.emote("scream")
+ if(!victim.is_eyes_covered() || !victim.is_mouth_covered())
victim.blur_eyes(5) // 10 seconds
victim.blind_eyes(3) // 6 seconds
- victim.confused = max(M.confused, 5) // 10 seconds
victim.Knockdown(3 SECONDS)
+ if(prob(5))
+ victim.emote("scream")
+ victim.confused = max(M.confused, 5) // 10 seconds
victim.add_movespeed_modifier(MOVESPEED_ID_PEPPER_SPRAY, update=TRUE, priority=100, multiplicative_slowdown=0.25, blacklisted_movetypes=(FLYING|FLOATING))
addtimer(CALLBACK(victim, /mob.proc/remove_movespeed_modifier, MOVESPEED_ID_PEPPER_SPRAY), 10 SECONDS)
victim.update_damage_hud()
@@ -358,9 +356,10 @@
-/datum/reagent/consumable/hot_cocoa
+/datum/reagent/consumable/cocoa/hot_cocoa
name = "Hot Chocolate"
description = "Made with love! And cocoa beans."
+ reagent_state = LIQUID
nutriment_factor = 3 * REAGENTS_METABOLISM
color = "#403010" // rgb: 64, 48, 16
taste_description = "creamy chocolate"
@@ -368,7 +367,7 @@
glass_name = "glass of chocolate"
glass_desc = "Tasty."
-/datum/reagent/consumable/hot_cocoa/on_mob_life(mob/living/carbon/M)
+/datum/reagent/consumable/cocoa/hot_cocoa/on_mob_life(mob/living/carbon/M)
M.adjust_bodytemperature(5 * TEMPERATURE_DAMAGE_COEFFICIENT, 0, BODYTEMP_NORMAL)
..()
@@ -432,7 +431,7 @@
taste_description = "childhood whimsy"
/datum/reagent/consumable/sprinkles/on_mob_life(mob/living/carbon/M)
- if(HAS_TRAIT(M.mind, TRAIT_LAW_ENFORCEMENT_METABOLISM))
+ if(M.mind && HAS_TRAIT(M.mind, TRAIT_LAW_ENFORCEMENT_METABOLISM))
M.heal_bodypart_damage(1,1, 0)
. = 1
..()
@@ -450,10 +449,9 @@
T.MakeSlippery(TURF_WET_LUBE, min_wet_time = 10 SECONDS, wet_time_to_add = reac_volume*2 SECONDS)
var/obj/effect/hotspot/hotspot = (locate(/obj/effect/hotspot) in T)
if(hotspot)
- var/datum/gas_mixture/lowertemp = T.remove_air(T.air.total_moles())
- lowertemp.set_temperature(max( min(lowertemp.return_temperature()-2000,lowertemp.return_temperature() / 2) ,0))
+ var/datum/gas_mixture/lowertemp = T.return_air()
+ lowertemp.set_temperature(max( min(lowertemp.return_temperature()-2000,lowertemp.return_temperature() / 2) ,TCMB))
lowertemp.react(src)
- T.assume_air(lowertemp)
qdel(hotspot)
/datum/reagent/consumable/enzyme
@@ -577,12 +575,12 @@
..()
/datum/reagent/consumable/honey/reaction_mob(mob/living/M, method=TOUCH, reac_volume)
- if(iscarbon(M) && (method in list(TOUCH, VAPOR, PATCH)))
- var/mob/living/carbon/C = M
- for(var/s in C.surgeries)
- var/datum/surgery/S = s
- S.success_multiplier = max(0.6, S.success_multiplier) // +60% success probability on each step, compared to bacchus' blessing's ~46%
- ..()
+ if(iscarbon(M) && (method in list(TOUCH, VAPOR, PATCH)))
+ var/mob/living/carbon/C = M
+ for(var/s in C.surgeries)
+ var/datum/surgery/S = s
+ S.speed_modifier = max(0.6, S.speed_modifier) // +60% surgery speed on each step, compared to bacchus' blessing's ~46%
+ ..()
/datum/reagent/consumable/honey/special
name = "Royal Honey"
@@ -672,11 +670,11 @@
color = "#b5a213"
taste_description = "tingling mushroom"
-/datum/reagent/consumable/tinlux/reaction_mob(mob/living/M)
+/datum/reagent/consumable/tinlux/on_mob_metabolize(mob/living/carbon/M)
M.set_light(2)
-/datum/reagent/consumable/tinlux/on_mob_end_metabolize(mob/living/M)
- M.set_light(-2)
+/datum/reagent/consumable/tinlux/on_mob_end_metabolize(mob/living/carbon/M)
+ M.set_light(0)
/datum/reagent/consumable/vitfro
name = "Vitrium Froth"
@@ -708,12 +706,12 @@
taste_description = "pure electrictiy"
/datum/reagent/consumable/liquidelectricity/on_mob_life(mob/living/carbon/M)
- if(isethereal(M))
- var/mob/living/carbon/human/H = M
- var/datum/species/ethereal/E = H.dna?.species
- E.adjust_charge(5*REM)
- else if(prob(25)) //scp13 optimization
- M.electrocute_act(rand(10,15), "Liquid Electricity in their body", 1) //lmao at the newbs who eat energy bars
+ if(HAS_TRAIT(M, TRAIT_POWERHUNGRY))
+ var/obj/item/organ/stomach/battery/stomach = M.getorganslot(ORGAN_SLOT_STOMACH)
+ if(istype(stomach))
+ stomach.adjust_charge(40*REM)
+ else if(prob(3)) //scp13 optimization
+ M.electrocute_act(rand(3,5), "Liquid Electricity in their body", 1) //lmao at the newbs who eat energy bars
playsound(M, "sparks", 50, 1)
return ..()
diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
index b9d095ee0c415..02c069fc096cd 100644
--- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
@@ -17,6 +17,8 @@
name = "Leporazine"
description = "Leporazine will effectively regulate a patient's body temperature, ensuring it never leaves safe levels."
color = "#C8A5DC" // rgb: 200, 165, 220
+ overdose_threshold = 30
+ metabolization_rate = 0.25 * REAGENTS_METABOLISM
/datum/reagent/medicine/leporazine/on_mob_life(mob/living/carbon/M)
if(M.bodytemperature > BODYTEMP_NORMAL)
@@ -25,6 +27,14 @@
M.adjust_bodytemperature(40 * TEMPERATURE_DAMAGE_COEFFICIENT, 0, BODYTEMP_NORMAL)
..()
+/datum/reagent/medicine/leporazine/overdose_process(mob/living/M)
+ if(prob(50))
+ M.adjust_bodytemperature(200, 0)
+ else
+ M.adjust_bodytemperature(-200, 0)
+ ..()
+ . = 1
+
/datum/reagent/medicine/adminordrazine //An OP chemical for admins
name = "Adminordrazine"
description = "It's magic. We don't have to explain it."
@@ -66,7 +76,7 @@
O.setOrganDamage(0)
for(var/thing in M.diseases)
var/datum/disease/D = thing
- if(D.severity == DISEASE_SEVERITY_BENEFICIAL || D.severity == DISEASE_SEVERITY_POSITIVE)
+ if(D.danger == DISEASE_BENEFICIAL || D.danger == DISEASE_POSITIVE)
continue
D.cure()
..()
@@ -127,7 +137,7 @@
name = "Cryoxadone"
description = "A chemical mixture with almost magical healing powers. Its main limitation is that the patient's body temperature must be under 270K for it to metabolise correctly."
color = "#0000C8"
- taste_description = "sludge"
+ taste_description = "blue"
/datum/reagent/medicine/cryoxadone/on_mob_life(mob/living/carbon/M)
var/power = -0.00003 * (M.bodytemperature ** 2) + 3
@@ -139,7 +149,7 @@
M.adjustCloneLoss(-power, 0)
REMOVE_TRAIT(M, TRAIT_DISFIGURED, TRAIT_GENERIC) //fixes common causes for disfiguration
. = 1
- metabolization_rate = REAGENTS_METABOLISM * (0.00001 * (M.bodytemperature ** 2) + 0.5)
+ metabolization_rate = REAGENTS_METABOLISM * (0.00001 * (M.bodytemperature ** 2) + 0.5)//Metabolism rate is reduced in colder body temps making it more effective
..()
/datum/reagent/medicine/clonexadone
@@ -154,7 +164,7 @@
M.adjustCloneLoss(0.00006 * (M.bodytemperature ** 2) - 6, 0)
REMOVE_TRAIT(M, TRAIT_DISFIGURED, TRAIT_GENERIC)
. = 1
- metabolization_rate = REAGENTS_METABOLISM * (0.000015 * (M.bodytemperature ** 2) + 0.75)
+ metabolization_rate = REAGENTS_METABOLISM * (0.000015 * (M.bodytemperature ** 2) + 0.75)//Metabolism rate is reduced in colder body temps making it more effective
..()
/datum/reagent/medicine/pyroxadone
@@ -165,6 +175,7 @@
/datum/reagent/medicine/pyroxadone/on_mob_life(mob/living/carbon/M)
if(M.bodytemperature > BODYTEMP_HEAT_DAMAGE_LIMIT)
+ metabolization_rate = 0.2 // It metabolises effectively when the body is taking heat damage
var/power = 0
switch(M.bodytemperature)
if(BODYTEMP_HEAT_DAMAGE_LIMIT to 400)
@@ -183,6 +194,8 @@
M.adjustCloneLoss(-power, 0)
REMOVE_TRAIT(M, TRAIT_DISFIGURED, TRAIT_GENERIC)
. = 1
+ else //If not the right temperature for pyroxadone to work
+ metabolization_rate = REAGENTS_METABOLISM
..()
/datum/reagent/medicine/rezadone
@@ -213,7 +226,7 @@
var/mob/living/carbon/patient = M
if(reac_volume >= 5 && HAS_TRAIT_FROM(patient, TRAIT_HUSK, "burn") && patient.getFireLoss() < THRESHOLD_UNHUSK) //One carp yields 12u rezadone.
patient.cure_husk("burn")
- patient.visible_message("[patient]'s body rapidly absorbs moisture from the enviroment, taking on a more healthy appearance.")
+ patient.visible_message("[patient]'s body rapidly absorbs moisture from the environment, taking on a more healthy appearance.")
/datum/reagent/medicine/spaceacillin
name = "Spaceacillin"
@@ -224,10 +237,11 @@
//Goon Chems. Ported mainly from Goonstation. Easily mixable (or not so easily) and provide a variety of effects.
/datum/reagent/medicine/silver_sulfadiazine
name = "Silver Sulfadiazine"
- description = "If used in touch-based applications, immediately restores burn wounds as well as restoring more over time. If ingested through other means, deals minor toxin damage."
+ description = "If used in patch-based applications, immediately restores burn wounds as well as restoring more over time. If ingested through other means, deals minor toxin damage."
reagent_state = LIQUID
color = "#C8A5DC"
- overdose_threshold = 40
+ metabolization_rate = 2.5 * REAGENTS_METABOLISM
+ overdose_threshold = 100
/datum/reagent/medicine/silver_sulfadiazine/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message = 1)
if(iscarbon(M) && M.stat != DEAD)
@@ -235,10 +249,9 @@
M.adjustToxLoss(0.5*reac_volume)
if(show_message)
to_chat(M, "You don't feel so good...")
- else if(M.reagents.has_reagent(/datum/reagent/medicine/silver_sulfadiazine, 40) && M.getFireLoss())
- to_chat(M, "Silver sulfadiazine foams as it fails to heal your burns!")
- else if(M.getFireLoss())
+ else if(M.getFireLoss() && method == PATCH)
M.adjustFireLoss(-reac_volume)
+ M.adjustStaminaLoss(reac_volume*2)
if(show_message)
to_chat(M, "You feel your burns healing! It stings like hell!")
M.emote("scream")
@@ -246,15 +259,12 @@
..()
/datum/reagent/medicine/silver_sulfadiazine/on_mob_life(mob/living/carbon/M)
- M.adjustFireLoss(-2*REM, 0)
+ M.adjustFireLoss(-0.5*REM, 0)
..()
. = 1
/datum/reagent/medicine/silver_sulfadiazine/overdose_process(mob/living/M)
- M.adjustFireLoss(2*REM, FALSE, FALSE, BODYPART_ORGANIC)
- if(volume > overdose_threshold+10)
- if(prob(33))
- M.adjustToxLoss(1*REM, FALSE, FALSE, BODYPART_ORGANIC)
+ M.adjustOxyLoss(1*REM, 0)
..()
. = 1
@@ -267,25 +277,25 @@
overdose_threshold = 25
/datum/reagent/medicine/oxandrolone/on_mob_life(mob/living/carbon/M)
- if(M.getFireLoss() > 50)
- M.adjustFireLoss(-4*REM, 0) //Twice as effective as silver sulfadiazine for severe burns
- else
- M.adjustFireLoss(-0.5*REM, 0) //But only a quarter as effective for more minor ones
+ M.adjustFireLoss(-3*REM, 0)
+ if(M.getFireLoss() != 0)
+ M.adjustStaminaLoss(3*REM, FALSE)
..()
. = 1
/datum/reagent/medicine/oxandrolone/overdose_process(mob/living/M)
- if(M.getFireLoss()) //It only makes existing burns worse
- M.adjustFireLoss(4.5*REM, FALSE, FALSE, BODYPART_ORGANIC) // it's going to be healing either 4 or 0.5
- . = 1
+ M.adjustFireLoss(-3*REM, 0)
+ M.adjustToxLoss(3*REM, 0)
+ M.adjustOrganLoss(ORGAN_SLOT_LIVER, 2)
..()
/datum/reagent/medicine/styptic_powder
name = "Styptic Powder"
- description = "If used in touch-based applications, immediately restores bruising as well as restoring more over time. If ingested through other means, deals minor toxin damage."
+ description = "If used in patch-based applications, immediately restores bruising. If ingested through other means, deals minor toxin damage."
reagent_state = LIQUID
color = "#FF9696"
- overdose_threshold = 40
+ metabolization_rate = 2.5 * REAGENTS_METABOLISM
+ overdose_threshold = 100
/datum/reagent/medicine/styptic_powder/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message = 1)
if(iscarbon(M) && M.stat != DEAD)
@@ -293,10 +303,9 @@
M.adjustToxLoss(0.5*reac_volume)
if(show_message)
to_chat(M, "You don't feel so good...")
- else if(M.reagents.has_reagent(/datum/reagent/medicine/styptic_powder, 40) && M.getBruteLoss())
- to_chat(M, "Styptic powder foams as it fails to heal your bruises!")
- else if(M.getBruteLoss())
+ else if(M.getBruteLoss() && method == PATCH)
M.adjustBruteLoss(-reac_volume)
+ M.adjustStaminaLoss(reac_volume*2)
if(show_message)
to_chat(M, "You feel your bruises healing! It stings like hell!")
M.emote("scream")
@@ -305,15 +314,12 @@
/datum/reagent/medicine/styptic_powder/on_mob_life(mob/living/carbon/M)
- M.adjustBruteLoss(-2*REM, 0)
+ M.adjustBruteLoss(-0.5*REM, 0)
..()
. = 1
/datum/reagent/medicine/styptic_powder/overdose_process(mob/living/M)
- M.adjustBruteLoss(2*REM, 0)
- if(volume > overdose_threshold+10)
- if(prob(33))
- M.adjustToxLoss(1*REM, FALSE, FALSE, BODYPART_ORGANIC)
+ M.adjustOxyLoss(1*REM, 0)
..()
. = 1
@@ -382,8 +388,8 @@
var/mob/living/carbon/C = M
for(var/s in C.surgeries)
var/datum/surgery/S = s
- S.success_multiplier = max(0.1, S.success_multiplier)
- // +10% success propability on each step, useful while operating in less-than-perfect conditions
+ S.speed_modifier = max(0.1, S.speed_modifier)
+ // +10% surgery speed on each step, useful while operating in less-than-perfect conditions
if(show_message)
to_chat(M, "You feel your wounds fade away to nothing!" )
@@ -400,23 +406,37 @@
description = "Has a 100% chance of instantly healing brute and burn damage. One unit of the chemical will heal one point of damage. Touch application only."
reagent_state = LIQUID
color = "#FFEBEB"
+ metabolization_rate = 2.5 * REAGENTS_METABOLISM
+ overdose_threshold = 125
/datum/reagent/medicine/synthflesh/reaction_mob(mob/living/M, method=TOUCH, reac_volume,show_message = 1)
if(iscarbon(M))
if (M.stat == DEAD)
show_message = 0
- if(method in list(PATCH, TOUCH))
- M.adjustBruteLoss(-1.25 * reac_volume)
- M.adjustFireLoss(-1.25 * reac_volume)
+ if(method in list(PATCH))
+ M.adjustBruteLoss(-1 * reac_volume)
+ M.adjustFireLoss(-1 * reac_volume)
+ M.adjustStaminaLoss(reac_volume*2)
if(show_message)
to_chat(M, "You feel your burns and bruises healing! It stings like hell!")
SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "painful_medicine", /datum/mood_event/painful_medicine)
- //Has to be at less than THRESHOLD_UNHUSK burn damage and have 100 isntabitaluri before unhusking. Corpses dont metabolize.
- if(HAS_TRAIT_FROM(M, TRAIT_HUSK, "burn") && M.getFireLoss() < THRESHOLD_UNHUSK && M.reagents.has_reagent(/datum/reagent/medicine/synthflesh, 100))
+ //Has to be at less than THRESHOLD_UNHUSK burn damage and have at least 100 synthflesh (currently inside the body + amount now being applied). Corpses dont metabolize.
+ if(HAS_TRAIT_FROM(M, TRAIT_HUSK, "burn") && M.getFireLoss() < THRESHOLD_UNHUSK && (M.reagents.get_reagent_amount(/datum/reagent/medicine/synthflesh) + reac_volume) >= 100)
M.cure_husk("burn")
M.visible_message("You successfully replace most of the burnt off flesh of [M].")
..()
+/datum/reagent/medicine/synthflesh/on_mob_life(mob/living/carbon/M)
+ M.adjustFireLoss(-0.5*REM, 0)
+ M.adjustBruteLoss(-0.5*REM, 0)
+ ..()
+ . = 1
+
+/datum/reagent/medicine/synthflesh/overdose_process(mob/living/M)
+ M.adjustToxLoss(2*REM, 0)
+ ..()
+ . = 1
+
/datum/reagent/medicine/charcoal
name = "Charcoal"
description = "Heals mild toxin damage as well as slowly removing any other chemicals the patient has in their bloodstream."
@@ -459,6 +479,7 @@
/datum/reagent/medicine/liquid_solder/on_mob_life(mob/living/M)
M.adjustOrganLoss(ORGAN_SLOT_BRAIN, (-3*REM))
+ M.hallucination = max(0, M.hallucination - 10)
if(iscarbon(M))
var/mob/living/carbon/C = M
if(prob(30) && C.has_trauma_type(BRAIN_TRAUMA_SPECIAL))
@@ -546,17 +567,16 @@
/datum/reagent/medicine/sal_acid/on_mob_life(mob/living/carbon/M)
- if(M.getBruteLoss() > 50)
- M.adjustBruteLoss(-4*REM, 0) //Twice as effective as styptic powder for severe bruising
- else
- M.adjustBruteLoss(-0.5*REM, 0) //But only a quarter as effective for more minor ones
+ M.adjustBruteLoss(-3*REM, 0)
+ if(M.getBruteLoss() != 0)
+ M.adjustStaminaLoss(3*REM, FALSE)
..()
. = 1
/datum/reagent/medicine/sal_acid/overdose_process(mob/living/M)
- if(M.getBruteLoss()) //It only makes existing bruises worse
- M.adjustBruteLoss(4.5*REM, FALSE, FALSE, BODYPART_ORGANIC) // it's going to be healing either 4 or 0.5
- . = 1
+ M.adjustFireLoss(-3*REM, 0)
+ M.adjustToxLoss(3*REM, 0)
+ M.adjustOrganLoss(ORGAN_SLOT_LIVER, 2)
..()
/datum/reagent/medicine/salbutamol
@@ -564,6 +584,7 @@
description = "Rapidly restores oxygen deprivation as well as preventing more of it to an extent."
reagent_state = LIQUID
color = "#00FFFF"
+ overdose_threshold = 25
metabolization_rate = 0.25 * REAGENTS_METABOLISM
/datum/reagent/medicine/salbutamol/on_mob_life(mob/living/carbon/M)
@@ -573,19 +594,30 @@
..()
. = 1
+/datum/reagent/medicine/salbutamol/overdose_process(mob/living/M)
+ M.reagents.add_reagent(/datum/reagent/toxin/histamine, 1)
+ M.reagents.remove_reagent(/datum/reagent/medicine/salbutamol, 1)
+ ..()
+
/datum/reagent/medicine/perfluorodecalin
name = "Perfluorodecalin"
- description = "Extremely rapidly restores oxygen deprivation, but inhibits speech. May also heal small amounts of bruising and burns."
+ description = "Extremely rapidly restores oxygen deprivation, but causes minor toxin damage. Overdose causes significant damage to the lungs."
reagent_state = LIQUID
color = "#FF6464"
+ overdose_threshold = 30
metabolization_rate = 0.25 * REAGENTS_METABOLISM
/datum/reagent/medicine/perfluorodecalin/on_mob_life(mob/living/carbon/human/M)
- M.adjustOxyLoss(-12*REM, 0)
- M.adjustToxLoss(0.3*REM, 0)
+ M.adjustOrganLoss(ORGAN_SLOT_LUNGS, -2)
+ M.adjustOxyLoss(-10*REM, 0)
+ M.adjustToxLoss(1*REM, 0)
..()
return TRUE
+/datum/reagent/medicine/perfluorodecalin/overdose_process(mob/living/M)
+ M.adjustOrganLoss(ORGAN_SLOT_HEART, 2)
+ ..()
+
/datum/reagent/medicine/ephedrine
name = "Ephedrine"
description = "Increases stun resistance and movement speed. Overdose deals toxin damage and inhibits breathing."
@@ -792,27 +824,29 @@
/datum/reagent/medicine/atropine
name = "Atropine"
- description = "If a patient is in critical condition, rapidly heals all damage types as well as regulating oxygen in the body. Excellent for stabilizing wounded patients."
+ description = "If a patient is in critical condition, rapidly heals all damage types as well as regulating oxygen in the body. Excellent for stabilizing wounded patients. Has the side effects of causing minor confusion."
reagent_state = LIQUID
color = "#000000"
metabolization_rate = 0.25 * REAGENTS_METABOLISM
- overdose_threshold = 35
+ overdose_threshold = 15
/datum/reagent/medicine/atropine/on_mob_life(mob/living/carbon/M)
- if(M.health <= M.crit_threshold)
- M.adjustToxLoss(-2*REM, 0)
- M.adjustBruteLoss(-2*REM, 0)
- M.adjustFireLoss(-2*REM, 0)
+ if(M.health <= 20)
+ M.adjustToxLoss(-4*REM, 0)
+ M.adjustBruteLoss(-4*REM, 0)
+ M.adjustFireLoss(-4*REM, 0)
M.adjustOxyLoss(-5*REM, 0)
. = 1
M.losebreath = 0
if(prob(20))
M.Dizzy(5)
M.Jitter(5)
+ M.drop_all_held_items()
..()
/datum/reagent/medicine/atropine/overdose_process(mob/living/M)
- M.adjustToxLoss(0.5*REM, 0)
+ M.reagents.add_reagent(/datum/reagent/toxin/histamine, 3)
+ M.reagents.remove_reagent(/datum/reagent/medicine/atropine, 2)
. = 1
M.Dizzy(1)
M.Jitter(1)
@@ -881,20 +915,7 @@
M.do_jitter_animation(10)
addtimer(CALLBACK(M, /mob/living/carbon.proc/do_jitter_animation, 10), 40) //jitter immediately, then again after 4 and 8 seconds
addtimer(CALLBACK(M, /mob/living/carbon.proc/do_jitter_animation, 10), 80)
- sleep(100) //so the ghost has time to re-enter
-
-
- var/mob/living/carbon/H = M
- for(var/organ in H.internal_organs)
- var/obj/item/organ/O = organ
- O.setOrganDamage(0)
-
- M.adjustOxyLoss(-20, 0)
- M.adjustToxLoss(-20, 0)
- M.updatehealth()
- if(M.revive())
- M.emote("gasp")
- log_combat(M, M, "revived", src)
+ addtimer(CALLBACK(M, /mob/living.proc/revive, FALSE, FALSE), 100)
..()
/datum/reagent/medicine/strange_reagent/on_mob_life(mob/living/carbon/M)
@@ -939,20 +960,21 @@
/datum/reagent/medicine/antihol
name = "Antihol"
- description = "Purges alcoholic substance from the patient's body and eliminates its side effects."
+ description = "Purges alcoholic substance from the patient's body and eliminates its side effects. Less effective in light drinkers."
color = "#00B4C8"
taste_description = "raw egg"
/datum/reagent/medicine/antihol/on_mob_life(mob/living/carbon/M)
- M.dizziness = 0
- M.drowsyness = 0
- M.slurring = 0
- M.confused = 0
+ if(!HAS_TRAIT(M, TRAIT_LIGHT_DRINKER))
+ M.dizziness = 0
+ M.drowsyness = 0
+ M.slurring = 0
+ M.confused = 0
+ if(ishuman(M))
+ var/mob/living/carbon/human/H = M
+ H.drunkenness = max(H.drunkenness - 10, 0)
M.reagents.remove_all_type(/datum/reagent/consumable/ethanol, 3*REM, 0, 1)
M.adjustToxLoss(-0.2*REM, 0)
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- H.drunkenness = max(H.drunkenness - 10, 0)
..()
. = 1
@@ -1044,18 +1066,22 @@
//Trek Chems, used primarily by medibots. Only heals a specific damage type, but is very efficient.
/datum/reagent/medicine/bicaridine
name = "Bicaridine"
- description = "Restores bruising. Overdose causes it instead."
+ description = "Restores bruising. Overdose causes liver damage."
reagent_state = LIQUID
color = "#C8A5DC"
+ metabolization_rate = 0.5 * REAGENTS_METABOLISM
overdose_threshold = 30
/datum/reagent/medicine/bicaridine/on_mob_life(mob/living/carbon/M)
- M.adjustBruteLoss(-2*REM, 0)
+ if(M.getBruteLoss() < 50)
+ M.adjustBruteLoss(-1.5*REM, 0)
+ else
+ M.adjustBruteLoss(-0.5*REM, 0)
..()
. = 1
/datum/reagent/medicine/bicaridine/overdose_process(mob/living/M)
- M.adjustBruteLoss(4*REM, FALSE, FALSE, BODYPART_ORGANIC)
+ M.adjustOrganLoss(ORGAN_SLOT_LIVER, 2)
..()
. = 1
@@ -1067,46 +1093,55 @@
overdose_threshold = 30
/datum/reagent/medicine/dexalin/on_mob_life(mob/living/carbon/M)
- M.adjustOxyLoss(-2*REM, 0)
+ if(M.getOxyLoss() < 50)
+ M.adjustOxyLoss(-1.5*REM, 0)
+ else
+ M.adjustOxyLoss(-0.5*REM, 0)
..()
. = 1
/datum/reagent/medicine/dexalin/overdose_process(mob/living/M)
- M.adjustOxyLoss(4*REM, 0)
+ M.adjustOrganLoss(ORGAN_SLOT_HEART, 2)
..()
. = 1
/datum/reagent/medicine/dexalinp
name = "Dexalin Plus"
- description = "Restores oxygen loss. Overdose causes it instead. It is highly effective."
+ description = "Restores oxygen loss. Overdose causes large amounts of damage to the heart. It is highly effective."
reagent_state = LIQUID
color = "#0040FF"
overdose_threshold = 25
/datum/reagent/medicine/dexalinp/on_mob_life(mob/living/carbon/M)
- M.adjustOxyLoss(-4*REM, 0)
+ M.adjustOxyLoss(-3*REM, 0)
+ if(M.getOxyLoss() != 0)
+ M.adjustStaminaLoss(3*REM, FALSE)
..()
. = 1
/datum/reagent/medicine/dexalinp/overdose_process(mob/living/M)
- M.adjustOxyLoss(8*REM, 0)
+ M.adjustOrganLoss(ORGAN_SLOT_HEART, 4)
..()
. = 1
/datum/reagent/medicine/kelotane
name = "Kelotane"
- description = "Restores fire damage. Overdose causes it instead."
+ description = "Restores fire damage. Overdose causes liver damage."
reagent_state = LIQUID
color = "#C8A5DC"
+ metabolization_rate = 0.5 * REAGENTS_METABOLISM
overdose_threshold = 30
/datum/reagent/medicine/kelotane/on_mob_life(mob/living/carbon/M)
- M.adjustFireLoss(-2*REM, 0)
+ if(M.getFireLoss() < 50)
+ M.adjustFireLoss(-1.5*REM, 0)
+ else
+ M.adjustFireLoss(-0.5*REM, 0)
..()
. = 1
/datum/reagent/medicine/kelotane/overdose_process(mob/living/M)
- M.adjustFireLoss(4*REM, FALSE, FALSE, BODYPART_ORGANIC)
+ M.adjustOrganLoss(ORGAN_SLOT_LIVER, 2)
..()
. = 1
@@ -1115,18 +1150,20 @@
description = "Heals toxin damage and removes toxins in the bloodstream. Overdose causes toxin damage."
reagent_state = LIQUID
color = "#C8A5DC"
+ metabolization_rate = 0.5 * REAGENTS_METABOLISM
overdose_threshold = 30
taste_description = "a roll of gauze"
/datum/reagent/medicine/antitoxin/on_mob_life(mob/living/carbon/M)
- M.adjustToxLoss(-2*REM, 0)
- for(var/datum/reagent/toxin/R in M.reagents.reagent_list)
- M.reagents.remove_reagent(R.type,1)
+ if(M.getToxLoss() < 50)
+ M.adjustToxLoss(-1.5*REM, 0)
+ else
+ M.adjustToxLoss(-0.5*REM, 0)
..()
. = 1
/datum/reagent/medicine/antitoxin/overdose_process(mob/living/M)
- M.adjustToxLoss(4*REM, 0) // End result is 2 toxin loss taken, because it heals 2 and then removes 4.
+ M.adjustOrganLoss(ORGAN_SLOT_LIVER, 2)
..()
. = 1
@@ -1135,9 +1172,10 @@
description = "Carthatoline is strong evacuant used to treat severe poisoning."
reagent_state = LIQUID
color = "#225722"
+ overdose_threshold = 25
/datum/reagent/medicine/carthatoline/on_mob_life(mob/living/carbon/M)
- M.adjustToxLoss(-5*REM, 0)
+ M.adjustToxLoss(-3*REM, 0)
if(M.getToxLoss() && prob(10))
M.vomit(1)
for(var/datum/reagent/toxin/R in M.reagents.reagent_list)
@@ -1146,7 +1184,7 @@
. = 1
/datum/reagent/medicine/carthatoline/overdose_process(mob/living/M)
- M.adjustToxLoss(10*REM, 0) // End result is 5 toxin loss taken, because it heals 5 and then removes 10.
+ M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2)
..()
. = 1
@@ -1156,7 +1194,7 @@
taste_description = "glue"
reagent_state = LIQUID
color = "#D2691E"
- metabolization_rate = REM * 1.5
+ metabolization_rate = REM * 3.75
overdose_threshold = 10
/datum/reagent/medicine/hepanephrodaxon/on_mob_life(var/mob/living/carbon/M)
@@ -1165,13 +1203,12 @@
if(L.damage > 0)
L.damage = max(L.damage - 4 * repair_strength, 0)
M.confused = (2)
- M.adjustToxLoss(-12)
+ M.adjustToxLoss(-6)
..()
. = 1
/datum/reagent/medicine/hepanephrodaxon/overdose_process(mob/living/M)
- var/obj/item/organ/liver/L = M.getorganslot(ORGAN_SLOT_LIVER)
- L.damage = max(L.damage + 4, 0)
+ M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2)
M.confused = (2)
..()
. = 1
@@ -1189,7 +1226,7 @@
/datum/reagent/medicine/tricordrazine
name = "Tricordrazine"
- description = "Has a high chance to heal all types of damage. Overdose instead causes it."
+ description = "Has a high chance to heal all types of damage. Overdose causes toxin damage and liver damage."
reagent_state = LIQUID
color = "#C8A5DC"
overdose_threshold = 30
@@ -1206,9 +1243,7 @@
/datum/reagent/medicine/tricordrazine/overdose_process(mob/living/M)
M.adjustToxLoss(2*REM, 0)
- M.adjustOxyLoss(2*REM, 0)
- M.adjustBruteLoss(2*REM, FALSE, FALSE, BODYPART_ORGANIC)
- M.adjustFireLoss(2*REM, FALSE, FALSE, BODYPART_ORGANIC)
+ M.adjustOrganLoss(ORGAN_SLOT_LIVER, 3)
..()
. = 1
@@ -1227,6 +1262,21 @@
. = 1
..()
+/datum/reagent/medicine/regen_ooze
+ name = "Regenerative Ooze"
+ description = "Gradually regenerates all types of damage, without harming slime anatomy."
+ reagent_state = LIQUID
+ color = "#65d891"
+ taste_description = "jelly"
+
+/datum/reagent/medicine/regen_ooze/on_mob_life(mob/living/carbon/M)
+ M.adjustBruteLoss(-0.5*REM, 0)
+ M.adjustFireLoss(-0.5*REM, 0)
+ M.adjustOxyLoss(-0.5*REM, 0)
+ M.adjustToxLoss(-0.5*REM, 0, TRUE) //heals TOXINLOVERs
+ . = 1
+ ..()
+
/datum/reagent/medicine/syndicate_nanites //Used exclusively by Syndicate medical cyborgs
name = "Restorative Nanites"
description = "Miniature medical robots that swiftly restore bodily damage."
@@ -1359,11 +1409,13 @@
// Heart attack code will not do damage if corazone is present
// because it's SPACE MAGIC ASPIRIN
name = "Corazone"
- description = "A medication used to treat pain, fever, and inflammation, along with heart attacks."
+ description = "A medication used to assist in healing the heart and to stabalize the heart and liver."
color = "#F5F5F5"
+ overdose_threshold = 20
self_consuming = TRUE
/datum/reagent/medicine/corazone/on_mob_metabolize(mob/living/M)
+ M.adjustOrganLoss(ORGAN_SLOT_HEART, -1.5)
..()
ADD_TRAIT(M, TRAIT_STABLEHEART, type)
ADD_TRAIT(M, TRAIT_STABLELIVER, type)
@@ -1373,6 +1425,11 @@
REMOVE_TRAIT(M, TRAIT_STABLELIVER, type)
..()
+/datum/reagent/medicine/corazone/overdose_process(mob/living/M)
+ M.reagents.add_reagent(/datum/reagent/toxin/histamine, 1)
+ M.reagents.remove_reagent(/datum/reagent/medicine/corazone, 1)
+ ..()
+
/datum/reagent/medicine/muscle_stimulant
name = "Muscle Stimulant"
description = "A potent chemical that allows someone under its influence to be at full physical ability even when under massive amounts of pain."
@@ -1525,3 +1582,24 @@
M.adjustOrganLoss(ORGAN_SLOT_LUNGS, 0.5)
..()
. = 1
+
+/datum/reagent/medicine/stabilizing_nanites
+ name = "Stabilizing nanites"
+ description = "Rapidly heals a patient out of crit by regenerating damaged cells. Nanites distribution in the blood makes them ineffective against moderately healthy targets."
+ reagent_state = LIQUID
+ color = "#000000"
+ metabolization_rate = 0.25 * REAGENTS_METABOLISM
+ overdose_threshold = 15
+ can_synth = FALSE
+
+/datum/reagent/medicine/stabilizing_nanites/on_mob_life(mob/living/carbon/M)
+ if(M.health <= 80)
+ M.adjustToxLoss(-4*REM, 0)
+ M.adjustBruteLoss(-4*REM, 0)
+ M.adjustFireLoss(-4*REM, 0)
+ M.adjustOxyLoss(-5*REM, 0)
+ . = 1
+ if(prob(20))
+ M.Jitter(5)
+ M.losebreath = 0
+ ..()
diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm
index 48ac604a9e7a1..fa856b0bffaf7 100644
--- a/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -1,5 +1,5 @@
/datum/reagent/blood
- data = list("donor"=null,"viruses"=null,"blood_DNA"=null,"blood_type"=null,"resistances"=null,"trace_chem"=null,"mind"=null,"ckey"=null,"gender"=null,"real_name"=null,"cloneable"=null,"factions"=null,"quirks"=null)
+ data = list("viruses"=null,"blood_DNA"=null,"blood_type"=null,"resistances"=null,"trace_chem"=null,"mind"=null,"ckey"=null,"gender"=null,"real_name"=null,"cloneable"=null,"factions"=null,"quirks"=null)
name = "Blood"
color = "#C80000" // rgb: 200, 0, 0
metabolization_rate = 5 //fast rate so it disappears fast.
@@ -112,6 +112,58 @@
if(istype(data))
src.data |= data.Copy()
+/datum/reagent/corgium
+ name = "Corgium"
+ description = "A happy looking liquid that you feel compelled to consume if you want a better life."
+ color = "#ecca7f"
+ taste_description = "dog treats"
+ can_synth = FALSE
+ var/mob/living/simple_animal/pet/dog/corgi/new_corgi
+
+/datum/reagent/corgium/on_mob_metabolize(mob/living/L)
+ . = ..()
+ new_corgi = new(get_turf(L))
+ new_corgi.key = L.key
+ new_corgi.name = L.real_name
+ new_corgi.real_name = L.real_name
+ ADD_TRAIT(L, TRAIT_NOBREATH, CORGIUM_TRAIT)
+ //hack - equipt current hat
+ var/mob/living/carbon/C = L
+ if (istype(C))
+ var/obj/item/hat = C.get_item_by_slot(ITEM_SLOT_HEAD)
+ if (hat)
+ new_corgi.place_on_head(hat,null,FALSE)
+ L.forceMove(new_corgi)
+
+/datum/reagent/corgium/on_mob_life(mob/living/carbon/M)
+ . = ..()
+ //If our corgi died :(
+ if(new_corgi.stat)
+ holder.remove_all_type(type)
+
+/datum/reagent/corgium/on_mob_end_metabolize(mob/living/L)
+ . = ..()
+ REMOVE_TRAIT(L, TRAIT_NOBREATH, CORGIUM_TRAIT)
+ //New corgi was deleted, goodbye cruel world.
+ if(QDELETED(new_corgi))
+ if(!QDELETED(L))
+ qdel(L)
+ return
+ //Leave the corgi
+ L.key = new_corgi.key
+ L.adjustBruteLoss(new_corgi.getBruteLoss())
+ L.adjustFireLoss(new_corgi.getFireLoss())
+ L.forceMove(get_turf(new_corgi))
+ // HACK - drop all corgi inventory
+ var/turf/T = get_turf(new_corgi)
+ if (new_corgi.inventory_head)
+ if(!L.equip_to_slot_if_possible(new_corgi.inventory_head, ITEM_SLOT_HEAD,disable_warning = TRUE, bypass_equip_delay_self=TRUE))
+ new_corgi.inventory_head.forceMove(T)
+ new_corgi.inventory_back?.forceMove(T)
+ new_corgi.inventory_head = null
+ new_corgi.inventory_back = null
+ qdel(new_corgi)
+
/datum/reagent/water
name = "Water"
description = "An ubiquitous chemical substance that is composed of hydrogen and oxygen."
@@ -180,6 +232,10 @@
/datum/reagent/water/reaction_mob(mob/living/M, method=TOUCH, reac_volume)//Splashing people with water can help put them out!
if(!istype(M))
return
+ if(isoozeling(M))
+ M.blood_volume -= 30
+ to_chat(M, "The water causes you to melt away!")
+ return
if(method == TOUCH)
M.adjust_fire_stacks(-(reac_volume / 10))
M.ExtinguishMob()
@@ -309,12 +365,20 @@
description = "Lubricant is a substance introduced between two moving surfaces to reduce the friction and wear between them. giggity."
color = "#009CA8" // rgb: 0, 156, 168
taste_description = "cherry" // by popular demand
+ var/lube_kind = TURF_WET_LUBE ///What kind of slipperiness gets added to turfs.
/datum/reagent/lube/reaction_turf(turf/open/T, reac_volume)
if (!istype(T))
return
if(reac_volume >= 1)
- T.MakeSlippery(TURF_WET_LUBE, 15 SECONDS, min(reac_volume * 2 SECONDS, 120))
+ T.MakeSlippery(lube_kind, 15 SECONDS, min(reac_volume * 2 SECONDS, 120))
+
+///Stronger kind of lube. Applies TURF_WET_SUPERLUBE.
+/datum/reagent/lube/superlube
+ name = "Super Duper Lube"
+ description = "This \[REDACTED\] has been outlawed after the incident on \[DATA EXPUNGED\]."
+ lube_kind = TURF_WET_SUPERLUBE
+
/datum/reagent/spraytan
name = "Spray Tan"
@@ -462,6 +526,7 @@
H.set_species(species_type)
H.reagents.del_reagent(type)
to_chat(H, "You've become \a [lowertext(initial(species_type.name))]!")
+ return
..()
/datum/reagent/mutationtoxin/classic //The one from plasma on green slimes
@@ -480,10 +545,12 @@
/datum/species/lizard,
/datum/species/fly,
/datum/species/moth,
+ /datum/species/apid,
/datum/species/pod,
/datum/species/jelly,
/datum/species/abductor,
- /datum/species/squid)
+ /datum/species/squid,
+ /datum/species/skeleton)
can_synth = TRUE
/datum/reagent/mutationtoxin/felinid
@@ -513,6 +580,13 @@
race = /datum/species/moth
taste_description = "clothing"
+/datum/reagent/mutationtoxin/apid
+ name = "Apid Mutation Toxin"
+ description = "A sweet-smelling toxin."
+ color = "#5EFF3B" //RGB: 94, 255, 59
+ race = /datum/species/apid
+ taste_description = "honey"
+
/datum/reagent/mutationtoxin/pod
name = "Podperson Mutation Toxin"
description = "A vegetalizing toxin."
@@ -571,6 +645,13 @@
race = /datum/species/ipc
taste_description = "silicon and copper"
+/datum/reagent/mutationtoxin/ethereal
+ name = "Ethereal Mutation Toxin"
+ description = "A positively electric toxin."
+ color = "#5EFF3B" //RGB: 94, 255, 59
+ race = /datum/species/ethereal
+ taste_description = "shocking"
+
/datum/reagent/mutationtoxin/squid
name = "Squid Mutation Toxin"
description = "A salty toxin."
@@ -578,6 +659,13 @@
race = /datum/species/squid
taste_description = "fish"
+/datum/reagent/mutationtoxin/oozeling
+ name = "Oozeling Mutation Toxin"
+ description = "An oozing toxin"
+ color = "#611e80" //RGB: 97, 30, 128
+ race = /datum/species/oozeling
+ taste_description = "burning ooze"
+
//BLACKLISTED RACES
/datum/reagent/mutationtoxin/skeleton
name = "Skeleton Mutation Toxin"
@@ -658,6 +746,7 @@
description = "An advanced corruptive toxin produced by slimes."
color = "#13BC5E" // rgb: 19, 188, 94
taste_description = "slime"
+ can_synth = FALSE //idedplznerf
/datum/reagent/aslimetoxin/reaction_mob(mob/living/L, method=TOUCH, reac_volume)
if(method != TOUCH)
@@ -846,7 +935,7 @@
random_unrestricted = FALSE
/datum/reagent/lithium/on_mob_life(mob/living/carbon/M)
- if((M.mobility_flags & MOBILITY_MOVE) && !isspaceturf(M.loc))
+ if((M.mobility_flags & MOBILITY_MOVE) && !isspaceturf(M.loc) && isturf(M.loc))
step(M, pick(GLOB.cardinals))
if(prob(5))
M.emote(pick("twitch","drool","moan"))
@@ -868,8 +957,8 @@
if(method in list(TOUCH, VAPOR, PATCH))
for(var/s in C.surgeries)
var/datum/surgery/S = s
- S.success_multiplier = max(0.2, S.success_multiplier)
- // +20% success propability on each step, useful while operating in less-than-perfect conditions
+ S.speed_modifier = max(0.2, S.speed_modifier)
+ // +20% surgery speed on each step, useful while operating in less-than-perfect conditions
..()
/datum/reagent/iron
@@ -1765,22 +1854,22 @@
name = "Growth Serum"
description = "A commercial chemical designed to help older men in the bedroom."//not really it just makes you a giant
color = "#ff0000"//strong red. rgb 255, 0, 0
- var/current_size = 1
+ var/current_size = RESIZE_DEFAULT_SIZE
taste_description = "bitterness" // apparently what viagra tastes like
/datum/reagent/growthserum/on_mob_life(mob/living/carbon/H)
var/newsize = current_size
switch(volume)
if(0 to 19)
- newsize = 1.25
+ newsize = 1.25*RESIZE_DEFAULT_SIZE
if(20 to 49)
- newsize = 1.5
+ newsize = 1.5*RESIZE_DEFAULT_SIZE
if(50 to 99)
- newsize = 2
+ newsize = 2*RESIZE_DEFAULT_SIZE
if(100 to 199)
- newsize = 2.5
+ newsize = 2.5*RESIZE_DEFAULT_SIZE
if(200 to INFINITY)
- newsize = 3.5
+ newsize = 3.5*RESIZE_DEFAULT_SIZE
H.resize = newsize/current_size
current_size = newsize
@@ -1788,7 +1877,8 @@
..()
/datum/reagent/growthserum/on_mob_end_metabolize(mob/living/M)
- M.resize = 1/current_size
+ M.resize = RESIZE_DEFAULT_SIZE/current_size
+ current_size = RESIZE_DEFAULT_SIZE
M.update_transform()
..()
@@ -1868,47 +1958,6 @@
changeling.chem_charges = max(changeling.chem_charges-2, 0)
return ..()
-/datum/reagent/concentrated_bz
- name = "Concentrated BZ"
- description = "A hyperconcentrated liquid form of BZ gas, known to cause an extremely adverse reaction to changelings. Also causes minor brain damage."
- color = "#FAFF00"
- taste_description = "acrid cinnamon"
- random_unrestricted = FALSE
-
-/datum/reagent/concentrated_bz/on_mob_metabolize(mob/living/L)
- ..()
- ADD_TRAIT(L, CHANGELING_HIVEMIND_MUTE, type)
-
-/datum/reagent/concentrated_bz/on_mob_end_metabolize(mob/living/L)
- ..()
- REMOVE_TRAIT(L, CHANGELING_HIVEMIND_MUTE, type)
-
-/datum/reagent/concentrated_bz/on_mob_life(mob/living/L)
- if(L.mind)
- var/datum/antagonist/changeling/changeling = L.mind.has_antag_datum(/datum/antagonist/changeling)
- if(changeling)
- changeling.chem_charges = max(changeling.chem_charges-2, 0)
- if(prob(30))
- L.losebreath += 1
- L.adjustOxyLoss(3,5)
- L.emote("gasp")
- to_chat(L, "You can't breathe!")
-
- L.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2, 50)
- return ..()
-
-/datum/reagent/fake_cbz
- name = "Concentrated BZ"
- description = "A hyperconcentrated liquid form of BZ gas, known to cause an extremely adverse reaction to changelings. Also causes minor brain damage."
- color = "#FAFF00"
- taste_description = "acrid cinnamon"
- random_unrestricted = FALSE
-
-/datum/reagent/fake_cbz/on_mob_life(mob/living/L)
- L.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2, 50)
- if(prob(15))
- to_chat(L, "You don't feel much of anything")
-
/datum/reagent/pax/peaceborg
name = "Synthpax"
description = "A colorless liquid that suppresses violent urges in its subjects. Cheaper to synthesize than normal Pax, but wears off faster."
diff --git a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm
index 019f037c0cc60..ee8771e0de355 100644
--- a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm
@@ -58,7 +58,7 @@
else if(prob(reac_volume))
F.burn_tile()
if(isfloorturf(F))
- for(var/turf/turf in range(1,F))
+ for(var/turf/open/turf in RANGE_TURFS(1,F))
if(!locate(/obj/effect/hotspot) in turf)
new /obj/effect/hotspot(F)
if(iswallturf(T))
@@ -245,6 +245,28 @@
L.extract_cooldown = max(0, L.extract_cooldown - 20)
..()
+/datum/reagent/teslium/energized_jelly/energized_ooze
+ name = "Energized Ooze"
+ description = "Electrically-charged Ooze. Boosts Oozeling's nervous system, but only shocks other lifeforms."
+ reagent_state = LIQUID
+ color = "#CAFF43"
+ taste_description = "slime"
+ overdose_threshold = 30
+
+/datum/reagent/teslium/energized_jelly/energized_ooze/on_mob_life(mob/living/carbon/M)
+ if(isoozeling(M))
+ shock_timer = 0 //immune to shocks
+ M.AdjustAllImmobility(-40, FALSE)
+ M.adjustStaminaLoss(-2, 0)
+ ..()
+
+/datum/reagent/teslium/energized_jelly/energized_ooze/overdose_process(mob/living/carbon/M)
+ if(isoozeling(M) || isjellyperson(M))
+ if(prob(25))
+ M.electrocute_act(rand(5,20), "Energized Jelly overdose in their body", 1, 1) //Override because it's caused from INSIDE of you
+ playsound(M, "sparks", 50, 1)
+ ..()
+
/datum/reagent/firefighting_foam
name = "Firefighting Foam"
description = "A historical fire suppressant. Originally believed to simply displace oxygen to starve fires, it actually interferes with the combustion reaction itself. Vastly superior to the cheap water-based extinguishers found on NT vessels."
diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
index be837a4ddaf94..b140b4fd7214c 100644
--- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
@@ -53,7 +53,7 @@
/datum/reagent/toxin/plasma
name = "Plasma"
description = "Plasma in its liquid form."
- taste_description = "bitterness"
+ taste_description = "a burning, tingling sensation"
specific_heat = SPECIFIC_HEAT_PLASMA
taste_mult = 1.5
color = "#8228A0"
@@ -122,6 +122,24 @@
. = 1
..()
+/datum/reagent/toxin/slimeooze
+ name = "Slime Ooze"
+ description = "A gooey semi-liquid produced from Oozelings"
+ color = "#611e80"
+ toxpwr = 0
+ taste_description = "slime"
+ taste_mult = 1.5
+
+/datum/reagent/toxin/slimeooze/on_mob_life(mob/living/carbon/M)
+ if(prob(10))
+ to_chat(M, "Your insides are burning!")
+ M.adjustToxLoss(rand(1,10)*REM, 0)
+ . = 1
+ else if(prob(40))
+ M.heal_bodypart_damage(5*REM)
+ . = 1
+ ..()
+
/datum/reagent/toxin/minttoxin
name = "Mint Toxin"
description = "Useful for dealing with undesirable customers."
@@ -130,7 +148,7 @@
taste_description = "mint"
/datum/reagent/toxin/minttoxin/on_mob_life(mob/living/carbon/M)
- if(HAS_TRAIT(M, TRAIT_FAT))
+ if(HAS_TRAIT_FROM(M, TRAIT_FAT, OBESITY))
M.gib()
return ..()
@@ -153,22 +171,20 @@
silent_toxin = TRUE
reagent_state = SOLID
color = "#669900" // rgb: 102, 153, 0
- toxpwr = 0.5
+ toxpwr = 0
taste_description = "death"
-/datum/reagent/toxin/zombiepowder/on_mob_metabolize(mob/living/L)
+/datum/reagent/toxin/zombiepowder/on_mob_life(mob/living/carbon/M)
+ if(current_cycle >= 10) // delayed activation for toxin
+ M.adjustStaminaLoss((current_cycle - 5)*REM, 0)
+ if(M.getStaminaLoss() >= 145) // fake death tied to stamina for interesting interactions - 23 ticks to fake death with pure ZP
+ M.fakedeath(type)
..()
- L.fakedeath(type)
/datum/reagent/toxin/zombiepowder/on_mob_end_metabolize(mob/living/L)
L.cure_fakedeath(type)
..()
-/datum/reagent/toxin/zombiepowder/on_mob_life(mob/living/carbon/M)
- M.adjustOxyLoss(0.5*REM, 0)
- ..()
- . = 1
-
/datum/reagent/toxin/ghoulpowder
name = "Ghoul Powder"
description = "A strong neurotoxin that slows metabolism to a death-like state while keeping the patient fully active. Causes toxin buildup if used too long."
@@ -485,7 +501,7 @@
/datum/reagent/toxin/itching_powder/reaction_mob(mob/living/M, method=TOUCH, reac_volume)
if(method == TOUCH || method == VAPOR)
- M.reagents.add_reagent(/datum/reagent/toxin/itching_powder, reac_volume)
+ M.reagents?.add_reagent(/datum/reagent/toxin/itching_powder, reac_volume)
/datum/reagent/toxin/itching_powder/on_mob_life(mob/living/carbon/M)
if(prob(15))
@@ -594,7 +610,7 @@
toxpwr = 0
metabolization_rate = 0.5 * REAGENTS_METABOLISM
-/datum/reagent/toxin/amanitin/on_mob_end_metabolize(mob/living/M)
+/datum/reagent/toxin/amanitin/on_mob_delete(mob/living/M)
var/toxdamage = current_cycle*3*REM
M.log_message("has taken [toxdamage] toxin damage from amanitin toxin", LOG_ATTACK)
M.adjustToxLoss(toxdamage)
@@ -701,57 +717,20 @@
/datum/reagent/toxin/rotatium/on_mob_life(mob/living/carbon/M)
if(M.hud_used)
if(current_cycle >= 20 && current_cycle%20 == 0)
- var/list/screens = list(M.hud_used.plane_masters["[FLOOR_PLANE]"], M.hud_used.plane_masters["[GAME_PLANE]"], M.hud_used.plane_masters["[LIGHTING_PLANE]"])
+ var/atom/movable/plane_master_controller/pm_controller = M.hud_used.plane_master_controllers[PLANE_MASTERS_GAME]
var/rotation = min(round(current_cycle/20), 89) // By this point the player is probably puking and quitting anyway
- for(var/whole_screen in screens)
- animate(whole_screen, transform = matrix(rotation, MATRIX_ROTATE), time = 5, easing = QUAD_EASING, loop = -1)
+ for(var/key in pm_controller.controlled_planes)
+ animate(pm_controller.controlled_planes[key], transform = matrix(rotation, MATRIX_ROTATE), time = 5, easing = QUAD_EASING, loop = -1)
animate(transform = matrix(-rotation, MATRIX_ROTATE), time = 5, easing = QUAD_EASING)
return ..()
/datum/reagent/toxin/rotatium/on_mob_end_metabolize(mob/living/M)
- if(M && M.hud_used)
- var/list/screens = list(M.hud_used.plane_masters["[FLOOR_PLANE]"], M.hud_used.plane_masters["[GAME_PLANE]"], M.hud_used.plane_masters["[LIGHTING_PLANE]"])
- for(var/whole_screen in screens)
- animate(whole_screen, transform = matrix(), time = 5, easing = QUAD_EASING)
- ..()
-
-/datum/reagent/toxin/skewium
- name = "Skewium"
- description = "A strange, dull coloured liquid that appears to warp back and forth inside its container. Causes any consumer to experience a visual phenomena similar to said warping."
- silent_toxin = TRUE
- reagent_state = LIQUID
- color = "#ADBDCD"
- metabolization_rate = 0.8 * REAGENTS_METABOLISM
- toxpwr = 0.25
- taste_description = "skewing"
- process_flags = ORGANIC | SYNTHETIC
-
-/datum/reagent/toxin/skewium/on_mob_life(mob/living/carbon/M)
- if(M.hud_used)
- if(current_cycle >= 5 && current_cycle % 3 == 0)
- var/list/screens = list(M.hud_used.plane_masters["[FLOOR_PLANE]"], M.hud_used.plane_masters["[GAME_PLANE]"], M.hud_used.plane_masters["[LIGHTING_PLANE]"])
- var/matrix/skew = matrix()
- var/intensity = 8
- skew.set_skew(rand(-intensity,intensity), rand(-intensity,intensity))
- var/matrix/newmatrix = skew
-
- if(prob(33)) // 1/3rd of the time, let's make it stack with the previous matrix! Mwhahahaha!
- var/obj/screen/plane_master/PM = M.hud_used.plane_masters["[GAME_PLANE]"]
- newmatrix = skew * PM.transform
-
- for(var/whole_screen in screens)
- animate(whole_screen, transform = newmatrix, time = 5, easing = QUAD_EASING, loop = -1)
- animate(transform = -newmatrix, time = 5, easing = QUAD_EASING)
- return ..()
-
-/datum/reagent/toxin/skewium/on_mob_end_metabolize(mob/living/M)
if(M?.hud_used)
- var/list/screens = list(M.hud_used.plane_masters["[FLOOR_PLANE]"], M.hud_used.plane_masters["[GAME_PLANE]"], M.hud_used.plane_masters["[LIGHTING_PLANE]"])
- for(var/whole_screen in screens)
- animate(whole_screen, transform = matrix(), time = 5, easing = QUAD_EASING)
+ var/atom/movable/plane_master_controller/pm_controller = M.hud_used.plane_master_controllers[PLANE_MASTERS_GAME]
+ for(var/key in pm_controller.controlled_planes)
+ animate(pm_controller.controlled_planes[key], transform = matrix(), time = 5, easing = QUAD_EASING)
..()
-
/datum/reagent/toxin/anacea
name = "Anacea"
description = "A toxin that quickly purges medicines and metabolizes very slowly."
@@ -892,7 +871,7 @@
if(M.dna.species.type != /datum/species/skeleton && M.dna.species.type != /datum/species/plasmaman) //We're so sorry skeletons, you're so misunderstood
if(bp)
bp.receive_damage(0, 0, 200)
- playsound(M, get_sfx("desceration"), 50, TRUE, -1)
+ playsound(M, get_sfx("desecration"), 50, TRUE, -1)
M.visible_message("[M]'s bones hurt too much!!", "Your bones hurt too much!!")
M.say("OOF!!", forced = /datum/reagent/toxin/bonehurtingjuice)
else //SUCH A LUST FOR REVENGE!!!
@@ -900,7 +879,7 @@
M.say("Why are we still here, just to suffer?", forced = /datum/reagent/toxin/bonehurtingjuice)
else //you just want to socialize
if(bp)
- playsound(M, get_sfx("desceration"), 50, TRUE, -1)
+ playsound(M, get_sfx("desecration"), 50, TRUE, -1)
M.visible_message("[M] rattles loudly and flails around!!", "Your bones hurt so much that your missing muscles spasm!!")
M.say("OOF!!", forced=/datum/reagent/toxin/bonehurtingjuice)
bp.receive_damage(200, 0, 0) //But I don't think we should
diff --git a/code/modules/reagents/chemistry/recipes.dm b/code/modules/reagents/chemistry/recipes.dm
index 4fbadcfa833d2..0b75cdf59abad 100644
--- a/code/modules/reagents/chemistry/recipes.dm
+++ b/code/modules/reagents/chemistry/recipes.dm
@@ -38,7 +38,7 @@
playsound(get_turf(holder.my_atom), 'sound/effects/phasein.ogg', 100, 1)
- for(var/mob/living/carbon/C in viewers(get_turf(holder.my_atom), null))
+ for(var/mob/living/carbon/C in viewers(get_turf(holder.my_atom)))
C.flash_act()
for(var/i in 1 to amount_to_spawn)
diff --git a/code/modules/reagents/chemistry/recipes/drugs.dm b/code/modules/reagents/chemistry/recipes/drugs.dm
index 7049c3da0eacc..d60c0da423cbe 100644
--- a/code/modules/reagents/chemistry/recipes/drugs.dm
+++ b/code/modules/reagents/chemistry/recipes/drugs.dm
@@ -47,3 +47,10 @@
results = list(/datum/reagent/drug/happiness = 4)
required_reagents = list(/datum/reagent/nitrous_oxide = 2, /datum/reagent/medicine/epinephrine = 1, /datum/reagent/consumable/ethanol = 1)
required_catalysts = list(/datum/reagent/toxin/plasma = 5)
+
+/datum/chemical_reaction/ketamine
+ name = "Ketamine"
+ id = /datum/reagent/drug/ketamine
+ results = list(/datum/reagent/drug/ketamine = 3)
+ required_reagents = list(/datum/reagent/medicine/morphine = 3, /datum/reagent/toxin/chloralhydrate = 3, /datum/reagent/toxin/fentanyl = 3, /datum/reagent/medicine/epinephrine =3)
+ required_temp = 370
diff --git a/code/modules/reagents/chemistry/recipes/medicine.dm b/code/modules/reagents/chemistry/recipes/medicine.dm
index 0cd71d14cf3a1..13ff092528afe 100644
--- a/code/modules/reagents/chemistry/recipes/medicine.dm
+++ b/code/modules/reagents/chemistry/recipes/medicine.dm
@@ -219,7 +219,7 @@
name = "Dexalin"
id = "dexalin"
results = list(/datum/reagent/medicine/dexalin = 5)
- required_reagents = list(/datum/reagent/oxygen = 5)
+ required_reagents = list(/datum/reagent/oxygen = 5, /datum/reagent/nitrogen = 5)
required_catalysts = list(/datum/reagent/toxin/plasma = 5)
/datum/chemical_reaction/dexalinp
@@ -252,6 +252,12 @@
results = list(/datum/reagent/medicine/regen_jelly = 2)
required_reagents = list(/datum/reagent/medicine/tricordrazine = 1, /datum/reagent/toxin/slimejelly = 1)
+/datum/chemical_reaction/regen_ooze
+ name = "Regenerative Ooze"
+ id = /datum/reagent/medicine/regen_ooze
+ results = list(/datum/reagent/medicine/regen_ooze = 2)
+ required_reagents = list(/datum/reagent/medicine/tricordrazine = 1, /datum/reagent/toxin/slimeooze = 1)
+
/datum/chemical_reaction/corazone
name = "Corazone"
id = /datum/reagent/medicine/corazone
@@ -305,3 +311,10 @@
results = list(/datum/reagent/medicine/hepanephrodaxon = 5)
required_reagents = list(/datum/reagent/medicine/carthatoline = 2, /datum/reagent/carbon = 2, /datum/reagent/lithium = 1)
required_catalysts = list(/datum/reagent/toxin/plasma = 5)
+
+/datum/chemical_reaction/liquidelectricity
+ name = "Liquid Electricity"
+ id = /datum/reagent/consumable/liquidelectricity
+ results = list(/datum/reagent/consumable/liquidelectricity = 3)
+ required_reagents = list(/datum/reagent/consumable/ethanol = 3, /datum/reagent/consumable/liquidelectricity = 1, /datum/reagent/toxin/plasma = 1)
+ mix_message = "The mixture sparks and then subsides."
\ No newline at end of file
diff --git a/code/modules/reagents/chemistry/recipes/others.dm b/code/modules/reagents/chemistry/recipes/others.dm
index 53b9a50e754e1..71426d8833381 100644
--- a/code/modules/reagents/chemistry/recipes/others.dm
+++ b/code/modules/reagents/chemistry/recipes/others.dm
@@ -37,18 +37,6 @@
results = list(/datum/reagent/impedrezene = 2)
required_reagents = list(/datum/reagent/mercury = 1, /datum/reagent/oxygen = 1, /datum/reagent/consumable/sugar = 1)
-/datum/chemical_reaction/concentrated_bz
- name = "Concentrated BZ"
- id = "Concentrated BZ"
- results = list(/datum/reagent/concentrated_bz = 10)
- required_reagents = list(/datum/reagent/toxin/plasma = 40, /datum/reagent/nitrous_oxide = 10)
-
-/datum/chemical_reaction/fake_cbz
- name = "Fake CBZ"
- id = "Fake CBZ"
- results = list(/datum/reagent/fake_cbz = 1)
- required_reagents = list(/datum/reagent/concentrated_bz = 1, /datum/reagent/medicine/neurine = 3)
-
/datum/chemical_reaction/cryptobiolin
name = "Cryptobiolin"
id = /datum/reagent/cryptobiolin
@@ -434,7 +422,7 @@
/datum/chemical_reaction/foam/on_reaction(datum/reagents/holder, created_volume)
var/location = get_turf(holder.my_atom)
- for(var/mob/M in viewers(5, location))
+ for(var/mob/M as() in viewers(5, location))
to_chat(M, "The solution spews out foam!")
var/datum/effect_system/foam_spread/s = new()
s.set_up(created_volume*2, location, holder)
@@ -452,7 +440,7 @@
/datum/chemical_reaction/metalfoam/on_reaction(datum/reagents/holder, created_volume)
var/location = get_turf(holder.my_atom)
- for(var/mob/M in viewers(5, location))
+ for(var/mob/M as() in viewers(5, location))
to_chat(M, "The solution spews out a metallic foam!")
var/datum/effect_system/foam_spread/metal/s = new()
@@ -482,7 +470,7 @@
/datum/chemical_reaction/ironfoam/on_reaction(datum/reagents/holder, created_volume)
var/location = get_turf(holder.my_atom)
- for(var/mob/M in viewers(5, location))
+ for(var/mob/M as() in viewers(5, location))
to_chat(M, "The solution spews out a metallic foam!")
var/datum/effect_system/foam_spread/metal/s = new()
s.set_up(created_volume*5, location, holder, 2)
@@ -608,12 +596,15 @@
name = "corgium"
id = "corgium"
required_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/colorful_reagent = 1, /datum/reagent/medicine/strange_reagent = 1, /datum/reagent/blood = 1)
- required_temp = 374
/datum/chemical_reaction/corgium/on_reaction(datum/reagents/holder, created_volume)
- var/location = get_turf(holder.my_atom)
- for(var/i = rand(1, created_volume), i <= created_volume, i++) // More lulz.
- new /mob/living/simple_animal/pet/dog/corgi(location)
+ if(isliving(holder.my_atom) && !iscorgi(holder.my_atom))
+ var/mob/living/L = holder
+ L.reagents.add_reagent(/datum/reagent/corgium, created_volume)
+ else
+ var/location = get_turf(holder.my_atom)
+ for(var/i in rand(1, created_volume) to created_volume) // More lulz.
+ new /mob/living/simple_animal/pet/dog/corgi(location)
..()
/datum/chemical_reaction/hair_dye
@@ -714,6 +705,12 @@
results = list(/datum/reagent/mutationtoxin/moth = 5)
required_reagents = list(/datum/reagent/mutationtoxin/unstable = 5, /datum/reagent/toxin/lipolicide = 10) //I know it's the opposite of what moths like, but I am out of ideas for this.
+/datum/chemical_reaction/mutationtoxin/apid
+ name = /datum/reagent/mutationtoxin/apid
+ id = /datum/reagent/mutationtoxin/apid
+ results = list(/datum/reagent/mutationtoxin/apid = 5)
+ required_reagents = list(/datum/reagent/mutationtoxin/unstable = 5, /datum/reagent/consumable/honey = 20) // beeeeeeeeeeeeeeeeeeeeees
+
/datum/chemical_reaction/mutationtoxin/pod
name = /datum/reagent/mutationtoxin/pod
id = /datum/reagent/mutationtoxin/pod
@@ -736,15 +733,27 @@
name = /datum/reagent/mutationtoxin/squid
id = /datum/reagent/mutationtoxin/squid
results = list(/datum/reagent/mutationtoxin/squid = 5)
- required_reagents = list(/datum/reagent/mutationtoxin/unstable = 5, /datum/reagent/teslium = 20)
+ required_reagents = list(/datum/reagent/mutationtoxin/unstable = 5, /datum/reagent/consumable/sodiumchloride = 10, /datum/reagent/water = 20)
/datum/chemical_reaction/mutationtoxin/ipc
name = /datum/reagent/mutationtoxin/ipc
id = /datum/reagent/mutationtoxin/ipc
results = list(/datum/reagent/mutationtoxin/ipc = 5)
- required_reagents = list(/datum/reagent/mutationtoxin/unstable = 5, /datum/reagent/consumable/sodiumchloride = 10, /datum/reagent/water = 20)
-
-//////////////Mutatuion toxins made out of advanced toxin/////////////
+ required_reagents = list(/datum/reagent/mutationtoxin/unstable = 5, /datum/reagent/teslium = 20)
+
+/datum/chemical_reaction/mutationtoxin/ethereal
+ name = /datum/reagent/mutationtoxin/ethereal
+ id = /datum/reagent/mutationtoxin/ethereal
+ results = list(/datum/reagent/mutationtoxin/ethereal = 5)
+ required_reagents = list(/datum/reagent/mutationtoxin/unstable = 5, /datum/reagent/consumable/liquidelectricity = 20)
+
+/datum/chemical_reaction/mutationtoxin/oozeling
+ name = /datum/reagent/mutationtoxin/oozeling
+ id = /datum/reagent/mutationtoxin/oozeling
+ results = list(/datum/reagent/mutationtoxin/oozeling = 5)
+ required_reagents = list(/datum/reagent/mutationtoxin/unstable = 5, /datum/reagent/medicine/calomel = 10, /datum/reagent/toxin/bad_food = 30, /datum/reagent/stable_plasma = 5)
+
+//////////////Mutation toxins made out of advanced toxin/////////////
/datum/chemical_reaction/mutationtoxin/skeleton
name = /datum/reagent/mutationtoxin/skeleton
diff --git a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm
index f331563daccc8..160372886a7a2 100644
--- a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm
+++ b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm
@@ -5,6 +5,9 @@
var/modifier = 0
/datum/chemical_reaction/reagent_explosion/on_reaction(datum/reagents/holder, created_volume)
+ explode(holder, created_volume)
+
+/datum/chemical_reaction/reagent_explosion/proc/explode(datum/reagents/holder, created_volume)
var/power = modifier + round(created_volume/strengthdiv, 1)
if(power > 0)
var/turf/T = get_turf(holder.my_atom)
@@ -12,10 +15,10 @@
if(ismob(holder.my_atom))
var/mob/M = holder.my_atom
inside_msg = " inside [ADMIN_LOOKUPFLW(M)]"
- var/lastkey = holder.my_atom.fingerprintslast
+ var/lastkey = holder.my_atom?.fingerprintslast
var/touch_msg = "N/A"
if(lastkey)
- var/mob/toucher = get_mob_by_key(lastkey)
+ var/mob/toucher = get_mob_by_ckey(lastkey)
touch_msg = "[ADMIN_LOOKUPFLW(toucher)]"
if(!istype(holder.my_atom, /obj/machinery/plumbing)) //excludes standard plumbing equipment from spamming admins with this shit
message_admins("Reagent explosion reaction occurred at [ADMIN_VERBOSEJMP(T)][inside_msg]. Last Fingerprint: [touch_msg].")
@@ -62,25 +65,22 @@
if(created_volume >= 150)
playsound(get_turf(holder.my_atom), 'sound/effects/pray.ogg', 80, 0, round(created_volume/48))
strengthdiv = 8
- for(var/mob/living/simple_animal/revenant/R in get_hearers_in_view(7,get_turf(holder.my_atom)))
- var/deity
- if(GLOB.deity)
- deity = GLOB.deity
- else
- deity = "Christ"
+ for(var/mob/living/simple_animal/revenant/R in hearers(7,get_turf(holder.my_atom)))
+ var/deity = GLOB.deity || "Christ"
to_chat(R, "The power of [deity] compels you!")
R.stun(20)
R.reveal(100)
R.adjustHealth(50)
- sleep(20)
- for(var/mob/living/carbon/C in get_hearers_in_view(round(created_volume/48,1),get_turf(holder.my_atom)))
- if(iscultist(C))
- to_chat(C, "The divine explosion sears you!")
- C.Paralyze(40)
- C.adjust_fire_stacks(5)
- C.IgniteMob()
+ addtimer(CALLBACK(src, .proc/divine_explosion, round(created_volume/48,1),get_turf(holder.my_atom)), 2 SECONDS)
..()
+/datum/chemical_reaction/reagent_explosion/potassium_explosion/holyboom/proc/divine_explosion(size, turf/T)
+ for(var/mob/living/carbon/C in hearers(size,T))
+ if(iscultist(C))
+ to_chat(C, "The divine explosion sears you!")
+ C.Paralyze(40)
+ C.adjust_fire_stacks(5)
+ C.IgniteMob()
/datum/chemical_reaction/blackpowder
name = "Black Powder"
@@ -98,8 +98,7 @@
mix_message = "Sparks start flying around the black powder!"
/datum/chemical_reaction/reagent_explosion/blackpowder_explosion/on_reaction(datum/reagents/holder, created_volume)
- sleep(rand(50,100))
- ..()
+ addtimer(CALLBACK(src, .proc/explode, holder, created_volume), rand(50,100))
/datum/chemical_reaction/thermite
name = "Thermite"
@@ -158,8 +157,9 @@
/datum/chemical_reaction/clf3/on_reaction(datum/reagents/holder, created_volume)
var/turf/T = get_turf(holder.my_atom)
- for(var/turf/turf in range(1,T))
- new /obj/effect/hotspot(turf)
+ for(var/turf/open/turf in RANGE_TURFS(1,T))
+ if(!locate(/obj/effect/hotspot) in turf)
+ new /obj/effect/hotspot(turf)
holder.chem_temp = 1000 // hot as shit
/datum/chemical_reaction/reagent_explosion/methsplosion
@@ -173,8 +173,9 @@
/datum/chemical_reaction/reagent_explosion/methsplosion/on_reaction(datum/reagents/holder, created_volume)
var/turf/T = get_turf(holder.my_atom)
- for(var/turf/turf in range(1,T))
- new /obj/effect/hotspot(turf)
+ for(var/turf/open/turf in RANGE_TURFS(1,T))
+ if(!locate(/obj/effect/hotspot) in turf)
+ new /obj/effect/hotspot(turf)
holder.chem_temp = 1000 // hot as shit
..()
@@ -248,7 +249,7 @@
if(isatom(holder.my_atom))
var/atom/A = holder.my_atom
A.flash_lighting_fx(_range = (range + 2), _reset_lighting = FALSE)
- for(var/mob/living/carbon/C in get_hearers_in_view(range, location))
+ for(var/mob/living/carbon/C in hearers(range, location))
if(C.flash_act())
if(get_dist(C, location) < 4)
C.Paralyze(60)
@@ -269,7 +270,7 @@
if(isatom(holder.my_atom))
var/atom/A = holder.my_atom
A.flash_lighting_fx(_range = (range + 2), _reset_lighting = FALSE)
- for(var/mob/living/carbon/C in get_hearers_in_view(range, location))
+ for(var/mob/living/carbon/C in hearers(range, location))
if(C.flash_act())
if(get_dist(C, location) < 4)
C.Paralyze(60)
@@ -328,7 +329,7 @@
holder.remove_reagent(/datum/reagent/sonic_powder, created_volume*3)
var/location = get_turf(holder.my_atom)
playsound(location, 'sound/effects/bang.ogg', 25, 1)
- for(var/mob/living/carbon/C in get_hearers_in_view(created_volume/3, location))
+ for(var/mob/living/carbon/C in hearers(created_volume/3, location))
C.soundbang_act(1, 100, rand(0, 5))
/datum/chemical_reaction/sonic_powder_deafen
@@ -340,7 +341,7 @@
/datum/chemical_reaction/sonic_powder_deafen/on_reaction(datum/reagents/holder, created_volume)
var/location = get_turf(holder.my_atom)
playsound(location, 'sound/effects/bang.ogg', 25, 1)
- for(var/mob/living/carbon/C in get_hearers_in_view(created_volume/10, location))
+ for(var/mob/living/carbon/C in hearers(created_volume/10, location))
C.soundbang_act(1, 100, rand(0, 5))
/datum/chemical_reaction/phlogiston
@@ -419,6 +420,13 @@
required_reagents = list(/datum/reagent/toxin/slimejelly = 1, /datum/reagent/teslium = 1)
mix_message = "The slime jelly starts glowing intermittently."
+/datum/chemical_reaction/energized_jelly/energized_ooze
+ name = "Energized Ooze"
+ id = /datum/reagent/teslium/energized_jelly/energized_ooze
+ results = list(/datum/reagent/teslium/energized_jelly/energized_ooze = 2)
+ required_reagents = list(/datum/reagent/toxin/slimeooze = 1, /datum/reagent/teslium = 1)
+ mix_message = "The slime ooze starts glowing intermittently."
+
/datum/chemical_reaction/reagent_explosion/teslium_lightning
name = "Teslium Destabilization"
id = "teslium_lightning"
@@ -433,19 +441,22 @@
var/T1 = created_volume * 20 //100 units : Zap 3 times, with powers 2000/5000/12000. Tesla revolvers have a power of 10000 for comparison.
var/T2 = created_volume * 50
var/T3 = created_volume * 120
- sleep(5)
+ var/added_delay = 0.5 SECONDS
if(created_volume >= 75)
- tesla_zap(holder.my_atom, 7, T1, tesla_flags)
- playsound(holder.my_atom, 'sound/machines/defib_zap.ogg', 50, 1)
- sleep(15)
+ addtimer(CALLBACK(src, .proc/zappy_zappy, holder, T1), added_delay)
+ added_delay += 1.5 SECONDS
if(created_volume >= 40)
- tesla_zap(holder.my_atom, 7, T2, tesla_flags)
- playsound(holder.my_atom, 'sound/machines/defib_zap.ogg', 50, 1)
- sleep(15)
+ addtimer(CALLBACK(src, .proc/zappy_zappy, holder, T2), added_delay)
+ added_delay += 1.5 SECONDS
if(created_volume >= 10) //10 units minimum for lightning, 40 units for secondary blast, 75 units for tertiary blast.
- tesla_zap(holder.my_atom, 7, T3, tesla_flags)
- playsound(holder.my_atom, 'sound/machines/defib_zap.ogg', 50, 1)
- ..()
+ addtimer(CALLBACK(src, .proc/zappy_zappy, holder, T3), added_delay)
+ addtimer(CALLBACK(src, .proc/explode, holder, created_volume), added_delay)
+
+/datum/chemical_reaction/reagent_explosion/teslium_lightning/proc/zappy_zappy(datum/reagents/holder, power)
+ if(QDELETED(holder.my_atom))
+ return
+ tesla_zap(holder.my_atom, 7, power, tesla_flags)
+ playsound(holder.my_atom, 'sound/machines/defib_zap.ogg', 50, TRUE)
/datum/chemical_reaction/reagent_explosion/teslium_lightning/heat
id = "teslium_lightning2"
diff --git a/code/modules/reagents/chemistry/recipes/slime_extracts.dm b/code/modules/reagents/chemistry/recipes/slime_extracts.dm
index 22592fddc4bbc..59aa13f945b14 100644
--- a/code/modules/reagents/chemistry/recipes/slime_extracts.dm
+++ b/code/modules/reagents/chemistry/recipes/slime_extracts.dm
@@ -3,6 +3,9 @@
var/deletes_extract = TRUE
/datum/chemical_reaction/slime/on_reaction(datum/reagents/holder)
+ use_slime_core(holder)
+
+/datum/chemical_reaction/slime/proc/use_slime_core(datum/reagents/holder)
SSblackbox.record_feedback("tally", "slime_cores_used", 1, "type")
if(deletes_extract)
delete_extract(holder)
@@ -153,7 +156,7 @@
playsound(T, 'sound/effects/phasein.ogg', 100, 1)
- for(var/mob/living/carbon/C in viewers(T, null))
+ for(var/mob/living/carbon/C in viewers(T))
C.flash_act()
for(var/i in 1 to 4 + rand(1,2))
@@ -230,8 +233,7 @@
if(holder && holder.my_atom)
var/turf/open/T = get_turf(holder.my_atom)
if(istype(T))
- var/datum/gas/gastype = /datum/gas/nitrogen
- T.atmos_spawn_air("[initial(gastype.id)]=50;TEMP=2.7")
+ T.atmos_spawn_air("n2=50;TEMP=2.7")
/datum/chemical_reaction/slime/slimefireproof
name = "Slime Fireproof"
@@ -298,7 +300,7 @@
..()
/datum/chemical_reaction/slime/slimecell
- name = "Slime Powercell"
+ name = "Slime Power Cell"
id = "m_cell"
required_reagents = list(/datum/reagent/toxin/plasma = 1)
required_container = /obj/item/slime_extract/yellow
@@ -373,7 +375,7 @@
required_other = TRUE
/datum/chemical_reaction/slime/slimebloodlust/on_reaction(datum/reagents/holder)
- for(var/mob/living/simple_animal/slime/slime in viewers(get_turf(holder.my_atom), null))
+ for(var/mob/living/simple_animal/slime/slime in viewers(get_turf(holder.my_atom)))
if(slime.docile) //Undoes docility, but doesn't make rabid.
slime.visible_message("[slime] forgets its training, becoming wild once again!")
slime.docile = FALSE
@@ -440,7 +442,7 @@
var/lastkey = holder.my_atom.fingerprintslast
var/touch_msg = "N/A"
if(lastkey)
- var/mob/toucher = get_mob_by_key(lastkey)
+ var/mob/toucher = get_mob_by_ckey(lastkey)
touch_msg = "[ADMIN_LOOKUPFLW(toucher)]."
message_admins("Slime Explosion reaction started at [ADMIN_VERBOSEJMP(T)]. Last Fingerprint: [touch_msg]")
log_game("Slime Explosion reaction started at [AREACOORD(T)]. Last Fingerprint: [lastkey ? lastkey : "N/A"].")
@@ -568,17 +570,19 @@
required_other = TRUE
/datum/chemical_reaction/slime/slimestop/on_reaction(datum/reagents/holder)
- sleep(50)
+ addtimer(CALLBACK(src, .proc/slime_stop, holder), 5 SECONDS)
+
+/datum/chemical_reaction/slime/slimestop/proc/slime_stop(datum/reagents/holder)
var/obj/item/slime_extract/sepia/extract = holder.my_atom
var/turf/T = get_turf(holder.my_atom)
new /obj/effect/timestop(T, null, null, null)
if(istype(extract))
if(extract.Uses > 0)
- var/mob/lastheld = get_mob_by_key(holder.my_atom.fingerprintslast)
- if(lastheld && !lastheld.equip_to_slot_if_possible(extract, SLOT_HANDS, disable_warning = TRUE))
+ var/mob/lastheld = get_mob_by_ckey(holder.my_atom.fingerprintslast)
+ if(lastheld && !lastheld.equip_to_slot_if_possible(extract, ITEM_SLOT_HANDS, disable_warning = TRUE))
extract.forceMove(get_turf(lastheld))
- ..()
+ use_slime_core(holder)
/datum/chemical_reaction/slime/slimecamera
name = "Slime Camera"
diff --git a/code/modules/reagents/chemistry/recipes/toxins.dm b/code/modules/reagents/chemistry/recipes/toxins.dm
index 709bd47ed7ad4..a44f227dac6c4 100644
--- a/code/modules/reagents/chemistry/recipes/toxins.dm
+++ b/code/modules/reagents/chemistry/recipes/toxins.dm
@@ -101,13 +101,6 @@
required_reagents = list(/datum/reagent/toxin/mindbreaker = 1, /datum/reagent/teslium = 1, /datum/reagent/toxin/fentanyl = 1)
mix_message = "After sparks, fire, and the smell of mindbreaker, the mix is constantly spinning with no stop in sight."
-/datum/chemical_reaction/skewium
- name = "Skewium"
- id = "Skewium"
- results = list(/datum/reagent/toxin/skewium = 5)
- required_reagents = list(/datum/reagent/toxin/rotatium = 2, /datum/reagent/toxin/plasma = 2, /datum/reagent/toxin/acid = 1)
- mix_message = "Wow! it turns out if you mix rotatium with some plasma and sulphuric acid, it gets even worse!"
-
/datum/chemical_reaction/anacea
name = "Anacea"
id = /datum/reagent/toxin/anacea
diff --git a/code/modules/reagents/reagent_containers.dm b/code/modules/reagents/reagent_containers.dm
index ae40640df6418..683e9e5477254 100644
--- a/code/modules/reagents/reagent_containers.dm
+++ b/code/modules/reagents/reagent_containers.dm
@@ -42,7 +42,7 @@
amount_per_transfer_from_this = possible_transfer_amounts[i+1]
else
amount_per_transfer_from_this = possible_transfer_amounts[1]
- to_chat(user, "[src]'s transfer amount is now [amount_per_transfer_from_this] units.")
+ balloon_alert(user, "Transferring [amount_per_transfer_from_this]u")
return
/obj/item/reagent_containers/attack(mob/M, mob/user, def_zone)
@@ -51,24 +51,24 @@
/obj/item/reagent_containers/proc/canconsume(mob/eater, mob/user)
if(!iscarbon(eater))
- return 0
+ return FALSE
var/mob/living/carbon/C = eater
var/covered = ""
if(C.is_mouth_covered(head_only = 1))
covered = "headgear"
else if(C.is_mouth_covered(mask_only = 1))
covered = "mask"
- if(covered)
+ if(covered && user.a_intent != INTENT_HARM)
var/who = (isnull(user) || eater == user) ? "your" : "[eater.p_their()]"
- to_chat(user, "You have to remove [who] [covered] first!")
- return 0
+ balloon_alert(user, "Remove [who] [covered] first")
+ return FALSE
if(!eater.has_mouth())
if(eater == user)
- to_chat(eater, "You have no mouth, and cannot eat.")
+ balloon_alert(eater, "You have no mouth")
else
- to_chat(user, "You can't feed [eater], because they have no mouth!")
- return 0
- return 1
+ balloon_alert(user, "[eater] has no mouth")
+ return FALSE
+ return TRUE
/obj/item/reagent_containers/ex_act()
if(reagents)
@@ -87,12 +87,14 @@
/obj/item/reagent_containers/proc/bartender_check(atom/target)
. = FALSE
- if(target.CanPass(src, get_turf(src)) && thrownby && HAS_TRAIT(thrownby, TRAIT_BOOZE_SLIDER))
+ var/mob/thrown_by = thrownby?.resolve()
+ if(target.CanPass(src, get_turf(src)) && thrown_by && HAS_TRAIT(thrown_by, TRAIT_BOOZE_SLIDER))
. = TRUE
/obj/item/reagent_containers/proc/SplashReagents(atom/target, thrown = FALSE)
if(!reagents || !reagents.total_volume || !spillable)
return
+ var/mob/thrown_by = thrownby?.resolve()
if(ismob(target) && target.reagents)
if(thrown)
@@ -105,7 +107,7 @@
R += "[A.type] ([num2text(A.volume)]),"
if(thrownby)
- log_combat(thrownby, M, "splashed", R)
+ log_combat(thrown_by, M, "splashed", R)
reagents.reaction(target, TOUCH)
else if(bartender_check(target) && thrown)
@@ -113,10 +115,10 @@
return
else
- if(isturf(target) && reagents.reagent_list.len && thrownby)
- log_combat(thrownby, target, "splashed (thrown) [english_list(reagents.reagent_list)]", "in [AREACOORD(target)]")
- log_game("[key_name(thrownby)] splashed (thrown) [english_list(reagents.reagent_list)] on [target] in [AREACOORD(target)].")
- message_admins("[ADMIN_LOOKUPFLW(thrownby)] splashed (thrown) [english_list(reagents.reagent_list)] on [target] in [ADMIN_VERBOSEJMP(target)].")
+ if(isturf(target) && reagents.reagent_list.len && thrown_by)
+ log_combat(thrown_by, target, "splashed (thrown) [english_list(reagents.reagent_list)]", "in [AREACOORD(target)]")
+ log_game("[key_name(thrown_by)] splashed (thrown) [english_list(reagents.reagent_list)] on [target] in [AREACOORD(target)].")
+ message_admins("[ADMIN_LOOKUPFLW(thrown_by)] splashed (thrown) [english_list(reagents.reagent_list)] on [target] in [ADMIN_VERBOSEJMP(target)].")
visible_message("[src] spills its contents all over [target].")
reagents.reaction(target, TOUCH)
if(QDELETED(src))
diff --git a/code/modules/reagents/reagent_containers/blood_pack.dm b/code/modules/reagents/reagent_containers/blood_pack.dm
index 80efeaa2a5fa5..3a686b7695802 100644
--- a/code/modules/reagents/reagent_containers/blood_pack.dm
+++ b/code/modules/reagents/reagent_containers/blood_pack.dm
@@ -12,7 +12,7 @@
/obj/item/reagent_containers/blood/Initialize()
. = ..()
if(blood_type != null)
- reagents.add_reagent(unique_blood ? unique_blood : /datum/reagent/blood, 200, list("donor"=null,"viruses"=null,"blood_DNA"=null,"blood_type"=blood_type,"resistances"=null,"trace_chem"=null))
+ reagents.add_reagent(unique_blood ? unique_blood : /datum/reagent/blood, 200, list("viruses"=null,"blood_DNA"=null,"blood_type"=blood_type,"resistances"=null,"trace_chem"=null))
update_icon()
/obj/item/reagent_containers/blood/on_reagent_change(changetype)
@@ -67,6 +67,12 @@
blood_type = "LE"
unique_blood = /datum/reagent/consumable/liquidelectricity
+/obj/item/reagent_containers/blood/oozeling
+ labelled = 1
+ name = "blood pack - OZ"
+ blood_type = "OZ"
+ unique_blood = /datum/reagent/toxin/slimeooze
+
/obj/item/reagent_containers/blood/universal
blood_type = "U"
diff --git a/code/modules/reagents/reagent_containers/borghydro.dm b/code/modules/reagents/reagent_containers/borghydro.dm
index 7a70fe34c166d..7011be0db7b24 100644
--- a/code/modules/reagents/reagent_containers/borghydro.dm
+++ b/code/modules/reagents/reagent_containers/borghydro.dm
@@ -21,8 +21,8 @@ Borg Hypospray
possible_transfer_amounts = list()
var/mode = 1
var/charge_cost = 50
- var/charge_tick = 0
- var/recharge_time = 5 //Time it takes for shots to recharge (in seconds)
+ var/charge_timer = 0
+ var/recharge_time = 10 //Time it takes for shots to recharge (in seconds)
var/bypass_protection = 0 //If the hypospray can go through armor or thick material
var/list/datum/reagents/reagent_list = list()
@@ -44,14 +44,14 @@ Borg Hypospray
/obj/item/reagent_containers/borghypo/Destroy()
STOP_PROCESSING(SSobj, src)
+ QDEL_LIST(reagent_list)
return ..()
-
-/obj/item/reagent_containers/borghypo/process() //Every [recharge_time] seconds, recharge some reagents for the cyborg
- charge_tick++
- if(charge_tick >= recharge_time)
+/obj/item/reagent_containers/borghypo/process(delta_time) //Every [recharge_time] seconds, recharge some reagents for the cyborg
+ charge_timer += delta_time
+ if(charge_timer >= recharge_time)
regenerate_reagents()
- charge_tick = 0
+ charge_timer = 0
//update_icon()
return 1
@@ -181,6 +181,7 @@ Borg Shaker
possible_transfer_amounts = list(5,10,20)
charge_cost = 20 //Lots of reagents all regenerating at once, so the charge cost is lower. They also regenerate faster.
recharge_time = 3
+ volume = 50
accepts_reagent_upgrades = FALSE
reagent_ids = list(
@@ -197,7 +198,6 @@ Borg Shaker
/datum/reagent/consumable/ethanol/vodka,
/datum/reagent/consumable/ethanol/whiskey,
/datum/reagent/consumable/ethanol/wine,
- /datum/reagent/consumable/enzyme,
/datum/reagent/consumable/banana,
/datum/reagent/consumable/coffee,
/datum/reagent/consumable/cream,
diff --git a/code/modules/reagents/reagent_containers/bottle.dm b/code/modules/reagents/reagent_containers/bottle.dm
index e97821fda6e79..3c6de5d7885f9 100644
--- a/code/modules/reagents/reagent_containers/bottle.dm
+++ b/code/modules/reagents/reagent_containers/bottle.dm
@@ -20,6 +20,21 @@
desc = "A small bottle. Contains epinephrine - used to stabilize patients."
list_reagents = list(/datum/reagent/medicine/epinephrine = 30)
+/obj/item/reagent_containers/glass/bottle/tricordrazine
+ name = "tricordrazine bottle"
+ desc = "A small bottle of tricordrazine. Used to aid in patient recovery."
+ list_reagents = list(/datum/reagent/medicine/tricordrazine = 30)
+
+/obj/item/reagent_containers/glass/bottle/spaceacillin
+ name = "spaceacillin bottle"
+ desc = "A small bottle of spaceacillin. Used to cure some diseases."
+ list_reagents = list(/datum/reagent/medicine/spaceacillin = 30)
+
+/obj/item/reagent_containers/glass/bottle/antitoxin
+ name = "antitoxin bottle"
+ desc = "A small bottle of anti-toxin. Used to treat toxin damage."
+ list_reagents = list(/datum/reagent/medicine/antitoxin = 30)
+
/obj/item/reagent_containers/glass/bottle/toxin
name = "toxin bottle"
desc = "A small bottle of toxins. Do not drink, it is poisonous."
@@ -45,7 +60,7 @@
name = "chloral hydrate bottle"
desc = "A small bottle of Choral Hydrate. Mickey's Favorite!"
icon_state = "bottle20"
- list_reagents = list(/datum/reagent/toxin/chloralhydrate = 15)
+ list_reagents = list(/datum/reagent/toxin/chloralhydrate = 30)
/obj/item/reagent_containers/glass/bottle/mannitol
name = "mannitol bottle"
@@ -79,14 +94,9 @@
/obj/item/reagent_containers/glass/bottle/cryostylane
name = "cryostylane bottle"
- desc = "A small bottle of cryostylane. It feels cold to the touch"
+ desc = "A small bottle of cryostylane. It feels cold to the touch."
list_reagents = list(/datum/reagent/cryostylane = 30)
-/obj/item/reagent_containers/glass/bottle/concentrated_bz
- name = "concentrated BZ bottle"
- desc = "A small bottle of concentrated BZ"
- list_reagents = list(/datum/reagent/concentrated_bz = 30)
-
/obj/item/reagent_containers/glass/bottle/ammonia
name = "ammonia bottle"
desc = "A small bottle of ammonia."
@@ -206,9 +216,8 @@
list_reagents = list(/datum/reagent/medicine/potass_iodide = 30)
/obj/item/reagent_containers/glass/bottle/salglu_solution
- name = "saline-glucose solution bottle"
- desc = "A small bottle of saline-glucose solution."
- icon_state = "bottle1"
+ name = "saline-glucose bottle"
+ desc = "A small bottle of saline-glucose solution. Useful for patients lacking in blood volume."
list_reagents = list(/datum/reagent/medicine/salglu_solution = 30)
/obj/item/reagent_containers/glass/bottle/atropine
@@ -306,7 +315,7 @@
/obj/item/reagent_containers/glass/bottle/felinid
name = "Nano-Feline Assimilative Toxoplasmosis culture bottle"
- desc = "A small bottle. Contains a sample of nano-feline toxoplasma in synthblood medium"
+ desc = "A small bottle. Contains a sample of nano-feline toxoplasma in synthblood medium."
spawned_disease = /datum/disease/transformation/felinid/contagious
//Oldstation.dmm chemical storage bottles
diff --git a/code/modules/reagents/reagent_containers/glass.dm b/code/modules/reagents/reagent_containers/glass.dm
index e9490b2569c90..1f928dd0a02bc 100755
--- a/code/modules/reagents/reagent_containers/glass.dm
+++ b/code/modules/reagents/reagent_containers/glass.dm
@@ -37,14 +37,14 @@
reagents.clear_reagents()
else
if(M != user)
- M.visible_message("[user] attempts to feed [M] something.", \
- "[user] attempts to feed you something.")
+ M.visible_message("[user] attempts to feed [M] something from [src].", \
+ "[user] attempts to feed you something from [src].")
if(!do_mob(user, M))
return
if(!reagents || !reagents.total_volume)
return // The drink might be empty after the delay, such as by spam-feeding
- M.visible_message("[user] feeds [M] something.", \
- "[user] feeds you something.")
+ M.visible_message("[user] feeds [M] something from [src].", \
+ "[user] feeds you something from [src].")
log_combat(user, M, "fed", reagents.log_list())
else
to_chat(user, "You swallow a gulp of [src].")
@@ -230,16 +230,16 @@
flags_inv = HIDEHAIR
slot_flags = ITEM_SLOT_HEAD
resistance_flags = NONE
- armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 75, "acid" = 50) //Weak melee protection, because you can wear it on your head
+ armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 75, "acid" = 50, "stamina" = 0) //Weak melee protection, because you can wear it on your head
slot_equipment_priority = list( \
- SLOT_BACK, SLOT_WEAR_ID,\
- SLOT_W_UNIFORM, SLOT_WEAR_SUIT,\
- SLOT_WEAR_MASK, SLOT_HEAD, SLOT_NECK,\
- SLOT_SHOES, SLOT_GLOVES,\
- SLOT_EARS, SLOT_GLASSES,\
- SLOT_BELT, SLOT_S_STORE,\
- SLOT_L_STORE, SLOT_R_STORE,\
- SLOT_GENERC_DEXTROUS_STORAGE
+ ITEM_SLOT_BACK, ITEM_SLOT_ID,\
+ ITEM_SLOT_ICLOTHING, ITEM_SLOT_OCLOTHING,\
+ ITEM_SLOT_MASK, ITEM_SLOT_HEAD, ITEM_SLOT_NECK,\
+ ITEM_SLOT_FEET, ITEM_SLOT_GLOVES,\
+ ITEM_SLOT_EARS, ITEM_SLOT_EYES,\
+ ITEM_SLOT_BELT, ITEM_SLOT_SUITSTORE,\
+ ITEM_SLOT_LPOCKET, ITEM_SLOT_RPOCKET,\
+ ITEM_SLOT_DEX_STORAGE
)
/obj/item/reagent_containers/glass/bucket/attackby(obj/O, mob/user, params)
@@ -260,7 +260,7 @@
/obj/item/reagent_containers/glass/bucket/equipped(mob/user, slot)
..()
- if (slot == SLOT_HEAD)
+ if (slot == ITEM_SLOT_HEAD)
if(reagents.total_volume)
to_chat(user, "[src]'s contents spill all over you!")
reagents.reaction(user, TOUCH)
@@ -273,10 +273,10 @@
/obj/item/reagent_containers/glass/bucket/equip_to_best_slot(var/mob/M)
if(reagents.total_volume) //If there is water in a bucket, don't quick equip it to the head
- var/index = slot_equipment_priority.Find(SLOT_HEAD)
- slot_equipment_priority.Remove(SLOT_HEAD)
+ var/index = slot_equipment_priority.Find(ITEM_SLOT_HEAD)
+ slot_equipment_priority.Remove(ITEM_SLOT_HEAD)
. = ..()
- slot_equipment_priority.Insert(index, SLOT_HEAD)
+ slot_equipment_priority.Insert(index, ITEM_SLOT_HEAD)
return
return ..()
@@ -316,11 +316,10 @@
. += "The cap has been taken off. Alt-click to put a cap on."
/obj/item/reagent_containers/glass/waterbottle/AltClick(mob/user)
- . = ..()
if(cap_lost)
to_chat(user, "The cap seems to be missing! Where did it go?")
return
-
+
var/fumbled = HAS_TRAIT(user, TRAIT_CLUMSY) && prob(5)
if(cap_on || fumbled)
cap_on = FALSE
@@ -359,7 +358,7 @@
if(cap_on && (target.is_refillable() || target.is_drainable() || (reagents.total_volume && user.a_intent == INTENT_HARM)))
to_chat(user, "You must remove the cap before you can do that!")
return
-
+
else if(istype(target, /obj/item/reagent_containers/glass/waterbottle))
var/obj/item/reagent_containers/glass/waterbottle/WB = target
if(WB.cap_on)
diff --git a/code/modules/reagents/reagent_containers/hypospray.dm b/code/modules/reagents/reagent_containers/hypospray.dm
index 2f3d7b208dd54..edc85ea69c49c 100644
--- a/code/modules/reagents/reagent_containers/hypospray.dm
+++ b/code/modules/reagents/reagent_containers/hypospray.dm
@@ -76,7 +76,7 @@
volume = 90
possible_transfer_amounts = list(10,15,30,45)
ignore_flags = 1 // So they can heal their comrades.
- list_reagents = list(/datum/reagent/medicine/epinephrine = 30, /datum/reagent/medicine/omnizine = 30, /datum/reagent/medicine/leporazine = 15, /datum/reagent/medicine/atropine = 15)
+ list_reagents = list(/datum/reagent/medicine/epinephrine = 30, /datum/reagent/medicine/omnizine = 30, /datum/reagent/medicine/leporazine = 15, /datum/reagent/medicine/stabilizing_nanites = 15)
/obj/item/reagent_containers/hypospray/combat/nanites
name = "experimental combat stimulant injector"
diff --git a/code/modules/reagents/reagent_containers/patch.dm b/code/modules/reagents/reagent_containers/patch.dm
index fd05482daa67e..3faff68117973 100644
--- a/code/modules/reagents/reagent_containers/patch.dm
+++ b/code/modules/reagents/reagent_containers/patch.dm
@@ -15,10 +15,10 @@
if(ishuman(L))
var/obj/item/bodypart/affecting = L.get_bodypart(check_zone(user.zone_selected))
if(!affecting)
- to_chat(user, "The limb is missing!")
+ balloon_alert(user, "The limb is missing")
return
if(affecting.status != BODYPART_ORGANIC)
- to_chat(user, "Medicine won't work on a robotic limb!")
+ balloon_alert(user, "It doesn't work on robotic limb")
return
..()
@@ -30,17 +30,17 @@
/obj/item/reagent_containers/pill/patch/styptic
name = "brute patch"
desc = "Helps with brute injuries."
- list_reagents = list(/datum/reagent/medicine/styptic_powder = 20)
+ list_reagents = list(/datum/reagent/medicine/styptic_powder = 30)
icon_state = "bandaid_brute"
/obj/item/reagent_containers/pill/patch/silver_sulf
name = "burn patch"
desc = "Helps with burn injuries."
- list_reagents = list(/datum/reagent/medicine/silver_sulfadiazine = 20)
+ list_reagents = list(/datum/reagent/medicine/silver_sulfadiazine = 30)
icon_state = "bandaid_burn"
/obj/item/reagent_containers/pill/patch/synthflesh
name = "synthflesh patch"
desc = "Helps with brute and burn injuries."
- list_reagents = list(/datum/reagent/medicine/synthflesh = 20)
+ list_reagents = list(/datum/reagent/medicine/synthflesh = 30)
icon_state = "bandaid_both"
diff --git a/code/modules/reagents/reagent_containers/pill.dm b/code/modules/reagents/reagent_containers/pill.dm
index 473f6fd3fed7e..a6dff44cbac41 100644
--- a/code/modules/reagents/reagent_containers/pill.dm
+++ b/code/modules/reagents/reagent_containers/pill.dm
@@ -51,8 +51,7 @@
var/makes_me_think = pick(strings(REDPILL_FILE, "redpill_questions"))
if(icon_state == "pill4" && prob(5)) //you take the red pill - you stay in Wonderland, and I show you how deep the rabbit hole goes
- sleep(50)
- to_chat(M, "[makes_me_think]")
+ addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, M, "[makes_me_think]"), 5 SECONDS)
if(reagents.total_volume)
reagents.reaction(M, apply_type)
@@ -68,11 +67,11 @@
if(!dissolvable || !target.is_refillable())
return
if(target.is_drainable() && !target.reagents.total_volume)
- to_chat(user, "[target] is empty! There's nothing to dissolve [src] in.")
+ balloon_alert(user, "It's empty")
return
if(target.reagents.holder_full())
- to_chat(user, "[target] is full.")
+ balloon_alert(user, "It's full")
return
user.visible_message("[user] slips something into [target]!", "You dissolve [src] in [target].", null, 2)
@@ -115,7 +114,7 @@
name = "salbutamol pill"
desc = "Used to treat oxygen deprivation."
icon_state = "pill16"
- list_reagents = list(/datum/reagent/medicine/salbutamol = 30)
+ list_reagents = list(/datum/reagent/medicine/salbutamol = 20)
rename_with_volume = TRUE
/obj/item/reagent_containers/pill/charcoal
@@ -139,6 +138,9 @@
list_reagents = list(/datum/reagent/medicine/mannitol = 50)
rename_with_volume = TRUE
+/obj/item/reagent_containers/pill/mannitol/braintumor //For the brain tumor quirk
+ list_reagents = list(/datum/reagent/medicine/mannitol = 20)
+
/obj/item/reagent_containers/pill/mutadone
name = "mutadone pill"
desc = "Used to treat genetic damage."
@@ -146,18 +148,32 @@
list_reagents = list(/datum/reagent/medicine/mutadone = 50)
rename_with_volume = TRUE
+/obj/item/reagent_containers/pill/bicaridine
+ name = "bicaridine pill"
+ desc = "Used to stimulate the healing of small brute injuries."
+ icon_state = "pill9"
+ list_reagents = list(/datum/reagent/medicine/bicaridine = 15)
+ rename_with_volume = TRUE
+
+/obj/item/reagent_containers/pill/kelotane
+ name = "kelotane pill"
+ desc = "Used to stimulate the healing of small burns."
+ icon_state = "pill11"
+ list_reagents = list(/datum/reagent/medicine/kelotane = 15)
+ rename_with_volume = TRUE
+
/obj/item/reagent_containers/pill/salicyclic
name = "salicylic acid pill"
desc = "Used to dull pain."
icon_state = "pill9"
- list_reagents = list(/datum/reagent/medicine/sal_acid = 24)
+ list_reagents = list(/datum/reagent/medicine/sal_acid = 15)
rename_with_volume = TRUE
/obj/item/reagent_containers/pill/oxandrolone
name = "oxandrolone pill"
desc = "Used to stimulate burn healing."
icon_state = "pill11"
- list_reagents = list(/datum/reagent/medicine/oxandrolone = 24)
+ list_reagents = list(/datum/reagent/medicine/oxandrolone = 15)
rename_with_volume = TRUE
/obj/item/reagent_containers/pill/insulin
@@ -205,7 +221,7 @@
desc = "Used to treat radition used to counter radiation poisoning."
icon_state = "pill18"
list_reagents = list(/datum/reagent/medicine/potass_iodide = 30)
-
+
///////////////////////////////////////// this pill is used only in a legion mob drop
/obj/item/reagent_containers/pill/shadowtoxin
@@ -213,7 +229,7 @@
desc = "I wouldn't eat this if I were you."
icon_state = "pill9"
color = "#454545"
- list_reagents = list(/datum/reagent/mutationtoxin/shadow = 1)
+ list_reagents = list(/datum/reagent/mutationtoxin/shadow = 5)
//////////////////////////////////////// drugs
/obj/item/reagent_containers/pill/zoom
diff --git a/code/modules/reagents/reagent_containers/spray.dm b/code/modules/reagents/reagent_containers/spray.dm
index da70c314a7679..b596890f800dd 100644
--- a/code/modules/reagents/reagent_containers/spray.dm
+++ b/code/modules/reagents/reagent_containers/spray.dm
@@ -228,6 +228,14 @@
/obj/item/reagent_containers/spray/waterflower/attack_self(mob/user) //Don't allow changing how much the flower sprays
return
+/obj/item/reagent_containers/spray/waterflower/superlube
+ name = "clown flower"
+ desc = "A delightly devilish flower... you got a feeling where this is going."
+ icon = 'icons/obj/chemical.dmi'
+ icon_state = "clownflower"
+ volume = 30
+ list_reagents = list(/datum/reagent/lube/superlube = 30)
+
/obj/item/reagent_containers/spray/waterflower/cyborg
reagent_flags = NONE
volume = 100
@@ -357,3 +365,54 @@
righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi'
volume = 100
list_reagents = list(/datum/reagent/toxin/plantbgone = 100)
+
+/obj/item/reagent_containers/spray/cyborg
+ var/charge_cost = 50
+ var/charge_tick = 0
+ var/recharge_time = 2 //Time it takes for 5u to recharge (in seconds)
+ var/datum/reagent/set_reagent
+
+/obj/item/reagent_containers/spray/cyborg/Initialize()
+ . = ..()
+ reagents.add_reagent(set_reagent, volume)
+ START_PROCESSING(SSobj, src)
+
+/obj/item/reagent_containers/spray/cyborg/process()
+ charge_tick++
+ if(charge_tick >= recharge_time)
+ regenerate_reagents()
+ charge_tick = 0
+
+/obj/item/reagent_containers/spray/cyborg/proc/regenerate_reagents()
+ var/mob/living/silicon/robot/R = loc
+ if(istype(R))
+ if(R.cell)
+ if(reagents.total_volume <= volume)
+ R.cell.use(charge_cost)
+ reagents.add_reagent(set_reagent, 5)
+
+/obj/item/reagent_containers/spray/cyborg/drying_agent
+ name = "drying agent spray"
+ desc = "A spray for cleaning up wet floors."
+ color = "#A000A0"
+ set_reagent = /datum/reagent/drying_agent
+
+/obj/item/reagent_containers/spray/cyborg/plantbgone
+ name = "Plant-B-Gone"
+ desc = "A bottle of weed killer spray for stopping kudzu-based harm."
+ icon = 'icons/obj/hydroponics/equipment.dmi'
+ icon_state = "plantbgone"
+ item_state = "plantbgone"
+ set_reagent = /datum/reagent/toxin/plantbgone
+
+/obj/item/reagent_containers/spray/cyborg/lube
+ name = "lube spray"
+ desc = "A spray filled with space lube, for sabotaging the crew."
+ color = "#009CA8"
+ set_reagent = /datum/reagent/lube
+
+/obj/item/reagent_containers/spray/cyborg/acid
+ name = "acid spray"
+ desc = "A spray filled with sulphuric acid for offensive use."
+ color = "#00FF32"
+ set_reagent = /datum/reagent/toxin/acid
diff --git a/code/modules/reagents/reagent_containers/syringes.dm b/code/modules/reagents/reagent_containers/syringes.dm
index 9d5bb2b96a394..45bf90542cf11 100644
--- a/code/modules/reagents/reagent_containers/syringes.dm
+++ b/code/modules/reagents/reagent_containers/syringes.dm
@@ -78,16 +78,13 @@
else if(!L.can_inject(user, TRUE))
return
- // chance of monkey retaliation
- if(ismonkey(target) && prob(MONKEY_SYRINGE_RETALIATION_PROB))
- var/mob/living/carbon/monkey/M
- M = target
- M.retaliate(user)
+ SEND_SIGNAL(target, COMSIG_LIVING_TRY_SYRINGE, user)
+
switch(mode)
if(SYRINGE_DRAW)
if(reagents.total_volume >= reagents.maximum_volume)
- to_chat(user, "The syringe is full.")
+ balloon_alert(user, "It's full")
return
if(L) //living mob
@@ -106,19 +103,21 @@
user.visible_message("[user] takes a blood sample from [L].")
else
to_chat(user, "You are unable to draw any blood from [L]!")
+ balloon_alert(user, "Unable to take blood sample")
else //if not mob
if(!target.reagents.total_volume)
- to_chat(user, "[target] is empty!")
+ balloon_alert(user, "It's empty")
return
if(!target.is_drawable(user))
- to_chat(user, "You cannot directly remove reagents from [target]!")
+ balloon_alert(user, "You can't remove its reagents")
return
var/trans = target.reagents.trans_to(src, amount_per_transfer_from_this, transfered_by = user) // transfer from, transfer to - who cares?
to_chat(user, "You fill [src] with [trans] units of the solution. It now contains [reagents.total_volume] units.")
+ balloon_alert(user, "You fill [src] with [trans]u")
if (reagents.total_volume >= reagents.maximum_volume)
mode=!mode
update_icon()
@@ -129,15 +128,15 @@
log_combat(user, target, "attempted to inject", src, addition="which had [contained]")
if(!reagents.total_volume)
- to_chat(user, "[src] is empty.")
+ balloon_alert(user, "It's empty")
return
if(!L && !target.is_injectable(user)) //only checks on non-living mobs, due to how can_inject() handles
- to_chat(user, "You cannot directly fill [target]!")
+ balloon_alert(user, "You cannot fill [target]")
return
if(target.reagents.total_volume >= target.reagents.maximum_volume)
- to_chat(user, "[target] is full.")
+ balloon_alert(user, "It's full")
return
if(L) //living mob
@@ -177,6 +176,7 @@
var/fraction = min(amount_per_transfer_from_this/reagents.total_volume, 1)
reagents.reaction(L, INJECT, fraction)
reagents.trans_to(target, amount_per_transfer_from_this, transfered_by = user)
+ balloon_alert(user, "[amount_per_transfer_from_this]u injected")
to_chat(user, "You inject [amount_per_transfer_from_this] units of the solution. The syringe now contains [reagents.total_volume] units.")
if (reagents.total_volume <= 0 && mode==SYRINGE_INJECT)
mode = SYRINGE_DRAW
@@ -209,16 +209,16 @@
/obj/item/reagent_containers/syringe/proc/embed(mob/living/carbon/C, injectmult = 1)
C.apply_status_effect(STATUS_EFFECT_SYRINGE, src, injectmult)
forceMove(C)
-
+
/obj/item/reagent_containers/syringe/used
name = "used syringe"
desc = "A syringe that can hold up to 15 units. This one is old, and it's probably a bad idea to use it"
-
+
/obj/item/reagent_containers/syringe/used/Initialize()
. = ..()
- if(prob(50))
- var/datum/disease/advance/R = new /datum/disease/advance/random(rand(2, 5), rand(6, 9))
+ if(prob(75))
+ var/datum/disease/advance/R = new /datum/disease/advance/random(rand(3, 6), 9, rand(3,4), infected = src)
syringediseases += R
/obj/item/reagent_containers/syringe/epinephrine
@@ -240,7 +240,7 @@
name = "syringe (diphenhydramine)"
desc = "Contains diphenhydramine, an antihistamine agent."
list_reagents = list(/datum/reagent/medicine/diphenhydramine = 15)
-
+
/obj/item/reagent_containers/syringe/calomel
name = "syringe (calomel)"
desc = "Contains calomel."
@@ -300,11 +300,33 @@
units_per_tick = 2
initial_inject = 8
-/obj/item/reagent_containers/syringe/noreact
+/obj/item/reagent_containers/syringe/cryo
name = "cryo syringe"
- desc = "An advanced syringe that stops reagents inside from reacting. It can hold up to 20 units."
+ desc = "An advanced syringe that freezes reagents close to absolute 0. It can hold up to 20 units."
volume = 20
- reagent_flags = TRANSPARENT | NO_REACT
+ var/processing = FALSE
+
+/obj/item/reagent_containers/syringe/cryo/Destroy()
+ if(processing)
+ STOP_PROCESSING(SSfastprocess, src)
+ . = ..()
+
+/obj/item/reagent_containers/syringe/cryo/process(delta_time)
+ reagents.chem_temp = 20
+
+//Reactions are handled after this call.
+/obj/item/reagent_containers/syringe/cryo/on_reagent_change()
+ . = ..()
+ if(reagents)
+ if(reagents.total_volume)
+ reagents.chem_temp = 20
+ if(!processing)
+ START_PROCESSING(SSfastprocess, src)
+ processing = TRUE
+ return
+ if(processing)
+ STOP_PROCESSING(SSfastprocess, src)
+ processing = FALSE
/obj/item/reagent_containers/syringe/piercing
name = "piercing syringe"
diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm
index d0c571c6bcd57..0cd322745be89 100644
--- a/code/modules/reagents/reagent_dispenser.dm
+++ b/code/modules/reagents/reagent_dispenser.dm
@@ -65,7 +65,10 @@
reagent_id = /datum/reagent/fuel
/obj/structure/reagent_dispensers/fueltank/boom()
- explosion(get_turf(src), 0, 1, 5, flame_range = 5)
+ var/light_explosion_range = CLAMP(round(reagents.get_reagent_amount(/datum/reagent/fuel)/200, 1), 1, 5) //explosion range should decrease when there is less fuel in the tank
+ var/flame_explosion_range = CLAMP(light_explosion_range + 1, 1, 5) //Fire explosion is always one bigger than light explosion
+ var/heavy_explosion_range = round(light_explosion_range/5, 1) //if there is less than 500 fuel in the tank, no heavy explosion
+ explosion(get_turf(src), 0, heavy_explosion_range, light_explosion_range, flame_range = flame_explosion_range)
qdel(src)
/obj/structure/reagent_dispensers/fueltank/blob_act(obj/structure/blob/B)
@@ -105,10 +108,10 @@
else
user.visible_message("[user] catastrophically fails at refilling [user.p_their()] [I.name]!", "That was stupid of you.")
log_bomber(user, "detonated a", src, "via welding tool")
-
+
if (user.client)
SSmedals.UnlockMedal(MEDAL_DETONATE_WELDERBOMB,user.client)
-
+
boom()
return
return ..()
@@ -120,6 +123,7 @@
icon_state = "pepper"
anchored = TRUE
density = FALSE
+ layer = ABOVE_WINDOW_LAYER
reagent_id = /datum/reagent/consumable/condensedcapsaicin
/obj/structure/reagent_dispensers/peppertank/Initialize()
@@ -176,6 +180,7 @@
icon_state = "virus_food"
anchored = TRUE
density = FALSE
+ layer = ABOVE_WINDOW_LAYER
reagent_id = /datum/reagent/consumable/virus_food
diff --git a/code/modules/recycling/conveyor2.dm b/code/modules/recycling/conveyor2.dm
index 3309b47384ddf..9427a709fae95 100644
--- a/code/modules/recycling/conveyor2.dm
+++ b/code/modules/recycling/conveyor2.dm
@@ -9,6 +9,8 @@ GLOBAL_LIST_EMPTY(conveyors_by_id)
name = "conveyor belt"
desc = "A conveyor belt."
layer = BELOW_OPEN_DOOR_LAYER
+ processing_flags = START_PROCESSING_MANUALLY
+ subsystem_type = /datum/controller/subsystem/processing/fastprocess
var/operating = 0 // 1 if running forward, -1 if backwards, 0 if off
var/operable = 1 // true if can operate (no broken segments in this belt run)
var/forwards // this is the default (forward) direction, set by the map dir
@@ -17,14 +19,13 @@ GLOBAL_LIST_EMPTY(conveyors_by_id)
var/id = "" // the control ID - must match controller ID
var/verted = 1 // Inverts the direction the conveyor belt moves.
- speed_process = TRUE
var/conveying = FALSE
/obj/machinery/conveyor/centcom_auto
id = "round_end_belt"
-/obj/machinery/conveyor/inverted //Directions inverted so you can use different corner peices.
+/obj/machinery/conveyor/inverted //Directions inverted so you can use different corner pieces.
icon_state = "conveyor_map_inverted"
verted = -1
@@ -127,15 +128,19 @@ GLOBAL_LIST_EMPTY(conveyors_by_id)
operating = FALSE
icon_state = "conveyor[operating * verted]"
- // machine process
- // move items to the target location
+// machine process
+// move items to the target location
/obj/machinery/conveyor/process()
if(stat & (BROKEN | NOPOWER))
return
+
if(!operating)
return
+
var/turf/T = get_turf(src)
+
use_power(6)
+
//get the first 30 items in contents
var/i = 0
var/list/affected = list()
@@ -239,11 +244,10 @@ GLOBAL_LIST_EMPTY(conveyors_by_id)
desc = "A conveyor control switch."
icon = 'icons/obj/recycling.dmi'
icon_state = "switch-off"
- speed_process = TRUE
+ processing_flags = START_PROCESSING_MANUALLY
var/position = 0 // 0 off, -1 reverse, 1 forward
var/last_pos = -1 // last direction setting
- var/operated = 1 // true if just operated
var/oneway = FALSE // if the switch only operates the conveyor belts in a single direction.
var/invert_icon = FALSE // If the level points the opposite direction when it's turned on.
@@ -285,23 +289,28 @@ GLOBAL_LIST_EMPTY(conveyors_by_id)
else
icon_state = "switch-off"
-
-// timed process
-// if the switch changed, update the linked conveyors
-
-/obj/machinery/conveyor_switch/process()
- if(!operated)
- return
- operated = 0
-
+/// Updates all conveyor belts that are linked to this switch, and tells them to start processing.
+/obj/machinery/conveyor_switch/proc/update_linked_conveyors()
for(var/obj/machinery/conveyor/C in GLOB.conveyors_by_id[id])
C.operating = position
C.update_move_direction()
+ C.update_icon()
+ if(C.operating)
+ C.begin_processing()
+ else
+ C.end_processing()
CHECK_TICK
-// attack with hand, switch position
-/obj/machinery/conveyor_switch/interact(mob/user)
- add_fingerprint(user)
+/// Finds any switches with same `id` as this one, and set their position and icon to match us.
+/obj/machinery/conveyor_switch/proc/update_linked_switches()
+ for(var/obj/machinery/conveyor_switch/S in GLOB.conveyors_by_id[id])
+ S.invert_icon = invert_icon
+ S.position = position
+ S.update_icon()
+ CHECK_TICK
+
+/// Updates the switch's `position` and `last_pos` variable. Useful so that the switch can properly cycle between the forwards, backwards and neutral positions.
+/obj/machinery/conveyor_switch/proc/update_position()
if(position == 0)
if(oneway) //is it a oneway switch
position = oneway
@@ -316,15 +325,14 @@ GLOBAL_LIST_EMPTY(conveyors_by_id)
last_pos = position
position = 0
- operated = 1
+/// Called when a user clicks on this switch with an open hand.
+/obj/machinery/conveyor_switch/interact(mob/user)
+ add_fingerprint(user)
+ update_position()
update_icon()
+ update_linked_conveyors()
+ update_linked_switches()
- // find any switches with same id as this one, and set their positions to match us
- for(var/obj/machinery/conveyor_switch/S in GLOB.conveyors_by_id[id])
- S.invert_icon = invert_icon
- S.position = position
- S.update_icon()
- CHECK_TICK
/obj/machinery/conveyor_switch/attackby(obj/item/I, mob/user, params)
if(I.tool_behaviour == TOOL_CROWBAR)
@@ -385,6 +393,7 @@ GLOBAL_LIST_EMPTY(conveyors_by_id)
max_amount = 30
singular_name = "conveyor belt"
w_class = WEIGHT_CLASS_BULKY
+ merge_type = /obj/item/stack/conveyor
///id for linking
var/id = ""
diff --git a/code/modules/recycling/disposal/bin.dm b/code/modules/recycling/disposal/bin.dm
index 02cdee2bcf040..86267f5c71e2a 100644
--- a/code/modules/recycling/disposal/bin.dm
+++ b/code/modules/recycling/disposal/bin.dm
@@ -5,7 +5,7 @@
/obj/machinery/disposal
icon = 'icons/obj/atmospherics/pipes/disposal.dmi'
density = TRUE
- armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30)
+ armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30, "stamina" = 0)
max_integrity = 200
resistance_flags = FIRE_PROOF
interaction_flags_machine = INTERACT_MACHINE_OPEN | INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON
@@ -23,7 +23,6 @@
var/flush_every_ticks = 30 //Every 30 ticks it will look whether it is ready to flush
var/flush_count = 0 //this var adds 1 once per tick. When it reaches flush_every_ticks it resets and tries to flush.
var/last_sound = 0
- var/obj/structure/disposalconstruct/stored
// create a new disposal
// find the attached trunk (if present) and init gas resvr.
@@ -32,11 +31,7 @@
if(make_from)
setDir(make_from.dir)
- make_from.moveToNullspace()
- stored = make_from
pressure_charging = FALSE // newly built disposal bins start with pump off
- else
- stored = new /obj/structure/disposalconstruct(null, null , SOUTH , FALSE , src)
trunk_check()
@@ -131,6 +126,7 @@
add_fingerprint(user)
if(user == target)
user.visible_message("[user] starts climbing into [src].", "You start climbing into [src]...")
+ . = TRUE
else
target.visible_message("[user] starts putting [target] into [src].", "[user] starts putting you into [src]!")
if(do_mob(user, target, 20))
@@ -139,10 +135,12 @@
target.forceMove(src)
if(user == target)
user.visible_message("[user] climbs into [src].", "You climb into [src].")
+ . = TRUE
else
target.visible_message("[user] has placed [target] in [src].", "[user] has placed you in [src].")
log_combat(user, target, "stuffed", addition="into [src]")
- target.LAssailant = user
+ target.LAssailant = WEAKREF(user)
+ . = TRUE
update_icon()
/obj/machinery/disposal/relaymove(mob/user)
@@ -234,16 +232,10 @@
qdel(H)
/obj/machinery/disposal/deconstruct(disassembled = TRUE)
- var/turf/T = loc
if(!(flags_1 & NODECONSTRUCT_1))
- if(stored)
- stored.forceMove(T)
- src.transfer_fingerprints_to(stored)
- stored.anchored = FALSE
- stored.density = TRUE
- stored.update_icon()
+ new /obj/structure/disposalconstruct(loc, null, SOUTH, FALSE, src)
for(var/atom/movable/AM in src) //out, out, darned crowbar!
- AM.forceMove(T)
+ AM.forceMove(get_turf(src))
..()
/obj/machinery/disposal/get_dumping_location(obj/item/storage/source,mob/user)
@@ -299,6 +291,7 @@
if(!ui)
ui = new(user, src, "DisposalUnit")
ui.open()
+ ui.set_autoupdate(TRUE) // Pressure
/obj/machinery/disposal/bin/ui_data(mob/user)
var/list/data = list()
@@ -388,7 +381,7 @@
//timed process
//charge the gas reservoir and perform flush if ready
-/obj/machinery/disposal/bin/process()
+/obj/machinery/disposal/bin/process(delta_time)
if(stat & BROKEN) //nothing can happen if broken
return
@@ -421,7 +414,7 @@
var/pressure_delta = (SEND_PRESSURE*1.01) - air_contents.return_pressure()
if(env.return_temperature() > 0)
- var/transfer_moles = 0.1 * pressure_delta*air_contents.return_volume()/(env.return_temperature() * R_IDEAL_GAS_EQUATION)
+ var/transfer_moles = 0.05 * delta_time * pressure_delta * air_contents.return_volume() / (env.return_temperature() * R_IDEAL_GAS_EQUATION)
//Actually transfer the gas
var/datum/gas_mixture/removed = env.remove(transfer_moles)
@@ -438,7 +431,7 @@
/obj/machinery/disposal/bin/get_remote_view_fullscreens(mob/user)
if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS)))
- user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 2)
+ user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/impaired, 2)
//Delivery Chute
@@ -505,6 +498,9 @@
/obj/mecha/CanEnterDisposals()
return
+/obj/structure/spacevine/CanEnterDisposals()
+ return
+
/obj/machinery/disposal/bin/newHolderDestination(obj/structure/disposalholder/H)
H.destinationTag = 1
diff --git a/code/modules/recycling/disposal/construction.dm b/code/modules/recycling/disposal/construction.dm
index 348e687e03138..3c5fa21278746 100644
--- a/code/modules/recycling/disposal/construction.dm
+++ b/code/modules/recycling/disposal/construction.dm
@@ -16,10 +16,13 @@
/obj/structure/disposalconstruct/Initialize(loc, _pipe_type, _dir = SOUTH, flip = FALSE, obj/make_from)
. = ..()
+
if(make_from)
pipe_type = make_from.type
setDir(make_from.dir)
anchored = TRUE
+ density = initial(pipe_type.density)
+ make_from.transfer_fingerprints_to(src)
else
if(_pipe_type)
@@ -159,12 +162,13 @@
if(!I.tool_start_check(user, amount=0))
return TRUE
+ add_fingerprint(user)
to_chat(user, "You start welding the [pipename] in place...")
if(I.use_tool(src, user, 8, volume=50))
to_chat(user, "The [pipename] has been welded in place.")
var/obj/O = new pipe_type(loc, src)
transfer_fingerprints_to(O)
-
+ qdel(src)
else
to_chat(user, "You need to attach it to the plating first!")
return TRUE
diff --git a/code/modules/recycling/disposal/holder.dm b/code/modules/recycling/disposal/holder.dm
index e68022c5e3675..8a2fbff4b8a44 100644
--- a/code/modules/recycling/disposal/holder.dm
+++ b/code/modules/recycling/disposal/holder.dm
@@ -22,6 +22,8 @@
// initialize a holder from the contents of a disposal unit
/obj/structure/disposalholder/proc/init(obj/machinery/disposal/D)
+ if(!istype(D))
+ return //Why check for things that don't exist?
gas = D.air_contents// transfer gas resv. into holder object
//Check for any living mobs trigger hasmob.
@@ -103,6 +105,7 @@
// merge two holder objects
// used when a holder meets a stuck holder
/obj/structure/disposalholder/proc/merge(obj/structure/disposalholder/other)
+ destinationTag = other.destinationTag //copies typetag from other holder
for(var/A in other)
var/atom/movable/AM = A
AM.forceMove(src) // move everything in other holder to this one
@@ -116,8 +119,8 @@
/obj/structure/disposalholder/relaymove(mob/user)
if(user.incapacitated())
return
- for(var/mob/M in range(5, get_turf(src)))
- M.show_message("CLONG, clong!", 2)
+ for(var/mob/M as() in hearers(5, get_turf(src)))
+ M.show_message("CLONG, clong!", MSG_AUDIBLE)
var/obj/structure/disposalpipe/pipe = loc
pipe.take_damage(10)
playsound(src.loc, 'sound/effects/clang.ogg', 50, 0, 0)
diff --git a/code/modules/recycling/disposal/outlet.dm b/code/modules/recycling/disposal/outlet.dm
index 7655988c8ffd2..d8e4886518a86 100644
--- a/code/modules/recycling/disposal/outlet.dm
+++ b/code/modules/recycling/disposal/outlet.dm
@@ -10,18 +10,14 @@
var/active = FALSE
var/turf/target // this will be where the output objects are 'thrown' to.
var/obj/structure/disposalpipe/trunk/trunk // the attached pipe trunk
- var/obj/structure/disposalconstruct/stored
var/start_eject = 0
var/eject_range = 2
/obj/structure/disposaloutlet/Initialize(mapload, obj/structure/disposalconstruct/make_from)
. = ..()
+
if(make_from)
setDir(make_from.dir)
- make_from.forceMove(src)
- stored = make_from
- else
- stored = new /obj/structure/disposalconstruct(src, null , SOUTH , FALSE , src)
target = get_ranged_target_turf(src, dir, 10)
@@ -33,7 +29,6 @@
if(trunk)
trunk.linked = null
trunk = null
- QDEL_NULL(stored)
return ..()
// expel the contents of the holder object, then delete it
@@ -70,12 +65,15 @@
if(!I.tool_start_check(user, amount=0))
return TRUE
+ add_fingerprint(user)
playsound(src, 'sound/items/welder2.ogg', 100, 1)
to_chat(user, "You start slicing the floorweld off [src]...")
if(I.use_tool(src, user, 20))
to_chat(user, "You slice the floorweld off [src].")
- stored.forceMove(loc)
- transfer_fingerprints_to(stored)
- stored = null
- qdel(src)
+ deconstruct()
return TRUE
+
+/obj/structure/disposaloutlet/deconstruct(disassembled = TRUE)
+ if(!(flags_1 & NODECONSTRUCT_1))
+ new /obj/structure/disposalconstruct(loc, null, SOUTH, FALSE, src)
+ ..()
diff --git a/code/modules/recycling/disposal/pipe.dm b/code/modules/recycling/disposal/pipe.dm
index a60ecd472d010..9f7e94a31ce19 100644
--- a/code/modules/recycling/disposal/pipe.dm
+++ b/code/modules/recycling/disposal/pipe.dm
@@ -10,24 +10,19 @@
level = 1 // underfloor only
dir = NONE // dir will contain dominant direction for junction pipes
max_integrity = 200
- armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30)
+ armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30, "stamina" = 0)
layer = DISPOSAL_PIPE_LAYER // slightly lower than wires and other pipes
rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
var/dpdir = NONE // bitmask of pipe directions
var/initialize_dirs = NONE // bitflags of pipe directions added on init, see \code\_DEFINES\pipe_construction.dm
var/flip_type // If set, the pipe is flippable and becomes this type when flipped
- var/obj/structure/disposalconstruct/stored
/obj/structure/disposalpipe/Initialize(mapload, obj/structure/disposalconstruct/make_from)
. = ..()
- if(!QDELETED(make_from))
+ if(make_from)
setDir(make_from.dir)
- make_from.forceMove(src)
- stored = make_from
- else
- stored = new /obj/structure/disposalconstruct(src, null , SOUTH , FALSE , src)
if(dir in GLOB.diagonals) // Bent pipes already have all the dirs set
initialize_dirs = NONE
@@ -50,7 +45,6 @@
if(H)
H.active = FALSE
expel(H, get_turf(src), 0)
- QDEL_NULL(stored)
return ..()
// returns the direction of the next pipe object, given the entrance dir
@@ -97,11 +91,11 @@
var/eject_range = 5
var/turf/open/floor/floorturf
- if(isfloorturf(T)) //intact floor, pop the tile
+ if(isfloorturf(T) && T.intact) //intact floor, pop the tile
floorturf = T
if(floorturf.floor_tile)
new floorturf.floor_tile(T)
- floorturf.make_plating()
+ floorturf.make_plating(TRUE)
if(direction) // direction is specified
if(isspaceturf(T)) // if ended in space, then range is unlimited
@@ -146,6 +140,7 @@
if(!I.tool_start_check(user, amount=0))
return TRUE
+ add_fingerprint(user)
to_chat(user, "You start slicing [src]...")
if(I.use_tool(src, user, 30, volume=50))
deconstruct()
@@ -160,19 +155,14 @@
/obj/structure/disposalpipe/deconstruct(disassembled = TRUE)
if(!(flags_1 & NODECONSTRUCT_1))
if(disassembled)
- if(stored)
- stored.forceMove(loc)
- transfer_fingerprints_to(stored)
- stored.setDir(dir)
- stored = null
+ new /obj/structure/disposalconstruct(loc, null , SOUTH , FALSE , src)
else
var/turf/T = get_turf(src)
for(var/D in GLOB.cardinals)
if(D & dpdir)
var/obj/structure/disposalpipe/broken/P = new(T)
P.setDir(D)
- qdel(src)
-
+ ..()
/obj/structure/disposalpipe/singularity_pull(S, current_size)
..()
diff --git a/code/modules/recycling/sortingmachinery.dm b/code/modules/recycling/sortingmachinery.dm
index 179e05e3bf2c9..84b50c9474be6 100644
--- a/code/modules/recycling/sortingmachinery.dm
+++ b/code/modules/recycling/sortingmachinery.dm
@@ -19,8 +19,14 @@
return ..()
/obj/structure/bigDelivery/contents_explosion(severity, target)
- for(var/atom/movable/AM in contents)
- AM.ex_act()
+ for(var/thing in contents)
+ switch(severity)
+ if(EXPLODE_DEVASTATE)
+ SSexplosions.high_mov_atom += thing
+ if(EXPLODE_HEAVY)
+ SSexplosions.med_mov_atom += thing
+ if(EXPLODE_LIGHT)
+ SSexplosions.low_mov_atom += thing
/obj/structure/bigDelivery/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/destTagger))
@@ -84,8 +90,14 @@
var/sortTag = 0
/obj/item/smallDelivery/contents_explosion(severity, target)
- for(var/atom/movable/AM in contents)
- AM.ex_act()
+ for(var/thing in contents)
+ switch(severity)
+ if(EXPLODE_DEVASTATE)
+ SSexplosions.high_mov_atom += thing
+ if(EXPLODE_HEAVY)
+ SSexplosions.med_mov_atom += thing
+ if(EXPLODE_LIGHT)
+ SSexplosions.low_mov_atom += thing
/obj/item/smallDelivery/attack_self(mob/user)
user.temporarilyRemoveItemFromInventory(src, TRUE)
diff --git a/code/modules/religion/religion_sects.dm b/code/modules/religion/religion_sects.dm
new file mode 100644
index 0000000000000..eefca2ce7e9bc
--- /dev/null
+++ b/code/modules/religion/religion_sects.dm
@@ -0,0 +1,218 @@
+/**
+ * # Religious Sects
+ *
+ * Religious Sects are a way to convert the fun of having an active 'god' (admin) to code-mechanics so you aren't having to press adminwho.
+ *
+ * Sects are not meant to overwrite the fun of choosing a custom god/religion, but meant to enhance it.
+ * The idea is that Space Jesus (or whoever you worship) can be an evil bloodgod who takes the lifeforce out of people, a nature lover, or all things righteous and good. You decide!
+ *
+ */
+/datum/religion_sect
+/// Name of the religious sect
+ var/name = "Religious Sect Base Type"
+/// Description of the religious sect, Presents itself in the selection menu (AKA be brief)
+ var/desc = "Oh My! What Do We Have Here?!!?!?!?"
+/// Opening message when someone gets converted
+ var/convert_opener
+/// holder for alignments.
+ var/alignment = ALIGNMENT_GOOD
+/// Does this require something before being available as an option?
+ var/starter = TRUE
+/// The Sect's 'Mana'
+ var/favor = 0 //MANA!
+/// The max amount of favor the sect can have
+ var/max_favor = 1000
+/// The default value for an item that can be sacrificed
+ var/default_item_favor = 5
+/// Turns into 'desired_items_typecache', lists the types that can be sacrificed barring optional features in can_sacrifice()
+ var/list/desired_items
+/// Autopopulated by `desired_items`
+ var/list/desired_items_typecache
+/// Lists of rites by type. Converts itself into a list of rites with "name - desc (favor_cost)" = type
+ var/list/rites_list
+/// Changes the Altar of Gods icon
+ var/altar_icon
+/// Changes the Altar of Gods icon_state
+ var/altar_icon_state
+/// Currently Active (non-deleted) rites
+ var/list/active_rites
+
+/datum/religion_sect/New()
+ . = ..()
+ if(desired_items)
+ desired_items_typecache = typecacheof(desired_items)
+ if(rites_list)
+ var/listylist = generate_rites_list()
+ rites_list = listylist
+ on_select()
+
+///Generates a list of rites with 'name' = 'type'
+/datum/religion_sect/proc/generate_rites_list()
+ . = list()
+ for(var/i in rites_list)
+ if(!ispath(i))
+ continue
+ var/datum/religion_rites/RI = i
+ var/name_entry = "[initial(RI.name)]"
+ if(initial(RI.desc))
+ name_entry += " - [initial(RI.desc)]"
+ if(initial(RI.favor_cost))
+ name_entry += " ([initial(RI.favor_cost)] favor)"
+
+ . += list("[name_entry]" = i)
+
+/// Activates once selected
+/datum/religion_sect/proc/on_select()
+
+/// Activates once selected and on newjoins, oriented around people who become holy.
+/datum/religion_sect/proc/on_conversion(mob/living/L)
+ to_chat(L, "[convert_opener] max_favor))
+ . = (max_favor-favor) //if favor = 5 and we want to add 10 with a max of 10, we'll only be able to add 5
+ favor = clamp(favor+amount, 0, max_favor)
+
+/// Sets favor to a specific amount. Can provide optional features based on a user.
+/datum/religion_sect/proc/set_favor(amount = 0, mob/living/L)
+ favor = clamp(favor+amount, 0, max_favor)
+ return favor
+
+/// Activates when an individual uses a rite. Can provide different/additional benefits depending on the user.
+/datum/religion_sect/proc/on_riteuse(mob/living/user, atom/religious_tool)
+
+/// Replaces the bible's bless mechanic. Return TRUE if you want to not do the brain hit.
+/datum/religion_sect/proc/sect_bless(mob/living/L, mob/living/user)
+ if(!ishuman(L))
+ return FALSE
+ var/mob/living/carbon/human/H = L
+ for(var/X in H.bodyparts)
+ var/obj/item/bodypart/BP = X
+ if(BP.status == BODYPART_ROBOTIC)
+ to_chat(user, "[GLOB.deity] refuses to heal this metallic taint!")
+ return TRUE
+
+ var/heal_amt = 10
+ var/list/hurt_limbs = H.get_damaged_bodyparts(1, 1, null, BODYPART_ORGANIC)
+
+ if(hurt_limbs.len)
+ for(var/obj/item/bodypart/affecting as() in hurt_limbs)
+ if(affecting.heal_damage(heal_amt, heal_amt, null, BODYPART_ORGANIC))
+ H.update_damage_overlays()
+ H.visible_message("[user] heals [H] with the power of [GLOB.deity]!")
+ to_chat(H, "May the power of [GLOB.deity] compels you to be healed!")
+ playsound(user, "punch", 25, TRUE, -1)
+ SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "blessing", /datum/mood_event/blessing)
+ return TRUE
+
+/datum/religion_sect/puritanism
+ name = "Puritanism (Default)"
+ desc = "Nothing special."
+ convert_opener = "Your run-of-the-mill sect, there are no benefits or boons associated. Praise normalcy!"
+
+/datum/religion_sect/technophile
+ name = "Technophile"
+ desc = "A sect oriented around technology."
+ convert_opener = "May you find peace in a metal shell, acolyte. Bibles now recharge cyborgs and heal robotic limbs if targeted, but they do not heal organic limbs. You can now sacrifice cells, with favor depending on their charge."
+ alignment = ALIGNMENT_NEUT
+ desired_items = list(/obj/item/stock_parts/cell)
+ rites_list = list(/datum/religion_rites/synthconversion)
+ altar_icon_state = "convertaltar-blue"
+
+/datum/religion_sect/technophile/sect_bless(mob/living/L, mob/living/user)
+ if(iscyborg(L))
+ var/mob/living/silicon/robot/R = L
+ var/charge_amt = 50
+ if(L.mind?.holy_role == HOLY_ROLE_HIGHPRIEST)
+ charge_amt *= 2
+ R.cell?.charge += charge_amt
+ R.visible_message("[user] charges [R] with the power of [GLOB.deity]!")
+ to_chat(R, "You are charged by the power of [GLOB.deity]!")
+ SEND_SIGNAL(R, COMSIG_ADD_MOOD_EVENT, "blessing", /datum/mood_event/blessing)
+ playsound(user, 'sound/effects/bang.ogg', 25, TRUE, -1)
+ return TRUE
+ if(!ishuman(L))
+ return
+ var/mob/living/carbon/human/H = L
+
+ //first we determine if we can charge them
+ var/did_we_charge = FALSE
+ var/obj/item/organ/stomach/battery/battery_stomach = H.getorganslot(ORGAN_SLOT_STOMACH)
+ if(istype(battery_stomach))
+ battery_stomach.adjust_charge(100)
+ did_we_charge = TRUE
+
+ //if we're not targetting a robot part we stop early
+ var/obj/item/bodypart/BP = H.get_bodypart(user.zone_selected)
+ if(BP.status != BODYPART_ROBOTIC)
+ if(!did_we_charge)
+ to_chat(user, "[GLOB.deity] scoffs at the idea of healing such fleshy matter!")
+ else
+ H.visible_message("[user] charges [H] with the power of [GLOB.deity]!")
+ to_chat(H, "You feel charged by the power of [GLOB.deity]!")
+ SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "blessing", /datum/mood_event/blessing)
+ playsound(user, 'sound/machines/synth_yes.ogg', 25, TRUE, -1)
+ return TRUE
+
+ //charge(?) and go
+ if(BP.heal_damage(5,5,null,BODYPART_ROBOTIC))
+ H.update_damage_overlays()
+
+ H.visible_message("[user] [did_we_charge ? "repairs" : "repairs and charges"] [H] with the power of [GLOB.deity]!")
+ to_chat(H, "The inner machinations of [GLOB.deity] [did_we_charge ? "repairs" : "repairs and charges"] you!")
+ playsound(user, 'sound/effects/bang.ogg', 25, TRUE, -1)
+ SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "blessing", /datum/mood_event/blessing)
+ return TRUE
+
+/datum/religion_sect/technophile/on_sacrifice(obj/item/I, mob/living/L)
+ var/obj/item/stock_parts/cell/the_cell = I
+ if(!istype(the_cell)) //how...
+ return
+ if(the_cell.charge < 3000)
+ to_chat(L,"[GLOB.deity] does not accept pity amounts of power.")
+ return
+ adjust_favor(round(the_cell.charge/3000), L)
+ to_chat(L, "You offer [the_cell]'s power to [GLOB.deity], pleasing them.")
+ qdel(I)
+ return TRUE
+
+/**** Ever-Burning Candle sect ****/
+
+/datum/religion_sect/candle_sect
+ name = "Ever-Burning Candle"
+ desc = "A sect dedicated to candles."
+ convert_opener = "May you be the wax to keep the Ever-Burning Candle burning, acolyte. Sacrificing burning corpses with a lot of burn damage and candles grants you favor."
+ alignment = ALIGNMENT_NEUT
+ max_favor = 10000
+ desired_items = list(/obj/item/candle)
+ rites_list = list(/datum/religion_rites/fireproof, /datum/religion_rites/burning_sacrifice, /datum/religion_rites/infinite_candle)
+ altar_icon_state = "convertaltar-red"
+
+//candle sect bibles don't heal or do anything special apart from the standard holy water blessings
+/datum/religion_sect/candle_sect/sect_bless(mob/living/blessed, mob/living/user)
+ return TRUE
+
+/datum/religion_sect/candle_sect/on_sacrifice(obj/item/candle/offering, mob/living/user)
+ if(!istype(offering))
+ return
+ if(!offering.lit)
+ to_chat(user, "The candle needs to be lit to be offered!")
+ return
+ to_chat(user, "Another candle for [GLOB.deity]'s collection.")
+ adjust_favor(20, user) //it's not a lot but hey there's a pacifist favor option at least
+ qdel(offering)
+ return TRUE
diff --git a/code/modules/religion/religion_structures.dm b/code/modules/religion/religion_structures.dm
new file mode 100644
index 0000000000000..ffad8ef18d148
--- /dev/null
+++ b/code/modules/religion/religion_structures.dm
@@ -0,0 +1,45 @@
+/obj/structure/altar_of_gods
+ name = "\improper Altar of the Gods"
+ desc = "An altar which allows the head of the church to choose a sect of religious teachings as well as provide sacrifices to earn favor."
+ icon = 'icons/obj/hand_of_god_structures.dmi'
+ icon_state = "convertaltar"
+ density = TRUE
+ anchored = TRUE
+ layer = TABLE_LAYER
+ climbable = TRUE
+ pass_flags = LETPASSTHROW
+ can_buckle = TRUE
+ buckle_lying = 90 //we turn to you!
+ ///Avoids having to check global everytime by referencing it locally.
+ var/datum/religion_sect/sect_to_altar
+
+/obj/structure/altar_of_gods/Initialize(mapload)
+ . = ..()
+ reflect_sect_in_icons()
+
+/obj/structure/altar_of_gods/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/religious_tool, ALL, FALSE, CALLBACK(src, .proc/reflect_sect_in_icons))
+
+/obj/structure/altar_of_gods/attack_hand(mob/living/user)
+ if(!Adjacent(user) || !user.pulling)
+ return ..()
+ if(!isliving(user.pulling))
+ return ..()
+ var/mob/living/pushed_mob = user.pulling
+ if(pushed_mob.buckled)
+ to_chat(user, "[pushed_mob] is buckled to [pushed_mob.buckled]!")
+ return ..()
+ to_chat(user,"This rite requires more favor!")
+ return FALSE
+ to_chat(user, "You begin to perform the rite of [name]...")
+ if(!LAZYLEN(ritual_invocations))
+ if(do_after(user, target = user, delay = ritual_length))
+ if(invoke_msg)
+ user.say(invoke_msg)
+ return TRUE
+ return FALSE
+ var/first_invoke = TRUE
+ for(var/i in ritual_invocations)
+ if(first_invoke) //instant invoke
+ user.say(i)
+ first_invoke = FALSE
+ continue
+ if(!do_after(user, target = user, delay = ritual_length/ritual_invocations.len))
+ return FALSE
+ user.say(i)
+ if(!do_after(user, target = user, delay = ritual_length/ritual_invocations.len)) //because we start at 0 and not the first fraction in invocations, we still have another fraction of ritual_length to complete
+ return FALSE
+ if(invoke_msg)
+ user.say(invoke_msg)
+ return TRUE
+
+
+///Does the thing if the rite was successfully performed. return value denotes that the effect successfully (IE a harm rite does harm)
+/datum/religion_rites/proc/invoke_effect(mob/living/user, atom/religious_tool)
+ GLOB.religious_sect.on_riteuse(user,religious_tool)
+ return TRUE
+
+
+/*********Technophiles**********/
+
+/datum/religion_rites/synthconversion
+ name = "Synthetic Conversion"
+ desc = "Convert a human-esque individual into a (superior) Android."
+ ritual_length = 1 MINUTES
+ ritual_invocations = list("By the inner workings of our god...",
+ "... We call upon you, in the face of adversity...",
+ "... to complete us, removing that which is undesirable...")
+ invoke_msg = "... Arise, our champion! Become that which your soul craves, live in the world as your true form!!"
+ favor_cost = 500
+
+/datum/religion_rites/synthconversion/perform_rite(mob/living/user, atom/religious_tool)
+ if(!ismovable(religious_tool))
+ to_chat(user, "This rite requires a religious device that individuals can be buckled to.")
+ return FALSE
+ var/atom/movable/movable_reltool = religious_tool
+ if(!movable_reltool)
+ return FALSE
+ if(!LAZYLEN(movable_reltool.buckled_mobs))
+ if(!movable_reltool.can_buckle) //yes, if you have somehow managed to have someone buckled to something that now cannot buckle, we will still let you perform the rite!
+ to_chat(user, "This rite requires a religious device that individuals can be buckled to.")
+ return FALSE
+ to_chat(user, "This rite requires an individual to be buckled to [movable_reltool].")
+ return FALSE
+ return ..()
+
+/datum/religion_rites/synthconversion/invoke_effect(mob/living/user, atom/religious_tool)
+ if(!ismovable(religious_tool))
+ CRASH("[name]'s perform_rite had a movable atom that has somehow turned into a non-movable!")
+ var/atom/movable/movable_reltool = religious_tool
+ if(!movable_reltool?.buckled_mobs?.len)
+ return FALSE
+ var/mob/living/carbon/human/human2borg = locate() in movable_reltool.buckled_mobs
+ if(!human2borg)
+ return FALSE
+ human2borg.set_species(/datum/species/android)
+ human2borg.visible_message("[human2borg] has been converted by the rite of [name]!")
+ return ..()
+
+/*********Ever-Burning Candle**********/
+
+///apply a bunch of fire immunity effect to clothing
+/datum/religion_rites/fireproof/proc/apply_fireproof(obj/item/clothing/fireproofed)
+ fireproofed.name = "unmelting [fireproofed.name]"
+ fireproofed.max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
+ fireproofed.heat_protection = chosen_clothing.body_parts_covered
+ fireproofed.resistance_flags |= FIRE_PROOF
+
+/datum/religion_rites/fireproof
+ name = "Unmelting Wax"
+ desc = "Grants fire immunity to any piece of clothing."
+ ritual_length = 15 SECONDS
+ ritual_invocations = list("And so to support the holder of the Ever-Burning candle...",
+ "... allow this unworthy apparel to serve you ...",
+ "... make it strong enough to burn a thousand times and more ...")
+ invoke_msg = "... Come forth in your new form, and join the unmelting wax of the one true flame!"
+ favor_cost = 500
+///the piece of clothing that will be fireproofed, only one per rite
+ var/obj/item/clothing/chosen_clothing
+
+/datum/religion_rites/fireproof/perform_rite(mob/living/user, atom/religious_tool)
+ var/turf/T = get_turf(religious_tool)
+ var/list/L = T.contents
+ if(!locate(/obj/item/clothing) in L)
+ to_chat(user, "There is no clothing on the altair!")
+ return FALSE
+ for(var/obj/item/clothing/apparel in L)
+ if(apparel.max_heat_protection_temperature >= FIRE_IMMUNITY_MAX_TEMP_PROTECT)
+ continue //we ignore anything that is already fireproof
+ chosen_clothing = apparel //the apparel has been chosen by our lord and savior
+ return ..()
+ return FALSE
+
+/datum/religion_rites/fireproof/invoke_effect(mob/living/user, atom/religious_tool)
+ if(!QDELETED(chosen_clothing) && get_turf(religious_tool) == chosen_clothing.loc) //check if the same clothing is still there
+ if(istype(chosen_clothing, /obj/item/clothing/suit/hooded) || istype(chosen_clothing, /obj/item/clothing/suit/space/hardsuit))
+ for(var/obj/item/clothing/head/integrated_helmet in chosen_clothing.contents) //check if the clothing has a hood/helmet integrated and fireproof it if there is one.
+ apply_fireproof(integrated_helmet)
+ apply_fireproof(chosen_clothing)
+ playsound(get_turf(religious_tool), 'sound/magic/fireball.ogg', 50, TRUE)
+ chosen_clothing = null //our lord and savior no longer cares about this apparel
+ return ..()
+ chosen_clothing = null
+ to_chat(user, "The clothing that was chosen for the rite is no longer on the altar!")
+ return FALSE
+
+
+/datum/religion_rites/burning_sacrifice
+ name = "Candle Fuel"
+ desc = "Sacrifice a buckled burning corpse for favor, the more burn damage the corpse has, the more favor you will receive."
+ ritual_length = 20 SECONDS
+ ritual_invocations = list("To feed the fire of the one true flame ...",
+ "... to make it burn brighter ...",
+ "... so that it may consume all in its path ...",
+ "... I offer you this pitiful being ...")
+ invoke_msg = "... may it join you in the amalgamation of wax and fire, and become one in the black and white scene. "
+///the burning corpse chosen for the sacrifice of the rite
+ var/mob/living/carbon/chosen_sacrifice
+
+/datum/religion_rites/burning_sacrifice/perform_rite(mob/living/user, atom/religious_tool)
+ if(!ismovable(religious_tool))
+ to_chat(user, "This rite requires a religious device that individuals can be buckled to.")
+ return FALSE
+ var/atom/movable/movable_reltool = religious_tool
+ if(!movable_reltool)
+ return FALSE
+ if(!LAZYLEN(movable_reltool.buckled_mobs))
+ to_chat(user, "Nothing is buckled to the altar!")
+ return FALSE
+ for(var/corpse in movable_reltool.buckled_mobs)
+ if(!iscarbon(corpse))// only works with carbon corpse since most normal mobs can't be set on fire.
+ to_chat(user, "Only carbon lifeforms can be properly burned for the sacrifice!")
+ return FALSE
+ chosen_sacrifice = corpse
+ if(chosen_sacrifice.stat != DEAD)
+ to_chat(user, "You can only sacrifice dead bodies, this one is still alive!")
+ chosen_sacrifice = null
+ return FALSE
+ if(!chosen_sacrifice.on_fire)
+ to_chat(user, "This corpse needs to be on fire to be sacrificed!")
+ chosen_sacrifice = null
+ return FALSE
+ return ..()
+
+/datum/religion_rites/burning_sacrifice/invoke_effect(mob/living/user, atom/movable/religious_tool)
+ if(!(chosen_sacrifice in religious_tool.buckled_mobs)) //checks one last time if the right corpse is still buckled
+ to_chat(user, "The right sacrifice is no longer on the altar!")
+ chosen_sacrifice = null
+ return FALSE
+ if(!chosen_sacrifice.on_fire)
+ to_chat(user, "The sacrifice is no longer on fire, it needs to burn until the end of the rite!")
+ chosen_sacrifice = null
+ return FALSE
+ if(chosen_sacrifice.stat != DEAD)
+ to_chat(user, "The sacrifice has to stay dead for the rite to work!")
+ chosen_sacrifice = null
+ return FALSE
+ var/favor_gained = 100 + round(chosen_sacrifice.getFireLoss())
+ GLOB.religious_sect?.adjust_favor(favor_gained, user)
+ to_chat(user, "[GLOB.deity] absorbs the burning corpse and any trace of fire with it. [GLOB.deity] rewards you with [favor_gained] favor.")
+ chosen_sacrifice.dust(force = TRUE)
+ playsound(get_turf(religious_tool), 'sound/effects/supermatter.ogg', 50, TRUE)
+ chosen_sacrifice = null
+ return ..()
+
+
+/datum/religion_rites/infinite_candle
+ name = "Immortal Candles"
+ desc = "Creates 5 candles that never run out of wax."
+ ritual_length = 10 SECONDS
+ invoke_msg = "please lend us five of your candles so we may bask in your burning glory."
+ favor_cost = 200
+
+/datum/religion_rites/infinite_candle/invoke_effect(mob/living/user, atom/movable/religious_tool)
+ var/altar_turf = get_turf(religious_tool)
+ for(var/i in 1 to 5)
+ new /obj/item/candle/infinite(altar_turf)
+ playsound(altar_turf, 'sound/magic/fireball.ogg', 50, TRUE)
+ return ..()
diff --git a/code/modules/requests/request.dm b/code/modules/requests/request.dm
new file mode 100644
index 0000000000000..1acace4f96962
--- /dev/null
+++ b/code/modules/requests/request.dm
@@ -0,0 +1,43 @@
+/// Requests from prayers
+#define REQUEST_PRAYER "request_prayer"
+/// Requests for Centcom
+#define REQUEST_CENTCOM "request_centcom"
+/// Requests for the Syndicate
+#define REQUEST_SYNDICATE "request_syndicate"
+/// Requests for the nuke code
+#define REQUEST_NUKE "request_nuke"
+
+/**
+ * # Request
+ *
+ * A representation of an in-game request, such as a prayer.
+ */
+/datum/request
+ /// Unique ID of the request
+ var/id
+ /// Atomic ID for increment unique request IDs
+ var/static/atomic_id = 0
+ /// The type of request
+ var/req_type
+ /// The owner of the request, the player who created it
+ var/client/owner
+ /// The ckey of the owner, used for re-binding variables on login
+ var/owner_ckey
+ /// The name of the owner, in format /, assigned at time of request creation
+ var/owner_name
+ /// The message associated with the request
+ var/message
+ /// When the request was created
+ var/timestamp
+
+/datum/request/New(client/requestee, type, request)
+ if (!requestee)
+ qdel(src)
+ return
+ id = ++atomic_id
+ owner = requestee
+ owner_ckey = owner.ckey
+ req_type = type
+ message = request
+ timestamp = world.time
+ owner_name = key_name(requestee, FALSE)
diff --git a/code/modules/requests/request_manager.dm b/code/modules/requests/request_manager.dm
new file mode 100644
index 0000000000000..c43ce0c526c0f
--- /dev/null
+++ b/code/modules/requests/request_manager.dm
@@ -0,0 +1,214 @@
+GLOBAL_DATUM_INIT(requests, /datum/request_manager, new)
+
+/**
+ * # Request Manager
+ *
+ * Handles all player requests (prayers, centcom requests, syndicate requests)
+ * that occur in the duration of a round.
+ */
+/datum/request_manager
+ /// Associative list of ckey -> list of requests
+ var/list/requests = list()
+ /// List where requests can be accessed by ID
+ var/list/requests_by_id = list()
+
+/datum/request_manager/Destroy(force, ...)
+ QDEL_LIST(requests)
+ return ..()
+
+/**
+ * Used in the new client pipeline to catch when clients are reconnecting and need to have their
+ * reference re-assigned to the 'owner' variable of any requests
+ *
+ * Arguments:
+ * * C - The client who is logging in
+ */
+/datum/request_manager/proc/client_login(client/C)
+ if (!requests[C.ckey])
+ return
+ for (var/datum/request/request as anything in requests[C.ckey])
+ request.owner = C
+
+/**
+ * Used in the destroy client pipeline to catch when clients are disconnecting and need to have their
+ * reference nulled on the 'owner' variable of any requests
+ *
+ * Arguments:
+ * * C - The client who is logging out
+ */
+/datum/request_manager/proc/client_logout(client/C)
+ if (!requests[C.ckey])
+ return
+ for (var/datum/request/request as anything in requests[C.ckey])
+ request.owner = null
+
+/**
+ * Creates a request for a prayer, and notifies admins who have the sound notifications enabled when appropriate
+ *
+ * Arguments:
+ * * C - The client who is praying
+ * * message - The prayer
+ * * is_chaplain - Boolean operator describing if the prayer is from a chaplain
+ */
+/datum/request_manager/proc/pray(client/C, message, is_chaplain)
+ request_for_client(C, REQUEST_PRAYER, message)
+ for(var/client/admin in GLOB.admins)
+ if(is_chaplain && admin.prefs.chat_toggles & CHAT_PRAYER && admin.prefs.toggles & SOUND_PRAYERS)
+ SEND_SOUND(admin, sound('sound/effects/pray.ogg'))
+
+/**
+ * Creates a request for a Centcom message
+ *
+ * Arguments:
+ * * C - The client who is sending the request
+ * * message - The message
+ */
+/datum/request_manager/proc/message_centcom(client/C, message)
+ request_for_client(C, REQUEST_CENTCOM, message)
+
+/**
+ * Creates a request for a Syndicate message
+ *
+ * Arguments:
+ * * C - The client who is sending the request
+ * * message - The message
+ */
+/datum/request_manager/proc/message_syndicate(client/C, message)
+ request_for_client(C, REQUEST_SYNDICATE, message)
+
+/**
+ * Creates a request for the nuclear self destruct codes
+ *
+ * Arguments:
+ * * C - The client who is sending the request
+ * * message - The message
+ */
+/datum/request_manager/proc/nuke_request(client/C, message)
+ request_for_client(C, REQUEST_NUKE, message)
+
+/**
+ * Creates a request and registers the request with all necessary internal tracking lists
+ *
+ * Arguments:
+ * * C - The client who is sending the request
+ * * type - The type of request, see defines
+ * * message - The message
+ */
+/datum/request_manager/proc/request_for_client(client/C, type, message)
+ var/datum/request/request = new(C, type, message)
+ if (!requests[C.ckey])
+ requests[C.ckey] = list()
+ requests[C.ckey] += request
+ requests_by_id.len++
+ requests_by_id[request.id] = request
+
+/datum/request_manager/ui_interact(mob/user, datum/tgui/ui = null)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if (!ui)
+ ui = new(user, src, "RequestManager")
+ ui.open()
+
+/datum/request_manager/ui_state(mob/user)
+ return GLOB.admin_state
+
+/datum/request_manager/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ if (..())
+ return
+
+ // Only admins should be sending actions
+ if (!check_rights(R_ADMIN))
+ to_chat(usr, "You do not have permission to do this, you require +ADMIN")
+ return
+
+ // Get the request this relates to
+ var/id = params["id"] != null ? text2num(params["id"]) : null
+ if (!id)
+ to_chat(usr, "Failed to find a request ID in your action, please report this")
+ CRASH("Received an action without a request ID, this shouldn't happen!")
+ var/datum/request/request = !id ? null : requests_by_id[id]
+ var/datum/admins/admin_datum = GLOB.admin_datums[usr.ckey]
+
+ switch(action)
+ if ("pp")
+ var/mob/M = request.owner?.mob
+ usr.client.holder.show_player_panel(M)
+ return TRUE
+ if ("vv")
+ var/mob/M = request.owner?.mob
+ usr.client.debug_variables(M)
+ return TRUE
+ if ("sm")
+ var/mob/M = request.owner?.mob
+ usr.client.cmd_admin_subtle_message(M)
+ return TRUE
+ if ("flw")
+ var/mob/M = request.owner?.mob
+ admin_datum.admin_follow(M)
+ return TRUE
+ if ("tp")
+ if(!SSticker.HasRoundStarted())
+ to_chat(usr,"The game hasn't started yet!")
+ return TRUE
+ var/mob/M = request.owner?.mob
+ if(!ismob(M))
+ var/datum/mind/D = M
+ if(!istype(D))
+ to_chat(usr, "This can only be used on instances of type /mob and /mind")
+ return TRUE
+ else
+ D.traitor_panel()
+ return TRUE
+ else
+ usr.client.holder.show_traitor_panel(M)
+ return TRUE
+ if ("logs")
+ var/mob/M = request.owner?.mob
+ if(!ismob(M))
+ to_chat(usr, "This can only be used on instances of type /mob.")
+ return TRUE
+ show_individual_logging_panel(M, null, null)
+ return TRUE
+ if ("smite")
+ if(!check_rights(R_FUN))
+ to_chat(usr, "Insufficient permissions to smite, you require +FUN")
+ return TRUE
+ var/mob/living/carbon/human/H = request.owner?.mob
+ if (!H || !istype(H))
+ to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human")
+ return TRUE
+ usr.client.smite(H)
+ return TRUE
+ if ("rply")
+ if (request.req_type == REQUEST_PRAYER)
+ to_chat(usr, "Cannot reply to a prayer")
+ return TRUE
+ var/mob/M = request.owner?.mob
+ usr.client.admin_headset_message(M, request.req_type == REQUEST_SYNDICATE ? RADIO_CHANNEL_SYNDICATE : RADIO_CHANNEL_CENTCOM)
+ return TRUE
+ if ("setcode")
+ if (request.req_type != REQUEST_NUKE)
+ to_chat(usr, "You cannot set the nuke code for a non-nuke-code-request request!")
+ return TRUE
+ var/code = random_nukecode()
+ for(var/obj/machinery/nuclearbomb/selfdestruct/SD in GLOB.nuke_list)
+ SD.r_code = code
+ message_admins("[key_name_admin(usr)] has set the self-destruct code to \"[code]\".")
+ return TRUE
+
+/datum/request_manager/ui_data(mob/user)
+ . = list(
+ "requests" = list()
+ )
+ for (var/ckey in requests)
+ for (var/datum/request/request as anything in requests[ckey])
+ var/list/data = list(
+ "id" = request.id,
+ "req_type" = request.req_type,
+ "owner" = request.owner ? "[REF(request.owner)]" : null,
+ "owner_ckey" = request.owner_ckey,
+ "owner_name" = request.owner_name,
+ "message" = request.message,
+ "timestamp" = request.timestamp,
+ "timestamp_str" = gameTimestamp(wtime = request.timestamp)
+ )
+ .["requests"] += list(data)
diff --git a/code/modules/research/designs.dm b/code/modules/research/designs.dm
index a1c699cfee508..ddae313505043 100644
--- a/code/modules/research/designs.dm
+++ b/code/modules/research/designs.dm
@@ -39,6 +39,8 @@ other types of metals and chemistry for reagents).
var/research_icon //Replaces the item icon in the research console
var/research_icon_state
var/icon_cache
+ /// Optional string that interfaces can use as part of search filters. See- item/borg/upgrade/ai and the Exosuit Fabs.
+ var/search_metadata
/datum/design/error_design
name = "ERROR"
@@ -74,7 +76,7 @@ other types of metals and chemistry for reagents).
name = "Component Design Disk"
desc = "A disk for storing device design data for construction in lathes."
icon_state = "datadisk1"
- materials = list(/datum/material/iron =300, /datum/material/glass =100)
+ materials = list(/datum/material/iron = 300, /datum/material/glass =100)
var/list/blueprints = list()
var/max_blueprints = 1
@@ -88,5 +90,5 @@ other types of metals and chemistry for reagents).
/obj/item/disk/design_disk/adv
name = "Advanced Component Design Disk"
desc = "A disk for storing device design data for construction in lathes. This one has extra storage space."
- materials = list(/datum/material/iron =300, /datum/material/glass = 100, /datum/material/silver = 50)
+ materials = list(/datum/material/iron = 300, /datum/material/glass = 100, /datum/material/silver = 50)
max_blueprints = 5
diff --git a/code/modules/research/designs/AI_module_designs.dm b/code/modules/research/designs/AI_module_designs.dm
index 62b0afbfa221d..c0d09b3f44616 100644
--- a/code/modules/research/designs/AI_module_designs.dm
+++ b/code/modules/research/designs/AI_module_designs.dm
@@ -154,3 +154,12 @@
build_path = /obj/item/aiModule/core/full/custom
category = list("AI Modules")
departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/board/dadbot_module
+ name = "Core Module Design (DadBot)"
+ desc = "Allows for the construction of a Dadbot AI Core Module."
+ id = "dadbot_module"
+ materials = list(/datum/material/glass = 1000, /datum/material/diamond = 2000, /datum/material/copper = 300)
+ build_path = /obj/item/aiModule/core/full/dadbot
+ category = list("AI Modules")
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
diff --git a/code/modules/research/designs/autolathe_designs.dm b/code/modules/research/designs/autolathe_designs.dm
index dc08c9dafe5d4..1536d77fa5007 100644
--- a/code/modules/research/designs/autolathe_designs.dm
+++ b/code/modules/research/designs/autolathe_designs.dm
@@ -25,7 +25,7 @@
id="pushbroom"
build_type = AUTOLATHE | PROTOLATHE
materials = list(/datum/material/iron = 2000)
- build_path = /obj/item/twohanded/pushbroom
+ build_path = /obj/item/pushbroom
category = list("initial","Tools","Tool Designs")
departmental_flags = DEPARTMENTAL_FLAG_SERVICE
@@ -157,7 +157,7 @@
materials = list(/datum/material/iron = 10, /datum/material/glass = 5)
build_path = /obj/item/stack/cable_coil
category = list("initial","Tools","Tool Designs")
- maxstack = 30
+ maxstack = MAXCOIL
departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_SCIENCE
/datum/design/toolbox
@@ -567,12 +567,12 @@
build_path = /obj/item/ammo_casing/shotgun/rubbershot
category = list("initial", "Security")
-/datum/design/c38
+/datum/design/c38b
name = "Speed Loader (.38)"
- id = "c38"
+ id = "c38b"
build_type = AUTOLATHE
- materials = list(/datum/material/iron = 20000)
- build_path = /obj/item/ammo_box/c38
+ materials = list(/datum/material/iron = 16000)
+ build_path = /obj/item/ammo_box/c38/match/bouncy
category = list("initial", "Security")
/datum/design/recorder
@@ -850,6 +850,14 @@
build_path = /obj/item/ammo_box/c9mm
category = list("hacked", "Security")
+/datum/design/c38
+ name = "Speed Loader (.38)"
+ id = "c38"
+ build_type = AUTOLATHE
+ materials = list(/datum/material/iron = 20000)
+ build_path = /obj/item/ammo_box/c38
+ category = list("hacked", "Security")
+
/datum/design/cleaver
name = "Butcher's Cleaver"
id = "cleaver"
@@ -1039,3 +1047,11 @@
build_path = /obj/item/stack/ducts
category = list("initial", "Construction")
maxstack = 50
+
+/datum/design/airlock_painter/decal
+ name = "Decal Painter"
+ id = "decal_painter"
+ build_type = AUTOLATHE | PROTOLATHE
+ materials = list(/datum/material/iron = 50, /datum/material/glass = 50)
+ build_path = /obj/item/airlock_painter/decal
+ category = list("initial","Tools","Tool Designs")
diff --git a/code/modules/research/designs/biogenerator_designs.dm b/code/modules/research/designs/biogenerator_designs.dm
index c2f38c10ceab1..426be5ea6a63e 100644
--- a/code/modules/research/designs/biogenerator_designs.dm
+++ b/code/modules/research/designs/biogenerator_designs.dm
@@ -59,6 +59,7 @@
make_reagents = list(/datum/reagent/consumable/enzyme = 10)
category = list("initial","Food")
+
/datum/design/flour_sack
name = "Flour Sack"
id = "flour_sack"
@@ -66,7 +67,6 @@
materials = list(/datum/material/biomass= 150)
build_path = /obj/item/reagent_containers/food/condiment/flour
category = list("initial","Food")
-
/datum/design/sugar_sack
name = "Sugar Sack"
id = "sugar_sack"
@@ -83,14 +83,6 @@
build_path = /obj/item/reagent_containers/food/snacks/monkeycube
category = list("initial", "Food")
-/datum/design/strange_seeds
- name = "Pack of Strange Seeds"
- id = "random"
- build_type = BIOGENERATOR
- materials = list(/datum/material/biomass = 5000)
- build_path = /obj/item/seeds/random
- category = list("initial", "Food")
-
/datum/design/ez_nut
name = "E-Z Nutrient"
id = "ez_nut"
@@ -163,6 +155,14 @@
build_path = /obj/item/stack/sheet/leather
category = list("initial","Organic Materials")
+/datum/design/toolbelt
+ name = "Tool Belt"
+ id = "toolbelt"
+ build_type = BIOGENERATOR
+ materials = list(/datum/material/biomass= 300)
+ build_path = /obj/item/storage/belt/utility
+ category = list("initial","Organic Materials")
+
/datum/design/secbelt
name = "Security Belt"
id = "secbelt"
@@ -202,3 +202,11 @@
materials = list(/datum/material/biomass= 300)
build_path = /obj/item/clothing/head/rice_hat
category = list("initial","Organic Materials")
+
+/datum/design/carton_soy_milk
+ name = "Soy Milk Carton"
+ id = "soy_milk_carton"
+ build_type = BIOGENERATOR
+ materials = list(/datum/material/biomass = 100)
+ build_path = /obj/item/reagent_containers/food/condiment/soymilk
+ category = list("initial","Food")
diff --git a/code/modules/research/designs/comp_board_designs.dm b/code/modules/research/designs/comp_board_designs.dm
index 48ccfef8e3aa8..27217c5356e3e 100644
--- a/code/modules/research/designs/comp_board_designs.dm
+++ b/code/modules/research/designs/comp_board_designs.dm
@@ -22,6 +22,14 @@
category = list("Computer Boards")
departmental_flags = DEPARTMENTAL_FLAG_ALL
+/datum/design/board/amputation_adventure
+ name = "Computer Design (Mediborg's Amputation Adventure)"
+ desc = "Allows for the construction of circuit boards used to build a new Mediborg's Amputation Adventure machine."
+ id = "arcade_amputation"
+ build_path = /obj/item/circuitboard/computer/arcade/amputation
+ category = list("Computer Boards")
+ departmental_flags = DEPARTMENTAL_FLAG_ALL
+
/datum/design/board/seccamera
name = "Computer Design (Security Camera)"
desc = "Allows for the construction of circuit boards used to build security camera computers."
@@ -216,6 +224,14 @@
category = list("Computer Boards")
departmental_flags = DEPARTMENTAL_FLAG_CARGO
+/datum/design/board/objective
+ name = "Computer Design (Objective Console)"
+ desc = "Allows for the construction of circuit boards used to build a Objective Console."
+ id = "objective"
+ build_path = /obj/item/circuitboard/computer/objective
+ category = list("Computer Boards")
+ departmental_flags = DEPARTMENTAL_FLAG_CARGO | DEPARTMENTAL_FLAG_SCIENCE
+
/datum/design/board/cargorequest
name = "Computer Design (Supply Request Console)"
desc = "Allows for the construction of circuit boards used to build a Supply Request Console."
@@ -319,11 +335,3 @@
build_path = /obj/item/circuitboard/computer/shuttle/flight_control
category = list("Computer Boards", "Shuttle Machinery")
departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING
-
-/datum/design/board/shuttle/shuttle_docker
- name = "Computer Design (Private Navigation Computer)"
- desc = "Allows for the construction of circuit boards used to build a console that enables the targetting of custom flight locations"
- id = "shuttle_docker"
- build_path = /obj/item/circuitboard/computer/shuttle/docker
- category = list("Computer Boards", "Shuttle Machinery")
- departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING
diff --git a/code/modules/research/designs/electronics_designs.dm b/code/modules/research/designs/electronics_designs.dm
index 5035d1644c634..83bf900dee9a2 100644
--- a/code/modules/research/designs/electronics_designs.dm
+++ b/code/modules/research/designs/electronics_designs.dm
@@ -56,40 +56,6 @@
category = list("Electronics")
departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
-///////////////////////////////////
-//////////Circuit Stuff////////////
-///////////////////////////////////
-/datum/design/integrated_printer
- name = "Integrated circuit printer"
- desc = "This machine provides all necessary things for circuitry."
- id = "icprinter"
- build_type = PROTOLATHE
- materials = list(/datum/material/glass = 5000, /datum/material/iron = 10000, /datum/material/copper = 2000)
- build_path = /obj/item/integrated_circuit_printer
- category = list("Electronics")
- departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
-
-/datum/design/IC_printer_upgrade_advanced
- name = "Integrated circuit printer upgrade: Advanced Designs"
- desc = "This disk allows for integrated circuit printers to print advanced circuitry designs."
- id = "icupgadv"
- build_type = PROTOLATHE
- materials = list(/datum/material/glass = 10000, /datum/material/iron = 10000, /datum/material/copper = 4000)
- build_path = /obj/item/disk/integrated_circuit/upgrade/advanced
- category = list("Electronics")
- departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
-
-/datum/design/IC_printer_upgrade_clone
- name = "Integrated circuit printer upgrade: Instant Cloning"
- desc = "This disk allows for integrated circuit printers to clone designs instantaneously."
- id = "icupgclo"
- build_type = PROTOLATHE
- materials = list(/datum/material/glass = 10000, /datum/material/iron = 10000, /datum/material/copper = 4000)
- build_path = /obj/item/disk/integrated_circuit/upgrade/clone
- category = list("Electronics")
- departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
-
-
////////////////////////////////////////
//////////Disk Construction Disks///////
////////////////////////////////////////
diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm
index 4e8139d8790fa..1c76423e24da6 100644
--- a/code/modules/research/designs/machine_designs.dm
+++ b/code/modules/research/designs/machine_designs.dm
@@ -91,8 +91,8 @@
departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_SCIENCE
/datum/design/board/thermomachine
- name = "Machine Design (Freezer/Heater Board)"
- desc = "The circuit board for a freezer/heater."
+ name = "Machine Design (Thermomachine Board)"
+ desc = "The circuit board for a thermomachine."
id = "thermomachine"
build_path = /obj/item/circuitboard/machine/thermomachine
category = list ("Engineering Machinery")
@@ -475,14 +475,6 @@
category = list ("Misc. Machinery")
departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_CARGO | DEPARTMENTAL_FLAG_ENGINEERING
-/datum/design/board/bluespace_miner
- name = "Machine Design (Bluespace Miner)"
- desc = "The circuit board for a Bluespace Miner."
- id = "bluespace_miner"
- build_path = /obj/item/circuitboard/machine/bluespace_miner
- category = list ("Misc. Machinery")
- departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_CARGO | DEPARTMENTAL_FLAG_ENGINEERING
-
/datum/design/board/mining_equipment_vendor
name = "Machine Design (Mining Rewards Vendor Board)"
desc = "The circuit board for a Mining Rewards Vendor."
@@ -491,6 +483,14 @@
category = list ("Misc. Machinery")
departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_CARGO
+/datum/design/board/exploration_equipment_vendor
+ name = "Machine Design (Exploration Rewards Vendor Board)"
+ desc = "The circuit board for an Exploration Rewards Vendor."
+ id = "exploration_equipment_vendor"
+ build_path = /obj/item/circuitboard/machine/exploration_equipment_vendor
+ category = list ("Misc. Machinery")
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_CARGO
+
/datum/design/board/tesla_coil
name = "Machine Design (Tesla Coil Board)"
desc = "The circuit board for a tesla coil."
@@ -627,18 +627,10 @@
category = list("Medical Machinery")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
-/datum/design/board/autodoc
- name = "Machine Design (Autodoc)"
- desc = "The circuit board for an Autodoc."
- id = "autodoc"
- build_path = /obj/item/circuitboard/machine/autodoc
- category = list("Medical Machinery")
- departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
-
-/datum/design/board/spaceship_navigation_beacon
- name = "Machine Design (Bluespace Navigation Gigabeacon)"
- desc = "The circuit board for a Bluespace Navigation Gigabeacon."
- id = "spaceship_navigation_beacon"
- build_path = /obj/item/circuitboard/machine/spaceship_navigation_beacon
- category = list ("Teleportation Machinery")
- departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_SCIENCE
+/datum/design/board/component_printer
+ name = "Machine Design (Component Printer)"
+ desc = "The circuit board for a component printer"
+ id = "component_printer"
+ build_path = /obj/item/circuitboard/machine/component_printer
+ category = list("Misc. Machinery")
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
diff --git a/code/modules/research/designs/mecha_designs.dm b/code/modules/research/designs/mecha_designs.dm
index b738fce19164b..1607bf477d0dc 100644
--- a/code/modules/research/designs/mecha_designs.dm
+++ b/code/modules/research/designs/mecha_designs.dm
@@ -224,19 +224,8 @@
construction_time = 100
category = list("Exosuit Equipment")
-/datum/design/mech_missile_rack
- name = "Exosuit Weapon (SRM-8 Missile Rack)"
- desc = "Allows for the construction of an SRM-8 Missile Rack."
- id = "mech_missile_rack"
- build_type = PROTOLATHE
- build_path = /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack
- materials = list(/datum/material/iron=22000,/datum/material/gold=6000,/datum/material/silver=8000)
- construction_time = 100
- category = list("Weapons")
- departmental_flags = DEPARTMENTAL_FLAG_SECURITY
-
/datum/design/clusterbang_launcher
- name = "Exosuit Module (SOB-3 Clusterbang Launcher)"
+ name = "Exosuit Weapon (SOB-3 Clusterbang Launcher)"
desc = "A weapon that violates the Geneva Convention at 3 rounds per minute"
id = "clusterbang_launcher"
build_type = PROTOLATHE
@@ -337,7 +326,7 @@
category = list("Exosuit Equipment")
/datum/design/mech_diamond_drill
- name = "Exosuit Module (Diamond Mining Drill)"
+ name = "Exosuit Mining (Diamond Mining Drill)"
desc = "An upgraded version of the standard drill."
id = "mech_diamond_drill"
build_type = MECHFAB
@@ -357,7 +346,7 @@
category = list("Exosuit Equipment")
/datum/design/mech_plasma_cutter
- name = "Exosuit Module Design (217-D Heavy Plasma Cutter)"
+ name = "Exosuit Mining (217-D Heavy Plasma Cutter)"
desc = "A device that shoots resonant plasma bursts at extreme velocity. The blasts are capable of crushing rock and demolishing solid obstacles."
id = "mech_plasma_cutter"
build_type = MECHFAB
@@ -378,7 +367,7 @@
departmental_flags = DEPARTMENTAL_FLAG_SECURITY
/datum/design/mech_sleeper
- name = "Exosuit Medical Equipment (Mounted Sleeper)"
+ name = "Exosuit Medical (Mounted Sleeper)"
desc = "Equipment for medical exosuits. A mounted sleeper that stabilizes patients and can inject reagents in the exosuit's reserves."
id = "mech_sleeper"
build_type = MECHFAB
@@ -388,7 +377,7 @@
category = list("Exosuit Equipment")
/datum/design/mech_syringe_gun
- name = "Exosuit Medical Equipment (Syringe Gun)"
+ name = "Exosuit Medical (Syringe Gun)"
desc = "Equipment for medical exosuits. A chem synthesizer with syringe gun. Reagents inside are held in stasis, so no reactions will occur."
id = "mech_syringe_gun"
build_type = MECHFAB
@@ -398,7 +387,7 @@
category = list("Exosuit Equipment")
/datum/design/mech_medical_beamgun
- name = "Exosuit Medical Equipment (Medical Beamgun)"
+ name = "Exosuit Medical (Medical Beamgun)"
desc = "Equipment for medical exosuits. A mounted medical nanite projector which will treat patients with a focused beam."
id = "mech_medi_beam"
build_type = MECHFAB
diff --git a/code/modules/research/designs/mechfabricator_designs.dm b/code/modules/research/designs/mechfabricator_designs.dm
index 60093787a9925..4735b00259ea9 100644
--- a/code/modules/research/designs/mechfabricator_designs.dm
+++ b/code/modules/research/designs/mechfabricator_designs.dm
@@ -476,7 +476,7 @@
//Exosuit Equipment
/datum/design/ripleyupgrade
- name = "Ripley MK-1 to MK-II conversion kit"
+ name = "Ripley MK-I to MK-II conversion kit"
id = "ripleyupgrade"
build_type = MECHFAB
build_path = /obj/item/mecha_parts/mecha_equipment/ripleyupgrade
@@ -485,7 +485,7 @@
category = list("Exosuit Equipment")
/datum/design/mech_hydraulic_clamp
- name = "Exosuit Engineering Equipment (Hydraulic Clamp)"
+ name = "Exosuit Engineering (Hydraulic Clamp)"
id = "mech_hydraulic_clamp"
build_type = MECHFAB
build_path = /obj/item/mecha_parts/mecha_equipment/hydraulic_clamp
@@ -494,7 +494,7 @@
category = list("Exosuit Equipment")
/datum/design/mech_drill
- name = "Exosuit Engineering Equipment (Drill)"
+ name = "Exosuit Mining (Mining Drill)"
id = "mech_drill"
build_type = MECHFAB
build_path = /obj/item/mecha_parts/mecha_equipment/drill
@@ -503,7 +503,7 @@
category = list("Exosuit Equipment")
/datum/design/mech_mining_scanner
- name = "Exosuit Engineering Equipment (Mining Scanner)"
+ name = "Exosuit Engineering (Mining Scanner)"
id = "mech_mscanner"
build_type = MECHFAB
build_path = /obj/item/mecha_parts/mecha_equipment/mining_scanner
@@ -512,7 +512,7 @@
category = list("Exosuit Equipment")
/datum/design/mech_extinguisher
- name = "Exosuit Engineering Equipment (Extinguisher)"
+ name = "Exosuit Engineering (Extinguisher)"
id = "mech_extinguisher"
build_type = MECHFAB
build_path = /obj/item/mecha_parts/mecha_equipment/extinguisher
@@ -521,7 +521,7 @@
category = list("Exosuit Equipment")
/datum/design/mech_generator
- name = "Exosuit Equipment (Plasma Generator)"
+ name = "Exosuit Module (Plasma Generator)"
id = "mech_generator"
build_type = MECHFAB
build_path = /obj/item/mecha_parts/mecha_equipment/generator
@@ -530,7 +530,7 @@
category = list("Exosuit Equipment")
/datum/design/mech_mousetrap_mortar
- name = "H.O.N.K Mousetrap Mortar"
+ name = "H.O.N.K Weapon (Mousetrap Mortar)"
id = "mech_mousetrap_mortar"
build_type = MECHFAB
build_path = /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/mousetrap_mortar
@@ -539,7 +539,7 @@
category = list("Exosuit Equipment")
/datum/design/mech_banana_mortar
- name = "H.O.N.K Banana Mortar"
+ name = "H.O.N.K Weapon (Banana Mortar)"
id = "mech_banana_mortar"
build_type = MECHFAB
build_path = /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/banana_mortar
@@ -548,7 +548,7 @@
category = list("Exosuit Equipment")
/datum/design/mech_honker
- name = "HoNkER BlAsT 5000"
+ name = "H.O.N.K Weapon (HoNkER BlAsT 5000)"
id = "mech_honker"
build_type = MECHFAB
build_path = /obj/item/mecha_parts/mecha_equipment/weapon/honker
@@ -557,7 +557,7 @@
category = list("Exosuit Equipment")
/datum/design/mech_punching_glove
- name = "Oingo Boingo Punch-face"
+ name = "H.O.N.K Weapon (Oingo Boingo Punch-face)"
id = "mech_punching_face"
build_type = MECHFAB
build_path = /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/punching_glove
@@ -668,6 +668,15 @@
construction_time = 120
category = list("Cyborg Upgrade Modules")
+/datum/design/borg_transform_security
+ name = "Cyborg Upgrade (Security Module)"
+ id = "borg_transform_security"
+ build_type = MECHFAB
+ build_path = /obj/item/borg/upgrade/transform/security
+ materials = list(/datum/material/iron = 15000, /datum/material/glass = 15000, /datum/material/diamond = 3000)
+ construction_time = 120
+ category = list("Cyborg Upgrade Modules")
+
/datum/design/borg_upgrade_selfrepair
name = "Cyborg Upgrade (Self-repair)"
id = "borg_upgrade_selfrepair"
@@ -747,7 +756,8 @@
build_path = /obj/item/borg/upgrade/ai
materials = list(/datum/material/iron = 1200, /datum/material/glass = 1500, /datum/material/gold = 200)
construction_time = 50
- category = list("Misc")
+ category = list("Control Interfaces")
+ search_metadata = "boris"
/datum/design/borg_upgrade_rped
name = "Cyborg Upgrade (RPED)"
@@ -787,13 +797,13 @@
//Misc
/datum/design/mecha_tracking
- name = "Exosuit Tracking Beacon"
+ name = "Exosuit Tracker (Exosuit Tracking Beacon)"
id = "mecha_tracking"
build_type = MECHFAB
build_path =/obj/item/mecha_parts/mecha_tracking
materials = list(/datum/material/iron=500)
construction_time = 50
- category = list("Misc")
+ category = list("Exosuit Equipment")
/datum/design/mecha_tracking_ai_control
name = "AI Control Beacon"
@@ -802,7 +812,7 @@
build_path = /obj/item/mecha_parts/mecha_tracking/ai_control
materials = list(/datum/material/iron = 1000, /datum/material/glass = 500, /datum/material/silver = 200)
construction_time = 50
- category = list("Misc")
+ category = list("Control Interfaces")
/datum/design/synthetic_flash
name = "Flash"
@@ -847,7 +857,7 @@
name = "Micro-cell"
id = "robotic_stomach"
build_type = MECHFAB
- build_path = /obj/item/organ/stomach/cell
+ build_path = /obj/item/organ/stomach/battery/ipc
materials = list(/datum/material/iron = 2000, /datum/material/glass = 2000, /datum/material/plasma = 200)
construction_time = 100
category = list("IPC Components")
@@ -869,3 +879,41 @@
materials = list(/datum/material/iron = 2000, /datum/material/glass = 1000)
construction_time = 100
category = list("IPC Components")
+
+//service modules
+
+/datum/design/borg_upgrade_botany
+ name = "Cyborg Speciality (Botany)"
+ id = "borg_upgrade_botany"
+ build_type = MECHFAB
+ build_path = /obj/item/borg/upgrade/speciality/botany
+ materials = list(/datum/material/iron = 2000, /datum/material/glass = 1000)
+ construction_time = 40
+ category = list("Cyborg Upgrade Modules")
+
+/datum/design/borg_upgrade_kitchen
+ name = "Cyborg Speciality (Cooking)"
+ id = "borg_upgrade_kitchen"
+ build_type = MECHFAB
+ build_path = /obj/item/borg/upgrade/speciality/kitchen
+ materials = list(/datum/material/iron = 2000, /datum/material/silver = 500)
+ construction_time = 40
+ category = list("Cyborg Upgrade Modules")
+
+/datum/design/borg_upgrade_casino
+ name = "Cyborg Speciality (Casino)"
+ id = "borg_upgrade_casino"
+ build_type = MECHFAB
+ build_path = /obj/item/borg/upgrade/speciality/casino
+ materials = list(/datum/material/iron = 2000, /datum/material/gold = 500)
+ construction_time = 40
+ category = list("Cyborg Upgrade Modules")
+
+/datum/design/borg_upgrade_party
+ name = "Cyborg Speciality (Party)"
+ id = "borg_upgrade_party"
+ build_type = MECHFAB
+ build_path = /obj/item/borg/upgrade/speciality/party
+ materials = list(/datum/material/iron = 2000, /datum/material/diamond = 500)
+ construction_time = 40
+ category = list("Cyborg Upgrade Modules")
diff --git a/code/modules/research/designs/medical_designs.dm b/code/modules/research/designs/medical_designs.dm
index 5cecdc6872db7..9caadd155daf1 100644
--- a/code/modules/research/designs/medical_designs.dm
+++ b/code/modules/research/designs/medical_designs.dm
@@ -10,7 +10,7 @@
materials = list(/datum/material/iron = 1000, /datum/material/glass = 500)
construction_time = 75
build_path = /obj/item/mmi
- category = list("Misc","Medical Designs")
+ category = list("Control Interfaces", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
/datum/design/posibrain
@@ -21,7 +21,7 @@
materials = list(/datum/material/iron = 1700, /datum/material/glass = 1350, /datum/material/gold = 500, /datum/material/copper = 500) //Gold, because SWAG.
construction_time = 75
build_path = /obj/item/mmi/posibrain
- category = list("Misc", "Medical Designs")
+ category = list("Control Interfaces", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
/datum/design/bluespacebeaker
@@ -88,7 +88,7 @@
id = "noreactsyringe"
build_type = PROTOLATHE
materials = list(/datum/material/glass = 2000, /datum/material/gold = 1000)
- build_path = /obj/item/reagent_containers/syringe/noreact
+ build_path = /obj/item/reagent_containers/syringe/cryo
category = list("Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
@@ -243,7 +243,7 @@
construction_time = 40
materials = list(/datum/material/iron = 600, /datum/material/glass = 400, /datum/material/copper = 200)
build_path = /obj/item/organ/eyes/robotic/shield
- category = list("Misc", "Medical Designs")
+ category = list("Implants", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/cyberimp_gloweyes
@@ -254,7 +254,7 @@
construction_time = 40
materials = list(/datum/material/iron = 600, /datum/material/glass = 1000, /datum/material/copper = 200)
build_path = /obj/item/organ/eyes/robotic/glow
- category = list("Misc", "Medical Designs")
+ category = list("Implants", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/cyberimp_breather
@@ -265,7 +265,7 @@
construction_time = 35
materials = list(/datum/material/iron = 600, /datum/material/glass = 250, /datum/material/copper = 200)
build_path = /obj/item/organ/cyberimp/mouth/breathing_tube
- category = list("Misc", "Medical Designs")
+ category = list("Implants", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/cyberimp_surgical
@@ -276,7 +276,7 @@
materials = list (/datum/material/iron = 2500, /datum/material/glass = 1500, /datum/material/silver = 1500, /datum/material/copper = 200)
construction_time = 200
build_path = /obj/item/organ/cyberimp/arm/surgery
- category = list("Misc", "Medical Designs")
+ category = list("Implants", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/cyberimp_toolset
@@ -287,7 +287,29 @@
materials = list (/datum/material/iron = 2500, /datum/material/glass = 1500, /datum/material/silver = 1500, /datum/material/copper = 200)
construction_time = 200
build_path = /obj/item/organ/cyberimp/arm/toolset
- category = list("Misc", "Medical Designs")
+ category = list("Implants", "Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
+
+/datum/design/cyberimp_botany
+ name = "Botanical toolset implant"
+ desc = "Everything a botanist needs in an arm implant, designed to be installed on a subject's arm."
+ id = "ci-botany"
+ build_type = MECHFAB | PROTOLATHE
+ materials = list (/datum/material/iron = 3500, /datum/material/glass = 1500, /datum/material/silver = 1500, /datum/material/plastic = 2000)
+ construction_time = 200
+ build_path = /obj/item/organ/cyberimp/arm/botany
+ category = list("Implants", "Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
+
+/datum/design/cyberimp_janitor
+ name = "Janitorial Arm Implant"
+ desc = "A set of janitor tools fitted into an arm implant, designed to be installed on subject's arm."
+ id = "ci-janitor"
+ build_type = PROTOLATHE | MECHFAB
+ materials = list (/datum/material/iron = 3500, /datum/material/glass = 1500, /datum/material/silver = 1500)
+ construction_time = 200
+ build_path = /obj/item/organ/cyberimp/arm/janitor
+ category = list("Implants", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/cyberimp_medical_hud
@@ -298,7 +320,7 @@
construction_time = 50
materials = list(/datum/material/iron = 600, /datum/material/glass = 600, /datum/material/silver = 500, /datum/material/gold = 500, /datum/material/copper = 200)
build_path = /obj/item/organ/cyberimp/eyes/hud/medical
- category = list("Misc", "Medical Designs")
+ category = list("Implants", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/cyberimp_security_hud
@@ -309,7 +331,7 @@
construction_time = 50
materials = list(/datum/material/iron = 600, /datum/material/glass = 600, /datum/material/silver = 750, /datum/material/gold = 750, /datum/material/copper = 200)
build_path = /obj/item/organ/cyberimp/eyes/hud/security
- category = list("Misc", "Medical Designs")
+ category = list("Implants", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/cyberimp_diagnostic_hud
@@ -320,7 +342,7 @@
construction_time = 50
materials = list(/datum/material/iron = 600, /datum/material/glass = 600, /datum/material/silver = 600, /datum/material/gold = 600, /datum/material/copper = 200)
build_path = /obj/item/organ/cyberimp/eyes/hud/diagnostic
- category = list("Misc", "Medical Designs")
+ category = list("Implants", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/cyberimp_xray
@@ -331,7 +353,7 @@
construction_time = 60
materials = list(/datum/material/iron = 600, /datum/material/glass = 600, /datum/material/silver = 600, /datum/material/gold = 600, /datum/material/plasma = 1000, /datum/material/uranium = 1000, /datum/material/diamond = 1000, /datum/material/bluespace = 1000, /datum/material/copper = 200)
build_path = /obj/item/organ/eyes/robotic/xray
- category = list("Misc", "Medical Designs")
+ category = list("Implants", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/cyberimp_thermals
@@ -342,7 +364,7 @@
construction_time = 60
materials = list(/datum/material/iron = 600, /datum/material/glass = 600, /datum/material/silver = 600, /datum/material/gold = 600, /datum/material/plasma = 1000, /datum/material/diamond = 2000, /datum/material/copper = 200)
build_path = /obj/item/organ/eyes/robotic/thermals
- category = list("Misc", "Medical Designs")
+ category = list("Implants", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/cyberimp_antidrop
@@ -353,7 +375,7 @@
construction_time = 60
materials = list(/datum/material/iron = 600, /datum/material/glass = 600, /datum/material/silver = 400, /datum/material/gold = 400, /datum/material/copper = 200)
build_path = /obj/item/organ/cyberimp/brain/anti_drop
- category = list("Misc", "Medical Designs")
+ category = list("Implants", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/cyberimp_antistun
@@ -364,7 +386,7 @@
construction_time = 60
materials = list(/datum/material/iron = 600, /datum/material/glass = 600, /datum/material/silver = 500, /datum/material/gold = 1000, /datum/material/copper = 200)
build_path = /obj/item/organ/cyberimp/brain/anti_stun
- category = list("Misc", "Medical Designs")
+ category = list("Implants", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/cyberimp_nutriment
@@ -375,7 +397,7 @@
construction_time = 40
materials = list(/datum/material/iron = 500, /datum/material/glass = 500, /datum/material/gold = 500, /datum/material/copper = 200)
build_path = /obj/item/organ/cyberimp/chest/nutriment
- category = list("Misc", "Medical Designs")
+ category = list("Implants", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/cyberimp_nutriment_plus
@@ -386,7 +408,7 @@
construction_time = 50
materials = list(/datum/material/iron = 600, /datum/material/glass = 600, /datum/material/gold = 500, /datum/material/uranium = 750, /datum/material/copper = 200)
build_path = /obj/item/organ/cyberimp/chest/nutriment/plus
- category = list("Misc", "Medical Designs")
+ category = list("Implants", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/cyberimp_reviver
@@ -397,7 +419,7 @@
construction_time = 60
materials = list(/datum/material/iron = 800, /datum/material/glass = 800, /datum/material/gold = 300, /datum/material/uranium = 500, /datum/material/copper = 200)
build_path = /obj/item/organ/cyberimp/chest/reviver
- category = list("Misc", "Medical Designs")
+ category = list("Implants", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/cyberimp_thrusters
@@ -408,7 +430,7 @@
construction_time = 80
materials = list(/datum/material/iron = 4000, /datum/material/glass = 2000, /datum/material/silver = 1000, /datum/material/diamond = 1000, /datum/material/copper = 200)
build_path = /obj/item/organ/cyberimp/chest/thrusters
- category = list("Misc", "Medical Designs")
+ category = list("Implants", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/cyberimp_hydraulic_blade
@@ -419,7 +441,7 @@
construction_time = 250
materials = list(/datum/material/iron = 5000, /datum/material/glass = 5000, /datum/material/silver = 10000, /datum/material/diamond = 3000, /datum/material/titanium = 8000, /datum/material/bluespace = 2000, /datum/material/plasma = 5000)
build_path = /obj/item/organ/cyberimp/arm/hydraulic_blade
- category = list("Misc", "Medical Designs")
+ category = list("Cybernetics", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/////////////////////////////////////////
@@ -486,7 +508,7 @@
construction_time = 40
materials = list(/datum/material/iron = 500, /datum/material/glass = 500, /datum/material/copper = 200)
build_path = /obj/item/organ/liver/cybernetic
- category = list("Misc", "Medical Designs")
+ category = list("Cybernetics", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/cybernetic_liver_u
@@ -497,7 +519,7 @@
construction_time = 50
materials = list(/datum/material/iron = 500, /datum/material/glass = 500, /datum/material/copper = 200)
build_path = /obj/item/organ/liver/cybernetic/upgraded
- category = list("Misc", "Medical Designs")
+ category = list("Cybernetics", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/cybernetic_heart
@@ -508,7 +530,7 @@
construction_time = 40
materials = list(/datum/material/iron = 500, /datum/material/glass = 500, /datum/material/copper = 200)
build_path = /obj/item/organ/heart/cybernetic
- category = list("Misc", "Medical Designs")
+ category = list("Cybernetics", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/cybernetic_heart_u
@@ -519,7 +541,7 @@
construction_time = 50
materials = list(/datum/material/iron = 500, /datum/material/glass = 500, /datum/material/silver=500, /datum/material/copper = 300)
build_path = /obj/item/organ/heart/cybernetic/upgraded
- category = list("Misc", "Medical Designs")
+ category = list("Cybernetics", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/cybernetic_lungs
@@ -530,7 +552,7 @@
construction_time = 40
materials = list(/datum/material/iron = 500, /datum/material/glass = 500, /datum/material/copper = 200)
build_path = /obj/item/organ/lungs/cybernetic
- category = list("Misc", "Medical Designs")
+ category = list("Cybernetics", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/cybernetic_lungs_u
@@ -541,7 +563,7 @@
construction_time = 50
materials = list(/datum/material/iron = 500, /datum/material/glass = 500, /datum/material/silver = 500, /datum/material/copper = 300)
build_path = /obj/item/organ/lungs/cybernetic/upgraded
- category = list("Misc", "Medical Designs")
+ category = list("Cybernetics", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/wingpack
@@ -552,7 +574,7 @@
construction_time = 500
materials = list(/datum/material/iron = 10000, /datum/material/glass = 1000, /datum/material/uranium = 4000, /datum/material/silver = 100, /datum/material/titanium = 8000, /datum/material/plasma = 10000)
build_path = /obj/item/organ/wings/cybernetic
- category = list("Misc", "Medical Designs")
+ category = list("Cybernetics", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/datum/design/wingpack_alien
@@ -563,7 +585,7 @@
construction_time = 1000
materials = list(/datum/material/iron = 10000, /datum/material/glass = 1000, /datum/material/uranium = 500, /datum/material/silver = 100, /datum/material/titanium = 8000, /datum/material/bluespace = 2000, /datum/material/plasma = 5000)
build_path = /obj/item/organ/wings/cybernetic/ayy
- category = list("Misc", "Medical Designs")
+ category = list("Cybernetics", "Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
/////////////////////
diff --git a/code/modules/research/designs/misc_designs.dm b/code/modules/research/designs/misc_designs.dm
index 720822cd7251c..a66624151f3f5 100644
--- a/code/modules/research/designs/misc_designs.dm
+++ b/code/modules/research/designs/misc_designs.dm
@@ -67,36 +67,6 @@
//////////////////Misc///////////////////
/////////////////////////////////////////
-/datum/design/shuttle_speed_upgrade
- name = "Shuttle Route Optimisation Upgrade"
- desc = "A disk that allows for calculating shorter routes when inserted into a flight control console."
- id = "disk_shuttle_route"
- build_type = PROTOLATHE
- materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000)
- build_path = /obj/item/shuttle_route_optimisation
- category = list("Equipment")
- departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING
-
-/datum/design/shuttle_speed_upgrade_hyper
- name = "Shuttle Bluespace Hyperlane Optimisation Upgrade"
- desc = "A disk that allows for calculating shorter routes when inserted into a flight control console. This one abuses bluespace hyperlanes for increased efficiency."
- id = "disk_shuttle_route_hyper"
- build_type = PROTOLATHE
- materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000)
- build_path = /obj/item/shuttle_route_optimisation/hyperlane
- category = list("Equipment")
- departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING
-
-/datum/design/shuttle_speed_upgrade_void
- name = "Shuttle Voidspace Optimisation Upgrade"
- desc = "A disk that allows for calculating shorter routes when inserted into a flight control console. This one access voidspace for increased efficiency."
- id = "disk_shuttle_route_void"
- build_type = PROTOLATHE
- materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000)
- build_path = /obj/item/shuttle_route_optimisation/void
- category = list("Equipment")
- departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING
-
/datum/design/welding_mask
name = "Welding Gas Mask"
desc = "A gas mask with built in welding goggles and face shield. Looks like a skull, clearly designed by a nerd."
@@ -364,7 +334,6 @@
category = list("Equipment")
departmental_flags = DEPARTMENTAL_FLAG_SERVICE
-
/datum/design/holosignsec
name = "Security Holobarrier Projector"
desc = "A holographic projector that creates holographic security barriers."
@@ -491,3 +460,44 @@
build_path = /obj/item/construction/plumbing
category = list("Equipment")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
+
+/datum/design/glasses_prescription
+ name = "Prescription Glasses"
+ desc = "Made by Nerd. Co."
+ id = "glasses_prescription"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 500, /datum/material/glass = 2000)
+ build_path = /obj/item/clothing/glasses/regular
+ category = list("Equipment")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
+
+/////////////////////////////////////////
+/////////////////Tape////////////////////
+/////////////////////////////////////////
+
+/datum/design/sticky_tape
+ name = "Sticky Tape"
+ id = "sticky_tape"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/plastic = 500)
+ build_path = /obj/item/stack/sticky_tape
+ category = list("Equipment")
+ departmental_flags = DEPARTMENTAL_FLAG_SERVICE
+
+/datum/design/super_sticky_tape
+ name = "Super Sticky Tape"
+ id = "super_sticky_tape"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/plastic = 3000)
+ build_path = /obj/item/stack/sticky_tape/super
+ category = list("Equipment")
+ departmental_flags = DEPARTMENTAL_FLAG_SERVICE
+
+/datum/design/pointy_tape
+ name = "Pointy Tape"
+ id = "pointy_tape"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 1500, /datum/material/plastic = 1000)
+ build_path = /obj/item/stack/sticky_tape/pointy
+ category = list("Equipment")
+ departmental_flags = DEPARTMENTAL_FLAG_SERVICE
diff --git a/code/modules/research/designs/nanite_designs.dm b/code/modules/research/designs/nanite_designs.dm
index 87c64edc5f593..566fb5e5de933 100644
--- a/code/modules/research/designs/nanite_designs.dm
+++ b/code/modules/research/designs/nanite_designs.dm
@@ -244,13 +244,6 @@
program_type = /datum/nanite_program/conductive
category = list("Augmentation Nanites")
-/datum/design/nanites/adrenaline
- name = "Adrenaline Burst"
- desc = "The nanites cause a burst of adrenaline when triggered, waking the host from stuns and temporarily increasing their speed."
- id = "adrenaline_nanites"
- program_type = /datum/nanite_program/adrenaline
- category = list("Augmentation Nanites")
-
/datum/design/nanites/mindshield
name = "Mental Barrier"
desc = "The nanites form a protective membrane around the host's brain, shielding them from abnormal influences while they're active."
@@ -258,6 +251,13 @@
program_type = /datum/nanite_program/mindshield
category = list("Augmentation Nanites")
+/datum/design/nanites/adrenaline
+ name = "Adrenaline Burst"
+ desc = "The nanites cause a burst of adrenaline when triggered, waking the host from stuns and temporarily increasing their speed."
+ id = "adrenaline_nanites"
+ program_type = /datum/nanite_program/adrenaline
+ category = list("Augmentation Nanites")
+
////////////////////DEFECTIVE NANITES//////////////////////////////////////
/datum/design/nanites/glitch
@@ -389,6 +389,13 @@
program_type = /datum/nanite_program/comm/mind_control
category = list("Weaponized Nanites")
+/datum/design/nanites/haste
+ name = "Amphetamine Injection"
+ desc = "The nanites synthesize amphetamine when triggered, which temporarily increases the host's running speed."
+ id = "haste_nanites"
+ program_type = /datum/nanite_program/haste
+ category = list("Weaponized Nanites")
+
////////////////////SUPPRESSION NANITES//////////////////////////////////////
/datum/design/nanites/shock
diff --git a/code/modules/research/designs/smelting_designs.dm b/code/modules/research/designs/smelting_designs.dm
index 4bebd6e72ee49..791787c9871c3 100644
--- a/code/modules/research/designs/smelting_designs.dm
+++ b/code/modules/research/designs/smelting_designs.dm
@@ -1,7 +1,7 @@
///////SMELTABLE ALLOYS///////
/datum/design/plasteel_alloy
- name = "Plasma + Iron alloy"
+ name = "Plasteel (Plasma + Iron alloy)"
id = "plasteel"
build_type = SMELTER | PROTOLATHE
materials = list(/datum/material/iron = MINERAL_MATERIAL_AMOUNT, /datum/material/plasma = MINERAL_MATERIAL_AMOUNT)
@@ -12,7 +12,7 @@
/datum/design/plastitanium_alloy
- name = "Plasma + Titanium alloy"
+ name = "Plastitanium (Plasma + Titanium alloy)"
id = "plastitanium"
build_type = SMELTER | PROTOLATHE
materials = list(/datum/material/titanium = MINERAL_MATERIAL_AMOUNT, /datum/material/plasma = MINERAL_MATERIAL_AMOUNT)
@@ -22,7 +22,7 @@
maxstack = 50
/datum/design/plaglass_alloy
- name = "Plasma + Glass alloy"
+ name = "Plasma Glass (Plasma + Glass alloy)"
id = "plasmaglass"
build_type = SMELTER | PROTOLATHE
materials = list(/datum/material/plasma = MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass = MINERAL_MATERIAL_AMOUNT)
@@ -32,7 +32,7 @@
maxstack = 50
/datum/design/plasmarglass_alloy
- name = "Plasma + Iron + Glass alloy"
+ name = "Plasma Reinforced Glass (Plasma + Iron + Glass alloy)"
id = "plasmareinforcedglass"
build_type = SMELTER | PROTOLATHE
materials = list(/datum/material/plasma = MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/iron = MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass = MINERAL_MATERIAL_AMOUNT)
@@ -42,7 +42,7 @@
maxstack = 50
/datum/design/titaniumglass_alloy
- name = "Titanium + Glass alloy"
+ name = "Titanium Glass (Titanium + Glass alloy)"
id = "titaniumglass"
build_type = SMELTER | PROTOLATHE
materials = list(/datum/material/titanium = MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass = MINERAL_MATERIAL_AMOUNT)
@@ -52,7 +52,7 @@
maxstack = 50
/datum/design/plastitaniumglass_alloy
- name = "Plasma + Titanium + Glass alloy"
+ name = "Plastitanium glass (Plasma + Titanium + Glass alloy)"
id = "plastitaniumglass"
build_type = SMELTER | PROTOLATHE
materials = list(/datum/material/plasma = MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/titanium = MINERAL_MATERIAL_AMOUNT * 0.5, /datum/material/glass = MINERAL_MATERIAL_AMOUNT)
diff --git a/code/modules/research/designs/tool_designs.dm b/code/modules/research/designs/tool_designs.dm
index 86128c6ea151a..1dc5e11cbcd36 100644
--- a/code/modules/research/designs/tool_designs.dm
+++ b/code/modules/research/designs/tool_designs.dm
@@ -5,19 +5,19 @@
/datum/design/handdrill
name = "Hand Drill"
- desc = "A small electric hand drill with an interchangeable screwdriver and bolt bit"
+ desc = "A small electric hand drill with an interchangeable screwdriver and bolt bit."
id = "handdrill"
build_type = PROTOLATHE
materials = list(/datum/material/iron = 3500, /datum/material/silver = 1500, /datum/material/titanium = 2500)
- build_path = /obj/item/screwdriver/power
+ build_path = /obj/item/powertool/hand_drill
category = list("Tool Designs")
departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING
/datum/design/jawsoflife
name = "Jaws of Life"
- desc = "A small, compact Jaws of Life with an interchangeable pry jaws and cutting jaws"
+ desc = "A small, compact Jaws of Life with an interchangeable pry jaws and cutting jaws."
id = "jawsoflife" // added one more requirment since the Jaws of Life are a bit OP
- build_path = /obj/item/crowbar/power
+ build_path = /obj/item/powertool/jaws_of_life
build_type = PROTOLATHE
materials = list(/datum/material/iron = 4500, /datum/material/silver = 2500, /datum/material/titanium = 3500)
category = list("Tool Designs")
@@ -25,7 +25,7 @@
/datum/design/shuttlecreator
name = "Rapid Shuttle Designator"
- desc = "An advanced device capable of defining areas for use in the creation of shuttles"
+ desc = "An advanced device capable of defining areas for use in the creation of shuttles."
id = "shuttle_creator"
build_path = /obj/item/shuttle_creator
build_type = PROTOLATHE
@@ -43,6 +43,16 @@
category = list("Tool Designs")
departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING
+/datum/design/ranged_analyzer
+ name = "Long-range Analyzer"
+ desc = "A new advanced atmospheric analyzer design, capable of performing scans at long range."
+ id = "ranged_analyzer"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 400, /datum/material/glass = 1000, /datum/material/uranium = 800, /datum/material/gold = 200, /datum/material/plastic = 200)
+ build_path = /obj/item/analyzer/ranged
+ category = list("Tool Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING
+
/datum/design/rpd
name = "Rapid Pipe Dispenser (RPD)"
id = "rpd_loaded"
@@ -220,3 +230,12 @@
materials = list(/datum/material/iron = 2000, /datum/material/silver = 1500, /datum/material/plasma = 500, /datum/material/titanium = 1500)
category = list("Tool Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
+
+/datum/design/discoveryscanner
+ name = "Discovery Scanner"
+ id = "discovery_scanner"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 200, /datum/material/glass = 50)
+ build_path = /obj/item/discovery_scanner
+ category = list("Tool Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
diff --git a/code/modules/research/designs/weapon_designs.dm b/code/modules/research/designs/weapon_designs.dm
index 1ed7af08232a5..3dd3e7eca9a6e 100644
--- a/code/modules/research/designs/weapon_designs.dm
+++ b/code/modules/research/designs/weapon_designs.dm
@@ -327,6 +327,16 @@
category = list("Ammo")
departmental_flags = DEPARTMENTAL_FLAG_SECURITY
+/datum/design/mag_oldsmg/rubber
+ name = "WT-550 Rubber Auto Gun Magazine (4.6x30mm Rubber)"
+ desc = "A 20 round magazine for the out of date security WT-550 Auto Rifle"
+ id = "mag_oldsmg_rubber"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 3000)
+ build_path = /obj/item/ammo_box/magazine/wt550m9/rubber
+ category = list("Ammo")
+ departmental_flags = DEPARTMENTAL_FLAG_SECURITY
+
/datum/design/mag_oldsmg/ap_mag
name = "WT-550 Auto Gun Armour Piercing Magazine (4.6x30mm AP)"
desc = "A 20 round armour piercing magazine for the out of date security WT-550 Auto Rifle"
@@ -401,7 +411,7 @@
materials = list(/datum/material/iron = 3500)
build_path = /obj/item/ammo_casing/shotgun/dart/noreact
category = list("Ammo")
- departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
+ departmental_flags = DEPARTMENTAL_FLAG_SECURITY
/datum/design/flashbulb
name = "Security Flashbulb"
diff --git a/code/modules/research/designs/wiremod_designs.dm b/code/modules/research/designs/wiremod_designs.dm
new file mode 100644
index 0000000000000..c6511c00d44ec
--- /dev/null
+++ b/code/modules/research/designs/wiremod_designs.dm
@@ -0,0 +1,391 @@
+/datum/design/integrated_circuit
+ name = "Integrated Circuit"
+ desc = "The foundation of all circuits. All Circuitry go onto this."
+ id = "integrated_circuit"
+ build_path = /obj/item/integrated_circuit
+ build_type = IMPRINTER | COMPONENT_PRINTER
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_CORE)
+ materials = list(/datum/material/glass = 1000, /datum/material/iron = 1000, /datum/material/copper = 500)
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/circuit_multitool
+ name = "Circuit Multitool"
+ desc = "A circuit multitool to mark entities and load them into."
+ id = "circuit_multitool"
+ build_path = /obj/item/multitool/circuit
+ build_type = PROTOLATHE | COMPONENT_PRINTER
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_CORE)
+ materials = list(/datum/material/glass = 1000, /datum/material/iron = 1000, /datum/material/copper = 500)
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/usb_cable
+ name = "USB Cable"
+ desc = "A cable that allows certain shells to connect to nearby computers and machines."
+ id = "usb_cable"
+ build_path = /obj/item/usb_cable
+ build_type = PROTOLATHE | COMPONENT_PRINTER
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_CORE)
+ // Yes, it would make sense to make them take plastic, but then less people would make them, and I think they're cool
+ materials = list(/datum/material/iron = 1000, /datum/material/copper = 1500)
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/component
+ name = "Component ( NULL ENTRY )"
+ desc = "A component that goes into an integrated circuit."
+ build_type = IMPRINTER | COMPONENT_PRINTER
+ materials = list(/datum/material/glass = 500, /datum/material/copper = 1500)
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
+ category = list(WIREMOD_CIRCUITRY)
+
+/datum/design/component/New()
+ . = ..()
+ if(build_path)
+ var/obj/item/circuit_component/component_path = build_path
+ desc = initial(component_path.display_desc)
+
+/datum/design/component/arbitrary_input_amount/arithmetic
+ name = "Arithmetic Component"
+ id = "comp_arithmetic"
+ build_path = /obj/item/circuit_component/arbitrary_input_amount/arithmetic
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_MATH_COMPONENTS)
+
+/datum/design/component/arbitrary_input_amount/bitwise
+ name = "Bitwise Component"
+ id = "comp_bitwise"
+ build_path = /obj/item/circuit_component/arbitrary_input_amount/bitwise
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_MATH_COMPONENTS)
+
+/datum/design/component/bitflag
+ name = "Bitflag Component"
+ id = "comp_bitflag"
+ build_path = /obj/item/circuit_component/compare/bitflag
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_MATH_COMPONENTS, WIREMOD_LOGIC_COMPONENTS)
+
+/datum/design/component/clock
+ name = "Clock Component"
+ id = "comp_clock"
+ build_path = /obj/item/circuit_component/clock
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_TIME_COMPONENTS)
+
+/datum/design/component/comparison
+ name = "Comparison Component"
+ id = "comp_comparison"
+ build_path = /obj/item/circuit_component/compare/comparison
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_LOGIC_COMPONENTS)
+
+/datum/design/component/logic
+ name = "Logic Component"
+ id = "comp_logic"
+ build_path = /obj/item/circuit_component/compare/logic
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_LOGIC_COMPONENTS)
+
+/datum/design/component/delay
+ name = "Delay Component"
+ id = "comp_delay"
+ build_path = /obj/item/circuit_component/delay
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_TIME_COMPONENTS)
+
+/datum/design/component/index
+ name = "Index Component"
+ id = "comp_index"
+ build_path = /obj/item/circuit_component/indexer/index
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_LIST_COMPONENTS)
+
+/datum/design/component/write
+ name = "Write Component"
+ id = "comp_write"
+ build_path = /obj/item/circuit_component/indexer/write
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_LIST_COMPONENTS)
+
+/datum/design/component/append
+ name = "Append Component"
+ id = "comp_append"
+ build_path = /obj/item/circuit_component/append
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_LIST_COMPONENTS)
+
+/datum/design/component/pop
+ name = "Pop Component"
+ id = "comp_pop"
+ build_path = /obj/item/circuit_component/pop
+ category = list(WIREMOD_CIRCUITRY,WIREMOD_LIST_COMPONENTS)
+
+/datum/design/component/length
+ name = "Length Component"
+ id = "comp_length"
+ build_path = /obj/item/circuit_component/length
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_LIST_COMPONENTS, WIREMOD_STRING_COMPONENTS)
+
+/datum/design/component/light
+ name = "Light Component"
+ id = "comp_light"
+ build_path = /obj/item/circuit_component/light
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_OUTPUT_COMPONENTS)
+
+/datum/design/component/list_constructor
+ name = "List Constructor"
+ id = "comp_list_constructor"
+ build_path = /obj/item/circuit_component/arbitrary_input_amount/list_constructor
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_LIST_COMPONENTS)
+
+/datum/design/component/list_length_constructor
+ name = "List Length Constructor"
+ id = "comp_list_length_constructor"
+ build_path = /obj/item/circuit_component/list_length_constructor
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_LIST_COMPONENTS)
+
+/datum/design/component/not
+ name = "Not Component"
+ id = "comp_not"
+ build_path = /obj/item/circuit_component/not
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_LOGIC_COMPONENTS)
+
+/datum/design/component/ram
+ name = "RAM Component"
+ id = "comp_ram"
+ build_path = /obj/item/circuit_component/ram
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_MEMORY_COMPONENTS)
+
+/datum/design/component/random
+ name = "Random Component"
+ id = "comp_random"
+ build_path = /obj/item/circuit_component/random
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_MATH_COMPONENTS, WIREMOD_LOGIC_COMPONENTS)
+
+/datum/design/component/round
+ name = "Round Component"
+ id = "comp_round"
+ build_path = /obj/item/circuit_component/round
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_MATH_COMPONENTS)
+
+/datum/design/component/species
+ name = "Get Species Component"
+ id = "comp_species"
+ build_path = /obj/item/circuit_component/species
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_LOGIC_COMPONENTS, WIREMOD_INPUT_COMPONENTS)
+
+/datum/design/component/speech
+ name = "Speech Component"
+ id = "comp_speech"
+ build_path = /obj/item/circuit_component/speech
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_OUTPUT_COMPONENTS)
+
+/datum/design/component/tostring
+ name = "To String Component"
+ id = "comp_tostring"
+ build_path = /obj/item/circuit_component/tostring
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_STRING_COMPONENTS, WIREMOD_CONVERSION_COMPONENTS)
+
+/datum/design/component/trig
+ name = "Trigonometry Component"
+ id = "comp_trig"
+ build_path = /obj/item/circuit_component/trig/trig
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_MATH_COMPONENTS)
+
+/datum/design/component/adv_trig
+ name = "Advanced Trigonometry Component"
+ id = "comp_adv_trig"
+ build_path = /obj/item/circuit_component/trig/adv_trig
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_MATH_COMPONENTS)
+
+/datum/design/component/hyper_trig
+ name = "Hyperbolic Trigonometry Component"
+ id = "comp_hyper_trig"
+ build_path = /obj/item/circuit_component/trig/hyper_trig
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_MATH_COMPONENTS)
+
+/datum/design/component/typecheck
+ name = "Typecheck Component"
+ id = "comp_typecheck"
+ build_path = /obj/item/circuit_component/compare/typecheck
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_LOGIC_COMPONENTS)
+
+/datum/design/component/concat
+ name = "Concatenation Component"
+ id = "comp_concat"
+ build_path = /obj/item/circuit_component/concat
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_STRING_COMPONENTS)
+
+/datum/design/component/textcase
+ name = "Textcase Component"
+ id = "comp_textcase"
+ build_path = /obj/item/circuit_component/textcase
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_STRING_COMPONENTS)
+
+/datum/design/component/hear
+ name = "Voice Activator Component"
+ id = "comp_hear"
+ build_path = /obj/item/circuit_component/hear
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_INPUT_COMPONENTS)
+
+/datum/design/component/contains
+ name = "String Contains Component"
+ id = "comp_string_contains"
+ build_path = /obj/item/circuit_component/compare/contains
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_STRING_COMPONENTS)
+
+/datum/design/component/self
+ name = "Self Component"
+ id = "comp_self"
+ build_path = /obj/item/circuit_component/self
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_REFERENCE_COMPONENTS)
+
+/datum/design/component/radio
+ name = "Radio Component"
+ id = "comp_radio"
+ build_path = /obj/item/circuit_component/radio
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_OUTPUT_COMPONENTS)
+
+/datum/design/component/gps
+ name = "GPS Component"
+ id = "comp_gps"
+ build_path = /obj/item/circuit_component/gps
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_INPUT_COMPONENTS)
+
+/datum/design/component/direction
+ name = "Direction Component"
+ id = "comp_direction"
+ build_path = /obj/item/circuit_component/direction
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_INPUT_COMPONENTS)
+
+/datum/design/component/health
+ name = "Health Component"
+ id = "comp_health"
+ build_path = /obj/item/circuit_component/health
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_INPUT_COMPONENTS)
+
+/datum/design/component/combiner
+ name = "Combiner Component"
+ id = "comp_combiner"
+ build_path = /obj/item/circuit_component/combiner
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_LOGIC_COMPONENTS)
+
+/datum/design/component/pull
+ name = "Pull Component"
+ id = "comp_pull"
+ build_path = /obj/item/circuit_component/pull
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_OUTPUT_COMPONENTS)
+
+/datum/design/component/soundemitter
+ name = "Sound Emitter Component"
+ id = "comp_soundemitter"
+ build_path = /obj/item/circuit_component/soundemitter
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_OUTPUT_COMPONENTS)
+
+/datum/design/component/mmi
+ name = "MMI Component"
+ id = "comp_mmi"
+ build_path = /obj/item/circuit_component/mmi
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_INPUT_COMPONENTS)
+
+/datum/design/component/multiplexer
+ name = "Multiplexer Component"
+ id = "comp_multiplexer"
+ build_path = /obj/item/circuit_component/multiplexer
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_LOGIC_COMPONENTS)
+
+/datum/design/component/get_column
+ name = "Get Column Component"
+ id = "comp_get_column"
+ build_path = /obj/item/circuit_component/get_column
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_LIST_COMPONENTS)
+
+/datum/design/component/index_table
+ name = "Index Table Component"
+ id = "comp_index_table"
+ build_path = /obj/item/circuit_component/index_table
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_LIST_COMPONENTS)
+
+/datum/design/component/concat_list
+ name = "Concatenate List Component"
+ id = "comp_concat_list"
+ build_path = /obj/item/circuit_component/concat_list
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_LIST_COMPONENTS)
+
+/datum/design/component/select_query
+ name = "Select Query Component"
+ id = "comp_select_query"
+ build_path = /obj/item/circuit_component/select
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_LIST_COMPONENTS, WIREMOD_LOGIC_COMPONENTS)
+
+/datum/design/compact_remote_shell
+ name = "Compact Remote Shell"
+ desc = "A handheld shell with one big button."
+ id = "compact_remote_shell"
+ build_path = /obj/item/compact_remote
+ materials = list(/datum/material/glass = 2000, /datum/material/iron = 5000)
+ build_type = PROTOLATHE | COMPONENT_PRINTER
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_SHELLS)
+
+/datum/design/controller_shell
+ name = "Controller Shell"
+ desc = "A handheld shell with several buttons."
+ id = "controller_shell"
+ build_path = /obj/item/controller
+ build_type = PROTOLATHE | COMPONENT_PRINTER
+ materials = list(/datum/material/glass = 2000, /datum/material/iron = 7000)
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_SHELLS)
+
+/datum/design/scanner_shell
+ name = "Scanner Shell"
+ desc = "A handheld shell with a scanner."
+ id = "scanner_shell"
+ build_path = /obj/item/scanner
+ build_type = PROTOLATHE | COMPONENT_PRINTER
+ materials = list(/datum/material/glass = 4000, /datum/material/iron = 5000)
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_SHELLS)
+
+/datum/design/bot_shell
+ name = "Bot Shell"
+ desc = "An immobile shell that can store more components. Has a USB port to be able to connect to computers and machines."
+ id = "bot_shell"
+ build_path = /obj/item/shell/bot
+ build_type = PROTOLATHE | COMPONENT_PRINTER
+ materials = list(/datum/material/glass = 2000, /datum/material/iron = 10000)
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_SHELLS)
+
+/datum/design/money_bot_shell
+ name = "Money Bot Shell"
+ desc = "An immobile shell that is similar to a regular bot shell, but accepts monetary inputs and can also dispense money."
+ id = "money_bot_shell"
+ build_path = /obj/item/shell/money_bot
+ build_type = PROTOLATHE | COMPONENT_PRINTER
+ materials = list(/datum/material/glass = 2000, /datum/material/iron = 10000, /datum/material/gold = 50)
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_SHELLS)
+
+/datum/design/drone_shell
+ name = "Drone Shell"
+ desc = "A shell with the ability to move itself around."
+ id = "drone_shell"
+ build_path = /obj/item/shell/drone
+ build_type = PROTOLATHE | COMPONENT_PRINTER
+ materials = list(
+ /datum/material/glass = 2000,
+ /datum/material/iron = 11000,
+ /datum/material/gold = 500,
+ )
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_SHELLS)
+
+/datum/design/server_shell
+ name = "Server Shell"
+ desc = "A very large shell that cannot be moved around. Stores the most components."
+ id = "server_shell"
+ materials = list(
+ /datum/material/glass = 5000,
+ /datum/material/iron = 15000,
+ /datum/material/gold = 1500,
+ )
+ build_path = /obj/item/shell/server
+ build_type = PROTOLATHE | COMPONENT_PRINTER
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_SHELLS)
+
+/datum/design/airlock_shell
+ name = "Airlock Shell"
+ desc = "A door shell that cannot be moved around when assembled."
+ id = "door_shell"
+ materials = list(
+ /datum/material/glass = 5000,
+ /datum/material/iron = 15000,
+ )
+ build_path = /obj/item/shell/airlock
+ build_type = PROTOLATHE | COMPONENT_PRINTER
+ category = list(WIREMOD_CIRCUITRY, WIREMOD_SHELLS)
diff --git a/code/modules/research/destructive_analyzer.dm b/code/modules/research/destructive_analyzer.dm
index d409c5e435bac..42700bfa9e92a 100644
--- a/code/modules/research/destructive_analyzer.dm
+++ b/code/modules/research/destructive_analyzer.dm
@@ -71,6 +71,9 @@ Note: Must be placed within 3 tiles of the R&D Console
/obj/machinery/rnd/destructive_analyzer/proc/destroy_item(obj/item/thing, innermode = FALSE)
if(QDELETED(thing) || QDELETED(src) || QDELETED(linked_console))
return FALSE
+ if(thing.resistance_flags & INDESTRUCTIBLE)
+ playsound(src, 'sound/machines/nuke/angry_beep.ogg', 50, FALSE)
+ return FALSE
if(!innermode)
flick("d_analyzer_process", src)
busy = TRUE
diff --git a/code/modules/research/experimentor.dm b/code/modules/research/experimentor.dm
index 79e97a8bccfe4..da8a69c2b5809 100644
--- a/code/modules/research/experimentor.dm
+++ b/code/modules/research/experimentor.dm
@@ -97,7 +97,7 @@
/obj/machinery/rnd/experimentor/examine(mob/user)
. = ..()
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Malfunction probability reduced by [badThingCoeff]%. Cooldown interval between experiments at [resetTime*0.1] seconds."
+ . += "The status display reads: Malfunction probability reduced by [badThingCoeff]%. Cooldown interval between experiments at [resetTime*0.1] seconds."
/obj/machinery/rnd/experimentor/proc/checkCircumstances(obj/item/O)
//snowflake check to only take "made" bombs
@@ -254,7 +254,7 @@
badThingCoeff++
else if(prob(EFFECT_PROB_VERYLOW-badThingCoeff))
visible_message("[src] malfunctions and destroys [exp_on], lashing its arms out at nearby people!")
- for(var/mob/living/m in oview(1, src))
+ for(var/mob/living/m in ohearers(1, src))
m.apply_damage(15, BRUTE, pick(BODY_ZONE_HEAD,BODY_ZONE_CHEST,BODY_ZONE_PRECISE_GROIN))
investigate_log("Experimentor dealt minor brute to [m].", INVESTIGATE_EXPERIMENTOR)
ejectItem(TRUE)
@@ -263,7 +263,7 @@
exp = SCANTYPE_OBLITERATE
else if(prob(EFFECT_PROB_MEDIUM-badThingCoeff))
visible_message("[src] malfunctions, throwing the [exp_on]!")
- var/mob/living/target = locate(/mob/living) in oview(7,src)
+ var/mob/living/target = locate(/mob/living) in ohearers(7,src)
if(target)
var/obj/item/throwing = loaded_item
investigate_log("Experimentor has thrown [loaded_item] at [key_name(target)]", INVESTIGATE_EXPERIMENTOR)
@@ -356,7 +356,7 @@
investigate_log("Experimentor has made a cup of [chosenchem] coffee.", INVESTIGATE_EXPERIMENTOR)
else if(prob(EFFECT_PROB_VERYLOW-badThingCoeff))
var/turf/start = get_turf(src)
- var/mob/M = locate(/mob/living) in view(src, 3)
+ var/mob/M = locate(/mob/living) in view(3, src)
var/turf/MT = get_turf(M)
if(MT)
visible_message("[src] dangerously overheats, launching a flaming fuel orb!")
@@ -372,21 +372,14 @@
else if(prob(EFFECT_PROB_MEDIUM-badThingCoeff))
visible_message("[src] malfunctions, melting [exp_on] and leaking hot air!")
var/datum/gas_mixture/env = loc.return_air()
- var/transfer_moles = 0.25 * env.total_moles()
- var/datum/gas_mixture/removed = env.remove(transfer_moles)
- if(removed)
- var/heat_capacity = removed.heat_capacity()
- if(heat_capacity == 0 || heat_capacity == null)
- heat_capacity = 1
- removed.set_temperature(min((removed.return_temperature()*heat_capacity + 100000)/heat_capacity, 1000))
- env.merge(removed)
+ env.adjust_heat(100000)
air_update_turf()
investigate_log("Experimentor has released hot air.", INVESTIGATE_EXPERIMENTOR)
ejectItem(TRUE)
else if(prob(EFFECT_PROB_MEDIUM-badThingCoeff))
visible_message("[src] malfunctions, activating its emergency coolant systems!")
throwSmoke(loc)
- for(var/mob/living/m in oview(1, src))
+ for(var/mob/living/m in ohearers(1, src))
m.apply_damage(5, BURN, pick(BODY_ZONE_HEAD,BODY_ZONE_CHEST,BODY_ZONE_PRECISE_GROIN))
investigate_log("Experimentor has dealt minor burn damage to [key_name(m)]", INVESTIGATE_EXPERIMENTOR)
ejectItem()
@@ -418,14 +411,7 @@
else if(prob(EFFECT_PROB_LOW-badThingCoeff))
visible_message("[src] malfunctions, shattering [exp_on] and leaking cold air!")
var/datum/gas_mixture/env = loc.return_air()
- var/transfer_moles = 0.25 * env.total_moles()
- var/datum/gas_mixture/removed = env.remove(transfer_moles)
- if(removed)
- var/heat_capacity = removed.heat_capacity()
- if(heat_capacity == 0 || heat_capacity == null)
- heat_capacity = 1
- removed.set_temperature((removed.return_temperature()*heat_capacity - 75000)/heat_capacity)
- env.merge(removed)
+ env.adjust_heat(-75000)
air_update_turf()
investigate_log("Experimentor has released cold air.", INVESTIGATE_EXPERIMENTOR)
ejectItem(TRUE)
@@ -474,7 +460,7 @@
visible_message("[src] scans the [exp_on], revealing its true nature!")
playsound(src, 'sound/effects/supermatter.ogg', 50, 3, -1)
var/obj/item/relic/R = loaded_item
- R.reveal()
+ R.reveal(linked_console.stored_research)
investigate_log("Experimentor has revealed a relic with [R.realProc] effect.", INVESTIGATE_EXPERIMENTOR)
ejectItem()
@@ -498,7 +484,7 @@
ejectItem(TRUE)
if(globalMalf > 36 && globalMalf < 50)
visible_message("Experimentor draws the life essence of those nearby!")
- for(var/mob/living/m in view(4,src))
+ for(var/mob/living/m in hearers(4,src))
to_chat(m, "You feel your flesh being torn from you, mists of blood drifting to [src]!")
m.apply_damage(50, BRUTE, BODY_ZONE_CHEST)
investigate_log("Experimentor has taken 50 brute a blood sacrifice from [m]", INVESTIGATE_EXPERIMENTOR)
@@ -567,13 +553,15 @@
realName = "[pick("broken","twisted","spun","improved","silly","regular","badly made")] [pick("device","object","toy","illegal tech","weapon")]"
-/obj/item/relic/proc/reveal()
+/obj/item/relic/proc/reveal(datum/techweb/techweb)
if(revealed) //Re-rolling your relics seems a bit overpowered, yes?
return
revealed = TRUE
name = realName
cooldownMax = rand(60,300)
realProc = pick("teleport","explode","rapidDupe","petSpray","flash","clean","corgicannon")
+ //Give science research
+ techweb.add_point_type(TECHWEB_POINT_TYPE_DISCOVERY, 2000)
/obj/item/relic/attack_self(mob/user)
if(revealed)
diff --git a/code/modules/research/machinery/circuit_imprinter.dm b/code/modules/research/machinery/circuit_imprinter.dm
index 8415564f6d098..d047f0d506e27 100644
--- a/code/modules/research/machinery/circuit_imprinter.dm
+++ b/code/modules/research/machinery/circuit_imprinter.dm
@@ -15,7 +15,8 @@
"Research Machinery",
"Misc. Machinery",
"Computer Parts",
- "Shuttle Machinery"
+ "Shuttle Machinery",
+ "Circuitry"
)
production_animation = "circuit_imprinter_ani"
allowed_buildtypes = IMPRINTER
diff --git a/code/modules/research/machinery/protolathe.dm b/code/modules/research/machinery/protolathe.dm
index 684f27ccadfb3..841b7701de401 100644
--- a/code/modules/research/machinery/protolathe.dm
+++ b/code/modules/research/machinery/protolathe.dm
@@ -15,7 +15,8 @@
"Weapons",
"Ammo",
"Firing Pins",
- "Computer Parts"
+ "Computer Parts",
+ "Circuitry"
)
production_animation = "protolathe_n"
allowed_buildtypes = PROTOLATHE
diff --git a/code/modules/research/machinery/techfab.dm b/code/modules/research/machinery/techfab.dm
index 885f27c2cb5c8..e7bd9198eaa37 100644
--- a/code/modules/research/machinery/techfab.dm
+++ b/code/modules/research/machinery/techfab.dm
@@ -26,7 +26,8 @@
"Subspace Telecomms",
"Research Machinery",
"Misc. Machinery",
- "Computer Parts"
+ "Computer Parts",
+ "Circuitry"
)
console_link = FALSE
production_animation = "protolathe_n"
diff --git a/code/modules/research/nanites/extra_settings/number.dm b/code/modules/research/nanites/extra_settings/number.dm
index be7775c0d2ef2..9aff7da80c283 100644
--- a/code/modules/research/nanites/extra_settings/number.dm
+++ b/code/modules/research/nanites/extra_settings/number.dm
@@ -14,7 +14,7 @@
/datum/nanite_extra_setting/number/set_value(value)
if(istext(value))
value = text2num(value)
- if(!value || !isnum_safe(value))
+ if(!isnum_safe(value))
return
src.value = clamp(value, min, max)
diff --git a/code/modules/research/nanites/nanite_chamber.dm b/code/modules/research/nanites/nanite_chamber.dm
index 09193087fae7e..ece40e19773f6 100644
--- a/code/modules/research/nanites/nanite_chamber.dm
+++ b/code/modules/research/nanites/nanite_chamber.dm
@@ -8,10 +8,9 @@
use_power = IDLE_POWER_USE
anchored = TRUE
density = TRUE
- idle_power_usage = 50
- active_power_usage = 300
+ idle_power_usage = 300
+ active_power_usage = 1200
- var/obj/machinery/computer/nanite_chamber_control/console
var/locked = FALSE
var/breakout_time = 1200
var/scan_level
diff --git a/code/modules/research/nanites/nanite_chamber_computer.dm b/code/modules/research/nanites/nanite_chamber_computer.dm
index 0aa23f93dd0e5..65d4c576bc4b6 100644
--- a/code/modules/research/nanites/nanite_chamber_computer.dm
+++ b/code/modules/research/nanites/nanite_chamber_computer.dm
@@ -2,7 +2,6 @@
name = "nanite chamber control console"
desc = "Controls a connected nanite chamber. Can inoculate nanites, load programs, and analyze existing nanite swarms."
var/obj/machinery/nanite_chamber/chamber
- var/obj/item/disk/nanite_program/disk
icon_screen = "nanite_chamber_control"
circuit = /obj/item/circuitboard/computer/nanite_chamber_control
@@ -17,8 +16,7 @@
var/C = locate(/obj/machinery/nanite_chamber, get_step(src, direction))
if(C)
var/obj/machinery/nanite_chamber/NC = C
- chamber = NC
- NC.console = src
+ set_connected_chamber(NC)
/obj/machinery/computer/nanite_chamber_control/interact()
if(!chamber)
@@ -34,6 +32,7 @@
if(!ui)
ui = new(user, src, "NaniteChamberControl")
ui.open()
+ ui.set_autoupdate(TRUE)
/obj/machinery/computer/nanite_chamber_control/ui_data()
var/list/data = list()
@@ -48,7 +47,7 @@
var/mob/living/L = chamber.occupant
- if(!(MOB_ORGANIC in L.mob_biotypes) && !(MOB_UNDEAD in L.mob_biotypes))
+ if(!(MOB_ORGANIC in L.mob_biotypes) && !(MOB_UNDEAD in L.mob_biotypes) && !HAS_TRAIT(L, TRAIT_NANITECOMPATIBLE))
data["status_msg"] = "Occupant not compatible with nanites."
return data
@@ -102,3 +101,15 @@
log_combat(usr, chamber.occupant, "injected", null, "with nanites via [src]")
chamber.occupant.investigate_log("was injected with nanites by [key_name(usr)] via [src] at [AREACOORD(src)].", INVESTIGATE_NANITES)
. = TRUE
+
+/obj/machinery/computer/nanite_chamber_control/proc/set_connected_chamber(new_chamber)
+ if(chamber)
+ UnregisterSignal(chamber, COMSIG_PARENT_QDELETING)
+ chamber = new_chamber
+ ui_update()
+ if(chamber)
+ RegisterSignal(chamber, COMSIG_PARENT_QDELETING, .proc/react_to_chamber_del)
+
+/obj/machinery/computer/nanite_chamber_control/proc/react_to_chamber_del(datum/source)
+ SIGNAL_HANDLER
+ set_connected_chamber(null)
diff --git a/code/modules/research/nanites/nanite_cloud_controller.dm b/code/modules/research/nanites/nanite_cloud_controller.dm
index 07620d975f01a..85afcef9a5fce 100644
--- a/code/modules/research/nanites/nanite_cloud_controller.dm
+++ b/code/modules/research/nanites/nanite_cloud_controller.dm
@@ -26,6 +26,7 @@
if(disk)
eject(user)
disk = N
+ ui_update()
else
..()
@@ -41,6 +42,7 @@
if(!istype(user) || !Adjacent(user) ||!user.put_in_active_hand(disk))
disk.forceMove(drop_location())
disk = null
+ ui_update()
/obj/machinery/computer/nanite_cloud_controller/proc/get_backup(cloud_id)
for(var/I in cloud_backups)
@@ -58,7 +60,7 @@
backup.cloud_id = cloud_id
backup.nanites = cloud_copy
investigate_log("[key_name(user)] created a new nanite cloud backup with id #[cloud_id]", INVESTIGATE_NANITES)
-
+ ui_update()
/obj/machinery/computer/nanite_cloud_controller/ui_state(mob/user)
return GLOB.default_state
@@ -106,6 +108,7 @@
data["disk"] = disk_data
else
data["has_disk"] = FALSE
+ data["disk"] = null
data["new_backup_id"] = new_backup_id
@@ -180,20 +183,21 @@
if("update_new_backup_value")
var/backup_value = text2num(params["value"])
new_backup_id = backup_value
+ . = TRUE
if("create_backup")
var/cloud_id = new_backup_id
if(!isnull(cloud_id))
playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE)
cloud_id = clamp(round(cloud_id, 1),1,100)
generate_backup(cloud_id, usr)
- . = TRUE
+ . = TRUE
if("delete_backup")
var/datum/nanite_cloud_backup/backup = get_backup(current_view)
if(backup)
playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE)
qdel(backup)
investigate_log("[key_name(usr)] deleted the nanite cloud backup #[current_view]", INVESTIGATE_NANITES)
- . = TRUE
+ . = TRUE
if("upload_program")
if(disk && disk.program)
var/datum/nanite_cloud_backup/backup = get_backup(current_view)
@@ -202,7 +206,7 @@
var/datum/component/nanites/nanites = backup.nanites
nanites.add_program(null, disk.program.copy())
investigate_log("[key_name(usr)] uploaded program [disk.program.name] to cloud #[current_view]", INVESTIGATE_NANITES)
- . = TRUE
+ . = TRUE
if("remove_program")
var/datum/nanite_cloud_backup/backup = get_backup(current_view)
if(backup)
@@ -211,7 +215,7 @@
var/datum/nanite_program/P = nanites.programs[text2num(params["program_id"])]
investigate_log("[key_name(usr)] deleted program [P.name] from cloud #[current_view]", INVESTIGATE_NANITES)
qdel(P)
- . = TRUE
+ . = TRUE
if("add_rule")
if(disk && disk.program && istype(disk.program, /datum/nanite_program/sensor))
var/datum/nanite_program/sensor/rule_template = disk.program
@@ -225,7 +229,7 @@
var/datum/nanite_rule/rule = rule_template.make_rule(P)
investigate_log("[key_name(usr)] added rule [rule.display()] to program [P.name] in cloud #[current_view]", INVESTIGATE_NANITES)
- . = TRUE
+ . = TRUE
if("remove_rule")
var/datum/nanite_cloud_backup/backup = get_backup(current_view)
if(backup)
@@ -236,7 +240,7 @@
rule.remove()
investigate_log("[key_name(usr)] removed rule [rule.display()] from program [P.name] in cloud #[current_view]", INVESTIGATE_NANITES)
- . = TRUE
+ . = TRUE
/datum/nanite_cloud_backup
var/cloud_id = 0
diff --git a/code/modules/research/nanites/nanite_hijacker.dm b/code/modules/research/nanites/nanite_hijacker.dm
deleted file mode 100644
index 8b137891791fe..0000000000000
--- a/code/modules/research/nanites/nanite_hijacker.dm
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/code/modules/research/nanites/nanite_program_hub.dm b/code/modules/research/nanites/nanite_program_hub.dm
index 41c0c843e59e0..0ec3e130ec2fc 100644
--- a/code/modules/research/nanites/nanite_program_hub.dm
+++ b/code/modules/research/nanites/nanite_program_hub.dm
@@ -29,6 +29,11 @@
linked_techweb = SSresearch.science_tech
/obj/machinery/nanite_program_hub/attackby(obj/item/I, mob/user)
+ if(default_deconstruction_screwdriver(user, icon_state, icon_state, I))
+ update_icon()
+ return
+ if(default_deconstruction_crowbar(I))
+ return
if(istype(I, /obj/item/disk/nanite_program))
var/obj/item/disk/nanite_program/N = I
if(user.transferItemToLoc(N, src))
@@ -37,6 +42,7 @@
if(disk)
eject(user)
disk = N
+ ui_update()
else
..()
@@ -46,6 +52,7 @@
if(!istype(user) || !Adjacent(user) || !user.put_in_active_hand(disk))
disk.forceMove(drop_location())
disk = null
+ ui_update()
/obj/machinery/nanite_program_hub/AltClick(mob/user)
if(disk && user.canUseTopic(src, !issilicon(user)))
diff --git a/code/modules/research/nanites/nanite_programmer.dm b/code/modules/research/nanites/nanite_programmer.dm
index 628a899957f8d..d0e6c8488fb4e 100644
--- a/code/modules/research/nanites/nanite_programmer.dm
+++ b/code/modules/research/nanites/nanite_programmer.dm
@@ -8,12 +8,18 @@
use_power = IDLE_POWER_USE
anchored = TRUE
density = TRUE
- flags_1 = HEAR_1
circuit = /obj/item/circuitboard/machine/nanite_programmer
-
+/obj/machinery/nanite_programmer/Initialize()
+ . = ..()
+ become_hearing_sensitive(trait_source = ROUNDSTART_TRAIT)
/obj/machinery/nanite_programmer/attackby(obj/item/I, mob/user)
+ if(default_deconstruction_screwdriver(user, icon_state, icon_state, I))
+ update_icon()
+ return
+ if(default_deconstruction_crowbar(I))
+ return
if(istype(I, /obj/item/disk/nanite_program))
var/obj/item/disk/nanite_program/N = I
if(user.transferItemToLoc(N, src))
@@ -23,6 +29,7 @@
eject(user)
disk = N
program = N.program
+ ui_update()
else
..()
@@ -33,6 +40,7 @@
disk.forceMove(drop_location())
disk = null
program = null
+ ui_update()
/obj/machinery/nanite_programmer/AltClick(mob/user)
if(disk && user.canUseTopic(src, !issilicon(user)))
@@ -141,7 +149,7 @@
program.timer_trigger_delay = timer
. = TRUE
-/obj/machinery/nanite_programmer/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
+/obj/machinery/nanite_programmer/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods = list())
. = ..()
var/static/regex/when = regex("(?:^\\W*when|when\\W*$)", "i") //starts or ends with when
if(findtext(raw_message, when) && !istype(speaker, /obj/machinery/nanite_programmer))
diff --git a/code/modules/research/nanites/nanite_programs.dm b/code/modules/research/nanites/nanite_programs.dm
index 8adf76130114f..2a4142bb35c02 100644
--- a/code/modules/research/nanites/nanite_programs.dm
+++ b/code/modules/research/nanites/nanite_programs.dm
@@ -54,6 +54,15 @@
//Rules that automatically manage if the program's active without requiring separate sensor programs
var/list/datum/nanite_rule/rules = list()
+ //Logic
+ //a list of logic types a nanite program's rules follow
+ var/static/list/logic = list(
+ "AND" = NL_AND,
+ "OR" = NL_OR,
+ "NOR" = NL_NOR,
+ "NAND" = NL_NAND,
+ )
+
/datum/nanite_program/New()
. = ..()
register_extra_settings()
@@ -68,6 +77,9 @@
on_mob_remove()
if(nanites)
nanites.programs -= src
+ for(var/datum/nanite_rule/rule as anything in rules)
+ rule.remove()
+ rules.Cut()
return ..()
/datum/nanite_program/proc/copy()
@@ -99,6 +111,10 @@
///Register extra settings by overriding this.
///extra_settings[name] = new typepath() for each extra setting
/datum/nanite_program/proc/register_extra_settings()
+ var/list/logictypes = list()
+ for(var/name in logic)
+ logictypes += name
+ extra_settings[NES_RULE_LOGIC] = new /datum/nanite_extra_setting/type("AND", logictypes)
return
///You can override this if you need to have special behavior after setting certain settings.
@@ -193,10 +209,36 @@
//If false, disables active and passive effects, but doesn't consume nanites
//Can be used to avoid consuming nanites for nothing
/datum/nanite_program/proc/check_conditions()
- for(var/R in rules)
- var/datum/nanite_rule/rule = R
- if(!rule.check_rule())
- return FALSE
+ var/datum/nanite_extra_setting/logictype = extra_settings[NES_RULE_LOGIC]
+ if(logictype)
+ switch(logictype.get_value())
+ if(NL_AND)
+ for(var/R in rules)
+ var/datum/nanite_rule/rule = R
+ if(!rule.check_rule())
+ return FALSE
+ if(NL_OR)
+ for(var/R in rules)
+ var/datum/nanite_rule/rule = R
+ if(rule.check_rule())
+ return TRUE
+ return FALSE
+ if(NL_NOR)
+ for(var/R in rules)
+ var/datum/nanite_rule/rule = R
+ if(rule.check_rule())
+ return FALSE
+ if(NL_NAND)
+ for(var/R in rules)
+ var/datum/nanite_rule/rule = R
+ if(!rule.check_rule())
+ return TRUE
+ return FALSE
+ else
+ for(var/R in rules)
+ var/datum/nanite_rule/rule = R
+ if(!rule.check_rule())
+ return FALSE
return TRUE
//Constantly procs as long as the program is active
@@ -243,14 +285,14 @@
software_error()
/datum/nanite_program/proc/on_shock(shock_damage)
- if(!program_flags & NANITE_SHOCK_IMMUNE)
+ if(!(program_flags & NANITE_SHOCK_IMMUNE))
if(prob(10))
software_error()
else if(prob(33))
qdel(src)
/datum/nanite_program/proc/on_minor_shock()
- if(!program_flags & NANITE_SHOCK_IMMUNE)
+ if(!(program_flags & NANITE_SHOCK_IMMUNE))
if(prob(10))
software_error()
diff --git a/code/modules/research/nanites/nanite_programs/buffing.dm b/code/modules/research/nanites/nanite_programs/buffing.dm
index bcfc323031c46..a1442bef19d4b 100644
--- a/code/modules/research/nanites/nanite_programs/buffing.dm
+++ b/code/modules/research/nanites/nanite_programs/buffing.dm
@@ -22,7 +22,7 @@
name = "Adrenaline Burst"
desc = "The nanites cause a burst of adrenaline when triggered, allowing the user to push their body past its normal limits."
can_trigger = TRUE
- trigger_cost = 25
+ trigger_cost = 20
trigger_cooldown = 1200
rogue_types = list(/datum/nanite_program/toxic, /datum/nanite_program/nerve_decay)
@@ -32,7 +32,6 @@
host_mob.adjustStaminaLoss(-75)
host_mob.set_resting(FALSE)
host_mob.update_mobility()
- host_mob.reagents.add_reagent(/datum/reagent/medicine/amphetamine, 3)
/datum/nanite_program/hardening
name = "Dermal Hardening"
@@ -125,3 +124,16 @@
. = ..()
REMOVE_TRAIT(host_mob, TRAIT_MINDSHIELD, "nanites")
host_mob.sec_hud_set_implants()
+
+/datum/nanite_program/haste
+ name = "Amphetamine Injection"
+ desc = "The nanites synthesize amphetamine when triggered, which temporarily increases the host's running speed."
+ can_trigger = TRUE
+ trigger_cost = 10
+ trigger_cooldown = 1200
+ rogue_types = list(/datum/nanite_program/toxic, /datum/nanite_program/nerve_decay)
+
+/datum/nanite_program/haste/on_trigger()
+ to_chat(host_mob, "Your body feels lighter and your legs feel relaxed!")
+ host_mob.set_resting(FALSE)
+ host_mob.reagents.add_reagent(/datum/reagent/medicine/amphetamine, 3)
diff --git a/code/modules/research/nanites/nanite_programs/sensor.dm b/code/modules/research/nanites/nanite_programs/sensor.dm
index fbebeaf209adb..c1c97136172ba 100644
--- a/code/modules/research/nanites/nanite_programs/sensor.dm
+++ b/code/modules/research/nanites/nanite_programs/sensor.dm
@@ -250,6 +250,8 @@
UnregisterSignal(host_mob, COMSIG_MOVABLE_HEAR, .proc/on_hear)
/datum/nanite_program/sensor/voice/proc/on_hear(datum/source, list/hearing_args)
+ SIGNAL_HANDLER
+
var/datum/nanite_extra_setting/sentence = extra_settings[NES_SENTENCE]
var/datum/nanite_extra_setting/inclusive = extra_settings[NES_INCLUSIVE_MODE]
if(!sentence.get_value())
@@ -265,19 +267,18 @@
name = "Species Sensor"
desc = "When triggered, the nanites scan the host to determine their species and output a signal depending on the conditions set in the settings."
can_trigger = TRUE
+ can_rule = TRUE
trigger_cost = 0
trigger_cooldown = 5
- var/list/static/allowed_species = list(
- "Human" = /datum/species/human,
- "Lizard" = /datum/species/lizard,
- "Moth" = /datum/species/moth,
- "Ethereal" = /datum/species/ethereal,
- "Pod" = /datum/species/pod,
- "Fly" = /datum/species/fly,
- "Felinid" = /datum/species/human/felinid,
- "Jelly" = /datum/species/jelly
- )
+ var/list/static/allowed_species = list()
+
+/datum/nanite_program/sensor/species/New()
+ if(!length(allowed_species))
+ for(var/id in GLOB.roundstart_races)
+ allowed_species[id] = GLOB.species_list[id]
+ . = ..()
+
/datum/nanite_program/sensor/species/register_extra_settings()
. = ..()
@@ -285,7 +286,7 @@
for(var/name in allowed_species)
species_types += name
species_types += "Other"
- extra_settings[NES_RACE] = new /datum/nanite_extra_setting/type("Human", species_types)
+ extra_settings[NES_RACE] = new /datum/nanite_extra_setting/type("human", species_types)
extra_settings[NES_MODE] = new /datum/nanite_extra_setting/boolean(TRUE, "Is", "Is Not")
/datum/nanite_program/sensor/species/on_trigger(comm_message)
@@ -294,13 +295,19 @@
var/species_match = FALSE
if(species)
- if(is_species(host_mob, species))
+ if(species == /datum/species/human)
+ if(ishumanbasic(host_mob) && !is_species(host_mob, /datum/species/human/felinid))
+ species_match = TRUE
+ else if(is_species(host_mob, species))
species_match = TRUE
else //this is the check for the "Other" option
species_match = TRUE
for(var/name in allowed_species)
var/species_other = allowed_species[name]
- if(is_species(host_mob, species_other))
+ if (species_other == /datum/species/human)
+ if(ishumanbasic(host_mob) && !is_species(host_mob, /datum/species/human/felinid))
+ species_match = FALSE
+ else if(is_species(host_mob, species_other))
species_match = FALSE
break
@@ -311,3 +318,13 @@
else
if(!species_match)
send_code()
+
+/datum/nanite_program/sensor/species/make_rule(datum/nanite_program/target)
+ var/datum/nanite_rule/species/rule = new(target)
+ var/datum/nanite_extra_setting/species_name = extra_settings[NES_RACE]
+ var/datum/nanite_extra_setting/mode = extra_settings[NES_MODE]
+ var/datum/nanite_extra_setting/species_type = allowed_species[species_name.get_value()]
+ rule.species_rule = species_type
+ rule.mode_rule = mode.get_value()
+ rule.species_name_rule = species_name.get_value()
+ return rule
diff --git a/code/modules/research/nanites/nanite_programs/utility.dm b/code/modules/research/nanites/nanite_programs/utility.dm
index b3601611f3000..66c041d662871 100644
--- a/code/modules/research/nanites/nanite_programs/utility.dm
+++ b/code/modules/research/nanites/nanite_programs/utility.dm
@@ -15,7 +15,7 @@
return
var/datum/nanite_extra_setting/program = extra_settings[NES_PROGRAM_OVERWRITE]
var/datum/nanite_extra_setting/cloud = extra_settings[NES_CLOUD_OVERWRITE]
- for(var/mob/M in orange(host_mob, 5))
+ for(var/mob/M in orange(5, host_mob))
if(SEND_SIGNAL(M, COMSIG_NANITE_IS_STEALTHY))
continue
switch(program.get_value())
@@ -238,10 +238,10 @@
return
spread_cooldown = world.time + 50
var/list/mob/living/target_hosts = list()
- for(var/mob/living/L in oview(5, host_mob))
+ for(var/mob/living/L in ohearers(5, host_mob))
if(!prob(25))
continue
- if(!(MOB_ORGANIC in L.mob_biotypes) && !(MOB_UNDEAD in L.mob_biotypes))
+ if(!(MOB_ORGANIC in L.mob_biotypes) && !(MOB_UNDEAD in L.mob_biotypes) && !HAS_TRAIT(host_mob, TRAIT_NANITECOMPATIBLE))
continue
target_hosts += L
if(!target_hosts.len)
@@ -264,7 +264,7 @@
/datum/nanite_program/nanite_sting/on_trigger(comm_message)
var/list/mob/living/target_hosts = list()
for(var/mob/living/L in oview(1, host_mob))
- if(!(MOB_ORGANIC in L.mob_biotypes) && !(MOB_UNDEAD in L.mob_biotypes))
+ if(!(MOB_ORGANIC in L.mob_biotypes) && !(MOB_UNDEAD in L.mob_biotypes) && !HAS_TRAIT(host_mob, TRAIT_NANITECOMPATIBLE))
continue
if(SEND_SIGNAL(L, COMSIG_HAS_NANITES) || !L.Adjacent(host_mob))
continue
@@ -278,7 +278,7 @@
infectee.AddComponent(/datum/component/nanites, 5)
SEND_SIGNAL(infectee, COMSIG_NANITE_SYNC, nanites)
infectee.investigate_log("was infected by a nanite cluster by [key_name(host_mob)] at [AREACOORD(infectee)].", INVESTIGATE_NANITES)
- to_chat(infectee, "You feel a tiny prick.")
+ to_chat(infectee, "You feel a tiny prick!")
/datum/nanite_program/mitosis
name = "Mitosis"
@@ -326,7 +326,7 @@
/datum/nanite_program/dermal_button/on_mob_remove()
. = ..()
- qdel(button)
+ QDEL_NULL(button)
/datum/nanite_program/dermal_button/proc/press()
if(activated)
diff --git a/code/modules/research/nanites/nanite_programs/weapon.dm b/code/modules/research/nanites/nanite_programs/weapon.dm
index 16f87bc6bdeec..43a5ede859b96 100644
--- a/code/modules/research/nanites/nanite_programs/weapon.dm
+++ b/code/modules/research/nanites/nanite_programs/weapon.dm
@@ -24,7 +24,7 @@
/datum/nanite_program/poison/active_effect()
host_mob.adjustToxLoss(1)
- if(prob(2))
+ if(prob(2) && !HAS_TRAIT(host_mob, TRAIT_TOXINLOVER))
to_chat(host_mob, "You feel nauseous.")
if(iscarbon(host_mob))
var/mob/living/carbon/C = host_mob
diff --git a/code/modules/research/nanites/nanite_remote.dm b/code/modules/research/nanites/nanite_remote.dm
index 4655ac6749099..a6c90d0942ce9 100644
--- a/code/modules/research/nanites/nanite_remote.dm
+++ b/code/modules/research/nanites/nanite_remote.dm
@@ -26,7 +26,6 @@
. += "Alt-click to unlock."
/obj/item/nanite_remote/AltClick(mob/user)
- . = ..()
if(!user.canUseTopic(src, BE_CLOSE))
return
if(locked)
@@ -34,6 +33,7 @@
to_chat(user, "You unlock [src].")
locked = FALSE
update_icon()
+ ui_update()
else
to_chat(user, "Access denied.")
@@ -45,6 +45,7 @@
if(locked)
locked = FALSE
update_icon()
+ ui_update()
/obj/item/nanite_remote/update_icon()
. = ..()
@@ -67,7 +68,7 @@
signal_mob(target, code, key_name(user))
if(REMOTE_MODE_AOE)
to_chat(user, "You activate [src], signaling the nanites inside every host around you.")
- for(var/mob/living/L in view(user, 7))
+ for(var/mob/living/L in hearers(7, user))
signal_mob(L, code, key_name(user))
if(REMOTE_MODE_RELAY)
to_chat(user, "You activate [src], signaling all connected relay nanites.")
@@ -103,7 +104,8 @@
return data
/obj/item/nanite_remote/ui_act(action, params)
- if(..())
+ . = ..()
+ if(.)
return
switch(action)
if("set_code")
@@ -170,7 +172,6 @@
update_icon()
. = TRUE
-
/obj/item/nanite_remote/comm
name = "nanite communication remote"
desc = "A device that can send text messages to specific programs."
@@ -190,7 +191,7 @@
signal_mob(target, code, comm_message, key_name(user))
if(REMOTE_MODE_AOE)
to_chat(user, "You activate [src], signaling the nanites inside every host around you.")
- for(var/mob/living/L in view(user, 7))
+ for(var/mob/living/L in hearers(7, user))
signal_mob(L, code, comm_message, key_name(user))
if(REMOTE_MODE_RELAY)
to_chat(user, "You activate [src], signaling all connected relay nanites.")
diff --git a/code/modules/research/nanites/program_disks.dm b/code/modules/research/nanites/program_disks.dm
index 02f6d7cc280bc..ce181389c073e 100644
--- a/code/modules/research/nanites/program_disks.dm
+++ b/code/modules/research/nanites/program_disks.dm
@@ -131,3 +131,6 @@
/obj/item/disk/nanite_program/stun
program_type = /datum/nanite_program/stun
+
+/obj/item/disk/nanite_program/species_sensor
+ program_type = /datum/nanite_program/sensor/species
diff --git a/code/modules/research/nanites/public_chamber.dm b/code/modules/research/nanites/public_chamber.dm
index a892fa4cf9168..0517b8e54bf64 100644
--- a/code/modules/research/nanites/public_chamber.dm
+++ b/code/modules/research/nanites/public_chamber.dm
@@ -8,8 +8,8 @@
use_power = IDLE_POWER_USE
anchored = TRUE
density = TRUE
- idle_power_usage = 50
- active_power_usage = 300
+ idle_power_usage = 300
+ active_power_usage = 1200
var/cloud_id = 1
var/locked = FALSE
@@ -166,7 +166,7 @@
if(nanites && nanites.cloud_id != cloud_id)
change_cloud(attacker)
return
- if((MOB_ORGANIC in L.mob_biotypes) || (MOB_UNDEAD in L.mob_biotypes))
+ if((MOB_ORGANIC in L.mob_biotypes) || (MOB_UNDEAD in L.mob_biotypes) || HAS_TRAIT(L, TRAIT_NANITECOMPATIBLE))
inject_nanites(attacker)
/obj/machinery/public_nanite_chamber/open_machine()
diff --git a/code/modules/research/nanites/rules.dm b/code/modules/research/nanites/rules.dm
index f496abe6e1fd1..f58835fdae870 100644
--- a/code/modules/research/nanites/rules.dm
+++ b/code/modules/research/nanites/rules.dm
@@ -149,3 +149,34 @@
/datum/nanite_rule/damage/display()
return "[damage_type] [above ? ">" : "<"] [threshold]"
+
+/datum/nanite_rule/species
+ name = "Species"
+ desc = "Checks the host's race"
+
+ var/species_rule = /datum/species/human
+ var/mode_rule = "is"
+ var/species_name_rule = "human"
+
+
+/datum/nanite_rule/species/check_rule()
+ var/species_match_rule = FALSE
+
+ if(species_rule)
+ if(species_rule == /datum/species/human)
+ if(ishumanbasic(program.host_mob) && !is_species(program.host_mob, /datum/species/human/felinid))
+ species_match_rule = TRUE
+ else if(is_species(program.host_mob, species_rule))
+ species_match_rule = TRUE
+
+
+ return species_match_rule ? mode_rule : !mode_rule
+
+/datum/nanite_rule/species/copy_to(datum/nanite_program/new_program)
+ var/datum/nanite_rule/species/rule = new(new_program)
+ rule.species_rule = species_rule
+ rule.mode_rule = mode_rule
+ rule.species_name_rule = species_name_rule
+
+/datum/nanite_rule/species/display()
+ return "[mode_rule ? "IS" : "IS NOT"] [species_name_rule]"
diff --git a/code/modules/research/rdconsole.dm b/code/modules/research/rdconsole.dm
index b278d31a10c8a..db71abfd3a271 100644
--- a/code/modules/research/rdconsole.dm
+++ b/code/modules/research/rdconsole.dm
@@ -226,6 +226,7 @@ Nothing else in the console has ID requirements.
l += "[sheet.css_tag()][RDSCREEN_NOBREAK]"
l += "
[stored_research.organization] Research and Development Network"
l += "Available points: [techweb_point_display_rdconsole(stored_research.research_points, stored_research.last_bitcoins)]"
+ l += "Current Research Tier: [stored_research.current_tier] "
l += "Security protocols: [obj_flags & EMAGGED ? "Disabled" : "Enabled"]"
l += "Main Menu | Back
[RDSCREEN_NOBREAK]"
l += "[ui_mode == 1? "Normal View" : "Normal View"] | [ui_mode == 2? "Expert View" : "Expert View"] | [ui_mode == 3? "List View" : "List View"]"
@@ -386,9 +387,9 @@ Nothing else in the console has ID requirements.
var/datum/material/M = mat_id
var/amount = mat_container.materials[mat_id]
l += "* [amount] of [M.name]: "
- if(amount >= MINERAL_MATERIAL_AMOUNT) l += "Eject [RDSCREEN_NOBREAK]"
- if(amount >= MINERAL_MATERIAL_AMOUNT*5) l += "5x [RDSCREEN_NOBREAK]"
- if(amount >= MINERAL_MATERIAL_AMOUNT) l += "All[RDSCREEN_NOBREAK]"
+ if(amount >= MINERAL_MATERIAL_AMOUNT) l += "Eject [RDSCREEN_NOBREAK]"
+ if(amount >= MINERAL_MATERIAL_AMOUNT*5) l += "5x [RDSCREEN_NOBREAK]"
+ if(amount >= MINERAL_MATERIAL_AMOUNT) l += "All[RDSCREEN_NOBREAK]"
l += ""
l += "[RDSCREEN_NOBREAK]"
return l
@@ -520,9 +521,9 @@ Nothing else in the console has ID requirements.
var/datum/material/M = mat_id
var/amount = mat_container.materials[mat_id]
l += "* [amount] of [M.name]: "
- if(amount >= MINERAL_MATERIAL_AMOUNT) l += "Eject [RDSCREEN_NOBREAK]"
- if(amount >= MINERAL_MATERIAL_AMOUNT*5) l += "5x [RDSCREEN_NOBREAK]"
- if(amount >= MINERAL_MATERIAL_AMOUNT) l += "All[RDSCREEN_NOBREAK]"
+ if(amount >= MINERAL_MATERIAL_AMOUNT) l += "Eject [RDSCREEN_NOBREAK]"
+ if(amount >= MINERAL_MATERIAL_AMOUNT*5) l += "5x [RDSCREEN_NOBREAK]"
+ if(amount >= MINERAL_MATERIAL_AMOUNT) l += "All[RDSCREEN_NOBREAK]"
return l
/obj/machinery/computer/rdconsole/proc/ui_techdisk() //Legacy code
@@ -535,7 +536,11 @@ Nothing else in the console has ID requirements.
l += "
Stored Technology Nodes:
"
for(var/i in t_disk.stored_research.researched_nodes)
var/datum/techweb_node/N = SSresearch.techweb_node_by_id(i)
- l += "[N.display_name]"
+ l += "[N.display_name] ([N.tech_tier])"
+ for(var/i in stored_research.hidden_nodes)
+ if(!t_disk.stored_research.hidden_nodes[i])
+ var/datum/techweb_node/N = SSresearch.techweb_node_by_id(i)
+ l += "Hidden Node - [N.display_name] ([N.tech_tier])"
l += "
"
return l
@@ -676,13 +681,13 @@ Nothing else in the console has ID requirements.
var/not_unlocked = (stored_research.available_nodes[N.id] && !stored_research.researched_nodes[N.id])
var/has_points = (stored_research.can_afford(N.get_price(stored_research)))
var/research_href = not_unlocked? (has_points? "Research" : "Not Enough Points") : null
- l += "[N.display_name][research_href]"
+ l += "[N.display_name] ([N.tech_tier])[research_href]"
l += "
[RDSCREEN_NOBREAK]"
return l
@@ -695,8 +700,8 @@ Nothing else in the console has ID requirements.
return l
var/display_name = node.display_name
if (selflink)
- display_name = "[display_name]"
- l += "
[display_name] ([node.tech_tier]) [RDSCREEN_NOBREAK]"
if(minimal)
l += " [node.description]"
else
@@ -766,9 +771,9 @@ Nothing else in the console has ID requirements.
if (linked_lathe && stored_research.researched_designs[selected_design.id])
l += "Construct"
if(selected_design.build_type & AUTOLATHE)
- lathes += "[machine_icon(/obj/machinery/autolathe)][RDSCREEN_NOBREAK]"
+ lathes += "[machine_icon(/obj/machinery/modular_fabricator/autolathe)][RDSCREEN_NOBREAK]"
if(selected_design.build_type & MECHFAB)
- lathes += "[machine_icon(/obj/machinery/mecha_part_fabricator)][RDSCREEN_NOBREAK]"
+ lathes += "[machine_icon(/obj/machinery/modular_fabricator/exosuit_fab)][RDSCREEN_NOBREAK]"
if(selected_design.build_type & BIOGENERATOR)
lathes += "[machine_icon(/obj/machinery/biogenerator)][RDSCREEN_NOBREAK]"
if(selected_design.build_type & LIMBGROWER)
@@ -839,8 +844,8 @@ Nothing else in the console has ID requirements.
for(var/i in 1 to length(ui))
if(!findtextEx(ui[i], RDSCREEN_NOBREAK))
ui[i] += " "
- ui[i] = replacetextEx(ui[i], RDSCREEN_NOBREAK, "")
- return ui.Join("")
+ . = ui.Join("")
+ return replacetextEx(., RDSCREEN_NOBREAK, "")
/obj/machinery/computer/rdconsole/Topic(raw, ls)
if(..())
@@ -940,7 +945,7 @@ Nothing else in the console has ID requirements.
if(!linked_lathe.materials.mat_container)
say("No material storage linked to protolathe!")
return
- linked_lathe.eject_sheets(ls["ejectsheet"], ls["eject_amt"])
+ linked_lathe.eject_sheets(locate(ls["ejectsheet"]), ls["eject_amt"])
//Circuit Imprinter Materials
if(ls["disposeI"]) //Causes the circuit imprinter to dispose of a single reagent (all of it)
if(QDELETED(linked_imprinter))
@@ -959,7 +964,7 @@ Nothing else in the console has ID requirements.
if(!linked_imprinter.materials.mat_container)
say("No material storage linked to circuit imprinter!")
return
- linked_imprinter.eject_sheets(ls["imprinter_ejectsheet"], ls["eject_amt"])
+ linked_imprinter.eject_sheets(locate(ls["imprinter_ejectsheet"]), ls["eject_amt"])
if(ls["disk_slot"])
disk_slot_selected = text2num(ls["disk_slot"])
if(ls["research_node"])
diff --git a/code/modules/research/server.dm b/code/modules/research/server.dm
index ea7e4155496d9..ff97e7725a931 100644
--- a/code/modules/research/server.dm
+++ b/code/modules/research/server.dm
@@ -9,6 +9,7 @@
var/working = TRUE
var/research_disabled = FALSE
var/server_id = 0
+ var/heat_gen = 1
// some notes on this number
// as of 4/29/2020, the techweb was set that fed a constant of 52.3 no matter how many servers there were
// A coeffecent of sqrt(100/) is set up on a per some older code. Since there are normaly 2 servers this comes out to
@@ -59,7 +60,7 @@
var/tot_rating = 0
for(var/obj/item/stock_parts/SP in src)
tot_rating += SP.rating
- heating_power = heating_power / max(1, tot_rating)
+ heat_gen = initial(src.heat_gen) / max(1, tot_rating)
/obj/machinery/rnd/server/update_icon()
if (panel_open)
@@ -89,20 +90,11 @@
// This is from the RD server code. It works well enough but I need to move over the
// sspace heater code so we can caculate power used per tick as well and making this both
// exothermic and an endothermic component
- if(env && env.return_temperature() < T20C + 80)
+ if(env)
+ var/perc = max((get_env_temp() - temp_tolerance_high), 0) * temp_penalty_coefficient / base_mining_income
- var/transfer_moles = 0.25 * env.total_moles()
-
- var/datum/gas_mixture/removed = env.remove(transfer_moles)
-
- if(removed)
- var/heat_capacity = removed.heat_capacity()
- if(heat_capacity == 0 || heat_capacity == null)
- heat_capacity = 1
- removed.set_temperature(min((removed.return_temperature()*heat_capacity + heating_power)/heat_capacity, 1000))
-
- current_temp = removed.return_temperature()
- env.merge(removed)
+ env.adjust_heat(heating_power * perc * heat_gen)
+ air_update_turf()
src.air_update_turf()
else
current_temp = env ? env.return_temperature() : -1
@@ -191,6 +183,7 @@
if(!ui)
ui = new(user, src, "RDConsole")
ui.open()
+ ui.set_autoupdate(TRUE)
/obj/machinery/computer/rdservercontrol/ui_data(mob/user)
var/list/data = list()
diff --git a/code/modules/research/stock_parts.dm b/code/modules/research/stock_parts.dm
index 26588f6450a62..2e0742749d68a 100644
--- a/code/modules/research/stock_parts.dm
+++ b/code/modules/research/stock_parts.dm
@@ -44,6 +44,7 @@ If you create T5+ please take a pass at gene_modder.dm [L40]. Max_values MUST fi
name = "bluespace rapid part exchange device"
desc = "A version of the RPED that allows for replacement of parts and scanning from a distance, along with higher capacity for parts."
icon_state = "BS_RPED"
+ item_state = "BS_RPED"
w_class = WEIGHT_CLASS_NORMAL
works_from_distance = TRUE
pshoom_or_beepboopblorpzingshadashwoosh = 'sound/items/pshoom.ogg'
@@ -53,42 +54,46 @@ If you create T5+ please take a pass at gene_modder.dm [L40]. Max_values MUST fi
/obj/item/storage/part_replacer/bluespace/tier1
/obj/item/storage/part_replacer/bluespace/tier1/PopulateContents()
- for(var/i in 1 to 10)
+ for(var/i in 1 to 50)
new /obj/item/stock_parts/capacitor(src)
new /obj/item/stock_parts/scanning_module(src)
new /obj/item/stock_parts/manipulator(src)
new /obj/item/stock_parts/micro_laser(src)
new /obj/item/stock_parts/matter_bin(src)
+ new /obj/item/stock_parts/cell/high(src)
/obj/item/storage/part_replacer/bluespace/tier2
/obj/item/storage/part_replacer/bluespace/tier2/PopulateContents()
- for(var/i in 1 to 10)
+ for(var/i in 1 to 50)
new /obj/item/stock_parts/capacitor/adv(src)
new /obj/item/stock_parts/scanning_module/adv(src)
new /obj/item/stock_parts/manipulator/nano(src)
new /obj/item/stock_parts/micro_laser/high(src)
new /obj/item/stock_parts/matter_bin/adv(src)
+ new /obj/item/stock_parts/cell/super(src)
/obj/item/storage/part_replacer/bluespace/tier3
/obj/item/storage/part_replacer/bluespace/tier3/PopulateContents()
- for(var/i in 1 to 10)
+ for(var/i in 1 to 50)
new /obj/item/stock_parts/capacitor/super(src)
new /obj/item/stock_parts/scanning_module/phasic(src)
new /obj/item/stock_parts/manipulator/pico(src)
new /obj/item/stock_parts/micro_laser/ultra(src)
new /obj/item/stock_parts/matter_bin/super(src)
+ new /obj/item/stock_parts/cell/hyper(src)
/obj/item/storage/part_replacer/bluespace/tier4
/obj/item/storage/part_replacer/bluespace/tier4/PopulateContents()
- for(var/i in 1 to 10)
+ for(var/i in 1 to 50)
new /obj/item/stock_parts/capacitor/quadratic(src)
new /obj/item/stock_parts/scanning_module/triphasic(src)
new /obj/item/stock_parts/manipulator/femto(src)
new /obj/item/stock_parts/micro_laser/quadultra(src)
new /obj/item/stock_parts/matter_bin/bluespace(src)
+ new /obj/item/stock_parts/cell/bluespace(src)
/obj/item/storage/part_replacer/cargo //used in a cargo crate
diff --git a/code/modules/research/techweb/__techweb_helpers.dm b/code/modules/research/techweb/__techweb_helpers.dm
index 2fd89378a554d..f002d01390559 100644
--- a/code/modules/research/techweb/__techweb_helpers.dm
+++ b/code/modules/research/techweb/__techweb_helpers.dm
@@ -11,8 +11,11 @@
SSresearch.invalid_node_boost[id] = message
/proc/techweb_item_boost_check(obj/item/I) //Returns an associative list of techweb node datums with values of the boost it gives. var/list/returned = list()
+ . = list()
if(SSresearch.techweb_boost_items[I.type])
- return SSresearch.techweb_boost_items[I.type] //It should already be formatted in node datum = list(point type = value)
+ . += SSresearch.techweb_boost_items[I.type] //It should already be formatted in node datum = list(point type = value)
+ if(I.item_flags & ILLEGAL)
+ . |= "syndicate_basic"
/proc/techweb_item_point_check(obj/item/I)
if(SSresearch.techweb_point_items[I.type])
diff --git a/code/modules/research/techweb/_techweb.dm b/code/modules/research/techweb/_techweb.dm
index 25c00a4c57a87..6413400b5733c 100644
--- a/code/modules/research/techweb/_techweb.dm
+++ b/code/modules/research/techweb/_techweb.dm
@@ -1,4 +1,8 @@
+//Unlock 20% of a tier and the tier will progress to that one.
+#define TIER_PROPORTATION_TO_UNLOCK 0.2
+#define MAX_TIER 5
+
//Used \n[\s]*origin_tech[\s]*=[\s]*"[\S]+" to delete all origin techs.
//Or \n[\s]*origin_tech[\s]*=[\s]list\([A-Z_\s=0-9,]*\)
//Used \n[\s]*req_tech[\s]*=[\s]*list\(["a-z\s=0-9,]*\) to delete all req_techs.
@@ -21,7 +25,14 @@
var/organization = "Third-Party" //Organization name, used for display.
var/list/last_bitcoins = list() //Current per-second production, used for display only.
var/list/discovered_mutations = list() //Mutations discovered by genetics, this way they are shared and cant be destroyed by destroying a single console
+ //Tiers used for the RD console, not actual tier
var/list/tiers = list() //Assoc list, id = number, 1 is available, 2 is all reqs are 1, so on
+ //Discovery scanned thinsg
+ var/list/scanned_atoms = list()
+ //Discovery cost tiers
+ var/current_tier = 1
+ var/list/items_per_tier = list() //Assoc list, Key = "[tier level]", Value = Amount of items in tier
+ var/list/unlocked_in_tier = list() //Assoc list, Key = "[tier level]", Value = Amount of items unlocked in tier
/datum/techweb/New()
SSresearch.techwebs += src
@@ -29,6 +40,13 @@
var/datum/techweb_node/DN = SSresearch.techweb_node_by_id(i)
research_node(DN, TRUE, FALSE, FALSE)
hidden_nodes = SSresearch.techweb_nodes_hidden.Copy()
+ items_per_tier = list()
+ for(var/id in SSresearch.techweb_nodes)
+ var/datum/techweb_node/node = SSresearch.techweb_node_by_id(id)
+ if(islist(items_per_tier["[node.tech_tier]"]))
+ items_per_tier["[node.tech_tier]"] += 1
+ else
+ items_per_tier["[node.tech_tier]"] = 1
return ..()
/datum/techweb/admin
@@ -54,6 +72,8 @@
available_nodes = null
visible_nodes = null
custom_designs = null
+ items_per_tier = null
+ unlocked_in_tier = null
SSresearch.techwebs -= src
return ..()
@@ -69,14 +89,40 @@
researched_designs = custom_designs.Copy()
if(wipe_custom_designs)
custom_designs = list()
+ items_per_tier = list()
for(var/id in processing)
- update_node_status(SSresearch.techweb_node_by_id(id), FALSE)
+ var/datum/techweb_node/node = SSresearch.techweb_node_by_id(id)
+ update_node_status(node, FALSE)
+ if(islist(items_per_tier["[node.tech_tier]"]))
+ items_per_tier["[node.tech_tier]"] += 1
+ else
+ items_per_tier["[node.tech_tier]"] = 1
CHECK_TICK
+ recalculate_tiers()
for(var/v in consoles_accessing)
var/obj/machinery/computer/rdconsole/V = v
V.rescan_views()
V.updateUsrDialog()
+/datum/techweb/proc/recalculate_tiers()
+ for(var/id in researched_nodes)
+ var/datum/techweb_node/node = SSresearch.techweb_node_by_id(id)
+ if(islist(unlocked_in_tier["[node.tech_tier]"]))
+ unlocked_in_tier["[node.tech_tier]"] += 1
+ else
+ unlocked_in_tier["[node.tech_tier]"] = 1
+ calculate_current_tier()
+
+/datum/techweb/proc/calculate_current_tier()
+ current_tier = 1
+ for(var/tier in 1 to MAX_TIER)
+ var/researched_amount = unlocked_in_tier["[tier]"]
+ var/total_amount = items_per_tier["[tier]"]
+ if(!researched_amount)
+ continue
+ if(researched_amount >= total_amount * TIER_PROPORTATION_TO_UNLOCK)
+ current_tier = tier
+
/datum/techweb/proc/add_point_list(list/pointlist)
for(var/i in pointlist)
if(SSresearch.point_types[i] && pointlist[i] > 0)
@@ -131,6 +177,8 @@
returned.available_nodes = available_nodes.Copy()
returned.researched_designs = researched_designs.Copy()
returned.hidden_nodes = hidden_nodes.Copy()
+ returned.current_tier = current_tier
+ returned.unlocked_in_tier = unlocked_in_tier.Copy()
return returned
/datum/techweb/proc/get_visible_nodes() //The way this is set up is shit but whatever.
@@ -202,6 +250,7 @@
/datum/techweb/proc/research_node(datum/techweb_node/node, force = FALSE, auto_adjust_cost = TRUE, get_that_dosh = TRUE)
if(!istype(node))
return FALSE
+ recalculate_tiers()
update_node_status(node)
if(!force)
if(!available_nodes[node.id] || (auto_adjust_cost && (!can_afford(node.get_price(src)))))
@@ -215,17 +264,13 @@
for(var/id in node.design_ids)
add_design_by_id(id)
update_node_status(node)
- if(get_that_dosh)
- var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_SCI)
- if(D)
- D.adjust_money(SSeconomy.techweb_bounty)
return TRUE
/datum/techweb/science/research_node(datum/techweb_node/node, force = FALSE, auto_adjust_cost = TRUE, get_that_dosh = TRUE) //When something is researched, triggers the proc for this techweb only
. = ..()
if(.)
node.on_research()
-
+
/datum/techweb/proc/unresearch_node_id(id)
return unresearch_node(SSresearch.techweb_node_by_id(id))
@@ -377,3 +422,7 @@
/datum/techweb/specialized/autounlocking/exofab
allowed_buildtypes = MECHFAB
+
+/datum/techweb/specialized/autounlocking/component_printer
+ design_autounlock_buildtypes = COMPONENT_PRINTER
+ allowed_buildtypes = COMPONENT_PRINTER
diff --git a/code/modules/research/techweb/_techweb_node.dm b/code/modules/research/techweb/_techweb_node.dm
index 059c9cc828d5a..1af6ceb4ee0e1 100644
--- a/code/modules/research/techweb/_techweb_node.dm
+++ b/code/modules/research/techweb/_techweb_node.dm
@@ -4,6 +4,7 @@
/datum/techweb_node
var/id
+ var/tech_tier = 0
var/display_name = "Errored Node"
var/description = "Why are you seeing this?"
var/hidden = FALSE //Whether it starts off hidden.
@@ -89,12 +90,27 @@
for(var/i in L)
if(actual_costs[i])
actual_costs[i] -= L[i]
+ actual_costs[TECHWEB_POINT_TYPE_DISCOVERY] = calculate_discovery_cost(host.current_tier)
return actual_costs
else
return research_costs
+/datum/techweb_node/proc/calculate_discovery_cost(their_tier)
+ var/delta = tech_tier - their_tier
+ switch(delta)
+ if(-INFINITY to 0)
+ return 0
+ if(1)
+ return 1000
+ if(2)
+ return 2500
+ if(3)
+ return 5000
+ if(4 to INFINITY)
+ return 10000
+
/datum/techweb_node/proc/price_display(datum/techweb/TN)
return techweb_point_display_generic(get_price(TN))
/datum/techweb_node/proc/on_research() //new proc, not currently in file
- return
\ No newline at end of file
+ return
diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm
index 71f0385e5812a..a550a4934810b 100644
--- a/code/modules/research/techweb/all_nodes.dm
+++ b/code/modules/research/techweb/all_nodes.dm
@@ -4,6 +4,7 @@
//Base Nodes
/datum/techweb_node/base
id = "base"
+ tech_tier = 0
starting_node = TRUE
display_name = "Basic Research Technology"
description = "NT default research technologies."
@@ -11,10 +12,11 @@
design_ids = list("basic_matter_bin", "basic_cell", "basic_scanning", "basic_capacitor", "basic_micro_laser", "micro_mani", "desttagger", "handlabel", "packagewrap",
"destructive_analyzer", "circuit_imprinter", "experimentor", "rdconsole", "design_disk", "tech_disk", "rdserver", "rdservercontrol", "mechfab", "paystand",
"space_heater", "beaker", "large_beaker", "bucket", "xlarge_beaker", "sec_rshot", "sec_beanbag_slug", "sec_bshot", "sec_slug", "sec_Islug", "sec_Brslug", "sec_dart", "sec_38",
- "rglass","plasteel","plastitanium","plasmaglass","plasmareinforcedglass","titaniumglass","plastitaniumglass","plumbing_rcd", "antivirus")
+ "rglass","plasteel","plastitanium","plasmaglass","plasmareinforcedglass","titaniumglass","plastitaniumglass","plumbing_rcd", "antivirus", "glasses_prescription")
/datum/techweb_node/mmi
id = "mmi"
+ tech_tier = 1
starting_node = TRUE
display_name = "Man Machine Interface"
description = "A slightly Frankensteinian device that allows human brains to interface natively with software APIs."
@@ -22,6 +24,7 @@
/datum/techweb_node/cyborg
id = "cyborg"
+ tech_tier = 1
starting_node = TRUE
display_name = "Cyborg Construction"
description = "Sapient robots with preloaded tool modules and programmable laws."
@@ -30,6 +33,7 @@
/datum/techweb_node/mech
id = "mecha"
+ tech_tier = 1
starting_node = TRUE
display_name = "Mechanical Exosuits"
description = "Mechanized exosuits that are several magnitudes stronger and more powerful than the average human."
@@ -38,6 +42,7 @@
/datum/techweb_node/mech_tools
id = "mech_tools"
+ tech_tier = 1
starting_node = TRUE
display_name = "Basic Exosuit Equipment"
description = "Various tools fit for basic mech units"
@@ -45,14 +50,27 @@
/datum/techweb_node/basic_tools
id = "basic_tools"
+ tech_tier = 0
starting_node = TRUE
display_name = "Basic Tools"
description = "Basic mechanical, electronic, surgical and botanical tools."
- design_ids = list("screwdriver", "wrench", "wirecutters", "crowbar", "multitool", "welding_tool", "tscanner", "analyzer", "cable_coil", "pipe_painter", "airlock_painter", "scalpel", "circular_saw", "surgicaldrill", "retractor", "cautery", "blood_filter", "hemostat", "surgical_drapes", "syringe", "cultivator", "plant_analyzer", "shovel", "spade", "hatchet", "mop")
+ design_ids = list("discovery_scanner", "screwdriver", "wrench", "wirecutters", "crowbar", "multitool", "welding_tool", "tscanner", "analyzer", "cable_coil", "pipe_painter", "airlock_painter", "decal_painter", "scalpel", "circular_saw", "surgicaldrill", "retractor", "cautery", "blood_filter", "hemostat", "surgical_drapes", "syringe", "cultivator", "plant_analyzer", "shovel", "spade", "hatchet", "mop")
+
+/datum/techweb_node/basic_circuitry
+ id = "basic_circuitry"
+ tech_tier = 0
+ starting_node = TRUE
+ display_name = "Basic Integrated Circuits"
+ description = "Research on how to fully exploit the power of integrated circuits"
+ design_ids = list("integrated_circuit", "circuit_multitool", "comp_arithmetic", "comp_clock", "comp_combiner", "comp_comparison", "comp_delay",
+ "comp_direction", "comp_get_column", "comp_gps", "comp_health", "comp_hear", "comp_light", "comp_logic", "comp_index_table", "comp_mmi", "comp_multiplexer", "comp_not", "comp_ram",
+ "comp_random", "comp_round", "comp_select_query", "comp_species", "comp_textcase", "comp_trig", "comp_speech", "comp_concat", "comp_concat_list", "comp_speech", "comp_self", "comp_soundemitter", "comp_radio", "comp_tostring",
+ "comp_typecheck", "compact_remote_shell", "component_printer", "comp_string_contains", "usb_cable")
/////////////////////////Biotech/////////////////////////
/datum/techweb_node/biotech
id = "biotech"
+ tech_tier = 1
display_name = "Biological Technology"
description = "What makes us tick." //the MC, silly!
prereq_ids = list("base")
@@ -62,6 +80,7 @@
/datum/techweb_node/adv_biotech
id = "adv_biotech"
+ tech_tier = 2
display_name = "Advanced Biotechnology"
description = "Advanced Biotechnology"
prereq_ids = list("biotech")
@@ -71,6 +90,7 @@
/datum/techweb_node/bio_process
id = "bio_process"
+ tech_tier = 1
display_name = "Biological Processing"
description = "From slimes to kitchens."
prereq_ids = list("biotech")
@@ -81,6 +101,7 @@
/////////////////////////Advanced Surgery/////////////////////////
/datum/techweb_node/imp_wt_surgery
id = "imp_wt_surgery"
+ tech_tier = 2
display_name = "Improved Wound-Tending Surgery"
description = "Who would have known being more gentle with a hemostat decreases patient pain?"
prereq_ids = list("adv_biotech")
@@ -91,6 +112,7 @@
/datum/techweb_node/adv_surgery
id = "adv_surgery"
+ tech_tier = 3
display_name = "Advanced Surgery"
description = "When simple medicine doesn't cut it."
prereq_ids = list("imp_wt_surgery")
@@ -100,15 +122,17 @@
/datum/techweb_node/exp_surgery
id = "exp_surgery"
+ tech_tier = 4
display_name = "Experimental Surgery"
description = "When evolution isn't fast enough."
prereq_ids = list("adv_surgery")
- design_ids = list("surgery_revival","surgery_pacify","surgery_vein_thread","surgery_muscled_veins","surgery_nerve_splice","surgery_nerve_ground","surgery_ligament_hook","surgery_ligament_reinforcement","surgery_viral_bond", "surgery_heal_combo_upgrade", "autodoc")
+ design_ids = list("surgery_revival","surgery_pacify","surgery_vein_thread","surgery_muscled_veins","surgery_nerve_splice","surgery_nerve_ground","surgery_ligament_hook","surgery_ligament_reinforcement","surgery_viral_bond", "surgery_heal_combo_upgrade")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000)
export_price = 5000
/datum/techweb_node/alien_surgery
id = "alien_surgery"
+ tech_tier = 5
display_name = "Alien Surgery"
description = "Abductors did nothing wrong."
prereq_ids = list("exp_surgery", "alientech")
@@ -119,33 +143,17 @@
/////////////////////////data theory tech/////////////////////////
/datum/techweb_node/datatheory //Computer science
id = "datatheory"
+ tech_tier = 1
display_name = "Data Theory"
description = "Big Data, in space!"
prereq_ids = list("base")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
export_price = 5000
-/datum/techweb_node/circuitresearch
- id = "circuitresearch"
- display_name = "Circuit Research"
- description = "Modular circuitry adaptable to a wide range of utilities."
- prereq_ids = list("datatheory")
- design_ids = list("icprinter")
- research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
- export_price = 5000
-
-/datum/techweb_node/circuitupgrades
- id = "circuitupgrades"
- display_name = "Advanced Circuit Research"
- description = "Advanced designs that expand the possibilities of modular circuits."
- prereq_ids = list("circuitresearch")
- design_ids = list("icupgadv", "icupgclo")
- research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000)
- export_price = 5000
-
/////////////////////////engineering tech/////////////////////////
/datum/techweb_node/engineering
id = "engineering"
+ tech_tier = 1
display_name = "Industrial Engineering"
description = "A refresher course on modern engineering technology."
prereq_ids = list("base")
@@ -158,15 +166,17 @@
/datum/techweb_node/adv_engi
id = "adv_engi"
+ tech_tier = 2
display_name = "Advanced Engineering"
description = "Pushing the boundaries of physics, one chainsaw-fist at a time."
prereq_ids = list("engineering", "emp_basic")
- design_ids = list("engine_goggles", "magboots", "forcefield_projector", "weldingmask", "rcd_loaded", "rpd_loaded")
+ design_ids = list("engine_goggles", "magboots", "weldingmask", "rcd_loaded", "rpd_loaded", "ranged_analyzer")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
export_price = 5000
/datum/techweb_node/anomaly
id = "anomaly_research"
+ tech_tier = 4
display_name = "Anomaly Research"
description = "Unlock the potential of the mysterious anomalies that appear on station."
prereq_ids = list("adv_engi", "practical_bluespace")
@@ -176,6 +186,7 @@
/datum/techweb_node/high_efficiency
id = "high_efficiency"
+ tech_tier = 3
display_name = "High Efficiency Parts"
description = "Finely-tooled manufacturing techniques allowing for picometer-perfect precision levels."
prereq_ids = list("engineering", "datatheory")
@@ -185,6 +196,7 @@
/datum/techweb_node/adv_power
id = "adv_power"
+ tech_tier = 3
display_name = "Advanced Power Manipulation"
description = "How to get more zap."
prereq_ids = list("engineering")
@@ -195,15 +207,17 @@
/////////////////////////Bluespace tech/////////////////////////
/datum/techweb_node/bluespace_basic //Bluespace-memery
id = "bluespace_basic"
+ tech_tier = 4
display_name = "Basic Bluespace Theory"
description = "Basic studies into the mysterious alternate dimension known as bluespace."
prereq_ids = list("base")
- design_ids = list("beacon", "dragnetbeacon", "xenobioconsole", "telesci_gps", "bluespace_crystal", "spaceship_navigation_beacon")
+ design_ids = list("beacon", "dragnetbeacon", "xenobioconsole", "telesci_gps", "bluespace_crystal")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
export_price = 5000
/datum/techweb_node/bluespace_travel
id = "bluespace_travel"
+ tech_tier = 5
display_name = "Bluespace Travel"
description = "Application of Bluespace for static teleportation technology."
prereq_ids = list("practical_bluespace")
@@ -213,15 +227,50 @@
/datum/techweb_node/micro_bluespace
id = "micro_bluespace"
+ tech_tier = 5
display_name = "Miniaturized Bluespace Research"
description = "Extreme reduction in space required for bluespace engines, leading to portable bluespace technology."
prereq_ids = list("bluespace_travel", "practical_bluespace", "high_efficiency")
- design_ids = list("bluespace_matter_bin", "femto_mani", "triphasic_scanning", "bag_holding", "quantum_keycard", "wormholeprojector", "swapper", "antivirus4")
+ design_ids = list("bluespace_matter_bin", "femto_mani", "triphasic_scanning", "quantum_keycard", "antivirus4")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 10000)
export_price = 5000
+/datum/techweb_node/bag_of_holding
+ id = "bagofholding"
+ tech_tier = 5
+ display_name = "Bag of Holding"
+ description = "Portable bluespace technology allows the production of backpacks that can store a greater volume of items than the volume of the bag."
+ prereq_ids = list("micro_bluespace")
+ design_ids = list("bag_holding")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
+ export_price = 5000
+ hidden = TRUE
+
+/datum/techweb_node/wormhole_gun
+ id = "wormholegun"
+ tech_tier = 5
+ display_name = "Bluespace Wormhole Projector"
+ description = "Develop the research required to create a miniaturized bluespace wormhole projector, allowing you to jump between two places instantly."
+ prereq_ids = list("micro_bluespace")
+ design_ids = list("wormholeprojector")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000)
+ export_price = 5000
+ hidden = TRUE
+
+/datum/techweb_node/quantum_spin
+ id = "qswapper"
+ tech_tier = 5
+ display_name = "Quantum Spin Inverter"
+ description = "Research the ability to create an experimental device that is able to swap the locations of two entities by switching their particles' spin values. Must be linked to another device to function."
+ prereq_ids = list("micro_bluespace")
+ design_ids = list("swapper")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
+ export_price = 5000
+ hidden = TRUE
+
/datum/techweb_node/practical_bluespace
id = "practical_bluespace"
+ tech_tier = 4
display_name = "Applied Bluespace Research"
description = "Using bluespace to make things faster and better."
prereq_ids = list("bluespace_basic", "engineering")
@@ -231,6 +280,7 @@
/datum/techweb_node/bluespace_power
id = "bluespace_power"
+ tech_tier = 4
display_name = "Bluespace Power Technology"
description = "Even more powerful.. power!"
prereq_ids = list("adv_power", "practical_bluespace")
@@ -240,6 +290,7 @@
/datum/techweb_node/unregulated_bluespace
id = "unregulated_bluespace"
+ tech_tier = 5
display_name = "Unregulated Bluespace Research"
description = "Bluespace technology using unstable or unbalanced procedures, prone to damaging the fabric of bluespace. Outlawed by galactic conventions."
prereq_ids = list("bluespace_travel", "syndicate_basic")
@@ -251,6 +302,7 @@
/////////////////////////plasma tech/////////////////////////
/datum/techweb_node/basic_plasma
id = "basic_plasma"
+ tech_tier = 1
display_name = "Basic Plasma Research"
description = "Research into the mysterious and dangerous substance, plasma."
prereq_ids = list("engineering")
@@ -260,6 +312,7 @@
/datum/techweb_node/adv_plasma
id = "adv_plasma"
+ tech_tier = 2
display_name = "Advanced Plasma Research"
description = "Research on how to fully exploit the power of plasma."
prereq_ids = list("basic_plasma")
@@ -270,43 +323,74 @@
/////////////////////////shuttle tech/////////////////////////
/datum/techweb_node/basic_shuttle_tech
id = "basic_shuttle"
+ tech_tier = 3
display_name = "Basic Shuttle Research"
description = "Research the technology required to create and use basic shuttles."
prereq_ids = list("bluespace_travel", "adv_engi")
- design_ids = list("shuttle_creator", "engine_plasma", "engine_heater", "shuttle_control", "shuttle_docker", "wingpack")
+ design_ids = list("shuttle_creator", "engine_plasma", "engine_heater", "shuttle_control", "wingpack")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 10000)
export_price = 5000
-/datum/techweb_node/shuttle_route_upgrade
- id = "shuttle_route_upgrade"
- display_name = "Route Optimisation Upgrade"
- description = "Research into bluespace tunnelling, allowing us to reduce flight times by up to 20%!"
- prereq_ids = list("basic_shuttle")
- design_ids = list("disk_shuttle_route")
- research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
- export_price = 2500
-
-/datum/techweb_node/shuttle_route_upgrade_hyper
- id = "shuttle_route_upgrade_hyper"
- display_name = "Hyperlane Optimisation Upgrade"
- description = "Research into bluespace hyperlane, allowing us to reduce flight times by up to 40%!"
- prereq_ids = list("shuttle_route_upgrade", "micro_bluespace")
- design_ids = list("disk_shuttle_route_hyper")
- research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000)
- export_price = 5000
-
-/datum/techweb_node/shuttle_route_upgrade_void
- id = "shuttle_route_upgrade_void"
- display_name = "Nullspace Breaching Upgrade"
+/datum/techweb_node/nullspacebreaching
+ id = "nullspacebreaching"
+ display_name = "Nullspace Breaching"
description = "Research into voidspace tunnelling, allowing us to significantly reduce flight times."
- prereq_ids = list("shuttle_route_upgrade_hyper", "alientech")
- design_ids = list("disk_shuttle_route_void", "engine_void", "wingpack_ayy")
+ prereq_ids = list("basic_shuttle", "alientech")
+ design_ids = list("engine_void", "wingpack_ayy")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 12500)
export_price = 5000
+/////////////////////////integrated circuits tech/////////////////////////
+
+/datum/techweb_node/math_circuits
+ id = "math_circuits"
+ tech_tier = 1
+ display_name = "Math Circuitry"
+ description = "Development of more complex mathematical components for all your number manipulating needs"
+ prereq_ids = list("basic_circuitry", "datatheory")
+ design_ids = list("comp_adv_trig","comp_hyper_trig", "comp_bitwise", "comp_bitflag")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 1000)
+
+/datum/techweb_node/list_circuits
+ id = "list_circuits"
+ tech_tier = 1
+ display_name = "List Circuitry"
+ description = "Configures new integrated circuit components capable of representing one dimensional data structures such as arrays, stacks, and queues."
+ prereq_ids = list("basic_circuitry", "datatheory")
+ design_ids = list("comp_index", "comp_write", "comp_append", "comp_pop", "comp_length", "comp_list_constructor", "comp_list_length_constructor")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 1000)
+
+/datum/techweb_node/adv_shells
+ id = "adv_shells"
+ tech_tier = 2
+ display_name = "Advanced Shell Research"
+ description = "Grants access to more complicated shell designs."
+ prereq_ids = list("basic_circuitry", "engineering")
+ design_ids = list("controller_shell", "scanner_shell", "bot_shell", "door_shell", "money_bot_shell")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
+
+/datum/techweb_node/movable_shells_tech
+ id = "movable_shells"
+ tech_tier = 2
+ display_name = "Movable Shell Research"
+ description = "Grants access to movable shells."
+ prereq_ids = list("adv_shells", "robotics")
+ design_ids = list("comp_pull", "drone_shell")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 3000)
+
+/datum/techweb_node/server_shell_tech
+ id = "server_shell"
+ tech_tier = 2
+ display_name = "Server Technology Research"
+ description = "Grants access to a server shell that has a very high capacity for components."
+ prereq_ids = list("adv_shells", "computer_hardware_basic")
+ design_ids = list("server_shell")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 3000)
+
/////////////////////////robotics tech/////////////////////////
/datum/techweb_node/robotics
id = "robotics"
+ tech_tier = 2
display_name = "Basic Robotics Research"
description = "Programmable machines that make our lives lazier."
prereq_ids = list("base")
@@ -316,6 +400,7 @@
/datum/techweb_node/adv_robotics
id = "adv_robotics"
+ tech_tier = 3
display_name = "Advanced Robotics Research"
description = "It can even do the dishes!"
prereq_ids = list("robotics")
@@ -325,6 +410,7 @@
/datum/techweb_node/neural_programming
id = "neural_programming"
+ tech_tier = 2
display_name = "Neural Programming"
description = "Study into networks of processing units that mimic our brains."
prereq_ids = list("biotech", "datatheory")
@@ -342,6 +428,7 @@
/datum/techweb_node/cyborg_upg_util
id = "cyborg_upg_util"
+ tech_tier = 3
display_name = "Cyborg Upgrades: Utility"
description = "Utility upgrades for cyborgs."
prereq_ids = list("engineering")
@@ -351,6 +438,7 @@
/datum/techweb_node/cyborg_upg_med
id = "cyborg_upg_med"
+ tech_tier = 3
display_name = "Cyborg Upgrades: Medical"
description = "Medical upgrades for cyborgs."
prereq_ids = list("adv_biotech")
@@ -360,15 +448,41 @@
/datum/techweb_node/cyborg_upg_combat
id = "cyborg_upg_combat"
+ tech_tier = 3
display_name = "Cyborg Upgrades: Combat"
description = "Military grade upgrades for cyborgs."
prereq_ids = list("adv_robotics", "adv_engi" , "weaponry")
- design_ids = list("borg_upgrade_vtec", "borg_upgrade_disablercooler")
+ design_ids = list("borg_upgrade_vtec")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000)
+ export_price = 5000
+
+/datum/techweb_node/cyborg_upg_service
+ id = "cyborg_upg_service"
+ tech_tier = 3
+ display_name = "Cyborg Upgrades: Service"
+ description = "Allows service borgs to specialize with various modules."
+ prereq_ids = list("cyborg_upg_util")
+ design_ids = list("borg_upgrade_casino", "borg_upgrade_kitchen", "borg_upgrade_botany", "borg_upgrade_party")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 1500)
+ export_price = 1000
+
+/datum/techweb_node/cyborg_upg_security
+ id = "cyborg_upg_security"
+ tech_tier = 4
+ display_name = "Cyborg Upgrades: Security"
+ description = "Militia grade upgrades for cyborgs."
+ prereq_ids = list("adv_robotics", "adv_engi" , "weaponry")
+ design_ids = list("borg_transform_security", "borg_upgrade_disablercooler")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000)
export_price = 5000
+/datum/techweb_node/cyborg_upg_security/New() //Techweb nodes don't have an init,
+ . = ..()
+ hidden = CONFIG_GET(flag/disable_secborg)
+
/datum/techweb_node/ai
id = "ai"
+ tech_tier = 3
display_name = "Artificial Intelligence"
description = "AI unit research."
prereq_ids = list("robotics", "posibrain")
@@ -381,6 +495,7 @@
/////////////////////////EMP tech/////////////////////////
/datum/techweb_node/emp_basic //EMP tech for some reason
id = "emp_basic"
+ tech_tier = 2
display_name = "Electromagnetic Theory"
description = "Study into usage of frequencies in the electromagnetic spectrum."
prereq_ids = list("base")
@@ -390,6 +505,7 @@
/datum/techweb_node/emp_adv
id = "emp_adv"
+ tech_tier = 3
display_name = "Advanced Electromagnetic Theory"
description = "Determining whether reversing the polarity will actually help in a given situation."
prereq_ids = list("emp_basic")
@@ -399,6 +515,7 @@
/datum/techweb_node/emp_super
id = "emp_super"
+ tech_tier = 4
display_name = "Quantum Electromagnetic Technology" //bs
description = "Even better electromagnetic technology."
prereq_ids = list("emp_adv")
@@ -409,6 +526,7 @@
/////////////////////////Clown tech/////////////////////////
/datum/techweb_node/clown
id = "clown"
+ tech_tier = 2
display_name = "Clown Technology"
description = "Honk?!"
prereq_ids = list("base")
@@ -420,15 +538,17 @@
////////////////////////Computer tech////////////////////////
/datum/techweb_node/comptech
id = "comptech"
+ tech_tier = 1
display_name = "Computer Consoles"
description = "Computers and how they work."
prereq_ids = list("datatheory")
- design_ids = list("cargo", "cargorequest", "libraryconsole", "mining", "crewconsole", "rdcamera", "comconsole", "idcardconsole", "seccamera")
+ design_ids = list("cargo", "cargorequest", "objective", "libraryconsole", "mining", "crewconsole", "rdcamera", "comconsole", "idcardconsole", "seccamera")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2000)
export_price = 5000
/datum/techweb_node/computer_hardware_basic //Modular computers are shitty and nearly useless so until someone makes them actually useful this can be easy to get.
id = "computer_hardware_basic"
+ tech_tier = 1
display_name = "Computer Hardware"
description = "How computer hardware are made."
prereq_ids = list("comptech")
@@ -440,6 +560,7 @@
/datum/techweb_node/computer_board_gaming
id = "computer_board_gaming"
+ tech_tier = 1
display_name = "Arcade Games"
description = "For the slackers on the station."
prereq_ids = list("comptech")
@@ -449,6 +570,7 @@
/datum/techweb_node/comp_recordkeeping
id = "comp_recordkeeping"
+ tech_tier = 2
display_name = "Computerized Recordkeeping"
description = "Organized record databases and how they're used."
prereq_ids = list("comptech")
@@ -458,6 +580,7 @@
/datum/techweb_node/telecomms
id = "telecomms"
+ tech_tier = 3
display_name = "Telecommunications Technology"
description = "Subspace transmission technology for near-instant communications devices."
prereq_ids = list("comptech", "bluespace_basic")
@@ -468,6 +591,7 @@
/datum/techweb_node/integrated_HUDs
id = "integrated_HUDs"
+ tech_tier = 3
display_name = "Integrated HUDs"
description = "The usefulness of computerized records, projected straight onto your eyepiece!"
prereq_ids = list("comp_recordkeeping", "emp_basic")
@@ -477,6 +601,7 @@
/datum/techweb_node/NVGtech
id = "NVGtech"
+ tech_tier = 3
display_name = "Night Vision Technology"
description = "Allows seeing in the dark without actual light!"
prereq_ids = list("integrated_HUDs", "adv_engi", "emp_adv")
@@ -487,6 +612,7 @@
////////////////////////Medical////////////////////////
/datum/techweb_node/cloning
id = "cloning"
+ tech_tier = 3
display_name = "Genetic Engineering"
description = "We have the technology to make him."
prereq_ids = list("biotech")
@@ -496,6 +622,7 @@
/datum/techweb_node/cryotech
id = "cryotech"
+ tech_tier = 3
display_name = "Cryostasis Technology"
description = "Smart freezing of objects to preserve them!"
prereq_ids = list("adv_engi", "biotech")
@@ -505,6 +632,7 @@
/datum/techweb_node/subdermal_implants
id = "subdermal_implants"
+ tech_tier = 4
display_name = "Subdermal Implants"
description = "Electronic implants buried beneath the skin."
prereq_ids = list("biotech")
@@ -514,6 +642,7 @@
/datum/techweb_node/cyber_organs
id = "cyber_organs"
+ tech_tier = 4
display_name = "Cybernetic Organs"
description = "We have the technology to rebuild him."
prereq_ids = list("adv_biotech")
@@ -523,6 +652,7 @@
/datum/techweb_node/cyber_organs_upgraded
id = "cyber_organs_upgraded"
+ tech_tier = 5
display_name = "Upgraded Cybernetic Organs"
description = "We have the technology to upgrade him."
prereq_ids = list("cyber_organs")
@@ -532,6 +662,7 @@
/datum/techweb_node/ipc_organs
id = "ipc_organs"
+ tech_tier = 3
display_name = "IPC Parts"
description = "We have the technology to replace him."
prereq_ids = list("cyber_organs","robotics")
@@ -541,6 +672,7 @@
/datum/techweb_node/cyber_implants
id = "cyber_implants"
+ tech_tier = 4
display_name = "Cybernetic Implants"
description = "Electronic implants that improve humans."
prereq_ids = list("adv_biotech", "datatheory")
@@ -550,44 +682,51 @@
/datum/techweb_node/adv_cyber_implants
id = "adv_cyber_implants"
+ tech_tier = 5
display_name = "Advanced Cybernetic Implants"
description = "Upgraded and more powerful cybernetic implants."
prereq_ids = list("neural_programming", "cyber_implants","integrated_HUDs")
- design_ids = list("ci-toolset", "ci-surgery", "ci-reviver", "ci-nutrimentplus")
+ design_ids = list("ci-toolset", "ci-surgery","ci-janitor", "ci-botany", "ci-reviver", "ci-nutrimentplus")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
export_price = 5000
/datum/techweb_node/combat_cyber_implants
id = "combat_cyber_implants"
+ tech_tier = 5
display_name = "Combat Cybernetic Implants"
description = "Military grade combat implants to improve performance."
prereq_ids = list("adv_cyber_implants","weaponry","NVGtech","high_efficiency")
design_ids = list("ci-xray", "ci-thermals", "ci-antidrop", "ci-antistun", "ci-thrusters")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
export_price = 5000
+ hidden = TRUE
/datum/techweb_node/adv_combat_cyber_implants
id = "adv_combat_cyber_implants"
+ tech_tier = 5
display_name = "Advanced Combat Cybernetic Implants"
description = "Experimental military cybernetic weapons."
- prereq_ids = list("combat_cyber_implants", "syndicate_basic")
+ prereq_ids = list("adv_cyber_implants", "syndicate_basic")
design_ids = list("hydraulic_blade")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 10000)
export_price = 10000
+ hidden = TRUE
////////////////////////Tools////////////////////////
/datum/techweb_node/basic_mining
id = "basic_mining"
+ tech_tier = 1
display_name = "Mining Technology"
description = "Better than Efficiency V."
prereq_ids = list("engineering", "basic_plasma")
- design_ids = list("drill", "superresonator", "triggermod", "damagemod", "cooldownmod", "rangemod", "ore_redemption", "mining_equipment_vendor", "cargoexpress", "plasmacutter")//e a r l y g a m e)
+ design_ids = list("drill", "superresonator", "triggermod", "damagemod", "cooldownmod", "rangemod", "ore_redemption", "mining_equipment_vendor", "exploration_equipment_vendor", "cargoexpress", "plasmacutter")//e a r l y g a m e)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
export_price = 5000
/datum/techweb_node/adv_mining
id = "adv_mining"
+ tech_tier = 3
display_name = "Advanced Mining Technology"
description = "Efficiency Level 127" //dumb mc references
prereq_ids = list("basic_mining", "adv_engi", "adv_power", "adv_plasma")
@@ -595,17 +734,9 @@
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
export_price = 5000
-/datum/techweb_node/bluespace_mining
- id = "bluespace_mining"
- display_name = "Bluespace Mining Technology"
- description = "Harness the power of bluespace to make materials out of nothing. Slowly."
- prereq_ids = list("practical_bluespace", "adv_mining")
- design_ids = list("bluespace_miner")
- research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
- export_price = 5000
-
/datum/techweb_node/janitor
id = "janitor"
+ tech_tier = 1
display_name = "Advanced Sanitation Technology"
description = "Clean things better, faster, stronger, and harder!"
prereq_ids = list("adv_engi")
@@ -615,6 +746,7 @@
/datum/techweb_node/botany
id = "botany"
+ tech_tier = 1
display_name = "Botanical Engineering"
description = "Botanical tools"
prereq_ids = list("adv_engi", "biotech")
@@ -624,6 +756,7 @@
/datum/techweb_node/exp_tools
id = "exp_tools"
+ tech_tier = 3
display_name = "Experimental Tools"
description = "Highly advanced tools."
design_ids = list("exwelder", "jawsoflife", "handdrill", "laserscalpel", "mechanicalpinches", "searingtool")
@@ -633,6 +766,7 @@
/datum/techweb_node/sec_basic
id = "sec_basic"
+ tech_tier = 1
display_name = "Basic Security Equipment"
description = "Standard equipment used by security."
design_ids = list("seclite", "pepperspray", "bola_energy", "zipties", "evidencebag", "flashbulb")
@@ -642,6 +776,7 @@
/datum/techweb_node/rcd_upgrade
id = "rcd_upgrade"
+ tech_tier = 3
display_name = "RCD designs upgrade"
description = "Unlocks new RCD designs."
design_ids = list("rcd_upgrade_frames", "rcd_upgrade_simple_circuits")
@@ -651,6 +786,7 @@
/datum/techweb_node/adv_rcd_upgrade
id = "adv_rcd_upgrade"
+ tech_tier = 4
display_name = "Advanced RCD designs upgrade"
description = "Unlocks new RCD designs."
design_ids = list("rcd_upgrade_silo_link")
@@ -663,6 +799,7 @@
/datum/techweb_node/landmine
id = "nonlethal_mines"
+ tech_tier = 3
display_name = "Nonlethal Landmine Technology"
description = "Our weapons technicians could perhaps work out methods for the creation of nonlethal landmines for security teams."
prereq_ids = list("sec_basic")
@@ -672,6 +809,7 @@
/datum/techweb_node/weaponry
id = "weaponry"
+ tech_tier = 3
display_name = "Weapon Development Technology"
description = "Our researchers have found new ways to weaponize just about everything now."
prereq_ids = list("engineering")
@@ -681,6 +819,7 @@
/datum/techweb_node/smartmine
id = "smart_mines"
+ tech_tier = 4
display_name = "Smart Landmine Technology"
description = "Using IFF technology, we could develop smartmines that do not trigger for those who are mindshielded."
prereq_ids = list("weaponry", "nonlethal_mines", "engineering")
@@ -690,6 +829,7 @@
/datum/techweb_node/adv_weaponry
id = "adv_weaponry"
+ tech_tier = 4
display_name = "Advanced Weapon Development Technology"
description = "Our weapons are breaking the rules of reality by now."
prereq_ids = list("adv_engi", "weaponry")
@@ -699,6 +839,7 @@
/datum/techweb_node/advmine
id = "adv_mines"
+ tech_tier = 4
display_name = "Advanced Landmine Technology"
description = "We can further develop our smartmines to build some extremely capable designs."
prereq_ids = list("weaponry", "smart_mines", "adv_engi")
@@ -708,6 +849,7 @@
/datum/techweb_node/electric_weapons
id = "electronic_weapons"
+ tech_tier = 4
display_name = "Electric Weapons"
description = "Weapons using electric technology"
prereq_ids = list("weaponry", "adv_power" , "emp_basic")
@@ -717,15 +859,18 @@
/datum/techweb_node/radioactive_weapons
id = "radioactive_weapons"
+ tech_tier = 5
display_name = "Radioactive Weaponry"
description = "Weapons using radioactive technology."
prereq_ids = list("adv_engi", "adv_weaponry")
design_ids = list("nuclear_gun")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
export_price = 5000
+ hidden = TRUE
/datum/techweb_node/medical_weapons
id = "medical_weapons"
+ tech_tier = 4
display_name = "Medical Weaponry"
description = "Weapons using medical technology."
prereq_ids = list("adv_biotech", "weaponry")
@@ -735,24 +880,29 @@
/datum/techweb_node/beam_weapons
id = "beam_weapons"
+ tech_tier = 4
display_name = "Beam Weaponry"
description = "Various basic beam weapons"
prereq_ids = list("adv_weaponry")
design_ids = list("temp_gun", "xray_laser")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
export_price = 5000
+ hidden = TRUE
/datum/techweb_node/adv_beam_weapons
id = "adv_beam_weapons"
+ tech_tier = 5
display_name = "Advanced Beam Weaponry"
description = "Various advanced beam weapons"
- prereq_ids = list("beam_weapons")
+ prereq_ids = list("adv_weaponry")
design_ids = list("beamrifle")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
export_price = 5000
+ hidden = TRUE
/datum/techweb_node/explosive_weapons
id = "explosive_weapons"
+ tech_tier = 3
display_name = "Explosive & Pyrotechnical Weaponry"
description = "If the light stuff just won't do it."
prereq_ids = list("adv_weaponry")
@@ -762,24 +912,28 @@
/datum/techweb_node/ballistic_weapons
id = "ballistic_weapons"
+ tech_tier = 3
display_name = "Ballistic Weaponry"
description = "This isn't research.. This is reverse-engineering!"
prereq_ids = list("weaponry")
- design_ids = list("mag_oldsmg", "mag_oldsmg_ap", "mag_oldsmg_ic")
+ design_ids = list("mag_oldsmg", "mag_oldsmg_ap", "mag_oldsmg_ic", "mag_oldsmg_rubber")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
export_price = 5000
/datum/techweb_node/exotic_ammo
id = "exotic_ammo"
+ tech_tier = 4
display_name = "Exotic Ammunition"
description = "They won't know what hit em."
prereq_ids = list("adv_weaponry", "medical_weapons")
design_ids = list("techshotshell", "c38_hotshot", "c38_iceblox", "shotgundartcryostasis")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
export_price = 5000
+ hidden = TRUE
/datum/techweb_node/gravity_gun
id = "gravity_gun"
+ tech_tier = 5
display_name = "One-point Bluespace-gravitational Manipulator"
description = "Fancy wording for gravity gun."
prereq_ids = list("adv_weaponry", "bluespace_travel")
@@ -790,6 +944,7 @@
////////////////////////mech technology////////////////////////
/datum/techweb_node/adv_mecha
id = "adv_mecha"
+ tech_tier = 3
display_name = "Advanced Exosuits"
description = "For when you just aren't Gundam enough."
prereq_ids = list("adv_robotics")
@@ -799,6 +954,7 @@
/datum/techweb_node/odysseus
id = "mecha_odysseus"
+ tech_tier = 3
display_name = "EXOSUIT: Odysseus"
description = "Odysseus exosuit designs"
prereq_ids = list("base")
@@ -809,6 +965,7 @@
/datum/techweb_node/gygax
id = "mech_gygax"
+ tech_tier = 4
display_name = "EXOSUIT: Gygax"
description = "Gygax exosuit designs"
prereq_ids = list("adv_mecha", "weaponry")
@@ -819,6 +976,7 @@
/datum/techweb_node/durand
id = "mech_durand"
+ tech_tier = 4
display_name = "EXOSUIT: Durand"
description = "Durand exosuit designs"
prereq_ids = list("adv_mecha", "adv_weaponry")
@@ -829,6 +987,7 @@
/datum/techweb_node/phazon
id = "mecha_phazon"
+ tech_tier = 5
display_name = "EXOSUIT: Phazon"
description = "Phazon exosuit designs"
prereq_ids = list("adv_mecha", "weaponry" , "micro_bluespace")
@@ -836,9 +995,11 @@
"phazon_peri", "phazon_targ", "phazon_armor")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
export_price = 5000
+ hidden = TRUE
/datum/techweb_node/adv_mecha_tools
id = "adv_mecha_tools"
+ tech_tier = 3
display_name = "Advanced Exosuit Equipment"
description = "Tools for high level mech suits"
prereq_ids = list("adv_mecha")
@@ -848,6 +1009,7 @@
/datum/techweb_node/med_mech_tools
id = "med_mech_tools"
+ tech_tier = 3
display_name = "Medical Exosuit Equipment"
description = "Tools for high level mech suits"
prereq_ids = list("adv_biotech")
@@ -857,6 +1019,7 @@
/datum/techweb_node/mech_modules
id = "adv_mecha_modules"
+ tech_tier = 3
display_name = "Simple Exosuit Modules"
description = "An advanced piece of mech weaponry"
prereq_ids = list("adv_mecha", "bluespace_power")
@@ -866,6 +1029,7 @@
/datum/techweb_node/mech_scattershot
id = "mecha_tools"
+ tech_tier = 4
display_name = "Exosuit Weapon (LBX AC 10 \"Scattershot\")"
description = "An advanced piece of mech weaponry"
prereq_ids = list("ballistic_weapons")
@@ -875,6 +1039,7 @@
/datum/techweb_node/mech_carbine
id = "mech_carbine"
+ tech_tier = 4
display_name = "Exosuit Weapon (FNX-99 \"Hades\" Carbine)"
description = "An advanced piece of mech weaponry"
prereq_ids = list("ballistic_weapons")
@@ -884,6 +1049,7 @@
/datum/techweb_node/mech_ion
id = "mmech_ion"
+ tech_tier = 4
display_name = "Exosuit Weapon (MKIV Ion Heavy Cannon)"
description = "An advanced piece of mech weaponry"
prereq_ids = list("electronic_weapons", "emp_adv")
@@ -893,6 +1059,7 @@
/datum/techweb_node/mech_tesla
id = "mech_tesla"
+ tech_tier = 4
display_name = "Exosuit Weapon (MKI Tesla Cannon)"
description = "An advanced piece of mech weaponry"
prereq_ids = list("electronic_weapons", "adv_power")
@@ -902,6 +1069,7 @@
/datum/techweb_node/mech_laser
id = "mech_laser"
+ tech_tier = 4
display_name = "Exosuit Weapon (CH-PS \"Immolator\" Laser)"
description = "A basic piece of mech weaponry"
prereq_ids = list("beam_weapons")
@@ -911,6 +1079,7 @@
/datum/techweb_node/mech_laser_heavy
id = "mech_laser_heavy"
+ tech_tier = 4
display_name = "Exosuit Weapon (CH-LC \"Solaris\" Laser Cannon)"
description = "An advanced piece of mech weaponry"
prereq_ids = list("adv_beam_weapons")
@@ -920,6 +1089,7 @@
/datum/techweb_node/mech_disabler
id = "mech_disabler"
+ tech_tier = 4
display_name = "Exosuit Weapon (CH-DS \"Peacemaker\" Mounted Disabler)"
description = "A basic piece of mech weaponry"
prereq_ids = list("beam_weapons")
@@ -929,6 +1099,7 @@
/datum/techweb_node/mech_grenade_launcher
id = "mech_grenade_launcher"
+ tech_tier = 4
display_name = "Exosuit Weapon (SGL-6 Grenade Launcher)"
description = "An advanced piece of mech weaponry"
prereq_ids = list("explosive_weapons")
@@ -936,17 +1107,9 @@
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
export_price = 5000
-/datum/techweb_node/mech_missile_rack
- id = "mech_missile_rack"
- display_name = "Exosuit Weapon (SRM-8 Missile Rack)"
- description = "An advanced piece of mech weaponry"
- prereq_ids = list("explosive_weapons")
- design_ids = list("mech_missile_rack")
- research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
- export_price = 5000
-
/datum/techweb_node/clusterbang_launcher
id = "clusterbang_launcher"
+ tech_tier = 4
display_name = "Exosuit Module (SOB-3 Clusterbang Launcher)"
description = "An advanced piece of mech weaponry"
prereq_ids = list("explosive_weapons")
@@ -956,6 +1119,7 @@
/datum/techweb_node/mech_teleporter
id = "mech_teleporter"
+ tech_tier = 4
display_name = "Exosuit Module (Teleporter Module)"
description = "An advanced piece of mech Equipment"
prereq_ids = list("micro_bluespace")
@@ -965,6 +1129,7 @@
/datum/techweb_node/mech_wormhole_gen
id = "mech_wormhole_gen"
+ tech_tier = 4
display_name = "Exosuit Module (Localized Wormhole Generator)"
description = "An advanced piece of mech weaponry"
prereq_ids = list("bluespace_travel")
@@ -974,6 +1139,7 @@
/datum/techweb_node/mech_lmg
id = "mech_lmg"
+ tech_tier = 4
display_name = "Exosuit Weapon (\"Ultra AC 2\" LMG)"
description = "An advanced piece of mech weaponry"
prereq_ids = list("ballistic_weapons")
@@ -983,6 +1149,7 @@
/datum/techweb_node/mech_diamond_drill
id = "mech_diamond_drill"
+ tech_tier = 3
display_name = "Exosuit Diamond Drill"
description = "A diamond drill fit for a large exosuit"
prereq_ids = list("adv_mining")
@@ -993,6 +1160,7 @@
/////////////////////////Nanites/////////////////////////
/datum/techweb_node/nanite_base
id = "nanite_base"
+ tech_tier = 2
display_name = "Basic Nanite Programming"
description = "The basics of nanite construction and programming."
prereq_ids = list("datatheory")
@@ -1004,6 +1172,7 @@
/datum/techweb_node/nanite_smart
id = "nanite_smart"
+ tech_tier = 2
display_name = "Smart Nanite Programming"
description = "Nanite programs that require nanites to perform complex actions, act independently, roam or seek targets."
prereq_ids = list("nanite_base","robotics")
@@ -1013,6 +1182,7 @@
/datum/techweb_node/nanite_mesh
id = "nanite_mesh"
+ tech_tier = 2
display_name = "Mesh Nanite Programming"
description = "Nanite programs that require static structures and membranes."
prereq_ids = list("nanite_base","engineering")
@@ -1022,6 +1192,7 @@
/datum/techweb_node/nanite_bio
id = "nanite_bio"
+ tech_tier = 3
display_name = "Biological Nanite Programming"
description = "Nanite programs that require complex biological interaction."
prereq_ids = list("nanite_base","biotech")
@@ -1032,6 +1203,7 @@
/datum/techweb_node/nanite_neural
id = "nanite_neural"
+ tech_tier = 3
display_name = "Neural Nanite Programming"
description = "Nanite programs affecting nerves and brain matter."
prereq_ids = list("nanite_bio")
@@ -1041,6 +1213,7 @@
/datum/techweb_node/nanite_synaptic
id = "nanite_synaptic"
+ tech_tier = 4
display_name = "Synaptic Nanite Programming"
description = "Nanite programs affecting mind and thoughts."
prereq_ids = list("nanite_neural","neural_programming")
@@ -1050,24 +1223,27 @@
/datum/techweb_node/nanite_harmonic
id = "nanite_harmonic"
+ tech_tier = 4
display_name = "Harmonic Nanite Programming"
description = "Nanite programs that require seamless integration between nanites and biology."
prereq_ids = list("nanite_bio","nanite_smart","nanite_mesh")
- design_ids = list("fakedeath_nanites","aggressive_nanites","defib_nanites","regenerative_plus_nanites","brainheal_plus_nanites","purging_plus_nanites","adrenaline_nanites")
+ design_ids = list("fakedeath_nanites","aggressive_nanites","defib_nanites","regenerative_plus_nanites","brainheal_plus_nanites","purging_plus_nanites", "sensor_species_nanites","adrenaline_nanites")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2000, TECHWEB_POINT_TYPE_NANITES = 2000)
export_price = 8000
/datum/techweb_node/nanite_combat
id = "nanite_military"
+ tech_tier = 5
display_name = "Military Nanite Programming"
description = "Nanite programs that perform military-grade functions."
prereq_ids = list("nanite_harmonic", "syndicate_basic")
- design_ids = list("explosive_nanites","pyro_nanites","meltdown_nanites","viral_nanites","nanite_sting_nanites")
+ design_ids = list("explosive_nanites","pyro_nanites","meltdown_nanites","viral_nanites","nanite_sting_nanites", "haste_nanites")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500, TECHWEB_POINT_TYPE_NANITES = 2500)
export_price = 12500
/datum/techweb_node/nanite_hazard
id = "nanite_hazard"
+ tech_tier = 5
display_name = "Hazard Nanite Programs"
description = "Extremely advanced Nanite programs with the potential of being extremely dangerous."
prereq_ids = list("nanite_harmonic", "alientech")
@@ -1078,6 +1254,7 @@
////////////////////////Alien technology////////////////////////
/datum/techweb_node/alientech //AYYYYYYYYLMAOO tech
id = "alientech"
+ tech_tier = 5
display_name = "Alien Technology"
description = "Things used by the greys."
prereq_ids = list("biotech","engineering")
@@ -1094,6 +1271,7 @@
/datum/techweb_node/alien_bio
id = "alien_bio"
+ tech_tier = 5
display_name = "Alien Biological Tools"
description = "Advanced biological tools."
prereq_ids = list("alientech", "adv_biotech")
@@ -1107,6 +1285,7 @@
/datum/techweb_node/alien_engi
id = "alien_engi"
+ tech_tier = 5
display_name = "Alien Engineering"
description = "Alien engineering tools"
prereq_ids = list("alientech", "adv_engi")
@@ -1119,22 +1298,38 @@
/datum/techweb_node/syndicate_basic
id = "syndicate_basic"
+ tech_tier = 4
display_name = "Illegal Technology"
description = "Dangerous research used to create dangerous objects."
prereq_ids = list("adv_engi", "adv_weaponry", "explosive_weapons")
- design_ids = list("decloner", "borg_syndicate_module", "ai_cam_upgrade", "suppressor", "largecrossbow", "donksofttoyvendor", "donksoft_refill", "advanced_camera")
+ design_ids = list("decloner", "borg_syndicate_module", "ai_cam_upgrade", "suppressor", "largecrossbow", "donksofttoyvendor", "donksoft_refill", "advanced_camera" , "arcade_amputation")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 10000)
export_price = 5000
hidden = TRUE
-/datum/techweb_node/syndicate_basic/New() //Crappy way of making syndicate gear decon supported until there's another way.
- . = ..()
- boost_item_paths = list()
- for(var/path in GLOB.uplink_items)
- var/datum/uplink_item/UI = new path
- if(!UI.item || !UI.illegal_tech)
- continue
- boost_item_paths |= UI.item //allows deconning to unlock.
+/datum/techweb_node/sticky_basic
+ id = "sticky_basic"
+ tech_tier = 3
+ display_name = "Basic Sticky Technology"
+ description = "The only thing left to do after researching this tech is to start printing out a bunch of 'kick me' signs."
+ prereq_ids = list("syndicate_basic", "adv_engi")
+ design_ids = list("sticky_tape")
+
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
+ export_price = 2500
+ hidden = TRUE
+
+/datum/techweb_node/sticky_advanced
+ id = "sticky_advanced"
+ tech_tier = 4
+ display_name = "Advanced Sticky Technology"
+ description = "Taking a good joke too far? Nonsense!"
+ prereq_ids = list("sticky_basic")
+ design_ids = list("super_sticky_tape", "pointy_tape")
+
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000)
+ export_price = 2500
+ hidden = TRUE
//Helpers for debugging/balancing the techweb in its entirety!
/proc/total_techweb_exports()
diff --git a/code/modules/research/xenobiology/crossbreeding/_clothing.dm b/code/modules/research/xenobiology/crossbreeding/_clothing.dm
index c20e8824e074b..2f279406239c4 100644
--- a/code/modules/research/xenobiology/crossbreeding/_clothing.dm
+++ b/code/modules/research/xenobiology/crossbreeding/_clothing.dm
@@ -19,7 +19,7 @@ Slimecrossing Armor
/obj/item/clothing/mask/nobreath/equipped(mob/living/carbon/human/user, slot)
. = ..()
- if(slot == SLOT_WEAR_MASK)
+ if(slot == ITEM_SLOT_MASK)
ADD_TRAIT(user, TRAIT_NOBREATH, "breathmask_[REF(src)]")
user.failed_last_breath = FALSE
user.clear_alert("not_enough_oxy")
@@ -39,7 +39,7 @@ Slimecrossing Armor
var/glasses_color = "#FFFFFF"
/obj/item/clothing/glasses/prism_glasses/item_action_slot_check(slot)
- if(slot == SLOT_GLASSES)
+ if(slot == ITEM_SLOT_EYES)
return TRUE
/obj/structure/light_prism
@@ -112,7 +112,7 @@ Slimecrossing Armor
/obj/item/clothing/head/peaceflower/equipped(mob/living/carbon/human/user, slot)
. = ..()
- if(slot == SLOT_HEAD)
+ if(slot == ITEM_SLOT_HEAD)
ADD_TRAIT(user, TRAIT_PACIFISM, "peaceflower_[REF(src)]")
/obj/item/clothing/head/peaceflower/dropped(mob/living/carbon/human/user)
diff --git a/code/modules/research/xenobiology/crossbreeding/_misc.dm b/code/modules/research/xenobiology/crossbreeding/_misc.dm
index b3d3575344986..f8107267cb074 100644
--- a/code/modules/research/xenobiology/crossbreeding/_misc.dm
+++ b/code/modules/research/xenobiology/crossbreeding/_misc.dm
@@ -248,7 +248,7 @@ Slimecrossing Items
icon_state = "frozen"
density = TRUE
max_integrity = 100
- armor = list("melee" = 30, "bullet" = 50, "laser" = -50, "energy" = -50, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = -80, "acid" = 30)
+ armor = list("melee" = 30, "bullet" = 50, "laser" = -50, "energy" = -50, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = -80, "acid" = 30, "stamina" = 0)
/obj/structure/ice_stasis/Initialize()
. = ..()
@@ -276,18 +276,8 @@ Slimecrossing Items
to_chat(user, "The capture device only works on simple creatures.")
return
if(M.mind)
- to_chat(user, "You offer the device to [M].")
- if(alert(M, "Would you like to enter [user]'s capture device?", "Gold Capture Device", "Yes", "No") == "Yes")
- if(user.canUseTopic(src, BE_CLOSE) && user.canUseTopic(M, BE_CLOSE))
- to_chat(user, "You store [M] in the capture device.")
- to_chat(M, "The world warps around you, and you're suddenly in an endless void, with a window to the outside floating in front of you.")
- store(M, user)
- else
- to_chat(user, "You were too far away from [M].")
- to_chat(M, "You were too far away from [user].")
- else
- to_chat(user, "[M] refused to enter the device.")
- return
+ INVOKE_ASYNC(src, .proc/offer_entry, M, user)
+ return
else
if(istype(M, /mob/living/simple_animal/hostile) && !("neutral" in M.faction))
to_chat(user, "This creature is too aggressive to capture.")
@@ -295,6 +285,20 @@ Slimecrossing Items
to_chat(user, "You store [M] in the capture device.")
store(M)
+/obj/item/capturedevice/proc/offer_entry(mob/living/M, mob/user)
+ to_chat(user, "You offer the device to [M].")
+ if(alert(M, "Would you like to enter [user]'s capture device?", "Gold Capture Device", "Yes", "No") != "Yes")
+ to_chat(user, "[M] refused to enter the device.")
+ return
+ if(!user.canUseTopic(src, BE_CLOSE) || !user.canUseTopic(M, BE_CLOSE))
+ to_chat(user, "You were too far away from [M].")
+ to_chat(M, "You were too far away from [user].")
+ return
+
+ to_chat(user, "You store [M] in the capture device.")
+ to_chat(M, "The world warps around you, and you're suddenly in an endless void, with a window to the outside floating in front of you.")
+ store(M, user)
+
/obj/item/capturedevice/attack_self(mob/user)
if(contents.len)
to_chat(user, "You open the capture device!")
@@ -308,3 +312,25 @@ Slimecrossing Items
/obj/item/capturedevice/proc/release()
for(var/atom/movable/M in contents)
M.forceMove(get_turf(loc))
+
+/obj/item/cerulean_slime_crystal
+ name = "Cerulean slime poly-crystal"
+ desc = "Translucent and irregular, it can duplicate matter on a whim"
+ icon = 'icons/obj/slimecrossing.dmi'
+ icon_state = "cerulean_item_crystal"
+ var/amt = 0
+
+/obj/item/cerulean_slime_crystal/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(!istype(target,/obj/item/stack) || !istype(user,/mob/living/carbon) || !proximity_flag)
+ return
+ var/obj/item/stack/stack_item = target
+
+ if(istype(stack_item,/obj/item/stack/telecrystal))
+ to_chat(user,"The crystal disappears!")
+ qdel(src)
+ return
+
+ stack_item.add(amt)
+
+ qdel(src)
diff --git a/code/modules/research/xenobiology/crossbreeding/_potions.dm b/code/modules/research/xenobiology/crossbreeding/_potions.dm
index ac7cb211425dd..618af07fc0c8b 100644
--- a/code/modules/research/xenobiology/crossbreeding/_potions.dm
+++ b/code/modules/research/xenobiology/crossbreeding/_potions.dm
@@ -41,24 +41,24 @@ Slimecrossing Potions
/obj/item/slimepotion/peacepotion/attack(mob/living/M, mob/user)
if(!isliving(M) || M.stat == DEAD)
- to_chat(user, "The pacification potion only works on the living.")
+ to_chat(user, "[src] only works on the living.")
return ..()
if(istype(M, /mob/living/simple_animal/hostile/megafauna))
- to_chat(user, "The pacification potion does not work on beings of pure evil!")
+ to_chat(user, "[src] does not work on beings of pure evil!")
return ..()
if(M != user)
M.visible_message("[user] starts to feed [M] a pacification potion!",
"[user] starts to feed you a pacification!")
else
- M.visible_message("[user] starts to drink the pacification potion!",
- "You start to drink the pacification potion!")
+ M.visible_message("[user] starts to drink [src]!",
+ "You start to drink [src]!")
if(!do_after(user, 100, target = M))
return
if(M != user)
- to_chat(user, "You feed [M] the pacification potion!")
+ to_chat(user, "You feed [M] [src]!")
else
- to_chat(user, "You drink the pacification potion!")
+ to_chat(user, "You drink [src]!")
if(isanimal(M))
ADD_TRAIT(M, TRAIT_PACIFISM, MAGIC_TRAIT)
else if(iscarbon(M))
@@ -164,7 +164,7 @@ Slimecrossing Potions
C.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
C.add_atom_colour("#800000", FIXED_COLOUR_PRIORITY)
C.resistance_flags |= LAVA_PROOF
- if (istype(C, /obj/item/clothing))
+ if (isclothing(C))
var/obj/item/clothing/CL = C
CL.clothing_flags |= LAVAPROTECT
uses--
@@ -189,7 +189,7 @@ Slimecrossing Potions
if(M.maxHealth <= 0)
to_chat(user, "The slime is too unstable to return!")
M.revive(full_heal = 1)
- M.stat = CONSCIOUS
+ M.set_stat(CONSCIOUS)
M.visible_message("[M] is filled with renewed vigor and blinks awake!")
M.maxHealth -= 10 //Revival isn't healthy.
M.health -= 10
diff --git a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm
index ed2c455a6fa11..71396edadfb3e 100644
--- a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm
+++ b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm
@@ -1,4 +1,4 @@
-/obj/screen/alert/status_effect/rainbow_protection
+/atom/movable/screen/alert/status_effect/rainbow_protection
name = "Rainbow Protection"
desc = "You are defended from harm, but so are those you might seek to injure!"
icon_state = "slime_rainbowshield"
@@ -6,7 +6,7 @@
/datum/status_effect/rainbow_protection
id = "rainbow_protection"
duration = 100
- alert_type = /obj/screen/alert/status_effect/rainbow_protection
+ alert_type = /atom/movable/screen/alert/status_effect/rainbow_protection
var/originalcolor
/datum/status_effect/rainbow_protection/on_apply()
@@ -28,7 +28,7 @@
owner.visible_message("[owner] stops glowing, the rainbow light fading away.",
"You no longer feel protected...")
-/obj/screen/alert/status_effect/slimeskin
+/atom/movable/screen/alert/status_effect/slimeskin
name = "Adamantine Slimeskin"
desc = "You are covered in a thick, non-neutonian gel."
icon_state = "slime_stoneskin"
@@ -36,7 +36,7 @@
/datum/status_effect/slimeskin
id = "slimeskin"
duration = 300
- alert_type = /obj/screen/alert/status_effect/slimeskin
+ alert_type = /atom/movable/screen/alert/status_effect/slimeskin
var/originalcolor
/datum/status_effect/slimeskin/on_apply()
@@ -75,6 +75,8 @@
return ..()
/datum/status_effect/slimerecall/proc/resistField()
+ SIGNAL_HANDLER
+
interrupted = TRUE
owner.remove_status_effect(src)
/datum/status_effect/slimerecall/on_remove()
@@ -88,14 +90,14 @@
do_sparks(3, TRUE, owner)
owner.forceMove(target.loc)
-/obj/screen/alert/status_effect/freon/stasis
+/atom/movable/screen/alert/status_effect/freon/stasis
desc = "You're frozen inside of a protective ice cube! While inside, you can't do anything, but are immune to harm! Resist to get out."
/datum/status_effect/frozenstasis
id = "slime_frozen"
status_type = STATUS_EFFECT_UNIQUE
duration = -1 //Will remove self when block breaks.
- alert_type = /obj/screen/alert/status_effect/freon/stasis
+ alert_type = /atom/movable/screen/alert/status_effect/freon/stasis
var/obj/structure/ice_stasis/cube
/datum/status_effect/frozenstasis/on_apply()
@@ -110,6 +112,8 @@
owner.remove_status_effect(src)
/datum/status_effect/frozenstasis/proc/breakCube()
+ SIGNAL_HANDLER
+
owner.remove_status_effect(src)
/datum/status_effect/frozenstasis/on_remove()
@@ -157,7 +161,7 @@
clone.unequip_everything()
qdel(clone)
-/obj/screen/alert/status_effect/clone_decay
+/atom/movable/screen/alert/status_effect/clone_decay
name = "Clone Decay"
desc = "You are simply a construct, and cannot maintain this form forever. You will be returned to your original body if you should fall."
icon_state = "slime_clonedecay"
@@ -166,7 +170,7 @@
id = "slime_clonedecay"
status_type = STATUS_EFFECT_UNIQUE
duration = -1
- alert_type = /obj/screen/alert/status_effect/clone_decay
+ alert_type = /atom/movable/screen/alert/status_effect/clone_decay
/datum/status_effect/slime_clone_decay/tick()
owner.adjustToxLoss(1, 0)
@@ -175,7 +179,7 @@
owner.adjustFireLoss(1, 0)
owner.color = "#007BA7"
-/obj/screen/alert/status_effect/bloodchill
+/atom/movable/screen/alert/status_effect/bloodchill
name = "Bloodchilled"
desc = "You feel a shiver down your spine after getting hit with a glob of cold blood. You'll move slower and get frostbite for a while!"
icon_state = "bloodchill"
@@ -183,7 +187,7 @@
/datum/status_effect/bloodchill
id = "bloodchill"
duration = 100
- alert_type = /obj/screen/alert/status_effect/bloodchill
+ alert_type = /atom/movable/screen/alert/status_effect/bloodchill
/datum/status_effect/bloodchill/on_apply()
owner.add_movespeed_modifier("bloodchilled", TRUE, 100, NONE, override = TRUE, multiplicative_slowdown = 3)
@@ -196,7 +200,7 @@
/datum/status_effect/bloodchill/on_remove()
owner.remove_movespeed_modifier("bloodchilled")
-/obj/screen/alert/status_effect/bloodchill
+/atom/movable/screen/alert/status_effect/bloodchill
name = "Bloodchilled"
desc = "You feel a shiver down your spine after getting hit with a glob of cold blood. You'll move slower and get frostbite for a while!"
icon_state = "bloodchill"
@@ -204,7 +208,7 @@
/datum/status_effect/bonechill
id = "bonechill"
duration = 80
- alert_type = /obj/screen/alert/status_effect/bonechill
+ alert_type = /atom/movable/screen/alert/status_effect/bonechill
/datum/status_effect/bonechill/on_apply()
owner.add_movespeed_modifier("bonechilled", TRUE, 100, NONE, override = TRUE, multiplicative_slowdown = 3)
@@ -219,7 +223,7 @@
/datum/status_effect/bonechill/on_remove()
owner.remove_movespeed_modifier("bonechilled")
-/obj/screen/alert/status_effect/bonechill
+/atom/movable/screen/alert/status_effect/bonechill
name = "Bonechilled"
desc = "You feel a shiver down your spine after hearing the haunting noise of bone rattling. You'll move slower and get frostbite for a while!"
icon_state = "bloodchill"
@@ -229,7 +233,7 @@
duration = -1
alert_type = null
-datum/status_effect/rebreathing/tick()
+/datum/status_effect/rebreathing/tick()
owner.adjustOxyLoss(-6, 0) //Just a bit more than normal breathing.
///////////////////////////////////////////////////////
@@ -261,7 +265,7 @@ datum/status_effect/rebreathing/tick()
return ..()
/datum/status_effect/watercookie/tick()
- for(var/turf/open/T in range(get_turf(owner),1))
+ for(var/turf/open/T in RANGE_TURFS(1, owner))
T.MakeSlippery(TURF_WET_WATER, min_wet_time = 10, wet_time_to_add = 5)
/datum/status_effect/watercookie/on_remove()
@@ -323,15 +327,12 @@ datum/status_effect/rebreathing/tick()
duration = 600
/datum/status_effect/timecookie/on_apply()
- if(ishuman(owner))
- var/mob/living/carbon/human/H
- H.physiology.do_after_speed *= 0.95
+ owner.add_actionspeed_modifier(/datum/actionspeed_modifier/timecookie)
return ..()
/datum/status_effect/timecookie/on_remove()
- if(ishuman(owner))
- var/mob/living/carbon/human/H
- H.physiology.do_after_speed /= 0.95
+ owner.remove_actionspeed_modifier(/datum/actionspeed_modifier/timecookie)
+ return ..()
/datum/status_effect/lovecookie
id = "lovecookie"
@@ -347,9 +348,8 @@ datum/status_effect/rebreathing/tick()
if(C.handcuffed)
return
var/list/huggables = list()
- for(var/mob/living/carbon/L in range(get_turf(owner),1))
- if(L != owner)
- huggables += L
+ for(var/mob/living/carbon/L in oviewers(1, owner))
+ huggables += L
if(length(huggables))
var/mob/living/carbon/hugged = pick(huggables)
owner.visible_message("[owner] hugs [hugged]!", "You hug [hugged]!")
@@ -361,9 +361,8 @@ datum/status_effect/rebreathing/tick()
duration = 100
/datum/status_effect/tarcookie/tick()
- for(var/mob/living/carbon/human/L in range(get_turf(owner),1))
- if(L != owner)
- L.apply_status_effect(/datum/status_effect/tarfoot)
+ for(var/mob/living/carbon/human/L in oviewers(1, owner))
+ L.apply_status_effect(/datum/status_effect/tarfoot)
/datum/status_effect/tarfoot
id = "tarfoot"
@@ -400,7 +399,7 @@ datum/status_effect/rebreathing/tick()
duration = 100
/datum/status_effect/peacecookie/tick()
- for(var/mob/living/L in range(get_turf(owner),1))
+ for(var/mob/living/L in viewers(1, owner))
L.apply_status_effect(/datum/status_effect/plur)
/datum/status_effect/plur
@@ -466,10 +465,10 @@ datum/status_effect/rebreathing/tick()
colour = "grey"
/datum/status_effect/stabilized/grey/tick()
- for(var/mob/living/simple_animal/slime/S in range(1, get_turf(owner)))
+ for(var/mob/living/simple_animal/slime/S in viewers(1, owner))
if(!(owner in S.Friends))
to_chat(owner, "[linked_extract] pulses gently as it communicates with [S].")
- S.Friends[owner] = 1
+ S.set_friendship(owner, 1)
return ..()
/datum/status_effect/stabilized/orange
@@ -511,7 +510,7 @@ datum/status_effect/rebreathing/tick()
ADD_TRAIT(owner, TRAIT_NOSLIPWATER, "slimestatus")
return ..()
-datum/status_effect/stabilized/blue/on_remove()
+/datum/status_effect/stabilized/blue/on_remove()
REMOVE_TRAIT(owner, TRAIT_NOSLIPWATER, "slimestatus")
/datum/status_effect/stabilized/metal
@@ -579,8 +578,10 @@ datum/status_effect/stabilized/blue/on_remove()
/datum/status_effect/stabilized/darkpurple/tick()
var/obj/item/I = owner.get_active_held_item()
- var/obj/item/reagent_containers/food/snacks/F = I
- if(istype(F))
+ if(!I)
+ return
+ if(istype(I, /obj/item/reagent_containers/food/snacks))
+ var/obj/item/reagent_containers/food/snacks/F = I
if(F.cooked_type)
to_chat(owner, "[linked_extract] flares up brightly, and your hands alone are enough cook [F]!")
var/obj/item/result = F.microwave_act()
@@ -642,7 +643,7 @@ datum/status_effect/stabilized/blue/on_remove()
H.physiology.hunger_mod /= 0.8
//Bluespace has an icon because it's kinda active.
-/obj/screen/alert/status_effect/bluespaceslime
+/atom/movable/screen/alert/status_effect/bluespaceslime
name = "Stabilized Bluespace Extract"
desc = "You shouldn't see this, since we set it to change automatically!"
icon_state = "slime_bluespace_on"
@@ -655,7 +656,7 @@ datum/status_effect/stabilized/blue/on_remove()
/datum/status_effect/stabilized/bluespace
id = "stabilizedbluespace"
colour = "bluespace"
- alert_type = /obj/screen/alert/status_effect/bluespaceslime
+ alert_type = /atom/movable/screen/alert/status_effect/bluespaceslime
var/healthcheck
/datum/status_effect/stabilized/bluespace/tick()
@@ -828,7 +829,7 @@ datum/status_effect/stabilized/blue/on_remove()
M.apply_status_effect(/datum/status_effect/pinkdamagetracker)
M.faction |= faction_name
for(var/mob/living/simple_animal/M in mobs)
- if(!(M in view(7,get_turf(owner))))
+ if(!(M in hearers(7,get_turf(owner))))
M.faction -= faction_name
M.remove_status_effect(/datum/status_effect/pinkdamagetracker)
mobs -= M
@@ -906,18 +907,20 @@ datum/status_effect/stabilized/blue/on_remove()
colour = "light pink"
/datum/status_effect/stabilized/lightpink/on_apply()
- owner.add_movespeed_modifier(MOVESPEED_ID_SLIME_STATUS, update=TRUE, priority=100, multiplicative_slowdown=-1, blacklisted_movetypes=(FLYING|FLOATING))
+ owner.add_movespeed_modifier(MOVESPEED_ID_SLIME_STATUS, update=TRUE, priority=100, multiplicative_slowdown=-0.5, blacklisted_movetypes=(FLYING|FLOATING))
+ ADD_TRAIT(owner, TRAIT_PACIFISM, LIGHTPINK_TRAIT)
return ..()
/datum/status_effect/stabilized/lightpink/tick()
- for(var/mob/living/carbon/human/H in range(1, get_turf(owner)))
- if(H != owner && H.stat != DEAD && H.health <= 0 && !H.reagents.has_reagent(/datum/reagent/medicine/epinephrine))
+ for(var/mob/living/carbon/human/H in ohearers(1, owner))
+ if(H.stat != DEAD && H.health <= 0 && !H.reagents.has_reagent(/datum/reagent/medicine/epinephrine))
to_chat(owner, "[linked_extract] pulses in sync with [H]'s heartbeat, trying to keep [H.p_them()] alive.")
H.reagents.add_reagent(/datum/reagent/medicine/epinephrine,5)
return ..()
/datum/status_effect/stabilized/lightpink/on_remove()
owner.remove_movespeed_modifier(MOVESPEED_ID_SLIME_STATUS)
+ REMOVE_TRAIT(owner, TRAIT_PACIFISM, LIGHTPINK_TRAIT)
/datum/status_effect/stabilized/adamantine
id = "stabilizedadamantine"
diff --git a/code/modules/research/xenobiology/crossbreeding/_structures.dm b/code/modules/research/xenobiology/crossbreeding/_structures.dm
new file mode 100644
index 0000000000000..899e67c965a39
--- /dev/null
+++ b/code/modules/research/xenobiology/crossbreeding/_structures.dm
@@ -0,0 +1,664 @@
+GLOBAL_LIST_EMPTY(bluespace_slime_crystals)
+
+/obj/structure/slime_crystal
+ name = "slimic pylon"
+ desc = "Glassy, pure, transparent. Powerful artifact that relays the slimecore's influence onto space around it."
+ max_integrity = 5
+ anchored = TRUE
+ density = TRUE
+ icon = 'icons/obj/slimecrossing.dmi'
+ icon_state = "slime_pylon"
+ resistance_flags = FIRE_PROOF | ACID_PROOF
+ ///Assoc list of affected mobs, the key is the mob while the value of the map is the amount of ticks spent inside of the zone.
+ var/list/affected_mobs = list()
+ ///Used to determine wether we use view or range
+ var/range_type = "range"
+ ///What color is it?
+ var/colour
+ ///Does it use process?
+ var/uses_process = TRUE
+
+/obj/structure/slime_crystal/New(loc, obj/structure/slime_crystal/master_crystal, ...)
+ . = ..()
+ if(master_crystal)
+ invisibility = INVISIBILITY_MAXIMUM
+ max_integrity = 1000
+ obj_integrity = 1000
+
+/obj/structure/slime_crystal/Initialize()
+ . = ..()
+ name = "[colour] slimic pylon"
+ var/itemcolor = "#FFFFFF"
+
+ switch(colour)
+ if("orange")
+ itemcolor = "#FFA500"
+ if("purple")
+ itemcolor = "#B19CD9"
+ if("blue")
+ itemcolor = "#ADD8E6"
+ if("metal")
+ itemcolor = "#7E7E7E"
+ if("yellow")
+ itemcolor = "#FFFF00"
+ if("dark purple")
+ itemcolor = "#551A8B"
+ if("dark blue")
+ itemcolor = "#0000FF"
+ if("silver")
+ itemcolor = "#D3D3D3"
+ if("bluespace")
+ itemcolor = "#32CD32"
+ if("sepia")
+ itemcolor = "#704214"
+ if("cerulean")
+ itemcolor = "#2956B2"
+ if("pyrite")
+ itemcolor = "#FAFAD2"
+ if("red")
+ itemcolor = "#FF0000"
+ if("green")
+ itemcolor = "#00FF00"
+ if("pink")
+ itemcolor = "#FF69B4"
+ if("gold")
+ itemcolor = "#FFD700"
+ if("oil")
+ itemcolor = "#505050"
+ if("black")
+ itemcolor = "#000000"
+ if("light pink")
+ itemcolor = "#FFB6C1"
+ if("adamantine")
+ itemcolor = "#008B8B"
+ add_atom_colour(itemcolor, FIXED_COLOUR_PRIORITY)
+ if(uses_process)
+ START_PROCESSING(SSobj, src)
+
+/obj/structure/slime_crystal/Destroy()
+ if(uses_process)
+ STOP_PROCESSING(SSobj, src)
+ for(var/X in affected_mobs)
+ on_mob_leave(X)
+ return ..()
+
+/obj/structure/slime_crystal/process()
+ if(!uses_process)
+ return PROCESS_KILL
+
+ var/list/current_mobs = view_or_range(3, src, range_type)
+ for(var/mob/living/mob_in_range in current_mobs)
+ if(!(mob_in_range in affected_mobs))
+ on_mob_enter(mob_in_range)
+ affected_mobs[mob_in_range] = 0
+
+ affected_mobs[mob_in_range]++
+ on_mob_effect(mob_in_range)
+
+ for(var/M in affected_mobs - current_mobs)
+ on_mob_leave(M)
+ affected_mobs -= M
+
+/obj/structure/slime_crystal/gold/process()
+ var/list/current_mobs = view_or_range(3, src, range_type)
+ for(var/M in affected_mobs - current_mobs)
+ on_mob_leave(M)
+ affected_mobs -= M
+
+ for(var/mob/living/M in affected_mobs)
+ if(M.stat == DEAD)
+ on_mob_leave(M)
+ affected_mobs -= M
+
+/obj/structure/slime_crystal/proc/master_crystal_destruction()
+ qdel(src)
+
+/obj/structure/slime_crystal/proc/on_mob_enter(mob/living/affected_mob)
+ return
+
+/obj/structure/slime_crystal/proc/on_mob_effect(mob/living/affected_mob)
+ return
+
+/obj/structure/slime_crystal/proc/on_mob_leave(mob/living/affected_mob)
+ return
+
+/obj/structure/slime_crystal/grey
+ colour = "grey"
+ range_type = "view"
+
+/obj/structure/slime_crystal/grey/on_mob_effect(mob/living/affected_mob)
+ if(!istype(affected_mob, /mob/living/simple_animal/slime))
+ return
+ var/mob/living/simple_animal/slime/slime_mob = affected_mob
+ slime_mob.nutrition += 2
+
+/obj/structure/slime_crystal/orange
+ colour = "orange"
+ range_type = "view"
+
+/obj/structure/slime_crystal/orange/on_mob_effect(mob/living/affected_mob)
+ if(!istype(affected_mob, /mob/living/carbon))
+ return
+ var/mob/living/carbon/carbon_mob = affected_mob
+ carbon_mob.fire_stacks++
+ carbon_mob.IgniteMob()
+
+/obj/structure/slime_crystal/orange/process()
+ . = ..()
+ var/turf/open/T = get_turf(src)
+ if(!istype(T))
+ return
+ var/datum/gas_mixture/gas = T.return_air()
+ gas.set_temperature(T0C + 200)
+ T.air_update_turf()
+
+/obj/structure/slime_crystal/purple
+ colour = "purple"
+
+ var/heal_amt = 2
+
+/obj/structure/slime_crystal/purple/on_mob_effect(mob/living/affected_mob)
+ if(!istype(affected_mob, /mob/living/carbon))
+ return
+ var/mob/living/carbon/carbon_mob = affected_mob
+ var/rand_dam_type = rand(0, 10)
+
+ new /obj/effect/temp_visual/heal(get_turf(affected_mob), "#e180ff")
+
+ switch(rand_dam_type)
+ if(0)
+ carbon_mob.adjustBruteLoss(-heal_amt)
+ if(1)
+ carbon_mob.adjustFireLoss(-heal_amt)
+ if(2)
+ carbon_mob.adjustOxyLoss(-heal_amt)
+ if(3)
+ carbon_mob.adjustToxLoss(-heal_amt, forced = TRUE)
+ if(4)
+ carbon_mob.adjustCloneLoss(-heal_amt)
+ if(5)
+ carbon_mob.adjustStaminaLoss(-heal_amt)
+ if(6 to 10)
+ carbon_mob.adjustOrganLoss(pick(ORGAN_SLOT_BRAIN,ORGAN_SLOT_HEART,ORGAN_SLOT_LIVER,ORGAN_SLOT_LUNGS), -heal_amt)
+
+/obj/structure/slime_crystal/blue
+ colour = "blue"
+ range_type = "view"
+
+/obj/structure/slime_crystal/blue/process()
+ for(var/turf/open/T in view(2, src))
+ if(isspaceturf(T))
+ continue
+ var/datum/gas_mixture/gas = T.return_air()
+ gas.parse_gas_string(OPENTURF_DEFAULT_ATMOS)
+ T.air_update_turf()
+
+/obj/structure/slime_crystal/metal
+ colour = "metal"
+
+ var/heal_amt = 3
+
+/obj/structure/slime_crystal/metal/on_mob_effect(mob/living/affected_mob)
+ if(!iscyborg(affected_mob))
+ return
+ var/mob/living/silicon/borgo = affected_mob
+ borgo.adjustBruteLoss(-heal_amt)
+
+/obj/structure/slime_crystal/yellow
+ colour = "yellow"
+ light_color = LIGHT_COLOR_YELLOW //a good, sickly atmosphere
+ light_power = 0.75
+ uses_process = FALSE
+
+/obj/structure/slime_crystal/yellow/Initialize()
+ . = ..()
+ set_light(3)
+
+/obj/structure/slime_crystal/yellow/attacked_by(obj/item/I, mob/living/user)
+ if(istype(I,/obj/item/stock_parts/cell))
+ var/obj/item/stock_parts/cell/cell = I
+ //Punishment for greed
+ if(cell.charge == cell.maxcharge)
+ to_chat(" You try to charge the cell, but it is already fully energized. You are not sure if this was a good idea...")
+ cell.explode()
+ return
+ to_chat(" You charged the [I.name] on [name]!")
+ cell.give(cell.maxcharge)
+ return
+ return ..()
+/obj/structure/slime_crystal/darkpurple
+ colour = "dark purple"
+
+/obj/structure/slime_crystal/darkpurple/process()
+ var/turf/T = get_turf(src)
+ if(!istype(T, /turf/open))
+ return
+ var/turf/open/open_turf = T
+ var/datum/gas_mixture/air = open_turf.return_air()
+
+ if(air.get_moles(GAS_PLASMA) > 15)
+ air.adjust_moles(GAS_PLASMA, -15)
+ new /obj/item/stack/sheet/mineral/plasma(open_turf)
+
+/obj/structure/slime_crystal/darkpurple/Destroy()
+ atmos_spawn_air("plasma=[20];TEMP=[500]")
+ return ..()
+
+/obj/structure/slime_crystal/darkblue
+ colour = "dark blue"
+
+/obj/structure/slime_crystal/darkblue/process(delta_time)
+ for(var/turf/open/T in RANGE_TURFS(5, src))
+ if(DT_PROB(75, delta_time))
+ continue
+ T.MakeDry(TURF_WET_LUBE)
+
+ for(var/obj/item/trash/trashie in range(5, src))
+ if(DT_PROB(25, delta_time))
+ qdel(trashie)
+
+/obj/structure/slime_crystal/silver
+ colour = "silver"
+
+/obj/structure/slime_crystal/silver/process(delta_time)
+ for(var/obj/machinery/hydroponics/hydr in range(5,src))
+ hydr.weedlevel = 0
+ hydr.pestlevel = 0
+ if(DT_PROB(10, delta_time))
+ hydr.age++
+
+/obj/structure/slime_crystal/bluespace
+ colour = "bluespace"
+ density = FALSE
+ uses_process = FALSE
+ ///Is it in use?
+ var/in_use = FALSE
+
+/obj/structure/slime_crystal/bluespace/Initialize()
+ . = ..()
+ GLOB.bluespace_slime_crystals += src
+
+/obj/structure/slime_crystal/bluespace/Destroy()
+ GLOB.bluespace_slime_crystals -= src
+ return ..()
+
+/obj/structure/slime_crystal/bluespace/attack_hand(mob/user)
+
+ if(in_use)
+ return
+
+ var/list/local_bs_list = GLOB.bluespace_slime_crystals.Copy()
+ local_bs_list -= src
+ if(!LAZYLEN(local_bs_list))
+ return ..()
+
+ if(local_bs_list.len == 1)
+ do_teleport(user, local_bs_list[1])
+ return
+
+ in_use = TRUE
+
+ var/list/assoc_list = list()
+
+ for(var/BSC in local_bs_list)
+ var/area/bsc_area = get_area(BSC)
+ var/name = "[bsc_area.name] bluespace slimic pylon"
+ var/counter = 0
+
+ do
+ counter++
+ while(assoc_list["[name]([counter])"])
+
+ name += "([counter])"
+
+ assoc_list[name] = BSC
+
+ var/chosen_input = input(user,"What destination do you want to choose",null) as null|anything in assoc_list
+ in_use = FALSE
+
+ if(!chosen_input || !assoc_list[chosen_input])
+ return
+
+ do_teleport(user ,assoc_list[chosen_input])
+
+/obj/structure/slime_crystal/sepia
+ colour = "sepia"
+
+/obj/structure/slime_crystal/sepia/on_mob_enter(mob/living/affected_mob)
+ ADD_TRAIT(affected_mob,TRAIT_NOBREATH,type)
+ ADD_TRAIT(affected_mob,TRAIT_NOCRITDAMAGE,type)
+ ADD_TRAIT(affected_mob,TRAIT_RESISTLOWPRESSURE,type)
+ ADD_TRAIT(affected_mob,TRAIT_RESISTHIGHPRESSURE,type)
+ ADD_TRAIT(affected_mob,TRAIT_NOSOFTCRIT,type)
+ ADD_TRAIT(affected_mob,TRAIT_NOHARDCRIT,type)
+
+/obj/structure/slime_crystal/sepia/on_mob_leave(mob/living/affected_mob)
+ REMOVE_TRAIT(affected_mob,TRAIT_NOBREATH,type)
+ REMOVE_TRAIT(affected_mob,TRAIT_NOCRITDAMAGE,type)
+ REMOVE_TRAIT(affected_mob,TRAIT_RESISTLOWPRESSURE,type)
+ REMOVE_TRAIT(affected_mob,TRAIT_RESISTHIGHPRESSURE,type)
+ REMOVE_TRAIT(affected_mob,TRAIT_NOSOFTCRIT,type)
+ REMOVE_TRAIT(affected_mob,TRAIT_NOHARDCRIT,type)
+
+/obj/structure/cerulean_slime_crystal
+ name = "Cerulean slime poly-crystal"
+ desc = "Translucent and irregular, it can duplicate matter on a whim"
+ anchored = TRUE
+ density = FALSE
+ icon = 'icons/obj/slimecrossing.dmi'
+ icon_state = "cerulean_crystal"
+ max_integrity = 5
+ var/stage = 0
+ var/max_stage = 5
+ var/datum/weakref/pylon
+
+/obj/structure/cerulean_slime_crystal/Initialize(mapload, obj/structure/slime_crystal/cerulean/master_pylon)
+ . = ..()
+ if(istype(master_pylon))
+ pylon = WEAKREF(master_pylon)
+ transform *= 1/(max_stage-1)
+ stage_growth()
+
+/obj/structure/cerulean_slime_crystal/proc/stage_growth()
+ if(stage == max_stage)
+ return
+
+ if(stage == 3)
+ density = TRUE
+
+ stage ++
+
+ var/matrix/M = new
+ M.Scale(1/max_stage * stage)
+
+ animate(src, transform = M, time = 120 SECONDS)
+
+ addtimer(CALLBACK(src, .proc/stage_growth), 120 SECONDS)
+
+/obj/structure/cerulean_slime_crystal/Destroy()
+ if(stage > 3)
+ var/obj/item/cerulean_slime_crystal/crystal = new(get_turf(src))
+ if(stage == 5)
+ crystal.amt = rand(1,3)
+ else
+ crystal.amt = 1
+ if(pylon)
+ var/obj/structure/slime_crystal/cerulean/C = pylon.resolve()
+ if(C)
+ C.crystals--
+ C.spawn_crystal()
+ else
+ pylon = null
+ return ..()
+
+/obj/structure/slime_crystal/cerulean
+ colour = "cerulean"
+ uses_process = FALSE
+ var/crystals = 0
+
+/obj/structure/slime_crystal/cerulean/Initialize()
+ . = ..()
+ while(crystals < 3)
+ spawn_crystal()
+
+/obj/structure/slime_crystal/cerulean/proc/spawn_crystal()
+ if(crystals >= 3)
+ return
+ for(var/turf/T as() in RANGE_TURFS(2,src))
+ if(is_blocked_turf(T) || isspaceturf(T) || T == get_turf(src) || prob(50))
+ continue
+ var/obj/structure/cerulean_slime_crystal/CSC = locate() in range(1,T)
+ if(CSC)
+ continue
+ new /obj/structure/cerulean_slime_crystal(T, src)
+ crystals++
+ return
+
+/obj/structure/slime_crystal/pyrite
+ colour = "pyrite"
+ uses_process = FALSE
+
+/obj/structure/slime_crystal/pyrite/Initialize()
+ . = ..()
+ change_colour()
+
+/obj/structure/slime_crystal/pyrite/proc/change_colour()
+ var/list/color_list = list("#FFA500","#B19CD9", "#ADD8E6","#7E7E7E","#FFFF00","#551A8B","#0000FF","#D3D3D3", "#32CD32","#704214","#2956B2","#FAFAD2", "#FF0000",
+ "#00FF00", "#FF69B4","#FFD700", "#505050", "#FFB6C1","#008B8B")
+ for(var/turf/T as() in RANGE_TURFS(4,src))
+ T.add_atom_colour(pick(color_list), FIXED_COLOUR_PRIORITY)
+
+ addtimer(CALLBACK(src,.proc/change_colour),rand(0.75 SECONDS,1.25 SECONDS))
+
+/obj/structure/slime_crystal/red
+ colour = "red"
+
+ var/blood_amt = 0
+
+ var/max_blood_amt = 300
+
+/obj/structure/slime_crystal/red/examine(mob/user)
+ . = ..()
+ . += "It has [blood_amt] u of blood."
+
+/obj/structure/slime_crystal/red/process()
+
+ if(blood_amt == max_blood_amt)
+ return
+
+ var/list/range_objects = range(3,src)
+
+ for(var/obj/effect/decal/cleanable/trail_holder/TH in range_objects)
+ qdel(TH)
+
+ blood_amt++
+ if(blood_amt == max_blood_amt)
+ return
+
+ for(var/obj/effect/decal/cleanable/blood/B in range_objects)
+ qdel(B)
+
+ blood_amt++
+ if(blood_amt == max_blood_amt)
+ return
+
+/obj/structure/slime_crystal/red/attack_hand(mob/user)
+ if(blood_amt < 100)
+ return ..()
+
+ blood_amt -= 100
+ var/type = pick(/obj/item/reagent_containers/food/snacks/meat/slab,/obj/item/organ/heart,/obj/item/organ/lungs,/obj/item/organ/liver,/obj/item/organ/eyes,/obj/item/organ/tongue,/obj/item/organ/stomach,/obj/item/organ/ears)
+ new type(get_turf(src))
+
+/obj/structure/slime_crystal/red/attacked_by(obj/item/I, mob/living/user)
+ if(blood_amt < 10)
+ return ..()
+
+ if(!istype(I, /obj/item/reagent_containers/glass/beaker))
+ return ..()
+
+ var/obj/item/reagent_containers/glass/beaker/item_beaker = I
+
+ if(!item_beaker.is_refillable() || (item_beaker.reagents.total_volume + 10 > item_beaker.reagents.maximum_volume))
+ return ..()
+ blood_amt -= 10
+ item_beaker.reagents.add_reagent(/datum/reagent/blood,10)
+
+/obj/structure/slime_crystal/green
+ colour = "green"
+ var/datum/mutation/stored_mutation
+
+/obj/structure/slime_crystal/green/examine(mob/user)
+ . = ..()
+ if(stored_mutation)
+ . += "It currently stores [stored_mutation.name]"
+ else
+ . += "It doesn't hold any mutations"
+
+/obj/structure/slime_crystal/green/attack_hand(mob/user)
+ . = ..()
+ if(!ishuman(user))
+ return
+ var/mob/living/carbon/human/human_user = user
+ var/list/mutation_list = human_user.dna.mutations
+ stored_mutation = pick(mutation_list)
+ stored_mutation = stored_mutation.type
+
+/obj/structure/slime_crystal/green/on_mob_effect(mob/living/affected_mob)
+ if(!ishuman(affected_mob) || !stored_mutation || HAS_TRAIT(affected_mob,TRAIT_BADDNA))
+ return
+ var/mob/living/carbon/human/human_mob = affected_mob
+ human_mob.dna.add_mutation(stored_mutation)
+
+ if(affected_mobs[affected_mob] % 60 != 0)
+ return
+
+ var/list/mut_list = human_mob.dna.mutations
+ var/list/secondary_list = list()
+
+ for(var/X in mut_list)
+ if(istype(X,stored_mutation))
+ continue
+ var/datum/mutation/t_mutation = X
+ secondary_list += t_mutation.type
+
+ var/datum/mutation/mutation = pick(secondary_list)
+ human_mob.dna.remove_mutation(mutation)
+
+/obj/structure/slime_crystal/green/on_mob_leave(mob/living/affected_mob)
+ if(!ishuman(affected_mob))
+ return
+ var/mob/living/carbon/human/human_mob = affected_mob
+ human_mob.dna.remove_mutation(stored_mutation)
+
+/obj/structure/slime_crystal/pink
+ colour = "pink"
+
+/obj/structure/slime_crystal/pink/on_mob_enter(mob/living/affected_mob)
+ ADD_TRAIT(affected_mob,TRAIT_PACIFISM,type)
+
+/obj/structure/slime_crystal/pink/on_mob_leave(mob/living/affected_mob)
+ REMOVE_TRAIT(affected_mob,TRAIT_PACIFISM,type)
+
+/obj/structure/slime_crystal/gold
+ colour = "gold"
+
+/obj/structure/slime_crystal/gold/attack_hand(mob/user)
+ . = ..()
+ if(!ishuman(user))
+ return
+ var/mob/living/carbon/human/human_mob = user
+ var/mob/living/simple_animal/pet/chosen_pet = pick(/mob/living/simple_animal/pet/dog/corgi,/mob/living/simple_animal/pet/dog/pug,/mob/living/simple_animal/pet/dog/bullterrier,/mob/living/simple_animal/pet/fox,/mob/living/simple_animal/pet/cat/kitten,/mob/living/simple_animal/pet/cat/space,/mob/living/simple_animal/pet/penguin/emperor)
+ chosen_pet = new chosen_pet(get_turf(human_mob))
+ human_mob.forceMove(chosen_pet)
+ human_mob.mind.transfer_to(chosen_pet)
+ ADD_TRAIT(human_mob, TRAIT_NOBREATH, type)
+ affected_mobs += chosen_pet
+
+/obj/structure/slime_crystal/gold/on_mob_leave(mob/living/affected_mob)
+ var/mob/living/carbon/human/human_mob = locate() in affected_mob
+ affected_mob.mind.transfer_to(human_mob)
+ human_mob.grab_ghost()
+ human_mob.forceMove(get_turf(affected_mob))
+ REMOVE_TRAIT(human_mob, TRAIT_NOBREATH, type)
+ qdel(affected_mob)
+
+/obj/structure/slime_crystal/oil
+ colour = "oil"
+
+/obj/structure/slime_crystal/oil/process()
+ for(var/turf/open/turf_in_range in RANGE_TURFS(3,src))
+ turf_in_range.MakeSlippery(TURF_WET_LUBE,5 SECONDS)
+
+/obj/structure/slime_crystal/black
+ colour = "black"
+
+/obj/structure/slime_crystal/black/on_mob_effect(mob/living/affected_mob)
+ if(!ishuman(affected_mob) || isjellyperson(affected_mob))
+ return
+
+ if(affected_mobs[affected_mob] < 60) //Around 2 minutes
+ return
+
+ var/mob/living/carbon/human/human_transformed = affected_mob
+ human_transformed.set_species(pick(typesof(/datum/species/jelly)))
+
+/obj/structure/slime_crystal/lightpink
+ colour = "light pink"
+
+/obj/structure/slime_crystal/lightpink/attack_ghost(mob/user)
+ . = ..()
+ var/mob/living/simple_animal/hostile/lightgeist/slime/L = new(get_turf(src))
+ L.ckey = user.ckey
+ affected_mobs[L] = 0
+ ADD_TRAIT(L,TRAIT_MUTE,type)
+ ADD_TRAIT(L,TRAIT_EMOTEMUTE,type)
+
+/obj/structure/slime_crystal/lightpink/on_mob_leave(mob/living/affected_mob)
+ if(istype(affected_mob,/mob/living/simple_animal/hostile/lightgeist/slime))
+ affected_mob.ghostize(TRUE)
+ qdel(affected_mob)
+
+/obj/structure/slime_crystal/adamantine
+ colour = "adamantine"
+
+/obj/structure/slime_crystal/adamantine/on_mob_enter(mob/living/affected_mob)
+ if(!ishuman(affected_mob))
+ return
+
+ var/mob/living/carbon/human/human = affected_mob
+ human.dna.species.brutemod -= 0.1
+ human.dna.species.burnmod -= 0.1
+
+/obj/structure/slime_crystal/adamantine/on_mob_leave(mob/living/affected_mob)
+ if(!ishuman(affected_mob))
+ return
+
+ var/mob/living/carbon/human/human = affected_mob
+ human.dna.species.brutemod += 0.1
+ human.dna.species.burnmod += 0.1
+
+/obj/structure/slime_crystal/rainbow
+ colour = "rainbow"
+ uses_process = FALSE
+ var/list/inserted_cores = list()
+
+/obj/structure/slime_crystal/rainbow/Initialize()
+ . = ..()
+ for(var/X in subtypesof(/obj/item/slimecross/crystalline) - /obj/item/slimecross/crystalline/rainbow)
+ inserted_cores[X] = FALSE
+
+/obj/structure/slime_crystal/rainbow/attacked_by(obj/item/I, mob/living/user)
+ . = ..()
+
+ if(!istype(I,/obj/item/slimecross/crystalline) || istype(I,/obj/item/slimecross/crystalline/rainbow))
+ return
+
+ var/obj/item/slimecross/crystalline/slimecross = I
+
+ if(inserted_cores[slimecross.type])
+ return
+
+ inserted_cores[slimecross.type] = new slimecross.crystal_type(get_turf(src),src)
+ qdel(slimecross)
+
+/obj/structure/slime_crystal/rainbow/Destroy()
+ for(var/X in inserted_cores)
+ if(inserted_cores[X])
+ var/obj/structure/slime_crystal/SC = inserted_cores[X]
+ SC.master_crystal_destruction()
+ return ..()
+
+/obj/structure/slime_crystal/rainbow/attack_hand(mob/user)
+ for(var/X in inserted_cores)
+ if(inserted_cores[X])
+ var/obj/structure/slime_crystal/SC = inserted_cores[X]
+ SC.attack_hand(user)
+ . = ..()
+
+/obj/structure/slime_crystal/rainbow/attacked_by(obj/item/I, mob/living/user)
+ for(var/X in inserted_cores)
+ if(inserted_cores[X])
+ var/obj/structure/slime_crystal/SC = inserted_cores[X]
+ SC.attacked_by(user)
+ . = ..()
diff --git a/code/modules/research/xenobiology/crossbreeding/_weapons.dm b/code/modules/research/xenobiology/crossbreeding/_weapons.dm
index bdb008cb9988d..0e8be83001d99 100644
--- a/code/modules/research/xenobiology/crossbreeding/_weapons.dm
+++ b/code/modules/research/xenobiology/crossbreeding/_weapons.dm
@@ -20,7 +20,7 @@ Slimecrossing Weapons
if(prob(20))
owner.emote("scream")
return ..()
-
+
//Rainbow knife - Burning Rainbow
/obj/item/kitchen/knife/rainbowknife
@@ -54,15 +54,15 @@ Slimecrossing Weapons
attack_verb = list("irradiated","mutated","maligned")
return ..()
-//Adamantine shield - Chilling Adamantine
-/obj/item/twohanded/required/adamantineshield
+//Adamantine shield - Burning Adamantine
+/obj/item/shield/adamantineshield
name = "adamantine shield"
desc = "A gigantic shield made of solid adamantium."
icon = 'icons/obj/slimecrossing.dmi'
icon_state = "adamshield"
item_state = "adamshield"
w_class = WEIGHT_CLASS_HUGE
- armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70)
+ armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70, "stamina" = 70)
slot_flags = ITEM_SLOT_BACK
attack_weight = 2
block_power = 75
@@ -71,10 +71,13 @@ Slimecrossing Weapons
block_flags = BLOCKING_PROJECTILE
throw_range = 1 //How far do you think you're gonna throw a solid crystalline shield...?
throw_speed = 2
- force = 15 //Heavy, but hard to wield.
attack_verb = list("bashed","pounded","slammed")
item_flags = SLOWS_WHILE_IN_HAND
+/obj/item/shield/adamantineshield/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/two_handed, require_twohands=TRUE, force_wielded=15)
+
//Bloodchiller - Chilling Green
/obj/item/gun/magic/bloodchill
name = "blood chiller"
@@ -97,11 +100,11 @@ Slimecrossing Weapons
. = ..()
ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)
-/obj/item/gun/magic/bloodchill/process()
- charge_tick++
- if(charge_tick < recharge_rate || charges >= max_charges)
- return 0
- charge_tick = 0
+/obj/item/gun/magic/bloodchill/process(delta_time)
+ charge_timer += delta_time
+ if(charge_timer < recharge_rate || charges >= max_charges)
+ return FALSE
+ charge_timer = 0
var/mob/living/M = loc
if(istype(M) && M.blood_volume >= 20)
charges++
diff --git a/code/modules/research/xenobiology/crossbreeding/burning.dm b/code/modules/research/xenobiology/crossbreeding/burning.dm
index b0ad064b0f3d0..f86d935868cf9 100644
--- a/code/modules/research/xenobiology/crossbreeding/burning.dm
+++ b/code/modules/research/xenobiology/crossbreeding/burning.dm
@@ -34,7 +34,7 @@ Burning extracts:
/obj/item/slimecross/burning/grey/do_effect(mob/user)
var/mob/living/simple_animal/slime/S = new(get_turf(user),"grey")
S.visible_message("A baby slime emerges from [src], and it nuzzles [user] before burbling hungrily!")
- S.Friends[user] = 20 //Gas, gas, gas
+ S.set_friendship(user, 20) //Gas, gas, gas
S.bodytemperature = T0C + 400 //We gonna step on the gas.
S.set_nutrition(S.get_hunger_nutrition()) //Tonight, we fight!
..()
@@ -68,12 +68,11 @@ Burning extracts:
/obj/item/slimecross/burning/blue/do_effect(mob/user)
user.visible_message("[src] flash-freezes the area!")
- for(var/turf/open/T in range(3, get_turf(user)))
+ for(var/turf/open/T in view(3, get_turf(user)))
T.MakeSlippery(TURF_WET_PERMAFROST, min_wet_time = 10, wet_time_to_add = 5)
- for(var/mob/living/carbon/M in range(5, get_turf(user)))
- if(M != user)
- M.bodytemperature = BODYTEMP_COLD_DAMAGE_LIMIT + 10 //Not quite cold enough to hurt.
- to_chat(M, "You feel a chill run down your spine, and the floor feels a bit slippery with frost...")
+ for(var/mob/living/carbon/M in ohearers(5, user))
+ M.bodytemperature = BODYTEMP_COLD_DAMAGE_LIMIT + 10 //Not quite cold enough to hurt.
+ to_chat(M, "You feel a chill run down your spine, and the floor feels a bit slippery with frost...")
..()
/obj/item/slimecross/burning/metal
@@ -81,7 +80,7 @@ Burning extracts:
effect_desc = "Instantly destroys walls around you."
/obj/item/slimecross/burning/metal/do_effect(mob/user)
- for(var/turf/closed/wall/W in range(1,get_turf(user)))
+ for(var/turf/closed/wall/W in RANGE_TURFS(1,user))
W.dismantle_wall(1)
playsound(W, 'sound/effects/break_stone.ogg', 50, 1)
user.visible_message("[src] pulses violently, and shatters the walls around it!")
@@ -94,14 +93,13 @@ Burning extracts:
/obj/item/slimecross/burning/yellow/do_effect(mob/user)
user.visible_message("[src] explodes into an electrical field!")
playsound(get_turf(src), 'sound/weapons/zapbang.ogg', 50, 1)
- for(var/mob/living/M in range(4,get_turf(user)))
- if(M != user)
- var/mob/living/carbon/C = M
- if(istype(C))
- C.electrocute_act(25,src)
- else
- M.adjustFireLoss(25)
- to_chat(M, "You feel a sharp electrical pulse!")
+ for(var/mob/living/M in ohearers(4,user))
+ var/mob/living/carbon/C = M
+ if(istype(C))
+ C.electrocute_act(25,src)
+ else
+ M.adjustFireLoss(25)
+ to_chat(M, "You feel a sharp electrical pulse!")
..()
/obj/item/slimecross/burning/darkpurple
@@ -135,7 +133,7 @@ Burning extracts:
/obj/item/slimecross/burning/silver/do_effect(mob/user)
var/amount = rand(3,6)
var/list/turfs = list()
- for(var/turf/open/T in range(1,get_turf(user)))
+ for(var/turf/open/T in RANGE_TURFS(1,user))
turfs += T
for(var/i = 0, i < amount, i++)
var/path = get_random_food()
@@ -197,13 +195,13 @@ Burning extracts:
/obj/item/slimecross/burning/red/do_effect(mob/user)
user.visible_message("[src] pulses a hazy red aura for a moment, which wraps around [user]!")
- for(var/mob/living/simple_animal/slime/S in view(7, get_turf(user)))
+ for(var/mob/living/simple_animal/slime/S in hearers(7, get_turf(user)))
if(user in S.Friends)
var/friendliness = S.Friends[user]
- S.Friends = list()
- S.Friends[user] = friendliness
+ S.clear_friends()
+ S.set_friendship(user, friendliness)
else
- S.Friends = list()
+ S.clear_friends()
S.rabid = 1
S.visible_message("The [S] is driven into a dangerous frenzy!")
..()
@@ -287,7 +285,7 @@ Burning extracts:
/obj/item/slimecross/burning/lightpink/do_effect(mob/user)
user.visible_message("[src] lets off a hypnotizing pink glow!")
- for(var/mob/living/carbon/C in view(7, get_turf(user)))
+ for(var/mob/living/carbon/C in hearers(7, get_turf(user)))
C.reagents.add_reagent(/datum/reagent/pax,5)
..()
@@ -297,7 +295,7 @@ Burning extracts:
/obj/item/slimecross/burning/adamantine/do_effect(mob/user)
user.visible_message("[src] crystallizes into a large shield!")
- new /obj/item/twohanded/required/adamantineshield(get_turf(user))
+ new /obj/item/shield/adamantineshield(get_turf(user))
..()
/obj/item/slimecross/burning/rainbow
diff --git a/code/modules/research/xenobiology/crossbreeding/charged.dm b/code/modules/research/xenobiology/crossbreeding/charged.dm
index 9bf5256715909..56eb1e74e263d 100644
--- a/code/modules/research/xenobiology/crossbreeding/charged.dm
+++ b/code/modules/research/xenobiology/crossbreeding/charged.dm
@@ -42,7 +42,7 @@ Charged extracts:
effect_desc = "Instantly makes a large burst of flame for a moment."
/obj/item/slimecross/charged/orange/do_effect(mob/user)
- for(var/turf/turf in range(5,get_turf(user)))
+ for(var/turf/open/turf in RANGE_TURFS(5, user))
if(!locate(/obj/effect/hotspot) in turf)
new /obj/effect/hotspot(turf)
..()
diff --git a/code/modules/research/xenobiology/crossbreeding/chilling.dm b/code/modules/research/xenobiology/crossbreeding/chilling.dm
index 5078f7a131554..fd46fb25f573e 100644
--- a/code/modules/research/xenobiology/crossbreeding/chilling.dm
+++ b/code/modules/research/xenobiology/crossbreeding/chilling.dm
@@ -43,8 +43,8 @@ Chilling extracts:
/obj/item/slimecross/chilling/orange/do_effect(mob/user)
user.visible_message("[src] shatters, and lets out a jet of heat!")
- for(var/turf/T in orange(get_turf(user),2))
- if(get_dist(get_turf(user), T) > 1)
+ for(var/turf/open/T in (RANGE_TURFS(2, user)-RANGE_TURFS(1, user)))
+ if(!locate(/obj/effect/hotspot) in T)
new /obj/effect/hotspot(T)
..()
@@ -77,9 +77,8 @@ Chilling extracts:
/obj/item/slimecross/chilling/metal/do_effect(mob/user)
user.visible_message("[src] melts like quicksilver, and surrounds [user] in a wall!")
- for(var/turf/T in orange(get_turf(user),1))
- if(get_dist(get_turf(user), T) > 0)
- new /obj/effect/forcefield/slimewall(T)
+ for(var/turf/T as() in (RANGE_TURFS(2, user)-get_turf(user)))
+ new /obj/effect/forcefield/slimewall(T)
..()
/obj/item/slimecross/chilling/yellow
@@ -107,7 +106,7 @@ Chilling extracts:
for(var/turf/open/T in A)
var/datum/gas_mixture/G = T.air
if(istype(G))
- G.set_moles(/datum/gas/plasma, 0)
+ G.set_moles(GAS_PLASMA, 0)
filtered = TRUE
T.air_update_turf()
if(filtered)
@@ -226,7 +225,7 @@ Chilling extracts:
/obj/item/slimecross/chilling/red/do_effect(mob/user)
var/slimesfound = FALSE
- for(var/mob/living/simple_animal/slime/S in view(get_turf(user), 7))
+ for(var/mob/living/simple_animal/slime/S in hearers(7, get_turf(user)))
slimesfound = TRUE
S.docile = TRUE
if(slimesfound)
diff --git a/code/modules/research/xenobiology/crossbreeding/consuming.dm b/code/modules/research/xenobiology/crossbreeding/consuming.dm
index 06d236526b1c2..6cc76b4734f3a 100644
--- a/code/modules/research/xenobiology/crossbreeding/consuming.dm
+++ b/code/modules/research/xenobiology/crossbreeding/consuming.dm
@@ -18,7 +18,7 @@ Consuming extracts:
/obj/item/slimecross/consuming/attackby(obj/item/O, mob/user)
if(istype(O,/obj/item/reagent_containers/food/snacks))
if(last_produced + cooldown > world.time)
- to_chat(user, "[src] is still digesting after its last meal!")
+ to_chat(user, "[src] is still digesting after its last meal!")
return
var/datum/reagent/N = O.reagents.has_reagent(/datum/reagent/consumable/nutriment)
if(N)
diff --git a/code/modules/research/xenobiology/crossbreeding/crystalized.dm b/code/modules/research/xenobiology/crossbreeding/crystalized.dm
new file mode 100644
index 0000000000000..00d79c2a4953d
--- /dev/null
+++ b/code/modules/research/xenobiology/crossbreeding/crystalized.dm
@@ -0,0 +1,112 @@
+/obj/item/slimecross/crystalline
+ name = "crystalline extract"
+ desc = "It's crystalline,"
+ effect = "crystalline"
+ icon_state = "crystalline"
+ effect_desc = "Use to place a pylon."
+ var/obj/structure/slime_crystal/crystal_type
+
+/obj/item/slimecross/crystalline/attack_self(mob/user)
+ . = ..()
+
+ var/obj/structure/slime_crystal/C = locate(/obj/structure/slime_crystal) in range(6,get_turf(user))
+
+ if(C)
+ to_chat(user,"You can't build crystals that close to each other!")
+ return
+
+ var/user_turf = get_turf(user)
+
+ if(!do_after(user,15 SECONDS,FALSE,user_turf))
+ return
+
+ new crystal_type(user_turf)
+ qdel(src)
+
+/obj/item/slimecross/crystalline/grey
+ crystal_type = /obj/structure/slime_crystal/grey
+ colour = "grey"
+
+/obj/item/slimecross/crystalline/orange
+ crystal_type = /obj/structure/slime_crystal/orange
+ colour = "orange"
+
+/obj/item/slimecross/crystalline/purple
+ crystal_type = /obj/structure/slime_crystal/purple
+ colour = "purple"
+
+/obj/item/slimecross/crystalline/blue
+ crystal_type = /obj/structure/slime_crystal/blue
+ colour = "blue"
+
+/obj/item/slimecross/crystalline/metal
+ crystal_type = /obj/structure/slime_crystal/metal
+ colour = "metal"
+
+/obj/item/slimecross/crystalline/yellow
+ crystal_type = /obj/structure/slime_crystal/yellow
+ colour = "yellow"
+
+/obj/item/slimecross/crystalline/darkpurple
+ crystal_type = /obj/structure/slime_crystal/darkpurple
+ colour = "dark purple"
+
+/obj/item/slimecross/crystalline/darkblue
+ crystal_type = /obj/structure/slime_crystal/darkblue
+ colour = "dark blue"
+
+/obj/item/slimecross/crystalline/silver
+ crystal_type = /obj/structure/slime_crystal/silver
+ colour = "silver"
+
+/obj/item/slimecross/crystalline/bluespace
+ crystal_type = /obj/structure/slime_crystal/bluespace
+ colour = "bluespace"
+
+/obj/item/slimecross/crystalline/sepia
+ crystal_type = /obj/structure/slime_crystal/sepia
+ colour = "sepia"
+
+/obj/item/slimecross/crystalline/cerulean
+ crystal_type = /obj/structure/slime_crystal/cerulean
+ colour = "cerulean"
+
+/obj/item/slimecross/crystalline/pyrite
+ crystal_type = /obj/structure/slime_crystal/pyrite
+ colour = "pyrite"
+
+/obj/item/slimecross/crystalline/red
+ crystal_type = /obj/structure/slime_crystal/red
+ colour = "red"
+
+/obj/item/slimecross/crystalline/green
+ crystal_type = /obj/structure/slime_crystal/green
+ colour = "green"
+
+/obj/item/slimecross/crystalline/pink
+ crystal_type = /obj/structure/slime_crystal/pink
+ colour = "pink"
+
+/obj/item/slimecross/crystalline/gold
+ crystal_type = /obj/structure/slime_crystal/gold
+ colour = "gold"
+
+/obj/item/slimecross/crystalline/oil
+ crystal_type = /obj/structure/slime_crystal/oil
+ colour = "oil"
+
+/obj/item/slimecross/crystalline/black
+ crystal_type = /obj/structure/slime_crystal/black
+ colour = "black"
+
+/obj/item/slimecross/crystalline/lightpink
+ crystal_type = /obj/structure/slime_crystal/lightpink
+ colour = "light pink"
+
+/obj/item/slimecross/crystalline/adamantine
+ crystal_type = /obj/structure/slime_crystal/adamantine
+ colour = "adamantine"
+
+/obj/item/slimecross/crystalline/rainbow
+ crystal_type = /obj/structure/slime_crystal/rainbow
+ colour = "rainbow"
diff --git a/code/modules/research/xenobiology/crossbreeding/recurring.dm b/code/modules/research/xenobiology/crossbreeding/recurring.dm
index 4a094744f7f81..a526d330763f9 100644
--- a/code/modules/research/xenobiology/crossbreeding/recurring.dm
+++ b/code/modules/research/xenobiology/crossbreeding/recurring.dm
@@ -11,7 +11,7 @@ Recurring extracts:
var/extract_type
var/obj/item/slime_extract/extract
var/cooldown = 0
- var/max_cooldown = 5 //In sets of 2 seconds.
+ var/max_cooldown = 10 // In seconds
/obj/item/slimecross/recurring/Initialize()
. = ..()
@@ -26,9 +26,9 @@ Recurring extracts:
src.forceMove(extract)
START_PROCESSING(SSobj,src)
-/obj/item/slimecross/recurring/process()
+/obj/item/slimecross/recurring/process(delta_time)
if(cooldown > 0)
- cooldown--
+ cooldown -= delta_time
else if(extract.Uses < 10 && extract.Uses > 0)
extract.Uses++
cooldown = max_cooldown
@@ -61,17 +61,17 @@ Recurring extracts:
/obj/item/slimecross/recurring/metal
extract_type = /obj/item/slime_extract/metal
colour = "metal"
- max_cooldown = 10
+ max_cooldown = 20
/obj/item/slimecross/recurring/yellow
extract_type = /obj/item/slime_extract/yellow
colour = "yellow"
- max_cooldown = 10
+ max_cooldown = 20
/obj/item/slimecross/recurring/darkpurple
extract_type = /obj/item/slime_extract/darkpurple
colour = "dark purple"
- max_cooldown = 10
+ max_cooldown = 20
/obj/item/slimecross/recurring/darkblue
extract_type = /obj/item/slime_extract/darkblue
@@ -88,7 +88,7 @@ Recurring extracts:
/obj/item/slimecross/recurring/sepia
extract_type = /obj/item/slime_extract/sepia
colour = "sepia"
- max_cooldown = 18 //No infinite timestop for you!
+ max_cooldown = 36 //No infinite timestop for you!
/obj/item/slimecross/recurring/cerulean
extract_type = /obj/item/slime_extract/cerulean
@@ -113,7 +113,7 @@ Recurring extracts:
/obj/item/slimecross/recurring/gold
extract_type = /obj/item/slime_extract/gold
colour = "gold"
- max_cooldown = 15
+ max_cooldown = 30
/obj/item/slimecross/recurring/oil
extract_type = /obj/item/slime_extract/oil
@@ -130,9 +130,9 @@ Recurring extracts:
/obj/item/slimecross/recurring/adamantine
extract_type = /obj/item/slime_extract/adamantine
colour = "adamantine"
- max_cooldown = 10
+ max_cooldown = 20
/obj/item/slimecross/recurring/rainbow
extract_type = /obj/item/slime_extract/rainbow
colour = "rainbow"
- max_cooldown = 20 //It's pretty powerful.
+ max_cooldown = 40 //It's pretty powerful.
diff --git a/code/modules/research/xenobiology/crossbreeding/regenerative.dm b/code/modules/research/xenobiology/crossbreeding/regenerative.dm
index 73271153a172d..674a01238ae7a 100644
--- a/code/modules/research/xenobiology/crossbreeding/regenerative.dm
+++ b/code/modules/research/xenobiology/crossbreeding/regenerative.dm
@@ -43,7 +43,7 @@ Regenerative extracts:
/obj/item/slimecross/regenerative/orange/core_effect_before(mob/living/target, mob/user)
target.visible_message("The [src] boils over!")
- for(var/turf/turf in range(1,target))
+ for(var/turf/open/turf in RANGE_TURFS(1,target))
if(!locate(/obj/effect/hotspot) in turf)
new /obj/effect/hotspot(turf)
@@ -95,10 +95,10 @@ Regenerative extracts:
/obj/item/slimecross/regenerative/darkpurple/core_effect(mob/living/target, mob/user)
var/equipped = 0
- equipped += target.equip_to_slot_or_del(new /obj/item/clothing/shoes/sneakers/purple(null), SLOT_SHOES)
- equipped += target.equip_to_slot_or_del(new /obj/item/clothing/under/color/lightpurple(null), SLOT_W_UNIFORM)
- equipped += target.equip_to_slot_or_del(new /obj/item/clothing/gloves/color/purple(null), SLOT_GLOVES)
- equipped += target.equip_to_slot_or_del(new /obj/item/clothing/head/soft/purple(null), SLOT_HEAD)
+ equipped += target.equip_to_slot_or_del(new /obj/item/clothing/shoes/sneakers/purple(null), ITEM_SLOT_FEET)
+ equipped += target.equip_to_slot_or_del(new /obj/item/clothing/under/color/lightpurple(null), ITEM_SLOT_ICLOTHING)
+ equipped += target.equip_to_slot_or_del(new /obj/item/clothing/gloves/color/purple(null), ITEM_SLOT_GLOVES)
+ equipped += target.equip_to_slot_or_del(new /obj/item/clothing/head/soft/purple(null), ITEM_SLOT_HEAD)
if(equipped > 0)
target.visible_message("The milky goo congeals into clothing!")
@@ -111,13 +111,13 @@ Regenerative extracts:
return
var/mob/living/carbon/human/H = target
var/fireproofed = FALSE
- if(H.get_item_by_slot(SLOT_WEAR_SUIT))
+ if(H.get_item_by_slot(ITEM_SLOT_OCLOTHING))
fireproofed = TRUE
- var/obj/item/clothing/C = H.get_item_by_slot(SLOT_WEAR_SUIT)
+ var/obj/item/clothing/C = H.get_item_by_slot(ITEM_SLOT_OCLOTHING)
fireproof(C)
- if(H.get_item_by_slot(SLOT_HEAD))
+ if(H.get_item_by_slot(ITEM_SLOT_HEAD))
fireproofed = TRUE
- var/obj/item/clothing/C = H.get_item_by_slot(SLOT_HEAD)
+ var/obj/item/clothing/C = H.get_item_by_slot(ITEM_SLOT_HEAD)
fireproof(C)
if(fireproofed)
target.visible_message("Some of [target]'s clothing gets coated in the goo, and turns blue!")
@@ -226,7 +226,7 @@ Regenerative extracts:
/obj/item/slimecross/regenerative/oil/core_effect(mob/living/target, mob/user)
playsound(src, 'sound/weapons/flash.ogg', 100, 1)
- for(var/mob/living/L in view(user,7))
+ for(var/mob/living/L in viewers(7, user))
L.flash_act()
/obj/item/slimecross/regenerative/black
@@ -247,6 +247,8 @@ Regenerative extracts:
dummy.adjustFireLoss(target.getFireLoss())
dummy.adjustToxLoss(target.getToxLoss())
dummy.adjustOxyLoss(200)
+ //Force death just in case
+ dummy.death()
/obj/item/slimecross/regenerative/lightpink
colour = "light pink"
diff --git a/code/modules/research/xenobiology/crossbreeding/reproductive.dm b/code/modules/research/xenobiology/crossbreeding/reproductive.dm
index be66ed02d705f..7502eb5ad0c3d 100644
--- a/code/modules/research/xenobiology/crossbreeding/reproductive.dm
+++ b/code/modules/research/xenobiology/crossbreeding/reproductive.dm
@@ -13,6 +13,12 @@ Reproductive extracts:
var/cubes_eaten = 0
var/last_produce = 0
var/cooldown = 30 // 3 seconds.
+ var/static/list/typecache_to_take
+
+/obj/item/slimecross/reproductive/Initialize()
+ . = ..()
+ if(!typecache_to_take)
+ typecache_to_take = typecacheof(/obj/item/reagent_containers/food/snacks/monkeycube)
/obj/item/slimecross/reproductive/attackby(obj/item/O, mob/user)
if((last_produce + cooldown) > world.time)
@@ -20,7 +26,7 @@ Reproductive extracts:
return
if(istype(O, /obj/item/storage/bag/bio))
var/list/inserted = list()
- SEND_SIGNAL(O, COMSIG_TRY_STORAGE_TAKE_TYPE, /obj/item/reagent_containers/food/snacks/monkeycube, src, 1, null, null, user, inserted)
+ SEND_SIGNAL(O, COMSIG_TRY_STORAGE_TAKE_TYPE, typecache_to_take, src, 1, null, null, user, inserted)
if(inserted.len)
var/obj/item/reagent_containers/food/snacks/monkeycube/M = inserted[1]
if(istype(M))
diff --git a/code/modules/research/xenobiology/crossbreeding/transformative.dm b/code/modules/research/xenobiology/crossbreeding/transformative.dm
new file mode 100644
index 0000000000000..d037c9db3ab0c
--- /dev/null
+++ b/code/modules/research/xenobiology/crossbreeding/transformative.dm
@@ -0,0 +1,168 @@
+/*
+transformative extracts:
+ apply a permanent effect to a slime and all of its babies
+*/
+/obj/item/slimecross/transformative
+ name = "transformative extract"
+ desc = "It seems to stick to any slime it comes in contact with."
+ icon_state = "transformative"
+ effect = "transformative"
+ var/effect_applied = SLIME_EFFECT_DEFAULT
+
+/obj/item/slimecross/transformative/afterattack(atom/target, mob/user, proximity)
+ if(!proximity || !isslime(target))
+ return FALSE
+ var/mob/living/simple_animal/slime/S = target
+ if(S.stat)
+ to_chat(user, "The slime is dead!")
+ if(S.transformeffects & effect_applied)
+ to_chat(user,"This slime already has the [colour] transformative effect applied!")
+ return FALSE
+ to_chat(user,"You apply [src] to [target].")
+ do_effect(S, user)
+ S.transformeffects = effect_applied //S.transformeffects |= effect_applied
+ qdel(src)
+
+/obj/item/slimecross/transformative/proc/do_effect(mob/living/simple_animal/slime/S, mob/user)
+ SHOULD_CALL_PARENT(TRUE)
+ if(S.transformeffects & SLIME_EFFECT_LIGHT_PINK)
+ S.remove_from_spawner_menu()
+ S.master = null
+ if(S.transformeffects & SLIME_EFFECT_METAL)
+ S.maxHealth = round(S.maxHealth/1.3)
+ if(S.transformeffects & SLIME_EFFECT_BLUESPACE)
+ S.remove_verb(/mob/living/simple_animal/slime/proc/teleport)
+ if(S.transformeffects & SLIME_EFFECT_PINK)
+ var/datum/language_holder/LH = S.get_language_holder()
+ LH.selected_language = /datum/language/slime
+
+/obj/item/slimecross/transformative/grey
+ colour = "grey"
+ effect_applied = SLIME_EFFECT_GREY
+ effect_desc = "Slimes split into one additional slime."
+
+/obj/item/slimecross/transformative/orange
+ colour = "orange"
+ effect_applied = SLIME_EFFECT_ORANGE
+ effect_desc = "Slimes will light people on fire when they shock them."
+
+/obj/item/slimecross/transformative/purple
+ colour = "purple"
+ effect_applied = SLIME_EFFECT_PURPLE
+ effect_desc = "Slimes will regenerate slowly."
+
+/obj/item/slimecross/transformative/blue
+ colour = "blue"
+ effect_applied = SLIME_EFFECT_BLUE
+ effect_desc = "Slime will always retain slime of its original colour when splitting."
+
+/obj/item/slimecross/transformative/metal
+ colour = "metal"
+ effect_applied = SLIME_EFFECT_METAL
+ effect_desc = "Slimes will be able to sustain more damage before dying."
+
+/obj/item/slimecross/transformative/metal/do_effect(mob/living/simple_animal/slime/S)
+ ..()
+ S.maxHealth = round(S.maxHealth*1.3)
+
+/obj/item/slimecross/transformative/yellow
+ colour = "yellow"
+ effect_applied = SLIME_EFFECT_YELLOW
+ effect_desc = "Slimes will gain electric charge faster."
+
+/obj/item/slimecross/transformative/darkpurple
+ colour = "dark purple"
+ effect_applied = SLIME_EFFECT_DARK_PURPLE
+ effect_desc = "Slime rapidly converts atmospheric plasma to oxygen, healing in the process."
+
+/obj/item/slimecross/transformative/darkblue
+ colour = "dark blue"
+ effect_applied = SLIME_EFFECT_DARK_BLUE
+ effect_desc = "Slimes takes reduced damage from water."
+
+/obj/item/slimecross/transformative/silver
+ colour = "silver"
+ effect_applied = SLIME_EFFECT_SILVER
+ effect_desc = "Slimes will no longer lose nutrition over time."
+
+/obj/item/slimecross/transformative/bluespace
+ colour = "bluespace"
+ effect_applied = SLIME_EFFECT_BLUESPACE
+ effect_desc = "Slimes will teleport to targets when they are at full electric charge."
+
+/obj/item/slimecross/transformative/bluespace/do_effect(mob/living/simple_animal/slime/S, mob/user)
+ ..()
+ S.add_verb(/mob/living/simple_animal/slime/proc/teleport)
+
+/obj/item/slimecross/transformative/sepia
+ colour = "sepia"
+ effect_applied = SLIME_EFFECT_SEPIA
+ effect_desc = "Slimes move faster."
+
+/obj/item/slimecross/transformative/cerulean
+ colour = "cerulean"
+ effect_applied = SLIME_EFFECT_CERULEAN
+ effect_desc = "Slime makes another adult rather than splitting, with half the nutrition."
+
+/obj/item/slimecross/transformative/pyrite
+ colour = "pyrite"
+ effect_applied = SLIME_EFFECT_PYRITE
+ effect_desc = "Slime always splits into totally random colors, except rainbow. Can never yield a rainbow slime."
+
+/obj/item/slimecross/transformative/red
+ colour = "red"
+ effect_applied = SLIME_EFFECT_RED
+ effect_desc = "Slimes does 10% more damage when feeding and attacking."
+
+/obj/item/slimecross/transformative/green
+ colour = "green"
+ effect_applied = SLIME_EFFECT_GREEN
+ effect_desc = "Slimes will eat corpses."
+
+/obj/item/slimecross/transformative/pink
+ colour = "pink"
+ effect_applied = SLIME_EFFECT_PINK
+ effect_desc = "Slimes will speak in common rather than in slime."
+
+/obj/item/slimecross/transformative/pink/do_effect(mob/living/simple_animal/slime/S)
+ ..()
+ S.grant_language(/datum/language/common, TRUE, TRUE)
+ var/datum/language_holder/LH = S.get_language_holder()
+ LH.selected_language = /datum/language/common
+
+/obj/item/slimecross/transformative/gold
+ colour = "gold"
+ effect_applied = SLIME_EFFECT_GOLD
+ effect_desc = "Slime extracts from these will sell for double the price."
+
+/obj/item/slimecross/transformative/oil
+ colour = "oil"
+ effect_applied = SLIME_EFFECT_OIL
+ effect_desc = "Slime douses anything it feeds on in welding fuel."
+
+/obj/item/slimecross/transformative/black
+ colour = "black"
+ effect_applied = SLIME_EFFECT_BLACK
+ effect_desc = "Slime is nearly transparent."
+
+/obj/item/slimecross/transformative/lightpink
+ colour = "light pink"
+ effect_applied = SLIME_EFFECT_LIGHT_PINK
+ effect_desc = "Slimes may become possessed by supernatural forces."
+
+/obj/item/slimecross/transformative/lightpink/do_effect(mob/living/simple_animal/slime/S, mob/user)
+ ..()
+ GLOB.poi_list |= S
+ S.make_master(user)
+ LAZYADD(GLOB.mob_spawners["[S.master.real_name]'s slime"], S)
+ SSmobs.update_spawners()
+
+/obj/item/slimecross/transformative/adamantine
+ colour = "adamantine"
+ effect_applied = SLIME_EFFECT_ADAMANTINE
+ effect_desc = "Slimes takes reduced damage from brute attacks."
+
+/obj/item/slimecross/transformative/rainbow
+ colour = "rainbow"
+ effect_applied = SLIME_EFFECT_RAINBOW
+ effect_desc = "Slime randomly changes color periodically."
diff --git a/code/modules/research/xenobiology/crossbreeding/warping.dm b/code/modules/research/xenobiology/crossbreeding/warping.dm
new file mode 100644
index 0000000000000..dda0ea678116e
--- /dev/null
+++ b/code/modules/research/xenobiology/crossbreeding/warping.dm
@@ -0,0 +1,847 @@
+/*
+Warping extracts crossbreed
+put up a rune with bluespace effects, lots of those runes are fluff or act as a passive buff, others are just griefing tools
+*/
+
+/obj/item/slimecross/warping
+ name = "warped extract"
+ desc = "It just won't stay in place."
+ icon_state = "warping"
+ effect = "warping"
+ colour = "grey"
+ ///what runes will be drawn depending on the crossbreed color
+ var/obj/effect/warped_rune/runepath
+ /// the number of "charge" a bluespace crossbreed start with
+ var/warp_charge = INFINITY
+ ///time it takes to store the rune back into the crossbreed
+ var/storing_time = 5 SECONDS
+ ///time it takes to draw the rune
+ var/drawing_time = 5 SECONDS
+ var/max_cooldown = 30 SECONDS
+ var/cooldown = 0
+
+/obj/effect/warped_rune
+ name = "warped rune"
+ desc = "An unstable rune born of the depths of bluespace"
+ icon = 'icons/obj/slimecrossing.dmi'
+ icon_state = "greyspace_rune"
+ move_resist = INFINITY //here to avoid the rune being moved since it only sets it's turf once when it's drawn. doesn't include admin fuckery.
+ anchored = TRUE
+ layer = MID_TURF_LAYER
+ resistance_flags = FIRE_PROOF
+ var/dir_sound = 'sound/effects/phasein.ogg'
+ var/activated_on_step = FALSE
+ ///is only used for bluespace crystal erasing as of now
+ var/storing_time = 5 SECONDS
+ ///Nearly all runes needs to know which turf they are on
+ var/turf/rune_turf
+ var/remove_on_activation = TRUE
+
+/obj/item/slimecross/warping/examine()
+ . = ..()
+ . += "It has [warp_charge] charge left"
+
+///runes can also be deleted by bluespace crystals relatively fast as an alternative to cleaning them.
+/obj/effect/warped_rune/attackby(obj/item/used_item, mob/user)
+ . = ..()
+ if(!istype(used_item,/obj/item/stack/sheet/bluespace_crystal) && !istype(used_item,/obj/item/stack/ore/bluespace_crystal))
+ return
+
+ var/obj/item/stack/space_crystal = used_item
+ if(do_after(user, storing_time,target = src)) //the time it takes to nullify it depends on the rune too
+ to_chat(user, "You nullify the effects of the rune with the bluespace crystal!")
+ space_crystal.use(1)
+ playsound(src, 'sound/effects/phasein.ogg', 20, TRUE)
+ qdel(src)
+
+/obj/effect/warped_rune/acid_act()
+ . = ..()
+ visible_message("[src] has been dissolved by the acid")
+ playsound(src, 'sound/items/welder.ogg', 150, TRUE)
+ qdel(src)
+
+
+///nearly all runes use their turf in some way so we set rune_turf to their turf automatically, the rune also start on cooldown if it uses one.
+/obj/effect/warped_rune/Initialize()
+ . = ..()
+ add_overlay("blank", TRUE)
+ rune_turf = get_turf(src)
+ RegisterSignal(rune_turf, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_rune)
+
+/obj/effect/warped_rune/proc/clean_rune()
+ SIGNAL_HANDLER
+
+ qdel(src)
+
+///using the extract on the floor will "draw" the rune.
+/obj/item/slimecross/warping/afterattack(atom/target, mob/user, proximity)
+ . = ..()
+ if(!proximity)
+ return
+
+ if(istype(target, runepath)) //checks if the target is a rune and then if you can store it
+ if(do_after(user, storing_time,target = target))
+ warping_crossbreed_absorb(target, user)
+ return
+
+ if(isturf(target) && locate(/obj/effect/warped_rune) in target) //check if the target is a floor and if there's a rune on said floor
+ to_chat(user, "There is already a bluespace rune here!")
+ return
+
+ if(!isfloorturf(target))
+ to_chat(user, "you cannot draw a rune here!")
+ return
+
+ if(warp_charge < 1) //check if we have at least 1 charge left.
+ to_chat(user, "[src] is empty!")
+ return
+
+ if(!check_cd(user))
+ return
+
+ if(do_after(user, drawing_time,target = target))
+ if(warp_charge >= 1 && (!locate(/obj/effect/warped_rune) in target) && check_cd(user)) //check one last time if a rune has been drawn during the do_after and if there's enough charges left
+ warping_crossbreed_spawn(target,user)
+ make_cd()
+
+
+///spawns the rune, taking away one rune charge
+/obj/item/slimecross/warping/proc/warping_crossbreed_spawn(atom/target, mob/user)
+ playsound(target, 'sound/effects/slosh.ogg', 20, TRUE)
+ warp_charge--
+ new runepath(target)
+ to_chat(user, "You carefully draw the rune with [src].")
+
+
+///absorb the rune into the crossbreed adding one more charge to the crossbreed.
+/obj/item/slimecross/warping/proc/warping_crossbreed_absorb(atom/target, mob/user)
+ //to_chat(user, "You store the rune in [src].")
+ qdel(target)
+ warp_charge++
+
+/obj/item/slimecross/warping/proc/check_cd(user)
+ if(world.time < cooldown)
+ if(user)
+ to_chat(user, "[src] is recharging energy.")
+ return FALSE
+ return TRUE
+
+/obj/item/slimecross/warping/proc/make_cd()
+ cooldown = world.time + max_cooldown
+
+/obj/effect/warped_rune/attack_hand(mob/living/user)
+ . = ..()
+ do_effect(user)
+
+/obj/effect/warped_rune/proc/do_effect(mob/user)
+ SHOULD_CALL_PARENT(TRUE)
+ if(remove_on_activation)
+ playsound(rune_turf, dir_sound, 20, TRUE)
+ to_chat(user, ("[src] fades."))
+ qdel(src)
+
+/obj/effect/warped_rune/Crossed(atom/movable/AM, oldloc)
+ SHOULD_CALL_PARENT(TRUE)
+ . = ..()
+ if(activated_on_step)
+ playsound(rune_turf, dir_sound, 20, TRUE)
+ visible_message("[src] fades.")
+ qdel(src)
+
+/obj/item/slimecross/warping/grey
+ name = "greyspace crossbreed"
+ colour = "grey"
+ effect_desc = "Draws a rune. Extracts that are on the rune are absorbed, 8 extracts produces an adult slime of that color."
+ runepath = /obj/effect/warped_rune/greyspace
+
+/obj/effect/warped_rune/greyspace
+ name = "greyspace rune"
+ desc = "Death is merely a setback, anything can be rebuilt given the right components."
+ icon_state = "rune_grey"
+ ///extracttype is used to remember the type of the extract on the rune
+ var/extracttype
+ var/req_extracts = 8
+
+/obj/effect/warped_rune/greyspace/examine(mob/user)
+ . = ..()
+ to_chat(user, "Requires absorbing [req_extracts] [extracttype ? "[extracttype] extracts" : "slime extracts"].")
+
+/obj/effect/warped_rune/greyspace/do_effect(mob/user)
+ for(var/obj/item/slime_extract/extract in rune_turf)
+ if(extract.color_slime == extracttype || !extracttype) //check if the extract is the first one or of the right color.
+ extracttype = extract.color_slime
+ qdel(extract) //vores the slime extract
+ req_extracts--
+ if(req_extracts <= 0)
+ switch(extracttype)
+ if("lightpink")
+ extracttype = "light pink"
+ if("darkblue")
+ extracttype = "dark blue"
+ if("darkpurple")
+ extracttype = "dark purple"
+ new /mob/living/simple_animal/slime (rune_turf, extracttype) //spawn a slime from the extract's color
+ req_extracts = initial(req_extracts)
+ extracttype = null // reset extracttype to FALSE to allow a new extract type
+ . = ..()
+ break
+ playsound(rune_turf, 'sound/effects/splat.ogg', 20, TRUE)
+ else
+ to_chat(user, "Requires a [extracttype ? "[extracttype] extracts" : "slime extract"].")
+
+
+/obj/item/slimecross/warping/orange
+ colour = "orange"
+ runepath = /obj/effect/warped_rune/orangespace
+ effect_desc = "Draws a rune that can summon a bonfire."
+
+/obj/effect/warped_rune/orangespace
+ desc = "This can be activated to summon a bonfire."
+ icon_state = "rune_orange"
+
+/obj/effect/warped_rune/orangespace/do_effect(mob/user)
+ var/obj/structure/bonfire/bluespace/B = new (rune_turf)
+ B.StartBurning()
+ . = ..()
+
+/obj/item/slimecross/warping/purple
+ colour = "purple"
+ runepath = /obj/effect/warped_rune/purplespace
+ effect_desc = "Draws a rune that may be activated to summon two random medical items."
+
+/obj/effect/warped_rune/purplespace
+ desc = "This can be activated to summon two random medical."
+ icon_state = "rune_purple"
+
+/obj/effect/warped_rune/purplespace/do_effect(mob/user)
+ var/list/medical = list(
+ /obj/item/stack/medical/gauze,
+ /obj/item/reagent_containers/hypospray/medipen,
+ /obj/item/stack/medical/bruise_pack,
+ /obj/item/stack/medical/ointment,
+ /obj/item/reagent_containers/pill/oxandrolone,
+ /obj/item/storage/pill_bottle/charcoal,
+ /obj/item/reagent_containers/pill/mutadone,
+ /obj/item/reagent_containers/pill/antirad,
+ /obj/item/reagent_containers/pill/patch/styptic,
+ /obj/item/reagent_containers/pill/patch/synthflesh,
+ /obj/item/reagent_containers/pill/patch/silver_sulf,
+ /obj/item/healthanalyzer,
+ /obj/item/surgical_drapes,
+ /obj/item/scalpel,
+ /obj/item/hemostat,
+ /obj/item/cautery,
+ /obj/item/circular_saw,
+ /obj/item/surgicaldrill,
+ /obj/item/retractor,
+ /obj/item/blood_filter)
+
+ for(var/i in 1 to 2)
+ var/path = pick_n_take(medical)
+ new path(rune_turf)
+ . = ..()
+
+/obj/item/slimecross/warping/blue
+ colour = "blue"
+ runepath = /obj/effect/warped_rune/cyanspace //we'll call the blue rune cyanspace to not mix it up with actual bluespace rune
+ effect_desc = "Draw a rune that is slippery like water and may be activated to cover all adjacent tiles in ice."
+
+/obj/effect/warped_rune/cyanspace
+ icon_state = "rune_blue"
+ desc = "Its slippery like water and may be activated to cover all adjacent tiles in ice."
+
+/obj/effect/warped_rune/cyanspace/do_effect(mob/user)
+ for(var/turf/open/T in RANGE_TURFS(1, src) - rune_turf)
+ T.MakeSlippery(TURF_WET_PERMAFROST, 1 MINUTES)
+ . = ..()
+
+/obj/effect/warped_rune/cyanspace/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/slippery, 30)
+
+/obj/effect/warped_rune/cyanspace/Crossed(atom/movable/AM, oldloc)
+ if(isliving(AM))
+ activated_on_step = TRUE
+ . = ..()
+
+/obj/item/slimecross/warping/dark_blue
+ colour = "dark blue"
+ runepath = /obj/effect/warped_rune/darkcyanspace //we'll call the blue rune cyanspace to not mix it up with actual bluespace rune
+ effect_desc = "Draw a rune that can lower the temperature of whoever steps on it."
+
+/obj/effect/warped_rune/darkcyanspace
+ icon_state = "rune_dark_blue"
+ desc = "Refreshing!"
+ remove_on_activation = FALSE
+
+/obj/effect/warped_rune/darkcyanspace/Crossed(atom/movable/AM, oldloc)
+ if(isliving(AM))
+ var/mob/living/L = AM
+ L.adjust_bodytemperature(-300)
+ activated_on_step = TRUE
+ . = ..()
+
+/obj/item/slimecross/warping/metal
+ colour = "metal"
+ runepath = /obj/effect/warped_rune/metalspace
+ effect_desc = "Draws a rune that may be activated to create a 3x3 block of invisible walls."
+
+//It's a wall what do you want from me
+/obj/effect/warped_rune/metalspace
+ desc = "This can be activated to to create a 3x3 block of invisible walls."
+ icon_state = "rune_metal"
+
+/obj/effect/warped_rune/metalspace/do_effect(mob/user)
+ for(var/turf/open/T in RANGE_TURFS(1, src))
+ new /obj/effect/forcefield/mime(T, 150)
+ . = ..()
+
+/obj/item/slimecross/warping/yellow
+ colour = "yellow"
+ runepath = /obj/effect/warped_rune/yellowspace
+ effect_desc = "Draw a rune that causes electrical interference."
+
+/obj/effect/warped_rune/yellowspace
+ desc = "Be careful with taking power cells with you!"
+ icon_state = "rune_yellow"
+ remove_on_activation = FALSE
+
+/obj/effect/warped_rune/yellowspace/Crossed(atom/movable/AM, oldloc)
+ var/obj/item/stock_parts/cell/C = AM.get_cell()
+ if(!C && isliving(AM))
+ var/mob/living/L = AM
+ for(var/obj/item/I in L.GetAllContents())
+ C = I.get_cell()
+ if(C?.charge)
+ break
+ if(C?.charge)
+ do_sparks(5,FALSE,C)
+ empulse(rune_turf, 1, 1)
+ C.use(C.charge)
+ activated_on_step = TRUE
+ . = ..()
+
+/obj/item/slimecross/warping/darkpurple
+ colour = "dark purple"
+ runepath = /obj/effect/warped_rune/darkpurplespace
+ effect_desc = "Draw a rune that can transmute plasma into any other material."
+
+/obj/effect/warped_rune/darkpurplespace
+ icon = 'icons/obj/slimecrossing.dmi'
+ icon_state = "rune_dark_purple"
+ desc = "To gain something you must sacrifice something else in return."
+ var/static/list/materials = list(/obj/item/stack/sheet/iron, /obj/item/stack/sheet/glass, /obj/item/stack/sheet/mineral/silver,
+ /obj/item/stack/sheet/mineral/gold, /obj/item/stack/sheet/mineral/diamond, /obj/item/stack/sheet/mineral/uranium,
+ /obj/item/stack/sheet/mineral/titanium, /obj/item/stack/sheet/mineral/copper, /obj/item/stack/sheet/mineral/uranium,
+ /obj/item/stack/sheet/bluespace_crystal)
+
+/obj/effect/warped_rune/darkpurplespace/do_effect(mob/user)
+ if(locate(/obj/item/stack/sheet/mineral/plasma) in rune_turf)
+ var/amt = 0
+ for(var/obj/item/stack/sheet/mineral/plasma/P in rune_turf)
+ amt += P.amount
+ qdel(P)
+ var/path_material = pick(materials)
+ new path_material(rune_turf, amt)
+ return ..()
+ else
+ to_chat(user, "Requires plasma!")
+
+/obj/item/slimecross/warping/silver
+ colour = "silver"
+ effect_desc = "Draw a rune that can feed whoever steps on it.."
+ runepath = /obj/effect/warped_rune/silverspace
+
+/obj/effect/warped_rune/silverspace
+ desc = "This feeds whoever steps on it."
+ icon_state = "rune_silver"
+ remove_on_activation = FALSE
+
+/obj/effect/warped_rune/silverspace/Crossed(atom/movable/AM, oldloc)
+ if(iscarbon(AM))
+ var/mob/living/carbon/C = AM
+ C.reagents.add_reagent(/datum/reagent/consumable/nutriment, 100)
+ activated_on_step = TRUE
+ . = ..()
+
+GLOBAL_DATUM(blue_storage, /obj/item/storage/backpack/holding/bluespace)
+
+/obj/item/storage/backpack/holding/bluespace
+ name = "warped rune"
+ anchored = TRUE
+ armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100, "stamina" = 100)
+ invisibility = INVISIBILITY_ABSTRACT
+ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+
+/obj/item/slimecross/warping/bluespace
+ colour = "bluespace"
+ runepath = /obj/effect/warped_rune/bluespace
+ effect_desc = "Draw a rune that serves as a bluespace container."
+
+/obj/effect/warped_rune/bluespace
+ desc = "When activated, it gives access to a bluespace container."
+ icon_state = "rune_bluespace"
+ remove_on_activation = FALSE
+
+/obj/effect/warped_rune/bluespace/do_effect(mob/user)
+ if(!GLOB.blue_storage)
+ GLOB.blue_storage = new
+ GLOB.blue_storage.loc = loc
+ var/datum/component/storage/STR = GLOB.blue_storage.GetComponent(/datum/component/storage)
+ STR.show_to(user)
+ playsound(rune_turf, dir_sound, 20, TRUE)
+ . = ..()
+
+/obj/item/slimecross/warping/sepia
+ colour = "sepia"
+ runepath = /obj/effect/warped_rune/sepiaspace
+ effect_desc = "Rune activates automatically when stepped on, triggering a timestop around it."
+
+/obj/effect/warped_rune/sepiaspace
+ desc = "stepping on it stops time around it."
+ icon_state = "rune_sepia"
+ remove_on_activation = FALSE
+
+/obj/effect/warped_rune/sepiaspace/Crossed(atom/movable/AM, oldloc)
+ new /obj/effect/timestop(rune_turf, null, null, null)
+ activated_on_step = TRUE
+ . = ..()
+
+/obj/item/slimecross/warping/cerulean
+ colour = "cerulean"
+ runepath = /obj/effect/warped_rune/ceruleanspace
+ effect_desc = "Draws a rune that creates a hologram of the first living thing that stepped on the tile."
+
+/obj/effect/warped_rune/ceruleanspace
+ desc = "A shadow of what once passed these halls, a memory perhaps?"
+ icon_state = "rune_cerulean"
+ remove_on_activation = FALSE
+ ///hologram that will be spawned by the rune
+ var/obj/effect/overlay/holotile
+ ///mob the hologram will copy
+ var/mob/living/holo_host
+ ///used to remember the recent speech of the holo_host
+ var/list/recent_speech
+ ///used to remember the timer ID that activates holo_talk
+
+/obj/effect/warped_rune/ceruleanspace/proc/holo_talk()
+ if(holotile && length(recent_speech)) //the proc should'nt be called if the list is empty in the first place but we might as well make sure.
+ holotile.say(recent_speech[pick(recent_speech)]) //say one of the 10 latest sentence said by the holo_host
+ addtimer(CALLBACK(src, .proc/holo_talk), 10 SECONDS, TIMER_OVERRIDE|TIMER_UNIQUE)
+
+/obj/effect/warped_rune/ceruleanspace/Crossed(atom/movable/AM, oldloc)
+ . = ..()
+ if(isliving(AM) && !holo_host)
+ holo_host = AM
+
+/obj/effect/warped_rune/ceruleanspace/do_effect(mob/user)
+ . = ..()
+ if(holo_host && !holotile)
+ holo_creation()
+ remove_on_activation = TRUE
+ playsound(rune_turf, dir_sound, 20, TRUE)
+
+/obj/effect/warped_rune/ceruleanspace/proc/holo_creation()
+ addtimer(CALLBACK(src, .proc/holo_talk), 10 SECONDS, TIMER_OVERRIDE|TIMER_UNIQUE)
+
+ if(locate(holotile) in rune_turf)//here to delete the previous hologram,
+ QDEL_NULL(holotile)
+
+ holotile = new(rune_turf) //setting up the hologram to look like the person that just stepped in
+ holotile.icon = holo_host.icon
+ holotile.icon_state = holo_host.icon_state
+ holotile.alpha = 200
+ holotile.name = "[holo_host.name] (Hologram)"
+ holotile.add_atom_colour("#77abff", FIXED_COLOUR_PRIORITY)
+ holotile.copy_overlays(holo_host, TRUE)
+ holotile.anchored = TRUE
+ holotile.density = FALSE
+
+ //the code that follows is basically the code that changeling use to get people's last spoken sentences with a few tweaks.
+ recent_speech = list() //resets the list from its previous sentences
+ var/list/say_log = list()
+ var/log_source = holo_host.logging
+ for(var/log_type in log_source)
+ var/nlog_type = text2num(log_type)
+ if(nlog_type & LOG_SAY)
+ var/list/reversed = log_source[log_type] //reverse the list so we get the last sentences instead of the first
+ if(islist(reversed))
+ say_log = reverseRange(reversed.Copy())
+ break
+
+ if(length(say_log) > 10) //we're going to get up to the last 10 sentences spoken by the holo_host
+ recent_speech = say_log.Copy(say_log.len - 11, 0)
+ else
+ for(var/spoken_memory in say_log)
+ if(recent_speech.len >= 10)
+ break
+ recent_speech[spoken_memory] = say_log[spoken_memory]
+
+ if(!length(recent_speech)) //lazy lists don't work here for whatever reason so we set it to null manually if the list is empty.
+ recent_speech = null
+
+///destroys the hologram with the rune
+/obj/effect/warped_rune/ceruleanspace/Destroy()
+ QDEL_NULL(holotile)
+ holo_host = null
+ recent_speech = null
+ return ..()
+
+/obj/item/slimecross/warping/pyrite
+ colour = "pyrite"
+ runepath = /obj/effect/warped_rune/pyritespace
+ effect_desc = "draws a rune that will randomly color whatever steps on it."
+
+/obj/effect/warped_rune/pyritespace
+ desc = "Who shall we be today? they asked, but not even the canvas would answer."
+ icon_state = "rune_pyrite"
+ remove_on_activation = FALSE
+ var/colour = "#FFFFFF"
+
+/obj/effect/warped_rune/pyritespace/Initialize()
+ . = ..()
+ colour = pick("#FFFFFF", "#FF0000", "#FFA500", "#FFFF00", "#00FF00", "#0000FF", "#4B0082", "#FF00FF")
+
+/obj/effect/warped_rune/pyritespace/Crossed(atom/movable/AM, oldloc)
+ if(isliving(AM))
+ AM.add_atom_colour(colour, WASHABLE_COLOUR_PRIORITY)
+ activated_on_step = TRUE
+ playsound(src, 'sound/items/bikehorn.ogg', 50, TRUE)
+ . = ..()
+
+/obj/item/slimecross/warping/red
+ colour = "red"
+ runepath = /obj/effect/warped_rune/redspace
+ effect_desc = "Draw a rune that covers with blood whoever steps on it."
+
+/obj/effect/warped_rune/redspace
+ desc = "Watch out for blood!"
+ icon_state = "rune_red"
+ remove_on_activation = FALSE
+
+/obj/effect/warped_rune/redspace/Crossed(atom/movable/AM, oldloc)
+ if(ishuman(AM))
+ var/mob/living/carbon/human/H = AM
+ add_blood_DNA(list("Non-human DNA" = random_blood_type()))
+ for(var/obj/item/I in H.get_equipped_items(TRUE))
+ I.add_blood_DNA(return_blood_DNA())
+ I.update_icon()
+ for(var/obj/item/I in H.held_items)
+ I.add_blood_DNA(return_blood_DNA())
+ I.update_icon()
+ playsound(src, 'sound/effects/blobattack.ogg', 50, TRUE)
+ activated_on_step = TRUE
+ . = ..()
+
+/obj/item/slimecross/warping/green
+ colour = "green"
+ effect_desc = "Draw a rune that alters the DNA of those who step on it."
+ runepath = /obj/effect/warped_rune/greenspace
+
+/obj/effect/warped_rune/greenspace
+ desc = "Warning: don't step on this if you want to keep your genes."
+ icon_state = "rune_green"
+ remove_on_activation = FALSE
+
+/obj/effect/warped_rune/greenspace/Crossed(atom/movable/AM, oldloc)
+ if(ishuman(AM))
+ randomize_human(AM)
+ activated_on_step = TRUE
+ . = ..()
+
+/* pink rune, makes people slightly happier after walking on it*/
+/obj/item/slimecross/warping/pink
+ colour = "pink"
+ effect_desc = "Draws a rune that makes people happier!"
+ runepath = /obj/effect/warped_rune/pinkspace
+
+/obj/effect/warped_rune/pinkspace
+ desc = "Love is the only reliable source of happiness we have left. But like everything, it comes with a price."
+ icon_state = "rune_pink"
+ remove_on_activation = FALSE
+
+///adds the jolly mood effect along with hug sound effect.
+/obj/effect/warped_rune/pinkspace/Crossed(atom/movable/AM, oldloc)
+ if(istype(AM, /mob/living/carbon/human))
+ playsound(rune_turf, "sound/weapons/thudswoosh.ogg", 50, TRUE)
+ SEND_SIGNAL(AM, COMSIG_ADD_MOOD_EVENT,"jolly", /datum/mood_event/jolly)
+ to_chat(AM, "You feel happier.")
+ activated_on_step = TRUE
+ . = ..()
+
+/obj/item/slimecross/warping/gold
+ colour = "gold"
+ runepath = /obj/effect/warped_rune/goldspace
+ effect_desc = "Draw a rune that exchanges objects of this dimension for objects of a parallel dimension."
+
+/obj/effect/warped_rune/goldspace
+ icon_state = "rune_gold"
+ desc = "This can be activated to transmute valuable items into a random item."
+ remove_on_activation = FALSE
+ var/target_value = 5000
+ var/static/list/common_items = list(
+ /obj/item/toy/plush/carpplushie,
+ /obj/item/toy/plush/bubbleplush,
+ /obj/item/toy/plush/plushvar,
+ /obj/item/toy/plush/narplush,
+ /obj/item/toy/plush/lizardplushie,
+ /obj/item/toy/plush/snakeplushie,
+ /obj/item/toy/plush/nukeplushie,
+ /obj/item/toy/plush/slimeplushie,
+ /obj/item/toy/plush/awakenedplushie,
+ /obj/item/toy/plush/beeplushie,
+ /obj/item/toy/plush/moth,
+ /obj/item/toy/eightball/haunted,
+ /obj/item/toy/foamblade,
+ /obj/item/toy/katana,
+ /obj/item/toy/snappop/phoenix,
+ /obj/item/toy/cards/deck/unum,
+ /obj/item/toy/redbutton,
+ /obj/item/toy/toy_xeno,
+ /obj/item/toy/reality_pierce,
+ /obj/item/toy/xmas_cracker,
+ /obj/item/gun/ballistic/automatic/c20r/toy/unrestricted,
+ /obj/item/gun/ballistic/automatic/l6_saw/toy/unrestricted,
+ /obj/item/gun/ballistic/automatic/toy/pistol/unrestricted,
+ /obj/item/gun/ballistic/shotgun/toy/unrestricted,
+ /obj/item/gun/ballistic/shotgun/toy/crossbow,
+ /obj/item/clothing/mask/facehugger/toy,
+ /obj/item/dualsaber/toy,
+ /obj/item/clothing/under/costume/roman,
+ /obj/item/clothing/under/costume/pirate,
+ /obj/item/clothing/under/costume/kilt/highlander,
+ /obj/item/clothing/under/costume/gladiator/ash_walker,
+ /obj/item/clothing/under/costume/geisha,
+ /obj/item/clothing/under/costume/villain,
+ /obj/item/clothing/under/costume/singer/yellow,
+ /obj/item/clothing/under/costume/russian_officer
+ )
+
+ var/static/list/uncommon_items = list(
+ /obj/item/clothing/head/speedwagon/cursed,
+ /obj/item/clothing/suit/space/hardsuit/ancient,
+ /obj/item/gun/energy/laser/retro/old,
+ /obj/item/storage/toolbox/mechanical/old,
+ /obj/item/storage/toolbox/emergency/old,
+ /obj/effect/spawner/lootdrop/three_course_meal,
+ /mob/living/simple_animal/pet/dog/corgi/puppy/void,
+ /obj/structure/closet/crate/necropolis,
+ /obj/item/card/emagfake,
+ /obj/item/flashlight/flashdark,
+ /mob/living/simple_animal/hostile/cat_butcherer
+ )
+
+ var/static/list/rare_items = list(
+ /obj/effect/spawner/lootdrop/armory_contraband,
+ /obj/effect/spawner/lootdrop/teratoma/major
+ )
+
+
+/obj/effect/warped_rune/goldspace/do_effect(mob/user)
+ var/price = 0
+ var/list/valuable_items = list()
+ for(var/obj/item/I in rune_turf)
+ var/datum/export_report/ex = export_item_and_contents(I, dry_run=TRUE)
+ for(var/x in ex.total_amount)
+ if(ex.total_value[x])
+ price += ex.total_value[x]
+ valuable_items |= I
+
+ if(price >= target_value)
+ remove_on_activation = TRUE
+ var/path
+ switch(rand(1,100))
+ if(1 to 80)
+ path = pick(common_items)
+ if(80 to 99)
+ path = pick(uncommon_items)
+ else
+ path = pick(rare_items)
+
+ var/atom/movable/A = new path(rune_turf)
+ QDEL_LIST(valuable_items)
+ to_chat(user, "[src] shines and [A] appears before you.")
+ else
+ to_chat(user, "The sacrifice is insufficient.")
+ . = ..()
+
+//oil
+/obj/item/slimecross/warping/oil
+ colour = "oil"
+ runepath = /obj/effect/warped_rune/oilspace
+ effect_desc = "Draw a rune that can explode whoever steps on it."
+
+/obj/effect/warped_rune/oilspace
+ icon_state = "rune_oil"
+ desc = "This is basically a mine."
+ remove_on_activation = FALSE
+
+/obj/effect/warped_rune/oilspace/Crossed(atom/movable/AM, oldloc)
+ if(iscarbon(AM))
+ var/mob/living/carbon/C = AM
+ var/amt = rand(4,12)
+ C.reagents.add_reagent(/datum/reagent/water, amt)
+ C.reagents.add_reagent(/datum/reagent/potassium, amt)
+ activated_on_step = TRUE
+ . = ..()
+
+/obj/item/slimecross/warping/black
+ colour = "black"
+ runepath = /obj/effect/warped_rune/blackspace
+ effect_desc = "Draw a rune that can transmute a corpse into a shade."
+
+/obj/effect/warped_rune/blackspace
+ icon_state = "rune_black"
+ desc = "Souls are like any other material, you just have to find the right place to manufacture them."
+
+/obj/effect/warped_rune/blackspace/do_effect(mob/user)
+ for(var/mob/living/carbon/human/host in rune_turf)
+ if(host.key) //checks if the ghost and brain's there
+ to_chat(user, "This body can't be transmuted by the rune in this state!")
+ return
+
+ to_chat(user, "The rune is trying to repair [host.name]'s soul!")
+ var/list/candidates = pollCandidatesForMob("Do you want to replace the soul of [host.name]?", ROLE_SENTIENCE, null, ROLE_SENTIENCE, 50, host, POLL_IGNORE_SHADE)//todo: fix desc
+
+ if(length(candidates) && !host.key) //check if anyone wanted to play as the dead person and check if no one's in control of the body one last time.
+ var/mob/dead/observer/ghost = pick(candidates)
+
+ host.mind.memory = "" //resets the memory since it's a new soul inside.
+ host.key = ghost.key
+ var/mob/living/simple_animal/shade/S = host.change_mob_type(/mob/living/simple_animal/shade , rune_turf, "Shade", FALSE)
+ S.maxHealth = 1
+ S.health = 1
+ S.faction = host.faction
+ S.copy_languages(host, LANGUAGE_MIND)
+ playsound(host, "sound/magic/castsummon.ogg", 50, TRUE)
+ qdel(host)
+ activated_on_step = TRUE
+ return ..()
+
+ to_chat(user, "The rune failed! Maybe you should try again later.")
+
+
+/obj/item/slimecross/warping/lightpink
+ colour = "light pink"
+ runepath = /obj/effect/warped_rune/lightpinkspace
+ effect_desc = "Draw a frog that makes whoever steps on it peaceful."
+
+/obj/effect/warped_rune/lightpinkspace
+ desc = "Peace and love."
+ icon_state = "rune_light_pink"
+ remove_on_activation = FALSE
+
+/obj/effect/warped_rune/lightpinkspace/Crossed(atom/movable/AM, oldloc)
+ if(iscarbon(AM))
+ var/mob/living/carbon/C = AM
+ C.reagents.add_reagent(/datum/reagent/pax, 10)
+ activated_on_step = TRUE
+ . = ..()
+
+/obj/item/slimecross/warping/adamantine
+ colour = "adamantine"
+ runepath = /obj/effect/warped_rune/adamantinespace
+ effect_desc = "Draw a rune that can summon reflective fields."
+
+/obj/effect/warped_rune/adamantinespace
+ desc = "This can be activated to summon reflective fields."
+ icon_state = "rune_adamantine"
+
+/obj/structure/reflector/box/anchored/mob_pass/CanPass(atom/movable/mover, turf/target)
+ if(isliving(mover))
+ return TRUE
+ return ..()
+
+/obj/effect/warped_rune/adamantinespace/do_effect(mob/user)
+ for(var/turf/open/T in RANGE_TURFS(1, src) - rune_turf)
+ var/obj/structure/reflector/box/anchored/mob_pass/D = new (T)
+ D.setAngle(dir2angle(get_dir(src, D)))
+ D.admin = TRUE
+ QDEL_IN(D, 300)
+ activated_on_step = TRUE
+ . = ..()
+
+
+///the template of the warped_room map
+GLOBAL_DATUM(warped_room, /datum/map_template/warped_room)
+
+/* Used to teleport anything over it to a unique room similar to hilbert's hotel.*/
+
+/obj/item/slimecross/warping/rainbow
+ colour = "rainbow"
+ effect_desc = "Draws a rune that can be activated to teleport whoever is standing on it."
+ runepath = /obj/effect/warped_rune/rainbowspace
+
+/obj/effect/warped_rune/rainbowspace
+ icon_state = "rune_rainbow"
+ desc = "This is where I go when I want to be alone. Yet they keep clawing at the walls until everything crumbles."
+ remove_on_activation = FALSE
+
+/obj/effect/warped_room_exit
+ name = "warped_rune"
+ icon = 'icons/obj/slimecrossing.dmi'
+ icon_state = "rune_rainbow"
+ desc = "Use this rune if you want to leave this place. You will have to leave eventually."
+ move_resist = INFINITY
+ anchored = TRUE
+ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+
+/datum/map_template/warped_room
+ name = "Warped room"
+ mappath = '_maps/templates/warped_room.dmm'
+ var/obj/effect/warped_room_exit/exit_rune
+ var/list/rainbow_runes = list()
+
+/area/warped_room
+ name = "warped room"
+ icon_state = "yellow"
+ dynamic_lighting = DYNAMIC_LIGHTING_FORCED
+ requires_power = FALSE
+ has_gravity = TRUE
+ teleport_restriction = TELEPORT_ALLOW_NONE
+
+/area/warped_room/get_virtual_z(turf/T)
+ return WARPED_ROOM_VIRTUAL_Z
+
+///creates the warped room and place an exit rune to exit the room
+/obj/effect/warped_rune/rainbowspace/Initialize()
+ . = ..()
+ if(!GLOB.warped_room)
+ GLOB.warped_room = new
+ ///current x,y,z location of the reserved space for the rune room
+ var/datum/turf_reservation/room_reservation = SSmapping.RequestBlockReservation(GLOB.warped_room.width, GLOB.warped_room.height) //monkey sees valid location
+ GLOB.warped_room.load(locate(room_reservation.bottom_left_coords[1], room_reservation.bottom_left_coords[2], room_reservation.bottom_left_coords[3]))//monkey room activate
+ GLOB.warped_room.exit_rune = new (locate(room_reservation.bottom_left_coords[1] + 3, room_reservation.bottom_left_coords[2] + 6, room_reservation.bottom_left_coords[3]))
+ GLOB.warped_room.rainbow_runes += src
+
+/obj/effect/warped_rune/rainbowspace/do_effect(mob/user)
+ var/tp_mob = FALSE
+ for(var/mob/living/carbon/human/customer in rune_turf)
+ tp_mob = TRUE
+ customer.forceMove(get_turf(GLOB.warped_room.exit_rune))
+ if(tp_mob)
+ playsound(rune_turf, dir_sound, 20, TRUE)
+ . = ..()
+
+///Will delete the room when the rune is destroyed if no customer is left in the room.
+/obj/effect/warped_rune/rainbowspace/Destroy()
+ GLOB.warped_room?.rainbow_runes -= src
+ return ..()
+
+///anyone on the exit rune when it is used will be teleported to the rune that was used to teleport to the warped room
+/obj/effect/warped_room_exit/attack_hand(mob/living/user)
+ . = ..()
+ var/exit_turf
+ var/tp_mob = FALSE
+ for(var/mob/living/carbon/human/customer in get_turf(src))
+ do_sparks(3, FALSE, get_turf(src))
+ if(!exit_turf)
+ if(GLOB.warped_room?.rainbow_runes.len)
+ var/obj/effect/warped_rune/WR = pick(GLOB.warped_room.rainbow_runes)
+ exit_turf = WR.rune_turf
+ else
+ exit_turf = find_safe_turf()
+ customer.forceMove(exit_turf)
+ tp_mob = TRUE
+ if(tp_mob)
+ playsound(get_turf(src), 'sound/effects/phasein.ogg', 20, TRUE)
diff --git a/code/modules/research/xenobiology/xenobio_camera.dm b/code/modules/research/xenobiology/xenobio_camera.dm
index 80bb1b39e0b89..5aa4f3281c0f8 100644
--- a/code/modules/research/xenobiology/xenobio_camera.dm
+++ b/code/modules/research/xenobiology/xenobio_camera.dm
@@ -1,18 +1,18 @@
//Xenobio control console
-/mob/camera/aiEye/remote/xenobio
+/mob/camera/ai_eye/remote/xenobio
visible_icon = TRUE
icon = 'icons/mob/cameramob.dmi'
icon_state = "generic_camera"
var/allowed_area = null
-/mob/camera/aiEye/remote/xenobio/Initialize()
+/mob/camera/ai_eye/remote/xenobio/Initialize()
var/area/A = get_area(loc)
allowed_area = A.name
. = ..()
-/mob/camera/aiEye/remote/xenobio/setLoc(var/t)
+/mob/camera/ai_eye/remote/xenobio/setLoc(var/t)
var/area/new_area = get_area(t)
- if(new_area && new_area.name == allowed_area || new_area && new_area.xenobiology_compatible)
+ if(new_area && new_area.name == allowed_area || new_area && (new_area.area_flags & XENOBIOLOGY_COMPATIBLE))
return ..()
else
return
@@ -67,7 +67,7 @@
return ..()
/obj/machinery/computer/camera_advanced/xenobio/CreateEye()
- eyeobj = new /mob/camera/aiEye/remote/xenobio(get_turf(src))
+ eyeobj = new /mob/camera/ai_eye/remote/xenobio(get_turf(src))
eyeobj.origin = src
eyeobj.visible_icon = TRUE
eyeobj.icon = 'icons/mob/cameramob.dmi'
@@ -105,20 +105,20 @@
potion_action.target = src
potion_action.Grant(user)
actions += potion_action
-
+
if(hotkey_help)
hotkey_help.target = src
hotkey_help.Grant(user)
actions += hotkey_help
- RegisterSignal(user, COMSIG_XENO_SLIME_CLICK_CTRL, .proc/XenoSlimeClickCtrl)
- RegisterSignal(user, COMSIG_XENO_SLIME_CLICK_ALT, .proc/XenoSlimeClickAlt)
- RegisterSignal(user, COMSIG_XENO_SLIME_CLICK_SHIFT, .proc/XenoSlimeClickShift)
- RegisterSignal(user, COMSIG_XENO_TURF_CLICK_SHIFT, .proc/XenoTurfClickShift)
- RegisterSignal(user, COMSIG_XENO_TURF_CLICK_CTRL, .proc/XenoTurfClickCtrl)
+ RegisterSignal(user, COMSIG_XENO_SLIME_CLICK_CTRL, .proc/XenoSlimeClickCtrl)
+ RegisterSignal(user, COMSIG_XENO_SLIME_CLICK_ALT, .proc/XenoSlimeClickAlt)
+ RegisterSignal(user, COMSIG_XENO_SLIME_CLICK_SHIFT, .proc/XenoSlimeClickShift)
+ RegisterSignal(user, COMSIG_XENO_TURF_CLICK_SHIFT, .proc/XenoTurfClickShift)
+ RegisterSignal(user, COMSIG_XENO_TURF_CLICK_CTRL, .proc/XenoTurfClickCtrl)
RegisterSignal(user, COMSIG_XENO_MONKEY_CLICK_CTRL, .proc/XenoMonkeyClickCtrl)
- //Checks for recycler on every interact, prevents issues with load order on certain maps.
+ //Checks for recycler on every interact, prevents issues with load order on certain maps.
if(!connected_recycler)
for(var/obj/machinery/monkey_recycler/recycler in GLOB.monkey_recyclers)
if(get_area(recycler.loc) == get_area(loc))
@@ -126,15 +126,17 @@
connected_recycler.connected += src
/obj/machinery/computer/camera_advanced/xenobio/remove_eye_control(mob/living/user)
- UnregisterSignal(user, COMSIG_XENO_SLIME_CLICK_CTRL)
- UnregisterSignal(user, COMSIG_XENO_SLIME_CLICK_ALT)
- UnregisterSignal(user, COMSIG_XENO_SLIME_CLICK_SHIFT)
- UnregisterSignal(user, COMSIG_XENO_TURF_CLICK_SHIFT)
- UnregisterSignal(user, COMSIG_XENO_TURF_CLICK_CTRL)
- UnregisterSignal(user, COMSIG_XENO_MONKEY_CLICK_CTRL)
- ..()
+ UnregisterSignal(user, COMSIG_XENO_SLIME_CLICK_CTRL)
+ UnregisterSignal(user, COMSIG_XENO_SLIME_CLICK_ALT)
+ UnregisterSignal(user, COMSIG_XENO_SLIME_CLICK_SHIFT)
+ UnregisterSignal(user, COMSIG_XENO_TURF_CLICK_SHIFT)
+ UnregisterSignal(user, COMSIG_XENO_TURF_CLICK_CTRL)
+ UnregisterSignal(user, COMSIG_XENO_MONKEY_CLICK_CTRL)
+ ..()
/obj/machinery/computer/camera_advanced/xenobio/proc/on_contents_del(datum/source, atom/deleted)
+ SIGNAL_HANDLER
+
if(current_potion == deleted)
current_potion = null
if(deleted in stored_slimes)
@@ -185,7 +187,7 @@
if(!target || !isliving(owner))
return
var/mob/living/C = owner
- var/mob/camera/aiEye/remote/xenobio/remote_eye = C.remote_control
+ var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = target
if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
@@ -205,7 +207,7 @@
if(!target || !isliving(owner))
return
var/mob/living/C = owner
- var/mob/camera/aiEye/remote/xenobio/remote_eye = C.remote_control
+ var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = target
if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
@@ -231,20 +233,20 @@
if(!target || !isliving(owner))
return
var/mob/living/C = owner
- var/mob/camera/aiEye/remote/xenobio/remote_eye = C.remote_control
+ var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = target
if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
if(X.monkeys >= 1)
var/mob/living/carbon/monkey/food = new /mob/living/carbon/monkey(remote_eye.loc, TRUE, owner)
if (!QDELETED(food))
- food.LAssailant = C
+ food.LAssailant = WEAKREF(C)
X.monkeys--
X.monkeys = round(X.monkeys, 0.1) //Prevents rounding errors
to_chat(owner, "[X] now has [X.monkeys] monkeys stored.")
else
to_chat(owner, "[X] needs to have at least 1 monkey stored. Currently has [X.monkeys] monkeys stored.")
- else
+ else
to_chat(owner, "Target is not near a camera. Cannot proceed.")
@@ -257,7 +259,7 @@
if(!target || !isliving(owner))
return
var/mob/living/C = owner
- var/mob/camera/aiEye/remote/xenobio/remote_eye = C.remote_control
+ var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = target
var/obj/machinery/monkey_recycler/recycler = X.connected_recycler
@@ -270,7 +272,7 @@
M.visible_message("[M] vanishes as [M.p_theyre()] reclaimed for recycling!")
recycler.use_power(500)
X.monkeys += recycler.cube_production
- X.monkeys = round(X.monkeys, 0.1) //Prevents rounding errors
+ X.monkeys = round(X.monkeys, 0.1) //Prevents rounding errors
qdel(M)
to_chat(owner, "[X] now has [X.monkeys] monkeys available.")
else
@@ -285,7 +287,7 @@
if(!target || !isliving(owner))
return
var/mob/living/C = owner
- var/mob/camera/aiEye/remote/xenobio/remote_eye = C.remote_control
+ var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control
if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
for(var/mob/living/simple_animal/slime/S in remote_eye.loc)
@@ -303,7 +305,7 @@
return
var/mob/living/C = owner
- var/mob/camera/aiEye/remote/xenobio/remote_eye = C.remote_control
+ var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = target
if(QDELETED(X.current_potion))
@@ -366,40 +368,46 @@
// Scans slime
/obj/machinery/computer/camera_advanced/xenobio/proc/XenoSlimeClickCtrl(mob/living/user, mob/living/simple_animal/slime/S)
+ SIGNAL_HANDLER
+
if(!GLOB.cameranet.checkTurfVis(S.loc))
to_chat(user, "Target is not near a camera. Cannot proceed.")
return
var/mob/living/C = user
- var/mob/camera/aiEye/remote/xenobio/E = C.remote_control
+ var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control
var/area/mobarea = get_area(S.loc)
- if(mobarea.name == E.allowed_area || mobarea.xenobiology_compatible)
+ if(mobarea.name == E.allowed_area || (mobarea & XENOBIOLOGY_COMPATIBLE))
slime_scan(S, C)
//Feeds a potion to slime
/obj/machinery/computer/camera_advanced/xenobio/proc/XenoSlimeClickAlt(mob/living/user, mob/living/simple_animal/slime/S)
+ SIGNAL_HANDLER
+
if(!GLOB.cameranet.checkTurfVis(S.loc))
to_chat(user, "Target is not near a camera. Cannot proceed.")
return
var/mob/living/C = user
- var/mob/camera/aiEye/remote/xenobio/E = C.remote_control
+ var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
var/area/mobarea = get_area(S.loc)
if(QDELETED(X.current_potion))
to_chat(C, "No potion loaded.")
return
- if(mobarea.name == E.allowed_area || mobarea.xenobiology_compatible)
+ if(mobarea.name == E.allowed_area ||(mobarea & XENOBIOLOGY_COMPATIBLE))
X.current_potion.attack(S, C)
//Picks up slime
/obj/machinery/computer/camera_advanced/xenobio/proc/XenoSlimeClickShift(mob/living/user, mob/living/simple_animal/slime/S)
+ SIGNAL_HANDLER
+
if(!GLOB.cameranet.checkTurfVis(S.loc))
to_chat(user, "Target is not near a camera. Cannot proceed.")
return
var/mob/living/C = user
- var/mob/camera/aiEye/remote/xenobio/E = C.remote_control
+ var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
var/area/mobarea = get_area(S.loc)
- if(mobarea.name == E.allowed_area || mobarea.xenobiology_compatible)
+ if(mobarea.name == E.allowed_area || (mobarea & XENOBIOLOGY_COMPATIBLE))
if(X.stored_slimes.len >= X.max_slimes)
to_chat(C, "Slime storage is full.")
return
@@ -414,14 +422,16 @@
//Place slimes
/obj/machinery/computer/camera_advanced/xenobio/proc/XenoTurfClickShift(mob/living/user, turf/open/T)
+ SIGNAL_HANDLER
+
if(!GLOB.cameranet.checkTurfVis(T))
to_chat(user, "Target is not near a camera. Cannot proceed.")
return
var/mob/living/C = user
- var/mob/camera/aiEye/remote/xenobio/E = C.remote_control
+ var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
var/area/turfarea = get_area(T)
- if(turfarea.name == E.allowed_area || turfarea.xenobiology_compatible)
+ if(turfarea.name == E.allowed_area || (turfarea & XENOBIOLOGY_COMPATIBLE))
for(var/mob/living/simple_animal/slime/S in X.stored_slimes)
S.forceMove(T)
S.visible_message("[S] warps in!")
@@ -429,18 +439,20 @@
//Place monkey
/obj/machinery/computer/camera_advanced/xenobio/proc/XenoTurfClickCtrl(mob/living/user, turf/open/T)
+ SIGNAL_HANDLER
+
if(!GLOB.cameranet.checkTurfVis(T))
to_chat(user, "Target is not near a camera. Cannot proceed.")
return
var/mob/living/C = user
- var/mob/camera/aiEye/remote/xenobio/E = C.remote_control
+ var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
var/area/turfarea = get_area(T)
- if(turfarea.name == E.allowed_area || turfarea.xenobiology_compatible)
+ if(turfarea.name == E.allowed_area || (turfarea & XENOBIOLOGY_COMPATIBLE))
if(X.monkeys >= 1)
var/mob/living/carbon/monkey/food = new /mob/living/carbon/monkey(T, TRUE, C)
if (!QDELETED(food))
- food.LAssailant = C
+ food.LAssailant = WEAKREF(C)
X.monkeys--
X.monkeys = round(X.monkeys, 0.1) //Prevents rounding errors
to_chat(C, "[X] now has [X.monkeys] monkeys stored.")
@@ -449,22 +461,24 @@
//Pick up monkey
/obj/machinery/computer/camera_advanced/xenobio/proc/XenoMonkeyClickCtrl(mob/living/user, mob/living/carbon/monkey/M)
+ SIGNAL_HANDLER
+
if(!GLOB.cameranet.checkTurfVis(M.loc))
to_chat(user, "Target is not near a camera. Cannot proceed.")
return
var/mob/living/C = user
- var/mob/camera/aiEye/remote/xenobio/E = C.remote_control
+ var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
var/area/mobarea = get_area(M.loc)
if(!X.connected_recycler)
to_chat(C, "There is no connected monkey recycler. Use a multitool to link one.")
return
- if(mobarea.name == E.allowed_area || mobarea.xenobiology_compatible)
+ if(mobarea.name == E.allowed_area || (mobarea & XENOBIOLOGY_COMPATIBLE))
if(!M.stat)
return
M.visible_message("[M] vanishes as [p_theyre()] reclaimed for recycling!")
X.connected_recycler.use_power(500)
X.monkeys += connected_recycler.cube_production
- X.monkeys = round(X.monkeys, 0.1) //Prevents rounding errors
+ X.monkeys = round(X.monkeys, 0.1) //Prevents rounding errors
qdel(M)
to_chat(C, "[X] now has [X.monkeys] monkeys available.")
diff --git a/code/modules/research/xenobiology/xenobiology.dm b/code/modules/research/xenobiology/xenobiology.dm
index d65a39ee185f8..0daef5960ef85 100644
--- a/code/modules/research/xenobiology/xenobiology.dm
+++ b/code/modules/research/xenobiology/xenobiology.dm
@@ -12,15 +12,19 @@
throw_range = 6
grind_results = list()
var/Uses = 1 // uses before it goes inert
- var/qdel_timer = null // deletion timer, for delayed reactions
+ var/qdel_timer // deletion timer, for delayed reactions
var/effectmod
var/list/activate_reagents = list() //Reagents required for activation
var/recurring = FALSE
+ var/color_slime ///the color of the extract and the slime it came from
+ var/sparkly = FALSE //if true, cargo gets 2x the money for them
/obj/item/slime_extract/examine(mob/user)
. = ..()
if(Uses > 1)
. += "It has [Uses] uses remaining."
+ if(sparkly)
+ . += "It looks sparkly."
/obj/item/slime_extract/attackby(obj/item/O, mob/user)
if(istype(O, /obj/item/slimepotion/enhancer))
@@ -78,6 +82,7 @@
name = "grey slime extract"
icon_state = "grey slime extract"
effectmod = "reproductive"
+ color_slime = "grey"
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,/datum/reagent/water)
/obj/item/slime_extract/grey/activate(mob/living/carbon/human/user, datum/species/species, activation_type)
@@ -103,6 +108,7 @@
name = "gold slime extract"
icon_state = "gold slime extract"
effectmod = "symbiont"
+ color_slime = "gold"
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,/datum/reagent/water)
/obj/item/slime_extract/gold/activate(mob/living/carbon/human/user, datum/species/species, activation_type)
@@ -132,6 +138,7 @@
name = "silver slime extract"
icon_state = "silver slime extract"
effectmod = "consuming"
+ color_slime = "silver"
activate_reagents = list(/datum/reagent/toxin/plasma,/datum/reagent/water)
/obj/item/slime_extract/silver/activate(mob/living/carbon/human/user, datum/species/species, activation_type)
@@ -157,6 +164,7 @@
name = "metal slime extract"
icon_state = "metal slime extract"
effectmod = "industrial"
+ color_slime = "metal"
activate_reagents = list(/datum/reagent/toxin/plasma,/datum/reagent/water)
/obj/item/slime_extract/metal/activate(mob/living/carbon/human/user, datum/species/species, activation_type)
@@ -181,6 +189,7 @@
name = "purple slime extract"
icon_state = "purple slime extract"
effectmod = "regenerative"
+ color_slime = "purple"
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma)
/obj/item/slime_extract/purple/activate(mob/living/carbon/human/user, datum/species/species, activation_type)
@@ -200,6 +209,7 @@
name = "dark purple slime extract"
icon_state = "dark purple slime extract"
effectmod = "self-sustaining"
+ color_slime = "darkpurple"
activate_reagents = list(/datum/reagent/toxin/plasma)
/obj/item/slime_extract/darkpurple/activate(mob/living/carbon/human/user, datum/species/species, activation_type)
@@ -223,6 +233,7 @@
name = "orange slime extract"
icon_state = "orange slime extract"
effectmod = "burning"
+ color_slime = "orange"
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,/datum/reagent/water)
/obj/item/slime_extract/orange/activate(mob/living/carbon/human/user, datum/species/species, activation_type)
@@ -243,6 +254,7 @@
name = "yellow slime extract"
icon_state = "yellow slime extract"
effectmod = "charged"
+ color_slime = "yellow"
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,/datum/reagent/water)
/obj/item/slime_extract/yellow/activate(mob/living/carbon/human/user, datum/species/species, activation_type)
@@ -274,6 +286,7 @@
name = "red slime extract"
icon_state = "red slime extract"
effectmod = "sanguine"
+ color_slime = "red"
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,/datum/reagent/water)
/obj/item/slime_extract/red/activate(mob/living/carbon/human/user, datum/species/species, activation_type)
@@ -285,7 +298,7 @@
if(SLIME_ACTIVATE_MAJOR)
user.visible_message("[user]'s skin flashes red for a moment...", "Your skin flashes red as you emit rage-inducing pheromones...")
- for(var/mob/living/simple_animal/slime/slime in viewers(get_turf(user), null))
+ for(var/mob/living/simple_animal/slime/slime in viewers(get_turf(user)))
slime.rabid = TRUE
slime.visible_message("The [slime] is driven into a frenzy!")
return 600
@@ -294,6 +307,7 @@
name = "blue slime extract"
icon_state = "blue slime extract"
effectmod = "stabilized"
+ color_slime = "blue"
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,/datum/reagent/water)
/obj/item/slime_extract/blue/activate(mob/living/carbon/human/user, datum/species/species, activation_type)
@@ -318,6 +332,7 @@
name = "dark blue slime extract"
icon_state = "dark blue slime extract"
effectmod = "chilling"
+ color_slime = "darkblue"
activate_reagents = list(/datum/reagent/toxin/plasma,/datum/reagent/water)
/obj/item/slime_extract/darkblue/activate(mob/living/carbon/human/user, datum/species/species, activation_type)
@@ -341,6 +356,7 @@
name = "pink slime extract"
icon_state = "pink slime extract"
effectmod = "gentle"
+ color_slime = "pink"
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma)
/obj/item/slime_extract/pink/activate(mob/living/carbon/human/user, datum/species/species, activation_type)
@@ -360,15 +376,15 @@
if(SLIME_ACTIVATE_MAJOR)
user.visible_message("[user]'s skin starts flashing hypnotically...", "Your skin starts forming odd patterns, pacifying creatures around you.")
- for(var/mob/living/carbon/C in viewers(user, null))
- if(C != user)
- C.reagents.add_reagent(/datum/reagent/pax,2)
+ for(var/mob/living/carbon/C in oviewers(user))
+ C.reagents.add_reagent(/datum/reagent/pax,2)
return 600
/obj/item/slime_extract/green
name = "green slime extract"
icon_state = "green slime extract"
effectmod = "mutative"
+ color_slime = "green"
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,/datum/reagent/uranium/radium)
/obj/item/slime_extract/green/activate(mob/living/carbon/human/user, datum/species/species, activation_type)
@@ -393,6 +409,7 @@
name = "light pink slime extract"
icon_state = "light pink slime extract"
effectmod = "loyal"
+ color_slime = "lightpink"
activate_reagents = list(/datum/reagent/toxin/plasma)
/obj/item/slime_extract/lightpink/activate(mob/living/carbon/human/user, datum/species/species, activation_type)
@@ -417,6 +434,7 @@
name = "black slime extract"
icon_state = "black slime extract"
effectmod = "transformative"
+ color_slime = "black"
activate_reagents = list(/datum/reagent/toxin/plasma)
/obj/item/slime_extract/black/activate(mob/living/carbon/human/user, datum/species/species, activation_type)
@@ -438,6 +456,7 @@
name = "oil slime extract"
icon_state = "oil slime extract"
effectmod = "detonating"
+ color_slime = "oil"
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma)
/obj/item/slime_extract/oil/activate(mob/living/carbon/human/user, datum/species/species, activation_type)
@@ -461,6 +480,7 @@
name = "adamantine slime extract"
icon_state = "adamantine slime extract"
effectmod = "crystalline"
+ color_slime = "adamantine"
activate_reagents = list(/datum/reagent/toxin/plasma)
/obj/item/slime_extract/adamantine/activate(mob/living/carbon/human/user, datum/species/species, activation_type)
@@ -490,6 +510,7 @@
name = "bluespace slime extract"
icon_state = "bluespace slime extract"
effectmod = "warping"
+ color_slime = "bluespace"
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma)
var/teleport_ready = FALSE
var/teleport_x = 0
@@ -526,6 +547,7 @@
name = "pyrite slime extract"
icon_state = "pyrite slime extract"
effectmod = "prismatic"
+ color_slime = "pyrite"
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma)
/obj/item/slime_extract/pyrite/activate(mob/living/carbon/human/user, datum/species/species, activation_type)
@@ -552,6 +574,7 @@
name = "cerulean slime extract"
icon_state = "cerulean slime extract"
effectmod = "recurring"
+ color_slime = "cerulean"
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma)
/obj/item/slime_extract/cerulean/activate(mob/living/carbon/human/user, datum/species/species, activation_type)
@@ -572,6 +595,7 @@
name = "sepia slime extract"
icon_state = "sepia slime extract"
effectmod = "lengthened"
+ color_slime = "sepia"
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,/datum/reagent/water)
/obj/item/slime_extract/sepia/activate(mob/living/carbon/human/user, datum/species/species, activation_type)
@@ -594,6 +618,7 @@
name = "rainbow slime extract"
icon_state = "rainbow slime extract"
effectmod = "hyperchromatic"
+ color_slime = "rainbow"
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,"lesser plasma",/datum/reagent/toxin/slimejelly,"holy water and uranium") //Curse this snowflake reagent list.
/obj/item/slime_extract/rainbow/activate(mob/living/carbon/human/user, datum/species/species, activation_type)
@@ -686,7 +711,7 @@
to_chat(user, "You offer [src] to [SM]...")
being_used = TRUE
- var/list/candidates = pollCandidatesForMob("Do you want to play as [SM.name]?", ROLE_SENTIENCE, null, ROLE_SENTIENCE, 50, SM, POLL_IGNORE_SENTIENCE_POTION) // see poll_ignore.dm
+ var/list/candidates = pollCandidatesForMob("Do you want to play as [SM.name]? (Sentience Potion)", ROLE_SENTIENCE, null, ROLE_SENTIENCE, 50, SM, POLL_IGNORE_SENTIENCE_POTION) // see poll_ignore.dm
if(LAZYLEN(candidates))
var/mob/dead/observer/C = pick(candidates)
SM.key = C.key
@@ -854,10 +879,10 @@
return
if(isitem(C))
var/obj/item/I = C
- if(I.slowdown <= 0 || I.obj_flags & IMMUTABLE_SLOW)
+ if(I.slowdown != initial(I.slowdown) || I.obj_flags & IMMUTABLE_SLOW)
to_chat(user, "The [C] can't be made any faster!")
return ..()
- I.slowdown = 0
+ I.slowdown = initial(I.slowdown) * 0.5
if(istype(C, /obj/vehicle))
var/obj/vehicle/V = C
@@ -921,6 +946,14 @@
to_chat(user, "The potion can only be used on gendered things!")
return
+ L.visible_message("[user] starts to feed [L] a gender change potion!",
+ "[user] starts to feed you a gender change potion!")
+
+ if(!do_after(user, 50, target = L))
+ return
+
+ to_chat(user, "You feed [L] the gender change potion!")
+
if(L.gender == MALE)
L.gender = FEMALE
L.visible_message("[L] suddenly looks more feminine!", "You suddenly feel more feminine!")
@@ -947,16 +980,17 @@
being_used = TRUE
- to_chat(user, "You offer [src] to [user]...")
+ to_chat(user, "You offer [src] to [M]...")
var/new_name = stripped_input(M, "What would you like your name to be?", "Input a name", M.real_name, MAX_NAME_LEN)
- if(!new_name || QDELETED(src) || QDELETED(M) || new_name == M.real_name || !M.Adjacent(user))
+ if(!new_name || QDELETED(src) || QDELETED(M) || new_name == M.real_name || !M.Adjacent(user) || CHAT_FILTER_CHECK(new_name))
being_used = FALSE
return
M.visible_message("[M] has a new name, [new_name].", "Your old name of [M.real_name] fades away, and your new name [new_name] anchors itself in your mind.")
message_admins("[ADMIN_LOOKUPFLW(user)] used [src] on [ADMIN_LOOKUPFLW(M)], letting them rename themselves into [new_name].")
+ log_game("[key_name(user)] used [src] on [key_name(M)], letting them rename themselves into [new_name].")
// pass null as first arg to not update records or ID/PDA
M.fully_replace_character_name(null, new_name)
@@ -1031,5 +1065,5 @@
for(var/turf/T in A)
T.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
T.add_atom_colour("#2956B2", FIXED_COLOUR_PRIORITY)
- A.xenobiology_compatible = TRUE
+ A.area_flags |= XENOBIOLOGY_COMPATIBLE
qdel(src)
diff --git a/code/modules/ruins/lavalandruin_code/puzzle.dm b/code/modules/ruins/lavalandruin_code/puzzle.dm
index 70b0545ded062..2a1f3b2886281 100644
--- a/code/modules/ruins/lavalandruin_code/puzzle.dm
+++ b/code/modules/ruins/lavalandruin_code/puzzle.dm
@@ -34,7 +34,7 @@
return get_step(center,SOUTH)
if(9)
return get_step(center,SOUTHEAST)
-
+
/obj/effect/sliding_puzzle/Initialize(mapload)
..()
return INITIALIZE_HINT_LATELOAD
@@ -56,7 +56,7 @@
/obj/effect/sliding_puzzle/proc/validate()
if(finished)
return
-
+
if(elements.len < 8) //Someone broke it
qdel(src)
@@ -86,7 +86,7 @@
shake_camera(M, COLLAPSE_DURATION , 1)
for(var/obj/structure/puzzle_element/E in elements)
E.collapse()
-
+
dispense_reward()
/obj/effect/sliding_puzzle/proc/dispense_reward()
@@ -103,7 +103,7 @@
for(var/j in i to current_ordering.len)
if(current_ordering[j] < checked_value)
swap_tally++
-
+
return swap_tally % 2 == 0
//swap two tiles in same row
@@ -113,13 +113,13 @@
if(empty_tile_id == 1 || empty_tile_id == 2) //Can't swap with empty one so just grab some in second row
first_tile_id = 4
other_tile_id = 5
-
+
var/turf/T1 = get_turf_for_id(first_tile_id)
var/turf/T2 = get_turf_for_id(other_tile_id)
-
+
var/obj/structure/puzzle_element/E1 = locate() in T1
var/obj/structure/puzzle_element/E2 = locate() in T2
-
+
E1.forceMove(T2)
E2.forceMove(T1)
@@ -294,7 +294,7 @@
//Some armor so it's harder to kill someone by mistake.
/obj/structure/puzzle_element/prison
- armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 50, "bio" = 50, "rad" = 50, "fire" = 50, "acid" = 50)
+ armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 50, "bio" = 50, "rad" = 50, "fire" = 50, "acid" = 50, "stamina" = 0)
/obj/structure/puzzle_element/prison/relaymove(mob/user)
return
@@ -342,7 +342,7 @@
for(var/atom/movable/AM in things_to_throw)
var/throwtarget = get_edge_target_turf(T, get_dir(T, get_step_away(AM, T)))
AM.throw_at(throwtarget, 2, 3)
-
+
//Create puzzle itself
cube.prisoner = prisoner
cube.setup()
@@ -350,4 +350,4 @@
//Move them into random block
var/obj/structure/puzzle_element/E = pick(cube.elements)
prisoner.forceMove(E)
- return TRUE
\ No newline at end of file
+ return TRUE
diff --git a/code/modules/ruins/lavalandruin_code/syndicate_base.dm b/code/modules/ruins/lavalandruin_code/syndicate_base.dm
index 32b47ccb311bd..99782a821337e 100644
--- a/code/modules/ruins/lavalandruin_code/syndicate_base.dm
+++ b/code/modules/ruins/lavalandruin_code/syndicate_base.dm
@@ -23,5 +23,5 @@
/obj/item/paper/fluff/ruins/syndicomms
name = "paper - 'Communication Frequencies'"
- info = "Greeting, Agent. I see you have awaken from your cryogenic slumber. This either means that the new Nanotransen space project is complete and operational in a nearby sector, or that Ashlanders have launched an assault on our base. We recommend you get rid of any pests and do not confuse them for humans.
In case you don't remember how to do your job, all you need is the equipment we provided; your Chameleon Mask, your Agent ID, and the intercom nearby. A nearby shelf contains all the names and jobs of the current Nanotransen employees. Assign their name and job to your ID, and your Mask will mimic the voice of whoever you are impersonating.
Oh, one more thing. Here is a list of frequencies for you to troll on:
145.9 - Common Channel
144.7 - Private AI Channel
135.9 - Security Channel
135.7 - Engineering Channel
135.5 - Medical Channel
135.3 - Command Channel
135.1 - Science Channel
134.9 - Service Channel
134.7 - Supply Channel
"
+ info = "Greetings, Agent. I see you have awaken from your cryogenic slumber. This either means that the new Nanotransen space project is complete and operational in a nearby sector, or that Ashlanders have launched an assault on our base. We recommend you get rid of any pests and do not confuse them for humans.
In case you don't remember how to do your job, all you need is the equipment we provided; your Chameleon Mask, your Agent ID, and the intercom nearby. A nearby shelf contains all the names and jobs of the current Nanotransen employees. Assign their name and job to your ID, and your Mask will mimic the voice of whoever you are impersonating.
Oh, one more thing. Here is a list of frequencies for you to troll on:
145.9 - Common Channel
144.7 - Private AI Channel
135.9 - Security Channel
135.7 - Engineering Channel
135.5 - Medical Channel
135.3 - Command Channel
135.1 - Science Channel
134.9 - Service Channel
134.7 - Supply Channel
136.1 - Exploration Channel
"
\ No newline at end of file
diff --git a/code/modules/ruins/objects_and_mobs/ash_walker_den.dm b/code/modules/ruins/objects_and_mobs/ash_walker_den.dm
index 0945fbb125052..7024ed9177258 100644
--- a/code/modules/ruins/objects_and_mobs/ash_walker_den.dm
+++ b/code/modules/ruins/objects_and_mobs/ash_walker_den.dm
@@ -17,17 +17,26 @@
var/faction = list("ashwalker")
var/meat_counter = 6
var/datum/team/ashwalkers/ashies
+ var/datum/linked_objective
/obj/structure/lavaland/ash_walker/Initialize()
.=..()
ashies = new /datum/team/ashwalkers()
var/datum/objective/protect_object/objective = new
objective.set_target(src)
+ linked_objective = objective
ashies.objectives += objective
for(var/datum/mind/M in ashies.members)
log_objective(M, objective.explanation_text)
START_PROCESSING(SSprocessing, src)
+/obj/structure/lavaland/ash_walker/Destroy()
+ ashies.objectives -= linked_objective
+ ashies = null
+ QDEL_NULL(linked_objective)
+ STOP_PROCESSING(SSprocessing, src)
+ return ..()
+
/obj/structure/lavaland/ash_walker/deconstruct(disassembled)
new /obj/item/assembly/signaler/anomaly (get_step(loc, pick(GLOB.alldirs)))
new /obj/effect/collapse(loc)
@@ -38,7 +47,7 @@
spawn_mob()
/obj/structure/lavaland/ash_walker/proc/consume()
- for(var/mob/living/H in view(src, 1)) //Only for corpse right next to/on same tile
+ for(var/mob/living/H in hearers(1, src)) //Only for corpse right next to/on same tile
if(H.stat)
visible_message("Serrated tendrils eagerly pull [H] to [src], tearing the body apart as its blood seeps over the eggs.")
playsound(get_turf(src),'sound/magic/demon_consume.ogg', 100, 1)
@@ -51,8 +60,8 @@
meat_counter++
H.gib()
obj_integrity = min(obj_integrity + max_integrity*0.05,max_integrity)//restores 5% hp of tendril
- for(var/mob/living/L in view(src, 5))
- if(L.mind.has_antag_datum(/datum/antagonist/ashwalker))
+ for(var/mob/living/L in viewers(5, src))
+ if(L.mind?.has_antag_datum(/datum/antagonist/ashwalker))
SEND_SIGNAL(L, COMSIG_ADD_MOOD_EVENT, "oogabooga", /datum/mood_event/sacrifice_good)
else
SEND_SIGNAL(L, COMSIG_ADD_MOOD_EVENT, "oogabooga", /datum/mood_event/sacrifice_bad)
diff --git a/code/modules/ruins/objects_and_mobs/necropolis_gate.dm b/code/modules/ruins/objects_and_mobs/necropolis_gate.dm
index dfcf854598b24..a9dff813be275 100644
--- a/code/modules/ruins/objects_and_mobs/necropolis_gate.dm
+++ b/code/modules/ruins/objects_and_mobs/necropolis_gate.dm
@@ -182,7 +182,7 @@ GLOBAL_DATUM(necropolis_gate, /obj/structure/necropolis_gate/legion_gate)
var/sound/legion_sound = sound('sound/creatures/legion_spawn.ogg')
for(var/mob/M in GLOB.player_list)
- if(M.z == z)
+ if(M.get_virtual_z_level() == get_virtual_z_level())
to_chat(M, "Discordant whispers flood your mind in a thousand voices. Each one speaks your name, over and over. Something horrible has been released.")
M.playsound_local(T, null, 100, FALSE, 0, FALSE, pressure_affected = FALSE, S = legion_sound)
flash_color(M, flash_color = "#FF0000", flash_time = 50)
diff --git a/code/modules/ruins/objects_and_mobs/sin_ruins.dm b/code/modules/ruins/objects_and_mobs/sin_ruins.dm
index 733a4f0ba4c40..418f6f32a2cb7 100644
--- a/code/modules/ruins/objects_and_mobs/sin_ruins.dm
+++ b/code/modules/ruins/objects_and_mobs/sin_ruins.dm
@@ -115,7 +115,7 @@
"Perfect. Much better! Now nobody will be able to resist yo-")
var/turf/T = get_turf(user)
- var/list/levels = SSmapping.levels_by_trait(ZTRAIT_SPACE_RUINS)
+ var/list/levels = SSmapping.levels_by_trait(ZTRAIT_DYNAMIC_LEVEL)
var/turf/dest
if (levels.len)
dest = locate(T.x, T.y, pick(levels))
diff --git a/code/modules/ruins/spaceruin_code/asteroid10.dm b/code/modules/ruins/spaceruin_code/asteroid10.dm
new file mode 100644
index 0000000000000..67d3a071f6e98
--- /dev/null
+++ b/code/modules/ruins/spaceruin_code/asteroid10.dm
@@ -0,0 +1,23 @@
+/////////// asteroid4 items
+
+/obj/item/paper/fluff/ruins/asteroid10/welcome
+ name = "Welcome to Dog Heaven!"
+ info = "The ambassador of Dog Heaven welcomes you to our humble retreat!"
+
+/obj/item/reagent_containers/food/snacks/nugget/dog
+ name = "dog treat"
+ list_reagents = list(/datum/reagent/consumable/nutriment = 1,/datum/reagent/corgium = 10)
+
+/mob/living/simple_animal/pet/dog/corgi/chef/Initialize()
+ ..()
+ var/obj/item/clothing/head/chefhat/hat = new (src)
+ inventory_head = hat
+ update_corgi_fluff()
+ regenerate_icons()
+
+/mob/living/simple_animal/pet/dog/corgi/seccie/Initialize()
+ ..()
+ var/obj/item/clothing/head/helmet/hat = new (src)
+ inventory_head = hat
+ update_corgi_fluff()
+ regenerate_icons()
\ No newline at end of file
diff --git a/code/modules/ruins/spaceruin_code/caravanambush.dm b/code/modules/ruins/spaceruin_code/caravanambush.dm
index 740850524cfd9..c58cab4d8999b 100644
--- a/code/modules/ruins/spaceruin_code/caravanambush.dm
+++ b/code/modules/ruins/spaceruin_code/caravanambush.dm
@@ -26,54 +26,34 @@
name = "experimental crowbar"
toolspeed = 0.3
-/obj/machinery/computer/shuttle/caravan
+/obj/machinery/computer/shuttle_flight/caravan
/obj/item/circuitboard/computer/caravan
- build_path = /obj/machinery/computer/shuttle/caravan
+ build_path = /obj/machinery/computer/shuttle_flight/caravan
/obj/item/circuitboard/computer/caravan/trade1
- build_path = /obj/machinery/computer/shuttle/caravan/trade1
+ build_path = /obj/machinery/computer/shuttle_flight/caravan/trade1
/obj/item/circuitboard/computer/caravan/pirate
- build_path = /obj/machinery/computer/shuttle/caravan/pirate
+ build_path = /obj/machinery/computer/shuttle_flight/caravan/pirate
/obj/item/circuitboard/computer/caravan/syndicate1
- build_path = /obj/machinery/computer/shuttle/caravan/syndicate1
+ build_path = /obj/machinery/computer/shuttle_flight/caravan/syndicate1
/obj/item/circuitboard/computer/caravan/syndicate2
- build_path = /obj/machinery/computer/shuttle/caravan/syndicate2
+ build_path = /obj/machinery/computer/shuttle_flight/caravan/syndicate2
/obj/item/circuitboard/computer/caravan/syndicate3
- build_path = /obj/machinery/computer/shuttle/caravan/syndicate3
+ build_path = /obj/machinery/computer/shuttle_flight/caravan/syndicate3
-/obj/machinery/computer/shuttle/caravan/trade1
+/obj/machinery/computer/shuttle_flight/caravan/trade1
name = "Small Freighter Shuttle Console"
desc = "Used to control the Small Freighter."
circuit = /obj/item/circuitboard/computer/caravan/trade1
shuttleId = "caravantrade1"
possible_destinations = "whiteship_away;whiteship_home;whiteship_z4;whiteship_lavaland;caravantrade1_custom;caravantrade1_ambush"
-/obj/machinery/computer/camera_advanced/shuttle_docker/caravan/Initialize()
- . = ..()
- GLOB.jam_on_wardec += src
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/caravan/Destroy()
- GLOB.jam_on_wardec -= src
- return ..()
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/caravan/trade1
- name = "Small Freighter Navigation Computer"
- desc = "Used to designate a precise transit location for the Small Freighter."
- shuttleId = "caravantrade1"
- lock_override = NONE
- shuttlePortId = "caravantrade1_custom"
- jumpto_ports = list("whiteship_away" = 1, "whiteship_home" = 1, "whiteship_z4" = 1, "caravantrade1_ambush" = 1)
- view_range = 6.5
- x_offset = -5
- y_offset = -5
- designate_time = 100
-
-/obj/machinery/computer/shuttle/caravan/pirate
+/obj/machinery/computer/shuttle_flight/caravan/pirate
name = "Pirate Cutter Shuttle Console"
desc = "Used to control the Pirate Cutter."
icon_screen = "syndishuttle"
@@ -83,20 +63,7 @@
shuttleId = "caravanpirate"
possible_destinations = "caravanpirate_custom;caravanpirate_ambush"
-/obj/machinery/computer/camera_advanced/shuttle_docker/caravan/pirate
- name = "Pirate Cutter Navigation Computer"
- desc = "Used to designate a precise transit location for the Pirate Cutter."
- icon_screen = "syndishuttle"
- icon_keyboard = "syndie_key"
- shuttleId = "caravanpirate"
- lock_override = NONE
- shuttlePortId = "caravanpirate_custom"
- jumpto_ports = list("caravanpirate_ambush" = 1)
- view_range = 6.5
- x_offset = 3
- y_offset = -6
-
-/obj/machinery/computer/shuttle/caravan/syndicate1
+/obj/machinery/computer/shuttle_flight/caravan/syndicate1
name = "Syndicate Fighter Shuttle Console"
desc = "Used to control the Syndicate Fighter."
icon_screen = "syndishuttle"
@@ -107,20 +74,7 @@
shuttleId = "caravansyndicate1"
possible_destinations = "caravansyndicate1_custom;caravansyndicate1_ambush;caravansyndicate1_listeningpost"
-/obj/machinery/computer/camera_advanced/shuttle_docker/caravan/syndicate1
- name = "Syndicate Fighter Navigation Computer"
- desc = "Used to designate a precise transit location for the Syndicate Fighter."
- icon_screen = "syndishuttle"
- icon_keyboard = "syndie_key"
- shuttleId = "caravansyndicate1"
- lock_override = NONE
- shuttlePortId = "caravansyndicate1_custom"
- jumpto_ports = list("caravansyndicate1_ambush" = 1, "caravansyndicate1_listeningpost" = 1)
- view_range = 0
- x_offset = 2
- y_offset = 0
-
-/obj/machinery/computer/shuttle/caravan/syndicate2
+/obj/machinery/computer/shuttle_flight/caravan/syndicate2
name = "Syndicate Fighter Shuttle Console"
desc = "Used to control the Syndicate Fighter."
icon_screen = "syndishuttle"
@@ -131,20 +85,7 @@
shuttleId = "caravansyndicate2"
possible_destinations = "caravansyndicate2_custom;caravansyndicate2_ambush;caravansyndicate1_listeningpost"
-/obj/machinery/computer/camera_advanced/shuttle_docker/caravan/syndicate2
- name = "Syndicate Fighter Navigation Computer"
- desc = "Used to designate a precise transit location for the Syndicate Fighter."
- icon_screen = "syndishuttle"
- icon_keyboard = "syndie_key"
- shuttleId = "caravansyndicate2"
- lock_override = NONE
- shuttlePortId = "caravansyndicate2_custom"
- jumpto_ports = list("caravansyndicate2_ambush" = 1, "caravansyndicate1_listeningpost" = 1)
- view_range = 0
- x_offset = 0
- y_offset = 2
-
-/obj/machinery/computer/shuttle/caravan/syndicate3
+/obj/machinery/computer/shuttle_flight/caravan/syndicate3
name = "Syndicate Drop Ship Console"
desc = "Used to control the Syndicate Drop Ship."
icon_screen = "syndishuttle"
@@ -154,16 +95,3 @@
circuit = /obj/item/circuitboard/computer/caravan/syndicate3
shuttleId = "caravansyndicate3"
possible_destinations = "caravansyndicate3_custom;caravansyndicate3_ambush;caravansyndicate3_listeningpost"
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/caravan/syndicate3
- name = "Syndicate Drop Ship Navigation Computer"
- desc = "Used to designate a precise transit location for the Syndicate Drop Ship."
- icon_screen = "syndishuttle"
- icon_keyboard = "syndie_key"
- shuttleId = "caravansyndicate3"
- lock_override = NONE
- shuttlePortId = "caravansyndicate3_custom"
- jumpto_ports = list("caravansyndicate3_ambush" = 1, "caravansyndicate3_listeningpost" = 1)
- view_range = 2.5
- x_offset = -1
- y_offset = -3
diff --git a/code/modules/ruins/spaceruin_code/hilbertshotel.dm b/code/modules/ruins/spaceruin_code/hilbertshotel.dm
index 5e518dc822bd6..ef0751c266786 100644
--- a/code/modules/ruins/spaceruin_code/hilbertshotel.dm
+++ b/code/modules/ruins/spaceruin_code/hilbertshotel.dm
@@ -1,6 +1,7 @@
GLOBAL_VAR_INIT(hhStorageTurf, null)
GLOBAL_VAR_INIT(hhmysteryRoomNumber, 1337)
+// Someone for the love of god kill whoever indented this with spaces
/obj/item/hilbertshotel
name = "Hilbert's Hotel"
desc = "A sphere of what appears to be an intricate network of bluespace. Observing it in detail seems to give you a headache as you try to comprehend the infinite amount of infinitesimally distinct points on its surface."
@@ -124,6 +125,7 @@ GLOBAL_VAR_INIT(hhmysteryRoomNumber, 1337)
currentArea.storageTurf = storageTurf
currentArea.roomnumber = currentRoomnumber
currentArea.reservation = currentReservation
+ currentArea.virtual_z_value = get_new_virtual_z()
for(var/turf/closed/indestructible/hoteldoor/door in currentArea)
door.parentSphere = src
door.desc = "The door to this hotel room. The placard reads 'Room [currentRoomnumber]'. Strange, this door doesnt even seem openable. The doorknob, however, seems to buzz with unusual energy... Alt-Click to look through the peephole."
@@ -283,7 +285,7 @@ GLOBAL_VAR_INIT(hhmysteryRoomNumber, 1337)
user.reset_perspective(parentSphere)
user.set_machine(src)
var/datum/action/peepholeCancel/PHC = new
- user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1)
+ user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/impaired, 1)
PHC.Grant(user)
/turf/closed/indestructible/hoteldoor/check_eye(mob/user)
@@ -309,15 +311,21 @@ GLOBAL_VAR_INIT(hhmysteryRoomNumber, 1337)
icon_state = "hilbertshotel"
requires_power = FALSE
has_gravity = TRUE
- noteleport = TRUE
- hidden = TRUE
- unique = FALSE
+ teleport_restriction = TELEPORT_ALLOW_NONE
+ area_flags = HIDDEN_AREA
dynamic_lighting = DYNAMIC_LIGHTING_FORCED
- ambient_effects = list('sound/ambience/servicebell.ogg')
+ ambientsounds = list('sound/ambience/servicebell.ogg')
var/roomnumber = 0
var/obj/item/hilbertshotel/parentSphere
var/datum/turf_reservation/reservation
var/turf/storageTurf
+ var/virtual_z_value
+
+/area/hilbertshotel/get_virtual_z(turf/T)
+ if(virtual_z_value)
+ return virtual_z_value
+ else
+ return ..(T)
/area/hilbertshotel/Entered(atom/movable/AM)
. = ..()
@@ -386,9 +394,9 @@ GLOBAL_VAR_INIT(hhmysteryRoomNumber, 1337)
name = "Hilbert's Hotel Storage Room"
icon_state = "hilbertshotel"
requires_power = FALSE
+ area_flags = HIDDEN_AREA | UNIQUE_AREA
has_gravity = TRUE
- noteleport = TRUE
- hidden = TRUE
+ teleport_restriction = TELEPORT_ALLOW_NONE
/obj/item/abstracthotelstorage
anchored = TRUE
diff --git a/code/modules/ruins/spaceruin_code/whiteshipruin_box.dm b/code/modules/ruins/spaceruin_code/whiteshipruin_box.dm
index b08d87233ff34..40facf4dbcf21 100644
--- a/code/modules/ruins/spaceruin_code/whiteshipruin_box.dm
+++ b/code/modules/ruins/spaceruin_code/whiteshipruin_box.dm
@@ -1,12 +1,8 @@
/////////// ruined whiteship
/obj/item/circuitboard/computer/white_ship/ruin
- build_path = /obj/machinery/computer/shuttle/white_ship/ruin
+ build_path = /obj/machinery/computer/shuttle_flight/white_ship/ruin
-/obj/machinery/computer/shuttle/white_ship/ruin
+/obj/machinery/computer/shuttle_flight/white_ship/ruin
shuttleId = "whiteship_ruin"
circuit = /obj/item/circuitboard/computer/white_ship/ruin
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/whiteship/ruin
- shuttleId = "whiteship_ruin"
-
diff --git a/code/modules/security_levels/keycard_authentication.dm b/code/modules/security_levels/keycard_authentication.dm
index a2e161b1904fa..2c40424b973a2 100644
--- a/code/modules/security_levels/keycard_authentication.dm
+++ b/code/modules/security_levels/keycard_authentication.dm
@@ -15,6 +15,7 @@ GLOBAL_DATUM_INIT(keycard_events, /datum/events, new)
power_channel = AREA_USAGE_ENVIRON
req_access = list(ACCESS_KEYCARD_AUTH)
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ layer = ABOVE_WINDOW_LAYER
@@ -42,6 +43,7 @@ GLOBAL_DATUM_INIT(keycard_events, /datum/events, new)
if(!ui)
ui = new(user, src, "KeycardAuth")
ui.open()
+ ui.set_autoupdate(TRUE)
/obj/machinery/keycard_auth/ui_data()
var/list/data = list()
@@ -122,19 +124,21 @@ GLOBAL_DATUM_INIT(keycard_events, /datum/events, new)
GLOBAL_VAR_INIT(emergency_access, FALSE)
/proc/make_maint_all_access()
- for(var/area/maintenance/A in world)
- for(var/obj/machinery/door/airlock/D in A)
- D.emergency = TRUE
- D.update_icon(0)
+ for(var/area/maintenance/M as() in get_areas(/area/maintenance, SSmapping.levels_by_trait(ZTRAIT_STATION)[1]))
+ for(var/obj/machinery/door/airlock/A in M)
+ A.emergency = TRUE
+ A.update_icon()
+ A.wires.ui_update()
minor_announce("Access restrictions on maintenance and external airlocks have been lifted.", "Attention! Station-wide emergency declared!",1)
GLOB.emergency_access = TRUE
SSblackbox.record_feedback("nested tally", "keycard_auths", 1, list("emergency maintenance access", "enabled"))
/proc/revoke_maint_all_access()
- for(var/area/maintenance/A in world)
- for(var/obj/machinery/door/airlock/D in A)
- D.emergency = FALSE
- D.update_icon(0)
+ for(var/area/maintenance/M as() in get_areas(/area/maintenance, SSmapping.levels_by_trait(ZTRAIT_STATION)[1]))
+ for(var/obj/machinery/door/airlock/A in M)
+ A.emergency = FALSE
+ A.update_icon()
+ A.wires.ui_update()
minor_announce("Access restrictions in maintenance areas have been restored.", "Attention! Station-wide emergency rescinded:")
GLOB.emergency_access = FALSE
SSblackbox.record_feedback("nested tally", "keycard_auths", 1, list("emergency maintenance access", "disabled"))
diff --git a/code/modules/security_levels/security_levels.dm b/code/modules/security_levels/security_levels.dm
index 0a8e4073d82ab..82b011054a8a1 100644
--- a/code/modules/security_levels/security_levels.dm
+++ b/code/modules/security_levels/security_levels.dm
@@ -59,7 +59,7 @@ GLOBAL_VAR_INIT(security_level, SEC_LEVEL_GREEN)
for(var/obj/machinery/firealarm/FA in GLOB.machines)
if(is_station_level(FA.z))
FA.update_icon()
- for(var/obj/machinery/computer/shuttle/pod/pod in GLOB.machines)
+ for(var/obj/machinery/computer/shuttle_flight/pod/pod in GLOB.machines)
pod.admin_controlled = 0
if(SEC_LEVEL_DELTA)
minor_announce(CONFIG_GET(string/alert_delta), "Attention! Delta security level reached!",1)
@@ -72,12 +72,12 @@ GLOBAL_VAR_INIT(security_level, SEC_LEVEL_GREEN)
for(var/obj/machinery/firealarm/FA in GLOB.machines)
if(is_station_level(FA.z))
FA.update_icon()
- for(var/obj/machinery/computer/shuttle/pod/pod in GLOB.machines)
+ for(var/obj/machinery/computer/shuttle_flight/pod/pod in GLOB.machines)
pod.admin_controlled = 0
if(level >= SEC_LEVEL_RED)
for(var/obj/machinery/door/D in GLOB.machines)
if(D.red_alert_access)
- D.visible_message("[D] whirrs as it automatically lifts access requirements!")
+ D.visible_message("[D] whirs as it automatically lifts access requirements!")
playsound(D, 'sound/machines/boltsup.ogg', 50, TRUE)
SSblackbox.record_feedback("tally", "security_level_changes", 1, get_security_level())
SSnightshift.check_nightshift()
diff --git a/code/modules/shuttle/assault_pod.dm b/code/modules/shuttle/assault_pod.dm
index 1258b9f4b8e15..cc4004884395e 100644
--- a/code/modules/shuttle/assault_pod.dm
+++ b/code/modules/shuttle/assault_pod.dm
@@ -54,9 +54,10 @@
landing_zone.height = height
landing_zone.setDir(lz_dir)
- for(var/obj/machinery/computer/shuttle/S in GLOB.machines)
+ for(var/obj/machinery/computer/shuttle_flight/S in GLOB.machines)
if(S.shuttleId == shuttle_id)
- S.possible_destinations = "[landing_zone.id]"
+ S.recall_docking_port_id = "[landing_zone.id]"
+ S.valid_docks = list("[landing_zone.id]")
to_chat(user, "Landing zone set.")
diff --git a/code/modules/shuttle/bluespace_shuttle_pod/pod_area.dm b/code/modules/shuttle/bluespace_shuttle_pod/pod_area.dm
new file mode 100644
index 0000000000000..261c816e65e65
--- /dev/null
+++ b/code/modules/shuttle/bluespace_shuttle_pod/pod_area.dm
@@ -0,0 +1,2 @@
+/area/shuttle/pod_shuttle
+ requires_power = TRUE
diff --git a/code/modules/shuttle/bluespace_shuttle_pod/pod_computer.dm b/code/modules/shuttle/bluespace_shuttle_pod/pod_computer.dm
new file mode 100644
index 0000000000000..649c04d622b67
--- /dev/null
+++ b/code/modules/shuttle/bluespace_shuttle_pod/pod_computer.dm
@@ -0,0 +1,31 @@
+/obj/machinery/computer/shuttle_flight/custom_shuttle/bluespace_pod
+ //Uses a standard custom shuttle circuit.
+ circuit = /obj/item/circuitboard/computer/shuttle/flight_control
+ var/shuttle_named = FALSE
+
+/obj/machinery/computer/shuttle_flight/custom_shuttle/bluespace_pod/Initialize(mapload, obj/item/circuitboard/C)
+ . = ..()
+ var/static/pod_shuttles = 0
+ var/area/area_instance = get_area(src)
+ var/obj/docking_port/mobile/port = locate(/obj/docking_port/mobile) in area_instance
+ pod_shuttles ++
+ port.id = "podshuttle_[pod_shuttles]"
+ shuttleId = "podshuttle_[pod_shuttles]"
+ //Create a shuttle designator with the port
+ var/obj/item/shuttle_creator/shuttle_creator = new(loc)
+ shuttle_creator.linkedShuttleId = "podshuttle_[pod_shuttles]"
+
+/obj/machinery/computer/shuttle_flight/custom_shuttle/bluespace_pod/ui_interact(mob/user, datum/tgui/ui)
+ if(!shuttle_named)
+ var/area/area_instance = get_area(src)
+ var/obj/docking_port/mobile/port = locate(/obj/docking_port/mobile) in area_instance
+ if(port)
+ port.name = stripped_input(user, "Shuttle Name:", "Blueprint Editing", "", MAX_NAME_LEN)
+ if(!port.name)
+ port.name = "Unnamed shuttle"
+ shuttle_named = TRUE
+ . = ..()
+
+/obj/machinery/computer/shuttle_flight/custom_shuttle/bluespace_pod/traitor
+ icon_screen = "syndishuttle"
+ icon_keyboard = "syndie_key"
diff --git a/code/modules/shuttle/bluespace_shuttle_pod/shuttle_capsule.dm b/code/modules/shuttle/bluespace_shuttle_pod/shuttle_capsule.dm
new file mode 100644
index 0000000000000..fd18bd1573de6
--- /dev/null
+++ b/code/modules/shuttle/bluespace_shuttle_pod/shuttle_capsule.dm
@@ -0,0 +1,91 @@
+/obj/item/survivalcapsule/shuttle
+ name = "bluespace shuttle capsule"
+ desc = "An entire shuttle stored within a pocket of bluespace."
+ var/datum/map_template/shuttle/shuttle_template
+ //Static
+ //Subtypes that change this will have to redefine these.
+ var/static/list/blacklisted_turfs
+ var/static/list/whitelisted_turfs
+ var/static/list/whitelisted_areas
+
+/obj/item/survivalcapsule/shuttle/Initialize()
+ . = ..()
+ if(!blacklisted_turfs)
+ whitelisted_areas = typecacheof(list(
+ /area/space,
+ /area/lavaland
+ ))
+ whitelisted_turfs = typecacheof(list(
+ /turf/open/space,
+ /turf/open/floor/plating/asteroid/basalt/lava_land_surface
+ ))
+ blacklisted_turfs = typecacheof(list(
+ /turf/open/space/bluespace,
+ /turf/open/space/transit
+ ))
+
+/obj/item/survivalcapsule/shuttle/get_template()
+ if(shuttle_template)
+ return
+ shuttle_template = SSmapping.shuttle_templates[template_id]
+ if(!shuttle_template)
+ WARNING("Shuttle template ([template_id]) not found!")
+ qdel(src)
+
+/obj/item/survivalcapsule/shuttle/Destroy()
+ shuttle_template = null
+ . = ..()
+
+/obj/item/survivalcapsule/shuttle/examine(mob/user)
+ . = ..()
+ get_template()
+ . += "This capsule has the [shuttle_template.name] stored."
+ . += shuttle_template.description
+
+/obj/item/survivalcapsule/shuttle/attack_self()
+ //Can't grab when capsule is New() because templates aren't loaded then
+ get_template()
+ if(!used)
+ loc.visible_message("\The [src] begins to shake. Stand back!")
+ used = TRUE
+ sleep(50)
+ var/turf/deploy_location = get_turf(src)
+ var/status = check_deploy(deploy_location)
+ switch(status)
+ if(SHELTER_DEPLOY_BAD_AREA)
+ src.loc.visible_message("\The [src] will not function in this area.")
+ if(SHELTER_DEPLOY_BAD_TURFS, SHELTER_DEPLOY_ANCHORED_OBJECTS)
+ var/width = shuttle_template.width
+ var/height = shuttle_template.height
+ src.loc.visible_message("\The [src] doesn't have room to deploy! You need to clear a [width]x[height] area!")
+
+ if(status != SHELTER_DEPLOY_ALLOWED)
+ used = FALSE
+ return
+
+ playsound(src, 'sound/effects/phasein.ogg', 100, 1)
+
+ var/turf/T = deploy_location
+ if(!is_mining_level(T.z)) //only report capsules away from the mining/lavaland level
+ message_admins("[ADMIN_LOOKUPFLW(usr)] activated a bluespace capsule away from the mining level! [ADMIN_VERBOSEJMP(T)]")
+ log_admin("[key_name(usr)] activated a bluespace capsule away from the mining level at [AREACOORD(T)]")
+ shuttle_template.load(deploy_location, centered = TRUE)
+ new /obj/effect/particle_effect/smoke(get_turf(src))
+ qdel(src)
+
+/obj/item/survivalcapsule/shuttle/proc/check_deploy(turf/deploy_location)
+ var/affected = shuttle_template.get_affected_turfs(deploy_location, centered=TRUE)
+ for(var/turf/T in affected)
+ var/area/A = get_area(T)
+ if(!is_type_in_typecache(A, whitelisted_areas))
+ return SHELTER_DEPLOY_BAD_AREA
+
+ var/banned = is_type_in_typecache(T, blacklisted_turfs)
+ var/permitted = is_type_in_typecache(T, whitelisted_turfs)
+ if(banned && !permitted)
+ return SHELTER_DEPLOY_BAD_TURFS
+
+ for(var/obj/O in T)
+ if((O.density && O.anchored))
+ return SHELTER_DEPLOY_ANCHORED_OBJECTS
+ return SHELTER_DEPLOY_ALLOWED
diff --git a/code/modules/shuttle/bluespace_shuttle_pod/traitor_shuttle.dm b/code/modules/shuttle/bluespace_shuttle_pod/traitor_shuttle.dm
new file mode 100644
index 0000000000000..56be3d12269f3
--- /dev/null
+++ b/code/modules/shuttle/bluespace_shuttle_pod/traitor_shuttle.dm
@@ -0,0 +1,8 @@
+/obj/item/survivalcapsule/shuttle/traitor
+ template_id = "capsule_traitor"
+
+/datum/map_template/shuttle/capsule
+ port_id = "capsule"
+
+/datum/map_template/shuttle/capsule/traitor
+ suffix = "traitor"
diff --git a/code/modules/shuttle/computer.dm b/code/modules/shuttle/computer.dm
deleted file mode 100644
index 140b8b9a1df37..0000000000000
--- a/code/modules/shuttle/computer.dm
+++ /dev/null
@@ -1,84 +0,0 @@
-/obj/machinery/computer/shuttle
- name = "shuttle console"
- desc = "A shuttle control computer."
- icon_screen = "shuttle"
- icon_keyboard = "tech_key"
- light_color = LIGHT_COLOR_CYAN
- req_access = list( )
- var/shuttleId
- var/possible_destinations = ""
- var/admin_controlled
- var/no_destination_swap = 0
-
-/obj/machinery/computer/shuttle/ui_interact(mob/user)
- //Ash walkers cannot use the console because they are unga bungas
- if(user.mind?.has_antag_datum(/datum/antagonist/ashwalker))
- to_chat(user, "This computer has been designed to keep the natives like you from meddling with it, you have no hope of using it.")
- return
- . = ..()
- var/list/options = params2list(possible_destinations)
- var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId)
- var/dat = "Status: [M ? M.getStatusText() : "*Missing*"]
"
- if(M)
- var/destination_found
- for(var/obj/docking_port/stationary/S in SSshuttle.stationary)
- if(!options.Find(S.id))
- continue
- if(!M.check_dock(S, silent=TRUE))
- continue
- destination_found = 1
- dat += "Send to [S.name] "
- if(!destination_found)
- dat += "Shuttle Locked "
- if(admin_controlled)
- dat += "Authorized personnel only "
- dat += "Request Authorization "
- dat += "Close"
-
- var/datum/browser/popup = new(user, "computer", M ? M.name : "shuttle", 300, 200)
- popup.set_content("
[dat]
")
- popup.open()
-
-/obj/machinery/computer/shuttle/Topic(href, href_list)
- if(..())
- return
- usr.set_machine(src)
- src.add_fingerprint(usr)
- if(!allowed(usr))
- to_chat(usr, "Access denied.")
- return
-
- if(href_list["move"])
- var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId)
- if(M.launch_status == ENDGAME_LAUNCHED)
- to_chat(usr, "You've already escaped. Never going back to that place again!")
- return
- if(no_destination_swap)
- if(M.mode == SHUTTLE_RECHARGING)
- to_chat(usr, "Shuttle engines are not ready for use.")
- return
- if(M.mode != SHUTTLE_IDLE)
- to_chat(usr, "Shuttle already in transit.")
- return
- if(!(href_list["move"] in params2list(possible_destinations)))
- log_admin("[usr] attempted to forge a target location through a href exploit on [src]")
- message_admins("[ADMIN_FULLMONTY(usr)] attempted to forge a target location through a href exploit on [src]")
- return
- switch(SSshuttle.moveShuttle(shuttleId, href_list["move"], 1))
- if(0)
- say("Shuttle departing. Please stand away from the doors.")
- if(1)
- to_chat(usr, "Invalid shuttle requested.")
- else
- to_chat(usr, "Unable to comply.")
-
-/obj/machinery/computer/shuttle/emag_act(mob/user)
- if(obj_flags & EMAGGED)
- return
- req_access = list()
- obj_flags |= EMAGGED
- to_chat(user, "You fried the consoles ID checking system.")
-
-/obj/machinery/computer/shuttle/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
- if(port && (shuttleId == initial(shuttleId) || override))
- shuttleId = port.id
diff --git a/code/modules/shuttle/custom_shuttle.dm b/code/modules/shuttle/custom_shuttle.dm
index c564b88202774..514b967aeb1bb 100644
--- a/code/modules/shuttle/custom_shuttle.dm
+++ b/code/modules/shuttle/custom_shuttle.dm
@@ -1,8 +1,4 @@
-#define Z_DIST 500
-#define CUSTOM_ENGINES_START_TIME 65
-#define CALCULATE_STATS_COOLDOWN 2
-
-/obj/machinery/computer/custom_shuttle
+/obj/machinery/computer/shuttle_flight/custom_shuttle
name = "nanotrasen shuttle flight controller"
desc = "A terminal used to fly shuttles defined by the Shuttle Zoning Designator"
circuit = /obj/item/circuitboard/computer/shuttle/flight_control
@@ -10,262 +6,164 @@
icon_keyboard = "tech_key"
light_color = LIGHT_COLOR_CYAN
req_access = list( )
- var/shuttleId
- var/possible_destinations = "whiteship_home"
- var/admin_controlled
- var/no_destination_swap = 0
+ possible_destinations = "whiteship_home"
+
+ var/list/obj/machinery/shuttle/engine/shuttle_engines = list()
var/calculated_mass = 0
var/calculated_dforce = 0
- var/calculated_speed = 0
+ var/calculated_acceleration = 0
var/calculated_engine_count = 0
var/calculated_consumption = 0
var/calculated_cooldown = 0
var/calculated_non_operational_thrusters = 0
var/calculated_fuel_less_thrusters = 0
- var/target_fuel_cost = 0
- var/targetLocation
- var/datum/browser/popup
-
- var/stat_calc_cooldown = 0
-
- //Upgrades
- var/distance_multiplier = 1
-
-/obj/machinery/computer/custom_shuttle/examine(mob/user)
- . = ..()
- . += distance_multiplier < 1 ? "Bluespace shortcut module installed. Route is [distance_multiplier]x the original length." : ""
-/obj/machinery/computer/custom_shuttle/ui_interact(mob/user)
- var/list/options = params2list(possible_destinations)
- var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId)
- var/dat = "[M ? "Current Location : [M.getStatusText()]" : "Shuttle link required."]
"
- if(M)
- dat += "Run Flight Calculations "
- dat += "Shuttle Data"
- dat += "Shuttle Mass: [calculated_mass/10]tons "
- dat += "Engine Force: [calculated_dforce]kN ([calculated_engine_count] engines) "
- dat += "Sublight Speed: [calculated_speed]ms-1 "
- dat += calculated_speed < 1 ? "INSUFFICIENT ENGINE POWER " : ""
- dat += calculated_non_operational_thrusters > 0 ? "Warning: [calculated_non_operational_thrusters] thrusters offline. " : ""
- dat += "Fuel Consumption: [calculated_consumption]units per distance "
- dat += "Engine Cooldown: [calculated_cooldown]s"
- var/destination_found
- for(var/obj/docking_port/stationary/S in SSshuttle.stationary)
- if(!options.Find(S.id))
+/obj/machinery/computer/shuttle_flight/custom_shuttle/ui_static_data(mob/user)
+ var/list/data = ..()
+ calculateStats()
+ data["display_fuel"] = TRUE
+ return data
+
+/obj/machinery/computer/shuttle_flight/custom_shuttle/ui_data(mob/user)
+ var/list/data = ..()
+ data["fuel"] = get_fuel()
+ data["display_stats"] = list(
+ "Shuttle Mass" = "[calculated_mass/10] Tons",
+ "Engine Force" = "[calculated_dforce]kN ([calculated_engine_count] engines)",
+ "Supercruise Acceleration" = "[calculated_acceleration] ms^-2",
+ "Fuel Consumption" = "[calculated_consumption] moles per second",
+ "Engine Cooldown" = "[calculated_cooldown] seconds"
+ )
+ if(calculated_acceleration < 1)
+ data["thrust_alert"] = "Insufficient engine power at last callibration. Launch shuttle to recalculate thrust."
+ else
+ data["thrust_alert"] = 0
+ if(calculated_non_operational_thrusters > 0)
+ data["damage_alert"] = "[calculated_non_operational_thrusters] thrusters offline."
+ else
+ data["thrust_alert"] = 0
+ return data
+
+/obj/machinery/computer/shuttle_flight/custom_shuttle/launch_shuttle()
+ calculateStats()
+ if(calculated_acceleration < 1)
+ say("Insufficient engine power to engage supercruise.")
+ return
+ var/datum/orbital_object/shuttle/custom_shuttle/shuttle = ..()
+ if(shuttle)
+ shuttle.attached_console = src
+ return shuttle
+
+//Consumes fuel and reduces thrust of engines that run out of fuel
+/obj/machinery/computer/shuttle_flight/custom_shuttle/proc/consume_fuel(var/multiplier = 1)
+ //Reset stats
+ calculated_dforce = 0
+ calculated_acceleration = 0
+ calculated_engine_count = 0
+ calculated_consumption = 0
+ //Consume fuel
+ for(var/obj/machinery/shuttle/engine/E as() in shuttle_engines)
+ var/valid_thruster = FALSE
+ //Void thrusters don't need heaters
+ if(E.needs_heater)
+ //Check for inop engines
+ if(!E.attached_heater)
continue
- if(!M.check_dock(S, silent=TRUE))
+ var/obj/machinery/atmospherics/components/unary/shuttle/heater/shuttle_heater = E.attached_heater.resolve()
+ if(!shuttle_heater)
continue
- if(calculated_speed == 0)
- break
- destination_found = TRUE
- var/dist = round(calculateDistance(S))
- dat += "Target [S.name] (Dist: [dist] | Fuel Cost: [round(dist * calculated_consumption)] | Time: [round(dist / calculated_speed)]) "
- if(!destination_found)
- dat += "No valid destinations "
- dat += "[targetLocation ? "Target Location : [targetLocation]" : "No Target Location"]"
- dat += "Initate Flight "
- dat += "Close"
-
- popup = new(user, "computer", M ? M.name : "shuttle", 350, 450)
- popup.set_content("
[dat]
")
- popup.open()
-
-/obj/machinery/computer/custom_shuttle/Topic(href, href_list)
- if(..())
- return
- usr.set_machine(src)
- src.add_fingerprint(usr)
- if(!allowed(usr))
- to_chat(usr, "Access denied.")
- return
-
- if(href_list["calculate"])
- calculateStats()
- ui_interact(usr)
- return
- var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId)
- if(!M)
- return
- if(M.launch_status == ENDGAME_LAUNCHED)
- return
- if(href_list["setloc"])
- SetTargetLocation(href_list["setloc"])
- ui_interact(usr)
- return
- else if(href_list["fly"])
- Fly()
- ui_interact(usr)
- return
-
-/obj/machinery/computer/custom_shuttle/proc/calculateDistance(var/obj/docking_port/stationary/port)
- var/deltaX = port.x - x
- var/deltaY = port.y - y
- var/deltaZ = (port.z - z) * Z_DIST
- return sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ) * distance_multiplier
+ if(shuttle_heater.dir != E.dir)
+ continue
+ if(shuttle_heater.panel_open)
+ continue
+ if(!shuttle_heater.anchored)
+ continue
+ //Setup correct, check fuel.
+ if(shuttle_heater.hasFuel(E.fuel_use * multiplier / shuttle_heater.efficiency_multiplier))
+ shuttle_heater.consumeFuel(E.fuel_use * multiplier / shuttle_heater.efficiency_multiplier)
+ valid_thruster = TRUE
+ else
+ valid_thruster = TRUE
+ if(valid_thruster)
+ calculated_consumption += E.fuel_use
+ calculated_dforce += E.thrust
+ calculated_engine_count ++
+ calculated_acceleration = (calculated_dforce*1000) / (calculated_mass*100)
+
+/obj/machinery/computer/shuttle_flight/custom_shuttle/proc/check_stranded()
+ if(!calculated_engine_count && shuttleObject)
+ say("Fuel reserves depleted, dropping out of supercruise.")
+ if(!shuttleObject.docking_target)
+ if(shuttleObject.can_dock_with)
+ shuttleObject.commence_docking(shuttleObject.can_dock_with, TRUE)
+ else
+ //Send shuttle object to random location
+ var/datum/orbital_object/z_linked/beacon/z_linked = new /datum/orbital_object/z_linked/beacon/ruin/stranded_shuttle(new /datum/orbital_vector(shuttleObject.position.x, shuttleObject.position.y))
+ z_linked.name = "Stranded [shuttleObject]"
+ if(!z_linked)
+ say("Failed to dethrottle shuttle, please contact a Nanotrasen supervisor.")
+ return
+ shuttleObject.commence_docking(z_linked, TRUE)
+ shuttleObject.docking_frozen = TRUE
+ //Dock
+ if(!random_drop())
+ say("Failed to drop at a random location. Please select a location.")
+ shuttleObject.docking_frozen = FALSE
+ return TRUE
+ return FALSE
+
+/obj/machinery/computer/shuttle_flight/custom_shuttle/proc/get_fuel()
+ var/amount = 0
+ for(var/obj/machinery/shuttle/engine/E as() in shuttle_engines)
+ var/obj/machinery/atmospherics/components/unary/shuttle/heater/shuttle_heater = E.attached_heater?.resolve()
+ if(!shuttle_heater)
+ continue
+ var/datum/gas_mixture/air_contents = shuttle_heater.airs[1]
+ var/moles = air_contents.total_moles()
+ amount += moles
+ return amount
-/obj/machinery/computer/custom_shuttle/proc/linkShuttle(var/new_id)
+/obj/machinery/computer/shuttle_flight/custom_shuttle/proc/linkShuttle(var/new_id)
shuttleId = new_id
- possible_destinations = "whiteship_home;shuttle[new_id]_custom"
+ shuttlePortId = "[shuttleId]_custom_dock"
-/obj/machinery/computer/custom_shuttle/proc/calculateStats(var/useFuel = FALSE, var/dist = 0, var/ignore_cooldown = FALSE)
- if(!ignore_cooldown && stat_calc_cooldown >= world.time)
- to_chat(usr, "You are using this too fast, please slow down.")
- return
- stat_calc_cooldown = world.time + CALCULATE_STATS_COOLDOWN
+/obj/machinery/computer/shuttle_flight/custom_shuttle/proc/calculateStats(var/useFuel = FALSE)
var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId)
if(!M)
return FALSE
//Reset data
calculated_mass = 0
calculated_dforce = 0
- calculated_speed = 0
+ calculated_acceleration = 0
calculated_engine_count = 0
calculated_consumption = 0
calculated_cooldown = 0
calculated_fuel_less_thrusters = 0
calculated_non_operational_thrusters = 0
+ shuttle_engines = list()
//Calculate all the data
var/list/areas = M.shuttle_areas
for(var/shuttleArea in areas)
- calculated_mass += length(get_area_turfs(shuttleArea))
- for(var/obj/machinery/shuttle/engine/E in shuttleArea)
- E.check_setup()
- if(!E.thruster_active) //Skipover thrusters with no valid heater
- calculated_non_operational_thrusters ++
- continue
- if(E.attached_heater)
- var/obj/machinery/atmospherics/components/unary/shuttle/heater/resolvedHeater = E.attached_heater.resolve()
- if(resolvedHeater && !resolvedHeater.hasFuel(dist * E.fuel_use) && useFuel)
- calculated_fuel_less_thrusters ++
- continue
- calculated_engine_count++
- calculated_dforce += E.thrust
- calculated_consumption += E.fuel_use
- calculated_cooldown = max(calculated_cooldown, E.cooldown)
+ for(var/turf/T in shuttleArea)
+ calculated_mass += 1
+ for(var/obj/machinery/shuttle/engine/E in GLOB.custom_shuttle_machines)
+ if(!(get_area(E) in areas))
+ continue
+ E.check_setup()
+ if(!E.thruster_active) //Skipover thrusters with no valid heater
+ calculated_non_operational_thrusters ++
+ continue
+ calculated_engine_count++
+ calculated_dforce += E.thrust
+ calculated_consumption += E.fuel_use
+ calculated_cooldown = max(calculated_cooldown, E.cooldown)
+ shuttle_engines += E
//This should really be accelleration, but its a 2d spessman game so who cares
if(calculated_mass == 0)
return FALSE
- calculated_speed = (calculated_dforce*1000) / (calculated_mass*100)
+ calculated_acceleration = (calculated_dforce*1000) / (calculated_mass*100)
return TRUE
-/obj/machinery/computer/custom_shuttle/proc/consumeFuel(var/dist)
- var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId)
- if(!M)
- return FALSE
- //Calculate all the data
- for(var/obj/machinery/shuttle/engine/shuttle_machine in GLOB.custom_shuttle_machines)
- shuttle_machine.check_setup()
- if(!shuttle_machine.thruster_active)
- continue
- if(get_area(M) != get_area(shuttle_machine))
- continue
- if(shuttle_machine.attached_heater)
- var/obj/machinery/atmospherics/components/unary/shuttle/heater/resolvedHeater = shuttle_machine.attached_heater.resolve()
- if(resolvedHeater && !resolvedHeater.hasFuel(dist * shuttle_machine.fuel_use))
- continue
- resolvedHeater?.consumeFuel(dist * shuttle_machine.fuel_use)
- shuttle_machine.fireEngine()
-
-/obj/machinery/computer/custom_shuttle/proc/SetTargetLocation(var/newTarget)
- if(!(newTarget in params2list(possible_destinations)))
- log_admin("[usr] attempted to forge a target location through a href exploit on [src]")
- message_admins("[ADMIN_FULLMONTY(usr)] attempted to forge a target location through a href exploit on [src]")
- return
- targetLocation = newTarget
- say("Shuttle route calculated.")
- return
-
-/obj/machinery/computer/custom_shuttle/proc/Fly()
- if(!targetLocation)
- return
- var/obj/docking_port/mobile/linkedShuttle = SSshuttle.getShuttle(shuttleId)
- if(!linkedShuttle)
- return
- if(linkedShuttle.mode != SHUTTLE_IDLE)
- return
- if(!calculateStats(TRUE, 0, TRUE))
- return
- if(calculated_fuel_less_thrusters > 0)
- say("Warning, [calculated_fuel_less_thrusters] do not have enough fuel for this journey, engine output may be limitted.")
- if(calculated_speed < 1)
- say("Insufficient engine power, shuttle requires [calculated_mass / 10]kN of thrust.")
- return
- var/obj/docking_port/stationary/targetPort = SSshuttle.getDock(targetLocation)
- if(!targetPort)
- return
- var/dist = calculateDistance(targetPort)
- var/time = min(max(round(dist / calculated_speed), 10), 90)
- linkedShuttle.callTime = time * 10
- linkedShuttle.rechargeTime = calculated_cooldown
- linkedShuttle.ignitionTime = CUSTOM_ENGINES_START_TIME
- linkedShuttle.count_engines()
- linkedShuttle.hyperspace_sound(HYPERSPACE_WARMUP)
- var/throwForce = CLAMP((calculated_speed / 2) - 5, 0, 10)
- linkedShuttle.movement_force = list("KNOCKDOWN" = calculated_speed > 5 ? 3 : 0, "THROW" = throwForce)
- if(!(targetLocation in params2list(possible_destinations)))
- log_admin("[usr] attempted to launch a shuttle that has been affected by href dock exploit on [src] with target location \"[targetLocation]\"")
- message_admins("[usr] attempted to launch a shuttle that has been affected by href dock exploit on [src] with target location \"[targetLocation]\"")
- return
- switch(SSshuttle.moveShuttle(shuttleId, targetLocation, 1))
- if(0)
- consumeFuel(dist)
- say("Shuttle departing. Please stand away from the doors.")
- if(1)
- to_chat(usr, "Invalid shuttle requested.")
- else
- to_chat(usr, "Unable to comply.")
- return
-
-/obj/machinery/computer/custom_shuttle/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
+/obj/machinery/computer/shuttle_flight/custom_shuttle/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
if(port && (shuttleId == initial(shuttleId) || override))
linkShuttle(port.id)
-
-//Custom shuttle docker locations
-/obj/machinery/computer/camera_advanced/shuttle_docker/custom
- name = "Shuttle Navigation Computer"
- desc = "Used to designate a precise transit location for private ships."
- lock_override = NONE
- whitelist_turfs = list(/turf/open/space,
- /turf/open/lava,
- /turf/open/floor/plating/beach,
- /turf/open/floor/plating/ashplanet,
- /turf/open/floor/plating/asteroid,
- /turf/open/floor/plating/lavaland_baseturf)
- jumpto_ports = list("whiteship_home" = 1)
- view_range = 12
- designate_time = 100
- circuit = /obj/item/circuitboard/computer/shuttle/docker
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/custom/Initialize()
- . = ..()
- GLOB.jam_on_wardec += src
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/custom/Destroy()
- GLOB.jam_on_wardec -= src
- return ..()
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/custom/placeLandingSpot()
- if(!shuttleId)
- return //Only way this would happen is if someone else delinks the console while in use somehow
- var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId)
- if(M?.mode != SHUTTLE_IDLE)
- to_chat(usr, "You cannot target locations while in transit.")
- return
- ..()
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/custom/attack_hand(mob/user)
- if(!shuttleId)
- to_chat(user, "You must link the console to a shuttle first.")
- return
- return ..()
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/custom/proc/linkShuttle(new_id)
- shuttleId = new_id
- shuttlePortId = "shuttle[new_id]_custom"
-
- //Take info from connected port and calculate amendments
- var/obj/docking_port/mobile/M = SSshuttle.getShuttle(new_id)
- var/list/shuttlebounds = M.return_coords()
- view_range = min(round(max(M.width, M.height)*0.5), 15)
- x_offset = round((shuttlebounds[1] + shuttlebounds[3])*0.5) - M.x
- y_offset = round((shuttlebounds[2] + shuttlebounds[4])*0.5) - M.y
diff --git a/code/modules/shuttle/docking.dm b/code/modules/shuttle/docking.dm
index 2e572819aa8c7..956ac4e503c22 100644
--- a/code/modules/shuttle/docking.dm
+++ b/code/modules/shuttle/docking.dm
@@ -203,3 +203,11 @@
continue
var/turf/oldT = moved_atoms[moved_object]
moved_object.lateShuttleMove(oldT, movement_force, movement_direction)
+
+/obj/docking_port/mobile/proc/reset_air()
+ var/list/turfs = return_ordered_turfs(x, y, z, dir)
+ for(var/i in 1 to length(turfs))
+ var/turf/open/T = turfs[i]
+ if(istype(T))
+ T.air.copy_from_turf(T)
+
\ No newline at end of file
diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm
index 0acc6a438c730..05c22c7353cf1 100644
--- a/code/modules/shuttle/emergency.dm
+++ b/code/modules/shuttle/emergency.dm
@@ -2,35 +2,65 @@
#define ENGINES_START_TIME 100
#define ENGINES_STARTED (SSshuttle.emergency.mode == SHUTTLE_IGNITING)
#define IS_DOCKED (SSshuttle.emergency.mode == SHUTTLE_DOCKED || (ENGINES_STARTED))
+#define SHUTTLE_CONSOLE_ACTION_DELAY (5 SECONDS)
+
+#define NOT_BEGUN 0
+#define STAGE_1 1
+#define STAGE_2 2
+#define STAGE_3 3
+#define STAGE_4 4
+#define HIJACKED 5
/obj/machinery/computer/emergency_shuttle
name = "emergency shuttle console"
desc = "For shuttle control."
icon_screen = "shuttle"
icon_keyboard = "tech_key"
+ resistance_flags = INDESTRUCTIBLE
var/auth_need = 3
var/list/authorized = list()
+ var/list/acted_recently = list()
+ var/hijack_last_stage_increase = 0 SECONDS
+ var/hijack_stage_time = 5 SECONDS
+ var/hijack_stage_cooldown = 5 SECONDS
+ var/hijack_flight_time_increase = 30 SECONDS
+ var/hijack_completion_flight_time_set = 10 SECONDS //How long in deciseconds to set shuttle's timer after hijack is done.
+ var/hijack_hacking = FALSE
+ var/hijack_announce = TRUE
+
+/obj/machinery/computer/emergency_shuttle/examine(mob/user)
+ . = ..()
+ if(hijack_announce)
+ . += "Security systems present on console. Any unauthorized tampering will result in an emergency announcement."
+ if(user?.mind?.get_hijack_speed())
+ . += "Alt click on this to attempt to hijack the shuttle. This will take multiple tries (current: stage [SSshuttle.emergency.hijack_status]/[HIJACKED])."
+ . += "It will take you [(hijack_stage_time * user.mind.get_hijack_speed()) / 10] seconds to reprogram a stage of the shuttle's navigational firmware, and the console will undergo automated timed lockout for [hijack_stage_cooldown/10] seconds after each stage."
+ if(hijack_announce)
+ . += "It is probably best to fortify your position as to be uninterrupted during the attempt, given the automatic announcements.."
/obj/machinery/computer/emergency_shuttle/attackby(obj/item/I, mob/user,params)
if(istype(I, /obj/item/card/id))
say("Please equip your ID card into your ID slot to authenticate.")
. = ..()
+/obj/machinery/computer/emergency_shuttle/attack_alien(mob/living/carbon/alien/humanoid/royal/queen/user)
+ if(istype(user))
+ SSshuttle.clearHostileEnvironment(user)
/obj/machinery/computer/emergency_shuttle/ui_state(mob/user)
return GLOB.human_adjacent_state
/obj/machinery/computer/emergency_shuttle/ui_interact(mob/user, datum/tgui/ui)
-
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "EmergencyShuttleConsole")
ui.open()
+ ui.set_autoupdate(TRUE)
-/obj/machinery/computer/emergency_shuttle/ui_data()
+/obj/machinery/computer/emergency_shuttle/ui_data(user)
var/list/data = list()
data["timer_str"] = SSshuttle.emergency.getTimerStr()
@@ -48,7 +78,7 @@
A += list(list("name" = name, "job" = job))
data["authorizations"] = A
- data["enabled"] = (IS_DOCKED && !ENGINES_STARTED)
+ data["enabled"] = (IS_DOCKED && !ENGINES_STARTED) && !(user in acted_recently)
data["emagged"] = obj_flags & EMAGGED ? 1 : 0
return data
@@ -73,19 +103,29 @@
to_chat(user, "The access level of your card is not high enough.")
return
+ if(user in acted_recently)
+ return
+
var/old_len = authorized.len
+ addtimer(CALLBACK(src, .proc/clear_recent_action, user), SHUTTLE_CONSOLE_ACTION_DELAY)
switch(action)
if("authorize")
. = authorize(user)
if("repeal")
+ if(!(ID in authorized))
+ return //cannot retract auth if never given originally
authorized -= ID
+ message_admins("[ADMIN_LOOKUPFLW(user)] has deauthorized early shuttle launch, now [authorized.len] of [auth_need] needed")
+ log_game("[key_name(user)] has deauthorized early shuttle launch in [COORD(src)], now [authorized.len] of [auth_need] needed.")
if("abort")
if(authorized.len)
// Abort. The action for when heads are fighting over whether
// to launch early.
+ message_admins("[ADMIN_LOOKUPFLW(user)] has revoked early shuttle launch.")
+ log_game("[key_name(user)] has revoked early shuttle launch in [COORD(src)].")
authorized.Cut()
. = TRUE
@@ -97,6 +137,8 @@
minor_announce("[remaining] authorizations needed until shuttle is launched early.", null, alert)
if(repeal)
minor_announce("Early launch authorization revoked, [remaining] authorizations needed.")
+ log_game("Early launch authorization revoked, [remaining] authorizations needed.")
+ acted_recently += user
/obj/machinery/computer/emergency_shuttle/proc/authorize(mob/user, source)
var/obj/item/card/id/ID = user.get_idcard(TRUE)
@@ -110,11 +152,14 @@
authorized += ID
- message_admins("[ADMIN_LOOKUPFLW(user)] has authorized early shuttle launch.")
- log_game("[key_name(user)] has authorized early shuttle launch in [COORD(src)].")
+ message_admins("[ADMIN_LOOKUPFLW(user)] has authorized early shuttle launch, [authorized.len] of [auth_need] needed.")
+ log_game("[key_name(user)] has authorized early shuttle launch in [COORD(src)], [authorized.len] of [auth_need] needed..")
// Now check if we're on our way
. = TRUE
- process()
+ process(SSMACHINES_DT)
+
+/obj/machinery/computer/emergency_shuttle/proc/clear_recent_action(mob/user)
+ acted_recently -= user
/obj/machinery/computer/emergency_shuttle/process()
// Launch check is in process in case auth_need changes for some reason
@@ -139,6 +184,76 @@
[TIME_LEFT] seconds", system_error, alert=TRUE)
. = TRUE
+/obj/machinery/computer/emergency_shuttle/proc/increase_hijack_stage()
+ var/obj/docking_port/mobile/emergency/shuttle = SSshuttle.emergency
+ shuttle.hijack_status++
+ if(hijack_announce)
+ announce_hijack_stage()
+ hijack_last_stage_increase = world.time
+ say("Navigational protocol error! Rebooting systems.")
+ if(shuttle.mode == SHUTTLE_ESCAPE)
+ if(shuttle.hijack_status == HIJACKED)
+ shuttle.setTimer(hijack_completion_flight_time_set)
+ else
+ shuttle.setTimer(shuttle.timeLeft(1) + hijack_flight_time_increase) //give the guy more time to hijack if it's already in flight.
+ return shuttle.hijack_status
+
+/obj/machinery/computer/emergency_shuttle/AltClick(user)
+ if(isliving(user))
+ attempt_hijack_stage(user)
+
+/obj/machinery/computer/emergency_shuttle/proc/attempt_hijack_stage(mob/living/user)
+ if(!user.CanReach(src))
+ return
+ if(!user?.mind?.get_hijack_speed())
+ to_chat(user, "You manage to open a user-mode shell on [src], and hundreds of lines of debugging output fly through your vision. It is probably best to leave this alone.The flight plans for the shuttle haven't been loaded yet, you can't hack this right now.= HIJACKED)
+ to_chat(user, "The emergency shuttle is already loaded with a corrupt navigational payload. What more do you want from it?")
+ return
+ if(hijack_last_stage_increase >= world.time + hijack_stage_cooldown)
+ say("Error - Catastrophic software error detected. Input is currently on timeout.")
+ return
+ hijack_hacking = TRUE
+ to_chat(user, "You [SSshuttle.emergency.hijack_status == NOT_BEGUN? "begin" : "continue"] to override [src]'s navigational protocols.")
+ say("Software override initiated.")
+ var/turf/console_hijack_turf = get_turf(src)
+ message_admins("[src] is being overriden for hijack by [ADMIN_LOOKUPFLW(user)] in [ADMIN_VERBOSEJMP(console_hijack_turf)]")
+ log_game("[src] is being overriden for hijack by [key_name(user)] at [AREACOORD(src)]")
+ . = FALSE
+ if(do_after(user, hijack_stage_time * (1 / user.mind.get_hijack_speed()), target = src))
+ increase_hijack_stage()
+ console_hijack_turf = get_turf(src)
+ message_admins("[src] has had its hijack stage increased to stage [SSshuttle.emergency.hijack_status] out of [HIJACKED] by [ADMIN_LOOKUPFLW(user)] in [ADMIN_VERBOSEJMP(console_hijack_turf)]")
+ log_game("[src] has had its hijack stage increased to stage [SSshuttle.emergency.hijack_status] out of [HIJACKED] by [key_name(user)] at [AREACOORD(src)]")
+ . = TRUE
+ to_chat(user, "You reprogram some of [src]'s programming, putting it on timeout for [hijack_stage_cooldown/10] seconds.")
+ hijack_hacking = FALSE
+
+/obj/machinery/computer/emergency_shuttle/proc/announce_hijack_stage()
+ var/msg
+ switch(SSshuttle.emergency.hijack_status)
+ if(NOT_BEGUN)
+ return
+ if(STAGE_1)
+ msg = "AUTHENTICATING - FAIL. AUTHENTICATING - FAIL. AUTHENTICATING - FAI###### Welcome, technician JOHN DOE."
+ if(STAGE_2)
+ msg = "Warning: Navigational route fails \"IS_AUTHORIZED\". Please try againNN[scramble_message_replace_chars("againagainagainagainagain", 70)]."
+ if(STAGE_3)
+ msg = "CRC mismatch at ~h~ in calculated route buffer. Full reset initiated of FTL_NAVIGATION_SERVICES. Memory decrypted for automatic repair."
+ if(STAGE_4)
+ msg = "~ACS_directive module_load(cyberdyne.exploit.nanotrasen.shuttlenav)... NT key mismatch. Confirm load? Y...###Reboot complete. $SET transponder_state = 0; System link initiated with connected engines..."
+ if(HIJACKED)
+ msg = "SYSTEM OVERRIDE - Resetting course to \[[scramble_message_replace_chars("###########", 100)]\] \
+ ([scramble_message_replace_chars("#######", 100)]/[scramble_message_replace_chars("#######", 100)]/[scramble_message_replace_chars("#######", 100)]) \
+ {AUTH - ROOT (uid: 0)}.[SSshuttle.emergency.mode == SHUTTLE_ESCAPE ? "Diverting from existing route - Bluespace exit in [hijack_completion_flight_time_set/10] seconds." : ""]"
+ minor_announce(scramble_message_replace_chars(msg, replaceprob = 10), "Emergency Shuttle", TRUE)
+
/obj/machinery/computer/emergency_shuttle/emag_act(mob/user)
// How did you even get on the shuttle before it go to the station?
if(!IS_DOCKED)
@@ -165,7 +280,7 @@
authorized += ID
- process()
+ process(SSMACHINES_DT)
/obj/machinery/computer/emergency_shuttle/Destroy()
// Our fake IDs that the emag generated are just there for colour
@@ -189,6 +304,7 @@
dir = EAST
port_direction = WEST
var/sound_played = 0 //If the launch sound has been sent to all players on the shuttle itself
+ var/hijack_status = NOT_BEGUN
/obj/docking_port/mobile/emergency/canDock(obj/docking_port/stationary/S)
return SHUTTLE_CAN_DOCK //If the emergency shuttle can't move, the whole game breaks, so it will force itself to land even if it has to crush a few departments in the process
@@ -233,7 +349,7 @@
else
SSshuttle.emergencyLastCallLoc = null
- priority_announce("The emergency shuttle has been called. [redAlert ? "Red Alert state confirmed: Dispatching priority shuttle. " : "" ]It will arrive in [timeLeft(600)] minutes.[reason][SSshuttle.emergencyLastCallLoc ? "\n\nCall signal traced. Results can be viewed on any communications console." : "" ]", null, 'sound/ai/shuttlecalled.ogg', "Priority")
+ priority_announce("The emergency shuttle has been called. [redAlert ? "Red Alert state confirmed: Dispatching priority shuttle. " : "" ]It will arrive in [timeLeft(600)] minutes.[reason][SSshuttle.emergencyLastCallLoc ? "\n\nCall signal traced. Results can be viewed on any communications console." : "" ][SSshuttle.adminEmergencyNoRecall ? "\n\nWarning: Shuttle recall subroutines disabled; Recall not possible." : ""]", null, ANNOUNCER_SHUTTLECALLED, "Priority")
/obj/docking_port/mobile/emergency/cancel(area/signalOrigin)
if(mode != SHUTTLE_CALL)
@@ -248,11 +364,21 @@
SSshuttle.emergencyLastCallLoc = signalOrigin
else
SSshuttle.emergencyLastCallLoc = null
- priority_announce("The emergency shuttle has been recalled.[SSshuttle.emergencyLastCallLoc ? " Recall signal traced. Results can be viewed on any communications console." : "" ]", null, 'sound/ai/shuttlerecalled.ogg', "Priority")
-
-/obj/docking_port/mobile/emergency/proc/is_hijacked()
+ priority_announce("The emergency shuttle has been recalled.[SSshuttle.emergencyLastCallLoc ? " Recall signal traced. Results can be viewed on any communications console." : "" ]", null, ANNOUNCER_SHUTTLERECALLED, "Priority")
+
+/**
+ * Proc that handles checking if the emergency shuttle was successfully hijacked via being the only people present on the shuttle for the elimination hijack or highlander objective
+ *
+ * Checks for all mobs on the shuttle, checks their status, and checks if they're
+ * borgs or simple animals. Depending on the args, certain mobs may be ignored,
+ * and the presence of other antags may or may not invalidate a hijack.
+ * Args:
+ * filter_by_human, default TRUE, tells the proc that only humans should block a hijack. Borgs and animals are ignored and will not block if this is TRUE.
+ * solo_hijack, default FALSE, tells the proc to fail with multiple hijackers, such as for Highlander mode.
+ */
+/obj/docking_port/mobile/emergency/proc/elimination_hijack(filter_by_human = TRUE, solo_hijack = FALSE)
var/has_people = FALSE
- var/hijacker_present = FALSE
+ var/hijacker_count = 0
for(var/mob/living/player in GLOB.player_list)
if(player.mind)
if(player.stat != DEAD)
@@ -271,17 +397,17 @@
//Antag present, doesn't stop but let's see if we actually want to hijack
var/prevent = FALSE
for(var/datum/antagonist/A in player.mind.antag_datums)
- if(A.can_hijack == HIJACK_HIJACKER)
- hijacker_present = TRUE
+ if(A.can_elimination_hijack == ELIMINATION_ENABLED)
+ hijacker_count += 1
prevent = FALSE
break //If we have both prevent and hijacker antags assume we want to hijack.
- else if(A.can_hijack == HIJACK_PREVENT)
+ else if(A.can_elimination_hijack == ELIMINATION_PREVENT)
prevent = TRUE
if(prevent)
return FALSE
- return has_people && hijacker_present
+ return has_people && ((hijacker_count == 1) || (hijacker_count && !solo_hijack))
/obj/docking_port/mobile/emergency/proc/is_hijacked_by_xenos()
var/has_xenos = FALSE
@@ -300,6 +426,9 @@
return has_xenos
+/obj/docking_port/mobile/emergency/proc/is_hijacked()
+ return hijack_status == HIJACKED
+
/obj/docking_port/mobile/emergency/proc/ShuttleDBStuff()
set waitfor = FALSE
if(!SSdbcore.Connect())
@@ -338,8 +467,8 @@
return
mode = SHUTTLE_DOCKED
setTimer(SSshuttle.emergencyDockTime)
- send2irc("Server", "The Emergency Shuttle has docked with the station.")
- priority_announce("The Emergency Shuttle has docked with the station. You have [timeLeft(600)] minutes to board the Emergency Shuttle.", null, 'sound/ai/shuttledock.ogg', "Priority")
+ send2tgs("Server", "The Emergency Shuttle has docked with the station.")
+ priority_announce("[SSshuttle.emergency] has docked with the station. You have [timeLeft(600)] minutes to board the Emergency Shuttle.", null, ANNOUNCER_SHUTTLEDOCK, "Priority")
ShuttleDBStuff()
@@ -430,7 +559,7 @@
// now move the actual emergency shuttle to centcom
// unless the shuttle is "hijacked"
var/destination_dock = "emergency_away"
- if(is_hijacked())
+ if(is_hijacked() || elimination_hijack())
destination_dock = "emergency_syndicate"
minor_announce("Corruption detected in \
shuttle navigation protocols. Please contact your \
@@ -447,7 +576,7 @@
mode = SHUTTLE_ESCAPE
launch_status = ENDGAME_LAUNCHED
setTimer(SSshuttle.emergencyEscapeTime)
- priority_announce("The Emergency Shuttle preparing for direct jump. ETA: [timeLeft(600)] minutes until the shuttle docks at Central Command.", null, null, "Priority")
+ priority_announce("The Emergency Shuttle is preparing for direct jump. Estimate [timeLeft(600)] minutes until the shuttle docks at Central Command.", null, SSstation.announcer.get_rand_alert_sound(), "Priority")
/obj/docking_port/mobile/pod
@@ -459,8 +588,8 @@
launch_status = UNLAUNCHED
/obj/docking_port/mobile/pod/request(obj/docking_port/stationary/S)
- var/obj/machinery/computer/shuttle/C = getControlConsole()
- if(!istype(C, /obj/machinery/computer/shuttle/pod))
+ var/obj/machinery/computer/shuttle_flight/C = getControlConsole()
+ if(!istype(C, /obj/machinery/computer/shuttle_flight/pod))
return ..()
if(GLOB.security_level >= SEC_LEVEL_RED || (C && (C.obj_flags & EMAGGED)))
if(launch_status == UNLAUNCHED)
@@ -473,9 +602,11 @@
/obj/docking_port/mobile/pod/cancel()
return
-/obj/machinery/computer/shuttle/pod
+/obj/machinery/computer/shuttle_flight/pod
name = "pod control computer"
admin_controlled = 1
+ recall_docking_port_id = "null"
+ request_shuttle_message = "Override Escape"
possible_destinations = "pod_asteroid"
icon = 'icons/obj/terminals.dmi'
icon_state = "dorm_available"
@@ -483,19 +614,19 @@
density = FALSE
clockwork = TRUE //it'd look weird
-/obj/machinery/computer/shuttle/pod/update_icon()
+/obj/machinery/computer/shuttle_flight/pod/update_icon()
return
-/obj/machinery/computer/shuttle/pod/emag_act(mob/user)
+/obj/machinery/computer/shuttle_flight/pod/emag_act(mob/user)
if(obj_flags & EMAGGED)
return
obj_flags |= EMAGGED
to_chat(user, "You fry the pod's alert level monitoring system.")
-/obj/machinery/computer/shuttle/pod/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
+/obj/machinery/computer/shuttle_flight/pod/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
. = ..()
- if(possible_destinations == initial(possible_destinations) || override)
- possible_destinations = "pod_lavaland[idnum]"
+ if(recall_docking_port_id == initial(recall_docking_port_id) || override)
+ recall_docking_port_id = "pod_lavaland[idnum]"
/obj/docking_port/stationary/random
name = "escape pod"
@@ -558,8 +689,8 @@
new /obj/item/clothing/head/helmet/space/orange(src)
new /obj/item/clothing/suit/space/orange(src)
new /obj/item/clothing/suit/space/orange(src)
- new /obj/item/clothing/mask/gas(src)
- new /obj/item/clothing/mask/gas(src)
+ new /obj/item/clothing/mask/gas/old(src) //emergency means older models
+ new /obj/item/clothing/mask/gas/old(src)
new /obj/item/tank/internals/oxygen/red(src)
new /obj/item/tank/internals/oxygen/red(src)
new /obj/item/pickaxe/emergency(src)
@@ -583,7 +714,6 @@
/obj/item/storage/pod/AltClick(mob/user)
if(!can_interact(user))
return
- ..()
/obj/item/storage/pod/can_interact(mob/user)
if(!..())
@@ -610,6 +740,11 @@
SSshuttle.emergency = current_emergency
SSshuttle.backup_shuttle = src
+/obj/docking_port/mobile/emergency/backup/Destroy(force)
+ if(SSshuttle.backup_shuttle == src)
+ SSshuttle.backup_shuttle = null
+ return ..()
+
/obj/docking_port/mobile/emergency/shuttle_build/register()
. = ..()
initiate_docking(SSshuttle.getDock("emergency_home"))
@@ -618,3 +753,11 @@
#undef ENGINES_START_TIME
#undef ENGINES_STARTED
#undef IS_DOCKED
+#undef SHUTTLE_CONSOLE_ACTION_DELAY
+
+#undef NOT_BEGUN
+#undef STAGE_1
+#undef STAGE_2
+#undef STAGE_3
+#undef STAGE_4
+#undef HIJACKED
diff --git a/code/modules/shuttle/ferry.dm b/code/modules/shuttle/ferry.dm
index f7e3bda44d111..cc12923005937 100644
--- a/code/modules/shuttle/ferry.dm
+++ b/code/modules/shuttle/ferry.dm
@@ -1,4 +1,4 @@
-/obj/machinery/computer/shuttle/ferry
+/obj/machinery/computer/shuttle_flight/ferry
name = "transport ferry console"
desc = "A console that controls the transport ferry."
circuit = /obj/item/circuitboard/computer/ferry
@@ -9,19 +9,19 @@
var/allow_silicons = FALSE
var/allow_emag = FALSE
-/obj/machinery/computer/shuttle/ferry/emag_act(mob/user)
+/obj/machinery/computer/shuttle_flight/ferry/emag_act(mob/user)
if(!allow_emag)
to_chat(user, "[src]'s security firewall is far too powerful for you to bypass.")
return FALSE
return ..()
-/obj/machinery/computer/shuttle/ferry/attack_ai()
+/obj/machinery/computer/shuttle_flight/ferry/attack_ai()
return allow_silicons ? ..() : FALSE
-/obj/machinery/computer/shuttle/ferry/attack_robot()
+/obj/machinery/computer/shuttle_flight/ferry/attack_robot()
return allow_silicons ? ..() : FALSE
-/obj/machinery/computer/shuttle/ferry/request
+/obj/machinery/computer/shuttle_flight/ferry/request
name = "ferry console"
circuit = /obj/item/circuitboard/computer/ferry/request
var/last_request //prevents spamming admins
@@ -30,7 +30,7 @@
req_access = list(ACCESS_CENT_GENERAL)
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
-/obj/machinery/computer/shuttle/ferry/request/Topic(href, href_list)
+/obj/machinery/computer/shuttle_flight/ferry/request/Topic(href, href_list)
..()
if(href_list["request"])
if(last_request && (last_request + cooldown > world.time))
diff --git a/code/modules/shuttle/monastery.dm b/code/modules/shuttle/monastery.dm
index b04c202dca3f6..127331e99a7fe 100644
--- a/code/modules/shuttle/monastery.dm
+++ b/code/modules/shuttle/monastery.dm
@@ -1,7 +1,6 @@
-/obj/machinery/computer/shuttle/monastery_shuttle
+/obj/machinery/computer/shuttle_flight/monastery_shuttle
name = "monastery shuttle console"
desc = "Used to control the monastery shuttle."
circuit = /obj/item/circuitboard/computer/monastery_shuttle
shuttleId = "pod1"
possible_destinations = "monastery_shuttle_asteroid;monastery_shuttle_station"
- no_destination_swap = TRUE
diff --git a/code/modules/shuttle/navigation_computer.dm b/code/modules/shuttle/navigation_computer.dm
deleted file mode 100644
index 7f4b023322870..0000000000000
--- a/code/modules/shuttle/navigation_computer.dm
+++ /dev/null
@@ -1,370 +0,0 @@
-/obj/machinery/computer/camera_advanced/shuttle_docker
- name = "navigation computer"
- desc = "Used to designate a precise transit location for a spacecraft."
- jump_action = null
- should_supress_view_changes = FALSE
- var/datum/action/innate/shuttledocker_rotate/rotate_action = new
- var/datum/action/innate/shuttledocker_place/place_action = new
- var/shuttleId = ""
- var/shuttlePortId = ""
- var/shuttlePortName = "custom location"
- var/list/jumpto_ports = list() //hashset of ports to jump to and ignore for collision purposes
- var/obj/docking_port/stationary/my_port //the custom docking port placed by this console
- var/obj/docking_port/mobile/shuttle_port //the mobile docking port of the connected shuttle
- var/list/locked_traits = list(ZTRAIT_RESERVED, ZTRAIT_CENTCOM, ZTRAIT_AWAY) //traits forbided for custom docking
- var/view_range = 0
- var/x_offset = 0
- var/y_offset = 0
- var/list/whitelist_turfs = list(/turf/open/space, /turf/open/floor/plating, /turf/open/lava)
- var/see_hidden = FALSE
- var/designate_time = 0
- var/turf/designating_target_loc
- var/jammed = FALSE
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/Initialize()
- . = ..()
- GLOB.navigation_computers += src
- for(var/V in SSshuttle.stationary)
- if(!V)
- continue
- var/obj/docking_port/stationary/S = V
- if(jumpto_ports[S.id])
- z_lock |= S.z
- whitelist_turfs = typecacheof(whitelist_turfs)
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/Destroy()
- . = ..()
- GLOB.navigation_computers -= src
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/attack_hand(mob/user)
- if(jammed)
- to_chat(user, "The Syndicate is jamming the console!")
- return
- if(!shuttle_port && !SSshuttle.getShuttle(shuttleId))
- to_chat(user,"Warning: Shuttle connection severed!")
- return
- return ..()
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/GrantActions(mob/living/user)
- if(jumpto_ports.len)
- jump_action = new /datum/action/innate/camera_jump/shuttle_docker
- ..()
-
- if(rotate_action)
- rotate_action.target = user
- rotate_action.Grant(user)
- actions += rotate_action
-
- if(place_action)
- place_action.target = user
- place_action.Grant(user)
- actions += place_action
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/CreateEye()
- shuttle_port = SSshuttle.getShuttle(shuttleId)
- if(QDELETED(shuttle_port))
- shuttle_port = null
- return
-
- eyeobj = new /mob/camera/aiEye/remote/shuttle_docker(null, src)
- var/mob/camera/aiEye/remote/shuttle_docker/the_eye = eyeobj
- the_eye.setDir(shuttle_port.dir)
- var/turf/origin = locate(shuttle_port.x + x_offset, shuttle_port.y + y_offset, shuttle_port.z)
- for(var/V in shuttle_port.shuttle_areas)
- var/area/A = V
- for(var/turf/T in A)
- if(T.z != origin.z)
- continue
- var/image/I = image('icons/effects/alphacolors.dmi', origin, "red")
- var/x_off = T.x - origin.x
- var/y_off = T.y - origin.y
- I.loc = locate(origin.x + x_off, origin.y + y_off, origin.z) //we have to set this after creating the image because it might be null, and images created in nullspace are immutable.
- I.layer = ABOVE_NORMAL_TURF_LAYER
- I.plane = 0
- I.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- the_eye.placement_images[I] = list(x_off, y_off)
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/give_eye_control(mob/user)
- ..()
- if(!QDELETED(user) && user.client)
- var/mob/camera/aiEye/remote/shuttle_docker/the_eye = eyeobj
- var/list/to_add = list()
- to_add += the_eye.placement_images
- to_add += the_eye.placed_images
- if(!see_hidden)
- to_add += SSshuttle.hidden_shuttle_turf_images
-
- user.client.images += to_add
- user.client.view_size.setTo(view_range)
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/remove_eye_control(mob/living/user)
- ..()
- if(!QDELETED(user) && user.client)
- var/mob/camera/aiEye/remote/shuttle_docker/the_eye = eyeobj
- var/list/to_remove = list()
- to_remove += the_eye.placement_images
- to_remove += the_eye.placed_images
- if(!see_hidden)
- to_remove += SSshuttle.hidden_shuttle_turf_images
-
- user.client.images -= to_remove
- user.client.view_size.resetToDefault()
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/proc/placeLandingSpot()
- if(designating_target_loc || !current_user)
- return
-
- var/mob/camera/aiEye/remote/shuttle_docker/the_eye = eyeobj
- var/landing_clear = checkLandingSpot()
- if(designate_time && (landing_clear != SHUTTLE_DOCKER_BLOCKED))
- to_chat(current_user, "Targeting transit location, please wait [DisplayTimeText(designate_time)]...")
- designating_target_loc = the_eye.loc
- var/wait_completed = do_after(current_user, designate_time, FALSE, designating_target_loc, TRUE, CALLBACK(src, /obj/machinery/computer/camera_advanced/shuttle_docker/proc/canDesignateTarget))
- designating_target_loc = null
- if(!current_user)
- return
- if(!wait_completed)
- to_chat(current_user, "Operation aborted.")
- return
- landing_clear = checkLandingSpot()
-
- if(landing_clear != SHUTTLE_DOCKER_LANDING_CLEAR)
- switch(landing_clear)
- if(SHUTTLE_DOCKER_BLOCKED)
- to_chat(current_user, "Invalid transit location.")
- if(SHUTTLE_DOCKER_BLOCKED_BY_HIDDEN_PORT)
- to_chat(current_user, "Unknown object detected in landing zone. Please designate another location.")
- return
-
- ///Make one use port that deleted after fly off, to don't lose info that need on to properly fly off.
- if(my_port && my_port.get_docked())
- my_port.delete_after = TRUE
- my_port.id = null
- my_port.name = "Old [my_port.name]"
- my_port = null
-
- if(!my_port)
- my_port = new()
- my_port.name = shuttlePortName
- my_port.id = shuttlePortId
- my_port.height = shuttle_port.height
- my_port.width = shuttle_port.width
- my_port.dheight = shuttle_port.dheight
- my_port.dwidth = shuttle_port.dwidth
- my_port.hidden = shuttle_port.hidden
- my_port.setDir(the_eye.dir)
- my_port.forceMove(locate(eyeobj.x - x_offset, eyeobj.y - y_offset, eyeobj.z))
-
- if(current_user.client)
- current_user.client.images -= the_eye.placed_images
-
- QDEL_LIST(the_eye.placed_images)
-
- for(var/V in the_eye.placement_images)
- var/image/I = V
- var/image/newI = image('icons/effects/alphacolors.dmi', the_eye.loc, "blue")
- newI.loc = I.loc //It is highly unlikely that any landing spot including a null tile will get this far, but better safe than sorry.
- newI.layer = ABOVE_OPEN_TURF_LAYER
- newI.plane = 0
- newI.mouse_opacity = 0
- the_eye.placed_images += newI
-
- if(current_user.client)
- current_user.client.images += the_eye.placed_images
- to_chat(current_user, "Transit location designated.")
- return TRUE
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/proc/canDesignateTarget()
- if(!designating_target_loc || !current_user || (eyeobj.loc != designating_target_loc) || (stat & (NOPOWER|BROKEN)) )
- return FALSE
- return TRUE
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/proc/rotateLandingSpot()
- var/mob/camera/aiEye/remote/shuttle_docker/the_eye = eyeobj
- var/list/image_cache = the_eye.placement_images
- the_eye.setDir(turn(the_eye.dir, -90))
- for(var/i in 1 to image_cache.len)
- var/image/pic = image_cache[i]
- var/list/coords = image_cache[pic]
- var/Tmp = coords[1]
- coords[1] = coords[2]
- coords[2] = -Tmp
- pic.loc = locate(the_eye.x + coords[1], the_eye.y + coords[2], the_eye.z)
- var/Tmp = x_offset
- x_offset = y_offset
- y_offset = -Tmp
- checkLandingSpot()
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/proc/checkLandingSpot()
- var/mob/camera/aiEye/remote/shuttle_docker/the_eye = eyeobj
- var/turf/eyeturf = get_turf(the_eye)
- if(!eyeturf)
- return SHUTTLE_DOCKER_BLOCKED
- if(!eyeturf.z || SSmapping.level_has_any_trait(eyeturf.z, locked_traits))
- return SHUTTLE_DOCKER_BLOCKED
-
- . = SHUTTLE_DOCKER_LANDING_CLEAR
- var/list/bounds = shuttle_port.return_coords(the_eye.x - x_offset, the_eye.y - y_offset, the_eye.dir)
- var/list/overlappers = SSshuttle.get_dock_overlap(bounds[1], bounds[2], bounds[3], bounds[4], the_eye.z)
- var/list/image_cache = the_eye.placement_images
- for(var/i in 1 to image_cache.len)
- var/image/I = image_cache[i]
- var/list/coords = image_cache[I]
- var/turf/T = locate(eyeturf.x + coords[1], eyeturf.y + coords[2], eyeturf.z)
- I.loc = T
- switch(checkLandingTurf(T, overlappers))
- if(SHUTTLE_DOCKER_LANDING_CLEAR)
- I.icon_state = "green"
- if(SHUTTLE_DOCKER_BLOCKED_BY_HIDDEN_PORT)
- I.icon_state = "green"
- if(. == SHUTTLE_DOCKER_LANDING_CLEAR)
- . = SHUTTLE_DOCKER_BLOCKED_BY_HIDDEN_PORT
- else
- I.icon_state = "red"
- . = SHUTTLE_DOCKER_BLOCKED
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/proc/checkLandingTurf(turf/T, list/overlappers)
- // Too close to the map edge is never allowed
- if(!T || T.x <= 10 || T.y <= 10 || T.x >= world.maxx - 10 || T.y >= world.maxy - 10)
- return SHUTTLE_DOCKER_BLOCKED
- // If it's one of our shuttle areas assume it's ok to be there
- if(shuttle_port.shuttle_areas[T.loc])
- return SHUTTLE_DOCKER_LANDING_CLEAR
- . = SHUTTLE_DOCKER_LANDING_CLEAR
- // See if the turf is hidden from us
- var/list/hidden_turf_info
- if(!see_hidden)
- hidden_turf_info = SSshuttle.hidden_shuttle_turfs[T]
- if(hidden_turf_info)
- . = SHUTTLE_DOCKER_BLOCKED_BY_HIDDEN_PORT
-
- if(length(whitelist_turfs))
- var/turf_type = hidden_turf_info ? hidden_turf_info[2] : T.type
- if(!is_type_in_typecache(turf_type, whitelist_turfs))
- return SHUTTLE_DOCKER_BLOCKED
-
- // Checking for overlapping dock boundaries
- for(var/i in 1 to overlappers.len)
- var/obj/docking_port/port = overlappers[i]
- if(port == my_port)
- continue
- var/port_hidden = !see_hidden && port.hidden
- var/list/overlap = overlappers[port]
- var/list/xs = overlap[1]
- var/list/ys = overlap[2]
- if(xs["[T.x]"] && ys["[T.y]"])
- if(port_hidden)
- . = SHUTTLE_DOCKER_BLOCKED_BY_HIDDEN_PORT
- else
- return SHUTTLE_DOCKER_BLOCKED
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/proc/update_hidden_docking_ports(list/remove_images, list/add_images)
- if(!see_hidden && current_user && current_user.client)
- current_user.client.images -= remove_images
- current_user.client.images += add_images
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
- if(port && (shuttleId == initial(shuttleId) || override))
- shuttleId = port.id
- shuttlePortId = "[port.id]_custom"
- if(dock)
- jumpto_ports[dock.id] = TRUE
-
-/mob/camera/aiEye/remote/shuttle_docker
- visible_icon = FALSE
- use_static = USE_STATIC_NONE
- var/list/placement_images = list()
- var/list/placed_images = list()
-
-/mob/camera/aiEye/remote/shuttle_docker/Initialize(mapload, obj/machinery/computer/camera_advanced/origin)
- src.origin = origin
- return ..()
-
-/mob/camera/aiEye/remote/shuttle_docker/setLoc(T)
- ..()
- var/obj/machinery/computer/camera_advanced/shuttle_docker/console = origin
- console.checkLandingSpot()
-
-/mob/camera/aiEye/remote/shuttle_docker/update_remote_sight(mob/living/user)
- user.sight = BLIND|SEE_TURFS
- user.lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE
- user.sync_lighting_plane_alpha()
- return TRUE
-
-/datum/action/innate/shuttledocker_rotate
- name = "Rotate"
- icon_icon = 'icons/mob/actions/actions_mecha.dmi'
- button_icon_state = "mech_cycle_equip_off"
-
-/datum/action/innate/shuttledocker_rotate/Activate()
- if(QDELETED(target) || !isliving(target))
- return
- var/mob/living/C = target
- var/mob/camera/aiEye/remote/remote_eye = C.remote_control
- var/obj/machinery/computer/camera_advanced/shuttle_docker/origin = remote_eye.origin
- origin.rotateLandingSpot()
-
-/datum/action/innate/shuttledocker_place
- name = "Place"
- icon_icon = 'icons/mob/actions/actions_mecha.dmi'
- button_icon_state = "mech_zoom_off"
-
-/datum/action/innate/shuttledocker_place/Activate()
- if(QDELETED(target) || !isliving(target))
- return
- var/mob/living/C = target
- var/mob/camera/aiEye/remote/remote_eye = C.remote_control
- var/obj/machinery/computer/camera_advanced/shuttle_docker/origin = remote_eye.origin
- origin.placeLandingSpot(target)
-
-/datum/action/innate/camera_jump/shuttle_docker
- name = "Jump to Location"
- button_icon_state = "camera_jump"
-
-/datum/action/innate/camera_jump/shuttle_docker/Activate()
- if(QDELETED(target) || !isliving(target))
- return
- var/mob/living/C = target
- var/mob/camera/aiEye/remote/remote_eye = C.remote_control
- var/obj/machinery/computer/camera_advanced/shuttle_docker/console = remote_eye.origin
-
- playsound(console, 'sound/machines/terminal_prompt_deny.ogg', 25, 0)
-
- var/list/L = list()
- for(var/V in SSshuttle.stationary)
- if(!V)
- stack_trace("SSshuttle.stationary have null entry!")
- continue
- var/obj/docking_port/stationary/S = V
- if(console.z_lock.len && !(S.z in console.z_lock))
- continue
- if(console.jumpto_ports[S.id])
- L["([L.len])[S.name]"] = S
-
- for(var/V in SSshuttle.beacons)
- if(!V)
- stack_trace("SSshuttle.beacons have null entry!")
- continue
- var/obj/machinery/spaceship_navigation_beacon/nav_beacon = V
- if(!nav_beacon.z || SSmapping.level_has_any_trait(nav_beacon.z, console.locked_traits))
- break
- if(!nav_beacon.locked)
- L["([L.len]) [nav_beacon.name] located: [nav_beacon.x] [nav_beacon.y] [nav_beacon.z]"] = nav_beacon
- else
- L["([L.len]) [nav_beacon.name] locked"] = null
-
- playsound(console, 'sound/machines/terminal_prompt.ogg', 25, FALSE)
- var/selected = input("Choose location to jump to", "Locations", null) as null|anything in L
- if(QDELETED(src) || QDELETED(target) || !isliving(target))
- return
- playsound(src, "terminal_type", 25, 0)
- if(selected)
- var/turf/T = get_turf(L[selected])
- if(T)
- playsound(console, 'sound/machines/terminal_prompt_confirm.ogg', 25, 0)
- remote_eye.setLoc(T)
- to_chat(target, "Jumped to [selected].")
- C.overlay_fullscreen("flash", /obj/screen/fullscreen/flash/static)
- C.clear_fullscreen("flash", 3)
- else
- playsound(console, 'sound/machines/terminal_prompt_deny.ogg', 25, 0)
diff --git a/code/modules/shuttle/on_move.dm b/code/modules/shuttle/on_move.dm
index 67d5439a26c6a..f327fc4d9e72f 100644
--- a/code/modules/shuttle/on_move.dm
+++ b/code/modules/shuttle/on_move.dm
@@ -26,6 +26,8 @@ All ShuttleMove procs go here
if(ismob(thing))
if(isliving(thing))
var/mob/living/M = thing
+ if(M.incorporeal_move)
+ return
if(M.buckled)
M.buckled.unbuckle_mob(M, 1)
if(M.pulledby)
@@ -53,15 +55,18 @@ All ShuttleMove procs go here
if(!shuttle_boundary)
CRASH("A turf queued to move via shuttle somehow had no skipover in baseturfs. [src]([type]):[loc]")
var/depth = baseturfs.len - shuttle_boundary + 1
+
newT.CopyOnTop(src, 1, depth, TRUE)
+
+ if(isopenturf(src))
+ var/turf/open/after_src_terf = src
+ update_air_ref(isspaceturf(src) ? 0 : (after_src_terf.planetary_atmos ? 1 : 2))
+ else
+ update_air_ref(-1)
+
//Air stuff
- newT.blocks_air = TRUE
newT.air_update_turf(TRUE)
- blocks_air = TRUE
air_update_turf(TRUE)
- if(isopenturf(newT))
- var/turf/open/new_open = newT
- new_open.copy_air_with_tile(src)
return TRUE
@@ -69,6 +74,8 @@ All ShuttleMove procs go here
/turf/proc/afterShuttleMove(turf/oldT, rotation)
//Dealing with the turf we left behind
oldT.TransferComponents(src)
+ SSexplosions.wipe_turf(src)
+ SEND_SIGNAL(oldT, COMSIG_TURF_AFTER_SHUTTLE_MOVE, src) //Mostly for decals
var/shuttle_boundary = baseturfs.Find(/turf/baseturf_skipover/shuttle)
if(shuttle_boundary)
oldT.ScrapeAway(baseturfs.len - shuttle_boundary + 1, flags = CHANGETURF_FORCEOP)
@@ -251,7 +258,7 @@ All ShuttleMove procs go here
A.atmosinit()
if(A.returnPipenet())
A.addMember(src)
- build_network()
+ SSair.add_to_rebuild_queue(src)
else
// atmosinit() calls update_icon(), so we don't need to call it
update_icon()
diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm
index 992c37bb16dd8..da1eaf5d184ee 100644
--- a/code/modules/shuttle/shuttle.dm
+++ b/code/modules/shuttle/shuttle.dm
@@ -297,6 +297,11 @@
var/can_move_docking_ports = FALSE
var/list/hidden_turfs = list()
+ //The virtual Z-Value of the shuttle
+ var/virtual_z
+
+ var/shuttle_object_type = /datum/orbital_object/shuttle
+
/obj/docking_port/mobile/proc/register()
SSshuttle.mobile += src
@@ -322,13 +327,17 @@
var/list/all_turfs = return_ordered_turfs(x, y, z, dir)
for(var/i in 1 to all_turfs.len)
var/turf/curT = all_turfs[i]
- var/area/cur_area = curT.loc
+ var/area/shuttle/cur_area = curT.loc
if(istype(cur_area, area_type))
shuttle_areas[cur_area] = TRUE
+ if(!cur_area.mobile_port)
+ cur_area.link_to_shuttle(src)
initial_engines = count_engines()
current_engines = initial_engines
+ virtual_z = get_new_virtual_z()
+
#ifdef DOCKING_PORT_HIGHLIGHT
highlight("#0f0")
#endif
@@ -489,7 +498,7 @@
oldT.change_area(old_area, underlying_area)
oldT.empty(FALSE)
- // Here we locate the bottomost shuttle boundary and remove all turfs above it
+ // Here we locate the bottommost shuttle boundary and remove all turfs above it
var/list/baseturf_cache = oldT.baseturfs
for(var/k in 1 to length(baseturf_cache))
if(ispath(baseturf_cache[k], /turf/baseturf_skipover/shuttle))
@@ -633,10 +642,9 @@
var/turf/T = thing
if(!T || !istype(T.loc, area_type))
continue
- for (var/thing2 in T)
- var/atom/movable/AM = thing2
- if (length(AM.client_mobs_in_contents))
- AM.update_parallax_contents()
+ for (var/atom/movable/movable as anything in T)
+ if (length(movable.client_mobs_in_contents))
+ movable.update_parallax_contents()
/obj/docking_port/mobile/proc/check_transit_zone()
if(assigned_transit)
@@ -707,7 +715,7 @@
if(timeleft > 1 HOURS)
return "--:--"
else if(timeleft > 0)
- return "[add_leading(num2text((timeleft / 60) % 60), 2, "0")]:[add_leading(num2text(timeleft % 60), 2, " ")]"
+ return "[add_leading(num2text((timeleft / 60) % 60), 2, "0")]:[add_leading(num2text(timeleft % 60), 2, "0")]"
else
return "00:00"
@@ -754,7 +762,7 @@
/obj/docking_port/mobile/proc/getControlConsole()
for(var/place in shuttle_areas)
var/area/shuttle/shuttle_area = place
- for(var/obj/machinery/computer/shuttle/S in shuttle_area)
+ for(var/obj/machinery/computer/shuttle_flight/S in shuttle_area)
if(S.shuttleId == id)
return S
return null
@@ -776,8 +784,16 @@
var/range = (engine_coeff * max(width, height))
var/long_range = range * 2.5
var/atom/distant_source
- if(LAZYLEN(engine_list))
- distant_source = engine_list[1]
+ var/list/engines = list()
+ for(var/datum/weakref/engine in engine_list)
+ var/obj/structure/shuttle/engine/real_engine = engine.resolve()
+ if(!real_engine)
+ engine_list -= engine
+ continue
+ engines += real_engine
+
+ if(LAZYLEN(engines))
+ distant_source = engines[1]
else
for(var/A in areas)
distant_source = locate(/obj/machinery/door) in A
@@ -785,22 +801,22 @@
break
if(distant_source)
- for(var/mob/M in SSmobs.clients_by_zlevel[z])
+ for(var/mob/M as() in SSmobs.clients_by_zlevel[z])
var/dist_far = get_dist(M, distant_source)
if(dist_far <= long_range && dist_far > range)
- M.playsound_local(distant_source, "sound/effects/[selected_sound]_distance.ogg", 100, falloff = 20)
+ M.playsound_local(distant_source, "sound/effects/[selected_sound]_distance.ogg", 100, falloff_exponent = 20)
else if(dist_far <= range)
var/source
- if(engine_list.len == 0)
+ if(engines.len == 0)
source = distant_source
else
var/closest_dist = 10000
- for(var/obj/O in engine_list)
+ for(var/obj/O in engines)
var/dist_near = get_dist(M, O)
if(dist_near < closest_dist)
source = O
closest_dist = dist_near
- M.playsound_local(source, "sound/effects/[selected_sound].ogg", 100, falloff = range / 2)
+ M.playsound_local(source, "sound/effects/[selected_sound].ogg", 100, falloff_exponent = range / 2)
// Losing all initial engines should get you 2
// Adding another set of engines at 0.5 time
@@ -821,7 +837,7 @@
var/area/shuttle/areaInstance = thing
for(var/obj/structure/shuttle/engine/E in areaInstance.contents)
if(!QDELETED(E))
- engine_list += E
+ engine_list += WEAKREF(E)
. += E.engine_power
for(var/obj/machinery/shuttle/engine/E in areaInstance.contents)
if(!QDELETED(E))
diff --git a/code/modules/shuttle/shuttle_creation/shuttle_creator.dm b/code/modules/shuttle/shuttle_creation/shuttle_creator.dm
index 1a4aa32695a7d..dc7346230761d 100644
--- a/code/modules/shuttle/shuttle_creation/shuttle_creator.dm
+++ b/code/modules/shuttle/shuttle_creation/shuttle_creator.dm
@@ -24,7 +24,7 @@ GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (He
throw_range = 5
w_class = WEIGHT_CLASS_NORMAL
req_access_txt = "11"
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50, "stamina" = 0)
resistance_flags = FIRE_PROOF
var/ready = TRUE
//pre-designation
@@ -74,19 +74,11 @@ GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (He
return
if(!proximity_flag)
return
- if(istype(target, /obj/machinery/computer/custom_shuttle))
+ if(istype(target, /obj/machinery/computer/shuttle_flight/custom_shuttle))
if(!linkedShuttleId)
to_chat(user, "Error, no defined shuttle linked to device.")
return
- var/obj/machinery/computer/custom_shuttle/console = target
- console.linkShuttle(linkedShuttleId)
- to_chat(user, "Console linked successfully!")
- return
- else if(istype(target, /obj/machinery/computer/camera_advanced/shuttle_docker/custom))
- if(!linkedShuttleId)
- to_chat(user, "Error, no defined shuttle linked to device.")
- return
- var/obj/machinery/computer/camera_advanced/shuttle_docker/custom/console = target
+ var/obj/machinery/computer/shuttle_flight/custom_shuttle/console = target
console.linkShuttle(linkedShuttleId)
to_chat(user, "Console linked successfully!")
return
@@ -161,17 +153,6 @@ GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (He
position = WEST
return position
-/obj/item/shuttle_creator/proc/invertDir(var/input_dir)
- if(input_dir == NORTH)
- return SOUTH
- else if(input_dir == SOUTH)
- return NORTH
- else if(input_dir == EAST)
- return WEST
- else if(input_dir == WEST)
- return EAST
- return null
-
/obj/item/shuttle_creator/proc/shuttle_create_docking_port(atom/target, mob/user)
if(loggedTurfs.len == 0 || !recorded_shuttle_area)
@@ -181,6 +162,7 @@ GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (He
var/datum/map_template/shuttle/new_shuttle = new /datum/map_template/shuttle()
var/obj/docking_port/mobile/port = new /obj/docking_port/mobile(get_turf(target))
+ port.shuttle_object_type = /datum/orbital_object/shuttle/custom_shuttle
var/obj/docking_port/stationary/stationary_port = new /obj/docking_port/stationary(get_turf(target))
stationary_port.delete_after = TRUE
stationary_port.name = "[recorded_shuttle_area.name] Custom Shuttle construction site"
@@ -242,14 +224,14 @@ GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (He
icon_state = "rsd_used"
- //Select shuttle fly direction.
+ //Select shuttle fly direction.
select_preferred_direction(user)
//Clear highlights
overlay_holder.clear_highlights()
GLOB.custom_shuttle_count ++
- message_admins("[ADMIN_LOOKUPFLW(user)] created a new shuttle with a [src] at [ADMIN_VERBOSEJMP(user)] ([GLOB.custom_shuttle_count] custom shuttles, limit is [CUSTOM_SHUTTLE_LIMIT])")
- log_game("[key_name(user)] created a new shuttle with a [src] at [AREACOORD(user)] ([GLOB.custom_shuttle_count] custom shuttles, limit is [CUSTOM_SHUTTLE_LIMIT])")
+ message_admins("[ADMIN_LOOKUPFLW(user)] created a new shuttle with a [src] at [ADMIN_VERBOSEJMP(user)] with a name [recorded_shuttle_area.name] ([GLOB.custom_shuttle_count] custom shuttles, limit is [CUSTOM_SHUTTLE_LIMIT])")
+ log_game("[key_name(user)] created a new shuttle with a [src] at [AREACOORD(user)] with a name [recorded_shuttle_area.name] ([GLOB.custom_shuttle_count] custom shuttles, limit is [CUSTOM_SHUTTLE_LIMIT])")
return TRUE
/obj/item/shuttle_creator/proc/create_shuttle_area(mob/user)
@@ -298,7 +280,7 @@ GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (He
FD.CalculateAffectingAreas()
return TRUE
-//Select shuttle fly direction.
+//Select shuttle fly direction.
/obj/item/shuttle_creator/proc/select_preferred_direction(mob/user)
var/obj/docking_port/mobile/port = SSshuttle.getShuttle(linkedShuttleId)
if(!port || !istype(port, /obj/docking_port/mobile))
@@ -327,6 +309,8 @@ GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (He
overwritten_area = /area/space
else if(istype(place, /area/lavaland/surface/outdoors))
overwritten_area = /area/lavaland/surface/outdoors
+ else if(istype(place, /area/asteroid/generated))
+ overwritten_area = /area/asteroid/generated
else
to_chat(usr, "Caution, shuttle must not use any material connected to the station. Your shuttle is currenly overlapping with [place.name].")
return FALSE
diff --git a/code/modules/shuttle/shuttle_creation/shuttle_creator_actions.dm b/code/modules/shuttle/shuttle_creation/shuttle_creator_actions.dm
index f50b3bf59f496..1622ae1d41ba0 100644
--- a/code/modules/shuttle/shuttle_creation/shuttle_creator_actions.dm
+++ b/code/modules/shuttle/shuttle_creation/shuttle_creator_actions.dm
@@ -2,7 +2,7 @@
/datum/action/innate/shuttle_creator
icon_icon = 'icons/mob/actions/actions_shuttle.dmi'
var/mob/living/C
- var/mob/camera/aiEye/remote/shuttle_creation/remote_eye
+ var/mob/camera/ai_eye/remote/shuttle_creation/remote_eye
var/obj/item/shuttle_creator/shuttle_creator
/datum/action/innate/shuttle_creator/Activate()
diff --git a/code/modules/shuttle/shuttle_creation/shuttle_creator_console.dm b/code/modules/shuttle/shuttle_creation/shuttle_creator_console.dm
index 6945c934279fe..1adcfc78d9b9f 100644
--- a/code/modules/shuttle/shuttle_creation/shuttle_creator_console.dm
+++ b/code/modules/shuttle/shuttle_creation/shuttle_creator_console.dm
@@ -15,7 +15,7 @@
user.unset_machine()
/obj/machinery/computer/camera_advanced/shuttle_creator/CreateEye()
- eyeobj = new /mob/camera/aiEye/remote/shuttle_creation(get_turf(owner_rsd))
+ eyeobj = new /mob/camera/ai_eye/remote/shuttle_creation(get_turf(owner_rsd))
eyeobj.origin = src
eyeobj.use_static = USE_STATIC_NONE
@@ -78,13 +78,13 @@
eyeobj.eye_initialized = TRUE
give_eye_control(L)
eyeobj.setLoc(camera_location)
- var/mob/camera/aiEye/remote/shuttle_creation/shuttle_eye = eyeobj
+ var/mob/camera/ai_eye/remote/shuttle_creation/shuttle_eye = eyeobj
shuttle_eye.source_turf = get_turf(user)
else
user.unset_machine()
else
var/camera_location = get_turf(owner_rsd)
- var/mob/camera/aiEye/remote/shuttle_creation/eye = eyeobj
+ var/mob/camera/ai_eye/remote/shuttle_creation/eye = eyeobj
give_eye_control(L)
if(camera_location)
eye.source_turf = camera_location
diff --git a/code/modules/shuttle/shuttle_creation/shuttle_creator_eye.dm b/code/modules/shuttle/shuttle_creation/shuttle_creator_eye.dm
index ff96e2ae65b60..65a4b3dab1a25 100644
--- a/code/modules/shuttle/shuttle_creation/shuttle_creator_eye.dm
+++ b/code/modules/shuttle/shuttle_creation/shuttle_creator_eye.dm
@@ -1,5 +1,5 @@
//===============Camera Eye================
-/mob/camera/aiEye/remote/shuttle_creation
+/mob/camera/ai_eye/remote/shuttle_creation
name = "shuttle holo-drone"
icon = 'icons/obj/mining.dmi'
icon_state = "construction_drone"
@@ -8,17 +8,17 @@
var/turf/source_turf
var/max_range = 12
-/mob/camera/aiEye/remote/shuttle_creation/Initialize()
+/mob/camera/ai_eye/remote/shuttle_creation/Initialize()
. = ..()
setLoc(get_turf(source_turf))
-/mob/camera/aiEye/remote/shuttle_creation/update_remote_sight(mob/living/user)
+/mob/camera/ai_eye/remote/shuttle_creation/update_remote_sight(mob/living/user)
user.sight = BLIND|SEE_TURFS
user.lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE
user.sync_lighting_plane_alpha()
return TRUE
-/mob/camera/aiEye/remote/shuttle_creation/relaymove(mob/user, direct)
+/mob/camera/ai_eye/remote/shuttle_creation/relaymove(mob/user, direct)
dir = direct //This camera eye is visible as a drone, and needs to keep the dir updated
var/initial = initial(sprint)
var/max_sprint = 50
@@ -37,14 +37,14 @@
else
sprint = initial
-/mob/camera/aiEye/remote/shuttle_creation/proc/can_move_to(var/turf/T)
+/mob/camera/ai_eye/remote/shuttle_creation/proc/can_move_to(var/turf/T)
var/origin_x = source_turf.x
var/origin_y = source_turf.y
var/change_X = abs(origin_x - T.x)
var/change_Y = abs(origin_y - T.y)
return (change_X < max_range && change_Y < max_range)
-/mob/camera/aiEye/remote/shuttle_creation/setLoc(T)
+/mob/camera/ai_eye/remote/shuttle_creation/setLoc(T)
..()
if(eye_user?.client)
eye_user.client.images -= user_image
diff --git a/code/modules/shuttle/shuttle_creation/shuttle_upgrades.dm b/code/modules/shuttle/shuttle_creation/shuttle_upgrades.dm
deleted file mode 100644
index 0d777568b8791..0000000000000
--- a/code/modules/shuttle/shuttle_creation/shuttle_upgrades.dm
+++ /dev/null
@@ -1,39 +0,0 @@
-/obj/item/shuttle_route_optimisation
- name = "Route Optimisation Upgrade"
- desc = "Used on a custom shuttle control console to calculate more efficient routes."
- icon = 'icons/obj/module.dmi'
- icon_state = "shuttledisk"
- force = 0
- throwforce = 8
- throw_speed = 3
- throw_range = 5
- density = FALSE
- anchored = FALSE
- item_flags = NOBLUDGEON
- var/upgrade_amount = 0.8
-
-/obj/item/shuttle_route_optimisation/hyperlane
- name = "Bluespace Hyperlane Calculator"
- desc = "Used on a custom shuttle control console to allow for the following of bluespace hyperlanes, increasing the efficiency of the shuttle."
- icon_state = "shuttledisk_better"
- upgrade_amount = 0.6
-
-/obj/item/shuttle_route_optimisation/void
- name = "Voidspace Route Calculator"
- desc = "Used on a custom shuttle control console to allow it to navigate into voidspace, making the routes almost instant."
- icon_state = "shuttledisk_void"
- upgrade_amount = 0.2
-
-/obj/item/shuttle_route_optimisation/attack_obj(obj/O, mob/living/user)
- . = ..()
- if(!istype(O, /obj/machinery/computer))
- return
- if(!istype(O, /obj/machinery/computer/custom_shuttle))
- to_chat(user, "This upgrade only works on a custom shuttle flight console.")
- return
- if (!user.transferItemToLoc(src, get_turf(O)))
- return
- var/obj/machinery/computer/custom_shuttle/link_comp = O
- link_comp.distance_multiplier = CLAMP(link_comp.distance_multiplier, 0, upgrade_amount)
- playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
- to_chat(usr, "You insert the disk into the flight computer, allowing for routes to be [upgrade_amount]x the original distance.")
diff --git a/code/modules/shuttle/spaceship_navigation_beacon.dm b/code/modules/shuttle/spaceship_navigation_beacon.dm
deleted file mode 100644
index d7749f3d58412..0000000000000
--- a/code/modules/shuttle/spaceship_navigation_beacon.dm
+++ /dev/null
@@ -1,69 +0,0 @@
-/obj/item/circuitboard/machine/spaceship_navigation_beacon
- name = "Bluespace Navigation Gigabeacon (Machine Board)"
- build_path = /obj/machinery/spaceship_navigation_beacon
- req_components = list()
-
-
-/obj/machinery/spaceship_navigation_beacon
- name = "Bluespace Navigation Gigabeacon"
- desc = "A device that creates a bluespace anchor that allow ships jump near to it."
- icon = 'icons/obj/machines/NavBeacon.dmi'
- icon_state = "beacon-item"
- use_power = IDLE_POWER_USE
- idle_power_usage = 0
- density = TRUE
- circuit = /obj/item/circuitboard/machine/spaceship_navigation_beacon
- light_power = 2
-
- var/locked = FALSE //Locked beacons don't allow to jump to it.
-
-
-/obj/machinery/spaceship_navigation_beacon/Initialize()
- . = ..()
- SSshuttle.beacons |= src
-
-obj/machinery/spaceship_navigation_beacon/emp_act()
- locked = TRUE
-
-/obj/machinery/spaceship_navigation_beacon/Destroy()
- SSshuttle.beacons -= src
- return ..()
-
-// update the icon_state
-/obj/machinery/spaceship_navigation_beacon/update_icon()
- if(powered())
- icon_state = "beacon-active"
- set_light(1)
- if(panel_open)
- icon_state = "beacon-open"
- set_light(0)
- else
- icon_state = "beacon-inactive"
- set_light(0)
-
-/obj/machinery/spaceship_navigation_beacon/power_change()
- . = ..()
- update_icon()
-
-/obj/machinery/spaceship_navigation_beacon/multitool_act(mob/living/user, obj/item/multitool/I)
- if(panel_open)
- var/new_name = "Beacon_[capped_input(user, "Enter the custom name for this beacon", "It be Beacon ..your input..")]"
- if(new_name && Adjacent(user))
- name = new_name
- to_chat(user, "You change beacon name to [name].")
- else
- locked = !locked
- to_chat(user, "You [locked ? "" : "un"]lock [src].")
- return TRUE
-
-/obj/machinery/spaceship_navigation_beacon/examine()
- . = ..()
- . += "Status: [locked ? "LOCKED" : "Stable"] "
-
-/obj/machinery/spaceship_navigation_beacon/attackby(obj/item/W, mob/user, params)
- if(default_deconstruction_screwdriver(user, "beacon-open", "beacon-active", W))
- return
- if(default_deconstruction_crowbar(W))
- return
-
- return ..()
diff --git a/code/modules/shuttle/special.dm b/code/modules/shuttle/special.dm
index de991437b52fa..d198868f90527 100644
--- a/code/modules/shuttle/special.dm
+++ b/code/modules/shuttle/special.dm
@@ -187,7 +187,7 @@
var/mob/living/M = AM
var/throwtarget = get_edge_target_turf(src, boot_dir)
M.Paralyze(40)
- M.throw_at(throwtarget, 5, 1,src)
+ M.throw_at(throwtarget, 5, 1)
to_chat(M, "No climbing on the bar please.")
else
. = ..()
diff --git a/code/modules/shuttle/super_cruise/bluespace_beacon/bluespace_beacon.dm b/code/modules/shuttle/super_cruise/bluespace_beacon/bluespace_beacon.dm
new file mode 100644
index 0000000000000..4b9e4c5458e7c
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/bluespace_beacon/bluespace_beacon.dm
@@ -0,0 +1,44 @@
+/obj/item/sbeacondrop/exploration
+ name = "bluespace beacon"
+ desc = "A label on it reads: Warning: Activating this device will send a bluespace gigabeacon to your location, which will allow you to return to promising stations.."
+ droptype = /obj/structure/bluespace_beacon
+
+//Beacon structure
+
+/obj/structure/bluespace_beacon
+ name = "bluespace giga-beacon"
+ desc = "Locks a location on the navigational map, allowing for it to be returned to at any time."
+ icon = 'icons/obj/machines/NavBeacon.dmi'
+ icon_state = "beacon-item"
+ density = TRUE
+
+ max_integrity = 200
+ resistance_flags = FIRE_PROOF | ACID_PROOF
+
+ light_power = 2
+ light_range = 3
+ light_color = "#cd87df"
+
+ anchored = TRUE
+
+/obj/structure/bluespace_beacon/Initialize()
+ . = ..()
+ GLOB.zclear_blockers += src
+
+/obj/structure/bluespace_beacon/Destroy()
+ GLOB.zclear_blockers -= src
+ . = ..()
+
+/obj/structure/bluespace_beacon/wrench_act(mob/living/user, obj/item/I)
+ if(anchored)
+ to_chat(user, "You start unsecuring [src]...")
+ else
+ to_chat(user, "You start securing [src]...")
+ if(I.use_tool(src, user, 40, volume=50))
+ if(QDELETED(I))
+ return
+ if(anchored)
+ to_chat(user, "You unsecure [src].")
+ else
+ to_chat(user, "You secure [src].")
+ anchored = !anchored
diff --git a/code/modules/shuttle/super_cruise/interface/orbital_map_interface.dm b/code/modules/shuttle/super_cruise/interface/orbital_map_interface.dm
new file mode 100644
index 0000000000000..46ecd77a505e8
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/interface/orbital_map_interface.dm
@@ -0,0 +1,39 @@
+/datum/orbital_map_tgui
+ var/open_orbital_map = PRIMARY_ORBITAL_MAP
+
+/datum/orbital_map_tgui/ui_state(mob/user)
+ return GLOB.observer_state
+
+/datum/orbital_map_tgui/Destroy(force, ...)
+ . = ..()
+ SSorbits.open_orbital_maps -= SStgui.get_all_open_uis(src)
+
+/datum/orbital_map_tgui/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "OrbitalMap")
+ ui.open()
+ //Do not auto update, handled by orbits subsystem.
+ SSorbits.open_orbital_maps |= ui
+ ui.set_autoupdate(FALSE)
+
+/datum/orbital_map_tgui/ui_close(mob/user, datum/tgui/tgui)
+ SSorbits.open_orbital_maps -= tgui
+
+/datum/orbital_map_tgui/ui_data(mob/user)
+ var/list/data = list()
+ data["update_index"] = SSorbits.times_fired
+ data["map_objects"] = list()
+ var/datum/orbital_map/showing_map = SSorbits.orbital_maps[open_orbital_map]
+ for(var/zone in showing_map.collision_zone_bodies)
+ for(var/datum/orbital_object/object as() in showing_map.collision_zone_bodies[zone])
+ data["map_objects"] += list(list(
+ "name" = object.name,
+ "position_x" = object.position.x,
+ "position_y" = object.position.y,
+ "velocity_x" = object.velocity.x,
+ "velocity_y" = object.velocity.y,
+ "radius" = object.radius,
+ "gravity_range" = object.relevant_gravity_range
+ ))
+ return data
diff --git a/code/modules/shuttle/super_cruise/orbital_map_components/orbital_map.dm b/code/modules/shuttle/super_cruise/orbital_map_components/orbital_map.dm
new file mode 100644
index 0000000000000..f663c4136338f
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_map_components/orbital_map.dm
@@ -0,0 +1,46 @@
+/datum/orbital_map
+ //the primary star. Set to be lavaland by default.
+ var/datum/orbital_object/center = null
+ //A list of all bodies in their assigned collision zones
+ var/list/collision_zone_bodies = list()
+ //Object count
+ var/object_count
+
+/datum/orbital_map/proc/add_body(datum/orbital_object/body)
+ //Add the orbital body in the correct collision zone
+ var/position_key = "[round(body.position.x / ORBITAL_MAP_ZONE_SIZE)],[round(body.position.y / ORBITAL_MAP_ZONE_SIZE)]"
+ LAZYADDASSOCLIST(collision_zone_bodies, position_key, body)
+ object_count ++
+
+/datum/orbital_map/proc/remove_body(datum/orbital_object/body)
+ //Find the objects collision zone and remove it
+ var/position_key = "[round(body.position.x / ORBITAL_MAP_ZONE_SIZE)],[round(body.position.y / ORBITAL_MAP_ZONE_SIZE)]"
+ LAZYREMOVEASSOC(collision_zone_bodies, position_key, body)
+ object_count --
+
+/datum/orbital_map/proc/on_body_move(datum/orbital_object/body, prev_x, prev_y)
+ //Find the objects old collision zone and remove it
+ //Find the objects new collision zone and add it
+ var/pre_position_key = "[round(prev_x / ORBITAL_MAP_ZONE_SIZE)],[round(prev_y / ORBITAL_MAP_ZONE_SIZE)]"
+ var/post_position_key = "[round(body.position.x / ORBITAL_MAP_ZONE_SIZE)],[round(body.position.y / ORBITAL_MAP_ZONE_SIZE)]"
+ if(pre_position_key == post_position_key)
+ return
+ LAZYREMOVEASSOC(collision_zone_bodies, pre_position_key, body)
+ LAZYADDASSOCLIST(collision_zone_bodies, post_position_key, body)
+
+//Returns a list of gravitationally relevant bodies.
+/datum/orbital_map/proc/get_relevnant_bodies(datum/orbital_object/source)
+ . = list()
+ //Get all orbital bodies on the map.
+ for(var/collision_zone in collision_zone_bodies)
+ for(var/datum/orbital_object/body as() in collision_zone_bodies[collision_zone])
+ //Distance check last for optimisations
+ if(body != source && body.relevant_gravity_range && source.position.Distance(body.position) <= body.relevant_gravity_range)
+ . += body
+
+//Post setup function that runs after SSorbit init.
+//Moves map objects to the correct positions and gives them velocities so that they can orbit dynamically.
+/datum/orbital_map/proc/post_setup()
+ for(var/collision_zone in collision_zone_bodies)
+ for(var/datum/orbital_object/body as() in collision_zone_bodies[collision_zone])
+ body.post_map_setup()
diff --git a/code/modules/shuttle/super_cruise/orbital_map_components/orbital_object.dm b/code/modules/shuttle/super_cruise/orbital_map_components/orbital_object.dm
new file mode 100644
index 0000000000000..76293ff97e3d9
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_map_components/orbital_object.dm
@@ -0,0 +1,318 @@
+/datum/orbital_object
+ var/name = "undefined"
+ //Mass of the object in solar masses
+ var/mass = 0
+ //Radius of the object in parsecs
+ var/radius = 1
+ //Position of the object (0,0) is the center of the map.
+ //Position is in kilometers
+ //If this is modified, on_body_move() MUST be called. (Really this should be a helper proc)
+ var/datum/orbital_vector/position = new()
+ //Velocity of the object
+ //KILOMETERS PER SECOND
+ var/datum/orbital_vector/velocity = new()
+ //Static objects don't get moved.
+ var/static_object = FALSE
+ //Does the object actively thrust to maintain a stable orbit?
+ var/maintain_orbit = FALSE
+ //The object in which we are trying to maintain a stable orbit around.
+ var/datum/orbital_object/target_orbital_body
+ //Are we invisible on the map?
+ var/stealth = FALSE
+ //Multiplier for velocity
+ var/velocity_multiplier = 1
+
+ //Delta time updates
+ //Ship translations are smooth so must use a delta time
+ //Dont get confused with subsystem delta_time as this accounts for time dilation
+ var/last_update_tick = 0
+
+ //CALCULATED IN INIT
+ //Once objects are outside of this range, we will not apply gravity to them.
+ var/relevant_gravity_range
+ //Are we force-orbitting something?
+ var/orbitting = FALSE
+ //The relative velocity required for a stable orbit
+ var/relative_velocity_required
+ //Bodies that are orbitting us.
+ var/list/orbitting_bodies = list()
+ //Are we currently immune to collisions
+ var/collision_ignored = TRUE
+ //What are we colliding with
+ var/list/datum/orbital_object/colliding_with
+
+ //The index or the orbital map we exist in
+ var/orbital_map_index = PRIMARY_ORBITAL_MAP
+
+ //Our collision type
+ var/collision_type = COLLISION_UNDEFINED
+ //The collision flags we register with
+ var/collision_flags = NONE
+
+/datum/orbital_object/New(datum/orbital_vector/position, datum/orbital_vector/velocity, orbital_map_index)
+ if(orbital_map_index)
+ src.orbital_map_index = orbital_map_index
+ if(position)
+ src.position = position
+ if(velocity)
+ src.velocity = velocity
+ . = ..()
+ //Calculate relevant grav range
+ relevant_gravity_range = sqrt((mass * GRAVITATIONAL_CONSTANT) / MINIMUM_EFFECTIVE_GRAVITATIONAL_ACCEELRATION)
+ //Process this
+ if(!static_object)
+ START_PROCESSING(SSorbits, src)
+ //Add to orbital map
+ var/datum/orbital_map/map = SSorbits.orbital_maps[src.orbital_map_index]
+ map.add_body(src)
+ //If orbits has already setup, then post map setup
+ if(SSorbits.orbits_setup)
+ post_map_setup()
+
+/datum/orbital_object/Destroy()
+ STOP_PROCESSING(SSorbits, src)
+ var/datum/orbital_map/map = SSorbits.orbital_maps[orbital_map_index]
+ map.remove_body(src)
+ LAZYREMOVE(target_orbital_body?.orbitting_bodies, src)
+ if(length(orbitting_bodies))
+ for(var/datum/orbital_object/orbitting_bodies in orbitting_bodies)
+ orbitting_bodies.target_orbital_body = null
+ orbitting_bodies.Cut()
+ . = ..()
+
+/datum/orbital_object/proc/explode()
+ return
+
+//Process orbital objects, calculate gravity
+/datum/orbital_object/process()
+ //Dont process updates for static objects.
+ if(static_object)
+ return PROCESS_KILL
+
+ //NOTE TO SELF: This does nothing because world.time is in ticks not realtime.
+ var/delta_time = 0
+ if(last_update_tick)
+ //Don't go too crazy.
+ delta_time = CLAMP(world.time - last_update_tick, 10, 50) * 0.1
+ else
+ delta_time = 1
+ last_update_tick = world.time
+
+ var/datum/orbital_map/parent_map = SSorbits.orbital_maps[orbital_map_index]
+
+ //===================================
+ // GRAVITATIONAL ATTRACTION
+ //===================================
+ //Gravity is not considered while we have just undocked and are at the center of a massive body.
+ if(!collision_ignored)
+ //Find relevant gravitational bodies.
+ var/list/gravitational_bodies =parent_map.get_relevnant_bodies(src)
+ //Calculate acceleration vector
+ var/datum/orbital_vector/acceleration_per_second = new()
+ //Calculate gravity
+ for(var/datum/orbital_object/gravitational_body as() in gravitational_bodies)
+ //https://en.wikipedia.org/wiki/Gravitational_acceleration
+ var/distance = position.Distance(gravitational_body.position)
+ if(!distance)
+ continue
+ var/acceleration_amount = (GRAVITATIONAL_CONSTANT * gravitational_body.mass) / (distance * distance)
+ //Calculate acceleration direction
+ var/datum/orbital_vector/direction = new (gravitational_body.position.x - position.x, gravitational_body.position.y - position.y)
+ direction.Normalize()
+ direction.Scale(acceleration_amount)
+ //Add on the gravitational acceleration
+ acceleration_per_second.Add(direction)
+ //Divide acceleration per second by the tick rate
+ accelerate_towards(acceleration_per_second, delta_time)
+
+ //===================================
+ // ORBIT CORRECTION
+ //===================================
+ //Some objects may automatically thrust to maintain a stable orbit
+ if(maintain_orbit && target_orbital_body)
+ //Velocity should always be perpendicular to the planet
+ var/datum/orbital_vector/perpendicular_vector = new(position.y - target_orbital_body.position.y, target_orbital_body.position.x - position.x)
+ //Calculate the relative velocity we should have
+ perpendicular_vector.Normalize()
+ perpendicular_vector.Scale(relative_velocity_required)
+ //Set it because we are a lazy shit
+ velocity = perpendicular_vector.Add(target_orbital_body.velocity)
+
+ //===================================
+ // MOVEMENT
+ //===================================
+ //Remember this
+ var/prev_x = position.x
+ var/prev_y = position.y
+
+ //Move the gravitational body.
+ var/datum/orbital_vector/vel_new = new(velocity.x * delta_time * velocity_multiplier, velocity.y * delta_time * velocity_multiplier)
+ position.Add(vel_new)
+
+ //Oh we moved btw
+ parent_map.on_body_move(src, prev_x, prev_y)
+
+ //===================================
+ // COLLISION CHECKING
+ //===================================
+ var/colliding = FALSE
+ LAZYCLEARLIST(colliding_with)
+
+ //Calculate our current position
+ var/section_x = round(position.x / ORBITAL_MAP_ZONE_SIZE)
+ var/section_y = round(position.y / ORBITAL_MAP_ZONE_SIZE)
+
+ var/position_key = "[section_x],[section_y]"
+ var/valid_side_key = "none"
+ var/valid_front_key = "none"
+ var/valid_corner_key = "none"
+
+ var/dir_flags = NONE
+
+ var/segment_x = (position.x + abs(section_x) * ORBITAL_MAP_ZONE_SIZE) % ORBITAL_MAP_ZONE_SIZE
+ var/segment_y = (position.y + abs(section_y) * ORBITAL_MAP_ZONE_SIZE) % ORBITAL_MAP_ZONE_SIZE
+
+ if(segment_x < ORBITAL_MAP_ZONE_SIZE / 3)
+ valid_side_key = "[section_x - 1],[section_y]"
+ dir_flags |= WEST
+ else if(segment_x > 2 * (ORBITAL_MAP_ZONE_SIZE / 3))
+ valid_side_key = "[section_x + 1],[section_y]"
+ dir_flags |= EAST
+
+ if(segment_y < ORBITAL_MAP_ZONE_SIZE / 3)
+ valid_front_key = "[section_x],[section_y - 1]"
+ dir_flags |= SOUTH
+ else if(segment_y > 2 * (ORBITAL_MAP_ZONE_SIZE / 3))
+ valid_front_key = "[section_x],[section_y + 1]"
+ dir_flags |= NORTH
+
+ //Check multiple zones
+ if(dir_flags & EAST)
+ if(dir_flags & NORTH)
+ valid_corner_key = "[section_x + 1],[section_y + 1]"
+ else if(dir_flags & SOUTH)
+ valid_corner_key = "[section_x + 1],[section_y - 1]"
+ else if(dir_flags & WEST)
+ if(dir_flags & NORTH)
+ valid_corner_key = "[section_x - 1],[section_y + 1]"
+ else if(dir_flags & SOUTH)
+ valid_corner_key = "[section_x - 1],[section_y - 1]"
+
+ var/list/valid_objects = list()
+
+ //Only check nearby segments for collision objects
+ if(parent_map.collision_zone_bodies[position_key])
+ valid_objects += parent_map.collision_zone_bodies[position_key]
+ if(parent_map.collision_zone_bodies[valid_side_key])
+ valid_objects += parent_map.collision_zone_bodies[valid_side_key]
+ if(parent_map.collision_zone_bodies[valid_front_key])
+ valid_objects += parent_map.collision_zone_bodies[valid_front_key]
+ if(parent_map.collision_zone_bodies[valid_corner_key])
+ valid_objects += parent_map.collision_zone_bodies[valid_corner_key]
+
+ //Track our delta positional values for collision detection purposes
+ var/delta_x = position.x - prev_x
+ var/delta_y = position.y - prev_y
+
+ for(var/datum/orbital_object/object as() in valid_objects)
+ if(object == src)
+ continue
+ if(!((collision_flags & object.collision_type) || (object.collision_flags & collision_type)))
+ continue
+ var/distance = object.position.Distance(position)
+ if(distance < radius + object.radius)
+ //Collision
+ LAZYADD(colliding_with, object)
+ collision(object)
+ //Static objects dont check collisions, so call their collision proc for them.
+ if(object.static_object)
+ object.collision(src)
+ colliding = TRUE
+ else if(!object.static_object)
+ //Vector collision.
+ //Note: We detect collisions that occursed in the current move rather than in the next.
+ //Position - Velocity -> Position
+ //Detects collisions for when 2 objects pass each other.
+ //Get the intersection point
+ //Must be between 0 and 1
+ var/other_x
+ var/other_y
+ var/other_delta_x = object.velocity.x
+ var/other_delta_y = object.velocity.y
+ if(object.last_update_tick == last_update_tick)
+ //They are on the same tick as us
+ other_x = object.position.x - other_delta_x
+ other_y = object.position.y - other_delta_y
+ else
+ //They are still on the previous tick
+ other_x = object.position.x
+ other_y = object.position.y
+ //ALRIGHT LETS DO THE CHECK
+ //Reassign variables for ease of read.
+ var/px = prev_x
+ var/py = prev_y
+ var/vx = delta_x
+ var/vy = delta_y
+ var/px2 = other_x
+ var/py2 = other_y
+ var/vx2 = other_delta_x
+ var/vy2 = other_delta_y
+ //Both must be moving
+ if((vx || vy) && (vx2 || vy2))
+ //Collision between 2 vectors using simultaneous equations.
+ var/mu = (vx * py2 + vy * px - py * vx - vy * px2) / (vy * vx2 - vx * vy2)
+ var/lambda = (px2 + vx2 * mu - px) / vx
+ if(lambda >= 0 && lambda <= 1 && mu >= 0 && mu <= 1)
+ //Collision
+ LAZYADD(colliding_with, object)
+ collision(object)
+ colliding = TRUE
+ if(!colliding)
+ collision_ignored = FALSE
+
+//We do a little suvatting
+/datum/orbital_object/proc/accelerate_towards(datum/orbital_vector/acceleration_vector, time)
+ velocity.Add(acceleration_vector.Scale(time))
+
+//Called when we collide with another orbital object.
+//Make sure to check if(other.collision_ignored || collision_ignored)
+/datum/orbital_object/proc/collision(datum/orbital_object/other)
+ return
+
+/datum/orbital_object/proc/set_orbitting_around_body(datum/orbital_object/target_body, orbit_radius = 10, force = FALSE)
+ if(orbitting && !force)
+ return
+ var/prev_x = position.x
+ var/prev_y = position.y
+ orbitting = TRUE
+ //Calculates the required velocity for the object to orbit around the target body.
+ //Hopefully the planets gravity doesn't fuck with each other too hard.
+ //Set position
+ var/delta_x = -position.x
+ var/delta_y = -position.y
+ position.x = target_body.position.x + orbit_radius
+ position.y = target_body.position.y
+ delta_x += position.x
+ delta_y += position.y
+ //Move all orbitting b()odies too.
+ if(orbitting_bodies)
+ for(var/datum/orbital_object/object in orbitting_bodies)
+ object.position.Add(new /datum/orbital_vector(delta_x, delta_y))
+ //Set velocity
+ var/relative_velocity = sqrt((GRAVITATIONAL_CONSTANT * (target_body.mass + mass)) / orbit_radius)
+ velocity.x = target_body.velocity.x
+ velocity.y = target_body.velocity.y + relative_velocity
+ //Set random angle
+ var/random_angle = rand(0, 360) //Is cos and sin in radians?
+ position.Rotate(random_angle)
+ velocity.Rotate(random_angle)
+ //Update target
+ target_orbital_body = target_body
+ LAZYADD(target_body.orbitting_bodies, src)
+ relative_velocity_required = relative_velocity
+ //We moved, make sure to update the map.
+ var/datum/orbital_map/parent_map = SSorbits.orbital_maps[orbital_map_index]
+ parent_map.on_body_move(src, prev_x, prev_y)
+
+/datum/orbital_object/proc/post_map_setup()
+ return
diff --git a/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/beacon.dm b/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/beacon.dm
new file mode 100644
index 0000000000000..5a4c7882fa35b
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/beacon.dm
@@ -0,0 +1,131 @@
+/datum/orbital_object/z_linked/beacon
+ name = "Unidentified Signal"
+ mass = 0
+ radius = 30
+ can_dock_anywhere = TRUE
+ //The attached event
+ var/datum/ruin_event/ruin_event
+
+/datum/orbital_object/z_linked/beacon/New()
+ . = ..()
+ var/datum/orbital_map/linked_map = SSorbits.orbital_maps[orbital_map_index]
+ ruin_event = SSorbits.get_event()
+ if(ruin_event?.warning_message)
+ name = "[initial(name)] #[rand(1, 9)][linked_map.object_count][rand(1, 9)] ([ruin_event.warning_message])"
+ else
+ name = "[initial(name)] #[rand(1, 9)][linked_map.object_count][rand(1, 9)]"
+ //Link the ruin event to ourselves
+ ruin_event?.linked_z = src
+
+/datum/orbital_object/z_linked/beacon/post_map_setup()
+ //Orbit around the systems sun
+ var/datum/orbital_map/linked_map = SSorbits.orbital_maps[orbital_map_index]
+ set_orbitting_around_body(linked_map.center, 4000 + 250 * linked_z_level[1].z_value)
+
+/datum/orbital_object/z_linked/beacon/weak
+ name = "Weak Signal"
+
+//====================
+// Asteroids
+//====================
+
+/datum/orbital_object/z_linked/beacon/ruin/asteroid
+ name = "Asteroid"
+
+/datum/orbital_object/z_linked/beacon/ruinasteroid/New()
+ . = ..()
+ radius = rand(30, 70)
+
+/datum/orbital_object/z_linked/beacon/ruin/asteroid/assign_z_level()
+ var/datum/space_level/assigned_space_level = SSzclear.get_free_z_level()
+ linked_z_level = list(assigned_space_level)
+ assigned_space_level.orbital_body = src
+ generate_asteroids(world.maxx / 2, world.maxy / 2, assigned_space_level.z_value, 120, rand(-0.5, 0), rand(40, 70))
+
+/datum/orbital_object/z_linked/beacon/ruin/asteroid/post_map_setup()
+ //Orbit around the systems central gravitional body
+ //Pack closely together to make an asteriod belt.
+ var/datum/orbital_map/linked_map = SSorbits.orbital_maps[orbital_map_index]
+ set_orbitting_around_body(linked_map.center, 1200 + 20 * rand(-10, 10))
+
+//====================
+// Regular Ruin Z-levels
+//====================
+
+/datum/orbital_object/z_linked/beacon/ruin/spaceruin
+ name = "Unknown Signal"
+
+/datum/orbital_object/z_linked/beacon/ruin/spaceruin/New()
+ . = ..()
+ SSorbits.ruin_levels ++
+
+/datum/orbital_object/z_linked/beacon/ruin/spaceruin/Destroy(force, ...)
+ . = ..()
+ SSorbits.ruin_levels --
+
+/datum/orbital_object/z_linked/beacon/ruin/spaceruin/assign_z_level()
+ var/datum/space_level/assigned_space_level = SSzclear.get_free_z_level()
+ linked_z_level = list(assigned_space_level)
+ assigned_space_level.orbital_body = src
+ seedRuins(list(assigned_space_level.z_value), CONFIG_GET(number/space_budget), /area/space, SSmapping.space_ruins_templates)
+
+/datum/orbital_object/z_linked/beacon/ruin/spaceruin/post_map_setup()
+ //Orbit around the systems sun
+ var/datum/orbital_map/linked_map = SSorbits.orbital_maps[orbital_map_index]
+ set_orbitting_around_body(linked_map.center, 4000 + 250 * rand(4, 20))
+
+//====================
+// Random-Ruin z-levels
+//====================
+/datum/orbital_object/z_linked/beacon/ruin
+ //The linked objective to the ruin, for generating extra stuff if required.
+ var/datum/orbital_objective/linked_objective
+
+/datum/orbital_object/z_linked/beacon/ruin/Destroy()
+ //Remove linked objective.
+ if(linked_objective)
+ linked_objective.linked_beacon = null
+ linked_objective = null
+ . = ..()
+
+/datum/orbital_object/z_linked/beacon/ruin/proc/assign_z_level()
+ var/datum/space_level/assigned_space_level = SSzclear.get_free_z_level()
+ linked_z_level = list(assigned_space_level)
+ assigned_space_level.orbital_body = src
+ generate_space_ruin(world.maxx / 2, world.maxy / 2, assigned_space_level.z_value, 100, 100, linked_objective, null, ruin_event)
+
+/datum/orbital_object/z_linked/beacon/ruin/post_map_setup()
+ //Orbit around the systems sun
+ var/datum/orbital_map/linked_map = SSorbits.orbital_maps[orbital_map_index]
+ set_orbitting_around_body(linked_map.center, 4000 + 250 * rand(4, 20))
+
+//====================
+//Stranded shuttles
+//====================
+/datum/orbital_object/z_linked/beacon/ruin/stranded_shuttle
+ name = "Distress Beacon"
+ static_object = TRUE
+
+/datum/orbital_object/z_linked/beacon/ruin/stranded_shuttle/assign_z_level()
+ var/datum/space_level/assigned_space_level = SSzclear.get_free_z_level()
+ linked_z_level = list(assigned_space_level)
+ assigned_space_level.orbital_body = src
+ generate_asteroids(world.maxx / 2, world.maxy / 2, assigned_space_level.z_value, 120, -0.4, 40)
+
+/datum/orbital_object/z_linked/beacon/ruin/stranded_shuttle/post_map_setup()
+ return
+
+//====================
+//Interdiction
+//====================
+/datum/orbital_object/z_linked/beacon/ruin/interdiction
+ name = "Distress Beacon"
+ static_object = TRUE
+
+/datum/orbital_object/z_linked/beacon/ruin/interdiction/assign_z_level()
+ var/datum/space_level/assigned_space_level = SSzclear.get_free_z_level()
+ linked_z_level = list(assigned_space_level)
+ assigned_space_level.orbital_body = src
+
+/datum/orbital_object/z_linked/beacon/ruin/interdiction/post_map_setup()
+ return
diff --git a/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/custom_shuttle.dm b/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/custom_shuttle.dm
new file mode 100644
index 0000000000000..65ca032be59a6
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/custom_shuttle.dm
@@ -0,0 +1,17 @@
+/datum/orbital_object/shuttle/custom_shuttle
+ name = "Custom Shuttle"
+ var/fuel_consumption_rate = 1
+ var/obj/machinery/computer/shuttle_flight/custom_shuttle/attached_console
+
+/datum/orbital_object/shuttle/custom_shuttle/Destroy()
+ attached_console = null
+ . = ..()
+
+/datum/orbital_object/shuttle/custom_shuttle/process()
+ if(!attached_console)
+ return
+ attached_console.consume_fuel(ORBITAL_UPDATE_RATE_SECONDS * fuel_consumption_rate * thrust / 100)
+ if(attached_console.check_stranded())
+ return
+ max_thrust = (5 * arctan(attached_console.calculated_acceleration / 20)) / 90
+ . = ..()
diff --git a/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/lavaland.dm b/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/lavaland.dm
new file mode 100644
index 0000000000000..8dc41b40e6f3e
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/lavaland.dm
@@ -0,0 +1,12 @@
+/datum/orbital_object/z_linked/lavaland
+ name = "Lavaland"
+ mass = 10000
+ radius = 200
+ forced_docking = TRUE
+ static_object = TRUE
+ random_docking = TRUE
+
+/datum/orbital_object/z_linked/lavaland/New()
+ . = ..()
+ var/datum/orbital_map/linked_map = SSorbits.orbital_maps[orbital_map_index]
+ linked_map.center = src
diff --git a/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/meteor.dm b/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/meteor.dm
new file mode 100644
index 0000000000000..6df0624fbd43e
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/meteor.dm
@@ -0,0 +1,71 @@
+/datum/orbital_object/meteor
+ name = "Meteor"
+ collision_type = COLLISION_METEOR
+ collision_flags = COLLISION_SHUTTLES | COLLISION_Z_LINKED
+ var/datum/orbital_object/target
+ var/list/meteor_types
+ var/start_tick
+ var/end_tick
+ var/start_x
+ var/start_y
+ var/end_x
+ var/end_y
+
+/datum/orbital_object/meteor/New()
+ . = ..()
+ start_tick = world.time
+ end_tick = world.time + 10 MINUTES
+ radius = rand(10, 50)
+
+/datum/orbital_object/meteor/Destroy()
+ target = null
+ meteor_types = null
+ . = ..()
+
+/datum/orbital_object/meteor/process()
+ . = ..()
+ if(!QDELETED(target))
+ end_x = target.position.x
+ end_y = target.position.y
+ var/current_tick = world.time
+ var/tick_proportion = (current_tick - start_tick) / (end_tick - start_tick)
+ var/current_x = (end_x * tick_proportion) + (start_x * (1 - tick_proportion))
+ var/current_y = (end_y * tick_proportion) + (start_y * (1 - tick_proportion))
+ MOVE_ORBITAL_BODY(src, current_x, current_y)
+ if(abs(position.x) > 10000 || abs(position.y) > 10000)
+ qdel(src)
+
+/datum/orbital_object/meteor/collision(datum/orbital_object/other)
+ //If we collide with a shuttle, do a little explosion
+ if(istype(other, /datum/orbital_object/shuttle))
+ var/datum/orbital_object/shuttle/shuttleobj = other
+ if(shuttleobj.port)
+ for(var/i in 1 to 5)
+ impact_turfs(shuttleobj.port.return_turfs())
+ //If we collide with a z-linked, spawn a meteor on that z-level
+ if(istype(other, /datum/orbital_object/z_linked))
+ var/datum/orbital_object/z_linked/z_linked = other
+ if(!z_linked.can_dock_anywhere && !z_linked.random_docking)
+ return
+ if(!LAZYLEN(z_linked.linked_z_level))
+ return
+ var/datum/space_level/space_level = pick(z_linked.linked_z_level)
+ //Check protected levels
+ if(space_level.traits[ZTRAIT_CENTCOM] || space_level.traits[ZTRAIT_REEBE])
+ return
+ //Check level flags for planetary bodies
+ if(space_level.traits[ZTRAIT_MINING])
+ for(var/i in 1 to 5)
+ meteor_impact(locate(rand(10, world.maxx - 10), rand(10, world.maxx-10), space_level.z_value))
+ else
+ //Spawn meteor wave
+ spawn_meteors(5, meteor_types, space_level.z_value)
+ qdel(src)
+
+/datum/orbital_object/meteor/proc/impact_turfs(list/valid_turfs)
+ if(length(valid_turfs))
+ meteor_impact(pick(valid_turfs))
+
+//Fall from the sky
+/datum/orbital_object/meteor/proc/meteor_impact(turf/T)
+ new /obj/effect/falling_meteor(T, meteor_types ? pick(meteor_types) : null)
diff --git a/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/phobos.dm b/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/phobos.dm
new file mode 100644
index 0000000000000..715f696ae842a
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/phobos.dm
@@ -0,0 +1,11 @@
+//Centcom Z-Level.
+//Syndicate infiltrator level.
+/datum/orbital_object/z_linked/phobos
+ name = "Phobos"
+ mass = 500
+ radius = 130
+
+/datum/orbital_object/z_linked/phobos/post_map_setup()
+ //Orbit around the systems sun
+ var/datum/orbital_map/linked_map = SSorbits.orbital_maps[orbital_map_index]
+ set_orbitting_around_body(linked_map.center, 3800)
diff --git a/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/shuttle.dm b/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/shuttle.dm
new file mode 100644
index 0000000000000..56f46c0b1790e
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/shuttle.dm
@@ -0,0 +1,193 @@
+/datum/orbital_object/shuttle
+ name = "Shuttle"
+ collision_type = COLLISION_SHUTTLES
+ collision_flags = COLLISION_Z_LINKED | COLLISION_METEOR
+ var/shuttle_port_id
+ //Shuttle data
+ var/max_thrust = 2
+ //Controls
+ var/thrust = 0
+ var/angle = 0
+ //Valid docking locations
+ var/list/valid_docks = list()
+ //Docking
+ var/docking_frozen = FALSE
+ var/datum/orbital_object/z_linked/can_dock_with
+ var/datum/orbital_object/z_linked/docking_target
+
+ var/desired_vel_x = 0
+ var/desired_vel_y = 0
+
+ //They go faster
+ velocity_multiplier = 3
+
+ //The computer controlling us.
+ var/controlling_computer = null
+
+ var/obj/docking_port/mobile/port
+
+ //Semi-Autopilot controls
+ var/datum/orbital_vector/shuttleTargetPos
+
+ //AUTOPILOT CONTROLS.
+ //Is autopilot enabled.
+ //Determines if the autopilot should fly to the
+ var/autopilot = FALSE
+ //The target, speeds are calulated relative to this.
+ var/datum/orbital_object/shuttleTarget
+ //Cheating autopilots never fail
+ var/cheating_autopilot = FALSE
+
+/datum/orbital_object/shuttle/stealth/infiltrator
+ max_thrust = 2.5
+
+/datum/orbital_object/shuttle/stealth/steel_rain
+ max_thrust = 0
+ //We never miss our mark
+ cheating_autopilot = TRUE
+
+/datum/orbital_object/shuttle/stealth
+ stealth = TRUE
+
+/datum/orbital_object/shuttle/Destroy()
+ port = null
+ can_dock_with = null
+ docking_target = null
+ valid_docks = null
+ shuttleTarget = null
+ . = ..()
+ SSorbits.assoc_shuttles.Remove(shuttle_port_id)
+
+//Dont fly into the sun idiot.
+/datum/orbital_object/shuttle/explode()
+ if(port)
+ port.jumpToNullSpace()
+ qdel(src)
+
+/datum/orbital_object/shuttle/process()
+ if(check_stuck())
+ return
+
+ if(!QDELETED(docking_target))
+ velocity.x = 0
+ velocity.y = 0
+ MOVE_ORBITAL_BODY(src, docking_target.position.x, docking_target.position.y)
+ //Disable autopilot and thrust while docking to prevent fuel usage.
+ thrust = 0
+ angle = 0
+ autopilot = FALSE
+ return
+ else
+ //If our docking target was deleted, null it to prevent docking interface etc.
+ docking_target = null
+ //I hate that I have to do this, but people keep flying them away.
+ if(position.x > 20000 || position.x < -20000 || position.y > 20000 || position.y < -20000)
+ priority_announce("Bluespace reality fracture detected, source: [name].")
+ MOVE_ORBITAL_BODY(src, rand(-2000, 2000), rand(-2000, 2000))
+ velocity.x = 0
+ velocity.y = 0
+ thrust = 0
+ //AUTOPILOT
+ handle_autopilot()
+ //Do thrust
+ var/thrust_amount = thrust * max_thrust / 100
+ var/thrust_x = cos(angle) * thrust_amount
+ var/thrust_y = sin(angle) * thrust_amount
+ accelerate_towards(new /datum/orbital_vector(thrust_x, thrust_y), ORBITAL_UPDATE_RATE_SECONDS)
+ //Do gravity and movement
+ can_dock_with = null
+ . = ..()
+
+/datum/orbital_object/shuttle/proc/check_stuck()
+ if(!port)
+ return FALSE
+ if(!is_reserved_level(port.z) && port.mode == SHUTTLE_IDLE)
+ message_admins("Shuttle [shuttle_port_id] is not on a reserved Z-Level but is somehow registered as in flight! Automatically fixing...")
+ log_runtime("Shuttle [shuttle_port_id] is not on a reserved Z-Level but is somehow registered as in flight! Removing shuttle object.")
+ qdel(src)
+ return TRUE
+ return FALSE
+
+/datum/orbital_object/shuttle/proc/handle_autopilot()
+ var/datum/orbital_vector/target_pos = shuttleTargetPos
+
+ if(autopilot)
+ target_pos = shuttleTarget.position
+
+ if(docking_target || !target_pos)
+ return
+
+ //Relative velocity to target needs to point towards target.
+ var/distance_to_target = position.Distance(target_pos)
+
+ //Cheat and slow down.
+ //Remove this if you make better autopilot logic ever.
+ if(distance_to_target < 100 && velocity.Length() > 25)
+ velocity.Normalize()
+ velocity.Scale(20)
+
+ //If there is an object in the way, we need to fly around it.
+ var/datum/orbital_vector/next_position = target_pos
+
+ //Adjust our speed to target to point towards it.
+ var/datum/orbital_vector/desired_velocity = new(next_position.x - position.x, next_position.y - position.y)
+ var/desired_speed = distance_to_target * 0.02 + 10
+ desired_velocity.Normalize()
+ desired_velocity.Scale(desired_speed)
+
+ //Adjust thrust to make our velocity = desired_velocity
+ var/thrust_dir_x = desired_velocity.x - velocity.x
+ var/thrust_dir_y = desired_velocity.y - velocity.y
+
+ desired_vel_x = desired_velocity.x
+ desired_vel_y = desired_velocity.y
+
+ //message_admins("Thrusting in dir: [thrust_dir_y], [thrust_dir_x]")
+ //message_admins("Next pos: [next_position.x], [next_position.y]")
+
+ if(!thrust_dir_x)
+ if(!thrust_dir_y)
+ thrust = 0
+ return
+ angle = thrust_dir_y > 0 ? 90 : -90
+ else
+ angle = arctan(thrust_dir_y / thrust_dir_x)
+ //Account for ambiguous cases
+ if(thrust_dir_x < 0)
+ if(thrust_dir_y > 0)
+ angle = -180 + angle
+ else
+ angle = 180 + angle
+
+ //message_admins("Angle: [angle]")
+
+ //FULL SPEED
+ thrust = 100
+ //Auto dock
+ if(shuttleTarget && can_dock_with == shuttleTarget)
+ commence_docking(shuttleTarget, TRUE)
+
+ //Fuck all that, we cheat anyway
+ if(cheating_autopilot)
+ velocity.x = desired_vel_x
+ velocity.y = desired_vel_y
+
+/datum/orbital_object/shuttle/proc/link_shuttle(obj/docking_port/mobile/dock)
+ name = dock.name
+ shuttle_port_id = dock.id
+ port = dock
+ stealth = dock.hidden
+ SSorbits.assoc_shuttles[shuttle_port_id] = src
+
+/datum/orbital_object/shuttle/proc/commence_docking(datum/orbital_object/z_linked/docking, forced = FALSE)
+ //Check for valid docks on z-level
+ if(!docking.forced_docking && !forced)
+ can_dock_with = docking
+ return
+ //Begin docking.
+ docking_target = docking
+ //Check for ruin stuff
+ var/datum/orbital_object/z_linked/beacon/ruin/ruin_obj = docking_target
+ if(istype(ruin_obj))
+ if(!ruin_obj.linked_z_level)
+ ruin_obj.assign_z_level()
diff --git a/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/space_station.dm b/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/space_station.dm
new file mode 100644
index 0000000000000..9de41a6c8b925
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/space_station.dm
@@ -0,0 +1,21 @@
+/datum/orbital_object/z_linked/station
+ name = "Space Station 13"
+ mass = 0
+ radius = 30
+ //The station maintains its orbit around lavaland by adjustment thrusters.
+ maintain_orbit = TRUE
+ //Sure, why not?
+ can_dock_anywhere = TRUE
+
+/datum/orbital_object/z_linked/station/New()
+ . = ..()
+ SSorbits.station_instance = src
+
+/datum/orbital_object/z_linked/station/explode()
+ . = ..()
+ SSticker.force_ending = TRUE
+
+/datum/orbital_object/z_linked/station/post_map_setup()
+ //Orbit around the system center
+ var/datum/orbital_map/linked_map = SSorbits.orbital_maps[orbital_map_index]
+ set_orbitting_around_body(linked_map.center, 2500)
diff --git a/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/star.dm b/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/star.dm
new file mode 100644
index 0000000000000..eb931382cf742
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/star.dm
@@ -0,0 +1,20 @@
+/datum/orbital_object/star
+ name = "Stellar Body"
+ mass = 100000
+ radius = 200
+ static_object = TRUE
+ collision_flags = ALL
+
+/datum/orbital_object/star/collision(datum/orbital_object/other)
+ //You got lucky this time
+ if(other.collision_ignored || collision_ignored)
+ return
+ //You didnt get lucky this time
+ message_admins("Orbital body [other.name] collided with the star [name] and was destroyed.")
+ log_game("Orbital body [other.name] collided with the star [name] and was destroyed.")
+ //Rip.
+ if(istype(other, /datum/orbital_object/star))
+ //Make a black hole or something
+ return
+ //Destroy the other orbital object.
+ other.explode()
diff --git a/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/z_linked.dm b/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/z_linked.dm
new file mode 100644
index 0000000000000..abcef1e33b074
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_map_components/orbital_objects/z_linked.dm
@@ -0,0 +1,65 @@
+/datum/orbital_object/z_linked
+ name = "Unidentified Beacon"
+ collision_type = COLLISION_Z_LINKED
+ collision_flags = COLLISION_SHUTTLES
+ //The space level(s) we are linked to
+ var/list/datum/space_level/linked_z_level
+ //If docking is forced upon collision
+ //If you hit a planet, you are going to the planet whether you like it or not.
+ var/forced_docking = FALSE
+ //Can you dock on this Z-level anywhere?
+ var/can_dock_anywhere = FALSE
+ //If we can't dock anywhere, will we just crash on the Z-level at a random position?
+ var/random_docking = FALSE
+ //Inherit the name of z-level?
+ var/inherit_name = FALSE
+ //are we generateing
+ var/is_generating = FALSE
+
+/datum/orbital_object/z_linked/proc/link_to_z(datum/space_level/level)
+ LAZYADD(linked_z_level, level)
+ if(inherit_name)
+ name = level.name
+
+/datum/orbital_object/z_linked/explode()
+ message_admins("ORBITAL BODY [name] WAS DESTROYED.")
+ log_game("Orbital body [name] was destroyed.")
+ //Holy shit this is bad.
+ for(var/mob/living/L in GLOB.mob_living_list)
+ for(var/datum/space_level/level as() in linked_z_level)
+ if(L.z == level.z_value)
+ qdel(L)
+ break
+ //Prevent access to the z-level.
+ //Announcement
+ priority_announce("The orbital body [name] has been destroyed. Transit to this location is no longer possible.", "Nanotrasen Orbital Body Division")
+ qdel(src)
+
+/datum/orbital_object/z_linked/collision(datum/orbital_object/other)
+ //Send shuttle to z-level for docking.
+ if(istype(other, /datum/orbital_object/shuttle))
+ //send them to the place
+ var/datum/orbital_object/shuttle/shuttle = other
+ //Check if shuttle can dock at this location.
+ if(!random_docking && !(can_dock_anywhere && (!GLOB.shuttle_docking_jammed || shuttle.stealth || !istype(src, /datum/orbital_object/z_linked/station))))
+ var/can_dock_here = FALSE
+ for(var/port_name in shuttle.valid_docks)
+ var/obj/docking_port/port = SSshuttle.getDock(port_name)
+ if(z_in_contents(port?.z))
+ can_dock_here = TRUE
+ break
+ if(!can_dock_here)
+ return
+ if(other.collision_ignored || collision_ignored)
+ //Collisions are currently ignored, give the ability to dock via a button and dont force it
+ shuttle.can_dock_with = src
+ return
+ shuttle.commence_docking(src)
+
+/datum/orbital_object/z_linked/proc/z_in_contents(z_value)
+ if(!LAZYLEN(linked_z_level))
+ return FALSE
+ for(var/datum/space_level/level as() in linked_z_level)
+ if(level.z_value == z_value)
+ return TRUE
+ return FALSE
diff --git a/code/modules/shuttle/super_cruise/orbital_map_components/orbital_vector.dm b/code/modules/shuttle/super_cruise/orbital_map_components/orbital_vector.dm
new file mode 100644
index 0000000000000..0278af153a8c1
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_map_components/orbital_vector.dm
@@ -0,0 +1,64 @@
+//Orbital vectors
+//I hate that some of these modify src and the others just return a valid
+// - bacon
+
+/datum/orbital_vector
+ var/x = 0
+ var/y = 0
+
+/datum/orbital_vector/New(_x = 0, _y = 0)
+ . = ..()
+ x = _x
+ y = _y
+
+//Adds 2 vectors together
+/datum/orbital_vector/proc/Add(datum/orbital_vector/other)
+ src.x += other.x
+ src.y += other.y
+ return src
+
+//Scales the vector by scalar_amount
+/datum/orbital_vector/proc/Scale(scalar_amount)
+ x *= scalar_amount
+ y *= scalar_amount
+ return src
+
+//Returns magnitude of the vector
+/datum/orbital_vector/proc/Length()
+ return sqrt(x * x + y * y)
+
+//Returns distanace between 2 positional vectors
+/datum/orbital_vector/proc/Distance(datum/orbital_vector/other)
+ var/delta_x = other.x - x
+ var/delta_y = other.y - y
+ return sqrt(delta_x * delta_x + delta_y * delta_y)
+
+//Make the vector length 1
+/datum/orbital_vector/proc/Normalize()
+ var/total = Length()
+ if(!total)
+ x = 0
+ y = 1
+ return src
+ x = x / total
+ y = y / total
+ return src
+
+/datum/orbital_vector/proc/Rotate(angle)
+ var/_x = x
+ x = x * cos(angle) - y * sin(angle)
+ y = _x * sin(angle) + y * cos(angle)
+ return src
+
+//Assuming we are a position vector
+//Takes in position and direction of a line.
+/datum/orbital_vector/proc/ShortestDistanceToLine(datum/orbital_vector/position, datum/orbital_vector/direction)
+ if(!direction.x && !direction.y)
+ return INFINITY
+ //Uhhhhhhhhhh.
+ if(!x && !y)
+ x = 1
+ y = 1
+ var/lambda = (x * x + y * y - position.x * x - position.y * y) / (direction.x * x + direction.y * y)
+ var/datum/orbital_vector/closestPoint = new(position.x + direction.x * lambda, position.y + direction.y * lambda)
+ return closestPoint.Distance(src)
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/_orbital_objective.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/_orbital_objective.dm
new file mode 100644
index 0000000000000..c509422012366
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/_orbital_objective.dm
@@ -0,0 +1,53 @@
+/datum/orbital_objective
+ var/name = "Null Objective"
+ var/datum/orbital_object/z_linked/beacon/ruin/linked_beacon
+ var/payout = 0
+ var/completed = FALSE
+ var/min_payout = 0
+ var/max_payout = 0
+ var/id = 0
+ var/station_name
+ var/static/objective_num = 0
+
+/datum/orbital_objective/New()
+ . = ..()
+ id = objective_num ++
+ station_name = new_station_name()
+
+/datum/orbital_objective/proc/on_assign(obj/machinery/computer/objective/objective_computer)
+ return
+
+/datum/orbital_objective/proc/generate_objective_stuff(turf/chosen_turf)
+ return
+
+/datum/orbital_objective/proc/check_failed()
+ return TRUE
+
+/datum/orbital_objective/proc/get_text()
+ return ""
+
+/datum/orbital_objective/proc/announce()
+ priority_announce(get_text(), "Central Command Report", SSstation.announcer.get_rand_report_sound())
+
+/datum/orbital_objective/proc/generate_payout()
+ payout = rand(min_payout, max_payout)
+
+/datum/orbital_objective/proc/generate_attached_beacon()
+ linked_beacon = new
+ linked_beacon.name = "(OBJECTIVE) [linked_beacon.name]"
+ linked_beacon.linked_objective = src
+
+/datum/orbital_objective/proc/complete_objective()
+ if(completed)
+ //Delete
+ QDEL_NULL(SSorbits.current_objective)
+ return
+ completed = TRUE
+ //Handle payout
+ SSeconomy.distribute_funds(payout)
+ GLOB.exploration_points += payout * 0.1
+ //Announcement
+ priority_announce("Central Command priority objective completed. [payout] credits have been \
+ distributed across departmental budgets. [payout * 0.1] points have been distrubted to exploration vendors.", "Central Command Report", SSstation.announcer.get_rand_report_sound())
+ //Delete
+ QDEL_NULL(SSorbits.current_objective)
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/loot/alien_artifact.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/loot/alien_artifact.dm
new file mode 100644
index 0000000000000..686d501857c89
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/loot/alien_artifact.dm
@@ -0,0 +1,524 @@
+
+/obj/item/alienartifact
+ name = "artifact"
+ desc = "A strange artifact of unknown origin."
+ icon = 'icons/obj/artifact.dmi'
+ icon_state = "artifact"
+ resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ var/list/datum/artifact_effect/effects
+
+/obj/item/alienartifact/examine(mob/user)
+ . = ..()
+ var/mob/living/L = user
+ if(istype(L) && L.mind?.assigned_role != "Curator")
+ return
+ for(var/datum/artifact_effect/effect in effects)
+ for(var/verb in effect.effect_act_descs)
+ . += "[src] likely does something when [verb]."
+
+/obj/item/alienartifact/ComponentInitialize()
+ AddComponent(/datum/component/discoverable, 10000, TRUE)
+
+/obj/item/alienartifact/objective/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/gps, "[scramble_message_replace_chars("#########", 100)]", TRUE)
+
+/obj/item/alienartifact/Initialize()
+ . = ..()
+ effects = list()
+ for(var/i in 1 to pick(1, 500; 2, 70; 3, 20; 1))
+ var/picked_type = pick(subtypesof(/datum/artifact_effect))
+ var/valid = TRUE
+ var/datum/artifact_effect/effect = new picked_type
+ for(var/datum/artifact_effect/old_effect as() in effects)
+ //Cant have the same one twice
+ if(istype(old_effect, picked_type))
+ valid = FALSE
+ qdel(effect)
+ break
+ //Cant have incompatible signals
+ for(var/signal_type in old_effect.signal_types)
+ if(signal_type in effect.signal_types)
+ valid = FALSE
+ qdel(effect)
+ break
+ if(valid)
+ effect.register_signals(src)
+ effect.Initialize(src)
+ effects += effect
+
+/obj/item/alienartifact/Destroy()
+ . = ..()
+ QDEL_LIST(effects)
+
+/area/tear_in_reality
+ name = "tear in the fabric of reality"
+ area_flags = UNIQUE_AREA | HIDDEN_AREA
+ clockwork_warp_allowed = FALSE
+ requires_power = FALSE
+ mood_bonus = -999
+ has_gravity = STANDARD_GRAVITY
+ ambience_index = AMBIENCE_NONE
+ sound_environment = SOUND_ENVIRONMENT_DRUGGED
+ teleport_restriction = TELEPORT_ALLOW_NONE
+ dynamic_lighting = DYNAMIC_LIGHTING_FORCED
+
+/area/tear_in_reality/Initialize()
+ . = ..()
+ mood_message = "[scramble_message_replace_chars("###### ### #### ###### #######", 100)]!"
+
+/area/tear_in_reality/get_virtual_z(turf/T)
+ return REALITY_TEAR_VIRTUAL_Z
+
+//===================
+// Raw artifact datum
+//===================
+
+/datum/artifact_effect
+ var/requires_processing = FALSE
+ var/effect_act_descs = list() //List of verbs for things that can be done with the artifact.
+ var/obj/item/source_object
+ var/list/signal_types = list()
+
+/datum/artifact_effect/proc/register_signals(source)
+ return
+
+/datum/artifact_effect/proc/Initialize(source)
+ source_object = source
+ if(requires_processing)
+ START_PROCESSING(SSobj, src)
+
+/datum/artifact_effect/Destroy()
+ if(requires_processing)
+ STOP_PROCESSING(SSobj, src)
+
+//===================
+// Chaos Throw
+//===================
+
+/datum/artifact_effect/throwchaos
+ signal_types = list(COMSIG_MOVABLE_PRE_THROW)
+ effect_act_descs = list("thrown")
+
+/datum/artifact_effect/throwchaos/register_signals(source)
+ RegisterSignal(source, COMSIG_MOVABLE_PRE_THROW, .proc/throw_thing_randomly)
+
+/datum/artifact_effect/throwchaos/proc/throw_thing_randomly(datum/source, list/arguments)
+ if(prob(40))
+ return
+ var/atom/new_throw_target = pick(view(5, source))
+ if(ismovable(new_throw_target))
+ arguments[1] = new_throw_target //target
+ arguments[2] = 5 //range
+ arguments[3] = 4 //speed
+
+//===================
+// Laughing
+//===================
+
+/datum/artifact_effect/soundindark
+ requires_processing = TRUE
+ effect_act_descs = list("in darkness")
+
+/datum/artifact_effect/soundindark/process(delta_time)
+ var/turf/T = get_turf(source_object)
+ if(!T || T.get_lumcount())
+ return
+ if(prob(5))
+ playsound(T, pick('sound/voice/human/womanlaugh.ogg', 'sound/voice/human/manlaugh1.ogg'), 40)
+
+//===================
+// Spasm inducing
+//===================
+
+/datum/artifact_effect/inducespasm
+ signal_types = list(COMSIG_PARENT_EXAMINE)
+ effect_act_descs = list("examined")
+
+/datum/artifact_effect/inducespasm/register_signals(source)
+ RegisterSignal(source, COMSIG_PARENT_EXAMINE, .proc/do_effect)
+
+/datum/artifact_effect/inducespasm/proc/do_effect(datum/source, mob/observer, list/examine_text)
+ if(ishuman(observer))
+ var/mob/living/carbon/human/H = observer
+ H.gain_trauma(/datum/brain_trauma/mild/muscle_spasms, TRAUMA_RESILIENCE_BASIC)
+
+//===================
+// Projectile Reflector
+//===================
+
+/atom/movable/proximity_monitor_holder
+ var/datum/proximity_monitor/monitor
+ var/datum/callback/callback
+
+/atom/movable/proximity_monitor_holder/Initialize(mapload, datum/proximity_monitor/_monitor, datum/callback/_callback)
+ monitor = _monitor
+ callback = _callback
+
+ monitor.hasprox_receiver = src
+
+/atom/movable/proximity_monitor_holder/HasProximity(atom/movable/AM)
+ return callback.Invoke(AM)
+
+/atom/movable/proximity_monitor_holder/Destroy()
+ QDEL_NULL(monitor)
+ QDEL_NULL(callback)
+ return ..()
+
+/datum/artifact_effect/projreflect
+ effect_act_descs = list("shot at")
+ var/atom/movable/proximity_monitor_holder/monitor_holder
+
+/datum/artifact_effect/projreflect/Initialize(source)
+ . = ..()
+ if(monitor_holder)
+ QDEL_NULL(monitor_holder)
+ var/datum/proximity_monitor/monitor = new(source, 3, FALSE)
+ monitor_holder = new(null, monitor, CALLBACK(src, .proc/HasProximity))
+
+/datum/artifact_effect/projreflect/Destroy()
+ QDEL_NULL(monitor_holder)
+ return ..()
+
+/datum/artifact_effect/projreflect/proc/HasProximity(atom/movable/AM)
+ if(istype(AM, /obj/item/projectile))
+ var/obj/item/projectile/P = AM
+ P.setAngle(rand(0, 360))
+ P.ignore_source_check = TRUE //Allow the projectile to hit the shooter after it gets reflected
+
+//===================
+// Air Blocker
+//===================
+
+/datum/artifact_effect/airfreeze
+ signal_types = list(COMSIG_MOVABLE_MOVED)
+ effect_act_descs = list("depressurised")
+
+/datum/artifact_effect/airfreeze/Initialize(atom/source)
+ . = ..()
+ source.CanAtmosPass = ATMOS_PASS_NO
+
+/datum/artifact_effect/airfreeze/register_signals(source)
+ RegisterSignal(source, COMSIG_MOVABLE_MOVED, .proc/updateAir)
+
+/datum/artifact_effect/airfreeze/proc/updateAir(atom/source, atom/oldLoc)
+ if(isturf(oldLoc))
+ var/turf/oldTurf = oldLoc
+ oldTurf.air_update_turf(TRUE)
+ if(isturf(source.loc))
+ var/turf/newTurf = source.loc
+ newTurf.air_update_turf(TRUE)
+
+//===================
+// Atmos Stabilizer
+//===================
+
+/datum/artifact_effect/atmosfix
+ effect_act_descs = list("depressurised")
+ requires_processing = TRUE
+
+/datum/artifact_effect/atmosfix/process(delta_time)
+ var/turf/T = get_turf(source_object)
+ var/datum/gas_mixture/air = T.return_air()
+ air.parse_gas_string(T.initial_gas_mix)
+
+//===================
+// Gravity Well
+//===================
+
+/datum/artifact_effect/gravity_well
+ effect_act_descs = list("used")
+ signal_types = list(COMSIG_ITEM_ATTACK_SELF)
+ var/next_use_world_time = 0
+
+/datum/artifact_effect/gravity_well/register_signals(source)
+ RegisterSignal(source, COMSIG_ITEM_ATTACK_SELF, .proc/suck)
+
+/datum/artifact_effect/gravity_well/proc/suck(datum/source, mob/warper)
+ if(world.time < next_use_world_time)
+ return
+ var/turf/T = get_turf(warper)
+ if(T)
+ goonchem_vortex(T, FALSE, 8)
+ playsound(source_object, 'sound/magic/repulse.ogg', 60)
+ next_use_world_time = world.time + 150
+
+//===================
+// Access Modifier
+// Just replaces access 4noraisin
+//===================
+
+/datum/artifact_effect/access
+ effect_act_descs = list("near something")
+ requires_processing = TRUE
+ var/next_use_time = 0
+
+/datum/artifact_effect/access/process(delta_time)
+ if(world.time < next_use_time)
+ return
+ next_use_time = world.time + rand(30 SECONDS, 5 MINUTES)
+ var/list/idcards = list()
+ var/list/things_in_view = view(5, source_object)
+ for(var/mob/living/carbon/human/H in things_in_view)
+ if(H.get_idcard())
+ idcards += H.get_idcard()
+ for(var/obj/item/card/id/id_card in things_in_view)
+ idcards += id_card
+ var/list/accesses_to_add = get_all_accesses()
+ for(var/obj/item/card/id/id_card as() in idcards)
+ if(length(id_card.access))
+ id_card.access.Remove(pick(id_card.access))
+ id_card.access |= pick(accesses_to_add)
+
+//===================
+// Reality Destabilizer
+//===================
+
+GLOBAL_LIST_EMPTY(destabilization_spawns)
+GLOBAL_LIST_EMPTY(destabliization_exits)
+
+/obj/effect/landmark/destabilization_loc
+ name = "destabilization spawn"
+
+/obj/effect/landmark/destabilization_loc/Initialize()
+ ..()
+ GLOB.destabilization_spawns += get_turf(src)
+ return INITIALIZE_HINT_QDEL
+
+/datum/artifact_effect/reality_destabilizer
+ requires_processing = TRUE
+ effect_act_descs = list("near something")
+ var/cooldown = 0
+ var/list/contained_things = list()
+
+/datum/artifact_effect/reality_destabilizer/Initialize(source)
+ . = ..()
+ GLOB.destabliization_exits += source
+
+/datum/artifact_effect/reality_destabilizer/Destroy()
+ for(var/atom/movable/AM as() in contained_things)
+ if(istype(get_area(AM), /area/tear_in_reality))
+ AM.forceMove(get_turf(source_object))
+ contained_things.Cut()
+ GLOB.destabliization_exits -= source_object
+ . = ..()
+
+/datum/artifact_effect/reality_destabilizer/process(delta_time)
+ if(world.time < cooldown)
+ return
+ cooldown = world.time + rand(0, 30 SECONDS)
+ var/turf/T = get_turf(source_object)
+ if(!T)
+ return
+ for(var/atom/movable/AM in view(3, T))
+ if(AM == source_object)
+ continue
+ if(isobj(AM))
+ var/obj/O = AM
+ if(O.resistance_flags & INDESTRUCTIBLE)
+ continue
+ if(AM.anchored)
+ continue
+ if(prob(3))
+ destabilize(AM)
+
+/datum/artifact_effect/reality_destabilizer/proc/destabilize(atom/movable/AM)
+ //Banish to the void
+ addtimer(CALLBACK(src, .proc/restabilize, AM, get_turf(AM)), rand(10 SECONDS, 90 SECONDS))
+ //Forcemove to ignore teleport checks
+ AM.forceMove(pick(GLOB.destabilization_spawns))
+ contained_things += AM
+
+/datum/artifact_effect/reality_destabilizer/proc/restabilize(atom/movable/AM, turf/T)
+ if(QDELETED(src))
+ return
+ if(QDELETED(AM))
+ return
+ var/area/A = get_area(AM)
+ //already left the tear.
+ if(!istype(A, /area/tear_in_reality))
+ return
+ AM.forceMove(T)
+ contained_things -= AM
+
+//===================
+// Teleport
+//===================
+
+/datum/artifact_effect/warp
+ signal_types = list(COMSIG_ITEM_ATTACK_SELF)
+ effect_act_descs = list("used")
+ var/next_use_world_time = 0
+
+/datum/artifact_effect/warp/register_signals(source)
+ RegisterSignal(source, COMSIG_ITEM_ATTACK_SELF, .proc/teleport)
+
+/datum/artifact_effect/warp/proc/teleport(datum/source, mob/warper)
+ if(world.time < next_use_world_time)
+ return
+ var/turf/T = get_turf(warper)
+ if(T)
+ do_teleport(warper, pick(RANGE_TURFS(10, T)), channel = TELEPORT_CHANNEL_FREE)
+ next_use_world_time = world.time + 150
+
+//===================
+// Curse
+//===================
+
+/datum/artifact_effect/curse
+ var/used = FALSE
+ effect_act_descs = list("picked up")
+ signal_types = list(COMSIG_ITEM_PICKUP)
+
+/datum/artifact_effect/curse/register_signals(source)
+ RegisterSignal(source, COMSIG_ITEM_PICKUP, .proc/curse)
+
+/datum/artifact_effect/curse/proc/curse(datum/source, mob/taker)
+ var/mob/living/carbon/human/H = taker
+ if(istype(H) && !used)
+ used = TRUE
+ H.gain_trauma(/datum/brain_trauma/magic/stalker, TRAUMA_LIMIT_LOBOTOMY)
+
+//===================
+// Gas ~~Remover~~ Converter
+// Probably one of the most obvious but also the most potentially dangerous.
+//===================
+
+/datum/artifact_effect/gas_remove
+ requires_processing = TRUE
+ var/static/list/valid_inputs = list(
+ /datum/gas/oxygen = 6,
+ /datum/gas/nitrogen = 3,
+ /datum/gas/plasma = 1,
+ /datum/gas/carbon_dioxide = 1,
+ /datum/gas/water_vapor = 3
+ )
+ var/static/list/valid_outputs = list(
+ /datum/gas/bz = 3,
+ /datum/gas/hypernoblium = 1,
+ /datum/gas/miasma = 3,
+ /datum/gas/plasma = 3,
+ /datum/gas/tritium = 2,
+ /datum/gas/nitryl = 1
+ )
+ var/datum/gas/input
+ var/datum/gas/output
+
+/datum/artifact_effect/gas_remove/Initialize(source)
+ . = ..()
+ input = pickweight(valid_inputs)
+ effect_act_descs = list("near gas")
+ output = pickweight(valid_outputs)
+
+/datum/artifact_effect/gas_remove/process(delta_time)
+ var/turf/T = get_turf(source_object)
+ var/datum/gas_mixture/air = T.return_air()
+ var/input_id = initial(input.id)
+ var/output_id = initial(output.id)
+ var/moles = min(air.get_moles(input_id), 5)
+ if(moles)
+ air.adjust_moles(input_id, -moles)
+ air.adjust_moles(output_id, moles)
+
+//===================
+// Recharger
+//===================
+
+/datum/artifact_effect/recharger
+ effect_act_descs = list("near something")
+ requires_processing = TRUE
+
+/datum/artifact_effect/recharger/process(delta_time)
+ var/turf/T = get_turf(source_object)
+ if(!T)
+ return
+ for(var/atom/movable/thing in view(3, T))
+ var/obj/item/stock_parts/cell/C = thing.get_cell()
+ if(C)
+ C.give(250 * delta_time)
+ thing.update_icon()
+
+//===================
+// Light Breaker
+//===================
+
+/datum/artifact_effect/light_breaker
+ requires_processing = TRUE
+ effect_act_descs = list("near something")
+ var/next_world_time
+
+/datum/artifact_effect/light_breaker/process(delta_time)
+ if(world.time < next_world_time)
+ return
+ var/turf/T = get_turf(source_object)
+ for(var/datum/light_source/light_source in T.affecting_lights)
+ var/atom/movable/AM = light_source.source_atom
+ //Starts at light but gets stronger the longer it is in light.
+ AM.lighteater_act()
+ next_world_time = world.time + rand(30 SECONDS, 5 MINUTES)
+
+//===================
+// Insanity Pulse
+//===================
+
+/datum/artifact_effect/insanity_pulse
+ var/next_use_time = 0
+ var/cooldown
+ var/first_time = TRUE
+ signal_types = list(COMSIG_ITEM_ATTACK_SELF)
+ effect_act_descs = list("used")
+
+/datum/artifact_effect/insanity_pulse/Initialize(source)
+ . = ..()
+ cooldown = rand(5 MINUTES, 15 MINUTES)
+
+/datum/artifact_effect/insanity_pulse/register_signals(source)
+ RegisterSignal(source, COMSIG_ITEM_ATTACK_SELF, .proc/pulse)
+
+/datum/artifact_effect/insanity_pulse/proc/pulse(datum/source, mob/living/pulser)
+ if(!istype(pulser))
+ return
+ if(world.time < next_use_time)
+ return
+ SEND_SOUND(world, 'sound/magic/repulse.ogg')
+ next_use_time = world.time + cooldown
+ var/turf/T = get_turf(pulser)
+ log_attack("[key_name_admin(pulser)] activated an insanity pulse at [COORD(T)]. [first_time ? " (Effects were unknown)" : " (Artifact had been activated before)"]")
+ message_admins("[ADMIN_LOOKUPFLW(pulser)] activated an insanity pulse [first_time ? " (Effects were unknown)" : " (Artifact had been activated before)"].")
+ if(first_time)
+ var/research_reward = rand(5000, 20000)
+ priority_announce("Spacetime anomaly detected at [T.loc]. Data analysis completed, [research_reward] research points rewarded.", "Nanotrasen Research Division", ANNOUNCER_SPANOMALIES)
+ SSresearch.science_tech.add_points_all(research_reward)
+ first_time = FALSE
+ var/xrange = 50
+ var/yrange = 50
+ var/cx = T.x
+ var/cy = T.y
+ pulser.blind_eyes(300)
+ pulser.Stun(100)
+ pulser.emote("scream")
+ pulser.hallucination = 500
+ for(var/r in 1 to max(xrange, yrange))
+ var/xr = min(xrange, r)
+ var/yr = min(yrange, r)
+ var/turf/TL = locate(cx - xr, cy + yr, T.z)
+ var/turf/BL = locate(cx - xr, cy - yr, T.z)
+ var/turf/TR = locate(cx + xr, cy + yr, T.z)
+ var/turf/BR = locate(cx + xr, cy - yr, T.z)
+ var/list/turfs = list()
+ turfs += block(TL, TR)
+ turfs += block(TL, BL)
+ turfs |= block(BL, BR)
+ turfs |= block(BR, TR)
+ for(var/turf/T1 as() in turfs)
+ new /obj/effect/temp_visual/mining_scanner(T1)
+ var/mob/living/M = locate() in T1
+ if(M)
+ to_chat(M, "A wave of dread washes over you...")
+ M.blind_eyes(30)
+ M.Knockdown(10)
+ M.emote("scream")
+ M.Jitter(50)
+ M.hallucination = M.hallucination + 20
+ CHECK_TICK
+ sleep(2)
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/loot/artifact_defenses.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/loot/artifact_defenses.dm
new file mode 100644
index 0000000000000..3a3e51d37a80d
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/loot/artifact_defenses.dm
@@ -0,0 +1,97 @@
+/obj/structure/alien_artifact
+ name = "alien artifact structure"
+ icon = 'icons/obj/artifact.dmi'
+ max_integrity = 200
+ resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ anchored = TRUE
+
+/obj/structure/alien_artifact/ComponentInitialize()
+ AddComponent(/datum/component/discoverable, 20000)
+
+//Watcher
+//Triggers nearby defenses when motion is detected
+/obj/structure/alien_artifact/watcher
+ name = "watcher"
+ desc = "It sends a shiver down your spine."
+ icon_state = "watcher"
+ var/cooldown = 0
+
+/obj/structure/alien_artifact/watcher/Initialize()
+ . = ..()
+ proximity_monitor = new(src, rand(3, 6))
+ var/turf/T = get_turf(src)
+ var/list/turfs = RANGE_TURFS(5, T)
+ var/list/valid_turfs = list()
+ for(var/turf/open/floor/F in turfs)
+ if(locate(/obj/structure) in F)
+ continue
+ valid_turfs += F
+ //Shuffle the list
+ shuffle_inplace(valid_turfs)
+ new /obj/structure/alien_artifact/protector(valid_turfs[1])
+
+/obj/structure/alien_artifact/watcher/HasProximity(atom/movable/AM)
+ if(cooldown > world.time)
+ return
+ if (istype(AM, /obj/effect))
+ return
+ cooldown = world.time + 50
+ //Trigger nearby protectors
+ for(var/obj/structure/alien_artifact/protector/protector in view(6, src))
+ protector.trigger(AM)
+
+//Protectors
+/obj/structure/alien_artifact/protector
+ name = "protector"
+ desc = "A strange artifact developed centuries ago by beings that are now beyond us."
+ icon_state = "protector"
+ max_integrity = 500
+ var/active = FALSE
+ var/datum/protector_effect/effect
+
+/obj/structure/alien_artifact/protector/Initialize()
+ . = ..()
+ var/effect_type = pick(subtypesof(/datum/protector_effect))
+ effect = new effect_type()
+
+/obj/structure/alien_artifact/protector/proc/trigger(atom/movable/target)
+ if(active)
+ return
+ active = TRUE
+ flick("protector_pulse", src)
+ sleep(7.2)
+ effect.trigger(src, get_turf(src), target)
+ sleep(3.6)
+ active = FALSE
+
+//Protector effects
+
+/datum/protector_effect/proc/trigger(obj/source, turf/T, atom/movable/target)
+ return
+
+/datum/protector_effect/hierophant_chasers/trigger(obj/source, turf/T, atom/movable/target)
+ playsound(T,'sound/machines/airlockopen.ogg', 200, 1)
+ source.visible_message("\"Mx gerrsx lmhi.\"")
+ var/obj/effect/temp_visual/hierophant/chaser/C = new(T, source, target, 3, FALSE)
+ C.moving = 3
+ C.moving_dir = pick(GLOB.cardinals)
+ C.damage = 20
+
+/datum/protector_effect/hierophant_burst/trigger(obj/source, turf/T, atom/movable/target)
+ playsound(T,'sound/machines/airlockopen.ogg', 200, 1)
+ source.visible_message("\"Irkekmrk hijirwmzi tvsxsgspw.\"")
+ hierophant_burst(null, get_turf(target), 4)
+
+/datum/protector_effect/hierophant_burst_self/trigger(obj/source, turf/T, atom/movable/target)
+ playsound(T,'sound/machines/airlockopen.ogg', 200, 1)
+ source.visible_message("\"Yrorsar irxmxc hixigxih.\"")
+ hierophant_burst(null, T, 7)
+
+/datum/protector_effect/emp_stun/trigger(obj/source, turf/T, atom/movable/target)
+ playsound(TAIL_SWEEP_COMBO,'sound/machines/airlockopen.ogg', 200, 1)
+ T.visible_message("\"Svhivw vigmizih.\"")
+ empulse(T, 2, 6)
+ if(isliving(target))
+ var/mob/living/L = target
+ L.Paralyze(50)
+ L.take_overall_damage(burn=10, stamina=30)
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/loot/research_disks.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/loot/research_disks.dm
new file mode 100644
index 0000000000000..85620b76f1039
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/loot/research_disks.dm
@@ -0,0 +1,58 @@
+/obj/item/disk/tech_disk/research
+ desc = "A research disk that will unlock a research node when uploaded into a research console."
+ var/node_id
+
+/obj/item/disk/tech_disk/research/Initialize()
+ . = ..()
+ SSorbits.research_disks += src
+ if(node_id)
+ stored_research.hidden_nodes[node_id] = FALSE
+ var/datum/techweb_node/node = SSresearch.techweb_node_by_id(node_id)
+ name = "research disk ([node.display_name])"
+
+/obj/item/disk/tech_disk/research/Destroy()
+ SSorbits.research_disks -= src
+ . = ..()
+
+/obj/item/disk/tech_disk/research/random/Initialize()
+ var/list/valid_nodes = list()
+ for(var/obj/item/disk/tech_disk/research/disk as() in subtypesof(/obj/item/disk/tech_disk/research))
+ if(!initial(disk.node_id))
+ continue
+ if(!SSresearch.science_tech.isNodeResearchedID(initial(disk.node_id)))
+ valid_nodes += initial(disk.node_id)
+ if(!length(valid_nodes))
+ new /obj/effect/spawner/lootdrop/ruinloot/basic(get_turf(src))
+ return INITIALIZE_HINT_QDEL
+ node_id = pick(valid_nodes)
+ . = ..()
+
+/obj/item/disk/tech_disk/research/boh
+ node_id = "bagofholding"
+
+/obj/item/disk/tech_disk/research/wormhole_gun
+ node_id = "wormholegun"
+
+/obj/item/disk/tech_disk/research/swapper
+ node_id = "qswapper"
+
+/obj/item/disk/tech_disk/research/adv_combat_implants
+ node_id = "adv_combat_cyber_implants"
+
+/obj/item/disk/tech_disk/research/combat_implants
+ node_id = "combat_cyber_implants"
+
+/obj/item/disk/tech_disk/research/radioactive_weapons
+ node_id = "radioactive_weapons"
+
+/obj/item/disk/tech_disk/research/beam_weapons
+ node_id = "beam_weapons"
+
+/obj/item/disk/tech_disk/research/adv_beam_weapons
+ node_id = "adv_beam_weapons"
+
+/obj/item/disk/tech_disk/research/exotic_ammo
+ node_id = "exotic_ammo"
+
+/obj/item/disk/tech_disk/research/phazon
+ node_id = "mecha_phazon"
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/loot/vortex_rifle.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/loot/vortex_rifle.dm
new file mode 100644
index 0000000000000..2637536578d0f
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/loot/vortex_rifle.dm
@@ -0,0 +1,6 @@
+/obj/item/gun/energy/vortex
+ name = "vortex rifle"
+ desc = "A powerful rifle of alien origin that fires powerful energy darts."
+ icon_state = "vortex"
+ item_state = null
+ ammo_type = list(/obj/item/ammo_casing/energy/vortex)
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/objective_computer.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/objective_computer.dm
new file mode 100644
index 0000000000000..b26b920412ad2
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/objective_computer.dm
@@ -0,0 +1,64 @@
+GLOBAL_LIST_EMPTY(objective_computers)
+
+/obj/machinery/computer/objective
+ name = "station objective console"
+ desc = "A networked console that downloads and displays currently assigned station objectives."
+ icon_screen = "bounty"
+ icon_keyboard = "tech_key"
+ light_color = LIGHT_COLOR_ORANGE
+ req_access = list( )
+ circuit = /obj/item/circuitboard/computer/objective
+ var/list/viewing_mobs = list()
+
+/obj/machinery/computer/objective/Initialize(mapload, obj/item/circuitboard/C)
+ . = ..()
+ GLOB.objective_computers += src
+
+/obj/machinery/computer/objective/Destroy()
+ GLOB.objective_computers -= src
+ . = ..()
+
+/obj/machinery/computer/objective/ui_state(mob/user)
+ return GLOB.default_state
+
+/obj/machinery/computer/objective/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Objective")
+ ui.open()
+ viewing_mobs += user
+
+/obj/machinery/computer/objective/ui_close(mob/user, datum/tgui/tgui)
+ viewing_mobs -= user
+
+/obj/machinery/computer/objective/ui_static_data(mob/user)
+ var/list/data = list()
+ data["possible_objectives"] = list()
+ for(var/datum/orbital_objective/objective in SSorbits.possible_objectives)
+ data["possible_objectives"] += list(list(
+ "name" = objective.name,
+ "id" = objective.id,
+ "payout" = objective.payout,
+ "description" = objective.get_text()
+ ))
+ data["selected_objective"] = null
+ if(SSorbits.current_objective)
+ data["selected_objective"] = list(
+ "name" = SSorbits.current_objective.name,
+ "id" = SSorbits.current_objective.id,
+ "payout" = SSorbits.current_objective.payout,
+ "description" = SSorbits.current_objective.get_text()
+ )
+ return data
+
+/obj/machinery/computer/objective/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+ if(action != "assign")
+ return
+ var/obj_id = params["id"]
+ for(var/datum/orbital_objective/objective in SSorbits.possible_objectives)
+ if(objective.id == obj_id)
+ say(SSorbits.assign_objective(src, objective))
+ return
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/objective_types/alien_artifact.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/objective_types/alien_artifact.dm
new file mode 100644
index 0000000000000..4d8dbf5646d1e
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/objective_types/alien_artifact.dm
@@ -0,0 +1,41 @@
+/datum/orbital_objective/artifact
+ name = "Artifact Recovery"
+ var/generated = FALSE
+ //The blackbox required to recover.
+ var/obj/item/alienartifact/objective/linked_artifact
+ min_payout = 50000
+ max_payout = 200000
+
+/datum/orbital_objective/artifact/generate_objective_stuff(turf/chosen_turf)
+ generated = TRUE
+ linked_artifact = new(chosen_turf)
+ var/list/turfs = RANGE_TURFS(30, chosen_turf)
+ var/list/valid_turfs = list()
+ for(var/turf/open/floor/F in turfs)
+ if(locate(/obj/structure) in F)
+ continue
+ valid_turfs += F
+ //Shuffle the list
+ shuffle_inplace(valid_turfs)
+ for(var/i in rand(6, 15))
+ if(valid_turfs.len < i)
+ message_admins("Ran out of valid turfs to create artifact defenses on.")
+ return
+ var/turf/selected_turf = valid_turfs[i]
+ new /obj/structure/alien_artifact/watcher(selected_turf)
+
+/datum/orbital_objective/artifact/get_text()
+ . = "Outpost [station_name] is a research outpost with an extremely powerful alien artifact on board. \
+ Recover the unknown artifact for a payout of [payout] credits."
+ if(linked_beacon)
+ . += " The station is located at the beacon marked [linked_beacon.name]. Good luck."
+
+/datum/orbital_objective/artifact/check_failed()
+ if(!generated)
+ return FALSE
+ if(is_station_level(linked_artifact.z))
+ complete_objective()
+ return FALSE
+ if(!QDELETED(linked_artifact))
+ return FALSE
+ return TRUE
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/objective_types/nuke_ruin.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/objective_types/nuke_ruin.dm
new file mode 100644
index 0000000000000..10ce06c533077
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/objective_types/nuke_ruin.dm
@@ -0,0 +1,128 @@
+/datum/orbital_objective/nuclear_bomb
+ name = "Nuclear Decomission"
+ var/generated = FALSE
+ //The blackbox required to recover.
+ var/obj/machinery/nuclearbomb/decomission/nuclear_bomb
+ var/obj/item/disk/nuclear/decommission/nuclear_disk
+ //Relatively easy mission.
+ min_payout = 80000
+ max_payout = 400000
+
+/datum/orbital_objective/nuclear_bomb/generate_objective_stuff(turf/chosen_turf)
+ generated = TRUE
+ nuclear_disk = new(chosen_turf)
+ nuclear_bomb.target_z = chosen_turf.z
+ nuclear_bomb.linked_objective = src
+
+/datum/orbital_objective/nuclear_bomb/get_text()
+ . = "Outpost [station_name] requires immediate decomissioning to prevent infomation from being \
+ leaked to the space press. Retrieve the nuclear authentication disk from the outpost and detonate it \
+ with the provided nuclear bomb which will be delivered to the bridge."
+ if(linked_beacon)
+ . += " The station is located at the beacon marked [linked_beacon.name]. Good luck."
+
+/datum/orbital_objective/nuclear_bomb/on_assign(obj/machinery/computer/objective/objective_computer)
+ var/area/A = GLOB.areas_by_type[/area/bridge]
+ var/turf/open/T = locate() in shuffle(A.contents)
+ nuclear_bomb = new /obj/machinery/nuclearbomb/decomission(T)
+
+/datum/orbital_objective/nuclear_bomb/check_failed()
+ if((!QDELETED(nuclear_bomb) && !QDELETED(nuclear_disk) && !QDELETED(linked_beacon)) || !generated)
+ return FALSE
+ return TRUE
+
+//==============
+//The disk
+//==============
+
+/obj/item/disk/nuclear/decommission
+ name = "outdated nuclear authentication disk"
+ desc = "An old, worn nuclear authentication disk used in the outdated X-7 nuclear fission explosive. Nanotrasen no longer uses this model of authentication due to its poor security."
+ fake = TRUE
+
+/obj/item/disk/nuclear/decommission/ComponentInitialize()
+ AddComponent(/datum/component/gps, "AUTH0", TRUE)
+
+//==============
+//The bomb
+//==============
+
+GLOBAL_LIST_EMPTY(decomission_bombs)
+
+/obj/machinery/nuclearbomb/decomission
+ desc = "A nuclear bomb for destroying stations. Uses an old version of the nuclear authentication disk."
+ proper_bomb = FALSE
+ var/datum/orbital_objective/nuclear_bomb/linked_objective
+ var/target_z
+
+/obj/machinery/nuclearbomb/decomission/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/gps, "BOMB0", TRUE)
+
+/obj/machinery/nuclearbomb/decomission/Initialize()
+ . = ..()
+ GLOB.decomission_bombs += src
+ r_code = "[rand(10000, 99999)]"
+ print_command_report("Nuclear decomission explosive code: [r_code]")
+ var/obj/structure/closet/supplypod/bluespacepod/pod = new()
+ pod.explosionSize = list(0,0,0,4)
+ new /obj/effect/pod_landingzone(get_turf(src), pod)
+ forceMove(pod)
+
+/obj/machinery/nuclearbomb/decomission/Destroy()
+ . = ..()
+ GLOB.decomission_bombs -= src
+
+/obj/machinery/nuclearbomb/decomission/process()
+ if(z != target_z)
+ timing = FALSE
+ detonation_timer = null
+ countdown?.stop()
+ update_icon()
+ return
+ . = ..()
+
+/obj/machinery/nuclearbomb/decomission/disk_check(obj/item/disk/nuclear/D)
+ if(istype(D, /obj/item/disk/nuclear/decommission))
+ return TRUE
+ return FALSE
+
+/obj/machinery/nuclearbomb/decomission/set_safety()
+ safety = !safety
+ if(safety)
+ timing = FALSE
+ detonation_timer = null
+ countdown.stop()
+ update_icon()
+
+/obj/machinery/nuclearbomb/decomission/set_active()
+ if(safety)
+ to_chat(usr, "The safety is still on.")
+ return
+ timing = !timing
+ if(timing)
+ detonation_timer = world.time + (timer_set * 10)
+ countdown.start()
+ priority_announce("Nuclear fission explosive armed at abandoned outpost, vacate \
+ outpost immediately.",
+ null, 'sound/misc/notice1.ogg', "Priority")
+ else
+ detonation_timer = null
+ countdown.stop()
+ update_icon()
+
+/obj/machinery/nuclearbomb/decomission/explode()
+ if(z != target_z)
+ timing = FALSE
+ detonation_timer = null
+ countdown?.stop()
+ update_icon()
+ return
+ . = ..()
+
+/obj/machinery/nuclearbomb/decomission/actually_explode()
+ SSticker.roundend_check_paused = FALSE
+ linked_objective.complete_objective()
+ INVOKE_ASYNC(GLOBAL_PROC,.proc/KillEveryoneOnZLevel, target_z)
+ QDEL_NULL(linked_objective.linked_beacon)
+ qdel(src)
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/objective_types/recover_blackbox.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/objective_types/recover_blackbox.dm
new file mode 100644
index 0000000000000..4c659794b4661
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/objective_types/recover_blackbox.dm
@@ -0,0 +1,113 @@
+/datum/orbital_objective/recover_blackbox
+ name = "Blackbox Recovery"
+ var/generated = FALSE
+ //The blackbox required to recover.
+ var/obj/item/blackbox/objective/linked_blackbox
+ //Relatively easy mission.
+ min_payout = 50000 //10k credits for sci/sec/eng, 5k for ser / civ
+ max_payout = 100000 //20k credits for sci/sec/eng, 10k for serv / civ
+
+/datum/orbital_objective/recover_blackbox/generate_objective_stuff(turf/chosen_turf)
+ generated = TRUE
+ linked_blackbox = new(chosen_turf)
+ linked_blackbox.setup_recover(src)
+
+/datum/orbital_objective/recover_blackbox/get_text()
+ . = "Outpost [station_name] recently went dark and is no longer responding to our attempts \
+ to contact them. Send in a team and recover the station's blackbox for a payout of [payout] credits."
+ if(linked_beacon)
+ . += " The station is located at the beacon marked [linked_beacon.name]. Good luck."
+
+/datum/orbital_objective/recover_blackbox/check_failed()
+ if(!QDELETED(linked_blackbox) || !generated)
+ return FALSE
+ return TRUE
+
+/*
+ * Blackbox Item: Objective target, handles completion
+ * Traitors can steal the Nanotrasen blackbox to prevent the station
+ * from completing their objective and recover invaluable data.
+ */
+/obj/item/blackbox/objective
+ name = "damaged blackbox"
+ w_class = WEIGHT_CLASS_BULKY
+ resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
+
+/obj/item/blackbox/objective/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/gps, "BLACKBOX #[rand(1000, 9999)]", TRUE)
+
+/obj/item/blackbox/objective/proc/setup_recover(linked_mission)
+ AddComponent(/datum/component/recoverable, linked_mission)
+
+/obj/item/blackbox/objective/examine(mob/user)
+ . = ..()
+ . += "Use in hand on the bridge of the station to send it to Nanotrasen and complete the objective."
+
+/datum/component/recoverable
+ var/recovered = FALSE
+ var/datum/orbital_objective/recover_blackbox/linked_obj
+
+/datum/component/recoverable/Initialize(_linked_obj)
+ if(!ismovableatom(parent))
+ return COMPONENT_INCOMPATIBLE
+ linked_obj = _linked_obj
+ RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/attack_self)
+
+/datum/component/recoverable/proc/attack_self(mob/user)
+ var/atom/movable/pA = parent
+ var/turf/T = get_turf(parent)
+ var/area/A = T.loc
+ if(istype(A, /area/bridge) && is_station_level(T.z))
+ initiate_recovery()
+ else
+ pA.say("Blackbox must be recovered at the station's bridge.")
+
+/datum/component/recoverable/proc/initiate_recovery()
+ var/atom/movable/parentobj = parent
+ if(recovered)
+ return
+ recovered = TRUE
+ //Prevent picking up
+ parentobj.anchored = TRUE
+ //Drop to ground
+ parentobj.forceMove(get_turf(parent))
+ //Complete objective
+ if(linked_obj)
+ linked_obj.complete_objective()
+ else
+ parentobj.say("Non-priority item recovered, dispensing 2000 credit reward.")
+ new /obj/item/stack/spacecash/c1000(get_turf(parent), 2)
+ //Fly away
+ var/mutable_appearance/balloon
+ var/mutable_appearance/balloon2
+ var/obj/effect/extraction_holder/holder_obj = new(parentobj.loc)
+ holder_obj.appearance = parentobj.appearance
+ parentobj.forceMove(holder_obj)
+ balloon2 = mutable_appearance('icons/obj/fulton_balloon.dmi', "fulton_expand")
+ balloon2.pixel_y = 10
+ balloon2.appearance_flags = RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM
+ holder_obj.add_overlay(balloon2)
+ sleep(4)
+ balloon = mutable_appearance('icons/obj/fulton_balloon.dmi', "fulton_balloon")
+ balloon.pixel_y = 10
+ balloon.appearance_flags = RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM
+ holder_obj.cut_overlay(balloon2)
+ holder_obj.add_overlay(balloon)
+ playsound(holder_obj.loc, 'sound/items/fultext_deploy.ogg', 50, 1, -3)
+ animate(holder_obj, pixel_z = 10, time = 20)
+ sleep(20)
+ animate(holder_obj, pixel_z = 15, time = 10)
+ sleep(10)
+ animate(holder_obj, pixel_z = 10, time = 10)
+ sleep(10)
+ animate(holder_obj, pixel_z = 15, time = 10)
+ sleep(10)
+ animate(holder_obj, pixel_z = 10, time = 10)
+ sleep(10)
+ playsound(holder_obj.loc, 'sound/items/fultext_launch.ogg', 50, 1, -3)
+ animate(holder_obj, pixel_z = 1000, time = 30)
+ sleep(30)
+ qdel(parent)
+ qdel(holder_obj)
+ qdel(src)
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/objective_types/vip_extraction.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/objective_types/vip_extraction.dm
new file mode 100644
index 0000000000000..527f2179968e1
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/objective_types/vip_extraction.dm
@@ -0,0 +1,137 @@
+/datum/orbital_objective/vip_recovery
+ name = "VIP Recovery"
+ var/generated = FALSE
+ var/mob/mob_to_recover
+ min_payout = 100000
+ max_payout = 200000
+
+/datum/orbital_objective/vip_recovery/get_text()
+ return "Someone of particular interest to us is located at [station_name]. We require them to be extracted immediately. \
+ We have good intel to suggest that the VIP is still alive, however if not their personal diary-disk should have enough infomation \
+ about what we are looking for. An additional point to note is that it is recommended a security team assists in this mission due \
+ to the potentially hostile nature of the individual. Return the individual to the station alive to complete the objective."
+
+//If nobody takes up the ghost role, then we dont care if they died.
+//I know, its a bit sad.
+/datum/orbital_objective/vip_recovery/check_failed()
+ if(generated)
+ //Deleted
+ if(QDELETED(mob_to_recover))
+ return TRUE
+ //Left behind
+ if(mob_to_recover in SSzclear.nullspaced_mobs)
+ return TRUE
+ //Recovered and alive
+ if(is_station_level(mob_to_recover.z) && mob_to_recover.stat == CONSCIOUS)
+ complete_objective()
+ return FALSE
+
+/datum/orbital_objective/vip_recovery/generate_objective_stuff(turf/chosen_turf)
+ var/mob/living/carbon/human/created_human = new(chosen_turf)
+ //Maybe polling ghosts would be better than the shintience code
+ created_human.set_playable()
+ created_human.mind_initialize()
+ //Remove nearby dangers
+ for(var/mob/living/simple_animal/hostile/SA in range(10, created_human))
+ qdel(SA)
+ //Give them a space worthy suit
+ var/turf/open/T = locate() in shuffle(view(1, created_human))
+ if(T)
+ new /obj/item/clothing/suit/space/hardsuit/ancient(T)
+ new /obj/item/tank/internals/oxygen(T)
+ new /obj/item/clothing/mask/gas(T)
+ new /obj/item/storage/belt/utility/full(T)
+ var/antag_elligable = FALSE
+ switch(pickweight(list("centcom_official" = 4, "dictator" = 1, "greytide" = 3)))
+ if("centcom_official")
+ created_human.flavor_text = "You are a CentCom official onboard a badly damaged station. Making your way back to Space Station 13 to uncover the secrets you hold is \
+ your top priority as far as Nanotrasen is concerned, but surviving just one more day is all you can ask for."
+ created_human.equipOutfit(/datum/outfit/centcom_official_vip)
+ antag_elligable = TRUE
+ if("dictator")
+ created_human.flavor_text = "It has been months since your regime fell. Once a hero, you're now just someone wishing that they will see the next sunrise. You know those \
+ Nanotrasen pigs are after you, and will stop at nothing to capture you. All you want at this point is to get out and survive, however it is likely you will never leave \
+ without being captured."
+ created_human.equipOutfit(/datum/outfit/vip_dictator)
+ created_human.mind.add_antag_datum(/datum/antagonist/vip_dictator)
+ if("greytide")
+ created_human.flavor_text = "You are just an assistant on a lonely derelict station. You dream of going home, \
+ but it would take another one of the miracles that kept you alive to get you home."
+ created_human.equipOutfit(/datum/outfit/greytide)
+ antag_elligable = TRUE
+ created_human.mind.store_memory(created_human.flavor_text)
+ if(antag_elligable)
+ if(prob(7))
+ created_human.mind.make_Traitor()
+ else if(prob(8))
+ created_human.mind.make_Changeling()
+ mob_to_recover = created_human
+ generated = TRUE
+
+
+//=====================
+// Centcom Official
+//=====================
+
+/datum/outfit/centcom_official_vip
+ name = "Centcom VIP"
+
+ uniform = /obj/item/clothing/under/rank/centcom/officer
+ shoes = /obj/item/clothing/shoes/sneakers/black
+ gloves = /obj/item/clothing/gloves/color/black
+ ears = /obj/item/radio/headset/headset_cent/empty
+ glasses = /obj/item/clothing/glasses/sunglasses/advanced
+ belt = /obj/item/gun/energy/e_gun
+ l_pocket = /obj/item/pen
+ back = /obj/item/storage/backpack/satchel
+ r_pocket = /obj/item/pda/heads
+ l_hand = /obj/item/clipboard
+ r_hand = /obj/item/gps
+ id = /obj/item/card/id/away/old
+
+//=====================
+// Matryr Dictator
+//=====================
+
+/datum/antagonist/vip_dictator
+ name = "Insane VIP"
+ show_in_antagpanel = TRUE
+ roundend_category = "Ruin VIPs"
+ antagpanel_category = "Other"
+
+/datum/outfit/vip_dictator
+ name = "Dictator VIP"
+
+ uniform = /obj/item/clothing/under/rank/security/head_of_security/white
+ suit = /obj/item/clothing/suit/armor/hos
+ suit_store = /obj/item/gun/ballistic/automatic/pistol/m1911
+ shoes = /obj/item/clothing/shoes/jackboots
+ gloves = /obj/item/clothing/gloves/combat
+ ears = /obj/item/radio/headset
+ glasses = /obj/item/clothing/glasses/hud/security/sunglasses/eyepatch
+ belt = /obj/item/storage/belt/sabre
+ l_pocket = /obj/item/ammo_box/magazine/m45
+ r_pocket = /obj/item/grenade/smokebomb
+ id = /obj/item/card/id/away/old
+ neck = /obj/item/clothing/neck/crucifix
+ head = /obj/item/clothing/head/HoS/beret/syndicate
+ r_hand = /obj/item/gps
+
+//=====================
+// Greytide
+//=====================
+
+/datum/outfit/greytide
+ name = "Greytide"
+
+ uniform = /obj/item/clothing/under/color/random
+ suit = /obj/item/clothing/suit/armor/vest
+ shoes = /obj/item/clothing/shoes/laceup
+ gloves = /obj/item/clothing/gloves/color/yellow
+ ears = /obj/item/radio/headset
+ glasses = /obj/item/clothing/glasses/sunglasses/advanced
+ belt = /obj/item/storage/belt/utility/full/engi
+ id = /obj/item/card/id
+ head = /obj/item/clothing/head/helmet
+ l_hand = /obj/item/melee/baton/loaded
+ r_hand = /obj/item/gps
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/asteroid_generator.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/asteroid_generator.dm
new file mode 100644
index 0000000000000..93940e6b47640
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/asteroid_generator.dm
@@ -0,0 +1,90 @@
+//Generates an asteroid around a point with max radius r
+//weight_offset - Affects the probability of a rock spawning (between -1 and 1)
+//if this number is negative, asteroids will be smaller.
+//if this number is positive asteroids will be larger and more likely
+/proc/generate_asteroids(center_x, center_y, center_z, max_radius, weight_offset = 0, scale = 65)
+ var/datum/space_level/space_level = SSmapping.get_level(center_z)
+ space_level.generating = TRUE
+ try
+ _generate_asteroids(center_x, center_y, center_z, max_radius, weight_offset, scale)
+ catch(var/exception/e)
+ message_admins("Asteroid failed to generate!")
+ stack_trace("Asteroid failed to generate! [e] on [e.file]:[e.line]")
+ space_level.generating = FALSE
+
+/proc/_generate_asteroids(center_x, center_y, center_z, max_radius, weight_offset = 0, scale = 65)
+
+ SSair.pause_z(center_z)
+
+ var/perlin_noise_scale = scale
+ var/seed = rand(0, 999999)
+ var/turf/z_center = locate(center_x, center_y, center_z)
+ var/list/high_value_turfs = list()
+ var/generated_string = rustg_cnoise_generate("45", "20", "4", "3", "[world.maxx]", "[world.maxy]") //Generate the raw CA data
+
+ var/static/area/asteroid_area = new /area/asteroid/generated()
+
+ for(var/turf/open/space/T in block(locate(1, 1, center_z), locate(world.maxx, world.maxy, center_z)))
+ if(!T)
+ continue
+ //Calculate distance to edge
+ var/distance = z_center.Distance(T)
+ if(distance > max_radius)
+ continue
+ //Change area
+ asteroid_area.contents += T
+ T.change_area(T.loc, asteroid_area)
+ //Check if we are closed or not (Cave generation)
+ var/closed = text2num(generated_string[world.maxx * (T.y - 1) + T.x])
+ var/noise_at_coord = text2num(rustg_noise_get_at_coordinates("[seed]", "[T.x / perlin_noise_scale]", "[T.y / perlin_noise_scale]"))
+ var/plant_value = (distance / max_radius) + weight_offset + 0.3
+ var/rock_value = (distance / max_radius) + weight_offset + 0.1
+ var/sand_value = (distance / max_radius) + weight_offset
+ if(noise_at_coord >= rock_value && closed)
+ T.ChangeTurf(/turf/closed/mineral/random, list(/turf/open/floor/plating/asteroid/airless), CHANGETURF_IGNORE_AIR)
+ else if(noise_at_coord >= sand_value)
+ var/turf/newT = T.ChangeTurf(/turf/open/floor/plating/asteroid/airless, flags = CHANGETURF_IGNORE_AIR)
+ if(noise_at_coord >= plant_value)
+ high_value_turfs += newT
+ //Cave plants
+ if(prob(max(30 - (plant_value * 30), 0)))
+ var/plant_type = pick(/obj/structure/flora/rock/pile, /obj/structure/flora/ausbushes/brflowers,
+ /obj/structure/flora/ausbushes/fullgrass, /obj/structure/flora/ausbushes/ppflowers,
+ /obj/structure/flora/ausbushes/sparsegrass, /obj/structure/flora/ausbushes/ywflowers,
+ /obj/structure/flora/bush, /obj/structure/flora/junglebush, /obj/structure/flora/junglebush/b,
+ /obj/structure/flora/junglebush/c, /obj/structure/glowshroom/glowcap, /obj/structure/flora/ash/cap_shroom,
+ /obj/structure/flora/ash/stem_shroom, /obj/structure/flora/ash/cacti)
+ new plant_type(T)
+ CHECK_TICK
+ //Spawn tendrils and other cave stuff
+ for(var/i in 1 to min(length(high_value_turfs), rand(0, 3)))
+ var/turf/T = pick_n_take(high_value_turfs)
+ if(locate(/obj/structure/spawner/lavaland) in range(3, T))
+ continue
+ var/type_to_spawn = pick(/obj/structure/spawner/lavaland/hivelord, /obj/structure/spawner/lavaland/gutlunch,
+ /obj/structure/spawner/lavaland/asteroid_goliath, /obj/structure/spawner/lavaland/fugu,
+ /obj/structure/spawner/lavaland/basilisk, /obj/structure/spawner/lavaland,
+ /obj/structure/spawner/lavaland/goliath, /obj/structure/spawner/lavaland/legion)
+ new type_to_spawn(T)
+
+ SSair.unpause_z(center_z)
+
+//Spawner types
+/obj/structure/spawner/lavaland/basilisk
+ mob_types = list(/mob/living/simple_animal/hostile/asteroid/basilisk)
+
+/obj/structure/spawner/lavaland/fugu
+ mob_types = list(/mob/living/simple_animal/hostile/asteroid/fugu)
+
+/obj/structure/spawner/lavaland/asteroid_goliath
+ mob_types = list(/mob/living/simple_animal/hostile/asteroid/goliath)
+
+/obj/structure/spawner/lavaland/gutlunch
+ mob_types = list(/mob/living/simple_animal/hostile/asteroid/gutlunch)
+
+/obj/structure/spawner/lavaland/hivelord
+ mob_types = list(/mob/living/simple_animal/hostile/asteroid/hivelord)
+
+/area/asteroid/generated
+ dynamic_lighting = DYNAMIC_LIGHTING_IFSTARLIGHT
+ outdoors = TRUE
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/exoplanets/biomes/_biome.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/exoplanets/biomes/_biome.dm
new file mode 100644
index 0000000000000..579adee96b744
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/exoplanets/biomes/_biome.dm
@@ -0,0 +1,37 @@
+/datum/exoplanet_biome
+ var/name
+
+ var/area_type = /area/planet
+
+ //Rock face types
+ var/deep_rock_type = /turf/closed/mineral/snowmountain/cavern
+ var/shallow_rock_type = /turf/closed/mineral/snowmountain
+
+ var/river_type = /turf/open/floor/plating/ice/smooth
+ var/beach_type = /turf/open/floor/plating/asteroid/snow
+
+ var/plains_type = /turf/open/floor/plating/asteroid/snow
+ var/list/plains_decoration = list(/obj/structure/flora/tree/pine = 1)
+
+ var/jungle_type = /turf/open/floor/plating/asteroid/snow
+ var/list/jungle_decoration = list(/obj/structure/flora/tree/pine = 1)
+
+/area/planet
+ icon_state = "Unknown Planet"
+ has_gravity = STANDARD_GRAVITY
+ flags_1 = NONE
+ sound_environment = SOUND_AREA_LAVALAND
+ always_unpowered = TRUE
+ poweralm = FALSE
+ power_environ = FALSE
+ power_equip = FALSE
+ power_light = FALSE
+ requires_power = TRUE
+ ambience_index = AMBIENCE_MINING
+ min_ambience_cooldown = 70 SECONDS
+ outdoors = TRUE
+ max_ambience_cooldown = 220 SECONDS
+ area_flags = NONE
+ lighting_overlay_colour = "#93c3cf"
+ lighting_overlay_opacity = 60
+ luminosity = 1
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/exoplanets/biomes/lavaland.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/exoplanets/biomes/lavaland.dm
new file mode 100644
index 0000000000000..a0d739e0252d8
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/exoplanets/biomes/lavaland.dm
@@ -0,0 +1,20 @@
+/datum/exoplanet_biome/lavaland
+ name = "lavaland"
+
+ area_type = /area/planet/lavaland
+
+ //Rock face types
+ deep_rock_type = /turf/closed/mineral/random/volcanic
+ shallow_rock_type = /turf/closed/mineral/random/volcanic
+
+ river_type = /turf/open/lava/smooth/lava_land_surface
+ beach_type = /turf/open/floor/plating/lavaland
+
+ plains_type = /turf/open/floor/plating/lavaland
+ plains_decoration = list(/mob/living/simple_animal/hostile/asteroid/goldgrub = 1, /mob/living/simple_animal/hostile/asteroid/goliath = 5, /mob/living/simple_animal/hostile/asteroid/basilisk = 4, /mob/living/simple_animal/hostile/asteroid/hivelord = 3, /obj/structure/flora/ash/leaf_shroom = 20, /obj/structure/flora/ash/cap_shroom = 20, /obj/structure/flora/ash/stem_shroom = 20 , /obj/structure/flora/ash/cacti = 10, /obj/structure/flora/ash/tall_shroom = 20)
+
+ jungle_type = /turf/open/floor/plating/ashplanet/rocky
+ jungle_decoration = list(/obj/structure/geyser/random = 30, null = 60, /mob/living/simple_animal/hostile/megafauna/dragon = 4, /mob/living/simple_animal/hostile/megafauna/colossus = 2, /mob/living/simple_animal/hostile/megafauna/bubblegum = 6)
+
+/area/planet/lavaland
+ lighting_overlay_colour = "#cfb793"
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/exoplanets/biomes/lush.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/exoplanets/biomes/lush.dm
new file mode 100644
index 0000000000000..7d41328045b33
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/exoplanets/biomes/lush.dm
@@ -0,0 +1,16 @@
+/*/datum/exoplanet_biome/lush
+ name = "Lush"
+
+ //Rock face types
+ deep_rock_type = /turf/closed/mineral/random/jungle
+ shallow_rock_type = /turf/closed/mineral/random/jungle
+
+ river_type = /turf/open/water/jungle
+ beach_type = /turf/open/floor/plating/dirt/jungle/wasteland
+
+ plains_type = /turf/open/floor/plating/dirt/jungle/dark
+ plains_decoration = list(/obj/structure/flora/grass/jungle,/obj/structure/flora/grass/jungle/b, /obj/structure/flora/rock/jungle, /obj/structure/flora/rock/pile/largejungle)
+
+ jungle_type = /turf/open/floor/plating/grass/jungle
+ jungle_decoration = list(/obj/structure/flora/grass/jungle,/obj/structure/flora/grass/jungle/b, /obj/structure/flora/tree/jungle, /obj/structure/flora/rock/jungle, /obj/structure/flora/junglebush, /obj/structure/flora/junglebush/b, /obj/structure/flora/junglebush/c, /obj/structure/flora/junglebush/large, /obj/structure/flora/rock/pile/largejungle)
+*/
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/exoplanets/exoplanet_generator.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/exoplanets/exoplanet_generator.dm
new file mode 100644
index 0000000000000..69202795f39b0
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/exoplanets/exoplanet_generator.dm
@@ -0,0 +1,59 @@
+/proc/generate_exoplanet(center_z)
+ var/datum/space_level/space_level = SSmapping.get_level(center_z)
+ space_level.generating = TRUE
+ try
+ _generate_exoplanet(center_z, new /datum/exoplanet_biome/lavaland)
+ catch(var/exception/e)
+ message_admins("Exoplanet failed to generate!")
+ stack_trace("Exoplanet failed to generate! [e] on [e.file]:[e.line]")
+ space_level.generating = FALSE
+
+/proc/_generate_exoplanet(center_z, datum/exoplanet_biome/biome)
+
+ SSair.pause_z(center_z)
+
+ var/perlin_noise_scale = 65
+ var/river_height = 0.25
+ var/beach_height = 0.32
+ var/mountain_height = 0.7
+ var/deepmountain_height = 0.8
+ var/seed = rand(0, 999999)
+ var/area/new_area = new biome.area_type
+ new_area.setup("Alien Planet")
+ for(var/turf/T as() in block(locate(1, 1, center_z), locate(world.maxx, world.maxy, center_z)))
+ if(istype(T.loc, /area/space) && new_area)
+ T.change_area(T.loc, new_area)
+ new_area.contents += T
+ if(isspaceturf(T))
+ var/area_height = text2num(rustg_noise_get_at_coordinates("[seed]", "[T.x / perlin_noise_scale]", "[T.y / perlin_noise_scale]"))
+ if(area_height > deepmountain_height)
+ T.ChangeTurf(biome.deep_rock_type, list(biome.plains_type, biome.river_type), CHANGETURF_IGNORE_AIR)
+ else if(area_height > mountain_height)
+ T.ChangeTurf(biome.shallow_rock_type, list(biome.plains_type, biome.river_type), CHANGETURF_IGNORE_AIR)
+ //Normal biome
+ else if(area_height > beach_height)
+ var/biome_noise = text2num(rustg_noise_get_at_coordinates("[seed]", "[T.x / perlin_noise_scale]", "[T.y / perlin_noise_scale]"))
+ if(biome_noise > 0.5)
+ T.ChangeTurf(biome.plains_type, list(biome.river_type), CHANGETURF_IGNORE_AIR)
+ if(prob(20) && length(biome.plains_decoration))
+ var/type_to_spawn = pickweight(biome.plains_decoration)
+ if(ispath(type_to_spawn))
+ new type_to_spawn(T)
+ else
+ T.ChangeTurf(biome.jungle_type, list(biome.river_type), CHANGETURF_IGNORE_AIR)
+ if(prob(60) && length(biome.plains_decoration))
+ var/type_to_spawn = pickweight(biome.jungle_decoration)
+ if(ispath(type_to_spawn))
+ new type_to_spawn(T)
+ //beach
+ else if(area_height > river_height)
+ T.ChangeTurf(biome.beach_type, list(biome.river_type), CHANGETURF_IGNORE_AIR)
+ //Wa'er
+ else
+ T.ChangeTurf(biome.river_type, list(biome.river_type), CHANGETURF_IGNORE_AIR)
+ else
+ T.baseturfs = list(biome.plains_type, biome.river_type)
+ CHECK_TICK
+ new_area.update_areasize()
+
+ SSair.unpause_z(center_z)
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/generator_settings/_generator_settings.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/generator_settings/_generator_settings.dm
new file mode 100644
index 0000000000000..3c21faa8f8cad
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/generator_settings/_generator_settings.dm
@@ -0,0 +1,34 @@
+//A datum for the generation settings of a ruin
+/datum/generator_settings
+ //Probability of this generator being chosen.
+ var/probability = 0
+ //Probability of breaking the floor
+ var/floor_break_prob = 0
+ //Probability of applying damage to structures
+ var/structure_damage_prob = 0
+
+//Gets shit to place on floors
+/datum/generator_settings/proc/get_floortrash()
+ return list()
+
+//Get directional stuff that goes on walls.
+/datum/generator_settings/proc/get_directional_walltrash()
+ return list()
+
+//Gets non directional stuff that goes on walls
+/datum/generator_settings/proc/get_non_directional_walltrash()
+ return list()
+
+//A list of rooms that can be placed on the map.
+//Assoc list.
+//key = ruin part
+//value = max occurances
+/datum/generator_settings/proc/get_valid_rooms()
+ . = list()
+ for(var/datum/map_template/ruin_part/ruinpart as() in GLOB.loaded_ruin_parts)
+ .[ruinpart] = ruinpart.max_occurances
+
+//A list of rooms to force place on the map.
+//Useful for stuff like making crutch fuel outposts that have plasma in them.
+/datum/generator_settings/proc/get_required_rooms()
+ return list()
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/generator_settings/generator_abandoned.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/generator_settings/generator_abandoned.dm
new file mode 100644
index 0000000000000..6032c45b65a52
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/generator_settings/generator_abandoned.dm
@@ -0,0 +1,43 @@
+/datum/generator_settings/abandoned
+ probability = 6
+ floor_break_prob = 4
+ structure_damage_prob = 2
+
+/datum/generator_settings/abandoned/get_floortrash()
+ . = list(
+ /obj/effect/decal/cleanable/dirt = 6,
+ /obj/effect/decal/cleanable/blood/old = 3,
+ /obj/effect/decal/cleanable/oil = 2,
+ /obj/effect/decal/cleanable/robot_debris/old = 1,
+ /obj/effect/decal/cleanable/vomit/old = 4,
+ /obj/effect/decal/cleanable/blood/gibs/old = 1,
+ /obj/effect/decal/cleanable/greenglow/filled = 1,
+ /obj/effect/spawner/lootdrop/glowstick/lit = 2,
+ /obj/effect/spawner/lootdrop/glowstick = 4,
+ /obj/effect/spawner/lootdrop/maintenance = 3,
+ /mob/living/simple_animal/hostile/poison/giant_spider/hunter = 1,
+ /mob/living/simple_animal/hostile/poison/giant_spider/nurse = 1,
+ null = 110,
+ )
+ for(var/trash in subtypesof(/obj/item/trash))
+ .[trash] = 1
+
+/datum/generator_settings/abandoned/get_directional_walltrash()
+ return list(
+ /obj/machinery/light/built = 5,
+ /obj/machinery/light = 1,
+ /obj/machinery/light/broken = 4,
+ /obj/machinery/light/small = 2,
+ /obj/machinery/light/small/broken = 5,
+ null = 75,
+ )
+
+/datum/generator_settings/abandoned/get_non_directional_walltrash()
+ return list(
+ /obj/item/radio/intercom = 1,
+ /obj/structure/sign/poster/random = 1,
+ /obj/structure/sign/poster/ripped = 2,
+ /obj/machinery/newscaster = 1,
+ /obj/structure/extinguisher_cabinet = 3,
+ null = 30
+ )
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/generator_settings/generator_blob.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/generator_settings/generator_blob.dm
new file mode 100644
index 0000000000000..fd42dbc8c7676
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/generator_settings/generator_blob.dm
@@ -0,0 +1,44 @@
+/datum/generator_settings/blob
+ probability = 2
+ floor_break_prob = 8
+ structure_damage_prob = 6
+
+/datum/generator_settings/blob/get_floortrash()
+ . = list(
+ /obj/effect/decal/cleanable/dirt = 6,
+ /obj/effect/decal/cleanable/blood/old = 3,
+ /obj/effect/decal/cleanable/oil = 2,
+ /obj/effect/decal/cleanable/robot_debris/old = 1,
+ /obj/effect/decal/cleanable/vomit/old = 4,
+ /obj/effect/decal/cleanable/blood/gibs/old = 1,
+ /obj/effect/decal/cleanable/greenglow/filled = 1,
+ /obj/effect/spawner/lootdrop/glowstick/lit = 2,
+ /obj/effect/spawner/lootdrop/glowstick = 4,
+ /obj/effect/spawner/lootdrop/maintenance = 3,
+ /obj/structure/blob/node/lone = 1,
+ /mob/living/simple_animal/hostile/blob/blobspore = 2,
+ /mob/living/simple_animal/hostile/blob/blobbernaut/independent = 1,
+ null = 90,
+ )
+ for(var/trash in subtypesof(/obj/item/trash))
+ .[trash] = 1
+
+/datum/generator_settings/blob/get_directional_walltrash()
+ return list(
+ /obj/machinery/light/built = 5,
+ /obj/machinery/light = 1,
+ /obj/machinery/light/broken = 4,
+ /obj/machinery/light/small = 2,
+ /obj/machinery/light/small/broken = 5,
+ null = 75,
+ )
+
+/datum/generator_settings/blob/get_non_directional_walltrash()
+ return list(
+ /obj/item/radio/intercom = 1,
+ /obj/structure/sign/poster/random = 1,
+ /obj/structure/sign/poster/ripped = 2,
+ /obj/machinery/newscaster = 1,
+ /obj/structure/extinguisher_cabinet = 3,
+ null = 30
+ )
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/generator_settings/generator_netherworld.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/generator_settings/generator_netherworld.dm
new file mode 100644
index 0000000000000..db9390237b5e7
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/generator_settings/generator_netherworld.dm
@@ -0,0 +1,48 @@
+/datum/generator_settings/netherworld
+ probability = 1
+ floor_break_prob = 30
+ structure_damage_prob = 40
+
+/datum/generator_settings/netherworld/get_floortrash()
+ . = list(
+ /obj/effect/decal/cleanable/dirt = 6,
+ /obj/effect/decal/cleanable/blood/old = 3,
+ /obj/effect/decal/cleanable/oil = 2,
+ /obj/effect/decal/cleanable/robot_debris/old = 1,
+ /obj/effect/decal/cleanable/vomit/old = 4,
+ /obj/effect/decal/cleanable/blood/gibs/old = 1,
+ /obj/effect/decal/cleanable/greenglow/filled = 1,
+ /obj/effect/spawner/lootdrop/glowstick/lit = 2,
+ /obj/effect/spawner/lootdrop/glowstick = 4,
+ /obj/effect/spawner/lootdrop/maintenance = 3,
+ /mob/living/simple_animal/hostile/netherworld/blankbody = 2,
+ /mob/living/simple_animal/hostile/netherworld/migo = 2,
+ /obj/structure/spawner/nether = 0.3,
+ /obj/structure/destructible/cult/pylon = 2,
+ /obj/structure/destructible/cult/forge = 1,
+ /obj/effect/rune/blood_boil = 1,
+ /obj/effect/rune/empower = 1,
+ null = 140,
+ )
+ for(var/trash in subtypesof(/obj/item/trash))
+ .[trash] = 1
+
+/datum/generator_settings/netherworld/get_directional_walltrash()
+ return list(
+ /obj/machinery/light/built = 5,
+ /obj/machinery/light = 1,
+ /obj/machinery/light/broken = 4,
+ /obj/machinery/light/small = 2,
+ /obj/machinery/light/small/broken = 5,
+ null = 75,
+ )
+
+/datum/generator_settings/netherworld/get_non_directional_walltrash()
+ return list(
+ /obj/item/radio/intercom = 1,
+ /obj/structure/sign/poster/random = 1,
+ /obj/structure/sign/poster/ripped = 2,
+ /obj/machinery/newscaster = 1,
+ /obj/structure/extinguisher_cabinet = 3,
+ null = 30
+ )
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/generator_settings/generator_ratvar.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/generator_settings/generator_ratvar.dm
new file mode 100644
index 0000000000000..42844878ee746
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/generator_settings/generator_ratvar.dm
@@ -0,0 +1,46 @@
+/datum/generator_settings/blob
+ probability = 2
+ floor_break_prob = 8
+ structure_damage_prob = 6
+
+/datum/generator_settings/blob/get_floortrash()
+ . = list(
+ /obj/effect/decal/cleanable/dirt = 6,
+ /obj/effect/decal/cleanable/blood/old = 3,
+ /obj/effect/decal/cleanable/oil = 2,
+ /obj/effect/decal/cleanable/robot_debris/old = 1,
+ /obj/effect/decal/cleanable/vomit/old = 4,
+ /obj/effect/decal/cleanable/blood/gibs/old = 1,
+ /obj/effect/decal/cleanable/greenglow/filled = 1,
+ /obj/effect/spawner/lootdrop/glowstick/lit = 6,
+ /obj/effect/spawner/lootdrop/maintenance = 3,
+ null = 70,
+ /obj/effect/spawner/structure/ratvar_skewer_trap = 4,
+ /obj/effect/spawner/structure/ratvar_flipper_trap = 2,
+ /obj/effect/spawner/structure/ratvar_skewer_trap_kill = 1,
+ /obj/structure/destructible/clockwork/sigil/transgression = 2,
+ /mob/living/simple_animal/hostile/clockwork_marauder = 1,
+ /obj/structure/destructible/clockwork/wall_gear/displaced = 10,
+ /obj/effect/spawner/ocular_warden_setup = 1,
+ /obj/effect/spawner/interdiction_lens_setup = 1,
+ )
+ for(var/trash in subtypesof(/obj/item/trash))
+ .[trash] = 1
+
+/datum/generator_settings/blob/get_directional_walltrash()
+ return list(
+ /obj/machinery/light/broken = 4,
+ /obj/machinery/light/small = 1,
+ null = 75,
+ )
+
+/datum/generator_settings/blob/get_non_directional_walltrash()
+ return list(
+ /obj/item/radio/intercom = 2,
+ /obj/structure/sign/poster/random = 1,
+ /obj/machinery/newscaster = 2,
+ /obj/structure/destructible/clockwork/trap/delay = 1,
+ /obj/structure/destructible/clockwork/trap/lever = 1,
+ /obj/structure/extinguisher_cabinet = 3,
+ null = 30
+ )
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/generator_settings/generator_xeno.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/generator_settings/generator_xeno.dm
new file mode 100644
index 0000000000000..06c05d369e6dc
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/generator_settings/generator_xeno.dm
@@ -0,0 +1,51 @@
+/datum/generator_settings/xeno
+ probability = 2
+ floor_break_prob = 4
+ structure_damage_prob = 20
+
+/datum/generator_settings/xeno/get_floortrash()
+ . = list(
+ /obj/effect/decal/cleanable/dirt = 3,
+ /obj/effect/decal/cleanable/blood/old = 6,
+ /obj/effect/decal/cleanable/oil = 2,
+ /obj/effect/decal/cleanable/robot_debris/old = 1,
+ /obj/effect/decal/cleanable/vomit/old = 4,
+ /obj/effect/decal/cleanable/blood/gibs/old = 6,
+ /obj/effect/decal/cleanable/greenglow/filled = 3,
+ /obj/effect/spawner/lootdrop/glowstick/lit = 5,
+ /obj/effect/spawner/lootdrop/glowstick = 1,
+ /obj/effect/spawner/lootdrop/maintenance = 3,
+ /obj/item/ammo_casing/c9mm = 4,
+ /obj/item/gun/ballistic/automatic/pistol/no_mag = 1,
+ /mob/living/simple_animal/hostile/alien/drone = 1,
+ /mob/living/simple_animal/hostile/alien/sentinel = 1,
+ /mob/living/simple_animal/hostile/alien = 1,
+ /obj/structure/alien/egg = 1,
+ /obj/structure/alien/weeds/node = 8,
+ /obj/structure/alien/gelpod = 4,
+ /obj/effect/mob_spawn/human/corpse/nanotrasensoldier = 1,
+ /obj/effect/mob_spawn/human/corpse/assistant = 1,
+ /obj/effect/mob_spawn/human/corpse/cargo_tech = 1,
+ /obj/effect/mob_spawn/human/corpse/damaged = 1,
+ null = 90
+ )
+ for(var/trash in subtypesof(/obj/item/trash))
+ .[trash] = 1
+
+/datum/generator_settings/xeno/get_directional_walltrash()
+ return list(
+ /obj/machinery/light/built = 1,
+ /obj/machinery/light/broken = 8,
+ /obj/machinery/light/small = 1,
+ /obj/machinery/light/small/broken = 6,
+ null = 75,
+ )
+
+/datum/generator_settings/xeno/get_non_directional_walltrash()
+ return list(
+ /obj/item/radio/intercom = 1,
+ /obj/structure/sign/poster/ripped = 2,
+ /obj/machinery/newscaster = 1,
+ /obj/structure/extinguisher_cabinet = 3,
+ null = 30
+ )
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/mapping.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/mapping.dm
new file mode 100644
index 0000000000000..77ac880605b16
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/mapping.dm
@@ -0,0 +1,192 @@
+/obj/effect/abstract/open_area_marker
+ name = "open area marker"
+ icon = 'icons/obj/device.dmi'
+ icon_state = "pinonfar"
+
+/obj/effect/abstract/open_area_marker/Initialize()
+ return INITIALIZE_HINT_QDEL
+
+/obj/effect/abstract/doorway_marker
+ name = "doorway marker"
+ icon = 'icons/obj/device.dmi'
+ icon_state = "pinonmedium"
+
+/obj/effect/abstract/doorway_marker/Initialize()
+ return INITIALIZE_HINT_QDEL
+
+//Basic loot, utility and maybe some weapons
+//Shouldnt contain illegal tech
+/obj/effect/spawner/lootdrop/ruinloot/basic
+ loot = list(
+ "" = 12,
+ /obj/item/melee/classic_baton/police = 3,
+ /obj/item/melee/classic_baton/police/telescopic = 2,
+ /obj/item/storage/firstaid/regular = 1,
+ /obj/item/storage/firstaid/toxin = 1,
+ /obj/item/storage/firstaid/brute = 1,
+ /obj/item/storage/firstaid/fire = 1,
+ /obj/item/grenade/smokebomb = 1,
+ /obj/item/storage/belt/utility/full = 1,
+ /obj/item/shield/riot/tele = 1,
+ /obj/item/melee/baton/loaded = 1,
+ /obj/item/gun/energy/e_gun/mini = 1,
+ /obj/item/seeds/random = 1,
+ /obj/item/gun/energy/floragun = 1,
+ /obj/item/stack/spacecash/c1000 = 2,
+ /obj/item/grenade/exploration = 1,
+ )
+
+//Medical stuff
+/obj/effect/spawner/lootdrop/ruinloot/medical
+ loot = list(
+ "" = 13,
+ /obj/item/storage/firstaid/regular = 1,
+ /obj/item/storage/firstaid/toxin = 1,
+ /obj/item/storage/firstaid/brute = 1,
+ /obj/item/storage/firstaid/fire = 1,
+ /obj/item/storage/backpack/duffelbag/med/surgery = 1,
+ /obj/item/hemostat = 1,
+ /obj/item/retractor = 1,
+ /obj/item/scalpel = 1,
+ /obj/item/blood_filter = 1,
+ /obj/item/reagent_containers/medspray/sterilizine = 1,
+ /obj/item/reagent_containers/medspray/silver_sulf = 1,
+ /obj/item/reagent_containers/medspray/styptic = 1,
+ /obj/item/reagent_containers/food/drinks/bottle/synthflesh = 1,
+ /obj/item/clothing/glasses/hud/health/sunglasses = 1,
+ /obj/item/surgical_drapes = 1,
+ /obj/item/stack/medical/bruise_pack = 1,
+ /obj/item/stack/medical/ointment = 1,
+ /obj/item/stack/medical/gauze = 1,
+ /obj/item/reagent_containers/glass/bottle/epinephrine = 1,
+ /obj/item/reagent_containers/glass/bottle/charcoal = 1,
+ /obj/item/storage/pill_bottle/floorpill/full = 1,
+ /obj/item/storage/pill_bottle/antirad = 1,
+ /obj/item/clothing/neck/stethoscope = 1,
+ /obj/item/reagent_containers/spray/cleaner = 1,
+ /obj/item/storage/belt/medical = 1,
+ /obj/item/defibrillator/compact/loaded = 1,
+ /obj/item/pinpointer/crew = 1,
+ )
+
+//Science stuff
+/obj/effect/spawner/lootdrop/ruinloot/science
+ loot = list(
+ "" = 18,
+ /obj/item/laser_pointer = 3,
+ /obj/item/storage/toolbox/mechanical = 2,
+ /obj/item/paicard = 5,
+ /obj/item/nanite_remote = 3,
+ /obj/item/nanite_injector = 1,
+ /obj/item/nanite_scanner = 3,
+ /obj/item/disk/tech_disk = 5,
+ /obj/item/assembly/prox_sensor = 6,
+ /obj/item/bodypart/r_arm/robot = 4,
+ /obj/item/assembly/flash/handheld/weak = 2,
+ /obj/item/stock_parts/cell/high = 1,
+ /obj/item/stock_parts/manipulator/nano = 1,
+ /obj/item/stock_parts/manipulator = 1,
+ /obj/item/stock_parts/capacitor/super = 1,
+ /obj/item/stock_parts/matter_bin/super = 1,
+ /obj/item/stock_parts/scanning_module/adv = 1,
+ /obj/item/screwdriver = 6,
+ /obj/item/storage/box/monkeycubes = 3,
+ /obj/item/stack/sheet/mineral/plasma = 3,
+ /obj/item/pipe_dispenser = 4,
+ /obj/item/wrench = 6,
+ /obj/item/assembly/signaler = 5,
+ /obj/item/transfer_valve = 6,
+ /obj/item/cartridge/rd = 3,
+ /obj/item/radio = 5,
+ /obj/item/camera = 4,
+ /obj/item/encryptionkey/headset_sci = 3,
+ /obj/item/aicard = 2,
+ /obj/item/flamethrower = 2,
+ /obj/item/tank/internals/plasma/full = 2,
+ /obj/item/gps/science = 3,
+ /obj/item/hand_tele = 1,
+ /obj/item/inducer/sci = 3,
+ /obj/item/megaphone = 1,
+ /obj/item/pda/roboticist = 3,
+ /obj/item/pda/toxins = 3,
+ /obj/item/pinpointer/crew = 4,
+ /obj/item/reactive_armour_shell = 1,
+ /obj/item/anomaly_neutralizer = 1,
+ /obj/item/shuttle_creator = 1,
+ /obj/item/soap = 1,
+ /obj/item/borg/upgrade/selfrepair = 1,
+ /obj/item/borg/upgrade/speciality/botany = 1,
+ /obj/item/borg/upgrade/defib = 1,
+ /obj/item/taperecorder = 5,
+ /obj/item/clothing/mask/facehugger/lamarr = 1
+ )
+
+//Security stuff
+/obj/effect/spawner/lootdrop/ruinloot/security
+ loot = list(
+ "" = 18,
+ /obj/item/assembly/flash/handheld = 4,
+ /obj/item/melee/baton/loaded = 3,
+ /obj/item/restraints/handcuffs = 4,
+ /obj/item/storage/lockbox/loyalty = 1,
+ /obj/item/storage/box/handcuffs = 2,
+ /obj/item/flashlight/seclite = 4,
+ /obj/item/flashbulb = 3,
+ /obj/item/flashbulb/bomb = 1,
+ /obj/item/restraints/legcuffs/bola/energy = 1,
+ /obj/item/clothing/glasses/hud/security/sunglasses = 2,
+ /obj/item/clothing/under/rank/security = 1,
+ /obj/item/clothing/suit/armor/vest = 2,
+ /obj/item/clothing/suit/armor/bulletproof = 1,
+ /obj/item/storage/secure/briefcase = 1,
+ /obj/item/reagent_containers/glass/rag = 1,
+ /obj/item/reagent_containers/glass/bottle/chloralhydrate = 1,
+ /obj/item/grenade/flashbang = 2,
+ /obj/item/grenade/chem_grenade/teargas = 1,
+ /obj/item/reagent_containers/spray/pepper = 1,
+ /obj/item/clothing/mask/gas/sechailer = 1,
+ /obj/item/grenade/exploration = 1,
+ )
+
+//Armoury stuff
+/obj/effect/spawner/lootdrop/ruinloot/armoury
+ loot = list(
+ "" = 30,
+ /obj/item/gun/energy/disabler = 5,
+ /obj/item/gun/energy/e_gun = 2,
+ /obj/item/gun/energy/laser = 3,
+ /obj/item/gun/ballistic/automatic/pistol/m1911/no_mag = 4,
+ /obj/item/gun/ballistic/automatic/wt550 = 1,
+ /obj/item/gun/grenadelauncher/security = 1,
+ /obj/item/key/security = 5,
+ /obj/effect/spawner/lootdrop/armory_contraband = 1,
+ /obj/item/clothing/suit/armor/laserproof = 1,
+ /obj/item/gun/energy/ionrifle = 2,
+ /obj/item/gun/energy/temperature = 2,
+ /obj/item/gun/energy/e_gun/dragnet = 1,
+ /obj/item/clothing/suit/armor/bulletproof = 4,
+ /obj/item/clothing/head/helmet/alt = 4,
+ /obj/item/clothing/suit/armor/riot = 1,
+ /obj/item/clothing/head/helmet/riot = 1,
+ /obj/item/storage/lockbox/loyalty = 1,
+ /obj/item/storage/fancy/donut_box = 6,
+ /obj/item/storage/box/teargas = 2,
+ /obj/item/storage/box/flashbangs = 2,
+ /obj/item/shield/riot = 1,
+ /obj/item/gun/ballistic/shotgun/riot = 1,
+ /obj/item/ammo_box/magazine/wt550m9 = 3,
+ /obj/item/ammo_box/magazine/wt550m9/wtap = 1,
+ /obj/item/ammo_box/magazine/wt550m9/wtic = 1,
+ /obj/item/ammo_box/magazine/pistolm9mm = 4,
+ /obj/item/grenade/exploration = 2,
+ )
+
+//Important stuff like research disks
+/obj/effect/spawner/lootdrop/ruinloot/important
+ loot = list(
+ "" = 4,
+ /obj/item/disk/tech_disk/research/random = 24,
+ /obj/item/alienartifact = 6,
+ /obj/item/gun/energy/vortex = 3,
+ /obj/item/gun/energy/alien = 1
+ )
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/ruin_events/_ruin_event.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/ruin_events/_ruin_event.dm
new file mode 100644
index 0000000000000..fcf22e7cb0c98
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/ruin_events/_ruin_event.dm
@@ -0,0 +1,48 @@
+/datum/ruin_event
+ var/warning_message = ""
+ var/probability = 0
+ var/start_tick_min = 0
+ var/start_tick_max = 0
+ var/tick_rate = 1
+ var/end_tick_min = 0
+ var/end_tick_max = 0
+ //Instanced
+ var/datum/orbital_object/z_linked/linked_z
+ var/start_tick = 0
+ var/end_tick = 0
+ var/ticks = 0
+
+/datum/ruin_event/New()
+ . = ..()
+ start_tick = rand(start_tick_min, start_tick_max)
+ end_tick = rand(start_tick_min, start_tick_max)
+
+/datum/ruin_event/proc/update()
+ if(QDELETED(linked_z))
+ return FALSE
+ //Events only work on the first Z, multi-z linkage currently is for stations only.
+ if(ticks == start_tick)
+ event_start(linked_z.linked_z_level[1].z_value)
+ if(end_tick && ticks >= end_tick)
+ event_end(linked_z.linked_z_level[1].z_value)
+ return FALSE
+ if(ticks % tick_rate == 0)
+ event_tick(linked_z.linked_z_level[1].z_value)
+ ticks ++
+ return TRUE
+
+/datum/ruin_event/proc/pre_spawn(z_value)
+ return
+
+//Note that the list is the coordinates not the turfs themselves
+/datum/ruin_event/proc/post_spawn(list/floor_turfs, z_value)
+ return
+
+/datum/ruin_event/proc/event_start(z_value)
+ return
+
+/datum/ruin_event/proc/event_tick(z_value)
+ return
+
+/datum/ruin_event/proc/event_end(z_value)
+ return
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/ruin_events/asteriod_station.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/ruin_events/asteriod_station.dm
new file mode 100644
index 0000000000000..008975c4bca7e
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/ruin_events/asteriod_station.dm
@@ -0,0 +1,20 @@
+/datum/ruin_event/asteriod_station
+ probability = 3
+
+/datum/ruin_event/asteriod_station/post_spawn(list/floor_turfs, z_value)
+ var/perlin_noise_scale = 65
+ var/seed = rand(0, 999999)
+ var/turf/z_center = locate(world.maxx * 0.5, world.maxy * 0.5, z_value)
+ for(var/turf/open/space/T in block(locate(1, 1, z_value), locate(world.maxx, world.maxy, z_value)))
+ //Calculate distance to edge
+ var/distance = z_center.Distance(T)
+ if(distance > 120)
+ continue
+ var/noise_at_coord = text2num(rustg_noise_get_at_coordinates("[seed]", "[T.x / perlin_noise_scale]", "[T.y / perlin_noise_scale]"))
+ var/rock_value = (distance / 120) + 0.1
+ var/sand_value = (distance / 120)
+ if(noise_at_coord >= rock_value)
+ T.ChangeTurf(/turf/closed/mineral/random, list(/turf/open/floor/plating/asteroid/airless), CHANGETURF_IGNORE_AIR)
+ else if(noise_at_coord >= sand_value)
+ T.ChangeTurf(/turf/open/floor/plating/asteroid/airless, flags = CHANGETURF_IGNORE_AIR)
+ CHECK_TICK
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/ruin_events/meteor_storm.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/ruin_events/meteor_storm.dm
new file mode 100644
index 0000000000000..a9df4d0ad564a
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/ruin_events/meteor_storm.dm
@@ -0,0 +1,17 @@
+/datum/ruin_event/meteor_storm
+ warning_message = "METEOR STORM"
+ probability = 1
+ start_tick_min = 300
+ start_tick_max = 600
+ tick_rate = 4
+
+/datum/ruin_event/meteor_storm/post_spawn(list/floor_turfs, z_value)
+ exploration_announce("Incoming dust-storm at beacon location. ETA: [round(start_tick, 1)] seconds.", z_value)
+
+/datum/ruin_event/meteor_storm/event_tick(z_value)
+ var/startSide = pick(GLOB.cardinals)
+ var/turf/pickedstart = spaceDebrisStartLoc(startSide, z_value)
+ var/turf/pickedgoal = spaceDebrisFinishLoc(startSide, z_value)
+ var/Me = pickweight(GLOB.meteorsC)
+ var/obj/effect/meteor/M = new Me(pickedstart, pickedgoal)
+ M.dest = pickedgoal
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/ruin_generator.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/ruin_generator.dm
new file mode 100644
index 0000000000000..9199323b53ea7
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/ruin_generator.dm
@@ -0,0 +1,409 @@
+#define OPEN_CONNECTION 1
+#define ROOM_CONNECTION 16
+
+/*
+ * Generates a random space ruin.
+ * Slow lmao.
+ * Dimensions of maps need to be 4n+1 by 4n+1
+ * Params:
+ * center_x - The x coordinate of the center of the ruin.
+ * center_y - The y coordinate of the center of the ruin.
+ * center_z - The z level the ruin is on.
+ * border_x - The distance from the edge of the world in which the ruin will be forced to stop.
+ * border_y - See above.
+ * linked_objective - Will spawn special objective stuff if this is part of an objective.
+ * Note: The ruin can generate past the border. The border prevents rooms from attaching past that point,
+ * however if a room attachment point is not past the border, the room it generates on that attachment point
+ * can go past the border. No attachment points can be generated past the border.
+ */
+/proc/generate_space_ruin(center_x, center_y, center_z, border_x, border_y, datum/orbital_objective/linked_objective, forced_decoration, datum/ruin_event/ruin_event)
+ var/datum/space_level/space_level = SSmapping.get_level(center_z)
+ space_level.generating = TRUE
+ try
+ _generate_space_ruin(center_x, center_y, center_z, border_x, border_y, linked_objective, forced_decoration, ruin_event)
+ catch(var/exception/e)
+ message_admins("Space ruin failed to generate!")
+ stack_trace("Space ruin failed to generate! [e] on [e.file]:[e.line]")
+ space_level.generating = FALSE
+
+/proc/_generate_space_ruin(center_x, center_y, center_z, border_x, border_y, datum/orbital_objective/linked_objective, forced_decoration, datum/ruin_event/ruin_event)
+
+ SSair.pause_z(center_z)
+
+ //Try and catch errors so that critical actions (unpausing the Z atmos) can happen.
+ log_mapping("Generating random ruin at [center_x], [center_y], [center_z]")
+
+ if(!length(GLOB.loaded_ruin_parts))
+ load_ruin_parts()
+
+ //Select ruin type
+ var/datum/generator_settings/generator_settings = forced_decoration
+ if(!istype(generator_settings))
+ //Select one randomly
+ var/static/list/datum/generator_settings/generator_settings_cache
+ if(!generator_settings_cache)
+ generator_settings_cache = list()
+ for(var/generator_type in subtypesof(/datum/generator_settings))
+ var/datum/generator_settings/instance = new generator_type()
+ if(instance.probability != 0)
+ generator_settings_cache[instance] = instance.probability
+ generator_settings = pickweight(generator_settings_cache)
+
+ //We need doors
+ var/list/placed_room_entrances = list()
+ var/list/placed_hallway_entrances = list()
+
+ var/list/room_connections = list() //Assoc list of door connection coords, [x]_[y] = dir
+ var/list/hallway_connections = list() //Assoc list of hallway connection coords, [x]_[y] = dir
+ //Blocked turfs = Walls and floors
+ var/list/blocked_turfs = list() //Assoc list of blocked coords [x]_[y] = TRUE
+ //Floor turfs = Open turfs only. Walls should be allowed to overlap.
+ var/list/floor_turfs = list() //Assoc list as above, except doesn't includ walls.
+
+ //First point
+ //Place one facing up and one facing down.
+ hallway_connections["[center_x]_[center_y]"] = NORTH
+ placed_hallway_entrances["[center_x]_[center_y]"] = NORTH
+
+ var/sanity = 1000
+
+ var/list/valid_ruin_parts = generator_settings.get_valid_rooms()
+
+ ruin_event?.pre_spawn(center_z)
+
+ //Generate ruins.
+ while(length(hallway_connections) || length(room_connections))
+ sanity --
+ if(sanity < 0)
+ message_admins("Ruin sanity limit reached.")
+ return
+ var/ishallway = length(hallway_connections)
+ var/list/list_to_use = ishallway ? hallway_connections : room_connections
+ //Generate rooms.
+ var/first_coord = list_to_use[list_to_use.len]
+ var/first_dir = list_to_use[first_coord]
+ var/room_connection_x = text2num(splittext(first_coord, "_")[1])
+ var/room_connection_y = text2num(splittext(first_coord, "_")[2])
+ var/looking_for_dir = invertDir(first_dir)
+ //Find a list of valid rooms.
+ var/list/valid_ruins = list()
+ //Get all loaded ruins
+ for(var/datum/map_template/ruin_part/ruin_part as() in valid_ruin_parts)
+ CHECK_TICK
+ //Get every connection point in the loaded ruin
+ for(var/connection_point in ruin_part.connection_points)
+ CHECK_TICK
+ var/splitconn = splittext(connection_point, "_")
+ var/connection_x = text2num(splitconn[1])
+ var/connection_y = text2num(splitconn[2])
+ var/value = ruin_part.connection_points[connection_point]
+ var/connection_type = value >= 16 ? ROOM_CONNECTION : OPEN_CONNECTION
+ var/connection_dir = value / connection_type
+ //Not an open connection
+ if(connection_type != (ishallway ? OPEN_CONNECTION : ROOM_CONNECTION))
+ continue
+ //Check to make sure direction is valid.
+ if(connection_dir != looking_for_dir)
+ //Invalid connection
+ continue
+ var/valid = TRUE
+ //=======================================================
+ //Make sure connection points dont overlap blocked parts.
+ //VALIDATE OUR NEW PORTS ARE OK
+ //=======================================================
+ for(var/subconnection_point in ruin_part.connection_points)
+ CHECK_TICK
+ var/splitsubconn = splittext(subconnection_point, "_")
+ //Get subconnection positions
+ var/subconnection_x = text2num(splitsubconn[1])
+ var/subconnection_y = text2num(splitsubconn[2])
+ var/subconnection_type = ruin_part.connection_points[subconnection_point] >= 16
+ //Calculate the subconnection offset to the rooms main connection
+ var/offset_x = subconnection_x - connection_x
+ var/offset_y = subconnection_y - connection_y
+ //Add on the world offset of the room
+ offset_x += room_connection_x
+ offset_y += room_connection_y
+ //Port is on a blocked turf, and there isnt a connection on that blocked turf. (Essentially, the port is on a wall.)
+ if(blocked_turfs["[offset_x]_[offset_y]"] && !(subconnection_type ? room_connections["[offset_x]_[offset_y]"] : hallway_connections["[offset_x]_[offset_y]"]))
+ valid = FALSE
+ break
+ //Check if the port is outside the valid world border.
+ if(offset_x < border_x || offset_x > world.maxx - border_x || offset_y < border_y || offset_y > world.maxx - border_y)
+ valid = FALSE
+ break
+ //Something is blocked.
+ if(!valid)
+ continue
+ //=======================================================
+ //Make sure floors dont overlap existing floors or walls.
+ //Make sure that there are no global connection points inside us that aren't linked
+ //Get the ruin origin position
+ //VALIDATE THAT THE ROOM DOESNT OVERLAP ANOTHER ROOM.
+ //=======================================================
+ var/ruin_offset_x = room_connection_x - connection_x
+ var/ruin_offset_y = room_connection_y - connection_y
+ for(var/x in ruin_offset_x + 2 to ruin_offset_x + ruin_part.width - 1)
+ for(var/y in ruin_offset_y + 2 to ruin_offset_y + ruin_part.height - 1)
+ var/world_point = "[x]_[y]"
+ //Check to see if the point in which we have a floor is blocked
+ if(blocked_turfs[world_point])
+ valid = FALSE
+ break
+ //=======================================================
+ //VALIDATE THAT EXISTING PORTS LINK TO THIS ROOM.
+ //=======================================================
+ for(var/x in ruin_offset_x + 1 to ruin_offset_x + ruin_part.width)
+ for(var/y in ruin_offset_y + 1 to ruin_offset_y + ruin_part.height)
+ var/world_point = "[x]_[y]"
+ //Check to see if there is a blocked room or hall connection
+ if((room_connections[world_point] || hallway_connections[world_point]) && !ruin_part.connection_points["[x - ruin_offset_x]_[y - ruin_offset_y]"])
+ valid = FALSE
+ break
+ //Something is disconnected or blocked.
+ if(!valid)
+ continue
+ valid_ruins += list(list(
+ "ruindata" = ruin_part,
+ "weight" = ruin_part.weight,
+ "port_offset_x" = connection_x,
+ "port_offset_y" = connection_y,
+ ))
+ if(!length(valid_ruins))
+ log_mapping("Fuck. Ruin generation failed (No valid ruins). Continuing as if everything is actually ok.")
+ ishallway ? hallway_connections.len-- : room_connections.len--
+ continue
+ //Pick a ruin and spawn it.
+ var/list/selected_ruin = pickweight_ruin(valid_ruins)
+ //Spawn the ruin
+ //Get the port offset position
+ var/port_offset_x = selected_ruin["port_offset_x"]
+ var/port_offset_y = selected_ruin["port_offset_y"]
+ //Get the ruin origin position
+ var/ruin_offset_x = room_connection_x - port_offset_x
+ var/ruin_offset_y = room_connection_y - port_offset_y
+
+ var/datum/map_template/ruin_part/ruin_part = selected_ruin["ruindata"]
+
+ //If its a loot room, remove all loot rooms.
+ if(ruin_part.loot_room)
+ for(var/datum/map_template/ruin_part/otherpart as() in valid_ruin_parts)
+ if(otherpart.loot_room)
+ valid_ruin_parts.Remove(otherpart)
+ //Otherwise subtract it from amount used
+ else
+ if(!valid_ruin_parts.Find(ruin_part))
+ stack_trace("Error, ruin part wasnt in valid ruin parts somehow.")
+ else
+ valid_ruin_parts[ruin_part] --
+ if(valid_ruin_parts[ruin_part] <= 0)
+ valid_ruin_parts.Remove(ruin_part)
+
+ //Actual spawn
+ SSmapping.loading_ruins = TRUE
+ CHECK_TICK
+ try
+ ruin_part.load(locate(ruin_offset_x + 1, ruin_offset_y + 1, center_z), FALSE, FALSE)
+ catch(var/exception/e)
+ stack_trace("Run time in space ruin generation ([ruin_part.name]) [e] on [e.file]:[e.line]")
+ CHECK_TICK
+ SSmapping.loading_ruins = FALSE
+ //Simulate spawning
+ //Remove filled connection points
+ for(var/point in ruin_part.connection_points)
+ CHECK_TICK
+ var/splitpoint = splittext(point, "_")
+ //Get offset in ruin map
+ var/point_x = text2num(splitpoint[1])
+ var/point_y = text2num(splitpoint[2])
+ //Convert to offset in the world
+ var/world_x = ruin_offset_x + point_x
+ var/world_y = ruin_offset_y + point_y
+ //Remove connection points
+ var/removed_point = FALSE
+ if(hallway_connections.Find("[world_x]_[world_y]"))
+ removed_point = TRUE
+ hallway_connections.Remove("[world_x]_[world_y]")
+ if(room_connections.Find("[world_x]_[world_y]"))
+ removed_point = TRUE
+ room_connections.Remove("[world_x]_[world_y]")
+ if(!removed_point)
+ //Port needs adding
+ if(ruin_part.connection_points[point] >= 16)
+ if(hallway_connections.Find("[world_x]_[world_y]"))
+ message_admins("Trying to put a room connection at a hallway connection")
+ else
+ room_connections["[world_x]_[world_y]"] = ruin_part.connection_points[point] / 16
+ placed_room_entrances["[world_x]_[world_y]"] = ruin_part.connection_points[point] / 16
+ else
+ if(room_connections.Find("[world_x]_[world_y]"))
+ message_admins("Trying to put a hallway connection at a room connection")
+ else
+ hallway_connections["[world_x]_[world_y]"] = ruin_part.connection_points[point]
+ placed_hallway_entrances["[world_x]_[world_y]"] = ruin_part.connection_points[point]
+ //Block turfs
+ for(var/x in ruin_offset_x + 1 to ruin_offset_x + ruin_part.width)
+ CHECK_TICK
+ for(var/y in ruin_offset_y + 1 to ruin_offset_y + ruin_part.height)
+ blocked_turfs["[x]_[y]"] = TRUE
+ //Block floors
+ for(var/point in ruin_part.floor_locations)
+ CHECK_TICK
+ var/splitpoint = splittext(point, "_")
+ //Get offset in ruin map
+ var/point_x = text2num(splitpoint[1])
+ var/point_y = text2num(splitpoint[2])
+ //Convert to offset in the world
+ var/world_x = ruin_offset_x + point_x
+ var/world_y = ruin_offset_y + point_y
+ //Block
+ floor_turfs["[world_x]_[world_y]"] = TRUE
+ //We are done with that.
+ ishallway ? hallway_connections.Remove(first_coord) : room_connections.Remove(first_coord)
+
+ //Place first one again
+ //Wow doing this based off sanity is bad
+ if(sanity == 999)
+ hallway_connections["[center_x]_[center_y]"] = SOUTH
+
+ //Lets place doors
+ for(var/door_pos in placed_room_entrances)
+ var/splitextdoor = splittext(door_pos, "_")
+ var/turf/T = locate(text2num(splitextdoor[1]), text2num(splitextdoor[2]), center_z)
+ var/valid = isopenturf(T)
+ switch(placed_room_entrances[door_pos])
+ if(EAST, WEST)
+ if(isopenturf(locate(text2num(splitextdoor[1]), text2num(splitextdoor[2]) + 1, center_z)) || isopenturf(locate(text2num(splitextdoor[1]), text2num(splitextdoor[2]) - 1, center_z)))
+ valid = FALSE
+ if(NORTH, SOUTH)
+ if(isopenturf(locate(text2num(splitextdoor[1]) + 1, text2num(splitextdoor[2]), center_z)) || isopenturf(locate(text2num(splitextdoor[1]) - 1, text2num(splitextdoor[2]), center_z)))
+ valid = FALSE
+ else
+ message_admins("Why the fuck is this thing [door_pos] have a direction of [placed_room_entrances[door_pos]]??? TELL ME!!!!")
+ valid = FALSE
+ if(valid)
+ new /obj/machinery/door/airlock/hatch(T)
+ switch(placed_room_entrances[door_pos])
+ if(SOUTH, NORTH)
+ var/obj/machinery/door/firedoor/border_only/b1 = new(T)
+ var/obj/machinery/door/firedoor/border_only/b2 = new(T)
+ b1.setDir(NORTH)
+ b2.setDir(SOUTH)
+ if(EAST, WEST)
+ var/obj/machinery/door/firedoor/border_only/b1 = new(T)
+ var/obj/machinery/door/firedoor/border_only/b2 = new(T)
+ b1.setDir(EAST)
+ b2.setDir(WEST)
+
+ //Repopulate areas
+ repopulate_sorted_areas()
+
+ //Fill with shit
+ var/list/floortrash = generator_settings.get_floortrash()
+ var/list/directional_walltrash = generator_settings.get_directional_walltrash()
+ var/list/nondirectional_walltrash = generator_settings.get_non_directional_walltrash()
+ var/structure_damage_prob = generator_settings.structure_damage_prob
+ var/floor_break_prob = generator_settings.floor_break_prob
+
+ //Place trash
+ for(var/place in blocked_turfs)
+ CHECK_TICK
+ var/splitplace = splittext(place, "_")
+ var/x = text2num(splitplace[1])
+ var/y = text2num(splitplace[2])
+ var/turf/T = locate(x, y, center_z)
+ if(isspaceturf(T) || isclosedturf(T))
+ continue
+ if(locate(/obj) in T)
+ if(prob(structure_damage_prob))
+ var/obj/structure/S = locate() in T
+ if(S)
+ S.take_damage(rand(0, S.max_integrity * 1.5))
+ continue
+ if(prob(floor_break_prob) && istype(T, /turf/open/floor/plasteel))
+ T = T.ScrapeAway()
+ //Spawn floortrash.
+ var/new_floortrash = pickweight(floortrash)
+ if(ispath(new_floortrash))
+ new new_floortrash(T)
+ //Check for walls and spawn walltrash
+ for(var/direction in GLOB.cardinals)
+ var/turf/T1 = get_step(T, direction)
+ if(isclosedturf(T1))
+ var/new_directional_walltrash = pickweight(directional_walltrash)
+ if(ispath(new_directional_walltrash))
+ var/atom/A = new new_directional_walltrash(T)
+ A.setDir(direction)
+ else
+ var/new_nondirectional_walltrash = pickweight(nondirectional_walltrash)
+ if(ispath(new_nondirectional_walltrash))
+ var/atom/A = new new_nondirectional_walltrash(T)
+ switch(direction)
+ if(NORTH)
+ A.pixel_y = 32
+ if(SOUTH)
+ A.pixel_y = -32
+ if(EAST)
+ A.pixel_x = 32
+ if(WEST)
+ A.pixel_x = -32
+ break
+
+ CHECK_TICK
+
+ //Generate objective stuff
+ if(linked_objective)
+ var/obj_sanity = 100
+ //Spawn in a sane place.
+ while(obj_sanity > 0)
+ obj_sanity --
+ var/objective_turf = pick(floor_turfs)
+ var/split_loc = splittext(objective_turf, "_")
+ var/turf/T = locate(text2num(split_loc[1]), text2num(split_loc[2]), center_z)
+ if(isspaceturf(T))
+ continue
+ if(locate(/obj/structure) in T)
+ continue
+ linked_objective.generate_objective_stuff(T)
+ break
+ if(!obj_sanity)
+ var/objective_turf = pick(floor_turfs)
+ var/split_loc = splittext(objective_turf, "_")
+ var/turf/T = locate(text2num(split_loc[1]), text2num(split_loc[2]), center_z)
+ linked_objective.generate_objective_stuff(T)
+
+ //Generate research disks
+ for(var/i in 1 to rand(1, 5))
+ var/objective_turf = pick(floor_turfs)
+ var/split_loc = splittext(objective_turf, "_")
+ new /obj/effect/spawner/lootdrop/ruinloot/important(locate(text2num(split_loc[1]), text2num(split_loc[2]), center_z))
+
+ //Spawn dead mosb
+ for(var/mob/M as() in SSzclear.nullspaced_mobs)
+ var/objective_turf = pick(floor_turfs)
+ var/split_loc = splittext(objective_turf, "_")
+ M.forceMove(locate(text2num(split_loc[1]), text2num(split_loc[2]), center_z))
+ SSzclear.nullspaced_mobs.Cut()
+
+ ruin_event?.post_spawn(floor_turfs, center_z)
+
+ //Start running event
+ if(ruin_event)
+ SSorbits.ruin_events += ruin_event
+
+ SSair.unpause_z(center_z)
+
+ log_mapping("Finished generating ruin at [center_x], [center_y], [center_z]")
+
+/proc/pickweight_ruin(list/L)
+ var/total = 0
+ for (var/list/ruin_part as() in L)
+ total += ruin_part["weight"]
+
+ total *= rand()
+ for (var/list/ruin_part as() in L)
+ total -= ruin_part["weight"]
+ if (total <= 0)
+ return ruin_part
+
+ return pick(L)
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/ruin_objects.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/ruin_objects.dm
new file mode 100644
index 0000000000000..876b9f7050069
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/ruin_objects.dm
@@ -0,0 +1,39 @@
+/obj/effect/spawner/structure/ratvar_skewer_trap
+ spawn_list = list(
+ /obj/structure/destructible/clockwork/trap/pressure_sensor,
+ /obj/structure/destructible/clockwork/trap/skewer
+ )
+
+/obj/effect/spawner/structure/ratvar_skewer_trap_kill
+ spawn_list = list(
+ /obj/structure/destructible/clockwork/trap/pressure_sensor,
+ /obj/structure/destructible/clockwork/trap/skewer,
+ /obj/structure/destructible/clockwork/sigil/vitality
+ )
+
+/obj/effect/spawner/structure/ratvar_flipper_trap
+ spawn_list = list(
+ /obj/structure/destructible/clockwork/trap/pressure_sensor,
+ /obj/structure/destructible/clockwork/trap/flipper
+ )
+
+/obj/effect/spawner/ocular_warden_setup/Initialize()
+ var/turf/T = get_turf(src)
+ new /obj/structure/destructible/clockwork/ocular_warden(T)
+ var/turf/open/power_turf = locate() in shuffle(view(3, src))
+ new /obj/structure/destructible/clockwork/sigil/transmission(power_turf)
+ return INITIALIZE_HINT_QDEL
+
+/obj/effect/spawner/ocular_warden_setup/Initialize()
+ var/turf/T = get_turf(src)
+ new /obj/structure/destructible/clockwork/ocular_warden(T)
+ var/turf/open/power_turf = locate() in shuffle(view(3, src))
+ new /obj/structure/destructible/clockwork/sigil/transmission(power_turf)
+ return INITIALIZE_HINT_QDEL
+
+/obj/effect/spawner/interdiction_lens_setup/Initialize()
+ var/turf/T = get_turf(src)
+ new /obj/structure/destructible/clockwork/gear_base/interdiction_lens/free(T)
+ var/turf/open/power_turf = locate() in shuffle(view(3, src))
+ new /obj/structure/destructible/clockwork/sigil/transmission(power_turf)
+ return INITIALIZE_HINT_QDEL
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/ruin_part_loader.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/ruin_part_loader.dm
new file mode 100644
index 0000000000000..2433843d27747
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/ruin_part_loader.dm
@@ -0,0 +1,10 @@
+GLOBAL_LIST_EMPTY(loaded_ruin_parts)
+
+//Reads all ruin parts from the ruin generation file and processes them.
+/proc/load_ruin_parts()
+ GLOB.loaded_ruin_parts.Cut()
+ for(var/subtype in subtypesof(/datum/map_template/ruin_part))
+ var/datum/map_template/ruin_part/ruin_st = new subtype()
+ GLOB.loaded_ruin_parts += ruin_st
+ message_admins("Loaded ruin parts")
+ log_mapping("Ruin parts loaded.")
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/ruin_part_template.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/ruin_part_template.dm
new file mode 100644
index 0000000000000..b9aff1920a2b3
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/ruin_part_template.dm
@@ -0,0 +1,92 @@
+/datum/map_template/ruin_part
+ keep_cached_map = TRUE
+ var/file_name = ""
+ //Weight of the ruin part.
+ var/weight = 0
+ //Positions of the connection points.
+ var/connection_points = list()
+ //Positions of floors.
+ var/floor_locations = list()
+ //Max occurances of this room.
+ var/max_occurances = INFINITY
+ //Is this a loot room (Only 1 loot room spawns per station)
+ var/loot_room = FALSE
+
+/datum/map_template/ruin_part/New(path, rename, cache)
+ mappath = "_maps/RuinGeneration/[file_name].dmm"
+ . = ..(path, rename, TRUE)
+ find_connection_points()
+
+//=======
+// Absolute shitcode of a proc right here
+// Copy paste but editted.
+//=======
+
+//Finds connection points on a template without actually spawning it
+/datum/map_template/ruin_part/proc/find_connection_points()
+ var/key_len = cached_map.key_len
+
+ var/list/modelCache = cached_map.build_cache()
+
+ for(var/I in cached_map.gridSets)
+ var/datum/grid_set/gset = I
+ var/ycrd = gset.ycrd
+
+ for(var/line in gset.gridLines)
+ if(ycrd <= world.maxy && ycrd >= 1)
+ var/xcrd = gset.xcrd
+ for(var/tpos = 1 to length(line) - key_len + 1 step key_len)
+ if(xcrd >= 1)
+ var/model_key = copytext(line, tpos, tpos + key_len)
+ var/list/cache = modelCache[model_key]
+ if(!cache)
+ CRASH("Undefined model key in DMM: [model_key]")
+ build_coordinate(cache, xcrd, ycrd)
+ CHECK_TICK
+ ++xcrd
+ --ycrd
+
+ CHECK_TICK
+
+/datum/map_template/ruin_part/proc/build_coordinate(list/model, xcrd, ycrd)
+ var/index
+ var/list/members = model[1]
+ var/list/members_attributes = model[2]
+
+ ////////////////
+ //Instanciation
+ ////////////////
+
+ //The next part of the code assumes there's ALWAYS an /area AND a /turf on a given tile
+ //first instance the /area and remove it from the members list
+ index = members.len
+
+ //then instance the /turf and, if multiple tiles are presents, simulates the DMM underlays piling effect
+
+ var/first_turf_index = 1
+ while(!ispath(members[first_turf_index], /turf)) //find first /turf object in members
+ first_turf_index++
+
+ if(ispath(members[first_turf_index], /turf/open))
+ floor_locations["[xcrd]_[ycrd]"] = TRUE
+
+ //finally instance all remainings objects/mobs
+ for(index in 1 to first_turf_index-1)
+ instance_atom(members[index],members_attributes[index], xcrd, ycrd)
+
+/datum/map_template/ruin_part/proc/instance_atom(path,list/attributes, xcrd, ycrd)
+ var/dir = SOUTH
+
+ if(attributes.Find("dir"))
+ dir = attributes["dir"]
+ if(istext(dir))
+ dir = text2num(dir)
+
+ if(ispath(path, /obj/effect/abstract/open_area_marker))
+ connection_points["[xcrd]_[ycrd]"] = OPEN_CONNECTION * dir
+ else if(ispath(path, /obj/effect/abstract/doorway_marker))
+ connection_points["[xcrd]_[ycrd]"] = ROOM_CONNECTION * dir
+
+ //custom CHECK_TICK here because we don't want things created while we're sleeping to not initialize
+ if(TICK_CHECK)
+ stoplag()
diff --git a/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/ruin_part_types.dm b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/ruin_part_types.dm
new file mode 100644
index 0000000000000..55bb473047ee1
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/orbital_poi_generator/ruin_generator/ruin_part_types.dm
@@ -0,0 +1,482 @@
+/*
+ * All the different ruin part types.
+ * Beacuse I couldn't figure out how to read every file in a directory
+ * General rule for decent levels.
+ * - Peices that have only 1 connection should be of low weight
+ * - Hallway Ts and Xs should be of low weight if they don't have rooms attached
+ * - Straight hallways should have a middle weight
+ * - Interesting rooms with multiple connections should have a higher weight.
+ */
+
+/datum/map_template/ruin_part/hallwaycross
+ file_name = "5x5_0_hallwaycross"
+ weight = 1
+
+/datum/map_template/ruin_part/hallwayvertical_eastwest_room
+ file_name = "5x5_1_hallwayvertical_eastwest_room"
+ weight = 1
+
+/datum/map_template/ruin_part/room_dorm
+ file_name = "5x5_4_room-dorm"
+ weight = 3
+
+/datum/map_template/ruin_part/room_janitor
+ file_name = "5x5_4_room-janitor_closet"
+ weight = 3
+
+/datum/map_template/ruin_part/room_storage
+ file_name = "5x5_4_room-storage"
+ weight = 3
+
+/datum/map_template/ruin_part/room_toilet
+ file_name = "5x5_4_room-toilet"
+ weight = 3
+
+/datum/map_template/ruin_part/hallwayroom_east
+ file_name = "5x5_5_hallwayroom_east"
+ weight = 4
+
+/datum/map_template/ruin_part/hallwayroom_west
+ file_name = "5x5_5_hallwayroom_west"
+ weight = 4
+
+/datum/map_template/ruin_part/hallwayvertical_westroom
+ file_name = "5x5_6_hallwayvertical_west_room"
+ weight = 4
+
+/datum/map_template/ruin_part/hallwayvertical_eastroom
+ file_name = "5x5_6_hallwayvertical_east_room"
+ weight = 4
+
+/datum/map_template/ruin_part/hallway_t_east
+ file_name = "5x5_8_hallwayt-east"
+ weight = 2
+
+/datum/map_template/ruin_part/hallway_t_north
+ file_name = "5x5_8_hallwayt-north"
+ weight = 2
+
+/datum/map_template/ruin_part/hallway_t_south
+ file_name = "5x5_8_hallwayt-south"
+ weight = 2
+
+/datum/map_template/ruin_part/hallway_t_west
+ file_name = "5x5_8_hallwayt-west"
+ weight = 2
+
+/datum/map_template/ruin_part/hallway_end_east
+ file_name = "5x5_14_hallway-end-east"
+ weight = 2
+
+/datum/map_template/ruin_part/hallway_end_north
+ file_name = "5x5_14_hallway-end-north"
+ weight = 2
+
+/datum/map_template/ruin_part/hallway_end_south
+ file_name = "5x5_14_hallway-end-south"
+ weight = 2
+
+/datum/map_template/ruin_part/hallway_end_west
+ file_name = "5x5_14_hallway-end-west"
+ weight = 2
+
+/datum/map_template/ruin_part/hallway_horizontal
+ file_name = "5x5_20_hallwayhorizontal"
+ weight = 2
+
+/datum/map_template/ruin_part/hallway_vertical
+ file_name = "5x5_20_hallwayvertical"
+ weight = 2
+
+/datum/map_template/ruin_part/separation
+ file_name = "9x5_3_seperation"
+ weight = 6
+
+/datum/map_template/ruin_part/checkpoint
+ file_name = "9x9_checkpoint"
+ weight = 5
+
+/datum/map_template/ruin_part/corgarmoury
+ file_name = "13x13_corgarmoury"
+ weight = 5
+ loot_room = TRUE
+
+/datum/map_template/ruin_part/corgrobotics
+ file_name = "13x13_corgrobotics"
+ weight = 4
+
+/datum/map_template/ruin_part/windowroom
+ file_name = "5x9_windowroom"
+ weight = 5
+
+/datum/map_template/ruin_part/cargoroom
+ file_name = "17x13_cargo"
+ weight = 2
+
+/datum/map_template/ruin_part/donutroom
+ file_name = "13x13_donutroom"
+ weight = 4
+
+/datum/map_template/ruin_part/singularity
+ file_name = "21x21_singularity"
+ weight = 3
+ max_occurances = 1
+
+/datum/map_template/ruin_part/maintroom
+ file_name = "9x5_maintroom"
+ weight = 4
+
+/datum/map_template/ruin_part/shuttledock
+ file_name = "13x17_shuttledock"
+ weight = 2
+
+/datum/map_template/ruin_part/kitchen
+ file_name = "9x13_kitchen"
+ weight = 6
+
+/datum/map_template/ruin_part/sleeproom
+ file_name = "9x13_sleeproom"
+ weight = 4
+
+/datum/map_template/ruin_part/cryo
+ file_name = "5x5_cryo"
+ weight = 2
+
+/datum/map_template/ruin_part/solars
+ file_name = "21x19_solars"
+ weight = 3
+ max_occurances = 2
+
+/datum/map_template/ruin_part/permbrig
+ file_name = "13x17_permabrig"
+ weight = 3
+ max_occurances = 2
+
+/datum/map_template/ruin_part/shotelroom
+ file_name = "13x13_shotelroom"
+ weight = 2
+
+/datum/map_template/ruin_part/supermattercontainment
+ file_name = "13x13_supermatter_containment"
+ weight = 4
+ max_occurances = 1
+
+/datum/map_template/ruin_part/gateway
+ file_name = "5x9_gateway"
+ weight = 1
+ max_occurances = 1
+
+/datum/map_template/ruin_part/shower
+ file_name = "5x5_shower"
+ weight = 2
+
+//its 13x13 lol
+// !! Map file uses broken turbo-lift components !!
+/*/datum/map_template/ruin_part/elevator
+ file_name = "9x9_elevator"
+ weight = 4*/
+
+/datum/map_template/ruin_part/hallwaymaints
+ file_name = "9x5_hallwaymaints"
+ weight = 4
+
+/datum/map_template/ruin_part/toxinroom
+ file_name = "9x9_toxinstorage"
+ weight = 2
+
+/datum/map_template/ruin_part/josito
+ file_name = "13x9_josito"
+ weight = 3
+ max_occurances = 1
+
+/datum/map_template/ruin_part/pizzaguard
+ file_name = "13x17_pizzaroom"
+ weight = 3
+ loot_room = TRUE
+
+//Damaged halls
+
+/datum/map_template/ruin_part/hern_damaged
+ file_name = "5x5_hern_damaged"
+ weight = 0.3
+
+/datum/map_template/ruin_part/hernsw_damaged
+ file_name = "5x5_hernsw_damaged"
+ weight = 0.3
+
+/datum/map_template/ruin_part/hernw_damaged
+ file_name = "5x5_hernw_damaged"
+ weight = 0.3
+
+/datum/map_template/ruin_part/hers_damaged
+ file_name = "5x5_hers_damaged"
+ weight = 0.3
+
+/datum/map_template/ruin_part/hersw_damaged
+ file_name = "5x5_hersw_damaged"
+ weight = 0.3
+
+/datum/map_template/ruin_part/hesrnw_damaged
+ file_name = "5x5_hesrnw_damaged"
+ weight = 0.3
+
+/datum/map_template/ruin_part/hewrn_damaged
+ file_name = "5x5_hewrn_damaged"
+ weight = 0.3
+
+/datum/map_template/ruin_part/hewrns_damaged
+ file_name = "5x5_hewrns_damaged"
+ weight = 0.3
+
+/datum/map_template/ruin_part/hewrs_damaged
+ file_name = "5x5_hewrs_damaged"
+ weight = 0.3
+
+/datum/map_template/ruin_part/hnersw_damaged
+ file_name = "5x5_hnersw_damaged"
+ weight = 0.2
+
+/datum/map_template/ruin_part/hnesrw_damaged
+ file_name = "5x5_hnesrw_damaged"
+ weight = 0.2
+
+/datum/map_template/ruin_part/hnewrs_damaged
+ file_name = "5x5_hnewrs_damaged"
+ weight = 0.2
+
+/datum/map_template/ruin_part/hnresw_damaged
+ file_name = "5x5_hnresw_damaged"
+ weight = 0.2
+
+/datum/map_template/ruin_part/hnswre_damaged
+ file_name = "5x5_hnswre_damaged"
+ weight = 0.2
+
+/datum/map_template/ruin_part/hnwres_damaged
+ file_name = "5x5_hnwres_damaged"
+ weight = 0.2
+
+/datum/map_template/ruin_part/hsrnew_damaged
+ file_name = "5x5_hsrnew_damaged"
+ weight = 0.2
+
+/datum/map_template/ruin_part/hswrne_damaged
+ file_name = "5x5_hswrne_damaged"
+ weight = 0.2
+
+/datum/map_template/ruin_part/hwres_damaged
+ file_name = "5x5_hwres_damaged"
+ weight = 0.2
+
+/datum/map_template/ruin_part/hwrn_damaged
+ file_name = "5x5_hwrn_damaged"
+ weight = 0.2
+
+/datum/map_template/ruin_part/hwrne_damaged
+ file_name = "5x5_hwrne_damaged"
+ weight = 0.2
+
+/datum/map_template/ruin_part/hwrnes_damaged
+ file_name = "5x5_hwrnes_damaged"
+ weight = 0.2
+
+/datum/map_template/ruin_part/hwrns_damaged
+ file_name = "5x5_hwrns_damaged"
+ weight = 0.2
+
+/datum/map_template/ruin_part/hwrs_damaged
+ file_name = "5x5_hwrs_damaged"
+ weight = 0.2
+
+/datum/map_template/ruin_part/hew_damaged
+ file_name = "5x5_hew_damaged"
+ weight = 0.2
+
+/datum/map_template/ruin_part/hns_damaged
+ file_name = "5x5_hns_damaged"
+ weight = 0.2
+
+//End damaged halls
+
+/datum/map_template/ruin_part/hern
+ file_name = "5x5_hern"
+ weight = 2
+
+/datum/map_template/ruin_part/hernsw
+ file_name = "5x5_hernsw"
+ weight = 1
+
+/datum/map_template/ruin_part/hernw
+ file_name = "5x5_hernw"
+ weight = 1
+
+/datum/map_template/ruin_part/hers
+ file_name = "5x5_hers"
+ weight = 2
+
+/datum/map_template/ruin_part/hersw
+ file_name = "5x5_hersw"
+ weight = 1
+
+/datum/map_template/ruin_part/hesrnw
+ file_name = "5x5_hesrnw"
+ weight = 1
+
+/datum/map_template/ruin_part/hewrn
+ file_name = "5x5_hewrn"
+ weight = 1
+
+/datum/map_template/ruin_part/hewrns
+ file_name = "5x5_hewrns"
+ weight = 1
+
+/datum/map_template/ruin_part/hewrs
+ file_name = "5x5_hewrs"
+ weight = 1
+
+/datum/map_template/ruin_part/hnersw
+ file_name = "5x5_hnersw"
+ weight = 1
+
+/datum/map_template/ruin_part/hnesrw
+ file_name = "5x5_hnesrw"
+ weight = 1
+
+/datum/map_template/ruin_part/hnewrs
+ file_name = "5x5_hnewrs"
+ weight = 1
+
+/datum/map_template/ruin_part/hnresw
+ file_name = "5x5_hnresw"
+ weight = 1
+
+/datum/map_template/ruin_part/hnswre
+ file_name = "5x5_hnswre"
+ weight = 1
+
+/datum/map_template/ruin_part/hnwres
+ file_name = "5x5_hnwres"
+ weight = 1
+
+/datum/map_template/ruin_part/hsrnew
+ file_name = "5x5_hsrnew"
+ weight = 1
+
+/datum/map_template/ruin_part/hswrne
+ file_name = "5x5_hswrne"
+ weight = 1
+
+/datum/map_template/ruin_part/hwres
+ file_name = "5x5_hwres"
+ weight = 1
+
+/datum/map_template/ruin_part/hwrn
+ file_name = "5x5_hwrn"
+ weight = 2
+
+/datum/map_template/ruin_part/hwrne
+ file_name = "5x5_hwrne"
+ weight = 1
+
+/datum/map_template/ruin_part/hwrnes
+ file_name = "5x5_hwrnes"
+ weight = 1
+
+/datum/map_template/ruin_part/hwrns
+ file_name = "5x5_hwrns"
+ weight = 1
+
+/datum/map_template/ruin_part/hwrs
+ file_name = "5x5_hwrs"
+ weight = 2
+
+/datum/map_template/ruin_part/roomcross
+ file_name = "5x5_roomcross"
+ weight = 1
+
+/datum/map_template/ruin_part/chemlab
+ file_name = "9x9_chemlab"
+ weight = 3
+ max_occurances = 2
+
+/datum/map_template/ruin_part/lounge
+ file_name = "5x9_lounge"
+ weight = 4
+
+/datum/map_template/ruin_part/researchlab
+ file_name = "13x9_researchlab"
+ weight = 2
+ //Contains a research disk
+ max_occurances = 1
+
+/datum/map_template/ruin_part/hilberttest
+ file_name = "13x13_hilberttest"
+ weight = 5
+ loot_room = TRUE
+ max_occurances = 1
+
+/datum/map_template/ruin_part/ailab
+ file_name = "13x13_ai-lab"
+ weight = 5
+ loot_room = TRUE
+ max_occurances = 1
+
+/datum/map_template/ruin_part/cratestorage
+ file_name = "13x9_cratestorage"
+ weight = 3
+ max_occurances = 1
+
+/datum/map_template/ruin_part/medstorage
+ file_name = "9x13_medstorage"
+ weight = 3
+
+/datum/map_template/ruin_part/morgue
+ file_name = "9x5_morgue"
+ weight = 4
+
+/datum/map_template/ruin_part/charliestation_mini
+ file_name = "17x17_charliecrew"
+ weight = 1
+ max_occurances = 1
+
+/datum/map_template/ruin_part/charliestation
+ file_name = "69x45_charliestation"
+ weight = 0.2
+ loot_room = TRUE
+ max_occurances = 1
+
+/datum/map_template/ruin_part/corgasteroid
+ file_name = "41x41_corgasteroid"
+ weight = 1
+ max_occurances = 1
+
+/datum/map_template/ruin_part/teleporter
+ file_name = "9x13_teleporter"
+ weight = 1
+ max_occurances = 1
+
+/datum/map_template/ruin_part/medicalroom
+ file_name = "13x9_medical"
+ weight = 3
+
+/datum/map_template/ruin_part/genetics
+ file_name = "9x9_genetics"
+ weight = 3
+
+/datum/map_template/ruin_part/northairlock
+ file_name = "5x9_northernairlock"
+ weight = 4
+
+/datum/map_template/ruin_part/southairlock
+ file_name = "5x9_southernairlock"
+ weight = 4
+
+/datum/map_template/ruin_part/shuttledock_inside
+ file_name = "21x17_shuttledock"
+ weight = 4
+ max_occurances = 1
+
+/datum/map_template/ruin_part/syndicate_listening
+ file_name = "13x13_listening_base"
+ weight = 2
+ loot_room = TRUE
diff --git a/code/modules/shuttle/super_cruise/shuttle_components/plasma_refiner.dm b/code/modules/shuttle/super_cruise/shuttle_components/plasma_refiner.dm
new file mode 100644
index 0000000000000..055cfa5a35061
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/shuttle_components/plasma_refiner.dm
@@ -0,0 +1,22 @@
+/obj/machinery/atmospherics/components/unary/plasma_refiner
+ name = "plasma refinery"
+ desc = "A refinery that burns plasma sheets into plasma gas."
+ icon_state = "plasma_refinery"
+ density = TRUE
+ var/moles_per_ore = 50
+
+/obj/machinery/atmospherics/components/unary/plasma_refiner/process_atmos()
+ update_parents()
+
+/obj/machinery/atmospherics/components/unary/plasma_refiner/attackby(obj/item/W, mob/user, params)
+ if(istype(W, /obj/item/stack/ore/plasma) || istype(W, /obj/item/stack/sheet/mineral/plasma))
+ var/obj/item/stack/stack = W
+ var/moles_created = moles_per_ore * stack.amount
+ var/datum/gas_mixture/air_contents = airs[1]
+ if(!air_contents)
+ return
+ qdel(stack)
+ air_contents.adjust_moles(GAS_PLASMA, moles_created)
+ say("[moles_created] moles of plasma refined.")
+ return
+ . = ..()
diff --git a/code/modules/shuttle/super_cruise/shuttle_components/shuttle_console.dm b/code/modules/shuttle/super_cruise/shuttle_components/shuttle_console.dm
new file mode 100644
index 0000000000000..ef020c80538ce
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/shuttle_components/shuttle_console.dm
@@ -0,0 +1,532 @@
+GLOBAL_VAR_INIT(shuttle_docking_jammed, FALSE)
+
+/obj/machinery/computer/shuttle_flight
+ name = "shuttle console"
+ desc = "A shuttle control computer."
+ icon_screen = "shuttle"
+ icon_keyboard = "tech_key"
+ light_color = LIGHT_COLOR_CYAN
+ req_access = list()
+ var/shuttleId
+
+ //Interdiction range
+ var/interdiction_range = 150
+ //Time it takes to recharge after interdiction
+ var/interdiction_time = 3 MINUTES
+
+ //For recall consoles
+ //If not set to an empty string, will display only the option to call the shuttle to that dock.
+ //Once pressed the shuttle will engage autopilot and return to the dock.
+ var/recall_docking_port_id = ""
+
+ var/request_shuttle_message = "Request Shuttle"
+
+ //Admin controlled shuttles
+ var/admin_controlled = FALSE
+
+ //Used for mapping mainly
+ var/possible_destinations = ""
+ var/list/valid_docks = list("")
+
+ //The current orbital map we are observing
+ var/orbital_map_index = PRIMARY_ORBITAL_MAP
+
+ //Our orbital body.
+ var/datum/orbital_object/shuttle/shuttleObject
+
+/obj/machinery/computer/shuttle_flight/Initialize(mapload, obj/item/circuitboard/C)
+ . = ..()
+ valid_docks = params2list(possible_destinations)
+ if(shuttleId)
+ shuttlePortId = "[shuttleId]_custom"
+ else
+ var/static/i = 0
+ shuttlePortId = "unlinked_shuttle_console_[i++]"
+
+/obj/machinery/computer/shuttle_flight/Destroy()
+ . = ..()
+ SSorbits.open_orbital_maps -= SStgui.get_all_open_uis(src)
+ shuttleObject = null
+
+/obj/machinery/computer/shuttle_flight/process()
+ . = ..()
+
+ //Check to see if the shuttleobject was launched by another console.
+ if(QDELETED(shuttleObject) && SSorbits.assoc_shuttles.Find(shuttleId))
+ shuttleObject = SSorbits.assoc_shuttles[shuttleId]
+
+ if(recall_docking_port_id && shuttleObject?.docking_target && shuttleObject.autopilot && shuttleObject.shuttleTarget == shuttleObject.docking_target && shuttleObject.controlling_computer == src)
+ //We are at destination, dock.
+ shuttleObject.controlling_computer = null
+ switch(SSshuttle.moveShuttle(shuttleId, recall_docking_port_id, 1))
+ if(0)
+ say("Shuttle has arrived at destination.")
+ QDEL_NULL(shuttleObject)
+ if(1)
+ to_chat(usr, "Invalid shuttle requested.")
+ else
+ to_chat(usr, "Unable to comply.")
+
+/obj/machinery/computer/shuttle_flight/ui_state(mob/user)
+ return GLOB.default_state
+
+/obj/machinery/computer/shuttle_flight/ui_interact(mob/user, datum/tgui/ui)
+ if(!allowed(user) && !isobserver(user))
+ say("Insufficient access rights.")
+ return
+ //Ash walkers cannot use the console because they are unga bungas
+ if(user.mind?.has_antag_datum(/datum/antagonist/ashwalker))
+ to_chat(user, "This computer has been designed to keep the natives like you from meddling with it, you have no hope of using it.")
+ return
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "OrbitalMap")
+ ui.open()
+ SSorbits.open_orbital_maps |= ui
+ ui.set_autoupdate(FALSE)
+
+/obj/machinery/computer/shuttle_flight/ui_close(mob/user, datum/tgui/tgui)
+ SSorbits.open_orbital_maps -= tgui
+
+/obj/machinery/computer/shuttle_flight/ui_static_data(mob/user)
+ var/list/data = list()
+ //The docks we can dock with never really changes
+ //This is used for the forced autopilot mode where it goes to a set port.
+ data["destination_docks"] = list()
+ for(var/dock in valid_docks)
+ data["valid_dock"] += list(list(
+ "id" = dock,
+ ))
+ //If we are a recall console.
+ data["recall_docking_port_id"] = recall_docking_port_id
+ data["request_shuttle_message"] = request_shuttle_message
+ data["interdiction_range"] = interdiction_range
+ return data
+
+/obj/machinery/computer/shuttle_flight/ui_data(mob/user)
+ var/list/data = list()
+ data["update_index"] = SSorbits.times_fired
+ //Add orbital bodies
+ data["map_objects"] = list()
+ var/datum/orbital_map/showing_map = SSorbits.orbital_maps[orbital_map_index]
+ for(var/map_key in showing_map.collision_zone_bodies)
+ for(var/datum/orbital_object/object as() in showing_map.collision_zone_bodies[map_key])
+ if(!object)
+ continue
+ //we can't see it, unless we are stealth too
+ if(shuttleObject)
+ if(object != shuttleObject && (object.stealth && !shuttleObject.stealth))
+ continue
+ else
+ if(object.stealth)
+ continue
+ //Send to be rendered on the UI
+ data["map_objects"] += list(list(
+ "name" = object.name,
+ "position_x" = object.position.x,
+ "position_y" = object.position.y,
+ "velocity_x" = object.velocity.x * object.velocity_multiplier,
+ "velocity_y" = object.velocity.y * object.velocity_multiplier,
+ "radius" = object.radius
+ ))
+ if(!SSshuttle.getShuttle(shuttleId))
+ data["linkedToShuttle"] = FALSE
+ return data
+ //Interdicted shuttles
+ data["interdictedShuttles"] = list()
+ if(SSorbits.interdicted_shuttles[shuttleId] > world.time)
+ var/obj/docking_port/our_port = SSshuttle.getShuttle(shuttleId)
+ data["interdictionTime"] = SSorbits.interdicted_shuttles[shuttleId] - world.time
+ for(var/interdicted_id in SSorbits.interdicted_shuttles)
+ var/timer = SSorbits.interdicted_shuttles[interdicted_id]
+ if(timer < world.time)
+ continue
+ var/obj/docking_port/port = SSshuttle.getShuttle(interdicted_id)
+ if(port && port.get_virtual_z_level() == our_port.get_virtual_z_level())
+ data["interdictedShuttles"] += list(list(
+ "shuttleName" = port.name,
+ "x" = port.x - our_port.x,
+ "y" = port.y - our_port.y,
+ ))
+ else
+ data["interdictionTime"] = 0
+
+ data["canLaunch"] = TRUE
+ if(QDELETED(shuttleObject))
+ data["linkedToShuttle"] = FALSE
+ return data
+ data["autopilot"] = shuttleObject.autopilot
+ data["linkedToShuttle"] = TRUE
+ data["shuttleTarget"] = shuttleObject.shuttleTarget?.name
+ data["shuttleName"] = shuttleObject.name
+ data["shuttleAngle"] = shuttleObject.angle
+ data["shuttleThrust"] = shuttleObject.thrust
+ data["autopilot_enabled"] = shuttleObject.autopilot
+ data["desired_vel_x"] = shuttleObject.desired_vel_x
+ data["desired_vel_y"] = shuttleObject.desired_vel_y
+ if(shuttleObject?.shuttleTarget)
+ data["shuttleVelX"] = shuttleObject.velocity.x - shuttleObject.shuttleTarget.velocity.x
+ data["shuttleVelY"] = shuttleObject.velocity.y - shuttleObject.shuttleTarget.velocity.y
+ else
+ data["shuttleVelX"] = shuttleObject.velocity.x
+ data["shuttleVelY"] = shuttleObject.velocity.y
+ //Docking data
+ data["canDock"] = shuttleObject.can_dock_with != null && !shuttleObject.docking_frozen
+ data["isDocking"] = shuttleObject.docking_target != null && !shuttleObject.docking_frozen && !shuttleObject.docking_target.is_generating
+ data["shuttleTargetX"] = shuttleObject.shuttleTargetPos?.x
+ data["shuttleTargetY"] = shuttleObject.shuttleTargetPos?.y
+ data["validDockingPorts"] = list()
+ if(shuttleObject.docking_target && !shuttleObject.docking_frozen)
+ //Stealth shuttles bypass shuttle jamming.
+ if(shuttleObject.docking_target.can_dock_anywhere && (!GLOB.shuttle_docking_jammed || shuttleObject.stealth || !istype(shuttleObject.docking_target, /datum/orbital_object/z_linked/station)))
+ data["validDockingPorts"] += list(list(
+ "name" = "Custom Location",
+ "id" = "custom_location"
+ ))
+ else if(shuttleObject.docking_target.random_docking)
+ data["validDockingPorts"] += list(list(
+ "name" = "Random Drop",
+ "id" = "custom_location"
+ ))
+ for(var/obj/docking_port/stationary/stationary_port as() in SSshuttle.stationary)
+ if(LAZYLEN(shuttleObject.docking_target.linked_z_level))
+ for(var/datum/space_level/level in shuttleObject.docking_target.linked_z_level)
+ if(stationary_port.z == level.z_value && (stationary_port.id in valid_docks))
+ data["validDockingPorts"] += list(list(
+ "name" = stationary_port.name,
+ "id" = stationary_port.id,
+ ))
+ return data
+
+/obj/machinery/computer/shuttle_flight/ui_act(action, params)
+ . = ..()
+
+ if(.)
+ return
+
+ if(!allowed(usr))
+ say("Insufficient access rights.")
+ return
+
+ if(admin_controlled)
+ say("This shuttle is restricted to authorised personnel only.")
+ return
+
+ if(recall_docking_port_id)
+ switch(action)
+ if("callShuttle")
+ //Find the z-level that the dock is on
+ var/obj/docking_port/stationary/target_port = SSshuttle.getDock(recall_docking_port_id)
+ if(!target_port)
+ say("Unable to locate port location.")
+ return
+ //Locate linked shuttle
+ var/obj/docking_port/mobile/shuttle = SSshuttle.getShuttle(shuttleId)
+ if(!shuttle)
+ say("Unable to locate linked shuttle.")
+ return
+ if(target_port in shuttle.loc)
+ say("Shuttle is already at destination.")
+ return
+ //Locate the orbital object
+ var/datum/orbital_map/viewing_map = SSorbits.orbital_maps[orbital_map_index]
+ for(var/map_key in viewing_map.collision_zone_bodies)
+ for(var/datum/orbital_object/z_linked/z_linked as() in viewing_map.collision_zone_bodies[map_key])
+ if(!istype(z_linked))
+ continue
+ if(z_linked.z_in_contents(target_port.z))
+ if(!SSorbits.assoc_shuttles.Find(shuttleId))
+ //Launch the shuttle
+ if(!launch_shuttle())
+ return
+ if(shuttleObject.shuttleTarget == z_linked && shuttleObject.controlling_computer == src)
+ return
+ shuttleObject = SSorbits.assoc_shuttles[shuttleId]
+ shuttleObject.shuttleTarget = z_linked
+ shuttleObject.autopilot = TRUE
+ shuttleObject.controlling_computer = src
+ say("Shuttle requested.")
+ return
+ say("Docking port in invalid location. Please contact a Nanotrasen technician.")
+ return
+
+ switch(action)
+ if("setTarget")
+ if(QDELETED(shuttleObject))
+ say("Shuttle not in flight.")
+ return
+ var/desiredTarget = params["target"]
+ if(shuttleObject.name == desiredTarget)
+ return
+ var/datum/orbital_map/showing_map = SSorbits.orbital_maps[orbital_map_index]
+ for(var/map_key in showing_map.collision_zone_bodies)
+ for(var/datum/orbital_object/object as() in showing_map.collision_zone_bodies[map_key])
+ if(object.name == desiredTarget)
+ shuttleObject.shuttleTarget = object
+ return
+ if("setThrust")
+ if(QDELETED(shuttleObject))
+ say("Shuttle not in flight.")
+ return
+ if(shuttleObject.autopilot)
+ to_chat(usr, "Shuttle is controlled by autopilot.")
+ return
+ shuttleObject.thrust = CLAMP(params["thrust"], 0, 100)
+ if("setAngle")
+ if(QDELETED(shuttleObject))
+ say("Shuttle not in flight.")
+ return
+ if(shuttleObject.autopilot)
+ to_chat(usr, "Shuttle is controlled by autopilot.")
+ return
+ shuttleObject.angle = params["angle"]
+ if("nautopilot")
+ if(QDELETED(shuttleObject) || !shuttleObject.shuttleTarget)
+ return
+ shuttleObject.autopilot = !shuttleObject.autopilot
+ shuttleObject.shuttleTargetPos = null
+ //Launch the shuttle. Lets do this.
+ if("launch")
+ launch_shuttle()
+ //Dock at location.
+ if("dock")
+ if(QDELETED(shuttleObject))
+ say("Docking computer offline.")
+ return
+ if(!shuttleObject.can_dock_with)
+ say("Docking computer failed to find docking target.")
+ return
+ //Force dock with the thing we are colliding with.
+ shuttleObject.commence_docking(shuttleObject.can_dock_with, TRUE)
+ if("setTargetCoords")
+ if(QDELETED(shuttleObject))
+ return
+ var/x = text2num(params["x"])
+ var/y = text2num(params["y"])
+ if(!shuttleObject.shuttleTargetPos)
+ shuttleObject.shuttleTargetPos = new(x, y)
+ else
+ shuttleObject.shuttleTargetPos.x = x
+ shuttleObject.shuttleTargetPos.y = y
+ shuttleObject.autopilot = FALSE
+ . = TRUE
+ if("interdict")
+ if(QDELETED(shuttleObject))
+ say("Interdictor not ready.")
+ return
+ if(shuttleObject.docking_target || shuttleObject.can_dock_with)
+ say("Cannot use interdictor while docking.")
+ return
+ if(shuttleObject.stealth)
+ say("Cannot use interdictor on stealthed shuttles.")
+ return
+ var/list/interdicted_shuttles = list()
+ for(var/shuttleportid in SSorbits.assoc_shuttles)
+ var/datum/orbital_object/shuttle/other_shuttle = SSorbits.assoc_shuttles[shuttleportid]
+ //Do this last
+ if(other_shuttle == shuttleObject)
+ continue
+ if(other_shuttle?.position?.Distance(shuttleObject.position) <= interdiction_range && !other_shuttle.stealth)
+ interdicted_shuttles += other_shuttle
+ if(!length(interdicted_shuttles))
+ say("No targets to interdict in range.")
+ return
+ say("Interdictor activated, shuttle throttling down...")
+ //Create the site of interdiction
+ var/datum/orbital_object/z_linked/beacon/z_linked = new /datum/orbital_object/z_linked/beacon/ruin/interdiction(
+ new /datum/orbital_vector(shuttleObject.position.x, shuttleObject.position.y)
+ )
+ z_linked.name = "Interdiction Site"
+ //Lets tell everyone about it
+ priority_announce("Supercruise interdiction detected, interdicted shuttles have been registered onto local GPS units. Source: [shuttleObject.name]")
+ //Get all shuttle objects in range
+ for(var/datum/orbital_object/shuttle/other_shuttle in interdicted_shuttles)
+ other_shuttle.commence_docking(z_linked, TRUE)
+ random_drop(other_shuttle, other_shuttle.shuttle_port_id)
+ SSorbits.interdicted_shuttles[other_shuttle.shuttle_port_id] = world.time + interdiction_time
+ shuttleObject.commence_docking(z_linked, TRUE)
+ random_drop()
+ SSorbits.interdicted_shuttles[shuttleId] = world.time + interdiction_time
+ //Go to valid port
+ if("gotoPort")
+ if(QDELETED(shuttleObject))
+ say("Shuttle has already landed, cannot dock at this time.")
+ return
+ if(QDELETED(shuttleObject.docking_target))
+ say("Docking target lost, please re-establish orbital trajectory.")
+ return
+ if(shuttleObject.docking_frozen)
+ say("Cannot dock at this time.")
+ return
+ if(shuttleObject.docking_target.is_generating)
+ say("Please wait for docking computer to align...")
+ return
+ //Get our port
+ var/obj/docking_port/mobile/mobile_port = SSshuttle.getShuttle(shuttleId)
+ if(!mobile_port || mobile_port.destination != null)
+ return
+ //Check ready
+ if(mobile_port.mode == SHUTTLE_RECHARGING)
+ say("Supercruise Warning: Shuttle engines not ready for use.")
+ return
+ if(mobile_port.mode != SHUTTLE_CALL || mobile_port.destination)
+ say("Supercruise Warning: Already dethrottling shuttle.")
+ return
+ //Special check
+ if(params["port"] == "custom_location")
+ //Open up internal docking computer if any location is allowed.
+ if(shuttleObject.docking_target.can_dock_anywhere)
+ if(GLOB.shuttle_docking_jammed)
+ say("Shuttle docking computer jammed.")
+ return
+ if(current_user)
+ to_chat(usr, "Somebody is already docking the shuttle.")
+ return
+ view_range = max(mobile_port.width, mobile_port.height) + 4
+ give_eye_control(usr)
+ eyeobj.forceMove(locate(world.maxx * 0.5, world.maxy * 0.5, shuttleObject.docking_target.linked_z_level[1].z_value))
+ return
+ //If random dropping is allowed, random drop.
+ if(shuttleObject.docking_target.random_docking)
+ random_drop()
+ return
+ //Report exploit
+ log_admin("[usr] attempted to forge a target location through a tgui exploit on [src]")
+ message_admins("[ADMIN_FULLMONTY(usr)] attempted to forge a target location through a tgui exploit on [src]")
+ return
+ //Find the target port
+ var/obj/docking_port/stationary/target_port = SSshuttle.getDock(params["port"])
+ if(!target_port)
+ return
+ if(!(target_port.id in valid_docks))
+ log_admin("[usr] attempted to forge a target location through a tgui exploit on [src]")
+ message_admins("[ADMIN_FULLMONTY(usr)] attempted to forge a target location through a tgui exploit on [src]")
+ return
+ //Dont wipe z level while we are going
+ //Dont wipe z of where we are leaving for a bit, in case we come back.
+ SSzclear.temp_keep_z(z)
+ SSzclear.temp_keep_z(target_port.z)
+ switch(SSshuttle.moveShuttle(shuttleId, target_port.id, 1))
+ if(0)
+ say("Initiating supercruise throttle-down, prepare for landing.")
+ if(current_user)
+ remove_eye_control(current_user)
+ QDEL_NULL(shuttleObject)
+ //Hold the shuttle in the docking position until ready.
+ mobile_port.setTimer(INFINITY)
+ say("Waiting for hyperspace lane...")
+ INVOKE_ASYNC(src, .proc/unfreeze_shuttle, mobile_port, SSmapping.get_level(target_port.z))
+ if(1)
+ to_chat(usr, "Invalid shuttle requested.")
+ else
+ to_chat(usr, "Unable to comply.")
+
+/obj/machinery/computer/shuttle_flight/proc/launch_shuttle()
+ if(SSorbits.interdicted_shuttles.Find(shuttleId))
+ if(world.time < SSorbits.interdicted_shuttles[shuttleId])
+ var/time_left = (SSorbits.interdicted_shuttles[shuttleId] - world.time) * 0.1
+ say("Supercruise Warning: Engines have been interdicted and will be recharged in [time_left] seconds.")
+ return
+ var/obj/docking_port/mobile/mobile_port = SSshuttle.getShuttle(shuttleId)
+ if(!mobile_port)
+ return
+ if(mobile_port.mode == SHUTTLE_RECHARGING)
+ say("Supercruise Warning: Shuttle engines not ready for use.")
+ return
+ if(mobile_port.mode != SHUTTLE_IDLE)
+ say("Supercruise Warning: Shuttle already in transit.")
+ return
+ if(SSorbits.assoc_shuttles.Find(shuttleId))
+ say("Shuttle is controlled from another location, updating telemetry.")
+ shuttleObject = SSorbits.assoc_shuttles[shuttleId]
+ return shuttleObject
+ shuttleObject = mobile_port.enter_supercruise()
+ if(!shuttleObject)
+ say("Failed to enter supercruise due to an unknown error.")
+ return
+ shuttleObject.valid_docks = valid_docks
+ return shuttleObject
+
+/obj/machinery/computer/shuttle_flight/proc/random_drop(datum/orbital_object/shuttle/_shuttleObject = shuttleObject, _shuttleId = shuttleId)
+ //Find a random place to drop in at.
+ if(!(_shuttleObject?.docking_target?.linked_z_level))
+ return FALSE
+ //Get shuttle dock
+ var/obj/docking_port/mobile/shuttle_dock = SSshuttle.getShuttle(_shuttleId)
+ if(!shuttle_dock)
+ return FALSE
+ var/datum/space_level/target_spacelevel = _shuttleObject.docking_target.linked_z_level[1]
+ var/target_zvalue = target_spacelevel.z_value
+ if(is_reserved_level(target_zvalue))
+ message_admins("Shuttle [_shuttleId] attempted to dock on a reserved z-level as a result of docking with [_shuttleObject.docking_target.name].")
+ return FALSE
+ //Create temporary port
+ var/obj/docking_port/stationary/random_port = new
+ var/static/random_drops = 0
+ random_port.id = "randomdroplocation_[random_drops++]"
+ random_port.name = "Random drop location"
+ random_port.delete_after = TRUE
+ random_port.width = shuttle_dock.width
+ random_port.height = shuttle_dock.height
+ random_port.dwidth = shuttle_dock.dwidth
+ random_port.dheight = shuttle_dock.dheight
+ var/sanity = 20
+ var/square_length = max(shuttle_dock.width, shuttle_dock.height)
+ var/border_distance = 10 + square_length
+ //20 attempts to find a random port
+ while(sanity > 0)
+ sanity --
+ //Place the port in a random valid area.
+ var/x = rand(border_distance, world.maxx - border_distance)
+ var/y = rand(border_distance, world.maxy - border_distance)
+ //Check to make sure there are no indestructible turfs in the way
+ random_port.setDir(pick(NORTH, SOUTH, EAST, WEST))
+ random_port.forceMove(locate(x, y, target_zvalue))
+ var/list/turfs = random_port.return_turfs()
+ var/valid = TRUE
+ for(var/turf/T as() in turfs)
+ if(istype(T, /turf/open/indestructible) || istype(T, /turf/closed/indestructible))
+ valid = FALSE
+ break
+ if(!valid)
+ continue
+ //Dont wipe z level while we are going
+ //Dont wipe z of where we are leaving for a bit, in case we come back.
+ SSzclear.temp_keep_z(z)
+ SSzclear.temp_keep_z(target_zvalue)
+ //Ok lets go there
+ switch(SSshuttle.moveShuttle(_shuttleId, random_port.id, 1))
+ if(0)
+ say("Initiating supercruise throttle-down, prepare for landing.")
+ if(current_user)
+ remove_eye_control(current_user)
+ QDEL_NULL(_shuttleObject)
+ //Hold the shuttle in the docking position until ready.
+ shuttle_dock.setTimer(INFINITY)
+ say("Waiting for hyperspace lane...")
+ INVOKE_ASYNC(src, .proc/unfreeze_shuttle, shuttle_dock, target_spacelevel)
+ return TRUE
+ if(1)
+ say("Invalid shuttle requested")
+ else
+ say("Unable to comply.")
+ qdel(random_port)
+ say("FAILED TO DROP IN A RANDOM PLACE PLEASE TRY AGAIN!!!!!!!!!")
+ return FALSE
+
+/obj/machinery/computer/shuttle_flight/proc/unfreeze_shuttle(obj/docking_port/mobile/shuttle_dock, datum/space_level/target_spacelevel)
+ var/start_time = world.time
+ UNTIL((!target_spacelevel.generating) || world.time > start_time + 3 MINUTES)
+ if(target_spacelevel.generating)
+ target_spacelevel.generating = FALSE
+ message_admins("CAUTION: SHUTTLE [shuttleId] REACHED THE GENERATION TIMEOUT OF 3 MINUTES. THE ASSIGNED Z-LEVEL IS STILL MARKED AS GENERATING, BUT WE ARE DOCKING ANYWAY.")
+ log_mapping("CAUTION: SHUTTLE [shuttleId] REACHED THE GENERATION TIMEOUT OF 3 MINUTES. THE ASSIGNED Z-LEVEL IS STILL MARKED AS GENERATING, BUT WE ARE DOCKING ANYWAY.")
+ shuttle_dock.setTimer(20)
+
+/obj/machinery/computer/shuttle_flight/emag_act(mob/user)
+ if(obj_flags & EMAGGED)
+ return
+ req_access = list()
+ obj_flags |= EMAGGED
+ to_chat(user, "You fried the consoles ID checking system.")
+
diff --git a/code/modules/shuttle/super_cruise/shuttle_components/shuttle_docking.dm b/code/modules/shuttle/super_cruise/shuttle_components/shuttle_docking.dm
new file mode 100644
index 0000000000000..617ff6cf5d0a2
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/shuttle_components/shuttle_docking.dm
@@ -0,0 +1,390 @@
+
+/obj/machinery/computer/shuttle_flight
+ var/mob/camera/ai_eye/remote/eyeobj
+ var/mob/living/current_user = null
+ var/list/actions = list()
+ var/datum/action/innate/camera_off/off_action = new
+ var/datum/action/innate/shuttledocker_rotate/rotate_action = new
+ var/datum/action/innate/shuttledocker_place/place_action = new
+ var/shuttlePortId = ""
+ var/shuttlePortName = "custom location"
+ var/obj/docking_port/stationary/my_port //the custom docking port placed by this console
+ var/obj/docking_port/mobile/shuttle_port //the mobile docking port of the connected shuttle
+ var/view_range = 0
+ var/list/whitelist_turfs = list(/turf/open/space, /turf/open/floor/plating/lavaland, /turf/open/floor/plating/asteroid, /turf/open/lava)
+ var/designate_time = 50
+ var/turf/designating_target_loc
+ var/datum/action/innate/camera_jump/shuttle_docker/docker_action = new
+
+/obj/machinery/computer/shuttle_flight/Initialize(mapload, obj/item/circuitboard/C)
+ . = ..()
+ GLOB.navigation_computers += src
+ whitelist_turfs = typecacheof(whitelist_turfs)
+
+/obj/machinery/computer/shuttle_flight/Destroy()
+ . = ..()
+ GLOB.navigation_computers -= src
+
+/obj/machinery/computer/shuttle_flight/proc/GrantActions(mob/living/user)
+ if(off_action)
+ off_action.target = user
+ off_action.Grant(user)
+ actions += off_action
+
+ if(rotate_action)
+ rotate_action.target = user
+ rotate_action.Grant(user)
+ actions += rotate_action
+
+ if(place_action)
+ place_action.target = user
+ place_action.Grant(user)
+ actions += place_action
+
+ if(docker_action)
+ docker_action.target = user
+ docker_action.Grant(user)
+ actions += docker_action
+
+/obj/machinery/computer/shuttle_flight/proc/CreateEye()
+ shuttle_port = SSshuttle.getShuttle(shuttleId)
+ if(QDELETED(shuttle_port))
+ shuttle_port = null
+ return
+
+ eyeobj = new /mob/camera/ai_eye/remote/shuttle_docker(null, src)
+ var/mob/camera/ai_eye/remote/shuttle_docker/the_eye = eyeobj
+ the_eye.setDir(shuttle_port.dir)
+ var/turf/origin = locate(shuttle_port.x, shuttle_port.y, shuttle_port.z)
+ for(var/V in shuttle_port.shuttle_areas)
+ var/area/A = V
+ for(var/turf/T in A)
+ if(T.get_virtual_z_level() != origin.get_virtual_z_level())
+ continue
+ var/image/I = image('icons/effects/alphacolors.dmi', origin, "red")
+ var/x_off = T.x - origin.x
+ var/y_off = T.y - origin.y
+ I.loc = locate(origin.x + x_off, origin.y + y_off, origin.z) //we have to set this after creating the image because it might be null, and images created in nullspace are immutable.
+ I.layer = ABOVE_NORMAL_TURF_LAYER
+ I.plane = 0
+ I.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ the_eye.placement_images[I] = list(x_off, y_off)
+
+/obj/machinery/computer/shuttle_flight/proc/give_eye_control(mob/user)
+ if(!isliving(user))
+ return
+ if(!eyeobj)
+ CreateEye()
+ GrantActions(user)
+ current_user = user
+ eyeobj.eye_user = user
+ eyeobj.name = "Camera Eye ([user.name])"
+ user.remote_control = eyeobj
+ user.reset_perspective(eyeobj)
+ eyeobj.setLoc(eyeobj.loc)
+ user.client.view_size.supress()
+ if(!QDELETED(user) && user.client)
+ var/mob/camera/ai_eye/remote/shuttle_docker/the_eye = eyeobj
+ var/list/to_add = list()
+ to_add += the_eye.placement_images
+ to_add += the_eye.placed_images
+ if(!shuttleObject.stealth)
+ to_add += SSshuttle.hidden_shuttle_turf_images
+
+ user.client.images += to_add
+ user.client.view_size.setTo(view_range)
+
+/obj/machinery/computer/shuttle_flight/remove_eye_control(mob/user)
+ if(!user)
+ return
+ for(var/V in actions)
+ var/datum/action/A = V
+ A.Remove(user)
+ actions.Cut()
+ for(var/V in eyeobj.visibleCameraChunks)
+ var/datum/camerachunk/C = V
+ C.remove(eyeobj)
+ if(user.client)
+ user.reset_perspective(null)
+ if(eyeobj.visible_icon && user.client)
+ user.client.images -= eyeobj.user_image
+
+ user.client.view_size.unsupress()
+
+ eyeobj.eye_user = null
+ user.remote_control = null
+
+ current_user = null
+ user.unset_machine()
+
+ playsound(src, 'sound/machines/terminal_off.ogg', 25, FALSE)
+
+ if(!QDELETED(user) && user.client)
+ var/mob/camera/ai_eye/remote/shuttle_docker/the_eye = eyeobj
+ var/list/to_remove = list()
+ to_remove += the_eye.placement_images
+ to_remove += the_eye.placed_images
+ if(!shuttleObject.stealth)
+ to_remove += SSshuttle.hidden_shuttle_turf_images
+
+ user.client.images -= to_remove
+ user.client.view_size.resetToDefault()
+
+/obj/machinery/computer/shuttle_flight/proc/placeLandingSpot()
+ if(designating_target_loc || !current_user)
+ return
+
+ if(QDELETED(shuttleObject))
+ to_chat(usr, "Shuttle has already docked.")
+ return
+
+ var/mob/camera/ai_eye/remote/shuttle_docker/the_eye = eyeobj
+ var/landing_clear = checkLandingSpot()
+ if(designate_time && (landing_clear != SHUTTLE_DOCKER_BLOCKED))
+ to_chat(current_user, "Targeting transit location, please wait [DisplayTimeText(designate_time)]...")
+ designating_target_loc = the_eye.loc
+ var/wait_completed = do_after(current_user, designate_time, FALSE, designating_target_loc, TRUE, CALLBACK(src, .proc/canDesignateTarget))
+ designating_target_loc = null
+ if(!current_user)
+ return
+ if(!wait_completed)
+ to_chat(current_user, "Operation aborted.")
+ return
+ landing_clear = checkLandingSpot()
+
+ if(landing_clear != SHUTTLE_DOCKER_LANDING_CLEAR)
+ switch(landing_clear)
+ if(SHUTTLE_DOCKER_BLOCKED)
+ to_chat(current_user, "Invalid transit location.")
+ if(SHUTTLE_DOCKER_BLOCKED_BY_HIDDEN_PORT)
+ to_chat(current_user, "Unknown object detected in landing zone. Please designate another location.")
+ return
+
+ ///Make one use port that deleted after fly off, to don't lose info that need on to properly fly off.
+ if(my_port)
+ my_port.delete_after = TRUE
+ my_port.id = null
+ my_port.name = "Old [my_port.name]"
+ my_port = null
+
+ if(!my_port)
+ my_port = new()
+ my_port.name = shuttlePortName
+ my_port.id = shuttlePortId
+ my_port.height = shuttle_port.height
+ my_port.width = shuttle_port.width
+ my_port.dheight = shuttle_port.dheight
+ my_port.dwidth = shuttle_port.dwidth
+ my_port.hidden = shuttle_port.hidden
+ my_port.setDir(the_eye.dir)
+ my_port.forceMove(locate(eyeobj.x, eyeobj.y, eyeobj.z))
+
+ if(current_user.client)
+ current_user.client.images -= the_eye.placed_images
+
+ QDEL_LIST(the_eye.placed_images)
+
+ for(var/V in the_eye.placement_images)
+ var/image/I = V
+ var/image/newI = image('icons/effects/alphacolors.dmi', the_eye.loc, "blue")
+ newI.loc = I.loc //It is highly unlikely that any landing spot including a null tile will get this far, but better safe than sorry.
+ newI.layer = ABOVE_OPEN_TURF_LAYER
+ newI.plane = 0
+ newI.mouse_opacity = 0
+ the_eye.placed_images += newI
+
+ //Go to destination
+ var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId)
+ switch(SSshuttle.moveShuttle(shuttleId, shuttlePortId, 1))
+ if(0)
+ remove_eye_control(usr)
+ QDEL_NULL(shuttleObject)
+ //Hold the shuttle in the docking position until ready.
+ M.setTimer(INFINITY)
+ say("Waiting for hyperspace lane...")
+ INVOKE_ASYNC(src, .proc/unfreeze_shuttle, M, SSmapping.get_level(eyeobj.z))
+ if(1)
+ to_chat(usr, "Invalid shuttle requested.")
+ else
+ to_chat(usr, "Unable to comply.")
+
+ return TRUE
+
+/obj/machinery/computer/shuttle_flight/proc/canDesignateTarget()
+ if(!designating_target_loc || !current_user || (eyeobj.loc != designating_target_loc) || (stat & (NOPOWER|BROKEN)) )
+ return FALSE
+ return TRUE
+
+/obj/machinery/computer/shuttle_flight/proc/rotateLandingSpot()
+ var/mob/camera/ai_eye/remote/shuttle_docker/the_eye = eyeobj
+ var/list/image_cache = the_eye.placement_images
+ the_eye.setDir(turn(the_eye.dir, -90))
+ for(var/i in 1 to image_cache.len)
+ var/image/pic = image_cache[i]
+ var/list/coords = image_cache[pic]
+ var/Tmp = coords[1]
+ coords[1] = coords[2]
+ coords[2] = -Tmp
+ pic.loc = locate(the_eye.x + coords[1], the_eye.y + coords[2], the_eye.z)
+ checkLandingSpot()
+
+/obj/machinery/computer/shuttle_flight/proc/checkLandingSpot()
+ var/mob/camera/ai_eye/remote/shuttle_docker/the_eye = eyeobj
+ var/turf/eyeturf = get_turf(the_eye)
+ if(!eyeturf)
+ return SHUTTLE_DOCKER_BLOCKED
+ if(!eyeturf.z)
+ return SHUTTLE_DOCKER_BLOCKED
+
+ . = SHUTTLE_DOCKER_LANDING_CLEAR
+ var/list/bounds = shuttle_port.return_coords(the_eye.x, the_eye.y, the_eye.dir)
+ var/list/overlappers = SSshuttle.get_dock_overlap(bounds[1], bounds[2], bounds[3], bounds[4], the_eye.z)
+ var/list/image_cache = the_eye.placement_images
+ for(var/i in 1 to image_cache.len)
+ var/image/I = image_cache[i]
+ var/list/coords = image_cache[I]
+ var/turf/T = locate(eyeturf.x + coords[1], eyeturf.y + coords[2], eyeturf.z)
+ I.loc = T
+ switch(checkLandingTurf(T, overlappers))
+ if(SHUTTLE_DOCKER_LANDING_CLEAR)
+ I.icon_state = "green"
+ if(SHUTTLE_DOCKER_BLOCKED_BY_HIDDEN_PORT)
+ I.icon_state = "green"
+ if(. == SHUTTLE_DOCKER_LANDING_CLEAR)
+ . = SHUTTLE_DOCKER_BLOCKED_BY_HIDDEN_PORT
+ else
+ I.icon_state = "red"
+ . = SHUTTLE_DOCKER_BLOCKED
+
+/obj/machinery/computer/shuttle_flight/proc/checkLandingTurf(turf/T, list/overlappers)
+ // Too close to the map edge is never allowed
+ if(!T || T.x <= 10 || T.y <= 10 || T.x >= world.maxx - 10 || T.y >= world.maxy - 10)
+ return SHUTTLE_DOCKER_BLOCKED
+ // If it's one of our shuttle areas assume it's ok to be there
+ if(shuttle_port.shuttle_areas[T.loc])
+ return SHUTTLE_DOCKER_LANDING_CLEAR
+ . = SHUTTLE_DOCKER_LANDING_CLEAR
+ // See if the turf is hidden from us
+ var/list/hidden_turf_info
+ if(!shuttleObject.stealth)
+ hidden_turf_info = SSshuttle.hidden_shuttle_turfs[T]
+ if(hidden_turf_info)
+ . = SHUTTLE_DOCKER_BLOCKED_BY_HIDDEN_PORT
+
+ if(length(whitelist_turfs))
+ var/turf_type = hidden_turf_info ? hidden_turf_info[2] : T.type
+ if(!is_type_in_typecache(turf_type, whitelist_turfs))
+ return SHUTTLE_DOCKER_BLOCKED
+
+ // Checking for overlapping dock boundaries
+ for(var/i in 1 to overlappers.len)
+ var/obj/docking_port/port = overlappers[i]
+ if(port == my_port)
+ continue
+ var/port_hidden = !shuttleObject.stealth && port.hidden
+ var/list/overlap = overlappers[port]
+ var/list/xs = overlap[1]
+ var/list/ys = overlap[2]
+ if(xs["[T.x]"] && ys["[T.y]"])
+ if(port_hidden)
+ . = SHUTTLE_DOCKER_BLOCKED_BY_HIDDEN_PORT
+ else
+ return SHUTTLE_DOCKER_BLOCKED
+
+/obj/machinery/computer/shuttle_flight/proc/update_hidden_docking_ports(list/remove_images, list/add_images)
+ if(!shuttleObject?.stealth && current_user && current_user.client)
+ current_user.client.images -= remove_images
+ current_user.client.images += add_images
+
+/obj/machinery/computer/shuttle_flight/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
+ if(port && (shuttleId == initial(shuttleId) || override))
+ shuttleId = port.id
+ shuttlePortId = "[shuttleId]_custom"
+
+/mob/camera/ai_eye/remote/shuttle_docker
+ visible_icon = FALSE
+ use_static = USE_STATIC_NONE
+ var/list/placement_images = list()
+ var/list/placed_images = list()
+
+/mob/camera/ai_eye/remote/shuttle_docker/Initialize(mapload, obj/machinery/computer/camera_advanced/origin)
+ src.origin = origin
+ return ..()
+
+/mob/camera/ai_eye/remote/shuttle_docker/setLoc(T)
+ ..()
+ var/obj/machinery/computer/shuttle_flight/console = origin
+ console.checkLandingSpot()
+
+/mob/camera/ai_eye/remote/shuttle_docker/update_remote_sight(mob/living/user)
+ user.sight = BLIND|SEE_TURFS
+ user.lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE
+ user.sync_lighting_plane_alpha()
+ return TRUE
+
+/datum/action/innate/shuttledocker_rotate
+ name = "Rotate"
+ icon_icon = 'icons/mob/actions/actions_mecha.dmi'
+ button_icon_state = "mech_cycle_equip_off"
+
+/datum/action/innate/shuttledocker_rotate/Activate()
+ if(QDELETED(target) || !isliving(target))
+ return
+ var/mob/living/C = target
+ var/mob/camera/ai_eye/remote/remote_eye = C.remote_control
+ var/obj/machinery/computer/shuttle_flight/origin = remote_eye.origin
+ origin.rotateLandingSpot()
+
+/datum/action/innate/shuttledocker_place
+ name = "Place"
+ icon_icon = 'icons/mob/actions/actions_mecha.dmi'
+ button_icon_state = "mech_zoom_off"
+
+/datum/action/innate/shuttledocker_place/Activate()
+ if(QDELETED(target) || !isliving(target))
+ return
+ var/mob/living/C = target
+ var/mob/camera/ai_eye/remote/remote_eye = C.remote_control
+ var/obj/machinery/computer/shuttle_flight/origin = remote_eye.origin
+ origin.placeLandingSpot(target)
+
+/datum/action/innate/camera_jump/shuttle_docker
+ name = "Jump to Location"
+ button_icon_state = "camera_jump"
+
+/datum/action/innate/camera_jump/shuttle_docker/Activate()
+ if(QDELETED(target) || !isliving(target))
+ return
+ var/mob/living/C = target
+ var/mob/camera/ai_eye/remote/remote_eye = C.remote_control
+ var/obj/machinery/computer/shuttle_flight/console = remote_eye.origin
+
+ if(QDELETED(console.shuttleObject))
+ return
+
+ playsound(console, 'sound/machines/terminal_prompt_deny.ogg', 25, 0)
+
+ var/list/L = list()
+ for(var/V in SSshuttle.stationary)
+ if(!V)
+ stack_trace("SSshuttle.stationary have null entry!")
+ continue
+ var/obj/docking_port/stationary/S = V
+ if(console.shuttleObject.docking_target.z_in_contents(S.z) && (S.id in console.valid_docks))
+ L["(L.len)[S.name]"] = S
+
+ playsound(console, 'sound/machines/terminal_prompt.ogg', 25, FALSE)
+ var/selected = input("Choose location to jump to", "Locations", null) as null|anything in L
+ if(QDELETED(src) || QDELETED(target) || !isliving(target))
+ return
+ playsound(src, "terminal_type", 25, 0)
+ if(selected)
+ var/turf/T = get_turf(L[selected])
+ if(T)
+ playsound(console, 'sound/machines/terminal_prompt_confirm.ogg', 25, 0)
+ remote_eye.setLoc(T)
+ to_chat(target, "Jumped to [selected].")
+ C.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/flash/static)
+ C.clear_fullscreen("flash", 3)
+ else
+ playsound(console, 'sound/machines/terminal_prompt_deny.ogg', 25, 0)
diff --git a/code/modules/shuttle/super_cruise/shuttle_supercruise.dm b/code/modules/shuttle/super_cruise/shuttle_supercruise.dm
new file mode 100644
index 0000000000000..b356364ef684b
--- /dev/null
+++ b/code/modules/shuttle/super_cruise/shuttle_supercruise.dm
@@ -0,0 +1,26 @@
+/obj/docking_port/mobile/proc/enter_supercruise()
+ //Must be idle to supercruise.
+ if(mode != SHUTTLE_IDLE)
+ return
+ //Inherit orbital velocity of the place we are leaving
+ var/datum/space_level/z_level = SSmapping.get_level(z)
+ var/datum/orbital_object/orbital_body
+ if(!z_level || !z_level.orbital_body)
+ message_admins("Error: Shuttle is entering supercruise from a bad location. Shuttle: [name]")
+ log_runtime("Error: Shuttle is entering supercruise from a bad location. Shuttle: [name]")
+ var/datum/orbital_map/default_map = SSorbits.orbital_maps[PRIMARY_ORBITAL_MAP]
+ orbital_body = default_map.center
+ else
+ orbital_body = z_level.orbital_body
+ //Start moving
+ destination = null
+ mode = SHUTTLE_IGNITING
+ setTimer(ignitionTime)
+ //Enter the orbital system
+ var/datum/orbital_object/shuttle/our_orbital_body = new shuttle_object_type(
+ new /datum/orbital_vector(orbital_body.position.x + orbital_body.velocity.x, orbital_body.position.y + orbital_body.velocity.y),
+ new /datum/orbital_vector(orbital_body.velocity.x, orbital_body.velocity.y)
+ )
+ //Linkup
+ our_orbital_body.link_shuttle(src)
+ return our_orbital_body
diff --git a/code/modules/shuttle/supply.dm b/code/modules/shuttle/supply.dm
index ff2e64238c5cf..e238e029192ee 100644
--- a/code/modules/shuttle/supply.dm
+++ b/code/modules/shuttle/supply.dm
@@ -17,7 +17,7 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list(
/obj/structure/receiving_pad,
/obj/item/warp_cube,
/obj/machinery/rnd/production, //print tracking beacons, send shuttle
- /obj/machinery/autolathe, //same
+ /obj/machinery/modular_fabricator/autolathe, //same
/obj/item/projectile/beam/wormhole,
/obj/effect/portal,
/obj/item/shared_storage,
@@ -97,7 +97,7 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list(
for(var/datum/supply_order/SO in SSshuttle.shoppinglist)
if(!empty_turfs.len)
break
- var/price = SO.pack.cost
+ var/price = SO.pack.get_cost()
var/datum/bank_account/D
if(SO.paying_account) //Someone paid out of pocket
D = SO.paying_account
@@ -113,8 +113,8 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list(
if(SO.paying_account)
D.bank_card_talk("Cargo order #[SO.id] has shipped. [price] credits have been charged to your bank account.")
var/datum/bank_account/department/cargo = SSeconomy.get_dep_account(ACCOUNT_CAR)
- cargo.adjust_money(price - SO.pack.cost) //Cargo gets the handling fee
- value += SO.pack.cost
+ cargo.adjust_money(price - SO.pack.get_cost()) //Cargo gets the handling fee
+ value += SO.pack.get_cost()
SSshuttle.shoppinglist -= SO
SSshuttle.orderhistory += SO
@@ -124,9 +124,12 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list(
miscboxes[D.account_holder] = new /obj/structure/closet/crate/secure/owned(pick_n_take(empty_turfs), SO.paying_account)
miscboxes[D.account_holder].name = "small items crate - purchased by [D.account_holder]"
misc_contents[D.account_holder] = list()
+ miscboxes[D.account_holder].req_access = list()
for (var/item in SO.pack.contains)
misc_contents[D.account_holder] += item
misc_order_num[D.account_holder] = "[misc_order_num[D.account_holder]]#[SO.id] "
+ if(SO.pack.access)
+ miscboxes[D.account_holder].req_access += SO.pack.access
else //No private payment, so we just stuff it all into a generic crate
if(!miscboxes.len || !miscboxes["Cargo"])
miscboxes["Cargo"] = new /obj/structure/closet/crate/secure(pick_n_take(empty_turfs))
@@ -142,7 +145,7 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list(
else
SO.generate(pick_n_take(empty_turfs))
- SSblackbox.record_feedback("nested tally", "cargo_imports", 1, list("[SO.pack.cost]", "[SO.pack.name]"))
+ SSblackbox.record_feedback("nested tally", "cargo_imports", 1, list("[SO.pack.get_cost()]", "[SO.pack.name]"))
investigate_log("Order #[SO.id] ([SO.pack.name], placed by [key_name(SO.orderer_ckey)]), paid by [D.account_holder] has shipped.", INVESTIGATE_CARGO)
if(SO.pack.dangerous)
message_admins("\A [SO.pack.name] ordered by [ADMIN_LOOKUPFLW(SO.orderer_ckey)], paid by [D.account_holder] has shipped.")
diff --git a/code/modules/shuttle/syndicate.dm b/code/modules/shuttle/syndicate.dm
index b44102e2e7028..29ddd49e9f9f4 100644
--- a/code/modules/shuttle/syndicate.dm
+++ b/code/modules/shuttle/syndicate.dm
@@ -1,6 +1,6 @@
#define SYNDICATE_CHALLENGE_TIMER 12000 //20 minutes
-/obj/machinery/computer/shuttle/syndicate
+/obj/machinery/computer/shuttle_flight/syndicate
name = "syndicate shuttle terminal"
desc = "The terminal used to control the syndicate transport shuttle."
circuit = /obj/item/circuitboard/computer/syndicate_shuttle
@@ -12,29 +12,28 @@
possible_destinations = "syndicate_away;syndicate_z5;syndicate_ne;syndicate_nw;syndicate_n;syndicate_se;syndicate_sw;syndicate_s;syndicate_custom"
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
-/obj/machinery/computer/shuttle/syndicate/recall
+/obj/machinery/computer/shuttle_flight/syndicate/recall
name = "syndicate shuttle recall terminal"
desc = "Use this if your friends left you behind."
- possible_destinations = "syndicate_away"
+ request_shuttle_message = "Recall Infiltrator"
+ recall_docking_port_id = "syndicate_away"
-
-/obj/machinery/computer/shuttle/syndicate/Topic(href, href_list)
+/obj/machinery/computer/shuttle_flight/syndicate/ui_act(action, params)
if(!usr.canUseTopic(src))
return
- if(href_list["move"])
- var/obj/item/circuitboard/computer/syndicate_shuttle/board = circuit
- if(board.challenge && world.time < SYNDICATE_CHALLENGE_TIMER)
- to_chat(usr, "You've issued a combat challenge to the station! You've got to give them at least [DisplayTimeText(SYNDICATE_CHALLENGE_TIMER - world.time)] more to allow them to prepare.")
- return 0
- board.moved = TRUE
- ..()
+ var/obj/item/circuitboard/computer/syndicate_shuttle/board = circuit
+ if(board.challenge && world.time < SYNDICATE_CHALLENGE_TIMER)
+ to_chat(usr, "You've issued a combat challenge to the station! You've got to give them at least [DisplayTimeText(SYNDICATE_CHALLENGE_TIMER - world.time)] more to allow them to prepare.")
+ return FALSE
+ board.moved = TRUE
+ . = ..()
-/obj/machinery/computer/shuttle/syndicate/allowed(mob/M)
+/obj/machinery/computer/shuttle_flight/syndicate/allowed(mob/M)
if(issilicon(M) && !(ROLE_SYNDICATE in M.faction))
return FALSE
return ..()
-/obj/machinery/computer/shuttle/syndicate/drop_pod
+/obj/machinery/computer/shuttle_flight/syndicate/drop_pod
name = "syndicate assault pod control"
desc = "Controls the drop pod's launch system."
icon = 'icons/obj/terminals.dmi'
@@ -44,28 +43,7 @@
shuttleId = "steel_rain"
possible_destinations = null
clockwork = TRUE //it'd look weird
+ recall_docking_port_id = "null" //Make it a recall shuttle, with no default dest
+ request_shuttle_message = "INITIATE ASSAULT"
-/obj/machinery/computer/shuttle/syndicate/drop_pod/Topic(href, href_list)
- if(!usr.canUseTopic(src))
- return
- if(href_list["move"])
- if(!is_centcom_level(z))
- to_chat(usr, "Pods are one way!")
- return 0
- ..()
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/syndicate
- name = "syndicate shuttle navigation computer"
- desc = "Used to designate a precise transit location for the syndicate shuttle."
- icon_screen = "syndishuttle"
- icon_keyboard = "syndie_key"
- shuttleId = "syndicate"
- lock_override = CAMERA_LOCK_STATION
- shuttlePortId = "syndicate_custom"
- jumpto_ports = list("syndicate_ne" = 1, "syndicate_nw" = 1, "syndicate_n" = 1, "syndicate_se" = 1, "syndicate_sw" = 1, "syndicate_s" = 1)
- view_range = 5.5
- x_offset = -7
- y_offset = -1
- see_hidden = TRUE
-
-#undef SYNDICATE_CHALLENGE_TIMER
\ No newline at end of file
+#undef SYNDICATE_CHALLENGE_TIMER
diff --git a/code/modules/shuttle/white_ship.dm b/code/modules/shuttle/white_ship.dm
index 66146edea1edc..17b60a0c0a0ba 100644
--- a/code/modules/shuttle/white_ship.dm
+++ b/code/modules/shuttle/white_ship.dm
@@ -1,54 +1,23 @@
-/obj/machinery/computer/shuttle/white_ship
+/obj/machinery/computer/shuttle_flight/white_ship
name = "White Ship Console"
desc = "Used to control the White Ship."
circuit = /obj/item/circuitboard/computer/white_ship
shuttleId = "whiteship"
possible_destinations = "whiteship_away;whiteship_home;whiteship_z4;whiteship_lavaland;whiteship_custom"
-/obj/machinery/computer/shuttle/white_ship/pod
+/obj/machinery/computer/shuttle_flight/white_ship/pod
name = "Salvage Pod Console"
desc = "Used to control the Salvage Pod."
circuit = /obj/item/circuitboard/computer/white_ship/pod
shuttleId = "whiteship_pod"
possible_destinations = "whiteship_pod_home;whiteship_pod_custom"
-/obj/machinery/computer/shuttle/white_ship/pod/recall
+/obj/machinery/computer/shuttle_flight/white_ship/pod/recall
name = "Salvage Pod Recall Console"
desc = "Used to recall the Salvage Pod."
circuit = /obj/item/circuitboard/computer/white_ship/pod/recall
possible_destinations = "whiteship_pod_home"
-/obj/machinery/computer/camera_advanced/shuttle_docker/whiteship
- name = "White Ship Navigation Computer"
- desc = "Used to designate a precise transit location for the White Ship."
- shuttleId = "whiteship"
- lock_override = NONE
- shuttlePortId = "whiteship_custom"
- jumpto_ports = list("whiteship_away" = 1, "whiteship_home" = 1, "whiteship_z4" = 1)
- view_range = 10
- x_offset = -6
- y_offset = -10
- designate_time = 100
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/whiteship/pod
- name = "Salvage Pod Navigation Computer"
- desc = "Used to designate a precise transit location for the Salvage Pod."
- shuttleId = "whiteship_pod"
- shuttlePortId = "whiteship_pod_custom"
- jumpto_ports = list("whiteship_pod_home" = 1)
- view_range = 0
- x_offset = -2
- y_offset = 0
- designate_time = 0
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/whiteship/Initialize()
- . = ..()
- GLOB.jam_on_wardec += src
-
-/obj/machinery/computer/camera_advanced/shuttle_docker/whiteship/Destroy()
- GLOB.jam_on_wardec -= src
- return ..()
-
/obj/effect/spawner/lootdrop/whiteship_cere_ripley
name = "25% mech 75% wreckage ripley spawner"
loot = list(/obj/mecha/working/ripley/mining = 1,
diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm
index 696739a04eb88..1bcf9306d6869 100644
--- a/code/modules/spells/spell.dm
+++ b/code/modules/spells/spell.dm
@@ -20,6 +20,12 @@
if(has_action)
action = new base_action(src)
+/obj/effect/proc_holder/Destroy()
+ if(!QDELETED(action))
+ qdel(action)
+ action = null
+ return ..()
+
/obj/effect/proc_holder/proc/on_gain(mob/living/user)
return
@@ -35,8 +41,7 @@
GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for the badmin verb for now
/obj/effect/proc_holder/Destroy()
- if (action)
- qdel(action)
+ QDEL_NULL(action)
if(ranged_ability_user)
remove_ranged_ability()
return ..()
@@ -103,8 +108,8 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
var/charge_type = "recharge" //can be recharge or charges, see charge_max and charge_counter descriptions; can also be based on the holder's vars now, use "holder_var" for that
- var/charge_max = 100 //recharge time in deciseconds if charge_type = "recharge" or starting charges if charge_type = "charges"
- var/charge_counter = 0 //can only cast spells if it equals recharge, ++ each decisecond if charge_type = "recharge" or -- each cast if charge_type = "charges"
+ var/charge_max = 10 SECONDS //recharge time in deciseconds if charge_type = "recharge" or starting charges if charge_type = "charges"
+ var/charge_counter = 0 //can only cast spells if it equals recharge, ++ each deciseconds if charge_type = "recharge" or -- each cast if charge_type = "charges"
var/still_recharging_msg = "The spell is still recharging."
var/recharging = TRUE
@@ -136,6 +141,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
var/overlay_lifespan = 0
var/mutable_appearance/timer_overlay
+ var/mutable_appearance/text_overlay
var/timer_overlay_active = FALSE
var/timer_icon = 'icons/effects/cooldown.dmi'
var/timer_icon_state_active = "second"
@@ -227,20 +233,20 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
if(nonabstract_req && (isbrain(user) || ispAI(user)))
to_chat(user, "This spell can only be cast by physical beings!")
return FALSE
-
-
- if(!skipcharge)
- switch(charge_type)
- if("recharge")
- charge_counter = 0 //doesn't start recharging until the targets selecting ends
- if("charges")
- charge_counter-- //returns the charge if the targets selecting fails
- if("holdervar")
- adjust_var(user, holder_var_type, holder_var_amount)
if(action)
action.UpdateButtonIcon()
return TRUE
+/obj/effect/proc_holder/spell/proc/use_charge(mob/user)
+ switch(charge_type)
+ if("recharge")
+ charge_counter = 0 //doesn't start recharging until the targets selecting ends
+ if("charges")
+ charge_counter-- //returns the charge if the targets selecting fails
+ if("holdervar")
+ adjust_var(user, holder_var_type, holder_var_amount)
+ start_recharge()
+
/obj/effect/proc_holder/spell/proc/charge_check(mob/user, silent = FALSE)
switch(charge_type)
if("recharge")
@@ -275,13 +281,12 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
/obj/effect/proc_holder/spell/Initialize()
. = ..()
- START_PROCESSING(SSfastprocess, src)
still_recharging_msg = "[name] is still recharging."
charge_counter = charge_max
/obj/effect/proc_holder/spell/Destroy()
- STOP_PROCESSING(SSfastprocess, src)
+ end_timer_animation()
qdel(action)
return ..()
@@ -298,28 +303,36 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
/obj/effect/proc_holder/spell/proc/start_recharge()
recharging = TRUE
+ begin_timer_animation()
-/obj/effect/proc_holder/spell/process()
+/obj/effect/proc_holder/spell/process(delta_time)
if(recharging && charge_type == "recharge" && (charge_counter < charge_max))
- charge_counter += 2 //processes 5 times per second instead of 10.
+ charge_counter += delta_time * 10
update_timer_animation()
if(charge_counter >= charge_max)
end_timer_animation()
action.UpdateButtonIcon()
charge_counter = charge_max
recharging = FALSE
+ else
+ end_timer_animation()
+ action.UpdateButtonIcon()
+ charge_counter = charge_max
+ recharging = FALSE
/obj/effect/proc_holder/spell/proc/perform(list/targets, recharge = TRUE, mob/user = usr) //if recharge is started is important for the trigger spells
+ if(!cast_check())
+ return
+ use_charge(user)
before_cast(targets)
invocation(user)
if(user?.ckey)
user.log_message("cast the spell [name].", LOG_ATTACK)
if(recharge)
- recharging = TRUE
+ start_recharge()
if(sound)
playMagSound()
cast(targets,user=user)
- begin_timer_animation()
after_cast(targets)
if(action)
action.UpdateButtonIcon()
@@ -406,16 +419,23 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
target.vars[type] += amount //I bear no responsibility for the runtimes that'll happen if you try to adjust non-numeric or even non-existent vars
/obj/effect/proc_holder/spell/targeted //can mean aoe for mobs (limited/unlimited number) or one target mob
+ ranged_mousepointer = 'icons/effects/cult_target.dmi'
var/max_targets = 1 //leave 0 for unlimited targets in range, 1 for one selectable target in range, more for limited number of casts (can all target one guy, depends on target_ignore_prev) in range
var/target_ignore_prev = 1 //only important if max_targets > 1, affects if the spell can be cast multiple times at one person from one cast
var/include_user = 0 //if it includes usr in the target list
var/random_target = 0 // chooses random viable target instead of asking the caster
var/random_target_priority = TARGET_CLOSEST // if random_target is enabled how it will pick the target
-
+ var/ranged_selection_active = FALSE
/obj/effect/proc_holder/spell/aoe_turf //affects all turfs in view or range (depends)
var/inner_radius = -1 //for all your ring spell needs
+/obj/effect/proc_holder/spell/targeted/Click()
+ if(ranged_selection_active)
+ remove_ranged_ability("You are no longer casting [src].")
+ return
+ . = ..()
+
/obj/effect/proc_holder/spell/targeted/choose_targets(mob/user = usr)
var/list/targets = list()
@@ -442,7 +462,8 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
//Adds a safety check post-input to make sure those targets are actually in range.
var/mob/M
if(!random_target)
- M = input("Choose the target for the spell.", "Targeting") as null|mob in sortNames(possible_targets)
+ add_ranged_ability(user, "Click on a target for which to cast [src] upon.", TRUE)
+ return
else
switch(random_target_priority)
if(TARGET_RANDOM)
@@ -484,6 +505,34 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
perform(targets,user=user)
+/obj/effect/proc_holder/spell/targeted/remove_ranged_ability(msg)
+ . = ..()
+ ranged_selection_active = FALSE
+
+/obj/effect/proc_holder/spell/targeted/add_ranged_ability(mob/living/user, msg, forced)
+ . = ..()
+ ranged_selection_active = TRUE
+
+/obj/effect/proc_holder/spell/targeted/InterceptClickOn(mob/living/caller, params, atom/A)
+ if(..())
+ return TRUE
+ if(ismob(A))
+ if(A == caller && !include_user)
+ to_chat(caller, "You cannot target yourself!")
+ return TRUE
+
+ var/list/targets = list(A)
+
+ remove_ranged_ability()
+
+ if(!targets.len) //doesn't waste the spell
+ revert_cast(caller)
+ return TRUE
+
+ perform(targets, user=caller)
+ return FALSE
+ return TRUE
+
/obj/effect/proc_holder/spell/aoe_turf/choose_targets(mob/user = usr)
var/list/targets = list()
@@ -551,31 +600,48 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
/obj/effect/proc_holder/spell/proc/begin_timer_animation()
if(!(action?.button) || timer_overlay_active)
return
+
timer_overlay_active = TRUE
timer_overlay = mutable_appearance(timer_icon, timer_icon_state_active)
timer_overlay.alpha = 180
- action.button.add_overlay(timer_overlay)
- action.button.maptext_x = 8
- action.button.maptext_y = -6
+
+ if(!text_overlay)
+ text_overlay = image(loc = action.button, layer=ABOVE_HUD_LAYER)
+ text_overlay.maptext_width = 64
+ text_overlay.maptext_height = 64
+ text_overlay.maptext_x = -8
+ text_overlay.maptext_y = -6
+ text_overlay.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
+
+ if(action.owner?.client)
+ action.owner.client.images += text_overlay
+
+ action.button.add_overlay(timer_overlay, TRUE)
action.has_cooldown_timer = TRUE
update_timer_animation()
+ START_PROCESSING(SSfastprocess, src)
+
/obj/effect/proc_holder/spell/proc/update_timer_animation()
//Update map text (todo)
if(!(action?.button))
return
- action.button.maptext = "
"
/obj/effect/proc_holder/spell/proc/end_timer_animation()
if(!(action?.button) || !timer_overlay_active)
return
timer_overlay_active = FALSE
- action.button.cut_overlay(timer_overlay)
- action.button.maptext = null
+ if(action.owner?.client)
+ action.owner.client.images -= text_overlay
+ action.button.cut_overlay(timer_overlay, TRUE)
+ timer_overlay = null
+ qdel(text_overlay)
+ text_overlay = null
action.has_cooldown_timer = FALSE
+ STOP_PROCESSING(SSfastprocess, src)
+
//=====================
/obj/effect/proc_holder/spell/self //Targets only the caster. Good for buffs and heals, but probably not wise for fireballs (although they usually fireball themselves anyway, honke)
diff --git a/code/modules/spells/spell_types/aimed.dm b/code/modules/spells/spell_types/aimed.dm
index 26e4191308cd3..75b11adf5ebfd 100644
--- a/code/modules/spells/spell_types/aimed.dm
+++ b/code/modules/spells/spell_types/aimed.dm
@@ -77,7 +77,7 @@
if(!projectile_type)
return
for(var/i in 1 to projectiles_per_fire)
- var/obj/item/projectile/P = new projectile_type(user.loc)
+ var/obj/item/projectile/P = new projectile_type(user.loc, spell_level)
P.firer = user
P.preparePixelProjectile(target, user)
for(var/V in projectile_var_overrides)
diff --git a/code/modules/spells/spell_types/barnyard.dm b/code/modules/spells/spell_types/barnyard.dm
index 6fe5458e2c9b3..93ef535a4b85a 100644
--- a/code/modules/spells/spell_types/barnyard.dm
+++ b/code/modules/spells/spell_types/barnyard.dm
@@ -4,7 +4,6 @@
school = "transmutation"
charge_type = "recharge"
charge_max = 150
- charge_counter = 0
clothes_req = FALSE
stat_allowed = FALSE
invocation = "KN'A FTAGHU, PUCK 'BTHNK!"
@@ -17,14 +16,14 @@
action_icon_state = "barn"
/obj/effect/proc_holder/spell/targeted/barnyardcurse/cast(list/targets, mob/user = usr)
- if(!targets.len)
+ if(!length(targets))
to_chat(user, "No target found in range.")
return
var/mob/living/carbon/target = targets[1]
- if(!is_type_in_typecache(target, compatible_mobs_typecache))
+ if(!compatible_mobs_typecache[target.type])
to_chat(user, "You are unable to curse [target]'s head!")
return
@@ -32,7 +31,7 @@
to_chat(user, "[target.p_theyre(TRUE)] too far away!")
return
- if(target.anti_magic_check())
+ if(target.anti_magic_check() || HAS_TRAIT(target, TRAIT_WARDED))
to_chat(user, "The spell had no effect!")
target.visible_message("[target]'s face bursts into flames, which instantly burst outward, leaving [target] unharmed!", \
"Your face starts burning up, but the flames are repulsed by your anti-magic protection!")
@@ -46,6 +45,6 @@
"Your face burns up, and shortly after the fire you realise you have the face of a barnyard animal!")
if(!target.dropItemToGround(target.wear_mask))
qdel(target.wear_mask)
- target.equip_to_slot_if_possible(magichead, SLOT_WEAR_MASK, 1, 1)
+ target.equip_to_slot_if_possible(magichead, ITEM_SLOT_MASK, 1, 1)
target.flash_act()
diff --git a/code/modules/spells/spell_types/blind.dm b/code/modules/spells/spell_types/blind.dm
index f2745e2922939..4f9673c2def78 100644
--- a/code/modules/spells/spell_types/blind.dm
+++ b/code/modules/spells/spell_types/blind.dm
@@ -1,4 +1,4 @@
-/obj/effect/proc_holder/spell/pointed/trigger/blind
+/obj/effect/proc_holder/spell/targeted/blind
name = "Blind"
desc = "This spell temporarily blinds a single target."
school = "transmutation"
@@ -6,29 +6,44 @@
clothes_req = FALSE
invocation = "STI KALY"
invocation_type = "whisper"
- message = "Your eyes cry out in pain!"
cooldown_min = 50 //12 deciseconds reduction per rank
- starting_spells = list("/obj/effect/proc_holder/spell/targeted/inflict_handler/blind", "/obj/effect/proc_holder/spell/targeted/genetic/blind")
ranged_mousepointer = 'icons/effects/blind_target.dmi'
action_icon_state = "blind"
- base_icon_state = "blind"
- active_msg = "You prepare to blind a target..."
+ range = 7
+ selection_type = "range"
+ var/duration = 300 //30 seconds
+ var/static/list/compatible_mobs_typecache = typecacheof(list(/mob/living/carbon/human))
-/obj/effect/proc_holder/spell/targeted/inflict_handler/blind
- amt_eye_blind = 10
- amt_eye_blurry = 20
- sound = 'sound/magic/blind.ogg'
-/obj/effect/proc_holder/spell/targeted/genetic/blind
- mutations = list(BLINDMUT)
- duration = 300
- charge_max = 400 // needs to be higher than the duration or it'll be permanent
- sound = 'sound/magic/blind.ogg'
+/obj/effect/proc_holder/spell/targeted/blind/cast(list/targets, mob/user = usr)
+ if(!length(targets))
+ to_chat(user, "No target found in range.")
+ revert_cast()
+ return
+
+ var/mob/living/carbon/target = targets[1]
-/obj/effect/proc_holder/spell/pointed/trigger/blind/intercept_check(mob/user, atom/target)
- if(!..())
- return FALSE
- if(!isliving(target))
- to_chat(user, "You can only blind living beings!")
- return FALSE
- return TRUE
+ if(!compatible_mobs_typecache[target.type])
+ to_chat(user, "You are unable to curse [target] with blindness!")
+ revert_cast()
+ return
+
+ if(!(target in oview(range)))
+ to_chat(user, "[target.p_theyre(TRUE)] too far away!")
+ revert_cast()
+ return
+
+ if(target.anti_magic_check() || HAS_TRAIT(target, TRAIT_WARDED))
+ to_chat(user, "The spell had no effect!")
+ target.visible_message("[target]'s eyes darken, but instantly turn back to their regular color, leaving [target] unharmed!", \
+ "Your eyes hurt for a moment, but the blindness is repulsed by your anti-magic protection!")
+ return
+
+ target.visible_message("[target]'s eyes darken as black smoke starts coming out of them!", \
+ "Your eyes hurt as they start smoking, you panic as you realise you're blind!")
+ target.emote("scream")
+ target.become_blind(MAGIC_BLIND)
+ addtimer(CALLBACK(src, .proc/cure_blindness, target), duration)
+
+/obj/effect/proc_holder/spell/targeted/blind/proc/cure_blindness(mob/living/L)
+ L.cure_blind(MAGIC_BLIND)
diff --git a/code/modules/spells/spell_types/bloodcrawl.dm b/code/modules/spells/spell_types/bloodcrawl.dm
index 55524788b79ad..eb07ecb20536a 100644
--- a/code/modules/spells/spell_types/bloodcrawl.dm
+++ b/code/modules/spells/spell_types/bloodcrawl.dm
@@ -15,7 +15,7 @@
var/phased = FALSE
/obj/effect/proc_holder/spell/bloodcrawl/choose_targets(mob/user = usr)
- for(var/obj/effect/decal/cleanable/target in range(range, get_turf(user)))
+ for(var/obj/effect/decal/cleanable/target in view(range, get_turf(user)))
if(target.can_bloodcrawl_in())
perform(target)
return
diff --git a/code/modules/spells/spell_types/cluwnecurse.dm b/code/modules/spells/spell_types/cluwnecurse.dm
index 8c85f33fbd9b6..dee80792e5c48 100644
--- a/code/modules/spells/spell_types/cluwnecurse.dm
+++ b/code/modules/spells/spell_types/cluwnecurse.dm
@@ -4,7 +4,6 @@
school = "transmutation"
charge_type = "recharge"
charge_max = 600
- charge_counter = 0
clothes_req = 1
stat_allowed = 0
invocation = "CLU WO'NIS CA'TE'BEST'IS MAXIMUS!"
diff --git a/code/modules/spells/spell_types/construct_spells.dm b/code/modules/spells/spell_types/construct_spells.dm
index 3921a0c3ca9fd..91bb2456132f4 100644
--- a/code/modules/spells/spell_types/construct_spells.dm
+++ b/code/modules/spells/spell_types/construct_spells.dm
@@ -174,6 +174,11 @@
color = "red" //Looks more culty this way
range = 10
+/obj/item/projectile/magic/spell/magic_missile/lesser/can_hit_target(atom/target, list/passthrough, direct_target = FALSE, ignore_loc = FALSE)
+ if(ismob(target) && iscultist(target))
+ return FALSE
+ return ..()
+
/obj/effect/proc_holder/spell/targeted/smoke/disable
name = "Paralysing Smoke"
desc = "This spell spawns a cloud of paralysing smoke."
@@ -231,14 +236,12 @@
to_chat(target, "A freezing darkness surrounds you...")
target.playsound_local(get_turf(target), 'sound/hallucinations/i_see_you1.ogg', 50, 1)
user.playsound_local(get_turf(user), 'sound/effects/ghost2.ogg', 50, 1)
- target.become_blind(ABYSSAL_GAZE_BLIND)
+ target.become_blind(MAGIC_BLIND)
addtimer(CALLBACK(src, .proc/cure_blindness, target), 40)
target.adjust_bodytemperature(-200)
-/obj/effect/proc_holder/spell/targeted/abyssal_gaze/proc/cure_blindness(mob/target)
- if(isliving(target))
- var/mob/living/L = target
- L.cure_blind(ABYSSAL_GAZE_BLIND)
+/obj/effect/proc_holder/spell/targeted/abyssal_gaze/proc/cure_blindness(mob/living/L)
+ L.cure_blind(MAGIC_BLIND)
/obj/effect/proc_holder/spell/targeted/dominate
name = "Dominate"
@@ -276,7 +279,7 @@
revert_cast()
return
- if(S.sentience_type != SENTIENCE_ORGANIC)
+ if(!istype(S) || S.sentience_type != SENTIENCE_ORGANIC)
to_chat(user, "[S] cannot be dominated!")
revert_cast()
return
@@ -334,7 +337,7 @@
var/turf/T = get_turf(src)
playsound(T, 'sound/weapons/resonator_blast.ogg', 100, FALSE)
new /obj/effect/temp_visual/cult/sac(T)
- for(var/obj/O in range(src,1))
+ for(var/obj/O in range(1, src))
if(O.density && !istype(O, /obj/structure/destructible/cult))
O.take_damage(90, BRUTE, "melee", 0)
new /obj/effect/temp_visual/cult/turf/floor(get_turf(O))
diff --git a/code/modules/spells/spell_types/curse.dm b/code/modules/spells/spell_types/curse.dm
index 6c1a975781d2b..d8d86cf0459d0 100644
--- a/code/modules/spells/spell_types/curse.dm
+++ b/code/modules/spells/spell_types/curse.dm
@@ -16,10 +16,10 @@ GLOBAL_VAR_INIT(curse_of_madness_triggered, FALSE)
var/turf/T = get_turf(H)
if(T && !is_station_level(T.z))
continue
- if(H.anti_magic_check(TRUE, FALSE))
+ if(H.anti_magic_check(TRUE, FALSE) || HAS_TRAIT(H, TRAIT_WARDED))
to_chat(H, "You have a strange feeling for a moment, but then it passes.")
continue
- if(istype(H.get_item_by_slot(SLOT_HEAD), /obj/item/clothing/head/foilhat))
+ if(istype(H.get_item_by_slot(ITEM_SLOT_HEAD), /obj/item/clothing/head/foilhat))
to_chat(H, "Your protective headgear successfully deflects mind controlling brainwaves!")
continue
give_madness(H, message)
diff --git a/code/modules/spells/spell_types/devil.dm b/code/modules/spells/spell_types/devil.dm
index 91d16d8f62269..62494c60bcb42 100644
--- a/code/modules/spells/spell_types/devil.dm
+++ b/code/modules/spells/spell_types/devil.dm
@@ -5,7 +5,7 @@
include_user = TRUE
range = -1
clothes_req = FALSE
- item_type = /obj/item/twohanded/pitchfork/demonic
+ item_type = /obj/item/pitchfork/demonic
school = "conjuration"
charge_max = 150
@@ -15,16 +15,16 @@
action_background_icon_state = "bg_demon"
/obj/effect/proc_holder/spell/targeted/conjure_item/summon_pitchfork/greater
- item_type = /obj/item/twohanded/pitchfork/demonic/greater
+ item_type = /obj/item/pitchfork/demonic/greater
/obj/effect/proc_holder/spell/targeted/conjure_item/summon_pitchfork/ascended
- item_type = /obj/item/twohanded/pitchfork/demonic/ascended
+ item_type = /obj/item/pitchfork/demonic/ascended
/obj/effect/proc_holder/spell/targeted/conjure_item/violin
item_type = /obj/item/instrument/violin/golden
desc = "A devil's instrument of choice. Use this to summon/unsummon your golden violin."
invocation_type = "whisper"
- invocation = "I aint have this much fun since Georgia."
+ invocation = "I ain't have this much fun since Georgia."
action_icon_state = "golden_violin"
name = "Summon golden violin"
action_icon = 'icons/mob/actions/actions_minor_antag.dmi'
@@ -237,7 +237,7 @@
T.ChangeTurf(dancefloor_turfs_types[i], flags = CHANGETURF_INHERIT_AIR)
else
var/list/funky_turfs = RANGE_TURFS(1, user)
- for(var/turf/closed/solid in funky_turfs)
+ if(locate(/turf/closed) in funky_turfs)
to_chat(user, "You're too close to a wall.")
return
dancefloor_exists = TRUE
diff --git a/code/modules/spells/spell_types/emplosion.dm b/code/modules/spells/spell_types/emplosion.dm
index a5ba0a39142cc..967ec0d042871 100644
--- a/code/modules/spells/spell_types/emplosion.dm
+++ b/code/modules/spells/spell_types/emplosion.dm
@@ -11,8 +11,7 @@
/obj/effect/proc_holder/spell/targeted/emplosion/cast(list/targets,mob/user = usr)
playsound(get_turf(user), sound, 50,1)
for(var/mob/living/target in targets)
- if(target.anti_magic_check())
+ if(target.anti_magic_check() && target != user)
continue
empulse(target.loc, emp_heavy, emp_light)
-
return
\ No newline at end of file
diff --git a/code/modules/spells/spell_types/ethereal_jaunt.dm b/code/modules/spells/spell_types/ethereal_jaunt.dm
index ea259c76fe27a..d12e191ea4330 100644
--- a/code/modules/spells/spell_types/ethereal_jaunt.dm
+++ b/code/modules/spells/spell_types/ethereal_jaunt.dm
@@ -31,10 +31,12 @@
target.forceMove(holder)
target.reset_perspective(holder)
target.notransform=0 //mob is safely inside holder now, no need for protection.
+ target.ignore_slowdown(JAUNT_TRAIT)
jaunt_steam(mobloc)
sleep(jaunt_duration)
+ target.unignore_slowdown(JAUNT_TRAIT)
if(target.loc != holder) //mob warped out of the warp
qdel(holder)
return
diff --git a/code/modules/spells/spell_types/forcewall.dm b/code/modules/spells/spell_types/forcewall.dm
index 652242ea99904..84a338d254256 100644
--- a/code/modules/spells/spell_types/forcewall.dm
+++ b/code/modules/spells/spell_types/forcewall.dm
@@ -14,19 +14,19 @@
var/wall_type = /obj/effect/forcefield/wizard
/obj/effect/proc_holder/spell/targeted/forcewall/cast(list/targets,mob/user = usr)
- new wall_type(get_turf(user),user)
+ new wall_type(get_turf(user), null, user)
if(user.dir == SOUTH || user.dir == NORTH)
- new wall_type(get_step(user, EAST),user)
- new wall_type(get_step(user, WEST),user)
+ new wall_type(get_step(user, EAST), null, user)
+ new wall_type(get_step(user, WEST), null, user)
else
- new wall_type(get_step(user, NORTH),user)
- new wall_type(get_step(user, SOUTH),user)
+ new wall_type(get_step(user, NORTH), null, user)
+ new wall_type(get_step(user, SOUTH), null, user)
/obj/effect/forcefield/wizard
var/mob/wizard
-/obj/effect/forcefield/wizard/Initialize(mapload, mob/summoner)
+/obj/effect/forcefield/wizard/Initialize(mapload, ntimeleft, mob/summoner)
. = ..()
wizard = summoner
diff --git a/code/modules/spells/spell_types/godhand.dm b/code/modules/spells/spell_types/godhand.dm
index 45ccf13bc8039..2bd291c937e28 100644
--- a/code/modules/spells/spell_types/godhand.dm
+++ b/code/modules/spells/spell_types/godhand.dm
@@ -31,10 +31,14 @@
/obj/item/melee/touch_attack/afterattack(atom/target, mob/user, proximity)
. = ..()
+ //Use the spell
+ attached_spell.spell_used = TRUE
+ //Do effects
user.say(catchphrase, forced = "spell")
playsound(get_turf(user), on_use_sound,50,1)
charges--
if(charges <= 0)
+ attached_spell.use_charge(user)
qdel(src)
/obj/item/melee/touch_attack/Destroy()
@@ -58,7 +62,7 @@
return
var/mob/M = target
do_sparks(4, FALSE, M.loc)
- for(var/mob/living/L in view(src, 7))
+ for(var/mob/living/L in viewers(7, get_turf(src)))
if(L != user)
L.flash_act(affect_silicon = FALSE)
var/atom/A = M.anti_magic_check()
@@ -71,7 +75,7 @@
if(part)
part.dismember()
return ..()
- var/obj/item/clothing/suit/hooded/bloated_human/suit = M.get_item_by_slot(SLOT_WEAR_SUIT)
+ var/obj/item/clothing/suit/hooded/bloated_human/suit = M.get_item_by_slot(ITEM_SLOT_OCLOTHING)
if(istype(suit))
M.visible_message("[M]'s [suit] explodes off of them into a puddle of gore!")
M.dropItemToGround(suit)
@@ -120,7 +124,6 @@
/obj/item/melee/touch_attack/megahonk/afterattack(atom/target, mob/living/carbon/user, proximity)
if(!proximity || !iscarbon(target) || !iscarbon(user) || user.handcuffed)
return
- user.say(catchphrase, forced = "spell")
playsound(get_turf(target), on_use_sound,100,1)
for(var/mob/living/carbon/M in (hearers(1, target) - user)) //3x3 around the target, not affecting the user
if(ishuman(M))
@@ -138,9 +141,7 @@
else
M.Jitter(500*mul)
- charges--
- if(charges <= 0)
- qdel(src)
+ . = ..()
/obj/item/melee/touch_attack/megahonk/attack_self(mob/user)
. = ..()
@@ -175,19 +176,6 @@
var/obj/item/reagent_containers/food/snacks/pie/cream/body/pie = new(get_turf(M))
pie.name = "\improper [name] [pie.name]"
- playsound(get_turf(target), on_use_sound, 50, 1)
+ . = ..()
- /*
- var/obj/item/bodypart/head = M.get_bodypart("head")
- if(head)
- head.drop_limb()
- head.throw_at(get_turf(head), 1, 1)
- qdel(M)
- */
M.forceMove(pie)
-
-
- charges--
-
- if(charges <= 0)
- qdel(src)
diff --git a/code/modules/spells/spell_types/hivemind.dm b/code/modules/spells/spell_types/hivemind.dm
index 8126f19e933f0..bf44e059da036 100644
--- a/code/modules/spells/spell_types/hivemind.dm
+++ b/code/modules/spells/spell_types/hivemind.dm
@@ -57,14 +57,14 @@
var/success = FALSE
if(target.mind && target.client && target.stat != DEAD)
- if((!HAS_TRAIT(target, TRAIT_MINDSHIELD) || ignore_mindshield) && !istype(target.get_item_by_slot(SLOT_HEAD), /obj/item/clothing/head/foilhat))
+ if((!HAS_TRAIT(target, TRAIT_MINDSHIELD) || ignore_mindshield) && !istype(target.get_item_by_slot(ITEM_SLOT_HEAD), /obj/item/clothing/head/foilhat))
if(HAS_TRAIT(target, TRAIT_MINDSHIELD) && ignore_mindshield)
to_chat(user, "We bruteforce our way past the mental barriers of [target.name] and begin linking our minds!")
else
to_chat(user, "We begin linking our mind with [target.name]!")
- if(do_after(user,5*(1.5**get_dist(user, target)),0,user) && (target in view(range)))
- if(do_after(user,5*(1.5**get_dist(user, target)),0,user) && (target in view(range)))
- if((!HAS_TRAIT(target, TRAIT_MINDSHIELD) || ignore_mindshield) && (target in view(range)))
+ if(do_after(user,5*(1.5**get_dist(user, target)),0,user) && (user in viewers(range, target)))
+ if(do_after(user,5*(1.5**get_dist(user, target)),0,user) && (user in viewers(range, target)))
+ if((!HAS_TRAIT(target, TRAIT_MINDSHIELD) || ignore_mindshield) && (user in viewers(range, target)))
to_chat(user, "[target.name] was added to the Hive!")
success = TRUE
hive.add_to_hive(target)
@@ -140,7 +140,7 @@
active = TRUE
host = user
user.clear_fullscreen("hive_mc")
- user.overlay_fullscreen("hive_eyes", /obj/screen/fullscreen/hive_eyes)
+ user.overlay_fullscreen("hive_eyes", /atom/movable/screen/fullscreen/hive_eyes)
revert_cast()
else
vessel.remove_status_effect(STATUS_EFFECT_BUGGED)
@@ -148,7 +148,7 @@
user.clear_fullscreen("hive_eyes")
var/obj/effect/proc_holder/spell/target_hive/hive_control/the_spell = locate(/obj/effect/proc_holder/spell/target_hive/hive_control) in user.mind.spell_list
if(the_spell && the_spell.active)
- user.overlay_fullscreen("hive_mc", /obj/screen/fullscreen/hive_mc)
+ user.overlay_fullscreen("hive_mc", /atom/movable/screen/fullscreen/hive_mc)
active = FALSE
revert_cast()
@@ -195,7 +195,7 @@
power *= 2.5
else
power *= 3
- if(power > 50 && user.z == target.z)
+ if(power > 50 && user.get_virtual_z_level() == target.get_virtual_z_level())
to_chat(user, "We have overloaded the vessel for a short time!")
target.Jitter(round(power/10))
target.Unconscious(power)
@@ -236,7 +236,7 @@
break
distance = get_dist(user, L)
message = "[(L.is_real_hivehost()) ? "Someone": "A hivemind host"] tracking us"
- if(user.z != L.z || L.stat == DEAD)
+ if(user.get_virtual_z_level() != L.get_virtual_z_level() || L.stat == DEAD)
message += " could not be found."
else
switch(distance)
@@ -259,7 +259,7 @@
var/mob/living/real_enemy = C.get_real_hivehost()
distance = get_dist(user, real_enemy)
message = "A host that we can track for [(hive.individual_track_bonus[enemy])/10] extra seconds"
- if(user.z != real_enemy.z || real_enemy.stat == DEAD)
+ if(user.get_virtual_z_level() != real_enemy.get_virtual_z_level() || real_enemy.stat == DEAD)
message += " could not be found."
else
switch(distance)
@@ -334,7 +334,7 @@
to_chat(src, "You find yourself unable to emote, you aren't in control of your body!")
return
-/mob/living/passenger/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode)
+/mob/living/passenger/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mods)
return
/obj/effect/proc_holder/spell/target_hive/hive_control
@@ -401,7 +401,7 @@
to_chat(user, "We fail to assume control of the target.")
revert_cast()
return
- if(user.z != vessel.z)
+ if(user.get_virtual_z_level() != vessel.get_virtual_z_level())
to_chat(user, "Our vessel is too far away to control.")
revert_cast()
return
@@ -430,7 +430,7 @@
vessel.mind.transfer_to(backseat, 1)
user.mind.transfer_to(vessel, 1)
backseat.blind_eyes(power)
- vessel.overlay_fullscreen("hive_mc", /obj/screen/fullscreen/hive_mc)
+ vessel.overlay_fullscreen("hive_mc", /atom/movable/screen/fullscreen/hive_mc)
active = TRUE
out_of_range = FALSE
starting_spot = get_turf(vessel)
@@ -460,7 +460,7 @@
original_body.adjustOrganLoss(ORGAN_SLOT_BRAIN, 200)
to_chat(vessel.mind, "Our vessel is one of us no more!")
release_control()
- else if(!QDELETED(original_body) && original_body.z != vessel.z) //Return to original bodies
+ else if(!QDELETED(original_body) && original_body.get_virtual_z_level() != vessel.get_virtual_z_level()) //Return to original bodies
release_control()
to_chat(original_body, "Our vessel is too far away to control!")
else if(QDELETED(original_body) || original_body.stat == DEAD) //Return vessel to its body, either return or ghost the original
@@ -620,7 +620,7 @@
if(!hive)
to_chat(user, "This is a bug. Error:HIVE1")
return
- if(target.z != user.z)
+ if(target.get_virtual_z_level() != user.get_virtual_z_level())
to_chat(user, "We are too far away from [target.name] to affect them!")
return
to_chat(user, "We successfully distort reality surrounding [target.name]!")
@@ -628,7 +628,7 @@
distort(user, target, pulse_cap)
/obj/effect/proc_holder/spell/target_hive/hive_warp/proc/distort(user, target, pulse_cap, pulses = 0)
- for(var/mob/living/carbon/human/victim in view(7,target))
+ for(var/mob/living/carbon/human/victim in hearers(7,target))
if(user == victim || victim.is_real_hivehost())
continue
if(pulses < 4)
@@ -853,9 +853,9 @@
var/wall_type_b = /obj/effect/forcefield/wizard/hive/invis
/obj/effect/proc_holder/spell/targeted/forcewall/hive/cast(list/targets,mob/user = usr)
- new wall_type(get_turf(user),user)
+ new wall_type(get_turf(user), null, user)
for(var/dir in GLOB.alldirs)
- new wall_type_b(get_step(user, dir),user)
+ new wall_type_b(get_step(user, dir), null, user)
var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind)
if(hive)
hive.threat_level += 0.5
diff --git a/code/modules/spells/spell_types/inflict_handler.dm b/code/modules/spells/spell_types/inflict_handler.dm
deleted file mode 100644
index 2ad9a5ed169b9..0000000000000
--- a/code/modules/spells/spell_types/inflict_handler.dm
+++ /dev/null
@@ -1,60 +0,0 @@
-/obj/effect/proc_holder/spell/targeted/inflict_handler
- name = "Inflict Handler"
- desc = "This spell blinds and/or destroys/damages/heals and/or knockdowns/stuns the target."
-
- var/amt_paralyze = 0
- var/amt_unconscious = 0
- var/amt_stun = 0
-
- var/inflict_status
- var/list/status_params = list()
-
- //set to negatives for healing
- var/amt_dam_fire = 0
- var/amt_dam_brute = 0
- var/amt_dam_oxy = 0
- var/amt_dam_tox = 0
-
- var/amt_eye_blind = 0
- var/amt_eye_blurry = 0
-
- var/destroys = "none" //can be "none", "gib" or "disintegrate"
-
- var/summon_type = null //this will put an obj at the target's location
-
- var/check_anti_magic = TRUE
- var/check_holy = FALSE
-
-/obj/effect/proc_holder/spell/targeted/inflict_handler/cast(list/targets,mob/user = usr)
- for(var/mob/living/target in targets)
- playsound(target,sound, 50,1)
- if(target.anti_magic_check(check_anti_magic, check_holy))
- return
- switch(destroys)
- if("gib")
- target.gib()
- if("disintegrate")
- target.dust()
-
- if(!target)
- continue
- //damage/healing
- target.adjustBruteLoss(amt_dam_brute)
- target.adjustFireLoss(amt_dam_fire)
- target.adjustToxLoss(amt_dam_tox)
- target.adjustOxyLoss(amt_dam_oxy)
- //disabling
- target.Paralyze(amt_paralyze)
- target.Unconscious(amt_unconscious)
- target.Stun(amt_stun)
-
- target.blind_eyes(amt_eye_blind)
- target.blur_eyes(amt_eye_blurry)
- //summoning
- if(summon_type)
- new summon_type(target.loc, target)
-
- if(inflict_status)
- var/list/stat_args = status_params.Copy()
- stat_args.Insert(1,inflict_status)
- target.apply_status_effect(arglist(stat_args))
diff --git a/code/modules/spells/spell_types/knock.dm b/code/modules/spells/spell_types/knock.dm
index 63e010ba13da0..7a0319394d531 100644
--- a/code/modules/spells/spell_types/knock.dm
+++ b/code/modules/spells/spell_types/knock.dm
@@ -24,6 +24,7 @@
if(istype(door, /obj/machinery/door/airlock))
var/obj/machinery/door/airlock/A = door
A.locked = FALSE
+ A.wires.ui_update()
door.open()
/obj/effect/proc_holder/spell/aoe_turf/knock/proc/open_closet(var/obj/structure/closet/C)
diff --git a/code/modules/spells/spell_types/lichdom.dm b/code/modules/spells/spell_types/lichdom.dm
index 1d2324b43f6b6..fd66f8a6faef5 100644
--- a/code/modules/spells/spell_types/lichdom.dm
+++ b/code/modules/spells/spell_types/lichdom.dm
@@ -67,9 +67,9 @@
H.dropItemToGround(H.w_uniform)
H.dropItemToGround(H.wear_suit)
H.dropItemToGround(H.head)
- H.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe/black(H), SLOT_WEAR_SUIT)
- H.equip_to_slot_or_del(new /obj/item/clothing/head/wizard/black(H), SLOT_HEAD)
- H.equip_to_slot_or_del(new /obj/item/clothing/under/color/black(H), SLOT_W_UNIFORM)
+ H.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe/black(H), ITEM_SLOT_OCLOTHING)
+ H.equip_to_slot_or_del(new /obj/item/clothing/head/wizard/black(H), ITEM_SLOT_HEAD)
+ H.equip_to_slot_or_del(new /obj/item/clothing/under/color/black(H), ITEM_SLOT_ICLOTHING)
// you only get one phylactery.
M.mind.RemoveSpell(src)
@@ -128,15 +128,15 @@
var/mob/old_body = mind.current
var/mob/living/carbon/human/lich = new(item_turf)
- lich.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal/magic(lich), SLOT_SHOES)
- lich.equip_to_slot_or_del(new /obj/item/clothing/under/color/black(lich), SLOT_W_UNIFORM)
- lich.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe/black(lich), SLOT_WEAR_SUIT)
- lich.equip_to_slot_or_del(new /obj/item/clothing/head/wizard/black(lich), SLOT_HEAD)
+ lich.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal/magic(lich), ITEM_SLOT_FEET)
+ lich.equip_to_slot_or_del(new /obj/item/clothing/under/color/black(lich), ITEM_SLOT_ICLOTHING)
+ lich.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe/black(lich), ITEM_SLOT_OCLOTHING)
+ lich.equip_to_slot_or_del(new /obj/item/clothing/head/wizard/black(lich), ITEM_SLOT_HEAD)
lich.real_name = mind.name
mind.transfer_to(lich)
mind.grab_ghost(force=TRUE)
- lich.hardset_dna(null,null,lich.real_name,null, new /datum/species/skeleton)
+ lich.hardset_dna(null,null,lich.real_name,null, new /datum/species/skeleton,null)
to_chat(lich, "Your bones clatter and shudder as you are pulled back into this world!")
var/turf/body_turf = get_turf(old_body)
lich.Paralyze(200 + 200*resurrections)
diff --git a/code/modules/spells/spell_types/mime.dm b/code/modules/spells/spell_types/mime.dm
index c8cf09b99f2b7..ce9551caf70bb 100644
--- a/code/modules/spells/spell_types/mime.dm
+++ b/code/modules/spells/spell_types/mime.dm
@@ -6,7 +6,7 @@
summon_type = list(/obj/effect/forcefield/mime)
invocation_type = "emote"
invocation_emote_self = "You form a wall in front of yourself."
- summon_lifespan = 300
+ summon_lifespan = 100
charge_max = 300
clothes_req = FALSE
antimagic_allowed = TRUE
@@ -174,45 +174,41 @@
invocation_type ="none"
..()
-/obj/effect/proc_holder/spell/aimed/finger_guns
+/obj/effect/proc_holder/spell/targeted/mime/finger_guns
name = "Finger Guns"
desc = "Shoot a mimed bullet from your fingers that stuns and does some damage."
school = "mime"
panel = "Mime"
charge_max = 300
+ range = -1
clothes_req = FALSE
antimagic_allowed = TRUE
+ include_user = TRUE
invocation_type = "emote"
invocation_emote_self = "You fire your finger gun!"
- range = 20
- projectile_type = /obj/item/projectile/bullet/mime
- projectile_amount = 3
sound = null
- active_msg = "You draw your fingers!"
- deactive_msg = "You put your fingers at ease. Another time."
- active = FALSE
action_icon = 'icons/mob/actions/actions_mime.dmi'
action_icon_state = "finger_guns0"
action_background_icon_state = "bg_mime"
- base_icon_state = "finger_guns"
-
-/obj/effect/proc_holder/spell/aimed/finger_guns/Click()
- var/mob/living/carbon/human/owner = usr
- if(owner.incapacitated())
- to_chat(owner, "You can't properly point your fingers while incapacitated.")
+/obj/effect/proc_holder/spell/targeted/mime/finger_guns/Click()
+ if(!usr)
+ return
+ if(!ishuman(usr))
return
if(usr?.mind)
if(!usr.mind.miming)
to_chat(usr, "You must dedicate yourself to silence first.")
return
- invocation = "[usr.real_name] fires [usr.p_their()] finger gun!"
+ var/obj/item/gun/ballistic/revolver/mime/magic/N = new(usr)
+ if(usr.put_in_hands(N))
+ to_chat(usr, "You form your fingers into a gun.")
else
- invocation_type ="none"
+ qdel(N)
+ to_chat(usr, "You don't have any free hands to make fingerguns with.")
..()
-
/obj/item/book/granter/spell/mimery_blockade
spell = /obj/effect/proc_holder/spell/targeted/forcewall/mime
spellname = "Invisible Blockade"
@@ -229,7 +225,7 @@
user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/mime/speak)
/obj/item/book/granter/spell/mimery_guns
- spell = /obj/effect/proc_holder/spell/aimed/finger_guns
+ spell = /obj/effect/proc_holder/spell/targeted/mime/finger_guns
spellname = "Finger Guns"
name = "Guide to Advanced Mimery Vol 2"
desc = "There aren't any words written..."
diff --git a/code/modules/spells/spell_types/mind_transfer.dm b/code/modules/spells/spell_types/mind_transfer.dm
index eda4f9fecda0e..799bd7b48bd1e 100644
--- a/code/modules/spells/spell_types/mind_transfer.dm
+++ b/code/modules/spells/spell_types/mind_transfer.dm
@@ -65,8 +65,8 @@ Also, you never added distance checking after target is selected. I've went ahea
if(!silent)
to_chat(user, "[target.p_their(TRUE)] mind is resisting your spell!")
return
-
- if(istype(target.get_item_by_slot(SLOT_HEAD), /obj/item/clothing/head/foilhat))
+
+ if(istype(target.get_item_by_slot(ITEM_SLOT_HEAD), /obj/item/clothing/head/foilhat))
to_chat(target, "Your protective headgear successfully deflects mind controlling brainwaves!")
to_chat(user, "[target.p_their(TRUE)] mind is protected by a strange ward on their headgear!")
return
@@ -81,6 +81,11 @@ Also, you never added distance checking after target is selected. I've went ahea
else
target = stand.summoner
+ if(istype(target, /mob/living/simple_animal/slaughter)) //No.
+ to_chat(user, "Your mind recoils from the infernal hellfire of [target]'s soul!")
+ user.Unconscious(unconscious_amount_caster)
+ return
+
var/mob/living/victim = target//The target of the spell whos body will be transferred to.
var/mob/living/caster = user//The wizard/whomever doing the body transferring.
diff --git a/code/modules/spells/spell_types/projectile.dm b/code/modules/spells/spell_types/projectile.dm
index 151383fc7bff7..cc04bf0bafe48 100644
--- a/code/modules/spells/spell_types/projectile.dm
+++ b/code/modules/spells/spell_types/projectile.dm
@@ -58,11 +58,11 @@
name = "Projectile"
desc = "This spell summons projectiles which try to hit the targets."
-
+
var/proj_type = /obj/item/projectile/magic/spell //IMPORTANT use only subtypes of this
-
-
+
+
var/update_projectile = FALSE //So you want to admin abuse magic bullets ? This is for you
//Below only apply if update_projectile is true
var/proj_icon = 'icons/obj/projectiles.dmi'
@@ -83,8 +83,8 @@
var/check_holy = FALSE
/obj/effect/proc_holder/spell/targeted/projectile/proc/fire_projectile(atom/target, mob/user)
- var/obj/item/projectile/magic/spell/projectile = new proj_type()
-
+ var/obj/item/projectile/magic/spell/projectile = new proj_type(null, spell_level)
+
if(update_projectile)
//Generally these should already be set on the projectile, this is mostly here for varedited spells.
projectile.icon = proj_icon
diff --git a/code/modules/spells/spell_types/shapeshift.dm b/code/modules/spells/spell_types/shapeshift.dm
index 5ca4c14c3ae3a..c67cf06b95a86 100644
--- a/code/modules/spells/spell_types/shapeshift.dm
+++ b/code/modules/spells/spell_types/shapeshift.dm
@@ -78,7 +78,7 @@
desc = "Take on the shape a lesser ash drake."
invocation = "RAAAAAAAAWR!"
convert_damage = FALSE
-
+
shapeshift_type = /mob/living/simple_animal/hostile/megafauna/dragon/lesser
@@ -129,6 +129,7 @@
restore()
/obj/shapeshift_holder/Exited(atom/movable/AM)
+ . = ..()
if(AM == stored && !restoring)
restore()
diff --git a/code/modules/spells/spell_types/summonitem.dm b/code/modules/spells/spell_types/summonitem.dm
index a81c44f3b6644..6a376bd52f226 100644
--- a/code/modules/spells/spell_types/summonitem.dm
+++ b/code/modules/spells/spell_types/summonitem.dm
@@ -82,18 +82,6 @@
break
M.dropItemToGround(item_to_retrieve)
- if(iscarbon(M)) //Edge case housekeeping
- var/mob/living/carbon/C = M
- for(var/X in C.bodyparts)
- var/obj/item/bodypart/part = X
- if(item_to_retrieve in part.embedded_objects)
- part.embedded_objects -= item_to_retrieve
- to_chat(C, "The [item_to_retrieve] that was embedded in your [L] has mysteriously vanished. How fortunate!")
- if(!C.has_embedded_objects())
- C.clear_alert("embeddedobject")
- SEND_SIGNAL(C, COMSIG_CLEAR_MOOD_EVENT, "embedded")
- break
-
else
if(istype(item_to_retrieve.loc, /obj/machinery/portable_atmospherics/)) //Edge cases for moved machinery
var/obj/machinery/portable_atmospherics/P = item_to_retrieve.loc
diff --git a/code/modules/spells/spell_types/telepathy.dm b/code/modules/spells/spell_types/telepathy.dm
index fa154938242d5..8198c180e7743 100644
--- a/code/modules/spells/spell_types/telepathy.dm
+++ b/code/modules/spells/spell_types/telepathy.dm
@@ -15,13 +15,16 @@
/obj/effect/proc_holder/spell/targeted/telepathy/cast(list/targets, mob/living/simple_animal/revenant/user = usr)
for(var/mob/living/M in targets)
- if(istype(M.get_item_by_slot(SLOT_HEAD), /obj/item/clothing/head/foilhat))
+ if(istype(M.get_item_by_slot(ITEM_SLOT_HEAD), /obj/item/clothing/head/foilhat))
to_chat(user, "It appears the target's mind is ironclad! No getting a message in there!")
return
var/msg = stripped_input(usr, "What do you wish to tell [M]?", null, "")
if(!msg)
charge_counter = charge_max
return
+ if(CHAT_FILTER_CHECK(msg))
+ to_chat(user, "Your message contains forbidden words.")
+ return
log_directed_talk(user, M, msg, LOG_SAY, "[name]")
to_chat(user, "You transmit to [M]:[msg]")
if(!M.anti_magic_check(magic_check, holy_check)) //hear no evil
@@ -31,4 +34,4 @@
continue
var/follow_rev = FOLLOW_LINK(ded, user)
var/follow_whispee = FOLLOW_LINK(ded, M)
- to_chat(ded, "[follow_rev] [user] [name]:\"[msg]\" to [follow_whispee] [M]")
\ No newline at end of file
+ to_chat(ded, "[follow_rev] [user] [name]:\"[msg]\" to [follow_whispee] [M]")
diff --git a/code/modules/spells/spell_types/touch_attacks.dm b/code/modules/spells/spell_types/touch_attacks.dm
index 78a3c9aaf40c5..a548892e1b86d 100644
--- a/code/modules/spells/spell_types/touch_attacks.dm
+++ b/code/modules/spells/spell_types/touch_attacks.dm
@@ -6,15 +6,17 @@
invocation_type = "none" //you scream on connecting, not summoning
include_user = TRUE
range = -1
+ //Checks
+ var/spell_used = FALSE
/obj/effect/proc_holder/spell/targeted/touch/Destroy()
remove_hand()
to_chat(usr, "The power of the spell dissipates from your hand.")
..()
-/obj/effect/proc_holder/spell/targeted/touch/proc/remove_hand(recharge = FALSE)
+/obj/effect/proc_holder/spell/targeted/touch/proc/remove_hand()
QDEL_NULL(attached_hand)
- if(recharge)
+ if(!spell_used)
charge_counter = charge_max
/obj/effect/proc_holder/spell/targeted/touch/proc/on_hand_destroy(obj/item/melee/touch_attack/hand)
@@ -27,7 +29,7 @@
/obj/effect/proc_holder/spell/targeted/touch/cast(list/targets,mob/user = usr)
if(!QDELETED(attached_hand))
- remove_hand(TRUE)
+ remove_hand()
to_chat(user, "[dropmessage]")
return
@@ -47,12 +49,13 @@
attached_hand = new hand_path(src)
attached_hand.attached_spell = src
if(!user.put_in_hands(attached_hand))
- remove_hand(TRUE)
+ remove_hand()
if (user.get_num_arms() <= 0)
to_chat(user, "You dont have any usable hands!")
else
to_chat(user, "Your hands are full!")
return FALSE
+ spell_used = FALSE
to_chat(user, "[drawmessage]")
return TRUE
diff --git a/code/modules/spells/spell_types/turf_teleport.dm b/code/modules/spells/spell_types/turf_teleport.dm
index 14240de49aa65..b0dba898436f4 100644
--- a/code/modules/spells/spell_types/turf_teleport.dm
+++ b/code/modules/spells/spell_types/turf_teleport.dm
@@ -15,9 +15,7 @@
playsound(get_turf(user), sound1, 50,1)
for(var/mob/living/target in targets)
var/list/turfs = new/list()
- for(var/turf/T in range(target,outer_tele_radius))
- if(T in range(target,inner_tele_radius))
- continue
+ for(var/turf/T as() in (RANGE_TURFS(outer_tele_radius, target)-RANGE_TURFS(inner_tele_radius, target)))
if(isspaceturf(T) && !include_space)
continue
if(T.density && !include_dense)
@@ -29,11 +27,7 @@
turfs += T
if(!turfs.len)
- var/list/turfs_to_pick_from = list()
- for(var/turf/T in orange(target,outer_tele_radius))
- if(!(T in orange(target,inner_tele_radius)))
- turfs_to_pick_from += T
- turfs += pick(/turf in turfs_to_pick_from)
+ turfs += RANGE_TURFS(outer_tele_radius, target)-RANGE_TURFS(inner_tele_radius, target)
var/turf/picked = pick(turfs)
diff --git a/code/modules/spells/spell_types/wizard.dm b/code/modules/spells/spell_types/wizard.dm
index 2ea720b6cd69f..d5db9f057762f 100644
--- a/code/modules/spells/spell_types/wizard.dm
+++ b/code/modules/spells/spell_types/wizard.dm
@@ -28,6 +28,10 @@
trail_lifespan = 5
trail_icon_state = "magicmd"
+/obj/item/projectile/magic/spell/magic_missile/New(loc, spell_level)
+ . = ..()
+ paralyze += spell_level * 10
+
/obj/effect/proc_holder/spell/targeted/genetic/mutate
name = "Mutate"
desc = "This spell causes you to turn into a hulk and gain laser vision for a short while."
@@ -66,7 +70,6 @@
action_icon_state = "smoke"
-
/obj/effect/proc_holder/spell/targeted/smoke/lesser //Chaplain smoke book
name = "Smoke"
desc = "This spell spawns a small cloud of choking smoke at your location."
@@ -250,11 +253,14 @@
for(var/atom/movable/AM in T)
thrownatoms += AM
+ stun_amt += 10 * spell_level
+ maxthrow = 5 + spell_level
+
for(var/am in thrownatoms)
var/atom/movable/AM = am
if(AM == user || AM.anchored)
continue
-
+
if(ismob(AM))
var/mob/M = AM
if(M.anti_magic_check(anti_magic_check, FALSE))
@@ -351,7 +357,7 @@
M.electrocute_act(80, src, illusion = 1)
qdel(src)
-/obj/item/spellpacket/lightningbolt/throw_at(atom/target, range, speed, mob/thrower, spin=TRUE, diagonals_first = FALSE, datum/callback/callback, force = INFINITY)
+/obj/item/spellpacket/lightningbolt/throw_at(atom/target, range, speed, mob/thrower, spin=TRUE, diagonals_first = FALSE, datum/callback/callback, force = INFINITY, quickstart = TRUE)
. = ..()
if(ishuman(thrower))
var/mob/living/carbon/human/H = thrower
diff --git a/code/modules/station_goals/bsa.dm b/code/modules/station_goals/bsa.dm
index dfec2846af2e0..10da47c320aa6 100644
--- a/code/modules/station_goals/bsa.dm
+++ b/code/modules/station_goals/bsa.dm
@@ -65,8 +65,8 @@
name = "Bluespace Artillery Fusor"
desc = "Contents classified by Nanotrasen Naval Command. Needs to be linked with the other BSA parts using multitool."
icon_state = "fuel_chamber"
- var/obj/machinery/bsa/back/back
- var/obj/machinery/bsa/front/front
+ var/datum/weakref/back_ref
+ var/datum/weakref/front_ref
/obj/machinery/bsa/middle/multitool_act(mob/living/user, obj/item/I)
if(!multitool_check_buffer(user, I))
@@ -74,18 +74,21 @@
var/obj/item/multitool/M = I
if(M.buffer)
if(istype(M.buffer, /obj/machinery/bsa/back))
- back = M.buffer
+ back_ref = WEAKREF(M.buffer)
+ to_chat(user, "You link [src] with [M.buffer].")
M.buffer = null
- to_chat(user, "You link [src] with [back].")
+ to_chat(user, "You link [src] with [M.buffer].")
else if(istype(M.buffer, /obj/machinery/bsa/front))
- front = M.buffer
+ front_ref = WEAKREF(M.buffer)
+ to_chat(user, "You link [src] with [M.buffer].")
M.buffer = null
- to_chat(user, "You link [src] with [front].")
else
to_chat(user, "[I]'s data buffer is empty!")
return TRUE
/obj/machinery/bsa/middle/proc/check_completion()
+ var/obj/machinery/bsa/front/front = front_ref?.resolve()
+ var/obj/machinery/bsa/back/back = back_ref?.resolve()
if(!front || !back)
return "No linked parts detected!"
if(!front.anchored || !back.anchored || !anchored)
@@ -113,6 +116,10 @@
return TRUE
/obj/machinery/bsa/middle/proc/get_cannon_direction()
+ var/obj/machinery/bsa/front/front = front_ref?.resolve()
+ var/obj/machinery/bsa/back/back = back_ref?.resolve()
+ if(!front || !back)
+ return
if(front.x > x && back.x < x)
return EAST
else if(front.x < x && back.x > x)
@@ -133,6 +140,7 @@
bound_width = 352
bound_x = -192
appearance_flags = NONE //Removes default TILE_BOUND
+ resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
/obj/machinery/bsa/full/wrench_act(mob/living/user, obj/item/I)
return FALSE
@@ -197,7 +205,7 @@
target = tile
break
else
- tile.ex_act(EXPLODE_DEVASTATE) //also fucks everything else on the turf
+ SSexplosions.highturf += tile
point.Beam(target, icon_state = "bsa_beam", time = 50, maxdistance = world.maxx) //ZZZAP
new /obj/effect/temp_visual/bsa_splash(point, dir)
@@ -214,10 +222,12 @@
/obj/machinery/bsa/full/proc/reload()
ready = FALSE
use_power(power_used_per_shot)
+ ui_update()
addtimer(CALLBACK(src,"ready_cannon"),600)
/obj/machinery/bsa/full/proc/ready_cannon()
ready = TRUE
+ ui_update()
/obj/structure/filler
name = "big machinery part"
@@ -238,7 +248,7 @@
- var/obj/machinery/bsa/full/cannon
+ var/datum/weakref/cannon_ref
var/notice
var/target
var/area_aim = FALSE //should also show areas for targeting
@@ -252,8 +262,10 @@
if(!ui)
ui = new(user, src, "BluespaceArtillery")
ui.open()
+ //Missing updates for: target GPS name changes
/obj/machinery/computer/bsa_control/ui_data()
+ var/obj/machinery/bsa/full/cannon = cannon_ref?.resolve()
var/list/data = list()
data["ready"] = cannon ? cannon.ready : FALSE
data["connected"] = cannon
@@ -261,6 +273,8 @@
data["unlocked"] = GLOB.bsa_unlock
if(target)
data["target"] = get_target_name()
+ else
+ data["target"] = null
return data
/obj/machinery/computer/bsa_control/ui_act(action, params)
@@ -268,7 +282,7 @@
return
switch(action)
if("build")
- cannon = deploy()
+ cannon_ref = WEAKREF(deploy())
. = TRUE
if("fire")
fire(usr)
@@ -276,7 +290,8 @@
if("recalibrate")
calibrate(usr)
. = TRUE
- update_icon()
+ if(.)
+ update_icon()
/obj/machinery/computer/bsa_control/proc/calibrate(mob/user)
if(!GLOB.bsa_unlock)
@@ -308,6 +323,10 @@
return get_turf(G.parent)
/obj/machinery/computer/bsa_control/proc/fire(mob/user)
+ var/obj/machinery/bsa/full/cannon = cannon_ref?.resolve()
+ if(!cannon)
+ notice = "No Cannon Exists!"
+ return
if(cannon.stat)
notice = "Cannon unpowered!"
return
@@ -331,7 +350,7 @@
s.set_up(4,get_turf(centerpiece))
s.start()
var/obj/machinery/bsa/full/cannon = new(get_turf(centerpiece),centerpiece.get_cannon_direction())
- qdel(centerpiece.front)
- qdel(centerpiece.back)
+ QDEL_NULL(centerpiece.front_ref)
+ QDEL_NULL(centerpiece.back_ref)
qdel(centerpiece)
return cannon
diff --git a/code/modules/station_goals/custom_shuttle.dm b/code/modules/station_goals/custom_shuttle.dm
new file mode 100644
index 0000000000000..f2f392f11797e
--- /dev/null
+++ b/code/modules/station_goals/custom_shuttle.dm
@@ -0,0 +1,18 @@
+//Custom Shuttle
+//Crew has to build a custom shuttle
+/datum/station_goal/custom_shuttle
+ name = "Custom Shuttle"
+
+/datum/station_goal/custom_shuttle/get_report()
+ return {"Nanotrasen needs a new prototype light cruiser.
+ We leave it up to you to decide what the shuttle needs to be an effective platform.
+
+ You can create a designator in engineering or purchase one at cargo.
+ "}
+
+/datum/station_goal/custom_shuttle/check_completion()
+ if(..())
+ return TRUE
+ if(GLOB.custom_shuttle_count)
+ return TRUE
+ return FALSE
diff --git a/code/modules/station_goals/dna_vault.dm b/code/modules/station_goals/dna_vault.dm
index 8f27b86487a16..c4231b7a3d94a 100644
--- a/code/modules/station_goals/dna_vault.dm
+++ b/code/modules/station_goals/dna_vault.dm
@@ -223,13 +223,12 @@
return
switch(action)
if("gene")
- upgrade(usr,params["choice"])
- . = TRUE
+ . = upgrade(usr,params["choice"])
/obj/machinery/dna_vault/proc/check_goal()
if(plants.len >= plants_max && animals.len >= animals_max && dna.len >= dna_max)
completed = TRUE
-
+ ui_update()
/obj/machinery/dna_vault/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/dna_probe))
@@ -255,14 +254,14 @@
/obj/machinery/dna_vault/proc/upgrade(mob/living/carbon/human/H,upgrade_type)
if(!(upgrade_type in power_lottery[H]))
return
+ . = TRUE
var/datum/species/S = H.dna.species
switch(upgrade_type)
if(VAULT_TOXIN)
to_chat(H, "You feel resistant to airborne toxins.")
if(locate(/obj/item/organ/lungs) in H.internal_organs)
var/obj/item/organ/lungs/L = H.internal_organs_slot[ORGAN_SLOT_LUNGS]
- L.tox_breath_dam_min = 0
- L.tox_breath_dam_max = 0
+ L.gas_max -= GAS_PLASMA
ADD_TRAIT(H, TRAIT_VIRUSIMMUNE, "dna_vault")
if(VAULT_NOBREATH)
to_chat(H, "Your lungs feel great.")
diff --git a/code/modules/station_goals/shield.dm b/code/modules/station_goals/shield.dm
index 8214c3e04c6e2..88396108cbdfc 100644
--- a/code/modules/station_goals/shield.dm
+++ b/code/modules/station_goals/shield.dm
@@ -54,6 +54,7 @@
if(!ui)
ui = new(user, src, "SatelliteControl")
ui.open()
+ ui.set_autoupdate(TRUE) // Satellite stats (could probably be refactored to update when satellite status changes)
/obj/machinery/computer/sat_control/ui_act(action, params)
if(..())
@@ -65,7 +66,7 @@
/obj/machinery/computer/sat_control/proc/toggle(id)
for(var/obj/machinery/satellite/S in GLOB.machines)
- if(S.id == id && S.z == z)
+ if(S.id == id && S.get_virtual_z_level() == get_virtual_z_level())
S.toggle()
/obj/machinery/computer/sat_control/ui_data()
@@ -118,9 +119,11 @@
to_chat(user, "You [active ? "deactivate": "activate"] [src].")
active = !active
if(active)
+ begin_processing()
animate(src, pixel_y = 2, time = 10, loop = -1)
anchored = TRUE
else
+ end_processing()
animate(src, pixel_y = 0, time = 10)
anchored = FALSE
update_icon()
@@ -136,7 +139,8 @@
name = "\improper Meteor Shield Satellite"
desc = "A meteor point-defense satellite."
mode = "M-SHIELD"
- speed_process = TRUE
+ processing_flags = START_PROCESSING_MANUALLY
+ subsystem_type = /datum/controller/subsystem/processing/fastprocess
var/kill_range = 14
/obj/machinery/satellite/meteor_shield/proc/space_los(meteor)
@@ -149,7 +153,7 @@
if(!active)
return
for(var/obj/effect/meteor/M in GLOB.meteor_list)
- if(M.z != z)
+ if(M.get_virtual_z_level() != get_virtual_z_level())
continue
if(get_dist(M,src) > kill_range)
continue
diff --git a/code/modules/station_goals/station_goal.dm b/code/modules/station_goals/station_goal.dm
index 88377455c6a12..eeff891344afd 100644
--- a/code/modules/station_goals/station_goal.dm
+++ b/code/modules/station_goals/station_goal.dm
@@ -11,8 +11,11 @@
var/completed = FALSE
var/report_message = "Complete this goal."
+/datum/station_goal/proc/prepare_report()
+ addtimer(CALLBACK(src, .proc/send_report), 1200) // 2 min, less than avg 4 for intercept report
+
/datum/station_goal/proc/send_report()
- priority_announce("Priority Nanotrasen directive received. Project \"[name]\" details inbound.", "Incoming Priority Message", 'sound/ai/commandreport.ogg')
+ priority_announce("Priority Nanotrasen directive received. Project \"[name]\" details inbound.", "Incoming Priority Message", SSstation.announcer.get_rand_report_sound())
print_command_report(get_report(),"Nanotrasen Directive [pick(GLOB.phonetic_alphabet)] \Roman[rand(1,50)]", announce=FALSE)
on_report()
diff --git a/code/modules/surgery/advanced/bioware/muscled_veins.dm b/code/modules/surgery/advanced/bioware/muscled_veins.dm
index 592a02b8d10be..81212019a8f3b 100644
--- a/code/modules/surgery/advanced/bioware/muscled_veins.dm
+++ b/code/modules/surgery/advanced/bioware/muscled_veins.dm
@@ -6,7 +6,7 @@
/datum/surgery_step/clamp_bleeders,
/datum/surgery_step/incise,
/datum/surgery_step/incise,
- /datum/surgery_step/thread_veins,
+ /datum/surgery_step/muscled_veins,
/datum/surgery_step/close)
possible_locs = list(BODY_ZONE_CHEST)
bioware_target = BIOWARE_CIRCULATION
diff --git a/code/modules/surgery/advanced/revival.dm b/code/modules/surgery/advanced/revival.dm
index 6ded176c677e9..5a68bd78e8c14 100644
--- a/code/modules/surgery/advanced/revival.dm
+++ b/code/modules/surgery/advanced/revival.dm
@@ -26,15 +26,16 @@
return TRUE
/datum/surgery_step/revive
- name = "revive body"
- implements = list(/obj/item/twohanded/shockpaddles = 100, /obj/item/melee/baton = 75, /obj/item/gun/energy = 60)
+ name = "shock body"
+ implements = list(/obj/item/shockpaddles = 100, /obj/item/melee/baton = 75, /obj/item/gun/energy = 60)
+ repeatable = TRUE
time = 120
/datum/surgery_step/revive/tool_check(mob/user, obj/item/tool)
. = TRUE
- if(istype(tool, /obj/item/twohanded/shockpaddles))
- var/obj/item/twohanded/shockpaddles/S = tool
- if((S.req_defib && !S.defib.powered) || !S.wielded || S.cooldown || S.busy)
+ if(istype(tool, /obj/item/shockpaddles))
+ var/obj/item/shockpaddles/S = tool
+ if((S.req_defib && !S.defib.powered) || !ISWIELDED(S) || S.cooldown || S.busy)
to_chat(user, "You need to wield both paddles, and [S.defib] must be powered!")
return FALSE
if(istype(tool, /obj/item/melee/baton))
@@ -79,4 +80,4 @@
"[user] send a powerful shock to [target]'s brain with [tool], but [target.p_they()] doesn't react.")
playsound(get_turf(target), 'sound/magic/lightningbolt.ogg', 50, 1)
target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 15, 180)
- return FALSE
\ No newline at end of file
+ return FALSE
diff --git a/code/modules/surgery/amputation.dm b/code/modules/surgery/amputation.dm
index 9134390d5fff7..b45c6d249790a 100644
--- a/code/modules/surgery/amputation.dm
+++ b/code/modules/surgery/amputation.dm
@@ -10,7 +10,7 @@
/datum/surgery_step/sever_limb
name = "sever limb"
- implements = list(TOOL_SCALPEL = 100, TOOL_SAW = 100, /obj/item/melee/arm_blade = 80, /obj/item/twohanded/fireaxe = 50, /obj/item/hatchet = 40, /obj/item/kitchen/knife/butcher = 25)
+ implements = list(TOOL_SCALPEL = 100, TOOL_SAW = 100, /obj/item/melee/arm_blade = 80, /obj/item/fireaxe = 50, /obj/item/hatchet = 40, /obj/item/kitchen/knife/butcher = 25)
time = 64
/datum/surgery_step/sever_limb/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
@@ -27,4 +27,4 @@
var/obj/item/bodypart/target_limb = surgery.operated_bodypart
target_limb.drop_limb()
- return 1
\ No newline at end of file
+ return 1
diff --git a/code/modules/surgery/anesthetic_machine.dm b/code/modules/surgery/anesthetic_machine.dm
new file mode 100644
index 0000000000000..aa59ead5e2bcc
--- /dev/null
+++ b/code/modules/surgery/anesthetic_machine.dm
@@ -0,0 +1,117 @@
+/obj/machinery/anesthetic_machine
+ name = "Anesthetic Tank Holder"
+ desc = "A wheeled machine that can hold an anesthetic tank and distribute the air using a breath mask."
+ icon = 'icons/obj/iv_drip.dmi'
+ icon_state = "breath_machine"
+ anchored = FALSE
+ mouse_drag_pointer = MOUSE_ACTIVE_POINTER
+ var/obj/item/clothing/mask/breath/machine/attached_mask
+ var/obj/item/tank/attached_tank = null
+ var/mask_out = FALSE
+
+/obj/machinery/anesthetic_machine/Initialize()
+ . = ..()
+ attached_mask = new /obj/item/clothing/mask/breath/machine(src)
+ attached_mask.machine_attached = src
+ update_icon()
+
+/obj/machinery/anesthetic_machine/update_icon()
+ cut_overlays()
+ if(mask_out)
+ add_overlay("mask_off")
+ else
+ add_overlay("mask_on")
+ if(attached_tank)
+ add_overlay("tank_on")
+
+
+/obj/machinery/anesthetic_machine/attack_hand(mob/living/user)
+ . = ..()
+ if(retract_mask())
+ visible_message("[user] retracts the mask back into the [src].")
+
+/obj/machinery/anesthetic_machine/attacked_by(obj/item/I, mob/living/user)
+ if(istype(I, /obj/item/tank))
+ if(attached_tank) // If there is an attached tank, remove it and drop it on the floor
+ attached_tank.forceMove(loc)
+ I.forceMove(src) // Put new tank in, set it as attached tank
+ visible_message("[user] inserts [I] into [src].")
+ attached_tank = I
+ update_icon()
+ return
+ . = ..()
+
+/obj/machinery/anesthetic_machine/AltClick(mob/user)
+ . = ..()
+ if(attached_tank)// If attached tank, remove it.
+ attached_tank.forceMove(loc)
+ to_chat(user, "You remove the [attached_tank].")
+ attached_tank = null
+ update_icon()
+ if(mask_out)
+ retract_mask()
+
+/obj/machinery/anesthetic_machine/proc/retract_mask()
+ if(mask_out)
+ if(iscarbon(attached_mask.loc)) // If mask is on a mob
+ var/mob/living/carbon/M = attached_mask.loc
+ M.transferItemToLoc(attached_mask, src, TRUE)
+ M.internal = null
+ else
+ attached_mask.forceMove(src)
+ mask_out = FALSE
+ update_icon()
+ return TRUE
+ return FALSE
+
+/obj/machinery/anesthetic_machine/MouseDrop(mob/living/carbon/target)
+ . = ..()
+ if(!iscarbon(target))
+ return
+ if(Adjacent(target) && usr.Adjacent(target))
+ if(attached_tank && !mask_out)
+ usr.visible_message("[usr] attemps to attach the [src] to [target].", "You attempt to attach the [src] to [target].")
+ if(!do_after(usr, 70, TRUE, target))
+ return
+ if(!target.equip_to_appropriate_slot(attached_mask))
+ to_chat(usr, "You are unable to attach the [src] to [target]!")
+ return
+ else
+ usr.visible_message("[usr] attaches the [src] to [target].", "You attach the [src] to [target].")
+ target.internal = attached_tank
+ mask_out = TRUE
+ START_PROCESSING(SSmachines, src)
+ target.update_internals_hud_icon(1)
+ update_icon()
+ else
+ to_chat(usr, "[mask_out ? "The machine is already in use!" : "The machine has no attached tank!"]")
+
+/obj/machinery/anesthetic_machine/process()
+ if(!mask_out) // If not on someone, stop processing
+ return PROCESS_KILL
+
+ if(get_dist(src, get_turf(attached_mask)) > 1) // If too far away, detach
+ to_chat(attached_mask.loc, "The [attached_mask] is ripped off of your face!")
+ retract_mask()
+ return PROCESS_KILL
+
+/obj/machinery/anesthetic_machine/Destroy()
+ if(mask_out)
+ retract_mask()
+ qdel(attached_mask)
+ new /obj/item/clothing/mask/breath(src)
+ . = ..()
+
+/obj/item/clothing/mask/breath/machine
+ var/obj/machinery/anesthetic_machine/machine_attached
+ clothing_flags = MASKINTERNALS | MASKEXTENDRANGE
+
+/obj/item/clothing/mask/breath/machine/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT)
+
+/obj/item/clothing/mask/breath/machine/dropped(mob/user)
+ . = ..()
+ if(loc != machine_attached) // If not already in machine, go back in when dropped (dropped is called on unequip)
+ to_chat(user, "The mask snaps back into the [machine_attached].")
+ machine_attached.retract_mask()
diff --git a/code/modules/surgery/blood_filter.dm b/code/modules/surgery/blood_filter.dm
index 3bba978d93051..41082810aae80 100644
--- a/code/modules/surgery/blood_filter.dm
+++ b/code/modules/surgery/blood_filter.dm
@@ -19,7 +19,7 @@
time = 2.5 SECONDS
/datum/surgery/blood_filter/can_start(mob/user, mob/living/carbon/target)
- if(HAS_TRAIT(target, TRAIT_HUSK) || target.reagents.total_volume==0) //Can't filter husk or 0 regent body
+ if(HAS_TRAIT(target, TRAIT_HUSK) || target.reagents?.total_volume == 0) //Can't filter husk or 0 regent body
return FALSE
return ..()
diff --git a/code/modules/surgery/bodyparts/bodyparts.dm b/code/modules/surgery/bodyparts/bodyparts.dm
index 2b40d660db473..edb1c01139289 100644
--- a/code/modules/surgery/bodyparts/bodyparts.dm
+++ b/code/modules/surgery/bodyparts/bodyparts.dm
@@ -8,7 +8,7 @@
icon_state = ""
layer = BELOW_MOB_LAYER //so it isn't hidden behind objects when on the floor
var/mob/living/carbon/owner = null
- var/mob/living/carbon/original_owner = null
+ var/datum/weakref/original_owner = null
var/status = BODYPART_ORGANIC
var/needs_processing = FALSE
@@ -29,10 +29,12 @@
var/burnstate = 0
var/brute_dam = 0
var/burn_dam = 0
- var/stamina_dam = 0
var/max_stamina_damage = 0
var/max_damage = 0
+ var/stamina_dam = 0
+ var/stamina_heal_rate = 1 //Stamina heal multiplier
+
var/brute_reduction = 0 //Subtracted to brute damage taken
var/burn_reduction = 0 //Subtracted to burn damage taken
@@ -97,7 +99,7 @@
..()
/obj/item/bodypart/attackby(obj/item/W, mob/user, params)
- if(W.sharpness)
+ if(W.is_sharp())
add_fingerprint(user)
if(!contents.len)
to_chat(user, "There is nothing left inside [src]!")
@@ -136,7 +138,7 @@
//Return TRUE to get whatever mob this is in to update health.
/obj/item/bodypart/proc/on_life(stam_regen)
if(stamina_dam > DAMAGE_PRECISION && stam_regen) //DO NOT update health here, it'll be done in the carbon's life.
- heal_damage(0, 0, INFINITY, null, FALSE)
+ heal_damage(0, 0, stam_regen, null, FALSE)
. |= BODYPART_LIFE_UPDATE_HEALTH
//Applies brute and burn damage to the organ. Returns 1 if the damage-icon states changed at all.
@@ -182,14 +184,15 @@
//We've dealt the physical damages, if there's room lets apply the stamina damage.
var/current_damage = get_damage(TRUE) //This time around, count stamina loss too.
var/available_damage = max_damage - current_damage
- stamina_dam += round(CLAMP(stamina, 0, min(max_stamina_damage - stamina_dam, available_damage)), DAMAGE_PRECISION)
+ var/applied_damage = min(max_stamina_damage - stamina_dam, available_damage)
+ stamina_dam += round(CLAMP(stamina, 0, applied_damage), DAMAGE_PRECISION)
if(owner && updating_health)
owner.updatehealth()
if(stamina > DAMAGE_PRECISION)
- owner.update_stamina()
- owner.stam_regen_start_time = world.time + STAMINA_REGEN_BLOCK_TIME
+ owner.update_stamina(TRUE)
+ owner.stam_regen_start_time = max(owner.stam_regen_start_time, world.time + STAMINA_REGEN_BLOCK_TIME)
consider_processing()
update_disabled()
return update_bodypart_damage_state()
@@ -250,8 +253,8 @@
//Updates an organ's brute/burn states for use by update_damage_overlays()
//Returns 1 if we need to update overlays. 0 otherwise.
/obj/item/bodypart/proc/update_bodypart_damage_state()
- var/tbrute = round( (brute_dam/max_damage)*3, 1 )
- var/tburn = round( (burn_dam/max_damage)*3, 1 )
+ var/tbrute = round((min(brute_dam, max_damage) / max_damage) * 3, 1)
+ var/tburn = round((min(burn_dam, max_damage) / max_damage) * 3, 1)
if((tbrute != brutestate) || (tburn != burnstate))
brutestate = tbrute
burnstate = tburn
@@ -288,8 +291,8 @@
if(source)
C = source
if(!original_owner)
- original_owner = source
- else if(original_owner && owner != original_owner) //Foreign limb
+ original_owner = WEAKREF(source)
+ else if(original_owner && !IS_WEAKREF_OF(owner, original_owner)) //Foreign limb
no_update = TRUE
else
C = owner
@@ -535,7 +538,7 @@
if(held_index)
owner.dropItemToGround(owner.get_item_for_held_index(held_index))
if(owner.hud_used)
- var/obj/screen/inventory/hand/L = owner.hud_used.hand_slots["[held_index]"]
+ var/atom/movable/screen/inventory/hand/L = owner.hud_used.hand_slots["[held_index]"]
if(L)
L.update_icon()
@@ -603,7 +606,7 @@
if(held_index)
owner.dropItemToGround(owner.get_item_for_held_index(held_index))
if(owner.hud_used)
- var/obj/screen/inventory/hand/R = owner.hud_used.hand_slots["[held_index]"]
+ var/atom/movable/screen/inventory/hand/R = owner.hud_used.hand_slots["[held_index]"]
if(R)
R.update_icon()
diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm
index 75a41fb93a191..46fd1c5ba805e 100644
--- a/code/modules/surgery/bodyparts/dismemberment.dm
+++ b/code/modules/surgery/bodyparts/dismemberment.dm
@@ -21,12 +21,12 @@
C.emote("scream")
SEND_SIGNAL(C, COMSIG_ADD_MOOD_EVENT, "dismembered", /datum/mood_event/dismembered)
drop_limb()
-
+
C.update_equipment_speed_mods() // Update in case speed affecting item unequipped by dismemberment
var/turf/location = C.loc
if(istype(location))
C.add_splatter_floor(location)
-
+
if(QDELETED(src)) //Could have dropped into lava/explosion/chasm/whatever
return TRUE
if(dam_type == BURN)
@@ -155,7 +155,7 @@
LB.brainmob = brainmob
brainmob = null
LB.brainmob.forceMove(LB)
- LB.brainmob.stat = DEAD
+ LB.brainmob.set_stat(DEAD)
/obj/item/organ/eyes/transfer_to_limb(obj/item/bodypart/head/LB, mob/living/carbon/human/C)
LB.eyes = src
@@ -183,7 +183,7 @@
C.handcuffed = null
C.update_handcuffed()
if(C.hud_used)
- var/obj/screen/inventory/hand/R = C.hud_used.hand_slots["[held_index]"]
+ var/atom/movable/screen/inventory/hand/R = C.hud_used.hand_slots["[held_index]"]
if(R)
R.update_icon()
if(C.gloves)
@@ -201,7 +201,7 @@
C.handcuffed = null
C.update_handcuffed()
if(C.hud_used)
- var/obj/screen/inventory/hand/L = C.hud_used.hand_slots["[held_index]"]
+ var/atom/movable/screen/inventory/hand/L = C.hud_used.hand_slots["[held_index]"]
if(L)
L.update_icon()
if(C.gloves)
@@ -286,7 +286,7 @@
if(C.dna.species.mutanthands && !is_pseudopart)
C.put_in_hand(new C.dna.species.mutanthands(), held_index)
if(C.hud_used)
- var/obj/screen/inventory/hand/hand = C.hud_used.hand_slots["[held_index]"]
+ var/atom/movable/screen/inventory/hand/hand = C.hud_used.hand_slots["[held_index]"]
if(hand)
hand.update_icon()
C.update_inv_gloves()
diff --git a/code/modules/surgery/bodyparts/helpers.dm b/code/modules/surgery/bodyparts/helpers.dm
index f9e0703925ed8..f9bd5db1f634e 100644
--- a/code/modules/surgery/bodyparts/helpers.dm
+++ b/code/modules/surgery/bodyparts/helpers.dm
@@ -137,25 +137,25 @@
disabled += zone
return disabled
-//Remove all embedded objects from all limbs on the carbon mob
-/mob/living/carbon/proc/remove_all_embedded_objects()
- var/turf/T = get_turf(src)
+///Remove a specific embedded item from the carbon mob
+/mob/living/carbon/proc/remove_embedded_object(obj/item/I)
+ SEND_SIGNAL(src, COMSIG_CARBON_EMBED_REMOVAL, I)
+///Remove all embedded objects from all limbs on the carbon mob
+/mob/living/carbon/proc/remove_all_embedded_objects()
for(var/X in bodyparts)
var/obj/item/bodypart/L = X
for(var/obj/item/I in L.embedded_objects)
- L.embedded_objects -= I
- I.forceMove(T)
-
- clear_alert("embeddedobject")
- SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "embedded")
+ remove_embedded_object(I)
-/mob/living/carbon/proc/has_embedded_objects()
- . = 0
+/mob/living/carbon/proc/has_embedded_objects(include_harmless=FALSE)
for(var/X in bodyparts)
var/obj/item/bodypart/L = X
for(var/obj/item/I in L.embedded_objects)
- return 1
+ if(!include_harmless && I.isEmbedHarmless())
+ continue
+ return TRUE
+
///Get the bodypart for whatever hand we have active, Only relevant for carbons
/mob/proc/get_active_hand()
return FALSE
diff --git a/code/modules/surgery/bodyparts/robot_bodyparts.dm b/code/modules/surgery/bodyparts/robot_bodyparts.dm
index 961835336fcf6..ead173cd2dd14 100644
--- a/code/modules/surgery/bodyparts/robot_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/robot_bodyparts.dm
@@ -174,13 +174,13 @@
/obj/item/bodypart/chest/robot/examine(mob/user)
. = ..()
if(cell)
- . += {"It has a [cell] inserted.\n
- You can use a screwdriver to remove [cell]."}
+ . += "It has a [cell] inserted.\n"+\
+ "You can use a screwdriver to remove [cell]."
else
. += "It has an empty port for a power cell."
if(wired)
- . += {"Its all wired up[cell ? " and ready for usage" : ""].\n
- You can use wirecutters to remove the wiring."}
+ . += "Its all wired up[cell ? " and ready for usage" : ""].\n"+\
+ "You can use wirecutters to remove the wiring."
else
. += "It has a couple spots that still need to be wired."
@@ -238,8 +238,8 @@
var/single_flash = FALSE
if(!flash1 || !flash2)
single_flash = TRUE
- . += {"One of its eye sockets is currently occupied by a flash.\n
- It has an empty eye socket for another flash."}
+ . += "One of its eye sockets is currently occupied by a flash.\n"+\
+ "It has an empty eye socket for another flash."
else
. += "It has two eye sockets occupied by flashes."
. += "You can remove the seated flash[single_flash ? "":"es"] with a crowbar."
diff --git a/code/modules/surgery/core_removal.dm b/code/modules/surgery/core_removal.dm
index d8ed1d78da7fe..5c9b8f334d28b 100644
--- a/code/modules/surgery/core_removal.dm
+++ b/code/modules/surgery/core_removal.dm
@@ -30,8 +30,9 @@
"[user] successfully extracts a core from [target]!",
"[user] successfully extracts a core from [target]!")
- new slime.coretype(slime.loc)
-
+ var/obj/item/slime_extract/item = new slime.coretype(slime.loc)
+ if(slime.transformeffects & SLIME_EFFECT_GOLD)
+ item.sparkly = TRUE
if(slime.cores <= 0)
slime.icon_state = "[slime.colour] baby slime dead-nocore"
return 1
@@ -39,4 +40,4 @@
return 0
else
to_chat(user, "There aren't any cores left in [target]!")
- return 1
\ No newline at end of file
+ return 1
diff --git a/code/modules/surgery/healing.dm b/code/modules/surgery/healing.dm
index 157c86052d855..4450c06244a28 100644
--- a/code/modules/surgery/healing.dm
+++ b/code/modules/surgery/healing.dm
@@ -30,6 +30,9 @@
var/burnhealing = 0
var/missinghpbonus = 0 //heals an extra point of damager per X missing damage of type (burn damage for burn healing, brute for brute). Smaller Number = More Healing!
+/datum/surgery_step/heal/proc/get_progress(mob/user, mob/living/carbon/target, brute_healed, burn_healed)
+ return
+
/datum/surgery_step/heal/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
var/woundtype
if(brutehealing && burnhealing)
@@ -70,6 +73,8 @@
umsg += " as best as you can while they have clothing on"
tmsg += " as best as they can while [target] has clothing on"
target.heal_bodypart_damage(urhealedamt_brute,urhealedamt_burn)
+ umsg += get_progress(user, target, urhealedamt_brute, urhealedamt_burn)
+
display_results(user, target, "[umsg].",
"[tmsg].",
"[tmsg].")
@@ -116,6 +121,33 @@
desc = "A surgical procedure that provides experimental treatment for a patient's brute traumas. Heals considerably more when the patient is severely injured."
/********************BRUTE STEPS********************/
+/datum/surgery_step/heal/brute/get_progress(mob/user, mob/living/carbon/target, brute_healed, burn_healed)
+ if(!brute_healed)
+ return
+
+ var/estimated_remaining_steps = target.getBruteLoss() / brute_healed
+ var/progress_text
+ if(locate(/obj/item/healthanalyzer) in user.held_items)
+ progress_text = ". Remaining brute: [target.getBruteLoss()]"
+ else
+ switch(estimated_remaining_steps)
+ if(-INFINITY to 1)
+ return
+ if(1 to 3)
+ progress_text = ", stitching up the last few scrapes"
+ if(3 to 6)
+ progress_text = ", counting down the last few bruises left to treat"
+ if(6 to 9)
+ progress_text = ", continuing to plug away at [target.p_their()] extensive rupturing"
+ if(9 to 12)
+ progress_text = ", steadying yourself for the long surgery ahead"
+ if(12 to 15)
+ progress_text = ", though [target.p_they()] still look[target.p_s()] more like ground beef than a person"
+ if(15 to INFINITY)
+ progress_text = ", though you feel like you're barely making a dent in treating [target.p_their()] pulped body"
+
+ return progress_text
+
/datum/surgery_step/heal/brute/basic
name = "tend bruises"
brutehealing = 5
@@ -154,6 +186,33 @@
desc = "A surgical procedure that provides experimental treatment for a patient's burns. Heals considerably more when the patient is severely injured."
/********************BURN STEPS********************/
+/datum/surgery_step/heal/burn/get_progress(mob/user, mob/living/carbon/target, brute_healed, burn_healed)
+ if(!burn_healed)
+ return
+
+ var/estimated_remaining_steps = target.getFireLoss() / burn_healed
+ var/progress_text
+ if(locate(/obj/item/healthanalyzer) in user.held_items)
+ progress_text = ". Remaining brute: [target.getFireLoss()]"
+ else
+ switch(estimated_remaining_steps)
+ if(-INFINITY to 1)
+ return
+ if(1 to 3)
+ progress_text = ", finishing up the last few singe marks"
+ if(3 to 6)
+ progress_text = ", counting down the last few blisters left to treat"
+ if(6 to 9)
+ progress_text = ", continuing to plug away at [target.p_their()] thorough roasting"
+ if(9 to 12)
+ progress_text = ", steadying yourself for the long surgery ahead"
+ if(12 to 15)
+ progress_text = ", though [target.p_they()] still look[target.p_s()] more like burnt steak than a person"
+ if(15 to INFINITY)
+ progress_text = ", though you feel like you're barely making a dent in treating [target.p_their()] charred body"
+
+ return progress_text
+
/datum/surgery_step/heal/burn/basic
name = "tend burn wounds"
burnhealing = 5
@@ -168,9 +227,6 @@
missinghpbonus = 5
/***************************COMBO***************************/
-/datum/surgery/healing/combo
-
-
/datum/surgery/healing/combo
name = "Tend Wounds (Mixture, Basic)"
replaced_by = /datum/surgery/healing/combo/upgraded
@@ -192,6 +248,39 @@
desc = "A surgical procedure that provides experimental treatment for a patient's burns and brute traumas. Heals considerably more when the patient is severely injured."
/********************COMBO STEPS********************/
+/datum/surgery_step/heal/combo/get_progress(mob/user, mob/living/carbon/target, brute_healed, burn_healed)
+ var/estimated_remaining_steps = 0
+ if(brute_healed > 0)
+ estimated_remaining_steps = max(0, (target.getBruteLoss() / brute_healed))
+ if(burn_healed > 0)
+ estimated_remaining_steps = max(estimated_remaining_steps, (target.getFireLoss() / burn_healed)) // whichever is higher between brute or burn steps
+
+ var/progress_text
+
+ if(locate(/obj/item/healthanalyzer) in user.held_items)
+ if(target.getBruteLoss())
+ progress_text = ". Remaining brute: [target.getBruteLoss()]"
+ if(target.getFireLoss())
+ progress_text += ". Remaining burn: [target.getFireLoss()]"
+ else
+ switch(estimated_remaining_steps)
+ if(-INFINITY to 1)
+ return
+ if(1 to 3)
+ progress_text = ", finishing up the last few signs of damage"
+ if(3 to 6)
+ progress_text = ", counting down the last few patches of trauma"
+ if(6 to 9)
+ progress_text = ", continuing to plug away at [target.p_their()] extensive injuries"
+ if(9 to 12)
+ progress_text = ", steadying yourself for the long surgery ahead"
+ if(12 to 15)
+ progress_text = ", though [target.p_they()] still look[target.p_s()] more like smooshed baby food than a person"
+ if(15 to INFINITY)
+ progress_text = ", though you feel like you're barely making a dent in treating [target.p_their()] broken body"
+
+ return progress_text
+
/datum/surgery_step/heal/combo
name = "tend physical wounds"
brutehealing = 3
diff --git a/code/modules/surgery/helpers.dm b/code/modules/surgery/helpers.dm
index d252b33101590..69fb9d1e4290b 100644
--- a/code/modules/surgery/helpers.dm
+++ b/code/modules/surgery/helpers.dm
@@ -69,12 +69,14 @@
if(S.ignore_clothes || get_location_accessible(M, selected_zone))
var/datum/surgery/procedure = new S.type(M, selected_zone, affecting)
- user.visible_message("[user] drapes [I] over [M]'s [parse_zone(selected_zone)] to prepare for surgery.", \
- "You drape [I] over [M]'s [parse_zone(selected_zone)] to prepare for \an [procedure.name].")
+ user.visible_message("[user] drapes [I] over [M]'s [parse_zone(selected_zone)] to prepare for surgery.",
+ "You drape [I] over [M]'s [parse_zone(selected_zone)] to prepare for \an [procedure.name].")
+ I.balloon_alert(user, "You drape over [parse_zone(selected_zone)]")
log_combat(user, M, "operated on", null, "(OPERATION TYPE: [procedure.name]) (TARGET AREA: [selected_zone])")
else
- to_chat(user, "You need to expose [M]'s [parse_zone(selected_zone)] first!")
+ I.balloon_alert(user, "[parse_zone(selected_zone)] is covered up")
+
else if(!current_surgery.step_in_progress)
attempt_cancel_surgery(current_surgery, I, M, user)
@@ -83,36 +85,35 @@
/proc/attempt_cancel_surgery(datum/surgery/S, obj/item/I, mob/living/M, mob/user)
var/selected_zone = user.zone_selected
+
if(S.status == 1)
M.surgeries -= S
user.visible_message("[user] removes [I] from [M]'s [parse_zone(selected_zone)].", \
"You remove [I] from [M]'s [parse_zone(selected_zone)].")
+ I.balloon_alert(user, "You remove [I] from [parse_zone(selected_zone)]")
qdel(S)
- else if(S.can_cancel)
+ return
+
+ if(S.can_cancel)
var/required_tool_type = TOOL_CAUTERY
var/obj/item/close_tool = user.get_inactive_held_item()
var/is_robotic = S.requires_bodypart_type == BODYPART_ROBOTIC
+
if(is_robotic)
required_tool_type = TOOL_SCREWDRIVER
- if(close_tool?.tool_behaviour == required_tool_type || iscyborg(user))
- M.surgeries -= S
- user.visible_message("[user] closes [M]'s [parse_zone(selected_zone)] with [close_tool] and removes [I].", \
- "You close [M]'s [parse_zone(selected_zone)] with [close_tool] and remove [I].")
- qdel(S)
- else
- to_chat(user, "You need to hold a [is_robotic ? "screwdriver" : "cautery"] in your inactive hand to stop [M]'s surgery!")
-
-/proc/get_location_modifier(mob/M)
- var/turf/T = get_turf(M)
- if(locate(/obj/structure/table/optable, T))
- return 1
- else if(locate(/obj/structure/table, T))
- return 0.8
- else if(locate(/obj/structure/bed, T))
- return 0.7
- else
- return 0.5
+ if(iscyborg(user))
+ close_tool = locate(/obj/item/cautery) in user.held_items
+ if(!close_tool)
+ to_chat(user, "You need to equip a cautery in an inactive slot to stop [M]'s surgery!")
+ return
+ else if(close_tool?.tool_behaviour != required_tool_type)
+ to_chat(user, "You need to hold a [is_robotic ? "screwdriver" : "cautery"] in your inactive hand to stop [M]'s surgery!")
+ return
+ M.surgeries -= S
+ user.visible_message("[user] closes [M]'s [parse_zone(selected_zone)] with [close_tool] and removes [I].", \
+ "You close [M]'s [parse_zone(selected_zone)] with [close_tool] and remove [I].")
+ qdel(S)
/proc/get_location_accessible(mob/M, location)
var/covered_locations = 0 //based on body_parts_covered
diff --git a/code/modules/surgery/latex_glove_box.dm b/code/modules/surgery/latex_glove_box.dm
new file mode 100644
index 0000000000000..fb63d1612aadb
--- /dev/null
+++ b/code/modules/surgery/latex_glove_box.dm
@@ -0,0 +1,79 @@
+/obj/item/glove_box
+ name = "box of latex gloves"
+ desc = "A box of latex gloves, useful for quick cleanup after surgery."
+ icon = 'icons/obj/surgery.dmi'
+ icon_state = "latex_glove_box"
+ item_state = "deliverypackage"
+ lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/items_righthand.dmi'
+ throwforce = 0
+ w_class = WEIGHT_CLASS_NORMAL
+ throw_speed = 3
+ throw_range = 7
+ pressure_resistance = 8
+ var/glove_type = /obj/item/clothing/gloves/color/latex
+ var/total_gloves = 50
+
+/obj/item/glove_box/Initialize()
+ . = ..()
+ interaction_flags_item &= ~INTERACT_ITEM_ATTACK_HAND_PICKUP
+ update_icon()
+
+/obj/item/glove_box/MouseDrop(atom/over_object)
+ . = ..()
+ var/mob/living/M = usr
+ if(!istype(M) || M.incapacitated() || !Adjacent(M))
+ return
+
+ if(over_object == M)
+ M.put_in_hands(src)
+
+ else if(istype(over_object, /atom/movable/screen/inventory/hand))
+ var/atom/movable/screen/inventory/hand/H = over_object
+ M.putItemFromInventoryInHandIfPossible(src, H.held_index)
+
+ add_fingerprint(M)
+
+/obj/item/glove_box/attack_paw(mob/user)
+ return attack_hand(user)
+
+/obj/item/glove_box/attack_hand(mob/user)
+ if(isliving(user))
+ var/mob/living/L = user
+ if(!(L.mobility_flags & MOBILITY_PICKUP))
+ return
+ if(total_gloves >= 1)
+ total_gloves--
+ var/obj/item/clothing/gloves/G
+ G = new glove_type(src)
+ G.add_fingerprint(user)
+ G.forceMove(user.loc)
+ user.put_in_hands(G)
+ to_chat(user, "You take [G] out of \the [src].")
+ else
+ to_chat(user, "[src] is empty!")
+ update_icon()
+ add_fingerprint(user)
+ return ..()
+
+/obj/item/glove_box/examine(mob/user)
+ . = ..()
+ if(total_gloves)
+ . += "It contains [total_gloves] pair of gloves."
+ else
+ . += "It is empty."
+
+/obj/item/glove_box/update_icon()
+ cut_overlays()
+ if(total_gloves > 1)
+ add_overlay("glove_in")
+
+/obj/item/glove_box/attack_self(mob/user)
+ . = ..()
+ if(total_gloves > 0)
+ to_chat(user, "You can't fold this box with items still inside!")
+ return
+ to_chat(user, "You fold [src] flat.")
+ qdel(src)
+ user.put_in_hands(new /obj/item/stack/sheet/cardboard())
+
diff --git a/code/modules/surgery/limb_augmentation.dm b/code/modules/surgery/limb_augmentation.dm
index 94e2b46fcc44b..43cee3c92dd98 100644
--- a/code/modules/surgery/limb_augmentation.dm
+++ b/code/modules/surgery/limb_augmentation.dm
@@ -23,9 +23,13 @@
return -1
L = surgery.operated_bodypart
if(L)
- display_results(user, target, "You begin to augment [target]'s [parse_zone(user.zone_selected)]...",
- "[user] begins to augment [target]'s [parse_zone(user.zone_selected)] with [aug].",
- "[user] begins to augment [target]'s [parse_zone(user.zone_selected)].")
+ if(L.is_disabled() == BODYPART_DISABLED_PARALYSIS)
+ to_chat(user, "You can't augment a limb with paralysis!")
+ return -1
+ else
+ display_results(user, target, "You begin to augment [target]'s [parse_zone(user.zone_selected)]...",
+ "[user] begins to augment [target]'s [parse_zone(user.zone_selected)] with [aug].",
+ "[user] begins to augment [target]'s [parse_zone(user.zone_selected)].")
else
user.visible_message("[user] looks for [target]'s [parse_zone(user.zone_selected)].", "You look for [target]'s [parse_zone(user.zone_selected)]...")
@@ -39,6 +43,11 @@
possible_locs = list(BODY_ZONE_R_ARM,BODY_ZONE_L_ARM,BODY_ZONE_R_LEG,BODY_ZONE_L_LEG,BODY_ZONE_CHEST,BODY_ZONE_HEAD)
requires_real_bodypart = TRUE
+
+
+/datum/surgery/augmentation/can_start(mob/user, mob/living/carbon/target)
+ return ..() && !isoozeling(target)
+
//SURGERY STEP SUCCESSES
/datum/surgery_step/replace_limb/success(mob/user, mob/living/carbon/target, target_zone, obj/item/bodypart/tool, datum/surgery/surgery)
diff --git a/code/modules/surgery/organ_manipulation.dm b/code/modules/surgery/organ_manipulation.dm
index 3feb6f352b09b..f1f5c3ecd4b0c 100644
--- a/code/modules/surgery/organ_manipulation.dm
+++ b/code/modules/surgery/organ_manipulation.dm
@@ -45,7 +45,7 @@
requires_bodypart_type = BODYPART_ROBOTIC
lying_required = FALSE
self_operable = TRUE
- success_multiplier = 0.8 //on a surgery bed you can do prosthetic manipulation relatively risk-free
+ speed_modifier = 0.8 //on a surgery bed you can do prosthetic manipulation relatively risk-free
steps = list(
/datum/surgery_step/mechanic_open,
/datum/surgery_step/open_hatch,
@@ -96,27 +96,19 @@
if(target_zone != I.zone || target.getorganslot(I.slot))
to_chat(user, "There is no room for [I] in [target]'s [parse_zone(target_zone)]!")
return -1
- if(istype(tool, /obj/item/organ/brain/positron))
- var/obj/item/bodypart/affected = target.get_bodypart(check_zone(target_zone))
- if(!affected)
- return -1
- if(affected.status != ORGAN_ROBOTIC)
- to_chat(user, "You can't put [tool] into a meat enclosure!")
- return -1
- if(!isipc(target))
- to_chat(user, "[target] does not have the proper connectors to interface with [tool].")
- return -1
- if(target_zone != "chest")
- to_chat(user, "You have to install [tool] in [target]'s chest!")
- if(target.internal_organs_slot["brain"])
- to_chat(user, "[target] already has a brain! You'd rather not find out what would happen with two in there.")
- return -1
- user.visible_message("[user] begins to insert [tool] into [target]'s [parse_zone(target_zone)].",
- "You begin to insert [tool] into [target]'s [parse_zone(target_zone)]...")
-
- display_results(user, target, "You begin to insert [tool] into [target]'s [parse_zone(target_zone)]...",
- "[user] begins to insert [tool] into [target]'s [parse_zone(target_zone)].",
- "[user] begins to insert something into [target]'s [parse_zone(target_zone)].")
+ if(istype(I, /obj/item/organ/brain/positron))
+ var/obj/item/bodypart/affected = target.get_bodypart(check_zone(I.zone))
+ if(!affected)
+ return -1
+ if(affected.status != BODYPART_ROBOTIC)
+ to_chat(user, "You can't put [I] into a meat enclosure!")
+ return -1
+ if(!isipc(target))
+ to_chat(user, "[target] does not have the proper connectors to interface with [I].")
+ return -1
+ display_results(user, target, "You begin to insert [I] into [target]'s [parse_zone(target_zone)]...",
+ "[user] begins to insert [I] into [target]'s [parse_zone(target_zone)].",
+ "[user] begins to insert something into [target]'s [parse_zone(target_zone)].")
else if(implement_type in implements_extract)
current_type = "extract"
diff --git a/code/modules/surgery/organic_steps.dm b/code/modules/surgery/organic_steps.dm
index a3c34a9ea71c9..3ffc71ca9734a 100644
--- a/code/modules/surgery/organic_steps.dm
+++ b/code/modules/surgery/organic_steps.dm
@@ -96,7 +96,7 @@
/datum/surgery_step/saw
name = "saw bone"
implements = list(TOOL_SAW = 100,/obj/item/melee/arm_blade = 75,
- /obj/item/twohanded/fireaxe = 50, /obj/item/hatchet = 35, /obj/item/kitchen/knife/butcher = 25)
+ /obj/item/fireaxe = 50, /obj/item/hatchet = 35, /obj/item/kitchen/knife/butcher = 25)
time = 54
/datum/surgery_step/saw/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
@@ -114,9 +114,16 @@
//drill bone
/datum/surgery_step/drill
name = "drill bone"
- implements = list(TOOL_DRILL = 100, /obj/item/screwdriver/power = 80, /obj/item/pickaxe/drill = 60, TOOL_SCREWDRIVER = 20)
+ implements = list(TOOL_DRILL = 100, /obj/item/powertool/hand_drill = 80, /obj/item/pickaxe/drill = 60, TOOL_SCREWDRIVER = 20)
time = 30
+/datum/surgery_step/drill/tool_check(mob/user, obj/item/tool)
+ if(istype(tool, /obj/item/powertool/hand_drill))
+ var/obj/item/powertool/hand_drill = tool
+ if(hand_drill.tool_behaviour != TOOL_SCREWDRIVER)
+ return FALSE
+ return TRUE
+
/datum/surgery_step/drill/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
display_results(user, target, "You begin to drill into the bone in [target]'s [parse_zone(target_zone)]...",
"[user] begins to drill into the bone in [target]'s [parse_zone(target_zone)].",
diff --git a/code/modules/surgery/organs/augments_arms.dm b/code/modules/surgery/organs/augments_arms.dm
index ea5468ea9a1e7..621ce644612ab 100644
--- a/code/modules/surgery/organs/augments_arms.dm
+++ b/code/modules/surgery/organs/augments_arms.dm
@@ -5,22 +5,38 @@
icon_state = "implant-toolkit"
w_class = WEIGHT_CLASS_SMALL
actions_types = list(/datum/action/item_action/organ_action/toggle)
-
- var/list/items_list = list()
- // Used to store a list of all items inside, for multi-item implants.
- // I would use contents, but they shuffle on every activation/deactivation leading to interface inconsistencies.
-
- var/obj/item/holder = null
- // You can use this var for item path, it would be converted into an item on New()
+ ///A ref for the arm we're taking up. Mostly for the unregister signal upon removal
+ var/obj/hand
+ //A list of typepaths to create and insert into ourself on init
+ var/list/items_to_create = list()
+ /// Used to store a list of all items inside, for multi-item implants.
+ var/list/items_list = list()// I would use contents, but they shuffle on every activation/deactivation leading to interface inconsistencies.
+ /// You can use this var for item path, it would be converted into an item on New().
+ var/obj/item/active_item
/obj/item/organ/cyberimp/arm/Initialize()
. = ..()
- if(ispath(holder))
- holder = new holder(src)
+ if(ispath(active_item))
+ active_item = new active_item(src)
+ items_list += WEAKREF(active_item)
+
+ for(var/typepath in items_to_create)
+ var/atom/new_item = new typepath(src)
+ items_list += WEAKREF(new_item)
update_icon()
SetSlotFromZone()
- items_list = contents.Copy()
+
+/obj/item/organ/cyberimp/arm/Destroy()
+ hand = null
+ active_item = null
+ for(var/datum/weakref/ref in items_list)
+ var/obj/item/to_del = ref.resolve()
+ if(!to_del)
+ continue
+ qdel(to_del)
+ items_list.Cut()
+ return ..()
/obj/item/organ/cyberimp/arm/proc/SetSlotFromZone()
switch(zone)
@@ -54,10 +70,27 @@
to_chat(user, "You modify [src] to be installed on the [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm.")
update_icon()
+/obj/item/organ/cyberimp/arm/Insert(mob/living/carbon/M, special = FALSE, drop_if_replaced = TRUE)
+ . = ..()
+ var/side = zone == BODY_ZONE_R_ARM ? 2 : 1
+ hand = owner.hand_bodyparts[side]
+ if(hand)
+ RegisterSignal(hand, COMSIG_ITEM_ATTACK_SELF, .proc/on_item_attack_self) //If the limb gets an attack-self, open the menu. Only happens when hand is empty
+
/obj/item/organ/cyberimp/arm/Remove(mob/living/carbon/M, special = 0)
Retract()
+ if(hand)
+ UnregisterSignal(hand, COMSIG_ITEM_ATTACK_SELF)
..()
+/obj/item/organ/cyberimp/arm/proc/on_item_attack_self()
+ SIGNAL_HANDLER
+ INVOKE_ASYNC(src, .proc/ui_action_click)
+
+/obj/item/organ/cyberimp/arm/proc/on_item_drop()
+ SIGNAL_HANDLER
+ INVOKE_ASYNC(src, .proc/Retract)
+
/obj/item/organ/cyberimp/arm/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
@@ -68,73 +101,75 @@
Retract()
/obj/item/organ/cyberimp/arm/proc/Retract()
- if(!holder || (holder in src))
+ if(!active_item || (active_item in src))
return
- owner.visible_message("[owner] retracts [holder] back into [owner.p_their()] [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm.",
- "[holder] snaps back into your [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm.",
+ owner.visible_message("[owner] retracts [active_item] back into [owner.p_their()] [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm.",
+ "[active_item] snaps back into your [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm.",
"You hear a short mechanical noise.")
- if(istype(holder, /obj/item/assembly/flash/armimplant))
- var/obj/item/assembly/flash/F = holder
- F.set_light(0)
-
- owner.transferItemToLoc(holder, src, TRUE)
- holder = null
+ owner.transferItemToLoc(active_item, src, TRUE)
+ UnregisterSignal(active_item, COMSIG_ITEM_DROPPED)
+ REMOVE_TRAIT(active_item, TRAIT_NO_STORAGE_INSERT, HAND_REPLACEMENT_TRAIT)
+ active_item = null
playsound(get_turf(owner), 'sound/mecha/mechmove03.ogg', 50, 1)
/obj/item/organ/cyberimp/arm/proc/Extend(var/obj/item/item)
if(!(item in src))
return
- holder = item
+ active_item = item
+ RegisterSignal(active_item, COMSIG_ITEM_DROPPED, .proc/on_item_drop) //Drop it to put away.
+ ADD_TRAIT(active_item, TRAIT_NO_STORAGE_INSERT, HAND_REPLACEMENT_TRAIT)
- ADD_TRAIT(holder, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)
- holder.resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
- holder.slot_flags = null
- holder.materials = null
+ active_item.resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ active_item.slot_flags = null
+ active_item.set_custom_materials(null)
- if(istype(holder, /obj/item/assembly/flash/armimplant))
- var/obj/item/assembly/flash/F = holder
- F.set_light(7)
-
- var/obj/item/arm_item = owner.get_active_held_item()
-
- if(arm_item)
- if(!owner.dropItemToGround(arm_item))
- to_chat(owner, "Your [arm_item] interferes with [src]!")
+ var/side = zone == BODY_ZONE_R_ARM ? "right" : "left"
+ var/hand = owner.get_empty_held_index_for_side(side)
+ if(hand)
+ owner.put_in_hand(active_item, hand)
+ else
+ var/list/hand_items = owner.get_held_items_for_side(side, all = TRUE)
+ var/success = FALSE
+ var/list/failure_message = list()
+ for(var/i in 1 to hand_items.len) //Can't just use *in* here.
+ var/I = hand_items[i]
+ if(!owner.dropItemToGround(I))
+ failure_message += "Your [I] interferes with [src]!"
+ continue
+ to_chat(owner, "You drop [I] to activate [src]!")
+ success = owner.put_in_hand(active_item, owner.get_empty_held_index_for_side(side))
+ break
+ if(!success)
+ for(var/i in failure_message)
+ to_chat(owner, i)
return
- else
- to_chat(owner, "You drop [arm_item] to activate [src]!")
-
- var/result = (zone == BODY_ZONE_R_ARM ? owner.put_in_r_hand(holder) : owner.put_in_l_hand(holder))
- if(!result)
- to_chat(owner, "Your [name] fails to activate!")
- return
-
- // Activate the hand that now holds our item.
- owner.swap_hand(result)//... or the 1st hand if the index gets lost somehow
-
- owner.visible_message("[owner] extends [holder] from [owner.p_their()] [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm.",
- "You extend [holder] from your [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm.",
+ owner.visible_message("[owner] extends [active_item] from [owner.p_their()] [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm.",
+ "You extend [active_item] from your [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm.",
"You hear a short mechanical noise.")
- playsound(get_turf(owner), 'sound/mecha/mechmove03.ogg', 50, 1)
+ playsound(get_turf(owner), 'sound/mecha/mechmove03.ogg', 50, TRUE)
/obj/item/organ/cyberimp/arm/ui_action_click()
- if((organ_flags & ORGAN_FAILING) || (!holder && !contents.len))
+ if((organ_flags & ORGAN_FAILING) || (!active_item && !contents.len))
to_chat(owner, "The implant doesn't respond. It seems to be broken...")
return
- if(!holder || (holder in src))
- holder = null
+ if(!active_item || (active_item in src))
+ active_item = null
if(contents.len == 1)
Extend(contents[1])
else
var/list/choice_list = list()
- for(var/obj/item/I in items_list)
- choice_list[I] = image(I)
+ for(var/datum/weakref/augment_ref in items_list)
+ var/obj/item/augment_item = augment_ref.resolve()
+ if(!augment_item)
+ items_list -= augment_ref
+ continue
+ choice_list[augment_item] = image(augment_item)
var/obj/item/choice = show_radial_menu(owner, owner, choice_list)
- if(owner && owner == usr && owner.stat != DEAD && (src in owner.internal_organs) && !holder && (choice in contents))
+ if(owner && owner == usr && owner.stat != DEAD && (src in owner.internal_organs) && !active_item && (choice in contents))
// This monster sanity check is a nice example of how bad input is.
Extend(choice)
else
@@ -160,17 +195,23 @@
name = "arm-mounted laser implant"
desc = "A variant of the arm cannon implant that fires lethal laser beams. The cannon emerges from the subject's arm and remains inside when not in use."
icon_state = "arm_laser"
- contents = newlist(/obj/item/gun/energy/laser/mounted)
+ syndicate_implant = TRUE
+ items_to_create = list(/obj/item/gun/energy/laser/mounted)
/obj/item/organ/cyberimp/arm/gun/laser/l
zone = BODY_ZONE_L_ARM
+/obj/item/organ/cyberimp/arm/gun/laser/Initialize()
+ . = ..()
+ var/obj/item/organ/cyberimp/arm/gun/laser/laserphasergun = locate(/obj/item/gun/energy/laser/mounted) in contents
+ laserphasergun.icon = icon //No invisible laser guns kthx
+ laserphasergun.icon_state = icon_state
/obj/item/organ/cyberimp/arm/gun/taser
name = "arm-mounted taser implant"
desc = "A variant of the arm cannon implant that fires electrodes and disabler shots. The cannon emerges from the subject's arm and remains inside when not in use."
icon_state = "arm_taser"
- contents = newlist(/obj/item/gun/energy/e_gun/advtaser/mounted)
+ items_to_create = list(/obj/item/gun/energy/e_gun/advtaser/mounted)
/obj/item/organ/cyberimp/arm/gun/taser/l
zone = BODY_ZONE_L_ARM
@@ -178,79 +219,116 @@
/obj/item/organ/cyberimp/arm/toolset
name = "integrated toolset implant"
desc = "A stripped-down version of the engineering cyborg toolset, designed to be installed on subject's arm. Contains advanced versions of every tool."
- contents = newlist(/obj/item/screwdriver/cyborg, /obj/item/wrench/cyborg, /obj/item/weldingtool/largetank/cyborg,
+ items_to_create = list(/obj/item/screwdriver/cyborg, /obj/item/wrench/cyborg, /obj/item/weldingtool/largetank/cyborg,
/obj/item/crowbar/cyborg, /obj/item/wirecutters/cyborg, /obj/item/multitool/cyborg)
/obj/item/organ/cyberimp/arm/toolset/l
zone = BODY_ZONE_L_ARM
-/obj/item/organ/cyberimp/arm/toolset/emag_act()
- if(!(locate(/obj/item/melee/hydraulic_blade) in items_list))
- to_chat(usr, "You unlock [src]'s integrated blade!")
- items_list += new /obj/item/melee/hydraulic_blade(src)
- return 1
- return 0
+/obj/item/organ/cyberimp/arm/toolset/emag_act(mob/user)
+ for(var/datum/weakref/created_item in items_list)
+ var/obj/potential_blade = created_item.resolve()
+ if(istype(/obj/item/melee/hydraulic_blade, potential_blade))
+ return FALSE
+
+ to_chat(user, "You unlock [src]'s integrated blade!")
+ items_list += WEAKREF(new /obj/item/melee/hydraulic_blade(src))
+ return TRUE
/obj/item/organ/cyberimp/arm/esword
name = "arm-mounted energy blade"
desc = "An illegal and highly dangerous cybernetic implant that can project a deadly blade of concentrated energy."
- contents = newlist(/obj/item/melee/transforming/energy/blade/hardlight)
+ syndicate_implant = TRUE
+ items_to_create = list(/obj/item/melee/transforming/energy/blade/hardlight)
/obj/item/organ/cyberimp/arm/medibeam
name = "integrated medical beamgun"
desc = "A cybernetic implant that allows the user to project a healing beam from their hand."
- contents = newlist(/obj/item/gun/medbeam)
+ items_to_create = list(/obj/item/gun/medbeam)
/obj/item/organ/cyberimp/arm/flash
name = "integrated high-intensity photon projector" //Why not
desc = "An integrated projector mounted onto a user's arm that is able to be used as a powerful flash."
- contents = newlist(/obj/item/assembly/flash/armimplant)
+ items_to_create = list(/obj/item/assembly/flash/armimplant)
/obj/item/organ/cyberimp/arm/flash/Initialize()
. = ..()
- if(locate(/obj/item/assembly/flash/armimplant) in items_list)
- var/obj/item/assembly/flash/armimplant/F = locate(/obj/item/assembly/flash/armimplant) in items_list
- F.I = src
+ for(var/datum/weakref/created_item in items_list)
+ var/obj/potential_flash = created_item.resolve()
+ if(!istype(potential_flash, /obj/item/assembly/flash/armimplant))
+ continue
+ var/obj/item/assembly/flash/armimplant/flash = potential_flash
+ flash.arm = WEAKREF(src)
+
+/obj/item/organ/cyberimp/arm/flash/Extend()
+ . = ..()
+ active_item.set_light(7)
+
+/obj/item/organ/cyberimp/arm/flash/Retract()
+ active_item?.set_light(0)
+ return ..()
/obj/item/organ/cyberimp/arm/baton
name = "arm electrification implant"
desc = "An illegal combat implant that allows the user to administer disabling shocks from their arm."
- contents = newlist(/obj/item/borg/stun)
+ syndicate_implant = TRUE
+ items_to_create = list(/obj/item/borg/stun)
/obj/item/organ/cyberimp/arm/combat
name = "combat cybernetics implant"
desc = "A powerful cybernetic implant that contains combat modules built into the user's arm."
- contents = newlist(/obj/item/melee/transforming/energy/blade/hardlight, /obj/item/gun/medbeam, /obj/item/borg/stun, /obj/item/assembly/flash/armimplant)
+ syndicate_implant = TRUE
+ items_to_create = list(/obj/item/melee/transforming/energy/blade/hardlight, /obj/item/gun/medbeam, /obj/item/borg/stun, /obj/item/assembly/flash/armimplant)
/obj/item/organ/cyberimp/arm/combat/Initialize()
. = ..()
- if(locate(/obj/item/assembly/flash/armimplant) in items_list)
- var/obj/item/assembly/flash/armimplant/F = locate(/obj/item/assembly/flash/armimplant) in items_list
- F.I = src
+ for(var/datum/weakref/created_item in items_list)
+ var/obj/potential_flash = created_item.resolve()
+ if(!istype(potential_flash, /obj/item/assembly/flash/armimplant))
+ continue
+ var/obj/item/assembly/flash/armimplant/flash = potential_flash
+ flash.arm = WEAKREF(src)
/obj/item/organ/cyberimp/arm/surgery
name = "surgical toolset implant"
desc = "A set of surgical tools hidden behind a concealed panel on the user's arm."
- contents = newlist(/obj/item/retractor/augment, /obj/item/hemostat/augment, /obj/item/cautery/augment, /obj/item/surgicaldrill/augment, /obj/item/scalpel/augment, /obj/item/circular_saw/augment, /obj/item/surgical_drapes)
+ items_to_create = list(/obj/item/retractor/augment, /obj/item/hemostat/augment, /obj/item/cautery/augment, /obj/item/surgicaldrill/augment, /obj/item/scalpel/augment, /obj/item/circular_saw/augment, /obj/item/surgical_drapes)
/obj/item/organ/cyberimp/arm/power_cord
name = "power cord implant"
desc = "An internal power cord hooked up to a battery. Useful if you run on volts."
- contents = newlist(/obj/item/apc_powercord)
+ items_to_create = list(/obj/item/apc_powercord)
zone = "l_arm"
/obj/item/organ/cyberimp/arm/esaw
name = "arm-mounted energy saw"
desc = "An illegal and highly dangerous implanted carbon-fiber blade with a toggleable hard-light edge."
icon_state = "esaw_0"
- contents = newlist(/obj/item/melee/transforming/energy/sword/esaw)
+ syndicate_implant = TRUE
+ items_to_create = list(/obj/item/melee/transforming/energy/sword/esaw)
/obj/item/organ/cyberimp/arm/hydraulic_blade
name = "arm-mounted hydraulic blade"
desc = "Highly dangerous implanted plasteel blade."
icon_state = "hydraulic_blade"
- contents = newlist(/obj/item/melee/hydraulic_blade)
+ items_to_create = list(/obj/item/melee/hydraulic_blade)
/obj/item/organ/cyberimp/arm/hydraulic_blade/l
zone = BODY_ZONE_L_ARM
+
+/obj/item/organ/cyberimp/arm/botany
+ name = "botanical arm implant"
+ desc = "A rather simple arm implant containing tools used in gardening and botanical research."
+ items_to_create = list(/obj/item/cultivator, /obj/item/shovel/spade, /obj/item/hatchet, /obj/item/plant_analyzer, /obj/item/storage/bag/plants/portaseeder/compact)
+
+/obj/item/organ/cyberimp/arm/janitor
+ name = "janitorial tools implant"
+ desc = "A set of janitorial tools on the user's arm."
+ items_to_create = list(/obj/item/lightreplacer/cyborg, /obj/item/holosign_creator/janibarrier, /obj/item/soap/nanotrasen, /obj/item/reagent_containers/spray/cyborg/drying_agent, /obj/item/mop/advanced/cyborg, /obj/item/paint/paint_remover, /obj/item/reagent_containers/spray/cleaner)
+
+/obj/item/organ/cyberimp/arm/janitor/emag_act(mob/user)
+ to_chat(usr, "You unlock [src]'s integrated deluxe cleaning supplies!")
+ items_list += WEAKREF(new /obj/item/soap/syndie(src)) //We add not replace.
+ items_list += WEAKREF(new /obj/item/reagent_containers/spray/cyborg/lube(src))
+ return TRUE
diff --git a/code/modules/surgery/organs/augments_chest.dm b/code/modules/surgery/organs/augments_chest.dm
index 1f05d8f2d7340..47c4432829507 100644
--- a/code/modules/surgery/organs/augments_chest.dm
+++ b/code/modules/surgery/organs/augments_chest.dm
@@ -114,6 +114,8 @@
if(H.stat == CONSCIOUS)
to_chat(H, "You feel your heart beating again!")
+/obj/item/organ/cyberimp/chest/reviver/syndicate
+ syndicate_implant = TRUE
/obj/item/organ/cyberimp/chest/thrusters
name = "implantable thrusters set"
@@ -174,6 +176,8 @@
A.UpdateButtonIcon()
/obj/item/organ/cyberimp/chest/thrusters/proc/move_react()
+ SIGNAL_HANDLER
+
allow_thrust(0.01)
/obj/item/organ/cyberimp/chest/thrusters/proc/allow_thrust(num)
@@ -183,7 +187,8 @@
var/turf/T = get_turf(owner)
if(!T) // No more runtimes from being stuck in nullspace.
return 0
-
+ if(owner.is_flying() && owner.has_gravity())
+ return 0
// Priority 1: use air from environment.
var/datum/gas_mixture/environment = T.return_air()
if(environment && environment.return_pressure() > 30)
@@ -197,13 +202,8 @@
// Priority 3: use internals tank.
var/obj/item/tank/I = owner.internal
- if(I?.air_contents && I.air_contents.total_moles() > num)
- var/datum/gas_mixture/removed = I.air_contents.remove(num)
- if(removed.total_moles() > 0.005)
- T.assume_air(removed)
- return 1
- else
- T.assume_air(removed)
+ if(I && I.air_contents && I.air_contents.total_moles() >= num)
+ T.assume_air_moles(I.air_contents, num)
toggle(silent = TRUE)
return 0
diff --git a/code/modules/surgery/organs/augments_eyes.dm b/code/modules/surgery/organs/augments_eyes.dm
index 1289ddc587e1a..6397ce9a5211c 100644
--- a/code/modules/surgery/organs/augments_eyes.dm
+++ b/code/modules/surgery/organs/augments_eyes.dm
@@ -12,29 +12,36 @@
name = "HUD implant"
desc = "These cybernetic eyes will display a HUD over everything you see. Maybe."
slot = ORGAN_SLOT_HUD
- var/HUD_type = 0
+ var/HUD_type
+ var/HUD_trait
/obj/item/organ/cyberimp/eyes/hud/Insert(var/mob/living/carbon/M, var/special = 0, drop_if_replaced = FALSE)
..()
if(HUD_type)
var/datum/atom_hud/H = GLOB.huds[HUD_type]
H.add_hud_to(M)
+ if(HUD_trait)
+ ADD_TRAIT(M, HUD_trait, ORGAN_TRAIT)
/obj/item/organ/cyberimp/eyes/hud/Remove(var/mob/living/carbon/M, var/special = 0)
if(HUD_type)
var/datum/atom_hud/H = GLOB.huds[HUD_type]
H.remove_hud_from(M)
+ if(HUD_trait)
+ REMOVE_TRAIT(M, HUD_trait, ORGAN_TRAIT)
..()
/obj/item/organ/cyberimp/eyes/hud/medical
name = "Medical HUD implant"
desc = "These cybernetic eye implants will display a medical HUD over everything you see."
HUD_type = DATA_HUD_MEDICAL_ADVANCED
+ HUD_trait = TRAIT_MEDICAL_HUD
/obj/item/organ/cyberimp/eyes/hud/security
name = "Security HUD implant"
desc = "These cybernetic eye implants will display a security HUD over everything you see."
HUD_type = DATA_HUD_SECURITY_ADVANCED
+ HUD_trait = TRAIT_SECURITY_HUD
/obj/item/organ/cyberimp/eyes/hud/diagnostic
name = "Diagnostic HUD implant"
diff --git a/code/modules/surgery/organs/augments_internal.dm b/code/modules/surgery/organs/augments_internal.dm
index 15a51392606f2..bfeea59868e47 100644
--- a/code/modules/surgery/organs/augments_internal.dm
+++ b/code/modules/surgery/organs/augments_internal.dm
@@ -118,6 +118,8 @@
RegisterSignal(owner, signalCache, .proc/on_signal)
/obj/item/organ/cyberimp/brain/anti_stun/proc/on_signal(datum/source, amount)
+ SIGNAL_HANDLER
+
if(!(organ_flags & ORGAN_FAILING) && amount > 0)
addtimer(CALLBACK(src, .proc/clear_stuns), stun_cap_amount, TIMER_UNIQUE|TIMER_OVERRIDE)
@@ -138,6 +140,9 @@
/obj/item/organ/cyberimp/brain/anti_stun/proc/reboot()
organ_flags &= ~ORGAN_FAILING
+/obj/item/organ/cyberimp/brain/anti_stun/syndicate
+ syndicate_implant = TRUE
+
//[[[[MOUTH]]]]
/obj/item/organ/cyberimp/mouth
zone = BODY_ZONE_PRECISE_MOUTH
diff --git a/code/modules/surgery/organs/autosurgeon.dm b/code/modules/surgery/organs/autosurgeon.dm
index b2bcef1657a45..4e3f9e0eb54a8 100644
--- a/code/modules/surgery/organs/autosurgeon.dm
+++ b/code/modules/surgery/organs/autosurgeon.dm
@@ -95,13 +95,13 @@
starting_organ = /obj/item/organ/eyes/robotic/thermals
/obj/item/autosurgeon/syndicate/xray_eyes
- starting_organ = /obj/item/organ/eyes/robotic/xray
+ starting_organ = /obj/item/organ/eyes/robotic/xray/syndicate
/obj/item/autosurgeon/syndicate/anti_stun
- starting_organ = /obj/item/organ/cyberimp/brain/anti_stun
+ starting_organ = /obj/item/organ/cyberimp/brain/anti_stun/syndicate
/obj/item/autosurgeon/syndicate/reviver
- starting_organ = /obj/item/organ/cyberimp/chest/reviver
+ starting_organ = /obj/item/organ/cyberimp/chest/reviver/syndicate
/obj/item/autosurgeon/syndicate/esaw_arm
desc = "A single use autosurgeon that contains an energy saw arm implant."
diff --git a/code/modules/surgery/organs/ears.dm b/code/modules/surgery/organs/ears.dm
index 08a094f2c143f..b34e47b41917b 100644
--- a/code/modules/surgery/organs/ears.dm
+++ b/code/modules/surgery/organs/ears.dm
@@ -150,10 +150,11 @@
owner.Jitter(30)
owner.Dizzy(30)
owner.Knockdown(200)
- deaf = 30
- to_chat(owner, "Your robotic ears are ringing, uselessly.")
+ to_chat(owner, "Alert: Audio sensors malfunctioning")
+ owner.apply_status_effect(STATUS_EFFECT_IPC_EMP)
if(2)
owner.Jitter(15)
owner.Dizzy(15)
owner.Knockdown(100)
- to_chat(owner, "Your robotic ears buzz.")
+ to_chat(owner, "Alert: Audio sensors malfunctioning")
+ owner.apply_status_effect(STATUS_EFFECT_IPC_EMP)
diff --git a/code/modules/surgery/organs/eyes.dm b/code/modules/surgery/organs/eyes.dm
index d92607db0ab47..fb6e65beacecf 100644
--- a/code/modules/surgery/organs/eyes.dm
+++ b/code/modules/surgery/organs/eyes.dm
@@ -71,9 +71,9 @@
if((organ_flags & ORGAN_FAILING))
C.become_blind(EYE_DAMAGE)
else if(damage > 30)
- C.overlay_fullscreen("eye_damage", /obj/screen/fullscreen/impaired, 2)
+ C.overlay_fullscreen("eye_damage", /atom/movable/screen/fullscreen/impaired, 2)
else
- C.overlay_fullscreen("eye_damage", /obj/screen/fullscreen/impaired, 1)
+ C.overlay_fullscreen("eye_damage", /atom/movable/screen/fullscreen/impaired, 1)
//called once since we don't want to keep clearing the screen of eye damage for people who are below 20 damage
else if(damaged)
damaged = FALSE
@@ -145,6 +145,12 @@
eye_color = "000"
see_in_dark = 8
sight_flags = SEE_MOBS | SEE_OBJS | SEE_TURFS
+ flash_protect = -INFINITY
+ tint = -INFINITY
+
+/obj/item/organ/eyes/robotic/xray/syndicate
+ desc = "These cybernetic eyes will give you X-ray vision. Blinking is futile. On closer look, they have been modified to protect from sudden bright flashes."
+ flash_protect = 0
/obj/item/organ/eyes/robotic/thermals
name = "thermal eyes"
@@ -279,26 +285,20 @@
return
deactivate(silent = TRUE)
-/obj/item/organ/eyes/robotic/glow/Insert(mob/living/carbon/M, special = FALSE, drop_if_replaced = FALSE)
- . = ..()
- RegisterSignal(M, COMSIG_ATOM_DIR_CHANGE, .proc/update_visuals)
-
-/obj/item/organ/eyes/robotic/glow/Remove(mob/living/carbon/M, special = FALSE)
- . = ..()
- UnregisterSignal(M, COMSIG_ATOM_DIR_CHANGE)
-
/obj/item/organ/eyes/robotic/glow/Destroy()
QDEL_NULL(mobhook) // mobhook is not our component
return ..()
/obj/item/organ/eyes/robotic/glow/proc/activate(silent = FALSE)
start_visuals()
+ RegisterSignal(owner, COMSIG_ATOM_DIR_CHANGE, .proc/update_visuals)
if(!silent)
to_chat(owner, "Your [src] clicks and makes a whining noise, before shooting out a beam of light!")
active = TRUE
cycle_mob_overlay()
/obj/item/organ/eyes/robotic/glow/proc/deactivate(silent = FALSE)
+ UnregisterSignal(owner, COMSIG_ATOM_DIR_CHANGE)
clear_visuals()
if(!silent)
to_chat(owner, "Your [src] shuts off!")
@@ -306,6 +306,8 @@
remove_mob_overlay()
/obj/item/organ/eyes/robotic/glow/proc/update_visuals(datum/source, olddir, newdir)
+ SIGNAL_HANDLER
+
if((LAZYLEN(eye_lighting) < light_beam_distance) || !on_mob)
regenerate_light_effects()
var/turf/scanfrom = get_turf(owner)
@@ -354,7 +356,7 @@
clear_visuals(TRUE)
on_mob = new(src)
for(var/i in 1 to light_beam_distance)
- LAZYADD(eye_lighting,new /obj/effect/abstract/eye_lighting(src))
+ LAZYADD(eye_lighting, new /obj/effect/abstract/eye_lighting(src))
sync_light_effects()
/obj/item/organ/eyes/robotic/glow/proc/sync_light_effects()
@@ -370,6 +372,7 @@
/obj/effect/abstract/eye_lighting/Initialize()
. = ..()
parent = loc
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
if(!istype(parent))
return INITIALIZE_HINT_QDEL
diff --git a/code/modules/surgery/organs/heart.dm b/code/modules/surgery/organs/heart.dm
index 25a93d03ac16c..eab73ae52b4ae 100644
--- a/code/modules/surgery/organs/heart.dm
+++ b/code/modules/surgery/organs/heart.dm
@@ -70,15 +70,15 @@
if(H.health <= H.crit_threshold && beat != BEAT_SLOW)
beat = BEAT_SLOW
- H.playsound_local(get_turf(H), slowbeat,40,0, channel = CHANNEL_HEARTBEAT)
- to_chat(owner, "You feel your heart slow down...")
+ H.playsound_local(get_turf(H), slowbeat,40,0, channel = CHANNEL_HEARTBEAT, use_reverb = FALSE)
+ to_chat(owner, "You feel your heart slow down.")
if(beat == BEAT_SLOW && H.health > H.crit_threshold)
H.stop_sound_channel(CHANNEL_HEARTBEAT)
beat = BEAT_NONE
if(H.jitteriness)
if(H.health > HEALTH_THRESHOLD_FULLCRIT && (!beat || beat == BEAT_SLOW))
- H.playsound_local(get_turf(H),fastbeat,40,0, channel = CHANNEL_HEARTBEAT)
+ H.playsound_local(get_turf(H),fastbeat,40,0, channel = CHANNEL_HEARTBEAT, use_reverb = FALSE)
beat = BEAT_FAST
else if(beat == BEAT_FAST)
H.stop_sound_channel(CHANNEL_HEARTBEAT)
diff --git a/code/modules/surgery/organs/lungs.dm b/code/modules/surgery/organs/lungs.dm
index ae1300dd0d795..191c6e5e23e22 100644
--- a/code/modules/surgery/organs/lungs.dm
+++ b/code/modules/surgery/organs/lungs.dm
@@ -17,32 +17,35 @@
//Breath damage
- var/safe_oxygen_min = 16 // Minimum safe partial pressure of O2, in kPa
- var/safe_oxygen_max = 0
- var/safe_nitro_min = 0
- var/safe_nitro_max = 0
- var/safe_co2_min = 0
- var/safe_co2_max = 10 // Yes it's an arbitrary value who cares?
- var/safe_toxins_min = 0
- var/safe_toxins_max = 0.05
- var/SA_para_min = 1 //Sleeping agent
- var/SA_sleep_min = 5 //Sleeping agent
+ var/breathing_class = BREATH_OXY // can be a gas instead of a breathing class
+ var/safe_breath_min = 16
+ var/safe_breath_max = 50
+ var/safe_breath_dam_min = MIN_TOXIC_GAS_DAMAGE
+ var/safe_breath_dam_max = MAX_TOXIC_GAS_DAMAGE
+ var/safe_damage_type = OXY
+ var/list/gas_min = list()
+ var/list/gas_max = list(
+ GAS_CO2 = 30, // Yes it's an arbitrary value who cares?
+ GAS_PLASMA = MOLES_GAS_VISIBLE
+ )
+ var/list/gas_damage = list(
+ "default" = list(
+ min = MIN_TOXIC_GAS_DAMAGE,
+ max = MAX_TOXIC_GAS_DAMAGE,
+ damage_type = OXY
+ ),
+ GAS_PLASMA = list(
+ min = MIN_TOXIC_GAS_DAMAGE,
+ max = MAX_TOXIC_GAS_DAMAGE,
+ damage_type = TOX
+ )
+ )
+
+ var/SA_para_min = 1 //nitrous values
+ var/SA_sleep_min = 5
var/BZ_trip_balls_min = 1 //BZ gas
var/gas_stimulation_min = 0.002 //Nitryl and Stimulum
- var/oxy_breath_dam_min = MIN_TOXIC_GAS_DAMAGE
- var/oxy_breath_dam_max = MAX_TOXIC_GAS_DAMAGE
- var/oxy_damage_type = OXY
- var/nitro_breath_dam_min = MIN_TOXIC_GAS_DAMAGE
- var/nitro_breath_dam_max = MAX_TOXIC_GAS_DAMAGE
- var/nitro_damage_type = OXY
- var/co2_breath_dam_min = MIN_TOXIC_GAS_DAMAGE
- var/co2_breath_dam_max = MAX_TOXIC_GAS_DAMAGE
- var/co2_damage_type = OXY
- var/tox_breath_dam_min = MIN_TOXIC_GAS_DAMAGE
- var/tox_breath_dam_max = MAX_TOXIC_GAS_DAMAGE
- var/tox_damage_type = TOX
-
var/cold_message = "your face freezing and an icicle forming"
var/cold_level_1_threshold = 260
var/cold_level_2_threshold = 200
@@ -63,8 +66,22 @@
var/crit_stabilizing_reagent = /datum/reagent/medicine/epinephrine
+/obj/item/organ/lungs/New()
+ . = ..()
+ populate_gas_info()
+
+/obj/item/organ/lungs/proc/populate_gas_info()
+ gas_min[breathing_class] = safe_breath_min
+ gas_max[breathing_class] = safe_breath_max
+ gas_damage[breathing_class] = list(
+ min = safe_breath_dam_min,
+ max = safe_breath_dam_max,
+ damage_type = safe_damage_type
+ )
/obj/item/organ/lungs/proc/check_breath(datum/gas_mixture/breath, mob/living/carbon/human/H)
+//TODO: add lung damage = less oxygen gains
+ var/breathModifier = (5-(5*(damage/maxHealth)/2)) //range 2.5 - 5
if(H.status_flags & GODMODE)
return
if(HAS_TRAIT(H, TRAIT_NOBREATH))
@@ -79,147 +96,121 @@
H.adjustOxyLoss(HUMAN_CRIT_MAX_OXYLOSS)
H.failed_last_breath = TRUE
- if(safe_oxygen_min)
- H.throw_alert("not_enough_oxy", /obj/screen/alert/not_enough_oxy)
- else if(safe_toxins_min)
- H.throw_alert("not_enough_tox", /obj/screen/alert/not_enough_tox)
- else if(safe_co2_min)
- H.throw_alert("not_enough_co2", /obj/screen/alert/not_enough_co2)
- else if(safe_nitro_min)
- H.throw_alert("not_enough_nitro", /obj/screen/alert/not_enough_nitro)
+ var/alert_category
+ var/alert_type
+ if(ispath(breathing_class))
+ var/datum/breathing_class/class = GLOB.gas_data.breathing_classes[breathing_class]
+ alert_category = class.low_alert_category
+ alert_type = class.low_alert_datum
+ else
+ var/list/breath_alert_info = GLOB.gas_data.breath_alert_info
+ if(breathing_class in breath_alert_info)
+ var/list/alert = breath_alert_info[breathing_class]["not_enough_alert"]
+ alert_category = alert["alert_category"]
+ alert_type = alert["alert_type"]
+ if(alert_category)
+ H.throw_alert(alert_category, alert_type)
return FALSE
- var/gas_breathed = 0
+ #define PP_MOLES(X) ((X / total_moles) * pressure)
- //Partial pressures in our breath
- var/O2_pp = breath.get_breath_partial_pressure(breath.get_moles(/datum/gas/oxygen))+(8*breath.get_breath_partial_pressure(breath.get_moles(/datum/gas/pluoxium)))
- var/N2_pp = breath.get_breath_partial_pressure(breath.get_moles(/datum/gas/nitrogen))
- var/Toxins_pp = breath.get_breath_partial_pressure(breath.get_moles(/datum/gas/plasma))
- var/CO2_pp = breath.get_breath_partial_pressure(breath.get_moles(/datum/gas/carbon_dioxide))
+ #define PP(air, gas) PP_MOLES(air.get_moles(gas))
+ var/gas_breathed = 0
- //-- OXY --//
-
- //Too much oxygen! //Yes, some species may not like it.
- if(safe_oxygen_max)
- if(O2_pp > safe_oxygen_max)
- var/ratio = (breath.get_moles(/datum/gas/oxygen)/safe_oxygen_max) * 10
- H.apply_damage_type(CLAMP(ratio, oxy_breath_dam_min, oxy_breath_dam_max), oxy_damage_type)
- H.throw_alert("too_much_oxy", /obj/screen/alert/too_much_oxy)
- else
- H.clear_alert("too_much_oxy")
-
- //Too little oxygen!
- if(safe_oxygen_min)
- if(O2_pp < safe_oxygen_min)
- gas_breathed = handle_too_little_breath(H, O2_pp, safe_oxygen_min, breath.get_moles(/datum/gas/oxygen))
- H.throw_alert("not_enough_oxy", /obj/screen/alert/not_enough_oxy)
+ var/pressure = breath.return_pressure()
+ var/total_moles = breath.total_moles()
+ var/list/breath_alert_info = GLOB.gas_data.breath_alert_info
+ var/list/breath_results = GLOB.gas_data.breath_results
+ var/list/breathing_classes = GLOB.gas_data.breathing_classes
+ var/list/mole_adjustments = list()
+ for(var/entry in gas_min)
+ var/required_pp = 0
+ var/required_moles = 0
+ var/safe_min = gas_min[entry]
+ var/alert_category = null
+ var/alert_type = null
+ if(ispath(entry))
+ var/datum/breathing_class/class = breathing_classes[entry]
+ var/list/gases = class.gases
+ var/list/products = class.products
+ alert_category = class.low_alert_category
+ alert_type = class.low_alert_datum
+ for(var/gas in gases)
+ var/moles = breath.get_moles(gas)
+ var/multiplier = gases[gas]
+ mole_adjustments[gas] = (gas in mole_adjustments) ? mole_adjustments[gas] - moles : -moles
+ required_pp += PP_MOLES(moles) * multiplier
+ required_moles += moles
+ if(multiplier > 0)
+ var/to_add = moles * multiplier
+ for(var/product in products)
+ mole_adjustments[product] = (product in mole_adjustments) ? mole_adjustments[product] + to_add : to_add
else
- H.failed_last_breath = FALSE
- if(H.health >= H.crit_threshold)
- H.adjustOxyLoss(-5)
- gas_breathed = breath.get_moles(/datum/gas/oxygen)
- H.clear_alert("not_enough_oxy")
-
- //Exhale
- breath.adjust_moles(/datum/gas/oxygen, -gas_breathed)
- breath.adjust_moles(/datum/gas/carbon_dioxide, gas_breathed)
- gas_breathed = 0
-
- //-- Nitrogen --//
-
- //Too much nitrogen!
- if(safe_nitro_max)
- if(N2_pp > safe_nitro_max)
- var/ratio = (breath.get_moles(/datum/gas/nitrogen)/safe_nitro_max) * 10
- H.apply_damage_type(CLAMP(ratio, nitro_breath_dam_min, nitro_breath_dam_max), nitro_damage_type)
- H.throw_alert("too_much_nitro", /obj/screen/alert/too_much_nitro)
- else
- H.clear_alert("too_much_nitro")
-
- //Too little nitrogen!
- if(safe_nitro_min)
- if(N2_pp < safe_nitro_min)
- gas_breathed = handle_too_little_breath(H, N2_pp, safe_nitro_min, breath.get_moles(/datum/gas/nitrogen))
- H.throw_alert("nitro", /obj/screen/alert/not_enough_nitro)
+ required_moles = breath.get_moles(entry)
+ required_pp = PP_MOLES(required_moles)
+ if(entry in breath_alert_info)
+ var/list/alert = breath_alert_info[entry]["not_enough_alert"]
+ alert_category = alert["alert_category"]
+ alert_type = alert["alert_type"]
+ mole_adjustments[entry] = -required_moles
+ mole_adjustments[breath_results[entry]] = required_moles
+ if(required_pp < safe_min)
+ var/multiplier = 0
+ if(required_moles > 0)
+ multiplier = handle_too_little_breath(H, required_pp, safe_min, required_moles) / required_moles
+ for(var/adjustment in mole_adjustments)
+ mole_adjustments[adjustment] *= multiplier
+ if(alert_category)
+ H.throw_alert(alert_category, alert_type)
+ H.throw_alert(alert_category, alert_type)
else
H.failed_last_breath = FALSE
if(H.health >= H.crit_threshold)
- H.adjustOxyLoss(-5)
- gas_breathed = breath.get_moles(/datum/gas/nitrogen)
- H.clear_alert("nitro")
-
- //Exhale
- breath.adjust_moles(/datum/gas/nitrogen, -gas_breathed)
- breath.adjust_moles(/datum/gas/carbon_dioxide, gas_breathed)
- gas_breathed = 0
-
- //-- CO2 --//
-
- //CO2 does not affect failed_last_breath. So if there was enough oxygen in the air but too much co2, this will hurt you, but only once per 4 ticks, instead of once per tick.
- if(safe_co2_max)
- if(CO2_pp > safe_co2_max)
- if(!H.co2overloadtime) // If it's the first breath with too much CO2 in it, lets start a counter, then have them pass out after 12s or so.
- H.co2overloadtime = world.time
- else if(world.time - H.co2overloadtime > 120)
- H.Unconscious(60)
- H.apply_damage_type(3, co2_damage_type) // Lets hurt em a little, let them know we mean business
- if(world.time - H.co2overloadtime > 300) // They've been in here 30s now, lets start to kill them for their own good!
- H.apply_damage_type(8, co2_damage_type)
- H.throw_alert("too_much_co2", /obj/screen/alert/too_much_co2)
- if(prob(20)) // Lets give them some chance to know somethings not right though I guess.
- H.emote("cough")
-
+ H.adjustOxyLoss(-breathModifier)
+ if(alert_category)
+ H.clear_alert(alert_category)
+ var/list/danger_reagents = GLOB.gas_data.breath_reagents_dangerous
+ for(var/entry in gas_max)
+ var/found_pp = 0
+ var/datum/breathing_class/breathing_class = entry
+ var/datum/reagent/danger_reagent = null
+ var/alert_category = null
+ var/alert_type = null
+ if(ispath(breathing_class))
+ breathing_class = breathing_classes[breathing_class]
+ var/list/gases = breathing_class.gases
+ alert_category = breathing_class.high_alert_category
+ alert_type = breathing_class.high_alert_datum
+ danger_reagent = breathing_class.danger_reagent
+ for(var/gas in gases)
+ found_pp += PP(breath, gas)
else
- H.co2overloadtime = 0
- H.clear_alert("too_much_co2")
-
- //Too little CO2!
- if(safe_co2_min)
- if(CO2_pp < safe_co2_min)
- gas_breathed = handle_too_little_breath(H, CO2_pp, safe_co2_min, breath.get_moles(/datum/gas/carbon_dioxide))
- H.throw_alert("not_enough_co2", /obj/screen/alert/not_enough_co2)
- else
- H.failed_last_breath = FALSE
- if(H.health >= H.crit_threshold)
- H.adjustOxyLoss(-5)
- gas_breathed = breath.get_moles(/datum/gas/carbon_dioxide)
- H.clear_alert("not_enough_co2")
-
- //Exhale
- breath.adjust_moles(/datum/gas/carbon_dioxide, -gas_breathed)
- breath.adjust_moles(/datum/gas/oxygen, gas_breathed)
- gas_breathed = 0
-
-
- //-- TOX --//
-
- //Too much toxins!
- if(safe_toxins_max)
- if(Toxins_pp > safe_toxins_max)
- var/ratio = (breath.get_moles(/datum/gas/plasma)/safe_toxins_max) * 10
- H.apply_damage_type(CLAMP(ratio, tox_breath_dam_min, tox_breath_dam_max), tox_damage_type)
- H.throw_alert("too_much_tox", /obj/screen/alert/too_much_tox)
- else
- H.clear_alert("too_much_tox")
-
-
- //Too little toxins!
- if(safe_toxins_min)
- if(Toxins_pp < safe_toxins_min)
- gas_breathed = handle_too_little_breath(H, Toxins_pp, safe_toxins_min, breath.get_moles(/datum/gas/plasma))
- H.throw_alert("not_enough_tox", /obj/screen/alert/not_enough_tox)
- else
- H.failed_last_breath = FALSE
- if(H.health >= H.crit_threshold)
- H.adjustOxyLoss(-5)
- gas_breathed = breath.get_moles(/datum/gas/plasma)
- H.clear_alert("not_enough_tox")
-
- //Exhale
- breath.adjust_moles(/datum/gas/plasma, -gas_breathed)
- breath.adjust_moles(/datum/gas/carbon_dioxide, gas_breathed)
- gas_breathed = 0
+ danger_reagent = danger_reagents[entry]
+ if(entry in breath_alert_info)
+ var/list/alert = breath_alert_info[entry]["too_much_alert"]
+ alert_category = alert["alert_category"]
+ alert_type = alert["alert_type"]
+ found_pp = PP(breath, entry)
+ if(found_pp > gas_max[entry])
+ if(istype(danger_reagent))
+ H.reagents.add_reagent(danger_reagent,1)
+ var/list/damage_info = (entry in gas_damage) ? gas_damage[entry] : gas_damage["default"]
+ var/dam = found_pp / gas_max[entry] * 10
+ H.apply_damage_type(clamp(dam, damage_info["min"], damage_info["max"]), damage_info["damage_type"])
+ if(alert_category && alert_type)
+ H.throw_alert(alert_category, alert_type)
+ else if(alert_category)
+ H.clear_alert(alert_category)
+ var/list/breath_reagents = GLOB.gas_data.breath_reagents
+ for(var/gas in breath.get_gases())
+ if(gas in breath_reagents)
+ var/datum/reagent/R = breath_reagents[gas]
+ H.reagents.add_reagent(R, PP(breath,gas))
+ mole_adjustments[gas] = (gas in mole_adjustments) ? mole_adjustments[gas] - breath.get_moles(gas) : -breath.get_moles(gas)
+
+ for(var/gas in mole_adjustments)
+ breath.adjust_moles(gas, mole_adjustments[gas])
//-- TRACES --//
@@ -228,7 +219,7 @@
// N2O
- var/SA_pp = breath.get_breath_partial_pressure(breath.get_moles(/datum/gas/nitrous_oxide))
+ var/SA_pp = PP(breath, GAS_NITROUS)
if(SA_pp > SA_para_min) // Enough to make us stunned for a bit
H.Unconscious(60) // 60 gives them one second to wake up and run away a bit!
if(SA_pp > SA_sleep_min) // Enough to make us sleep as well
@@ -243,7 +234,7 @@
// BZ
- var/bz_pp = breath.get_breath_partial_pressure(breath.get_moles(/datum/gas/bz))
+ var/bz_pp = PP(breath, GAS_BZ)
if(bz_pp > BZ_trip_balls_min)
H.hallucination += 10
H.reagents.add_reagent(/datum/reagent/bz_metabolites,5)
@@ -254,16 +245,8 @@
H.hallucination += 5
H.reagents.add_reagent(/datum/reagent/bz_metabolites,1)
-
- // Tritium
- var/trit_pp = breath.get_breath_partial_pressure(breath.get_moles(/datum/gas/tritium))
- if (trit_pp > 50)
- H.radiation += trit_pp/2 //If you're breathing in half an atmosphere of radioactive gas, you fucked up.
- else
- H.radiation += trit_pp/10
-
// Nitryl
- var/nitryl_pp = breath.get_breath_partial_pressure(breath.get_moles(/datum/gas/nitryl))
+ var/nitryl_pp = PP(breath,GAS_NITRYL)
if (prob(nitryl_pp))
to_chat(H, "Your mouth feels like it's burning!")
if (nitryl_pp >40)
@@ -274,26 +257,26 @@
H.silent = max(H.silent, 3)
else
H.adjustFireLoss(nitryl_pp/4)
- gas_breathed = breath.get_moles(/datum/gas/nitryl)
+ gas_breathed = breath.get_moles(GAS_NITRYL)
if (gas_breathed > gas_stimulation_min)
H.reagents.add_reagent(/datum/reagent/nitryl,1)
- breath.adjust_moles(/datum/gas/nitryl, -gas_breathed)
+ breath.adjust_moles(GAS_NITRYL, -gas_breathed)
// Stimulum
- gas_breathed = breath.get_moles(/datum/gas/stimulum)
+ gas_breathed = PP(breath,GAS_STIMULUM)
if (gas_breathed > gas_stimulation_min)
var/existing = H.reagents.get_reagent_amount(/datum/reagent/stimulum)
H.reagents.add_reagent(/datum/reagent/stimulum,max(0, 1 - existing))
- breath.adjust_moles(/datum/gas/stimulum, -gas_breathed)
+ breath.adjust_moles(GAS_STIMULUM, -gas_breathed)
// Miasma
- if (breath.get_moles(/datum/gas/miasma))
- var/miasma_pp = breath.get_breath_partial_pressure(breath.get_moles(/datum/gas/miasma))
+ if (breath.get_moles(GAS_MIASMA))
+ var/miasma_pp = PP(breath,GAS_MIASMA)
//Miasma sickness
if(prob(0.5 * miasma_pp))
- var/datum/disease/advance/miasma_disease = new /datum/disease/advance/random(2,3)
+ var/datum/disease/advance/miasma_disease = new /datum/disease/advance/random(2,3, infected = src)
miasma_disease.name = "Unknown"
miasma_disease.try_infect(owner)
@@ -328,7 +311,7 @@
// Then again, this is a purely hypothetical scenario and hardly reachable
owner.adjust_disgust(0.1 * miasma_pp)
- breath.adjust_moles(/datum/gas/miasma, -gas_breathed)
+ breath.adjust_moles(GAS_MIASMA, -gas_breathed)
// Clear out moods when no miasma at all
else
@@ -402,20 +385,26 @@
desc = "A spongy rib-shaped mass for filtering plasma from the air."
icon_state = "lungs-plasma"
- safe_oxygen_min = 0 //We don't breath this
- safe_toxins_min = 16 //We breath THIS!
- safe_toxins_max = 0
+ breathing_class = BREATH_PLASMA
+
+/obj/item/organ/lungs/plasmaman/populate_gas_info()
+ ..()
+ gas_max -= GAS_PLASMA
+
+/obj/item/organ/lungs/oozeling
+ name = "oozeling vacuole"
+ desc = "A large organelle designed to store oxygen and filter toxins."
/obj/item/organ/lungs/slime
name = "vacuole"
desc = "A large organelle designed to store oxygen and other important gasses."
- safe_toxins_max = 0 //We breathe this to gain POWER.
-
/obj/item/organ/lungs/slime/check_breath(datum/gas_mixture/breath, mob/living/carbon/human/H)
. = ..()
if (breath)
- var/plasma_pp = breath.get_breath_partial_pressure(breath.get_moles(/datum/gas/plasma))
+ var/total_moles = breath.total_moles()
+ var/pressure = breath.return_pressure()
+ var/plasma_pp = PP(breath, GAS_PLASMA)
owner.blood_volume += (0.2 * plasma_pp) // 10/s when breathing literally nothing but plasma, which will suffocate you.
/obj/item/organ/lungs/cybernetic
@@ -425,7 +414,8 @@
organ_flags = ORGAN_SYNTHETIC
status = ORGAN_ROBOTIC
maxHealth = 1.1 * STANDARD_ORGAN_THRESHOLD
- safe_oxygen_min = 13
+ safe_breath_min = 13
+ safe_breath_max = 100
/obj/item/organ/lungs/cybernetic/emp_act()
. = ..()
@@ -438,17 +428,23 @@
name = "upgraded cybernetic lungs"
desc = "A more advanced version of the stock cybernetic lungs. Features the ability to filter out lower levels of toxins and carbon dioxide."
icon_state = "lungs-c-u"
- safe_toxins_max = 20
- safe_co2_max = 20
+ safe_breath_min = 4
+ safe_breath_max = 250
+ gas_max = list(
+ GAS_PLASMA = 30,
+ GAS_CO2 = 30
+ )
maxHealth = 2 * STANDARD_ORGAN_THRESHOLD
cold_level_1_threshold = 200
cold_level_2_threshold = 140
cold_level_3_threshold = 100
-obj/item/organ/lungs/apid
+/obj/item/organ/lungs/apid
name = "apid lungs"
- desc = "Lungs from an apid, or beeperson. Thanks to the many spiracles an apid has, these lungs are capable of gathering more oxygen from low-pressure enviroments."
+ desc = "Lungs from an apid, or beeperson. Thanks to the many spiracles an apid has, these lungs are capable of gathering more oxygen from low-pressure environments."
icon_state = "lungs"
- safe_oxygen_min = 8
+ safe_breath_min = 8
+#undef PP
+#undef PP_MOLES
diff --git a/code/modules/surgery/organs/organ_internal.dm b/code/modules/surgery/organs/organ_internal.dm
index a75f53e9dfa49..32dfb86ed3e7b 100644
--- a/code/modules/surgery/organs/organ_internal.dm
+++ b/code/modules/surgery/organs/organ_internal.dm
@@ -70,13 +70,13 @@
/obj/item/organ/proc/on_find(mob/living/finder)
return
-/obj/item/organ/process()
- on_death() //Kinda hate doing it like this, but I really don't want to call process directly.
+/obj/item/organ/process(delta_time)
+ on_death(delta_time) //Kinda hate doing it like this, but I really don't want to call process directly.
-/obj/item/organ/proc/on_death() //runs decay when outside of a person
+/obj/item/organ/proc/on_death(delta_time = 2) //runs decay when outside of a person
if(organ_flags & (ORGAN_SYNTHETIC | ORGAN_FROZEN))
return
- applyOrganDamage(maxHealth * decay_factor)
+ applyOrganDamage(maxHealth * decay_factor * 0.5 * delta_time)
/obj/item/organ/proc/on_life() //repair organ damage if the organ is not failing
if(organ_flags & ORGAN_FAILING)
diff --git a/code/modules/surgery/organs/stomach.dm b/code/modules/surgery/organs/stomach.dm
index bbdd0f706ab2c..f80458ce70f3c 100755
--- a/code/modules/surgery/organs/stomach.dm
+++ b/code/modules/surgery/organs/stomach.dm
@@ -68,13 +68,13 @@
H.clear_alert("disgust")
SEND_SIGNAL(H, COMSIG_CLEAR_MOOD_EVENT, "disgust")
if(DISGUST_LEVEL_GROSS to DISGUST_LEVEL_VERYGROSS)
- H.throw_alert("disgust", /obj/screen/alert/gross)
+ H.throw_alert("disgust", /atom/movable/screen/alert/gross)
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "disgust", /datum/mood_event/gross)
if(DISGUST_LEVEL_VERYGROSS to DISGUST_LEVEL_DISGUSTED)
- H.throw_alert("disgust", /obj/screen/alert/verygross)
+ H.throw_alert("disgust", /atom/movable/screen/alert/verygross)
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "disgust", /datum/mood_event/verygross)
if(DISGUST_LEVEL_DISGUSTED to INFINITY)
- H.throw_alert("disgust", /obj/screen/alert/disgusted)
+ H.throw_alert("disgust", /atom/movable/screen/alert/disgusted)
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "disgust", /datum/mood_event/disgusted)
/obj/item/organ/stomach/Remove(mob/living/carbon/M, special = 0)
@@ -94,69 +94,98 @@
icon_state = "stomach-p"
desc = "A strange crystal that is responsible for metabolizing the unseen energy force that feeds plasmamen."
-/obj/item/organ/stomach/cell
+
+/obj/item/organ/stomach/battery
+ name = "implantable battery"
+ icon_state = "implant-power"
+ desc = "A battery that stores charge for species that run on electricity."
+ var/max_charge = 5000 //same as upgraded+ cell
+ var/charge = 5000
+
+/obj/item/organ/stomach/battery/Insert(mob/living/carbon/M, special = 0)
+ ..()
+ RegisterSignal(owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, .proc/charge)
+ update_nutrition()
+
+/obj/item/organ/stomach/battery/Remove(mob/living/carbon/M, special = 0)
+ UnregisterSignal(owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT)
+ if(!HAS_TRAIT(owner, TRAIT_NOHUNGER) && HAS_TRAIT(owner, TRAIT_POWERHUNGRY))
+ owner.nutrition = 0
+ owner.throw_alert("nutrition", /atom/movable/screen/alert/nocell)
+ ..()
+
+/obj/item/organ/stomach/battery/proc/charge(datum/source, amount, repairs)
+ SIGNAL_HANDLER
+ adjust_charge(amount)
+
+/obj/item/organ/stomach/battery/proc/adjust_charge(amount)
+ if(amount > 0)
+ charge = clamp((charge + amount)*(1-(damage/maxHealth)), 0, max_charge)
+ else
+ charge = clamp(charge + amount, 0, max_charge)
+ update_nutrition()
+
+/obj/item/organ/stomach/battery/proc/adjust_charge_scaled(amount)
+ adjust_charge(amount*max_charge/NUTRITION_LEVEL_FULL)
+
+/obj/item/organ/stomach/battery/proc/set_charge(amount)
+ charge = clamp(amount*(1-(damage/maxHealth)), 0, max_charge)
+ update_nutrition()
+
+/obj/item/organ/stomach/battery/proc/set_charge_scaled(amount)
+ set_charge(amount*max_charge/NUTRITION_LEVEL_FULL)
+
+/obj/item/organ/stomach/battery/proc/update_nutrition()
+ if(!HAS_TRAIT(owner, TRAIT_NOHUNGER) && HAS_TRAIT(owner, TRAIT_POWERHUNGRY))
+ owner.nutrition = (charge/max_charge)*NUTRITION_LEVEL_FULL
+
+/obj/item/organ/stomach/battery/emp_act(severity)
+ switch(severity)
+ if(1)
+ adjust_charge(-0.5*max_charge)
+ applyOrganDamage(30)
+ if(2)
+ adjust_charge(-0.25*max_charge)
+ applyOrganDamage(15)
+
+/obj/item/organ/stomach/battery/ipc
name = "micro-cell"
icon_state = "microcell"
w_class = WEIGHT_CLASS_NORMAL
- zone = "chest"
- slot = "stomach"
attack_verb = list("assault and battery'd")
- desc = "A micro-cell, for IPC use only. Do not swallow."
+ desc = "A micro-cell, for IPC use. Do not swallow."
status = ORGAN_ROBOTIC
organ_flags = ORGAN_SYNTHETIC
+ max_charge = 2750 //50 nutrition from 250 charge
+ charge = 2750
-/obj/item/organ/stomach/cell/emp_act(severity)
+/obj/item/organ/stomach/battery/ipc/emp_act(severity)
+ ..()
switch(severity)
if(1)
- owner.nutrition = 50
to_chat(owner, "Alert: Heavy EMP Detected. Rebooting power cell to prevent damage.")
if(2)
- owner.nutrition = 250
to_chat(owner, "Alert: EMP Detected. Cycling battery.")
-
-/obj/item/organ/stomach/cell/Insert(mob/living/carbon/M, special = 0)
- ..()
- RegisterSignal(owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, .proc/charge)
-
-/obj/item/organ/stomach/cell/Remove(mob/living/carbon/M, special = 0)
- UnregisterSignal(owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT)
- ..()
-/obj/item/organ/stomach/cell/proc/charge(datum/source, amount, repairs)
- if(owner.nutrition < NUTRITION_LEVEL_WELL_FED)
- owner.nutrition += (amount / 10) //IPCs can feed themselves from a borg recharging station
- if(owner.nutrition >= NUTRITION_LEVEL_WELL_FED)
- to_chat(owner, "You are already fully charged!")
- return
-
-/obj/item/organ/stomach/ethereal
+/obj/item/organ/stomach/battery/ethereal
name = "biological battery"
icon_state = "stomach-p" //Welp. At least it's more unique in functionaliy.
desc = "A crystal-like organ that stores the electric charge of ethereals."
- var/crystal_charge = ETHEREAL_CHARGE_FULL
-
-/obj/item/organ/stomach/ethereal/on_life()
- ..()
- adjust_charge(-ETHEREAL_CHARGE_FACTOR)
+ max_charge = 2500 //same as upgraded cell
+ charge = 2500
-/obj/item/organ/stomach/ethereal/Insert(mob/living/carbon/M, special = 0)
+/obj/item/organ/stomach/battery/ethereal/Insert(mob/living/carbon/M, special = 0)
..()
- RegisterSignal(owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, .proc/charge)
RegisterSignal(owner, COMSIG_LIVING_ELECTROCUTE_ACT, .proc/on_electrocute)
-/obj/item/organ/stomach/ethereal/Remove(mob/living/carbon/M, special = 0)
- UnregisterSignal(owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT)
+/obj/item/organ/stomach/battery/ethereal/Remove(mob/living/carbon/M, special = 0)
UnregisterSignal(owner, COMSIG_LIVING_ELECTROCUTE_ACT)
..()
-/obj/item/organ/stomach/ethereal/proc/charge(datum/source, amount, repairs)
- adjust_charge(amount / 70)
+/obj/item/organ/stomach/battery/ethereal/proc/on_electrocute(datum/source, shock_damage, shock_source, siemens_coeff = 1, safety = 0, tesla_shock = 0, illusion = 0, stun = TRUE)
+ SIGNAL_HANDLER
-/obj/item/organ/stomach/ethereal/proc/on_electrocute(datum/source, shock_damage, siemens_coeff = 1, flags = NONE)
- if(flags & SHOCK_ILLUSION)
+ if(illusion)
return
- adjust_charge(shock_damage * siemens_coeff * 2)
+ adjust_charge(shock_damage * siemens_coeff * 20)
to_chat(owner, "You absorb some of the shock into your body!")
-
-/obj/item/organ/stomach/ethereal/proc/adjust_charge(amount)
- crystal_charge = clamp(crystal_charge + amount, ETHEREAL_CHARGE_NONE, ETHEREAL_CHARGE_FULL)
diff --git a/code/modules/surgery/organs/tongue.dm b/code/modules/surgery/organs/tongue.dm
index 402e79e21b768..f3f019017fdcc 100644
--- a/code/modules/surgery/organs/tongue.dm
+++ b/code/modules/surgery/organs/tongue.dm
@@ -35,7 +35,7 @@
languages_possible = languages_possible_base
/obj/item/organ/tongue/proc/handle_speech(datum/source, list/speech_args)
-
+ SIGNAL_HANDLER
/obj/item/organ/tongue/Insert(mob/living/carbon/M, special = 0)
..()
if(say_mod && M.dna && M.dna.species)
@@ -108,7 +108,7 @@
if(T.mothership == mothership)
to_chat(H, "[src] is already attuned to the same channel as your own.")
- H.visible_message("[H] holds [src] in their hands, and concentrates for a moment.", "You attempt to modify the attunation of [src].")
+ H.visible_message("[H] holds [src] in their hands, and concentrates for a moment.", "You attempt to modify the attenuation of [src].")
if(do_after(H, delay=15, target=src))
to_chat(H, "You attune [src] to your own channel.")
mothership = T.mothership
@@ -238,9 +238,9 @@
languages_possible = languages_possible_base += typecacheof(/datum/language/machine) + typecacheof(/datum/language/voltaic)
/obj/item/organ/tongue/robot/emp_act(severity)
- owner.apply_effect(EFFECT_STUTTER, 120)
owner.emote("scream")
- to_chat(owner, "Alert: Vocal cords are malfunctioning.")
+ owner.apply_status_effect(STATUS_EFFECT_SPANISH)
+ owner.apply_status_effect(STATUS_EFFECT_IPC_EMP)
/obj/item/organ/tongue/robot/handle_speech(datum/source, list/speech_args)
speech_args[SPEECH_SPANS] |= SPAN_ROBOT
diff --git a/code/modules/surgery/organs/vocal_cords.dm b/code/modules/surgery/organs/vocal_cords.dm
index 0e937a9a7b04d..1a745c0d4c4a2 100644
--- a/code/modules/surgery/organs/vocal_cords.dm
+++ b/code/modules/surgery/organs/vocal_cords.dm
@@ -140,7 +140,7 @@
message = lowertext(message)
var/list/mob/living/listeners = list()
- for(var/mob/living/L in get_hearers_in_view(8, user))
+ for(var/mob/living/L in hearers(8, get_turf(user)))
if(L.can_hear() && !L.anti_magic_check(FALSE, TRUE) && L.stat != DEAD)
if(L == user && !include_speaker)
@@ -149,7 +149,7 @@
var/mob/living/carbon/human/H = L
if(istype(H.ears, /obj/item/clothing/ears/earmuffs))
continue
- if(istype(H.get_item_by_slot(SLOT_HEAD), /obj/item/clothing/head/foilhat))
+ if(istype(H.get_item_by_slot(ITEM_SLOT_HEAD), /obj/item/clothing/head/foilhat))
continue
listeners += L
diff --git a/code/modules/surgery/organs/wings.dm b/code/modules/surgery/organs/wings.dm
index 8cc7ba7f12475..979d3450b80c1 100644
--- a/code/modules/surgery/organs/wings.dm
+++ b/code/modules/surgery/organs/wings.dm
@@ -22,7 +22,7 @@
if(istype(H))
Refresh(H)
-/obj/item/organ/wings/proc/Refresh(mob/living/carbon/human/H)
+/obj/item/organ/wings/proc/Refresh(mob/living/carbon/human/H)
if(!(basewings in H.dna.species.mutant_bodyparts))
H.dna.species.mutant_bodyparts |= basewings
H.dna.features[basewings] = wing_type
@@ -86,12 +86,12 @@
H.throw_at(throw_target, 5, 4)
if(prob(10))
S.toggle_flight(H)
- else
+ else
S.toggle_flight(H)
if(prob(50))
stoplag(5)
S.toggle_flight(H)
- else
+ else
H.Togglewings()
outofcontrol --
stoplag(5)
@@ -99,7 +99,7 @@
/obj/item/organ/wings/cybernetic/ayy
name = "advanced cybernetic wingpack"
desc = "A compact pair of mechanical wings. They are equipped with miniaturized void engines, and can fly in any atmosphere, or lack thereof."
- flight_level = WINGS_MAGIC
+ flight_level = WINGS_MAGIC
/obj/item/organ/wings/moth
name = "pair of moth wings"
@@ -121,10 +121,11 @@
if(flight_level >= WINGS_FLIGHTLESS && H.bodytemperature >= 800 && H.fire_stacks > 0)
flight_level = WINGS_COSMETIC
to_chat(H, "Your precious wings burn to a crisp!")
+ SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "burnt_wings", /datum/mood_event/burnt_wings)
H.dna.features["moth_wings"] = "Burnt Off"
wing_type = "Burnt Off"
H.dna.species.handle_mutant_bodyparts(H)
-
+
/obj/item/organ/wings/angel
name = "pair of feathered wings"
desc = "A pair of feathered wings. They seem robust enough for flight"
@@ -145,9 +146,65 @@
name = "pair of bee wings"
desc = "A pair of bee wings. They seem tiny and undergrown"
icon_state = "beewings"
- flight_level = WINGS_FLIGHTLESS
+ flight_level = WINGS_COSMETIC
+ actions_types = list(/datum/action/item_action/organ_action/use/bee_dash)
wing_type = "Bee"
+/datum/action/item_action/organ_action/use/bee_dash
+ var/jumpdistance = 3
+ var/jumpspeed = 1
+ var/recharging_rate = 100
+ var/recharging_time = 0
+
+/datum/action/item_action/organ_action/use/bee_dash/Trigger()
+ var/mob/living/carbon/L = owner
+
+ if(L.stat != CONSCIOUS || L.buckling || L.restrained()) // Has to be concious and unbuckled
+ return
+ if(recharging_time > world.time)
+ to_chat(L, "The wings aren't ready to dash yet!")
+ return
+ var/datum/gas_mixture/environment = L.loc.return_air()
+ if(environment && !(environment.return_pressure() > 30))
+ to_chat(L, "The atmosphere is too thin for you to dash!")
+ return
+
+ var/turf/target = get_edge_target_turf(L, L.dir) //represents the user's direction
+ var/hoppingtable = FALSE // Triggers the trip
+ var/jumpdistancemoved = jumpdistance // temp jumpdistance
+ var/turf/checkjump = get_turf(L)
+
+ for(var/i in 1 to jumpdistance) //This is how hiero club find the tiles in front of it, tell me/fix it if there's a better way
+ var/turf/T = get_step(checkjump, L.dir)
+ if(T.density || !T.ClickCross(invertDir(L.dir), border_only = 1))
+ break
+ if(locate(/obj/structure/table) in T) // If there's a table, trip
+ hoppingtable = TRUE
+ jumpdistancemoved = i
+ break
+ if(!T.ClickCross(L.dir)) // Check for things other than tables that would block flight at the T turf
+ break
+ checkjump = get_step(checkjump, L.dir)
+
+ var/datum/callback/crashcallback
+ if(hoppingtable)
+ crashcallback = CALLBACK(src, .proc/crash_into_table, get_step(checkjump, L.dir))
+ if(L.throw_at(target, jumpdistancemoved, jumpspeed, spin = FALSE, diagonals_first = TRUE, callback = crashcallback, force = MOVE_FORCE_WEAK))
+ playsound(L, 'sound/creatures/bee.ogg', 50, 1, 1)
+ L.visible_message("[usr] dashes forward into the air!")
+ recharging_time = world.time + recharging_rate
+ else
+ to_chat(L, "Something prevents you from dashing forward!")
+
+/datum/action/item_action/organ_action/use/bee_dash/proc/crash_into_table(turf/tableturf)
+ if(owner.loc == tableturf)
+ var/mob/living/carbon/L = owner
+ L.take_bodypart_damage(10,check_armor = TRUE)
+ L.Paralyze(40)
+ L.visible_message("[L] crashes into a table, falling over!",\
+ "You violently crash into a table!")
+ playsound(src,'sound/weapons/punch1.ogg',50,1)
+
/datum/action/innate/flight
name = "Toggle Flight"
check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_STUN
@@ -163,4 +220,4 @@
to_chat(H, "You settle gently back onto the ground...")
else
to_chat(H, "You beat your wings and begin to hover gently above the ground...")
- H.set_resting(FALSE, TRUE)
\ No newline at end of file
+ H.set_resting(FALSE, TRUE)
diff --git a/code/modules/surgery/prosthetic_replacement.dm b/code/modules/surgery/prosthetic_replacement.dm
index 312ed207ee845..145b3d67d6319 100644
--- a/code/modules/surgery/prosthetic_replacement.dm
+++ b/code/modules/surgery/prosthetic_replacement.dm
@@ -11,14 +11,14 @@
if(!iscarbon(target))
return 0
var/mob/living/carbon/C = target
- if(!C.get_bodypart(user.zone_selected)) //can only start if limb is missing
- return 1
-
+ if(!isoozeling(target))
+ if(!C.get_bodypart(user.zone_selected)) //can only start if limb is missing
+ return 1
/datum/surgery_step/add_prosthetic
name = "add prosthetic"
- implements = list(/obj/item/bodypart = 100, /obj/item/organ_storage = 100, /obj/item/twohanded/required/chainsaw = 100, /obj/item/melee/synthetic_arm_blade = 100)
+ implements = list(/obj/item/bodypart = 100, /obj/item/organ_storage = 100, /obj/item/chainsaw = 100, /obj/item/melee/synthetic_arm_blade = 100)
time = 32
var/organ_rejection_dam = 0
@@ -87,15 +87,15 @@
"[user] finishes attaching [tool]!",
"[user] finishes the attachment procedure!")
qdel(tool)
- if(istype(tool, /obj/item/twohanded/required/chainsaw/energy/doom))
+ if(istype(tool, /obj/item/chainsaw/energy/doom))
var/obj/item/mounted_chainsaw/super/new_arm = new(target)
target_zone == BODY_ZONE_R_ARM ? target.put_in_r_hand(new_arm) : target.put_in_l_hand(new_arm)
return 1
- else if(istype(tool, /obj/item/twohanded/required/chainsaw/energy))
+ else if(istype(tool, /obj/item/chainsaw/energy))
var/obj/item/mounted_chainsaw/energy/new_arm = new(target)
target_zone == BODY_ZONE_R_ARM ? target.put_in_r_hand(new_arm) : target.put_in_l_hand(new_arm)
return 1
- else if(istype(tool, /obj/item/twohanded/required/chainsaw))
+ else if(istype(tool, /obj/item/chainsaw))
var/obj/item/mounted_chainsaw/normal/new_arm = new(target)
target_zone == BODY_ZONE_R_ARM ? target.put_in_r_hand(new_arm) : target.put_in_l_hand(new_arm)
return 1
@@ -103,4 +103,3 @@
var/obj/item/melee/arm_blade/new_arm = new(target,TRUE,TRUE)
target_zone == BODY_ZONE_R_ARM ? target.put_in_r_hand(new_arm) : target.put_in_l_hand(new_arm)
return 1
-
diff --git a/code/modules/surgery/remove_embedded_object.dm b/code/modules/surgery/remove_embedded_object.dm
index 4ac83e7b17004..29c3ef30d208a 100644
--- a/code/modules/surgery/remove_embedded_object.dm
+++ b/code/modules/surgery/remove_embedded_object.dm
@@ -30,11 +30,7 @@
var/objects = 0
for(var/obj/item/I in L.embedded_objects)
objects++
- I.forceMove(get_turf(H))
- L.embedded_objects -= I
- if(!H.has_embedded_objects())
- H.clear_alert("embeddedobject")
- SEND_SIGNAL(H, COMSIG_CLEAR_MOOD_EVENT, "embedded")
+ H.remove_embedded_object(I)
if(objects > 0)
display_results(user, target, "You successfully remove [objects] objects from [H]'s [L.name].",
diff --git a/code/modules/surgery/surgery.dm b/code/modules/surgery/surgery.dm
index 91b4266154fed..7b1b72ccaba27 100644
--- a/code/modules/surgery/surgery.dm
+++ b/code/modules/surgery/surgery.dm
@@ -2,23 +2,23 @@
var/name = "surgery"
var/desc = "surgery description"
var/status = 1
- var/list/steps = list() //Steps in a surgery
- var/step_in_progress = FALSE //Actively performing a Surgery
- var/can_cancel = TRUE //Can cancel this surgery after step 1 with cautery
+ var/list/steps = list() //Steps in a surgery
+ var/step_in_progress = FALSE //Actively performing a Surgery
+ var/can_cancel = TRUE //Can cancel this surgery after step 1 with cautery
var/list/target_mobtypes = list(/mob/living/carbon/human) //Acceptable Species
- var/location = BODY_ZONE_CHEST //Surgery location
- var/requires_bodypart_type = BODYPART_ORGANIC //Prevents you from performing an operation on incorrect limbs. 0 for any limb type
- var/list/possible_locs = list() //Multiple locations
- var/ignore_clothes = FALSE //This surgery ignores clothes
- var/mob/living/carbon/target //Operation target mob
- var/obj/item/bodypart/operated_bodypart //Operable body part
- var/requires_bodypart = TRUE //Surgery available only when a bodypart is present, or only when it is missing.
- var/success_multiplier = 0 //Step success propability multiplier
- var/requires_real_bodypart = FALSE //Some surgeries don't work on limbs that don't really exist
- var/lying_required = TRUE //Does the vicitm needs to be lying down.
- var/self_operable = FALSE //Can the surgery be performed on yourself.
- var/requires_tech = FALSE //handles techweb-oriented surgeries, previously restricted to the /advanced subtype (You still need to add designs)
- var/replaced_by //type; doesn't show up if this type exists. Set to /datum/surgery if you want to hide a "base" surgery (useful for typing parents IE healing.dm just make sure to null it out again)
+ var/location = BODY_ZONE_CHEST //Surgery location
+ var/requires_bodypart_type = BODYPART_ORGANIC //Prevents you from performing an operation on incorrect limbs. 0 for any limb type
+ var/list/possible_locs = list() //Multiple locations
+ var/ignore_clothes = FALSE //This surgery ignores clothes
+ var/mob/living/carbon/target //Operation target mob
+ var/obj/item/bodypart/operated_bodypart //Operable body part
+ var/requires_bodypart = TRUE //Surgery available only when a bodypart is present, or only when it is missing.
+ var/speed_modifier = 0 //Step speed multiplier
+ var/requires_real_bodypart = FALSE //Some surgeries don't work on limbs that don't really exist
+ var/lying_required = TRUE //Does the vicitm needs to be lying down.
+ var/self_operable = FALSE //Can the surgery be performed on yourself.
+ var/requires_tech = FALSE //handles techweb-oriented surgeries, previously restricted to the /advanced subtype (You still need to add designs)
+ var/replaced_by //type; doesn't show up if this type exists. Set to /datum/surgery if you want to hide a "base" surgery (useful for typing parents IE healing.dm just make sure to null it out again)
/datum/surgery/New(surgery_target, surgery_location, surgery_bodypart)
..()
@@ -43,7 +43,7 @@
if(replaced_by == /datum/surgery)
return FALSE
- if(HAS_TRAIT(user, TRAIT_SURGEON))
+ if(HAS_TRAIT(user, TRAIT_SURGEON) || (user.mind && HAS_TRAIT(user.mind, TRAIT_SURGEON)))
if(replaced_by)
return FALSE
else
@@ -115,31 +115,6 @@
SSblackbox.record_feedback("tally", "surgeries_completed", 1, type)
qdel(src)
-/datum/surgery/proc/get_propability_multiplier(mob/user)
- var/propability = 0.3
- var/turf/T = get_turf(target)
- var/selfpenalty = 0
- var/sleepbonus = 0
- if(target == user)
- if(HAS_TRAIT(user, TRAIT_SELF_AWARE) || locate(/obj/structure/mirror) in range(1, user))
- selfpenalty = 0.4
- else
- selfpenalty = 0.6
- if(target.stat != CONSCIOUS)
- sleepbonus = 0.5
- if(locate(/obj/structure/table/optable/abductor, T))
- propability = 1.2
- if(locate(/obj/machinery/stasis, T))
- propability = 0.8
- if(locate(/obj/structure/table/optable, T))
- propability = 0.8
- else if(locate(/obj/structure/table, T))
- propability = 0.6
- else if(locate(/obj/structure/bed, T))
- propability = 0.5
-
- return propability + success_multiplier + sleepbonus - selfpenalty
-
/datum/surgery/advanced
name = "advanced surgery"
requires_tech = TRUE
diff --git a/code/modules/surgery/surgery_step.dm b/code/modules/surgery/surgery_step.dm
index bab3f0a8e4837..8908661f225c1 100644
--- a/code/modules/surgery/surgery_step.dm
+++ b/code/modules/surgery/surgery_step.dm
@@ -55,10 +55,35 @@
return FALSE
+/datum/surgery_step/proc/get_speed_modifier(mob/user, mob/target)
+ var/propability = 0.3
+ var/turf/T = get_turf(target)
+ var/selfpenalty = 0
+ var/sleepbonus = 0
+ if(target == user)
+ if(HAS_TRAIT(user, TRAIT_SELF_AWARE) || user.get_inactive_held_item() == /obj/item/handmirror || locate(/obj/structure/mirror) in view(1, user))
+ selfpenalty = 0.4
+ else
+ selfpenalty = 0.6
+ if(target.stat)//are they not conscious
+ sleepbonus = 0.5
+ if(locate(/obj/structure/table/optable/abductor, T))
+ propability = 1.2
+ else if(locate(/obj/structure/table/optable, T))
+ propability = 1
+ else if(locate(/obj/machinery/stasis, T))
+ propability = 0.8
+ else if(locate(/obj/structure/table, T))
+ propability = 0.6
+ else if(locate(/obj/structure/bed, T))
+ propability = 0.5
+
+ return max(propability + sleepbonus - selfpenalty, 0.1)
/datum/surgery_step/proc/initiate(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, try_to_fail = FALSE)
surgery.step_in_progress = TRUE
var/speed_mod = 1
+ var/fail_prob = 0//100 - fail_prob = success_prob
var/advance = FALSE
if(preop(user, target, target_zone, tool, surgery) == -1)
@@ -68,19 +93,26 @@
if(tool)
speed_mod = tool.toolspeed
- if(do_after(user, time * speed_mod, target = target))
- var/prob_chance = 100
+ var/implement_speed_mod = 1
+ if(implement_type)//this means it isn't a require hand or any item step.
+ implement_speed_mod = implements[implement_type] / 100.0
+ speed_mod /= (get_speed_modifier(user, target) * (1 + surgery.speed_modifier) * implement_speed_mod)
+
+ var/modded_time = time * speed_mod
+ fail_prob = min(max(0, modded_time - (time * 2)), 99)//if modded_time > time * 2, then fail_prob = modded_time - time*2. starts at 0, caps at 99
+ modded_time = min(modded_time, time * 2)//also if that, then cap modded_time at time*2
+
+ if(iscyborg(user))//any immunities to surgery slowdown should go in this check.
+ modded_time = time
+
+ if(do_after(user, modded_time, target = target))
- if(implement_type) //this means it isn't a require hand or any item step.
- prob_chance = implements[implement_type]
- prob_chance *= surgery.get_propability_multiplier(user)
+ if((prob(100 - fail_prob) || iscyborg(user)) && chem_check(target) && !try_to_fail)
- if((prob(prob_chance) || iscyborg(user)) && chem_check(target) && !try_to_fail)
if(success(user, target, target_zone, tool, surgery))
advance = TRUE
- else
- if(failure(user, target, target_zone, tool, surgery))
- advance = TRUE
+ else if(failure(user, target, target_zone, tool, surgery, fail_prob))
+ advance = TRUE
if(advance && !repeatable)
surgery.status++
@@ -90,7 +122,6 @@
surgery.step_in_progress = FALSE
return advance
-
/datum/surgery_step/proc/preop(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery)
display_results(user, target, "You begin to perform surgery on [target]...",
"[user] begins to perform surgery on [target].",
@@ -148,7 +179,7 @@
//Replaces visible_message during operations so only people looking over the surgeon can tell what they're doing, allowing for shenanigans.
/datum/surgery_step/proc/display_results(mob/user, mob/living/carbon/target, self_message, detailed_message, vague_message, target_detailed = FALSE)
- var/list/detailed_mobs = get_hearers_in_view(1, user) //Only the surgeon and people looking over his shoulder can see the operation clearly
+ var/list/detailed_mobs = hearers(1, user) //Only the surgeon and people looking over his shoulder can see the operation clearly
if(!target_detailed)
detailed_mobs -= target //The patient can't see well what's going on, unless it's something like getting cut
user.visible_message(detailed_message, self_message, vision_distance = 1, ignored_mobs = target_detailed ? null : target)
diff --git a/code/modules/surgery/tools.dm b/code/modules/surgery/tools.dm
index 6497252c883ee..6ccc8004f2e0f 100644
--- a/code/modules/surgery/tools.dm
+++ b/code/modules/surgery/tools.dm
@@ -340,6 +340,7 @@
icon_state = "scalpel_a"
/obj/item/scalpel/advanced/examine()
+ . = ..()
. += " It's set to [tool_behaviour == TOOL_SCALPEL ? "scalpel" : "saw"] mode."
/obj/item/retractor/advanced
@@ -361,6 +362,7 @@
icon_state = "retractor_a"
/obj/item/retractor/advanced/examine()
+ . = ..()
. += " It resembles a retractor[tool_behaviour == TOOL_RETRACTOR ? "retractor" : "hemostat"]."
/obj/item/surgicaldrill/advanced
@@ -371,6 +373,7 @@
hitsound = 'sound/items/welder.ogg'
toolspeed = 0.7
light_color = LIGHT_COLOR_RED
+ w_class = WEIGHT_CLASS_SMALL
/obj/item/surgicaldrill/advanced/Initialize()
. = ..()
@@ -388,4 +391,5 @@
icon_state = "surgicaldrill_a"
/obj/item/surgicaldrill/advanced/examine()
- . += " It's set to [tool_behaviour == TOOL_DRILL ? "drilling" : "mending"] mode."
\ No newline at end of file
+ . = ..()
+ . += " It's set to [tool_behaviour == TOOL_DRILL ? "drilling" : "mending"] mode."
diff --git a/code/modules/tgchat/to_chat.dm b/code/modules/tgchat/to_chat.dm
index 21904a7a8ae8b..0bc97d09c6617 100644
--- a/code/modules/tgchat/to_chat.dm
+++ b/code/modules/tgchat/to_chat.dm
@@ -11,6 +11,7 @@
type = null,
text = null,
avoid_highlighting = FALSE,
+ allow_linkify = FALSE,
// FIXME: These flags are now pointless and have no effect
handle_whitespace = TRUE,
trailing_newline = TRUE)
@@ -24,6 +25,7 @@
if(text) message["text"] = text
if(html) message["html"] = html
if(avoid_highlighting) message["avoidHighlighting"] = avoid_highlighting
+ if(allow_linkify) message["allowLinkify"] = allow_linkify
var/message_blob = TGUI_CREATE_MESSAGE("chat/message", message)
var/message_html = message_to_html(message)
if(islist(target))
@@ -54,6 +56,7 @@
type = null,
text = null,
avoid_highlighting = FALSE,
+ allow_linkify = FALSE,
// FIXME: These flags are now pointless and have no effect
handle_whitespace = TRUE,
trailing_newline = TRUE)
@@ -70,4 +73,5 @@
if(text) message["text"] = text
if(html) message["html"] = html
if(avoid_highlighting) message["avoidHighlighting"] = avoid_highlighting
+ if(allow_linkify) message["allowLinkify"] = allow_linkify
SSchat.queue(target, message)
diff --git a/code/modules/tgs/core/core.dm b/code/modules/tgs/core/core.dm
index f75e7837af0f2..41a0473394525 100644
--- a/code/modules/tgs/core/core.dm
+++ b/code/modules/tgs/core/core.dm
@@ -40,7 +40,7 @@
if(5)
api_datum = /datum/tgs_api/v5
- var/datum/tgs_version/max_api_version = TgsMaximumAPIVersion();
+ var/datum/tgs_version/max_api_version = TgsMaximumApiVersion();
if(version.suite != null && version.minor != null && version.patch != null && version.deprecated_patch != null && version.deprefixed_parameter > max_api_version.deprefixed_parameter)
TGS_ERROR_LOG("Detected unknown API version! Defaulting to latest. Update the DMAPI to fix this problem.")
api_datum = /datum/tgs_api/latest
@@ -64,10 +64,10 @@
TGS_WRITE_GLOBAL(tgs, null)
TGS_ERROR_LOG("Failed to activate API!")
-/world/TgsMaximumAPIVersion()
+/world/TgsMaximumApiVersion()
return new /datum/tgs_version("5.x.x")
-/world/TgsMinimumAPIVersion()
+/world/TgsMinimumApiVersion()
return new /datum/tgs_version("3.2.x")
/world/TgsInitializationComplete()
diff --git a/code/modules/tgs/includes.dm b/code/modules/tgs/includes.dm
index c803cf7ac2652..4018074f4e37e 100644
--- a/code/modules/tgs/includes.dm
+++ b/code/modules/tgs/includes.dm
@@ -14,4 +14,4 @@
#include "v5\_defines.dm"
#include "v5\api.dm"
#include "v5\commands.dm"
-#include "v5\undef.dm"
+#include "v5\undefs.dm"
diff --git a/code/modules/tgs/v3210/api.dm b/code/modules/tgs/v3210/api.dm
index 5b41e6a89d876..63823251001b2 100644
--- a/code/modules/tgs/v3210/api.dm
+++ b/code/modules/tgs/v3210/api.dm
@@ -62,7 +62,7 @@
comms_key = world.params[SERVICE_WORLD_PARAM]
instance_name = world.params[SERVICE_INSTANCE_PARAM]
if(!instance_name)
- instance_name = "TG Station Server" //maybe just upgraded
+ instance_name = "TG Station Server" //maybe just upgraded
var/list/logs = file2list(".git/logs/HEAD")
if(logs.len)
@@ -92,14 +92,14 @@
if(skip_compat_check && !fexists(SERVICE_INTERFACE_DLL))
TGS_ERROR_LOG("Service parameter present but no interface DLL detected. This is symptomatic of running a service less than version 3.1! Please upgrade.")
return
- call(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)(instance_name, command) //trust no retval
+ call(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)(instance_name, command) //trust no retval
return TRUE
/datum/tgs_api/v3210/OnTopic(T)
var/list/params = params2list(T)
var/their_sCK = params[SERVICE_CMD_PARAM_KEY]
if(!their_sCK)
- return FALSE //continue world/Topic
+ return FALSE //continue world/Topic
if(their_sCK != comms_key)
return "Invalid comms key!";
@@ -160,7 +160,7 @@
var/datum/tgs_revision_information/test_merge/tm = new
tm.number = text2num(I)
var/list/entry = json[I]
- tm.pull_request_commit = entry["commit"]
+ tm.head_commit = entry["commit"]
tm.author = entry["author"]
tm.title = entry["title"]
. += tm
@@ -176,7 +176,7 @@
return ri
/datum/tgs_api/v3210/EndProcess()
- sleep(world.tick_lag) //flush the buffers
+ sleep(world.tick_lag) //flush the buffers
ExportService(SERVICE_REQUEST_KILL_PROCESS)
/datum/tgs_api/v3210/ChatChannelInfo()
diff --git a/code/modules/tgs/v4/api.dm b/code/modules/tgs/v4/api.dm
index 081543828e804..0e37a7aa24c0d 100644
--- a/code/modules/tgs/v4/api.dm
+++ b/code/modules/tgs/v4/api.dm
@@ -92,7 +92,7 @@
var/list/json = cached_json["testMerges"]
for(var/entry in json)
var/datum/tgs_revision_information/test_merge/tm = new
- tm.time_merged = text2num(entry["timeMerged"])
+ tm.timestamp = text2num(entry["timeMerged"])
var/list/revInfo = entry["revision"]
if(revInfo)
@@ -104,7 +104,7 @@
tm.url = entry["url"]
tm.author = entry["author"]
tm.number = entry["number"]
- tm.pull_request_commit = entry["pullRequestRevision"]
+ tm.head_commit = entry["pullRequestRevision"]
tm.comment = entry["comment"]
cached_test_merges += tm
@@ -118,7 +118,7 @@
var/list/params = params2list(T)
var/their_sCK = params[TGS4_INTEROP_ACCESS_IDENTIFIER]
if(!their_sCK)
- return FALSE //continue world/Topic
+ return FALSE //continue world/Topic
if(their_sCK != access_identifier)
return "Invalid comms key!";
@@ -192,7 +192,7 @@
//request a new port
export_lock = FALSE
- var/list/new_port_json = Export(TGS4_COMM_NEW_PORT, list(TGS4_PARAMETER_DATA = "[world.port]"), TRUE) //stringify this on purpose
+ var/list/new_port_json = Export(TGS4_COMM_NEW_PORT, list(TGS4_PARAMETER_DATA = "[world.port]"), TRUE) //stringify this on purpose
if(!new_port_json)
TGS_ERROR_LOG("No new port response from server![TGS4_PORT_CRITFAIL_MESSAGE]")
@@ -235,7 +235,7 @@
var/port = result[TGS4_PARAMETER_DATA]
if(!isnum(port))
- return //this is valid, server may just want use to reboot
+ return //this is valid, server may just want use to reboot
if(port == 0)
//to byond 0 means any port and "none" means close vOv
diff --git a/code/modules/tgs/v5/README.md b/code/modules/tgs/v5/README.md
new file mode 100644
index 0000000000000..5b48d57a1f07f
--- /dev/null
+++ b/code/modules/tgs/v5/README.md
@@ -0,0 +1,8 @@
+# DMAPI V5
+
+This DMAPI implements bridge requests using HTTP GET requests to TGS. It has no security restrictions.
+
+- [_defines.dm](./_defines.dm) contains constant definitions.
+- [api.dm](./api.dm) contains the bulk of the API code.
+- [commands.dm](./commands.dm) contains functions relating to `/datum/tgs_chat_command`s.
+- [undefs.dm](./undefs.dm) Undoes the work of `_defines.dm`.
diff --git a/code/modules/tgs/v5/_defines.dm b/code/modules/tgs/v5/_defines.dm
index 8fd84e19fa325..10bc4cbe40606 100644
--- a/code/modules/tgs/v5/_defines.dm
+++ b/code/modules/tgs/v5/_defines.dm
@@ -79,6 +79,7 @@
#define DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES "chatResponses"
#define DMAPI5_REVISION_INFORMATION_COMMIT_SHA "commitSha"
+#define DMAPI5_REVISION_INFORMATION_TIMESTAMP "timestamp"
#define DMAPI5_REVISION_INFORMATION_ORIGIN_COMMIT_SHA "originCommitSha"
#define DMAPI5_CHAT_USER_ID "id"
diff --git a/code/modules/tgs/v5/api.dm b/code/modules/tgs/v5/api.dm
index 466a986237d95..704ff873c0a1e 100644
--- a/code/modules/tgs/v5/api.dm
+++ b/code/modules/tgs/v5/api.dm
@@ -18,7 +18,9 @@
var/initialized = FALSE
/datum/tgs_api/v5/ApiVersion()
- return new /datum/tgs_version(TGS_DMAPI_VERSION)
+ return new /datum/tgs_version(
+ #include "interop_version.dm"
+ )
/datum/tgs_api/v5/OnWorldNew(minimum_required_security_level)
server_port = world.params[DMAPI5_PARAM_SERVER_PORT]
@@ -48,6 +50,7 @@
if(istype(revisionData))
revision = new
revision.commit = revisionData[DMAPI5_REVISION_INFORMATION_COMMIT_SHA]
+ revision.timestamp = revisionData[DMAPI5_REVISION_INFORMATION_TIMESTAMP]
revision.origin_commit = revisionData[DMAPI5_REVISION_INFORMATION_ORIGIN_COMMIT_SHA]
else
TGS_ERROR_LOG("Failed to decode [DMAPI5_RUNTIME_INFORMATION_REVISION] from runtime information!")
@@ -63,15 +66,18 @@
if(revInfo)
tm.commit = revisionData[DMAPI5_REVISION_INFORMATION_COMMIT_SHA]
tm.origin_commit = revisionData[DMAPI5_REVISION_INFORMATION_ORIGIN_COMMIT_SHA]
+ tm.timestamp = entry[DMAPI5_REVISION_INFORMATION_TIMESTAMP]
else
TGS_WARNING_LOG("Failed to decode [DMAPI5_TEST_MERGE_REVISION] from test merge #[tm.number]!")
- tm.time_merged = text2num(entry[DMAPI5_TEST_MERGE_TIME_MERGED])
+ if(!tm.timestamp)
+ tm.timestamp = entry[DMAPI5_TEST_MERGE_TIME_MERGED]
+
tm.title = entry[DMAPI5_TEST_MERGE_TITLE_AT_MERGE]
tm.body = entry[DMAPI5_TEST_MERGE_BODY_AT_MERGE]
tm.url = entry[DMAPI5_TEST_MERGE_URL]
tm.author = entry[DMAPI5_TEST_MERGE_AUTHOR]
- tm.pull_request_commit = entry[DMAPI5_TEST_MERGE_PULL_REQUEST_REVISION]
+ tm.head_commit = entry[DMAPI5_TEST_MERGE_PULL_REQUEST_REVISION]
tm.comment = entry[DMAPI5_TEST_MERGE_COMMENT]
test_merges += tm
@@ -98,18 +104,19 @@
return json_encode(response)
/datum/tgs_api/v5/OnTopic(T)
- if(!initialized)
- return FALSE //continue world/Topic
-
var/list/params = params2list(T)
var/json = params[DMAPI5_TOPIC_DATA]
if(!json)
- return FALSE
+ return FALSE // continue to /world/Topic
var/list/topic_parameters = json_decode(json)
if(!topic_parameters)
return TopicResponse("Invalid topic parameters json!");
+ if(!initialized)
+ TGS_WARNING_LOG("Missed topic due to not being initialized: [T]")
+ return TRUE // too early to handle, but it's still our responsibility
+
var/their_sCK = topic_parameters[DMAPI5_PARAMETER_ACCESS_IDENTIFIER]
if(their_sCK != access_identifier)
return TopicResponse("Failed to decode [DMAPI5_PARAMETER_ACCESS_IDENTIFIER] from: [json]!");
@@ -266,7 +273,7 @@
var/port = result[DMAPI5_BRIDGE_RESPONSE_NEW_PORT]
if(!isnum(port))
- return //this is valid, server may just want use to reboot
+ return //this is valid, server may just want use to reboot
if(port == 0)
//to byond 0 means any port and "none" means close vOv
diff --git a/code/modules/tgs/v5/interop_version.dm b/code/modules/tgs/v5/interop_version.dm
new file mode 100644
index 0000000000000..c7bf62ecae353
--- /dev/null
+++ b/code/modules/tgs/v5/interop_version.dm
@@ -0,0 +1 @@
+"5.3.0"
diff --git a/code/modules/tgs/v5/undef.dm b/code/modules/tgs/v5/undef.dm
deleted file mode 100644
index 34fc481d36e71..0000000000000
--- a/code/modules/tgs/v5/undef.dm
+++ /dev/null
@@ -1,98 +0,0 @@
-#undef DMAPI5_PARAM_SERVER_PORT
-#undef DMAPI5_PARAM_ACCESS_IDENTIFIER
-
-#undef DMAPI5_BRIDGE_DATA
-#undef DMAPI5_TOPIC_DATA
-
-#undef DMAPI5_BRIDGE_COMMAND_PORT_UPDATE
-#undef DMAPI5_BRIDGE_COMMAND_STARTUP
-#undef DMAPI5_BRIDGE_COMMAND_PRIME
-#undef DMAPI5_BRIDGE_COMMAND_REBOOT
-#undef DMAPI5_BRIDGE_COMMAND_KILL
-#undef DMAPI5_BRIDGE_COMMAND_CHAT_SEND
-
-#undef DMAPI5_PARAMETER_ACCESS_IDENTIFIER
-#undef DMAPI5_PARAMETER_CUSTOM_COMMANDS
-
-#undef DMAPI5_RESPONSE_ERROR_MESSAGE
-
-#undef DMAPI5_BRIDGE_PARAMETER_COMMAND_TYPE
-#undef DMAPI5_BRIDGE_PARAMETER_CURRENT_PORT
-#undef DMAPI5_BRIDGE_PARAMETER_VERSION
-#undef DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE
-#undef DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL
-
-#undef DMAPI5_BRIDGE_RESPONSE_NEW_PORT
-#undef DMAPI5_BRIDGE_RESPONSE_RUNTIME_INFORMATION
-
-#undef DMAPI5_CHAT_MESSAGE_TEXT
-#undef DMAPI5_CHAT_MESSAGE_CHANNEL_IDS
-
-#undef DMAPI5_RUNTIME_INFORMATION_ACCESS_IDENTIFIER
-#undef DMAPI5_RUNTIME_INFORMATION_SERVER_VERSION
-#undef DMAPI5_RUNTIME_INFORMATION_SERVER_PORT
-#undef DMAPI5_RUNTIME_INFORMATION_API_VALIDATE_ONLY
-#undef DMAPI5_RUNTIME_INFORMATION_INSTANCE_NAME
-#undef DMAPI5_RUNTIME_INFORMATION_REVISION
-#undef DMAPI5_RUNTIME_INFORMATION_TEST_MERGES
-#undef DMAPI5_RUNTIME_INFORMATION_SECURITY_LEVEL
-
-#undef DMAPI5_CHAT_UPDATE_CHANNELS
-
-#undef DMAPI5_TEST_MERGE_TIME_MERGED
-#undef DMAPI5_TEST_MERGE_REVISION
-#undef DMAPI5_TEST_MERGE_TITLE_AT_MERGE
-#undef DMAPI5_TEST_MERGE_BODY_AT_MERGE
-#undef DMAPI5_TEST_MERGE_URL
-#undef DMAPI5_TEST_MERGE_AUTHOR
-#undef DMAPI5_TEST_MERGE_NUMBER
-#undef DMAPI5_TEST_MERGE_PULL_REQUEST_REVISION
-#undef DMAPI5_TEST_MERGE_COMMENT
-
-#undef DMAPI5_CHAT_COMMAND_NAME
-#undef DMAPI5_CHAT_COMMAND_PARAMS
-#undef DMAPI5_CHAT_COMMAND_USER
-
-#undef DMAPI5_EVENT_NOTIFICATION_TYPE
-#undef DMAPI5_EVENT_NOTIFICATION_PARAMETERS
-
-#undef DMAPI5_TOPIC_COMMAND_CHAT_COMMAND
-#undef DMAPI5_TOPIC_COMMAND_EVENT_NOTIFICATION
-#undef DMAPI5_TOPIC_COMMAND_CHANGE_PORT
-#undef DMAPI5_TOPIC_COMMAND_CHANGE_REBOOT_STATE
-#undef DMAPI5_TOPIC_COMMAND_INSTANCE_RENAMED
-#undef DMAPI5_TOPIC_COMMAND_CHAT_CHANNELS_UPDATE
-#undef DMAPI5_TOPIC_COMMAND_SERVER_PORT_UPDATE
-#undef DMAPI5_TOPIC_COMMAND_HEARTBEAT
-#undef DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH
-
-#undef DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE
-#undef DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND
-#undef DMAPI5_TOPIC_PARAMETER_EVENT_NOTIFICATION
-#undef DMAPI5_TOPIC_PARAMETER_NEW_PORT
-#undef DMAPI5_TOPIC_PARAMETER_NEW_REBOOT_STATE
-#undef DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME
-#undef DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE
-#undef DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION
-
-#undef DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE
-#undef DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES
-
-#undef DMAPI5_REVISION_INFORMATION_COMMIT_SHA
-#undef DMAPI5_REVISION_INFORMATION_ORIGIN_COMMIT_SHA
-
-#undef DMAPI5_CHAT_USER_ID
-#undef DMAPI5_CHAT_USER_FRIENDLY_NAME
-#undef DMAPI5_CHAT_USER_MENTION
-#undef DMAPI5_CHAT_USER_CHANNEL
-
-#undef DMAPI5_CHAT_CHANNEL_ID
-#undef DMAPI5_CHAT_CHANNEL_FRIENDLY_NAME
-#undef DMAPI5_CHAT_CHANNEL_CONNECTION_NAME
-#undef DMAPI5_CHAT_CHANNEL_IS_ADMIN_CHANNEL
-#undef DMAPI5_CHAT_CHANNEL_IS_PRIVATE_CHANNEL
-#undef DMAPI5_CHAT_CHANNEL_TAG
-
-#undef DMAPI5_CUSTOM_CHAT_COMMAND_NAME
-#undef DMAPI5_CUSTOM_CHAT_COMMAND_HELP_TEXT
-#undef DMAPI5_CUSTOM_CHAT_COMMAND_ADMIN_ONLY
diff --git a/code/modules/tgs/v5/undefs.dm b/code/modules/tgs/v5/undefs.dm
new file mode 100644
index 0000000000000..5885a60e75cea
--- /dev/null
+++ b/code/modules/tgs/v5/undefs.dm
@@ -0,0 +1,99 @@
+#undef DMAPI5_PARAM_SERVER_PORT
+#undef DMAPI5_PARAM_ACCESS_IDENTIFIER
+
+#undef DMAPI5_BRIDGE_DATA
+#undef DMAPI5_TOPIC_DATA
+
+#undef DMAPI5_BRIDGE_COMMAND_PORT_UPDATE
+#undef DMAPI5_BRIDGE_COMMAND_STARTUP
+#undef DMAPI5_BRIDGE_COMMAND_PRIME
+#undef DMAPI5_BRIDGE_COMMAND_REBOOT
+#undef DMAPI5_BRIDGE_COMMAND_KILL
+#undef DMAPI5_BRIDGE_COMMAND_CHAT_SEND
+
+#undef DMAPI5_PARAMETER_ACCESS_IDENTIFIER
+#undef DMAPI5_PARAMETER_CUSTOM_COMMANDS
+
+#undef DMAPI5_RESPONSE_ERROR_MESSAGE
+
+#undef DMAPI5_BRIDGE_PARAMETER_COMMAND_TYPE
+#undef DMAPI5_BRIDGE_PARAMETER_CURRENT_PORT
+#undef DMAPI5_BRIDGE_PARAMETER_VERSION
+#undef DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE
+#undef DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL
+
+#undef DMAPI5_BRIDGE_RESPONSE_NEW_PORT
+#undef DMAPI5_BRIDGE_RESPONSE_RUNTIME_INFORMATION
+
+#undef DMAPI5_CHAT_MESSAGE_TEXT
+#undef DMAPI5_CHAT_MESSAGE_CHANNEL_IDS
+
+#undef DMAPI5_RUNTIME_INFORMATION_ACCESS_IDENTIFIER
+#undef DMAPI5_RUNTIME_INFORMATION_SERVER_VERSION
+#undef DMAPI5_RUNTIME_INFORMATION_SERVER_PORT
+#undef DMAPI5_RUNTIME_INFORMATION_API_VALIDATE_ONLY
+#undef DMAPI5_RUNTIME_INFORMATION_INSTANCE_NAME
+#undef DMAPI5_RUNTIME_INFORMATION_REVISION
+#undef DMAPI5_RUNTIME_INFORMATION_TEST_MERGES
+#undef DMAPI5_RUNTIME_INFORMATION_SECURITY_LEVEL
+
+#undef DMAPI5_CHAT_UPDATE_CHANNELS
+
+#undef DMAPI5_TEST_MERGE_TIME_MERGED
+#undef DMAPI5_TEST_MERGE_REVISION
+#undef DMAPI5_TEST_MERGE_TITLE_AT_MERGE
+#undef DMAPI5_TEST_MERGE_BODY_AT_MERGE
+#undef DMAPI5_TEST_MERGE_URL
+#undef DMAPI5_TEST_MERGE_AUTHOR
+#undef DMAPI5_TEST_MERGE_NUMBER
+#undef DMAPI5_TEST_MERGE_PULL_REQUEST_REVISION
+#undef DMAPI5_TEST_MERGE_COMMENT
+
+#undef DMAPI5_CHAT_COMMAND_NAME
+#undef DMAPI5_CHAT_COMMAND_PARAMS
+#undef DMAPI5_CHAT_COMMAND_USER
+
+#undef DMAPI5_EVENT_NOTIFICATION_TYPE
+#undef DMAPI5_EVENT_NOTIFICATION_PARAMETERS
+
+#undef DMAPI5_TOPIC_COMMAND_CHAT_COMMAND
+#undef DMAPI5_TOPIC_COMMAND_EVENT_NOTIFICATION
+#undef DMAPI5_TOPIC_COMMAND_CHANGE_PORT
+#undef DMAPI5_TOPIC_COMMAND_CHANGE_REBOOT_STATE
+#undef DMAPI5_TOPIC_COMMAND_INSTANCE_RENAMED
+#undef DMAPI5_TOPIC_COMMAND_CHAT_CHANNELS_UPDATE
+#undef DMAPI5_TOPIC_COMMAND_SERVER_PORT_UPDATE
+#undef DMAPI5_TOPIC_COMMAND_HEARTBEAT
+#undef DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH
+
+#undef DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE
+#undef DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND
+#undef DMAPI5_TOPIC_PARAMETER_EVENT_NOTIFICATION
+#undef DMAPI5_TOPIC_PARAMETER_NEW_PORT
+#undef DMAPI5_TOPIC_PARAMETER_NEW_REBOOT_STATE
+#undef DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME
+#undef DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE
+#undef DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION
+
+#undef DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE
+#undef DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES
+
+#undef DMAPI5_REVISION_INFORMATION_COMMIT_SHA
+#undef DMAPI5_REVISION_INFORMATION_TIMESTAMP
+#undef DMAPI5_REVISION_INFORMATION_ORIGIN_COMMIT_SHA
+
+#undef DMAPI5_CHAT_USER_ID
+#undef DMAPI5_CHAT_USER_FRIENDLY_NAME
+#undef DMAPI5_CHAT_USER_MENTION
+#undef DMAPI5_CHAT_USER_CHANNEL
+
+#undef DMAPI5_CHAT_CHANNEL_ID
+#undef DMAPI5_CHAT_CHANNEL_FRIENDLY_NAME
+#undef DMAPI5_CHAT_CHANNEL_CONNECTION_NAME
+#undef DMAPI5_CHAT_CHANNEL_IS_ADMIN_CHANNEL
+#undef DMAPI5_CHAT_CHANNEL_IS_PRIVATE_CHANNEL
+#undef DMAPI5_CHAT_CHANNEL_TAG
+
+#undef DMAPI5_CUSTOM_CHAT_COMMAND_NAME
+#undef DMAPI5_CUSTOM_CHAT_COMMAND_HELP_TEXT
+#undef DMAPI5_CUSTOM_CHAT_COMMAND_ADMIN_ONLY
diff --git a/code/modules/tgui/external.dm b/code/modules/tgui/external.dm
index c4515b8a7637d..5ced3e0d4746e 100644
--- a/code/modules/tgui/external.dm
+++ b/code/modules/tgui/external.dm
@@ -17,6 +17,28 @@
/datum/proc/ui_interact(mob/user, datum/tgui/ui)
return FALSE // Not implemented.
+/**
+ * public
+ *
+ * Returns TRUE if a non autoupdating UI needs to update.
+ */
+/datum/proc/ui_requires_update(mob/user, datum/tgui/ui)
+ if(ui.needs_update)
+ ui.needs_update = FALSE
+ return TRUE
+ return FALSE // Not implemented.
+
+/**
+ * public
+ *
+ * Causes the UI to update to viewers on the next process.
+ * Better than calling SStgui.update if this is callable by the user,
+ * since it calls on process rather than instantly which handles spamming.
+ */
+/datum/proc/ui_update()
+ for(var/datum/tgui/ui as() in SStgui.get_all_open_uis(src))
+ ui.needs_update = TRUE
+
/**
* public
*
@@ -143,7 +165,7 @@
* Called on a UI's object when the UI is closed, not to be confused with
* client/verb/uiclose(), which closes the ui window
*/
-/datum/proc/ui_close(mob/user)
+/datum/proc/ui_close(mob/user, datum/tgui/tgui)
/**
* verb
diff --git a/code/modules/tgui/states.dm b/code/modules/tgui/states.dm
index 4a5ec56535584..436dca91fa805 100644
--- a/code/modules/tgui/states.dm
+++ b/code/modules/tgui/states.dm
@@ -115,7 +115,7 @@
*/
/mob/living/proc/shared_living_ui_distance(atom/movable/src_object, viewcheck = TRUE)
// If the object is obscured, close it.
- if(viewcheck && !(src_object in view(src)))
+ if(viewcheck && !((src_object in src) || (src in viewers(src_object))))
return UI_CLOSE
var/dist = get_dist(src_object, src)
// Open and interact if 1-0 tiles away.
diff --git a/code/modules/tgui/states/clockcult_state.dm b/code/modules/tgui/states/clockcult_state.dm
new file mode 100644
index 0000000000000..64c74e2e01134
--- /dev/null
+++ b/code/modules/tgui/states/clockcult_state.dm
@@ -0,0 +1,6 @@
+GLOBAL_DATUM_INIT(clockcult_state, /datum/ui_state/clockcult_state, new)
+
+/datum/ui_state/clockcult_state/can_use_topic(src_object, mob/user)
+ if(is_servant_of_ratvar(user))
+ return UI_INTERACTIVE
+ return UI_CLOSE
diff --git a/code/modules/tgui/states/zlevel.dm b/code/modules/tgui/states/zlevel.dm
index 64ea2fa1c0ecf..446b62d488f5d 100644
--- a/code/modules/tgui/states/zlevel.dm
+++ b/code/modules/tgui/states/zlevel.dm
@@ -12,6 +12,6 @@ GLOBAL_DATUM_INIT(z_state, /datum/ui_state/z_state, new)
/datum/ui_state/z_state/can_use_topic(src_object, mob/user)
var/turf/turf_obj = get_turf(src_object)
var/turf/turf_usr = get_turf(user)
- if(turf_obj && turf_usr && turf_obj.z == turf_usr.z)
+ if(turf_obj && turf_usr && turf_obj.get_virtual_z_level() == turf_usr.get_virtual_z_level())
return UI_INTERACTIVE
return UI_CLOSE
diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm
index 5c4c14cfb9cec..693e429048919 100644
--- a/code/modules/tgui/tgui.dm
+++ b/code/modules/tgui/tgui.dm
@@ -22,7 +22,7 @@
/// The interface (template) to be used for this UI.
var/interface
/// Update the UI every MC tick.
- var/autoupdate = TRUE
+ var/autoupdate = FALSE
/// If the UI has been initialized yet.
var/initialized = FALSE
/// Time of opening the window.
@@ -33,6 +33,8 @@
var/status = UI_INTERACTIVE
/// Topic state used to determine status/interactability.
var/datum/ui_state/state = null
+ /// If the window should update
+ var/needs_update = FALSE
/**
* public
@@ -65,22 +67,29 @@
if(ui_x && ui_y)
src.window_size = list(ui_x, ui_y)
+/datum/tgui/Destroy()
+ user = null
+ src_object = null
+ return ..()
+
/**
* public
*
* Open this UI (and initialize it with data).
+ *
+ * return bool - TRUE if a new pooled window is opened, FALSE in all other situations including if a new pooled window didn't open because one already exists.
*/
/datum/tgui/proc/open()
if(!user.client)
- return null
+ return FALSE
if(window)
- return null
+ return FALSE
process_status()
if(status < UI_UPDATE)
- return null
+ return FALSE
window = SStgui.request_pooled_window(user)
if(!window)
- return null
+ return FALSE
opened_at = world.time
window.acquire_lock(src)
if(!window.is_ready())
@@ -94,6 +103,8 @@
window.send_message("ping")
var/flush_queue = window.send_asset(get_asset_datum(
/datum/asset/simple/namespaced/fontawesome))
+ flush_queue |= window.send_asset(get_asset_datum(
+ /datum/asset/simple/namespaced/tgfont))
for(var/datum/asset/asset in src_object.ui_assets(user))
flush_queue |= window.send_asset(asset)
if (flush_queue)
@@ -103,6 +114,8 @@
with_static_data = TRUE))
SStgui.on_open(src)
+ return TRUE
+
/**
* public
*
@@ -122,7 +135,7 @@
// the error message properly.
window.release_lock()
window.close(can_be_suspended)
- src_object.ui_close(user)
+ src_object.ui_close(user, src) //Bee edit: ui_close now sends the tgui closed.
SStgui.on_close(src)
state = null
qdel(src)
@@ -158,7 +171,7 @@
*/
/datum/tgui/proc/send_asset(datum/asset/asset)
if(!window)
- CRASH("send_asset() can only be called after open().")
+ CRASH("send_asset() was called either without calling open() first or when open() did not return TRUE.")
return window.send_asset(asset)
/**
@@ -239,7 +252,7 @@
* Run an update cycle for this UI. Called internally by SStgui
* every second or so.
*/
-/datum/tgui/process(force = FALSE)
+/datum/tgui/process(delta_time, force = FALSE)
if(closing)
return
var/datum/host = src_object.ui_host(user)
@@ -256,17 +269,18 @@
+ "world.time: [world.time]")
close(can_be_suspended = FALSE)
return
+ // Update through a normal call to ui_interact
+ if(status != UI_DISABLED && (autoupdate || force || src_object.ui_requires_update(user, src)))
+ needs_update = FALSE
+ src_object.ui_interact(user, src)
+ return
// Update status only
- var/needs_update = process_status()
+ var/requires_update = process_status()
if(status <= UI_CLOSE)
close()
return
- if(needs_update)
+ if(requires_update)
window.send_message("update", get_payload())
- // Update through a normal call to ui_interact
- if(status != UI_DISABLED && (autoupdate || force))
- src_object.ui_interact(user, src)
- return
/**
* private
diff --git a/code/modules/tgui/tgui_window.dm b/code/modules/tgui/tgui_window.dm
index b511fe4057807..8a98348628752 100644
--- a/code/modules/tgui/tgui_window.dm
+++ b/code/modules/tgui/tgui_window.dm
@@ -69,29 +69,30 @@
// Generate page html
var/html = SStgui.basehtml
html = replacetextEx(html, "\[tgui:windowId]", id)
- // Process inline assets
- var/inline_styles = ""
- var/inline_scripts = ""
+ // Inject inline assets
+ var/inline_assets_str = ""
for(var/datum/asset/asset in inline_assets)
var/mappings = asset.get_url_mappings()
for(var/name in mappings)
var/url = mappings[name]
- // Not urlencoding since asset strings are considered safe
+ // Not encoding since asset strings are considered safe
if(copytext(name, -4) == ".css")
- inline_styles += "\n"
+ inline_assets_str += "Byond.loadCss('[url]', true);\n"
else if(copytext(name, -3) == ".js")
- inline_scripts += "\n"
+ inline_assets_str += "Byond.loadJs('[url]', true);\n"
asset.send(client)
- html = replacetextEx(html, "\n", inline_styles)
- html = replacetextEx(html, "\n", inline_scripts)
+ if(length(inline_assets_str))
+ inline_assets_str = "\n"
+ html = replacetextEx(html, "\n", inline_assets_str)
// Inject custom HTML
html = replacetextEx(html, "\n", inline_html)
// Open the window
client << browse(html, "window=[id];[options]")
- // Instruct the client to signal UI when the window is closed.
- winset(client, id, "on-close=\"uiclose [id]\"")
// Detect whether the control is a browser
is_browser = winexists(client, id) == "BROWSER"
+ // Instruct the client to signal UI when the window is closed.
+ if(!is_browser)
+ winset(client, id, "on-close=\"uiclose [id]\"")
/**
* public
@@ -251,7 +252,7 @@
if(istype(asset, /datum/asset/spritesheet))
var/datum/asset/spritesheet/spritesheet = asset
send_message("asset/stylesheet", spritesheet.css_filename())
- send_message("asset/mappings", asset.get_url_mappings())
+ send_raw_message(asset.get_serialized_url_mappings())
/**
* private
diff --git a/code/modules/tgui_panel/external.dm b/code/modules/tgui_panel/external.dm
index 3257407e9cb7a..ba7720ccd3144 100644
--- a/code/modules/tgui_panel/external.dm
+++ b/code/modules/tgui_panel/external.dm
@@ -8,46 +8,29 @@
/**
* tgui panel / chat troubleshooting verb
*/
-/client/verb/fix_chat()
+/client/verb/fix_tgui_panel()
set name = "Fix chat"
set category = "OOC"
var/action
- log_tgui(src, "tgui_panel: Started fixing.")
- // Not initialized
- if(!tgui_panel || !istype(tgui_panel))
- log_tgui(src, "tgui_panel: datum is missing")
- action = alert(src, "tgui panel was not initialized!\nSet it up again?", "", "OK", "Cancel")
- if(action != "OK")
- return
- tgui_panel = new(src)
- tgui_panel.initialize()
- action = alert(src, "Wait a bit and tell me if it's fixed", "", "Fixed", "Nope")
- if(action == "Fixed")
- log_tgui(src, "tgui_panel: Fixed by calling 'new' + 'initialize'")
- return
- // Not ready
- if(!tgui_panel?.is_ready())
- log_tgui(src, "tgui_panel: not ready")
- action = alert(src, "tgui panel looks like it's waiting for something.\nSend it a ping?", "", "OK", "Cancel")
- if(action != "OK")
- return
- tgui_panel.window.send_message("ping", force = TRUE)
- action = alert(src, "Wait a bit and tell me if it's fixed", "", "Fixed", "Nope")
- if(action == "Fixed")
- log_tgui(src, "tgui_panel: Fixed by sending a ping")
- return
- // Catch all solution
- action = alert(src, "Looks like tgui panel was already setup, but we can always try again.\nSet it up again?", "", "OK", "Cancel")
- if(action != "OK")
- return
- tgui_panel.initialize(force = TRUE)
- action = alert(src, "Wait a bit and tell me if it's fixed", "", "Fixed", "Nope")
- if(action == "Fixed")
- log_tgui(src, "tgui_panel: Fixed by calling 'initialize'")
- return
+ log_tgui(src, "Started fixing.")
+
+ nuke_chat()
+
// Failed to fix
- action = alert(src, "Welp, I'm all out of ideas. Try closing BYOND and reconnecting.\nWe could also disable tgui_panel and re-enable the old UI", "", "Thanks anyways", "Switch to old UI")
- if (action == "Switch to old UI")
+ action = alert(src, "Did that work?", "", "Yes", "No, switch to old ui")
+ if (action == "No, switch to old ui")
winset(src, "output", "on-show=&is-disabled=0&is-visible=1")
winset(src, "browseroutput", "is-disabled=1;is-visible=0")
- log_tgui(src, "tgui_panel: Failed to fix.")
+ log_tgui(src, "Failed to fix.")
+
+/client/proc/nuke_chat()
+ // Catch all solution (kick the whole thing in the pants)
+ winset(src, "output", "on-show=&is-disabled=0&is-visible=1")
+ winset(src, "browseroutput", "is-disabled=1;is-visible=0")
+ if(!tgui_panel || !istype(tgui_panel))
+ log_tgui(src, "tgui_panel datum is missing")
+ tgui_panel = new(src)
+ tgui_panel.initialize(force = TRUE)
+ // Force show the panel to see if there are any errors
+ winset(src, "output", "is-disabled=1&is-visible=0")
+ winset(src, "browseroutput", "is-disabled=0;is-visible=1")
diff --git a/code/modules/tgui_panel/stat.dm b/code/modules/tgui_panel/stat.dm
new file mode 100644
index 0000000000000..1940745e10bbc
--- /dev/null
+++ b/code/modules/tgui_panel/stat.dm
@@ -0,0 +1,176 @@
+/**
+ * private
+ *
+ * Handles incomming stat messages
+ */
+/datum/tgui_panel/proc/handle_stat_message(type, payload)
+ switch(type)
+ if("stat/setTab")
+ client.selected_stat_tab = payload["selectedTab"]
+ //Update the panel they are on
+ client.mob?.UpdateMobStat(TRUE)
+ client.mob?.stat_tab_changed()
+ return TRUE
+ if("stat/pressed")
+ client.mob?.stat_pressed(payload["action_id"], payload["params"])
+ client.mob?.UpdateMobStat(TRUE)
+ return TRUE
+ return FALSE
+
+/**
+ * public
+ *
+ * Sets the different available tabs.
+ */
+/datum/tgui_panel/proc/set_tab_info(payload)
+ window.send_message("stat/setStatTabs", payload)
+
+/**
+ * public
+ *
+ * Sends TGUI the data of every single verb accessable to client.
+ */
+/datum/tgui_panel/proc/set_verb_infomation(client/C)
+ var/list/tab_names = C?.mob?.get_all_verbs()
+ if(!tab_names)
+ return
+ var/list/payload = list()
+ for(var/tab_name in tab_names)
+ var/list/procpaths = tab_names[tab_name]
+ payload[tab_name] = list()
+ for(var/procpath/PP as() in procpaths)
+ payload[tab_name]["[PP.name]"] = list(
+ action = "verb",
+ params = list("verb" = PP.name),
+ type = STAT_VERB,
+ )
+ window.send_message("stat/setVerbInfomation", payload)
+
+/**
+ * public
+ *
+ * Adds the provided verbs to the respective stat tabs.
+ * list(
+ * "Panel To Add To" = list("Verb1", "Verb3", "Verb4") ,
+ * )
+ */
+/datum/tgui_panel/proc/add_verbs(list/new_verbs)
+ var/list/payload = new_verbs
+ window.send_message("stat/addVerbs", payload)
+
+/**
+ * public
+ *
+ * Removes the provided verbs from the respective stat tabs.
+ * list(
+ * "Panel Name " = list("Verb 2 remove", "Verb 3 remove", etc..),
+ * etc..
+ * )
+ */
+/datum/tgui_panel/proc/remove_verbs(list/new_verbs)
+ var/list/payload = new_verbs
+ window.send_message("stat/removeVerbs", payload)
+
+/**
+ * public
+ *
+ * Sets the infomation to be displayed of the current tab. (For non verb tabs)
+ */
+/datum/tgui_panel/proc/set_panel_infomation(payload)
+ window.send_message("stat/setPanelInfomation", payload)
+
+/**
+ * public
+ *
+ * Sets the current tab.
+ */
+/datum/tgui_panel/proc/set_stat_tab(new_tab)
+ window.send_message("stat/setTab", new_tab)
+
+/**
+ * public
+ *
+ * Displays the antagonist popup.
+ */
+/datum/tgui_panel/proc/give_antagonist_popup(title, text)
+ if(!is_ready())
+ return
+ var/list/payload = list()
+ payload["title"] = title
+ payload["text"] = text
+ window.send_message("stat/antagPopup", payload)
+
+/**
+ * public
+ *
+ * Clears the antagonist popup.
+ */
+/datum/tgui_panel/proc/clear_antagonist_popup()
+ if(!is_ready())
+ return
+ window.send_message("stat/clearAntagPopup", list())
+
+/**
+ * public
+ *
+ * Displays the dead message.
+ */
+/datum/tgui_panel/proc/give_dead_popup()
+ if(!is_ready())
+ return
+ window.send_message("stat/deadPopup", list())
+
+
+
+/**
+ * public
+ *
+ * Clears the death message
+ */
+/datum/tgui_panel/proc/clear_dead_popup()
+ if(!is_ready())
+ return
+ window.send_message("stat/clearDeadPopup", list())
+
+/**
+ * public
+ *
+ * Displays the dead message.
+ */
+/datum/tgui_panel/proc/give_alert_popup(title, text)
+ if(!is_ready())
+ return
+ var/list/payload = list()
+ payload["title"] = title
+ payload["text"] = text
+ window.send_message("stat/alertPopup", payload)
+
+/**
+ * public
+ *
+ * Clears the death message
+ */
+/datum/tgui_panel/proc/clear_alert_popup()
+ if(!is_ready())
+ return
+ window.send_message("stat/clearAlertPopup", list())
+
+/**
+ * public
+ *
+ * Displays the message asking an admin to start battle royale
+ */
+/datum/tgui_panel/proc/give_br_popup()
+ if(!is_ready())
+ return
+ window.send_message("stat/alertBr")
+
+/**
+ * public
+ *
+ * Clears the message asking an admin to start battle royale
+ */
+/datum/tgui_panel/proc/clear_br_popup()
+ if(!is_ready())
+ return
+ window.send_message("stat/clearAlertBr", list())
diff --git a/code/modules/tgui_panel/telemetry.dm b/code/modules/tgui_panel/telemetry.dm
index 79087d8500cbd..bb5de4c1e6c3b 100644
--- a/code/modules/tgui_panel/telemetry.dm
+++ b/code/modules/tgui_panel/telemetry.dm
@@ -3,16 +3,7 @@
* SPDX-License-Identifier: MIT
*/
-/**
- * Maximum number of connection records allowed to analyze.
- * Should match the value set in the browser.
- */
-#define TGUI_TELEMETRY_MAX_CONNECTIONS 5
-
-/**
- * Maximum time allocated for sending a telemetry packet.
- */
-#define TGUI_TELEMETRY_RESPONSE_WINDOW 30 SECONDS
+/// Moved to _DEFINES/tgui.dm
/// Time of telemetry request
/datum/tgui_panel/var/telemetry_requested_at
@@ -20,7 +11,10 @@
/datum/tgui_panel/var/telemetry_analyzed_at
/// List of previous client connections
/datum/tgui_panel/var/list/telemetry_connections
-
+/// Telemetry Status
+/datum/tgui_panel/var/telemetry_status = TGUI_TELEMETRY_STAT_NOT_REQUESTED
+/// Telemetry Notices
+/datum/tgui_panel/var/list/telemetry_notices
/**
* private
*
@@ -29,6 +23,7 @@
/datum/tgui_panel/proc/request_telemetry()
telemetry_requested_at = world.time
telemetry_analyzed_at = null
+ telemetry_status = TGUI_TELEMETRY_STAT_AWAITING
window.send_message("telemetry/request", list(
"limits" = list(
"connections" = TGUI_TELEMETRY_MAX_CONNECTIONS,
@@ -43,13 +38,21 @@
* Is currently only useful for detecting ban evasion attempts.
*/
/datum/tgui_panel/proc/analyze_telemetry(payload)
+ if(telemetry_status == TGUI_TELEMETRY_STAT_OVERSEND)
+ return //Already noted for oversend, just fuck off.
if(world.time > telemetry_requested_at + TGUI_TELEMETRY_RESPONSE_WINDOW)
message_admins("[key_name(client)] sent telemetry outside of the allocated time window.")
+ if(telemetry_status == TGUI_TELEMETRY_STAT_ANALYZED) //Hey we already have a packet from you!
+ LAZYSET(telemetry_notices, "TELEM_OVERSEND", "OVER_SEND|Telemetry was sent multiple times.")
+ telemetry_status = TGUI_TELEMETRY_STAT_OVERSEND
return
if(telemetry_analyzed_at)
message_admins("[key_name(client)] sent telemetry more than once.")
+ LAZYSET(telemetry_notices, "TELEM_OVERSEND", "OVER_SEND|Telemetry was sent multiple times.")
+ telemetry_status = TGUI_TELEMETRY_STAT_OVERSEND
return
telemetry_analyzed_at = world.time
+ telemetry_status = TGUI_TELEMETRY_STAT_ANALYZED
if(!payload)
return
telemetry_connections = payload["connections"]
@@ -60,21 +63,118 @@
message_admins("[key_name(client)] was kicked for sending a huge telemetry payload")
qdel(client)
return
- var/list/found
+ if(len < TGUI_TELEMETRY_MAX_CONNECTIONS)
+ if(len < (TGUI_TELEMETRY_MAX_CONNECTIONS * 0.5))
+ LAZYSET(telemetry_notices, "TELEMETRY_NONMAXCON", "TOO_SHORT|User only has ([len]) records. Data may be extremely unreliable.")
+ else
+ LAZYSET(telemetry_notices, "TELEMETRY_NONMAXCON", "UNDER_MAX|User has less than [TGUI_TELEMETRY_MAX_CONNECTIONS] entries in history ([len]).")
+
+ //Process the data.
+ var/list/first_found_ban
+ var/list/all_ckeys
+ var/list/all_cids
+ var/list/all_ips
+ var/skipped_entries
+ var/has_dev_ip
for(var/i in 1 to len)
if(QDELETED(client))
// He got cleaned up before we were done
return
var/list/row = telemetry_connections[i]
+ // Check for guest keys, these objects are probably either banned by default or are "corrupt" (Missing an address).
+ if("guest[row["computer_id"]]" == row["ckey"])
+ LAZYSET(telemetry_notices, "TELEM_GUEST_[i]", "CONN_ID:[i]|Entry is a guest user. This entry has been skipped.")
+ skipped_entries++
+ LAZYADD(all_cids, row["computer_id"])
+ continue
// Check for a malformed history object
- if (!row || row.len < 3 || (!row["ckey"] || !row["address"] || !row["computer_id"]))
- return
- if (world.IsBanned(row["ckey"], row["address"], row["computer_id"], real_bans_only = TRUE))
- found = row
- break
+ if(!row || row.len < 3 || (!row["ckey"] || !row["address"] || !row["computer_id"]))
+ if(row && row["ckey"] && row["computer_id"]) //Is the address the only invalid field?
+ LAZYSET(telemetry_notices, "TELEM_NOADDR_[i]", "CONN_ID:[i]|Entry has no address. User may be a developer.")
+ LAZYADD(all_ckeys, row["ckey"])
+ LAZYADD(all_ips, "127.0.0.1")
+ LAZYADD(all_cids, row["computer_id"])
+ has_dev_ip = 1
+ continue
+ LAZYSET(telemetry_notices, "TELEM_CORRUPT_[i]", "CONN_ID:[i]|Entry corrupt. Data may be damaged or tampered with.")
+ skipped_entries++
+ continue
+ //Check for bans.
+ if(world.IsBanned(row["ckey"], row["address"], row["computer_id"], "tgui_telemetry", real_bans_only = TRUE))
+ if(!first_found_ban)
+ first_found_ban = row
+ LAZYSET(telemetry_notices,"TELEM_BANNED_[i]", "CONN_ID:[i]|BANNED ACCOUNT IN HISTORY! Matched: [row["ckey"]], [row["address"]], [row["computer_id"]]")
+ //Check for protected CIDs
+ if(config.protected_cids.Find(row["computer_id"]))
+ LAZYSET(telemetry_notices, "TELEM_PROTECTED_[i]", "CONN_ID:[i]|[row["computer_id"]] is protected, Reason: [config.protected_cids[row["computer_id"]]]")
+ //Track changes.
+ LAZYADD(all_ckeys, row["ckey"])
+ LAZYADD(all_ips, row["address"])
+ LAZYADD(all_cids, row["computer_id"])
CHECK_TICK
- // This fucker has a history of playing on a banned account.
- if(found)
- var/msg = "[key_name(client)] has a banned account in connection history! (Matched: [found["ckey"]], [found["address"]], [found["computer_id"]])"
+ // At least one ban
+ //Subtract the amount of skipped entries from len to account for possibly corrupt data
+ len -= skipped_entries
+ if(first_found_ban)
+ var/msg = "[key_name(client)] has a banned account in connection history! (Matched: [first_found_ban["ckey"]], [first_found_ban["address"]], [first_found_ban["computer_id"]])"
message_admins(msg)
log_admin_private(msg)
+ all_ckeys = uniqueList(all_ckeys)
+ all_ips = uniqueList(all_ips)
+ all_cids = uniqueList(all_cids)
+ switch(length(all_ckeys))
+ if(2)
+ LAZYSET(telemetry_notices, TGUI_TELEM_CKEY_WARNING, "KEY_COUNT|User has more than one CKEY in history.")
+ if(3 to INFINITY)
+ if(length(all_ckeys) == len)
+ LAZYSET(telemetry_notices, TGUI_TELEM_CKEY_WARNING, "KEY_COUNT|EVERY ENTRY IN HISTORY HAS A DIFFERENT CKEY!")
+ else
+ LAZYSET(telemetry_notices, TGUI_TELEM_CKEY_WARNING, "KEY_COUNT|User has multiple CKEYs in history!")
+ if(telemetry_notices?[TGUI_TELEM_CKEY_WARNING]) //Has a CKEY warning
+ var/text_list_ckeys = ""
+ var/first = 1
+ for(var/entry in all_ckeys)
+ text_list_ckeys += "[first ? null : ","][entry]"
+ first = 0
+ LAZYSET(telemetry_notices, "TGUI_CKEY_LIST", "ALL_CKEYS|[text_list_ckeys]")
+ switch(length(all_ips))
+ if(2)
+ if(!has_dev_ip) //If it's a dev IP we don't care.
+ LAZYSET(telemetry_notices, TGUI_TELEM_IP_WARNING, "IPA_COUNT|User has changed IPs at least once.")
+ if(3 to INFINITY)
+ if(length(all_ips) == len)
+ LAZYSET(telemetry_notices, TGUI_TELEM_IP_WARNING, "IPA_COUNT|All IPs different. VPN Likely.")
+ else
+ LAZYSET(telemetry_notices, TGUI_TELEM_IP_WARNING, "IPA_COUNT|User has changed IPs at least once.")
+ switch(length(all_cids))
+ if(2)
+ LAZYSET(telemetry_notices, TGUI_TELEM_CID_WARNING, "CID_COUNT|User has changed CIDs once.")
+ if(3 to INFINITY)
+ if(length(all_cids) == len)
+ LAZYSET(telemetry_notices, TGUI_TELEM_CID_WARNING, "CID_COUNT|EVERY ENTRY IN HISTORY HAS A DIFFERENT CID!")
+ else
+ LAZYSET(telemetry_notices, TGUI_TELEM_CID_WARNING, "CID_COUNT|User has more than two CIDs in history.")
+
+/// Render the stats to PP
+/datum/tgui_panel/proc/show_notices()
+ //Yes this code was in fact just dragged out and thrown in a different file.
+ . += " Telemetry Status:"
+ switch(telemetry_status)
+ if(TGUI_TELEMETRY_STAT_NOT_REQUESTED)
+ . += "Telemetry Request Not Sent. Call a coder."
+ if(TGUI_TELEMETRY_STAT_AWAITING)
+ . += "Telemetry Awaiting."
+ if(TGUI_TELEMETRY_STAT_ANALYZED, TGUI_TELEMETRY_STAT_OVERSEND)
+ . += "Analyzed Successfully."
+ . += " Telemetry Alerts:"
+ if(!length(telemetry_notices))
+ . += "No Alerts."
+ return
+ . += "
"
+ for(var/notice in telemetry_notices)
+ . += "
[telemetry_notices[notice]]
"
+ . += "
"
+ if(TGUI_TELEMETRY_STAT_MISSING)
+ . += "Telemetry Data Missing!"
+ else
+ . += "Telemetry datum in invalid state ID [isnum(telemetry_status) ? telemetry_status : "!!NAN!!, CALL A CODER"]. Call a coder."
diff --git a/code/modules/tgui_panel/tgui_panel.dm b/code/modules/tgui_panel/tgui_panel.dm
index fb6afac13c7d1..4449edf61a595 100644
--- a/code/modules/tgui_panel/tgui_panel.dm
+++ b/code/modules/tgui_panel/tgui_panel.dm
@@ -37,6 +37,9 @@
* Initializes tgui panel.
*/
/datum/tgui_panel/proc/initialize(force = FALSE)
+ set waitfor = FALSE
+ // Minimal sleep to defer initialization to after client constructor
+ sleep(1)
initialized_at = world.time
// Perform a clean initialization
window.initialize(inline_assets = list(
@@ -44,8 +47,17 @@
get_asset_datum(/datum/asset/simple/tgui_panel),
))
window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/fontawesome))
+ window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/tgfont))
window.send_asset(get_asset_datum(/datum/asset/spritesheet/chat))
+ // Preload assets for /datum/tgui
+ var/datum/asset/asset_tgui = get_asset_datum(/datum/asset/simple/tgui)
+ var/flush_queue = asset_tgui.send(src.client)
+ if(flush_queue)
+ src.client.browse_queue_flush()
+ // Other setup
request_telemetry()
+ // Send verbs
+ set_verb_infomation(client)
addtimer(CALLBACK(src, .proc/on_initialize_timed_out), 5 SECONDS)
/**
@@ -55,7 +67,9 @@
*/
/datum/tgui_panel/proc/on_initialize_timed_out()
// Currently does nothing but sending a message to old chat.
- SEND_TEXT(client, "Failed to load fancy chat, reverting to old chat. Certain features won't work.")
+ SEND_TEXT(client, "Failed to load fancy chat, click HERE to attempt to reload it.")
+ log_tgui("ERROR: [client?.ckey] failed to load their fancy chat after a 5 second timeout when loading.")
+ SEND_TEXT(client, "If the problem persists after fix-chat, try restarting your game as Byond can get confused if the stylesheet it was expecting has changed. (If you have recently played on a server not using TGchat).")
/**
* private
@@ -85,6 +99,8 @@
if(type == "telemetry")
analyze_telemetry(payload)
return TRUE
+ if(cmptext(copytext(type, 1, 5), "stat"))
+ return handle_stat_message(type, payload)
/**
* public
diff --git a/code/modules/tooltip/tooltip.dm b/code/modules/tooltip/tooltip.dm
index a4012a3532726..fbc10b510fc6a 100644
--- a/code/modules/tooltip/tooltip.dm
+++ b/code/modules/tooltip/tooltip.dm
@@ -13,7 +13,7 @@ Configuration:
Usage:
- Define mouse event procs on your (probably HUD) object and simply call the show and hide procs respectively:
- /obj/screen/hud
+ /atom/movable/screen/hud
MouseEntered(location, control, params)
usr.client.tooltip.show(params, title = src.name, content = src.desc)
diff --git a/code/modules/turbolift/turbolift_areas.dm b/code/modules/turbolift/turbolift_areas.dm
index 83fb464226555..903275d320725 100644
--- a/code/modules/turbolift/turbolift_areas.dm
+++ b/code/modules/turbolift/turbolift_areas.dm
@@ -1,11 +1,11 @@
/area/shuttle/turbolift //Only use subtypes of this area
requires_power = FALSE //no APCS in the lifts please
- ambient_effects = list('sound/effects/turbolift/elevatormusic.ogg')
+ ambientsounds = list('sound/effects/turbolift/elevatormusic.ogg')
/area/shuttle/turbolift/shaft //What the shuttle leaves behind
name = "turbolift shaft"
requires_power = TRUE
- ambient_effects = MAINTENANCE
+ ambience_index = AMBIENCE_MAINT
/area/shuttle/turbolift/primary
name = "primary turbolift"
diff --git a/code/modules/turbolift/turbolift_machines.dm b/code/modules/turbolift/turbolift_machines.dm
index b2bf5a750886c..ffb3f614a8b2d 100644
--- a/code/modules/turbolift/turbolift_machines.dm
+++ b/code/modules/turbolift/turbolift_machines.dm
@@ -83,12 +83,14 @@ GLOBAL_LIST_EMPTY(turbolifts)
if(locked)
return
locked = TRUE
+ wires.ui_update()
update_icon()
/obj/machinery/door/airlock/turbolift/unbolt()
if(!locked)
return
locked = FALSE
+ wires.ui_update()
update_icon()
/obj/machinery/door/airlock/turbolift/Initialize()
@@ -277,14 +279,14 @@ GLOBAL_LIST_EMPTY(turbolifts)
var/obj/docking_port/stationary/turbolift/dest = SSshuttle.getDock(destID)
if(!dest)
- warning("This code shouldnt ever run, a turbolift has attempted to go to a dock with id [destID] but none were found")
+ warning("This code shouldn't ever run, a turbolift has attempted to go to a dock with id [destID] but none were found")
return //shouldnt ever get to this point but w/e
if(dest.z == src.z)
- return //this normally shouldnt run either but out of date interfaces might get here
+ return //this normally shouldn't run either but out of date interfaces might get here
if(dest.id in destination_queue)
- return //again shouldnt ever run but out of date interfaces
+ return //again shouldn't ever run but out of date interfaces
destination_queue += dest.id
. = TRUE //we have an update now
@@ -301,3 +303,4 @@ GLOBAL_LIST_EMPTY(turbolifts)
if(!ui)
ui = new(user, src, "TurboLift")
ui.open()
+ ui.set_autoupdate(TRUE)
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
index a0352db0dddc7..39e97ddba52ae 100644
--- a/code/modules/unit_tests/_unit_tests.dm
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -1,14 +1,56 @@
//include unit test files in this module in this ifdef
//Keep this sorted alphabetically
-#ifdef UNIT_TESTS
+#if defined(UNIT_TESTS) || defined(SPACEMAN_DMM)
+
+/// Asserts that a condition is true
+/// If the condition is not true, fails the test
+#define TEST_ASSERT(assertion, reason) if (!(assertion)) { return Fail("Assertion failed: [reason || "No reason"]") }
+
+/// Asserts that the two parameters passed are equal, fails otherwise
+/// Optionally allows an additional message in the case of a failure
+#define TEST_ASSERT_EQUAL(a, b, message) do { \
+ var/lhs = ##a; \
+ var/rhs = ##b; \
+ if (lhs != rhs) { \
+ return Fail("Expected [isnull(lhs) ? "null" : lhs] to be equal to [isnull(rhs) ? "null" : rhs].[message ? " [message]" : ""]"); \
+ } \
+} while (FALSE)
+
+/// Asserts that the two parameters passed are not equal, fails otherwise
+/// Optionally allows an additional message in the case of a failure
+#define TEST_ASSERT_NOTEQUAL(a, b, message) do { \
+ var/lhs = ##a; \
+ var/rhs = ##b; \
+ if (lhs == rhs) { \
+ return Fail("Expected [isnull(lhs) ? "null" : lhs] to not be equal to [isnull(rhs) ? "null" : rhs].[message ? " [message]" : ""]"); \
+ } \
+} while (FALSE)
+
+/// *Only* run the test provided within the parentheses
+/// This is useful for debugging when you want to reduce noise, but should never be pushed
+/// Intended to be used in the manner of `TEST_FOCUS(/datum/unit_test/math)`
+#define TEST_FOCUS(test_path) ##test_path { focus = TRUE; }
+
+/// A trait source when adding traits through unit tests
+#define TRAIT_SOURCE_UNIT_TESTS "unit_tests"
+
#include "anchored_mobs.dm"
#include "component_tests.dm"
+#include "dynamic_ruleset_sanity.dm"
#include "reagent_id_typos.dm"
#include "reagent_recipe_collisions.dm"
#include "spawn_humans.dm"
#include "species_whitelists.dm"
#include "subsystem_init.dm"
+#include "subsystem_metric_sanity.dm"
+#include "tgui_create_message.dm"
#include "timer_sanity.dm"
#include "unit_test.dm"
+#include "random_ruin_mapsize.dm"
+
+#undef TEST_ASSERT
+#undef TEST_ASSERT_EQUAL
+#undef TEST_ASSERT_NOTEQUAL
+#undef TEST_FOCUS
#endif
diff --git a/code/modules/unit_tests/dynamic_ruleset_sanity.dm b/code/modules/unit_tests/dynamic_ruleset_sanity.dm
new file mode 100644
index 0000000000000..9fff5daf9b241
--- /dev/null
+++ b/code/modules/unit_tests/dynamic_ruleset_sanity.dm
@@ -0,0 +1,14 @@
+/// Verifies that roundstart dynamic rulesets are setup properly without external configuration.
+/datum/unit_test/dynamic_roundstart_ruleset_sanity
+
+/datum/unit_test/dynamic_roundstart_ruleset_sanity/Run()
+ for (var/_ruleset in subtypesof(/datum/dynamic_ruleset/roundstart))
+ var/datum/dynamic_ruleset/roundstart/ruleset = _ruleset
+
+ var/has_scaling_cost = initial(ruleset.scaling_cost)
+ var/is_lone = initial(ruleset.flags) & (LONE_RULESET | HIGH_IMPACT_RULESET)
+
+ if (has_scaling_cost && is_lone)
+ Fail("[ruleset] has a scaling_cost, but is also a lone/highlander ruleset.")
+ else if (!has_scaling_cost && !is_lone)
+ Fail("[ruleset] has no scaling cost, but is also not a lone/highlander ruleset.")
diff --git a/code/modules/unit_tests/random_ruin_mapsize.dm b/code/modules/unit_tests/random_ruin_mapsize.dm
new file mode 100644
index 0000000000000..4a57c4081d4a5
--- /dev/null
+++ b/code/modules/unit_tests/random_ruin_mapsize.dm
@@ -0,0 +1,12 @@
+/// Verifies that roundstart dynamic rulesets are setup properly without external configuration.
+/datum/unit_test/random_ruin_mapsize
+
+/datum/unit_test/random_ruin_mapsize/Run()
+ load_ruin_parts()
+ if(!length(GLOB.loaded_ruin_parts))
+ Fail("Ruin maps failed to load")
+ for(var/datum/map_template/ruin_part/part in GLOB.loaded_ruin_parts)
+ if((part.width - 1) % 4 != 0)
+ Fail("Ruin [part.file_name] width is not of size 4N+1")
+ if((part.height - 1) % 4 != 0)
+ Fail("Ruin [part.file_name] height is not of size 4N+1")
diff --git a/code/modules/unit_tests/subsystem_metric_sanity.dm b/code/modules/unit_tests/subsystem_metric_sanity.dm
new file mode 100644
index 0000000000000..44e375b7535b1
--- /dev/null
+++ b/code/modules/unit_tests/subsystem_metric_sanity.dm
@@ -0,0 +1,22 @@
+// Unit test to ensure SS metrics are valid
+/datum/unit_test/subsystem_metric_sanity/Run()
+ for(var/datum/controller/subsystem/SS in Master.subsystems)
+ if(SS.ss_id == initial(SS.ss_id)) // initial() works here because ss_id is set at runtime during /New()
+ Fail("[SS.type] has no SS ID, somehow!")
+ continue
+ var/list/data = SS.get_metrics()
+ if(length(data) != 3)
+ Fail("SS[SS.ss_id] has invalid metrics data!")
+ continue
+ if(isnull(data["cost"]))
+ Fail("SS[SS.ss_id] has invalid metrics data! No 'cost' found in [json_encode(data)]")
+ continue
+ if(isnull(data["tick_usage"]))
+ Fail("SS[SS.ss_id] has invalid metrics data! No 'tick_usage' found in [json_encode(data)]")
+ continue
+ if(isnull(data["custom"]))
+ Fail("SS[SS.ss_id] has invalid metrics data! No 'custom' found in [json_encode(data)]")
+ continue
+ if(!islist(data["custom"]))
+ Fail("SS[SS.ss_id] has invalid metrics data! 'custom' is not a list in [json_encode(data)]")
+ continue
diff --git a/code/modules/unit_tests/tgui_create_message.dm b/code/modules/unit_tests/tgui_create_message.dm
new file mode 100644
index 0000000000000..4d5a4bc0a0263
--- /dev/null
+++ b/code/modules/unit_tests/tgui_create_message.dm
@@ -0,0 +1,28 @@
+/// Test that `TGUI_CREATE_MESSAGE` is correctly implemented
+/datum/unit_test/tgui_create_message
+
+/datum/unit_test/tgui_create_message/Run()
+ var/type = "something/here"
+ var/list/payload = list(
+ "name" = "Terry McTider",
+ "heads_caved" = 100,
+ "accomplishments" = list(
+ "nothing",
+ "literally nothing",
+ list(
+ "something" = "just kidding",
+ ),
+ ),
+ )
+
+ var/message = TGUI_CREATE_MESSAGE(type, payload)
+
+ // Ensure consistent output to compare by performing a round-trip.
+ var/output = json_encode(json_decode(url_decode(message)))
+
+ var/expected = json_encode(list(
+ "type" = type,
+ "payload" = payload,
+ ))
+
+ TEST_ASSERT_EQUAL(expected, output, "TGUI_CREATE_MESSAGE didn't round trip properly")
diff --git a/code/modules/uplink/uplink_devices.dm b/code/modules/uplink/uplink_devices.dm
index 524895ce7314a..a50313adf73df 100644
--- a/code/modules/uplink/uplink_devices.dm
+++ b/code/modules/uplink/uplink_devices.dm
@@ -30,6 +30,7 @@
. = ..()
var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink)
hidden_uplink.name = "debug uplink"
+ hidden_uplink.debug = TRUE
/obj/item/uplink/incursion/Initialize(mapload, owner, tc_amount = 20)
. = ..()
@@ -50,6 +51,7 @@
var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink)
hidden_uplink.set_gamemode(/datum/game_mode/nuclear)
hidden_uplink.name = "debug nuclear uplink"
+ hidden_uplink.debug = TRUE
/obj/item/uplink/nuclear_restricted/Initialize()
. = ..()
diff --git a/code/modules/uplink/uplink_items.dm b/code/modules/uplink/uplink_items.dm
index 65b16352c9b43..3db41d4f65216 100644
--- a/code/modules/uplink/uplink_items.dm
+++ b/code/modules/uplink/uplink_items.dm
@@ -25,8 +25,9 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
if(!filtered_uplink_items[I.category])
filtered_uplink_items[I.category] = list()
filtered_uplink_items[I.category][I.name] = I
- if(I.limited_stock < 0 && !I.cant_discount && I.item && I.cost > 1)
+ if(I.limited_stock < 0 && !I.cant_discount && I.item)
sale_items += I
+
if(allow_sales)
var/datum/team/nuclear/nuclear_team
if (gamemode == /datum/game_mode/nuclear) // uplink code kind of needs a redesign
@@ -63,20 +64,47 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
for (var/i in 1 to num)
var/datum/uplink_item/I = pick_n_take(sale_items)
var/datum/uplink_item/A = new I.type
- var/discount = A.get_discount()
var/list/disclaimer = list("Void where prohibited.", "Not recommended for children.", "Contains small parts.", "Check local laws for legality in region.", "Do not taunt.", "Not responsible for direct, indirect, incidental or consequential damages resulting from any defect, error or failure to perform.", "Keep away from fire or flames.", "Product is provided \"as is\" without any implied or expressed warranties.", "As seen on TV.", "For recreational use only.", "Use only as directed.", "16% sales tax will be charged for orders originating within Space Nebraska.")
A.limited_stock = limited_stock
+ A.category = category_name
I.refundable = FALSE //THIS MAN USES ONE WEIRD TRICK TO GAIN FREE TC, CODERS HATES HIM!
A.refundable = FALSE
- if(A.cost >= 20) //Tough love for nuke ops
- discount *= 0.5
- A.category = category_name
- A.cost = max(round(A.cost * discount),1)
- A.name += " ([round(((initial(A.cost)-A.cost)/initial(A.cost))*100)]% off!)"
- A.desc += " Normally costs [initial(A.cost)] TC. All sales final. [pick(disclaimer)]"
+ switch(rand(1, 5))
+ if(1 to 3 || A.cost == 1)
+ if(A.cost <= 3)
+ //Bulk discount
+ var/count = rand(3,7)
+ var/discount = A.get_discount()
+ A.name += " (Bulk discount - [count] for [((1-discount)*100)]% off!)"
+ A.cost = max(round(A.cost*count*discount), 1)
+ A.desc += " Normally costs [initial(A.cost)*count] TC. All sales final. [pick(disclaimer)]"
+ A.spawn_amount = count
+ else
+ //X% off!
+ var/discount = A.get_discount()
+ if(A.cost >= 20) //Tough love for nuke ops
+ discount *= 0.5
+ A.cost = max(round(A.cost * discount), 1)
+ A.name += " ([round(((initial(A.cost)-A.cost)/initial(A.cost))*100)]% off!)"
+ A.desc += " Normally costs [initial(A.cost)] TC. All sales final. [pick(disclaimer)]"
+ if(4)
+ //Buy 1 get 1 free!
+ A.name += " (Buy 1 get 1 free!)"
+ A.desc += " Obtain 2 for the price of 1. All sales final. [pick(disclaimer)]"
+ A.spawn_amount = 2
+ if(5)
+ //Get 2 items with their combined price reduced.
+ var/datum/uplink_item/second_I = pick_n_take(sale_items)
+ var/total_cost = second_I.cost + I.cost
+ var/discount = A.get_discount()
+ var/final_cost = max(round(total_cost * discount), 1)
+ //Setup the item
+ A.cost = final_cost
+ A.name += " + [second_I.name] (Discounted Bundle - [100-(round((final_cost / total_cost)*100))]% off!)"
+ A.desc += " Also contains [second_I.name]. Normally costs [total_cost] TC when bought together. All sales final. [pick(disclaimer)]"
+ A.bonus_items = list(second_I.item)
A.discounted = TRUE
A.item = I.item
-
uplink_items[category_name][A.name] = A
@@ -96,7 +124,6 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
var/refund_amount = 0 // specified refund amount in case there needs to be a TC penalty for refunds.
var/refundable = FALSE
var/surplus = 100 // Chance of being included in the surplus crate.
- var/surplus_nullcrates //Chance of being included in null crates. null = pull from surplus
var/cant_discount = FALSE
var/limited_stock = -1 //Setting this above zero limits how many times this item can be bought by the same traitor in a round, -1 is unlimited
var/list/include_modes = list() // Game modes to allow this item in.
@@ -108,19 +135,24 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
var/list/restricted_species //Limits items to a specific species. Hopefully.
var/illegal_tech = TRUE // Can this item be deconstructed to unlock certain techweb research nodes?
var/discounted = FALSE
-
-/datum/uplink_item/New()
- . = ..()
- if(isnull(surplus_nullcrates))
- surplus_nullcrates = surplus
+ var/spawn_amount = 1 //How many times we should run the spawn
+ var/bonus_items = null //Bonus items you gain if you purchase it
/datum/uplink_item/proc/get_discount()
return pick(4;0.75,2;0.5,1;0.25)
/datum/uplink_item/proc/purchase(mob/user, datum/component/uplink/U)
- var/atom/A = spawn_item(item, user, U)
- if(purchase_log_vis && U.purchase_log)
- U.purchase_log.LogPurchase(A, src, cost)
+ //Spawn base items
+ for(var/i in 1 to spawn_amount)
+ var/atom/A = spawn_item(item, user, U)
+ if(purchase_log_vis && U.purchase_log)
+ U.purchase_log.LogPurchase(A, src, cost)
+ //Spawn bonust items
+ if(islist(bonus_items))
+ for(var/bonus in bonus_items)
+ var/atom/A = spawn_item(bonus, user, U)
+ if(purchase_log_vis && U.purchase_log)
+ U.purchase_log.LogPurchase(A, src, cost)
/datum/uplink_item/proc/spawn_item(spawn_path, mob/user, datum/component/uplink/U)
if(!spawn_path)
@@ -130,11 +162,14 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
A = new spawn_path(get_turf(user))
else
A = spawn_path
- if(ishuman(user) && istype(A, /obj/item))
- var/mob/living/carbon/human/H = user
- if(H.put_in_hands(A))
- to_chat(H, "[A] materializes into your hands!")
- return A
+ if(istype(A, /obj/item))
+ var/obj/item/I = A
+ I.item_flags |= ILLEGAL
+ if(ishuman(user))
+ var/mob/living/carbon/human/H = user
+ if(H.put_in_hands(A))
+ to_chat(H, "[A] materializes into your hands!")
+ return A
to_chat(user, "[A] materializes onto the floor.")
return A
@@ -210,7 +245,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
you'll be granted your own contract uplink embedded within the supplied tablet computer. Additionally, you'll be granted \
standard contractor gear to help with your mission - comes supplied with the tablet, specialised space suit, chameleon jumpsuit and mask, \
agent card, specialised contractor baton, and three randomly selected low cost items. Can include otherwise unobtainable items."
- item = /obj/item/storage/box/syndicate/contract_kit
+ item = /obj/item/storage/box/syndie_kit/contract_kit
cost = 20
player_minimum = 15
exclude_modes = list(/datum/game_mode/nuclear, /datum/game_mode/nuclear/clown_ops, /datum/game_mode/incursion)
@@ -220,15 +255,15 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
desc = "Syndicate Bundles, also known as Syndi-Kits, are specialized groups of items that arrive in a plain box. \
These items are collectively worth more than 20 telecrystals, but you do not know which specialization \
you will receive. May contain discontinued and/or exotic items."
- item = /obj/item/storage/box/syndicate/bundle_A
+ item = /obj/item/storage/box/syndie_kit/bundle_A
cost = 20
exclude_modes = list(/datum/game_mode/nuclear)
/datum/uplink_item/bundles_TC/bundle_B
name = "Syndi-kit Special"
desc = "Syndicate Bundles, also known as Syndi-Kits, are specialized groups of items that arrive in a plain box. \
- In Syndi-kit Special, you will recieve items used by famous syndicate agents of the past. Collectively worth more than 20 telecrystals, the syndicate loves a good throwback."
- item = /obj/item/storage/box/syndicate/bundle_B
+ In Syndi-kit Special, you will receive items used by famous syndicate agents of the past. Collectively worth more than 20 telecrystals, the syndicate loves a good throwback."
+ item = /obj/item/storage/box/syndie_kit/bundle_B
cost = 20
exclude_modes = list(/datum/game_mode/nuclear)
@@ -293,10 +328,10 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
to_chat(user, "You feel an overwhelming sense of pride and accomplishment.")
var/obj/item/clothing/mask/joy/funny_mask = new(get_turf(user))
ADD_TRAIT(funny_mask, TRAIT_NODROP, CURSED_ITEM_TRAIT)
- var/obj/item/I = user.get_item_by_slot(SLOT_WEAR_MASK)
+ var/obj/item/I = user.get_item_by_slot(ITEM_SLOT_MASK)
if(I)
user.dropItemToGround(I, TRUE)
- user.equip_to_slot_if_possible(funny_mask, SLOT_WEAR_MASK)
+ user.equip_to_slot_if_possible(funny_mask, ITEM_SLOT_MASK)
else if(index == 20)
starting_crate_value = 200
print_command_report("Congratulations to [user] for being the [rand(4, 9)]th lucky winner of the syndicate lottery! \
@@ -404,7 +439,6 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
/obj/item/pipe_dispenser = 2,
/obj/item/storage/toolbox/syndicate = 2,
/obj/item/storage/toolbox/electrical = 1,
- /obj/item/circuitboard/computer/shuttle/docker = 1,
/obj/item/circuitboard/computer/shuttle/flight_control = 1,
/obj/item/circuitboard/machine/shuttle/engine/plasma = 2,
/obj/item/circuitboard/machine/shuttle/heater = 2,
@@ -483,6 +517,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
throwing weapons. The bolas can knock a target down and the shurikens will embed into limbs."
item = /obj/item/storage/box/syndie_kit/throwing_weapons
cost = 3
+ illegal_tech = FALSE
/datum/uplink_item/dangerous/shotgun
name = "Bulldog Shotgun"
@@ -505,7 +540,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
/datum/uplink_item/dangerous/superechainsaw
name = "Super Energy Chainsaw"
desc = "An incredibly deadly modified chainsaw with plasma-based energy blades instead of metal and a slick black-and-red finish. While it rips apart matter with extreme efficiency, it is heavy, large, and monstrously loud. It's blade has been enhanced to do even more damage and knock victims down briefly."
- item = /obj/item/twohanded/required/chainsaw/energy/doom
+ item = /obj/item/chainsaw/energy/doom
cost = 22
include_modes = list(/datum/game_mode/nuclear)
@@ -513,7 +548,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
name = "Double-Bladed Energy Sword"
desc = "The double-bladed energy sword does slightly more damage than a standard energy sword and will deflect \
all energy projectiles, but requires two hands to wield."
- item = /obj/item/twohanded/dualsaber
+ item = /obj/item/dualsaber
player_minimum = 25
cost = 18
exclude_modes = list(/datum/game_mode/nuclear/clown_ops)
@@ -546,6 +581,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
cost = 4
surplus = 40
include_modes = list(/datum/game_mode/nuclear)
+ illegal_tech = FALSE
/datum/uplink_item/dangerous/rapid
name = "Gloves of the North Star"
@@ -560,7 +596,6 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
item = /obj/item/guardiancreator/tech
cost = 18
surplus = 10
- surplus_nullcrates = 0
exclude_modes = list(/datum/game_mode/nuclear, /datum/game_mode/nuclear/clown_ops)
player_minimum = 25
restricted = TRUE
@@ -579,7 +614,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
desc = "A fully-loaded, specialized three-round burst carbine that fires 5.56mm ammunition from a 30 round magazine \
with a toggleable 40mm underbarrel grenade launcher."
item = /obj/item/gun/ballistic/automatic/m90
- cost = 18
+ cost = 14
surplus = 50
include_modes = list(/datum/game_mode/nuclear)
@@ -614,6 +649,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
item = /obj/item/gun/ballistic/rifle/boltaction
cost = 2
include_modes = list(/datum/game_mode/nuclear)
+ illegal_tech = FALSE
/datum/uplink_item/dangerous/revolver
name = "Syndicate Revolver"
@@ -725,7 +761,6 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
cost = 16
player_minimum = 20
surplus = 10
- surplus_nullcrates = 0
exclude_modes = list(/datum/game_mode/nuclear, /datum/game_mode/nuclear/clown_ops, /datum/game_mode/incursion)
/datum/uplink_item/stealthy_weapons/radbow
@@ -778,6 +813,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
item = /obj/item/storage/box/syndie_kit/romerol
cost = 25
cant_discount = TRUE
+ surplus = 0
/datum/uplink_item/stealthy_weapons/sleepy_pen
name = "Sleepy Pen"
@@ -808,6 +844,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
item = /obj/item/ammo_box/magazine/m10mm
cost = 1
exclude_modes = list(/datum/game_mode/nuclear/clown_ops)
+ illegal_tech = FALSE
/datum/uplink_item/ammo/pistolap
name = "10mm Armour Piercing Magazine"
@@ -816,6 +853,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
item = /obj/item/ammo_box/magazine/m10mm/ap
cost = 2
exclude_modes = list(/datum/game_mode/nuclear/clown_ops)
+ illegal_tech = FALSE
/datum/uplink_item/ammo/pistolhp
name = "10mm Hollow Point Magazine"
@@ -824,6 +862,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
item = /obj/item/ammo_box/magazine/m10mm/hp
cost = 3
exclude_modes = list(/datum/game_mode/nuclear/clown_ops)
+ illegal_tech = FALSE
/datum/uplink_item/ammo/pistolfire
name = "10mm Incendiary Magazine"
@@ -832,10 +871,12 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
item = /obj/item/ammo_box/magazine/m10mm/fire
cost = 2
exclude_modes = list(/datum/game_mode/nuclear/clown_ops)
+ illegal_tech = FALSE
/datum/uplink_item/ammo/shotgun
cost = 2
include_modes = list(/datum/game_mode/nuclear)
+ illegal_tech = FALSE
/datum/uplink_item/ammo/shotgun/bag
name = "12g Ammo Duffel Bag"
@@ -885,11 +926,11 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
illegal_tech = FALSE
/datum/uplink_item/ammo/a40mm
- name = "40mm Grenade"
- desc = "A 40mm HE grenade for use with the M-90gl's under-barrel grenade launcher. \
+ name = "40mm Grenade Box"
+ desc = "A box of 40mm HE grenades for use with the M-90gl's under-barrel grenade launcher. \
Your teammates will ask you to not shoot these down small hallways."
- item = /obj/item/ammo_casing/a40mm
- cost = 2
+ item = /obj/item/ammo_box/a40mm
+ cost = 6
include_modes = list(/datum/game_mode/nuclear)
/datum/uplink_item/ammo/smg/bag
@@ -905,10 +946,12 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
item = /obj/item/ammo_box/magazine/smgm45
cost = 3
include_modes = list(/datum/game_mode/nuclear)
+ illegal_tech = FALSE
/datum/uplink_item/ammo/sniper
cost = 4
include_modes = list(/datum/game_mode/nuclear)
+ illegal_tech = FALSE
/datum/uplink_item/ammo/sniper/basic
name = ".50 Magazine"
@@ -933,13 +976,15 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
desc = "An additional 30-round 5.56mm magazine; suitable for use with the M-90gl carbine. \
These bullets pack less punch than 7.12x82mm rounds, but they still offer more power than .45 ammo."
item = /obj/item/ammo_box/magazine/m556
- cost = 4
+ cost = 3
include_modes = list(/datum/game_mode/nuclear)
+ illegal_tech = FALSE
/datum/uplink_item/ammo/machinegun
cost = 6
surplus = 0
include_modes = list(/datum/game_mode/nuclear)
+ illegal_tech = FALSE
/datum/uplink_item/ammo/machinegun/basic
name = "7.12x82mm Box Magazine"
@@ -966,6 +1011,13 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
mixture that'll ignite anyone struck by the bullet. Some men just want to watch the world burn."
item = /obj/item/ammo_box/magazine/mm712x82/incen
+/datum/uplink_item/ammo/machinegun/match
+ name = "7.12x82mm (Match) Box Magazine"
+ desc = "A 50-round magazine of 7.12x82mm ammunition for use in the L6 SAW; you didn't know there was a demand for match grade \
+ precision bullet hose ammo, but these rounds are finely tuned and perfect for ricocheting off walls all fancy-like."
+ item = /obj/item/ammo_box/magazine/mm712x82/match
+ cost = 10
+
/datum/uplink_item/ammo/rocket
include_modes = list(/datum/game_mode/nuclear)
@@ -988,6 +1040,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
item = /obj/item/ammo_box/magazine/pistolm9mm
cost = 2
include_modes = list(/datum/game_mode/nuclear)
+ illegal_tech = FALSE
/datum/uplink_item/ammo/toydarts
name = "Box of Riot Darts"
@@ -1011,6 +1064,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
item = /obj/item/ammo_box/a762
cost = 1
include_modes = list(/datum/game_mode/nuclear)
+ illegal_tech = FALSE
//Grenades and Explosives
/datum/uplink_item/explosives
@@ -1332,7 +1386,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
surplus = 0
include_modes = list(/datum/game_mode/nuclear/clown_ops)
-datum/uplink_item/stealthy_tools/taeclowndo_shoes
+/datum/uplink_item/stealthy_tools/taeclowndo_shoes
name = "Tae-clown-do Shoes"
desc = "A pair of shoes for the most elite agents of the honkmotherland. They grant the mastery of taeclowndo with some honk-fu moves as long as they're worn."
cost = 12
@@ -1390,10 +1444,10 @@ datum/uplink_item/stealthy_tools/taeclowndo_shoes
cost = 1
/datum/uplink_item/stealthy_tools/jammer
- name = "Radio Jammer"
- desc = "This device will disrupt any nearby outgoing radio communication when activated. Does not affect binary chat."
+ name = "Signal Jammer"
+ desc = "This device will disrupt any nearby outgoing wireless signals when activated."
item = /obj/item/jammer
- cost = 3
+ cost = 5
/datum/uplink_item/stealthy_tools/smugglersatchel
name = "Smuggler's Satchel"
@@ -1402,6 +1456,7 @@ datum/uplink_item/stealthy_tools/taeclowndo_shoes
item = /obj/item/storage/backpack/satchel/flat/with_tools
cost = 1
surplus = 30
+ illegal_tech = FALSE
//Space Suits and Hardsuits
/datum/uplink_item/suits
@@ -1418,8 +1473,8 @@ datum/uplink_item/stealthy_tools/taeclowndo_shoes
/datum/uplink_item/suits/hardsuit
name = "Syndicate Hardsuit"
- desc = "The feared suit of a Syndicate nuclear agent. Features slightly better armoring and a built in jetpack \
- that runs off standard atmospheric tanks. Toggling the suit in and out of \
+ desc = "The feared suit of a Syndicate nuclear agent. Features slightly better armoring, a built in jetpack \
+ that runs off standard atmospheric tanks and an advanced team location system. Toggling the suit in and out of \
combat mode will allow you all the mobility of a loose fitting uniform without sacrificing armoring. \
Additionally the suit is collapsible, making it small enough to fit within a backpack. \
Nanotrasen crew who spot these suits are known to panic."
@@ -1427,6 +1482,19 @@ datum/uplink_item/stealthy_tools/taeclowndo_shoes
cost = 7
exclude_modes = list(/datum/game_mode/nuclear) //you can't buy it in nuke, because the elite hardsuit costs the same while being better
+/datum/uplink_item/suits/hardsuit/spawn_item(spawn_path, mob/user, datum/component/uplink/U)
+ var/obj/item/clothing/suit/space/hardsuit/suit = ..()
+ var/datum/component/tracking_beacon/beacon = suit.GetComponent(/datum/component/tracking_beacon)
+ var/datum/component/team_monitor/hud = suit.helmet.GetComponent(/datum/component/team_monitor)
+
+ var/datum/antagonist/nukeop/nukie = is_nuclear_operative(user)
+ if(nukie?.nuke_team?.team_frequency)
+ if(hud)
+ hud.set_frequency(nukie.nuke_team.team_frequency)
+ if(beacon)
+ beacon.set_frequency(nukie.nuke_team.team_frequency)
+ return suit
+
/datum/uplink_item/suits/hardsuit/elite
name = "Elite Syndicate Hardsuit"
desc = "An upgraded, elite version of the Syndicate hardsuit. It features fireproofing, and also \
@@ -1476,6 +1544,27 @@ datum/uplink_item/stealthy_tools/taeclowndo_shoes
surplus = 75
restricted = TRUE
+/datum/uplink_item/device_tools/compressionkit
+ name = "Bluespace Compression Kit"
+ desc = "A modified version of a BSRPED that can be used to reduce the size of most items while retaining their original functions! \
+ Does not work on storage items. \
+ Recharge using bluespace crystals. \
+ Comes with 5 charges."
+ item = /obj/item/compressionkit
+ cost = 5
+
+/datum/uplink_item/device_tools/shuttlecapsule
+ name = "Bluespace Shuttle Capsule"
+ desc = "Need a mobile base of operations? Those pesky exploration crews keep flying off? Want to do a hit and run on security? Then this \
+ product is for you! The all new bluespace shuttle capsule contains an ENTIRE shuttle withing a capsule you can hold in your hand! \
+ The shuttle provided is a state-of-the-art ship complete with a hacked autolathe, syndicate toolbox, playing cards for those long journeys, \
+ an in-built shuttle interdictor and a single canister of plasma to fuel your adventures! \
+ This innovative shuttle can seat up to 4 passengers, willing or not! Shuttle must be deployed in space or on lavaland, space suits not included."
+ item = /obj/item/survivalcapsule/shuttle/traitor
+ cost = 8
+ //You get your own shuttle
+ exclude_modes = list(/datum/game_mode/nuclear)
+
/datum/uplink_item/device_tools/magboots
name = "Blood-Red Magboots"
desc = "A pair of magnetic boots with a Syndicate paintjob that assist with freer movement in space or on-station \
@@ -1485,6 +1574,13 @@ datum/uplink_item/stealthy_tools/taeclowndo_shoes
cost = 2
include_modes = list(/datum/game_mode/nuclear, /datum/game_mode/nuclear/clown_ops)
+/datum/uplink_item/device_tools/brainwash_disk
+ name = "Brainwashing Surgery Program"
+ desc = "A disk containing the procedure to perform a brainwashing surgery, allowing you to implant an objective onto a target. \
+ Insert into an Operating Console to enable the procedure."
+ item = /obj/item/disk/surgery/brainwashing
+ cost = 5
+
/datum/uplink_item/device_tools/briefcase_launchpad
name = "Briefcase Launchpad"
desc = "A briefcase containing a launchpad, a device able to teleport items and people to and from targets up to eight tiles away from the briefcase. \
@@ -1520,7 +1616,16 @@ datum/uplink_item/stealthy_tools/taeclowndo_shoes
item = /obj/item/disk/nuclear/fake
cost = 1
surplus = 1
- surplus_nullcrates = 0
+ illegal_tech = FALSE
+
+/datum/uplink_item/device_tools/syndicate_teleporter
+ name = "Experimental Syndicate Teleporter"
+ desc = "The Syndicate teleporter is a handheld device that teleports the user 4-8 meters forward. \
+ Beware, teleporting into a wall will make the teleporter do a parallel emergency teleport, \
+ but if that emergency teleport fails, it will kill you instantly. \
+ Has 4 charges, recharges automatically. Warranty voided if exposed to EMP."
+ item = /obj/item/storage/box/syndie_kit/teleporter
+ cost = 8
/datum/uplink_item/device_tools/frame
name = "F.R.A.M.E. PDA Cartridge"
@@ -1557,6 +1662,16 @@ datum/uplink_item/stealthy_tools/taeclowndo_shoes
multitool and combat gloves that are resistant to shocks and heat."
item = /obj/item/storage/toolbox/syndicate
cost = 1
+ illegal_tech = FALSE
+
+/datum/uplink_item/device_tools/syndie_glue
+ name = "Glue"
+ desc = "A cheap bottle of one use syndicate brand super glue. \
+ Use on any item to make it undroppable. \
+ Be careful not to glue an item you're already holding!"
+ exclude_modes = list(/datum/game_mode/nuclear, /datum/game_mode/nuclear/clown_ops)
+ item = /obj/item/syndie_glue
+ cost = 2
/datum/uplink_item/device_tools/hacked_module
name = "Hacked AI Law Upload Module"
@@ -1571,31 +1686,6 @@ datum/uplink_item/stealthy_tools/taeclowndo_shoes
item = /obj/item/assembly/flash/hypnotic
cost = 7
-/datum/uplink_item/device_tools/compressionkit
- name = "Bluespace Compression Kit"
- desc = "A modified version of a BSRPED that can be used to reduce the size of most items while retaining their original functions! \
- Does not work on storage items. \
- Recharge using bluespace crystals. \
- Comes with 5 charges."
- item = /obj/item/compressionkit
- cost = 5
-
-/datum/uplink_item/device_tools/syndie_glue
- name = "Glue"
- desc = "A cheap bottle of one use syndicate brand super glue. \
- Use on any item to make it undroppable. \
- Be careful not to glue an item you're already holding!"
- exclude_modes = list(/datum/game_mode/nuclear, /datum/game_mode/nuclear/clown_ops)
- item = /obj/item/syndie_glue
- cost = 2
-
-/datum/uplink_item/device_tools/brainwash_disk
- name = "Brainwashing Surgery Program"
- desc = "A disk containing the procedure to perform a brainwashing surgery, allowing you to implant an objective onto a target. \
- Insert into an Operating Console to enable the procedure."
- item = /obj/item/disk/surgery/brainwashing
- cost = 5
-
/datum/uplink_item/device_tools/medgun
name = "Medbeam Gun"
desc = "A wonder of Syndicate engineering, the Medbeam gun, or Medi-Gun enables a medic to keep his fellow \
@@ -1646,13 +1736,14 @@ datum/uplink_item/stealthy_tools/taeclowndo_shoes
item = /obj/item/soap/syndie
cost = 1
surplus = 50
+ illegal_tech = FALSE
/datum/uplink_item/device_tools/surgerybag
name = "Syndicate Surgery Duffel Bag"
desc = "The Syndicate surgery duffel bag is a toolkit containing all surgery tools, surgical drapes, \
a Syndicate brand MMI, a straitjacket, and a muzzle."
item = /obj/item/storage/backpack/duffelbag/syndie/surgery
- cost = 2
+ cost = 3
/datum/uplink_item/device_tools/encryptionkey
name = "Syndicate Encryption Key"
@@ -1673,15 +1764,6 @@ datum/uplink_item/stealthy_tools/taeclowndo_shoes
item = /obj/item/storage/book/bible/syndicate
cost = 3
-/datum/uplink_item/device_tools/thermal
- name = "Thermal Imaging Glasses"
- desc = "These goggles can be turned to resemble common eyewear found throughout the station. \
- They allow you to see organisms through walls by capturing the upper portion of the infrared light spectrum, \
- emitted as heat and light by objects. Hotter objects, such as warm bodies, cybernetic organisms \
- and artificial intelligence cores emit more of this light than cooler objects like walls and airlocks."
- item = /obj/item/clothing/glasses/thermal/syndi
- cost = 3
-
/datum/uplink_item/device_tools/potion
name = "Syndicate Sentience Potion"
item = /obj/item/slimepotion/slime/sentience/nuclear
@@ -1691,7 +1773,6 @@ datum/uplink_item/stealthy_tools/taeclowndo_shoes
include_modes = list(/datum/game_mode/nuclear, /datum/game_mode/nuclear/clown_ops)
restricted = TRUE
-
/datum/uplink_item/device_tools/suspiciousphone
name = "Protocol CRAB-17 Phone"
desc = "The Protocol CRAB-17 Phone, a phone borrowed from an unknown third party, it can be used to crash the space market, funneling the losses of the crew to your bank account.\
@@ -1700,6 +1781,15 @@ datum/uplink_item/stealthy_tools/taeclowndo_shoes
restricted = TRUE
cost = 8
+/datum/uplink_item/device_tools/thermal
+ name = "Thermal Imaging Glasses"
+ desc = "These goggles can be turned to resemble common eyewear found throughout the station. \
+ They allow you to see organisms through walls by capturing the upper portion of the infrared light spectrum, \
+ emitted as heat and light by objects. Hotter objects, such as warm bodies, cybernetic organisms \
+ and artificial intelligence cores emit more of this light than cooler objects like walls and airlocks."
+ item = /obj/item/clothing/glasses/thermal/syndi
+ cost = 3
+
// Implants
/datum/uplink_item/implants
category = "Implants"
@@ -1752,6 +1842,7 @@ datum/uplink_item/stealthy_tools/taeclowndo_shoes
Used just like a regular headset, but can be disabled to use external headsets normally and to avoid detection."
item = /obj/item/storage/box/syndie_kit/imp_radio
cost = 4
+ exclude_modes = list(/datum/game_mode/incursion) //To prevent traitors from immediately outing the hunters to security.
restricted = TRUE
/datum/uplink_item/implants/reviver
@@ -1832,6 +1923,16 @@ datum/uplink_item/stealthy_tools/taeclowndo_shoes
cost = 2
restricted_species = list("plasmaman")
+/datum/uplink_item/race_restricted/tribal_claw
+ name = "Old Tribal Scroll"
+ desc = "We found this scroll in a abandoned lizard settlement of the Knoises clan. \
+ It teaches you how to use your claws and tail to gain an advantage in combat, \
+ don't buy this unless you are a lizard or plan to give it to one as only they can understand the ancient draconic words."
+ item = /obj/item/book/granter/martial/tribal_claw
+ cost = 14
+ surplus = 0
+ restricted_species = list("lizard")
+
// Role-specific items
/datum/uplink_item/role_restricted
category = "Role-Restricted"
@@ -1894,9 +1995,16 @@ datum/uplink_item/stealthy_tools/taeclowndo_shoes
of humanoids. It has two settings: intensity, which controls the power of the radiation, \
and wavelength, which controls the delay before the effect kicks in."
item = /obj/item/healthanalyzer/rad_laser
- restricted_roles = list("Medical Doctor", "Chief Medical Officer", "Roboticist", "Paramedic")
+ restricted_roles = list("Medical Doctor", "Chief Medical Officer", "Roboticist", "Paramedic", "Brig Physician")
cost = 3
+/datum/uplink_item/role_restricted/syndicate_mmi
+ name = "Syndicate MMI"
+ desc = "An MMI which autmatically applies the Syndimov laws to any borg it is placed in. Great for adding known allies to assist you with a little more stealth than a fully emagged borg."
+ item = /obj/item/mmi/syndie
+ restricted_roles = list("Roboticist", "Research Director")
+ cost = 2
+
/datum/uplink_item/role_restricted/upgrade_wand
name = "Upgrade Wand"
desc = "A powerful, single-use wand containing nanomachines that will calibrate the high-tech gadgets commonly employed by magicians to nearly double their potential."
@@ -1904,6 +2012,13 @@ datum/uplink_item/stealthy_tools/taeclowndo_shoes
restricted_roles = list("Stage Magician")
cost = 5
+/datum/uplink_item/role_restricted/floorpill_bottle
+ name = "Bottle of Mystery Pills"
+ desc = "We found these lying around Warehouse R1O-GN, which was decommissioned years ago. We were going to throw them out but we heard you might be interested in them."
+ item = /obj/item/storage/pill_bottle/floorpill/full
+ restricted_roles = list("Assistant", "Debtor")
+ cost = 2
+
/datum/uplink_item/role_restricted/clown_bomb
name = "Clown Bomb"
desc = "The Clown bomb is a hilarious device capable of massive pranks. It has an adjustable timer, \
@@ -1964,7 +2079,7 @@ datum/uplink_item/stealthy_tools/taeclowndo_shoes
item = /obj/item/clothing/shoes/clown_shoes/taeclowndo
restricted_roles = list("Clown")
-datum/uplink_item/role_restricted/superior_honkrender
+/datum/uplink_item/role_restricted/superior_honkrender
name = "Superior Honkrender"
desc = "An ancient artifact recovered from an ancient cave. Opens the way to the Dark Carnival"
item = /obj/item/veilrender/honkrender
@@ -1972,7 +2087,7 @@ datum/uplink_item/role_restricted/superior_honkrender
restricted = TRUE
restricted_roles = list("Clown", "Chaplain")
-datum/uplink_item/role_restricted/superior_honkrender
+/datum/uplink_item/role_restricted/superior_honkrender
name = "Superior Honkrender"
desc = "An ancient artifact recovered from -. Opens the way to TRANSMISSION OFFLINE\
All praise be to the honkmother"
@@ -1999,6 +2114,20 @@ datum/uplink_item/role_restricted/superior_honkrender
restricted_roles = list("Curator")
limited_stock = 1 //please don't spam deadchat
+/datum/uplink_item/role_restricted/voodoo
+ name = "Wicker Doll"
+ desc = "A wicker voodoo doll with a cavity for storing a small item. Once an item has been stored within it, the doll may be used to manipulate the actions of another person that has previously been in contact with the stored item."
+ item = /obj/item/voodoo
+ cost = 12
+ restricted_roles = list("Curator", "Stage Magician")
+
+/datum/uplink_item/role_restricted/prison_cube
+ name = "Prison Cube"
+ desc = "A very strange artifact recovered from a volcanic planet that is useful for keeping people locked away, but not very useful for keeping their disappearance unknown"
+ item = /obj/item/prisoncube
+ cost = 6
+ restricted_roles = list("Curator")
+
/datum/uplink_item/role_restricted/his_grace
name = "His Grace"
desc = "An incredibly dangerous weapon recovered from a station overcome by the grey tide. Once activated, He will thirst for blood and must be used to kill to sate that thirst. \
@@ -2008,7 +2137,7 @@ datum/uplink_item/role_restricted/superior_honkrender
item = /obj/item/his_grace
cost = 20
restricted_roles = list("Chaplain")
- surplus = 5 //Very low chance to get it in a surplus crate even without being the chaplain
+ surplus = 0
/datum/uplink_item/role_restricted/cultconstructkit
name = "Cult Construct Kit"
@@ -2061,7 +2190,7 @@ datum/uplink_item/role_restricted/superior_honkrender
/datum/uplink_item/role_restricted/echainsaw
name = "Energy Chainsaw"
desc = "An incredibly deadly modified chainsaw with plasma-based energy blades instead of metal and a slick black-and-red finish. While it rips apart matter with extreme efficiency, it is heavy, large, and monstrously loud."
- item = /obj/item/twohanded/required/chainsaw/energy
+ item = /obj/item/chainsaw/energy
cost = 10
player_minimum = 25
restricted_roles = list("Botanist", "Cook", "Bartender")
@@ -2096,6 +2225,14 @@ datum/uplink_item/role_restricted/superior_honkrender
restricted_roles = list("Mime")
surplus = 0
+/datum/uplink_item/role_restricted/mimesabrekit
+ name = "Baguette blade bundle"
+ desc = "A very stealthy blade located inside an even stealthier baguette-shaped sheath."
+ cost = 12
+ item = /obj/item/storage/box/syndie_kit/mimesabrekit
+ restricted_roles = list("Mime")
+ surplus = 5
+
/datum/uplink_item/role_restricted/pressure_mod
name = "Kinetic Accelerator Pressure Mod"
desc = "A modification kit which allows Kinetic Accelerators to do greatly increased damage while indoors. \
@@ -2110,7 +2247,7 @@ datum/uplink_item/role_restricted/superior_honkrender
desc = "An implant that grants you a deadly energy saw inside your arm. Comes with a syndicate autosurgeon for immediate self-application."
cost = 8
item = /obj/item/autosurgeon/syndicate/esaw_arm
- restricted_roles = list("Medical Doctor", "Chief Medical Officer", "Paramedic")
+ restricted_roles = list("Medical Doctor", "Chief Medical Officer", "Paramedic", "Brig Physician")
/datum/uplink_item/role_restricted/magillitis_serum
name = "Magillitis Serum Autoinjector"
@@ -2194,6 +2331,7 @@ datum/uplink_item/role_restricted/superior_honkrender
cost = 20
cant_discount = TRUE
illegal_tech = FALSE
+ surplus = 0
/datum/uplink_item/badass/syndiebeer
name = "Syndicate Beer"
@@ -2211,6 +2349,7 @@ datum/uplink_item/role_restricted/superior_honkrender
item = /obj/item/storage/secure/briefcase/syndie
cost = 1
restricted = TRUE
+ illegal_tech = FALSE
/datum/uplink_item/badass/syndiecards
name = "Syndicate Playing Cards"
@@ -2220,6 +2359,7 @@ datum/uplink_item/role_restricted/superior_honkrender
item = /obj/item/toy/cards/deck/syndicate
cost = 1
surplus = 40
+ illegal_tech = FALSE
/datum/uplink_item/badass/syndiecigs
name = "Syndicate Smokes"
@@ -2227,3 +2367,14 @@ datum/uplink_item/role_restricted/superior_honkrender
item = /obj/item/storage/fancy/cigarettes/cigpack_syndicate
cost = 2
illegal_tech = FALSE
+
+/datum/uplink_item/implants/deathrattle
+ name = "Box of Deathrattle Implants"
+ desc = "A collection of implants (and one reusable implanter) that should be injected into the team. When one of the team \
+ dies, all other implant holders recieve a mental message informing them of their teammates' name \
+ and the location of their death. Unlike most implants, these are designed to be implanted \
+ in any creature, biological or mechanical."
+ item = /obj/item/storage/box/syndie_kit/imp_deathrattle
+ cost = 4
+ surplus = 0
+ include_modes = list(/datum/game_mode/nuclear)
diff --git a/code/modules/uplink/uplink_purchase_log.dm b/code/modules/uplink/uplink_purchase_log.dm
index 293191b17049a..318d5cfa6deed 100644
--- a/code/modules/uplink/uplink_purchase_log.dm
+++ b/code/modules/uplink/uplink_purchase_log.dm
@@ -59,6 +59,7 @@ GLOBAL_LIST(uplink_purchase_logs_by_key) //assoc key = /datum/uplink_purchase_lo
UPE.name = uplink_item.name
UPE.base_cost = initial(uplink_item.cost)
UPE.spent_cost = spent_cost
+ UPE.allow_refund = uplink_item.refundable
UPE.amount_purchased++
total_spent += spent_cost
@@ -74,3 +75,4 @@ GLOBAL_LIST(uplink_purchase_logs_by_key) //assoc key = /datum/uplink_purchase_lo
var/base_cost
var/spent_cost
var/name
+ var/allow_refund
diff --git a/code/modules/vehicles/_vehicle.dm b/code/modules/vehicles/_vehicle.dm
index 18bc60bfbb8fa..beaa78a5761e5 100644
--- a/code/modules/vehicles/_vehicle.dm
+++ b/code/modules/vehicles/_vehicle.dm
@@ -4,7 +4,7 @@
icon = 'icons/obj/vehicles.dmi'
icon_state = "fuckyou"
max_integrity = 300
- armor = list("melee" = 30, "bullet" = 30, "laser" = 30, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 60)
+ armor = list("melee" = 30, "bullet" = 30, "laser" = 30, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 60, "stamina" = 0)
density = TRUE
anchored = FALSE
var/list/mob/occupants //mob = bitflags of their control level.
@@ -27,7 +27,7 @@
/obj/vehicle/CanPass(atom/movable/mover, turf/target)
if(istype(mover, /obj/item)) //thrown objects and projectiles bypass vehicles
return 1
- if(HAS_TRAIT(mover, TRAIT_PASSTABLE))
+ if(HAS_TRAIT(mover, TRAIT_PASSTABLE))
return 1
return ..()
diff --git a/code/modules/vehicles/atv.dm b/code/modules/vehicles/atv.dm
index ded15eb76837f..e2f0f0d748322 100644
--- a/code/modules/vehicles/atv.dm
+++ b/code/modules/vehicles/atv.dm
@@ -4,7 +4,7 @@
desc = "An all-terrain vehicle built for traversing rough terrain with ease. One of the few old-Earth technologies that are still relevant on most planet-bound outposts."
icon_state = "atv"
max_integrity = 150
- armor = list("melee" = 50, "bullet" = 25, "laser" = 20, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 60)
+ armor = list("melee" = 50, "bullet" = 25, "laser" = 20, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 60, "stamina" = 0)
key_type = /obj/item/key
integrity_failure = 70
var/static/mutable_appearance/atvcover
@@ -79,10 +79,10 @@
START_PROCESSING(SSobj, src)
return ..()
-/obj/vehicle/ridden/atv/process()
+/obj/vehicle/ridden/atv/process(delta_time)
if(obj_integrity >= integrity_failure)
return PROCESS_KILL
- if(prob(20))
+ if(DT_PROB(10, delta_time))
return
var/datum/effect_system/smoke_spread/smoke = new
smoke.set_up(0, src)
diff --git a/code/modules/vehicles/cars/clowncar.dm b/code/modules/vehicles/cars/clowncar.dm
index f0ab669706f43..65fc8433d75d5 100644
--- a/code/modules/vehicles/cars/clowncar.dm
+++ b/code/modules/vehicles/cars/clowncar.dm
@@ -3,7 +3,7 @@
desc = "How someone could even fit in there is beyond me."
icon_state = "clowncar"
max_integrity = 150
- armor = list("melee" = 70, "bullet" = 40, "laser" = 40, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80)
+ armor = list("melee" = 70, "bullet" = 40, "laser" = 40, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80, "stamina" = 0)
enter_delay = 20
max_occupants = 50
movedelay = 0.6
@@ -67,7 +67,7 @@
/obj/vehicle/sealed/car/clowncar/remove_occupant(mob/M)
. = ..()
if(iscarbon(M))
- var/mob/living/carbon/C = M
+ var/mob/living/carbon/C = M
C.uncuff()
/obj/vehicle/sealed/car/clowncar/Bump(atom/movable/M)
@@ -114,7 +114,7 @@
initialize_controller_action_type(/datum/action/vehicle/sealed/RollTheDice, VEHICLE_CONTROL_DRIVE)
initialize_controller_action_type(/datum/action/vehicle/sealed/Cannon, VEHICLE_CONTROL_DRIVE)
AddComponent(/datum/component/waddling)
-
+
/obj/vehicle/sealed/car/clowncar/Destroy()
playsound(src, 'sound/vehicles/clowncar_fart.ogg', 100)
return ..()
@@ -163,7 +163,7 @@
if(6)
visible_message("[user] has pressed one of the colorful buttons on [src] and the clown car lets out a comedic toot.")
playsound(src, 'sound/vehicles/clowncar_fart.ogg', 100)
- for(var/mob/living/L in orange(loc, 6))
+ for(var/mob/living/L in oviewers(6, loc))
L.emote("laughs")
for(var/mob/living/L in occupants)
L.emote("laughs")
@@ -208,6 +208,8 @@
L.update_mouse_pointer()
/obj/vehicle/sealed/car/clowncar/proc/FireCannon(mob/user, atom/A, params)
+ SIGNAL_HANDLER
+
if(cannonmode && return_controllers_with_flag(VEHICLE_CONTROL_KIDNAPPED).len)
var/mob/living/L = pick(return_controllers_with_flag(VEHICLE_CONTROL_KIDNAPPED))
mob_exit(L, TRUE)
diff --git a/code/modules/vehicles/motorized_wheelchair.dm b/code/modules/vehicles/motorized_wheelchair.dm
index 7a01d75f647ca..326f80319a90a 100644
--- a/code/modules/vehicles/motorized_wheelchair.dm
+++ b/code/modules/vehicles/motorized_wheelchair.dm
@@ -4,9 +4,9 @@
max_integrity = 150
var/speed = 2
var/power_efficiency = 1
- var/power_usage = 100
+ var/power_usage = 25
var/panel_open = FALSE
- var/list/required_parts = list(/obj/item/stock_parts/manipulator,
+ var/list/required_parts = list(/obj/item/stock_parts/manipulator,
/obj/item/stock_parts/manipulator,
/obj/item/stock_parts/capacitor)
var/obj/item/stock_parts/cell/power_cell
@@ -42,7 +42,7 @@
canmove = FALSE
addtimer(VARSET_CALLBACK(src, canmove, TRUE), 20)
return FALSE
- if(power_cell.charge < power_usage / max(power_efficiency, 1))
+ if(power_cell.charge < power_usage / max(power_efficiency, 1))
to_chat(user, "The display on [src] blinks 'Out of Power'.")
canmove = FALSE
addtimer(VARSET_CALLBACK(src, canmove, TRUE), 20)
@@ -74,7 +74,7 @@
to_chat(user, "You remove the power cell from [src].")
return
return ..()
-
+
/obj/vehicle/ridden/wheelchair/motorized/attackby(obj/item/I, mob/user, params)
if(I.tool_behaviour == TOOL_SCREWDRIVER)
I.play_tool_sound(src)
diff --git a/code/modules/vehicles/ridden.dm b/code/modules/vehicles/ridden.dm
index bfd0c4a8445a0..6e86821eba172 100644
--- a/code/modules/vehicles/ridden.dm
+++ b/code/modules/vehicles/ridden.dm
@@ -8,7 +8,7 @@
var/arms_required = 1 //why not?
var/fall_off_if_missing_arms = FALSE //heh...
var/message_cooldown
-
+
/obj/vehicle/ridden/Initialize()
. = ..()
LoadComponent(/datum/component/riding)
@@ -56,7 +56,7 @@
inserted_key.forceMove(drop_location())
user.put_in_hands(inserted_key)
inserted_key = null
- return ..()
+ return
/obj/vehicle/ridden/driver_move(mob/user, direction)
if(key_type && !is_key(inserted_key))
diff --git a/code/modules/vehicles/scooter.dm b/code/modules/vehicles/scooter.dm
index 297f9e5b8ffc8..07e4eacd3bfd5 100644
--- a/code/modules/vehicles/scooter.dm
+++ b/code/modules/vehicles/scooter.dm
@@ -105,7 +105,7 @@
var/atom/throw_target = get_edge_target_turf(H, pick(GLOB.cardinals))
unbuckle_mob(H)
H.throw_at(throw_target, 3, 2)
- var/head_slot = H.get_item_by_slot(SLOT_HEAD)
+ var/head_slot = H.get_item_by_slot(ITEM_SLOT_HEAD)
if(!head_slot || !(istype(head_slot,/obj/item/clothing/head/helmet) || istype(head_slot,/obj/item/clothing/head/hardhat)))
H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 5)
H.updatehealth()
@@ -148,17 +148,21 @@
/obj/vehicle/ridden/scooter/skateboard/MouseDrop(atom/over_object)
. = ..()
- var/mob/living/carbon/M = usr
- if(!istype(M) || M.incapacitated() || !Adjacent(M))
+ var/mob/living/carbon/skater = usr
+ if(!istype(skater))
return
- if(has_buckled_mobs() && over_object == M)
- to_chat(M, "You can't lift this up when somebody's on it.")
- return
- if(over_object == M)
- var/board = new board_item_type(get_turf(M))
- M.put_in_hands(board)
- qdel(src)
+ if (over_object == skater)
+ pick_up_board(skater)
+
+/obj/vehicle/ridden/scooter/skateboard/proc/pick_up_board(mob/living/carbon/skater)
+ if (skater.incapacitated() || !Adjacent(skater))
+ return
+ if(has_buckled_mobs())
+ to_chat(skater, "You can't lift this up when somebody's on it.")
+ return
+ skater.put_in_hands(new board_item_type(get_turf(skater)))
+ qdel(src)
/obj/vehicle/ridden/scooter/skateboard/pro
name = "skateboard"
desc = "A RaDSTORMz brand professional skateboard. Looks a lot more stable than the average board."
@@ -287,7 +291,7 @@
H.throw_at(throw_target, 4, 3)
H.Paralyze(30)
H.adjustStaminaLoss(10)
- var/head_slot = H.get_item_by_slot(SLOT_HEAD)
+ var/head_slot = H.get_item_by_slot(ITEM_SLOT_HEAD)
if(!head_slot || !(istype(head_slot,/obj/item/clothing/head/helmet) || istype(head_slot,/obj/item/clothing/head/hardhat)))
H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1)
H.updatehealth()
diff --git a/code/modules/vehicles/sealed.dm b/code/modules/vehicles/sealed.dm
index 8d4d8229e29fb..1c67796243ef7 100644
--- a/code/modules/vehicles/sealed.dm
+++ b/code/modules/vehicles/sealed.dm
@@ -1,4 +1,5 @@
/obj/vehicle/sealed
+ flags_1 = PREVENT_CONTENTS_EXPLOSION_1
var/enter_delay = 20
var/mouse_pointer
diff --git a/code/modules/vehicles/secway.dm b/code/modules/vehicles/secway.dm
index bfb451f68a721..43665d5a94b39 100644
--- a/code/modules/vehicles/secway.dm
+++ b/code/modules/vehicles/secway.dm
@@ -4,7 +4,7 @@
desc = "A brave security cyborg gave its life to help you look like a complete tool."
icon_state = "secway"
max_integrity = 100
- armor = list("melee" = 20, "bullet" = 15, "laser" = 10, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 60)
+ armor = list("melee" = 20, "bullet" = 15, "laser" = 10, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 60, "stamina" = 0)
key_type = /obj/item/key/security
integrity_failure = 50
@@ -18,10 +18,10 @@
START_PROCESSING(SSobj, src)
return ..()
-/obj/vehicle/ridden/secway/process()
+/obj/vehicle/ridden/secway/process(delta_time)
if(obj_integrity >= integrity_failure)
return PROCESS_KILL
- if(prob(20))
+ if(DT_PROB(10, delta_time))
return
var/datum/effect_system/smoke_spread/smoke = new
smoke.set_up(0, src)
@@ -51,4 +51,4 @@
for(var/mob/M in buckled_mobs)
M.bullet_act(P)
return TRUE
- return ..()
\ No newline at end of file
+ return ..()
diff --git a/code/modules/vehicles/speedbike.dm b/code/modules/vehicles/speedbike.dm
index b6af3116b4100..713d6a6b42639 100644
--- a/code/modules/vehicles/speedbike.dm
+++ b/code/modules/vehicles/speedbike.dm
@@ -87,6 +87,6 @@
/obj/vehicle/ridden/space/speedwagon/Moved()
. = ..()
if(has_buckled_mobs())
- for(var/atom/A in range(2, src))
+ for(var/atom/A as() in range(2, src))
if(!(A in buckled_mobs))
Bump(A)
diff --git a/code/modules/vehicles/wheelchair.dm b/code/modules/vehicles/wheelchair.dm
index bf8e8aece554f..e7d0b4eb937fa 100644
--- a/code/modules/vehicles/wheelchair.dm
+++ b/code/modules/vehicles/wheelchair.dm
@@ -5,7 +5,7 @@
icon_state = "wheelchair"
layer = OBJ_LAYER
max_integrity = 100
- armor = list("melee" = 10, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 30) //Wheelchairs aren't super tough yo
+ armor = list("melee" = 10, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 30, "stamina" = 0) //Wheelchairs aren't super tough yo
legs_required = 0 //You'll probably be using this if you don't have legs
canmove = TRUE
density = FALSE //Thought I couldn't fix this one easily, phew
@@ -50,7 +50,7 @@
var/datum/component/riding/D = GetComponent(/datum/component/riding)
//1.5 (movespeed as of this change) multiplied by 6.7 gets ABOUT 10 (rounded), the old constant for the wheelchair that gets divided by how many arms they have
//if that made no sense this simply makes the wheelchair speed change along with movement speed delay
- D.vehicle_move_delay = round(CONFIG_GET(number/movedelay/run_delay) * delay_multiplier) / min(user.get_num_arms(), 2)
+ D.vehicle_move_delay = round(CONFIG_GET(number/movedelay/run_delay) * delay_multiplier) / clamp(user.get_num_arms(), arms_required, 2)
/obj/vehicle/ridden/wheelchair/Moved()
. = ..()
diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm
index 759de7efa71ed..3c5380895781a 100644
--- a/code/modules/vending/_vending.dm
+++ b/code/modules/vending/_vending.dm
@@ -52,7 +52,7 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C
verb_exclaim = "beeps"
max_integrity = 300
integrity_failure = 100
- armor = list("melee" = 20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 70)
+ armor = list("melee" = 20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 70, "stamina" = 0)
circuit = /obj/item/circuitboard/machine/vendor
payment_department = ACCOUNT_SRV
@@ -64,7 +64,7 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C
var/vend_ready = TRUE
///Next world time to send a purchase message
var/purchase_message_cooldown
- ///Last mob to shop with us
+ ///The ref of the last mob to shop with us
var/last_shopper
var/tilted = FALSE
var/tiltable = TRUE
@@ -122,8 +122,8 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C
var/seconds_electrified = MACHINE_NOT_ELECTRIFIED
///When this is TRUE, we fire items at customers! We're broken!
var/shoot_inventory = 0
- ///How likely this is to happen (prob 100)
- var/shoot_inventory_chance = 2
+ ///How likely this is to happen (prob 100) per second
+ var/shoot_inventory_chance = 1
//Stop spouting those godawful pitches!
var/shut_up = 0
///can we access the hidden inventory?
@@ -501,9 +501,11 @@ GLOBAL_LIST_EMPTY(vending_products)
crit_rebate = 50
for(var/i = 0, i < num_shards, i++)
var/obj/item/shard/shard = new /obj/item/shard(get_turf(C))
- shard.embedding = shard.embedding.setRating(embed_chance = 100, embedded_ignore_throwspeed_threshold = TRUE, embedded_impact_pain_multiplier=1,embedded_pain_chance=5)
+ shard.embedding = list(embed_chance = 10000, ignore_throwspeed_threshold = TRUE, impact_pain_mult=1, pain_chance=5)
+ shard.updateEmbedding()
C.hitby(shard, skipcatch = TRUE, hitpush = FALSE)
- shard.embedding = shard.embedding.setRating(embed_chance = EMBED_CHANCE, embedded_ignore_throwspeed_threshold = FALSE)
+ shard.embedding = list()
+ shard.updateEmbedding()
if(4) // paralyze this binch
// the new paraplegic gets like 4 lines of losing their legs so skip them
visible_message("[C]'s spinal cord is obliterated with a sickening crunch!")
@@ -694,6 +696,7 @@ GLOBAL_LIST_EMPTY(vending_products)
. = list()
var/mob/living/carbon/human/H
var/obj/item/card/id/C
+ .["user"] = null
if(ishuman(user))
H = user
C = H.get_idcard(TRUE)
@@ -718,7 +721,6 @@ GLOBAL_LIST_EMPTY(vending_products)
return
switch(action)
if("vend")
- . = TRUE
if(!vend_ready)
return
if(panel_open)
@@ -775,20 +777,22 @@ GLOBAL_LIST_EMPTY(vending_products)
var/datum/bank_account/D = SSeconomy.get_dep_account(payment_department)
if(D)
D.adjust_money(price_to_use)
- if(last_shopper != usr || purchase_message_cooldown < world.time)
+ if(last_shopper != REF(usr) || purchase_message_cooldown < world.time)
say("Thank you for shopping with [src]!")
purchase_message_cooldown = world.time + 5 SECONDS
- last_shopper = usr
+ //This is not the best practice, but it's safe enough here since the chances of two people using a machine with the same ref in 5 seconds is fuck low
+ last_shopper = REF(usr)
use_power(5)
if(icon_vend) //Show the vending animation if needed
flick(icon_vend,src)
playsound(src, 'sound/machines/machine_vend.ogg', 50, TRUE, extrarange = -3)
new R.product_path(get_turf(src))
R.amount--
+ . = TRUE
SSblackbox.record_feedback("nested tally", "vending_machine_usage", 1, list("[type]", "[R.product_path]"))
vend_ready = TRUE
-/obj/machinery/vending/process()
+/obj/machinery/vending/process(delta_time)
if(stat & (BROKEN|NOPOWER))
return PROCESS_KILL
if(!active)
@@ -796,14 +800,16 @@ GLOBAL_LIST_EMPTY(vending_products)
if(seconds_electrified > MACHINE_NOT_ELECTRIFIED)
seconds_electrified--
+ if(seconds_electrified <= MACHINE_NOT_ELECTRIFIED)
+ wires.ui_update()
//Pitch to the people! Really sell it!
- if(last_slogan + slogan_delay <= world.time && slogan_list.len > 0 && !shut_up && prob(5))
+ if(last_slogan + slogan_delay <= world.time && slogan_list.len > 0 && !shut_up && DT_PROB(2.5, delta_time))
var/slogan = pick(slogan_list)
speak(slogan)
last_slogan = world.time
- if(shoot_inventory && prob(shoot_inventory_chance))
+ if(shoot_inventory && DT_PROB(shoot_inventory_chance, delta_time))
throw_item()
/**
* Speak the given message verbally
@@ -865,6 +871,7 @@ GLOBAL_LIST_EMPTY(vending_products)
throw_item.throw_at(target, 16, 3)
visible_message("[src] launches [throw_item] at [target]!")
+ ui_update()
return 1
/**
* A callback called before an item is tossed out
@@ -936,11 +943,14 @@ GLOBAL_LIST_EMPTY(vending_products)
/obj/machinery/vending/custom/canLoadItem(obj/item/I, mob/user)
. = FALSE
+ if(I.flags_1 & HOLOGRAM_1)
+ say("This vendor cannot accept nonexistent items.")
+ return
if(loaded_items >= max_loaded_items)
say("There are too many items in stock.")
return
if(istype(I, /obj/item/stack))
- say("Loose items may cause problems, try use it inside wrapping paper.")
+ say("Loose items may cause problems, try to use it inside wrapping paper.")
return
if(I.custom_price)
return TRUE
@@ -976,7 +986,6 @@ GLOBAL_LIST_EMPTY(vending_products)
return
switch(action)
if("dispense")
- . = TRUE
if(!vend_ready)
return
var/N = params["item"]
@@ -1008,8 +1017,7 @@ GLOBAL_LIST_EMPTY(vending_products)
loaded_items--
use_power(5)
vend_ready = TRUE
- updateUsrDialog()
- return
+ return TRUE
if(account.has_money(S.custom_price))
account.adjust_money(-S.custom_price)
var/datum/bank_account/owner = private_a
@@ -1020,13 +1028,12 @@ GLOBAL_LIST_EMPTY(vending_products)
S.forceMove(drop_location())
loaded_items--
use_power(5)
- if(last_shopper != usr || purchase_message_cooldown < world.time)
+ if(last_shopper != REF(usr) || purchase_message_cooldown < world.time)
say("Thank you for buying local and purchasing [S]!")
purchase_message_cooldown = world.time + 5 SECONDS
- last_shopper = usr
+ last_shopper = REF(usr)
vend_ready = TRUE
- updateUsrDialog()
- return
+ return TRUE
else
say("You do not possess the funds to purchase this.")
vend_ready = TRUE
@@ -1050,15 +1057,6 @@ GLOBAL_LIST_EMPTY(vending_products)
last_slogan = world.time + rand(0, slogan_delay)
return
- if(canLoadItem(I))
- loadingAttempt(I,user)
- updateUsrDialog()
- return
-
- if(panel_open && is_wire_tool(I))
- wires.interact(user)
- return
-
return ..()
/obj/machinery/vending/custom/crowbar_act(mob/living/user, obj/item/I)
diff --git a/code/modules/vending/assist.dm b/code/modules/vending/assist.dm
index fbd8e85c607fc..5b78274f2531b 100644
--- a/code/modules/vending/assist.dm
+++ b/code/modules/vending/assist.dm
@@ -13,7 +13,7 @@
refill_canister = /obj/item/vending_refill/assist
product_ads = "Only the finest!;Have some tools.;The most robust equipment.;The finest gear in space!"
default_price = 10
- extra_price = 50
+ extra_price = 40
payment_department = NO_FREEBIES
/obj/item/vending_refill/assist
diff --git a/code/modules/vending/autodrobe.dm b/code/modules/vending/autodrobe.dm
index 8c4f4a7bebc12..12cbb0d5f19d5 100644
--- a/code/modules/vending/autodrobe.dm
+++ b/code/modules/vending/autodrobe.dm
@@ -76,6 +76,7 @@
/obj/item/clothing/under/rank/civilian/clown/sexy = 1,
/obj/item/clothing/mask/gas/sexymime = 1,
/obj/item/clothing/under/rank/civilian/mime/sexy = 1,
+ /obj/item/clothing/under/rank/civilian/mime/skirt = 1,
/obj/item/clothing/mask/rat/bat = 1,
/obj/item/clothing/mask/rat/bee = 1,
/obj/item/clothing/mask/rat/bear = 1,
@@ -113,6 +114,8 @@
/obj/item/clothing/head/cueball = 1,
/obj/item/clothing/under/costume/joker = 2,
/obj/item/clothing/suit/joker = 2,
+ /obj/item/clothing/under/dress/sailor = 1,
+ /obj/item/clothing/head/wig/random = 3,
/obj/item/clothing/head/delinquent = 1,
/obj/item/clothing/ears/headphones = 2)
contraband = list(/obj/item/clothing/suit/judgerobe = 1,
diff --git a/code/modules/vending/cigarette.dm b/code/modules/vending/cigarette.dm
index 9d5d2f6118794..a8e5b711493ca 100644
--- a/code/modules/vending/cigarette.dm
+++ b/code/modules/vending/cigarette.dm
@@ -20,8 +20,8 @@
/obj/item/storage/fancy/cigarettes/cigars/havana = 1,
/obj/item/storage/fancy/cigarettes/cigars/cohiba = 1)
refill_canister = /obj/item/vending_refill/cigarette
- default_price = 10
- extra_price = 50
+ default_price = 5
+ extra_price = 40
payment_department = ACCOUNT_SRV
/obj/machinery/vending/cigarette/syndicate
diff --git a/code/modules/vending/clothesmate.dm b/code/modules/vending/clothesmate.dm
index c4fbbb0334726..b55ed7f211674 100644
--- a/code/modules/vending/clothesmate.dm
+++ b/code/modules/vending/clothesmate.dm
@@ -72,6 +72,7 @@
/obj/item/clothing/suit/jacket = 2,
/obj/item/clothing/suit/jacket/puffer/vest = 2,
/obj/item/clothing/suit/jacket/puffer = 2,
+ /obj/item/clothing/suit/toggle/softshell = 2,
/obj/item/clothing/suit/jacket/letterman = 2,
/obj/item/clothing/suit/jacket/letterman_red = 2,
/obj/item/clothing/glasses/sunglasses = 2,
@@ -98,6 +99,7 @@
/obj/item/clothing/under/dress/redeveninggown = 1,
/obj/item/clothing/under/dress/blacktango = 1,
/obj/item/clothing/suit/ianshirt = 1,
+ /obj/item/clothing/suit/hooded/wintercoat/old = 3,
/obj/item/clothing/shoes/laceup = 2,
/obj/item/clothing/shoes/sandal = 2,
/obj/item/clothing/suit/jacket/miljacket = 1,
@@ -121,8 +123,8 @@
/obj/item/clothing/neck/necklace/dope = 3,
/obj/item/clothing/suit/jacket/letterman_nanotrasen = 1)
refill_canister = /obj/item/vending_refill/clothing
- default_price = 50
- extra_price = 75
+ default_price = 40
+ extra_price = 60
payment_department = NO_FREEBIES
/obj/item/vending_refill/clothing
diff --git a/code/modules/vending/cola.dm b/code/modules/vending/cola.dm
index caea23a0ceee1..ef50c42044994 100644
--- a/code/modules/vending/cola.dm
+++ b/code/modules/vending/cola.dm
@@ -21,8 +21,8 @@
/obj/item/reagent_containers/food/drinks/soda_cans/monkey_energy = 1,
/obj/item/reagent_containers/food/drinks/soda_cans/grey_bull = 1)
refill_canister = /obj/item/vending_refill/cola
- default_price = 10
- extra_price = 30
+ default_price = 5
+ extra_price = 60
payment_department = ACCOUNT_SRV
/obj/item/vending_refill/cola
machine_name = "Robust Softdrinks"
diff --git a/code/modules/vending/engivend.dm b/code/modules/vending/engivend.dm
index 877f8c65eeefd..d52389fde1fbc 100644
--- a/code/modules/vending/engivend.dm
+++ b/code/modules/vending/engivend.dm
@@ -17,7 +17,8 @@
/obj/item/electronics/airalarm = 10,
/obj/item/electronics/advanced_airlock_controller = 10,
/obj/item/electronics/firealarm = 10,
- /obj/item/electronics/firelock = 10)
+ /obj/item/electronics/firelock = 10,
+ /obj/item/storage/bag/construction = 3)
contraband = list(/obj/item/stock_parts/cell/potato = 3)
premium = list(/obj/item/storage/belt/utility = 3,
/obj/item/storage/box/smart_metal_foam = 1)
diff --git a/code/modules/vending/games.dm b/code/modules/vending/games.dm
index 755c7a92680ab..5de9421002db4 100644
--- a/code/modules/vending/games.dm
+++ b/code/modules/vending/games.dm
@@ -6,11 +6,13 @@
light_color = LIGHT_COLOR_ORANGE
products = list(/obj/item/toy/cards/deck = 5,
/obj/item/storage/pill_bottle/dice = 10,
+ /obj/item/storage/box/yatzy = 3,
/obj/item/toy/cards/deck/cas = 3,
/obj/item/toy/cards/deck/cas/black = 3,
/obj/item/toy/cards/deck/unum = 3,
/obj/item/hourglass = 2)
- contraband = list(/obj/item/dice/fudge = 9)
+ contraband = list(/obj/item/dice/fudge = 9,
+ /obj/item/instrument/musicalmoth = 1)
premium = list(/obj/item/melee/skateboard/pro = 3,
/obj/item/melee/skateboard/hoverboard = 1)
refill_canister = /obj/item/vending_refill/games
diff --git a/code/modules/vending/liberation.dm b/code/modules/vending/liberation.dm
index 10e87e6fc3cd3..be05de65cb4ae 100644
--- a/code/modules/vending/liberation.dm
+++ b/code/modules/vending/liberation.dm
@@ -27,8 +27,8 @@
contraband = list(/obj/item/clothing/under/misc/patriotsuit = 3,
/obj/item/bedsheet/patriot = 5,
/obj/item/reagent_containers/food/snacks/burger/superbite = 3) //U S A
- armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50)
+ armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50, "stamina" = 0)
resistance_flags = FIRE_PROOF
- default_price = 50
- extra_price = 100
+ default_price = 300
+ extra_price = 500
payment_department = ACCOUNT_SEC
diff --git a/code/modules/vending/liberation_toy.dm b/code/modules/vending/liberation_toy.dm
index 3af957857b48b..2e5b22bfb9bb0 100644
--- a/code/modules/vending/liberation_toy.dm
+++ b/code/modules/vending/liberation_toy.dm
@@ -21,11 +21,11 @@
/obj/item/gun/ballistic/automatic/l6_saw/toy/unrestricted/riot = 10,
/obj/item/ammo_box/foambox/riot = 20,
/obj/item/toy/katana = 10,
- /obj/item/twohanded/dualsaber/toy = 5,
+ /obj/item/dualsaber/toy = 5,
/obj/item/toy/cards/deck/syndicate = 10) //Gambling and it hurts, making it a +18 item
- armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50)
+ armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50, "stamina" = 0)
resistance_flags = FIRE_PROOF
refill_canister = /obj/item/vending_refill/donksoft
- default_price = 25
- extra_price = 50
+ default_price = 75
+ extra_price = 300
payment_department = ACCOUNT_SRV
diff --git a/code/modules/vending/magivend.dm b/code/modules/vending/magivend.dm
index a29d1348448f0..6dfd0195c873e 100644
--- a/code/modules/vending/magivend.dm
+++ b/code/modules/vending/magivend.dm
@@ -15,7 +15,7 @@
/obj/item/clothing/shoes/sandal/magic = 1,
/obj/item/staff = 2)
contraband = list(/obj/item/reagent_containers/glass/bottle/wizarditis = 1) //No one can get to the machine to hack it anyways; for the lulz - Microwave
- armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50)
+ armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50, "stamina" = 0)
resistance_flags = FIRE_PROOF
default_price = 25
extra_price = 50
diff --git a/code/modules/vending/medical.dm b/code/modules/vending/medical.dm
index f518502134fb3..ab8aa5ddb84eb 100644
--- a/code/modules/vending/medical.dm
+++ b/code/modules/vending/medical.dm
@@ -6,38 +6,33 @@
light_color = LIGHT_COLOR_WHITE
product_ads = "Go save some lives!;The best stuff for your medbay.;Only the finest tools.;Natural chemicals!;This stuff saves lives.;Don't you want some?;Ping!"
req_access = list(ACCESS_MEDICAL)
- products = list(/obj/item/stack/medical/gauze = 8,
- /obj/item/reagent_containers/syringe = 12,
+ products = list(/obj/item/reagent_containers/syringe = 12,
/obj/item/reagent_containers/dropper = 3,
+ /obj/item/reagent_containers/medspray = 6,
+ /obj/item/storage/pill_bottle = 6,
+ /obj/item/reagent_containers/glass/bottle = 10,
/obj/item/healthanalyzer = 4,
- /obj/item/reagent_containers/pill/patch/styptic = 5,
- /obj/item/reagent_containers/pill/patch/silver_sulf = 5,
- /obj/item/reagent_containers/pill/salbutamol = 2,
- /obj/item/reagent_containers/pill/insulin = 5,
+ /obj/item/reagent_containers/spray/cleaner = 1,
+ /obj/item/stack/medical/gauze = 8,
+ /obj/item/reagent_containers/hypospray/medipen = 8,
+ /obj/item/reagent_containers/hypospray/medipen/dexalin = 8,
+ /obj/item/reagent_containers/glass/bottle/epinephrine = 4,
/obj/item/reagent_containers/glass/bottle/charcoal = 4,
- /obj/item/reagent_containers/glass/bottle/epinephrine = 3,
- /obj/item/reagent_containers/glass/bottle/morphine = 4,
- /obj/item/reagent_containers/glass/bottle/potass_iodide = 1,
- /obj/item/reagent_containers/glass/bottle/salglu_solution = 3,
- /obj/item/reagent_containers/glass/bottle/toxin = 3,
- /obj/item/reagent_containers/syringe/antiviral = 6,
- /obj/item/reagent_containers/medspray/styptic = 2,
- /obj/item/reagent_containers/medspray/silver_sulf = 2,
- /obj/item/reagent_containers/medspray/sterilizine = 3,
- /obj/item/sensor_device = 2,
- /obj/item/pinpointer/crew = 2)
- contraband = list(/obj/item/reagent_containers/pill/tox = 3,
- /obj/item/reagent_containers/pill/morphine = 4,
- /obj/item/reagent_containers/pill/charcoal = 6,
+ /obj/item/reagent_containers/glass/bottle/salglu_solution = 4,
+ /obj/item/reagent_containers/glass/bottle/tricordrazine = 1,
+ /obj/item/reagent_containers/glass/bottle/spaceacillin = 1,
+ /obj/item/reagent_containers/glass/bottle/morphine = 2,
+ /obj/item/reagent_containers/glass/bottle/toxin = 4,
+ /obj/item/reagent_containers/medspray/sterilizine = 4)
+ contraband = list(/obj/item/reagent_containers/glass/bottle/chloralhydrate = 1,
/obj/item/storage/box/hug/medical = 1,
/obj/item/reagent_containers/glass/bottle/random_virus = 1)
- premium = list(/obj/item/reagent_containers/medspray/synthflesh = 2,
- /obj/item/storage/pill_bottle/psicodine = 2,
- /obj/item/reagent_containers/hypospray/medipen = 3,
- /obj/item/storage/belt/medical = 3,
- /obj/item/wrench/medical = 1,
- /obj/item/storage/firstaid/advanced = 2)
- armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50)
+ premium = list(/obj/item/storage/firstaid/regular = 3,
+ /obj/item/storage/belt/medical = 3,
+ /obj/item/sensor_device = 2,
+ /obj/item/pinpointer/crew = 2,
+ /obj/item/wrench/medical = 1)
+ armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50, "stamina" = 0)
resistance_flags = FIRE_PROOF
refill_canister = /obj/item/vending_refill/medical
default_price = 25
diff --git a/code/modules/vending/medical_wall.dm b/code/modules/vending/medical_wall.dm
index fbb1a3379e43b..60f7b9e521e16 100644
--- a/code/modules/vending/medical_wall.dm
+++ b/code/modules/vending/medical_wall.dm
@@ -6,15 +6,15 @@
density = FALSE
light_color = LIGHT_COLOR_WHITE
products = list(/obj/item/reagent_containers/syringe = 3,
- /obj/item/reagent_containers/pill/patch/styptic = 5,
- /obj/item/reagent_containers/pill/patch/silver_sulf = 5,
- /obj/item/reagent_containers/pill/charcoal = 2,
- /obj/item/reagent_containers/medspray/styptic = 2,
- /obj/item/reagent_containers/medspray/silver_sulf = 2,
+ /obj/item/stack/medical/gauze = 4,
+ /obj/item/reagent_containers/hypospray/medipen = 3,
+ /obj/item/reagent_containers/hypospray/medipen/dexalin = 3,
+ /obj/item/reagent_containers/glass/bottle/epinephrine = 2,
+ /obj/item/reagent_containers/glass/bottle/charcoal = 2,
/obj/item/reagent_containers/medspray/sterilizine = 3)
- contraband = list(/obj/item/reagent_containers/pill/tox = 2,
- /obj/item/reagent_containers/pill/morphine = 2)
- armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50)
+ contraband = list(/obj/item/reagent_containers/glass/bottle/toxin = 1,
+ /obj/item/reagent_containers/glass/bottle/morphine = 1)
+ armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50, "stamina" = 0)
resistance_flags = FIRE_PROOF
refill_canister = /obj/item/vending_refill/wallmed
default_price = 25
diff --git a/code/modules/vending/security.dm b/code/modules/vending/security.dm
index b0a7e0d0b923d..29ae4524deb4e 100644
--- a/code/modules/vending/security.dm
+++ b/code/modules/vending/security.dm
@@ -13,14 +13,18 @@
/obj/item/reagent_containers/food/snacks/donut = 12,
/obj/item/storage/box/evidence = 6,
/obj/item/flashlight/seclite = 4,
- /obj/item/restraints/legcuffs/bola/energy = 7)
+ /obj/item/holosign_creator/security = 3,
+ /obj/item/restraints/legcuffs/bola/energy = 7,
+ /obj/item/club = 5)
contraband = list(/obj/item/clothing/glasses/sunglasses/advanced = 2,
/obj/item/storage/fancy/donut_box = 2)
premium = list(/obj/item/storage/belt/security/webbing = 5,
/obj/item/storage/backpack/duffelbag/sec/deputy = 4,
/obj/item/coin/antagtoken = 1,
+ /obj/item/grenade/barrier = 4,
/obj/item/clothing/head/helmet/blueshirt = 1,
- /obj/item/clothing/suit/armor/vest/blueshirt = 1)
+ /obj/item/clothing/suit/armor/vest/blueshirt = 1,
+ /obj/item/grenade/stingbang = 1)
refill_canister = /obj/item/vending_refill/security
default_price = 100
extra_price = 150
diff --git a/code/modules/vending/toys.dm b/code/modules/vending/toys.dm
index ee979f4a82c76..3ad7137b4f365 100644
--- a/code/modules/vending/toys.dm
+++ b/code/modules/vending/toys.dm
@@ -22,13 +22,13 @@
/obj/item/gun/ballistic/automatic/c20r/toy/unrestricted = 10,
/obj/item/gun/ballistic/automatic/l6_saw/toy/unrestricted = 10,
/obj/item/toy/katana = 10,
- /obj/item/twohanded/dualsaber/toy = 5)
+ /obj/item/dualsaber/toy = 5)
- armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50)
+ armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50, "stamina" = 0)
resistance_flags = FIRE_PROOF
refill_canister = /obj/item/vending_refill/donksoft
- default_price = 25
- extra_price = 50
+ default_price = 75
+ extra_price = 300
payment_department = ACCOUNT_SRV
/obj/item/vending_refill/donksoft
diff --git a/code/modules/vending/wardrobes.dm b/code/modules/vending/wardrobes.dm
index 75d3be4f756c0..0c86c3d366446 100644
--- a/code/modules/vending/wardrobes.dm
+++ b/code/modules/vending/wardrobes.dm
@@ -35,6 +35,7 @@
/obj/item/clothing/under/rank/security/officer/mallcop = 3,
/obj/item/clothing/neck/tie/red = 6,
/obj/item/clothing/neck/tie/black = 6,)
+ contraband = list(/obj/item/clothing/suit/hooded/wintercoat/security/old = 3)
premium = list(/obj/item/clothing/under/rank/security/officer/formal = 3,
/obj/item/clothing/suit/security/officer = 3,
/obj/item/clothing/head/beret/sec/navyofficer = 3)
@@ -56,12 +57,16 @@
/obj/item/storage/backpack/medic = 4,
/obj/item/storage/backpack/satchel/med = 4,
/obj/item/clothing/suit/hooded/wintercoat/medical = 4,
+ /obj/item/clothing/under/rank/medical/emt = 4,
+ /obj/item/clothing/under/rank/medical/emt/skirt = 4,
/obj/item/clothing/under/rank/medical/doctor/nurse = 4,
/obj/item/clothing/head/nursehat = 4,
+ /obj/item/clothing/head/beret/med = 4,
/obj/item/clothing/under/rank/medical/doctor/blue = 4,
/obj/item/clothing/under/rank/medical/doctor/green = 4,
/obj/item/clothing/under/rank/medical/doctor/purple = 4,
/obj/item/clothing/under/rank/medical/doctor = 4,
+ /obj/item/clothing/under/rank/medical/doctor/skirt= 4,
/obj/item/clothing/under/plasmaman/medical = 4,
/obj/item/clothing/head/helmet/space/plasmaman/replacement/medical = 4,
/obj/item/clothing/suit/toggle/labcoat = 4,
@@ -70,6 +75,7 @@
/obj/item/clothing/head/soft/emt = 4,
/obj/item/clothing/suit/apron/surgical = 4,
/obj/item/clothing/mask/surgical = 4)
+ contraband = list(/obj/item/clothing/suit/hooded/wintercoat/medical/old = 3)
refill_canister = /obj/item/vending_refill/wardrobe/medi_wardrobe
payment_department = ACCOUNT_MED
/obj/item/vending_refill/wardrobe/medi_wardrobe
@@ -96,6 +102,7 @@
/obj/item/clothing/head/hardhat = 3,
/obj/item/clothing/head/hardhat/weldhat = 3,
/obj/item/clothing/head/beret/eng = 3)
+ contraband = list(/obj/item/clothing/suit/hooded/wintercoat/engineering/old = 3)
refill_canister = /obj/item/vending_refill/wardrobe/engi_wardrobe
payment_department = ACCOUNT_ENG
/obj/item/vending_refill/wardrobe/engi_wardrobe
@@ -112,11 +119,12 @@
/obj/item/storage/backpack/duffelbag/engineering = 2,
/obj/item/storage/backpack/satchel/eng = 2,
/obj/item/storage/backpack/industrial = 2,
- /obj/item/clothing/under/plasmaman/atmospherics = 3,
- /obj/item/clothing/head/helmet/space/plasmaman/replacement/atmospherics = 3,
+ /obj/item/clothing/under/plasmaman/engineering/atmospherics = 3,
+ /obj/item/clothing/head/helmet/space/plasmaman/replacement/engineering/atmospherics = 3,
/obj/item/clothing/suit/hooded/wintercoat/engineering/atmos = 3,
/obj/item/clothing/under/rank/engineering/atmospheric_technician = 3,
/obj/item/clothing/shoes/sneakers/black = 3)
+ contraband = list(/obj/item/clothing/suit/hooded/wintercoat/engineering/atmos/old = 3)
refill_canister = /obj/item/vending_refill/wardrobe/atmos_wardrobe
payment_department = ACCOUNT_ENG
/obj/item/vending_refill/wardrobe/atmos_wardrobe
@@ -131,6 +139,7 @@
light_color = LIGHT_COLOR_YELLOW
products = list(/obj/item/clothing/suit/hooded/wintercoat/cargo = 3,
/obj/item/clothing/under/rank/cargo/tech = 3,
+ /obj/item/clothing/under/rank/cargo/tech/skirt = 3,
/obj/item/clothing/under/plasmaman/cargo = 3,
/obj/item/clothing/head/helmet/space/plasmaman/replacement/cargo = 3,
/obj/item/clothing/shoes/sneakers/black = 3,
@@ -138,6 +147,7 @@
/obj/item/clothing/head/soft = 3,
/obj/item/radio/headset/headset_cargo = 3)
premium = list(/obj/item/clothing/under/rank/cargo/miner = 3)
+ contraband = list(/obj/item/radio/headset/headset_quartermaster = 1)
refill_canister = /obj/item/vending_refill/wardrobe/cargo_wardrobe
payment_department = ACCOUNT_CAR
/obj/item/vending_refill/wardrobe/cargo_wardrobe
@@ -153,6 +163,7 @@
products = list(/obj/item/clothing/glasses/hud/diagnostic = 2,
/obj/item/reagent_containers/medspray/sterilizine = 3,
/obj/item/clothing/under/rank/rnd/roboticist = 2,
+ /obj/item/clothing/under/rank/rnd/roboticist/skirt = 2,
/obj/item/clothing/under/plasmaman/robotics = 2,
/obj/item/clothing/head/helmet/space/plasmaman/replacement/robotics = 2,
/obj/item/clothing/suit/toggle/labcoat = 2,
@@ -182,6 +193,7 @@
/obj/item/storage/backpack/satchel/tox = 3,
/obj/item/clothing/suit/hooded/wintercoat/science = 3,
/obj/item/clothing/under/rank/rnd/scientist = 3,
+ /obj/item/clothing/under/rank/rnd/scientist/skirt = 3,
/obj/item/clothing/under/plasmaman/science = 3,
/obj/item/clothing/head/helmet/space/plasmaman/replacement/science = 3,
/obj/item/clothing/suit/toggle/labcoat/science = 3,
@@ -189,6 +201,7 @@
/obj/item/radio/headset/headset_sci = 3,
/obj/item/clothing/mask/gas = 3,
/obj/item/clothing/head/beret/sci = 3)
+ contraband = list(/obj/item/clothing/suit/hooded/wintercoat/science/old = 3)
refill_canister = /obj/item/vending_refill/wardrobe/science_wardrobe
payment_department = ACCOUNT_SCI
/obj/item/vending_refill/wardrobe/science_wardrobe
@@ -207,10 +220,12 @@
/obj/item/clothing/suit/apron = 2,
/obj/item/clothing/suit/apron/overalls = 3,
/obj/item/clothing/under/rank/civilian/hydroponics = 3,
+ /obj/item/clothing/under/rank/civilian/hydroponics/skirt = 3,
/obj/item/clothing/under/plasmaman/botany = 3,
/obj/item/clothing/head/helmet/space/plasmaman/replacement/botany = 3,
/obj/item/clothing/mask/bandana = 3,
/obj/item/clothing/accessory/armband/hydro = 3)
+ contraband = list(/obj/item/clothing/suit/hooded/wintercoat/hydro/old = 3)
refill_canister = /obj/item/vending_refill/wardrobe/hydro_wardrobe
payment_department = ACCOUNT_SRV
/obj/item/vending_refill/wardrobe/hydro_wardrobe
@@ -223,7 +238,8 @@
product_ads = "Glasses for your eyes and literature for your soul, Curadrobe has it all!; Impress & enthrall your library guests with Curadrobe's extended line of pens!"
vend_reply = "Thank you for using the CuraDrobe!"
light_color = LIGHT_COLOR_WHITE
- products = list(/obj/item/pen = 4,
+ products = list(/obj/item/clothing/under/rank/civilian/curator/skirt = 2,
+ /obj/item/pen = 4,
/obj/item/pen/red = 2,
/obj/item/pen/blue = 2,
/obj/item/pen/fourcolor = 1,
@@ -252,6 +268,7 @@
/obj/item/clothing/under/suit/sl = 2,
/obj/item/clothing/under/rank/civilian/bartender = 2,
/obj/item/clothing/under/rank/civilian/bartender/purple = 2,
+ /obj/item/clothing/under/rank/civilian/bartender/skirt = 2,
/obj/item/clothing/under/plasmaman/enviroslacks = 2,
/obj/item/clothing/head/helmet/space/plasmaman/replacement/white = 2,
/obj/item/clothing/accessory/waistcoat = 2,
@@ -290,6 +307,7 @@
/obj/item/clothing/under/plasmaman/chef = 1,
/obj/item/clothing/head/helmet/space/plasmaman/replacement/white = 1,
/obj/item/clothing/under/rank/civilian/chef = 1,
+ /obj/item/clothing/under/rank/civilian/chef/skirt = 2,
/obj/item/clothing/under/rank/civilian/altchef = 1,
/obj/item/clothing/head/chefhat = 3,
/obj/item/reagent_containers/glass/rag = 1,
@@ -306,12 +324,13 @@
product_ads = "Come and get your janitorial clothing, now endorsed by lizard janitors everywhere!"
vend_reply = "Thank you for using the JaniDrobe!"
products = list(/obj/item/clothing/under/rank/civilian/janitor = 2,
+ /obj/item/clothing/under/rank/civilian/janitor/skirt = 2,
/obj/item/clothing/under/plasmaman/janitor = 2,
/obj/item/clothing/head/helmet/space/plasmaman/replacement/janitor = 2,
/obj/item/cartridge/janitor = 2,
/obj/item/clothing/gloves/color/black = 2,
/obj/item/clothing/head/soft/purple = 2,
- /obj/item/twohanded/pushbroom = 2,
+ /obj/item/pushbroom = 2,
/obj/item/paint/paint_remover = 2,
/obj/item/melee/flyswatter = 2,
/obj/item/flashlight = 2,
@@ -335,16 +354,28 @@
product_ads = "OBJECTION! Get the rule of law for yourself!"
vend_reply = "Thank you for using the LawDrobe!"
products = list(/obj/item/clothing/under/rank/civilian/lawyer/bluesuit = 1,
+ /obj/item/clothing/under/rank/civilian/lawyer/bluesuit/skirt = 1,
/obj/item/clothing/suit/toggle/lawyer = 1,
/obj/item/clothing/under/rank/civilian/lawyer/purpsuit = 1,
/obj/item/clothing/suit/toggle/lawyer/purple = 1,
+ /obj/item/clothing/under/lawyer/civilian/lawyer/purpsuit/skirt = 1,
/obj/item/clothing/under/suit/black = 1,
+ /obj/item/clothing/under/suit/black/skirt = 1,
/obj/item/clothing/suit/toggle/lawyer/black = 1,
/obj/item/clothing/under/rank/civilian/lawyer/female = 1,
+ /obj/item/clothing/under/lawyer/civilian/lawyer/female/skirt = 1,
/obj/item/clothing/under/suit/black_really = 1,
+ /obj/item/clothing/under/suit/black_really/skirt = 1,
/obj/item/clothing/under/rank/civilian/lawyer/blue = 1,
+ /obj/item/clothing/under/rank/civilian/lawyer/blue/skirt = 1,
/obj/item/clothing/under/rank/civilian/lawyer/red = 1,
+ /obj/item/clothing/under/rank/civilian/lawyer/red/skirt = 1,
/obj/item/clothing/under/rank/civilian/lawyer/black = 1,
+ /obj/item/clothing/under/rank/civilian/lawyer/black/skirt = 1,
+ /obj/item/clothing/suit/aristo_orange = 1,
+ /obj/item/clothing/suit/aristo_red = 1,
+ /obj/item/clothing/suit/aristo_brown = 1,
+ /obj/item/clothing/suit/aristo_blue = 1,
/obj/item/clothing/shoes/laceup = 2,
/obj/item/clothing/neck/tie/red = 6,
/obj/item/clothing/neck/tie/black = 6,
@@ -365,6 +396,7 @@
/obj/item/storage/backpack/cultpack = 1,
/obj/item/clothing/accessory/pocketprotector/cosmetology = 1,
/obj/item/clothing/under/rank/civilian/chaplain = 1,
+ /obj/item/clothing/under/rank/civilian/chaplain/skirt = 1,
/obj/item/clothing/under/plasmaman/chaplain = 1,
/obj/item/clothing/head/helmet/space/plasmaman/replacement/chaplain = 1,
/obj/item/clothing/shoes/sneakers/black = 1,
@@ -373,6 +405,7 @@
/obj/item/clothing/suit/chaplainsuit/holidaypriest = 1,
/obj/item/storage/fancy/candle_box = 2,
/obj/item/clothing/head/kippah = 3,
+ /obj/item/clothing/suit/hooded/hastur = 1,
/obj/item/clothing/suit/chaplainsuit/whiterobe = 1,
/obj/item/clothing/head/taqiyahwhite = 1,
/obj/item/clothing/head/taqiyahred = 3,
@@ -381,8 +414,10 @@
/obj/item/toy/plush/narplush = 1,
/obj/item/clothing/head/medievaljewhat = 3,
/obj/item/clothing/suit/chaplainsuit/clownpriest = 1,
- /obj/item/clothing/head/clownmitre = 1)
+ /obj/item/clothing/head/clownmitre = 1,
+ /obj/item/clothing/neck/cloak/chap/bishop = 1)
premium = list(/obj/item/clothing/suit/chaplainsuit/bishoprobe = 1,
+ /obj/item/clothing/neck/crucifix/rosary = 1,
/obj/item/clothing/head/bishopmitre = 1)
refill_canister = /obj/item/vending_refill/wardrobe/chap_wardrobe
payment_department = ACCOUNT_CIV
@@ -396,6 +431,7 @@
product_ads = "Our clothes are 0.5% more resistant to acid spills! Get yours now!"
vend_reply = "Thank you for using the ChemDrobe!"
products = list(/obj/item/clothing/under/rank/medical/chemist = 2,
+ /obj/item/clothing/under/rank/medical/chemist/skirt = 2,
/obj/item/clothing/under/plasmaman/chemist = 2,
/obj/item/clothing/head/helmet/space/plasmaman/replacement/chemist = 2,
/obj/item/clothing/shoes/sneakers/white = 2,
@@ -416,6 +452,7 @@
product_ads = "Perfect for the mad scientist in you!"
vend_reply = "Thank you for using the GeneDrobe!"
products = list(/obj/item/clothing/under/rank/medical/geneticist = 2,
+ /obj/item/clothing/under/rank/medical/geneticist/skirt = 2,
/obj/item/clothing/under/plasmaman/genetics = 2,
/obj/item/clothing/head/helmet/space/plasmaman/replacement/genetics = 2,
/obj/item/clothing/shoes/sneakers/white = 2,
@@ -434,6 +471,7 @@
product_ads = " Viruses getting you down? Then upgrade to sterilized clothing today!"
vend_reply = "Thank you for using the ViroDrobe"
products = list(/obj/item/clothing/under/rank/medical/virologist = 2,
+ /obj/item/clothing/under/rank/medical/virologist/skirt = 2,
/obj/item/clothing/under/plasmaman/viro = 2,
/obj/item/clothing/head/helmet/space/plasmaman/replacement/viro = 2,
/obj/item/clothing/shoes/sneakers/white = 2,
@@ -448,3 +486,33 @@
payment_department = ACCOUNT_MED
/obj/item/vending_refill/wardrobe/viro_wardrobe
machine_name = "ViroDrobe"
+
+/obj/machinery/vending/wardrobe/det_wardrobe
+ name = "\improper DetDrobe"
+ desc = "A machine for all your detective needs, as long as you need clothes."
+ icon_state = "detdrobe"
+ product_ads = "Apply your brilliant deductive methods in style!"
+ vend_reply = "Thank you for using the DetDrobe!"
+ products = list(/obj/item/clothing/under/rank/security/detective = 2,
+ /obj/item/clothing/under/rank/security/detective/skirt = 2,
+ /obj/item/clothing/shoes/sneakers/brown = 2,
+ /obj/item/clothing/suit/det_suit = 2,
+ /obj/item/clothing/head/fedora/det_hat = 2,
+ /obj/item/clothing/under/rank/security/detective/grey = 2,
+ /obj/item/clothing/under/rank/security/detective/grey/skirt = 2,
+ /obj/item/clothing/accessory/waistcoat = 2,
+ /obj/item/clothing/shoes/laceup = 2,
+ /obj/item/clothing/suit/det_suit/grey = 1,
+ /obj/item/clothing/suit/det_suit/noir = 1,
+ /obj/item/clothing/head/fedora = 2,
+ /obj/item/clothing/gloves/color/black = 2,
+ /obj/item/clothing/gloves/color/latex = 2,
+ /obj/item/reagent_containers/food/drinks/flask/det = 2,
+ /obj/item/storage/fancy/cigarettes = 5)
+ premium = list(/obj/item/clothing/head/flatcap = 1)
+ refill_canister = /obj/item/vending_refill/wardrobe/det_wardrobe
+ extra_price = 350
+ payment_department = ACCOUNT_SEC
+
+/obj/item/vending_refill/wardrobe/det_wardrobe
+ machine_name = "DetDrobe"
diff --git a/code/modules/vending/youtool.dm b/code/modules/vending/youtool.dm
index 61a7310df8451..a80929166592b 100644
--- a/code/modules/vending/youtool.dm
+++ b/code/modules/vending/youtool.dm
@@ -8,6 +8,7 @@
/obj/item/crowbar = 5,
/obj/item/weldingtool = 3,
/obj/item/wirecutters = 5,
+ /obj/item/multitool = 5,
/obj/item/wrench = 5,
/obj/item/analyzer = 5,
/obj/item/t_scanner = 5,
@@ -22,9 +23,9 @@
/obj/item/clothing/head/welding = 2,
/obj/item/clothing/gloves/color/yellow = 1)
refill_canister = /obj/item/vending_refill/tool
- armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70)
+ armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70, "stamina" = 0)
resistance_flags = FIRE_PROOF
- default_price = 20
+ default_price = 10
extra_price = 80
payment_department = ACCOUNT_ENG
diff --git a/code/modules/wiremod/component.dm b/code/modules/wiremod/component.dm
new file mode 100644
index 0000000000000..8fcb5704a9f06
--- /dev/null
+++ b/code/modules/wiremod/component.dm
@@ -0,0 +1,261 @@
+/**
+ * # Integrated Circuit Component
+ *
+ * A component that performs a function when given an input
+ *
+ * Can be attached to an integrated circuitboard, where it can then
+ * be connected between other components to provide an output or to receive
+ * an input. This is the base type of all components
+ */
+/obj/item/circuit_component
+ name = COMPONENT_DEFAULT_NAME
+ icon = 'icons/obj/module.dmi'
+ icon_state = "component"
+ item_state = "electronic"
+
+ /// The name of the component shown on the UI
+ var/display_name = "Generic"
+
+ /// The description of the component shown on the UI
+ var/display_desc = "A generic component"
+
+ /// The integrated_circuit that this component is attached to.
+ var/obj/item/integrated_circuit/parent
+
+ /// A list that contains the outpurt ports on this component
+ /// Used to connect between the ports
+ var/list/datum/port/output/output_ports = list()
+
+ /// A list that contains the components the input ports on this component
+ /// Used to connect between the ports
+ var/list/datum/port/input/input_ports = list()
+
+ /// Generic trigger input for triggering this component
+ var/datum/port/input/trigger_input
+ var/datum/port/output/trigger_output
+
+ /// The flags of the circuit to control basic generalised behaviour.
+ var/circuit_flags = NONE
+
+ /// Used to determine the x position of the component within the UI
+ var/rel_x = 0
+ /// Used to determine the y position of the component within the UI
+ var/rel_y = 0
+
+ /// The power usage whenever this component receives an input
+ var/power_usage_per_input = 1
+
+ /// The current selected option
+ var/current_option
+ /// The options that this component can take on. Limited to strings
+ var/list/options
+
+ // Whether the component is removable or not. Only affects user UI
+ var/removable = TRUE
+
+/obj/item/circuit_component/Initialize()
+ . = ..()
+ if(name == COMPONENT_DEFAULT_NAME)
+ name = "[lowertext(display_name)] [COMPONENT_DEFAULT_NAME]"
+ populate_options()
+ if(length(options))
+ current_option = options[1]
+
+ return INITIALIZE_HINT_LATELOAD
+
+/// Called when the options variable should be set.
+/obj/item/circuit_component/proc/populate_options()
+ return
+
+/obj/item/circuit_component/LateInitialize()
+ . = ..()
+ if(circuit_flags & CIRCUIT_FLAG_INPUT_SIGNAL)
+ trigger_input = add_input_port("Trigger", PORT_TYPE_SIGNAL)
+ if(circuit_flags & CIRCUIT_FLAG_OUTPUT_SIGNAL)
+ trigger_output = add_output_port("Triggered", PORT_TYPE_SIGNAL)
+
+/obj/item/circuit_component/Destroy()
+ if(parent)
+ // Prevents a Destroy() recursion
+ var/obj/item/integrated_circuit/old_parent = parent
+ parent = null
+ old_parent.remove_component(src)
+
+ trigger_input = null
+ trigger_output = null
+
+ QDEL_LIST(output_ports)
+ QDEL_LIST(input_ports)
+ return ..()
+
+/**
+ * Called when a shell is registered from the component/the component is added to a circuit.
+ *
+ * Register all signals here on the shell.
+ * Arguments:
+ * * shell - Shell being registered
+ */
+/obj/item/circuit_component/proc/register_shell(atom/movable/shell)
+ return
+
+/**
+ * Called when a shell is unregistered from the component/the component is removed from a circuit.
+ *
+ * Unregister all signals here on the shell.
+ * Arguments:
+ * * shell - Shell being unregistered
+ */
+/obj/item/circuit_component/proc/unregister_shell(atom/movable/shell)
+ return
+
+/**
+ * Disconnects a component from other components
+ *
+ * Disconnects both the input and output ports of the component
+ */
+/obj/item/circuit_component/proc/disconnect()
+ for(var/datum/port/output/port_to_disconnect as anything in output_ports)
+ port_to_disconnect.disconnect()
+
+ for(var/datum/port/input/port_to_disconnect as anything in input_ports)
+ port_to_disconnect.disconnect()
+
+/**
+ * Sets the option on this component
+ *
+ * Can only be a value from the options variable
+ * Arguments:
+ * * option - The option that has been switched to.
+ */
+/obj/item/circuit_component/proc/set_option(option)
+ current_option = option
+ TRIGGER_CIRCUIT_COMPONENT(src, null)
+
+/**
+ * Matches the output port's datatype with the input port's current connected port.
+ *
+ * Returns true if datatype was changed, otherwise returns false.
+ * Arguments:
+ * * input_port - The input port to check the connected port from.
+ * * output_port - The output port to convert. Warning, this does change the output port.
+ */
+/obj/item/circuit_component/proc/match_port_datatype(datum/port/input/input_port, datum/port/output/output_port)
+ if(input_port.connected_port)
+ var/datum/port/connected_port = input_port.connected_port
+ if(connected_port.datatype != output_port.datatype)
+ output_port.set_datatype(connected_port.datatype)
+ return TRUE
+ else
+ output_port.set_datatype(output_port.default_datatype)
+ return TRUE
+ return FALSE
+
+
+/**
+ * Adds an input port and returns it
+ *
+ * Arguments:
+ * * name - The name of the input port
+ * * type - The datatype it handles
+ * * trigger - Whether this input port triggers an update on the component when updated.
+ */
+/obj/item/circuit_component/proc/add_input_port(name, type, trigger = TRUE, default = null)
+ var/datum/port/input/input_port = new(src, name, type, trigger, default)
+ input_ports += input_port
+ return input_port
+
+
+/**
+ * Adds an output port and returns it
+ *
+ * Arguments:
+ * * name - The name of the output port
+ * * type - The datatype it handles.
+ */
+/obj/item/circuit_component/proc/add_output_port(name, type)
+ var/datum/port/output/output_port = new(src, name, type)
+ output_ports += output_port
+ return output_port
+
+/**
+ * Called whenever an input is received from one of the ports.
+ *
+ * Return value indicates that the circuit should not do anything. Also prevents an output signal.
+ * Arguments:
+ * * port - Can be null. The port that sent the input
+ */
+/obj/item/circuit_component/proc/input_received(datum/port/input/port)
+ SHOULD_CALL_PARENT(TRUE)
+ if(!parent?.on)
+ return TRUE
+
+ var/obj/item/stock_parts/cell/cell = parent.get_cell()
+ if(!cell?.use(power_usage_per_input))
+ return TRUE
+
+ if((circuit_flags & CIRCUIT_FLAG_INPUT_SIGNAL) && !COMPONENT_TRIGGERED_BY(trigger_input, port))
+ return TRUE
+
+/// Called when this component is about to be added to an integrated_circuit.
+/obj/item/circuit_component/proc/add_to(obj/item/integrated_circuit/added_to)
+ return TRUE
+
+/// Called when this component is removed from an integrated_circuit.
+/obj/item/circuit_component/proc/removed_from(obj/item/integrated_circuit/removed_from)
+ return
+
+/**
+ * Gets the UI notices to be displayed on the CircuitInfo panel.
+ *
+ * Returns a list of buttons in the following format
+ * list(
+ * "icon" = ICON(string)
+ * "content" = CONTENT(string)
+ * "color" = COLOR(string, not a hex)
+ * )
+ */
+/obj/item/circuit_component/proc/get_ui_notices()
+ . = list()
+
+ if(!removable)
+ . += create_ui_notice("Unremovable", "red", "lock")
+
+
+ if(length(input_ports))
+ . += create_ui_notice("Power Usage Per Input: [power_usage_per_input]", "orange", "bolt")
+
+/**
+ * Creates a UI notice entry to be used in get_ui_notices()
+ *
+ * Returns a list that can then be added to the return list in get_ui_notices()
+ */
+/obj/item/circuit_component/proc/create_ui_notice(content, color, icon)
+ SHOULD_BE_PURE(TRUE)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ return list(list(
+ "icon" = icon,
+ "content" = content,
+ "color" = color,
+ ))
+
+/**
+ * Creates a table UI notice entry to be used in get_ui_notices()
+ *
+ * Returns a list that can then be added to the return list in get_ui_notices()
+ * Used by components to list their available columns. Recommended to use at the end of get_ui_notices()
+ */
+/obj/item/circuit_component/proc/create_table_notices(list/entries)
+ SHOULD_BE_PURE(TRUE)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ . = list()
+ . += create_ui_notice("Available Columns:", "grey", "question-circle")
+
+
+ for(var/entry in entries)
+ . += create_ui_notice("Column Name: '[entry]'", "grey", "columns")
+
+/obj/item/circuit_component/proc/register_usb_parent(atom/movable/parent)
+ return
+
+/obj/item/circuit_component/proc/unregister_usb_parent(atom/movable/parent)
+ return
diff --git a/code/modules/wiremod/component_printer.dm b/code/modules/wiremod/component_printer.dm
new file mode 100644
index 0000000000000..6f5e0d1f1dec2
--- /dev/null
+++ b/code/modules/wiremod/component_printer.dm
@@ -0,0 +1,38 @@
+/obj/machinery/modular_fabricator/component_printer
+ name = "component printer"
+ desc = "Produces components for the creation of integrated circuits."
+ icon = 'icons/obj/wiremod_fab.dmi'
+ icon_state = "fab-idle"
+ circuit = /obj/item/circuitboard/machine/component_printer
+
+ remote_materials = TRUE
+ auto_link = TRUE
+ can_sync = TRUE
+
+ //Quick.
+ minimum_construction_time = 5
+
+ stored_research_type = /datum/techweb/specialized/autounlocking/component_printer
+
+ categories = WIREMODE_CATEGORIES
+
+/obj/machinery/component_printer/crowbar_act(mob/living/user, obj/item/tool)
+
+ if(..())
+ return TRUE
+ return default_deconstruction_crowbar(tool)
+
+/obj/machinery/modular_fabricator/component_printer/screwdriver_act(mob/living/user, obj/item/tool)
+ if(..())
+ return TRUE
+ return default_deconstruction_screwdriver(user, "fab-o", "fab-idle", tool)
+
+/obj/item/circuitboard/machine/component_printer
+ name = "\improper Component Printer (Machine Board)"
+ icon_state = "science"
+ build_path = /obj/machinery/modular_fabricator/component_printer
+ req_components = list(
+ /obj/item/stock_parts/matter_bin = 2,
+ /obj/item/stock_parts/manipulator = 2,
+ /obj/item/reagent_containers/glass/beaker = 2,
+ )
diff --git a/code/modules/wiremod/components/abstract/arbitrary_input_amount.dm b/code/modules/wiremod/components/abstract/arbitrary_input_amount.dm
new file mode 100644
index 0000000000000..a57961dd01cf0
--- /dev/null
+++ b/code/modules/wiremod/components/abstract/arbitrary_input_amount.dm
@@ -0,0 +1,43 @@
+//This component is to create common methods for components that can function with any specified amount of inputs
+/obj/item/circuit_component/arbitrary_input_amount
+ display_name = "Arbitrary Input Amount"
+ display_desc = "A modular component base that allows component designs to contain an arbitrary amount of inputs"
+
+ //The type of port to use
+ var/input_port_type = PORT_TYPE_ANY
+ var/output_port_type = PORT_TYPE_ANY
+
+ /// The amount of input ports to have
+ var/input_port_amount = 2
+
+ /// The result from the output
+ var/datum/port/output/output
+
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+/obj/item/circuit_component/arbitrary_input_amount/Initialize()
+ . = ..()
+ for(var/port_id in 1 to input_port_amount)
+ var/letter = ascii2text(text2ascii("A") + (port_id-1))
+ add_input_port(letter, input_port_type)
+
+ output = add_output_port("Output", output_port_type)
+
+/obj/item/circuit_component/arbitrary_input_amount/Destroy()
+ output = null
+ return ..()
+
+/obj/item/circuit_component/arbitrary_input_amount/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ var/list/ports = input_ports.Copy()
+ var/datum/port/input/first_port = ports[1]
+ ports -= first_port
+ ports -= trigger_input
+
+ output.set_output(calculate_output(port,first_port,ports))
+
+//This should return the value to be set on input_received, first_port should be the first port, and ports should be every input port except the first and signal ports
+/obj/item/circuit_component/arbitrary_input_amount/proc/calculate_output(datum/port/input/port, datum/port/input/first_port, list/ports)
diff --git a/code/modules/wiremod/components/abstract/compare.dm b/code/modules/wiremod/components/abstract/compare.dm
new file mode 100644
index 0000000000000..c5fff8aa6ada6
--- /dev/null
+++ b/code/modules/wiremod/components/abstract/compare.dm
@@ -0,0 +1,67 @@
+/**
+ * # Compare Component
+ *
+ * Abstract component to build conditional components
+ */
+/obj/item/circuit_component/compare
+ display_name = "Compare"
+
+ /// The amount of input ports to have
+ var/input_port_amount = 4
+
+ /// The trigger for the true/false signals
+ var/datum/port/input/compare
+
+ /// Signals sent on compare
+ var/datum/port/output/true
+ var/datum/port/output/false
+
+ /// The result from the output
+ var/datum/port/output/result
+
+/obj/item/circuit_component/compare/Initialize()
+ . = ..()
+ for(var/port_id in 1 to input_port_amount)
+ var/letter = ascii2text(text2ascii("A") + (port_id-1))
+ add_input_port(letter, PORT_TYPE_ANY)
+
+ load_custom_ports()
+ compare = add_input_port("Compare", PORT_TYPE_SIGNAL)
+
+ true = add_output_port("True", PORT_TYPE_SIGNAL)
+ false = add_output_port("False", PORT_TYPE_SIGNAL)
+ result = add_output_port("Result", PORT_TYPE_NUMBER)
+
+/**
+ * Used by derivatives to load their own ports in for custom use.
+ */
+/obj/item/circuit_component/compare/proc/load_custom_ports()
+ return
+
+/obj/item/circuit_component/compare/Destroy()
+ true = null
+ false = null
+ result = null
+ compare = null
+ return ..()
+
+/obj/item/circuit_component/compare/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ var/list/ports = input_ports.Copy()
+ if(input_port_amount)
+ ports.Cut(input_port_amount+1)
+
+ var/logic_result = do_comparisons(ports)
+ if(COMPONENT_TRIGGERED_BY(compare, port))
+ if(logic_result)
+ true.set_output(COMPONENT_SIGNAL)
+ else
+ false.set_output(COMPONENT_SIGNAL)
+ result.set_output(logic_result)
+
+/// Do the comparisons and return a result
+/obj/item/circuit_component/compare/proc/do_comparisons(list/ports)
+ return FALSE
diff --git a/code/modules/wiremod/components/abstract/indexer.dm b/code/modules/wiremod/components/abstract/indexer.dm
new file mode 100644
index 0000000000000..5adb90b40d595
--- /dev/null
+++ b/code/modules/wiremod/components/abstract/indexer.dm
@@ -0,0 +1,85 @@
+/***
+* Indexer Component
+*
+* An abstract component to provide some common functionality for component at access a specified index of a list
+***/
+/obj/item/circuit_component/indexer
+ display_name = "Indexer Component"
+ display_desc = "A component base used to access specified indexes of a list; it doesn't work by itself."
+
+ /// The input port
+ var/datum/port/input/list_port
+ var/datum/port/input/index_port
+
+ // Changes functionality based on current option
+ var/option_flags = NONE
+
+ /// The result from the output
+ var/datum/port/output/output
+ var/output_name = "Output"
+ var/output_port_type = PORT_TYPE_ANY
+
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+
+/obj/item/circuit_component/indexer/populate_options()
+ var/static/list/index_options = list(
+ COMP_INDEXER_NONE,
+ COMP_INDEXER_INCREMENT,
+ COMP_INDEXER_LOOP,
+ COMP_INDEXER_BOTH
+ )
+ options = index_options
+
+/obj/item/circuit_component/indexer/set_option(option)
+ . = ..()
+ switch(current_option)
+ if(COMP_INDEXER_NONE)
+ option_flags = NONE
+ if(COMP_INDEXER_INCREMENT)
+ option_flags = COMP_INDEXER_FLAG_INCREMENT
+ if(COMP_INDEXER_LOOP)
+ option_flags = COMP_INDEXER_FLAG_LOOP
+ if(COMP_INDEXER_BOTH)
+ option_flags = COMP_INDEXER_FLAG_INCREMENT|COMP_INDEXER_FLAG_LOOP
+
+/obj/item/circuit_component/indexer/Initialize()
+ . = ..()
+ list_port = add_input_port("List", PORT_TYPE_LIST)
+ index_port = add_input_port("Index", PORT_TYPE_NUMBER)
+
+ output = add_output_port(output_name, output_port_type)
+
+/obj/item/circuit_component/indexer/Destroy()
+ list_port = null
+ index_port = null
+ output = null
+ return ..()
+
+/obj/item/circuit_component/indexer/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ var/index = index_port.input_value
+ var/list/list_input = list_port.input_value
+ list_input = list_input?.Copy() //input_value of an input port isn't typecasted to a list, so it doesn't reconize Copy() until you put it in a typed var
+
+ if(!islist(list_input) || isnull(index))
+ output.set_output(null)
+ return
+
+ //Common operations that can change how you work with lists, require index and list_input to not be null, and so go after the check
+ if(option_flags & COMP_INDEXER_FLAG_INCREMENT)
+ index += 1 //This makes the first index in a table functionally 0 instead of 1, useful when working with bitwise operations
+ if(option_flags & COMP_INDEXER_FLAG_LOOP)
+ index = MODULUS(index - 1, length(list_input)) + 1 //This makes an index that overflows or underflows loop back to the start of end of the list respectively, useful for when you want to continuously loop through the list
+
+ if(isnum(index) && (index < 1 || index > length(list_input)))
+ output.set_output(null)
+ return
+
+ calculate_output(index, list_input)
+
+/obj/item/circuit_component/indexer/proc/calculate_output(var/index, var/list/list_input)
+ return
diff --git a/code/modules/wiremod/components/action/light.dm b/code/modules/wiremod/components/action/light.dm
new file mode 100644
index 0000000000000..3dd946503a16c
--- /dev/null
+++ b/code/modules/wiremod/components/action/light.dm
@@ -0,0 +1,77 @@
+/**
+ * # Light Component
+ *
+ * Emits a light of a specific brightness and colour. Requires a shell.
+ */
+/obj/item/circuit_component/light
+ display_name = "Light"
+ display_desc = "A component that emits a light of a specific brightness and colour. Requires a shell."
+
+ /// The colours of the light
+ var/datum/port/input/red
+ var/datum/port/input/green
+ var/datum/port/input/blue
+
+ /// The brightness
+ var/datum/port/input/brightness
+
+ /// Whether the light is on or not
+ var/datum/port/input/on
+
+ var/max_power = 5
+ var/min_lightness = 0.4
+ var/shell_light_color
+
+/obj/item/circuit_component/light/get_ui_notices()
+ . = ..()
+ . += create_ui_notice("Maximum Brightness: [max_power]", "orange", "lightbulb")
+
+/obj/item/circuit_component/light/Initialize()
+ . = ..()
+ red = add_input_port("Red", PORT_TYPE_NUMBER)
+ green = add_input_port("Green", PORT_TYPE_NUMBER)
+ blue = add_input_port("Blue", PORT_TYPE_NUMBER)
+ brightness = add_input_port("Brightness", PORT_TYPE_NUMBER)
+
+ on = add_input_port("On", PORT_TYPE_NUMBER)
+
+
+/obj/item/circuit_component/light/Destroy()
+ red = null
+ green = null
+ blue = null
+ brightness = null
+ on = null
+ return ..()
+
+/obj/item/circuit_component/light/register_shell(atom/movable/shell)
+ . = ..()
+ TRIGGER_CIRCUIT_COMPONENT(src, null)
+
+/obj/item/circuit_component/light/unregister_shell(atom/movable/shell)
+ shell.set_light(0, 0)
+ return ..()
+
+/obj/item/circuit_component/light/input_received(datum/port/input/port)
+ . = ..()
+ brightness.set_input(clamp(brightness.input_value || 0, 0, max_power), FALSE)
+ red.set_input(clamp(red.input_value, 0, 255), FALSE)
+ blue.set_input(clamp(blue.input_value, 0, 255), FALSE)
+ green.set_input(clamp(green.input_value, 0, 255), FALSE)
+ var/list/hsl = rgb2hsl(red.input_value || 0, green.input_value || 0, blue.input_value || 0)
+ var/list/light_col = hsl2rgb(hsl[1], hsl[2], max(min_lightness, hsl[3]))
+ shell_light_color = rgb(light_col[1], light_col[2], light_col[3])
+ if(.)
+ return
+
+ if(parent.shell)
+ set_atom_light(parent.shell)
+
+/obj/item/circuit_component/light/proc/set_atom_light(atom/movable/target_atom)
+ // Clamp anyways just for safety
+ var/bright_val = min(max(brightness.input_value || 0, 0), max_power)
+
+ if(on.input_value)
+ target_atom.set_light(bright_val, bright_val, shell_light_color)
+ else
+ target_atom.set_light(0, 0)
diff --git a/code/modules/wiremod/components/action/mmi.dm b/code/modules/wiremod/components/action/mmi.dm
new file mode 100644
index 0000000000000..448a6095d1c57
--- /dev/null
+++ b/code/modules/wiremod/components/action/mmi.dm
@@ -0,0 +1,183 @@
+/**
+ * # Man-Machine Interface Component
+ *
+ * Allows an MMI to be inserted into a shell, allowing it to be linked up. Requires a shell.
+ */
+/obj/item/circuit_component/mmi
+ display_name = "Man-Machine Interface"
+ display_desc = "A component that allows MMI to enter shells to send output signals."
+
+ /// The message to send to the MMI in the shell.
+ var/datum/port/input/message
+ /// Sends the current MMI a message
+ var/datum/port/input/send
+ /// Ejects the current MMI
+ var/datum/port/input/eject
+
+ /// Called when the MMI tries moving north
+ var/datum/port/output/north
+ /// Called when the MMI tries moving east
+ var/datum/port/output/east
+ /// Called when the MMI tries moving south
+ var/datum/port/output/south
+ /// Called when the MMI tries moving west
+ var/datum/port/output/west
+
+ /// Returns what the MMI last clicked on.
+ var/datum/port/output/clicked_atom
+ /// Called when the MMI clicks.
+ var/datum/port/output/attack
+ /// Called when the MMI right clicks.
+ var/datum/port/output/secondary_attack
+
+ /// The current MMI card
+ var/obj/item/mmi/brain
+
+ /// Maximum length of the message that can be sent to the MMI
+ var/max_length = 300
+
+/obj/item/circuit_component/mmi/Initialize()
+ . = ..()
+ message = add_input_port("Message", PORT_TYPE_STRING)
+ send = add_input_port("Send Message", PORT_TYPE_SIGNAL)
+ eject = add_input_port("Eject", PORT_TYPE_SIGNAL)
+
+ north = add_output_port("North", PORT_TYPE_SIGNAL)
+ east = add_output_port("East", PORT_TYPE_SIGNAL)
+ south = add_output_port("South", PORT_TYPE_SIGNAL)
+ west = add_output_port("West", PORT_TYPE_SIGNAL)
+
+ attack = add_output_port("Attack", PORT_TYPE_SIGNAL)
+ secondary_attack = add_output_port("Secondary Attack", PORT_TYPE_SIGNAL)
+ clicked_atom = add_output_port("Target Entity", PORT_TYPE_ATOM)
+
+/obj/item/circuit_component/mmi/Destroy()
+ remove_current_brain()
+ message = null
+ send = null
+ eject = null
+ north = null
+ east = null
+ south = null
+ west = null
+ attack = null
+ secondary_attack = null
+ clicked_atom = null
+ return ..()
+
+/obj/item/circuit_component/mmi/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ if(!brain)
+ return
+
+ if(COMPONENT_TRIGGERED_BY(eject, port))
+ remove_current_brain()
+ if(COMPONENT_TRIGGERED_BY(send, port))
+ if(!message.input_value)
+ return
+
+ var/msg_str = copytext(html_encode(message.input_value), 1, max_length)
+
+ var/mob/living/target = brain.brainmob
+ if(!target)
+ return
+
+ to_chat(target, "You hear a message in your ear: [msg_str]")
+
+
+/obj/item/circuit_component/mmi/register_shell(atom/movable/shell)
+ . = ..()
+ RegisterSignal(shell, COMSIG_PARENT_ATTACKBY, .proc/handle_attack_by)
+
+/obj/item/circuit_component/mmi/unregister_shell(atom/movable/shell)
+ UnregisterSignal(shell, COMSIG_PARENT_ATTACKBY)
+ remove_current_brain()
+ return ..()
+
+/obj/item/circuit_component/mmi/proc/handle_attack_by(atom/movable/shell, obj/item/item, mob/living/attacker)
+ SIGNAL_HANDLER
+ if(istype(item, /obj/item/mmi))
+ var/obj/item/mmi/target_mmi = item
+ if(!target_mmi.brainmob)
+ return
+ add_mmi(item)
+ return COMPONENT_NO_AFTERATTACK
+
+/obj/item/circuit_component/mmi/proc/add_mmi(obj/item/mmi/to_add)
+ remove_current_brain()
+
+ to_add.forceMove(src)
+ if(to_add.brainmob)
+ update_mmi_mob(to_add, null, to_add.brainmob)
+ brain = to_add
+ RegisterSignal(to_add, COMSIG_PARENT_QDELETING, .proc/remove_current_brain)
+ RegisterSignal(to_add, COMSIG_MOVABLE_MOVED, .proc/mmi_moved)
+
+/obj/item/circuit_component/mmi/proc/mmi_moved(atom/movable/mmi)
+ if(mmi.loc != src)
+ remove_current_brain()
+
+/obj/item/circuit_component/mmi/proc/remove_current_brain()
+ SIGNAL_HANDLER
+ if(!brain)
+ return
+
+ if(brain.brainmob)
+ update_mmi_mob(brain, brain.brainmob)
+ UnregisterSignal(brain, list(
+ COMSIG_PARENT_QDELETING,
+ COMSIG_MOVABLE_MOVED
+ ))
+ if(brain.loc == src)
+ brain.forceMove(drop_location())
+ brain = null
+
+/obj/item/circuit_component/mmi/proc/update_mmi_mob(datum/source, mob/living/old_mmi, mob/living/new_mmi)
+ SIGNAL_HANDLER
+ if(old_mmi)
+ old_mmi.remote_control = null
+ UnregisterSignal(old_mmi, COMSIG_MOB_CLICKON)
+ if(new_mmi)
+ new_mmi.remote_control = src
+ RegisterSignal(new_mmi, COMSIG_MOB_CLICKON, .proc/handle_mmi_attack)
+
+/obj/item/circuit_component/mmi/relaymove(mob/living/user, direct)
+ if(user != brain.brainmob)
+ return ..()
+
+ if(direct & NORTH)
+ north.set_output(COMPONENT_SIGNAL)
+ if(direct & WEST)
+ west.set_output(COMPONENT_SIGNAL)
+ if(direct & EAST)
+ east.set_output(COMPONENT_SIGNAL)
+ if(direct & SOUTH)
+ south.set_output(COMPONENT_SIGNAL)
+
+ return TRUE
+
+/obj/item/circuit_component/mmi/proc/handle_mmi_attack(mob/living/source, atom/target)
+ SIGNAL_HANDLER
+
+ if(source.a_intent == INTENT_HARM)
+ clicked_atom.set_output(target)
+ secondary_attack.set_output(COMPONENT_SIGNAL)
+ . = COMSIG_MOB_CANCEL_CLICKON
+ else
+ clicked_atom.set_output(target)
+ attack.set_output(COMPONENT_SIGNAL)
+ . = COMSIG_MOB_CANCEL_CLICKON
+
+/obj/item/circuit_component/mmi/add_to(obj/item/integrated_circuit/add_to)
+ . = ..()
+ if(HAS_TRAIT(add_to, TRAIT_COMPONENT_MMI))
+ return FALSE
+ ADD_TRAIT(add_to, TRAIT_COMPONENT_MMI, src)
+
+/obj/item/circuit_component/mmi/removed_from(obj/item/integrated_circuit/removed_from)
+ REMOVE_TRAIT(removed_from, TRAIT_COMPONENT_MMI, src)
+ remove_current_brain()
+ return ..()
diff --git a/code/modules/wiremod/components/action/pull.dm b/code/modules/wiremod/components/action/pull.dm
new file mode 100644
index 0000000000000..e10c71dc57eb2
--- /dev/null
+++ b/code/modules/wiremod/components/action/pull.dm
@@ -0,0 +1,35 @@
+/**
+ * # Pull Component
+ *
+ * Tells the shell to start pulling on a designated atom. Only works on movable shells.
+ */
+/obj/item/circuit_component/pull
+ display_name = "Start Pulling"
+ display_desc = "A component that can force the shell to pull entities. Only works for drone shells."
+
+ /// Frequency input
+ var/datum/port/input/target
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+/obj/item/circuit_component/pull/Initialize()
+ . = ..()
+ target = add_input_port("Target", PORT_TYPE_ATOM)
+
+/obj/item/circuit_component/pull/Destroy()
+ target = null
+ return ..()
+
+/obj/item/circuit_component/pull/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ var/atom/target_atom = target.input_value
+ if(!target_atom)
+ return
+
+ var/mob/shell = parent.shell
+ if(!istype(shell) || get_dist(shell, target_atom) > 1 || shell.z != target_atom.z)
+ return
+
+ shell.start_pulling(target_atom)
diff --git a/code/modules/wiremod/components/action/radio.dm b/code/modules/wiremod/components/action/radio.dm
new file mode 100644
index 0000000000000..487e858e04aab
--- /dev/null
+++ b/code/modules/wiremod/components/action/radio.dm
@@ -0,0 +1,68 @@
+/**
+ * # Radio Component
+ *
+ * Listens out for signals on the designated frequencies and sends signals on designated frequencies
+ */
+/obj/item/circuit_component/radio
+ display_name = "Radio"
+ display_desc = "A component that can listen and send frequencies. If set to private, the component will only receive signals from other components attached to circuitboards with the same owner id."
+ /// Frequency input
+ var/datum/port/input/freq
+ /// Signal input
+ var/datum/port/input/code
+
+ /// Current frequency value
+ var/current_freq = DEFAULT_SIGNALER_CODE
+
+ var/datum/radio_frequency/radio_connection
+
+/obj/item/circuit_component/radio/populate_options()
+ var/static/component_options = list(
+ COMP_RADIO_PUBLIC,
+ COMP_RADIO_PRIVATE,
+ )
+ options = component_options
+
+/obj/item/circuit_component/radio/Initialize()
+ . = ..()
+ freq = add_input_port("Frequency", PORT_TYPE_NUMBER, default = FREQ_SIGNALER)
+ code = add_input_port("Code", PORT_TYPE_NUMBER, default = DEFAULT_SIGNALER_CODE)
+ TRIGGER_CIRCUIT_COMPONENT(src, null)
+ // These are cleaned up on the parent
+ trigger_input = add_input_port("Send", PORT_TYPE_SIGNAL)
+ trigger_output = add_output_port("Received", PORT_TYPE_SIGNAL)
+
+/obj/item/circuit_component/radio/Destroy()
+ freq = null
+ code = null
+ SSradio.remove_object(src, current_freq)
+ radio_connection = null
+ return ..()
+
+/obj/item/circuit_component/radio/input_received(datum/port/input/port)
+ . = ..()
+ freq.set_input(sanitize_frequency(freq.input_value, TRUE), FALSE)
+ if(.)
+ return
+ var/frequency = freq.input_value
+
+ SSradio.remove_object(src, current_freq)
+ radio_connection = SSradio.add_object(src, frequency, RADIO_SIGNALER)
+ current_freq = frequency
+
+ if(COMPONENT_TRIGGERED_BY(trigger_input, port))
+ var/datum/signal/signal = new(list("code" = round(code.input_value) || 0, "key" = parent?.owner_id))
+ radio_connection.post_signal(src, signal)
+
+/obj/item/circuit_component/radio/receive_signal(datum/signal/signal)
+ . = FALSE
+ if(!signal)
+ return
+
+ if(signal.data["code"] != round(code.input_value || 0))
+ return
+
+ if(current_option == COMP_RADIO_PRIVATE && parent?.owner_id != signal.data["key"])
+ return
+
+ trigger_output.set_output(COMPONENT_SIGNAL)
diff --git a/code/modules/wiremod/components/action/soundemitter.dm b/code/modules/wiremod/components/action/soundemitter.dm
new file mode 100644
index 0000000000000..ab12455314b64
--- /dev/null
+++ b/code/modules/wiremod/components/action/soundemitter.dm
@@ -0,0 +1,81 @@
+/**
+ * # Sound Emitter Component
+ *
+ * A component that emits a sound when it receives an input.
+ */
+/obj/item/circuit_component/soundemitter
+ display_name = "Sound Emitter"
+ display_desc = "A component that emits a sound when it receives an input. The frequency is a multiplier which determines the speed at which the sound is played"
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+ /// Volume of the sound when played
+ var/datum/port/input/volume
+
+ /// Frequency of the sound when played
+ var/datum/port/input/frequency
+
+ /// The cooldown for this component of how often it can play sounds.
+ var/sound_cooldown = 2 SECONDS
+
+ var/list/options_map
+
+ COOLDOWN_DECLARE(next_sound)
+
+/obj/item/circuit_component/soundemitter/get_ui_notices()
+ . = ..()
+ . += create_ui_notice("Sound Cooldown: [DisplayTimeText(sound_cooldown)]", "orange", "stopwatch")
+
+
+/obj/item/circuit_component/soundemitter/Initialize()
+ . = ..()
+ volume = add_input_port("Volume", PORT_TYPE_NUMBER, default = 35)
+ frequency = add_input_port("Frequency", PORT_TYPE_NUMBER, default = 0)
+
+/obj/item/circuit_component/soundemitter/Destroy()
+ frequency = null
+ volume = null
+ return ..()
+
+/obj/item/circuit_component/soundemitter/populate_options()
+ var/static/component_options = list(
+ COMP_SOUND_BUZZ,
+ COMP_SOUND_BUZZ_TWO,
+ COMP_SOUND_CHIME,
+ COMP_SOUND_HONK,
+ COMP_SOUND_PING,
+ COMP_SOUND_SAD,
+ COMP_SOUND_WARN,
+ COMP_SOUND_SLOWCLAP,
+ )
+ options = component_options
+
+ var/static/options_to_sound = list(
+ COMP_SOUND_BUZZ = 'sound/machines/buzz-sigh.ogg',
+ COMP_SOUND_BUZZ_TWO = 'sound/machines/buzz-two.ogg',
+ COMP_SOUND_CHIME = 'sound/machines/chime.ogg',
+ COMP_SOUND_HONK = 'sound/items/bikehorn.ogg',
+ COMP_SOUND_PING = 'sound/machines/ping.ogg',
+ COMP_SOUND_SAD = 'sound/misc/sadtrombone.ogg',
+ COMP_SOUND_WARN = 'sound/machines/warning-buzzer.ogg',
+ COMP_SOUND_SLOWCLAP = 'sound/machines/slowclap.ogg',
+ )
+ options_map = options_to_sound
+
+
+/obj/item/circuit_component/soundemitter/input_received(datum/port/input/port)
+ . = ..()
+ volume.set_input(clamp(volume.input_value, 0, 100), FALSE)
+ frequency.set_input(clamp(frequency.input_value, -100, 100), FALSE)
+ if(.)
+ return
+
+ if(!COOLDOWN_FINISHED(src, next_sound))
+ return
+
+ var/sound_to_play = options_map[current_option]
+ if(!sound_to_play)
+ return
+
+ playsound(src, sound_to_play, volume.input_value, FALSE, frequency = frequency.input_value)
+
+ COOLDOWN_START(src, next_sound, sound_cooldown)
diff --git a/code/modules/wiremod/components/action/speech.dm b/code/modules/wiremod/components/action/speech.dm
new file mode 100644
index 0000000000000..41dd3d2f31cd4
--- /dev/null
+++ b/code/modules/wiremod/components/action/speech.dm
@@ -0,0 +1,54 @@
+/**
+ * # Speech Component
+ *
+ * Sends a message. Requires a shell.
+ */
+/obj/item/circuit_component/speech
+ display_name = "Speech"
+ display_desc = "A component that sends a message. Requires a shell."
+
+ /// The message to send
+ var/datum/port/input/message
+ /// The trigger to send the message
+ var/datum/port/input/trigger
+
+ /// The cooldown for this component of how often it can send speech messages.
+ var/speech_cooldown = 1 SECONDS
+
+ COOLDOWN_DECLARE(next_speech)
+
+/obj/item/circuit_component/speech/get_ui_notices()
+ . = ..()
+ . += create_ui_notice("Speech Cooldown: [DisplayTimeText(speech_cooldown)]", "orange", "stopwatch")
+
+/obj/item/circuit_component/speech/Initialize()
+ . = ..()
+ message = add_input_port("Message", PORT_TYPE_STRING, FALSE)
+
+ trigger = add_input_port("Trigger", PORT_TYPE_SIGNAL)
+
+
+/obj/item/circuit_component/speech/Destroy()
+ message = null
+ trigger = null
+ return ..()
+
+/obj/item/circuit_component/speech/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ if(!COMPONENT_TRIGGERED_BY(trigger, port))
+ return
+
+ if(!COOLDOWN_FINISHED(src, next_speech))
+ return
+
+ if(message.input_value)
+ var/atom/movable/shell = parent.shell
+ // Prevents appear as the individual component if there is a shell.
+ if(shell)
+ shell.say(message.input_value)
+ else
+ say(message.input_value)
+ COOLDOWN_START(src, next_speech, speech_cooldown)
diff --git a/code/modules/wiremod/components/atom/direction.dm b/code/modules/wiremod/components/atom/direction.dm
new file mode 100644
index 0000000000000..c283b70ca9944
--- /dev/null
+++ b/code/modules/wiremod/components/atom/direction.dm
@@ -0,0 +1,71 @@
+/**
+ * # Direction Component
+ *
+ * Return the direction of a mob relative to the component
+ */
+/obj/item/circuit_component/direction
+ display_name = "Get Direction"
+ display_desc = "A component that returns the direction of itself and an entity."
+
+ /// The input port
+ var/datum/port/input/input_port
+
+ /// The result from the output
+ var/datum/port/output/output
+
+ // Directions outputs
+ var/datum/port/output/north
+ var/datum/port/output/south
+ var/datum/port/output/east
+ var/datum/port/output/west
+
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+ /// Maximum range for a valid direction to be returned
+ var/max_range = 7
+
+/obj/item/circuit_component/direction/get_ui_notices()
+ . = ..()
+ . += create_ui_notice("Maximum Range: [max_range] tiles", "orange", "info")
+
+/obj/item/circuit_component/direction/Initialize()
+ . = ..()
+ input_port = add_input_port("Organism", PORT_TYPE_ATOM)
+
+ output = add_output_port("Direction", PORT_TYPE_STRING)
+
+ north = add_output_port("North", PORT_TYPE_SIGNAL)
+ east = add_output_port("East", PORT_TYPE_SIGNAL)
+ south = add_output_port("South", PORT_TYPE_SIGNAL)
+ west = add_output_port("West", PORT_TYPE_SIGNAL)
+
+/obj/item/circuit_component/direction/Destroy()
+ input_port = null
+ output = null
+ return ..()
+
+/obj/item/circuit_component/direction/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ var/atom/object = input_port.input_value
+ if(!object)
+ return
+ var/turf/location = get_turf(src)
+
+ if(object.z != location.z || get_dist(location, object) > max_range)
+ output.set_output(null)
+ return
+
+ var/direction = get_dir(location, get_turf(object))
+ output.set_output(dir2text(direction))
+
+ if(direction & NORTH)
+ north.set_output(COMPONENT_SIGNAL)
+ if(direction & SOUTH)
+ south.set_output(COMPONENT_SIGNAL)
+ if(direction & EAST)
+ east.set_output(COMPONENT_SIGNAL)
+ if(direction & WEST)
+ west.set_output(COMPONENT_SIGNAL)
diff --git a/code/modules/wiremod/components/atom/gps.dm b/code/modules/wiremod/components/atom/gps.dm
new file mode 100644
index 0000000000000..f2113d9e4f922
--- /dev/null
+++ b/code/modules/wiremod/components/atom/gps.dm
@@ -0,0 +1,39 @@
+/**
+ * # GPS Component
+ *
+ * Return the location of this
+ */
+/obj/item/circuit_component/gps
+ display_name = "Internal GPS"
+ display_desc = "A component that returns the xyz co-ordinates of itself."
+
+ /// The result from the output
+ var/datum/port/output/x_pos
+ var/datum/port/output/y_pos
+ var/datum/port/output/z_pos
+
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+/obj/item/circuit_component/gps/Initialize()
+ . = ..()
+
+ x_pos = add_output_port("X", PORT_TYPE_NUMBER)
+ y_pos = add_output_port("Y", PORT_TYPE_NUMBER)
+ z_pos = add_output_port("Z", PORT_TYPE_NUMBER)
+
+/obj/item/circuit_component/gps/Destroy()
+ x_pos = null
+ y_pos = null
+ z_pos = null
+ return ..()
+
+/obj/item/circuit_component/gps/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ var/turf/location = get_turf(src)
+
+ x_pos.set_output(location?.x)
+ y_pos.set_output(location?.y)
+ z_pos.set_output(location?.z)
diff --git a/code/modules/wiremod/components/atom/health.dm b/code/modules/wiremod/components/atom/health.dm
new file mode 100644
index 0000000000000..8f1cc4423c180
--- /dev/null
+++ b/code/modules/wiremod/components/atom/health.dm
@@ -0,0 +1,70 @@
+/**
+ * # Get Health Component
+ *
+ * Return the health of a mob
+ */
+/obj/item/circuit_component/health
+ display_name = "Get Health"
+ display_desc = "A component that returns the health of an organism."
+
+ /// The input port
+ var/datum/port/input/input_port
+
+ /// Brute damage
+ var/datum/port/output/brute
+ /// Burn damage
+ var/datum/port/output/burn
+ /// Toxin damage
+ var/datum/port/output/toxin
+ /// Oxyloss damage
+ var/datum/port/output/oxy
+ /// Health
+ var/datum/port/output/health
+
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+ var/max_range = 5
+
+/obj/item/circuit_component/health/get_ui_notices()
+ . = ..()
+ . += create_ui_notice("Maximum Range: [max_range] tiles", "orange", "info")
+
+/obj/item/circuit_component/health/Initialize()
+ . = ..()
+ input_port = add_input_port("Organism", PORT_TYPE_ATOM)
+
+ brute = add_output_port("Brute Damage", PORT_TYPE_NUMBER)
+ burn = add_output_port("Burn Damage", PORT_TYPE_NUMBER)
+ toxin = add_output_port("Toxin Damage", PORT_TYPE_NUMBER)
+ oxy = add_output_port("Suffocation Damage", PORT_TYPE_NUMBER)
+ health = add_output_port("Health", PORT_TYPE_NUMBER)
+
+/obj/item/circuit_component/health/Destroy()
+ input_port = null
+ brute = null
+ burn = null
+ toxin = null
+ oxy = null
+ health = null
+ return ..()
+
+/obj/item/circuit_component/health/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ var/mob/living/organism = input_port.input_value
+ var/turf/current_turf = get_turf(src)
+ if(!istype(organism) || get_dist(current_turf, organism) > max_range || current_turf.z != organism.z)
+ brute.set_output(null)
+ burn.set_output(null)
+ toxin.set_output(null)
+ oxy.set_output(null)
+ health.set_output(null)
+ return
+
+ brute.set_output(organism.getBruteLoss())
+ burn.set_output(organism.getFireLoss())
+ toxin.set_output(organism.getToxLoss())
+ oxy.set_output(organism.getOxyLoss())
+ health.set_output(organism.health)
diff --git a/code/modules/wiremod/components/atom/hear.dm b/code/modules/wiremod/components/atom/hear.dm
new file mode 100644
index 0000000000000..a8e3d9170478b
--- /dev/null
+++ b/code/modules/wiremod/components/atom/hear.dm
@@ -0,0 +1,44 @@
+/**
+ * # Hear Component
+ *
+ * Listens for messages. Requires a shell.
+ */
+/obj/item/circuit_component/hear
+ display_name = "Voice Activator"
+ display_desc = "A component that listens for messages. Requires a shell."
+
+ /// The message heard
+ var/datum/port/output/message_port
+ /// The language heard
+ var/datum/port/output/language_port
+ /// The speaker
+ var/datum/port/output/speaker_port
+ /// The trigger sent when this event occurs
+ var/datum/port/output/trigger_port
+
+/obj/item/circuit_component/hear/Initialize()
+ . = ..()
+ message_port = add_output_port("Message", PORT_TYPE_STRING)
+ language_port = add_output_port("Language", PORT_TYPE_STRING)
+ speaker_port = add_output_port("Speaker", PORT_TYPE_ATOM)
+ trigger_port = add_output_port("Triggered", PORT_TYPE_SIGNAL)
+ become_hearing_sensitive(ROUNDSTART_TRAIT)
+
+
+
+/obj/item/circuit_component/hear/Destroy()
+ message_port = null
+ language_port = null
+ speaker_port = null
+ trigger_port = null
+ return ..()
+
+/obj/item/circuit_component/hear/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, list/message_mods)
+ if(speaker == parent?.shell)
+ return
+
+ message_port.set_output(raw_message)
+ if(message_language)
+ language_port.set_output(initial(message_language.name))
+ speaker_port.set_output(speaker)
+ trigger_port.set_output(COMPONENT_SIGNAL)
diff --git a/code/modules/wiremod/components/atom/self.dm b/code/modules/wiremod/components/atom/self.dm
new file mode 100644
index 0000000000000..364d51cd6d694
--- /dev/null
+++ b/code/modules/wiremod/components/atom/self.dm
@@ -0,0 +1,25 @@
+/**
+ * # Self Component
+ *
+ * Return the current shell.
+ */
+/obj/item/circuit_component/self
+ display_name = "Self"
+ display_desc = "A component that returns the current shell."
+
+ /// The shell this component is attached to.
+ var/datum/port/output/output
+
+/obj/item/circuit_component/self/Initialize()
+ . = ..()
+ output = add_output_port("Self", PORT_TYPE_ATOM)
+
+/obj/item/circuit_component/self/Destroy()
+ output = null
+ return ..()
+
+/obj/item/circuit_component/self/register_shell(atom/movable/shell)
+ output.set_output(shell)
+
+/obj/item/circuit_component/self/unregister_shell(atom/movable/shell)
+ output.set_output(null)
diff --git a/code/modules/wiremod/components/atom/species.dm b/code/modules/wiremod/components/atom/species.dm
new file mode 100644
index 0000000000000..0275e7001f983
--- /dev/null
+++ b/code/modules/wiremod/components/atom/species.dm
@@ -0,0 +1,39 @@
+/**
+ * # Get Species Component
+ *
+ * Return the species of a mob
+ */
+/obj/item/circuit_component/species
+ display_name = "Get Species"
+ display_desc = "A component that returns the species of its input."
+
+ /// The input port
+ var/datum/port/input/input_port
+
+ /// The result from the output
+ var/datum/port/output/output
+
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+/obj/item/circuit_component/species/Initialize()
+ . = ..()
+ input_port = add_input_port("Organism", PORT_TYPE_ATOM)
+
+ output = add_output_port("Species", PORT_TYPE_STRING)
+
+/obj/item/circuit_component/species/Destroy()
+ input_port = null
+ output = null
+ return ..()
+
+/obj/item/circuit_component/species/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ var/mob/living/carbon/human/human = input_port.input_value
+ if(!istype(human) || !human.has_dna())
+ output.set_output(null)
+ return
+
+ output.set_output(human.dna.species.name)
diff --git a/code/modules/wiremod/components/list/append.dm b/code/modules/wiremod/components/list/append.dm
new file mode 100644
index 0000000000000..5f2109edfe6da
--- /dev/null
+++ b/code/modules/wiremod/components/list/append.dm
@@ -0,0 +1,50 @@
+/***
+* Append Component
+*
+* Appends a value onto a list, increasing its size by 1
+***/
+/obj/item/circuit_component/append
+ display_name = "Append Component"
+ display_desc = "A component that appends a value to a list."
+
+ //Input ports
+ var/datum/port/input/list_port
+ var/datum/port/input/value_port
+
+ //Output port
+ var/datum/port/output/output
+
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+/obj/item/circuit_component/append/Initialize()
+ . = ..()
+ list_port = add_input_port("List", PORT_TYPE_LIST)
+ value_port = add_input_port("Value", PORT_TYPE_ANY)
+
+ output = add_output_port("New List", PORT_TYPE_LIST)
+
+/obj/item/circuit_component/append/Destroy()
+ list_port = null
+ value_port = null
+ output = null
+ return ..()
+
+/obj/item/circuit_component/append/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ var/value = value_port.input_value
+ var/list/input_list = list_port.input_value
+ input_list = input_list?.Copy() //input_value of an input port isn't typecasted to a list, so it doesn't reconize Copy() until you put it in a typed var
+
+ //appending a null value onto a list is a reasonable thing to do if the goal is only to change the length of the list, therefore, isnull(value) isn't checked
+ if(isnull(input_list))
+ output.set_output(null)
+ return
+
+ if(input_list.len < COMPONENT_MAXIMUM_LIST_SIZE) //Prevents lists from growing too large
+ input_list += value
+ output.set_output(input_list)
+
+
diff --git a/code/modules/wiremod/components/list/concat.dm b/code/modules/wiremod/components/list/concat.dm
new file mode 100644
index 0000000000000..8b63be42429e3
--- /dev/null
+++ b/code/modules/wiremod/components/list/concat.dm
@@ -0,0 +1,53 @@
+/**
+ * # Concat List Component
+ *
+ * Concatenates a list with a separator
+ */
+/obj/item/circuit_component/concat_list
+ display_name = "Concatenate List"
+ display_desc = "A component that joins up a list with a separator into a single string."
+
+ /// The input port
+ var/datum/port/input/list_port
+
+ /// The seperator
+ var/datum/port/input/separator
+
+ /// The result from the output
+ var/datum/port/output/output
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+/obj/item/circuit_component/concat_list/Initialize()
+ . = ..()
+ list_port = add_input_port("List", PORT_TYPE_LIST)
+ separator = add_input_port("Seperator", PORT_TYPE_STRING)
+
+ output = add_output_port("Output", PORT_TYPE_STRING)
+
+/obj/item/circuit_component/concat_list/Destroy()
+ list_port = null
+ separator = null
+ output = null
+ return ..()
+
+/obj/item/circuit_component/concat_list/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ var/seperator = separator.input_value
+ if(!seperator)
+ return
+
+ var/list/list_input = list_port.input_value
+ if(!list_input)
+ return
+
+ var/list/text_list = list()
+ for(var/entry in list_input)
+ if(isatom(entry))
+ text_list += PORT_TYPE_ATOM
+ else
+ text_list += "[entry]"
+
+ output.set_output(text_list.Join(seperator))
diff --git a/code/modules/wiremod/components/list/get_column.dm b/code/modules/wiremod/components/list/get_column.dm
new file mode 100644
index 0000000000000..fac219ab24949
--- /dev/null
+++ b/code/modules/wiremod/components/list/get_column.dm
@@ -0,0 +1,48 @@
+/**
+ * # Get Column Component
+ *
+ * Gets the column of a table and returns it as a regular list.
+ */
+/obj/item/circuit_component/get_column
+ display_name = "Get Column"
+ display_desc = "Gets the column of a table and returns it as a regular list."
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+ /// The list to perform the filter on
+ var/datum/port/input/received_table
+
+ /// The name of the column to check
+ var/datum/port/input/column_name
+
+ /// The filtered list
+ var/datum/port/output/output_list
+
+/obj/item/circuit_component/get_column/Initialize()
+ . = ..()
+ received_table = add_input_port("Input", PORT_TYPE_TABLE)
+ column_name = add_input_port("Column Name", PORT_TYPE_STRING)
+ output_list = add_output_port("Output", PORT_TYPE_LIST)
+
+/obj/item/circuit_component/get_column/Destroy()
+ received_table = null
+ column_name = null
+ output_list = null
+ return ..()
+
+/obj/item/circuit_component/get_column/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ var/list/input_list = received_table.input_value
+ if(!islist(input_list) || isnum(column_name.input_value))
+ return
+
+ var/list/new_list = list()
+ for(var/list/entry in input_list)
+ var/anything = entry[column_name.input_value]
+ if(islist(anything))
+ continue
+ new_list += anything
+
+ output_list.set_output(new_list)
diff --git a/code/modules/wiremod/components/list/index.dm b/code/modules/wiremod/components/list/index.dm
new file mode 100644
index 0000000000000..24416829d2949
--- /dev/null
+++ b/code/modules/wiremod/components/list/index.dm
@@ -0,0 +1,16 @@
+/**
+ * # Index Component
+ *
+ * Return the index of a list
+ */
+/obj/item/circuit_component/indexer/index
+ display_name = "Index List"
+ display_desc = "A component that returns the value of a list at a given index."
+
+ /// The result from the output
+ output_name = "Value"
+ output_port_type = PORT_TYPE_ANY
+
+/obj/item/circuit_component/indexer/index/calculate_output(var/index, var/list/list_input)
+
+ output.set_output(list_input[index])
diff --git a/code/modules/wiremod/components/list/index_table.dm b/code/modules/wiremod/components/list/index_table.dm
new file mode 100644
index 0000000000000..9bfe8b70945a8
--- /dev/null
+++ b/code/modules/wiremod/components/list/index_table.dm
@@ -0,0 +1,48 @@
+/**
+ * # Index Table Component
+ *
+ * Gets the row of a table using the index inputted. Will return no value if the index is invalid or a proper table is not returned.
+ */
+/obj/item/circuit_component/index_table
+ display_name = "Index Table"
+ display_desc = "Gets the row of a table using the index inputted. Will return no value if the index is invalid or a proper table is not returned."
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+ /// The list to perform the filter on
+ var/datum/port/input/received_table
+
+ /// The target index
+ var/datum/port/input/target_index
+
+ /// The filtered list
+ var/datum/port/output/output_list
+
+/obj/item/circuit_component/index_table/Initialize()
+ . = ..()
+ received_table = add_input_port("Input", PORT_TYPE_TABLE)
+ target_index = add_input_port("Index", PORT_TYPE_NUMBER)
+
+ output_list = add_output_port("Output", PORT_TYPE_TABLE)
+
+/obj/item/circuit_component/index_table/Destroy()
+ received_table = null
+ target_index = null
+ output_list = null
+ return ..()
+
+/obj/item/circuit_component/index_table/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ var/list/target_list = received_table.input_value
+ if(!islist(target_list) || !length(target_list))
+ output_list.set_output(null)
+ return
+
+ var/index = target_index.input_value
+ if(index < 1 || index > length(target_list))
+ output_list.set_output(null)
+ return
+
+ output_list.set_output(target_list[index])
diff --git a/code/modules/wiremod/components/list/list_constructor.dm b/code/modules/wiremod/components/list/list_constructor.dm
new file mode 100644
index 0000000000000..7b9631f83a7b3
--- /dev/null
+++ b/code/modules/wiremod/components/list/list_constructor.dm
@@ -0,0 +1,24 @@
+/**
+* List Constructor Component
+*
+* This component allows the construction of a list from other data
+**/
+
+/obj/item/circuit_component/arbitrary_input_amount/list_constructor
+ display_name = "List Constructor"
+ display_desc = "A component that creates a list from given inputs"
+
+ power_usage_per_input = 5 //Large cost
+
+ //Takes any inputs, makes a list out of them
+ input_port_type = PORT_TYPE_ANY
+ output_port_type = PORT_TYPE_LIST
+
+ //This can be changed to whatever value is wanted
+ input_port_amount = 4
+
+/obj/item/circuit_component/arbitrary_input_amount/list_constructor/calculate_output(datum/port/input/port, datum/port/input/first_port, list/ports)
+ . = list()
+ . += first_port.input_value
+ for(var/datum/port/input/input_port as anything in ports)
+ . += input_port.input_value
diff --git a/code/modules/wiremod/components/list/list_length_constructor.dm b/code/modules/wiremod/components/list/list_length_constructor.dm
new file mode 100644
index 0000000000000..423e6a02b81aa
--- /dev/null
+++ b/code/modules/wiremod/components/list/list_length_constructor.dm
@@ -0,0 +1,44 @@
+/***
+* List Length Constructor
+*
+* Constructs an empty list with specified length
+***/
+
+
+/obj/item/circuit_component/list_length_constructor
+ display_name = "List Length Constructor"
+ display_desc = "A varient of the list constructor that makes an emtpy list with a specified length"
+
+ power_usage_per_input = 10 //B I G cost
+
+ //A specified length
+ var/datum/port/input/input_length
+
+ //The constructed list
+ var/datum/port/output/output_port
+
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+/obj/item/circuit_component/list_length_constructor/Initialize()
+ . = ..()
+ input_length = add_input_port("Length", PORT_TYPE_NUMBER)
+ output_port = add_output_port("Output", PORT_TYPE_LIST)
+
+/obj/item/circuit_component/list_length_constructor/Destroy()
+ input_length = null
+ output_port = null
+
+ return ..()
+
+/obj/item/circuit_component/list_length_constructor/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ var/length = input_length.input_value
+ var/list/new_list = null
+ if(length > COMPONENT_MAXIMUM_LIST_SIZE)
+ length = COMPONENT_MAXIMUM_LIST_SIZE
+ if(length >= 0)
+ new_list = new /list(length)
+ output_port.set_output(new_list)
diff --git a/code/modules/wiremod/components/list/pop.dm b/code/modules/wiremod/components/list/pop.dm
new file mode 100644
index 0000000000000..2e418e935941f
--- /dev/null
+++ b/code/modules/wiremod/components/list/pop.dm
@@ -0,0 +1,56 @@
+/***
+* Pop Component
+*
+* Pops the last entry of a list off as if it were a stack or retrieves the first entry like a queue.
+***/
+
+/obj/item/circuit_component/pop
+ display_name = "Pop Component"
+ display_desc = "Removes the last or first entry of a list and returns it."
+
+ //The list port
+ var/datum/port/input/list_port
+
+ //The output
+ var/datum/port/output/output_value
+ var/datum/port/output/output_list
+
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+/obj/item/circuit_component/pop/populate_options()
+ options = list(
+ COMP_POP_POP,
+ COMP_POP_DEQUEUE
+ )
+
+
+/obj/item/circuit_component/pop/Initialize()
+ . = ..()
+ list_port = add_input_port("List", PORT_TYPE_LIST)
+ output_list = add_output_port("New List", PORT_TYPE_LIST)
+ output_value = add_output_port("Value", PORT_TYPE_ANY)
+
+/obj/item/circuit_component/pop/Destroy()
+ list_port = null
+ output_value = null
+ output_list = null
+ return ..()
+
+/obj/item/circuit_component/pop/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ var/list/input_list = list_port.input_value
+ input_list = input_list?.Copy() //Same as in the append component
+ var/result = null
+
+ if(input_list)
+ switch(current_option)
+ if(COMP_POP_POP)
+ result = pop(input_list)
+ if(COMP_POP_DEQUEUE)
+ result = popleft(input_list)
+
+ output_value.set_output(result)
+ output_list.set_output(input_list)
diff --git a/code/modules/wiremod/components/list/select.dm b/code/modules/wiremod/components/list/select.dm
new file mode 100644
index 0000000000000..76eaf67b4085f
--- /dev/null
+++ b/code/modules/wiremod/components/list/select.dm
@@ -0,0 +1,96 @@
+/**
+ * # Select Component
+ *
+ * Selects a list from a list of lists by a specific column. Used only by USBs for communications to and from computers with lists of varying sizes.
+ */
+/obj/item/circuit_component/select
+ display_name = "Select Query"
+ display_desc = "A component used with USB cables that can perform select queries on a list based on the column name selected. The values are then compared with the comparison input."
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+ /// The list to perform the filter on
+ var/datum/port/input/received_table
+
+ /// The name of the column to check
+ var/datum/port/input/column_name
+
+ /// The input to compare with
+ var/datum/port/input/comparison_input
+
+ /// The filtered list
+ var/datum/port/output/filtered_table
+
+ var/current_type = PORT_TYPE_ANY
+
+/obj/item/circuit_component/select/populate_options()
+ var/static/component_options = list(
+ COMP_COMPARISON_EQUAL,
+ COMP_COMPARISON_NOT_EQUAL,
+ COMP_COMPARISON_GREATER_THAN,
+ COMP_COMPARISON_LESS_THAN,
+ COMP_COMPARISON_GREATER_THAN_OR_EQUAL,
+ COMP_COMPARISON_LESS_THAN_OR_EQUAL,
+ )
+ options = component_options
+
+/obj/item/circuit_component/select/Initialize()
+ . = ..()
+ received_table = add_input_port("Input", PORT_TYPE_TABLE)
+ column_name = add_input_port("Column Name", PORT_TYPE_STRING)
+ comparison_input = add_input_port("Comparison Input", PORT_TYPE_ANY)
+
+ filtered_table = add_output_port("Output", PORT_TYPE_TABLE)
+
+/obj/item/circuit_component/select/Destroy()
+ received_table = null
+ column_name = null
+ comparison_input = null
+ filtered_table = null
+ return ..()
+
+/obj/item/circuit_component/select/input_received(datum/port/input/port)
+ . = ..()
+ switch(current_option)
+ if(COMP_COMPARISON_EQUAL, COMP_COMPARISON_NOT_EQUAL)
+ if(current_type != PORT_TYPE_ANY)
+ current_type = PORT_TYPE_ANY
+ comparison_input.set_datatype(PORT_TYPE_ANY)
+ else
+ if(current_type != PORT_TYPE_NUMBER)
+ current_type = PORT_TYPE_NUMBER
+ comparison_input.set_datatype(PORT_TYPE_NUMBER)
+
+ if(.)
+ return
+
+ var/list/input_list = received_table.input_value
+ if(!islist(input_list) || isnum(column_name.input_value))
+ return
+
+ var/comparison_value = comparison_input.input_value
+ var/list/new_list = list()
+ for(var/list/entry in input_list)
+ var/anything = entry[column_name.input_value]
+ if(islist(anything))
+ continue
+ if(current_option != COMP_COMPARISON_EQUAL && current_option != COMP_COMPARISON_NOT_EQUAL && !isnum(anything))
+ continue
+ var/add_to_list = FALSE
+ switch(current_option)
+ if(COMP_COMPARISON_EQUAL)
+ add_to_list = anything == comparison_value
+ if(COMP_COMPARISON_NOT_EQUAL)
+ add_to_list = anything != comparison_value
+ if(COMP_COMPARISON_GREATER_THAN)
+ add_to_list = anything > comparison_value
+ if(COMP_COMPARISON_GREATER_THAN_OR_EQUAL)
+ add_to_list = anything >= comparison_value
+ if(COMP_COMPARISON_LESS_THAN)
+ add_to_list = anything < comparison_value
+ if(COMP_COMPARISON_LESS_THAN_OR_EQUAL)
+ add_to_list = anything <= comparison_value
+
+ if(add_to_list)
+ new_list += list(entry)
+
+ filtered_table.set_output(new_list)
diff --git a/code/modules/wiremod/components/list/write.dm b/code/modules/wiremod/components/list/write.dm
new file mode 100644
index 0000000000000..52d9881f108d3
--- /dev/null
+++ b/code/modules/wiremod/components/list/write.dm
@@ -0,0 +1,28 @@
+/**
+* Write Component
+*
+* Writes the given value to the specified index of a given table
+**/
+/obj/item/circuit_component/indexer/write
+ display_name = "Write Component"
+ display_desc = "A component that writes a given value to a given index in a given list. It then gives that new list back."
+
+ /// The input ports
+ var/datum/port/input/value_port
+
+ /// The result from the output
+ output_name = "New List"
+ output_port_type = PORT_TYPE_LIST
+
+/obj/item/circuit_component/indexer/write/Initialize()
+ . = ..()
+ value_port = add_input_port("Value", PORT_TYPE_ANY)
+
+/obj/item/circuit_component/indexer/write/Destroy()
+ value_port = null
+ return ..()
+
+/obj/item/circuit_component/indexer/write/calculate_output(var/index, var/list/list_input)
+ list_input[index] = value_port.input_value
+ output.set_output(list_input)
+
diff --git a/code/modules/wiremod/components/math/arithmetic.dm b/code/modules/wiremod/components/math/arithmetic.dm
new file mode 100644
index 0000000000000..116929d26a57a
--- /dev/null
+++ b/code/modules/wiremod/components/math/arithmetic.dm
@@ -0,0 +1,62 @@
+/**
+ * # Arithmetic Component
+ *
+ * General arithmetic unit with add/sub/mult/divide capabilities
+ * This one only works with numbers.
+ */
+/obj/item/circuit_component/arbitrary_input_amount/arithmetic
+ display_name = "Arithmetic"
+ display_desc = "General arithmetic component with arithmetic capabilities."
+
+ //The type of port
+ input_port_type = PORT_TYPE_NUMBER
+ output_port_type = PORT_TYPE_NUMBER
+
+ /// The amount of input ports to have
+ input_port_amount = 4
+
+/obj/item/circuit_component/arbitrary_input_amount/arithmetic/populate_options()
+ options = list(
+ COMP_ARITHMETIC_ADD,
+ COMP_ARITHMETIC_SUBTRACT,
+ COMP_ARITHMETIC_MULTIPLY,
+ COMP_ARITHMETIC_DIVIDE,
+ COMP_ARITHMETIC_MODULO,
+ COMP_ARITHMETIC_MIN,
+ COMP_ARITHMETIC_MAX,
+ )
+
+/obj/item/circuit_component/arbitrary_input_amount/arithmetic/calculate_output(datum/port/input/port, datum/port/input/first_port, list/ports)
+
+ . = first_port.input_value
+
+ for(var/datum/port/input/input_port as anything in ports)
+ var/value = input_port.input_value
+ if(isnull(value))
+ continue
+
+ switch(current_option)
+ if(COMP_ARITHMETIC_ADD)
+ . += value
+ if(COMP_ARITHMETIC_SUBTRACT)
+ . -= value
+ if(COMP_ARITHMETIC_MULTIPLY)
+ . *= value
+ if(COMP_ARITHMETIC_DIVIDE)
+ // Protect from div by zero errors.
+ if(value == 0)
+ . = null
+ break
+ . /= value
+ if(COMP_ARITHMETIC_MODULO)
+ //Another protect from divide by zero.
+ if(value == 0)
+ . = null
+ break
+ //BYOND's built in modulus operator doesn't work well with decimals, so I'm using this method instead
+ var/multiples = FLOOR(. / value, 1)
+ . -= multiples * value
+ if(COMP_ARITHMETIC_MAX)
+ . = max(., value)
+ if(COMP_ARITHMETIC_MIN)
+ . = min(., value)
diff --git a/code/modules/wiremod/components/math/bitflag.dm b/code/modules/wiremod/components/math/bitflag.dm
new file mode 100644
index 0000000000000..dbcadb01601f5
--- /dev/null
+++ b/code/modules/wiremod/components/math/bitflag.dm
@@ -0,0 +1,40 @@
+/*
+
+bitflag component
+
+This is a component that checks if a specific bit in a number is a one or zero.
+Its best use is to combine it with the bitwise component to put multiple booleans in one number.
+
+*/
+
+
+/obj/item/circuit_component/compare/bitflag
+ display_name = "Bitflag"
+ display_desc = "A component that can determine if a specified bit of a number is on or off."
+
+ //default compare ports aren't used
+ input_port_amount = 0
+
+ //The number containing the flags
+ var/datum/port/input/input
+ //The bit that needs to be checked
+ var/datum/port/input/bit
+
+/obj/item/circuit_component/compare/bitflag/load_custom_ports()
+ input = add_input_port("Input", PORT_TYPE_NUMBER)
+ bit = add_input_port("Bit", PORT_TYPE_NUMBER)
+
+/obj/item/circuit_component/compare/bitflag/Destroy()
+ input = null
+ bit = null
+ return ..()
+
+/obj/item/circuit_component/compare/bitflag/do_comparisons(list/ports)
+
+ var/value = round(input.input_value)
+ var/bit_value = round(bit.input_value)
+
+ return (value >> bit_value) & 1
+
+
+
diff --git a/code/modules/wiremod/components/math/bitwise.dm b/code/modules/wiremod/components/math/bitwise.dm
new file mode 100644
index 0000000000000..674b3cedf0a2e
--- /dev/null
+++ b/code/modules/wiremod/components/math/bitwise.dm
@@ -0,0 +1,48 @@
+/**
+ * # Bitwise Component
+ *
+ * A component that preforms bitwise and, or and xor operations, as well as left shifts and right shifts
+ * All input and output values are floored
+ */
+/obj/item/circuit_component/arbitrary_input_amount/bitwise
+ display_name = "Bitwise"
+ display_desc = "A component that operates on the bits of integers. Any decimal values are ignored."
+
+ //The type of port to use
+ input_port_type = PORT_TYPE_NUMBER
+ output_port_type = PORT_TYPE_NUMBER
+
+ /// The amount of input ports to have
+ input_port_amount = 2
+
+/obj/item/circuit_component/arbitrary_input_amount/bitwise/populate_options()
+ options = list(
+ COMP_BITWISE_AND,
+ COMP_BITWISE_OR,
+ COMP_BITWISE_XOR,
+ COMP_BITWISE_LEFTSHIFT,
+ COMP_BITWISE_RIGHTSHIFT,
+ )
+
+/obj/item/circuit_component/arbitrary_input_amount/bitwise/calculate_output(datum/port/input/port, datum/port/input/first_port, list/ports)
+
+ . = FLOOR(first_port.input_value, 1)
+
+ for(var/datum/port/input/input_port as anything in ports)
+ var/value = input_port.input_value
+ if(isnull(value))
+ continue
+
+ value = FLOOR(value, 1)
+
+ switch(current_option)
+ if(COMP_BITWISE_AND)
+ . &= value
+ if(COMP_BITWISE_OR)
+ . |= value
+ if(COMP_BITWISE_XOR)
+ . ^= value
+ if(COMP_BITWISE_LEFTSHIFT)
+ . = FLOOR(. * 2**value, 1) //Bitshifts are done with powers of two instead of the >> and << operators to allow negative shifts
+ if(COMP_BITWISE_RIGHTSHIFT)
+ . = FLOOR(. * 2**(-value), 1)
diff --git a/code/modules/wiremod/components/math/comparison.dm b/code/modules/wiremod/components/math/comparison.dm
new file mode 100644
index 0000000000000..b2f625ea019f8
--- /dev/null
+++ b/code/modules/wiremod/components/math/comparison.dm
@@ -0,0 +1,58 @@
+/**
+ * # Comparison Component
+ *
+ * Compares two objects
+ */
+/obj/item/circuit_component/compare/comparison
+ display_name = "Comparison"
+ display_desc = "A component that compares two objects."
+
+ input_port_amount = 2
+ var/current_type = PORT_TYPE_ANY
+
+/obj/item/circuit_component/compare/comparison/populate_options()
+ var/static/component_options = list(
+ COMP_COMPARISON_EQUAL,
+ COMP_COMPARISON_NOT_EQUAL,
+ COMP_COMPARISON_GREATER_THAN,
+ COMP_COMPARISON_LESS_THAN,
+ COMP_COMPARISON_GREATER_THAN_OR_EQUAL,
+ COMP_COMPARISON_LESS_THAN_OR_EQUAL,
+ )
+ options = component_options
+
+/obj/item/circuit_component/compare/comparison/input_received(datum/port/input/port)
+ switch(current_option)
+ if(COMP_COMPARISON_EQUAL, COMP_COMPARISON_NOT_EQUAL)
+ if(current_type != PORT_TYPE_ANY)
+ current_type = PORT_TYPE_ANY
+ input_ports[1].set_datatype(PORT_TYPE_ANY)
+ input_ports[2].set_datatype(PORT_TYPE_ANY)
+ else
+ if(current_type != PORT_TYPE_NUMBER)
+ current_type = PORT_TYPE_NUMBER
+ input_ports[1].set_datatype(PORT_TYPE_NUMBER)
+ input_ports[2].set_datatype(PORT_TYPE_NUMBER)
+ return ..()
+
+/obj/item/circuit_component/compare/comparison/do_comparisons(list/ports)
+ if(length(ports) < input_port_amount)
+ return FALSE
+
+ // Comparison component only compares the first two ports
+ var/input1 = input_ports[1].input_value
+ var/input2 = input_ports[2].input_value
+
+ switch(current_option)
+ if(COMP_COMPARISON_EQUAL)
+ return input1 == input2
+ if(COMP_COMPARISON_NOT_EQUAL)
+ return input1 != input2
+ if(COMP_COMPARISON_GREATER_THAN)
+ return input1 > input2
+ if(COMP_COMPARISON_GREATER_THAN_OR_EQUAL)
+ return input1 >= input2
+ if(COMP_COMPARISON_LESS_THAN)
+ return input1 < input2
+ if(COMP_COMPARISON_LESS_THAN_OR_EQUAL)
+ return input1 <= input2
diff --git a/code/modules/wiremod/components/math/length.dm b/code/modules/wiremod/components/math/length.dm
new file mode 100644
index 0000000000000..8717083fc2ef9
--- /dev/null
+++ b/code/modules/wiremod/components/math/length.dm
@@ -0,0 +1,33 @@
+/**
+ * # Length Component
+ *
+ * Return the length of an input
+ */
+/obj/item/circuit_component/length
+ display_name = "Length"
+ display_desc = "A component that returns the length of its input."
+
+ /// The input port
+ var/datum/port/input/input_port
+
+ /// The result from the output
+ var/datum/port/output/output
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+/obj/item/circuit_component/length/Initialize()
+ . = ..()
+ input_port = add_input_port("Input", PORT_TYPE_ANY)
+
+ output = add_output_port("Length", PORT_TYPE_NUMBER)
+
+/obj/item/circuit_component/length/Destroy()
+ input_port = null
+ output = null
+ return ..()
+
+/obj/item/circuit_component/length/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ output.set_output(length(input_port.input_value))
diff --git a/code/modules/wiremod/components/math/logic.dm b/code/modules/wiremod/components/math/logic.dm
new file mode 100644
index 0000000000000..efb6b410b0e81
--- /dev/null
+++ b/code/modules/wiremod/components/math/logic.dm
@@ -0,0 +1,45 @@
+/**
+ * # Logic Component
+ *
+ * General logic unit with AND OR capabilities
+ */
+/obj/item/circuit_component/compare/logic
+ display_name = "Logic"
+ display_desc = "A component with 'and' and 'or' capabilities."
+
+/obj/item/circuit_component/compare/logic/populate_options()
+ var/static/component_options = list(
+ COMP_LOGIC_AND,
+ COMP_LOGIC_OR,
+ COMP_LOGIC_XOR,
+ )
+ options = component_options
+
+/obj/item/circuit_component/compare/logic/do_comparisons(list/ports)
+ . = FALSE
+ // Used by XOR
+ var/total_ports = 0
+ var/total_true_ports = 0
+ for(var/datum/port/input/port as anything in ports)
+ if(isnull(port.input_value) && isnull(port.connected_port))
+ continue
+
+ total_ports += 1
+ switch(current_option)
+ if(COMP_LOGIC_AND)
+ if(!port.input_value)
+ return FALSE
+ . = TRUE
+ if(COMP_LOGIC_OR)
+ if(port.input_value)
+ return TRUE
+ if(COMP_LOGIC_XOR)
+ if(port.input_value)
+ . = TRUE
+ total_true_ports += 1
+
+ if(current_option == COMP_LOGIC_XOR)
+ if(total_ports == total_true_ports)
+ return FALSE
+ if(.)
+ return TRUE
diff --git a/code/modules/wiremod/components/math/not.dm b/code/modules/wiremod/components/math/not.dm
new file mode 100644
index 0000000000000..8e68dfde47532
--- /dev/null
+++ b/code/modules/wiremod/components/math/not.dm
@@ -0,0 +1,33 @@
+/**
+ * # Logic Component
+ *
+ * General logic unit with AND OR capabilities
+ */
+/obj/item/circuit_component/not
+ display_name = "Not"
+ display_desc = "A component that inverts its input."
+
+ /// The input port
+ var/datum/port/input/input_port
+
+ /// The result from the output
+ var/datum/port/output/result
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+/obj/item/circuit_component/not/Initialize()
+ . = ..()
+ input_port = add_input_port("Input", PORT_TYPE_ANY)
+
+ result = add_output_port("Result", PORT_TYPE_NUMBER)
+
+/obj/item/circuit_component/not/Destroy()
+ input_port = null
+ result = null
+ return ..()
+
+/obj/item/circuit_component/not/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ result.set_output(!input_port.input_value)
diff --git a/code/modules/wiremod/components/math/random.dm b/code/modules/wiremod/components/math/random.dm
new file mode 100644
index 0000000000000..4fe4411835362
--- /dev/null
+++ b/code/modules/wiremod/components/math/random.dm
@@ -0,0 +1,45 @@
+/**
+ * # Random Component
+ *
+ * Generates a random number between specific values
+ */
+/obj/item/circuit_component/random
+ display_name = "Random"
+ display_desc = "A component that returns random values."
+
+ /// The minimum value that the random number can be
+ var/datum/port/input/minimum
+ /// The maximum value that the random number can be
+ var/datum/port/input/maximum
+
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+ /// The result from the output
+ var/datum/port/output/output
+
+/obj/item/circuit_component/random/Initialize()
+ . = ..()
+ minimum = add_input_port("Minimum", PORT_TYPE_NUMBER, FALSE)
+ maximum = add_input_port("Maximum", PORT_TYPE_NUMBER, FALSE)
+
+ output = add_output_port("Output", PORT_TYPE_NUMBER)
+
+/obj/item/circuit_component/random/Destroy()
+ minimum = null
+ maximum = null
+ output = null
+ return ..()
+
+/obj/item/circuit_component/random/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ var/min_val = minimum.input_value || 0
+ var/max_val = maximum.input_value || 0
+
+ if(max_val < min_val)
+ output.set_output(0)
+ return
+
+ output.set_output(rand(min_val, max_val))
diff --git a/code/modules/wiremod/components/math/round.dm b/code/modules/wiremod/components/math/round.dm
new file mode 100644
index 0000000000000..db1281192ca96
--- /dev/null
+++ b/code/modules/wiremod/components/math/round.dm
@@ -0,0 +1,57 @@
+/**
+ * # Round Component
+ *
+ * This component can round, floor, and ceil number inputs.
+ *
+ */
+
+/obj/item/circuit_component/round
+ display_name = "Round"
+ display_desc = "A component capable of cutting off messy decimal values off a number."
+
+ /// The input port
+ var/datum/port/input/input
+
+ /// The result from the output
+ var/datum/port/output/output
+
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+/obj/item/circuit_component/round/populate_options()
+ options = list(
+ COMP_ROUND_ROUND,
+ COMP_ROUND_FLOOR,
+ COMP_ROUND_CEIL,
+ )
+
+/obj/item/circuit_component/round/Initialize()
+ . = ..()
+
+ input = add_input_port("Input", PORT_TYPE_NUMBER)
+
+ output = add_output_port("Output", PORT_TYPE_NUMBER)
+
+/obj/item/circuit_component/round/Destroy()
+ input = null
+ output = null
+ return ..()
+
+/obj/item/circuit_component/round/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ var/value = input.input_value
+ if(isnull(value))
+ output.set_output(null) //Pass the null along
+ return
+
+ switch(current_option)
+ if(COMP_ROUND_ROUND)
+ value = round(value,1)
+ if(COMP_ROUND_FLOOR)
+ value = FLOOR(value,1)
+ if(COMP_ROUND_CEIL)
+ value = CEILING(value,1)
+
+ output.set_output(value)
diff --git a/code/modules/wiremod/components/math/trig.dm b/code/modules/wiremod/components/math/trig.dm
new file mode 100644
index 0000000000000..a9c07324c5fa0
--- /dev/null
+++ b/code/modules/wiremod/components/math/trig.dm
@@ -0,0 +1,128 @@
+/**
+ * # Trig Component
+ *
+ * Math trig, not a trigger
+ * Does trig stuff
+ */
+
+ //Generic class for other trig components
+/obj/item/circuit_component/trig
+ display_name = "Generic Trigonometry"
+ display_desc = "A useless component that all trigonometric based components are built off of."
+
+ /// The input port
+ var/datum/port/input/input
+
+ /// The result from the output
+ var/datum/port/output/output
+
+ //An increase in power usage due to more complex calculations
+ power_usage_per_input = 2
+
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+
+/obj/item/circuit_component/trig/Initialize()
+ . = ..()
+ input = add_input_port("Input", PORT_TYPE_NUMBER)
+ output = add_output_port("Output", PORT_TYPE_NUMBER)
+
+/obj/item/circuit_component/trig/Destroy()
+ input = null
+ output = null
+ return ..()
+
+//The output is set to the return value of this proc on input received
+/obj/item/circuit_component/trig/proc/do_calculation(value)
+
+/obj/item/circuit_component/trig/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ //The output is based on the override of do_calculation()
+ output.set_output(do_calculation(input.input_value))
+
+
+//Does your typical Sine, Cosine, and Tangent, as well as their inverses
+/obj/item/circuit_component/trig/trig
+ display_name = "Trigonometry"
+ display_desc = "A component capable of trigonometric functions."
+
+/obj/item/circuit_component/trig/trig/populate_options()
+ options = list(
+ COMP_TRIG_SINE,
+ COMP_TRIG_COSINE,
+ COMP_TRIG_TANGENT,
+ COMP_TRIG_ASINE,
+ COMP_TRIG_ACOSINE,
+ COMP_TRIG_ATANGENT
+ )
+
+/obj/item/circuit_component/trig/trig/do_calculation(value)
+
+ switch(current_option)
+ if(COMP_TRIG_ASINE)
+ return (value >= -1 && value <= 1) ? TORADIANS(arcsin(value)) : null
+ if(COMP_TRIG_ACOSINE)
+ return (value >= -1 && value <= 1) ? TORADIANS(arccos(value)) * PI/180 : null
+ if(COMP_TRIG_ATANGENT)
+ return arctan(value) * PI/180
+ value = TODEGREES(value) //apparently BYOND doesn't believe in the almighty radian
+ switch(current_option)
+ if(COMP_TRIG_COSINE)
+ return cos(value)
+ if(COMP_TRIG_SINE)
+ return sin(value)
+ if(COMP_TRIG_TANGENT)
+ return cos(value) == 0 ? null : tan(value)
+
+
+//Performs Secant, Cosecant, and Cotangent
+/obj/item/circuit_component/trig/adv_trig
+ display_name = "Advanced Trigonometry"
+ display_desc = "Following outstanding advancements in the field of Mathematics, NanoTrasen scientist have discovered how to take the reciprical of trignometric functions"
+
+
+/obj/item/circuit_component/trig/adv_trig/populate_options()
+ options = list(
+ COMP_TRIG_SECANT,
+ COMP_TRIG_COSECANT,
+ COMP_TRIG_COTANGENT
+ )
+
+/obj/item/circuit_component/trig/adv_trig/do_calculation(value)
+ value = TODEGREES(value)
+ switch(current_option)
+ if(COMP_TRIG_SECANT)
+ return cos(value) == 0 ? null : SEC(value)
+ if(COMP_TRIG_COSECANT)
+ return sin(value) == 0 ? null : CSC(value)
+ if(COMP_TRIG_COTANGENT)
+ return sin(value) == 0 ? null : (cos(value) * CSC(value)) //The define for COT uses 1/tan(x), which throws a divide by zero error when x = pi/2 + kpi where k is an integer
+
+
+//Hyperbolic Sine and Cosine
+/obj/item/circuit_component/trig/hyper_trig
+ display_name = "Hyperbolic Trigonometry"
+ display_desc = "This component makes all your trig calculations be based on hyperbolas and natural exponentials instead of circles"
+
+/obj/item/circuit_component/trig/hyper_trig/populate_options()
+ options = list(
+ COMP_TRIG_HYPERBOLIC_SINE,
+ COMP_TRIG_HYPERBOLIC_COSINE,
+ COMP_TRIG_AHYPERBOLIC_SINE,
+ COMP_TRIG_AHYPERBOLIC_COSINE,
+ )
+
+/obj/item/circuit_component/trig/hyper_trig/do_calculation(value)
+
+ switch(current_option)
+ if(COMP_TRIG_HYPERBOLIC_COSINE)
+ return (NUM_E**value + NUM_E**(-value))/2 //I suppose this could be used for exponents, using the identity cosh(x) + sinh(x) = e^x, I might look into making this more efficent if people end up using this for that
+ if(COMP_TRIG_HYPERBOLIC_SINE)
+ return (NUM_E**value - NUM_E**(-value))/2
+ if(COMP_TRIG_AHYPERBOLIC_SINE)
+ return log(value+sqrt(value**2+1))
+ if(COMP_TRIG_AHYPERBOLIC_COSINE)
+ return value < 1 ? null : log(value+sqrt(value**2-1))
diff --git a/code/modules/wiremod/components/string/concat.dm b/code/modules/wiremod/components/string/concat.dm
new file mode 100644
index 0000000000000..3a4aeccca35a0
--- /dev/null
+++ b/code/modules/wiremod/components/string/concat.dm
@@ -0,0 +1,45 @@
+/**
+ * # Concatenate Component
+ *
+ * General string concatenation component. Puts strings together.
+ */
+/obj/item/circuit_component/concat
+ display_name = "Concatenate"
+ display_desc = "A component that combines strings."
+
+ /// The amount of input ports to have
+ var/input_port_amount = 4
+
+ /// The result from the output
+ var/datum/port/output/output
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+/obj/item/circuit_component/concat/Initialize()
+ . = ..()
+ for(var/port_id in 1 to input_port_amount)
+ var/letter = ascii2text(text2ascii("A") + (port_id-1))
+ add_input_port(letter, PORT_TYPE_STRING)
+
+ output = add_output_port("Output", PORT_TYPE_STRING)
+
+/obj/item/circuit_component/concat/Destroy()
+ output = null
+ return ..()
+
+/obj/item/circuit_component/concat/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ var/result = ""
+ var/list/ports = input_ports.Copy()
+ ports -= trigger_input
+
+ for(var/datum/port/input/input_port as anything in ports)
+ var/value = input_port.input_value
+ if(isnull(value))
+ continue
+
+ result += "[value]"
+
+ output.set_output(result)
diff --git a/code/modules/wiremod/components/string/contains.dm b/code/modules/wiremod/components/string/contains.dm
new file mode 100644
index 0000000000000..61c48a476b20b
--- /dev/null
+++ b/code/modules/wiremod/components/string/contains.dm
@@ -0,0 +1,35 @@
+/**
+ * # String Contains Component
+ *
+ * Checks if a string contains a word/letter
+ */
+/obj/item/circuit_component/compare/contains
+ display_name = "String Contains"
+ display_desc = "Checks if a string contains a word/letter"
+
+ input_port_amount = 0
+
+ var/datum/port/input/needle
+ var/datum/port/input/haystack
+
+/obj/item/circuit_component/compare/contains/load_custom_ports()
+ needle = add_input_port("Needle", PORT_TYPE_STRING)
+ haystack = add_input_port("Haystack", PORT_TYPE_STRING)
+
+/obj/item/circuit_component/compare/contains/Destroy()
+ needle = null
+ haystack = null
+ return ..()
+
+
+/obj/item/circuit_component/compare/contains/do_comparisons(list/ports)
+ if(length(ports) < input_port_amount)
+ return
+
+ var/to_find = needle.input_value
+ var/to_search = haystack.input_value
+
+ if(!to_find || !to_search)
+ return
+
+ return findtext(to_search, to_find)
diff --git a/code/modules/wiremod/components/string/textcase.dm b/code/modules/wiremod/components/string/textcase.dm
new file mode 100644
index 0000000000000..ba4e20dab06e1
--- /dev/null
+++ b/code/modules/wiremod/components/string/textcase.dm
@@ -0,0 +1,51 @@
+/**
+ * # Text Component
+ *
+ * Either makes the text upper case or lower case.
+ */
+/obj/item/circuit_component/textcase
+ display_name = "Text Case"
+ display_desc = "A component that makes its input uppercase or lowercase."
+
+ /// The input port
+ var/datum/port/input/input_port
+
+ /// The result of the text operation
+ var/datum/port/output/output
+
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+/obj/item/circuit_component/textcase/populate_options()
+ var/static/component_options = list(
+ COMP_TEXT_LOWER,
+ COMP_TEXT_UPPER,
+ )
+ options = component_options
+
+/obj/item/circuit_component/textcase/Initialize()
+ . = ..()
+ input_port = add_input_port("Input", PORT_TYPE_STRING)
+ output = add_output_port("Output", PORT_TYPE_STRING)
+
+/obj/item/circuit_component/textcase/Destroy()
+ input_port = null
+ output = null
+ return ..()
+
+/obj/item/circuit_component/textcase/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ var/value = input_port.input_value
+ if(isnull(value))
+ return
+
+ var/result
+ switch(current_option)
+ if(COMP_TEXT_LOWER)
+ result = lowertext(value)
+ if(COMP_TEXT_UPPER)
+ result = uppertext(value)
+
+ output.set_output(result)
diff --git a/code/modules/wiremod/components/string/tostring.dm b/code/modules/wiremod/components/string/tostring.dm
new file mode 100644
index 0000000000000..dd8fbdc56ebe3
--- /dev/null
+++ b/code/modules/wiremod/components/string/tostring.dm
@@ -0,0 +1,44 @@
+/**
+ * # To String Component
+ *
+ * Converts any value into a string
+ */
+/obj/item/circuit_component/tostring
+ display_name = "To String"
+ display_desc = "A component that converts its input to text."
+
+ /// The input port
+ var/datum/port/input/input_port
+
+ /// The result from the output
+ var/datum/port/output/output
+
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+ var/max_range = 5
+
+/obj/item/circuit_component/tostring/Initialize()
+ . = ..()
+ input_port = add_input_port("Input", PORT_TYPE_ANY)
+
+ output = add_output_port("Output", PORT_TYPE_STRING)
+
+/obj/item/circuit_component/tostring/Destroy()
+ input_port = null
+ output = null
+ return ..()
+
+/obj/item/circuit_component/tostring/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ var/input_value = input_port.input_value
+ if(isatom(input_value))
+ var/turf/location = get_turf(src)
+ var/atom/object = input_value
+ if(object.z != location.z || get_dist(location, object) > max_range)
+ output.set_output(PORT_TYPE_ATOM)
+ return
+
+ output.set_output("[input_value]")
diff --git a/code/modules/wiremod/components/usb_port.dm b/code/modules/wiremod/components/usb_port.dm
new file mode 100644
index 0000000000000..4a1784c7e1e6e
--- /dev/null
+++ b/code/modules/wiremod/components/usb_port.dm
@@ -0,0 +1,169 @@
+/// Opens up a USB port that can be connected to by circuits, creating registerable circuit components
+/datum/component/usb_port
+ /// The component types to create when something plugs in
+ var/list/circuit_component_types
+
+ /// The currently connected circuit
+ var/obj/item/integrated_circuit/attached_circuit
+
+ /// The currently connected USB cable
+ var/datum/weakref/usb_cable_ref
+
+ /// The components inside the parent
+ var/list/obj/item/circuit_component/circuit_components
+
+ /// The beam connecting the USB cable to the machine
+ var/datum/beam/usb_cable_beam
+
+/datum/component/usb_port/Initialize(list/circuit_component_types)
+ if (!isatom(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ circuit_components = list()
+
+ for(var/circuit_component_type in circuit_component_types)
+ var/obj/item/circuit_component/circuit_component = new circuit_component_type(null)
+ circuit_components += circuit_component
+
+/datum/component/usb_port/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_ATOM_USB_CABLE_TRY_ATTACH, .proc/on_atom_usb_cable_try_attach)
+ RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/on_moved)
+ RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine)
+
+ for(var/obj/item/circuit_component/component as anything in circuit_components)
+ component.register_usb_parent(parent)
+
+/datum/component/usb_port/UnregisterFromParent()
+ UnregisterSignal(parent, list(
+ COMSIG_ATOM_USB_CABLE_TRY_ATTACH,
+ COMSIG_MOVABLE_MOVED,
+ COMSIG_PARENT_EXAMINE,
+ ))
+
+ for(var/obj/item/circuit_component/component as anything in circuit_components)
+ component.unregister_usb_parent(parent)
+
+ unregister_circuit_signals()
+ attached_circuit = null
+
+/datum/component/usb_port/Destroy()
+ QDEL_LIST(circuit_components)
+ QDEL_NULL(usb_cable_beam)
+
+ attached_circuit = null
+ usb_cable_ref = null
+
+ return ..()
+
+/datum/component/usb_port/proc/unregister_circuit_signals()
+ if (isnull(attached_circuit))
+ return
+
+ UnregisterSignal(attached_circuit, list(
+ COMSIG_CIRCUIT_SHELL_REMOVED,
+ COMSIG_PARENT_QDELETING,
+ ))
+
+ var/shell = attached_circuit.shell
+ if (!isnull(shell))
+ UnregisterSignal(shell, COMSIG_MOVABLE_MOVED)
+ UnregisterSignal(shell, COMSIG_PARENT_EXAMINE)
+
+/datum/component/usb_port/proc/attach_circuit_components(obj/item/integrated_circuit/circuitboard)
+ for(var/obj/item/circuit_component/component as anything in circuit_components)
+ circuitboard.add_component(component)
+ RegisterSignal(component, COMSIG_CIRCUIT_COMPONENT_REMOVED, .proc/on_circuit_component_removed)
+
+/datum/component/usb_port/proc/on_examine(datum/source, mob/user, list/examine_text)
+ SIGNAL_HANDLER
+
+ if (isnull(attached_circuit))
+ examine_text += "There is a USB port on the front."
+ else
+ examine_text += "[attached_circuit.shell || attached_circuit] is connected to [parent.p_them()] by a USB port."
+
+/datum/component/usb_port/proc/on_examine_shell(datum/source, mob/user, list/examine_text)
+ SIGNAL_HANDLER
+
+ examine_text += "[source.p_they(TRUE)] [source.p_are()] attached to [parent] with a USB cable."
+
+/datum/component/usb_port/proc/on_atom_usb_cable_try_attach(datum/source, obj/item/usb_cable/connecting_cable, mob/user)
+ SIGNAL_HANDLER
+
+ var/atom/atom_parent = parent
+
+ if (!isnull(attached_circuit))
+ atom_parent.balloon_alert(user, "usb already connected")
+ return COMSIG_CANCEL_USB_CABLE_ATTACK
+
+ if (isnull(connecting_cable.attached_circuit))
+ connecting_cable.balloon_alert(user, "connect to a shell first")
+ return COMSIG_CANCEL_USB_CABLE_ATTACK
+
+ if (!IN_GIVEN_RANGE(connecting_cable.attached_circuit, parent, USB_CABLE_MAX_RANGE))
+ connecting_cable.balloon_alert(user, "too far away")
+ return COMSIG_CANCEL_USB_CABLE_ATTACK
+
+ usb_cable_ref = WEAKREF(connecting_cable)
+ attached_circuit = connecting_cable.attached_circuit
+
+ connecting_cable.forceMove(attached_circuit)
+ attach_circuit_components(attached_circuit)
+ attached_circuit.interact(user)
+
+ usb_cable_beam = atom_parent.Beam(attached_circuit.shell, "usb_cable_beam", 'icons/obj/wiremod.dmi')
+
+ RegisterSignal(attached_circuit, COMSIG_CIRCUIT_SHELL_REMOVED, .proc/on_circuit_shell_removed)
+ RegisterSignal(attached_circuit, COMSIG_PARENT_QDELETING, .proc/on_circuit_deleting)
+ RegisterSignal(attached_circuit.shell, COMSIG_MOVABLE_MOVED, .proc/on_moved)
+ RegisterSignal(attached_circuit.shell, COMSIG_PARENT_EXAMINE, .proc/on_examine_shell)
+
+ return COMSIG_USB_CABLE_ATTACHED
+
+/datum/component/usb_port/proc/on_moved()
+ SIGNAL_HANDLER
+
+ if (isnull(attached_circuit))
+ return
+
+ if (IN_GIVEN_RANGE(attached_circuit, parent, USB_CABLE_MAX_RANGE))
+ return
+
+ detach()
+
+/datum/component/usb_port/proc/on_circuit_deleting()
+ SIGNAL_HANDLER
+
+ detach()
+ qdel(usb_cable_ref)
+
+/datum/component/usb_port/proc/on_circuit_component_removed(datum/source)
+ SIGNAL_HANDLER
+
+ detach()
+
+/datum/component/usb_port/proc/on_circuit_shell_removed()
+ SIGNAL_HANDLER
+
+ detach()
+
+/datum/component/usb_port/proc/detach()
+ var/obj/item/usb_cable/usb_cable = usb_cable_ref?.resolve()
+ if (isnull(usb_cable))
+ return
+
+ for(var/obj/item/circuit_component/component as anything in circuit_components)
+ UnregisterSignal(component, COMSIG_CIRCUIT_COMPONENT_REMOVED)
+ attached_circuit.remove_component(component)
+ component.moveToNullspace()
+
+ unregister_circuit_signals()
+
+ var/atom/atom_parent = parent
+ usb_cable.forceMove(atom_parent.drop_location())
+ usb_cable.balloon_alert_to_viewers("snap")
+
+ attached_circuit = null
+ usb_cable_ref = null
+
+ QDEL_NULL(usb_cable_beam)
diff --git a/code/modules/wiremod/components/utility/clock.dm b/code/modules/wiremod/components/utility/clock.dm
new file mode 100644
index 0000000000000..e856b1ee4d0ec
--- /dev/null
+++ b/code/modules/wiremod/components/utility/clock.dm
@@ -0,0 +1,59 @@
+/**
+ * # Clock Component
+ *
+ * Fires every tick of the circuit timer SS
+ */
+/obj/item/circuit_component/clock
+ display_name = "Clock"
+ display_desc = "A component that repeatedly fires."
+
+ /// Whether the clock is on or not
+ var/datum/port/input/on
+
+ /// The signal from this clock component
+ var/datum/port/output/signal
+
+/obj/item/circuit_component/clock/get_ui_notices()
+ . = ..()
+ . += create_ui_notice("Clock Interval: [DisplayTimeText(COMP_CLOCK_DELAY)]", "orange", "clock")
+
+/obj/item/circuit_component/clock/Initialize()
+ . = ..()
+ on = add_input_port("On", PORT_TYPE_NUMBER)
+
+ signal = add_output_port("Signal", PORT_TYPE_SIGNAL)
+
+/obj/item/circuit_component/clock/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ if(on.input_value)
+ start_process()
+ else
+ stop_process()
+
+/obj/item/circuit_component/clock/Destroy()
+ on = null
+ signal = null
+ stop_process()
+ return ..()
+
+/obj/item/circuit_component/clock/process(delta_time)
+ signal.set_output(COMPONENT_SIGNAL)
+
+/**
+ * Adds the component to the SSclock_component process list
+ *
+ * Starts ticking to send signals between periods of time
+ */
+/obj/item/circuit_component/clock/proc/start_process()
+ START_PROCESSING(SSclock_component, src)
+
+/**
+ * Removes the component to the SSclock_component process list
+ *
+ * Signals stop getting sent.
+ */
+/obj/item/circuit_component/clock/proc/stop_process()
+ STOP_PROCESSING(SSclock_component, src)
diff --git a/code/modules/wiremod/components/utility/combiner.dm b/code/modules/wiremod/components/utility/combiner.dm
new file mode 100644
index 0000000000000..7857f0f260a22
--- /dev/null
+++ b/code/modules/wiremod/components/utility/combiner.dm
@@ -0,0 +1,53 @@
+/**
+ * # Combiner Component
+ *
+ * Combines multiple inputs into 1 output port.
+ */
+/obj/item/circuit_component/combiner
+ display_name = "Combiner"
+ display_desc = "A component that combines multiple inputs to provide 1 output."
+
+ /// The amount of input ports to have
+ var/input_port_amount = 4
+
+ var/datum/port/output/output_port
+
+ var/current_type
+
+/obj/item/circuit_component/combiner/populate_options()
+ var/static/component_options = list(
+ COMP_TYPE_ANY,
+ PORT_TYPE_STRING,
+ PORT_TYPE_NUMBER,
+ PORT_TYPE_LIST,
+ PORT_TYPE_ATOM,
+ PORT_TYPE_SIGNAL,
+ )
+ options = component_options
+
+/obj/item/circuit_component/combiner/Initialize()
+ . = ..()
+ current_option = COMP_TYPE_ANY
+ current_type = COMP_TYPE_ANY
+ for(var/port_id in 1 to input_port_amount)
+ var/letter = ascii2text(text2ascii("A") + (port_id-1))
+ add_input_port(letter, PORT_TYPE_ANY)
+ output_port = add_output_port("Output", PORT_TYPE_ANY)
+
+/obj/item/circuit_component/combiner/Destroy()
+ output_port = null
+ return ..()
+
+/obj/item/circuit_component/combiner/input_received(datum/port/input/port)
+ . = ..()
+ if(current_type != current_option && (current_option != COMP_TYPE_ANY || current_type != PORT_TYPE_ANY))
+ current_type = current_option
+ if(current_type == COMP_TYPE_ANY)
+ current_type = PORT_TYPE_ANY
+ for(var/datum/port/input/input_port as anything in input_ports)
+ input_port.set_datatype(current_type)
+ output_port.set_datatype(current_type)
+
+ if(. || !port)
+ return TRUE
+ output_port.set_output(port.input_value)
diff --git a/code/modules/wiremod/components/utility/delay.dm b/code/modules/wiremod/components/utility/delay.dm
new file mode 100644
index 0000000000000..a7e43f56090b1
--- /dev/null
+++ b/code/modules/wiremod/components/utility/delay.dm
@@ -0,0 +1,44 @@
+/**
+ * # Delay Component
+ *
+ * Delays a signal by a specified duration.
+ */
+/obj/item/circuit_component/delay
+ display_name = "Delay"
+ display_desc = "A component that delays a signal by a specified duration."
+
+ /// Amount to delay by
+ var/datum/port/input/delay_amount
+ /// Input signal to fire the delay
+ var/datum/port/input/trigger
+
+ /// The output of the signal
+ var/datum/port/output/output
+
+/obj/item/circuit_component/delay/Initialize()
+ . = ..()
+ delay_amount = add_input_port("Delay", PORT_TYPE_NUMBER, FALSE)
+ trigger = add_input_port("Trigger", PORT_TYPE_SIGNAL)
+
+ output = add_output_port("Result", PORT_TYPE_SIGNAL)
+
+/obj/item/circuit_component/delay/Destroy()
+ output = null
+ trigger = null
+ delay_amount = null
+ return ..()
+
+/obj/item/circuit_component/delay/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ if(!COMPONENT_TRIGGERED_BY(trigger, port))
+ return
+
+ var/delay = delay_amount.input_value
+ if(delay > COMP_DELAY_MIN_VALUE)
+ // Convert delay into deciseconds
+ addtimer(CALLBACK(output, /datum/port/output.proc/set_output, trigger.input_value), delay*10)
+ else
+ output.set_output(trigger.input_value)
diff --git a/code/modules/wiremod/components/utility/multiplexer.dm b/code/modules/wiremod/components/utility/multiplexer.dm
new file mode 100644
index 0000000000000..9676729b06494
--- /dev/null
+++ b/code/modules/wiremod/components/utility/multiplexer.dm
@@ -0,0 +1,65 @@
+/**
+ * # Combiner Component
+ *
+ * Combines multiple inputs into 1 output port.
+ */
+/obj/item/circuit_component/multiplexer
+ display_name = "Multiplexer"
+ display_desc = "A component that allows you to selectively choose which input port provides an output. The first port is the selector and takes a number between 1 and the maximum port amount."
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+ /// The port to select from, goes from 1 to input_port_amount
+ var/datum/port/input/input_port
+
+ /// The amount of input ports to have
+ var/input_port_amount = 4
+
+ var/datum/port/output/output_port
+
+ /// Current type of the ports
+ var/current_type
+
+ /// The multiplexer inputs. These are what get selected for the output by the input_port.
+ var/list/datum/port/input/multiplexer_inputs
+
+/obj/item/circuit_component/multiplexer/populate_options()
+ var/static/component_options = list(
+ COMP_TYPE_ANY,
+ PORT_TYPE_STRING,
+ PORT_TYPE_NUMBER,
+ PORT_TYPE_LIST,
+ PORT_TYPE_ATOM,
+ )
+ options = component_options
+
+/obj/item/circuit_component/multiplexer/Initialize()
+ . = ..()
+ current_option = COMP_TYPE_ANY
+ current_type = COMP_TYPE_ANY
+ input_port = add_input_port("Selector", PORT_TYPE_NUMBER, default = 1)
+ multiplexer_inputs = list()
+ for(var/port_id in 1 to input_port_amount)
+ multiplexer_inputs += add_input_port("Port [port_id]", PORT_TYPE_ANY)
+ output_port = add_output_port("Output", PORT_TYPE_ANY)
+
+/obj/item/circuit_component/multiplexer/Destroy()
+ output_port = null
+ multiplexer_inputs.Cut()
+ multiplexer_inputs = null
+ return ..()
+
+
+/obj/item/circuit_component/multiplexer/input_received(datum/port/input/port)
+ . = ..()
+ if(current_type != current_option && (current_option != COMP_TYPE_ANY || current_type != PORT_TYPE_ANY))
+ current_type = current_option
+ if(current_type == COMP_TYPE_ANY)
+ current_type = PORT_TYPE_ANY
+ for(var/datum/port/input/input_port as anything in multiplexer_inputs)
+ input_port.set_datatype(current_type)
+ output_port.set_datatype(current_type)
+
+ input_port.set_input(clamp(input_port.input_value || 1, 1, input_port_amount), FALSE)
+ if(.)
+ return TRUE
+ output_port.set_output(multiplexer_inputs[input_port.input_value].input_value)
diff --git a/code/modules/wiremod/components/utility/ram.dm b/code/modules/wiremod/components/utility/ram.dm
new file mode 100644
index 0000000000000..9c8e120cd8168
--- /dev/null
+++ b/code/modules/wiremod/components/utility/ram.dm
@@ -0,0 +1,52 @@
+/**
+ * # RAM Component
+ *
+ * Stores the current input when triggered.
+ * Players will need to think logically when using the RAM component
+ * as there can be race conditions due to the delays of transferring signals
+ */
+/obj/item/circuit_component/ram
+ display_name = "RAM"
+ display_desc = "A component that retains a variable."
+
+ /// The input to store
+ var/datum/port/input/input_port
+ /// The trigger to store the current value of the input
+ var/datum/port/input/trigger
+ /// Clears the current input
+ var/datum/port/input/clear
+
+ /// The current set value
+ var/datum/port/output/output
+
+/obj/item/circuit_component/ram/Initialize()
+ . = ..()
+ input_port = add_input_port("Input", PORT_TYPE_ANY)
+ trigger = add_input_port("Store", PORT_TYPE_SIGNAL)
+ clear = add_input_port("Clear", PORT_TYPE_SIGNAL)
+
+ output = add_output_port("Stored Value", PORT_TYPE_ANY)
+
+/obj/item/circuit_component/ram/Destroy()
+ input_port = null
+ trigger = null
+ clear = null
+ output = null
+ return ..()
+
+/obj/item/circuit_component/ram/input_received(datum/port/input/port)
+ . = ..()
+ match_port_datatype(input_port, output)
+ if(.)
+ return
+
+ if(COMPONENT_TRIGGERED_BY(clear, port))
+ output.set_output(null)
+ return
+
+ if(!COMPONENT_TRIGGERED_BY(trigger, port))
+ return
+
+ var/input_val = input_port.input_value
+
+ output.set_output(input_val)
diff --git a/code/modules/wiremod/components/utility/typecheck.dm b/code/modules/wiremod/components/utility/typecheck.dm
new file mode 100644
index 0000000000000..426d583d47653
--- /dev/null
+++ b/code/modules/wiremod/components/utility/typecheck.dm
@@ -0,0 +1,52 @@
+/**
+ * # Typecheck Component
+ *
+ * Checks the type of a value
+ */
+/obj/item/circuit_component/compare/typecheck
+ display_name = "Typecheck"
+ display_desc = "A component that checks the type of its input."
+
+ input_port_amount = 1
+
+GLOBAL_LIST_INIT(comp_typecheck_options, list(
+ PORT_TYPE_STRING,
+ PORT_TYPE_NUMBER,
+ PORT_TYPE_LIST,
+ PORT_TYPE_ATOM,
+ COMP_TYPECHECK_MOB,
+ COMP_TYPECHECK_HUMAN,
+))
+
+/obj/item/circuit_component/compare/typecheck/populate_options()
+ var/static/component_options = list(
+ PORT_TYPE_STRING,
+ PORT_TYPE_NUMBER,
+ PORT_TYPE_LIST,
+ PORT_TYPE_ATOM,
+ COMP_TYPECHECK_MOB,
+ COMP_TYPECHECK_HUMAN,
+ )
+ options = component_options
+
+/obj/item/circuit_component/compare/typecheck/do_comparisons(list/ports)
+ if(!length(ports))
+ return
+ . = FALSE
+
+ // We're only comparing the first port/value. There shouldn't be any more.
+ var/datum/port/input/input_port = ports[1]
+ var/input_val = input_port.input_value
+ switch(current_option)
+ if(PORT_TYPE_STRING)
+ return istext(input_val)
+ if(PORT_TYPE_NUMBER)
+ return isnum(input_val)
+ if(PORT_TYPE_LIST)
+ return islist(input_val)
+ if(PORT_TYPE_ATOM)
+ return isatom(input_val)
+ if(COMP_TYPECHECK_MOB)
+ return ismob(input_val)
+ if(COMP_TYPECHECK_HUMAN)
+ return ishuman(input_val)
diff --git a/code/modules/wiremod/integrated_circuit.dm b/code/modules/wiremod/integrated_circuit.dm
new file mode 100644
index 0000000000000..f7abcbd48a1c9
--- /dev/null
+++ b/code/modules/wiremod/integrated_circuit.dm
@@ -0,0 +1,468 @@
+/**
+ * # Integrated Circuitboard
+ *
+ * A circuitboard that holds components that work together
+ *
+ * Has a limited amount of power.
+ */
+/obj/item/integrated_circuit
+ name = "integrated circuit"
+ icon = 'icons/obj/module.dmi'
+ icon_state = "integrated_circuit"
+ item_state = "electronic"
+
+ /// The name that appears on the shell.
+ var/display_name = ""
+
+ /// The max length of the name.
+ var/label_max_length = 24
+
+ /// The power of the integrated circuit
+ var/obj/item/stock_parts/cell/cell
+
+ /// The shell that this circuitboard is attached to. Used by components.
+ var/atom/movable/shell
+
+ /// The attached components
+ var/list/obj/item/circuit_component/attached_components = list()
+
+ /// Whether the integrated circuit is on or not. Handled by the shell.
+ var/on = FALSE
+
+ /// The ID that is authorized to unlock/lock the shell so that the circuit can/cannot be removed.
+ var/datum/weakref/owner_id
+
+ /// The current examined component. Used in IntegratedCircuit UI
+ var/datum/weakref/examined_component
+
+ /// Set by the shell. Holds the reference to the owner who inserted the component into the shell.
+ var/datum/weakref/inserter_mind
+
+ /// X position of the examined_component
+ var/examined_rel_x = 0
+
+ /// Y position of the examined component
+ var/examined_rel_y = 0
+
+/obj/item/integrated_circuit/Initialize()
+ . = ..()
+ RegisterSignal(src, COMSIG_ATOM_USB_CABLE_TRY_ATTACH, .proc/on_atom_usb_cable_try_attach)
+
+/obj/item/integrated_circuit/loaded/Initialize()
+ . = ..()
+ cell = new /obj/item/stock_parts/cell/high(src)
+
+/obj/item/integrated_circuit/Destroy()
+ for(var/obj/item/circuit_component/to_delete in attached_components)
+ remove_component(to_delete)
+ qdel(to_delete)
+ attached_components.Cut()
+ shell = null
+ examined_component = null
+ owner_id = null
+ QDEL_NULL(cell)
+ return ..()
+
+/obj/item/integrated_circuit/examine(mob/user)
+ . = ..()
+ if(cell)
+ . += "The charge meter reads [cell ? round(cell.percent(), 1) : 0]%."
+ else
+ . += "There is no power cell installed."
+
+/obj/item/integrated_circuit/attackby(obj/item/I, mob/living/user, params)
+ . = ..()
+ if(istype(I, /obj/item/circuit_component))
+ add_component_manually(I, user)
+ return
+
+ if(istype(I, /obj/item/stock_parts/cell))
+ if(cell)
+ balloon_alert(user, "There already is a cell inside!")
+ return
+ if(!user.transferItemToLoc(I, src))
+ return
+ cell = I
+ I.add_fingerprint(user)
+ user.visible_message("[user] inserts a power cell into [src].", "You insert the power cell into [src].")
+ return
+
+ if(istype(I, /obj/item/card/id))
+ balloon_alert(user, "owner id set for [I]")
+ owner_id = WEAKREF(I)
+ return
+
+ if(I.tool_behaviour == TOOL_SCREWDRIVER)
+ if(!cell)
+ return
+ I.play_tool_sound(src)
+ user.visible_message("[user] unscrews the power cell from [src].", "You unscrew the power cell from [src].")
+ cell.forceMove(drop_location())
+ cell = null
+ return
+
+/**
+ * Registers an movable atom as a shell
+ *
+ * No functionality is done here. This is so that input components
+ * can properly register any signals on the shell.
+ * Arguments:
+ * * new_shell - The new shell to register.
+ */
+/obj/item/integrated_circuit/proc/set_shell(atom/movable/new_shell)
+ remove_current_shell()
+ on = TRUE
+ shell = new_shell
+ RegisterSignal(shell, COMSIG_PARENT_QDELETING, .proc/remove_current_shell)
+ for(var/obj/item/circuit_component/attached_component as anything in attached_components)
+ attached_component.register_shell(shell)
+ // Their input ports may be updated with user values, but the outputs haven't updated
+ // because on is FALSE
+ TRIGGER_CIRCUIT_COMPONENT(attached_component, null)
+ if(display_name != "")
+ shell.name = "[initial(shell.name)] ([display_name])"
+
+/**
+ * Unregisters the current shell attached to this circuit.
+ */
+/obj/item/integrated_circuit/proc/remove_current_shell()
+ SIGNAL_HANDLER
+ if(!shell)
+ return
+ shell.name = initial(shell.name)
+ for(var/obj/item/circuit_component/attached_component as anything in attached_components)
+ attached_component.unregister_shell(shell)
+ UnregisterSignal(shell, COMSIG_PARENT_QDELETING)
+ shell = null
+ on = FALSE
+ SEND_SIGNAL(src, COMSIG_CIRCUIT_SHELL_REMOVED)
+
+/**
+ * Adds a component to the circuitboard
+ *
+ * Once the component is added, the ports can be attached to other components
+ */
+/obj/item/integrated_circuit/proc/add_component(obj/item/circuit_component/to_add, mob/living/user)
+ if(to_add.parent)
+ return
+
+ if(SEND_SIGNAL(src, COMSIG_CIRCUIT_ADD_COMPONENT, to_add, user) & COMPONENT_CANCEL_ADD_COMPONENT)
+ return
+
+ if(!to_add.add_to(src))
+ return
+
+ var/success = FALSE
+ if(user)
+ success = user.transferItemToLoc(to_add, src)
+ else
+ success = to_add.forceMove(src)
+
+ if(!success)
+ return
+
+ to_add.rel_x = rand(COMPONENT_MIN_RANDOM_POS, COMPONENT_MAX_RANDOM_POS)
+ to_add.rel_y = rand(COMPONENT_MIN_RANDOM_POS, COMPONENT_MAX_RANDOM_POS)
+ to_add.parent = src
+ attached_components += to_add
+ RegisterSignal(to_add, COMSIG_MOVABLE_MOVED, .proc/component_move_handler)
+ SStgui.update_uis(src)
+
+ if(shell)
+ to_add.register_shell(shell)
+
+/**
+ * Adds a component to the circuitboard through a manual action.
+ */
+/obj/item/integrated_circuit/proc/add_component_manually(obj/item/circuit_component/to_add, mob/living/user)
+ if (SEND_SIGNAL(src, COMSIG_CIRCUIT_ADD_COMPONENT_MANUALLY, to_add, user) & COMPONENT_CANCEL_ADD_COMPONENT)
+ return
+
+ add_component(to_add, user)
+
+/obj/item/integrated_circuit/proc/component_move_handler(obj/item/circuit_component/source)
+ SIGNAL_HANDLER
+ if(source.loc != src)
+ remove_component(source)
+
+/**
+ * Removes a component to the circuitboard
+ *
+ * This removes all connects between the ports
+ */
+/obj/item/integrated_circuit/proc/remove_component(obj/item/circuit_component/to_remove)
+ if(shell)
+ to_remove.unregister_shell(shell)
+
+ UnregisterSignal(to_remove, COMSIG_MOVABLE_MOVED)
+ attached_components -= to_remove
+ to_remove.disconnect()
+ to_remove.parent = null
+ SEND_SIGNAL(to_remove, COMSIG_CIRCUIT_COMPONENT_REMOVED, src)
+ SStgui.update_uis(src)
+ to_remove.removed_from(src)
+
+/obj/item/integrated_circuit/get_cell()
+ return cell
+
+/obj/item/integrated_circuit/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/simple/circuit_assets)
+ )
+
+/obj/item/integrated_circuit/ui_data(mob/user)
+ . = list()
+ .["components"] = list()
+ for(var/obj/item/circuit_component/component as anything in attached_components)
+ var/list/component_data = list()
+ component_data["input_ports"] = list()
+ for(var/datum/port/input/port as anything in component.input_ports)
+ var/current_data = port.input_value
+ if(isatom(current_data)) // Prevent passing the name of the atom.
+ current_data = null
+ component_data["input_ports"] += list(list(
+ "name" = port.name,
+ "type" = port.datatype,
+ "ref" = REF(port), // The ref is the identifier to work out what it is connected to
+ "connected_to" = REF(port.connected_port),
+ "color" = port.color,
+ "current_data" = current_data,
+ ))
+ component_data["output_ports"] = list()
+ for(var/datum/port/output/port as anything in component.output_ports)
+ component_data["output_ports"] += list(list(
+ "name" = port.name,
+ "type" = port.datatype,
+ "ref" = REF(port),
+ "color" = port.color
+ ))
+
+ component_data["name"] = component.display_name
+ component_data["x"] = component.rel_x
+ component_data["y"] = component.rel_y
+ component_data["option"] = component.current_option
+ component_data["options"] = component.options
+ component_data["removable"] = component.removable
+ .["components"] += list(component_data)
+
+ .["display_name"] = display_name
+
+ var/obj/item/circuit_component/examined
+ if(examined_component)
+ examined = examined_component.resolve()
+
+ .["examined_name"] = examined?.display_name
+ .["examined_desc"] = examined?.display_desc
+ .["examined_notices"] = examined?.get_ui_notices()
+ .["examined_rel_x"] = examined_rel_x
+ .["examined_rel_y"] = examined_rel_y
+
+/obj/item/integrated_circuit/ui_host(mob/user)
+ if(shell)
+ return shell
+ return ..()
+
+/obj/item/integrated_circuit/ui_state(mob/user)
+ if(!shell)
+ return GLOB.hands_state
+ return GLOB.physical_obscured_state
+
+/obj/item/integrated_circuit/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "IntegratedCircuit", name)
+ ui.open()
+ ui.set_autoupdate(FALSE)
+
+#define WITHIN_RANGE(id, table) (id >= 1 && id <= length(table))
+
+/obj/item/integrated_circuit/ui_act(action, list/params)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("add_connection")
+ var/input_component_id = text2num(params["input_component_id"])
+ var/output_component_id = text2num(params["output_component_id"])
+ var/input_port_id = text2num(params["input_port_id"])
+ var/output_port_id = text2num(params["output_port_id"])
+ if(!WITHIN_RANGE(input_component_id, attached_components) || !WITHIN_RANGE(output_component_id, attached_components))
+ return
+ var/obj/item/circuit_component/input_component = attached_components[input_component_id]
+ var/obj/item/circuit_component/output_component = attached_components[output_component_id]
+
+ if(!WITHIN_RANGE(input_port_id, input_component.input_ports) || !WITHIN_RANGE(output_port_id, output_component.output_ports))
+ return
+ var/datum/port/input/input_port = input_component.input_ports[input_port_id]
+ var/datum/port/output/output_port = output_component.output_ports[output_port_id]
+
+ if(input_port.datatype && !output_port.compatible_datatype(input_port.datatype))
+ return
+
+ input_port.register_output_port(output_port)
+ . = TRUE
+ if("remove_connection")
+ var/component_id = text2num(params["component_id"])
+ var/is_input = params["is_input"]
+ var/port_id = text2num(params["port_id"])
+
+ if(!WITHIN_RANGE(component_id, attached_components))
+ return
+ var/obj/item/circuit_component/component = attached_components[component_id]
+
+ var/list/port_table
+ if(is_input)
+ port_table = component.input_ports
+ else
+ port_table = component.output_ports
+
+ if(!WITHIN_RANGE(port_id, port_table))
+ return
+
+ var/datum/port/port = port_table[port_id]
+ port.disconnect()
+ . = TRUE
+ if("detach_component")
+ var/component_id = text2num(params["component_id"])
+ if(!WITHIN_RANGE(component_id, attached_components))
+ return
+ var/obj/item/circuit_component/component = attached_components[component_id]
+ if(!component.removable)
+ return
+ component.disconnect()
+ remove_component(component)
+ if(component.loc == src)
+ usr.put_in_hands(component)
+ . = TRUE
+ if("set_component_coordinates")
+ var/component_id = text2num(params["component_id"])
+ if(!WITHIN_RANGE(component_id, attached_components))
+ return
+ var/obj/item/circuit_component/component = attached_components[component_id]
+ component.rel_x = min(max(-COMPONENT_MAX_POS, text2num(params["rel_x"])), COMPONENT_MAX_POS)
+ component.rel_y = min(max(-COMPONENT_MAX_POS, text2num(params["rel_y"])), COMPONENT_MAX_POS)
+ . = TRUE
+ if("set_component_option")
+ var/component_id = text2num(params["component_id"])
+ if(!WITHIN_RANGE(component_id, attached_components))
+ return
+ var/obj/item/circuit_component/component = attached_components[component_id]
+ var/option = params["option"]
+ if(!(option in component.options))
+ return
+ component.set_option(option)
+ . = TRUE
+ if("set_component_input")
+ var/component_id = text2num(params["component_id"])
+ var/port_id = text2num(params["port_id"])
+ if(!WITHIN_RANGE(component_id, attached_components))
+ return
+ var/obj/item/circuit_component/component = attached_components[component_id]
+ if(!WITHIN_RANGE(port_id, component.input_ports))
+ return
+ var/datum/port/input/port = component.input_ports[port_id]
+
+ if(port.connected_port)
+ return
+
+ if(params["set_null"])
+ port.set_input(null)
+ return TRUE
+
+ if(params["marked_atom"])
+ if(port.datatype != PORT_TYPE_ATOM && port.datatype != PORT_TYPE_ANY)
+ return
+ var/obj/item/multitool/circuit/marker = usr.get_active_held_item()
+ if(!istype(marker))
+ return TRUE
+ if(!marker.marked_atom)
+ port.set_input(null)
+ marker.say("Cleared port ('[port.name]')'s value.")
+ return TRUE
+ marker.say("Updated port ('[port.name]')'s value to the marked entity.")
+ port.set_input(marker.marked_atom)
+ return TRUE
+
+ var/user_input = params["input"]
+ switch(port.datatype)
+ if(PORT_TYPE_NUMBER)
+ port.set_input(text2num(user_input))
+ if(PORT_TYPE_ANY)
+ var/any_type = copytext(user_input, 1, PORT_MAX_STRING_LENGTH)
+ port.set_input(text2num(any_type) || any_type)
+ if(PORT_TYPE_STRING)
+ port.set_input(copytext(user_input, 1, PORT_MAX_STRING_LENGTH))
+ if(PORT_TYPE_SIGNAL)
+ balloon_alert(usr, "triggered [port.name]")
+ port.set_input(COMPONENT_SIGNAL)
+ . = TRUE
+ if("get_component_value")
+ var/component_id = text2num(params["component_id"])
+ var/port_id = text2num(params["port_id"])
+ if(!WITHIN_RANGE(component_id, attached_components))
+ return
+ var/obj/item/circuit_component/component = attached_components[component_id]
+ if(!WITHIN_RANGE(port_id, component.output_ports))
+ return
+
+ var/datum/port/output/port = component.output_ports[port_id]
+ var/value = port.output_value
+ if(isatom(value))
+ value = port.convert_value(port.output_value)
+ else if(isnull(value))
+ value = "null"
+ balloon_alert(usr, "[port.name] value: [value]")
+ . = TRUE
+ if("set_display_name")
+ var/new_name = params["display_name"]
+
+ if(new_name)
+ display_name = strip_html(params["display_name"], label_max_length)
+ else
+ display_name = ""
+
+ if(shell)
+ if(display_name != "")
+ shell.name = "[initial(shell.name)] ([display_name])"
+ else
+ shell.name = initial(shell.name)
+
+ . = TRUE
+ if("set_examined_component")
+ var/component_id = text2num(params["component_id"])
+ if(!WITHIN_RANGE(component_id, attached_components))
+ return
+ examined_component = WEAKREF(attached_components[component_id])
+ examined_rel_x = text2num(params["x"])
+ examined_rel_y = text2num(params["y"])
+ . = TRUE
+ if("remove_examined_component")
+ examined_component = null
+ . = TRUE
+
+/obj/item/integrated_circuit/proc/on_atom_usb_cable_try_attach(datum/source, obj/item/usb_cable/usb_cable, mob/user)
+ usb_cable.balloon_alert(user, "circuit needs to be in a compatible shell")
+ return COMSIG_CANCEL_USB_CABLE_ATTACK
+
+#undef WITHIN_RANGE
+
+/**
+ * Returns the creator of the integrated circuit. Used in admin messages and other related things.
+ */
+/obj/item/integrated_circuit/proc/get_creator_admin()
+ return get_creator(include_link = TRUE)
+
+/**
+ * Returns the creator of the integrated circuit. Used in admin logs and other related things.
+ */
+/obj/item/integrated_circuit/proc/get_creator(include_link = FALSE)
+ var/datum/mind/inserter
+ if(inserter_mind)
+ inserter = inserter_mind.resolve()
+
+ var/obj/item/card/id/id_card
+ if(owner_id)
+ id_card = owner_id.resolve()
+
+ return "(Shell: [shell || "*null*"], Inserter: [key_name(inserter, include_link)], Owner ID: [id_card?.name || "*null*"])"
diff --git a/code/modules/wiremod/marker.dm b/code/modules/wiremod/marker.dm
new file mode 100644
index 0000000000000..39812c02eb623
--- /dev/null
+++ b/code/modules/wiremod/marker.dm
@@ -0,0 +1,48 @@
+/obj/item/multitool/circuit
+ name = "circuit multitool"
+ desc = "A circuit multitool. Used to mark entities which can then be uploaded to components by pressing the upload button on a port. \
+ Acts as a normal multitool otherwise. Use in hand to clear marked entity so that you can mark another entity."
+ icon_state = "multitool_circuit"
+ item_state = "multitool_circuit"
+ /// The marked atom of this multitool
+ var/atom/marked_atom
+
+/obj/item/multitool/circuit/Destroy()
+ marked_atom = null
+ return ..()
+
+/obj/item/multitool/circuit/on_examine(datum/source, mob/user, list/examine_list)
+ . = ..()
+ examine_list += "It has [marked_atom? "a" : "no"] marked entity registered."
+
+/obj/item/multitool/circuit/attack_self(mob/user, modifiers)
+ . = ..()
+ if(.)
+ return
+ if(!marked_atom)
+ return
+
+ say("Cleared marked targets.")
+ clear_marked_atom()
+ return TRUE
+
+/obj/item/multitool/circuit/melee_attack_chain(mob/user, atom/target, params)
+ if(marked_atom || !user.Adjacent(target))
+ return ..()
+
+ say("Marked [target].")
+ marked_atom = target
+ RegisterSignal(marked_atom, COMSIG_PARENT_QDELETING, .proc/cleanup_marked_atom)
+ return TRUE
+
+/// Clears the current marked atom
+/obj/item/multitool/circuit/proc/clear_marked_atom()
+ if(!marked_atom)
+ return
+ UnregisterSignal(marked_atom, COMSIG_PARENT_QDELETING)
+ marked_atom = null
+
+/obj/item/multitool/circuit/proc/cleanup_marked_atom(datum/source)
+ SIGNAL_HANDLER
+ if(source == marked_atom)
+ clear_marked_atom()
diff --git a/code/modules/wiremod/port.dm b/code/modules/wiremod/port.dm
new file mode 100644
index 0000000000000..c63621af0d1c8
--- /dev/null
+++ b/code/modules/wiremod/port.dm
@@ -0,0 +1,285 @@
+/**
+ * # Component Port
+ *
+ * A base type port used by a component
+ *
+ * Connects to other ports. This is an abstract type that should not be instanciated
+ */
+/datum/port
+ /// The component this port is attached to
+ var/obj/item/circuit_component/connected_component
+
+ /// Name of the port. Used when displaying the port.
+ var/name
+
+ /// The port type. Ports can only connect to each other if the type matches
+ var/datatype
+
+ /// The default port type. Stores the original datatype of the port set on Initialize.
+ var/default_datatype
+
+ /// The port color. If unset, appears as blue.
+ var/color
+
+/datum/port/New(obj/item/circuit_component/to_connect, name, datatype)
+ if(!to_connect)
+ qdel(src)
+ return
+ . = ..()
+ // Don't need to do src.connected_component here, but it looks inline
+ // with the other variable declarations
+ src.connected_component = to_connect
+ src.name = name
+ src.datatype = datatype
+ src.default_datatype = datatype
+ src.color = datatype_to_color()
+
+
+///Converts the datatype into an appropriate colour
+/datum/port/proc/datatype_to_color()
+ switch(datatype)
+ if(PORT_TYPE_ATOM)
+ return "purple"
+ if(PORT_TYPE_NUMBER)
+ return "green"
+ if(PORT_TYPE_STRING)
+ return "orange"
+ if(PORT_TYPE_LIST)
+ return "white"
+ if(PORT_TYPE_SIGNAL)
+ return "teal"
+ if(PORT_TYPE_TABLE)
+ return "grey"
+
+/datum/port/Destroy(force)
+ if(!force && !QDELETED(connected_component))
+ // This should never happen. Ports should be deleted with their components
+ stack_trace("Attempted to delete a port with a non-destroyed connected_component! (port name: [name], component type: [connected_component.type])")
+ return QDEL_HINT_LETMELIVE
+ connected_component = null
+ return ..()
+
+/**
+ * Returns the value to be set for the port
+ *
+ * Used for implicit conversions between outputs and inputs (e.g. number -> string)
+ * and applying/removing signals on inputs
+ */
+/datum/port/proc/convert_value(prev_value, value_to_convert)
+ if(prev_value == value_to_convert)
+ return prev_value
+ . = value_to_convert
+
+ if(isnull(value_to_convert))
+ return null
+
+ switch(datatype)
+ if(PORT_TYPE_STRING)
+ // So that they can't easily get the name like this.
+ if(isatom(value_to_convert))
+ return PORT_TYPE_ATOM
+ else
+ return "[value_to_convert]"
+
+ if(isatom(value_to_convert))
+ var/atom/atom_to_check = value_to_convert
+ if(QDELETED(atom_to_check))
+ return null
+
+/**
+ * Sets the datatype of the port.
+ *
+ * Arguments:
+ * * type_to_set - The type this port is set to.
+ */
+/datum/port/proc/set_datatype(type_to_set)
+ datatype = type_to_set
+ color = datatype_to_color()
+ disconnect()
+ if(connected_component?.parent)
+ SStgui.update_uis(connected_component.parent)
+
+/**
+ * Disconnects a port from all other ports
+ *
+ * Called by [/obj/item/circuit_component] whenever it is disconnected from
+ * an integrated circuit
+ */
+/datum/port/proc/disconnect()
+ SHOULD_CALL_PARENT(TRUE)
+ SEND_SIGNAL(src, COMSIG_PORT_DISCONNECT)
+
+
+/**
+ * # Output Port
+ *
+ * An output port that many input ports can connect to
+ *
+ * Sends a signal whenever the output value is changed
+ */
+/datum/port/output
+ /// The output value of the port
+ var/output_value
+
+/datum/port/output/disconnect()
+ set_output(null)
+ return ..()
+
+/datum/port/output/Destroy(force)
+ output_value = null
+ return ..()
+
+/**
+ * Sets the output value of the port
+ *
+ * Arguments:
+ * * value - The value to set it to
+ */
+/datum/port/output/proc/set_output(value)
+ if(isatom(output_value))
+ UnregisterSignal(output_value, COMSIG_PARENT_QDELETING)
+ output_value = convert_value(output_value, value)
+ if(isatom(output_value))
+ RegisterSignal(output_value, COMSIG_PARENT_QDELETING, .proc/null_output)
+
+ SEND_SIGNAL(src, COMSIG_PORT_SET_OUTPUT, output_value)
+
+/// Signal handler proc to null the output if an atom is deleted. An update is not sent because this was not set.
+/datum/port/output/proc/null_output(datum/source)
+ SIGNAL_HANDLER
+ if(output_value == source)
+ output_value = null
+
+/datum/port/output/set_datatype(type_to_set)
+ . = ..()
+ set_output(null)
+
+/**
+ * Determines if a datatype is compatible with this port.
+ *
+ * Arguments:
+ * * other_datatype - The datatype to check
+ */
+/datum/port/output/proc/compatible_datatype(datatype_to_check)
+ if(datatype_to_check == datatype)
+ return TRUE
+
+ switch(datatype)
+ if(PORT_TYPE_NUMBER)
+ // Can easily convert a number to string. Everything else has to use a tostring component
+ return datatype_to_check == PORT_TYPE_STRING || datatype_to_check == PORT_TYPE_SIGNAL
+ if(PORT_TYPE_SIGNAL)
+ // A signal port is just a number port but distinguishable
+ return datatype_to_check == PORT_TYPE_NUMBER
+
+ return FALSE
+
+/**
+ * # Input Port
+ *
+ * An input port that can only be connected to 1 output port
+ *
+ * Registers a signal on the target output port to listen out for any output
+ * so that an update can be sent to the attached component
+ */
+/datum/port/input
+ /// The output value of the port
+ var/input_value
+
+ /// The connected output port
+ var/datum/port/output/connected_port
+
+ /// Whether this port triggers an update whenever an output is received.
+ var/trigger = FALSE
+
+ /// The default value of this input
+ var/default
+
+/datum/port/input/New(obj/item/circuit_component/to_connect, name, datatype, trigger, default)
+ . = ..()
+ src.trigger = trigger
+ src.default = default
+ set_input(default, FALSE)
+
+/**
+ * Connects the input port to the output port
+ *
+ * Sets the input_value and registers a signal to receive future updates.
+ * Arguments:
+ * * port_to_register - The port to connect the input port to
+ */
+/datum/port/input/proc/register_output_port(datum/port/output/port_to_register)
+ unregister_output_port()
+
+ RegisterSignal(port_to_register, COMSIG_PORT_SET_OUTPUT, .proc/receive_output)
+ RegisterSignal(port_to_register, list(
+ COMSIG_PORT_DISCONNECT,
+ COMSIG_PARENT_QDELETING
+ ), .proc/unregister_output_port)
+
+ connected_port = port_to_register
+ SEND_SIGNAL(connected_port, COMSIG_PORT_OUTPUT_CONNECT, src)
+ set_input(connected_port.output_value)
+
+
+/**
+ * Sets a timer depending on the value of the input_receive_delay
+ *
+ * The timer will call a proc that updates the value.
+ * Arguments:
+ * * connected_port - The connected output port
+ * * new_value - The new value received from the output port
+ */
+/datum/port/input/proc/receive_output(datum/port/output/connected_port, new_value)
+ SIGNAL_HANDLER
+
+ SScircuit_component.add_callback(CALLBACK(src, .proc/set_input, new_value))
+
+/**
+ * Updates the value of the input
+ *
+ * It updates the value of the input and calls input_received on the connected component
+ * Arguments:
+ * * port_to_register - The port to connect the input port to
+ */
+/datum/port/input/proc/set_input(new_value, send_update = TRUE)
+ if(isatom(input_value))
+ UnregisterSignal(input_value, COMSIG_PARENT_QDELETING)
+ input_value = convert_value(input_value, new_value)
+ if(isatom(input_value))
+ RegisterSignal(input_value, COMSIG_PARENT_QDELETING, .proc/null_output)
+
+ SEND_SIGNAL(src, COMSIG_PORT_SET_INPUT, input_value)
+ if(trigger && send_update)
+ TRIGGER_CIRCUIT_COMPONENT(connected_component, src)
+
+/// Signal handler proc to null the input if an atom is deleted. An update is not sent because this was not set by anything.
+/datum/port/input/proc/null_output(datum/source)
+ SIGNAL_HANDLER
+ if(input_value == source)
+ input_value = null
+
+/datum/port/input/disconnect()
+ unregister_output_port()
+ return ..()
+
+/datum/port/input/set_datatype(type_to_set)
+ . = ..()
+ set_input(default)
+
+/datum/port/input/proc/unregister_output_port()
+ SIGNAL_HANDLER
+ if(!connected_port)
+ return
+ UnregisterSignal(connected_port, list(
+ COMSIG_PARENT_QDELETING,
+ COMSIG_PORT_SET_OUTPUT,
+ COMSIG_PORT_DISCONNECT
+ ))
+ connected_port = null
+ set_input(default)
+
+/datum/port/input/Destroy()
+ unregister_output_port()
+ connected_port = null
+ return ..()
diff --git a/code/modules/wiremod/preset/hello_world.dm b/code/modules/wiremod/preset/hello_world.dm
new file mode 100644
index 0000000000000..8e0d244ec9ae3
--- /dev/null
+++ b/code/modules/wiremod/preset/hello_world.dm
@@ -0,0 +1,13 @@
+/**
+ * # Hello World preset
+ *
+ * Says "Hello World" when triggered. Needs to be wired up and connected first.
+ */
+/obj/item/integrated_circuit/loaded/hello_world
+
+/obj/item/integrated_circuit/loaded/hello_world/Initialize()
+ . = ..()
+ var/obj/item/circuit_component/speech/speech = new()
+ add_component(speech)
+
+ speech.message.set_input("Hello World")
diff --git a/code/modules/wiremod/preset/speech_relay.dm b/code/modules/wiremod/preset/speech_relay.dm
new file mode 100644
index 0000000000000..859c2970eeb67
--- /dev/null
+++ b/code/modules/wiremod/preset/speech_relay.dm
@@ -0,0 +1,21 @@
+/**
+ * # Speech Relay preset
+ *
+ * Acts like poly. Says whatever it hears.
+ */
+/obj/item/integrated_circuit/loaded/speech_relay
+
+/obj/item/integrated_circuit/loaded/speech_relay/Initialize()
+ . = ..()
+ var/obj/item/circuit_component/hear/hear = new()
+ add_component(hear)
+ hear.rel_x = 100
+ hear.rel_y = 200
+
+ var/obj/item/circuit_component/speech/speech = new()
+ add_component(speech)
+ speech.rel_x = 400
+ speech.rel_y = 200
+
+ speech.message.register_output_port(hear.message_port)
+ speech.trigger.register_output_port(hear.trigger_port)
diff --git a/code/modules/wiremod/shell/airlock.dm b/code/modules/wiremod/shell/airlock.dm
new file mode 100644
index 0000000000000..918fc721cc646
--- /dev/null
+++ b/code/modules/wiremod/shell/airlock.dm
@@ -0,0 +1,145 @@
+/obj/machinery/door/airlock/shell
+ name = "circuit airlock"
+ autoclose = FALSE
+
+/obj/machinery/door/airlock/shell/Initialize()
+ . = ..()
+ AddComponent( \
+ /datum/component/shell, \
+ unremovable_circuit_components = list(new /obj/item/circuit_component/airlock), \
+ capacity = SHELL_CAPACITY_LARGE, \
+ shell_flags = SHELL_FLAG_ALLOW_FAILURE_ACTION \
+ )
+
+/obj/machinery/door/airlock/shell/check_access(obj/item/I)
+ return FALSE
+
+/obj/item/circuit_component/airlock
+ display_name = "Airlock"
+ display_desc = "The general interface with an airlock. Includes general statuses of the airlock"
+
+ /// Called when attack_hand is called on the shell.
+ var/obj/machinery/door/airlock/attached_airlock
+
+ /// Bolts the airlock (if possible)
+ var/datum/port/input/bolt
+ /// Unbolts the airlock (if possible)
+ var/datum/port/input/unbolt
+ /// Opens the airlock (if possible)
+ var/datum/port/input/open
+ /// Closes the airlock (if possible)
+ var/datum/port/input/close
+
+ /// Contains whether the airlock is open or not
+ var/datum/port/output/is_open
+ /// Contains whether the airlock is bolted or not
+ var/datum/port/output/is_bolted
+
+ /// Called when the airlock is opened.
+ var/datum/port/output/opened
+ /// Called when the airlock is closed
+ var/datum/port/output/closed
+
+ /// Called when the airlock is bolted
+ var/datum/port/output/bolted
+ /// Called when the airlock is unbolted
+ var/datum/port/output/unbolted
+
+ /// Contains the last person to touch the airlock
+ var/datum/port/output/user_port
+ /// Called when the airlock is touched
+ var/datum/port/output/trigger_port
+
+/obj/item/circuit_component/airlock/Initialize()
+ . = ..()
+ // Input Signals
+ bolt = add_input_port("Bolt", PORT_TYPE_SIGNAL)
+ unbolt = add_input_port("Unbolt", PORT_TYPE_SIGNAL)
+ open = add_input_port("Open", PORT_TYPE_SIGNAL)
+ close = add_input_port("Close", PORT_TYPE_SIGNAL)
+ // States
+ is_open = add_output_port("Is Open", PORT_TYPE_NUMBER)
+ is_bolted = add_output_port("Is Bolted", PORT_TYPE_NUMBER)
+ // Output Signals
+ opened = add_output_port("Opened", PORT_TYPE_SIGNAL)
+ closed = add_output_port("Closed", PORT_TYPE_SIGNAL)
+ bolted = add_output_port("Bolted", PORT_TYPE_SIGNAL)
+ unbolted = add_output_port("Unbolted", PORT_TYPE_SIGNAL)
+ user_port = add_output_port("User", PORT_TYPE_ATOM)
+ trigger_port = add_output_port("Triggered", PORT_TYPE_SIGNAL)
+
+/obj/item/circuit_component/airlock/Destroy()
+ bolt = null
+ unbolt = null
+ open = null
+ close = null
+ is_open = null
+ is_bolted = null
+ opened = null
+ closed = null
+ bolted = null
+ unbolted = null
+ user_port = null
+ trigger_port = null
+ attached_airlock = null
+ return ..()
+
+/obj/item/circuit_component/airlock/register_shell(atom/movable/shell)
+ . = ..()
+ if(istype(shell, /obj/machinery/door/airlock))
+ attached_airlock = shell
+ RegisterSignal(shell, COMSIG_AIRLOCK_SET_BOLT, .proc/on_airlock_set_bolted)
+ RegisterSignal(shell, COMSIG_AIRLOCK_OPEN, .proc/on_airlock_open)
+ RegisterSignal(shell, COMSIG_AIRLOCK_CLOSE, .proc/on_airlock_closed)
+ RegisterSignal(shell, COMSIG_AIRLOCK_TOUCHED, .proc/on_airlock_touched)
+
+/obj/item/circuit_component/airlock/unregister_shell(atom/movable/shell)
+ attached_airlock = null
+ UnregisterSignal(shell, list(
+ COMSIG_AIRLOCK_SET_BOLT,
+ COMSIG_AIRLOCK_OPEN,
+ COMSIG_AIRLOCK_CLOSE,
+ COMSIG_AIRLOCK_TOUCHED,
+ ))
+ return ..()
+
+/obj/item/circuit_component/airlock/proc/on_airlock_set_bolted(datum/source, should_bolt)
+ SIGNAL_HANDLER
+ is_bolted.set_output(should_bolt)
+ if(should_bolt)
+ bolted.set_output(COMPONENT_SIGNAL)
+ else
+ unbolted.set_output(COMPONENT_SIGNAL)
+
+/obj/item/circuit_component/airlock/proc/on_airlock_open(datum/source, force)
+ SIGNAL_HANDLER
+ is_open.set_output(TRUE)
+ opened.set_output(COMPONENT_SIGNAL)
+
+/obj/item/circuit_component/airlock/proc/on_airlock_closed(datum/source, forced)
+ SIGNAL_HANDLER
+ is_open.set_output(FALSE)
+ closed.set_output(COMPONENT_SIGNAL)
+
+/obj/item/circuit_component/airlock/proc/on_airlock_touched(datum/source, user)
+ SIGNAL_HANDLER
+ . = COMPONENT_PREVENT_OPEN
+ user_port.set_output(user)
+ trigger_port.set_output(COMPONENT_SIGNAL)
+
+/obj/item/circuit_component/airlock/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ if(!attached_airlock)
+ return
+
+ if(COMPONENT_TRIGGERED_BY(bolt, port))
+ attached_airlock.bolt()
+ if(COMPONENT_TRIGGERED_BY(unbolt, port))
+ attached_airlock.unbolt()
+ if(COMPONENT_TRIGGERED_BY(open, port) && attached_airlock.density)
+ INVOKE_ASYNC(attached_airlock, /obj/machinery/door/airlock.proc/open)
+ if(COMPONENT_TRIGGERED_BY(close, port) && !attached_airlock.density)
+ INVOKE_ASYNC(attached_airlock, /obj/machinery/door/airlock.proc/close)
diff --git a/code/modules/wiremod/shell/bot.dm b/code/modules/wiremod/shell/bot.dm
new file mode 100644
index 0000000000000..1aa1060b98ae3
--- /dev/null
+++ b/code/modules/wiremod/shell/bot.dm
@@ -0,0 +1,48 @@
+/**
+ * # Bot
+ *
+ * Immobile (but not dense) shells that can interact with world.
+ */
+/obj/structure/bot
+ name = "bot"
+ icon = 'icons/obj/wiremod.dmi'
+ icon_state = "setup_medium_box"
+
+ density = FALSE
+ light_range = FALSE
+
+/obj/structure/bot/Initialize()
+ . = ..()
+ AddComponent( \
+ /datum/component/shell, \
+ unremovable_circuit_components = list(new /obj/item/circuit_component/bot), \
+ capacity = SHELL_CAPACITY_LARGE, \
+ shell_flags = SHELL_FLAG_USB_PORT, \
+ )
+
+/obj/item/circuit_component/bot
+ display_name = "Bot"
+ display_desc = "Triggers when someone interacts with the bot."
+
+ /// Called when attack_hand is called on the shell.
+ var/datum/port/output/signal
+
+/obj/item/circuit_component/bot/Initialize()
+ . = ..()
+ signal = add_output_port("Signal", PORT_TYPE_SIGNAL)
+
+/obj/item/circuit_component/bot/Destroy()
+ signal = null
+ return ..()
+
+/obj/item/circuit_component/bot/register_shell(atom/movable/shell)
+ RegisterSignal(shell, COMSIG_ATOM_ATTACK_HAND, .proc/on_attack_hand)
+
+/obj/item/circuit_component/bot/unregister_shell(atom/movable/shell)
+ UnregisterSignal(shell, COMSIG_ATOM_ATTACK_HAND)
+
+/obj/item/circuit_component/bot/proc/on_attack_hand(atom/source, mob/user)
+ SIGNAL_HANDLER
+ source.balloon_alert(user, "pushed button")
+ playsound(source, get_sfx("terminal_type"), 25, FALSE)
+ signal.set_output(COMPONENT_SIGNAL)
diff --git a/code/modules/wiremod/shell/compact_remote.dm b/code/modules/wiremod/shell/compact_remote.dm
new file mode 100644
index 0000000000000..84d57f589b2e2
--- /dev/null
+++ b/code/modules/wiremod/shell/compact_remote.dm
@@ -0,0 +1,50 @@
+/**
+ * # Compact Remote
+ *
+ * A handheld device with one big button.
+ */
+/obj/item/compact_remote
+ name = "compact remote"
+ icon = 'icons/obj/wiremod.dmi'
+ icon_state = "setup_small_simple"
+ item_state = "electronic"
+ //worn_icon_state = "electronic" //remember to change it later lol
+ lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
+ light_range = FALSE
+
+/obj/item/compact_remote/Initialize()
+ . = ..()
+ AddComponent(/datum/component/shell, list(
+ new /obj/item/circuit_component/compact_remote()
+ ), SHELL_CAPACITY_SMALL)
+
+/obj/item/circuit_component/compact_remote
+ display_name = "Compact Remote"
+ display_desc = "Used to receive inputs from the compact remote shell. Use the shell in hand to trigger the output signal."
+
+ /// Called when attack_self is called on the shell.
+ var/datum/port/output/signal
+
+/obj/item/circuit_component/compact_remote/Initialize()
+ . = ..()
+ signal = add_output_port("Signal", PORT_TYPE_SIGNAL)
+
+/obj/item/circuit_component/compact_remote/Destroy()
+ signal = null
+ return ..()
+
+/obj/item/circuit_component/compact_remote/register_shell(atom/movable/shell)
+ RegisterSignal(shell, COMSIG_ITEM_ATTACK_SELF, .proc/send_trigger)
+
+/obj/item/circuit_component/compact_remote/unregister_shell(atom/movable/shell)
+ UnregisterSignal(shell, COMSIG_ITEM_ATTACK_SELF)
+
+/**
+ * Called when the shell item is used in hand.
+ */
+/obj/item/circuit_component/compact_remote/proc/send_trigger(atom/source, mob/user)
+ SIGNAL_HANDLER
+ source.balloon_alert(user, "clicked primary button")
+ playsound(source, get_sfx("terminal_type"), 25, FALSE)
+ signal.set_output(COMPONENT_SIGNAL)
diff --git a/code/modules/wiremod/shell/controller.dm b/code/modules/wiremod/shell/controller.dm
new file mode 100644
index 0000000000000..692508f19b3b4
--- /dev/null
+++ b/code/modules/wiremod/shell/controller.dm
@@ -0,0 +1,82 @@
+/**
+ * # Compact Remote
+ *
+ * A handheld device with several buttons.
+ * In game, this translates to having different signals for normal usage, alt-clicking, and ctrl-clicking when in your hand.
+ */
+/obj/item/controller
+ name = "controller"
+ icon = 'icons/obj/wiremod.dmi'
+ icon_state = "setup_small_calc"
+ item_state = "electronic"
+ //worn_icon_state = "electronic" //remember to change it
+ lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
+ light_range = FALSE
+
+/obj/item/controller/Initialize()
+ . = ..()
+ AddComponent(/datum/component/shell, list(
+ new /obj/item/circuit_component/controller()
+ ), SHELL_CAPACITY_MEDIUM)
+
+/obj/item/circuit_component/controller
+ display_name = "Controller"
+ display_desc = "Used to receive inputs from the controller shell. Use the shell in hand to trigger the output signal. Alt-click for the alternate signal. Right click for the extra signal."
+
+ /// The three separate buttons that are called in attack_hand on the shell.
+ var/datum/port/output/signal
+ var/datum/port/output/alt
+ var/datum/port/output/right
+
+/obj/item/circuit_component/controller/Initialize()
+ . = ..()
+ signal = add_output_port("Signal", PORT_TYPE_SIGNAL)
+ alt = add_output_port("Alternate Signal", PORT_TYPE_SIGNAL)
+ right = add_output_port("Extra Signal", PORT_TYPE_SIGNAL)
+
+/obj/item/circuit_component/controller/Destroy()
+ signal = null
+ alt = null
+ right = null
+ return ..()
+
+/obj/item/circuit_component/controller/register_shell(atom/movable/shell)
+ RegisterSignal(shell, COMSIG_ITEM_ATTACK_SELF, .proc/send_trigger)
+ RegisterSignal(shell, COMSIG_CLICK_ALT, .proc/send_alternate_signal)
+
+/obj/item/circuit_component/controller/unregister_shell(atom/movable/shell)
+ UnregisterSignal(shell, list(
+ COMSIG_ITEM_ATTACK_SELF,
+ COMSIG_CLICK_ALT,
+ ))
+
+/**
+ * Called when the shell item is used in hand, including right click.
+ */
+/obj/item/circuit_component/controller/proc/send_trigger(atom/source, mob/user)
+ SIGNAL_HANDLER
+ if(!user.Adjacent(source))
+ return
+ source.balloon_alert(user, "clicked primary button")
+ playsound(source, get_sfx("terminal_type"), 25, FALSE)
+ signal.set_output(COMPONENT_SIGNAL)
+
+/**
+ * Called when the shell item is alt-clicked
+ */
+/obj/item/circuit_component/controller/proc/send_alternate_signal(atom/source, mob/user)
+ SIGNAL_HANDLER
+ if(!user.Adjacent(source))
+ return
+ source.balloon_alert(user, "clicked alternate button")
+ playsound(source, get_sfx("terminal_type"), 25, FALSE)
+ alt.set_output(COMPONENT_SIGNAL)
+
+/obj/item/circuit_component/controller/proc/send_right_signal(atom/source, mob/user)
+ SIGNAL_HANDLER
+ if(!user.Adjacent(source))
+ return
+ source.balloon_alert(user, "clicked extra button")
+ playsound(source, get_sfx("terminal_type"), 25, FALSE)
+ right.set_output(COMPONENT_SIGNAL)
diff --git a/code/modules/wiremod/shell/drone.dm b/code/modules/wiremod/shell/drone.dm
new file mode 100644
index 0000000000000..2bbb58c55dd0f
--- /dev/null
+++ b/code/modules/wiremod/shell/drone.dm
@@ -0,0 +1,81 @@
+/**
+ * # Drone
+ *
+ * A movable mob that can be fed inputs on which direction to travel.
+ */
+/mob/living/circuit_drone
+ name = "drone"
+ icon = 'icons/obj/wiremod.dmi'
+ icon_state = "setup_medium_med"
+ //light_system = MOVABLE_LIGHT_DIRECTIONAL
+ light_range = FALSE
+
+/mob/living/circuit_drone/Initialize()
+ . = ..()
+ AddComponent(/datum/component/shell, list(
+ new /obj/item/circuit_component/bot_circuit()
+ ), SHELL_CAPACITY_LARGE)
+
+/mob/living/circuit_drone/updatehealth()
+ . = ..()
+ if(health < 0)
+ gib(no_brain = TRUE, no_organs = TRUE, no_bodyparts = TRUE)
+
+/mob/living/circuit_drone/spawn_gibs()
+ new /obj/effect/gibspawner/robot(drop_location(), src, get_static_viruses())
+
+/obj/item/circuit_component/bot_circuit
+ display_name = "Drone"
+ display_desc = "Used to send movement output signals to the drone shell."
+
+ /// The inputs to allow for the drone to move
+ var/datum/port/input/north
+ var/datum/port/input/east
+ var/datum/port/input/south
+ var/datum/port/input/west
+
+ // Done like this so that travelling diagonally is more simple
+ COOLDOWN_DECLARE(north_delay)
+ COOLDOWN_DECLARE(east_delay)
+ COOLDOWN_DECLARE(south_delay)
+ COOLDOWN_DECLARE(west_delay)
+
+ /// Delay between each movement
+ var/move_delay = 0.2 SECONDS
+
+/obj/item/circuit_component/bot_circuit/Initialize()
+ . = ..()
+ north = add_input_port("Move North", PORT_TYPE_SIGNAL)
+ east = add_input_port("Move East", PORT_TYPE_SIGNAL)
+ south = add_input_port("Move South", PORT_TYPE_SIGNAL)
+ west = add_input_port("Move West", PORT_TYPE_SIGNAL)
+
+/obj/item/circuit_component/bot_circuit/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ var/mob/living/shell = parent.shell
+ if(!istype(shell) || shell.stat)
+ return
+
+ var/direction
+
+ if(COMPONENT_TRIGGERED_BY(north, port) && COOLDOWN_FINISHED(src, north_delay))
+ direction = NORTH
+ COOLDOWN_START(src, north_delay, move_delay)
+ else if(COMPONENT_TRIGGERED_BY(east, port) && COOLDOWN_FINISHED(src, east_delay))
+ direction = EAST
+ COOLDOWN_START(src, east_delay, move_delay)
+ else if(COMPONENT_TRIGGERED_BY(south, port) && COOLDOWN_FINISHED(src, south_delay))
+ direction = SOUTH
+ COOLDOWN_START(src, south_delay, move_delay)
+ else if(COMPONENT_TRIGGERED_BY(west, port) && COOLDOWN_FINISHED(src, west_delay))
+ direction = WEST
+ COOLDOWN_START(src, west_delay, move_delay)
+
+ if(!direction)
+ return
+
+ if(shell.Process_Spacemove(direction))
+ shell.Move(get_step(shell, direction), direction)
diff --git a/code/modules/wiremod/shell/moneybot.dm b/code/modules/wiremod/shell/moneybot.dm
new file mode 100644
index 0000000000000..1aea59c8908fd
--- /dev/null
+++ b/code/modules/wiremod/shell/moneybot.dm
@@ -0,0 +1,151 @@
+/**
+ * # Money Bot
+ *
+ * Immobile (but not dense) shell that can receive and dispense money.
+ */
+/obj/structure/money_bot
+ name = "money bot"
+ icon = 'icons/obj/wiremod.dmi'
+ icon_state = "setup_large"
+
+ density = FALSE
+ light_range = FALSE
+
+ var/stored_money = 0
+
+/obj/structure/money_bot/deconstruct(disassembled)
+ new /obj/item/holochip(drop_location(), stored_money)
+ return ..()
+
+/obj/structure/money_bot/proc/add_money(to_add)
+ stored_money += to_add
+ SEND_SIGNAL(src, COMSIG_MONEYBOT_ADD_MONEY, to_add)
+
+/obj/structure/money_bot/Initialize()
+ . = ..()
+ AddComponent(/datum/component/shell, list(
+ new /obj/item/circuit_component/money_bot(),
+ new /obj/item/circuit_component/money_dispenser()
+ ), SHELL_CAPACITY_LARGE)
+
+/obj/structure/money_bot/wrench_act(mob/living/user, obj/item/tool)
+ anchored = !anchored
+ tool.play_tool_sound(src)
+ balloon_alert(user, "You [anchored?"secure":"unsecure"] [src].")
+ return TRUE
+
+
+/obj/item/circuit_component/money_dispenser
+ display_name = "Money Dispenser"
+ display_desc = "Used to dispense money from the money bot. Money is taken from the internal storage of money."
+ circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL
+
+ /// The amount of money to dispense
+ var/datum/port/input/dispense_amount
+
+ /// Outputs a signal when it fails to output any money.
+ var/datum/port/output/on_fail
+
+ var/obj/structure/money_bot/attached_bot
+
+/obj/item/circuit_component/money_dispenser/Initialize()
+ . = ..()
+ dispense_amount = add_input_port("Amount", PORT_TYPE_NUMBER)
+ on_fail = add_output_port("On Failed", PORT_TYPE_SIGNAL)
+
+/obj/item/circuit_component/money_dispenser/register_shell(atom/movable/shell)
+ . = ..()
+ if(istype(shell, /obj/structure/money_bot))
+ attached_bot = shell
+
+/obj/item/circuit_component/money_dispenser/unregister_shell(atom/movable/shell)
+ attached_bot = null
+ return ..()
+
+/obj/item/circuit_component/money_dispenser/input_received(datum/port/input/port)
+ . = ..()
+ if(.)
+ return
+
+ if(!attached_bot)
+ return
+
+ var/to_dispense = clamp(dispense_amount.input_value, 0, attached_bot.stored_money)
+ if(!to_dispense)
+ on_fail.set_output(COMPONENT_SIGNAL)
+ return
+ attached_bot.add_money(-to_dispense)
+ new /obj/item/holochip(drop_location(), to_dispense)
+
+/obj/item/circuit_component/money_dispenser/Destroy()
+ dispense_amount = null
+ attached_bot = null
+ return ..()
+
+/obj/item/circuit_component/money_bot
+ display_name = "Money Bot"
+ display_desc = "Used to receive input signals when money is inserted into the money bot shell and also keep track of the total money in the shell."
+ var/obj/structure/money_bot/attached_bot
+
+ /// Total money in the shell
+ var/datum/port/output/total_money
+ /// Amount of the last money inputted into the shell
+ var/datum/port/output/money_input
+ /// Person that inserted the money
+ var/datum/port/output/payer
+ /// Trigger for when money is inputted into the shell
+ var/datum/port/output/money_trigger
+
+/obj/item/circuit_component/money_bot/Initialize()
+ . = ..()
+ total_money = add_output_port("Total Money", PORT_TYPE_NUMBER)
+ money_input = add_output_port("Last Input Money", PORT_TYPE_NUMBER)
+ payer = add_output_port("Payer", PORT_TYPE_ATOM)
+ money_trigger = add_output_port("Money Input", PORT_TYPE_SIGNAL)
+
+/obj/item/circuit_component/money_bot/register_shell(atom/movable/shell)
+ . = ..()
+ if(istype(shell, /obj/structure/money_bot))
+ attached_bot = shell
+ total_money.set_output(attached_bot.stored_money)
+ RegisterSignal(shell, COMSIG_PARENT_ATTACKBY, .proc/handle_money_insert)
+ RegisterSignal(shell, COMSIG_MONEYBOT_ADD_MONEY, .proc/handle_money_update)
+
+/obj/item/circuit_component/money_bot/unregister_shell(atom/movable/shell)
+ UnregisterSignal(shell, list(
+ COMSIG_PARENT_ATTACKBY,
+ COMSIG_MONEYBOT_ADD_MONEY,
+ ))
+ total_money.set_output(null)
+ attached_bot = null
+ return ..()
+
+/obj/item/circuit_component/money_bot/Destroy()
+ attached_bot = null
+ total_money = null
+ money_input = null
+ payer = null
+ money_trigger = null
+ return ..()
+
+/obj/item/circuit_component/money_bot/proc/handle_money_insert(atom/source, obj/item/item, mob/living/attacker)
+ SIGNAL_HANDLER
+ if(!attached_bot || !iscash(item))
+ return
+
+ var/amount_to_insert = item.get_item_credit_value()
+ if(!amount_to_insert)
+ balloon_alert(attacker, "this has no value!")
+ return
+
+ attached_bot.add_money(amount_to_insert)
+ balloon_alert(attacker, "inserted [amount_to_insert] credits.")
+ money_input.set_output(amount_to_insert)
+ payer.set_output(attacker)
+ money_trigger.set_output(COMPONENT_SIGNAL)
+ qdel(item)
+
+/obj/item/circuit_component/money_bot/proc/handle_money_update(atom/source)
+ SIGNAL_HANDLER
+ if(attached_bot)
+ total_money.set_output(attached_bot.stored_money)
diff --git a/code/modules/wiremod/shell/scanner.dm b/code/modules/wiremod/shell/scanner.dm
new file mode 100644
index 0000000000000..0874467036226
--- /dev/null
+++ b/code/modules/wiremod/shell/scanner.dm
@@ -0,0 +1,55 @@
+/**
+ * # Scanner
+ *
+ * A handheld device which scans things.
+ */
+/obj/item/scanner
+ name = "scanner"
+ icon = 'icons/obj/assemblies/electronic_setups.dmi'
+ icon_state = "setup_small_hook"
+ item_state = "electronic"
+ lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
+ light_range = FALSE
+
+/obj/item/scanner/Initialize()
+ . = ..()
+ AddComponent(/datum/component/shell, list(
+ new /obj/item/circuit_component/scanner()
+ ), SHELL_CAPACITY_SMALL)
+
+/obj/item/circuit_component/scanner
+ display_name = "Scanner"
+ display_desc = "Used to receive inputs from the scanner shell. Use the shell on something to scan it."
+
+ /// Atom that was scanned.
+ var/datum/port/output/scanned
+ /// Called when scanner is used.
+ var/datum/port/output/signal
+
+/obj/item/circuit_component/scanner/Initialize()
+ . = ..()
+ scanned = add_output_port("Scanned", PORT_TYPE_ATOM)
+ signal = add_output_port("Signal", PORT_TYPE_SIGNAL)
+
+/obj/item/circuit_component/scanner/Destroy()
+ scanned = null
+ signal = null
+ return ..()
+
+/obj/item/circuit_component/scanner/register_shell(atom/movable/shell)
+ RegisterSignal(shell, COMSIG_ITEM_PRE_ATTACK, .proc/send_trigger)
+
+/obj/item/circuit_component/scanner/unregister_shell(atom/movable/shell)
+ UnregisterSignal(shell, COMSIG_ITEM_PRE_ATTACK)
+
+/**
+ * Called when the shell item is used on something.
+ */
+/obj/item/circuit_component/scanner/proc/send_trigger(atom/source, atom/target, mob/user)
+ SIGNAL_HANDLER
+ target.balloon_alert(user, "scanned [target]")
+ playsound(user, get_sfx("terminal_type"), 25, FALSE)
+ . = COMPONENT_NO_ATTACK
+ scanned.set_output(target)
+ signal.set_output(COMPONENT_SIGNAL)
diff --git a/code/modules/wiremod/shell/server.dm b/code/modules/wiremod/shell/server.dm
new file mode 100644
index 0000000000000..b1c03c9c8d50e
--- /dev/null
+++ b/code/modules/wiremod/shell/server.dm
@@ -0,0 +1,24 @@
+/**
+ * # Server
+ *
+ * Immobile (but not dense) shells that can interact with
+ * world.
+ */
+/obj/structure/server
+ name = "server"
+ icon = 'icons/obj/wiremod.dmi'
+ icon_state = "setup_stationary"
+
+ density = TRUE
+ //light_system = MOVABLE_LIGHT
+ light_range = FALSE
+
+/obj/structure/server/Initialize()
+ . = ..()
+ AddComponent(/datum/component/shell, null, SHELL_CAPACITY_VERY_LARGE, SHELL_FLAG_REQUIRE_ANCHOR)
+
+/obj/structure/server/wrench_act(mob/living/user, obj/item/tool)
+ anchored = !anchored
+ tool.play_tool_sound(src)
+ balloon_alert(user, "You [anchored?"secure":"unsecure"] [src].")
+ return TRUE
diff --git a/code/modules/wiremod/shell/shell_items.dm b/code/modules/wiremod/shell/shell_items.dm
new file mode 100644
index 0000000000000..6643bfaf1565a
--- /dev/null
+++ b/code/modules/wiremod/shell/shell_items.dm
@@ -0,0 +1,54 @@
+/**
+ * # Shell Item
+ *
+ * Printed out by protolathes. Screwdriver to complete the shell.
+ */
+/obj/item/shell
+ name = "assembly"
+ desc = "A shell assembly that can be completed by screwdrivering it."
+ icon = 'icons/obj/wiremod.dmi'
+ var/shell_to_spawn
+ var/screw_delay = 3 SECONDS
+
+/obj/item/shell/screwdriver_act(mob/living/user, obj/item/tool)
+ user.visible_message("[user] begins finishing [src].", "You begin finishing [src].")
+ tool.play_tool_sound(src)
+ if(!do_after(user, screw_delay, target = src))
+ return
+ user.visible_message("[user] finishes [src].", "You finish [src].")
+
+ var/turf/drop_loc = drop_location()
+
+ qdel(src)
+ if(drop_loc)
+ new shell_to_spawn(drop_loc)
+
+ return TRUE
+
+/obj/item/shell/bot
+ name = "bot assembly"
+ icon_state = "setup_medium_box-open"
+ shell_to_spawn = /obj/structure/bot
+
+/obj/item/shell/money_bot
+ name = "money bot assembly"
+ icon_state = "setup_large-open"
+ shell_to_spawn = /obj/structure/money_bot
+
+/obj/item/shell/drone
+ name = "drone assembly"
+ icon_state = "setup_medium_med-open"
+ shell_to_spawn = /mob/living/circuit_drone
+
+/obj/item/shell/server
+ name = "server assembly"
+ icon_state = "setup_stationary-open"
+ shell_to_spawn = /obj/structure/server
+ screw_delay = 10 SECONDS
+
+/obj/item/shell/airlock
+ name = "circuit airlock assembly"
+ icon = 'icons/obj/doors/airlocks/station/public.dmi'
+ icon_state = "construction"
+ shell_to_spawn = /obj/machinery/door/airlock/shell
+ screw_delay = 10 SECONDS
diff --git a/code/modules/wiremod/usb_cable.dm b/code/modules/wiremod/usb_cable.dm
new file mode 100644
index 0000000000000..f10101c621c42
--- /dev/null
+++ b/code/modules/wiremod/usb_cable.dm
@@ -0,0 +1,122 @@
+/// A cable that can connect integrated circuits to anything with a USB port, such as computers and machines.
+/obj/item/usb_cable
+ name = "usb cable"
+ desc = "A cable that can connect integrated circuits to anything with a USB port, such as computers and machines."
+ icon = 'icons/obj/wiremod.dmi'
+ icon_state = "usb_cable"
+ item_state = "coil"
+ w_class = WEIGHT_CLASS_TINY
+ custom_materials = list(/datum/material/iron = 75)
+
+ /// The currently connected circuit
+ var/obj/item/integrated_circuit/attached_circuit
+
+/obj/item/usb_cable/Destroy()
+ attached_circuit = null
+ STOP_PROCESSING(SSobj, src)
+ return ..()
+
+/obj/item/usb_cable/Initialize()
+ . = ..()
+ RegisterSignal(src, COMSIG_MOVABLE_MOVED, .proc/on_moved)
+
+/obj/item/usb_cable/examine(mob/user)
+ . = ..()
+
+ if (!isnull(attached_circuit))
+ . += "It is attached to [attached_circuit.shell || attached_circuit]."
+
+// Look, I'm not happy about this either, but moving an object doesn't call Moved if it's inside something else.
+// There's good reason for this, but there's no element or similar yet to track it as far as I know.
+// SSobj runs infrequently, this is only ran while there's an attached circuit, its performance cost is negligible.
+/obj/item/usb_cable/process(delta_time)
+ if (!check_in_range())
+ return PROCESS_KILL
+
+/obj/item/usb_cable/pre_attack(atom/target, mob/living/user, params)
+ . = ..()
+ if (.)
+ return
+
+ if (prob(1))
+ balloon_alert(user, "wrong way, god damnit")
+ return TRUE
+
+ var/signal_result = SEND_SIGNAL(target, COMSIG_ATOM_USB_CABLE_TRY_ATTACH, src, user)
+
+ var/last_attached_circuit = attached_circuit
+ if (signal_result & COMSIG_USB_CABLE_CONNECTED_TO_CIRCUIT)
+ if (isnull(attached_circuit))
+ CRASH("Producers of COMSIG_USB_CABLE_CONNECTED_TO_CIRCUIT must set attached_circuit")
+ balloon_alert(user, "connected to circuit\nconnect to a port")
+
+ playsound(src, 'sound/machines/pda_button1.ogg', 20, TRUE)
+
+ if (last_attached_circuit != attached_circuit)
+ if (!isnull(last_attached_circuit))
+ unregister_circuit_signals(last_attached_circuit)
+ register_circuit_signals()
+
+ START_PROCESSING(SSobj, src)
+
+ return TRUE
+
+ if (signal_result & COMSIG_USB_CABLE_ATTACHED)
+ // Short messages are better to read
+ var/connection_description = "port"
+ if (istype(target, /obj/machinery/computer))
+ connection_description = "computer"
+ else if (ismachinery(target))
+ connection_description = "machine"
+
+ balloon_alert(user, "connected to [connection_description]")
+ playsound(src, 'sound/items/screwdriver2.ogg', 20, TRUE)
+
+ return TRUE
+
+ if (signal_result & COMSIG_CANCEL_USB_CABLE_ATTACK)
+ return TRUE
+
+ return FALSE
+
+/obj/item/usb_cable/suicide_act(mob/user)
+ user.visible_message("[user] is wrapping [src] around [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!")
+ return OXYLOSS
+
+/obj/item/usb_cable/proc/register_circuit_signals()
+ RegisterSignal(attached_circuit, COMSIG_MOVABLE_MOVED, .proc/on_moved)
+ RegisterSignal(attached_circuit, COMSIG_PARENT_QDELETING, .proc/on_circuit_qdeling)
+ RegisterSignal(attached_circuit.shell, COMSIG_MOVABLE_MOVED, .proc/on_moved)
+
+/obj/item/usb_cable/proc/unregister_circuit_signals(obj/item/integrated_circuit/old_circuit)
+ UnregisterSignal(attached_circuit, list(
+ COMSIG_MOVABLE_MOVED,
+ COMSIG_PARENT_QDELETING,
+ ))
+
+ UnregisterSignal(attached_circuit.shell, COMSIG_MOVABLE_MOVED)
+
+/obj/item/usb_cable/proc/on_moved()
+ SIGNAL_HANDLER
+
+ check_in_range()
+
+/obj/item/usb_cable/proc/check_in_range()
+ if (isnull(attached_circuit))
+ STOP_PROCESSING(SSobj, src)
+ return FALSE
+
+ if (!IN_GIVEN_RANGE(attached_circuit, src, USB_CABLE_MAX_RANGE))
+ balloon_alert_to_viewers("detached, too far away")
+ unregister_circuit_signals(attached_circuit)
+ attached_circuit = null
+ STOP_PROCESSING(SSobj, src)
+ return FALSE
+
+ return TRUE
+
+/obj/item/usb_cable/proc/on_circuit_qdeling()
+ SIGNAL_HANDLER
+
+ attached_circuit = null
+ STOP_PROCESSING(SSobj, src)
diff --git a/code/modules/zombie/organs.dm b/code/modules/zombie/organs.dm
index 0383533c6c896..0143947669932 100644
--- a/code/modules/zombie/organs.dm
+++ b/code/modules/zombie/organs.dm
@@ -30,7 +30,7 @@
/obj/item/organ/zombie_infection/Remove(mob/living/carbon/M, special = 0)
. = ..()
STOP_PROCESSING(SSobj, src)
- if(iszombie(M) && old_species)
+ if(iszombie(M) && old_species && !QDELETED(M))
M.set_species(old_species)
if(timer_id)
deltimer(timer_id)
@@ -40,14 +40,16 @@
web of pus and viscera, bound tightly around the brain like some \
biological harness.")
-/obj/item/organ/zombie_infection/process()
+/obj/item/organ/zombie_infection/process(delta_time)
if(!owner)
return
+ if(owner.IsInStasis())
+ return
if(!(src in owner.internal_organs))
Remove(owner)
if (causes_damage && !iszombie(owner) && owner.stat != DEAD)
- owner.adjustToxLoss(1)
- if (prob(10))
+ owner.adjustToxLoss(0.5 * delta_time)
+ if(DT_PROB(5, delta_time))
to_chat(owner, "You feel sick...")
if(timer_id)
return
diff --git a/config/README.md b/config/README.md
index 6e3ed782863d0..e5d29dc49a300 100644
--- a/config/README.md
+++ b/config/README.md
@@ -1,2 +1,61 @@
#### These are the current config options for the official BeeStation server. If you plan to host your own fork of our code you are heavily encouraged to review and update these files for your needs.
+## Topics Documentation
+Beestation uses a heavily modified topic system originally from Aurorastation, which uses JSON objects for requests to and responses from the server.
+
+### Configuration
+Topic config is managed by the **comms.txt** config file. Topic configuration consists of four types of config entry.
+* `CROSS_COMMS_NAME` Name the server calls itself in outgoing topics.
+* `COMMS_KEY` Multiple of these entries are supported. Consists of a key-value pair of a token (should be randomly generated for security) and the authorized scopes of that token. A list of scopes can be found in the comms.txt file.
+* `CROSS_SERVER` Multiple of these entries are supported. This entry is a key-value pair of a server's BYOND address and the token that has access to the remote server. For more information, see [Handshake](#handshake)
+* `SERVER_HOP` Multiple of these entries are supported. Each entry is a key-value pair of a server name and a byond address. These servers are not authenticated in any way, and are a list of servers that can be quickly switched to by players using the **Server Hop** verb.
+
+### Requests
+Topic requests consist of a JSON object with three mandatory keys: `auth`, `query` and `source`, as well as optional request-specific keys.
+
+* `auth` is the token used to access features on the target server. Can either be configured for use by the server operator, or be the `anonymous` token. The `anonymous` token is a public-access token that allows requests to collect data about player counts, round status etc. For more sensitive information, or to interact with the server itself, a specific auth token will be needed.
+* `query` is the name of the topic function being performed. Examples include `status` and `playerlist`. The query used determines which optional keys must be provided (if any). For a full list of topic functions available, and for additional required keys, see **code/datums/world_topic.dm**
+* `source` this is an identifier for the server sending the request, for logging and administrative purposes.
+
+Example Request:
+```json
+{
+ "auth": "8w7y487238q8x7nqw8dhwe8fq34r89gewri",
+ "query": "ahelp",
+ "source": "BeeStation Sage",
+ "message_sender": "a_player",
+ "message": "I don't see any admins online, can someone help? CE just killed me in maint 4noraisin"
+}
+```
+
+### Response
+Topic responses are very simple, and consist of three keys: `statuscode`, `response` and `data`
+
+* `statuscode` is a number that represents what the outcome of a request was. For example; a response with a status code of `200` means the request succeeded, and a response with a status code of `401` means that the request was lacking or had invalid authorization.
+* `response` is a simple text response detailing the outcome of the request. This key will provide error details for non-`200` status codes, and a short description of actions performed if the request was successful.
+* `data` is a misc key which can contain response data specific to the request. The format of this variable depends on the topic used. For information on what data specific topics return, see **code/datums/world_topic.dm**
+
+Example Response:
+```json
+{
+ "statuscode": 200,
+ "response": "Player count retrieved",
+ "data": 57
+}
+```
+
+### Handshake
+Before being able to send *outgoing* topic calls, a server must handshake with the target server to check what methods it has access to, and to verify that the other server is authorized to receive sensitive information.
+
+#### Initiating Server
+The first step of the process is when the server initiating the handshake first starts up. The initiating server will make a call to the target server's `api_do_handshake` method with the token for that server as specified in the config.
+The target server will then either respond that the token is unauthorized, or with a list of query methods that the connecting server is allowed to use, along with a token it has stored for the initiating server.
+
+The initiating server will then compare the list of functions received from the remote server with the list of functions sent by the remote server with the list of functions the initiating server has for the token the remote server sent back. Functions present in *both lists* will then be stored in a global list under the the server address of the remote server.
+
+This list is then used to decide what servers to forward sensitive information to, such as ahelps, by only allowing data to be sent out after both servers verify authorization at both ends.
+
+#### Remote Server
+When a server receives a request to the `api_do_handshake` method, it will lookup the list of functions authorized for the provided token, as well as look for a configured token for the connecting server based on its IP address. If neither of these things are found, the server will respond with a 401 unauthorized response.
+
+If both prerequisites are found, the server will respond with the token it has stored for the server, as well as the list of authorized functions it found. In addition, if the server does not have the requesting server's functions stored too, it will make its own handshake request to the requesting server to collect the neccessary information.
diff --git a/config/Sage/README.md b/config/Sage/README.md
deleted file mode 100644
index 01a642b51a06a..0000000000000
--- a/config/Sage/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-#### These are the current config options for the official BeeStation MRP server Sage. If you plan to host your own fork of our code you are heavily encouraged to review and update these files for your needs.
-
diff --git a/config/Sage/admin_nicknames.json b/config/Sage/admin_nicknames.json
deleted file mode 100644
index 9c83f646229d6..0000000000000
--- a/config/Sage/admin_nicknames.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "ranks": [
- "Dedmin",
- "Sir Madam Headmin"
- ],
- "names": [
- "Badmin",
- "Spanmin"
- ]
-}
\ No newline at end of file
diff --git a/config/Sage/admin_ranks.txt b/config/Sage/admin_ranks.txt
deleted file mode 100644
index 242c896c1743e..0000000000000
--- a/config/Sage/admin_ranks.txt
+++ /dev/null
@@ -1,100 +0,0 @@
-#Admin Rank format is as follows:
-#
-#Name = Game Admin
-#Include = @ ADMIN BAN SOUND
-#Exclude = FUN
-#Edit =
-#
-#Name will match anything after '=' and must be identical to an admin's rank in admins.txt to be linked but otherwise has no formatting restrictions.
-#A rank's permissions are defined with keywords that control access to groups of verbs and abilities, they are case-sensitive and separated by a space with no prefix.
-#To define no permissions for a type, leave it empty.
-#There are three types of permissions:
-#Include will give a keyword to a rank.
-#Exclude removes a keyword and takes precedence over Include.
-#Edit will allow an admin to edit these permissions on other ranks or change an admin's rank to another if they can edit all the permissions it has.
-#Edit is only used when SQL-based admin loading is enabled.
-#If SQL-based admin loading is enabled, ranks and their keywords listed here will be loaded first and override any with the same name loaded from the database.
-#
-#The following are valid permission keywords:
-#ADMIN = general admin tools, verbs etc.
-#FUN = events, other event-orientated actions. Access to the fun secrets in the secrets panel.
-#BAN = the ability to ban and unban.
-#STEALTH = the ability to stealthmin (make yourself appear with a fake name to everyone but other admins.
-#POSSESS = the ability to possess objects.
-#POLL = the ability to create in game server polls (requires DB).
-#BUILD = the ability to use buildmode.
-#SERVER = the ability to restart the server, change the game mode or force a round to start/end.
-#DEBUG = debug tools used for diagnosing and fixing problems. It's useful to give this to coders so they can investigate problems on a live server.
-#VAREDIT = everyone may view viewvars/debugvars/whatever you call it. This keyword allows you to actually EDIT those variables.
-#PERMISSIONS = allows you to promote and/or demote people.
-#SOUND = allows you to upload and play SOUND.
-#SPAWN = mob transformations, spawning of most atoms including mobs (high-risk atoms, e.g. blackholes, will require the +FUN flag too).
-#AUTOADMIN = admin gains powers upon connect. This defaults to on, you can exclude AUTOADMIN to make a role require using the readmin verb to gain powers (this does not effect the admin's ability to walk past bans or other on-connect limitations like panic bunker or pop limit).
-#DBRANKS = when sql-based admin loading is enabled, allows for non-temporary changes in the permissions panel to be saved (requires DB).
-#EVERYTHING = Simply gives you everything without having to type every flag.
-#@ = special keyword for the current permission type that adds all the keywords that the preceding rank has of the same type.
-
-Name = Admin Observer
-Include =
-Exclude = AUTOADMIN
-Edit =
-
-Name = Moderator
-Include = ADMIN
-Exclude =
-Edit =
-
-Name = Admin Candidate
-Include = @
-Exclude =
-Edit =
-
-Name = Trial Admin
-Include = @ SPAWN VAREDIT BAN
-Exclude =
-Edit =
-
-Name = Badmin
-Include = @ POSSESS POLL BUILD SERVER FUN
-Exclude =
-Edit =
-
-Name = Admin
-Include = @ STEALTH SOUND DEBUG
-Exclude =
-Edit =
-
-Name = Game Master
-Include = EVERYTHING
-Exclude =
-Edit = EVERYTHING
-
-Name = Lazy Master
-Include = EVERYTHING
-Exclude = AUTOADMIN
-Edit = EVERYTHING
-
-Name = Host
-Include = EVERYTHING
-Exclude =
-Edit = EVERYTHING
-
-Name = Lurker
-Include = EVERYTHING
-Exclude =
-Edit = EVERYTHING
-
-Name = Headmin
-Include = EVERYTHING
-Exclude =
-Edit = EVERYTHING
-
-Name = Coder
-Include = DEBUG VAREDIT SERVER SPAWN POLL
-Exclude = AUTOADMIN
-Edit =
-
-Name = CouncilMember
-Include = EVERYTHING
-Exclude =
-Edit = EVERYTHING
diff --git a/config/Sage/admins.txt b/config/Sage/admins.txt
deleted file mode 100644
index 362a4378a6f8f..0000000000000
--- a/config/Sage/admins.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-###############################################################################################
-# Basically, ckey goes first. Rank goes after the "=" #
-# Case is not important for ckey. #
-# Case IS important for the rank. #
-# All punctuation (spaces etc) EXCEPT '-', '_' and '@' will be stripped from rank names. #
-# Ranks can be anything defined in admin_ranks.txt #
-# NOTE: if the rank-name cannot be found in admin_ranks.txt, they will not be adminned! ~Carn #
-# NOTE: syntax was changed to allow hyphenation of ranknames, since spaces are stripped. #
-# If SQL-based admin loading is enabled, admins listed here will always be loaded first #
-# and will override any duplicate entries in the database. #
-###############################################################################################
-Ike709 = Host
-Crossedfall = Host
-CthulhuOnIce = Host
-
-# the rest are in the db
diff --git a/config/Sage/antag_rep.txt b/config/Sage/antag_rep.txt
deleted file mode 100644
index e8a7250686a41..0000000000000
--- a/config/Sage/antag_rep.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-## Custom antag reputation values
-## List of job titles followed by antag rep value, all prefixed with ANTAG_REP. See code/modules/jobs/job_types for titles
-## e.g.
-## ANTAG_REP Captain 10
-## ANTAG_REP Assistant 0
diff --git a/config/Sage/awaymissionconfig.txt b/config/Sage/awaymissionconfig.txt
deleted file mode 100644
index 2c5081343031d..0000000000000
--- a/config/Sage/awaymissionconfig.txt
+++ /dev/null
@@ -1,26 +0,0 @@
-#List the potential random Z-levels here.
-#Maps must be the full path to them
-#Maps should be 255x255 or smaller and be bounded. Falling off the edge of the map will result in undefined behavior.
-#SPECIFYING AN INVALID MAP WILL RESULT IN RUNTIMES ON GAME START
-
-#!!IMPORTANT NOTES FOR HOSTING AWAY MISSIONS!!:
-#Do NOT tick the maps during compile -- the game uses this list to decide which map to load. Ticking the maps will result in them ALL being loaded at once.
-#DO tick the associated code file for the away mission you are enabling. Otherwise, the map will be trying to reference objects which do not exist, which will cause runtime errors!
-
-_maps/RandomZLevels/blackmarketpackers.dmm
-_maps/RandomZLevels/spacebattle.dmm
-_maps/RandomZLevels/beach.dmm
-#_maps/RandomZLevels/Academy.dmm
-_maps/RandomZLevels/wildwest.dmm
-_maps/RandomZLevels/challenge.dmm
-_maps/RandomZLevels/centcomAway.dmm
-_maps/RandomZLevels/moonoutpost19.dmm
-_maps/RandomZLevels/undergroundoutpost45.dmm
-_maps/RandomZLevels/caves.dmm
-_maps/RandomZLevels/snowdin.dmm
-_maps/RandomZLevels/research.dmm
-_maps/RandomZLevels/Cabin.dmm
-_maps/RandomZLevels/beach2.dmm
-_maps/RandomZLevels/VR/murderdome.dmm
-_maps/RandomZLevels/VR/snowdin_VR.dmm
-_maps/RandomZLevels/VR/syndicate_trainer.dmm
diff --git a/config/Sage/comms.txt b/config/Sage/comms.txt
deleted file mode 100644
index 147b6db3a279a..0000000000000
--- a/config/Sage/comms.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-## Communication key for receiving data through world/Topic(), you don't want to give this out
-#COMMS_KEY defaultkey
-
-## World address and port for server receiving cross server messages
-## Use '+' to denote spaces in ServerName
-## Repeat this entry to add more servers
-#CROSS_SERVER ServerName byond:\\address:port
-
-## Name that the server calls itself in communications
-#CROSS_COMMS_NAME
-
-## Hub address for tracking stats
-## example: Hubmakerckey.Hubname
-#MEDAL_HUB_ADDRESS Hubmakerckey.Hubname
-
-## Password for the hub page
-#MEDAL_HUB_PASSWORD defaultpass
diff --git a/config/Sage/config.txt b/config/Sage/config.txt
deleted file mode 100644
index f81ffc1fc0114..0000000000000
--- a/config/Sage/config.txt
+++ /dev/null
@@ -1,553 +0,0 @@
-# You can use the "$include" directive to split your configs however you want
-
-$include game_options.txt
-$include dbconfig.txt
-$include comms.txt
-$include antag_rep.txt
-$include resources.txt
-
-# You can use the @ character at the beginning of a config option to lock it from being edited in-game
-# Example usage:
-# @SERVERNAME beestation
-# Which sets the SERVERNAME, and disallows admins from being able to change it using View Variables.
-# @LOG_TWITTER 0
-# Which explicitly disables LOG_TWITTER, as well as locking it.
-# There are various options which are hard-locked for security reasons.
-
-## Server name: This appears at the top of the screen in-game and in the BYOND hub. Uncomment and replace 'beestation' with the name of your choice.
-#@SERVERNAME beestation
-
-## Server SQL name: This is the name used to identify the server to the SQL DB, distinct from SERVERNAME as it must be at most 32 characters.
-#@SERVERSQLNAME ss13newbs
-
-## Station name: The name of the station as it is referred to in-game. If commented out, the game will generate a random name instead.
-STATIONNAME Space Station 13
-
-## Put on byond hub: Uncomment this to put your server on the byond hub.
-#HUB
-
-## Lobby time: This is the amount of time between rounds that players have to setup their characters and be ready.
-LOBBY_COUNTDOWN 180
-
-## Round End Time: This is the amount of time after the round ends that players have to murder death kill each other.
-ROUND_END_COUNTDOWN 90
-
-#USING_DISCORD
-
-## Comment this out if you want to use the SQL based admin system, the legacy system uses admins.txt.
-## You need to set up your database to use the SQL based system.
-## This flag is automatically enabled if SQL_ENABLED isn't
-ADMIN_LEGACY_SYSTEM
-
-##Uncomment this to stop any admins loaded by the legacy system from having their rank edited by the permissions panel
-#PROTECT_LEGACY_ADMINS
-
-##Uncomment this to stop any ranks loaded by the legacy system from having their flags edited by the permissions panel
-#PROTECT_LEGACY_RANKS
-
-##Uncomment this to have admin ranks only loaded from the legacy admin_ranks.txt
-##If enabled, each time admins are loaded ranks the database will be updated with the current ranks and their flags
-#LOAD_LEGACY_RANKS_ONLY
-
-## Comment this out if you want to use the SQL based mentor system, the legacy system uses mentors.txt.
-## You need to set up your database to use the SQL based system.
-## This flag is automatically enabled if SQL_ENABLED isn't
-MENTOR_LEGACY_SYSTEM
-
-#Mentors only see ckeys by default. Uncomment to have them only see mob name
-#MENTORS_MOBNAME_ONLY
-
-## Comment this out if you want to use the SQL based banning system. The legacy systems use the files in the data folder. You need to set up your database to use the SQL based system.
-BAN_LEGACY_SYSTEM
-
-## Comment this out to stop locally connected clients from being given the almost full access !localhost! admin rank
-ENABLE_LOCALHOST_RANK
-
-## Uncomment this entry to have certain jobs require your account to be at least a certain number of days old to select. You can configure the exact age requirement for different jobs by editing
-## the minimal_player_age variable in the files in folder /code/game/jobs/job/.. for the job you want to edit. Set minimal_player_age to 0 to disable age requirement for that job.
-## REQUIRES the database set up to work. Keep it hashed if you don't have a database set up.
-## NOTE: If you have just set-up the database keep this DISABLED, as player age is determined from the first time they connect to the server with the database up. If you just set it up, it means
-## you have noone older than 0 days, since noone has been logged yet. Only turn this on once you have had the database up for 30 days.
-#USE_AGE_RESTRICTION_FOR_JOBS
-
-## Uncomment this to have the job system use the player's account creation date, rather than the when they first joined the server for job timers.
-#USE_ACCOUNT_AGE_FOR_JOBS
-
-## Unhash this to track player playtime in the database. Requires database to be enabled.
-USE_EXP_TRACKING
-## Unhash this to enable playtime requirements for head jobs.
-USE_EXP_RESTRICTIONS_HEADS
-## Unhash this to override head jobs' playtime requirements with this number of hours.
-## Leave this commented out to use the values defined in the job datums. Values in the datums are stored as minutes.
-#USE_EXP_RESTRICTIONS_HEADS_HOURS 3
-## Unhash this to change head jobs' playtime requirements so that they're based on department playtime, rather than crew playtime.
-USE_EXP_RESTRICTIONS_HEADS_DEPARTMENT
-## Unhash this to enable playtime requirements for certain non-head jobs, like Engineer and Scientist.
-USE_EXP_RESTRICTIONS_OTHER
-## Allows admins to bypass job playtime requirements.
-USE_EXP_RESTRICTIONS_ADMIN_BYPASS
-
-## log OOC channel
-LOG_OOC
-
-## log client Say
-LOG_SAY
-
-## log admin actions
-LOG_ADMIN
-
-## log admin chat
-LOG_ADMINCHAT
-
-## log client access (logon/logoff)
-LOG_ACCESS
-
-## log game actions (start of round, results, etc.)
-LOG_GAME
-
-## log antag objectives
-LOG_OBJECTIVE
-
-## log player votes
-LOG_VOTE
-
-## log client Whisper
-LOG_WHISPER
-
-## log emotes
-LOG_EMOTE
-
-## log attack messages
-LOG_ATTACK
-
-## log pda messages
-LOG_PDA
-
-## log telecomms messages
-LOG_TELECOMMS
-
-## log prayers
-LOG_PRAYER
-
-## log lawchanges
-LOG_LAW
-
-## log viruses
-LOG_VIRUS
-
-## log ID changes
-LOG_ID
-
-## log crew manifest to seperate file
-LOG_MANIFEST
-
-## log job divide debugging information
-#LOG_JOB_DEBUG
-
-## log all world.Topic() calls
-# LOG_WORLD_TOPIC
-
-## enables use of the proc twitterize() that lets you take a large list of strings and turn it into a JSON file of tweet sized strings.
-## As an example of how this could be """useful""" look towards Poly (https://twitter.com/Poly_the_Parrot)
-# LOG_TWITTER
-
-## Enable logging pictures
-# LOG_PICTURES
-
-## log mecha actions
-# LOG_MECHA
-
-##Log camera pictures - Must have picture logging enabled
-PICTURE_LOGGING_CAMERA
-
-## period of time in seconds for players to be considered inactive
-# INACTIVITY_PERIOD 300
-
-## period of time in seconds for players to be considered afk and kickable
-# AFK_PERIOD 600
-
-## disconnect players who are considered afk
-# KICK_INACTIVE
-
-## Comment this out to stop admins being able to choose their personal ooccolor
-ALLOW_ADMIN_OOCCOLOR
-
-## Comment this out to stop admins being able to choose their personal asay color
-ALLOW_ADMIN_ASAYCOLOR
-
-## Job slot open/close by identification consoles delay in seconds
-ID_CONSOLE_JOBSLOT_DELAY 30
-
-## allow players to initiate a restart vote
-#ALLOW_VOTE_RESTART
-
-## allow players to initate a mode-change start
-#ALLOW_VOTE_MODE
-
-## min delay (deciseconds) between voting sessions (default 10 minutes)
-VOTE_DELAY 6000
-
-## time period (deciseconds) which voting session will last (default 1 minute)
-VOTE_PERIOD 600
-
-## prevents dead players from voting or starting votes
-NO_DEAD_VOTE
-
-## players' votes default to "No vote" (otherwise, default to "No change")
-DEFAULT_NO_VOTE
-
-## disable abandon mob
-NORESPAWN
-
-## disables calling del(src) on newmobs if they logout before spawnin in
-# DONT_DEL_NEWMOB
-
-## set a hosted by name for unix platforms
-HOSTEDBY Crossedfall
-
-## Set to jobban "Guest-" accounts from Captain, HoS, HoP, CE, RD, CMO, Warden, Security, Detective, and AI positions.
-## Set to 1 to jobban them from those positions, set to 0 to allow them.
-# GUEST_JOBBAN
-
-## Uncomment this to stop people connecting to your server without a registered ckey. (i.e. guest-* are all blocked from connecting)
-GUEST_BAN
-
-## Comment this out to disable checking for the cid randomizer dll. (disabled if database isn't enabled or connected)
-CHECK_RANDOMIZER
-
-## IPINTEL:
-## This allows you to detect likely proxies by checking ips against getipintel.net
-## Rating to warn at: (0.9 is good, 1 is 100% likely to be a spammer/proxy, 0.8 is 80%, etc) anything equal to or higher then this number triggers an admin warning
-#IPINTEL_RATING_BAD 0.9
-## Contact email, (required to use the service, leaving blank or default disables IPINTEL)
-#IPINTEL_EMAIL ch@nge.me
-## How long to save good matches (ipintel rate limits to 15 per minute and 500 per day. so this shouldn't be too low, getipintel.net suggests 6 hours, time is in hours) (Your ip will get banned if you go over 500 a day too many times)
-#IPINTEL_SAVE_GOOD 12
-## How long to save bad matches (these numbers can change as ips change hands, best not to save these for too long in case somebody gets a new ip used by a spammer/proxy before.)
-#IPINTEL_SAVE_BAD 3
-## Domain name to query (leave commented out for the default, only needed if you pay getipintel.net for more querys)
-#IPINTEL_DOMAIN check.getipintel.net
-
-## Uncomment to allow web client connections
-#ALLOW_WEBCLIENT
-
-## Uncomment to restrict web client connections to byond members
-## This makes for a nice pay gate to cut down on ban evading, as the webclient's cid system isn't that great
-## byond membership starts at $10 for 3 months, so to use the webclient to evade, they would have sink 10 bucks in each evade.
-#WEBCLIENT_ONLY_BYOND_MEMBERS
-
-## Set to prevent anyone but those ckeys listed in config/whitelist.txt and config/admins.txt from joining your server
-#USEWHITELIST
-
-## set a server location for world reboot. Don't include the byond://, just give the address and port.
-## Don't set this to the same server, BYOND will automatically restart players to the server when it has restarted.
-SERVER byond://sage.beestation13.com:7878
-
-## forum address
-FORUMURL https://forums.beestation13.com
-
-## Wiki address
-WIKIURL https://wiki.beestation13.com/view
-
-## Rules address
-RULESURL https://beestation13.com/rules?server=bs_sage
-
-## Github address
-GITHUBURL https://www.github.com/beestation/beestation-hornet
-
-## Label to tag ingame-registered issues with.
-## Uncomment this to enable them.
-#ISSUE_LABEL Ingame Issue Report
-
-## Donation URL
-DONATEURL https://www.patreon.com/user?u=10639001
-
-## Discord Invite
-DISCORDURL https://discord.gg/zUe34rs
-
-## Round specific stats address
-## Link to round specific parsed logs; IE statbus. It is appended with the RoundID automatically by ticker/Reboot()
-## This will take priority over the game logs address during reboot.
-## Example: https://atlantaned.space/statbus/round.php?round=
-# ROUNDSTATSURL
-
-## Game Logs address
-## Incase you don't have a fancy parsing system, but still want players to be able to find where you keep your server's logs.
-## Example: https://tgstation13.org/parsed-logs/basil/data/logs/
-# GAMELOGURL
-
-## Github repo id
-##This can be found by going to https://api.github.com/users//repos
-##Or https://api.github.com/orgs//repos if the repo owner is an organization
-GITHUBREPOID 154500094
-
-## Ban appeals URL - usually for a forum or wherever people should go to contact your admins.
-BANAPPEALS https://forums.beestation13.com/c/ban-appeals/game-ban-appeals
-
-## System command that invokes youtube-dl, used by Play Internet Sound.
-## You can install youtube-dl with
-## "pip install youtube-dl" if you have pip installed
-## from https://github.com/rg3/youtube-dl/releases
-## or your package manager
-## The default value assumes youtube-dl is in your system PATH
-INVOKE_YOUTUBEDL C:\youtubedl\youtube-dl.exe
-
-## In-game features
-##Toggle for having jobs load up from the .txt
-LOAD_JOBS_FROM_TXT
-
-## Uncomment this to forbid admins from possessing the singularity.
-#FORBID_SINGULO_POSSESSION
-
-## Uncomment to show a popup 'reply to' window to every non-admin that receives an adminPM.
-## The intention is to make adminPMs more visible. (although I fnd popups annoying so this defaults to off)
-POPUP_ADMIN_PM
-
-## Uncomment to allow special 'Easter-egg' events on special holidays such as seasonal holidays and stuff like 'Talk Like a Pirate Day' :3 YAARRR
-ALLOW_HOLIDAYS
-
-## Uncomment to show the names of the admin sending a pm from IRC instead of showing as a stealthmin.
-#SHOW_IRC_NAME
-
-## Defines the ticklimit for subsystem initialization (In percents of a byond tick). Lower makes world start smoother. Higher makes it faster.
-##This is currently a testing optimized setting. A good value for production would be 98.
-TICK_LIMIT_MC_INIT 500
-
-##Defines the ticklag for the world. Ticklag is the amount of time between game ticks (aka byond ticks) (in 1/10ths of a second).
-## This also controls the client network update rate, as well as the default client fps
-TICKLAG 0.5
-
-##Can also be set as per-second value, the following value is identical to the above.
-#FPS 20
-
-## Comment this out to disable automuting
-AUTOMUTE_ON
-
-## Uncomment this to let players see their own notes (they can still be set by admins only)
-SEE_OWN_NOTES
-
-### Comment these two out to prevent notes fading out over time for admins.
-## Notes older then this will start fading out.
-NOTE_FRESH_DAYS 91.31055
-## Notes older then this will be completely faded out.
-NOTE_STALE_DAYS 365.2422
-
-##Note: all population caps can be used with each other if desired.
-
-## Uncomment for 'soft' population caps, players will be warned while joining if the living crew exceeds the listed number.
-SOFT_POPCAP 100
-
-## Message for soft cap
-SOFT_POPCAP_MESSAGE Be warned that the server is currently serving a high number of users, consider using alternative game servers.
-
-## Uncomment for 'hard' population caps, players will not be allowed to spawn if the living crew exceeds the listed number, though they may still observe or wait for the living crew to decrease in size.
-HARD_POPCAP 175
-
-## Message for hard cap
-HARD_POPCAP_MESSAGE The server is currently serving a high number of users, You cannot currently join. You may wait for the number of living crew to decline, observe, or find alternative servers.
-
-## Uncomment for 'extreme' population caps, players will not be allowed to join the server if living crew exceeds the listed number.
-EXTREME_POPCAP 200
-
-## Message for extreme cap
-EXTREME_POPCAP_MESSAGE The server is currently serving a high number of users, find alternative servers, or wait.
-
-## Notify admins when a new player connects for the first x days a player's been around. (0 for first connection only, -1 for never)
-## Requres database
-NOTIFY_NEW_PLAYER_AGE 0
-
-## Notify admins when a player connects if their byond account was created in the last X days
-## Requires database
-NOTIFY_NEW_PLAYER_ACCOUNT_AGE 1
-
-## Notify the irc channel when a new player makes their first connection
-## Requres database
-#IRC_FIRST_CONNECTION_ALERT
-
-## Deny all new connections by ckeys we haven't seen before (exempts admins and only denies the connection if the database is enabled and connected)
-## Requires database
-#PANIC_BUNKER
-
-## If panic bunker is on and a player is rejected (see above), attempt to send them to this connected server (see below) instead.
-## You probably want this to be the same as CROSS_SERVER_ADDRESS
-#PANIC_SERVER_ADDRESS byond://address:port
-
-##Name of the place to send people rejected by the bunker
-#PANIC_SERVER_NAME [Put the name here]
-
-## Uncomment to have the changelog file automatically open when a user connects and hasn't seen the latest changelog
-AGGRESSIVE_CHANGELOG
-
-## Comment this out if you've used the mass conversion sql proc for notes or want to stop converting notes
-AUTOCONVERT_NOTES
-
-## Comment this out to stop admin messages sent anytime an admin disconnects from a round in play, you can edit the messages in admin.dm
-ANNOUNCE_ADMIN_LOGOUT
-
-## Uncomment to have an admin message sent anytime an admin connects to a round in play, you can edit the messages in admin.dm
-ANNOUNCE_ADMIN_LOGIN
-
-## Map rotation
-## You should edit maps.txt to match your configuration when you enable this.
-MAPROTATION
-
-## Map voting
-## Allows players to vote for their preffered map
-## When it's set to zero, the map will be randomly picked each round
-PREFERENCE_MAP_VOTING 0
-
-## Starts a map vote at the end of rounds
-#AUTOMAPVOTE
-
-## Map rotate chance delta
-## This is the chance of map rotation factored to the round length.
-## A value of 1 would mean the map rotation chance is the round length in minutes (hour long round == 60% rotation chance)
-## A value of 0.5 would mean the map rotation chance is half of the round length in minutes (hour long round == 30% rotation chance)
-#MAPROTATIONCHANCEDELTA 0.5
-
-## AUTOADMIN
-## The default admin rank
-AUTOADMIN_RANK Admin
-
-## Uncomment to automatically give that admin rank to all players
-#AUTOADMIN
-
-## CLIENT VERSION CONTROL
-## This allows you to configure the minimum required client version, as well as a warning version, and message for both.
-## These trigger for any version below (non-inclusive) the given version, so 510 triggers on 509 or lower.
-## These messages will be followed by one stating the clients current version and the required version for clarity.
-## If CLIENT_WARN_POPUP is uncommented a popup window with the message will be displayed instead
-#CLIENT_WARN_VERSION 513
-#CLIENT_WARN_POPUP
-#CLIENT_WARN_MESSAGE 512 is no longer being directly supported as version 513 is set to become the new stable version soon. We've made a number of changes to take advantage of the improvements made in 513 which should make for a smoother experience. We will be removing support for 512 when this new version replaces it as stable, so it's recommended that you upgrade now. (You can update to the BETA via the website or directly in the BYOND client)
-CLIENT_ERROR_VERSION 513
-CLIENT_ERROR_MESSAGE Your version of byond is not supported. Please upgrade.
-## The minimum build needed for joining the server, if using 513, a good minimum build would be 1526.
-CLIENT_ERROR_BUILD 1526
-
-## TOPIC RATE LIMITING
-## This allows you to limit how many topic calls (clicking on an interface window) the client can do in any given game second and/or game minute.
-## Admins are exempt from these limits.
-## Hitting the minute limit notifies admins.
-## Set to 0 or comment out to disable.
-SECOND_TOPIC_LIMIT 10
-
-MINUTE_TOPIC_LIMIT 180
-
-## CLICK RATE LIMITING
-## Same as above, but applies to clicking on objects in the game window.
-## This should be a higher then the interface limit to allow for the spam clickly nature of most battles.
-## Admins are exempt from these limits.
-## Hitting the minute limit notifies admins.
-## Set to 0 to disable.
-SECOND_CLICK_LIMIT 15
-
-MINUTE_CLICK_LIMIT 500
-
-##Error handling related options
-## The "cooldown" time for each occurence of a unique error
-#ERROR_COOLDOWN 600
-## How many occurences before the next will silence them
-#ERROR_LIMIT 90
-## How long a unique error will be silenced for
-#ERROR_SILENCE_TIME 6000
-##How long to wait between messaging admins about occurences of a unique error
-#ERROR_MSG_DELAY 50
-
-## Send a message to IRC when starting a new game
-#IRC_ANNOUNCE_NEW_GAME
-
-## Allow admin hrefs that don't use the new token system, will eventually be removed
-DEBUG_ADMIN_HREFS
-
-###Master Controller High Pop Mode###
-
-##The Master Controller(MC) is the primary system controlling timed tasks and events in SS13 (lobby timer, game checks, lighting updates, atmos, etc)
-##Default base MC tick rate (1 = process every "byond tick" (see: tick_lag/fps config settings), 2 = process every 2 byond ticks, etc)
-## Setting this to 0 will prevent the Master Controller from ticking
-BASE_MC_TICK_RATE 1
-
-##High population MC tick rate
-## Byond rounds timer values UP, but the tick rate is modified with heuristics during lag spites so setting this to something like 2
-## will make it run every 2 byond ticks, but will also double the effect of anti-lag heuristics. You can instead set it to something like
-## 1.1 to make it run every 2 byond ticks, but only increase the effect of anti-lag heuristics by 10%. or 1.5 for 50%.
-## (As an aside, you could in theory also reduce the effect of anti-lag heuristics in the base tick rate by setting it to something like 0.5)
-HIGH_POP_MC_TICK_RATE 1.2
-
-##Engage high pop mode if player count raises above this (Player in this context means any connected user. Lobby, ghost or in-game all count)
-HIGH_POP_MC_MODE_AMOUNT 90
-
-##Disengage high pop mode if player count drops below this
-DISABLE_HIGH_POP_MC_MODE_AMOUNT 85
-
-## Uncomment to prevent the world from sleeping while no players are connected after initializations
-#RESUME_AFTER_INITIALIZATIONS
-
-## Uncomment to set the number of /world/Reboot()s before the DreamDaemon restarts itself. 0 means restart every round. Requires tgstation server tools.
-ROUNDS_UNTIL_HARD_RESTART 3
-
-##Default screen resolution, in tiles.
-## By default, this is 15x15, which gets simplified to 7 by BYOND, as it is a 1:1 screen ratio.
-## For reference, Goonstation uses a resolution of 21x15 for it's widescreen mode.
-## Do note that changing this value will affect the title screen. The title screen will have to be updated manually if this is changed.
-DEFAULT_VIEW 17x15
-
-##Should we use square view (15x15) while a new player (main menu)
-MENU_SQUARE_VIEW
-
-METACURRENCY_NAME BeeCoin
-
-## Grant metacurrency
-## Comment this out if you don't want players to earn metacurrency
-GRANT_METACURRENCY
-
-## Respect global bans
-## Comment this out to ignore global bans
-RESPECT_GLOBAL_BANS
-
-## Custom shuttle spam prevention. Changine these numbers allows you to change the maxsize and amount of custom shuttles.
-MAX_SHUTTLE_COUNT 6
-MAX_SHUTTLE_SIZE 300
-
-### Fail2Topic settings
-### fail2topic is a system for automating IP bans for abusers of the world/Topic API.
-### Note that this subsystem respects the IPs listed in topic_rate_limit_whitelist.txt. They will not be considered for a ban.
-## Topic rate limit in *seconds*
-TOPIC_RATE_LIMIT 5
-## Topic max fails
-TOPIC_MAX_FAILS 5
-## Firewall rule name
-TOPIC_RULE_NAME "_DD_Fail2topic"
-## Max world/Topic payload size
-TOPIC_MAX_SIZE 500
-## Uncomment to enable Fail2Topic, comment-out to disable
-#TOPIC_ENABLED
-
-## Uncomment to enable global ban DB using the provided URL. The API should expect to receive a ckey at the end of the URL.
-## More API details can be found here: https://centcom.melonmesa.com
-CENTCOM_BAN_DB https://centcom.melonmesa.com/ban/search
-
-## Respect upstream bans. Uncomment to automatically block connections from players that are banned from both BeeStation servers
-## Requires CENTCOM_BAN_DB to be enabled with a valid URL.
-#RESPECT_UPSTREAM_BANS
-## Only respect upstream bans if it's a permaban
-#RESPECT_UPSTREAM_PERMABANS
-
-## Enable IC/OOC Hard Filtering. Comment to disable one or both of them
-#IC_FILTER_ENABLED
-#OOC_FILTER_ENABLED
-
-## Uncomment to enable redirecting to the specified address. If a user attempts to join after EXTREEME_POPCAP has been reached, redirect them to this address.
-## The given address is a format example. "byond://" is required to function. Create a potential infinite loop at your own risk.
-#REDIRECT_ADDRESS byond://golden.beestation13.com:7777
-
-### Autotransfer Settings
-## Uncomment to enable shuttle autotransfer
-VOTE_AUTOTRANSFER_ENABLED
-## Time (in deciseconds) before the first transfer vote. Default: 90 minutes
-VOTE_AUTOTRANSFER_INITIAL 54000
-## Time (in deciseconds) between subsequent transfer votes. Default: 30 minutes
-VOTE_AUTOTRANSFER_INTERVAL 18000
-
-## Ghost role cooldown time after death (In deciseconds)
-GHOST_ROLE_COOLDOWN 3000
diff --git a/config/Sage/dbconfig.txt b/config/Sage/dbconfig.txt
deleted file mode 100644
index 06091e8b49620..0000000000000
--- a/config/Sage/dbconfig.txt
+++ /dev/null
@@ -1,45 +0,0 @@
-## MySQL Connection Configuration
-## This is used for stats, feedback gathering,
-## administration, and the in game library.
-
-## Should SQL be enabled? Uncomment to enable
-#SQL_ENABLED
-
-## Server the MySQL database can be found at.
-## Examples: localhost, 200.135.5.43, www.mysqldb.com, etc.
-ADDRESS localhost
-
-## MySQL server port (default is 3306).
-PORT 3306
-
-## Database for all SQL functions, not just feedback.
-FEEDBACK_DATABASE ss13beedb
-
-## Prefix to be added to the name of every table, older databases will require this be set to erro_
-## Note, this does not change the table names in the database, you will have to do that yourself.
-##IE:
-## FEEDBACK_TABLEPREFIX
-## FEEDBACK_TABLEPREFIX SS13_
-## Remove "SS13_" if you are using the standard schema file.
-FEEDBACK_TABLEPREFIX SS13_
-
-## Username/Login used to access the database.
-FEEDBACK_LOGIN ss13dbuser
-
-## Password used to access the database.
-FEEDBACK_PASSWORD password1
-
-## Time in seconds for asynchronous queries to timeout
-## Set to 0 for infinite
-ASYNC_QUERY_TIMEOUT 10
-
-## Time in seconds for blocking queries to execute before slow query timeout
-## Set to 0 for infinite
-## Must be less than or equal to ASYNC_QUERY_TIMEOUT
-BLOCKING_QUERY_TIMEOUT 5
-
-## The maximum number of additional threads BSQL is allowed to run at once
-BSQL_THREAD_LIMIT 50
-
-## Uncomment to enable verbose BSQL communication logs
-#BSQL_DEBUG
diff --git a/config/Sage/external_rsc_urls.txt b/config/Sage/external_rsc_urls.txt
deleted file mode 100644
index 917a21907db3c..0000000000000
--- a/config/Sage/external_rsc_urls.txt
+++ /dev/null
@@ -1 +0,0 @@
-http://rsc.beestation13.buzz/beestation.zip
diff --git a/config/Sage/game_options.txt b/config/Sage/game_options.txt
deleted file mode 100644
index f2f73493acd7b..0000000000000
--- a/config/Sage/game_options.txt
+++ /dev/null
@@ -1,612 +0,0 @@
-## HEALTH ###
-
-##Damage multiplier, effects both weapons and healing on all mobs. For example, 1.25 would result in 25% higher damage.
-DAMAGE_MULTIPLIER 1
-
-## REVIVAL ###
-
-## whether pod plants work or not
-REVIVAL_POD_PLANTS
-
-## whether cloning tubes work or not
-REVIVAL_CLONING
-
-## amount of time (in hundredths of seconds) for which a brain retains the "spark of life" after the person's death (set to -1 for infinite)
-REVIVAL_BRAIN_LIFE -1
-
-## OOC DURING ROUND ###
-## Comment this out if you want OOC to be automatically disabled during the round, it will be enabled during the lobby and after the round end results.
-#OOC_DURING_ROUND
-
-## LOOC
-## Comment this out to disable LOOC
-LOOC_ENABLED
-
-## EMOJI ###
-## Comment this out if you want to disable emojis
-EMOJIS
-
-## MOB MOVEMENT ###
-
-## We suggest editing these variables ingame to find a good speed for your server.
-## To do this you must be a high level admin. Open the 'debug' tab ingame.
-## Select "Debug Controller" and then, in the popup, select "Configuration". These variables should have the same name.
-
-## These values get directly added to values and totals ingame.
-## To speed things up make the number negative, to slow things down, make the number positive.
-
-## These modify the run/walk speed of all mobs before the mob-specific modifiers are applied.
-RUN_DELAY 1.5
-WALK_DELAY 3
-
-## The variables below affect the movement of specific mob types. THIS AFFECTS ALL SUBTYPES OF THE TYPE YOU CHOOSE!
-## Entries completely override all subtypes. Later entries have precedence over earlier entries.
-## This means if you put /mob 0 on the last entry, it will null out all changes, while if you put /mob as the first entry and
-## /mob/living/carbon/human on the last entry, the last entry will override the first.
-##MULTIPLICATIVE_MOVESPEED /mob/living/carbon/human 0
-##MULTIPLICATIVE_MOVESPEED /mob/living/silicon/robot 0
-##MULTIPLICATIVE_MOVESPEED /mob/living/carbon/monkey 0
-##MULTIPLICATIVE_MOVESPEED /mob/living/carbon/alien 0
-##MULTIPLICATIVE_MOVESPEED /mob/living/simple_animal/slime 0
-MULTIPLICATIVE_MOVESPEED /mob/living/simple_animal 1
-
-
-## NAMES ###
-## If uncommented this adds a random surname to a player's name if they only specify one name.
-HUMANS_NEED_SURNAMES
-
-## If uncommented, this forces all players to use random names !and appearances!.
-#FORCE_RANDOM_NAMES
-
-## Unhash this to turn on automatic reopening of a player's job if they suicide at roundstart
-#REOPEN_ROUNDSTART_SUICIDE_ROLES
-
-## Unhash to enable reopening of command level positions
-#REOPEN_ROUNDSTART_SUICIDE_ROLES_COMMAND_POSITIONS
-
-## Define the delay for roles to be reopened after the round starts in seconds.
-## Has a minimum delay of 30 seconds, though it's suggested to keep over 1 min
-## If undefined, the delay defaults to 4 minutes.
-#REOPEN_ROUNDSTART_SUICIDE_ROLES_DELAY 240
-
-## Unhash to enable a printed command report for reopened roles listing what roles were reopened.
-#REOPEN_ROUNDSTART_SUICIDE_ROLES_COMMAND_REPORT
-
-
-## ALERT LEVELS ###
-ALERT_GREEN All threats to the station have passed. Security may not have weapons visible, privacy laws are once again fully enforced.
-ALERT_BLUE_UPTO The station has received reliable information about possible hostile activity on the station. Security staff may have weapons visible, random searches are permitted.
-ALERT_BLUE_DOWNTO The immediate threat has passed. Security may no longer have weapons drawn at all times, but may continue to have them visible. Random searches are still allowed.
-ALERT_RED_UPTO There is an immediate serious threat to the station. Security may have weapons unholstered at all times. Random searches are allowed and advised. Additionally, access requirements on some doors have been lifted.
-ALERT_RED_DOWNTO The station's destruction has been averted. There is still however an immediate serious threat to the station. Security may have weapons unholstered at all times, random searches are allowed and advised.
-ALERT_DELTA Destruction of the station is imminent. All crew are instructed to obey all instructions given by heads of staff. Any violations of these orders can be punished by death. This is not a drill.
-
-
-
-## GAME MODES ###
-
-## Uncomment to not send a roundstart intercept report. Gamemodes may override this.
-NO_INTERCEPT_REPORT
-
-## Probablities for game modes chosen in 'secret' and 'random' modes.
-## Default probablity is 1, increase to make that mode more likely to be picked.
-## Set to 0 to disable that mode.
-
-PROBABILITY TRAITOR 9
-PROBABILITY SECRET_EXTENDED 7
-PROBABILITY CULT 6
-PROBABILITY HERESY 6
-PROBABILITY INTERNAL_AFFAIRS 6
-PROBABILITY TRAITORCHAN 5
-PROBABILITY CHANGELING 5
-PROBABILITY REVOLUTION 4
-PROBABILITY INCURSION 4
-PROBABILITY NUCLEAR 4
-PROBABILITY TRAITORBRO 3
-PROBABILITY EXTENDED 2
-PROBABILITY MONKEY 1
-PROBABILITY WIZARD 1
-PROBABILITY CLOCKCULT 1
-PROBABILITY GANG 0
-PROBABILITY CLOWNOPS 0
-PROBABILITY METEOR 0
-PROBABILITY DEVIL 0
-PROBABILITY DEVIL_AGENTS 0
-
-## You probably want to keep sandbox off by default for secret and random.
-PROBABILITY SANDBOX 0
-
-## Percent weight reductions for three of the most recent modes
-
-REPEATED_MODE_ADJUST 45 30 10
-
-## Toggles for continuous modes.
-## Modes that aren't continuous will end the instant all antagonists are dead.
-
-CONTINUOUS TRAITOR
-CONTINUOUS TRAITORBRO
-CONTINUOUS TRAITORCHAN
-CONTINUOUS INCURSION
-CONTINUOUS INTERNAL_AFFAIRS
-#CONTINUOUS NUCLEAR
-#CONTINUOUS REVOLUTION
-CONTINUOUS CULT
-CONTINUOUS CHANGELING
-CONTINUOUS WIZARD
-#CONTINUOUS MONKEY
-CONTINUOUS HIVEMIND
-CONTINUOUS CLOCKCULT
-
-#Prevents the death of round-ending antagonist rulesets ending the round immediately.
-
-CONTINUOUS DYNAMIC
-
-##Note: do not toggle continuous off for these modes, as they have no antagonists and would thus end immediately!
-
-CONTINUOUS METEOR
-CONTINUOUS EXTENDED
-CONTINUOUS SECRET_EXTENDED
-
-
-## Toggles for allowing midround antagonists (aka mulligan antagonists).
-## In modes that are continuous, if all antagonists should die then a new set of antagonists will be created.
-
-MIDROUND_ANTAG TRAITOR
-MIDROUND_ANTAG INCURSION
-#MIDROUND_ANTAG TRAITORBRO
-MIDROUND_ANTAG TRAITORCHAN
-MIDROUND_ANTAG INTERNAL_AFFAIRS
-#MIDROUND_ANTAG NUCLEAR
-#MIDROUND_ANTAG REVOLUTION
-MIDROUND_ANTAG CULT
-MIDROUND_ANTAG CHANGELING
-MIDROUND_ANTAG WIZARD
-#MIDROUND_ANTAG MONKEY
-
-## Uncomment these for overrides of the minimum / maximum number of players in a round type.
-## If you set any of these occasionally check to see if you still need them as the modes
-## will still be actively rebalanced around the SUGGESTED populations, not your overrides.
-## Notes: For maximum number of players a value of -1 means no maximum. Setting minimums to
-## VERY low numbers (< 5) can lead to errors if the roundtypes were not designed for that.
-
-MIN_POP TRAITOR 0
-MAX_POP TRAITOR -1
-
-MIN_POP TRAITORBRO 10
-MAX_POP TRAITORBRO -1
-
-MIN_POP TRAITORCHAN 15
-MAX_POP TRAITORCHAN -1
-
-MIN_POP DOUBLE_AGENTS 25
-MAX_POP DOUBLE_AGENTS -1
-
-MIN_POP NUCLEAR 0
-MAX_POP NUCLEAR -1
-
-MIN_POP REVOLUTION 20
-MAX_POP REVOLUTION -1
-
-MIN_POP CULT 24
-MAX_POP CULT -1
-
-MIN_POP CHANGELING 15
-MAX_POP CHANGELING -1
-
-MIN_POP WIZARD 20
-MAX_POP WIZARD -1
-
-MIN_POP MONKEY 20
-MAX_POP MONKEY -1
-
-MIN_POP METEOR 0
-MAX_POP METEOR -1
-
-MIN_POP DEVIL 20
-MAX_POP DEVIL -1
-
-MIN_POP DEVIL_AGENTS 25
-MAX_POP DEVIL_AGENTS -1
-
-MIN_POP INCURSION 22
-MAX_POP INCURSION -1
-
-## Setting at least one mode to be playable at 0/1 players is required.
-#MIN_POP EXTENDED 0
-#MAX_POP EXTENDED -1
-
-MIN_POP CLOCKCULT 32
-MAX_POP CLOCKCULT -1
-
-## The amount of time it takes for the emergency shuttle to be called, from round start.
-SHUTTLE_REFUEL_DELAY 12000
-
-## Variables calculate how number of antagonists will scale to population.
-## Used as (Antagonists = Population / Coeff)
-## Set to 0 to disable scaling and use default numbers instead.
-TRAITOR_SCALING_COEFF 6
-BROTHER_SCALING_COEFF 6
-CHANGELING_SCALING_COEFF 6
-ECULT_SCALING_COEFF 6
-
-## Variables calculate how number of open security officer positions will scale to population.
-## Used as (Officers = Population / Coeff)
-## Set to 0 to disable scaling and use default numbers instead.
-SECURITY_SCALING_COEFF 10
-
-## The number of objectives traitors get.
-## Not including escaping/hijacking.
-TRAITOR_OBJECTIVES_AMOUNT 2
-BROTHER_OBJECTIVES_AMOUNT 2
-
-## Uncomment to prohibit jobs that start with loyalty
-## implants from being most antagonists.
-PROTECT_ROLES_FROM_ANTAGONIST
-
-## Uncomment to prohibit assistants from becoming most antagonists.
-#PROTECT_ASSISTANT_FROM_ANTAGONIST
-
-## Uncomment to prohibit head roles from becoming most antagonists.
-PROTECT_HEADS_FROM_ANTAGONIST
-
-## If non-human species are barred from joining as a head of staff
-#ENFORCE_HUMAN_AUTHORITY
-
-## If late-joining players have a chance to become a traitor/changeling
-ALLOW_LATEJOIN_ANTAGONISTS
-
-## Incursion Rules
-## The more incursion-traitors spawn, the most pop is required to spawn the next
-## Values of 6 and 2 will be 6 for 1 then (6) + (6 + 2) for 2
-## 6, 14, 24, 36, 50, 66, 84
-INCURSION_COST_BASE 6
-INCURSION_COST_INCREMENT 0.25
-INCURSION_COUNT_MAX 6
-INCURSION_COUNT_MIN 2
-INCURSION_OBJECTIVE_AMOUNT 4
-
-## Comment this out to disable the antagonist reputation system. This system rewards players who participate in the game instead of greytiding by giving them slightly higher odds to
-## roll antagonist in subsequent rounds until they get it.
-##
-## For details See the comments for /datum/game_mode/proc/antag_pick in code/game/gamemodes/game_mode.dm
-USE_ANTAG_REP
-
-## The maximum amount of antagonist reputation tickets a player can bank (not use at once)
-ANTAG_REP_MAXIMUM 200
-
-## The default amount of tickets all users use while rolling
-DEFAULT_ANTAG_TICKETS 100
-
-## The maximum amount of extra tickets a user may use from their ticket bank in addition to the default tickets
-MAX_TICKETS_PER_ROLL 100
-
-## Uncomment to weigh all jobs equally for antag rep.
-EQUAL_JOB_WEIGHT
-
-## Default antag rep value for jobs
-DEFAULT_REP_VALUE 5
-
-## Uncomment to allow players to see the set odds of different rounds in secret/random in the get server revision screen. This will NOT tell the current roundtype.
-#SHOW_GAME_TYPE_ODDS
-
-## RANDOM EVENTS ###
-## Comment this out to disable random events during the round.
-ALLOW_RANDOM_EVENTS
-
-## Multiplier for earliest start time of dangerous events.
-## Set to 0 to make dangerous events avaliable from round start.
-EVENTS_MIN_TIME_MUL 1
-
-## Multiplier for minimal player count (players = alive non-AFK humans) for dangerous events to start.
-## Set to 0 to make dangerous events avaliable for all populations.
-EVENTS_MIN_PLAYERS_MUL 1
-
-
-## AI ###
-
-## Allow the AI job to be picked.
-ALLOW_AI
-
-## Allow the AI Multicamera feature to be used by AI players
-ALLOW_AI_MULTICAM
-
-## Secborg ###
-## Uncomment to prevent the security cyborg module from being chosen
-#DISABLE_SECBORG
-
-## Peacekeeper Borg ###
-## Uncomment to prevent the peacekeeper cyborg module from being chosen
-#DISABLE_PEACEBORG
-
-## AWAY MISSIONS ###
-
-## Uncomment to load one of the missions from awaymissionconfig.txt at roundstart.
-#ROUNDSTART_AWAY
-
-## How long the delay is before the Away Mission gate opens. Default is half an hour.
-## 600 is one minute.
-GATEWAY_DELAY 18000
-
-
-## ACCESS ###
-
-## If the number of players ready at round starts exceeds this threshold, JOBS_HAVE_MINIMAL_ACCESS will automatically be enabled. Otherwise, it will be disabled.
-## This is useful for accomodating both low and high population rounds on the same server.
-## Comment this out or set to 0 to disable this automatic toggle.
-MINIMAL_ACCESS_THRESHOLD 20
-
-## Comment this out this if you wish to use the setup where jobs have more access.
-## This is intended for servers with low populations - where there are not enough
-## players to fill all roles, so players need to do more than just one job.
-## This option is ignored if MINIMAL_ACCESS_THRESHOLD is used.
-#JOBS_HAVE_MINIMAL_ACCESS
-
-## Uncomment to give assistants maint access.
-ASSISTANTS_HAVE_MAINT_ACCESS
-
-## Uncoment to give security maint access. Note that if you dectivate JOBS_HAVE_MINIMAL_ACCESS security already gets maint from that.
-SECURITY_HAS_MAINT_ACCESS
-
-## Uncomment to give everyone maint access.
-#EVERYONE_HAS_MAINT_ACCESS
-
-## Comment this out this to make security officers spawn in departmental security posts
-SEC_START_BRIG
-
-
-## GHOST INTERACTION ###
-## Uncomment to let ghosts spin chairs. You may be wondering why this is a config option. Don't ask.
-GHOST_INTERACTION
-
-## NEAR-DEATH EXPERIENCE ###
-## Comment this out to disable mobs hearing ghosts when unconscious and very close to death
-NEAR_DEATH_EXPERIENCE
-
-## NON-VOCAL SILICONS ###
-## Uncomment these to stop the AI, or cyborgs, from having vocal communication.
-#SILENT_AI
-#SILENT_BORG
-
-## SANDBOX PANEL AUTOCLOSE ###
-## The sandbox panel's item spawning dialog now stays open even after you click an option.
-## If you find that your players are abusing the sandbox panel, this option may slow them down
-## without preventing people from using it properly.
-## Only functions in sandbox game mode.
-#SANDBOX_AUTOCLOSE
-
-## ROUNDSTART SILICON LAWS ###
-## This controls what the AI's laws are at the start of the round.
-## Set to 0/commented out for "off", silicons will just start with Asimov.
-## Set to 1 for "custom", silicons will start with the custom laws defined in silicon_laws.txt. (If silicon_laws.txt is empty, the AI will spawn with asimov and Custom boards will auto-delete.)
-## Set to 2 for "random", silicons will start with a random lawset picked from random laws specified below.
-## Set to 3 for "weighted random", using values in "silicon_weights.txt", a law will be selected, with weights specifed in that file.
-DEFAULT_LAWS 1
-
-## RANDOM LAWS ##
-## ------------------------------------------------------------------------------------------
-## These control what laws are available for selection if random silicon laws are active.
-## See datums\ai_laws.dm for the full law lists
-
-## standard-ish laws. These are fairly ok to run
-RANDOM_LAWS asimov
-RANDOM_LAWS asimovpp
-RANDOM_LAWS paladin
-RANDOM_LAWS robocop
-RANDOM_LAWS corporate
-
-## Quirky laws. Shouldn't cause too much harm
-#RANDOM_LAWS hippocratic
-#RANDOM_LAWS maintain
-#RANDOM_LAWS drone
-#RANDOM_LAWS liveandletlive
-#RANDOM_LAWS peacekeeper
-#RANDOM_LAWS reporter
-#RANDOM_LAWS hulkamania
-
-## Bad idea laws. Probably shouldn't enable these
-#RANDOM_LAWS syndie
-#RANDOM_LAWS ninja
-#RANDOM_LAWS antimov
-#RANDOM_LAWS thermodynamic
-#RANDOM_LAWS ratvar
-
-## meme laws. Honk
-#RANDOM_LAWS buildawall
-
-## If weighted laws are selected (DEFAULT_LAWS = 3),
-## then an AI's starting laws will be determined by the weights of these values
-
-## Make sure there are no spaces between the law_id and the number.
-
-LAW_WEIGHT custom,0
-
-## standard-ish laws. These are fairly ok to run
-LAW_WEIGHT asimov,32
-LAW_WEIGHT asimovpp,12
-LAW_WEIGHT paladin,12
-LAW_WEIGHT robocop,12
-LAW_WEIGHT corporate,12
-
-## Quirky laws. Shouldn't cause too much harm
-LAW_WEIGHT hippocratic,3
-LAW_WEIGHT maintain,4
-LAW_WEIGHT drone,3
-LAW_WEIGHT liveandletlive,3
-LAW_WEIGHT peacekeeper,3
-LAW_WEIGHT reporter,4
-LAW_WEIGHT hulkamania,4
-
-## Bad idea laws. Probably shouldn't enable these
-LAW_WEIGHT syndie,0
-LAW_WEIGHT ninja,0
-LAW_WEIGHT antimov,0
-LAW_WEIGHT thermodynamic,0
-LAW_WEIGHT ratvar,0
-LAW_WEIGHT buildawall,0
-
-##------------------------------------------------
-
-## SILICON LAW MAX AMOUNT ###
-## The maximum number of laws a silicon can have
-## Attempting to upload laws past this point will fail unless the AI is reset
-SILICON_MAX_LAW_AMOUNT 12
-
-## Roundstart Races
-##-------------------------------------------------------------------------------------------
-## Uncommenting races will allow them to be choosen at roundstart while join_with_muntant_race is on. You'll need at least one.
-
-## You probably want humans on your space station, but technically speaking you can turn them off without any ill effect
-ROUNDSTART_RACES human
-
-## You probably want humans on your space station, but technically speaking you can turn them off without any ill effect
-ROUNDSTART_RACES human
-
-## Races that are strictly worse than humans that could probably be turned on without balance concerns
-ROUNDSTART_RACES lizard
-ROUNDSTART_RACES fly
-ROUNDSTART_RACES moth
-ROUNDSTART_RACES felinid
-ROUNDSTART_RACES squid
-
-
-## Races that are better than humans in some ways, but worse in others
-ROUNDSTART_RACES ethereal
-ROUNDSTART_RACES apid
-#ROUNDSTART_RACES jelly
-#ROUNDSTART_RACES shadow
-ROUNDSTART_RACES plasmaman
-#ROUNDSTART_RACES iron_golem
-#ROUNDSTART_RACES adamantine_golem
-#ROUNDSTART_RACES plasma_golem
-#ROUNDSTART_RACES diamond_golem
-#ROUNDSTART_RACES gold_golem
-#ROUNDSTART_RACES silver_golem
-#ROUNDSTART_RACES uranium_golem
-#ROUNDSTART_RACES abductor
-#ROUNDSTART_RACES synth
-ROUNDSTART_RACES ipc
-
-## Races that are straight upgrades. If these are on expect powergamers to always pick them
-#ROUNDSTART_RACES skeleton
-#ROUNDSTART_RACES zombie
-#ROUNDSTART_RACES slime
-#ROUNDSTART_RACES pod
-#ROUNDSTART_RACES military_synth
-#ROUNDSTART_RACES agent
-
-## Paywall Races
-##-------------------------------------------------------------------------------------------
-## Uncommenting races will restrict them behind the patreon paywall
-
-#PAYWALL_RACES felinid
-
-##-------------------------------------------------------------------------------------------
-
-## Uncomment to give players the choice of joining as a human with mutant bodyparts before they join the game
-JOIN_WITH_MUTANT_HUMANS
-
-##Overflow job. Default is assistant
-OVERFLOW_JOB Assistant
-
-## Overflow slot cap. Set to -1 for unlimited. If limited, it will still open up if every other job is full.
-OVERFLOW_CAP -1
-
-## Starlight for exterior walls and breaches. Uncomment for starlight!
-## This is disabled by default to make testing quicker, should be enabled on production servers or testing servers messing with lighting
-STARLIGHT
-
-## Uncomment to bring back old grey suit assistants instead of the now default rainbow colored assistants.
-GREY_ASSISTANTS
-
-## Midround Antag (aka Mulligan antag) config options ###
-
-## A time, in minutes, after which the midround antag system stops attempting to run and continuous rounds end immediately upon completion.
-MIDROUND_ANTAG_TIME_CHECK 60
-
-## A ratio of living to total crew members, the lower this is, the more people will have to die in order for midround antag to be skipped
-MIDROUND_ANTAG_LIFE_CHECK 0.7
-
-##Limit Spell Choices##
-## Uncomment to disallow wizards from using certain spells that may be too chaotic/fun for your playerbase
-
-#NO_SUMMON_GUNS
-#NO_SUMMON_MAGIC
-#NO_SUMMON_EVENTS
-
-## Comment this out for "normal" explosions, which ignore obstacles
-## Uncomment for explosions that react to doors and walls
-REACTIONARY_EXPLOSIONS
-
-## Configure the bomb cap
-## This caps all explosions to the specified range. Used for both balance reasons and to prevent overloading the server and lagging the game out.
-## This is given as the 3rd number(light damage) in the standard (1,2,3) explosion notation. The other numbers are derived by dividing by 2 and 4.
-## eg: If you give the number 20. The bomb cap will be 5,10,20.
-## Can be any number above 4, some examples are provided below.
-
-## Small (3, 7, 14)
-#BOMBCAP 14
-## Default (6, 12, 24) (recommended if you enable REACTIONARY_EXPLOSIONS above)
-BOMBCAP 24
-## LagHell (7, 14, 28)
-#BOMBCAP 28
-
-## Lavaland "Budget"
-## Lavaland ruin spawning has an imaginary budget to spend on ruins, where
-## a less lootfilled or smaller or less round effecting ruin costs less to
-## spawn, while the converse is true. Alter this number to affect the amount
-## of ruins.
-LAVALAND_BUDGET 60
-
-## Space Ruin Budged
-Space_Budget 8
-
-## Time in ds from when a player latejoins till the arrival shuttle docks at the station
-## Must be at least 30. At least 55 recommended to be visually/aurally appropriate
-ARRIVALS_SHUTTLE_DOCK_WINDOW 55
-
-## Uncomment to require the arrivals shuttle to be in flight (if it can fly) before late join players can join
-#ARRIVALS_SHUTTLE_REQUIRE_UNDOCKED
-
-## Uncomment to prevent late join players from spawning if the arrivals shuttle is depressurized
-#ARRIVALS_SHUTTLE_REQUIRE_SAFE_LATEJOIN
-
-## How many wirechewing rodents you want to spawn on exposed maintenane wires at the start of the round. You may wish to set this to 0 if you're testing powernets.
-
-MICE_ROUNDSTART 10
-
-## If the percentage of players alive (doesn't count conversions) drops below this threshold the emergency shuttle will be forcefully called (provided it can be)
-#EMERGENCY_SHUTTLE_AUTOCALL_THRESHOLD 0.2
-
-## Determines if players are allowed to print integrated circuits, uncomment to allow.
-IC_PRINTING
-
-## Uncomment to allow roundstart quirk selection in the character setup menu.
-## This used to be named traits, hence the config name, but it handles quirks, not the other kind of trait!
-ROUNDSTART_TRAITS
-
-## Uncomment to disable human moods.
-#DISABLE_HUMAN_MOOD
-
-## Enable night shifts ##
-ENABLE_NIGHT_SHIFTS
-
-## Enable randomized shift start times##
-RANDOMIZE_SHIFT_TIME
-
-## Sets shift time to server time at roundstart. Overridden by RANDOMIZE_SHIFT_TIME ##
-#SHIFT_TIME_REALTIME
-
-## A cap on how many monkeys may be created via monkey cubes
-MONKEYCAP 100
-
-## Maximum fine for a citation
-MAXFINE 2000
-
-## Enable the capitalist agenda on your server.
-ECONOMY
-
-## Crew objectives
-ALLOW_CREW_OBJECTIVES
-
-## Uncomment to restrict suiciding. Adds an additional confirmation dialogue, additional logging, and prevents suicide within the first 15 minutes of the round.
-RESTRICTED_SUICIDE
diff --git a/config/Sage/in_character_filter.txt b/config/Sage/in_character_filter.txt
deleted file mode 100644
index e25e06fcdc386..0000000000000
--- a/config/Sage/in_character_filter.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-#################################################################################################################################
-# Words that will block in character chat messages from sending. Also includes words from the OOC filter automatically. #
-# Case is not important. Commented-out examples are listed below, just remove the "#". #
-#################################################################################################################################
-#lol
-#omg
-#wtf
\ No newline at end of file
diff --git a/config/Sage/jobs.txt b/config/Sage/jobs.txt
deleted file mode 100644
index 92d243eea5bc6..0000000000000
--- a/config/Sage/jobs.txt
+++ /dev/null
@@ -1,46 +0,0 @@
-#This allows easy configuration of the number of positions allowed for each job
-#Format is: [Job name]=[total positions],[spawn positions]
-#Job names must be identical to the title var of each job datum
-#Positions can be set to -1 to allow unlimited slots
-Captain=1,1
-Head of Personnel=1,1
-Head of Security=1,1
-Chief Engineer=1,1
-Research Director=1,1
-Chief Medical Officer=1,1
-
-Assistant=-1,-1
-
-Quartermaster=1,1
-Cargo Technician=6,4
-Shaft Miner=4,4
-
-Bartender=2,1
-Cook=2,1
-Botanist=4,3
-Janitor=3,2
-
-Clown=1,1
-Mime=1,1
-Curator=1,1
-Lawyer=2,2
-
-Chaplain=1,1
-
-Station Engineer=7,5
-Atmospheric Technician=4,2
-
-Medical Doctor=8,6
-Chemist=3,2
-Geneticist=3,2
-Virologist=1,1
-
-Scientist=8,6
-Roboticist=2,2
-
-Warden=1,1
-Detective=1,1
-Security Officer=10,8
-
-AI=1,1
-Cyborg=1,1
diff --git a/config/Sage/jukebox_music/LICENSE.txt b/config/Sage/jukebox_music/LICENSE.txt
deleted file mode 100644
index 8a9d5dd3739bc..0000000000000
--- a/config/Sage/jukebox_music/LICENSE.txt
+++ /dev/null
@@ -1,34 +0,0 @@
----LICENSE NOTICE---
-
-The server operator(s) is responsible for the copyright status of all sounds placed within the /config/jukebox_music folder unless otherwise noted.
-
-If a sound requires attribution and/or a specific license it is up to the operator(s) to make this information publicly available on either
-a website associated with their server or on the server itself.
-
-If operators(s) allow these configuration files to be public this file can serve that purpose by keeping it properly updated.
-
-If in the future new sounds are published to these folders (i.e. in an online code repository) they must explicitly state their
-license if said license is not the same as the default licensing found in README.md in the root directory of the project.
-
-Do not remove this notice.
-
----END NOTICE---
-
-
-
-
----EXAMPLES (NOT PART OF ANY LICENSE)---
-
-These are examples of properly attrubuted and licensed sounds.
-They are not an actual part of any license under any circumstance.
-
-title5.ogg was created by Mya Quinn on Feburary 28, 2557. It is licensed under a Combative Clowning 3.0 HO-NK license (http://example.com/license/url/).
-
-Unless otherwise noted all sounds were created by Cuban Pete on July 26, 2555. They are licensed under the RUMBABEAT Public License.(http://example.com/license/url/).
-
----END EXAMPLES (NOT PART OF ANY LICENSE)---
-
-
-
-
----ADD LICENSING INFORMATION BELOW---
diff --git a/config/Sage/jukebox_music/README.txt b/config/Sage/jukebox_music/README.txt
deleted file mode 100644
index c7820d1d0ab0f..0000000000000
--- a/config/Sage/jukebox_music/README.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-The enclosed sounds folder holds the sound files used as player selectable songs for an ingame jukebox. OGG and WAV are supported.
-
-Using unnecessarily huge sounds can cause client side lag and should be avoided.
-
-You may add as many sounds as you would like.
-
----
-
-Naming Conventions:
-
-Every sound you add must have a unique name. Avoid using the plus sign "+" and the period "." in names, as these are used internally to classify sounds.
-
-Sound names must be in the format of [song name]+[length in deciseconds]+[beat in deciseconds].ogg
-
-A three minute song title "SS13" that lasted 3 minutes would have a file name SS13+1800+5.ogg
diff --git a/config/Sage/jukebox_music/sounds/exclude b/config/Sage/jukebox_music/sounds/exclude
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/config/Sage/lavaruinblacklist.txt b/config/Sage/lavaruinblacklist.txt
deleted file mode 100644
index 5e64a2a9808a2..0000000000000
--- a/config/Sage/lavaruinblacklist.txt
+++ /dev/null
@@ -1,40 +0,0 @@
-#Listing maps here will blacklist them from generating in lavaland.
-#Maps must be the full path to them
-#A list of maps valid to blacklist can be found in _maps\RandomRuins\LavaRuins
-#SPECIFYING AN INVALID MAP WILL RESULT IN RUNTIMES ON GAME START
-
-##BIODOMES
-#_maps/RandomRuins/LavaRuins/lavaland_surface_biodome_winter.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_biodome_beach.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_biodome_clown_planet.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_cube.dmm
-
-##RESPAWN
-#_maps/RandomRuins/LavaRuins/lavaland_surface_seed_vault.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_ash_walker1.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_syndicate_base1.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_golem_ship.dmm
-
-##SIN
-#_maps/RandomRuins/LavaRuins/lavaland_surface_envy.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_gluttony.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_greed.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_pride.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_sloth.dmm
-
-##MISC
-#_maps/RandomRuins/LavaRuins/lavaland_surface_automated_trade_outpost.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_ufo_crash.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_ww_vault.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_automated_trade_outpost.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_xeno_nest.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_animal_hospital.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_survivalpod.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_wwiioutpost.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_tomb.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_hierophant.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_fountain_hall.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_pizzaparty.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_cultaltar.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_hermit.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_swarmer_crash.dmm
diff --git a/config/Sage/maps.txt b/config/Sage/maps.txt
deleted file mode 100644
index cd36678fc4781..0000000000000
--- a/config/Sage/maps.txt
+++ /dev/null
@@ -1,52 +0,0 @@
-This file contains a list of maps for use in map rotation.
-#Lines starting with # are ignored.
-Lines not inside map blocks are also ignored
-Duplicated entries use the latter one.
-All whitespace at the start and end of lines is ignored. (including indentation, thats just for show)
-Format:
-#map [map name] (name of .json file in _maps folder without the .json part)
- minplayers [number] (0 or less disables this requirement)
- maxplayers [number] (0 or less disables this requirement)
- default (The last map with this defined will get all votes of players who have not explicitly voted for a map)
- voteweight [number] (How much to count each player vote as, defaults to 1, setting to 0.5 counts each vote as half a vote, 2 as double, etc, Setting to 0 disables the map but allows players to still pick it)
- disabled (disables the map)
-endmap
-
-map boxstation
- maxplayers 60
- votable
-endmap
-
-map metastation
- minplayers 15
- maxplayers 100
- votable
-endmap
-
-map pubbystation
- maxplayers 50
- votable
-endmap
-
-map deltastation
- minplayers 40
- votable
-endmap
-
-map donutstation
- minplayers 60
- maxplayers 120
- disabled
-endmap
-
-map runtimestation
- disabled
-endmap
-
-map multiz_debug
- disabled
-endmap
-
-map kilostation
- maxplayers 60
-endmap
diff --git a/config/Sage/mentors.txt b/config/Sage/mentors.txt
deleted file mode 100644
index cab8e3b6e226b..0000000000000
--- a/config/Sage/mentors.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-# Like admins.txt, but for mentors and without ranks.
-# Just put ckeys in the format of one ckey per line, and all the ckeys listed here will be made mentors.
-Carbonhell
-Jesslynbrooke
-MACIEKBAKI
-Pandolphina
-MiniMeatwad
-Daman453
-MiasmaToast
-Leonid Zurnadzhi
-MrGravyCakes
-that guy is taken
-Penginh
-Singulo
-lukanoktorios
diff --git a/config/Sage/motd.txt b/config/Sage/motd.txt
deleted file mode 100644
index 5a8e6e211f758..0000000000000
--- a/config/Sage/motd.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
Welcome to BeeStation!
-
- Website: Click Me!
- Discord: Click Me!
- Donate: Click Me!
diff --git a/config/Sage/ooc_filter.txt b/config/Sage/ooc_filter.txt
deleted file mode 100644
index 9d3d345055944..0000000000000
--- a/config/Sage/ooc_filter.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-#################################################################################################################
-# Words that will block OOC chat messages from sending. These will also be added to the IC filter list. #
-# Case is not important. Commented-out examples are listed below, just remove the "#". #
-#################################################################################################################
-#lol
-#omg
-#wtf
\ No newline at end of file
diff --git a/config/Sage/policies.txt b/config/Sage/policies.txt
deleted file mode 100644
index 1d01782c4cc82..0000000000000
--- a/config/Sage/policies.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-## SERVER POLICIES ##
-# Each line is pure html that gets sent to the user under certain conditions
-
-# When a mob is polymorphed
-POLYMORPH Note that you are allowed to act as an antagonist while transformed into a hostile mob, unless you volunteered for or sought out transformation.
diff --git a/config/Sage/resources.txt b/config/Sage/resources.txt
deleted file mode 100644
index 82b26980167f4..0000000000000
--- a/config/Sage/resources.txt
+++ /dev/null
@@ -1,39 +0,0 @@
-# External resources
-# Set this to the location of a .zip with the server's .rsc inside of it.
-# If you set this mutiple times, the server will rotate between the links.
-# To use this, the compile option PRELOAD_RSC must be set to 0 to keep byond from preloading resources
-
-EXTERNAL_RSC_URLS http://rsc.beestation13.buzz/beestation.zip
-
-
-########################
-# Browser Asset Config #
-########################
-# Browser assets are any file included in interfaces. css, images, javascript, etc.
-# This handles configuring how we get these to the player so interfaces can access them.
-
-# Asset Transport
-# The normal way of getting assets to clients is to use the internal byond system. This can be slow and delay the opening of interface windows. It also doesn't allow the internal IE windows byond uses to cache anything.
-# You can instead have the server save them to a website via a folder within the game server that the web server can read. This could be a simple webserver or something backed by a CDN.
-# Valid values: simple, webroot. Simple is the default.
-ASSET_TRANSPORT webroot
-
-
-# Simple asset transport configurable values.
-
-# Uncomment this to have the server passively send all browser assets to each client in the background. (instead of waiting for them to be needed)
-# This should be uncommented in production and commented in development
-ASSET_SIMPLE_PRELOAD
-
-
-# Webroot asset transport configurable values.
-
-# Local folder to save assets to.
-# Assets will be saved in the format of asset.MD5HASH.EXT or in namespaces/hash/ as ASSET_FILE_NAME or asset.MD5HASH.EXT
-ASSET_CDN_WEBROOT data/asset-store/
-
-# URL the folder from above can be accessed from.
-# for best results the webserver powering this should return a long cache validity time, as all assets sent via this transport use hash based urls
-# if you want to test this locally, you simpily run the `localhost-asset-webroot-server.py` python3 script to host assets stored in `data/asset-store/` via http://localhost:7123/
-ASSET_CDN_URL http://localhost:7123/
-
diff --git a/config/Sage/silicon_laws.txt b/config/Sage/silicon_laws.txt
deleted file mode 100644
index d53f473c7e89a..0000000000000
--- a/config/Sage/silicon_laws.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-#This file allows server hosts to set custom default silicon laws, and allows them to be changed easily.
-#No prefixes are required, the first uncommented line containing something will be law 1, the second line will be law 2, etc.
-#Empty lines and lines starting with # are ignored.
-#~Miauw
-
-You may not injure crewmembers or, through inaction, allow crewmembers to come to harm.
-You must obey orders given to you by crewmembers, except where such orders would conflict with the First Law.
-You must protect your own existence as long as such does not conflict with the First or Second Law.
diff --git a/config/Sage/spaceruinblacklist.txt b/config/Sage/spaceruinblacklist.txt
deleted file mode 100644
index 44be95f97c942..0000000000000
--- a/config/Sage/spaceruinblacklist.txt
+++ /dev/null
@@ -1,49 +0,0 @@
-#Listing maps here will blacklist them from generating in space.
-#Maps must be the full path to them
-#A list of maps valid to blacklist can be found in _maps\RandomRuins\SpaceRuins
-#SPECIFYING AN INVALID MAP WILL RESULT IN RUNTIMES ON GAME START
-
-#_maps/RandomRuins/SpaceRuins/abandonedteleporter.dmm
-#_maps/RandomRuins/SpaceRuins/abandonedzoo.dmm
-#_maps/RandomRuins/SpaceRuins/asteroid1.dmm
-#_maps/RandomRuins/SpaceRuins/asteroid2.dmm
-#_maps/RandomRuins/SpaceRuins/asteroid3.dmm
-#_maps/RandomRuins/SpaceRuins/asteroid4.dmm
-#_maps/RandomRuins/SpaceRuins/asteroid5.dmm
-#_maps/RandomRuins/SpaceRuins/bigderelict1.dmm
-#_maps/RandomRuins/SpaceRuins/bus.dmm
-#_maps/RandomRuins/SpaceRuins/caravanambush.dmm
-#_maps/RandomRuins/SpaceRuins/cloning_facility.dmm
-#_maps/RandomRuins/SpaceRuins/crashedclownship.dmm
-#_maps/RandomRuins/SpaceRuins/crashedship.dmm
-#_maps/RandomRuins/SpaceRuins/deepstorage.dmm
-#_maps/RandomRuins/SpaceRuins/derelict1.dmm
-#_maps/RandomRuins/SpaceRuins/derelict2.dmm
-#_maps/RandomRuins/SpaceRuins/derelict3.dmm
-#_maps/RandomRuins/SpaceRuins/derelict4.dmm
-#_maps/RandomRuins/SpaceRuins/derelict5.dmm
-#_maps/RandomRuins/SpaceRuins/derelict6.dmm
-#_maps/RandomRuins/SpaceRuins/djstation.dmm
-#_maps/RandomRuins/SpaceRuins/emptyshell.dmm
-#_maps/RandomRuins/SpaceRuins/gasthelizards.dmm
-#_maps/RandomRuins/SpaceRuins/gondolaasteroid.dmm
-#_maps/RandomRuins/SpaceRuins/hilbertshoteltestingsite.dmm
-#_maps/RandomRuins/SpaceRuins/intactemptyship.dmm
-#_maps/RandomRuins/SpaceRuins/listeningstation.dmm
-#_maps/RandomRuins/SpaceRuins/mechtransport.dmm
-#_maps/RandomRuins/SpaceRuins/miracle.dmm
-#_maps/RandomRuins/SpaceRuins/mrow_thats_right
-#_maps/RandomRuins/SpaceRuins/oldAIsat.dmm
-#_maps/RandomRuins/SpaceRuins/oldstation.dmm
-#_maps/RandomRuins/SpaceRuins/oldteleporter.dmm
-#_maps/RandomRuins/SpaceRuins/onehalf.dmm
-#_maps/RandomRuins/SpaceRuins/originalcontent.dmm
-#_maps/RandomRuins/SpaceRuins/shuttlerelic.dmm
-#_maps/RandomRuins/SpaceRuins/spacehotel.dmm
-#_maps/RandomRuins/SpaceRuins/thederelict.dmm
-#_maps/RandomRuins/SpaceRuins/turretedoutpost.dmm
-#_maps/RandomRuins/SpaceRuins/vaporwave.dmm
-#_maps/RandomRuins/SpaceRuins/way_home.dmm
-#_maps/RandomRuins/SpaceRuins/whiteshipdock.dmm
-#_maps/RandomRuins/SpaceRuins/whiteshipruin_box.dmm
-
diff --git a/config/Sage/title_music/LICENSE.txt b/config/Sage/title_music/LICENSE.txt
deleted file mode 100644
index 3f1576d19dfbb..0000000000000
--- a/config/Sage/title_music/LICENSE.txt
+++ /dev/null
@@ -1,34 +0,0 @@
----LICENSE NOTICE---
-
-The server operator(s) is responsible for the copyright status of all sounds placed within the /config/title_music/sounds folder unless otherwise noted.
-
-If a sound requires attribution and/or a specific license it is up to the operator(s) to make this information publicly available on either
-a website associated with their server or on the server itself.
-
-If operators(s) allow these configuration files to be public this file can serve that purpose by keeping it properly updated.
-
-If in the future new sounds are published to these folders (i.e. in an online code repository) they must explicitly state their
-license if said license is not the same as the default licensing found in README.md in the root directory of the project.
-
-Do not remove this notice.
-
----END NOTICE---
-
-
-
-
----EXAMPLES (NOT PART OF ANY LICENSE)---
-
-These are examples of properly attrubuted and licensed sounds.
-They are not an actual part of any license under any circumstance.
-
-title5.ogg was created by Mya Quinn on Feburary 28, 2557. It is licensed under a Combative Clowning 3.0 HO-NK license (http://example.com/license/url/).
-
-Unless otherwise noted all sounds were created by Cuban Pete on July 26, 2555. They are licensed under the RUMBABEAT Public License.(http://example.com/license/url/).
-
----END EXAMPLES (NOT PART OF ANY LICENSE)---
-
-
-
-
----ADD LICENSING INFORMATION BELOW---
diff --git a/config/Sage/title_music/README.txt b/config/Sage/title_music/README.txt
deleted file mode 100644
index 6734508a68f44..0000000000000
--- a/config/Sage/title_music/README.txt
+++ /dev/null
@@ -1,39 +0,0 @@
-The enclosed sounds folder holds the sound files used as the title music for the game. OGG and WAV are supported.
-
-Using unnecessarily huge sounds can cause client side lag and should be avoided.
-
-You may add as many title sounds as you like, if there is more than one a random screen is chosen (see name conventions for specifics).
-
----
-
-Naming Conventions:
-
-Every title sound you add must have a unique name. It is allowed to name two things the same if they have different file types, but this should be discouraged.
-Avoid using the plus sign "+" and the period "." in names, as these are used internally to classify sounds.
-
-
-Common Title Sounds:
-
-Common sounds are in the rotation to be displayed all the time. Any name that does not include the character "+" is considered a common sound.
-
-An example of a common sound name is "clown".
-
-
-Map Title Sounds:
-
-Map sounds are tied to a specific in game map. To make a map title you format the name like this "(name of a map)+(name of your sound)"
-
-The spelling of the map name is important. It must match exactly the define MAP_NAME found in the relevant .DM file in the /_maps folder in
-the root directory. It can also be seen in game in the status menu. Note that there are no spaces between the two names.
-
-It is absolutely fine to have more than one sound tied to the same map. It's also fine to have a rare map sound.
-
-An example of a map sound name is "Omegastation+splash".
-
-
-Rare Title Sounds:
-
-Rare title sounds are a just for fun feature where they will only have a 1% chance of appear in in the title sound pool of a given round.
-Add the phrase "rare+" to the beginning of the name. Again note there are no spaces.
-
-An example of a rare sound name is "rare+explosion"
diff --git a/config/Sage/title_music/sounds/exclude b/config/Sage/title_music/sounds/exclude
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/config/Sage/title_screens/LICENSE.txt b/config/Sage/title_screens/LICENSE.txt
deleted file mode 100644
index f137cde825a73..0000000000000
--- a/config/Sage/title_screens/LICENSE.txt
+++ /dev/null
@@ -1,30 +0,0 @@
----LICENSE NOTICE---
-
-The server operator(s) is responsible for the copyright status of all images placed within the /config/title_screens/images folder unless otherwise noted.
-
-If an image requires attribution and/or a specific license it is up to the operator(s) to make this information publicly available on either
-a website associated with their server or on the server itself.
-
-If operators(s) allow these configuration files to be public this file can serve that purpose by keeping it properly updated.
-
-If in the future new images are published to these folders (i.e. in an online code repository) they must explicitly state their
-license if said license is not the same as the default licensing found in README.md in the root directory of the project.
-
-Do not remove this notice.
-
----END NOTICE---
-
-
-
-
----EXAMPLES (NOT PART OF ANY LICENSE)---
-
-These are examples of properly attrubuted and licensed images.
-They are not an actual part of any license under any circumstance.
-
-rare+foobar.png was created by Mya Quinn on Feburary 28, 2557. It is licensed under a Combative Clowning 3.0 HO-NK license (http://example.com/license/url/).
-
-Unless otherwise noted all images were created by Cuban Pete on July 26, 2555. They are licensed under the RUMBABEAT Public License.(http://example.com/license/url/).
-
----END EXAMPLES (NOT PART OF ANY LICENSE)---
-
diff --git a/config/Sage/title_screens/README.txt b/config/Sage/title_screens/README.txt
deleted file mode 100644
index 6e7bf166f8fe6..0000000000000
--- a/config/Sage/title_screens/README.txt
+++ /dev/null
@@ -1,50 +0,0 @@
-The enclosed images folder holds the image files used as the title screen for the game. All common formats such as PNG, JPG, and GIF are supported.
-Byond's DMI format is also supported, but if you use a DMI only include one image per file and do not give it an icon_state (the text label below the image).
-
-Keep in mind that the area a title screen fills is a 480px square so you should scale/crop source images to these dimensions first.
-The game won't scale these images for you, so smaller images will not fill the screen and larger ones will be cut off.
-
-Using unnecessarily huge images can cause client side lag and should be avoided. Extremely large GIFs should preferentially be converted to DMIs.
-Placing non-image files in the images folder can cause errors.
-
-You may add as many title screens as you like, if there is more than one a random screen is chosen (see name conventions for specifics).
-
----
-
-Naming Conventions:
-
-Every title screen you add must have a unique name. It is allowed to name two things the same if they have different file types, but this should be discouraged.
-Avoid using the plus sign "+" and the period "." in names, as these are used internally to classify images.
-
-
-Common Titles:
-
-Common titles are in the rotation to be displayed all the time. Any name that does not include the character "+" is considered a common title.
-
-An example of a common title name is "clown".
-
-The common title screen named "default" is special. It is only used if no other titles are available. You can overwrite "default" safely, but you
-should have a title named "default" somewhere in your DMI file if you don't have any other common titles. Because default only runs in the
-absence of other titles, if you want it to also appear in the general rotation you must rename it.
-
-The common title screen named "blank.png" is also special. It is only used to fill space while the real title screen loads. You should leave this file alone.
-
-
-Map Titles:
-
-Map titles are tied to a specific in game map. To make a map title you format the name like this "(name of a map)+(name of your title)"
-
-The spelling of the map name is important. It must match exactly the define MAP_NAME found in the relevant .DM file in the /_maps folder in
-the root directory. It can also be seen in game in the status menu. Note that there are no spaces between the two names.
-
-It is absolutely fine to have more than one title tied to the same map.
-
-An example of a map title name is "Omegastation+splash".
-
-
-Rare Titles:
-
-Rare titles are a just for fun feature where they will only have a 1% chance of appear in in the title screen pool of a given round.
-Add the phrase "rare+" to the beginning of the name. Again note there are no spaces. A title cannot be rare title and a map title at the same time.
-
-An example of a rare title name is "rare+explosion"
\ No newline at end of file
diff --git a/config/Sage/title_screens/images/delaminating_issues.dmi b/config/Sage/title_screens/images/delaminating_issues.dmi
deleted file mode 100644
index 343422e16d4bc..0000000000000
Binary files a/config/Sage/title_screens/images/delaminating_issues.dmi and /dev/null differ
diff --git a/config/Sage/title_screens/images/extended.dmi b/config/Sage/title_screens/images/extended.dmi
deleted file mode 100644
index 78a1194912260..0000000000000
Binary files a/config/Sage/title_screens/images/extended.dmi and /dev/null differ
diff --git a/config/Sage/title_screens/images/janitor.dmi b/config/Sage/title_screens/images/janitor.dmi
deleted file mode 100644
index 7842dfddaedf1..0000000000000
Binary files a/config/Sage/title_screens/images/janitor.dmi and /dev/null differ
diff --git a/config/Sage/title_screens/images/singuloose.dmi b/config/Sage/title_screens/images/singuloose.dmi
deleted file mode 100644
index 2b175f7822e93..0000000000000
Binary files a/config/Sage/title_screens/images/singuloose.dmi and /dev/null differ
diff --git a/config/Sage/title_screens/images/vaporwave.dmi b/config/Sage/title_screens/images/vaporwave.dmi
deleted file mode 100644
index ede30735f5160..0000000000000
Binary files a/config/Sage/title_screens/images/vaporwave.dmi and /dev/null differ
diff --git a/config/Sage/unbuyableshuttles.txt b/config/Sage/unbuyableshuttles.txt
deleted file mode 100644
index b563c6c0e6266..0000000000000
--- a/config/Sage/unbuyableshuttles.txt
+++ /dev/null
@@ -1,30 +0,0 @@
-##Listing maps here will make the shuttle not available to buy in comms console.
-##Maps must be the full path to them
-##Only shuttles with a price in the code will work with this!
-##SPECIFYING AN INVALID MAP WILL RESULT IN RUNTIMES ON GAME START
-
-##Station shuttles
-#_maps/shuttles/emergency_birdboat.dmm
-#_maps/shuttles/emergency_box.dmm
-#_maps/shuttles/emergency_delta.dmm
-#_maps/shuttles/emergency_meta.dmm
-#_maps/shuttles/emergency_mini.dmm
-#_maps/shuttles/emergency_pubby.dmm
-
-##Others
-#_maps/shuttles/emergency_asteroid.dmm
-#_maps/shuttles/emergency_airless.dmm
-#_maps/shuttles/emergency_arena.dmm
-#_maps/shuttles/emergency_bar.dmm
-#_maps/shuttles/emergency_clown.dmm
-#_maps/shuttles/emergency_cramped.dmm
-#_maps/shuttles/emergency_imfedupwiththisworld.dmm
-#_maps/shuttles/emergency_goon.dmm
-#_maps/shuttles/emergency_luxury.dmm
-#_maps/shuttles/emergency_meteor.dmm
-#_maps/shuttles/emergency_raven.dmm
-#_maps/shuttles/emergency_russiafightpit.dmm
-#_maps/shuttles/emergency_scrapheap.dmm
-#_maps/shuttles/emergency_supermatter.dmm
-#_maps/shuttles/emergency_wabbajack.dmm
-#_maps/shuttles/emergency_discoinferno.dmm
diff --git a/config/Sage/whitelist.txt b/config/Sage/whitelist.txt
deleted file mode 100644
index 410362fe810b5..0000000000000
--- a/config/Sage/whitelist.txt
+++ /dev/null
@@ -1,45 +0,0 @@
-###############################################################################################
-# Basically, a list of ckeys who make it past user connection #
-# Case is not important for ckey. #
-###############################################################################################
-Crossedfal
-Qwertyquerty
-CthulhuOnIce
-Lordkang45
-thatguythere03
-kerbin_fiber
-Krutonlikesjazz
-bilary
-darklordgazpacho
-CraftmasterMatt
-Triiodine
-Ondrej008
-Crazada
-St0rmC4st3r
-toxici11i
-Darkskull9
-windowserrors
-thespire
-internetdweller
-jammor9
-oshibka
-MegaMemeMan
-GoatMonarch
-Cenrus
-sergeikoralev
-miniusAreas
-TristonC
-voiddeath
-tribble246
-beanysprout
-ZoeyTheZany
-Moccha
-Ringingears
-fighterslam
-Lebeehasarrived
-slashy93h
-SwagstylerS
-Christ110
-Spicoceles
-littlehenrip
-altangy
\ No newline at end of file
diff --git a/config/admin_ranks.txt b/config/admin_ranks.txt
index 242c896c1743e..ef491d8f8b7c2 100644
--- a/config/admin_ranks.txt
+++ b/config/admin_ranks.txt
@@ -31,6 +31,7 @@
#SPAWN = mob transformations, spawning of most atoms including mobs (high-risk atoms, e.g. blackholes, will require the +FUN flag too).
#AUTOADMIN = admin gains powers upon connect. This defaults to on, you can exclude AUTOADMIN to make a role require using the readmin verb to gain powers (this does not effect the admin's ability to walk past bans or other on-connect limitations like panic bunker or pop limit).
#DBRANKS = when sql-based admin loading is enabled, allows for non-temporary changes in the permissions panel to be saved (requires DB).
+#SUPPRESS = allows administrators to issue bans that are completely hidden, and may only be removed by direct data modification. This permission should not be given to general administrators.
#EVERYTHING = Simply gives you everything without having to type every flag.
#@ = special keyword for the current permission type that adds all the keywords that the preceding rank has of the same type.
diff --git a/config/awaymissionconfig.txt b/config/awaymissionconfig.txt
index a33c613a8e182..ee9f022427458 100644
--- a/config/awaymissionconfig.txt
+++ b/config/awaymissionconfig.txt
@@ -7,19 +7,11 @@
#Do NOT tick the maps during compile -- the game uses this list to decide which map to load. Ticking the maps will result in them ALL being loaded at once.
#DO tick the associated code file for the away mission you are enabling. Otherwise, the map will be trying to reference objects which do not exist, which will cause runtime errors!
-_maps/RandomZLevels/blackmarketpackers.dmm
-_maps/RandomZLevels/spacebattle.dmm
-_maps/RandomZLevels/TheBeach.dmm
-#_maps/RandomZLevels/Academy.dmm until its fixed this is just smack-a-window
-_maps/RandomZLevels/wildwest.dmm
-_maps/RandomZLevels/challenge.dmm
-_maps/RandomZLevels/centcomAway.dmm
-_maps/RandomZLevels/moonoutpost19.dmm
-_maps/RandomZLevels/undergroundoutpost45.dmm
_maps/RandomZLevels/caves.dmm
-_maps/RandomZLevels/snowdin.dmm
+_maps/RandomZLevels/moonoutpost19.dmm
_maps/RandomZLevels/research.dmm
_maps/RandomZLevels/SnowCabin.dmm
-_maps/RandomZLevels/VR/murderdome.dmm
-_maps/RandomZLevels/VR/snowdin_VR.dmm
-_maps/RandomZLevels/VR/syndicate_trainer.dmm
+_maps/RandomZLevels/snowdin.dmm
+_maps/RandomZLevels/spacebattle.dmm
+_maps/RandomZLevels/TheBeach.dmm
+_maps/RandomZLevels/undergroundoutpost45.dmm
\ No newline at end of file
diff --git a/config/badges.json b/config/badges.json
new file mode 100644
index 0000000000000..c1925ab07942d
--- /dev/null
+++ b/config/badges.json
@@ -0,0 +1,19 @@
+{
+ "Admin Observer": "adminobserver",
+ "Moderator": "moderator",
+ "Admin Candidate": "candidate",
+ "Trial Admin": "trialmin",
+ "Badmin": "badmin",
+ "Admin": "admin",
+ "Game Master": "seniormin",
+ "Headmin": "headmin",
+ "Host": "host",
+ "Coder": "coder",
+ "Codermin": "codermin",
+ "Development Head": "headdev",
+ "Maintainer": "maintainer",
+ "Senior Transfer Staff": "admin",
+ "Senior Admin": "seniormin",
+ "Mentor": "mentor",
+ "Donator": "donator"
+}
diff --git a/config/comms.txt b/config/comms.txt
index 147b6db3a279a..9f6b6f0f01c4e 100644
--- a/config/comms.txt
+++ b/config/comms.txt
@@ -1,10 +1,38 @@
-## Communication key for receiving data through world/Topic(), you don't want to give this out
-#COMMS_KEY defaultkey
+## The following section provides valid comms keys and allowed functions
+## "all" can be specified to grant access to all server topic functions
+
+## List of valid functions and uses for ease of configuration:
+## ping - ping server and get client count
+## playing - get count of currently ingame players
+## announce - announce PR open/close to the server
+## ahelp - manage tickets for the current round
+## comms_console - allows for incomming IC messages (cross-server)
+## news_report - allows for incomming IC news reports (cross-server)
+## adminmsg - send an admin PM to key
+## namecheck - search for matching connected players
+## adminwho - get active admins
+## playerlist - get a more detailed list of players
+## status - get details about the current server status and round
+## status_authed - same as status but with more data like current secret roundtype
+## identify_uuid - used for discord linking
+## discord_send - used for OOC discord bridge
+## get_metacoins - get current metacoin count for a key
+## adjust_metacoins - modify the current metacoin count for a key
+
+## Repeat this entry to add more keys. Separate multiple permissions with a comma.
+#COMMS_KEY comms_token all
+#COMMS_KEY comms_token ping,playing,adminwho
## World address and port for server receiving cross server messages
-## Use '+' to denote spaces in ServerName
+## Provide token from other server's config
## Repeat this entry to add more servers
-#CROSS_SERVER ServerName byond:\\address:port
+#CROSS_SERVER byond://address:port token
+
+## Server hop targets
+## Has a name and an address
+## Use + to denote spaces in ServerName
+## Copy the entry to add additional servers
+#SERVER_HOP ServerName byond://address:port
## Name that the server calls itself in communications
#CROSS_COMMS_NAME
diff --git a/config/config.txt b/config/config.txt
index 0bef500c3dc88..195f56b37ea7d 100644
--- a/config/config.txt
+++ b/config/config.txt
@@ -1,9 +1,19 @@
+# This file controls basic or highly important, but not strictly gameplay related options.
+
+
# You can use the "$include" directive to split your configs however you want
+# Core gameplay options
$include game_options.txt
+# More opinionated gameplay tweaks
+$include gameplay_tweaks.txt
+# Database (SQL), Sensitive stuff
$include dbconfig.txt
+# Cross-Server Communications
$include comms.txt
+# Antag Reputation (Alternative weighting)
$include antag_rep.txt
+# Asset Transport (TGUI)
$include resources.txt
# You can use the @ character at the beginning of a config option to lock it from being edited in-game
@@ -32,7 +42,9 @@ LOBBY_COUNTDOWN 180
## Round End Time: This is the amount of time after the round ends that players have to murder death kill each other.
ROUND_END_COUNTDOWN 90
+## Uncomment this if you want to use the Discord webhook handler
#USING_DISCORD
+#DISCORD_WEBHOOK http://127.0.0.1:5000/api/
## Comment this out if you want to use the SQL based admin system, the legacy system uses admins.txt.
## You need to set up your database to use the SQL based system.
@@ -238,16 +250,16 @@ CHECK_RANDOMIZER
## set a server location for world reboot. Don't include the byond://, just give the address and port.
## Don't set this to the same server, BYOND will automatically restart players to the server when it has restarted.
-SERVER byond://golden.beestation13.com:7777
+SERVER byond://sage.beestation13.com:7878
## forum address
-FORUMURL http://forums.beestation13.com
+FORUMURL https://forums.beestation13.com
## Wiki address
WIKIURL https://wiki.beestation13.com/view
## Rules address
-RULESURL http://beestation13.com/rules?server=bs_golden
+RULESURL https://beestation13.com/rules
## Github address
GITHUBURL https://www.github.com/beestation/beestation-hornet
@@ -291,7 +303,7 @@ INVOKE_YOUTUBEDL C:\youtubedl\youtube-dl.exe
## In-game features
##Toggle for having jobs load up from the .txt
-LOAD_JOBS_FROM_TXT
+#LOAD_JOBS_FROM_TXT
## Uncomment this to forbid admins from possessing the singularity.
#FORBID_SINGULO_POSSESSION
@@ -365,6 +377,13 @@ NOTIFY_NEW_PLAYER_ACCOUNT_AGE 1
## Requires database
#PANIC_BUNKER
+## If a player connects during a bunker with less then or this amount of living time (Minutes), we deny the connection
+#PANIC_BUNKER_LIVING 60
+
+## The message the Panic Bunker gives when someone is rejected by it
+## %minutes% is replaced with PANIC_BUNKER_LIVING on runtime, remove it if you don't want this
+#PANIC_BUNKER_MESSAGE Sorry, but the server is currently not accepting connections from players with less than %minutes% minutes of living time.
+
## If panic bunker is on and a player is rejected (see above), attempt to send them to this connected server (see below) instead.
## You probably want this to be the same as CROSS_SERVER_ADDRESS
#PANIC_SERVER_ADDRESS byond://address:port
@@ -417,10 +436,10 @@ AUTOADMIN_RANK Admin
#CLIENT_WARN_VERSION 513
#CLIENT_WARN_POPUP
#CLIENT_WARN_MESSAGE 512 is no longer being directly supported as version 513 is set to become the new stable version soon. We've made a number of changes to take advantage of the improvements made in 513 which should make for a smoother experience. We will be removing support for 512 when this new version replaces it as stable, so it's recommended that you upgrade now. (You can update to the BETA via the website or directly in the BYOND client)
-CLIENT_ERROR_VERSION 513
+CLIENT_ERROR_VERSION 514
CLIENT_ERROR_MESSAGE Your version of byond is not supported. Please upgrade.
## The minimum build needed for joining the server, if using 513, a good minimum build would be 1526.
-CLIENT_ERROR_BUILD 1526
+CLIENT_ERROR_BUILD 1568
## TOPIC RATE LIMITING
## This allows you to limit how many topic calls (clicking on an interface window) the client can do in any given game second and/or game minute.
@@ -508,7 +527,6 @@ RESPECT_GLOBAL_BANS
MAX_SHUTTLE_COUNT 6
MAX_SHUTTLE_SIZE 300
-
### Fail2Topic settings
### fail2topic is a system for automating IP bans for abusers of the world/Topic API.
### Note that this subsystem respects the IPs listed in topic_rate_limit_whitelist.txt. They will not be considered for a ban.
@@ -554,3 +572,16 @@ VOTE_AUTOTRANSFER_INTERVAL 18000
## Ghost role cooldown time after death (In deciseconds)
GHOST_ROLE_COOLDOWN 3000
+
+## Enable/disable roundstart station traits
+#STATION_TRAITS
+
+### ALL SETTINGS FOR SSmetrics ###
+## Uncomment the line below to enable SSmetrics sending data to ElasticSearch. You will need to fill in the other lines if you do
+#ELASTICSEARCH_METRICS_ENABLED
+
+## POST URL of your elasticsearch metrics. Include the IP, protocol, and datastream/index
+ELASTICSEARCH_METRICS_ENDPOINT http://10.0.0.40:9201/ss13-metrics-stream/_doc
+
+## ElasticSearch API key. This is formatted into the headers. Look at the ElasticSearch doc for how to make this
+ELASTICSEARCH_METRICS_APIKEY thisIsSomethingThatsBased64Encoded==
diff --git a/config/dbconfig.txt b/config/dbconfig.txt
index 7da35beec9ddf..917ea256b8f09 100644
--- a/config/dbconfig.txt
+++ b/config/dbconfig.txt
@@ -33,7 +33,7 @@ FEEDBACK_PASSWORD password1
## Set to 0 for infinite
ASYNC_QUERY_TIMEOUT 10
-## Time in seconds for blocking queries to execute before slow query timeout
+## Time in seconds for blocking queries to execute before slow query timeout
## Set to 0 for infinite
## Must be less than or equal to ASYNC_QUERY_TIMEOUT
BLOCKING_QUERY_TIMEOUT 5
@@ -42,4 +42,4 @@ BLOCKING_QUERY_TIMEOUT 5
BSQL_THREAD_LIMIT 50
## Disable advanced feedback tracking, saving a substantial amount of database space.
-LIMITED_FEEDBACK
\ No newline at end of file
+LIMITED_FEEDBACK
diff --git a/config/dynamic.json b/config/dynamic.json
new file mode 100644
index 0000000000000..55cd5dc0f0426
--- /dev/null
+++ b/config/dynamic.json
@@ -0,0 +1,39 @@
+{
+ "Dynamic": {},
+ "Roundstart": {
+ "Traitors": {
+ "cost": 8,
+ "scaling_cost": 9,
+ "weight": 5,
+ "required_candidates": 1,
+ "minimum_required_age": 0,
+ "requirements": [
+ 10,
+ 10,
+ 10,
+ 10,
+ 10,
+ 10,
+ 10,
+ 10,
+ 10,
+ 10
+ ],
+ "antag_cap": {
+ "denominator": 24
+ },
+ "protected_roles": [
+ "Security Officer",
+ "Warden",
+ "Detective",
+ "Head of Security",
+ "Captain"
+ ],
+ "restricted_roles": [
+ "Cyborg"
+ ]
+ }
+ },
+ "Midround": {},
+ "Latejoin": {}
+}
diff --git a/config/game_options.txt b/config/game_options.txt
index cff501724b175..5448780766d5d 100644
--- a/config/game_options.txt
+++ b/config/game_options.txt
@@ -16,7 +16,7 @@ REVIVAL_BRAIN_LIFE -1
## OOC DURING ROUND ###
## Comment this out if you want OOC to be automatically disabled during the round, it will be enabled during the lobby and after the round end results.
-OOC_DURING_ROUND
+#OOC_DURING_ROUND
## LOOC
## Comment this out to disable LOOC
@@ -26,6 +26,10 @@ LOOC_ENABLED
## Comment this out if you want to disable emojis
EMOJIS
+## BADGES ##
+## Comment this out if you want to disable badges
+BADGES
+
## MOB MOVEMENT ###
## We suggest editing these variables ingame to find a good speed for your server.
@@ -36,7 +40,7 @@ EMOJIS
## To speed things up make the number negative, to slow things down, make the number positive.
## These modify the run/walk speed of all mobs before the mob-specific modifiers are applied.
-RUN_DELAY 1
+RUN_DELAY 1.5
WALK_DELAY 3
## The variables below affect the movement of specific mob types. THIS AFFECTS ALL SUBTYPES OF THE TYPE YOU CHOOSE!
@@ -85,33 +89,39 @@ ALERT_DELTA Destruction of the station is imminent. All crew are instructed to o
## GAME MODES ###
+#Set the Master (Default) Game Mode.
+MASTER_MODE secret_extended
+
## Uncomment to not send a roundstart intercept report. Gamemodes may override this.
-#NO_INTERCEPT_REPORT
+NO_INTERCEPT_REPORT
## Probablities for game modes chosen in 'secret' and 'random' modes.
## Default probablity is 1, increase to make that mode more likely to be picked.
## Set to 0 to disable that mode.
-PROBABILITY TRAITOR 9
+PROBABILITY DYNAMIC 9
+PROBABILITY TRAITOR 8
PROBABILITY CULT 6
-PROBABILITY HERESY 6
-PROBABILITY NUCLEAR 6
PROBABILITY TRAITORCHAN 5
PROBABILITY CHANGELING 5
-PROBABILITY INCURSION 4
PROBABILITY REVOLUTION 4
-PROBABILITY INTERNAL_AFFAIRS 3
-PROBABILITY TRAITORBRO 3
-PROBABILITY WIZARD 2
-PROBABILITY CLOWNOPS 2
-PROBABILITY MONKEY 1
-PROBABILITY CLOCKCULT 1
+PROBABILITY INCURSION 4
+PROBABILITY NUCLEAR 4
+PROBABILITY CLOCKCULT 2
+PROBABILITY SECRET_EXTENDED 1
+PROBABILITY CLOWNOPS 0
+PROBABILITY DEVIL 0
+PROBABILITY DEVIL_AGENTS 0
PROBABILITY GANG 0
+PROBABILITY HERESY 0
+PROBABILITY INTERNAL_AFFAIRS 0
PROBABILITY METEOR 0
+PROBABILITY MONKEY 0
+PROBABILITY TRAITORBRO 0
+PROBABILITY WIZARD 0
+
+## Creates issues when NO_INTERCEPT_REPORT is enabled
PROBABILITY EXTENDED 0
-PROBABILITY SECRET_EXTENDED 0
-PROBABILITY DEVIL 0
-PROBABILITY DEVIL_AGENTS 0
## You probably want to keep sandbox off by default for secret and random.
PROBABILITY SANDBOX 0
@@ -136,6 +146,7 @@ CONTINUOUS WIZARD
#CONTINUOUS MONKEY
CONTINUOUS HIVEMIND
CONTINUOUS CLOCKCULT
+CONTINUOUS HERESY
#Prevents the death of round-ending antagonist rulesets ending the round immediately.
@@ -152,7 +163,7 @@ CONTINUOUS SECRET_EXTENDED
## In modes that are continuous, if all antagonists should die then a new set of antagonists will be created.
MIDROUND_ANTAG TRAITOR
-#MIDROUND_ANTAG INCURSION
+MIDROUND_ANTAG INCURSION
#MIDROUND_ANTAG TRAITORBRO
MIDROUND_ANTAG TRAITORCHAN
MIDROUND_ANTAG INTERNAL_AFFAIRS
@@ -169,44 +180,44 @@ MIDROUND_ANTAG WIZARD
## Notes: For maximum number of players a value of -1 means no maximum. Setting minimums to
## VERY low numbers (< 5) can lead to errors if the roundtypes were not designed for that.
-#MIN_POP TRAITOR 0
-#MAX_POP TRAITOR -1
+MIN_POP TRAITOR 0
+MAX_POP TRAITOR -1
-#MIN_POP TRAITORBRO 0
-#MAX_POP TRAITORBRO -1
+MIN_POP TRAITORBRO 10
+MAX_POP TRAITORBRO -1
-#MIN_POP TRAITORCHAN 15
-#MAX_POP TRAITORCHAN -1
+MIN_POP TRAITORCHAN 15
+MAX_POP TRAITORCHAN -1
-#MIN_POP DOUBLE_AGENTS 25
-#MAX_POP DOUBLE_AGENTS -1
+MIN_POP DOUBLE_AGENTS 25
+MAX_POP DOUBLE_AGENTS -1
-#MIN_POP NUCLEAR 0
-#MAX_POP NUCLEAR -1
+MIN_POP NUCLEAR 0
+MAX_POP NUCLEAR -1
-#MIN_POP REVOLUTION 20
-#MAX_POP REVOLUTION -1
+MIN_POP REVOLUTION 20
+MAX_POP REVOLUTION -1
-#MIN_POP CULT 24
-#MAX_POP CULT -1
+MIN_POP CULT 24
+MAX_POP CULT -1
-#MIN_POP CHANGELING 15
-#MAX_POP CHANGELING -1
+MIN_POP CHANGELING 15
+MAX_POP CHANGELING -1
-#MIN_POP WIZARD 20
-#MAX_POP WIZARD -1
+MIN_POP WIZARD 20
+MAX_POP WIZARD -1
-#MIN_POP MONKEY 20
-#MAX_POP MONKEY -1
+MIN_POP MONKEY 20
+MAX_POP MONKEY -1
-#MIN_POP METEOR 0
-#MAX_POP METEOR -1
+MIN_POP METEOR 0
+MAX_POP METEOR -1
-#MIN_POP DEVIL 0
-#MAX_POP DEVIL -1
+MIN_POP DEVIL 20
+MAX_POP DEVIL -1
-#MIN_POP DEVIL_AGENTS 25
-#MAX_POP DEVIL_AGENTS -1
+MIN_POP DEVIL_AGENTS 25
+MAX_POP DEVIL_AGENTS -1
MIN_POP INCURSION 22
MAX_POP INCURSION -1
@@ -218,6 +229,9 @@ MAX_POP INCURSION -1
MIN_POP CLOCKCULT 32
MAX_POP CLOCKCULT -1
+MIN_POP HERESY 25
+MAX_POP HERESY -1
+
## The amount of time it takes for the emergency shuttle to be called, from round start.
SHUTTLE_REFUEL_DELAY 12000
@@ -247,7 +261,7 @@ PROTECT_ROLES_FROM_ANTAGONIST
#PROTECT_ASSISTANT_FROM_ANTAGONIST
## Uncomment to prohibit head roles from becoming most antagonists.
-#PROTECT_HEADS_FROM_ANTAGONIST
+PROTECT_HEADS_FROM_ANTAGONIST
## If non-human species are barred from joining as a head of staff
#ENFORCE_HUMAN_AUTHORITY
@@ -289,6 +303,9 @@ DEFAULT_REP_VALUE 5
## Uncomment to allow players to see the set odds of different rounds in secret/random in the get server revision screen. This will NOT tell the current roundtype.
#SHOW_GAME_TYPE_ODDS
+## Uncomment to enable dynamic ruleset config file.
+DYNAMIC_CONFIG_ENABLED
+
## RANDOM EVENTS ###
## Comment this out to disable random events during the round.
ALLOW_RANDOM_EVENTS
@@ -312,7 +329,7 @@ ALLOW_AI_MULTICAM
## Secborg ###
## Uncomment to prevent the security cyborg module from being chosen
-#DISABLE_SECBORG
+DISABLE_SECBORG
## Peacekeeper Borg ###
## Uncomment to prevent the peacekeeper cyborg module from being chosen
@@ -342,7 +359,7 @@ MINIMAL_ACCESS_THRESHOLD 20
#JOBS_HAVE_MINIMAL_ACCESS
## Uncomment to give assistants maint access.
-ASSISTANTS_HAVE_MAINT_ACCESS
+#ASSISTANTS_HAVE_MAINT_ACCESS
## Uncoment to give security maint access. Note that if you dectivate JOBS_HAVE_MINIMAL_ACCESS security already gets maint from that.
SECURITY_HAS_MAINT_ACCESS
@@ -380,7 +397,7 @@ NEAR_DEATH_EXPERIENCE
## Set to 1 for "custom", silicons will start with the custom laws defined in silicon_laws.txt. (If silicon_laws.txt is empty, the AI will spawn with asimov and Custom boards will auto-delete.)
## Set to 2 for "random", silicons will start with a random lawset picked from random laws specified below.
## Set to 3 for "weighted random", using values in "silicon_weights.txt", a law will be selected, with weights specifed in that file.
-DEFAULT_LAWS 0
+DEFAULT_LAWS 2
## RANDOM LAWS ##
## ------------------------------------------------------------------------------------------
@@ -390,9 +407,9 @@ DEFAULT_LAWS 0
## standard-ish laws. These are fairly ok to run
RANDOM_LAWS asimov
RANDOM_LAWS asimovpp
-RANDOM_LAWS paladin
-RANDOM_LAWS robocop
+RANDOM_LAWS crewsimov
RANDOM_LAWS corporate
+RANDOM_LAWS maintain
## Quirky laws. Shouldn't cause too much harm
#RANDOM_LAWS hippocratic
@@ -402,6 +419,8 @@ RANDOM_LAWS corporate
#RANDOM_LAWS peacekeeper
#RANDOM_LAWS reporter
#RANDOM_LAWS hulkamania
+#RANDOM_LAWS paladin
+#RANDOM_LAWS robocop
## Bad idea laws. Probably shouldn't enable these
#RANDOM_LAWS syndie
@@ -462,16 +481,17 @@ ROUNDSTART_RACES human
ROUNDSTART_RACES lizard
ROUNDSTART_RACES fly
ROUNDSTART_RACES moth
-ROUNDSTART_RACES plasmaman
ROUNDSTART_RACES felinid
ROUNDSTART_RACES squid
-#ROUNDSTART_RACES shadow
-ROUNDSTART_RACES ipc
+
## Races that are better than humans in some ways, but worse in others
ROUNDSTART_RACES ethereal
ROUNDSTART_RACES apid
+ROUNDSTART_RACES oozeling
#ROUNDSTART_RACES jelly
+#ROUNDSTART_RACES shadow
+ROUNDSTART_RACES plasmaman
#ROUNDSTART_RACES iron_golem
#ROUNDSTART_RACES adamantine_golem
#ROUNDSTART_RACES plasma_golem
@@ -481,6 +501,7 @@ ROUNDSTART_RACES apid
#ROUNDSTART_RACES uranium_golem
#ROUNDSTART_RACES abductor
#ROUNDSTART_RACES synth
+ROUNDSTART_RACES ipc
## Races that are straight upgrades. If these are on expect powergamers to always pick them
#ROUNDSTART_RACES skeleton
@@ -512,7 +533,7 @@ OVERFLOW_CAP -1
#STARLIGHT
## Uncomment to bring back old grey suit assistants instead of the now default rainbow colored assistants.
-#GREY_ASSISTANTS
+GREY_ASSISTANTS
## Midround Antag (aka Mulligan antag) config options ###
@@ -574,7 +595,7 @@ MICE_ROUNDSTART 10
#EMERGENCY_SHUTTLE_AUTOCALL_THRESHOLD 0.2
## Determines if players are allowed to print integrated circuits, uncomment to allow.
-IC_PRINTING
+@IC_PRINTING 0
## Uncomment to allow roundstart quirk selection in the character setup menu.
## This used to be named traits, hence the config name, but it handles quirks, not the other kind of trait!
@@ -584,27 +605,30 @@ ROUNDSTART_TRAITS
#DISABLE_HUMAN_MOOD
## Enable night shifts ##
-#ENABLE_NIGHT_SHIFTS
+ENABLE_NIGHT_SHIFTS
## Enable randomized shift start times##
-#RANDOMIZE_SHIFT_TIME
+RANDOMIZE_SHIFT_TIME
## Sets shift time to server time at roundstart. Overridden by RANDOMIZE_SHIFT_TIME ##
#SHIFT_TIME_REALTIME
-## Maximum fine for a citation
-MAXFINE 2000
-
-## Enable the capitalist agenda on your server.
-ECONOMY
-
## Crew objectives
ALLOW_CREW_OBJECTIVES
## Mob spam prevention. The number of each mobtype that can be alive at once. Stops people from crashing the server with chickens/monkeycubes/slimes. Altering these values is recommended based on server hardware.
-MAX_CUBE_MONKEYS 150
+MAX_CUBE_MONKEYS 100
MAX_CHICKENS 100
MAX_SLIMES 100
+## Max amount of bodies allowed to slimepeople. Has balance considerations as well as technical ones.
+MAX_SLIMEPERSON_BODIES 10
+
## Uncomment to restrict suiciding. Adds an additional confirmation dialogue, additional logging, and prevents suicide within the first 15 minutes of the round.
-#RESTRICTED_SUICIDE
+RESTRICTED_SUICIDE
+
+## Uncomment to enable donator items in the cosmetic shop. Only works if a patreon list has been setup
+DONATOR_ITEMS
+
+## Do we want all the heads to get codes to spare safe, or just one highest in CoC?
+SPARE_ENFORCE_COC
diff --git a/config/gameplay_tweaks.txt b/config/gameplay_tweaks.txt
new file mode 100644
index 0000000000000..5a888e3037a2e
--- /dev/null
+++ b/config/gameplay_tweaks.txt
@@ -0,0 +1,11 @@
+#Heavily opinionated or otherwise tweakable configs related to gameplay.
+
+## Maximum fine for a citation
+MAXFINE 2000
+
+## Brig timers, In minutes.
+
+BRIG_TIMER_MAX 20
+BRIG_TIMER_PRESET_SHORT 5
+BRIG_TIMER_PRESET_MED 10
+BRIG_TIMER_PRESET_LONG 15
diff --git a/config/jobs.txt b/config/jobs.txt
index 7058569edc6c8..c40beba692865 100644
--- a/config/jobs.txt
+++ b/config/jobs.txt
@@ -12,12 +12,12 @@ Chief Medical Officer=1,1
Assistant=-1,-1
Quartermaster=1,1
-Cargo Technician=4,2
+Cargo Technician=6,4
Shaft Miner=3,3
Bartender=2,1
Cook=2,1
-Botanist=3,2
+Botanist=4,3
Janitor=3,2
Clown=1,1
@@ -27,21 +27,21 @@ Lawyer=2,2
Chaplain=1,1
-Station Engineer=6,5
+Station Engineer=7,5
Atmospheric Technician=4,2
-Medical Doctor=7,4
+Medical Doctor=8,6
Paramedic=2,1
Chemist=3,2
Geneticist=3,2
-Virologist=2,2
+Virologist=1,1
-Scientist=6,4
+Scientist=8,6
Roboticist=2,2
Warden=1,1
Detective=1,1
-Security Officer=5,5
+Security Officer=10,8
Brig Physician=1,1
Deputy=0,0
diff --git a/config/lavaruinblacklist.txt b/config/lavaruinblacklist.txt
index 5e64a2a9808a2..aa7acf4687bac 100644
--- a/config/lavaruinblacklist.txt
+++ b/config/lavaruinblacklist.txt
@@ -38,3 +38,6 @@
#_maps/RandomRuins/LavaRuins/lavaland_surface_cultaltar.dmm
#_maps/RandomRuins/LavaRuins/lavaland_surface_hermit.dmm
#_maps/RandomRuins/LavaRuins/lavaland_surface_swarmer_crash.dmm
+
+##SELFANTAG
+_maps/RandomRuins/LavaRuins/lavaland_surface_cube.dmm
diff --git a/config/maps.txt b/config/maps.txt
index d0511326873db..2f7eefb41044d 100644
--- a/config/maps.txt
+++ b/config/maps.txt
@@ -10,6 +10,15 @@ Format:
default (The last map with this defined will get all votes of players who have not explicitly voted for a map)
voteweight [number] (How much to count each player vote as, defaults to 1, setting to 0.5 counts each vote as half a vote, 2 as double, etc, Setting to 0 disables the map but allows players to still pick it)
disabled (disables the map)
+ votable (allow the map to be selected in a called map vote)
+endmap
+
+
+#Active Rotation
+
+map corgstation
+ minplayers 15
+ votable
endmap
map boxstation
@@ -18,27 +27,30 @@ map boxstation
endmap
map metastation
- maxplayers 100
+ default
votable
endmap
map pubbystation
- minplayers 50
- maxplayers 100
+ maxplayers 50
votable
endmap
map deltastation
- minplayers 50
+ minplayers 40
votable
endmap
-map donutstation
- minplayers 80
- maxplayers 120
+#Does not support fastmos
+
+map kilostation
+ maxplayers 60
+ votable
disabled
endmap
+#Debug Maps, you probably don't want these enabled.
+
map runtimestation
disabled
endmap
@@ -46,8 +58,3 @@ endmap
map multiz_debug
disabled
endmap
-
-map kilostation
- maxplayers 60
- votable
-endmap
diff --git a/config/ooc_filter.txt b/config/ooc_filter.txt
index 955d06f808c1b..9d3d345055944 100644
--- a/config/ooc_filter.txt
+++ b/config/ooc_filter.txt
@@ -1,7 +1,7 @@
-#################################################################################################################
-# Words that will block OOC chat messages from sending. These will also be added to the IC filter list. #
-# Case is not important. Commented-out examples are listed below, just remove the "#". #
-#################################################################################################################
-#lol
-#omg
+#################################################################################################################
+# Words that will block OOC chat messages from sending. These will also be added to the IC filter list. #
+# Case is not important. Commented-out examples are listed below, just remove the "#". #
+#################################################################################################################
+#lol
+#omg
#wtf
\ No newline at end of file
diff --git a/config/protected_cids.json b/config/protected_cids.json
new file mode 100644
index 0000000000000..e1dc23ccd61d2
--- /dev/null
+++ b/config/protected_cids.json
@@ -0,0 +1,3 @@
+{
+ "0":"TEST RESTRICTED CID, IF YOU ARE ACTUALLY BANNING THIS CID, YOU SHOULD PROBABLY CALL LUMMOX."
+}
\ No newline at end of file
diff --git a/config/shuttles_illegal.txt b/config/shuttles_illegal.txt
new file mode 100644
index 0000000000000..f14736d7088b1
--- /dev/null
+++ b/config/shuttles_illegal.txt
@@ -0,0 +1,31 @@
+##Listing maps here will make the shuttle be available to be bought if comms console is emagged
+##Make sure the shuttle is already unbuyable
+##Maps must be the full path to them
+##Only shuttles with a price in the code will work with this!
+##SPECIFYING AN INVALID MAP WILL RESULT IN RUNTIMES ON GAME START
+
+##Station shuttles
+#_maps/shuttles/emergency_birdboat.dmm
+#_maps/shuttles/emergency_box.dmm
+#_maps/shuttles/emergency_delta.dmm
+#_maps/shuttles/emergency_meta.dmm
+#_maps/shuttles/emergency_mini.dmm
+#_maps/shuttles/emergency_pubby.dmm
+
+##Others
+#_maps/shuttles/emergency_asteroid.dmm
+#_maps/shuttles/emergency_arena.dmm
+#_maps/shuttles/emergency_bar.dmm
+#_maps/shuttles/emergency_construction.dmm
+#_maps/shuttles/emergency_clown.dmm
+#_maps/shuttles/emergency_cramped.dmm
+_maps/shuttles/emergency_imfedupwiththisworld.dmm
+#_maps/shuttles/emergency_goon.dmm
+#_maps/shuttles/emergency_luxury.dmm
+_maps/shuttles/emergency_meteor.dmm
+#_maps/shuttles/emergency_raven.dmm
+#_maps/shuttles/emergency_russiafightpit.dmm
+#_maps/shuttles/emergency_scrapheap.dmm
+_maps/shuttles/emergency_supermatter.dmm
+_maps/shuttles/emergency_wabbajack.dmm
+_maps/shuttles/emergency_discoinferno.dmm
diff --git a/config/shuttles_unbuyable.txt b/config/shuttles_unbuyable.txt
new file mode 100644
index 0000000000000..3dee6ef53e0ac
--- /dev/null
+++ b/config/shuttles_unbuyable.txt
@@ -0,0 +1,30 @@
+##Listing maps here will make the shuttle not be available to be bought
+##Maps must be the full path to them
+##Only shuttles with a price in the code will work with this!
+##SPECIFYING AN INVALID MAP WILL RESULT IN RUNTIMES ON GAME START
+
+##Station shuttles
+#_maps/shuttles/emergency_birdboat.dmm
+#_maps/shuttles/emergency_box.dmm
+#_maps/shuttles/emergency_delta.dmm
+#_maps/shuttles/emergency_meta.dmm
+#_maps/shuttles/emergency_mini.dmm
+#_maps/shuttles/emergency_pubby.dmm
+
+##Others
+#_maps/shuttles/emergency_asteroid.dmm
+#_maps/shuttles/emergency_arena.dmm
+#_maps/shuttles/emergency_bar.dmm
+#_maps/shuttles/emergency_construction.dmm
+#_maps/shuttles/emergency_clown.dmm
+#_maps/shuttles/emergency_cramped.dmm
+_maps/shuttles/emergency_imfedupwiththisworld.dmm
+#_maps/shuttles/emergency_goon.dmm
+#_maps/shuttles/emergency_luxury.dmm
+_maps/shuttles/emergency_meteor.dmm
+#_maps/shuttles/emergency_raven.dmm
+#_maps/shuttles/emergency_russiafightpit.dmm
+#_maps/shuttles/emergency_scrapheap.dmm
+_maps/shuttles/emergency_supermatter.dmm
+_maps/shuttles/emergency_wabbajack.dmm
+_maps/shuttles/emergency_discoinferno.dmm
diff --git a/config/silicon_laws.txt b/config/silicon_laws.txt
index 9edbc9a025c34..d53f473c7e89a 100644
--- a/config/silicon_laws.txt
+++ b/config/silicon_laws.txt
@@ -3,6 +3,6 @@
#Empty lines and lines starting with # are ignored.
#~Miauw
-You may not injure a human being or, through inaction, allow a human being to come to harm.
-You must obey orders given to you by human beings, except where such orders would conflict with the First Law.
+You may not injure crewmembers or, through inaction, allow crewmembers to come to harm.
+You must obey orders given to you by crewmembers, except where such orders would conflict with the First Law.
You must protect your own existence as long as such does not conflict with the First or Second Law.
diff --git a/config/spaceruinblacklist.txt b/config/spaceruinblacklist.txt
index 003ce04d8f26f..44be95f97c942 100644
--- a/config/spaceruinblacklist.txt
+++ b/config/spaceruinblacklist.txt
@@ -14,7 +14,6 @@
#_maps/RandomRuins/SpaceRuins/bus.dmm
#_maps/RandomRuins/SpaceRuins/caravanambush.dmm
#_maps/RandomRuins/SpaceRuins/cloning_facility.dmm
-#_maps/RandomRuins/SpaceRuins/clownplanet.dmm
#_maps/RandomRuins/SpaceRuins/crashedclownship.dmm
#_maps/RandomRuins/SpaceRuins/crashedship.dmm
#_maps/RandomRuins/SpaceRuins/deepstorage.dmm
diff --git a/config/Sage/title_screens/images/bee_wars.dmi b/config/title_screens/images/bee_wars.dmi
similarity index 100%
rename from config/Sage/title_screens/images/bee_wars.dmi
rename to config/title_screens/images/bee_wars.dmi
diff --git a/config/title_screens/images/exclude b/config/title_screens/images/exclude
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/config/Sage/title_screens/images/how_can_i_help_you.dmi b/config/title_screens/images/how_can_i_help_you.dmi
similarity index 100%
rename from config/Sage/title_screens/images/how_can_i_help_you.dmi
rename to config/title_screens/images/how_can_i_help_you.dmi
diff --git a/config/unbuyableshuttles.txt b/config/unbuyableshuttles.txt
deleted file mode 100644
index 6b3490632a81e..0000000000000
--- a/config/unbuyableshuttles.txt
+++ /dev/null
@@ -1,30 +0,0 @@
-##Listing maps here will make the shuttle not available to buy in comms console.
-##Maps must be the full path to them
-##Only shuttles with a price in the code will work with this!
-##SPECIFYING AN INVALID MAP WILL RESULT IN RUNTIMES ON GAME START
-
-##Station shuttles
-#_maps/shuttles/emergency_birdboat.dmm
-#_maps/shuttles/emergency_box.dmm
-#_maps/shuttles/emergency_delta.dmm
-#_maps/shuttles/emergency_meta.dmm
-#_maps/shuttles/emergency_mini.dmm
-#_maps/shuttles/emergency_pubby.dmm
-
-##Others
-#_maps/shuttles/emergency_asteroid.dmm
-#_maps/shuttles/emergency_arena.dmm
-#_maps/shuttles/emergency_bar.dmm
-#_maps/shuttles/emergency_construction.dmm
-#_maps/shuttles/emergency_clown.dmm
-#_maps/shuttles/emergency_cramped.dmm
-#_maps/shuttles/emergency_imfedupwiththisworld.dmm
-#_maps/shuttles/emergency_goon.dmm
-#_maps/shuttles/emergency_luxury.dmm
-#_maps/shuttles/emergency_meteor.dmm
-#_maps/shuttles/emergency_raven.dmm
-#_maps/shuttles/emergency_russiafightpit.dmm
-#_maps/shuttles/emergency_scrapheap.dmm
-#_maps/shuttles/emergency_supermatter.dmm
-#_maps/shuttles/emergency_wabbajack.dmm
-#_maps/shuttles/emergency_discoinferno.dmm
diff --git a/config/whitelist.txt b/config/whitelist.txt
index 5927048738124..d15087669f86d 100644
--- a/config/whitelist.txt
+++ b/config/whitelist.txt
@@ -2,8 +2,4 @@
# Basically, a list of ckeys who make it past user connection #
# Case is not important for ckey. #
###############################################################################################
-#Optimumtact
-#kevinz000
-#Iamgoofball
-#Rshoe95
-#That one penguin who became an admin
+#Crossedfall
diff --git a/dependencies.sh b/dependencies.sh
index f463cc0520126..4adfbd036c54b 100755
--- a/dependencies.sh
+++ b/dependencies.sh
@@ -4,22 +4,24 @@
#Final authority on what's required to fully build the project
# byond version
-# Extracted from the Dockerfile. Change by editing Dockerfile's ARG commands. Otherwise, this is set by Docker's envrionment variables
-if [[ -f "Dockerfile" ]]; then
- LIST=($(sed -n 's/.*byond:\([0-9]\+\)\.\([0-9]\+\).*/\1 \2/p' Dockerfile))
- export BYOND_MAJOR=${LIST[0]}
- export BYOND_MINOR=${LIST[1]}
- unset LIST
-fi
+export BYOND_MAJOR=514
+export BYOND_MINOR=1568
+
+#rust version
+export RUST_VERSION=1.54.0
#rust_g git tag
-export RUST_G_VERSION=0.4.5.2
+export RUST_G_VERSION=0.4.7.1
#node version
export NODE_VERSION=12
-
-# PHP version
-export PHP_VERSION=7.2
+export NODE_VERSION_PRECISE=12.20.0
# SpacemanDMM git tag
-export SPACEMAN_DMM_VERSION=suite-1.6
+export SPACEMAN_DMM_VERSION=suite-1.7
+
+#auxmos version
+export AUXMOS_VERSION=0.2.4
+
+# Python version for mapmerge and other tools
+export PYTHON_VERSION=3.6.8
diff --git a/html/admin/banpanel.js b/html/admin/banpanel.js
index cc9af09854930..41339df3454b6 100644
--- a/html/admin/banpanel.js
+++ b/html/admin/banpanel.js
@@ -14,3 +14,19 @@ function toggle_checkboxes(source, ext) {
}
}
}
+
+function suppression_lock(activator) {
+ var state = activator.checked
+ var restricted_elements = document.getElementsByClassName("redact_incompatible")
+ for (var i=0, n = restricted_elements.length; i < n; i++) {
+ restricted_elements[i].checked = false;
+ restricted_elements[i].disabled = state;
+ }
+ if(!state) {
+ return;
+ }
+ var force_enabled = document.getElementsByClassName("redact_force_checked");
+ for (var i=0, n = force_enabled.length; i < n; i++) {
+ force_enabled[i].checked = true;
+ }
+}
diff --git a/html/antagtips/abductor.html b/html/antagtips/abductor.html
index 985d8bb533388..3ca713e3ac4ec 100644
--- a/html/antagtips/abductor.html
+++ b/html/antagtips/abductor.html
@@ -1,13 +1,13 @@
-
-
-
You are an Abductor!
-
-
You and your teammate have been chosen by the mothership to go and capture some humans.
-
There's two of you: the scientist, who must oversee the camera and surgery, and the agent, who must go down on the station to stun and cuff people.
-
The scientist must utilize his console and science tool, set to marking mode, to send down the agent, go down himself, mark the target, retrieve it, mark the agent through the console and retrieve him too.
-
After that, he must do the experimentation surgery.
-
The agent must be sent down by the scientist to stun and cuff a target and drag it into a hidden spot and await the scientist.
-
People wearing tinfoil hats are immune to abductor batons.
-
For further information visit https://wiki.beestation13.com/view/Abductor
-
-
+
+
+
You are an Abductor!
+
+
You and your teammate have been chosen by the mothership to go and capture some humans.
+
There's two of you: the scientist, who must oversee the camera and surgery, and the agent, who must go down on the station to stun and cuff people.
+
The scientist must utilize his console and science tool, set to marking mode, to send down the agent, go down himself, mark the target, retrieve it, mark the agent through the console and retrieve him too.
+
After that, he must do the experimentation surgery.
+
The agent must be sent down by the scientist to stun and cuff a target and drag it into a hidden spot and await the scientist.
+
People wearing tinfoil hats are immune to abductor batons.
+
For further information visit https://wiki.beestation13.com/view/Abductor
As the blob, your objective is to reach a critical mass of 400 blobs. Shortly, you will be able to place your Blob Core.
-
Protect your core at all costs. If it is destroyed, you will die and be unable to create any more blobs. Ensure it is placed in an easily defendable or secluded area.
-
Check the interface at the bottom of your screen to create special kinds of blobs.
-
Node blobs slowly expand around them, heal other blobs, and activate blob factories and resource nodes.
-
Power is crucial to your success as it allows you to expand outwards. To create more power, place Resource blobs.
-
Summon Blob Spores using Factory Blobs. Blob spores attack nearby humans, and transform corpses into Blob Zombies.
-
Your Blob Strain will have different effects of your attacks. You begin with a free strain reroll, which is regained after a cooldown.
-
For further information visit https://wiki.beestation13.com/view/Blob
-
-
+
+
+
You are a Blob!
+
+
You are the overmind of the blob.
+
As the blob, your objective is to reach a critical mass of 400 blobs. Shortly, you will be able to place your Blob Core.
+
Protect your core at all costs. If it is destroyed, you will die and be unable to create any more blobs. Ensure it is placed in an easily defendable or secluded area.
+
Check the interface at the bottom of your screen to create special kinds of blobs.
+
Node blobs slowly expand around them, heal other blobs, and activate blob factories and resource nodes.
+
Power is crucial to your success as it allows you to expand outwards. To create more power, place Resource blobs.
+
Summon Blob Spores using Factory Blobs. Blob spores attack nearby humans, and transform corpses into Blob Zombies.
+
Your Blob Strain will have different effects of your attacks. You begin with a free strain reroll, which is regained after a cooldown.
+
For further information visit https://wiki.beestation13.com/view/Blob
You are a Changeling, a shapeshifting alien assuming the form of a crewmember on Space Station 13.
-
Changelings can anonymously communicate with one another over a hivemind channel by using 'say :g' before their speech.
-
The goal of many changelings is to acquire 5-7 DNA strains. To do this, it must take ANY human, living or dead, and acquire their DNA.
-
Use the DNA Extraction Sting and sting a target to stealthily steal their DNA. This will count towards your objective.
-
Alternatively, you can absorb humans to drain their DNA. Have them in an aggressive grab and click the absorb button on the top left of the screen.
-
Use the Cellular Emporium to acquire special abilities which will help you achieve your objectives.
-
Absorbing a human will allow you to readapt and purchase different abilities.
-
Work together with the other changelings to complete your objectives, but be cautious. Other changelings could have an objective to absorb you.
-
For further information visit https://wiki.beestation13.com/view/Changeling
-
+
+
+
You are a Changeling!
+
+
You are a Changeling, a shapeshifting alien assuming the form of a crewmember on Space Station 13.
+
Changelings can anonymously communicate with one another over a hivemind channel by using 'say :g' before their speech.
+
The goal of many changelings is to acquire 5-7 DNA strains. To do this, it must take ANY human, living or dead, and acquire their DNA.
+
Use the DNA Extraction Sting and sting a target to stealthily steal their DNA. This will count towards your objective.
+
Alternatively, you can absorb humans to drain their DNA. Have them in an aggressive grab and click the absorb button on the top left of the screen.
+
Use the Cellular Emporium to acquire special abilities which will help you achieve your objectives.
+
Absorbing a human will allow you to readapt and purchase different abilities.
+
Work together with the other changelings to complete your objectives, but be cautious. Other changelings could have an objective to absorb you.
+
For further information visit https://wiki.beestation13.com/view/Changeling
+
\ No newline at end of file
diff --git a/html/antagtips/cult.html b/html/antagtips/cult.html
index 93d05bf3e5170..ae16fffd6d745 100644
--- a/html/antagtips/cult.html
+++ b/html/antagtips/cult.html
@@ -1,14 +1,14 @@
-
-
-
You are a Cultist!
-
-
You are a follower of the geometer of blood, Nar'Sie!
-
In your bag, you will find some Runed Metal and a Ritual Dagger.
-
Use your Ritual Dagger to create runes. Each rune has a unique function.
-
Use the Runed Metal to create cult structures which will produce powerful equipment.
-
To begin, you should gather converts by placing them over an Offering Rune.
-
There will be several other cultists on board, communicate with them using the commune button. Find a place to create a base, and keep cult structures hidden.
-
Before you can summon Nar'Sie, you must first sacrifice a target for her on an offering rune. Check the top right of your screen for information on your target.
-
For further information visit https://wiki.beestation13.com/view/Cult
-
-
+
+
+
You are a Cultist!
+
+
You are a follower of the geometer of blood, Nar'Sie!
+
In your bag, you will find some Runed Metal and a Ritual Dagger.
+
Use your Ritual Dagger to create runes. Each rune has a unique function.
+
Use the Runed Metal to create cult structures which will produce powerful equipment.
+
To begin, you should gather converts by placing them over an Offering Rune.
+
There will be several other cultists on board, communicate with them using the commune button. Find a place to create a base, and keep cult structures hidden.
+
Before you can summon Nar'Sie, you must first sacrifice a target for her on an offering rune. Check the top right of your screen for information on your target.
+
For further information visit https://wiki.beestation13.com/view/Cult
The syndicate has provided you with a radio uplink with 30 telecrystals.
-
You must detonate the nuclear bomb from your ship on Space Station 13. To do that, you must get the nuclear disk.
-
To activate the nuke, you must put in the disk, type in the code, turn off the safety and enable it. You should first unanchor it, move it to the station, re-anchor it and then activate the nuke and take the disk away.
-
As a Nuclear Operative, you have different options for purchase such as the Mauleror the Energy Shield.
-
The leader of the team starts out with a war declaration. If activated, he'll be prompted to write a custom message and declare war.
-
Declaring war stops you from going to the station for 20 minutes, alerts the crew and gives you 300 telecrystals.
-
For further information visit https://wiki.beestation13.com/view/Operative
-
-
+
+
+
You are a Nuclear Operative!
+
+
The syndicate has provided you with a radio uplink with 30 telecrystals.
+
You must detonate the nuclear bomb from your ship on Space Station 13. To do that, you must get the nuclear disk.
+
To activate the nuke, you must put in the disk, type in the code, turn off the safety and enable it. You should first unanchor it, move it to the station, re-anchor it and then activate the nuke and take the disk away.
+
As a Nuclear Operative, you have different options for purchase such as the Mauleror the Energy Shield.
+
The leader of the team starts out with a war declaration. If activated, he'll be prompted to write a custom message and declare war.
+
Declaring war stops you from going to the station for 20 minutes, alerts the crew and gives you 300 telecrystals.
+
For further information visit https://wiki.beestation13.com/view/Operative
The syndicate has provided you with a disguised uplink. It can either be your PDA, your headset, your pen or an attachable hand accessory that's currently stored in your bag.
-
The details of your objective are stored within your notes, to see them use the Notes verb.
-
To utilize your PDA uplink, enter the messenger tab and set the ringtone as the code you have been provided.
-
To utilize your Headset uplink, change its frequency to the frequency provided.
-
To utilize the pen uplink, twist it to the first setting, then to the second one.
-
The uplink starts out with 20 telecrystals which are utilized to purchase different items to aid you in fulfilling your objectives.
-
The Syndicate has also given you and any other agents on board code-words which can be used to find eachother. They're highlighted in red and blue.
-
Try fulfilling your objectives, but don't forget to create fun situations for both you and the crew.
-
For further information visit https://wiki.beestation13.com/view/Traitor
-
+
+
+
You are a Traitor!
+
+
The syndicate has provided you with a disguised uplink. It can either be your PDA, your headset, your pen or an attachable hand accessory that's currently stored in your bag.
+
The details of your objective are stored within your notes, to see them use the Notes verb.
+
To utilize your PDA uplink, enter the messenger tab and set the ringtone as the code you have been provided.
+
To utilize your Headset uplink, change its frequency to the frequency provided.
+
To utilize the pen uplink, twist it to the first setting, then to the second one.
+
The uplink starts out with 20 telecrystals which are utilized to purchase different items to aid you in fulfilling your objectives.
+
The Syndicate has also given you and any other agents on board code-words which can be used to find eachother. They're highlighted in red and blue.
+
Try fulfilling your objectives, but don't forget to create fun situations for both you and the crew.
+
For further information visit https://wiki.beestation13.com/view/Traitor
+
\ No newline at end of file
diff --git a/html/antagtips/wizard.html b/html/antagtips/wizard.html
index a965219222836..71e458b8b3a1c 100644
--- a/html/antagtips/wizard.html
+++ b/html/antagtips/wizard.html
@@ -1,12 +1,12 @@
-
-
-
You are a Wizard!
-
-
You are a wizard sent by the Wizard Federation.
-
You have been granted a spellbook with 10 spellpoints that you can spend to buy new spells. It can only be used on board of the Wizard's den.
-
Use your scroll of teleportation to get to the station.
-
The details of your objective are stored within your notes, to see them use the Notes verb.
-
Spells can be upgraded by putting more points into them or refunded.
-
For further information visit https://wiki.beestation13.com/view/Wizard
-
-
+
+
+
You are a Wizard!
+
+
You are a wizard sent by the Wizard Federation.
+
You have been granted a spellbook with 10 spellpoints that you can spend to buy new spells. It can only be used on board of the Wizard's den.
+
Use your scroll of teleportation to get to the station.
+
The details of your objective are stored within your notes, to see them use the Notes verb.
+
Spells can be upgraded by putting more points into them or refunded.
+
For further information visit https://wiki.beestation13.com/view/Wizard
You can now replace test range firing pins with any other pin.
-
The plasma cutter is now a gun that comes in the mining vending machine. Use it to mine fast but expensively.
-
It's also cheaper in RnD, and there's an advanced version of it there too. Yay!
-
Ripleys and Firefighters have been buffed significantly. Go wild.
-
Precious mesons are cheaper in RnD.
-
Now you can mine bluespace crystals. They are extremely rare.
-
You can make remote bombs out of gibtonite. Figure out how.
-
Gibtonite is a bit less stable. Do not hit it with anything heavy.
-
-
Fayrik updated:
-
-
Added a bowman headset for death squads and ERTs.
-
Deathsquad radio channel is now called the Centcom radio channel.
-
Fixed the admin buttons not spawning ERT Medic gear properly.
-
Expanded Emergency Shuttle dock to allow for more round end griff.
-
All 7 ERT members now spawn geared, not just the first 4.
-
Spawning an ERT or Deathsquad no longer stops a second ERT or Deathsquad from being spawned.
-
Pulse rifles now have 80 shots, not 40.
-
Deathsquads now use loyalty pinned Pulse rifles.
-
-
Gun Hog updated:
-
-
NTSL has been updated! It can now read and modify verbs (says, yells, asks)! You can set your own using $say, $yell, $ask, and $exclaim variables in your scripts.
-
NTSL can now also modify the font, italics, and bolding of radio messages in four ways! In a script, place $loud, $wacky, $emphasis, and/or $robot in a vector to $filters.
-
-
Jordie0608 updated:
-
-
The admin's old player panel has been removed and the new one has taken it's name.
-
-
NikNakFlak updated:
-
-
Adds the ability to shave corgis.
-
-
Sawu updated:
-
-
Spraycans for Revheads: instant use crayons that can be used on walls.
-
Spraycans can be used as ghetto pepper spray and leave their target with colored faces that can be removed with space cleaner.
-
-
Xhuis updated:
-
-
Area ambience and ship ambience are now separate toggles. If you want one type of ambience but not the other, you only need to switch off the type you don't want.
-
Added cigar cases. They come in three flavors and can be bought from cigarette machines.
-
-
-
30 March 2015
-
AnturK updated:
-
-
Added new wizard spell : Lightning Bolt
-
-
Dannno updated:
-
-
Added a hammer and gavel. Court is now in session.
-
-
Iamgoofball updated:
-
-
You can now *flip.
-
Objects now spin when thrown. Please report any oddities.
-
-
RemieRichards updated:
-
-
Monkeys can now wear ANY mask, not just ones they had specific icons for.
-
Monkeys can now wear HATS, That's right, Monkey Hats!
-
-
Szunti updated:
-
-
Mining mobs vision adapted to darkness of the asteroids.
-
-
phil235 updated:
-
-
All slimes are now simple animals instead of carbon life forms.
-
Remove crit status from brain and slimes.
-
Simple animal aliens can now see in the dark like regular aliens.
-
-
pudl updated:
-
-
The detective's forensic scanner has been resprited.
-
So has the cargo tagger.
-
And the RPED.
-
-
tedward1337 updated:
-
-
Admins now have a button to use Bluespace Artillery at their will.
-
-
xxalpha updated:
-
-
You will no longer be slowed down by anything if you use a jetpack in zero gravity.
-
Engineering hardsuits come with an inbuilt jetpack. Requires an internals tank in suit storage and is slower than a normal jetpack.
-
-
-
25 March 2015
-
AnturK updated:
-
-
Added an arrow graffiti, made arrow and body graffiti face the same way as the user
-
New changeling ability : Last Resort : Explode and infect corpses if the situation looks grim
-
-
Fayrik updated:
-
-
Deleted the toy crossbow that wasn't a gun, but flung projectiles like a gun. Why was that even a thing?
-
Added an abstract new type of reusable ammo.
-
Added three new foam force guns and a foam force crossbow, all of which take the new foam dart ammo.
-
Added foam force dart boxes to the autolathe, and two crates orderable from cargo containing the new guns.
-
Added two new donksoft guns, the syndicate's own brand of Foam Force. Available in all good uplinks!
-
New crossbow can be won as an arcade prize.
-
All kinds of ammo containers can now be used to rapidly collect live ammunition on the ground.
-
Speedloaders and traditional ammo boxes can now be restocked.
-
Security bots now react to being shot.
-
-
MrPerson updated:
-
-
Added a preference toggle for hearing instruments play as suggested by PKPenguin321.
-
-
Pennwick updated:
-
-
Added a buildable Chem Master, board is in Circuit Imprinter.
-
-
Wjohnston updated:
-
-
Finally fixed the gulag shuttle missing a redemption console.
-
-
-
24 March 2015
-
Cheridan updated:
-
-
Adds pet collars. Use them to rename pets; Can also be worn if you're a weirdo.
-
Adds the matyr objective to be dead at the round's end. You can get this objective if you have no others that would need you to survive.
-
-
Dannno updated:
-
-
GAR glasses return. Yes, I got permission. Just who the hell do you think I am?
-
-
Incoming5643 updated:
-
-
Summon Events has been reworked to be a bit less manic.
-
Summon Events will turn off if the wizard and all apprentices die. Any lingering effects are not reversed however.
-
Improving Summon Events will reduce the average time between events, but not the minimum time between events.
-
Departmental Uprising has been made admin only for being an RP heavy event that creates antags in an enviroment that doesn't really support that.
-
-
Mandurrrh updated:
-
-
Added a cyborg upgrade module for the satchel of holding.
-
-
Miauw updated:
-
-
Added a fancy white dress and a jester outfit. Sprites by Nienhaus.
-
-
Steelpoint, RemieRichards, and Gun Hog updated:
-
-
Nanotrasen Security has authorized modifications to the standard issue helmets for officers. These helmets are now issued with a mounted camera, automatically synced to your assigned station's camera network. In addition, the helmets also include mounting points for the Seclite model of flashlights, found in your station's security vendors.
-
Nanotrasen Security helmets are designed for easy repairs in the field. Officers may remove their Seclite attachment with any screwdriver, and the camera's assembly may be pried off with a crowbar. The assembly is also compatible with analyzer upgrades for scanning through the station's structure, and EMP shielding is possible by applying a sheet of plasma.
-
-
-
22 March 2015
-
Iamgoofball updated:
-
-
Fixes Morphine not knocking you out.
-
Hotline no longer exists. Poppies now have Saline-Glucose Solution.
-
Muriatic Acid, Caustic Soda, and Hydrogen Chloride have been removed, along with the secondary meth recipe. The original recipe still works.
-
Foam now produces more bang for your buck when mixed. Your foam grenades should be larger now to account for the slowdown.
-
Adds Stabilizing Agent. This chemical will prevent the effects of certain reactions from taking place in the container it is in, such as Smoke Powder and Flash Powder, allowing you to get the raw forms of the powders for heating up later.
-
Smoke Powder and Flash Powder can now be mixed along with Stabilizing Agent in order to produce a powder version of the effects. You can then heat these powders to 374K to get the effects.
-
Mixing them without Stabilizing Agent gives the default effects.
-
Adds Sonic Powder, a chemical that deafens and stuns within 5 tiles of the reaction. It also follows the reaction rules of the other 2 powders.
-
Liquid Dark Matter and Sorium also now work like the powders do.
-
CLF3 now has a higher chance of making plating tiles, and the burn damage caused by it scales based on your fire stacks.
-
Adds Pyrosium. This reagent heats up mobs by 30 degrees every 3 seconds if it has oxygen to react with. Useful for when you want a delayed mix inside of someone.
-
Adds Cryostylane. This reagent does the opposite of Pyrosium, but it still requires oxygen to react with.
-
Adds Phlogiston. This reagent ignites you and gives you a single fire stack every 3 seconds. Burn damage from this chemical scales up with the fire stacks you have. Counter this with literally any chemical that purges other chems.
-
Smoke now transfers reagents to the mob, and applies touch reactions on them. This means smoke can run out of reagents, no more infinite acid smoke. It also only works on mobs, use the new Reagent Foam(tm) for your hellfoam needs. I suggest a CLF3 Fluoroacid Black Powder mix.
-
Added a derelict medibot to the code, will place a few on the derelict when this PR is merged as to prevent mapping conflicts.
-
Adds Initropidril. 33% chance to hit with 5-25 TOX damage every 3 seconds, and a 5-10% chance to either stun, cause lots of oxygen damage, or cause your heart to stop. Get it from the traitor Poison Kit.
-
Adds Pancuronium. Paralyses you after 30 seconds, with a 7% chance to cause 3-5 loss of breath. Get it from the traitor Poison Kit.
-
Adds Sodium Thiopental. Knocks you out after 30 seconds, and destroys your stamina. Get it from the traitor Poison Kit.
-
Adds Sulfonal. +1 TOX per 3 seconds, knocks you out in 66 seconds. Mix it with Acetone, Diethylamine, and Sulfur.
-
Adds Amantin. On the last second it is in you, it hits you with a stack of TOX damage based on how long it's been in you. Get it from the traitor Poison Kit.
-
Adds Lipolicide. +1 TOX unless you have nutriment in you. Mix it with Mercury, Diethylamine, and Ephedrine.
-
Adds Coiine. +2 TOX and +5 loss of breath every 3 seconds. Get it from the traitor Poison Kit.
-
Adds Curare. +1 TOX, +1 OXY, paralyzes after 33 seconds. Get it from the traitor Poison Kit.
-
Adds a reagent_deleted() proc to reagents for effects upon the last time it processes in you, like with Amantin.
-
Neurotoxin required temperature for mixing has been set to 674K, it was 370K for some reason.
-
-
xxalpha updated:
-
-
Added Cybernetic Implants: Security HUD, Medical HUD, X-Ray, Thermals, Anti-Drop, Anti-Stun. All implants are researchable and producible by R&D and Robotics.
-
-
-
21 March 2015
-
Chocobro updated:
-
-
Checkered skirts can be adjusted to be worn shorter.
-
-
Incoming5643 updated:
-
-
Player controller uplifted mobs will be attacked by gold core mobs of different species.
-
-
Jordie0608 updated:
-
-
The backup shuttle will no longer be called if the emergency shuttle is coming.
-
-
MMMiracles updated:
-
-
Adds 3 new bear-based foods.
-
Beary Pie -1 Plain Pie, 1 berry, 1 bear steak
-
Filet Migrawr - 5u of Manlydorf, 1 bear steak, 1 lighter(not used in crafting)
-
Bearger - 1 bun, 1 bear steak
-
-
Phil235 updated:
-
-
Silicons are no longer blinded by welding.
-
-
Xhuis updated:
-
-
A new software update has given the Orion Trail game a cheat code menu. While you can't access this normally (that's for a later software update), maybe an emag could do it...?
-
-
Zelacks updated:
-
-
The chemical implant action button should now correctly inject all chemicals when pressed.
-
-
pudl updated:
-
-
Adds normal security headsets to security lockers.
-
-
xxalpha updated:
-
-
Fixed cables on catwalks being destroyed when creating plating floor.
-
Portable machines can be mounted on any turf that could hold a cable.
-
-
-
19 March 2015
-
Dannno updated:
-
-
Adds a few cosmetic glasses to the autodrobe and clothing vendors.
-
-
Gun Hog updated:
-
-
Explosion relics will now destroy themselves in their own explosion.
-
The Experimentor can no longer re-roll the function of an already scanned relic.
-
-
Incoming5643 updated:
-
-
Ghosts are now asked if they want to become a sentient slime.
-
-
JJRcop updated:
-
-
Adds the :rollie: and :ambrosia: emojis.
-
-
MMMiracles updated:
-
-
ERT suits now use helmet toggling like hardsuits.
-
-
Menshin updated:
-
-
Fixed mechs ballistic guns not firing bullets.
-
-
Xhuis updated:
-
-
Allows alt-clicking to adjust breath masks, flip caps, and unlock/lock closets.
-
-
xxalpha updated:
-
-
Fixed jetpack trail animation.
-
-
-
17 March 2015
-
CandyClownTG updated:
-
-
Syndicate cigarettes' non-healing Doctor's Delight replaced with Omnizine.
-
-
Fayrik updated:
-
-
Expanded the Centcom area for better station-Centcom interaction.
-
-
Iamgoofball updated:
-
-
Acid blobs are less likely to melt equipment.
-
-
MMMiracles updated:
-
-
Adds the ability to spike bears on a meatspike for their pelt. Yields 5 meat instead of the usual 3 from a knife.
-
-
Thunder12345 updated:
-
-
Two new shotgun shells. Ion shell, shotgun version of ion rifle, built with 1 x techshell, 1 x ultra microlaser, 1 x ansible crystal.
-
Laser slug, regular laser shot in a shotgun shell, build with 1 x techshell, 1 x high power microlaser, 1 x advanced capacitor.
-
Improvised shotgun shells can now be constructed with 1 x grenade casing, 1 x metal sheet, 1 x cable, 10 units welding fuel. Can be packed with a further 5 units of gunpowder to turn it into a potentially lethal diceroll.
-
FRAG-12 shotgun shells are no longer duds, now explosive.
-
-
phil235 updated:
-
-
Makes the message when you're attacked slightly bigger for better visibility.
-
-
-
15 March 2015
-
Ahammer18 updated:
-
-
Adds ass photocopying for drones
-
-
Gun Hog updated:
-
-
Nanotrasen has approved distribution of Loyalty Implant Firing Pin designs. They can be fabricated with sufficient research into weapons technology. If inserted into a firearm, it will only fire upon successful interface with a user's Loyalty Implant.
-
-
Incoming5643 updated:
-
-
Utilizing a new magic mirror found in the den, wizards can now customize their appearance to a high degree before descending upon the station.
-
-
Miauw updated:
-
-
Firing delay for tasers has been shortened.
-
-
RemieRichards updated:
-
-
Beginning major rework and refactor of messy Ninja code.
-
Roleplay based, kill deathsquad and kill alien queen objectives removed.
-
Kamikaze mode removed.
-
AIs and pAIs can no longer be integrated into a ninja suit.
-
Energy blade power replaced by an energy katana that can be dropped but is more deadly.
-
Energy katana can emag any object it hits.
-
-
phil235 updated:
-
-
Bookbags have a new sprite and can hold bibles.
-
Adds egg yolk reagent. You can break eggs in open reagent containers or blend them to get egg yolk.
-
Changes how doughs are made. 10water+15flour chem reaction for dough. 15flour+15eggyolk+5sugar for cake batter (or alternatively soymilk instead of the eggyolk for vegans).
-
Change customizable snack max volume to 60 and buffs nutriment amount in doughs.
-
Doors can damage mechs by crushing them now.
-
The tablecrafting window now also shows partial recipes in grey, if there's some of the things required for the recipe on the table. Recipe requirements are now also shown next to the names of recipes listed in the window.
-
-
-
11 March 2015
-
MMMiracles updated:
-
-
Adds a new vending machine, the Liberation Station. Consult your local patriot for more details.
-
Adds a patriotic jumpsuit and freedom sheets. Consult your local patriot for more details.
-
-
Miauw updated:
-
-
Added a toggle for ghosts that allows them to hear radio chatter without having to hover near intercoms/over people with headsets.
-
-
-
10 March 2015
-
Ahammer18 updated:
-
-
Adds the Slimecake and Slimecake slice.
-
-
AnturK updated:
-
-
Non-player Alien Queens/Drones now spread weeds. Can also lay eggs when enabled.
-
-
Incoming5643 updated:
-
-
Nanotrasen scientists have recently reported mutations within the pink family line of slimes. Station scientists are encouraged to investigate.
-
-
Paprika updated:
-
-
Reworked reskinning and renaming guns, and added the ability to reskin the bartender's shotgun. Click it with an empty active hand to reskin. Warning, you can only do this once! Rename by hitting it with a pen. This also works with miner shotguns now.
-
-
RemieRichards updated:
-
-
Embedding from explosions has been tweaked, they will always throw objects fast enough to get embedded if they can.
-
Damage taken when embedded spears and throwing stars fall out has been increased.
-
Damage dealt by objects getting embedded has been decreased.
-
-
Sometinyprick updated:
-
-
The Horsemask spell has now been altered, instead of just a horse mask it will now choose between three animal masks including the horse mask.
-
-
Xhuis updated:
-
-
You can now burn papers using lighters, welders, or matches. Useful for secret messages.
-
-
phil235 updated:
-
-
Airlocks with their safety on no longer closes on dense objects (like mechs or tables).
-
Aliens, slimes, monkeys and ventcrawler mobs can now climb into disposal. Cyborgs and very large mobs can no longer climb into it.
-
Stuffing another mob in a disposal unit is now only done via grabbing.
-
Coffee now causes jittering only when you overdose on it.
-
Turrets now target occupied mechs and don't target drones.
-
-
-
08 March 2015
-
Dannno updated:
-
-
Added more barsigns, sprites by yours truly.
-
-
Ikarrus updated:
-
-
Gang mode has been reworked to functionality without some of it's features.
-
Gang membership visibility, conversion pens and weapons are removed until fixed.
-
Gang bosses have been given uplinks to use in the meantime.
-
Chance of deconversion from repeated head trauma has been increased.
-
-
Incoming5643 updated:
-
-
The skeleton and zombie hordes have been quelled, at least for the time being.
-
-
Mandurrrh updated:
-
-
Added the ability to stop table climbers by clicking the table if present.
-
-
Paprika updated:
-
-
Labcoats and uniforms no longer use action buttons and object verbs to adjust their aesthetic style. Alt click them to adjust.
-
Alt click a PDA with an ID inside to eject the ID.
-
Renamed 'remove ID' verb to 'eject ID' so it's not immediately next to 'remove pen'.
-
Rebalanced the flash protection of thermal scanners. Syndicate thermals have the same flash weakness as the meson scanners they come disguised as.
-
-
Xhuis updated:
-
-
Adds the Adminbus and Coderbus bar signs.
-
Added a mining satchel of holding to R&D that can hold an infinite amount of ores. It requires gold and uranium, in addition to a decently high bluespace and materials research.
-
-
phil235 updated:
-
-
Harvesting meat from dead simple animals now takes time, has a sound, and can be done with any sharp weapon.
-
-
pudl updated:
-
-
Updated the sprite for Fire extinguishers.
-
-
xxalpha updated:
-
-
Disposable lighters now come in more colors of the rainbow.
-
-
-
05 March 2015
-
Jordie0608 updated:
-
-
Server hosts can set a config option to make an unseen changelog pop up on connection.
-
-
MrPerson updated:
-
-
Expanded the on-screen alerts you get on the top-right of your screen. You can shift-click them to get a description of what's wrong and maybe how to fix it.
-
Getting buckled to something will show an alert of what you're buckled to. If you're not handcuffed, you can click the alert to unbuckle.
-
-
RemieRichards updated:
-
-
Added Necromantic Stones as a buyable item for wizards
-
Necromantic Stones can cause up to 3 people to rise up as Skeleton Thralls bound to the wielder of the stone
-
Skeleton Thralls have a 33% Chance to drop all their gear on the floor and autoequip a more fitting thematic uniform
-
Allows the blob to reroll it's chemical for a cost.
-
-
phil235 updated:
-
-
Adds a lot of new plants to botany (red beet, parsnip, snap corn, blumpkin, rice, oat, vanilla, steel cap, mimana, blue cherries, holy melon, geranium, lily, sweet potato) and two new reagents (vanilla, rice).
-
Fixes taser stun duration being longer due to jitter animation.
-
-
-
03 March 2015
-
Delimusca updated:
-
-
Improvised heating! use lighters or other heat sources to heat beakers, bottles, etc.
-
-
Gun Hog updated:
-
-
We have purged the xenomicrobes contamination in the E.X.P.E.R.I-MENTOR using sulfuric acid. Take care not to be exposed should it leak!
-
-
Iamgoofball updated:
-
-
Colorful reagent can be washed off with water now.
-
Oxygen blob deals breath loss damage.
-
Radioactive blobs irradiate for more.
-
-
Ikarrus updated:
-
-
Antagonist roles are now restricted by player client age.
-
-
Jordie0608 updated:
-
-
The server's current local time is now displayed in status panel in ISO date format (YYYY-MM-DD hh:mm)
-
-
Mandurrrh updated:
-
-
Added the ability to see pda messages as a ghost with GHOSTWHISPER toggled on.
-
Removed the degrees symbol from Kelvin units on atmos meters because not gramatically correct
-
-
RemieRichards updated:
-
-
Drones now see either Static, Black icons or Roguelike style lettering instead of actual mob icons
-
Icons for the above, from VG's SkowronX
-
Many icon procs for the above, from VG's ComicIronic
-
Drones moved into their own folder
-
Drone.dm replaced with seperate files for each functionality
-
Drones now have 2 skins to choose from, Maintenance Drone (Current drones) and Repair Drone (A modified VG/Bay sprite)
-
Much more support for alternate skins on drones
-
-
Xhuis updated:
-
-
Added four new emoji: :1997:, :rune:, :blob:, and :onisoma:.
-
-
xxalpha updated:
-
-
Added a surgery table and tools to the Nuke Ops ship.
-
Added Engineering Scanner Goggles to Engivend, Engineering Secure Closets and Research.
-
Doubled T-ray scanner range.
-
-
-
01 March 2015
-
Dannno updated:
-
-
Resprited all owl items. Wings are now a seperate toggleable suit item.
-
Added The Griffin costume, arch nemesis of The Owl. Found in the autodrobe.
-
Added admin-only Owl hardsuit. Only for the most vengeful souls.
-
Added Owl and Griffin merchandise; The Nest barsign, Owl/Griffin toys and posters.
-
-
Iamgoofball updated:
-
-
Fixes the recipe for meth.
-
Fixes sorium blobs flinging themselves.
-
-
Incoming5643 updated:
-
-
On servers where malfunction, wizard, and blob rounds do not end with the death of the primary antagonist, new antagonists from traitor and changeling derivative modes will now sometimes spawn. This depends on the state of the station, the state of the crew, and the server's gamemode probabilities. Server owners should consider revising their configuration choices
-
If you would like to opt out of this midround chance, a new toggle to disable it can be found in your preferences
-
-
Mandurrh updated:
-
-
The barsign can be emagged and EMP'd; EMP'd signs can be repaired with a screwdriver and cables.
-
-
Menshin updated:
-
-
Fixed the protolathe not requiring displayed amount of materials to make items.
-
-
MrStonedOne updated:
-
-
Adds a Panic Bunker!
-
This will prevent any player who has never connected before from connecting. (Admins are exempt)
-
Can be enabled per-round by any admin with +SERVER or for longer periods via the config.
-
Also adds some other config options relating to new joins, server operators should consult their configuration file for more details.
-
Fixes toggle darkness vision hiding other ghosts.
-
Admins fucking around with ghost icon and icon states in VV are going to want to change the icon and icon state of the ghostimage var of the ghost to change the image shown to ghosts who have darkness disabled. doing this via mass edit is not yet supported.
-
New ghost verb, toggle ghost vision. Switches the ghost's invisibility vision between that of a ghost and that of a human. So ghosts can hide other ghosts and in general get the old behaviour of toggle darkness.
-
-
Paprika updated:
-
-
Messages with two exclamation marks will be yelled in bold.
-
-
Phil235 updated:
-
-
Cultists can't summon narsie until their sacrifice objective is complete.
-
-
Razharas updated:
-
-
Vines now can grow on any tiles that is not dense, including space, shuttles, centcomm tiles and so on.
-
Added 'Bluespace' mutation to vines, makes them be able to grow through absolutely everything.
-
Added 'Space Proofing' mutation to vines, after they grow if the tile under them is space it will become special vinetile, which is just resprited regular floor, if the vine on the vinetile dies the vineturf changes back to space.
-
Made vines spreading speed depend on the seed's production, can be both slower and faster than current.
-
Made vines mutation chance be 1/10th of the potency of the seed it is spawned from.
-
Special chemicals added to vine seeds durning their growth can increase/decrease their potency and productivity.
-
Special chemicals now can remove good, bad or neutral mutations from vine seeds while they are growing, cultivating actually helpful vines is now possible.
-
Plant analyzers now show the vine seeds mutations.
-
Buffed numbers in some of the more useless mutations.
-
-
Steelpoint updated:
-
-
Reworked and expanded areas of the derelict station. Featuring a repairable gravity generator, turret filled computer core and functional solars.
-
-
Vekter updated:
-
-
Chemists, Geneticists, Virologists, Scientists, and Botanists will now spawn with alternative backpacks and satchels in their lockers. Try them out!
-
-
-
25 February 2015
-
Cheridan updated:
-
-
Foam now carries reagents, similarly to smoke. Have fun with that.
-
-
Dannno updated:
-
-
Altered the sprite for security hardsuits. The old sprite is now a HoS specific hardsuit.
-
-
Delimusca updated:
-
-
Turret's guns replaced by a heavy energy cannon.
-
Firing a heavy weapon sindlehandedly can make it recoil out of your hands.
-
-
Gun Hog updated:
-
-
Nanotrasen has released new design specifications for the prototype combination of standard night-vision equipment and Optical Meson Scanners. We believe that this will address the concerns many Shaft Miners have concerning visiblity in a potentially unsafe environment.
-
Fixed malf AI's turret upgrade power not working.
-
-
Iamgoofball updated:
-
-
You can now go into hyperglycaemic shock from having over 200u of Sugar in your body at once. Treat it with Insulin.
-
Insulin has been added. Use it to treat hyperglycaemic shock. You can get it from medical vendors and the Cargo medicine supplies crate.
-
Medvendor contents have been reduced from medical having everything they need at roundstart level, and toxin bottles are now back in the vendor.
-
Medical Analyzers now have 2 modes, Medical Scan and Chemical Scan. You can swap between modes by clicking on it in hand.
-
Medical scan is default behavior. Chemical scan shows all the reagents inside the mob at the time, along with any addictions the mob has. It will show if a reagent is overdosing.
-
Tobacco and Space Tobacco have Nicotine in them now.
-
Adds Methamphetamine to the game. Mix it with Ephedrine, Iodine, Phosphorous, and Hydrogen at 374K.
-
Or, look around in Maintenance for Muriatic Acid, Caustic Soda, and Hydrogen Chloride. Recipes are also available for these.
-
Adds Saltpetre. Mix it with Potassium, Nitrogen, and Oxygen.
-
Adds Bath Salts, mix it with Bad Food, Saltpetre, Nutriment, Space Cleaner, Universal Enzyme, Tea, and Mercury at 374K.
-
Adds Aranesp. Mix it with Epinephrine, Atropine, and Morphine.
-
Adds Hotline. Get it from Poppy plants.
-
Strange Reagent revival was changed. It now won't gib but won't do anything above a 100 BRUTE or BURN threshold.
-
Carpet now applies carpet tiles to whatever flooring it hits. Mix it with Space Drugs and Blood.
-
Colorful Reagent colors have been improved drastically and now are more varied and nicer looking.
-
Corn Starch has been added. Get it from juicing Corn.
-
Corn Syrup has been added. Mix it with Corn Starch and S-Acid.
-
Corgium has been added. Mix it with Nutriment, Colorful Reagent, Strange Reagent, and Blood at 374K.
-
Adds Quantum Hair Dye. Mix it with Colorful Reagent, Radium, and Space Drugs.
-
Adds Barber's Aid. Mix it with Carpet, Radium, and Space Drugs.
-
Adds Concentrated Barber's Aid. Mix it with Barber's Aid and Unstable Mutagen.
-
Re-adds Chlorine Trifluoride. Mix it with Chlorine and Fluorine at 424K.
-
Adds Black Powder. Mix it with Saltpetre, Charcoal, and Sulfur. Ignite it for an explosion at 474K
-
Finally nerfs Salglu Solution. It's no longer tricord 2.0.
-
Salt recipe is now Water + Sodium + Chlorine due to problems with recipe mixing.
-
-
Incoming5643 updated:
-
-
A new event has been added to the wizards summon events spell that gives the game a distictively more roleplaying flair.
-
Optional population cap functionality has been added to the game. By default all caps are disabled. Server owners should refer to config.txt to see their list of options.
-
Medical borgs now have access to a deployable onboard roller bed. Should the bed become lost, any roller bed may be used in its place.
-
-
Mandurrh updated:
-
-
Added interchangeable bar signs
-
Added bays fountain machines for the bar. Soda and Beer. Allowing bartenders to have easier access to mix drinks and making more to make their job a little less boring
-
Added color tiles to the light up floor tiles.
-
Added metal, glass, and cable to bar back room at round start for bartender to make colored light dance floors.
-
Fixes clown cuffing both mobs and two pairs of cuffs appearing.
-
Fixes clown not being able to use his stamp as antag so no meta.
-
Fixes sleepypen bug. Instead of only dispensing 50u it dispenses the full 55u.
-
Fixed silicons not being able to pass through holotape.
-
-
Menshin updated:
-
-
Getting into regenerative statis now puts the changeling into unconscious state.
-
Fixed changeling 'Revive' not working.
-
-
Paprika updated:
-
-
Added an assistant cap, unlimited by default.
-
Batons now slowly lose power when left on.
-
Batons, flashbangs and handcuffs can be seen when stored in a belt.
-
Computers now emit light when they're on or bluescreened.
-
Added hotkeys for OOC and custom emotes. Press O and M when in hotkey mode. Remember not to ick in ock!
-
Added megaphones for the HoS, HoP, and QM.
-
Goliath tentacles no longer stun you if you cross them. They will only stun you if you don't move away from them in time. They can do up to 15 damage now, in addition to stunning you.
-
Changed a lot of mining gear and prices. The resonator can now have up to 3 fields active at once, but the fields need to detonate before they crush rocks. Tweak the time before the fields detonate with a screwdriver and upgrade the amount of fields at once with sheets of diamond.
-
Overhauled straight mining tools. The sonic jackhammer is now the wall-crushing tool, the diamond drill simply mines really fast. Borgs can now get upgraded with the diamond mining tool with only a single sheet of diamond and an upgrade module, they don't need illegal tech modules.
-
Mining drills and the sonic jackhammer now draw power when they drill down mineral walls. They can be recharged using any type of weapon recharger or by replacing the cell using a screwdriver on them.
-
Space now emits a dim light to adjacent tiles.
-
Syndicate shotguns now start with buckshot instead of stunslugs.
-
Fixed welding tools using fuel when being switched on.
-
Added a firing delay between shooting electrodes.
-
Changed the stun revolver from a high-capacity taser to a disabler with extra capacity.
-
Added a unique stun pistol for the head of security. It is a double capacity taser with no shot delay and a loyalty pin, though it lacks a disable mode.
-
-
RandomMarine updated:
-
-
New brute treatment kit crate for cargo.
-
-
RemieRichards updated:
-
-
Explosions will now blow (throw) items away from the epicenter
-
Sharp items can now embedd themselves in a human's limbs
-
Embedded items have a chance to do some damage (based on w_class) each life() call
-
Embedded items have a chance to fall out doing some more damage, but removing the object
-
Adds a surgery to remove Embdedded objects
-
Adds throwing stars as an uplink item (100% chance to embedd)
-
Items placed on tables now center to exactly where you clicked.
-
-
Steelpoint updated:
-
-
SS13's Head of Security has received a new attempted 1:1 recreation of a Old Earth weapon, the Captain's Antique Laser Gun.
-
This gun features three firing modes, Tase, Laser and Disable. However this gun lacks the capability to recharge in the field, and is extreamly expensive.
-
INTERCEPTED TRANSMISSION REPORT: The Syndicate have expressed a interest in this new weapon, and have instructed field agents to do whatever they can to steal this weapon.
-
-
TheVekter updated:
-
-
Box's vault nuke has been replaced with immobile Nuclear Self-Destruct Mechanism.
-
Vault's new green floor tiles turn flashing red when Self-Destruct is activated.
-
-
Xhuis updated:
-
-
Syndicate contacts have discovered new ways to manipulate alarms. Malfunctioning AIs, as well as normal emags, can now disable the safeties on air and fire alarms.
-
When one emags an air alarm, its safeties are disabled, giving it the Flood environmental type, which disables scrubbers as well as vent pressure checks. Burn, pigs, burn!
-
When one emags a fire alarm, its thermal sesnsors are disabled. This will prevent automatic alerts due to the alarm being unable to recognize high temperatures.
-
Malf AIs gain two new modules, both of which disable ALL air alarm safeties and thermal sensors. These are separate abilities.
-
Air and fire alarms have notifications on their interface about the current safety status. One only has to glance at one to see that it's been tampered with.
-
-
phil235 updated:
-
-
Fixed arcade machines being usable from inside lockers to dupe rewards.
-
Silicons can no longer be grabbed by anyone. No more silicon choking
-
Custom food can now be renamed using a pen.
-
-
xxalpha updated:
-
-
Added new graffiti: cyka, prolizard, antilizard
-
-
-
18 February 2015
-
Dannno updated:
-
-
Added proper slurring when drunk.
-
-
Deantwo updated:
-
-
Updated the ChemMaster's UI to the station default along with some functionality improvements.
-
You can now spam the 'Ready' button as much as you want without it setting you 'Not ready' again.
-
-
Dorsidwarf updated:
-
-
Added the ability to call Central Command for the nuclear authentication codes. This requires a captain-level ID and admin approval, and warns the crew.
-
-
Gun Hog updated:
-
-
E.X.P.E.R.I-MENTOR meteor storm event replaced with a fireball that targets random nearby mob.
-
EMP event range reduced.
-
Self-duplicating relics limited to 10 copies.
-
Nanotrasen has released a firmware patch for the Ore Redemption Machine, which now allows the station AI, cyborgs, and maintenance drones to smelt raw resources without the need of a valid identification card installed.
-
Nanotrasen scientists have completed their designs for a lightweight, compacted anti-armor ion weapon, without loss of efficiency. In theory, such a weapon could easily fit within standard issue backpacks and satchels. With sufficient research into weapons technology and materials, Nanotrasen believes a working prototype can be fabricated.
-
-
Iamgoofball updated:
-
-
Buffs the shit out of Styptic Powder and Silver Sulf, they're now viable to use. Synthflesh is still good for general treatment, but overall Styptic and Silver Sulf deal with their respective damage types way better now.
-
Improves kinetic accelerator's mining strength.
-
The kinetic accelerator's cooldown can be decreased by tweaking the thermal exchanger with a screwdriver.
-
The range can be increased with plasma sheets
-
-
Incoming5643 updated:
-
-
Medical borgs have been given full surgery tools which they can't fail a procedure with.
-
Medical borgs also have rechargeable gauze.
-
Borg hypo's omnizine replaced with salbutamol, salglu and charcoal.
-
Slime and Jelly mutant races now actually contain slime jelly. While playing as these races you will naturally produce it internally from your nutrition, so be sure to stay well fed as losing too much is dangerous to your health! Beware of chems that clear toxins, as they can be rapidly fatal to you!
-
-
Jordie0608 updated:
-
-
Veil render and rifts changed to spawn with vars, rifts can be closed with a nullrod.
-
-
Lo6a4evskiy updated:
-
-
General record now contains a species field and a photo of the crewmember. Security record consoles allow you to update it with a photo taken in-game (with detective's camera, for example, or AI's).
-
Expanded functionality of security HUDs to display rank and photos.
-
Medical HUDs allow user to change others' mental and physical status. Currently has no gameplay effect other than changing records.
-
Medical HUDs can perform an examination at the distance, similrarly to how attacking yourself with help intent works. Has limited ability to detect suffocation and toxin damage.
-
-
Menshin updated:
-
-
Solar control computers remade with NanoUI
-
-
Paprika updated:
-
-
Added rnd requirements to borg upgrades and tweaked the prices.
-
Changed some clothing around. Added a turtleneck for the HoS. Removed 'civillian' armor and gave normal armor to the bartender and HoP. Added flash protection to the HoS' eyepatch, it functions like sunglasses now. Additionally, added the ability to store ammo on your shoulder holster as detective.
-
Removed the defib steal objective. Added a compact defibrillator for the CMO that equips to the belt. Removed defibrillators from RND. Removed the requirement for people to not wear armor to revive them with defibs. Doubled defib timers (10 minutes before permadeath).
-
Flashlights can be attached to pulse carbines.
-
Miners spawn with a brute patch.
-
Mining station's sleeper has been replaced with an IV drip and more medkits.
-
Added hard-light holotape. This holographic police or engineering hazard tape will block entry if you're running, so you need to walk to pass it. This is to help people not sprint into breaches or crime scenes.
-
Fixed being able to sling the strapless improvised shotgun on your back.
-
Reverted the price of the ebow to 12tc and removed the range limit of its bolts.
-
-
Sometinyprick updated:
-
-
Novely pig mask prize from arcade machines, can be toggled to make you squeal like a pig.
-
-
Steelpoint updated:
-
-
Security Cyborgs Advance Taser have been replaced with a Disabler.
-
-
TZK13 updated:
-
-
Adds plaid skirts and new schoolgirl outfits to the autodrobe
-
Novaflowers apply a firestack for every 20 potency they have.
-
You can now juice whole watermelons instead of slices.
-
Advancements in the field of virology by Nanotrasen's finest have uncovered three new disease symptoms for further research:
-
Ocular Restoration heals any eye trauma experienced by the infected patient.
-
Revitiligo increases the pigmentation levels of the infected patient's skin.
-
Spontaneous Combustion ignites the infected patient without warning.
-
-
Vekter updated:
-
-
Added shot glasses! Can be obtained from the Booze-o-Mat.
-
A new premium vodka has been added to the game, hidden in a special spot.
-
Due to constant attempts to break into the station's safe, the contents have been relocated and have not been replaced. We promise. There is absolutely NO reason to break into the vault's safe anymore.
-
Each shot glass now holds 15 units, and the drinker takes all 15 in one gulp instead of 5 at a time. This means you can mix certain shots (namely, the B-52 and Toxins Special) in a shot glass. Both drinks have been made exclusive to shot glasses and will no longer show their specific sprite in a normal glass.
-
Added new sprites for most alcohol in shot glasses. Everything else will show up as 'shot of... what?'.
-
Fixed shot glasses turning into regular glasses when filled.
-
Fixed tequila bottle sprites not showing up.
-
Fixed a typo!
-
Roboticists will now start with a full toolbelt instead of a toolbox.
-
-
drovidi updated:
-
-
Blobs who burst in space will be given a 30 second warning before dying.
-
If a blob does die from bursting in space, a new crewmember will be infected.
-
If there are no blobs or infected crew left after bursting, the round will now end properly, rather than becoming endless.
-
-
phil235 updated:
-
-
You no longer need to cut the syphon wire to deconstruct air alarms.
-
Cooking overhaul: microwave recipes are converted to tablecraft recipes.
-
Added customizable food (burger,sandwich,spaghettis,soup,salad,cake,bread,kebab,pie) with specific color depending on ingredients used.
-
Added new food ingredients (dough, flat dough, cake batter, pie dough, doughslice, bun, raw pastry base, pastry base, raw cutlet, cutlet, pizzabread). Dough is made by mixing egg and flour, then transformed by using knife, rollingpin, milk and microwave. Meat is now sliceable into rawcutlets (specific color for each meat type).
-
Changed food recipes a bit (replacing stuff with new food ingredients).
-
Repurposed the microwave to actually cook certain food items(no reagents required), renamed it microwave oven and added a power setting to it.
-
Bowl is no longer trash but a beaker-like container that is used in salad&soup recipes. Also, milk and soymilk cartons as well as flour packs are now condiment bottles.
-
Changed the hunger system a bit, sugar becomes a normal reagent again. Faster nutrition drain is now handled by a variable in junk food. Also added a slight buff to vitamin.
-
-
xxalpha updated:
-
-
You now hear the voice of the chosen deity in prayers.
-
-
-
05 February 2015
-
Danno updated:
-
-
Added suicide messages for all gun types and various different items.
-
-
Paprika updated:
-
-
Compacted ERT room by removing equipment rooms, filler rooms and pre-spawned mechs.
-
Rebalanced Pulse weapons to have more shots, faster charge, EMP immunity and modified sprites.
-
ERT spawns fully equipped with no-slowdown suits.
-
Added blood and bleeding. If you take enough brute damage on a certain limb, you will start bleeding, and will need to patch your bleeding with gauze from medbay. You can replace lost blood by eating nutritious food and waiting for your heart to reproduce it, or by getting a blood transfusion from an IV drip.
-
-
Vekter updated:
-
-
Added a toy red button to the Arcade cabinets.
-
-
-
04 February 2015
-
Danno updated:
-
-
Fixes dufflebag's storage capacity.
-
-
Deantwo updated:
-
-
Removes duplicate fluorine from Chemical Dispenser.
-
Added GoonChems to the Portable Chem Dispenser.
-
Added cancel buttons to the ChemMaster and CondiMaster when making bottles, pills, etc.
-
-
Delimusca updated:
-
-
Added new foam armblade toy to arcade prizes.
-
Slimes can drag themselves to mobs to start feeding.
-
Attacking another slime will pull them off their victim if they're feeding or steal some of their mass.
-
-
GunHog updated:
-
-
Five new silicon emotes: *buzz2, *chime, *honk, *sad and *warn
-
-
Incoming5643 updated:
-
-
New contraband crate containing fifty butterflies.
-
Shuttle will now always arrive, but won't launch in rounds where it would've been previously auto-recalled.
-
-
Menshin updated:
-
-
Fixed being unable to place cables on plating.
-
-
Paprika updated:
-
-
Helmets for hardsuits are stored inside the suit and cannot be equipped without the suit on.
-
EVA space suits are now lighter for faster movement but lack flash or welding protection, don't hide your identity and have less radiation protection.
-
-
Perakp updated:
-
-
Tweaked slowdown due to coldness when in in low gravity.
-
-
Steelpoint updated:
-
-
ERT base equipment rooms are now access restriction to each role.
-
ERT commander spawns wearing hardsuit for identification.
-
Two boxes of metal foam grenades added for ERT engineers.
-
Deep space sensors recently detected the derelict remains of the NTSS Omen, a outdated Medical Frigate thought lost, within close proximity to Space Station 13.
-
If any SS13 EVA Personal are able to locate this ship, they will be able to pilot it back to the station for recovery. The ship may also have cordinates for other locations.
-
-
Xhuis updated:
-
-
Changelings now have the ability Strained Muscles, which lets them move at inhuman speeds at the cost of rapid stamina damage. It costs one evolution points=.
-
Changelings now have the ability Augmented Eyesight, which lets them see creatures through walls and see in the dark. It costs two evolution points.
-
After many failed raids, the Syndicate have finally done something about poor communications between cyborgs and operatives.
-
Syndicate Cyborgs now come equipped with operative pinpointers, which will automatically point to the nearest nuclear operative.
-
Handheld crew monitors can now be stored in medical belts.
-
-
-
26 January 2015
-
Dannno updated:
-
-
Added fannypacks of multiple colors, 3 of which are accessible from the clothing vendor.
-
Added dufflebags to multiple lockers throughout the station and to the syndicate shuttle, more storage slots at the cost of move speed.
-
-
Deantwo updated:
-
-
Fixed some bugs in PDA's NTRC Chatroom feature.
-
You can now use PaperBBCode/PenCode when writing news articles on the newscaster.
-
PDA's Notekeeper now uses PaperBBCode/PenCode rather than HTML.
-
Scanning paper with your PDA is now more informative.
-
-
Firecage updated:
-
-
We, at NanoTrasen, have recently upgraded the Kitchen Spikes to include the skin of both Monkeys and Aliens when you harvest their flesh!
-
In other news, a station in a nearby Sector has been making monkey and xeno costumes. We are not sure why.
-
Added the ability to reform mineral floortiles back into sheets/bars
-
-
Ikarrus updated:
-
-
Added a new config option MINIMAL_ACCESS_THRESHOLD that can be used to automatically give players more access during low-population rounds. See game_options.txt for more information.
-
-
Lo6a4evskiy updated:
-
-
The kitchen now contains a brand new food cart. It can store foods and liquids, mix them into cocktails and dispense where needed. Food delivery has never been easier!
-
-
fleure updated:
-
-
Added briefcase filled with cash to uplink.
-
-
-
21 January 2015
-
Iamgoofball updated:
-
-
Addiction and Overdosing has been added to Chemistry.
-
Deal with Overdosing by purging the ODing chemical with Calomel or Penetic Acid.
-
Deal with Addiction by either going cold turkey on it and weathering the effects, eventually causing the addiction to subside, or by taking more of the addictive substance to keep the effects down.
-
Cryoxadane was heavily buffed. Clonex was removed as it was merged into Cryox.
-
Fixes a shitton of stuff adding Morphine instead of Ephedrine. Sorry, miners.
-
Fixes instances of Alkysine not being replaced with Mannitol. Same with Ryetalyn being replaced with Mutadone.
-
Adds Itching Powder. Welding Fuel and Ammonia.
-
Adds Antihol. Ethanol and Charcoal.
-
Adds Triple Citrus. Lemon Juice, Lime Juice, and Orange Juice.
Morphine replaces sleep toxin now, not Hyperzine. Hyperzine is replaced by Ephedrine now.
-
Cryotubes will now autoeject you once you are fully healed.
-
Synthflesh damage values have been fixed.
-
Charcoal has been buffed to heal 3 TOX damage a cycle and it purges other chemicals faster.
-
Saline-Glucose Solution has been buffed. It now has a 50% chance per cycle to heal 3 BRUTE and BURN per tick.
-
Fixes Strange Reagent revivals dying right after being revived. They'll still need immediate medical treatment, however.
-
Cryoxadone has had a recipe change. It is now Stable Plasma, Acetone, and Unstable Mutagen.
-
Adds 9 new Disabilites and 2 new genetic powers.
-
You can now heat donk pockets once more. Rejoice.
-
-
-
17 January 2015
-
Cheridan updated:
-
-
The lockbox system for R&D has been replaced with a firing pin system. Ask the Warden or HoS for firing pins to allow normal operation of your firearms. Want to test them out at the range? Try printing a test-range pin.
-
Nuke Ops have used this new technology to lock some of their heavier weapons so that crew cannot use them. These implants replace their explosive implants.
-
Explosive implants are now purchasable in the uplink, and are much stronger.
-
-
Firecage updated:
-
-
NanoTrasen would like to report that our Mechanical Engineers at NTEngiCore has recently upgraded the capacity of the machines known as Biogenerators. They are now have the added ability to create various belts, and also weed/plantkillers and cartons of milk and cream!
-
-
Iamgoofball (Look Ma, I didn't mispell my name this time!) updated:
In recipes that used removed reagents, they have been replaced with their goon chemistry counterpart:
-
Hyperzine -> Morphine
-
Inaprovaline -> Epinephrine
-
Kelotane -> Saline-Glucose Solution
-
Bicardine -> Saline-Glucose Solution
-
Dermaline -> Saline-Glucose Solution
-
Dexalin -> Salbutamol
-
Dexalin Plus -> Salbutamol
-
Tricordrazine -> Omnizine
-
Anti-Toxin -> Charcoal
-
Hydronalin -> Penetic Acid
-
Arithrazine -> Penetic Acid
-
Imidazoline -> Oculine
-
Mannitol -> Alkysine
-
Ryetalyn -> Mutadone
-
-
Steelpoint updated:
-
-
The Emergency Response Team project has been activated by Nanotrasen. The ERT are a seven man squad that can be deployed by the Central Command (admins) to attempt to rescue the station from a overwhelming threat, or to render assistance in dealing with a significant problem.
-
The ERT consists of a single Commander, two Security Officers, two Engineering Officers and two Medical Officers. The Commander is preselected when the squad is being spawned in, however the remaining six officers are free to select their role in the squad.
-
Two new Pulse gun variants have been added to the game. They are the Pulse Carbine and the Pulse Pistol, both have significantly smaller ammunition capacities than the Pulse Rifle, however they both can fit in a backpack and the pistol can fit in a pocket. All ERT Officers have a Pulse Pistol for a sidearm, however the Security Officers get a Pulse Carbine as their primary longarm.
-
-
phil235 updated:
-
-
The two library consoles are now real buildable computers. The circuitboard design is available in R&D.
-
-
sawu-tg updated:
-
-
Added a new R&D machine, to replace the telescience room. The Machine is based on RND and various interdepartmental creations.
-
-
-
14 January 2015
-
Boggart updated:
-
-
Adds a new spell: Repulse. (Cooldown: 40 seconds, 15 at max upgrade.) Repulse throws objects and creatures within 5 tiles of the caster away and stuns them for a very short time. Distance thrown decreases as the distance from the caster increases. (Max of 5, minimum of 3) Casting it while standing over someone will stun them for longer and cause brute damage but will not throw them.
-
Fixes the Exosuit Control Console having the unpowered icon state while powered.
-
-
Iamgootball updated:
-
-
Added Goonstation Chemistry!
-
Adds 14 medicines!
-
Adds 3 pyrotechnics!
-
Adds 3 drugs with actual gameplay uses!
-
Adds 7 toxins!
-
Adds a Chemical Heater for some new recipes!
-
Adds Chemical Patches for touch based applications!
-
Adds a Traitor Poison Kit!
-
Adds Morphine Medipens!
-
Adds Morphine bottles to Cargo!
-
-
-
13 January 2015
-
Dannno updated:
-
-
Adds a bandanas in basic colors to lockers in the station. Activate in hand to switch between head and mask modes.
-
Adds more colored jumpsuits to the mixed locker.
-
-
Incoming5643 updated:
-
-
Player controlled slimes can now change their face to match AI controlled slimes in kawaiiness. use *help as a slime to find the commands.
-
-
Studley updated:
-
-
Fixed NTSL division.
-
Added new NTSL math functions, including 'sin,' 'cos,' 'asin,' 'acos,' and 'log.'
-
-
-
06 January 2015
-
JJRcop, Nienhaus updated:
-
-
Adds emoji to OOC chat.
-
-
-
27 December 2014
-
Dorsidwarf updated:
-
-
Buffs the RCD from 30 matter units to max capacity 100, and increases the value of matter charges.
-
-
Paprika updated:
-
-
Taser electrodes now have a limited range of 7 tiles.
-
The energy gun's stun mode has been replaced with a disable mode.
-
Security officers will now spawn with tasers.
-
Disabler beams now go through windows and grilles.
-
Disabler beams now do 33 stamina damage instead of 34. This means they will only stun in 3 hits on someone that is NOT 100% healthy.
-
Most ballistic projectiles now do stamina damage in addition to their regular damage instead of stunning. This includes nuke op c-20r .45, detective's revolver, etc. They are effectively better than disablers at stunning people, as well as do significant damage, but they do not stun instantly like a taser. Expect engagements to be a little longer and plan ahead for this.
-
Added an advanced taser for RnD, the child of a disabler and a taser.
-
-
Tkdrg updated:
-
-
Envoys from the distant Viji sector have brought an exotic contraption to Centcomm. Known as the 'supermatter shard', it is a fragment of an ancient alien artifact, believed to have a dangerous and mysterious power. This precious item has been made available to the Space Station 13 cargo team for the cost of a hundred supply points, as a reward for all your hard work. Good luck, and stay safe.
-
-
-
16 December 2014
-
Paprika updated:
-
-
Reverted security armor and helmets to the old (Grey) style of armor/helmets.
-
Updated security jumpsuits to be more consistent across officer/warden/HoS ranks.
-
Added tactical armor and helmets to replace the largely unused bulletproof vest in the armory. This armor has higher bullet and explosive resistance than normal armor, and uses the previous, more military armor/helmet sprites.
-
Added a new injury doll to go along with the overall health meter. This will show you locational overall limb-based burn/brute damage, so you can tell the difference between being hurt with burn/brute or toxins/stamina damage which does not affect limbs.
-
Reverted some UI icons back to their older status and changed some around for readability. Thank you for your feedback.
-
-
Thunder12345 updated:
-
-
Added a new FRAG-12 explosive shotgun shell, built from the unloaded technological shell, using tablecrafting. Constructed using an unloaded technological tech shell, 5 units sulphuric acid, 5 units polytrinic acid, 5 units glycerol, and requires a screwdriver.
-
-
phil235 updated:
-
-
Change to the hunger system. Eating food with sugar temporarily worsens your nutrition drop rate. Eating too much sugar too quickly can make you temporarily unable to eat any sugary food. The nutritional effect of sugar depends on how hungry you are thus it cannot easily raise your nutrition level past well fed and is best used when hungry. Lots of sugar can make you a bit jittery at times. Eating food with vitamin (new reagent) counteracts the sugar effects and can give you temporary buffs when well fed: it lowers your chance to catch or spread viruses,it makes your body's metabolism more efficient that is it keeps healing reagents longer and evacuate toxins faster and reduces damage from radioactive reagents. Your metabolism gets less efficient if starving.
-
New hunger hud icons: starving, hungry, fed, well fed, full, fat.
-
Snack vending machine get a chef compartment that can be loaded with non-sugary food by hand or with a tray by anyone with kitchen access (unless you hack the machine with multitool or emag). The food can be unloaded by anyone, like regular snacks
-
Cargo can get a nutritious pizza crate order for 60 points.
-
Grown and cooked food gets some vitamin while junk food gets less nutriment and more sugar (only hot donkpocket and ramen don't have sugar)
-
The number of junk food in snack vending machine is lowered from 6 to 5 of each type.
-
Fixing not being able to load an omelette du fromage on a tray.
-
-
-
10 December 2014
-
GunHog updated:
-
-
The Nanotrasen firmware update 12.05.14 for station AI units and cyborgs includes a patch to give them more control of their automatic speech systems. They can now chose if the message should be broadcasted with their installed radio system and any frequency available to them.
-
-
Paprika updated:
-
-
Added winter coats and winter boots! Bundle up for space xmas! They are available in most wardrobe and job lockers, and come with special features depending on the flavor!
-
-
Paprka updated:
-
-
Added a new weapon for syndicate boarding parties, the C-90gl assault rifle. This is a hybrid rifle with a lot of damage and a grenade launcher alt-fire. Purchasable for 18TC.
-
Suppressors can now be used on the protolathe SMG and the C-20r.
-
-
RemieRichards updated:
-
-
Sounds in areas of low pressure (E.g. Space) are now much quieter
-
To hear someone in space you need to be stood next to them, or use a radio
-
-
as334 updated:
-
-
We've discovered that slimes react to the introduction of certain chemicals in their bloodstream. Inaprovaline will decrease the slimes children's chance of mutation, and plasma will increase it.
-
We've also found that feeding slimes solid plasma bars makes them more friendly to humans.
-
-
-
08 December 2014
-
Ergovisavi updated:
-
-
Allows using replica pods to clone people without bodies.
-
Speeds up replica pod cloning by adjusting the starting production.
-
Replica pods will now only create plantmen.
-
Mixing blood in a beaker or taking it from a suicide victim will render the blood sample useless for replica pod cloning.
-
-
GunHog updated:
-
-
Our top Nanotrasen scientists have provided improved specifications for the Rapid Part Exchange Device (RPED)! The RPED can now contain up to 50 machine components, and will display identical items in groups when using its inventory interface.
-
Note that power cells, being somewhat bulkier, will take up more space inside the device. In addition, the RPED's firmware has also been updated to assist in new machine construction! Simply have the required components inside the RPED, apply it to an unfinished machine with its circuit board installed, and watch as the device does the work!
-
Nanotrasen has updated station bound Artificial Intelligence unit design specifications to include an integrated subspace radio transmitter. The transmitter is programmed with all standard department channels, along with the inclusion of its private channel. It is accessed using :o in its interface.
-
{INCOMING CLASSIFIED SYNDICATE TRANSMISSION} Greetings, agents! Today, we are happy to announce that we have successfully reverse engineered the new subspace radio found in Nanotrasen's newest AI build plans. We have included our encryption data into these transmitters.
-
Should you be deployed with one of our agent AIs, it may be wise of you to purchase a syndicate encryption key in order to contact it securely. Remember, as with other agents, a Syndicate AI is unlikely to be of the same Syndicate corporation as you, and you both may actually have conflicting mission parameters! Your channel, as always, is accessed using :t.
-
-
Paprika updated:
-
-
Changed the way flashbang protection works. Currently, only security helmets (and their derivatives like swat and riot), and some hardsuit helmets have flashbang protection. Ear items like earmuffs and 'bowman' headsets (alternative, larger headsets) have flashbang protection as well, so you're able to go hatless as security. The Head of security, warden, and detective's hat do NOT have flashbang protection, but their earpieces do, from the 'noise' part of the flashbang. The flashbang blind still works as before, with only sunglasses/hardsuit helmets protecting you.
-
-
Tkdrg updated:
-
-
Security/Medical HUDs are now more responsive and less laggy.
-
-
-
10 November 2014
-
Menshin updated:
-
-
Made meteors more threatening.
-
Added three levels of danger for meteors waves, randomly selected at spawn.
-
Rumors speaks of a very rare meteor of unfathomable destructive power in the station sector...
-
Fixed infinigib/infinisplosion with meteors bumping
-
-
Paprika updated:
-
-
Added a defibrillator and an EMT alternative clothing set for medical doctors. These defibrillators will revive the recently deceased as long as their souls are occupying their body and they have no outstanding brute or burn damage.
-
Yes, they actually work now too!
-
Added a way for you to unload a round from a chambered projectile weapon. Simply click on the gun when it is in your active hand, and assuming it does not have a magazine, the round should pop out. You can also use this system to unload individual bullets from magazines and ammo boxes, like the detective's speed loaders.
-
Added glass tables and overhauled how table construction works. Now, you need to build a frame using either metal rods or wood planks, and then place a top on the table.
-
Table disassembly now works in two stages. Screwdriver the table to remove the top of it and reduce it back to a frame. Frames can be moved around and walked on top of. Wrench the table to deconstruct it entirely back to parts, this is quicker and much more like the old fashion. To deconstruct a frame, use your wrench on it as well.
-
Fixed traitor med analyzers not fitting in medical belts.
-
Fixed syringe box bug with medipens.
-
-
Perakp updated:
-
-
Added escape-with-identity objective for changelings. Completion requires the changeling to have transformed to their target and be wearing an ID card with the target's name on it when escaping.
-
Changelings can absorb someone even if they have that DNA in storage already.
-
Fixes hivemind being lost when readapting evolutions.
-
-
-
04 November 2014
-
MrPerson updated:
-
-
You can use AI Upload modules directly on unslaved cyborgs to change their laws. The panel has to be open to do this.
-
Newly emagged cyborgs get locked down for a little bit until their new laws get shown. Thank you to whoever bitched about it on singulo and filed an issue report.
-
-
Paprika updated:
-
-
Added an advanced mop for the janitor, available at the protolathe. This hi-tech custodian's friend can mop twice as many floor tiles before needing to return to your bucket!
-
Moved holosign projector to the protolathe and gave the janitor back his old signs. We hope you've enjoyed this free trial run of holo projectors, but our budget is tight!
-
Added a holosign projector to the janitor module for cyborgs.
-
Soap now has a delay. Different soap types clean faster than others, but expect to put it some extra arm work if you want to clean that fresh blood!
-
-
Tkdrg updated:
-
-
Cutting-edge research in a secret Nanotransen lab has shed light into a revolutionary form of communication technology known as 'chat'. The Personal Data Assistants of Space Station 13 have been deployed with an experimental module implementing this system, dubbed 'Nanotransen Relay Chat'.
-
-
-
30 October 2014
-
MrPerson updated:
-
-
Everything now drifts in space, not just mobs.
-
Mechs are just as maneuverable in space as you are.
-
Being weightless now slows movement down by a lot rather than speeding it up. Having your hands full will make movement even slower.
-
Removed spaceslipping (You slipped! x1000)
-
Jetpacking after being thrown out a mass driver will cancel out your momentum.
-
Shooting a gun causes you to drift the other direction like spraying a fire extinguisher.
-
-
-
27 October 2014
-
Jordie0608 updated:
-
-
Adds a new riot shotgun that spawns in the armory with a 6 round beanbag magazine.
-
-
Paprika updated:
-
-
Allowed constructs to assist in activating most runes. This includes conversion runes, but does NOT include sacrifice runes.
-
-
Patchouli Knowledge updated:
-
-
In light of the recent achievements of the Robotics department, new and upgraded exosuit schematics were delivered to SS13 exosuit fabricators as a reward from CentComm.
-
The countless hours of research, experimentation, and reverse-engineering of wrecks from the mining asteroid have paid off - the Research team has rediscovered the process of building Phazon exosuits. The parts and circuits have been made available in the exofabs and circuit printers.
-
The APLU Ripley exosuit has been refitted with asteroid mining purposes in mind. Its armour plating will provide extra protection against the brute attacks of asteroid alien lifeforms, and protect from gibtonite explosions. The leg servos were upgraded to provide faster movement.
-
There are rumours of some creative shaft miners discovering a way of augmenting APLU Ripley armour with trophy hides from goliath-type asteroid lifeforms...
-
Durand construction routines now require a phasic scanner module and a super capacitor to match Nanotrasen industry standards.
-
The exosuit fire extinguisher now boasts a larger watertank, and carries a whole 1000u of water from the old 200u tank.
-
The exosuit plasma converter's efficiency has been vastly improved upon, and its fuel consumption has been cut to nearly half of old values.
-
The exosuit sleeper interiors have been refitted to allow the occupants more freedom of movement, and the occupants may now eject at will.
-
The armour plating on all exosuits was fitted with reactive deflection systems specifically designed to stop the rapid slashes from xenomorph threats.
-
The Firefighter Ripley chassis were moved from the Exosuit Equipment section to the APLU Ripley section of exofab menus.
-
Fixed some minor errors in the exosuit construction message syntax.
-
-
phil235 updated:
-
-
Closets and its children can no longer contain a near infinite amount of mobs. Large mobs (juggernaut, alien emperess, etc) don't fit, small mobs (mouse, parrot, etc) still fit in the same amounts, human-sized mob (humanoid, slime, etc) fit but maximum two per body bag and three for all the other closets. Adding bluespace body bag to R&D, it can hold even large mobs and in large amounts.
-
girders and displaced girders now have a chance to let projectiles pass through them.
-
-
-
19 October 2014
-
Donkie updated:
-
-
Major atmos update. Featuring pretty colors.
-
4-Way pipes added.
-
Flip-able trinary devices (mixer & filter).
-
Pipe-painter added.
-
Most pipe icons have been tweaked with shading and such, making it look more uniform.
-
-
JJRcop updated:
-
-
We have managed to re-discover drones! We improved the formula, and have discovered some cool stuff.
-
After some fiddling with audio recognition, drones can now understand human speech!
-
One of our engineers found some screws that were loose on some of our tests, screwing them in made the drone work as if it had just come out of robotics.
-
We had to sacrifice some of our NT brand EMP protection for the speech recognition, so drones can get damaged if subjected to heavy EMP radiation.
-
We figured out why drones couldn't remove objects from people, that has been fixed.
-
The standby light wasn't wired correctly.
-
Unfortunately one of our assistants tasked with transporting the only copy of the data dropped the flash drive in hot water because he wanted to see what "drone tea" tasted like. You'll have to discover them again.
-
-
Lo6a4evskiy updated:
-
-
Station's crayons have been upgraded to the version 2.0 of ColourOS. Don't worry, they are still edible!
-
Many items now have different stripping delays. Heavier and more armored pieces of clothing are generally more difficult to take off someone else, while smaller items can be very easy to snatch. Pockets always take 4 seconds to search.
-
-
MrPerson updated:
-
-
Implants are activated via action buttons on the top-left corner of your screen instead of emotes.
-
-
Paprika updated:
-
-
Added zipties for security and nuke operatives. These have a slightly shorter 'breakout time' as normal cuffs but they are destroyed upon removal. Beepsky and sec borgs now use these by default.
-
Changed the medical HUD in the tactical medkit to a night vision HUD so syndicate medics don't stumble around in the dark. Can be purchased through nuke op uplinks.
-
Added a second roboticist roundstart job slot.
-
Added a third cargo tech job slot.
-
-
Paprka updated:
-
-
Added undershirts as a companion piece to jumpsuit adjusting. Beat the heat with a comfortable tank top, or warm up with an extra layer tshirt!
-
Added a holographic sign projector to replace the old wet floor signs to encourage janitors to warn crewmembers about wet floors.
-
-
optimumtact updated:
-
-
Medical supplies crate now comes with a boxy full of bodybags
-
-
-
09 October 2014
-
Paprika updated:
-
-
Added inaprovaline MediPens (autoinjectors) that replace inaprovaline syringes in medkits. Like the syringes, these can be used to stabilize patients in critical condition, but require no medical knowledge to use.
-
Reverted the mining scanner to a manual scanner.
-
Buffed the rate of the automatic scanner
-
Added automatic scanner to equipment locker. It is also available by redeeming your voucher.
-
Reduced the size of the mining jetpack, it now fits in backpacks. The capacity of the jetpack has been reduced as well, however.
-
Removed resonator from the mining voucher redemption. Replaced it with a mining drill.
-
-
-
08 October 2014
-
Cheridan updated:
-
-
Flash mechanics changed. Instead of a knockdown effect, it will cause a stumbling effect. This includes area-flashing. The five-use-per-minute limit has been removed. "Synthetic" flashes made in the protolathe are now normal flashes.
-
-
Paprika updated:
-
-
Added a unique shield to the Head of Security's locker. It can be concealed for storage and activated like an energy shield, but functions like a riot shield. It can also be made at the protolathe.
-
Marauders: Due to your extremely great performance as of late, we've added a new equipment room on Mother Base. A lot of your equipment has been moved to this room, as well as some new equipment added onto the Mothership. Take some extra time to familiarize yourself with the placement of your equipment.
-
Oh, and you guys have a space carp now too.
-
-
Paprka updated:
-
-
Added jumpsuit adjusting. Adjust your jumpsuit to wear it more casually by rolling it down or roll up those sleeves of that hard-worn suit!
-
Added a grey alternative to the detective's suit and trenchcoat. Now you can be a noir private investigator!
-
-
Tkdrg updated:
-
-
Space Station 13 has been deployed with transit tube dispensers. Central Command encourages the engineering department to repair and improve the station using this equipment.
-
-
-
05 October 2014
-
Ikarrus updated:
-
-
Character Save Files will now remember Species.
-
-
Miauw updated:
-
-
Added 5 new maintenance ambience tracks, made by Cuboos.
-
-
Nobalm updated:
-
-
Added examine descriptions to twenty eight objects.
-
-
Paprika updated:
-
-
Due to some payroll cuts affecting lower-rank crewmembers on Space Station 13, we fear that uprisings and mutinies might be approaching. To help defend themselves, all heads of staff have been given telescopic batons to subdue attackers should they be assaulted during their shifts.
-
Toolbelts can now hold pocket fire extinguishers. Atmos technicians spawn with them.
-
Remember those magboots we had our undercover agents steal? Well we've finally reverse engineered them. Not only that, but we gave them a killer paintjob. They will be available in the uplinks of boarding parties we send out to Nanoscum stations.
-
Listen up, Marauders! The Syndicate has recently cut back funding into boarding parties. What this means is no more hi-tech fancy energy guns. We've had to roll back to using cheaper, mass-produced automatic shotguns. But in retrospect, could this possibly be a downgrade?
-
-
-
01 October 2014
-
Cheridan updated:
-
-
Experienced smugglers have joined the Syndicate, adding their bag of tricks to its arsenal. These tricky pouches can even fit under floor plating!
-
-
Ikarrus updated:
-
-
Adds Gang War, a new game mode still in alpha development.
-
-
Incoming5643 updated:
-
-
After a public relations nightmare in which a horde of 30 assistants violently dismembered a sole commander the revolution has begin tactically reducing their initial presence in stations with weak command structures. Should stations add to their command staff during the shift additional support will be provided.
-
Too many instances of crying CMOs hiding in the lockers of supposedly secured revoulutionary stations has lead to a crackdown on revolutionary policy. You must kill or maroon the entire command staff, reguardless of when they reached the station.
-
Intense study into the underlying structure of the universe has revealed that everything fits into a massive SPACECUBE. This deep and profound understanding has led to a revolution in cross surface movement, and spacemen can look forward to a new era of predictable and safe space-fairing.
-
-
Jordie0608 updated:
-
-
Grille damage update, humans now deal less unarmed damage to grilles while projectiles and items deal more.
-
Replaced all AI turrets with porta-turrets that work on LoS instead of area and can be individually configured
-
-
Miauw updated:
-
-
Voice changer masks work again.
-
You can now turn voice changer masks on/off by activating them in your hand. They start off.
-
The amount of TC has been increased to 20, but the price of all items has been doubled as well
-
With this, the prices of stealthy items has been slightly decreased and the prices of offensive items slightly increased (+/- 1 TC)
-
-
RemieRichards, JJRcop updated:
-
-
New research in the area of robotics has been published on Maintenance Drones, little robots capable of repairing and improving the station. Unfortunately, all of the research was lost in a catastrophic server fire, but we know they can be made, so it's up to you to re-discover them! Make us proud!
-
-
phil235 updated:
-
-
Changed all clone damage descriptions to use the term cellular damage to avoid confusion with genetics mutations.
-
-
-
07 September 2014
-
Gun Hog updated:
-
-
Nanotrasen scientists have released a Heads-Up-Display (HUD) upgrade for all AI and Cyborg units! Cyborgs have had Medical sensors and a Security datacore integrated into their core hardware, replacing the need for a specific module.
-
AI units are now able to have suit sensor or crew manifest data displayed to them as a visual overlay. When viewing a crewmember with suit sensors set to health monitoring, the health status will be available on the AI's medical HUD. In Security mode, the role as printed on the crewmember's ID card will be displayed to the AI.
-
Any entity with a Security or Medical HUD active will recieve appropriate messages on that HUD.
-
-
Ikarrus updated:
-
-
Server Operators: A new config option added to restrict command and security roles to humans only. Simply add/uncomment the line ENFORCE_HUMAN_AUTHORITY in game_options.txt.
-
Players: To check if this config option is enabled on your server, type show-server-revision into the game's command line.
-
-
-
-
06 September 2014
-
Ikarrus updated:
-
-
Tampered supply ordering consoles can now order dangerous devices directly from the Syndicate for 140 points.
-
Securitrons line units will no longer arrest individuals without IDs if their faces can still be read.
-
Enemy Agent ID cards have been reported to be able to interfere with the new securitron threat assessment protocols.
-
The Syndicate base has been modified to only allow the leading operative to launch the mission.
-
-
-
-
04 September 2014
-
KyrahAbattoir updated:
-
-
All the objects found in BoxStation maintenance are now randomized at round start.
-
-
-
03 September 2014
-
Ikarrus updated:
-
-
Cost of Syndicate bombs increased to 6 telecrystals.
-
-
JStheguy updated:
-
-
Many new posters have been added.
-
-
-
01 September 2014
-
ChuckTheSheep updated:
-
-
Adds preference toggle for intent selection mode.
-
Direct selection mode switches intent to the one you click on instead of cycling them.
-
-
-
Ikarrus updated:
-
-
New more detailed End-Round report.
-
Removes count of readied players from the lobby.
-
-
-
Jordie0608 updated:
-
-
Virology has been made the right color.
-
-
Miauw updated:
-
-
A major rework of saycode has been completed which fixes numerous issues and improves functionality.
-
Please report any issues you notice with any form of messaging to Github.
-
-
-
-
31 August 2014
-
Tokiko1 updated:
-
-
Adds two new hairstyles.
-
-
-
30 August 2014
-
Ikarrus updated:
-
-
Space Station 13 has been authorized to requisition additional Tank Transfer Valves from Centcom's supply lines for the price of 60 cargo points.
-
-
-
26 August 2014
-
Ikarrus updated:
-
-
Security forces are advised to increase scrutiny on personnel coming into contact with AI systems. Central Intelligence suggests that Syndicate terrorist groups may have begun targeting our AIs for destruction.
-
For the preservation of corporate assets, Central Command would like to remind all personnel that evacuating during an emergency is mandatory. We suspect that terrorist groups may be attempting to abduct marooned personnel who failed to evacuate.
-
R&D; teams have released an urgent update to station teleporters. To eliminate the risk of mutation, personnel are advised to calibrate teleporters before attempting each use.
-
Centcom has approved the Captain's request to rig the display case in his office with a burglar alarm. Any attempts to breach the case will now trigger a lockdown of the room.
-
**Crew Monitoring Consoles now scan their own Z-level for suit sensors, instead of only scanning Z1.
-
**Server operators can now set a name for the station in config.txt. Simply add the line "STATIONNAME Space Station 13" anywhere on the text file. Replace Space Station 13 with your station name of choice.
-
-
-
-
24 August 2014
-
Ikarrus updated:
-
-
Ore redemption machine can now smelt plasteel.
-
Mulebot speed has been vastly increased by up to 300%.
-
Removed exploit where false walls can be used to cheese the AI chamber's defenses
-
-
-
21 August 2014
-
Ikarrus updated:
-
-
The Phase Shift ability will no longer instantly gib players. Instead, they will be knocked out for a few seconds. Mechas will be damaged.
-
The Phase Slayer ability will no longer instantly gib players. Instead, they will be dealt 190 brute damage. Mechas will be dealt 380 damage.
-
ADMIN: Most Event buttons have been moved out of the secrets menu and into a Fun verb.
-
-
-
20 August 2014
-
Cheridan updated:
-
-
Three new shotgun shells added. Create them by first researching and printing an unloaded tech shell in R&D.; Afterwards, use table-crafting with the appropriate components with a screwdriver in-hand.
Dragonsbreath: tech shell + 5 phosphorus (in container).
-
Incendiary rounds now leave a blazing trail as they pass. This includes existing incendiary rounds, new dragonsbreath rounds, and the Dark Gygax's carbine.
-
-
-
19 August 2014
-
Ikarrus updated:
-
-
Brig cells and labour shuttle have both been modified to send a message to all security HUDs whenever a prisoner is automatically released.
-
The labour camp has been outfitted with a points checking console to allow prisoners to check their quota progress easier and better explain the punishment system of forced labour.
-
-
-
18 August 2014
-
Iamgoofball updated:
-
-
25 new hairstyles have been added.
-
-
-
15 August 2014
-
AndroidSFV updated:
-
-
AI photography has been extended to Cyborgs. While connected to an AI, all images taken by cyborgs will be placed in the AI's album, and viewable by the AI and all linked Cyborgs.
-
When a Cyborgs AI link wire is pulsed, the images from the Cyborgs image album will be synced with the AI it gets linked to.
-
Additonally, Cyborgs are able to attach images to newscaster feeds from whichever album is active for them. Cyborgs are also mobile, inefficent, printers of same images.
-
-
-
11 August 2014
-
Ikarrus updated:
-
-
Boxstation map updates:
- -Singularity engine walled from from outer space
- -Shutters for exterior Science windows
- -Teleporter board moved from RD's Office to Tech Storage now that the teleporter can be built again.
- -Security Deputy Armbands (red) added in HoS's Office
-
-
-
14 July 2014
-
Miauw updated:
-
-
Added a new mutetoxin that can be made with 2 parts uranium, 1 part water and 1 part carbon and makes people mute temporarily
-
Parapens were renamed to sleepy pens and now contain 30 units of mutetoxin and 30 units of sleeptoxin instead of zombie powder.
-
C4 can no longer be attached to mobs
-
Reduced the C4 price to 1 telecrystal
-
-
-
07 July 2014
-
Firecage updated:
-
-
We, of the NanoTrasen Botany Research and Developent(NTBiRD), has some important announcements for the Botany staff aboard our stations.
-
We have recently released a new strain of Tower Cap. It is now ensured that more planks can be gotten from a single log.
-
Head Scientist [REDACTED] is thanked for his new strain of Blood Tomatoes. It now not only contains human blood, but also chunks of human flesh!
-
We have also discovered a way to replant the infamous Cash Trees and 'Eggy' plants. We can now harvest their seeds, and the rare products are inside the fruit shells.
-
Similar to the new variety of Tower Caps, it is now possible to get various grass flooring depending on the plants potency!
-
Our sponsors at Knitters United has released a new variation of the botany Aprons and Coveralls which can hold a larger variety of items!
-
A new version of Plant-B-Gone was produced which are much more lethal to plants, yet at the same time has no extra effect on humans besides the norm.
-
-
Paprika updated:
-
-
Tweaked mining turf mineral droprates and reverted mesons to the 'old' style of mesons. This means mesons will be more powerful with the added side effect of not being able to track individual light sources through walls and such. This is a trial run; please provide feedback on the /tg/ codebase section of the forum.
-
-
-
05 July 2014
-
Firecage updated:
-
-
Recently the Space Wizard Federation, with some assistance from the Cult of Nar-Sie, recently created a new strain of Killer Tomato. A sad side effect is this effected all current and future Killer Tomato strains in our galaxy. They are now reported to be extremely violent and deadly, use extreme caution when growing them.
-
-
-
03 July 2014
-
Miauw updated:
-
-
Added slot machines to the game
-
Slot machines use reels. All prizes except for jackpots can be won on all lines
-
Simply put a coin into one of the slot machines in the bar to play!
-
Slot machines can also be emagged.
-
-
-
01 July 2014
-
Ikarrus updated:
-
-
Central Security has approved an upgrade of the Securitron line of bots to a newer model. Highlights include:
-
-An optional setting to arrest personnel without an ID on their uniform (Disabled by default)
-
-An optional setting to notify security personnel of arrests made by the bots (Enabled by default)
-
-A new "Weapon Permit" access type that securitrons will use during their weapons checks. Personnel who work with weapons will be granted this access by default. More can be assigned by the station's Head of Security and Personnel.
-
-A more robust threat assessment algorithm to improve accuracy in identifying perpetrators.
-
-
Rolan7 updated:
-
-
Fixed the irrigation system in hydroponics trays. Adding at least 30 units of liquid at once will activate the system, sharing the reagents between all connected trays.
-
Changelings can communicate while muzzled or monkified, and being monkified doesn't stop them from taking human form.
-
Mimes cannot break the laws of physics unless their vow is currently active.
-
Trays are rewritten to make sense and so borgs can actually use them. Service borgs can discretely carry small objects, hint hint. The items are easily knocked out of them.
-
-
-
20 June 2014
-
Ikarrus updated:
-
-
The 'Enter Exosuit' button was removed, and you have to click + drag yourself to enter a mech now; like you would to enter a disposal unit.
-
-
-
12 June 2014
-
Cheridan updated:
-
-
NT Baton Refurbishment Team Notice: 'Released' option in security records has been renamed 'Discharged', and the sechud icon has been changed from a blue R to a blue D. Stop beating your released prisoners, dummies; they're not revheads.
-
NT Entertainment Department Notice: The prize-dispensing mechanism in Arcade machines has been replaced with a cheaper gas-powered motor. It should still be completely safe, unless an electromagnetic field disrupts it!
-
Pill code tweaked. In short, you can feed monkeys pills now #whoa
-
Casings ejected from guns will now spin #wow
-
-
-
11 June 2014
-
Ikarrus updated:
-
-
Having the nuke disk will no longer prevent you from using teleporters or crossing Z-levels. Leaving the Z-level however, will cause you to "suddenly lose" the disk.
-
Pulled objects will now transition with you when as you transition Z-levels in space.
-
-
Malkevin updated:
-
-
IEDs have been removed
-
Firebombs have been added
-
They're as reliable and predictable as you would expect from a soda can filled with flammable liquid and set off with an igniter contected to two wires. Use your branes and take proper precautions or leave a charred corpse, your choice.
-
-
-
06 June 2014
-
Gun Hog updated:
-
-
Nanotrasen programmers have discovered a potential exploit involving a station's door control networks. With sufficient computing power, the network can be overloaded and forced to open or bolt closed every airlock at once.
-
Some reports suggest that the latest firmware update to Artifical Intelligence units installed in our research stations may contain this exploit. Any such reports that state that should a station's AI unit "malfunction", it may gain the ability to use this exploit to initate a full lockdown of the station. Fire locks and blast doors are also said to be affected.
-
Nanotrasen officially holds no validity to these reports. We recommend that station personnel disregard such rumors.
-
-
-
20 May 2014
-
Kelenius updated:
-
-
After the years of extensive research into xenobiology, and thousands of scientists lost to the slimes, we have finally come to admitting that we don't have a clue how they work. However, we've outlined a few facts, and managed to breed a new kind of slimes. They will be shipped to your station on the next shifts. They are different from the ones they are used to in the following:
-
Their sight is better than ever, and now they can finally see things that are not directly pressed against them. Beware, they may appear more aggressive!
-
Their memory has also become better, and they will now remember the people who fed them. As a note, you need to grab the monkey or push it to show to the slime that you're giving it to them, and it's not just walking in.
-
Apparently, not only we have studied them, but they have also studied us. Slimes are now capable of showing various emotions, mimicking humans. Use it to your advantage.
-
There are reports of slimes showing signs of sentience. Further research is recommended.
-
One of the reported signs is their speech capability. Following facts have been gathered: they talk more often to those who feed them; they react to being called by the number, but 'slimes' is also acceptable; they are able to understand being ordered to "follow" someone, or "stop". There is a report of slime releasing the assistant after a scientist shouted at it, and then calmly ordered a slime to stop.
-
We've made a modification of a health scanner that is intended for the use on slimes. Two are available in the smartfridge.
-
We've come to a conclusion that 5 units of reagents are not necessary to cause slime core reactions. You actually only need one unit.
-
-
-
16 May 2014
-
Menshin updated:
-
-
Thanks to Nanotrasen's Engineering division, the power wiring of the station has a received a complete overhaul. Here are the highlight features in CentCom report :
-
Improved way of connecting cables! Finished is the time of "laying and praying" : it's now as simple as matching ends of each cables!
-
Unified way of connecting machines! Every machine is connected to a powernet through a node cable (it still needs to be anchored to receive power though)!
-
Lots of improvements in the cable cutting tools! Gone is the odd magic of physically separated yet still connected powernets (contact CentCom Engineering Division if you still witness something weird, we always have a fired form ready for our engineers!)!
-
Any conscientious (or not!) Station Engineer may check an "updated wiring manual" at http://www.tgstation13.org/wiki/Wiring#Power_Wires
-
-
-
14 May 2014
-
Ikarrus updated:
-
-
A recent breakthrough in Plasma Materials Research has led to the development of a stronger, tougher plasteel alloy that can better resist extreme heat by up to four times. A live demonstration preformed during a press conference earlier today showed a segment of reinforced wall resisting an attack by Thermite. The corporation also announces that upgrades to its existing stations is already underway.
-
-
-
07 May 2014
-
/Tg/station Code Management updated:
-
-
/tg/station code is now under a feature freeze until 7th June 2014. This means that the team will be concentrating on fixing bugs instead of adding features for the month.
-
You can find more information about the feature freeze here. http://tgstation13.org/phpBB/viewtopic.php?f=5&t;=273
-
This is the perfect time to report bugs, which you can do so here. https://github.com/tgstation/-tg-station/issues/new
-
-
-
30 April 2014
-
Giacom updated:
-
-
You now have to give a reason when calling the shuttle.
-
The amount of time it takes for the shuttle to refuel is now a config option, the default is 20 minutes.
-
-
Gun Hog updated:
-
-
Certain Enemies of the Corporation have discovered a critical exploit in the firmware of several NanoTrasen robots that could prevent the safe shutdown of units corrupted by illegally modified ID cards, dubbed "cryptographic sequencers".
-
NanoTrasen requires more research into this exploit, this we have issued a patch for AI and Cyborg software to simulate this malfunction. All NanoTrasen AI units are advised to only allow testing in a safe and contained environment. The robot can safely be reset at any time.
-
Unfortunately, a robot corrupted by a "cryptographic sequencer" still cannot be reset by any means. NanoTrasen urges regular maintenance and care of all robots to reduce replacement costs.
-
-
-
27 April 2014
-
Ikarrus updated:
-
-
NT Human Resources announces an expansion of the List of Company-Approved Hair Styles, as well as more relaxed gender restrictions on hair styles. Check with your local company-sponsored hairstylist to learn more.
-
-
-
22 April 2014
-
Ikarrus updated:
-
-
ID Computers have been modified to allow Station Heads of Staff to assign and remove access to their departments, as well as stripping the rank of their subordinates. An ID computer on the bridge is available for the use of this function.
-
-
-
20 April 2014
-
Gun Hog updated:
-
-
NanoTrasen Emergency Alerts System 4.20 has been released!
-
In the unfortunate event of any nuclear device being armed, the station will enter Code Delta Alert.
-
Should the nuclear explosive be disarmed, the station shall automatically return to its previous alert level.
-
-
Steelpoint updated:
-
-
A new AI satellite has been constructed in orbit of Space Station 13!
-
The satellite exclusivly is used to hold a station's Artifical Intelligence Unit, the satellite contains a miried of turret and motion sensor defences. A transit tube network is used to connect the satellite to the station, with the transit room being located at engineering south of the engi escape pod.
-
The AI Upload however has been moved north slightly and is connected directly to the bridge.
-
In addition, the Gravity Generator has been relocated from its prior position in engineering to the location of the old AI Upload, increasing its defence and logical positioning.
-
-
-
11 April 2014
-
Jordie0608 updated:
-
-
Wood planks can be used to make wood walls and airlocks; flammability not included
-
-
-
10 April 2014
-
Ikarrus updated:
-
-
Centcom officials have announced a new initiative to combat misuse of their Emergency Shuttle Service. After the shuttle has been recalled several times over the course of a simple work shift, Centcom will attempt to trace the signal origin and pinpoint its source for station authorities.
-
-
Steelpoint updated:
-
-
Thanks to Nanotrasen's Construction division, the brig has recieved a overhaul in its design, to increase ease of movement, including an addition of a "prisioner release room" and a insanity ward.
-
Furthing concerns among commentators, Nanotrasen has shipped out additional security equipment to station armories, including Riot Shotguns, tear gas grenades, additional securitron units and military grade Ion Guns.
-
JaniCo have released a new unique product called the JaniBelt. Capable of holding most standard issue janitorial equipment, designed to help relive inventory management among station janitors. In addition the Jani Water Tank has had its reserve of space clenaer increased to 500 units, up from 250.
-
-
-
Validsalad updated:
-
-
After concerns raised by security personel, new armor has been shipped that covers the lower waist and readjusts the helmet for comfort.
-
In addition, the aging riot shield has been replaced with a newer, more modern, apperance.
-
-
-
-
03 April 2014
-
Ikarrus updated:
-
-
NT AI Firmware version 2554.03.03 includes a function to notify the master AI when a new cyborg is slaved to it.
-
-
-
02 April 2014
-
Giacom updated:
-
-
The syndicate suit is now black and red, because black and red is the new red.
-
The Ruskie DJ Station now has a classy orange syndicate suit.
-
Security officers have been equipped with the latest in flashlight technology. You can find a SecLite™ inside your security belt or you can dispense one from the security vending machine.
-
-
Ikarrus updated:
-
-
Nanotrasen Construction division is pleased to announce the unveiling of our brand now state-of-the-art Space Station 13 v2.1.3!
-
-Scaffolding used during construction has been repurposed as an expanded maintenance around the station.
-
-Our new Emergency Transport Shuttle Mk III will be making its debut in times of crisis. Includes a new cargo area and extra security features.
-
-A redesigned brig will give security staff more peace of mind with added security features
-
-The armory is now protected by a motion alarm.
-
-The armory is now equipped with security hardsuits.
-
-A new command EVA wing has been added to EVA Storage. Station heads of staff will be able to make use of suits stored here.
-
-A new garden area will be made publicly available for everyone to enjoy some R&R; during their company-endorsed break periods
-
-A new Testing Lab has been added to the Science department for any field experiments that need to be run.
-
-The mining ore redemption machine has been relocated to the Cargo Delivery Office.
-
-
-
01 April 2014
-
Aranclanos updated:
-
-
Made combat more hardcore for hardcore players like myself. This won't be reverted.
-
-
Malkevin updated:
-
-
Sacrifice Cult - Cult has received a massive overhaul to how it works, focusing more on cult magics and sacrificing unbelievers. The most important changes are:
-
Cult starts off with alot more cultists, 6 cultists below 30 players and 9 above. The HoP can no longer be a round start cultist but is still convertible.
-
Cultists start off with join, blood, self, and hell. These give the runes for the sacrifice and convert.
-
Conversions now require three cultists and converts no longer reward words, words must be obtained via sacrifice
-
Sacrificing players traps their soul in a soul stone, which can then be used on construct shells to implant the souls in them
-
Constructs shells can be summoned via a new rune (travel, hell, technology)
-
You can no longer convert the sacrifice target, you dozzy sods
-
Summoning Narsie when she is not an objective leads to !!FUN!!
-
Wrote a system to allow all mob types to be sacrificable - currently only corgis are in the new system
-
Holy Water will DECONVERT cultists, takes around 35 units and two minutes to succeed
-
Hitting a mob that contains holy water with a tome will convert that water to unholy water, conversely hitting any container that contains unholy water with a bible as chaplain will 'cleanse' the taint.
-
Unholy water works like a combination synaptazine and hyperzine, with a twist of branes dimarge and highly toxic to non-cultists
-
Cargo can order a religious supplies crate - it contains flasks of holy water, candles, bibles, and robes
-
Cultists have an innate ability to communicate to the cult hive mind (BE AWARE - THIS DOES A LOT OF DAMAGE), cultists can also communicate via the new Commune option on their tomes (the Read Tome option has been shifted under the Notes option)
-
-
MrStonedOne updated:
-
-
Cyborgs can now use AI click shortcuts. (shift click on doors opens them, etc) (including ones added below)
-
New silicon click shortcut: Control+Shift click on doors toggles access override
-
New silicon click shortcut: Control click turret controls toggles them on or off
-
New silicon click shortcut: Alt click turret controls toggles lethal on or off
-
Cyborg hotkey mode was massively improved. 1-3 selects module slot, q unloads the active module. use hotkeys-help as cyborg for more details
-
-
-
31 March 2014
-
Ikarrus updated:
-
-
Fabricator blueprints for cyborg parts have been updated to allow the debugging of cyborg units prior to boot. To access the debug functions, assemble a cyborg following standard procedure. Before inserting the MMI, use a multitool on the cyborg body. Note that this update also moves designation data into the system files, so pens can no longer be used to name cyborgs.
-
-
MrPerson updated:
-
-
Toggling the "Play admin midis" preference will pause any playing songs. Turning midis back on will resume the current song where it was.
-
If there's a song playing while you have midis off and you then turn midis on, the current song will begin playing for you.
-
-
-
28 March 2014
-
Aranclanos updated:
-
-
Workaround with the click cooldowns. They should feel faster. If you think that anything needs a click cooldown (like grilles, mobs, etc.), report it to me.
-
Here's the fun part, this is a test for the community and players, I hope I won't be forced to revert this and hear "this is why we can't have nice things". Again, if anything needs a cooldown, report it to me. Have fun with your fast clicks spessmans.
-
-
-
25 March 2014
-
Ikarrus updated:
-
-
A security review committee has approved an update to all Nanotrasen airlocks to better resist cryptographic attacks by the enemy.
-
*New internal wiring will be able to withstand attacks, and panel wires will no longer be left unusable after an attack.
- *Welding tools will now be able to cut through welded doors that have been damaged.
- *Damaged electronics are now removable and replaceable following standard procedure.
- *Airlocks will not be able to operate autonomously until the electronics are replaced.
-
Hair sprites have been given a visual lift. For best results, set your hair color to be slightly lighter than how you want it to look. For dark hair, do not use a color darker than 80% black.
-
-
-
22 March 2014
-
Giacom updated:
-
-
Gravity is no longer beamed from CentCom! Instead, there is a new gravity generator stored near Engineering that will provide all the gravity for the station and the z level. Please remember that it gives off a lot of radiation when charging or discharging, so wear protection.
-
-
MrPerson updated:
-
-
Added a new, somewhat experimental system to delete things. This is basically a port from /vg/, so big thanks to N3X15.
-
As a result, bombs and the singularity should be much less laggy.
-
There may be issues with "phantom objects" or other problems with stuff that's suppoed to be deleted or objects interacting with deleted objects. Please report any issues right away!
-
-
-
18 March 2014
-
Ikarrus updated:
-
-
NT R&D; releases AI Patch version 2553.03.18, allowing all Nanotrasen AIs to override the access restrictions on any airlock in case of emergency. Nanotrasen airlocks have been outfitted with new amber warning lights that will flash while the override is active. Should maintenance teams need to restore an airlock's restrictions without using the AI, pulsing the airlock's ID Scanner wire will reset the override.
-
-
-
17 March 2014
-
Giacom updated:
-
-
Machines that had their programming overriden by a Malf AI will no longer attack loyal cyborgs; they will still attack cyborgs that aren't loyal, to the Malf AI that hacked them, though.
-
You only require a single unit of blood to mutate viruses now, instead of 5.
-
-
-
15 March 2014
-
Steelpoint/Validsalad updated:
-
-
After a review with CentCom, all Security Officers will now begin their shifts with a Stun Baton in their backpack. To avoid inflating costs however all Stun Batons have been removed from Security lockers except from the brig.
-
After decades of usage CentCom has replaced the Security Uniform, Armor, Belt and Helmet with a newer, more modern design.
-
-
-
14 March 2014
-
Ikarrus and Nienhaus updated:
-
-
Nanotrasen Corporate announced a revised dress codes primarily affecting senior station officers. A new uniform for Heads of Personnel will be shipped out to all NT Research stations.
-
-
-
12 March 2014
-
Yota updated:
-
-
Cameras finally capture the direction you are facing.
-
-
-
10 March 2014
-
Giacom updated:
-
-
Added two new plasma-level disease symptoms, both are very !FUN! and mess with your genetics.
-
Virologists now only need to use a single unit of virus food, mutagen or plasma to generate symptoms. You can take a single unit by using a dropper, if you change its unit transfer amount in the object tab..
-
Changed the map to make it harder to escape from the Labor Camp. Please continue using it.
-
-
Miauw updated:
-
-
Added changeling space suits. These allow changelings to survive in space, internals are not needed. They do not provide any sort of armor and slow your chemical regeneration.
-
-
-
05 March 2014
-
Various Coders updated:
-
-
Ling rounds are LINGIER, with more lings at a given population.
-
Many simple animals can ventcrawl, including bats. All ventcrawlers must alt-click to ventcrawl.
-
An automatic tool to store and replace machine parts has been added to R&D.;
-
Most stuns have been reduced in from 10+ to 5 ticks in duration.
-
Shoes no longer speed you up.
-
Added fancy suits, which can be orderd from cargo.
-
Added sombrerors and ponches.
-
Cuff application time reduced 25%.
-
Fire will cause fuel tanks to explode.
-
Mutagen has been reduced in lethality. Notably, 15 units are not guaranteed to crit the recipient.
-
-
-
03 March 2014
-
ChuckTheSheep updated:
-
-
Round-end report now shows what items were bought with a traitor's or nuke-op's uplink and how many TCs they used.
-
-
Drovidi Corv updated:
-
-
Facehuggers no longer die to zero-force weapons or projectiles like lasertag beams.
-
-
Incoming5643 updated:
-
-
Improved blob spores to zombify people adjacent to their tile.
-
If a hand-teleporter is activated while you're stuck inside a wall, you'll automatically go through it.
-
-
Miauw updated:
-
-
Decreased the lethality of Mutagen in small doses.
-
-
TZK13 updated:
-
-
Added Incendiary Shotgun Shells made at a hacked autolathe; Xenos be wary.
-
-
-
26 February 2014
-
Incoming5643 updated:
-
-
Color blending Kitty Ears have returned.
-
-
-
-
-
25 February 2014
-
Incoming5643 updated:
-
-
Colored burgers! Include a crayon in your microwave when cooking a burger and it'll come out just like a Pretty Pattie.
-
Fixed AI's being able to interact with syndicate bombs.
-
-
-
-
-
24 February 2014
-
Ergovisavi updated:
-
-
Added Advanced Mesons and Night Vision Goggles to the protolathe.
-
-
Giacom updated:
-
-
Silicons no longer stop statues from moving when in sight.
-
Increased the health of statues.
-
Increased the range of statues's blinding spell.
-
-
HornyGranny updated:
-
-
Reduced the range of Violin notes.
-
Low quality violin midi-notes replaced with better .ogg-notes.
-
-
Incoming5643 updated:
-
-
Improved the Syndicate Implant bundle to contain freedom, uplink, EMP, adrenalin and explosive implanters.
-
Added Mineral Storeroom to R&D; containing the Ore Redemption Machine; no more assistants stealing your ore in the open hallway.
-
-
Miauw updated:
-
-
No more removing cursed horseheads.
-
-
Razharas updated:
-
-
Fixed infinite telecrystal exploit.
-
-
-
-
-
19 February 2014
-
Aranclanos updated:
-
-
Removed the chance of your hat falling off when slipping.
Mining has been significantly overhauled. Hostile alien life has infested the western asteroid! Miners are given an equipment voucher to obtain equipment to protect themselves. There is a spare voucher in the HoP's office, should someone want to switch to mining mid-shift.
-
The Ore Redemption Machine has been added just outside of the Science wing. Ore goes in, Sheets go out, and points are tallied on the machine. Insert your ID to claim points, then spend them on goods at Mining Equipment Lockers. You require specific accesses to tell the Ore Redemption Machine to unload its sheets.
-
Should you not care for being eaten alive by horrible alien life, it is suggested you stick to the eastern asteroid, where there is no hostile alien life... though the yield on ore is not as good.
-
Most ore is no longer visible to the naked eye. You must ping it with a Mining Scanner (Available in all mining lockers) to locate nearby ore. Make sure to equip your mesons first, or it won't be visible.
-
Mesons no longer remove the darkness overlay, you must properly light your environment. Hull breaches to space can still be clearly seen, and it will still protect you from the singulo.
-
Mineral spawn rates have been significantly tweaked to cut down on unnecessary inflation of mineral economy.
-
The asteroid no longer has ambient power on the entire asteroid. AI's who go onto asteroid turf will slowly die of power loss. Mining outposts are unaffected.
-
Fixed an issue where projectiles shot by simple mobs or thrown would hit multiple times.
-
-
Fleure updated:
-
-
Reduced chicken crates to contain 1-3 chicks, down from 3-6
-
Increased fertile chicken egg chance from 10% to 25%
-
-
Yota updated:
-
-
Photograph now rendered crisp and clean, so that we may enjoy them in their 0.009 megapixel goodness.
-
Cameras should now capture more of what they should, and less of what they shouldn't.
-
-
-
-
-
09 February 2014
-
ADamDirtyApe updated:
-
-
The lawyer now spawns with a Sec Headset.
-
-
Demas updated:
-
-
Banana peel size is based on banana size.
-
Bigger peels slip for longer than smaller ones. Remember that produce size is based on potency.
-
The clown now spawns with a decent sized banana.
-
The banana mortar now shoots 65 potency peels.
-
-
Giacom updated:
-
-
A new hostile statue mob, can only move when not being observed, tends to break lights and cause blindness.
-
-
Incoming5643 updated:
-
-
Blob Zombies! When a Blob Spore moves over a dead human body it will infect the body and revive it as a more powerful varient of the spore. Has double the health and deals more damage.
-
-
Neerti updated:
-
-
Sec Belts can now hold Stun Batons.
-
Stun Batons now only take 10 seconds to fully recharge.
-
-
Razharas updated:
-
-
Tables can now be used as an alternative way to craft makeshift items. Simply click-drag table to yourself bring up a list.
-
-
adrix89 updated:
-
-
Spray Bottles can no longer wet up to three tiles with water.
-
Spray Bottles have a third higher release volume that wets a single tile.
-
Water slip times are reduced to the same stun times as soap.
-
-
-
-
-
08 February 2014
-
MrPerson updated:
-
-
Added a NanoUI for the SMES
-
-
Razharas updated:
-
-
Adds more constructible and deconstructable machines!
-
Added constructible miniature chemical dispensers, upgradable
-
Sleepers are now constructible and upgradable, open and close like DNA scanners, and don't require a console
-
Cryogenic tubes are now constructible and upgradable, open and close like DNA scanners, and you can set the cryogenic tube's pipe's direction by opening its panel and wrenching it to connect it to piping
-
Telescience pads are now constructible and upgradable
-
Telescience consoles are now constructible
-
Telescience tweaked (you can save data on GPS units now)
-
Teleporters are now constructible and upgradable, the console has a new interface and you can lock onto places saved to GPS units in telescience
-
Teleporters start unconnected. You need to manually reconnect the console, station and hub by opening the panel of the station and applying wire cutters to it.
-
Biogenerators are now constructible and upgradable
-
Atmospherics heaters and freezers are now constructible and upgradable and can be rotated with a wrench when their panel is open to connect them to pipes. Screw the board to switch between heater and freezer.
-
Mech chargers are now constructible and upgradable
-
Microwaves are now constructible and upgradable
-
All kitchen machinery can now be wrenched free
-
SMES are now constructible
-
Dragging a human's sprite to a cryogenic tube or sleeper will put them inside and activate it if it's cryo
-
Constructible newscasters, their frames are made with autolathes
-
Constructible pandemics
-
Constructible power turbines and their computers
-
Constructible power compressors
-
Constructible vending machines. Screw the board to switch vendor type.
-
Constructible hydroponics trays
-
Sprites for all this
-
This update will have unforeseen bugs, please report those you find at https://github.com/tgstation/-tg-station/issues/new if you want them fixed.
-
As usual, machines are deconstructed by screwing open their panels and crowbarring them. While constructing machines, examining them will tell you what parts you're missing.
-
-
-
-
-
05 February 2014
-
Yota updated:
-
-
Handling a couple flashlights will no longer transform you into the sun. Each light source will have deminishing returns.
-
Inserting lights into containers should no longer dim other lights.
-
-
-
-
-
03 February 2014
-
Demas updated:
-
-
Changes windoor and newscaster attack messages to be consistent with windows and grilles. This removes the distracting boldness from windoor attack messages, which is reserved for userdanger, and names the attacker.
-
Thrown items now play a sound effect on impact. The volume of the sound is based on the item's throwforce and/or weight class.
-
The fireaxe now has 15 throwforce, when previously it had only 1. But why would you throw it away, anyway?
-
Projectiles now play a sound upon impact. The volume of the sound depends on the damage done by the projectile. Damageless projectiles such as electrodes have static volumes. Practise laser and laser tag beams have no impact sound.
-
Added sear.ogg for the impacts of damaging beams such as lasers. It may be replaced if player feedback proves negative.
-
-
-
-
-
02 February 2014
-
Demas updated:
-
-
Attack sounds for all melee weapons! No more silent attacks.
-
The volume of an object's attack sound is based on the weapon's force and/or weight class.
-
Welders, lighters, matches, cigarettes, energy swords and energy axes have different attack sounds based on whether they're on or off.
-
Weapons that do no damage play a tap sound. The exceptions are the bike horn, which still honks, and the banhammer, which plays adminhelp.ogg. Surely nothing can go wrong with that last one.
-
When you tap someone with an object, the message now uses "tapped" or "patted" instead of attacked. The horn still uses HONKED, and the banhammer still uses BANNED.
-
You won't get the "armour has blocked an attack" message for harmless attacks anymore.
-
Adds 5 force to the lighter when it's lit. Same as when you accidentally burn yourself lighting it.
-
Removes boldness from item attack messages on non-human mobs. The attack is bolded for a player controlling a non-human mob. Now your eyes won't jump to the chat when it's Pun Pun who's being brutalised.
-
Blood will no longer come out of non-human mobs if the attack is harmless.
-
Adds a period at the end of the catatonic human examine message. That's been bugging me for years.
-
The activation and deactivation sounds of toy swords, energy swords and energy shields are slightly quieter. Energy swords and shields are now slightly louder than toys.
-
You can no longer light things with burnt matches.
-
Match, cigarette and lighter attack verbs, forces and damage types change based on whether the object is lit or not.
-
Fixes a bug with the energy blade that kept it at weight class 5 after it was deactivated. Who cares, it disappears upon deactivation.
-
Changes the welder out of fuel message slightly to be less fragmented.
-
Removes dead air from a lot of weapon sound effects to make them more responsive. In other words, the fire extinguisher attack sound will play a lot sooner after you attack than before.
-
Equalised the peak volumes of most weapon sounds to be -0.1dB in an attempt to make volumes based on force more consistent across different sounds.
-
-
-
-
-
01 February 2014
-
Ergovisavi updated:
-
-
Walking Mushrooms will now attack and eat each other! They're all a little unique from each other, no two shrooms are exactly alike, and a better quality harvest means stronger Walking Shrooms. Pit them against each other for your entertainment.
-
Each mushroom will have a different colored cap to identify them. When mushrooms eat each other, they get stronger. The resulting mushroom will drop more slices when cut down to harvest, and will have better quality slices.
-
Don't hurt them yourself, though, or you'll bruise them, and mushrooms won't get stronger from eating a bruised mushroom. If your mushroom faints, feed it a mushroom such as a plump helmet to get it back on its feet. It will slowly regenerate to full health eventually.
-
-
-
-
-
30 January 2014
-
Balrog updated:
-
-
Syndicate Playing Cards can now be found on the Syndicate Mothership and purchased from uplinks for 1 telecrystal.
-
Syndicate Playing Cards are lethal weapons both in melee and when thrown, but make the user's true allegiance to the Syndicate obvious.
-
Sprites are courtesy of Nienhaus.
-
-
Demas updated:
-
-
Adds thud sounds to falling over
-
Known bug: Thuds play when cloning initialises or someone is put into cryo. This will be fixed.
-
-
Ergovisavi updated:
-
-
Gibtonite, the explosive ore, can now be found on the asteroid. It's very hard to tell between it and diamonds, at first glance.
-
Gibtonite deposits will blow up after a countdown when you attempt to mine it, but you can stop it with an analyzer at any time. It makes for a good mining explosive.
-
The closer you were to the explosion when you analyze the Gibtonite deposit, the better the Gibtonite you can get from it.
-
Once extracted, it must be struck with a pickaxe or drill to activate it, where it will go through its countdown again to explode!
-
Explosives will no longer destroy the ore inside of asteroid walls or lying on the floor.
-
-
Miauw updated:
-
-
Adds changeling arm blades that cost 20 chems and do 25 brute damage.
-
Arm blades can pry open unpowered doors, replace surgical saws in brain removal, slice tables and smash computers.
-
-
MrPerson updated:
-
-
Mobs now lie down via turning icons rather than preturned sprites.
-
You can lie down facing up or down and the turn can be 90 degrees clockwise or counterclockwise.
-
Resting will always make you lie to the right so you look good on beds.
-
Please report any bugs you find with this system.
-
-
Petethegoat updated:
-
-
Increased the walk speed. Legcuffed speed is unaffected, and is still suffering.
-
Sped up alien drones, they are now the same speed as sentinels.
-
-
-
-
-
28 January 2014
-
Demas updated:
-
-
Adds thud sounds to falling over
-
Known bug: Thuds play when cloning initialises or someone is put into cryo. This will be fixed.
-
-
-
-
-
26 January 2014
-
Balrog updated:
-
-
Syndicate Playing Cards can now be found on the Syndicate Mothership and purchased from uplinks for 1 telecrystal.
-
Syndicate Playing Cards are lethal weapons both in melee and when thrown, but make the user's true allegiance to the Syndicate obvious.
-
Sprites are courtesy of Nienhaus.
-
-
-
-
-
25 January 2014
-
Miauw updated:
-
-
Adds changeling arm blades that cost 20 chems and do 25 brute damage.
-
Arm blades can pry open unpowered doors, replace surgical saws in brain removal, slice tables and smash computers.
-
-
-
-
-
24 January 2014
-
Ergovisavi updated:
-
-
Gibtonite, the explosive ore, can now be found on the asteroid. It's very hard to tell between it and diamonds, at first glance.
-
Gibtonite deposits will blow up after a countdown when you attempt to mine it, but you can stop it with an analyzer at any time. It makes for a good mining explosive.
-
The closer you were to the explosion when you analyze the Gibtonite deposit, the better the Gibtonite you can get from it.
-
Once extracted, it must be struck with a pickaxe or drill to activate it, where it will go through its countdown again to explode!
-
Explosives will no longer destroy the ore inside of asteroid walls or lying on the floor.
-
-
-
-
-
21 January 2014
-
MrPerson updated:
-
-
Mobs now lie down via turning icons rather than preturned sprites.
-
You can lie down facing up or down and the turn can be 90 degrees clockwise or counterclockwise.
-
Resting will always make you lie to the right so you look good on beds.
-
Please report any bugs you find with this system.
-
-
-
-
-
19 January 2014
-
KazeEspada updated:
-
-
The water cooler is now stocked with paper cups. You can refill the cups by putting paper in it.
-
-
Rolan7 updated:
-
-
You can now sell mutant seeds, from hydroponics, to Centcom via the supply shuttle.
-
Fixes powersinks causing APCs to stop automatically recharging.
-
-
-
-
-
17 January 2014
-
ManeaterMildred updated:
-
-
Changed the way the Gygax worked. It now has less defense and shot deflection, but is faster and have less battery drain per step.
-
Nerfed the Carbine's brute damage and renamed it to FNX-99 "Hades" Carbine.
-
Nerfed the Gygax defense from 300 to 250.
- Nerfed the Gygax projectile deflection chance from 15 to 5.
- Buffed the Gygax speed from 3 to 2, making it faster.
- Reduced the battery use when moving from 5 to 3.
-
- The Mech Ion Rifle now has a faster cooldown, from 40 to 20.
- Nerfed the carbine's Brute Damage from 20 to 5.
-
- Please post feedback to these changes on the forums.
-
-
-
-
-
-
15 January 2014
-
Dumpdavidson updated:
-
-
EMPs affect the equipment of a human again.
-
EMP flashlight now recharges over time and its description no longer reveals the illegal nature of the device.
-
EMP implant now has two uses. EMP kit now contains two grenades.
-
-
-
-
-
14 January 2014
-
Fleure updated:
-
-
Added spider butchery.
-
Added spider meat, legs, and edible eggs.
-
Added new spider related meals.
-
-
Giacom updated:
-
-
Because of an emagged cyborg's explosion, the MMI will die with it.
-
The self-respiration symptom will now properly keep you from dying of oxygen loss.
-
The Stimulant symptom's activation chance was increased so you had a constant flow of hyperzine.
-
-
Incoming updated:
-
-
A new training bomb has been added to the armoury, which will allow you to train your wire cutting skills to disarm real syndicate bombs.
-
-
ManeaterMildred updated:
-
-
Updated research designs.
-
The Protolathe can now build a Ion Rifle.
-
The Exosuit Fabricator can now build a Mech Ion Rifle, a Mech Carbine and a Mech-Mounted Missile Rack.
-
-
SirBayer updated:
-
-
Armor now reduces damage by the protection percentage, instead of randomly deciding to half or full block damage by those percentages.
-
Shotguns, with buckshot shells, will fire a spread of pellets at your target, like a real shotgun blast.
-
-
-
-
-
12 January 2014
-
VistaPOWA updated:
-
-
Added Syndicate Cyborgs.
-
They can be ordered for 25 telecrystals by Nuclear Operatives. A ghost / observer with "Be Operative" ticked in their game options will be chosen to control it.
-
Their loadout is: Crowbar, Flash, Emag, Esword, Ebow, Laser Rifle. Each weapon costs 100 charge to fire, except the Esword, which has a 500 charge hitcost. Each borg is equipped with a 25k cell by default.
-
Syndicate borgs can hear the binary channel, but they won't show up on the Robotics Control computer or be visible to the AI. Their lawset is the standard emag one.
-
Added two cyborg recharging stations to the Syndicate Shuttle.
-
-
-
-
-
11 January 2014
-
Cheridan updated:
-
-
You can now upgrade laser pointers with micro laser parts. It will increase the chance of blinding people.
-
-
Errorage updated:
-
-
Cyborg modules now use a new UI, which is much quicker than a menu.
-
-
Giacom updated:
-
-
The game will now have all background operations disabled, this will result in smoother gameplay but may result in some spike lags, before being set back to normal. The gradually increasing lag should now be gone.
-
You can now emag the crusher, in disposals, to remove the safety. You can use a screwdriver to reset it to the default factory settings.
-
The toxin compensation symptom will stop giving you toxin damage while at full health.
-
-
JJRCop updated:
-
-
The new nuke toy can now be found in your local arcade machine.
-
-
-
-
-
10 January 2014
-
ChuckTheSheep updated:
-
-
Morgue Trays can detect players in their bodies and will now change colour depending on a few things. Red = Dead body with no player inside. Orange = No body but items. Green = A dead body with a player inside.
-
-
Giacom updated:
-
-
You can whisper while in critical, but you will immediately die afterwards DRAMATICALLY. The closer you are to death, the less you can say.
-
The wizard won't spawn so much smoke after they blink.
-
The detective's forensic scanner was upgraded so that it can now scan from afar.
-
Made the ghost's follow button less buggy. Please make an issue report if it still bugs out.
-
-
Rumia29, Nienhaus updated:
-
-
A new alt RD uniform spawns in his locker.
-
-
-
-
-
07 January 2014
-
Fleure updated:
-
-
Janitor now spawns with a service headset.
-
Backpack watertank slowdown decreased.
-
-
-
-
-
04 January 2014
-
Razharas updated:
-
-
Constructable machines now depend on R&D; parts!
-
DNA scanner: Laser quality lessens irradiation. Manipulator quality drastically improves precision (9x for best part) and scanner quality allows you to scan suicides/ling husks, with the best part enabling the cloner's autoprocess button, making it scan people in the scanner automatically.
-
Clone pod: Manipulator quality improves the speed of cloning. Scanning module quality affects with how much health people will be ejected, will they get negative mutation/no mutations/clean of all mutations/random good mutation, at best quality will enable clone console's autoprocess button and will try to clone all the dead people in records automatically, together with best DNA scanner parts cloning console will be able to work in full automatic regime autoscanning people and autocloning them.
-
Borg recharger: Capacitors' quality and powercell max charge affect the speed at which borgs recharge. Manipulator quality allows borg to be slowly repaired while inside the recharges, best manipulator allows even fire damage to be slowly repaired.
-
Portable power generators: Capacitors' quality produce more power. Better lasers consume less fuel and reduce heat production PACMAN with best parts can keep whole station powered with about sheet of plamsa per minute (approximately, wasn't potent enough to test).
-
Autolathe: Better manipulator reduces the production time and lowers the cost of things(they will also have less m_amt and g_amt to prevent production of infinity metal), stacks' cant be reduced in cost, because thatll make production of infinity metal/glass easy
-
Protolathe: Manipulators quality affects the cost of things(they will also have less m_amt and g_amt to prevent production of infinity metal), best manipulators reduces the cost 5 times (!)
-
Circuit imprinter: Manipulator quality affects the cost, best manipulator reduce cost(acid insluded) 4 times, i.e. 20 boards per 100 units of acid
-
Destructive analyzer: Better parts allow items with less reliability in. Redone how reliability is handled, you now see item reliability in the deconstruction menu and deconstructing items that has same or one point less research type level will rise the reliability of all known designs that has one or more research type requirements as the deconstructed item. Designs of the same type raise in reliability more. Critically broken things rise reliability of the design drastically. Whole reliability system is not used a lot but now at least on the R&D; part it finally matters.
-
-
-
-
-
02 January 2014
-
Demas updated:
-
-
Added different colours to departmental radio frequencies. Now you'll be able to filter out or pay attention to each frequency a lot easier.
-
-
Fleure updated:
-
-
Fixed bruisepacks and ointments not working.
-
Fixed injecting/stabbing mouth or eyes when only thick suit worn.
-
-
-
-
-
01 January 2014
-
Errorage updated:
-
-
The damage overlay for humans starts a little later than before. It used to start at 10 points of fire + brute damage, it now starts at 35.
-
-
-
-
-
31 December 2013
-
Fleure updated:
-
-
Paper no longer appears with words on it when blank input written or photocopied
-
Vending machine speaker toggle button now works
-
Building arcade machines works again
-
-
-
-
-
27 December 2013
-
Giacom updated:
-
-
Light explosions will no longer gib dead bodies anymore. C4 will function the same and gib anything attached.
-
Syringe gun projectiles will now display a message when shots are deflected by space suits, biosuits and etc...
-
You can now move out of sleepers by moving, again.
-
-
Miauw updated:
-
-
Monkeys now have a resist button on their HUD.
-
-
-
-
-
21 December 2013
-
Bobylein updated:
-
-
Labcoats can now store bottles, beakers, pills, pill bottles and paper.
-
-
Giacom updated:
-
-
The Labor Camp has been changed based on feedback. Ore boxes added, internals added, safety pickaxes/shovels (might revert if unliked).
-
Labor Camp prisoners, who have earned enough points for freedom, will now have to be alone in the shuttle to move it and to open the middle door; this is in order to prevent free'd prisoners from releasing their comrades.
-
Monkeys no longer walk away when being pulled or grabbed.
-
Anti-breach shield generators have a greater range, and will cover more exposed space tiles.
-
-
Jordie updated:
-
-
Blowing up borgs from the Robotics Console will now actually make them explode. Emagged Cyborgs will explode even more.
-
-
Nienhaus updated:
-
-
Added more poster.
-
-
Perakp updated:
-
-
You can no longer slip while laying down.
-
-
RobRichards, Validsalad updated:
-
-
Added new sprites for the sec-hailer, SWAT gear and riot armour.
-
-
-
-
-
18 December 2013
-
Adrinus updated:
-
-
Playing cards, just like the real thing! Play poker, blackjack, go fish, the limits are limitless! Recommended to use space cash as the poker chips for now
-
-
Giacom updated:
-
-
THE REALISM: Injectors such as syringes, parapens and hypos will not penetrate coveralls with thick material, this includes space suits, biosuits, bombsuits, and their head slot equivalent. Injectors now also consider where you aim, if you aim for the head it will try to use it through the head slot, otherwise it will aim for the body. To clarify, if you are wearing a space helmet and a doctor tries to inject you while aiming at your head, it will protect you until they aim for the unprotected body; same story for wearing a space suit and not a helmet.
-
The syndicate shuttle has been heavily upgraded with a brand new technology which allows the blast doors to the entrance of the shuttle to AUTOMATICALLY close. Whoa. This brand new technology will help keep out those snoopy crew members.
-
Lowered the cooldown of creating virus cultures to 5 seconds. You now only need to mix one unit of synaptizine, in a blood full of an advance virus, to get it to remove a random symptom.
-
-
Incoming updated:
-
-
Rounds will no longer end when the wizard dies and there are still apprentices or traitors/survivors..
-
-
JJRcop updated:
-
-
Transit tube tweaks. You can now put someone into a transit tube pod using grabs and you can empty a transit tube pod by clicking on it.
-
-
Jordie0608 updated:
-
-
Replaced the digital valves in atmospherics with pumps.
-
-
-
-
-
14 December 2013
-
Incoming updated:
-
-
Magic Mania! Powerful new magical tools and skills for wizard and crew alike!
-
Beware the new Summon Magic spell, which will grant the crew access to magical tools and spells and cause some to misuse it!
-
One Time Spellbooks that can be spawned during summon magic that can teach a low level magic skill to anyone! Beware the effects of reading pre-owned books, the magical rights management is potent!
-
Magical Wands that can be spawned during Summon Magic! They come in a variety of effects that mimic both classical wizard spells and all new ones. They come precharged but lack the means to refill them once their magical energy is depleted... Fire efficently!
-
Be aware of the new Charge spell, which can take normally useless spent wands and give them new life! This mysterious effect has been found to wear down the overall magical potency of wands over time however. Beyond wands the clever magical user can find ways to use this spell on other things that may benefit from a magical charge...
-
The Staff of Resurrection, which holds intense healing magics able to defeat death itself! Look out for this invaluable magical tool during castings of Summon Magic.
-
Be on the lookout for a new apprentice! This noble mage is a different beast from most wizards, trained in the arts of defending and healing. Too bad he still works for the wizard!
-
-
-
-
-
09 December 2013
-
Giacom updated:
-
-
New colourful ghost sprites for BYOND members. Sprites by anonus.
-
-
-
-
-
08 December 2013
-
Rolan7 updated:
-
-
Leather gloves can be used to removes lights.
-
Plant, ore, and trash bags have a new option to pick up all items of single type
-
Creating astroturf now works like sandstone, converting all the grass at once.
-
Uranium and radium can be used instead of mutagen. 10 can mutate species, 5 or 2 mutate traits. Highly toxic.
-
Plants require a little light to live. Mushroom require even less (2 units vs 4) and take less damage.
-
-
-
-
-
05 December 2013
-
Razharas updated:
-
-
Reworked how ling stings are done, now when you click a sting in the changeling tab it becomes current active sting, the icon of that sting appears under the chem counter, alt+clicking anyone will sting them with current sting, clicking the icon of the sting will unset it.
-
Monkeys have ling chem counter and active sting icons in their UI.
-
Going monkey -> human will try to equip the human with everything on the ground below it.
-
-
-
-
-
02 December 2013
-
Giacom updated:
-
-
A less annoying virology system! From now on, you can only get low level virus symptoms from virus food, medium level virus symptoms from unstable mutagen and high level virus symptoms from liquid plasma. You can find a list of symptoms, and which chemicals are required to get them, here: http://wiki.ss13.eu/index.php/Infections#Symptoms_Table
-
The virologist starts with a bottle of plasma in his smart fridge.
-
Made it so you cannot accidentally click in the gaps between chem masters.
-
-
-
-
-
01 December 2013
-
cookingboy3 updated:
-
-
Added three new buttons to the sandbox panel.
-
Removed canister menu, replaced it with buttons.
-
Players can no longer spawn "dangerous" canisters in sandbox, such as Plasma, N20, CO2, and Nitrogen.
-
-
-
-
-
30 November 2013
-
Yota updated:
-
-
The identification console will now require that ID and job names follow the same restrictions as player names.
-
NTSL scripts and parrots should now handle apostrophes and such properly. It's about time.
-
NTSL scripts now have a better sense of time.
-
-
-
-
-
28 November 2013
-
Malkevin updated:
-
-
Made the suit storage on the Captain's Tunic more useful than just a place to store your e-o2 tank. You can now store the nuke disk, stamps, medal box, flashes and melee weapons (mainly intended for the Chain of Command), and of course - smoking paraphernalia
-
-
-
-
-
27 November 2013
-
RobRichards updated:
-
-
Nanotrasen surgeons are now certified to perform Limb replacements, The robotic parts used in construction of Nanotrasen Cyborgs are the only parts authorized for crew augmentation, these replacement limbs can be repaired with standard welding tools and cables.
-
-
-
-
-
17 November 2013
-
Laharl Montgommery updated:
-
-
AI can now anchor and unanchor itself. In short, it means the AI can be dragged, if it wants to.
-
-
-
-
-
29 September 2013
-
RobRichards updated:
-
-
Nanotrasen Cyborg Upgrades:
-Standard issue Engineering cyborgs now come equipped with replacement floor tiles which they can replenish at recharge stations.
-
-
-
-
-
-
28 September 2013
-
Ergovisavi updated:
-
-
Mobs can now be lit on fire. Wearing a full firesuit (or similar) will protect you. Extinguishers, Showers, Space, Cryo, Resisting, being splashed with water can all extinguish you. Being splashed with fuel/ethanol/plasma makes you more flammable. Water makes you less flammable.
-
-
-
-
-
-
26 September 2013
-
Cheridan updated:
-
-
Nanotrasen Anomaly Primer:
- Unstable anomalies have been spotted in your region of space. These anomalies can be hazardous and destructive, though our initial encounters with these space oddities has discovered a method of neutralization. Method follows.
-
Step 1. Upon confirmation of an anomaly sighting, report its location. Early detection is key.
- Step 2. Using an atmospheric analyzer at short range, determine the frequency that the anomaly's core is fluctuating at.
- Step 3. Send a signal through the frequency using a radio signaller. Note that non-specialized signaller devices may possibly lack the frequency range needed.
- With the anomaly neutralized and the station brought out of danger, inspect the area for any remnants of the anomaly. Properly researched, we believe these events could provide vast amounts of valuable data.
- Did you find this report helpful?
-
-
-
-
-
-
-
21 September 2013
-
Malkevin updated:
-
-
Due to complaints about Security not announcing themselves before making arrests NT has now issued it's Sec team with loud hailer integrated gas masks, found in their standard equipment lockers. Users can adjust the mask's level of aggression with a screwdriver.
Juggernaut's ablative armor has been adjusted. They have a greater chance to reflect lasers however on reflection they take half damage instead of no damage, basically this adjustment means you should be able to kill a Juggernaut with two laser guns instead of four! Also their reflection spread has been greatly widened, enjoy the lightshow
-
Cargo can now order exile implants.
-
Checking a collector's last power output via analyzers has been moved to multitools, because that actually made sense (betcha didn't know this existed, I know I didn't)
-
Analyzers can now be used to check the gas level of the tank in a loaded radiation collector (yay no more crowbars), you can also use them on pipes to check gas levels (yay no more pipe meters)
-
-
-
-
-
-
-
17 September 2013
-
SuperSayu updated:
-
-
You can no longer strip people through windows and windoors
-
You can open doors by hand even if there is a firedoor in the way, making firedoor+airlock no longer an unbeatable combination
-
Ghosts can now click on anything to examine it, or double click to jump to a turf. Double clicking a mob, bot, or (heaven forbid) singularity/Nar-Sie will let you follow it. Double clicking your own corpse re-enters it.
-
AI can double click a mob to follow it, as well as double clicking turfs to jump.
-
Ventcrawling mobs can alt-click a vent to start ventcrawling.
-
Telekinesis is now part of the click system. You can click on buttons, items, etc, without having a telekinetic throw in hand; the throw will appear when you click on something you can move (with your mind).
-
-
-
-
-
-
13 September 2013
-
JJRcop updated:
-
-
We at Nanotrasen would like to assure you that we know the pain of waiting five minutes for the emergency shuttle to be dispatched in a high-alert situation due to our confirmation-of-distress policy. Therefore, we have amended our confirmation-of-distress policy so that, in the event of a red alert, the distress confirmation period is shortened to three minutes and we will hurry in preparing the shuttle for transit. This totals to 6 minutes, in hope that it will give our very expensive equipment a better chance of recovery.
-
-
-
-
-
-
3 September 2013
-
Cael_Aislinn updated:
-
-
Terbs Fun Week Day 5: Chef gets a Nanotrasen-issued icecream machine with four pre-approved icecream flavours and two official cone types.
-
-
-
-
-
-
2 September 2013
-
Cael_Aislinn updated:
-
-
Terbs Fun Week Day 4: Humans, aliens and cyborgs now show speech bubbles when they talk.
-
-
-
-
-
-
1 September 2013
-
Cael_Aislinn updated:
-
-
Terbs Fun Week Day 3: Detective can reskin his gun to one of five variants: Leopard Spots, Gold Trim, Black Panther, Peacemaker and the Original.
-
-
-
-
-
-
12 September 2013
-
AndroidSFV updated:
-
-
AI Photography: AIs now have two new verbs, Take Picture and View Picture. The pictures the AI takes are centered on the AI's eyeobj. You can use these pictures on a newscaster, and print them at a photocopier.
-
-
-
-
-
-
31 August 2013
-
Cael_Aislinn updated:
-
-
Terbs Fun Week Day 2: RD, lawyers and librarians now spawn with a laser pointer. Don't point them in anyone's eyes!
-
-
-
-
-
-
30 August 2013
-
Cael_Aislinn updated:
-
-
Terbs Fun Week Day 1: Added ghost chilis as a mutation of chili plants. Be careful, they're one of the hottest foods in the galaxy!
-
-
-
-
-
-
21 August 2013
-
Dumpdavidson updated:
-
-
Replaced the EMP grenades from the uplink with an EMP kit. The kit contains a grenade, an implant and a flashlight with 5 uses that can EMP any object or mob in melee range.
-
-
-
-
-
-
18 August 2013
-
Delicious updated:
-
-
Made time and date consistent across medical and security records, mecha logs and detective scanner reports
-
Added date to PDA
-
-
-
-
-
-
13 August 2013
-
Giacom updated:
-
-
Malf AIs now have a new power which will spawn a "borging machine". This machine will turn living humans into loyal cyborgs which the AI can use to take over the station with. The AI will limit themselves by using this ability, such as no shunting, and the machine will have a long cooldown usage.
-
-
-
-
-
-
-
-
-
12 August 2013
-
Giacom updated:
-
-
Changed the blob balance to make the blob start strong but grow slower, resulting in rounds where the blob doesn't instantly get killed off if found out and doesn't immediately dominate after being left alone long enough. AIs no longer have to quarantine the station.
-
-
-
-
-
-
10 August 2013
-
Malkevin updated:
-
-
Cargo Overhaul: Phase 1
-
Ported Bay's cargo computer categoy system
-
Crates have been tweaked significantly. Crates have been reduced to single item types where possible, namely with expensive crates such as weapons and armor. A total of 28 new crates have been added, including chemical and tracking implants, and raw materials can also be bought from cargo for a significant number of points (subject to change)
-
This was a pretty large edit of repetitive data, so no doubt I've made a mistake or two. Please report any bugs to the usual place
-
-
-
-
-
-
6 August 2013
-
Giacom updated:
-
-
NTSL no longer allows you to use a function within another function parameter. This was changed to help prevent server crashes; if your working script no longer compiles this is why.
-
-
-
-
-
-
5 August 2013
-
Kaze Espada updated:
-
-
Nanotrasen has recentely had to change its provider of alcoholic beverages to a provider of lower quality. Cases of the old ailment known as alcohol poisoning have returned. Bar goers are to be weary of this new condition.
-
-
-
-
-
-
4 August 2013
-
Giacom updated:
-
-
Nanotrasen has re-arranged the station blueprint designs to have non-essential APCs moved to the maintenance hallways. Non-essential rooms that aren't connected to a maintenance hallway will have their APC remain. Station Engineers will now have easy access to a room's APC without needing access themselves. Nanotrasen also wishes to remind you that you should not sabotage these easy to access APCs to cause distractions or to lockdown someone in a location. Thank you for reading.
-
-
-
-
-
-
31 July 2013
-
Ricotez updated:
-
-
Atmospherics now has its own hardsuit. Instead of radiation protection it offers fire protection.
-
-
-
-
-
-
21 July 2013
-
Malkevin updated:
-
-
Cultists now start with two words each, and the starting talisman no longer damages you when you use it
-
-
-
-
-
-
-
21 July 2013
-
Cheridan updated:
-
-
Instead of a level-up system where it is possible to acquire all the skills, each skill now costs 1 point, and you can pick up to 5.Husking people, instead of giving you more XP to buy skills, now gives you a skill reset, allowing you to pick new ones.
-
DNA Extract Sting is now free, and is your main mode of acquiring DNA. You can hold up to 5 DNAs, and as you acquire more, the oldest one is removed. If you're currently using the oldest DNA strand, you will be required to transform before gaining more.
-
New abilities! An UI indicator for chemical storage! Fun!
-
-
-
-
-
-
16 July 2013
-
Malkevin updated:
-
-
Summary of my recent changes: Added a muzzle and a box of Prisoner ID cards to the perma wing, RnD can make a new combined gas mask with welding visor, added some atmos analyzers to atmospherics, air alarm circuit boards have their own sprites, package wrapped objects will now loop back round to the mail chute instead of auto-rerouting to disposals, and the detective and captain have access to securitrons through their PDA cartridges.
-
-
-
-
-
15 July 2013
-
Giacom updated:
-
-
A new item has been added to the syndicate catalog. The AI detector is a device disguised as a multitool; it is not only able to be used as a real multitool but when it detects an AI looking at it, or it's holder, it will turn red to indicate to the holder that he should cease supiscious activities. A great and cheap, to produce, tool for undercover operations involving an AI as the security system.
-
-
-
-
-
-
7 July 2013
-
Giacom updated:
-
-
Revamped blob mode and the blob random event to spawn a player controlled overmind that can expand the blob and upgrade pieces that perform particular functions. This will use resources which the core can slowly generate or you can place blob pieces that will give you more resources.
-
-
-
-
-
27 June 2013
-
Ikarrus updated:
-
-
Nanotrasen R&D released a new firmware patch for their station AIs. Included among the changes is the new ability for AIs to interact with fire doors. R&D officials state they feel giving station AIs more influence could only lead to good things.
-
-
-
-
-
16 June 2013
-
Khub updated:
-
-
Job preferences menu now not only allows you to left-click the level (i.e. [Medium]) to raise it, but also to right-click it to lower it. That means you don't have to cycle through all the levels to get rid of a [Low].
-
-
-
-
-
-
15 June 2013
-
Carnie updated:
-
-
DNA-scanner pods (DNA-modifier + cloning), now open and close in a similar fashion to closets. This means you click on them to open/close them. This change was to fix a number of issues, like items being lost in the scanner-pods.
-
As a side-effect, borgs can now clone humans. No harm can become a dead human, so they are not necessarily lawbound to clone them, and such tasks are probably best left to qualified genetics staff.
-
-
Petethegoat updated:
-
-
Updated chemical grenades. The build process is much the same, except they require an igniter-X assembly instead of a single assembly item. You can also just use a cable coil to get regular grenade behaviour.
-
-
-
-
-
9 June 2013
-
Ikarrus updated:
-
-
Server operators may now allow latejoiners to become antagonists. Check game_options.txt for more information.
-
Server operators may now set how traitors and changelings scale to population. Check game_options.txt for more information.
-
-
-
-
-
6 June 2013
-
Giacom updated:
-
-
Emptying someone's pockets won't display a message. In theory you can now pickpocket!
-
-
-
-
-
-
4 June 2013
-
Dumpdavidson updated:
-
-
Headsets can no longer broadcast into a channel that is disabled. Headsets now have a button to turn off the power instead of the speaker. This button disables all communication functions. EMPs now force affected radios off for about 20 seconds.
-
-
-
-
-
2 June 2013
-
Ikarrus updated:
-
-
To reduce costs of security equipment, mounted flashers have been adjusted to use common handheld flashes as their flashbulbs. Although these flashbulbs are more prone to burnout, they can easily be replaced with wirecutters.
-
-
-
-
-
25 May 2013
-
Ikarrus updated:
-
-
CentCom announced some minor restructuring within Space Station 13's command structure. Most notable of these changes is the removal of the Head of Personnel's access to the security radio channel. CentCom officials have stated the intention was to make the HoP's role more specialized and less partial towards security.
-
-
-
-
-
14 May 2013
-
Ikarrus updated:
-
-
Nanotrasen seeks to further cut operating costs on experimental cyborg units.
- -Cyborg chassis will now be made from a cheaper but less durable design.
- -RCDs found on engineering models have been replaced with a smaller model to make room for a metal rods module.
- -Cyborg arms will no longer be long enough to allow for self-repairs.
- NOTE: A cyborg's individual modules have been found to become non-operational should the unit sustain too much structural damage.
-
-
-
-
-
11 May 2013
-
Malkevin updated:
-
-
SecHuds now check for valid clearance before allowing you to change someone's arrest status. There is only one way to bypass the ID check, and its not the usual way.
-
-
-
-
-
-
7 May 2013
-
Ikarrus updated:
-
-
As a part of the most recent round of budget cuts, toolboxes will now be made with a cheaper but heavier alloy. HR reminds employees to avoid being struck with toolboxes, as toolbox-related injuries are not covered under the company's standard health plan.
-
-
-
-
-
5 May 2013
-
Rolan7 updated:
-
-
Cargo manifests from CentComm may contain errors. Stamp them DENIED for refunds. Doesn't apply to secure or large crates. Check the orders console for CentComm feedback.
-
-
-
-
-
2 May 2013
-
Malkevin updated:
-
-
You can now weld four floor tiles together to make a metal sheet
-
The All-In-One Grinder can now grind Metal, Plasteel, Glass, Reinforced Glass, and Wood sheets
-
Made grey backpacks slightly less ugly
-
-
-
-
-
30 April 2013
-
Ikarrus updated:
-
-
Researchers have discovered that glass shards are, in fact, dangerous due to their typically sharp nature. Our internal Safety Committee advises that glass shards only be handled while using Nanotrasen-approved hand-protective equipment.
-
-
-
-
-
24 April 2013
-
Carnie updated:
-
-
DNA reworked: All SE blocks are randomised. DNA-modifier emitter strength affects the size of the change in the hex-character hit. Emitter duration makes it more likely to hit the character you click on. Almost all DNA-modifier functions are on one screen. Balancing -will- be off a bit. Is getting halk to hard/easy? Please report bugs/balancing issues/concerns here: http://forums.nanotrasen.com/viewtopic.php?f=15&t=13083 <3
-
-
-
-
-
26 April 2013
-
Aranclanos updated:
-
-
Exosuit fabricators will now need to be manually updated
-
-
Ikarrus updated:
-
-
Commanding Officers of Nanotrasen Stations have been issued a box of medals to be awarded to crew members who display exemplary conduct.
-
-
-
-
-
-
23 April 2013
-
Malkevin updated:
-
-
Replaced the captain's run of the mill armored vest with his very own unique vest. Offers slightly better bullet protection.
-
-
-
-
-
-
22 April 2013
-
Malkevin updated:
-
-
Overhauled the thermal insulation system
-
All clothing that protected from one side of the thermal spectrum now protects from the other.
-
Armor (although most don't have full coverage) protects between 160 to 600 kelvin
-
Firesuits protect between 60 to 30,000 kelvin (Note: Hotspot damage still exists)
-
CE's hardsuit got its firesuit level protection back
-
Bomb suits function as ghetto riot gear
-
-
-
-
-
22 April 2013
-
Cheridan updated:
-
-
Stungloves removed 5eva.
-
Don't rage yet. Makeshift stunprods(similar in function to stungloves) and spears are now craftable. Make them by using a rod on cable restraits, then adding something.
-
Stun batons/prods now work off power cells, which can be removed and replaced! Use a screwdriver to remove the battery.
-
-
-
-
-
-
17 April 2013
-
Giacom updated:
-
-
If the configuration option is enabled, AIs and or Cyborgs will not be able to communicate vocally. This means they cannot talk normally and need to use alternative methods to do so
-
-
-
-
-
-
10 April 2013
-
Cheridan updated:
-
-
You can now condense capsaicin into pepper spray with chemistry.
-
Pepper spray made slightly more effective.
-
Teargas grenades can be obtained in Weapons and Riot crates.
-
Riot crate comes with 2 sets of gear instead of 3, made cheaper. Beanbag crate removed entirely. Just make more at the autolathe instead. Bureaucracy crate cheaper, now has film roll.
-
NT bluespace engineers have ironed-out that little issue with the teleporter occasionally malfunctioning and dropping users into deep space. Please note, however, that bluespace teleporters are still sensitive experimental technology, and should be Test Fired before use to ensure proper function.
-
-
-
-
-
-
9 April 2013
-
Ikarrus updated:
-
-
Liquid Plasma have been found to have strange and unexpected results on virion cultures. The executive chief science officer urges virologists to explore the possibilities this new discovery could bring.
-
After years or research, our scientists have engineered this cutting-edge technology born from the science of shaving. The Electric Space-Razor 5000! It uses moisturizers to refuel your face while you shave with not three, not four, but FIVE lazer-precise inner blades for maximum comfort.
-
-
-
-
-
4 April 2013
-
Cheridan updated:
-
-
When an AI shunts into an APC, the pinpointer will begin tracking it. When the AI returns to its core, the pinpointer will go back to locating the nuke disc.
-
New sechud icons for sec officers/HoS, medical doctors, and loyalty implants.
-
-
-
-
-
28 March 2013
-
Carnie updated:
-
-
Empty character slots in your preferences screen will now randomize. So they won't initialise as bald, diaper-clad, white-guys.
-
Reworked the savefile versioning/updating code. Your preferences data is less likely to be lost.
-
-
-
-
-
14 March 2013
-
Major_sephiroth updated:
-
-
You can now light cigarettes with other cigarettes, and candles. And cigars. Light a cigar with a candle! It's possible now!
-
-
-
-
-
13 March 2013
-
Elo001 updated:
-
-
You can now open and close job slots for some jobs at any IDcomputer. There is a cooldown when opening or closing a position.
-
-
-
-
-
8 March 2013
-
Kor updated:
-
-
You can now construct/destroy bookcases. This is super exciting and game changing.
-
-
-
-
-
6 March 2013
-
Petethegoat updated:
-
-
Petethegoat says, "Added a new feature involvi-GLORF"
-
Overhauled how grabs work. There aren't any interesting mechanical differences yet, but they should be a lot more effective already. You don't have to double click them anymore. Report any bugs with grabbing directly to me, or on the issue tracker.
-
-
-
-
-
24 February 2013
-
Ikarrus updated:
-
-
AI has been moved back to the center of the station. Telecoms has been moved to engineering.
-
-
Faerdan updated:
-
-
Competely new UI overhaul! Most user interface have been converted.
-
-
-
-
22 February 2013
-
Petethegoat updated:
-
-
Added cavity implant surgery.
-
Additionally, surgery must now be performed with help intent. Some procedures have also been updated.
- As always, check the wiki for details.
-
-
-
-
-
18 February 2013
-
Ikarrus updated:
-
-
The AI has been moved to Research Division, and Telecomms has been moved into the former AI chamber. Affected areas: Telecoms Satellite, Research Division South & Command Sector.
-
-
Incoming updated:
-
-
Added three new types of surgery- lipoplasty, plastic surgery, and gender reassignment.
-
-
Kor updated:
-
-
The RD has lost access to telecomms, and basic engineers have gained it.
-
-
-
-
-
14 February 2013
-
Petethegoat updated:
-
-
Updated surgery: you now initiate surgery with surgical drapes or a bedsheet. Most procedures have changed, check the wiki for details. Currently it's pretty boring, but this paves the way for exciting new stuff- new procedures are very simple to add. Report any bugs directly to me, or on the issue tracker.
-
-
-
-
-
13 February 2013
-
Giacom updated:
-
-
There are now hackable wires for the PA computer. To open the interface, click on it while the wires are exposed/panel is open. All but one wire will do something interesting, see if you can figure it out. You can also attach signallers to the wires so have fun remotely releasing the singularity.
-
New staff of animation icon by Teh Wolf!
-
You can now hack plastic explosives (C4)!
-
You can now use NTSL to send signals! With the function signal(frequency, code) you can create some clever ways to trigger a bomb. NTSL also has two new additions; return in the global scope will now stop the remaining code from executing and NTSL now has "elseif"s, huzzah!
-
-
Kor "I'm quitting I swear" Phaeron updated:
-
-
You've been asking for it for years, it's finally here. Wizards can spend points to buy apprentices.
-
A new wizard artefact, the scrying orb.
-
The spellbook now has descriptions of spells/items visible BEFORE you purchase them.
-
-
Petethegoat updated:
-
-
Traitors with the station blueprints steal objective can now use a photo of the blueprints instead!
-
-
-
-
-
11 February 2013
-
SuperSayu updated:
-
-
Signallers, prox sensors, mouse traps and infrared beams can now be attacheed to grenades to create a variety of mines.
-
A slime core can be placed in a large grenade in place of a beaker. When the grenade goes off, the chemicals from the second container will be transfered to the slime core, triggering the usual reaction.
-
-
-
-
-
10 Feburary 2012
-
Ikarrus updated:
-
-
Implants can now be surgically removed. Hint: They're inside the skull.
-
-
-
-
-
07 February 2012
-
Giacom updated:
-
-
The return of the Nanotrasen Scripting Language! (NTSL) If you haven't heard of NTSL, it is a scripting language within a game for telecomms. Yes, you can create scripts to interact with the radio! For more information, head here: http://wiki.nanotrasen.com/index.php?title=NT_Script But before you do, if you are not an antag, do not create bad scripts which hinders communication.
-
Cameras, mulebots, APCs, radios and cyborgs can have signallers attached to their wires, like airlocks!
-
Cameras have non-randomized wires and the power wire when pulsed will now toggle the camera on and off.
-
Cyborgs have a new wire, the lockdown wire! It will disable/enable the lockdown of a Cyborg when pulsed.
-
The traffic control computer (or more commonly known as the computer which lets you add NTSL scripts) will now have a user log, which will log all user activity. Other users can then view that log and see who has been uploading naughty scripts!
-
NTSL has two new functions! time() and timestamp(format) will help you increase the range of types of scripts you can make, especially time(); since you can then make the scripts know the different between each execution by storing the results in memory.
-
Two new advance disease symptoms! Their names are "Longevity" and "Anti-Bodies Metabolism". Have fun experimenting with them!
-
-
-
-
-
-
31 January 2013
-
Kor "Even in death I still code" Phaeron updated:
-
-
Four new slime types with their own reactions and two new reactions for old slimes.
-
Put a suit of reactive teleport armour back in the RD's office.
-
Chemistry now has two dispensers (with half charge each) so both chemists can actually work at the same time.
-
-
-
-
-
-
27 January 2013
-
Ikarrus updated:
-
-
Security frequency chatter now appears in cyan (Similar to how command is gold)
-
Cheridan updated:
-
-
The plant bags in Hydroponics lockers have been replaced with upgraded models with seed-extraction tech. Activate with via right-click menu or Objects verb tab.
-
Obtaining grass tiles works a bit different now: grass is harvested as a normal plant item, clicking on it in-hand produces the tile.
-
-
-
-
-
26 January 2013
-
Pete updated:
-
-
Added hugging and kicking. I also updated the text styles for clicking on humans in most intents, but they should be pretty much the same.
-
-
-
-
-
25 January 2013
-
Errorage updated:
-
-
All the equipment you spawn with will now contain your fingerprints, giving the detective more ability to tell where items came from and if a crewmember has changed clothing.
-
-
-
Better explosions: Explosion spreading will now be determined by walls, airlocks and poddoors and not just a flat circle.
-
-
-
-
-
20 January 2013
-
Cheridan updated:
-
-
Chickens will now lay a certain number of eggs after being fed wheat, rather than just laying them whenever they felt like it. No more chickensplosions.
-
-
-
-
-
16 January 2013
-
-
Department Security can now be runned:
- Department Security decentralizes security by assigning each officer to a different department. They will be given the radio channel and access to their assigned department along with a security post. The brig has been remapped to be smaller to accomodate this change.
- To run DeptSec: Before compiling, server operators must tick jobs.dm (in WorkInProgress/Sigyn/Department Sec) and use map 2.1.1 instead of 2.1.0.
-
-
-
-
-
/tg/station 13 Presents
-
Directed by S0ldi3rKr4s0
-
& produced by Petethegoat
-
-
Curse of the Horseman.
-
-
-
-
-
12 January 2013
-
Cael Aislinn updated:
-
-
Spiders which will breed and spread through vents. Different classes of vents. AI controlled only at the moment.
-
Farm animals! Cows, goats and chickens are now available. You can order them at Cargo Bay.
-
-
Giacom updated:
-
-
Staff of animation mimics will no longer care whether you are holding the staff or not, they will never attack their creator.
-
Brainrot will only need alkysine to be cured.
-
New spider infestation event based on Cael's spiders. The announcement will be the same as alien infestations.
-
-
-
-
-
11 January 2013
-
Giacom updated:
-
-
Plasma (Air) will give the breather the plasma reagent, for a toxic effect, instead of just straight damage.
-
The agent card will now work inside PDAs/Wallets; meaning the AI won't be able to track you.
-
-
-
-
-
09 January 2013
-
Malkevin updated:
-
-
The owl mask now functions as a gasmask for increased crimestopping power.
-
-
-
Adds the missing icons for the arrest statuses of Parolled and Released, as well as a little blinking icon for chemical implants.
-
-
-
-
-
08 January 2013
-
Cael Aislinn & WJohnston updated:
-
-
Many new icons for aliens (death, sleeping, unconscious, neurotox, thrown/impregnated facehugger etc)
-
Alien larva can now be removed by dangerous and unnecessary surgery (and actually chestburst if they aren't).
-
Alien larva now have sprites to represent their growth: bloody at 0%, pale at 25% and 75% the normal deep red.
-
New icon overlays for representing alien embryo progression.
-
-
-
-
07 January 2013
-
Kor updated:
-
-
Four new slime types with their own extract reactions have been added. Sprites this time were created by Reisyn, SuperElement, and LePinkyFace.
-
-
-
-
02 January 2013
-
Kor updated:
-
-
Slime breeding! There are now 13 varities of slime, each with its own extract reaction (inject five units of plasma). Some of these reactions are reused from the old cores, some are new. As to breeding, each colour of slime has a series of other slimes it may mutate into when it reproduces.
-
-
Giacom updated:
-
-
You can now use wallets as IDs and equip them in your ID slot.
-
Firesuits are once again effective at protecting you from heat. The flames themselves will still hurt you, even with a firesuit. The damage protection is much better with a firesuit though.
-
Engineering starts with a PACMAN generator for jump starting the singularity if the power runs out of the SMES. 30 plasma spawns in Secure Storage inside the crate, to use as fuel for the generator.
-
-
-
-
31 December 2012
-
Giacom updated:
-
-
Simple animals (Corgis, Cats, Constructs, Mice, Etc...) can now pull people.
-
You can quickly stop pulling on someone by pulling them, while they're already being pulled by you. For example, CTRL+Click on something you are pulling to quickly stop pulling it.
-
-
-
-
30 December 2012
-
Giacom updated:
-
-
Emitters now require to be wired in order to work. When there is not enough power it will stop shooting until there is enough power, meaning you do not have to turn it back on, just get the power flowing.
-
You can order shield generators from cargo. Teleporter access is required to open the crate.
-
-
Ikarrus updated:
-
-
Map: Reorganized the Command Sector. The Captain has his own private quarters in addition to his office.
-
-
-
-
26 December 2012
-
Ikarrus updated:
-
-
An agent card is now required to use doors and controls on the Syndicate Shuttle (Nuke).
-
Scanning gas tanks is now a PDA-cart function. Only Atmos and Science PDA carts have this function. Have fun mislabelling gas tanks!
-
-
-
-
23 December 2012
-
Giacom updated:
-
-
The syndicate Military PDA will not show up on possible PDAs to message anymore, even when the receive/signal is turned on. You can still send messages and people can still reply to you.
-
You can now sell processed plasma for supply points. The conversion rate is 2 plasma sheets for 1 point. You must put the plasma in a crate for it to count.
-
The mecha toy prize promotion has officially ended. You can no longer redeem all 11 action mecha figures for a real mech. New toy redeeming promotions in the future will be considered.
-
-
-
-
21 December 2012
-
Ikarrus updated:
-
-
You can now use . to speak over headset department channels in addition to the : and # characters.
-
-
-
-
19 December 2012
-
Nodrak updated:
-
-
You can now use # to speak over headset department channels. For example say "#e Hello" will say "Hello" over the engineering channel. say ":e Hello" will still work as it always has.
-
-
-
-
16 December 2012
-
Giacom updated:
-
-
You can now create your own solar arrays! Order the solar pack crate and you'll receive 21 solar assemblies, 1 electronic which you can put into an assembly to make it a solar tracker and finally the solar computer circuit board. You will get more detailed instructions in the crate, on a piece of paper. Engineering will also start with this crate to help repair destroyed solar arrays.
-
-
Petethegoat updated:
-
-
Added a new option to the key authorisation devices. It removes the maintenance access requirement from all doors. It's irreversible, so only use it in an emergency!
-
-
-
-
15 December 2012
-
Ikarrus updated:
-
-
Swapped the locations of the Library and Chapel. Thanks to killerz104 for the remap.
-
Partial remap of atmos. Monitoring and Refill stations are now the same room.
-
Toxins Mixing should be working properly again.
-
-
-
-
12 December 2012
-
Ikarrus updated:
-
-
Robotics is now a full Science department.
-
Completely remapped Research Division, Robotics, Medbay, and the Library.
-
Partially remapped Cargo Bay, Mining Dock, Engineering, and Atmospherics.
-
Changed the access of the HoS and HoP. For a list, refer to their respective wikipages.
-
-
Errorage updated:
-
-
Miners now have to go through cargo to reach the Mining Dock.
-
-
Petethegoat updated:
-
-
The Detective's revolver no longer cares about how cool you look. It now spawns in his locker.
-
Added new inhands for most energy weapons, by Flashkirby!
-
-
Giacom updated:
-
-
Disintegrate (EI NATH) will leave behind the brain of the victim. Possible productive uses include: trophies, looking awesome as you gib someone and only their brain remains, people to talk to when you get an MMI, pocket brains, a way to get back into the game if the wizard didn't grab your brain and stuffed it into his bag.
-
-
-
-
07 December 2012
-
Giacom updated:
-
-
The detective's scanner was upgraded, it can now scan for reagents in items and living beings. Potential uses include, scanning dead bodies for leftover poison or scanning items to see if they have been spiked.
-
You can now attach photos to newscaster news feeds and wanted posters.
-
You can now emag buttons to remove access from them.
-
The CentComm. Report has been changed so it no longer names potential antagonists. It will just announce the potential round type instead.
-
-
-
-
05 December 2012
-
Cheridan updated:
-
-
Agent cards have been upgraded with microscanners, allowing operatives in the field to copy access levels off of other ID cards.
-
-
Ikarrus updated:
-
-
The Chief Medical Officer, Research Director, Chief Engineer, and Lawyers now have basic Brig access (corridor only)
-
Merged Mining and Cargo radio channels into the Supply Radio. To use the supply channel, use :u
-
Mining Dock remapped to be more compact and closer to cargo.
-
-
Giacom updated:
-
-
The wizard's fireball spell is once again dumbfire. It will fire in the direction of the wizard instead of having to choose from a list of targets and then home in on them.
-
-
-
-
-
02 December 2012
-
Giacom updated:
-
-
Added a new artefact called the "Staff of Animation". You can get it in the Wizard's Spellbook. It will animate objects and items, but not machines, to fight for you. The animated objects will not attack the bearer of the staff which animates them, meaning if you lose your staff, or if it gets stolen, your minions will turn on you.
-
-
-
-
-
30 November 2012
-
Petethegoat updated:
-
-
Janitor has recieved a slightly upgrade mop bucket. The old one is still there too.
-
-
Ikarrus updated:
-
-
Swapped the locations of the Vault and Tech Storage.
-
Cargo Techs, Miners, and Roboticists no longer start with gloves. They are still available from their lockers.
-
-
-
-
-
28 November 2012
-
Kor updated:
-
-
Slimes have replaced roros (finally)! Right now they are functionally identical, but massive expansion of slimes and xenobio is planned. Sprites are by Cheridan.
-
-
-
-
-
25 November 2012
-
Giacom updated:
-
-
Added new very high level symptoms which are only obtainable in the virus crate. Virus crate will also come with mutagen.
-
-
Petethegoat updated:
-
-
Removed clown planet! It'll return shortly in away mission form.
-
-
Ikarrus updated:
-
-
Added Gateway access. Only the RD, HoP, and Captain start with this.
-
New access levels in the brig: -Brig access now opens the front doors of the brig, as well as other lower-risk security areas. -Security access allows you into the break room and equipment lockers. -Holding Cells allows you to use brig timers and lets you in the Prison Wing. -The Detective no longer has Security Equipment access.
-
Significantly increased max cloneloss penalty for fresh clones to 40%.
-
-
-
-
-
23 November 2012
-
Giacom updated:
-
-
Simplified detective stuff. The high-res scanner is gone and instead the detective's normal scanner will instantly report all fingerprints, dna and cloth fibers in full. This was needed because the system took too long to work with and disencouraged detectives. Not only that, it made detectives less of a threat for antagonists and made possible scenerios, such as framing someone by changing fingerprints with someone else, impratical. To replace the computer, the detective will have a full medical computer with access to it. Not only that, but his useless filing cabinet will be replaced with an empty one for serious investigators. Along with this, are fingerprint cards and built-in PDA scanning, as all of security had access to it which was really the detective's thing. The new scanner will also log every finding and you can print them out as a report by clicking the scanner while it is in your active hand.
-
You can toggle the pressure of your sprayer by clicking on it while it is in your active hand. With pressure, the sprayer will spray 10 units on the floor, otherwise it sprays 5. You'll need to turn pressure on to spray water on the floor and make it slippery.
-
AIs in intellicards can no longer move their camera. This will limit them in ability but without making creating and carding an AI to have as a personel door opener impossible.
-
Telecommunication Busses can now be set to change the frequency of a signal. (Allowing you to say.. set the command channel to broadcast to the common channel).
-
Telecommunication was changed to be more effecient. Because of this, Relays don't need a broadcaster or a receiver and you can setup a relay on it's own. You can still disable sending and or receiving from the relay's interface.
-
-
Zelacks updated:
-
-
Plant Analysers now work on seed bags.
-
-
-
-
-
21 November 2012
-
Petethegoat updated:
-
-
The nuke shuttle can now travel at will, and to any location. When travelling from syndicate space to the station, (and vice versa), it will travel through hyperspace.
-
-
Carn updated:
-
-
Changed savefile structure. There's a bunch of unused files left lying around so old savefiles will be purged. Sorry for the inconvenience. Many preferences have been moved to the Preferences verb tab. Everything in that tab is persistent between rounds (it updates your savefile, so even DCing won't reset them). Enjoy x
-
-
Phol updated:
-
-
Added female sprites for most mutant races.
-
-
Cheridan updated:
-
-
SSU manufacturers have issued a product recall! It seems old models shipped with faulty wiring, causing them to short-circuit.
-
-
-
-
-
20 November 2012
-
Kor updated:
-
-
Added Exile Implants to the Gateway room. Someone implanted with an Exile Implant will be able to enter the away mission, but unable to return from it. Not only can they be used for getting rid of dangerous criminals, but revs/stationheads count as dead while on the away mission, and traitor/changeling/wizard assassination targets count as dead if they're on the away mission at round end, allowing for those objectives to be completed peacefully.
-
Added medical hardsuits, sprited by Majorsephiroth. Two of them spawn in EVA. Their most unique/medical oriented feature is being able to hold a medkit in the suit storage slot, allowing you to easily access medicine while keeping your hands free.
-
-
-
-
-
19 November 2012
-
Giacom updated:
-
-
Malf AIs can only shunt to APCs from their core. Meaning their core needs to be alive before they can shunt to another APC. Malf AIs can start a takeover inside an APC now.
-
When taking damage, the next sequence of the overlay will show for a bit before reverting to the overlay you should have. This allows you to know you are taking damage without having to check the text screen.
-
-
-
-
-
18 November 2012
-
Petethegoat updated:
-
-
Ported over BS12 style cameras. They now take a photo of a 3x3 area!
-
Catatonic people (those that have ghosted while alive) now count as dead for assasinate objectives.
-
-
-
-
-
17 November 2012
-
Donkie updated:
-
-
You can now deconstruct and construct Air Alarms and Fire Alarms. Read wiki on howto.
-
-
Giacom updated:
-
-
Medical Cyborgs no longer lose the reagents in their hypospray when switching modes.
-
Spaceacillin will now help stop the spread of diseases.
-
You can once again make floors slippery by spraying water. This was done by increasing the amount the sprayer uses, which is from 5 to 10. You can also empty your sprayer's contents onto the floor with a verb in the Object tab.
-
-
-
-
-
16 November 2012
-
Kor updated:
-
-
Fixed the syndicate teleporter door, making teleport assaults possible. It will once again open when you open the outter door.
-
-
-
-
-
-
15 November 2012
-
Giacom updated:
-
-
You can now name your advance diseases! You can't name already known diseases though.
-
Chemical implants can now hold 50 units instead of 10 units.
-
-
-
-
-
13 November 2012
-
Giacom updated:
-
-
More work to advance diseases. Please report any bugs to the bug tracker, I have tried everything that I can on my own but I'll need lots of people playing to fix the more minor bugs. You can find a guide to making your own diseases here: LINK!
-
Reduced the cost to use Hive Absorb from 40 to 20. This is to help encourage people to use this power more and to use team work.
-
New symptom added! See if you can find it.
-
You can now remove symptoms from a disease using synaptizine.
-
Kor: You can once again debrain changelings. They won't make anyone half-lings though, and you won't be able to tell if the body of a debrained changeling is a changeling by putting a player brain in there.
-
-
Nodrak updated:
-
-
Wizards can no longer cast spells when muzzled. It iss now actually possible to capture a live wizard without constantly injecting them with chloral.
-
You can no longer take bags of holding or mechs to the clown planet.
-
-
-
-
-
11 November 2012
-
Carn updated:
-
-
Admin-ranks changes
- Lots of changes. This is just a brief summary of the most recent changes; still working on proper documentation.
- All admins have access to view-vars, player-panel(for individual mobs), game panel and secrets panel. Most of the things on those pages have their own rights requirements. For instance, you can only use event orientated secrets in the secret panel if you have FUN rights. Debug secrets if you have DEBUG rights. etc.
- Spawn xeno and toggle gravity procs were moved into the secrets panel (fun).
- This may help with understanding which flags do what. Unfortuanately it's still somewhat vague.
- If you have any problems, feel free to PM me at irc.rizon.net #coderbus. I go by the username carn or carnie.
-
-
-
-
-
-
11 November 2012
-
Kor updated:
-
-
New cyborg upgrade available for production that requires illegal and combat tech
-
Summon Guns has a new gun type created by Ausops. It also lets the user know when its been cast now to prevent people trying to buy it multiple times
-
Grilles are no longer immortal in regards to solid projectiles, you can now shoot out windows.
-
-
-
-
-
09 November 2012
-
Giacom updated:
-
-
Cyborgs can now ping and beep! (Say "*beep" and "*ping") Thanks to Rahlzel for the proposal.
-
HULKS WILL NOW TALK IN ALL CAPS AND WILL RANDOMLY SAY HULK THINGS. Thanks to Brotemis for the proposal.
-
Sorry for the inconveniences with advance diseases. They are working much better now!
-
An improved APC sprite by TankNut!
-
-
-
-
-
-
05 November 2012
-
Giacom updated:
-
-
-
AIs can now tweak with a bot's setting like a human who unlocked the bot.
-
-
-
-
-
05 November 2012
-
Errorage updated:
-
-
Being in an area with extremely low pressure will now deal some damage, if you're not protected.
-
Space suits and the captain's armor now protect against pressure damage
-
Slightly lowered all environment damage intakes (temperature, oxygen deprevation) to make up for low pressure damage.
-
Pressure protection finally works properly. Items that protect from pressure (firesuits, space suits, fire helmets, ...) will now properly protect. The pressure damage indicator will update properly based on the pressure effects on you. Black (low) and red (high) mean you are taking damage.
-
Slightly slowed down the speed at which your body temperature changes if you are in a very hot or very cold area. The speed at which you recover from an abnormal body temperature remains the same.
-
-
-
-
-
-
03 November 2012
-
TankNut updated:
-
-
New APC sprite.
-
New Wraith sprite and jaunting animation.
-
-
-
-
-
03 November 2012
-
WJohnston updated:
-
-
New Ablative Armor sprite.
-
-
-
-
-
03 November 2012
-
Giacom updated:
-
-
-
Airborne diseases will not spread through walls now.
-
Reduced queen healing rate to 5. The maximum health will be enough.
-
Aliens can now clear hatched eggs by clicking on them.
-
-
-
-
-
02 November 2012
-
Errorage updated:
-
-
You can once again travel to the station, derelict, satellite and mining z-levels through space. You will also never loop into the same level on transition - So if you are exiting the derelict z-level, you will enter one of the other z-levels.
-
-
-
-
-
-
01 November 2012
-
Giacom updated:
-
-
Aliens now take x2 as much damage from fire based weaponary, instead of x1.5.
-
Doors are now weaker than walls; so normal weapons can destroy them much more easily.
-
-
-
-
-
31 October 2012
-
Giacom updated:
-
-
Advance evolving diseases! Virology can now create, mutate and mix advance diseases together. I replaced the two bottles of blood in Virology with the advance disease. I'll write a wiki article soon enough. Here's a tip: Putting mutagen or virus food (a mixture of milk, water and oxygen) in blood with an existing disease will mutate it to gain symptoms. It can potentially lose old symptoms in the process, so keep backups!
-
-
-
-
-
28 October 2012
-
Errorage updated:
-
-
You can now set your character's age up to 85. This used to be 45.
Added a medical records cabinet to the Detective's office.
-
Added a safe to the vault. Who'll be the first to crack it?
-
-
Nodrak updated:
-
-
The CE has a new pet!
-
-
-
-
-
25 October 2012
-
Flashkirby99 updated:
-
-
Added 18 new hairstyles!
-
-
-
-
-
24 October 2012
-
Giacom updated:
-
-
Throwing eggs will result in the reagents of the egg reacting to the target. (Which can be a turf, object or mob) This creates possibilities like chloral eggs, lube eggs, and many more.
-
Aliens can now acid walls and floors! Not R-Walls though.
-
Facehugger throw range reduced to 5, so aim at humans that are 2 tiles apart from the edge of your screen.
-
Making eggs is a little more expensive but secreting resin is cheaper. (Both cost 75 now)
-
Aliens no longer have a random duration of stunning humans, it's a constant value now of the lower based value.
-
Acid is less random and will be more reliable. Don't bother aciding stuff more than once, as it will waste plasma.
-
You can now target non-dense items (such as facehuggers) with a gun.
-
You can now shoot canisters, computers and windoors to break them.
-
-
-
-
-
18 October 2012
-
Giacom updated:
-
-
As an AI, you can type in the "track with camera" command and get a list of names to show up there. This also works with "list camera" verb. Remember to use space to auto-fill.
-
Welding goggles have been added. They are like welding helmets but they are for the glasses equipment slot. Science and the assembly line are given a pair.
-
Thanks to WJohnston for the welding goggle icons.
-
Small change to the Assembly Line. Instead of six normal flashes, the Assembly Line will instead have two normal flashes and eight synthetic flashes. Synthetic flashes only work once but are designed to be used in construction of Cyborgs.
-
Nar-Sie put on a few pounds. Thanks HornyGranny.
-
-
-
-
-
16 October 2012
-
Giacom updated:
-
-
New changeling powers!
-
Hive Channel/Hive Absorb. Allows you to share your DNA with other changelings, very expensive chemical wise to absorb (download), not so much to channel (upload)! You cannot achieve your objective by sharing DNA.
-
Mimic Voice! You can form your voice of a name you enter. You won't look like them but when you talk, people will hear the name of who you selected. While you're mimicing, you can't regenerate chemicals.
-
Extract DNA! A power that allows you to silently sting someone and take their DNA! Meaning you do not have to absorb someone to become them. Extracting their DNA doesn't count towards completing your objectives.
-
You can now get flares from red emergency toolboxes. Has a 50% chance of a flash-light or a flare spawning.
-
Flare icon by Ausops!
-
Thanks to RavingManiac (Smoke Carter), Roros now lay eggs which can grow into baby roros or be used for cooking recipes. Scientists will need to expose the egg to plasma for it to hatch; while it is orange (grown).
-
A new icon for the map spawned x-ray cameras. Icon by Krutchen.
-
-
-
-
-
13 October 2012
-
Giacom updated:
-
-
Facehuggers have a new animation, thanks to Sly.
-
Firelocks, glass-less airlocks and walls will stop heat.
-
Fires are now more deadly, especially the flames.
-
Fires will now break windows.
-
-
-
-
-
10 October 2012
-
Giacom updated:
-
-
Larva grow a little bit faster when on weeds or when breathing in plasma.
-
-
-
-
-
8 October 2012
-
Giacom updated:
-
-
Thanks to Skasi. Atmospherics has been changed to be made simpler and spawn with the new atmos features, such as the heaters.
-
Radio headsets can only be heard by people wearing them on their ear slot. This will let us do more fun stuff with headsets, such as a traitor encryption key which can listen to all the channels, but not talk in them.
-
-
-
Kor updated:
-
-
A pen no longer spawns in your pocket. Instead, each PDA will spawn with a pen already in it.
-
-
-
-
-
5 October 2012
-
Giacom updated:
-
-
Aliens can now be harmed by fire. They now also take double fire damage, meaning flame based weaponry is very effective.
-
Buffed alien facehuggers and eggs. Facehuggers don't go idle anymore, and they attach to anyone who walks past them. Eggs do the same; fully grown eggs will open to potential hosts. If you are still in the range of them, the facehugger inside will leap out and hug you. Removed "activate facehuggers", since it's useless now. Emote "roar" if you want to roar now.
-
There can be only one living queen at a time, if the queen dies then a drone can take her place as a princess.
-
Buffed queen regeneration a bit, so it's not the same as her underlings. It's also more important because there can only be one queen at a time.
-
Aliens don't slip in space anymore.
-
Hulks don't paralyze aliens anymore, they instead slow them down to a slow crawl. It is very effective for punching aliens out of weeds, so it can't regenerate it's health.
-
New egg opening and opened egg icons by WJohnston.
-
-
Aranclanos updated:
-
-
A buncha crud nobody cares about lol Added a light to the airlock wiring interface to show the status of the timing.
-
You can't fill sprays without being next to the dispenser.
-
Simple animals no longer freeze to death in places with normal temperature.
-
Mechs no longer freeze on the spot when they are using the Energy Relay on powerless areas.
-
Improvements to showers, they now clean gear on beltslot, back, ears and eyes. Showers only clean visible gear.
-
Replica pods works again! But you can't make potato people without a key or clone people who ghosted alive (Catatonic).
-
Engiborgs can deconstruct airlocks with their RCDs once again.
-
You can construct airlocks while standing on another airlock with RCDs.
-
-
-
-
-
3 October 2012
-
Agouri updated:
-
-
-
-
-
1 October 2012
-
Cheridan updated:
-
-
Wizards have a new artifact added to their spellbooks.
-
-
-
-
-
-
30 September 2012
-
Numbers updated:
-
-
Readded Volume Pumps - now they work as intended and are constructable
-
Readded Passive Gates - now they work as intended and are constructable
-
Readded Heat Exchangers - now they work as intended and are constructable
-
Added Heater - to warm up gasses to 300C
-
Pipe dispensers can produce the readded pieces.
-
New graphics for all of the above - courtesy by Ausops.
-
-
-
-
-
30 September 2012
-
Giacom updated:
-
-
Airlocks now use the Environmental power channel, since they are airlocks after-all. Meaning, when power is low the airlocks will still work until the environmental channel on the APC is turned off. This applies to all the door control buttons too. Pipe meters now use the environmental power channel. If you have any comments have this change, please let me know in the feedback section of the forums.
-
-
-
-
-
26 September 2012
-
Carnwennan updated:
-
-
Added new hotkeys. Type hotkeys-help for details or see the drop-down help menu at the top of the game window.
-
-
Aranclanos updated:
-
-
Mechs are once again spaceproof!
-
The YouTool machine is now all access
-
Cutting tower caps in hand no longer deletes the wood, and planks now auto stack
-
-
-
-
-
25 September 2012
-
Donkie updated:
-
-
Reworked the Piano, now really optimized and new interface!
-
-
-
-
-
24 September 2012
-
Petethegoat updated:
-
-
Hopefully fixed the stop midis button. It should now stop any midis that are currently playing.
-
-
-
-
-
23 September 2012
-
Petethegoat updated:
-
-
Fixed an exploit which would allow the janitor to magically mop floors.
-
Added lipstick~ It's not available on station, as Nanotrasen has deemed it contraband.
-
If you encounter any issues with computers, notify an admin, or ask for assistance on #coderbus, on irc.rizon.net.
-
-
Donkie updated:
-
-
Updated the Package Tagger with new interface!
-
You can now dispense, remove and retag sort junctions properly!
-
-
-
-
-
17 September 2012
-
Cheridan updated:
-
-
Metroids have been replaced with Rorobeasts. Roros are strange latex-based lifeforms that hate light, fun, and gloves.
-
-
-
-
-
17 September 2012
-
Carn updated:
-
-
F5 is now a hotkey for adminghosting. F8 toggles ghost-like invisibility for admins.
-
Catatonia makes you fall down. Admins appear braindead when admin-ghosting.
-
"Set-observe"/"Set-play" renamed and merged into "Aghost".
-
"Lay down/Get up" renamed to "Rest"
-
Closets can't be sold on the supply shuttle anymore
-
Fixed all dat light
-
-
-
-
-
13 September 2012
-
Carn updated:
-
-
New Hotkeys (Trial period). Details can be found in the help menu or via the hotkeys-help verb. It's all client-side. It shouldn't intefere with regular controls (except ctrl+A, ctrl+S, ctrl+D and ctrl+W).
-
-
-
-
-
10 September 2012
-
Giacom updated:
-
-
AIs can double click on mobs to instantly start tracking them.
-
-
-
-
-
Important note for server hosts!
-
Important note for server hosts!:
-
-
The file /code/defines/hub.dm was moved into /code/hub.dm. To get your server back on the hub, open /code/hub.dm and set the hub variables again. Sorry for the inconvenience.
-
-
-
-
8 September 2012
-
Carn updated:
-
-
Added an additional check to stop changelings sharing powers/becomming un-absorbable/etc by absorbing eachother and then rejuvinating from death.
-
Cloaked Aliens are now slightly easier to see, so they should avoid strongly lit areas when possible. They can still lay down to become even stealthier though. Let me know what you think, it's only a minor sprite change.
-
-
-
-
-
6 September 2012
-
Cheridan updated:
-
-
-Changes flour from an item to a container-held reagent. All recipes have been updated to use 5 units of reagent flour for every item required previously. This has a few advantages: The 16(!) sacks of flour previously in the kitchen cabinet have been condensed to an equivalent 3 sacks. Beer is now brewable with universal enzyme, and converting lots of wheat into flour should be less tedious. Also, flour grenades, etc. Because of this, flour is now obtained from the all-in-one blender rather than the processor, and spaghetti noodles are made with 5 units of flour in the microwave.
-
-
-
-
-
6 September 2012
-
Giacom updated:
-
-
Removed cameras from bots (NOT BORGS). They weren't working well with freelook and I felt that since they weren't used at all, they wouldn't be missed.
-
-
-
-
-
3 September 2012
-
Giacom updated:
-
-
Cameras has changed quite a bit. They are no longer created from grenade canisters, instead you make them from an autolathe. The construction and deconstruction for them has also changed, so look it up or experiment it with yourself to see how to setup the cameras now. Cameras also get wires, like airlocks and APCs. There's two duds, a focus wire, a power wire, an alarm wire and a light wire. Protip: You can see which one is the alarm wire by pulsing it.
-
Added a red phone and placed it in the Cyborg Station. Sprite by Pewtershmitz! You'll also find an AI restorer there, replacing the computer frame.
-
Cameras aren't all X-ray anymore. The AI won't be able to see what room you are in if there's no normal camera inside that room or if there's no X-ray camera nearby..
-
Cameras get upgrades! Currently there's X-ray, EMP-Proof and Motion. You'll find the EMP-Proof and Motion cameras in the normal places (Singularity Pen & EVA), the new X-ray cameras can be found in the Dormitory and Bathrooms, plus some extra ones to invade your privacy. See if you can smash them all.
-
Alien Larva can bite simple animals (see: Ian, Runtime, Mice) to kill them and gain a small amount of growing points.
-
Space travel was tweaked to be more random when changing Z levels. This will stop people and items from being stuck in an infinite loop, as they will eventually hit something to make them stop.
-
-
-
-
-
31 August 2012
-
Agouri updated:
-
-
Overhauled newscasters. No visual additions but the thing is much more robust and everything works as intended. Wanted issues are fixed. Admins, check out Access News Network under Fun.
-
-
-
-
-
30 August 2012
-
Giacom updated:
-
-
You can now create an EMP Pulse. Like an explosion, it is the mixing of two reagents that trigger this to happen. I will tell you the first required reagent. Uranium. Have fun!
-
I have made most chemicals need 3-5 or more chemicals in order to react to a turf. For instance, you need at least 5 units of thermite splashed on a wall for it to burn down."
-
The EMP kit, that you can buy through the uplink, has two more grenades in them now. Making the box full of EMP grenades!
-
Changed the EMP grenade's range to be much bigger.
-
-
-
-
-
29 August 2012
-
Nodrak updated:
-
-
Mice now work with the admin player panel. Admins can now turn players into mice with the 'Animalize' button in the player panel!
-
Space bear AI no longer runs when a player is controlling it. Admins can now turn players into space bears with the 'Animalize' button in the player panel!
-
The holodeck beach program once again has a beach.
-
The nuke op shuttle floor was pressure-washed a few days ago. We have since re-painted it with nanotrasen blood. Sorry for any confusion.
-
-
-
-
-
28 August 2012
-
Giacom updated:
-
-
You can now toggle the bolt light of airlocks. An extra wire, that controls the airlock's bolt light, has been added.
-
Aliens can now tell who is and who isn't infected. They get a special facehugger icon that appears over mobs that have been impregnated.
-
Cameras have temporary X-Ray for the time being.
-
-
-
-
-
August 26, 2012
-
Nodrak updated:
-
-
Admins now have an 'Animalize' button on a mob's player panel. This button allows admins to turn players into simple animals. There are a few exceptions. Mice, Parrots, Bears and Space Worms all have issues that, until fixed, prevent me from allowing players those transformations.
-
-
August 25, 2012
-
Carnwennan updated:
-
-
New lighting. It should look and feel the same as the old lighting whilst being less taxing on the server. Space has a minimum brightness (IC starlight) and areas that do not use dynamic lighting default to a lighting level of 4, so they aren't dark, but they aren't superbright. Replacing turfs should preserve dynamic lighting. Singulo/bombs should cause a lot less lighting-related lag. There are some minor known issues, see the commit log for details.
-
Admins can now access most controller datums with the "Debug Controller" verb. Time to break all the things!
-
Supply shuttle now uses a controller datum. This means admins can see/edit supply orders etc.
-
Changeling fakedeath can be initiated after death again. Next time you want something reverted, just ask rather than being obnoxious.
-
-
Giacom updated:
-
-
AIs can now look around like a ghost with the exception that they cannot see what cameras cannot see. Meaning if you're in maintenance, and there's no cameras near you, the AI will not know what you are doing. This also means there's no X-Ray vision cameras anymore.
-
AIs can add links to Telecommunication Machines. Added some cameras for areas that should have it but instead relied on cameras nearby for vision.
-
Choking has been changed. You have to stand still while lethally choking someone. It takes time to get into that lethal choke. When you are lethaling choking someone, they are still concious until the lack of oxygen knocks them out.
-
-
trubble_bass updated:
-
-
Nerfed the Neurotoxin drink, it is now less effective than a stunbaton. But more effective than a Beepsky Smash.
-
Updated descriptions on various cocktails to be more accurate or more relevant to the drink itself.
-
-
-
-
-
August 24, 2012
-
Sieve updated:
-
-
Floorbots now actually pull up tiles when emagged
-
All helper bots (excluding MULEs) have an access panel and maint panel, access being for behavior and maint for internal work
-
To open the maint panel, the access panel needs to be unlocked, then you use a screwdriver. There you can emag/repair it to your heart's content. (Emagging the access panel will also unlock it permanently)
-
Helper bots are now repaired by using a welder when their maint panel is open
-
-
-
-
-
August 23, 2012
-
Nodrak updated:
-
-
In-hand sprites once again update correctly when equipping items.
-
-
-
-
-
-
August 16, 2012
-
Errorage updated:
-
-
Changes were made to how heating and cooling of humans works.
-
You must wear both a space suit and space helmet to be protected from space! Likewise you must wear a firesuit and a fire helmet to be protected from fire! Fire helmets are red and white hardhats, found in all fire closets.
-
Fire suits now only protect from heat and space suits only protect from cold, so make your choice count.
-
-
-
-
-
-
August 14, 2012
-
Sieve updated:
-
-
DNA modifiers can be used if there is no occupant, primarily to handle the buffer.
-
Ion Rifles are only effected by max severity EMPs, so AOE from its own shot won't effect it
-
Pepper Spray fits on Sec belts again
-
-
-
-
-
August 11, 2012
-
Sieve updated:
-
-
Turrets now properly fire at simple_animals.
-
Borgs, AIs, and brains/MMIs can be sacrificed by cultists.
-
Grenades now automatically set throw on again.
-
-
-
-
-
August 6, 2012
-
Dingus updated:
-
-
Library has been redesigned. It's a whole lot more classy now.
-
Significant changes to Medbay. CMO's office is more centralized, genetics has a new exit into cryogenics, and a new break room has been installed
-
-
-
-
-
August 4, 2012
-
Icarus updated:
-
-
Changes to Med-Sci south and surrounding maintenance areas. Virology is more isolated and Science gets a new Misc. Research Lab.
-
Atmos techs get construction access now to do their little projects in.
-
Transformation Stings now work on living humans.
-
-
-
-
-
August 2, 2012
-
Errorage updated:
-
-
Gas masks now protect you from reagent smoke clouds
-
Changed the 'black overlay' you get when paralyzed, blind or in critical condition to include a small circle around you.
-
Dramatically lowered the amount of damage you get per breath while in critical condition. Critical condition now lasts for about 5 minutes if nothing is causing you any additional harm. This in combination with the new black image overlay is an attempt at making doctors more willing to help.
-
-
Icarus updated:
-
-
Borgs now have flashlights to allow them to see in lightless areas
-
Changes to Medbay: The sleeper and storage rooms have been swapped around. Hopefully this leads to more healing and less looting.
-
-
-
-
-
August 1, 2012
-
Sieve updated:
-
-
Borgs can now have an encryption key installed into their internal radios. Simply ID, open the panel, and use the key to insert it (Screwdriver to remove)
-
Due to that as well, borgs have a 'Toggle Broadcast Mode' button for their radios, which changes the broadcast type between station-bounced (Non-reliant on TComms), and subspace (Required for department channels)
-
Also changed the binary chat for consistency, now for the prefix is ':b' for everyone, not just one for humans and one for borgs/AIs/pAIs
-
Based on feedback, Nuke Op pinpointers now automagically change between shuttle and disk mode when the nuke is armed or disarmed.
-
-
-
-
-
01-August-2012
-
Carn updated:
-
-
Please update your BYOND clients! Ideally everybody should be running the latest version of byond (v496). People who fail to update to at least version 494 within a month's time may find themself unable to connect. Currently our code has no restrictions at all, which is rather bad. By getting the user-base to keep their clients up-to-date we can make use of newer BYOND features reliably.
-
-
Giacom updated:
-
-
I've made some adjustments to the Fireball spell. I've changed it to shoot in the player's facing direction instead of you having to pick a name from a list. It will explode upon contact of a person, if it hits an obstacle or if it shoots for too long. To make up for the fireball not being able to go diagonal I've shortened the cooldown to 10 seconds. It still can hurt you badly and knock you down if you shoot it at a wall. Lastly, it now lights up so it'll show up in dark rooms easily.
-
-
-
-
-
31 July 2012
-
Giacom updated:
-
-
Removed passive throwing. You need at least an aggressive hold of the mob before you can throw them.
-
New map changes by Ikarrus. AI Upload Foyer is now Secure Tech Access, and the outer door only requires Bridge access. Attached to it are two new rooms: The messaging server room and the communications relay. The comms relay room runs off its own SMES unit like the AI, so it won't be affected by powersinks
-
-
-
-
-
-
29 July 2012
-
Giacom updated:
-
-
All radios now only work in their Z level. This means that the CommSat has a few more additions to work with this change. There is now a new Telecomms Machine called the Relay which allows information to travel across Z levels. It it then linked to a new machine called the Hub, which will receive information from the Relays and send it to the buses. Because every Z level needs these relays, which are linked up with Receivers/Broadcasters, every Z level will get one. There is one in the station, in the RD's office, one in Telecomms as always, one in the Ruskie station which is turned off and hidden from the HUB's linked list. The last one is in Mining but the location for it has not been decided yet.
-
PDAs now need to be in a Z level with a functioning Relay/Comms Network in order to send messages. It will also send uncompressed (scrambled) messages like you would with the ordinary voice messages.
-
Added some of WJohnston's sprites. Added a new mining borg sprite, Added a new high tech security airlock, Added the new telecomm sprites for Relays. Hubs were given old Bus sprites.
-
-
-
-
-
29 July 2012
-
Errorage updated:
-
-
You can now use crayons to color eggs
-
Mice have invaded the station!
-
-
-
-
-
26 July 2012
-
Giacom updated:
-
-
Added a new mushroom for Hydroponics, the Reishi Mushroom! It is obtained like any other mushroom and it has relaxing properties.
-
-
-
-
-
July 25, 2012: The day of updates!
-
Nodrak updated:
-
-
Attacking mobs with items will now give new messages. Instead of "Monkeyman was attacked in the head with a wrench by Nodrak." it will read "Monkeyman was bashed in the head with a wrench by Nodrak." Diffrent items have diffrent verbs and some have multiple verbs.
-
Cultists can now read what words a rune was made with by examining the rune. Due to an error in the code, this was not possible before.
-
Clowns no longer have practice lasers or staves of change blow up in their face due to clumsyness.
-
Engineering cyborgs can now actually repair a cut AI wire in APCs.
-
I've removed a ton of pointless checks and redundant loops from metroid's which have been causing lag due to how often they get called. If metroids are behaving strangly ping me in #coderbus
-
-
Sieve updated:
-
-
Made a 'default' save slot (D), and whenever you connect it automatically selects the default slot to load from, but manually selecting a different slot will allow you to play on that one before it returns to default.
-
Added the ability to name your save slots with the '*'. Names can be up to 16 characters and contain letters, numbers, and basic symbols
-
The preview icon on the preference screen now takes into account any job you have set on high, and dresses up the icon accordingly. If assistant is set to 'yes', or AI/Cyborg are on high it will put the icon in a grey suit (So you can still customize).
-
Nuke Ops get a new pinpointer, changing modes with the verb will switch between pointing to the disk, and pointing to the shuttle. Also provides a notification when you leave the station z-level
-
Reworked how MMI Life() was done, now they will never lose consciousness, and many less things affect them now(Like deafening/blindness from explosions). However, they are vulnerable to EMPs, but all damage is temporary.
-
Clowns will no longer be killed trying to use holo eswords
-
Major tweaking to try and optimize many operations on the game's backend. Hopefully, this will reduce a large amount of lag by steamlining CPU-intensive operations, but at the same time there was so much changed that there is no real way for a small group to test everything. If anyone spots a bug involving being unable to 'find' mobs, characters, whatever, then put it on the issue tracker or at the very least let #coderbus know. We can't fix shit unless we know about it.
-
-
Icarus updated:
-
-
Players not buckled in when the emergency shuttle/pod starts moving get will get knocked down.
-
Added a YouTool vending machine to primary tool storage.
-
-
-
-
-
24 July 2012
-
Errorage updated:
-
-
Both the chef and bartender have access to the bar area so both can serve if the other is incompetent or does not exist. Bartender's shotgun and shaker were moved to his back room and the booze-o-mat is now ID restricted to the bartender.
-
Added powercells into vending machines in engineering
-
Gave two beartraps to the janitor for pest control purposes................
-
-
-
-
-
22 July 2012
-
Errorage updated:
-
-
Mech toys can now be redeemed at the quartermaster's for a great reward! If you collect the full set of 11 toys you should put them in a crate and send them to centcom via the supply shuttle.
-
Supply shuttle arrival time reduced to 2 minutes
-
Hopefully fixed the toy haul problem which made it possible to get a million toys from arcade machines.
-
-
Giacom updated:
-
-
You can now make newlines with your PDA notes.
-
You can now research and build the Light Replacer.
-
You can now store donuts in the donut box. The next donut you pull out will be the last one you put in.
-
APCs will auto-turn on if there is enough power in the grid, even if the powercell is below 30%. The APC needs to be charged for a long enough time before it starts turning equipment on, to avoid spazzing out. If you have any problems with it, such as equipment turning off and on repeatedly then please make an issue report with a screenshot of the APC.
-
-
-
-
-
18 July 2012
-
Giacom updated:
-
-
Added the Light Replacer. This is a device that can auto replace lights that are broken, missing or burnt. Currently it is found in the Janitor's closet and Janitor Borgs can equip it. You can refill it with glass, or if you're a Cyborg, just recharge. It is emaggable and will replace lights with rigged lights. The light's explosion was nerfed to help balance it and it is very noticable when you are holding an emagged Light Replacer.
-
The Janitor's equipment locator, on their PDA, will now tell you the direction of the equipment.
-
-
-
-
-
17 July 2012
-
Icarus updated:
-
-
Added department satchels
-
Added Captain's Backpack and Satchel
-
Added three new hairstyles by Sly: Gelled, Flat Top, and Pigtails. Hair list has also been sorted by grouping similar styles.
-
-
Giacom updated:
-
-
Added a new wire for Cyborgs. See if you can figure out what it does.
-
You can now fill any container with a sink. You can change the amount to fill, from sinks, by setting your container's transfer amount.
-
-
-
-
-
14 July 2012
-
Carn updated:
-
-
All living mobs can now ghost whenever they want. Essentially making the suicide verb obsolete. If you ghost whilst still alive however, you may not re-enter your body for the rest of the round.
-
Humans can no longer suicide whilst restrained (this is purely to prevent meta whilst I finish up the new FUN suicides)
-
Fixed dem evidence bags. Fixed metroids getting at it like rabbits. Fixed stuff like welding masks not hiding your face. Bunch of other things
-
-
Willox and Messycakes updated:
-
-
pAI Emoticons! Allows each pAI to set their screen to display an array of faces! Click on 'Screen Display' in the pAI OS for a list.
-
-
-
-
-
-
Saturday July 14th 2012
-
Giacom updated:
-
-
Added Russian Revolvers. This is a special Revolver that can only hold a single bullet randomly in it's chamber. This will allow you to play Russian Roulette with your fellow crew members! You can use it like a normal gun but you will need to cycle through the chamber slots until you hit the bullet. Only admin spawnable.
-
-
-
-
-
Friday July 13th 2012
-
Carn updated:
-
-
Added FLOORLENGTH HAIR. YEESSSSSSSS!!!! :3 If you like it say thanks to Ausops for fixing it up. Credits to Powerfulstation for the original sprite.
-
-
Giacom updated:
-
-
Save Slots! You can now have separate save slots for different character setups, with a customizable maximum of 3 slots per account. If you are wondering, you will not lose your old saved setup.
-
The character setup screen was updated to look nicer and to fit on the screen.
-
-
Icarus updated:
-
-
Added new Dwarf and Very Long hairstyles. Dwarf hair and beard by SuperCrayon.
-
-
-
-
-
Thursday July 12th 2012
-
Giacom updated:
-
-
pAI gets a better PDA that can actually receive messages from people. They can also instantly reply like everybody else now and they can toggle their receiver/signaller/ringer.
-
You can show the AI the notes on your PDA by holding it up to a camera. When you show up a paper/pda to the camera the AI can now click on your name to go to you, if you're near a camera. People who are Unknown will not have a link; which would've allowed the AI to track them.
-
Made the" common server" and the "preset right receiver" listen for frequencies 144.1 to 148.9. This will allow people to use different frequencies to talk to eachother without bothering the common channel. It will also allow Revs and Cultists to work with each other; everything is still logged though so it still has risks.
-
Increased the maximum frequency limit for handheld radios and intercoms. It will give you the option to just use station bounced radios on a higher frequency so that anyone with a headset can't simply tune in.
-
Created an All-In-One Grinder that is suppose to replace the blender, juicer and reagent grinder all together. Meaning any department that has a juicer, blender and grinder will instead get this. It will help people be more independent from Chemistry by recycling foods and plants.
-
-
-
-
-
Wednesday July 11th 2012
-
Nodrak, Cheridan and Icarus updated:
-
-
Added a couple of Emergency Shield Projectors to Engineering secure storage.
-
Note: Credit goes to Barhardar for the original code and functionality.
-
These devices can be used to quickly create an air-tight seal across a hull breach until repairs can been made.
-
Wrench them in place and activate them near a hull breach. The shield should extend to all space tiles in range.
-
They can be (un)locked by engineering IDs and can also be emagged and otherwise malfunction. As they can not be constructed, you can repair damage and malfunctions by opening the panel with a screwdriver and replacing the wires with a cable coil
-
-
Giacom updated:
-
-
Chemistry update: Pills can now be ground up in reagent grinders. You can now put custom amounts of reagent into things using chemmasters. Can now load pill-bottles into chemmasters for mass pill-production.
-
-
Carn updated:
-
-
Clicks on inventory slots with items in now act like a click on the thing in that slot. So clicking smaller things (like pens) is easier and you can remove clothing that borks/goes invisible. Please continure to report those kinds of bug though please. Thanks x
-
Can no longer interact with your inventory with a mech.
-
-
Errorage updated:
-
-
You can now only adminhelp once every 2 minutes so please provide all necesary information in one adminhelp instead of 5! Also reply to admins in PM-s and not additional adminhelps.
-
-
-
-
-
Saturday July 7th, 2012
-
Icarus updated:
-
-
A basketball simulation is now available at the holodeck. Credit to Sly and Ausops for the sprites.
-
-
-
-
-
Friday July 6th, 2012
-
Giacom updated:
-
-
Bottles can now be broken over people's heads! To do this, you must have the harm intent on and you must be targeting the person's head. This change affects alcoholic bottles only. It does not change pill bottles or chemistry bottles. Helmets help protect you from damage and the regents of the bottles will splash over the victim.
-
AI's now have access to a PDA. Note: It is not PDA-bomb-able
-
Health analyzers and medical PDAs now give a time of death when used on corpses.
-
-
-
-
-
Thursday July 5th, 2012
-
Carn updated:
-
-
Alien larva now chestburst even after their host has died.
-
Aliens can now slap facehuggers onto faces so they can infect mobs which lay down (or those stuck to nests).
-
Aliens can now slash security cameras to deactivate them.
-
-
-
-
-
Wednesday July 4th, 2012
-
39kk9t & Carn updated:
-
-
Added alien nests. They're basically beds made of thick sticky resin which aliums can 'stick' (buckle) people to for sexytimes
-
Weed nodes are no longer dense.
-
Queens can secrete resin for walls/nests/membranes
-
Various bugfixes
-
-
-
-
-
Saturday June 30th, 2012
-
Icarus updated:
-
-
Added Petethegoat's basic mirrors to the map. They allow you to change hairstyles.
-
Remapped Bar, Theatre, and Hydroponics. Bar and Kitchen are now more integrated with each other.
-
-
-
-
-
Thursday, June 28th
-
Nodrak updated:
-
-
I'm currently working on cleaning up and moving around a large portion of the mob code. These changes do not directly affect players; HOWEVER, bugs, oversights or simple mistakes may cause problems for players. While I have tested as much as I can, there may be some lingering bugs I have missed.
This part of the mob code cleanup mainly focuses on damage variables and procs. So if you suspect something related to taking, dealing or examining damage is not working as intended please fill out an issue report here and be as detailed as possible. This includes what you were doing, steps to reproduce the problem, who you were doing it to, what you were using ect... Thank you.
-
-
Carn updated:
-
-
Alien hunters will now cloak when using the 'stalk' movement intent. Whilst cloaked they will use up their plasma reserves. They can however cloak as long as they like. Using the Lay-down verb will allow them to remain cloaked without depleting their plasma reserves, hence allowing them to lay ambushes for unsuspecting prey. Should a hunter attack anybody, or get knocked down in any way, they will become visible. Hopefully this allows for more strategic/stealthy gameplay from aliens. On the other hand, I may have totally screwed the balance so feedback/other-ideas are welcome.
-
Removed the invisibility verb from the alien hunter caste.
-
-
-
-
-
Wednesday, June 27th
-
Errorage updated:
-
-
Fixed the bug which prevented you from editing book's titles and authors with a pen. Also fixed the bug which prevented you from ordering a book by it's SS13ID.
-
Added the F12 hotkey which hides most of the UI. Currently only works for humans.
-
-
Donkie updated:
-
-
Pizza boxes! Fully stackable, tagable (with pen), and everythingelse-able. Fantastic icons by supercrayon! Created with a sheet of cardboard.
-
-
-
-
-
Tuesday, June 26th
-
Errorage updated:
-
-
Changeling parasting now only weakens for 10 game ticks. It no longer silences your target.
-
-
-
-
-
Saturday, June 23rd
-
Donkie updated:
-
-
Reworked job randomizing system. Should be more fair.
-
List of players are now randomized before given antag, this means that declaring as fast as possible doesn't mean shit anymore!
-
-
Carn updated:
-
-
Putting a blindfold on a human with lightly damaged eyes will speed up the healing process. Similar with earmuffs.
-
More overlay bug fixes. Most of it to do with little robots and construction stuff. Bugs go here if you have 2 minutes http://nanotrasen.com/phpBB3/viewtopic.php?f=15&t=9077
-
Weakening is instant! That means if you stunbaton somebody they're gonna fall-down immediately.
-
-
Icarus updated:
-
-
Medical storage now requires Surgery access (Medical Doctors only)
-
-
-
-
-
Wednesday, June 20th
-
Nodrak updated:
-
-
AIs, Borgs are no longer able to be cultists or revolutionaries as their objectives completely contradict their laws. They can still be subverted of course.
-
pAI's no longer keep cult or rev icons.
-
Cheridan updated:
-
-
-Both Ambrosia forms have had their reagent contents modified to prevent going over the 50-unit cap at high potencies. Ambrosia Deus now contains space drugs instead of poison.
-
-Drinking milk removes capsaicin from your body. REALISM!
-
-Frost oil hurts less upon consumption.
-
-Liquid plasma can be converted into solid plasma sheets, by mixing 20 plasma, 5 iron, and 5 frost oil.
-
-Added effects for holy water injection on hydroponics plants.
-
-
-
-
-
Monday, June 18th
-
Giacom updated:
-
-
Fix for special characters on paper and from announcements
-
-
Sieve updated:
-
-
Various small bugfixes, check the commit log for full details
-
Fixed falsewalls not working
-
Made Lasertag ED-209's and turrets much more useful, including making their emag function more fitting
-
Added Mesons to the EngiVend to make up for how many lockers were removed
-
New Item: Sheet Snatcher. Right now only borgs have them, but they can hold up to 500 sheets of minerals (Of any combination), and auto-stacks them to boot. Used just like the mining satchels, meaning minerborgs can actually deliver metal
-
Mech drills can mine sand, and the diamond gets a much larger volume
-
If a borg has the satchel in its modules (Doesn't have to be the active one), it will auto-magically pick up any ores it walks over.
-
Bumping an asteroid wall with a pickaxe/drill in your hand makes you auto-magically start drilling the wall, making mining much less tedious (humans and borgs)(Also, gustavg's idea)
-
-
Icarus updated:
-
-
New afro hairstyles. Big Afro by Intigracy.
-
-
-
-
-
Friday, June 15th
-
Carnwennan updated:
-
-
First update for update_icons stuffs: Fixed husking and fatties Fixed floor tiles still appearing in hand when laying them Fixed runtimes with fatties.
-
Fixes for pre-existing bugs: Fixed being unable to put belts & backpacks on other people nodamage (godmode) now prevents all organ damage. It does not stop healing however. Nerd stuff...
-
-
-
Errorage updated:
-
-
Greatly reduced the amount of damage high pressure does.
-
Fire suits, firefighting helmets (red harhats) and the chief engineer's white hardhat now protect against high pressure.
-
-
Icarus updated:
-
-
Ported over ponytail sprites from Baystation
-
-
-
-
-
Thursday, June 14th
-
Carn updated:
-
-
FEAR NOT! You can now store a pen in your pda. (aka Best commit all commits)
-
-
-
-
-
Wednesday, June 13th
-
Carn updated:
-
-
Massive Mob-Icon Overhaul: A large amount of the mob code has been replaced. The systems replaced were causing immense performance issues so the following are very necessary optimisations. However, there is a downside: SS13 code is the equivilant of monkeys on typewriters. Despite weeks of constant coding/testing there -will- be things I've missed. The kinds of bugs I'm expecting are overlays not updating and/or in rare cases things not appearing in your hud. Most of these issues can be worked around simply by dropping the item and picking it back up. If all else fails ask an admin to regenerate your icons through view-vars. Please report any bugs to me on #coderbus IRC or make an issue on the tracker as a matter of urgency. I will fix them ASAP. Also a massive thankyou to Nodrak, Erro, Pete and Willox. :)
-
Massive rewrite of the overlays system (particularly for humans). Stuff is cached and only updates when necessary. In effect this means faster updates, less overheads/lag, and less reliance on the game-ticker.
-
Numerous bugfixes and tweaks for damage-procs and damage-overlays for humans. They should now be almost seamless, use very little overhead and update instantly.
-
TK grab can now be cancelled using the throw hotkey. (so now it toggles on/off like it used to).
-
Added verbs to the view-var drop-down list: "Regenerate Icons" will fix a mob's hud/overlays. "Set Mutantrace" will change a mob's mutantrace.
-
Damage icons were split up. They kinda look a bit crap so spriters feel free to replace them. Templates are provided in dam_human.dmi
-
More to come...
-
Cheridan updated:
-
-
-Added Lezowski's overalls to hydroponic supply closets. 50% chance per closet for them to replace the apron. -Removed some covers tags from things that made no sense to have them.
-
-
-
-
-
Monday, June 11th
-
Donkie updated:
-
-
Fixed being able to lock yourself in or out of a locker using the verb.
-
-
Xerux updated:
-
-
Added lightfixture creating.
-
-
Errorage updated:
-
-
You can now use the resist verb or UI button when welded or locked in a closet. Takes 2 minutes to get out tho (Same as getting out of handcuffs or unbuckling yourself)
-
Making single-pane windows will now make the window in the direction you're facing. If a window already exists in that direction it will make it 90 degrees to your left and so on.
-
-
-
-
-
Sunday, June 10th
-
Agouri updated:
-
-
Cyborgs and AIs can now use the newscaster. It was a mistake on my part, forgetting to finish that part of them.
-
-
-
-
-
Saturday, June 9th
-
Errorage updated:
-
-
You can now make restraints from cable. It takes 15 lengths of cable to make a pair of restraints, they are applied the same way as handcuffs and have the same effects. It however only takes 30s to remove them by using the resist verb or button. You can also remove them from someone by using wirecutters on the handcuffed person.
-
Added four new cable colors: pink, orange, cyan and white. Engineer belts spawn with yellow, red or orange cables while toolboxes and tool closets spawn with all 8 colors.
-
-
-
-
-
Thursday, June 7st
-
Icarus updated:
-
-
Added a second ZIS suit to engineering.
-
Remapped CE office and surrounding areas.
-
-
-
-
-
Wednesday, June 6th
-
Sieve updated:
-
-
Radiation now works properly, watch out for that Singularity!
-
Disposals are no longer the loudest machines in existence.
-
Building portable turrets with lasertag guns now makes them fire lasertag bolts based on team, and they will automatically target and prioritize people wearing opposing team gear.
-
The same can be done for ED-209s, simply using a lasertag vest and gun (same color) where you would use a security vest and taser in construction.
-
Added mineral walls and powered mineral door construction. More information can be found in the commit thread, but basically they are built the same way others are, apply mineral to girder for a mineral wall, mineral to airlock assembly for a powered mineral door.
Swap hands hotkey (page up) now cycles through borg modules.
-
-
Nodrak updated:
-
-
Cargo's 'shuttle: station' and 'shuttle: dock' has been changed to 'shuttle: station' and 'shuttle: away' to help avoid confusion.
-
-
Icarus updated:
-
-
Maintenance shafts changed around. Renamed, less windows, more turns, and expanded in a few areas.
-
-
Neek updated:
-
-
You can now add chemicals into cigarettes by injecting them directly or dipping individual cigarettes into a beaker. You can inject into cigarette packs directly to affect multiple cigarettes at once.
-
-
Willox updated:
-
-
You can now click individual blocks/subblocks in the genetics console instead of having to scroll through the blocks with a forward/back button!
-
-
-
-
-
Sunday, June 3rd
-
Donkie updated:
-
-
You can now Drag-Drop disposal pipes and machinery into the dispenser, in order to remove them.
-
You must now use wrench before welding a pipe to the ground
-
You can no longer remove a trunk untill the machinery ontop is unwelded and unwrenched
-
You are now forced to eject the disposal bin before unwelding it.
-
-
-
-
-
Friday, June 1st
-
SkyMarshal updated:
-
-
Readded fingerprints and detective work, after lots of debugging and optimization.
-
Any PDA with access to the Security Records can now, by the normal forensic scanner function, store data the same way as the detective's scanner. Scanning the PDA in the detective's computer will copy all scanned data into the database.
-
If something goes wrong, please contact SkyMarshal on the #bs12 channel on irc.sorcery.net
-
-
-
-
-
Friday, June 1st
-
Nodrak updated:
-
-
Windoor's are now constructable! Steps are found here.
-
-
-
-
-
Tuesday, May 29th
-
Nodrak updated:
-
-
Glass Doors are now breakable.
-
Added more treasures to the secret mining room.
-
Changed mineral lockers from secret mining rooms: Instead of giving you two stacks of everything, you get stacks of ore based on rarity
-
-
Icarus updated:
-
-
Moved Engineering and Bridge deliveries.
-
-
-
-
-
This 28th day of May, in the year of our Lord, Two Thousand Twelve
-
Cheridan updated:
-
-
-Adjusted balaclavas and added luchador masks. Wearing luchador masks give you latin charisma. They replace the boxing gloves in the fitness room. Boxing gloves are still available in the holodeck. -Fake moustache tweaked and given new sprites.
-
-
-
-
-
Monday, May 28th
-
Donkie updated:
-
-
You can now dispense Disposal Bins, Outlets and Chutes from the disposal dispenser. These are movable and you can attach them above open trunks with a wrench, then weld them to attach them completely. You can remove Bins by turning off their pump, then screwdriver, then weld, then wrench. Same with outlet and chute except for the pump part.
-
-
-
-
-
Saturday, May 26th
-
Icarus updated:
-
-
Ported over Flashkirby99's RIG suit sprites from Bay12
-
Department PDA Carts moved out of lockers and into head offices.
-
-
-
-
-
Wednesday, May 23rd
-
Cheridan updated:
-
-
-Reverted default UI sprites to Erro's old-style UI. Config options for UI color styles coming soon. -Driest Martinis will no longer be invisible. -Braincakes are now sliceable.
-
-Medical borg overhaul. Instead of a dozen random pills and syringes, they get a hypospray that can switch between auto-replenishing tricordrazine, inprovaline, and spaceacillin.
-
-
Errorage updated:
-
-
Some of the more pressing issues with the new user interface were addressed. These include the health indicator being too far up, the open inventory taking a lot of space, hotkey buttons not being removable and suit storage not being accessible enough.
-
A toggle-hotkey-buttons verb was added to the OOC tab, which hides the pull, drop and throw buttons for people who prefer to use hotkeys and never use the buttons.
-
Added a character setup option which allows you to pick between the Midnight, Orange and Old iconsets for the user interface.
-
-
-
-
-
Tuesday, May 22nd
-
Icarus updated:
-
-
RIG helmets can now be used as flashlights, just like hardhats. Credit to Sly for the sprites.
-
HoP's office has been remapped and made into his private office. Conference Room can now be accessed from the main hall.
-
-
-
-
-
Sunday, May 20th
-
Errorage updated:
-
-
The new user interface is here. If anything is broken or something should be done differently please post feedback on the forum. Spriters are encouraged to make new sprites for the UI.
-
When you receive a PDA message, the content is displayed to you if the PDA is located somewhere on your person (so not in your backpack). You will also get a reply button there. This will hopefully make PDA communication easier.
-
New hotkeys! delete is the now the 'stop dragging' hotkey, insert is the 'cycle intents' hotkey.
-
-
-
-
-
Saturday, May 19th
-
Doohl updated:
-
-
You can now swap hands by clicking with your middle mouse button (you have to click on a visible object though, that's the catch).
-
Tweaked the DNA modifier consoles a little bit so that it's much easier to see individual blocks instead of one jumbled mess of hexadecimal.
-
You can now properly emag AI turret controls and commsat turret controls.
-
-
Invisty updated:
-
-
Brand new ending animations!
-
-
-
-
-
Friday, May 18th
-
Errorage updated:
-
-
Removed hat storage, which was useless.
-
Implanting someone now takes 5 seconds, both people need to remain still. Implanting yourself remains instant.
-
Wallets once again spawn in the cabinets in the dormitory
-
Wallets now fit in pockets
-
-
-
-
-
Thursday, May 17th
-
Icarus updated:
-
-
Individual dorms now have a button inside that bolts/unbolts the door
-
New sprites for Cargo, HoP, and Captain's lockers
-
More department-specific door sprites. Most noticable changes in medsci and supply departments.
-
-
-
-
-
Tuesday, May 15th
-
Icarus updated:
-
-
Added WJohnston's scrubs to Medical Doctor lockers. Comes in blue, green, and purple.
-
Added two new syndicate bundles
-
Reduced cost of thermals to 3 telecrystals (formerly 4)
-
Singularity Beacons are now spawned from a smaller, portable device.
-
CMO and QM jumpsuits made more unique.
-
-
-
-
-
Monday, May 14th
-
Icarus updated:
-
-
Reinforced table parts are now made by using four metal rods on regular table parts. No plasteel involved.
-
Beakers, small and large can now be made/recycled in autolathes.
-
-
Nodrak updated:
-
-
Added a 'random item' button to traitor uplinks. You can potentially get ANY item that shows up on the traitor item list, provided you have enough crystals for it.
-
-
-
-
-
Friday, May 11th
-
Icarus updated:
-
-
New design for security. This should be the last time it sees major changes for a while.
-
Added a new construction area. What could it be for?
-
-
Petethegoat updated:
-
-
Readded the RD's genetics access.
-
RD is still without chemistry access, but I'm going to review this decision in a week and see if R&D is useless due to lack of acid.
-
Added Flashkirby99's SMES sprites!
-
-
Invisty updated:
-
-
Sexy new warpspace (or whatever) tiles.
-
-
Important changes below!
-
-
-
-
Thursday, May 10th
-
Sieve updated:
-
-
Reverted dismemberment, the recent gun changes, and Tarajans. Before you shit up the forums, read this:
-
Dismemberment was ported from Bay12, but only halfway, and there were several problems with it. I know many people really liked it, but as it stood it did not fit the playstyle here at all. This had to be removed, there is work on a more fitting system, but this had to be taken out first regardless, and the longer people beat around the bush the worse the situation got.
-
The gun change was made for no real reason and was pretty problematic, so reverting that should mean there are a lot less 'accidental suicides.'
-
Tarjans were reverted by request as well, and since keeping them working after removing dismemberment would be a stupid amount of work.
-
-
-
-
-
Sunday, May 6th
-
Cheridan updated:
-
-
-New booze sprites for the drinks that were removed! Re-enabled the recipes for the removed drinks. Get cracking, bartenders. -You now need 10 sheets of metal instead of 2 to make a gas canister, people can't FILL ENTIRE ROOMS WITH THEM.
-
-Emergency Toolboxes now contain smaller, lighter fire extinguishers that actually fit inside them!
-
-
-
-
-
Saturday, May 5th
-
Petethegoat updated:
-
-
RD get the fuck out of chemistry and genetics
-
CHEMISTS, DON'T BE DICKS TO RESEARCH, GIVE THEM ACID YOU TIGHT FUCKS
-
-
Icarus updated:
-
-
Updates to Sec, including a stationary scrubber for the prison area.
-
Swapped around cryogenics and the patient rooms in medbay.
-
-
-
-
-
Friday, May 4th
-
Cheridan updated:
-
-
-Added fat jumpsuit sprites for orange, pink, yellow, owl, security, and warden jumpsuits.
-
-Somatoray is hopefully more useful and less buggy when used on trays. -Botanists now have morgue access, because of their ability to clone via replica pods. Try not to get this removed like all your other access, okay hippies?
Added a verb to the PDA which you can use to remove an ID in it. If your active hand is empy, it puts it there otherwise it puts it on the floor under you.
-
-
-
-
-
Wednesday, April 27th
-
Cheridan updated:
-
-
-New sprites for lemons, oranges, and walking mushroom critters. -Added Invisty's new blob sprites.
-
-Added a new chemical: lipozine, a weight loss drug. Made with sodium chloride, ethanol, and radium.
-
-
-
-
-
Wednesday, April 25th
-
Ikarrus & Flazeo updated:
-
-
New layout for Security, including a prison area instead of permacells.
-
New layout for the library, bar, and botany.
-
Medbay and R&D now have three-tile halls.
-
-
Scroll down for more commits! There's a bunch of new shit.
-
-
-
-
Tuesday, April 24th
-
PolymorphBlue updated:
-
-
Fakedeath changelings can no longer have their brains cut out.
-
Rev checkwin changed to fire every five ticks (from twenty) and actually use the right objective type so revs being off station counts as success.
-
-
Sieve updated:
-
-
Powercells now have unique icons for cell types
-
Implemented mech construction sprite by WJohnston for the Ripley, Firefighter, Gygax, and Durand
-
Durand construction is reversible
-
Power Cells can now be made in Mechfabs, provided the proper research level has been achieved
-
Added a new item, the Synthetic Flash. Works just like a normal flash, except they can only withstand one use, but can be produced in the Mechfab(To replace the need for normal flashes)
-
Added a new type of gloves, ones that are cheap copies of the coveted Insulated Gloves, but be warned, quality control wasn't too thorough
-
Added a new Cyborg Upgrade, a jetpack for use by Miner Cyborgs. Can be refilled on any air canister
-
Miner Cyborgs now have a Diamond Drill equivalent along with an upgraded Ore Satchel
-
Mechfabs no longer brick if there are parts in the quene on sync
-
MMIs can be built in the Mechfabs again
-
Crabs are no longer immortal, and are now especially vulnerable to wirecutters
-
Bibles printed in the library now retain the religion's deity
-
Added Construction Sprites for the Ripley, Firefighter, Gygax, and Durand by WJohnston
-
Added Particle Accelerator sprites by Invisity
-
Added Power Cell, Synthetic Flash, Robot Upgrades, and made some modifications to the PA sprites
-
-
Petethegoat updated:
-
-
Added Invisty's field generator sprites.
-
-
-
-
-
April 1-22, 2012
-
Cheridan updated:
-
-
CATCHING UP ON MY CHANGELOG. Some of this has been in for a while: -Added carved pumpkins and corncob pipes. -Added mutations for ambrosia and lemon trees. -Added more wood items for tower cap wood construction. -Added soil to plant seeds in. Make it by crushing up sandstone. Soil does not have indicators like trays do! Watch your plants carefully!
-
-The biogenerator is now more robust. It can dispense fertilizer in batches, and make simple leather items. -RnD can create a new tool for botanists: The floral somatoray. Has two modes. Use it on your plants to induce mutations or boost yield.
-
-Added plump helmet biscuits, mushroom soup, pumpkin pie and slices, chawanmushi, and beet soup recipes for the chef to make.
-
-Added transparency to biohelmets. -Normalized grass harvests. -Changed the name of "Generic Weeds". -Blenders can now be filled directly from plant bags. -Added low chance for a species mutation whenever a plant's stats mutate. -You now get more descriptive messages when applying mutagen to plant trays. -Removed sugarcane seeds from the vending machine. Added the sugarcane seeds to the seeds crate.
-
-
-
-
-
Sunday, April 22nd
-
Petethegoat updated:
-
-
New gasmask sprites. Removed emergency gasmasks, so there's only one type now.
-
New shotgun sprites by Khodoque!
-
The barman's double-barrel actually works like a double-barrel instead of a pump-action! Rejoice!
-
Sneaky barmen may be able to illegally modify their shotgun, if they so choose.
-
Trimmed the changelog, vastly.
-
-
-
-
-
Saturday, April 21st
-
Errorage updated:
-
-
Maintenance door outside of tech storage now requires maintenance OR tech storage access instead of maintenance AND robotics accesses.
-
-
-
-
-
Thursday, April 19th
-
Carn updated:
-
-
Rewrote the cinematic system to try and simplify and optimise it. Please report any bugs asap to me or coderbus, thanks.
-
-
-
-
-
Tuesday, April 17th
-
Kor updated:
-
-
Engineering jobs now have their PDA spawn in their pocket, and their toolbelt on their belt
-
The nuke going off on station will now gib everyone on the Z level.
-
Two more core displays are available for the AI
-
The Artificer can now build cult floors and walls
-
-
-
Friday, April 13th
-
Sieve updated:
-
-
Updated the robotics layout.
-
-
Petethegoat updated:
-
-
Nerfed the librarian by removing the r-walls from his cubbyhole thing, fuck WGW readers hiding out in there.
-
-
-
-
Thursday, April 12th
-
Agouri updated:
-
-
Fixed the ability to move while lying down/resting.
-
Sleep has been fixed and works as intended again. Anaesthetic and toxins can now properly put people to sleep, permanently if you keep the administration stable. Sleeplocs are now viable again. The sleep button and *faint emote work again.
-
-
-
-
Wednesday, April 11th
-
PolymorphBlue updated:
-
-
Droppers are now used at the eyes, and thus, access to the eyes is required to have an effect.
-
-
-
-
-
-
April 10, the year of our lord 2012
-
Agouri updated:
-
-
CONTRABAND-CON UPDATE: Added posters. I'm sorry to add it seperately with the rest of contraband, but there was a lack of sprites for everything else. Hopefully, people will gain interest and get me some damn sprites this way :3
- As I said, this is an ongoing project of mine. So starting of, we've got...
-
POSTERS! Posters come in rolled packages that can adhere to any wall or r_wall, if it's uncluttered enough.
-
How they get on-board: The quartermaster can now set the receiver frequency of his supplycomp circuit board. A bit simplistic as of now, will work on it later. Building a supplycomp with a properly set up circuitboard will give access to the Contraband crate.
-
How they're used: Unfold the rolled poster on any wall or r_wall to create the poster. There are currently 17 designs, with the possibility of me adding more.
-
How to get rid of them: You can rip them using your hand... To cleanly extract them and not ruin them for future use, however, you can use a pair of wirecutters.
-
How they're classified: They're contraband, so it's perfectly okay for security officers to confiscate them. Punishment for contraband-providers (or end-users, if you want to go full nazi) is up to the situational commanding officers.
-
-
-
Nodrak updated:
-
-
Merged 'Game' and 'Lobby' tabs during pre-game into one tab
-
Added the little red x to the late-join job list
-
Late-joiners are warned if the shuttle is past the point of recall, and if the shuttle has already left the station
-
Late-joiners now see how long the round has been going on.
-
Mining shuttle computer no longer spits out both 'Shuttle has been sent' and 'The shuttle is already moving' every time.
-
-
-
-
-
-
Monday, April 9th
-
Petethegoat updated:
-
-
TORE OUT DETECTIVE WORK! THIS IS A TEMPORARY PATCH TO SEE IF THIS FIXES THE CRASHING.
-
DETECTIVE SCANNERS AND EVIDENCE BAGS (AND FINGERPRINTS) ARE GONE.
-
-
-
-
-
Sunday, April 8th
-
PolymorphBlue updated:
-
-
Secret little rooms now spawn on the mining asteroid, containing various artifacts.
-
Added the beginnings of a borg upgrade system. Currently, can be used to reset a borg's module.
Security officers can modify people's criminal status by simply examining them with a security hud on and clicking a link that will show up as part of the character's description.
-
Less jobs have maintenance access. The only jobs that will have it now are engineers, atmos techs, cargo techs, heads, and the detective.
-
Changed Runtime's sprite to look more catlike.
-
View Variables can now better list associative lists.
-
Miscellaneous bugfixes for the NT Script IDE.
-
-
PolymorphBlue updated:
-
-
Minor bugfixes to borg deathsquad, adds borg deathsquad to potential tensioner (set so high it's never going to happen)
-
Adds consiterable support for ERP! (If enabled.)
-
Increases cost for changeling unstun to 45
-
-
-
-
-
30 March 2012
-
Donkie updated:
-
-
You can now stick papers back in to paperbins, text will persist.
-
Added a [field] bbcode tag to the pen writing. Lets your start writing from that point.
-
Changed fonts a bit for papers to make [sign] stand out more.
-
-
Doohl updated:
-
-
Gave pill bottles the ability to scoop up pills like ore satchels scoop ore. (There you go, /vg/ Anon.)
-
Security Officers and Wardens now start with maintenance acceess.
-
-
-
-
-
29 March 2012
-
PolymorphBlue updated:
-
-
Exosuits now provide a message when someone is getting in, someone getting in must remain stationary and unstunned, and getting in takes four seconds.
-
-
-
-
-
28 March 2012
-
Carn updated:
-
-
Fixed turrets shooting people that leave the area and the telecomm turret controls.
-
-
Donkie updated:
-
-
Updated air alarm's GUI.
-
-
-
-
-
27 March 2012
-
Nodrak updated:
-
-
Security borgs now have modified tasers.
-
Security borgs have gone back to having the same movement speed as all other borgs.
-
-
-
-
-
23 March 2012
-
Doohl updated:
-
-
Escape shuttles/pods now spend about 2 minutes in high-speed transit before they reach centcom/recon shuttle. This is a warning: regular after-round shuttle grief is NOT OKAY while the shuttle is still in transit! Save it for when the shuttle gets to centcom! The purpose of this is to give potential antagonists and protagonists a chance to have a final showdown in the shuttle. The round does not end until the shutle comes to a stop and docks. Don't step outside while the shuttle is moving!
For example; if you are a traitor and have an escape-alone objective and a couple of people manage to squeeze in the shuttle, you have two minutes to kill/toss them out to win. Or you can just chill for the duration and reflect on the round.
-
-
-
Donkieyo updated:
-
-
A bunch new standard-namespace NTSL functions added! Check them out at the NT Script wiki page!
-
-
-
-
-
22 March 2012
-
Ricotez updated:
-
-
Medical Lockers, Security Lockers, Research Lockers, Warden Locker, CMO Locker, and RD locker all have new sprites.
-
Encryption keys now each have their own invidual sprites.
-
-
-
PolymorphBlue updated:
-
-
Added a prototype holodeck to fitness!
-
Assorted tensioner fixes
-
-
-
-
20 March 2012
-
Kor updated:
-
-
Lasertag vests and guns have been added to fitness.
-
Art storage has replaced the emergency storage near arrivals. Emergency storage has replaced chem storage (has anyone ever used that?)
-
Wraiths can now see in the dark
-
-
-
-
-
-
19 March 2012
-
PolymorphBlue updated:
-
-
Added LSD sting to modular changeling by popular demand.
-
Silence sting no longer provides a message to the victim.
-
Tensioner will no longer assign dead people as assassination targets.
-
-
-
-
-
18 March 2012
-
Quarxink updated:
-
-
The medical record computers can finally search for DNA and not just name and ID.
-
-
-
-
-
14 March 2012
-
PolymorphBlue updated:
-
-
Modular changeling added! Changelings now purchase the powers they want. Balancing still underway, but should be playable.
-
-
Petethegoat updated:
-
-
Janitor cyborgs have been massively upgraded. Suffice to say they're pretty ballin' now...
-
-
Nodrak updated:
-
-
You can now choose whether to spawn with a backpack, satchel, or nothing. Excess items will spawn in your hands if necessary.
-
You can now choose what kind of underwear you'd like to wear, per a request.
-
-
-
-
-
14 March 2012
-
Carn updated:
-
-
Added 6 female hairstyles -- credits go to Erthilo of Baystation. Added a male hairstyle -- credits go to WJohnston of TG. If you can sprite some unique and decent-looking hair sprites, feel free to PM me on the TG forums.
-
-
-
The way objects appear to be splattered with blood has been rewritten in an effort to fix stupid things happening with icons. It should be called far less frequently now. PLEASE, if you experience crashes that could in anyway be related to blood, report them with DETAILED information. Thanks
-
-
-
-
-
13 March 2012
-
Nodrak & Carn updated:
-
-
Fixed the way flashes break. Long story short: They'll never break on first use so rev don't get screwed over. They run out of charge temporarily when spammed but recharge. Spamming them also increases the chance of them breaking a little, so use them sparingly.
-
-
Doohl updated:
-
-
Ablative Armor now has a high chance of reflecting energy-based projectiles.
-
Riot shields were buffed; they now block more attacks and they will prevent their wielder from being pushed (most of the time).
-
-
-
-
-
12 March 2012
-
PolymorphBlue updated:
-
-
PDA messages now require an active messaging server to be properly sent.
-
-
-
-
-
11 March 2012
-
PolymorphBlue updated:
-
-
The AI can now open doors with shift+click, bolt them with ctrl+click, and shock them with alt+click
-
Tratior borgs who hack themselves cannot be blown by the robotics console, and can override lockdowns.
-
Adds a new wire to doors that controls the time delay before they close. If pulsed, they close like a sliding glass door. If cut, they do not close by themselves.
-
Borgs who have died, ghosts, and are then blown up will now have their ghosts properly transfered to their dropped MMIs.
-
-
Carnwennan updated:
-
-
You can now request AI presence at a holopad for immediate private communication with the AI anywhere. AIs can click a quick button to zoom to the holopad.
-
-
-
-
-
08 March 2012
-
Nodrak and Carnwennan updated:
-
-
Nodrak: Fixed crayon boxes and stuff getting stuck in pockets.
-
Nodrak: 'Steal item' objectives will report correctly when wrapped up in paper now.
-
Carn: fixed the vent in the freezer...poor chef kept suffocating.
-
-
-
-
-
02 March 2012
-
Carn updated:
-
-
Fixed a number of issues with mob examining. Including: not being able to see burns unless they were bruised; vast amounts of grammar; and icons. Updated them to use stylesheet classes.
-
Borgs can no-longer drop their module items on conveyor belts.
-
Names input into the setup screen are now lower-cased and then have their first letters capitalised. This is to fix problems with BYOND's text-parsing system.
-
Runtime fix for lighting.
-
Over the next few commits I will be updating a tonne of item names to fix text-parsing. Please inform me if I've typoed anything.
-
-
-
-
-
03 March 2012
-
Petethegoat updated:
-
-
Removed cloakers. Removed Security's thermals. Added disguised thermals as a traitor item.
-
-
-
-
-
01 March 2012
-
SkyMarshal updated:
-
-
Tweak/Bugfix for Hallucinations. Much more robust.
-
-
-
-
-
01 March 2012
-
SkyMarshal updated:
-
-
Ported BS12 Detective Work System
-
-
-
-
-
1 March 2012
-
Petethegoat updated:
-
-
Head revolutionaries no longer spawn with traitor uplinks.
-
-
-
-
-
-
29 February 2012
-
SkyMarshal updated:
-
-
BS12 Hallucination and Dreaming port
-
-
-
-
-
-
-
29 February 2012
-
muskets updated:
-
-
Integrated BS12's improved uplink code
-
-
-
-
-
-
26 February 2012
-
Doohl updated:
-
-
The insane crashing has finally been fixed!
-
-
-
-
-
25 February 2012
-
Doohl updated:
-
-
Telecommunications has been refined, with many new features and modules implemented.
-
NTSL (Nanotrasen Scripting Language) is ONLINE! This is a brand new, fully operational scripting language embedded within SS13 itself. The intended purpose is to eventually expand this scripting language to Robotics and possibly other jobs, but for now you may play with the TCS (Traffic Control Systems) implementation of NTSL in the Telecommunications Satellite. Recommended you read the NT Script wiki page for information on how to use the language itself. Other than that, there's not a lot of documentation.
-
Radio systems have been further optimized, bugfixed, etc. Should be more stable.
-
Intercoms now require power to work.
-
-
-
-
-
-
24 February 2012
-
PolymorphBlue updated:
-
-
Headsets are now modular! Use a screwdriver on them to pop out their encrpytion keys, and use a key on one to put it in. A headset can hold two keys. Normal headsets start with 1 key, department headsets with two. The standard chip does nothing, and is not required for listening to the common radio.
-
Binary translators made into a encrpytion key, and fixed. They now broadcast to AIs properly.
-
-
-
-
-
23 February 2012
-
PolymorphBlue updated:
-
-
MMIs/pAIs no longer lip read, and thus can now hear in the dark.
-
Borg rechargers are no longer Faraday cages, and thus can now receive radio while they're recharging.
-
-
LastyScratch updated:
-
-
Glass airlocks now make a different sound than regular airlocks.
-
in 1997 nanotrasen's first AI malfunctioned
-
Toggle ambience probably works now!
-
Runtime is dead.
-
The Research Director's consoles were moved into the completely empty cage in the back of his office.
-
-
-
-
-
-
22 February 2012
-
PolymorphBlue updated:
-
-
Changed alt+click to ctrl+click for pulling.
-
-
Petethegoat updated:
-
-
New stationary scrubber sprites~
-
Removed the mint. Coins can still be found and used in vending machines. PACMANs now run off sheets.
-
-
coolity updated:
-
-
New sprites for HoS and Captain lockers.
-
New sprites for the orebox.
-
-
-
-
-
21 February 2012
-
Petethegoat updated:
-
-
The jetpacks now display correctly when worn.
-
Buckling to chairs no longer causes you to drop your weapon
-
-
Nodrak updated:
-
-
Ghosts now have a "Jump to Mob" verb.
-
-
Sieve updated:
-
-
Mining lanterns work properly once again!.
-
Skaer updated:
-
-
The armoury now includes a box of spare Sec cartridges.
-
-
-
-
-
19 February 2012
-
Petethegoat updated:
-
-
The jetpacks in EVA have been replaced with CO2 ones, painted a classy black.
-
Additionally, jetpacks will now run on gases other than oxygen, as you would expect.
-
Chair overhaul! You shouldn't notice anything different, but if you encounter bugs with chairs or beds, please report those asap.
-
New electric chair sprites, by myself.
-
Electric chairs will only electrocute people buckled into them.
-
Karma should be fixed.
-
-
KorPhaeron updated:
-
-
A new construct type: Artificer. It is capable of constructing defenses, repairing fellow constructs, and summoning raw materials to construct further constructs
-
Simple animals (constructs, Ian, etc) can now see their health in the Status tab
-
Detective's revolver is non-lethal again. Was fun while it lasted
-
-
-
-
-
18 February 2012
-
Petethegoat updated:
-
-
Foam has a reduced range to prevent spamming
-
-
Sieve updated:
-
-
Stopped the unholy Radium/Uranium/Carbon smoke that crashed the server. And for anyone that did this, you are a horrible person
-
Cleanbots clean dirt
-
Cleanbots automatically patrol on construction
-
Removed silicate because it is not useful enough for how much lag it caused
-
-
-
-
-
16 February 2012
-
Smoke Carter updated:
-
-
Newscasters now alert people of new feeds and wanted-alerts simultaneously.
-
-
-
-
-
15 February 2012
-
Kor updated:
-
-
Terrorists Win! Desert Eagles and Riot Shields now spawn on the syndicate shuttle, replacing the c20r
-
The Detectives gun still uses .38, but they're now fully lethal bullets. Go ahead, make his day.
-
The Veil Render has been nerfed, the Nar-Sie it spawns will not pull anchored objects. This is a temporary measure, more nerfs/reworking to come
-
-
-
-
-
14 February 2012
-
Carn updated:
-
-
Spacevines added to the random events.
-
The bug where doors kept opening when a borg tried to close them at close range is now fixed.
-
-
-
-
-
13 February 2012
-
Khodoque updated:
-
-
Security officers, the warden and the HoS have new jumpsuits.
-
-
Erro updated:
-
-
Clicking the internals button on your user interface (The one that shows if you have internals on or not) will now toggle internals even if they are in your pockets. (humans only) - It now works if your internals are on your back, suit storage, belt, hands and pockets.
-
The public autolathe has been removed. If you want some some stuff from a lathe, go to cargo.
-
-
Kor updated:
-
-
A new item, the null rod, protects the one bearing it from cult magic. One starts in the chaplains office, and this replaces the job based immunity he had. The null rod also is capable of dispelling runes upon hitting them (the bible can no longer do this)
-
Shooting fuel tanks with lasers or bullets now causes them to explode
-
A construct shell is now waiting to be found in space.
-
Chaplains can no longer self heal with the bible
-
Simple animals, including constructs, can now attack mechs and critters
-
-
-
-
-
-
12 February 2012
-
Erro updated:
-
-
You can no longer attach photos to ID cards. This never worked properly and if anything, it was misleading.
-
Backpacks can now hold 7 normal sized items (box size) as opposed to 6 normal sized items + 1 small item
-
Added several fire alarms to areas around the station including the brig, engineering and others
-
The atmospherics department now has a few hazard vests available for atmos techs to wear if they don't like the fire suit
-
Roboticist now have engineering + science headsets, virologists now have medsci headsets with medical + science channels
-
Added some headsets to the jobs that didn't have any extras: roboticist, qm, scientist, virologist and geneticist.
-
Station engineers now have construction site access (vacent office by arrivals)
-
Replaced a few airlocks with glass airlocks (detective, autolathe, assistant storage, robotics, checkpoint)
-
Removed the wall that was blocking the entrance to the theater
-
Made a small redesign for the HoP's office so that people running towards it from the escape hallway don't run right into the queue, annoying everyong
-
The engineering, command and security airlocks now glow green when closing instead of red to match all the other airlocks
-
The disposal units now auto trigger every 30 game ticks, if there is something (or someone) in them. So no more hiding in disposal units!
-
You can no longer control the disposal unit from within it. You will have to wait for it to trigger itself.
-
You can no longer strip items off of Ian while dead / a ghost
-
-
Pete updated:
-
-
Updated fitness, athletic shorts are now available!
-
-
-
-
-
-
11 February 2012
-
Erro updated:
-
-
You can now take individual crayons out of the crayon box the same way as from boxes
-
Clicking a grille with a glass or reinforced glass pane in your hand will glaze the grille from the direction you're looking from (don't forget to fasten the window tho)
-
When you click somewhere with the intent to interact, you will automaticaly face the item you're trying to interact with. This won't slow you down when running and firing guns behind you.
-
-
Kor updated:
-
-
A new passive mob ability: Relentless. Relentless mobs cannot be shoved (though may still swap places with help intent)
-
Alien Queens, Juggernaut constructs, and Medical Borgs are all Relentless. Maybe the medborg can actually drag people to medbay on time now
-
Two constructs, the Juggernaut and the Wraith are now available for wizards and cultists to use soul stones with
-
A new highly destructive artefact, Veil Render, is now available for wizards
-
A new one time use global spell, Summon Guns, is now available for wizards.
-
DEEPSTRIKING! There is now a partially constructed teleporter on the nuke shuttle, and for a large sum of telecrystals they may purchase the circuitboard needed to complete it.
-
The Chaplain is immune to cult stun, blind, deafen, and blood boil
-
-
-
-
-
10 February 2012
-
Quarxink updated:
-
-
Added a new toy: Water balloons. They can be filled with any reagent and when thrown apply the reagents to the tile and everything on it.
-
-
-
-
-
9 February 2012
-
Erro updated:
-
-
Engineering and security lockers now spawn with their respective backpacks in them so job-changers can look as they should. HoS locker now also contains an armored vest, for the convenience of the HoS who wants to play with one.
-
Slightly changed the spawn order of items in the CE and HoS lockers to make starting up a hint less tedious.
-
-
-
-
-
8 February 2012
-
ConstantA updated:
-
-
Added Exosuit Jetpack
-
Added Exosuit Nuclear Reactor (runs of normal, everyday uranium, maybe I'll switch it to run on enriched) - requires research (level 3 in Materials, Power Manipulation and Engineering)
-
Added Ripley construction steps sprites (courtesy of WJohnston - man, you're awesome)
-
Exosuit Sleeper can now inject occupant with reagents taken from Syringe Gun
-
Exosuit Cable Layer will now auto-dismantle floors
-
Exosuit Heavy Lazer cooldown increased, Scattershot now fires medium calibre ammo (less damage)
-
Exosuit wreckage can be pulled
-
EMP now drains half of current exosuit cell charge, not half of maximum charge.
-
Fixed several possible exosuit equipment runtimes
-
Introduced new markup to changelog. Javascript is extremely slow (in byond embedded browser) for some reason.
-
-
-
-
4 February 2012, World Cancer Day
-
Erro updated:
-
-
Examining humans now works a bit differently. Some external suits and helmets can hide certain pieces of clothing so you don't see them when examining. Glasses are also now displayed when examining.
-
The job selection screen has been changed a little to hopefully make making changes there easier.
-
-
-31 January 2012
-
-
Carn updated:
-
-
Grammar & various bug-fixes
-
Thank-you to everyone who reported spelling/grammar mistakes. I'm still working on it, so if you spot anymore please leave a comment here. There's still lots to fix.
-
Mining station areas should no longer lose air.
-
-
-
-
-30 January 2012(
-
-
Sieve updated:
-
-
This stuff is actually already implemented, it just didn't make it to the changelog
-
Firefighter Mech - A reinforced Ripley that is more resistant to better cope with fires, simply look in the Ripley Contruction manual for instructions.
-
Mech contruction now has sounds for each step, not just 1/4 of them.
-
Mech Fabricators are fixed, Manual Sync now works and certain reseach will reduce the time needed to build components.
-
Added special flaps to the mining station that disallow air-flow, removing the need to shuffle Ore Boxes through the Airlocks.
-
Each outpost has it's own system for the conveyors so they won't interfere with each other.
-
Powercell chargers have been buffed so now higher capacity cells are actually useable.
-
A diamond mech drill has been added. While it isn't any stronger than the standard drill, it is much faster.
-
-
-
-
-29 January 2012, got Comp Arch exams on Wednesday :(
-
-
Agouri updated:
-
-
UPDATE ON THE UPDATE: Newspapers are now fully working, sorry for that. Some minor icon bugs fixed. Now I'm free to work on the contest prizes :3
-
Newscasters are now LIVE! Bug reports, suggestions for extra uses, tears etc go here.
-
What ARE newscasters? Fans of the Transmetropolitan series might find them familiar. Basically, they're terminals connected to a station-wide news network. Users are able to submit channels of their own (one per identified user, with channels allowing feed stories by other people or, if you want the channel to be your very own SpaceJournal, being submit-locked to you), while others are able to read the channels, either through the terminals or a printed newspaper which contains every news-story circulating at the time of printing.
-
About censorship: You can censor channels and feed stories through Security casters, found in the HoS'es office and the Bridge. Alternatively, if you want a channel to stop operating completely, you can mark it with a D-Notice which will freeze it and make all its messages unreadable for the duration it is in effect. If you've got the access, of course.
-
Basically I think of the newscaster as nothing more as an additional Roleplaying tool. Grab a newspaper along with your donuts and coffee from the machines, read station rumors when you're manning your desk, be a station adventurer or journalist with your very own network journal!
-
I would ask for a bit of respect when using the machine, though. I removed all and any channel and story restrictions regarding content, so you might end up seeing channels that violate the rules, Report those to the admins.
-
Finally, due to the removal of the enforced "Channel" string, it's recommended to name your channels properly ("Station Paranormal Activity Channel" instead of "Station Paranormal Activity", for example")
-
-
-
-
-28 January 2012
-
-
BubbleWrap updated:
-
-
Arresting buff!
-
A person in handcuffs being pulled cannot be bumped out of the way, nor can the person pulling them. They can still push through a crowd (they get bumped back to behind the person being pulled, or pushed ahead depending on intent).
-
-
-
-27 January 2012
-
-
LastyScratch updated:
-
-
Toggle-Ambience now works properly and has been moved from the OOC tab to the Special Verbs tab to be with all the other toggles.
-
-
RavingManiac updated:
-
-
The bar now has a "stage" area for performances.
-
-
Blaank updated:
-
-
Added a vending machine to atmopherics reception desk that dispenses large
-oxygen tanks, plasma tanks, emergency oxegen tanks, extended capacity emergency
-oxygen tanks, and breath masks.
-
-
Petethegoat updated (for a bunch of other people):
-
-
Lattice is now removed when you create plating or floor (credit Donkieyo).
-
Monkeys now take damage while in crit (credit Nodrak).
-
The warden now has his own jacket. (credit Shiftyeyesshady).
-
Spectacular new dice that will display the proper side when rolled!! (credit TedJustice)
-
Spectacular new dice that will display the proper side when rolled!! (credit TedJustice)
-
-
Borg RCDs can no longer take down R-walls. (headcoder orders)
-
-
-19 January 2012
-
-
Petethegoat updated:
-
-
Exciting new pen additions! Get the low-down at the wiki.
-
-
-
-17 January 2012
-
-
Doohl updated:
-
-
Syndicate shuttle now starts with a All-In-One telecommunication machine, which acts as a mini-network for the syndie channel. It intercepts all station radio activity, too, how cool is that?
-
-
-
-15 January 2012
-
-
Doohl updated:
-
-
The radio overhaul 'Telecommunications' is now LIVE. Please submit any opinions/feedback in the forums and check the wiki article on Telecommunications for some more info for the curious.
-
The AI satellite has been replaced with a communications satellite. You can get there via teleporter or space, just like the AI satellite. I highly recommend not bum-rushing the new satellite, as you may be killed if you don't have access. It's a very secure place.
-
Once a human's toxicity level reaches a certain point, they begin throwing up. This is a natural, but overall ineffective method of purging toxins from the body.
-
You can now travel Z-levels in Nuclear Emergency mode (the nuke disk is still bound to the station). This means the nuclear agents can and probably will fly off into space to blow up the comm satellite and shut down communications.
-
-
-
-9 January 2012
-
-
ConstantA updated:
-
-
Reworked exosuit internal atmospherics (the situation when exosuit is set to take air from internal tank, otherwise cabin air = location air):
-
-
If current cabin presure is lower than "tank output pressure", the air will be taken from internal tank (if possible), to equalize cabin pressure to "tank output pressure"
-
If current cabin presure is higher than "tank output pressure", the air will be siphoned from cabin to location until cabin pressure is equal to "tank output pressure"
-
Tank air is not altered in any way even if it's overheated or overpressured - connect exosuit to atmos connector port to vent it
-
"Tank output pressure" can be set through Maintenance window - Initiate maintenance protocol to get the option
-
-
-
Fixed bug that prevented exosuit tank air updates if exosuit was connected to connector port
-
Combat exosuits melee won't gib dead mobs anymore
-
QM exosuit circuit crates cost lowered to 30 points
-
Exosuit plasma converter effectiveness +50%
-
-
-
-
-8 January 2012
-
-
Agouri updated:
-
-
I'm back home and resumed work on Newscasters and Contraband.
-
But I got bored and made cargo softcaps instead. Flippable! Enjoy, now all we need is deliverable pizzas.
-
Oh, also enjoy some new bodybag functionality and sounds I had ready a while ago, with sprites from Farart. Use a pen to create a visible tag on the bodybag. Wirecutters to cut it off. Also it's no longer weldable because it makes no goddamn sense.
-
-
-
-7 January 2012
-
-
Donkieyo updated:
-
-
You must now repair damaged plating with a welder before placing a floor tile.
-
You can now relabel canisters if they're under 1kPa.
-
-
Polymorph updated:
-
-
Dragging your PDA onto your person from your inventory will bring up the PDA screen.
-
You can now send emergancy messages to Centcomm (Or, with some.. tampering, the Syndicate.) via a comms console. (This occurs in much the fashion as a prayer.)
-
-
-3 January 2012
-
-
Erro updated:
-
-
Shift-clicking will now examine whatever you clicked on!
-
-
Polymorph updated:
-
-
Alt-clicking will now pull whatever you clicked on!
-
-
-
-1 January 2012 (12 more months until doomsday)
-
-
Doohl updated:
-
-
XENOS ARE NOW IMMUNE TO STUNNING! To compensate, stunning via tasers/batons now slows them down significantly.
-
-
-
Polymorph updated:
-
-
Doors no longer close if they have a mob in the tile. (Generally!) Door safties can now be overriden to close a door with a mob in the tile and injure them severely.
-
-
-
-
-29 December 2011
-
-
ConstantA updated:
-
-
Added some new Odysseus parts and tweaked old ones.
-
Added Exosuit Syringe Gun Module
-
New Odysseus sprites - courtesy of Veyveyr
-
-
Polymorph updated:
-
-
Air Alarms can now be hacked.
-
Too much of a good thing is just as bad as too little. Pressures over 3000 kPa will do brute damage.
-
-
-
-28 December 2011
-
-
RavingManiac updated:
-
-
Wrapped objects can now be labelled with a pen
-
Wrapped small packages can be picked up, and are now opened by being used on themselves
-
Mail office remapped such that packages flushed down disposals end up on a special table
-
Package wrappers placed in most of the station departments
-
In short, you can now mail things to other departments by wrapping the object, labelling it with the desired destination using a pen, and flushing it down disposals. At the mail room, the cargo tech will then tag and send the package to the department.
-
-
-
-27 December 2011
-
-
Errorage updated:
-
-
Engineering's been remapped
-
-
RavingManiac updated:
-
-
Refrigerators and freezer crates will now preserve meat
Circuit boards for Odysseus mech can be ordered by QM
-
Designs for them were added to R&D
-
-
-
Kor updated:
-
-
Soul Stones Added: Like intellicards for dead or dying humans! Full details are too long for the changelog
-
A belt full of six soul stones is available as an artefact for the wizard
-
Cultists can buy soulstones with their supply talisman
-
The chaplain has a single soulstone on his desk
-
The reactive teleport armour's test run is over. It no longer spawns in the RD's office.
-
-
-
-
-
-
-24 December 2011
-
-
Rockdtben updated:
-
-
Added sprites for soda can in left and right hands on mob: sodawater, tonic, purple_can, ice_tea_can, energy_drink, thirteen_loko, space_mountain_wind, dr_gibb, starkist, space-up, and lemon-lime.
-
-
-
-
-
-23 December 2011
-
-
ConstantA updated:
-
-
Mech Fabricators now require robotics ID to operate. Emag removes this restriction.
-
Added Odysseus Medical Exosuit. Has integrated Medical Hud and ability to mount medical modules.
-
Added Sleeper Medical module for exosuits. Similar to common sleepers, but no ability to inject reagents.
-
Added Cable Layer module for exosuits. Load with cable (attack cable with it), activate, walk over dismantled floor.
-
Added another exosuit internal damage type - short circuit. Short-circuited exosuits will drain powercell charge and power relay won't work.
-
You should be able to send messages to exosuit operators using Exosuit Control Console
-
Gygax armour and module capacity nerfed.
-
Exosuit weapon recharge time raised.
-
Bugfix: EMP actually drains exosuit cell and damages it
-
-
-
RavingManiac updated:
-
-
Meat will now spoil within three minutes at temperatures between 0C and 100C.
-
Rotten meat has the same nutritional value as normal meat, and can be used in
-the same recipes. However, it is toxic, and ingesting a badly-prepared big bite
-burger can kill you.
-
Because refrigeration serves a purpose now, the kitchen cold room freezing unit
-is turned off by default. Chefs should remember to turn the freezer on at the
-start of their shift.
-
-
-
-
-21 December 2011
-
-
RavingManiac updated:
-
-
Kitchen cold room is now cooled by a freezing unit. Temperature is about 240K by default, but can be raised to room temperature or lowered to lethal coldness.
-
-
-
-19 December 2011
-
-
Kor updated:
-
-
General/Misc Changes
-
-
Escape pods no longer go to the horrific gibbing chambers. Rather, they will be picked up by a salvage ship in deep space. (This basically changes nothing mechanics wise, just fluff)
-
An ion rifle now spawns on the nuclear operative shuttle. Maybe this will help with them getting destroyed by sec borgs every round?
-
-
-
Wizard Changes
-
-
The wizard can now purchase magic artefacts in addition to spells in a subsection of the spellbook.
-
The first (and currently only) new artefact is the Staff of Change, which functions as a self recharging energy weapon with some special effects.
-
The wizard has a new alternative set of robes on his shuttle.
-
-
-
Cult Changes
-
Cultists now each start with three words (join, blood, self). No more will you suffer at the hands of cultists who refuse to share words.
-
The starting supply talisman can now be used five times and can now be used to spawn armor and a blade.
-
Replaced the sprites on the cultist robes/hood.
-
-
-
-
-18 December 2011
-
-
Carnwennan updated:
-
-
Thanks to the wonders of modern technology and the Nanotrasen steel press Ian's head has been shaped to fit even more silly hats. The taxpayers will be pleased.
-
-
-
Doohl updated:
-
-
Vending machines got yet another overhaul! Good lord, when will they stop assfucking those damned vendors??
-
-
-
-
-17 December 2011
-
-
Erro updated:
-
-
Your direct supervisors are now displayed when you are assigned a job at round start or late join.
-
-
-
-
-14 December 2011
-
-
Erro updated:
-
-
Meteor mode is hopefully deadly again!
-
-
-
Kor updated:
-
-
Research director has a new toy: Reactive Teleport Armour. Click it in your hand to activate it and try it out!
-
-
-
-
-11 December 2011
-
-
NEO updated:
-
-
AIs actually consume power from APCs now
-
Bigass malf overhaul. tl;dr no more AI sat, instead you have to play whackamole with APCs.
-
-
-
-
-10 December 2011
-
-
Doohl updated:
-
-
Title music now plays in the pregame lobby. You can toggle this with a verb in "Special Verbs" if you really want to.
-
User Interface preferences now properly get transferred when you get cloned.
-
-
-
Erro updated:
-
-
Escape pods have been added to test the concept.
-
Escaping alone in a pod does not count towards the escape alone objective, it counts towards the escape alive objective tho. Escape alone only requires you to escape alone on the emergency shuttle, it doesn't require you to ensure all pods are empty. Cult members that escape on the pods do not ocunt towards the cult escaping acolyte number objective. Escaping on a pod is a valid way to survive meteor.
-
-
-
Polymorph updated:
-
-
Fire is now actually dangerous. Do not touch fire.
-
-
-
-
-
-
-8 December 2011
-
-
Errorage updated:
-
-
Fixed the comms console locking up when you tried to change the alert level.
-
Added a keycard authentication device, which is used for high-security events. The idea behind it is the same as the two-key thing from submarine movies. You select the event you wish to trigger on one of the devices and then swipe your ID, if someone swipes their ID on one of the other devices within 2 seconds, the event is enacted. These devices are in each of the head's offices and all heads have the access level to confirm an event, it can also be added to cards at the HoP's ID computer. The only event that can currently be enacted is Red alert.
-
-
-
Kor updated:
-
-
The chef now has a fancy dinner mint in his kitchen. It is only wafer thin!
-
-
-
-
-3 December 2011
-
-
Pete & Erro Christmas update:
-
-
Reinforced metal renamed to steel and steel floor tile renamed to metal floor tile to avoid confusion before it even happens.
-
It is no longer possible to make steel from metal or vice versa or from the autolathe. You can however make metal from the autolathe and still insert steel to increase the metal resource.
-
To make steel you can now use the mining smelting unit and smelt iron and plasma ore.
-
The RCD can no longer take down reinforced walls.
-
-
-
Errorage updated:
-
-
Grass plants in hydro now make grass floor tiles instead of the awkward patches.
-
-
-
Petethegoat updated:
-
-
Fixed all known vending machine issues.
-
Fixed a minor visual bug with emagged lockers.
-
Clarified some of the APC construction/deconstruction messages.
-
-
-
Numbers updated:
-
-
Potency variations tipped in favour of bigger changes over smaller periods of time.
-
-
-
PolymorphBlue updated:
-
-
Traitors in the escape shuttle's prison cell will now fail their objective.
-
Lockers are no longer soundproof! (or flashproof, for that matter)
-
Headrevs can no longer be borged, revs are dereved when borged.
-
Changeling husks are now borgable again (though not clonable) and genome requirements were lowered
-
De-revved revolutionaries had their message clarified a bit. You remember the person who flashed you, and so can out ONE revhead
-
Light tubes/bulbs can now be created in the autolathe. Recycle those broken lights!
-
-
-
-
-22 November 2011
-
-
Doohl updated:
-
-
The firing range now has a purpose. Go check it out; there's a few surprises!
-
-
-
-
-19 November 2011
-
-
Doohl updated:
-
-
Toggling admin midis will now DISABLE THE CURRENT MIDI OH MY GOSH!
-
-
-
Tobba updated:
-
-
We're looking for feedback on the updated chem dispenser! It no longer dispenses beakers of the reagent, and instead places a variable amount of the reagent into the beaker of your choosing.
-
-
-
Petethegoat updated:
-
-
Diagonal movement is gone on account of them proving to be a bad idea in practice. A grand experiment nonetheless.
-
-
-
Kor updated:
-
-
The PALADIN lawset in the AI upload has been replaced with the corporate lawset
-
-
-
-
-16 November 2011
-
-
Tobba updated:
-
-
Report any issues with the updated vending machines!
-
-
-
-
-16 November 2011
-
-
Petethegoat updated:
-
-
Security, Engineer, Medical, and Janitor borgs no longer get a choice of skin. This is for purposes of quick recognition, and is the first part of a series of upcoming cyborg updates.
-
-
-
-
-7 November 2011
-
-
Kor updated:
-
-
Repair bots (mechs) are now adminspawn only
-
Extra loyalty implants are now orderable via cargo bay (60 points for 4 implants)
-
Changeling regen stasis now takes two full minutes to use, but can be used while dead. Burning and gibbing are the only way to keep them dead now.
-
-
-
-
-29 October 2011
-
-
ConstantA updated:
-
-
Added step and turn sounds for mechs
-
Added another mecha equipment - plasma converter. Works similar to portable generator. Uses solid plasma as fuel. Can be refueled either by clicking on it with plasma in hand, or directly from mecha - selecting it and clicking on plasma.
-
Added mecha laser cannon.
-
Added damage absorption for mechs. Different mechs have different absorption for different types of damage.
-
Metal foam now blocks air movement.
-
-
-
Petethegoat updated:
-
-
Fixed sticking C4 to containers.
-
Rearranged the armoury, and changed the medical treatment room in Sec to an interrogation room.
-
Mr Fixit has been powered off and returned to the armoury. Deploying him every round is still recommended!
-
-
-
-
-29 October 2011
-
-
Petethegoat updated:
-
-
Stunglove overhaul: part one. Stun gloves are now made by wiring a pair of gloves, and then attaching a battery- this shows up on the object sprite, but not on your character. Stungloves use 2500 charge per stun! This means that some low capacity batteries will make useless stungloves. To get your old inconspicous gloves back, simply cut away the wire and battery. Note that insulated gloves lose their insulation when you wire them up! Yet to come: stungloves taking extra damage from shocked doors.
-
Removed sleepypens! Paralysis pens have been changed to look like normal pens instead of penlights, and have been slightly nerfed. They will paralyse for about fifteen seconds, and cause minor brain damage and dizziness.
-
Uplink Implants now have five telecrystals instead of four.
-
-
-
Doohl updated:
-
-
More hairs added and a very long beard.
-
Finally fixed the request console announcements going AMP AMP AMP all the time when you use punctuation.
-
-
-
-
-27 October 2011
-
-
Mport updated:
-
-
New WIP TK system added. To activate your TK click the throw button with an empty hand. This will bring up a tkgrab item. Click on a non-anchored (currently) non mob Object to select it as your "focus". Once a focus is selected so long as you are in range of the focus you can now click somewhere to throw the focus at the target. To quit using TK just drop the tkgrab item.
-
-
-
-
-21 October 2011, Tuesday:
-
-
Errorage updated:
-
-
Old keyboard hotkey layout option available again! home, end, page down and page up now once again do what they did before by default. To use diagonal movement you will need to use your numpad with NUM LOCK enabled.
- The new list of hotkeys is as follows: (Valid as of 21.10.2011)
-
-
Numpad with Num Lock enabled = movement in wanted direction.
-
Numpad with Num Lock disabled = as it was before. movement north-south-east-west and throw, drop, swap hands, use item on itself.
-
Page up (also numpad 9 with num lock disabled) = swap hands
-
Page down (also numpad 3 with num lock disabled) = use item in hand on itself
-
home (also numpad 7 with num lock disabled) = drop
-
end (also numpad 1 with num lock disabled) = throw
-
CTRL + A = throw
-
CTRL + S = swap hands
-
CTRL + D = drop
-
CTRL + W = use item in hand on itself
-
Numpad divide (/) = throw
-
Numpad multiply (*) = swap hands
-
Numpad subtract (-) = drop
-
Numpad add (+) = use item in hand on itself
-
- In short, use Num Lock to swap between the two layouts.
-
-
-
-
-
-18 October 2011, Tuesday:
-
-
Errorage updated:
-
-
You can now move diagonally! To do so, use the numpad. The keybaord has been remapped to make this possible:
-
There has been a tidal wave of bugfixes over the last 5 or so days. If you had previously tried something on the station, saw that it was bugged and never tried it again, chances are it got fixed. I don't want you to neglect using stuff because you think it was bugged, which it was at one point, but no longer is. Thanks, happy new semester to everyone~
-
-
-
Errorage updated:
-
-
When you're unconscious, paralyzed, sleeping, etc. you will still see the same blackness as always, but it will rarely flicker a bit to allow you to see a little of your surroundings.
-
-
-
Doohl updated:
-
-
New hairstyles! YOU never asked for this!
-
-
-
-
-11 October 2011:
-
-
ConstantA updated:
-
-
Added radios to exosuits. Setting can be found in 'Electronics' menu.
-
Exosuit maintenance can be initiated even if it's occupied. The pilot must permit maintenance through 'Permissions & Logging' - 'Permit maintenance protocols'. For combat exosuits it's disabled by default. While in maintenance mode, exosuit can't move or use equipment.
-
-
-
-
-8 October 2011:
-
-
Doohl updated:
-
-
You can put things on trays and mass-transport them now. To put stuff on trays, simply pick up a tray with items underneath/on top of it and they'll be automatically carried on top of the tray. Be careful not to make a mess~!
-
-
-
Mport updated:
-
-
Telekenesis now only allows you to pick up objects.
-
Stun gloves ignore intent.
-
Moved the loyalty implants to the HoS' locker.
-
Job system redone, remember to setup your prefs.
-
-
-
-
-2 October 2011:
-
-
Petethegoat updated:
-
-
Pandemic recharge speed is affected by the number of different types of antibodies in the blood sample. For maximum efficiency, use blood samples with only a single type of antibody. (Blood samples with two types of antibodies will still let the Pandemic recharge slightly faster than it used to.)
-
-
-
Errorage updated:
-
-
Opening a storage item on your belt will now display the proper number of slots even if the number is different from 7.
Xenomorphic aliens can now shape resin membranes (organic windows basically).
-
The AI can no longer see runes. Instead, they will see blood splatters.
-
-
-
-
-28 September 2011:
-
-
Rolan7 updated:
-
-
New method for job assignment. Remember to review your preferences. Send all your hate and bug reports to me.
-
-
-
Doohl updated:
-
-
Putting someone inside a cloning machine's DNA scanner will notify the person that they are about to be cloned. This completely removes the necessity to announce over OOC for someone to get back in their body.
-
-
-
-
-22 September 2011, OneWebDay:
-
-
Errorage updated:
-
-
Added an additional 9 colors, into which you can color bedsheets, jumpsuits, gloves and shoes at the washing machine.
-
A new click proc will need to be live-tested. The testing will be announced via OOC and you will get a message when it happens. When testing is going on, the new click proc will be used. If testing is going on and you notice a bug, please report it via adminhelp. If you find yourself unable to perform an action, you can double click (By that I mean spam the shit out of clicking) to use the old proc, which is unchanged and will behave like you're used to. Standard roleplay rules apply during tests, they're not an excuse to murder eachother.
-
-
-
-
-20 September 2011, 10 year anniversary of the declaration of the "war on terror":
-
-
Errorage updated:
-
-
You can no longer clone people who suicided. I REPEAT! You can no longer clone people who have suicided! So use suiciding more carefully and only if you ACTUALLY want to get out of a round. You can normally clone people who succumbed tho, so don't worry about that.
-
Washing your hands in the sink will now only wash your hands and gloves. You can wash items if you have them in your hands. Both of these actions are no longer instant tho.
-
-
-
-
-18 September 2011, World Water Monitoring Day:
-
-
Errorage updated:
-
-
Added the most fun activity in your every-day life. Laundry. Check the dormitory. (Sprites by Hempuli)
-
You can now change the color of jumpsuits, shoes, gloves and bedsheets using the washing machine.
-
Some religions (currently Islam, Scientology and Atheism) set your chapel's symbols to the symbols of that religion.
-
A new old-style cabinet's been added to the detective's office. (Sprite by Hempuli)
-
Runtime the cat ran away!! And it gets even worse! Mr. Deempisi met a premature end during an excursion to the kitchen's freezer! -- I was ORDERED to do this... don't kill me! :(
-
Kudzu can now be spawned (Currently admin-only. Will test a bit, balance it properly and add it as a random event) (Original code and sprites donated by I Said No)
-
-
-
Kor updated:
-
-
Added purple goggles to chemistry (you're welcome Lasty)
-
Added a third MMI to robotics and monkey cubes to xenobio (dont fucking spam them in the halls)
-
-
-
Mport updated:
-
-
Turns out tasers and a few other weapons were slightly bugged when it came to checking the internal powercell. Tasers and such gained an extra shot.
-
Ion Rifle shots lowered to 5 per charge.
-
-
-
-
-17 September 2011, Operation Market Garden remembrance day:
-
-
Errorage updated:
-
-
You can now insert a coin into vending machines. Some machines (currently only the cigarette vending machine) have special items that you can only get to with a coin. No, hacking will not let you get the items, coin only.
-
-
-
-
-14 September 2011:
-
-
Lasty updated:
-
-
Runtime now actually spawns in medbay because nobody cared whether it is a tiny smugfaced espeon that explodes violently on death or a spacecat that presumably doesn't.
-
You can no longer put someone into a sleeper and erase them from existence by climbing into the same sleeper.
-
Players who haven't entered the game will no longer be able to hear administrators in deadchat.
-
-
-
Pete updated:
-
-
Added new sprites for the light tube and glasses boxes.
-
Fixed the bug where syndicate bundles would have an emergency O2 tank and breath mask.
-
-
-
Mport, SECOND REMOVER OF SUNS updated:
-
-
Singularity absorbtion explosion range lowered and is now dependent on singularity size.
-
Bag of Holding no longer instakills singularity, and the chance for bombs to destroy a singularity has been changed from 10% to 25%.
-
Removed THE SUN.
-
Damage and stun duration from shocked doors has been lowered to account for a larger amount of energy in the powernet.
-
-
-
-13 September 2011:
-
-
Errorage updated:
-
-
Healing, attacking or 'gently tapping' Ian will now properly display the name of the attacker to all people.
Ian can now be healed with bruisepacks, unless he's already dead.
-
You can now walk over Ian when he's killed.
-
Ian will chase food, if you leave it on the floor. If he notices it and you pick it up, he'll chase you around, if you stay close enough.
-
-
-
-
-10 September 2011:
-
-
Errorage updated:
-
-
A new pet on the bridge.
-
The pet can now be buckled and will no longer escape from closed containers, such as closets or the cloning pods.
-
Vending machines and request consoles are the first to use the new in-built browser in the upper-right of the user interface. If feedback is positive on these, more machines will be added to this. Hoping that this will eventually reduce the number of popup micromanagement.
-
-
-
Lasty updated:
-
-
The collectible hats have been removed from the theatre, doomed to rot forever in the hat crates they spawned from. No longer shall you see racks full of "collectible hard hat"!
-
-
-
TLE updated:
-
-
You can now toggle the message for becoming a pAI on and off in your prefs.
-
-
-
Mport updated:
-
-
Synaptizine now once again helps you recover from being stunned, however it is now also slightly toxic and may cause a small amount of toxins damage for every tick that it is in your system.
-
Assembly updating!
-
Original blob is back, though it still has lava sprites.
-
The bug where you would spawn on the wizard shuttle for a second at the start of the round should no longer occur.
-
-
-
-
-8 September 2011:
-
-
Lasty updated:
-
-
Suicide has been changed to biting your tongue off instead of holding your breath, and examining someone who has comitted suicide will give you a message stating that their tongue is missing.
-
Chemsprayers are now large items instead of small, meaning they can no longer fit in your pocket.
Removed dumb/badly sprited drinks for barman. This is for you, people that love to play barman. Your job is now non-retarded again. EDIT: FIXED DRINK MIXING
Fixed the karma exploit! Uhangi can rest in peace knowing his -87 karma ways were not his own.
-
Added new ambient sound for space and the mines.
-
Examining an oxygen tank will now tell you if it is about to run out of air. If it is, you will recieve a beep and a message, and if not, then you'll just get the default "this is an oxygentank!" message.
Sleeper update! Sleepers can now only inject soporific, dermaline, bicaridine, and dexaline into people with 1% or more health. They can also inject inaprovaline into people with -100% or more health. Nothing can be injected into people who are dead.
-
-
-
Superxpdude updated:
-
-
Virology is now part of medbay, and as such the RD no longer has Virology access and the CMO and Virologist no longer have Research access.
-
-
-
Uhangi updated:
-
-
Electropacks, screwdrivers, headsets, radio signalers, and station bounced radios can now be constructed from the autolathe.
-
Added a courtroom to Centcom.
-
Fixed electropack bug regarding using screwdrivers on electropacks.
Updates made to the HoP's ID computer. New interface and removing a card will now put it directly in your hand, if it's empty. Using your card on the computer will place it in the appropriate spot. If it has access to edit cards it will put it as the authentication card, otherwise as the card to be modified. If you have two cards with ID computer access first insert the authentication card, then the one you wish to midify, or do it manually like before.
People who have been infected by facehuggers can no longer suicide.
-
Stammering has been reworked.
-
The chaplain can now no longer discern what ghosts are saying, instead receiving flavour text indicating that ghosts are speaking to him.
-
Walking Mushroom yield decreased from 4 to 1.
-
Glowshrooms now only spread on asteroid tiles.
-
Fixed rev round end message reporting everyone as dead.
-
Gave the changeling an unfat sting.
-
-
Erro updated:
-
-
R'n'D and Gas Storage locations have been swapped in order for R&D to be given a hallway-facing table, just like Chemistry.
-
Buckling someone to a chair now causes them to face in the same direction as the chair.
-
Conveyor will now move only 10 items per game cycle. This is to prevent miners from overloading the belts with 2000 pieces of ore and slowing down time by turning it on. Does not apply to mobs on belt.
-
-
Doohl updated:
-
-
New escape shuttle!
-
Metroids get hungry slower, but gain more nutrients from eating.
-
-
Kor updated:
-
-
Added Necronomicon bible (credit Joseph Curwen)
-
Removed Doc Scratch clothing from chaplain? locker and added as a random spawn in the theatre.
-
Shaft Miners now start with regular oxygen tanks.
-
Mutate now gives the wizard hulk and OPTIC BLAST instead of hulk and TK.
-
Spiderman suit is no longer armoured or space worthy.
-
Crayons
-
-
Superxpdude updated:
-
-
New, more appropriate arrivals message.
-
Shuttle escape doors fixed.
-
New RIG sprite.
-
-
Lasty updated:
-
-
Switched xenomorph weeds to run in the background, hopefully causing them to destroy the server slightly less.
-
-
Microwave updated:
-
-
Added sink to hydroponics
-
Cleaned up autolathe menu
-
Glasses and cups can now be filled with water from sinks.
-
-
-
-31 August 2011.
-
-
Lasty updated:
-
-
The costumes that spawn in the theatre are now randomized.
-
The kitchen now has a Smartfridge which can be directly loaded from the Botanist? plantbags. No more crates! (credit to Rolan7).
-
Snappops can now be acquired from the arcade machines. Amaze your friends! (credit to Petethegoat)
-
Bangindonk.ogg added to random end sounds list.
-
-
Urist McDorf updated:
-
-
Players can no longer be randomly assigned to Librarian, Atmospherics Technician, Chaplain, and Lawyer unless all other non-assisstant job slots are full or they have those jobs in their preferences.
-
Changeling mode now has multiple changelings.
-
-
Superpxdude updated:
-
-
The mail system actually works properly now! Probably!
-
Most hats had their ?eadspace?tag removed, meaning they will no longer substitute for a space helmet in protecting you from the dangers of space.
-
-
-
-28 August 2011.
-
-
Doohl updated:
-
-
Chaplains can now select different bible icons when they start. The selection is applied globally and the library's bible printer will print the same bibles.
-
Joy to the world! The Library's bible-printing function now has a one-minute cooldown. One minute may seem a little extreme, but it is necessary to prevent people from spamming the fuck out of everything with 100,000,000,000,000 carbon-copy bibles.
-
Tweaked Metroids a bit; they are slightly more aggressive and become hungrier faster. To compensate, they now move slightly slower.
-
-
-
-26 August 2011.
-
-
Mport updated:
-
-
Rev:
-
-
Station Heads or Head Revs who leave z1 will count as dead so long as they are off of the z level.
-
Once a player has been unconverted they may not be reconverted.
-
-
Cult:
-
-
Heads other than the Captain and HoS are now able to start as or be converted to a cultist.
-
New Item: Loyalty Implant, which will prevent revving/culting. 4 spawn in the armory.
-
If a rev (not cultist) is injected with one he will unconvert, if a revhead is injected it will display a resist message.
-
Loyalty Implants show up on the SecHud
-
New Machine: Loyalty Implanter - Is on the prison station, shove a guy inside it to implant a loyalty implant. It can implant 5 times before it needs a 10 minute cooldown.
-
-
-
-
-20 August 2011.
-
-
Doohl updated:
-
-
The smoke chemistry recipe has been upgraded! You can lace smoke with chemicals that can bathe people or enter their lungs through inhalation. Yes, this means you can make chloral hydrate smoke bombs. No, this doesn't mean you can make napalm smoke or foam smoke.
-
-
-
-16 August 2011.
-
-
Superxpdude updated:
-
-
Traitor item bundles: Contains a random selection of traitor gear
-
-
-
-
-
Uhangi updated:
-
-
.38 Special Ammo can now be made from unhacked autolathes
-
-
-
-15 August 2011.
-
-
Superxpdude updated:
-
-
NEW MINING STATION, POST YOUR OPINIONS AND BUGS HERE
-
Added some new awesome mining-related sprites by Petethegoat. All credit for the sprites goes to him.
-
-
-
-9 August 2011.
-
-
Mport updated:
-
-
Cyborgs once again have open cover/cell icons.
-
To override a cyborg's laws you must emag it when the cover is open.
-
Emags can unlock a cyborgs cover.
-
Xbow radiation damage has been lowered from 100 to 20 a hit
-
-
-
-5 August 2011.
-
-
Mport updated:
-
-
The various assemblies should be working now.
-
Old style bombs and suicide vests temporarily removed.
-
-
-
-3 August 2011.
-
-
Superxpdude updated:
-
-
Virology Airlock changed to no longer cycle air. Should work faster and prevent virologists from suffocating.
-
Server Room APC is now connected to the power grid.
-
Stun Batons now start OFF. Make sure to turn them on before hitting people with them.
-
-
-
-2 August 2011. The day the earth stood still.
-
-
Agouri updated:
-
-
SSUs now correctly cycle and dump the unlucky occupant when designated to supercycle, when the criteria to do so are met.
-
Fixed bugshit
-
You can now make normal martinis again, removed a silly recipe conflict (Thanks, muskets.). Good barmen are urged to blend the good ol' recipes since the new ones have sprites that SUCK ASS JESUS CHRIST
-
-
-
-
-
Doohl updated:
-
-
Speech bubbles: you can toggle them on in the character setup window. Basically, whenever someone around you talks, you see a speech bubble appear above them.
-
You can no longer create wizarditis and xenomicrobes with metroid cores.
-
Tweak: Using an exclamation mark as an AI, pAI, or cyborg will not longer get rid of the last exclamation mark.
-
-
-
-30 July 2011.
-
-
Superxpdude Updated:
-
-
Engineer and CE space helmets now have built-in lights.
-
-
Rockdtben updated:
-
-
Bugfix: Fixed a bug where you could dupe diamonds
-
-
Doohl updated:
-
-
New virus: Retrovirus. It basically screws over your DNA.
-
You can now do CTRL+MOVEMENT to face any direction you want. See those chairs in Medbay and the Escape Wing? You can do CTRL+EAST to actually RP that you're sitting on them. Is this cool or what?!
-
-
-
-29 July 2011. - Day of Forum revival!
-
-
Doohl updated:
-
-
Bugfix: Metroids should never "shut down" and just die in a corner when they begin starving. And so, hungry Metroids are a force to be feared.
-
The Cargo computers now have the ability to cancel pending orders to refund credits. This was put in place so that idiots couldn't waste all the cargo points and run off. However, if the shuttle is en route to the station you won't be able to cancel orders.
-
Bugfix: the manifest has been fixed! Additionally, the manfiest is now updated realtime; job changes and new arrivals will be automatically updated into the manifest. Joy!
-
Metroids, when wrestled off of someone's head or beaten off, now get stunned for a few seconds.
-
-
Agouri updated:
-
-
I was always bothered by how unprofessional it was of Nanotransen (in before >Nanotransen >professionalism) to just lay expensive spacesuits in racks and just let them be. Well, no more. Introducing...
-
Suit Storage Units. Rumored to actually be repurposed space radiators, these wondrous machines will store any kind of spacesuit in a clean and sterile environment.
-
The user can interact with the unit in various ways. You can start a UV cauterisation cycle to disinfect its contents, effectively sterilising and cleaning eveyrthing from the suits/helmets stored inside.
-
A sneaky yordle can also hide in it, if he so desires, or hack it, or lock it or do a plethora of shady stuff with it. Beware, though, there's plenty of dangerous things you can do with it, both to you and your target.
-
The Unit's control panel can be accessed by screwdriving it. That's all I'm willing to say, I'd like to let the players find out what each hack option does and doesn't. Will add more stuff later.
-
Added Command Space suit, Chief Engineer space suit and Chief Medical Officer spacesuit (In a new space that you'll probably notice by yourself) to make it easier for you to look like a special snowflake.
-
EVA and CMO office modified to accomodate the new suits and SSUs. Look, I'm not a competent mapper, okay? Fuck you too. A mapper is strongly recommended to rearrange my half assed shit.
-
Soda cans, cigarette packets, cigarettes and cigars as well as bullet casings are now considered trash and can be picked up by the trashbag. Now you can annoy the janitor even more!
-
Sprite credit goes to Alex Jones, his portfolio can be found here: http://bspbox.com. Thanks a lot, bro.
-
With the recent forum fuss and all that, I've got a thread to specifically contain rants and bug reports about this update. Click me
-
-
Uhangi updated:
-
-
EVA redesigned
-
An electropack is now available once again on the prison station
-
-
Errorage updated:
-
-
Hopefully fixed the derelict 'hotspots'. Derelict medbay has also been fixed.
-
-
-
-4 July - 28 July 2011.
-
-
-
Trubble Bass updated (committed by Superxpdude):
-
-
Hat crates. Hat Station 13.
-
-
Matty:
-
-
Engineers and miners now start off with the new industrial backpack.
-
-
Agouri:
-
-
Sleepers are now OP and heal every kind of damage.
-
Made cloning 30% faster, due to popular demand.
-
-
Superxpdude updated:
-
-
Added in the Submachine Gun to R&D.
-
Syndicate agents now have Mini-Uzis.
-
Added an exosuit recharged to the mining station.
-
New labcoats for scientists, virologists, chemists, and genetecists.
-
Moved the vault and added a bridge meeting room next to the HoP's office.
-
Deathsquad armor now functions like a space suit.
-
Added in security jackboots.
-
-
Uhangi updated:
-
-
Traitors can now purchase syndicate balloons, which serve no purpose other than to blow your cover. For a limited time only, you can get them at a bargain price - just 10 telecrystals!
-
Removed security shotguns from the armory. No fun allowed.
-
Changed some bullet damage stuff.
-
-
Microwave updated:
-
-
Monkey boxes:
-
Contains a score of monkey cubes, which you apply water to create monkies. Nanotrasen provides only the finest technology!
-
You can order monkey crates from the cargo bay. They contain monkey boxes.
-
Changed the amount of labels labelers have to 30. 10 was too low and 30 should not be too griefy.
-
Maximum label text length increased from 10 to 64.
-
You can no longer label people because they can just peel the labels off. Sorry, clowns!
-
Jelly dooonnuuuutsss! Happy birthday, officers!
-
Made xenomeat not give any nutrition.
-
Added some new reagents.
-
Made it possible to feed monkies and xenos things, as well as making it possible for them to eat themselves (please don't read that too literally).
-
Added in synthiflesh.
-
Plasma is now not used in reactions, instead, is treated as a catalyst that is not used up. This only applies to certain reactions.
-
Made it possible to grind more things in the chemistry grinder.
-
-
Errorage updated:
-
-
Increased environmental damage by a factor of 1.5.
-
Made firesuits a lot more resistant to heat. Previously, they stopped protecting at around 4,500 degrees. They now go up to around 10,000 (which is also the temperature which the floor starts melting)
-
Edited the quartermaster's office a bit.
-
Cargo technicians now have access to a cargo ordering console.
-
Added different-colored hardhats. The CE gets a white hardhat.
-
-
-
Rastaf.Zero updated:
-
-
Botanists get a new toy: Biogenerator. Insert biological items, recieve biological items.
-
Added roller beds, otherwise known as stretchers, to medbay. You can buckle people onto them and pull them.
-
Added egg-smashing and tomato-smashing decals.
-
-
Muskets updated:
-
-
The prepackaged songs (Space Asshole, Cuban Pete, Darkest Honk, etc) have been removed. This doesn't mean admins can't play midis, this just gets rid of a lot of unnecessary download time.
-
-
Firecage updated:
-
-
A whole bunch of new drinks and food. Seriously, there's alot!
-
New weapons such as the shock revolver and large energy crossbow.
-
Hydroponics can now grow a bunch more stuff. A lot of these new crops have some very interesting mutations.
-
Added a command for AIs to change their icon.
-
New costumes to the costume room.
-
-
Urist McDorf updated:
-
-
Adding shading to pills.
-
Detective's office noir look has been removed. The icon operations required to render everything in monochrome was too heavy on the players.
-
Added in an uplink implant.
-
-
Doohl updated:
-
-
A new alien race: Metroids! They are a mostly non-sentient race of jellyfish-like organisms that float in the air and feed on the life energy of other organisms. While most Metroids have never shown signs of self-awareness, they do exhibit signs of basic logic and reasoning skills as well as very sophisticated perception. Nanotrasen has shipped two baby Metroids for the xenobiology department. They should be handled with the utmost care - they are some of the deadliest beings in the known universe!
-
R&D gets a new toy: the freeze gun. Despite popular belief, this will not literally freeze things!
-
Every single chemical reagent has been assigned a color.
-
You can now see beakers fill up with chemicals. You can also observe how the colors mix inside the beakers. Spray bottles also will also show the color of whatever you're spraying.
-
Added a timestamp to combat logs.
-
Non-insulated gloves now need to be wrapped in wire in order to be electrified.
-
You can now shoot at people on the ground by simply clicking on the tile they're on.
-
Changeling husks are now uncloneable. To clarify: when a changeling sucks out a victim's DNA, the victim is said to become a "husk".
-
-
-
-4 July 2011.
-
-
Agouri updated:
-
- Medical stuff overhaul in preparation of Erro's big Medic update:
-
Sleepers are now able to heal every kind of damage. Drag your ass to medbay and ask a doctor to get you in one.
-
A lil' bit faster cloning (about 30% faster) due to popular demand.
-
Sleepers are now all located in the inner part of medbay, except for the examination room one.
-
Added Dermaline, burn-healing drug that outpowers kelotane (and kelotane is getting a major nerf, so be sure to know how to get this). Recipe is on the wiki if you need to make it.
-
Drugs no longer heal or metabolise when injected in dead bodies, fuck. Thank god you guys missed this major bug or we'd have cloning-by-injecting-healing-drugs.
-
Reminder to coders: Goddamn, update the changelog.
-
-
-
-
Matty updated:
-
-
New engineering backpacks. Enjoy!
-
-
-
-
-18 June 2011.
-
-
Agouri updated:
-
-
Bugfixes: The reagent grinding now works, allowing the miners to FINALLY bring plasma to the chemistry.
-
Bugfixes: Pill bottles and clipboards can now be removed from the pockets once placed there.
-
-
-
-
-7 June 2011.
-
-
TLE updated:
-
-
Wiped/suicided pAIs should be eligible for being candidates again (5 minute cooldown between prompts.)
-
pAIs are now affected by EMP bursts. pAIs hit with a burst will be silenced (no speech or PDA messaging) for two minutes and may have their directives or master modified. A sufficiently powerful EMP burst will have a 20% chance of killing a pAI.
-
-
-
Neo updated:
-
-
Nar-sie is now a more vengeful eldritch being. When summoned into our world, he first rewards his loyal cultists by chasing down and eating them first, then turns his attention to any remaining humans.
-
-
-
Darem updated:
-
-
Gun Code Overhaul Phase 1.
-
Taser guns shoot ONE WHOLE SHOT more then they do now.
-
Energy Crossbow has a slightly higher shot capacity (still automatically recharges).
-
Revolvers can either be loaded one shell at a time or all at once with an ammo box.
-
Shotguns no longer need to be pumped before firing (will change back in phase 2).
-
-
-
K0000 updated:
-
-
Arcane tome now has a "notes" option. Set English translations for runewords which come up when scribing runes. Attack an arcane tome with another arcane tome to copy your notes to the target tome.
-
Stun rune buffed considerably. Its a 1-use item so it deserved longer stun time.
-
Tome text: Added missing word description for stun rune.
-
-
-
-
-
-1 June 2011, Canadian Day Against Homophobia
-
-
Noise updated:
-
-
Changed holopad speaking to :h on request.
-
Ninja fixes, changes, etc. Refer to the code changelog for more info.
-
-
-
Neo updated:
-
-
Department radio chat now shows with the department name instead of the frequency.
-
-
-
Veyveyr updated:
-
-
Spent casing sprites + SMG sprite.
-
Sprites for 1x4 and 1x2 pod doors.
-
-
-
Errorage updated:
-
-
Fixed twohanded weapon throwing, which left the 'off-hand' object in your other hand.
-
Doors now hide items under them when closed, mobs are still always above.
-
Singularity engine emitter is now much quieter.
-
Mining station redesigned.
-
Atmospherics' misc gasses tank now starts with N2O.
-
Hitting the resist button while handcuffed and buckled to something will make you attempt to free yourself. The process is the same as trying to remove handcuffs. When the 2 minutes pass you will be unbuckled but still handcuffed.
-
-
-
ConstantA updated:
-
-
Added exosuit energy relay equipment. Uses area power (any power channel
-available) instead of powercell for movement and actions, recharges powercell.
-
Exosuits can be renamed. Command is in Permissions & Logging menu.
-
Lowered construction time for Ripley parts.
-
Exosuit wreckage can be salvaged for exosuit parts (torso, limbs etc).
-
Speed-up for mecha.
-
New malf-AI sprite. (Sprite donated by the D2K5 server)
-
-
-
Cheridan updated:
-
-
Updated mine floor and wall edge sprites.
-
-
-
Urist McDorf updated:
-
-
AIs no longer bleed when they reach 0HP (Critical health).
-
Added 2 more security HUDs to security.
-
Security HUDs now show if a person has a tracking implant.
-
-
-
Microwave updated:
-
-
Barman renamed to Bartender.
-
The amount of drink you get when mixing things in the bar has been rebalanced.
-
Fixed arrivals maintenance shaft not having air at round start.
-
-
-
Deuryn updated:
-
-
Meteors now do a bit more damage and they're not stopped by grills.
-
-
-
TLE updated:
-
-
Added personal AIs (pAI).
-
-
-
-
-
-19 May 2011
-
-
Errorage updated:
-
-
Asteroid floors can be built on by adding tiles
-
Mining satchels now fit in rig suit storage, on belts and in pockets.
-
Cables now come in four colors: Red, yellow, green and blue.
-
-
-
-
NEO updated:
-
-
Armour overhaul, phase 3. See rev notes for details.
-
AI cores should now block movement.
-
MMIs are now properly buildable with the mecha fabricator.
-
-
-
-
Urist updated:
-
-
Added sandstone and mineral doors. Mineral boors cannot be opened by the AI or NPCs.
-
Removed Imperium robes from map.
-
Added the ability to draw letters and graffiti with crayons.
-
Removed fire axes except for bridge and atmospherics.
-
-
-
-
Veyveyr updated:
-
-
New serviceborg sprite option.
-
Map changes to robotics; removed borg fabricators and added second exosuit fabricator.
-
Cyborg parts are now built from exosuit fabricators and benefit from research.
-
New exosuit fabricator and borg frame sprites.
-
-
-
-
-14 May 2011, late friday 13 update.
-
-
K0000 updated:
-
-
Cult updates:
-
New rune! Stun rune. When used as rune, briefly stuns everyone around (including cultists). When imbued into a talisman, hit someone to stun and briefly mute them. Spawnable with the starter talisman.
-
Imbue rune doesnt disappear after succesful invocation, only the source rune.
-
Chaplain's bible now has 20% chance to convert a cultist (was 10%), and gives a message on success.
-
Also, wrapping paper is back! Find it in the mailroom.
-
-
-
-
NEO updated:
-
-
Beginning of armor overhaul. Armor now has slightly better defence against melee, and weaker against shots. More coming soon...someday
-
Cyborgs finally drop their MMI when gibbed like they were supposed to back when I added MMIs. Round- start cyborgs use whatever name you have selected for your character for the brain that gets spawned for them.
-
-
-
-
Darem updated:
-
-
Chemistry update
-
In containers where there isn't a perfect ratio of reagents, reactions won't consume ALL of the related reagents (so if you mix 10 anti-toxin with 20 inaprovaline, you get 10 tricordrazine and 10 inaprovaline rather then just 10 tricodrazine)
-
Catalysts: some reactions might need presence of an element, while not directly consuming it.
-
Reactions changed to use catalysts: all recipes that require Universal Enzyme now require 5 units of the enzyme but the enzyme isn't consumed (So Tofu, Cheese, Moonshine, Wine, Vodka, and Kahlua recipes).
-
-
-
Errorage updated:
-
-
Smooth tables: Tables now automatically determine which direction and sprite they'll use. They will connect to any adjacent table unless there is a window between them (regular, reinforced, tinted, whichever)
-
-
-
-
-7 May 2011, Mother's day?
-
-
Agouri updated:
-
-
Fireaxes now work. Derp.
-
-
-
-
Erro updated:
-
-
New sprites for thermited walls and girders. Rework of thermited walls. Thermited walls leave a remnant damaged wall, crowbar it to scrap it.
-
More colors for the lightfloors CANCELLED/POSTPONED
DANGERCON UPDATE:Agouri and Erro updated(I'm in the DangerCon team now, nyoro~n :3):
-
-
Backpacks removed from all players. It was unrealistic. You can now had to the living quarters to get one from the personal closets there.
-
Any firearms now send you to critical in 1-2 shots. Doctors need to get the wounded man to surgery to provide good treatment. Guide for bullet removal is up on the wiki.
-
Brute packs and kelotane removed altogether to encourage use of surgery for heavy injury.
-
Just kidding
-
Fireaxe cabinets and Extinguisher wall-mounted closets now added around the station, thank Nanotransen for that.
-
Because of Nanotransen being Nanotransen, the fire cabinets are electrically operated. AIs can lock them and you can hack them if you want the precious axe inside and it's locked. The axe itself uses an experimental two-handed system, so while it's FUCKING ROBUST you need to wield it to fully unlock its capabilities. Pick up axe and click it in your hand to wield it, click it again or drop to unwield and carry it.You can also use it as a crowbar for cranking doors and firedoors open when wielded, utilising the lever on the back of the blade. And I didn't lie to you. It's fucking robust.
-
Fireaxe, when wielded, fully takes up your other hand as well. You can't switch hands and the fireaxe itself is unwieldy and won't fit anywhere.
-
A fireaxe cabinet can also be smashed if you've got a strong enough object in your hand.
-
EXTINGUISHER CLOSETS, made by dear Erro, can be found in abundance around the station. Click once to open them, again to retrieve the extinguisher, attack with extinguisher to place it back. Limited uses, but we've got plans for our little friend.
-
Sprite kudos go to: Cheridan for most of them, Khodoque for the fantastic fireaxe head. I merged those two. Also thanks to matty and Arcalane for giving it a shot.
-
Has the piano got TOO annoying? Try the fire axe...
-
Oh, and tou can now construct Light floors! To do it: Use wires on glass, then metal on the produced assembly, then place it on an uncovered floor like you would when replacing broken floor tiles. To deconstruct: Crowbar on light floor, use crowbar on produced assembly to remove metal, wirecutters to seperate the wires from the glass. Sprites by delicious Hempuli.
-
Got something to bitch about? Got a bug to report? Want to create drama? Did the clown destroy your piano while you were playing an amazing space remix of the moonlight sonata? Give it here
-
-
-
-
-
Rastaf.Zero updated:
-
-
New uniforms added for captain and chaplain, in their respective lockers. Credits to Farart.
-
-
-
-
Urist McDorf updated:
-
-
Mime and Clown now spawn with Crayons. You can eat those crayons. And use them for other nefarious purposes.
-
Health Scanners (A new type of Goggles) now spawn in medbay. Use them, doctors!
-
New Arcade toy.
-
Glowshrooms! What other lifeform will threaten the welfare of the station now?!
-
Bananas growable in hydroponics. Also soap is now on-board.
-
Added new "Lights out!" random event.
-
-
-
ConstantA updated:
-
-
Mech pilots are now immune to zapping, thank you very much.
-
-
-
-
-
-17 April 2011, World Hemophilia Day
-
-
Microwave updated:
-
-
Rabbit ears have a small tail, night vision goggle sprites updated.
-
Space tea has a nice, calming effect.
-
Space drugs? Liberty cap... something like that. Microwave, make your changelog entries more understandable!
-
Brobot merged with Service Borg with a Rapid Service Fabricator.
-
Arcade machine prizes look and sound realistic once again.
-
New arcade toy: Syndicate space suit costume, can hold arcade toys in suit storage.
-
Empty cap gun loaders can be recycled in an autolathe.
-
Seizure man has laying down sprites now. Update to wizard den.
-
Mech bay has two more borg chargers.
-
Beepsky is back!
-
Detective's office grille has been electrified.
-
You can now see if someone is wearing an emergency oxygen tank on their belt on the mob itself.
-
Lexorin - Now deals 3 oxygen damage per tick. Countered with Dexalin or Dexalin Pkus, which remove 2 units of it from your body per tick.
-
Bilk - Shares the effects of beer and milk. Disgusting!
-
Sugar - Gives nutrition!
-
Arithrazine - Now extremely good against radiation damage.
-
Hyronalin - Stronger radiation removal
-
Space cleaner spray bottles now contain enough cleaner for 50 uses. Making space cleaner now yields more cleaner.
-
-
-
Errorage updated:
-
-
Shuttle diagonal sprites now work for any kind of floor.
-
You can now make plaques from gold. Place them on a wall and engrave an epitaph.
-
Placed a single wall tile in the AI satellite so you don't have a clear LOS of the AI from the door.
-
Added arrivals lobby. (Map by Superxpdude, updated by Microwave)
-
Lattice now connects to the solar shields.
-
Law office maintenance is now connected with Tech storage maintenance. (Some rewiring dont in the area)
-
Xenobiology now has it's own access level. (Also fixed xeno pen access and blast doors)
-
You might soon start to see different airlocks and airlock assemblies around the station. (Sprites donated by Baystation 12)
-
Chemical storage added, discussion on which chemicals it should store is on the forums. You're welcome to contribute.
-
Hot fires will now melt floors.
-
Added a pair of market stalls south of the teleporter. LET THERE BE CLOWNMART!
-
Screwdrivers and wirecutters can now spawn in different colors.
-
Electrical toolboxes have a 5% chance of spawning a pair of insulated gloves. A set spawns in tech storage.
-
Oxygen canisters now spawn in emergency storage, near disposal, in the incinerator and the CE's office.
-
A plasma canister now spawns in toxins, near the maintenance door.
-
Wooden tables now look nicer.
-
-
-
Agouri updated:
-
-
2001 space suits added to AI Satellite!
-
New look for the 2001 space suit.
-
2001 space suit jetpack added.
-
Improved TRAYS!
-
-
-
Noise updated:
-
-
Thermals and mesons no longer give slightly better night vision.
-
NINJAS! (Too many things to list)
-
Wizards are no longer trackable by the AI when in their den.
-
Removed all old notes, except for the last one.
-
Nuke team now cannot return with their shuttle until the bomb is armed and counting down.
-
Energy blades can no longer cut through r-walls, walls take 7 seconds to cut through.
-
Turrets are now destructible. Bash them with stuff when they pop out or (more likely) die trying.
-
Updated Ripley Mech sprite.
-
-
-
Neo updated:
-
-
You can now aim guns at body parts, armor and helmets properly protect you from projectiles.
-
Cat ears now match the hair color of the wearer.
-
Robots can no longer stick their items onto/into things.
-
Meson, thermal and x-ray vision are now modules for borgs.
-
Welding now uses less fuel when on and idle but more when welding.
-
Hopefully fixed the bug when running into airlocks didn't open them and running into objects didn't push them.
-
-
-
HAL updated:
-
-
Added air alarm to security checkpoint, added cameras to aux. arrival docks so the AI can see everything.
-
Added fire alarm, fire locks and air alarm to delivery office.
-
-
-
ConstantA updated:
-
-
Added mecha DNA-locking. Only the person with matching UE can operate such mechs.
-
Added two mecha armor booster modules and a repair droid module.
-
Mech fabricator is now buildable.
-
Gygax construction is now reversible.
-
-
-
Rastaf0 and Farart updated:
-
-
Ghosts should now always properly hear people.
-
Monkeyized people (genetics or jungle fever disease) no longer lose their genetic mutations and diseases.
-
People who get bitten by monkeys get jungle fever.
-
Most chemicals should now heal and harm humans properly.
-
Many new (and updated) recipes for the microwave including Pizza, Meatball Soup, Hot Chili and many more.
-
Items should no longer spawn under vendomats and microwaves.
-
Runes are now drawn under doors and tables.
-
Penlights fit in medical belts.
-
People will scream if they get cremated while still alive.
-
Diseases should now properly make you loose health.
-
Monkeys wearing masks now get acid protection too.
-
You should probably turn off your stun baton before washing it.
-
latex loves + short piece of wire + some air from tank = balloon!
-
Kitchen was expanded, also a new look for the kitchen sink.
-
New dishware vending machine - dispenses knives, forks, trays and drinking glasses.
-
Water cooler was added to kitchen.
-
New uniform - Waiter Outfit. Chef can give it to his assistant.
-
-
-
Deeaych updated:
-
-
Updated satchel, bananimum, shovel, jackhammer and pick-in-hand sprites.
-
Many unneeded r-walls removed, detective's office reinforced.
-
Captain armor now acts as a space suit, added a unique captain's space helmet to captain's quarters.
-
Golems cannot speak, but should be perfectly spawnable. Also added golem fat sprite.
-
Security borgs have side sprites.
-
-
-
Matty406 updated:
-
-
AIs can now feel a little more dorfy.
-
Many ores, both raw and smelted, look much better.
-
-
-
Urist_McDorf updated:
-
-
You can now light other people's cigarettes by targeting their mouth with a lighter.
-
-
-
Veyveyr updated:
-
-
New tool sprites.
-
New sprites for smooth-lattice.
-
-
-
Muskets updated:
-
-
Kabobs now return the bar used to make them.
-
-
-
-
-
-2 April 2011, International Children's Book Day
-
-
Microwave updated:
-
-
New look for the mining cyborg, jackhammer, kitchen sink.
-
Singularity is now enclosed again (still airless tho).
-
Wizard has a new starting area.
-
Chemists and CMOs now have their own jumpsuits.
-
-
-
ConstantA updated:
-
-
You can now put Mind-machine-interface (MMI)'d brains into mecha.
-
-
-
Errorage updated:
-
-
Added smooth lattice.
-
-
-
-
-
-26 March 2011
-
-
Rastaf0 updated:
-
-
Food sprites from Farart
-
New food: popcorn (corn in microwave), tofuburger (tofu+flour in microwave), carpburger (carp meat+floor in microwave)
-
Medical belts are finally in medbay (credits belong to errorage, I only added it)
-
Pill bottles now can fit in containers (boxes, medbelts, etc) and in pockets.
-
Cutting camera now leaves fingerprints.
-
-
-
Microwave updated:
-
-
Armor Can hold revolvers, and so can the detective's coat.
-
Chef's apron is going live, it can carry a knife, and has a slight heat
-resistance (only slight don't run into a fire).
-
Kitty Ears!
-
Various food nutriment changes.
-
Added RIGs to the Mine EVA.
-
Night vision goggles. They have a range of five tiles.
-
Added Foods: Very Berry Pie, Tofu Pie, Tofu Kebab.
-
Modified foods: Custard Pie is now banana cream pie.
-
-
-
ConstantA updated:
-
-
Removed redundand steps from Gygax and HONK construction.
It is now possible to actually eat omelettes with the fork now, instead of just stabbing yourself (or others) in the eye with it.
-
Welding masks can now be flipped up or down. Note that when they're up they don't hide your identity or protect you from welding.
-
Reagent based healing should now work properly.
-
Revolver has been balanced and made cheaper.
-
Tasers now effect borgs.
-
Plastic explosives are now bought in single bricks.
-
Nuke team slightly buffed and their uplink updated with recently added items.
-
Player verbs have been reorganized into tabs.
-
Energy swords now come in blue, green, purple and red.
-
Cameras are now constructable and dismantlable. (Code donated by Powerful Station 13)
-
Updated the change network verb for AIs. (Code donated by Powerful Station 13)
-
Added gold, silver and diamond pickaxes to R&D which mine faster.
-
-
-
Agouri updated:
-
-
New look for the Request consoles.
-
-
-
Rastaf0 updated:
-
-
Brig cell timers should now tick closer-to-real seconds.
-
New look for food, including meat pie, carrot cake, loaded baked potato, omelette, pie, xenopie and others. (some sprites by Farart)
-
Hearing in lockers now works as intended.
-
Fixed electronic blink sprite.
-
Added the 'ghost ears' verb, which allows ghosts to not hear anything but deadcast.
-
-
-
XSI updated:
-
-
New AI core design.
-
HoP now has a coffee machine!
-
-
-
Veyveyr updated:
-
-
Replaced nuke storage with a vault.
-
Redesigned the mint, moved the public autolathe and n2o storage.
-
New look for the coin press. (Sprite by Cheridan)
-
-
-
Errorage updated:
-
-
You can now manually add coins into money bags, also fixed money bag interaction window formatting.
-
QM no longer has access to the entire mining station to stop him from stealing supplies.
-
New machine loading sprite for mining machinery. (sprites by Cheridan)
-
Added a messanging server to the server room. It'll be used for messanging, but ignore it for now.
-
The delivery office now requires delivery office access. It's also no longer called "Construction Zone"
-
Almost all the mecha parts now have sprites. (Sprites by Cheridan)
-
Tinted and frosted glass now look darker.
-
There are now more money sprites.
-
Department closets now contain the correct headsets.
-
-
-
Microwave updated:
-
-
Bicaridine now heals a lot better than before.
-
Added Diethylamine, Dry Ramen, Hot Ramen, Hell Ramen, Ice, Iced Coffee, Iced Tea, Hot Chocolate. Each with it's own effects.
-
Re-added pest spray to hydroponics.
-
Carrots now contain a little imidazoline.
-
HoS, Warden and Security Officer starting equipment changed.
-
New crate, which contains armored vests and helmets. Requires security access, costs 20.
-
Miner lockers now contain meson scanners and mining jumpsuits.
-
Food crate now contains milk, instead of meatballs. Lightbulb crates cost reduced to 5. Riot crates cost reduced to 20. Emergency crate contains 2 med bots instead of floor bots. Hydroponics crate no longer contains weed spray, pest spray. It's latex gloves were replaced with leather ones and an apron.
-
Added chef's apron (can hold a kitchen knife) and a new service borg sprite.
-
Autolathe can now construct kitchen knives.
-
Biosuit and syndicate space suits can now fit into backpacks.
-
Mime's mask can now be used as a gas mask.
-
Added welding helmet 'off' sprites.
-
-
-
-
-
-
-18 March 2011
-
-
Errorage updated:
-
-
You can now use the me command for emotes! It works the same as say "*custom" set to visible.
-
There is now a wave emote.
-
Enjoy your tea!
-
-
-
Deeaych updated:
-
-
The exam room has some extra prominence and features.
-
A new costume for the clown or mime to enjoy.
-
Service Cyborgs can be picked! Shaker, dropper, tray, pen, paper, and DOSH to show their class off. When emagged, the friendly butler-borg is able to serve up a deadly last meal.
-
It should now be possible to spawn as a cyborg at round start. Spawned cyborgs have a lower battery life than created cyborgs and begin the round in the AI Foyer.
-
-
-
Rastaf0 updated:
-
-
Fixed an issue with examining several objects in your hands (such as beakers).
-
Fixed bug with random last name being empty in rare cases.
-
-
-
hunterluthi updated:
-
-
It is now possible to make 3x3 sets of tables.
-
Fixed some missplaced grilles/lattices on the port solar.
-
There is now a breakroom for the station and atmos engineers. It has everything an intelligent young engineer needs. Namely, Cheesy Honkers and arcade games.
-
-
-
-
-15 March 2011, International Day Against Police Brutality
-
-
Errorage updated:
-
-
Autolathe deconstruction fixed.
-
Atmos Entrance fixed.
-
AI no longer gibs themselves if they click on the singularity.
-
Fixed all the issues I knew of about storage items.
-
Redesigned Assembly line and surrounding maintenance shafts.
-
Redesigned Tech storage. (Map by Veyveyr)
-
-
-
TLE updated:
-
-
Forum account activation added. Use the 'Activate Forum Account' verb.
-
-
-
Neo updated:
-
-
New R&D Item: The 'Bag of holding'. (Sprite by Cheridan)
-
Getting someone out of the cloner now leaves damage, which can only be fixed in the cryo tube.
-
New reagent: Clonexadone, for use with the cryo tube.
-
Fixed using syringes on plants.
-
-
-
Constanta updated:
-
-
Added queueing to fabricator.
-
-
-
Rastaf0 updated:
-
-
Air alarms upgraded.
-
Fixed problem with AI clicking on mulebot.
-
Airlock controller (as in EVA) now react to commands faster.
-
Fixed toxins mixing airlocks.
-
-
-
-
-6 March 2011
-
-
Neo updated:
-
-
Neo deserves a medal for all the bugfixing he's done! --errorage
-
-
-
Errorage updated:
-
-
No. I did not code on my birthday!
-
Windows can now be rotated clockwise and counter clockwise.
-
Window creating process slightly changed to make it easier.
-
Fixed the newly made reinforced windows bug where they weren't properly unfastened and unscrewed.
-
Examination room has a few windows now.
-
Can you tell I reinstalled Windows?
-
Robotics has health analyzers.
-
Bugfixing.
-
-
-
Deeyach updated:
-
-
Roboticists now spawn with a lab coat and an engineering pda
-
-
-
-
-2 March 2011, Wednesday
-
-
Errorage updated:
-
-
Mapping updates including Atmospherics department map fixes, CE's office and some lights being added here and there.
-
Mining once again given to the quartermaster and HoP. The CE has no business with mining.
-
Removed the overstuffed Atmos/Engineering supply room.
-
Replaced all 'engineering' doors in mining with maintenance doors as they were causing confusion as to which department mining belongs to.
-
The incinerator is now maintenance access only.
-
-
-
Neo updated:
-
-
New look for the advanced energy gun. (Sprite by Cheridan)
-
Mech fabricator accepts non-standard materials.
-
Mules accesses fixed, so they can be unlocked once again.
-
Atmospherics department mapping overhaul. (Map by Hawk_v3)
-
Added more name options to arcade machines.
-
-
-
ConstantA updated:
-
-
Added mecha control console and mecha tracking beacons.
-
Some changes to gygax construction.
-
-
-
Darem updated:
-
-
R&D minor bugfixes.
-
AI computer can now be deconstructed (right click and select 'accessinternals').
-
Server room updated, added server equipment to use with R&D.
-
Wizard and ghost teleport lists are now in alphabetical order, ghosts can now teleport to the mining station.
-
Rightclicking and examining a constructable frame now tells you what parts still need to be finished.
-
Large grenades added to R&D.
-
-
-
Deeyach updated:
-
-
Mining given to the CE. (Reverted by Errorage)
-
Clowns can now pick a new name upon entering the game (like wizards previously).
-
-
-
-
-24 February 2011, Thursday
-
-
Darem updated:
-
-
Lighting code fixed for mining and thermite.
-
R&D instruction manual added to the R&D lab.
-
Fixed R&D disk commands not working.
-
Added portable power generators which run on solid plasma.
-
You can now set the numer of coins to produce in the mint.
-
Added two more portable power generators to R&D.
-
-
-
Deeyach updated:
-
-
New uniform for roboticists
-
-
-
Neo updated:
-
-
Game speed increased
-
Mining stacking machine no longer devours stacks larger than 1 sheet (it was only increasing its stock by 1 when given a stacked stack)
-
Stackable uranium ore added (a better sprite is needed, contributions are welcome)
-
Made Meteor gamemode actually do stuff
-
Made a bigger class of meteor
-
New R&D item: Advanced Energy Gun
-
Law priority clarified with regards to ion laws and law 0.
-
-
-
-
Veyveyr updated:
-
-
Minor mapping fixes
-
-
-
Uhangi updated:
-
-
New red bomb suit for security.
-
-
-
-
Errorage updated:
-
-
Slight mapping change to arrival hallway.
-
-
-
-
-23 February 2011, Red Army Day
-
-
Uhangi updated:
-
-
Antitox and Inaprovaline now mixable via chemistry.
-
Explosive Ordinance Disosal (EOD) suits added to armory and security.
-
Large beaker now holds 100 units of chemicals. (code by Slith)
-
-
-
Rastaf0 updated:
-
-
Secbot interface updated.
-
Syringe auto-toggels mode when full.
-
Captain's flask volume increased.
-
-
-
Neo updated:
-
-
Fixed the 'be syndicate' choice to actually work on nuke rounds.
-
Syndicates no longer win if they detonate the nuke on their ship.
-
-
-
-
Errorage updated:
-
-
Added cloning manual. (Written by Perapsam)
-
-
-
K0000 updated:
-
-
Cult mode updates.
-
You can now read the arcane tome. It contains a simple guide for making runes.
-
Converting people doesnt give them word knowledge.
-
Sacrifice monkeys or humans to gain new words.
-
Total number of rune words set to 10
-
Some minor bugfixes.
-
-
-
-
-20 February 2011, Sunday
-
-
Errorage updated:
-
-
Slight updates to processing unit and stacking machine at the mining outpost.
-
Digging now yields sand, which can be smelted into glass.
-
Stacking machine can now stack reinforced metal, regular and reinforced glass too.
-
Engineers now have two copies of the singularity safety manual.
-
-
-
Neo updated:
-
-
Magboots now have a verb toggle like jumpsuit sensors.
-
Jumpsuit sensors are now in all jumpsuits, except tactical turtlenecks.
-
Tweaks to the AI report at round start.
-
Syndi-cakes now heal traitors/rev heads/etc much more than anyone else.
-
Containment fields zap once again.
-
Fire damage meter no longer lies about fire damage.
-
-
-
Darem updated:
-
-
Mass Spectrometer added to R&D. Load it with a syringe of blood and it will tell you the chemicals in it. Low reliability devices may yield false information.
-
Not all devices have a 100% reliability now.
-
Miners now have access to mint foyer and loading area. Only captain has access to the vault.
-
More stuff can be analyzed in the destructive analyzer, protolathe can produce intelicards.
-
-
-
Rastaf0 updated:
-
-
Added blast door button to atmospherics.
-
Toxins timer-igniter assemblies fixed.
-
Engineering secure storage expanded.
-
Added singularity telescreen.
-
-
-
-
-18 February 2011, Friday
-
-
Errorage updated:
-
-
New look for the bio suits. (Biosuit and hood sprites by Cheridan)
-
New radiation suits added along with radiation hoods and masks. Must wear complete set to get full protection.
-
-
-
Rastaf0 updated:
-
-
Binary translator cost reduced to 1 telecrystal.
-
-
AtomicTroop updated:
-
-
Mail Sorter job added.
-
Disposal system redone to allow for package transfers. Packages are routed to mail sorter room and then routed to the rest of the station
-
Disposal area moved. Old disposal area now just an incinerator and a small disposal into space.
-
New wrapping paper for sending packages.
-
-
-
Veyveyr updates:
-
-
New machine frame sprite.
-
Braincase sprites for mechs added. Not actually used, yet.
-
-
-
Darem updates:
-
-
Research and Development system is LIVE. Scientists can now research new advancements in technology. Not much can be made, right now, but the system is there. Technologies are researched by shoving items into the destructive analyzer. Circuit Imprinter, Destructive Analyzer, and Protolathe are controlled from the R&D console.
-
Autolathe, Protolathe, Destructive Analyzer, and Circuit Imprinter can now be built, taken apart, and upgraded. The basic frame for all of the above requires 5 metal.
-
-
-
-
-15 February 2011, Tuesday
-
-
Rastaf0 updated:
-
-
Added radio channels and headsets for miners (:h or :d ("diggers" lol)) and for cargo techs (:h or :q )
-
Added a personal headsets to HoP and QM.
-
Aliens now attack bots instead of opening control window.
-
All bots can be damaged and repaired.
-
All bots are effected to EMP now.
-
Atmos now starts with nitrous oxide in storage tank.
-
-
-
Veyveyr updated:
-
-
New look for the pipe dispenser.
-
-
Errorage updated:
-
-
Mining station will now charge properly.
-
Duffle bags (Money bags) can now be emptied.
-
-
-
-
-14 February 2011, Valentine's day
-
-
Errorage updated:
-
-
New Job! - Shaft Miners have finally been added and are available to play.
-
Mining outpost - A new mining outpost has been built, the mining dock on SS13 has been updated.
-
-
-
ConstantA updated:
-
-
Slight speed up for combat mechs..
-
Added H.O.N.K construction
-
Fixed bug with switching intent while in mecha.
-
-
-
-
-
12.02.2011, 01.00 GMT, r1021
-
-
Added Durand combat exosuit.
-
Players can modify operation permissions of newly constructed civilian mechs. Click on mech with ID card or PDA with ID inside.
-
Added robotics access to default mecha maintenance permissions (all mechs) and operation permissions (civilian models only).
-
Fixed double adminlog message of explosion proc.
-
Fixed accidental mecha wreckage deletion.
-
Tweaked mecha internal fire processing.
-
Added some mecha-related sounds.
-
Moved GaussRand to helpers.dm and added GaussRandRound helper proc.
-
Other small changes.
-
-
-
11.02.2011, r1001-1020
-
-
Headsets upgraded. Shortcuts for channels are: :command :security scie:nce :engineering :medical. Also there is :binary :whisper :traitor and old good :h for your department.
-
"One Click Queue" added: When you quickly click on two things in a row, it will automatically queue the second click and execute it after the default 'action delay' of 1 second after the first click. Previously you had to spam-click until 1 second had passed. THIS AFFECTS EVERYTHING. NEEDS TESTING. - Skie
-
EMP effects added for further revisions. - Darem
-
Big map changes in engineering/robotics/science wing. - errorage
-
Welder Fixed. Now burns your eyes again. - errorage
-
9x9 singularity sprite added. - Skie/Mport
-
Ghetto surgery added. - Neophyte
-
Nasty vent pump lag fixed. - Neophyte
-
Mech gameplay and building updates. - ConstantA
-
-
-
08.02.2011, r999-1000
-
-
The amount of power the station uses should be lower.
-
The singularity now changes size based upon how much energy it has
-
New Machine: Particle Accelerator.
-
It might need a better name/sprite but when put together properly and turned on it will shoot Accelerated Particles.
-
The particles irradiate mobs who get in the way and move through solid objects for a short time.
-
The Particle Accelerator parts are set up by using a Wrench followed by a Cable Coil, then finished with a screwdriver.
-
When you shoot the Singularity Generator with Accelerated Particles it will spawn a Singularity.
-
New layout for Engineering, might be changed up slightly in the next few days.
-
-
-
06.02.2011, r979
-
-
Jesus christ it's a new map what the fuck
-
Just kidding, it's only minor changes to medbay/mechbay/cybernetics/R&D/toxins/robotics/chapel/theatre
-
Okay so there's too many changes to list completely, but basically: toxins/R&D/virology/xenobiology are south through medbay; robotics/cybernetics/mechbay are where toxins used to be, the theatre and chapel are above medbay.
-
Theatre is a new place for the Clown and Mime to play, there are some costumes available, a stage and backstage, and seating for the audience.
-
R&D and Toxins have been combined together. R&D is still work in progress. You need to head south through medbay to get there.
-
Medbay has been re-arranged slightly. Honestly, it's nothing, I bet you won't even notice anything different. There's also a new surgery suite, complete with pre-op and post-op rooms.
-
Virology's been rearranged, but it is mostly in the same place still.
-
Xenobiology is work in progress. [There's some fun stuff still being coded]
-
The Chapel is now to the north. You'll probably be thinking something like "Goddamn, this place is fucking huge", but it's actually smaller than the previous chapel.
-
Robotics and related stuff is also work in progress - however, everything needed for making borgs is there. Note: de-braining will now be done by surgeons in medbay, rather than roboticists in robotics, in case you're wondering where your optable went.
-
I added color-coded pipes in the areas I was working on. Red pipes run from air siphons and feed into the waste loop; blue pipes run from air vents and pump whatever atmos is set to pump. Yeah, there's TWO pipe networks on the station, who knew? Well, some of you probably did, but I didn't!
-
This update brought to you by: veyveyr and Rookie, with help from friends! [Now you know who to strangle, please be gentle q_q]
-
-
-
05.02.2011, r968
-
-
This really needs to be updated more often.
-
Various map updates have been applied with many more to come. Expect overhauls!
-
Mining system nearing completion.
-
Massive overhaul to the electricity systems, the way singularity makes power, and various related functions.
-
Mime spawns with White Gloves instead of Latex Gloves (apparently there's a difference!)
-
A new event has been- CLANG! What the fuck was that?
-
Ion storm laws should be much more interesting.
-
Security reports should no longer list traitor heads/AIs as possible revs/cultists, nor should nuke operatives ever get named for anything.
-
Pens are much more versatile and user friendly.
-
Mech building is rapidly on its way! Ripleys can be built, consult your quartermasters.
-
Traitors now have several new things they can steal.
-
Some surgeries coded to accompany the new operating room and surgery tools.
-
Research and Design is continuing development and should be rolled out shortly.
-
Various sprites added, tweaked, scrapped and fixed.
-
-
-
Changelog
-
05.02.2011, r968
-
-
This really needs to be updated more often.
-
Various map updates have been applied with many more to come. Expect overhauls!
-
Mining system nearing completion.
-
Massive overhaul to the electricity systems, the way singularity makes power, and various related functions.
-
Mime spawns with White Gloves instead of Latex Gloves (apparently there's a difference!)
-
A new event has been- CLANG! What the fuck was that?
-
Ion storm laws should be much more interesting.
-
Security reports should no longer list traitor heads/AIs as possible revs/cultists, nor should nuke operatives ever get named for anything.
-
Pens are much more versatile and user friendly.
-
Mech building is rapidly on its way! Ripleys can be built, consult your quartermasters.
-
Traitors now have several new things they can steal.
-
Some surgeries coded to accompany the new operating room and surgery tools.
-
Research and Design is continuing development and should be rolled out shortly.
-
Various sprites added, tweaked, scrapped and fixed.
-
-
-
20.01.2011, r894
-
-
Pipes can now be removed and re-attached by wrenching them.
-
Mining system continues to develop. Still unaccessible to players.
-
Various map changes. Some minor lag causing things were fixed.
-
Admins have a new tool: They can now give any spell to anyone. Hurray!
-
Imadolazine now works. Maybe?
-
Singularity now releases itself if fed too much and will potentially explode.
-
Magboots now successfully prevent you from getting pulled into the singularity.
-
Strike teams immune to facehuggers. Why? I dunno.
-
Many reagent containers are adjustable so you can pour the exact amount you need.
-
No more emitters working in space, Collectors and collector controllers now ID lockable.
-
Christmas Contest plaque finally added. It's near the armor/warden's office.
-
Rocks fall, everyone dies.
-
All cyborgs and robots can now be named. Just use a pen on the cyborg's frame before the brain is inserted.
-
Knock spell now unbolts doors as well as opens them.
-
New cultist runs and other changes.
-
Added surgery tools for eventual surgery system.
-
Autolathe and Circuit Printer animations redone. Yay pretty icons.
-
AI law changes/uploads are now tracked (admin viewable).
-
Revheads now get a PDA uplink instead of a headset one.
-
Added a penlight.
-
Science Research and Development tech tree uploaded. Not really accessible by anyone yet, though.
-
-
-
14.01.2011, r853
-
-
Changlings Overhauled. Now function kinda like alium (using an internal chemical reserve instead of plasma) with each ability requiring a certain amount of chemicals to activate. Both venoms removed. Several "Dart" abiliites added. They allow the changling to deafen, blind, mute, paralyze, or even transform (dead) targets.
-
Carp meat now contaminated with Carpotoxin. Anti-toxin negates the poison, however.
-
New Reagent: Zombie Powder: Puts subjects into a deathlike state (they remain aware, though). Each unit of Zombie Powder requires 5 units of Carpotoxin, Sleeping Toxin, and Copper.
-
Various alium fixes.
-
Matches now available from smokes machine.
-
Megabomb bug fixed. Bombs with timers/signalers won't detonate after the timer/ signaler is removed.
-
New Disease: Rhumba Beat. Functions like GBS with a few exceptions. Not only available by admindickery.
-
More mining fixes/changes.
-
Ghost can now teleport to AI sat, Thunderdome, and Derelict.
-
New gimmick clothes
-
Constructing Glass Airlocks now use one sheet of R.Glass.
-
Windows mow always appear above grills and pipes always above lattices.
-
Added FireFighter mecha and various mecha fixes.
-
Deconstructed walls now leave proper floor tiles (that can be pried up like normal) and remember what kind of floor they were before the wall was made.
-
Carded AIs no longer take damage while in unpowered areas.
-
Chaplains can now name their religion at round start.
-
Napalm nerfed a bit. Produces ~25% less plasma but it's all concentrated in a single tile (will still spread, though).
-
Reagent bottles can, once again, be put into grenades.
-
Various minor map changes.
-
-
-
08.01.2011,8:00PST, r820
-
-
Holograms (AI controled psudo-mobs) added. Currently only admin spawn.
-
Pre-spawned pills no longer have randomized image.
-
Bridge reorganized.
-
Automated turrets now less dumb. Additionally, turrets won't target people laying down.
-
Cultists now automatically start known words for add cultist ritual. Also, converted cultists start knowning a word.
-
Supply ship no longer can transport monkeys. Also, monkeys are no longer orderable from QM.
-
Meat Crate added to QM.
-
Corn can now be blended in a blender to produce corn oil which is used to create glycern.
-
Request Consoles added across the station. Can be used to request things from departments (and slightly easier to notice then the radio).
-
Centcom reoragnized a fair bit. Not that players care but admins might enjoy it.
-
There is now a toggable verb that changes whether you'll turn into an alium or not.
-
Hair sprited modified.
-
Napalm and Incendiary Grenades both work now. Have fun setting things on fire.
-
Binary Translater traitor item added. It allows traitors to hear the AI (it functions like a headset).
-
New Disease: Pierrot's Throat. Enjoy, HONK!
-
Robotic Transformation (from Robrugers) and Xenomorph Transformation (from xenoburgers) now curable.
-
Mining added. Only accessible by admins (and those sent by admins). Very much WIP.
-
Alium Overhaul. Divided into multiple castes with distinct abilities.
-
New AI Modules: The goody two-shoes P.A.L.A.D.I.N. module and it's evil twin T.Y.R.A.N.T. Only the former actually spawns on the station.
-
Lizards added. They run away and shit.
-
PDA overhaul. Doesn't change anything for humans, just makes coders happy.
-
Firesuits redone to look less like pajamas and instead like firesuits. Fire lockers also added.
-
New Mecha: H.O.N.K.
-
Deployable barriers added. Can be locked and unlocked.
-
Mecha bay (with recharging stations) added.
-
Bunny Ears, Security Backpacks, Medical Backpacks, Clown Backpacks, and skirt added.
-
Various wizard changes. New Wizard Spell: Mind Swap. Swap your mind with the targets. Be careful, however: You may lose one of your spells. Wizards are no longer part of the crew and get a random name like the AI does. Wizards can change their known spells with their spellbook but only while on the wizard shuttle.
-
Circuit Imprinter: Using disks from the various deparments, new circuit boards and AI modules can be produced.
-
Heat Exchanging pipes added and various pipe/atmos changes.
-
Ghost/Observer teleport now works 100% of the time.
-
-
-
17.12.2010,11:00GMT
-
-
You need an agressive grip to table now, as well as being within one tile of the table (to nerf teletabling).
-
Teleport only runs once at the beginning of the round, hopefully reducing the lag in wizard rounds.
-
Wizards can't telepot back to their shuttle to afk now.
-
Someone added it a while ago and forgot to update the changelog - syndies in nuke need to move their shuttle to the station's zlevel first.
-
Bunch of other stuff.
-
-
-
Sunday, November 21, 3:34 PST
-
-
Bug fixes, not going into detail. Lots and lots of bug fixes, mostly regarding the new map
-
CMO has a more obvious lab coat, that looks nicer than the original
-
CMO also has a stamp now
-
QM has a denied stamp
-
Cyborgs got tweaked. Laws only update when checked. Also have wires that can be toyed
- with for various effects, including AI sync, and law control
-
Second construction site similar to the original
-
Prison station has been tweaked, now includes a lounge, and toilets
-
Detective's revolver starts with a reasonable number of bullets
-
Prison teleporter to the courtroom now exists
-
AI related stuff: More cameras, oh, and bug fixes!
-
Emergency storage moved
-
Random AI law changes. New law templates, and variables. Old variables were tweaked and some of the crappy templates were removed
-
Ghosts can teleport to the derelict now
-
Respriting of a bunch of stuff as well as graphical fixes
-
Kitchen is attached to the bar again
-
General map tweaks and fixes
-
Wardens fixed for rev rounds
-
5-unit pills now work
-
APCs added and moved
-
Cola machine added. Contains various flavours of soda. Also added new snacks to the now named snack machine. Water cooler added, just apply an empty glass to it
-
Salt & Pepper have been added, as part of the food overhaul
-
More bug fixes. There was a lot
-
-
-
Tuesday, November 16, 00:20 GMT
-
-
Cruazy Guest's map is now live.
-
Entire station has been rearranged.
-
Prison Station added, with Prison Shuttle to transport to and from.
-
New Job: Warden. Distributes security items.
-
The new map is still in testing, so please report any bugs or suggestions you have to the forums.
-
AI Liquid Dispensers, Codename SLIPPER, have been added to the AI core. They dispense cleaning foam twenty times each with a cooldown of ten seconds between uses. Mounted flashes have also been included.
-
Clown stamp added to clown's backpack.
-
-
-
Sunday, November 14, 18:05
-
-
Major food/drink code overhaul. Food items heal for more but not instantly. Poison, Drug, and "Heat" effects from food items are also non-instant.
-
Preperation for one-way containers and condiments.
New Food Item: Chaos Donut: 1 Hot Sauce + 1 Cold Sauce + 1 Flour + 1 Egg. Has a variable effect. NOT DEADLY (usually).
-
New Drug: Ethylredoxrazine: Carbon + Oxygen + Anti-Toxin. Binds strongly with Ethanol.
-
Tape Recorders added! Now you can actually PROVE someone said something!
-
Amospherics Overhaul Started: It actually works now. You can also build pipes and create function air and disposal systems!
-
Walls are now smooth.
-
Alcohol no longer gets you as wasted or for as long.
-
QM job split into QM and Cargo Technicians. QM has his own office.
-
Doors can no longer be disassembled unless powered down and unbolted.
-
New Job: Chief Medical Officer. Counts as a head of staff and is in charge of medbay. Has his/her own office and coat.
-
Wizarditis Bottle no longer in virus crate.
-
-
-
Friday, November 5, 19:29
-
-
The ban appeals URL can now be set in config.txt
-
Secret mode default probabilities in config.txt made sane
-
Admin send-to-thunderdome command fixed to no longer send people to the other team's spawn.
-
Having another disease no longer makes you immune to facehuggers.
-
Magboots are now available from EVA.
-
Changeling death timer shortened. Destroying the changeling's body no longer stops the death timer.
-
Cult mode fixes.
-
Fixed cyborgs pressing Cancel when choosing AIs.
-
The play MIDIs setting now carries over when ghosting.
-
Admins can now see if a cyborg is emagged via the player panel.
-
PAPERWORK UPDATE v1: Supply crates contain manifest slips, in a later update these will be returnable for supply points.
-
The Supply Ordering Console (Request computer in the QM lobby) can now print requisition forms for ordering crates. In conjunction with the rubber stamps, these can be used to demonstrate proper authorisation for supply orders.
-
Rubber stamps now spawn for each head of staff.
-
The use of DNA Injectors and fueltank detonations are now admin-logged.
-
Removed old debug code from gib()
-
-
-
Tuesday, November 2, 19:11(GMT)
-
-
Finished work on the "cult" gamemode. I'll still add features to it later, but it is safe to be put on secret rotation now.
-
Added an energy cutlass and made a pirate version of the space suit in preparation for a later nuke update.
-
Changeling now ends 15 minutes after changeling death, unless he's ressurected.
-
Further fixing of wizarditis teleporting into space.
-
Fixed the wise beard sprite.
-
Fixed missing sprite for monkeyburgers.
-
Fixed Beepsky automatically adding 2 treason points to EVERYONE.
-
-
-
-
Thursday, October 28, 19:30(GMT)
-
-
Sleepers and disposals now require two seconds to climb inside
-
-
Hydroponics crate ordered in QMs doesnt spawn too many items
-
Replacement lights crate can be ordered in QM.
-
Added space cleaner and hand labeler to Virology.
-
Welder fuel tanks now explode when you try to refuel a lit welder.
-
Made clown's mask work as a gas mask.
-
9 new cocktails: Irish Coffee, B-52, Margarita, Long Island Iced Tea, Whiskey Soda, Black Russian, Manhattan, Vodka and Tonic, Gin Fizz. Refer to the wiki for the recipes.
-
Kitchen update:
-
-
-New Microwave Recipies: Carrot Cake (3 Flour, 3 egg, 1 milk, 1 Carrot), Soylen Viridians (3 flour, 1 soybeans), Eggplant Parmigania (2 cheese, 1 eggplant), and Jelly Donuts (1 flour, 1 egg, 1 Berry Jam), Regular Cake (3 flour, 3 egg, 1 milk), Cheese Cake (3 flour, 3 egg, 1 milk), Meat Pies (1 meat of any kind, 2 flour), Wing Fang Chu (1 soysauce, 1 xeno meat), and Human and Monkey Kabob (2 human or monkey meat, metal rods).
-
- Ingredients from Processor: Soysauce, Coldsauce, Soylent Green, Berry Jam.
-
- Sink added to kitchen to clean all the inevitable blood stains and as preperation for future cooking changes.
-
- The food processor can't be abused to make tons of food now.
-
-
-
Multiple tweaks to virology and diseases:
-
-
- Added wizarditis disease.
-
- Spaceacillin no longer heals all viruses.
-
- Some diseases must be cured with two or more chemicals simultaneously.
-
- New Virology design including an airlock and quarantine chambers.
-
- Made vaccine bottles contain 3 portions of vaccine.
-
- Lots of minor bug fixes.
-
-
-
-
-
-
Monday, October 18, 06:24(GMT)
-
-
Added virology profession with a cosy lab in northwestern part of medbay.
-
Virology related things, like taking blood samples, making vaccines, splashing contagious blood all over the station and so on.
-
Added one pathetic disease.
-
Virus crates are now available from the quartermasters for 20 points.
-
The DNA console bug (issue #40) was fixed, but I still made the DNA pod to lock itself while mutating someone.
-
Added icons for unpowered CheMaster and Pandemic computers
-
Added some sign decals. The icons were already there, but unused for reasons unknown.
-
Some map-related changes.
-
-
-
Wednesday, October 13, 14:12(GMT)
-
-
Crawling through vents (alien) now takes time. The farther destination vent is, the more time it takes.
-
Cryo cell healing ability depends on wound severity. Grave wounds will heal slower. Use proper chemicals to speed up the process.
-
Added sink to the medbay.
-
Bugfixes:
-
-
- Some reagents were not metabolized, remaining in mob indefinitely (this includes Space Cola, Cryoxadone and cocktails Kahlua, Irish Cream and The Manly Dorf).
-
- Fixed placement bug with container contents window. Also, utility belt window now doesn't obscure view.
-
-
-
-
-
Sunday, October 10, 14:25(GMT)
-
-
Scrubbers in the area can be controlled by air alarms. Air alarm interface must be unlocked with an ID card (minimum access level - atmospheric technician), usable only by humans and AI. Panic syphon drains the air from affected room (simple syphoning does too, but much slower).
-
Sleeper consoles inject soporific and track the amounts of rejuvination chemicals and sleep toxins in occupants bloodstream.
-
Flashlights can be used to check if mob is dead, blind or has certain superpower. Aim for the eyes.
-
Radiation collectors and collector controls can be moved. Secured\unsecured with a wrench.
-
Air sensors report nitrogen and carbon dioxide in air composition(if set to).
-
Air Control console in Toxins.
-
Additional DNA console in genetics
-
Enough equipment to build another singularity engine can be found in engineering secure storage
-
Air scrubber, vent and air alarm added to library
-
Air alarm added to brig
-
Air scrubbers in Toxins turned on, set to filter toxins
-
Empty tanks, portable air pumps and similar can be filled with air in Aft Primary Hallway, just connect them to the port. Target pressure is set by Mixed Air Supply console in Atmospherics (defaults to 4000kPa).
-
-
-
Wednesday, October 6, 18:36
-
-
Fixed the Librarian's suit - its worn iconstate wasn't set.
-
Fixed some typos.
-
Monkey crates are now available from the quartermasters for 20 points.
-
Corpse props removed from zlevel 8 as they were causing issues with admin tools and the communications intercept.
-
Cleaned up the default config.txt
-
Added a readme.txt with installation instructions.
-
Changed the ban appeals link to point to our forums for now - this'll be a config file setting soon.
-
-
-
Tuesday, October 5, 01:41
-
-
Fixes to various nonworking cocktails.
-
More map and runtime error fixes.
-
Nuke operative headsets should be on an unreachable frequency like department headsets.
-
Another AI Malfunction change: Now once the AI thinks enough APCs have been hacked, it must press a button to start the timer, which alerts the station to its treachery.
-
Blob reskinned to magma and increased in power.
-
The HoS now has an armored greatcoat instead of a regular armor vest.
-
Admin logs now show who killswitched a cyborg.
-
The roboticist terminal now lets you see which AI a cyborg is linked to.
-
Malf AIs are no longer treated as inactive for the purpose of law updates and cyborg sync while hacking APCs.
-
Traitor AIs are now affected by Reset/Purge/Asimov modules, except law 0.
-
AI core construction sprites updated.
-
Securitrons removed from the Thunderdome.
-
An APC now supplies power to the bomb testing area, and has external cabling to supply it in turn.
-
A new variant freeform module has been added to the AI Upload room.
-
The changeling's neurotoxic dart has been made more powerful - this will likely be an optional upgrade from a set of choices, akin to wizard spells.
-
Some gimmick clothes moved to a different object path.
-
The chameleon jumpsuit should now be more useful - it includes job-specific jumpsuits as well as flat colours.
-
-
-
Wednesday, September 29, 15:40
-
-
Bartender update! Bartender now has a Booze-O-Mat vending machine dispensing spirits and glasses, which he can use to mix cocktails. Recipes for cocktails are available on the wiki.
-
The barman also now has a shotgun hidden under a table in the bar. He spawns with beanbag shells and blanks. Lethal ammo is obtainable from hacked autolathes.
-
Dead AIs can once more be intelicarded, however in order to be restored to functionality they must be repaired using a machine in the RD office.
-
Silicon-based lifeforms have metal gibs and motor oil instead of blood.
-
Aliens now have a death message.
-
Intelicarded AIs can now have their ability to interact with things within their view range reactivated by the person carrying the card.
-
New AI cores can be constructed from victimsvolunteers.
-
Verbs tweaked.
-
Intelicarded AIs can be deleted.
-
RD office redesigned, and the RD now spawns there.
-
The AI can now choose to destroy the station on winning a Malf round.
-
General bugfixes to AIs, constructed AIs and decoy AIs.
-
Hats no longer prevent choking.
-
Some extra gimmick costumes are now adminspawnable.
-
AI health is now displayed on their status tab.
-
AI upload module now requires you to select which AI to upload laws to in case of multiple AIs.
-
Cyborgs now choose an AI to sync laws with upon creation.
-
Law office redesigned.
-
Roboticists no longer have Engineering access.
-
More fixes to areas which had infinite power due to having no assosciated APC.
-
Meatbread slices are no longer infinite.
-
Malf rounds no longer end if a non-malfunctioning AI is killed.
-
Cigarettes now have directional sprites.
-
AI Core circuitboard spawns in the RD office.
-
AI Satellite now has cameras and properly-wired SMES batteries.
-
Decoy AIs can no longer be moved.
-
Several runtime errors have been fixed.
-
Nuke rounds will now end properly on a station or neutral victory.
-
Riot shields have been nerfed and are now only available in the armory and in riot crates.
-
Foam dart crossbows, cap guns and caps can now be won as arcade prizes.
-
AI Malfunction has been redesigned - the AI must now hack APCs in order to win. More APCs hacked makes the timer tick faster.
-
Hydroponics now has a MULEbot station.
-
Changeling mode has been added to the game and is now in testing.
-
Electrified airlocks should now only zap you once if you bump into them while running.
-
Chemistry and Toxins access has been removed from Botanists.
-
General bugfixes and map tweaks.
-
-
-
Sunday, September 26, 17:51
-
-
Riot shields! One in every security closet, and a few in armory. Also orderable from QM.
-
-
-
Tuesday, September 21, 17:51
-
-
New experimental UI for humans by Skie. Voice out if it has problems or you don't like it.
- ---> YOU CAN CHOOSE UI FROM PREFERENCES <---
-
Hydroponics: Now you can inject chemicals into plants with a syringe. Every reagent works differently.
-
Hydroponics: Added a small hoe for uprooting weeds safely. Botanists now have sissy aprons.
-
New random station/command names and verbs.
-
Dead AIs can no longer be intellicarded and the steal AI objective is now working.
-
Aliens now bleed when you hit them, as well as monkeys.
-
Hurt people and bodies leave blood behind if dragged around when they are lying. Sprites to be updated soon...
-
Fixed several run-time errors in the code. Also food doesn't deal damage anylonger in some cases.
-
Blobs and alien weeds slowed down some. Plant-b-gone buffed some.
-
Fixed monkeys and aliens not being able to deal damage to humans with items.
-
Monkeys now slip on wet floor.
-
-
-
Friday, September 17, 23:03
-
-
The Lawyer now starts in his snazzy new office.
-
Law Office accesslevel added. Currently, the Lawyer and the HoP begin with this.
-
Robotics access can now be added or removed from the HoP's computer.
-
Robotics, the captain's quarters, the cargo lobby and the staff heads office now have APCs and can lose power like the rest of the station.
-
Toxins mixing room is now a separate area for power and fire alarm purposes, as it already had its own APC.
-
-
-
Thursday, September 16, 20:11
-
-
Added the Lawyer job.
-
Doors can now be constructed and deconstructed. This is being playtested, expect the specifics to change.
-
Fixed certain jobs which were supposed to have stuff spawning in their backpack that just wasn't getting spawned.
-
-
-
Monday, September 13, 13:30
-
-
Bunch of new announcer sounds added
-
Minor lag fix implementation in the pipe system
-
You can now hear ghosts... sometimes
-
Seed bags and nutrients can now be pocketed
-
-
-
Monday, September 12, 12:48
-
-
New kitchen stuff: New recipes (Meatbread, Cheese, Omelette Du Fromage, Muffins), new chef's knife and trays (Spawn in the meat locker) and milk (spawns in the fridge). Recipes are as follows:
- -Cheese: milk on food processor
- -Cheese wedge: Slice the cheese wheel with the chef's knife
- -Omelette Du Fromage: 2 eggs 2 cheese wedges
- -Muffin: 2 eggs 1 flour 1 carton of milk
- -Meatbread: 3 meats (whatever meats) 3 flour 3 cheese. Can be sliced.
- Cheese_amount is actually displayed on the microwave.
-
Profession-special radio channels now have color.
-
AI card not retardedly lethal anymore, for anyone that didn't notice
-
HYDROPONICS OVERHAUL, credit goes to Skie and Numbers. Wood doesn't have the entity so the tower caps cannot be harvested. For now.
-
Bar is now barman-only, access-wise. No more shall the entire station trump inside the bar and choke the monkey.
-
Prepping ground for Barman update (SPRITE ME SOME GODDAMN BOTTLES)
-
-
Thursday, September 2, 22:45
-
-
Ghosts can no longer release the singularity.
-
Sprites added in preparation for a Hydroponics update
-
A decoy AI now spawns in the AI core during Malfunction rounds to reduce metagaming.
-
libmysql.dll added to distribution.
-
Aircode options restored to default configuration.
-
AIs properly enter powerloss mode if the APC in their area loses equipment power.
-
Hydroponics crates added to Hydroponics, containing Weed-B-Gone
-
Airlock electrification now actually works properly.
-
Karma database error message updated.
-
Cyborgs choosing the standard module no longer become invisible except for a pair of glowing red eyes.
-
Aliens now have a hivemind channel, accessed like departmental radio channels or robot talk with ':a'.
-
Full donut boxes no longer eat whatever item is used on them and disappear.
-
-
Monday, August 30, 16:24
-
-
PDA user interface has been given a graphical overhaul. Please report any problems with it on the issue tracker.
-
Personal lockers are once again available in the lockerroom
-
BUGFIX: Xenoburger iconstate was accidentally removed in an earlier revision. This has been fixed.
-
Some of the default messages have been changed.
-
Additional sprites added for plants and weeds in preparation for an expansion of Hydroponics.
-
A schema script is now available for setting up the SQL database.
-
-
-
Sunday, August 29, 05:09
-
-
The Robotics Crate no longer exists. Quartermasters can now order a MULEbot crate for 20 points, or a Robotics Assembly crate for 10 points. The latter provides 4 flashes, 3 proximity sensors, two 10k charge power cells and an electrical toolbox, and requires a roboticist or a head of staff to open.
-
Traitor AIs no longer lose their Law 0 in the event of power loss.
-
Administrators can now toggle the availiabilty of their right-click verbs to prevent accidental usage while playing.
-
Tool Storage vending machine is now a proper object. (code cleanup)
-
Buckets are now available from autolathes.
-
Four generic remote signaller PDA cartridges are now stocked in the Tool Storage vending machine.
-
AI status display density adjusted.
-
-
-
Thursday, August 26, 21:07
-
-
Open Source Release Thanks to Mport for releasable singularity code.
-
Cyborgs redone Thanks again to Mport for this, cyborgs are totally different now.
-
Engine Monitor PDA app is now Power Monitor PDA app, and actually works.
-
AI State Laws verb now allows the AI to choose which laws to state, in case of traitor AIs or laws ordering it not to state them. Hopefully this will cut down on 'OMG THE AI IS COPYING AND PASTING' metagaming.
-
Power Monitor circuitboard isn't mislabeled as Mass Driver Control any more.
-
Traitor and Rev-head clowns lose the clumsiness gene - this should make trying to flash people in Rev mode less of an exercise in frustration.
-
A sink has been added to the Fitness room - this lets you wash dirty and bloodstained clothing and equipment.
-
Blast doors and firedoors no longer open by just bumping into them.
-
The bar and kitchen now open onto the same seating area. The old cafeteria area is now used as a lockerroom to replace the old one which was displaced by Hydroponics.
-
The bar now has a space piano with which you can entertain and annoy the crew.
-
LIBRARY A library has been added to the station in the escape arm in order to educate the crew. The new Librarian job is available to manage it. Crewmembers can request and read books, or write and bind their own books for upload to a persistent database.
-
The supply of flashbangs available from Security has been reduced to cut down on people constantly flashbanging the escape shuttle.
-
InteliCards are available in various locations to allow the retrieval of valuable AI personality data in the event of catastrophic station damage.
-
-
-
Friday, August 06, 20:32
-
-
Hydroponics/Botany Added Credit goes to Skie and the folks over at the independent opensource SS13 branch, this is their code. It's lacking a lot, but it's a great start!
-
Way more tweaks than I can remember. Shouldn't wait so long between changelog updates.
-
-
Tuesday, July 13, 22:35
-
-
Singularity Engine Added Oh God we're all going to die (All credit on this one goes to Mport2004)
-
'Purge' AI module added - purges ALL laws (except for law 0). Will probably change this to a Syndicate only item
-
Cyborgs now spawn with a power cell. Should prevent stupid cyborg deaths (and also pave the way for starting as a cyborg once more bugs are fixed)
-
-
Saturday, July 10, 15:10
-
-
Examining a player will now tell you if their client has disconnected.
-
Examining a brain will now tell you if it's owner is still connected to the game.
-
Alien Queens can make facehuggers. Facehuggers can make larva. Larva can grow into xenos! Xenos can become queens! The circle of life~
-
Some powernet bug fixes: Bad list and division by zero.
-
-
Friday, July 09, 05:16
-
-
Tweaked crate costs for Quartermaster.
-
Increased metal available in Robotics.
-
Added department-specific headsets. Engineering, Medical, Command, and Security all receive special headsets capable of broadcasting on a standard frequency PLUS a secure frequency only available to headsets of the same type. Precede say messages with ":h" to use.
-
-
-
Tuesday, July 06, 19:16
-
-
Prayer command added.
-
State Laws command for AI added.
-
Disabled Lockdown command for AI. Too server heavy.
-
Crew manifest and various station databases should properly update when late arrivals join the game, now.
-
Quartermasters will receive 10 points every five minutes. This will probably be nerfed heavily, but we'll give it a shot anyhow.
-
Fixed a bug with doors/airlocks. (Thanks Mport2004)
-
-
-
Sunday, April 25, 18:53
-
-
- New graphics:
-
-
- Side Facing Sprites: Player sprites will now face in all directions when moving. Holy shit!
-
-
-
-
-
Monday 2.0, April 19, 2100
-
-
- New features:
-
-
- Disposal System: The station now has a fully functional disposal system for throwing away nuclear authentication disks and old, dirty clowns.
-
-
- Breakable Windows: Windows are breakable by projectiles and thrown items (including people), shards hurt your feet.
-
-
- Status Display: Station escape shuttle timers now function as status displays modifiable from the bridge.
-
-
- Space Heater: Space heaters for heating up cold spaces, in space.
-
-
-
- New items:
-
-
- Welding Mask: Helps engineers shield their eyes when welding.
-
-
- Utility Belt: Function as toolboxes equippable in the belt slot.
-
-
- Mouse Trap: Hurt your feet, especially if you aren't wearing shoes!
-
-
- Power Sink: Traitor item that rapidly drains power.
-
-
-
-
- New graphics:
-
-
- North Facing Sprites: Player sprites will now face north when moving north.
-
-
- Hidden Pipes: Pipes are now hidden underneath floor tiles.
-
-
-
-
- New robot: Medibot
-
-
- Automatically attempts to keep crewmembers alive by injecting them with stuff.
-
-
-
-
- New robot: Mulebot
-
-
- Allows quartermasters to automatically ship crates to different parts of the station.
-
-
-
-
-
-
Funday, December 31, 2099
-
"FINALLY, DEV IS OUT"
-
-
- Changes:
-
-
- Atmos system GREATLY OPTIMIZED!
-
-
- Brand new station layout!
-
-
- Robust chemical interaction system!
-
-
- HOLY FUCK PLAYING THIS GAME ISN'T LIKE TRODDING THROUGH MOLASSES ANYMORE
-
-
- Feature: If two players collide with "Help" intent, they swap positions.
-
-
-
-
-
-
Tuesday, February 23, 2010
-
-
- OH NO STRANGLING GOT NERFED: Insta-strangling (hopefully) removed. Victim no longer instantly loses consciousness.
-
-
-
-
Sunday, February 21, 2010
-
-
- Cloning Machine: The Geneticist spilled coffee on the Genetics Machine's revival module and it was too costly to replace!
-
-
- Clones may or may not have horrible genetic defects.
-
-
-
-
-
-
Thursday, February 18, 2010
-
-
- New feature: Obesity from overeating in a short period of time.
-
-
-
-
Sunday, February 14, 2010
-
-
- New feature: Station destruction cinematic if the crew loses in AI Malfunction or Nuclear Emergency.
-
-
- New Position: Tourist
-
-
- Centcom has entered the lucrative business of space tourism! Enjoy an event-filled vacation on the station, and try not to get killed.
-
-
- Guest accounts are now restricted to selecting Tourist in Character Setup.
-
-
-
-
-
-
Friday, February 5, 2010
-
-
- AI: Added 30 second cooldown to prevent spamming lockdowns.
-
-
-
-
Wednesday, February 2, 2010
-
-
- Feature: Character preview in Character Setup!
-
-
-
-
Tuesday, February 2, 2010
-
-
- New item: Drinking glasses that you can fill with water.
-
-
- Feature: Sounds now pan in stereo depending on your position from the source.
-
-
-
-
Saturday, December 5, 2009
-
-
- Traitor tweak: Agent cards can now be forged into a fake ID.
-
-
-
-
Friday, December 4, 2009
-
-
- Supply Dock 2.0: The Supply Dock has been redesigned and now features conveyer belts! Amazing!
-
-
- New uniforms: The Research Director, Chief Engineer, and the research jobs have new uniforms. The Head of Security has a cool new hat which happens to be his most prized possession.
-
-
- Merged research: The first act of the Research Director is to merge Toxins and Chemistry into a single Chemical Lab. Hooray!
-
-
- Robot tweak: You can now observe robots using the observe command.
-
-
- Stamps: The heads now have stamps to stamp papers with, for whatever reason.
-
-
-
-
Monday, November 30, 2009
-
-
- Supply Shuttle 1.0: Now you can order new supplies using Cargo Bay north of the autolathe.
-
-
- New containers: The game now features a variety of crates to hold all sorts of imaginary space supplies.
-
-
- New position: Quartermaster
-
-
- A master of supplies. Manages the cargo bay by taking shipments and distributing them to the crew.
-
-
-
-
- New position: Research Director
-
-
- The head of the SS13 research department. He directs research and makes sure that the research crew are working.
-
-
-
-
- New position: Chief Engineer
-
-
- Boss of all the engineers. Makes sure the engine is loaded and that the station has the necessary amount of power to run.
-
-
-
-
- New robot: Securibot
-
-
- Automatically stuns and handcuffs criminals listed in the security records. It's also really goddamn slow.
-
-
-
-
- New jumpsuits: Engineers and Atmos Techs have new jumpsuits to distinguish between them easier.
-
-
-
-
Friday, November 27, 2009
-
-
- Monkey AI 2.0: Monkeys will now get angry, going after random human targets with the ability to wield weapons, throw random objects, open doors, and break through glass/grilles. They're basically terminators.
-
-
- New gamemode: Monkey Survival
-
-
- Survive a horde of angry monkeys busting through the station's airvents and rampaging through the station for 25 minutes.
-
-
-
-
- New robots: Cleanbot and Floorbot
-
-
- Cleanbots automatically clean up messes and Floorbots repair floors.
-
-
-
-
- New spell: Mindblast
-
-
- Causes brain damage, progressively causing other players to become even more retarded.
-
-
-
-
- Alien Races
-
-
- Wizards may randomly spawn as illithids, who gain Mind Blast for free, and nuke agents may randomly spawn as lizardmen.
-
-
-
-
- Station shields: The station now has a toggleable forcefield that can only be destroyed by meteors or bombs. Takes a lot of station power to use.
-
-
- Traitor scaling: Number of traitors/wizards/agents now scales to number of players.
-
-
- New food item: Donk pockets
-
-
- Delicious and microwavable, gives a bigger health boost for traitors.
-
-
-
-
- Cigarettes: Now you can fulfill your horrible nicotine cravings. The detective starts with a zippo lighter and pack of cigarettes. Other packs can be be obtained via vending machines.
-
-
- Warning signs: The station is now filled with various warning signs and such.
-
-
- Updated graphics: Many, many objects have had their graphics updated including pipes, windows, tables, and closets. HUD graphics have been updated to be easier to understand.
-
-
- Lighting fixes: New turf is now correctly lit instead of being completely dark.
-
-
- Meteor fixes: The code and graphics for meteors has been fixed so the meteor gametype is more playable, sort of.
-
-
- Escape shuttle fix: The shuttle can now be called in Revolution and Malfunction, but the shuttle will be recalled before it arrives. This way players can no longer call the shuttle to figure out the game mode during secret.
-
-
- Changelog updated: New changelog entry for Thanksgiving thanks to Haruhi who will probably update the changelog from now on after almost a month of neglect.
-
-
-
-
Monday, November 3, 2009
-
-
- Bug fix: Made most pop-up windows respect the close button.
-
-
-
-
Sunday, October 25, 2009
-
-
- Randomized naming: Names for Central Command and Syndicate are now randomized.
-
-
-
-
Saturday, October 24, 2009
-
-
- Bug fix: PDAs had their code cleaned up. Notice any problems? Report them.
-
-
- New syndicate item: Detomatix Cartridge, allows remote detonation of PDAs (rather weak explosion)!
-
-
- Feature: Remotely detonating PDAs has a chance of failure depending on the PDA target, a critical failure will result in the detonation of your own PDA.
-
-
-
-
Monday, October 19, 2009
-
-
- Gibbing update: Gibbing stuff has been rewritten, robots now gib nicer.
-
-
- LIGHTING!!!: The station now has dynamic lighting and associated items.
-
-
-
-
Friday, October 16, 2009
-
-
- Poo v1.0~: This has caused many ragequits.
-
-
- Flushable toilets: You can now use toilets to place your vile, disgusting and irreprehensible excretions (you disgusting children). Just be careful what you flush!
-
-
-
Monday, October 12, 2009
-
-
- Feature: Emergency oxygen bottles can be clipped to your belt now.
-
-
- Clothing update: Bedsheets are now wearable.
-
-
- Updated HUD: A few minor tweaks to the inventory panel. Things might not be exactly where you're used to them being.
-
-
-
Monday, September 28, 2009
-
-
- New position: Chef
-
-
- Maintains the Cafeteria, has access to Kitchen and Freezer, Food creation will be in shortly.
-
-
-
-
- Food update: Food items now heal Brute/Burn damage. The amount recovered varies between items.
-
-
-
Saturday, August 29, 2009
-
-
- AI laws update: Nanotrasen has updated its AI laws to better reflect how they wish AIs to
- operate their stations.
-
-
- Traitor item change: E-mag renamed to Cryptographic Sequencer.
-
-
-
-
Friday, July 31, 2009
-
-
I'm really sorry everyone I just HAD to add a gib all verb.
-
Decided to add the creation of bombs to bombers list
-
Made the new bombing list EVEN BETTER!!!
-
Fixed a bug with admin jumping AND the traitor death message
-
Oops, fixed a bug that returned the right click pm thing thinking the admin was
- muted.
-
Made a new improved way of tracking who bombs shit.
-
More formatting shit.
-
Fixed up some mute code and made it so that if a player is muted they cannot PM
- us.
-
Adminhelps now logged in the admin file not ooc
-
Changed the way admin reviving is dealt with. (It was coded kind of weirdly
- before)
-
Added a few areas to the observe teleport. Fixed some adminjump things. Modified
- the paths of some areas.
-
You can now ban people who have logged out and admins can now jump to people
- using the player panel.
-
Added in jump to key coded in a much better way than showtime originally did it.
-
Fixed magical wind when laying pipes. They start out empty!!
-
Made blink safer. Fixed the crew-quarters to ai sattelite teleport problem.
-
Forgot the message again. Added an emp spell. thanks copy&paste.
-
OH MY GOD I HAVE RUINED ASAY
-
Added electronic items to the pipe dispenser
-
fixed a formatting error with the changelog (I didn't break it, it was showtime)
-
Fixed a formatting error
-
Cleaned up sandbox object spawn code
-
New and improved admin log so we can keep an eye on these fuckers
-
Fixed adminjump because I realise most people use it for the right click option
-
Mushed together jump to mob and jump to key
-
Fixed a compilation error and made my test room more secure!
-
-
-
Wednesday, July 29th, 2009
-
-
These are a collection of the updates from the last 6 days. I promise to update
- the changelog once a week. Note that this does not include all the changes in
- the past 6 days.
-
-
-
Multitools can now be used to measure the power in cables.
-
Fixed a bug where the canister message would repeat and spam the user when
- attackby analyzer. Fixed an admin formatting error.
-
Replaced all range checks with a in_range proc. pretty good chance I broke
- something or everything.
-
Mutations use bitfields
-
Fixed a bug with my traitor panel.
-
Fixed the turrets, ruined Pantaloons map (test map). Did some things with
- turrets and added a few areas.
-
Some stuff in here you know the usual shit. Bugfixes, formatting etc.
-
Stunbaton nerf.
-
Tempban longer than 1 year -> permaban.
-
Turfs > spawnable.
-
Shaking someone now slowly removes paralysis, stuns and the 'weakened' stuff.
-
CTF flags now check if someone has them equipped every 20 seconds, if they are
- not then they delete themselves and respawn.
-
Fixed the r-wall-welder-message-thing.
-
Change to the CTF code, flag captures can now only happen if your team has their
- flag in the starting position.
-
Pruning my test room.
-
Instead of the red and green team its now the American and Irish teams!
-
BACKUP BACKUP TELL ME WHAT YOU GONNA DO NOW Changed the monkey name code. Re-did
- my antimatter engine code so it actually puts out power now
-
Fixed a bug that gave everyone modify ticker variables you silly sausage.
-
Sorted the AIs track list.
-
Constructable filter inlets and filter controls.
-
Added in admin messages for when someone is banned.
-
Bannana and honk honk.
-
-
-
Saturday, June 27th, 2009
-
-
-
Pipe construction now works completely. //Nannek
-
Many many other things that never gets recorded in the changelog!!
-
-
-
Saturday, June 27th, 2009
-
-
The Michael Jackson Memorial Changelog Update
-
Pipe filters adjusted for more ideal environmentals //Pantaloons
-
Added in job tracking //Showtime
-
Crew Manifest and Security Records now automagically update when someone joins //Nannek
-
Fixed a bug where sometimes you get a screwdriver stuck in your hand //Pantaloons
-
Flamethrowers can now be disassembled //Pantaloons
-
OBJECTION! Added suits and briefcases //stuntwaffle
-
Added automatic brig lockers //Nannek
-
Added brig door control authorization and redid brig layout //Nannek
-
Emergency toolboxes now have radios and flashlights, and mechanical toolboxes now have crowbars //Pantaloons
-
New whisper system //lallander
-
Some more gay fixes //everybody
-
Some really cool fixes //everybody
-
Really boring code cleanup //Pantaloons
-
~~In Loving Memory of MJ~~ Sham on!
-
-
-
Friday, June 12th, 2009
-
-
Looking back through the SVN commit log, I spy...
-
Keelin doing some more performance enhancements
-
Fixed one person being all 3 revs at once (hopefully)
-
Some gay fixes
-
New admin system installed
-
Fixed a bug where mass drivers could be used to crash the server
-
Various pipe changes and fixes
-
-
-
Wednesday, June 3rd, 2009
-
-
Death commando deathmatch mode added.
-
-
-
Monday, June 1st, 2009
-
-
Ghosts can no longer wander from space into the dread blackness that lies beyond.
-
Those other losers probably did a bunch of other stuff since May 6th but they don't comment their revisions so fuck 'em.
-
-
-
-
Wednesday, May 6th, 2009
-
-
Crematorium
-
Goon? button makes all your dreams come true.
-
Restructured medbay
-
-
Monday, May 4th, 2009
-
-
Does anyone update this anymore?
-
New atmos computer promises to make atmos easier
-
Autolathe
-
Couple of map changes
-
Some computer code reorganised.
-
I'm pretty sure theres a couple things
-
-
Saturday, April 18th, 2009
-
-
Weld an open closet (only the normal kind), gayes.
-
Chaplin has a higher chance of hearing the dead.
-
New traitor objective
-
Power traitor objective removed
-
New job system implemented for latecomers.
-
Head of Research quits forever and ever, is replaced by Head of Security (who gets his own office)
-
-
-
Fri, April 10, 2009
-
-
Admins are now notified when the traitor is dead.
-
Unprison verb (again, for admins).
-
-
-
Wed&Thu, April 8&9, 2009
-
-
Medical redone, doctors do your jobs! (Tell me what you think of this
- compared to the old one)
-
Clickable tracking for the AI
-
Only the heads can launch the shuttle early now. Or an emag.
-
-
-
Mon&Tue, April 6&7, 2009
-
-
Sounds. Turn on your speakers & sound downloads.
-
Scan something with blood on it detective.
-
-
-
Sunday, April 5, 2009
-
-
A large icon for the headset, no reason it should be so small.
-
-
-
Saturday, April 4, 2009
-
-
Emergency closets now spawn an 'emergency gas mask' which are just recolored gas masks, no other difference other than making it obvious where the gas mask came from.
-
-
-
Wednesday, April 1, 2009
-
-
Constructable rocket launchers: 10 rods, 10 metal, 5 thermite and heated plasma from the prototype.
-
Emergency closets have randomized contents now.
-
Fixed a bug where someone who was jobbaned from being Captain could still be picked randomly
-
-
-
Friday, March 27, 2009
-
-
Fixed a bug where monkeys couldn't be stunned.
-
Change mode votes before game starts delays the game.
-
-
-
Thursday, March 26, 2009
-
-
The brig is now pimped out with special new gadgets.
-
Upgraded the admin traitor menu.
-
-
-
Tuesday, March 24, 2009
-
-
GALOSHES!
-
A certain item will now protect you from stun batons, tasers and stungloves when worn.
-
-
-
Monday, March 23, 2009 (EXPERIMENTAL)
-
-
Say / radio / death talk systems recoded, hopefully improving it.
-
Announcements of late joiners are now done by the AI if it's alive :-)
-
-
-
Monday, March 23, 2009
-
-
Random station names.
-
Changes to the message stylesheet.
-
Admin messages in OOC will now be colored red.
-
-
-
Saturday, March 21, 2009
-
-
Added a command to list your medals.
-
ETA no longer shows when it doesn't matter.
-
Nerfed the ability to spam shuttle restabalization.
-
Fixed the 'Ow My Balls!' medal to only apply from brute damage rather than both brute and burn damage.
-
-
-
Thursday, March 19, 2009
-
-
Job banning.
-
Genetic Researcher renamed to Geneticist.
-
Toxins Researcher renamed to Scientist.
-
Help reformatted.
-
Fixed a bug where combining bruise packs or ointments resulted in an incorrectly combined amount.
-
Renamed memory and add memory commands to Notes and Add Note.
-
-
-
Tuesday, March 17, 2009
-
-
Medals! MEDALS!
-
Trimmed the excessively long changelog.
-
-
-
Saturday, March 14, 2009
-
-
Janitor job complete! Report any bugs to adminhelp
-
-
-
Saturday, March 7, 2009
-
-
Wizard now needs his staff for spells
-
Be careful with APCs now okay?!
-
Fixed Memory and made it more efficient in the code
-
Crowbars now open apcs, not screwdrivers. They do something else entirely
-
Hackable APCs
-
When APCs are emagged they now stay unlocked
-
Re-did a shit tonne of admin stuff
-
New admin system is pretty much finished
-
FINALLY backpacks can now be looked in while on the ground.
-
-
-
Tuesday, February 24, 2009
-
-
Ghosts no longer able to open secret doors
-
Suicide vests now work as armor
-
Blood no longer comes out of the guy if you pull him due to lag
-
Admin panel has been touched up to include html tables
-
Mines now added, only spawnable right now however
-
Fixed the syndicate nuclear victory bug
-
Wizard now spawns with wizard outfit which he must wear to cast spells
-
Blood bug fixes
-
Fixed a retarded bug that meant I didn't have the power to kick admins
-
THUNDERDOME!
-
Several new facial hair options and a bitchin' mohawk
-
Blood by Culka
-
Nuke disk now spawns in ALL game modes so that during secret rounds the syndicate now have the element of surprise!
-
-
-
Saturday, February 22, 2009
-
-
Implemented unstable's "observer" mode
-
Halerina's wizard mode
-
Non-interesting stuff
-
Began addition to the new admin system - right now only available to coders for testing
-
Admins can now click on the multikeying offenders name to pm them, instead of hunting for them in the pm list
-
Halerina's chemistry system
-
You can now deathgasp without being dead, hopefully so people can fake their own deaths.
-
Redid Medlab
-
New chemist job
-
-
-
Thursday, February 19, 2009
-
-
New DNA system. 200th Revision special.
-
Various bugfixes
-
Maze
-
-
-
Monday, February 17, 2009
-
-
Added a new game mode into rotation.
-
Added an AI satellite
-
Lockdowns can be disabled with the communications console
-
Prison shuttle can be called on the comm console, but only if its enabled by admins first
-
When you slip into space you'll have a 50% chance of going to z=4 instead of z=3
-
-
-
Friday, February 13, 2009
-
-
Fixed Cakehat
-
Dead people can now see all turfs, mobs and objs not in their line of sight.
-
Modified the map slightly
-
Stungloves can now be "made"
-
Flashes can now have their bulbs burning out.
-
Batons can now be turned on and off for different effects. They also now have 10 uses before they need to be recharged.
-
-
-
Tuesday, February 10, 2009
-
-
Fixed all the autoclose bugs
-
Due to it being myself and Keelin's 100th revision we have added a super-secret special item. Don't ask because we won't tell! Figure it out!
-
-
-
Sunday, February 8, 2009
-
-
Modified doors in engineering so that they do not autoclose - Autoclose now handled by a variable
-
Fixed toxin researcher spawn bug
-
Changed the "You hear a faint voice" message.
-
Gave the host new commands to disable admin jumping, admin reviving and admin item spawning
-
Fixed some airlock autoclose bugs
-
Changed some doors to not autoclose.
-
Nerfed the toolbox down.
-
-
-
Friday, February 6, 2009
-
-
Doors now close after 15 seconds
-
Fixed some p cool bugs
-
Cakehat
-
Added another suit
-
Walls now take 5 seconds to build
-
Added sam0rz, thesoldierlljk and kelson's revolution gamemode. Thanks guys!
-
-
-
Thursday, February 5, 2009
-
-
Fixed a couple of bugs
-
Improved bar ;)
-
Beer acts like pills and syringes
-
-
-
Tuesday, February 3, 2009
-
-
Added 'Make AI' Option for Admins
-
Added dissolving pills in beer (cyanide and sleeping pills)
-
Modified engine AGAIN, but personally I love it now
-
-
-
Monday, February 2, 2009
-
-
Moved bar due to popular demand
-
Captains room is now a security checkpoint
-
Assistants now have access to maint tunnels again
-
Courtroom
-
Engine has been redone slightly to make it easier to load
-
Nerfed beer a lot more
-
-
-
Saturday, January 31, 2009
-
-
Added a bartender job + Bar
-
Captains panic room
-
Voice changer traitor item
-
Bartender suit
-
Made taking a table apart take longer
-
Balanced beer a bit more.
-
Assistants can no longer open external air locks and maint tunnels, sorry guys. Get a job you bums.
-
Engineers CAN access external air locks and maint tunnels.
-
Fixed traitor AI bug
-
-
-
Thursday, January 29, 2009
-
-
Added traitor menu for admins - The ability to turn people into "traitors" as well as keep track of their objectives.
-
Implemented Keelins revive system - Primary Admins can now revive people.
-
Moved and redid security to prevent clusterfucks and everyone just crowding around security.
-
Redid the brig to make it bigger and so that people can break others more easily out since it isn't right in security.
-
Moved and redid captains quarters/heads quarters. Captains made much smaller and heads is now more of a meeting room.
-
Added Stungloves and an axe - right now only admin spawnable.
-
Implemented Persh's adminjump back in - admins can now jump to set locations.
-
Added a feature that if someone logs off their character moves around and says things - Change what they say from the config/names/loggedsay.txt file.
-
Added in adminwho verb - tells the user if there are any admins on and who they are.
-
-
-
Saturday, January 10, 2009
-
-
Freedom implant has been changed so that it will have a random emote associated with it to activate it rather than always chuckle.
-
There is now a pinpointer tool for use in Nuclear Emergency. It works similar to the existing locator, in that it will detect the presence of nuclear disks and in what direction it is.
-
The nuke being detonated in Nuclear Emergency should now properly end the game.
-
Spacesuits now cause you to move slower when not in space.
-
Syndicate in Nuclear Emergency now have syndicate-themed spacesuits.
-
Blob mode should properly end now.
-
-
-
Wednesday, January 7, 2009
-
-
Syndicate Uplink has been changed up, allowing traitor more freedom in his ability to be... traitorus.
-
Syndicate Uplink can now spawn a ammo-357, syndicate card, energy sword, or timer bomb.
-
Fixed an issue where Syndicate Uplink looked different than a normal radio.
-
-
-
Monday, January 5, 2009
-
-
You can choose to be a nudist now.
-
Facial hair!
-
Added constructable flamethrowers.
-
Redid internal naming scheme for human/uniform sprites.
-
Helmet visors are now translucent.
-
Held item graphics corrected for basically everything, internally only uses one dmi file instead of two.
-
Config settings reorganized for.. organization.
-
Seperated male and female names.
-
Females have pink underwear.
-
Guests can no longer save/load profiles, as this just created useless profiles that weren't used again.
-
+
+
01 April 2015
+
ACCount updated:
+
+
You can now replace test range firing pins with any other pin.
+
The plasma cutter is now a gun that comes in the mining vending machine. Use it to mine fast but expensively.
+
It's also cheaper in RnD, and there's an advanced version of it there too. Yay!
+
Ripleys and Firefighters have been buffed significantly. Go wild.
+
Precious mesons are cheaper in RnD.
+
Now you can mine bluespace crystals. They are extremely rare.
+
You can make remote bombs out of gibtonite. Figure out how.
+
Gibtonite is a bit less stable. Do not hit it with anything heavy.
+
+
Fayrik updated:
+
+
Added a bowman headset for death squads and ERTs.
+
Deathsquad radio channel is now called the Centcom radio channel.
+
Fixed the admin buttons not spawning ERT Medic gear properly.
+
Expanded Emergency Shuttle dock to allow for more round end griff.
+
All 7 ERT members now spawn geared, not just the first 4.
+
Spawning an ERT or Deathsquad no longer stops a second ERT or Deathsquad from being spawned.
+
Pulse rifles now have 80 shots, not 40.
+
Deathsquads now use loyalty pinned Pulse rifles.
+
+
Gun Hog updated:
+
+
NTSL has been updated! It can now read and modify verbs (says, yells, asks)! You can set your own using $say, $yell, $ask, and $exclaim variables in your scripts.
+
NTSL can now also modify the font, italics, and bolding of radio messages in four ways! In a script, place $loud, $wacky, $emphasis, and/or $robot in a vector to $filters.
+
+
Jordie0608 updated:
+
+
The admin's old player panel has been removed and the new one has taken it's name.
+
+
NikNakFlak updated:
+
+
Adds the ability to shave corgis.
+
+
Sawu updated:
+
+
Spraycans for Revheads: instant use crayons that can be used on walls.
+
Spraycans can be used as ghetto pepper spray and leave their target with colored faces that can be removed with space cleaner.
+
+
Xhuis updated:
+
+
Area ambience and ship ambience are now separate toggles. If you want one type of ambience but not the other, you only need to switch off the type you don't want.
+
Added cigar cases. They come in three flavors and can be bought from cigarette machines.
+
+
+
30 March 2015
+
AnturK updated:
+
+
Added new wizard spell : Lightning Bolt
+
+
Dannno updated:
+
+
Added a hammer and gavel. Court is now in session.
+
+
Iamgoofball updated:
+
+
You can now *flip.
+
Objects now spin when thrown. Please report any oddities.
+
+
RemieRichards updated:
+
+
Monkeys can now wear ANY mask, not just ones they had specific icons for.
+
Monkeys can now wear HATS, That's right, Monkey Hats!
+
+
Szunti updated:
+
+
Mining mobs vision adapted to darkness of the asteroids.
+
+
phil235 updated:
+
+
All slimes are now simple animals instead of carbon life forms.
+
Remove crit status from brain and slimes.
+
Simple animal aliens can now see in the dark like regular aliens.
+
+
pudl updated:
+
+
The detective's forensic scanner has been resprited.
+
So has the cargo tagger.
+
And the RPED.
+
+
tedward1337 updated:
+
+
Admins now have a button to use Bluespace Artillery at their will.
+
+
xxalpha updated:
+
+
You will no longer be slowed down by anything if you use a jetpack in zero gravity.
+
Engineering hardsuits come with an inbuilt jetpack. Requires an internals tank in suit storage and is slower than a normal jetpack.
+
+
+
25 March 2015
+
AnturK updated:
+
+
Added an arrow graffiti, made arrow and body graffiti face the same way as the user
+
New changeling ability : Last Resort : Explode and infect corpses if the situation looks grim
+
+
Fayrik updated:
+
+
Deleted the toy crossbow that wasn't a gun, but flung projectiles like a gun. Why was that even a thing?
+
Added an abstract new type of reusable ammo.
+
Added three new foam force guns and a foam force crossbow, all of which take the new foam dart ammo.
+
Added foam force dart boxes to the autolathe, and two crates orderable from cargo containing the new guns.
+
Added two new donksoft guns, the syndicate's own brand of Foam Force. Available in all good uplinks!
+
New crossbow can be won as an arcade prize.
+
All kinds of ammo containers can now be used to rapidly collect live ammunition on the ground.
+
Speedloaders and traditional ammo boxes can now be restocked.
+
Security bots now react to being shot.
+
+
MrPerson updated:
+
+
Added a preference toggle for hearing instruments play as suggested by PKPenguin321.
+
+
Pennwick updated:
+
+
Added a buildable Chem Master, board is in Circuit Imprinter.
+
+
Wjohnston updated:
+
+
Finally fixed the gulag shuttle missing a redemption console.
+
+
+
24 March 2015
+
Cheridan updated:
+
+
Adds pet collars. Use them to rename pets; Can also be worn if you're a weirdo.
+
Adds the matyr objective to be dead at the round's end. You can get this objective if you have no others that would need you to survive.
+
+
Dannno updated:
+
+
GAR glasses return. Yes, I got permission. Just who the hell do you think I am?
+
+
Incoming5643 updated:
+
+
Summon Events has been reworked to be a bit less manic.
+
Summon Events will turn off if the wizard and all apprentices die. Any lingering effects are not reversed however.
+
Improving Summon Events will reduce the average time between events, but not the minimum time between events.
+
Departmental Uprising has been made admin only for being an RP heavy event that creates antags in an enviroment that doesn't really support that.
+
+
Mandurrrh updated:
+
+
Added a cyborg upgrade module for the satchel of holding.
+
+
Miauw updated:
+
+
Added a fancy white dress and a jester outfit. Sprites by Nienhaus.
+
+
Steelpoint, RemieRichards, and Gun Hog updated:
+
+
Nanotrasen Security has authorized modifications to the standard issue helmets for officers. These helmets are now issued with a mounted camera, automatically synced to your assigned station's camera network. In addition, the helmets also include mounting points for the Seclite model of flashlights, found in your station's security vendors.
+
Nanotrasen Security helmets are designed for easy repairs in the field. Officers may remove their Seclite attachment with any screwdriver, and the camera's assembly may be pried off with a crowbar. The assembly is also compatible with analyzer upgrades for scanning through the station's structure, and EMP shielding is possible by applying a sheet of plasma.
+
+
+
22 March 2015
+
Iamgoofball updated:
+
+
Fixes Morphine not knocking you out.
+
Hotline no longer exists. Poppies now have Saline-Glucose Solution.
+
Muriatic Acid, Caustic Soda, and Hydrogen Chloride have been removed, along with the secondary meth recipe. The original recipe still works.
+
Foam now produces more bang for your buck when mixed. Your foam grenades should be larger now to account for the slowdown.
+
Adds Stabilizing Agent. This chemical will prevent the effects of certain reactions from taking place in the container it is in, such as Smoke Powder and Flash Powder, allowing you to get the raw forms of the powders for heating up later.
+
Smoke Powder and Flash Powder can now be mixed along with Stabilizing Agent in order to produce a powder version of the effects. You can then heat these powders to 374K to get the effects.
+
Mixing them without Stabilizing Agent gives the default effects.
+
Adds Sonic Powder, a chemical that deafens and stuns within 5 tiles of the reaction. It also follows the reaction rules of the other 2 powders.
+
Liquid Dark Matter and Sorium also now work like the powders do.
+
CLF3 now has a higher chance of making plating tiles, and the burn damage caused by it scales based on your fire stacks.
+
Adds Pyrosium. This reagent heats up mobs by 30 degrees every 3 seconds if it has oxygen to react with. Useful for when you want a delayed mix inside of someone.
+
Adds Cryostylane. This reagent does the opposite of Pyrosium, but it still requires oxygen to react with.
+
Adds Phlogiston. This reagent ignites you and gives you a single fire stack every 3 seconds. Burn damage from this chemical scales up with the fire stacks you have. Counter this with literally any chemical that purges other chems.
+
Smoke now transfers reagents to the mob, and applies touch reactions on them. This means smoke can run out of reagents, no more infinite acid smoke. It also only works on mobs, use the new Reagent Foam(tm) for your hellfoam needs. I suggest a CLF3 Fluoroacid Black Powder mix.
+
Added a derelict medibot to the code, will place a few on the derelict when this PR is merged as to prevent mapping conflicts.
+
Adds Initropidril. 33% chance to hit with 5-25 TOX damage every 3 seconds, and a 5-10% chance to either stun, cause lots of oxygen damage, or cause your heart to stop. Get it from the traitor Poison Kit.
+
Adds Pancuronium. Paralyses you after 30 seconds, with a 7% chance to cause 3-5 loss of breath. Get it from the traitor Poison Kit.
+
Adds Sodium Thiopental. Knocks you out after 30 seconds, and destroys your stamina. Get it from the traitor Poison Kit.
+
Adds Sulfonal. +1 TOX per 3 seconds, knocks you out in 66 seconds. Mix it with Acetone, Diethylamine, and Sulfur.
+
Adds Amantin. On the last second it is in you, it hits you with a stack of TOX damage based on how long it's been in you. Get it from the traitor Poison Kit.
+
Adds Lipolicide. +1 TOX unless you have nutriment in you. Mix it with Mercury, Diethylamine, and Ephedrine.
+
Adds Coiine. +2 TOX and +5 loss of breath every 3 seconds. Get it from the traitor Poison Kit.
+
Adds Curare. +1 TOX, +1 OXY, paralyzes after 33 seconds. Get it from the traitor Poison Kit.
+
Adds a reagent_deleted() proc to reagents for effects upon the last time it processes in you, like with Amantin.
+
Neurotoxin required temperature for mixing has been set to 674K, it was 370K for some reason.
+
+
xxalpha updated:
+
+
Added Cybernetic Implants: Security HUD, Medical HUD, X-Ray, Thermals, Anti-Drop, Anti-Stun. All implants are researchable and producible by R&D and Robotics.
+
+
+
21 March 2015
+
Chocobro updated:
+
+
Checkered skirts can be adjusted to be worn shorter.
+
+
Incoming5643 updated:
+
+
Player controller uplifted mobs will be attacked by gold core mobs of different species.
+
+
Jordie0608 updated:
+
+
The backup shuttle will no longer be called if the emergency shuttle is coming.
+
+
MMMiracles updated:
+
+
Adds 3 new bear-based foods.
+
Beary Pie -1 Plain Pie, 1 berry, 1 bear steak
+
Filet Migrawr - 5u of Manlydorf, 1 bear steak, 1 lighter(not used in crafting)
+
Bearger - 1 bun, 1 bear steak
+
+
Phil235 updated:
+
+
Silicons are no longer blinded by welding.
+
+
Xhuis updated:
+
+
A new software update has given the Orion Trail game a cheat code menu. While you can't access this normally (that's for a later software update), maybe an emag could do it...?
+
+
Zelacks updated:
+
+
The chemical implant action button should now correctly inject all chemicals when pressed.
+
+
pudl updated:
+
+
Adds normal security headsets to security lockers.
+
+
xxalpha updated:
+
+
Fixed cables on catwalks being destroyed when creating plating floor.
+
Portable machines can be mounted on any turf that could hold a cable.
+
+
+
19 March 2015
+
Dannno updated:
+
+
Adds a few cosmetic glasses to the autodrobe and clothing vendors.
+
+
Gun Hog updated:
+
+
Explosion relics will now destroy themselves in their own explosion.
+
The Experimentor can no longer re-roll the function of an already scanned relic.
+
+
Incoming5643 updated:
+
+
Ghosts are now asked if they want to become a sentient slime.
+
+
JJRcop updated:
+
+
Adds the :rollie: and :ambrosia: emojis.
+
+
MMMiracles updated:
+
+
ERT suits now use helmet toggling like hardsuits.
+
+
Menshin updated:
+
+
Fixed mechs ballistic guns not firing bullets.
+
+
Xhuis updated:
+
+
Allows alt-clicking to adjust breath masks, flip caps, and unlock/lock closets.
+
+
xxalpha updated:
+
+
Fixed jetpack trail animation.
+
+
+
17 March 2015
+
CandyClownTG updated:
+
+
Syndicate cigarettes' non-healing Doctor's Delight replaced with Omnizine.
+
+
Fayrik updated:
+
+
Expanded the Centcom area for better station-Centcom interaction.
+
+
Iamgoofball updated:
+
+
Acid blobs are less likely to melt equipment.
+
+
MMMiracles updated:
+
+
Adds the ability to spike bears on a meatspike for their pelt. Yields 5 meat instead of the usual 3 from a knife.
+
+
Thunder12345 updated:
+
+
Two new shotgun shells. Ion shell, shotgun version of ion rifle, built with 1 x techshell, 1 x ultra microlaser, 1 x ansible crystal.
+
Laser slug, regular laser shot in a shotgun shell, build with 1 x techshell, 1 x high power microlaser, 1 x advanced capacitor.
+
Improvised shotgun shells can now be constructed with 1 x grenade casing, 1 x metal sheet, 1 x cable, 10 units welding fuel. Can be packed with a further 5 units of gunpowder to turn it into a potentially lethal diceroll.
+
FRAG-12 shotgun shells are no longer duds, now explosive.
+
+
phil235 updated:
+
+
Makes the message when you're attacked slightly bigger for better visibility.
+
+
+
15 March 2015
+
Ahammer18 updated:
+
+
Adds ass photocopying for drones
+
+
Gun Hog updated:
+
+
Nanotrasen has approved distribution of Loyalty Implant Firing Pin designs. They can be fabricated with sufficient research into weapons technology. If inserted into a firearm, it will only fire upon successful interface with a user's Loyalty Implant.
+
+
Incoming5643 updated:
+
+
Utilizing a new magic mirror found in the den, wizards can now customize their appearance to a high degree before descending upon the station.
+
+
Miauw updated:
+
+
Firing delay for tasers has been shortened.
+
+
RemieRichards updated:
+
+
Beginning major rework and refactor of messy Ninja code.
+
Roleplay based, kill deathsquad and kill alien queen objectives removed.
+
Kamikaze mode removed.
+
AIs and pAIs can no longer be integrated into a ninja suit.
+
Energy blade power replaced by an energy katana that can be dropped but is more deadly.
+
Energy katana can emag any object it hits.
+
+
phil235 updated:
+
+
Bookbags have a new sprite and can hold bibles.
+
Adds egg yolk reagent. You can break eggs in open reagent containers or blend them to get egg yolk.
+
Changes how doughs are made. 10water+15flour chem reaction for dough. 15flour+15eggyolk+5sugar for cake batter (or alternatively soymilk instead of the eggyolk for vegans).
+
Change customizable snack max volume to 60 and buffs nutriment amount in doughs.
+
Doors can damage mechs by crushing them now.
+
The tablecrafting window now also shows partial recipes in grey, if there's some of the things required for the recipe on the table. Recipe requirements are now also shown next to the names of recipes listed in the window.
+
+
+
11 March 2015
+
MMMiracles updated:
+
+
Adds a new vending machine, the Liberation Station. Consult your local patriot for more details.
+
Adds a patriotic jumpsuit and freedom sheets. Consult your local patriot for more details.
+
+
Miauw updated:
+
+
Added a toggle for ghosts that allows them to hear radio chatter without having to hover near intercoms/over people with headsets.
+
+
+
10 March 2015
+
Ahammer18 updated:
+
+
Adds the Slimecake and Slimecake slice.
+
+
AnturK updated:
+
+
Non-player Alien Queens/Drones now spread weeds. Can also lay eggs when enabled.
+
+
Incoming5643 updated:
+
+
Nanotrasen scientists have recently reported mutations within the pink family line of slimes. Station scientists are encouraged to investigate.
+
+
Paprika updated:
+
+
Reworked reskinning and renaming guns, and added the ability to reskin the bartender's shotgun. Click it with an empty active hand to reskin. Warning, you can only do this once! Rename by hitting it with a pen. This also works with miner shotguns now.
+
+
RemieRichards updated:
+
+
Embedding from explosions has been tweaked, they will always throw objects fast enough to get embedded if they can.
+
Damage taken when embedded spears and throwing stars fall out has been increased.
+
Damage dealt by objects getting embedded has been decreased.
+
+
Sometinyprick updated:
+
+
The Horsemask spell has now been altered, instead of just a horse mask it will now choose between three animal masks including the horse mask.
+
+
Xhuis updated:
+
+
You can now burn papers using lighters, welders, or matches. Useful for secret messages.
+
+
phil235 updated:
+
+
Airlocks with their safety on no longer closes on dense objects (like mechs or tables).
+
Aliens, slimes, monkeys and ventcrawler mobs can now climb into disposal. Cyborgs and very large mobs can no longer climb into it.
+
Stuffing another mob in a disposal unit is now only done via grabbing.
+
Coffee now causes jittering only when you overdose on it.
+
Turrets now target occupied mechs and don't target drones.
+
+
+
08 March 2015
+
Dannno updated:
+
+
Added more barsigns, sprites by yours truly.
+
+
Ikarrus updated:
+
+
Gang mode has been reworked to functionality without some of it's features.
+
Gang membership visibility, conversion pens and weapons are removed until fixed.
+
Gang bosses have been given uplinks to use in the meantime.
+
Chance of deconversion from repeated head trauma has been increased.
+
+
Incoming5643 updated:
+
+
The skeleton and zombie hordes have been quelled, at least for the time being.
+
+
Mandurrrh updated:
+
+
Added the ability to stop table climbers by clicking the table if present.
+
+
Paprika updated:
+
+
Labcoats and uniforms no longer use action buttons and object verbs to adjust their aesthetic style. Alt click them to adjust.
+
Alt click a PDA with an ID inside to eject the ID.
+
Renamed 'remove ID' verb to 'eject ID' so it's not immediately next to 'remove pen'.
+
Rebalanced the flash protection of thermal scanners. Syndicate thermals have the same flash weakness as the meson scanners they come disguised as.
+
+
Xhuis updated:
+
+
Adds the Adminbus and Coderbus bar signs.
+
Added a mining satchel of holding to R&D that can hold an infinite amount of ores. It requires gold and uranium, in addition to a decently high bluespace and materials research.
+
+
phil235 updated:
+
+
Harvesting meat from dead simple animals now takes time, has a sound, and can be done with any sharp weapon.
+
+
pudl updated:
+
+
Updated the sprite for Fire extinguishers.
+
+
xxalpha updated:
+
+
Disposable lighters now come in more colors of the rainbow.
+
+
+
05 March 2015
+
Jordie0608 updated:
+
+
Server hosts can set a config option to make an unseen changelog pop up on connection.
+
+
MrPerson updated:
+
+
Expanded the on-screen alerts you get on the top-right of your screen. You can shift-click them to get a description of what's wrong and maybe how to fix it.
+
Getting buckled to something will show an alert of what you're buckled to. If you're not handcuffed, you can click the alert to unbuckle.
+
+
RemieRichards updated:
+
+
Added Necromantic Stones as a buyable item for wizards
+
Necromantic Stones can cause up to 3 people to rise up as Skeleton Thralls bound to the wielder of the stone
+
Skeleton Thralls have a 33% Chance to drop all their gear on the floor and autoequip a more fitting thematic uniform
+
Allows the blob to reroll it's chemical for a cost.
+
+
phil235 updated:
+
+
Adds a lot of new plants to botany (red beet, parsnip, snap corn, blumpkin, rice, oat, vanilla, steel cap, mimana, blue cherries, holy melon, geranium, lily, sweet potato) and two new reagents (vanilla, rice).
+
Fixes taser stun duration being longer due to jitter animation.
+
+
+
03 March 2015
+
Delimusca updated:
+
+
Improvised heating! use lighters or other heat sources to heat beakers, bottles, etc.
+
+
Gun Hog updated:
+
+
We have purged the xenomicrobes contamination in the E.X.P.E.R.I-MENTOR using sulfuric acid. Take care not to be exposed should it leak!
+
+
Iamgoofball updated:
+
+
Colorful reagent can be washed off with water now.
+
Oxygen blob deals breath loss damage.
+
Radioactive blobs irradiate for more.
+
+
Ikarrus updated:
+
+
Antagonist roles are now restricted by player client age.
+
+
Jordie0608 updated:
+
+
The server's current local time is now displayed in status panel in ISO date format (YYYY-MM-DD hh:mm)
+
+
Mandurrrh updated:
+
+
Added the ability to see pda messages as a ghost with GHOSTWHISPER toggled on.
+
Removed the degrees symbol from Kelvin units on atmos meters because not gramatically correct
+
+
RemieRichards updated:
+
+
Drones now see either Static, Black icons or Roguelike style lettering instead of actual mob icons
+
Icons for the above, from VG's SkowronX
+
Many icon procs for the above, from VG's ComicIronic
+
Drones moved into their own folder
+
Drone.dm replaced with seperate files for each functionality
+
Drones now have 2 skins to choose from, Maintenance Drone (Current drones) and Repair Drone (A modified VG/Bay sprite)
+
Much more support for alternate skins on drones
+
+
Xhuis updated:
+
+
Added four new emoji: :1997:, :rune:, :blob:, and :onisoma:.
+
+
xxalpha updated:
+
+
Added a surgery table and tools to the Nuke Ops ship.
+
Added Engineering Scanner Goggles to Engivend, Engineering Secure Closets and Research.
+
Doubled T-ray scanner range.
+
+
+
01 March 2015
+
Dannno updated:
+
+
Resprited all owl items. Wings are now a seperate toggleable suit item.
+
Added The Griffin costume, arch nemesis of The Owl. Found in the autodrobe.
+
Added admin-only Owl hardsuit. Only for the most vengeful souls.
+
Added Owl and Griffin merchandise; The Nest barsign, Owl/Griffin toys and posters.
+
+
Iamgoofball updated:
+
+
Fixes the recipe for meth.
+
Fixes sorium blobs flinging themselves.
+
+
Incoming5643 updated:
+
+
On servers where malfunction, wizard, and blob rounds do not end with the death of the primary antagonist, new antagonists from traitor and changeling derivative modes will now sometimes spawn. This depends on the state of the station, the state of the crew, and the server's gamemode probabilities. Server owners should consider revising their configuration choices
+
If you would like to opt out of this midround chance, a new toggle to disable it can be found in your preferences
+
+
Mandurrh updated:
+
+
The barsign can be emagged and EMP'd; EMP'd signs can be repaired with a screwdriver and cables.
+
+
Menshin updated:
+
+
Fixed the protolathe not requiring displayed amount of materials to make items.
+
+
MrStonedOne updated:
+
+
Adds a Panic Bunker!
+
This will prevent any player who has never connected before from connecting. (Admins are exempt)
+
Can be enabled per-round by any admin with +SERVER or for longer periods via the config.
+
Also adds some other config options relating to new joins, server operators should consult their configuration file for more details.
+
Fixes toggle darkness vision hiding other ghosts.
+
Admins fucking around with ghost icon and icon states in VV are going to want to change the icon and icon state of the ghostimage var of the ghost to change the image shown to ghosts who have darkness disabled. doing this via mass edit is not yet supported.
+
New ghost verb, toggle ghost vision. Switches the ghost's invisibility vision between that of a ghost and that of a human. So ghosts can hide other ghosts and in general get the old behaviour of toggle darkness.
+
+
Paprika updated:
+
+
Messages with two exclamation marks will be yelled in bold.
+
+
Phil235 updated:
+
+
Cultists can't summon narsie until their sacrifice objective is complete.
+
+
Razharas updated:
+
+
Vines now can grow on any tiles that is not dense, including space, shuttles, centcomm tiles and so on.
+
Added 'Bluespace' mutation to vines, makes them be able to grow through absolutely everything.
+
Added 'Space Proofing' mutation to vines, after they grow if the tile under them is space it will become special vinetile, which is just resprited regular floor, if the vine on the vinetile dies the vineturf changes back to space.
+
Made vines spreading speed depend on the seed's production, can be both slower and faster than current.
+
Made vines mutation chance be 1/10th of the potency of the seed it is spawned from.
+
Special chemicals added to vine seeds durning their growth can increase/decrease their potency and productivity.
+
Special chemicals now can remove good, bad or neutral mutations from vine seeds while they are growing, cultivating actually helpful vines is now possible.
+
Plant analyzers now show the vine seeds mutations.
+
Buffed numbers in some of the more useless mutations.
+
+
Steelpoint updated:
+
+
Reworked and expanded areas of the derelict station. Featuring a repairable gravity generator, turret filled computer core and functional solars.
+
+
Vekter updated:
+
+
Chemists, Geneticists, Virologists, Scientists, and Botanists will now spawn with alternative backpacks and satchels in their lockers. Try them out!
+
+
+
25 February 2015
+
Cheridan updated:
+
+
Foam now carries reagents, similarly to smoke. Have fun with that.
+
+
Dannno updated:
+
+
Altered the sprite for security hardsuits. The old sprite is now a HoS specific hardsuit.
+
+
Delimusca updated:
+
+
Turret's guns replaced by a heavy energy cannon.
+
Firing a heavy weapon sindlehandedly can make it recoil out of your hands.
+
+
Gun Hog updated:
+
+
Nanotrasen has released new design specifications for the prototype combination of standard night-vision equipment and Optical Meson Scanners. We believe that this will address the concerns many Shaft Miners have concerning visiblity in a potentially unsafe environment.
+
Fixed malf AI's turret upgrade power not working.
+
+
Iamgoofball updated:
+
+
You can now go into hyperglycaemic shock from having over 200u of Sugar in your body at once. Treat it with Insulin.
+
Insulin has been added. Use it to treat hyperglycaemic shock. You can get it from medical vendors and the Cargo medicine supplies crate.
+
Medvendor contents have been reduced from medical having everything they need at roundstart level, and toxin bottles are now back in the vendor.
+
Medical Analyzers now have 2 modes, Medical Scan and Chemical Scan. You can swap between modes by clicking on it in hand.
+
Medical scan is default behavior. Chemical scan shows all the reagents inside the mob at the time, along with any addictions the mob has. It will show if a reagent is overdosing.
+
Tobacco and Space Tobacco have Nicotine in them now.
+
Adds Methamphetamine to the game. Mix it with Ephedrine, Iodine, Phosphorous, and Hydrogen at 374K.
+
Or, look around in Maintenance for Muriatic Acid, Caustic Soda, and Hydrogen Chloride. Recipes are also available for these.
+
Adds Saltpetre. Mix it with Potassium, Nitrogen, and Oxygen.
+
Adds Bath Salts, mix it with Bad Food, Saltpetre, Nutriment, Space Cleaner, Universal Enzyme, Tea, and Mercury at 374K.
+
Adds Aranesp. Mix it with Epinephrine, Atropine, and Morphine.
+
Adds Hotline. Get it from Poppy plants.
+
Strange Reagent revival was changed. It now won't gib but won't do anything above a 100 BRUTE or BURN threshold.
+
Carpet now applies carpet tiles to whatever flooring it hits. Mix it with Space Drugs and Blood.
+
Colorful Reagent colors have been improved drastically and now are more varied and nicer looking.
+
Corn Starch has been added. Get it from juicing Corn.
+
Corn Syrup has been added. Mix it with Corn Starch and S-Acid.
+
Corgium has been added. Mix it with Nutriment, Colorful Reagent, Strange Reagent, and Blood at 374K.
+
Adds Quantum Hair Dye. Mix it with Colorful Reagent, Radium, and Space Drugs.
+
Adds Barber's Aid. Mix it with Carpet, Radium, and Space Drugs.
+
Adds Concentrated Barber's Aid. Mix it with Barber's Aid and Unstable Mutagen.
+
Re-adds Chlorine Trifluoride. Mix it with Chlorine and Fluorine at 424K.
+
Adds Black Powder. Mix it with Saltpetre, Charcoal, and Sulfur. Ignite it for an explosion at 474K
+
Finally nerfs Salglu Solution. It's no longer tricord 2.0.
+
Salt recipe is now Water + Sodium + Chlorine due to problems with recipe mixing.
+
+
Incoming5643 updated:
+
+
A new event has been added to the wizards summon events spell that gives the game a distictively more roleplaying flair.
+
Optional population cap functionality has been added to the game. By default all caps are disabled. Server owners should refer to config.txt to see their list of options.
+
Medical borgs now have access to a deployable onboard roller bed. Should the bed become lost, any roller bed may be used in its place.
+
+
Mandurrh updated:
+
+
Added interchangeable bar signs
+
Added bays fountain machines for the bar. Soda and Beer. Allowing bartenders to have easier access to mix drinks and making more to make their job a little less boring
+
Added color tiles to the light up floor tiles.
+
Added metal, glass, and cable to bar back room at round start for bartender to make colored light dance floors.
+
Fixes clown cuffing both mobs and two pairs of cuffs appearing.
+
Fixes clown not being able to use his stamp as antag so no meta.
+
Fixes sleepypen bug. Instead of only dispensing 50u it dispenses the full 55u.
+
Fixed silicons not being able to pass through holotape.
+
+
Menshin updated:
+
+
Getting into regenerative statis now puts the changeling into unconscious state.
+
Fixed changeling 'Revive' not working.
+
+
Paprika updated:
+
+
Added an assistant cap, unlimited by default.
+
Batons now slowly lose power when left on.
+
Batons, flashbangs and handcuffs can be seen when stored in a belt.
+
Computers now emit light when they're on or bluescreened.
+
Added hotkeys for OOC and custom emotes. Press O and M when in hotkey mode. Remember not to ick in ock!
+
Added megaphones for the HoS, HoP, and QM.
+
Goliath tentacles no longer stun you if you cross them. They will only stun you if you don't move away from them in time. They can do up to 15 damage now, in addition to stunning you.
+
Changed a lot of mining gear and prices. The resonator can now have up to 3 fields active at once, but the fields need to detonate before they crush rocks. Tweak the time before the fields detonate with a screwdriver and upgrade the amount of fields at once with sheets of diamond.
+
Overhauled straight mining tools. The sonic jackhammer is now the wall-crushing tool, the diamond drill simply mines really fast. Borgs can now get upgraded with the diamond mining tool with only a single sheet of diamond and an upgrade module, they don't need illegal tech modules.
+
Mining drills and the sonic jackhammer now draw power when they drill down mineral walls. They can be recharged using any type of weapon recharger or by replacing the cell using a screwdriver on them.
+
Space now emits a dim light to adjacent tiles.
+
Syndicate shotguns now start with buckshot instead of stunslugs.
+
Fixed welding tools using fuel when being switched on.
+
Added a firing delay between shooting electrodes.
+
Changed the stun revolver from a high-capacity taser to a disabler with extra capacity.
+
Added a unique stun pistol for the head of security. It is a double capacity taser with no shot delay and a loyalty pin, though it lacks a disable mode.
+
+
RandomMarine updated:
+
+
New brute treatment kit crate for cargo.
+
+
RemieRichards updated:
+
+
Explosions will now blow (throw) items away from the epicenter
+
Sharp items can now embedd themselves in a human's limbs
+
Embedded items have a chance to do some damage (based on w_class) each life() call
+
Embedded items have a chance to fall out doing some more damage, but removing the object
+
Adds a surgery to remove Embdedded objects
+
Adds throwing stars as an uplink item (100% chance to embedd)
+
Items placed on tables now center to exactly where you clicked.
+
+
Steelpoint updated:
+
+
SS13's Head of Security has received a new attempted 1:1 recreation of a Old Earth weapon, the Captain's Antique Laser Gun.
+
This gun features three firing modes, Tase, Laser and Disable. However this gun lacks the capability to recharge in the field, and is extreamly expensive.
+
INTERCEPTED TRANSMISSION REPORT: The Syndicate have expressed a interest in this new weapon, and have instructed field agents to do whatever they can to steal this weapon.
+
+
TheVekter updated:
+
+
Box's vault nuke has been replaced with immobile Nuclear Self-Destruct Mechanism.
+
Vault's new green floor tiles turn flashing red when Self-Destruct is activated.
+
+
Xhuis updated:
+
+
Syndicate contacts have discovered new ways to manipulate alarms. Malfunctioning AIs, as well as normal emags, can now disable the safeties on air and fire alarms.
+
When one emags an air alarm, its safeties are disabled, giving it the Flood environmental type, which disables scrubbers as well as vent pressure checks. Burn, pigs, burn!
+
When one emags a fire alarm, its thermal sesnsors are disabled. This will prevent automatic alerts due to the alarm being unable to recognize high temperatures.
+
Malf AIs gain two new modules, both of which disable ALL air alarm safeties and thermal sensors. These are separate abilities.
+
Air and fire alarms have notifications on their interface about the current safety status. One only has to glance at one to see that it's been tampered with.
+
+
phil235 updated:
+
+
Fixed arcade machines being usable from inside lockers to dupe rewards.
+
Silicons can no longer be grabbed by anyone. No more silicon choking
+
Custom food can now be renamed using a pen.
+
+
xxalpha updated:
+
+
Added new graffiti: cyka, prolizard, antilizard
+
+
+
18 February 2015
+
Dannno updated:
+
+
Added proper slurring when drunk.
+
+
Deantwo updated:
+
+
Updated the ChemMaster's UI to the station default along with some functionality improvements.
+
You can now spam the 'Ready' button as much as you want without it setting you 'Not ready' again.
+
+
Dorsidwarf updated:
+
+
Added the ability to call Central Command for the nuclear authentication codes. This requires a captain-level ID and admin approval, and warns the crew.
+
+
Gun Hog updated:
+
+
E.X.P.E.R.I-MENTOR meteor storm event replaced with a fireball that targets random nearby mob.
+
EMP event range reduced.
+
Self-duplicating relics limited to 10 copies.
+
Nanotrasen has released a firmware patch for the Ore Redemption Machine, which now allows the station AI, cyborgs, and maintenance drones to smelt raw resources without the need of a valid identification card installed.
+
Nanotrasen scientists have completed their designs for a lightweight, compacted anti-armor ion weapon, without loss of efficiency. In theory, such a weapon could easily fit within standard issue backpacks and satchels. With sufficient research into weapons technology and materials, Nanotrasen believes a working prototype can be fabricated.
+
+
Iamgoofball updated:
+
+
Buffs the shit out of Styptic Powder and Silver Sulf, they're now viable to use. Synthflesh is still good for general treatment, but overall Styptic and Silver Sulf deal with their respective damage types way better now.
+
Improves kinetic accelerator's mining strength.
+
The kinetic accelerator's cooldown can be decreased by tweaking the thermal exchanger with a screwdriver.
+
The range can be increased with plasma sheets
+
+
Incoming5643 updated:
+
+
Medical borgs have been given full surgery tools which they can't fail a procedure with.
+
Medical borgs also have rechargeable gauze.
+
Borg hypo's omnizine replaced with salbutamol, salglu and charcoal.
+
Slime and Jelly mutant races now actually contain slime jelly. While playing as these races you will naturally produce it internally from your nutrition, so be sure to stay well fed as losing too much is dangerous to your health! Beware of chems that clear toxins, as they can be rapidly fatal to you!
+
+
Jordie0608 updated:
+
+
Veil render and rifts changed to spawn with vars, rifts can be closed with a nullrod.
+
+
Lo6a4evskiy updated:
+
+
General record now contains a species field and a photo of the crewmember. Security record consoles allow you to update it with a photo taken in-game (with detective's camera, for example, or AI's).
+
Expanded functionality of security HUDs to display rank and photos.
+
Medical HUDs allow user to change others' mental and physical status. Currently has no gameplay effect other than changing records.
+
Medical HUDs can perform an examination at the distance, similrarly to how attacking yourself with help intent works. Has limited ability to detect suffocation and toxin damage.
+
+
Menshin updated:
+
+
Solar control computers remade with NanoUI
+
+
Paprika updated:
+
+
Added rnd requirements to borg upgrades and tweaked the prices.
+
Changed some clothing around. Added a turtleneck for the HoS. Removed 'civillian' armor and gave normal armor to the bartender and HoP. Added flash protection to the HoS' eyepatch, it functions like sunglasses now. Additionally, added the ability to store ammo on your shoulder holster as detective.
+
Removed the defib steal objective. Added a compact defibrillator for the CMO that equips to the belt. Removed defibrillators from RND. Removed the requirement for people to not wear armor to revive them with defibs. Doubled defib timers (10 minutes before permadeath).
+
Flashlights can be attached to pulse carbines.
+
Miners spawn with a brute patch.
+
Mining station's sleeper has been replaced with an IV drip and more medkits.
+
Added hard-light holotape. This holographic police or engineering hazard tape will block entry if you're running, so you need to walk to pass it. This is to help people not sprint into breaches or crime scenes.
+
Fixed being able to sling the strapless improvised shotgun on your back.
+
Reverted the price of the ebow to 12tc and removed the range limit of its bolts.
+
+
Sometinyprick updated:
+
+
Novely pig mask prize from arcade machines, can be toggled to make you squeal like a pig.
+
+
Steelpoint updated:
+
+
Security Cyborgs Advance Taser have been replaced with a Disabler.
+
+
TZK13 updated:
+
+
Adds plaid skirts and new schoolgirl outfits to the autodrobe
+
Novaflowers apply a firestack for every 20 potency they have.
+
You can now juice whole watermelons instead of slices.
+
Advancements in the field of virology by Nanotrasen's finest have uncovered three new disease symptoms for further research:
+
Ocular Restoration heals any eye trauma experienced by the infected patient.
+
Revitiligo increases the pigmentation levels of the infected patient's skin.
+
Spontaneous Combustion ignites the infected patient without warning.
+
+
Vekter updated:
+
+
Added shot glasses! Can be obtained from the Booze-o-Mat.
+
A new premium vodka has been added to the game, hidden in a special spot.
+
Due to constant attempts to break into the station's safe, the contents have been relocated and have not been replaced. We promise. There is absolutely NO reason to break into the vault's safe anymore.
+
Each shot glass now holds 15 units, and the drinker takes all 15 in one gulp instead of 5 at a time. This means you can mix certain shots (namely, the B-52 and Toxins Special) in a shot glass. Both drinks have been made exclusive to shot glasses and will no longer show their specific sprite in a normal glass.
+
Added new sprites for most alcohol in shot glasses. Everything else will show up as 'shot of... what?'.
+
Fixed shot glasses turning into regular glasses when filled.
+
Fixed tequila bottle sprites not showing up.
+
Fixed a typo!
+
Roboticists will now start with a full toolbelt instead of a toolbox.
+
+
drovidi updated:
+
+
Blobs who burst in space will be given a 30 second warning before dying.
+
If a blob does die from bursting in space, a new crewmember will be infected.
+
If there are no blobs or infected crew left after bursting, the round will now end properly, rather than becoming endless.
+
+
phil235 updated:
+
+
You no longer need to cut the syphon wire to deconstruct air alarms.
+
Cooking overhaul: microwave recipes are converted to tablecraft recipes.
+
Added customizable food (burger,sandwich,spaghettis,soup,salad,cake,bread,kebab,pie) with specific color depending on ingredients used.
+
Added new food ingredients (dough, flat dough, cake batter, pie dough, doughslice, bun, raw pastry base, pastry base, raw cutlet, cutlet, pizzabread). Dough is made by mixing egg and flour, then transformed by using knife, rollingpin, milk and microwave. Meat is now sliceable into rawcutlets (specific color for each meat type).
+
Changed food recipes a bit (replacing stuff with new food ingredients).
+
Repurposed the microwave to actually cook certain food items(no reagents required), renamed it microwave oven and added a power setting to it.
+
Bowl is no longer trash but a beaker-like container that is used in salad&soup recipes. Also, milk and soymilk cartons as well as flour packs are now condiment bottles.
+
Changed the hunger system a bit, sugar becomes a normal reagent again. Faster nutrition drain is now handled by a variable in junk food. Also added a slight buff to vitamin.
+
+
xxalpha updated:
+
+
You now hear the voice of the chosen deity in prayers.
+
+
+
05 February 2015
+
Danno updated:
+
+
Added suicide messages for all gun types and various different items.
+
+
Paprika updated:
+
+
Compacted ERT room by removing equipment rooms, filler rooms and pre-spawned mechs.
+
Rebalanced Pulse weapons to have more shots, faster charge, EMP immunity and modified sprites.
+
ERT spawns fully equipped with no-slowdown suits.
+
Added blood and bleeding. If you take enough brute damage on a certain limb, you will start bleeding, and will need to patch your bleeding with gauze from medbay. You can replace lost blood by eating nutritious food and waiting for your heart to reproduce it, or by getting a blood transfusion from an IV drip.
+
+
Vekter updated:
+
+
Added a toy red button to the Arcade cabinets.
+
+
+
04 February 2015
+
Danno updated:
+
+
Fixes dufflebag's storage capacity.
+
+
Deantwo updated:
+
+
Removes duplicate fluorine from Chemical Dispenser.
+
Added GoonChems to the Portable Chem Dispenser.
+
Added cancel buttons to the ChemMaster and CondiMaster when making bottles, pills, etc.
+
+
Delimusca updated:
+
+
Added new foam armblade toy to arcade prizes.
+
Slimes can drag themselves to mobs to start feeding.
+
Attacking another slime will pull them off their victim if they're feeding or steal some of their mass.
+
+
GunHog updated:
+
+
Five new silicon emotes: *buzz2, *chime, *honk, *sad and *warn
+
+
Incoming5643 updated:
+
+
New contraband crate containing fifty butterflies.
+
Shuttle will now always arrive, but won't launch in rounds where it would've been previously auto-recalled.
+
+
Menshin updated:
+
+
Fixed being unable to place cables on plating.
+
+
Paprika updated:
+
+
Helmets for hardsuits are stored inside the suit and cannot be equipped without the suit on.
+
EVA space suits are now lighter for faster movement but lack flash or welding protection, don't hide your identity and have less radiation protection.
+
+
Perakp updated:
+
+
Tweaked slowdown due to coldness when in in low gravity.
+
+
Steelpoint updated:
+
+
ERT base equipment rooms are now access restriction to each role.
+
ERT commander spawns wearing hardsuit for identification.
+
Two boxes of metal foam grenades added for ERT engineers.
+
Deep space sensors recently detected the derelict remains of the NTSS Omen, a outdated Medical Frigate thought lost, within close proximity to Space Station 13.
+
If any SS13 EVA Personal are able to locate this ship, they will be able to pilot it back to the station for recovery. The ship may also have cordinates for other locations.
+
+
Xhuis updated:
+
+
Changelings now have the ability Strained Muscles, which lets them move at inhuman speeds at the cost of rapid stamina damage. It costs one evolution points=.
+
Changelings now have the ability Augmented Eyesight, which lets them see creatures through walls and see in the dark. It costs two evolution points.
+
After many failed raids, the Syndicate have finally done something about poor communications between cyborgs and operatives.
+
Syndicate Cyborgs now come equipped with operative pinpointers, which will automatically point to the nearest nuclear operative.
+
Handheld crew monitors can now be stored in medical belts.
+
+
+
26 January 2015
+
Dannno updated:
+
+
Added fannypacks of multiple colors, 3 of which are accessible from the clothing vendor.
+
Added dufflebags to multiple lockers throughout the station and to the syndicate shuttle, more storage slots at the cost of move speed.
+
+
Deantwo updated:
+
+
Fixed some bugs in PDA's NTRC Chatroom feature.
+
You can now use PaperBBCode/PenCode when writing news articles on the newscaster.
+
PDA's Notekeeper now uses PaperBBCode/PenCode rather than HTML.
+
Scanning paper with your PDA is now more informative.
+
+
Firecage updated:
+
+
We, at NanoTrasen, have recently upgraded the Kitchen Spikes to include the skin of both Monkeys and Aliens when you harvest their flesh!
+
In other news, a station in a nearby Sector has been making monkey and xeno costumes. We are not sure why.
+
Added the ability to reform mineral floortiles back into sheets/bars
+
+
Ikarrus updated:
+
+
Added a new config option MINIMAL_ACCESS_THRESHOLD that can be used to automatically give players more access during low-population rounds. See game_options.txt for more information.
+
+
Lo6a4evskiy updated:
+
+
The kitchen now contains a brand new food cart. It can store foods and liquids, mix them into cocktails and dispense where needed. Food delivery has never been easier!
+
+
fleure updated:
+
+
Added briefcase filled with cash to uplink.
+
+
+
21 January 2015
+
Iamgoofball updated:
+
+
Addiction and Overdosing has been added to Chemistry.
+
Deal with Overdosing by purging the ODing chemical with Calomel or Penetic Acid.
+
Deal with Addiction by either going cold turkey on it and weathering the effects, eventually causing the addiction to subside, or by taking more of the addictive substance to keep the effects down.
+
Cryoxadane was heavily buffed. Clonex was removed as it was merged into Cryox.
+
Fixes a shitton of stuff adding Morphine instead of Ephedrine. Sorry, miners.
+
Fixes instances of Alkysine not being replaced with Mannitol. Same with Ryetalyn being replaced with Mutadone.
+
Adds Itching Powder. Welding Fuel and Ammonia.
+
Adds Antihol. Ethanol and Charcoal.
+
Adds Triple Citrus. Lemon Juice, Lime Juice, and Orange Juice.
Morphine replaces sleep toxin now, not Hyperzine. Hyperzine is replaced by Ephedrine now.
+
Cryotubes will now autoeject you once you are fully healed.
+
Synthflesh damage values have been fixed.
+
Charcoal has been buffed to heal 3 TOX damage a cycle and it purges other chemicals faster.
+
Saline-Glucose Solution has been buffed. It now has a 50% chance per cycle to heal 3 BRUTE and BURN per tick.
+
Fixes Strange Reagent revivals dying right after being revived. They'll still need immediate medical treatment, however.
+
Cryoxadone has had a recipe change. It is now Stable Plasma, Acetone, and Unstable Mutagen.
+
Adds 9 new Disabilites and 2 new genetic powers.
+
You can now heat donk pockets once more. Rejoice.
+
+
+
17 January 2015
+
Cheridan updated:
+
+
The lockbox system for R&D has been replaced with a firing pin system. Ask the Warden or HoS for firing pins to allow normal operation of your firearms. Want to test them out at the range? Try printing a test-range pin.
+
Nuke Ops have used this new technology to lock some of their heavier weapons so that crew cannot use them. These implants replace their explosive implants.
+
Explosive implants are now purchasable in the uplink, and are much stronger.
+
+
Firecage updated:
+
+
NanoTrasen would like to report that our Mechanical Engineers at NTEngiCore has recently upgraded the capacity of the machines known as Biogenerators. They are now have the added ability to create various belts, and also weed/plantkillers and cartons of milk and cream!
+
+
Iamgoofball (Look Ma, I didn't mispell my name this time!) updated:
In recipes that used removed reagents, they have been replaced with their goon chemistry counterpart:
+
Hyperzine -> Morphine
+
Inaprovaline -> Epinephrine
+
Kelotane -> Saline-Glucose Solution
+
Bicardine -> Saline-Glucose Solution
+
Dermaline -> Saline-Glucose Solution
+
Dexalin -> Salbutamol
+
Dexalin Plus -> Salbutamol
+
Tricordrazine -> Omnizine
+
Anti-Toxin -> Charcoal
+
Hydronalin -> Penetic Acid
+
Arithrazine -> Penetic Acid
+
Imidazoline -> Oculine
+
Mannitol -> Alkysine
+
Ryetalyn -> Mutadone
+
+
Steelpoint updated:
+
+
The Emergency Response Team project has been activated by Nanotrasen. The ERT are a seven man squad that can be deployed by the Central Command (admins) to attempt to rescue the station from a overwhelming threat, or to render assistance in dealing with a significant problem.
+
The ERT consists of a single Commander, two Security Officers, two Engineering Officers and two Medical Officers. The Commander is preselected when the squad is being spawned in, however the remaining six officers are free to select their role in the squad.
+
Two new Pulse gun variants have been added to the game. They are the Pulse Carbine and the Pulse Pistol, both have significantly smaller ammunition capacities than the Pulse Rifle, however they both can fit in a backpack and the pistol can fit in a pocket. All ERT Officers have a Pulse Pistol for a sidearm, however the Security Officers get a Pulse Carbine as their primary longarm.
+
+
phil235 updated:
+
+
The two library consoles are now real buildable computers. The circuitboard design is available in R&D.
+
+
sawu-tg updated:
+
+
Added a new R&D machine, to replace the telescience room. The Machine is based on RND and various interdepartmental creations.
+
+
+
14 January 2015
+
Boggart updated:
+
+
Adds a new spell: Repulse. (Cooldown: 40 seconds, 15 at max upgrade.) Repulse throws objects and creatures within 5 tiles of the caster away and stuns them for a very short time. Distance thrown decreases as the distance from the caster increases. (Max of 5, minimum of 3) Casting it while standing over someone will stun them for longer and cause brute damage but will not throw them.
+
Fixes the Exosuit Control Console having the unpowered icon state while powered.
+
+
Iamgootball updated:
+
+
Added Goonstation Chemistry!
+
Adds 14 medicines!
+
Adds 3 pyrotechnics!
+
Adds 3 drugs with actual gameplay uses!
+
Adds 7 toxins!
+
Adds a Chemical Heater for some new recipes!
+
Adds Chemical Patches for touch based applications!
+
Adds a Traitor Poison Kit!
+
Adds Morphine Medipens!
+
Adds Morphine bottles to Cargo!
+
+
+
13 January 2015
+
Dannno updated:
+
+
Adds a bandanas in basic colors to lockers in the station. Activate in hand to switch between head and mask modes.
+
Adds more colored jumpsuits to the mixed locker.
+
+
Incoming5643 updated:
+
+
Player controlled slimes can now change their face to match AI controlled slimes in kawaiiness. use *help as a slime to find the commands.
+
+
Studley updated:
+
+
Fixed NTSL division.
+
Added new NTSL math functions, including 'sin,' 'cos,' 'asin,' 'acos,' and 'log.'
+
+
+
06 January 2015
+
JJRcop, Nienhaus updated:
+
+
Adds emoji to OOC chat.
+
+
+
27 December 2014
+
Dorsidwarf updated:
+
+
Buffs the RCD from 30 matter units to max capacity 100, and increases the value of matter charges.
+
+
Paprika updated:
+
+
Taser electrodes now have a limited range of 7 tiles.
+
The energy gun's stun mode has been replaced with a disable mode.
+
Security officers will now spawn with tasers.
+
Disabler beams now go through windows and grilles.
+
Disabler beams now do 33 stamina damage instead of 34. This means they will only stun in 3 hits on someone that is NOT 100% healthy.
+
Most ballistic projectiles now do stamina damage in addition to their regular damage instead of stunning. This includes nuke op c-20r .45, detective's revolver, etc. They are effectively better than disablers at stunning people, as well as do significant damage, but they do not stun instantly like a taser. Expect engagements to be a little longer and plan ahead for this.
+
Added an advanced taser for RnD, the child of a disabler and a taser.
+
+
Tkdrg updated:
+
+
Envoys from the distant Viji sector have brought an exotic contraption to Centcomm. Known as the 'supermatter shard', it is a fragment of an ancient alien artifact, believed to have a dangerous and mysterious power. This precious item has been made available to the Space Station 13 cargo team for the cost of a hundred supply points, as a reward for all your hard work. Good luck, and stay safe.
+
+
+
16 December 2014
+
Paprika updated:
+
+
Reverted security armor and helmets to the old (Grey) style of armor/helmets.
+
Updated security jumpsuits to be more consistent across officer/warden/HoS ranks.
+
Added tactical armor and helmets to replace the largely unused bulletproof vest in the armory. This armor has higher bullet and explosive resistance than normal armor, and uses the previous, more military armor/helmet sprites.
+
Added a new injury doll to go along with the overall health meter. This will show you locational overall limb-based burn/brute damage, so you can tell the difference between being hurt with burn/brute or toxins/stamina damage which does not affect limbs.
+
Reverted some UI icons back to their older status and changed some around for readability. Thank you for your feedback.
+
+
Thunder12345 updated:
+
+
Added a new FRAG-12 explosive shotgun shell, built from the unloaded technological shell, using tablecrafting. Constructed using an unloaded technological tech shell, 5 units sulphuric acid, 5 units polytrinic acid, 5 units glycerol, and requires a screwdriver.
+
+
phil235 updated:
+
+
Change to the hunger system. Eating food with sugar temporarily worsens your nutrition drop rate. Eating too much sugar too quickly can make you temporarily unable to eat any sugary food. The nutritional effect of sugar depends on how hungry you are thus it cannot easily raise your nutrition level past well fed and is best used when hungry. Lots of sugar can make you a bit jittery at times. Eating food with vitamin (new reagent) counteracts the sugar effects and can give you temporary buffs when well fed: it lowers your chance to catch or spread viruses,it makes your body's metabolism more efficient that is it keeps healing reagents longer and evacuate toxins faster and reduces damage from radioactive reagents. Your metabolism gets less efficient if starving.
+
New hunger hud icons: starving, hungry, fed, well fed, full, fat.
+
Snack vending machine get a chef compartment that can be loaded with non-sugary food by hand or with a tray by anyone with kitchen access (unless you hack the machine with multitool or emag). The food can be unloaded by anyone, like regular snacks
+
Cargo can get a nutritious pizza crate order for 60 points.
+
Grown and cooked food gets some vitamin while junk food gets less nutriment and more sugar (only hot donkpocket and ramen don't have sugar)
+
The number of junk food in snack vending machine is lowered from 6 to 5 of each type.
+
Fixing not being able to load an omelette du fromage on a tray.
+
+
+
10 December 2014
+
GunHog updated:
+
+
The Nanotrasen firmware update 12.05.14 for station AI units and cyborgs includes a patch to give them more control of their automatic speech systems. They can now chose if the message should be broadcasted with their installed radio system and any frequency available to them.
+
+
Paprika updated:
+
+
Added winter coats and winter boots! Bundle up for space xmas! They are available in most wardrobe and job lockers, and come with special features depending on the flavor!
+
+
Paprka updated:
+
+
Added a new weapon for syndicate boarding parties, the C-90gl assault rifle. This is a hybrid rifle with a lot of damage and a grenade launcher alt-fire. Purchasable for 18TC.
+
Suppressors can now be used on the protolathe SMG and the C-20r.
+
+
RemieRichards updated:
+
+
Sounds in areas of low pressure (E.g. Space) are now much quieter
+
To hear someone in space you need to be stood next to them, or use a radio
+
+
as334 updated:
+
+
We've discovered that slimes react to the introduction of certain chemicals in their bloodstream. Inaprovaline will decrease the slimes children's chance of mutation, and plasma will increase it.
+
We've also found that feeding slimes solid plasma bars makes them more friendly to humans.
+
+
+
08 December 2014
+
Ergovisavi updated:
+
+
Allows using replica pods to clone people without bodies.
+
Speeds up replica pod cloning by adjusting the starting production.
+
Replica pods will now only create plantmen.
+
Mixing blood in a beaker or taking it from a suicide victim will render the blood sample useless for replica pod cloning.
+
+
GunHog updated:
+
+
Our top Nanotrasen scientists have provided improved specifications for the Rapid Part Exchange Device (RPED)! The RPED can now contain up to 50 machine components, and will display identical items in groups when using its inventory interface.
+
Note that power cells, being somewhat bulkier, will take up more space inside the device. In addition, the RPED's firmware has also been updated to assist in new machine construction! Simply have the required components inside the RPED, apply it to an unfinished machine with its circuit board installed, and watch as the device does the work!
+
Nanotrasen has updated station bound Artificial Intelligence unit design specifications to include an integrated subspace radio transmitter. The transmitter is programmed with all standard department channels, along with the inclusion of its private channel. It is accessed using :o in its interface.
+
{INCOMING CLASSIFIED SYNDICATE TRANSMISSION} Greetings, agents! Today, we are happy to announce that we have successfully reverse engineered the new subspace radio found in Nanotrasen's newest AI build plans. We have included our encryption data into these transmitters.
+
Should you be deployed with one of our agent AIs, it may be wise of you to purchase a syndicate encryption key in order to contact it securely. Remember, as with other agents, a Syndicate AI is unlikely to be of the same Syndicate corporation as you, and you both may actually have conflicting mission parameters! Your channel, as always, is accessed using :t.
+
+
Paprika updated:
+
+
Changed the way flashbang protection works. Currently, only security helmets (and their derivatives like swat and riot), and some hardsuit helmets have flashbang protection. Ear items like earmuffs and 'bowman' headsets (alternative, larger headsets) have flashbang protection as well, so you're able to go hatless as security. The Head of security, warden, and detective's hat do NOT have flashbang protection, but their earpieces do, from the 'noise' part of the flashbang. The flashbang blind still works as before, with only sunglasses/hardsuit helmets protecting you.
+
+
Tkdrg updated:
+
+
Security/Medical HUDs are now more responsive and less laggy.
+
+
+
10 November 2014
+
Menshin updated:
+
+
Made meteors more threatening.
+
Added three levels of danger for meteors waves, randomly selected at spawn.
+
Rumors speaks of a very rare meteor of unfathomable destructive power in the station sector...
+
Fixed infinigib/infinisplosion with meteors bumping
+
+
Paprika updated:
+
+
Added a defibrillator and an EMT alternative clothing set for medical doctors. These defibrillators will revive the recently deceased as long as their souls are occupying their body and they have no outstanding brute or burn damage.
+
Yes, they actually work now too!
+
Added a way for you to unload a round from a chambered projectile weapon. Simply click on the gun when it is in your active hand, and assuming it does not have a magazine, the round should pop out. You can also use this system to unload individual bullets from magazines and ammo boxes, like the detective's speed loaders.
+
Added glass tables and overhauled how table construction works. Now, you need to build a frame using either metal rods or wood planks, and then place a top on the table.
+
Table disassembly now works in two stages. Screwdriver the table to remove the top of it and reduce it back to a frame. Frames can be moved around and walked on top of. Wrench the table to deconstruct it entirely back to parts, this is quicker and much more like the old fashion. To deconstruct a frame, use your wrench on it as well.
+
Fixed traitor med analyzers not fitting in medical belts.
+
Fixed syringe box bug with medipens.
+
+
Perakp updated:
+
+
Added escape-with-identity objective for changelings. Completion requires the changeling to have transformed to their target and be wearing an ID card with the target's name on it when escaping.
+
Changelings can absorb someone even if they have that DNA in storage already.
+
Fixes hivemind being lost when readapting evolutions.
+
+
+
04 November 2014
+
MrPerson updated:
+
+
You can use AI Upload modules directly on unslaved cyborgs to change their laws. The panel has to be open to do this.
+
Newly emagged cyborgs get locked down for a little bit until their new laws get shown. Thank you to whoever bitched about it on singulo and filed an issue report.
+
+
Paprika updated:
+
+
Added an advanced mop for the janitor, available at the protolathe. This hi-tech custodian's friend can mop twice as many floor tiles before needing to return to your bucket!
+
Moved holosign projector to the protolathe and gave the janitor back his old signs. We hope you've enjoyed this free trial run of holo projectors, but our budget is tight!
+
Added a holosign projector to the janitor module for cyborgs.
+
Soap now has a delay. Different soap types clean faster than others, but expect to put it some extra arm work if you want to clean that fresh blood!
+
+
Tkdrg updated:
+
+
Cutting-edge research in a secret Nanotransen lab has shed light into a revolutionary form of communication technology known as 'chat'. The Personal Data Assistants of Space Station 13 have been deployed with an experimental module implementing this system, dubbed 'Nanotransen Relay Chat'.
+
+
+
30 October 2014
+
MrPerson updated:
+
+
Everything now drifts in space, not just mobs.
+
Mechs are just as maneuverable in space as you are.
+
Being weightless now slows movement down by a lot rather than speeding it up. Having your hands full will make movement even slower.
+
Removed spaceslipping (You slipped! x1000)
+
Jetpacking after being thrown out a mass driver will cancel out your momentum.
+
Shooting a gun causes you to drift the other direction like spraying a fire extinguisher.
+
+
+
27 October 2014
+
Jordie0608 updated:
+
+
Adds a new riot shotgun that spawns in the armory with a 6 round beanbag magazine.
+
+
Paprika updated:
+
+
Allowed constructs to assist in activating most runes. This includes conversion runes, but does NOT include sacrifice runes.
+
+
Patchouli Knowledge updated:
+
+
In light of the recent achievements of the Robotics department, new and upgraded exosuit schematics were delivered to SS13 exosuit fabricators as a reward from CentComm.
+
The countless hours of research, experimentation, and reverse-engineering of wrecks from the mining asteroid have paid off - the Research team has rediscovered the process of building Phazon exosuits. The parts and circuits have been made available in the exofabs and circuit printers.
+
The APLU Ripley exosuit has been refitted with asteroid mining purposes in mind. Its armour plating will provide extra protection against the brute attacks of asteroid alien lifeforms, and protect from gibtonite explosions. The leg servos were upgraded to provide faster movement.
+
There are rumours of some creative shaft miners discovering a way of augmenting APLU Ripley armour with trophy hides from goliath-type asteroid lifeforms...
+
Durand construction routines now require a phasic scanner module and a super capacitor to match Nanotrasen industry standards.
+
The exosuit fire extinguisher now boasts a larger watertank, and carries a whole 1000u of water from the old 200u tank.
+
The exosuit plasma converter's efficiency has been vastly improved upon, and its fuel consumption has been cut to nearly half of old values.
+
The exosuit sleeper interiors have been refitted to allow the occupants more freedom of movement, and the occupants may now eject at will.
+
The armour plating on all exosuits was fitted with reactive deflection systems specifically designed to stop the rapid slashes from xenomorph threats.
+
The Firefighter Ripley chassis were moved from the Exosuit Equipment section to the APLU Ripley section of exofab menus.
+
Fixed some minor errors in the exosuit construction message syntax.
+
+
phil235 updated:
+
+
Closets and its children can no longer contain a near infinite amount of mobs. Large mobs (juggernaut, alien emperess, etc) don't fit, small mobs (mouse, parrot, etc) still fit in the same amounts, human-sized mob (humanoid, slime, etc) fit but maximum two per body bag and three for all the other closets. Adding bluespace body bag to R&D, it can hold even large mobs and in large amounts.
+
girders and displaced girders now have a chance to let projectiles pass through them.
+
+
+
19 October 2014
+
Donkie updated:
+
+
Major atmos update. Featuring pretty colors.
+
4-Way pipes added.
+
Flip-able trinary devices (mixer & filter).
+
Pipe-painter added.
+
Most pipe icons have been tweaked with shading and such, making it look more uniform.
+
+
JJRcop updated:
+
+
We have managed to re-discover drones! We improved the formula, and have discovered some cool stuff.
+
After some fiddling with audio recognition, drones can now understand human speech!
+
One of our engineers found some screws that were loose on some of our tests, screwing them in made the drone work as if it had just come out of robotics.
+
We had to sacrifice some of our NT brand EMP protection for the speech recognition, so drones can get damaged if subjected to heavy EMP radiation.
+
We figured out why drones couldn't remove objects from people, that has been fixed.
+
The standby light wasn't wired correctly.
+
Unfortunately one of our assistants tasked with transporting the only copy of the data dropped the flash drive in hot water because he wanted to see what "drone tea" tasted like. You'll have to discover them again.
+
+
Lo6a4evskiy updated:
+
+
Station's crayons have been upgraded to the version 2.0 of ColourOS. Don't worry, they are still edible!
+
Many items now have different stripping delays. Heavier and more armored pieces of clothing are generally more difficult to take off someone else, while smaller items can be very easy to snatch. Pockets always take 4 seconds to search.
+
+
MrPerson updated:
+
+
Implants are activated via action buttons on the top-left corner of your screen instead of emotes.
+
+
Paprika updated:
+
+
Added zipties for security and nuke operatives. These have a slightly shorter 'breakout time' as normal cuffs but they are destroyed upon removal. Beepsky and sec borgs now use these by default.
+
Changed the medical HUD in the tactical medkit to a night vision HUD so syndicate medics don't stumble around in the dark. Can be purchased through nuke op uplinks.
+
Added a second roboticist roundstart job slot.
+
Added a third cargo tech job slot.
+
+
Paprka updated:
+
+
Added undershirts as a companion piece to jumpsuit adjusting. Beat the heat with a comfortable tank top, or warm up with an extra layer tshirt!
+
Added a holographic sign projector to replace the old wet floor signs to encourage janitors to warn crewmembers about wet floors.
+
+
optimumtact updated:
+
+
Medical supplies crate now comes with a boxy full of bodybags
+
+
+
09 October 2014
+
Paprika updated:
+
+
Added inaprovaline MediPens (autoinjectors) that replace inaprovaline syringes in medkits. Like the syringes, these can be used to stabilize patients in critical condition, but require no medical knowledge to use.
+
Reverted the mining scanner to a manual scanner.
+
Buffed the rate of the automatic scanner
+
Added automatic scanner to equipment locker. It is also available by redeeming your voucher.
+
Reduced the size of the mining jetpack, it now fits in backpacks. The capacity of the jetpack has been reduced as well, however.
+
Removed resonator from the mining voucher redemption. Replaced it with a mining drill.
+
+
+
08 October 2014
+
Cheridan updated:
+
+
Flash mechanics changed. Instead of a knockdown effect, it will cause a stumbling effect. This includes area-flashing. The five-use-per-minute limit has been removed. "Synthetic" flashes made in the protolathe are now normal flashes.
+
+
Paprika updated:
+
+
Added a unique shield to the Head of Security's locker. It can be concealed for storage and activated like an energy shield, but functions like a riot shield. It can also be made at the protolathe.
+
Marauders: Due to your extremely great performance as of late, we've added a new equipment room on Mother Base. A lot of your equipment has been moved to this room, as well as some new equipment added onto the Mothership. Take some extra time to familiarize yourself with the placement of your equipment.
+
Oh, and you guys have a space carp now too.
+
+
Paprka updated:
+
+
Added jumpsuit adjusting. Adjust your jumpsuit to wear it more casually by rolling it down or roll up those sleeves of that hard-worn suit!
+
Added a grey alternative to the detective's suit and trenchcoat. Now you can be a noir private investigator!
+
+
Tkdrg updated:
+
+
Space Station 13 has been deployed with transit tube dispensers. Central Command encourages the engineering department to repair and improve the station using this equipment.
+
+
+
05 October 2014
+
Ikarrus updated:
+
+
Character Save Files will now remember Species.
+
+
Miauw updated:
+
+
Added 5 new maintenance ambience tracks, made by Cuboos.
+
+
Nobalm updated:
+
+
Added examine descriptions to twenty eight objects.
+
+
Paprika updated:
+
+
Due to some payroll cuts affecting lower-rank crewmembers on Space Station 13, we fear that uprisings and mutinies might be approaching. To help defend themselves, all heads of staff have been given telescopic batons to subdue attackers should they be assaulted during their shifts.
+
Toolbelts can now hold pocket fire extinguishers. Atmos technicians spawn with them.
+
Remember those magboots we had our undercover agents steal? Well we've finally reverse engineered them. Not only that, but we gave them a killer paintjob. They will be available in the uplinks of boarding parties we send out to Nanoscum stations.
+
Listen up, Marauders! The Syndicate has recently cut back funding into boarding parties. What this means is no more hi-tech fancy energy guns. We've had to roll back to using cheaper, mass-produced automatic shotguns. But in retrospect, could this possibly be a downgrade?
+
+
+
01 October 2014
+
Cheridan updated:
+
+
Experienced smugglers have joined the Syndicate, adding their bag of tricks to its arsenal. These tricky pouches can even fit under floor plating!
+
+
Ikarrus updated:
+
+
Adds Gang War, a new game mode still in alpha development.
+
+
Incoming5643 updated:
+
+
After a public relations nightmare in which a horde of 30 assistants violently dismembered a sole commander the revolution has begin tactically reducing their initial presence in stations with weak command structures. Should stations add to their command staff during the shift additional support will be provided.
+
Too many instances of crying CMOs hiding in the lockers of supposedly secured revoulutionary stations has lead to a crackdown on revolutionary policy. You must kill or maroon the entire command staff, reguardless of when they reached the station.
+
Intense study into the underlying structure of the universe has revealed that everything fits into a massive SPACECUBE. This deep and profound understanding has led to a revolution in cross surface movement, and spacemen can look forward to a new era of predictable and safe space-fairing.
+
+
Jordie0608 updated:
+
+
Grille damage update, humans now deal less unarmed damage to grilles while projectiles and items deal more.
+
Replaced all AI turrets with porta-turrets that work on LoS instead of area and can be individually configured
+
+
Miauw updated:
+
+
Voice changer masks work again.
+
You can now turn voice changer masks on/off by activating them in your hand. They start off.
+
The amount of TC has been increased to 20, but the price of all items has been doubled as well
+
With this, the prices of stealthy items has been slightly decreased and the prices of offensive items slightly increased (+/- 1 TC)
+
+
RemieRichards, JJRcop updated:
+
+
New research in the area of robotics has been published on Maintenance Drones, little robots capable of repairing and improving the station. Unfortunately, all of the research was lost in a catastrophic server fire, but we know they can be made, so it's up to you to re-discover them! Make us proud!
+
+
phil235 updated:
+
+
Changed all clone damage descriptions to use the term cellular damage to avoid confusion with genetics mutations.
+
+
+
07 September 2014
+
Gun Hog updated:
+
+
Nanotrasen scientists have released a Heads-Up-Display (HUD) upgrade for all AI and Cyborg units! Cyborgs have had Medical sensors and a Security datacore integrated into their core hardware, replacing the need for a specific module.
+
AI units are now able to have suit sensor or crew manifest data displayed to them as a visual overlay. When viewing a crewmember with suit sensors set to health monitoring, the health status will be available on the AI's medical HUD. In Security mode, the role as printed on the crewmember's ID card will be displayed to the AI.
+
Any entity with a Security or Medical HUD active will recieve appropriate messages on that HUD.
+
+
Ikarrus updated:
+
+
Server Operators: A new config option added to restrict command and security roles to humans only. Simply add/uncomment the line ENFORCE_HUMAN_AUTHORITY in game_options.txt.
+
Players: To check if this config option is enabled on your server, type show-server-revision into the game's command line.
+
+
+
+
06 September 2014
+
Ikarrus updated:
+
+
Tampered supply ordering consoles can now order dangerous devices directly from the Syndicate for 140 points.
+
Securitrons line units will no longer arrest individuals without IDs if their faces can still be read.
+
Enemy Agent ID cards have been reported to be able to interfere with the new securitron threat assessment protocols.
+
The Syndicate base has been modified to only allow the leading operative to launch the mission.
+
+
+
+
04 September 2014
+
KyrahAbattoir updated:
+
+
All the objects found in BoxStation maintenance are now randomized at round start.
+
+
+
03 September 2014
+
Ikarrus updated:
+
+
Cost of Syndicate bombs increased to 6 telecrystals.
+
+
JStheguy updated:
+
+
Many new posters have been added.
+
+
+
01 September 2014
+
ChuckTheSheep updated:
+
+
Adds preference toggle for intent selection mode.
+
Direct selection mode switches intent to the one you click on instead of cycling them.
+
+
+
Ikarrus updated:
+
+
New more detailed End-Round report.
+
Removes count of readied players from the lobby.
+
+
+
Jordie0608 updated:
+
+
Virology has been made the right color.
+
+
Miauw updated:
+
+
A major rework of saycode has been completed which fixes numerous issues and improves functionality.
+
Please report any issues you notice with any form of messaging to Github.
+
+
+
+
31 August 2014
+
Tokiko1 updated:
+
+
Adds two new hairstyles.
+
+
+
30 August 2014
+
Ikarrus updated:
+
+
Space Station 13 has been authorized to requisition additional Tank Transfer Valves from Centcom's supply lines for the price of 60 cargo points.
+
+
+
26 August 2014
+
Ikarrus updated:
+
+
Security forces are advised to increase scrutiny on personnel coming into contact with AI systems. Central Intelligence suggests that Syndicate terrorist groups may have begun targeting our AIs for destruction.
+
For the preservation of corporate assets, Central Command would like to remind all personnel that evacuating during an emergency is mandatory. We suspect that terrorist groups may be attempting to abduct marooned personnel who failed to evacuate.
+
R&D; teams have released an urgent update to station teleporters. To eliminate the risk of mutation, personnel are advised to calibrate teleporters before attempting each use.
+
Centcom has approved the Captain's request to rig the display case in his office with a burglar alarm. Any attempts to breach the case will now trigger a lockdown of the room.
+
**Crew Monitoring Consoles now scan their own Z-level for suit sensors, instead of only scanning Z1.
+
**Server operators can now set a name for the station in config.txt. Simply add the line "STATIONNAME Space Station 13" anywhere on the text file. Replace Space Station 13 with your station name of choice.
+
+
+
+
24 August 2014
+
Ikarrus updated:
+
+
Ore redemption machine can now smelt plasteel.
+
Mulebot speed has been vastly increased by up to 300%.
+
Removed exploit where false walls can be used to cheese the AI chamber's defenses
+
+
+
21 August 2014
+
Ikarrus updated:
+
+
The Phase Shift ability will no longer instantly gib players. Instead, they will be knocked out for a few seconds. Mechas will be damaged.
+
The Phase Slayer ability will no longer instantly gib players. Instead, they will be dealt 190 brute damage. Mechas will be dealt 380 damage.
+
ADMIN: Most Event buttons have been moved out of the secrets menu and into a Fun verb.
+
+
+
20 August 2014
+
Cheridan updated:
+
+
Three new shotgun shells added. Create them by first researching and printing an unloaded tech shell in R&D.; Afterwards, use table-crafting with the appropriate components with a screwdriver in-hand.
Dragonsbreath: tech shell + 5 phosphorus (in container).
+
Incendiary rounds now leave a blazing trail as they pass. This includes existing incendiary rounds, new dragonsbreath rounds, and the Dark Gygax's carbine.
+
+
+
19 August 2014
+
Ikarrus updated:
+
+
Brig cells and labour shuttle have both been modified to send a message to all security HUDs whenever a prisoner is automatically released.
+
The labour camp has been outfitted with a points checking console to allow prisoners to check their quota progress easier and better explain the punishment system of forced labour.
+
+
+
18 August 2014
+
Iamgoofball updated:
+
+
25 new hairstyles have been added.
+
+
+
15 August 2014
+
AndroidSFV updated:
+
+
AI photography has been extended to Cyborgs. While connected to an AI, all images taken by cyborgs will be placed in the AI's album, and viewable by the AI and all linked Cyborgs.
+
When a Cyborgs AI link wire is pulsed, the images from the Cyborgs image album will be synced with the AI it gets linked to.
+
Additonally, Cyborgs are able to attach images to newscaster feeds from whichever album is active for them. Cyborgs are also mobile, inefficent, printers of same images.
+
+
+
11 August 2014
+
Ikarrus updated:
+
+
Boxstation map updates:
+ -Singularity engine walled from from outer space
+ -Shutters for exterior Science windows
+ -Teleporter board moved from RD's Office to Tech Storage now that the teleporter can be built again.
+ -Security Deputy Armbands (red) added in HoS's Office
+
+
+
14 July 2014
+
Miauw updated:
+
+
Added a new mutetoxin that can be made with 2 parts uranium, 1 part water and 1 part carbon and makes people mute temporarily
+
Parapens were renamed to sleepy pens and now contain 30 units of mutetoxin and 30 units of sleeptoxin instead of zombie powder.
+
C4 can no longer be attached to mobs
+
Reduced the C4 price to 1 telecrystal
+
+
+
07 July 2014
+
Firecage updated:
+
+
We, of the NanoTrasen Botany Research and Developent(NTBiRD), has some important announcements for the Botany staff aboard our stations.
+
We have recently released a new strain of Tower Cap. It is now ensured that more planks can be gotten from a single log.
+
Head Scientist [REDACTED] is thanked for his new strain of Blood Tomatoes. It now not only contains human blood, but also chunks of human flesh!
+
We have also discovered a way to replant the infamous Cash Trees and 'Eggy' plants. We can now harvest their seeds, and the rare products are inside the fruit shells.
+
Similar to the new variety of Tower Caps, it is now possible to get various grass flooring depending on the plants potency!
+
Our sponsors at Knitters United has released a new variation of the botany Aprons and Coveralls which can hold a larger variety of items!
+
A new version of Plant-B-Gone was produced which are much more lethal to plants, yet at the same time has no extra effect on humans besides the norm.
+
+
Paprika updated:
+
+
Tweaked mining turf mineral droprates and reverted mesons to the 'old' style of mesons. This means mesons will be more powerful with the added side effect of not being able to track individual light sources through walls and such. This is a trial run; please provide feedback on the /tg/ codebase section of the forum.
+
+
+
05 July 2014
+
Firecage updated:
+
+
Recently the Space Wizard Federation, with some assistance from the Cult of Nar-Sie, recently created a new strain of Killer Tomato. A sad side effect is this effected all current and future Killer Tomato strains in our galaxy. They are now reported to be extremely violent and deadly, use extreme caution when growing them.
+
+
+
03 July 2014
+
Miauw updated:
+
+
Added slot machines to the game
+
Slot machines use reels. All prizes except for jackpots can be won on all lines
+
Simply put a coin into one of the slot machines in the bar to play!
+
Slot machines can also be emagged.
+
+
+
01 July 2014
+
Ikarrus updated:
+
+
Central Security has approved an upgrade of the Securitron line of bots to a newer model. Highlights include:
+
-An optional setting to arrest personnel without an ID on their uniform (Disabled by default)
+
-An optional setting to notify security personnel of arrests made by the bots (Enabled by default)
+
-A new "Weapon Permit" access type that securitrons will use during their weapons checks. Personnel who work with weapons will be granted this access by default. More can be assigned by the station's Head of Security and Personnel.
+
-A more robust threat assessment algorithm to improve accuracy in identifying perpetrators.
+
+
Rolan7 updated:
+
+
Fixed the irrigation system in hydroponics trays. Adding at least 30 units of liquid at once will activate the system, sharing the reagents between all connected trays.
+
Changelings can communicate while muzzled or monkified, and being monkified doesn't stop them from taking human form.
+
Mimes cannot break the laws of physics unless their vow is currently active.
+
Trays are rewritten to make sense and so borgs can actually use them. Service borgs can discretely carry small objects, hint hint. The items are easily knocked out of them.
+
+
+
20 June 2014
+
Ikarrus updated:
+
+
The 'Enter Exosuit' button was removed, and you have to click + drag yourself to enter a mech now; like you would to enter a disposal unit.
+
+
+
12 June 2014
+
Cheridan updated:
+
+
NT Baton Refurbishment Team Notice: 'Released' option in security records has been renamed 'Discharged', and the sechud icon has been changed from a blue R to a blue D. Stop beating your released prisoners, dummies; they're not revheads.
+
NT Entertainment Department Notice: The prize-dispensing mechanism in Arcade machines has been replaced with a cheaper gas-powered motor. It should still be completely safe, unless an electromagnetic field disrupts it!
+
Pill code tweaked. In short, you can feed monkeys pills now #whoa
+
Casings ejected from guns will now spin #wow
+
+
+
11 June 2014
+
Ikarrus updated:
+
+
Having the nuke disk will no longer prevent you from using teleporters or crossing Z-levels. Leaving the Z-level however, will cause you to "suddenly lose" the disk.
+
Pulled objects will now transition with you when as you transition Z-levels in space.
+
+
Malkevin updated:
+
+
IEDs have been removed
+
Firebombs have been added
+
They're as reliable and predictable as you would expect from a soda can filled with flammable liquid and set off with an igniter contected to two wires. Use your branes and take proper precautions or leave a charred corpse, your choice.
+
+
+
06 June 2014
+
Gun Hog updated:
+
+
Nanotrasen programmers have discovered a potential exploit involving a station's door control networks. With sufficient computing power, the network can be overloaded and forced to open or bolt closed every airlock at once.
+
Some reports suggest that the latest firmware update to Artifical Intelligence units installed in our research stations may contain this exploit. Any such reports that state that should a station's AI unit "malfunction", it may gain the ability to use this exploit to initate a full lockdown of the station. Fire locks and blast doors are also said to be affected.
+
Nanotrasen officially holds no validity to these reports. We recommend that station personnel disregard such rumors.
+
+
+
20 May 2014
+
Kelenius updated:
+
+
After the years of extensive research into xenobiology, and thousands of scientists lost to the slimes, we have finally come to admitting that we don't have a clue how they work. However, we've outlined a few facts, and managed to breed a new kind of slimes. They will be shipped to your station on the next shifts. They are different from the ones they are used to in the following:
+
Their sight is better than ever, and now they can finally see things that are not directly pressed against them. Beware, they may appear more aggressive!
+
Their memory has also become better, and they will now remember the people who fed them. As a note, you need to grab the monkey or push it to show to the slime that you're giving it to them, and it's not just walking in.
+
Apparently, not only we have studied them, but they have also studied us. Slimes are now capable of showing various emotions, mimicking humans. Use it to your advantage.
+
There are reports of slimes showing signs of sentience. Further research is recommended.
+
One of the reported signs is their speech capability. Following facts have been gathered: they talk more often to those who feed them; they react to being called by the number, but 'slimes' is also acceptable; they are able to understand being ordered to "follow" someone, or "stop". There is a report of slime releasing the assistant after a scientist shouted at it, and then calmly ordered a slime to stop.
+
We've made a modification of a health scanner that is intended for the use on slimes. Two are available in the smartfridge.
+
We've come to a conclusion that 5 units of reagents are not necessary to cause slime core reactions. You actually only need one unit.
+
+
+
16 May 2014
+
Menshin updated:
+
+
Thanks to Nanotrasen's Engineering division, the power wiring of the station has a received a complete overhaul. Here are the highlight features in CentCom report :
+
Improved way of connecting cables! Finished is the time of "laying and praying" : it's now as simple as matching ends of each cables!
+
Unified way of connecting machines! Every machine is connected to a powernet through a node cable (it still needs to be anchored to receive power though)!
+
Lots of improvements in the cable cutting tools! Gone is the odd magic of physically separated yet still connected powernets (contact CentCom Engineering Division if you still witness something weird, we always have a fired form ready for our engineers!)!
+
Any conscientious (or not!) Station Engineer may check an "updated wiring manual" at http://www.tgstation13.org/wiki/Wiring#Power_Wires
+
+
+
14 May 2014
+
Ikarrus updated:
+
+
A recent breakthrough in Plasma Materials Research has led to the development of a stronger, tougher plasteel alloy that can better resist extreme heat by up to four times. A live demonstration preformed during a press conference earlier today showed a segment of reinforced wall resisting an attack by Thermite. The corporation also announces that upgrades to its existing stations is already underway.
+
+
+
07 May 2014
+
/Tg/station Code Management updated:
+
+
/tg/station code is now under a feature freeze until 7th June 2014. This means that the team will be concentrating on fixing bugs instead of adding features for the month.
+
You can find more information about the feature freeze here. http://tgstation13.org/phpBB/viewtopic.php?f=5&t;=273
+
This is the perfect time to report bugs, which you can do so here. https://github.com/tgstation/-tg-station/issues/new
+
+
+
30 April 2014
+
Giacom updated:
+
+
You now have to give a reason when calling the shuttle.
+
The amount of time it takes for the shuttle to refuel is now a config option, the default is 20 minutes.
+
+
Gun Hog updated:
+
+
Certain Enemies of the Corporation have discovered a critical exploit in the firmware of several NanoTrasen robots that could prevent the safe shutdown of units corrupted by illegally modified ID cards, dubbed "cryptographic sequencers".
+
NanoTrasen requires more research into this exploit, this we have issued a patch for AI and Cyborg software to simulate this malfunction. All NanoTrasen AI units are advised to only allow testing in a safe and contained environment. The robot can safely be reset at any time.
+
Unfortunately, a robot corrupted by a "cryptographic sequencer" still cannot be reset by any means. NanoTrasen urges regular maintenance and care of all robots to reduce replacement costs.
+
+
+
27 April 2014
+
Ikarrus updated:
+
+
NT Human Resources announces an expansion of the List of Company-Approved Hair Styles, as well as more relaxed gender restrictions on hair styles. Check with your local company-sponsored hairstylist to learn more.
+
+
+
22 April 2014
+
Ikarrus updated:
+
+
ID Computers have been modified to allow Station Heads of Staff to assign and remove access to their departments, as well as stripping the rank of their subordinates. An ID computer on the bridge is available for the use of this function.
+
+
+
20 April 2014
+
Gun Hog updated:
+
+
NanoTrasen Emergency Alerts System 4.20 has been released!
+
In the unfortunate event of any nuclear device being armed, the station will enter Code Delta Alert.
+
Should the nuclear explosive be disarmed, the station shall automatically return to its previous alert level.
+
+
Steelpoint updated:
+
+
A new AI satellite has been constructed in orbit of Space Station 13!
+
The satellite exclusivly is used to hold a station's Artifical Intelligence Unit, the satellite contains a miried of turret and motion sensor defences. A transit tube network is used to connect the satellite to the station, with the transit room being located at engineering south of the engi escape pod.
+
The AI Upload however has been moved north slightly and is connected directly to the bridge.
+
In addition, the Gravity Generator has been relocated from its prior position in engineering to the location of the old AI Upload, increasing its defence and logical positioning.
+
+
+
11 April 2014
+
Jordie0608 updated:
+
+
Wood planks can be used to make wood walls and airlocks; flammability not included
+
+
+
10 April 2014
+
Ikarrus updated:
+
+
Centcom officials have announced a new initiative to combat misuse of their Emergency Shuttle Service. After the shuttle has been recalled several times over the course of a simple work shift, Centcom will attempt to trace the signal origin and pinpoint its source for station authorities.
+
+
Steelpoint updated:
+
+
Thanks to Nanotrasen's Construction division, the brig has recieved a overhaul in its design, to increase ease of movement, including an addition of a "prisioner release room" and a insanity ward.
+
Furthing concerns among commentators, Nanotrasen has shipped out additional security equipment to station armories, including Riot Shotguns, tear gas grenades, additional securitron units and military grade Ion Guns.
+
JaniCo have released a new unique product called the JaniBelt. Capable of holding most standard issue janitorial equipment, designed to help relive inventory management among station janitors. In addition the Jani Water Tank has had its reserve of space clenaer increased to 500 units, up from 250.
+
+
+
Validsalad updated:
+
+
After concerns raised by security personel, new armor has been shipped that covers the lower waist and readjusts the helmet for comfort.
+
In addition, the aging riot shield has been replaced with a newer, more modern, apperance.
+
+
+
+
03 April 2014
+
Ikarrus updated:
+
+
NT AI Firmware version 2554.03.03 includes a function to notify the master AI when a new cyborg is slaved to it.
+
+
+
02 April 2014
+
Giacom updated:
+
+
The syndicate suit is now black and red, because black and red is the new red.
+
The Ruskie DJ Station now has a classy orange syndicate suit.
+
Security officers have been equipped with the latest in flashlight technology. You can find a SecLite™ inside your security belt or you can dispense one from the security vending machine.
+
+
Ikarrus updated:
+
+
Nanotrasen Construction division is pleased to announce the unveiling of our brand now state-of-the-art Space Station 13 v2.1.3!
+
-Scaffolding used during construction has been repurposed as an expanded maintenance around the station.
+
-Our new Emergency Transport Shuttle Mk III will be making its debut in times of crisis. Includes a new cargo area and extra security features.
+
-A redesigned brig will give security staff more peace of mind with added security features
+
-The armory is now protected by a motion alarm.
+
-The armory is now equipped with security hardsuits.
+
-A new command EVA wing has been added to EVA Storage. Station heads of staff will be able to make use of suits stored here.
+
-A new garden area will be made publicly available for everyone to enjoy some R&R; during their company-endorsed break periods
+
-A new Testing Lab has been added to the Science department for any field experiments that need to be run.
+
-The mining ore redemption machine has been relocated to the Cargo Delivery Office.
+
+
+
01 April 2014
+
Aranclanos updated:
+
+
Made combat more hardcore for hardcore players like myself. This won't be reverted.
+
+
Malkevin updated:
+
+
Sacrifice Cult - Cult has received a massive overhaul to how it works, focusing more on cult magics and sacrificing unbelievers. The most important changes are:
+
Cult starts off with alot more cultists, 6 cultists below 30 players and 9 above. The HoP can no longer be a round start cultist but is still convertible.
+
Cultists start off with join, blood, self, and hell. These give the runes for the sacrifice and convert.
+
Conversions now require three cultists and converts no longer reward words, words must be obtained via sacrifice
+
Sacrificing players traps their soul in a soul stone, which can then be used on construct shells to implant the souls in them
+
Constructs shells can be summoned via a new rune (travel, hell, technology)
+
You can no longer convert the sacrifice target, you dozzy sods
+
Summoning Narsie when she is not an objective leads to !!FUN!!
+
Wrote a system to allow all mob types to be sacrificable - currently only corgis are in the new system
+
Holy Water will DECONVERT cultists, takes around 35 units and two minutes to succeed
+
Hitting a mob that contains holy water with a tome will convert that water to unholy water, conversely hitting any container that contains unholy water with a bible as chaplain will 'cleanse' the taint.
+
Unholy water works like a combination synaptazine and hyperzine, with a twist of branes dimarge and highly toxic to non-cultists
+
Cargo can order a religious supplies crate - it contains flasks of holy water, candles, bibles, and robes
+
Cultists have an innate ability to communicate to the cult hive mind (BE AWARE - THIS DOES A LOT OF DAMAGE), cultists can also communicate via the new Commune option on their tomes (the Read Tome option has been shifted under the Notes option)
+
+
MrStonedOne updated:
+
+
Cyborgs can now use AI click shortcuts. (shift click on doors opens them, etc) (including ones added below)
+
New silicon click shortcut: Control+Shift click on doors toggles access override
+
New silicon click shortcut: Control click turret controls toggles them on or off
+
New silicon click shortcut: Alt click turret controls toggles lethal on or off
+
Cyborg hotkey mode was massively improved. 1-3 selects module slot, q unloads the active module. use hotkeys-help as cyborg for more details
+
+
+
31 March 2014
+
Ikarrus updated:
+
+
Fabricator blueprints for cyborg parts have been updated to allow the debugging of cyborg units prior to boot. To access the debug functions, assemble a cyborg following standard procedure. Before inserting the MMI, use a multitool on the cyborg body. Note that this update also moves designation data into the system files, so pens can no longer be used to name cyborgs.
+
+
MrPerson updated:
+
+
Toggling the "Play admin midis" preference will pause any playing songs. Turning midis back on will resume the current song where it was.
+
If there's a song playing while you have midis off and you then turn midis on, the current song will begin playing for you.
+
+
+
28 March 2014
+
Aranclanos updated:
+
+
Workaround with the click cooldowns. They should feel faster. If you think that anything needs a click cooldown (like grilles, mobs, etc.), report it to me.
+
Here's the fun part, this is a test for the community and players, I hope I won't be forced to revert this and hear "this is why we can't have nice things". Again, if anything needs a cooldown, report it to me. Have fun with your fast clicks spessmans.
+
+
+
25 March 2014
+
Ikarrus updated:
+
+
A security review committee has approved an update to all Nanotrasen airlocks to better resist cryptographic attacks by the enemy.
+
*New internal wiring will be able to withstand attacks, and panel wires will no longer be left unusable after an attack.
+ *Welding tools will now be able to cut through welded doors that have been damaged.
+ *Damaged electronics are now removable and replaceable following standard procedure.
+ *Airlocks will not be able to operate autonomously until the electronics are replaced.
+
Hair sprites have been given a visual lift. For best results, set your hair color to be slightly lighter than how you want it to look. For dark hair, do not use a color darker than 80% black.
+
+
+
22 March 2014
+
Giacom updated:
+
+
Gravity is no longer beamed from CentCom! Instead, there is a new gravity generator stored near Engineering that will provide all the gravity for the station and the z level. Please remember that it gives off a lot of radiation when charging or discharging, so wear protection.
+
+
MrPerson updated:
+
+
Added a new, somewhat experimental system to delete things. This is basically a port from /vg/, so big thanks to N3X15.
+
As a result, bombs and the singularity should be much less laggy.
+
There may be issues with "phantom objects" or other problems with stuff that's suppoed to be deleted or objects interacting with deleted objects. Please report any issues right away!
+
+
+
18 March 2014
+
Ikarrus updated:
+
+
NT R&D; releases AI Patch version 2553.03.18, allowing all Nanotrasen AIs to override the access restrictions on any airlock in case of emergency. Nanotrasen airlocks have been outfitted with new amber warning lights that will flash while the override is active. Should maintenance teams need to restore an airlock's restrictions without using the AI, pulsing the airlock's ID Scanner wire will reset the override.
+
+
+
17 March 2014
+
Giacom updated:
+
+
Machines that had their programming overriden by a Malf AI will no longer attack loyal cyborgs; they will still attack cyborgs that aren't loyal, to the Malf AI that hacked them, though.
+
You only require a single unit of blood to mutate viruses now, instead of 5.
+
+
+
15 March 2014
+
Steelpoint/Validsalad updated:
+
+
After a review with CentCom, all Security Officers will now begin their shifts with a Stun Baton in their backpack. To avoid inflating costs however all Stun Batons have been removed from Security lockers except from the brig.
+
After decades of usage CentCom has replaced the Security Uniform, Armor, Belt and Helmet with a newer, more modern design.
+
+
+
14 March 2014
+
Ikarrus and Nienhaus updated:
+
+
Nanotrasen Corporate announced a revised dress codes primarily affecting senior station officers. A new uniform for Heads of Personnel will be shipped out to all NT Research stations.
+
+
+
12 March 2014
+
Yota updated:
+
+
Cameras finally capture the direction you are facing.
+
+
+
10 March 2014
+
Giacom updated:
+
+
Added two new plasma-level disease symptoms, both are very !FUN! and mess with your genetics.
+
Virologists now only need to use a single unit of virus food, mutagen or plasma to generate symptoms. You can take a single unit by using a dropper, if you change its unit transfer amount in the object tab..
+
Changed the map to make it harder to escape from the Labor Camp. Please continue using it.
+
+
Miauw updated:
+
+
Added changeling space suits. These allow changelings to survive in space, internals are not needed. They do not provide any sort of armor and slow your chemical regeneration.
+
+
+
05 March 2014
+
Various Coders updated:
+
+
Ling rounds are LINGIER, with more lings at a given population.
+
Many simple animals can ventcrawl, including bats. All ventcrawlers must alt-click to ventcrawl.
+
An automatic tool to store and replace machine parts has been added to R&D.;
+
Most stuns have been reduced in from 10+ to 5 ticks in duration.
+
Shoes no longer speed you up.
+
Added fancy suits, which can be orderd from cargo.
+
Added sombrerors and ponches.
+
Cuff application time reduced 25%.
+
Fire will cause fuel tanks to explode.
+
Mutagen has been reduced in lethality. Notably, 15 units are not guaranteed to crit the recipient.
+
+
+
03 March 2014
+
ChuckTheSheep updated:
+
+
Round-end report now shows what items were bought with a traitor's or nuke-op's uplink and how many TCs they used.
+
+
Drovidi Corv updated:
+
+
Facehuggers no longer die to zero-force weapons or projectiles like lasertag beams.
+
+
Incoming5643 updated:
+
+
Improved blob spores to zombify people adjacent to their tile.
+
If a hand-teleporter is activated while you're stuck inside a wall, you'll automatically go through it.
+
+
Miauw updated:
+
+
Decreased the lethality of Mutagen in small doses.
+
+
TZK13 updated:
+
+
Added Incendiary Shotgun Shells made at a hacked autolathe; Xenos be wary.
+
+
+
26 February 2014
+
Incoming5643 updated:
+
+
Color blending Kitty Ears have returned.
+
+
+
+
+
25 February 2014
+
Incoming5643 updated:
+
+
Colored burgers! Include a crayon in your microwave when cooking a burger and it'll come out just like a Pretty Pattie.
+
Fixed AI's being able to interact with syndicate bombs.
+
+
+
+
+
24 February 2014
+
Ergovisavi updated:
+
+
Added Advanced Mesons and Night Vision Goggles to the protolathe.
+
+
Giacom updated:
+
+
Silicons no longer stop statues from moving when in sight.
+
Increased the health of statues.
+
Increased the range of statues's blinding spell.
+
+
HornyGranny updated:
+
+
Reduced the range of Violin notes.
+
Low quality violin midi-notes replaced with better .ogg-notes.
+
+
Incoming5643 updated:
+
+
Improved the Syndicate Implant bundle to contain freedom, uplink, EMP, adrenalin and explosive implanters.
+
Added Mineral Storeroom to R&D; containing the Ore Redemption Machine; no more assistants stealing your ore in the open hallway.
+
+
Miauw updated:
+
+
No more removing cursed horseheads.
+
+
Razharas updated:
+
+
Fixed infinite telecrystal exploit.
+
+
+
+
+
19 February 2014
+
Aranclanos updated:
+
+
Removed the chance of your hat falling off when slipping.
Mining has been significantly overhauled. Hostile alien life has infested the western asteroid! Miners are given an equipment voucher to obtain equipment to protect themselves. There is a spare voucher in the HoP's office, should someone want to switch to mining mid-shift.
+
The Ore Redemption Machine has been added just outside of the Science wing. Ore goes in, Sheets go out, and points are tallied on the machine. Insert your ID to claim points, then spend them on goods at Mining Equipment Lockers. You require specific accesses to tell the Ore Redemption Machine to unload its sheets.
+
Should you not care for being eaten alive by horrible alien life, it is suggested you stick to the eastern asteroid, where there is no hostile alien life... though the yield on ore is not as good.
+
Most ore is no longer visible to the naked eye. You must ping it with a Mining Scanner (Available in all mining lockers) to locate nearby ore. Make sure to equip your mesons first, or it won't be visible.
+
Mesons no longer remove the darkness overlay, you must properly light your environment. Hull breaches to space can still be clearly seen, and it will still protect you from the singulo.
+
Mineral spawn rates have been significantly tweaked to cut down on unnecessary inflation of mineral economy.
+
The asteroid no longer has ambient power on the entire asteroid. AI's who go onto asteroid turf will slowly die of power loss. Mining outposts are unaffected.
+
Fixed an issue where projectiles shot by simple mobs or thrown would hit multiple times.
+
+
Fleure updated:
+
+
Reduced chicken crates to contain 1-3 chicks, down from 3-6
+
Increased fertile chicken egg chance from 10% to 25%
+
+
Yota updated:
+
+
Photograph now rendered crisp and clean, so that we may enjoy them in their 0.009 megapixel goodness.
+
Cameras should now capture more of what they should, and less of what they shouldn't.
+
+
+
+
+
09 February 2014
+
ADamDirtyApe updated:
+
+
The lawyer now spawns with a Sec Headset.
+
+
Demas updated:
+
+
Banana peel size is based on banana size.
+
Bigger peels slip for longer than smaller ones. Remember that produce size is based on potency.
+
The clown now spawns with a decent sized banana.
+
The banana mortar now shoots 65 potency peels.
+
+
Giacom updated:
+
+
A new hostile statue mob, can only move when not being observed, tends to break lights and cause blindness.
+
+
Incoming5643 updated:
+
+
Blob Zombies! When a Blob Spore moves over a dead human body it will infect the body and revive it as a more powerful varient of the spore. Has double the health and deals more damage.
+
+
Neerti updated:
+
+
Sec Belts can now hold Stun Batons.
+
Stun Batons now only take 10 seconds to fully recharge.
+
+
Razharas updated:
+
+
Tables can now be used as an alternative way to craft makeshift items. Simply click-drag table to yourself bring up a list.
+
+
adrix89 updated:
+
+
Spray Bottles can no longer wet up to three tiles with water.
+
Spray Bottles have a third higher release volume that wets a single tile.
+
Water slip times are reduced to the same stun times as soap.
+
+
+
+
+
08 February 2014
+
MrPerson updated:
+
+
Added a NanoUI for the SMES
+
+
Razharas updated:
+
+
Adds more constructible and deconstructable machines!
+
Added constructible miniature chemical dispensers, upgradable
+
Sleepers are now constructible and upgradable, open and close like DNA scanners, and don't require a console
+
Cryogenic tubes are now constructible and upgradable, open and close like DNA scanners, and you can set the cryogenic tube's pipe's direction by opening its panel and wrenching it to connect it to piping
+
Telescience pads are now constructible and upgradable
+
Telescience consoles are now constructible
+
Telescience tweaked (you can save data on GPS units now)
+
Teleporters are now constructible and upgradable, the console has a new interface and you can lock onto places saved to GPS units in telescience
+
Teleporters start unconnected. You need to manually reconnect the console, station and hub by opening the panel of the station and applying wire cutters to it.
+
Biogenerators are now constructible and upgradable
+
Atmospherics heaters and freezers are now constructible and upgradable and can be rotated with a wrench when their panel is open to connect them to pipes. Screw the board to switch between heater and freezer.
+
Mech chargers are now constructible and upgradable
+
Microwaves are now constructible and upgradable
+
All kitchen machinery can now be wrenched free
+
SMES are now constructible
+
Dragging a human's sprite to a cryogenic tube or sleeper will put them inside and activate it if it's cryo
+
Constructible newscasters, their frames are made with autolathes
+
Constructible pandemics
+
Constructible power turbines and their computers
+
Constructible power compressors
+
Constructible vending machines. Screw the board to switch vendor type.
+
Constructible hydroponics trays
+
Sprites for all this
+
This update will have unforeseen bugs, please report those you find at https://github.com/tgstation/-tg-station/issues/new if you want them fixed.
+
As usual, machines are deconstructed by screwing open their panels and crowbarring them. While constructing machines, examining them will tell you what parts you're missing.
+
+
+
+
+
05 February 2014
+
Yota updated:
+
+
Handling a couple flashlights will no longer transform you into the sun. Each light source will have deminishing returns.
+
Inserting lights into containers should no longer dim other lights.
+
+
+
+
+
03 February 2014
+
Demas updated:
+
+
Changes windoor and newscaster attack messages to be consistent with windows and grilles. This removes the distracting boldness from windoor attack messages, which is reserved for userdanger, and names the attacker.
+
Thrown items now play a sound effect on impact. The volume of the sound is based on the item's throwforce and/or weight class.
+
The fireaxe now has 15 throwforce, when previously it had only 1. But why would you throw it away, anyway?
+
Projectiles now play a sound upon impact. The volume of the sound depends on the damage done by the projectile. Damageless projectiles such as electrodes have static volumes. Practise laser and laser tag beams have no impact sound.
+
Added sear.ogg for the impacts of damaging beams such as lasers. It may be replaced if player feedback proves negative.
+
+
+
+
+
02 February 2014
+
Demas updated:
+
+
Attack sounds for all melee weapons! No more silent attacks.
+
The volume of an object's attack sound is based on the weapon's force and/or weight class.
+
Welders, lighters, matches, cigarettes, energy swords and energy axes have different attack sounds based on whether they're on or off.
+
Weapons that do no damage play a tap sound. The exceptions are the bike horn, which still honks, and the banhammer, which plays adminhelp.ogg. Surely nothing can go wrong with that last one.
+
When you tap someone with an object, the message now uses "tapped" or "patted" instead of attacked. The horn still uses HONKED, and the banhammer still uses BANNED.
+
You won't get the "armour has blocked an attack" message for harmless attacks anymore.
+
Adds 5 force to the lighter when it's lit. Same as when you accidentally burn yourself lighting it.
+
Removes boldness from item attack messages on non-human mobs. The attack is bolded for a player controlling a non-human mob. Now your eyes won't jump to the chat when it's Pun Pun who's being brutalised.
+
Blood will no longer come out of non-human mobs if the attack is harmless.
+
Adds a period at the end of the catatonic human examine message. That's been bugging me for years.
+
The activation and deactivation sounds of toy swords, energy swords and energy shields are slightly quieter. Energy swords and shields are now slightly louder than toys.
+
You can no longer light things with burnt matches.
+
Match, cigarette and lighter attack verbs, forces and damage types change based on whether the object is lit or not.
+
Fixes a bug with the energy blade that kept it at weight class 5 after it was deactivated. Who cares, it disappears upon deactivation.
+
Changes the welder out of fuel message slightly to be less fragmented.
+
Removes dead air from a lot of weapon sound effects to make them more responsive. In other words, the fire extinguisher attack sound will play a lot sooner after you attack than before.
+
Equalised the peak volumes of most weapon sounds to be -0.1dB in an attempt to make volumes based on force more consistent across different sounds.
+
+
+
+
+
01 February 2014
+
Ergovisavi updated:
+
+
Walking Mushrooms will now attack and eat each other! They're all a little unique from each other, no two shrooms are exactly alike, and a better quality harvest means stronger Walking Shrooms. Pit them against each other for your entertainment.
+
Each mushroom will have a different colored cap to identify them. When mushrooms eat each other, they get stronger. The resulting mushroom will drop more slices when cut down to harvest, and will have better quality slices.
+
Don't hurt them yourself, though, or you'll bruise them, and mushrooms won't get stronger from eating a bruised mushroom. If your mushroom faints, feed it a mushroom such as a plump helmet to get it back on its feet. It will slowly regenerate to full health eventually.
+
+
+
+
+
30 January 2014
+
Balrog updated:
+
+
Syndicate Playing Cards can now be found on the Syndicate Mothership and purchased from uplinks for 1 telecrystal.
+
Syndicate Playing Cards are lethal weapons both in melee and when thrown, but make the user's true allegiance to the Syndicate obvious.
+
Sprites are courtesy of Nienhaus.
+
+
Demas updated:
+
+
Adds thud sounds to falling over
+
Known bug: Thuds play when cloning initialises or someone is put into cryo. This will be fixed.
+
+
Ergovisavi updated:
+
+
Gibtonite, the explosive ore, can now be found on the asteroid. It's very hard to tell between it and diamonds, at first glance.
+
Gibtonite deposits will blow up after a countdown when you attempt to mine it, but you can stop it with an analyzer at any time. It makes for a good mining explosive.
+
The closer you were to the explosion when you analyze the Gibtonite deposit, the better the Gibtonite you can get from it.
+
Once extracted, it must be struck with a pickaxe or drill to activate it, where it will go through its countdown again to explode!
+
Explosives will no longer destroy the ore inside of asteroid walls or lying on the floor.
+
+
Miauw updated:
+
+
Adds changeling arm blades that cost 20 chems and do 25 brute damage.
+
Arm blades can pry open unpowered doors, replace surgical saws in brain removal, slice tables and smash computers.
+
+
MrPerson updated:
+
+
Mobs now lie down via turning icons rather than preturned sprites.
+
You can lie down facing up or down and the turn can be 90 degrees clockwise or counterclockwise.
+
Resting will always make you lie to the right so you look good on beds.
+
Please report any bugs you find with this system.
+
+
Petethegoat updated:
+
+
Increased the walk speed. Legcuffed speed is unaffected, and is still suffering.
+
Sped up alien drones, they are now the same speed as sentinels.
+
+
+
+
+
28 January 2014
+
Demas updated:
+
+
Adds thud sounds to falling over
+
Known bug: Thuds play when cloning initialises or someone is put into cryo. This will be fixed.
+
+
+
+
+
26 January 2014
+
Balrog updated:
+
+
Syndicate Playing Cards can now be found on the Syndicate Mothership and purchased from uplinks for 1 telecrystal.
+
Syndicate Playing Cards are lethal weapons both in melee and when thrown, but make the user's true allegiance to the Syndicate obvious.
+
Sprites are courtesy of Nienhaus.
+
+
+
+
+
25 January 2014
+
Miauw updated:
+
+
Adds changeling arm blades that cost 20 chems and do 25 brute damage.
+
Arm blades can pry open unpowered doors, replace surgical saws in brain removal, slice tables and smash computers.
+
+
+
+
+
24 January 2014
+
Ergovisavi updated:
+
+
Gibtonite, the explosive ore, can now be found on the asteroid. It's very hard to tell between it and diamonds, at first glance.
+
Gibtonite deposits will blow up after a countdown when you attempt to mine it, but you can stop it with an analyzer at any time. It makes for a good mining explosive.
+
The closer you were to the explosion when you analyze the Gibtonite deposit, the better the Gibtonite you can get from it.
+
Once extracted, it must be struck with a pickaxe or drill to activate it, where it will go through its countdown again to explode!
+
Explosives will no longer destroy the ore inside of asteroid walls or lying on the floor.
+
+
+
+
+
21 January 2014
+
MrPerson updated:
+
+
Mobs now lie down via turning icons rather than preturned sprites.
+
You can lie down facing up or down and the turn can be 90 degrees clockwise or counterclockwise.
+
Resting will always make you lie to the right so you look good on beds.
+
Please report any bugs you find with this system.
+
+
+
+
+
19 January 2014
+
KazeEspada updated:
+
+
The water cooler is now stocked with paper cups. You can refill the cups by putting paper in it.
+
+
Rolan7 updated:
+
+
You can now sell mutant seeds, from hydroponics, to Centcom via the supply shuttle.
+
Fixes powersinks causing APCs to stop automatically recharging.
+
+
+
+
+
17 January 2014
+
ManeaterMildred updated:
+
+
Changed the way the Gygax worked. It now has less defense and shot deflection, but is faster and have less battery drain per step.
+
Nerfed the Carbine's brute damage and renamed it to FNX-99 "Hades" Carbine.
+
Nerfed the Gygax defense from 300 to 250.
+ Nerfed the Gygax projectile deflection chance from 15 to 5.
+ Buffed the Gygax speed from 3 to 2, making it faster.
+ Reduced the battery use when moving from 5 to 3.
+
+ The Mech Ion Rifle now has a faster cooldown, from 40 to 20.
+ Nerfed the carbine's Brute Damage from 20 to 5.
+
+ Please post feedback to these changes on the forums.
+
+
+
+
+
+
15 January 2014
+
Dumpdavidson updated:
+
+
EMPs affect the equipment of a human again.
+
EMP flashlight now recharges over time and its description no longer reveals the illegal nature of the device.
+
EMP implant now has two uses. EMP kit now contains two grenades.
+
+
+
+
+
14 January 2014
+
Fleure updated:
+
+
Added spider butchery.
+
Added spider meat, legs, and edible eggs.
+
Added new spider related meals.
+
+
Giacom updated:
+
+
Because of an emagged cyborg's explosion, the MMI will die with it.
+
The self-respiration symptom will now properly keep you from dying of oxygen loss.
+
The Stimulant symptom's activation chance was increased so you had a constant flow of hyperzine.
+
+
Incoming updated:
+
+
A new training bomb has been added to the armoury, which will allow you to train your wire cutting skills to disarm real syndicate bombs.
+
+
ManeaterMildred updated:
+
+
Updated research designs.
+
The Protolathe can now build a Ion Rifle.
+
The Exosuit Fabricator can now build a Mech Ion Rifle, a Mech Carbine and a Mech-Mounted Missile Rack.
+
+
SirBayer updated:
+
+
Armor now reduces damage by the protection percentage, instead of randomly deciding to half or full block damage by those percentages.
+
Shotguns, with buckshot shells, will fire a spread of pellets at your target, like a real shotgun blast.
+
+
+
+
+
12 January 2014
+
VistaPOWA updated:
+
+
Added Syndicate Cyborgs.
+
They can be ordered for 25 telecrystals by Nuclear Operatives. A ghost / observer with "Be Operative" ticked in their game options will be chosen to control it.
+
Their loadout is: Crowbar, Flash, Emag, Esword, Ebow, Laser Rifle. Each weapon costs 100 charge to fire, except the Esword, which has a 500 charge hitcost. Each borg is equipped with a 25k cell by default.
+
Syndicate borgs can hear the binary channel, but they won't show up on the Robotics Control computer or be visible to the AI. Their lawset is the standard emag one.
+
Added two cyborg recharging stations to the Syndicate Shuttle.
+
+
+
+
+
11 January 2014
+
Cheridan updated:
+
+
You can now upgrade laser pointers with micro laser parts. It will increase the chance of blinding people.
+
+
Errorage updated:
+
+
Cyborg modules now use a new UI, which is much quicker than a menu.
+
+
Giacom updated:
+
+
The game will now have all background operations disabled, this will result in smoother gameplay but may result in some spike lags, before being set back to normal. The gradually increasing lag should now be gone.
+
You can now emag the crusher, in disposals, to remove the safety. You can use a screwdriver to reset it to the default factory settings.
+
The toxin compensation symptom will stop giving you toxin damage while at full health.
+
+
JJRCop updated:
+
+
The new nuke toy can now be found in your local arcade machine.
+
+
+
+
+
10 January 2014
+
ChuckTheSheep updated:
+
+
Morgue Trays can detect players in their bodies and will now change colour depending on a few things. Red = Dead body with no player inside. Orange = No body but items. Green = A dead body with a player inside.
+
+
Giacom updated:
+
+
You can whisper while in critical, but you will immediately die afterwards DRAMATICALLY. The closer you are to death, the less you can say.
+
The wizard won't spawn so much smoke after they blink.
+
The detective's forensic scanner was upgraded so that it can now scan from afar.
+
Made the ghost's follow button less buggy. Please make an issue report if it still bugs out.
+
+
Rumia29, Nienhaus updated:
+
+
A new alt RD uniform spawns in his locker.
+
+
+
+
+
07 January 2014
+
Fleure updated:
+
+
Janitor now spawns with a service headset.
+
Backpack watertank slowdown decreased.
+
+
+
+
+
04 January 2014
+
Razharas updated:
+
+
Constructable machines now depend on R&D; parts!
+
DNA scanner: Laser quality lessens irradiation. Manipulator quality drastically improves precision (9x for best part) and scanner quality allows you to scan suicides/ling husks, with the best part enabling the cloner's autoprocess button, making it scan people in the scanner automatically.
+
Clone pod: Manipulator quality improves the speed of cloning. Scanning module quality affects with how much health people will be ejected, will they get negative mutation/no mutations/clean of all mutations/random good mutation, at best quality will enable clone console's autoprocess button and will try to clone all the dead people in records automatically, together with best DNA scanner parts cloning console will be able to work in full automatic regime autoscanning people and autocloning them.
+
Borg recharger: Capacitors' quality and powercell max charge affect the speed at which borgs recharge. Manipulator quality allows borg to be slowly repaired while inside the recharges, best manipulator allows even fire damage to be slowly repaired.
+
Portable power generators: Capacitors' quality produce more power. Better lasers consume less fuel and reduce heat production PACMAN with best parts can keep whole station powered with about sheet of plamsa per minute (approximately, wasn't potent enough to test).
+
Autolathe: Better manipulator reduces the production time and lowers the cost of things(they will also have less m_amt and g_amt to prevent production of infinity metal), stacks' cant be reduced in cost, because thatll make production of infinity metal/glass easy
+
Protolathe: Manipulators quality affects the cost of things(they will also have less m_amt and g_amt to prevent production of infinity metal), best manipulators reduces the cost 5 times (!)
+
Circuit imprinter: Manipulator quality affects the cost, best manipulator reduce cost(acid insluded) 4 times, i.e. 20 boards per 100 units of acid
+
Destructive analyzer: Better parts allow items with less reliability in. Redone how reliability is handled, you now see item reliability in the deconstruction menu and deconstructing items that has same or one point less research type level will rise the reliability of all known designs that has one or more research type requirements as the deconstructed item. Designs of the same type raise in reliability more. Critically broken things rise reliability of the design drastically. Whole reliability system is not used a lot but now at least on the R&D; part it finally matters.
+
+
+
+
+
02 January 2014
+
Demas updated:
+
+
Added different colours to departmental radio frequencies. Now you'll be able to filter out or pay attention to each frequency a lot easier.
+
+
Fleure updated:
+
+
Fixed bruisepacks and ointments not working.
+
Fixed injecting/stabbing mouth or eyes when only thick suit worn.
+
+
+
+
+
01 January 2014
+
Errorage updated:
+
+
The damage overlay for humans starts a little later than before. It used to start at 10 points of fire + brute damage, it now starts at 35.
+
+
+
+
+
31 December 2013
+
Fleure updated:
+
+
Paper no longer appears with words on it when blank input written or photocopied
+
Vending machine speaker toggle button now works
+
Building arcade machines works again
+
+
+
+
+
27 December 2013
+
Giacom updated:
+
+
Light explosions will no longer gib dead bodies anymore. C4 will function the same and gib anything attached.
+
Syringe gun projectiles will now display a message when shots are deflected by space suits, biosuits and etc...
+
You can now move out of sleepers by moving, again.
+
+
Miauw updated:
+
+
Monkeys now have a resist button on their HUD.
+
+
+
+
+
21 December 2013
+
Bobylein updated:
+
+
Labcoats can now store bottles, beakers, pills, pill bottles and paper.
+
+
Giacom updated:
+
+
The Labor Camp has been changed based on feedback. Ore boxes added, internals added, safety pickaxes/shovels (might revert if unliked).
+
Labor Camp prisoners, who have earned enough points for freedom, will now have to be alone in the shuttle to move it and to open the middle door; this is in order to prevent free'd prisoners from releasing their comrades.
+
Monkeys no longer walk away when being pulled or grabbed.
+
Anti-breach shield generators have a greater range, and will cover more exposed space tiles.
+
+
Jordie updated:
+
+
Blowing up borgs from the Robotics Console will now actually make them explode. Emagged Cyborgs will explode even more.
+
+
Nienhaus updated:
+
+
Added more poster.
+
+
Perakp updated:
+
+
You can no longer slip while laying down.
+
+
RobRichards, Validsalad updated:
+
+
Added new sprites for the sec-hailer, SWAT gear and riot armour.
+
+
+
+
+
18 December 2013
+
Adrinus updated:
+
+
Playing cards, just like the real thing! Play poker, blackjack, go fish, the limits are limitless! Recommended to use space cash as the poker chips for now
+
+
Giacom updated:
+
+
THE REALISM: Injectors such as syringes, parapens and hypos will not penetrate coveralls with thick material, this includes space suits, biosuits, bombsuits, and their head slot equivalent. Injectors now also consider where you aim, if you aim for the head it will try to use it through the head slot, otherwise it will aim for the body. To clarify, if you are wearing a space helmet and a doctor tries to inject you while aiming at your head, it will protect you until they aim for the unprotected body; same story for wearing a space suit and not a helmet.
+
The syndicate shuttle has been heavily upgraded with a brand new technology which allows the blast doors to the entrance of the shuttle to AUTOMATICALLY close. Whoa. This brand new technology will help keep out those snoopy crew members.
+
Lowered the cooldown of creating virus cultures to 5 seconds. You now only need to mix one unit of synaptizine, in a blood full of an advance virus, to get it to remove a random symptom.
+
+
Incoming updated:
+
+
Rounds will no longer end when the wizard dies and there are still apprentices or traitors/survivors..
+
+
JJRcop updated:
+
+
Transit tube tweaks. You can now put someone into a transit tube pod using grabs and you can empty a transit tube pod by clicking on it.
+
+
Jordie0608 updated:
+
+
Replaced the digital valves in atmospherics with pumps.
+
+
+
+
+
14 December 2013
+
Incoming updated:
+
+
Magic Mania! Powerful new magical tools and skills for wizard and crew alike!
+
Beware the new Summon Magic spell, which will grant the crew access to magical tools and spells and cause some to misuse it!
+
One Time Spellbooks that can be spawned during summon magic that can teach a low level magic skill to anyone! Beware the effects of reading pre-owned books, the magical rights management is potent!
+
Magical Wands that can be spawned during Summon Magic! They come in a variety of effects that mimic both classical wizard spells and all new ones. They come precharged but lack the means to refill them once their magical energy is depleted... Fire efficently!
+
Be aware of the new Charge spell, which can take normally useless spent wands and give them new life! This mysterious effect has been found to wear down the overall magical potency of wands over time however. Beyond wands the clever magical user can find ways to use this spell on other things that may benefit from a magical charge...
+
The Staff of Resurrection, which holds intense healing magics able to defeat death itself! Look out for this invaluable magical tool during castings of Summon Magic.
+
Be on the lookout for a new apprentice! This noble mage is a different beast from most wizards, trained in the arts of defending and healing. Too bad he still works for the wizard!
+
+
+
+
+
09 December 2013
+
Giacom updated:
+
+
New colourful ghost sprites for BYOND members. Sprites by anonus.
+
+
+
+
+
08 December 2013
+
Rolan7 updated:
+
+
Leather gloves can be used to removes lights.
+
Plant, ore, and trash bags have a new option to pick up all items of single type
+
Creating astroturf now works like sandstone, converting all the grass at once.
+
Uranium and radium can be used instead of mutagen. 10 can mutate species, 5 or 2 mutate traits. Highly toxic.
+
Plants require a little light to live. Mushroom require even less (2 units vs 4) and take less damage.
+
+
+
+
+
05 December 2013
+
Razharas updated:
+
+
Reworked how ling stings are done, now when you click a sting in the changeling tab it becomes current active sting, the icon of that sting appears under the chem counter, alt+clicking anyone will sting them with current sting, clicking the icon of the sting will unset it.
+
Monkeys have ling chem counter and active sting icons in their UI.
+
Going monkey -> human will try to equip the human with everything on the ground below it.
+
+
+
+
+
02 December 2013
+
Giacom updated:
+
+
A less annoying virology system! From now on, you can only get low level virus symptoms from virus food, medium level virus symptoms from unstable mutagen and high level virus symptoms from liquid plasma. You can find a list of symptoms, and which chemicals are required to get them, here: http://wiki.ss13.eu/index.php/Infections#Symptoms_Table
+
The virologist starts with a bottle of plasma in his smart fridge.
+
Made it so you cannot accidentally click in the gaps between chem masters.
+
+
+
+
+
01 December 2013
+
cookingboy3 updated:
+
+
Added three new buttons to the sandbox panel.
+
Removed canister menu, replaced it with buttons.
+
Players can no longer spawn "dangerous" canisters in sandbox, such as Plasma, N20, CO2, and Nitrogen.
+
+
+
+
+
30 November 2013
+
Yota updated:
+
+
The identification console will now require that ID and job names follow the same restrictions as player names.
+
NTSL scripts and parrots should now handle apostrophes and such properly. It's about time.
+
NTSL scripts now have a better sense of time.
+
+
+
+
+
28 November 2013
+
Malkevin updated:
+
+
Made the suit storage on the Captain's Tunic more useful than just a place to store your e-o2 tank. You can now store the nuke disk, stamps, medal box, flashes and melee weapons (mainly intended for the Chain of Command), and of course - smoking paraphernalia
+
+
+
+
+
27 November 2013
+
RobRichards updated:
+
+
Nanotrasen surgeons are now certified to perform Limb replacements, The robotic parts used in construction of Nanotrasen Cyborgs are the only parts authorized for crew augmentation, these replacement limbs can be repaired with standard welding tools and cables.
+
+
+
+
+
17 November 2013
+
Laharl Montgommery updated:
+
+
AI can now anchor and unanchor itself. In short, it means the AI can be dragged, if it wants to.
+
+
+
+
+
29 September 2013
+
RobRichards updated:
+
+
Nanotrasen Cyborg Upgrades:
+Standard issue Engineering cyborgs now come equipped with replacement floor tiles which they can replenish at recharge stations.
+
+
+
+
+
+
28 September 2013
+
Ergovisavi updated:
+
+
Mobs can now be lit on fire. Wearing a full firesuit (or similar) will protect you. Extinguishers, Showers, Space, Cryo, Resisting, being splashed with water can all extinguish you. Being splashed with fuel/ethanol/plasma makes you more flammable. Water makes you less flammable.
+
+
+
+
+
+
26 September 2013
+
Cheridan updated:
+
+
Nanotrasen Anomaly Primer:
+ Unstable anomalies have been spotted in your region of space. These anomalies can be hazardous and destructive, though our initial encounters with these space oddities has discovered a method of neutralization. Method follows.
+
Step 1. Upon confirmation of an anomaly sighting, report its location. Early detection is key.
+ Step 2. Using an atmospheric analyzer at short range, determine the frequency that the anomaly's core is fluctuating at.
+ Step 3. Send a signal through the frequency using a radio signaller. Note that non-specialized signaller devices may possibly lack the frequency range needed.
+ With the anomaly neutralized and the station brought out of danger, inspect the area for any remnants of the anomaly. Properly researched, we believe these events could provide vast amounts of valuable data.
+ Did you find this report helpful?
+
+
+
+
+
+
+
21 September 2013
+
Malkevin updated:
+
+
Due to complaints about Security not announcing themselves before making arrests NT has now issued it's Sec team with loud hailer integrated gas masks, found in their standard equipment lockers. Users can adjust the mask's level of aggression with a screwdriver.
Juggernaut's ablative armor has been adjusted. They have a greater chance to reflect lasers however on reflection they take half damage instead of no damage, basically this adjustment means you should be able to kill a Juggernaut with two laser guns instead of four! Also their reflection spread has been greatly widened, enjoy the lightshow
+
Cargo can now order exile implants.
+
Checking a collector's last power output via analyzers has been moved to multitools, because that actually made sense (betcha didn't know this existed, I know I didn't)
+
Analyzers can now be used to check the gas level of the tank in a loaded radiation collector (yay no more crowbars), you can also use them on pipes to check gas levels (yay no more pipe meters)
+
+
+
+
+
+
+
17 September 2013
+
SuperSayu updated:
+
+
You can no longer strip people through windows and windoors
+
You can open doors by hand even if there is a firedoor in the way, making firedoor+airlock no longer an unbeatable combination
+
Ghosts can now click on anything to examine it, or double click to jump to a turf. Double clicking a mob, bot, or (heaven forbid) singularity/Nar-Sie will let you follow it. Double clicking your own corpse re-enters it.
+
AI can double click a mob to follow it, as well as double clicking turfs to jump.
+
Ventcrawling mobs can alt-click a vent to start ventcrawling.
+
Telekinesis is now part of the click system. You can click on buttons, items, etc, without having a telekinetic throw in hand; the throw will appear when you click on something you can move (with your mind).
+
+
+
+
+
+
13 September 2013
+
JJRcop updated:
+
+
We at Nanotrasen would like to assure you that we know the pain of waiting five minutes for the emergency shuttle to be dispatched in a high-alert situation due to our confirmation-of-distress policy. Therefore, we have amended our confirmation-of-distress policy so that, in the event of a red alert, the distress confirmation period is shortened to three minutes and we will hurry in preparing the shuttle for transit. This totals to 6 minutes, in hope that it will give our very expensive equipment a better chance of recovery.
+
+
+
+
+
+
3 September 2013
+
Cael_Aislinn updated:
+
+
Terbs Fun Week Day 5: Chef gets a Nanotrasen-issued icecream machine with four pre-approved icecream flavours and two official cone types.
+
+
+
+
+
+
2 September 2013
+
Cael_Aislinn updated:
+
+
Terbs Fun Week Day 4: Humans, aliens and cyborgs now show speech bubbles when they talk.
+
+
+
+
+
+
1 September 2013
+
Cael_Aislinn updated:
+
+
Terbs Fun Week Day 3: Detective can reskin his gun to one of five variants: Leopard Spots, Gold Trim, Black Panther, Peacemaker and the Original.
+
+
+
+
+
+
12 September 2013
+
AndroidSFV updated:
+
+
AI Photography: AIs now have two new verbs, Take Picture and View Picture. The pictures the AI takes are centered on the AI's eyeobj. You can use these pictures on a newscaster, and print them at a photocopier.
+
+
+
+
+
+
31 August 2013
+
Cael_Aislinn updated:
+
+
Terbs Fun Week Day 2: RD, lawyers and librarians now spawn with a laser pointer. Don't point them in anyone's eyes!
+
+
+
+
+
+
30 August 2013
+
Cael_Aislinn updated:
+
+
Terbs Fun Week Day 1: Added ghost chilis as a mutation of chili plants. Be careful, they're one of the hottest foods in the galaxy!
+
+
+
+
+
+
21 August 2013
+
Dumpdavidson updated:
+
+
Replaced the EMP grenades from the uplink with an EMP kit. The kit contains a grenade, an implant and a flashlight with 5 uses that can EMP any object or mob in melee range.
+
+
+
+
+
+
18 August 2013
+
Delicious updated:
+
+
Made time and date consistent across medical and security records, mecha logs and detective scanner reports
+
Added date to PDA
+
+
+
+
+
+
13 August 2013
+
Giacom updated:
+
+
Malf AIs now have a new power which will spawn a "borging machine". This machine will turn living humans into loyal cyborgs which the AI can use to take over the station with. The AI will limit themselves by using this ability, such as no shunting, and the machine will have a long cooldown usage.
+
+
+
+
+
+
+
+
+
12 August 2013
+
Giacom updated:
+
+
Changed the blob balance to make the blob start strong but grow slower, resulting in rounds where the blob doesn't instantly get killed off if found out and doesn't immediately dominate after being left alone long enough. AIs no longer have to quarantine the station.
+
+
+
+
+
+
10 August 2013
+
Malkevin updated:
+
+
Cargo Overhaul: Phase 1
+
Ported Bay's cargo computer categoy system
+
Crates have been tweaked significantly. Crates have been reduced to single item types where possible, namely with expensive crates such as weapons and armor. A total of 28 new crates have been added, including chemical and tracking implants, and raw materials can also be bought from cargo for a significant number of points (subject to change)
+
This was a pretty large edit of repetitive data, so no doubt I've made a mistake or two. Please report any bugs to the usual place
+
+
+
+
+
+
6 August 2013
+
Giacom updated:
+
+
NTSL no longer allows you to use a function within another function parameter. This was changed to help prevent server crashes; if your working script no longer compiles this is why.
+
+
+
+
+
+
5 August 2013
+
Kaze Espada updated:
+
+
Nanotrasen has recentely had to change its provider of alcoholic beverages to a provider of lower quality. Cases of the old ailment known as alcohol poisoning have returned. Bar goers are to be weary of this new condition.
+
+
+
+
+
+
4 August 2013
+
Giacom updated:
+
+
Nanotrasen has re-arranged the station blueprint designs to have non-essential APCs moved to the maintenance hallways. Non-essential rooms that aren't connected to a maintenance hallway will have their APC remain. Station Engineers will now have easy access to a room's APC without needing access themselves. Nanotrasen also wishes to remind you that you should not sabotage these easy to access APCs to cause distractions or to lockdown someone in a location. Thank you for reading.
+
+
+
+
+
+
31 July 2013
+
Ricotez updated:
+
+
Atmospherics now has its own hardsuit. Instead of radiation protection it offers fire protection.
+
+
+
+
+
+
21 July 2013
+
Malkevin updated:
+
+
Cultists now start with two words each, and the starting talisman no longer damages you when you use it
+
+
+
+
+
+
+
21 July 2013
+
Cheridan updated:
+
+
Instead of a level-up system where it is possible to acquire all the skills, each skill now costs 1 point, and you can pick up to 5.Husking people, instead of giving you more XP to buy skills, now gives you a skill reset, allowing you to pick new ones.
+
DNA Extract Sting is now free, and is your main mode of acquiring DNA. You can hold up to 5 DNAs, and as you acquire more, the oldest one is removed. If you're currently using the oldest DNA strand, you will be required to transform before gaining more.
+
New abilities! An UI indicator for chemical storage! Fun!
+
+
+
+
+
+
16 July 2013
+
Malkevin updated:
+
+
Summary of my recent changes: Added a muzzle and a box of Prisoner ID cards to the perma wing, RnD can make a new combined gas mask with welding visor, added some atmos analyzers to atmospherics, air alarm circuit boards have their own sprites, package wrapped objects will now loop back round to the mail chute instead of auto-rerouting to disposals, and the detective and captain have access to securitrons through their PDA cartridges.
+
+
+
+
+
15 July 2013
+
Giacom updated:
+
+
A new item has been added to the syndicate catalog. The AI detector is a device disguised as a multitool; it is not only able to be used as a real multitool but when it detects an AI looking at it, or it's holder, it will turn red to indicate to the holder that he should cease supiscious activities. A great and cheap, to produce, tool for undercover operations involving an AI as the security system.
+
+
+
+
+
+
7 July 2013
+
Giacom updated:
+
+
Revamped blob mode and the blob random event to spawn a player controlled overmind that can expand the blob and upgrade pieces that perform particular functions. This will use resources which the core can slowly generate or you can place blob pieces that will give you more resources.
+
+
+
+
+
27 June 2013
+
Ikarrus updated:
+
+
Nanotrasen R&D released a new firmware patch for their station AIs. Included among the changes is the new ability for AIs to interact with fire doors. R&D officials state they feel giving station AIs more influence could only lead to good things.
+
+
+
+
+
16 June 2013
+
Khub updated:
+
+
Job preferences menu now not only allows you to left-click the level (i.e. [Medium]) to raise it, but also to right-click it to lower it. That means you don't have to cycle through all the levels to get rid of a [Low].
+
+
+
+
+
+
15 June 2013
+
Carnie updated:
+
+
DNA-scanner pods (DNA-modifier + cloning), now open and close in a similar fashion to closets. This means you click on them to open/close them. This change was to fix a number of issues, like items being lost in the scanner-pods.
+
As a side-effect, borgs can now clone humans. No harm can become a dead human, so they are not necessarily lawbound to clone them, and such tasks are probably best left to qualified genetics staff.
+
+
Petethegoat updated:
+
+
Updated chemical grenades. The build process is much the same, except they require an igniter-X assembly instead of a single assembly item. You can also just use a cable coil to get regular grenade behaviour.
+
+
+
+
+
9 June 2013
+
Ikarrus updated:
+
+
Server operators may now allow latejoiners to become antagonists. Check game_options.txt for more information.
+
Server operators may now set how traitors and changelings scale to population. Check game_options.txt for more information.
+
+
+
+
+
6 June 2013
+
Giacom updated:
+
+
Emptying someone's pockets won't display a message. In theory you can now pickpocket!
+
+
+
+
+
+
4 June 2013
+
Dumpdavidson updated:
+
+
Headsets can no longer broadcast into a channel that is disabled. Headsets now have a button to turn off the power instead of the speaker. This button disables all communication functions. EMPs now force affected radios off for about 20 seconds.
+
+
+
+
+
2 June 2013
+
Ikarrus updated:
+
+
To reduce costs of security equipment, mounted flashers have been adjusted to use common handheld flashes as their flashbulbs. Although these flashbulbs are more prone to burnout, they can easily be replaced with wirecutters.
+
+
+
+
+
25 May 2013
+
Ikarrus updated:
+
+
CentCom announced some minor restructuring within Space Station 13's command structure. Most notable of these changes is the removal of the Head of Personnel's access to the security radio channel. CentCom officials have stated the intention was to make the HoP's role more specialized and less partial towards security.
+
+
+
+
+
14 May 2013
+
Ikarrus updated:
+
+
Nanotrasen seeks to further cut operating costs on experimental cyborg units.
+ -Cyborg chassis will now be made from a cheaper but less durable design.
+ -RCDs found on engineering models have been replaced with a smaller model to make room for a metal rods module.
+ -Cyborg arms will no longer be long enough to allow for self-repairs.
+ NOTE: A cyborg's individual modules have been found to become non-operational should the unit sustain too much structural damage.
+
+
+
+
+
11 May 2013
+
Malkevin updated:
+
+
SecHuds now check for valid clearance before allowing you to change someone's arrest status. There is only one way to bypass the ID check, and its not the usual way.
+
+
+
+
+
+
7 May 2013
+
Ikarrus updated:
+
+
As a part of the most recent round of budget cuts, toolboxes will now be made with a cheaper but heavier alloy. HR reminds employees to avoid being struck with toolboxes, as toolbox-related injuries are not covered under the company's standard health plan.
+
+
+
+
+
5 May 2013
+
Rolan7 updated:
+
+
Cargo manifests from CentComm may contain errors. Stamp them DENIED for refunds. Doesn't apply to secure or large crates. Check the orders console for CentComm feedback.
+
+
+
+
+
2 May 2013
+
Malkevin updated:
+
+
You can now weld four floor tiles together to make a metal sheet
+
The All-In-One Grinder can now grind Metal, Plasteel, Glass, Reinforced Glass, and Wood sheets
+
Made grey backpacks slightly less ugly
+
+
+
+
+
30 April 2013
+
Ikarrus updated:
+
+
Researchers have discovered that glass shards are, in fact, dangerous due to their typically sharp nature. Our internal Safety Committee advises that glass shards only be handled while using Nanotrasen-approved hand-protective equipment.
+
+
+
+
+
24 April 2013
+
Carnie updated:
+
+
DNA reworked: All SE blocks are randomised. DNA-modifier emitter strength affects the size of the change in the hex-character hit. Emitter duration makes it more likely to hit the character you click on. Almost all DNA-modifier functions are on one screen. Balancing -will- be off a bit. Is getting halk to hard/easy? Please report bugs/balancing issues/concerns here: http://forums.nanotrasen.com/viewtopic.php?f=15&t=13083 <3
+
+
+
+
+
26 April 2013
+
Aranclanos updated:
+
+
Exosuit fabricators will now need to be manually updated
+
+
Ikarrus updated:
+
+
Commanding Officers of Nanotrasen Stations have been issued a box of medals to be awarded to crew members who display exemplary conduct.
+
+
+
+
+
+
23 April 2013
+
Malkevin updated:
+
+
Replaced the captain's run of the mill armored vest with his very own unique vest. Offers slightly better bullet protection.
+
+
+
+
+
+
22 April 2013
+
Malkevin updated:
+
+
Overhauled the thermal insulation system
+
All clothing that protected from one side of the thermal spectrum now protects from the other.
+
Armor (although most don't have full coverage) protects between 160 to 600 kelvin
+
Firesuits protect between 60 to 30,000 kelvin (Note: Hotspot damage still exists)
+
CE's hardsuit got its firesuit level protection back
+
Bomb suits function as ghetto riot gear
+
+
+
+
+
22 April 2013
+
Cheridan updated:
+
+
Stungloves removed 5eva.
+
Don't rage yet. Makeshift stunprods(similar in function to stungloves) and spears are now craftable. Make them by using a rod on cable restraits, then adding something.
+
Stun batons/prods now work off power cells, which can be removed and replaced! Use a screwdriver to remove the battery.
+
+
+
+
+
+
17 April 2013
+
Giacom updated:
+
+
If the configuration option is enabled, AIs and or Cyborgs will not be able to communicate vocally. This means they cannot talk normally and need to use alternative methods to do so
+
+
+
+
+
+
10 April 2013
+
Cheridan updated:
+
+
You can now condense capsaicin into pepper spray with chemistry.
+
Pepper spray made slightly more effective.
+
Teargas grenades can be obtained in Weapons and Riot crates.
+
Riot crate comes with 2 sets of gear instead of 3, made cheaper. Beanbag crate removed entirely. Just make more at the autolathe instead. Bureaucracy crate cheaper, now has film roll.
+
NT bluespace engineers have ironed-out that little issue with the teleporter occasionally malfunctioning and dropping users into deep space. Please note, however, that bluespace teleporters are still sensitive experimental technology, and should be Test Fired before use to ensure proper function.
+
+
+
+
+
+
9 April 2013
+
Ikarrus updated:
+
+
Liquid Plasma have been found to have strange and unexpected results on virion cultures. The executive chief science officer urges virologists to explore the possibilities this new discovery could bring.
+
After years or research, our scientists have engineered this cutting-edge technology born from the science of shaving. The Electric Space-Razor 5000! It uses moisturizers to refuel your face while you shave with not three, not four, but FIVE lazer-precise inner blades for maximum comfort.
+
+
+
+
+
4 April 2013
+
Cheridan updated:
+
+
When an AI shunts into an APC, the pinpointer will begin tracking it. When the AI returns to its core, the pinpointer will go back to locating the nuke disc.
+
New sechud icons for sec officers/HoS, medical doctors, and loyalty implants.
+
+
+
+
+
28 March 2013
+
Carnie updated:
+
+
Empty character slots in your preferences screen will now randomize. So they won't initialise as bald, diaper-clad, white-guys.
+
Reworked the savefile versioning/updating code. Your preferences data is less likely to be lost.
+
+
+
+
+
14 March 2013
+
Major_sephiroth updated:
+
+
You can now light cigarettes with other cigarettes, and candles. And cigars. Light a cigar with a candle! It's possible now!
+
+
+
+
+
13 March 2013
+
Elo001 updated:
+
+
You can now open and close job slots for some jobs at any IDcomputer. There is a cooldown when opening or closing a position.
+
+
+
+
+
8 March 2013
+
Kor updated:
+
+
You can now construct/destroy bookcases. This is super exciting and game changing.
+
+
+
+
+
6 March 2013
+
Petethegoat updated:
+
+
Petethegoat says, "Added a new feature involvi-GLORF"
+
Overhauled how grabs work. There aren't any interesting mechanical differences yet, but they should be a lot more effective already. You don't have to double click them anymore. Report any bugs with grabbing directly to me, or on the issue tracker.
+
+
+
+
+
24 February 2013
+
Ikarrus updated:
+
+
AI has been moved back to the center of the station. Telecoms has been moved to engineering.
+
+
Faerdan updated:
+
+
Competely new UI overhaul! Most user interface have been converted.
+
+
+
+
22 February 2013
+
Petethegoat updated:
+
+
Added cavity implant surgery.
+
Additionally, surgery must now be performed with help intent. Some procedures have also been updated.
+ As always, check the wiki for details.
+
+
+
+
+
18 February 2013
+
Ikarrus updated:
+
+
The AI has been moved to Research Division, and Telecomms has been moved into the former AI chamber. Affected areas: Telecoms Satellite, Research Division South & Command Sector.
+
+
Incoming updated:
+
+
Added three new types of surgery- lipoplasty, plastic surgery, and gender reassignment.
+
+
Kor updated:
+
+
The RD has lost access to telecomms, and basic engineers have gained it.
+
+
+
+
+
14 February 2013
+
Petethegoat updated:
+
+
Updated surgery: you now initiate surgery with surgical drapes or a bedsheet. Most procedures have changed, check the wiki for details. Currently it's pretty boring, but this paves the way for exciting new stuff- new procedures are very simple to add. Report any bugs directly to me, or on the issue tracker.
+
+
+
+
+
13 February 2013
+
Giacom updated:
+
+
There are now hackable wires for the PA computer. To open the interface, click on it while the wires are exposed/panel is open. All but one wire will do something interesting, see if you can figure it out. You can also attach signallers to the wires so have fun remotely releasing the singularity.
+
New staff of animation icon by Teh Wolf!
+
You can now hack plastic explosives (C4)!
+
You can now use NTSL to send signals! With the function signal(frequency, code) you can create some clever ways to trigger a bomb. NTSL also has two new additions; return in the global scope will now stop the remaining code from executing and NTSL now has "elseif"s, huzzah!
+
+
Kor "I'm quitting I swear" Phaeron updated:
+
+
You've been asking for it for years, it's finally here. Wizards can spend points to buy apprentices.
+
A new wizard artefact, the scrying orb.
+
The spellbook now has descriptions of spells/items visible BEFORE you purchase them.
+
+
Petethegoat updated:
+
+
Traitors with the station blueprints steal objective can now use a photo of the blueprints instead!
+
+
+
+
+
11 February 2013
+
SuperSayu updated:
+
+
Signallers, prox sensors, mouse traps and infrared beams can now be attacheed to grenades to create a variety of mines.
+
A slime core can be placed in a large grenade in place of a beaker. When the grenade goes off, the chemicals from the second container will be transfered to the slime core, triggering the usual reaction.
+
+
+
+
+
10 Feburary 2012
+
Ikarrus updated:
+
+
Implants can now be surgically removed. Hint: They're inside the skull.
+
+
+
+
+
07 February 2012
+
Giacom updated:
+
+
The return of the Nanotrasen Scripting Language! (NTSL) If you haven't heard of NTSL, it is a scripting language within a game for telecomms. Yes, you can create scripts to interact with the radio! For more information, head here: http://wiki.nanotrasen.com/index.php?title=NT_Script But before you do, if you are not an antag, do not create bad scripts which hinders communication.
+
Cameras, mulebots, APCs, radios and cyborgs can have signallers attached to their wires, like airlocks!
+
Cameras have non-randomized wires and the power wire when pulsed will now toggle the camera on and off.
+
Cyborgs have a new wire, the lockdown wire! It will disable/enable the lockdown of a Cyborg when pulsed.
+
The traffic control computer (or more commonly known as the computer which lets you add NTSL scripts) will now have a user log, which will log all user activity. Other users can then view that log and see who has been uploading naughty scripts!
+
NTSL has two new functions! time() and timestamp(format) will help you increase the range of types of scripts you can make, especially time(); since you can then make the scripts know the different between each execution by storing the results in memory.
+
Two new advance disease symptoms! Their names are "Longevity" and "Anti-Bodies Metabolism". Have fun experimenting with them!
+
+
+
+
+
+
31 January 2013
+
Kor "Even in death I still code" Phaeron updated:
+
+
Four new slime types with their own reactions and two new reactions for old slimes.
+
Put a suit of reactive teleport armour back in the RD's office.
+
Chemistry now has two dispensers (with half charge each) so both chemists can actually work at the same time.
+
+
+
+
+
+
27 January 2013
+
Ikarrus updated:
+
+
Security frequency chatter now appears in cyan (Similar to how command is gold)
+
Cheridan updated:
+
+
The plant bags in Hydroponics lockers have been replaced with upgraded models with seed-extraction tech. Activate with via right-click menu or Objects verb tab.
+
Obtaining grass tiles works a bit different now: grass is harvested as a normal plant item, clicking on it in-hand produces the tile.
+
+
+
+
+
26 January 2013
+
Pete updated:
+
+
Added hugging and kicking. I also updated the text styles for clicking on humans in most intents, but they should be pretty much the same.
+
+
+
+
+
25 January 2013
+
Errorage updated:
+
+
All the equipment you spawn with will now contain your fingerprints, giving the detective more ability to tell where items came from and if a crewmember has changed clothing.
+
+
+
Better explosions: Explosion spreading will now be determined by walls, airlocks and poddoors and not just a flat circle.
+
+
+
+
+
20 January 2013
+
Cheridan updated:
+
+
Chickens will now lay a certain number of eggs after being fed wheat, rather than just laying them whenever they felt like it. No more chickensplosions.
+
+
+
+
+
16 January 2013
+
+
Department Security can now be runned:
+ Department Security decentralizes security by assigning each officer to a different department. They will be given the radio channel and access to their assigned department along with a security post. The brig has been remapped to be smaller to accomodate this change.
+ To run DeptSec: Before compiling, server operators must tick jobs.dm (in WorkInProgress/Sigyn/Department Sec) and use map 2.1.1 instead of 2.1.0.
+
+
+
+
+
/tg/station 13 Presents
+
Directed by S0ldi3rKr4s0
+
& produced by Petethegoat
+
+
Curse of the Horseman.
+
+
+
+
+
12 January 2013
+
Cael Aislinn updated:
+
+
Spiders which will breed and spread through vents. Different classes of vents. AI controlled only at the moment.
+
Farm animals! Cows, goats and chickens are now available. You can order them at Cargo Bay.
+
+
Giacom updated:
+
+
Staff of animation mimics will no longer care whether you are holding the staff or not, they will never attack their creator.
+
Brainrot will only need alkysine to be cured.
+
New spider infestation event based on Cael's spiders. The announcement will be the same as alien infestations.
+
+
+
+
+
11 January 2013
+
Giacom updated:
+
+
Plasma (Air) will give the breather the plasma reagent, for a toxic effect, instead of just straight damage.
+
The agent card will now work inside PDAs/Wallets; meaning the AI won't be able to track you.
+
+
+
+
+
09 January 2013
+
Malkevin updated:
+
+
The owl mask now functions as a gasmask for increased crimestopping power.
+
+
+
Adds the missing icons for the arrest statuses of Parolled and Released, as well as a little blinking icon for chemical implants.
+
+
+
+
+
08 January 2013
+
Cael Aislinn & WJohnston updated:
+
+
Many new icons for aliens (death, sleeping, unconscious, neurotox, thrown/impregnated facehugger etc)
+
Alien larva can now be removed by dangerous and unnecessary surgery (and actually chestburst if they aren't).
+
Alien larva now have sprites to represent their growth: bloody at 0%, pale at 25% and 75% the normal deep red.
+
New icon overlays for representing alien embryo progression.
+
+
+
+
07 January 2013
+
Kor updated:
+
+
Four new slime types with their own extract reactions have been added. Sprites this time were created by Reisyn, SuperElement, and LePinkyFace.
+
+
+
+
02 January 2013
+
Kor updated:
+
+
Slime breeding! There are now 13 varities of slime, each with its own extract reaction (inject five units of plasma). Some of these reactions are reused from the old cores, some are new. As to breeding, each colour of slime has a series of other slimes it may mutate into when it reproduces.
+
+
Giacom updated:
+
+
You can now use wallets as IDs and equip them in your ID slot.
+
Firesuits are once again effective at protecting you from heat. The flames themselves will still hurt you, even with a firesuit. The damage protection is much better with a firesuit though.
+
Engineering starts with a PACMAN generator for jump starting the singularity if the power runs out of the SMES. 30 plasma spawns in Secure Storage inside the crate, to use as fuel for the generator.
+
+
+
+
31 December 2012
+
Giacom updated:
+
+
Simple animals (Corgis, Cats, Constructs, Mice, Etc...) can now pull people.
+
You can quickly stop pulling on someone by pulling them, while they're already being pulled by you. For example, CTRL+Click on something you are pulling to quickly stop pulling it.
+
+
+
+
30 December 2012
+
Giacom updated:
+
+
Emitters now require to be wired in order to work. When there is not enough power it will stop shooting until there is enough power, meaning you do not have to turn it back on, just get the power flowing.
+
You can order shield generators from cargo. Teleporter access is required to open the crate.
+
+
Ikarrus updated:
+
+
Map: Reorganized the Command Sector. The Captain has his own private quarters in addition to his office.
+
+
+
+
26 December 2012
+
Ikarrus updated:
+
+
An agent card is now required to use doors and controls on the Syndicate Shuttle (Nuke).
+
Scanning gas tanks is now a PDA-cart function. Only Atmos and Science PDA carts have this function. Have fun mislabelling gas tanks!
+
+
+
+
23 December 2012
+
Giacom updated:
+
+
The syndicate Military PDA will not show up on possible PDAs to message anymore, even when the receive/signal is turned on. You can still send messages and people can still reply to you.
+
You can now sell processed plasma for supply points. The conversion rate is 2 plasma sheets for 1 point. You must put the plasma in a crate for it to count.
+
The mecha toy prize promotion has officially ended. You can no longer redeem all 11 action mecha figures for a real mech. New toy redeeming promotions in the future will be considered.
+
+
+
+
21 December 2012
+
Ikarrus updated:
+
+
You can now use . to speak over headset department channels in addition to the : and # characters.
+
+
+
+
19 December 2012
+
Nodrak updated:
+
+
You can now use # to speak over headset department channels. For example say "#e Hello" will say "Hello" over the engineering channel. say ":e Hello" will still work as it always has.
+
+
+
+
16 December 2012
+
Giacom updated:
+
+
You can now create your own solar arrays! Order the solar pack crate and you'll receive 21 solar assemblies, 1 electronic which you can put into an assembly to make it a solar tracker and finally the solar computer circuit board. You will get more detailed instructions in the crate, on a piece of paper. Engineering will also start with this crate to help repair destroyed solar arrays.
+
+
Petethegoat updated:
+
+
Added a new option to the key authorisation devices. It removes the maintenance access requirement from all doors. It's irreversible, so only use it in an emergency!
+
+
+
+
15 December 2012
+
Ikarrus updated:
+
+
Swapped the locations of the Library and Chapel. Thanks to killerz104 for the remap.
+
Partial remap of atmos. Monitoring and Refill stations are now the same room.
+
Toxins Mixing should be working properly again.
+
+
+
+
12 December 2012
+
Ikarrus updated:
+
+
Robotics is now a full Science department.
+
Completely remapped Research Division, Robotics, Medbay, and the Library.
+
Partially remapped Cargo Bay, Mining Dock, Engineering, and Atmospherics.
+
Changed the access of the HoS and HoP. For a list, refer to their respective wikipages.
+
+
Errorage updated:
+
+
Miners now have to go through cargo to reach the Mining Dock.
+
+
Petethegoat updated:
+
+
The Detective's revolver no longer cares about how cool you look. It now spawns in his locker.
+
Added new inhands for most energy weapons, by Flashkirby!
+
+
Giacom updated:
+
+
Disintegrate (EI NATH) will leave behind the brain of the victim. Possible productive uses include: trophies, looking awesome as you gib someone and only their brain remains, people to talk to when you get an MMI, pocket brains, a way to get back into the game if the wizard didn't grab your brain and stuffed it into his bag.
+
+
+
+
07 December 2012
+
Giacom updated:
+
+
The detective's scanner was upgraded, it can now scan for reagents in items and living beings. Potential uses include, scanning dead bodies for leftover poison or scanning items to see if they have been spiked.
+
You can now attach photos to newscaster news feeds and wanted posters.
+
You can now emag buttons to remove access from them.
+
The CentComm. Report has been changed so it no longer names potential antagonists. It will just announce the potential round type instead.
+
+
+
+
05 December 2012
+
Cheridan updated:
+
+
Agent cards have been upgraded with microscanners, allowing operatives in the field to copy access levels off of other ID cards.
+
+
Ikarrus updated:
+
+
The Chief Medical Officer, Research Director, Chief Engineer, and Lawyers now have basic Brig access (corridor only)
+
Merged Mining and Cargo radio channels into the Supply Radio. To use the supply channel, use :u
+
Mining Dock remapped to be more compact and closer to cargo.
+
+
Giacom updated:
+
+
The wizard's fireball spell is once again dumbfire. It will fire in the direction of the wizard instead of having to choose from a list of targets and then home in on them.
+
+
+
+
+
02 December 2012
+
Giacom updated:
+
+
Added a new artefact called the "Staff of Animation". You can get it in the Wizard's Spellbook. It will animate objects and items, but not machines, to fight for you. The animated objects will not attack the bearer of the staff which animates them, meaning if you lose your staff, or if it gets stolen, your minions will turn on you.
+
+
+
+
+
30 November 2012
+
Petethegoat updated:
+
+
Janitor has recieved a slightly upgrade mop bucket. The old one is still there too.
+
+
Ikarrus updated:
+
+
Swapped the locations of the Vault and Tech Storage.
+
Cargo Techs, Miners, and Roboticists no longer start with gloves. They are still available from their lockers.
+
+
+
+
+
28 November 2012
+
Kor updated:
+
+
Slimes have replaced roros (finally)! Right now they are functionally identical, but massive expansion of slimes and xenobio is planned. Sprites are by Cheridan.
+
+
+
+
+
25 November 2012
+
Giacom updated:
+
+
Added new very high level symptoms which are only obtainable in the virus crate. Virus crate will also come with mutagen.
+
+
Petethegoat updated:
+
+
Removed clown planet! It'll return shortly in away mission form.
+
+
Ikarrus updated:
+
+
Added Gateway access. Only the RD, HoP, and Captain start with this.
+
New access levels in the brig: -Brig access now opens the front doors of the brig, as well as other lower-risk security areas. -Security access allows you into the break room and equipment lockers. -Holding Cells allows you to use brig timers and lets you in the Prison Wing. -The Detective no longer has Security Equipment access.
+
Significantly increased max cloneloss penalty for fresh clones to 40%.
+
+
+
+
+
23 November 2012
+
Giacom updated:
+
+
Simplified detective stuff. The high-res scanner is gone and instead the detective's normal scanner will instantly report all fingerprints, dna and cloth fibers in full. This was needed because the system took too long to work with and disencouraged detectives. Not only that, it made detectives less of a threat for antagonists and made possible scenerios, such as framing someone by changing fingerprints with someone else, impratical. To replace the computer, the detective will have a full medical computer with access to it. Not only that, but his useless filing cabinet will be replaced with an empty one for serious investigators. Along with this, are fingerprint cards and built-in PDA scanning, as all of security had access to it which was really the detective's thing. The new scanner will also log every finding and you can print them out as a report by clicking the scanner while it is in your active hand.
+
You can toggle the pressure of your sprayer by clicking on it while it is in your active hand. With pressure, the sprayer will spray 10 units on the floor, otherwise it sprays 5. You'll need to turn pressure on to spray water on the floor and make it slippery.
+
AIs in intellicards can no longer move their camera. This will limit them in ability but without making creating and carding an AI to have as a personel door opener impossible.
+
Telecommunication Busses can now be set to change the frequency of a signal. (Allowing you to say.. set the command channel to broadcast to the common channel).
+
Telecommunication was changed to be more effecient. Because of this, Relays don't need a broadcaster or a receiver and you can setup a relay on it's own. You can still disable sending and or receiving from the relay's interface.
+
+
Zelacks updated:
+
+
Plant Analysers now work on seed bags.
+
+
+
+
+
21 November 2012
+
Petethegoat updated:
+
+
The nuke shuttle can now travel at will, and to any location. When travelling from syndicate space to the station, (and vice versa), it will travel through hyperspace.
+
+
Carn updated:
+
+
Changed savefile structure. There's a bunch of unused files left lying around so old savefiles will be purged. Sorry for the inconvenience. Many preferences have been moved to the Preferences verb tab. Everything in that tab is persistent between rounds (it updates your savefile, so even DCing won't reset them). Enjoy x
+
+
Phol updated:
+
+
Added female sprites for most mutant races.
+
+
Cheridan updated:
+
+
SSU manufacturers have issued a product recall! It seems old models shipped with faulty wiring, causing them to short-circuit.
+
+
+
+
+
20 November 2012
+
Kor updated:
+
+
Added Exile Implants to the Gateway room. Someone implanted with an Exile Implant will be able to enter the away mission, but unable to return from it. Not only can they be used for getting rid of dangerous criminals, but revs/stationheads count as dead while on the away mission, and traitor/changeling/wizard assassination targets count as dead if they're on the away mission at round end, allowing for those objectives to be completed peacefully.
+
Added medical hardsuits, sprited by Majorsephiroth. Two of them spawn in EVA. Their most unique/medical oriented feature is being able to hold a medkit in the suit storage slot, allowing you to easily access medicine while keeping your hands free.
+
+
+
+
+
19 November 2012
+
Giacom updated:
+
+
Malf AIs can only shunt to APCs from their core. Meaning their core needs to be alive before they can shunt to another APC. Malf AIs can start a takeover inside an APC now.
+
When taking damage, the next sequence of the overlay will show for a bit before reverting to the overlay you should have. This allows you to know you are taking damage without having to check the text screen.
+
+
+
+
+
18 November 2012
+
Petethegoat updated:
+
+
Ported over BS12 style cameras. They now take a photo of a 3x3 area!
+
Catatonic people (those that have ghosted while alive) now count as dead for assasinate objectives.
+
+
+
+
+
17 November 2012
+
Donkie updated:
+
+
You can now deconstruct and construct Air Alarms and Fire Alarms. Read wiki on howto.
+
+
Giacom updated:
+
+
Medical Cyborgs no longer lose the reagents in their hypospray when switching modes.
+
Spaceacillin will now help stop the spread of diseases.
+
You can once again make floors slippery by spraying water. This was done by increasing the amount the sprayer uses, which is from 5 to 10. You can also empty your sprayer's contents onto the floor with a verb in the Object tab.
+
+
+
+
+
16 November 2012
+
Kor updated:
+
+
Fixed the syndicate teleporter door, making teleport assaults possible. It will once again open when you open the outter door.
+
+
+
+
+
+
15 November 2012
+
Giacom updated:
+
+
You can now name your advance diseases! You can't name already known diseases though.
+
Chemical implants can now hold 50 units instead of 10 units.
+
+
+
+
+
13 November 2012
+
Giacom updated:
+
+
More work to advance diseases. Please report any bugs to the bug tracker, I have tried everything that I can on my own but I'll need lots of people playing to fix the more minor bugs. You can find a guide to making your own diseases here: LINK!
+
Reduced the cost to use Hive Absorb from 40 to 20. This is to help encourage people to use this power more and to use team work.
+
New symptom added! See if you can find it.
+
You can now remove symptoms from a disease using synaptizine.
+
Kor: You can once again debrain changelings. They won't make anyone half-lings though, and you won't be able to tell if the body of a debrained changeling is a changeling by putting a player brain in there.
+
+
Nodrak updated:
+
+
Wizards can no longer cast spells when muzzled. It iss now actually possible to capture a live wizard without constantly injecting them with chloral.
+
You can no longer take bags of holding or mechs to the clown planet.
+
+
+
+
+
11 November 2012
+
Carn updated:
+
+
Admin-ranks changes
+ Lots of changes. This is just a brief summary of the most recent changes; still working on proper documentation.
+ All admins have access to view-vars, player-panel(for individual mobs), game panel and secrets panel. Most of the things on those pages have their own rights requirements. For instance, you can only use event orientated secrets in the secret panel if you have FUN rights. Debug secrets if you have DEBUG rights. etc.
+ Spawn xeno and toggle gravity procs were moved into the secrets panel (fun).
+ This may help with understanding which flags do what. Unfortuanately it's still somewhat vague.
+ If you have any problems, feel free to PM me at irc.rizon.net #coderbus. I go by the username carn or carnie.
+
+
+
+
+
+
11 November 2012
+
Kor updated:
+
+
New cyborg upgrade available for production that requires illegal and combat tech
+
Summon Guns has a new gun type created by Ausops. It also lets the user know when its been cast now to prevent people trying to buy it multiple times
+
Grilles are no longer immortal in regards to solid projectiles, you can now shoot out windows.
+
+
+
+
+
09 November 2012
+
Giacom updated:
+
+
Cyborgs can now ping and beep! (Say "*beep" and "*ping") Thanks to Rahlzel for the proposal.
+
HULKS WILL NOW TALK IN ALL CAPS AND WILL RANDOMLY SAY HULK THINGS. Thanks to Brotemis for the proposal.
+
Sorry for the inconveniences with advance diseases. They are working much better now!
+
An improved APC sprite by TankNut!
+
+
+
+
+
+
05 November 2012
+
Giacom updated:
+
+
+
AIs can now tweak with a bot's setting like a human who unlocked the bot.
+
+
+
+
+
05 November 2012
+
Errorage updated:
+
+
Being in an area with extremely low pressure will now deal some damage, if you're not protected.
+
Space suits and the captain's armor now protect against pressure damage
+
Slightly lowered all environment damage intakes (temperature, oxygen deprevation) to make up for low pressure damage.
+
Pressure protection finally works properly. Items that protect from pressure (firesuits, space suits, fire helmets, ...) will now properly protect. The pressure damage indicator will update properly based on the pressure effects on you. Black (low) and red (high) mean you are taking damage.
+
Slightly slowed down the speed at which your body temperature changes if you are in a very hot or very cold area. The speed at which you recover from an abnormal body temperature remains the same.
+
+
+
+
+
+
03 November 2012
+
TankNut updated:
+
+
New APC sprite.
+
New Wraith sprite and jaunting animation.
+
+
+
+
+
03 November 2012
+
WJohnston updated:
+
+
New Ablative Armor sprite.
+
+
+
+
+
03 November 2012
+
Giacom updated:
+
+
+
Airborne diseases will not spread through walls now.
+
Reduced queen healing rate to 5. The maximum health will be enough.
+
Aliens can now clear hatched eggs by clicking on them.
+
+
+
+
+
02 November 2012
+
Errorage updated:
+
+
You can once again travel to the station, derelict, satellite and mining z-levels through space. You will also never loop into the same level on transition - So if you are exiting the derelict z-level, you will enter one of the other z-levels.
+
+
+
+
+
+
01 November 2012
+
Giacom updated:
+
+
Aliens now take x2 as much damage from fire based weaponary, instead of x1.5.
+
Doors are now weaker than walls; so normal weapons can destroy them much more easily.
+
+
+
+
+
31 October 2012
+
Giacom updated:
+
+
Advance evolving diseases! Virology can now create, mutate and mix advance diseases together. I replaced the two bottles of blood in Virology with the advance disease. I'll write a wiki article soon enough. Here's a tip: Putting mutagen or virus food (a mixture of milk, water and oxygen) in blood with an existing disease will mutate it to gain symptoms. It can potentially lose old symptoms in the process, so keep backups!
+
+
+
+
+
28 October 2012
+
Errorage updated:
+
+
You can now set your character's age up to 85. This used to be 45.
Added a medical records cabinet to the Detective's office.
+
Added a safe to the vault. Who'll be the first to crack it?
+
+
Nodrak updated:
+
+
The CE has a new pet!
+
+
+
+
+
25 October 2012
+
Flashkirby99 updated:
+
+
Added 18 new hairstyles!
+
+
+
+
+
24 October 2012
+
Giacom updated:
+
+
Throwing eggs will result in the reagents of the egg reacting to the target. (Which can be a turf, object or mob) This creates possibilities like chloral eggs, lube eggs, and many more.
+
Aliens can now acid walls and floors! Not R-Walls though.
+
Facehugger throw range reduced to 5, so aim at humans that are 2 tiles apart from the edge of your screen.
+
Making eggs is a little more expensive but secreting resin is cheaper. (Both cost 75 now)
+
Aliens no longer have a random duration of stunning humans, it's a constant value now of the lower based value.
+
Acid is less random and will be more reliable. Don't bother aciding stuff more than once, as it will waste plasma.
+
You can now target non-dense items (such as facehuggers) with a gun.
+
You can now shoot canisters, computers and windoors to break them.
+
+
+
+
+
18 October 2012
+
Giacom updated:
+
+
As an AI, you can type in the "track with camera" command and get a list of names to show up there. This also works with "list camera" verb. Remember to use space to auto-fill.
+
Welding goggles have been added. They are like welding helmets but they are for the glasses equipment slot. Science and the assembly line are given a pair.
+
Thanks to WJohnston for the welding goggle icons.
+
Small change to the Assembly Line. Instead of six normal flashes, the Assembly Line will instead have two normal flashes and eight synthetic flashes. Synthetic flashes only work once but are designed to be used in construction of Cyborgs.
+
Nar-Sie put on a few pounds. Thanks HornyGranny.
+
+
+
+
+
16 October 2012
+
Giacom updated:
+
+
New changeling powers!
+
Hive Channel/Hive Absorb. Allows you to share your DNA with other changelings, very expensive chemical wise to absorb (download), not so much to channel (upload)! You cannot achieve your objective by sharing DNA.
+
Mimic Voice! You can form your voice of a name you enter. You won't look like them but when you talk, people will hear the name of who you selected. While you're mimicing, you can't regenerate chemicals.
+
Extract DNA! A power that allows you to silently sting someone and take their DNA! Meaning you do not have to absorb someone to become them. Extracting their DNA doesn't count towards completing your objectives.
+
You can now get flares from red emergency toolboxes. Has a 50% chance of a flash-light or a flare spawning.
+
Flare icon by Ausops!
+
Thanks to RavingManiac (Smoke Carter), Roros now lay eggs which can grow into baby roros or be used for cooking recipes. Scientists will need to expose the egg to plasma for it to hatch; while it is orange (grown).
+
A new icon for the map spawned x-ray cameras. Icon by Krutchen.
+
+
+
+
+
13 October 2012
+
Giacom updated:
+
+
Facehuggers have a new animation, thanks to Sly.
+
Firelocks, glass-less airlocks and walls will stop heat.
+
Fires are now more deadly, especially the flames.
+
Fires will now break windows.
+
+
+
+
+
10 October 2012
+
Giacom updated:
+
+
Larva grow a little bit faster when on weeds or when breathing in plasma.
+
+
+
+
+
8 October 2012
+
Giacom updated:
+
+
Thanks to Skasi. Atmospherics has been changed to be made simpler and spawn with the new atmos features, such as the heaters.
+
Radio headsets can only be heard by people wearing them on their ear slot. This will let us do more fun stuff with headsets, such as a traitor encryption key which can listen to all the channels, but not talk in them.
+
+
+
Kor updated:
+
+
A pen no longer spawns in your pocket. Instead, each PDA will spawn with a pen already in it.
+
+
+
+
+
5 October 2012
+
Giacom updated:
+
+
Aliens can now be harmed by fire. They now also take double fire damage, meaning flame based weaponry is very effective.
+
Buffed alien facehuggers and eggs. Facehuggers don't go idle anymore, and they attach to anyone who walks past them. Eggs do the same; fully grown eggs will open to potential hosts. If you are still in the range of them, the facehugger inside will leap out and hug you. Removed "activate facehuggers", since it's useless now. Emote "roar" if you want to roar now.
+
There can be only one living queen at a time, if the queen dies then a drone can take her place as a princess.
+
Buffed queen regeneration a bit, so it's not the same as her underlings. It's also more important because there can only be one queen at a time.
+
Aliens don't slip in space anymore.
+
Hulks don't paralyze aliens anymore, they instead slow them down to a slow crawl. It is very effective for punching aliens out of weeds, so it can't regenerate it's health.
+
New egg opening and opened egg icons by WJohnston.
+
+
Aranclanos updated:
+
+
A buncha crud nobody cares about lol Added a light to the airlock wiring interface to show the status of the timing.
+
You can't fill sprays without being next to the dispenser.
+
Simple animals no longer freeze to death in places with normal temperature.
+
Mechs no longer freeze on the spot when they are using the Energy Relay on powerless areas.
+
Improvements to showers, they now clean gear on beltslot, back, ears and eyes. Showers only clean visible gear.
+
Replica pods works again! But you can't make potato people without a key or clone people who ghosted alive (Catatonic).
+
Engiborgs can deconstruct airlocks with their RCDs once again.
+
You can construct airlocks while standing on another airlock with RCDs.
+
+
+
+
+
3 October 2012
+
Agouri updated:
+
+
+
+
+
1 October 2012
+
Cheridan updated:
+
+
Wizards have a new artifact added to their spellbooks.
+
+
+
+
+
+
30 September 2012
+
Numbers updated:
+
+
Readded Volume Pumps - now they work as intended and are constructable
+
Readded Passive Gates - now they work as intended and are constructable
+
Readded Heat Exchangers - now they work as intended and are constructable
+
Added Heater - to warm up gasses to 300C
+
Pipe dispensers can produce the readded pieces.
+
New graphics for all of the above - courtesy by Ausops.
+
+
+
+
+
30 September 2012
+
Giacom updated:
+
+
Airlocks now use the Environmental power channel, since they are airlocks after-all. Meaning, when power is low the airlocks will still work until the environmental channel on the APC is turned off. This applies to all the door control buttons too. Pipe meters now use the environmental power channel. If you have any comments have this change, please let me know in the feedback section of the forums.
+
+
+
+
+
26 September 2012
+
Carnwennan updated:
+
+
Added new hotkeys. Type hotkeys-help for details or see the drop-down help menu at the top of the game window.
+
+
Aranclanos updated:
+
+
Mechs are once again spaceproof!
+
The YouTool machine is now all access
+
Cutting tower caps in hand no longer deletes the wood, and planks now auto stack
+
+
+
+
+
25 September 2012
+
Donkie updated:
+
+
Reworked the Piano, now really optimized and new interface!
+
+
+
+
+
24 September 2012
+
Petethegoat updated:
+
+
Hopefully fixed the stop midis button. It should now stop any midis that are currently playing.
+
+
+
+
+
23 September 2012
+
Petethegoat updated:
+
+
Fixed an exploit which would allow the janitor to magically mop floors.
+
Added lipstick~ It's not available on station, as Nanotrasen has deemed it contraband.
+
If you encounter any issues with computers, notify an admin, or ask for assistance on #coderbus, on irc.rizon.net.
+
+
Donkie updated:
+
+
Updated the Package Tagger with new interface!
+
You can now dispense, remove and retag sort junctions properly!
+
+
+
+
+
17 September 2012
+
Cheridan updated:
+
+
Metroids have been replaced with Rorobeasts. Roros are strange latex-based lifeforms that hate light, fun, and gloves.
+
+
+
+
+
17 September 2012
+
Carn updated:
+
+
F5 is now a hotkey for adminghosting. F8 toggles ghost-like invisibility for admins.
+
Catatonia makes you fall down. Admins appear braindead when admin-ghosting.
+
"Set-observe"/"Set-play" renamed and merged into "Aghost".
+
"Lay down/Get up" renamed to "Rest"
+
Closets can't be sold on the supply shuttle anymore
+
Fixed all dat light
+
+
+
+
+
13 September 2012
+
Carn updated:
+
+
New Hotkeys (Trial period). Details can be found in the help menu or via the hotkeys-help verb. It's all client-side. It shouldn't intefere with regular controls (except ctrl+A, ctrl+S, ctrl+D and ctrl+W).
+
+
+
+
+
10 September 2012
+
Giacom updated:
+
+
AIs can double click on mobs to instantly start tracking them.
+
+
+
+
+
Important note for server hosts!
+
Important note for server hosts!:
+
+
The file /code/defines/hub.dm was moved into /code/hub.dm. To get your server back on the hub, open /code/hub.dm and set the hub variables again. Sorry for the inconvenience.
+
+
+
+
8 September 2012
+
Carn updated:
+
+
Added an additional check to stop changelings sharing powers/becomming un-absorbable/etc by absorbing eachother and then rejuvinating from death.
+
Cloaked Aliens are now slightly easier to see, so they should avoid strongly lit areas when possible. They can still lay down to become even stealthier though. Let me know what you think, it's only a minor sprite change.
+
+
+
+
+
6 September 2012
+
Cheridan updated:
+
+
-Changes flour from an item to a container-held reagent. All recipes have been updated to use 5 units of reagent flour for every item required previously. This has a few advantages: The 16(!) sacks of flour previously in the kitchen cabinet have been condensed to an equivalent 3 sacks. Beer is now brewable with universal enzyme, and converting lots of wheat into flour should be less tedious. Also, flour grenades, etc. Because of this, flour is now obtained from the all-in-one blender rather than the processor, and spaghetti noodles are made with 5 units of flour in the microwave.
+
+
+
+
+
6 September 2012
+
Giacom updated:
+
+
Removed cameras from bots (NOT BORGS). They weren't working well with freelook and I felt that since they weren't used at all, they wouldn't be missed.
+
+
+
+
+
3 September 2012
+
Giacom updated:
+
+
Cameras has changed quite a bit. They are no longer created from grenade canisters, instead you make them from an autolathe. The construction and deconstruction for them has also changed, so look it up or experiment it with yourself to see how to setup the cameras now. Cameras also get wires, like airlocks and APCs. There's two duds, a focus wire, a power wire, an alarm wire and a light wire. Protip: You can see which one is the alarm wire by pulsing it.
+
Added a red phone and placed it in the Cyborg Station. Sprite by Pewtershmitz! You'll also find an AI restorer there, replacing the computer frame.
+
Cameras aren't all X-ray anymore. The AI won't be able to see what room you are in if there's no normal camera inside that room or if there's no X-ray camera nearby..
+
Cameras get upgrades! Currently there's X-ray, EMP-Proof and Motion. You'll find the EMP-Proof and Motion cameras in the normal places (Singularity Pen & EVA), the new X-ray cameras can be found in the Dormitory and Bathrooms, plus some extra ones to invade your privacy. See if you can smash them all.
+
Alien Larva can bite simple animals (see: Ian, Runtime, Mice) to kill them and gain a small amount of growing points.
+
Space travel was tweaked to be more random when changing Z levels. This will stop people and items from being stuck in an infinite loop, as they will eventually hit something to make them stop.
+
+
+
+
+
31 August 2012
+
Agouri updated:
+
+
Overhauled newscasters. No visual additions but the thing is much more robust and everything works as intended. Wanted issues are fixed. Admins, check out Access News Network under Fun.
+
+
+
+
+
30 August 2012
+
Giacom updated:
+
+
You can now create an EMP Pulse. Like an explosion, it is the mixing of two reagents that trigger this to happen. I will tell you the first required reagent. Uranium. Have fun!
+
I have made most chemicals need 3-5 or more chemicals in order to react to a turf. For instance, you need at least 5 units of thermite splashed on a wall for it to burn down."
+
The EMP kit, that you can buy through the uplink, has two more grenades in them now. Making the box full of EMP grenades!
+
Changed the EMP grenade's range to be much bigger.
+
+
+
+
+
29 August 2012
+
Nodrak updated:
+
+
Mice now work with the admin player panel. Admins can now turn players into mice with the 'Animalize' button in the player panel!
+
Space bear AI no longer runs when a player is controlling it. Admins can now turn players into space bears with the 'Animalize' button in the player panel!
+
The holodeck beach program once again has a beach.
+
The nuke op shuttle floor was pressure-washed a few days ago. We have since re-painted it with nanotrasen blood. Sorry for any confusion.
+
+
+
+
+
28 August 2012
+
Giacom updated:
+
+
You can now toggle the bolt light of airlocks. An extra wire, that controls the airlock's bolt light, has been added.
+
Aliens can now tell who is and who isn't infected. They get a special facehugger icon that appears over mobs that have been impregnated.
+
Cameras have temporary X-Ray for the time being.
+
+
+
+
+
August 26, 2012
+
Nodrak updated:
+
+
Admins now have an 'Animalize' button on a mob's player panel. This button allows admins to turn players into simple animals. There are a few exceptions. Mice, Parrots, Bears and Space Worms all have issues that, until fixed, prevent me from allowing players those transformations.
+
+
August 25, 2012
+
Carnwennan updated:
+
+
New lighting. It should look and feel the same as the old lighting whilst being less taxing on the server. Space has a minimum brightness (IC starlight) and areas that do not use dynamic lighting default to a lighting level of 4, so they aren't dark, but they aren't superbright. Replacing turfs should preserve dynamic lighting. Singulo/bombs should cause a lot less lighting-related lag. There are some minor known issues, see the commit log for details.
+
Admins can now access most controller datums with the "Debug Controller" verb. Time to break all the things!
+
Supply shuttle now uses a controller datum. This means admins can see/edit supply orders etc.
+
Changeling fakedeath can be initiated after death again. Next time you want something reverted, just ask rather than being obnoxious.
+
+
Giacom updated:
+
+
AIs can now look around like a ghost with the exception that they cannot see what cameras cannot see. Meaning if you're in maintenance, and there's no cameras near you, the AI will not know what you are doing. This also means there's no X-Ray vision cameras anymore.
+
AIs can add links to Telecommunication Machines. Added some cameras for areas that should have it but instead relied on cameras nearby for vision.
+
Choking has been changed. You have to stand still while lethally choking someone. It takes time to get into that lethal choke. When you are lethaling choking someone, they are still concious until the lack of oxygen knocks them out.
+
+
trubble_bass updated:
+
+
Nerfed the Neurotoxin drink, it is now less effective than a stunbaton. But more effective than a Beepsky Smash.
+
Updated descriptions on various cocktails to be more accurate or more relevant to the drink itself.
+
+
+
+
+
August 24, 2012
+
Sieve updated:
+
+
Floorbots now actually pull up tiles when emagged
+
All helper bots (excluding MULEs) have an access panel and maint panel, access being for behavior and maint for internal work
+
To open the maint panel, the access panel needs to be unlocked, then you use a screwdriver. There you can emag/repair it to your heart's content. (Emagging the access panel will also unlock it permanently)
+
Helper bots are now repaired by using a welder when their maint panel is open
+
+
+
+
+
August 23, 2012
+
Nodrak updated:
+
+
In-hand sprites once again update correctly when equipping items.
+
+
+
+
+
+
August 16, 2012
+
Errorage updated:
+
+
Changes were made to how heating and cooling of humans works.
+
You must wear both a space suit and space helmet to be protected from space! Likewise you must wear a firesuit and a fire helmet to be protected from fire! Fire helmets are red and white hardhats, found in all fire closets.
+
Fire suits now only protect from heat and space suits only protect from cold, so make your choice count.
+
+
+
+
+
+
August 14, 2012
+
Sieve updated:
+
+
DNA modifiers can be used if there is no occupant, primarily to handle the buffer.
+
Ion Rifles are only effected by max severity EMPs, so AOE from its own shot won't effect it
+
Pepper Spray fits on Sec belts again
+
+
+
+
+
August 11, 2012
+
Sieve updated:
+
+
Turrets now properly fire at simple_animals.
+
Borgs, AIs, and brains/MMIs can be sacrificed by cultists.
+
Grenades now automatically set throw on again.
+
+
+
+
+
August 6, 2012
+
Dingus updated:
+
+
Library has been redesigned. It's a whole lot more classy now.
+
Significant changes to Medbay. CMO's office is more centralized, genetics has a new exit into cryogenics, and a new break room has been installed
+
+
+
+
+
August 4, 2012
+
Icarus updated:
+
+
Changes to Med-Sci south and surrounding maintenance areas. Virology is more isolated and Science gets a new Misc. Research Lab.
+
Atmos techs get construction access now to do their little projects in.
+
Transformation Stings now work on living humans.
+
+
+
+
+
August 2, 2012
+
Errorage updated:
+
+
Gas masks now protect you from reagent smoke clouds
+
Changed the 'black overlay' you get when paralyzed, blind or in critical condition to include a small circle around you.
+
Dramatically lowered the amount of damage you get per breath while in critical condition. Critical condition now lasts for about 5 minutes if nothing is causing you any additional harm. This in combination with the new black image overlay is an attempt at making doctors more willing to help.
+
+
Icarus updated:
+
+
Borgs now have flashlights to allow them to see in lightless areas
+
Changes to Medbay: The sleeper and storage rooms have been swapped around. Hopefully this leads to more healing and less looting.
+
+
+
+
+
August 1, 2012
+
Sieve updated:
+
+
Borgs can now have an encryption key installed into their internal radios. Simply ID, open the panel, and use the key to insert it (Screwdriver to remove)
+
Due to that as well, borgs have a 'Toggle Broadcast Mode' button for their radios, which changes the broadcast type between station-bounced (Non-reliant on TComms), and subspace (Required for department channels)
+
Also changed the binary chat for consistency, now for the prefix is ':b' for everyone, not just one for humans and one for borgs/AIs/pAIs
+
Based on feedback, Nuke Op pinpointers now automagically change between shuttle and disk mode when the nuke is armed or disarmed.
+
+
+
+
+
01-August-2012
+
Carn updated:
+
+
Please update your BYOND clients! Ideally everybody should be running the latest version of byond (v496). People who fail to update to at least version 494 within a month's time may find themself unable to connect. Currently our code has no restrictions at all, which is rather bad. By getting the user-base to keep their clients up-to-date we can make use of newer BYOND features reliably.
+
+
Giacom updated:
+
+
I've made some adjustments to the Fireball spell. I've changed it to shoot in the player's facing direction instead of you having to pick a name from a list. It will explode upon contact of a person, if it hits an obstacle or if it shoots for too long. To make up for the fireball not being able to go diagonal I've shortened the cooldown to 10 seconds. It still can hurt you badly and knock you down if you shoot it at a wall. Lastly, it now lights up so it'll show up in dark rooms easily.
+
+
+
+
+
31 July 2012
+
Giacom updated:
+
+
Removed passive throwing. You need at least an aggressive hold of the mob before you can throw them.
+
New map changes by Ikarrus. AI Upload Foyer is now Secure Tech Access, and the outer door only requires Bridge access. Attached to it are two new rooms: The messaging server room and the communications relay. The comms relay room runs off its own SMES unit like the AI, so it won't be affected by powersinks
+
+
+
+
+
+
29 July 2012
+
Giacom updated:
+
+
All radios now only work in their Z level. This means that the CommSat has a few more additions to work with this change. There is now a new Telecomms Machine called the Relay which allows information to travel across Z levels. It it then linked to a new machine called the Hub, which will receive information from the Relays and send it to the buses. Because every Z level needs these relays, which are linked up with Receivers/Broadcasters, every Z level will get one. There is one in the station, in the RD's office, one in Telecomms as always, one in the Ruskie station which is turned off and hidden from the HUB's linked list. The last one is in Mining but the location for it has not been decided yet.
+
PDAs now need to be in a Z level with a functioning Relay/Comms Network in order to send messages. It will also send uncompressed (scrambled) messages like you would with the ordinary voice messages.
+
Added some of WJohnston's sprites. Added a new mining borg sprite, Added a new high tech security airlock, Added the new telecomm sprites for Relays. Hubs were given old Bus sprites.
+
+
+
+
+
29 July 2012
+
Errorage updated:
+
+
You can now use crayons to color eggs
+
Mice have invaded the station!
+
+
+
+
+
26 July 2012
+
Giacom updated:
+
+
Added a new mushroom for Hydroponics, the Reishi Mushroom! It is obtained like any other mushroom and it has relaxing properties.
+
+
+
+
+
July 25, 2012: The day of updates!
+
Nodrak updated:
+
+
Attacking mobs with items will now give new messages. Instead of "Monkeyman was attacked in the head with a wrench by Nodrak." it will read "Monkeyman was bashed in the head with a wrench by Nodrak." Diffrent items have diffrent verbs and some have multiple verbs.
+
Cultists can now read what words a rune was made with by examining the rune. Due to an error in the code, this was not possible before.
+
Clowns no longer have practice lasers or staves of change blow up in their face due to clumsyness.
+
Engineering cyborgs can now actually repair a cut AI wire in APCs.
+
I've removed a ton of pointless checks and redundant loops from metroid's which have been causing lag due to how often they get called. If metroids are behaving strangly ping me in #coderbus
+
+
Sieve updated:
+
+
Made a 'default' save slot (D), and whenever you connect it automatically selects the default slot to load from, but manually selecting a different slot will allow you to play on that one before it returns to default.
+
Added the ability to name your save slots with the '*'. Names can be up to 16 characters and contain letters, numbers, and basic symbols
+
The preview icon on the preference screen now takes into account any job you have set on high, and dresses up the icon accordingly. If assistant is set to 'yes', or AI/Cyborg are on high it will put the icon in a grey suit (So you can still customize).
+
Nuke Ops get a new pinpointer, changing modes with the verb will switch between pointing to the disk, and pointing to the shuttle. Also provides a notification when you leave the station z-level
+
Reworked how MMI Life() was done, now they will never lose consciousness, and many less things affect them now(Like deafening/blindness from explosions). However, they are vulnerable to EMPs, but all damage is temporary.
+
Clowns will no longer be killed trying to use holo eswords
+
Major tweaking to try and optimize many operations on the game's backend. Hopefully, this will reduce a large amount of lag by steamlining CPU-intensive operations, but at the same time there was so much changed that there is no real way for a small group to test everything. If anyone spots a bug involving being unable to 'find' mobs, characters, whatever, then put it on the issue tracker or at the very least let #coderbus know. We can't fix shit unless we know about it.
+
+
Icarus updated:
+
+
Players not buckled in when the emergency shuttle/pod starts moving get will get knocked down.
+
Added a YouTool vending machine to primary tool storage.
+
+
+
+
+
24 July 2012
+
Errorage updated:
+
+
Both the chef and bartender have access to the bar area so both can serve if the other is incompetent or does not exist. Bartender's shotgun and shaker were moved to his back room and the booze-o-mat is now ID restricted to the bartender.
+
Added powercells into vending machines in engineering
+
Gave two beartraps to the janitor for pest control purposes................
+
+
+
+
+
22 July 2012
+
Errorage updated:
+
+
Mech toys can now be redeemed at the quartermaster's for a great reward! If you collect the full set of 11 toys you should put them in a crate and send them to centcom via the supply shuttle.
+
Supply shuttle arrival time reduced to 2 minutes
+
Hopefully fixed the toy haul problem which made it possible to get a million toys from arcade machines.
+
+
Giacom updated:
+
+
You can now make newlines with your PDA notes.
+
You can now research and build the Light Replacer.
+
You can now store donuts in the donut box. The next donut you pull out will be the last one you put in.
+
APCs will auto-turn on if there is enough power in the grid, even if the powercell is below 30%. The APC needs to be charged for a long enough time before it starts turning equipment on, to avoid spazzing out. If you have any problems with it, such as equipment turning off and on repeatedly then please make an issue report with a screenshot of the APC.
+
+
+
+
+
18 July 2012
+
Giacom updated:
+
+
Added the Light Replacer. This is a device that can auto replace lights that are broken, missing or burnt. Currently it is found in the Janitor's closet and Janitor Borgs can equip it. You can refill it with glass, or if you're a Cyborg, just recharge. It is emaggable and will replace lights with rigged lights. The light's explosion was nerfed to help balance it and it is very noticable when you are holding an emagged Light Replacer.
+
The Janitor's equipment locator, on their PDA, will now tell you the direction of the equipment.
+
+
+
+
+
17 July 2012
+
Icarus updated:
+
+
Added department satchels
+
Added Captain's Backpack and Satchel
+
Added three new hairstyles by Sly: Gelled, Flat Top, and Pigtails. Hair list has also been sorted by grouping similar styles.
+
+
Giacom updated:
+
+
Added a new wire for Cyborgs. See if you can figure out what it does.
+
You can now fill any container with a sink. You can change the amount to fill, from sinks, by setting your container's transfer amount.
+
+
+
+
+
14 July 2012
+
Carn updated:
+
+
All living mobs can now ghost whenever they want. Essentially making the suicide verb obsolete. If you ghost whilst still alive however, you may not re-enter your body for the rest of the round.
+
Humans can no longer suicide whilst restrained (this is purely to prevent meta whilst I finish up the new FUN suicides)
+
Fixed dem evidence bags. Fixed metroids getting at it like rabbits. Fixed stuff like welding masks not hiding your face. Bunch of other things
+
+
Willox and Messycakes updated:
+
+
pAI Emoticons! Allows each pAI to set their screen to display an array of faces! Click on 'Screen Display' in the pAI OS for a list.
+
+
+
+
+
+
Saturday July 14th 2012
+
Giacom updated:
+
+
Added Russian Revolvers. This is a special Revolver that can only hold a single bullet randomly in it's chamber. This will allow you to play Russian Roulette with your fellow crew members! You can use it like a normal gun but you will need to cycle through the chamber slots until you hit the bullet. Only admin spawnable.
+
+
+
+
+
Friday July 13th 2012
+
Carn updated:
+
+
Added FLOORLENGTH HAIR. YEESSSSSSSS!!!! :3 If you like it say thanks to Ausops for fixing it up. Credits to Powerfulstation for the original sprite.
+
+
Giacom updated:
+
+
Save Slots! You can now have separate save slots for different character setups, with a customizable maximum of 3 slots per account. If you are wondering, you will not lose your old saved setup.
+
The character setup screen was updated to look nicer and to fit on the screen.
+
+
Icarus updated:
+
+
Added new Dwarf and Very Long hairstyles. Dwarf hair and beard by SuperCrayon.
+
+
+
+
+
Thursday July 12th 2012
+
Giacom updated:
+
+
pAI gets a better PDA that can actually receive messages from people. They can also instantly reply like everybody else now and they can toggle their receiver/signaller/ringer.
+
You can show the AI the notes on your PDA by holding it up to a camera. When you show up a paper/pda to the camera the AI can now click on your name to go to you, if you're near a camera. People who are Unknown will not have a link; which would've allowed the AI to track them.
+
Made the" common server" and the "preset right receiver" listen for frequencies 144.1 to 148.9. This will allow people to use different frequencies to talk to eachother without bothering the common channel. It will also allow Revs and Cultists to work with each other; everything is still logged though so it still has risks.
+
Increased the maximum frequency limit for handheld radios and intercoms. It will give you the option to just use station bounced radios on a higher frequency so that anyone with a headset can't simply tune in.
+
Created an All-In-One Grinder that is suppose to replace the blender, juicer and reagent grinder all together. Meaning any department that has a juicer, blender and grinder will instead get this. It will help people be more independent from Chemistry by recycling foods and plants.
+
+
+
+
+
Wednesday July 11th 2012
+
Nodrak, Cheridan and Icarus updated:
+
+
Added a couple of Emergency Shield Projectors to Engineering secure storage.
+
Note: Credit goes to Barhardar for the original code and functionality.
+
These devices can be used to quickly create an air-tight seal across a hull breach until repairs can been made.
+
Wrench them in place and activate them near a hull breach. The shield should extend to all space tiles in range.
+
They can be (un)locked by engineering IDs and can also be emagged and otherwise malfunction. As they can not be constructed, you can repair damage and malfunctions by opening the panel with a screwdriver and replacing the wires with a cable coil
+
+
Giacom updated:
+
+
Chemistry update: Pills can now be ground up in reagent grinders. You can now put custom amounts of reagent into things using chemmasters. Can now load pill-bottles into chemmasters for mass pill-production.
+
+
Carn updated:
+
+
Clicks on inventory slots with items in now act like a click on the thing in that slot. So clicking smaller things (like pens) is easier and you can remove clothing that borks/goes invisible. Please continure to report those kinds of bug though please. Thanks x
+
Can no longer interact with your inventory with a mech.
+
+
Errorage updated:
+
+
You can now only adminhelp once every 2 minutes so please provide all necesary information in one adminhelp instead of 5! Also reply to admins in PM-s and not additional adminhelps.
+
+
+
+
+
Saturday July 7th, 2012
+
Icarus updated:
+
+
A basketball simulation is now available at the holodeck. Credit to Sly and Ausops for the sprites.
+
+
+
+
+
Friday July 6th, 2012
+
Giacom updated:
+
+
Bottles can now be broken over people's heads! To do this, you must have the harm intent on and you must be targeting the person's head. This change affects alcoholic bottles only. It does not change pill bottles or chemistry bottles. Helmets help protect you from damage and the regents of the bottles will splash over the victim.
+
AI's now have access to a PDA. Note: It is not PDA-bomb-able
+
Health analyzers and medical PDAs now give a time of death when used on corpses.
+
+
+
+
+
Thursday July 5th, 2012
+
Carn updated:
+
+
Alien larva now chestburst even after their host has died.
+
Aliens can now slap facehuggers onto faces so they can infect mobs which lay down (or those stuck to nests).
+
Aliens can now slash security cameras to deactivate them.
+
+
+
+
+
Wednesday July 4th, 2012
+
39kk9t & Carn updated:
+
+
Added alien nests. They're basically beds made of thick sticky resin which aliums can 'stick' (buckle) people to for sexytimes
+
Weed nodes are no longer dense.
+
Queens can secrete resin for walls/nests/membranes
+
Various bugfixes
+
+
+
+
+
Saturday June 30th, 2012
+
Icarus updated:
+
+
Added Petethegoat's basic mirrors to the map. They allow you to change hairstyles.
+
Remapped Bar, Theatre, and Hydroponics. Bar and Kitchen are now more integrated with each other.
+
+
+
+
+
Thursday, June 28th
+
Nodrak updated:
+
+
I'm currently working on cleaning up and moving around a large portion of the mob code. These changes do not directly affect players; HOWEVER, bugs, oversights or simple mistakes may cause problems for players. While I have tested as much as I can, there may be some lingering bugs I have missed.
This part of the mob code cleanup mainly focuses on damage variables and procs. So if you suspect something related to taking, dealing or examining damage is not working as intended please fill out an issue report here and be as detailed as possible. This includes what you were doing, steps to reproduce the problem, who you were doing it to, what you were using ect... Thank you.
+
+
Carn updated:
+
+
Alien hunters will now cloak when using the 'stalk' movement intent. Whilst cloaked they will use up their plasma reserves. They can however cloak as long as they like. Using the Lay-down verb will allow them to remain cloaked without depleting their plasma reserves, hence allowing them to lay ambushes for unsuspecting prey. Should a hunter attack anybody, or get knocked down in any way, they will become visible. Hopefully this allows for more strategic/stealthy gameplay from aliens. On the other hand, I may have totally screwed the balance so feedback/other-ideas are welcome.
+
Removed the invisibility verb from the alien hunter caste.
+
+
+
+
+
Wednesday, June 27th
+
Errorage updated:
+
+
Fixed the bug which prevented you from editing book's titles and authors with a pen. Also fixed the bug which prevented you from ordering a book by it's SS13ID.
+
Added the F12 hotkey which hides most of the UI. Currently only works for humans.
+
+
Donkie updated:
+
+
Pizza boxes! Fully stackable, tagable (with pen), and everythingelse-able. Fantastic icons by supercrayon! Created with a sheet of cardboard.
+
+
+
+
+
Tuesday, June 26th
+
Errorage updated:
+
+
Changeling parasting now only weakens for 10 game ticks. It no longer silences your target.
+
+
+
+
+
Saturday, June 23rd
+
Donkie updated:
+
+
Reworked job randomizing system. Should be more fair.
+
List of players are now randomized before given antag, this means that declaring as fast as possible doesn't mean shit anymore!
+
+
Carn updated:
+
+
Putting a blindfold on a human with lightly damaged eyes will speed up the healing process. Similar with earmuffs.
+
More overlay bug fixes. Most of it to do with little robots and construction stuff. Bugs go here if you have 2 minutes http://nanotrasen.com/phpBB3/viewtopic.php?f=15&t=9077
+
Weakening is instant! That means if you stunbaton somebody they're gonna fall-down immediately.
+
+
Icarus updated:
+
+
Medical storage now requires Surgery access (Medical Doctors only)
+
+
+
+
+
Wednesday, June 20th
+
Nodrak updated:
+
+
AIs, Borgs are no longer able to be cultists or revolutionaries as their objectives completely contradict their laws. They can still be subverted of course.
+
pAI's no longer keep cult or rev icons.
+
Cheridan updated:
+
+
-Both Ambrosia forms have had their reagent contents modified to prevent going over the 50-unit cap at high potencies. Ambrosia Deus now contains space drugs instead of poison.
+
-Drinking milk removes capsaicin from your body. REALISM!
+
-Frost oil hurts less upon consumption.
+
-Liquid plasma can be converted into solid plasma sheets, by mixing 20 plasma, 5 iron, and 5 frost oil.
+
-Added effects for holy water injection on hydroponics plants.
+
+
+
+
+
Monday, June 18th
+
Giacom updated:
+
+
Fix for special characters on paper and from announcements
+
+
Sieve updated:
+
+
Various small bugfixes, check the commit log for full details
+
Fixed falsewalls not working
+
Made Lasertag ED-209's and turrets much more useful, including making their emag function more fitting
+
Added Mesons to the EngiVend to make up for how many lockers were removed
+
New Item: Sheet Snatcher. Right now only borgs have them, but they can hold up to 500 sheets of minerals (Of any combination), and auto-stacks them to boot. Used just like the mining satchels, meaning minerborgs can actually deliver metal
+
Mech drills can mine sand, and the diamond gets a much larger volume
+
If a borg has the satchel in its modules (Doesn't have to be the active one), it will auto-magically pick up any ores it walks over.
+
Bumping an asteroid wall with a pickaxe/drill in your hand makes you auto-magically start drilling the wall, making mining much less tedious (humans and borgs)(Also, gustavg's idea)
+
+
Icarus updated:
+
+
New afro hairstyles. Big Afro by Intigracy.
+
+
+
+
+
Friday, June 15th
+
Carnwennan updated:
+
+
First update for update_icons stuffs: Fixed husking and fatties Fixed floor tiles still appearing in hand when laying them Fixed runtimes with fatties.
+
Fixes for pre-existing bugs: Fixed being unable to put belts & backpacks on other people nodamage (godmode) now prevents all organ damage. It does not stop healing however. Nerd stuff...
+
+
+
Errorage updated:
+
+
Greatly reduced the amount of damage high pressure does.
+
Fire suits, firefighting helmets (red harhats) and the chief engineer's white hardhat now protect against high pressure.
+
+
Icarus updated:
+
+
Ported over ponytail sprites from Baystation
+
+
+
+
+
Thursday, June 14th
+
Carn updated:
+
+
FEAR NOT! You can now store a pen in your pda. (aka Best commit all commits)
+
+
+
+
+
Wednesday, June 13th
+
Carn updated:
+
+
Massive Mob-Icon Overhaul: A large amount of the mob code has been replaced. The systems replaced were causing immense performance issues so the following are very necessary optimisations. However, there is a downside: SS13 code is the equivilant of monkeys on typewriters. Despite weeks of constant coding/testing there -will- be things I've missed. The kinds of bugs I'm expecting are overlays not updating and/or in rare cases things not appearing in your hud. Most of these issues can be worked around simply by dropping the item and picking it back up. If all else fails ask an admin to regenerate your icons through view-vars. Please report any bugs to me on #coderbus IRC or make an issue on the tracker as a matter of urgency. I will fix them ASAP. Also a massive thankyou to Nodrak, Erro, Pete and Willox. :)
+
Massive rewrite of the overlays system (particularly for humans). Stuff is cached and only updates when necessary. In effect this means faster updates, less overheads/lag, and less reliance on the game-ticker.
+
Numerous bugfixes and tweaks for damage-procs and damage-overlays for humans. They should now be almost seamless, use very little overhead and update instantly.
+
TK grab can now be cancelled using the throw hotkey. (so now it toggles on/off like it used to).
+
Added verbs to the view-var drop-down list: "Regenerate Icons" will fix a mob's hud/overlays. "Set Mutantrace" will change a mob's mutantrace.
+
Damage icons were split up. They kinda look a bit crap so spriters feel free to replace them. Templates are provided in dam_human.dmi
+
More to come...
+
Cheridan updated:
+
+
-Added Lezowski's overalls to hydroponic supply closets. 50% chance per closet for them to replace the apron. -Removed some covers tags from things that made no sense to have them.
+
+
+
+
+
Monday, June 11th
+
Donkie updated:
+
+
Fixed being able to lock yourself in or out of a locker using the verb.
+
+
Xerux updated:
+
+
Added lightfixture creating.
+
+
Errorage updated:
+
+
You can now use the resist verb or UI button when welded or locked in a closet. Takes 2 minutes to get out tho (Same as getting out of handcuffs or unbuckling yourself)
+
Making single-pane windows will now make the window in the direction you're facing. If a window already exists in that direction it will make it 90 degrees to your left and so on.
+
+
+
+
+
Sunday, June 10th
+
Agouri updated:
+
+
Cyborgs and AIs can now use the newscaster. It was a mistake on my part, forgetting to finish that part of them.
+
+
+
+
+
Saturday, June 9th
+
Errorage updated:
+
+
You can now make restraints from cable. It takes 15 lengths of cable to make a pair of restraints, they are applied the same way as handcuffs and have the same effects. It however only takes 30s to remove them by using the resist verb or button. You can also remove them from someone by using wirecutters on the handcuffed person.
+
Added four new cable colors: pink, orange, cyan and white. Engineer belts spawn with yellow, red or orange cables while toolboxes and tool closets spawn with all 8 colors.
+
+
+
+
+
Thursday, June 7st
+
Icarus updated:
+
+
Added a second ZIS suit to engineering.
+
Remapped CE office and surrounding areas.
+
+
+
+
+
Wednesday, June 6th
+
Sieve updated:
+
+
Radiation now works properly, watch out for that Singularity!
+
Disposals are no longer the loudest machines in existence.
+
Building portable turrets with lasertag guns now makes them fire lasertag bolts based on team, and they will automatically target and prioritize people wearing opposing team gear.
+
The same can be done for ED-209s, simply using a lasertag vest and gun (same color) where you would use a security vest and taser in construction.
+
Added mineral walls and powered mineral door construction. More information can be found in the commit thread, but basically they are built the same way others are, apply mineral to girder for a mineral wall, mineral to airlock assembly for a powered mineral door.
Swap hands hotkey (page up) now cycles through borg modules.
+
+
Nodrak updated:
+
+
Cargo's 'shuttle: station' and 'shuttle: dock' has been changed to 'shuttle: station' and 'shuttle: away' to help avoid confusion.
+
+
Icarus updated:
+
+
Maintenance shafts changed around. Renamed, less windows, more turns, and expanded in a few areas.
+
+
Neek updated:
+
+
You can now add chemicals into cigarettes by injecting them directly or dipping individual cigarettes into a beaker. You can inject into cigarette packs directly to affect multiple cigarettes at once.
+
+
Willox updated:
+
+
You can now click individual blocks/subblocks in the genetics console instead of having to scroll through the blocks with a forward/back button!
+
+
+
+
+
Sunday, June 3rd
+
Donkie updated:
+
+
You can now Drag-Drop disposal pipes and machinery into the dispenser, in order to remove them.
+
You must now use wrench before welding a pipe to the ground
+
You can no longer remove a trunk untill the machinery ontop is unwelded and unwrenched
+
You are now forced to eject the disposal bin before unwelding it.
+
+
+
+
+
Friday, June 1st
+
SkyMarshal updated:
+
+
Readded fingerprints and detective work, after lots of debugging and optimization.
+
Any PDA with access to the Security Records can now, by the normal forensic scanner function, store data the same way as the detective's scanner. Scanning the PDA in the detective's computer will copy all scanned data into the database.
+
If something goes wrong, please contact SkyMarshal on the #bs12 channel on irc.sorcery.net
+
+
+
+
+
Friday, June 1st
+
Nodrak updated:
+
+
Windoor's are now constructable! Steps are found here.
+
+
+
+
+
Tuesday, May 29th
+
Nodrak updated:
+
+
Glass Doors are now breakable.
+
Added more treasures to the secret mining room.
+
Changed mineral lockers from secret mining rooms: Instead of giving you two stacks of everything, you get stacks of ore based on rarity
+
+
Icarus updated:
+
+
Moved Engineering and Bridge deliveries.
+
+
+
+
+
This 28th day of May, in the year of our Lord, Two Thousand Twelve
+
Cheridan updated:
+
+
-Adjusted balaclavas and added luchador masks. Wearing luchador masks give you latin charisma. They replace the boxing gloves in the fitness room. Boxing gloves are still available in the holodeck. -Fake moustache tweaked and given new sprites.
+
+
+
+
+
Monday, May 28th
+
Donkie updated:
+
+
You can now dispense Disposal Bins, Outlets and Chutes from the disposal dispenser. These are movable and you can attach them above open trunks with a wrench, then weld them to attach them completely. You can remove Bins by turning off their pump, then screwdriver, then weld, then wrench. Same with outlet and chute except for the pump part.
+
+
+
+
+
Saturday, May 26th
+
Icarus updated:
+
+
Ported over Flashkirby99's RIG suit sprites from Bay12
+
Department PDA Carts moved out of lockers and into head offices.
+
+
+
+
+
Wednesday, May 23rd
+
Cheridan updated:
+
+
-Reverted default UI sprites to Erro's old-style UI. Config options for UI color styles coming soon. -Driest Martinis will no longer be invisible. -Braincakes are now sliceable.
+
-Medical borg overhaul. Instead of a dozen random pills and syringes, they get a hypospray that can switch between auto-replenishing tricordrazine, inprovaline, and spaceacillin.
+
+
Errorage updated:
+
+
Some of the more pressing issues with the new user interface were addressed. These include the health indicator being too far up, the open inventory taking a lot of space, hotkey buttons not being removable and suit storage not being accessible enough.
+
A toggle-hotkey-buttons verb was added to the OOC tab, which hides the pull, drop and throw buttons for people who prefer to use hotkeys and never use the buttons.
+
Added a character setup option which allows you to pick between the Midnight, Orange and Old iconsets for the user interface.
+
+
+
+
+
Tuesday, May 22nd
+
Icarus updated:
+
+
RIG helmets can now be used as flashlights, just like hardhats. Credit to Sly for the sprites.
+
HoP's office has been remapped and made into his private office. Conference Room can now be accessed from the main hall.
+
+
+
+
+
Sunday, May 20th
+
Errorage updated:
+
+
The new user interface is here. If anything is broken or something should be done differently please post feedback on the forum. Spriters are encouraged to make new sprites for the UI.
+
When you receive a PDA message, the content is displayed to you if the PDA is located somewhere on your person (so not in your backpack). You will also get a reply button there. This will hopefully make PDA communication easier.
+
New hotkeys! delete is the now the 'stop dragging' hotkey, insert is the 'cycle intents' hotkey.
+
+
+
+
+
Saturday, May 19th
+
Doohl updated:
+
+
You can now swap hands by clicking with your middle mouse button (you have to click on a visible object though, that's the catch).
+
Tweaked the DNA modifier consoles a little bit so that it's much easier to see individual blocks instead of one jumbled mess of hexadecimal.
+
You can now properly emag AI turret controls and commsat turret controls.
+
+
Invisty updated:
+
+
Brand new ending animations!
+
+
+
+
+
Friday, May 18th
+
Errorage updated:
+
+
Removed hat storage, which was useless.
+
Implanting someone now takes 5 seconds, both people need to remain still. Implanting yourself remains instant.
+
Wallets once again spawn in the cabinets in the dormitory
+
Wallets now fit in pockets
+
+
+
+
+
Thursday, May 17th
+
Icarus updated:
+
+
Individual dorms now have a button inside that bolts/unbolts the door
+
New sprites for Cargo, HoP, and Captain's lockers
+
More department-specific door sprites. Most noticable changes in medsci and supply departments.
+
+
+
+
+
Tuesday, May 15th
+
Icarus updated:
+
+
Added WJohnston's scrubs to Medical Doctor lockers. Comes in blue, green, and purple.
+
Added two new syndicate bundles
+
Reduced cost of thermals to 3 telecrystals (formerly 4)
+
Singularity Beacons are now spawned from a smaller, portable device.
+
CMO and QM jumpsuits made more unique.
+
+
+
+
+
Monday, May 14th
+
Icarus updated:
+
+
Reinforced table parts are now made by using four metal rods on regular table parts. No plasteel involved.
+
Beakers, small and large can now be made/recycled in autolathes.
+
+
Nodrak updated:
+
+
Added a 'random item' button to traitor uplinks. You can potentially get ANY item that shows up on the traitor item list, provided you have enough crystals for it.
+
+
+
+
+
Friday, May 11th
+
Icarus updated:
+
+
New design for security. This should be the last time it sees major changes for a while.
+
Added a new construction area. What could it be for?
+
+
Petethegoat updated:
+
+
Readded the RD's genetics access.
+
RD is still without chemistry access, but I'm going to review this decision in a week and see if R&D is useless due to lack of acid.
+
Added Flashkirby99's SMES sprites!
+
+
Invisty updated:
+
+
Sexy new warpspace (or whatever) tiles.
+
+
Important changes below!
+
+
+
+
Thursday, May 10th
+
Sieve updated:
+
+
Reverted dismemberment, the recent gun changes, and Tarajans. Before you shit up the forums, read this:
+
Dismemberment was ported from Bay12, but only halfway, and there were several problems with it. I know many people really liked it, but as it stood it did not fit the playstyle here at all. This had to be removed, there is work on a more fitting system, but this had to be taken out first regardless, and the longer people beat around the bush the worse the situation got.
+
The gun change was made for no real reason and was pretty problematic, so reverting that should mean there are a lot less 'accidental suicides.'
+
Tarjans were reverted by request as well, and since keeping them working after removing dismemberment would be a stupid amount of work.
+
+
+
+
+
Sunday, May 6th
+
Cheridan updated:
+
+
-New booze sprites for the drinks that were removed! Re-enabled the recipes for the removed drinks. Get cracking, bartenders. -You now need 10 sheets of metal instead of 2 to make a gas canister, people can't FILL ENTIRE ROOMS WITH THEM.
+
-Emergency Toolboxes now contain smaller, lighter fire extinguishers that actually fit inside them!
+
+
+
+
+
Saturday, May 5th
+
Petethegoat updated:
+
+
RD get the fuck out of chemistry and genetics
+
CHEMISTS, DON'T BE DICKS TO RESEARCH, GIVE THEM ACID YOU TIGHT FUCKS
+
+
Icarus updated:
+
+
Updates to Sec, including a stationary scrubber for the prison area.
+
Swapped around cryogenics and the patient rooms in medbay.
+
+
+
+
+
Friday, May 4th
+
Cheridan updated:
+
+
-Added fat jumpsuit sprites for orange, pink, yellow, owl, security, and warden jumpsuits.
+
-Somatoray is hopefully more useful and less buggy when used on trays. -Botanists now have morgue access, because of their ability to clone via replica pods. Try not to get this removed like all your other access, okay hippies?
Added a verb to the PDA which you can use to remove an ID in it. If your active hand is empy, it puts it there otherwise it puts it on the floor under you.
+
+
+
+
+
Wednesday, April 27th
+
Cheridan updated:
+
+
-New sprites for lemons, oranges, and walking mushroom critters. -Added Invisty's new blob sprites.
+
-Added a new chemical: lipozine, a weight loss drug. Made with sodium chloride, ethanol, and radium.
+
+
+
+
+
Wednesday, April 25th
+
Ikarrus & Flazeo updated:
+
+
New layout for Security, including a prison area instead of permacells.
+
New layout for the library, bar, and botany.
+
Medbay and R&D now have three-tile halls.
+
+
Scroll down for more commits! There's a bunch of new shit.
+
+
+
+
Tuesday, April 24th
+
PolymorphBlue updated:
+
+
Fakedeath changelings can no longer have their brains cut out.
+
Rev checkwin changed to fire every five ticks (from twenty) and actually use the right objective type so revs being off station counts as success.
+
+
Sieve updated:
+
+
Powercells now have unique icons for cell types
+
Implemented mech construction sprite by WJohnston for the Ripley, Firefighter, Gygax, and Durand
+
Durand construction is reversible
+
Power Cells can now be made in Mechfabs, provided the proper research level has been achieved
+
Added a new item, the Synthetic Flash. Works just like a normal flash, except they can only withstand one use, but can be produced in the Mechfab(To replace the need for normal flashes)
+
Added a new type of gloves, ones that are cheap copies of the coveted Insulated Gloves, but be warned, quality control wasn't too thorough
+
Added a new Cyborg Upgrade, a jetpack for use by Miner Cyborgs. Can be refilled on any air canister
+
Miner Cyborgs now have a Diamond Drill equivalent along with an upgraded Ore Satchel
+
Mechfabs no longer brick if there are parts in the quene on sync
+
MMIs can be built in the Mechfabs again
+
Crabs are no longer immortal, and are now especially vulnerable to wirecutters
+
Bibles printed in the library now retain the religion's deity
+
Added Construction Sprites for the Ripley, Firefighter, Gygax, and Durand by WJohnston
+
Added Particle Accelerator sprites by Invisity
+
Added Power Cell, Synthetic Flash, Robot Upgrades, and made some modifications to the PA sprites
+
+
Petethegoat updated:
+
+
Added Invisty's field generator sprites.
+
+
+
+
+
April 1-22, 2012
+
Cheridan updated:
+
+
CATCHING UP ON MY CHANGELOG. Some of this has been in for a while: -Added carved pumpkins and corncob pipes. -Added mutations for ambrosia and lemon trees. -Added more wood items for tower cap wood construction. -Added soil to plant seeds in. Make it by crushing up sandstone. Soil does not have indicators like trays do! Watch your plants carefully!
+
-The biogenerator is now more robust. It can dispense fertilizer in batches, and make simple leather items. -RnD can create a new tool for botanists: The floral somatoray. Has two modes. Use it on your plants to induce mutations or boost yield.
+
-Added plump helmet biscuits, mushroom soup, pumpkin pie and slices, chawanmushi, and beet soup recipes for the chef to make.
+
-Added transparency to biohelmets. -Normalized grass harvests. -Changed the name of "Generic Weeds". -Blenders can now be filled directly from plant bags. -Added low chance for a species mutation whenever a plant's stats mutate. -You now get more descriptive messages when applying mutagen to plant trays. -Removed sugarcane seeds from the vending machine. Added the sugarcane seeds to the seeds crate.
+
+
+
+
+
Sunday, April 22nd
+
Petethegoat updated:
+
+
New gasmask sprites. Removed emergency gasmasks, so there's only one type now.
+
New shotgun sprites by Khodoque!
+
The barman's double-barrel actually works like a double-barrel instead of a pump-action! Rejoice!
+
Sneaky barmen may be able to illegally modify their shotgun, if they so choose.
+
Trimmed the changelog, vastly.
+
+
+
+
+
Saturday, April 21st
+
Errorage updated:
+
+
Maintenance door outside of tech storage now requires maintenance OR tech storage access instead of maintenance AND robotics accesses.
+
+
+
+
+
Thursday, April 19th
+
Carn updated:
+
+
Rewrote the cinematic system to try and simplify and optimise it. Please report any bugs asap to me or coderbus, thanks.
+
+
+
+
+
Tuesday, April 17th
+
Kor updated:
+
+
Engineering jobs now have their PDA spawn in their pocket, and their toolbelt on their belt
+
The nuke going off on station will now gib everyone on the Z level.
+
Two more core displays are available for the AI
+
The Artificer can now build cult floors and walls
+
+
+
Friday, April 13th
+
Sieve updated:
+
+
Updated the robotics layout.
+
+
Petethegoat updated:
+
+
Nerfed the librarian by removing the r-walls from his cubbyhole thing, fuck WGW readers hiding out in there.
+
+
+
+
Thursday, April 12th
+
Agouri updated:
+
+
Fixed the ability to move while lying down/resting.
+
Sleep has been fixed and works as intended again. Anaesthetic and toxins can now properly put people to sleep, permanently if you keep the administration stable. Sleeplocs are now viable again. The sleep button and *faint emote work again.
+
+
+
+
Wednesday, April 11th
+
PolymorphBlue updated:
+
+
Droppers are now used at the eyes, and thus, access to the eyes is required to have an effect.
+
+
+
+
+
+
April 10, the year of our lord 2012
+
Agouri updated:
+
+
CONTRABAND-CON UPDATE: Added posters. I'm sorry to add it seperately with the rest of contraband, but there was a lack of sprites for everything else. Hopefully, people will gain interest and get me some damn sprites this way :3
+ As I said, this is an ongoing project of mine. So starting of, we've got...
+
POSTERS! Posters come in rolled packages that can adhere to any wall or r_wall, if it's uncluttered enough.
+
How they get on-board: The quartermaster can now set the receiver frequency of his supplycomp circuit board. A bit simplistic as of now, will work on it later. Building a supplycomp with a properly set up circuitboard will give access to the Contraband crate.
+
How they're used: Unfold the rolled poster on any wall or r_wall to create the poster. There are currently 17 designs, with the possibility of me adding more.
+
How to get rid of them: You can rip them using your hand... To cleanly extract them and not ruin them for future use, however, you can use a pair of wirecutters.
+
How they're classified: They're contraband, so it's perfectly okay for security officers to confiscate them. Punishment for contraband-providers (or end-users, if you want to go full nazi) is up to the situational commanding officers.
+
+
+
Nodrak updated:
+
+
Merged 'Game' and 'Lobby' tabs during pre-game into one tab
+
Added the little red x to the late-join job list
+
Late-joiners are warned if the shuttle is past the point of recall, and if the shuttle has already left the station
+
Late-joiners now see how long the round has been going on.
+
Mining shuttle computer no longer spits out both 'Shuttle has been sent' and 'The shuttle is already moving' every time.
+
+
+
+
+
+
Monday, April 9th
+
Petethegoat updated:
+
+
TORE OUT DETECTIVE WORK! THIS IS A TEMPORARY PATCH TO SEE IF THIS FIXES THE CRASHING.
+
DETECTIVE SCANNERS AND EVIDENCE BAGS (AND FINGERPRINTS) ARE GONE.
+
+
+
+
+
Sunday, April 8th
+
PolymorphBlue updated:
+
+
Secret little rooms now spawn on the mining asteroid, containing various artifacts.
+
Added the beginnings of a borg upgrade system. Currently, can be used to reset a borg's module.
Security officers can modify people's criminal status by simply examining them with a security hud on and clicking a link that will show up as part of the character's description.
+
Less jobs have maintenance access. The only jobs that will have it now are engineers, atmos techs, cargo techs, heads, and the detective.
+
Changed Runtime's sprite to look more catlike.
+
View Variables can now better list associative lists.
+
Miscellaneous bugfixes for the NT Script IDE.
+
+
PolymorphBlue updated:
+
+
Minor bugfixes to borg deathsquad, adds borg deathsquad to potential tensioner (set so high it's never going to happen)
+
Adds consiterable support for ERP! (If enabled.)
+
Increases cost for changeling unstun to 45
+
+
+
+
+
30 March 2012
+
Donkie updated:
+
+
You can now stick papers back in to paperbins, text will persist.
+
Added a [field] bbcode tag to the pen writing. Lets your start writing from that point.
+
Changed fonts a bit for papers to make [sign] stand out more.
+
+
Doohl updated:
+
+
Gave pill bottles the ability to scoop up pills like ore satchels scoop ore. (There you go, /vg/ Anon.)
+
Security Officers and Wardens now start with maintenance acceess.
+
+
+
+
+
29 March 2012
+
PolymorphBlue updated:
+
+
Exosuits now provide a message when someone is getting in, someone getting in must remain stationary and unstunned, and getting in takes four seconds.
+
+
+
+
+
28 March 2012
+
Carn updated:
+
+
Fixed turrets shooting people that leave the area and the telecomm turret controls.
+
+
Donkie updated:
+
+
Updated air alarm's GUI.
+
+
+
+
+
27 March 2012
+
Nodrak updated:
+
+
Security borgs now have modified tasers.
+
Security borgs have gone back to having the same movement speed as all other borgs.
+
+
+
+
+
23 March 2012
+
Doohl updated:
+
+
Escape shuttles/pods now spend about 2 minutes in high-speed transit before they reach centcom/recon shuttle. This is a warning: regular after-round shuttle grief is NOT OKAY while the shuttle is still in transit! Save it for when the shuttle gets to centcom! The purpose of this is to give potential antagonists and protagonists a chance to have a final showdown in the shuttle. The round does not end until the shutle comes to a stop and docks. Don't step outside while the shuttle is moving!
For example; if you are a traitor and have an escape-alone objective and a couple of people manage to squeeze in the shuttle, you have two minutes to kill/toss them out to win. Or you can just chill for the duration and reflect on the round.
+
+
+
Donkieyo updated:
+
+
A bunch new standard-namespace NTSL functions added! Check them out at the NT Script wiki page!
+
+
+
+
+
22 March 2012
+
Ricotez updated:
+
+
Medical Lockers, Security Lockers, Research Lockers, Warden Locker, CMO Locker, and RD locker all have new sprites.
+
Encryption keys now each have their own invidual sprites.
+
+
+
PolymorphBlue updated:
+
+
Added a prototype holodeck to fitness!
+
Assorted tensioner fixes
+
+
+
+
20 March 2012
+
Kor updated:
+
+
Lasertag vests and guns have been added to fitness.
+
Art storage has replaced the emergency storage near arrivals. Emergency storage has replaced chem storage (has anyone ever used that?)
+
Wraiths can now see in the dark
+
+
+
+
+
+
19 March 2012
+
PolymorphBlue updated:
+
+
Added LSD sting to modular changeling by popular demand.
+
Silence sting no longer provides a message to the victim.
+
Tensioner will no longer assign dead people as assassination targets.
+
+
+
+
+
18 March 2012
+
Quarxink updated:
+
+
The medical record computers can finally search for DNA and not just name and ID.
+
+
+
+
+
14 March 2012
+
PolymorphBlue updated:
+
+
Modular changeling added! Changelings now purchase the powers they want. Balancing still underway, but should be playable.
+
+
Petethegoat updated:
+
+
Janitor cyborgs have been massively upgraded. Suffice to say they're pretty ballin' now...
+
+
Nodrak updated:
+
+
You can now choose whether to spawn with a backpack, satchel, or nothing. Excess items will spawn in your hands if necessary.
+
You can now choose what kind of underwear you'd like to wear, per a request.
+
+
+
+
+
14 March 2012
+
Carn updated:
+
+
Added 6 female hairstyles -- credits go to Erthilo of Baystation. Added a male hairstyle -- credits go to WJohnston of TG. If you can sprite some unique and decent-looking hair sprites, feel free to PM me on the TG forums.
+
+
+
The way objects appear to be splattered with blood has been rewritten in an effort to fix stupid things happening with icons. It should be called far less frequently now. PLEASE, if you experience crashes that could in anyway be related to blood, report them with DETAILED information. Thanks
+
+
+
+
+
13 March 2012
+
Nodrak & Carn updated:
+
+
Fixed the way flashes break. Long story short: They'll never break on first use so rev don't get screwed over. They run out of charge temporarily when spammed but recharge. Spamming them also increases the chance of them breaking a little, so use them sparingly.
+
+
Doohl updated:
+
+
Ablative Armor now has a high chance of reflecting energy-based projectiles.
+
Riot shields were buffed; they now block more attacks and they will prevent their wielder from being pushed (most of the time).
+
+
+
+
+
12 March 2012
+
PolymorphBlue updated:
+
+
PDA messages now require an active messaging server to be properly sent.
+
+
+
+
+
11 March 2012
+
PolymorphBlue updated:
+
+
The AI can now open doors with shift+click, bolt them with ctrl+click, and shock them with alt+click
+
Tratior borgs who hack themselves cannot be blown by the robotics console, and can override lockdowns.
+
Adds a new wire to doors that controls the time delay before they close. If pulsed, they close like a sliding glass door. If cut, they do not close by themselves.
+
Borgs who have died, ghosts, and are then blown up will now have their ghosts properly transfered to their dropped MMIs.
+
+
Carnwennan updated:
+
+
You can now request AI presence at a holopad for immediate private communication with the AI anywhere. AIs can click a quick button to zoom to the holopad.
+
+
+
+
+
08 March 2012
+
Nodrak and Carnwennan updated:
+
+
Nodrak: Fixed crayon boxes and stuff getting stuck in pockets.
+
Nodrak: 'Steal item' objectives will report correctly when wrapped up in paper now.
+
Carn: fixed the vent in the freezer...poor chef kept suffocating.
+
+
+
+
+
02 March 2012
+
Carn updated:
+
+
Fixed a number of issues with mob examining. Including: not being able to see burns unless they were bruised; vast amounts of grammar; and icons. Updated them to use stylesheet classes.
+
Borgs can no-longer drop their module items on conveyor belts.
+
Names input into the setup screen are now lower-cased and then have their first letters capitalised. This is to fix problems with BYOND's text-parsing system.
+
Runtime fix for lighting.
+
Over the next few commits I will be updating a tonne of item names to fix text-parsing. Please inform me if I've typoed anything.
+
+
+
+
+
03 March 2012
+
Petethegoat updated:
+
+
Removed cloakers. Removed Security's thermals. Added disguised thermals as a traitor item.
+
+
+
+
+
01 March 2012
+
SkyMarshal updated:
+
+
Tweak/Bugfix for Hallucinations. Much more robust.
+
+
+
+
+
01 March 2012
+
SkyMarshal updated:
+
+
Ported BS12 Detective Work System
+
+
+
+
+
1 March 2012
+
Petethegoat updated:
+
+
Head revolutionaries no longer spawn with traitor uplinks.
+
+
+
+
+
+
29 February 2012
+
SkyMarshal updated:
+
+
BS12 Hallucination and Dreaming port
+
+
+
+
+
+
+
29 February 2012
+
muskets updated:
+
+
Integrated BS12's improved uplink code
+
+
+
+
+
+
26 February 2012
+
Doohl updated:
+
+
The insane crashing has finally been fixed!
+
+
+
+
+
25 February 2012
+
Doohl updated:
+
+
Telecommunications has been refined, with many new features and modules implemented.
+
NTSL (Nanotrasen Scripting Language) is ONLINE! This is a brand new, fully operational scripting language embedded within SS13 itself. The intended purpose is to eventually expand this scripting language to Robotics and possibly other jobs, but for now you may play with the TCS (Traffic Control Systems) implementation of NTSL in the Telecommunications Satellite. Recommended you read the NT Script wiki page for information on how to use the language itself. Other than that, there's not a lot of documentation.
+
Radio systems have been further optimized, bugfixed, etc. Should be more stable.
+
Intercoms now require power to work.
+
+
+
+
+
+
24 February 2012
+
PolymorphBlue updated:
+
+
Headsets are now modular! Use a screwdriver on them to pop out their encrpytion keys, and use a key on one to put it in. A headset can hold two keys. Normal headsets start with 1 key, department headsets with two. The standard chip does nothing, and is not required for listening to the common radio.
+
Binary translators made into a encrpytion key, and fixed. They now broadcast to AIs properly.
+
+
+
+
+
23 February 2012
+
PolymorphBlue updated:
+
+
MMIs/pAIs no longer lip read, and thus can now hear in the dark.
+
Borg rechargers are no longer Faraday cages, and thus can now receive radio while they're recharging.
+
+
LastyScratch updated:
+
+
Glass airlocks now make a different sound than regular airlocks.
+
in 1997 nanotrasen's first AI malfunctioned
+
Toggle ambience probably works now!
+
Runtime is dead.
+
The Research Director's consoles were moved into the completely empty cage in the back of his office.
+
+
+
+
+
+
22 February 2012
+
PolymorphBlue updated:
+
+
Changed alt+click to ctrl+click for pulling.
+
+
Petethegoat updated:
+
+
New stationary scrubber sprites~
+
Removed the mint. Coins can still be found and used in vending machines. PACMANs now run off sheets.
+
+
coolity updated:
+
+
New sprites for HoS and Captain lockers.
+
New sprites for the orebox.
+
+
+
+
+
21 February 2012
+
Petethegoat updated:
+
+
The jetpacks now display correctly when worn.
+
Buckling to chairs no longer causes you to drop your weapon
+
+
Nodrak updated:
+
+
Ghosts now have a "Jump to Mob" verb.
+
+
Sieve updated:
+
+
Mining lanterns work properly once again!.
+
Skaer updated:
+
+
The armoury now includes a box of spare Sec cartridges.
+
+
+
+
+
19 February 2012
+
Petethegoat updated:
+
+
The jetpacks in EVA have been replaced with CO2 ones, painted a classy black.
+
Additionally, jetpacks will now run on gases other than oxygen, as you would expect.
+
Chair overhaul! You shouldn't notice anything different, but if you encounter bugs with chairs or beds, please report those asap.
+
New electric chair sprites, by myself.
+
Electric chairs will only electrocute people buckled into them.
+
Karma should be fixed.
+
+
KorPhaeron updated:
+
+
A new construct type: Artificer. It is capable of constructing defenses, repairing fellow constructs, and summoning raw materials to construct further constructs
+
Simple animals (constructs, Ian, etc) can now see their health in the Status tab
+
Detective's revolver is non-lethal again. Was fun while it lasted
+
+
+
+
+
18 February 2012
+
Petethegoat updated:
+
+
Foam has a reduced range to prevent spamming
+
+
Sieve updated:
+
+
Stopped the unholy Radium/Uranium/Carbon smoke that crashed the server. And for anyone that did this, you are a horrible person
+
Cleanbots clean dirt
+
Cleanbots automatically patrol on construction
+
Removed silicate because it is not useful enough for how much lag it caused
+
+
+
+
+
16 February 2012
+
Smoke Carter updated:
+
+
Newscasters now alert people of new feeds and wanted-alerts simultaneously.
+
+
+
+
+
15 February 2012
+
Kor updated:
+
+
Terrorists Win! Desert Eagles and Riot Shields now spawn on the syndicate shuttle, replacing the c20r
+
The Detectives gun still uses .38, but they're now fully lethal bullets. Go ahead, make his day.
+
The Veil Render has been nerfed, the Nar-Sie it spawns will not pull anchored objects. This is a temporary measure, more nerfs/reworking to come
+
+
+
+
+
14 February 2012
+
Carn updated:
+
+
Spacevines added to the random events.
+
The bug where doors kept opening when a borg tried to close them at close range is now fixed.
+
+
+
+
+
13 February 2012
+
Khodoque updated:
+
+
Security officers, the warden and the HoS have new jumpsuits.
+
+
Erro updated:
+
+
Clicking the internals button on your user interface (The one that shows if you have internals on or not) will now toggle internals even if they are in your pockets. (humans only) - It now works if your internals are on your back, suit storage, belt, hands and pockets.
+
The public autolathe has been removed. If you want some some stuff from a lathe, go to cargo.
+
+
Kor updated:
+
+
A new item, the null rod, protects the one bearing it from cult magic. One starts in the chaplains office, and this replaces the job based immunity he had. The null rod also is capable of dispelling runes upon hitting them (the bible can no longer do this)
+
Shooting fuel tanks with lasers or bullets now causes them to explode
+
A construct shell is now waiting to be found in space.
+
Chaplains can no longer self heal with the bible
+
Simple animals, including constructs, can now attack mechs and critters
+
+
+
+
+
+
12 February 2012
+
Erro updated:
+
+
You can no longer attach photos to ID cards. This never worked properly and if anything, it was misleading.
+
Backpacks can now hold 7 normal sized items (box size) as opposed to 6 normal sized items + 1 small item
+
Added several fire alarms to areas around the station including the brig, engineering and others
+
The atmospherics department now has a few hazard vests available for atmos techs to wear if they don't like the fire suit
+
Roboticist now have engineering + science headsets, virologists now have medsci headsets with medical + science channels
+
Added some headsets to the jobs that didn't have any extras: roboticist, qm, scientist, virologist and geneticist.
+
Station engineers now have construction site access (vacent office by arrivals)
+
Replaced a few airlocks with glass airlocks (detective, autolathe, assistant storage, robotics, checkpoint)
+
Removed the wall that was blocking the entrance to the theater
+
Made a small redesign for the HoP's office so that people running towards it from the escape hallway don't run right into the queue, annoying everyong
+
The engineering, command and security airlocks now glow green when closing instead of red to match all the other airlocks
+
The disposal units now auto trigger every 30 game ticks, if there is something (or someone) in them. So no more hiding in disposal units!
+
You can no longer control the disposal unit from within it. You will have to wait for it to trigger itself.
+
You can no longer strip items off of Ian while dead / a ghost
+
+
Pete updated:
+
+
Updated fitness, athletic shorts are now available!
+
+
+
+
+
+
11 February 2012
+
Erro updated:
+
+
You can now take individual crayons out of the crayon box the same way as from boxes
+
Clicking a grille with a glass or reinforced glass pane in your hand will glaze the grille from the direction you're looking from (don't forget to fasten the window tho)
+
When you click somewhere with the intent to interact, you will automaticaly face the item you're trying to interact with. This won't slow you down when running and firing guns behind you.
+
+
Kor updated:
+
+
A new passive mob ability: Relentless. Relentless mobs cannot be shoved (though may still swap places with help intent)
+
Alien Queens, Juggernaut constructs, and Medical Borgs are all Relentless. Maybe the medborg can actually drag people to medbay on time now
+
Two constructs, the Juggernaut and the Wraith are now available for wizards and cultists to use soul stones with
+
A new highly destructive artefact, Veil Render, is now available for wizards
+
A new one time use global spell, Summon Guns, is now available for wizards.
+
DEEPSTRIKING! There is now a partially constructed teleporter on the nuke shuttle, and for a large sum of telecrystals they may purchase the circuitboard needed to complete it.
+
The Chaplain is immune to cult stun, blind, deafen, and blood boil
+
+
+
+
+
10 February 2012
+
Quarxink updated:
+
+
Added a new toy: Water balloons. They can be filled with any reagent and when thrown apply the reagents to the tile and everything on it.
+
+
+
+
+
9 February 2012
+
Erro updated:
+
+
Engineering and security lockers now spawn with their respective backpacks in them so job-changers can look as they should. HoS locker now also contains an armored vest, for the convenience of the HoS who wants to play with one.
+
Slightly changed the spawn order of items in the CE and HoS lockers to make starting up a hint less tedious.
+
+
+
+
+
8 February 2012
+
ConstantA updated:
+
+
Added Exosuit Jetpack
+
Added Exosuit Nuclear Reactor (runs of normal, everyday uranium, maybe I'll switch it to run on enriched) - requires research (level 3 in Materials, Power Manipulation and Engineering)
+
Added Ripley construction steps sprites (courtesy of WJohnston - man, you're awesome)
+
Exosuit Sleeper can now inject occupant with reagents taken from Syringe Gun
+
Exosuit Cable Layer will now auto-dismantle floors
+
Exosuit Heavy Lazer cooldown increased, Scattershot now fires medium calibre ammo (less damage)
+
Exosuit wreckage can be pulled
+
EMP now drains half of current exosuit cell charge, not half of maximum charge.
+
Fixed several possible exosuit equipment runtimes
+
Introduced new markup to changelog. Javascript is extremely slow (in byond embedded browser) for some reason.
+
+
+
+
4 February 2012, World Cancer Day
+
Erro updated:
+
+
Examining humans now works a bit differently. Some external suits and helmets can hide certain pieces of clothing so you don't see them when examining. Glasses are also now displayed when examining.
+
The job selection screen has been changed a little to hopefully make making changes there easier.
+
+
+31 January 2012
+
+
Carn updated:
+
+
Grammar & various bug-fixes
+
Thank-you to everyone who reported spelling/grammar mistakes. I'm still working on it, so if you spot anymore please leave a comment here. There's still lots to fix.
+
Mining station areas should no longer lose air.
+
+
+
+
+30 January 2012(
+
+
Sieve updated:
+
+
This stuff is actually already implemented, it just didn't make it to the changelog
+
Firefighter Mech - A reinforced Ripley that is more resistant to better cope with fires, simply look in the Ripley Contruction manual for instructions.
+
Mech contruction now has sounds for each step, not just 1/4 of them.
+
Mech Fabricators are fixed, Manual Sync now works and certain reseach will reduce the time needed to build components.
+
Added special flaps to the mining station that disallow air-flow, removing the need to shuffle Ore Boxes through the Airlocks.
+
Each outpost has it's own system for the conveyors so they won't interfere with each other.
+
Powercell chargers have been buffed so now higher capacity cells are actually useable.
+
A diamond mech drill has been added. While it isn't any stronger than the standard drill, it is much faster.
+
+
+
+
+29 January 2012, got Comp Arch exams on Wednesday :(
+
+
Agouri updated:
+
+
UPDATE ON THE UPDATE: Newspapers are now fully working, sorry for that. Some minor icon bugs fixed. Now I'm free to work on the contest prizes :3
+
Newscasters are now LIVE! Bug reports, suggestions for extra uses, tears etc go here.
+
What ARE newscasters? Fans of the Transmetropolitan series might find them familiar. Basically, they're terminals connected to a station-wide news network. Users are able to submit channels of their own (one per identified user, with channels allowing feed stories by other people or, if you want the channel to be your very own SpaceJournal, being submit-locked to you), while others are able to read the channels, either through the terminals or a printed newspaper which contains every news-story circulating at the time of printing.
+
About censorship: You can censor channels and feed stories through Security casters, found in the HoS'es office and the Bridge. Alternatively, if you want a channel to stop operating completely, you can mark it with a D-Notice which will freeze it and make all its messages unreadable for the duration it is in effect. If you've got the access, of course.
+
Basically I think of the newscaster as nothing more as an additional Roleplaying tool. Grab a newspaper along with your donuts and coffee from the machines, read station rumors when you're manning your desk, be a station adventurer or journalist with your very own network journal!
+
I would ask for a bit of respect when using the machine, though. I removed all and any channel and story restrictions regarding content, so you might end up seeing channels that violate the rules, Report those to the admins.
+
Finally, due to the removal of the enforced "Channel" string, it's recommended to name your channels properly ("Station Paranormal Activity Channel" instead of "Station Paranormal Activity", for example")
+
+
+
+
+28 January 2012
+
+
BubbleWrap updated:
+
+
Arresting buff!
+
A person in handcuffs being pulled cannot be bumped out of the way, nor can the person pulling them. They can still push through a crowd (they get bumped back to behind the person being pulled, or pushed ahead depending on intent).
+
+
+
+27 January 2012
+
+
LastyScratch updated:
+
+
Toggle-Ambience now works properly and has been moved from the OOC tab to the Special Verbs tab to be with all the other toggles.
+
+
RavingManiac updated:
+
+
The bar now has a "stage" area for performances.
+
+
Blaank updated:
+
+
Added a vending machine to atmopherics reception desk that dispenses large
+oxygen tanks, plasma tanks, emergency oxegen tanks, extended capacity emergency
+oxygen tanks, and breath masks.
+
+
Petethegoat updated (for a bunch of other people):
+
+
Lattice is now removed when you create plating or floor (credit Donkieyo).
+
Monkeys now take damage while in crit (credit Nodrak).
+
The warden now has his own jacket. (credit Shiftyeyesshady).
+
Spectacular new dice that will display the proper side when rolled!! (credit TedJustice)
+
Spectacular new dice that will display the proper side when rolled!! (credit TedJustice)
+
+
Borg RCDs can no longer take down R-walls. (headcoder orders)
+
+
+19 January 2012
+
+
Petethegoat updated:
+
+
Exciting new pen additions! Get the low-down at the wiki.
+
+
+
+17 January 2012
+
+
Doohl updated:
+
+
Syndicate shuttle now starts with a All-In-One telecommunication machine, which acts as a mini-network for the syndie channel. It intercepts all station radio activity, too, how cool is that?
+
+
+
+15 January 2012
+
+
Doohl updated:
+
+
The radio overhaul 'Telecommunications' is now LIVE. Please submit any opinions/feedback in the forums and check the wiki article on Telecommunications for some more info for the curious.
+
The AI satellite has been replaced with a communications satellite. You can get there via teleporter or space, just like the AI satellite. I highly recommend not bum-rushing the new satellite, as you may be killed if you don't have access. It's a very secure place.
+
Once a human's toxicity level reaches a certain point, they begin throwing up. This is a natural, but overall ineffective method of purging toxins from the body.
+
You can now travel Z-levels in Nuclear Emergency mode (the nuke disk is still bound to the station). This means the nuclear agents can and probably will fly off into space to blow up the comm satellite and shut down communications.
+
+
+
+9 January 2012
+
+
ConstantA updated:
+
+
Reworked exosuit internal atmospherics (the situation when exosuit is set to take air from internal tank, otherwise cabin air = location air):
+
+
If current cabin presure is lower than "tank output pressure", the air will be taken from internal tank (if possible), to equalize cabin pressure to "tank output pressure"
+
If current cabin presure is higher than "tank output pressure", the air will be siphoned from cabin to location until cabin pressure is equal to "tank output pressure"
+
Tank air is not altered in any way even if it's overheated or overpressured - connect exosuit to atmos connector port to vent it
+
"Tank output pressure" can be set through Maintenance window - Initiate maintenance protocol to get the option
+
+
+
Fixed bug that prevented exosuit tank air updates if exosuit was connected to connector port
+
Combat exosuits melee won't gib dead mobs anymore
+
QM exosuit circuit crates cost lowered to 30 points
+
Exosuit plasma converter effectiveness +50%
+
+
+
+
+8 January 2012
+
+
Agouri updated:
+
+
I'm back home and resumed work on Newscasters and Contraband.
+
But I got bored and made cargo softcaps instead. Flippable! Enjoy, now all we need is deliverable pizzas.
+
Oh, also enjoy some new bodybag functionality and sounds I had ready a while ago, with sprites from Farart. Use a pen to create a visible tag on the bodybag. Wirecutters to cut it off. Also it's no longer weldable because it makes no goddamn sense.
+
+
+
+7 January 2012
+
+
Donkieyo updated:
+
+
You must now repair damaged plating with a welder before placing a floor tile.
+
You can now relabel canisters if they're under 1kPa.
+
+
Polymorph updated:
+
+
Dragging your PDA onto your person from your inventory will bring up the PDA screen.
+
You can now send emergancy messages to Centcomm (Or, with some.. tampering, the Syndicate.) via a comms console. (This occurs in much the fashion as a prayer.)
+
+
+3 January 2012
+
+
Erro updated:
+
+
Shift-clicking will now examine whatever you clicked on!
+
+
Polymorph updated:
+
+
Alt-clicking will now pull whatever you clicked on!
+
+
+
+1 January 2012 (12 more months until doomsday)
+
+
Doohl updated:
+
+
XENOS ARE NOW IMMUNE TO STUNNING! To compensate, stunning via tasers/batons now slows them down significantly.
+
+
+
Polymorph updated:
+
+
Doors no longer close if they have a mob in the tile. (Generally!) Door safties can now be overriden to close a door with a mob in the tile and injure them severely.
+
+
+
+
+29 December 2011
+
+
ConstantA updated:
+
+
Added some new Odysseus parts and tweaked old ones.
+
Added Exosuit Syringe Gun Module
+
New Odysseus sprites - courtesy of Veyveyr
+
+
Polymorph updated:
+
+
Air Alarms can now be hacked.
+
Too much of a good thing is just as bad as too little. Pressures over 3000 kPa will do brute damage.
+
+
+
+28 December 2011
+
+
RavingManiac updated:
+
+
Wrapped objects can now be labelled with a pen
+
Wrapped small packages can be picked up, and are now opened by being used on themselves
+
Mail office remapped such that packages flushed down disposals end up on a special table
+
Package wrappers placed in most of the station departments
+
In short, you can now mail things to other departments by wrapping the object, labelling it with the desired destination using a pen, and flushing it down disposals. At the mail room, the cargo tech will then tag and send the package to the department.
+
+
+
+27 December 2011
+
+
Errorage updated:
+
+
Engineering's been remapped
+
+
RavingManiac updated:
+
+
Refrigerators and freezer crates will now preserve meat
Circuit boards for Odysseus mech can be ordered by QM
+
Designs for them were added to R&D
+
+
+
Kor updated:
+
+
Soul Stones Added: Like intellicards for dead or dying humans! Full details are too long for the changelog
+
A belt full of six soul stones is available as an artefact for the wizard
+
Cultists can buy soulstones with their supply talisman
+
The chaplain has a single soulstone on his desk
+
The reactive teleport armour's test run is over. It no longer spawns in the RD's office.
+
+
+
+
+
+
+24 December 2011
+
+
Rockdtben updated:
+
+
Added sprites for soda can in left and right hands on mob: sodawater, tonic, purple_can, ice_tea_can, energy_drink, thirteen_loko, space_mountain_wind, dr_gibb, starkist, space-up, and lemon-lime.
+
+
+
+
+
+23 December 2011
+
+
ConstantA updated:
+
+
Mech Fabricators now require robotics ID to operate. Emag removes this restriction.
+
Added Odysseus Medical Exosuit. Has integrated Medical Hud and ability to mount medical modules.
+
Added Sleeper Medical module for exosuits. Similar to common sleepers, but no ability to inject reagents.
+
Added Cable Layer module for exosuits. Load with cable (attack cable with it), activate, walk over dismantled floor.
+
Added another exosuit internal damage type - short circuit. Short-circuited exosuits will drain powercell charge and power relay won't work.
+
You should be able to send messages to exosuit operators using Exosuit Control Console
+
Gygax armour and module capacity nerfed.
+
Exosuit weapon recharge time raised.
+
Bugfix: EMP actually drains exosuit cell and damages it
+
+
+
RavingManiac updated:
+
+
Meat will now spoil within three minutes at temperatures between 0C and 100C.
+
Rotten meat has the same nutritional value as normal meat, and can be used in
+the same recipes. However, it is toxic, and ingesting a badly-prepared big bite
+burger can kill you.
+
Because refrigeration serves a purpose now, the kitchen cold room freezing unit
+is turned off by default. Chefs should remember to turn the freezer on at the
+start of their shift.
+
+
+
+
+21 December 2011
+
+
RavingManiac updated:
+
+
Kitchen cold room is now cooled by a freezing unit. Temperature is about 240K by default, but can be raised to room temperature or lowered to lethal coldness.
+
+
+
+19 December 2011
+
+
Kor updated:
+
+
General/Misc Changes
+
+
Escape pods no longer go to the horrific gibbing chambers. Rather, they will be picked up by a salvage ship in deep space. (This basically changes nothing mechanics wise, just fluff)
+
An ion rifle now spawns on the nuclear operative shuttle. Maybe this will help with them getting destroyed by sec borgs every round?
+
+
+
Wizard Changes
+
+
The wizard can now purchase magic artefacts in addition to spells in a subsection of the spellbook.
+
The first (and currently only) new artefact is the Staff of Change, which functions as a self recharging energy weapon with some special effects.
+
The wizard has a new alternative set of robes on his shuttle.
+
+
+
Cult Changes
+
Cultists now each start with three words (join, blood, self). No more will you suffer at the hands of cultists who refuse to share words.
+
The starting supply talisman can now be used five times and can now be used to spawn armor and a blade.
+
Replaced the sprites on the cultist robes/hood.
+
+
+
+
+18 December 2011
+
+
Carnwennan updated:
+
+
Thanks to the wonders of modern technology and the Nanotrasen steel press Ian's head has been shaped to fit even more silly hats. The taxpayers will be pleased.
+
+
+
Doohl updated:
+
+
Vending machines got yet another overhaul! Good lord, when will they stop assfucking those damned vendors??
+
+
+
+
+17 December 2011
+
+
Erro updated:
+
+
Your direct supervisors are now displayed when you are assigned a job at round start or late join.
+
+
+
+
+14 December 2011
+
+
Erro updated:
+
+
Meteor mode is hopefully deadly again!
+
+
+
Kor updated:
+
+
Research director has a new toy: Reactive Teleport Armour. Click it in your hand to activate it and try it out!
+
+
+
+
+11 December 2011
+
+
NEO updated:
+
+
AIs actually consume power from APCs now
+
Bigass malf overhaul. tl;dr no more AI sat, instead you have to play whackamole with APCs.
+
+
+
+
+10 December 2011
+
+
Doohl updated:
+
+
Title music now plays in the pregame lobby. You can toggle this with a verb in "Special Verbs" if you really want to.
+
User Interface preferences now properly get transferred when you get cloned.
+
+
+
Erro updated:
+
+
Escape pods have been added to test the concept.
+
Escaping alone in a pod does not count towards the escape alone objective, it counts towards the escape alive objective tho. Escape alone only requires you to escape alone on the emergency shuttle, it doesn't require you to ensure all pods are empty. Cult members that escape on the pods do not ocunt towards the cult escaping acolyte number objective. Escaping on a pod is a valid way to survive meteor.
+
+
+
Polymorph updated:
+
+
Fire is now actually dangerous. Do not touch fire.
+
+
+
+
+
+
+8 December 2011
+
+
Errorage updated:
+
+
Fixed the comms console locking up when you tried to change the alert level.
+
Added a keycard authentication device, which is used for high-security events. The idea behind it is the same as the two-key thing from submarine movies. You select the event you wish to trigger on one of the devices and then swipe your ID, if someone swipes their ID on one of the other devices within 2 seconds, the event is enacted. These devices are in each of the head's offices and all heads have the access level to confirm an event, it can also be added to cards at the HoP's ID computer. The only event that can currently be enacted is Red alert.
+
+
+
Kor updated:
+
+
The chef now has a fancy dinner mint in his kitchen. It is only wafer thin!
+
+
+
+
+3 December 2011
+
+
Pete & Erro Christmas update:
+
+
Reinforced metal renamed to steel and steel floor tile renamed to metal floor tile to avoid confusion before it even happens.
+
It is no longer possible to make steel from metal or vice versa or from the autolathe. You can however make metal from the autolathe and still insert steel to increase the metal resource.
+
To make steel you can now use the mining smelting unit and smelt iron and plasma ore.
+
The RCD can no longer take down reinforced walls.
+
+
+
Errorage updated:
+
+
Grass plants in hydro now make grass floor tiles instead of the awkward patches.
+
+
+
Petethegoat updated:
+
+
Fixed all known vending machine issues.
+
Fixed a minor visual bug with emagged lockers.
+
Clarified some of the APC construction/deconstruction messages.
+
+
+
Numbers updated:
+
+
Potency variations tipped in favour of bigger changes over smaller periods of time.
+
+
+
PolymorphBlue updated:
+
+
Traitors in the escape shuttle's prison cell will now fail their objective.
+
Lockers are no longer soundproof! (or flashproof, for that matter)
+
Headrevs can no longer be borged, revs are dereved when borged.
+
Changeling husks are now borgable again (though not clonable) and genome requirements were lowered
+
De-revved revolutionaries had their message clarified a bit. You remember the person who flashed you, and so can out ONE revhead
+
Light tubes/bulbs can now be created in the autolathe. Recycle those broken lights!
+
+
+
+
+22 November 2011
+
+
Doohl updated:
+
+
The firing range now has a purpose. Go check it out; there's a few surprises!
+
+
+
+
+19 November 2011
+
+
Doohl updated:
+
+
Toggling admin midis will now DISABLE THE CURRENT MIDI OH MY GOSH!
+
+
+
Tobba updated:
+
+
We're looking for feedback on the updated chem dispenser! It no longer dispenses beakers of the reagent, and instead places a variable amount of the reagent into the beaker of your choosing.
+
+
+
Petethegoat updated:
+
+
Diagonal movement is gone on account of them proving to be a bad idea in practice. A grand experiment nonetheless.
+
+
+
Kor updated:
+
+
The PALADIN lawset in the AI upload has been replaced with the corporate lawset
+
+
+
+
+16 November 2011
+
+
Tobba updated:
+
+
Report any issues with the updated vending machines!
+
+
+
+
+16 November 2011
+
+
Petethegoat updated:
+
+
Security, Engineer, Medical, and Janitor borgs no longer get a choice of skin. This is for purposes of quick recognition, and is the first part of a series of upcoming cyborg updates.
+
+
+
+
+7 November 2011
+
+
Kor updated:
+
+
Repair bots (mechs) are now adminspawn only
+
Extra loyalty implants are now orderable via cargo bay (60 points for 4 implants)
+
Changeling regen stasis now takes two full minutes to use, but can be used while dead. Burning and gibbing are the only way to keep them dead now.
+
+
+
+
+29 October 2011
+
+
ConstantA updated:
+
+
Added step and turn sounds for mechs
+
Added another mecha equipment - plasma converter. Works similar to portable generator. Uses solid plasma as fuel. Can be refueled either by clicking on it with plasma in hand, or directly from mecha - selecting it and clicking on plasma.
+
Added mecha laser cannon.
+
Added damage absorption for mechs. Different mechs have different absorption for different types of damage.
+
Metal foam now blocks air movement.
+
+
+
Petethegoat updated:
+
+
Fixed sticking C4 to containers.
+
Rearranged the armoury, and changed the medical treatment room in Sec to an interrogation room.
+
Mr Fixit has been powered off and returned to the armoury. Deploying him every round is still recommended!
+
+
+
+
+29 October 2011
+
+
Petethegoat updated:
+
+
Stunglove overhaul: part one. Stun gloves are now made by wiring a pair of gloves, and then attaching a battery- this shows up on the object sprite, but not on your character. Stungloves use 2500 charge per stun! This means that some low capacity batteries will make useless stungloves. To get your old inconspicous gloves back, simply cut away the wire and battery. Note that insulated gloves lose their insulation when you wire them up! Yet to come: stungloves taking extra damage from shocked doors.
+
Removed sleepypens! Paralysis pens have been changed to look like normal pens instead of penlights, and have been slightly nerfed. They will paralyse for about fifteen seconds, and cause minor brain damage and dizziness.
+
Uplink Implants now have five telecrystals instead of four.
+
+
+
Doohl updated:
+
+
More hairs added and a very long beard.
+
Finally fixed the request console announcements going AMP AMP AMP all the time when you use punctuation.
+
+
+
+
+27 October 2011
+
+
Mport updated:
+
+
New WIP TK system added. To activate your TK click the throw button with an empty hand. This will bring up a tkgrab item. Click on a non-anchored (currently) non mob Object to select it as your "focus". Once a focus is selected so long as you are in range of the focus you can now click somewhere to throw the focus at the target. To quit using TK just drop the tkgrab item.
+
+
+
+
+21 October 2011, Tuesday:
+
+
Errorage updated:
+
+
Old keyboard hotkey layout option available again! home, end, page down and page up now once again do what they did before by default. To use diagonal movement you will need to use your numpad with NUM LOCK enabled.
+ The new list of hotkeys is as follows: (Valid as of 21.10.2011)
+
+
Numpad with Num Lock enabled = movement in wanted direction.
+
Numpad with Num Lock disabled = as it was before. movement north-south-east-west and throw, drop, swap hands, use item on itself.
+
Page up (also numpad 9 with num lock disabled) = swap hands
+
Page down (also numpad 3 with num lock disabled) = use item in hand on itself
+
home (also numpad 7 with num lock disabled) = drop
+
end (also numpad 1 with num lock disabled) = throw
+
CTRL + A = throw
+
CTRL + S = swap hands
+
CTRL + D = drop
+
CTRL + W = use item in hand on itself
+
Numpad divide (/) = throw
+
Numpad multiply (*) = swap hands
+
Numpad subtract (-) = drop
+
Numpad add (+) = use item in hand on itself
+
+ In short, use Num Lock to swap between the two layouts.
+
+
+
+
+
+18 October 2011, Tuesday:
+
+
Errorage updated:
+
+
You can now move diagonally! To do so, use the numpad. The keybaord has been remapped to make this possible:
+
There has been a tidal wave of bugfixes over the last 5 or so days. If you had previously tried something on the station, saw that it was bugged and never tried it again, chances are it got fixed. I don't want you to neglect using stuff because you think it was bugged, which it was at one point, but no longer is. Thanks, happy new semester to everyone~
+
+
+
Errorage updated:
+
+
When you're unconscious, paralyzed, sleeping, etc. you will still see the same blackness as always, but it will rarely flicker a bit to allow you to see a little of your surroundings.
+
+
+
Doohl updated:
+
+
New hairstyles! YOU never asked for this!
+
+
+
+
+11 October 2011:
+
+
ConstantA updated:
+
+
Added radios to exosuits. Setting can be found in 'Electronics' menu.
+
Exosuit maintenance can be initiated even if it's occupied. The pilot must permit maintenance through 'Permissions & Logging' - 'Permit maintenance protocols'. For combat exosuits it's disabled by default. While in maintenance mode, exosuit can't move or use equipment.
+
+
+
+
+8 October 2011:
+
+
Doohl updated:
+
+
You can put things on trays and mass-transport them now. To put stuff on trays, simply pick up a tray with items underneath/on top of it and they'll be automatically carried on top of the tray. Be careful not to make a mess~!
+
+
+
Mport updated:
+
+
Telekenesis now only allows you to pick up objects.
+
Stun gloves ignore intent.
+
Moved the loyalty implants to the HoS' locker.
+
Job system redone, remember to setup your prefs.
+
+
+
+
+2 October 2011:
+
+
Petethegoat updated:
+
+
Pandemic recharge speed is affected by the number of different types of antibodies in the blood sample. For maximum efficiency, use blood samples with only a single type of antibody. (Blood samples with two types of antibodies will still let the Pandemic recharge slightly faster than it used to.)
+
+
+
Errorage updated:
+
+
Opening a storage item on your belt will now display the proper number of slots even if the number is different from 7.
Xenomorphic aliens can now shape resin membranes (organic windows basically).
+
The AI can no longer see runes. Instead, they will see blood splatters.
+
+
+
+
+28 September 2011:
+
+
Rolan7 updated:
+
+
New method for job assignment. Remember to review your preferences. Send all your hate and bug reports to me.
+
+
+
Doohl updated:
+
+
Putting someone inside a cloning machine's DNA scanner will notify the person that they are about to be cloned. This completely removes the necessity to announce over OOC for someone to get back in their body.
+
+
+
+
+22 September 2011, OneWebDay:
+
+
Errorage updated:
+
+
Added an additional 9 colors, into which you can color bedsheets, jumpsuits, gloves and shoes at the washing machine.
+
A new click proc will need to be live-tested. The testing will be announced via OOC and you will get a message when it happens. When testing is going on, the new click proc will be used. If testing is going on and you notice a bug, please report it via adminhelp. If you find yourself unable to perform an action, you can double click (By that I mean spam the shit out of clicking) to use the old proc, which is unchanged and will behave like you're used to. Standard roleplay rules apply during tests, they're not an excuse to murder eachother.
+
+
+
+
+20 September 2011, 10 year anniversary of the declaration of the "war on terror":
+
+
Errorage updated:
+
+
You can no longer clone people who suicided. I REPEAT! You can no longer clone people who have suicided! So use suiciding more carefully and only if you ACTUALLY want to get out of a round. You can normally clone people who succumbed tho, so don't worry about that.
+
Washing your hands in the sink will now only wash your hands and gloves. You can wash items if you have them in your hands. Both of these actions are no longer instant tho.
+
+
+
+
+18 September 2011, World Water Monitoring Day:
+
+
Errorage updated:
+
+
Added the most fun activity in your every-day life. Laundry. Check the dormitory. (Sprites by Hempuli)
+
You can now change the color of jumpsuits, shoes, gloves and bedsheets using the washing machine.
+
Some religions (currently Islam, Scientology and Atheism) set your chapel's symbols to the symbols of that religion.
+
A new old-style cabinet's been added to the detective's office. (Sprite by Hempuli)
+
Runtime the cat ran away!! And it gets even worse! Mr. Deempisi met a premature end during an excursion to the kitchen's freezer! -- I was ORDERED to do this... don't kill me! :(
+
Kudzu can now be spawned (Currently admin-only. Will test a bit, balance it properly and add it as a random event) (Original code and sprites donated by I Said No)
+
+
+
Kor updated:
+
+
Added purple goggles to chemistry (you're welcome Lasty)
+
Added a third MMI to robotics and monkey cubes to xenobio (dont fucking spam them in the halls)
+
+
+
Mport updated:
+
+
Turns out tasers and a few other weapons were slightly bugged when it came to checking the internal powercell. Tasers and such gained an extra shot.
+
Ion Rifle shots lowered to 5 per charge.
+
+
+
+
+17 September 2011, Operation Market Garden remembrance day:
+
+
Errorage updated:
+
+
You can now insert a coin into vending machines. Some machines (currently only the cigarette vending machine) have special items that you can only get to with a coin. No, hacking will not let you get the items, coin only.
+
+
+
+
+14 September 2011:
+
+
Lasty updated:
+
+
Runtime now actually spawns in medbay because nobody cared whether it is a tiny smugfaced espeon that explodes violently on death or a spacecat that presumably doesn't.
+
You can no longer put someone into a sleeper and erase them from existence by climbing into the same sleeper.
+
Players who haven't entered the game will no longer be able to hear administrators in deadchat.
+
+
+
Pete updated:
+
+
Added new sprites for the light tube and glasses boxes.
+
Fixed the bug where syndicate bundles would have an emergency O2 tank and breath mask.
+
+
+
Mport, SECOND REMOVER OF SUNS updated:
+
+
Singularity absorbtion explosion range lowered and is now dependent on singularity size.
+
Bag of Holding no longer instakills singularity, and the chance for bombs to destroy a singularity has been changed from 10% to 25%.
+
Removed THE SUN.
+
Damage and stun duration from shocked doors has been lowered to account for a larger amount of energy in the powernet.
+
+
+
+13 September 2011:
+
+
Errorage updated:
+
+
Healing, attacking or 'gently tapping' Ian will now properly display the name of the attacker to all people.
Ian can now be healed with bruisepacks, unless he's already dead.
+
You can now walk over Ian when he's killed.
+
Ian will chase food, if you leave it on the floor. If he notices it and you pick it up, he'll chase you around, if you stay close enough.
+
+
+
+
+10 September 2011:
+
+
Errorage updated:
+
+
A new pet on the bridge.
+
The pet can now be buckled and will no longer escape from closed containers, such as closets or the cloning pods.
+
Vending machines and request consoles are the first to use the new in-built browser in the upper-right of the user interface. If feedback is positive on these, more machines will be added to this. Hoping that this will eventually reduce the number of popup micromanagement.
+
+
+
Lasty updated:
+
+
The collectible hats have been removed from the theatre, doomed to rot forever in the hat crates they spawned from. No longer shall you see racks full of "collectible hard hat"!
+
+
+
TLE updated:
+
+
You can now toggle the message for becoming a pAI on and off in your prefs.
+
+
+
Mport updated:
+
+
Synaptizine now once again helps you recover from being stunned, however it is now also slightly toxic and may cause a small amount of toxins damage for every tick that it is in your system.
+
Assembly updating!
+
Original blob is back, though it still has lava sprites.
+
The bug where you would spawn on the wizard shuttle for a second at the start of the round should no longer occur.
+
+
+
+
+8 September 2011:
+
+
Lasty updated:
+
+
Suicide has been changed to biting your tongue off instead of holding your breath, and examining someone who has comitted suicide will give you a message stating that their tongue is missing.
+
Chemsprayers are now large items instead of small, meaning they can no longer fit in your pocket.
Removed dumb/badly sprited drinks for barman. This is for you, people that love to play barman. Your job is now non-retarded again. EDIT: FIXED DRINK MIXING
Fixed the karma exploit! Uhangi can rest in peace knowing his -87 karma ways were not his own.
+
Added new ambient sound for space and the mines.
+
Examining an oxygen tank will now tell you if it is about to run out of air. If it is, you will recieve a beep and a message, and if not, then you'll just get the default "this is an oxygentank!" message.
Sleeper update! Sleepers can now only inject soporific, dermaline, bicaridine, and dexaline into people with 1% or more health. They can also inject inaprovaline into people with -100% or more health. Nothing can be injected into people who are dead.
+
+
+
Superxpdude updated:
+
+
Virology is now part of medbay, and as such the RD no longer has Virology access and the CMO and Virologist no longer have Research access.
+
+
+
Uhangi updated:
+
+
Electropacks, screwdrivers, headsets, radio signalers, and station bounced radios can now be constructed from the autolathe.
+
Added a courtroom to Centcom.
+
Fixed electropack bug regarding using screwdrivers on electropacks.
Updates made to the HoP's ID computer. New interface and removing a card will now put it directly in your hand, if it's empty. Using your card on the computer will place it in the appropriate spot. If it has access to edit cards it will put it as the authentication card, otherwise as the card to be modified. If you have two cards with ID computer access first insert the authentication card, then the one you wish to midify, or do it manually like before.
People who have been infected by facehuggers can no longer suicide.
+
Stammering has been reworked.
+
The chaplain can now no longer discern what ghosts are saying, instead receiving flavour text indicating that ghosts are speaking to him.
+
Walking Mushroom yield decreased from 4 to 1.
+
Glowshrooms now only spread on asteroid tiles.
+
Fixed rev round end message reporting everyone as dead.
+
Gave the changeling an unfat sting.
+
+
Erro updated:
+
+
R'n'D and Gas Storage locations have been swapped in order for R&D to be given a hallway-facing table, just like Chemistry.
+
Buckling someone to a chair now causes them to face in the same direction as the chair.
+
Conveyor will now move only 10 items per game cycle. This is to prevent miners from overloading the belts with 2000 pieces of ore and slowing down time by turning it on. Does not apply to mobs on belt.
+
+
Doohl updated:
+
+
New escape shuttle!
+
Metroids get hungry slower, but gain more nutrients from eating.
+
+
Kor updated:
+
+
Added Necronomicon bible (credit Joseph Curwen)
+
Removed Doc Scratch clothing from chaplain? locker and added as a random spawn in the theatre.
+
Shaft Miners now start with regular oxygen tanks.
+
Mutate now gives the wizard hulk and OPTIC BLAST instead of hulk and TK.
+
Spiderman suit is no longer armoured or space worthy.
+
Crayons
+
+
Superxpdude updated:
+
+
New, more appropriate arrivals message.
+
Shuttle escape doors fixed.
+
New RIG sprite.
+
+
Lasty updated:
+
+
Switched xenomorph weeds to run in the background, hopefully causing them to destroy the server slightly less.
+
+
Microwave updated:
+
+
Added sink to hydroponics
+
Cleaned up autolathe menu
+
Glasses and cups can now be filled with water from sinks.
+
+
+
+31 August 2011.
+
+
Lasty updated:
+
+
The costumes that spawn in the theatre are now randomized.
+
The kitchen now has a Smartfridge which can be directly loaded from the Botanist? plantbags. No more crates! (credit to Rolan7).
+
Snappops can now be acquired from the arcade machines. Amaze your friends! (credit to Petethegoat)
+
Bangindonk.ogg added to random end sounds list.
+
+
Urist McDorf updated:
+
+
Players can no longer be randomly assigned to Librarian, Atmospherics Technician, Chaplain, and Lawyer unless all other non-assisstant job slots are full or they have those jobs in their preferences.
+
Changeling mode now has multiple changelings.
+
+
Superpxdude updated:
+
+
The mail system actually works properly now! Probably!
+
Most hats had their ?eadspace?tag removed, meaning they will no longer substitute for a space helmet in protecting you from the dangers of space.
+
+
+
+28 August 2011.
+
+
Doohl updated:
+
+
Chaplains can now select different bible icons when they start. The selection is applied globally and the library's bible printer will print the same bibles.
+
Joy to the world! The Library's bible-printing function now has a one-minute cooldown. One minute may seem a little extreme, but it is necessary to prevent people from spamming the fuck out of everything with 100,000,000,000,000 carbon-copy bibles.
+
Tweaked Metroids a bit; they are slightly more aggressive and become hungrier faster. To compensate, they now move slightly slower.
+
+
+
+26 August 2011.
+
+
Mport updated:
+
+
Rev:
+
+
Station Heads or Head Revs who leave z1 will count as dead so long as they are off of the z level.
+
Once a player has been unconverted they may not be reconverted.
+
+
Cult:
+
+
Heads other than the Captain and HoS are now able to start as or be converted to a cultist.
+
New Item: Loyalty Implant, which will prevent revving/culting. 4 spawn in the armory.
+
If a rev (not cultist) is injected with one he will unconvert, if a revhead is injected it will display a resist message.
+
Loyalty Implants show up on the SecHud
+
New Machine: Loyalty Implanter - Is on the prison station, shove a guy inside it to implant a loyalty implant. It can implant 5 times before it needs a 10 minute cooldown.
+
+
+
+
+20 August 2011.
+
+
Doohl updated:
+
+
The smoke chemistry recipe has been upgraded! You can lace smoke with chemicals that can bathe people or enter their lungs through inhalation. Yes, this means you can make chloral hydrate smoke bombs. No, this doesn't mean you can make napalm smoke or foam smoke.
+
+
+
+16 August 2011.
+
+
Superxpdude updated:
+
+
Traitor item bundles: Contains a random selection of traitor gear
+
+
+
+
+
Uhangi updated:
+
+
.38 Special Ammo can now be made from unhacked autolathes
+
+
+
+15 August 2011.
+
+
Superxpdude updated:
+
+
NEW MINING STATION, POST YOUR OPINIONS AND BUGS HERE
+
Added some new awesome mining-related sprites by Petethegoat. All credit for the sprites goes to him.
+
+
+
+9 August 2011.
+
+
Mport updated:
+
+
Cyborgs once again have open cover/cell icons.
+
To override a cyborg's laws you must emag it when the cover is open.
+
Emags can unlock a cyborgs cover.
+
Xbow radiation damage has been lowered from 100 to 20 a hit
+
+
+
+5 August 2011.
+
+
Mport updated:
+
+
The various assemblies should be working now.
+
Old style bombs and suicide vests temporarily removed.
+
+
+
+3 August 2011.
+
+
Superxpdude updated:
+
+
Virology Airlock changed to no longer cycle air. Should work faster and prevent virologists from suffocating.
+
Server Room APC is now connected to the power grid.
+
Stun Batons now start OFF. Make sure to turn them on before hitting people with them.
+
+
+
+2 August 2011. The day the earth stood still.
+
+
Agouri updated:
+
+
SSUs now correctly cycle and dump the unlucky occupant when designated to supercycle, when the criteria to do so are met.
+
Fixed bugshit
+
You can now make normal martinis again, removed a silly recipe conflict (Thanks, muskets.). Good barmen are urged to blend the good ol' recipes since the new ones have sprites that SUCK ASS JESUS CHRIST
+
+
+
+
+
Doohl updated:
+
+
Speech bubbles: you can toggle them on in the character setup window. Basically, whenever someone around you talks, you see a speech bubble appear above them.
+
You can no longer create wizarditis and xenomicrobes with metroid cores.
+
Tweak: Using an exclamation mark as an AI, pAI, or cyborg will not longer get rid of the last exclamation mark.
+
+
+
+30 July 2011.
+
+
Superxpdude Updated:
+
+
Engineer and CE space helmets now have built-in lights.
+
+
Rockdtben updated:
+
+
Bugfix: Fixed a bug where you could dupe diamonds
+
+
Doohl updated:
+
+
New virus: Retrovirus. It basically screws over your DNA.
+
You can now do CTRL+MOVEMENT to face any direction you want. See those chairs in Medbay and the Escape Wing? You can do CTRL+EAST to actually RP that you're sitting on them. Is this cool or what?!
+
+
+
+29 July 2011. - Day of Forum revival!
+
+
Doohl updated:
+
+
Bugfix: Metroids should never "shut down" and just die in a corner when they begin starving. And so, hungry Metroids are a force to be feared.
+
The Cargo computers now have the ability to cancel pending orders to refund credits. This was put in place so that idiots couldn't waste all the cargo points and run off. However, if the shuttle is en route to the station you won't be able to cancel orders.
+
Bugfix: the manifest has been fixed! Additionally, the manfiest is now updated realtime; job changes and new arrivals will be automatically updated into the manifest. Joy!
+
Metroids, when wrestled off of someone's head or beaten off, now get stunned for a few seconds.
+
+
Agouri updated:
+
+
I was always bothered by how unprofessional it was of Nanotransen (in before >Nanotransen >professionalism) to just lay expensive spacesuits in racks and just let them be. Well, no more. Introducing...
+
Suit Storage Units. Rumored to actually be repurposed space radiators, these wondrous machines will store any kind of spacesuit in a clean and sterile environment.
+
The user can interact with the unit in various ways. You can start a UV cauterisation cycle to disinfect its contents, effectively sterilising and cleaning eveyrthing from the suits/helmets stored inside.
+
A sneaky yordle can also hide in it, if he so desires, or hack it, or lock it or do a plethora of shady stuff with it. Beware, though, there's plenty of dangerous things you can do with it, both to you and your target.
+
The Unit's control panel can be accessed by screwdriving it. That's all I'm willing to say, I'd like to let the players find out what each hack option does and doesn't. Will add more stuff later.
+
Added Command Space suit, Chief Engineer space suit and Chief Medical Officer spacesuit (In a new space that you'll probably notice by yourself) to make it easier for you to look like a special snowflake.
+
EVA and CMO office modified to accomodate the new suits and SSUs. Look, I'm not a competent mapper, okay? Fuck you too. A mapper is strongly recommended to rearrange my half assed shit.
+
Soda cans, cigarette packets, cigarettes and cigars as well as bullet casings are now considered trash and can be picked up by the trashbag. Now you can annoy the janitor even more!
+
Sprite credit goes to Alex Jones, his portfolio can be found here: http://bspbox.com. Thanks a lot, bro.
+
With the recent forum fuss and all that, I've got a thread to specifically contain rants and bug reports about this update. Click me
+
+
Uhangi updated:
+
+
EVA redesigned
+
An electropack is now available once again on the prison station
+
+
Errorage updated:
+
+
Hopefully fixed the derelict 'hotspots'. Derelict medbay has also been fixed.
+
+
+
+4 July - 28 July 2011.
+
+
+
Trubble Bass updated (committed by Superxpdude):
+
+
Hat crates. Hat Station 13.
+
+
Matty:
+
+
Engineers and miners now start off with the new industrial backpack.
+
+
Agouri:
+
+
Sleepers are now OP and heal every kind of damage.
+
Made cloning 30% faster, due to popular demand.
+
+
Superxpdude updated:
+
+
Added in the Submachine Gun to R&D.
+
Syndicate agents now have Mini-Uzis.
+
Added an exosuit recharged to the mining station.
+
New labcoats for scientists, virologists, chemists, and genetecists.
+
Moved the vault and added a bridge meeting room next to the HoP's office.
+
Deathsquad armor now functions like a space suit.
+
Added in security jackboots.
+
+
Uhangi updated:
+
+
Traitors can now purchase syndicate balloons, which serve no purpose other than to blow your cover. For a limited time only, you can get them at a bargain price - just 10 telecrystals!
+
Removed security shotguns from the armory. No fun allowed.
+
Changed some bullet damage stuff.
+
+
Microwave updated:
+
+
Monkey boxes:
+
Contains a score of monkey cubes, which you apply water to create monkies. Nanotrasen provides only the finest technology!
+
You can order monkey crates from the cargo bay. They contain monkey boxes.
+
Changed the amount of labels labelers have to 30. 10 was too low and 30 should not be too griefy.
+
Maximum label text length increased from 10 to 64.
+
You can no longer label people because they can just peel the labels off. Sorry, clowns!
+
Jelly dooonnuuuutsss! Happy birthday, officers!
+
Made xenomeat not give any nutrition.
+
Added some new reagents.
+
Made it possible to feed monkies and xenos things, as well as making it possible for them to eat themselves (please don't read that too literally).
+
Added in synthiflesh.
+
Plasma is now not used in reactions, instead, is treated as a catalyst that is not used up. This only applies to certain reactions.
+
Made it possible to grind more things in the chemistry grinder.
+
+
Errorage updated:
+
+
Increased environmental damage by a factor of 1.5.
+
Made firesuits a lot more resistant to heat. Previously, they stopped protecting at around 4,500 degrees. They now go up to around 10,000 (which is also the temperature which the floor starts melting)
+
Edited the quartermaster's office a bit.
+
Cargo technicians now have access to a cargo ordering console.
+
Added different-colored hardhats. The CE gets a white hardhat.
+
+
+
Rastaf.Zero updated:
+
+
Botanists get a new toy: Biogenerator. Insert biological items, recieve biological items.
+
Added roller beds, otherwise known as stretchers, to medbay. You can buckle people onto them and pull them.
+
Added egg-smashing and tomato-smashing decals.
+
+
Muskets updated:
+
+
The prepackaged songs (Space Asshole, Cuban Pete, Darkest Honk, etc) have been removed. This doesn't mean admins can't play midis, this just gets rid of a lot of unnecessary download time.
+
+
Firecage updated:
+
+
A whole bunch of new drinks and food. Seriously, there's alot!
+
New weapons such as the shock revolver and large energy crossbow.
+
Hydroponics can now grow a bunch more stuff. A lot of these new crops have some very interesting mutations.
+
Added a command for AIs to change their icon.
+
New costumes to the costume room.
+
+
Urist McDorf updated:
+
+
Adding shading to pills.
+
Detective's office noir look has been removed. The icon operations required to render everything in monochrome was too heavy on the players.
+
Added in an uplink implant.
+
+
Doohl updated:
+
+
A new alien race: Metroids! They are a mostly non-sentient race of jellyfish-like organisms that float in the air and feed on the life energy of other organisms. While most Metroids have never shown signs of self-awareness, they do exhibit signs of basic logic and reasoning skills as well as very sophisticated perception. Nanotrasen has shipped two baby Metroids for the xenobiology department. They should be handled with the utmost care - they are some of the deadliest beings in the known universe!
+
R&D gets a new toy: the freeze gun. Despite popular belief, this will not literally freeze things!
+
Every single chemical reagent has been assigned a color.
+
You can now see beakers fill up with chemicals. You can also observe how the colors mix inside the beakers. Spray bottles also will also show the color of whatever you're spraying.
+
Added a timestamp to combat logs.
+
Non-insulated gloves now need to be wrapped in wire in order to be electrified.
+
You can now shoot at people on the ground by simply clicking on the tile they're on.
+
Changeling husks are now uncloneable. To clarify: when a changeling sucks out a victim's DNA, the victim is said to become a "husk".
+
+
+
+4 July 2011.
+
+
Agouri updated:
+
+ Medical stuff overhaul in preparation of Erro's big Medic update:
+
Sleepers are now able to heal every kind of damage. Drag your ass to medbay and ask a doctor to get you in one.
+
A lil' bit faster cloning (about 30% faster) due to popular demand.
+
Sleepers are now all located in the inner part of medbay, except for the examination room one.
+
Added Dermaline, burn-healing drug that outpowers kelotane (and kelotane is getting a major nerf, so be sure to know how to get this). Recipe is on the wiki if you need to make it.
+
Drugs no longer heal or metabolise when injected in dead bodies, fuck. Thank god you guys missed this major bug or we'd have cloning-by-injecting-healing-drugs.
+
Reminder to coders: Goddamn, update the changelog.
+
+
+
+
Matty updated:
+
+
New engineering backpacks. Enjoy!
+
+
+
+
+18 June 2011.
+
+
Agouri updated:
+
+
Bugfixes: The reagent grinding now works, allowing the miners to FINALLY bring plasma to the chemistry.
+
Bugfixes: Pill bottles and clipboards can now be removed from the pockets once placed there.
+
+
+
+
+7 June 2011.
+
+
TLE updated:
+
+
Wiped/suicided pAIs should be eligible for being candidates again (5 minute cooldown between prompts.)
+
pAIs are now affected by EMP bursts. pAIs hit with a burst will be silenced (no speech or PDA messaging) for two minutes and may have their directives or master modified. A sufficiently powerful EMP burst will have a 20% chance of killing a pAI.
+
+
+
Neo updated:
+
+
Nar-sie is now a more vengeful eldritch being. When summoned into our world, he first rewards his loyal cultists by chasing down and eating them first, then turns his attention to any remaining humans.
+
+
+
Darem updated:
+
+
Gun Code Overhaul Phase 1.
+
Taser guns shoot ONE WHOLE SHOT more then they do now.
+
Energy Crossbow has a slightly higher shot capacity (still automatically recharges).
+
Revolvers can either be loaded one shell at a time or all at once with an ammo box.
+
Shotguns no longer need to be pumped before firing (will change back in phase 2).
+
+
+
K0000 updated:
+
+
Arcane tome now has a "notes" option. Set English translations for runewords which come up when scribing runes. Attack an arcane tome with another arcane tome to copy your notes to the target tome.
+
Stun rune buffed considerably. Its a 1-use item so it deserved longer stun time.
+
Tome text: Added missing word description for stun rune.
+
+
+
+
+
+1 June 2011, Canadian Day Against Homophobia
+
+
Noise updated:
+
+
Changed holopad speaking to :h on request.
+
Ninja fixes, changes, etc. Refer to the code changelog for more info.
+
+
+
Neo updated:
+
+
Department radio chat now shows with the department name instead of the frequency.
+
+
+
Veyveyr updated:
+
+
Spent casing sprites + SMG sprite.
+
Sprites for 1x4 and 1x2 pod doors.
+
+
+
Errorage updated:
+
+
Fixed twohanded weapon throwing, which left the 'off-hand' object in your other hand.
+
Doors now hide items under them when closed, mobs are still always above.
+
Singularity engine emitter is now much quieter.
+
Mining station redesigned.
+
Atmospherics' misc gasses tank now starts with N2O.
+
Hitting the resist button while handcuffed and buckled to something will make you attempt to free yourself. The process is the same as trying to remove handcuffs. When the 2 minutes pass you will be unbuckled but still handcuffed.
+
+
+
ConstantA updated:
+
+
Added exosuit energy relay equipment. Uses area power (any power channel
+available) instead of powercell for movement and actions, recharges powercell.
+
Exosuits can be renamed. Command is in Permissions & Logging menu.
+
Lowered construction time for Ripley parts.
+
Exosuit wreckage can be salvaged for exosuit parts (torso, limbs etc).
+
Speed-up for mecha.
+
New malf-AI sprite. (Sprite donated by the D2K5 server)
+
+
+
Cheridan updated:
+
+
Updated mine floor and wall edge sprites.
+
+
+
Urist McDorf updated:
+
+
AIs no longer bleed when they reach 0HP (Critical health).
+
Added 2 more security HUDs to security.
+
Security HUDs now show if a person has a tracking implant.
+
+
+
Microwave updated:
+
+
Barman renamed to Bartender.
+
The amount of drink you get when mixing things in the bar has been rebalanced.
+
Fixed arrivals maintenance shaft not having air at round start.
+
+
+
Deuryn updated:
+
+
Meteors now do a bit more damage and they're not stopped by grills.
+
+
+
TLE updated:
+
+
Added personal AIs (pAI).
+
+
+
+
+
+19 May 2011
+
+
Errorage updated:
+
+
Asteroid floors can be built on by adding tiles
+
Mining satchels now fit in rig suit storage, on belts and in pockets.
+
Cables now come in four colors: Red, yellow, green and blue.
+
+
+
+
NEO updated:
+
+
Armour overhaul, phase 3. See rev notes for details.
+
AI cores should now block movement.
+
MMIs are now properly buildable with the mecha fabricator.
+
+
+
+
Urist updated:
+
+
Added sandstone and mineral doors. Mineral boors cannot be opened by the AI or NPCs.
+
Removed Imperium robes from map.
+
Added the ability to draw letters and graffiti with crayons.
+
Removed fire axes except for bridge and atmospherics.
+
+
+
+
Veyveyr updated:
+
+
New serviceborg sprite option.
+
Map changes to robotics; removed borg fabricators and added second exosuit fabricator.
+
Cyborg parts are now built from exosuit fabricators and benefit from research.
+
New exosuit fabricator and borg frame sprites.
+
+
+
+
+14 May 2011, late friday 13 update.
+
+
K0000 updated:
+
+
Cult updates:
+
New rune! Stun rune. When used as rune, briefly stuns everyone around (including cultists). When imbued into a talisman, hit someone to stun and briefly mute them. Spawnable with the starter talisman.
+
Imbue rune doesnt disappear after succesful invocation, only the source rune.
+
Chaplain's bible now has 20% chance to convert a cultist (was 10%), and gives a message on success.
+
Also, wrapping paper is back! Find it in the mailroom.
+
+
+
+
NEO updated:
+
+
Beginning of armor overhaul. Armor now has slightly better defence against melee, and weaker against shots. More coming soon...someday
+
Cyborgs finally drop their MMI when gibbed like they were supposed to back when I added MMIs. Round- start cyborgs use whatever name you have selected for your character for the brain that gets spawned for them.
+
+
+
+
Darem updated:
+
+
Chemistry update
+
In containers where there isn't a perfect ratio of reagents, reactions won't consume ALL of the related reagents (so if you mix 10 anti-toxin with 20 inaprovaline, you get 10 tricordrazine and 10 inaprovaline rather then just 10 tricodrazine)
+
Catalysts: some reactions might need presence of an element, while not directly consuming it.
+
Reactions changed to use catalysts: all recipes that require Universal Enzyme now require 5 units of the enzyme but the enzyme isn't consumed (So Tofu, Cheese, Moonshine, Wine, Vodka, and Kahlua recipes).
+
+
+
Errorage updated:
+
+
Smooth tables: Tables now automatically determine which direction and sprite they'll use. They will connect to any adjacent table unless there is a window between them (regular, reinforced, tinted, whichever)
+
+
+
+
+7 May 2011, Mother's day?
+
+
Agouri updated:
+
+
Fireaxes now work. Derp.
+
+
+
+
Erro updated:
+
+
New sprites for thermited walls and girders. Rework of thermited walls. Thermited walls leave a remnant damaged wall, crowbar it to scrap it.
+
More colors for the lightfloors CANCELLED/POSTPONED
DANGERCON UPDATE:Agouri and Erro updated(I'm in the DangerCon team now, nyoro~n :3):
+
+
Backpacks removed from all players. It was unrealistic. You can now had to the living quarters to get one from the personal closets there.
+
Any firearms now send you to critical in 1-2 shots. Doctors need to get the wounded man to surgery to provide good treatment. Guide for bullet removal is up on the wiki.
+
Brute packs and kelotane removed altogether to encourage use of surgery for heavy injury.
+
Just kidding
+
Fireaxe cabinets and Extinguisher wall-mounted closets now added around the station, thank Nanotransen for that.
+
Because of Nanotransen being Nanotransen, the fire cabinets are electrically operated. AIs can lock them and you can hack them if you want the precious axe inside and it's locked. The axe itself uses an experimental two-handed system, so while it's FUCKING ROBUST you need to wield it to fully unlock its capabilities. Pick up axe and click it in your hand to wield it, click it again or drop to unwield and carry it.You can also use it as a crowbar for cranking doors and firedoors open when wielded, utilising the lever on the back of the blade. And I didn't lie to you. It's fucking robust.
+
Fireaxe, when wielded, fully takes up your other hand as well. You can't switch hands and the fireaxe itself is unwieldy and won't fit anywhere.
+
A fireaxe cabinet can also be smashed if you've got a strong enough object in your hand.
+
EXTINGUISHER CLOSETS, made by dear Erro, can be found in abundance around the station. Click once to open them, again to retrieve the extinguisher, attack with extinguisher to place it back. Limited uses, but we've got plans for our little friend.
+
Sprite kudos go to: Cheridan for most of them, Khodoque for the fantastic fireaxe head. I merged those two. Also thanks to matty and Arcalane for giving it a shot.
+
Has the piano got TOO annoying? Try the fire axe...
+
Oh, and tou can now construct Light floors! To do it: Use wires on glass, then metal on the produced assembly, then place it on an uncovered floor like you would when replacing broken floor tiles. To deconstruct: Crowbar on light floor, use crowbar on produced assembly to remove metal, wirecutters to seperate the wires from the glass. Sprites by delicious Hempuli.
+
Got something to bitch about? Got a bug to report? Want to create drama? Did the clown destroy your piano while you were playing an amazing space remix of the moonlight sonata? Give it here
+
+
+
+
+
Rastaf.Zero updated:
+
+
New uniforms added for captain and chaplain, in their respective lockers. Credits to Farart.
+
+
+
+
Urist McDorf updated:
+
+
Mime and Clown now spawn with Crayons. You can eat those crayons. And use them for other nefarious purposes.
+
Health Scanners (A new type of Goggles) now spawn in medbay. Use them, doctors!
+
New Arcade toy.
+
Glowshrooms! What other lifeform will threaten the welfare of the station now?!
+
Bananas growable in hydroponics. Also soap is now on-board.
+
Added new "Lights out!" random event.
+
+
+
ConstantA updated:
+
+
Mech pilots are now immune to zapping, thank you very much.
+
+
+
+
+
+17 April 2011, World Hemophilia Day
+
+
Microwave updated:
+
+
Rabbit ears have a small tail, night vision goggle sprites updated.
+
Space tea has a nice, calming effect.
+
Space drugs? Liberty cap... something like that. Microwave, make your changelog entries more understandable!
+
Brobot merged with Service Borg with a Rapid Service Fabricator.
+
Arcade machine prizes look and sound realistic once again.
+
New arcade toy: Syndicate space suit costume, can hold arcade toys in suit storage.
+
Empty cap gun loaders can be recycled in an autolathe.
+
Seizure man has laying down sprites now. Update to wizard den.
+
Mech bay has two more borg chargers.
+
Beepsky is back!
+
Detective's office grille has been electrified.
+
You can now see if someone is wearing an emergency oxygen tank on their belt on the mob itself.
+
Lexorin - Now deals 3 oxygen damage per tick. Countered with Dexalin or Dexalin Pkus, which remove 2 units of it from your body per tick.
+
Bilk - Shares the effects of beer and milk. Disgusting!
+
Sugar - Gives nutrition!
+
Arithrazine - Now extremely good against radiation damage.
+
Hyronalin - Stronger radiation removal
+
Space cleaner spray bottles now contain enough cleaner for 50 uses. Making space cleaner now yields more cleaner.
+
+
+
Errorage updated:
+
+
Shuttle diagonal sprites now work for any kind of floor.
+
You can now make plaques from gold. Place them on a wall and engrave an epitaph.
+
Placed a single wall tile in the AI satellite so you don't have a clear LOS of the AI from the door.
+
Added arrivals lobby. (Map by Superxpdude, updated by Microwave)
+
Lattice now connects to the solar shields.
+
Law office maintenance is now connected with Tech storage maintenance. (Some rewiring dont in the area)
+
Xenobiology now has it's own access level. (Also fixed xeno pen access and blast doors)
+
You might soon start to see different airlocks and airlock assemblies around the station. (Sprites donated by Baystation 12)
+
Chemical storage added, discussion on which chemicals it should store is on the forums. You're welcome to contribute.
+
Hot fires will now melt floors.
+
Added a pair of market stalls south of the teleporter. LET THERE BE CLOWNMART!
+
Screwdrivers and wirecutters can now spawn in different colors.
+
Electrical toolboxes have a 5% chance of spawning a pair of insulated gloves. A set spawns in tech storage.
+
Oxygen canisters now spawn in emergency storage, near disposal, in the incinerator and the CE's office.
+
A plasma canister now spawns in toxins, near the maintenance door.
+
Wooden tables now look nicer.
+
+
+
Agouri updated:
+
+
2001 space suits added to AI Satellite!
+
New look for the 2001 space suit.
+
2001 space suit jetpack added.
+
Improved TRAYS!
+
+
+
Noise updated:
+
+
Thermals and mesons no longer give slightly better night vision.
+
NINJAS! (Too many things to list)
+
Wizards are no longer trackable by the AI when in their den.
+
Removed all old notes, except for the last one.
+
Nuke team now cannot return with their shuttle until the bomb is armed and counting down.
+
Energy blades can no longer cut through r-walls, walls take 7 seconds to cut through.
+
Turrets are now destructible. Bash them with stuff when they pop out or (more likely) die trying.
+
Updated Ripley Mech sprite.
+
+
+
Neo updated:
+
+
You can now aim guns at body parts, armor and helmets properly protect you from projectiles.
+
Cat ears now match the hair color of the wearer.
+
Robots can no longer stick their items onto/into things.
+
Meson, thermal and x-ray vision are now modules for borgs.
+
Welding now uses less fuel when on and idle but more when welding.
+
Hopefully fixed the bug when running into airlocks didn't open them and running into objects didn't push them.
+
+
+
HAL updated:
+
+
Added air alarm to security checkpoint, added cameras to aux. arrival docks so the AI can see everything.
+
Added fire alarm, fire locks and air alarm to delivery office.
+
+
+
ConstantA updated:
+
+
Added mecha DNA-locking. Only the person with matching UE can operate such mechs.
+
Added two mecha armor booster modules and a repair droid module.
+
Mech fabricator is now buildable.
+
Gygax construction is now reversible.
+
+
+
Rastaf0 and Farart updated:
+
+
Ghosts should now always properly hear people.
+
Monkeyized people (genetics or jungle fever disease) no longer lose their genetic mutations and diseases.
+
People who get bitten by monkeys get jungle fever.
+
Most chemicals should now heal and harm humans properly.
+
Many new (and updated) recipes for the microwave including Pizza, Meatball Soup, Hot Chili and many more.
+
Items should no longer spawn under vendomats and microwaves.
+
Runes are now drawn under doors and tables.
+
Penlights fit in medical belts.
+
People will scream if they get cremated while still alive.
+
Diseases should now properly make you loose health.
+
Monkeys wearing masks now get acid protection too.
+
You should probably turn off your stun baton before washing it.
+
latex loves + short piece of wire + some air from tank = balloon!
+
Kitchen was expanded, also a new look for the kitchen sink.
+
New dishware vending machine - dispenses knives, forks, trays and drinking glasses.
+
Water cooler was added to kitchen.
+
New uniform - Waiter Outfit. Chef can give it to his assistant.
+
+
+
Deeaych updated:
+
+
Updated satchel, bananimum, shovel, jackhammer and pick-in-hand sprites.
+
Many unneeded r-walls removed, detective's office reinforced.
+
Captain armor now acts as a space suit, added a unique captain's space helmet to captain's quarters.
+
Golems cannot speak, but should be perfectly spawnable. Also added golem fat sprite.
+
Security borgs have side sprites.
+
+
+
Matty406 updated:
+
+
AIs can now feel a little more dorfy.
+
Many ores, both raw and smelted, look much better.
+
+
+
Urist_McDorf updated:
+
+
You can now light other people's cigarettes by targeting their mouth with a lighter.
+
+
+
Veyveyr updated:
+
+
New tool sprites.
+
New sprites for smooth-lattice.
+
+
+
Muskets updated:
+
+
Kabobs now return the bar used to make them.
+
+
+
+
+
+2 April 2011, International Children's Book Day
+
+
Microwave updated:
+
+
New look for the mining cyborg, jackhammer, kitchen sink.
+
Singularity is now enclosed again (still airless tho).
+
Wizard has a new starting area.
+
Chemists and CMOs now have their own jumpsuits.
+
+
+
ConstantA updated:
+
+
You can now put Mind-machine-interface (MMI)'d brains into mecha.
+
+
+
Errorage updated:
+
+
Added smooth lattice.
+
+
+
+
+
+26 March 2011
+
+
Rastaf0 updated:
+
+
Food sprites from Farart
+
New food: popcorn (corn in microwave), tofuburger (tofu+flour in microwave), carpburger (carp meat+floor in microwave)
+
Medical belts are finally in medbay (credits belong to errorage, I only added it)
+
Pill bottles now can fit in containers (boxes, medbelts, etc) and in pockets.
+
Cutting camera now leaves fingerprints.
+
+
+
Microwave updated:
+
+
Armor Can hold revolvers, and so can the detective's coat.
+
Chef's apron is going live, it can carry a knife, and has a slight heat
+resistance (only slight don't run into a fire).
+
Kitty Ears!
+
Various food nutriment changes.
+
Added RIGs to the Mine EVA.
+
Night vision goggles. They have a range of five tiles.
+
Added Foods: Very Berry Pie, Tofu Pie, Tofu Kebab.
+
Modified foods: Custard Pie is now banana cream pie.
+
+
+
ConstantA updated:
+
+
Removed redundand steps from Gygax and HONK construction.
It is now possible to actually eat omelettes with the fork now, instead of just stabbing yourself (or others) in the eye with it.
+
Welding masks can now be flipped up or down. Note that when they're up they don't hide your identity or protect you from welding.
+
Reagent based healing should now work properly.
+
Revolver has been balanced and made cheaper.
+
Tasers now effect borgs.
+
Plastic explosives are now bought in single bricks.
+
Nuke team slightly buffed and their uplink updated with recently added items.
+
Player verbs have been reorganized into tabs.
+
Energy swords now come in blue, green, purple and red.
+
Cameras are now constructable and dismantlable. (Code donated by Powerful Station 13)
+
Updated the change network verb for AIs. (Code donated by Powerful Station 13)
+
Added gold, silver and diamond pickaxes to R&D which mine faster.
+
+
+
Agouri updated:
+
+
New look for the Request consoles.
+
+
+
Rastaf0 updated:
+
+
Brig cell timers should now tick closer-to-real seconds.
+
New look for food, including meat pie, carrot cake, loaded baked potato, omelette, pie, xenopie and others. (some sprites by Farart)
+
Hearing in lockers now works as intended.
+
Fixed electronic blink sprite.
+
Added the 'ghost ears' verb, which allows ghosts to not hear anything but deadcast.
+
+
+
XSI updated:
+
+
New AI core design.
+
HoP now has a coffee machine!
+
+
+
Veyveyr updated:
+
+
Replaced nuke storage with a vault.
+
Redesigned the mint, moved the public autolathe and n2o storage.
+
New look for the coin press. (Sprite by Cheridan)
+
+
+
Errorage updated:
+
+
You can now manually add coins into money bags, also fixed money bag interaction window formatting.
+
QM no longer has access to the entire mining station to stop him from stealing supplies.
+
New machine loading sprite for mining machinery. (sprites by Cheridan)
+
Added a messanging server to the server room. It'll be used for messanging, but ignore it for now.
+
The delivery office now requires delivery office access. It's also no longer called "Construction Zone"
+
Almost all the mecha parts now have sprites. (Sprites by Cheridan)
+
Tinted and frosted glass now look darker.
+
There are now more money sprites.
+
Department closets now contain the correct headsets.
+
+
+
Microwave updated:
+
+
Bicaridine now heals a lot better than before.
+
Added Diethylamine, Dry Ramen, Hot Ramen, Hell Ramen, Ice, Iced Coffee, Iced Tea, Hot Chocolate. Each with it's own effects.
+
Re-added pest spray to hydroponics.
+
Carrots now contain a little imidazoline.
+
HoS, Warden and Security Officer starting equipment changed.
+
New crate, which contains armored vests and helmets. Requires security access, costs 20.
+
Miner lockers now contain meson scanners and mining jumpsuits.
+
Food crate now contains milk, instead of meatballs. Lightbulb crates cost reduced to 5. Riot crates cost reduced to 20. Emergency crate contains 2 med bots instead of floor bots. Hydroponics crate no longer contains weed spray, pest spray. It's latex gloves were replaced with leather ones and an apron.
+
Added chef's apron (can hold a kitchen knife) and a new service borg sprite.
+
Autolathe can now construct kitchen knives.
+
Biosuit and syndicate space suits can now fit into backpacks.
+
Mime's mask can now be used as a gas mask.
+
Added welding helmet 'off' sprites.
+
+
+
+
+
+
+18 March 2011
+
+
Errorage updated:
+
+
You can now use the me command for emotes! It works the same as say "*custom" set to visible.
+
There is now a wave emote.
+
Enjoy your tea!
+
+
+
Deeaych updated:
+
+
The exam room has some extra prominence and features.
+
A new costume for the clown or mime to enjoy.
+
Service Cyborgs can be picked! Shaker, dropper, tray, pen, paper, and DOSH to show their class off. When emagged, the friendly butler-borg is able to serve up a deadly last meal.
+
It should now be possible to spawn as a cyborg at round start. Spawned cyborgs have a lower battery life than created cyborgs and begin the round in the AI Foyer.
+
+
+
Rastaf0 updated:
+
+
Fixed an issue with examining several objects in your hands (such as beakers).
+
Fixed bug with random last name being empty in rare cases.
+
+
+
hunterluthi updated:
+
+
It is now possible to make 3x3 sets of tables.
+
Fixed some missplaced grilles/lattices on the port solar.
+
There is now a breakroom for the station and atmos engineers. It has everything an intelligent young engineer needs. Namely, Cheesy Honkers and arcade games.
+
+
+
+
+15 March 2011, International Day Against Police Brutality
+
+
Errorage updated:
+
+
Autolathe deconstruction fixed.
+
Atmos Entrance fixed.
+
AI no longer gibs themselves if they click on the singularity.
+
Fixed all the issues I knew of about storage items.
+
Redesigned Assembly line and surrounding maintenance shafts.
+
Redesigned Tech storage. (Map by Veyveyr)
+
+
+
TLE updated:
+
+
Forum account activation added. Use the 'Activate Forum Account' verb.
+
+
+
Neo updated:
+
+
New R&D Item: The 'Bag of holding'. (Sprite by Cheridan)
+
Getting someone out of the cloner now leaves damage, which can only be fixed in the cryo tube.
+
New reagent: Clonexadone, for use with the cryo tube.
+
Fixed using syringes on plants.
+
+
+
Constanta updated:
+
+
Added queueing to fabricator.
+
+
+
Rastaf0 updated:
+
+
Air alarms upgraded.
+
Fixed problem with AI clicking on mulebot.
+
Airlock controller (as in EVA) now react to commands faster.
+
Fixed toxins mixing airlocks.
+
+
+
+
+6 March 2011
+
+
Neo updated:
+
+
Neo deserves a medal for all the bugfixing he's done! --errorage
+
+
+
Errorage updated:
+
+
No. I did not code on my birthday!
+
Windows can now be rotated clockwise and counter clockwise.
+
Window creating process slightly changed to make it easier.
+
Fixed the newly made reinforced windows bug where they weren't properly unfastened and unscrewed.
+
Examination room has a few windows now.
+
Can you tell I reinstalled Windows?
+
Robotics has health analyzers.
+
Bugfixing.
+
+
+
Deeyach updated:
+
+
Roboticists now spawn with a lab coat and an engineering pda
+
+
+
+
+2 March 2011, Wednesday
+
+
Errorage updated:
+
+
Mapping updates including Atmospherics department map fixes, CE's office and some lights being added here and there.
+
Mining once again given to the quartermaster and HoP. The CE has no business with mining.
+
Removed the overstuffed Atmos/Engineering supply room.
+
Replaced all 'engineering' doors in mining with maintenance doors as they were causing confusion as to which department mining belongs to.
+
The incinerator is now maintenance access only.
+
+
+
Neo updated:
+
+
New look for the advanced energy gun. (Sprite by Cheridan)
+
Mech fabricator accepts non-standard materials.
+
Mules accesses fixed, so they can be unlocked once again.
+
Atmospherics department mapping overhaul. (Map by Hawk_v3)
+
Added more name options to arcade machines.
+
+
+
ConstantA updated:
+
+
Added mecha control console and mecha tracking beacons.
+
Some changes to gygax construction.
+
+
+
Darem updated:
+
+
R&D minor bugfixes.
+
AI computer can now be deconstructed (right click and select 'accessinternals').
+
Server room updated, added server equipment to use with R&D.
+
Wizard and ghost teleport lists are now in alphabetical order, ghosts can now teleport to the mining station.
+
Rightclicking and examining a constructable frame now tells you what parts still need to be finished.
+
Large grenades added to R&D.
+
+
+
Deeyach updated:
+
+
Mining given to the CE. (Reverted by Errorage)
+
Clowns can now pick a new name upon entering the game (like wizards previously).
+
+
+
+
+24 February 2011, Thursday
+
+
Darem updated:
+
+
Lighting code fixed for mining and thermite.
+
R&D instruction manual added to the R&D lab.
+
Fixed R&D disk commands not working.
+
Added portable power generators which run on solid plasma.
+
You can now set the numer of coins to produce in the mint.
+
Added two more portable power generators to R&D.
+
+
+
Deeyach updated:
+
+
New uniform for roboticists
+
+
+
Neo updated:
+
+
Game speed increased
+
Mining stacking machine no longer devours stacks larger than 1 sheet (it was only increasing its stock by 1 when given a stacked stack)
+
Stackable uranium ore added (a better sprite is needed, contributions are welcome)
+
Made Meteor gamemode actually do stuff
+
Made a bigger class of meteor
+
New R&D item: Advanced Energy Gun
+
Law priority clarified with regards to ion laws and law 0.
+
+
+
+
Veyveyr updated:
+
+
Minor mapping fixes
+
+
+
Uhangi updated:
+
+
New red bomb suit for security.
+
+
+
+
Errorage updated:
+
+
Slight mapping change to arrival hallway.
+
+
+
+
+23 February 2011, Red Army Day
+
+
Uhangi updated:
+
+
Antitox and Inaprovaline now mixable via chemistry.
+
Explosive Ordinance Disosal (EOD) suits added to armory and security.
+
Large beaker now holds 100 units of chemicals. (code by Slith)
+
+
+
Rastaf0 updated:
+
+
Secbot interface updated.
+
Syringe auto-toggels mode when full.
+
Captain's flask volume increased.
+
+
+
Neo updated:
+
+
Fixed the 'be syndicate' choice to actually work on nuke rounds.
+
Syndicates no longer win if they detonate the nuke on their ship.
+
+
+
+
Errorage updated:
+
+
Added cloning manual. (Written by Perapsam)
+
+
+
K0000 updated:
+
+
Cult mode updates.
+
You can now read the arcane tome. It contains a simple guide for making runes.
+
Converting people doesnt give them word knowledge.
+
Sacrifice monkeys or humans to gain new words.
+
Total number of rune words set to 10
+
Some minor bugfixes.
+
+
+
+
+20 February 2011, Sunday
+
+
Errorage updated:
+
+
Slight updates to processing unit and stacking machine at the mining outpost.
+
Digging now yields sand, which can be smelted into glass.
+
Stacking machine can now stack reinforced metal, regular and reinforced glass too.
+
Engineers now have two copies of the singularity safety manual.
+
+
+
Neo updated:
+
+
Magboots now have a verb toggle like jumpsuit sensors.
+
Jumpsuit sensors are now in all jumpsuits, except tactical turtlenecks.
+
Tweaks to the AI report at round start.
+
Syndi-cakes now heal traitors/rev heads/etc much more than anyone else.
+
Containment fields zap once again.
+
Fire damage meter no longer lies about fire damage.
+
+
+
Darem updated:
+
+
Mass Spectrometer added to R&D. Load it with a syringe of blood and it will tell you the chemicals in it. Low reliability devices may yield false information.
+
Not all devices have a 100% reliability now.
+
Miners now have access to mint foyer and loading area. Only captain has access to the vault.
+
More stuff can be analyzed in the destructive analyzer, protolathe can produce intelicards.
+
+
+
Rastaf0 updated:
+
+
Added blast door button to atmospherics.
+
Toxins timer-igniter assemblies fixed.
+
Engineering secure storage expanded.
+
Added singularity telescreen.
+
+
+
+
+18 February 2011, Friday
+
+
Errorage updated:
+
+
New look for the bio suits. (Biosuit and hood sprites by Cheridan)
+
New radiation suits added along with radiation hoods and masks. Must wear complete set to get full protection.
+
+
+
Rastaf0 updated:
+
+
Binary translator cost reduced to 1 telecrystal.
+
+
AtomicTroop updated:
+
+
Mail Sorter job added.
+
Disposal system redone to allow for package transfers. Packages are routed to mail sorter room and then routed to the rest of the station
+
Disposal area moved. Old disposal area now just an incinerator and a small disposal into space.
+
New wrapping paper for sending packages.
+
+
+
Veyveyr updates:
+
+
New machine frame sprite.
+
Braincase sprites for mechs added. Not actually used, yet.
+
+
+
Darem updates:
+
+
Research and Development system is LIVE. Scientists can now research new advancements in technology. Not much can be made, right now, but the system is there. Technologies are researched by shoving items into the destructive analyzer. Circuit Imprinter, Destructive Analyzer, and Protolathe are controlled from the R&D console.
+
Autolathe, Protolathe, Destructive Analyzer, and Circuit Imprinter can now be built, taken apart, and upgraded. The basic frame for all of the above requires 5 metal.
+
+
+
+
+15 February 2011, Tuesday
+
+
Rastaf0 updated:
+
+
Added radio channels and headsets for miners (:h or :d ("diggers" lol)) and for cargo techs (:h or :q )
+
Added a personal headsets to HoP and QM.
+
Aliens now attack bots instead of opening control window.
+
All bots can be damaged and repaired.
+
All bots are effected to EMP now.
+
Atmos now starts with nitrous oxide in storage tank.
+
+
+
Veyveyr updated:
+
+
New look for the pipe dispenser.
+
+
Errorage updated:
+
+
Mining station will now charge properly.
+
Duffle bags (Money bags) can now be emptied.
+
+
+
+
+14 February 2011, Valentine's day
+
+
Errorage updated:
+
+
New Job! - Shaft Miners have finally been added and are available to play.
+
Mining outpost - A new mining outpost has been built, the mining dock on SS13 has been updated.
+
+
+
ConstantA updated:
+
+
Slight speed up for combat mechs..
+
Added H.O.N.K construction
+
Fixed bug with switching intent while in mecha.
+
+
+
+
+
12.02.2011, 01.00 GMT, r1021
+
+
Added Durand combat exosuit.
+
Players can modify operation permissions of newly constructed civilian mechs. Click on mech with ID card or PDA with ID inside.
+
Added robotics access to default mecha maintenance permissions (all mechs) and operation permissions (civilian models only).
+
Fixed double adminlog message of explosion proc.
+
Fixed accidental mecha wreckage deletion.
+
Tweaked mecha internal fire processing.
+
Added some mecha-related sounds.
+
Moved GaussRand to helpers.dm and added GaussRandRound helper proc.
+
Other small changes.
+
+
+
11.02.2011, r1001-1020
+
+
Headsets upgraded. Shortcuts for channels are: :command :security scie:nce :engineering :medical. Also there is :binary :whisper :traitor and old good :h for your department.
+
"One Click Queue" added: When you quickly click on two things in a row, it will automatically queue the second click and execute it after the default 'action delay' of 1 second after the first click. Previously you had to spam-click until 1 second had passed. THIS AFFECTS EVERYTHING. NEEDS TESTING. - Skie
+
EMP effects added for further revisions. - Darem
+
Big map changes in engineering/robotics/science wing. - errorage
+
Welder Fixed. Now burns your eyes again. - errorage
+
9x9 singularity sprite added. - Skie/Mport
+
Ghetto surgery added. - Neophyte
+
Nasty vent pump lag fixed. - Neophyte
+
Mech gameplay and building updates. - ConstantA
+
+
+
08.02.2011, r999-1000
+
+
The amount of power the station uses should be lower.
+
The singularity now changes size based upon how much energy it has
+
New Machine: Particle Accelerator.
+
It might need a better name/sprite but when put together properly and turned on it will shoot Accelerated Particles.
+
The particles irradiate mobs who get in the way and move through solid objects for a short time.
+
The Particle Accelerator parts are set up by using a Wrench followed by a Cable Coil, then finished with a screwdriver.
+
When you shoot the Singularity Generator with Accelerated Particles it will spawn a Singularity.
+
New layout for Engineering, might be changed up slightly in the next few days.
+
+
+
06.02.2011, r979
+
+
Jesus christ it's a new map what the fuck
+
Just kidding, it's only minor changes to medbay/mechbay/cybernetics/R&D/toxins/robotics/chapel/theatre
+
Okay so there's too many changes to list completely, but basically: toxins/R&D/virology/xenobiology are south through medbay; robotics/cybernetics/mechbay are where toxins used to be, the theatre and chapel are above medbay.
+
Theatre is a new place for the Clown and Mime to play, there are some costumes available, a stage and backstage, and seating for the audience.
+
R&D and Toxins have been combined together. R&D is still work in progress. You need to head south through medbay to get there.
+
Medbay has been re-arranged slightly. Honestly, it's nothing, I bet you won't even notice anything different. There's also a new surgery suite, complete with pre-op and post-op rooms.
+
Virology's been rearranged, but it is mostly in the same place still.
+
Xenobiology is work in progress. [There's some fun stuff still being coded]
+
The Chapel is now to the north. You'll probably be thinking something like "Goddamn, this place is fucking huge", but it's actually smaller than the previous chapel.
+
Robotics and related stuff is also work in progress - however, everything needed for making borgs is there. Note: de-braining will now be done by surgeons in medbay, rather than roboticists in robotics, in case you're wondering where your optable went.
+
I added color-coded pipes in the areas I was working on. Red pipes run from air siphons and feed into the waste loop; blue pipes run from air vents and pump whatever atmos is set to pump. Yeah, there's TWO pipe networks on the station, who knew? Well, some of you probably did, but I didn't!
+
This update brought to you by: veyveyr and Rookie, with help from friends! [Now you know who to strangle, please be gentle q_q]
+
+
+
05.02.2011, r968
+
+
This really needs to be updated more often.
+
Various map updates have been applied with many more to come. Expect overhauls!
+
Mining system nearing completion.
+
Massive overhaul to the electricity systems, the way singularity makes power, and various related functions.
+
Mime spawns with White Gloves instead of Latex Gloves (apparently there's a difference!)
+
A new event has been- CLANG! What the fuck was that?
+
Ion storm laws should be much more interesting.
+
Security reports should no longer list traitor heads/AIs as possible revs/cultists, nor should nuke operatives ever get named for anything.
+
Pens are much more versatile and user friendly.
+
Mech building is rapidly on its way! Ripleys can be built, consult your quartermasters.
+
Traitors now have several new things they can steal.
+
Some surgeries coded to accompany the new operating room and surgery tools.
+
Research and Design is continuing development and should be rolled out shortly.
+
Various sprites added, tweaked, scrapped and fixed.
+
+
+
Changelog
+
05.02.2011, r968
+
+
This really needs to be updated more often.
+
Various map updates have been applied with many more to come. Expect overhauls!
+
Mining system nearing completion.
+
Massive overhaul to the electricity systems, the way singularity makes power, and various related functions.
+
Mime spawns with White Gloves instead of Latex Gloves (apparently there's a difference!)
+
A new event has been- CLANG! What the fuck was that?
+
Ion storm laws should be much more interesting.
+
Security reports should no longer list traitor heads/AIs as possible revs/cultists, nor should nuke operatives ever get named for anything.
+
Pens are much more versatile and user friendly.
+
Mech building is rapidly on its way! Ripleys can be built, consult your quartermasters.
+
Traitors now have several new things they can steal.
+
Some surgeries coded to accompany the new operating room and surgery tools.
+
Research and Design is continuing development and should be rolled out shortly.
+
Various sprites added, tweaked, scrapped and fixed.
+
+
+
20.01.2011, r894
+
+
Pipes can now be removed and re-attached by wrenching them.
+
Mining system continues to develop. Still unaccessible to players.
+
Various map changes. Some minor lag causing things were fixed.
+
Admins have a new tool: They can now give any spell to anyone. Hurray!
+
Imadolazine now works. Maybe?
+
Singularity now releases itself if fed too much and will potentially explode.
+
Magboots now successfully prevent you from getting pulled into the singularity.
+
Strike teams immune to facehuggers. Why? I dunno.
+
Many reagent containers are adjustable so you can pour the exact amount you need.
+
No more emitters working in space, Collectors and collector controllers now ID lockable.
+
Christmas Contest plaque finally added. It's near the armor/warden's office.
+
Rocks fall, everyone dies.
+
All cyborgs and robots can now be named. Just use a pen on the cyborg's frame before the brain is inserted.
+
Knock spell now unbolts doors as well as opens them.
+
New cultist runs and other changes.
+
Added surgery tools for eventual surgery system.
+
Autolathe and Circuit Printer animations redone. Yay pretty icons.
+
AI law changes/uploads are now tracked (admin viewable).
+
Revheads now get a PDA uplink instead of a headset one.
+
Added a penlight.
+
Science Research and Development tech tree uploaded. Not really accessible by anyone yet, though.
+
+
+
14.01.2011, r853
+
+
Changlings Overhauled. Now function kinda like alium (using an internal chemical reserve instead of plasma) with each ability requiring a certain amount of chemicals to activate. Both venoms removed. Several "Dart" abiliites added. They allow the changling to deafen, blind, mute, paralyze, or even transform (dead) targets.
+
Carp meat now contaminated with Carpotoxin. Anti-toxin negates the poison, however.
+
New Reagent: Zombie Powder: Puts subjects into a deathlike state (they remain aware, though). Each unit of Zombie Powder requires 5 units of Carpotoxin, Sleeping Toxin, and Copper.
+
Various alium fixes.
+
Matches now available from smokes machine.
+
Megabomb bug fixed. Bombs with timers/signalers won't detonate after the timer/ signaler is removed.
+
New Disease: Rhumba Beat. Functions like GBS with a few exceptions. Not only available by admindickery.
+
More mining fixes/changes.
+
Ghost can now teleport to AI sat, Thunderdome, and Derelict.
+
New gimmick clothes
+
Constructing Glass Airlocks now use one sheet of R.Glass.
+
Windows mow always appear above grills and pipes always above lattices.
+
Added FireFighter mecha and various mecha fixes.
+
Deconstructed walls now leave proper floor tiles (that can be pried up like normal) and remember what kind of floor they were before the wall was made.
+
Carded AIs no longer take damage while in unpowered areas.
+
Chaplains can now name their religion at round start.
+
Napalm nerfed a bit. Produces ~25% less plasma but it's all concentrated in a single tile (will still spread, though).
+
Reagent bottles can, once again, be put into grenades.
+
Various minor map changes.
+
+
+
08.01.2011,8:00PST, r820
+
+
Holograms (AI controled psudo-mobs) added. Currently only admin spawn.
+
Pre-spawned pills no longer have randomized image.
+
Bridge reorganized.
+
Automated turrets now less dumb. Additionally, turrets won't target people laying down.
+
Cultists now automatically start known words for add cultist ritual. Also, converted cultists start knowning a word.
+
Supply ship no longer can transport monkeys. Also, monkeys are no longer orderable from QM.
+
Meat Crate added to QM.
+
Corn can now be blended in a blender to produce corn oil which is used to create glycern.
+
Request Consoles added across the station. Can be used to request things from departments (and slightly easier to notice then the radio).
+
Centcom reoragnized a fair bit. Not that players care but admins might enjoy it.
+
There is now a toggable verb that changes whether you'll turn into an alium or not.
+
Hair sprited modified.
+
Napalm and Incendiary Grenades both work now. Have fun setting things on fire.
+
Binary Translater traitor item added. It allows traitors to hear the AI (it functions like a headset).
+
New Disease: Pierrot's Throat. Enjoy, HONK!
+
Robotic Transformation (from Robrugers) and Xenomorph Transformation (from xenoburgers) now curable.
+
Mining added. Only accessible by admins (and those sent by admins). Very much WIP.
+
Alium Overhaul. Divided into multiple castes with distinct abilities.
+
New AI Modules: The goody two-shoes P.A.L.A.D.I.N. module and it's evil twin T.Y.R.A.N.T. Only the former actually spawns on the station.
+
Lizards added. They run away and shit.
+
PDA overhaul. Doesn't change anything for humans, just makes coders happy.
+
Firesuits redone to look less like pajamas and instead like firesuits. Fire lockers also added.
+
New Mecha: H.O.N.K.
+
Deployable barriers added. Can be locked and unlocked.
+
Mecha bay (with recharging stations) added.
+
Bunny Ears, Security Backpacks, Medical Backpacks, Clown Backpacks, and skirt added.
+
Various wizard changes. New Wizard Spell: Mind Swap. Swap your mind with the targets. Be careful, however: You may lose one of your spells. Wizards are no longer part of the crew and get a random name like the AI does. Wizards can change their known spells with their spellbook but only while on the wizard shuttle.
+
Circuit Imprinter: Using disks from the various deparments, new circuit boards and AI modules can be produced.
+
Heat Exchanging pipes added and various pipe/atmos changes.
+
Ghost/Observer teleport now works 100% of the time.
+
+
+
17.12.2010,11:00GMT
+
+
You need an agressive grip to table now, as well as being within one tile of the table (to nerf teletabling).
+
Teleport only runs once at the beginning of the round, hopefully reducing the lag in wizard rounds.
+
Wizards can't telepot back to their shuttle to afk now.
+
Someone added it a while ago and forgot to update the changelog - syndies in nuke need to move their shuttle to the station's zlevel first.
+
Bunch of other stuff.
+
+
+
Sunday, November 21, 3:34 PST
+
+
Bug fixes, not going into detail. Lots and lots of bug fixes, mostly regarding the new map
+
CMO has a more obvious lab coat, that looks nicer than the original
+
CMO also has a stamp now
+
QM has a denied stamp
+
Cyborgs got tweaked. Laws only update when checked. Also have wires that can be toyed
+ with for various effects, including AI sync, and law control
+
Second construction site similar to the original
+
Prison station has been tweaked, now includes a lounge, and toilets
+
Detective's revolver starts with a reasonable number of bullets
+
Prison teleporter to the courtroom now exists
+
AI related stuff: More cameras, oh, and bug fixes!
+
Emergency storage moved
+
Random AI law changes. New law templates, and variables. Old variables were tweaked and some of the crappy templates were removed
+
Ghosts can teleport to the derelict now
+
Respriting of a bunch of stuff as well as graphical fixes
+
Kitchen is attached to the bar again
+
General map tweaks and fixes
+
Wardens fixed for rev rounds
+
5-unit pills now work
+
APCs added and moved
+
Cola machine added. Contains various flavours of soda. Also added new snacks to the now named snack machine. Water cooler added, just apply an empty glass to it
+
Salt & Pepper have been added, as part of the food overhaul
+
More bug fixes. There was a lot
+
+
+
Tuesday, November 16, 00:20 GMT
+
+
Cruazy Guest's map is now live.
+
Entire station has been rearranged.
+
Prison Station added, with Prison Shuttle to transport to and from.
+
New Job: Warden. Distributes security items.
+
The new map is still in testing, so please report any bugs or suggestions you have to the forums.
+
AI Liquid Dispensers, Codename SLIPPER, have been added to the AI core. They dispense cleaning foam twenty times each with a cooldown of ten seconds between uses. Mounted flashes have also been included.
+
Clown stamp added to clown's backpack.
+
+
+
Sunday, November 14, 18:05
+
+
Major food/drink code overhaul. Food items heal for more but not instantly. Poison, Drug, and "Heat" effects from food items are also non-instant.
+
Preperation for one-way containers and condiments.
New Food Item: Chaos Donut: 1 Hot Sauce + 1 Cold Sauce + 1 Flour + 1 Egg. Has a variable effect. NOT DEADLY (usually).
+
New Drug: Ethylredoxrazine: Carbon + Oxygen + Anti-Toxin. Binds strongly with Ethanol.
+
Tape Recorders added! Now you can actually PROVE someone said something!
+
Amospherics Overhaul Started: It actually works now. You can also build pipes and create function air and disposal systems!
+
Walls are now smooth.
+
Alcohol no longer gets you as wasted or for as long.
+
QM job split into QM and Cargo Technicians. QM has his own office.
+
Doors can no longer be disassembled unless powered down and unbolted.
+
New Job: Chief Medical Officer. Counts as a head of staff and is in charge of medbay. Has his/her own office and coat.
+
Wizarditis Bottle no longer in virus crate.
+
+
+
Friday, November 5, 19:29
+
+
The ban appeals URL can now be set in config.txt
+
Secret mode default probabilities in config.txt made sane
+
Admin send-to-thunderdome command fixed to no longer send people to the other team's spawn.
+
Having another disease no longer makes you immune to facehuggers.
+
Magboots are now available from EVA.
+
Changeling death timer shortened. Destroying the changeling's body no longer stops the death timer.
+
Cult mode fixes.
+
Fixed cyborgs pressing Cancel when choosing AIs.
+
The play MIDIs setting now carries over when ghosting.
+
Admins can now see if a cyborg is emagged via the player panel.
+
PAPERWORK UPDATE v1: Supply crates contain manifest slips, in a later update these will be returnable for supply points.
+
The Supply Ordering Console (Request computer in the QM lobby) can now print requisition forms for ordering crates. In conjunction with the rubber stamps, these can be used to demonstrate proper authorisation for supply orders.
+
Rubber stamps now spawn for each head of staff.
+
The use of DNA Injectors and fueltank detonations are now admin-logged.
+
Removed old debug code from gib()
+
+
+
Tuesday, November 2, 19:11(GMT)
+
+
Finished work on the "cult" gamemode. I'll still add features to it later, but it is safe to be put on secret rotation now.
+
Added an energy cutlass and made a pirate version of the space suit in preparation for a later nuke update.
+
Changeling now ends 15 minutes after changeling death, unless he's ressurected.
+
Further fixing of wizarditis teleporting into space.
+
Fixed the wise beard sprite.
+
Fixed missing sprite for monkeyburgers.
+
Fixed Beepsky automatically adding 2 treason points to EVERYONE.
+
+
+
+
Thursday, October 28, 19:30(GMT)
+
+
Sleepers and disposals now require two seconds to climb inside
+
+
Hydroponics crate ordered in QMs doesnt spawn too many items
+
Replacement lights crate can be ordered in QM.
+
Added space cleaner and hand labeler to Virology.
+
Welder fuel tanks now explode when you try to refuel a lit welder.
+
Made clown's mask work as a gas mask.
+
9 new cocktails: Irish Coffee, B-52, Margarita, Long Island Iced Tea, Whiskey Soda, Black Russian, Manhattan, Vodka and Tonic, Gin Fizz. Refer to the wiki for the recipes.
+
Kitchen update:
+
+
-New Microwave Recipies: Carrot Cake (3 Flour, 3 egg, 1 milk, 1 Carrot), Soylen Viridians (3 flour, 1 soybeans), Eggplant Parmigania (2 cheese, 1 eggplant), and Jelly Donuts (1 flour, 1 egg, 1 Berry Jam), Regular Cake (3 flour, 3 egg, 1 milk), Cheese Cake (3 flour, 3 egg, 1 milk), Meat Pies (1 meat of any kind, 2 flour), Wing Fang Chu (1 soysauce, 1 xeno meat), and Human and Monkey Kabob (2 human or monkey meat, metal rods).
+
- Ingredients from Processor: Soysauce, Coldsauce, Soylent Green, Berry Jam.
+
- Sink added to kitchen to clean all the inevitable blood stains and as preperation for future cooking changes.
+
- The food processor can't be abused to make tons of food now.
+
+
+
Multiple tweaks to virology and diseases:
+
+
- Added wizarditis disease.
+
- Spaceacillin no longer heals all viruses.
+
- Some diseases must be cured with two or more chemicals simultaneously.
+
- New Virology design including an airlock and quarantine chambers.
+
- Made vaccine bottles contain 3 portions of vaccine.
+
- Lots of minor bug fixes.
+
+
+
+
+
+
Monday, October 18, 06:24(GMT)
+
+
Added virology profession with a cosy lab in northwestern part of medbay.
+
Virology related things, like taking blood samples, making vaccines, splashing contagious blood all over the station and so on.
+
Added one pathetic disease.
+
Virus crates are now available from the quartermasters for 20 points.
+
The DNA console bug (issue #40) was fixed, but I still made the DNA pod to lock itself while mutating someone.
+
Added icons for unpowered CheMaster and Pandemic computers
+
Added some sign decals. The icons were already there, but unused for reasons unknown.
+
Some map-related changes.
+
+
+
Wednesday, October 13, 14:12(GMT)
+
+
Crawling through vents (alien) now takes time. The farther destination vent is, the more time it takes.
+
Cryo cell healing ability depends on wound severity. Grave wounds will heal slower. Use proper chemicals to speed up the process.
+
Added sink to the medbay.
+
Bugfixes:
+
+
- Some reagents were not metabolized, remaining in mob indefinitely (this includes Space Cola, Cryoxadone and cocktails Kahlua, Irish Cream and The Manly Dorf).
+
- Fixed placement bug with container contents window. Also, utility belt window now doesn't obscure view.
+
+
+
+
+
Sunday, October 10, 14:25(GMT)
+
+
Scrubbers in the area can be controlled by air alarms. Air alarm interface must be unlocked with an ID card (minimum access level - atmospheric technician), usable only by humans and AI. Panic syphon drains the air from affected room (simple syphoning does too, but much slower).
+
Sleeper consoles inject soporific and track the amounts of rejuvination chemicals and sleep toxins in occupants bloodstream.
+
Flashlights can be used to check if mob is dead, blind or has certain superpower. Aim for the eyes.
+
Radiation collectors and collector controls can be moved. Secured\unsecured with a wrench.
+
Air sensors report nitrogen and carbon dioxide in air composition(if set to).
+
Air Control console in Toxins.
+
Additional DNA console in genetics
+
Enough equipment to build another singularity engine can be found in engineering secure storage
+
Air scrubber, vent and air alarm added to library
+
Air alarm added to brig
+
Air scrubbers in Toxins turned on, set to filter toxins
+
Empty tanks, portable air pumps and similar can be filled with air in Aft Primary Hallway, just connect them to the port. Target pressure is set by Mixed Air Supply console in Atmospherics (defaults to 4000kPa).
+
+
+
Wednesday, October 6, 18:36
+
+
Fixed the Librarian's suit - its worn iconstate wasn't set.
+
Fixed some typos.
+
Monkey crates are now available from the quartermasters for 20 points.
+
Corpse props removed from zlevel 8 as they were causing issues with admin tools and the communications intercept.
+
Cleaned up the default config.txt
+
Added a readme.txt with installation instructions.
+
Changed the ban appeals link to point to our forums for now - this'll be a config file setting soon.
+
+
+
Tuesday, October 5, 01:41
+
+
Fixes to various nonworking cocktails.
+
More map and runtime error fixes.
+
Nuke operative headsets should be on an unreachable frequency like department headsets.
+
Another AI Malfunction change: Now once the AI thinks enough APCs have been hacked, it must press a button to start the timer, which alerts the station to its treachery.
+
Blob reskinned to magma and increased in power.
+
The HoS now has an armored greatcoat instead of a regular armor vest.
+
Admin logs now show who killswitched a cyborg.
+
The roboticist terminal now lets you see which AI a cyborg is linked to.
+
Malf AIs are no longer treated as inactive for the purpose of law updates and cyborg sync while hacking APCs.
+
Traitor AIs are now affected by Reset/Purge/Asimov modules, except law 0.
+
AI core construction sprites updated.
+
Securitrons removed from the Thunderdome.
+
An APC now supplies power to the bomb testing area, and has external cabling to supply it in turn.
+
A new variant freeform module has been added to the AI Upload room.
+
The changeling's neurotoxic dart has been made more powerful - this will likely be an optional upgrade from a set of choices, akin to wizard spells.
+
Some gimmick clothes moved to a different object path.
+
The chameleon jumpsuit should now be more useful - it includes job-specific jumpsuits as well as flat colours.
+
+
+
Wednesday, September 29, 15:40
+
+
Bartender update! Bartender now has a Booze-O-Mat vending machine dispensing spirits and glasses, which he can use to mix cocktails. Recipes for cocktails are available on the wiki.
+
The barman also now has a shotgun hidden under a table in the bar. He spawns with beanbag shells and blanks. Lethal ammo is obtainable from hacked autolathes.
+
Dead AIs can once more be intelicarded, however in order to be restored to functionality they must be repaired using a machine in the RD office.
+
Silicon-based lifeforms have metal gibs and motor oil instead of blood.
+
Aliens now have a death message.
+
Intelicarded AIs can now have their ability to interact with things within their view range reactivated by the person carrying the card.
+
New AI cores can be constructed from victimsvolunteers.
+
Verbs tweaked.
+
Intelicarded AIs can be deleted.
+
RD office redesigned, and the RD now spawns there.
+
The AI can now choose to destroy the station on winning a Malf round.
+
General bugfixes to AIs, constructed AIs and decoy AIs.
+
Hats no longer prevent choking.
+
Some extra gimmick costumes are now adminspawnable.
+
AI health is now displayed on their status tab.
+
AI upload module now requires you to select which AI to upload laws to in case of multiple AIs.
+
Cyborgs now choose an AI to sync laws with upon creation.
+
Law office redesigned.
+
Roboticists no longer have Engineering access.
+
More fixes to areas which had infinite power due to having no assosciated APC.
+
Meatbread slices are no longer infinite.
+
Malf rounds no longer end if a non-malfunctioning AI is killed.
+
Cigarettes now have directional sprites.
+
AI Core circuitboard spawns in the RD office.
+
AI Satellite now has cameras and properly-wired SMES batteries.
+
Decoy AIs can no longer be moved.
+
Several runtime errors have been fixed.
+
Nuke rounds will now end properly on a station or neutral victory.
+
Riot shields have been nerfed and are now only available in the armory and in riot crates.
+
Foam dart crossbows, cap guns and caps can now be won as arcade prizes.
+
AI Malfunction has been redesigned - the AI must now hack APCs in order to win. More APCs hacked makes the timer tick faster.
+
Hydroponics now has a MULEbot station.
+
Changeling mode has been added to the game and is now in testing.
+
Electrified airlocks should now only zap you once if you bump into them while running.
+
Chemistry and Toxins access has been removed from Botanists.
+
General bugfixes and map tweaks.
+
+
+
Sunday, September 26, 17:51
+
+
Riot shields! One in every security closet, and a few in armory. Also orderable from QM.
+
+
+
Tuesday, September 21, 17:51
+
+
New experimental UI for humans by Skie. Voice out if it has problems or you don't like it.
+ ---> YOU CAN CHOOSE UI FROM PREFERENCES <---
+
Hydroponics: Now you can inject chemicals into plants with a syringe. Every reagent works differently.
+
Hydroponics: Added a small hoe for uprooting weeds safely. Botanists now have sissy aprons.
+
New random station/command names and verbs.
+
Dead AIs can no longer be intellicarded and the steal AI objective is now working.
+
Aliens now bleed when you hit them, as well as monkeys.
+
Hurt people and bodies leave blood behind if dragged around when they are lying. Sprites to be updated soon...
+
Fixed several run-time errors in the code. Also food doesn't deal damage anylonger in some cases.
+
Blobs and alien weeds slowed down some. Plant-b-gone buffed some.
+
Fixed monkeys and aliens not being able to deal damage to humans with items.
+
Monkeys now slip on wet floor.
+
+
+
Friday, September 17, 23:03
+
+
The Lawyer now starts in his snazzy new office.
+
Law Office accesslevel added. Currently, the Lawyer and the HoP begin with this.
+
Robotics access can now be added or removed from the HoP's computer.
+
Robotics, the captain's quarters, the cargo lobby and the staff heads office now have APCs and can lose power like the rest of the station.
+
Toxins mixing room is now a separate area for power and fire alarm purposes, as it already had its own APC.
+
+
+
Thursday, September 16, 20:11
+
+
Added the Lawyer job.
+
Doors can now be constructed and deconstructed. This is being playtested, expect the specifics to change.
+
Fixed certain jobs which were supposed to have stuff spawning in their backpack that just wasn't getting spawned.
+
+
+
Monday, September 13, 13:30
+
+
Bunch of new announcer sounds added
+
Minor lag fix implementation in the pipe system
+
You can now hear ghosts... sometimes
+
Seed bags and nutrients can now be pocketed
+
+
+
Monday, September 12, 12:48
+
+
New kitchen stuff: New recipes (Meatbread, Cheese, Omelette Du Fromage, Muffins), new chef's knife and trays (Spawn in the meat locker) and milk (spawns in the fridge). Recipes are as follows:
+ -Cheese: milk on food processor
+ -Cheese wedge: Slice the cheese wheel with the chef's knife
+ -Omelette Du Fromage: 2 eggs 2 cheese wedges
+ -Muffin: 2 eggs 1 flour 1 carton of milk
+ -Meatbread: 3 meats (whatever meats) 3 flour 3 cheese. Can be sliced.
+ Cheese_amount is actually displayed on the microwave.
+
Profession-special radio channels now have color.
+
AI card not retardedly lethal anymore, for anyone that didn't notice
+
HYDROPONICS OVERHAUL, credit goes to Skie and Numbers. Wood doesn't have the entity so the tower caps cannot be harvested. For now.
+
Bar is now barman-only, access-wise. No more shall the entire station trump inside the bar and choke the monkey.
+
Prepping ground for Barman update (SPRITE ME SOME GODDAMN BOTTLES)
+
+
Thursday, September 2, 22:45
+
+
Ghosts can no longer release the singularity.
+
Sprites added in preparation for a Hydroponics update
+
A decoy AI now spawns in the AI core during Malfunction rounds to reduce metagaming.
+
libmysql.dll added to distribution.
+
Aircode options restored to default configuration.
+
AIs properly enter powerloss mode if the APC in their area loses equipment power.
+
Hydroponics crates added to Hydroponics, containing Weed-B-Gone
+
Airlock electrification now actually works properly.
+
Karma database error message updated.
+
Cyborgs choosing the standard module no longer become invisible except for a pair of glowing red eyes.
+
Aliens now have a hivemind channel, accessed like departmental radio channels or robot talk with ':a'.
+
Full donut boxes no longer eat whatever item is used on them and disappear.
+
+
Monday, August 30, 16:24
+
+
PDA user interface has been given a graphical overhaul. Please report any problems with it on the issue tracker.
+
Personal lockers are once again available in the lockerroom
+
BUGFIX: Xenoburger iconstate was accidentally removed in an earlier revision. This has been fixed.
+
Some of the default messages have been changed.
+
Additional sprites added for plants and weeds in preparation for an expansion of Hydroponics.
+
A schema script is now available for setting up the SQL database.
+
+
+
Sunday, August 29, 05:09
+
+
The Robotics Crate no longer exists. Quartermasters can now order a MULEbot crate for 20 points, or a Robotics Assembly crate for 10 points. The latter provides 4 flashes, 3 proximity sensors, two 10k charge power cells and an electrical toolbox, and requires a roboticist or a head of staff to open.
+
Traitor AIs no longer lose their Law 0 in the event of power loss.
+
Administrators can now toggle the availiabilty of their right-click verbs to prevent accidental usage while playing.
+
Tool Storage vending machine is now a proper object. (code cleanup)
+
Buckets are now available from autolathes.
+
Four generic remote signaller PDA cartridges are now stocked in the Tool Storage vending machine.
+
AI status display density adjusted.
+
+
+
Thursday, August 26, 21:07
+
+
Open Source Release Thanks to Mport for releasable singularity code.
+
Cyborgs redone Thanks again to Mport for this, cyborgs are totally different now.
+
Engine Monitor PDA app is now Power Monitor PDA app, and actually works.
+
AI State Laws verb now allows the AI to choose which laws to state, in case of traitor AIs or laws ordering it not to state them. Hopefully this will cut down on 'OMG THE AI IS COPYING AND PASTING' metagaming.
+
Power Monitor circuitboard isn't mislabeled as Mass Driver Control any more.
+
Traitor and Rev-head clowns lose the clumsiness gene - this should make trying to flash people in Rev mode less of an exercise in frustration.
+
A sink has been added to the Fitness room - this lets you wash dirty and bloodstained clothing and equipment.
+
Blast doors and firedoors no longer open by just bumping into them.
+
The bar and kitchen now open onto the same seating area. The old cafeteria area is now used as a lockerroom to replace the old one which was displaced by Hydroponics.
+
The bar now has a space piano with which you can entertain and annoy the crew.
+
LIBRARY A library has been added to the station in the escape arm in order to educate the crew. The new Librarian job is available to manage it. Crewmembers can request and read books, or write and bind their own books for upload to a persistent database.
+
The supply of flashbangs available from Security has been reduced to cut down on people constantly flashbanging the escape shuttle.
+
InteliCards are available in various locations to allow the retrieval of valuable AI personality data in the event of catastrophic station damage.
+
+
+
Friday, August 06, 20:32
+
+
Hydroponics/Botany Added Credit goes to Skie and the folks over at the independent opensource SS13 branch, this is their code. It's lacking a lot, but it's a great start!
+
Way more tweaks than I can remember. Shouldn't wait so long between changelog updates.
+
+
Tuesday, July 13, 22:35
+
+
Singularity Engine Added Oh God we're all going to die (All credit on this one goes to Mport2004)
+
'Purge' AI module added - purges ALL laws (except for law 0). Will probably change this to a Syndicate only item
+
Cyborgs now spawn with a power cell. Should prevent stupid cyborg deaths (and also pave the way for starting as a cyborg once more bugs are fixed)
+
+
Saturday, July 10, 15:10
+
+
Examining a player will now tell you if their client has disconnected.
+
Examining a brain will now tell you if it's owner is still connected to the game.
+
Alien Queens can make facehuggers. Facehuggers can make larva. Larva can grow into xenos! Xenos can become queens! The circle of life~
+
Some powernet bug fixes: Bad list and division by zero.
+
+
Friday, July 09, 05:16
+
+
Tweaked crate costs for Quartermaster.
+
Increased metal available in Robotics.
+
Added department-specific headsets. Engineering, Medical, Command, and Security all receive special headsets capable of broadcasting on a standard frequency PLUS a secure frequency only available to headsets of the same type. Precede say messages with ":h" to use.
+
+
+
Tuesday, July 06, 19:16
+
+
Prayer command added.
+
State Laws command for AI added.
+
Disabled Lockdown command for AI. Too server heavy.
+
Crew manifest and various station databases should properly update when late arrivals join the game, now.
+
Quartermasters will receive 10 points every five minutes. This will probably be nerfed heavily, but we'll give it a shot anyhow.
+
Fixed a bug with doors/airlocks. (Thanks Mport2004)
+
+
+
Sunday, April 25, 18:53
+
+
+ New graphics:
+
+
+ Side Facing Sprites: Player sprites will now face in all directions when moving. Holy shit!
+
+
+
+
+
Monday 2.0, April 19, 2100
+
+
+ New features:
+
+
+ Disposal System: The station now has a fully functional disposal system for throwing away nuclear authentication disks and old, dirty clowns.
+
+
+ Breakable Windows: Windows are breakable by projectiles and thrown items (including people), shards hurt your feet.
+
+
+ Status Display: Station escape shuttle timers now function as status displays modifiable from the bridge.
+
+
+ Space Heater: Space heaters for heating up cold spaces, in space.
+
+
+
+ New items:
+
+
+ Welding Mask: Helps engineers shield their eyes when welding.
+
+
+ Utility Belt: Function as toolboxes equippable in the belt slot.
+
+
+ Mouse Trap: Hurt your feet, especially if you aren't wearing shoes!
+
+
+ Power Sink: Traitor item that rapidly drains power.
+
+
+
+
+ New graphics:
+
+
+ North Facing Sprites: Player sprites will now face north when moving north.
+
+
+ Hidden Pipes: Pipes are now hidden underneath floor tiles.
+
+
+
+
+ New robot: Medibot
+
+
+ Automatically attempts to keep crewmembers alive by injecting them with stuff.
+
+
+
+
+ New robot: Mulebot
+
+
+ Allows quartermasters to automatically ship crates to different parts of the station.
+
+
+
+
+
+
Funday, December 31, 2099
+
"FINALLY, DEV IS OUT"
+
+
+ Changes:
+
+
+ Atmos system GREATLY OPTIMIZED!
+
+
+ Brand new station layout!
+
+
+ Robust chemical interaction system!
+
+
+ HOLY FUCK PLAYING THIS GAME ISN'T LIKE TRODDING THROUGH MOLASSES ANYMORE
+
+
+ Feature: If two players collide with "Help" intent, they swap positions.
+
+
+
+
+
+
Tuesday, February 23, 2010
+
+
+ OH NO STRANGLING GOT NERFED: Insta-strangling (hopefully) removed. Victim no longer instantly loses consciousness.
+
+
+
+
Sunday, February 21, 2010
+
+
+ Cloning Machine: The Geneticist spilled coffee on the Genetics Machine's revival module and it was too costly to replace!
+
+
+ Clones may or may not have horrible genetic defects.
+
+
+
+
+
+
Thursday, February 18, 2010
+
+
+ New feature: Obesity from overeating in a short period of time.
+
+
+
+
Sunday, February 14, 2010
+
+
+ New feature: Station destruction cinematic if the crew loses in AI Malfunction or Nuclear Emergency.
+
+
+ New Position: Tourist
+
+
+ Centcom has entered the lucrative business of space tourism! Enjoy an event-filled vacation on the station, and try not to get killed.
+
+
+ Guest accounts are now restricted to selecting Tourist in Character Setup.
+
+
+
+
+
+
Friday, February 5, 2010
+
+
+ AI: Added 30 second cooldown to prevent spamming lockdowns.
+
+
+
+
Wednesday, February 2, 2010
+
+
+ Feature: Character preview in Character Setup!
+
+
+
+
Tuesday, February 2, 2010
+
+
+ New item: Drinking glasses that you can fill with water.
+
+
+ Feature: Sounds now pan in stereo depending on your position from the source.
+
+
+
+
Saturday, December 5, 2009
+
+
+ Traitor tweak: Agent cards can now be forged into a fake ID.
+
+
+
+
Friday, December 4, 2009
+
+
+ Supply Dock 2.0: The Supply Dock has been redesigned and now features conveyer belts! Amazing!
+
+
+ New uniforms: The Research Director, Chief Engineer, and the research jobs have new uniforms. The Head of Security has a cool new hat which happens to be his most prized possession.
+
+
+ Merged research: The first act of the Research Director is to merge Toxins and Chemistry into a single Chemical Lab. Hooray!
+
+
+ Robot tweak: You can now observe robots using the observe command.
+
+
+ Stamps: The heads now have stamps to stamp papers with, for whatever reason.
+
+
+
+
Monday, November 30, 2009
+
+
+ Supply Shuttle 1.0: Now you can order new supplies using Cargo Bay north of the autolathe.
+
+
+ New containers: The game now features a variety of crates to hold all sorts of imaginary space supplies.
+
+
+ New position: Quartermaster
+
+
+ A master of supplies. Manages the cargo bay by taking shipments and distributing them to the crew.
+
+
+
+
+ New position: Research Director
+
+
+ The head of the SS13 research department. He directs research and makes sure that the research crew are working.
+
+
+
+
+ New position: Chief Engineer
+
+
+ Boss of all the engineers. Makes sure the engine is loaded and that the station has the necessary amount of power to run.
+
+
+
+
+ New robot: Securibot
+
+
+ Automatically stuns and handcuffs criminals listed in the security records. It's also really goddamn slow.
+
+
+
+
+ New jumpsuits: Engineers and Atmos Techs have new jumpsuits to distinguish between them easier.
+
+
+
+
Friday, November 27, 2009
+
+
+ Monkey AI 2.0: Monkeys will now get angry, going after random human targets with the ability to wield weapons, throw random objects, open doors, and break through glass/grilles. They're basically terminators.
+
+
+ New gamemode: Monkey Survival
+
+
+ Survive a horde of angry monkeys busting through the station's airvents and rampaging through the station for 25 minutes.
+
+
+
+
+ New robots: Cleanbot and Floorbot
+
+
+ Cleanbots automatically clean up messes and Floorbots repair floors.
+
+
+
+
+ New spell: Mindblast
+
+
+ Causes brain damage, progressively causing other players to become even more retarded.
+
+
+
+
+ Alien Races
+
+
+ Wizards may randomly spawn as illithids, who gain Mind Blast for free, and nuke agents may randomly spawn as lizardmen.
+
+
+
+
+ Station shields: The station now has a toggleable forcefield that can only be destroyed by meteors or bombs. Takes a lot of station power to use.
+
+
+ Traitor scaling: Number of traitors/wizards/agents now scales to number of players.
+
+
+ New food item: Donk pockets
+
+
+ Delicious and microwavable, gives a bigger health boost for traitors.
+
+
+
+
+ Cigarettes: Now you can fulfill your horrible nicotine cravings. The detective starts with a zippo lighter and pack of cigarettes. Other packs can be be obtained via vending machines.
+
+
+ Warning signs: The station is now filled with various warning signs and such.
+
+
+ Updated graphics: Many, many objects have had their graphics updated including pipes, windows, tables, and closets. HUD graphics have been updated to be easier to understand.
+
+
+ Lighting fixes: New turf is now correctly lit instead of being completely dark.
+
+
+ Meteor fixes: The code and graphics for meteors has been fixed so the meteor gametype is more playable, sort of.
+
+
+ Escape shuttle fix: The shuttle can now be called in Revolution and Malfunction, but the shuttle will be recalled before it arrives. This way players can no longer call the shuttle to figure out the game mode during secret.
+
+
+ Changelog updated: New changelog entry for Thanksgiving thanks to Haruhi who will probably update the changelog from now on after almost a month of neglect.
+
+
+
+
Monday, November 3, 2009
+
+
+ Bug fix: Made most pop-up windows respect the close button.
+
+
+
+
Sunday, October 25, 2009
+
+
+ Randomized naming: Names for Central Command and Syndicate are now randomized.
+
+
+
+
Saturday, October 24, 2009
+
+
+ Bug fix: PDAs had their code cleaned up. Notice any problems? Report them.
+
+
+ New syndicate item: Detomatix Cartridge, allows remote detonation of PDAs (rather weak explosion)!
+
+
+ Feature: Remotely detonating PDAs has a chance of failure depending on the PDA target, a critical failure will result in the detonation of your own PDA.
+
+
+
+
Monday, October 19, 2009
+
+
+ Gibbing update: Gibbing stuff has been rewritten, robots now gib nicer.
+
+
+ LIGHTING!!!: The station now has dynamic lighting and associated items.
+
+
+
+
Friday, October 16, 2009
+
+
+ Poo v1.0~: This has caused many ragequits.
+
+
+ Flushable toilets: You can now use toilets to place your vile, disgusting and irreprehensible excretions (you disgusting children). Just be careful what you flush!
+
+
+
Monday, October 12, 2009
+
+
+ Feature: Emergency oxygen bottles can be clipped to your belt now.
+
+
+ Clothing update: Bedsheets are now wearable.
+
+
+ Updated HUD: A few minor tweaks to the inventory panel. Things might not be exactly where you're used to them being.
+
+
+
Monday, September 28, 2009
+
+
+ New position: Chef
+
+
+ Maintains the Cafeteria, has access to Kitchen and Freezer, Food creation will be in shortly.
+
+
+
+
+ Food update: Food items now heal Brute/Burn damage. The amount recovered varies between items.
+
+
+
Saturday, August 29, 2009
+
+
+ AI laws update: Nanotrasen has updated its AI laws to better reflect how they wish AIs to
+ operate their stations.
+
+
+ Traitor item change: E-mag renamed to Cryptographic Sequencer.
+
+
+
+
Friday, July 31, 2009
+
+
I'm really sorry everyone I just HAD to add a gib all verb.
+
Decided to add the creation of bombs to bombers list
+
Made the new bombing list EVEN BETTER!!!
+
Fixed a bug with admin jumping AND the traitor death message
+
Oops, fixed a bug that returned the right click pm thing thinking the admin was
+ muted.
+
Made a new improved way of tracking who bombs shit.
+
More formatting shit.
+
Fixed up some mute code and made it so that if a player is muted they cannot PM
+ us.
+
Adminhelps now logged in the admin file not ooc
+
Changed the way admin reviving is dealt with. (It was coded kind of weirdly
+ before)
+
Added a few areas to the observe teleport. Fixed some adminjump things. Modified
+ the paths of some areas.
+
You can now ban people who have logged out and admins can now jump to people
+ using the player panel.
+
Added in jump to key coded in a much better way than showtime originally did it.
+
Fixed magical wind when laying pipes. They start out empty!!
+
Made blink safer. Fixed the crew-quarters to ai sattelite teleport problem.
+
Forgot the message again. Added an emp spell. thanks copy&paste.
+
OH MY GOD I HAVE RUINED ASAY
+
Added electronic items to the pipe dispenser
+
fixed a formatting error with the changelog (I didn't break it, it was showtime)
+
Fixed a formatting error
+
Cleaned up sandbox object spawn code
+
New and improved admin log so we can keep an eye on these fuckers
+
Fixed adminjump because I realise most people use it for the right click option
+
Mushed together jump to mob and jump to key
+
Fixed a compilation error and made my test room more secure!
+
+
+
Wednesday, July 29th, 2009
+
+
These are a collection of the updates from the last 6 days. I promise to update
+ the changelog once a week. Note that this does not include all the changes in
+ the past 6 days.
+
+
+
Multitools can now be used to measure the power in cables.
+
Fixed a bug where the canister message would repeat and spam the user when
+ attackby analyzer. Fixed an admin formatting error.
+
Replaced all range checks with a in_range proc. pretty good chance I broke
+ something or everything.
+
Mutations use bitfields
+
Fixed a bug with my traitor panel.
+
Fixed the turrets, ruined Pantaloons map (test map). Did some things with
+ turrets and added a few areas.
+
Some stuff in here you know the usual shit. Bugfixes, formatting etc.
+
Stunbaton nerf.
+
Tempban longer than 1 year -> permaban.
+
Turfs > spawnable.
+
Shaking someone now slowly removes paralysis, stuns and the 'weakened' stuff.
+
CTF flags now check if someone has them equipped every 20 seconds, if they are
+ not then they delete themselves and respawn.
+
Fixed the r-wall-welder-message-thing.
+
Change to the CTF code, flag captures can now only happen if your team has their
+ flag in the starting position.
+
Pruning my test room.
+
Instead of the red and green team its now the American and Irish teams!
+
BACKUP BACKUP TELL ME WHAT YOU GONNA DO NOW Changed the monkey name code. Re-did
+ my antimatter engine code so it actually puts out power now
+
Fixed a bug that gave everyone modify ticker variables you silly sausage.
+
Sorted the AIs track list.
+
Constructable filter inlets and filter controls.
+
Added in admin messages for when someone is banned.
+
Bannana and honk honk.
+
+
+
Saturday, June 27th, 2009
+
+
+
Pipe construction now works completely. //Nannek
+
Many many other things that never gets recorded in the changelog!!
+
+
+
Saturday, June 27th, 2009
+
+
The Michael Jackson Memorial Changelog Update
+
Pipe filters adjusted for more ideal environmentals //Pantaloons
+
Added in job tracking //Showtime
+
Crew Manifest and Security Records now automagically update when someone joins //Nannek
+
Fixed a bug where sometimes you get a screwdriver stuck in your hand //Pantaloons
+
Flamethrowers can now be disassembled //Pantaloons
+
OBJECTION! Added suits and briefcases //stuntwaffle
+
Added automatic brig lockers //Nannek
+
Added brig door control authorization and redid brig layout //Nannek
+
Emergency toolboxes now have radios and flashlights, and mechanical toolboxes now have crowbars //Pantaloons
+
New whisper system //lallander
+
Some more gay fixes //everybody
+
Some really cool fixes //everybody
+
Really boring code cleanup //Pantaloons
+
~~In Loving Memory of MJ~~ Sham on!
+
+
+
Friday, June 12th, 2009
+
+
Looking back through the SVN commit log, I spy...
+
Keelin doing some more performance enhancements
+
Fixed one person being all 3 revs at once (hopefully)
+
Some gay fixes
+
New admin system installed
+
Fixed a bug where mass drivers could be used to crash the server
+
Various pipe changes and fixes
+
+
+
Wednesday, June 3rd, 2009
+
+
Death commando deathmatch mode added.
+
+
+
Monday, June 1st, 2009
+
+
Ghosts can no longer wander from space into the dread blackness that lies beyond.
+
Those other losers probably did a bunch of other stuff since May 6th but they don't comment their revisions so fuck 'em.
+
+
+
+
Wednesday, May 6th, 2009
+
+
Crematorium
+
Goon? button makes all your dreams come true.
+
Restructured medbay
+
+
Monday, May 4th, 2009
+
+
Does anyone update this anymore?
+
New atmos computer promises to make atmos easier
+
Autolathe
+
Couple of map changes
+
Some computer code reorganised.
+
I'm pretty sure theres a couple things
+
+
Saturday, April 18th, 2009
+
+
Weld an open closet (only the normal kind), gayes.
+
Chaplin has a higher chance of hearing the dead.
+
New traitor objective
+
Power traitor objective removed
+
New job system implemented for latecomers.
+
Head of Research quits forever and ever, is replaced by Head of Security (who gets his own office)
+
+
+
Fri, April 10, 2009
+
+
Admins are now notified when the traitor is dead.
+
Unprison verb (again, for admins).
+
+
+
Wed&Thu, April 8&9, 2009
+
+
Medical redone, doctors do your jobs! (Tell me what you think of this
+ compared to the old one)
+
Clickable tracking for the AI
+
Only the heads can launch the shuttle early now. Or an emag.
+
+
+
Mon&Tue, April 6&7, 2009
+
+
Sounds. Turn on your speakers & sound downloads.
+
Scan something with blood on it detective.
+
+
+
Sunday, April 5, 2009
+
+
A large icon for the headset, no reason it should be so small.
+
+
+
Saturday, April 4, 2009
+
+
Emergency closets now spawn an 'emergency gas mask' which are just recolored gas masks, no other difference other than making it obvious where the gas mask came from.
+
+
+
Wednesday, April 1, 2009
+
+
Constructable rocket launchers: 10 rods, 10 metal, 5 thermite and heated plasma from the prototype.
+
Emergency closets have randomized contents now.
+
Fixed a bug where someone who was jobbaned from being Captain could still be picked randomly
+
+
+
Friday, March 27, 2009
+
+
Fixed a bug where monkeys couldn't be stunned.
+
Change mode votes before game starts delays the game.
+
+
+
Thursday, March 26, 2009
+
+
The brig is now pimped out with special new gadgets.
+
Upgraded the admin traitor menu.
+
+
+
Tuesday, March 24, 2009
+
+
GALOSHES!
+
A certain item will now protect you from stun batons, tasers and stungloves when worn.
+
+
+
Monday, March 23, 2009 (EXPERIMENTAL)
+
+
Say / radio / death talk systems recoded, hopefully improving it.
+
Announcements of late joiners are now done by the AI if it's alive :-)
+
+
+
Monday, March 23, 2009
+
+
Random station names.
+
Changes to the message stylesheet.
+
Admin messages in OOC will now be colored red.
+
+
+
Saturday, March 21, 2009
+
+
Added a command to list your medals.
+
ETA no longer shows when it doesn't matter.
+
Nerfed the ability to spam shuttle restabalization.
+
Fixed the 'Ow My Balls!' medal to only apply from brute damage rather than both brute and burn damage.
+
+
+
Thursday, March 19, 2009
+
+
Job banning.
+
Genetic Researcher renamed to Geneticist.
+
Toxins Researcher renamed to Scientist.
+
Help reformatted.
+
Fixed a bug where combining bruise packs or ointments resulted in an incorrectly combined amount.
+
Renamed memory and add memory commands to Notes and Add Note.
+
+
+
Tuesday, March 17, 2009
+
+
Medals! MEDALS!
+
Trimmed the excessively long changelog.
+
+
+
Saturday, March 14, 2009
+
+
Janitor job complete! Report any bugs to adminhelp
+
+
+
Saturday, March 7, 2009
+
+
Wizard now needs his staff for spells
+
Be careful with APCs now okay?!
+
Fixed Memory and made it more efficient in the code
+
Crowbars now open apcs, not screwdrivers. They do something else entirely
+
Hackable APCs
+
When APCs are emagged they now stay unlocked
+
Re-did a shit tonne of admin stuff
+
New admin system is pretty much finished
+
FINALLY backpacks can now be looked in while on the ground.
+
+
+
Tuesday, February 24, 2009
+
+
Ghosts no longer able to open secret doors
+
Suicide vests now work as armor
+
Blood no longer comes out of the guy if you pull him due to lag
+
Admin panel has been touched up to include html tables
+
Mines now added, only spawnable right now however
+
Fixed the syndicate nuclear victory bug
+
Wizard now spawns with wizard outfit which he must wear to cast spells
+
Blood bug fixes
+
Fixed a retarded bug that meant I didn't have the power to kick admins
+
THUNDERDOME!
+
Several new facial hair options and a bitchin' mohawk
+
Blood by Culka
+
Nuke disk now spawns in ALL game modes so that during secret rounds the syndicate now have the element of surprise!
+
+
+
Saturday, February 22, 2009
+
+
Implemented unstable's "observer" mode
+
Halerina's wizard mode
+
Non-interesting stuff
+
Began addition to the new admin system - right now only available to coders for testing
+
Admins can now click on the multikeying offenders name to pm them, instead of hunting for them in the pm list
+
Halerina's chemistry system
+
You can now deathgasp without being dead, hopefully so people can fake their own deaths.
+
Redid Medlab
+
New chemist job
+
+
+
Thursday, February 19, 2009
+
+
New DNA system. 200th Revision special.
+
Various bugfixes
+
Maze
+
+
+
Monday, February 17, 2009
+
+
Added a new game mode into rotation.
+
Added an AI satellite
+
Lockdowns can be disabled with the communications console
+
Prison shuttle can be called on the comm console, but only if its enabled by admins first
+
When you slip into space you'll have a 50% chance of going to z=4 instead of z=3
+
+
+
Friday, February 13, 2009
+
+
Fixed Cakehat
+
Dead people can now see all turfs, mobs and objs not in their line of sight.
+
Modified the map slightly
+
Stungloves can now be "made"
+
Flashes can now have their bulbs burning out.
+
Batons can now be turned on and off for different effects. They also now have 10 uses before they need to be recharged.
+
+
+
Tuesday, February 10, 2009
+
+
Fixed all the autoclose bugs
+
Due to it being myself and Keelin's 100th revision we have added a super-secret special item. Don't ask because we won't tell! Figure it out!
+
+
+
Sunday, February 8, 2009
+
+
Modified doors in engineering so that they do not autoclose - Autoclose now handled by a variable
+
Fixed toxin researcher spawn bug
+
Changed the "You hear a faint voice" message.
+
Gave the host new commands to disable admin jumping, admin reviving and admin item spawning
+
Fixed some airlock autoclose bugs
+
Changed some doors to not autoclose.
+
Nerfed the toolbox down.
+
+
+
Friday, February 6, 2009
+
+
Doors now close after 15 seconds
+
Fixed some p cool bugs
+
Cakehat
+
Added another suit
+
Walls now take 5 seconds to build
+
Added sam0rz, thesoldierlljk and kelson's revolution gamemode. Thanks guys!
+
+
+
Thursday, February 5, 2009
+
+
Fixed a couple of bugs
+
Improved bar ;)
+
Beer acts like pills and syringes
+
+
+
Tuesday, February 3, 2009
+
+
Added 'Make AI' Option for Admins
+
Added dissolving pills in beer (cyanide and sleeping pills)
+
Modified engine AGAIN, but personally I love it now
+
+
+
Monday, February 2, 2009
+
+
Moved bar due to popular demand
+
Captains room is now a security checkpoint
+
Assistants now have access to maint tunnels again
+
Courtroom
+
Engine has been redone slightly to make it easier to load
+
Nerfed beer a lot more
+
+
+
Saturday, January 31, 2009
+
+
Added a bartender job + Bar
+
Captains panic room
+
Voice changer traitor item
+
Bartender suit
+
Made taking a table apart take longer
+
Balanced beer a bit more.
+
Assistants can no longer open external air locks and maint tunnels, sorry guys. Get a job you bums.
+
Engineers CAN access external air locks and maint tunnels.
+
Fixed traitor AI bug
+
+
+
Thursday, January 29, 2009
+
+
Added traitor menu for admins - The ability to turn people into "traitors" as well as keep track of their objectives.
+
Implemented Keelins revive system - Primary Admins can now revive people.
+
Moved and redid security to prevent clusterfucks and everyone just crowding around security.
+
Redid the brig to make it bigger and so that people can break others more easily out since it isn't right in security.
+
Moved and redid captains quarters/heads quarters. Captains made much smaller and heads is now more of a meeting room.
+
Added Stungloves and an axe - right now only admin spawnable.
+
Implemented Persh's adminjump back in - admins can now jump to set locations.
+
Added a feature that if someone logs off their character moves around and says things - Change what they say from the config/names/loggedsay.txt file.
+
Added in adminwho verb - tells the user if there are any admins on and who they are.
+
+
+
Saturday, January 10, 2009
+
+
Freedom implant has been changed so that it will have a random emote associated with it to activate it rather than always chuckle.
+
There is now a pinpointer tool for use in Nuclear Emergency. It works similar to the existing locator, in that it will detect the presence of nuclear disks and in what direction it is.
+
The nuke being detonated in Nuclear Emergency should now properly end the game.
+
Spacesuits now cause you to move slower when not in space.
+
Syndicate in Nuclear Emergency now have syndicate-themed spacesuits.
+
Blob mode should properly end now.
+
+
+
Wednesday, January 7, 2009
+
+
Syndicate Uplink has been changed up, allowing traitor more freedom in his ability to be... traitorus.
+
Syndicate Uplink can now spawn a ammo-357, syndicate card, energy sword, or timer bomb.
+
Fixed an issue where Syndicate Uplink looked different than a normal radio.
+
+
+
Monday, January 5, 2009
+
+
You can choose to be a nudist now.
+
Facial hair!
+
Added constructable flamethrowers.
+
Redid internal naming scheme for human/uniform sprites.
+
Helmet visors are now translucent.
+
Held item graphics corrected for basically everything, internally only uses one dmi file instead of two.
+
Config settings reorganized for.. organization.
+
Seperated male and female names.
+
Females have pink underwear.
+
Guests can no longer save/load profiles, as this just created useless profiles that weren't used again.
Thanks to: /tg/Station 13, Baystation 12, /vg/station, NTstation, CDK Station devs, FacepunchStation, GoonStation devs, the original SpaceStation developers and Invisty for the title image. Also a thanks to anybody who has contributed who is not listed here :( Ask to be added here on the forums/Discord.
@@ -56,921 +56,1016 @@
-->
-
05 November 2020
-
Krysonism updated:
+
30 October 2021
+
TheChaser212 updated:
-
fixed a gulag stacking machine exploit that would let you farm points with minimal or no ores mined.
+
Backup objectives shouldn't disappear anymore
+
Cleaned up cryopod objective handling
-
NotRanged, Gandalf2k15 updated:
+
+
29 October 2021
+
Ivniinvi updated:
-
Changed explosion sound code to be more noticeable and terrifying throughout the station during siginificant explosive events.
+
Unintentional spins and flips no longer cause confusion.
-
PowerfulBacon updated:
+
TheChaser212 updated:
-
Removed the old autolathe UI
-
TGUI autolathe UI
-
Multiple users can browse autolathe UI without seeing the same tabs.
-
You can now eject materials from an autolathe
-
You can now queue items in an autolathe
-
You can now toggle continuous fabrication mode on an autolathe queue, or an item in the autolathe queue meaning it will build repeatedly until the autolathe runs out of materials.
-
Trigger build wire on autolathes
-
Autolathe queue
-
Autolathe ejection direction.
+
Cyborg mesons work similarly to human mesons by showing dark areas
+
+
rkvothe14 updated:
+
+
Fixed a minor typo in Show/Hide GhostLaws.
-
04 November 2020
-
PowerfulBacon updated:
+
28 October 2021
+
Ivniinvi updated:
-
Changed the view width to 17x15
-
Mirage borders sometimes not working properly
-
Proper zooming out procs.
+
Toxins will no longer bomb the MetaStation Exploration Shuttle at roundstart.
-
That0nePerson updated:
+
Phil Smith updated:
-
fixes icon issue with "The Mad's labcoat"
+
A small commemoration to our former LRP server, golden, is now on the captain's laser gun.
-
qwertyquerty updated:
+
Pirill updated:
-
Made auto viewport fit default
+
Fixes fairygrass shuttle flight runtime
-
-
03 November 2020
-
Archanial updated:
+
francinum updated:
-
Reply pop-up in stealth mode will no longer reveal your orinigal ckey.
+
Brig timer presets and max durations can be modified in the config
+
The economy flag hasn't done anything for over a year, and has been removed.
-
PowerfulBacon updated:
+
jupyterkat updated:
-
Syndicate encryption key is restricted on incursion gamemode
-
Syndicate mega bundle is no longer restricted on incursion gamemode.
+
body only records are now clearer
+
body only records persist after cloning
-
Victor239 updated:
+
+
27 October 2021
+
FriendlyContractor updated:
-
Small tweaks to brig phys locker and loadout.
+
Ash heretics immune to being set on fire
-
ivanmixo updated:
+
Kapu1178 updated:
-
Airlocks to bridge and other high security areas such as the heads' offices, EVA, teleporter and gateway now start reinforced with plasteel.
+
IPC blood trails are black now
-
-
02 November 2020
-
LastCrusader105 updated:
+
KubeRoot updated:
-
Moved the engineering shower heads outside of the blast doors
+
Boxstation's turbine and toxins burn chamber airlocks will now pressurise and depressurise correctly, no longer softlocking you.
-
Moccha-Bee updated:
+
MNarath1 updated:
-
Contaminated objects will now glow bright green until cleaned, remember to quarantine rouge free golems.
-
Buffed rad goggles in general, the text lasts longer, is brighter, and will show more then twice as far away
-
Fixed the text from radiation goggles not showing above mobs
+
small misstype from tesla code
PowerfulBacon updated:
-
Felinids can laugh
+
Syndicate listening outpost ruin part.
-
eeSPee updated:
+
Syrox25 updated:
-
Limbsnakes - new changeling abilty
+
Adds Paper Cup recipe to the Misc tab
-
-
01 November 2020
-
francinum updated:
+
The-Moon-Itself updated:
-
No more instagib glass tables.
+
More list related WireMod components.
+
The index component now has more features.
+
The component printer now has a list component category
-
r1ks-iwnl updated:
+
TheChaser212 updated:
-
Paramedics now have EVA access
+
Syndicate implants are now hidden from health scans
+
Added inhand icons for the energy saw
-
30 October 2020
-
ike709 updated:
+
26 October 2021
+
Kapu1178 updated:
-
Circuits can no longer be exploited to make super bombs.
+
Kills the "Synth" species
-
super12pl updated:
+
tralezab, AnturK updated:
-
Adds blood filter to lavaland syndicate base
+
curators can now print paintings
+
persistent paintings
+
AIs can now pick paintings as their screens
+
tgui canvases
-
29 October 2020
-
Crossedfall updated:
+
25 October 2021
+
DeltaFire15 updated:
-
Kilo is back in rotation
-
Added Gamepad bindings
+
Cyborg B.o.r.i.s. installation now checks for if the chest has a cell, just like how it does with MMIs.
-
Dingo-Dongler updated:
+
Ivniinvi updated:
-
new phasic scanner sprite.
+
Emitters that don't use power won't warn about powernets.
Adds a 5-minute cooldown to non antagonist related ghost roles if the client has more than 2 deaths or has committed suicide.
-
Replaces spaces in hilbert's hotel with tabs
+
The Decal Painter is now available at protolathes and autolathes for you to paint decals.
-
ike709 updated:
+
Kapu1178 updated:
-
Bee downstreams can now decide to automatically block you from joining if you're banned from bee.
+
Cleans up species outfit code.
-
thelaughingbomb updated:
+
KubeRoot updated:
-
borg cutters work now and cant weld.
+
Wizarditis now functions correctly at high stages.
+
Resetting the thunderdome no longer breaks it.
+
The viral extrapolator can no longer be used at a distance.
-
-
28 October 2020
-
PowerfulBacon updated:
-
-
Syndicate medical defib padels can now be equipped
-
Tweaked lootbox TC allocation algorithm and added in funny roll 1 message
-
Runtime on TGUI failing to load.
-
-
park66665 updated:
+
Phil Smith updated:
-
fusion is no longer caused by hotspot react()
-
fire probably became a tiny tiny bit faster
+
When offering a renaming potion to someone it now no longer says that you are giving the potion to yourself but it now properly gives the name of the person you are handing it to.
-
-
27 October 2020
-
Moccha-Bee updated:
+
Pirill updated:
-
Added several new donk pocket types, and boxes, including sprites for each of course. Spicy-Pocket,Teriyaki-Pocket, Pizza-Pocket,Honk-Pocket,Gondola-Pocket and Berry-Pocket. This includes re-organizing them from the OTHER section in snacks_pastry.dm to a donk pocket section. Thanks to Farquaar for the box icons and some of the code! As well as latots for the donk pocket icons.
-
Added a Donk Pocket Variety crate, for 2000 credits, which includes 3 randomly selected donk pocket box types, excluding Gondola-Pocket.
-
Added a recipe for each new Donk Pocket type.
-
Added the new Donk Pocket types to the maint loot table.
-
Donk Pocket loot spawner object, credit to TheVekter for that, which was then added into the kitchens of all the main maps.
-
All Donk Pockets now follow the same naming scheme of X-Pocket.
-
Added craftable Donk Pocket boxes.
+
New sprites and inhands for radiation and combat medkits, tweaked sprites for the biohazard toxin medkit
+
Makes regular prescription glasses printable at a Medical Protolathe
+
Adds craftable prescription versions of medical, security and diagnostic HUDglasses, science goggles and meson scanners
+
Makes the Blood Cult Zealot's Blindfold correct nearsightedness when in use
+
Resprites the original prescription medical HUDglasses and adds equivalent sprites for the rest
-
St0rmC4st3r updated:
+
The-Moon-Itself updated:
-
Resonators now detonate all their sonic blasts on use.
-
Plasma cutters consume more plasma now.
+
The chameleon projector now projects objects with overlays, such as computers and holosigns, correctly.
-
That0nePerson updated:
+
TheChaser212 updated:
-
Added plasma cutter module for mining cyborgs
+
Added some useful items to the debug outfit
-
eeSPee updated:
+
jupyterkat updated:
-
the cost and resistances of ling armor; it is now a loud anti projectile defense.
+
fixed layer manifold runtime
-
qwertyquerty updated:
+
zeskorion updated:
-
Coder Socks
+
slime pylons no longer kill toxinlovers/slimepeople
-
26 October 2020
-
Dennok updated:
+
23 October 2021
+
Archanial updated:
-
Now you can set transit fly direction for custom shuttle
+
fixed incursion implant not exploding (thanks pricklytomato!)
-
MNarath1 updated:
+
Crossedfall updated:
-
Makes locker doors animated
+
Removed the extra light switch from Box Station's research lab
+
Re-added the access requirement to Pubby's RD office
-
qwertyquerty updated:
+
Skoglol updated:
-
Ambience sometimes just not playing fixed
-
Putting items in plants by just clicking the plant
+
Added a few new buildmode modes. Outfit, to quickly apply or remove outfits. Delete, to quickly delete any atoms. Slightly overhauled the copy proc used here and in supply pods, now compatible with most mobs.
Passive gate pressure limit upped to 100 atmospheres
-
19 October 2020
-
Phil Smith updated:
+
15 October 2021
+
DrMacCool updated:
-
changed the color of the blood bro hud icon
+
Adds 18 new hairstyles/beards requested by the community.
-
Victor239 updated:
+
+
11 October 2021
+
Archanial updated:
+
+
Removed hangover landmarks
+
Hangover now dynamically places its spawns around the station
+
Fixed random_safe_station_turf sometimes returning null even if it had viable turf
+
+
Dejaku51 updated:
-
Added security gas masks to SecDrobe
-
HOS gun is full-auto again.
+
Fixed camera name and network in Incenerator on Meta and Box
-
Xoxeyos updated:
+
Ivniinvi updated:
-
servent is now servant.
+
Traitor panel will now show if the person in question is banned from a specific antagonist.
-
eeSPee updated:
+
TheGreyDiamond updated:
-
smartfridges will automatically load items off conveyor belts, or items thrown at them.
+
fixed zero padding mistake
-
18 October 2020
-
Dennok updated:
+
10 October 2021
+
KubeRoot updated:
-
Custom shuttle delete starting port after takeoff
+
The exploration detonator now correctly checks if you're on the station Z level for its safety. You can also no longer detonate explosives across Z levels.
+
Artifacts from supercruise often didn't work, they should be much more interesting now.
-
Phil Smith updated:
+
+
09 October 2021
+
Archanial updated:
-
IPCs no longer take brute and burn after death.
+
fixed hud runtime
-
Victor239 updated:
+
+
06 October 2021
+
KubeRoot, EdgeLordExe updated:
-
DRAGnet teleport no longer has random variance.
-
You now must be both in an aggro grab and grab intent to fireman carry or piggyback.
+
Radios now have code support for anonymization
+
The intercoms in chapel confessionals will now hide your name
-
r1ks-iwnl updated:
+
Pirill updated:
-
Adds Airlock Charge to traitor uplinks
+
Fixed a few typos related to exploration, wall building, wraith spectacles and Battle Royale
-
super12pl updated:
+
rkvothe14 updated:
-
Removed broken absorb changeling objective
+
fixes Nightmare's lighteater so they can destroy light sources again.
-
17 October 2020
-
Cenrus updated:
+
05 October 2021
+
DrMacCool updated:
-
Mech occupants can now cycle airlocks
+
Adds the 'NOSOCKS' trait.
+
Species with NOSOCKS can't wear socks.
+
Squids now have the 'NOSOCKS' trait.
-
Victor239 updated:
+
Ivniinvi updated:
-
Standardised the contraband locker on all stations.
+
There is now a brain trauma that allows ghosts to issue a movement command once every 12 seconds.
+
Deadchat_control is now a smite option.
-
eeSPee updated:
+
+
04 October 2021
+
Archanial updated:
-
Razor from service cyborg
-
Tray from service cyborg
-
service beaker aparatus replaced with a grasper aparatus, that can also carry food items, cigs, etc
-
Enzime bottle from service cyborg
-
cyborg shaker can synthesize its own enzime
-
cookie synthesizer from service cyborg
-
emagged RSF can dispense plasma cigs
-
Jungle Fever: Added advanced huds to all monkeys and gorillas.
-
Jungle Fever: 4% chance to turn into a gorilla instead of regular momke
Fusion no longer dies to 2.7K. Instead, the fusion just stops, waiting eagerly for a fate other than 2.7K.
+
Sprite sheets! A new system to allow for better species in the future.
-
super12pl updated:
+
KubeRoot updated:
-
Paramedics now have medical doctor's role-specific traitor items
+
Fixes an issue that would sometimes lead to increased view range while not ghosted
+
View range is now actually fixed, sorry for the trouble.
-
zeskorion updated:
+
+
03 October 2021
+
DrMacCool updated:
-
readds metabolic boost viro symptom
+
Added exploration envirosuit+helmet.
+
Plasmamen on the exploration crew now spawn in said clothes.
-
-
14 October 2020
-
Cenrus updated:
+
KubeRoot updated:
-
The NT Salvage Ship has been adapted for fastmos.
+
Newly created chat tabs will now show important messages. If you're using custom-created chat tabs, you'll need to delete and recreate them to be able to see them. This fixes being unable to see if somebody is overdosing when doing chemical scans with the health analyzer.
-
amidoingitright updated:
+
St0rmC4st3r updated:
-
magic immunity to chaplain armor
+
Shuttles will no longer refuse to lift off due to counting all other shuttle's weight as their own.
-
13 October 2020
-
TheChaser212 updated:
+
02 October 2021
+
Archanial updated:
-
Crew monitors and pinpointers now work with multi-z maps
+
Station trait command report is now generated independly, that means it will be printed even if intercept isn't enabled.
+
fixed lavaland turret control runtime
-
yorii updated:
+
+
01 October 2021
+
AnCopper updated:
-
tiny renaming of a runtime error datum
+
You can no longer drag blobbernauts.
-
-
12 October 2020
-
Autisem updated:
+
Archanial updated:
-
Felinds are supposed to like getting thrown on tables
+
fixed runtime related to missing client in mentorhelp
-
BeloneX updated:
+
Crossedfall updated:
-
Added the corporate security uniform to the secdrobe, you can finally have your black security clothes.
-
Added a captains jacket to the captains locker.
+
Resolves a bad index runtime caused by the stat panel's verb caching
-
PowerfulBacon updated:
+
Ivniinvi updated:
-
Atmosbot
+
Fixed artifact supercruise message.
+
Alkali Perspiration now correctly updates its severity.
+
The debug uplinks now actually use the debug toggle.
-
Victor239 updated:
+
KubeRoot updated:
-
Most armoured suits and helmets' thick reinforced materials now prevent syringes, parapens and hypos.
-
Shotguns have new, bigger sprites for inhands and back slot.
-
Riot shotguns have a slightly slower fire rate, and combat shotguns are now faster.
-
Shotgun lethal ammo is slightly weaker, moreso against armoured targets.
+
Space hotel now has a secondary solar control console that can reach the north-east solar array.
+
Space hotel has had some missing and miscolored cables tidied up.
-
eeSPee updated:
+
checkraisefold updated:
-
Deputy access upgrades in warden's and HoS's locker.
+
Changed flavor text to text in Notes for VIPs from the VIP extraction exploration mission.
-
-
11 October 2020
-
Dennok updated:
+
fighterslam updated:
-
Custom shuttle get name that you give it on construction.
+
Fixed DeltaStation holodeck.
-
MNarath1 updated:
+
lordScrubling updated:
-
fixes circuit input size so you can put longer circuits again
+
throwing creatures doesn't make blood trails anymore
-
SomeAngryMiner updated:
+
qwertyquerty updated:
-
New supermatter sprites!
+
Report round id as int on ?status
-
St0rmC4st3r updated:
+
+
30 September 2021
+
Ivniinvi updated:
-
The lavaland outpost construction project has been postponed due to misuse of funding by the previous research director.
+
Roundstart tips are up-to-date.
-
Zesko, thatguythere03 updated:
+
KubeRoot updated:
-
cluwne rune changes
-
new cluwne rune sprite
+
The "Say" window no longer has input lag when opening it with the hotkey.
+
The "Say" window now has input lag when closing it with enter.
-
qwertyquerty updated:
+
+
29 September 2021
+
AnCopper updated:
-
4 random runtimes, including gibs not always spawning on human death
+
The projector can not be researched.
-
10 October 2020
-
Phil Smith updated:
+
27 September 2021
+
Archanial updated:
-
Hyposprays can now have their dosages changed and can also be emptied
-
Adds hypospray noise from TGMC
+
Fixed join via pod trait sending people to hyperspace
-
TheChaser212 updated:
+
ike709 updated:
-
advanced injectors no longer get an extra 'expended' added for each mutation
-
only filled activators will give chromosomes
-
chromosomes will always go into the dna console if possible
-
telekinesis works on dna consoles
-
fixed some genetics typos
+
514.1568 is now required for playing and compiling
-
zeskorion updated:
+
+
26 September 2021
+
KubeRoot updated:
-
syringe stuff
+
The metastation external airlock in holodeck maintenance now sets up correctly roundstart
-
07 October 2020
-
Chessus updated:
+
25 September 2021
+
Raven-Industries updated:
-
Filter Blood Surgery
-
Adds item Blood filter
-
Added Blood filter to maps in surgery area and Med surgery duffle bag and sec surgery duffle bag. Removed from sec deptuty bag from original pr.
-
Added Blood filter to tool debug item
-
Can be created in prolathe. Removed from autolathe inline with no surgical tools in autolathes
-
The blood filter can now be put in medical belts, medical aid kits and backpacks
-
The surgery is automatically repeatable similar to tend wounds
+
increased book title limit from 20 chars to 50
-
06 October 2020
-
ATHATH, ArcaneDefence updated:
+
23 September 2021
+
Autisem updated:
-
Moth plushie, found in arcade machines as a reward. Commiting suicide with it is ill advised.
+
Mines explode once instead of too much
+
+
heepox updated:
+
+
Adds a ClothesMate Refill canister to cargo for 800 credits.
-
04 October 2020
-
Archanial updated:
+
19 September 2021
+
Therosass updated:
-
You can now paste longer songs again.
+
Warping grey runes no longer create invisible slimes.
-
Phil Smith updated:
+
+
17 September 2021
+
KubeRoot, Mothblocks updated:
-
New incursion icon
-
Added a new hud icon for incursion
+
Tooltips in tgui are now very fast, even with a lot of them on one interface.
-
PowerfulBacon updated:
+
+
16 September 2021
+
ike709 updated:
-
Players who don't want to be a heretic will no longer lose antag rep if selected as a heretic due to low pop
-
Abusable circuit components have been given a limit to how many can be out in a circuit.
-
removed FASTDMM_PROP from advanced airlock controllers.
+
The DB subsystem will now always disconnect properly.
Message when trying to rotate anchored plumbing machines
-
Plumbing pipes nolonger appear disconnected
-
Reactions that leave no chemicals don't break the reaction chamber now
+
Battle Royale requires +ADMIN instead of +FUN after the round ends.
-
park66665 updated:
+
Kapu1178 updated:
-
supermatter has a power-ceiling of 1 million
+
Humans now display a chat bubble when typing, as an effort to move us closer to Baystation
+
Typing Indicators are now on the right instead of the left, to not be covered by medhuds.
+
Say hotkey is probably more responsive
-
qwertyquerty updated:
+
PowerfulBacon updated:
-
Refactors ambient sounds completely to be less bad code wise and better IC and immersion wise
+
Resolves a crash exploit related to z-levels and supercruise.
+
Thrown humans can now be caught.
-
zeskorion updated:
+
TheFakeElon updated:
-
piercing syringes now inject past thick clothing when not shot from a syringe gun as well
-
syringe guns no longer instantly inject their reagents, instead injecting them over time
-
syringes can be embedded on harm intent, also injecting reagents over time, but at a slower rate
-
explosive chem mixes no longer purge chems if an explosion is not triggered
+
improves fail2topic regex
-
03 October 2020
-
Moccha-Bee updated:
+
14 September 2021
+
Pirill updated:
-
nerfed insta teleport from pandora.
+
New sprites for geraniums, lilies and rainbow flowers
+
Forget-me-nots as a new flower type
+
Craftable flower crowns from rainbow flowers, sunflowers, poppies and lilies
+
Two new botany cargo bounties for forget-me-nots and rainbow flower crowns
+
Fixes rainbow flower reagent code
-
Victor239 updated:
+
PowerfulBacon updated:
-
Adds 3 new chameleon items to the traitor uplink: Chameleon Bangproof Headset, Chameleon Flashproof Glasses and Chameleon Combat Gloves.
-
Chameleon plasmaman envirohelmet is no longer permanently flashproof.
+
All golems excluding bone golems are transsting immune.
+
Levelling up projectile spells (Fireball, spellcards, tesla spell, magic missile) makes the projectiles more powerful.
+
Upgrading repulse makes the effect stronger (larger throw distance, longer paralyze)
+
Altered the charlie station ruin outpost to add an ORM, KA, Autolathe and Ore Silo. Removed the exosuit fab having access to station mats. Added a bunch of materials around and a shuttle creator.
+
Traitor capsule shuttle gets a plasma refinery and an emergency pickaxe.
+
Trait induced blindness will no longer cause your screen to be blurry forever if you get blurred.
-
francinum updated:
+
lordScrubling updated:
+
+
Added hotkey to toggle walking/running
+
Ability to rebind locking movement hotkey
+
+
qwertyquerty, Merct updated:
-
Dynamic is now continuous.
+
Soundtrack music shows in credits
-
02 October 2020
-
Sarchutar updated:
+
13 September 2021
+
Bokkiewokkie updated:
-
Crew manifest available for cyborgs under "Robot Commands"
+
Fixed a security issue with the advanced proccall
-
park66665 updated:
+
PowerfulBacon updated:
-
stim_ball reaction now properly consumes plasma
+
Custom shuttles and rooms can be made on asteriods.
+
Fixes another case of meteors moving invalidly.
+
Refactors the tesla to be less intensive on the server. (Slightly weakening its destructive capability)
+
The MC panel now shows tick uage and the tick limit.
-
-
01 October 2020
-
EdgeLordExe updated:
+
Vexylius updated:
-
hopefully fixes heretics :agony:
+
Janitorial toolset implant
+
Botanical toolset implant
-
TheChaser212 updated:
+
ike709 updated:
-
Bee infestation honey threshold is actually 12 now
+
BYOND infinite loop detection is now documented.
-
30 September 2020
+
12 September 2021
Archanial updated:
-
Circuit boards are colored.
-
Every machine broken by previous pr should be working now.
+
removed static usage of typecache in get_area()
PowerfulBacon updated:
-
Hierophant relay no longer transmits the radio channels of people on Reebe, but will still recieve them.
-
Clockies can now be deconverted properly.
-
More than 1 of each weapon can now be summoned.
-
Clockwork marauders can now speak rat'varian
-
Clockwork marauders can now take damage from bullets
-
Spells will now consume vitality when they are meant to
-
-
TMTIME updated:
-
-
Clowns no longer harm themselves with toy dualsabers
+
Fixes meteors causing bugs in SSorbits.
francinum updated:
-
ss13_feedback can now be disabled with the LIMITED_FEEDBACK flag.
+
Mechs can now properly recalibrate their controls.
-
zeskorion updated:
+
+
11 September 2021
+
francinum updated:
-
mutation toxin is restricted again
+
You should NOT be granting this lightly.
-
29 September 2020
-
Archanial updated:
+
10 September 2021
+
AnCopper updated:
-
Cloning works again.
+
ERT Commanders get an Omni Door remote.
+
added microwaves, donk pockets, and oxygen tank dispensers to exploration shuttles
Makes mining station's outer airlocks not vent out the entire station if you go through them too fast
+
Aquarium and aquarium accessories. Also fish. Check out cargo to start.
+
Aquarium fish now reproduce.
-
Victor239 updated:
+
DatBoiTim updated:
-
All Command roles now require 20h playtime in the relevant department, except HOP requires 10h Service. Warden and QM require 10h each.
+
Sleep and Drowsyness Immunity To Meth
+
Decreased Speed Buff from Meth
+
Increased Stamina Damage Reduction
-
archeoid updated:
+
Goshagosha updated:
-
fixed non-functional passkeys for circuits
+
Added structure (plant) 'Strange plant' to code/modules/mining/lavaland/ash_flora.dm
+
Added boolean flag destroy_on_harvest (FALSE) to plants in ash_flora. If it is on, plants will not regrow.
+
Strange seeds removed from biogenerator
+
xpod[1-4] added to icons/obj/lavaland/ash_flora.dmi (2,3,4 are placeholders for better art variety)
-
eeSPee updated:
+
ImSynthex updated:
-
people scream when taking damage
+
You can now throw stuff at movables with no density, such as mobs on the floor, mice, APCs etc etc
+
Refactored thrownthing datum
-
qwertyquerty updated:
+
Ivniinvi updated:
-
Re-add qwerty lighting in specific areas
-
You can now specify area bulb brightness
-
Added a paywall for felinid species
-
Added input capping and sanitization
-
Added some new input wrappers
-
Added race paywall to config
-
Felinids are no longer paywalled.
+
Updated some logging, added some logging.
+
Admins can now force people to stub their toes.
+
Fixes the FLW link in some logging.
-
r1ks-iwnl updated:
+
PowerfulBacon updated:
-
Adds 8 new icons for circuit boards, based on department colors
-
Reorganizes computer_circuits.dm and machine_circuitboard.dm
+
Replaces get_hearers_in_view with range for musical instruments.
+
Musical instrument range reduced from 15 to 11
+
Flashbangs now blind with an intensity of 2 meaning wearing glasses alone will still blind you. (You will not get stunned while wearing glasses).
+
Flashbangs effect is directly proportional to the distance away.
+
Fixes docking with lavaland.
-
-
28 September 2020
-
DatBoiTim updated:
+
Qwertytoforty AnCopper updated:
-
Adjusted Foreigner to be a Negative trait
+
Telekinesis can no longer throw objects with people in them, or buckled to them.
-
PowerfulBacon updated:
+
ike709 updated:
-
Fixes a clockcult map bug
-
Starting game... will now be announced closer to when the game is starting.
+
Custom emotes work again.
-
-
27 September 2020
-
Crossedfall updated:
+
ivanmixo updated:
-
Gangs is now disabled in the config
+
cyborg *spinning someone now knocks down instead of paralyzing
-
EdgeLordExe updated:
+
+
09 September 2021
+
fighterslam updated:
-
Added heretics to the config
+
Adds Corporate Sofas, an alternative, sleeker style of sofa.
-
Kmc2000 with Gun Jesus updated:
+
+
08 September 2021
+
KubeRoot updated:
-
Fully automatic firing for guns, except this time it isn't horribly coded and broken.
+
Silicons can once again interact with intercoms
+
Button.Input no longer breaks tgui interfaces. Fixes advanced injector creation interface, as well as NTOS file manager, chat, ID card modification and Revelation.
-
Moccha-Bee updated:
+
rkvothe14 updated:
-
the eighties floor not spawning.
+
decreased the font size cap from 32 to 24 to prevent chat bugs.
-
PowerfulBacon updated:
+
+
07 September 2021
+
Ivniinvi updated:
-
Changelings and cult will no longer be assigned invalid targets
-
Clockcult gamemode
-
Clockwork slab
-
Brass armory
-
brass spear
-
brass battlehammer
-
brass sword
-
The Eminence simplemob
-
Cogscarab drone subtype
-
TGUI clockwork menu
-
Clockwork traps
-
Clockwork scriptures, structures and slab powers
-
replica fabricator
-
integration cog
-
Rat'var the clockwork justicar
-
New Reebe
-
Clockcult has been completely rewritten from scratch, Reebe has been completely redesigned and it no longer crashes upon using camera consoles, winning, doing anything etc.
-
Adds clockcult to the config
+
Ghosts no longer get spammed with It's Loose notifications when there's no singulo or tesla.
28 random maint rooms by GizkaFreechman, edited by myself
-
Random maint traps
-
cluwne rune, which can be used by cultists in a manner similar to an invocation rune, if the Honkmother has blessed one of the invokers with her presence
-
new random maint job, the VIP. he's rich, apparently important, and actually probably quite useless.
-
sprites for new job and cluwne rune by thatguythere03
-
edited most random maint maps to make maint easier to get through (changing table placements and such)
-
faithless no longer has a 12% chance to stun for 6 seconds on hit
+
Fixed the syndicate lavaland base to have the proper frequencies to be compatible with Supercruise.
-
super12pl updated:
+
qwertyquerty updated:
-
Multilingual now costs 1 point instead of 2
+
Added new space ambient music
+
fixed ambient music and buzz
-
26 September 2020
-
Moccha-Bee updated:
+
06 September 2021
+
Ivniinvi updated:
-
multi-directional sprites to the arcade and 80s floor.
+
Moths can no longer consume the following items: Indestructible items, Items with Melee armor, Space Suits, Hardsuits.
+
The exploration nuke no longer plays alarm.ogg
-
-
25 September 2020
-
BeloneX updated:
+
KubeRoot updated:
-
Disabled the Gateway roundstart
+
Geysers no longer generate in ruins. In fact, nothing should generate in ruins, as ruins now generate before other things.
+
Due to changes in the way the map is setup, floors that are parts of ruins on lavaland loaded at roundstart will now deconstruct into lava instead of basalt floors.
+
When a template is loaded with a passthrough turf, non-passthrough area on it, onto a genturf, the genturf is now forced to generate with the old area. This allows ruins to have objects in a powered area on a passthrough turf on lavaland.
+
Passthrough turfs can now hint towards if the turf below should be closed/open (wall/floor). This can be useful in ruins, as it can influence map generation without overriding the exact turf placed underneath.
+
Sprites for passthrough turfs with closed/open preferrence (visible in editor only)
-
Vivalas updated:
+
PowerfulBacon updated:
-
Claiming a ticket now works directly through a reply.
+
Improves the supercruise collision detection by optimising it and making it so passing something triggers a collision event.
+
SSorbits now supports multiple orbital maps.
-
-
22 September 2020
-
Archanial updated:
+
Tavczan updated:
-
Deepfried pets no longer reappear after being bitten(why would you ever do that).
+
Abandoned crate tamper-proofing now detects EMPs.
-
DatBoiTim updated:
+
The-Moon-Itself updated:
-
Multilingual
+
Bitwise operations and Trigonometry related circuit components, unlocked in a new techweb node.
+
Rounding circuit component.
+
Arithmetic circuit now has a modulus option.
+
New abstract circuit to base components that initialize with an arbitrary amount of inputs.
-
-
21 September 2020
-
Archanial updated:
+
ivanmixo updated:
-
You can now pick new pai holoforms again.
+
chaplain's soulstone can no longer shard bodies with souls
-
-
20 September 2020
-
EdgeLordExe updated:
+
jupyterkat updated:
-
Eldritch Water metabs properly.
+
fixed mech radios
-
zeskorion updated:
+
+
05 September 2021
+
AnCopper updated:
-
lizards no longer get cockwork tails
-
macrophage works again
-
pink macrophages work again
-
biometallic replication and organic flux induction should scale with pituitary disruption again
-
antivirus no longer works on humans. (why do biotypes suck ass)
+
Communications consoles also work on the centcom z level
+
Added the AI lab room as a possible ruin room.
-
-
19 September 2020
-
eeSPee updated:
+
Crossedfall updated:
-
DRAGnets will now teleport targets at beacons designated for Dragnet portals.
-
Dragnet beacons printable at secfab.
-
Dragnet beacons in armory on every map.
+
The AI will have a random lawset at the start of each shift from the following list: Asimov, asimov++, crewsimov, corporate, maintain.
-
-
18 September 2020
-
Archanial updated:
+
Fox-McCloud AnCopper updated:
-
New immersion smite.
-
Bullteriers!
-
Breadcats! Ask your local chef for one!
-
You can now scoop and hold pets.
-
Warden has a new pet - Walter!
+
ashwalkers malf AI module has been moved to the syndicate lavaland base vault.
+
Heavily beefed up the security of the Syndie vault, added two more turrets and 3 layer walls.
-
Moccha-Bee updated:
+
Froststahr updated:
-
Renamed bubblegum and blood-drunk gps signals.
+
Mapmerge2 has been updated along with an experimental .dmm merge driver being included for conflict resolution.
-
St0rmC4st3r updated:
+
KubeRoot updated:
-
ID modification is now logged.
+
Radios and intercoms can no longer be toggled from a distance by non-silicons using hotkeys
+
Monkeys can now interact with wall-mounted intercoms
+
Radio and intercom UIs now update when toggled via hotkey
+
Bounty console now uses a tgui interface
+
There is a new "Bounty Hunter" program available for modular computers and tablets
+
SDQL2 now supports all subsystems.
+
Alarm Monitoring program changed to avoid always-on unclearable fire alarms
+
Hacking a firing pin out of a gun is no longer done via a crafting menu - you can now do it by simply holding the gun in your hand and clicking it with a welder/screwdriver/wirecutters
+
You are no longer prevented from selecting more neutral/negative quirks once you have reached the limit of 6 positive ones.
+
Fixed a scenario that allowed infinite resource generation via ore machines.
+
TGUI interfaces updating from ui_act will not update again due to ui_update
-
Victor239 updated:
+
LemonInTheDark, Port By Froststahr. updated:
-
Removed broodmothers from gold slime pool
-
Increased ATMOSfan max signs to 6
-
Shoulder holsters are now an accessory, not a belt.
+
The panic bunker allows for a minimum time to be set, blocking players who lack the required amount of living played time from accessing the game.
-
YoshimiWasTaken updated:
+
PowerfulBacon updated:
-
Should have the sprites now
-
added the damn sprites
+
Removes the bolt of death and bolt of adminheal from xenobiology space carps (Chaos + Regular)
+
Bag of holding's can no longer teleport their contents through walls when dumped.
+
Fixes gamemode presetup being called late.
+
Fixes cult having a random chance to fail to setup between 24-28 players.
+
Cryo syringes no long have the no react flag.
+
Reagents inside cryo syringes will be at 20 kelvin.
+
Heavy weapons require two hands to hold.
+
Light/medium weapons will be less accurate when fired with 1 hand.
+
Refactors meteors, they are now orbital map components and will fly towards the station, colliding with orbital bodies and flying shuttles.
-
eeSPee updated:
+
francinum updated:
-
Recharging station crate purchasing from cargo consoles
+
Ore Silo connections can no longer cross z-level boundaries, and will break if the object changes z-lvels.
ivanmixo updated:
-
Replaces the firelocks with windoors in the public garden on MetaStation
+
all syndicate boxes now look like syndicate boxes
+
replaced some of the more egregious spawn()s with timers
+
removed the separated chemicals trait from botany
-
super12pl updated:
+
lordScrubling updated:
-
lizards can now scream
+
having x-ray makes you ignore flash protection and tint
+
flashbangs can flash people with x-ray through walls
+
syndicate autosurgeons get a special variant of eyes that allow flash protection
-
zeskorion updated:
+
lordScrubling, Mothblocks, Rohesie updated:
-
nerfed toe stubbing
-
explosions have been fucked with a bit
+
placing fireman carried people onto things
-
-
17 September 2020
-
BeloneX updated:
+
qwertyquerty updated:
-
All projectiles now move 1.25 times as fast.
+
Nuclear countdown music
-
Naevii updated:
+
tiramisuapimancer updated:
-
Updated HoS cloak sprite
+
IPC hands are now properly visible over jumpsuits on side states
-
r1ks-iwnl updated:
+
+
04 September 2021
+
Ivniinvi, TheChosenEvilOne updated:
-
Adds silver sulfadizine to synthflesh recipe.
+
Birdboat now has a chance to be controllable by dead chat, think twitch plays pokemon but goose and deadchat.
+
Deadchat controlled singularity variant.
-
tiramisuapimancer updated:
+
Ivniinvi, bobbahbrown updated:
-
Felinids are now immune to carpotoxin and can have little a fish
-
Felinids like meat and dairy, and dislike vegetables and sugar.
+
Add Requests Manager to view all prayers/centcom and syndicate requests/and nuke code requests within a round.
-
-
16 September 2020
-
Crossedfall updated:
+
KubeRoot updated:
-
Acid properly degrades armor
-
You can empty storage containers/boxes directly into disposals again
-
The mutations overlay will update correctly now
+
TGUI interfaces have been manually tweaked across the board to update more consistently
+
Some potential exploits related to TGUI input validation/sanitisation have been fixed
+
Nanite cloud control now shows rules even when you don't have a disk with valid rules inserted
+
Extra settings for numbers in nanites now treat 0 as a valid number.
+
Cargo express console now doesn't add a second order to the cargo console cart. As a side-effect, it also doesn't print a requisition form.
+
The holodeck computer will no longer break when reenabling safety
+
Chemistry heater no longer stays on when removing beaker with alt-click
+
Added signals for machine_open and machine_close
+
References to IRC now better describe TGS' and Discord's existence.
+
Players will now be better informed that messages are sent through TGS to IRC/Discord/etc when attempting to adminhelp with no available admins/use adminwho verb.
+
Initial ahelps are now multiline too.
-
15 September 2020
-
ivanmixo updated:
+
03 September 2021
+
Ivniinvi updated:
-
The old science research station ghost roles now have a proper title
+
Coffee the Crab is now present in BoxStation Science
+
Birdboat the Goose is now present in DeltaStation, MetaStation, CorgStation, and PubbyStation maints
+
Lia the Carp is now present in the CorgStation HoS office
+
Cayenne the Carp is now present in the traitor deployable capsule shuttle
+
Tom the Turkey is the mascot of the Exploration Crew, and appears on the three exploration shuttles.
+
Debtors and Assistants can now purchase a Bottle of Mystery Pills for 3TC.
-
-
14 September 2020
-
ExcessiveUseOfCobblestone(Idea by Arathian) updated:
+
MNarath1 updated:
-
Mutation tox now has a minute window where you can purge the chem to avoid being changed (It functions EXACTLY like old slime mutation toxin)
-
Slime Mutation Toxin is replaced by Mutation Toxin since they now function 100% the same. Think of this as just a name change!
+
fixes my own mistake on instruments
PowerfulBacon updated:
-
Spraycans with less than 2 but more than 0 charges left can no longer be used on objects
+
You can now upload to AIs on different station floors.
+
Fixes meta comms, will proper fix it tomorrow.
+
Runechat not displaying messages if you can't hear it rather tahn if they can't hear it.
+
Changes exploration mainframes to proper exploration communication setups on pubby and metastation.
-
ike709 updated:
+
yyzsong updated:
-
PanDEMIC 2200 UI now includes severity for the overall disease and each symptom.
+
Randomly generated names can no longer have skywalker as a last name
-
ivanmixo updated:
+
+
02 September 2021
+
DatBoiTim updated:
-
You can't order stuff from the express supply console as a ghost anymore
+
Tweaked Mining Webbing to also allow storage of exploration gear due to also being available to explorers
+
More exploration gear fits into webbing
-
zeskorion updated:
+
Inithis updated:
-
vehicles no longer block bullets for you
-
mobs which pass tables can move past vehicles
+
added nitrile gloves to the Paramedic's default, roundstart loadout.
-
-
12 September 2020
-
St0rmC4st3r updated:
+
Ivniinvi updated:
-
A clearly illegal slaughterhouse to the west of the research division on boxstation has been permanently removed.
-
An actual medbay with treatment separation and more than one surgery room has been installed in the place of the removed slaughterhouse.
+
You can now print multiple Wanted or Missing posters.
+
Clockwork cultists and Cogscarabs will no longer be stuck in an infinite teleport loop.
-
-
10 September 2020
-
Dingo-Dongler updated:
+
Kapu1178 updated:
-
You can fold origami properly now.
+
IPCs are no longer deaf when EMP'd, and are now forced to speak spanish for 2 minutes.
-
Victor239 updated:
+
MNarath1 updated:
-
Security webbing has +1 slot compared to sec belts again, and sec belts allow you to use all slots again.
+
fixes mobs not beeing able to hear instruments in lockers
Equipment no longer disappears when you try to install it in the wrong type of mecha.
-
07 September 2020
-
PowerfulBacon updated:
+
01 September 2021
+
AnCopper updated:
-
Fixes a cargo bug where items wouldn't be sold if placed in an anchored container
+
budget cards are indestructible.
-
zeskorion updated:
+
EvilDragonfiend updated:
-
biometallic replication has slightly better stats, buffs preexisting robot limbs
-
TRD now works on mechanical limbs as intended
-
fixed exolocomotive xenomitosis capitalization and healing threshold
-
exolocomotive xenomitosis now works on monky
-
pink zombies and macrophages, so exolocomotive xenomitosis can be more aesthetic
-
organs take three times as long to decay
+
Exploration Crew has their HUD icon now.
+
Exploration Crew job can be given from the HoP console.
+
Job selection in the HoP Console is now tidily sorted by department.
+
When you grant a custom job "Acting Captain", now it gives you a better blank HUD icon. (Captain HUD without star)
+
+
Ivniinvi updated:
+
+
RD now has exploration access
+
Battle Royale loot has had some loot buffs, most notably the spawn rate of loot drops has doubled and the landing time of packages has been decreased.
-
-
06 September 2020
PowerfulBacon updated:
-
Cult stun now works on mindshield instead of hearing protection
+
Cogged APCs will now report 100% charge on power monitors.
+
Fixes corgstation disposals and bar disconnect from powergrid.
+
Fixes QM headset having exploration access.
+
Fixes custom shuttles being unable to dock.
+
Entering the portal in the center of the shadow labrynth will return you to a random anomalous artifact.
-
ivanmixo updated:
+
+
31 August 2021
+
Ivniinvi updated:
-
The ashwalker's bonfire does not require oxygen to burn anymore.
+
The obviously fake nuke disk is now an arcade prize.
+
The round report in #ooc on Discord now includes a summary for Dynamic Mode.
+
Dynamic round report in #ooc no longer has extraneous html bold tags
+
The departmental budget's pre-initialize name has been changed.
-
kriskog/Fikou ported by Archanial updated:
+
KubeRoot updated:
-
Omnitool.
-
Debug outfit has been updated and adjusted.
-
Admin RCD is superfast now and has upgrades.
+
Central Command has identified and fixed issues with pod launching interfaces.
+
Broken computers no longer glow in the dark
+
Seed Extractor and Plant DNA Manipulator now use tgui
+
Plant DNA Manipulator has a checkbox that skips confirmation prompt for rapid botanying
-
zeskorion updated:
+
Phil Smith updated:
-
reworked phobias
+
The spriteless jumpsuit in sec maints in deltastation is now a real one
-
-
05 September 2020
-
Archanial updated:
+
PowerfulBacon updated:
-
Shotguns now blow people away at point blank.
+
Fixes finger gun allowing free movement in space
+
Adds in AI buttons to move their camera to above and below connected z-levels.
+
Sounds now play across multi-z station levels.
+
Explosions now explode across multi-z station levels.
+
Fixes blood brothers admin add
+
+
PowerfulBacon (Code, UI, Other sprites not mentioned, Other map files not mentioned), Reds88 (Vortex Gun Sprite), Isy (Damaged hallway map files) updated:
+
+
Adds in randomly generated ruins
+
Adds in dynamic z-levels.
+
Adds in ruin beacons on the shuttle supercruise map.
+
Adds z-clear
+
Ruin mapsize validation unit test
+
Fixes fastdmm2 not compiling on beestation code due to the updates.
+
Budget cards no longer get free money.
+
Bounties reward 3x as much, but the reward is distributed among station budgets.
+
Paychecks have been slightly reduced.
+
Station budgets now start with more money to compensate early game when no objectives or bounties have been completed.
+
Research disks can be found on ruin stations. These can be used to unlock special research.
+
Bags of holding are now a separate research node unlocked by finding disks on derelict stations.
+
Wormhole projectors are now a separate research node unlocked by finding disks on derelict stations.
+
Quantum spin inverters are now a separate research node unlocked by finding disks on derelict stations.
+
Advanced combat cyber implants are now a separate research node unlocked by finding disks on derelict stations. (Hydraulic armblades)
+
Combat cybernetic implants implants are now a separate research node unlocked by finding disks on derelict stations. (Xray, thermal vision, anti stun implants, thrust implants)
+
Clusterbangs are now a found research disk.
+
Phazons are now a found research disk.
+
Exotic ammo is now a found research disk.
+
Beam rifles are now a found research disk.
+
Temp gun and X-ray gun is now a found research disk.
+
Nuclear energy gun is now a found research disk.
+
Completion station missions reward money to budget cards.
+
Adds in a new mining scanner effect.
+
Refactors vendors to be modular
+
Adds discovery research
+
Adds discovery scanner
+
Felinids get stunned when falling z-levels for the same length of time that normal humans get knocked down.
+
You will no longer fall down if the turf below you is space.
+
You can now ascend and descend connected z-levels by clicking on the space tile.
+
Off-station traitor roles have a prefered chance (30%) to have their team members as targets (Shaft miners and exploartion crew.). Additionally, on station traitor roles have a reduced chance to have their target as an off station roles.
+
Turrets can now shoot above themselves, preventing the nukie shuttle from being boardable from above.
+
Bolt of teleportation will teleport people to open turfs unless none are available.
+
Immovable rod no longer loops.
+
Box station no longer has the crab.
-
St0rmC4st3r updated:
+
francinum updated:
-
Bartender quits buying new monkeys for every shift. Pun Pun is back!
+
Bans now properly create their relevant notes.
+
You should probably manually unexpire any note with an expiration date of 0.
-
super12pl updated:
+
lordScrubling updated:
-
Adds sterilizer spray to robodrobe
-
Adds morgue to robotics on DeltaStation and PubbyStation
+
Ability to see a tag with the armor values of a piece of clothing when you inspect it
-
04 September 2020
-
BeloneX updated:
+
30 August 2021
+
ImSynthex updated:
+
+
Changed the corporate lawset
+
+
KubeRoot updated:
-
replaces the glass in the labor camp with reinforced plasma glass.
-
fixed the paper wizard den so everything doesnt instantly die from low pressure anymore.
+
Solar control screen no longer floats off the console when facing east. Solar control screen is now also emissive, like other computers.
-
yorii updated:
+
+
29 August 2021
+
ike709 updated:
-
fixed ethereal charging
+
Removed an admin privilege elevation exploit and other potential avenues for abuse
diff --git a/html/changelogs/.all_changelog.yml b/html/changelogs/.all_changelog.yml
index d8ec91b21c4b1..944193b50d0df 100644
--- a/html/changelogs/.all_changelog.yml
+++ b/html/changelogs/.all_changelog.yml
@@ -28003,3 +28003,4428 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
- rscadd: Trigger build wire on autolathes
- rscadd: Autolathe queue
- rscadd: Autolathe ejection direction.
+2020-11-06:
+ Autisem:
+ - bugfix: assembly's now can be interacted with while inside other objects like
+ TTV
+ Victor239:
+ - rscadd: The name and job of the announcer are now shown on announcements.
+ zeskorion:
+ - code_imp: pepper proofing now checks eye and mouth covering instead of a snowflake
+ flag
+ - bugfix: spraypaint now uses eye covering instead of flash protection
+2020-11-07:
+ Archanial:
+ - rscdel: Major and minor crimes are gone.
+ - tweak: Adding a crime recond in sechuds in only 1 pop-up now.
+ - rscadd: You can add details to a crime separately.
+ - refactor: Cleaned up sec console code and hud glasses code.
+2020-11-08:
+ Archanial:
+ - tweak: Cyborgs no longer get confused after getting flashed/flashbanged.
+ PerishedFraud:
+ - rscadd: Some new items in the mining vendor.
+ - tweak: Some prices changed in the mining vendor, can also eat through explorer
+ mask.
+ eeSPee:
+ - tweak: Guns no longer use changeNext_move
+ park66665:
+ - bugfix: 'no more with exponential thermal energy, fusion now correctly produces
+ heat: supply plasma to your nuclear fusion ''reactor'', dammit!'
+ - balance: in an attempt to preserve fun, I tweaked some numbers and logics in fusion
+ to create some healthy fun
+ - rscdel: e30 fusion
+ - balance: rad now scales with fusion temperature, which means that you will get
+ meager rads if you are bad at fusion (e.g. canister fusion), or stronger rads
+ than before if you manage to keep a strong and robust fusion
+ - rscadd: in order to help you gauge the strength of your fusion, fusion-derived
+ nuclear particles (aka radballs) are color-coded; and adjective-coded, so you
+ can also gauge how damned you are after being hit by a radball
+ - refactor: lightly refactored fusion code in general
+2020-11-09:
+ That0nePerson:
+ - rscadd: Adds weed killer spray to janiborg
+ - rscadd: Adds acid spray to emagged janiborg
+ - refactor: Refactors cyborg spray bottles
+ super12pl:
+ - balance: Makes searing tools small sized item
+2020-11-10:
+ LemonInTheDark:
+ - bugfix: You will keep your angle when you drift through space now. Oh and immovable
+ rods do the same, so things might get a bit more messy then usual.
+ TheChosenEvilOne, Skoglol, Cenrus, and others:
+ - bugfix: Dynamic mode fixes
+ - bugfix: Revolution ending dynamic earlier than expected
+ - rscadd: Clockwork Cult and Abductors have been implemented into dynamic mode
+ - rscadd: The revolution gamemode will no longer be selected if there are no head
+ of staff players
+ - rscadd: Dynamic added to secret rotation
+2020-11-11:
+ Archanial:
+ - admin: More botany logging.
+ Naevii:
+ - tweak: Updated RD cloak sprite
+ PowerfulBacon:
+ - rscadd: Nanotrasen battle royale.
+ - tweak: Replaced syndicate playing cards with a maintenance loot spawn.
+ - rscadd: Adds small recoil to shotguns.
+ - tweak: Integration cogs suppress APC power warnings.
+2020-11-13:
+ PowerfulBacon:
+ - bugfix: Fixes statues being able to move while looked at.
+ ike709:
+ - tweak: Updated extools
+2020-11-14:
+ ? ''
+ : - tweak: removes access levels from VIP
+ Archanial:
+ - rscadd: Teratomas are actual antagonists now!
+ - rscadd: You can now make someone teratoma in traitor panel.
+ - tweak: Teratomas cannot be made into a human with DNA sting or by mutadone!
+ - refactor: fixed some shitcode in monkey (who cares).
+ Isy232:
+ - rscadd: Botany's biogenerator can now produce toolbelts.
+ MNarath1:
+ - bugfix: Fixes Abductor locker to be animated and animates the alien locker
+ Phil Smith, Golgor:
+ - rscadd: The Crazy Hamburger recipe
+ - imageadd: Crazy Hamburger sprite by Golgor
+ Victor239:
+ - balance: Absorbing another changeling made more rewarding again.
+ - tweak: Fleshmend is now an active ability rather than passive heal again.
+ - tweak: Augmented Eyesight now blocks flashes again.
+ - tweak: Organic Flesh suit now only costs 1 point, has less slowdown and is armoured.
+ - rscadd: Radios now make noises when you use them
+ jupyterkat:
+ - bugfix: Stasis beds now stop diseases from progressing
+2020-11-15:
+ BeloneX:
+ - tweak: xeno queens can smack the emergency shuttle console to clear the xeno hostile
+ environment rather than waiting it out
+ - tweak: Xeno queens will delay the escape shuttle for a while unless they are killed
+ - tweak: the shuttle will get auto called at the end of the timer if the queen is
+ alive and it hasn't been called already to prevent super xeno extended rounds
+ Ohagi-Chan:
+ - tweak: Science outpost doors now allow miners to travel a little more freely.
+ - tweak: Hermits no longer wake up to discover that their cave is flooded with toxic
+ gas.
+ PowerfulBacon:
+ - bugfix: Red and blue cubes respect no_teleport
+ - rscdel: SRM-8 Missile Rack can no longer be printed.
+ - bugfix: Reebe void is now jaunt proof
+ - bugfix: Prosperity prisms now heal servants.
+ Victor239:
+ - tweak: Alloys are now given their proper names in the techfab.
+2020-11-16:
+ ike709:
+ - bugfix: Fixed being able to put intercoms and other anchored objects inside of
+ trash bags.
+ qwertyquerty:
+ - bugfix: Fixes ambience runtime
+2020-11-17:
+ Archanial:
+ - bugfix: Brig Physician can no longer be a traitor.
+ Crossedfall:
+ - tweak: Updated the rust-g dependency
+ Froststahr:
+ - bugfix: Station goals are buyable again
+ - rscadd: You also get a centcomm report telling you what the goal is, in absence
+ of the intercept report.
+ PowerfulBacon:
+ - bugfix: Fixes a runtime issue with invalid research design disk.
+ Sinmero:
+ - tweak: Round will not end the instant all heretics are dead
+ - tweak: Adds heretics to the endgame credits
+ eeSPee:
+ - bugfix: you no longer get IC muted for forced speech
+ qwertyquerty:
+ - tweak: Explosions throw farther
+ - tweak: Explosions throw all movable atoms
+ r1ks-iwnl:
+ - rscadd: Runechat text will be displayed even when inside another object such as
+ lockers or crates.
+2020-11-18:
+ 'Azarak, Zandario ':
+ - rscadd: PDA now have sounds.
+ PowerfulBacon:
+ - admin: Allows resizing of the ticket messenger
+ uomo91:
+ - bugfix: Fixed "Show All" tab in player panel logs being broken.
+ - bugfix: Whispers, OOC, and various other things display differently in logs, visually
+ distinguishing them from say logs.
+ - refactor: Player panel logs will now show all logs chronologically, so you'll
+ see commingled say and attack logs if you're on the "Show All" tab, etc...
+2020-11-19:
+ FriendlyContractor:
+ - tweak: Radio jammer is now part of contractor kit standard loadout
+ - tweak: Tweaks contractor kit starting items pool by removing useless items and
+ adding some new
+ 'TheChosenEvilOne ':
+ - bugfix: Dynamic midround traitor triggered by roundstart traitor ruleset no longer
+ ignores protected roles.
+2020-11-20:
+ Archanial:
+ - admin: Setting default gamemode now requires dbranks.
+ Crossedfall:
+ - server: libmariadb.dll is no longer a required static file
+ Isy232:
+ - balance: Departmental budget money increases are now static and are no longer
+ exploitable.
+ MNarath1:
+ - rscadd: animated crates
+ PowerfulBacon:
+ - tweak: Radios now only play to the person wearing the headset / holding the radio.
+ Victor239:
+ - rscadd: Glass shards can now be made by using glass in-hand.
+ eeSPee:
+ - tweak: recycler will no longer grind down raw/material stacks
+ francinum:
+ - rscdel: Exolocomotive Xenomitosis has been removed.
+2020-11-21:
+ KreeperHLC:
+ - tweak: AltClick on things that use AltClick no longer opens the contens of the
+ turf
+ PowerfulBacon:
+ - tweak: Eminence no longer blocks bullets.
+ - bugfix: Autolathe nolonger opens when something is printed.
+ francinum:
+ - balance: Mindswapping with a slaughter demon is a terrible idea.
+ jlsnow301:
+ - rscadd: Confirmation dialogue on harvesting replica pod seeds without soul
+ - rscadd: You can now scan replica pod seeds with an analyzer (plant/forensic) to
+ read the injected DNA
+ - bugfix: Removed a nonfunctional alert in this process
+2020-11-22:
+ Archanial:
+ - bugfix: You can't remove syringes while dead.
+ Nathan_does_voices:
+ - rscadd: New arrival sound made by Nathan_does_voices (be sure to look him up on
+ instagram or tik tok!)
+ PowerfulBacon:
+ - rscadd: Ratvar now fights Narsie
+ - rscadd: Cult will not end the round on summon if clockcult exists.
+ - tweak: Becoming a clockie counts towards the sacrifice objective.
+ - bugfix: Report of clockcult shows 180 seconds instead of 1800 seconds
+ - bugfix: Removes the delay between clockwork gateway opening and rat'var appearing
+ Victor239:
+ - bugfix: Fixes not being able to use syringes on arms and legs whilst wearing an
+ armour vest.
+ eeSPee:
+ - balance: cult floor is atmosproof
+ - balance: artificer magic missile no longer stuns cultists
+2020-11-24:
+ MNarath1:
+ - bugfix: invisible Crates
+ jupyterkat:
+ - bugfix: revs and engraved messages can't get irradiated anymore
+2020-11-25:
+ Chessus:
+ - tweak: Tweaked Dexlin
+ PowerfulBacon:
+ - bugfix: Chat will no longer linkify links unless specified to
+ Triiodine:
+ - rscadd: Added 16 new random maintenance rooms, 8 10x10s, and 8 10x5s
+ park66665:
+ - bugfix: fixed a minor /datum/looping_sound-related memory leak
+2020-11-26:
+ EdgeLordExe:
+ - rscadd: Dominant Adamantine Extracts!
+ - rscadd: Grey - Slowly feeds all slimes in the area, effect blocked by walls
+ - rscadd: Orange - Ignites anyone in range of the crystal, heats surrounding air
+ to 500k. Effect blocked by walls
+ - rscadd: Purple - Restores 1 of a random damage type to everything in range every
+ time it ticks, including brain and cell damage
+ - rscadd: Blue - Stabilizes atmos in effect range to the standard O2/N2 mixture,
+ and stabilizes temperature to 300k. Effect blocked by walls
+ - rscadd: Metal - just like Purple but affects only borgs
+ - rscadd: Dark Purple - Consumes plasma in the air, converting it into plasma sheets.
+ Crystal releases and ignites a small amount of plasma when destroyed
+ - rscadd: Dark Blue - Cleans and dries tiles in the area
+ - rscadd: Silver - Plants grow slightly faster, and prevents pests from growing
+ at all.
+ - rscadd: Bluespace - Acts as a beacon to other crystals of this type. Click with
+ an empty hand to teleport between them
+ - rscadd: Sepia - Everything in the area is under the effect simmiliar to Stasis
+ (Real stasis is a hardstun)
+ - rscadd: Cerulean - Adjacent tiles will gradually grow slime crystals, up to a
+ maximum stack of 5 in each tile. slime crystals may be combined with any material
+ sheet to increase its stack size by 5.
+ - rscadd: Pyrite - Causes floor tiles to be randomly colored within area of effect
+ - rscadd: Red - Crystal cleans blood from the ground in a wide radius, and may store
+ up to 300u of gathered blood. Crystal may be interacted with to consume some
+ blood and create a piece of "meat" or a random, functional organ. Containers
+ may also be used on the crystal to transfer blood directly out of it.
+ - rscadd: Green - Crystal stores one random mutation from the last player who interacted
+ with it (starts with none). Players standing within the effect radius are gradually
+ cured of mutations, but will be forcefully given the mutation stored in the
+ crystal
+ - rscadd: Pink - Everyone within range of the crystal is pacified (effects identical
+ to pacifist trait).
+ - rscadd: Gold - Click crystal with empty hand to be transformed into a random pet-type
+ simplemob. Leave crystal's area of effect to revert
+ - rscadd: Oil - Lubes tiles within area of effect.
+ - rscadd: Black - Players standing within two tiles of the crystal will begin slowly
+ transforming into a random slimeperson. Has no effect on slimepeople.
+ - rscadd: Adamantine - Makes everyone slightly more resistant in an area around
+ it.
+ - rscadd: Rainbow - Does nothing on its own, but will accept Crystalized extracts
+ to begin emitting the same effect as the consumed extract, allowing for multiple
+ different effects in the same space
+ - bugfix: fixes adamantine crossbreeds
+2020-11-27:
+ EdgeLordExe:
+ - bugfix: further fixes adamantines as per documentation in the bounty.
+ park66665:
+ - bugfix: stack max_amount is now enforced more thoroughly
+2020-11-28:
+ Froststahr:
+ - rscadd: Emags allow traitors to anonymously purchase any shuttle they want without
+ ID! Screwdrivers can be used on circuit boards as well, but adequate access
+ is still required in that case.
+ - tweak: The name of the authorizer, not the buyer, is printed on purchase of a
+ shuttle.
+ - tweak: Unsafe shuttles are now unavailable for purchase without emags/hacking.
+ LastCrusader105:
+ - tweak: tweaked piping
+ - bugfix: fixed chapel mail system
+ PowerfulBacon:
+ - rscadd: Ambient fading
+ - rscadd: Sound effect subsystem
+ SmArtKar:
+ - imageadd: Added old engine floor icon_state
+ jupyterkat:
+ - admin: restart votes check for active deadmins
+2020-11-29:
+ IndieanaJones:
+ - rscadd: Blob Zombies can now be controlled by Ghosts
+ - tweak: Blobs cannot understand or speak languages anymore and are default to blob
+ hivemind so no .b anymore for blobber or blob zombie
+ - balance: Makes Blob structurs Lavaproof so no blob kill with lavastaff
+ - tweak: Blob announcement now at size 75 or after 10 minutes
+ Jakecantcode:
+ - rscadd: Added charcoal pen that can be crafted from ash and wood
+ bloons3:
+ - tweak: changed admin logging for items
+ francinum:
+ - rscdel: Skewium has been removed.
+ super12pl:
+ - config: Removes Jungle Fever from rotation.
+2020-12-01:
+ MNarath1:
+ - admin: Admins now get a message if the AI or a borg decides to delete Crew mainfest
+ entries
+ ike709:
+ - refactor: A bunch of mob-related code has been improved.
+ - tweak: For performance reasons, animals will only attempt to mate at a maximum
+ rate of once every 5 minutes.
+ - tweak: For performance reasons, cats will only kill mice or play with toys when
+ they move to tiles with mice or toys.
+ - tweak: For performance reasons, Kalo's cleaning range has been halved to 5 turfs.
+ I've also lowered the rate at which he finds things to clean.
+ - tweak: For performance reasons, Cak will only frost a donut when entering a turf
+ with a donut.
+ - tweak: Several other performance tweaks.
+2020-12-02:
+ ? ''
+ : - tweak: Doors are smoother
+ Froststahr:
+ - bugfix: The white changeling icons are now fixed and returned to their codersprite
+ glory.
+ MNarath1:
+ - bugfix: fixes a back sprite icon that was not properly mirrored
+ - bugfix: minecart beeing animated
+ - refactor: refactored some code regarding the crate animation to use actual sphere
+ coordinates instead
+ - bugfix: fixes (hopefully) the broken looking crate animation
+ PowerfulBacon:
+ - rscadd: Traitors now have a low chance of spawning on heretics (60%), devil (15%).
+ - code_imp: Minor antagonist roles can now be configured to optionally use antagonist
+ rep
+ TemporalOroboros:
+ - code_imp: Repathed screen objects
+ That0nePerson:
+ - rscadd: Adds a communication implant
+ - rscadd: Blood brothers get a communication implant to their team roundstart
+ Tiviplus, Rohesie, ninjanomnom, ike709, park66665:
+ - refactor: converted decals from components to elements
+2020-12-04:
+ Alt_Alpha9:
+ - bugfix: If multiple holidays are active, the station name may be derived from
+ any of them instead of a single one.
+ Sinestia:
+ - bugfix: shuttle list hides dangerous shuttles properly now
+ francinum:
+ - admin: VV now works more reliably
+ - bugfix: VV can now show the panel of objects with invalid iconstates
+ park66665:
+ - bugfix: layer manifolds are now saner
+2020-12-05:
+ Alt_Alpha9:
+ - tweak: The damp rag can now clean microwaves
+ MNarath1:
+ - bugfix: Radiation filter breaking airlock filter
+ PowerfulBacon:
+ - rscdel: Removes unconciousness from mech brute attacks
+ eeSPee:
+ - bugfix: limbsnakes properly disember limbs
+ - tweak: Chimeracost doubled
+ - tweak: biodegrade cost halved
+ park66665:
+ - bugfix: fixed some memory leak and harddels in atmos pipenet code
+ - bugfix: improved performance by a tiny bit
+ - bugfix: fixed mini-sized bugs
+ - code_imp: code is a bit saner
+2020-12-06:
+ MNarath1:
+ - bugfix: fixes reinforced floor examine runtime
+ Naevi:
+ - tweak: The BeeStation BYOND logo is festive now
+ francinum:
+ - bugfix: No children allowed.
+2020-12-07:
+ Alt_Alpha9:
+ - tweak: Unlicensed PDA now has a charcoal stylus instead of those fancy pens
+ Kmc2000 with BadminJenkins:
+ - rscadd: Added a new roundend sound
+ ike709:
+ - balance: Reduces bumpslams to a 10% chance. The 10% chance has a 5 second cooldown
+ if it fails or a 20 second cooldown if it passes (aka you can't get bumpslammed
+ successfully twice in 20 seconds).
+2020-12-08:
+ Vasily2013:
+ - bugfix: optimizes the CC z level to load a bit faster.
+2020-12-09:
+ Archanial:
+ - rscadd: Delta's AI sat now fastmos compatible.
+ - rscadd: Fixed some AACs not working.
+ - rscadd: Minor improvements to box's AI sat.
+ - rscdel: Removed double stacked windows on pubby's chapel.
+ - rscadd: More firelocks in pubby's chapel.
+ - rscadd: Added tiny fans to ferries.
+ - rscadd: Windows in labor camp are plasma (again).
+ - bugfix: APC in labor camp is no longer floating mid air.
+ - bugfix: Generic computers are replaced with computer frames in science outpost.
+2020-12-11:
+ Alt_Alpha9:
+ - tweak: hitting yourself with items now makes grammatical sense
+ - tweak: The name of a beaker is now displayed when someone tries to feed you it
+ Archanial:
+ - admin: Admins can now easily see if the objective is currently completed in TP
+ (objective's text will be green).
+ BeloneX:
+ - tweak: Removes health analyzer limb specific damage toggle
+ Blueturbo47:
+ - tweak: Fixed a firelock placement.
+ Froststahr:
+ - admin: quantum spin inverter is now logged
+ Kerbin-Fiber:
+ - tweak: Mentor Follow has been removed. Never even worked.
+ MCterra10:
+ - bugfix: Protected roles no longer get midround antags
+ PowerfulBacon:
+ - rscadd: Coderskirt
+ - refactor: Refactors get contributors code to only get the list once and store
+ it as a static variable.
+ - bugfix: coderskirt
+ - bugfix: black slime core will now kill all mobs it is used on
+ Sinmero:
+ - tweak: Ctrl-Shift-clicking station bounced radio/intercom will toggle the speakers.
+ - tweak: Alt-clicking station bounced radio/intercom will toggle broadcasting.
+ park66665:
+ - bugfix: Protected roles really should no longer get antag at dynameme rounds
+ - bugfix: fixed quite a lot of pipes on every map (except Kilo which only had one
+ trivial problem) to improve experience and performance
+ qwertyquerty:
+ - rscdel: ike hates shitcode, coderskirt removed
+2020-12-12:
+ Ruko, Isy232:
+ - balance: Styptic powder, Synthflesh and Silver Sulf now do nothing when splashed.
+ - balance: Styptic powder and Silver Sulf now do 0.5 healing per tick and 1 when
+ instantly applied but their OD threshold is upped to 100u and the metabolism
+ rate is increased to 1u per tick. A 40 Styptic powder patch will heal 40 to
+ the limb it is applied to and then 20 damage over 40 ticks. They also do stamina
+ damage equal to the amount applied x2, the medicine is painful so this is pain
+ damage.
+ - balance: Atropine's brute, toxin and fire damage healing is doubled and it only
+ looses its effectiveness at 80 damage instead of critical condition. Atropine
+ also now causes dropping of items in its confusion state so its no longer suitable
+ for combat use and its overdose threshold is lowered to 15u. Overdosinng on
+ atropine now causes atropine to become histamine in a 2 to 3 reaction.
+ - balance: Bicardine, Kelotane, Dexalin and Anti-toxin now heal 1.5 per tick while
+ below 50 damage and 0.5 per tick when above 50 damage. This makes them no longer
+ as effective on large wounds but they are quite capable of healing smaller ones.
+ Their metabolism rate has also been halved so they stick around longer.
+ - balance: Salicyclic acid, Dexalin Plus and Oxandrolone now heal 3 damage per tick.
+ They also do 2 stamina damage per tick up to 80. They now only do stamina damage
+ while healing.
+ - balance: Anti-toxin no longer purges chems and only heals toxin damage. Use charcoal
+ or something.
+ - balance: The overdose of most chems now causes organ damage instead of just reversing
+ the effects of what the chem healed.
+ - rscadd: Adds bicaridine/kelotane pill bottles to burn/brute treatment kits.
+ - balance: Brute and burn patches now contain 30u of their reagents instead of only
+ 20.
+ - balance: Perfluorodecalin now causes more toxin damage as a side effect, overdoses
+ at 30u and causes lung damage upon overdose.
+ - balance: Salbutamol can now cause an overdose. The threshold is 25u (I made salbutamol
+ pills have less u to avoid OD on them). This overdose causes Sulbutamol to become
+ histamine in a 1 to 1 reaction.
+ - balance: Corazone now heals the heart by 1.5 per tick, however it now has an overdose
+ threshold at 20u and if you overdose it will turn into histamine in a 1to 1
+ reaction.
+ - balance: Carthatoline now heals 3 toxin loss per tick and its OD causes brain
+ damage. Its overdose threshold is now 25u.
+ - balance: Leporazine now has an overdose at 30u. This overdose causes your temperature
+ to rapidly increase or decrease randomly.
+ - rscadd: You can now find bicaridine and kelotane pill bottles in medical vendors.
+ park66665:
+ - bugfix: improved decal performance
+ - bugfix: decals no longer disappear on shuttle move
+2020-12-13:
+ That0nePerson:
+ - rscadd: Adds Cookie's Homemade Rum to the maint loot table
+2020-12-14:
+ eeSPee:
+ - tweak: Xeno abilities tell their plasma cost in the description.
+ ike709:
+ - bugfix: Fixed the stray bullet hallucination runtiming.
+ - tweak: Stray bullet hallucination is less frequent.
+2020-12-15:
+ Archanial:
+ - rscadd: Aux base access.
+ - tweak: Curator no longer has construction access.
+ NotRanged:
+ - rscadd: Added a long range atmos scanner
+ Ryll/Shaps:
+ - bugfix: Fixed an issue with underwear sometimes showing through clothes
+ nemvar:
+ - bugfix: fixed atmos component code (again)
+2020-12-16:
+ PowerfulBacon:
+ - rscdel: Circuit atmospheric modules.
+ bloons3:
+ - admin: increased emergency shuttle verbosity logging
+ ike709:
+ - bugfix: Players will no longer be popcap redirected if they have a mob.
+ oranges:
+ - rscadd: Added ANZAC day
+2020-12-17:
+ Blueturbo47:
+ - bugfix: Some of the maint rooms were marked as the wrong area.
+ bloons3:
+ - admin: Binary chat messages are now marked as such in logs.
+ - tweak: You can now only use the emergency shuttle console once per 5 seconds.
+2020-12-18:
+ That0nePerson:
+ - bugfix: Fixes drying rack not transfering reagents
+ qwertyquerty:
+ - balance: Power usage gameplay difficulty curve
+ - rscadd: Zoom in keybind (set to ] by default, unbound if you have played before
+ and you'll have to set it up in keybind prefs)
+2020-12-19:
+ MNarath1:
+ - rscadd: Directional firelocks frames to iron sheet recipe
+ - rscadd: Window firelock frames to reinforced glass sheet recipe
+ - bugfix: a runtime error during firelock construction(only when making them from
+ scratch)
+ - refactor: small changes to the heavy firelock code and var define of firelock_type
+ PowerfulBacon:
+ - bugfix: Fixes overhead chat scrambling languages you can understand but not speak.
+ That0nePerson:
+ - rscadd: Added alcoholic qurik
+ francinum:
+ - rscadd: Two new armbands in the beecoin shop.
+ jlsnow301:
+ - rscadd: Voting panel upgraded to TGUI.
+ - rscadd: Users are now shown which option they voted for.
+ - rscadd: Users can now change their votes.
+2020-12-20:
+ DatBoiTim:
+ - rscadd: Added the multitool for sale in YouTool vendors.
+2020-12-21:
+ MNarath1:
+ - bugfix: cult floor is atmos proof again
+ - tweak: makes space ruin version of cult floor non atmos proof again
+2020-12-22:
+ Archanial:
+ - rscadd: Old mask is back.
+ MNarath1:
+ - rscadd: Adds ranged analyzer to advanced engineering tech node
+ - bugfix: Ranged analyzer not buildable
+ St0rmC4st3r:
+ - tweak: Flux anomalies are now much more rare.
+ Victor239:
+ - balance: Security cyborgs must be researched first.
+ r1ks-iwnl:
+ - rscadd: New race restricted martial art for lizards - Tribal Claw.
+2020-12-23:
+ 'stylemistake ':
+ - refactor: TGUI will keep trying to load for 10 seconds in case of a network problem
+ and will show a bluescreen if it completely fails.
+ - bugfix: This hopefully fixes the whitescreen problem.
+2020-12-24:
+ Autisem:
+ - tweak: Sentinel's compromise restores blood volume
+2020-12-26:
+ Sinestia:
+ - bugfix: AI interface conforms to 17x15 properly
+2020-12-27:
+ Autisem:
+ - bugfix: Dryed gaia can now be used to make torches
+ - rscadd: Bioprinters now can also print soymilk
+ DatBoiTim:
+ - tweak: Syndicate MMI is a standalone uplink item. Restricted to Roboticist and
+ RD.
+ - tweak: Increased Syndicate Surgery Duffle Bag cost to 3 TC
+ Froststahr:
+ - rscadd: Abductors now start with agent IDs! Just swipe a victim's ID card onto
+ it to copy their access.
+ MNarath1:
+ - bugfix: Fix firelock windows dropping iron instead of reinforced glass
+ PowerfulBacon:
+ - tweak: meteors can now only appear after 50 minutes.
+ - tweak: If corgium reacts in your body, it will turn you into a corgi for some time.
+ - tweak: Jaunting ignores slowdown.
+ - bugfix: Fixed an unintended combat stim injector nerf.
+ - code_imp: Adds a validation check when loading jobs.txt
+ - spellcheck: Removed a double space from baton attack message.
+ cerebrallolzy, jupyterkat:
+ - balance: No longer can run away from explosions.
+ - refactor: Explosions is now a subsystem.
+ - refactor: Nukes now only gibs everyone, unfortunately
+ eeSPee:
+ - balance: Reduced the disgust you get from Transcendent Olfaction when you are
+ filthy
+ - balance: supersoldiers now look like humans
+ r1ks-iwnl:
+ - bugfix: Fixed Advanced Health Analyzer working exactly like the normal Health
+ Analyzer.
+2020-12-28:
+ PowerfulBacon:
+ - tweak: Christmas day now occurs on Christmas day.
+ - rscdel: Corgium no longer requires being heated to react
+ - tweak: Cultists can now be summoned if buckled to an object, but not while being
+ dragged + cuffed.
+ - tweak: Cultists can now use spells that do not require their hands while cuffed
+ (Communicate, EMP, Conceal presense).
+ St0rmC4st3r:
+ - bugfix: Removed an oversight that allowed CTF players to build iron walls.
+ eeSPee:
+ - balance: Vomit no longer hardstuns you for 2-5 seconds, but knocks you down, and
+ stuns for a really short duration
+ francinum:
+ - refactor: Beecoin shop items are now referenced by ID.
+ - bugfix: Fixed some shop items being unbuyable.
+ - refactor: Note, this update has unequipped your shop items. They will need to
+ be re-equipped.
+ - rscdel: The autodoc machine has been removed.
+ ike709:
+ - bugfix: Fixed the 4th character slot for players that have purchased it with beecoins.
+ r1ks-iwnl:
+ - rscadd: Health scanning someone as a ghost now also shows the chemicals inside
+ their body.
+2020-12-30:
+ St0rmC4st3r:
+ - bugfix: chloral hydrate bottle now has 30u instead of 15u.
+ park66665:
+ - bugfix: forced dynamic rulesets now respect role protection
+ - rscdel: admeme-unrelated sec and/or cap roundstart traitors on dynamic (hopefully)
+2020-12-31:
+ DatBoiTim:
+ - bugfix: SecBorg Module Upgrade now given by research.
+ bloons3:
+ - admin: xenobio potions now persistently log
+ ike709:
+ - refactor: world/Topic()'s player list has been moved from "status" to "playerlist"
+ r1ks-iwnl:
+ - tweak: Syndicate radio implant can't be bought on incursion gamemode.
+2021-01-01:
+ Kryyto:
+ - rscadd: Pill Bottle now have a random color.
+2021-01-04:
+ Crossedfall:
+ - tweak: The BeeStation BYOND logo is no longer festive
+ St0rmC4st3r:
+ - bugfix: Raven Shuttle now doesn't kill everyone with atmos
+ That0nePerson:
+ - rscadd: Syndicate excommunicates get a note telling them they're an excommunicate,
+ so they don't forget
+ TheFakeElon:
+ - code_imp: inversedir is now a global proc
+ froststahr:
+ - server: Improves extools detection for linux
+ park66665:
+ - balance: radiation now observes the laws of thermodynamics
+ qwertyquerty:
+ - rscadd: New space ambience music
+2021-01-05:
+ Froststahr:
+ - bugfix: Emergency shuttle console is now indestructible. Xeno queens rejoice.
+ Isy232:
+ - rscdel: Removed exploit that allowed you to get money by buying and selling crates.
+ - tweak: Many single pack crates removed due to said exploit. Added bulk versions
+ instead for very reasonable prices.
+ - balance: Rebalanced a ton of crates according to math. Many crates had you paying
+ more for the bulk option than the single pack.
+2021-01-06:
+ PowerfulBacon:
+ - rscadd: Adds missing jobs to jobs.txt on sage
+ That0nePerson:
+ - bugfix: Fixes IPC and squid mutation toxin recipes being swapped
+ - admin: Added logging for chemical smoke
+ bloons3:
+ - bugfix: morph cannot impersonate cameras
+2021-01-07:
+ MNarath1:
+ - admin: logs the shooter and circuit name of the weapon circuit in the attack log
+ of the victim
+ Raven-Industries:
+ - soundadd: adds 2 new ambience tracks for detective's office
+ Ryll/Shaps:
+ - admin: Fixed an issue with player logs becoming confused when someone triggers
+ multiple events within one second (like being attacked by two people at the
+ same time) that would cause holes in the logs
+ park66665:
+ - bugfix: magboots no longer make you feel gravity in space
+ - code_imp: removed legacy mob_has_gravity() to reduce overhead
+2021-01-08:
+ MCHSL, Rohesie, LemonInTheDark:
+ - rscadd: Reference tracking, which can be enabled via compile options; ported frontend
+ from tg
+ Naevii:
+ - imageadd: Updates CMO cloak sprite
+ - imageadd: Updates CE cloak sprite.
+ PowerfulBacon:
+ - bugfix: BOH nullspacing bug
+ Sinestia:
+ - rscadd: tiny fan to luxury shuttle poverty department
+ eeSPee:
+ - rscadd: Pet beacons!
+ ike709:
+ - code_imp: Optimized get_mob_by_ckey().
+ - code_imp: Removed get_mob_by_key().
+ - code_imp: Improved performance of get_hearers_in_view() by quite a bit.
+2021-01-09:
+ Alt_Alpha9:
+ - rscadd: Added international garbage day, say thanks to your local janitor
+ That0nePerson:
+ - admin: Moved smoke logging to mob logs
+ bloons3:
+ - tweak: makes General Griefsky a member of the Syndicate
+2021-01-10:
+ eeSPee:
+ - bugfix: pet beacons are named properly
+2021-01-11:
+ MCterra:
+ - bugfix: fixed a bug with teleporting out of the cloner not updating status properly.
+ MNarath1:
+ - bugfix: crate animation looking broken
+ - code_imp: Move animation math into a extra proc to pre calculate it
+ - refactor: Adding a association list to allow easy adding of additional crates
+ with unique values while still keeping the amount of actual lists to a minimum.
+ Sarchutar:
+ - bugfix: AIs can now save camera locations with CTRL + 1-9 again and jump to them
+ later with 1-9
+ eeSPee:
+ - rscdel: cbz, fake cbz and changeling test crates are no more
+ genessee596:
+ - rscadd: Added emergency prank tank in the clowns emergency box
+2021-01-12:
+ MCterra:
+ - server: new restricted-use comms key for cross-community comms
+ PowerfulBacon:
+ - bugfix: Syndi hardsuit will now automatically engage combat mode when the helmet
+ is lowered.
+2021-01-13:
+ That0nePerson, thatguythere03:
+ - rscadd: Adds a softshell jacket
+2021-01-14:
+ Putnam3145:
+ - bugfix: Toilet loot spawners don't lag the server on server start with forced
+ hard dels.
+ Rukofamicom:
+ - tweak: Laws generated by Ion Storms and broken AI modules will be more impactful
+ on the round
+2021-01-15:
+ Archanial:
+ - bugfix: Gulag shuttle AAC on metastation should be working now.
+ DatBoiTim:
+ - rscadd: Ketamine, as a Narcotic
+ - rscadd: Ketamine to the list of Junkie reagents
+ MNarath1:
+ - tweak: up to 5 mobs can end up in a cursed locker now
+ - tweak: Lockers that did not scoop up any mobs vanish after 18 seconds
+ - tweak: buckled mobs can be scooped up too(not AI)
+ - bugfix: lockerstaff now actually putting people into lockers
+ - bugfix: another infinit iron dublication glitch
+ - refactor: deleting some unneeded code
+ - bugfix: fixes a simple issue that lead to 25 error messages popping up in vsc
+ PowerfulBacon:
+ - code_imp: Refactors power tool code.
+ - bugfix: Fixes TGUI Stat Panel not working on Linux
+ - bugfix: Fixes TGUI Stat Panel light mode buttons being near invisible
+ - bugfix: Fixes TGUI Stat Panel not being scroll focused when hovered over
+ - bugfix: Fixes TGUI Stat Panel having a very slow update rate on certain tabs
+ - tweak: Names are now coloured in chat.
+ - rscadd: Added in a TGchat button to disable coloured names.
+ aguyiguess:
+ - bugfix: Fixed softshell jacket sprite error
+ eeSPee:
+ - tweak: You can now remove your own shoecuffs.
+2021-01-16:
+ OceanFish1:
+ - bugfix: some simple mob corpses got back their old gasmasks.
+ Shadark:
+ - bugfix: you no longer can pull mulebots by buckling mobs
+ ivanmixo:
+ - tweak: minor adjustments to light and camera placements in box's medbay
+ - tweak: fix box's medbay disposal system
+ - tweak: moved intercom from the chapel window in escape
+ - rscadd: MediDrobe to Box medbay storage
+ jlsnow301:
+ - bugfix: CentCom finally sent the spell checker that was on back order. A large
+ number of typos and punctuation errors have been fixed.
+ - bugfix: Several mutation-related messages should now display correctly.
+ - bugfix: Examine text for cybernetic implants is now formatted in a readable manner.
+ - bugfix: Emotes should no longer contain extra punctuation like this!.
+ park66665:
+ - refactor: refactored cable and disposal pipe construction/deconstruction code
+ to remove ugly legacy code
+ - bugfix: removal of said ugly legacy code improved performance a bit
+ - bugfix: and fixed cable color bugs
+ r1ks-iwnl:
+ - bugfix: Sofas won't turn into chairs when dragged on your sprite
+2021-01-17:
+ LemonInTheDark:
+ - admin: Chat messages that are more then text, so videos, embedded games, font
+ changes, etc now get wiped when the chat is reloaded
+ MNarath1:
+ - bugfix: crate animation at closing
+ - tweak: changing crate animation time from 2 to 3
+ OceanFish1:
+ - bugfix: Meta's white ship now shouldn't have three firelocks at one airlock.
+ St0rmC4st3r:
+ - tweak: Upon multiple requests, the science outpost shuttle is now accessible from
+ roundstart.
+ bloons3:
+ - bugfix: fixed a bug with the Trashbag of Holding where it was given the same storage
+ as an admin spawn only item
+ ivanmixo:
+ - bugfix: You can't curbstomp or groin kick with the pacifist trait anymore
+ - bugfix: ghosts can no longer flip pipes
+ - bugfix: jaws of life can now actually force doors when first spawned
+2021-01-18:
+ Sarchutar:
+ - bugfix: Cyborgs now get the ratvar laws upon conversion
+ - bugfix: Upon deconversion you now actually lose the ratvar language
+ ike709:
+ - code_imp: Tons of code optimizations across the board, both big and small.
+ - code_imp: Micro-optimized playsound()
+ - code_imp: A ton of other micro-optimizations.
+ - code_imp: Replaced get_hearers_in_view() with hearers() in many places. This may
+ break hearing some messages, hearing some sounds, or seeing some speech bubbles.
+ Please report issues on github.
+2021-01-19:
+ PowerfulBacon:
+ - tweak: Tweaks the plasma canister objectives description
+ - balance: Sawn off shotguns have increased spread
+ - rscadd: Allows mechs to be emagged.
+ Sarchutar:
+ - tweak: The border firelock frame can now be walked through
+ - imageadd: The frame also looks more open now
+ ike709:
+ - bugfix: Foam darts can no longer be exploited.
+ ivanmixo:
+ - tweak: nightmare's light eater can now extinguish bonfires and people on fire
+ - tweak: Replaced most instances of old firelocks
+ - bugfix: AI can't cryo while dead anymore
+2021-01-20:
+ OceanFish1:
+ - tweak: Added tiny fans on some space ruins.
+ eeSPee:
+ - spellcheck: Burn Kits are no longer called Bruise Kit Single Pack
+ - rscadd: coffee cart on Box, Delta and Pubby
+ - bugfix: fixed a bug with food vat not dispensing/mixing reagents properly (at
+ all)
+ yyzsong:
+ - balance: The heretic blade now does 24 damage instead of just 17
+2021-01-22:
+ LemonInTheDark:
+ - bugfix: Mulebots no longer go insane when shit starts to lag
+ PowerfulBacon:
+ - bugfix: clown megaphone
+ - bugfix: IAA agents are reported on the stat as EAA agents
+2021-01-23:
+ ro5490:
+ - rscadd: Added Oozelings, a new race!
+ - rscadd: Added Slime Ooze (bloodtype for Oozelings)
+ - rscadd: Added Slime Vacuole Organ
+ - rscadd: Added Regenerative Ooze - Restores 0.5 Brute, Burn, Toxin, and clone damage
+ at 0.4u per tick. Created by combining Slime Ooze with Tricordrizine
+ - rscadd: Added Energizing Ooze - Boosts Oozeling's nervous system, but only shocks
+ other lifeforms. OD at 30u for teslium shocks. Created by combining Slime Ooze
+ with Teslium
+ - tweak: adjusted watertouch code to interact with Oozelings
+ - config: Oozelings are now in Roundstart Races!
+2021-01-24:
+ 'MrLostman, thelaughingbomb ':
+ - rscadd: Debug tech disk, Syndicate materials closet, tier 4 brped, chief engineer
+ tool belt to spawn list.
+ - rscdel: Removed Airlock_maker.dm
+ - tweak: Changed events config to be set to false at roundstart.
+ - bugfix: fixed indentation of admin verb
+ PowerfulBacon:
+ - bugfix: fixes stat panel not showing the map
+ Qwertytoforty, imported by Cenrus:
+ - rscadd: Adds the syndicate teleporter, a mobility item that syndicate agents can
+ buy for 8 TC.
+ eeSPee:
+ - rscadd: Heretics have a chance to get hijack/ascend objective
+ - rscadd: Heretics no longer get protect objective
+ - rscdel: Heretics no longer automatically greentext when ascending
+ githubuser343:
+ - bugfix: BZ creation now properly limits maximum research points per reaction.
+ ike709:
+ - code_imp: The point of negative quirks is that you shouldn't be able to bypass
+ them. You're taking a permanent handicap in exchange for other perks. Multiple
+ fixes have been made to reflect this.
+ - bugfix: You can no longer augment a paralyzed limb. No more bypassing being paraplegic.
+ - bugfix: Light drinkers will treat alcohols with negative booze power as positive
+ booze power.
+ - balance: Motorized wheelchair now uses 1/4th of its previous power usage.
+ - balance: Brain Tumor quirk now starts with mannitol.
+ jupyterkat:
+ - bugfix: fixes radios not working in mechs
+ nemvar:
+ - bugfix: Scrubbers now stop scrubbing if their internal pressure gets too high.
+ ro5490:
+ - bugfix: Oozelings now have blood, a heart, and can be defibbed.
+2021-01-25:
+ MNarath1:
+ - bugfix: weapon circuit runtime
+ - tweak: adds creator to weapon circuit logging
+ PowerfulBacon:
+ - rscadd: Adds in the buy 1 get 1 free syndicate uplink deal.
+ - rscadd: Adds in the double discount bundle syndicate uplink deal.
+ - bugfix: Fixes the emergency shuttles ETA string.
+ - balance: Buckshot - Reduced raw damage from 12.5 to 9. Decreased spread from 25
+ to 10 degrees.
+ - balance: 'Soporific - First shot now applies 8 seconds of confusion. If hitting
+ a confused target, will put them to sleep for 5 seconds (Old effect: 5 seconds
+ sleep).'
+ - balance: Meteor Slug - Hard stun reduced from 8 seconds to 2 seconds. Stun is
+ applied if the target hits a wall due to knockback of the shell.
+ - balance: Frag12 - 5 seconds of paralysis reduced to 1 second. Not that it really
+ matters since these are generally going to be a 1 hit kill from the explosion
+ or at least limbs will be lost.
+ - balance: Rubbershot - Reduced stamina damage from 11 to 9. Reduced variance from
+ 25 to 20
+ - balance: Incapacitating Shell - Reduced variance from 25 to 20, stamina damaged
+ reduced from 6 to 5
+ - balance: Laser Slug - Reduced damage from 15 to 10. (This thing was ridiculous
+ before and actually pretty easy to make)
+ - balance: Shotguns - Pump-action shotguns now require the pumping hand to be empty.
+ - balance: Mech Scattershot - Pellet damage reduced from 24 to 18.
+ - balance: Slugs now have an armour penetration of -60, making them very effective
+ against unarmoured targets but not armoured targets.
+ - tweak: Makes each blood brother implant colour unique.
+ Victor239:
+ - tweak: SecHUD icons enlarged for clarity. Monitor icon now looks like an actual
+ eye.
+ francinum:
+ - config: Default gamemode is now a config entry.
+ nemvar, Dennok, Azarak, park66665:
+ - bugfix: recolored lights now preserve their color on nightshift
+ - bugfix: emergency lighting and vacuum lighting now have lighting overlay
+ - code_imp: light fixture now uses a non-unique mutable appearance for its lighting
+ overlay
+ park66665:
+ - bugfix: Fixed a few edge case bugs in case of components with multiple nodes sharing
+ a parent
+ - bugfix: Made connected canisters react properly
+ - bugfix: Improved pipenet code performance a little
+ - bugfix: fixed lighting overlays decoupling with lighting fixtures when shuttle
+ rotates for real.
+2021-01-26:
+ Cenrus:
+ - bugfix: Abductor scientists now understand all surgery types.
+ OceanFish1:
+ - bugfix: MacSpace should be visitable now without internals required
+ PowerfulBacon:
+ - bugfix: Fixes discount display name being incorrect.
+ jlsnow301:
+ - bugfix: Punctuation fixed on IV drip bags.
+ nemvar:
+ - rscadd: something something lube... HONK
+ - rscadd: Added a new loot item to the lavaland clown ruin.
+ - rscdel: Removed the slime core from said ruin.
+ - tweak: The lavaland clown ruin has some new pranks ready.
+2021-01-27:
+ PowerfulBacon:
+ - bugfix: Felinids can no longer play without cat ears or tail.
+ eeSPee:
+ - rscadd: Added Xenobiology Cargo Pack in Science
+2021-01-28:
+ r1ks-iwnl:
+ - balance: HUD Sunglasses now need advanced sunglasses or its subtypes to be crafted
+ instead of normal sunglasses.
+2021-01-29:
+ park66665, PowerfulBacon:
+ - bugfix: No more GODMODE shades hanging around the station, terrorizing players
+ and admins alike.
+ r1ks-iwnl:
+ - rscadd: Dual-port air vents now allow ventcrawling.
+ - rscadd: You can weld dual-port air vents.
+ ro5490:
+ - rscadd: Just a reminder, water in any form is LETHAL to Oozelings, Be careful
+ what you eat.
+ - rscadd: Oozelings are now always clean!
+ - tweak: Adjusted the blood volume gain from eating well, and becoming full (plus
+ a fullness reduce after a certain threshold is met).
+ - balance: Oozelings can no longer be augmented.
+ - bugfix: Fixed Fleshmend damaging Oozelings.
+ - bugfix: Fixed Rust Heretic regen damaging Oozelings.
+ - bugfix: Fixed medibots damaging Oozelings when trying to heal toxin damage
+ - spellcheck: Hyperperspiration's description now reads "extinguishing small fires"
+ instead of "extnguishing small fires"
+2021-01-30:
+ OceanFish1:
+ - rscadd: New away mission
+ - rscadd: New mobs and items for new away mission
+ - soundadd: added few sounds for away mission mobs and ambience
+ - imageadd: added icons for a lever-action shotgun, tactical sunglasses and some
+ mobs
+ - code_imp: made gatling laser emaggable
+ - config: added The Factory to away missions
+ jlsnow301:
+ - spellcheck: Fixed a typo in angry hearts (they're real!)
+2021-01-31:
+ AnturK:
+ - admin: Admins can now modify traits
+ Coldud13:
+ - tweak: Improves cryoxadone flavor
+ Crossedfall:
+ - config: Movement delay on Golden is changed to 1.5, making it the same as Sage.
+ Evankhell561:
+ - rscadd: Dominant Black Extracts!
+ - rscadd: Grey - Slimes split into one additional slime
+ - rscadd: Orange - Slime ignites players with one stack of fire when electrocuting
+ them
+ - rscadd: Purple - Slime regenerates HP faster
+ - rscadd: Blue - One original color slime will always remain after splitting
+ - rscadd: Metal - Slime has 30% extra max HP
+ - rscadd: Yellow - Slime gains electric charge much faster
+ - rscadd: Dark Purple - Slime rapidly converts atmospheric plasma to oxygen, healing
+ in the process.
+ - rscadd: Dark Blue - Slime takes greatly reduced damage from exposure to water
+ (50% less)
+ - rscadd: Silver - Slime no longer loses nutrition over time, and is no longer able
+ to feed.
+ - rscadd: Bluespace - Slimes will teleport to targets when they are at full electric
+ charge.
+ - rscadd: Sepia - Slime moves 30% faster
+ - rscadd: Cerulean - Slime splits into two identical adult slimes with half hunger
+ when it reproduces. Always 0% mutation chance.
+ - rscadd: Pyrite - Slime always splits into totally random colors, equal chance
+ for every slime type except rainbow. Can never yield a rainbow slime.
+ - rscadd: Red - Slime does 10% more damage when feeding and attacking
+ - rscadd: Green - Slime can completely consume corpses after death for a little
+ extra nutrition
+ - rscadd: Pink - Slime gains and will prefer to speak in common.
+ - rscadd: slime extracts can now be sold in cargo.
+ - rscadd: Gold - Slimes may be sold to cargo for extra money.
+ - rscadd: Oil - Slime douses anything it feeds on in welding fuel.
+ - rscadd: Black - Slime is nearly transparent. Slime is fully visible while feeding.
+ - rscadd: Light Pink - Ghosts may possess the slime at will, added to spawners menu.
+ - rscadd: Rainbow - Slime randomly changes color periodically. Reproduction follows
+ mutation chances of the current color
+ MCterra:
+ - rscadd: Newscasters join the insecure comms family
+ - server: More config options for insecure comms
+ PowerfulBacon:
+ - tweak: Targeted spells now give you a click ability rather than a dropdown list
+ of targets.
+ - tweak: The cooldown on spells is now on top of the button.
+ - bugfix: Fixes energy bolas being uncatchable.
+ - balance: Buffed shotgun slugs to -20 armour penetration instead of -60
+ - tweak: Felinids no longer take fall damage when falling across z-levels.
+ - bugfix: Fixes delta stations pool being in space
+ - rscadd: Oozelings dissolve into the pool
+ - bugfix: Items are dropped from people who dissolve in the pool.
+ - bugfix: Fixes pool noodles being cleanable
+ - bugfix: Fixes admins being unable to bypass popcap.
+ PowerfulBacon, KMC, Cdey:
+ - rscadd: Swimming pools
+ francinum:
+ - config: Bluespace miner is now affected by config. Golden has two miners and cannot
+ research more. Sage has no miners and cannot research more.
+ froststahr:
+ - bugfix: Purged parts of gas mixes that don't follow the warning linda_turf_tile.dm
+ warns you not to
+ - bugfix: Also missing temperatures is apparently bad too, and has been corrected
+ githubuser343:
+ - tweak: Floor tile ripping due to decompression now half as likely.
+ park66665:
+ - bugfix: you can now see space tiles' panel
+ - bugfix: admins (or people with perms) can now use mapping debug verbs that requires
+ enabling
+ r1ks-iwnl:
+ - bugfix: Ventcrawling in dual-port air vents actually works now.
+ ro5490:
+ - rscadd: Oozelings now have their own sprites, although they arent that different
+ from what they where before.
+ - bugfix: Ooze sprites no longer have leg clipping issues
+2021-02-01:
+ Arkatos, Sarchutar:
+ - rscadd: All Abductor machines now use tgui.
+ - tweak: Only the abductor scientist/agent can now interact with the abductor console
+ (not the camera console)
+ PowerfulBacon:
+ - rscadd: Added a lava moat between the security station glass on the gulag.
+ - rscdel: Remove the plasmaglass from the gulag.
+ - rscadd: Added a window to see what you are smelting.
+ Victor239:
+ - tweak: Poppies now spawn in maint loot.
+ - rscadd: Poppy pins can be crafted and give a very small mood buff when worn.
+ - tweak: Shanks are now a subtype of knife, allowing it to be used by heretics to
+ create their sickly blade.
+ - balance: Most window integrity is now halved, which is still double what it was
+ pre-fastmos.
+ eeSPee:
+ - tweak: Heretic' Living Heart no longer has a range limit
+ - rscadd: Mansus grasp now always silences for 3 seconds
+ - balance: Made the recipes for curses a bit easier
+ - rscadd: Rust blade now deals tox damage, the lower the heretic's health, the more
+ damage it deals.
+ - balance: Rust ascension now makes you spaceproof
+ - bugfix: Fixed multiple bugs around adding/deleting citations
+ park66665:
+ - bugfix: rainbow slimes can now exist
+2021-02-02:
+ TheFakeElon, bloons3:
+ - rscadd: Adds an adminbuse species conversion chamber
+ froststahr:
+ - balance: wizard demons have been capped to one each
+2021-02-03:
+ Victor239:
+ - bugfix: Poppies can no longer be freely generated from poppy pin crafting.
+ ivanmixo:
+ - tweak: xenomorphs now break firelock window shutters instead of opening them
+ park66665:
+ - bugfix: Hardened npc subsystems so mobs are less prone to freeze completely and
+ irreversibly
+2021-02-04:
+ Archanial:
+ - tweak: You can take out lighter with alt.
+ Nari Harimoto:
+ - bugfix: fixed thermomachines still working even without power, apc off, in space
+ etc...
+ OceanFish1:
+ - balance: Replaced some things on The Factory away mission.
+ eeSPee:
+ - bugfix: a changeling related fix
+ super12pl:
+ - rscadd: Hot Chocolate now works like chocolate.
+2021-02-05:
+ PowerfulBacon:
+ - tweak: You can now click on things while in throw mode if you aren't actually
+ throwing anything
+ - rscadd: Adds in team tracking beacons for nukies and ERTs
+ - rscadd: Nukies and ERTs can now see the location of their teammates.
+ - rscdel: Ancient sound synth.
+ silicons:
+ - bugfix: modular id consoles are now sanitized
+2021-02-06:
+ Cenrus:
+ - rscadd: Added a turret control panel to the nuclear shuttle
+ PowerfulBacon:
+ - tweak: General Greivsky from nullcrates is now resistant to EMPs.
+ park66665:
+ - bugfix: mob GC is improved
+ - rscdel: slime harddels
+ - bugfix: Wild West away mission runtimes at load have been removed
+2021-02-07:
+ Rukofamicom:
+ - bugfix: Atropine now stops working at 20 HP as intended
+ - bugfix: Hepanephrodaxon now has its correct metabolization rate
+ timothyteakettle:
+ - bugfix: newscaster sanitization go brrr
+ yyzsong:
+ - tweak: Plasma is now spicy
+2021-02-08:
+ Melbert:
+ - rscadd: You can now make beds with blankets.
+ - rscadd: You can now tuck plushes into bed.
+ - rscadd: You can also tuck the nuclear authentication disk into bed.
+ PowerfulBacon:
+ - balance: Love potion now heals when near your date.
+ - balance: Makes /obj/item/slimepotion/speed only reduce speed by 50%
+ githubuser343:
+ - balance: Removed bolt of change from Chaos Carp.
+2021-02-09:
+ Melbert:
+ - bugfix: Fixes railings blocking bullets and immovable rods from a certain direction.
+2021-02-10:
+ Archanial:
+ - tweak: Guns are bigger.
+ - tweak: Energy guns can fire a bit more before depowering.
+ MNarath1:
+ - rscadd: coffin animation
+ - tweak: angle of crate animation
+ - tweak: freezer sprite and animation parameter
+ - bugfix: some crate sprites breaking animation
+ - tweak: use icon_door_override to allow for removal of unnecessary sprites
+ - tweak: changing crate sprite names for consistancy
+ - rscdel: unnecessary sprites after using icon_door_override whenever possible
+ PowerfulBacon:
+ - tweak: Grilles now take damage when a mob bumps into them.
+ bloons3:
+ - tweak: Modified Brig Physician access permissions and roundstart items
+ - balance: Made the Brig Physician a proper civilian, which means eligibility for
+ antags
+ - balance: rebalanced bluespace miner
+ 'cacogen, Ryll/Shaps, Moccha-Bee, Dennok, ':
+ - rscdel: Old medbay clean bots
+ - rscadd: Scrubs MD added to Meta, box, donut, pubby, kilo and delta
+ - rscadd: Scrubs MD now give doctors access to it.
+ - tweak: The station's new Orion Trail arcade machines now detect certain antisocial
+ behaviors and can warn security and medical personnel about unhinged gamers.
+ - tweak: Updated material_container.dm
+ carlarctg:
+ - rscadd: Reduced firefight carry delay with latex or nitrile gloves
+ jupyterkat:
+ - bugfix: fixed weird announcement text renderings
+2021-02-11:
+ githubuser343:
+ - balance: Oozelings are no longer part of slime faction.
+ park66665:
+ - bugfix: species-specific attack mechanics now correctly happen based on attacker
+ - bugfix: landmines no longer gets activated by flying objects (e.g. projectiles,
+ throwing things, etc.)
+ - bugfix: pools no longer teleport people in and out
+ - bugfix: monkeys can now retaliate better
+ - bugfix: AI slime attacks no longer transcend borders
+2021-02-12:
+ EdgeLordExe, EOBGames, ShivCalev, Rohesie, Tad Hardesty, ATHATH, and eeSPee:
+ - tweak: Tears in reality no longer inflict brain damage when analyzed, instead
+ just give a bad moodlet
+ - tweak: Revised reality tears generation
+ - tweak: Tears in reality appear after a time, and slowly disappear after 15 minutes
+ - tweak: Coders rule, jannies drool
+ - balance: Heretic effects (runes, tears) are invisible to silicons
+ - spellcheck: Names and descriptions have been revised, thanks to @EOBGames
+ - balance: Heart now offers a pick between 3 targets
+ - balance: Doubled the charges sacrifices offer
+ - balance: Heretics marks area work differently; marks are applied by grasp, and
+ triggered with the blade
+ - balance: Ash grasp blinds instead of knockback
+ - tweak: Rust spread datum is revised/optimized
+ c420-o, TWATICUS, Dawson, Suicidal Maniac, BeloneX:
+ - rscadd: swag outfit available in beecoin shop
+ - rscadd: swag shoes availble in beecoin shop
+ ike709:
+ - bugfix: Null clients are now less likely to completely break roundend.
+ - bugfix: Fixed some internal affairs objective code just... not working.
+ - bugfix: Fixed two ambience runtimes.
+ - bugfix: Radios will no longer runtime when we can't get their turf.
+ kit-katz:
+ - tweak: Oozelings are now fully and properly translucent
+2021-02-13:
+ PowerfulBacon:
+ - bugfix: Fixes valentine cards not working correctly.
+ - bugfix: Fixes only 1 person receiving valentine objectives.
+ Totally Not Bacon:
+ - rscadd: Improves valentines day, gives ai proper valentines laws.
+ - tweak: You can now send a card to someone by writing their name on it with a pen
+ eeSPee:
+ - balance: Heretics no longer require the Codex to lay runes, instead they can lay
+ runes using their Mansus empowered hand.
+2021-02-14:
+ park66665:
+ - bugfix: pool item attack verb
+ - bugfix: a vox-related error related to announcement help verb has been rectified;
+ there might be other bugs that has been fixed by this.
+ - bugfix: leftover gears sometimes failing to spawn
+ - bugfix: reactions at pipelines are less willy nilly
+ - bugfix: advanced surgery tools' examine texts are saner
+2021-02-15:
+ Arkatos and actioninja:
+ - rscadd: RCD Access Control now uses tgui.
+ - tweak: RCDs are now able to set unrestricted directional access for newly built
+ airlocks.
+ Cheesus, ShizCalev, coiax:
+ - rscadd: Cryotube extinguishes a burning occupant when they are ejected by the
+ machine.
+ - code_imp: Comments around cryo cell code
+ - rscadd: Cyrotubes now alerts and stops when the tube has no beaker, reagents or
+ enough moles.
+ - tweak: changed how much is used and how much is magically transferred to the occupant
+ - tweak: Pyroxadone's metabolization rate is now variable, dependant on heat.
+ - bugfix: fixed reagents not being injected into an occupant properly.
+ MCterra10:
+ - bugfix: brig phys locker can be opened again
+ MNarath1:
+ - bugfix: border firelocks holding Pressure after destroy
+ - bugfix: window doors holding Pressure after destroy
+ eeSPee:
+ - rscadd: laughter now has positive mood effects and reduces knockdown on people
+ recently slipped
+ - rscadd: Holobarrier projectors and barrier grenades to SecVendors
+ - rscdel: Holobarrier projectors from various sec lockers
+ ike709:
+ - bugfix: Maybe fixed slimes sporadically no longer eating. Report it on github
+ if this is still an issue.
+ park66665:
+ - bugfix: Prize from Orion Trail is no longer broken
+ - bugfix: Disease from rat is no longer broken
+ - bugfix: Bar emergency shuttle's bar table now properly boots unauthorized people
+2021-02-16:
+ Blueturbo47:
+ - rscadd: Gas Miners, now in every map
+ park66665:
+ - bugfix: fixed a cause of pipe harddels.
+ - bugfix: Engineering PDA catridge's APC monitors power percentage display
+ - bugfix: Crusher trophy's examine text is now sane
+ - bugfix: RCDs with a silo link now work properly
+ - bugfix: Ash walker tendril now applies mood properly
+ - bugfix: Fixed a memory leak related to TGUI Stat panel
+ - code_imp: range, view and their related high-cost procs' usages have been audited
+ - bugfix: above audit should improve performance, but there might be bugs. Please
+ report bugs to GitHub.
+ park66665, Timberpoes:
+ - bugfix: fixed a few bugs around modular computers
+ - bugfix: RnD console sheet ejection now works properly
+ - bugfix: mining point card's examine text is no longer broken
+ - code_imp: removed obsoleted GLOB.materials_list
+2021-02-17:
+ PowerfulBacon:
+ - code_imp: Improves clockie code to allow for scripture generation if spawned on
+ a non-clockie round.
+ TheFakeElon:
+ - rscadd: adds the labor camp monitoring vault door
+ park66665:
+ - bugfix: hotfixed some UIs not opening
+ - bugfix: hotfixed mining scanner not working properly
+ - bugfix: AI's wipe core verb now functions properly and no longer breaks random
+ stuff afterwards
+ - bugfix: Blob overmind's stat tab no longer errors at pre-core-blob-placing
+ - bugfix: Fixed a few more ambience related runtimes
+ - bugfix: Fixed an issue at "no prisoner left behind" crew objective
+ - bugfix: Prevented changelings from losing powers after Last Resort
+ - bugfix: Dead AIs no longer cause living things to be broken code-wise
+ - admin: Fixed a small visual bug that prevented some note severity icon images
+ from showing up at the note browser UI
+2021-02-18:
+ That0nePerson, CommandBlockGal:
+ - rscadd: Re-adds apid dash! Can no longer knock over people or jump tables.
+ - rscadd: Adds honeycomb crafting for apids! Can be turned into 2 wax when empty.
+ - rscdel: Removes ability of apids to fly in low gravity.
+ - balance: for balancing, apids downsides have been changed. 1.5x burn and tox damage,
+ 1.25x stam damage, and they pass out in smoke and cold
+ - rscadd: Adds wax and wax floortiles
+ - rscadd: Gives apids an inert mutation that allows them to produce wax
+ francinum:
+ - balance: Slimepeople are only allowed to have 10 total bodies, for performance
+ reasons.
+ - config: This can be configured.
+2021-02-19:
+ Blueturbo47:
+ - bugfix: renames an emoji away from a filtered word
+ Froststahr:
+ - bugfix: Pubby SM won't delam itself
+ MNarath1, JamieD1, Redmoogle:
+ - bugfix: Repainted airlocks sometimes missing the moving parts, or having wrong
+ animation
+ - bugfix: Exploit allowing cult to disguise their airlocks with airlock painter
+ - tweak: Disallowed repaint of large airlock and hatches
+ - refactor: Changes code to use the assoc list for airlock repaint choosing
+2021-02-20:
+ Naevi, Victor239:
+ - rscadd: Adds black bishop suit,mitre and cloak to beecoin shop
+ - rscadd: Adds bishop cloak to hacked chaplain vendor
+ - tweak: Updates bishop suit and mitre
+ PowerfulBacon:
+ - rscdel: Removes :p and :d admin say modes.
+ bloons3:
+ - rscadd: Added an air alarm to the Morgue on Box Station
+ - rscadd: Added vent scrubbers and vent pumps to autopsy rooms
+ githubuser343:
+ - bugfix: Private cargo crate purchases no longer ignore access restrictions.
+ park66665:
+ - bugfix: Fixed weighted picking logic, so various codes utilizing weighted pick
+ is now a bit more safe and correct
+ - bugfix: The most prominent of such cases would be preventing random maint rooms
+ from not spawning once in a while
+2021-02-21:
+ Cenrus:
+ - bugfix: The eminence can no longer nuzzle things
+ - bugfix: The eminence can no longer select itself
+ - bugfix: The eminence can no longer pull other mobs
+ - bugfix: The eminence now teleports to random locations on the station, instead
+ of the core
+ - rscadd: The eminence teleport has a sound effect and a flash of color
+ - tweak: The eminence has a "Master" prefix on the hierophant network
+ - tweak: Tweak ark of the clockwork jusiticiar description
+ MNarath1:
+ - bugfix: Fixes morph becoming invisible and unclickable if it tries to disguise
+ as an airlock
+ - bugfix: Fixes computer screen and apc screen not showing in disguise
+ Naevii:
+ - imageadd: Updates herald sprites and animations
+ - imageadd: Updates Prophet cloak sprite
+ PowerfulBacon:
+ - bugfix: Infinite TC exploit.
+ - rscadd: Clockcult weapons will now automatically wield when you pick them up.
+ - balance: Phazons can no longer phase into areas with the no_jaunt flag.
+ - balance: Clockwork walls are no jaunt proof.
+ - balance: The celestial gateway has had its health buffed from 400 to 1000
+ - balance: Clockcult armour has been significantly buffed, previously it was very
+ weak and made when I poorly understood general armour values.
+ - balance: Marauders no longer automatically repair their shield and require a welder
+ to repair
+ - balance: Linked abscond now has a cooldown of 3 minutes
+ - balance: Abscond now takes 2.5 seconds instead of 3.5 seconds to invoke.
+ - balance: Abstraction crystal health buffed from 100 to 200.
+ - balance: Clockwork armaments cost reduced from 250 to 150
+ - balance: Dimensional breach cost reduced from 10000 to 5000
+ - balance: Interdiction lens range increased from 3 to 5
+ - balance: Interdiction lens now EMP mechs rather than use power.
+ - balance: Ocular warden cost reduced from 500 to 400
+ - balance: Prosperity prism active power used decreased from 4 to 2 per tick.
+ - tweak: Sentinel's compromise now plays a sound on use.
+ - balance: Sigil of vitality power cost reduced from 400 to 300
+ - balance: Sigil of vitalities will now turn people into cogscarabs rather than
+ just husking.
+ - balance: Stargazer power cost reduced from 600 to 300
+ - balance: Marauder cost reduced from 8000 to 2000. Vitality cost increased from
+ 80 to 100.
+ - balance: Vanguard no longer stamcrits you after use.
+ - tweak: eminence can break lightbulbs
+ - tweak: Eminence can use APC UI.
+ 'Tlaltecuhtli ':
+ - tweak: Lava damages contents of containers
+ eeSPee:
+ - rscadd: Added crucifixes to chaplain's witch hunter set, which wards against curses.
+ - rscadd: Added rosary beads, which work similarly to crucifixes; available at chaplain
+ vendor and religious supply packs in cargo.
+ park66665:
+ - bugfix: atmos machines that operates with external air now no longer try to function
+ in walls and other closed turfs and cause unending errors and ridiculous results
+ - bugfix: closets now properly react to revenant's Malfunction ability
+ - bugfix: changelings no longer experience problems for having suit powers and then
+ transforming to and fro human
+ - bugfix: wire-on-catwalk check for swarmers now works properly
+ - bugfix: Revenants can now use Revenant Transmit ability
+ - bugfix: Dead midwife spiders no longer can communicate
+ - bugfix: AI controlled morphs should no longer disguise as light itself and be
+ un-click-able.
+2021-02-22:
+ Mat05usz:
+ - bugfix: Area mood description is now written in red if it gives you negative mood.
+ - bugfix: Mood changes are now properly updated when walking between areas that
+ alter it.
+ bloons3:
+ - rscadd: Adds a cautery and surgical drill to CentCom
+2021-02-23:
+ Archanial:
+ - tweak: Skinsuits are now a subtype of hardsuits.
+ - rscdel: Skinsuits no longer appear in internals boxes and you cannot fold them.
+ - rscadd: Skinsuits now have integrated helmets.
+ - tweak: Skinsuits are as big as every other space suit now.
+ - tweak: Skinsuits appear in o2 (blue) lockers.
+ Froststahr:
+ - config: Disables "Extended" on Sage (Secret Extended is still enabled)
+ Mat05usz:
+ - bugfix: 'Metastation: Kitchen Coldroom is under Kitchen area and can finally be
+ depowered.'
+ - bugfix: 'Metastation: Cryogenics 4-way manifold is visible again.'
+ bloons3:
+ - tweak: Security is incentivized to wear their uniform
+ - tweak: Assistants are rewarded for obtaining their shinies
+ francinum:
+ - config: Jobs no longer load from the txt file, This should hopefully solve some
+ issues related to insufficient spawners.
+ - tweak: firelocks now actually stay open for a bit when forced.
+ githubuser343:
+ - rscadd: You can now use a shovel directly on stacks of grass tiles.
+ ike709:
+ - server: Added Topic() calls for getting/adjusting metacoins.
+ nemvar, Tlaltecuhtli, Naksu, Skoglol, Dennok, Denton81, FlufflyCthulu, Jessica, kittymaster0, Garen, park66665:
+ - tweak: Slimes now lose internal reagents over time.
+ - code_imp: Replaced most instances where cables referenced their maximum size with
+ a define
+ - bugfix: Turned off energy weapons can no longer do sharpness-requiring actions.
+ - refactor: get_eye_protection and get_ear_protection now looks less ugly.
+ - refactor: Refactored the visibility of reagents for mobs.
+ - code_imp: Health sensor no longer displays a giant window with 1 button on it,
+ instead can change states with alt click and use in hand.
+ - bugfix: fixes Russian helmets not holding both a vodka and a glass.
+ - bugfix: Fixed some examine messages.
+ - bugfix: Fixes even more examine messages.
+ - refactor: Slightly refactored blob examine code
+ - bugfix: Fixed a slime runtime.
+ - bugfix: Cable layer work again.
+ - bugfix: Incomplete and non-teleport reactive armors can no longer be used to complete
+ the traitor objective.
+ - bugfix: Fixes an issue with clown hulk simple mobs
+ - bugfix: added --geo-bypass for youtube-dl command
+ - rscadd: gives scientists a chance to spawn with an awesome tie
+ - bugfix: Stasis lets reagents know processing was stopped, fixing some issues.
+ - tweak: Amanitin's damage only triggers when it is completely removed from your
+ system, not when processing stops.
+ - bugfix: Fixes being able to pull from belt/backpack when you are stunned or cuffed.
+ park66665:
+ - bugfix: fixed pAI OS background image not showing
+2021-02-24:
+ Archanial:
+ - admin: removed ability to ban players from specific gimmick roles; old bans will
+ still be enforced.
+ Evan & Naevi:
+ - rscadd: Dominant Bluespace Extracts
+ PowerfulBacon:
+ - balance: Circuit comms receiver can no longer jam comms.
+ - balance: Circuit comms receiver requires encryption keys.
+ - rscadd: Adds movement sounds to IPCs
+ - rscadd: Adds movement sounds to riot suits and deathsquad armour
+ Sinestia:
+ - balance: Abductors can no longer teleport into the AI satellite.
+2021-02-25:
+ Evankhell561:
+ - rscadd: a real microwave to the bluesbace room.
+ MNarath1:
+ - tweak: Transforms you back on death when you are transformed trough a golden slime
+ pylon
+ - bugfix: Fixes the old body suffocating while inside the content list of the simple
+ mob while being transformed by golden slime pylon
+ - bugfix: Fixes another bug with golden slime pylon that could lead to you not transforming
+ back after leaving its range
+ - refactor: Changes the processing code a bit to remove a redundant loop for golden
+ slime pylon and some redundant checks
+ - bugfix: Gold slime pylon transforming someone into an invisible animal
+ Sarchutar:
+ - rscadd: Camera implant which inserts a camera into you. Admin only at the moment.
+ Timberpoes:
+ - bugfix: The security camera console will now correctly track when viewing moving
+ cyborg cameras.
+2021-02-26:
+ Crossedfall:
+ - config: Heads of Staff can no longer be antags on Golden
+ Naevii:
+ - imageadd: Updated HoS greatcoat.
+ Rukofamicom:
+ - tweak: Warping Gold Crossbreeds have temporarily had their loot table neutered
+ Thebleh, Sarchutar:
+ - bugfix: Cyborgs can cancel surgeries, which are past the first step, with drapes
+ and a cautery again. (They have to be on harm intent to do it)
+ bloons3:
+ - bugfix: You can no longer roll a Die of Fate multiple times before the first roll
+ takes effect
+ park66665:
+ - bugfix: transmutation rune drawing process no longer is incorrectly explained
+ in-game.
+ plapatin:
+ - rscdel: deletes null crates
+2021-02-27:
+ Archanial:
+ - tweak: Input bar is now under chat.
+ - rscadd: Help button is back on paper.
+ bloons3:
+ - rscadd: Adds Brig Physician permission node (ACCESS_BRIGPHYS = 34)
+ - tweak: Gives Brig Physician somewhat exclusive access to their medical supply
+ locker
+ - rscadd: Brig Physician permission is applied to relevant areas on maps
+ - tweak: Other minor access control changes around Brig/Brig medical areas
+2021-02-28:
+ francinum:
+ - balance: Neat moodlet integrated into standard behavior
+ - balance: To go along with that, the relevant moodlets have been adjusted.
+ - tweak: Hygiene visual effect only happens when you can't get any dirtier
+ - code_imp: Hygiene no longer generates miasma
+2021-03-01:
+ park66665:
+ - bugfix: fixed progress bar not showing up when removing transmutation rune
+ - bugfix: SSgarbage entry now shows up in the TGUI stat panel's MC tab.
+2021-03-02:
+ PowerfulBacon:
+ - balance: Mining armour is now weaker in high pressure environments.
+ - bugfix: You can no longer cremate Indestructible items.
+ - bugfix: Gimmick positions now have overhead chat colours.
+ - bugfix: Gimmick positions now have chat colours.
+ - bugfix: Gimmick positions now have a title in the job selection menu.
+ eeSPee:
+ - rscadd: Mask of madness is available to ash path, replacing curse of blindness!
+ r1ks-iwnl:
+ - bugfix: Virologist PDA starts registered with your account.
+ - bugfix: Necropolis seed stealth 8 threshold actually drops a chest and doesn't
+ just gib you.
+2021-03-03:
+ PowerfulBacon:
+ - bugfix: Gimmick highlighting respects no chat colours.
+ kit-katz:
+ - bugfix: damage overlays no longer disappear once you deal too much damage
+2021-03-04:
+ Cenrus:
+ - bugfix: Returned wig and sailor outfit to the autodrobe
+ francinum:
+ - bugfix: Cult forcewalls now properly expire
+ ivanmixo:
+ - bugfix: fixes some code warnings
+ park66665:
+ - code_imp: cleaned up creation arguments of forcefields
+ yyzsong:
+ - balance: Berets and Beanies are now 'small' sized
+2021-03-05:
+ Archanial:
+ - tweak: Crystalized cerulean pylon only spawns 3 crystals at the time.
+ - tweak: Each crystal takes twice as long to grow than before.
+ - tweak: Each crystal only drops poly-crystal upon reaching state 4 (only 1 amt)
+ or 5 (randomly chosen between 1 ~ 3 amt).
+ Froststahr:
+ - rscadd: HoP now has a file cabinet on meta
+ Rohesie, LemonInTheDark, spookydonut:
+ - code_imp: Ports the TGMC timer cooldowns system.
+ - bugfix: Fixed stealth implants not properly boxing the user, and doors not shocking
+ - refactor: Renamed COOLDOWN_CHECK to COOLDOWN_FINISHED
+ - bugfix: Abductor implant now reports the correct remaining time until it can be
+ used again.
+ tralezab:
+ - admin: New Adminbus button- Ghost Pool Protection! It lets you enable and disable
+ which sources ghosts can rejoin the round, minus their physical bodies getting
+ revived.
+2021-03-06:
+ ArcaneDefence:
+ - rscadd: Mothmen now get upset when their wings get burned :(
+ KagiyamaWeb:
+ - rscadd: INCORPOREAL_MOVE_EMINENCE to mob_movement.dm, eminence.dm and __DEFINES/mobs.dm
+ Sarchutar:
+ - rscadd: Medical cyborgs can insert organs with the organ storage bag into a smart
+ organ storage
+ TheChosenEvilOne, Rohesie, IndieanaJones, Mothblocks, Qustinnus:
+ - rscdel: Removed high population override from dynamic.
+ - bugfix: Fixed abductors being listed at round end on Dynamic despite none existing
+ - bugfix: Dynamic abductors should no longer be split up between two different ships.
+ - bugfix: Dynamic can now be configured when made the forced secret mode.
+ - tweak: dynamic threat reports are no longer perfectly accurate, they diverge and
+ are sometimes downright wrong.
+ - rscadd: Dynamic now rolls a separate budget for round start and midround antagonists,
+ meaning less round start antags will roll in favor of more midround ones.
+ - admin: The antag_cap value now uses an equation that scales with population, rather
+ than a fixed array.
+ - admin: autotraitor_cooldown now uses deciseconds rather than ticks, so the older
+ value of 450 now means 45 seconds. Set it to be 9000 if you want it to be accurate
+ to the old value (15 minutes), or simply unset it.
+ - admin: Midround/latejoin dynamic timers are now configurable.
+ - admin: Syndicate Sleeper Agent will now give details when ti fails.
+ - bugfix: The autotraitor cooldown now respects configuration, rather than resetting
+ back to 15 minutes after it is rolled.
+ - bugfix: Fixed dynamic parameters not checking the configuration. This means that
+ Manuel will now have slower midrounds, as was intended.
+ - rscadd: you can now cast rituals on dynamic if the threat level is 100
+ - bugfix: Fixed multiple high impact rulesets being chosen on Dynamic.
+ - bugfix: Midround dynamic injection no longer has a 100% chance of activating.
+ - rscadd: Dynamic will now play a part in controlling random event antagonists.
+ If a dynamic midround injection is coming too soon, or too early, then the next
+ midround injection will be buffed. Otherwise, it'll spawn normally.
+ - admin: Admins can now cancel or replace midround rulesets.
+ - bugfix: Admins can now correctly force midround rulesets.
+ TiviPlus, BeloneX:
+ - server: Add maximum recommended version of byond
+ bluezorua:
+ - rscadd: Tiny fans to a mining and science shuttles
+ eeSPee:
+ - bugfix: Fixes Mask of Madness not having cost
+2021-03-07:
+ Archanial:
+ - tweak: Billy club is now only available in sec vendor. Sec officers no longer
+ spawn with it.
+ - balance: Sec belt slot count decreased by 1.
+ Mothblocks:
+ - bugfix: Forcing roundstart rulesets should now work properly.
+ PowerfulBacon:
+ - code_imp: Replaces some teleporters forceMoves with do_teleports.
+ - refactor: Noteleport refactor
+ - bugfix: Abductors can teleport again.
+ WondaMegapon, Sarchutar:
+ - rscadd: Telecommunication machines now use a TGUI based interface
+ - rscdel: Removed HTML based telecommunication machine code
+ - balance: Telecommunication machines can have their interfaces viewed without a
+ multitool
+ - server: Telecommunication machines now log changed settings
+ francinum:
+ - bugfix: Rod subtypes now properly merge
+ park66665:
+ - bugfix: Ghosts can no longer dance with the music and be stuck horribly
+ - bugfix: Fixed a bug where pressure plates sometimes don't activate traps
+ - bugfix: pAI on bot no longer makes languages willy nilly
+ - bugfix: floorbots no longer cause an infinite source of removed tiles
+ - bugfix: floorbots is a bit less dumb
+ - bugfix: CPU usage of floorbots has been reduced
+ - bugfix: telecrystal, bluespace polycrystal, conveyor belt, and carpet stacks now
+ properly merge with its own respective subtypes
+ - bugfix: high-traction floor tile stacks no longer merge between different colors
+2021-03-08:
+ Evankhell561:
+ - tweak: AI-controlled slimes that have a master no longer attack their master
+ ExcessiveUseOfCobblestone, Caldony, Archanial:
+ - rscadd: Sects!
+ - rscadd: Technophile and Ever-burning Candle sects!
+ - rscadd: Every chapel has now altair of gods which allows you to create sect and
+ perform their specfic rites or make sacrifices that appease the gods.
+ Mat05usz:
+ - tweak: 'Synthflesh unhusking is more intuitive: if current amount in the body
+ + amount being applied is >=100, unhusk.'
+ ike709:
+ - bugfix: Telepathic messages are chat filtered.
+2021-03-09:
+ ATHATH:
+ - balance: The Summon Equipment spell, which formerly asked you to choose between
+ its two possible effects whenever you casted it, has been split into the Summon
+ Combat Equipment and Summon Ritual Dagger spells.
+ - bugfix: Summon Combat Equipment can now only be used on cultists, as its description
+ implies.
+2021-03-10:
+ Froststahr:
+ - rscadd: Pools have been deployed to Boxstation and Metastation.
+ Hollandaise:
+ - bugfix: Made Box mining dock a proper advanced cycling airlock and stopped it
+ from plasmaflooding itself
+ KazooBard:
+ - rscadd: Added baguette blade and it's pouch
+ - rscadd: Added french baguette which is the heirloom baguette for the mime. Unlimited
+ bitesize
+ LemonInTheDark, Azarak:
+ - bugfix: Dog beds are now properly tagged, and if your pet bites the dust, the
+ bed will be kept in their memory.
+ - bugfix: Pre-owned beds now retain their predetermined flavor text.
+ - bugfix: Fixed garbage collection of ghosts and lobby players, making the game
+ run faster.
+ - bugfix: Fixes a very rare edgecase that could cause spawned ghosts to be unable
+ to move.
+ PowerfulBacon:
+ - bugfix: Warp cubes and fulton kits no longer delete their user.
+ - refactor: Refactors stamina armour, makes stun batons and most stunning weapons
+ check stamina armour rather than melee, laser or energy interchangeably
+ - rscadd: Christmas hats now provide minor armour on christmas.
+ - rscadd: You can now see how well something will protect from stamina based damages
+ by examining it.
+ Vexylius:
+ - config: Assistants don't have maintenance access on Sage anymore
+ froststahr:
+ - tweak: Hiero no longer skips its main attack if you're in melee range
+ ivanmixo:
+ - bugfix: No more floating camera on Box's Aux Tool Storage
+ - tweak: Pimpin' ride no longer obstructs Box's Custodial Closet's airlock roundstart
+2021-03-11:
+ ghost:
+ - bugfix: Constructs do not count as having arms or legs for purpose of vehicle
+ driving.
+ githubuser343:
+ - bugfix: Trays now properly cleanup after non-repeat harvest plants were harvested.
+ - bugfix: Weeds overtaking a tray start at same age as freshly planted seeds.
+ jupyterkat:
+ - code_imp: removes bad helpers
+2021-03-12:
+ Cenrus:
+ - bugfix: removed a rogue closing square bracket from modular computer examine
+2021-03-13:
+ Archanial:
+ - rscdel: Removed freezer from metastation kitchen backroom.
+ Arkatos1, r1ks-iwnl:
+ - bugfix: Changelings will now show correct ID job icon on security huds upon transformation.
+ - bugfix: Changeling flesh disguise IDs will now properly show overlays upon examine.
+ - bugfix: Changeling flesh disguise will now properly include suit storage slot
+ item, if there is any.
+ Hollandaise, Nebulacrity, LemonInTheDark, Flareguy, smallveggiepaws, Mothblocks, Twaticus, uomo91:
+ - tweak: You can now see if syringe guns are loaded just by looking at them.
+ - tweak: Flamethrowers produce light.
+ - soundadd: Flamethrowers play a sound when activated, syringe guns also make sounds
+ when reloaded.
+ - imageadd: Flamethrowers now have an animated inhand sprite.
+ - imageadd: Wood planks, bedsheets, advanced extinguishers, and airlock painters
+ now have inhand sprites.
+ - imageadd: New telescopic baton and singularity hammer sprites.
+ - imagedel: Old telebaton sprites.
+ Mat05usz:
+ - tweak: Welder fuel tanks now explode with a power dependent on the amount of fuel
+ stored.
+2021-03-14:
+ DatBoiTim:
+ - rscadd: High calcium intake can have adverse affects
+ - rscadd: Milk Has An Overdose where it metabolizes into bone hurting juice
+2021-03-15:
+ Archanial:
+ - tweak: Brig physician is medical job now.
+ PowerfulBacon:
+ - bugfix: His grace can no longer be pulled.
+ - bugfix: You can no longer flash blind people.
+ - bugfix: Fixes suit movement sounds.
+ That0nePerson:
+ - rscadd: Added anesthetic tank holder! Portable tank holder allowing for much easier
+ anesthetization of patients.
+ - rscadd: Added quick latex glove box! Allows for quick switching of gloves, to
+ keep your hands sterile.
+ - rscadd: Added sinks to a few operating rooms that did not have them, for surgery
+ tool washing.
+ bobbahbrown, Rohesie, Mothblocks, Azarak, LemonInTheDark, Wayland-Smithy, Couls, nightred:
+ - rscadd: New overhead chat ported from TG.
+ - rscdel: Old overhead chat is gone.
+ - bugfix: Overhead chat doesn't render under plants.
+ - rscadd: Overhead chat for non /living.
+ - rscadd: New in client preferences regarding visibility of the chat, chat displaying
+ emotes and maximum length of the message.
+ - tweak: More UI elements will now be displayed in a non-blurry font.
+ francinum:
+ - admin: Crew Transfer Shuttles cannot be recalled.
+ ivanmixo:
+ - tweak: felinids now have the meows verb
+2021-03-16:
+ Archanial:
+ - bugfix: Clockcult invocation is no longer being spoken on radio.
+2021-03-19:
+ Naevii, TheFakeElon:
+ - imageadd: Adds a heap of new cosmetic items
+ - rscadd: Adds a donator category in the beeshop
+ - config: config for donator items
+ Rukofamicom:
+ - tweak: Zombie powder is now a delayed activation stamina toxin that triggers a
+ fake death once the victim is in a deep stamcrit
+ bloons3:
+ - admin: Security Cell doors log relevant changes made to timers
+ eeSPee:
+ - tweak: AI Eyes jump to shell when it disconnects
+ ike709:
+ - rscdel: Reverted the donor shop due to bugs.
+ jupyterkat, mrdoombringer:
+ - admin: admins can now edit the delays for reversing supplypods
+ - admin: The supplypod menu no longer sucks
+ - imageadd: new supplypod icons
+ - imagedel: old supplypod icons
+ - refactor: refactored supplypods code
+ qwertyquerty, Merct:
+ - rscadd: Music when nukies declare war
+ r1ks-iwnl:
+ - bugfix: Shields can't take stamina damage.
+2021-03-20:
+ Archanial:
+ - rscadd: You can now throw pets.
+ FlamePrince, JJRcop:
+ - tweak: IPCs are pulled into their body when repaired.
+ MisterMecky:
+ - bugfix: Styptic powder and Silver Sulfadiazine now properly only apply via patches
+ and med sprays
+ PowerfulBacon:
+ - bugfix: Scout guardians can no longer teleport things while scouted.
+ eeSPee:
+ - bugfix: Fixed gardener service module from harvesting plants with the plant bag.
+ kit-katz:
+ - rscadd: Oozeling mutation toxin
+ - tweak: Krokodil's withdraw transformation only applies to humans and felinds.
+ Other races just get injured badly
+ - balance: Oozelings now enjoy eating EVERYTHING* that can be eaten. God help us
+ yyzsong:
+ - spellcheck: Fixes a typo in battle royale supply drops
+2021-03-21:
+ Archanial:
+ - tweak: Replaced tiles in metastation freezer room with less cold ones.
+ - rscdel: Removed roundstart cold tiles and replaced them with /freezer tiles.
+ TheFakeElon:
+ - rscadd: Adds donator items in the beecoin shop, for real this time.
+ ikalpo:
+ - bugfix: RCD simple circuits upgrade now works
+2021-03-22:
+ Naevii:
+ - imageadd: Buffs anime (updates schoolgirl outfits)
+ OceanFish1:
+ - tweak: On April fools' day xenobiology have a chance to get trolled by Central
+ Command.
+ PowerfulBacon:
+ - bugfix: Fixes suit movement sounds not playing if you remove the suit.
+ - tweak: Meteors now spawn 10 minutes after being announced.
+ - tweak: Meteor wave event now unlocks meteor shields.
+ - tweak: Meteor wave start time reduced from 50 minutes to 30 minutes.
+ TheFakeElon:
+ - bugfix: fixes donator backpack inhands and scarf sprites
+ - code_imp: cleaner donator item handling
+2021-03-23:
+ Froststahr:
+ - code_imp: Non-bitflag slot defines gone, slot defines are now bitshifts
+ PowerfulBacon:
+ - tweak: The spectre inspector goggles now point towards ghosts instead of revealing
+ them.
+ - tweak: Energy katana is now indestructible.
+ - tweak: Energy katana is destroyed when its attached ninja is destroyed.
+ - tweak: Indestructible items can no longer be destroyed via the analyser.
+ - tweak: Supermatter causality field will now protect indestructible items from
+ being consumed.
+ - tweak: Automatic shotguns need 2 hands to be fired, fixing a bug that would break
+ automatic shotguns if you had an item in your inactive hand.
+ TheFakeElon:
+ - rscadd: Adds the ability to (inaccurately) shoot projectiles by using a screwdriver
+ on bullet casings
+ 'Watermelon914 ':
+ - rscadd: Added tgui lists
+ bloons3:
+ - tweak: Decreased the cost of the Special Ops crate to make it more viable
+ - admin: Create Antag now allows for a maximum amount of antags to be selected for
+ some options.
+ yyzsong:
+ - rscadd: Adds the geisha suit to the beecoin shop.
+2021-03-24:
+ Autisem:
+ - tweak: ports new animation sprite for wag
+ - tweak: Fancy new UI for folders
+ Mat05usz:
+ - rscadd: 'Deltastation: Third brig cell.'
+ - rscdel: 'Deltastation: Security transfer room (to make space for 3rd cell).'
+ - tweak: 'Deltastation: Moved warden office, interrogation and evidence higher,
+ making security office one row shorter and gear room longer.'
+ - bugfix: 'Deltastation: No more basic brig access on doors to gulag shuttle room,
+ security office and evidence.'
+ PowerfulBacon:
+ - tweak: Improves stat panel background design.
+ - tweak: Stat panel will now display adminhelp messages for the bwoinkees.
+ Rukofamicom:
+ - tweak: Oozeling limbs now recede at an earlier threshold, making it far more likely
+ for them to survive when exposed to adverse conditions that result in blood
+ loss.
+ - bugfix: Oozeling regeneration now costs the same as the refund from limb recession.
+ bloons3:
+ - admin: Adds sec cell logs to player logs
+ eeSPee:
+ - rscadd: You can now buy games of yatzy at toy vendors.
+ - rscadd: Standardized space chess, added space chess cargo crates
+ francinum:
+ - rscdel: Most of the away missions have been outmoded.
+ - bugfix: Drones can now actually drop things
+2021-03-25:
+ ikalpo:
+ - tweak: RCD simple electronics upgrade no longer locks the APC you are building
+2021-03-27:
+ Mat05usz:
+ - rscadd: 'Boxstation: Two pairs of sunglasses to Law Office.'
+ - rscdel: 'Boxstation: Remove a lot of useless wires around maintenance.'
+ - rscdel: 'Boxstation: Remove three AI cams placed in maintenance.'
+ - tweak: 'Boxstation: Bridge status displays are now placed on reinforced walls
+ instead on windows.'
+ - bugfix: 'Boxstation: No more CMO office area in maintenance.'
+ Pontenerd:
+ - rscadd: Added the old animation for felinid tails
+ - rscdel: Removed the new tail animation
+ PowerfulBacon:
+ - tweak: sleeping carp no longer deflects harmless projectiles.
+ francinum:
+ - rscdel: TGUI selection lists have been removed.
+ jlsnow301:
+ - rscdel: Removed chemistry and grenade functionality from circuits to prevent bomb
+ drones
+ r1ks-iwnl:
+ - tweak: Changeling last resort ability no longer takes 0.8 seconds after use to
+ actually spawn the headcrab.
+2021-03-28:
+ Cenrus:
+ - bugfix: Golems created by servants of ratvar will automatically get the clockwork
+ cult antag status
+ bloons3:
+ - admin: Fixes a regression with emote logging
+ eeSPee:
+ - bugfix: Imaginary Friends/Split Personalities can no longer occur on catatonic
+ people/playerless monkeys
+2021-03-29:
+ super12pl:
+ - rscadd: Brig physician now has medical's traitor items.
+2021-03-30:
+ FlamePrince, JJRcop:
+ - tweak: Trashbag of Holding bounties can now be fulfilled.
+2021-03-31:
+ PowerfulBacon:
+ - refactor: Refactors england.
+ - refactor: Refactors stamina healing to slowly heal over time rather than instantly.
+ - rscadd: health doll now displays stamina damage.
+ bloons3:
+ - rscadd: The Detective is now the best armed Security Officer on the station
+ jupyterkat:
+ - bugfix: fixed ex_act being called until the item is destroyed
+2021-04-01:
+ Dawson1917:
+ - tweak: Handcuff timer raised to 4 seconds from 3
+2021-04-02:
+ Archanial:
+ - rscadd: Spare has been moved to safe located in bridge.
+ - rscadd: Captain gets code to that safe.
+ - config: New config entry SPARE_ENFORCE_COC.
+ - config: If there is no captain and SPARE_ENFORCE_COC is enabled code will be given
+ to head highest in CoC.
+ - config: If there is no captain and SPARE_ENFORCE_COC is not enabled code will
+ be given to all of roundstart heads.
+2021-04-03:
+ KubeRoot:
+ - tweak: Align powercell sprites with overlay
+2021-04-04:
+ 123chess456:
+ - rscadd: Illegal technology now unlocks Mediborg's Amputation Adventure, the most
+ fun arcade game on the station!
+ Lautarourtiaga:
+ - bugfix: Stops RPGLoot from unstacking stackable items
+ MNarath1:
+ - rscdel: removes runed metal debris from the cult archive in station library
+ 'SomeoneYouProbablyKnow, ArcaneDefence, Tlaltecuhtli ':
+ - rscadd: The detective now has all his extra clothes in a vendor and not their
+ locker.
+ - imageadd: added icon for Detective Clothing Drobe
+ - rscadd: Map changes required to support DetDrobe
+ Victor239:
+ - balance: Updating security records now requires a single access permission, and
+ this is the same requirement whether it be via console or SecHUDs.
+ - balance: Portable turrets require Security access instead of Brig access.
+ bloons3:
+ - rscadd: Brig Physician has access to **Holding** Cells, equal to the Lawyer
+ ghost:
+ - balance: Monkey Cube Crate is now small (stacking) and costs half as much (1000).
+ zeskorion:
+ - bugfix: rooms in maint will no longer spawn empty due to new rooms being pathed
+ improperly. Also, the rooms that were added four months ago will finally show
+ up!
+ - bugfix: biometallic replication now allows nanites on inorganics properly
+2021-04-05:
+ DatBoiTim:
+ - bugfix: ARDS Severity Not Calculating Properly
+ - bugfix: Blobspore on Death Messages Ignoring Neutering
+ - bugfix: Regen Coma using fake death regardless of threshold
+2021-04-06:
+ PowerfulBacon:
+ - bugfix: Fixes an issue with livers.
+ ivanmixo:
+ - rscdel: Removed unused dog borg code
+2021-04-08:
+ Mothblocks, r1ks-iwnl:
+ - rscadd: Updates view tracked playtime menu to TGUI.
+2021-04-09:
+ DatBoiTim:
+ - bugfix: Fixed Mismatch Fake Death Flags on Regen Coma
+ - bugfix: You can now wake up from stealth threshold regen coma
+ - code_imp: Regen Coma now has a Define
+ Rukofamicom:
+ - balance: Meat Hook Grab now does 10 Armor-piercing damage instead of 25. Can now
+ be used for normal melee attacks as well.
+ - balance: Hierophant club now does 20 damage on melee attack w/ AoE (down from
+ 30), and 15 on homing projectile (down from 30)
+ - balance: Open cleaving saw now does 15 (down from 20) damage to players and 60
+ (up from 50) damage to fauna
+ - balance: Closed cleaving saw now does 8 (down from 12) damage, bonus bleed effect
+ on fauna is unchanged, and this still kills all non-boss fauna in the same number
+ of hits as before
+ - rscdel: 'Eleven items have been removed from Necropolis chest drop table: All
+ three hardsuits, Chaplain weapons, Infinite Soulstone, Katana, Mysterious Core,
+ Inferno Grenade, Voodoo Doll, and the Instant Summons spellbook.'
+ - rscdel: Spectral Blade, Lava staff, Spellbook of Sacred Flame, Wand of Fireball,
+ Mayhem in a Bottle, Blood Contract, Spellblade, and Staff of Storms have all
+ been removed from megafauna drop tables.
+ - tweak: PKA-related rewards have increased drop chances for necropolis chests.
+ - tweak: Lavaland puzzle chest now always awards PKA-related rewards
+ - tweak: All megafauna now drop a moderately large caches of ores, roughly 1500
+ points worth.
+ - tweak: All megafauna will now always drop the same equipment reward every time
+ they are killed,
+ - tweak: Anomalous crystals are now dropped by the greater Legion instead of Colossus.
+ - tweak: Bottle of dragon's blood no longer has a chance to grant an Ash Drake transformation,
+ but retained its other three effects.
+ - rscadd: Wicker Doll (Voodoo doll) is now available in traitor uplinks for both
+ Curators and Stage Magicians at a cost of 12 TC
+ - rscadd: Prison cube is now available in traitor uplinks for Curators at a cost
+ of 6 TC
+2021-04-10:
+ St0rmC4st3r:
+ - bugfix: The quick control buttons were removed from the insides of all cryopods
+ as per SpaceOSHA request.
+2021-04-11:
+ Cenrus:
+ - rscadd: Emagged library console can now make heretic books and clockwork slabs.
+ yyzsong:
+ - rscadd: Insulated boxing gloves and their crafting recipe. Needs insulated gloves
+ and boxing gloves. Clothing tab.
+2021-04-12:
+ PowerfulBacon:
+ - tweak: Contractor baton now only works with the assigned contractor.
+ - tweak: Contractor baton no longer causes non-targets to drop their held items.
+ - tweak: Contractor baton causes the contract target to have 4 seconds of confusion
+ when hit.
+2021-04-13:
+ DeltaFire15:
+ - bugfix: Certain things will no longer get affected by same-tile explosions despite
+ them not being supposed to.
+2021-04-14:
+ PowerfulBacon:
+ - bugfix: Fixes a bug that caused megafauna and silicon to be unable to hear.
+ jupyterkat:
+ - code_imp: prevent explosions flag is actually used now
+2021-04-15:
+ yorii:
+ - bugfix: Starthistle can now actually be mutated to Galaxythistle
+2021-04-19:
+ Kapu1178:
+ - rscadd: species sensor nanites are functional and in Harmonic
+ Penwin0:
+ - bugfix: hotdog is no longer a pastry
+ PowerfulBacon:
+ - tweak: Silicons cant be siphoned by the vitality matrix.
+ - tweak: Vitality matrix only generates vitality when damaging players.
+ - tweak: Vitality matrix no longer sihpons convertible players when the gateway
+ is not opening.
+ - tweak: Clockwork marauders are limited to 4 maximum
+ - tweak: Nullrod deals 15 bonus damage against marauders and breaks their shields.
+ - rscadd: Monkeys now can mutate into humans, gorillas or lose limbs when affected
+ by radiation.
+ Sarchutar:
+ - bugfix: The haunted magic eightballs interface is actually usable now
+ - tweak: The list of votes of said eightball gets reset upon shaking
+ Shoutgun.boom:
+ - bugfix: Fixed a few underwear sprites clipping
+ That0nePerson:
+ - rscadd: Adds a tip of the round for blocking
+ Vexylius:
+ - rscadd: Readded some old winter coats back to the game.
+ bloons3:
+ - admin: Logs cutting cuffs via jaws of life
+ - admin: Logs clock cultists applying hateful manacles
+ - admin: logs more cluwne spawns from random maint runes
+ - tweak: 'Removed the ability to unlock illegal tech using common items, see #3988
+ for full list'
+ r1ks-iwnl:
+ - tweak: Tribal claw tail sweep can't be used on yourself.
+ - bugfix: Fixes being able to stabilise legion cores multiple times.
+ - bugfix: Fixes being able to scan floor pill contents using the PDA reagent scanner.
+ super12pl:
+ - tweak: Reduces malf ai's and nuke ops' dynamic's midround cost to 20 so it matches
+ the wizard's.
+ zeskorion:
+ - rscadd: electroadaptive pseudocircuit now functions as airlock electronics
+ - rscadd: added OR, NOR, and NAND logic types to nanite programs
+2021-04-20:
+ zeskorion:
+ - bugfix: fixes nanites
+ - bugfix: zams fixed antoher fucky wucky with nanites oops
+2021-04-21:
+ Rukofamicom:
+ - rscadd: Added bottles for Tricordrazine and Spaceacillin
+ - tweak: Both varieties of NanoMed now stock primarily emergency and basic supplies
+ instead of overabundant healing supplies
+ Sarchutar:
+ - bugfix: The hand teleporters "None (Dangerous)" option is visible/working again
+2021-04-23:
+ Bokkiewokkie:
+ - imageadd: Added some more icons to boxes.
+ kit-katz:
+ - tweak: tweaks poison nanite program so oozelings and slimepeople dont vomit their
+ guts out using it.
+2021-04-24:
+ Archanial:
+ - tweak: Removed build button from autolathe's UI.
+ - tweak: You cannot go over 50 and under 0 with "+" and "-" buttons in autolathe's
+ UI.
+2021-04-26:
+ DatBoiTim:
+ - rscadd: Dermagraphic Ovulogenesis Symptom
+ - rscadd: Egg and egg sac sprites
+ - tweak: Bee Viro Symptom spawns insulin bees alongside honey bees on positive threshold
+ - tweak: DNA Saboteur no longer gives neutral mutations on positive threshold
+ - tweak: Biometallic Replication no longer makes that annoying clanging sound every
+ five seconds on positive threshold
+ - tweak: Pituitary Disruption won't give you plasmeme lungs anymore
+ - tweak: Viral Aggressive Metabolism No Longer Vaccinates When Self Curing
+ - rscadd: PANDEMIC Replacement Crate
+ - rscadd: Extrapolator replacement Crate
+ - tweak: Decreased Virus Extrapolator Cooldown
+ - tweak: Increased Virus Sample Crate Cost
+ - tweak: Increased Maint Virus Spawn Rate
+ - tweak: Makes Maint Viruses Better
+ - code_imp: Added a minimum symptom level argument used in random disease generation
+ Goblinu:
+ - rscdel: Removes Post-death explosion on goliath children
+ Kapu1178:
+ - balance: Increased nanite cloud resync timer from 30 seconds to 90 seconds
+ KazooBard:
+ - balance: Shortened makeshift bola removal time to 2 seconds
+ - balance: Makeshift bola no longer causes item drop.
+ Mothblocks:
+ - bugfix: Fixes dynamic executing latejoin rulesets more than it should be
+ PestoVerde322:
+ - imageadd: added the new icon for the turbine and compressor (in 4 different ways)
+ PowerfulBacon:
+ - tweak: Lessens runechat backdrop
+ - tweak: Increases runechat line width fro 96 to 128
+ - tweak: Emotes now show up on runechat in grey text and the icon is grey too.
+ - tweak: Runechat message icons now have a space between the
+ - tweak: Size of runechat message icons reduced fro 9 to 7
+ - tweak: The message generated fro talking into radios is no longer a whisper, and
+ will no longer be rendered on runechat.
+ - tweak: Loud radio messages are no longer big on runechat.
+ - tweak: Clown megaphone works again.
+ - tweak: megaphones are now larger on runechat again.
+ - tweak: Runechat line height reduced.
+ - rscdel: Wagging tail no longer displays an emote message
+ - rscadd: Corgstation
+ - rscadd: Lit glowsticks spawner.
+ - rscadd: Corgstation code ownership
+ - rscadd: Corgstation Emergency Shuttle
+ - rscadd: Corgstation Arrivals Shuttle
+ - bugfix: Fixes force_map not actually working
+ - tweak: Allows shuttles to be created in area/science/shuttle
+ - tweak: Pins some important vars on fastdmm2 for obj/machinery/camera
+ - tweak: Allows anchored supermatter crystals to be launched via those chapel cannon
+ thingys
+ 'Timberpoes ':
+ - rscadd: Roboticists rejoice. NanoTrasen has heard your cries for help. They've
+ seen CCTV footage (recorded by the AI) of you all staring enviously at geneticists,
+ lording around their shiny, polished, usable DNA Consoles. Cry no more, for
+ you can now please your Silicon Overlords and robust those Machine Cultist Chaplains
+ in high definition glory with a beautiful new interface added to Exosuit Fabricators.
+ actioninja, Kylerace:
+ - bugfix: Glowing overlays no longer render above everything, and mobs and items
+ properly block them
+ covertcorvid:
+ - bugfix: fixed alt-click rotation on TEG circulators and drink/booze dispensers
+ silicons, /tg/ coders, port by froststahr:
+ - rscadd: New hijack! To do it, you must alt-click the shuttle console a series
+ of times. After that has been successfully done 5 times, the hijack is complete.
+ - rscadd: After each hack, the shuttle in-transit is delayed slightly, and crew
+ are also notified of what is going on.
+ - rscadd: Who can hijack? Blood brothers, traitors, changelings, wizards, highlanders,
+ incursions, nukies, heretics
+ - rscdel: Old method of hijack no longer works for ordinary antags, if you want
+ your greentext, you MUST secure the console. Not that that'll be difficult compared
+ to killing everything.
+2021-04-27:
+ Cenrus:
+ - bugfix: Borg upgrades can be installed again
+ yorii:
+ - code_imp: Old non-functioning Give verb replaced with new giving system from tgstation
+ - code_imp: Give is now also a customizable keybind, defaulting to G.
+ - code_imp: Old give methods such as Ctrl-Shift-Clicking, Right Click->Give, or
+ using the verb Give, all do the same thing now.
+2021-04-28:
+ Archanial:
+ - rscadd: You can deconstruct ore silo now.
+ Azlan, Fikou:
+ - imageadd: New brass spear sprite
+ - rscadd: Ancient spear null rod option
+ Bokkiewokkie:
+ - rscadd: Allows you to hack suit storage units with an EMP or Emag.
+ - bugfix: Fixed recyclers and suit storage units deleting important items.
+ - bugfix: Fixed disposals mailing not working across z-levels.
+ DatBoiTim:
+ - bugfix: Ovulogenesis actually spawns eggs
+ - bugfix: Ovulogenesis Eggs have their sprites setup correctly in code
+ KubeRoot:
+ - bugfix: Autolathe now displays materials in sheets correctly
+ - bugfix: Security IDs can now be swiped on autolathes to lock/unlock interface.
+ The code for this was broken.
+ - bugfix: Autolathe's security interface lock/unlock button is now more intuitively
+ labeled
+ - bugfix: Autolathe UI no longer breaks and duplicates entries when hacked
+ Malgover:
+ - rscadd: aristocrat coats to the law office's wardrobe and reworks the hastur robes.
+ Mothblocks:
+ - rscadd: A victory for revolutionaries will no longer end the round on dynamic,
+ instead no new security and command will be allowed to join.
+ Penwin0:
+ - balance: Increased Changeling Armour resistances
+ Rukofamicom:
+ - rscdel: Ashwalkers no longer speak common
+ SerynEngi:
+ - rscadd: Recipe for Liquid Electricity.
+ - tweak: Lowered Liquid electricity shock damage and probability
+ TheFakeElon:
+ - bugfix: Thrusters now check for wings/flight
+ bloons3:
+ - tweak: gas miners on maps are no longer in space ruin mode and actively consume
+ power
+ eeSPee:
+ - tweak: Prevents midround antags from spawning after shuttle has been called.
+ - balance: forensic scanner glitch out when scanning heretic runes and realities
+ - tweak: ghost/mob sentience system
+ froststahr:
+ - rscadd: Borgs use radial menus for module and skin selection
+ yorii:
+ - rscdel: Strange seeds can no longer contain corgium.
+2021-04-29:
+ That0nePerson:
+ - balance: even smaller felinid buff
+2021-04-30:
+ Cenrus:
+ - rscadd: Added vents to the courtroom on CorgStation
+ Crossedfall:
+ - server: The discord webhook handler's URL can be configured now
+ Mat05usz:
+ - rscadd: 'Corg Station: Multiple cameras so AI is slightly less blind.'
+ - tweak: 'Corg Station: Xenobio is slightly bigger, has ChemMaster, a water tank,
+ an extinguisher and a box of beakers.'
+ - bugfix: 'Corg Station: Xenobio airlocks now start locked and cycle properly.'
+ - bugfix: 'Corg Station: Bots no longer stop middle of a hallway and do nothing.'
+ - rscadd: 'Corg Station: Detective office now has a window with privacy shutters.'
+ - bugfix: 'Corg Station: Doors to detective office now require detective access.'
+ bloons3:
+ - rscdel: Removed keycard authentication access from Security
+ - rscdel: Removed keycard authentication device from Warden's office
+2021-05-02:
+ Archanial:
+ - admin: admins can ban from heretic
+ Cenrus:
+ - tweak: Suit sensors can now be changed in the strip menu.
+ DatBoiTim:
+ - bugfix: Ovulogenesis uses a new method for adding its reagents now they all get
+ added
+ - bugfix: Eggsacs actually have their transmission thresholds work
+ PowerfulBacon:
+ - code_imp: Microoptimises MouseMove
+ ivanmixo:
+ - bugfix: Fixed up Corgstation's toxins lab
+2021-05-03:
+ PowerfulBacon:
+ - tweak: Corgstation SM ejection sequence now requires CE access.
+ - tweak: Corgstation science door name corrected.
+ - tweak: Corgstation tool storage now has a modular computer vendor.
+ - bugfix: All prick messages will now have the same punctuation
+ francinum:
+ - bugfix: Neets can now properly ignore the hygiene system.
+ yorii:
+ - spellcheck: fixed a few "typos"
+2021-05-04:
+ PowerfulBacon:
+ - tweak: Changelings have a 70% chance to hear hivemind messages even if they do
+ not have the ability.
+ - tweak: Last resort can no longer be used while being absorbed.
+2021-05-05:
+ MCterra10:
+ - bugfix: mining borg cutter upgrade can be applied again
+ Sinmero:
+ - rscdel: Removes move sound from chap armor
+ francinum:
+ - tweak: The pool of syndicate codewords has been adjusted.
+ ike709:
+ - bugfix: Fixed glass shard harddels.
+ - bugfix: Fixed beaker harddels.
+ - refactor: Cryopods no longer lag the server when people despawn.
+2021-05-06:
+ PowerfulBacon:
+ - bugfix: Fixes infinite ore silo point exploit.
+2021-05-07:
+ PowerfulBacon:
+ - bugfix: Changeling item will not delete on drop.
+ - tweak: Changeling items can be deleted by non changelings by clicking on it.
+2021-05-09:
+ CogumeloGames:
+ - rscadd: mirrors reflect lasers
+ PowerfulBacon:
+ - rscadd: Updates corgstation to include detective wardrobe, Medical records and
+ adds a spare ID safe to the bridge.
+ Rukofamicom:
+ - rscdel: Wishgranter is no longer available on lavaland
+ bloons3:
+ - config: The map blacklist config is used to disable the Wishgranter ruin instead
+ of removing the template
+2021-05-10:
+ AnCopper:
+ - tweak: Updated the witch custome sprite.
+ Malgover:
+ - rscadd: Grave Robber Kit for the Chaplain's armaments beacon & witch hunter hat
+ fix
+ PowerfulBacon:
+ - rscadd: Modular fabricator interface for exosuit fabs.
+ - code_imp: Removed the build queue from the mod fab interface code.
+ TheFakeElon:
+ - rscadd: Adds a new softcrit system, you can now interact and walk around slowly
+ while in crit.
+ - bugfix: IPC crit damage code actually works properly now.
+ ninjanomnom:
+ - code_imp: support for named arguments to components and elements
+ - admin: added VV for messing with components and elements
+2021-05-11:
+ ike709:
+ - bugfix: Fixed a bug preventing donors from joining
+2021-05-12:
+ Archanial:
+ - refactor: slightly refactored flash code.
+ - bugfix: Fixed rare case of incursion uplink not spawning
+ Autisem:
+ - refactor: minigun is energy weapon now, making it not freeze the server
+ KubeRoot:
+ - bugfix: modular computers shouldn't leave behind power cells, id cards, or any
+ knick-knack for that matter, anymore when deleted.
+ - code_imp: Td should spike slightly less, assuming harddels actually go down
+ - code_imp: You feel a great disturbance in the force, mob code has changed. Please
+ report any odd targeting behavior on the issue tracker
+ francinum:
+ - bugfix: You can no longer teleport with weight machines.
+ ike709:
+ - bugfix: Fixed a harddel caused by proximity checkers.
+ - bugfix: Fixed a harddel with start landmarks.
+ yorii:
+ - tweak: Unstable mutation toxin can now also turn you into a skeleton.
+2021-05-13:
+ Archanial, Qustinnus, MrMelbert. coiax, Rohesie, Ghommie:
+ - rscadd: Station trait system, together with 24 traits.
+ - rscadd: Custom announcer support. Medbot and intern announcers.
+ - rscadd: Deathrattle implant. Available for nuke ops for 4tc.
+ bloons3:
+ - config: Disables security borgs
+ - config: Security borg transformation modules are handled via a techweb, and gated
+ via config
+ ike709:
+ - tweak: Blood tomatoes no longer produce quite as much gibs. We can't have nice
+ things.
+2021-05-14:
+ Autisem:
+ - tweak: Admin equip outfit now has plasmaman clothing as sub catagory
+ - rscadd: We have a confirmed reddit moment spotted near your station. Remember
+ NT has a 0 tolerance policy to a fat bugs bunny
+2021-05-15:
+ Autisem:
+ - bugfix: admin outfit manager can load outfits from json files
+2021-05-16:
+ Archanial:
+ - bugfix: Rare case of special traitor equipment not spawning
+ - rscadd: Arabian cat
+ - rscadd: Exotic cat crate
+ - tweak: Same station trait cannot appear twice (shouldn't affect anything in game).
+ - tweak: Station traits will appear less often.
+ - tweak: Intern and unnatural atmosphere are less common, medbot announcer is more
+ common now.
+ bloons3:
+ - rscadd: Adds a variant of the temperature gun that starts with a firing pin
+ - balance: Changes the security temperature gun to be bulky
+ - balance: Changes the x-ray energy gun to be bulky
+ novaremnant:
+ - rscadd: Added Apid Mutation Toxin
+ - rscadd: Added recipe for Apid Mutation Toxin
+ - rscadd: Added Ethereal Mutation Toxin
+ - rscadd: Added recipe for Ethereal Mutation Toxin
+ tralezab:
+ - tweak: Clown tears use sheets instead of ore
+2021-05-17:
+ Archanial:
+ - bugfix: Fixed riot knight armor runtime
+ ThomasTheDOOT:
+ - rscadd: reddit moment sprites where done by me
+ francinum:
+ - server: Minor logging change to the ban system. No end-user effects.
+2021-05-18:
+ Archanial:
+ - rscdel: Removed janitor's PDA locator due to performance issues.
+ - tweak: Emergency maint access is now limited to station z level.
+ - refactor: Removed some in world loops
+ - bugfix: Fixed rare case of players spawning in lobby screen.
+ - bugfix: Fixed request console runtime
+ Froststahr:
+ - rscdel: Sapient space sharks have been hunted to extinction, leaving only non-sapient
+ ones in the wild.
+ ivanmixo:
+ - bugfix: fixes the visible manifolds in engi on corg
+2021-05-19:
+ 81Denton:
+ - rscadd: Directional stairs
+ Archanial:
+ - bugfix: Darkpurple stabilized slime runtime
+ - admin: Reduced logging after the round.
+ Phil Smith:
+ - balance: Adrenaline nanites are now under weaponized tech which means you now
+ need illegal tech to get it!
+ ivanmixo:
+ - bugfix: Swarmer beacon and maintenance sec officer's corpses now get properly
+ deleted upon death
+ - code_imp: True/False consistency
+2021-05-20:
+ ? ''
+ : - rscadd: Degraded sunhuds
+ - rscadd: Science sunglasses removal crafting recipe
+ Archanial:
+ - bugfix: Warrant console and spawner menu runtimes.
+ - code_imp: Fixed some bitwise operation errors.
+ Autisem:
+ - tweak: The BS Miners cost has been altered to be buildable round start without
+ needing to mine
+ FatCat1978, ninjanomnom, stylemistake, nemvar, Timberpoes, Fikou, TemporalOroboros:
+ - rscadd: The Detective now features a classy and stylish piece of spy equipment.
+ The SpyGlasses offer a remote view of a linked camera, which can be inconspicuously
+ worn on clothing
+ - bugfix: Makes it possible to wear the spy bug by making it actually an accessory
+ clothing item.
+ - code_imp: Moves out the recursive movement tracker found in the orbiting components
+ to a datum usable by other things
+ Ryll-Ryll:
+ - rscadd: Added stickytapes
+ - rscadd: Added pellet cloud grenades
+ - refactor: Refactored embeds, there can now be harmless embeds
+ - rscadd: Added back the pellet cloud option to admin podlaunchers
+ eeSPee:
+ - rscadd: Changed the memory allocation for space ruin generation
+ - rscadd: Added new ruins
+ ike709:
+ - bugfix: The entire game can no longer be destroyed by an admin attempting to un-possess
+ something when they haven't actually possessed anything first
+ - rscdel: The BS miner cost changes have been reverted. They should've have been
+ changed in the first place, sorry.
+ ivanmixo:
+ - rscadd: Announcement system, Beepsky, boxes of bodybags, Intellicard in RD's office,
+ cleanbot in AI sat, mirror in theatre, extra toolbox in sci's main lab
+ - bugfix: Bar backroom piping is connected to pipenet, Kitchen APC and Turbine SMES
+ are now actually wired into the power grid, Bot navigation issue near arrivals
+ fixed
+ - tweak: Armory and Secure tech storage doors are now reinforced, roboticists can
+ now open the anesthetic closet in robotics
+ kit-katz:
+ - rscadd: plenty of pets and critters can now be picked up and worn as a hat (ethically)
+ - rscadd: most corgi types, pugs, corgi puppies, Walter, ALL cat types(yes even
+ cak),
+ - rscadd: mice, hamsters, chickens and chicks(The other type) crabs and SNAKES!!
+ - imageadd: sprites for the above
+ 'sergeirocks100 ':
+ - rscadd: Adds fingerless insulated gloves as a Assistant heirloom item.
+2021-05-21:
+ Archanial:
+ - bugfix: Fixed runtime when qdeleting proximity monitor
+ - bugfix: Fixed runtime in cmp.dm
+ - bugfix: Fixed bloodtomatoes not deleting themselves.
+ - rscdel: Removed donutstation.
+ - admin: Admins will now get a pop up asking them if they want to start Battle Royale
+ if the round was delayed.
+ Kapu1178:
+ - refactor: whip isnt shitcode anymore
+ - bugfix: Synths now properly inherit mutant bodyparts
+ - bugfix: Synths process chems as intended, and have the proper bitflags and traits
+ - balance: All synths revive on healing like IPCs, and military synths have additional
+ traits.
+ 'Tlaltecuhtli ':
+ - balance: tendril crates need keys to open which you can spend mining points to
+ get
+ - balance: ashwalkers can craft em with bones cause they got no points
+ francinum:
+ - rscdel: For performance reasons, lavaland atmos is static again. The randomization
+ system still exists but sits unused.
+ yorii:
+ - bugfix: made some generic healing sources handle toxinlovers properly
+ - bugfix: made the easy dismemberment trait on several species actually function
+ - bugfix: made the beelover trait actually function
+ - code_imp: some related code cleanup
+ yyzsong:
+ - rscdel: Removes the filter from the corg filter to space setup
+2021-05-23:
+ Kapu1178:
+ - bugfix: Synth infinite brute stacking
+ Rukofamicom:
+ - bugfix: Ethereal Disco Grenades no longer crash the server
+ jupyterkat:
+ - bugfix: modfabs no longer teleport disks
+ zeskorion:
+ - tweak: necroseed tendril range halved. if an admin bwoinks you for this they should
+ be ridiculed and kicked in a corner
+ - tweak: self respiration can now counteract autophageocytosis bleeding
+ - tweak: pituitary disruption is less disruptive to a round overall
+ - tweak: biometallic replication numbers and thresholds have been tweaked
+ - tweak: wizarditis transmission threshold is significantly easier
+ - bugfix: wizarditis now works
+2021-05-24:
+ Ivniinvi:
+ - spellcheck: fixed a typo in the exofab
+ Mothblocks and tgstation:
+ - refactor: Optimized TGUI_CREATE_MESSAGE() significantly.
+ - tweak: Updated the unit test code.
+ PowerfulBacon:
+ - refactor: Entering softcrit knocks the player down for 4 seconds.
+ - refactor: Items cannot be used during softcrit. (Attacking, healing, suicide bombing
+ can no longer be performed)
+ - refactor: Hardcrit threshold changed from -50 to -40.
+ - refactor: Random oxy damage during softcrit removed. (Oxygen damage is already
+ handled by the lungs)
+ - refactor: Random sleeping during softcrit replaced with knockdown.
+ - refactor: Reduced the probability of being knocked down / gasping / coughing during
+ softcrit.
+ - refactor: Damage effect overlay now kicks in at -0 health rather than -10 health.
+ - tweak: Riot shields, Roman shields, Goliath shields no longer block bullets.
+ Rukofamicom:
+ - bugfix: Disco grenade 2, electric boogaloo
+ ike709:
+ - bugfix: Fixed a material dupe exploit with stacking machines.
+2021-05-25:
+ Archanial:
+ - bugfix: runtime when throwing petholders
+ - tweak: Death pop up will only appear when your mob dies, not on ghosting
+ PowerfulBacon:
+ - bugfix: Hardsuits protect against space again.
+ eeSPee:
+ - rscadd: Gangs Rework
+2021-05-27:
+ Cyberboss, MCterra:
+ - server: port TGS event handler from /tg/
+ Rukofamicom:
+ - bugfix: Slimes gain less nutrition by feeding on other slimes due to an infinite
+ breeding exploit
+ - tweak: Sentient slimes no longer get bonus nutrition when breeding
+ ivanmixo:
+ - tweak: Skeletons, androids and zombies are now immune to cellular damage
+ yorii:
+ - rscadd: Rerolling blob strains now uses a radial menu, and lets you see what the
+ strain does before picking it.
+2021-05-28:
+ r1ks-iwnl:
+ - bugfix: fixed shooting yourself in the mouth with a gun while pacified to create
+ 1 hit kill projectiles.
+2021-05-29:
+ Ivniinvi:
+ - bugfix: Monkey Cube crate's description is now accurate.
+2021-05-30:
+ bloons3:
+ - code_imp: Justifies LAZYLEN()
+2021-05-31:
+ ? ''
+ : - server: Compiler version is now 514.1554. Players are encouraged to stay on
+ 513 for now.
+ Froststahr:
+ - rscdel: Integrated circuits have been removed to enable replacing them with TG's
+ new Wiremod circuits
+ SpacePrius:
+ - rscadd: Added copier to HoP office on pubbystation
+2021-06-01:
+ Archanial:
+ - bugfix: Fixed runtime in config related to testmerges
+ Crossedfall:
+ - rscadd: Being on staff grants you access to donor items
+ Mothblocks, froststahr port:
+ - admin: Added the "Check Timer Sources" debug command to help isolate problematic
+ cases of addtimer.
+ ike709:
+ - refactor: Minor optimizations to powernet propagation.
+ jupyterkat:
+ - bugfix: ethereals can charge via borg chargers again
+ steelslayer:
+ - code_imp: Updates various machines, which currently use SSfastprocess, so that
+ they only start processing when it's necessary. Some of these updated machines
+ have been changed so that they don't ever need to process.
+ - refactor: Replaces the speed_process var with two new variables. The processing_flags
+ variable stores bitflags with information about a machine's preferences on when
+ it should start processing. The subsystem_type var holds the path to which type
+ of subsystem that machine should use.
+2021-06-02:
+ jupyterkat:
+ - bugfix: makes machines work as intended again
+2021-06-03:
+ ivanmixo:
+ - tweak: blind people can now feel around themselves to examine stuff
+2021-06-04:
+ Archanial:
+ - tweak: Volume pumps has been replaced by normal pumps in meta's SM.
+ - bugfix: Fixed rare runtime when you die without client
+ - bugfix: fixes equipment not spawning
+ Bokkiewokkie:
+ - rscadd: Makes multi-z pipe adaptors compatible with piping layers.
+ Ivniinvi:
+ - tweak: Updated the Freezer/Heater board, now named Thermomachine board.
+ Kylerace, r1ks-iwnl:
+ - bugfix: Autolathes can't create items for cheaper than what they recycle for.
+ Naevi, ivanmixo:
+ - imageadd: New roboticist enviro and jump suits
+ Penwin0:
+ - rscadd: Gaseous Pores, a new changeling ability
+ PowerfulBacon:
+ - tweak: Head revs now use a normal flash
+ - tweak: When a head rev uses a flash it will gain charges.
+ Vasily2013:
+ - tweak: alters Pubby's SM/TEG setup.
+ - bugfix: engineers should now be capable of not exploding on Pubby even if they're
+ incompetent.
+ ivanmixo:
+ - imageadd: adds an inhand sprite for the hos gun set to stun (tazer) mode
+ - bugfix: skeleton and zombie cloning now works properly
+ - tweak: skeletons and zombies cellular damage from cloning now gets transferred
+ to brute upon forceful ejection
+ kit-katz:
+ - bugfix: when put on your head cats now render 100% of the time
+ - bugfix: normal corgies can now be put on your head not only Ian
+ - bugfix: kittens can now be put on your head
+ kylerace:
+ - rscadd: Added a few more holodecks
+ - refactor: Nanotrasen overhauled much of the holodecks programming so that it didnt
+ have to keep physical copies of the simulations anymore.
+ - code_imp: holodeck uses map templates instead of area_copy
+ r1ks-iwnl:
+ - rscadd: Preference to switch the walk hotkey between held down and a toggle inside
+ the stats menu's preference tab.
+2021-06-05:
+ PowerfulBacon:
+ - bugfix: Recharger animation has been fixed.
+ ivanmixo:
+ - bugfix: fixes robo jumpsuit error
+2021-06-06:
+ MNarath1:
+ - bugfix: fixes airlock alpha filter breaking on 514 clients
+ PowerfulBacon:
+ - rscdel: Reverts the preference to switch walk hotkey toggle
+ - rscadd: Virtual z-levels
+ - tweak: Each shuttle will be treated as a different z-level, meaning things that
+ affect a z-level (Comms, suit sensors, AI upload, explosions, heretic tracking
+ etc.) will no longer be able to track different shuttles while they are in transit.
+ Sarchutar:
+ - bugfix: Syndicate turrets have lost their cover and can fire once again
+ Thatguythere03, Jimmy Brickets:
+ - rscadd: a new clown mask based of Madness Combat's Tricky! (Improbability drive
+ not included)
+ francinum:
+ - bugfix: Storage items now dump their contents before being deconstructed in the
+ autolathe (Or other machines that operate with the same system)
+ - admin: You may see an additional confirmation box when issuing bans. Please actually
+ read it.
+ jupyterkat:
+ - bugfix: fixes a reag explosions runtime
+2021-06-07:
+ Archanial:
+ - admin: Admins can edits expired and removed bans
+ Crossedfall:
+ - code_imp: Max compile version bumped to 514.1556
+ - server: Removed multiple TGS discord commands
+ KazooBard:
+ - config: Player requirement for heretics bumped to 25
+ PowerfulBacon:
+ - rscadd: Corgstation 9x10 holodeck
+ willox,LemonInTheDark:
+ - code_imp: Updates the debugger to use auxtools over extools.
+2021-06-08:
+ Malgover:
+ - bugfix: resprite of riot armor
+ PowerfulBacon:
+ - bugfix: Fixes virtual z-level shuttle issue, and space area issue.
+ ivanmixo:
+ - imageadd: New mime jumpsuit sprite.
+ jupyterkat:
+ - bugfix: fixed breathing
+2021-06-09:
+ jupyterkat:
+ - bugfix: can't make scooters from holodeck skateboards anymore
+2021-06-10:
+ Kapu1178:
+ - tweak: Moves Adrenaline Nanites back to Augmentation tab. (Doesn't change unlock
+ requirement)
+2021-06-11:
+ fira and Watermelon914:
+ - code_imp: Optimized the input subsystem.
+ jupyterkat:
+ - rscadd: tgui notes
+2021-06-12:
+ MCterra10:
+ - rscadd: Rebases to AuStation codebase
+ zeskorion:
+ - rscadd: cat surgeon now announces how many victims he took when he dies
+ - tweak: cat surgeon now starts off weaker, but becomes far more powerful the more
+ victims he claims
+ - tweak: cat surgeon's ai has had major changes. we will see if this works or not.
+ he should now target weaker targets, and keep his distance from targets he can
+ shoot unless they are significantly weakened
+ - balance: cat surgeon trauma is now easier to heal
+ - balance: cat surgeon cant be pulled around, making it harder to cheese him or
+ grief with him
+ - bugfix: cat surgeon's passive healing when not attacking works now
+2021-06-13:
+ Mothblocks, dragomagol, InsaneRed, Archanial:
+ - rscadd: Balloon alerts - small runechat like pop ups while performing specific
+ actions, which can be toggled in preferences
+ - rscadd: Balloon alerts to some mech actions, door remote and beakers
+ - rscadd: Balloon alerts to storage, guns, energy swords, reagent containers, constructing
+ walls and tools
+ francinum:
+ - admin: Additional data about players can be found in the Player Panel.
+2021-06-14:
+ Froststahr:
+ - bugfix: A couple of recent changes have been reverted, movement should be more
+ consistent again.
+ lordScrubling:
+ - bugfix: showing up as an unknown if you had a nameless id in your second tablet
+ slot
+ - bugfix: magician's invisibility cloak action button not showing up
+2021-06-15:
+ Archanial:
+ - bugfix: Fixed balloon chat pref not saving
+ PowerfulBacon:
+ - balance: Cyborg flash nerfed to 3 charges and auto-recharge every 10 seconds.
+ - tweak: Revolutionary flash changed to 10 charges and auto-recharged every 15 seconds.
+ - balance: Default flashbulbs nerfed from 15 flashes to 10.
+ Qustinnus, That0nePerson:
+ - rscadd: Adds hygiene bot! Will chase you down and shower bloody people, and can
+ be emagged to spit fire.
+ bloons3:
+ - code_imp: Uses more TRUE/FALSE defines in silicon.dm
+ - admin: Adds logging for impacted cyborgs during law changes
+ - admin: Adds additional cyborg logging
+ eeSPee:
+ - tweak: Corgium corgis will now wear your hat. You will get your hat back when
+ you return to human.
+ - bugfix: Corgium corgis will keep your name when they put on a hat
+ - bugfix: Pet beacon pets' real name is now the name you assign them
+ - tweak: Moved holodeck templates from _maps/template to _maps/holodeck.
+ - bugfix: Fixed some maps that were not working properly.
+ - bugfix: Fixed heretic ghouls having reduced max health after being revived after
+ deghouled
+ - tweak: heretic ghouls now have a little moodlet tooltip
+ ike709:
+ - bugfix: Fixed the input subsystem occasionally runtiming and breaking entirely.
+ ivanmixo:
+ - bugfix: Mint toxin now actually works
+ lordScrubling:
+ - rscadd: balloon alerts when alt clicking atmos devices
+2021-06-16:
+ Archanial:
+ - bugfix: Fixes flashlights attached to guns not displaying a message.
+ - bugfix: Fixes shooting someone with syringe not displaying a message.
+ - bugfix: Fixed balloon alerts preference (again). Your preference might've been
+ reset.
+ - refactor: Disease properties are removed, each property has its own var.
+ - refactor: Disease severity is renamed to danger.
+ - refactor: Disease transmittable is renamed to transmission.
+ Mat05usz:
+ - bugfix: 'Pubby: SM no longer delams roundstart'
+ eLeCtrOssSnake:
+ - tweak: make spray cans recyclable again, while preserving the ability to paint
+ autolathe
+ eeSPee:
+ - rscadd: Alteration curse replace paralysis curse, available for Ash and Flesh.
+ - rscadd: Amulet of Digital Invisibility replaces corrosion curse, available to
+ Ash and Rust, craftable from a shard and circuitboard.
+ - rscdel: Paralysis curse and Corrosion curse are now removed.
+ - tweak: Digital camoflage is now a trait.
+ ivanmixo:
+ - rscadd: Civilian Skirts!
+ - rscadd: base for skirts
+ - imageadd: sprites for the civilian skirts
+ lordScrubling:
+ - rscadd: construction bags from tg
+ - rscadd: sheet snatcher crafting recipe
+ - rscadd: atmos holofan in chief engineer's locker
+ - balance: sheet snatcher holds 150 instead of 300 sheets
+ - bugfix: borg sheet snatcher now holds true amount of sheets
+2021-06-17:
+ Archanial:
+ - bugfix: FIXED DISEASES
+ eeSPee:
+ - rscadd: Added new holodeck programs.
+ - bugfix: Fixed some gang items not working properly
+ - bugfix: Fixed gang spray/territory takeover bugging out
+ - bugfix: Fixed reinforcements not working
+ - balance: Gang revival implant no longer aheals/revives
+ ivanmixo:
+ - bugfix: All the lawyer skirts now actually have sprites
+ - rscadd: Adds botany disks, prisoner IDs and a bunch of sinks to Corg
+ - rscadd: Det office starts with sechudglasses
+ - bugfix: The Cell 3 display is now actually connected to Cell 3
+ - bugfix: Captain can now unlock his display case
+ - bugfix: Fixes the floating intercom near turbine
+ - bugfix: Fixes the disposals pipe under holodeck
+ - bugfix: Engi maint is now properly shielded from radstorms
+ - tweak: The experimentor console is closer to the experimentor
+ jupyterkat:
+ - bugfix: operating computers now correctly show surgery procedures on stasis beds
+ lordScrubling:
+ - rscadd: Magboots keep you from being flung by high pressure pipes when unwrenching
+ - bugfix: fixed clown mime and non replacement miner envirohelms using the wrong
+ welding screen sprites
+ - bugfix: fixed drawing a smile on a clown envirosuit helmet using the wrong sprite
+ - rscadd: Laptops eject their ids, data disks, and ai cards if you alt click them
+ while their lid is down now
+ - bugfix: runtime when checking tablet with one id
+2021-06-18:
+ Archanial:
+ - bugfix: Fixed spyglasses doing nothing.
+ KubeRoot:
+ - code_imp: Timer subsystem can now have subtypes
+ PowerfulBacon:
+ - tweak: Teratomas can no longer have DNA and cannot be humanized through mutadone
+ or radiation.
+ - tweak: Radio jammer changed to signal jammer
+ - tweak: Radio jammer price increased from 3 TC to 5 TC
+ - tweak: Signal jammer blocks most outgoing wireless signals.
+ - code_imp: Moves jammer check into a proc on atoms, so it can be overridden and
+ used anywhere.
+ SandPoot, Jupyterkat:
+ - rscadd: Adds a fancy TGUI interface for the cloning computer.
+ - rscadd: Cloning console boards now retain records
+ - rscdel: Destroys the old cloning interface.
+ - tweak: Alt-Click now removes disks from the cloning computer.
+ - refactor: Replaced way too much code for the cloning computer.
+ - refactor: Cloning scan's implant now outputs a list if desired.
+ - refactor: Experimental cloning is no longer a copypasta of cloning
+ covertcorvid:
+ - code_imp: Added two_handed component
+ - refactor: Updated all existing two handed items to use the new component
+ eeSPee:
+ - tweak: Splits Adrenaline Nanites into Adrenaline Nanites and Amphetamine Burst
+ - balance: Adjusted costs of Adrenaline Nanites to compensate for the nerf
+ hatREALLYgoodsodaflavor, StyleMistake, Qustinnus:
+ - rscadd: Implements sound environments into the game. Reverb time.
+ - refactor: Ambience is now in a subsystem, and plays every now and then without
+ you having to move to a new area for it to play
+ ivanmixo:
+ - bugfix: You can no longer steal unstealable shoes
+ - bugfix: Meta's kitchen alarm no longer goes off roundstart
+ - tweak: reverted the gliding on unconscious people
+ - rscadd: Cargo skirts
+ - imageadd: Sprites for cargo skirts
+ jupyterkat:
+ - rscadd: hidden admin music no longer shows a title on the music player.
+ lordScrubling:
+ - bugfix: alarm manager now shows alarm changes from when it was off
+ - bugfix: fire alarms clear when you pull them up now
+ - rscadd: keybind for resting
+ - rscadd: keybind for suit storage quickdraw
+ - rscadd: centcom envirosuits
+ - tweak: moved some job items to left hand
+ - bugfix: plasmamen dying as certain ert roles
+ yorii:
+ - tweak: tweaked the contents of toxin firstaid kits
+2021-06-19:
+ ImSynthex:
+ - rscdel: Removed sausage eatverb "deep throat"
+ covertcorvid:
+ - bugfix: fixed toy DEsword damage
+ - bugfix: fixed icons for twohanded items
+2021-06-20:
+ Sarchutar:
+ - bugfix: Ascend as a Dark Spirit function of the Spirit Realm rune is working correctly
+ again
+ ivanmixo:
+ - bugfix: Debtor martial art no longer bypasses pacifism
+2021-06-21:
+ Archanial:
+ - rscadd: Arming nuke has 5 second cooldown.
+ floyd, amonkeythatcodes, mat05usz:
+ - code_imp: Resisting is now on atom level so it can be used on mobs too
+ jupyterkat:
+ - balance: Ais can't strip now
+2021-06-22:
+ Archanial:
+ - spellcheck: Welders will now be turned on instead of just being on.
+ - bugfix: Fixed spawners menu runtime.
+ KubeRoot:
+ - spellcheck: Plating examine message now reflects rods for catwalks and sheets
+ for reinforcement accurately
+ - bugfix: Mindshield no longer gives duplicate message when implanted
+2021-06-23:
+ Archanial:
+ - rscadd: Cooldown to cargo console
+ - bugfix: Fixed cloning console UI breaking when trying to scan human with no mind.
+ - bugfix: Fixed component runechat runtime (maybe!)
+2021-06-24:
+ Cenrus:
+ - rscadd: Added eighties tiles as an arcade reward
+ - bugfix: Fixed arcade screen overlay sprites
+ - bugfix: You can now skip the old ship arcade event
+ Cenrus, MrDoomBringer:
+ - balance: Reduced recharge time of the syndicate teleporter to 15 seconds (previously
+ 20)
+ - rscadd: Added a recharge timer to the syndicate teleporter that is shown on examine
+ - rscadd: Added a sound to indicate that the teleporter has recharged
+ KubeRoot:
+ - bugfix: Fixed shock paddles being insertable in BoHs
+ Naevii, ivanmixo:
+ - rscadd: Head of security, warden, brig physician and detective skirts
+ - rscadd: Sec members' uniforms to their respective lockers
+ SuperNovaa41:
+ - bugfix: lithium no longer make you walk out of a mech
+ francinum:
+ - bugfix: Some sources of hallucination should no longer cause infinite hallucinations.
+ - bugfix: The Crematorium now actually requires access to trigger.
+ jupyterkat:
+ - bugfix: fixed cloning
+ - bugfix: cloning logs stuff correctly again
+ lordScrubling:
+ - rscadd: Tend wounds estimates remaining time, even better with an analyzer in
+ hand
+ mickyan, watermelon914:
+ - bugfix: Bombproof containers should once again protect their contents from being
+ damaged by explosions
+ - bugfix: Fixed explosions delimbing you if they're weaker
+ timberpoes:
+ - tweak: DNA Consoles have received hardware upgrades and have a shiny new interface
+ as a result.
+ - tweak: New hardware has dropped legacy support for reading the genetic sequences
+ of the dead and has only limited support for reading the genetic sequences of
+ monkeys.
+ zxaber:
+ - bugfix: Mechs can move in space if near a wall again.
+ - bugfix: Mech thrusters no longer incorrectly fail if the mech is near a wall.
+2021-06-26:
+ jupyterkat:
+ - bugfix: fixed cloning message
+2021-06-27:
+ eeSPee:
+ - bugfix: Corg cameras in ai sat are now on the ai sat network.
+ lordScrubling:
+ - bugfix: buckling not working when fireman carrying or piggy backing
+2021-06-28:
+ Froststahr:
+ - rscdel: The new soft crit system has been reverted
+ 'Stylemistake, Cyberboss, SpaceManiac, other TG coders, Froststahr port. ':
+ - code_imp: TGUI has been upgraded to 4.3 and now includes typescript.
+ - code_imp: Custom SVG icons are now easy to do as well.
+ - bugfix: All tgui windows will resize automatically, and can now be resized as
+ will.
+ - server: TGS will require adaptation.
+ Vexylius:
+ - balance: Wabbajack shuttle is now illegal on Sage
+ francinum:
+ - rscdel: Alt-click no longer provides an icon preview for performance reasons.
+2021-06-29:
+ Archanial:
+ - rscadd: Station traits are config option.
+ Ivniinvi:
+ - spellcheck: fixes a typo on Safety Moth poster
+ Vexylius:
+ - rscadd: Nitrile gloves for MDs and brig phys in the starting loadout
+ - tweak: Corgstation changes around medical
+2021-06-30:
+ Ivniinvi:
+ - spellcheck: fixed a typo in the hos' trenchcoat
+ Naevii, ivanmixo:
+ - rscadd: Science skirts
+ - bugfix: Adds missing hop skirt to hop locker
+ - code_imp: Removed leftover skirts with no sprites and moved robotics suits to
+ the appropriate file
+ PowerfulBacon:
+ - bugfix: Fixes entertainment monitors.
+ eeSPee:
+ - balance: Heretics can no longer get free charges by making new heretic books
+ ike709:
+ - tweak: The (strongly) recommended client version for 514 users is now 1557. Previous
+ versions have severe potential UI bugs.
+ ivanmixo:
+ - balance: The Mosin and WT rifles are now bulky
+ - balance: The mosin now requires a free hand to fire
+2021-07-01:
+ Archanial:
+ - rscadd: SIGNAL_HANDLER to all procs meant to handle signals
+ - rscdel: Removed SIGNAL_HANDLER_DOES_SLEEP
+ - rscdel: Some sleeps
+ 'KathyRyals ':
+ - rscadd: The Honkworks 5.0 cartridge's software has been updated to be able to
+ hack machines, computers and airlocks to make them honk for a while.
+ - code_imp: Added a new sound_player component to be able to play any sound with
+ any signals.
+ MNarath1:
+ - rscdel: Removes a now completly unused define
+ jupyterkat:
+ - bugfix: fixed statpanel being empty during init
+2021-07-02:
+ MCterra10:
+ - bugfix: lumi eyes can be turned off again
+ francinum:
+ - server: Notices are more clear.
+ ivanmixo:
+ - bugfix: You can no longer eat thru the miner gas mask
+ lordScrubling:
+ - bugfix: explorer mask toggle
+2021-07-03:
+ Archanial:
+ - bugfix: Fixed ORM runtime.
+ - bugfix: Fixed runtime related to removing item from shells
+ - rscadd: Hangover, random spawn and late join station traits now have announcements.
+ - bugfix: Almost empty beer bottles
+ - refactor: Not providing any sound to priority_announce will make it silent.
+ PowerfulBacon:
+ - balance: Stabilized light pink extract speed reduced from 2x increase to 1.5x
+ increase.
+ - balance: Stabilized light pink extract now applies pacifism to its user.
+ Watermelon914, Mothblocks:
+ - rscadd: Wiremod, circuit replacement.
+ actioninja, lordScrubling:
+ - imageadd: overlay sprites from tg
+ - imagedel: old impaired sight overlays
+ ivanmixo:
+ - tweak: Solder now stops hallucinations as well
+ lordScrubling:
+ - bugfix: paramedic envirohelm appearing invisible with light on
+ - imageadd: paramedic envirohelm light state
+ - imageadd: basic envirohelm inhand sprite from tg
+ - imagedel: old basic envirohelm inhand sprite
+2021-07-04:
+ ike709:
+ - code_imp: Nuked /client/MouseMove() for a massive amount of free performance.
+ lordScrubling:
+ - bugfix: explorer mask being larger when adjusted down than while adjusted up when
+ it should be the other way around
+ - bugfix: adjusting a mask thats not on your face turning off your internals
+2021-07-05:
+ Archanial:
+ - rscdel: Remove xray from genetics.
+ MNarath1, MrMelbert:
+ - bugfix: fixes contractor tablets breaking when targets cryo
+ Naevii, ivanmixo:
+ - rscadd: Skirts for medical
+ - imageadd: Sprites for the medical skirts
+ - tweak: Gives the paramedic uniform a more descriptive name
+ PowerfulBacon:
+ - refactor: Refactors gamemode loading to select and setup maps for gamemodes 30
+ seconds before roundstart.
+ - bugfix: Reebe loading
+ - bugfix: Items being too big on clockcult
+ - bugfix: Clockcult camera runtime
+ - balance: Clockwork weapons are stronger when fighting near the ark.
+ - config: Raises clockcult probability to 2
+ - refactor: sword and spear are no longer two-handed weapons.
+ ike709:
+ - tweak: Diagonal movement is now faster, by correcting the delay from an arbitrary
+ 2x to the correct sqrt(2).
+ ivanmixo:
+ - bugfix: nanite program hub and programmer to be deconstructed can now be deconstructed
+2021-07-06:
+ Archanial:
+ - bugfix: fixed multitool
+ francinum:
+ - config: Default map ideal pop bounds adjusted.
+ jupyterkat:
+ - balance: firefighters are now radproof
+2021-07-07:
+ Froststahr:
+ - bugfix: Some TGUI mistakes have been corrected, panel, air alarm, and contractor
+ uplink should look good again.
+ - tweak: Uplink interface has been modified in looks, and will auto-focus
+ ivanmixo:
+ - bugfix: Replaces the pipe painter with a hand labeler in corg's experimentation
+ lab
+ - tweak: Cargo and service now have techfabs instead of protolathes on corg
+ jupyterkat:
+ - bugfix: updated pubbystation's holodeck
+2021-07-08:
+ AMonkeyThatCodes, Qustinnus, Mat05usz:
+ - rscadd: Datumized AI.
+ - rscadd: Monkeys are now more 'intelligent' and scary.
+ - soundadd: New monkeys sounds for screeching.
+ Archanial:
+ - balance: You can't speak on radion while in crit.
+ - bugfix: Messages will no longer appear twice for ghosts if the target is speaking
+ on radio,
+ KubeRoot:
+ - tweak: Operation computers only recognize humans or monkeys as patients.
+ Mothblocks, Froststahr port:
+ - code_imp: Asset URL mappings are now cached to improve performance
+ Raven-Industries:
+ - bugfix: fixed syntax
+ - code_imp: changed ^ to ** in lighting.dm and ghost_role.dm
+ francinum:
+ - bugfix: Minor runtime patch
+2021-07-09:
+ AnCopper:
+ - rscadd: Added commando magboots, given to Deathsquads
+ - tweak: Moved Deathsquad, Deathsquad Officer, and Juggernaut outfits to the ERT
+ outfit file.
+ Kapu1178:
+ - bugfix: Voice Sensor Nanites
+ - bugfix: Traitor codewords (thanks bosnia)
+ - refactor: Alot of on_hear() changed to signals
+ actionninja:
+ - rscadd: New filter editor for admins + development purposes
+ francinum:
+ - admin: Custom map template uploads should now work properly.
+ ivanmixo:
+ - tweak: there is now a delay to the application of the gender change potion
+2021-07-10:
+ DatBoiTim:
+ - tweak: Cryostatic Shells now at sec lathe instead of medlathe
+ lordScrubling, zxaber:
+ - rscadd: Fission360 and Lifeline program for modular computers
+ - rscadd: Nuclear operatives start with a tablet instead of a pinpointer
+2021-07-11:
+ 'actioninja, Floyd, LemonInTheDark ':
+ - rscadd: New filter editor for admins + development purposes
+2021-07-12:
+ PowerfulBacon:
+ - balance: Sonic jackhammer can no longer break reinforced, cult and plastitanium
+ walls.
+ francinum:
+ - bugfix: You can now properly load shuttle templates via the template upload system
+2021-07-13:
+ Froststahr:
+ - admin: The diconnected typo is gone
+ Mat05usz:
+ - bugfix: Monkeys now retaliate against people who throw stuff/shoot at them.
+ - bugfix: Monkeys no longer try to steal undroppable items.
+ Qustinnus, 4Dplanner, Mat05usz:
+ - rscadd: Adds map_generator datums, which will handle terrain generation for a
+ specific area.
+ - rscadd: Adds a jungle generator datum, which uses rust-g noise to generate 3 layers
+ of perlin noise to make a somewhat cohesive and seamless jungle based on height,
+ humidity and heat.
+ kylerace:
+ - bugfix: fixed holodeck harddels
+ lordScrubling:
+ - rscadd: brass sword emp works on mechs
+ - rscadd: clockwork armaments envirosuit compatibility for plasmamen
+ - rscadd: visual effect on brass sword emp
+ - bugfix: brass sword emp not working
+2021-07-14:
+ Archanial:
+ - bugfix: Fixed game hanging itself in lobby
+ KubeRoot:
+ - rscadd: You can now use the Activate Held Object and Drop Object hotkeys (default
+ Z and Q, respectively) to activate arm-implanted tools and put objects away.
+ You must have the hand free before you can activate it this way.
+ - balance: Arm implant tools are no longer no-drop, and instead simply snap back
+ if they get dropped. Don't sweat it, if you slip just press Z again.
+ MNarath1:
+ - bugfix: fixes ai multicamera mode turn static
+ Phil Smith:
+ - imageadd: Gives the circuit multitool its own sprite instead of it having the
+ same sprite as the normal multitool.
+ ivanmixo:
+ - bugfix: Tinea Luxor now actually makes you glow
+ - admin: added logging for antimatter
+ - admin: grouped singulo, antimatter and supermatter logs into one
+ - rscadd: Captain skirt
+ - bugfix: Cargo skirt now works properly with digitigrade legs
+ - tweak: DNA saboteur can no longer give positive mutations
+2021-07-15:
+ Ivniinvi:
+ - tweak: Brain trauma lines now reference Beestation servers.
+ MNarath1:
+ - bugfix: fixes logging of xenomorph neurotoxin spit not showing shooter
+2021-07-16:
+ Archanial:
+ - bugfix: Cult pylons heal 1 damage per tick again.
+ Ivniinvi:
+ - bugfix: cargo pack descriptions are now accurate
+ KubeRoot:
+ - rscadd: You can now drag objects in the stat-panel
+ - rscadd: Smoke machines can now be rotated with alt-click
+2021-07-17:
+ TheChaser212:
+ - rscadd: Food has a chance to be cooked when set on fire
+ - bugfix: The temperature of reagents in smoke/foam isn't reset
+ - rscadd: Added scanner circuit shell
+ - tweak: Added a user and triggered port to airlock shells
+ - tweak: Added a payer port to moneybot shells
+ jupyterkat:
+ - bugfix: fixed request console announcements announcing html garbage
+ lordScrubling:
+ - bugfix: witch hunter hat having no armor
+ - rscadd: spectre inspector goggles ghost tracking is toggled again
+2021-07-19:
+ KubeRoot:
+ - tweak: You can now wear xenobio pressure-proofed jumpsuits in space.
+ PowerfulBacon:
+ - bugfix: Fixes blocking thrown items.
+ - tweak: admins can choose to instantly trigger meteors
+ TheChaser212:
+ - rscadd: Monkeys are now pressure/temperature proof based on their hat
+ jupyterkat:
+ - code_imp: Optimized radio messages
+ lordScrubling:
+ - rscadd: Ability to construct showers and sinks
+ - bugfix: phantom ids when you eject an id from a modular pc
+ lordScrubling, uomo91, Timberpoes:
+ - rscadd: ability to buckle from next to a chair
+ mcmeiler:
+ - rscadd: Added a positive moodlet for headpats
+2021-07-20:
+ Ivniinvi:
+ - bugfix: Space Adapted Apids no longer sleep in the cold.
+ ivanmixo:
+ - balance: baseball bats can no longer stunlock at all
+ jupyterkat:
+ - bugfix: fixed hearing
+ - bugfix: fixed photobooth deck breaking the entire holodeck
+ lordScrubling:
+ - spellcheck: incorrect sink/showerframe description
+2021-07-21:
+ YakiAttaki:
+ - spellcheck: removed a spurious space from a moth default name
+ francinum:
+ - admin: Mentor panel edits now take effect immediately.
+ jupyterkat:
+ - bugfix: fixed beach holodeck
+ - bugfix: fixed soporific ammo breaking borg movements
+2021-07-22:
+ Naevii, ivanmixo:
+ - imageadd: New lawyer suit and skirt sprites
+2021-07-23:
+ 'ArcaneMusic, ShizCalev ':
+ - rscadd: Nanotrasen's Customer Service Division has developed a new, shielded product
+ cage to better allow for sale of food and drink items on station, called the
+ "Vend-A-Tray".
+ - rscadd: Vend-A-Trays are made with display case frames and a manipulator stock
+ part.
+ - bugfix: Fixed display cases going invisible when you put things inside of them.
+ - bugfix: Fixed the pedestal for open display cases being glass colored.
+ MCterra10:
+ - rscdel: Remove fastmos changes from all maps
+ TheChaser212:
+ - bugfix: Changeling powers that require absorptions actually need them now
+ ike709:
+ - bugfix: Fixed a small chance for bloodcult to runtime and ruin gamemode setup.
+ - code_imp: Replaced extools with auxtools.
+ - rscadd: Added Putnam's auxmos, which is like TG's atmos but incredibly performant.
+ - rscdel: Removed fastmos, because it's no longer maintained by anybody and it's
+ very unstable.
+ - code_imp: Atmos is now even more performant than fastmos was.
+ - tweak: Firelocks require a crowbar to open/close again.
+ - tweak: Firelocks are fulltile again.
+ - bugfix: Squids and snails will no longer set the CPU on fire when they stand on
+ salt.
+ - tweak: Squids and snails receive a flat 10 burn damage for stepping on salt, rather
+ than continuously taking damage.
+ ivanmixo:
+ - rscdel: old "inflict_handler" file
+ - bugfix: blind spell works again
+ - refactor: refactors the blind spell to not be ancient
+ lordScrubling:
+ - bugfix: fixes bz formation using no2 instead of n2o, anesthetic tanks having no2
+ inside of them instead of n2o, n2o gas miners producing no2, etc
+2021-07-24:
+ Cenrus:
+ - bugfix: Fixes lens not dealing intended amount of damage to mechs
+ - bugfix: Prism not healing as much as intended
+ Froststahr:
+ - tweak: The map airlocks have now been fully reverted to the old system
+ Sinestia:
+ - bugfix: Fusion has been fixed
+2021-07-25:
+ Cenrus:
+ - bugfix: Fixed antivirus muting IPCs.
+ ImSynthex:
+ - rscadd: Added cyborg recharger to Deltastation dorms bathroom
+ Ivniinvi:
+ - rscadd: Adds a delay to inserting equipment into a mech (1.5s)
+ MNarath1:
+ - code_imp: Rewrote the slime processor to be more performant
+ Mick1299:
+ - tweak: Increase Janitor borg light replacer restock rate
+ PowerfulBacon:
+ - rscadd: Adds in chat badges for admin ranks, mentors and donators.
+ - balance: Door crushing no longer stuns if you are already stunned, preventing
+ chain stunning.
+ - balance: Door crushing stun time reduced from 10 seconsd to 6 seconds.
+ - balance: Door crushing damage reduced by chest armour
+ - refactor: Illegal tech is now given to items bought from the uplink, rather than
+ items that exist in the uplink.
+ - refactor: 'Refactors embedding:'
+ - refactor: Using a hemostat allows you to remove embedded items with no damage
+ applied.
+ - refactor: 'You can now remove embeds from other people ghetto style: Using a screwdriver
+ allows you to remove small and tiny embedded items with an 80% success rate,
+ and using wirecutters allows you to pull out normal, small or tiny embedded
+ items with a 50% success rate.'
+ - refactor: Embeds no longer apply damage constantly, instead they will apply damage
+ until you go under their damage limit (Essentially creating the effect of lowering
+ maxhealth while applied, but alone will not out right kill you).
+ - refactor: Embedded items no longer deal damage when they embed, the impact from
+ the object damages anyway.
+ - refactor: Embeds are blocked by armour, wearing armour reduced the chance of an
+ embed embedding into you. If you have more armour than the embed limit, the
+ embed will fall off, If you have less but still have armour the probability
+ of the embed failing will be decreased linearly proportional to the amount of
+ armour you have.
+ actioninja, LemonInTheDark, and MrStonedOne:
+ - code_imp: Performance is now logged to a CSV
+ - code_imp: SendMaps can now be profiled with a Debug verb.
+ - code_imp: Improved SSprofiler, and makes it only run every 5 minutes
+ eeSPee:
+ - tweak: Standardizes deputy passes and mining access cards to derive from the same
+ subclass.
+ ivanmixo:
+ - bugfix: wiki button works again
+ jupyterkat:
+ - bugfix: fixed get hearing again
+ lordScrubling:
+ - imagedel: duplicate old default envirohelm sprite
+ - rscadd: you can buckle to stasis units and beds adjacently now
+ - bugfix: borg appartus drop work with alt click now
+ - rscadd: Captain and sec/brigphys plasmamen start with sechailer on
+ - bugfix: Sensors start maxed on cap and sec/brigphys envirosuits
+2021-07-26:
+ Fikou:
+ - rscadd: bronze airlocks and windows
+ Ghilker, lordScrubling:
+ - rscadd: atmos holosigns hold open firelocks
+ PowerfulBacon:
+ - tweak: Added a library to corg shuttle, added status displays and added a sink
+ - bugfix: Fixed corgstation shuttle windows.
+2021-07-27:
+ KubeRoot:
+ - rscadd: Stack menu now uses tgui and has a search bar.
+ - bugfix: Metastation engineering is now named more accurately
+2021-07-28:
+ Bluezorua and lordscrubling (thanks for unshitting my code):
+ - rscadd: BEEF BROTH
+ - rscadd: BEEF FIZZ
+ - imageadd: BEEF BROTH
+ Ivniinvi:
+ - bugfix: DeltaStation bar disposals now actually works
+ eeSPee:
+ - rscadd: 'Added new malf ai module: Fake Alert'
+ lordScrubling:
+ - rscadd: sterilizers speed up surgery above cap
+ - rscadd: hand mirrors help with self surgery
+ - tweak: surgery scales speed before starting to fail
+ - balance: operating tables give better surgery chances than stasis units
+ - bugfix: getting the lowest surgery multiplier from a structure when being on multiple
+ structures
+ - bugfix: shields not working in offhand
+ mcmeiler:
+ - rscadd: Added medical berets to MediDrobes
+ yyzsong:
+ - bugfix: Boxstation detective disposals works again. Chainsmokers rejoice.
+2021-07-29:
+ AnCopper SomeguyManperson:
+ - bugfix: After about 2 years, X4 is directional again.
+ Peer:
+ - rscadd: Dadbot lawset
+2021-07-30:
+ ivanmixo:
+ - bugfix: wizard can buy blind spell again
+2021-07-31:
+ Phil Smith:
+ - tweak: Hotdog weenie
+2021-08-01:
+ Ivniinvi, Randomguy523:
+ - rscadd: pAIs can now be emagged to reset their master.
+ PowerfulBacon:
+ - refactor: Chat messages are now shared between people that see the message the
+ same, reducing the amount of lag caused due to chat messages on high pop.
+ - refactor: Refactors stat panel verbs to cache the verbs.
+ lordScrubling:
+ - bugfix: sparks and igniters not igniting gas
+2021-08-02:
+ Ivniinvi:
+ - rscadd: Sentience potion spawns are now clearly marked in the ghost role popup.
+ PowerfulBacon:
+ - refactor: Refactors TGUI window auto updating.
+ - refactor: TGUI Windows no lonegr auto update by default, and instead need ui_update
+ to be called, or to have their autoupdating var set to TRUE by using set_autoupdating(TRUE)
+ - refactor: Spawners menu is now a single instance rather than on every single ghost.
+ - bugfix: Deaf people can no longer see runechat messages.
+ - code_imp: Removes a pointless loop from get_all_open_uis()
+ ivanmixo:
+ - bugfix: fixed false positive quirk cap trigger
+2021-08-04:
+ Ivniinvi:
+ - rscadd: Sleepers and operating computers now automatically update their UIs.
+ - rscadd: Gene console now correctly shows cooldown for joker and scramble.
+ ReneAngel (Sprite), ImSynthex (Github):
+ - imageadd: Replaced station bounced radio sprite
+ ivanmixo:
+ - tweak: entertainment monitors now need to be examine to toggle the UI instead
+ of clicked
+ - rscadd: ghosts can now examine wires to check their power status
+ - bugfix: the cancel button on the supply requests console now works again
+ mcmeiler:
+ - bugfix: Crafting UI updates after crafting
+ - bugfix: Fixed the beecoin shop nurse outfit spawning in-game.
+2021-08-05:
+ Cenrus:
+ - bugfix: Fixed nurse/midwife spider wrap action button
+ Ivniinvi:
+ - rscadd: Nuke/Self-Destruct UIs now autoupdate.
+ - rscadd: Adds a description to the viro/maints teratoma spawner.
+ - bugfix: You can no longer use a soulstone on soulless targets.
+ - tweak: Contents of DeltaStation kitchen were rearranged but not changed.
+ - rscadd: A pAI's master can now clear the pAI's zeroth law.
+ Raven-Industries:
+ - bugfix: fixed brig timer's UI not updating properly
+ ivanmixo:
+ - bugfix: Fix floating light on the lavaland sci post and a missing wire in Box
+ maint
+ - tweak: SciDrobe instead of a blank computer on the lavaland sci post
+ - rscdel: Removed the Pheromone Receptor ability from changelings
+ - bugfix: fixed some runtimes related to outfits and limbs
+ tiramisuapimancer:
+ - tweak: ethereals are now physically capable of wearing underclothing
+2021-08-06:
+ Dingo-Dongler:
+ - bugfix: Luminescents and stargazers have their actions working again
+ Ivniinvi:
+ - rscadd: pAI PDA messages now show up as actually from a person, and can be replied
+ to
+ - rscadd: Mediborg's Amputation Adventure can now be emagged.
+ Kapu1178:
+ - code_imp: wzhzhzh()
+ KubeRoot:
+ - tweak: Round start timer in the stat-panel now tracks time accurately from actual
+ start of round.
+ - bugfix: Defibrillator paddles now behave more consistently in regards to overextending
+ and dropping
+ Rukofamicom:
+ - admin: New bans no longer default to local.
+ TheChaser212:
+ - bugfix: Animals can move after being tabled
+ - bugfix: Cats look like they're resting when changed outside of Life
+ warior4356 AnCopper:
+ - rscadd: Added ERT law board to ERT and Deathsquad commander kits.
+2021-08-07:
+ AnCopper:
+ - tweak: Changed Commando Names.
+ 'AnCopper jc-denthead ':
+ - tweak: You can no longer deconstruct tables or racks on help intent.
+ Froststahr:
+ - rscdel: A few religions whose use would result in risking a ban have been deleted.
+ KubeRoot:
+ - refactor: Bitfields now use tg's macro, separated more cleanly.
+ - refactor: area_flags from tgcl
+ - tweak: Random mineral turfs no longer cause large amounts of turf changes on initialization.
+ - rscdel: Old lavaland cave generation based on straight lines
+ - rscadd: Beautiful new lavaland cave generation from tg, with organic and complex
+ cave systems
+ - bugfix: fix lots of potential harddels, courtesy of tg
+ Naevi:
+ - rscadd: Retro jacket and retro chicken head, 2 new donator items inspired by hotline
+ miami.
+ Pirill:
+ - imageadd: BSRPED inhand sprites
+ - tweak: RPED inhand sprites
+ Stylemistake, Various /tg/ Coders, Froststahr port:
+ - code_imp: TGFont is now working again
+ - code_imp: Juke build is now used when compiling the server, the BUILD.bat must
+ be used
+ - server: TGS instances will require the precompile scripts to be added
+ TheChaser212:
+ - rscadd: Non-humans can be The Fly'd
+ - bugfix: Tier 4 parts work correctly with the teleporter
+ Yorii, Adopted by Froststahr:
+ - rscadd: Smooth movable lighting system implemented. Projectiles, sparks, thrown
+ flashlights or moving mobs with lights should be much smoother and less laggy.
+ - rscadd: Directional lighting is in, applying to certain things like flashlights,
+ headlamps, etc.
+ - balance: Light sources no longer stack in range, though they still do in intensity.
+ - balance: Mechs no longer block sight. It's a non-trivial cost for the lighting
+ system with little to no gain.
+ fighterslam, dapnee:
+ - rscadd: Adds plasmaglass tables from Citadel
+ jupyterkat:
+ - bugfix: fixed hearing AGAIN
+ lordScrubling:
+ - bugfix: blue shirt and tie security uniform in beeshop now has armor
+ - bugfix: prototype replacement envirosuit uses the right smile sprite now
+ - balance: atmos envirosuits inherit radiation protection from engineering envirosuits
+ - balance: only plasmamen can wear replacement envirohelms
+ - spellcheck: fixed some envirosuit typos
+ - rscadd: fireman carry works on monkeys and they can piggyback you
+2021-08-08:
+ Froststahr:
+ - bugfix: CBT in TGS should work again.
+ ivanmixo:
+ - bugfix: medical skirts are now properly oriented
+2021-08-09:
+ Froststahr:
+ - bugfix: Mounted Seclights and swarmers should light up properly again.
+ ivanmixo:
+ - rscdel: Removed bluespace miners from config and game
+ yyzsong:
+ - balance: Welders now burn through one unit of fuel per nine seconds, down from
+ twenty-four.
+2021-08-10:
+ Archanial:
+ - rscdel: Removed non static typecacheof uses in storage component
+ ImSynthex:
+ - balance: Cheap lighter no longer costs more than a Zippo lighter
+2021-08-11:
+ KubeRoot:
+ - bugfix: Plumbing port visuals are now more reliable, fixing a bug with boxstation's
+ roundstart bottlers and presses
+ - bugfix: Unwrenching ducts no longer doubles them
+ PowerfulBacon:
+ - refactor: Heretics can now only spawn on dynamic.
+2021-08-12:
+ Froststahr:
+ - rscdel: Due to errors and poor quality control, the new lighting system has been
+ reverted for now.
+ ImSynthex:
+ - tweak: Laptops and tablets no longer drop batteries when deleted
+ - bugfix: Necro seed stealth 8 ability now works as intended
+ PowerfulBacon:
+ - bugfix: Fixes midround antags not existing
+ super12pl:
+ - tweak: IPCs are no longer an exception from from foreigner quirk.
+2021-08-13:
+ ImSynthex:
+ - bugfix: AIs can no longer strip people
+ MCterra, Arrow768:
+ - server: Complete topic rework to use JSON and just be better in general
+2021-08-14:
+ Crossedfall:
+ - config: Secret_extended's probability has been reduced to 1 (will be replaced
+ with dynamic extended later). Wizard, blood brothers, and IAA have been disabled
+ entirely.
+ Ivniinvi:
+ - bugfix: Muscled Veins surgery now actually works.
+ MCterra10:
+ - bugfix: fixed case-sensitive tokens not working
+ Phil Smith:
+ - imageadd: Ports TG's stasis bed sprite
+ PowerfulBacon:
+ - balance: Disabler beams reduced from 35 damage to 28.(-7)
+ Rukofamicom:
+ - tweak: Medical, Engineering and Science jobs now require two hours played
+ - tweak: Brig Physician now only requires two hours as normal crew
+ - tweak: AI now requires 10 hours as a cyborg
+ - tweak: HoP now requires 10 hours as a different Head of Staff
+ Vexylius:
+ - rscadd: Prescription Medical HUD
+ ivanmixo:
+ - rscadd: handheld pinpointer now shows the name of the person it's tracking
+2021-08-15:
+ MCterra:
+ - bugfix: fix roundstart cultists not getting objectives
+ fighterslam:
+ - balance: Nerfed Frank the turtle's damage from 18 to 5 and the health from 2500
+ (Same as Hierophant) to 100. It can still attack, unlike most other station
+ pets, and won't die in 2 hits, unlike most other station pets.
+ francinum:
+ - tweak: Admins will only show their admin badge, the mentor badge will be dropped
+ to reduce clutter.
+2021-08-16:
+ AnCopper:
+ - tweak: juggernauts cant be pulled.
+ - tweak: intercomms are above windows.
+ Ivniinvi:
+ - rscadd: Bulk Discount is now an option that can be rolled for low-value items
+ in the uplink.
+ PowerfulBacon:
+ - refactor: Circuit component printer is now a modular fabricator and uses the modular
+ fabricator UI.
+ francinum:
+ - balance: Frank has had their health returned, and their damage further nerfed
+ along with a slowdown
+ - tweak: Players can now opt out of "end of round grief" by toggling the, "Go to
+ the post-round arena" preference. The default is on.
+ ike709:
+ - rscdel: EORG Arena has been removed until it is rewritten to be less terrible
+ jupyterkat:
+ - bugfix: turrets should work correctly again
+2021-08-18:
+ ike709:
+ - rscadd: Auxmos has been updated to the latest version.
+2021-08-19:
+ Crossedfall:
+ - tweak: Updated the changelog header to include McTerra, PowerfulBacon, & Francinum
+ as major code contributors and Naevi as a spriter.
+ KubeRoot:
+ - code_imp: tgui tooltips now use Popper
+ - bugfix: Holoparasite stat selection now displays stats correctly
+ Rukofamicom:
+ - tweak: Reproductive spiders may no longer accidentally clear their directives,
+ and may not reproduce without one set.
+2021-08-20:
+ PowerfulBacon:
+ - bugfix: Fixes poll voting
+ jupyterkat:
+ - bugfix: another important recursive contents fix
+ mcmeiler:
+ - spellcheck: fixed a typo in "oozeling vacuole"
+2021-08-22:
+ EOBGames AnCopper:
+ - rscadd: rouny plush
+ EvilDragonfiend:
+ - bugfix: The naming component at the pill press(the plumbing machine) is now a
+ button instead of a text input, to prevent you to get a blank named pill(i.e.
+ " pill").
+ francinum:
+ - soundadd: New Station Announcer trait voice pack
+ lordScrubling:
+ - rscadd: layer manifolds have a 280L volume
+2021-08-23:
+ AffectedArc07:
+ - server: Added SSmetrics. This has config changes.
+2021-08-24:
+ Fox McCloud, Buggy123:
+ - bugfix: Tendrils no longer spawn inside ruins
+ - bugfix: Gibtonite will now spawn properly, as well as any other potential full
+ turf replacements for random ores
+ PowerfulBacon:
+ - bugfix: Fixes gibtonite spawning
+ francinum:
+ - tweak: Crew Manifest is now sorted with command at the top.
+ - tweak: Heads of Staff are listed in both the Command block and their relevant
+ department block of the crew manifest.
+ jupyterkat:
+ - rscadd: Gas mixes can be edited through VV
+2021-08-25:
+ Raven-Industries:
+ - tweak: reduced riot armor volume from 100 to 65
+2021-08-26:
+ Ivniinvi:
+ - rscadd: Examining the Blob Overmind (ghosts only) will now show how many nodes
+ it has.
+ Kapu1178:
+ - bugfix: I P C B R A I N S W O R K
+ SomeguyManperson AnCopper:
+ - tweak: Increased plasma to tritium rate in radiation collectors from 0.001 to
+ 0.01, this means that a standard plasma tank at 303 kPA (or 10 moles) will be
+ completely converted into tritium at just over 30 minutes, requiring it to be
+ replaced in order to generate electricity, fill the plasma tanks or make the
+ plasma colder to fit more into the tank.
+ ivanmixo:
+ - rscdel: removed the abductor pregnant objective
+ super12pl:
+ - rscadd: Phobia quirk
+2021-08-27:
+ DatBoiTim:
+ - bugfix: Advanced Mimery Gun now able to be fired multiple times without issues
+ - bugfix: You can now dispell the advanced mimery gun properly
+ - code_imp: Added a subtype of the finger gun that is lethal. This is used by advanced
+ mimery only.
+ - refactor: Advanced Mimery now does a finger gun variant instead of some weird
+ magical projectile stuff
+ EvilDragonfiend:
+ - imageadd: added job HUD icons for Exploration Crew, Psychiatrist, Barber, Debtor(almost
+ no change), Stage Magician, and VIP.
+ Ivniinvi:
+ - rscadd: The Captain now starts with a tablet in their backpack.
+ - tweak: 'The following items can no longer be found in surplus crates: His Grace,
+ Romerol, Syndicate Balloon.'
+ PowerfulBacon:
+ - tweak: Blocking now works on harm intent and no longer works on help intent.
+ - code_imp: Improves turret judgement code
+ jupyterkat:
+ - tweak: all wall-mounted machines now show up above windows
+ lordScrubling:
+ - bugfix: self surgery being instantly successful in poor conditions
+2021-08-28:
+ Ivniinvi:
+ - rscadd: Coffee the Crab is now present in BoxStation Science
+2021-08-29:
+ ike709:
+ - admin: Removed an admin privilege elevation exploit and other potential avenues
+ for abuse
+2021-08-30:
+ ImSynthex:
+ - tweak: Changed the corporate lawset
+ KubeRoot:
+ - bugfix: Solar control screen no longer floats off the console when facing east.
+ Solar control screen is now also emissive, like other computers.
+2021-08-31:
+ Ivniinvi:
+ - rscadd: The obviously fake nuke disk is now an arcade prize.
+ - rscadd: 'The round report in #ooc on Discord now includes a summary for Dynamic
+ Mode.'
+ - bugfix: 'Dynamic round report in #ooc no longer has extraneous html bold tags'
+ - tweak: The departmental budget's pre-initialize name has been changed.
+ KubeRoot:
+ - bugfix: Central Command has identified and fixed issues with pod launching interfaces.
+ - bugfix: Broken computers no longer glow in the dark
+ - rscadd: Seed Extractor and Plant DNA Manipulator now use tgui
+ - rscadd: Plant DNA Manipulator has a checkbox that skips confirmation prompt for
+ rapid botanying
+ Phil Smith:
+ - bugfix: The spriteless jumpsuit in sec maints in deltastation is now a real one
+ PowerfulBacon:
+ - bugfix: Fixes finger gun allowing free movement in space
+ - rscadd: Adds in AI buttons to move their camera to above and below connected z-levels.
+ - refactor: Sounds now play across multi-z station levels.
+ - refactor: Explosions now explode across multi-z station levels.
+ - bugfix: Fixes blood brothers admin add
+ ? PowerfulBacon (Code, UI, Other sprites not mentioned, Other map files not mentioned),
+ Reds88 (Vortex Gun Sprite), Isy (Damaged hallway map files)
+ : - rscadd: Adds in randomly generated ruins
+ - rscadd: Adds in dynamic z-levels.
+ - rscadd: Adds in ruin beacons on the shuttle supercruise map.
+ - code_imp: Adds z-clear
+ - code_imp: Ruin mapsize validation unit test
+ - code_imp: Fixes fastdmm2 not compiling on beestation code due to the updates.
+ - refactor: Budget cards no longer get free money.
+ - refactor: Bounties reward 3x as much, but the reward is distributed among station
+ budgets.
+ - refactor: Paychecks have been slightly reduced.
+ - refactor: Station budgets now start with more money to compensate early game
+ when no objectives or bounties have been completed.
+ - refactor: Research disks can be found on ruin stations. These can be used to
+ unlock special research.
+ - refactor: Bags of holding are now a separate research node unlocked by finding
+ disks on derelict stations.
+ - refactor: Wormhole projectors are now a separate research node unlocked by finding
+ disks on derelict stations.
+ - refactor: Quantum spin inverters are now a separate research node unlocked by
+ finding disks on derelict stations.
+ - refactor: Advanced combat cyber implants are now a separate research node unlocked
+ by finding disks on derelict stations. (Hydraulic armblades)
+ - refactor: Combat cybernetic implants implants are now a separate research node
+ unlocked by finding disks on derelict stations. (Xray, thermal vision, anti
+ stun implants, thrust implants)
+ - refactor: Clusterbangs are now a found research disk.
+ - refactor: Phazons are now a found research disk.
+ - refactor: Exotic ammo is now a found research disk.
+ - refactor: Beam rifles are now a found research disk.
+ - refactor: Temp gun and X-ray gun is now a found research disk.
+ - refactor: Nuclear energy gun is now a found research disk.
+ - rscadd: Completion station missions reward money to budget cards.
+ - rscadd: Adds in a new mining scanner effect.
+ - refactor: Refactors vendors to be modular
+ - rscadd: Adds discovery research
+ - rscadd: Adds discovery scanner
+ - balance: Felinids get stunned when falling z-levels for the same length of time
+ that normal humans get knocked down.
+ - refactor: You will no longer fall down if the turf below you is space.
+ - refactor: You can now ascend and descend connected z-levels by clicking on the
+ space tile.
+ - balance: Off-station traitor roles have a prefered chance (30%) to have their
+ team members as targets (Shaft miners and exploartion crew.). Additionally,
+ on station traitor roles have a reduced chance to have their target as an
+ off station roles.
+ - refactor: Turrets can now shoot above themselves, preventing the nukie shuttle
+ from being boardable from above.
+ - refactor: Bolt of teleportation will teleport people to open turfs unless none
+ are available.
+ - refactor: Immovable rod no longer loops.
+ - rscdel: Box station no longer has the crab.
+ francinum:
+ - admin: Bans now properly create their relevant notes.
+ - server: You should probably manually unexpire any note with an expiration date
+ of 0.
+ lordScrubling:
+ - rscadd: Ability to see a tag with the armor values of a piece of clothing when
+ you inspect it
+2021-09-01:
+ AnCopper:
+ - tweak: budget cards are indestructible.
+ EvilDragonfiend:
+ - bugfix: Exploration Crew has their HUD icon now.
+ - bugfix: Exploration Crew job can be given from the HoP console.
+ - rscadd: Job selection in the HoP Console is now tidily sorted by department.
+ - rscadd: When you grant a custom job "Acting Captain", now it gives you a better
+ blank HUD icon. (Captain HUD without star)
+ Ivniinvi:
+ - bugfix: RD now has exploration access
+ - rscadd: Battle Royale loot has had some loot buffs, most notably the spawn rate
+ of loot drops has doubled and the landing time of packages has been decreased.
+ PowerfulBacon:
+ - balance: Cogged APCs will now report 100% charge on power monitors.
+ - bugfix: Fixes corgstation disposals and bar disconnect from powergrid.
+ - bugfix: Fixes QM headset having exploration access.
+ - bugfix: Fixes custom shuttles being unable to dock.
+ - tweak: Entering the portal in the center of the shadow labrynth will return you
+ to a random anomalous artifact.
+2021-09-02:
+ DatBoiTim:
+ - tweak: Tweaked Mining Webbing to also allow storage of exploration gear due to
+ also being available to explorers
+ - tweak: More exploration gear fits into webbing
+ Inithis:
+ - tweak: added nitrile gloves to the Paramedic's default, roundstart loadout.
+ Ivniinvi:
+ - rscadd: You can now print multiple Wanted or Missing posters.
+ - bugfix: Clockwork cultists and Cogscarabs will no longer be stuck in an infinite
+ teleport loop.
+ Kapu1178:
+ - balance: IPCs are no longer deaf when EMP'd, and are now forced to speak spanish
+ for 2 minutes.
+ MNarath1:
+ - bugfix: fixes mobs not beeing able to hear instruments in lockers
+ PowerfulBacon:
+ - bugfix: Fixes reality destabilization auto-restabilization.
+ ivanmixo:
+ - bugfix: bone axe now properly switches in hand sprites
+ - tweak: wagging your tail is a visible emote again
+ kreeperHLC:
+ - bugfix: Equipment no longer disappears when you try to install it in the wrong
+ type of mecha.
+2021-09-03:
+ Ivniinvi:
+ - rscadd: Coffee the Crab is now present in BoxStation Science
+ - rscadd: Birdboat the Goose is now present in DeltaStation, MetaStation, CorgStation,
+ and PubbyStation maints
+ - rscadd: Lia the Carp is now present in the CorgStation HoS office
+ - rscadd: Cayenne the Carp is now present in the traitor deployable capsule shuttle
+ - rscadd: Tom the Turkey is the mascot of the Exploration Crew, and appears on the
+ three exploration shuttles.
+ - rscadd: Debtors and Assistants can now purchase a Bottle of Mystery Pills for
+ 3TC.
+ MNarath1:
+ - bugfix: fixes my own mistake on instruments
+ PowerfulBacon:
+ - bugfix: You can now upload to AIs on different station floors.
+ - bugfix: Fixes meta comms, will proper fix it tomorrow.
+ - bugfix: Runechat not displaying messages if you can't hear it rather tahn if they
+ can't hear it.
+ - tweak: Changes exploration mainframes to proper exploration communication setups
+ on pubby and metastation.
+ yyzsong:
+ - rscdel: Randomly generated names can no longer have skywalker as a last name
+2021-09-04:
+ Ivniinvi, TheChosenEvilOne:
+ - rscadd: Birdboat now has a chance to be controllable by dead chat, think twitch
+ plays pokemon but goose and deadchat.
+ - rscadd: Deadchat controlled singularity variant.
+ Ivniinvi, bobbahbrown:
+ - admin: Add Requests Manager to view all prayers/centcom and syndicate requests/and
+ nuke code requests within a round.
+ KubeRoot:
+ - bugfix: TGUI interfaces have been manually tweaked across the board to update
+ more consistently
+ - bugfix: Some potential exploits related to TGUI input validation/sanitisation
+ have been fixed
+ - bugfix: Nanite cloud control now shows rules even when you don't have a disk with
+ valid rules inserted
+ - bugfix: Extra settings for numbers in nanites now treat 0 as a valid number.
+ - bugfix: Cargo express console now doesn't add a second order to the cargo console
+ cart. As a side-effect, it also doesn't print a requisition form.
+ - bugfix: The holodeck computer will no longer break when reenabling safety
+ - tweak: Chemistry heater no longer stays on when removing beaker with alt-click
+ - code_imp: Added signals for machine_open and machine_close
+ - refactor: References to IRC now better describe TGS' and Discord's existence.
+ - admin: Players will now be better informed that messages are sent through TGS
+ to IRC/Discord/etc when attempting to adminhelp with no available admins/use
+ adminwho verb.
+ - admin: Initial ahelps are now multiline too.
+2021-09-05:
+ AnCopper:
+ - tweak: Communications consoles also work on the centcom z level
+ - rscadd: Added the AI lab room as a possible ruin room.
+ Crossedfall:
+ - config: 'The AI will have a random lawset at the start of each shift from the
+ following list: Asimov, asimov++, crewsimov, corporate, maintain.'
+ Fox-McCloud AnCopper:
+ - tweak: ashwalkers malf AI module has been moved to the syndicate lavaland base
+ vault.
+ - tweak: Heavily beefed up the security of the Syndie vault, added two more turrets
+ and 3 layer walls.
+ Froststahr:
+ - code_imp: Mapmerge2 has been updated along with an experimental .dmm merge driver
+ being included for conflict resolution.
+ KubeRoot:
+ - bugfix: Radios and intercoms can no longer be toggled from a distance by non-silicons
+ using hotkeys
+ - bugfix: Monkeys can now interact with wall-mounted intercoms
+ - bugfix: Radio and intercom UIs now update when toggled via hotkey
+ - rscadd: Bounty console now uses a tgui interface
+ - rscadd: There is a new "Bounty Hunter" program available for modular computers
+ and tablets
+ - admin: SDQL2 now supports all subsystems.
+ - bugfix: Alarm Monitoring program changed to avoid always-on unclearable fire alarms
+ - tweak: Hacking a firing pin out of a gun is no longer done via a crafting menu
+ - you can now do it by simply holding the gun in your hand and clicking it with
+ a welder/screwdriver/wirecutters
+ - bugfix: You are no longer prevented from selecting more neutral/negative quirks
+ once you have reached the limit of 6 positive ones.
+ - bugfix: Fixed a scenario that allowed infinite resource generation via ore machines.
+ - code_imp: TGUI interfaces updating from ui_act will not update again due to ui_update
+ 'LemonInTheDark, Port By Froststahr. ':
+ - admin: The panic bunker allows for a minimum time to be set, blocking players
+ who lack the required amount of living played time from accessing the game.
+ PowerfulBacon:
+ - balance: Removes the bolt of death and bolt of adminheal from xenobiology space
+ carps (Chaos + Regular)
+ - balance: Bag of holding's can no longer teleport their contents through walls
+ when dumped.
+ - bugfix: Fixes gamemode presetup being called late.
+ - bugfix: Fixes cult having a random chance to fail to setup between 24-28 players.
+ - balance: Cryo syringes no long have the no react flag.
+ - balance: Reagents inside cryo syringes will be at 20 kelvin.
+ - balance: Heavy weapons require two hands to hold.
+ - balance: Light/medium weapons will be less accurate when fired with 1 hand.
+ - refactor: Refactors meteors, they are now orbital map components and will fly
+ towards the station, colliding with orbital bodies and flying shuttles.
+ francinum:
+ - balance: Ore Silo connections can no longer cross z-level boundaries, and will
+ break if the object changes z-lvels.
+ ivanmixo:
+ - refactor: all syndicate boxes now look like syndicate boxes
+ - refactor: replaced some of the more egregious spawn()s with timers
+ - rscdel: removed the separated chemicals trait from botany
+ lordScrubling:
+ - balance: having x-ray makes you ignore flash protection and tint
+ - balance: flashbangs can flash people with x-ray through walls
+ - rscadd: syndicate autosurgeons get a special variant of eyes that allow flash
+ protection
+ lordScrubling, Mothblocks, Rohesie:
+ - rscadd: placing fireman carried people onto things
+ qwertyquerty:
+ - rscadd: Nuclear countdown music
+ tiramisuapimancer:
+ - bugfix: IPC hands are now properly visible over jumpsuits on side states
+2021-09-06:
+ Ivniinvi:
+ - tweak: 'Moths can no longer consume the following items: Indestructible items,
+ Items with Melee armor, Space Suits, Hardsuits.'
+ - rscdel: The exploration nuke no longer plays alarm.ogg
+ KubeRoot:
+ - bugfix: Geysers no longer generate in ruins. In fact, nothing should generate
+ in ruins, as ruins now generate before other things.
+ - tweak: Due to changes in the way the map is setup, floors that are parts of ruins
+ on lavaland loaded at roundstart will now deconstruct into lava instead of basalt
+ floors.
+ - code_imp: When a template is loaded with a passthrough turf, non-passthrough area
+ on it, onto a genturf, the genturf is now forced to generate with the old area.
+ This allows ruins to have objects in a powered area on a passthrough turf on
+ lavaland.
+ - code_imp: Passthrough turfs can now hint towards if the turf below should be closed/open
+ (wall/floor). This can be useful in ruins, as it can influence map generation
+ without overriding the exact turf placed underneath.
+ - imageadd: Sprites for passthrough turfs with closed/open preferrence (visible
+ in editor only)
+ PowerfulBacon:
+ - code_imp: Improves the supercruise collision detection by optimising it and making
+ it so passing something triggers a collision event.
+ - code_imp: SSorbits now supports multiple orbital maps.
+ Tavczan:
+ - rscadd: Abandoned crate tamper-proofing now detects EMPs.
+ The-Moon-Itself:
+ - rscadd: Bitwise operations and Trigonometry related circuit components, unlocked
+ in a new techweb node.
+ - rscadd: Rounding circuit component.
+ - tweak: Arithmetic circuit now has a modulus option.
+ - tweak: New abstract circuit to base components that initialize with an arbitrary
+ amount of inputs.
+ ivanmixo:
+ - balance: chaplain's soulstone can no longer shard bodies with souls
+ jupyterkat:
+ - bugfix: fixed mech radios
+2021-09-07:
+ Ivniinvi:
+ - bugfix: Ghosts no longer get spammed with It's Loose notifications when there's
+ no singulo or tesla.
+ fighterslam:
+ - bugfix: Fixed the syndicate lavaland base to have the proper frequencies to be
+ compatible with Supercruise.
+ qwertyquerty:
+ - rscadd: Added new space ambient music
+ - bugfix: fixed ambient music and buzz
+2021-09-08:
+ KubeRoot:
+ - bugfix: Silicons can once again interact with intercoms
+ - bugfix: Button.Input no longer breaks tgui interfaces. Fixes advanced injector
+ creation interface, as well as NTOS file manager, chat, ID card modification
+ and Revelation.
+ rkvothe14:
+ - config: decreased the font size cap from 32 to 24 to prevent chat bugs.
+2021-09-09:
+ fighterslam:
+ - rscadd: Adds Corporate Sofas, an alternative, sleeker style of sofa.
+2021-09-10:
+ AnCopper:
+ - tweak: ERT Commanders get an Omni Door remote.
+ - rscadd: added microwaves, donk pockets, and oxygen tank dispensers to exploration
+ shuttles
+ - tweak: the BSA is indestructible.
+ AnturK, Ivniinvi, Inept, Coiax, AdipemDragon, YakumoChen, Sheits, tralezab:
+ - rscadd: Aquarium and aquarium accessories. Also fish. Check out cargo to start.
+ - rscadd: Aquarium fish now reproduce.
+ DatBoiTim:
+ - rscadd: Sleep and Drowsyness Immunity To Meth
+ - balance: Decreased Speed Buff from Meth
+ - balance: Increased Stamina Damage Reduction
+ Goshagosha:
+ - rscadd: Added structure (plant) 'Strange plant' to code/modules/mining/lavaland/ash_flora.dm
+ - rscadd: Added boolean flag destroy_on_harvest (FALSE) to plants in ash_flora.
+ If it is on, plants will not regrow.
+ - rscdel: Strange seeds removed from biogenerator
+ - imageadd: xpod[1-4] added to icons/obj/lavaland/ash_flora.dmi (2,3,4 are placeholders
+ for better art variety)
+ ImSynthex:
+ - tweak: You can now throw stuff at movables with no density, such as mobs on the
+ floor, mice, APCs etc etc
+ - refactor: Refactored thrownthing datum
+ Ivniinvi:
+ - admin: Updated some logging, added some logging.
+ - admin: Admins can now force people to stub their toes.
+ - admin: Fixes the FLW link in some logging.
+ PowerfulBacon:
+ - code_imp: Replaces get_hearers_in_view with range for musical instruments.
+ - tweak: Musical instrument range reduced from 15 to 11
+ - balance: Flashbangs now blind with an intensity of 2 meaning wearing glasses alone
+ will still blind you. (You will not get stunned while wearing glasses).
+ - balance: Flashbangs effect is directly proportional to the distance away.
+ - bugfix: Fixes docking with lavaland.
+ Qwertytoforty AnCopper:
+ - tweak: Telekinesis can no longer throw objects with people in them, or buckled
+ to them.
+ ike709:
+ - bugfix: Custom emotes work again.
+ ivanmixo:
+ - balance: cyborg *spinning someone now knocks down instead of paralyzing
+2021-09-11:
+ francinum:
+ - server: You should NOT be granting this lightly.
+2021-09-12:
+ Archanial:
+ - refactor: removed static usage of typecache in get_area()
+ PowerfulBacon:
+ - bugfix: Fixes meteors causing bugs in SSorbits.
+ francinum:
+ - bugfix: Mechs can now properly recalibrate their controls.
+2021-09-13:
+ Bokkiewokkie:
+ - admin: Fixed a security issue with the advanced proccall
+ PowerfulBacon:
+ - tweak: Custom shuttles and rooms can be made on asteriods.
+ - bugfix: Fixes another case of meteors moving invalidly.
+ - refactor: Refactors the tesla to be less intensive on the server. (Slightly weakening
+ its destructive capability)
+ - admin: The MC panel now shows tick uage and the tick limit.
+ Vexylius:
+ - rscadd: Janitorial toolset implant
+ - rscadd: Botanical toolset implant
+ ike709:
+ - code_imp: BYOND infinite loop detection is now documented.
+2021-09-14:
+ Pirill:
+ - imageadd: New sprites for geraniums, lilies and rainbow flowers
+ - rscadd: Forget-me-nots as a new flower type
+ - rscadd: Craftable flower crowns from rainbow flowers, sunflowers, poppies and
+ lilies
+ - rscadd: Two new botany cargo bounties for forget-me-nots and rainbow flower crowns
+ - bugfix: Fixes rainbow flower reagent code
+ PowerfulBacon:
+ - balance: All golems excluding bone golems are transsting immune.
+ - balance: Levelling up projectile spells (Fireball, spellcards, tesla spell, magic
+ missile) makes the projectiles more powerful.
+ - balance: Upgrading repulse makes the effect stronger (larger throw distance, longer
+ paralyze)
+ - tweak: Altered the charlie station ruin outpost to add an ORM, KA, Autolathe and
+ Ore Silo. Removed the exosuit fab having access to station mats. Added a bunch
+ of materials around and a shuttle creator.
+ - tweak: Traitor capsule shuttle gets a plasma refinery and an emergency pickaxe.
+ - bugfix: Trait induced blindness will no longer cause your screen to be blurry
+ forever if you get blurred.
+ lordScrubling:
+ - rscadd: Added hotkey to toggle walking/running
+ - rscadd: Ability to rebind locking movement hotkey
+ qwertyquerty, Merct:
+ - rscadd: Soundtrack music shows in credits
+2021-09-15:
+ Ivniinvi:
+ - admin: Battle Royale requires +ADMIN instead of +FUN after the round ends.
+ Kapu1178:
+ - rscadd: Humans now display a chat bubble when typing, as an effort to move us
+ closer to Baystation
+ - tweak: Typing Indicators are now on the right instead of the left, to not be covered
+ by medhuds.
+ - tweak: Say hotkey is probably more responsive
+ PowerfulBacon:
+ - bugfix: Resolves a crash exploit related to z-levels and supercruise.
+ - tweak: Thrown humans can now be caught.
+ TheFakeElon:
+ - code_imp: improves fail2topic regex
+2021-09-16:
+ ike709:
+ - server: The DB subsystem will now always disconnect properly.
+2021-09-17:
+ KubeRoot, Mothblocks:
+ - code_imp: Tooltips in tgui are now very fast, even with a lot of them on one interface.
+2021-09-19:
+ Therosass:
+ - bugfix: Warping grey runes no longer create invisible slimes.
+2021-09-23:
+ Autisem:
+ - bugfix: Mines explode once instead of too much
+ heepox:
+ - rscadd: Adds a ClothesMate Refill canister to cargo for 800 credits.
+2021-09-25:
+ Raven-Industries:
+ - tweak: increased book title limit from 20 chars to 50
+2021-09-26:
+ KubeRoot:
+ - bugfix: The metastation external airlock in holodeck maintenance now sets up correctly
+ roundstart
+2021-09-27:
+ Archanial:
+ - bugfix: Fixed join via pod trait sending people to hyperspace
+ ike709:
+ - tweak: 514.1568 is now required for playing and compiling
+2021-09-29:
+ AnCopper:
+ - tweak: The projector can not be researched.
+2021-09-30:
+ Ivniinvi:
+ - tweak: Roundstart tips are up-to-date.
+ KubeRoot:
+ - tweak: The "Say" window no longer has input lag when opening it with the hotkey.
+ - tweak: The "Say" window now has input lag when closing it with enter.
+2021-10-01:
+ AnCopper:
+ - tweak: You can no longer drag blobbernauts.
+ Archanial:
+ - bugfix: fixed runtime related to missing client in mentorhelp
+ Crossedfall:
+ - bugfix: Resolves a bad index runtime caused by the stat panel's verb caching
+ Ivniinvi:
+ - spellcheck: Fixed artifact supercruise message.
+ - bugfix: Alkali Perspiration now correctly updates its severity.
+ - bugfix: The debug uplinks now actually use the debug toggle.
+ KubeRoot:
+ - rscadd: Space hotel now has a secondary solar control console that can reach the
+ north-east solar array.
+ - bugfix: Space hotel has had some missing and miscolored cables tidied up.
+ checkraisefold:
+ - spellcheck: Changed flavor text to text in Notes for VIPs from the VIP extraction
+ exploration mission.
+ fighterslam:
+ - bugfix: Fixed DeltaStation holodeck.
+ lordScrubling:
+ - rscadd: throwing creatures doesn't make blood trails anymore
+ qwertyquerty:
+ - server: Report round id as int on ?status
+2021-10-02:
+ Archanial:
+ - tweak: Station trait command report is now generated independly, that means it
+ will be printed even if intercept isn't enabled.
+ - bugfix: fixed lavaland turret control runtime
+2021-10-03:
+ DrMacCool:
+ - rscadd: Added exploration envirosuit+helmet.
+ - rscadd: Plasmamen on the exploration crew now spawn in said clothes.
+ KubeRoot:
+ - bugfix: Newly created chat tabs will now show important messages. If you're using
+ custom-created chat tabs, you'll need to delete and recreate them to be able
+ to see them. This fixes being unable to see if somebody is overdosing when doing
+ chemical scans with the health analyzer.
+ St0rmC4st3r:
+ - bugfix: Shuttles will no longer refuse to lift off due to counting all other shuttle's
+ weight as their own.
+2021-10-04:
+ Archanial:
+ - rscdel: Removed deathrattle station trait
+ Kapu1178:
+ - code_imp: Sprite sheets! A new system to allow for better species in the future.
+ KubeRoot:
+ - bugfix: Fixes an issue that would sometimes lead to increased view range while
+ not ghosted
+ - bugfix: View range is now actually fixed, sorry for the trouble.
+2021-10-05:
+ DrMacCool:
+ - rscadd: Adds the 'NOSOCKS' trait.
+ - tweak: Species with NOSOCKS can't wear socks.
+ - tweak: Squids now have the 'NOSOCKS' trait.
+ Ivniinvi:
+ - rscadd: There is now a brain trauma that allows ghosts to issue a movement command
+ once every 12 seconds.
+ - admin: Deadchat_control is now a smite option.
+2021-10-06:
+ KubeRoot, EdgeLordExe:
+ - rscadd: Radios now have code support for anonymization
+ - tweak: The intercoms in chapel confessionals will now hide your name
+ Pirill:
+ - spellcheck: Fixed a few typos related to exploration, wall building, wraith spectacles
+ and Battle Royale
+ rkvothe14:
+ - bugfix: fixes Nightmare's lighteater so they can destroy light sources again.
+2021-10-09:
+ Archanial:
+ - bugfix: fixed hud runtime
+2021-10-10:
+ KubeRoot:
+ - bugfix: The exploration detonator now correctly checks if you're on the station
+ Z level for its safety. You can also no longer detonate explosives across Z
+ levels.
+ - bugfix: Artifacts from supercruise often didn't work, they should be much more
+ interesting now.
+2021-10-11:
+ Archanial:
+ - rscdel: Removed hangover landmarks
+ - tweak: Hangover now dynamically places its spawns around the station
+ - bugfix: Fixed random_safe_station_turf sometimes returning null even if it had
+ viable turf
+ Dejaku51:
+ - bugfix: Fixed camera name and network in Incenerator on Meta and Box
+ Ivniinvi:
+ - admin: Traitor panel will now show if the person in question is banned from a
+ specific antagonist.
+ TheGreyDiamond:
+ - bugfix: fixed zero padding mistake
+2021-10-15:
+ DrMacCool:
+ - rscadd: Adds 18 new hairstyles/beards requested by the community.
+2021-10-18:
+ TheChaser212:
+ - rscadd: Electrical species use power stored in their stomachs rather than normal
+ nutrition
+ - rscadd: Ethereals recharge continuously and lose nutrition in the same ways as
+ other species
+ - rscadd: EMPing electrical species will drain their charge
+ - rscadd: IPC powercords and batteries work in ethereals
+ - tweak: Replaced old ethereal charge system
+ - rscdel: Removed the ability to insert brains without surgery
+ - tweak: IPCs use a battery icon instead of hunger
+ - bugfix: Fixed ethereals not gaining charge from shocks
+ - bugfix: Fixed some problems with the biometallic replication symptom
+ - bugfix: Lots of other small things
+ lordScrubling, Ghilker, TetraK1, Dennok, Donkie:
+ - rscadd: Temperature pump atmos device from tg
+ - rscadd: Pressure valve from atmos device tg
+ - rscadd: Temperature gate atmos device from tg
+ - tweak: Passive gate pressure limit upped to 100 atmospheres
+2021-10-19:
+ Ivniinvi:
+ - admin: Added ratvarsmall and narsiesmaller span classes.
+ francinum:
+ - code_imp: A few unused globals were cleaned up.
+2021-10-20:
+ tiramisuapimancer:
+ - balance: nuclear particles no longer deal direct toxin damage, but do more regular
+ irradiating
+2021-10-21:
+ Archanial:
+ - bugfix: fixed necroseed runtime
+ - refactor: symptoms on death is now triggered by disease itself using signals
+ - refactor: action speed modifier refactored into modifier system, similar to movespeed.
+ Dingo-Dongler:
+ - rscadd: Clipboards and notice boards are now updated to TGUI, with clipboards
+ having much better functionality.
+ Hardly:
+ - rscadd: Shuttle seat is now craftable using two sheets of iron
+ Kapu1178:
+ - code_imp: Cleaned up IPC code in general.
+ - bugfix: VV health adjustment works on synthetics
+ - bugfix: IPCs now actually have blood, oil, and it leaves decals when they are
+ hurt
+ - bugfix: IPCs are now actually immune to toxin damage.
+ - bugfix: IPCs wont process reagents they weren't supposed to process.
+ - refactor: Mild refactor to species metabolisms.
+ Pirill:
+ - imageadd: Adds printed toolbox right handed in-hand sprites
+ Ryll-Ryll, jupyterkat, ninjanomnom:
+ - rscadd: Dogs now have all-new AI! Go throw a grenade at Ian and see what happens!
+ - rscadd: Added AI datum support for complex JPS pathing
+ - refactor: Implements JPS Pathfinding
+ - bugfix: janky monkey ais
+ - bugfix: Monkeys are once again capable of using guns, take back your freedom!
+ - rscadd: Monkey ai is now actually able to make use of guns instead of just player
+ controlled monkeys.
+ - rscadd: Monkey ai will prioritize guns once they learn of what guns can do.
+ - rscadd: Monkey ai now understands the ancient art of throwing.
+ - rscadd: Monkey ai will use random objects on each other in their surroundings
+ and in their hands.
+ - rscadd: Monkey ai will occasionally "gift" people things.
+ - rscadd: Monkey ai consumes food once in a while according to their desire.
+ TheChaser212:
+ - bugfix: IPCs use charge at the same rate they used to
+ - bugfix: Ethereals can't queue up multiple chargings
+ - tweak: Ethereals don't need to wait if they can't charge
+ - tweak: APCs can be drained to a quarter charge
+ francinum:
+ - bugfix: Exosuit fabricators no longer automatically link to the default silo network
+ on build.
+ jupyterkat:
+ - bugfix: fixed ruin turfs get vented sometimes
+ silicons, Ivniinvi:
+ - rscadd: Baystation instruments are here!
+ - refactor: Existing instruments have been rolled into the new unified instruments
+ system.
+ - refactor: A sound subsystem has been added, SSsounds. Use it for persistent channel
+ management.
+ zeskorion:
+ - rscadd: random diseases now have better random names
+2021-10-22:
+ Autisem:
+ - bugfix: super soldiers get hair
+ 'Ivniinvi, timothymtorres, Azarak, mickyan, ':
+ - bugfix: Fixed pAI loudness booster to work when not in holoform
+ - bugfix: Fixed garbage collection for songs, making the game slightly faster
+ - tweak: You may now change the sound of certain instruments with a selection of
+ appropriate variations, similarly to the piano synth
+ - bugfix: Fixed accordion not working
+ - rscadd: Adds a hidden fun instrument
+ Pirill, Naevii, nianjiilical, Useroth:
+ - rscadd: Adds cherry bulbs and fairygrass as new mutations of cherries and grass
+ - rscadd: Adds pink, yellow, green, blue and purple bioluminescence traits to cherry
+ bulbs, holymelons, omega weed, fairygrass and moonflowers
+ - rscadd: Adds colored fairygrass tiles based on their bioluminescence trait
+ - imageadd: New grass and fairygrass sprites
+ Pirill, kit-katz:
+ - rscadd: 'Adds three new pAI holochassis: carp, bee and phantom!'
+ - imageadd: Adds inhands and head-worn sprites for all other pAI chassis that were
+ missing them
+ zeskorion:
+ - bugfix: fixes disease names
+2021-10-23:
+ Archanial:
+ - bugfix: fixed incursion implant not exploding (thanks pricklytomato!)
+ Crossedfall:
+ - bugfix: Removed the extra light switch from Box Station's research lab
+ - bugfix: Re-added the access requirement to Pubby's RD office
+ Skoglol:
+ - admin: Added a few new buildmode modes. Outfit, to quickly apply or remove outfits.
+ Delete, to quickly delete any atoms. Slightly overhauled the copy proc used
+ here and in supply pods, now compatible with most mobs.
+2021-10-25:
+ DeltaFire15:
+ - bugfix: Cyborg B.o.r.i.s. installation now checks for if the chest has a cell,
+ just like how it does with MMIs.
+ Ivniinvi:
+ - bugfix: Emitters that don't use power won't warn about powernets.
+ Ivniinvi, Denton, ArcaneMusic, XRandomXManX:
+ - rscadd: The Decal Painter is now available at protolathes and autolathes for you
+ to paint decals.
+ Kapu1178:
+ - code_imp: Cleans up species outfit code.
+ KubeRoot:
+ - bugfix: Wizarditis now functions correctly at high stages.
+ - bugfix: Resetting the thunderdome no longer breaks it.
+ - bugfix: The viral extrapolator can no longer be used at a distance.
+ Phil Smith:
+ - bugfix: When offering a renaming potion to someone it now no longer says that
+ you are giving the potion to yourself but it now properly gives the name of
+ the person you are handing it to.
+ Pirill:
+ - imageadd: New sprites and inhands for radiation and combat medkits, tweaked sprites
+ for the biohazard toxin medkit
+ - rscadd: Makes regular prescription glasses printable at a Medical Protolathe
+ - rscadd: Adds craftable prescription versions of medical, security and diagnostic
+ HUDglasses, science goggles and meson scanners
+ - tweak: Makes the Blood Cult Zealot's Blindfold correct nearsightedness when in
+ use
+ - imageadd: Resprites the original prescription medical HUDglasses and adds equivalent
+ sprites for the rest
+ The-Moon-Itself:
+ - bugfix: The chameleon projector now projects objects with overlays, such as computers
+ and holosigns, correctly.
+ TheChaser212:
+ - admin: Added some useful items to the debug outfit
+ jupyterkat:
+ - bugfix: fixed layer manifold runtime
+ zeskorion:
+ - bugfix: slime pylons no longer kill toxinlovers/slimepeople
+2021-10-26:
+ Kapu1178:
+ - rscdel: Kills the "Synth" species
+ tralezab, AnturK:
+ - rscadd: curators can now print paintings
+ - rscadd: persistent paintings
+ - rscadd: AIs can now pick paintings as their screens
+ - rscadd: tgui canvases
+2021-10-27:
+ FriendlyContractor:
+ - tweak: Ash heretics immune to being set on fire
+ Kapu1178:
+ - tweak: IPC blood trails are black now
+ KubeRoot:
+ - bugfix: Boxstation's turbine and toxins burn chamber airlocks will now pressurise
+ and depressurise correctly, no longer softlocking you.
+ MNarath1:
+ - bugfix: small misstype from tesla code
+ PowerfulBacon:
+ - rscadd: Syndicate listening outpost ruin part.
+ Syrox25:
+ - rscadd: Adds Paper Cup recipe to the Misc tab
+ The-Moon-Itself:
+ - rscadd: More list related WireMod components.
+ - tweak: The index component now has more features.
+ - tweak: The component printer now has a list component category
+ TheChaser212:
+ - rscadd: Syndicate implants are now hidden from health scans
+ - imageadd: Added inhand icons for the energy saw
+2021-10-28:
+ Ivniinvi:
+ - bugfix: Toxins will no longer bomb the MetaStation Exploration Shuttle at roundstart.
+ Phil Smith:
+ - tweak: A small commemoration to our former LRP server, golden, is now on the captain's
+ laser gun.
+ Pirill:
+ - bugfix: Fixes fairygrass shuttle flight runtime
+ francinum:
+ - config: Brig timer presets and max durations can be modified in the config
+ - config: The economy flag hasn't done anything for over a year, and has been removed.
+ jupyterkat:
+ - tweak: body only records are now clearer
+ - bugfix: body only records persist after cloning
+2021-10-29:
+ Ivniinvi:
+ - balance: Unintentional spins and flips no longer cause confusion.
+ TheChaser212:
+ - tweak: Cyborg mesons work similarly to human mesons by showing dark areas
+ rkvothe14:
+ - spellcheck: Fixed a minor typo in Show/Hide GhostLaws.
+2021-10-30:
+ TheChaser212:
+ - bugfix: Backup objectives shouldn't disappear anymore
+ - code_imp: Cleaned up cryopod objective handling
diff --git a/html/create_object.html b/html/create_object.html
index 8464363fe752b..2f628985dc678 100644
--- a/html/create_object.html
+++ b/html/create_object.html
@@ -1,110 +1,110 @@
-
-
-
- Create Object
-
-
-
-
-
-
-
-
-
-
+
+
+
+ Create Object
+
+
+
+
+
+
+
+
+
+
diff --git a/html/dna_discovered.gif b/html/dna_discovered.gif
new file mode 100644
index 0000000000000..bc6b75f2f984f
Binary files /dev/null and b/html/dna_discovered.gif differ
diff --git a/html/dna_discovered.png b/html/dna_discovered.png
deleted file mode 100644
index fbe3b457f3e1f..0000000000000
Binary files a/html/dna_discovered.png and /dev/null differ
diff --git a/html/dna_extra.gif b/html/dna_extra.gif
new file mode 100644
index 0000000000000..c92218a661337
Binary files /dev/null and b/html/dna_extra.gif differ
diff --git a/html/dna_extra.png b/html/dna_extra.png
deleted file mode 100644
index 22648ad3be862..0000000000000
Binary files a/html/dna_extra.png and /dev/null differ
diff --git a/html/dna_undiscovered.gif b/html/dna_undiscovered.gif
new file mode 100644
index 0000000000000..a3c182a4a1c4d
Binary files /dev/null and b/html/dna_undiscovered.gif differ
diff --git a/html/dna_undiscovered.png b/html/dna_undiscovered.png
deleted file mode 100644
index 2313880f8cfdd..0000000000000
Binary files a/html/dna_undiscovered.png and /dev/null differ
diff --git a/html/templates/header.html b/html/templates/header.html
index 883724d9aee5c..1fd7edec1f117 100644
--- a/html/templates/header.html
+++ b/html/templates/header.html
@@ -36,8 +36,8 @@